(7ee8dbc11) v0.9.8.0
This commit is contained in:
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<ShowAllFiles>true</ShowAllFiles>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -9,7 +9,7 @@ namespace Barotrauma
|
||||
|
||||
public void Draw(SpriteBatch spriteBatch)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,16 +5,6 @@ using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public enum TransitionMode
|
||||
{
|
||||
Linear,
|
||||
Smooth,
|
||||
Smoother,
|
||||
EaseIn,
|
||||
EaseOut,
|
||||
Exponential
|
||||
}
|
||||
|
||||
public enum SpriteFallBackState
|
||||
{
|
||||
None,
|
||||
|
||||
@@ -524,23 +524,9 @@ namespace Barotrauma
|
||||
};
|
||||
}
|
||||
|
||||
private float GetEasing(TransitionMode easing, float t)
|
||||
{
|
||||
return easing switch
|
||||
{
|
||||
TransitionMode.Smooth => MathUtils.SmoothStep(t),
|
||||
TransitionMode.Smoother => MathUtils.SmootherStep(t),
|
||||
TransitionMode.EaseIn => MathUtils.EaseIn(t),
|
||||
TransitionMode.EaseOut => MathUtils.EaseOut(t),
|
||||
TransitionMode.Exponential => t * t,
|
||||
TransitionMode.Linear => t,
|
||||
_ => t,
|
||||
};
|
||||
}
|
||||
|
||||
protected Color GetBlendedColor(Color targetColor, ref Color blendedColor)
|
||||
{
|
||||
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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>();
|
||||
|
||||
@@ -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.";
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -350,8 +350,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AiTarget?.Draw(spriteBatch);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -15,6 +15,7 @@ namespace Barotrauma.Networking
|
||||
public byte ID;
|
||||
public UInt16 CharacterID;
|
||||
public bool Muted;
|
||||
public bool InGame;
|
||||
public bool AllowKicking;
|
||||
}
|
||||
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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", "");
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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'">
|
||||
|
||||
Binary file not shown.
1
Barotrauma/BarotraumaServer/DedicatedServer.exe
Normal file
1
Barotrauma/BarotraumaServer/DedicatedServer.exe
Normal file
@@ -0,0 +1 @@
|
||||
./DedicatedServer
|
||||
@@ -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'">
|
||||
|
||||
@@ -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'">
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 = "";
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()));
|
||||
|
||||
@@ -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'">
|
||||
|
||||
@@ -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>
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) { }
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user