(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

View File

@@ -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>

View File

@@ -9,7 +9,7 @@ namespace Barotrauma
public void Draw(SpriteBatch spriteBatch)
{
if (!ShowAITargets) return;
if (!ShowAITargets) { return; }
var pos = new Vector2(WorldPosition.X, -WorldPosition.Y);
if (soundRange > 0.0f)
{
@@ -43,7 +43,7 @@ namespace Barotrauma
}
else
{
color = Color.WhiteSmoke;
//color = Color.WhiteSmoke;
// disable the indicators for structures, because they clutter the debug view
return;
}

View File

@@ -17,7 +17,7 @@ namespace Barotrauma
if (State == AIState.Idle && PreviousState == AIState.Attack)
{
var target = _selectedAiTarget ?? _lastAiTarget;
if (target != null)
if (target != null && target.Entity != null)
{
var memory = GetTargetMemory(target);
Vector2 targetPos = memory.Location;

View File

@@ -16,11 +16,6 @@ namespace Barotrauma
}*/
}
partial void SetOrderProjSpecific(Order order, string option)
{
GameMain.GameSession.CrewManager.DisplayCharacterOrder(Character, order, option);
}
public override void DebugDraw(Microsoft.Xna.Framework.Graphics.SpriteBatch spriteBatch)
{
Vector2 pos = Character.WorldPosition;
@@ -40,7 +35,7 @@ namespace Barotrauma
var currentOrder = ObjectiveManager.CurrentOrder;
if (currentOrder != null)
{
GUI.DrawString(spriteBatch, pos + textOffset + new Vector2(0, 20), $"ORDER: {currentOrder.DebugTag} ({currentOrder.GetPriority().FormatZeroDecimal()})", Color.White, Color.Black);
GUI.DrawString(spriteBatch, pos + textOffset + new Vector2(0, 20), $"ORDER: {currentOrder.DebugTag} ({currentOrder.Priority.FormatZeroDecimal()})", Color.White, Color.Black);
}
else if (ObjectiveManager.WaitTimer > 0)
{
@@ -51,17 +46,17 @@ namespace Barotrauma
{
if (currentOrder == null)
{
GUI.DrawString(spriteBatch, pos + textOffset + new Vector2(0, 20), $"MAIN OBJECTIVE: {currentObjective.DebugTag} ({currentObjective.GetPriority().FormatZeroDecimal()})", Color.White, Color.Black);
GUI.DrawString(spriteBatch, pos + textOffset + new Vector2(0, 20), $"MAIN OBJECTIVE: {currentObjective.DebugTag} ({currentObjective.Priority.FormatZeroDecimal()})", Color.White, Color.Black);
}
var subObjective = currentObjective.SubObjectives.FirstOrDefault();
var subObjective = currentObjective.CurrentSubObjective;
if (subObjective != null)
{
GUI.DrawString(spriteBatch, pos + textOffset + new Vector2(0, 40), $"SUBOBJECTIVE: {subObjective.DebugTag} ({subObjective.GetPriority().FormatZeroDecimal()})", Color.White, Color.Black);
GUI.DrawString(spriteBatch, pos + textOffset + new Vector2(0, 40), $"SUBOBJECTIVE: {subObjective.DebugTag} ({subObjective.Priority.FormatZeroDecimal()})", Color.White, Color.Black);
}
var activeObjective = ObjectiveManager.GetActiveObjective();
if (activeObjective != null)
{
GUI.DrawString(spriteBatch, pos + textOffset + new Vector2(0, 60), $"ACTIVE OBJECTIVE: {activeObjective.DebugTag} ({activeObjective.GetPriority().FormatZeroDecimal()})", Color.White, Color.Black);
GUI.DrawString(spriteBatch, pos + textOffset + new Vector2(0, 60), $"ACTIVE OBJECTIVE: {activeObjective.DebugTag} ({activeObjective.Priority.FormatZeroDecimal()})", Color.White, Color.Black);
}
}
}

View File

@@ -638,8 +638,7 @@ namespace Barotrauma
public void Draw(SpriteBatch spriteBatch, Camera cam)
{
if (!Enabled) return;
if (!Enabled) { return; }
AnimController.Draw(spriteBatch, cam);
}
@@ -656,8 +655,6 @@ namespace Barotrauma
if (GameMain.DebugDraw)
{
AnimController.DebugDraw(spriteBatch);
if (aiTarget != null) aiTarget.Draw(spriteBatch);
}
if (GUI.DisableHUD) return;

View File

@@ -92,7 +92,7 @@ namespace Barotrauma
if (!character.IsUnconscious && character.Stun <= 0.0f)
{
if (character.Info != null && !character.ShouldLockHud())
if (character.Info != null && !character.ShouldLockHud() && character.SelectedCharacter == null)
{
bool mouseOnPortrait = HUDLayoutSettings.BottomRightInfoArea.Contains(PlayerInput.MousePosition) && GUI.MouseOn == null;
if (mouseOnPortrait && PlayerInput.PrimaryMouseButtonClicked())

View File

@@ -8,26 +8,25 @@ namespace Barotrauma
{
partial class AfflictionHusk : Affliction
{
partial void UpdateMessages(float prevStrength, Character character)
partial void UpdateMessages()
{
if (Strength < Prefab.MaxStrength * 0.5f)
switch (State)
{
if (prevStrength % 10.0f > 0.05f && Strength % 10.0f < 0.05f)
{
case InfectionState.Dormant:
GUI.AddMessage(TextManager.Get("HuskDormant"), GUI.Style.Red);
}
}
else if (Strength < Prefab.MaxStrength)
{
if (state == InfectionState.Dormant && Character.Controlled == character)
{
break;
case InfectionState.Transition:
GUI.AddMessage(TextManager.Get("HuskCantSpeak"), GUI.Style.Red);
}
}
else if (state != InfectionState.Active && Character.Controlled == character)
{
GUI.AddMessage(TextManager.GetWithVariable("HuskActivate", "[Attack]", GameMain.Config.KeyBindText(InputType.Attack)),
GUI.Style.Red);
break;
case InfectionState.Active:
if (character.Params.UseHuskAppendage)
{
GUI.AddMessage(TextManager.GetWithVariable("HuskActivate", "[Attack]", GameMain.Config.KeyBindText(InputType.Attack)), GUI.Style.Red);
}
break;
case InfectionState.Final:
default:
break;
}
}
}

View File

@@ -132,7 +132,7 @@ namespace Barotrauma
private List<HeartratePosition> heartratePositions;
private float currentHeartrateTime;
private float heartbeatTimer;
private Texture2D heartrateFade;
private static Texture2D heartrateFade;
private readonly HeartratePosition[] heartbeatPattern =
{
@@ -478,7 +478,7 @@ namespace Barotrauma
cprFrame = new GUIFrame(new RectTransform(new Vector2(0.7f, 1.0f), cprLayout.RectTransform), style: "GUIFrameListBox");
heartrateFade = TextureLoader.FromFile("Content/UI/Health/HeartrateFade.png");
heartrateFade ??= TextureLoader.FromFile("Content/UI/Health/HeartrateFade.png");
new GUICustomComponent(new RectTransform(Vector2.One * 0.95f, cprFrame.RectTransform, Anchor.Center), DrawHeartrate, UpdateHeartrate);
@@ -816,9 +816,7 @@ namespace Barotrauma
if (highlightedLimbIndex < 0 && selectedLimbIndex < 0)
{
// If no limb is selected or highlighted, select the one with the most critical afflictions.
var affliction = GetAllAfflictions(a => a.Prefab.IndicatorLimb != LimbType.None)
.OrderByDescending(a => a.DamagePerSecond)
.ThenByDescending(a => a.Strength).FirstOrDefault();
var affliction = SortAfflictionsBySeverity(GetAllAfflictions(a => a.Prefab.IndicatorLimb != LimbType.None)).FirstOrDefault();
if (affliction.DamagePerSecond > 0 || affliction.Strength > 0)
{
var limbHealth = GetMatchingLimbHealth(affliction);
@@ -1185,7 +1183,8 @@ namespace Barotrauma
Dictionary<string, float> treatmentSuitability = new Dictionary<string, float>();
GetSuitableTreatments(treatmentSuitability, normalize: true, randomization: randomVariance);
Affliction mostSevereAffliction = afflictions.FirstOrDefault(a => !a.Prefab.IsBuff && !afflictions.Any(a2 => !a2.Prefab.IsBuff && a2.Strength > a.Strength)) ?? afflictions.FirstOrDefault();
//Affliction mostSevereAffliction = afflictions.FirstOrDefault(a => !a.Prefab.IsBuff && !afflictions.Any(a2 => !a2.Prefab.IsBuff && a2.Strength > a.Strength)) ?? afflictions.FirstOrDefault();
Affliction mostSevereAffliction = SortAfflictionsBySeverity(afflictions).FirstOrDefault();
GUIButton buttonToSelect = null;
foreach (Affliction affliction in afflictions)
@@ -1811,8 +1810,8 @@ namespace Barotrauma
float iconScale = 0.25f * scale;
Vector2 iconPos = highlightArea.Center.ToVector2();
Affliction mostSevereAffliction = thisAfflictions.FirstOrDefault(a => !a.Prefab.IsBuff && !thisAfflictions.Any(a2 => !a2.Prefab.IsBuff && a2.Strength > a.Strength)) ?? thisAfflictions.FirstOrDefault();
//Affliction mostSevereAffliction = thisAfflictions.FirstOrDefault(a => !a.Prefab.IsBuff && !thisAfflictions.Any(a2 => !a2.Prefab.IsBuff && a2.Strength > a.Strength)) ?? thisAfflictions.FirstOrDefault();
Affliction mostSevereAffliction = SortAfflictionsBySeverity(thisAfflictions).FirstOrDefault();
if (mostSevereAffliction != null) { DrawLimbAfflictionIcon(spriteBatch, mostSevereAffliction, iconScale, ref iconPos); }
if (thisAfflictions.Count() > 1)
@@ -1992,6 +1991,9 @@ namespace Barotrauma
}
}
medUIExtra?.Remove();
medUIExtra = null;
limbIndicatorOverlay?.Remove();
limbIndicatorOverlay = null;
}

View File

@@ -220,9 +220,18 @@ namespace Barotrauma
}
set
{
if (HuskSprite != null && value != enableHuskSprite)
if (enableHuskSprite == value) { return; }
enableHuskSprite = value;
if (enableHuskSprite)
{
if (value)
if (HuskSprite == null)
{
LoadHuskSprite();
}
}
if (HuskSprite != null)
{
if (enableHuskSprite)
{
List<WearableSprite> otherWearablesWithHusk = new List<WearableSprite>() { HuskSprite };
otherWearablesWithHusk.AddRange(OtherWearables);
@@ -235,7 +244,6 @@ namespace Barotrauma
UpdateWearableTypesToHide();
}
}
enableHuskSprite = value;
}
}

View File

@@ -195,6 +195,34 @@ namespace Barotrauma
}
}
private static bool IsCommandPermitted(string command, GameClient client)
{
switch (command)
{
case "kick":
return client.HasPermission(ClientPermissions.Kick);
case "ban":
case "banip":
case "banendpoint":
return client.HasPermission(ClientPermissions.Ban);
case "unban":
case "unbanip":
return client.HasPermission(ClientPermissions.Unban);
case "netstats":
case "help":
case "dumpids":
case "admin":
case "entitylist":
case "togglehud":
case "toggleupperhud":
case "togglecharacternames":
case "fpscounter":
return true;
default:
return client.HasConsoleCommandPermission(command);
}
}
public static void DequeueMessages()
{
while (queuedMessages.Count > 0)
@@ -367,6 +395,7 @@ namespace Barotrauma
NewMessage("Steam achievements have been disabled during this play session.", Color.Red);
#endif
}));
AssignRelayToServer("enablecheats", true);
commands.Add(new Command("mainmenu|menu", "mainmenu/menu: Go to the main menu.", (string[] args) =>
{
@@ -2113,6 +2142,11 @@ namespace Barotrauma
commands.Add(new Command("flipx", "flipx: mirror the main submarine horizontally", (string[] args) =>
{
if (GameMain.NetworkMember != null)
{
ThrowError("Cannot use the flipx command while playing online.");
return;
}
Submarine.MainSub?.FlipX();
}, isCheat: true));

View File

@@ -135,31 +135,33 @@ namespace Barotrauma
}
}
Color textColor = textBox.Color;
switch (command)
{
case "r":
case "radio":
textBox.TextColor = ChatMessage.MessageColor[(int)ChatMessageType.Radio];
textColor = ChatMessage.MessageColor[(int)ChatMessageType.Radio];
break;
case "d":
case "dead":
textBox.TextColor = ChatMessage.MessageColor[(int)ChatMessageType.Dead];
textColor = ChatMessage.MessageColor[(int)ChatMessageType.Dead];
break;
default:
if (Character.Controlled != null && (Character.Controlled.IsDead || Character.Controlled.SpeechImpediment >= 100.0f))
{
textBox.TextColor = ChatMessage.MessageColor[(int)ChatMessageType.Dead];
textColor = ChatMessage.MessageColor[(int)ChatMessageType.Dead];
}
else if (command != "") //PMing
{
textBox.TextColor = ChatMessage.MessageColor[(int)ChatMessageType.Private];
textColor = ChatMessage.MessageColor[(int)ChatMessageType.Private];
}
else
{
textBox.TextColor = ChatMessage.MessageColor[(int)ChatMessageType.Default];
textColor = ChatMessage.MessageColor[(int)ChatMessageType.Default];
}
break;
}
textBox.TextColor = textBox.TextBlock.SelectedTextColor = textColor;
return true;
}
@@ -252,21 +254,29 @@ namespace Barotrauma
Visible = false,
CanBeFocused = false
};
var senderText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), popupMsg.RectTransform, Anchor.TopRight),
senderName, textColor: senderColor, font: GUI.SmallFont, textAlignment: Alignment.TopRight)
var content = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), popupMsg.RectTransform, Anchor.Center));
Vector2 senderTextSize = Vector2.Zero;
if (!string.IsNullOrEmpty(senderName))
{
var senderText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform),
senderName, textColor: senderColor, style: null, font: GUI.SmallFont)
{
CanBeFocused = false
};
senderTextSize = senderText.Font.MeasureString(senderText.WrappedText);
senderText.RectTransform.MinSize = new Point(0, senderText.Rect.Height);
}
var msgPopupText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform),
displayedText, textColor: message.Color, font: GUI.SmallFont, textAlignment: Alignment.BottomLeft, style: null, wrap: true)
{
CanBeFocused = false
};
var msgPopupText = new GUITextBlock(new RectTransform(new Vector2(0.8f, 0.0f), popupMsg.RectTransform, Anchor.TopRight)
{ AbsoluteOffset = new Point(0, senderText.Rect.Height) },
displayedText, textColor: message.Color, font: GUI.SmallFont, textAlignment: Alignment.TopRight, style: null, wrap: true)
{
CanBeFocused = false
};
int textWidth = (int)Math.Max(
msgPopupText.Font.MeasureString(msgPopupText.WrappedText).X,
senderText.Font.MeasureString(senderText.WrappedText).X);
popupMsg.RectTransform.Resize(new Point(textWidth + 20, msgPopupText.Rect.Bottom - senderText.Rect.Y), resizeChildren: false);
msgPopupText.RectTransform.MinSize = new Point(0, msgPopupText.Rect.Height);
Vector2 msgSize = msgPopupText.Font.MeasureString(msgPopupText.WrappedText);
int textWidth = (int)Math.Max(msgSize.X + msgPopupText.Padding.X + msgPopupText.Padding.Z, senderTextSize.X) + 10;
popupMsg.RectTransform.Resize(new Point((int)(textWidth / content.RectTransform.RelativeSize.X) , (int)((senderTextSize.Y + msgSize.Y) / content.RectTransform.RelativeSize.Y)), resizeChildren: true);
popupMsg.RectTransform.IsFixedSize = true;
content.Recalculate();
popupMessages.Enqueue(popupMsg);
}

View File

@@ -5,16 +5,6 @@ using System.Xml.Linq;
namespace Barotrauma
{
public enum TransitionMode
{
Linear,
Smooth,
Smoother,
EaseIn,
EaseOut,
Exponential
}
public enum SpriteFallBackState
{
None,

View File

@@ -524,23 +524,9 @@ namespace Barotrauma
};
}
private float GetEasing(TransitionMode easing, float t)
{
return easing switch
{
TransitionMode.Smooth => MathUtils.SmoothStep(t),
TransitionMode.Smoother => MathUtils.SmootherStep(t),
TransitionMode.EaseIn => MathUtils.EaseIn(t),
TransitionMode.EaseOut => MathUtils.EaseOut(t),
TransitionMode.Exponential => t * t,
TransitionMode.Linear => t,
_ => t,
};
}
protected Color GetBlendedColor(Color targetColor, ref Color blendedColor)
{
blendedColor = ColorCrossFadeTime > 0 ? Color.Lerp(blendedColor, targetColor, MathUtils.InverseLerp(ColorCrossFadeTime, 0, GetEasing(ColorTransition, colorFadeTimer))) : targetColor;
blendedColor = ColorCrossFadeTime > 0 ? Color.Lerp(blendedColor, targetColor, MathUtils.InverseLerp(ColorCrossFadeTime, 0, ToolBox.GetEasing(ColorTransition, colorFadeTimer))) : targetColor;
return blendedColor;
}
@@ -598,7 +584,7 @@ namespace Barotrauma
foreach (UISprite uiSprite in previousSprites)
{
float alphaMultiplier = SpriteCrossFadeTime > 0 && (uiSprite.CrossFadeOut || currentSprites != null && currentSprites.Any(s => s.CrossFadeIn))
? MathUtils.InverseLerp(0, SpriteCrossFadeTime, GetEasing(uiSprite.TransitionMode, spriteFadeTimer)) : 0;
? MathUtils.InverseLerp(0, SpriteCrossFadeTime, ToolBox.GetEasing(uiSprite.TransitionMode, spriteFadeTimer)) : 0;
if (alphaMultiplier > 0)
{
uiSprite.Draw(spriteBatch, rect, previousColor * alphaMultiplier, SpriteEffects);
@@ -612,7 +598,7 @@ namespace Barotrauma
foreach (UISprite uiSprite in currentSprites)
{
float alphaMultiplier = SpriteCrossFadeTime > 0 && (uiSprite.CrossFadeIn || previousSprites != null && previousSprites.Any(s => s.CrossFadeOut))
? MathUtils.InverseLerp(SpriteCrossFadeTime, 0, GetEasing(uiSprite.TransitionMode, spriteFadeTimer)) : (_currentColor.A / 255.0f);
? MathUtils.InverseLerp(SpriteCrossFadeTime, 0, ToolBox.GetEasing(uiSprite.TransitionMode, spriteFadeTimer)) : (_currentColor.A / 255.0f);
if (alphaMultiplier > 0)
{
uiSprite.Draw(spriteBatch, rect, _currentColor * alphaMultiplier, SpriteEffects);

View File

@@ -95,6 +95,15 @@ namespace Barotrauma
set { button.TextColor = value; }
}
public override ScalableFont Font
{
get { return button?.Font ?? base.Font; }
set
{
if (button != null) { button.Font = value; }
}
}
public void ReceiveTextInput(char inputChar)
{
GUI.KeyboardDispatcher.Subscriber = null;
@@ -168,12 +177,11 @@ namespace Barotrauma
Anchor listAnchor = dropAbove ? Anchor.TopCenter : Anchor.BottomCenter;
Pivot listPivot = dropAbove ? Pivot.BottomCenter : Pivot.TopCenter;
listBox = new GUIListBox(new RectTransform(new Point(Rect.Width, Rect.Height * MathHelper.Clamp(elementCount, 2, 10)), rectT, listAnchor, listPivot)
{ IsFixedSize = false }, style: null)
{
Enabled = !selectMultiple,
OnSelected = SelectItem
Enabled = !selectMultiple
};
if (!selectMultiple) { listBox.OnSelected = SelectItem; }
GUI.Style.Apply(listBox, "GUIListBox", this);
GUI.Style.Apply(listBox.ContentBackground, "GUIListBox", this);
@@ -245,7 +253,7 @@ namespace Barotrauma
ToolTip = toolTip
};
new GUITickBox(new RectTransform(new Point((int)(button.Rect.Height * 0.8f)), frame.RectTransform, anchor: Anchor.CenterLeft), text)
new GUITickBox(new RectTransform(new Vector2(1.0f, 0.8f), frame.RectTransform, anchor: Anchor.CenterLeft) { MaxSize = new Point(int.MaxValue, (int)(button.Rect.Height * 0.8f)) }, text)
{
UserData = userData,
ToolTip = toolTip,

View File

@@ -328,11 +328,7 @@ namespace Barotrauma
{
if (text == null) { return; }
censoredText = "";
for (int i = 0; i < text.Length; i++)
{
censoredText += "\u2022";
}
censoredText = string.IsNullOrEmpty(text) ? "" : new string('\u2022', text.Length);
var rect = Rect;
@@ -446,9 +442,10 @@ namespace Barotrauma
Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle;
if (overflowClipActive)
{
spriteBatch.End();
Rectangle scissorRect = new Rectangle(rect.X + (int)padding.X, rect.Y, rect.Width - (int)padding.X - (int)padding.Z, rect.Height);
spriteBatch.GraphicsDevice.ScissorRectangle = scissorRect;
if (!scissorRect.Intersects(prevScissorRect)) { return; }
spriteBatch.End();
spriteBatch.GraphicsDevice.ScissorRectangle = Rectangle.Intersect(prevScissorRect, scissorRect);
spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
}

View File

@@ -189,6 +189,11 @@ namespace Barotrauma
Instance = this;
if (!Directory.Exists(Content.RootDirectory))
{
throw new Exception("Content folder not found. If you are trying to compile the game from the source code and own a legal copy of the game, you can copy the Content folder from the game's files to BarotraumaShared/Content.");
}
Config = new GameSettings();
Md5Hash.LoadCache();
@@ -421,17 +426,27 @@ namespace Barotrauma
GUI.Init(Window, Config.SelectedContentPackages, GraphicsDevice);
DebugConsole.Init();
CrossThread.RequestExecutionOnMainThread(() =>
if (Config.AutoUpdateWorkshopItems)
{
if (Config.AutoUpdateWorkshopItems)
bool waitingForWorkshopUpdates = true;
bool result = false;
TaskPool.Add(SteamManager.AutoUpdateWorkshopItems(), (task) =>
{
if (SteamManager.AutoUpdateWorkshopItems())
result = task.Result;
waitingForWorkshopUpdates = false;
});
while (waitingForWorkshopUpdates) { yield return CoroutineStatus.Running; }
if (result)
{
CrossThread.RequestExecutionOnMainThread(() =>
{
ContentPackage.LoadAll();
Config.ReloadContentPackages();
}
});
}
});
}
if (SelectedPackages.None())

View File

@@ -330,17 +330,6 @@ namespace Barotrauma
}
AddCharacterToCrewList(character);
if (character is AICharacter)
{
var ai = character.AIController as HumanAIController;
if (ai == null)
{
DebugConsole.ThrowError("Error in crewmanager - attempted to give orders to a character with no HumanAIController");
return;
}
character.SetOrder(ai.CurrentOrder, "", null, false);
}
}
public void AddCharacterInfo(CharacterInfo characterInfo)
@@ -788,11 +777,11 @@ namespace Barotrauma
characterFrame.SetAsFirstChild();
}
private void DisplayPreviousCharacterOrder(Character character, GUILayoutGroup characterComponent, OrderInfo currentOrderInfo)
private void DisplayPreviousCharacterOrder(Character character, GUILayoutGroup characterComponent, OrderInfo orderInfo)
{
if (currentOrderInfo.Order == null || currentOrderInfo.Order.Identifier == dismissedOrderPrefab.Identifier) { return; }
if (orderInfo.Order == null || orderInfo.Order.Identifier == dismissedOrderPrefab.Identifier) { return; }
var previousOrderInfo = new OrderInfo(currentOrderInfo);
var previousOrderInfo = new OrderInfo(orderInfo);
var prevOrderFrame = new GUIButton(
new RectTransform(
characterComponent.GetChildByUserData("job").RectTransform.RelativeSize,
@@ -835,12 +824,12 @@ namespace Barotrauma
private GUIComponent GetCurrentOrderComponent(GUILayoutGroup characterComponent)
{
return characterComponent.FindChild(c => c.UserData is OrderInfo orderInfo && orderInfo.ComponentIdentifier == "currentorder");
return characterComponent?.FindChild(c => c?.UserData is OrderInfo orderInfo && orderInfo.ComponentIdentifier == "currentorder");
}
private GUIComponent GetPreviousOrderComponent(GUILayoutGroup characterComponent)
{
return characterComponent.FindChild(c => c.UserData is OrderInfo orderInfo && orderInfo.ComponentIdentifier == "previousorder");
return characterComponent?.FindChild(c => c?.UserData is OrderInfo orderInfo && orderInfo.ComponentIdentifier == "previousorder");
}
private struct OrderInfo
@@ -910,7 +899,18 @@ namespace Barotrauma
{
if (!(c.UserData is Character character) || character.IsDead || character.Removed) { continue; }
AddCharacter(character);
DisplayCharacterOrder(character, character.CurrentOrder, (character.AIController as HumanAIController)?.CurrentOrderOption);
if (c.GetChild<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;
}
DisableCommandUI();
Character.Controlled = character;
}
@@ -1176,19 +1177,22 @@ namespace Barotrauma
if (PlayerInput.KeyHit(InputType.RadioChat) && !ChatBox.InputBox.Selected)
{
ChatBox.InputBox.AddToGUIUpdateList();
ChatBox.GUIFrame.Flash(Color.YellowGreen, 0.5f);
if (!ChatBox.ToggleOpen)
if (Character.Controlled == null || Character.Controlled.SpeechImpediment < 100)
{
ChatBox.CloseAfterMessageSent = !ChatBox.ToggleOpen;
ChatBox.ToggleOpen = true;
}
ChatBox.InputBox.AddToGUIUpdateList();
ChatBox.GUIFrame.Flash(Color.YellowGreen, 0.5f);
if (!ChatBox.ToggleOpen)
{
ChatBox.CloseAfterMessageSent = !ChatBox.ToggleOpen;
ChatBox.ToggleOpen = true;
}
if (!ChatBox.InputBox.Text.StartsWith(ChatBox.RadioChatString))
{
ChatBox.InputBox.Text = ChatBox.RadioChatString;
if (!ChatBox.InputBox.Text.StartsWith(ChatBox.RadioChatString))
{
ChatBox.InputBox.Text = ChatBox.RadioChatString;
}
ChatBox.InputBox.Select(ChatBox.InputBox.Text.Length);
}
ChatBox.InputBox.Select(ChatBox.InputBox.Text.Length);
}
}
}
@@ -1293,13 +1297,21 @@ namespace Barotrauma
{
get
{
#if DEBUG
return Character.Controlled == null || Character.Controlled.Info != null && Character.Controlled.SpeechImpediment < 100.0f;
#else
return Character.Controlled != null && Character.Controlled.SpeechImpediment < 100.0f;
#endif
}
}
private bool CanSomeoneHearCharacter()
{
#if DEBUG
return true;
#else
return Character.Controlled != null && characters.Any(c => c != Character.Controlled && c.CanHearCharacter(Character.Controlled));
#endif
}
private void CreateCommandUI(Character characterContext = null)
@@ -1505,17 +1517,7 @@ namespace Barotrauma
shortcutCenterNode = null;
}
CreateNodes(userData);
if (returnNode != null && returnNode.Visible)
{
var hotkey = optionNodes.Count + 1;
if (expandNode != null && expandNode.Visible) { hotkey += 1; }
CreateHotkeyIcon(returnNode.RectTransform, hotkey % 10, true);
returnNodeHotkey = Keys.D0 + hotkey % 10;
}
else
{
returnNodeHotkey = Keys.None;
}
CreateReturnNodeHotkey();
return true;
}
@@ -1539,10 +1541,20 @@ namespace Barotrauma
returnNode = null;
}
CreateNodes(userData);
CreateReturnNodeHotkey();
return true;
}
private void CreateReturnNodeHotkey()
{
if (returnNode != null && returnNode.Visible)
{
var hotkey = optionNodes.Count + 1;
if (expandNode != null && expandNode.Visible) { hotkey += 1; }
var hotkey = 1;
if (targetFrame == null || !targetFrame.Visible)
{
hotkey = optionNodes.Count + 1;
if (expandNode != null && expandNode.Visible) { hotkey += 1; }
}
CreateHotkeyIcon(returnNode.RectTransform, hotkey % 10, true);
returnNodeHotkey = Keys.D0 + hotkey % 10;
}
@@ -1550,7 +1562,6 @@ namespace Barotrauma
{
returnNodeHotkey = Keys.None;
}
return true;
}
private void SetCenterNode(GUIButton node)
@@ -2148,7 +2159,11 @@ namespace Barotrauma
ToolTip = character.Info.DisplayName + " (" + character.Info.Job.Name + ")"
};
#if DEBUG
bool canHear = true;
#else
bool canHear = character.CanHearCharacter(Character.Controlled);
#endif
if (!canHear)
{
node.CanBeFocused = icon.CanBeFocused = false;
@@ -2271,8 +2286,14 @@ namespace Barotrauma
private Character GetBestCharacterForOrder(Order order)
{
#if !DEBUG
if (Character.Controlled == null) { return null; }
return characters.FindAll(c => c != Character.Controlled && c.TeamID == Character.Controlled.TeamID)
#endif
if (order.Category == OrderCategory.Operate && AIObjectiveOperateItem.IsOperatedByAnother(null, order.TargetItemComponent, out Character operatingCharacter))
{
return operatingCharacter;
}
return characters.FindAll(c => Character.Controlled == null || (c != Character.Controlled && c.TeamID == Character.Controlled.TeamID))
.OrderByDescending(c => c.CurrentOrder == null || c.CurrentOrder.Identifier == dismissedOrderPrefab.Identifier)
.ThenByDescending(c => order.HasAppropriateJob(c))
.ThenBy(c => c.CurrentOrder?.Weight)
@@ -2281,16 +2302,18 @@ namespace Barotrauma
private List<Character> GetCharactersSortedForOrder(Order order)
{
#if !DEBUG
if (Character.Controlled == null) { return new List<Character>(); }
#endif
if (order.Identifier == "follow")
{
return characters.FindAll(c => c != Character.Controlled && c.TeamID == Character.Controlled.TeamID)
return characters.FindAll(c => Character.Controlled == null || (c != Character.Controlled && c.TeamID == Character.Controlled.TeamID))
.OrderByDescending(c => c.CurrentOrder == null || c.CurrentOrder.Identifier == dismissedOrderPrefab.Identifier)
.ToList();
}
else
{
return characters.FindAll(c => c.TeamID == Character.Controlled.TeamID)
return characters.FindAll(c => Character.Controlled == null || c.TeamID == Character.Controlled.TeamID)
.OrderByDescending(c => c.CurrentOrder == null || c.CurrentOrder.Identifier == dismissedOrderPrefab.Identifier)
.ThenByDescending(c => order.HasAppropriateJob(c))
.ThenBy(c => c.CurrentOrder?.Weight)
@@ -2298,9 +2321,9 @@ namespace Barotrauma
}
}
#endregion
#endregion
#endregion
#endregion
/// <summary>
/// Creates a listbox that includes all the characters in the crew, can be used externally (round info menus etc)
@@ -2387,7 +2410,7 @@ namespace Barotrauma
return true;
}
#region Reports
#region Reports
/// <summary>
/// Enables/disables report buttons when needed
@@ -2447,7 +2470,7 @@ namespace Barotrauma
}
}
#endregion
#endregion
public void InitSinglePlayerRound()
{

View File

@@ -91,7 +91,7 @@ namespace Barotrauma
if (Character.Controlled.Submarine != outpost) { return null; }
//if there's a sub docked to the outpost, we can leave the level
if (outpost.DockedTo.Count > 0)
if (outpost.DockedTo.Any())
{
var dockedSub = outpost.DockedTo.FirstOrDefault();
return dockedSub.DockedTo.Contains(Submarine.MainSub) ? Submarine.MainSub : dockedSub;

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using Barotrauma.Items.Components;
using Barotrauma.Networking;
@@ -221,7 +222,7 @@ namespace Barotrauma.Tutorials
{
//captain_navConsoleCustomInterface.HighlightElement(0, uiHighlightColor, duration: 1.0f, pulsateAmount: 0.0f);
yield return new WaitForSeconds(1.0f, false);
} while (Submarine.MainSub.DockedTo.Count > 0);
} while (Submarine.MainSub.DockedTo.Any());
RemoveCompletedObjective(segments[4]);
yield return new WaitForSeconds(2f, false);
TriggerTutorialSegment(5); // Navigate to destination
@@ -245,7 +246,7 @@ namespace Barotrauma.Tutorials
{
//captain_navConsoleCustomInterface.HighlightElement(0, uiHighlightColor, duration: 1.0f, pulsateAmount: 0.0f);
yield return new WaitForSeconds(1.0f, false);
} while (!Submarine.MainSub.AtEndPosition || Submarine.MainSub.DockedTo.Count == 0);
} while (!Submarine.MainSub.AtEndPosition || Submarine.MainSub.DockedTo.Any());
RemoveCompletedObjective(segments[6]);
yield return new WaitForSeconds(3f, false);
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.GetWithVariable("Captain.Radio.Complete", "[OUTPOSTNAME]", GameMain.GameSession.EndLocation.Name), ChatMessageType.Radio, null);

