diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterHUD.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterHUD.cs index b73c316c0..23cab333e 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterHUD.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterHUD.cs @@ -321,6 +321,17 @@ namespace Barotrauma { character.SelectedConstruction.DrawHUD(spriteBatch, cam, Character.Controlled); } + if (Character.Controlled.Inventory != null) + { + foreach (Item item in Character.Controlled.Inventory.Items) + { + if (item == null) { continue; } + if (Character.Controlled.HasEquippedItem(item)) + { + item.DrawHUD(spriteBatch, cam, Character.Controlled); + } + } + } if (IsCampaignInterfaceOpen) { return; } @@ -431,6 +442,14 @@ namespace Barotrauma GUI.DrawString(spriteBatch, textPos, focusName, nameColor, Color.Black * 0.7f, 2, GUI.SubHeadingFont); textPos.X += 10.0f * GUI.Scale; textPos.Y += GUI.SubHeadingFont.MeasureString(focusName).Y; + + if (!character.FocusedCharacter.IsIncapacitated && character.FocusedCharacter.IsPet) + { + GUI.DrawString(spriteBatch, textPos, GetCachedHudText("PlayHint", GameMain.Config.KeyBindText(InputType.Use)), + GUI.Style.Green, Color.Black, 2, GUI.SmallFont); + textPos.Y += largeTextSize.Y; + } + if (character.FocusedCharacter.CanBeDragged) { GUI.DrawString(spriteBatch, textPos, GetCachedHudText("GrabHint", GameMain.Config.KeyBindText(InputType.Grab)), diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Limb.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Limb.cs index dd6ec35d7..f452ddcb2 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Limb.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Limb.cs @@ -278,17 +278,7 @@ namespace Barotrauma DamagedSprite = new Sprite(subElement, file: GetSpritePath(subElement, Params.damagedSpriteParams)); break; case "conditionalsprite": - ISerializableEntity targetEntity; - string target = subElement.GetAttributeString("target", null); - if (string.Equals(target, "character", StringComparison.OrdinalIgnoreCase)) - { - targetEntity = character; - } - else - { - targetEntity = this; - } - var conditionalSprite = new ConditionalSprite(subElement, targetEntity, file: GetSpritePath(subElement, null)); + var conditionalSprite = new ConditionalSprite(subElement, GetConditionalTarget(), file: GetSpritePath(subElement, null)); ConditionalSprites.Add(conditionalSprite); if (conditionalSprite.DeformableSprite != null) { @@ -300,7 +290,7 @@ namespace Barotrauma CreateDeformations(subElement); break; case "lightsource": - LightSource = new LightSource(subElement) + LightSource = new LightSource(subElement, GetConditionalTarget()) { ParentBody = body, SpriteScale = Vector2.One * Scale * TextureScale @@ -310,6 +300,21 @@ namespace Barotrauma break; } + ISerializableEntity GetConditionalTarget() + { + ISerializableEntity targetEntity; + string target = subElement.GetAttributeString("target", null); + if (string.Equals(target, "character", StringComparison.OrdinalIgnoreCase)) + { + targetEntity = character; + } + else + { + targetEntity = this; + } + return targetEntity; + } + void CreateDeformations(XElement e) { foreach (XElement animationElement in e.GetChildElements("spritedeformation")) @@ -341,6 +346,7 @@ namespace Barotrauma } } } + LightSource?.CheckConditionals(); } public void RecreateSprites() @@ -561,6 +567,11 @@ namespace Barotrauma } } + foreach (var conditionalSprite in ConditionalSprites) + { + conditionalSprite.CheckConditionals(); + } + if (LightSource != null) { LightSource.ParentSub = body.Submarine; @@ -573,6 +584,7 @@ namespace Barotrauma { LightSource.DeformableLightSprite.Sprite.Depth = ActiveSprite.Depth; } + LightSource.CheckConditionals(); } UpdateSpriteStates(deltaTime); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/EventActions/ConversationAction.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/EventActions/ConversationAction.cs index 9c2c4795a..a3270bafa 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Events/EventActions/ConversationAction.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/EventActions/ConversationAction.cs @@ -271,7 +271,7 @@ namespace Barotrauma private static List CreateConversation(GUIListBox parentBox, string text, Character speaker, IEnumerable options, bool drawChathead = true) { - var content = new GUILayoutGroup(new RectTransform(Vector2.One, parentBox.Content.RectTransform), childAnchor: Anchor.CenterLeft, isHorizontal: true) + var content = new GUILayoutGroup(new RectTransform(Vector2.One, parentBox.Content.RectTransform), childAnchor: Anchor.TopLeft, isHorizontal: true) { Stretch = true, CanBeFocused = true, @@ -289,7 +289,7 @@ namespace Barotrauma }); } - var textContent = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f), content.RectTransform), childAnchor: Anchor.TopCenter) + var textContent = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0f), content.RectTransform), childAnchor: Anchor.TopCenter) { AbsoluteSpacing = GUI.IntScale(5) }; @@ -316,7 +316,7 @@ namespace Barotrauma content.Recalculate(); textContent.Recalculate(); textBlock.CalculateHeightFromText(); - textBlock.RectTransform.MinSize = new Point(0, (int)(textBlock.Rect.Height * 1.2f)); + textBlock.RectTransform.MinSize = new Point(0, textBlock.Rect.Height); foreach (GUIButton btn in buttons) { btn.TextBlock.SetTextPos(); @@ -324,10 +324,12 @@ namespace Barotrauma btn.RectTransform.MinSize = new Point(0, (int)(btn.TextBlock.Rect.Height * 1.2f)); } - textContent.RectTransform.MinSize = new Point(0, textContent.Children.Sum(c => c.Rect.Height + textContent.AbsoluteSpacing) + GUI.IntScale(16)); + textContent.RectTransform.MinSize = new Point(0, textContent.Children.Sum(c => c.Rect.Height) + GUI.IntScale(16)); + content.RectTransform.MinSize = new Point(0, content.Children.Sum(c => c.Rect.Height)); // Recalculate the text size as it is scaled up and no longer matching the text height due to the textContent's minSize increasing textBlock.CalculateHeightFromText(); + textBlock.TextAlignment = Alignment.TopLeft; //content.RectTransform.MinSize = new Point(0, textContent.Rect.Height); return buttons; diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/ChatBox.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/ChatBox.cs index e38b411ea..41ac0aeba 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/ChatBox.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/ChatBox.cs @@ -650,6 +650,8 @@ namespace Barotrauma if (Character.Controlled != null && ChatMessage.CanUseRadio(Character.Controlled, out WifiComponent radio)) { radio.Channel = channel; + GameMain.Client?.CreateEntityEvent(radio.Item, new object[] { NetEntityEvent.Type.ChangeProperty, radio.SerializableProperties["channel"] }); + if (setText) { string text = radio.Channel.ToString().PadLeft(4, '0'); diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/LoadingScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/LoadingScreen.cs index eb719fe7f..ba3c56caf 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/LoadingScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/LoadingScreen.cs @@ -348,7 +348,7 @@ namespace Barotrauma spriteBatch.Draw(currSplashScreen.GetTexture(), new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight), Color.White); spriteBatch.End(); - if (DateTime.Now > videoStartTime + new TimeSpan(0, 0, 0, 0, milliseconds: 500) && GameMain.WindowActive && (PlayerInput.KeyHit(Keys.Space) || PlayerInput.KeyHit(Keys.Enter) || PlayerInput.PrimaryMouseButtonDown())) + if (DateTime.Now > videoStartTime + new TimeSpan(0, 0, 0, 0, milliseconds: 500) && GameMain.WindowActive && (PlayerInput.KeyHit(Keys.Escape) || PlayerInput.KeyHit(Keys.Space) || PlayerInput.KeyHit(Keys.Enter) || PlayerInput.PrimaryMouseButtonDown())) { currSplashScreen.Dispose(); currSplashScreen = null; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs index 68c2ea1dc..72234cabf 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs @@ -261,12 +261,7 @@ namespace Barotrauma var sub = Character.Controlled.Submarine; if (sub == null || sub.TeamID != Character.Controlled.TeamID || sub.Info.IsWreck) { return false; } SetCharacterOrder(null, order, null, Character.Controlled); - var visibleHulls = new List(Character.Controlled.GetVisibleHulls()); - foreach (var hull in visibleHulls) - { - HumanAIController.PropagateHullSafety(Character.Controlled, hull); - HumanAIController.RefreshTargets(Character.Controlled, order, hull); - } + if (IsSinglePlayer) { HumanAIController.ReportProblem(Character.Controlled, order); } return true; }, UserData = order, @@ -2221,8 +2216,8 @@ namespace Barotrauma var operateWeaponsPrefab = Order.GetPrefab(orderIdentifier); if (contextualOrders.None(o => o.Identifier.Equals(orderIdentifier)) && itemContext.Components.Any(c => c is Controller)) { - var turret = itemContext.GetConnectedComponents().FirstOrDefault(c => operateWeaponsPrefab.TargetItems.Contains(c.Item.Prefab.Identifier)) ?? - itemContext.GetConnectedComponents(recursive: true).FirstOrDefault(c => operateWeaponsPrefab.TargetItems.Contains(c.Item.Prefab.Identifier)); + var turret = itemContext.GetConnectedComponents().FirstOrDefault(c => c.Item.HasTag(operateWeaponsPrefab.TargetItems)) ?? + itemContext.GetConnectedComponents(recursive: true).FirstOrDefault(c => c.Item.HasTag(operateWeaponsPrefab.TargetItems)); if (turret != null) { contextualOrders.Add(new Order(operateWeaponsPrefab, turret.Item, turret, Character.Controlled)); } } @@ -2316,8 +2311,8 @@ namespace Barotrauma if (item.Repairables.Any(r => item.ConditionPercentage < r.RepairThreshold)) { return true; } var operateWeaponsPrefab = Order.GetPrefab("operateweapons"); return item.Components.Any(c => c is Controller) && - (item.GetConnectedComponents().Any(c => operateWeaponsPrefab.TargetItems.Contains(c.Item.Prefab.Identifier)) || - item.GetConnectedComponents(recursive: true).Any(c => operateWeaponsPrefab.TargetItems.Contains(c.Item.Prefab.Identifier))); + (item.GetConnectedComponents().Any(c => c.Item.HasTag(operateWeaponsPrefab.TargetItems)) || + item.GetConnectedComponents(recursive: true).Any(c => c.Item.HasTag(operateWeaponsPrefab.TargetItems))); } private GUIButton CreateOrderNode(Point size, RectTransform parent, Point offset, Order order, int hotkey, bool disableNode = false, bool checkIfOrderCanBeHeard = true) diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/EngineerTutorial.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/EngineerTutorial.cs index 77b00586c..89aff68ae 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/EngineerTutorial.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/EngineerTutorial.cs @@ -311,6 +311,8 @@ namespace Barotrauma.Tutorials } yield return null; } while (engineer_reactor.AvailableFuel == 0); + RemoveCompletedObjective(segments[1]); + TriggerTutorialSegment(2); CoroutineManager.StartCoroutine(ReactorOperatedProperly()); do { @@ -352,7 +354,7 @@ namespace Barotrauma.Tutorials } while (wait > 0.0f); engineer.SelectedConstruction = null; engineer_reactor.CanBeSelected = false; - RemoveCompletedObjective(segments[1]); + RemoveCompletedObjective(segments[2]); SetHighlight(engineer_reactor.Item, false); SetHighlight(engineer_brokenJunctionBox, true); SetDoorAccess(engineer_secondDoor, engineer_secondDoorLight, true); @@ -361,7 +363,7 @@ namespace Barotrauma.Tutorials do { yield return null; } while (!engineer_secondDoor.IsOpen); yield return new WaitForSeconds(1f, false); Repairable repairableJunctionBoxComponent = engineer_brokenJunctionBox.GetComponent(); - TriggerTutorialSegment(2, GameMain.Config.KeyBindText(InputType.Select)); // Repair the junction box + TriggerTutorialSegment(3, GameMain.Config.KeyBindText(InputType.Select)); // Repair the junction box do { if (!engineer.HasEquippedItem("screwdriver")) @@ -389,7 +391,7 @@ namespace Barotrauma.Tutorials do { yield return null; } while (!engineer_thirdDoor.IsOpen); GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Engineer.Radio.FaultyWiring"), ChatMessageType.Radio, null); yield return new WaitForSeconds(2f, false); - TriggerTutorialSegment(3, GameMain.Config.KeyBindText(InputType.Use), GameMain.Config.KeyBindText(InputType.Deselect)); // Connect the junction boxes + TriggerTutorialSegment(4, GameMain.Config.KeyBindText(InputType.Use), GameMain.Config.KeyBindText(InputType.Deselect)); // Connect the junction boxes do { CheckGhostWires(); HandleJunctionBoxWiringHighlights(); yield return null; } while (engineer_workingPump.Voltage < engineer_workingPump.MinVoltage); // Wait until connected all the way to the pump CheckGhostWires(); for (int i = 0; i < engineer_disconnectedJunctionBoxes.Length; i++) @@ -406,7 +408,7 @@ namespace Barotrauma.Tutorials do { yield return null; } while (!tutorial_enteredSubmarineSensor.MotionDetected); GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Engineer.Radio.Submarine"), ChatMessageType.Radio, null); yield return new WaitForSeconds(2f, false); - TriggerTutorialSegment(4); // Repair junction box + TriggerTutorialSegment(5); // Repair junction box while (ContentRunning) yield return null; engineer.AddActiveObjectiveEntity(engineer_submarineJunctionBox_1, engineer_repairIcon, engineer_repairIconColor); engineer.AddActiveObjectiveEntity(engineer_submarineJunctionBox_2, engineer_repairIcon, engineer_repairIconColor); @@ -425,7 +427,7 @@ namespace Barotrauma.Tutorials RemoveCompletedObjective(segments[4]); yield return new WaitForSeconds(2f, false); - TriggerTutorialSegment(5); // Powerup reactor + TriggerTutorialSegment(6); // Powerup reactor SetHighlight(engineer_submarineReactor.Item, true); engineer.AddActiveObjectiveEntity(engineer_submarineReactor.Item, engineer_reactorIcon, engineer_reactorIconColor); do { yield return null; } while (!IsReactorPoweredUp(engineer_submarineReactor)); // Wait until ~matches load diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/MechanicTutorial.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/MechanicTutorial.cs index e4c715ade..ea817521c 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/MechanicTutorial.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/MechanicTutorial.cs @@ -141,7 +141,7 @@ namespace Barotrauma.Tutorials mechanic_brokenWall_1.SpriteColor = Color.White; for (int i = 0; i < mechanic_brokenWall_1.SectionCount; i++) { - mechanic_brokenWall_1.AddDamage(i, 165); + mechanic_brokenWall_1.AddDamage(i, 85); } mechanic_brokenhull_1 = mechanic_brokenWall_1.Sections[0].gap.FlowTargetHull; @@ -199,7 +199,7 @@ namespace Barotrauma.Tutorials mechanic_brokenWall_2.SpriteColor = Color.White; for (int i = 0; i < mechanic_brokenWall_2.SectionCount; i++) { - mechanic_brokenWall_2.AddDamage(i, 165); + mechanic_brokenWall_2.AddDamage(i, 85); } mechanic_brokenhull_2 = mechanic_brokenWall_2.Sections[0].gap.FlowTargetHull; SetDoorAccess(tutorial_submarineDoor, tutorial_submarineDoorLight, false); diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/OfficerTutorial.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/OfficerTutorial.cs index 67077870c..caaa10e45 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/OfficerTutorial.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/OfficerTutorial.cs @@ -362,10 +362,10 @@ namespace Barotrauma.Tutorials SetHighlight(officer_rangedWeaponHolder.Item, false); do { - HighlightInventorySlot(officer.Inventory, "harpoongun", highlightColor, 0.5f, 0.5f, 0f); + HighlightInventorySlot(officer.Inventory, "shotgun", highlightColor, 0.5f, 0.5f, 0f); yield return null; - } while (!officer.HasEquippedItem("harpoongun")); // Wait until equipped - ItemContainer harpoonGunChamber = officer.Inventory.FindItemByIdentifier("harpoongun").GetComponent(); + } while (!officer.HasEquippedItem("shotgun")); // Wait until equipped + ItemContainer shotGunChamber = officer.Inventory.FindItemByIdentifier("shotgun").GetComponent(); SetHighlight(officer_rangedWeaponCabinet.Item, true); do { @@ -376,7 +376,7 @@ namespace Barotrauma.Tutorials for (int i = 0; i < officer_rangedWeaponCabinet.Inventory.Items.Length; i++) { if (officer_rangedWeaponCabinet.Inventory.Items[i] == null) continue; - if (officer_rangedWeaponCabinet.Inventory.Items[i].Prefab.Identifier == "spear") + if (officer_rangedWeaponCabinet.Inventory.Items[i].Prefab.Identifier == "shotgunshell") { HighlightInventorySlot(officer_rangedWeaponCabinet.Inventory, i, highlightColor, 0.5f, 0.5f, 0f); } @@ -387,18 +387,18 @@ namespace Barotrauma.Tutorials for (int i = 0; i < officer.Inventory.Items.Length; i++) { if (officer.Inventory.Items[i] == null) continue; - if (officer.Inventory.Items[i].Prefab.Identifier == "spear") + if (officer.Inventory.Items[i].Prefab.Identifier == "shotgunshell") { HighlightInventorySlot(officer.Inventory, i, highlightColor, 0.5f, 0.5f, 0f); } } - if (officer.Inventory.FindItemByIdentifier("spear") != null || (IsSelectedItem(officer_rangedWeaponCabinet.Item) && officer_rangedWeaponCabinet.Inventory.FindItemByIdentifier("spear") != null)) + if (officer.Inventory.FindItemByIdentifier("shotgunshell") != null || (IsSelectedItem(officer_rangedWeaponCabinet.Item) && officer_rangedWeaponCabinet.Inventory.FindItemByIdentifier("shotgunshell") != null)) { - HighlightInventorySlot(officer.Inventory, "harpoongun", highlightColor, 0.5f, 0.5f, 0f); + HighlightInventorySlot(officer.Inventory, "shotgun", highlightColor, 0.5f, 0.5f, 0f); } yield return null; - } while (!harpoonGunChamber.Inventory.IsFull()); // Wait until all six harpoons loaded + } while (!shotGunChamber.Inventory.IsFull()); // Wait until all six harpoons loaded RemoveCompletedObjective(segments[5]); SetHighlight(officer_rangedWeaponCabinet.Item, false); SetDoorAccess(officer_fourthDoor, officer_fourthDoorLight, true); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/CharacterInventory.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/CharacterInventory.cs index ba28a7192..9171c6b6d 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/CharacterInventory.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/CharacterInventory.cs @@ -51,13 +51,13 @@ namespace Barotrauma limbSlotIcons.Add(InvSlotType.LeftHand, new Sprite("Content/UI/InventoryUIAtlas.png", new Rectangle(634, 0, 128, 128))); limbSlotIcons.Add(InvSlotType.RightHand, new Sprite("Content/UI/InventoryUIAtlas.png", new Rectangle(762, 0, 128, 128))); limbSlotIcons.Add(InvSlotType.OuterClothes, new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(256 + margin, 128 + margin, 128 - margin * 2, 128 - margin * 2))); - limbSlotIcons.Add(InvSlotType.Bag, new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(256 + margin, 128 + margin, 128 - margin * 2, 128 - margin * 2))); + limbSlotIcons.Add(InvSlotType.Bag, new Sprite("Content/UI/CommandUIAtlas.png", new Rectangle(639, 926, 128,80))); } return limbSlotIcons; } } - public const InvSlotType PersonalSlots = InvSlotType.Card | InvSlotType.Headset | InvSlotType.InnerClothes | InvSlotType.OuterClothes | InvSlotType.Head; + public const InvSlotType PersonalSlots = InvSlotType.Card | InvSlotType.Bag | InvSlotType.Headset | InvSlotType.InnerClothes | InvSlotType.OuterClothes | InvSlotType.Head; private Point screenResolution; @@ -730,6 +730,7 @@ namespace Barotrauma { for (int i = 0; i < indicators.Length; i++) { + if (indicatorIndexes[i] < 0) { continue; } Item item = Items[indicatorIndexes[i]]; if (item != null) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs index c38856dd6..06d89d21f 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs @@ -1002,6 +1002,7 @@ namespace Barotrauma { if (DraggingItemToWorld && Character.Controlled.FocusedItem?.OwnInventory != null && + (Character.Controlled.FocusedItem.GetComponent()?.HasRequiredItems(Character.Controlled, addMessage: false) ?? false) && Character.Controlled.FocusedItem.OwnInventory.CanBePut(draggingItem) && Character.Controlled.FocusedItem.OwnInventory.TryPutItem(draggingItem, Character.Controlled)) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs index 1ccc9fdcb..dbca8e7a8 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs @@ -62,7 +62,7 @@ namespace Barotrauma } } - public override bool DrawBelowWater => (!(Screen.Selected is SubEditorScreen editor) || !editor.WiringMode || !isWire) && base.DrawBelowWater; + public override bool DrawBelowWater => (!(Screen.Selected is SubEditorScreen editor) || !editor.WiringMode || !isWire) && (base.DrawBelowWater || ParentInventory is CharacterInventory); public override bool DrawOverWater => base.DrawOverWater || (IsSelected || Screen.Selected is SubEditorScreen editor && editor.WiringMode) && isWire; @@ -332,6 +332,7 @@ namespace Barotrauma var holdable = GetComponent(); if (holdable != null && holdable.Picker?.AnimController != null) { + if (!back) { return; } float depthStep = 0.000001f; if (holdable.Picker.SelectedItems[0] == this) { @@ -855,7 +856,7 @@ namespace Barotrauma public void UpdateHUD(Camera cam, Character character, float deltaTime) { bool editingHUDCreated = false; - if ((HasInGameEditableProperties && character.SelectedConstruction == this) || + if ((HasInGameEditableProperties && (character.SelectedConstruction == this || EditableWhenEquipped)) || Screen.Selected == GameMain.SubEditorScreen) { GUIComponent prevEditingHUD = editingHUD; @@ -956,7 +957,7 @@ namespace Barotrauma public void DrawHUD(SpriteBatch spriteBatch, Camera cam, Character character) { - if (HasInGameEditableProperties) + if (HasInGameEditableProperties && (character.SelectedConstruction == this || EditableWhenEquipped)) { DrawEditing(spriteBatch, cam); } @@ -1028,13 +1029,13 @@ namespace Barotrauma } else { - if (HasInGameEditableProperties) + if (HasInGameEditableProperties && Character.Controlled != null && (Character.Controlled.SelectedConstruction == this || EditableWhenEquipped)) { if (editingHUD != null && editingHUD.UserData == this) { editingHUD.AddToGUIUpdateList(); } } } - if (Character.Controlled != null && Character.Controlled?.SelectedConstruction != this) { return; } + if (Character.Controlled != null && Character.Controlled.SelectedConstruction != this) { return; } bool needsLayoutUpdate = false; foreach (ItemComponent ic in activeHUDs) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Hull.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Hull.cs index 24cd6ce0d..7c2bc18f3 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Hull.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Hull.cs @@ -94,20 +94,21 @@ namespace Barotrauma { if (entity == this || !entity.IsHighlighted) { continue; } if (!entity.IsMouseOn(position)) { continue; } - if (entity.linkedTo != null && entity.linkedTo.Contains(this)) + if (entity.linkedTo == null || !entity.Linkable) { continue; } + if (entity.linkedTo.Contains(this) || linkedTo.Contains(entity) || rClick) { - if (entity == this || !entity.IsHighlighted) continue; - if (!entity.IsMouseOn(position)) continue; - if (entity.Linkable && entity.linkedTo != null && !entity.linkedTo.Contains(this)) + if (entity == this || !entity.IsHighlighted) { continue; } + if (!entity.IsMouseOn(position)) { continue; } + if (entity.linkedTo.Contains(this)) { - entity.linkedTo.Add(this); - linkedTo.Add(entity); + entity.linkedTo.Remove(this); + linkedTo.Remove(entity); } } - else if (entity.Linkable && entity.linkedTo != null) + else { - entity.linkedTo.Add(this); - linkedTo.Add(entity); + if (!entity.linkedTo.Contains(this)) { entity.linkedTo.Add(this); } + if (!linkedTo.Contains(this)) { linkedTo.Add(entity); } } } } @@ -605,11 +606,6 @@ namespace Barotrauma { float colorStrength = message.ReadRangedSingle(0.0f, 1.0f, 8); Color color = new Color(message.ReadUInt32()); - float prevColorStrength = BackgroundSections[i].ColorStrength; - BackgroundSections[i].SetColorStrength(colorStrength); - BackgroundSections[i].SetColor(color); - paintAmount = Math.Max(0, paintAmount + (BackgroundSections[i].ColorStrength - prevColorStrength) / BackgroundSections.Count); - var remoteBackgroundSection = remoteBackgroundSections.Find(s => s.Index == i); if (remoteBackgroundSection != null) { @@ -655,8 +651,10 @@ namespace Barotrauma { foreach (BackgroundSection remoteBackgroundSection in remoteBackgroundSections) { + float prevColorStrength = BackgroundSections[remoteBackgroundSection.Index].ColorStrength; BackgroundSections[remoteBackgroundSection.Index].SetColor(remoteBackgroundSection.Color); BackgroundSections[remoteBackgroundSection.Index].SetColorStrength(remoteBackgroundSection.ColorStrength); + paintAmount = Math.Max(0, paintAmount + (BackgroundSections[remoteBackgroundSection.Index].ColorStrength - prevColorStrength) / BackgroundSections.Count); } remoteBackgroundSections.Clear(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightSource.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightSource.cs index 069fb85ef..22f24c0c2 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightSource.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightSource.cs @@ -1,3 +1,4 @@ +using Barotrauma.Extensions; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using System; @@ -6,6 +7,7 @@ using System.Linq; using System.Text; using System.Xml.Linq; + namespace Barotrauma.Lights { class LightSourceParams : ISerializableEntity @@ -331,16 +333,42 @@ namespace Barotrauma.Lights public bool Enabled = true; - public LightSource (XElement element) + private ISerializableEntity conditionalTarget; + private readonly PropertyConditional.Comparison comparison; + private readonly List conditionals = new List(); + + public LightSource (XElement element, ISerializableEntity conditionalTarget = null) : this(Vector2.Zero, 100.0f, Color.White, null) { lightSourceParams = new LightSourceParams(element); CastShadows = element.GetAttributeBool("castshadows", true); + string comparison = element.GetAttributeString("comparison", null); + if (comparison != null) + { + Enum.TryParse(comparison, ignoreCase: true, out this.comparison); + } if (lightSourceParams.DeformableLightSpriteElement != null) { DeformableLightSprite = new DeformableSprite(lightSourceParams.DeformableLightSpriteElement, invert: true); } + + this.conditionalTarget = conditionalTarget; + foreach (XElement subElement in element.Elements()) + { + switch (subElement.Name.ToString().ToLowerInvariant()) + { + case "conditional": + foreach (XAttribute attribute in subElement.Attributes()) + { + if (PropertyConditional.IsValid(attribute)) + { + conditionals.Add(new PropertyConditional(attribute)); + } + } + break; + } + } } public LightSource(LightSourceParams lightSourceParams) @@ -363,7 +391,8 @@ namespace Barotrauma.Lights CastShadows = true; texture = LightTexture; diffToSub = new Dictionary(); - if (addLight) GameMain.LightManager.AddLight(this); + if (addLight) { GameMain.LightManager.AddLight(this); } + } /// @@ -1128,8 +1157,21 @@ namespace Barotrauma.Lights GUI.DrawLine(spriteBatch, drawPos - Vector2.One * Range, drawPos + Vector2.One * Range, Color); GUI.DrawLine(spriteBatch, drawPos - new Vector2(1.0f, -1.0f) * Range, drawPos + new Vector2(1.0f, -1.0f) * Range, Color); } + } + } + + public void CheckConditionals() + { + if (conditionals.None()) { return; } + if (conditionalTarget == null) { return; } + if (comparison == PropertyConditional.Comparison.And) + { + Enabled = conditionals.All(c => c.Matches(conditionalTarget)); + } + else + { + Enabled = conditionals.Any(c => c.Matches(conditionalTarget)); } - } public void DrawLightVolume(SpriteBatch spriteBatch, BasicEffect lightEffect, Matrix transform) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/GameScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/GameScreen.cs index f60568d4c..b1193a13d 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/GameScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/GameScreen.cs @@ -75,6 +75,17 @@ namespace Barotrauma { Character.Controlled.SelectedConstruction.AddToGUIUpdateList(); } + if (Character.Controlled?.Inventory != null) + { + foreach (Item item in Character.Controlled.Inventory.Items) + { + if (item == null) { continue; } + if (Character.Controlled.HasEquippedItem(item)) + { + item.AddToGUIUpdateList(); + } + } + } if (GameMain.GameSession != null) GameMain.GameSession.AddToGUIUpdateList(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs index 4e4549571..f7d61be76 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs @@ -879,7 +879,8 @@ namespace Barotrauma ChildServerRelay.Start(processInfo); Thread.Sleep(1000); //wait until the server is ready before connecting - GameMain.Client = new GameClient(name, System.Net.IPAddress.Loopback.ToString(), Steam.SteamManager.GetSteamID(), name, ownerKey, true); + GameMain.Client = new GameClient(string.IsNullOrEmpty(GameMain.Config.PlayerName) ? name : GameMain.Config.PlayerName, + System.Net.IPAddress.Loopback.ToString(), Steam.SteamManager.GetSteamID(), name, ownerKey, true); } catch (Exception e) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs index f03212400..e8b13c0a7 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs @@ -2409,7 +2409,6 @@ namespace Barotrauma int min = Math.Min(6, AutoSaveInfo.Root.Elements().Count()); var loadAutoSave = new GUIDropDown(new RectTransform(Vector2.One, deleteButtonHolder.RectTransform, Anchor.BottomCenter), TextManager.Get("LoadAutoSave"), elementCount: min) { - Enabled = File.Exists(Path.Combine(SubmarineInfo.SavePath, ".AutoSaves", "AutoSave.sub")), ToolTip = TextManager.Get("LoadAutoSaveTooltip"), UserData = "loadautosave", OnSelected = (button, o) => diff --git a/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundPlayer.cs b/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundPlayer.cs index f0353ef99..ec53aaf92 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundPlayer.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundPlayer.cs @@ -418,7 +418,7 @@ namespace Barotrauma private static void UpdateWaterAmbience(float ambienceVolume, float deltaTime) { - if (GameMain.SoundManager.Disabled) { return; } + if (GameMain.SoundManager.Disabled || GameMain.GameScreen?.Cam == null) { return; } //how fast the sub is moving, scaled to 0.0 -> 1.0 float movementSoundVolume = 0.0f; @@ -426,6 +426,7 @@ namespace Barotrauma float insideSubFactor = 0.0f; foreach (Submarine sub in Submarine.Loaded) { + if (sub == null || sub.Removed) { continue; } float movementFactor = (sub.Velocity == Vector2.Zero) ? 0.0f : sub.Velocity.Length() / 10.0f; movementFactor = MathHelper.Clamp(movementFactor, 0.0f, 1.0f); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Sprite/DecorativeSprite.cs b/Barotrauma/BarotraumaClient/ClientSource/Sprite/DecorativeSprite.cs index c82285907..207c24449 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Sprite/DecorativeSprite.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Sprite/DecorativeSprite.cs @@ -99,6 +99,7 @@ namespace Barotrauma { Sprite = new Sprite(element, path, file, lazyLoad: lazyLoad); SerializableProperties = SerializableProperty.DeserializeProperties(this, element); + // TODO: what's the purpose of this? foreach (XElement subElement in element.Elements()) { List conditionalList = null; diff --git a/Barotrauma/BarotraumaClient/ClientSource/StatusEffects/StatusEffect.cs b/Barotrauma/BarotraumaClient/ClientSource/StatusEffects/StatusEffect.cs index e2a0842e5..98a5c6638 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/StatusEffects/StatusEffect.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/StatusEffects/StatusEffect.cs @@ -105,6 +105,10 @@ namespace Barotrauma if (soundChannel != null) { soundChannel.Looping = loopSound; } } } + else + { + soundChannel.Position = new Vector3(worldPosition, 0.0f); + } if (soundChannel != null && soundChannel.Looping) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Utils/ToolBox.cs b/Barotrauma/BarotraumaClient/ClientSource/Utils/ToolBox.cs index 848747acb..38f1863cc 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Utils/ToolBox.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Utils/ToolBox.cs @@ -138,6 +138,7 @@ namespace Barotrauma public static Color GradientLerp(float t, params Color[] gradient) { + if (!MathUtils.IsValid(t)) { return Color.Purple; } System.Diagnostics.Debug.Assert(gradient.Length > 0, "Empty color array passed to the GradientLerp method"); if (gradient.Length == 0) { diff --git a/Barotrauma/BarotraumaClient/LinuxClient.csproj b/Barotrauma/BarotraumaClient/LinuxClient.csproj index 9d1a4f926..4c0350701 100644 --- a/Barotrauma/BarotraumaClient/LinuxClient.csproj +++ b/Barotrauma/BarotraumaClient/LinuxClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.10.600.0 + 0.10.601.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/MacClient.csproj b/Barotrauma/BarotraumaClient/MacClient.csproj index 26e3ffe0c..8e5630bf8 100644 --- a/Barotrauma/BarotraumaClient/MacClient.csproj +++ b/Barotrauma/BarotraumaClient/MacClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.10.600.0 + 0.10.601.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/WindowsClient.csproj b/Barotrauma/BarotraumaClient/WindowsClient.csproj index bfb762f30..49df8bb39 100644 --- a/Barotrauma/BarotraumaClient/WindowsClient.csproj +++ b/Barotrauma/BarotraumaClient/WindowsClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.10.600.0 + 0.10.601.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaServer/LinuxServer.csproj b/Barotrauma/BarotraumaServer/LinuxServer.csproj index e0371d641..d67dcdb85 100644 --- a/Barotrauma/BarotraumaServer/LinuxServer.csproj +++ b/Barotrauma/BarotraumaServer/LinuxServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.10.600.0 + 0.10.601.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/MacServer.csproj b/Barotrauma/BarotraumaServer/MacServer.csproj index 7ef22f0e5..606e373bf 100644 --- a/Barotrauma/BarotraumaServer/MacServer.csproj +++ b/Barotrauma/BarotraumaServer/MacServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.10.600.0 + 0.10.601.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/MonsterMission.cs b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/MonsterMission.cs index e46a6bf95..35bbdcf39 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/MonsterMission.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/MonsterMission.cs @@ -15,7 +15,7 @@ namespace Barotrauma msg.Write((byte)monsters.Count); foreach (Character monster in monsters) { - monster.WriteSpawnData(msg, monster.ID, restrictMessageSize: false); + monster.WriteSpawnData(msg, monster.OriginalID, restrictMessageSize: false); } } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Items/Item.cs b/Barotrauma/BarotraumaServer/ServerSource/Items/Item.cs index 97cf0b1f9..2649fbbb4 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Items/Item.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Items/Item.cs @@ -131,7 +131,7 @@ namespace Barotrauma case NetEntityEvent.Type.ChangeProperty: try { - WritePropertyChange(msg, extraData, false); + WritePropertyChange(msg, extraData, inGameEditableOnly: !GameMain.NetworkMember.IsServer); } catch (Exception e) { @@ -222,7 +222,7 @@ namespace Barotrauma break; case NetEntityEvent.Type.ChangeProperty: - ReadPropertyChange(msg, true, c); + ReadPropertyChange(msg, inGameEditableOnly: GameMain.NetworkMember.IsServer, sender: c); break; case NetEntityEvent.Type.Combine: UInt16 combineTargetID = msg.ReadUInt16(); @@ -367,7 +367,7 @@ namespace Barotrauma public void CreateServerEvent(T ic) where T : ItemComponent, IServerSerializable { - if (GameMain.Server == null) return; + if (GameMain.Server == null) { return; } if (!ItemList.Contains(this)) { @@ -378,12 +378,31 @@ namespace Barotrauma } int index = components.IndexOf(ic); - if (index == -1) return; + if (index == -1) { return; } object[] extraData = new object[] { NetEntityEvent.Type.ComponentState, index }; ic.ServerAppendExtraData(ref extraData); GameMain.Server.CreateEntityEvent(this, extraData); } + + public void CreateServerEvent(T ic, object[] extraData) where T : ItemComponent, IServerSerializable + { + if (GameMain.Server == null) { return; } + + if (!ItemList.Contains(this)) + { + string errorMsg = "Attempted to create a network event for an item (" + Name + ") that hasn't been fully initialized yet.\n" + Environment.StackTrace.CleanupStackTrace(); + DebugConsole.ThrowError(errorMsg); + GameAnalyticsManager.AddErrorEventOnce("Item.CreateServerEvent:EventForUninitializedItem" + Name + ID, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + return; + } + + int index = components.IndexOf(ic); + if (index == -1) { return; } + + object[] data = new object[] { NetEntityEvent.Type.ComponentState, index }.Concat(extraData).ToArray(); + GameMain.Server.CreateEntityEvent(this, data); + } } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/ChatMessage.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/ChatMessage.cs index 07bf4055a..a8135919f 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/ChatMessage.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/ChatMessage.cs @@ -119,7 +119,11 @@ namespace Barotrauma.Networking if (type == ChatMessageType.Order) { if (c.Character == null || c.Character.SpeechImpediment >= 100.0f || c.Character.IsDead) { return; } - if (!orderMsg.Order.TargetAllCharacters && orderTargetCharacter != null) + if (orderMsg.Order.TargetAllCharacters) + { + HumanAIController.ReportProblem(orderMsg.Sender, orderMsg.Order); + } + else if (orderTargetCharacter != null) { var order = orderTargetPosition == null ? new Order(orderMsg.Order.Prefab, orderTargetEntity, orderMsg.Order.Prefab?.GetTargetItemComponent(orderTargetEntity as Item), orderMsg.Sender) : diff --git a/Barotrauma/BarotraumaServer/WindowsServer.csproj b/Barotrauma/BarotraumaServer/WindowsServer.csproj index 9c79402b4..7fded1041 100644 --- a/Barotrauma/BarotraumaServer/WindowsServer.csproj +++ b/Barotrauma/BarotraumaServer/WindowsServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.10.600.0 + 0.10.601.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.9.xml b/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.9.xml index 0c48790c9..7777353ed 100644 --- a/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.9.xml +++ b/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.9.xml @@ -52,6 +52,8 @@ + + @@ -88,11 +90,14 @@ + + + diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AIController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AIController.cs index 9f6f4eacc..2af7dcc39 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AIController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AIController.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; namespace Barotrauma { - public enum AIState { Idle, Attack, Escape, Eat, Flee, Avoid, Aggressive, PassiveAggressive, Protect, Observe } + public enum AIState { Idle, Attack, Escape, Eat, Flee, Avoid, Aggressive, PassiveAggressive, Protect, Observe, Freeze } abstract partial class AIController : ISteerable { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs index 99bbfb0f6..2c9d2d17b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs @@ -99,6 +99,7 @@ namespace Barotrauma public LatchOntoAI LatchOntoAI { get; private set; } public SwarmBehavior SwarmBehavior { get; private set; } + public PetBehavior PetBehavior { get; private set; } public CharacterParams.TargetParams SelectedTargetingParams { get { return selectedTargetingParams; } } @@ -151,7 +152,10 @@ namespace Barotrauma private set { reverse = value; - FishAnimController.reverse = reverse; + if (FishAnimController != null) + { + FishAnimController.reverse = reverse; + } } } @@ -204,6 +208,9 @@ namespace Barotrauma case "swarmbehavior": SwarmBehavior = new SwarmBehavior(subElement, this); break; + case "petbehavior": + PetBehavior = new PetBehavior(subElement, this); + break; } } @@ -221,7 +228,7 @@ namespace Barotrauma avoidLookAheadDistance = Math.Max(colliderWidth * 3, 1.5f); } - private CharacterParams.AIParams AIParams => Character.Params.AI; + public CharacterParams.AIParams AIParams => Character.Params.AI; private CharacterParams.TargetParams GetTargetParams(string targetTag) => AIParams.GetTarget(targetTag, false); private CharacterParams.TargetParams GetTargetParams(AITarget aiTarget) => GetTargetParams(GetTargetingTag(aiTarget)); private string GetTargetingTag(AITarget aiTarget) @@ -423,6 +430,9 @@ namespace Barotrauma bool run = false; switch (State) { + case AIState.Freeze: + SteeringManager.Reset(); + break; case AIState.Idle: UpdateIdle(deltaTime); break; @@ -582,6 +592,7 @@ namespace Barotrauma if (!Character.AnimController.SimplePhysicsEnabled) { LatchOntoAI?.Update(this, deltaTime); + PetBehavior?.Update(deltaTime); } IsSteeringThroughGap = false; if (SwarmBehavior != null) @@ -1619,7 +1630,7 @@ namespace Barotrauma State = AIState.Idle; return; } - if (SelectedAiTarget.Entity is Character target) + if (SelectedAiTarget.Entity is Character || SelectedAiTarget.Entity is Item) { Limb mouthLimb = Character.AnimController.GetLimb(LimbType.Head); if (mouthLimb == null) @@ -1629,12 +1640,34 @@ namespace Barotrauma return; } Vector2 mouthPos = Character.AnimController.SimplePhysicsEnabled ? SimPosition : Character.AnimController.GetMouthPosition().Value; - Vector2 attackSimPosition = Character.GetRelativeSimPosition(target); + Vector2 attackSimPosition = Character.GetRelativeSimPosition(SelectedAiTarget.Entity); Vector2 limbDiff = attackSimPosition - mouthPos; float extent = Math.Max(mouthLimb.body.GetMaxExtent(), 2); if (limbDiff.LengthSquared() < extent * extent) { - Character.SelectCharacter(target); + if (SelectedAiTarget.Entity is Character targetCharacter) + { + Character.SelectCharacter(targetCharacter); + } + else if (SelectedAiTarget.Entity is Item item) + { + if (!item.Removed && item.body != null) + { + float itemBodyExtent = item.body.GetMaxExtent() * 2; + if (limbDiff.LengthSquared() < itemBodyExtent * itemBodyExtent) + { + item.AddDamage(Character, item.WorldPosition, new Attack(0.0f, 0.0f, 0.0f, 0.0f, 0.1f), deltaTime); + item.body.ApplyForce(-limbDiff * item.body.Mass); + + if (item.Condition <= 0.0f) + { + Entity.Spawner.AddToRemoveQueue(item); + } + + PetBehavior?.OnEat(item.GetTags(), 0.1f / item.MaxCondition); + } + } + } steeringManager.SteeringManual(deltaTime, Vector2.Normalize(limbDiff) * 3); Character.AnimController.Collider.ApplyForce(limbDiff * mouthLimb.Mass * 50.0f, mouthPos); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs index 62872195d..5affd17de 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs @@ -715,6 +715,17 @@ namespace Barotrauma } } + public static void ReportProblem(Character reporter, Order order) + { + if (reporter == null || order == null) { return; } + var visibleHulls = new List(reporter.GetVisibleHulls()); + foreach (var hull in visibleHulls) + { + PropagateHullSafety(reporter, hull); + RefreshTargets(reporter, order, hull); + } + } + private void UpdateSpeaking() { if (Character.Oxygen < 20.0f) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCleanupItems.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCleanupItems.cs index 7d691c4ac..c19f7afa5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCleanupItems.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCleanupItems.cs @@ -66,6 +66,7 @@ namespace Barotrauma //if (rootContainer != null) { return false; } var pickable = item.GetComponent(); if (pickable == null) { return false; } + if (pickable is Holdable h && h.Attachable && h.Attached) { return false; } var wire = item.GetComponent(); if (wire != null) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PetBehavior.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PetBehavior.cs new file mode 100644 index 000000000..f0385f2e2 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PetBehavior.cs @@ -0,0 +1,291 @@ +using Barotrauma.Items.Components; +using Microsoft.Xna.Framework; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma +{ + class PetBehavior + { + public float Hunger { get; set; } = 50.0f; + public float Happiness { get; set; } = 50.0f; + + public float MaxHappiness { get; set; } + public float MaxHunger { get; set; } + + public float HappinessDecreaseRate { get; set; } + public float HungerIncreaseRate { get; set; } + + public float PlayForce { get; set; } + + public float PlayTimer { get; set; } + + public EnemyAIController AiController { get; private set; } = null; + + public Character Owner { get; set; } + + private class ItemProduction + { + public struct Item + { + public ItemPrefab Prefab; + public float Commonness; + } + public List Items; + public Vector2 HungerRange; + public Vector2 HappinessRange; + public float Rate; + public float HungerRate; + public float InvHungerRate; + public float HappinessRate; + public float InvHappinessRate; + + private readonly float totalCommonness; + private float timer; + + public ItemProduction(XElement element) + { + Items = new List(); + + HungerRate = element.GetAttributeFloat("hungerrate", 0.0f); + InvHungerRate = element.GetAttributeFloat("invhungerrate", 0.0f); + HappinessRate = element.GetAttributeFloat("happinessrate", 0.0f); + InvHappinessRate = element.GetAttributeFloat("invhappinessrate", 0.0f); + + string[] requiredHappinessStr = element.GetAttributeString("requiredhappiness", "0-100").Split('-'); + string[] requiredHungerStr = element.GetAttributeString("requiredhunger", "0-100").Split('-'); + HappinessRange = new Vector2(0, 100); + HungerRange = new Vector2(0, 100); + float tempF; + if (requiredHappinessStr.Length >= 2) + { + if (float.TryParse(requiredHappinessStr[0], NumberStyles.Any, CultureInfo.InvariantCulture, out tempF)) { HappinessRange.X = tempF; } + if (float.TryParse(requiredHappinessStr[1], NumberStyles.Any, CultureInfo.InvariantCulture, out tempF)) { HappinessRange.Y = tempF; } + } + if (requiredHungerStr.Length >= 2) + { + if (float.TryParse(requiredHungerStr[0], NumberStyles.Any, CultureInfo.InvariantCulture, out tempF)) { HungerRange.X = tempF; } + if (float.TryParse(requiredHungerStr[1], NumberStyles.Any, CultureInfo.InvariantCulture, out tempF)) { HungerRange.Y = tempF; } + } + Rate = element.GetAttributeFloat("rate", 0.016f); + totalCommonness = 0.0f; + foreach (XElement subElement in element.Elements()) + { + switch (subElement.Name.LocalName.ToLowerInvariant()) + { + case "item": + Item newItemToProduce = new Item + { + Prefab = ItemPrefab.Find("", subElement.GetAttributeString("identifier", "")), + Commonness = subElement.GetAttributeFloat("commonness", 0.0f) + }; + totalCommonness += newItemToProduce.Commonness; + Items.Add(newItemToProduce); + break; + } + } + + timer = 1.0f; + } + + public void Update(PetBehavior pet, float deltaTime) + { + if (pet.Happiness < HappinessRange.X || pet.Happiness > HappinessRange.Y) { return; } + if (pet.Hunger < HungerRange.X || pet.Hunger > HungerRange.Y) { return; } + + float currentRate = Rate; + currentRate += HappinessRate * (pet.Happiness - HappinessRange.X) / (HappinessRange.Y - HappinessRange.X); + currentRate += InvHappinessRate * (1.0f - ((pet.Happiness - HappinessRange.X) / (HappinessRange.Y - HappinessRange.X))); + currentRate += HungerRate * (pet.Hunger - HungerRange.X) / (HungerRange.Y - HungerRange.X); + currentRate += InvHungerRate * (1.0f - ((pet.Hunger - HungerRange.X) / (HungerRange.Y - HungerRange.X))); + timer -= currentRate * deltaTime; + if (timer <= 0.0f) + { + timer = 1.0f; + float r = Rand.Range(0.0f, totalCommonness); + float aggregate = 0.0f; + for (int i = 0; i < Items.Count; i++) + { + aggregate += Items[i].Commonness; + if (aggregate >= r) + { + Entity.Spawner.AddToSpawnQueue(Items[i].Prefab, pet.AiController.Character.WorldPosition); + break; + } + } + } + } + } + + private class Food + { + public string Tag; + public Vector2 HungerRange; + public float Hunger; + public float Happiness; + public float Priority; + + public CharacterParams.TargetParams TargetParams = null; + } + + private readonly List itemsToProduce = new List(); + private readonly List foods = new List(); + + public PetBehavior(XElement element, EnemyAIController aiController) + { + AiController = aiController; + AiController.Character.CanBeDragged = true; + + MaxHappiness = element.GetAttributeFloat("maxhappiness", 100.0f); + MaxHunger = element.GetAttributeFloat("maxhunger", 100.0f); + + Happiness = MaxHappiness * 0.5f; + Hunger = MaxHunger * 0.5f; + + HappinessDecreaseRate = element.GetAttributeFloat("happinessdecreaserate", 0.1f); + HungerIncreaseRate = element.GetAttributeFloat("hungerincreaserate", 0.25f); + + PlayForce = element.GetAttributeFloat("playforce", 15.0f); + + foreach (XElement subElement in element.Elements()) + { + switch (subElement.Name.LocalName.ToLowerInvariant()) + { + case "itemproduction": + itemsToProduce.Add(new ItemProduction(subElement)); + break; + case "eat": + Food food = new Food + { + Tag = subElement.GetAttributeString("tag", "") + }; + string[] requiredHungerStr = subElement.GetAttributeString("requiredhunger", "0-100").Split('-'); + food.HungerRange = new Vector2(0, 100); + if (requiredHungerStr.Length >= 2) + { + if (float.TryParse(requiredHungerStr[0], NumberStyles.Any, CultureInfo.InvariantCulture, out float tempF)) { food.HungerRange.X = tempF; } + if (float.TryParse(requiredHungerStr[1], NumberStyles.Any, CultureInfo.InvariantCulture, out tempF)) { food.HungerRange.Y = tempF; } + } + food.Hunger = subElement.GetAttributeFloat("hunger", -1); + food.Happiness = subElement.GetAttributeFloat("happiness", 1); + food.Priority = subElement.GetAttributeFloat("priority", 100); + foods.Add(food); + break; + } + } + } + + public void OnEat(IEnumerable tags, float amount) + { + for (int i = 0; i < foods.Count; i++) + { + if (tags.Any(t => t.Equals(foods[i].Tag, System.StringComparison.OrdinalIgnoreCase))) + { + Hunger += foods[i].Hunger * amount; + Happiness += foods[i].Happiness * amount; + break; + } + } + } + + public void OnEat(string tag, float amount) + { + for (int i = 0; i < foods.Count; i++) + { + if (tag.Equals(foods[i].Tag, System.StringComparison.OrdinalIgnoreCase)) + { + Hunger += foods[i].Hunger * amount; + Happiness += foods[i].Happiness * amount; + break; + } + } + } + + public void Play() + { + if (PlayTimer > 0.0f) { return; } + PlayTimer = 5.0f; + AiController.Character.Stun = 1.0f; + Happiness += 10.0f; + if (Happiness > MaxHappiness) { Happiness = MaxHappiness; } + AiController.Character.AnimController.MainLimb.body.LinearVelocity += new Vector2(0, PlayForce); + } + + public string GetName() + { + if (AiController.Character.Inventory != null) + { + var items = AiController.Character.Inventory.Items; + for (int i = 0; i < items.Length; i++) + { + var item = items[i]; + if (item == null) { continue; } + var tag = item.GetComponent(); + if (tag != null && !string.IsNullOrWhiteSpace(tag.WrittenName)) + { + return tag.WrittenName; + } + } + } + + return AiController.Character.Name; + } + + public void Update(float deltaTime) + { + var character = AiController.Character; + if (character?.Removed ?? true || character.IsDead) { return; } + if (GameMain.NetworkMember?.IsClient ?? false) { return; } //TODO: syncing + + Hunger += HungerIncreaseRate * deltaTime; + + Happiness -= HappinessDecreaseRate * deltaTime; + + PlayTimer -= deltaTime; + + for (int i = 0; i < foods.Count; i++) + { + if (Hunger >= foods[i].HungerRange.X && Hunger <= foods[i].HungerRange.Y) + { + if (foods[i].TargetParams == null && + AiController.AIParams.TryAddNewTarget(foods[i].Tag, AIState.Eat, foods[i].Priority, out CharacterParams.TargetParams targetParams)) + { + foods[i].TargetParams = targetParams; + } + } + else if (foods[i].TargetParams != null) + { + AiController.AIParams.RemoveTarget(foods[i].TargetParams); + foods[i].TargetParams = null; + } + } + + if (Hunger < 0.0f) { Hunger = 0.0f; } + if (Hunger > MaxHunger) { Hunger = MaxHunger; } + if (Happiness < 0.0f) { Happiness = 0.0f; } + if (Happiness > MaxHappiness) { Happiness = MaxHappiness; } + if (PlayTimer < 0.0f) { PlayTimer = 0.0f; } + + if (Hunger >= MaxHunger * 0.99f) + { + character.CharacterHealth.ApplyAffliction(character.AnimController.MainLimb, new Affliction(AfflictionPrefab.InternalDamage, 8.0f * deltaTime)); + } + else if (Hunger < MaxHunger * 0.1f) + { + character.CharacterHealth.ReduceAffliction(null, null, 8.0f * deltaTime); + } + + if (character.SelectedBy != null) + { + character.Stun = 1.0f; + } + + for (int i = 0; i < itemsToProduce.Count; i++) + { + itemsToProduce[i].Update(this, deltaTime); + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/FishAnimController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/FishAnimController.cs index 980aa32e0..ad5759762 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/FishAnimController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/FishAnimController.cs @@ -381,6 +381,12 @@ namespace Barotrauma { //only one limb left, the character is now full eaten Entity.Spawner?.AddToRemoveQueue(target); + + if (Character.AIController is EnemyAIController enemyAi) + { + enemyAi.PetBehavior?.OnEat("dead", 1.0f); + } + character.SelectedCharacter = null; } else //sever a random joint diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs index 6de1242cd..d0db174f1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs @@ -753,6 +753,7 @@ namespace Barotrauma foreach (Limb limb in Limbs) { if (connectedLimbs.Contains(limb)) { continue; } + if (!character.IsDead && !limb.CanBeSeveredAlive) { continue; } limb.IsSevered = true; if (limb.type == LimbType.RightHand) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs index 632c1c3c9..15d0909d4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs @@ -215,9 +215,6 @@ namespace Barotrauma public bool IsDismissed => Info != null && Info.IsDismissed; private readonly List statusEffects = new List(); - private readonly List speedMultipliers = new List(); - private float greatestNegativeSpeedMultiplier = 1f; - private float greatestPositiveSpeedMultiplier = 1f; public Entity ViewTarget { @@ -274,6 +271,11 @@ namespace Barotrauma { get { + if (IsPet) + { + return (AIController as EnemyAIController).PetBehavior.GetName(); + } + if (info != null && !string.IsNullOrWhiteSpace(info.Name)) { return info.Name; } var displayName = Params.DisplayName; if (string.IsNullOrWhiteSpace(displayName)) @@ -473,6 +475,11 @@ namespace Barotrauma get { return CharacterHealth.IsUnconscious; } } + public bool IsPet + { + get { return AIController is EnemyAIController enemyController && enemyController.PetBehavior != null; } + } + public float Oxygen { get { return CharacterHealth.OxygenAmount; } @@ -488,7 +495,7 @@ namespace Barotrauma get { return oxygenAvailable; } set { oxygenAvailable = MathHelper.Clamp(value, 0.0f, 100.0f); } } - + public float Stun { get { return IsRagdolled ? 1.0f : CharacterHealth.StunTimer; } @@ -638,7 +645,7 @@ namespace Barotrauma { if (!canBeDragged) { return false; } if (Removed || !AnimController.Draggable) { return false; } - return IsDead || Stun > 0.0f || LockHands || IsIncapacitated; + return IsDead || Stun > 0.0f || LockHands || IsIncapacitated || IsPet; } set { canBeDragged = value; } } @@ -1218,10 +1225,13 @@ namespace Barotrauma return targetMovement; } + private float greatestNegativeSpeedMultiplier = 1f; + private float greatestPositiveSpeedMultiplier = 1f; + /// /// Can be used to modify the character's speed via StatusEffects /// - public float SpeedMultiplier { get; private set; } + public float SpeedMultiplier { get; private set; } = 1; public void StackSpeedMultiplier(float val) { @@ -1247,6 +1257,40 @@ namespace Barotrauma greatestNegativeSpeedMultiplier = 1f; } + private float greatestNegativeHealthMultiplier = 1f; + private float greatestPositiveHealthMultiplier = 1f; + + /// + /// Can be used to modify the character's health via StatusEffects + /// + public float HealthMultiplier { get; private set; } = 1; + + public void StackHealthMultiplier(float val) + { + if (val < 1f) + { + if (val < greatestNegativeHealthMultiplier) + { + greatestNegativeHealthMultiplier = val; + } + } + else + { + if (val > greatestPositiveHealthMultiplier) + { + greatestPositiveHealthMultiplier = val; + } + } + } + + private void CalculateHealthMultiplier() + { + HealthMultiplier = greatestPositiveHealthMultiplier - (1f - greatestNegativeHealthMultiplier); + // Reset, status effects should set the values again, if the conditions match + greatestPositiveHealthMultiplier = 1f; + greatestNegativeHealthMultiplier = 1f; + } + /// /// Speed reduction from the current limb specific damage. Min 0, max 1. /// @@ -2132,6 +2176,10 @@ namespace Barotrauma { SelectCharacter(FocusedCharacter); } + else if (FocusedCharacter != null && !FocusedCharacter.IsIncapacitated && IsKeyHit(InputType.Use) && FocusedCharacter.IsPet && CanInteract) + { + (FocusedCharacter.AIController as EnemyAIController).PetBehavior.Play(); + } else if (FocusedCharacter != null && IsKeyHit(InputType.Health) && FocusedCharacter.CharacterHealth.UseHealthWindow && CanInteract && CanInteractWith(FocusedCharacter, 160f, false)) { if (FocusedCharacter == SelectedCharacter) @@ -2369,6 +2417,7 @@ namespace Barotrauma } ApplyStatusEffects(AnimController.InWater ? ActionType.InWater : ActionType.NotInWater, deltaTime); + ApplyStatusEffects(ActionType.OnActive, deltaTime); UpdateControlled(deltaTime, cam); @@ -2377,6 +2426,8 @@ namespace Barotrauma { UpdateOxygen(deltaTime); } + + CalculateHealthMultiplier(); CharacterHealth.Update(deltaTime); if (IsIncapacitated) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs index 91f923123..9512f59a9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs @@ -141,17 +141,17 @@ namespace Barotrauma { get { + float max = maxVitality; if (Character?.Info?.Job?.Prefab != null) { - return maxVitality + Character.Info.Job.Prefab.VitalityModifier; + max += Character.Info.Job.Prefab.VitalityModifier; } - return maxVitality; + return max * Character.HealthMultiplier; } set { maxVitality = Math.Max(0, value); } - } public float MinVitality @@ -450,9 +450,13 @@ namespace Barotrauma matchingAfflictions.AddRange(limbHealth.Afflictions); } } - matchingAfflictions.RemoveAll(a => - !a.Prefab.Identifier.Equals(affliction, StringComparison.OrdinalIgnoreCase) && - !a.Prefab.AfflictionType.Equals(affliction, StringComparison.OrdinalIgnoreCase)); + + if (!string.IsNullOrEmpty(affliction)) + { + matchingAfflictions.RemoveAll(a => + !a.Prefab.Identifier.Equals(affliction, StringComparison.OrdinalIgnoreCase) && + !a.Prefab.AfflictionType.Equals(affliction, StringComparison.OrdinalIgnoreCase)); + } if (matchingAfflictions.Count == 0) return; @@ -617,8 +621,8 @@ namespace Barotrauma private void AddAffliction(Affliction newAffliction) { - if (!DoesBleed && newAffliction is AfflictionBleeding) return; - if (!Character.NeedsOxygen && newAffliction.Prefab == AfflictionPrefab.OxygenLow) return; + if (!DoesBleed && newAffliction is AfflictionBleeding) { return; } + if (!Character.NeedsOxygen && newAffliction.Prefab == AfflictionPrefab.OxygenLow) { return; } if (newAffliction.Prefab.AfflictionType == "huskinfection") { var huskPrefab = newAffliction.Prefab as AfflictionPrefabHusk; @@ -636,7 +640,10 @@ namespace Barotrauma affliction.Strength = newStrength; affliction.Source = newAffliction.Source; CalculateVitality(); - if (Vitality <= MinVitality) Kill(); + if (Vitality <= MinVitality) + { + Kill(); + } return; } } @@ -650,7 +657,10 @@ namespace Barotrauma Character.HealthUpdateInterval = 0.0f; CalculateVitality(); - if (Vitality <= MinVitality) Kill(); + if (Vitality <= MinVitality) + { + Kill(); + } } @@ -707,7 +717,11 @@ namespace Barotrauma UpdateLimbAfflictionOverlays(); CalculateVitality(); - if (Vitality <= MinVitality) Kill(); + + if (Vitality <= MinVitality) + { + Kill(); + } } private void UpdateOxygen(float deltaTime) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/HumanPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/HumanPrefab.cs index 5d23aa8ca..00d102c41 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/HumanPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/HumanPrefab.cs @@ -163,7 +163,13 @@ namespace Barotrauma { item.AddTag("job:" + job.Name); } + var idCardTags = itemElement.GetAttributeStringArray("tags", new string[0]); + foreach (string tag in idCardTags) + { + item.AddTag(tag); + } } + foreach (WifiComponent wifiComponent in item.GetComponents()) { wifiComponent.TeamID = character.TeamID; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Decals/DecalPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Decals/DecalPrefab.cs index fe55e454d..3f80bbecd 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Decals/DecalPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Decals/DecalPrefab.cs @@ -62,9 +62,9 @@ namespace Barotrauma { Sprites.Add(new Sprite(subElement)); } - } - - Color = new Color(element.GetAttributeVector4("color", Vector4.One)); + } + + Color = element.GetAttributeColor("color", Color.White); LifeTime = element.GetAttributeFloat("lifetime", 10.0f); FadeOutTime = Math.Min(LifeTime, element.GetAttributeFloat("fadeouttime", 1.0f)); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs index 796e995d2..76a4cc6be 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs @@ -120,6 +120,7 @@ namespace Barotrauma AddChildEvents(initialEventSet); void AddChildEvents(EventSet eventSet) { + if (eventSet == null) { return; } foreach (EventPrefab ep in eventSet.EventPrefabs.Select(e => e.First)) { if (!level.LevelData.NonRepeatableEvents.Contains(ep)) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RepairTool.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RepairTool.cs index 9594d47bd..f218cfcee 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RepairTool.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RepairTool.cs @@ -717,11 +717,10 @@ namespace Barotrauma.Items.Components bool leakFixed = (leak.Open <= 0.0f || leak.Removed) && (leak.ConnectedWall == null || leak.ConnectedWall.Sections.Average(s => s.damage) < 1); - if (leakFixed && leak.FlowTargetHull != null) + if (leakFixed && leak.FlowTargetHull?.DisplayName != null) { if (!leak.FlowTargetHull.ConnectedGaps.Any(g => !g.IsRoomToRoom && g.Open > 0.0f)) - { - + { character.Speak(TextManager.GetWithVariable("DialogLeaksFixed", "[roomname]", leak.FlowTargetHull.DisplayName, true), null, 0.0f, "leaksfixed", 10.0f); } else diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemComponent.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemComponent.cs index f191617c5..cddb7d1f2 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemComponent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemComponent.cs @@ -146,7 +146,7 @@ namespace Barotrauma.Items.Components public bool DrawHudWhenEquipped { get; - private set; + protected set; } [Serialize(false, false, description: "Can the item be selected by interacting with it.")] diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/NameTag.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/NameTag.cs new file mode 100644 index 000000000..6cd9e1d39 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/NameTag.cs @@ -0,0 +1,21 @@ +using Barotrauma.Networking; +using System.Xml.Linq; +#if CLIENT +using Microsoft.Xna.Framework.Graphics; +#endif + +namespace Barotrauma.Items.Components +{ + class NameTag : ItemComponent + { + [InGameEditable, Serialize("", false, description: "Name written on the tag.", alwaysUseInstanceValues: true)] + public string WrittenName { get; set; } + + public NameTag(Item item, XElement element) : base(item, element) + { + AllowInGameEditing = true; + DrawHudWhenEquipped = true; + item.EditableWhenEquipped = true; + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Terminal.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Terminal.cs index ac22fe394..19599da8e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Terminal.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Terminal.cs @@ -1,10 +1,11 @@ -using System.Xml.Linq; +using Barotrauma.Networking; +using System.Xml.Linq; namespace Barotrauma.Items.Components { partial class Terminal : ItemComponent { - private const int MaxMessageLength = 150; + private const int MaxMessageLength = ChatMessage.MaxLength; public string DisplayedWelcomeMessage { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs index 15dc283dd..6963fd045 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs @@ -304,13 +304,16 @@ namespace Barotrauma for (int j = 0; j < otherInventory.capacity; j++) { - if (otherInventory.Items[j] == item) otherInventory.Items[j] = null; + if (otherInventory.Items[j] == item) { otherInventory.Items[j] = null; } } for (int j = 0; j < capacity; j++) { - if (Items[j] == existingItem) Items[j] = null; + if (Items[j] == existingItem) { Items[j] = null; } } + (otherInventory.Owner as Character)?.DeselectItem(item); + (otherInventory.Owner as Character)?.DeselectItem(existingItem); + bool swapSuccessful = false; if (otherIsEquipped) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs index 3aeaa04cb..d2cfc8dce 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs @@ -119,6 +119,8 @@ namespace Barotrauma } } + public bool EditableWhenEquipped { get; set; } = false; + //the inventory in which the item is contained in public Inventory ParentInventory { @@ -2257,7 +2259,7 @@ namespace Barotrauma var propertyOwner = allProperties.Find(p => p.Second == property); if (allProperties.Count > 1) { - msg.WriteRangedInteger(allProperties.FindIndex(p => p.Second == property), 0, allProperties.Count - 1); + msg.Write((byte)allProperties.FindIndex(p => p.Second == property)); } object value = property.GetValue(propertyOwner.First); @@ -2339,7 +2341,7 @@ namespace Barotrauma int propertyIndex = 0; if (allProperties.Count > 1) { - propertyIndex = msg.ReadRangedInteger(0, allProperties.Count - 1); + propertyIndex = msg.ReadByte(); } bool allowEditing = true; @@ -2360,6 +2362,7 @@ namespace Barotrauma if (type == typeof(string)) { string val = msg.ReadString(); + logValue = val; if (allowEditing) { property.TrySetValue(parentObject, val); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/FireSource.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/FireSource.cs index 0d9c42405..7da6b79b7 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/FireSource.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/FireSource.cs @@ -73,13 +73,13 @@ namespace Barotrauma { get; set; - } + } = true; public bool DamagesCharacters { get; set; - } + } = true; public bool Removed { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs index 35f1aa997..bc3bbf8d1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs @@ -65,8 +65,8 @@ namespace Barotrauma Noise = new Vector2( PerlinNoise.GetPerlin(Rect.X / 1000.0f, Rect.Y / 1000.0f), PerlinNoise.GetPerlin(Rect.Y / 1000.0f + 0.5f, Rect.X / 1000.0f + 0.5f)); - - Color = DirtColor = Color.Lerp(new Color(10, 10, 10, 100), new Color(54, 57, 28, 200), Noise.X); + + DirtColor = Color.Lerp(new Color(10, 10, 10, 100), new Color(54, 57, 28, 200), Noise.X); } public bool SetColor(Color color) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Serialization/SerializableProperty.cs b/Barotrauma/BarotraumaShared/SharedSource/Serialization/SerializableProperty.cs index c8cb130b4..ec442e36d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Serialization/SerializableProperty.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Serialization/SerializableProperty.cs @@ -480,6 +480,9 @@ namespace Barotrauma case "IsDead": { if (parentObject is Character character) { return character.IsDead; } } break; + case "IsHuman": + { if (parentObject is Character character) { return character.IsHuman; } } + break; case "IsOn": { if (parentObject is LightComponent lightComponent) { return lightComponent.IsOn; } } break; @@ -543,6 +546,9 @@ namespace Barotrauma case "SpeedMultiplier": { if (parentObject is Character character && value is float) { character.StackSpeedMultiplier((float)value); return true; } } break; + case "HealthMultiplier": + { if (parentObject is Character character && value is float) { character.StackHealthMultiplier((float)value); return true; } } + break; case "IsOn": { if (parentObject is LightComponent lightComponent && value is bool) { lightComponent.IsOn = (bool)value; return true; } } break; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Sprite/ConditionalSprite.cs b/Barotrauma/BarotraumaShared/SharedSource/Sprite/ConditionalSprite.cs index 4b93e0dc1..b1f25b2c0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Sprite/ConditionalSprite.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Sprite/ConditionalSprite.cs @@ -8,14 +8,7 @@ namespace Barotrauma partial class ConditionalSprite { public readonly List conditionals = new List(); - public bool IsActive - { - get - { - if (Target == null) { return false; } - return Comparison == PropertyConditional.Comparison.And ? conditionals.All(c => c.Matches(Target)) : conditionals.Any(c => c.Matches(Target)); - } - } + public bool IsActive { get; private set; } = true; public readonly PropertyConditional.Comparison Comparison; public readonly bool Exclusive; @@ -55,5 +48,17 @@ namespace Barotrauma } } } + + public void CheckConditionals() + { + if (Target == null) + { + IsActive = false; + } + else + { + IsActive = Comparison == PropertyConditional.Comparison.And ? conditionals.All(c => c.Matches(Target)) : conditionals.Any(c => c.Matches(Target)); + } + } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs index e0eff9c7c..ca7308180 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs @@ -687,16 +687,8 @@ namespace Barotrauma } if (targetIdentifiers != null && currentTargets.Count == 0) { return; } - - if (!HasRequiredItems(entity)) { return; } - // If "This" is defined as the target, let's target the conditions only to this entity, because when you use "NearbyTargets", this entity is also taken into account. - // If we want to target both, leaving "This" out and using only "NearbyTargets" should work. - // Currently we don't have means for targeting only the nearby characters, though. - if (HasTargetType(TargetType.This)) - { - if (!HasRequiredConditions(((ISerializableEntity)entity).ToEnumerable())) { return; } - } - else if (!HasRequiredConditions(currentTargets)) { return; } + + if (!HasRequiredItems(entity) || !HasRequiredConditions(currentTargets)) { return; } if (duration > 0.0f && !Stackable) { @@ -919,6 +911,13 @@ namespace Barotrauma Entity.Spawner.AddToSpawnQueue(characterSpawnInfo.SpeciesName, position + Rand.Vector(characterSpawnInfo.Spread, Rand.RandSync.Server) + characterSpawnInfo.Offset, onSpawn: newCharacter => { + if (newCharacter.AIController is EnemyAIController enemyAi && + enemyAi.PetBehavior != null && + entity is Item item && + item.ParentInventory is CharacterInventory inv) + { + enemyAi.PetBehavior.Owner = inv.Owner as Character; + } characters.Add(newCharacter); if (characters.Count == characterSpawnInfo.Count) { diff --git a/Barotrauma/BarotraumaShared/Submarines/Azimuth.sub b/Barotrauma/BarotraumaShared/Submarines/Azimuth.sub index 685a7f69b..3d3b56903 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Azimuth.sub and b/Barotrauma/BarotraumaShared/Submarines/Azimuth.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Berilia.sub b/Barotrauma/BarotraumaShared/Submarines/Berilia.sub index 35bd2c966..91504bf7f 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Berilia.sub and b/Barotrauma/BarotraumaShared/Submarines/Berilia.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Dugong.sub b/Barotrauma/BarotraumaShared/Submarines/Dugong.sub index b93388369..899b0e13f 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Dugong.sub and b/Barotrauma/BarotraumaShared/Submarines/Dugong.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Humpback.sub b/Barotrauma/BarotraumaShared/Submarines/Humpback.sub index c0cfb9246..30744ae23 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Humpback.sub and b/Barotrauma/BarotraumaShared/Submarines/Humpback.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Kastrull.sub b/Barotrauma/BarotraumaShared/Submarines/Kastrull.sub index 0d16a5b95..ce8ce2c69 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Kastrull.sub and b/Barotrauma/BarotraumaShared/Submarines/Kastrull.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Orca.sub b/Barotrauma/BarotraumaShared/Submarines/Orca.sub index 9fce2b208..3d50aca0a 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Orca.sub and b/Barotrauma/BarotraumaShared/Submarines/Orca.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/R-29.sub b/Barotrauma/BarotraumaShared/Submarines/R-29.sub index 4da4355c9..554cac19c 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/R-29.sub and b/Barotrauma/BarotraumaShared/Submarines/R-29.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Remora.sub b/Barotrauma/BarotraumaShared/Submarines/Remora.sub index 6d94c2860..56a0ffaad 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Remora.sub and b/Barotrauma/BarotraumaShared/Submarines/Remora.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Typhon.sub b/Barotrauma/BarotraumaShared/Submarines/Typhon.sub index 9d9a1af32..24115cf94 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Typhon.sub and b/Barotrauma/BarotraumaShared/Submarines/Typhon.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Typhon2.sub b/Barotrauma/BarotraumaShared/Submarines/Typhon2.sub index bb99d09eb..c0e2cfd04 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Typhon2.sub and b/Barotrauma/BarotraumaShared/Submarines/Typhon2.sub differ diff --git a/Barotrauma/BarotraumaShared/changelog.txt b/Barotrauma/BarotraumaShared/changelog.txt index 0f12f37c9..53eceeba8 100644 --- a/Barotrauma/BarotraumaShared/changelog.txt +++ b/Barotrauma/BarotraumaShared/changelog.txt @@ -1,3 +1,34 @@ +--------------------------------------------------------------------------------------------------------- +v0.10.601.0 (Unstable) +--------------------------------------------------------------------------------------------------------- + +- Added 2 pets: Peanut and Psilotoad. Currently only obtainable via console commands ("spawnitem peanutegg", "spawnitem psilotoadegg", "spawn peanut" or "spawn psilotoad"). +- Improvements to the Watcher. +- Fixed 2 equipped storage containers from one hand to another causing one of them to get stuck mid-air. +- Fixed a crash caused by humanoid enemies (e.g. husks). +- Moved toolbelt slot to the right side of the generic slots, added inventory icon for the slot. +- Fixed EventManager crashing if there are no event sets configured for the current location type (only affected mods that add new location types without adding any events for them). +- Use player name instead of server name for the server owner when hosting a server. +- Fixed diving suit's low oxygen warning beep not following the player wearing the diving suit. +- Made large monsters immune to paralyzant (mudraptor is the largest affected monster). +- Fixed bots not reacting to player reports in multiplayer. +- Added more copper to chalcopyrite and bornite deconstruct recipe. +- More calcium for aragonite, adjusted prices. +- Fixed fires not damaging characters. +- Set terminal's maximum message length to match maximum chat message length (otherwise chat-linked terminals work differently in multiplayer). +- Fixed inability to unlink hulls in the sub editor. +- Fixed bots "cleaning up" components attached to walls. +- Fixed yet another cause for "missing entity" errors. Occasionally happened in monster missions when a monster happened to get assigned the same ID as an item in a player's inventory. +- Fixed items held in the left hand being drawn in front of the characters. +- Allow closing the splash screens with esc. +- Fixed "inventory sizes don't match" error when a human becomes a husk. +- Fixed ability to drag and drop items into outpost reactors. +- Fixed torso getting hidden when wearing a toolbelt. +- Fixed coilgun ammo only using 90% of the fabrication materials. +- Fixed paints reverting to the dirt color client-side after spraying. +- Added missing dialog for the new "Cleanup Items" order. +- Rebalanced upgrade parameters, allowing for more noticable benefits. + --------------------------------------------------------------------------------------------------------- v0.10.600.0 (Unstable) ---------------------------------------------------------------------------------------------------------