diff --git a/Barotrauma/BarotraumaClient/Source/Characters/AI/HumanAIController.cs b/Barotrauma/BarotraumaClient/Source/Characters/AI/HumanAIController.cs index 4204f8957..f6571cde5 100644 --- a/Barotrauma/BarotraumaClient/Source/Characters/AI/HumanAIController.cs +++ b/Barotrauma/BarotraumaClient/Source/Characters/AI/HumanAIController.cs @@ -61,14 +61,6 @@ namespace Barotrauma var path = pathSteering.CurrentPath; if (path != null) { - if (path.CurrentNode != null) - { - GUI.DrawLine(spriteBatch, pos, - new Vector2(path.CurrentNode.DrawPosition.X, -path.CurrentNode.DrawPosition.Y), - Color.BlueViolet, 0, 3); - - GUI.DrawString(spriteBatch, pos + textOffset - new Vector2(0, 20), "Path cost: " + path.Cost.FormatZeroDecimal(), Color.White, Color.Black * 0.5f); - } for (int i = 1; i < path.Nodes.Count; i++) { var previousNode = path.Nodes[i - 1]; @@ -83,6 +75,14 @@ namespace Barotrauma new Vector2(currentNode.DrawPosition.X - 10, -currentNode.DrawPosition.Y - 30), Color.Blue); } + if (path.CurrentNode != null) + { + GUI.DrawLine(spriteBatch, pos, + new Vector2(path.CurrentNode.DrawPosition.X, -path.CurrentNode.DrawPosition.Y), + Color.BlueViolet, 0, 3); + + GUI.DrawString(spriteBatch, pos + textOffset - new Vector2(0, 20), "Path cost: " + path.Cost.FormatZeroDecimal(), Color.White, Color.Black * 0.5f); + } } } GUI.DrawLine(spriteBatch, pos, pos + ConvertUnits.ToDisplayUnits(new Vector2(Character.AnimController.TargetMovement.X, -Character.AnimController.TargetMovement.Y)), Color.SteelBlue, width: 2); diff --git a/Barotrauma/BarotraumaClient/Source/Characters/Animation/Ragdoll.cs b/Barotrauma/BarotraumaClient/Source/Characters/Animation/Ragdoll.cs index fa30d8a82..8f9276859 100644 --- a/Barotrauma/BarotraumaClient/Source/Characters/Animation/Ragdoll.cs +++ b/Barotrauma/BarotraumaClient/Source/Characters/Animation/Ragdoll.cs @@ -121,90 +121,7 @@ namespace Barotrauma 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) - { - 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.MemLocalState.Clear(); - } - else - { - //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++) - { - 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); - } - } - } character.MemLocalState.Clear(); } diff --git a/Barotrauma/BarotraumaClient/Source/GameSession/CrewManager.cs b/Barotrauma/BarotraumaClient/Source/GameSession/CrewManager.cs index e9bd2ba83..8b52b1fd9 100644 --- a/Barotrauma/BarotraumaClient/Source/GameSession/CrewManager.cs +++ b/Barotrauma/BarotraumaClient/Source/GameSession/CrewManager.cs @@ -74,12 +74,17 @@ namespace Barotrauma public CrewManager(XElement element, bool isSinglePlayer) : this(isSinglePlayer) { - if (!isSinglePlayer) + if (GameMain.Client != null) { - DebugConsole.ThrowError("Cannot add messages to single player chat box in multiplayer mode!\n" + Environment.StackTrace); + //let the server create random conversations in MP return; } - if (string.IsNullOrEmpty(text)) { return; } + List availableSpeakers = Character.CharacterList.FindAll(c => + c.AIController is HumanAIController && + !c.IsDead && + c.SpeechImpediment <= 100.0f); + pendingConversationLines.AddRange(NPCConversation.CreateRandom(availableSpeakers)); + } var characterInfo = new CharacterInfo(subElement); characterInfos.Add(characterInfo); @@ -90,7 +95,6 @@ namespace Barotrauma break; } } - ChatBox.AddMessage(ChatMessage.Create(senderName, text, messageType, sender)); } partial void InitProjectSpecific() @@ -239,27 +243,24 @@ namespace Barotrauma public IEnumerable GetCharacters() { - if (character?.Inventory == null) return null; + if (characterInfos.Contains(characterInfo)) + { + DebugConsole.ThrowError("Tried to add the same character info to CrewManager twice.\n" + Environment.StackTrace); + return; + } - var radioItem = character.Inventory.Items.FirstOrDefault(it => it != null && it.GetComponent() != null); - if (radioItem == null) return null; - if (requireEquipped && !character.HasEquippedItem(radioItem)) return null; - - return radioItem.GetComponent(); + characterInfos.Add(characterInfo); } public IEnumerable GetCharacterInfos() { - if (GameMain.Client != null) + if (character == null) { - //let the server create random conversations in MP + DebugConsole.ThrowError("Tried to remove a null character from CrewManager.\n" + Environment.StackTrace); return; } - List availableSpeakers = Character.CharacterList.FindAll(c => - c.AIController is HumanAIController && - !c.IsDead && - c.SpeechImpediment <= 100.0f); - pendingConversationLines.AddRange(NPCConversation.CreateRandom(availableSpeakers)); + characters.Remove(character); + if (removeInfo) characterInfos.Remove(character.Info); } public void AddCharacter(Character character) @@ -633,9 +634,183 @@ namespace Barotrauma { characterListBox.BarScroll = roundedPos; } - soundIcon.Visible = !muted && !mutedLocally; - soundIconDisabled.Visible = muted || mutedLocally; - soundIconDisabled.ToolTip = TextManager.Get(mutedLocally ? "MutedLocally" : "MutedGlobally"); + 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") + { + 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 + }; + + if (isSinglePlayer) + { + characterArea.OnClicked = CharacterClicked; + } + else + { + characterArea.CanBeFocused = false; + characterArea.CanBeSelected = false; + } + + 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) + { + 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) + { + SetCharacterOrder(character, Order.PrefabList.Find(o => o.AITag == "dismissed"), null, Character.Controlled); + } + else + { + if (order.ItemComponentType != null || order.ItemIdentifiers.Length > 0 || order.Options.Length > 1) + { + CreateOrderTargetFrame(button, character, order); + } + else + { + SetCharacterOrder(character, order, null, Character.Controlled); + } + } + return true; + }; + btn.UserData = order; + btn.ToolTip = order.Name; + + //divider between different groups of orders + if (i == correctOrderCount - 1 || i == correctOrderCount + neutralOrderCount - 1) + { + //TODO: divider sprite + new GUIFrame(new RectTransform(new Point(8, iconSize), orderButtonFrame.RectTransform), style: "GUIButton"); + } + } + + var toggleWrongOrderBtn = new GUIButton(new RectTransform(new Point((int)(30 * GUI.Scale), wrongOrderList.Rect.Height), wrongOrderList.Content.RectTransform), + "", style: "UIToggleButton") + { + 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; } private IEnumerable KillCharacterAnim(GUIComponent component) @@ -779,6 +954,12 @@ namespace Barotrauma } return; } + List availableSpeakers = Character.CharacterList.FindAll(c => + c.AIController is HumanAIController && + !c.IsDead && + c.SpeechImpediment <= 100.0f); + pendingConversationLines.AddRange(NPCConversation.CreateRandom(availableSpeakers)); + } character.SetOrder(order, option, orderGiver, speak: orderGiver != character); if (IsSinglePlayer) @@ -836,19 +1017,23 @@ namespace Barotrauma } } } - //only one target (or an order with no particular targets), just show options - else + + character.SetOrder(order, option, orderGiver, speak: orderGiver != character); + if (IsSinglePlayer) { - orderTargetFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.2f + order.Options.Length * 0.1f, 0.18f), GUI.Canvas) - { AbsoluteOffset = new Point(orderButton.Rect.Center.X, orderButton.Rect.Bottom) }, - isHorizontal: true, childAnchor: Anchor.BottomLeft) + orderGiver?.Speak( + order.GetChatMessage(character.Name, orderGiver.CurrentHull?.DisplayName, givingOrderToSelf: character == orderGiver, orderOption: option), null); + } + else if (orderGiver != null) + { + OrderChatMessage msg = new OrderChatMessage(order, option, order.TargetItemComponent?.Item, character, orderGiver); + if (GameMain.Client != null) { - UserData = character, - Stretch = true - }; - //line connecting the order button to the option buttons - //TODO: sprite - new GUIFrame(new RectTransform(new Vector2(0.5f, 1.0f), orderTargetFrame.RectTransform), style: null); + GameMain.Client.SendChatMessage(msg); + } + } + DisplayCharacterOrder(character, order); + } /// /// Create the UI panel that's used to select the target and options for a given order diff --git a/Barotrauma/BarotraumaClient/Source/Screens/CampaignSetupUI.cs b/Barotrauma/BarotraumaClient/Source/Screens/CampaignSetupUI.cs index b5429e97d..9ea92ed7b 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/CampaignSetupUI.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/CampaignSetupUI.cs @@ -472,6 +472,8 @@ namespace Barotrauma private GUILayoutGroup subPreviewContainer; + private GUILayoutGroup subPreviewContainer; + private GUIButton loadGameButton; public Action StartNewGame; diff --git a/Barotrauma/BarotraumaClient/Source/Screens/MainMenuScreen.cs b/Barotrauma/BarotraumaClient/Source/Screens/MainMenuScreen.cs index 287ac0265..96c441896 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/MainMenuScreen.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/MainMenuScreen.cs @@ -789,6 +789,10 @@ namespace Barotrauma GUI.Draw(Cam, spriteBatch); + spriteBatch.Begin(SpriteSortMode.Deferred, null, null, null, GameMain.ScissorTestEnable); + + GUI.Draw(Cam, spriteBatch); + GUI.Draw(Cam, spriteBatch); #if DEBUG diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/HumanAIController.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/HumanAIController.cs index 9574f72fa..a22274784 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/HumanAIController.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/HumanAIController.cs @@ -493,20 +493,25 @@ namespace Barotrauma item.ContainedItems.Any(i => i.HasTag(containedTag) && i.ConditionPercentage > conditionPercentage))); } - /// - /// Updates the hull safety for all ai characters in the team. - /// - public static void PropagateHullSafety(Character character, Hull hull) + public static void DoForEachCrewMember(Character character, Action action) { foreach (var c in Character.CharacterList) { if (c.AIController is HumanAIController humanAi && humanAi.IsFriendly(character)) { - humanAi.RefreshHullSafety(hull); + action(humanAi); } } } + /// + /// Updates the hull safety for all ai characters in the team. + /// + public static void PropagateHullSafety(Character character, Hull hull) + { + DoForEachCrewMember(character, (humanAi) => humanAi.RefreshHullSafety(hull)); + } + private void RefreshHullSafety(Hull hull) { if (GetHullSafety(hull) > HULL_SAFETY_THRESHOLD) @@ -577,21 +582,26 @@ namespace Barotrauma private static bool AddTargets(Character caller, T2 target) where T1 : AIObjectiveLoop { bool targetAdded = false; - foreach (var c in Character.CharacterList) + DoForEachCrewMember(caller, humanAI => { - if (IsFriendly(caller, c) && c.AIController is HumanAIController humanAI) + var objective = humanAI.ObjectiveManager.GetObjective(); + if (objective != null) { - var objective = humanAI.ObjectiveManager.GetObjective(); - if (objective == null) { continue; } if (!targetAdded && objective.AddTarget(target)) { targetAdded = true; } } - } + }); return targetAdded; } + public static void RemoveTargets(Character caller, T2 target) where T1 : AIObjectiveLoop + { + DoForEachCrewMember(caller, humanAI => + humanAI.ObjectiveManager.GetObjective()?.ReportedTargets.Remove(target)); + } + public float GetHullSafety(Hull hull) { if (hull == null) { return 0; } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjective.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjective.cs index 42cff3670..3fc4a3380 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjective.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjective.cs @@ -71,6 +71,7 @@ namespace Barotrauma #if DEBUG DebugConsole.NewMessage($"Removing subobjective {subObjective.DebugTag} of {DebugTag}, because it is completed."); #endif + subObjective.OnCompleted(); subObjectives.Remove(subObjective); } else if (!subObjective.CanBeCompleted) @@ -93,9 +94,9 @@ namespace Barotrauma bool wasCompleted = IsCompleted(); Act(deltaTime); - if (!wasCompleted && IsCompleted()) + if (IsCompleted()) { - Completed?.Invoke(); + OnCompleted(); } } @@ -213,6 +214,15 @@ namespace Barotrauma //} } + protected virtual void OnCompleted() + { + if (Completed != null) + { + Completed(); + Completed = null; + } + } + public virtual void Reset() { } protected abstract void Act(float deltaTime); diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveChargeBatteries.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveChargeBatteries.cs index 095989fbe..3b79d2f00 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveChargeBatteries.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveChargeBatteries.cs @@ -65,5 +65,8 @@ namespace Barotrauma protected override AIObjective ObjectiveConstructor(PowerContainer battery) => new AIObjectiveOperateItem(battery, character, objectiveManager, Option, false, priorityModifier: PriorityModifier) { IsLoop = false }; + + protected override void OnObjectiveCompleted(AIObjective objective, PowerContainer target) + => HumanAIController.RemoveTargets(character, target); } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveExtinguishFires.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveExtinguishFires.cs index fc5e33548..14e6bb13b 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveExtinguishFires.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveExtinguishFires.cs @@ -1,6 +1,7 @@ using System.Linq; using System.Collections.Generic; using Barotrauma.Extensions; +using System; namespace Barotrauma { @@ -20,7 +21,11 @@ namespace Barotrauma public override bool IsDuplicate(AIObjective otherObjective) => otherObjective is AIObjectiveExtinguishFires; protected override IEnumerable GetList() => Hull.hullList; - protected override AIObjective ObjectiveConstructor(Hull target) => new AIObjectiveExtinguishFire(character, target, objectiveManager, PriorityModifier); + protected override AIObjective ObjectiveConstructor(Hull target) + => new AIObjectiveExtinguishFire(character, target, objectiveManager, PriorityModifier); + + protected override void OnObjectiveCompleted(AIObjective objective, Hull target) + => HumanAIController.RemoveTargets(character, target); public static bool IsValidTarget(Hull hull, Character character) { diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFightIntruders.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFightIntruders.cs index 6fbaab3d2..162d0f5e5 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFightIntruders.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFightIntruders.cs @@ -21,14 +21,18 @@ namespace Barotrauma protected override IEnumerable GetList() => Character.CharacterList; - protected override AIObjective ObjectiveConstructor(Character target) => new AIObjectiveCombat(character, target, AIObjectiveCombat.CombatMode.Offensive, objectiveManager, PriorityModifier); - protected override float TargetEvaluation() { // TODO: sorting criteria return 100; } + protected override AIObjective ObjectiveConstructor(Character target) + => new AIObjectiveCombat(character, target, AIObjectiveCombat.CombatMode.Offensive, objectiveManager, PriorityModifier); + + protected override void OnObjectiveCompleted(AIObjective objective, Character target) + => HumanAIController.RemoveTargets(character, target); + public static bool IsValidTarget(Character target, Character character) { if (target == null || target.IsDead || target.Removed) { return false; } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFixLeak.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFixLeak.cs index 2789e2983..1f2ee838a 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFixLeak.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFixLeak.cs @@ -697,6 +697,32 @@ namespace Barotrauma { 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; + } + 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 }); + } } private Vector2 GetStandPosition() diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFixLeaks.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFixLeaks.cs index 34c7093ea..1ba96d4e4 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFixLeaks.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFixLeaks.cs @@ -27,7 +27,11 @@ namespace Barotrauma public override bool IsDuplicate(AIObjective otherObjective) => otherObjective is AIObjectiveFixLeaks; protected override float TargetEvaluation() => Targets.Max(t => GetLeakSeverity(t)); protected override IEnumerable GetList() => Gap.GapList; - protected override AIObjective ObjectiveConstructor(Gap gap) => new AIObjectiveFixLeak(gap, character, objectiveManager, PriorityModifier); + protected override AIObjective ObjectiveConstructor(Gap gap) + => new AIObjectiveFixLeak(gap, character, objectiveManager, PriorityModifier); + + protected override void OnObjectiveCompleted(AIObjective objective, Gap target) + => HumanAIController.RemoveTargets(character, target); public static bool IsValidTarget(Gap gap, Character character) { diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGetItem.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGetItem.cs index 66273676a..16ccde05e 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGetItem.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGetItem.cs @@ -271,10 +271,13 @@ namespace Barotrauma { bool isEquipped = !equip || character.HasEquippedItem(item); if (character.Inventory.Items.Contains(item) && isEquipped) { return true; } - Item rootContainer = item.GetRootContainer(); - if (rootContainer != null && rootContainer.ParentInventory is CharacterInventory) + if (!equip) { - return rootContainer.ParentInventory.Owner == character; + Item rootContainer = item.GetRootContainer(); + if (rootContainer != null && rootContainer.ParentInventory is CharacterInventory) + { + return rootContainer.ParentInventory.Owner == character; + } } return false; } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveIdle.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveIdle.cs index eeef8131c..1e1e961f6 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveIdle.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveIdle.cs @@ -73,6 +73,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; diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveLoop.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveLoop.cs index cbcd1305f..d288ca2c2 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveLoop.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveLoop.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using Barotrauma.Extensions; using Microsoft.Xna.Framework; @@ -87,7 +88,7 @@ namespace Barotrauma } } - // the timer is set between 1 and 10 seconds, depending on the priority + // the timer is set between 1 and 10 seconds, depending on the priority modifier and a random +-25% private float SetTargetUpdateTimer() => targetUpdateTimer = 1 / MathHelper.Clamp(PriorityModifier * Rand.Range(0.75f, 1.25f), 0.1f, 1); public override void Reset() @@ -158,13 +159,18 @@ namespace Barotrauma if (!Objectives.TryGetValue(target, out AIObjective objective)) { objective = ObjectiveConstructor(target); - objective.Completed += () => ReportedTargets.Remove(target); + objective.Completed += () => OnObjectiveCompleted(objective, target); Objectives.Add(target, objective); - AddSubObjective(objective); + if (!subObjectives.Contains(objective)) + { + subObjectives.Add(objective); + } } } } + protected abstract void OnObjectiveCompleted(AIObjective objective, T target); + /// /// List of all possible items of the specified type. Used for filtering the removed objectives. /// diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveOperateItem.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveOperateItem.cs index 92a3e2c73..8c70f0df6 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveOperateItem.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveOperateItem.cs @@ -346,6 +346,10 @@ namespace Barotrauma { isCompleted = true; } + if (component.AIOperate(deltaTime, character, this)) + { + isCompleted = true; + } } else { diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectivePumpWater.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectivePumpWater.cs index 5dd4d4768..a1f38a53b 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectivePumpWater.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectivePumpWater.cs @@ -54,7 +54,6 @@ namespace Barotrauma return pumpList; } - protected override AIObjective ObjectiveConstructor(Pump pump) => new AIObjectiveOperateItem(pump, character, objectiveManager, Option, false) { IsLoop = false }; protected override float TargetEvaluation() { if (Option == "stoppumping") @@ -66,5 +65,11 @@ namespace Barotrauma return Targets.Max(t => MathHelper.Lerp(100, 0, Math.Abs(-t.FlowPercentage / 100))); } } + + protected override AIObjective ObjectiveConstructor(Pump pump) + => new AIObjectiveOperateItem(pump, character, objectiveManager, Option, false) { IsLoop = false }; + + protected override void OnObjectiveCompleted(AIObjective objective, Pump target) + => HumanAIController.RemoveTargets(character, target); } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRepairItems.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRepairItems.cs index 77c4326c0..cfbda6189 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRepairItems.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRepairItems.cs @@ -54,7 +54,12 @@ namespace Barotrauma protected override float TargetEvaluation() => Targets.Max(t => 100 - t.ConditionPercentage); protected override IEnumerable GetList() => Item.ItemList; - protected override AIObjective ObjectiveConstructor(Item item) => new AIObjectiveRepairItem(character, item, objectiveManager, PriorityModifier); + + protected override AIObjective ObjectiveConstructor(Item item) + => new AIObjectiveRepairItem(character, item, objectiveManager, PriorityModifier); + + protected override void OnObjectiveCompleted(AIObjective objective, Item target) + => HumanAIController.RemoveTargets(character, target); public static bool IsValidTarget(Item item, Character character) { diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRescueAll.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRescueAll.cs index 0d7cea7ec..819ff805f 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRescueAll.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRescueAll.cs @@ -22,12 +22,16 @@ namespace Barotrauma protected override IEnumerable GetList() => Character.CharacterList; - protected override AIObjective ObjectiveConstructor(Character target) => new AIObjectiveRescue(character, target, objectiveManager, PriorityModifier); - protected override float TargetEvaluation() => Targets.Max(t => GetVitalityFactor(t)) * 100; public static float GetVitalityFactor(Character character) => (character.MaxVitality - character.Vitality) / character.MaxVitality; + protected override AIObjective ObjectiveConstructor(Character target) + => new AIObjectiveRescue(character, target, objectiveManager, PriorityModifier); + + protected override void OnObjectiveCompleted(AIObjective objective, Character target) + => HumanAIController.RemoveTargets(character, target); + public static bool IsValidTarget(Character target, Character character) { if (target == null || target.IsDead || target.Removed) { return false; } diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Deconstructor.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Deconstructor.cs index 76b490647..2f15d6437 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Deconstructor.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Deconstructor.cs @@ -677,6 +677,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 a251165ce..2aafee648 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Steering.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Steering.cs @@ -212,33 +212,6 @@ namespace Barotrauma.Items.Components } } - public Vector2? PosToMaintain - { - get { return posToMaintain; } - set { posToMaintain = value; } - } - - struct ObstacleDebugInfo - { - public Vector2 Point1; - public Vector2 Point2; - - public Vector2? Intersection; - - public float Dot; - - public Vector2 AvoidStrength; - - public ObstacleDebugInfo(GraphEdge edge, Vector2? intersection, float dot, Vector2 avoidStrength) - { - Point1 = edge.Point1; - Point2 = edge.Point2; - Intersection = intersection; - Dot = dot; - AvoidStrength = avoidStrength; - } - } - //edge point 1, edge point 2, avoid strength private List debugDrawObstacles = new List(); diff --git a/Barotrauma/BarotraumaShared/Source/Items/Item.cs b/Barotrauma/BarotraumaShared/Source/Items/Item.cs index 39bdfb8f0..4392ee683 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Item.cs @@ -1128,6 +1128,10 @@ namespace Barotrauma { ApplyStatusEffects(!waterProof && inWater ? ActionType.InWater : ActionType.NotInWater, deltaTime); } + if (!broken) + { + ApplyStatusEffects(!waterProof && inWater ? ActionType.InWater : ActionType.NotInWater, deltaTime); + } if (body == null || !body.Enabled || !inWater || ParentInventory != null || Removed) { return; } @@ -1172,6 +1176,27 @@ namespace Barotrauma } } + public void UpdateTransform() + { + Submarine prevSub = Submarine; + + FindHull(); + + if (body == null || !body.Enabled || !inWater || ParentInventory != null || Removed) { return; } + + 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 f47eb56e0..990d2e0cc 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Hull.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Hull.cs @@ -550,6 +550,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 diff --git a/Barotrauma/BarotraumaShared/changelog.txt b/Barotrauma/BarotraumaShared/changelog.txt index 1a241fb5f..59edb7328 100644 --- a/Barotrauma/BarotraumaShared/changelog.txt +++ b/Barotrauma/BarotraumaShared/changelog.txt @@ -4,47 +4,27 @@ v0.9.0.0 Additions and changes: - Translations to German, Russian, French, Brazilian Portuguese, Traditional Chinese and Simplified Chinese. +- New logo and main menu art. +- 2 new outposts. +- New docking system. - Numerous fixes and improvements to the crew AI. The biggest change is that the AI characters can now do tasks by themselves even when they haven't been assigned an order. -- One new outpost. -- New docking system. -- New logo and main menu art. -- Added a credits tab to the main menu. -- Some new item and structure sprites. -- Some new environment sprites. -- Increased the amount of credits the player starts with in the campaign. -- Balanced location and mission distribution. -- Balanced mission rewards. -- AI characters can be ordered to fight intruders. - Lock the inventory when aiming with a railgun or a coilgun. - Swap the order of the starting location & destination in nav terminals when playing in a mirrored sub. -- Option to automatically pause the game when the window loses focus. Bugfixes: - Fixed splash screens and tutorial videos crashing the Linux version. - Fixed damaged item sprites not updating client-side after being repaired. - Fixed characters occasionally taking impact damage when hitting walls while climbing. -- Fixes crashing when attempting to use symbols such as <, > or | in the save file name. -- UI layout/scale fixes on resolutions larger than 1080p. -- Fixed crashing when loading the same sub twice in the sub editor. -- Fixed submarines occasionally being rendered at an incorrect position when viewing them in the sub editor -after they've been used in-game. - Improved the syncing of ragdolled characters. -- Fixed characters not receiving impact damage when ragdolled. -- Fixed characters' arms occasionally spinning around when standing still. -- Fixed tutorial level generation parameters being used in some normal campaign levels (leading to extremely -small levels and submarines overlapping with the outposts). - Fixed characters running more slowly when their torso is in a different hull than the feet (for example in Humpback's bilge). -- Fixed character's feet getting stuck to platforms when climbing ladders while holding A/D. -- Fixed monsters being able to drag characters through walls. - Fixed items "vanishing" if they move directly from sub to another without going outside first. - Fixed content package hash calculation failing if the package is not enabled and contains new monster files. - Fixed inability to enable content packages if some of the files included in the package are already in the game folder (which may happen, for example, if enabling a content package fails). - Fixed AllowRagdollButton settings not being synced with clients, leading to strange ragdolling behavior client-side if the server has disabled ragdolling. -- Fixed incorrect keybinds being displayed in a couple of tutorial texts. --------------------------------------------------------------------------------------------------------- v0.8.10.0