View File

@@ -13,6 +13,7 @@ namespace Barotrauma
private GUIFrame infoFrameContent;
public RoundSummary RoundSummary { get; private set; }
public static bool IsInfoFrameOpen => GameMain.GameSession?.infoFrame != null;
private bool ToggleInfoFrame()
{

View File

@@ -47,7 +47,7 @@ namespace Barotrauma
{
keyMapping = new KeyOrMouse[Enum.GetNames(typeof(InputType)).Length];
keyMapping[(int)InputType.Run] = new KeyOrMouse(Keys.LeftShift);
keyMapping[(int)InputType.Attack] = new KeyOrMouse(MouseButton.MiddleMouse);
keyMapping[(int)InputType.Attack] = new KeyOrMouse(Keys.R);
keyMapping[(int)InputType.Crouch] = new KeyOrMouse(Keys.LeftControl);
keyMapping[(int)InputType.Grab] = new KeyOrMouse(Keys.G);
keyMapping[(int)InputType.Health] = new KeyOrMouse(Keys.H);

View File

@@ -154,7 +154,7 @@ namespace Barotrauma.Items.Components
};
// === POWER WARNING === //
inSufficientPowerWarning = new GUITextBlock(new RectTransform(Vector2.One, activateButton.RectTransform),
TextManager.Get("FabricatorNoPower"), textColor: GUI.Style.Orange, textAlignment: Alignment.Center, color: Color.Black, style: "OuterGlow")
TextManager.Get("FabricatorNoPower"), textColor: GUI.Style.Orange, textAlignment: Alignment.Center, color: Color.Black, style: "OuterGlow", wrap: true)
{
HoverColor = Color.Black,
IgnoreLayoutGroups = true,
@@ -584,15 +584,20 @@ namespace Barotrauma.Items.Components
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{
FabricatorState newState = (FabricatorState)msg.ReadByte();
float newTimeUntilReady = msg.ReadSingle();
int itemIndex = msg.ReadRangedInteger(-1, fabricationRecipes.Count - 1);
UInt16 userID = msg.ReadUInt16();
Character user = Entity.FindEntityByID(userID) as Character;
if (itemIndex == -1 || user == null)
State = newState;
timeUntilReady = newTimeUntilReady;
if (newState == FabricatorState.Stopped || itemIndex == -1 || user == null)
{
CancelFabricating();
}
else
else if (newState == FabricatorState.Active || newState == FabricatorState.Paused)
{
//if already fabricating the selected item, return
if (fabricatedItem != null && fabricationRecipes.IndexOf(fabricatedItem) == itemIndex) { return; }

View File

@@ -490,7 +490,7 @@ namespace Barotrauma.Items.Components
disruptedDirections.Clear();
foreach (AITarget t in AITarget.List)
{
if (t.SoundRange <= 0.0f || !t.Enabled || float.IsNaN(t.SoundRange) || float.IsInfinity(t.SoundRange)) { continue; }
if (t.SoundRange <= 0.0f || float.IsNaN(t.SoundRange) || float.IsInfinity(t.SoundRange)) { continue; }
float distSqr = Vector2.DistanceSquared(t.WorldPosition, transducerCenter);
if (distSqr > t.SoundRange * t.SoundRange * 2) { continue; }

View File

@@ -665,7 +665,7 @@ namespace Barotrauma.Items.Components
if (Vector2.DistanceSquared(PlayerInput.MousePosition, steerArea.Rect.Center.ToVector2()) < steerRadius * steerRadius)
{
if (PlayerInput.PrimaryMouseButtonHeld() && !CrewManager.IsCommandInterfaceOpen)
if (PlayerInput.PrimaryMouseButtonHeld() && !CrewManager.IsCommandInterfaceOpen && !GameSession.IsInfoFrameOpen)
{
Vector2 displaySubPos = (-sonar.DisplayOffset * sonar.Zoom) / sonar.Range * sonar.DisplayRadius * sonar.Zoom;
displaySubPos.Y = -displaySubPos.Y;

View File

@@ -106,11 +106,10 @@ namespace Barotrauma.Items.Components
{
if (indicatorSize.X <= 1.0f || indicatorSize.Y <= 1.0f) { return; }
GUI.DrawRectangle(spriteBatch,
new Vector2(
item.DrawPosition.X - item.Sprite.SourceRect.Width / 2 * item.Scale + indicatorPosition.X * item.Scale,
-item.DrawPosition.Y - item.Sprite.SourceRect.Height / 2 * item.Scale + indicatorPosition.Y * item.Scale),
indicatorSize * item.Scale, Color.Black, depth: item.SpriteDepth - 0.00001f);
Vector2 itemSize = new Vector2(item.Sprite.SourceRect.Width, item.Sprite.SourceRect.Height) * item.Scale;
Vector2 indicatorPos = -itemSize / 2 + indicatorPosition * item.Scale;
if (item.FlippedX && item.Prefab.CanSpriteFlipX) { indicatorPos.X = -indicatorPos.X - indicatorSize.X * item.Scale; }
if (item.FlippedY && item.Prefab.CanSpriteFlipY) { indicatorPos.Y = -indicatorPos.Y - indicatorSize.Y * item.Scale; }
if (charge > 0)
{
@@ -118,25 +117,23 @@ namespace Barotrauma.Items.Components
if (!isHorizontal)
{
GUI.DrawRectangle(spriteBatch,
new Vector2(
item.DrawPosition.X - item.Sprite.SourceRect.Width / 2 * item.Scale + indicatorPosition.X * item.Scale + 1,
-item.DrawPosition.Y - item.Sprite.SourceRect.Height / 2 * item.Scale + indicatorPosition.Y * item.Scale + 1 + ((indicatorSize.Y * item.Scale) * (1.0f - charge / capacity))),
new Vector2(indicatorSize.X * item.Scale - 2, (indicatorSize.Y * item.Scale - 2) * (charge / capacity)), indicatorColor, true,
depth: item.SpriteDepth - 0.00001f);
new Vector2(item.DrawPosition.X, -item.DrawPosition.Y + ((indicatorSize.Y * item.Scale) * (1.0f - charge / capacity))) + indicatorPos,
new Vector2(indicatorSize.X * item.Scale, (indicatorSize.Y * item.Scale) * (charge / capacity)), indicatorColor, true,
depth: item.SpriteDepth - 0.00001f);
}
else
{
GUI.DrawRectangle(spriteBatch,
new Vector2(
item.DrawPosition.X - item.Sprite.SourceRect.Width / 2 * item.Scale + indicatorPosition.X * item.Scale + 1 ,
-item.DrawPosition.Y - item.Sprite.SourceRect.Height / 2 * item.Scale + indicatorPosition.Y * item.Scale + 1),
new Vector2((indicatorSize.X * item.Scale - 2) * (charge / capacity), indicatorSize.Y * item.Scale - 2), indicatorColor, true,
depth: item.SpriteDepth - 0.00001f);
new Vector2(item.DrawPosition.X, -item.DrawPosition.Y) + indicatorPos,
new Vector2((indicatorSize.X * item.Scale) * (charge / capacity), indicatorSize.Y * item.Scale), indicatorColor, true,
depth: item.SpriteDepth - 0.00001f);
}
}
GUI.DrawRectangle(spriteBatch,
new Vector2(item.DrawPosition.X, -item.DrawPosition.Y) + indicatorPos,
indicatorSize * item.Scale, Color.Black, depth: item.SpriteDepth - 0.00001f);
}
public void ClientWrite(IWriteMessage msg, object[] extraData)
{
msg.WriteRangedInteger((int)(rechargeSpeed / MaxRechargeSpeed * 10), 0, 10);

View File

@@ -142,7 +142,7 @@ namespace Barotrauma.Items.Components
private void DrawCharacterInfo(SpriteBatch spriteBatch, Character target, float alpha = 1.0f)
{
Vector2 hudPos = GameMain.GameScreen.Cam.WorldToScreen(target.WorldPosition);
Vector2 hudPos = GameMain.GameScreen.Cam.WorldToScreen(target.DrawPosition);
hudPos += Vector2.UnitX * 50.0f;
List<string> texts = new List<string>();

View File

@@ -152,7 +152,7 @@ namespace Barotrauma.Items.Components
string errorMsg = "Error while reading a docking port network event (Dock method did not create a joint between the ports)." +
" Submarine: " + (item.Submarine?.Name ?? "null") +
", target submarine: " + (DockingTarget.item.Submarine?.Name ?? "null");
if (item.Submarine?.DockedTo.Contains(DockingTarget.item.Submarine) ?? false)
if (item.Submarine?.ConnectedDockingPorts.ContainsKey(DockingTarget.item.Submarine) ?? false)
{
errorMsg += "\nAlready docked.";
}

View File

@@ -284,6 +284,7 @@ namespace Barotrauma
}
protected static HashSet<SlotReference> highlightedSubInventorySlots = new HashSet<SlotReference>();
private static List<SlotReference> subInventorySlotsToDraw = new List<SlotReference>();
protected static SlotReference selectedSlot;
@@ -1048,11 +1049,14 @@ namespace Barotrauma
return hoverArea;
}
public static void DrawFront(SpriteBatch spriteBatch)
{
if (GUI.PauseMenuOpen || GUI.SettingsMenuOpen) return;
if (GUI.PauseMenuOpen || GUI.SettingsMenuOpen) { return; }
foreach (var slot in highlightedSubInventorySlots)
subInventorySlotsToDraw.Clear();
subInventorySlotsToDraw.AddRange(highlightedSubInventorySlots);
foreach (var slot in subInventorySlotsToDraw)
{
int slotIndex = Array.IndexOf(slot.ParentInventory.slots, slot.Slot);
if (slotIndex > -1 && slotIndex < slot.ParentInventory.slots.Length)

View File

@@ -333,15 +333,6 @@ namespace Barotrauma
if (GameMain.DebugDraw)
{
aiTarget?.Draw(spriteBatch);
var containedItems = ContainedItems;
if (containedItems != null)
{
foreach (Item item in containedItems)
{
item.AiTarget?.Draw(spriteBatch);
}
}
if (body != null)
{
body.DebugDraw(spriteBatch, Color.White);

View File

@@ -248,8 +248,6 @@ namespace Barotrauma
if (!editing && !GameMain.DebugDraw) return;
if (aiTarget != null) aiTarget.Draw(spriteBatch);
Rectangle drawRect =
Submarine == null ? rect : new Rectangle((int)(Submarine.DrawPosition.X + rect.X), (int)(Submarine.DrawPosition.Y + rect.Y), rect.Width, rect.Height);

View File

@@ -4,6 +4,7 @@ using Barotrauma.SpriteDeformations;
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma
@@ -120,7 +121,7 @@ namespace Barotrauma
SerializableProperty.SerializeProperties(this, element);
foreach (XElement subElement in element.Elements())
foreach (XElement subElement in element.Elements().ToList())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
@@ -139,7 +140,7 @@ namespace Barotrauma
break;
}
}
foreach (LightSourceParams lightSourceParams in LightSourceParams)
{
var lightElement = new XElement("LightSource");

View File

@@ -12,10 +12,10 @@ namespace Barotrauma
{
public override void Draw(SpriteBatch spriteBatch, bool editing, bool back = true)
{
if (!editing || wallVertices == null) return;
if (!editing || wallVertices == null) { return; }
Color color = (IsHighlighted) ? GUI.Style.Orange : GUI.Style.Green;
if (IsSelected) color = GUI.Style.Red;
Color color = IsHighlighted ? GUI.Style.Orange : GUI.Style.Green;
if (IsSelected) { color = GUI.Style.Red; }
Vector2 pos = Position;
@@ -37,11 +37,7 @@ namespace Barotrauma
GUI.DrawLine(spriteBatch, pos + Vector2.UnitY * 50.0f, pos - Vector2.UnitY * 50.0f, color, 0.0f, 5);
GUI.DrawLine(spriteBatch, pos + Vector2.UnitX * 50.0f, pos - Vector2.UnitX * 50.0f, color, 0.0f, 5);
Rectangle drawRect = rect;
drawRect.Y = -rect.Y;
GUI.DrawRectangle(spriteBatch, drawRect, GUI.Style.Red, true);
if (!Item.ShowLinks) return;
if (!Item.ShowLinks) { return; }
foreach (MapEntity e in linkedTo)
{

View File

@@ -350,8 +350,6 @@ namespace Barotrauma
}
}
}
AiTarget?.Draw(spriteBatch);
}
}

View File

@@ -222,7 +222,7 @@ namespace Barotrauma
GUI.DrawRectangle(spriteBatch, worldBorders, Color.White, false, 0, 5);
if (sub.subBody.PositionBuffer.Count < 2) continue;
if (sub.subBody == null || sub.subBody.PositionBuffer.Count < 2) continue;
Vector2 prevPos = ConvertUnits.ToDisplayUnits(sub.subBody.PositionBuffer[0].Position);
prevPos.Y = -prevPos.Y;

View File

@@ -15,6 +15,7 @@ namespace Barotrauma.Networking
public byte ID;
public UInt16 CharacterID;
public bool Muted;
public bool InGame;
public bool AllowKicking;
}

View File

@@ -8,6 +8,7 @@ using System.IO.Compression;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Barotrauma.Networking
{
@@ -60,6 +61,19 @@ namespace Barotrauma.Networking
private bool connected;
private enum RoundInitStatus
{
NotStarted,
Starting,
WaitingForStartGameFinalize,
Started,
TimedOut,
Error,
Interrupted
}
private RoundInitStatus roundInitStatus = RoundInitStatus.NotStarted;
private byte myID;
private List<Client> otherClients;
@@ -148,6 +162,8 @@ namespace Barotrauma.Networking
this.ownerKey = ownerKey;
this.steamP2POwner = steamP2POwner;
roundInitStatus = RoundInitStatus.NotStarted;
allowReconnect = true;
netStats = new NetStats();
@@ -558,6 +574,13 @@ namespace Barotrauma.Networking
try
{
incomingMessagesToProcess.Clear();
incomingMessagesToProcess.AddRange(pendingIncomingMessages);
foreach (var inc in incomingMessagesToProcess)
{
ReadDataMessage(inc);
}
pendingIncomingMessages.Clear();
clientPeer?.Update(deltaTime);
}
catch (Exception e)
@@ -632,11 +655,23 @@ namespace Barotrauma.Networking
}
}
private CoroutineHandle startGameCoroutine;
private readonly List<IReadMessage> pendingIncomingMessages = new List<IReadMessage>();
private readonly List<IReadMessage> incomingMessagesToProcess = new List<IReadMessage>();
private void ReadDataMessage(IReadMessage inc)
{
ServerPacketHeader header = (ServerPacketHeader)inc.ReadByte();
if (header != ServerPacketHeader.STARTGAMEFINALIZE &&
header != ServerPacketHeader.ENDGAME &&
roundInitStatus == RoundInitStatus.WaitingForStartGameFinalize)
{
//rewind the header byte we just read
inc.BitPosition -= 8;
pendingIncomingMessages.Add(inc);
return;
}
switch (header)
{
case ServerPacketHeader.UPDATE_LOBBY:
@@ -714,7 +749,13 @@ namespace Barotrauma.Networking
}
break;
case ServerPacketHeader.STARTGAME:
startGameCoroutine = GameMain.Instance.ShowLoading(StartGame(inc), false);
GameMain.Instance.ShowLoading(StartGame(inc), false);
break;
case ServerPacketHeader.STARTGAMEFINALIZE:
if (roundInitStatus == RoundInitStatus.WaitingForStartGameFinalize)
{
ReadStartGameFinalize(inc);
}
break;
case ServerPacketHeader.ENDGAME:
string endMessage = inc.ReadString();
@@ -725,6 +766,8 @@ namespace Barotrauma.Networking
GameMain.GameSession.WinningTeam = winningTeam;
GameMain.GameSession.Mission.Completed = true;
}
roundInitStatus = RoundInitStatus.Interrupted;
CoroutineManager.StartCoroutine(EndGame(endMessage), "EndGame");
break;
case ServerPacketHeader.CAMPAIGN_SETUP_INFO:
@@ -775,7 +818,37 @@ namespace Barotrauma.Networking
break;
}
}
private void ReadStartGameFinalize(IReadMessage inc)
{
ushort contentToPreloadCount = inc.ReadUInt16();
List<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()
{
if (SteamManager.IsInitialized)
@@ -884,6 +957,7 @@ namespace Barotrauma.Networking
}
else
{
connected = false;
connectCancelled = true;
string msg = "";
@@ -1108,6 +1182,7 @@ namespace Barotrauma.Networking
if (Character != null) Character.Remove();
HasSpawned = false;
eventErrorWritten = false;
GameMain.NetLobbyScreen.StopWaitingForStartRound();
while (CoroutineManager.IsCoroutineRunning("EndGame"))
{
@@ -1126,9 +1201,11 @@ namespace Barotrauma.Networking
EndVoteTickBox.Selected = false;
roundInitStatus = RoundInitStatus.Starting;
int seed = inc.ReadInt32();
string levelSeed = inc.ReadString();
int levelEqualityCheckVal = inc.ReadInt32();
//int levelEqualityCheckVal = inc.ReadInt32();
float levelDifficulty = inc.ReadSingle();
byte losMode = inc.ReadByte();
@@ -1146,24 +1223,16 @@ namespace Barotrauma.Networking
int missionIndex = inc.ReadInt16();
bool respawnAllowed = inc.ReadBoolean();
bool loadSecondSub = inc.ReadBoolean();
bool disguisesAllowed = inc.ReadBoolean();
bool rewiringAllowed = inc.ReadBoolean();
bool allowRagdollButton = inc.ReadBoolean();
ushort contentToPreloadCount = inc.ReadUInt16();
List<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);
bool includesFinalize = inc.ReadBoolean(); inc.ReadPadBits();
GameModePreset gameMode = GameModePreset.List.Find(gm => gm.Identifier == modeIdentifier);
MultiPlayerCampaign campaign =
GameMain.NetLobbyScreen.SelectedMode == GameMain.GameSession?.GameMode.Preset && gameMode == GameMain.NetLobbyScreen.SelectedMode ?
@@ -1237,21 +1306,105 @@ namespace Barotrauma.Networking
yield return CoroutineStatus.Failure;
}
MissionPrefab missionPrefab = missionIndex < 0 ? null : MissionPrefab.List[missionIndex];
GameMain.GameSession = missionIndex < 0 ?
new GameSession(GameMain.NetLobbyScreen.SelectedSub, "", gameMode, MissionType.None) :
new GameSession(GameMain.NetLobbyScreen.SelectedSub, "", gameMode, MissionPrefab.List[missionIndex]);
GameMain.GameSession.StartRound(levelSeed, levelDifficulty, loadSecondSub);
new GameSession(GameMain.NetLobbyScreen.SelectedSub, "", gameMode, missionPrefab);
//startRoundTask = Task.Run(async () => { await Task.Yield(); GameMain.GameSession.StartRound(levelSeed, levelDifficulty); });
GameMain.GameSession.StartRound(levelSeed, levelDifficulty);
}
else
{
if (GameMain.GameSession?.CrewManager != null) GameMain.GameSession.CrewManager.Reset();
/*startRoundTask = Task.Run(async () =>
{
await Task.Yield();
GameMain.GameSession.StartRound(campaign.Map.SelectedConnection.Level,
reloadSub: true,
mirrorLevel: campaign.Map.CurrentLocation != campaign.Map.SelectedConnection.Locations[0]);
});*/
GameMain.GameSession.StartRound(campaign.Map.SelectedConnection.Level,
reloadSub: true,
loadSecondSub: false,
mirrorLevel: campaign.Map.CurrentLocation != campaign.Map.SelectedConnection.Locations[0]);
reloadSub: true,
mirrorLevel: campaign.Map.CurrentLocation != campaign.Map.SelectedConnection.Locations[0]);
}
GameMain.GameSession.Mission?.ClientReadInitial(inc);
roundInitStatus = RoundInitStatus.WaitingForStartGameFinalize;
DateTime? timeOut = null;
DateTime requestFinalizeTime = DateTime.Now;
TimeSpan requestFinalizeInterval = new TimeSpan(0, 0, 2);
while (true)
{
try
{
if (timeOut.HasValue)
{
if (DateTime.Now > requestFinalizeTime)
{
IWriteMessage msg = new WriteOnlyMessage();
msg.Write((byte)ClientPacketHeader.REQUEST_STARTGAMEFINALIZE);
clientPeer.Send(msg, DeliveryMethod.Unreliable);
requestFinalizeTime = DateTime.Now + requestFinalizeInterval;
}
if (DateTime.Now > timeOut)
{
DebugConsole.ThrowError("Error while starting the round (did not receive STARTGAMEFINALIZE message from the server). Stopping the round...");
roundInitStatus = RoundInitStatus.TimedOut;
break;
}
}
else
{
if (includesFinalize)
{
ReadStartGameFinalize(inc);
break;
}
//wait for up to 30 seconds for the server to send the STARTGAMEFINALIZE message
timeOut = DateTime.Now + new TimeSpan(0, 0, seconds: 30);
}
if (!connected)
{
roundInitStatus = RoundInitStatus.Interrupted;
break;
}
if (roundInitStatus != RoundInitStatus.WaitingForStartGameFinalize)
{
break;
}
clientPeer.Update((float)Timing.Step);
}
catch (Exception e)
{
DebugConsole.ThrowError("There was an error initializing the round.", e, true);
roundInitStatus = RoundInitStatus.Error;
break;
}
//waiting for a STARTGAMEFINALIZE message
yield return CoroutineStatus.Running;
}
if (roundInitStatus != RoundInitStatus.Started)
{
if (roundInitStatus != RoundInitStatus.Interrupted)
{
DebugConsole.ThrowError(roundInitStatus.ToString());
CoroutineManager.StartCoroutine(EndGame(""));
yield return CoroutineStatus.Failure;
}
else
{
yield return CoroutineStatus.Success;
}
}
if (GameMain.GameSession.Submarine.IsFileCorrupted)
{
@@ -1261,7 +1414,7 @@ namespace Barotrauma.Networking
for (int i = 0; i < Submarine.MainSubs.Length; i++)
{
if (!loadSecondSub && i > 0) { break; }
if (Submarine.MainSubs[i] == null) { break; }
var teamID = i == 0 ? Character.TeamType.Team1 : Character.TeamType.Team2;
Submarine.MainSubs[i].TeamID = teamID;
@@ -1271,23 +1424,10 @@ namespace Barotrauma.Networking
}
}
if (Level.Loaded.EqualityCheckVal != levelEqualityCheckVal)
{
string errorMsg = "Level equality check failed. The level generated at your end doesn't match the level generated by the server (seed: " + Level.Loaded.Seed +
", sub: " + Submarine.MainSub.Name + " (" + Submarine.MainSub.MD5Hash.ShortHash + ")" +
", mirrored: " + Level.Loaded.Mirrored + ").";
DebugConsole.ThrowError(errorMsg, createMessageBox: true);
GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:LevelsDontMatch" + levelSeed, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
CoroutineManager.StartCoroutine(EndGame(""));
yield return CoroutineStatus.Failure;
}
if (respawnAllowed) { respawnManager = new RespawnManager(this, GameMain.NetLobbyScreen.UsingShuttle ? GameMain.NetLobbyScreen.SelectedShuttle : null); }
GameMain.GameSession.EventManager.PreloadContent(contentToPreload);
ServerSettings.ServerDetailsChanged = true;
gameStarted = true;
ServerSettings.ServerDetailsChanged = true;
GameMain.GameScreen.Select();
@@ -1394,6 +1534,7 @@ namespace Barotrauma.Networking
string preferredJob = inc.ReadString();
UInt16 characterID = inc.ReadUInt16();
bool muted = inc.ReadBoolean();
bool inGame = inc.ReadBoolean();
bool allowKicking = inc.ReadBoolean();
inc.ReadPadBits();
@@ -1406,6 +1547,7 @@ namespace Barotrauma.Networking
PreferredJob = preferredJob,
CharacterID = characterID,
Muted = muted,
InGame = inGame,
AllowKicking = allowKicking
});
}
@@ -1424,6 +1566,7 @@ namespace Barotrauma.Networking
{
SteamID = tc.SteamID,
Muted = tc.Muted,
InGame = tc.InGame,
AllowKicking = tc.AllowKicking
};
ConnectedClients.Add(existingClient);
@@ -1433,15 +1576,12 @@ namespace Barotrauma.Networking
existingClient.PreferredJob = tc.PreferredJob;
existingClient.Character = null;
existingClient.Muted = tc.Muted;
existingClient.InGame = tc.InGame;
existingClient.AllowKicking = tc.AllowKicking;
GameMain.NetLobbyScreen.SetPlayerNameAndJobPreference(existingClient);
if (tc.CharacterID > 0)
if (Screen.Selected != GameMain.NetLobbyScreen && tc.CharacterID > 0)
{
existingClient.Character = Entity.FindEntityByID(tc.CharacterID) as Character;
if (existingClient.Character == null)
{
updateClientListId = false;
}
existingClient.CharacterID = tc.CharacterID;
}
if (existingClient.ID == myID)
{
@@ -2440,7 +2580,7 @@ namespace Barotrauma.Networking
if (GUI.KeyboardDispatcher.Subscriber == null)
{
bool chatKeyHit = PlayerInput.KeyHit(InputType.Chat);
bool radioKeyHit = PlayerInput.KeyHit(InputType.RadioChat);
bool radioKeyHit = PlayerInput.KeyHit(InputType.RadioChat) && (Character.Controlled == null || Character.Controlled.SpeechImpediment < 0);
if (chatKeyHit || radioKeyHit)
{
@@ -2498,6 +2638,7 @@ namespace Barotrauma.Networking
{
var transfer = fileReceiver.ActiveTransfers.First();
GameMain.NetLobbyScreen.FileTransferFrame.Visible = true;
GameMain.NetLobbyScreen.FileTransferFrame.UserData = transfer;
GameMain.NetLobbyScreen.FileTransferTitle.Text =
ToolBox.LimitString(
TextManager.GetWithVariable("DownloadingFile", "[filename]", transfer.FileName),

View File

@@ -91,6 +91,7 @@ namespace Barotrauma.Networking
return;
}
incomingLidgrenMessages.Clear();
netClient.ReadMessages(incomingLidgrenMessages);
foreach (NetIncomingMessage inc in incomingLidgrenMessages)
@@ -107,8 +108,6 @@ namespace Barotrauma.Networking
break;
}
}
incomingLidgrenMessages.Clear();
}
private void HandleDataMessage(NetIncomingMessage inc)

View File

@@ -1314,79 +1314,69 @@ namespace Barotrauma.Steam
return upToDate;
}
public static bool AutoUpdateWorkshopItems()
public static async Task<bool> AutoUpdateWorkshopItems()
{
if (!isInitialized) { return false; }
var query = new Steamworks.Ugc.Query(Steamworks.UgcType.All)
.WhereUserSubscribed()
.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)
{
cancelToken.ThrowIfCancellationRequested();
}
try
{
if (!item.IsInstalled || !CheckWorkshopItemEnabled(item) || CheckWorkshopItemUpToDate(item)) { continue; }
if (!UpdateWorkshopItem(item, out string errorMsg))
{
DebugConsole.ThrowError(errorMsg);
new GUIMessageBox(
TextManager.Get("Error"),
TextManager.GetWithVariables("WorkshopItemUpdateFailed", new string[2] { "[itemname]", "[errormessage]" }, new string[2] { item.Title, errorMsg }));
}
else
{
//TODO: potential race condition
new GUIMessageBox("", TextManager.GetWithVariable("WorkshopItemUpdated", "[itemname]", item.Title));
}
}
catch (Exception e)
if (!item.IsInstalled || !CheckWorkshopItemEnabled(item) || CheckWorkshopItemUpToDate(item)) { continue; }
if (!UpdateWorkshopItem(item, out string errorMsg))
{
DebugConsole.ThrowError(errorMsg);
new GUIMessageBox(
TextManager.Get("Error"),
TextManager.GetWithVariables("WorkshopItemUpdateFailed", new string[2] { "[itemname]", "[errormessage]" }, new string[2] { item.Title, e.Message + ", " + e.TargetSite }));
GameAnalyticsManager.AddErrorEventOnce(
"SteamManager.AutoUpdateWorkshopItems:" + e.Message,
GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
"Failed to autoupdate workshop item \"" + item.Title + "\". " + e.Message + "\n" + e.StackTrace);
TextManager.GetWithVariables("WorkshopItemUpdateFailed", new string[2] { "[itemname]", "[errormessage]" }, new string[2] { item.Title, errorMsg }));
}
else
{
//TODO: potential race condition
new GUIMessageBox("", TextManager.GetWithVariable("WorkshopItemUpdated", "[itemname]", item.Title));
}
}
processedResults += resultPage.Value.ResultCount;
pageIndex++;
if (processedResults < resultPage?.TotalCount)
catch (Exception e)
{
resultPage = await query.GetPageAsync(pageIndex);
}
else
{
resultPage = null;
new GUIMessageBox(
TextManager.Get("Error"),
TextManager.GetWithVariables("WorkshopItemUpdateFailed", new string[2] { "[itemname]", "[errormessage]" }, new string[2] { item.Title, e.Message + ", " + e.TargetSite }));
GameAnalyticsManager.AddErrorEventOnce(
"SteamManager.AutoUpdateWorkshopItems:" + e.Message,
GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
"Failed to autoupdate workshop item \"" + item.Title + "\". " + e.Message + "\n" + e.StackTrace);
}
}
}, cancelToken, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
task.Wait(10000);
if (!task.IsCompleted)
{
cancelTokenSource.Cancel();
task.Wait();
processedResults += resultPage.Value.ResultCount;
pageIndex++;
if (processedResults < resultPage?.TotalCount)
{
resultPage = await query.GetPageAsync(pageIndex);
}
else
{
resultPage = null;
}
}
return task.Status == TaskStatus.RanToCompletion;
return true;
}
public static bool UpdateWorkshopItem(Steamworks.Ugc.Item? item, out string errorMsg)

