diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/AI/EnemyAIController.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/AI/EnemyAIController.cs index 74548b53d..eacea79b3 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/AI/EnemyAIController.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/AI/EnemyAIController.cs @@ -11,7 +11,7 @@ namespace Barotrauma { if (Character.IsUnconscious || !Character.Enabled || !Enabled) { return; } - Vector2 pos = Character.WorldPosition; + Vector2 pos = Character.DrawPosition; pos.Y = -pos.Y; if (State == AIState.Idle && PreviousState == AIState.Attack) @@ -31,7 +31,7 @@ namespace Barotrauma } else if (SelectedAiTarget?.Entity != null) { - Vector2 targetPos = SelectedAiTarget.WorldPosition; + Vector2 targetPos = SelectedAiTarget.Entity.DrawPosition; if (State == AIState.Attack) { targetPos = attackWorldPos; @@ -72,7 +72,7 @@ namespace Barotrauma } GUI.DrawString(spriteBatch, pos - Vector2.UnitY * 80.0f, State.ToString(), stateColor, Color.Black); - if (LatchOntoAI != null) + if (LatchOntoAI != null && (State == AIState.Idle || LatchOntoAI.IsAttachedToSub)) { foreach (Joint attachJoint in LatchOntoAI.AttachJoints) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs index f129f04ea..89ca9a2f7 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs @@ -312,11 +312,7 @@ namespace Barotrauma } } - cursorPosition = cam.ScreenToWorld(PlayerInput.MousePosition); - if (AnimController.CurrentHull?.Submarine != null) - { - cursorPosition -= AnimController.CurrentHull.Submarine.Position; - } + UpdateLocalCursor(cam); Vector2 mouseSimPos = ConvertUnits.ToSimUnits(cursorPosition); if (GUI.PauseMenuOpen) @@ -393,6 +389,15 @@ namespace Barotrauma DisableControls = false; } + public void UpdateLocalCursor(Camera cam) + { + cursorPosition = cam.ScreenToWorld(PlayerInput.MousePosition); + if (AnimController.CurrentHull?.Submarine != null) + { + cursorPosition -= AnimController.CurrentHull.Submarine.DrawPosition; + } + } + partial void UpdateControlled(float deltaTime, Camera cam) { if (controlled != this) return; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterHUD.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterHUD.cs index 0446de782..edc745dfc 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterHUD.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterHUD.cs @@ -431,7 +431,7 @@ namespace Barotrauma foreach (Item item in Item.ItemList) { if (item.IconStyle is null || item.Submarine != character.Submarine) { continue; } - if (Vector2.DistanceSquared(character.Position, item.Position) > 500f*500f) { continue; } + if (Vector2.DistanceSquared(character.Position, item.Position) > 500f * 500f) { continue; } var body = Submarine.CheckVisibility(character.SimPosition, item.SimPosition, ignoreLevel: true); if (body != null && body.UserData as Item != item) { continue; } GUI.DrawIndicator(spriteBatch, item.WorldPosition + new Vector2(0f, item.RectHeight * 0.65f), cam, new Range(-100f, 500.0f), item.IconStyle.GetDefaultSprite(), item.IconStyle.Color, createOffset: false); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/AfflictionHusk.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/AfflictionHusk.cs index 73c444116..e5bd89ebb 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/AfflictionHusk.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/AfflictionHusk.cs @@ -5,6 +5,7 @@ private InfectionState? prevDisplayedMessage; partial void UpdateMessages() { + if (character != Character.Controlled) { return; } if (Prefab is AfflictionPrefabHusk { SendMessages: false }) { return; } if (prevDisplayedMessage.HasValue && prevDisplayedMessage.Value == State) { return; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Limb.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Limb.cs index 2057d9581..15bd29bf4 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Limb.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Limb.cs @@ -896,13 +896,20 @@ namespace Barotrauma { if (wearable.Type == WearableType.Husk) { continue; } if (wearableTypesToHide.Contains(wearable.Type)) - { - if (wearable.Type == WearableType.Hair && HairWithHatSprite != null) + { + if (wearable.Type == WearableType.Hair) { - DrawWearable(HairWithHatSprite, depthStep, spriteBatch, blankColor, alpha: color.A / 255f, spriteEffect); - depthStep += step; + if (HairWithHatSprite != null) + { + DrawWearable(HairWithHatSprite, depthStep, spriteBatch, blankColor, alpha: color.A / 255f, spriteEffect); + depthStep += step; + continue; + } + } + else + { + continue; } - continue; } DrawWearable(wearable, depthStep, spriteBatch, blankColor, alpha: color.A / 255f, spriteEffect); //if there are multiple sprites on this limb, make the successive ones be drawn in front diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/AbandonedOutpostMission.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/AbandonedOutpostMission.cs index d5161fc4d..879d68743 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/AbandonedOutpostMission.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/AbandonedOutpostMission.cs @@ -1,3 +1,4 @@ +using Barotrauma.Extensions; using Barotrauma.Networking; namespace Barotrauma @@ -20,6 +21,9 @@ namespace Barotrauma } } + public override bool IsAtCompletionState => State > 0 && requireRescue.None(); + public override bool IsAtFailureState => State == HostagesKilledState; + public override void ClientReadInitial(IReadMessage msg) { base.ClientReadInitial(msg); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/AlienRuinMission.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/AlienRuinMission.cs index b943081b2..16b042db2 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/AlienRuinMission.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/AlienRuinMission.cs @@ -4,6 +4,9 @@ namespace Barotrauma { partial class AlienRuinMission : Mission { + public override bool IsAtCompletionState => State > 0; + public override bool IsAtFailureState => false; + public override void ClientReadInitial(IReadMessage msg) { base.ClientReadInitial(msg); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/BeaconMission.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/BeaconMission.cs new file mode 100644 index 000000000..7ad088f6c --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/BeaconMission.cs @@ -0,0 +1,8 @@ +namespace Barotrauma +{ + partial class BeaconMission : Mission + { + public override bool IsAtCompletionState => false; + public override bool IsAtFailureState => false; + } +} diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/CargoMission.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/CargoMission.cs index 23b228922..301a89f12 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/CargoMission.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/CargoMission.cs @@ -5,6 +5,9 @@ namespace Barotrauma { partial class CargoMission : Mission { + public override bool IsAtCompletionState => false; + public override bool IsAtFailureState => false; + public override string GetMissionRewardText(Submarine sub) { string rewardText = TextManager.GetWithVariable("currencyformat", "[credits]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", GetReward(sub))); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/CombatMission.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/CombatMission.cs index 9fb2b490b..eeea4f4f9 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/CombatMission.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/CombatMission.cs @@ -20,5 +20,8 @@ namespace Barotrauma return descriptions[GameMain.Client.Character.TeamID == CharacterTeamType.Team1 ? 1 : 2]; } } + + public override bool IsAtCompletionState => false; + public override bool IsAtFailureState => false; } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/EscortMission.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/EscortMission.cs index 230570e5e..9a6745f46 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/EscortMission.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/EscortMission.cs @@ -4,6 +4,9 @@ namespace Barotrauma { partial class EscortMission : Mission { + public override bool IsAtCompletionState => false; + public override bool IsAtFailureState => State == 1; + public override void ClientReadInitial(IReadMessage msg) { base.ClientReadInitial(msg); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/GoToMission.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/GoToMission.cs new file mode 100644 index 000000000..5eb29df7e --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/GoToMission.cs @@ -0,0 +1,8 @@ +namespace Barotrauma +{ + partial class GoToMission : Mission + { + public override bool IsAtCompletionState => false; + public override bool IsAtFailureState => false; + } +} diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/MineralMission.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/MineralMission.cs index 3ce80fd05..a0400ff17 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/MineralMission.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/MineralMission.cs @@ -1,15 +1,14 @@ -using Barotrauma.Extensions; -using Barotrauma.Items.Components; +using Barotrauma.Items.Components; using Barotrauma.Networking; -using Microsoft.Xna.Framework; -using System; using System.Collections.Generic; -using System.Linq; namespace Barotrauma { partial class MineralMission : Mission { + public override bool IsAtCompletionState => false; + public override bool IsAtFailureState => false; + public override void ClientReadInitial(IReadMessage msg) { base.ClientReadInitial(msg); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/Mission.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/Mission.cs index 0dc6284b6..72016eb4c 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/Mission.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/Mission.cs @@ -19,6 +19,15 @@ namespace Barotrauma public virtual IEnumerable HudIconTargets => Enumerable.Empty(); + /// + /// State at which the only thing left to do is to reach the end of the level. Use for UI references. + /// + public abstract bool IsAtCompletionState { get; } + /// + /// State at which the mission cannot be completed anymore. Use for UI references. + /// + public abstract bool IsAtFailureState { get; } + public Color GetDifficultyColor() { int v = Difficulty ?? MissionPrefab.MinDifficulty; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/MonsterMission.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/MonsterMission.cs index e5aab9f41..836483409 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/MonsterMission.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/MonsterMission.cs @@ -4,6 +4,9 @@ namespace Barotrauma { partial class MonsterMission : Mission { + public override bool IsAtCompletionState => State > 0; + public override bool IsAtFailureState => false; + public override void ClientReadInitial(IReadMessage msg) { base.ClientReadInitial(msg); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/NestMission.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/NestMission.cs index dcc6ee187..b804770e9 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/NestMission.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/NestMission.cs @@ -6,6 +6,9 @@ namespace Barotrauma { partial class NestMission : Mission { + public override bool IsAtCompletionState => State > 0 && !requireDelivery; + public override bool IsAtFailureState => false; + public override void ClientReadInitial(IReadMessage msg) { base.ClientReadInitial(msg); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/PirateMission.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/PirateMission.cs index d111e664c..1d20ac8b5 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/PirateMission.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/PirateMission.cs @@ -4,6 +4,9 @@ namespace Barotrauma { partial class PirateMission : Mission { + public override bool IsAtCompletionState => State > 1; + public override bool IsAtFailureState => false; + public override void ClientReadInitial(IReadMessage msg) { base.ClientReadInitial(msg); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/SalvageMission.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/SalvageMission.cs index 6479ddb90..d40dfd612 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/SalvageMission.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/SalvageMission.cs @@ -5,6 +5,9 @@ namespace Barotrauma { partial class SalvageMission : Mission { + public override bool IsAtCompletionState => false; + public override bool IsAtFailureState => false; + public override void ClientReadInitial(IReadMessage msg) { base.ClientReadInitial(msg); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/ScanMission.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/ScanMission.cs index c7488925c..a3fa87736 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/ScanMission.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/ScanMission.cs @@ -1,5 +1,4 @@ using Barotrauma.Networking; -using Microsoft.Xna.Framework; using System; using System.Collections.Generic; using System.Linq; @@ -23,6 +22,9 @@ namespace Barotrauma } } + public override bool IsAtCompletionState => false; + public override bool IsAtFailureState => false; + public override void ClientReadInitial(IReadMessage msg) { base.ClientReadInitial(msg); diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs index 2dc2d7ec9..116f2b857 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs @@ -1356,7 +1356,7 @@ namespace Barotrauma #region Element drawing - private static List usedIndicatorAngles = new List(); + private static readonly List usedIndicatorAngles = new List(); /// Should the indicator move based on the camera position? /// Override the distance-based alpha value with the specified alpha value diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIComponent.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIComponent.cs index 5f3d68929..e9ce18130 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIComponent.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIComponent.cs @@ -1019,8 +1019,10 @@ namespace Barotrauma case "gridtext": LoadGridText(element, parent); return null; + case "conditional": + break; default: - throw new NotImplementedException("Loading GUI component \""+element.Name+"\" from XML is not implemented."); + throw new NotImplementedException("Loading GUI component \"" + element.Name + "\" from XML is not implemented."); } if (component != null) diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/Store.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/Store.cs index 8c2e11dc1..4d2e47df8 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/Store.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/Store.cs @@ -1683,8 +1683,9 @@ namespace Barotrauma private void SetItemFrameStatus(GUIComponent itemFrame, bool enabled) { - if (itemFrame == null || !(itemFrame.UserData is PurchasedItem pi)) { return; } - + if (!(itemFrame?.UserData is PurchasedItem pi)) { return; } + bool refreshFrameStatus = !pi.IsStoreComponentEnabled.HasValue || pi.IsStoreComponentEnabled.Value != enabled; + if (!refreshFrameStatus) { return; } if (itemFrame.FindChild("icon", recursive: true) is GUIImage icon) { if (pi.ItemPrefab?.InventoryIcon != null) @@ -1696,14 +1697,11 @@ namespace Barotrauma icon.Color = pi.ItemPrefab.SpriteColor * (enabled ? 1.0f : 0.5f); } }; - var color = Color.White * (enabled ? 1.0f : 0.5f); - if (itemFrame.FindChild("name", recursive: true) is GUITextBlock name) { name.TextColor = color; } - if (itemFrame.FindChild("quantitylabel", recursive: true) is GUITextBlock qty) { qty.TextColor = color; @@ -1712,25 +1710,21 @@ namespace Barotrauma { numberInput.Enabled = enabled; } - if (itemFrame.FindChild("owned", recursive: true) is GUITextBlock ownedBlock) { ownedBlock.TextColor = color; } - - var isDiscounted = false; + bool isDiscounted = false; if (itemFrame.FindChild("undiscountedprice", recursive: true) is GUITextBlock undiscountedPriceBlock) { undiscountedPriceBlock.TextColor = color; undiscountedPriceBlock.Strikethrough.Color = color; isDiscounted = true; } - if (itemFrame.FindChild("price", recursive: true) is GUITextBlock priceBlock) { priceBlock.TextColor = isDiscounted ? storeSpecialColor * (enabled ? 1.0f : 0.5f) : color; } - if (itemFrame.FindChild("addbutton", recursive: true) is GUIButton addButton) { addButton.Enabled = enabled; @@ -1739,6 +1733,8 @@ namespace Barotrauma { removeButton.Enabled = enabled; } + pi.IsStoreComponentEnabled = enabled; + itemFrame.UserData = pi; } private void SetQuantityLabelText(StoreTab mode, GUIComponent itemFrame) diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs index 863202f87..7daa03f0f 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs @@ -129,7 +129,6 @@ namespace Barotrauma public TabMenu() { if (!initialized) { Initialize(); } - CreateInfoFrame(SelectedTab); SelectInfoFrameTab(SelectedTab); } @@ -300,7 +299,7 @@ namespace Barotrauma CreateMissionInfo(infoFrameHolder); break; case InfoFrameTab.Reputation: - if (GameMain.GameSession.RoundSummary != null && GameMain.GameSession.GameMode is CampaignMode campaignMode) + if (GameMain.GameSession?.RoundSummary != null && GameMain.GameSession?.GameMode is CampaignMode campaignMode) { infoFrameHolder.ClearChildren(); GUIFrame reputationFrame = new GUIFrame(new RectTransform(Vector2.One, infoFrameHolder.RectTransform, Anchor.TopCenter), style: "GUIFrameListBox"); @@ -308,8 +307,8 @@ namespace Barotrauma } break; case InfoFrameTab.Traitor: - TraitorMissionPrefab traitorMission = GameMain.Client.TraitorMission; - Character traitor = GameMain.Client.Character; + TraitorMissionPrefab traitorMission = GameMain.Client?.TraitorMission; + Character traitor = GameMain.Client?.Character; if (traitor == null || traitorMission == null) { return; } CreateTraitorInfo(infoFrameHolder, traitorMission, traitor); break; @@ -753,7 +752,7 @@ namespace Barotrauma if (character != null) { - if (GameMain.NetworkMember == null) + if (GameMain.Client == null) { GUIComponent preview = character.Info.CreateInfoFrame(background, false, null); } @@ -1021,13 +1020,15 @@ namespace Barotrauma int iconHeight = Math.Max(missionTextGroup.RectTransform.NonScaledSize.Y, (int)(iconWidth * iconAspectRatio)); Point iconSize = new Point(iconWidth, iconHeight);*/ - new GUIImage(new RectTransform(new Point(iconSize), missionDescriptionHolder.RectTransform), mission.Prefab.Icon, null, true) + var icon = new GUIImage(new RectTransform(new Point(iconSize), missionDescriptionHolder.RectTransform), mission.Prefab.Icon, null, true) { Color = mission.Prefab.IconColor, HoverColor = mission.Prefab.IconColor, SelectedColor = mission.Prefab.IconColor, CanBeFocused = false }; + UpdateMissionStateIcon(mission, icon); + mission.OnMissionStateChanged += (mission) => UpdateMissionStateIcon(mission, icon); } new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextGroup.RectTransform), missionNameRichTextData, missionNameString, font: GUI.LargeFont); GUILayoutGroup difficultyIndicatorGroup = null; @@ -1064,6 +1065,33 @@ namespace Barotrauma } } + private void UpdateMissionStateIcon(Mission mission, GUIImage missionIcon) + { + if (mission == null || missionIcon == null) { return; } + string style = string.Empty; + if (mission.IsAtFailureState) + { + style = "MissionFailedIcon"; + } + else if (mission.IsAtCompletionState) + { + style = "MissionCompletedIcon"; + } + GUIImage stateIcon = missionIcon.GetChild(); + if (string.IsNullOrEmpty(style)) + { + if (stateIcon != null) + { + stateIcon.Visible = false; + } + } + else + { + stateIcon ??= new GUIImage(new RectTransform(Vector2.One, missionIcon.RectTransform), style, scaleToFit: true); + stateIcon.Visible = true; + } + } + private void CreateTraitorInfo(GUIFrame infoFrame, TraitorMissionPrefab traitorMission, Character traitor) { GUIFrame missionFrame = new GUIFrame(new RectTransform(Vector2.One, infoFrame.RectTransform, Anchor.TopCenter), style: "GUIFrameListBox"); @@ -1441,7 +1469,7 @@ namespace Barotrauma selectedTalents.Remove(talentIdentifier); } - UpdateTalentButtons(); + UpdateTalentInfo(); return true; }, }; @@ -1506,7 +1534,7 @@ namespace Barotrauma }; GUITextBlock.AutoScaleAndNormalize(talentResetButton.TextBlock, talentApplyButton.TextBlock); - UpdateTalentButtons(); + UpdateTalentInfo(); } private void CreateTalentSkillList(Character character, GUIListBox parent) @@ -1541,11 +1569,13 @@ namespace Barotrauma GUITextBlock.AutoScaleAndNormalize(skillNames); } - private void UpdateTalentButtons() + private void UpdateTalentInfo() { Character controlledCharacter = Character.Controlled; if (controlledCharacter?.Info == null) { return; } + if (SelectedTab != InfoFrameTab.Talents) { return; } + bool unlockedAllTalents = controlledCharacter.HasUnlockedAllTalents(); if (unlockedAllTalents) @@ -1626,7 +1656,7 @@ namespace Barotrauma } } selectedTalents = controlledCharacter.Info.GetUnlockedTalentsInTree().ToList(); - UpdateTalentButtons(); + UpdateTalentInfo(); } private bool ApplyTalentSelection(GUIButton guiButton, object userData) @@ -1640,63 +1670,14 @@ namespace Barotrauma { Character controlledCharacter = Character.Controlled; selectedTalents = controlledCharacter.Info.GetUnlockedTalentsInTree().ToList(); - UpdateTalentButtons(); + UpdateTalentInfo(); return true; } public void OnExperienceChanged(Character character) { if (character != Character.Controlled) { return; } - UpdateTalentButtons(); - } - - private readonly StatTypes[] basicStats = new StatTypes[] - { - StatTypes.MaximumHealthMultiplier, - StatTypes.MovementSpeed, - StatTypes.SwimmingSpeed, - StatTypes.RepairSpeed, - }; - - private readonly StatTypes[] combatStats = new StatTypes[] - { - StatTypes.MeleeAttackMultiplier, - StatTypes.MeleeAttackSpeed, - StatTypes.RangedAttackSpeed, - StatTypes.TurretAttackSpeed, - }; - - private readonly StatTypes[] miscStats = new StatTypes[] - { - StatTypes.ReputationGainMultiplier, - StatTypes.MissionMoneyGainMultiplier, - StatTypes.ExperienceGainMultiplier, - StatTypes.MissionExperienceGainMultiplier, - }; - - private void CreateCharacterSheet(GUILayoutGroup characterInfoColumn) - { - Character controlledCharacter = Character.Controlled; - - CreateRow(basicStats); - CreateRow(combatStats); - CreateRow(miscStats); - - void CreateRow(StatTypes[] statTypes) - { - GUILayoutGroup characterInfoRow = new GUILayoutGroup(new RectTransform(new Vector2(0.33f, 1.0f), characterInfoColumn.RectTransform, anchor: Anchor.TopLeft), childAnchor: Anchor.TopCenter); - foreach (StatTypes statType in statTypes) - { - ShowStat(statType, characterInfoRow); - } - } - - void ShowStat(StatTypes statType, GUILayoutGroup characterInfoRow) - { - GUIFrame textInfoFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.33f), characterInfoRow.RectTransform, Anchor.TopCenter), style: null); - new GUITextBlock(new RectTransform(new Vector2(1f, 1f), textInfoFrame.RectTransform, Anchor.TopLeft), statType.ToString(), font: GUI.SmallFont, textAlignment: Alignment.TopLeft); - new GUITextBlock(new RectTransform(new Vector2(1f, 1f), textInfoFrame.RectTransform, Anchor.TopLeft), (int)(100f * (1 + controlledCharacter.GetStatValue(statType))) + "%", font: GUI.Font, textAlignment: Alignment.TopRight); - } + UpdateTalentInfo(); } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/HintManager.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/HintManager.cs index 3a22e29d8..40cff56c3 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/HintManager.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/HintManager.cs @@ -12,6 +12,8 @@ namespace Barotrauma static class HintManager { private const string HintManagerFile = "hintmanager.xml"; + + public static bool Enabled => GameMain.Config != null && !GameMain.Config.DisableInGameHints; private static HashSet HintIdentifiers { get; set; } private static Dictionary> HintTags { get; } = new Dictionary>(); private static Dictionary HintOrders { get; } = new Dictionary(); @@ -666,6 +668,8 @@ namespace Barotrauma ActiveHintMessageBox.InnerFrame.Flash(color: iconColor ?? Color.Orange, flashDuration: 0.75f); onDisplay?.Invoke(); + GameAnalyticsManager.AddDesignEvent($"HintManager:{GameMain.GameSession?.GameMode?.Preset?.Identifier ?? "none"}:HintDisplayed:{hintIdentifier}"); + return true; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSettings.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSettings.cs index 79bc45113..fd2e7a7cd 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSettings.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSettings.cs @@ -565,6 +565,13 @@ namespace Barotrauma }); #endif + foreach (var child in leftPanel.Children) + { + if (child is GUITextBlock textBlock) + { + textBlock.RectTransform.MinSize = new Point(textBlock.RectTransform.MinSize.X, (int)Math.Max(textBlock.RectTransform.MinSize.Y, textBlock.TextSize.Y)); + } + } // right panel -------------------------------------- diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs index c02cb3613..efda23637 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs @@ -1693,13 +1693,12 @@ namespace Barotrauma.Items.Components } } - if (iconIdentifier == null || !targetIcons.ContainsKey(iconIdentifier)) + if (iconIdentifier == null || !targetIcons.TryGetValue(iconIdentifier, out var iconInfo) || iconInfo.Item1 == null) { GUI.DrawRectangle(spriteBatch, new Rectangle((int)markerPos.X - 3, (int)markerPos.Y - 3, 6, 6), markerColor, thickness: 2); } else { - var iconInfo = targetIcons[iconIdentifier]; iconInfo.Item1.Draw(spriteBatch, markerPos, iconInfo.Item2); } @@ -1712,7 +1711,10 @@ namespace Barotrauma.Items.Components Vector2 textSize = GUI.SmallFont.MeasureString(wrappedLabel); //flip the text to left side when the marker is on the left side or goes outside the right edge of the interface - if ((dir.X < 0.0f || labelPos.X + textSize.X + 10 > GuiFrame.Rect.X) && labelPos.X - textSize.X > 0) labelPos.X -= textSize.X + 10; + if (GuiFrame != null && (dir.X < 0.0f || labelPos.X + textSize.X + 10 > GuiFrame.Rect.X) && labelPos.X - textSize.X > 0) + { + labelPos.X -= textSize.X + 10; + } GUI.DrawString(spriteBatch, new Vector2(labelPos.X + 10, labelPos.Y), diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Repairable.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Repairable.cs index 87f0dbe07..0900f4fed 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Repairable.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Repairable.cs @@ -381,17 +381,17 @@ namespace Barotrauma.Items.Components if (deteriorationTimer > 0.0f) { GUI.DrawString(spriteBatch, - new Vector2(item.WorldPosition.X, -item.WorldPosition.Y), "Deterioration delay " + ((int)deteriorationTimer) + (paused ? " [PAUSED]" : ""), + new Vector2(item.DrawPosition.X, -item.DrawPosition.Y), "Deterioration delay " + ((int)deteriorationTimer) + (paused ? " [PAUSED]" : ""), paused ? Color.Cyan : Color.Lime, Color.Black * 0.5f); } else { GUI.DrawString(spriteBatch, - new Vector2(item.WorldPosition.X, -item.WorldPosition.Y), "Deteriorating at " + (int)(DeteriorationSpeed * 60.0f) + " units/min" + (paused ? " [PAUSED]" : ""), + new Vector2(item.DrawPosition.X, -item.DrawPosition.Y), "Deteriorating at " + (int)(DeteriorationSpeed * 60.0f) + " units/min" + (paused ? " [PAUSED]" : ""), paused ? Color.Cyan : GUI.Style.Red, Color.Black * 0.5f); } GUI.DrawString(spriteBatch, - new Vector2(item.WorldPosition.X, -item.WorldPosition.Y + 20), "Condition: " + (int)item.Condition + "/" + (int)item.MaxCondition, + new Vector2(item.DrawPosition.X, -item.DrawPosition.Y + 20), "Condition: " + (int)item.Condition + "/" + (int)item.MaxCondition, GUI.Style.Orange); } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Connection.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Connection.cs index 336769480..2e8e07a2b 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Connection.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Connection.cs @@ -23,7 +23,7 @@ namespace Barotrauma.Items.Components public static void DrawConnections(SpriteBatch spriteBatch, ConnectionPanel panel, Character character) { - if (DraggingConnected?.Item.Removed ?? false) + if (DraggingConnected?.Item?.Removed ?? true) { DraggingConnected = null; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Turret.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Turret.cs index 11c827e3a..98f9490aa 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Turret.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Turret.cs @@ -580,14 +580,20 @@ namespace Barotrauma.Items.Components private void GetAvailablePower(out float availableCharge, out float availableCapacity) { - var batteries = item.GetConnectedComponents(); - availableCharge = 0.0f; availableCapacity = 0.0f; - foreach (PowerContainer battery in batteries) + if (item.Connections == null) { return; } + foreach (Connection c in item.Connections) { - availableCharge += battery.Charge; - availableCapacity += battery.Capacity; + var recipients = c.Recipients; + foreach (Connection recipient in recipients) + { + if (!recipient.IsPower || !recipient.IsOutput) { continue; } + var battery = recipient.Item?.GetComponent(); + if (battery == null) { continue; } + availableCharge += battery.Charge; + availableCapacity += battery.Capacity; + } } } @@ -647,7 +653,9 @@ namespace Barotrauma.Items.Components bool readyToFire = reload <= 0.0f && charged && availableAmmo.Any(p => p != null); if (ShowChargeIndicator && PowerConsumption > 0.0f) { - powerIndicator.Color = charged ? GUI.Style.Green : GUI.Style.Red; + powerIndicator.Color = charged ? + (HasPowerToShoot() ? GUI.Style.Green : GUI.Style.Orange) : + GUI.Style.Red; if (flashLowPower) { powerIndicator.BarSize = 1; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Wearable.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Wearable.cs index 10d0fc376..26665699e 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Wearable.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Wearable.cs @@ -10,7 +10,13 @@ namespace Barotrauma.Items.Components int roundedValue = (int)Math.Round((1 - damageModifier.DamageMultiplier * damageModifier.ProbabilityMultiplier) * 100); if (roundedValue == 0) { return; } string colorStr = XMLExtensions.ColorToString(GUI.Style.Green); - description += $"\n ‖color:{colorStr}‖{roundedValue.ToString("-0;+#")}%‖color:end‖ {AfflictionPrefab.List.FirstOrDefault(ap => ap.Identifier.Equals(afflictionIdentifier, StringComparison.OrdinalIgnoreCase))?.Name ?? afflictionIdentifier}"; + + string afflictionName = + AfflictionPrefab.List.FirstOrDefault(ap => ap.Identifier.Equals(afflictionIdentifier, StringComparison.OrdinalIgnoreCase))?.Name ?? + TextManager.Get($"afflictiontype.{afflictionIdentifier}", returnNull: true) ?? + afflictionIdentifier; + + description += $"\n ‖color:{colorStr}‖{roundedValue.ToString("-0;+#")}%‖color:end‖ {afflictionName}"; } public override void AddTooltipInfo(ref string name, ref string description) @@ -33,9 +39,9 @@ namespace Barotrauma.Items.Components { GetDamageModifierText(ref description, damageModifier, afflictionIdentifier); } - foreach (string afflictionIdentifier in damageModifier.ParsedAfflictionTypes) + foreach (string afflictionType in damageModifier.ParsedAfflictionTypes) { - GetDamageModifierText(ref description, damageModifier, afflictionIdentifier); + GetDamageModifierText(ref description, damageModifier, afflictionType); } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs index f5a43c09e..86b76aebb 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs @@ -1181,7 +1181,7 @@ namespace Barotrauma } texts.Add(new ColoredText(nameText, GUI.Style.TextColor, false, false)); - if (CampaignInteractionType != CampaignMode.InteractionType.None) + if (CampaignMode.BlocksInteraction(CampaignInteractionType)) { texts.Add(new ColoredText(TextManager.GetWithVariable($"CampaignInteraction.{CampaignInteractionType}", "[key]", GameMain.Config.KeyBindText(InputType.Use)), Color.Cyan, false, false)); } @@ -1550,6 +1550,7 @@ namespace Barotrauma Vector2 pos = Vector2.Zero; Submarine sub = null; + float rotation = 0.0f; int itemContainerIndex = -1; int inventorySlotIndex = -1; @@ -1561,7 +1562,7 @@ namespace Barotrauma else { pos = new Vector2(msg.ReadSingle(), msg.ReadSingle()); - + rotation = msg.ReadRangedSingle(0, MathHelper.TwoPi, 8); ushort subID = msg.ReadUInt16(); if (subID > 0) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Map/Map.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Map/Map.cs index 0104d2e1c..2c34707c6 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Map/Map.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Map/Map.cs @@ -130,18 +130,31 @@ namespace Barotrauma int tilesY = (int)Math.Ceiling(Height / tileSize.Y); mapTiles = new Sprite[tilesX, tilesY]; tileDiscovered = new bool[tilesX, tilesY]; + HashSet missingBiomes = new HashSet(); for (int x = 0; x < tilesX; x++) { for (int y = 0; y < tilesY; y++) { var biome = GetBiome(x * tileSize.X); - var tileList = generationParams.MapTiles.ContainsKey(biome.Identifier) ? - generationParams.MapTiles[biome.Identifier] : - generationParams.MapTiles.Values.First(); + List tileList = null; + if (generationParams.MapTiles.ContainsKey(biome.Identifier)) + { + tileList = generationParams.MapTiles[biome.Identifier]; + } + else + { + tileList = generationParams.MapTiles.Values.First(); + missingBiomes.Add(biome); + } mapTiles[x, y] = tileList[x % tileList.Count]; } } + foreach (var missingBiome in missingBiomes) + { + DebugConsole.ThrowError($"Could not find campaign map sprites for the biome \"{missingBiome.Identifier}\". Using the sprites of the first biome instead..."); + } + RemoveFogOfWar(StartLocation); GenerateLocationConnectionVisuals(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/MapEntity.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/MapEntity.cs index 0d93a3efe..1c9c15096 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/MapEntity.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/MapEntity.cs @@ -772,7 +772,7 @@ namespace Barotrauma if (item.FlippedX && item.Prefab.CanSpriteFlipX) { spriteEffects ^= SpriteEffects.FlipHorizontally; } if (item.flippedY && item.Prefab.CanSpriteFlipY) { spriteEffects ^= SpriteEffects.FlipVertically; } var wire = item.GetComponent(); - if (wire != null && !wire.Item.body.Enabled) + if (wire != null && wire.Item.body != null && !wire.Item.body.Enabled) { wire.Draw(spriteBatch, editing: false, new Vector2(moveAmount.X, -moveAmount.Y)); continue; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/SubmarineBody.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/SubmarineBody.cs index 2a63d6690..57bbe7a9e 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/SubmarineBody.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/SubmarineBody.cs @@ -55,9 +55,9 @@ namespace Barotrauma if (closestSub != null && subsToMove.Contains(closestSub)) { GameMain.GameScreen.Cam.Position += moveAmount; - if (GameMain.GameScreen.Cam.TargetPos != Vector2.Zero) GameMain.GameScreen.Cam.TargetPos += moveAmount; + if (GameMain.GameScreen.Cam.TargetPos != Vector2.Zero) { GameMain.GameScreen.Cam.TargetPos += moveAmount; } - if (Character.Controlled != null) Character.Controlled.CursorPosition += moveAmount; + if (Character.Controlled != null) { Character.Controlled.CursorPosition += moveAmount; } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/SubmarinePreview.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/SubmarinePreview.cs index 9eff76d75..900a6d355 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/SubmarinePreview.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/SubmarinePreview.cs @@ -316,11 +316,11 @@ namespace Barotrauma var srcRect = prefab.sprite.SourceRect; SpriteEffects spriteEffects = SpriteEffects.None; - if (flippedX) + if (flippedX && ((prefab as ItemPrefab)?.CanSpriteFlipX ?? true)) { spriteEffects |= SpriteEffects.FlipHorizontally; } - if (flippedY) + if (flippedY && ((prefab as ItemPrefab)?.CanSpriteFlipY ?? true)) { spriteEffects |= SpriteEffects.FlipVertically; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/WayPoint.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/WayPoint.cs index 6a2abf2aa..b2cbce8ff 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/WayPoint.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/WayPoint.cs @@ -90,14 +90,14 @@ namespace Barotrauma { GUI.DrawLine(spriteBatch, drawPos, - new Vector2(ConnectedGap.WorldPosition.X, -ConnectedGap.WorldPosition.Y), + new Vector2(ConnectedGap.DrawPosition.X, -ConnectedGap.DrawPosition.Y), GUI.Style.Green * 0.5f, width: 1); } if (Ladders != null) { GUI.DrawLine(spriteBatch, drawPos, - new Vector2(Ladders.Item.WorldPosition.X, -Ladders.Item.WorldPosition.Y), + new Vector2(Ladders.Item.DrawPosition.X, -Ladders.Item.DrawPosition.Y), GUI.Style.Green * 0.5f, width: 1); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/EntitySpawner.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/EntitySpawner.cs index 9ab68965f..86897d268 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/EntitySpawner.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/EntitySpawner.cs @@ -1,4 +1,5 @@ -using Barotrauma.Networking; +using Barotrauma.Items.Components; +using Barotrauma.Networking; namespace Barotrauma { @@ -15,7 +16,11 @@ namespace Barotrauma var entity = FindEntityByID(entityId); if (entity != null) { - DebugConsole.Log("Received entity removal message for \"" + entity.ToString() + "\"."); + DebugConsole.Log($"Received entity removal message for \"{entity}\"."); + if (entity is Item item && item.Container?.GetComponent() != null) + { + GameAnalyticsManager.AddDesignEvent("ItemDeconstructed:" + (GameMain.GameSession?.GameMode?.Preset.Identifier ?? "none") + ":" + item.prefab.Identifier); + } entity.Remove(); } else @@ -28,7 +33,11 @@ namespace Barotrauma switch (message.ReadByte()) { case (byte)SpawnableType.Item: - Item.ReadSpawnData(message, true); + var newItem = Item.ReadSpawnData(message, true); + if (newItem is Item item && item.Container?.GetComponent() != null) + { + GameAnalyticsManager.AddDesignEvent("ItemFabricated:" + (GameMain.GameSession?.GameMode?.Preset.Identifier ?? "none") + ":" + item.prefab.Identifier); + } break; case (byte)SpawnableType.Character: Character.ReadSpawnData(message); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/CharacterEditor/CharacterEditorScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/CharacterEditor/CharacterEditorScreen.cs index 5cc04acb3..41b8e927c 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/CharacterEditor/CharacterEditorScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/CharacterEditor/CharacterEditorScreen.cs @@ -251,7 +251,7 @@ namespace Barotrauma.CharacterEditor GUI.ForceMouseOn(null); if (isEndlessRunner) { - Submarine.MainSub.Remove(); + Submarine.MainSub?.Remove(); GameMain.World.ProcessChanges(); isEndlessRunner = false; Reset(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs index e013965ea..5319a5ca1 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs @@ -115,7 +115,7 @@ namespace Barotrauma #if TEST_REMOTE_CONTENT - var doc = XMLExtensions.TryLoadXml("Content/UI/MenuTextTest.xml"); + var doc = XMLExtensions.TryLoadXml("Content/UI/MenuContent.xml"); if (doc?.Root != null) { foreach (XElement subElement in doc?.Root.Elements()) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs index 1fc248426..6b6b947dc 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs @@ -144,7 +144,7 @@ namespace Barotrauma private GUIDropDown linkedSubBox; private static GUIComponent autoSaveLabel; - private static int maxAutoSaves = GameSettings.MaximumAutoSaves; + private readonly static int maxAutoSaves = GameSettings.MaximumAutoSaves; public static readonly object ItemAddMutex = new object(), ItemRemoveMutex = new object(); @@ -541,7 +541,7 @@ namespace Barotrauma //----------------------------------------------- - layerPanel = new GUIFrame(new RectTransform(new Vector2(0.2f, 0.4f), GUI.Canvas)) + layerPanel = new GUIFrame(new RectTransform(new Vector2(0.25f, 0.4f), GUI.Canvas, minSize: new Point(300, 320))) { Visible = false }; @@ -604,11 +604,13 @@ namespace Barotrauma RenameLayer(layer, newName); }); } - return true; } }; + GUITextBlock.AutoScaleAndNormalize(layerAddButton.TextBlock, layerDeleteButton.TextBlock, layerRenameButton.TextBlock); + + Vector2 subPanelSize = new Vector2(0.925f, 0.9f); undoBufferPanel = new GUIFrame(new RectTransform(new Vector2(0.15f, 0.2f), GUI.Canvas) { MinSize = new Point(200, 200) }) @@ -4433,9 +4435,9 @@ namespace Barotrauma layerList.Deselect(); GUILayoutGroup buttonHeaders = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.075f), layerList.Content.RectTransform), isHorizontal: true, childAnchor: Anchor.BottomLeft); - new GUIButton(new RectTransform(new Vector2(0.25f, 1f), buttonHeaders.RectTransform), TextManager.Get("editor.layer.headervisible"), style: "GUIButtonSmallFreeScale") { CanBeFocused = false, ForceUpperCase = true }; - new GUIButton(new RectTransform(new Vector2(0.15f, 1f), buttonHeaders.RectTransform), TextManager.Get("editor.layer.headerlink"), style: "GUIButtonSmallFreeScale") { CanBeFocused = false, ForceUpperCase = true }; - new GUIButton(new RectTransform(new Vector2(0.65f, 1f), buttonHeaders.RectTransform), TextManager.Get("name"), style: "GUIButtonSmallFreeScale") { CanBeFocused = false, ForceUpperCase = true }; + new GUIButton(new RectTransform(new Vector2(0.25f, 1f), buttonHeaders.RectTransform), TextManager.Get("editor.layer.headervisible"), style: "GUIButtonSmallFreeScale") { ForceUpperCase = true }; + new GUIButton(new RectTransform(new Vector2(0.15f, 1f), buttonHeaders.RectTransform), TextManager.Get("editor.layer.headerlink"), style: "GUIButtonSmallFreeScale") { ForceUpperCase = true }; + new GUIButton(new RectTransform(new Vector2(0.6f, 1f), buttonHeaders.RectTransform), TextManager.Get("name"), style: "GUIButtonSmallFreeScale") { ForceUpperCase = true }; foreach (var (layer, (visibility, linkage)) in Layers) { @@ -4494,6 +4496,17 @@ namespace Barotrauma layerList.RecalculateChildren(); buttonHeaders.Recalculate(); + foreach (var child in buttonHeaders.Children) + { + var btn = child as GUIButton; + string originalBtnText = btn.Text; + btn.Text = ToolBox.LimitString(btn.Text, btn.Font, btn.Rect.Width); + if (originalBtnText != btn.Text) + { + btn.ToolTip = originalBtnText; + } + } + } public void UpdateUndoHistoryPanel() diff --git a/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundPlayer.cs b/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundPlayer.cs index 32f365887..57920dea6 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundPlayer.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundPlayer.cs @@ -1178,7 +1178,7 @@ namespace Barotrauma if ((s.damageRange == Vector2.Zero || (damage >= s.damageRange.X && damage <= s.damageRange.Y)) && string.Equals(s.damageType, damageType, StringComparison.OrdinalIgnoreCase) && - (tags == null ? string.IsNullOrEmpty(s.requiredTag) : tags.Contains(s.requiredTag))) + (string.IsNullOrEmpty(s.requiredTag) || (tags == null ? string.IsNullOrEmpty(s.requiredTag) : tags.Contains(s.requiredTag)))) { tempList.Add(s); } diff --git a/Barotrauma/BarotraumaClient/LinuxClient.csproj b/Barotrauma/BarotraumaClient/LinuxClient.csproj index ca56a06bf..be3b808b9 100644 --- a/Barotrauma/BarotraumaClient/LinuxClient.csproj +++ b/Barotrauma/BarotraumaClient/LinuxClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.16.2.0 + 0.16.3.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/MacClient.csproj b/Barotrauma/BarotraumaClient/MacClient.csproj index 67d428f69..a54282943 100644 --- a/Barotrauma/BarotraumaClient/MacClient.csproj +++ b/Barotrauma/BarotraumaClient/MacClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.16.2.0 + 0.16.3.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/WindowsClient.csproj b/Barotrauma/BarotraumaClient/WindowsClient.csproj index 9fbc4f6b4..8abfbc602 100644 --- a/Barotrauma/BarotraumaClient/WindowsClient.csproj +++ b/Barotrauma/BarotraumaClient/WindowsClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.16.2.0 + 0.16.3.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaServer/LinuxServer.csproj b/Barotrauma/BarotraumaServer/LinuxServer.csproj index b3c348211..c42db27f4 100644 --- a/Barotrauma/BarotraumaServer/LinuxServer.csproj +++ b/Barotrauma/BarotraumaServer/LinuxServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.16.2.0 + 0.16.3.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/MacServer.csproj b/Barotrauma/BarotraumaServer/MacServer.csproj index 0c3d83771..bca081ce7 100644 --- a/Barotrauma/BarotraumaServer/MacServer.csproj +++ b/Barotrauma/BarotraumaServer/MacServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.16.2.0 + 0.16.3.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/ServerSource/Items/Item.cs b/Barotrauma/BarotraumaServer/ServerSource/Items/Item.cs index 68c431fbc..918bfde23 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Items/Item.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Items/Item.cs @@ -252,6 +252,7 @@ namespace Barotrauma msg.Write(Position.X); msg.Write(Position.Y); + msg.WriteRangedSingle(body == null ? 0.0f : MathUtils.WrapAngleTwoPi(body.Rotation), 0.0f, MathHelper.TwoPi, 8); msg.Write(Submarine != null ? Submarine.ID : (ushort)0); } else diff --git a/Barotrauma/BarotraumaServer/WindowsServer.csproj b/Barotrauma/BarotraumaServer/WindowsServer.csproj index 9d8d2aafb..371b2edab 100644 --- a/Barotrauma/BarotraumaServer/WindowsServer.csproj +++ b/Barotrauma/BarotraumaServer/WindowsServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.16.2.0 + 0.16.3.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs index fe05e7a41..4b2dc74dc 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs @@ -207,8 +207,10 @@ namespace Barotrauma } = new HashSet(); public bool IsTargetingPlayerTeam => IsTargetInPlayerTeam(SelectedAiTarget); - public bool IsBeingChasedBy(Character c) => c.AIController is EnemyAIController enemyAI && enemyAI.SelectedAiTarget?.Entity is Character && (enemyAI.State == AIState.Aggressive || enemyAI.State == AIState.Attack); - private bool IsBeingChased => SelectedAiTarget?.Entity is Character targetCharacter && IsBeingChasedBy(targetCharacter); + public static bool IsTargetBeingChasedBy(Character target, Character character) + => character?.AIController is EnemyAIController enemyAI && enemyAI.SelectedAiTarget?.Entity == target && (enemyAI.State == AIState.Attack || enemyAI.State == AIState.Aggressive); + public bool IsBeingChasedBy(Character c) => IsTargetBeingChasedBy(Character, c); + private bool IsBeingChased => IsBeingChasedBy(SelectedAiTarget?.Entity as Character); private bool IsTargetInPlayerTeam(AITarget target) => target?.Entity?.Submarine != null && target.Entity.Submarine.Info.IsPlayer || target?.Entity is Character targetCharacter && targetCharacter.IsOnPlayerTeam; @@ -1322,7 +1324,7 @@ namespace Barotrauma Vector2 rayEnd = rayStart + dir.ClampLength(Character.AnimController.Collider.GetLocalFront().Length() * 2); Body closestBody = Submarine.CheckVisibility(rayStart, rayEnd, ignoreSubs: true); if (Submarine.LastPickedFraction != 1.0f && closestBody != null && - (!AIParams.TargetOuterWalls || !canAttackWalls && closestBody.UserData is Structure s && s.Submarine != null || !canAttackDoors && closestBody.UserData is Item i && i.Submarine != null && i.GetComponent() != null)) + ((!AIParams.TargetOuterWalls || !canAttackWalls) && closestBody.UserData is Structure s && s.Submarine != null || !canAttackDoors && closestBody.UserData is Item i && i.Submarine != null && i.GetComponent() != null)) { // Target is unreachable, there's a door or wall ahead State = AIState.Idle; @@ -2385,6 +2387,24 @@ namespace Barotrauma } #region Targeting + public static bool IsLatchedTo(Character target, Character character) + { + if (target.AIController is EnemyAIController enemyAI && enemyAI.LatchOntoAI != null) + { + return enemyAI.LatchOntoAI.IsAttached && enemyAI.LatchOntoAI.TargetCharacter == character; + } + return false; + } + + public static bool IsLatchedToSomeoneElse(Character target, Character character) + { + if (target.AIController is EnemyAIController enemyAI && enemyAI.LatchOntoAI != null) + { + return enemyAI.LatchOntoAI.IsAttached && enemyAI.LatchOntoAI.TargetCharacter != null && enemyAI.LatchOntoAI.TargetCharacter != character; + } + return false; + } + private bool IsLatchedOnSub => LatchOntoAI != null && LatchOntoAI.IsAttachedToSub; //goes through all the AItargets, evaluates how preferable it is to attack the target, @@ -2398,6 +2418,7 @@ namespace Barotrauma targetingParams = null; bool isAnyTargetClose = false; bool isBeingChased = IsBeingChased; + float maxModifier = 5; foreach (AITarget aiTarget in AITarget.List) { if (aiTarget.InDetectable) { continue; } @@ -2537,12 +2558,12 @@ namespace Barotrauma { if (CanPassThroughHole(s, i)) { - valueModifier *= leadsInside ? (IsAggressiveBoarder ? 3 : 1) : 0; + valueModifier *= leadsInside ? (IsAggressiveBoarder ? maxModifier : 1) : 0; } - else if (IsAggressiveBoarder && leadsInside && canAttackWalls && AIParams.TargetOuterWalls) + else if (IsAggressiveBoarder && leadsInside && canAttackWalls) { - // Up to 25% priority increase for every gap in the wall when an aggressive boarder is outside - valueModifier *= 1 + section.gap.Open * 0.25f; + // Up to 100% priority increase for every gap in the wall when an aggressive boarder is outside + valueModifier *= 1 + section.gap.Open; } } else @@ -2580,6 +2601,7 @@ namespace Barotrauma // We are actually interested in breaking things -> reduce the priority when the wall is already broken // (Terminalcells) valueModifier *= 1 - section.gap.Open * 0.25f; + valueModifier = Math.Max(valueModifier, 0.1f); } } } @@ -2599,6 +2621,7 @@ namespace Barotrauma valueModifier *= 1 + section.gap.Open; } } + valueModifier = Math.Clamp(valueModifier, 0, maxModifier); } } if (door != null) @@ -2610,7 +2633,7 @@ namespace Barotrauma bool isOpen = door.CanBeTraversed; if (!isOpen) { - if (!canAttackDoors || isOutdoor && !AIParams.TargetOuterWalls) { continue; } + if (!canAttackDoors) { continue; } } else if (!Character.AnimController.CanEnterSubmarine) { @@ -2624,11 +2647,11 @@ namespace Barotrauma // Increase the priority if the character is outside and the door is from outside to inside if (door.CanBeTraversed) { - valueModifier = 3; + valueModifier = maxModifier; } else if (door.LinkedGap != null) { - valueModifier = 1 + door.LinkedGap.Open; + valueModifier = 1 + door.LinkedGap.Open * (maxModifier - 1); } } else @@ -2727,6 +2750,37 @@ namespace Barotrauma if (SelectedAiTarget == aiTarget) { + if (Character.Submarine == null && aiTarget.Entity is ISpatialEntity spatialEntity && spatialEntity.Submarine != null) + { + if (targetingTag == "door" || targetingTag == "wall") + { + Vector2 rayStart = Character.SimPosition; + Vector2 rayEnd = aiTarget.SimPosition + spatialEntity.Submarine.SimPosition; + Body closestBody = Submarine.PickBody(rayStart, rayEnd, collisionCategory: Physics.CollisionWall | Physics.CollisionLevel, allowInsideFixture: true); + if (closestBody != null && closestBody.UserData is ISpatialEntity hit) + { + Vector2 hitPos = hit.SimPosition; + if (closestBody.UserData is Submarine) + { + hitPos = Submarine.LastPickedPosition; + } + else if (hit.Submarine != null) + { + hitPos += hit.Submarine.SimPosition; + } + float subHalfWidth = spatialEntity.Submarine.Borders.Width / 2; + float subHalfHeight = spatialEntity.Submarine.Borders.Height / 2; + Vector2 diff = ConvertUnits.ToDisplayUnits(rayEnd - hitPos); + bool isOtherSideOfTheSub = Math.Abs(diff.X) > subHalfWidth || Math.Abs(diff.Y) > subHalfHeight; + if (isOtherSideOfTheSub) + { + IgnoreTarget(aiTarget); + ResetAITarget(); + continue; + } + } + } + } // Stick to the current target valueModifier *= 1.1f; } @@ -2757,19 +2811,22 @@ namespace Barotrauma } } - if (targetParams.AttackPattern == AttackPattern.Circle) + if (Character.Submarine == null && aiTarget.Entity?.Submarine != null && targetCharacter == null) { - if (Character.Submarine == null && aiTarget.Entity?.Submarine != null && !isAnyTargetClose) + if (targetParams.AttackPattern == AttackPattern.Circle || targetParams.AttackPattern == AttackPattern.Sweep) { - if (Submarine.MainSubs.Contains(aiTarget.Entity.Submarine)) + if (!isAnyTargetClose) { - // Prioritize targets that are near the horizontal center of the sub, but only when none of the targets is reachable. - float horizontalDistanceToSubCenter = Math.Abs(aiTarget.WorldPosition.X - aiTarget.Entity.Submarine.WorldPosition.X); - dist *= MathHelper.Lerp(1f, 5f, MathUtils.InverseLerp(0, 10000, horizontalDistanceToSubCenter)); - } - else - { - dist *= 5; + if (Submarine.MainSubs.Contains(aiTarget.Entity.Submarine)) + { + // Prioritize targets that are near the horizontal center of the sub, but only when none of the targets is reachable. + float horizontalDistanceToSubCenter = Math.Abs(aiTarget.WorldPosition.X - aiTarget.Entity.Submarine.WorldPosition.X); + dist *= MathHelper.Lerp(1f, 5f, MathUtils.InverseLerp(0, 10000, horizontalDistanceToSubCenter)); + } + else if (targetParams.AttackPattern == AttackPattern.Circle) + { + dist *= 5; + } } } } @@ -2936,9 +2993,9 @@ namespace Barotrauma if (HasValidPath(requireNonDirty: true)) { return; } wallHits.Clear(); Structure wall = null; + Vector2 rayStart = AttackingLimb != null ? AttackingLimb.SimPosition : SimPosition; if (AIParams.WallTargetingMethod.HasFlag(WallTargetingMethod.Target)) { - Vector2 rayStart = SimPosition; Vector2 rayEnd = SelectedAiTarget.SimPosition; if (SelectedAiTarget.Entity.Submarine != null && Character.Submarine == null) { @@ -2952,7 +3009,6 @@ namespace Barotrauma } if (AIParams.WallTargetingMethod.HasFlag(WallTargetingMethod.Heading)) { - Vector2 rayStart = SimPosition; Vector2 rayEnd = rayStart + VectorExtensions.Forward(Character.AnimController.Collider.Rotation + MathHelper.PiOver2, avoidLookAheadDistance * 5); if (SelectedAiTarget.Entity.Submarine != null && Character.Submarine == null) { @@ -2968,7 +3024,6 @@ namespace Barotrauma } if (AIParams.WallTargetingMethod.HasFlag(WallTargetingMethod.Steering)) { - Vector2 rayStart = SimPosition; Vector2 rayEnd = rayStart + Steering * 5; if (SelectedAiTarget.Entity.Submarine != null && Character.Submarine == null) { @@ -3021,6 +3076,7 @@ namespace Barotrauma // Blocked by a wall that shouldn't be targeted. The main intention here is to prevent monsters from entering the the tail and the nose pieces. if (!isTargetingDoor) { + IgnoreTarget(SelectedAiTarget); ResetAITarget(); } } @@ -3032,6 +3088,7 @@ namespace Barotrauma else { // Blocked by a disabled wall. + IgnoreTarget(SelectedAiTarget); ResetAITarget(); } } @@ -3082,8 +3139,17 @@ namespace Barotrauma if (!(hit.UserData is Structure w)) { return false; } if (w.Submarine == null) { return false; } if (w.Submarine != SelectedAiTarget.Entity.Submarine) { return false; } - if (Character.Submarine == null && w.prefab.Tags.Contains("inner")) { return false; } - if (!AIParams.TargetOuterWalls && !w.prefab.Tags.Contains("inner")) { return false; } + if (Character.Submarine == null) + { + if (w.prefab.Tags.Contains("inner")) + { + if (!Character.AnimController.CanEnterSubmarine) { return false; } + } + else if (!AIParams.TargetOuterWalls) + { + return false; + } + } wall = w; return true; } @@ -3120,7 +3186,8 @@ namespace Barotrauma { if (door.LinkedGap.Size > ConvertUnits.ToDisplayUnits(colliderWidth)) { - return SteerThroughGap(door.LinkedGap, door.LinkedGap.FlowTargetHull.WorldPosition, deltaTime, maxDistance: 100); + float maxDistance = Math.Max(ConvertUnits.ToDisplayUnits(colliderLength), 100); + return SteerThroughGap(door.LinkedGap, door.LinkedGap.FlowTargetHull.WorldPosition, deltaTime, maxDistance: maxDistance); } } } @@ -3584,12 +3651,12 @@ namespace Barotrauma public override bool SteerThroughGap(Gap gap, Vector2 targetWorldPos, float deltaTime, float maxDistance = -1) { - wallTarget = null; - LatchOntoAI?.DeattachFromBody(reset: true, cooldown: 2); - Character.AnimController.ReleaseStuckLimbs(); bool success = base.SteerThroughGap(gap, targetWorldPos, deltaTime, maxDistance); if (success) { + wallTarget = null; + LatchOntoAI?.DeattachFromBody(reset: true, cooldown: 2); + Character.AnimController.ReleaseStuckLimbs(); SteeringManager.SteeringAvoid(deltaTime, avoidLookAheadDistance, weight: 1); } IsSteeringThroughGap = success; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs index 39c698d32..1a55d8649 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs @@ -20,7 +20,6 @@ namespace Barotrauma private float reactTimer; private float unreachableClearTimer; private bool shouldCrouch; - public bool IsInsideCave { get; private set; } /// /// Resets each frame /// @@ -58,14 +57,14 @@ namespace Barotrauma private float obstacleRaycastTimer; private readonly float enemyCheckInterval = 0.2f; - private readonly float enemySpotDistanceOutside = 1500; + private readonly float enemySpotDistanceOutside = 800; private readonly float enemySpotDistanceInside = 1000; private float enemycheckTimer; /// - /// How far other characters can hear reports done by this character (e.g. reports for fires, intruders). Defaults to infinity. + /// How far other characters can hear reports done by this character (e.g. reports for fires, intruders). /// - public float ReportRange { get; set; } = float.PositiveInfinity; + public float ReportRange { get; set; } private float _aimSpeed = 1; public float AimSpeed @@ -167,6 +166,7 @@ namespace Barotrauma objectiveManager = new AIObjectiveManager(c); reactTimer = GetReactionTime(); SortTimer = Rand.Range(0f, sortObjectiveInterval); + ReportRange = Character.IsOnPlayerTeam ? float.PositiveInfinity : 1000; } public override void Update(float deltaTime) @@ -306,7 +306,7 @@ namespace Barotrauma UseIndoorSteeringOutside = false; } - if (Character.Submarine == null || !IsOnFriendlyTeam(Character.TeamID, Character.Submarine.TeamID) && !Character.IsEscorted) + if (Character.Submarine == null || Character.IsOnPlayerTeam && !Character.IsEscorted && !IsOnFriendlyTeam(Character.TeamID, Character.Submarine.TeamID)) { // Spot enemies while staying outside or inside an enemy ship. // does not apply for escorted characters, such as prisoners or terrorists who have their own behavior @@ -327,9 +327,13 @@ namespace Barotrauma float dist = toTarget.LengthSquared(); float maxDistance = Character.Submarine == null ? enemySpotDistanceOutside : enemySpotDistanceInside; if (dist > maxDistance * maxDistance) { continue; } - Vector2 forward = VectorExtensions.Forward(Character.AnimController.Collider.Rotation); - forward.X *= Character.AnimController.Dir; - if (Vector2.Dot(toTarget, forward) < 0.2f) { continue; } + if (EnemyAIController.IsLatchedToSomeoneElse(c, Character)) { continue; } + var head = Character.AnimController.GetLimb(LimbType.Head); + if (head == null) { continue; } + float rotation = head.body.TransformedRotation; + Vector2 forward = VectorExtensions.Forward(rotation); + float angle = MathHelper.ToDegrees(VectorExtensions.Angle(toTarget, forward)); + if (angle > 70) { continue; } if (!Character.CanSeeCharacter(c)) { continue; } if (dist < closestDistance || closestEnemy == null) { @@ -344,8 +348,6 @@ namespace Barotrauma } } } - - IsInsideCave = Character.CurrentHull == null && Level.Loaded?.Caves.FirstOrDefault(c => c.Area.Contains(Character.WorldPosition)) is Level.Cave; if (UseIndoorSteeringOutside || Character.CurrentHull?.Submarine != null || hasValidPath || IsCloseEnoughToTarget(steeringBuffer)) { @@ -1242,7 +1244,7 @@ namespace Barotrauma { //if the other character did not witness the attack, and the character is not within report range (or capable of reporting) //don't react to the attack - if (Character.IsDead || Character.IsUnconscious || !CheckReportRange(Character, otherCharacter, ReportRange)) + if (Character.IsDead || Character.IsUnconscious || otherCharacter.TeamID != Character.TeamID || !CheckReportRange(Character, otherCharacter, ReportRange)) { continue; } @@ -1259,8 +1261,8 @@ namespace Barotrauma { if (Character.Submarine == null) { - // Outside -> don't react. - return AIObjectiveCombat.CombatMode.None; + // Outside + return attacker.Submarine == null ? AIObjectiveCombat.CombatMode.Defensive : AIObjectiveCombat.CombatMode.Retreat; } if (!Character.Submarine.GetConnectedSubs().Contains(attacker.Submarine)) { @@ -1852,7 +1854,7 @@ namespace Barotrauma bool ignoreFire = objectiveManager.CurrentOrder is AIObjectiveExtinguishFires extinguishOrder && extinguishOrder.Priority > 0 || objectiveManager.HasActiveObjective(); bool ignoreWater = HasDivingSuit(character); bool ignoreOxygen = ignoreWater || HasDivingMask(character); - bool ignoreEnemies = ObjectiveManager.IsCurrentOrder() || ObjectiveManager.GetActiveObjectives().Any(); + bool ignoreEnemies = ObjectiveManager.IsCurrentOrder() || ObjectiveManager.IsCurrentObjective(); float safety = CalculateHullSafety(hull, visibleHulls, character, ignoreWater, ignoreOxygen, ignoreFire, ignoreEnemies); if (isCurrentHull) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCleanupItems.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCleanupItems.cs index c47cc92f8..b84bf63ba 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCleanupItems.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCleanupItems.cs @@ -52,7 +52,7 @@ namespace Barotrauma // The validity changes when a character picks the item up. if (!IsValidTarget(target, character, checkInventory: true)) { return Objectives.ContainsKey(target) && IsItemInsideValidSubmarine(target, character); } if (target.CurrentHull.FireSources.Count > 0) { return false; } - // Don't repair items in rooms that have enemies inside. + // Don't clean up items in rooms that have enemies inside. if (Character.CharacterList.Any(c => c.CurrentHull == target.CurrentHull && !HumanAIController.IsFriendly(c) && HumanAIController.IsActive(c))) { return false; } return true; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs index 0fa953f73..3de6f2a28 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs @@ -117,7 +117,10 @@ namespace Barotrauma private float AimSpeed => HumanAIController.AimSpeed; private float AimAccuracy => HumanAIController.AimAccuracy; - private bool EnemyIsClose() => Enemy != null && Enemy.CurrentHull != null && HumanAIController.VisibleHulls.Contains(Enemy.CurrentHull) && Math.Abs(character.WorldPosition.X - Enemy.WorldPosition.X) < 300; + private bool IsEnemyCloserThan(float margin) => + Enemy != null && Enemy.CurrentHull != null && + character.InWater && Vector2.DistanceSquared(character.WorldPosition, Enemy.WorldPosition) < margin * margin || + HumanAIController.VisibleHulls.Contains(Enemy.CurrentHull) && Math.Abs(character.WorldPosition.X - Enemy.WorldPosition.X) < margin; public AIObjectiveCombat(Character character, Character enemy, CombatMode mode, AIObjectiveManager objectiveManager, float priorityModifier = 1, float coolDown = 10.0f) : base(character, objectiveManager, priorityModifier) @@ -144,12 +147,19 @@ namespace Barotrauma Mode = CombatMode.Retreat; } spreadTimer = Rand.Range(-10f, 10f); + SetAimTimer(Rand.Range(1f, 1.5f) / AimSpeed); HumanAIController.SortTimer = 0; } protected override float GetPriority() { - if (character.TeamID == CharacterTeamType.FriendlyNPC && Enemy != null) + if (Enemy == null) + { + Priority = 0; + Abandon = true; + return Priority; + } + if (character.TeamID == CharacterTeamType.FriendlyNPC) { if (Enemy.Submarine == null || (Enemy.Submarine.TeamID != character.TeamID && Enemy.Submarine != character.Submarine)) { @@ -160,6 +170,13 @@ namespace Barotrauma } float damageFactor = MathUtils.InverseLerp(0.0f, 5.0f, character.GetDamageDoneByAttacker(Enemy) / 100.0f); Priority = TargetEliminated ? 0 : Math.Min((95 + damageFactor) * PriorityModifier, 100); + if (Priority > 0) + { + if (EnemyAIController.IsLatchedToSomeoneElse(Enemy, character)) + { + Priority = 0; + } + } return Priority; } @@ -366,7 +383,7 @@ namespace Barotrauma } } } - bool isAllowedToSeekWeapons = character.CurrentHull != null && !EnemyIsClose() && character.TeamID != CharacterTeamType.FriendlyNPC && IsOffensiveOrArrest; + bool isAllowedToSeekWeapons = character.CurrentHull != null && !IsEnemyCloserThan(300) && character.IsOnPlayerTeam && IsOffensiveOrArrest; if (!isAllowedToSeekWeapons) { if (WeaponComponent == null) @@ -418,9 +435,16 @@ namespace Barotrauma onCompleted: () => RemoveSubObjective(ref seekWeaponObjective), onAbandon: () => { - SpeakNoWeapons(); RemoveSubObjective(ref seekWeaponObjective); - Mode = CombatMode.Retreat; + if (Weapon == null) + { + SpeakNoWeapons(); + Mode = CombatMode.Retreat; + } + else + { + Mode = CombatMode.Defensive; + } }); } } @@ -478,13 +502,25 @@ namespace Barotrauma weaponComponent = null; float bestPriority = 0; float lethalDmg = -1; - bool enemyIsClose = EnemyIsClose(); + bool isAllowedToSeekWeapons = !IsEnemyCloserThan(300); + bool prioritizeMelee = IsEnemyCloserThan(50) || EnemyAIController.IsLatchedTo(Enemy, character); foreach (var weapon in weaponList) { float priority = weapon.CombatPriority; + if (prioritizeMelee) + { + if (weapon is MeleeWeapon) + { + priority *= 5; + } + else + { + priority /= 2; + } + } if (!weapon.IsLoaded(character)) { - if (weapon is RangedWeapon && enemyIsClose) + if (weapon is RangedWeapon && !isAllowedToSeekWeapons) { // Close to the enemy. Ignore weapons that don't have any ammunition (-> Don't seek ammo). continue; @@ -693,7 +729,7 @@ namespace Barotrauma var slots = Weapon.AllowedSlots.Where(s => IsHandSlotType(s)); if (character.Inventory.TryPutItem(Weapon, character, slots)) { - aimTimer = Rand.Range(0.2f, 0.4f) / AimSpeed; + SetAimTimer(Rand.Range(0.2f, 0.4f) / AimSpeed); } else { @@ -1014,7 +1050,7 @@ namespace Barotrauma } if (!canSeeTarget) { - aimTimer = Rand.Range(0.2f, 0.4f) / AimSpeed; + SetAimTimer(Rand.Range(0.2f, 0.4f) / AimSpeed); return; } if (Weapon.RequireAimToUse) @@ -1074,7 +1110,7 @@ namespace Barotrauma else if (!character.IsFacing(Enemy.WorldPosition)) { // Don't do the facing check if we are close to the target, because it easily causes the character to get stuck here when it flips around. - aimTimer = Rand.Range(1f, 1.5f) / AimSpeed; + SetAimTimer(Rand.Range(1f, 1.5f) / AimSpeed); } } else @@ -1190,5 +1226,7 @@ namespace Barotrauma } } } + + private void SetAimTimer(float newTimer) => aimTimer = Math.Max(aimTimer, newTimer); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveContainItem.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveContainItem.cs index e2ba2c782..26d2e8757 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveContainItem.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveContainItem.cs @@ -120,7 +120,7 @@ namespace Barotrauma } if (character.CanInteractWith(container.Item, checkLinked: false)) { - if (RemoveExisting || (RemoveExistingWhenNecessary && !container.Inventory.CanBePut(item))) + if (RemoveExisting || (RemoveExistingWhenNecessary && !container.Inventory.CanBePut(ItemToContain))) { HumanAIController.UnequipContainedItems(container.Item, predicate: RemoveExistingPredicate, unequipMax: RemoveMax); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFightIntruders.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFightIntruders.cs index 6cb902dbd..ce20e9de1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFightIntruders.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFightIntruders.cs @@ -70,6 +70,7 @@ namespace Barotrauma if (!targetCharactersInOtherSubs && character.Submarine.TeamID != target.Submarine.TeamID) { return false; } if (target.HasAbilityFlag(AbilityFlags.IgnoredByEnemyAI)) { return false; } if (target.IsArrested) { return false; } + if (EnemyAIController.IsLatchedToSomeoneElse(target, character)) { return false; } return true; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindDivingGear.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindDivingGear.cs index 5afa3665f..cd3abcc19 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindDivingGear.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindDivingGear.cs @@ -108,7 +108,7 @@ namespace Barotrauma AllowToFindDivingGear = false, AllowDangerousPressure = true, ConditionLevel = MIN_OXYGEN, - RemoveExisting = true + RemoveExistingWhenNecessary = true }; }, onAbandon: () => diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindSafety.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindSafety.cs index bb988f3c5..dc2a12da2 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindSafety.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindSafety.cs @@ -76,6 +76,10 @@ namespace Barotrauma // -> ignore find safety unless we need to find a diving gear Priority = 0; } + else if (objectiveManager.Objectives.Any(o => o is AIObjectiveCombat && o.Priority > 0)) + { + Priority = 0; + } Priority = MathHelper.Clamp(Priority, 0, 100); if (divingGearObjective != null && !divingGearObjective.IsCompleted && divingGearObjective.CanBeCompleted) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGetItem.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGetItem.cs index e6f4fb9b5..a0d14f7dc 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGetItem.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGetItem.cs @@ -401,6 +401,11 @@ namespace Barotrauma { if (!ownerItem.IsInteractable(character)) { continue; } if (!(ownerItem.GetComponent()?.HasRequiredItems(character, addMessage: false) ?? true)) { continue; } + //the item is inside an item inside an item (e.g. fuel tank in a welding tool in a cabinet -> reduce priority to prefer items that aren't inside a tool) + if (ownerItem != item.Container) + { + itemPriority *= 0.1f; + } } Vector2 itemPos = (rootInventoryOwner ?? item).WorldPosition; float yDist = Math.Abs(character.WorldPosition.Y - itemPos.Y); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGoTo.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGoTo.cs index 5f76c9663..dd250f619 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGoTo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGoTo.cs @@ -77,6 +77,9 @@ namespace Barotrauma // TODO: Currently we never check the visibility (to the end node), which is actually unintentional. // I don't think it has caused any issues so far, so let's keep defaulting to false for now, because the less we do raycasts the better. // However, if there are cases where the bots attempt to go through walls (select the end node that is behind an obstacle), we should set this true. + + // NOTE: This seemes to have caused an issue now Regalis11/Barotrauma#8067: namely, the bot was trying to use a waypoint that was obstructed by a shuttle + // because obstruction was only checked when checking visibility in PathFinder. Changed that so that obstructed nodes are no longer used. public bool CheckVisibility { get; set; } public bool IgnoreIfTargetDead { get; set; } public bool AllowGoingOutside { get; set; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveReturn.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveReturn.cs index 78d80ac98..31f2b4d75 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveReturn.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveReturn.cs @@ -7,11 +7,11 @@ namespace Barotrauma class AIObjectiveReturn : AIObjective { public override string Identifier { get; set; } = "return"; - private AIObjectiveGoTo moveInsideObjective, moveInCaveObjective, moveOutsideObjective; - private bool usingEscapeBehavior; - private bool isSteeringThroughGap; public Submarine ReturnTarget { get; } + private AIObjectiveGoTo moveInsideObjective, moveOutsideObjective; + private bool usingEscapeBehavior, isSteeringThroughGap; + public AIObjectiveReturn(Character character, Character orderGiver, AIObjectiveManager objectiveManager, float priorityModifier = 1.0f) : base(character, objectiveManager, priorityModifier) { ReturnTarget = GetReturnTarget(Submarine.MainSubs) ?? GetReturnTarget(Submarine.Loaded); @@ -112,7 +112,6 @@ namespace Barotrauma } if (targetHull != null) { - RemoveSubObjective(ref moveInCaveObjective); RemoveSubObjective(ref moveOutsideObjective); TryAddSubObjective(ref moveInsideObjective, constructor: () => new AIObjectiveGoTo(targetHull, character, objectiveManager) @@ -137,91 +136,41 @@ namespace Barotrauma IsCompleted = true; } } - else if (!isSteeringThroughGap && moveInCaveObjective == null && moveOutsideObjective == null) + else if (!isSteeringThroughGap && moveOutsideObjective == null) { - if (HumanAIController.IsInsideCave) + Hull targetHull = null; + float targetDistanceSquared = float.MaxValue; + bool targetIsAirlock = false; + foreach (var hull in ReturnTarget.GetHulls(false)) { - WayPoint closestOutsideWaypoint = null; - float closestDistance = float.MaxValue; - foreach (var w in WayPoint.WayPointList) + bool hullIsAirlock = hull.IsTaggedAirlock(); + if(hullIsAirlock || (!targetIsAirlock && hull.LeadsOutside(character))) { - if (w.Tunnel != null && w.Tunnel.Type == Level.TunnelType.Cave) { continue; } - if (w.linkedTo.None(l => l is WayPoint linkedWaypoint && linkedWaypoint.Tunnel?.Type == Level.TunnelType.Cave)) { continue; } - float distance = Vector2.DistanceSquared(character.WorldPosition, w.WorldPosition); - if (closestOutsideWaypoint == null || distance < closestDistance) + float distanceSquared = Vector2.DistanceSquared(character.WorldPosition, hull.WorldPosition); + if (targetHull == null || distanceSquared < targetDistanceSquared) { - closestOutsideWaypoint = w; - closestDistance = distance; + targetHull = hull; + targetDistanceSquared = distanceSquared; + targetIsAirlock = hullIsAirlock; } } - if (closestOutsideWaypoint != null) - { - RemoveSubObjective(ref moveInsideObjective); - RemoveSubObjective(ref moveOutsideObjective); - TryAddSubObjective(ref moveInCaveObjective, - constructor: () => new AIObjectiveGoTo(closestOutsideWaypoint, character, objectiveManager) - { - endNodeFilter = n => n.Waypoint == closestOutsideWaypoint, - AllowGoingOutside = true - }, - onCompleted: () => RemoveSubObjective(ref moveInCaveObjective), - onAbandon: () => Abandon = true); - } - else - { -#if DEBUG - DebugConsole.ThrowError("Error with a Return objective: no suitable main or side path node target found for 'moveOutsideObjective'"); -#endif - } + } + if (targetHull != null) + { + RemoveSubObjective(ref moveInsideObjective); + TryAddSubObjective(ref moveOutsideObjective, + constructor: () => new AIObjectiveGoTo(targetHull, character, objectiveManager) + { + AllowGoingOutside = true + }, + onCompleted: () => RemoveSubObjective(ref moveOutsideObjective), + onAbandon: () => Abandon = true); } else { - Hull targetHull = null; - float targetDistanceSquared = float.MaxValue; - bool targetIsAirlock = false; - foreach (var hull in ReturnTarget.GetHulls(false)) - { - bool hullIsAirlock = hull.IsTaggedAirlock(); - if(hullIsAirlock || (!targetIsAirlock && hull.LeadsOutside(character))) - { - float distanceSquared = Vector2.DistanceSquared(character.WorldPosition, hull.WorldPosition); - if (targetHull == null || distanceSquared < targetDistanceSquared) - { - targetHull = hull; - targetDistanceSquared = distanceSquared; - targetIsAirlock = hullIsAirlock; - } - } - } - if (targetHull != null) - { - RemoveSubObjective(ref moveInsideObjective); - RemoveSubObjective(ref moveInCaveObjective); - TryAddSubObjective(ref moveOutsideObjective, - constructor: () => new AIObjectiveGoTo(targetHull, character, objectiveManager) - { - AllowGoingOutside = true - }, - onCompleted: () => RemoveSubObjective(ref moveOutsideObjective), - onAbandon: () => Abandon = true); - } - else - { #if DEBUG - DebugConsole.ThrowError("Error with a Return objective: no suitable target for 'moveOutsideObjective'"); + DebugConsole.ThrowError("Error with a Return objective: no suitable target for 'moveOutsideObjective'"); #endif - } - } - } - else - { - if (HumanAIController.IsInsideCave) - { - RemoveSubObjective(ref moveOutsideObjective); - } - else - { - RemoveSubObjective(ref moveInCaveObjective); } } usingEscapeBehavior = shouldUseEscapeBehavior; @@ -249,7 +198,6 @@ namespace Barotrauma { base.Reset(); moveInsideObjective = null; - moveInCaveObjective = null; moveOutsideObjective = null; usingEscapeBehavior = false; isSteeringThroughGap = false; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PathFinder.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PathFinder.cs index 540e4135d..3a8186c25 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PathFinder.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PathFinder.cs @@ -333,7 +333,6 @@ namespace Barotrauma //if searching for a path inside the sub, make sure the waypoint is visible if (checkVisibility && isCharacter) { - if (node.Waypoint.isObstructed) { return false; } var body = Submarine.PickBody(rayStart, node.TempPosition, collisionCategory: Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionStairs); if (body != null) @@ -350,6 +349,7 @@ namespace Barotrauma { if (nodeFilter != null && !nodeFilter(node)) { return false; } if (startNodeFilter != null && !startNodeFilter(node)) { return false; } + if (node.Waypoint.isObstructed) { return false; } // Always check the visibility for the start node if (!IsWaypointVisible(node, start)) { return false; } if (node.IsBlocked()) { return false; } @@ -364,6 +364,7 @@ namespace Barotrauma { if (nodeFilter != null && !nodeFilter(node)) { return false; } if (endNodeFilter != null && !endNodeFilter(node)) { return false; } + if (node.Waypoint.isObstructed) { return false; } // Only check the visibility for the end node when allowed (fix leaks) if (!IsWaypointVisible(node, end, checkVisibility: checkVisibility)) { return false; } if (node.IsBlocked()) { return false; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs index 9d2017b81..e86ee571e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs @@ -776,8 +776,8 @@ namespace Barotrauma if (limbDiff.LengthSquared() < 0.0001f) { limbDiff = Rand.Vector(1.0f); } limbDiff = Vector2.Normalize(limbDiff); float mass = limbJoint.BodyA.Mass + limbJoint.BodyB.Mass; - limbJoint.LimbA.body.ApplyLinearImpulse(limbDiff * mass, (limbJoint.LimbA.SimPosition + limbJoint.LimbB.SimPosition) / 2.0f); - limbJoint.LimbB.body.ApplyLinearImpulse(-limbDiff * mass, (limbJoint.LimbA.SimPosition + limbJoint.LimbB.SimPosition) / 2.0f); + limbJoint.LimbA.body.ApplyLinearImpulse(limbDiff * Math.Min(mass, limbJoint.BodyA.Mass * 500), (limbJoint.LimbA.SimPosition + limbJoint.LimbB.SimPosition) / 2.0f); + limbJoint.LimbB.body.ApplyLinearImpulse(-limbDiff * Math.Min(mass, limbJoint.BodyB.Mass * 500), (limbJoint.LimbA.SimPosition + limbJoint.LimbB.SimPosition) / 2.0f); connectedLimbs.Clear(); checkedJoints.Clear(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Attack.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Attack.cs index 2e0ad881b..130bd9307 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Attack.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Attack.cs @@ -461,7 +461,7 @@ namespace Barotrauma ReloadAfflictions(element); } - public AttackResult DoDamage(Character attacker, IDamageable target, Vector2 worldPosition, float deltaTime, bool playSound = true, PhysicsBody sourceBody = null) + public AttackResult DoDamage(Character attacker, IDamageable target, Vector2 worldPosition, float deltaTime, bool playSound = true, PhysicsBody sourceBody = null, Limb sourceLimb = null) { Character targetCharacter = target as Character; if (OnlyHumans) @@ -486,10 +486,10 @@ namespace Barotrauma foreach (StatusEffect effect in statusEffects) { effect.sourceBody = sourceBody; - // TODO: do we want to apply the effect at the world position or the entity positions in each cases? -> go through also other cases where status effects are applied if (effect.HasTargetType(StatusEffect.TargetType.This)) { - effect.Apply(effectType, deltaTime, attacker, attacker, worldPosition); + // TODO: do we want to apply the effect at the world position or the entity positions in each cases? -> go through also other cases where status effects are applied + effect.Apply(effectType, deltaTime, attacker, sourceLimb ?? attacker as ISerializableEntity, worldPosition); } if (targetCharacter != null) { @@ -526,7 +526,7 @@ namespace Barotrauma } readonly List targets = new List(); - public AttackResult DoDamageToLimb(Character attacker, Limb targetLimb, Vector2 worldPosition, float deltaTime, bool playSound = true, PhysicsBody sourceBody = null) + public AttackResult DoDamageToLimb(Character attacker, Limb targetLimb, Vector2 worldPosition, float deltaTime, bool playSound = true, PhysicsBody sourceBody = null, Limb sourceLimb = null) { if (targetLimb == null) { @@ -553,7 +553,7 @@ namespace Barotrauma effect.sourceBody = sourceBody; if (effect.HasTargetType(StatusEffect.TargetType.This)) { - effect.Apply(effectType, deltaTime, attacker, attacker); + effect.Apply(effectType, deltaTime, attacker, sourceLimb ?? attacker as ISerializableEntity); } if (effect.HasTargetType(StatusEffect.TargetType.Character)) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs index 49ccb10ce..10ad82b34 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs @@ -503,7 +503,7 @@ namespace Barotrauma get { return cursorPosition; } set { - if (!MathUtils.IsValid(value)) return; + if (!MathUtils.IsValid(value)) { return; } cursorPosition = value; } } @@ -853,7 +853,7 @@ namespace Barotrauma } else { - return IsKnockedDown || LockHands || IsBot && TeamID != CharacterTeamType.FriendlyNPC; + return IsKnockedDown || LockHands || IsBot && IsOnPlayerTeam; } } set { canInventoryBeAccessed = value; } @@ -3596,8 +3596,12 @@ namespace Barotrauma foreach (LimbJoint joint in AnimController.LimbJoints) { if (!joint.CanBeSevered) { continue; } - // Limb A is where we usually create the joints from. Let's not allow severing when the "parent" limb is hit, or the head can pop off when we hit the torso, for example. - if (joint.LimbB != targetLimb) { continue; } + // Limb A is where we start creating the joint and LimbB is where the joint ends. + // Normally the joints have been created starting from the body, in which case we'd want to use LimbB e.g. to severe a hand when it's hit. + // But heads are a different case, because many characters have been created so that the head is first and then comes the rest of the body. + // If this is the case, we'll have to use LimbA to decapitate the creature when it's hit on the head. Otherwise decapitation could happen only when we hit the body, not the head. + var referenceLimb = targetLimb.type == LimbType.Head && targetLimb.Params.ID == 0 ? joint.LimbA : joint.LimbB; + if (referenceLimb != targetLimb) { continue; } float probability = severLimbsProbability; if (!IsDead) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs index d32e68cab..85d189f96 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs @@ -1134,6 +1134,10 @@ namespace Barotrauma { head.HairWithHatElement = hairs[hairWithHatIndex]; } + else + { + head.HairWithHatElement = null; + } } if (IsValidIndex(Head.BeardIndex, beards)) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionHusk.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionHusk.cs index 1f9ed8f30..4d42e7aa4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionHusk.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionHusk.cs @@ -246,7 +246,7 @@ namespace Barotrauma if (huskPrefab.ControlHusk) { #if SERVER - var client = GameMain.Server?.ConnectedClients.FirstOrDefault(c => c.CharacterInfo.Character == character); + var client = GameMain.Server?.ConnectedClients.FirstOrDefault(c => c.Character == character); if (client != null) { GameMain.Server.SetClientCharacter(client, husk); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/Skill.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/Skill.cs index c82419886..2baae954b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/Skill.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/Skill.cs @@ -18,7 +18,7 @@ namespace Barotrauma public void IncreaseSkill(float value, bool increasePastMax) { - level = MathHelper.Clamp(level + value, 0.0f, increasePastMax ? SkillSettings.Current.MaximumOlympianSkill : MaximumSkill); + level = MathHelper.Clamp(level + value, 0.0f, increasePastMax ? SkillSettings.Current.MaximumSkillWithTalents : MaximumSkill); } private Sprite icon; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Limb.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Limb.cs index 46577e017..978410599 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Limb.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Limb.cs @@ -556,6 +556,7 @@ namespace Barotrauma // TODO: We might need this or solve the cases where a limb is severed while holding on to an item //if (character.Params.CanInteract) { return false; } if (this == character.AnimController.MainLimb) { return false; } + bool canBeSevered = Params.CanBeSeveredAlive; if (character.AnimController.CanWalk) { switch (type) @@ -571,7 +572,7 @@ namespace Barotrauma return false; } } - return true; + return canBeSevered; } } @@ -1070,7 +1071,7 @@ namespace Barotrauma #endif if (damageTarget is Character targetCharacter && targetLimb != null) { - attackResult = attack.DoDamageToLimb(character, targetLimb, WorldPosition, 1.0f, playSound, body); + attackResult = attack.DoDamageToLimb(character, targetLimb, WorldPosition, 1.0f, playSound, body, this); } else { @@ -1080,7 +1081,7 @@ namespace Barotrauma } else { - attackResult = attack.DoDamage(character, damageTarget, WorldPosition, 1.0f, playSound, body); + attackResult = attack.DoDamage(character, damageTarget, WorldPosition, 1.0f, playSound, body, this); } } /*if (structureBody != null && attack.StickChance > Rand.Range(0.0f, 1.0f, Rand.RandSync.Server)) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Ragdoll/RagdollParams.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Ragdoll/RagdollParams.cs index 59481d908..3d2c05297 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Ragdoll/RagdollParams.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Ragdoll/RagdollParams.cs @@ -648,6 +648,9 @@ namespace Barotrauma [Serialize(1f, true, description:"How much damage must be done by the attack in order to be able to cut off the limb. Note that it's evaluated after the damage modifiers."), Editable(DecimalCount = 0, MinValueFloat = 0, MaxValueFloat = 1000)] public float MinSeveranceDamage { get; set; } + [Serialize(true, true, description: "Disable if you don't want to allow severing this joint while the creature is alive. Note: Does nothing if the 'Severance Probability Modifier' in the joint settings is 0 (default). Also note that the setting doesn't override certain limitations, e.g. severing the main limb, or legs of a walking creature is not allowed."), Editable] + public bool CanBeSeveredAlive { get; set; } + //how long it takes for severed limbs to fade out [Serialize(10f, true, "How long it takes for the severed limb to fade out"), Editable(MinValueFloat = 0, MaxValueFloat = 100, ValueStep = 1)] public float SeveredFadeOutTime { get; set; } = 10.0f; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/SkillSettings.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/SkillSettings.cs index c23cf4ad1..f023b51f7 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/SkillSettings.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/SkillSettings.cs @@ -96,8 +96,8 @@ namespace Barotrauma set; } - [Serialize(500.0f, true)] - public float MaximumOlympianSkill + [Serialize(200.0f, true)] + public float MaximumSkillWithTalents { get; set; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionCharacter.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionCharacter.cs index 407446785..cc14b466a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionCharacter.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionCharacter.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Xml.Linq; namespace Barotrauma.Abilities @@ -7,9 +8,26 @@ namespace Barotrauma.Abilities { private readonly List targetTypes; + private List conditionals = new List(); + public AbilityConditionCharacter(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) { targetTypes = ParseTargetTypes(conditionElement.GetAttributeStringArray("targettypes", new string[0], convertToLowerInvariant: true)); + + foreach (XElement subElement in conditionElement.Elements()) + { + if (subElement.Name.ToString().Equals("conditional", StringComparison.OrdinalIgnoreCase)) + { + foreach (XAttribute attribute in subElement.Attributes()) + { + if (PropertyConditional.IsValid(attribute)) + { + conditionals.Add(new PropertyConditional(attribute)); + } + } + break; + } + } } protected override bool MatchesConditionSpecific(AbilityObject abilityObject) @@ -18,7 +36,10 @@ namespace Barotrauma.Abilities { if (!(abilityCharacter.Character is Character character)) { return false; } if (!IsViableTarget(targetTypes, character)) { return false; } - + foreach (var conditional in conditionals) + { + if (!conditional.Matches(character)) { return false; } + } return true; } else diff --git a/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs b/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs index 0832d8e1d..cd253c368 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs @@ -978,7 +978,10 @@ namespace Barotrauma else { NewMessage("Level seed: " + Level.Loaded.Seed); - NewMessage("Level size: " + Level.Loaded.Size.X+"x"+ Level.Loaded.Size.Y); + NewMessage("Level generation params: " + Level.Loaded.GenerationParams.Identifier); + NewMessage("Adjacent locations: " + (Level.Loaded.StartLocation?.Type.Identifier ?? "none") + ", " + (Level.Loaded.StartLocation?.Type.Identifier ?? "none")); + NewMessage("Mirrored: " + Level.Loaded.Mirrored); + NewMessage("Level size: " + Level.Loaded.Size.X + "x" + Level.Loaded.Size.Y); NewMessage("Minimum main path width: " + (Level.Loaded.LevelData?.MinMainPathWidth?.ToString() ?? "unknown")); } },null)); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/NPCWaitAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/NPCWaitAction.cs index 27197a60a..751731fbc 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/NPCWaitAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/NPCWaitAction.cs @@ -17,7 +17,7 @@ namespace Barotrauma public NPCWaitAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element) { } - private List affectedNpcs = null; + private IEnumerable affectedNpcs; private AIObjectiveGoTo gotoObjective; @@ -25,7 +25,7 @@ namespace Barotrauma { if (isFinished) { return; } - affectedNpcs = ParentEvent.GetTargets(NPCTag).Where(c => c is Character).Select(c => c as Character).ToList(); + affectedNpcs = ParentEvent.GetTargets(NPCTag).Where(c => c is Character).Select(c => c as Character); foreach (var npc in affectedNpcs) { @@ -62,7 +62,7 @@ namespace Barotrauma { foreach (var npc in affectedNpcs) { - if (npc.Removed || !(npc.AIController is HumanAIController humanAiController)) { continue; } + if (npc.Removed || !(npc.AIController is HumanAIController)) { continue; } if (gotoObjective != null) { gotoObjective.Abandon = true; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs index f956de233..0fb1480a4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs @@ -796,8 +796,9 @@ namespace Barotrauma monsterStrength += enemyAI.CombatStrength; } - if (character.CurrentHull?.Submarine != null && - (character.CurrentHull.Submarine == Submarine.MainSub || Submarine.MainSub.DockedTo.Contains(character.CurrentHull.Submarine))) + if (character.CurrentHull?.Submarine?.Info != null && + (character.CurrentHull.Submarine == Submarine.MainSub || Submarine.MainSub.DockedTo.Contains(character.CurrentHull.Submarine)) && + character.CurrentHull.Submarine.Info.Type == SubmarineType.Player) { // Enemy onboard -> Crawler inside the sub adds 0.2 to enemy danger, Mudraptor 0.42 enemyDanger += enemyAI.CombatStrength / 500.0f; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/EscortMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/EscortMission.cs index b2e27d3ea..99bdf971e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/EscortMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/EscortMission.cs @@ -50,7 +50,8 @@ namespace Barotrauma return; } - int multiplier = CalculateScalingEscortedCharacterCount(); + // Disabled for now, because they make balancing the missions a pain. + int multiplier = 1;//CalculateScalingEscortedCharacterCount(); calculatedReward = Prefab.Reward * multiplier; string rewardText = $"‖color:gui.orange‖{string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0:N0}", GetReward(missionSub))}‖end‖"; @@ -319,31 +320,33 @@ namespace Barotrauma } } - foreach (Character character in characters) + if (!IsClient) { - if (character.Inventory == null) { continue; } - foreach (Item item in character.Inventory.AllItemsMod) + foreach (Character character in characters) { - //item didn't spawn with the characters -> drop it - if (!characterItems.Any(c => c.Value.Contains(item))) + if (character.Inventory == null) { continue; } + foreach (Item item in character.Inventory.AllItemsMod) { - item.Drop(character); + //item didn't spawn with the characters -> drop it + if (!characterItems.Any(c => c.Value.Contains(item))) + { + item.Drop(character); + } } } - } - // characters that survived will take their items with them, in case players tried to be crafty and steal them - // this needs to run here in case players abort the mission by going back home - // TODO: I think this might feel like a bug. - foreach (var characterItem in characterItems) - { - if (Survived(characterItem.Key) || !completed) + // characters that survived will take their items with them, in case players tried to be crafty and steal them + // this needs to run here in case players abort the mission by going back home + foreach (var characterItem in characterItems) { - foreach (Item item in characterItem.Value) + if (Survived(characterItem.Key) || !completed) { - if (!item.Removed) + foreach (Item item in characterItem.Value) { - item.Remove(); + if (!item.Removed) + { + item.Remove(); + } } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs index 3e8a55495..097d2ca16 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs @@ -30,6 +30,7 @@ namespace Barotrauma GameMain.Server?.UpdateMissionState(this); #endif ShowMessage(State); + OnMissionStateChanged?.Invoke(this); } } } @@ -145,7 +146,9 @@ namespace Barotrauma } private List delayedTriggerEvents = new List(); - + + public Action OnMissionStateChanged; + public Mission(MissionPrefab prefab, Location[] locations, Submarine sub) { System.Diagnostics.Debug.Assert(locations.Length == 2); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MissionPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MissionPrefab.cs index 0fa150677..c6b93aab0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MissionPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MissionPrefab.cs @@ -96,9 +96,9 @@ namespace Barotrauma public readonly bool RequireWreck; /// - /// The mission can only be received when travelling from Pair.First to Pair.Second + /// The mission can only be received when travelling from a location of the first type to a location of the second type /// - public readonly List> AllowedConnectionTypes; + public readonly List<(string from, string to)> AllowedConnectionTypes; /// /// The mission can only be received in these location types @@ -185,7 +185,14 @@ namespace Barotrauma tags = element.GetAttributeStringArray("tags", new string[0], convertToLowerInvariant: true); - Name = TextManager.Get("MissionName." + TextIdentifier, true) ?? element.GetAttributeString("name", ""); + Name = TextManager.Get("MissionName." + TextIdentifier, true); + if (Name == null) + { +#if DEBUG + DebugConsole.ThrowError($"Error in mission \"{Identifier}\" - could not find a name in localization files. Make sure the texts are present in the loca file or that the mission is set to share texts with another mission using the TextIdentifier attribute."); +#endif + Name = element.GetAttributeString("name", ""); + } Description = TextManager.Get("MissionDescription." + TextIdentifier, true) ?? element.GetAttributeString("description", ""); Reward = element.GetAttributeInt("reward", 1); AllowRetry = element.GetAttributeBool("allowretry", false); @@ -209,10 +216,20 @@ namespace Barotrauma FailureMessage = element.GetAttributeString("failuremessage", ""); } - SonarLabel = - TextManager.Get("MissionSonarLabel." + TextIdentifier, true) ?? - TextManager.Get("MissionSonarLabel." + element.GetAttributeString("sonarlabel", ""), true) ?? - element.GetAttributeString("sonarlabel", ""); + if (element.Attribute("sonarlabel") == null) + { + SonarLabel = + TextManager.Get("MissionSonarLabel." + TextIdentifier, true) ?? + TextManager.Get("missionsonarlabel.target"); + } + else + { + SonarLabel = + TextManager.Get("MissionSonarLabel." + element.GetAttributeString("sonarlabel", ""), true) ?? + TextManager.Get(element.GetAttributeString("sonarlabel", ""), true) ?? + element.GetAttributeString("sonarlabel", ""); + } + SonarIconIdentifier = element.GetAttributeString("sonaricon", ""); MultiplayerOnly = element.GetAttributeBool("multiplayeronly", false); @@ -224,7 +241,7 @@ namespace Barotrauma Headers = new List(); Messages = new List(); - AllowedConnectionTypes = new List>(); + AllowedConnectionTypes = new List<(string from, string to)>(); for (int i = 0; i < 100; i++) { @@ -260,9 +277,7 @@ namespace Barotrauma } else { - AllowedConnectionTypes.Add(new Pair( - subElement.GetAttributeString("from", ""), - subElement.GetAttributeString("to", ""))); + AllowedConnectionTypes.Add((subElement.GetAttributeString("from", "").ToLowerInvariant(), subElement.GetAttributeString("to", "").ToLowerInvariant())); } break; case "locationtypechange": @@ -358,13 +373,15 @@ namespace Barotrauma AllowedLocationTypes.Any(lt => lt.Equals(from.Type.Identifier, StringComparison.OrdinalIgnoreCase)); } - foreach (Pair allowedConnectionType in AllowedConnectionTypes) + foreach ((string fromType, string toType) in AllowedConnectionTypes) { - if (allowedConnectionType.First.Equals("any", StringComparison.OrdinalIgnoreCase) || - allowedConnectionType.First.Equals(from.Type.Identifier, StringComparison.OrdinalIgnoreCase)) + if (fromType.Equals("any", StringComparison.OrdinalIgnoreCase) || + fromType.Equals(from.Type.Identifier, StringComparison.OrdinalIgnoreCase) || + (fromType == "anyoutpost" && from.HasOutpost())) { - if (allowedConnectionType.Second.Equals("any", StringComparison.OrdinalIgnoreCase) || - allowedConnectionType.Second.Equals(to.Type.Identifier, StringComparison.OrdinalIgnoreCase)) + if (toType.Equals("any", StringComparison.OrdinalIgnoreCase) || + toType.Equals(to.Type.Identifier, StringComparison.OrdinalIgnoreCase) || + (toType == "anyoutpost" && to.HasOutpost())) { return true; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/PirateMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/PirateMission.cs index f71ef9992..bc69390bf 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/PirateMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/PirateMission.cs @@ -345,12 +345,13 @@ namespace Barotrauma protected override void UpdateMissionSpecific(float deltaTime) { - int newState = State; + if (state >= 2) { return; } + float sqrSonarRange = MathUtils.Pow2(Sonar.DefaultSonarRange); outsideOfSonarRange = Vector2.DistanceSquared(enemySub.WorldPosition, Submarine.MainSub.WorldPosition) > sqrSonarRange; - if (State < 2 && CheckWinState()) + if (CheckWinState()) { - newState = 2; + State = 2; } else { @@ -366,7 +367,7 @@ namespace Barotrauma } if (!outsideOfSonarRange || patrolPositions.None()) { - newState = 1; + State = 1; } break; case 1: @@ -391,14 +392,13 @@ namespace Barotrauma break; } } - State = newState; } private bool CheckWinState() => !IsClient && characters.All(m => DeadOrCaptured(m)); private bool DeadOrCaptured(Character character) { - return character == null || character.Removed || character.IsDead || (character.LockHands && character.Submarine == Submarine.MainSub); + return character == null || character.Removed || character.Submarine == null || (character.LockHands && character.Submarine == Submarine.MainSub) || character.IsIncapacitated; } public override void End() diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameAnalytics/GameAnalyticsManager.cs b/Barotrauma/BarotraumaShared/SharedSource/GameAnalytics/GameAnalyticsManager.cs index d4f099142..763f59a77 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameAnalytics/GameAnalyticsManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameAnalytics/GameAnalyticsManager.cs @@ -149,9 +149,17 @@ namespace Barotrauma internal void ConfigureAvailableResourceCurrencies(params ResourceCurrency[] customDimensions) => configureAvailableResourceCurrencies(customDimensions.Select(d => d.ToString()).ToArray()); + private readonly Action configureAvailableResourceItemTypes; + internal void ConfigureAvailableResourceItemTypes(params string[] resourceItemTypes) + => configureAvailableResourceItemTypes(resourceItemTypes); + private readonly Action setEnabledInfoLog; internal void SetEnabledInfoLog(bool enabled) => setEnabledInfoLog(enabled); + + private readonly Action setEnabledVerboseLog; + internal void SetEnabledVerboseLog(bool enabled) + => setEnabledVerboseLog(enabled); #endregion #region Data required to fetch methods via reflection @@ -292,10 +300,14 @@ namespace Barotrauma configureAvailableResourceCurrencies = Call(getMethod(nameof(ConfigureAvailableResourceCurrencies), new Type[] { typeof(string[]) })); + configureAvailableResourceItemTypes = Call(getMethod(nameof(ConfigureAvailableResourceItemTypes), + new Type[] { typeof(string[]) })); addResourceEvent = Call(getMethod(nameof(AddResourceEvent), new Type[] { resourceFlowTypeEnumType, typeof(string), typeof(float), typeof(string), typeof(string) })); setEnabledInfoLog = Call(getMethod(nameof(SetEnabledInfoLog), new Type[] { typeof(bool) })); + setEnabledVerboseLog = Call(getMethod(nameof(SetEnabledVerboseLog), + new Type[] { typeof(bool) })); onQuit = Call(getMethod("OnQuit", Array.Empty())); } @@ -450,6 +462,7 @@ namespace Barotrauma try { loadedImplementation?.SetEnabledInfoLog(true); + loadedImplementation?.SetEnabledVerboseLog(true); } catch (Exception e) { @@ -489,7 +502,10 @@ namespace Barotrauma + AssemblyInfo.GitRevision + ":" + buildConfiguration); loadedImplementation?.ConfigureAvailableCustomDimensions01(Enum.GetValues(typeof(CustomDimensions01)).Cast().ToArray()); + loadedImplementation?.ConfigureAvailableCustomDimensions02(Enum.GetValues(typeof(CustomDimensions02)).Cast().ToArray()); loadedImplementation?.ConfigureAvailableResourceCurrencies(Enum.GetValues(typeof(ResourceCurrency)).Cast().ToArray()); + loadedImplementation?.ConfigureAvailableResourceItemTypes( + Enum.GetValues(typeof(MoneySink)).Cast().Select(s => s.ToString()).Union(Enum.GetValues(typeof(MoneySource)).Cast().Select(s => s.ToString())).ToArray()); InitKeys(); @@ -521,7 +537,7 @@ namespace Barotrauma loadedImplementation?.AddDesignEvent("ContentPackage:" + sanitizedName); } packageNames.Sort(); - loadedImplementation?.AddDesignEvent("AllContentPackages:" + string.Join(", ", packageNames)); + loadedImplementation?.AddDesignEvent("AllContentPackages:" + string.Join(" ", packageNames)); } loadedImplementation?.AddDesignEvent("Language:" + GameMain.Config.Language); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/AutoItemPlacer.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/AutoItemPlacer.cs index 5d20749f1..e10ee447a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/AutoItemPlacer.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/AutoItemPlacer.cs @@ -220,10 +220,10 @@ namespace Barotrauma private static readonly (int quality, float commonness)[] qualityCommonnesses = new (int quality, float commonness)[Quality.MaxQuality + 1] { - (0, 0.85f), - (1, 0.125f), - (2, 0.0225f), - (3, 0.0025f), + (0, 1.0f), + (1, 0.0f), + (2, 0.0f), + (3, 0.0f), }; private static List SpawnItem(ItemPrefab itemPrefab, List containers, KeyValuePair validContainer, float difficultyModifier) diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/CargoManager.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/CargoManager.cs index 15fde73a8..6b20e104d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/CargoManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/CargoManager.cs @@ -16,11 +16,13 @@ namespace Barotrauma { public ItemPrefab ItemPrefab { get; } public int Quantity { get; set; } + public bool? IsStoreComponentEnabled { get; set; } public PurchasedItem(ItemPrefab itemPrefab, int quantity) { ItemPrefab = itemPrefab; Quantity = quantity; + IsStoreComponentEnabled = null; } } @@ -425,11 +427,13 @@ namespace Barotrauma } var item = new Item(pi.ItemPrefab, position, wp.Submarine); - itemContainer?.Inventory.TryPutItem(item, null); - itemSpawned(item); + itemContainer?.Inventory.TryPutItem(item, null); + + itemSpawned(item); #if SERVER Entity.Spawner?.CreateNetworkEvent(item, false); #endif + (itemContainer?.Item ?? item).CampaignInteractionType = CampaignMode.InteractionType.Cargo; static void itemSpawned(Item item) { Submarine sub = item.Submarine ?? item.GetRootContainer()?.Submarine; diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs index fc92c4147..21e7ecf93 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs @@ -81,7 +81,12 @@ namespace Barotrauma public double TotalPlayTime; public int TotalPassedLevels; - public enum InteractionType { None, Talk, Examine, Map, Crew, Store, Repair, Upgrade, PurchaseSub, MedicalClinic } + public enum InteractionType { None, Talk, Examine, Map, Crew, Store, Repair, Upgrade, PurchaseSub, MedicalClinic, Cargo } + + public static bool BlocksInteraction(InteractionType interactionType) + { + return interactionType != InteractionType.None && interactionType != InteractionType.Cargo; + } public readonly CargoManager CargoManager; public UpgradeManager UpgradeManager; diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs index cd5d9118f..817e4ff80 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs @@ -440,6 +440,7 @@ namespace Barotrauma GameAnalyticsManager.AddDesignEvent("FirstLaunch:" + eventId + tutorialMode.Tutorial.Identifier); } } + GameAnalyticsManager.AddDesignEvent($"{eventId}HintManager:{(HintManager.Enabled ? "Enabled" : "Disabled")}"); #endif if (GameMode is CampaignMode campaignMode) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ElectricalDischarger.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ElectricalDischarger.cs index 279fa113d..eb7b5381c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ElectricalDischarger.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ElectricalDischarger.cs @@ -118,6 +118,7 @@ namespace Barotrauma.Items.Components if (character != null && !CharacterUsable) { return false; } CurrPowerConsumption = powerConsumption; + Voltage = 0.0f; charging = true; timer = Duration; IsActive = true; @@ -141,7 +142,7 @@ namespace Barotrauma.Items.Components timer -= deltaTime; if (charging) { - if (GetAvailableBatteryPower() >= powerConsumption) + if (GetAvailableInstantaneousBatteryPower() >= powerConsumption) { var batteries = item.GetConnectedComponents(); float neededPower = powerConsumption; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Pickable.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Pickable.cs index 93001af68..f0812eb45 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Pickable.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Pickable.cs @@ -130,6 +130,11 @@ namespace Barotrauma.Items.Components } if (picker.Inventory.TryPutItemWithAutoEquipCheck(item, picker, allowedSlots)) { + if (item.CampaignInteractionType == CampaignMode.InteractionType.Cargo) + { + item.CampaignInteractionType = CampaignMode.InteractionType.None; + } + if (!picker.HeldItems.Contains(item) && item.body != null) { item.body.Enabled = false; } this.picker = picker; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Steering.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Steering.cs index 0707d3e1f..e56dcf7e9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Steering.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Steering.cs @@ -370,8 +370,22 @@ namespace Barotrauma.Items.Components item.SendSignal(new Signal((ConvertUnits.ToDisplayUnits(sub.Velocity.X * Physics.DisplayToRealWorldRatio) * 3.6f).ToString("0.0000", CultureInfo.InvariantCulture), sender: user), "current_velocity_x"); item.SendSignal(new Signal((ConvertUnits.ToDisplayUnits(sub.Velocity.Y * Physics.DisplayToRealWorldRatio) * -3.6f).ToString("0.0000", CultureInfo.InvariantCulture), sender: user), "current_velocity_y"); - item.SendSignal(new Signal((sub.WorldPosition.X * Physics.DisplayToRealWorldRatio).ToString("0.0000", CultureInfo.InvariantCulture), sender: user), "current_position_x"); - item.SendSignal(new Signal(sub.RealWorldDepth.ToString("0.0000", CultureInfo.InvariantCulture), sender: user), "current_position_y"); + Vector2 pos = new Vector2(sub.WorldPosition.X * Physics.DisplayToRealWorldRatio, sub.RealWorldDepth); + if (sonar != null && sonar.UseTransducers && sonar.CenterOnTransducers && sonar.ConnectedTransducers.Any()) + { + pos = Vector2.Zero; + foreach (var connectedTransducer in sonar.ConnectedTransducers) + { + pos += connectedTransducer.Item.WorldPosition; + } + pos /= sonar.ConnectedTransducers.Count(); + pos = new Vector2( + pos.X * Physics.DisplayToRealWorldRatio, + Level.Loaded?.GetRealWorldDepth(pos.Y) ?? (-pos.Y * Physics.DisplayToRealWorldRatio)); + } + + item.SendSignal(new Signal(pos.X.ToString("0.0000", CultureInfo.InvariantCulture), sender: user), "current_position_x"); + item.SendSignal(new Signal(pos.Y.ToString("0.0000", CultureInfo.InvariantCulture), sender: user), "current_position_y"); } // if our tactical AI pilot has left, revert back to maintaining position diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Power/Powered.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Power/Powered.cs index a5292d49c..07e8ab3f3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Power/Powered.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Power/Powered.cs @@ -302,17 +302,24 @@ namespace Barotrauma.Items.Components /// /// Returns the amount of power that can be supplied by batteries directly connected to the item /// - protected float GetAvailableBatteryPower() + protected float GetAvailableInstantaneousBatteryPower() { - var batteries = item.GetConnectedComponents(); - + if (item.Connections == null) { return 0.0f; } float availablePower = 0.0f; - foreach (PowerContainer battery in batteries) + foreach (Connection c in item.Connections) { - float batteryPower = Math.Min(battery.Charge * 3600.0f, battery.MaxOutPut); - availablePower += batteryPower; - } + var recipients = c.Recipients; + foreach (Connection recipient in recipients) + { + if (!recipient.IsPower || !recipient.IsOutput) { continue; } + var battery = recipient.Item?.GetComponent(); + if (battery == null) { continue; } + float maxOutputPerFrame = battery.MaxOutPut / 60.0f; + float framesPerMinute = 3600.0f; + availablePower += Math.Min(battery.Charge * framesPerMinute, maxOutputPerFrame); + } + } return availablePower; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs index ea96276e9..fb4d7bbd1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs @@ -196,6 +196,15 @@ namespace Barotrauma.Items.Components set; } + private float deactivationTimer; + + [Serialize(0f, false)] + public float DeactivationTime + { + get; + set; + } + public Body StickTarget { get; @@ -207,6 +216,9 @@ namespace Barotrauma.Items.Components get { return StickTarget != null; } } + private Category originalCollisionCategories; + private Category originalCollisionTargets; + public Projectile(Item item, XElement element) : base (item, element) { @@ -223,21 +235,26 @@ namespace Barotrauma.Items.Components public override void OnItemLoaded() { - if (Attack != null && Attack.DamageRange <= 0.0f && item.body != null) + if (item.body != null) { - switch (item.body.BodyShape) + if (Attack != null && Attack.DamageRange <= 0.0f) { - case PhysicsBody.Shape.Circle: - Attack.DamageRange = item.body.radius; - break; - case PhysicsBody.Shape.Capsule: - Attack.DamageRange = item.body.height / 2 + item.body.radius; - break; - case PhysicsBody.Shape.Rectangle: - Attack.DamageRange = new Vector2(item.body.width / 2.0f, item.body.height / 2.0f).Length(); - break; + switch (item.body.BodyShape) + { + case PhysicsBody.Shape.Circle: + Attack.DamageRange = item.body.radius; + break; + case PhysicsBody.Shape.Capsule: + Attack.DamageRange = item.body.height / 2 + item.body.radius; + break; + case PhysicsBody.Shape.Rectangle: + Attack.DamageRange = new Vector2(item.body.width / 2.0f, item.body.height / 2.0f).Length(); + break; + } + Attack.DamageRange = ConvertUnits.ToDisplayUnits(Attack.DamageRange); } - Attack.DamageRange = ConvertUnits.ToDisplayUnits(Attack.DamageRange); + originalCollisionCategories = item.body.CollisionCategories; + originalCollisionTargets = item.body.CollidesWith; } } @@ -259,6 +276,10 @@ namespace Barotrauma.Items.Components launchPos = simPosition; //set the rotation of the projectile again because dropping the projectile resets the rotation Item.SetTransform(simPosition, rotation + (Item.body.Dir * LaunchRotationRadians)); + if (DeactivationTime > 0) + { + deactivationTimer = DeactivationTime; + } } public void Shoot(Character user, Vector2 weaponPos, Vector2 spawnPos, float rotation, List ignoredBodies, bool createNetworkEvent, float damageMultiplier = 1f) @@ -585,7 +606,7 @@ namespace Barotrauma.Items.Components { if (dropper != null) { - Deactivate(); + DisableProjectileCollisions(); Unstick(); } base.Drop(dropper); @@ -593,6 +614,14 @@ namespace Barotrauma.Items.Components public override void Update(float deltaTime, Camera cam) { + if (DeactivationTime > 0) + { + deactivationTimer -= deltaTime; + if (deactivationTimer < 0) + { + DisableProjectileCollisions(); + } + } while (impactQueue.Count > 0) { var impact = impactQueue.Dequeue(); @@ -614,8 +643,12 @@ namespace Barotrauma.Items.Components } //projectiles with a stickjoint don't become inactive until the stickjoint is detached if (stickJoint == null && !item.body.FarseerBody.IsBullet) - { - IsActive = false; + { + IsActive = false; + if (DeactivationTime > 0 && deactivationTimer > 0) + { + DisableProjectileCollisions(); + } } if (stickJoint == null) { return; } @@ -715,7 +748,7 @@ namespace Barotrauma.Items.Components } if (hits.Count() >= MaxTargetsToHit || target.Body.UserData is VoronoiCell) { - Deactivate(); + DisableProjectileCollisions(); return true; } else @@ -864,7 +897,7 @@ namespace Barotrauma.Items.Components if (hits.Count() >= MaxTargetsToHit || hits.LastOrDefault()?.UserData is VoronoiCell) { - Deactivate(); + DisableProjectileCollisions(); } if (attackResult.AppliedDamageModifiers != null && @@ -934,18 +967,26 @@ namespace Barotrauma.Items.Components return true; } - private void Deactivate() + private void DisableProjectileCollisions() { item.body.FarseerBody.OnCollision -= OnProjectileCollision; - if ((item.Prefab.DamagedByProjectiles || item.Prefab.DamagedByMeleeWeapons) && item.Condition > 0) + if (originalCollisionCategories != Category.None && originalCollisionTargets != Category.None) { - item.body.CollisionCategories = Physics.CollisionCharacter; - item.body.CollidesWith = Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionPlatform | Physics.CollisionProjectile; + item.body.CollisionCategories = originalCollisionCategories; + item.body.CollidesWith = originalCollisionTargets; } else { - item.body.CollisionCategories = Physics.CollisionItem; - item.body.CollidesWith = Physics.CollisionWall | Physics.CollisionLevel; + if ((item.Prefab.DamagedByProjectiles || item.Prefab.DamagedByMeleeWeapons) && item.Condition > 0) + { + item.body.CollisionCategories = Physics.CollisionCharacter; + item.body.CollidesWith = Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionPlatform | Physics.CollisionProjectile; + } + else + { + item.body.CollisionCategories = Physics.CollisionItem; + item.body.CollidesWith = Physics.CollisionWall | Physics.CollisionLevel; + } } IgnoredBodies.Clear(); } @@ -996,7 +1037,14 @@ namespace Barotrauma.Items.Components } stickJoint = null; } - if (!item.body.FarseerBody.IsBullet) { IsActive = false; } + if (!item.body.FarseerBody.IsBullet) + { + IsActive = false; + if (DeactivationTime > 0 && deactivationTimer > 0) + { + DisableProjectileCollisions(); + } + } item.GetComponent()?.Snap(); if (stickTargetCharacter != null) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/CustomInterface.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/CustomInterface.cs index 9dc5b92af..8c8ed6036 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/CustomInterface.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/CustomInterface.cs @@ -236,6 +236,13 @@ namespace Barotrauma.Items.Components { ciElement.Connection = item.Connections?.FirstOrDefault(c => c.Name == ciElement.ConnectionName); } +#if SERVER + //make sure the clients know about the states of the checkboxes and text fields + if (item.Submarine == null || !item.Submarine.Loading) + { + item.CreateServerEvent(this); + } +#endif } partial void UpdateLabelsProjSpecific(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs index 6a818a572..0de5ab645 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs @@ -542,7 +542,7 @@ namespace Barotrauma.Items.Components public bool HasPowerToShoot() { - return GetAvailableBatteryPower() >= GetPowerRequiredToShoot(); + return GetAvailableInstantaneousBatteryPower() >= GetPowerRequiredToShoot(); } private bool TryLaunch(float deltaTime, Character character = null, bool ignorePower = false) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Wearable.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Wearable.cs index 2b4190bb4..93ada4ac4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Wearable.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Wearable.cs @@ -365,7 +365,16 @@ namespace Barotrauma.Items.Components { foreach (var allowedSlot in allowedSlots) { - if (allowedSlot != InvSlotType.Any && !character.Inventory.IsInLimbSlot(item, allowedSlot)) { return; } + if (allowedSlot == InvSlotType.Any) { continue; } + foreach (Enum value in Enum.GetValues(typeof(InvSlotType))) + { + var slotType = (InvSlotType)value; + if (slotType == InvSlotType.Any || slotType == InvSlotType.None) { continue; } + if (allowedSlot.HasFlag(slotType) && !character.Inventory.IsInLimbSlot(item, slotType)) + { + return; + } + } } picker = character; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs index 48504c6d1..cfe23ac81 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs @@ -98,6 +98,14 @@ namespace Barotrauma private readonly ItemInventory ownInventory; private Rectangle defaultRect; + /// + /// Unscaled rect + /// + public Rectangle DefaultRect + { + get { return defaultRect; } + set { defaultRect = value; } + } private Dictionary connections; @@ -645,10 +653,6 @@ namespace Barotrauma } } - private float buoyancySineMagnitude; - private float buoyancySineFrequency; - private float buoyancyRandomForce; - public bool FireProof { get { return Prefab.FireProof; } @@ -767,7 +771,7 @@ namespace Barotrauma get { return Position.X; } private set { - Move(new Vector2((value - Position.X) * Scale, 0.0f)); + Move(new Vector2(value * Scale, 0.0f)); } } /// @@ -778,7 +782,7 @@ namespace Barotrauma get { return Position.Y; } private set { - Move(new Vector2(0.0f, (value - Position.Y) * Scale)); + Move(new Vector2(0.0f, value * Scale)); } } @@ -851,7 +855,16 @@ namespace Barotrauma switch (subElement.Name.ToString().ToLowerInvariant()) { case "body": - body = new PhysicsBody(subElement, ConvertUnits.ToSimUnits(Position), Scale); + float density = subElement.GetAttributeFloat("density", 10.0f); + float minDensity = subElement.GetAttributeFloat("mindensity", density); + float maxDensity = subElement.GetAttributeFloat("maxdensity", density); + if (minDensity < maxDensity) + { + var rand = new Random(ID); + density = MathHelper.Lerp(minDensity, maxDensity, (float)rand.NextDouble()); + } + body = new PhysicsBody(subElement, ConvertUnits.ToSimUnits(Position), Scale, density); + string collisionCategory = subElement.GetAttributeString("collisioncategory", null); if ((Prefab.DamagedByProjectiles || Prefab.DamagedByMeleeWeapons) && Condition > 0) { @@ -879,9 +892,6 @@ namespace Barotrauma } body.FarseerBody.AngularDamping = subElement.GetAttributeFloat("angulardamping", 0.2f); body.FarseerBody.LinearDamping = subElement.GetAttributeFloat("lineardamping", 0.1f); - buoyancySineMagnitude = subElement.GetAttributeFloat("buoyancysinemagnitude", 0f); - buoyancySineFrequency = subElement.GetAttributeFloat("buoyancysinefrequency", 0f); - buoyancyRandomForce = subElement.GetAttributeFloat("buoyancyrandom", 0f); body.UserData = this; break; case "trigger": @@ -1600,7 +1610,7 @@ namespace Barotrauma float damageAmount = attack.GetItemDamage(deltaTime); Condition -= damageAmount; - if (damageAmount > 0) + if (damageAmount >= Prefab.OnDamagedThreshold) { ApplyStatusEffects(ActionType.OnDamaged, 1.0f); } @@ -1729,7 +1739,7 @@ namespace Barotrauma UpdateNetPosition(deltaTime); if (inWater) { - ApplyWaterForces(deltaTime); + ApplyWaterForces(); CurrentHull?.ApplyFlowForces(deltaTime, this); } } @@ -1818,24 +1828,16 @@ namespace Barotrauma transformDirty = false; } - private float sineTime; /// /// Applies buoyancy, drag and angular drag caused by water /// - private void ApplyWaterForces(float deltaTime) + private void ApplyWaterForces() { if (body.Mass <= 0.0f || body.Density <= 0.0f) { return; } - if (buoyancySineFrequency > 0) - { - if (sineTime >= float.MaxValue) - { - sineTime = float.MinValue; - } - sineTime += deltaTime * buoyancySineFrequency; - } + float forceFactor = 1.0f; if (CurrentHull != null) { @@ -1854,10 +1856,7 @@ namespace Barotrauma Vector2 drag = body.LinearVelocity * volume; - float sine = (float)Math.Sin(sineTime) * buoyancySineMagnitude; - Vector2 sineForce = Vector2.UnitY * sine * volume; - Vector2 randomForce = Vector2.UnitY * Rand.Range(-buoyancyRandomForce, buoyancyRandomForce, Rand.RandSync.Unsynced) * volume; - body.ApplyForce((uplift - drag) * 10.0f + sineForce + randomForce); + body.ApplyForce((uplift - drag) * 10.0f); //apply simple angular drag body.ApplyTorque(body.AngularVelocity * volume * -0.05f); @@ -1869,9 +1868,17 @@ namespace Barotrauma if (transformDirty) { return false; } var projectile = GetComponent(); - if (projectile?.IgnoredBodies != null) + if (projectile != null) { - if (projectile.IgnoredBodies.Contains(f2.Body)) { return false; } + //ignore character colliders (a projectile only hits limbs) + if (f2.CollisionCategories == Physics.CollisionCharacter && f2.Body.UserData is Character) + { + return false; + } + if (projectile.IgnoredBodies != null) + { + if (projectile.IgnoredBodies.Contains(f2.Body)) { return false; } + } } contact.GetWorldManifold(out Vector2 normal, out _); @@ -2216,7 +2223,7 @@ namespace Barotrauma foreach (Rectangle trigger in Prefab.Triggers) { transformedTrigger = TransformTrigger(trigger, true); - if (Submarine.RectContains(transformedTrigger, worldPosition)) return true; + if (Submarine.RectContains(transformedTrigger, worldPosition)) { return true; } } transformedTrigger = Rectangle.Empty; @@ -2230,7 +2237,10 @@ namespace Barotrauma public bool TryInteract(Character user, bool ignoreRequiredItems = false, bool forceSelectKey = false, bool forceUseKey = false) { - if (CampaignInteractionType != CampaignMode.InteractionType.None) { return false; } + if (CampaignMode.BlocksInteraction(CampaignInteractionType)) + { + return false; + } bool picked = false, selected = false; #if CLIENT diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs index c822cffe3..40cfa040f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs @@ -504,6 +504,9 @@ namespace Barotrauma set { impactTolerance = Math.Max(value, 0.0f); } } + [Serialize(0.0f, false)] + public float OnDamagedThreshold { get; set; } + [Serialize(0.0f, false)] public float SonarSize { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs index 6e6f1118a..bd22ea0ce 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs @@ -1723,7 +1723,7 @@ namespace Barotrauma { vertices[j] += position; } - var newChunk = new LevelWall(vertices, GenerationParams.WallColor, this); + var newChunk = new LevelWall(vertices, GenerationParams.WallColor, this, createBody: false); AbyssIslands.Add(new AbyssIsland(islandArea, newChunk.Cells)); continue; } @@ -1842,7 +1842,7 @@ namespace Barotrauma Rectangle allowedArea = new Rectangle(padding, padding, Size.X - padding * 2, Size.Y - padding * 2); int radius = Math.Max(caveSize.X, caveSize.Y) / 2; - var cavePos = FindPosAwayFromMainPath((parentTunnel.MinWidth + radius) * 1.5f, asCloseAsPossible: true, allowedArea); + var cavePos = FindPosAwayFromMainPath((parentTunnel.MinWidth + radius) * 1.25f, asCloseAsPossible: true, allowedArea); GenerateCave(caveParams, parentTunnel, cavePos, caveSize); @@ -2107,12 +2107,42 @@ namespace Barotrauma private Point FindPosAwayFromMainPath(double minDistance, bool asCloseAsPossible, Rectangle? limits = null) { - var validPoints = distanceField.FindAll(d => d.distance >= minDistance && (limits == null || limits.Value.Contains(d.point))); - validPoints.RemoveAll(d => d.point.Y < GetBottomPosition(d.point.X).Y + minDistance); - if (asCloseAsPossible || !validPoints.Any()) + var pointsAboveBottom = distanceField.FindAll(d => d.point.Y > GetBottomPosition(d.point.X).Y + minDistance); + if (pointsAboveBottom.Count == 0) + { + DebugConsole.ThrowError("Error in FindPosAwayFromMainPath: no valid positions above the bottom of the sea floor. Has the position of the sea floor been set too high up?"); + return distanceField[Rand.Int(distanceField.Count, Rand.RandSync.Server)].point; + } + + var validPoints = pointsAboveBottom.FindAll(d => d.distance >= minDistance && (limits == null || limits.Value.Contains(d.point))); + if (!validPoints.Any()) + { + DebugConsole.AddWarning("Failed to find a valid position far enough from the main path. Choosing the furthest possible position.\n" + Environment.StackTrace); + if (limits != null) + { + //try choosing something within the specified limits + validPoints = pointsAboveBottom.FindAll(d => limits.Value.Contains(d.point)); + } + if (!validPoints.Any()) + { + //couldn't find anything, let's just go with the furthest one + validPoints = pointsAboveBottom; + } + (Point position, double distance) furthestPoint = validPoints.First(); + foreach (var point in validPoints) + { + if (point.distance > furthestPoint.distance) + { + furthestPoint = point; + } + } + return furthestPoint.position; + } + + if (asCloseAsPossible) { if (!validPoints.Any()) { validPoints = distanceField; } - (Point position, double distance) closestPoint = validPoints.First(); + (Point position, double distance) closestPoint = validPoints.First(); foreach (var point in validPoints) { if (point.distance < closestPoint.distance) @@ -2172,7 +2202,7 @@ namespace Barotrauma { double xDiff = Math.Abs(point.X - ruinPos.X); double yDiff = Math.Abs(point.Y - ruinPos.Y); - if (xDiff < ruinSize || yDiff < ruinSize) + if (xDiff < ruinSize && yDiff < ruinSize) { shortestDistSqr = 0.0f; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelWall.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelWall.cs index 186c6bf20..67cb59004 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelWall.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelWall.cs @@ -57,7 +57,7 @@ namespace Barotrauma set { moveState = MathHelper.Clamp(value, 0.0f, MathHelper.TwoPi); } } - public LevelWall(List vertices, Color color, Level level, bool giftWrap = false) + public LevelWall(List vertices, Color color, Level level, bool giftWrap = false, bool createBody = true) { this.level = level; this.color = color; @@ -74,14 +74,17 @@ namespace Barotrauma wallCell.Edges[i].IsSolid = true; } Cells = new List() { wallCell }; - Body = CaveGenerator.GeneratePolygons(Cells, level, out triangles); - if (triangles.Count == 0) + if (createBody) { - throw new ArgumentException("Failed to generate a wall (not enough triangles). Original vertices: " + string.Join(", ", originalVertices.Select(v => v.ToString()))); - } + Body = CaveGenerator.GeneratePolygons(Cells, level, out triangles); + if (triangles.Count == 0) + { + throw new ArgumentException("Failed to generate a wall (not enough triangles). Original vertices: " + string.Join(", ", originalVertices.Select(v => v.ToString()))); + } #if CLIENT - GenerateVertices(); + GenerateVertices(); #endif + } } public LevelWall(List edgePositions, Vector2 extendAmount, Color color, Level level) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs index efec856ac..6901ddf25 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs @@ -1776,7 +1776,7 @@ namespace Barotrauma if (connectedWp.isObstructed) { continue; } Vector2 start = ConvertUnits.ToSimUnits(wp.WorldPosition); Vector2 end = ConvertUnits.ToSimUnits(connectedWp.WorldPosition); - var body = Submarine.PickBody(start, end, null, Physics.CollisionLevel, allowInsideFixture: false); + var body = PickBody(start, end, null, Physics.CollisionLevel, allowInsideFixture: false); if (body != null) { connectedWp.isObstructed = true; @@ -1803,7 +1803,7 @@ namespace Barotrauma foreach (var connection in node.connections) { var connectedWp = connection.Waypoint; - if (connectedWp.isObstructed) { continue; } + if (connectedWp.isObstructed || connectedWp.Ladders != null) { continue; } Vector2 start = ConvertUnits.ToSimUnits(wp.WorldPosition) - otherSub.SimPosition; Vector2 end = ConvertUnits.ToSimUnits(connectedWp.WorldPosition) - otherSub.SimPosition; var body = PickBody(start, end, null, Physics.CollisionWall, allowInsideFixture: true); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Physics/PhysicsBody.cs b/Barotrauma/BarotraumaShared/SharedSource/Physics/PhysicsBody.cs index 6a21d2d52..8baf4ad07 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Physics/PhysicsBody.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Physics/PhysicsBody.cs @@ -315,14 +315,33 @@ namespace Barotrauma set { FarseerBody.BodyType = value; } } + private Category _collisionCategories; + public Category CollisionCategories { - set { FarseerBody.CollisionCategories = value; } + set + { + _collisionCategories = value; + FarseerBody.CollisionCategories = value; + } + get + { + return _collisionCategories; + } } + private Category _collidesWith; public Category CollidesWith { - set { FarseerBody.CollidesWith = value; } + set + { + _collidesWith = value; + FarseerBody.CollidesWith = value; + } + get + { + return _collidesWith; + } } public PhysicsBody(XElement element, float scale = 1.0f) : this(element, Vector2.Zero, scale) { } @@ -383,12 +402,12 @@ namespace Barotrauma list.Add(this); } - public PhysicsBody(XElement element, Vector2 position, float scale = 1.0f) + public PhysicsBody(XElement element, Vector2 position, float scale = 1.0f, float? forceDensity = null) { float radius = ConvertUnits.ToSimUnits(element.GetAttributeFloat("radius", 0.0f)) * scale; float height = ConvertUnits.ToSimUnits(element.GetAttributeFloat("height", 0.0f)) * scale; float width = ConvertUnits.ToSimUnits(element.GetAttributeFloat("width", 0.0f)) * scale; - density = Math.Max(element.GetAttributeFloat("density", 10.0f), MinDensity); + density = Math.Max(forceDensity ?? element.GetAttributeFloat("density", 10.0f), MinDensity); CreateBody(width, height, radius, density); Enum.TryParse(element.GetAttributeString("bodytype", "Dynamic"), out BodyType bodyType); FarseerBody.BodyType = bodyType; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Screens/GameScreen.cs b/Barotrauma/BarotraumaShared/SharedSource/Screens/GameScreen.cs index f6ad777c5..93f0a0a79 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Screens/GameScreen.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Screens/GameScreen.cs @@ -245,6 +245,8 @@ namespace Barotrauma } cam.MoveCamera((float)deltaTime, allowZoom: GUI.MouseOn == null && !Inventory.IsMouseOnInventory); + + Character.Controlled?.UpdateLocalCursor(cam); #endif foreach (Submarine sub in Submarine.Loaded) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Serialization/SerializableProperty.cs b/Barotrauma/BarotraumaShared/SharedSource/Serialization/SerializableProperty.cs index 46ba2fae3..f5cf90bc5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Serialization/SerializableProperty.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Serialization/SerializableProperty.cs @@ -929,6 +929,21 @@ namespace Barotrauma (int)structure.Prefab.ScaledSize.Y); } } + else if (entity is Item item) + { + if (!item.ResizeHorizontal) + { + item.Rect = item.DefaultRect = new Rectangle(item.Rect.X, item.Rect.Y, + (int)(item.Prefab.Size.X * item.Prefab.Scale), + item.Rect.Height); + } + if (!item.ResizeVertical) + { + item.Rect = item.DefaultRect = new Rectangle(item.Rect.X, item.Rect.Y, + item.Rect.Width, + (int)(item.Prefab.Size.Y * item.Prefab.Scale)); + } + } } if (entity.SerializableProperties.TryGetValue(attributeName, out SerializableProperty property)) diff --git a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs index 3d0a7c719..87ed68291 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs @@ -133,7 +133,8 @@ namespace Barotrauma Target, Limb, MainLimb, - Collider + Collider, + Random } public readonly ItemPrefab ItemPrefab; @@ -1487,7 +1488,7 @@ namespace Barotrauma if (giveTalentInfo.GiveRandom) { - targetCharacter.GiveTalent(viableTalents.GetRandom(), true); + targetCharacter.GiveTalent(viableTalents.GetRandom(Rand.RandSync.Unsynced), true); } else { @@ -1542,7 +1543,7 @@ namespace Barotrauma var characters = new List(); for (int i = 0; i < characterSpawnInfo.Count; i++) { - Entity.Spawner.AddToSpawnQueue(characterSpawnInfo.SpeciesName, position + Rand.Vector(characterSpawnInfo.Spread, Rand.RandSync.Server) + characterSpawnInfo.Offset, + Entity.Spawner.AddToSpawnQueue(characterSpawnInfo.SpeciesName, position + Rand.Vector(characterSpawnInfo.Spread, Rand.RandSync.Unsynced) + characterSpawnInfo.Offset, onSpawn: newCharacter => { if (newCharacter.AIController is EnemyAIController enemyAi && @@ -1563,7 +1564,7 @@ namespace Barotrauma if (spawnItemRandomly) { - SpawnItem(spawnItems.GetRandom()); + SpawnItem(spawnItems.GetRandom(Rand.RandSync.Unsynced)); } else { @@ -1582,7 +1583,7 @@ namespace Barotrauma switch (chosenItemSpawnInfo.SpawnPosition) { case ItemSpawnInfo.SpawnPositionType.This: - Entity.Spawner.AddToSpawnQueue(chosenItemSpawnInfo.ItemPrefab, position + Rand.Vector(chosenItemSpawnInfo.Spread, Rand.RandSync.Server), onSpawned: newItem => + Entity.Spawner.AddToSpawnQueue(chosenItemSpawnInfo.ItemPrefab, position + Rand.Vector(chosenItemSpawnInfo.Spread, Rand.RandSync.Unsynced), onSpawned: newItem => { Projectile projectile = newItem.GetComponent(); if (projectile != null && user != null && sourceBody != null && entity != null) @@ -1597,7 +1598,7 @@ namespace Barotrauma } float spread = MathHelper.ToRadians(Rand.Range(-chosenItemSpawnInfo.AimSpread, chosenItemSpawnInfo.AimSpread)); var worldPos = sourceBody.Position; - float rotation = chosenItemSpawnInfo.Rotation; + float rotation = 0; if (user.Submarine != null) { worldPos += user.Submarine.Position; @@ -1614,11 +1615,14 @@ namespace Barotrauma rotation = sourceBody.TransformedRotation; break; case ItemSpawnInfo.SpawnRotationType.Collider: - rotation = user.AnimController.Collider.Rotation; + rotation = user.AnimController.Collider.Rotation + MathHelper.PiOver2; break; case ItemSpawnInfo.SpawnRotationType.MainLimb: rotation = user.AnimController.MainLimb.body.TransformedRotation; break; + case ItemSpawnInfo.SpawnRotationType.Random: + DebugConsole.ShowError("Random rotation is not supported for Projectiles."); + break; default: throw new NotImplementedException("Not implemented: " + chosenItemSpawnInfo.RotationType); } @@ -1631,19 +1635,37 @@ namespace Barotrauma if (body != null) { float rotation = MathHelper.ToRadians(chosenItemSpawnInfo.Rotation); - if (chosenItemSpawnInfo.RotationType == ItemSpawnInfo.SpawnRotationType.Limb) + switch (chosenItemSpawnInfo.RotationType) { - if (sourceBody != null) - { - rotation += sourceBody.Rotation; - } - } - else if (chosenItemSpawnInfo.RotationType == ItemSpawnInfo.SpawnRotationType.Collider) - { - if (entity is Character character) - { - rotation += character.AnimController.Collider.Rotation; - } + case ItemSpawnInfo.SpawnRotationType.Fixed: + if (sourceBody != null) + { + rotation = sourceBody.TransformRotation(chosenItemSpawnInfo.Rotation); + } + break; + case ItemSpawnInfo.SpawnRotationType.Limb: + if (sourceBody != null) + { + rotation += sourceBody.Rotation; + } + break; + case ItemSpawnInfo.SpawnRotationType.Collider: + if (entity is Character character) + { + rotation += character.AnimController.Collider.Rotation + MathHelper.PiOver2; + } + break; + case ItemSpawnInfo.SpawnRotationType.MainLimb: + if (entity is Character c) + { + rotation = c.AnimController.MainLimb.body.TransformedRotation; + } + break; + case ItemSpawnInfo.SpawnRotationType.Random: + rotation = Rand.Range(0f, MathHelper.TwoPi, Rand.RandSync.Unsynced); + break; + default: + throw new NotImplementedException("Not implemented: " + chosenItemSpawnInfo.RotationType); } body.SetTransform(newItem.SimPosition, rotation); body.ApplyLinearImpulse(Rand.Vector(1) * chosenItemSpawnInfo.Speed); diff --git a/Barotrauma/BarotraumaShared/Submarines/Azimuth.sub b/Barotrauma/BarotraumaShared/Submarines/Azimuth.sub index 05289e918..26e46ca4f 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Azimuth.sub and b/Barotrauma/BarotraumaShared/Submarines/Azimuth.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Barsuk.sub b/Barotrauma/BarotraumaShared/Submarines/Barsuk.sub index 9f09b8a2c..33061f035 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Barsuk.sub and b/Barotrauma/BarotraumaShared/Submarines/Barsuk.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Berilia.sub b/Barotrauma/BarotraumaShared/Submarines/Berilia.sub index 7d5ef8e3d..b87771309 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 081ca15a3..c41b15a1c 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Dugong.sub and b/Barotrauma/BarotraumaShared/Submarines/Dugong.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Herja.sub b/Barotrauma/BarotraumaShared/Submarines/Herja.sub index 422059a09..ab6a4f115 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Herja.sub and b/Barotrauma/BarotraumaShared/Submarines/Herja.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Humpback.sub b/Barotrauma/BarotraumaShared/Submarines/Humpback.sub index 6fde41765..0e37d0b39 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 b95f6b326..db4d8efad 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Kastrull.sub and b/Barotrauma/BarotraumaShared/Submarines/Kastrull.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/KastrullDrone.sub b/Barotrauma/BarotraumaShared/Submarines/KastrullDrone.sub index 55eb5b23d..0696aa7ab 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/KastrullDrone.sub and b/Barotrauma/BarotraumaShared/Submarines/KastrullDrone.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Orca.sub b/Barotrauma/BarotraumaShared/Submarines/Orca.sub index 116a2f410..50ce30820 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Orca.sub and b/Barotrauma/BarotraumaShared/Submarines/Orca.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Orca2.sub b/Barotrauma/BarotraumaShared/Submarines/Orca2.sub index 04e8414ce..24c960518 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Orca2.sub and b/Barotrauma/BarotraumaShared/Submarines/Orca2.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Remora.sub b/Barotrauma/BarotraumaShared/Submarines/Remora.sub index f7431caff..de98f9241 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Remora.sub and b/Barotrauma/BarotraumaShared/Submarines/Remora.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/RemoraDrone.sub b/Barotrauma/BarotraumaShared/Submarines/RemoraDrone.sub index 3147d053a..6361caafd 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/RemoraDrone.sub and b/Barotrauma/BarotraumaShared/Submarines/RemoraDrone.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Typhon.sub b/Barotrauma/BarotraumaShared/Submarines/Typhon.sub index f125fb933..0771f67c3 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 57f80a052..9f602a6e0 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Typhon2.sub and b/Barotrauma/BarotraumaShared/Submarines/Typhon2.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Winterhalter.sub b/Barotrauma/BarotraumaShared/Submarines/Winterhalter.sub index ba3210be5..b1b1becf2 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Winterhalter.sub and b/Barotrauma/BarotraumaShared/Submarines/Winterhalter.sub differ diff --git a/Barotrauma/BarotraumaShared/changelog.txt b/Barotrauma/BarotraumaShared/changelog.txt index c436200fd..834c8307c 100644 --- a/Barotrauma/BarotraumaShared/changelog.txt +++ b/Barotrauma/BarotraumaShared/changelog.txt @@ -1,3 +1,81 @@ +--------------------------------------------------------------------------------------------------------- +v0.16.3.0 +--------------------------------------------------------------------------------------------------------- + +Changes and additions: +- Optimized multiplayer store interface. +- Set supercapacitors on subs to be charging by default (unstable only). +- Added damage sounds to doors when they take 10 or more damage. +- Made nav terminals output the position of the transducer instead of the sub when using the new "center on transducer" setting (unstable only). +- Made outpost containers' sprite depths more consistent with other containers. +- Added tags to outpost medical compartments and made them linkable. +- Mission completion and failure icons are now displayed mid-round in the tab menu. +- Monster mission reward and difficulty level adjustments. +- Adjustments to the random monster spawn events. +- The new monster variants now also spawn in the multiplayer single mission -mode rounds. +- More polished monster variants (still WIP) +- Added a ranged attack for Crawler Broodmother. +- An overall balance and behavior fix pass on all monsters. Feedback is welcome. +- Moloch Pupa and Hammerhead Matriarch now also drop some loot. +- Adjusted the loot dropped by Crawler Broodmother, Giant Spineling, and Bonethresher. +- Crawler Eggs now deconstruct into suplhuric acid (and adrenaline gland, if they are not the smallest variants). +- Changed the small arms max stack sizes to either 6 or 12 when the clip size is 6. Makes it less tedious to use the extra ammunition. +- Tigerthreshers can now target doors. +- Monsters that try to get inside the sub, should now notice and priorize doors more overall. Affects e.g. Mudraptors, and to lesser extent Crawlers (and Tigerthreshers). +- Reduced the range where the bots can spot enemies outside of the sub. +- Improved bots' ability to return back to the submarine from caves. + +Talent nerfing: +- Removed the special stat boosts from "Olympian" (now it only increases the skill cap to 200). +- Halved the amount of damage "Still Kicking" heals (100 -> 50) +- Reduced gunshot wounds inflicted by handcannon. +- "True Potential" only has a chance of instakilling things smaller than a moloch. +- Halved damage buff from "Quickdraw" (80% -> 40%). +- Reduced skill gain from "Field Medic" (7 -> 3). +- Nerfed "Warlord" (20% chance of doubling the damage -> 5% chance). +- Reduced damage buff from "Expert Commando" (40% -> 20%). + +Fixes: +- Misc fixes to Barsuk, Herja, Winterhalter and Orca 2. +- Fixed crashing when dragging a docking port in the sub editor (unstable only). +- Fixed turrets being able to pull power from supercapacitor's power_in connection (unstable only). +- Fixed cursor position jittering when the sub is moving fast. +- Fixed discharge coils in Berilia and Orca 2 being connected to junction boxes instead of supercapacitors. +- Fixed wall bodies generating twice on abyss islands without caves, and the 1st generated wall not getting mirrored along with the level, leading to "invisible walls" in some areas of the abyss in mirrored levels. +- Pirates that are outside or unconscious count as being dead in the pirate missions. Fixes pirate missions failing if e.g. one of the pirates gets stranded outside their sub. +- Fixed some turrets being possible to power with batteries, even though the maximum power output of the batteries shouldn't be high enough. +- Fixed bots dropping the syringe inside PUCS when replacing the oxygen tank. +- Fixed spineling mission using "Giant Spineling" as the sonar label on all the spinelings (unstable only). +- Fixed bots sometimes failing to find a path to a docked shuttle or drone. +- Fixed sound effects not playing when a monster hits the sub's inner wall. +- Fixed correct sprite not being used in the great sea on the campaign map. +- Fixed "settings" text overlapping in the settings menu when using a very large text size. +- EventManager doesn't consider monsters in a docked non-player sub (e.g. abandoned outpost) to be "inside the sub". Fixes intensity always being at 100% in monster-infested outposts. +- Fixed outpost cabinet's sprite having empty space above it. +- Fixed inability to put syringe guns, toy hammers, welding tools, plasma cutters and sprayers in weapon holders. +- Fixed the SMG magazine recycle recipe requiring a full magazine instead of an empty one. +- Fixed monsters being unable to target inner walls when they are technically outside of the sub (= when there's no hull where they are). Such places, between the outer and the inner walls, can be found e.g. in Humpback. +- Fixed escort missions giving huge rewards in higher difficulty levels. +- Fixed NPCs reacting to combat between other characters when they shouldn't (e.g. when they don't witness it). +- Fixed the drug dealer in "heartofgold" fleeing from the other bandits. +- Fixed bandits (and pirates?) fleeing from the player after being in a combat for a while (unstable only). +- Fixed mudraptors being unable to hit the targets that are very near. +- Fixed tigerthreshers getting stuck on doors instead of attacking them (unstable only). +- Fixed monsters sometimes getting stuck near the Humpback's bottom railgun. +- Fixed monsters getting stuck on trying to reach open gaps that are on the other side of the sub. +- Fixed decapitating not working as it should. +- Fixed being able to grab hostile NPCs. +- Fixed bots not always reacting to monsters when they should be able to see them, while swimming outside. +- Fixed bots accidentally damaging friendly characters while trying to hit Swarmfeeders latched on to them. +- Fixed bots not using melee weapons when there's Swarmfeeders latched on to them. +- Fixed bots being able to shoot without any delay if they already have a weapon equipped. +- Fixed some monsters, like crawlers, trying to target walls with lots of gaps even though there are better targets closer to them. +- Fixed bots taking battery cells from portable pumps without considering their condition when acting on the Recharge Battery Cells order. + +Modding: +- Fixed crashing in multiplayer when there are spectators in the server and someone reaches the final stage of a modded husk affliction that allows remaining in control of the final form. +- Fixed wearables that are equipped into multiple slots (e.g. InnerClothes+OuterClothes) not being visible when worn. + --------------------------------------------------------------------------------------------------------- v0.16.2.0 --------------------------------------------------------------------------------------------------------- @@ -30,6 +108,8 @@ Fixes: - Fixed some hairs clipping through hats (unstable only). - Fixed bots returning to the sub even when they have an active wait order. Happened when the order was given inside and then when e.g. the character is controlled by the player, and then when the player changes the character, the bot falls to the "find safety objective", because it's not allowed to stay outside. - Fixed aggressive boarders not being aggressive enough inside the player sub, because they couldn't target things that were blocked by a wall. +- Fixed Molochs not doing anything when there's babies around. +- Fixed Mudraptors not staying together in swarms. - Adjusted threshers' targeting priorities. Tigerthreshers can now damage doors, but should do so only when they get inside. - Fixed crouching animation not appearing in MP when moving backwards while crouching (unstable only). - Fixed crashing when trying to send a messagebox to clients using console commands (unstable only).