From 0ba5793b462cc694c686e6d1cdd9a7adf4fbf35a Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Thu, 16 May 2019 06:50:59 +0300 Subject: [PATCH] (3628de633) Use the visible hulls instead of the current hull in multiple cases. --- .../Source/Characters/Animation/Ragdoll.cs | 101 +++++-- .../Source/GameSession/CrewManager.cs | 263 ++++-------------- .../Source/Screens/CampaignSetupUI.cs | 2 + .../Source/Characters/AI/HumanAIController.cs | 117 ++------ .../AI/Objectives/AIObjectiveCombat.cs | 5 +- .../AI/Objectives/AIObjectiveFindSafety.cs | 19 +- .../AI/Objectives/AIObjectiveFixLeak.cs | 26 ++ .../AI/Objectives/AIObjectiveIdle.cs | 22 +- .../AI/Objectives/AIObjectiveOperateItem.cs | 4 + .../AI/Objectives/AIObjectiveRescue.cs | 6 +- .../Source/Characters/Character.cs | 9 + .../Components/Machines/Deconstructor.cs | 19 ++ .../Items/Components/Machines/Steering.cs | 17 +- .../BarotraumaShared/Source/Items/Item.cs | 32 +++ .../BarotraumaShared/Source/Map/Hull.cs | 21 ++ 15 files changed, 307 insertions(+), 356 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/Characters/Animation/Ragdoll.cs b/Barotrauma/BarotraumaClient/Source/Characters/Animation/Ragdoll.cs index bcf133b30..fa30d8a82 100644 --- a/Barotrauma/BarotraumaClient/Source/Characters/Animation/Ragdoll.cs +++ b/Barotrauma/BarotraumaClient/Source/Characters/Animation/Ragdoll.cs @@ -108,6 +108,61 @@ namespace Barotrauma } } + //unconscious/dead characters can't correct their position using AnimController movement + // -> we need to correct it manually + if (!character.AllowInput) + { + float mainLimbDistSqrd = Vector2.DistanceSquared(MainLimb.PullJointWorldAnchorA, Collider.SimPosition); + float mainLimbErrorTolerance = 0.1f; + //if the main limb is roughly at the correct position and the collider isn't moving (much at least), + //don't attempt to correct the position. + if (mainLimbDistSqrd > mainLimbErrorTolerance || Collider.LinearVelocity.LengthSquared() > 0.05f) + { + MainLimb.PullJointWorldAnchorB = Collider.SimPosition; + MainLimb.PullJointEnabled = true; + } + character.SelectedConstruction = character.MemState[0].SelectedItem; + } + + if (character.MemState[0].Animation == AnimController.Animation.CPR) + { + character.AnimController.Anim = AnimController.Animation.CPR; + } + else if (character.AnimController.Anim == AnimController.Animation.CPR) + { + character.AnimController.Anim = AnimController.Animation.None; + } + + Vector2 newVelocity = Collider.LinearVelocity; + Vector2 newPosition = Collider.SimPosition; + float newRotation = Collider.Rotation; + float newAngularVelocity = Collider.AngularVelocity; + Collider.CorrectPosition(character.MemState, out newPosition, out newVelocity, out newRotation, out newAngularVelocity); + + newVelocity = newVelocity.ClampLength(100.0f); + if (!MathUtils.IsValid(newVelocity)) { newVelocity = Vector2.Zero; } + overrideTargetMovement = newVelocity.LengthSquared() > 0.01f ? newVelocity : Vector2.Zero; + + Collider.LinearVelocity = newVelocity; + Collider.AngularVelocity = newAngularVelocity; + + float distSqrd = Vector2.DistanceSquared(newPosition, Collider.SimPosition); + float errorTolerance = character.AllowInput ? 0.01f : 0.2f; + if (distSqrd > errorTolerance) + { + if (distSqrd > 10.0f || !character.AllowInput) + { + Collider.TargetRotation = newRotation; + SetPosition(newPosition, lerp: distSqrd < 5.0f, ignorePlatforms: false); + } + else + { + Collider.TargetRotation = newRotation; + Collider.TargetPosition = newPosition; + Collider.MoveToTargetPosition(true); + } + } + //unconscious/dead characters can't correct their position using AnimController movement // -> we need to correct it manually if (!character.AllowInput) @@ -151,32 +206,34 @@ namespace Barotrauma } } - - if (character.MemLocalState.Count > 120) character.MemLocalState.RemoveRange(0, character.MemLocalState.Count - 120); - character.MemState.Clear(); + character.MemLocalState.Clear(); } - } - - partial void ImpactProjSpecific(float impact, Body body) - { - float volume = MathHelper.Clamp(impact - 3.0f, 0.5f, 1.0f); - - if (body.UserData is Limb limb && character.Stun <= 0f) + else { - if (impact > 3.0f) { PlayImpactSound(limb); } - } - else if (body.UserData is Limb || body == Collider.FarseerBody) - { - if (!character.IsRemotePlayer && impact > ImpactTolerance) + //remove states with a timestamp (there may still timestamp-based states + //in the list if the controlled character switches from timestamp-based interpolation to ID-based) + character.MemState.RemoveAll(m => m.Timestamp > 0.0f); + + for (int i = 0; i < character.MemLocalState.Count; i++) { - SoundPlayer.PlayDamageSound("LimbBlunt", strongestImpact, Collider); + if (character.Submarine == null) + { + //transform in-sub coordinates to outside coordinates + if (character.MemLocalState[i].Position.Y > lowestSubPos) + { + character.MemLocalState[i].TransformInToOutside(); + } + } + else if (currentHull?.Submarine != null) + { + //transform outside coordinates to in-sub coordinates + if (character.MemLocalState[i].Position.Y < lowestSubPos) + { + character.MemLocalState[i].TransformOutToInside(currentHull.Submarine); + } + } + } - } - if (Character.Controlled == character) - { - GameMain.GameScreen.Cam.Shake = Math.Min(Math.Max(strongestImpact, GameMain.GameScreen.Cam.Shake), 3.0f); - } - } if (character.MemState.Count < 1) return; diff --git a/Barotrauma/BarotraumaClient/Source/GameSession/CrewManager.cs b/Barotrauma/BarotraumaClient/Source/GameSession/CrewManager.cs index f31ba2977..98948571e 100644 --- a/Barotrauma/BarotraumaClient/Source/GameSession/CrewManager.cs +++ b/Barotrauma/BarotraumaClient/Source/GameSession/CrewManager.cs @@ -77,37 +77,7 @@ namespace Barotrauma { if (GameMain.Client != null) { - //let the server create random conversations in MP - return; - } - List availableSpeakers = Character.CharacterList.FindAll(c => - c.AIController is HumanAIController && - !c.IsDead && - c.SpeechImpediment <= 100.0f); - pendingConversationLines.AddRange(NPCConversation.CreateRandom(availableSpeakers)); - } - - public void AddCharacter(Character character) - { - if (character.Removed) - { - DebugConsole.ThrowError("Tried to add a removed character to CrewManager!\n" + Environment.StackTrace); - return; - } - if (character.IsDead) - { - DebugConsole.ThrowError("Tried to add a dead character to CrewManager!\n" + Environment.StackTrace); - return; - } - - if (!characters.Contains(character)) characters.Add(character); - if (!characterInfos.Contains(character.Info)) - { - characterInfos.Add(character.Info); - } - - CreateCharacterFrame(character, characterListBox.Content); - characterListBox.Content.RectTransform.SortChildren((c1, c2) => { return c2.NonScaledSize.X - c1.NonScaledSize.X; }); + if (subElement.Name.ToString().ToLowerInvariant() != "character") continue; var characterInfo = new CharacterInfo(subElement); characterInfos.Add(characterInfo); @@ -269,24 +239,25 @@ namespace Barotrauma public IEnumerable GetCharacters() { - if (characterInfos.Contains(characterInfo)) - { - DebugConsole.ThrowError("Tried to add the same character info to CrewManager twice.\n" + Environment.StackTrace); - return; - } + if (client?.Character == null) { return; } - characterInfos.Add(characterInfo); + var playerFrame = characterListBox.Content.FindChild(client.Character)?.FindChild(client.Character); + if (playerFrame == null) { return; } + var soundIcon = playerFrame.FindChild("soundicon"); + var soundIconDisabled = playerFrame.FindChild("soundicondisabled"); + + if (!soundIcon.Visible) + { + soundIcon.Color = new Color(soundIcon.Color, 0.0f); + } + soundIcon.Visible = !muted && !mutedLocally; + soundIconDisabled.Visible = muted || mutedLocally; + soundIconDisabled.ToolTip = TextManager.Get(mutedLocally ? "MutedLocally" : "MutedGlobally"); } public IEnumerable GetCharacterInfos() { - if (character == null) - { - DebugConsole.ThrowError("Tried to remove a null character from CrewManager.\n" + Environment.StackTrace); - return; - } - characters.Remove(character); - if (removeInfo) characterInfos.Remove(character.Info); + if (client?.Character != null) { SetCharacterSpeaking(client.Character); } } public void AddCharacter(Character character) @@ -660,183 +631,56 @@ namespace Barotrauma { characterListBox.BarScroll = roundedPos; } - var characterArea = new GUIButton(new RectTransform(new Point(characterInfoWidth, frame.Rect.Height), frame.RectTransform, Anchor.CenterLeft), style: "GUITextBox") - { - UserData = character, - Color = frame.Color, - SelectedColor = frame.SelectedColor, - HoverColor = frame.HoverColor, - ToolTip = characterToolTip - }; + } - var soundIcon = new GUIImage(new RectTransform(new Point((int)(characterArea.Rect.Height * 0.5f)), characterArea.RectTransform, Anchor.CenterRight) { AbsoluteOffset = new Point(5, 0) }, - "GUISoundIcon") + /// + /// Displays the specified order in the crew UI next to the character. + /// + public void DisplayCharacterOrder(Character character, Order order) + { + foreach (GUIComponent characterListElement in characterListBox.Content.Children) { - UserData = "soundicon", - CanBeFocused = false, - Visible = true - }; - soundIcon.Color = new Color(soundIcon.Color, 0.0f); - new GUIImage(new RectTransform(new Point((int)(characterArea.Rect.Height * 0.5f)), characterArea.RectTransform, Anchor.CenterRight) { AbsoluteOffset = new Point(5, 0) }, - "GUISoundIconDisabled") - { - UserData = "soundicondisabled", - CanBeFocused = true, - Visible = false - }; + var characterFrame = characterListElement.FindChild(character); + if (characterFrame == null) continue; - if (isSinglePlayer) - { - characterArea.OnClicked = CharacterClicked; - } - else - { - characterArea.CanBeFocused = false; - characterArea.CanBeSelected = false; - } + var orderButtonFrame = characterListElement.GetChildByUserData("orderbuttons"); - var characterImage = new GUICustomComponent(new RectTransform(new Point(characterArea.Rect.Height), characterArea.RectTransform, Anchor.CenterLeft), - onDraw: (sb, component) => character.Info.DrawIcon(sb, component.Rect.Center.ToVector2(), targetAreaSize: component.Rect.Size.ToVector2())) - { - CanBeFocused = false, - HoverColor = Color.White, - SelectedColor = Color.White, - ToolTip = characterToolTip - }; - - var characterName = new GUITextBlock(new RectTransform(new Point(characterArea.Rect.Width - characterImage.Rect.Width - soundIcon.Rect.Width - 10, characterArea.Rect.Height), - characterArea.RectTransform, Anchor.CenterRight) { AbsoluteOffset = new Point(soundIcon.Rect.Width + 10, 0) }, - character.Name, textColor: frame.Color, font: GUI.SmallFont, wrap: true) - { - Color = frame.Color, - HoverColor = Color.Transparent, - SelectedColor = Color.Transparent, - CanBeFocused = false, - ToolTip = characterToolTip, - AutoScale = true - }; - - //---------------- order buttons ---------------- - - var orderButtonFrame = new GUILayoutGroup(new RectTransform(new Point(100, frame.Rect.Height), frame.RectTransform) - { AbsoluteOffset = new Point(characterInfoWidth + spacing, 0) }, - isHorizontal: true, childAnchor: Anchor.CenterLeft) - { - AbsoluteSpacing = (int)(10 * GUI.Scale), - UserData = "orderbuttons", - CanBeFocused = false - }; - - //listbox for holding the orders inappropriate for this character - //(so we can easily toggle their visibility) - var wrongOrderList = new GUIListBox(new RectTransform(new Point(50, orderButtonFrame.Rect.Height), orderButtonFrame.RectTransform), isHorizontal: true, style: null) - { - ScrollBarEnabled = false, - ScrollBarVisible = false, - Enabled = false, - Spacing = spacing, - ClampMouseRectToParent = false - }; - wrongOrderList.Content.ClampMouseRectToParent = false; - - for (int i = 0; i < orders.Count; i++) - { - var order = orders[i]; - if (order.TargetAllCharacters) continue; - - RectTransform btnParent = (i >= correctOrderCount + neutralOrderCount) ? - wrongOrderList.Content.RectTransform : - orderButtonFrame.RectTransform; - - var btn = new GUIButton(new RectTransform(new Point(iconSize, iconSize), btnParent, Anchor.CenterLeft), - style: null) + //get all order buttons from the frame + List orderButtons = new List(); + foreach (GUIComponent child in orderButtonFrame.Children) { - UserData = order - }; - - new GUIFrame(new RectTransform(new Vector2(1.5f), btn.RectTransform, Anchor.Center), "OuterGlow") - { - Color = Color.Lerp(order.Color, frame.Color, 0.5f) * 0.8f, - HoverColor = Color.Lerp(order.Color, frame.Color, 0.5f) * 1.0f, - PressedColor = Color.Lerp(order.Color, frame.Color, 0.5f) * 0.6f, - UserData = "selected", - CanBeFocused = false, - Visible = false - }; - - var img = new GUIImage(new RectTransform(Vector2.One, btn.RectTransform), order.Prefab.SymbolSprite); - img.Scale = iconSize / (float)img.SourceRect.Width; - img.Color = Color.Lerp(order.Color, frame.Color, 0.5f); - img.ToolTip = order.Name; - img.HoverColor = Color.Lerp(img.Color, Color.White, 0.5f); - - btn.OnClicked += (GUIButton button, object userData) => - { - if (Character.Controlled == null || Character.Controlled.SpeechImpediment >= 100.0f) return false; - - if (btn.GetChildByUserData("selected").Visible) + if (child is GUIButton orderBtn) { - SetCharacterOrder(character, Order.PrefabList.Find(o => o.AITag == "dismissed"), null, Character.Controlled); + orderButtons.Add(orderBtn); } - else + //the non-character-appropriate orders are in a hideable listbox, we need to go deeper! + else if (child is GUIListBox listBox) { - if (order.ItemComponentType != null || order.ItemIdentifiers.Length > 0 || order.Options.Length > 1) + foreach (GUIComponent listBoxElement in listBox.Content.Children) { - CreateOrderTargetFrame(button, character, order); - } - else - { - SetCharacterOrder(character, order, null, Character.Controlled); + if (listBoxElement is GUIButton orderBtn2 && listBoxElement.UserData is Order) orderButtons.Add(orderBtn2); } } - return true; - }; - btn.UserData = order; - btn.ToolTip = order.Name; + } - //divider between different groups of orders - if (i == correctOrderCount - 1 || i == correctOrderCount + neutralOrderCount - 1) + foreach (GUIButton button in orderButtons) { - //TODO: divider sprite - new GUIFrame(new RectTransform(new Point(8, iconSize), orderButtonFrame.RectTransform), style: "GUIButton"); + var selectedIndicator = button.GetChildByUserData("selected"); + if (selectedIndicator != null) + { + selectedIndicator.Visible = (order != null && ((Order)button.UserData).Prefab == order.Prefab); + } } } - - var toggleWrongOrderBtn = new GUIButton(new RectTransform(new Point((int)(30 * GUI.Scale), wrongOrderList.Rect.Height), wrongOrderList.Content.RectTransform), - "", style: "UIToggleButton") + else if (orderGiver != null) { - UserData = "togglewrongorder", - CanBeFocused = false - }; - - wrongOrderList.RectTransform.NonScaledSize = new Point( - wrongOrderList.Content.Children.Sum(c => c.Rect.Width + wrongOrderList.Spacing), - wrongOrderList.RectTransform.NonScaledSize.Y); - wrongOrderList.RectTransform.SetAsLastChild(); - - new GUIFrame(new RectTransform(new Point( - wrongOrderList.Rect.Width - toggleWrongOrderBtn.Rect.Width - wrongOrderList.Spacing * 2, - wrongOrderList.Rect.Height), wrongOrderList.Content.RectTransform), - style: null) - { - CanBeFocused = false - }; - - //scale to fit the content - orderButtonFrame.RectTransform.NonScaledSize = new Point( - orderButtonFrame.Children.Sum(c => c.Rect.Width + orderButtonFrame.AbsoluteSpacing), - orderButtonFrame.RectTransform.NonScaledSize.Y); - - frame.RectTransform.NonScaledSize = new Point( - characterInfoWidth + spacing + (orderButtonFrame.Rect.Width - wrongOrderList.Rect.Width), - frame.RectTransform.NonScaledSize.Y); - - characterListBox.RectTransform.NonScaledSize = new Point( - characterListBox.Content.Children.Max(c => c.Rect.Width) + wrongOrderList.Rect.Width, - characterListBox.RectTransform.NonScaledSize.Y); - characterListBox.Content.RectTransform.NonScaledSize = characterListBox.RectTransform.NonScaledSize; - characterListBox.UpdateScrollBarSize(); - return frame; + OrderChatMessage msg = new OrderChatMessage(order, option, order.TargetItemComponent?.Item, character, orderGiver); + if (GameMain.Client != null) + { + GameMain.Client.SendChatMessage(msg); + } + } + DisplayCharacterOrder(character, order); } private IEnumerable KillCharacterAnim(GUIComponent component) @@ -1038,15 +882,6 @@ namespace Barotrauma } } } - else if (orderGiver != null) - { - OrderChatMessage msg = new OrderChatMessage(order, option, order.TargetItemComponent?.Item, character, orderGiver); - if (GameMain.Client != null) - { - GameMain.Client.SendChatMessage(msg); - } - } - DisplayCharacterOrder(character, order); } /// diff --git a/Barotrauma/BarotraumaClient/Source/Screens/CampaignSetupUI.cs b/Barotrauma/BarotraumaClient/Source/Screens/CampaignSetupUI.cs index 1d023f463..9f646ed8d 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/CampaignSetupUI.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/CampaignSetupUI.cs @@ -698,6 +698,8 @@ namespace Barotrauma private GUILayoutGroup subPreviewContainer; + private GUILayoutGroup subPreviewContainer; + private GUIButton loadGameButton; public Action StartNewGame; diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/HumanAIController.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/HumanAIController.cs index 45670a973..e406a2709 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/HumanAIController.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/HumanAIController.cs @@ -285,79 +285,6 @@ namespace Barotrauma } } - protected void ReportProblems() - { - Order newOrder = null; - if (Character.CurrentHull != null) - { - if (AIObjectiveExtinguishFires.IsValidTarget(Character.CurrentHull, Character)) - { - if (AddTargets(Character, Character.CurrentHull)) - { - var orderPrefab = Order.PrefabList.Find(o => o.AITag == "reportfire"); - newOrder = new Order(orderPrefab, Character.CurrentHull, null); - } - } - - foreach (var gap in Character.CurrentHull.ConnectedGaps) - { - if (AIObjectiveFixLeaks.IsValidTarget(gap, Character)) - { - if (AddTargets(Character, gap)) - { - var orderPrefab = Order.PrefabList.Find(o => o.AITag == "reportbreach"); - newOrder = new Order(orderPrefab, Character.CurrentHull, null); - } - } - } - - foreach (Item item in Item.ItemList) - { - if (item.CurrentHull != Character.CurrentHull) { continue; } - if (AIObjectiveRepairItems.IsValidTarget(item, Character)) - { - if (item.Repairables.All(r => item.Condition > r.ShowRepairUIThreshold)) { continue; } - if (AddTargets(Character, item)) - { - var orderPrefab = Order.PrefabList.Find(o => o.AITag == "reportbrokendevices"); - newOrder = new Order(orderPrefab, Character.CurrentHull, item.Repairables?.FirstOrDefault()); - } - } - } - - foreach (Character c in Character.CharacterList) - { - if (c.CurrentHull != Character.CurrentHull) { continue; } - if (AIObjectiveFightIntruders.IsValidTarget(c, Character)) - { - if (AddTargets(Character, c)) - { - var orderPrefab = Order.PrefabList.Find(o => o.AITag == "reportintruders"); - newOrder = new Order(orderPrefab, Character.CurrentHull, null); - } - } - } - - if (Character.Bleeding > 1.0f || Character.Vitality < Character.MaxVitality * 0.1f) - { - - if (AddTargets(Character, Character)) - { - var orderPrefab = Order.PrefabList.Find(o => o.AITag == "requestfirstaid"); - newOrder = new Order(orderPrefab, Character.CurrentHull, null); - } - } - } - - if (newOrder != null) - { - if (GameMain.GameSession?.CrewManager != null && GameMain.GameSession.CrewManager.AddOrder(newOrder, newOrder.FadeOutTime)) - { - Character.Speak(newOrder.GetChatMessage("", Character.CurrentHull?.DisplayName, givingOrderToSelf: false), ChatMessageType.Order); - } - } - } - protected void ReportProblems() { Order newOrder = null; @@ -639,7 +566,7 @@ namespace Barotrauma private void RefreshHullSafety(Hull hull) { - if (GetHullSafety(hull) > HULL_SAFETY_THRESHOLD) + if (GetHullSafety(hull, Character, VisibleHulls) > HULL_SAFETY_THRESHOLD) { UnsafeHulls.Remove(hull); } @@ -727,17 +654,19 @@ namespace Barotrauma humanAI.ObjectiveManager.GetObjective()?.ReportedTargets.Remove(target)); } - public float GetHullSafety(Hull hull) + public float GetCurrentHullSafety() => GetHullSafety(Character.CurrentHull, Character, VisibleHulls); + + public float GetHullSafety(Hull hull, Character character, IEnumerable visibleHulls = null) { if (hull == null) { return 0; } bool ignoreFire = ObjectiveManager.IsCurrentObjective() || ObjectiveManager.IsCurrentObjective(); - bool ignoreWater = HasDivingSuit(Character); - bool ignoreOxygen = ignoreWater || HasDivingMask(Character); + bool ignoreWater = HasDivingSuit(character); + bool ignoreOxygen = ignoreWater || HasDivingMask(character); bool ignoreEnemies = ObjectiveManager.IsCurrentObjective(); - return GetHullSafety(hull, Character, ignoreWater, ignoreOxygen, ignoreFire, ignoreEnemies); + return GetHullSafety(hull, visibleHulls, character, ignoreWater, ignoreOxygen, ignoreFire, ignoreEnemies); } - public static float GetHullSafety(Hull hull, Character character, bool ignoreWater = false, bool ignoreOxygen = false, bool ignoreFire = false, bool ignoreEnemies = false) + public static float GetHullSafety(Hull hull, IEnumerable visibleHulls, Character character, bool ignoreWater = false, bool ignoreOxygen = false, bool ignoreFire = false, bool ignoreEnemies = false) { if (hull == null) { return 0; } if (hull.LethalPressure > 0 && character.PressureProtection <= 0) { return 0; } @@ -748,20 +677,30 @@ namespace Barotrauma oxygenFactor = 1; waterFactor = 1; } - // Even the smallest fire reduces the safety by 50% - float fire = hull.FireSources.Count * 0.5f + hull.FireSources.Sum(fs => fs.DamageRange) / hull.Size.X; - float fireFactor = ignoreFire ? 1 : MathHelper.Lerp(1, 0, MathHelper.Clamp(fire, 0, 1)); - int enemyCount = Character.CharacterList.Count(e => - e.CurrentHull == hull && !e.IsDead && !e.IsUnconscious && - (e.AIController is EnemyAIController || (e.TeamID != character.TeamID && character.TeamID != Character.TeamType.FriendlyNPC && e.TeamID != Character.TeamType.FriendlyNPC))); - // The hull safety decreases 90% per enemy up to 100% (TODO: test smaller percentages) - float enemyFactor = ignoreEnemies ? 1 : MathHelper.Lerp(1, 0, MathHelper.Clamp(enemyCount * 0.9f, 0, 1)); + float fireFactor = 1; + if (!ignoreFire) + { + Func calculateFire = h => h.FireSources.Count * 0.5f + h.FireSources.Sum(fs => fs.DamageRange) / h.Size.X; + // Even the smallest fire reduces the safety by 50% + float fire = visibleHulls == null ? calculateFire(hull) : visibleHulls.Sum(h => calculateFire(h)); + fireFactor = MathHelper.Lerp(1, 0, MathHelper.Clamp(fire, 0, 1)); + } + float enemyFactor = 1; + if (!ignoreEnemies) + { + Func isValidTarget = e => !e.IsDead && !e.IsUnconscious && !e.Removed && !IsFriendly(character, e); + int enemyCount = visibleHulls == null ? + Character.CharacterList.Count(e => e.CurrentHull == hull && isValidTarget(e)) : + Character.CharacterList.Count(e => visibleHulls.Contains(e.CurrentHull) && isValidTarget(e)); + // The hull safety decreases 90% per enemy up to 100% (TODO: test smaller percentages) + enemyFactor = MathHelper.Lerp(1, 0, MathHelper.Clamp(enemyCount * 0.9f, 0, 1)); + } float safety = oxygenFactor * waterFactor * fireFactor * enemyFactor; return MathHelper.Clamp(safety * 100, 0, 100); } public bool IsFriendly(Character other) => IsFriendly(Character, other); - // TODO: If the aliens are quaranteed to be in another team than the player, we wouldn't need to check the species. - public static bool IsFriendly(Character me, Character other) => other.TeamID == me.TeamID && other.SpeciesName == me.SpeciesName; + + public static bool IsFriendly(Character me, Character other) => (other.TeamID == me.TeamID || other.TeamID == Character.TeamType.FriendlyNPC || me.TeamID == Character.TeamType.FriendlyNPC) && other.SpeciesName == me.SpeciesName; } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveCombat.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveCombat.cs index 68ee23641..49755942c 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveCombat.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveCombat.cs @@ -319,7 +319,7 @@ namespace Barotrauma } if (retreatTarget == null || (retreatObjective != null && !retreatObjective.CanBeCompleted)) { - retreatTarget = findSafety.FindBestHull(new List() { character.CurrentHull }); + retreatTarget = findSafety.FindBestHull(HumanAIController.VisibleHulls); } TryAddSubObjective(ref retreatObjective, () => new AIObjectiveGoTo(retreatTarget, character, objectiveManager, false, true)); } @@ -439,8 +439,7 @@ namespace Barotrauma character.CursorPosition = Enemy.Position; float engageDistance = 500; if (squaredDistance > engageDistance * engageDistance) { return; } - bool canSeeTarget = character.CanSeeCharacter(Enemy); - if (!canSeeTarget && character.CurrentHull != Enemy.CurrentHull) { return; } + if (!character.CanSeeCharacter(Enemy)) { return; } if (Weapon.RequireAimToUse) { bool isOperatingButtons = false; diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFindSafety.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFindSafety.cs index 0df7cc0a5..cfd809ee5 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFindSafety.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFindSafety.cs @@ -51,7 +51,8 @@ namespace Barotrauma return; } if (character.OxygenAvailable < CharacterHealth.LowOxygenThreshold) { Priority = 100; } - currenthullSafety = HumanAIController.GetHullSafety(character.CurrentHull); + // TODO: no need to update every frame? + currenthullSafety = HumanAIController.GetCurrentHullSafety(); if (currenthullSafety > HumanAIController.HULL_SAFETY_THRESHOLD) { Priority -= priorityDecrease * deltaTime; @@ -128,7 +129,8 @@ namespace Barotrauma //goto objective doesn't exist (a safe hull not found, or a path to a safe hull not found) // -> attempt to manually steer away from hazards Vector2 escapeVel = Vector2.Zero; - foreach (FireSource fireSource in currentHull.FireSources) + // TODO: optimize + foreach (FireSource fireSource in HumanAIController.VisibleHulls.SelectMany(h => h.FireSources)) { Vector2 dir = character.Position - fireSource.Position; float distMultiplier = MathHelper.Clamp(100.0f / Vector2.Distance(fireSource.Position, character.Position), 0.1f, 10.0f); @@ -136,12 +138,8 @@ namespace Barotrauma } foreach (Character enemy in Character.CharacterList) { - //don't run from friendly NPCs - if (enemy.TeamID == Character.TeamType.FriendlyNPC) { continue; } - //friendly NPCs don't run away from anything but characters controlled by EnemyAIController (= monsters) - if (character.TeamID == Character.TeamType.FriendlyNPC && !(enemy.AIController is EnemyAIController)) { continue; } - if (enemy.CurrentHull == currentHull && !enemy.IsDead && !enemy.IsUnconscious && - (enemy.AIController is EnemyAIController || enemy.TeamID != character.TeamID)) + if (enemy.IsDead || enemy.IsUnconscious || enemy.Removed || HumanAIController.IsFriendly(enemy)) { continue; } + if (HumanAIController.VisibleHulls.Contains(enemy.CurrentHull)) { Vector2 dir = character.Position - enemy.Position; float distMultiplier = MathHelper.Clamp(100.0f / Vector2.Distance(enemy.Position, character.Position), 0.1f, 10.0f); @@ -150,9 +148,10 @@ namespace Barotrauma } if (escapeVel != Vector2.Zero) { + float left = HumanAIController.VisibleHulls.Min(h => h.Rect.X) + 50; + float right = HumanAIController.VisibleHulls.Max(h => h.Rect.Right) - 50; //only move if we haven't reached the edge of the room - if ((escapeVel.X < 0 && character.Position.X > currentHull.Rect.X + 50) || - (escapeVel.X > 0 && character.Position.X < currentHull.Rect.Right - 50)) + if (escapeVel.X < 0 && character.Position.X > left || escapeVel.X > 0 && character.Position.X < right) { character.AIController.SteeringManager.SteeringManual(deltaTime, escapeVel); } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFixLeak.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFixLeak.cs index 26cb19289..485ec975c 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFixLeak.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFixLeak.cs @@ -127,6 +127,32 @@ namespace Barotrauma { #if DEBUG DebugConsole.ThrowError("AIObjectiveFixLeak failed - the item \"" + weldingTool + "\" has no RepairTool component but is tagged as a welding tool"); +#endif + abandon = true; + return; + } + Vector2 gapDiff = Leak.WorldPosition - character.WorldPosition; + // TODO: use the collider size/reach? + if (!character.AnimController.InWater && Math.Abs(gapDiff.X) < 100 && gapDiff.Y < 0.0f && gapDiff.Y > -150) + { + HumanAIController.AnimController.Crouching = true; + } + float reach = ConvertUnits.ToSimUnits(repairTool.Range); + bool canOperate = ConvertUnits.ToSimUnits(gapDiff.Length()) < reach * 1.5f; + if (canOperate) + { + TryAddSubObjective(ref operateObjective, () => new AIObjectiveOperateItem(repairTool, character, objectiveManager, option: "", requireEquip: true, operateTarget: Leak)); + } + else + { + TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character, objectiveManager) { CloseEnough = reach * 0.75f }); + } + if (subObjectives.Any()) { return; } + var repairTool = weldingTool.GetComponent(); + if (repairTool == null) + { +#if DEBUG + DebugConsole.ThrowError("AIObjectiveFixLeak failed - the item \"" + weldingTool + "\" has no RepairTool component but is tagged as a welding tool"); #endif abandon = true; return; diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveIdle.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveIdle.cs index 71bfd44b5..56abec596 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveIdle.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveIdle.cs @@ -3,6 +3,7 @@ using Microsoft.Xna.Framework; using System; using System.Collections.Generic; using System.Linq; +using Barotrauma.Extensions; namespace Barotrauma { @@ -73,6 +74,21 @@ namespace Barotrauma } } + public override void Update(float deltaTime) + { + if (objectiveManager.CurrentObjective == this) + { + if (randomTimer > 0) + { + randomTimer -= deltaTime; + } + else + { + SetRandom(); + } + } + } + public override bool IsCompleted() => false; public override bool CanBeCompleted => true; @@ -98,7 +114,7 @@ namespace Barotrauma bool currentTargetIsInvalid = currentTarget == null || IsForbidden(currentTarget) || (PathSteering.CurrentPath != null && PathSteering.CurrentPath.Nodes.Any(n => HumanAIController.UnsafeHulls.Contains(n.CurrentHull))); - if (currentTargetIsInvalid || currentTarget == null && IsForbidden(character.CurrentHull)) + if (currentTargetIsInvalid || currentTarget == null && HumanAIController.VisibleHulls.Any(h => IsForbidden(h))) { newTargetTimer = 0; standStillTimer = 0; @@ -141,9 +157,7 @@ namespace Barotrauma // Check that there is no unsafe or forbidden hulls on the way to the target // Only do this when the current hull is ok, because otherwise would block all paths from the current hull to the target hull. var path = PathSteering.PathFinder.FindPath(character.SimPosition, randomHull.SimPosition); - if (path.Unreachable || - path.Nodes.Any(n => HumanAIController.UnsafeHulls.Contains(n.CurrentHull) || - IsForbidden(n.CurrentHull))) + if (path.Unreachable || path.Nodes.Any(n => HumanAIController.UnsafeHulls.Contains(n.CurrentHull) || IsForbidden(n.CurrentHull))) { //can't go to this room, remove it from the list and try another room next frame int index = targetHulls.IndexOf(randomHull); diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveOperateItem.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveOperateItem.cs index 8ba696802..d41e7c409 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveOperateItem.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveOperateItem.cs @@ -797,6 +797,10 @@ namespace Barotrauma { isCompleted = true; } + if (component.AIOperate(deltaTime, character, this)) + { + isCompleted = true; + } } else { diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRescue.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRescue.cs index a8921a9b7..7cac8a326 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRescue.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRescue.cs @@ -40,7 +40,7 @@ namespace Barotrauma if (character.SelectedCharacter != targetCharacter) { character.Speak(TextManager.Get("DialogFoundUnconsciousTarget") - .Replace("[targetname]", targetCharacter.Name).Replace("[roomname]", character.CurrentHull.DisplayName), + .Replace("[targetname]", targetCharacter.Name).Replace("[roomname]", targetCharacter.CurrentHull.DisplayName), null, 1.0f, "foundunconscioustarget" + targetCharacter.Name, 60.0f); @@ -71,7 +71,7 @@ namespace Barotrauma // Ensure that we have the find safety objective (should always be the case) objectiveManager.AddObjective(new AIObjectiveFindSafety(character, objectiveManager)); } - TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(findSafety.FindBestHull(new Hull[] { character.CurrentHull }), character, objectiveManager)); + TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(findSafety.FindBestHull(HumanAIController.VisibleHulls), character, objectiveManager)); } return; } @@ -89,7 +89,7 @@ namespace Barotrauma if (character.SelectedCharacter != targetCharacter) { character.Speak(TextManager.Get("DialogFoundWoundedTarget") - .Replace("[targetname]", targetCharacter.Name).Replace("[roomname]", character.CurrentHull.DisplayName), + .Replace("[targetname]", targetCharacter.Name).Replace("[roomname]", targetCharacter.CurrentHull.DisplayName), null, 1.0f, "foundwoundedtarget" + targetCharacter.Name, 60.0f); diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Character.cs b/Barotrauma/BarotraumaShared/Source/Characters/Character.cs index 922c12222..eecc373a6 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Character.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Character.cs @@ -1154,6 +1154,15 @@ namespace Barotrauma SmoothedCursorPosition = cursorPosition - smoothedCursorDiff; } + if (!(this is AICharacter) || Controlled == this || IsRemotePlayer) + { + //apply some smoothing to the cursor positions of remote players when playing as a client + //to make aiming look a little less choppy + Vector2 smoothedCursorDiff = cursorPosition - SmoothedCursorPosition; + smoothedCursorDiff = NetConfig.InterpolateCursorPositionError(smoothedCursorDiff); + SmoothedCursorPosition = cursorPosition - smoothedCursorDiff; + } + if (!(this is AICharacter) || Controlled == this || IsRemotePlayer) { if (speedMultipliers.Count == 0) return 1f; diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Deconstructor.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Deconstructor.cs index 2f15d6437..ef697cb74 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Deconstructor.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Deconstructor.cs @@ -696,6 +696,25 @@ namespace Barotrauma.Items.Components } } + if (targetItem.Prefab.DeconstructItems.Any()) + { + inputContainer.Inventory.RemoveItem(targetItem); + Entity.Spawner.AddToRemoveQueue(targetItem); + MoveInputQueue(); + PutItemsToLinkedContainer(); + } + else + { + if (outputContainer.Inventory.Items.All(i => i != null)) + { + targetItem.Drop(dropper: null); + } + else + { + outputContainer.Inventory.TryPutItem(targetItem, user: null, createNetworkEvent: true); + } + } + if (targetItem.Prefab.DeconstructItems.Any()) { inputContainer.Inventory.RemoveItem(targetItem); diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Steering.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Steering.cs index 7a471a65c..f6d19e87b 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Steering.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Steering.cs @@ -144,17 +144,6 @@ namespace Barotrauma.Items.Components } } - public Vector2 SteeringInput - { - get { return steeringInput; } - set - { - if (!MathUtils.IsValid(value)) return; - steeringInput.X = MathHelper.Clamp(value.X, -100.0f, 100.0f); - steeringInput.Y = MathHelper.Clamp(value.Y, -100.0f, 100.0f); - } - } - public SteeringPath SteeringPath { if (!CanBeSelected) return false; @@ -175,6 +164,12 @@ namespace Barotrauma.Items.Components set { posToMaintain = value; } } + public Vector2? PosToMaintain + { + get { return posToMaintain; } + set { posToMaintain = value; } + } + struct ObstacleDebugInfo { public Vector2 Point1; diff --git a/Barotrauma/BarotraumaShared/Source/Items/Item.cs b/Barotrauma/BarotraumaShared/Source/Items/Item.cs index fcfcb9e02..e83b84678 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Item.cs @@ -1171,6 +1171,38 @@ namespace Barotrauma } } + public void UpdateTransform() + { + Submarine prevSub = Submarine; + + FindHull(); + + if (Submarine == null && prevSub != null) + { + body.SetTransform(body.SimPosition + prevSub.SimPosition, body.Rotation); + } + else if (Submarine != null && prevSub == null) + { + body.SetTransform(body.SimPosition - Submarine.SimPosition, body.Rotation); + } + else if (Submarine != null && prevSub != null && Submarine != prevSub) + { + body.SetTransform(body.SimPosition + prevSub.SimPosition - Submarine.SimPosition, body.Rotation); + } + + Vector2 displayPos = ConvertUnits.ToDisplayUnits(body.SimPosition); + rect.X = (int)(displayPos.X - rect.Width / 2.0f); + rect.Y = (int)(displayPos.Y + rect.Height / 2.0f); + + if (Math.Abs(body.LinearVelocity.X) > NetConfig.MaxPhysicsBodyVelocity || + Math.Abs(body.LinearVelocity.Y) > NetConfig.MaxPhysicsBodyVelocity) + { + body.LinearVelocity = new Vector2( + MathHelper.Clamp(body.LinearVelocity.X, -NetConfig.MaxPhysicsBodyVelocity, NetConfig.MaxPhysicsBodyVelocity), + MathHelper.Clamp(body.LinearVelocity.Y, -NetConfig.MaxPhysicsBodyVelocity, NetConfig.MaxPhysicsBodyVelocity)); + } + } + public void UpdateTransform() { Submarine prevSub = Submarine; diff --git a/Barotrauma/BarotraumaShared/Source/Map/Hull.cs b/Barotrauma/BarotraumaShared/Source/Map/Hull.cs index a14e02bfd..c1815ac87 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Hull.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Hull.cs @@ -15,6 +15,8 @@ namespace Barotrauma public static List hullList = new List(); public static List EntityGrids { get; } = new List(); + public static bool ShowHulls = true; + public static bool EditWater, EditFire; public const float OxygenDistributionSpeed = 500.0f; public const float OxygenDeteriorationSpeed = 0.3f; @@ -208,6 +210,25 @@ namespace Barotrauma } } + public string DisplayName + { + get; + private set; + } + + private string roomName; + [Editable, Serialize("", true, translationTextTag: "RoomName.")] + public string RoomName + { + get { return roomName; } + set + { + if (roomName == value) { return; } + roomName = value; + DisplayName = TextManager.Get(roomName, returnNull: true) ?? roomName; + } + } + public override Rectangle Rect { get