View File

@@ -119,7 +119,7 @@ namespace Barotrauma.Networking
GameMain.NetLobbyScreen?.SetPlayerSpeaking(client);
GameMain.GameSession?.CrewManager?.SetClientSpeaking(client);
if (client.VoipSound.CurrentAmplitude > 0.1f) //TODO: might need to tweak
if ((client.VoipSound.CurrentAmplitude * client.VoipSound.Gain * GameMain.SoundManager.GetCategoryGainMultiplier("voip")) > 0.1f) //TODO: might need to tweak
{
if (client.Character != null && !client.Character.Removed)
{

View File

@@ -7,6 +7,7 @@ using System.Text;
using GameAnalyticsSDK.Net;
using Barotrauma.Steam;
using System.Diagnostics;
using System.Runtime.InteropServices;
#if WINDOWS
using SharpDX;
@@ -22,42 +23,56 @@ namespace Barotrauma
/// </summary>
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>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main(string[] args)
{
GameMain game = null;
string executableDir = "";
#if !DEBUG
AppDomain currentDomain = AppDomain.CurrentDomain;
currentDomain.UnhandledException += new UnhandledExceptionEventHandler(CrashHandler);
#endif
#if LINUX
setLinuxEnv();
#endif
Game = null;
executableDir = Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location);
Directory.SetCurrentDirectory(executableDir);
SteamManager.Initialize();
Game = new GameMain(args);
Game.Run();
Game.Dispose();
}
private static GameMain Game;
private static void CrashHandler(object sender, UnhandledExceptionEventArgs args)
{
try
{
#endif
executableDir = Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location);
Directory.SetCurrentDirectory(executableDir);
SteamManager.Initialize();
game = new GameMain(args);
game.Run();
game.Dispose();
#if !DEBUG
Game?.Exit();
CrashDump(Game, "crashreport.log", (Exception)args.ExceptionObject);
Game?.Dispose();
}
catch (Exception e)
catch
{
try
{
CrashDump(game, Path.Combine(executableDir,"crashreport.log"), e);
}
catch (Exception e2)
{
CrashMessageBox("Barotrauma seems to have crashed, and failed to generate a crash report: "
+ e2.Message + "\n" + e2.StackTrace.ToString(),
null);
}
game?.Dispose();
//exception handler is broken, we have a serious problem here!!
return;
}
#endif
}
public static void CrashMessageBox(string message, string filePath)
@@ -83,16 +98,9 @@ namespace Barotrauma
string exePath = System.Reflection.Assembly.GetEntryAssembly().Location;
var md5 = System.Security.Cryptography.MD5.Create();
Md5Hash exeHash = null;
try
using (var stream = File.OpenRead(exePath))
{
using (var stream = File.OpenRead(exePath))
{
exeHash = new Md5Hash(stream);
}
}
catch
{
//gotta catch them all, we don't want to throw an exception while writing a crash report
exeHash = new Md5Hash(stream);
}
StreamWriter sw = new StreamWriter(filePath);
@@ -217,4 +225,4 @@ namespace Barotrauma
}
}
#endif
}
}

View File

@@ -392,18 +392,19 @@ namespace Barotrauma
{
nameText.Text = Path.GetFileNameWithoutExtension(saveFile);
XDocument doc = SaveUtil.LoadGameSessionDoc(saveFile);
if (doc.Root.GetChildElement("multiplayercampaign") != null)
{
//multiplayer campaign save in the wrong folder -> don't show the save
saveList.Content.RemoveChild(saveFrame);
continue;
}
if (doc?.Root == null)
{
DebugConsole.ThrowError("Error loading save file \"" + saveFile + "\". The file may be corrupted.");
nameText.TextColor = GUI.Style.Red;
continue;
}
if (doc.Root.GetChildElement("multiplayercampaign") != null)
{
//multiplayer campaign save in the wrong folder -> don't show the save
saveList.Content.RemoveChild(saveFrame);
continue;
}
subName = doc.Root.GetAttributeString("submarine", "");
saveTime = doc.Root.GetAttributeString("savetime", "");
contentPackageStr = doc.Root.GetAttributeString("selectedcontentpackages", "");

View File

@@ -3347,6 +3347,7 @@ namespace Barotrauma.CharacterEditor
void CreateCloseButton(SerializableEntityEditor editor, Action onButtonClicked, float size = 1)
{
if (editor == null) { return; }
int height = 30;
var parent = new GUIFrame(new RectTransform(new Point(editor.Rect.Width, (int)(height * size * GUI.yScale)), editor.RectTransform, isFixedSize: true), style: null)
{
@@ -3366,6 +3367,7 @@ namespace Barotrauma.CharacterEditor
void CreateAddButtonAtLast(ParamsEditor editor, Action onButtonClicked, string text)
{
if (editor == null) { return; }
var parentFrame = new GUIFrame(new RectTransform(new Point(editor.EditorBox.Rect.Width, (int)(50 * GUI.yScale)), editor.EditorBox.Content.RectTransform), style: null, color: ParamsEditor.Color)
{
CanBeFocused = false
@@ -3383,6 +3385,7 @@ namespace Barotrauma.CharacterEditor
void CreateAddButton(SerializableEntityEditor editor, Action onButtonClicked, string text)
{
if (editor == null) { return; }
var parent = new GUIFrame(new RectTransform(new Point(editor.Rect.Width, (int)(60 * GUI.yScale)), editor.RectTransform), style: null)
{
CanBeFocused = false
@@ -4386,7 +4389,7 @@ namespace Barotrauma.CharacterEditor
ResetParamsEditor();
}
limb.PullJointWorldAnchorA = ScreenToSim(PlayerInput.MousePosition);
TryUpdateLimbParam(limb, "pullpos", ConvertUnits.ToDisplayUnits(limb.PullJointLocalAnchorA / limb.Params.Ragdoll.LimbScale));
TryUpdateLimbParam(limb, "pullpos", ConvertUnits.ToDisplayUnits(limb.PullJointLocalAnchorA / limb.Params.Scale / limb.Params.Ragdoll.LimbScale));
GUI.DrawLine(spriteBatch, SimToScreen(limb.SimPosition), tformedPullPos, Color.MediumPurple);
});
}
@@ -4469,7 +4472,7 @@ namespace Barotrauma.CharacterEditor
if (joint.BodyA == limb.body.FarseerBody)
{
joint.LocalAnchorA += input;
Vector2 transformedValue = ConvertUnits.ToDisplayUnits(joint.LocalAnchorA / RagdollParams.JointScale);
Vector2 transformedValue = ConvertUnits.ToDisplayUnits(joint.LocalAnchorA / joint.Scale);
TryUpdateJointParam(joint, "limb1anchor", transformedValue);
// Snap all selected joints to the first selected
if (copyJointSettings)
@@ -4484,7 +4487,7 @@ namespace Barotrauma.CharacterEditor
else if (joint.BodyB == limb.body.FarseerBody)
{
joint.LocalAnchorB += input;
Vector2 transformedValue = ConvertUnits.ToDisplayUnits(joint.LocalAnchorB / RagdollParams.JointScale);
Vector2 transformedValue = ConvertUnits.ToDisplayUnits(joint.LocalAnchorB / joint.Scale);
TryUpdateJointParam(joint, "limb2anchor", transformedValue);
// Snap all selected joints to the first selected
if (copyJointSettings)
@@ -4504,12 +4507,12 @@ namespace Barotrauma.CharacterEditor
if (joint.BodyA == limb.body.FarseerBody && otherJoint.BodyA == otherLimb.body.FarseerBody)
{
otherJoint.LocalAnchorA = joint.LocalAnchorA;
TryUpdateJointParam(otherJoint, "limb1anchor", ConvertUnits.ToDisplayUnits(joint.LocalAnchorA / RagdollParams.JointScale));
TryUpdateJointParam(otherJoint, "limb1anchor", ConvertUnits.ToDisplayUnits(joint.LocalAnchorA / joint.Scale));
}
else if (joint.BodyB == limb.body.FarseerBody && otherJoint.BodyB == otherLimb.body.FarseerBody)
{
otherJoint.LocalAnchorB = joint.LocalAnchorB;
TryUpdateJointParam(otherJoint, "limb2anchor", ConvertUnits.ToDisplayUnits(joint.LocalAnchorB / RagdollParams.JointScale));
TryUpdateJointParam(otherJoint, "limb2anchor", ConvertUnits.ToDisplayUnits(joint.LocalAnchorB / joint.Scale));
}
});
}
@@ -4873,10 +4876,10 @@ namespace Barotrauma.CharacterEditor
{
// We want the collider to be slightly smaller than the source rect, because the source rect is usually a bit bigger than the graphic.
float multiplier = 0.85f;
l.body.SetSize(new Vector2(ConvertUnits.ToSimUnits(width), ConvertUnits.ToSimUnits(height)) * RagdollParams.LimbScale * RagdollParams.TextureScale * multiplier);
TryUpdateLimbParam(l, "radius", ConvertUnits.ToDisplayUnits(l.body.radius / RagdollParams.LimbScale / RagdollParams.TextureScale));
TryUpdateLimbParam(l, "width", ConvertUnits.ToDisplayUnits(l.body.width / RagdollParams.LimbScale / RagdollParams.TextureScale));
TryUpdateLimbParam(l, "height", ConvertUnits.ToDisplayUnits(l.body.height / RagdollParams.LimbScale / RagdollParams.TextureScale));
l.body.SetSize(new Vector2(ConvertUnits.ToSimUnits(width), ConvertUnits.ToSimUnits(height)) * l.Scale * RagdollParams.TextureScale * multiplier);
TryUpdateLimbParam(l, "radius", ConvertUnits.ToDisplayUnits(l.body.radius / l.Params.Scale / RagdollParams.LimbScale / RagdollParams.TextureScale));
TryUpdateLimbParam(l, "width", ConvertUnits.ToDisplayUnits(l.body.width / l.Params.Scale / RagdollParams.LimbScale / RagdollParams.TextureScale));
TryUpdateLimbParam(l, "height", ConvertUnits.ToDisplayUnits(l.body.height / l.Params.Scale / RagdollParams.LimbScale / RagdollParams.TextureScale));
}
void RecalculateOrigin(Limb l)
{
@@ -4963,7 +4966,7 @@ namespace Barotrauma.CharacterEditor
{
continue;
}
Vector2 tformedJointPos = jointPos = jointPos / RagdollParams.JointScale / limb.TextureScale * spriteSheetZoom;
Vector2 tformedJointPos = jointPos = jointPos / joint.Scale / limb.TextureScale * spriteSheetZoom;
tformedJointPos.Y = -tformedJointPos.Y;
tformedJointPos.X *= character.AnimController.Dir;
tformedJointPos += limbScreenPos;
@@ -4991,11 +4994,11 @@ namespace Barotrauma.CharacterEditor
Vector2 input = ConvertUnits.ToSimUnits(scaledMouseSpeed);
input.Y = -input.Y;
input.X *= character.AnimController.Dir;
input *= RagdollParams.JointScale * limb.TextureScale / spriteSheetZoom;
input *= joint.Scale * limb.TextureScale / spriteSheetZoom;
if (joint.BodyA == limb.body.FarseerBody)
{
joint.LocalAnchorA += input;
Vector2 transformedValue = ConvertUnits.ToDisplayUnits(joint.LocalAnchorA / RagdollParams.JointScale);
Vector2 transformedValue = ConvertUnits.ToDisplayUnits(joint.LocalAnchorA / joint.Scale);
TryUpdateJointParam(joint, "limb1anchor", transformedValue);
// Snap all selected joints to the first selected
if (copyJointSettings)
@@ -5010,7 +5013,7 @@ namespace Barotrauma.CharacterEditor
else if (joint.BodyB == limb.body.FarseerBody)
{
joint.LocalAnchorB += input;
Vector2 transformedValue = ConvertUnits.ToDisplayUnits(joint.LocalAnchorB / RagdollParams.JointScale);
Vector2 transformedValue = ConvertUnits.ToDisplayUnits(joint.LocalAnchorB / joint.Scale);
TryUpdateJointParam(joint, "limb2anchor", transformedValue);
// Snap all selected joints to the first selected
if (copyJointSettings)
@@ -5029,12 +5032,12 @@ namespace Barotrauma.CharacterEditor
if (joint.BodyA == limb.body.FarseerBody && otherJoint.BodyA == otherLimb.body.FarseerBody)
{
otherJoint.LocalAnchorA = joint.LocalAnchorA;
TryUpdateJointParam(otherJoint, "limb1anchor", ConvertUnits.ToDisplayUnits(joint.LocalAnchorA / RagdollParams.JointScale));
TryUpdateJointParam(otherJoint, "limb1anchor", ConvertUnits.ToDisplayUnits(joint.LocalAnchorA / joint.Scale));
}
else if (joint.BodyB == limb.body.FarseerBody && otherJoint.BodyB == otherLimb.body.FarseerBody)
{
otherJoint.LocalAnchorB = joint.LocalAnchorB;
TryUpdateJointParam(otherJoint, "limb2anchor", ConvertUnits.ToDisplayUnits(joint.LocalAnchorB / RagdollParams.JointScale));
TryUpdateJointParam(otherJoint, "limb2anchor", ConvertUnits.ToDisplayUnits(joint.LocalAnchorB / joint.Scale));
}
});
}

View File

@@ -91,7 +91,13 @@ namespace Barotrauma
c.DoVisibilityCheck(cam);
if (c.IsVisible != wasVisible)
{
c.AnimController.Limbs.ForEach(l => { if (l.LightSource != null) l.LightSource.Enabled = c.IsVisible; });
c.AnimController.Limbs.ForEach(l =>
{
if (l.LightSource != null)
{
l.LightSource.Enabled = c.IsVisible;
}
});
}
}
@@ -282,14 +288,23 @@ namespace Barotrauma
}
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, SamplerState.LinearWrap, DepthStencilState.None, null, null, cam.Transform);
foreach (Character c in Character.CharacterList) c.DrawFront(spriteBatch, cam);
if (Level.Loaded != null) Level.Loaded.DrawFront(spriteBatch, cam);
if (GameMain.DebugDraw && GameMain.GameSession?.EventManager != null)
foreach (Character c in Character.CharacterList)
{
GameMain.GameSession.EventManager.DebugDraw(spriteBatch);
c.DrawFront(spriteBatch, cam);
}
if (Level.Loaded != null)
{
Level.Loaded.DrawFront(spriteBatch, cam);
}
if (GameMain.DebugDraw)
{
MapEntity.mapEntityList.ForEach(me => me.AiTarget?.Draw(spriteBatch));
Character.CharacterList.ForEach(c => c.AiTarget?.Draw(spriteBatch));
if (GameMain.GameSession?.EventManager != null)
{
GameMain.GameSession.EventManager.DebugDraw(spriteBatch);
}
}
spriteBatch.End();
if (GameMain.LightManager.LosEnabled && GameMain.LightManager.LosMode != LosMode.None && Character.Controlled != null)

View File

@@ -517,8 +517,21 @@ namespace Barotrauma
{
foreach (XElement element in doc.Root.Elements())
{
if (element.Name.ToString().ToLowerInvariant() != genParams.Name.ToLowerInvariant()) continue;
SerializableProperty.SerializeProperties(genParams, element, true);
XElement levelParamElement = element;
if (element.IsOverride())
{
foreach (XElement subElement in element.Elements())
{
if (subElement.Name.ToString().Equals(genParams.Name, StringComparison.OrdinalIgnoreCase))
{
SerializableProperty.SerializeProperties(genParams, subElement, true);
}
}
}
else if (element.Name.ToString().Equals(genParams.Name, StringComparison.OrdinalIgnoreCase))
{
SerializableProperty.SerializeProperties(genParams, element, true);
}
break;
}
}

View File

@@ -79,8 +79,7 @@ namespace Barotrauma
private IEnumerable<object> LoadRound()
{
GameMain.GameSession.StartRound(campaignUI.SelectedLevel,
reloadSub: true,
loadSecondSub: false,
reloadSub: true,
mirrorLevel: GameMain.GameSession.Map.CurrentLocation != GameMain.GameSession.Map.SelectedConnection.Locations[0]);
GameMain.GameScreen.Select();

View File

@@ -443,7 +443,7 @@ namespace Barotrauma
{
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.FileReceiver.StopTransfer(transfer);
return true;
@@ -658,7 +658,7 @@ namespace Barotrauma
OnClicked = (btn, obj) =>
{
GameMain.Client.RequestStartRound();
CoroutineManager.StartCoroutine(WaitForStartRound(StartButton, allowCancel: true), "WaitForStartRound");
CoroutineManager.StartCoroutine(WaitForStartRound(StartButton, allowCancel: false), "WaitForStartRound");
return true;
}
};
@@ -1147,6 +1147,22 @@ namespace Barotrauma
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)
{
GUI.SetCursorWaiting();
@@ -1173,7 +1189,8 @@ namespace Barotrauma
}
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));
yield return CoroutineStatus.Running;
@@ -1322,6 +1339,8 @@ namespace Barotrauma
if (GameMain.Client == null) return;
spectateButton.Visible = true;
spectateButton.Enabled = true;
StartButton.Visible = false;
}
public void SetCampaignCharacterInfo(CharacterInfo newCampaignCharacterInfo)
@@ -2745,11 +2764,11 @@ namespace Barotrauma
availableJobs = availableJobs.ToList();
int itemsInRow = 1;
int itemsInRow = 0;
foreach (var jobPrefab in availableJobs)
{
if (itemsInRow >= 4)
if (itemsInRow >= 3)
{
row = new GUILayoutGroup(new RectTransform(Vector2.One, rows.RectTransform), true);
itemsInRow = 0;

View File

@@ -741,6 +741,11 @@ namespace Barotrauma
{
base.Select();
GameMain.LightManager.AmbientLight =
Level.Loaded?.GenerationParams?.AmbientLightColor ??
LevelGenerationParams.LevelParams?.FirstOrDefault()?.AmbientLightColor ??
new Color(20, 20, 20, 255);
UpdateEntityList();
string name = (Submarine.MainSub == null) ? TextManager.Get("unspecifiedsubfilename") : Submarine.MainSub.Name;
@@ -1624,7 +1629,10 @@ namespace Barotrauma
foreach (Item item in Item.ItemList)
{
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"))
@@ -1790,6 +1798,7 @@ namespace Barotrauma
}
MapEntity.DeselectAll();
MapEntity.FilteredSelectedList.Clear();
}
private void RemoveDummyCharacter()

View File

@@ -569,15 +569,13 @@ namespace Barotrauma
Font = GUI.SmallFont,
Text = value,
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);
textBox.Deselect();
}
return true;
TrySendNetworkUpdate(entity, property);
textBox.Text = (string)property.GetValue(entity);
}
};
if (translationTextTag != null)

View File

@@ -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; }
@@ -112,17 +112,19 @@ namespace Barotrauma
foreach (ParticleEmitter emitter in particleEmitters)
{
float angle = 0.0f;
float particleRotation = 0.0f;
if (emitter.Prefab.CopyEntityAngle)
{
if (entity is Item item && item.body != null)
{
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)

View File

@@ -2,11 +2,11 @@
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>netcoreapp3.0</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product>
<Version>0.9.7.1</Version>
<Version>0.9.8.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName>

View File

@@ -2,11 +2,11 @@
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>netcoreapp3.0</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product>
<Version>0.9.7.1</Version>
<Version>0.9.8.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName>

View File

@@ -2,16 +2,17 @@
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>netcoreapp3.0</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product>
<Version>0.9.7.1</Version>
<Version>0.9.8.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName>
<ApplicationIcon>..\BarotraumaShared\Icon.ico</ApplicationIcon>
<Configurations>Debug;Release;Unstable</Configurations>
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">

View File

@@ -0,0 +1 @@
./DedicatedServer

View File

@@ -2,11 +2,11 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.0</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product>
<Version>0.9.7.1</Version>
<Version>0.9.8.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName>
@@ -54,6 +54,7 @@
<Content Include="..\BarotraumaShared\**\*" CopyToOutputDirectory="PreserveNewest" />
<Content Remove="..\BarotraumaShared\**\*.cs" />
<Compile Include="..\BarotraumaShared\**\*.cs" />
<Content Include="DedicatedServer.exe" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
<ItemGroup Condition="'$(Configuration)'!='Debug'">

View File

@@ -2,11 +2,11 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.0</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product>
<Version>0.9.7.1</Version>
<Version>0.9.8.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName>
@@ -61,6 +61,7 @@
<Compile Include="..\BarotraumaShared\**\*.cs" />
<Content Remove="..\BarotraumaShared\libsteam_api64.dylib" />
<Content Remove="..\BarotraumaShared\libsteam_api64.so" />
<Content Remove="DedicatedServer.exe" />
</ItemGroup>
<ItemGroup Condition="'$(Configuration)'!='Debug'">

View File

@@ -52,7 +52,7 @@ namespace Barotrauma
if (memInput.Count > 0)
{
prevDequeuedInput = dequeuedInput;
dequeuedInput = memInput[memInput.Count - 1].states;
dequeuedInput = memInput[memInput.Count - 1].states & InputNetFlags.Ragdoll;
memInput.RemoveAt(memInput.Count - 1);
}
}
@@ -333,6 +333,14 @@ namespace Barotrauma
attack = dequeuedInput.HasFlag(InputNetFlags.Attack);
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(shoot);

View File

@@ -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);
for (int i = 0; i < randList.Count; i++)
@@ -44,7 +44,6 @@ namespace Barotrauma
randList[i].TeamID = Character.TeamType.Team2;
}
}
return true;
}
public override void Update(float deltaTime)

View File

@@ -33,6 +33,8 @@ namespace Barotrauma.Items.Components
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);
msg.WriteRangedInteger(itemIndex, -1, fabricationRecipes.Count - 1);
UInt16 userID = fabricatedItem == null || user == null ? (UInt16)0 : user.ID;

View File

@@ -17,9 +17,10 @@ namespace Barotrauma.Items.Components
GameServer.Log(c.Character.LogName + " entered \"" + newOutputValue + "\" on " + item.Name,
ServerLog.MessageType.ItemInteraction);
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)

View File

@@ -8,6 +8,11 @@ namespace Barotrauma
{
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)
{
string errorMsg = "";

View File

@@ -627,16 +627,21 @@ namespace Barotrauma.Networking
//game already started -> send start message immediately
if (gameStarted)
{
SendStartMessage(roundStartSeed, Submarine.MainSub, GameMain.GameSession.GameMode.Preset, connectedClient);
SendStartMessage(roundStartSeed, GameMain.GameSession.Level.Seed, GameMain.GameSession, connectedClient, true);
}
}
break;
case ClientPacketHeader.REQUEST_STARTGAMEFINALIZE:
if (gameStarted && connectedClient != null)
{
SendRoundStartFinalize(connectedClient);
}
break;
case ClientPacketHeader.UPDATE_LOBBY:
ClientReadLobby(inc);
break;
case ClientPacketHeader.UPDATE_INGAME:
if (!gameStarted) return;
if (!gameStarted) { return; }
ClientReadIngame(inc);
break;
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 ? (ushort)0 : client.Character.ID);
outmsg.Write(client.Muted);
outmsg.Write(client.InGame);
outmsg.Write(client.Connection != OwnerConnection); //is kicking the player allowed
outmsg.WritePadBits();
}
@@ -1773,10 +1779,10 @@ namespace Barotrauma.Networking
//always allow the server owner to spectate even if it's disallowed in server settings
playingClients.RemoveAll(c => c.Connection == OwnerConnection && c.SpectateOnly);
if (GameMain.GameSession.GameMode.Mission != null &&
GameMain.GameSession.GameMode.Mission.AssignTeamIDs(playingClients))
if (GameMain.GameSession.GameMode.Mission != null)
{
teamCount = 2;
GameMain.GameSession.GameMode.Mission.AssignTeamIDs(playingClients);
teamCount = GameMain.GameSession.GameMode.Mission.TeamCount;
}
else
{
@@ -1796,9 +1802,10 @@ namespace Barotrauma.Networking
campaign.Map.SelectRandomLocation(preferUndiscovered: true);
}
SendStartMessage(roundStartSeed, campaign.Map.SelectedConnection.Level.Seed, GameMain.GameSession, connectedClients, false);
GameMain.GameSession.StartRound(campaign.Map.SelectedConnection.Level,
reloadSub: true,
loadSecondSub: teamCount > 1,
mirrorLevel: campaign.Map.CurrentLocation != campaign.Map.SelectedConnection.Locations[0]);
campaign.AssignClientCharacterInfos(connectedClients);
@@ -1808,7 +1815,9 @@ namespace Barotrauma.Networking
}
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("Submarine: " + selectedSub.Name, 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"));
SendStartMessage(roundStartSeed, Submarine.MainSub, GameMain.GameSession.GameMode.Preset, connectedClients);
yield return CoroutineStatus.Running;
GameMain.GameScreen.Select();
@@ -1965,35 +1972,34 @@ namespace Barotrauma.Networking
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)
{
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();
msg.Write((byte)ServerPacketHeader.STARTGAME);
msg.Write(seed);
msg.Write(GameMain.GameSession.Level.Seed);
msg.Write(GameMain.GameSession.Level.EqualityCheckVal);
msg.Write(levelSeed);
msg.Write(serverSettings.SelectedLevelDifficulty);
msg.Write((byte)GameMain.Config.LosMode);
msg.Write((byte)GameMain.NetLobbyScreen.MissionType);
msg.Write(selectedSub.Name);
msg.Write(selectedSub.MD5Hash.Hash);
msg.Write(gameSession.Submarine.Name);
msg.Write(gameSession.Submarine.MD5Hash.Hash);
msg.Write(serverSettings.UseRespawnShuttle);
msg.Write(GameMain.NetLobbyScreen.SelectedShuttle.Name);
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 ?
-1 : MissionPrefab.List.IndexOf(GameMain.GameSession.GameMode.Mission.Prefab)));
@@ -2002,13 +2008,33 @@ namespace Barotrauma.Networking
MissionMode missionMode = GameMain.GameSession.GameMode as MissionMode;
bool missionAllowRespawn = campaign == null && (missionMode?.Mission == null || missionMode.Mission.AllowRespawn);
msg.Write(serverSettings.AllowRespawn && missionAllowRespawn);
msg.Write(Submarine.MainSubs[1] != null); //loadSecondSub
msg.Write(serverSettings.AllowDisguises);
msg.Write(serverSettings.AllowRewiring);
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
var contentToPreload = GameMain.GameSession.EventManager.GetFilesToPreload();
msg.Write((ushort)contentToPreload.Count());
@@ -2017,12 +2043,8 @@ namespace Barotrauma.Networking
msg.Write((byte)contentFile.Type);
msg.Write(contentFile.Path);
}
serverSettings.WriteMonsterEnabled(msg);
msg.Write(GameMain.GameSession.Level.EqualityCheckVal);
GameMain.GameSession.Mission?.ServerWriteInitial(msg, client);
serverPeer.Send(msg, client.Connection, DeliveryMethod.Reliable);
}
public void EndGame()

View File

@@ -7,6 +7,7 @@ using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Runtime.InteropServices;
#endregion
@@ -17,53 +18,62 @@ namespace Barotrauma
/// </summary>
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>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main(string[] args)
{
GameMain game = null;
#if !DEBUG
try
{
AppDomain currentDomain = AppDomain.CurrentDomain;
currentDomain.UnhandledException += new UnhandledExceptionEventHandler(CrashHandler);
#endif
Console.WriteLine("Barotrauma Dedicated Server " + GameMain.Version +
" (" + AssemblyInfo.GetBuildString() + ", branch " + AssemblyInfo.GetGitBranch() + ", revision " + AssemblyInfo.GetGitRevision() + ")");
game = new GameMain(args);
game.Run();
if (GameSettings.SendUserStatistics) { GameAnalytics.OnQuit(); }
SteamManager.ShutDown();
#if !DEBUG
}
catch (Exception e)
{
CrashDump(game, "servercrashreport.log", e);
GameMain.Server?.NotifyCrash();
}
#if LINUX
setLinuxEnv();
#endif
Console.WriteLine("Barotrauma Dedicated Server " + GameMain.Version +
" (" + AssemblyInfo.GetBuildString() + ", branch " + AssemblyInfo.GetGitBranch() + ", revision " + AssemblyInfo.GetGitRevision() + ")");
Game = new GameMain(args);
Game.Run();
if (GameSettings.SendUserStatistics) { GameAnalytics.OnQuit(); }
SteamManager.ShutDown();
}
static void CrashDump(GameMain game, string filePath, Exception exception)
static GameMain Game;
private static void CrashHandler(object sender, UnhandledExceptionEventArgs args)
{
try
{
GameMain.Server?.ServerSettings?.SaveSettings();
GameMain.Server?.ServerSettings?.BanList.Save();
if (GameMain.Server?.ServerSettings?.KarmaPreset == "custom")
{
GameMain.Server?.KarmaManager?.SaveCustomPreset();
GameMain.Server?.KarmaManager?.Save();
}
Game?.Exit();
CrashDump("servercrashreport.log", (Exception)args.ExceptionObject);
GameMain.Server?.NotifyCrash();
}
//gotta catch them all, we don't want to crash while writing a crash report
catch (Exception e)
catch
{
string errorMsg = "Exception thrown while writing a crash report: " + e.Message + "\n" + e.StackTrace;
GameAnalyticsManager.AddErrorEventOnce("CrashDump:FailedToSaveSettings", EGAErrorSeverity.Error, errorMsg);
//exception handler is broken, we have a serious problem here!!
return;
}
}
static void CrashDump(string filePath, Exception exception)
{
GameMain.Server?.ServerSettings?.SaveSettings();
GameMain.Server?.ServerSettings?.BanList.Save();
if (GameMain.Server?.ServerSettings?.KarmaPreset == "custom")
{
GameMain.Server?.KarmaManager?.SaveCustomPreset();
GameMain.Server?.KarmaManager?.Save();
}
int existingFiles = 0;
@@ -81,9 +91,11 @@ namespace Barotrauma
sb.AppendLine("\n");
sb.AppendLine("Barotrauma seems to have crashed. Sorry for the inconvenience! ");
sb.AppendLine("\n");
sb.AppendLine("Game version " + GameMain.Version +
" (" + AssemblyInfo.GetBuildString() + ", branch " + AssemblyInfo.GetGitBranch() + ", revision " + AssemblyInfo.GetGitRevision() + ")");
sb.AppendLine("Selected content packages: " + (!GameMain.SelectedPackages.Any() ? "None" : string.Join(", ", GameMain.SelectedPackages.Select(c => c.Name))));
sb.AppendLine("Game version " + GameMain.Version + " (" + AssemblyInfo.GetBuildString() + ", branch " + AssemblyInfo.GetGitBranch() + ", revision " + AssemblyInfo.GetGitRevision() + ")");
if (GameMain.SelectedPackages != null)
{
sb.AppendLine("Selected content packages: " + (!GameMain.SelectedPackages.Any() ? "None" : string.Join(", ", GameMain.SelectedPackages.Select(c => c.Name))));
}
sb.AppendLine("Level seed: " + ((Level.Loaded == null) ? "no level loaded" : Level.Loaded.Seed));
sb.AppendLine("Loaded submarine: " + ((Submarine.MainSub == null) ? "None" : Submarine.MainSub.Name + " (" + Submarine.MainSub.MD5Hash + ")"));
sb.AppendLine("Selected screen: " + (Screen.Selected == null ? "None" : Screen.Selected.ToString()));

View File

@@ -2,11 +2,11 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.0</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product>
<Version>0.9.7.1</Version>
<Version>0.9.8.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName>
@@ -54,6 +54,7 @@
<Content Include="..\BarotraumaShared\**\*" CopyToOutputDirectory="PreserveNewest" />
<Content Remove="..\BarotraumaShared\**\*.cs" />
<Compile Include="..\BarotraumaShared\**\*.cs" />
<Content Remove="DedicatedServer.exe" />
</ItemGroup>
<ItemGroup Condition="'$(Configuration)'!='Debug'">

View File

@@ -1,5 +1,5 @@
<?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">
<?xml version="1.0" encoding="utf-8"?>
<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/Alien/alienitems.xml" />
<Item file="Content/Items/Button/button.xml" />
@@ -55,6 +55,7 @@
<Character file="Content/Characters/Charybdis/Charybdis.xml" />
<Character file="Content/Characters/Coelanth/Coelanth.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/Fractalguardian/Fractalguardian.xml" />
<Character file="Content/Characters/Fractalguardian2/Fractalguardian2.xml" />
@@ -73,7 +74,6 @@
<Character file="Content/Characters/Mudraptor/Mudraptor.xml" />
<Character file="Content/Characters/Mantis/Mantis.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/Tigerthresher/Tigerthresher.xml" />
<Character file="Content/Characters/Bonethresher/Bonethresher.xml" />
@@ -106,34 +106,34 @@
<Text file="Content/Texts/Japanese/JapaneseVanilla.xml" />
<Text file="Content/Texts/Turkish/TurkishVanilla.xml" />
<Text file="Content/Texts/Korean/KoreanVanilla.xml" />
<UIStyle file="Content/UI/style.xml"/>
<Afflictions file="Content/Afflictions.xml"/>
<UIStyle file="Content/UI/style.xml" />
<Afflictions file="Content/Afflictions.xml" />
<Structure file="Content/Map/StructurePrefabs.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" />
<Particles file="Content/Particles/ParticlePrefabs.xml"/>
<Decals file="Content/Particles/DecalPrefabs.xml"/>
<Particles file="Content/Particles/ParticlePrefabs.xml" />
<Decals file="Content/Particles/DecalPrefabs.xml" />
<RandomEvents file="Content/randomevents.xml" />
<EventManagerSettings file="Content/EventManagerSettings.xml" />
<LocationTypes file="Content/Map/locationTypes.xml" />
<MapGenerationParameters file="Content/Map/MapGenerationParameters.xml" />
<LevelGenerationParameters file="Content/Map/LevelGenerationParameters.xml" />
<LevelGenerationParameters file="Content/Map/LevelGenerationParameters.xml" />
<Missions file="Content/Missions.xml" />
<TraitorMissions file="Content/TraitorMissions.xml" />
<NPCConversations file="Content/NPCConversations/English/NpcConversations_English.xml"/>
<NPCConversations file="Content/NPCConversations/German/NpcConversations_German.xml"/>
<NPCConversations file="Content/NPCConversations/French/NpcConversations_French.xml"/>
<NPCConversations file="Content/NPCConversations/Russian/NpcConversations_Russian.xml"/>
<NPCConversations file="Content/NPCConversations/Polish/NpcConversations_Polish.xml"/>
<NPCConversations file="Content/NPCConversations/BrazilianPortuguese/NpcConversations_BrazilianPortuguese.xml"/>
<NPCConversations file="Content/NPCConversations/CastilianSpanish/NpcConversations_CastilianSpanish.xml"/>
<NPCConversations file="Content/NPCConversations/LatinamericanSpanish/NpcConversations_LatinamericanSpanish.xml"/>
<NPCConversations file="Content/NPCConversations/SimplifiedChinese/NpcConversations_SimplifiedChinese.xml"/>
<NPCConversations file="Content/NPCConversations/TraditionalChinese/NpcConversations_TraditionalChinese.xml"/>
<NPCConversations file="Content/NPCConversations/Japanese/NpcConversations_Japanese.xml"/>
<NPCConversations file="Content/NPCConversations/Turkish/NpcConversations_Turkish.xml"/>
<NPCConversations file="Content/NPCConversations/Korean/NpcConversations_Korean.xml"/>
<NPCConversations file="Content/NPCConversations/English/NpcConversations_English.xml" />
<NPCConversations file="Content/NPCConversations/German/NpcConversations_German.xml" />
<NPCConversations file="Content/NPCConversations/French/NpcConversations_French.xml" />
<NPCConversations file="Content/NPCConversations/Russian/NpcConversations_Russian.xml" />
<NPCConversations file="Content/NPCConversations/Polish/NpcConversations_Polish.xml" />
<NPCConversations file="Content/NPCConversations/BrazilianPortuguese/NpcConversations_BrazilianPortuguese.xml" />
<NPCConversations file="Content/NPCConversations/CastilianSpanish/NpcConversations_CastilianSpanish.xml" />
<NPCConversations file="Content/NPCConversations/LatinamericanSpanish/NpcConversations_LatinamericanSpanish.xml" />
<NPCConversations file="Content/NPCConversations/SimplifiedChinese/NpcConversations_SimplifiedChinese.xml" />
<NPCConversations file="Content/NPCConversations/TraditionalChinese/NpcConversations_TraditionalChinese.xml" />
<NPCConversations file="Content/NPCConversations/Japanese/NpcConversations_Japanese.xml" />
<NPCConversations file="Content/NPCConversations/Turkish/NpcConversations_Turkish.xml" />
<NPCConversations file="Content/NPCConversations/Korean/NpcConversations_Korean.xml" />
<Jobs file="Content/Jobs.xml" />
<Orders file="Content/Orders.xml" />
<Sounds file="Content/Sounds/sounds.xml" />
@@ -141,4 +141,4 @@
<SkillSettings file="Content/SkillSettings.xml" />
<Executable file="Barotrauma" />
<ServerExecutable file="DedicatedServer" />
</contentpackage>
</contentpackage>

View File

@@ -25,7 +25,7 @@ namespace Barotrauma
/// <summary>
/// How long does it take for the ai target to fade out if not kept alive.
/// </summary>
public float FadeOutTime { get; private set; } = 1;
public float FadeOutTime { get; private set; } = 2;
public bool Static { get; private set; }
public bool StaticSound { get; private set; }
@@ -92,7 +92,7 @@ namespace Barotrauma
public string SonarLabel;
public string SonarIconIdentifier;
public bool Enabled = true;
public bool Enabled => SoundRange > 0 || SightRange > 0;
public float MinSoundRange, MinSightRange;
public float MaxSoundRange = 100000, MaxSightRange = 100000;
@@ -195,7 +195,7 @@ namespace Barotrauma
public void Update(float deltaTime)
{
if (!Static && FadeOutTime > 0)
if (Enabled && !Static && FadeOutTime > 0)
{
// The aitarget goes silent/invisible if the components don't keep it active
if (!StaticSight)

View File

@@ -89,9 +89,6 @@ namespace Barotrauma
private readonly float memoryFadeTime = 0.5f;
private readonly float avoidTime = 3;
//Has the character been attacked since the last Update.
private bool wasAttacked;
private float avoidTimer;
public LatchOntoAI LatchOntoAI { get; private set; }
@@ -234,13 +231,6 @@ namespace Barotrauma
public override void Update(float deltaTime)
{
if (wasAttacked)
{
LatchOntoAI?.DeattachFromBody();
Character.AnimController.ReleaseStuckLimbs();
wasAttacked = false;
}
if (DisableEnemyAI) { return; }
base.Update(deltaTime);
@@ -305,7 +295,8 @@ namespace Barotrauma
FadeMemories(updateMemoriesInverval);
updateMemoriesTimer = updateMemoriesInverval;
}
if (Character.HealthPercentage <= FleeHealthThreshold)
if (Character.HealthPercentage <= FleeHealthThreshold && SelectedAiTarget != null &&
SelectedAiTarget.Entity is Character target && (target.IsPlayer || IsBeingChasedBy(target)))
{
State = AIState.Flee;
wallTarget = null;
@@ -384,9 +375,9 @@ namespace Barotrauma
State = AIState.Idle;
return;
}
float distance = Vector2.DistanceSquared(WorldPosition, SelectedAiTarget.WorldPosition);
float squaredDistance = Vector2.DistanceSquared(WorldPosition, SelectedAiTarget.WorldPosition);
var attackLimb = GetAttackLimb(SelectedAiTarget.WorldPosition);
if (attackLimb != null && distance <= Math.Pow(attackLimb.attack.Range, 2))
if (attackLimb != null && squaredDistance <= Math.Pow(attackLimb.attack.Range, 2))
{
run = true;
if (State == AIState.Avoid)
@@ -402,17 +393,18 @@ namespace Barotrauma
{
bool isBeingChased = IsBeingChased;
float reactDistance = !isBeingChased && selectedTargetingParams != null && selectedTargetingParams.ReactDistance > 0 ? selectedTargetingParams.ReactDistance : GetPerceivingRange(SelectedAiTarget);
if (distance <= Math.Pow(reactDistance + escapeMargin, 2))
if (squaredDistance <= Math.Pow(reactDistance + escapeMargin, 2))
{
float halfReactDistance = reactDistance / 2;
if (State == AIState.Aggressive || State == AIState.PassiveAggressive && distance < Math.Pow(halfReactDistance, 2))
float attackDistance = selectedTargetingParams != null && selectedTargetingParams.AttackDistance > 0 ? selectedTargetingParams.AttackDistance : halfReactDistance;
if (State == AIState.Aggressive || State == AIState.PassiveAggressive && squaredDistance < Math.Pow(attackDistance, 2))
{
run = true;
UpdateAttack(deltaTime);
}
else
{
run = isBeingChased ? true : distance < Math.Pow(halfReactDistance, 2);
run = isBeingChased ? true : squaredDistance < Math.Pow(halfReactDistance, 2);
if (escapeMargin <= 0)
{
escapeMargin = halfReactDistance;
@@ -440,13 +432,14 @@ namespace Barotrauma
SwarmBehavior.Refresh();
SwarmBehavior.UpdateSteering(deltaTime);
}
float speed = Character.AnimController.GetCurrentSpeed(run);
float speed = Character.AnimController.GetCurrentSpeed(run && Character.CanRun);
steeringManager.Update(speed);
Character.AnimController.TargetMovement = Character.ApplyMovementLimits(Steering, State == AIState.Idle && Character.AnimController.InWater ? Steering.Length() : speed);
if (Character.CurrentHull != null && Character.AnimController.InWater)
{
// Halve the swimming speed inside the sub
speed /= 2;
Character.AnimController.TargetMovement *= 0.5f;
}
steeringManager.Update(speed);
}
#region Idle
@@ -577,7 +570,7 @@ namespace Barotrauma
else if (SelectedAiTarget?.Entity is Character targetCharacter && targetCharacter.CurrentHull == Character.CurrentHull)
{
// Steer away from the target if in the same room
Vector2 escapeDir = Vector2.Normalize(SelectedAiTarget != null ? WorldPosition - SelectedAiTarget.WorldPosition : Character.GetTargetMovement());
Vector2 escapeDir = Vector2.Normalize(SelectedAiTarget != null ? WorldPosition - SelectedAiTarget.WorldPosition : Character.AnimController.TargetMovement);
if (!MathUtils.IsValid(escapeDir)) escapeDir = Vector2.UnitY;
SteeringManager.SteeringManual(deltaTime, escapeDir);
}
@@ -615,7 +608,7 @@ namespace Barotrauma
{
escapeTarget = null;
allGapsSearched = false;
Vector2 escapeDir = Vector2.Normalize(SelectedAiTarget != null ? WorldPosition - SelectedAiTarget.WorldPosition : Character.GetTargetMovement());
Vector2 escapeDir = Vector2.Normalize(SelectedAiTarget != null ? WorldPosition - SelectedAiTarget.WorldPosition : Character.AnimController.TargetMovement);
if (!MathUtils.IsValid(escapeDir)) escapeDir = Vector2.UnitY;
SteeringManager.SteeringManual(deltaTime, escapeDir);
if (Character.CurrentHull == null)
@@ -758,6 +751,10 @@ namespace Barotrauma
bool pursue = false;
if (IsCoolDownRunning)
{
if (AttackingLimb.attack.CoolDownTimer >= AttackingLimb.attack.CoolDown + AttackingLimb.attack.CurrentRandomCoolDown - AttackingLimb.attack.AfterAttackDelay)
{
return;
}
switch (AttackingLimb.attack.AfterAttack)
{
case AIBehaviorAfterAttack.Pursue:
@@ -1017,58 +1014,63 @@ namespace Barotrauma
return;
}
Vector2 offset = Character.SimPosition - steeringLimb.SimPosition;
// Offset so that we don't overshoot the movement
Vector2 steerPos = attackSimPos + offset;
if (SteeringManager is IndoorsSteeringManager pathSteering)
if (AttackingLimb != null && AttackingLimb.attack.Retreat)
{
if (pathSteering.CurrentPath != null)
UpdateFallBack(attackWorldPos, deltaTime, false);
}
else
{
Vector2 offset = Character.SimPosition - steeringLimb.SimPosition;
// Offset so that we don't overshoot the movement
Vector2 steerPos = attackSimPos + offset;
if (SteeringManager is IndoorsSteeringManager pathSteering)
{
// Attack doors
if (canAttackSub)
if (pathSteering.CurrentPath != null)
{
// If the target is in the same hull, there shouldn't be any doors blocking the path
if (targetCharacter == null || targetCharacter.CurrentHull != Character.CurrentHull)
// Attack doors
if (canAttackSub)
{
var door = pathSteering.CurrentPath.CurrentNode?.ConnectedDoor ?? pathSteering.CurrentPath.NextNode?.ConnectedDoor;
if (door != null && !door.IsOpen)
// If the target is in the same hull, there shouldn't be any doors blocking the path
if (targetCharacter == null || targetCharacter.CurrentHull != Character.CurrentHull)
{
if (door.Item.AiTarget != null && SelectedAiTarget != door.Item.AiTarget)
var door = pathSteering.CurrentPath.CurrentNode?.ConnectedDoor ?? pathSteering.CurrentPath.NextNode?.ConnectedDoor;
if (door != null && !door.IsOpen)
{
SelectTarget(door.Item.AiTarget, selectedTargetMemory.Priority);
return;
if (door.Item.AiTarget != null && SelectedAiTarget != door.Item.AiTarget)
{
SelectTarget(door.Item.AiTarget, selectedTargetMemory.Priority);
return;
}
}
}
}
}
// Steer towards the target if in the same room and swimming
if ((Character.AnimController.InWater || pursue) && targetCharacter != null && VisibleHulls.Contains(targetCharacter.CurrentHull))
{
SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(attackSimPos - steeringLimb.SimPosition));
// Steer towards the target if in the same room and swimming
if ((Character.AnimController.InWater || pursue) && targetCharacter != null && VisibleHulls.Contains(targetCharacter.CurrentHull))
{
SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(attackSimPos - steeringLimb.SimPosition));
}
else
{
SteeringManager.SteeringSeek(steerPos, 2);
// Switch to Idle when cannot reach the target and if cannot damage the walls
if ((!canAttackSub || wallTarget == null) && !pathSteering.IsPathDirty && pathSteering.CurrentPath.Unreachable)
{
State = AIState.Idle;
return;
}
}
}
else
{
SteeringManager.SteeringSeek(steerPos, 2);
// Switch to Idle when cannot reach the target and if cannot damage the walls
if ((!canAttackSub || wallTarget == null) && !pathSteering.IsPathDirty && pathSteering.CurrentPath.Unreachable)
{
State = AIState.Idle;
return;
}
SteeringManager.SteeringSeek(steerPos, 5);
}
}
else
{
SteeringManager.SteeringSeek(steerPos, 5);
SteeringManager.SteeringSeek(steerPos, 10);
SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: avoidLookAheadDistance, weight: 15);
}
}
else
{
SteeringManager.SteeringSeek(steerPos, 10);
SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: avoidLookAheadDistance, weight: 15);
}
if (canAttack)
{
if (!UpdateLimbAttack(deltaTime, AttackingLimb, attackSimPos, distance, attackTargetLimb))
@@ -1259,9 +1261,16 @@ namespace Barotrauma
float reactionTime = Rand.Range(0.1f, 0.3f);
updateTargetsTimer = Math.Min(updateTargetsTimer, reactionTime);
wasAttacked = true;
bool wasLatched = IsLatchedOnSub;
Character.AnimController.ReleaseStuckLimbs();
LatchOntoAI?.DeattachFromBody();
if (attacker == null || attacker.AiTarget == null) { return; }
if (wasLatched)
{
avoidTimer = avoidTime * Rand.Range(0.75f, 1.25f);
SelectTarget(attacker.AiTarget);
return;
}
if (State == AIState.Flee)
{
@@ -1272,22 +1281,30 @@ namespace Barotrauma
if (attackResult.Damage > 0.0f)
{
bool canAttack = attacker.Submarine == Character.Submarine && canAttackCharacters || attacker.Submarine != null && canAttackSub;
if (Character.Params.AI.AttackWhenProvoked)
if (Character.Params.AI.AttackWhenProvoked && canAttack)
{
if (canAttack)
if (attacker.IsHusk)
{
ChangeTargetState("husk", AIState.Attack, 100);
}
else
{
ChangeTargetState(attacker, AIState.Attack, 100);
}
}
else if (!AIParams.HasTag(attacker.SpeciesName))
{
if (attacker.AIController is EnemyAIController enemyAI)
if (attacker.IsHusk)
{
ChangeTargetState("husk", canAttack ? AIState.Attack : AIState.Escape, 100);
}
else if (attacker.AIController is EnemyAIController enemyAI)
{
if (enemyAI.CombatStrength > CombatStrength)
{
if (!AIParams.HasTag("stronger"))
{
ChangeTargetState(attacker, AIState.Escape, 100);
ChangeTargetState(attacker, canAttack ? AIState.Attack : AIState.Escape, 100);
}
}
else if (enemyAI.CombatStrength < CombatStrength)
@@ -1305,7 +1322,7 @@ namespace Barotrauma
}
else
{
ChangeTargetState(attacker, AIState.Escape, 100);
ChangeTargetState(attacker, canAttack ? AIState.Attack : AIState.Escape, 100);
}
}
}
@@ -1515,7 +1532,8 @@ namespace Barotrauma
if (targetCharacter.Submarine != Character.Submarine)
{
// In a different sub or the target is outside when we are inside or vice versa.
if (State == AIState.Avoid && State == AIState.Escape & State == AIState.Flee)
// Make an exception for humans so that creatures cannot avoid/flee from humans that are inside a sub.
if (!targetCharacter.IsHuman && State == AIState.Avoid && State == AIState.Escape & State == AIState.Flee)
{
// If we are escaping, let's not ignore the target entirely, because there can be a gaps where we or they can go freely
if (targetCharacter.Submarine != null)
@@ -1550,29 +1568,36 @@ namespace Barotrauma
// Ignore targets that are in the same group (treat them like they were of the same species)
continue;
}
if (enemy.CombatStrength > CombatStrength)
if (targetCharacter.IsHusk && AIParams.HasTag("husk"))
{
targetingTag = "stronger";
targetingTag = "husk";
}
else if (enemy.CombatStrength < CombatStrength)
else
{
targetingTag = "weaker";
}
if (targetingTag == "stronger" && (State == AIState.Avoid || State == AIState.Escape || State == AIState.Flee))
{
if (SelectedAiTarget == aiTarget)
if (enemy.CombatStrength > CombatStrength)
{
// Freightened -> hold on to the target
valueModifier *= 2;
targetingTag = "stronger";
}
if (IsBeingChasedBy(targetCharacter))
else if (enemy.CombatStrength < CombatStrength)
{
valueModifier *= 2;
targetingTag = "weaker";
}
if (Character.CurrentHull != null && !VisibleHulls.Contains(targetCharacter.CurrentHull))
if (targetingTag == "stronger" && (State == AIState.Avoid || State == AIState.Escape || State == AIState.Flee))
{
// Inside but in a different room
valueModifier /= 2;
if (SelectedAiTarget == aiTarget)
{
// Freightened -> hold on to the target
valueModifier *= 2;
}
if (IsBeingChasedBy(targetCharacter))
{
valueModifier *= 2;
}
if (Character.CurrentHull != null && !VisibleHulls.Contains(targetCharacter.CurrentHull))
{
// Inside but in a different room
valueModifier /= 2;
}
}
}
}
@@ -1746,6 +1771,9 @@ namespace Barotrauma
if (valueModifier > targetValue)
{
// Don't target items that we own.
// This is a rare case, and almost entirely related to Humanhusks, so let's check it last to reduce unnecessary checks (although the check shouldn't be expensive)
if (aiTarget.Entity is Item i && i.IsOwnedBy(character)) { continue; }
newTarget = aiTarget;
selectedTargetMemory = targetMemory;
targetValue = valueModifier;
@@ -1867,6 +1895,39 @@ namespace Barotrauma
private readonly Dictionary<string, CharacterParams.TargetParams> modifiedParams = 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>
/// Temporarily changes the predefined state for a target. Eg. Idle -> Attack.
/// </summary>
@@ -1874,50 +1935,27 @@ namespace Barotrauma
{
isStateChanged = true;
SetStateResetTimer();
ChangeParams(target.SpeciesName);
// Target also items, because if we are blind and the target doesn't move, we can only perceive the target when it uses items
if (state == AIState.Attack || state == AIState.Escape)
ChangeParams(target.SpeciesName, state, priority);
if (target.IsHuman)
{
ChangeParams("weapon");
ChangeParams("tool");
}
if (state == AIState.Attack)
{
// If the target is shooting from the submarine, we might not perceive it because it doesn't move.
// --> Target the submarine too.
if (target.Submarine != null && canAttackSub)
// Target also items, because if we are blind and the target doesn't move, we can only perceive the target when it uses items
if (state == AIState.Attack || state == AIState.Escape)
{
ChangeParams("room");
ChangeParams("wall");
ChangeParams("door");
ChangeParams("weapon", state, priority);
ChangeParams("tool", state, priority);
}
ChangeParams("provocative", onlyExisting: true);
ChangeParams("light", onlyExisting: true);
}
void ChangeParams(string tag, bool onlyExisting = false)
{
if (!AIParams.TryGetTarget(tag, out CharacterParams.TargetParams targetParams))
if (state == AIState.Attack)
{
if (!onlyExisting && !tempParams.ContainsKey(tag))
// If the target is shooting from the submarine, we might not perceive it because it doesn't move.
// --> Target the submarine too.
if (target.Submarine != null && canAttackSub)
{
if (AIParams.TryAddNewTarget(tag, state, priority ?? 100, out targetParams))
{
tempParams.Add(tag, targetParams);
}
}
}
if (targetParams != null)
{
if (priority.HasValue)
{
targetParams.Priority = priority.Value;
}
targetParams.State = state;
if (!modifiedParams.ContainsKey(tag))
{
modifiedParams.Add(tag, targetParams);
ChangeParams("room", state, priority);
ChangeParams("wall", state, priority);
ChangeParams("door", state, priority);
}
ChangeParams("provocative", state, priority, onlyExisting: true);
ChangeParams("light", state, priority, onlyExisting: true);
}
}
}

View File

@@ -170,12 +170,7 @@ namespace Barotrauma
}
}
}
if (run)
{
run = !AnimController.Crouching && !AnimController.IsMovingBackwards;
}
float currentSpeed = Character.AnimController.GetCurrentSpeed(run);
steeringManager.Update(currentSpeed);
steeringManager.Update(Character.AnimController.GetCurrentSpeed(run && Character.CanRun));
bool ignorePlatforms = Character.AnimController.TargetMovement.Y < -0.5f &&
(-Character.AnimController.TargetMovement.Y > Math.Abs(Character.AnimController.TargetMovement.X));
@@ -209,17 +204,6 @@ namespace Barotrauma
targetMovement = new Vector2(Character.AnimController.TargetMovement.X, MathHelper.Clamp(Character.AnimController.TargetMovement.Y, -1.0f, 1.0f));
}
float maxSpeed = Character.ApplyTemporarySpeedLimits(currentSpeed);
targetMovement.X = MathHelper.Clamp(targetMovement.X, -maxSpeed, maxSpeed);
targetMovement.Y = MathHelper.Clamp(targetMovement.Y, -maxSpeed, maxSpeed);
//apply speed multiplier if
// a. it's boosting the movement speed and the character is trying to move fast (= running)
// b. it's a debuff that decreases movement speed
float speedMultiplier = Character.SpeedMultiplier;
if (run || speedMultiplier <= 0.0f) targetMovement *= speedMultiplier;
Character.ResetSpeedMultiplier(); // Reset, items will set the value before the next update
if (Character.AnimController.InWater && targetMovement.LengthSquared() < 0.000001f)
{
bool isAiming = false;
@@ -243,7 +227,7 @@ namespace Barotrauma
}
}
Character.AnimController.TargetMovement = targetMovement;
Character.AnimController.TargetMovement = Character.ApplyMovementLimits(targetMovement, AnimController.GetCurrentSpeed(run));
flipTimer -= deltaTime;
if (flipTimer <= 0.0f)
@@ -323,7 +307,7 @@ namespace Barotrauma
|| ObjectiveManager.IsCurrentObjective<AIObjectiveFindSafety>()
|| ObjectiveManager.CurrentObjective.GetSubObjectivesRecursive(true).Any(o => o.KeepDivingGearOn);
bool removeDivingSuit = !Character.AnimController.HeadInWater && oxygenLow;
AIObjectiveGoTo gotoObjective = ObjectiveManager.CurrentOrder as AIObjectiveGoTo;
AIObjectiveGoTo gotoObjective = ObjectiveManager.GetActiveObjective<AIObjectiveGoTo>();
if (!removeDivingSuit)
{
bool targetHasNoSuit = gotoObjective != null && gotoObjective.mimic && !HasDivingSuit(gotoObjective.Target as Character);
@@ -536,14 +520,16 @@ namespace Barotrauma
Hull targetHull = null;
if (Character.CurrentHull != null)
{
bool isFighting = ObjectiveManager.HasActiveObjective<AIObjectiveCombat>();
bool isFleeing = ObjectiveManager.HasActiveObjective<AIObjectiveFindSafety>();
foreach (var hull in VisibleHulls)
{
foreach (Character c in Character.CharacterList)
foreach (Character target in Character.CharacterList)
{
if (c.CurrentHull != hull || !c.Enabled) { continue; }
if (AIObjectiveFightIntruders.IsValidTarget(c, Character))
if (target.CurrentHull != hull || !target.Enabled) { continue; }
if (AIObjectiveFightIntruders.IsValidTarget(target, Character))
{
if (AddTargets<AIObjectiveFightIntruders, Character>(Character, c) && newOrder == null)
if (AddTargets<AIObjectiveFightIntruders, Character>(Character, target) && newOrder == null)
{
var orderPrefab = Order.GetPrefab("reportintruders");
newOrder = new Order(orderPrefab, hull, null, orderGiver: Character);
@@ -560,42 +546,48 @@ namespace Barotrauma
targetHull = hull;
}
}
foreach (Character c in Character.CharacterList)
if (!isFighting)
{
if (c.CurrentHull != hull) { continue; }
if (AIObjectiveRescueAll.IsValidTarget(c, Character))
foreach (var gap in hull.ConnectedGaps)
{
if (AddTargets<AIObjectiveRescueAll, Character>(c, Character) && newOrder == null && !ObjectiveManager.HasActiveObjective<AIObjectiveRescue>())
if (AIObjectiveFixLeaks.IsValidTarget(gap, Character))
{
var orderPrefab = Order.GetPrefab("requestfirstaid");
newOrder = new Order(orderPrefab, hull, null, orderGiver: Character);
targetHull = hull;
if (AddTargets<AIObjectiveFixLeaks, Gap>(Character, gap) && newOrder == null && !gap.IsRoomToRoom)
{
var orderPrefab = Order.GetPrefab("reportbreach");
newOrder = new Order(orderPrefab, hull, null, orderGiver: Character);
targetHull = hull;
}
}
}
}
foreach (var gap in hull.ConnectedGaps)
{
if (AIObjectiveFixLeaks.IsValidTarget(gap, Character))
if (!isFleeing)
{
if (AddTargets<AIObjectiveFixLeaks, Gap>(Character, gap) && newOrder == null && !gap.IsRoomToRoom)
foreach (Character target in Character.CharacterList)
{
var orderPrefab = Order.GetPrefab("reportbreach");
newOrder = new Order(orderPrefab, hull, null, orderGiver: Character);
targetHull = hull;
if (target.CurrentHull != hull) { continue; }
if (AIObjectiveRescueAll.IsValidTarget(target, Character))
{
if (AddTargets<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)
{
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>())
foreach (Item item in Item.ItemList)
{
var orderPrefab = Order.GetPrefab("reportbrokendevices");
newOrder = new Order(orderPrefab, hull, item.Repairables?.FirstOrDefault(), orderGiver: Character);
targetHull = hull;
if (item.CurrentHull != hull) { continue; }
if (AIObjectiveRepairItems.IsValidTarget(item, Character))
{
if (item.Repairables.All(r => item.ConditionPercentage > r.AIRepairThreshold)) { continue; }
if (AddTargets<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).
return;
}
if (!attacker.IsRemotePlayer && Character.Controlled != attacker && attacker.AIController != null && attacker.AIController.Enabled)
if (attacker.IsBot)
{
// Don't retaliate on damage done by friendly ai, because we know that it's accidental
AddCombatObjective(AIObjectiveCombat.CombatMode.Retreat, Rand.Range(0.5f, 1f, Rand.RandSync.Unsynced));
@@ -664,9 +656,8 @@ namespace Barotrauma
}
else
{
float currentVitality = Character.CharacterHealth.Vitality;
float dmgPercentage = damage / currentVitality * 100;
if (dmgPercentage < currentVitality / 10)
float dmgPercentage = MathUtils.Percentage(damage, Character.CharacterHealth.Vitality);
if (dmgPercentage < 10)
{
// Don't retaliate on minor (accidental) dmg done by characters that are in the same team
AddCombatObjective(AIObjectiveCombat.CombatMode.Retreat, Rand.Range(0.5f, 1f, Rand.RandSync.Unsynced));
@@ -711,7 +702,6 @@ namespace Barotrauma
public void SetOrder(Order order, string option, Character orderGiver, bool speak = true)
{
SetOrderProjSpecific(order, option);
CurrentOrderOption = option;
CurrentOrder = order;
objectiveManager.SetOrder(order, option, orderGiver);
@@ -752,8 +742,6 @@ namespace Barotrauma
}
}
partial void SetOrderProjSpecific(Order order, string option);
public override void SelectTarget(AITarget target)
{
SelectedAiTarget = target;
@@ -806,11 +794,11 @@ namespace Barotrauma
/// </summary>
public static bool HasDivingMask(Character character, float conditionPercentage = 0) => HasItem(character, "divingmask", "oxygensource", conditionPercentage);
public static bool HasItem(Character character, string identifier, string containedTag, float conditionPercentage = 0)
public static bool HasItem(Character character, string tagOrIdentifier, string containedTag = null, float conditionPercentage = 0)
{
if (character == null) { return false; }
if (character.Inventory == null) { return false; }
var item = character.Inventory.FindItemByIdentifier(identifier) ?? character.Inventory.FindItemByTag(identifier);
var item = character.Inventory.FindItemByIdentifier(tagOrIdentifier) ?? character.Inventory.FindItemByTag(tagOrIdentifier);
return item != null &&
item.ConditionPercentage > conditionPercentage &&
character.HasEquippedItem(item) &&
@@ -934,7 +922,7 @@ namespace Barotrauma
visibleHulls = VisibleHulls;
}
// TODO: should we calculate the visible hulls for each hull? -> could be a bit heavy.
bool ignoreFire = ObjectiveManager.IsCurrentObjective<AIObjectiveExtinguishFires>() || objectiveManager.HasActiveObjective<AIObjectiveExtinguishFire>();
bool ignoreFire = objectiveManager.HasActiveObjective<AIObjectiveExtinguishFire>();
bool ignoreWater = HasDivingSuit(character);
bool ignoreOxygen = ignoreWater || HasDivingMask(character);
bool ignoreEnemies = ObjectiveManager.IsCurrentObjective<AIObjectiveFightIntruders>();
@@ -1022,15 +1010,23 @@ namespace Barotrauma
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; }
int count = 0;
foreach (var c in Character.CharacterList)
foreach (var other in Character.CharacterList)
{
if (FilterCrewMember(character, c))
if (onlyActive && !IsActive(other))
{
if (predicate == null || predicate(c.AIController as HumanAIController))
continue;
}
if (onlyBots && other.IsPlayer)
{
continue;
}
if (FilterCrewMember(character, other))
{
if (predicate == null || predicate(other.AIController as HumanAIController))
{
count++;
}
@@ -1058,7 +1054,7 @@ namespace Barotrauma
public void DoForEachCrewMember(Action<HumanAIController> action) => DoForEachCrewMember(Character, action);
public bool IsTrueForAnyCrewMember(Func<HumanAIController, bool> predicate) => IsTrueForAnyCrewMember(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
}
}

View File

@@ -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)
{
bool needsNewPath = character.Params.PathFinderPriority > 0.5f && (currentPath == null || currentPath.Unreachable || currentPath.NextNode == null || Vector2.DistanceSquared(target, currentTarget) > 1);
bool needsNewPath = character.Params.PathFinderPriority > 0.5f && (currentPath == null || currentPath.Unreachable || currentPath.Finished || Vector2.DistanceSquared(target, currentTarget) > 1);
//find a new path if one hasn't been found yet or the target is different from the current target
if (needsNewPath || findPathTimer < -1.0f)
{
@@ -308,7 +308,7 @@ namespace Barotrauma
currentPath.SkipToNextNode();
}
}
else
else if (!IsNextLadderSameAsCurrent)
{
Vector2 colliderBottom = character.AnimController.GetColliderBottom();
Vector2 colliderSize = collider.GetSize();
@@ -530,7 +530,6 @@ namespace Barotrauma
if (node.Waypoint != null && node.Waypoint.CurrentHull != null)
{
var hull = node.Waypoint.CurrentHull;
if (hull.FireSources.Count > 0)
{
foreach (FireSource fs in hull.FireSources)
@@ -538,9 +537,14 @@ namespace Barotrauma
penalty += fs.Size.X * 10.0f;
}
}
if (character.NeedsAir && hull.WaterVolume / hull.Rect.Width > 100.0f) penalty += 500.0f;
if (character.PressureProtection < 10.0f && hull.WaterVolume > hull.Volume) penalty += 1000.0f;
if (character.NeedsAir && hull.WaterVolume / hull.Rect.Width > 100.0f)
{
penalty += 500.0f;
}
if (character.PressureProtection < 10.0f && hull.WaterVolume > hull.Volume)
{
penalty += 1000.0f;
}
}
return penalty;

View File

@@ -253,7 +253,7 @@ namespace Barotrauma
jointDir = attachLimb.Dir;
Vector2 transformedLocalAttachPos = localAttachPos * attachLimb.character.AnimController.RagdollParams.LimbScale;
Vector2 transformedLocalAttachPos = localAttachPos * attachLimb.Scale * attachLimb.Params.Ragdoll.LimbScale;
if (jointDir < 0.0f) transformedLocalAttachPos.X = -transformedLocalAttachPos.X;
//transformedLocalAttachPos = Vector2.Transform(transformedLocalAttachPos, Matrix.CreateRotationZ(attachLimb.Rotation));

View File

@@ -32,6 +32,18 @@ namespace Barotrauma
public virtual bool UnequipItems => false;
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 PriorityModifier { get; private set; } = 1;
public readonly Character character;
@@ -59,6 +71,7 @@ namespace Barotrauma
/// </summary>
public virtual bool IsLoop { get; set; }
public IEnumerable<AIObjective> SubObjectives => subObjectives;
public AIObjective CurrentSubObjective => subObjectives.FirstOrDefault();
private readonly List<AIObjective> all = new List<AIObjective>();
public IEnumerable<AIObjective> GetSubObjectivesRecursive(bool includingSelf = false)
@@ -86,7 +99,7 @@ namespace Barotrauma
public AIObjective GetActiveObjective()
{
var subObjective = SubObjectives.FirstOrDefault();
var subObjective = CurrentSubObjective;
return subObjective == null ? this : subObjective.GetActiveObjective();
}
@@ -157,7 +170,8 @@ namespace Barotrauma
{
if (!AllowSubObjectiveSorting) { return; }
if (subObjectives.None()) { return; }
subObjectives.Sort((x, y) => y.GetPriority().CompareTo(x.GetPriority()));
subObjectives.ForEach(so => so.GetPriority());
subObjectives.Sort((x, y) => y.Priority.CompareTo(x.Priority));
if (ConcurrentObjectives)
{
subObjectives.ForEach(so => so.SortSubObjectives());
@@ -168,26 +182,38 @@ namespace Barotrauma
}
}
public virtual float GetPriority() => Priority * PriorityModifier;
public virtual bool IsDuplicate<T>(T otherObjective) where T : AIObjective => otherObjective.Option == Option;
public virtual void Update(float deltaTime)
/// <summary>
/// Call this only when the priority needs to be recalculated. Use the cached Priority property when you don't need to recalculate.
/// </summary>
public virtual float GetPriority()
{
if (objectiveManager.CurrentOrder == this)
{
Priority = AIObjectiveManager.OrderPriority;
}
else if (objectiveManager.WaitTimer <= 0)
else
{
if (objectiveManager.CurrentObjective != null)
{
if (objectiveManager.CurrentObjective == this || objectiveManager.CurrentObjective.subObjectives.Any(so => so == this))
{
Priority += Devotion * PriorityModifier * deltaTime;
}
}
Priority = MathHelper.Clamp(Priority, 0, 100);
Priority = CumulatedDevotion * PriorityModifier;
}
return Priority;
}
private void UpdateDevotion(float deltaTime)
{
var currentObjective = objectiveManager.CurrentObjective;
if (currentObjective != null && (currentObjective == this || currentObjective.subObjectives.Any(so => so == this)))
{
CumulatedDevotion += Devotion * PriorityModifier * deltaTime;
}
}
public virtual bool IsDuplicate<T>(T otherObjective) where T : AIObjective => otherObjective.Option == Option;
public virtual void Update(float deltaTime)
{
if (objectiveManager.CurrentOrder != this && objectiveManager.WaitTimer <= 0)
{
UpdateDevotion(deltaTime);
}
subObjectives.ForEach(so => so.Update(deltaTime));
}
@@ -264,6 +290,7 @@ namespace Barotrauma
public virtual void OnDeselected()
{
CumulatedDevotion = 0;
Deselected?.Invoke();
}
@@ -282,6 +309,7 @@ namespace Barotrauma
isCompleted = false;
hasBeenChecked = false;
_abandon = false;
CumulatedDevotion = 0;
}
protected abstract void Act(float deltaTime);

View File

@@ -100,7 +100,11 @@ namespace Barotrauma
}
}
public override float GetPriority() => (Enemy != null && (Enemy.Removed || Enemy.IsDead)) ? 0 : Math.Min(100 * PriorityModifier, 100);
public override float GetPriority()
{
Priority = (Enemy != null && (Enemy.Removed || Enemy.IsDead)) ? 0 : Math.Min(100 * PriorityModifier, 100);
return Priority;
}
public override void Update(float deltaTime)
{
@@ -139,7 +143,7 @@ namespace Barotrauma
}
if (seekAmmunition == null)
{
if (TryArm() && Enemy != null && !Enemy.Removed)
if (Mode != CombatMode.Retreat && TryArm() && Enemy != null && !Enemy.Removed)
{
OperateWeapon(deltaTime);
}

View File

@@ -74,15 +74,6 @@ namespace Barotrauma
}
}
public override float GetPriority()
{
if (objectiveManager.CurrentOrder == this)
{
return AIObjectiveManager.OrderPriority;
}
return 1.0f;
}
private bool CheckItem(Item i) => itemIdentifiers.Any(id => i.Prefab.Identifier == id || i.HasTag(id)) && i.ConditionPercentage > ConditionLevel;
protected override void Act(float deltaTime)

View File

@@ -54,15 +54,6 @@ namespace Barotrauma
protected override bool Check() => IsCompleted;
public override float GetPriority()
{
if (objectiveManager.CurrentOrder == this)
{
return AIObjectiveManager.OrderPriority;
}
return 1.0f;
}
protected override void Act(float deltaTime)
{
Item itemToDecontain = targetItem ?? sourceContainer.Inventory.FindItem(i => itemIdentifiers.Any(id => i.Prefab.Identifier == id || i.HasTag(id)), recursive: false);

View File

@@ -1,5 +1,4 @@
using Barotrauma.Items.Components;
using FarseerPhysics;
using Microsoft.Xna.Framework;
using System;
using System.Linq;
@@ -29,32 +28,44 @@ namespace Barotrauma
public override float GetPriority()
{
if (!objectiveManager.IsCurrentOrder<AIObjectiveExtinguishFires>()
&& Character.CharacterList.Any(c => c.CurrentHull == targetHull && !HumanAIController.IsFriendly(c) && HumanAIController.IsActive(c))) { return 0; }
float yDist = Math.Abs(character.WorldPosition.Y - targetHull.WorldPosition.Y);
yDist = yDist > 100 ? yDist * 3 : 0;
float dist = Math.Abs(character.WorldPosition.X - targetHull.WorldPosition.X) + yDist;
float distanceFactor = MathHelper.Lerp(1, 0.1f, MathUtils.InverseLerp(0, 5000, dist));
if (targetHull == character.CurrentHull)
&& Character.CharacterList.Any(c => c.CurrentHull == targetHull && !HumanAIController.IsFriendly(c) && HumanAIController.IsActive(c)))
{
distanceFactor = 1;
Priority = 0;
}
float severity = AIObjectiveExtinguishFires.GetFireSeverity(targetHull);
float severityFactor = MathHelper.Lerp(0, 1, severity / 100);
float devotion = Math.Min(Priority, 10) / 100;
return MathHelper.Lerp(0, 100, MathHelper.Clamp(devotion + severityFactor * distanceFactor, 0, 1));
else
{
float yDist = Math.Abs(character.WorldPosition.Y - targetHull.WorldPosition.Y);
yDist = yDist > 100 ? yDist * 3 : 0;
float dist = Math.Abs(character.WorldPosition.X - targetHull.WorldPosition.X) + yDist;
float distanceFactor = MathHelper.Lerp(1, 0.1f, MathUtils.InverseLerp(0, 5000, dist));
if (targetHull == character.CurrentHull)
{
distanceFactor = 1;
}
float severity = AIObjectiveExtinguishFires.GetFireSeverity(targetHull);
float severityFactor = MathHelper.Lerp(0, 1, severity / 100);
float devotion = CumulatedDevotion / 100;
Priority = MathHelper.Lerp(0, 100, MathHelper.Clamp(devotion + (severityFactor * distanceFactor * PriorityModifier), 0, 1));
}
return Priority;
}
protected override bool Check() => targetHull.FireSources.None();
private float sinTime;
protected override void Act(float deltaTime)
{
var extinguisherItem = character.Inventory.FindItemByIdentifier("extinguisher") ?? character.Inventory.FindItemByTag("extinguisher");
var extinguisherItem = character.Inventory.FindItemByIdentifier("fireextinguisher") ?? character.Inventory.FindItemByTag("fireextinguisher");
if (extinguisherItem == null || extinguisherItem.Condition <= 0.0f || !character.HasEquippedItem(extinguisherItem))
{
TryAddSubObjective(ref getExtinguisherObjective, () =>
{
character.Speak(TextManager.Get("DialogFindExtinguisher"), null, 2.0f, "findextinguisher", 30.0f);
return new AIObjectiveGetItem(character, "extinguisher", objectiveManager, equip: true);
return new AIObjectiveGetItem(character, "fireextinguisher", objectiveManager, equip: true)
{
// If the item is inside an unsafe hull, decrease the priority
GetItemPriority = i => HumanAIController.UnsafeHulls.Contains(i.CurrentHull) ? 0.1f : 1
};
});
}
else
@@ -79,8 +90,12 @@ namespace Barotrauma
{
useExtinquisherTimer = 0.0f;
}
// Aim
character.CursorPosition = fs.Position;
if (extinguisher.Item.RequireAimToUse)
Vector2 fromCharacterToFireSource = fs.WorldPosition - character.WorldPosition;
float dist = fromCharacterToFireSource.Length();
character.CursorPosition += VectorExtensions.Forward(extinguisherItem.body.TransformedRotation + (float)Math.Sin(sinTime) / 2, dist / 2);
if (extinguisherItem.RequireAimToUse)
{
bool isOperatingButtons = false;
if (SteeringManager == PathSteering)
@@ -95,8 +110,9 @@ namespace Barotrauma
{
character.SetInput(InputType.Aim, false, true);
}
sinTime += deltaTime * 10;
}
character.SetInput(extinguisher.Item.IsShootable ? InputType.Shoot : InputType.Use, false, true);
character.SetInput(extinguisherItem.IsShootable ? InputType.Shoot : InputType.Use, false, true);
extinguisher.Use(deltaTime, character);
if (!targetHull.FireSources.Contains(fs))
{
@@ -110,7 +126,7 @@ namespace Barotrauma
if (move)
{
//go to the first firesource
TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(fs, character, objectiveManager)
TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(fs, character, objectiveManager, closeEnough: extinguisher.Range / 2)
{
DialogueIdentifier = "dialogcannotreachfire",
TargetName = fs.Hull.DisplayName

View File

@@ -9,7 +9,6 @@ namespace Barotrauma
{
public override string DebugTag => "extinguish fires";
public override bool ForceRun => true;
public override bool IgnoreUnsafeHulls => true;
public AIObjectiveExtinguishFires(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) { }

View File

@@ -9,7 +9,6 @@ namespace Barotrauma
public override string DebugTag => $"find diving gear ({gearTag})";
public override bool ForceRun => true;
public override bool KeepDivingGearOn => true;
public override bool IgnoreUnsafeHulls => true;
private readonly string gearTag;
private readonly string fallbackTag;

View File

@@ -26,13 +26,35 @@ namespace Barotrauma
private AIObjectiveGoTo goToObjective;
private AIObjectiveFindDivingGear divingGearObjective;
public AIObjectiveFindSafety(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) { }
public AIObjectiveFindSafety(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) { }
protected override bool Check() => false;
public override bool CanBeCompleted => true;
private bool resetPriority;
public override float GetPriority()
{
if (character.CurrentHull == null)
{
Priority = objectiveManager.CurrentOrder is AIObjectiveGoTo ? 0 : 100;
}
else
{
if (HumanAIController.NeedsDivingGear(character, character.CurrentHull, out _) && !HumanAIController.HasDivingGear(character))
{
Priority = 100;
}
Priority = MathHelper.Clamp(Priority, 0, 100);
if (divingGearObjective != null && !divingGearObjective.IsCompleted && divingGearObjective.CanBeCompleted)
{
// Boost the priority while seeking the diving gear
Priority = Math.Max(Priority, Math.Min(AIObjectiveManager.OrderPriority + 20, 100));
}
}
return Priority;
}
public override void Update(float deltaTime)
{
if (resetPriority)
@@ -44,28 +66,19 @@ namespace Barotrauma
if (character.CurrentHull == null)
{
currenthullSafety = 0;
Priority = objectiveManager.CurrentOrder is AIObjectiveGoTo ? 0 : 100;
return;
}
if (HumanAIController.NeedsDivingGear(character, character.CurrentHull, out _) && !HumanAIController.HasDivingGear(character))
{
Priority = 100;
}
currenthullSafety = HumanAIController.CurrentHullSafety;
if (currenthullSafety > HumanAIController.HULL_SAFETY_THRESHOLD)
{
Priority -= priorityDecrease * deltaTime;
}
else
{
float dangerFactor = (100 - currenthullSafety) / 100;
Priority += dangerFactor * priorityIncrease * deltaTime;
}
Priority = MathHelper.Clamp(Priority, 0, 100);
if (divingGearObjective != null && !divingGearObjective.IsCompleted && divingGearObjective.CanBeCompleted)
{
// Boost the priority while seeking the diving gear
Priority = Math.Max(Priority, Math.Min(AIObjectiveManager.OrderPriority + 20, 100));
currenthullSafety = HumanAIController.CurrentHullSafety;
if (currenthullSafety > HumanAIController.HULL_SAFETY_THRESHOLD)
{
Priority -= priorityDecrease * deltaTime;
}
else
{
float dangerFactor = (100 - currenthullSafety) / 100;
Priority += dangerFactor * priorityIncrease * deltaTime;
}
}
}

View File

@@ -29,16 +29,23 @@ namespace Barotrauma
public override float GetPriority()
{
if (Leak.Removed || Leak.Open <= 0) { return 0; }
float xDist = Math.Abs(character.WorldPosition.X - Leak.WorldPosition.X);
float yDist = Math.Abs(character.WorldPosition.Y - Leak.WorldPosition.Y);
// Vertical distance matters more than horizontal (climbing up/down is harder than moving horizontally).
// If the target is close, ignore the distance factor alltogether so that we keep fixing the leaks that are nearby.
float distanceFactor = xDist < 200 && yDist < 100 ? 1 : MathHelper.Lerp(1, 0.1f, MathUtils.InverseLerp(0, 5000, xDist + yDist * 3.0f));
float severity = AIObjectiveFixLeaks.GetLeakSeverity(Leak) / 100;
float max = Math.Min((AIObjectiveManager.OrderPriority - 1), 90);
float devotion = Math.Min(Priority, 10) / 100;
return MathHelper.Lerp(0, max, MathHelper.Clamp(devotion + severity * distanceFactor * PriorityModifier, 0, 1));
if (Leak.Removed || Leak.Open <= 0)
{
Priority = 0;
}
else
{
float xDist = Math.Abs(character.WorldPosition.X - Leak.WorldPosition.X);
float yDist = Math.Abs(character.WorldPosition.Y - Leak.WorldPosition.Y);
// Vertical distance matters more than horizontal (climbing up/down is harder than moving horizontally).
// If the target is close, ignore the distance factor alltogether so that we keep fixing the leaks that are nearby.
float distanceFactor = xDist < 200 && yDist < 100 ? 1 : MathHelper.Lerp(1, 0.1f, MathUtils.InverseLerp(0, 5000, xDist + yDist * 3.0f));
float severity = AIObjectiveFixLeaks.GetLeakSeverity(Leak) / 100;
float max = Math.Min((AIObjectiveManager.OrderPriority - 1), 90);
float devotion = CumulatedDevotion / 100;
Priority = MathHelper.Lerp(0, max, MathHelper.Clamp(devotion + (severity * distanceFactor * PriorityModifier), 0, 1));
}
return Priority;
}
protected override void Act(float deltaTime)

View File

@@ -11,7 +11,6 @@ namespace Barotrauma
public override string DebugTag => "fix leaks";
public override bool ForceRun => true;
public override bool KeepDivingGearOn => true;
public override bool IgnoreUnsafeHulls => true;
public AIObjectiveFixLeaks(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) { }
@@ -36,7 +35,7 @@ namespace Barotrauma
protected override float TargetEvaluation()
{
int otherFixers = HumanAIController.CountCrew(c => c != HumanAIController && c.ObjectiveManager.IsCurrentObjective<AIObjectiveFixLeaks>());
int otherFixers = HumanAIController.CountCrew(c => c != HumanAIController && c.ObjectiveManager.IsCurrentObjective<AIObjectiveFixLeaks>(), onlyBots: true);
int totalLeaks = Targets.Count();
if (totalLeaks == 0) { return 0; }
int secondaryLeaks = Targets.Count(l => l.IsRoomToRoom);
@@ -44,13 +43,13 @@ namespace Barotrauma
bool anyFixers = otherFixers > 0;
if (objectiveManager.CurrentOrder == this)
{
float ratio = anyFixers ? totalLeaks / otherFixers : 1;
float ratio = anyFixers ? totalLeaks / (float)otherFixers : 1;
return Targets.Sum(t => GetLeakSeverity(t)) * ratio;
}
else
{
float ratio = leaks == 0 ? 1 : anyFixers ? leaks / otherFixers : 1;
if (anyFixers && (ratio <= 1 || otherFixers > 5 || otherFixers / HumanAIController.CountCrew() > 0.75f))
if (anyFixers && (ratio <= 1 || otherFixers > 5 || otherFixers / (float)HumanAIController.CountCrew(onlyBots: true) > 0.75f))
{
// Enough fixers
return 0;

View File

@@ -31,15 +31,6 @@ namespace Barotrauma
public bool AllowToFindDivingGear { get; set; } = true;
public override float GetPriority()
{
if (objectiveManager.CurrentOrder == this)
{
return AIObjectiveManager.OrderPriority;
}
return 1.0f;
}
public AIObjectiveGetItem(Character character, Item targetItem, AIObjectiveManager objectiveManager, bool equip = true, float priorityModifier = 1)
: base(character, objectiveManager, priorityModifier)
{

View File

@@ -51,14 +51,23 @@ namespace Barotrauma
public override float GetPriority()
{
if (followControlledCharacter && Character.Controlled == null) { return 0.0f; }
if (Target is Entity e && e.Removed) { return 0.0f; }
if (IgnoreIfTargetDead && Target is Character character && character.IsDead) { return 0.0f; }
if (objectiveManager.CurrentOrder == this)
if (followControlledCharacter && Character.Controlled == null)
{
return AIObjectiveManager.OrderPriority;
Priority = 0;
}
return 1.0f;
if (Target is Entity e && e.Removed)
{
Priority = 0;
}
if (IgnoreIfTargetDead && Target is Character character && character.IsDead)
{
Priority = 0;
}
else
{
return base.GetPriority();
}
return Priority;
}
public AIObjectiveGoTo(ISpatialEntity target, Character character, AIObjectiveManager objectiveManager, bool repeat = false, bool getDivingGearIfNeeded = true, float priorityModifier = 1, float closeEnough = 0)

View File

@@ -45,20 +45,17 @@ namespace Barotrauma
private float randomUpdateInterval = 5;
public float Random { get; private set; }
public void SetRandom()
public void CalculatePriority()
{
Random = Rand.Range(0.5f, 1.5f);
randomTimer = randomUpdateInterval;
}
public override float GetPriority()
{
float max = Math.Min(Math.Min(AIObjectiveManager.RunPriority, AIObjectiveManager.OrderPriority) - 1, 100);
float initiative = character.GetSkillLevel("initiative");
Priority = MathHelper.Lerp(1, max, MathUtils.InverseLerp(100, 0, initiative * Random));
return Priority;
}
public override float GetPriority() => Priority;
public override void Update(float deltaTime)
{
if (objectiveManager.CurrentObjective == this)
@@ -69,7 +66,7 @@ namespace Barotrauma
}
else
{
SetRandom();
CalculatePriority();
}
}
}
@@ -182,7 +179,7 @@ namespace Barotrauma
if (!character.IsClimbing)
{
if (SteeringManager != PathSteering || (PathSteering.CurrentPath != null &&
(PathSteering.CurrentPath.NextNode == null || PathSteering.CurrentPath.Unreachable || PathSteering.CurrentPath.HasOutdoorsNodes)))
(PathSteering.CurrentPath.Finished || PathSteering.CurrentPath.Unreachable || PathSteering.CurrentPath.HasOutdoorsNodes)))
{
Wander(deltaTime);
return;

View File

@@ -45,6 +45,7 @@ namespace Barotrauma
public override bool CanBeCompleted => true;
public override bool AbandonWhenCannotCompleteSubjectives => false;
public override bool AllowSubObjectiveSorting => true;
public virtual bool InverseTargetEvaluation => false;
public override bool IsLoop { get => true; set => throw new System.Exception("Trying to set the value for IsLoop from: " + System.Environment.StackTrace); }
@@ -107,21 +108,46 @@ namespace Barotrauma
public override float GetPriority()
{
if (character.LockHands) { return 0; }
if (character.Submarine == null) { return 0; }
if (Targets.None()) { return 0; }
// Allow the target value to be more than 100.
float targetValue = TargetEvaluation();
// If the target value is less than 1% of the max value, let's just treat it as zero.
if (targetValue < 1) { return 0; }
if (objectiveManager.CurrentOrder == this)
if (character.LockHands || character.Submarine == null || Targets.None())
{
return AIObjectiveManager.OrderPriority;
Priority = 0;
}
float max = MathHelper.Min(AIObjectiveManager.OrderPriority - 1, 90);
float devotion = MathHelper.Min(10, Priority);
float value = MathHelper.Clamp((devotion + targetValue * PriorityModifier) / 100, 0, 1);
return MathHelper.Lerp(0, max, value);
else
{
// Allow the target value to be more than 100.
float targetValue = TargetEvaluation();
if (InverseTargetEvaluation)
{
targetValue = 100 - targetValue;
}
var currentSubObjective = CurrentSubObjective;
if (currentSubObjective != null && currentSubObjective.Priority > targetValue)
{
// If the priority is higher than the target value, let's just use it.
// The priority calculation is more precise, but it takes into account things like distances,
// so it's better not to use it if it's lower than the rougher targetValue.
targetValue = Priority;
}
// If the target value is less than 1% of the max value, let's just treat it as zero.
if (targetValue < 1)
{
Priority = 0;
}
else
{
if (objectiveManager.CurrentOrder == this)
{
Priority = AIObjectiveManager.OrderPriority;
}
else
{
float max = MathHelper.Min(AIObjectiveManager.OrderPriority - 1, 90);
float value = MathHelper.Clamp((CumulatedDevotion + (targetValue * PriorityModifier)) / 100, 0, 1);
Priority = MathHelper.Lerp(0, max, value);
}
}
}
return Priority;
}
protected void UpdateTargets()

View File

@@ -41,6 +41,15 @@ namespace Barotrauma
public bool IsActiveObjective<T>() where T : AIObjective => GetActiveObjective() is T;
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);
@@ -146,7 +155,7 @@ namespace Barotrauma
{
var previousObjective = CurrentObjective;
var firstObjective = Objectives.FirstOrDefault();
if (CurrentOrder != null && firstObjective != null && CurrentOrder.GetPriority() > firstObjective.GetPriority())
if (CurrentOrder != null && firstObjective != null && CurrentOrder.Priority > firstObjective.Priority)
{
CurrentObjective = CurrentOrder;
}
@@ -158,14 +167,14 @@ namespace Barotrauma
{
previousObjective?.OnDeselected();
CurrentObjective?.OnSelected();
GetObjective<AIObjectiveIdle>().SetRandom();
GetObjective<AIObjectiveIdle>().CalculatePriority();
}
return CurrentObjective;
}
public float GetCurrentPriority()
{
return CurrentObjective == null ? 0.0f : CurrentObjective.GetPriority();
return CurrentObjective == null ? 0.0f : CurrentObjective.Priority;
}
public void UpdateObjectives(float deltaTime)
@@ -203,9 +212,11 @@ namespace Barotrauma
public void SortObjectives()
{
CurrentOrder?.GetPriority();
Objectives.ForEach(o => o.GetPriority());
if (Objectives.Any())
{
Objectives.Sort((x, y) => y.GetPriority().CompareTo(x.GetPriority()));
Objectives.Sort((x, y) => y.Priority.CompareTo(x.Priority));
}
GetCurrentObjective()?.SortSubObjectives();
}
@@ -297,7 +308,7 @@ namespace Barotrauma
{
IsLoop = true,
// Don't override unless it's an order by a player
Override = orderGiver != null && (orderGiver == Character.Controlled || orderGiver.IsRemotePlayer)
Override = orderGiver != null && orderGiver.IsPlayer
};
break;
default:
@@ -306,7 +317,7 @@ namespace Barotrauma
{
IsLoop = true,
// Don't override unless it's an order by a player
Override = orderGiver != null && (orderGiver == Character.Controlled || orderGiver.IsRemotePlayer)
Override = orderGiver != null && orderGiver.IsPlayer
};
break;
}

View File

@@ -3,6 +3,7 @@ using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using Barotrauma.Extensions;
namespace Barotrauma
{
@@ -31,19 +32,32 @@ namespace Barotrauma
public override float GetPriority()
{
if (component.Item.ConditionPercentage <= 0) { return 0; }
if (objectiveManager.CurrentOrder == this)
if (component.Item.ConditionPercentage <= 0)
{
return AIObjectiveManager.OrderPriority;
Priority = 0;
}
if (component.Item.CurrentHull == null) { return 0; }
if (component.Item.CurrentHull.FireSources.Count > 0) { return 0; }
if (IsOperatedByAnother(GetTarget())) { return 0; }
if (Character.CharacterList.Any(c => c.CurrentHull == component.Item.CurrentHull && !HumanAIController.IsFriendly(c) && HumanAIController.IsActive(c))) { return 0; }
float devotion = MathHelper.Min(10, Priority);
float value = devotion + AIObjectiveManager.OrderPriority * PriorityModifier;
float max = MathHelper.Min((AIObjectiveManager.OrderPriority - 1), 90);
return MathHelper.Clamp(value, 0, max);
else
{
if (objectiveManager.CurrentOrder == this)
{
Priority = AIObjectiveManager.OrderPriority;
}
if (component.Item.CurrentHull == null || component.Item.CurrentHull.FireSources.Any() || IsOperatedByAnother(character, GetTarget(), out _))
{
Priority = 0;
}
else if (Character.CharacterList.Any(c => c.CurrentHull == component.Item.CurrentHull && !HumanAIController.IsFriendly(c) && HumanAIController.IsActive(c)))
{
Priority = 0;
}
else
{
float value = CumulatedDevotion + (AIObjectiveManager.OrderPriority * PriorityModifier);
float max = MathHelper.Min((AIObjectiveManager.OrderPriority - 1), 90);
Priority = MathHelper.Clamp(value, 0, max);
}
}
return Priority;
}
public AIObjectiveOperateItem(ItemComponent item, Character character, AIObjectiveManager objectiveManager, string option, bool requireEquip, Entity operateTarget = null, bool useController = false, float priorityModifier = 1)
@@ -62,25 +76,31 @@ namespace Barotrauma
}
}
private bool IsOperatedByAnother(ItemComponent target)
public static bool IsOperatedByAnother(Character character, ItemComponent target, out Character operatingCharacter)
{
operatingCharacter = null;
foreach (var c in Character.CharacterList)
{
if (c == character) { continue; }
if (!HumanAIController.IsFriendly(c)) { continue; }
if (character != null && c == character) { continue; }
if (character?.AIController is HumanAIController humanAi && !humanAi.IsFriendly(c)) { continue; }
if (c.SelectedConstruction != target.Item) { continue; }
operatingCharacter = c;
// If the other character is player, don't try to operate
if (c.IsRemotePlayer || Character.Controlled == c) { return true; }
if (c.AIController is HumanAIController humanAi)
if (c.AIController is HumanAIController controllingHumanAi)
{
// If the other character is ordered to operate the item, let him do it
if (humanAi.ObjectiveManager.IsCurrentOrder<AIObjectiveOperateItem>())
if (controllingHumanAi.ObjectiveManager.IsCurrentOrder<AIObjectiveOperateItem>())
{
return true;
}
else
{
if (target is Steering)
if (character == null)
{
return true;
}
else if (target is Steering)
{
// Steering is hard-coded -> cannot use the required skills collection defined in the xml
return character.GetSkillLevel("helm") <= c.GetSkillLevel("helm");
@@ -116,7 +136,7 @@ namespace Barotrauma
return;
}
// Don't allow to operate an item that someone with a better skills already operates, unless this is an order
if (objectiveManager.CurrentOrder != this && IsOperatedByAnother(target))
if (objectiveManager.CurrentOrder != this && IsOperatedByAnother(character, target, out _))
{
// Don't abandon
return;

View File

@@ -12,7 +12,6 @@ namespace Barotrauma
public override string DebugTag => "pump water";
public override bool KeepDivingGearOn => true;
public override bool UnequipItems => true;
public override bool IgnoreUnsafeHulls => true;
private IEnumerable<Pump> pumpList;

View File

@@ -30,21 +30,28 @@ namespace Barotrauma
{
// TODO: priority list?
// Ignore items that are being repaired by someone else.
if (Item.Repairables.Any(r => r.CurrentFixer != null && r.CurrentFixer != character)) { return 0; }
float yDist = Math.Abs(character.WorldPosition.Y - Item.WorldPosition.Y);
yDist = yDist > 100 ? yDist * 5 : 0;
float dist = Math.Abs(character.WorldPosition.X - Item.WorldPosition.X) + yDist;
float distanceFactor = MathHelper.Lerp(1, 0.25f, MathUtils.InverseLerp(0, 5000, dist));
if (Item.CurrentHull == character.CurrentHull)
if (Item.Repairables.Any(r => r.CurrentFixer != null && r.CurrentFixer != character))
{
distanceFactor = 1;
Priority = 0;
}
float damagePriority = MathHelper.Lerp(1, 0, Item.Condition / Item.MaxCondition);
float successFactor = MathHelper.Lerp(0, 1, Item.Repairables.Average(r => r.DegreeOfSuccess(character)));
float isSelected = IsRepairing ? 50 : 0;
float devotion = (Math.Min(Priority, 10) + isSelected) / 100;
float max = MathHelper.Min(AIObjectiveManager.OrderPriority - 1, 90);
return MathHelper.Lerp(0, max, MathHelper.Clamp(devotion + damagePriority * distanceFactor * successFactor * PriorityModifier, 0, 1));
else
{
float yDist = Math.Abs(character.WorldPosition.Y - Item.WorldPosition.Y);
yDist = yDist > 100 ? yDist * 5 : 0;
float dist = Math.Abs(character.WorldPosition.X - Item.WorldPosition.X) + yDist;
float distanceFactor = MathHelper.Lerp(1, 0.25f, MathUtils.InverseLerp(0, 5000, dist));
if (Item.CurrentHull == character.CurrentHull)
{
distanceFactor = 1;
}
float damagePriority = MathHelper.Lerp(1, 0, Item.Condition / Item.MaxCondition);
float successFactor = MathHelper.Lerp(0, 1, Item.Repairables.Average(r => r.DegreeOfSuccess(character)));
float isSelected = IsRepairing ? 50 : 0;
float devotion = (CumulatedDevotion + isSelected) / 100;
float max = MathHelper.Min(AIObjectiveManager.OrderPriority - 1, 90);
Priority = MathHelper.Lerp(0, max, MathHelper.Clamp(devotion + (damagePriority * distanceFactor * successFactor * PriorityModifier), 0, 1));
}
return Priority;
}
protected override bool Check()

View File

@@ -81,18 +81,17 @@ namespace Barotrauma
// Don't stop fixing until done
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;
bool anyFixers = otherFixers > 0;
float ratio = anyFixers ? items / otherFixers : 1;
var result = ratio;
float ratio = anyFixers ? items / (float)otherFixers : 1;
if (objectiveManager.CurrentOrder == this)
{
return Targets.Sum(t => 100 - t.ConditionPercentage) * ratio;
}
else
{
if (anyFixers && (ratio <= 1 || otherFixers > 5 || otherFixers / HumanAIController.CountCrew() > 0.75f))
if (anyFixers && (ratio <= 1 || otherFixers > 5 || otherFixers / (float)HumanAIController.CountCrew(onlyBots: true) > 0.75f))
{
// Enough fixers
return 0;

View File

@@ -14,7 +14,7 @@ namespace Barotrauma
const float TreatmentDelay = 0.5f;
const float CloseEnoughToTreat = 150.0f;
const float CloseEnoughToTreat = 100.0f;
private readonly Character targetCharacter;
@@ -44,6 +44,15 @@ namespace Barotrauma
Abandon = true;
return;
}
if (targetCharacter.SelectedBy != null && targetCharacter.SelectedBy != character)
{
var otherCharacter = character.SelectedBy;
if (otherCharacter != null)
{
// Someone else is rescuing/holding the target.
Abandon = otherCharacter.IsPlayer || character.GetSkillLevel("medical") < otherCharacter.GetSkillLevel("medical");
}
}
if (targetCharacter != character)
{
@@ -67,7 +76,11 @@ namespace Barotrauma
TargetName = targetCharacter.DisplayName
},
onCompleted: () => RemoveSubObjective(ref goToObjective),
onAbandon: () => RemoveSubObjective(ref goToObjective));
onAbandon: () =>
{
RemoveSubObjective(ref goToObjective);
Abandon = true;
});
}
else
{
@@ -86,7 +99,11 @@ namespace Barotrauma
RemoveSubObjective(ref goToObjective);
TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(safeHull, character, objectiveManager),
onCompleted: () => RemoveSubObjective(ref goToObjective),
onAbandon: () => RemoveSubObjective(ref goToObjective));
onAbandon: () =>
{
RemoveSubObjective(ref goToObjective);
safeHull = character.CurrentHull;
});
}
}
}
@@ -105,7 +122,11 @@ namespace Barotrauma
TargetName = targetCharacter.DisplayName
},
onCompleted: () => RemoveSubObjective(ref goToObjective),
onAbandon: () => RemoveSubObjective(ref goToObjective));
onAbandon: () =>
{
RemoveSubObjective(ref goToObjective);
Abandon = true;
});
}
else
{
@@ -127,6 +148,11 @@ namespace Barotrauma
private Dictionary<string, float> currentTreatmentSuitabilities = new Dictionary<string, float>();
private void GiveTreatment(float deltaTime)
{
if (!targetCharacter.IsPlayer)
{
// If the target is a bot, don't let it move
targetCharacter.AIController.SteeringManager.Reset();
}
if (treatmentTimer > 0.0f)
{
treatmentTimer -= deltaTime;
@@ -137,9 +163,8 @@ namespace Barotrauma
//find which treatments are the most suitable to treat the character's current condition
targetCharacter.CharacterHealth.GetSuitableTreatments(currentTreatmentSuitabilities, normalize: false);
var allAfflictions = GetVitalityReducingAfflictions(targetCharacter).OrderByDescending(a => a.GetVitalityDecrease(targetCharacter.CharacterHealth));
//check if we already have a suitable treatment for any of the afflictions
foreach (Affliction affliction in allAfflictions)
foreach (Affliction affliction in GetSortedAfflictions(targetCharacter))
{
foreach (KeyValuePair<string, float> treatmentSuitability in affliction.Prefab.TreatmentSuitability)
{
@@ -200,10 +225,12 @@ namespace Barotrauma
onAbandon: () => RemoveSubObjective(ref getItemObjective));
}
}
character.AnimController.Anim = AnimController.Animation.CPR;
if (character != targetCharacter)
{
character.AnimController.Anim = AnimController.Animation.CPR;
}
}
private void ApplyTreatment(Affliction affliction, Item item)
{
var targetLimb = targetCharacter.CharacterHealth.GetAfflictionLimb(affliction);
@@ -240,7 +267,7 @@ namespace Barotrauma
Abandon = true;
return false;
}
bool isCompleted = AIObjectiveRescueAll.GetVitalityFactor(targetCharacter) > AIObjectiveRescueAll.GetVitalityThreshold(objectiveManager);
bool isCompleted = AIObjectiveRescueAll.GetVitalityFactor(targetCharacter) >= AIObjectiveRescueAll.GetVitalityThreshold(objectiveManager, character, targetCharacter);
if (isCompleted && targetCharacter != character)
{
character.Speak(TextManager.GetWithVariable("DialogTargetHealed", "[targetname]", targetCharacter.Name),
@@ -253,20 +280,24 @@ namespace Barotrauma
{
if (targetCharacter == null || targetCharacter.CurrentHull == null || targetCharacter.Removed || targetCharacter.IsDead)
{
return 0;
Priority = 0;
}
// Vertical distance matters more than horizontal (climbing up/down is harder than moving horizontally)
float dist = Math.Abs(character.WorldPosition.X - targetCharacter.WorldPosition.X) + Math.Abs(character.WorldPosition.Y - targetCharacter.WorldPosition.Y) * 2.0f;
float distanceFactor = MathHelper.Lerp(1, 0.1f, MathUtils.InverseLerp(0, 5000, dist));
if (targetCharacter.CurrentHull == character.CurrentHull)
else
{
distanceFactor = 1;
// Vertical distance matters more than horizontal (climbing up/down is harder than moving horizontally)
float dist = Math.Abs(character.WorldPosition.X - targetCharacter.WorldPosition.X) + Math.Abs(character.WorldPosition.Y - targetCharacter.WorldPosition.Y) * 2.0f;
float distanceFactor = MathHelper.Lerp(1, 0.1f, MathUtils.InverseLerp(0, 5000, dist));
if (targetCharacter.CurrentHull == character.CurrentHull)
{
distanceFactor = 1;
}
float vitalityFactor = 1 - AIObjectiveRescueAll.GetVitalityFactor(targetCharacter) / 100;
float devotion = CumulatedDevotion / 100;
Priority = MathHelper.Lerp(0, 100, MathHelper.Clamp(devotion + (vitalityFactor * distanceFactor * PriorityModifier), 0, 1));
}
float vitalityFactor = AIObjectiveRescueAll.GetVitalityFactor(targetCharacter);
float devotion = Math.Min(Priority, 10) / 100;
return MathHelper.Lerp(0, 100, MathHelper.Clamp(devotion + vitalityFactor * distanceFactor, 0, 1));
return Priority;
}
public static IEnumerable<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());
}
}

View File

@@ -8,11 +8,11 @@ namespace Barotrauma
{
public override string DebugTag => "rescue all";
public override bool ForceRun => true;
public override bool IgnoreUnsafeHulls => true;
public override bool InverseTargetEvaluation => true;
private const float vitalityThreshold = 80;
private const float vitalityThresholdForOrders = 95;
public static float GetVitalityThreshold(AIObjectiveManager manager)
private const float vitalityThresholdForOrders = 100;
public static float GetVitalityThreshold(AIObjectiveManager manager, Character character, Character target)
{
if (manager == null)
{
@@ -20,7 +20,7 @@ namespace Barotrauma
}
else
{
return manager.CurrentOrder is AIObjectiveRescueAll ? vitalityThresholdForOrders : vitalityThreshold;
return character == target || manager.CurrentOrder is AIObjectiveRescueAll ? vitalityThresholdForOrders : vitalityThreshold;
}
}
@@ -31,9 +31,50 @@ namespace Barotrauma
protected override IEnumerable<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)
=> new AIObjectiveRescue(character, target, objectiveManager, PriorityModifier);
@@ -47,16 +88,34 @@ namespace Barotrauma
if (!HumanAIController.IsFriendly(character, target)) { return false; }
if (character.AIController is HumanAIController humanAI)
{
if (GetVitalityFactor(target) > GetVitalityThreshold(humanAI.ObjectiveManager)) { return false; }
if (GetVitalityFactor(target) >= GetVitalityThreshold(humanAI.ObjectiveManager, character, target)) { return false; }
if (!humanAI.ObjectiveManager.IsCurrentOrder<AIObjectiveRescueAll>())
{
// Ignore unsafe hulls, unless ordered
if (humanAI.UnsafeHulls.Contains(target.CurrentHull))
{
return false;
}
}
}
else
{
if (GetVitalityFactor(target) > vitalityThreshold) { return false; }
if (GetVitalityFactor(target) >= vitalityThreshold) { return false; }
}
if (target.Submarine == null || character.Submarine == null) { return false; }
if (target.Submarine.TeamID != character.Submarine.TeamID) { return false; }
if (target.CurrentHull == null) { return false; }
if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(target.CurrentHull, true)) { return false; }
if (!target.IsPlayer && HumanAIController.IsActive(target) && target.AIController is HumanAIController targetAI)
{
// Ignore all concious targets that are currently fighting, fleeing or treating characters
if (targetAI.ObjectiveManager.HasActiveObjective<AIObjectiveCombat>() ||
targetAI.ObjectiveManager.HasActiveObjective<AIObjectiveFindSafety>() ||
targetAI.ObjectiveManager.HasActiveObjective<AIObjectiveRescue>())
{
return false;
}
}
// Don't go into rooms that have enemies
if (Character.CharacterList.Any(c => c.CurrentHull == target.CurrentHull && !HumanAIController.IsFriendly(character, c) && HumanAIController.IsActive(c))) { return false; }
return true;

View File

@@ -231,25 +231,12 @@ namespace Barotrauma
}
else
{
Limb refLimb = GetLimb(LimbType.Head);
float refAngle;
if (refLimb == null)
float rotation = MathHelper.WrapAngle(Collider.Rotation);
rotation = MathHelper.ToDegrees(rotation);
if (rotation < 0.0f)
{
refAngle = CurrentAnimationParams.TorsoAngleInRadians;
refLimb = GetLimb(LimbType.Torso);
rotation += 360;
}
else
{
refAngle = CurrentAnimationParams.HeadAngleInRadians;
}
float rotation = refLimb.Rotation;
if (!float.IsNaN(refAngle)) { rotation -= refAngle * Dir; }
rotation = MathHelper.ToDegrees(MathUtils.WrapAngleTwoPi(rotation));
if (rotation < 0.0f) rotation += 360;
if (rotation > 20 && rotation < 160)
{
TargetDir = Direction.Left;
@@ -347,9 +334,23 @@ namespace Barotrauma
target.AnimController.Collider.MoveToPos(mouthPos, (float)(Math.Sin(eatTimer) + dragForce));
}
//pull the character's mouth to the target character (again with a fluctuating force)
float pullStrength = (float)(Math.Sin(eatTimer) * Math.Max(Math.Sin(eatTimer * 0.5f), 0.0f));
mouthLimb.body.ApplyForce(limbDiff * mouthLimb.Mass * 50.0f * pullStrength, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
if (InWater)
{
//pull the character's mouth to the target character (again with a fluctuating force)
float pullStrength = (float)(Math.Sin(eatTimer) * Math.Max(Math.Sin(eatTimer * 0.5f), 0.0f));
mouthLimb.body.ApplyForce(limbDiff * mouthLimb.Mass * 50.0f * pullStrength, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
}
else
{
float force = (float)Math.Sin(eatTimer * 100) * mouthLimb.Mass;
mouthLimb.body.ApplyLinearImpulse(Vector2.UnitY * force * 2, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
mouthLimb.body.ApplyTorque(-force * 50);
}
var jaw = GetLimb(LimbType.Jaw);
if (jaw != null)
{
jaw.body.ApplyTorque(-(float)Math.Sin(eatTimer * 150) * jaw.Mass * 25);
}
character.ApplyStatusEffects(ActionType.OnEating, deltaTime);
@@ -439,7 +440,7 @@ namespace Barotrauma
if (CurrentSwimParams.RotateTowardsMovement)
{
Collider.SmoothRotate(movementAngle, CurrentSwimParams.SteerTorque);
Collider.SmoothRotate(movementAngle, CurrentSwimParams.SteerTorque * character.SpeedMultiplier);
if (TorsoAngle.HasValue)
{
Limb torso = GetLimb(LimbType.Torso);
@@ -491,11 +492,11 @@ namespace Barotrauma
}
if (mainLimb.type == LimbType.Head && HeadAngle.HasValue)
{
Collider.SmoothRotate(HeadAngle.Value * Dir, CurrentSwimParams.SteerTorque);
Collider.SmoothRotate(HeadAngle.Value * Dir, CurrentSwimParams.SteerTorque * character.SpeedMultiplier);
}
else if (mainLimb.type == LimbType.Torso && TorsoAngle.HasValue)
{
Collider.SmoothRotate(TorsoAngle.Value * Dir, CurrentSwimParams.SteerTorque);
Collider.SmoothRotate(TorsoAngle.Value * Dir, CurrentSwimParams.SteerTorque * character.SpeedMultiplier);
}
if (TorsoAngle.HasValue)
{
@@ -515,7 +516,7 @@ namespace Barotrauma
}
var waveLength = Math.Abs(CurrentSwimParams.WaveLength * RagdollParams.JointScale);
var waveAmplitude = Math.Abs(CurrentSwimParams.WaveAmplitude);
var waveAmplitude = Math.Abs(CurrentSwimParams.WaveAmplitude * character.SpeedMultiplier);
if (waveLength > 0 && waveAmplitude > 0)
{
WalkPos -= transformedMovement.Length() / Math.Abs(waveLength);
@@ -524,6 +525,10 @@ namespace Barotrauma
foreach (var limb in Limbs)
{
if (Math.Abs(limb.Params.ConstantTorque) > 0)
{
limb.body.SmoothRotate(movementAngle + MathHelper.ToRadians(limb.Params.ConstantAngle) * Dir, limb.Params.ConstantTorque, wrapAngle: true);
}
switch (limb.type)
{
case LimbType.LeftFoot:
@@ -548,7 +553,7 @@ namespace Barotrauma
if (Limbs[i].SteerForce <= 0.0f) { continue; }
if (!Collider.PhysEnabled) { continue; }
Vector2 pullPos = Limbs[i].PullJointWorldAnchorA;
Limbs[i].body.ApplyForce(movement * Limbs[i].SteerForce * Limbs[i].Mass, pullPos);
Limbs[i].body.ApplyForce(movement * Limbs[i].SteerForce * Limbs[i].Mass * Math.Max(character.SpeedMultiplier, 1), pullPos);
}
Vector2 mainLimbDiff = mainLimb.PullJointWorldAnchorB - mainLimb.SimPosition;
@@ -665,6 +670,10 @@ namespace Barotrauma
foreach (Limb limb in Limbs)
{
if (Math.Abs(limb.Params.ConstantTorque) > 0)
{
limb.body.SmoothRotate(movementAngle + MathHelper.ToRadians(limb.Params.ConstantAngle) * Dir, limb.Params.ConstantTorque, wrapAngle: true);
}
switch (limb.type)
{
case LimbType.LeftFoot:
@@ -729,7 +738,10 @@ namespace Barotrauma
break;
case LimbType.LeftLeg:
case LimbType.RightLeg:
if (Math.Abs(CurrentGroundedParams.LegTorque) > 0.001f) limb.body.ApplyTorque(limb.Mass * CurrentGroundedParams.LegTorque * Dir);
if (Math.Abs(CurrentGroundedParams.LegTorque) > 0)
{
limb.body.ApplyTorque(limb.Mass * CurrentGroundedParams.LegTorque * Dir);
}
break;
}
}

View File

@@ -900,7 +900,7 @@ namespace Barotrauma
surfaceLimiter = ConvertUnits.ToDisplayUnits(Collider.SimPosition.Y + 0.4f) - surfacePos;
surfaceLimiter = Math.Max(1.0f, surfaceLimiter);
if (surfaceLimiter > 50.0f) return;
if (surfaceLimiter > 50.0f) { return; }
}
Limb leftHand = GetLimb(LimbType.LeftHand);
@@ -928,8 +928,7 @@ namespace Barotrauma
if (!aiming)
{
float newRotation = MathUtils.VectorToAngle(TargetMovement) - MathHelper.PiOver2;
Collider.SmoothRotate(newRotation, 5.0f);
//torso.body.SmoothRotate(newRotation);
Collider.SmoothRotate(newRotation, 5.0f * character.SpeedMultiplier);
}
}
else
@@ -942,13 +941,13 @@ namespace Barotrauma
TargetMovement = new Vector2(0.0f, -0.1f);
float newRotation = MathUtils.VectorToAngle(diff);
Collider.SmoothRotate(newRotation, 5.0f);
Collider.SmoothRotate(newRotation, 5.0f * character.SpeedMultiplier);
}
}
torso.body.MoveToPos(Collider.SimPosition + new Vector2((float)Math.Sin(-Collider.Rotation), (float)Math.Cos(-Collider.Rotation)) * 0.4f, 5.0f);
if (TargetMovement == Vector2.Zero) return;
if (TargetMovement == Vector2.Zero) { return; }
movement = MathUtils.SmoothStep(movement, TargetMovement, 0.3f);
@@ -1007,7 +1006,7 @@ namespace Barotrauma
var waist = GetLimb(LimbType.Waist);
footPos = waist == null ? Vector2.Zero : waist.SimPosition - new Vector2((float)Math.Sin(-Collider.Rotation), (float)Math.Cos(-Collider.Rotation)) * (upperLegLength + lowerLegLength);
Vector2 transformedFootPos = new Vector2((float)Math.Sin(legCyclePos / CurrentSwimParams.LegCycleLength) * CurrentSwimParams.LegMoveAmount * CurrentAnimationParams.CycleSpeed, 0.0f);
Vector2 transformedFootPos = new Vector2((float)Math.Sin(legCyclePos / CurrentSwimParams.LegCycleLength / character.SpeedMultiplier) * CurrentSwimParams.LegMoveAmount, 0.0f);
transformedFootPos = Vector2.Transform(transformedFootPos, Matrix.CreateRotationZ(Collider.Rotation));
if (rightFoot != null && !rightFoot.Disabled)
@@ -1061,7 +1060,7 @@ namespace Barotrauma
rightHandPos.X = (Dir == 1.0f) ? Math.Max(0.3f, rightHandPos.X) : Math.Min(-0.3f, rightHandPos.X);
rightHandPos = Vector2.Transform(rightHandPos, rotationMatrix);
HandIK(rightHand, handPos + rightHandPos, CurrentSwimParams.HandMoveStrength);
HandIK(rightHand, handPos + rightHandPos, CurrentSwimParams.HandMoveStrength * character.SpeedMultiplier);
}
if (leftHand != null && !leftHand.Disabled)
@@ -1070,7 +1069,7 @@ namespace Barotrauma
leftHandPos.X = (Dir == 1.0f) ? Math.Max(0.3f, leftHandPos.X) : Math.Min(-0.3f, leftHandPos.X);
leftHandPos = Vector2.Transform(leftHandPos, rotationMatrix);
HandIK(leftHand, handPos + leftHandPos, CurrentSwimParams.HandMoveStrength);
HandIK(leftHand, handPos + leftHandPos, CurrentSwimParams.HandMoveStrength * character.SpeedMultiplier);
}
}
@@ -1929,7 +1928,7 @@ namespace Barotrauma
float sqrDist = Vector2.DistanceSquared(character.WorldPosition, handWorldPos);
if (sqrDist > MathUtils.Pow(ConvertUnits.ToDisplayUnits(upperArmLength + forearmLength), 2))
{
TargetMovement = Vector2.Normalize(handWorldPos - character.WorldPosition) * GetCurrentSpeed(false);
TargetMovement = Vector2.Normalize(handWorldPos - character.WorldPosition) * GetCurrentSpeed(false) * Math.Max(character.SpeedMultiplier, 1);
}
}

View File

@@ -383,7 +383,7 @@ namespace Barotrauma
}
}
if (character.IsHusk)
if (character.IsHusk && character.Params.UseHuskAppendage)
{
var characterPrefab = CharacterPrefab.FindByFilePath(character.ConfigPath);
if (characterPrefab?.XDocument != null)
@@ -1626,7 +1626,7 @@ namespace Barotrauma
float sin = (float)Math.Sin(mouthLimb.Rotation);
Vector2 bodySize = mouthLimb.body.GetSize();
Vector2 offset = new Vector2(mouthLimb.MouthPos.X * bodySize.X / 2, mouthLimb.MouthPos.Y * bodySize.Y / 2);
return mouthLimb.SimPosition + new Vector2(offset.X * cos - offset.Y * sin, offset.X * sin + offset.Y * cos) * RagdollParams.LimbScale;
return mouthLimb.SimPosition + new Vector2(offset.X * cos - offset.Y * sin, offset.X * sin + offset.Y * cos) * mouthLimb.Scale * RagdollParams.LimbScale;
}
public Vector2 GetColliderBottom()

View File

@@ -13,11 +13,12 @@ namespace Barotrauma
public enum AttackContext
{
NotDefined,
Any,
Water,
Ground,
Inside,
Outside
Outside,
NotDefined
}
public enum AttackTarget
@@ -72,13 +73,13 @@ namespace Barotrauma
partial class Attack : ISerializableEntity
{
[Serialize(AttackContext.NotDefined, true, description: "The attack will be used only in this context."), Editable]
[Serialize(AttackContext.Any, true, description: "The attack will be used only in this context."), Editable]
public AttackContext Context { get; private set; }
[Serialize(AttackTarget.Any, true, description: "Does the attack target only specific targets?"), Editable]
public AttackTarget TargetType { get; private set; }
[Serialize(LimbType.None, true, description: "If not defined or set to none, the closest limb is used (default)."), Editable]
[Serialize(LimbType.None, true, description: "To which limb is the attack aimed at? If not defined or set to none, the closest limb is used (default)."), Editable]
public LimbType TargetLimbType { get; private set; }
[Serialize(HitDetection.Distance, true, description: "Collision detection is more accurate, but it only affects targets that are in contact with the limb."), Editable]
@@ -87,9 +88,15 @@ namespace Barotrauma
[Serialize(AIBehaviorAfterAttack.FallBack, true, description: "The preferred AI behavior after the attack."), Editable]
public AIBehaviorAfterAttack AfterAttack { get; set; }
[Serialize(false, true, description: "Should the AI try to reverse when aiming with this attack?"), Editable]
[Serialize(0f, true, description: "A delay before reacting after performing an attack."), Editable]
public float AfterAttackDelay { get; set; }
[Serialize(false, true, description: "Should the AI try to turn around when aiming with this attack?"), Editable]
public bool Reverse { get; private set; }
[Serialize(false, true, description: "Should the AI try to steer away from the target when aiming with this attack? Best combined with PassiveAggressive behavior."), Editable]
public bool Retreat { get; private set; }
[Serialize(0.0f, true, description: "The min distance from the attack limb to the target before the AI tries to attack."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 2000.0f)]
public float Range { get; set; }
@@ -147,7 +154,19 @@ namespace Barotrauma
[Serialize(0.0f, true, description: "Applied to the attacking limb (or limbs defined using ApplyForceOnLimbs). The direction of the force is towards the target that's being attacked."), Editable(MinValueFloat = -1000.0f, MaxValueFloat = 1000.0f)]
public float Force { get; private set; }
[Serialize(0.0f, true, description: "Applied to the attacking limb (or limbs defined using ApplyForceOnLimbs)"), Editable(MinValueFloat = -1000.0f, MaxValueFloat = 1000.0f)]
[Serialize("0.0, 0.0", true, description: "Applied to the main limb. In world space coordinates(i.e. 0, 1 pushes the character upwards a bit). The attacker's facing direction is taken into account."), Editable]
public Vector2 RootForceWorldStart { get; private set; }
[Serialize("0.0, 0.0", true, description: "Applied to the main limb. In world space coordinates(i.e. 0, 1 pushes the character upwards a bit). The attacker's facing direction is taken into account."), Editable]
public Vector2 RootForceWorldMiddle { get; private set; }
[Serialize("0.0, 0.0", true, description: "Applied to the main limb. In world space coordinates(i.e. 0, 1 pushes the character upwards a bit). The attacker's facing direction is taken into account."), Editable]
public Vector2 RootForceWorldEnd { get; private set; }
[Serialize(TransitionMode.Linear, true, description:""), Editable]
public TransitionMode RootTransitionEasing { get; private set; }
[Serialize(0.0f, true, description: "Applied to the attacking limb (or limbs defined using ApplyForceOnLimbs)"), Editable(MinValueFloat = -10000.0f, MaxValueFloat = 10000.0f)]
public float Torque { get; private set; }
[Serialize(false, true), Editable]
@@ -156,13 +175,13 @@ namespace Barotrauma
[Serialize(0.0f, true, description: "Applied to the target the attack hits. The direction of the impulse is from this limb towards the target (use negative values to pull the target closer)."), Editable(MinValueFloat = -1000.0f, MaxValueFloat = 1000.0f)]
public float TargetImpulse { get; private set; }
[Serialize("0.0, 0.0", true, description: "Applied to the target, in world space coordinates(i.e. 0, -1 pushes the target downwards)."), Editable]
[Serialize("0.0, 0.0", true, description: "Applied to the target, in world space coordinates(i.e. 0, -1 pushes the target downwards). The attacker's facing direction is taken into account."), Editable]
public Vector2 TargetImpulseWorld { get; private set; }
[Serialize(0.0f, true, description: "Applied to the target the attack hits. The direction of the force is from this limb towards the target (use negative values to pull the target closer)."), Editable(-1000.0f, 1000.0f)]
public float TargetForce { get; private set; }
[Serialize("0.0, 0.0", true, description: "Applied to the target, in world space coordinates(i.e. 0, -1 pushes the target downwards)."), Editable]
[Serialize("0.0, 0.0", true, description: "Applied to the target, in world space coordinates(i.e. 0, -1 pushes the target downwards). The attacker's facing direction is taken into account."), Editable]
public Vector2 TargetForceWorld { get; private set; }
[Serialize(0.0f, true, description: "How likely the attack causes target limbs to be severed when the target is dead."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1.0f)]
@@ -419,7 +438,10 @@ namespace Barotrauma
public AttackResult DoDamageToLimb(Character attacker, Limb targetLimb, Vector2 worldPosition, float deltaTime, bool playSound = true)
{
if (targetLimb == null) return new AttackResult();
if (targetLimb == null)
{
return new AttackResult();
}
if (OnlyHumans)
{
@@ -461,6 +483,7 @@ namespace Barotrauma
public float AttackTimer { get; private set; }
public float CoolDownTimer { get; set; }
public float CurrentRandomCoolDown { get; private set; }
public float SecondaryCoolDownTimer { get; set; }
public bool IsRunning { get; private set; }
@@ -492,7 +515,8 @@ namespace Barotrauma
public void SetCoolDown()
{
float randomFraction = CoolDown * CoolDownRandomFactor;
CoolDownTimer = CoolDown + MathHelper.Lerp(-randomFraction, randomFraction, Rand.Value(Rand.RandSync.Server));
CurrentRandomCoolDown = MathHelper.Lerp(-randomFraction, randomFraction, Rand.Value(Rand.RandSync.Server));
CoolDownTimer = CoolDown + CurrentRandomCoolDown;
randomFraction = SecondaryCoolDown * CoolDownRandomFactor;
SecondaryCoolDownTimer = SecondaryCoolDown + MathHelper.Lerp(-randomFraction, randomFraction, Rand.Value(Rand.RandSync.Server));
}
@@ -501,11 +525,12 @@ namespace Barotrauma
{
CoolDownTimer = 0;
SecondaryCoolDownTimer = 0;
CurrentRandomCoolDown = 0;
}
partial void DamageParticles(float deltaTime, Vector2 worldPosition);
public bool IsValidContext(AttackContext context) => Context == context || Context == AttackContext.NotDefined;
public bool IsValidContext(AttackContext context) => Context == context || Context == AttackContext.Any || Context == AttackContext.NotDefined;
public bool IsValidContext(IEnumerable<AttackContext> contexts)
{
@@ -559,5 +584,11 @@ namespace Barotrauma
return true;
}
}
public Vector2 CalculateAttackPhase(TransitionMode easing = TransitionMode.Linear)
{
float t = AttackTimer / Duration;
return MathUtils.Bezier(RootForceWorldStart, RootForceWorldMiddle, RootForceWorldEnd, ToolBox.GetEasing(easing, t));
}
}
}

View File

@@ -57,6 +57,10 @@ namespace Barotrauma
public Hull CurrentHull = null;
public bool IsRemotePlayer;
public bool IsPlayer => Controlled == this || IsRemotePlayer;
public bool IsBot => !IsPlayer && AIController is HumanAIController humanAI && humanAI.Enabled;
public readonly Dictionary<string, SerializableProperty> Properties;
public Dictionary<string, SerializableProperty> SerializableProperties
{
@@ -128,6 +132,12 @@ namespace Barotrauma
set => Params.Noise = value;
}
public float Visibility
{
get => Params.Visibility;
set => Params.Visibility = value;
}
public bool IsTraitor;
public string TraitorCurrentObjective = "";
public bool IsHuman => SpeciesName.Equals(CharacterPrefab.HumanSpeciesName, StringComparison.OrdinalIgnoreCase);
@@ -201,7 +211,7 @@ namespace Barotrauma
{
displayName = TextManager.Get($"Character.{SpeciesName}", returnNull: true);
}
return displayName ?? Name;
return string.IsNullOrWhiteSpace(displayName) ? Name : displayName;
}
}
@@ -770,10 +780,14 @@ namespace Barotrauma
{
nonHuskedSpeciesName = AfflictionHusk.GetNonHuskedSpeciesName(speciesName, matchingAffliction);
}
ragdollParams = IsHumanoid ? RagdollParams.GetDefaultRagdollParams<HumanRagdollParams>(nonHuskedSpeciesName) : RagdollParams.GetDefaultRagdollParams<FishRagdollParams>(nonHuskedSpeciesName) as RagdollParams;
if (info == null)
if (ragdollParams == null)
{
info = new CharacterInfo(nonHuskedSpeciesName, ragdollParams.FileName);
string name = Params.UseHuskAppendage ? nonHuskedSpeciesName : speciesName;
ragdollParams = IsHumanoid ? RagdollParams.GetDefaultRagdollParams<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)));
#if CLIENT
head.LoadHuskSprite();
head.EnableHuskSprite = Params.Husk;
head.LoadHerpesSprite();
head.UpdateWearableTypesToHide();
#endif
@@ -1010,59 +1024,55 @@ namespace Barotrauma
}
else
{
if (IsKeyDown(InputType.Left)) targetMovement.X -= 1.0f;
if (IsKeyDown(InputType.Right)) targetMovement.X += 1.0f;
if (IsKeyDown(InputType.Up)) targetMovement.Y += 1.0f;
if (IsKeyDown(InputType.Down)) targetMovement.Y -= 1.0f;
if (IsKeyDown(InputType.Left)) { targetMovement.X -= 1.0f; }
if (IsKeyDown(InputType.Right)) { targetMovement.X += 1.0f; }
if (IsKeyDown(InputType.Up)) { targetMovement.Y += 1.0f; }
if (IsKeyDown(InputType.Down)) { targetMovement.Y -= 1.0f; }
}
bool run = false;
if ((IsKeyDown(InputType.Run) && AnimController.ForceSelectAnimationType == AnimationType.NotDefined) || ForceRun)
{
run = CanRun;
}
return ApplyMovementLimits(targetMovement, AnimController.GetCurrentSpeed(run));
}
//can't run if
// - dragging someone
// - crouching
// - moving backwards
public bool CanRun => (SelectedCharacter == null || !SelectedCharacter.CanBeDragged) &&
(!(AnimController is HumanoidAnimController) || !((HumanoidAnimController)AnimController).Crouching) &&
!AnimController.IsMovingBackwards;
public Vector2 ApplyMovementLimits(Vector2 targetMovement, float currentSpeed)
{
//the vertical component is only used for falling through platforms and climbing ladders when not in water,
//so the movement can't be normalized or the Character would walk slower when pressing down/up
if (AnimController.InWater)
{
float length = targetMovement.Length();
if (length > 0.0f) targetMovement /= length;
if (length > 0.0f)
{
targetMovement /= length;
}
}
bool run = false;
if ((IsKeyDown(InputType.Run) && AnimController.ForceSelectAnimationType == AnimationType.NotDefined) || ForceRun)
{
//can't run if
// - dragging someone
// - crouching
// - moving backwards
run = (SelectedCharacter == null || !SelectedCharacter.CanBeDragged) &&
(!(AnimController is HumanoidAnimController) || !((HumanoidAnimController)AnimController).Crouching) &&
!AnimController.IsMovingBackwards;
}
float currentSpeed = AnimController.GetCurrentSpeed(run);
targetMovement *= currentSpeed;
float maxSpeed = ApplyTemporarySpeedLimits(currentSpeed);
targetMovement.X = MathHelper.Clamp(targetMovement.X, -maxSpeed, maxSpeed);
targetMovement.Y = MathHelper.Clamp(targetMovement.Y, -maxSpeed, maxSpeed);
//apply speed multiplier if
// a. it's boosting the movement speed and the character is trying to move fast (= running)
// b. it's a debuff that decreases movement speed
float speedMultiplier = SpeedMultiplier;
if (run || speedMultiplier <= 0.0f) targetMovement *= speedMultiplier;
ResetSpeedMultiplier(); // Reset, items will set the value before the next update
SpeedMultiplier = greatestPositiveSpeedMultiplier - (1f - greatestNegativeSpeedMultiplier);
targetMovement *= SpeedMultiplier;
// Reset, status effects will set the value before the next update
ResetSpeedMultiplier();
return targetMovement;
}
/// <summary>
/// Can be used to modify the character's speed via StatusEffects
/// </summary>
public float SpeedMultiplier
{
get
{
return greatestPositiveSpeedMultiplier - (1f - greatestNegativeSpeedMultiplier);
}
}
public float SpeedMultiplier { get; private set; }
public void StackSpeedMultiplier(float val)
{
@@ -1915,7 +1925,7 @@ namespace Barotrauma
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
{
//disable AI characters that are far away from all clients and the host's character and not controlled by anyone
if (c == Controlled || c.IsRemotePlayer)
if (c.IsPlayer || c.IsBot)
{
c.Enabled = true;
}
@@ -2012,8 +2022,8 @@ namespace Barotrauma
HideFace = false;
UpdateSightRange();
UpdateSoundRange();
UpdateSightRange(deltaTime);
UpdateSoundRange(deltaTime);
if (IsDead) { return; }
@@ -2290,18 +2300,39 @@ namespace Barotrauma
}
}
private void UpdateSightRange()
private readonly float maxAIRange = 10000;
private readonly float aiTargetChangeSpeed = 5;
private void UpdateSightRange(float deltaTime)
{
if (aiTarget == null) { return; }
float range = (float)Math.Sqrt(Mass) * 250 + AnimController.Collider.LinearVelocity.Length() * 500;
aiTarget.SightRange = MathHelper.Clamp(range, 0, 10000);
float minRange = Math.Clamp((float)Math.Sqrt(Mass) * Visibility, 250, 1000);
float massFactor = (float)Math.Sqrt(Mass / 20);
float targetRange = Math.Min(minRange + massFactor * AnimController.Collider.LinearVelocity.Length() * 2 * Visibility, maxAIRange);
float newRange = MathHelper.SmoothStep(aiTarget.SightRange, targetRange, deltaTime * aiTargetChangeSpeed);
if (!float.IsNaN(newRange))
{
aiTarget.SightRange = newRange;
}
}
private void UpdateSoundRange()
private void UpdateSoundRange(float deltaTime)
{
if (aiTarget == null) { return; }
float range = ((float)Math.Sqrt(Mass) / 3) * (AnimController.TargetMovement.Length() * 2) * Noise;
aiTarget.SoundRange = MathHelper.Clamp(range, 0, 10000);
if (IsDead)
{
aiTarget.SoundRange = 0;
}
else
{
float massFactor = (float)Math.Sqrt(Mass / 10);
float targetRange = Math.Min(massFactor * AnimController.Collider.LinearVelocity.Length() * 2 * Noise, maxAIRange);
float newRange = MathHelper.SmoothStep(aiTarget.SoundRange, targetRange, deltaTime * aiTargetChangeSpeed);
if (!float.IsNaN(newRange))
{
aiTarget.SoundRange = newRange;
}
}
}
public bool CanHearCharacter(Character speaker)
@@ -2316,21 +2347,16 @@ namespace Barotrauma
public void SetOrder(Order order, string orderOption, Character orderGiver, bool speak = true)
{
if (orderGiver != null)
{
//set the character order only if the character is close enough to hear the message
if (!CanHearCharacter(orderGiver)) { return; }
}
//set the character order only if the character is close enough to hear the message
if (orderGiver != null && !CanHearCharacter(orderGiver)) { return; }
if (AIController is HumanAIController humanAI)
{
humanAI.SetOrder(order, orderOption, orderGiver, speak);
}
#if CLIENT
else
{
GameMain.GameSession?.CrewManager?.DisplayCharacterOrder(this, order, orderOption);
}
GameMain.GameSession?.CrewManager?.DisplayCharacterOrder(this, order, orderOption);
#endif
CurrentOrder = order;
@@ -2469,13 +2495,17 @@ namespace Barotrauma
DamageLimb(worldPosition, targetLimb, attack.Afflictions.Keys, attack.Stun, playSound, attackImpulse, attacker);
if (limbHit == null) { return new AttackResult(); }
limbHit.body?.ApplyLinearImpulse(attack.TargetImpulseWorld + attack.TargetForceWorld * deltaTime, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
Vector2 forceWorld = attack.TargetImpulseWorld + attack.TargetForceWorld;
if (attacker != null)
{
forceWorld.X *= attacker.AnimController.Dir;
}
limbHit.body?.ApplyLinearImpulse(forceWorld * deltaTime, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
var mainLimb = limbHit.character.AnimController.MainLimb;
if (limbHit != mainLimb)
{
// Always add force to mainlimb
mainLimb.body?.ApplyLinearImpulse(attack.TargetImpulseWorld + attack.TargetForceWorld * deltaTime, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
mainLimb.body?.ApplyLinearImpulse(forceWorld * deltaTime, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
}
#if SERVER
if (attacker is Character attackingCharacter && attackingCharacter.AIController == null)

View File

@@ -14,8 +14,13 @@ namespace Barotrauma
public Dictionary<string, SerializableProperty> SerializableProperties { get; set; }
protected float _strength;
[Serialize(0f, true), Editable]
public float Strength { get; set; }
public virtual float Strength
{
get { return _strength; }
set { _strength = value; }
}
[Serialize("", true), Editable]
public string Identifier { get; private set; }
@@ -38,7 +43,7 @@ namespace Barotrauma
public Affliction(AfflictionPrefab prefab, float strength)
{
Prefab = prefab;
Strength = strength;
_strength = strength;
Identifier = prefab?.Identifier;
}
@@ -173,11 +178,11 @@ namespace Barotrauma
if (currentEffect.StrengthChange < 0) // Reduce diminishing of buffs if boosted
{
Strength += currentEffect.StrengthChange * deltaTime * StrengthDiminishMultiplier;
_strength += currentEffect.StrengthChange * deltaTime * StrengthDiminishMultiplier;
}
else // Reduce strengthening of afflictions if resistant
{
Strength += currentEffect.StrengthChange * deltaTime * (1f - characterHealth.GetResistance(Prefab.Identifier));
_strength += currentEffect.StrengthChange * deltaTime * (1f - characterHealth.GetResistance(Prefab.Identifier));
}
foreach (StatusEffect statusEffect in currentEffect.StatusEffects)

View File

@@ -1,7 +1,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Linq;
using System;
namespace Barotrauma
{
@@ -9,7 +9,7 @@ namespace Barotrauma
{
public enum InfectionState
{
Dormant, Transition, Active
Initial, Dormant, Transition, Active, Final
}
private bool subscribedToDeathEvent;
@@ -17,154 +17,157 @@ namespace Barotrauma
private InfectionState state;
private List<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
{
get { return state; }
private set
{
if (state == value) { return; }
state = value;
if (character != null && character == Character.Controlled)
{
UpdateMessages();
}
}
}
public AfflictionHusk(AfflictionPrefab prefab, float strength) :
base(prefab, strength)
{
}
private float DormantThreshold => Prefab.MaxStrength * 0.5f;
private float ActiveThreshold => Prefab.MaxStrength * 0.75f;
public AfflictionHusk(AfflictionPrefab prefab, float strength) : base(prefab, strength) { }
public override void Update(CharacterHealth characterHealth, Limb targetLimb, float deltaTime)
{
float prevStrength = Strength;
base.Update(characterHealth, targetLimb, deltaTime);
character = characterHealth.Character;
if (character == null) { return; }
if (!subscribedToDeathEvent)
{
characterHealth.Character.OnDeath += CharacterDead;
character.OnDeath += CharacterDead;
subscribedToDeathEvent = true;
}
if (characterHealth.Character == Character.Controlled) UpdateMessages(prevStrength, characterHealth.Character);
if (Strength < Prefab.MaxStrength * 0.5f)
if (Strength < DormantThreshold)
{
UpdateDormantState(deltaTime, characterHealth.Character);
DeactivateHusk();
State = InfectionState.Dormant;
}
else if (Strength < ActiveThreshold)
{
DeactivateHusk();
character.SpeechImpediment = 100;
State = InfectionState.Transition;
}
else if (Strength < Prefab.MaxStrength)
{
characterHealth.Character.SpeechImpediment = 100.0f;
UpdateTransitionState(deltaTime, characterHealth.Character);
if (State != InfectionState.Active)
{
character.SetStun(Rand.Range(2, 4, Rand.RandSync.Server));
}
State = InfectionState.Active;
ActivateHusk();
}
else
{
characterHealth.Character.SpeechImpediment = 100.0f;
UpdateActiveState(deltaTime, characterHealth.Character);
State = InfectionState.Final;
ActivateHusk();
ApplyDamage(deltaTime, applyForce: true);
character.SetStun(1);
}
}
partial void UpdateMessages(float prevStrength, Character character);
partial void UpdateMessages();
private void UpdateDormantState(float deltaTime, Character character)
private void ApplyDamage(float deltaTime, bool applyForce)
{
if (state != InfectionState.Dormant)
{
DeactivateHusk(character);
}
state = InfectionState.Dormant;
}
private void UpdateTransitionState(float deltaTime, Character character)
{
if (state != InfectionState.Transition)
{
DeactivateHusk(character);
}
state = InfectionState.Transition;
}
private void UpdateActiveState(float deltaTime, Character character)
{
if (state != InfectionState.Active)
{
ActivateHusk(character);
state = InfectionState.Active;
}
foreach (Limb limb in character.AnimController.Limbs)
{
float random = Rand.Value(Rand.RandSync.Server);
huskInfection.Clear();
huskInfection.Add(AfflictionPrefab.InternalDamage.Instantiate(random * deltaTime / character.AnimController.Limbs.Length));
character.LastDamageSource = null;
character.DamageLimb(
limb.WorldPosition, limb,
new List<Affliction>() { AfflictionPrefab.InternalDamage.Instantiate(0.5f * deltaTime / character.AnimController.Limbs.Length) },
0.0f, false, 0.0f);
float force = applyForce ? random * 0.1f * limb.Mass : 0;
character.DamageLimb(limb.WorldPosition, limb, huskInfection, 0, false, force);
}
}
public void ActivateHusk(Character character)
public void ActivateHusk()
{
if (huskAppendage == null)
if (huskAppendage == null && character.Params.UseHuskAppendage)
{
huskAppendage = AttachHuskAppendage(character, Prefab.Identifier);
if (huskAppendage != null)
{
character.NeedsAir = false;
character.SetStun(0.5f);
}
#if CLIENT
character.AnimController.GetLimb(LimbType.Head).EnableHuskSprite = true;
#endif
}
character.NeedsAir = false;
character.SpeechImpediment = 100;
}
private void DeactivateHusk(Character character)
private void DeactivateHusk()
{
character.NeedsAir = character.Params.MainElement.GetAttributeBool("needsair", false);
if (huskAppendage != null)
{
huskAppendage.ForEach(l => character.AnimController.RemoveLimb(l));
huskAppendage = null;
#if CLIENT
character.AnimController.GetLimb(LimbType.Head).EnableHuskSprite = false;
#endif
}
}
public void Remove(Character character)
public void Remove()
{
DeactivateHusk(character);
if (character != null) character.OnDeath -= CharacterDead;
if (character == null) { return; }
DeactivateHusk();
character.OnDeath -= CharacterDead;
subscribedToDeathEvent = false;
}
private void CharacterDead(Character character, CauseOfDeath causeOfDeath)
{
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
if (Strength < Prefab.MaxStrength * 0.5f || character.Removed) { return; }
if (Strength < ActiveThreshold || character.Removed) { return; }
//don't turn the character into a husk if any of its limbs are severed
if (character.AnimController?.LimbJoints != null)
{
foreach (var limbJoint in character.AnimController.LimbJoints)
{
if (limbJoint.IsSevered) return;
if (limbJoint.IsSevered) { return; }
}
}
//create the AI husk in a coroutine to ensure that we don't modify the character list while enumerating it
CoroutineManager.StartCoroutine(CreateAIHusk(character));
CoroutineManager.StartCoroutine(CreateAIHusk());
}
private IEnumerable<object> CreateAIHusk(Character character)
private IEnumerable<object> CreateAIHusk()
{
character.Enabled = false;
Entity.Spawner.AddToRemoveQueue(character);
string speciesName = GetHuskedSpeciesName(character.SpeciesName, Prefab as AfflictionPrefabHusk);
CharacterPrefab prefab = CharacterPrefab.FindBySpeciesName(speciesName);
string huskedSpeciesName = GetHuskedSpeciesName(character.SpeciesName, Prefab as AfflictionPrefabHusk);
CharacterPrefab prefab = CharacterPrefab.FindBySpeciesName(huskedSpeciesName);
if (prefab == null)
{
DebugConsole.ThrowError("Failed to turn character \"" + character.Name + "\" into a husk - husk config file not found.");
yield return CoroutineStatus.Success;
}
var husk = Character.Create(speciesName, character.WorldPosition, character.Info.Name, character.Info, isRemotePlayer: false, hasAi: true, ragdoll: character.AnimController.RagdollParams);
var husk = Character.Create(huskedSpeciesName, character.WorldPosition, ToolBox.RandomSeed(8), character.Info, isRemotePlayer: false, hasAi: true);
foreach (Limb limb in husk.AnimController.Limbs)
{
@@ -197,6 +200,11 @@ namespace Barotrauma
husk.Inventory.TryPutItem(character.Inventory.Items[i], i, true, false, null);
}
husk.SetStun(5);
yield return new WaitForSeconds(5, false);
#if CLIENT
husk.PlaySound(CharacterSound.SoundType.Idle);
#endif
yield return CoroutineStatus.Success;
}

View File

@@ -791,8 +791,7 @@ namespace Barotrauma
/// </summary>
/// <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="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)
{
//key = item identifier
@@ -873,5 +872,11 @@ namespace Barotrauma
}
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);
}
}

View File

@@ -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)
{

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