From 3ca584f2fc2c9f3f6e9aeea67a2a3694f230b492 Mon Sep 17 00:00:00 2001 From: Juan Pablo Arce Date: Wed, 28 Sep 2022 21:30:52 -0300 Subject: [PATCH] v0.19.8.0 --- .../ClientSource/Characters/CharacterHUD.cs | 33 +- .../Characters/CharacterNetworking.cs | 47 +- .../Characters/Health/CharacterHealth.cs | 13 +- .../Events/EventActions/ConversationAction.cs | 10 +- .../EventActions/InventoryHighlightAction.cs | 58 ++ .../Events/EventActions/MessageBoxAction.cs | 10 +- .../EventActions/TutorialHighlightAction.cs | 18 +- .../EventActions/TutorialSegmentAction.cs | 14 +- .../Events/EventActions/UIHighlightAction.cs | 46 ++ .../ClientSource/Events/EventManager.cs | 4 +- .../BarotraumaClient/ClientSource/GUI/GUI.cs | 7 +- .../ClientSource/GUI/GUIComponent.cs | 3 +- .../ClientSource/GUI/GUIImage.cs | 4 +- .../ClientSource/GUI/GUIListBox.cs | 9 +- .../ClientSource/GUI/GUIMessageBox.cs | 2 +- .../ClientSource/GUI/GUIStyle.cs | 9 +- .../ClientSource/GUI/Store.cs | 16 +- .../ClientSource/GUI/TabMenu.cs | 18 +- .../ClientSource/GUI/UpgradeStore.cs | 12 +- .../ClientSource/GameSession/CargoManager.cs | 4 +- .../ClientSource/GameSession/CrewManager.cs | 30 +- .../GameModes/MultiPlayerCampaign.cs | 1 + .../GameModes/Tutorials/Tutorial.cs | 80 ++- .../ClientSource/Items/Components/Door.cs | 13 +- .../Items/Components/ItemComponent.cs | 5 +- .../Items/Components/ItemContainer.cs | 4 +- .../Components/Machines/Deconstructor.cs | 20 +- .../Items/Components/Machines/Fabricator.cs | 2 + .../Items/Components/Machines/MiniMap.cs | 8 +- .../Items/Components/Machines/Pump.cs | 4 +- .../Items/Components/Machines/Reactor.cs | 6 +- .../Items/Components/Machines/Sonar.cs | 24 +- .../Items/Components/Machines/Steering.cs | 10 +- .../Items/Components/Power/PowerContainer.cs | 3 +- .../Items/Components/Repairable.cs | 1 + .../Components/Signal/CustomInterface.cs | 2 +- .../ClientSource/Items/Item.cs | 17 +- .../ClientSource/Items/ItemPrefab.cs | 1 + .../BarotraumaClient/ClientSource/Map/Gap.cs | 4 - .../ClientSource/Networking/BanList.cs | 6 +- .../ClientSource/Networking/Client.cs | 7 +- .../ClientSource/Networking/GameClient.cs | 22 +- .../Networking/Primitives/Peers/ClientPeer.cs | 2 +- .../Primitives/Peers/SteamP2PClientPeer.cs | 3 + .../Primitives/Peers/SteamP2POwnerPeer.cs | 2 +- .../Networking/ServerList/PingUtils.cs | 35 +- .../Networking/ServerList/ServerInfo.cs | 12 +- .../ClientSource/Screens/CampaignUI.cs | 2 +- .../ClientSource/Screens/MainMenuScreen.cs | 80 ++- .../ClientSource/Screens/SubEditorScreen.cs | 2 +- .../ClientSource/Sounds/SoundManager.cs | 79 +-- .../BarotraumaClient/LinuxClient.csproj | 2 +- Barotrauma/BarotraumaClient/MacClient.csproj | 2 +- .../BarotraumaClient/WindowsClient.csproj | 2 +- .../BarotraumaServer/LinuxServer.csproj | 2 +- Barotrauma/BarotraumaServer/MacServer.csproj | 2 +- .../Characters/CharacterNetworking.cs | 11 +- .../ServerSource/Networking/GameServer.cs | 7 +- .../Peers/Server/LidgrenServerPeer.cs | 6 +- .../Primitives/Peers/Server/ServerPeer.cs | 9 +- .../Peers/Server/SteamP2PServerPeer.cs | 50 +- .../BarotraumaServer/WindowsServer.csproj | 2 +- .../Characters/AI/HumanAIController.cs | 7 +- .../AI/Objectives/AIObjectiveCombat.cs | 9 +- .../AI/Objectives/AIObjectiveGoTo.cs | 4 + .../AI/Objectives/AIObjectiveLoadItem.cs | 4 +- .../AI/Objectives/AIObjectiveLoadItems.cs | 2 +- .../AI/Objectives/AIObjectiveOperateItem.cs | 21 +- .../AI/Objectives/AIObjectiveRescue.cs | 2 +- .../SharedSource/Characters/AI/Order.cs | 7 +- .../Animation/HumanoidAnimController.cs | 18 +- .../SharedSource/Characters/Attack.cs | 9 +- .../SharedSource/Characters/Character.cs | 26 +- .../Characters/CharacterEventData.cs | 34 +- .../Characters/CharacterNetworking.cs | 6 +- .../Characters/Health/CharacterHealth.cs | 21 +- .../Characters/Health/DamageModifier.cs | 22 + .../CharacterAbilityTandemFire.cs | 8 +- .../SharedSource/DebugConsole.cs | 10 +- .../Events/EventActions/BinaryOptionAction.cs | 1 - .../EventActions/CheckAfflictionAction.cs | 10 +- .../EventActions/CheckConditionalAction.cs | 6 +- .../EventActions/CheckConnectionAction.cs | 26 +- .../Events/EventActions/CheckDataAction.cs | 24 +- .../Events/EventActions/CheckItemAction.cs | 92 ++- .../Events/EventActions/CheckOrderAction.cs | 14 +- .../EventActions/CheckSelectedAction.cs | 89 +++ .../EventActions/CheckSelectedItemAction.cs | 13 +- .../Events/EventActions/EventAction.cs | 2 +- .../Events/EventActions/GodModeAction.cs | 19 +- .../EventActions/InventoryHighlightAction.cs | 33 ++ .../Events/EventActions/MessageBoxAction.cs | 10 +- .../Events/EventActions/MissionAction.cs | 36 +- .../EventActions/NPCChangeTeamAction.cs | 42 +- .../Events/EventActions/NPCFollowAction.cs | 19 +- .../EventActions/NPCOperateItemAction.cs | 122 ++++ .../Events/EventActions/NPCWaitAction.cs | 4 +- .../Events/EventActions/SpawnAction.cs | 9 +- .../Events/EventActions/TutorialIconAction.cs | 6 +- .../EventActions/TutorialSegmentAction.cs | 10 +- .../Events/EventActions/UIHighlightAction.cs | 62 ++ .../Missions/AbandonedOutpostMission.cs | 21 +- .../Events/Missions/AlienRuinMission.cs | 15 +- .../Events/Missions/BeaconMission.cs | 20 +- .../Events/Missions/CargoMission.cs | 17 +- .../Events/Missions/CombatMission.cs | 10 +- .../Events/Missions/EscortMission.cs | 9 +- .../Events/Missions/GoToMission.cs | 21 +- .../Events/Missions/MineralMission.cs | 23 +- .../SharedSource/Events/Missions/Mission.cs | 29 +- .../Events/Missions/MissionPrefab.cs | 64 ++- .../Events/Missions/MonsterMission.cs | 26 +- .../Events/Missions/NestMission.cs | 19 +- .../Events/Missions/PirateMission.cs | 12 +- .../Events/Missions/SalvageMission.cs | 19 +- .../Events/Missions/ScanMission.cs | 34 +- .../SharedSource/Events/MonsterEvent.cs | 8 +- .../SharedSource/GameSession/CargoManager.cs | 4 +- .../SharedSource/GameSession/CrewManager.cs | 28 + .../GameModes/Tutorials/TutorialPrefab.cs | 8 + .../GameSession/UpgradeManager.cs | 8 +- .../Items/Components/DockingPort.cs | 16 +- .../Items/Components/ElectricalDischarger.cs | 2 +- .../Items/Components/Holdable/Throwable.cs | 6 + .../Items/Components/ItemContainer.cs | 12 +- .../Items/Components/Machines/Sonar.cs | 7 + .../Items/Components/Power/PowerTransfer.cs | 3 +- .../Items/Components/Power/Powered.cs | 38 +- .../SharedSource/Items/Components/Quality.cs | 13 +- .../Items/Components/Repairable.cs | 3 + .../BooleanOperatorComponent.cs | 4 + .../Items/Components/Signal/MotionSensor.cs | 11 +- .../SharedSource/Items/Components/Turret.cs | 10 +- .../SharedSource/Items/Item.cs | 17 +- .../SharedSource/Items/ItemPrefab.cs | 2 +- .../SharedSource/Map/Levels/Level.cs | 15 +- .../SharedSource/Map/LinkedSubmarine.cs | 4 + .../SharedSource/Map/Map/Location.cs | 10 +- .../SharedSource/Map/Map/Map.cs | 7 + .../SharedSource/Map/Submarine.cs | 2 +- .../SharedSource/Map/SubmarineBody.cs | 4 +- .../SharedSource/Networking/Client.cs | 1 - .../Primitives/NetworkPeerStructs.cs | 32 +- .../StatusEffects/DelayedEffect.cs | 8 +- .../StatusEffects/StatusEffect.cs | 2 +- Barotrauma/BarotraumaShared/changelog.txt | 544 ++++++++---------- CONTRIBUTING.md | 6 +- .../GA_SDK_NETSTANDARD.csproj | 2 +- LinuxSolution.sln | 15 + MacSolution.sln | 18 + README.md | 6 +- WindowsSolution.sln | 9 + 152 files changed, 1931 insertions(+), 1071 deletions(-) create mode 100644 Barotrauma/BarotraumaClient/ClientSource/Events/EventActions/InventoryHighlightAction.cs create mode 100644 Barotrauma/BarotraumaClient/ClientSource/Events/EventActions/UIHighlightAction.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckSelectedAction.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/InventoryHighlightAction.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/NPCOperateItemAction.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/UIHighlightAction.cs diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterHUD.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterHUD.cs index 9ef84bc9e..f4742ac64 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterHUD.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterHUD.cs @@ -399,12 +399,9 @@ namespace Barotrauma progressBar.Draw(spriteBatch, cam); } - void DrawInteractionIcon(Entity entity, string iconStyle) + void DrawInteractionIcon(Entity entity, Identifier iconStyle) { if (entity == null || entity.Removed) { return; } - var characterEntity = entity as Character; - if (characterEntity is not null && (characterEntity.IsDead || characterEntity.IsIncapacitated)) { return; } - if (GUIStyle.GetComponentStyle(iconStyle) is not GUIComponentStyle style) { return; } Hull currentHull = entity switch { @@ -412,20 +409,28 @@ namespace Barotrauma Item item => item.CurrentHull, _ => null }; - Range visibleRange = new Range(currentHull == Character.Controlled.CurrentHull ? 500.0f : 100.0f, float.PositiveInfinity); - if (characterEntity?.CampaignInteractionType == CampaignMode.InteractionType.Examine) + LocalizedString label = null; + if (entity is Character characterEntity) { - //TODO: we could probably do better than just hardcoding - //a check for InteractionType.Examine here. + if (characterEntity.IsDead || characterEntity.IsIncapacitated) { return; } + if (characterEntity?.CampaignInteractionType == CampaignMode.InteractionType.Examine) + { + //TODO: we could probably do better than just hardcoding + //a check for InteractionType.Examine here. - if (Vector2.DistanceSquared(character.Position, entity.Position) > 500f * 500f) { return; } + if (Vector2.DistanceSquared(character.Position, entity.Position) > 500f * 500f) { return; } - var body = Submarine.CheckVisibility(character.SimPosition, entity.SimPosition, ignoreLevel: true); - if (body != null && body.UserData != entity) { return; } + var body = Submarine.CheckVisibility(character.SimPosition, entity.SimPosition, ignoreLevel: true); + if (body != null && body.UserData != entity) { return; } - visibleRange = new Range(-100f, 500f); + visibleRange = new Range(-100f, 500f); + } + label = characterEntity?.Info?.Title; } + + if (GUIStyle.GetComponentStyle(iconStyle) is not GUIComponentStyle style) { return; } + float dist = Vector2.Distance(character.WorldPosition, entity.WorldPosition); float distFactor = 1.0f - MathUtils.InverseLerp(1000.0f, 3000.0f, dist); float alpha = MathHelper.Lerp(0.3f, 1.0f, distFactor); @@ -436,13 +441,13 @@ namespace Barotrauma visibleRange, style.GetDefaultSprite(), style.Color * alpha, - label: characterEntity?.Info?.Title); + label: label); } foreach (Character npc in Character.CharacterList) { if (npc.CampaignInteractionType == CampaignMode.InteractionType.None) { continue; } - DrawInteractionIcon(npc, "CampaignInteractionIcon." + npc.CampaignInteractionType); + DrawInteractionIcon(npc, ("CampaignInteractionIcon." + npc.CampaignInteractionType).ToIdentifier()); } if (GameMain.GameSession?.GameMode is TutorialMode tutorialMode && tutorialMode.Tutorial is not null) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterNetworking.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterNetworking.cs index 4ad12a2d4..4a9a99547 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterNetworking.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterNetworking.cs @@ -2,6 +2,7 @@ using Barotrauma.Networking; using Microsoft.Xna.Framework; using System; +using System.Collections.Immutable; using System.Linq; namespace Barotrauma @@ -141,7 +142,7 @@ namespace Barotrauma public virtual void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null) { - if (!(extraData is IEventData eventData)) { throw new Exception($"Malformed character event: expected {nameof(Character)}.{nameof(IEventData)}"); } + if (extraData is not IEventData eventData) { throw new Exception($"Malformed character event: expected {nameof(Character)}.{nameof(IEventData)}"); } msg.WriteRangedInteger((int)eventData.EventType, (int)EventType.MinValue, (int)EventType.MaxValue); switch (eventData) @@ -471,25 +472,11 @@ namespace Barotrauma break; case EventType.AddToCrew: GameMain.GameSession.CrewManager.AddCharacter(this); - CharacterTeamType teamID = (CharacterTeamType)msg.ReadByte(); - ushort itemCount = msg.ReadUInt16(); - for (int i = 0; i < itemCount; i++) - { - ushort itemID = msg.ReadUInt16(); - if (!(Entity.FindEntityByID(itemID) is Item item)) { continue; } - item.AllowStealing = true; - var wifiComponent = item.GetComponent(); - if (wifiComponent != null) - { - wifiComponent.TeamID = teamID; - } - var idCard = item.GetComponent(); - if (idCard != null) - { - idCard.TeamID = teamID; - idCard.SubmarineSpecificID = 0; - } - } + ReadItemTeamChange(msg, true); + break; + case EventType.RemoveFromCrew: + GameMain.GameSession.CrewManager.RemoveCharacter(this, removeInfo: true); + ReadItemTeamChange(msg, false); break; case EventType.UpdateExperience: int experienceAmount = msg.ReadInt32(); @@ -520,9 +507,27 @@ namespace Barotrauma info?.ChangeSavedStatValue(statType, statValue, statIdentifier, removeOnDeath, setValue: true); } break; - } msg.ReadPadBits(); + + static void ReadItemTeamChange(IReadMessage msg, bool allowStealing) + { + var itemTeamChange = INetSerializableStruct.Read(msg); + foreach (var itemID in itemTeamChange.ItemIds) + { + if (FindEntityByID(itemID) is not Item item) { continue; } + item.AllowStealing = allowStealing; + if (item.GetComponent() is { } wifiComponent) + { + wifiComponent.TeamID = itemTeamChange.TeamId; + } + if (item.GetComponent() is { } idCard) + { + idCard.TeamID = itemTeamChange.TeamId; + idCard.SubmarineSpecificID = 0; + } + } + } } public static Character ReadSpawnData(IReadMessage inc) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs index b5acfe2e8..983906c72 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs @@ -280,6 +280,7 @@ namespace Barotrauma cprButton = new GUIButton(new RectTransform(new Vector2(0.17f, 0.17f), characterIndicatorArea.RectTransform, Anchor.BottomLeft, scaleBasis: ScaleBasis.Smallest), text: "", style: "CPRButton") { + UserData = UIHighlightAction.ElementId.CPRButton, OnClicked = (button, userData) => { Character selectedCharacter = Character.Controlled?.SelectedCharacter; @@ -690,7 +691,7 @@ namespace Barotrauma { distortTimer = (distortTimer + deltaTime * distortSpeed) % MathHelper.TwoPi; Character.BlurStrength = (float)(Math.Sin(distortTimer) + 1.5f) * 0.25f * blurStrength; - Character.DistortStrength = (float)(Math.Sin(distortTimer) + 1.0f) * 0.1f * distortStrength; + Character.DistortStrength = (float)(Math.Sin(distortTimer) + 1.0f) * 0.05f * distortStrength; } else { @@ -1294,6 +1295,7 @@ namespace Barotrauma Dictionary treatmentSuitability = new Dictionary(); GetSuitableTreatments(treatmentSuitability, normalize: true, + user: Character.Controlled, ignoreHiddenAfflictions: true, limb: selectedLimbIndex == -1 ? null : Character.AnimController.Limbs.Find(l => l.HealthIndex == selectedLimbIndex)); @@ -1337,13 +1339,13 @@ namespace Barotrauma }; var innerFrame = new GUIButton(new RectTransform(Vector2.One, itemSlot.RectTransform, Anchor.Center, Pivot.Center, scaleBasis: ScaleBasis.Smallest), style: "SubtreeHeader") - { + { UserData = item, DisabledColor = Color.White * 0.1f, PlaySoundOnSelect = false, OnClicked = (btn, userdata) => { - if (!(userdata is ItemPrefab itemPrefab)) { return false; } + if (userdata is not ItemPrefab itemPrefab) { return false; } var item = Character.Controlled.Inventory.FindItem(it => it.Prefab == itemPrefab, recursive: true); if (item == null) { return false; } Limb targetLimb = Character.AnimController.Limbs.FirstOrDefault(l => l.HealthIndex == selectedLimbIndex); @@ -1900,6 +1902,7 @@ namespace Barotrauma public void ClientRead(IReadMessage inc) { newAfflictions.Clear(); + newPeriodicEffects.Clear(); byte afflictionCount = inc.ReadByte(); for (int i = 0; i < afflictionCount; i++) { @@ -1921,7 +1924,7 @@ namespace Barotrauma int periodicAfflictionCount = inc.ReadByte(); for (int j = 0; j < periodicAfflictionCount; j++) { - float periodicAfflictionTimer = inc.ReadRangedSingle(afflictionPrefab.PeriodicEffects[j].MinInterval, afflictionPrefab.PeriodicEffects[j].MaxInterval, 8); + float periodicAfflictionTimer = inc.ReadRangedSingle(0, afflictionPrefab.PeriodicEffects[j].MaxInterval, 8); newPeriodicEffects.Add((afflictionPrefab.PeriodicEffects[j], periodicAfflictionTimer)); } newAfflictions.Add((null, afflictionPrefab, afflictionStrength)); @@ -1991,13 +1994,13 @@ namespace Barotrauma //timer has wrapped around, apply the effect if (periodicEffect.timer - existingAffliction.PeriodicEffectTimers[periodicEffect.effect] > periodicEffect.effect.MinInterval / 2) { - existingAffliction.PeriodicEffectTimers[periodicEffect.effect] = periodicEffect.timer; foreach (StatusEffect effect in periodicEffect.effect.StatusEffects) { Limb targetLimb = Character.AnimController.Limbs.FirstOrDefault(l => l.HealthIndex == limbHealths.IndexOf(limb)); existingAffliction.ApplyStatusEffect(ActionType.OnActive, effect, deltaTime: 1.0f, this, targetLimb: targetLimb); } } + existingAffliction.PeriodicEffectTimers[periodicEffect.effect] = periodicEffect.timer; } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/EventActions/ConversationAction.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/EventActions/ConversationAction.cs index 065feb05f..865634dd1 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Events/EventActions/ConversationAction.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/EventActions/ConversationAction.cs @@ -92,11 +92,13 @@ namespace Barotrauma } } + float prevSize = conversationList.TotalSize; + List extraButtons = CreateConversation(conversationList, text, speaker, options, string.IsNullOrWhiteSpace(spriteIdentifier)); AssignActionsToButtons(extraButtons, lastMessageBox); RecalculateLastMessage(conversationList, true); - - conversationList.ScrollToEnd(0.5f); + conversationList.BarScroll = (prevSize - conversationList.Content.Rect.Height) / (conversationList.TotalSize - conversationList.Content.Rect.Height); + conversationList.ScrollToEnd(duration: 0.5f); lastMessageBox.SetBackgroundIcon(eventSprite); return; } @@ -112,7 +114,7 @@ namespace Barotrauma }; messageBox.OnAddedToGUIUpdateList += (GUIComponent component) => { - if (!(Screen.Selected is GameScreen)) { messageBox.Close(); } + if (Screen.Selected is not GameScreen) { messageBox.Close(); } }; lastMessageBox = messageBox; @@ -321,7 +323,7 @@ namespace Barotrauma AlwaysOverrideCursor = true }; - LocalizedString translatedText = TextManager.Get(text).Fallback(text); + LocalizedString translatedText = TextManager.ParseInputTypes(TextManager.Get(text)).Fallback(text); if (speaker?.Info != null && drawChathead) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/EventActions/InventoryHighlightAction.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/EventActions/InventoryHighlightAction.cs new file mode 100644 index 000000000..cdcede52f --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/EventActions/InventoryHighlightAction.cs @@ -0,0 +1,58 @@ +using Microsoft.Xna.Framework; +using System.Linq; + +namespace Barotrauma; + +partial class InventoryHighlightAction : EventAction +{ + private static readonly Color highlightColor = Color.Orange; + + partial void UpdateProjSpecific() + { + foreach (var target in ParentEvent.GetTargets(TargetTag)) + { + SetHighlight(target); + } + } + + private void SetHighlight(Entity entity) + { + if (entity is Item item) + { + int i = 0; + foreach (var itemContainer in item.GetComponents()) + { + if (ItemContainerIndex == -1 || i == ItemContainerIndex) + { + SetHighlight(itemContainer.Inventory); + } + i++; + } + } + else if (entity is Character c) + { + SetHighlight(c.Inventory); + } + } + + private void SetHighlight(Inventory inventory) + { + if (inventory?.visualSlots == null) { return; } + for (int i = 0; i < inventory.visualSlots.Length; i++) + { + if (inventory.visualSlots[i].HighlightTimer > 0) { continue; } + Item item = inventory.GetItemAt(i); + if (IsSuitableItem(item) || + (Recursive && item?.OwnInventory != null && item.OwnInventory.FindAllItems(it => IsSuitableItem(it), recursive: true).Any())) + { + inventory.visualSlots[i].ShowBorderHighlight(highlightColor, 0.5f, 0.5f, 0.1f); + } + } + } + + private bool IsSuitableItem(Item item) + { + return (ItemIdentifier.IsEmpty && item == null) || + (item != null && (item.Prefab.Identifier == ItemIdentifier || item.HasTag(ItemIdentifier))); + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/EventActions/MessageBoxAction.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/EventActions/MessageBoxAction.cs index e2bd6f963..b75a1af4a 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Events/EventActions/MessageBoxAction.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/EventActions/MessageBoxAction.cs @@ -8,20 +8,24 @@ partial class MessageBoxAction : EventAction { partial void UpdateProjSpecific() { - if (Type == ActionType.Create) + if (Type == ActionType.Create || Type == ActionType.ConnectObjective) { CreateMessageBox(); if (!ObjectiveTag.IsEmpty && GameMain.GameSession?.GameMode is TutorialMode tutorialMode) { - Identifier id = Identifier.IsEmpty ? Text : Identifier; + Identifier id = Identifier.IfEmpty(Text); var segment = Tutorial.Segment.CreateMessageBoxSegment(id, ObjectiveTag, CreateMessageBox); - tutorialMode.Tutorial?.TriggerTutorialSegment(segment); + tutorialMode.Tutorial?.TriggerTutorialSegment(segment, connectObjective: Type == ActionType.ConnectObjective); } } else if (Type == ActionType.Close) { GUIMessageBox.Close(Tag); } + else if (Type == ActionType.Clear) + { + GUIMessageBox.CloseAll(); + } } public void CreateMessageBox() diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/EventActions/TutorialHighlightAction.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/EventActions/TutorialHighlightAction.cs index beb14498a..732c1a480 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Events/EventActions/TutorialHighlightAction.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/EventActions/TutorialHighlightAction.cs @@ -4,7 +4,7 @@ namespace Barotrauma; partial class TutorialHighlightAction : EventAction { - private static readonly Color highlightColor = Color.OrangeRed; + private static readonly Color highlightColor = Color.Orange; partial void UpdateProjSpecific() { @@ -19,32 +19,32 @@ partial class TutorialHighlightAction : EventAction { if (entity is Item i) { - SetHighlight(i); + SetItemHighlight(i); } else if (entity is Structure s) { - SetHighlight(s); + SetStructureHighlight(s); } else if (entity is Character c) { - SetHighlight(c); + SetCharacterHighlight(c); } } - private void SetHighlight(Item item) + private void SetItemHighlight(Item item) { if (item.ExternalHighlight == State) { return; } - item.SpriteColor = (State) ? highlightColor : Color.White; + item.HighlightColor = State ? highlightColor : null; item.ExternalHighlight = State; } - private void SetHighlight(Structure structure) + private void SetStructureHighlight(Structure structure) { - structure.SpriteColor = (State) ? highlightColor : Color.White; + structure.SpriteColor = State ? highlightColor : Color.White; structure.ExternalHighlight = State; } - private void SetHighlight(Character character) + private void SetCharacterHighlight(Character character) { character.ExternalHighlight = State; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/EventActions/TutorialSegmentAction.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/EventActions/TutorialSegmentAction.cs index d39340d05..ab9b097d3 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Events/EventActions/TutorialSegmentAction.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/EventActions/TutorialSegmentAction.cs @@ -11,13 +11,13 @@ partial class TutorialSegmentAction : EventAction // Only need to create the segment when it's being triggered (otherwise the tutorial already has the segment instance) if (Type == SegmentActionType.Trigger) { - segment = Tutorial.Segment.CreateInfoBoxSegment(Id, ObjectiveTag, AutoPlayVideo ? Tutorials.AutoPlayVideo.Yes : Tutorials.AutoPlayVideo.No, + segment = Tutorial.Segment.CreateInfoBoxSegment(Identifier, ObjectiveTag, AutoPlayVideo ? Tutorials.AutoPlayVideo.Yes : Tutorials.AutoPlayVideo.No, new Tutorial.Segment.Text(TextTag, Width, Height, Anchor.Center), new Tutorial.Segment.Video(VideoFile, TextTag, Width, Height)); } else if (Type == SegmentActionType.Add) { - segment = Tutorial.Segment.CreateObjectiveSegment(Id, !ObjectiveTag.IsEmpty ? ObjectiveTag : Id); + segment = Tutorial.Segment.CreateObjectiveSegment(Identifier, !ObjectiveTag.IsEmpty ? ObjectiveTag : Identifier); } if (GameMain.GameSession?.GameMode is TutorialMode tutorialMode) { @@ -30,21 +30,21 @@ partial class TutorialSegmentAction : EventAction tutorial.TriggerTutorialSegment(segment); break; case SegmentActionType.Complete: - tutorial.CompleteTutorialSegment(Id); + tutorial.CompleteTutorialSegment(Identifier); break; case SegmentActionType.Remove: - tutorial.RemoveTutorialSegment(Id); + tutorial.RemoveTutorialSegment(Identifier); break; case SegmentActionType.CompleteAndRemove: - tutorial.CompleteTutorialSegment(Id); - tutorial.RemoveTutorialSegment(Id); + tutorial.CompleteTutorialSegment(Identifier); + tutorial.RemoveTutorialSegment(Identifier); break; } } } else { - DebugConsole.ShowError($"Error in event \"{ParentEvent.Prefab.Identifier}\": attempting to use TutorialSegmentAction during a non-Tutorial game mode!"); + DebugConsole.ThrowError($"Error in event \"{ParentEvent.Prefab.Identifier}\": attempting to use TutorialSegmentAction during a non-Tutorial game mode!"); } } } \ No newline at end of file diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/EventActions/UIHighlightAction.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/EventActions/UIHighlightAction.cs new file mode 100644 index 000000000..2826454ca --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/EventActions/UIHighlightAction.cs @@ -0,0 +1,46 @@ +using Microsoft.Xna.Framework; +using System.Linq; + +namespace Barotrauma; + +partial class UIHighlightAction : EventAction +{ + private static readonly Color highlightColor = Color.Orange; + + partial void UpdateProjSpecific() + { + bool useCircularFlash = false; + GUIComponent component = null; + + if (Id != ElementId.None) + { + component = GUI.GetAdditions().FirstOrDefault(c => Equals(Id, c.UserData)); + } + else if (!EntityIdentifier.IsEmpty) + { + component = GUI.GetAdditions().FirstOrDefault(c => + c.UserData is MapEntityPrefab mep && mep.Identifier == EntityIdentifier || c.UserData is MapEntity me && me.Prefab.Identifier == EntityIdentifier); + } + else if (!OrderIdentifier.IsEmpty) + { + useCircularFlash = true; + if (!OrderTargetTag.IsEmpty) + { + component = + GUI.GetAdditions().FirstOrDefault(c => + c.UserData is CrewManager.MinimapNodeData nodeData && nodeData.Order is Order order && + order.Identifier == OrderIdentifier && order.Option == OrderOption && order.TargetEntity is Item item && item.HasTag(OrderTargetTag)); + } + component ??= + GUI.GetAdditions().FirstOrDefault(c => c.UserData is Order order && order.Identifier == OrderIdentifier && order.Option == OrderOption) ?? + GUI.GetAdditions().FirstOrDefault(c => c.UserData is Order order && order.Identifier == OrderIdentifier) ?? + GUI.GetAdditions().FirstOrDefault(c => Equals(OrderCategory, c.UserData)); + } + + if (component != null && component.FlashTimer <= 0.0f) + { + component.Flash(highlightColor, useCircularFlash: useCircularFlash); + component.Bounce |= Bounce; + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/EventManager.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/EventManager.cs index 103e2cd2c..9093450ec 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Events/EventManager.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/EventManager.cs @@ -651,11 +651,11 @@ namespace Barotrauma break; case NetworkEventType.MISSION: Identifier missionIdentifier = msg.ReadIdentifier(); - + string missionName = msg.ReadString(); MissionPrefab? prefab = MissionPrefab.Prefabs.Find(mp => mp.Identifier == missionIdentifier); if (prefab != null) { - new GUIMessageBox(string.Empty, TextManager.GetWithVariable("missionunlocked", "[missionname]", prefab.Name), + new GUIMessageBox(string.Empty, TextManager.GetWithVariable("missionunlocked", "[missionname]", missionName), Array.Empty(), type: GUIMessageBox.Type.InGame, icon: prefab.Icon, relativeSize: new Vector2(0.3f, 0.15f), minSize: new Point(512, 128)) { IconColor = prefab.IconColor diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs index cbe1cb50d..c158fc21d 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs @@ -410,7 +410,7 @@ namespace Barotrauma { y += yStep; DrawString(spriteBatch, new Vector2(10, y), - "Sub pos: " + Submarine.MainSub.Position.ToPoint(), + "Sub pos: " + Submarine.MainSub.WorldPosition.ToPoint(), Color.White, Color.Black * 0.5f, 0, GUIStyle.SmallFont); } @@ -879,6 +879,11 @@ namespace Barotrauma } } } + + public static IEnumerable GetAdditions() + { + return additions; + } #endregion public static GUIComponent MouseOn { get; private set; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIComponent.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIComponent.cs index 73ba38a9c..926e5520b 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIComponent.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIComponent.cs @@ -486,7 +486,7 @@ namespace Barotrauma { if (bounceTimer > 3.0f || bounceDown) { - RectTransform.ScreenSpaceOffset = new Point(RectTransform.ScreenSpaceOffset.X, (int) -(bounceJump * 10f)); + RectTransform.ScreenSpaceOffset = new Point(RectTransform.ScreenSpaceOffset.X, (int) -(bounceJump * 15f * GUI.Scale)); if (!bounceDown) { bounceJump += deltaTime * 4; @@ -503,6 +503,7 @@ namespace Barotrauma bounceJump = 0.0f; bounceTimer = 0.0f; bounceDown = false; + Bounce = false; } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIImage.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIImage.cs index c19bcc378..ea44140e7 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIImage.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIImage.cs @@ -85,11 +85,11 @@ namespace Barotrauma get { return sprite; } set { - if (sprite == value) return; + if (sprite == value) { return; } sprite = value; sourceRect = value == null ? Rectangle.Empty : value.SourceRect; origin = value == null ? Vector2.Zero : value.size / 2; - if (scaleToFit) RecalculateScale(); + if (scaleToFit) { RecalculateScale(); } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIListBox.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIListBox.cs index b532b7900..71173d75d 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIListBox.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIListBox.cs @@ -601,16 +601,13 @@ namespace Barotrauma yield return CoroutineStatus.Success; } float t = 0.0f; - float startScroll = BarScroll * BarSize; + float startScroll = BarScroll; float distanceToTravel = ScrollBar.MaxValue - startScroll; - float progress = startScroll; float speed = distanceToTravel / duration; - - while (t < duration && !MathUtils.NearlyEqual(ScrollBar.MaxValue, progress)) + while (t < duration && !MathUtils.NearlyEqual(ScrollBar.MaxValue, BarScroll)) { t += CoroutineManager.DeltaTime; - progress += speed * CoroutineManager.DeltaTime; - BarScroll = progress; + BarScroll += speed * CoroutineManager.DeltaTime; yield return CoroutineStatus.Running; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIMessageBox.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIMessageBox.cs index b67fd7713..3aa1cff1c 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIMessageBox.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIMessageBox.cs @@ -207,7 +207,7 @@ namespace Barotrauma { InnerFrame.RectTransform.AbsoluteOffset = new Point(0, GameMain.GraphicsHeight); CanBeFocused = false; - AutoClose = true; + AutoClose = type == Type.InGame; GUIStyle.Apply(InnerFrame, "", this); var horizontalLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.98f, 0.95f), InnerFrame.RectTransform, Anchor.Center), diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIStyle.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIStyle.cs index 4737c85f7..07e976ada 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIStyle.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIStyle.cs @@ -160,8 +160,13 @@ namespace Barotrauma public static Point ItemFrameOffset => new Point(0, 3).Multiply(GUI.SlicedSpriteScale); - public static GUIComponentStyle GetComponentStyle(string name) - => ComponentStyles.TryGet(name, out var style) ? style : null; + public static GUIComponentStyle GetComponentStyle(string styleName) + { + return GetComponentStyle(styleName.ToIdentifier()); + } + + public static GUIComponentStyle GetComponentStyle(Identifier identifier) + => ComponentStyles.TryGet(identifier, out var style) ? style : null; public static void Apply(GUIComponent targetComponent, string styleName = "", GUIComponent parent = null) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/Store.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/Store.cs index f32e5de6e..eca7c4334 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/Store.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/Store.cs @@ -238,7 +238,7 @@ namespace Barotrauma errorId = "Store.SelectStore:StoreDoesntExist"; msg = $"Error selecting store with identifier \"{identifier}\" at {CurrentLocation}: store with the identifier doesn't exist at the location."; } - DebugConsole.ShowError(msg); + DebugConsole.LogError(msg); GameAnalyticsManager.AddErrorEventOnce(errorId, GameAnalyticsManager.ErrorSeverity.Error, msg); } } @@ -263,7 +263,7 @@ namespace Barotrauma } if (!msg.IsNullOrEmpty()) { - DebugConsole.ShowError(msg); + DebugConsole.LogError(msg); GameAnalyticsManager.AddErrorEventOnce(errorId, GameAnalyticsManager.ErrorSeverity.Error, msg); } } @@ -1250,7 +1250,7 @@ namespace Barotrauma } catch (NotImplementedException e) { - DebugConsole.ShowError($"Error getting item price: Uknown store tab type. {e.StackTrace.CleanupStackTrace()}"); + DebugConsole.LogError($"Error getting item price: Uknown store tab type. {e.StackTrace.CleanupStackTrace()}"); } } @@ -1808,7 +1808,7 @@ namespace Barotrauma { string errorMsg = $"Error creating a store quantity label text: unknown store tab.\n{e.StackTrace.CleanupStackTrace()}"; #if DEBUG - DebugConsole.ShowError(errorMsg); + DebugConsole.LogError(errorMsg); #else DebugConsole.AddWarning(errorMsg); #endif @@ -1882,7 +1882,7 @@ namespace Barotrauma } catch (NotImplementedException e) { - DebugConsole.ShowError($"Error getting item availability: Uknown store tab type. {e.StackTrace.CleanupStackTrace()}"); + DebugConsole.LogError($"Error getting item availability: Uknown store tab type. {e.StackTrace.CleanupStackTrace()}"); } if (list != null && list.Find(i => i.ItemPrefab == itemPrefab) is PurchasedItem item) { @@ -1962,7 +1962,7 @@ namespace Barotrauma } catch (NotImplementedException e) { - DebugConsole.ShowError($"Error adding an item to the shopping crate: Uknown store tab type. {e.StackTrace.CleanupStackTrace()}"); + DebugConsole.LogError($"Error adding an item to the shopping crate: Uknown store tab type. {e.StackTrace.CleanupStackTrace()}"); return false; } } @@ -1982,7 +1982,7 @@ namespace Barotrauma } catch (NotImplementedException e) { - DebugConsole.ShowError($"Error clearing the shopping crate: Uknown store tab type. {e.StackTrace.CleanupStackTrace()}"); + DebugConsole.LogError($"Error clearing the shopping crate: Uknown store tab type. {e.StackTrace.CleanupStackTrace()}"); return false; } } @@ -2029,7 +2029,7 @@ namespace Barotrauma } catch (NotImplementedException e) { - DebugConsole.ShowError($"Error confirming the store transaction: Uknown store tab type. {e.StackTrace.CleanupStackTrace()}"); + DebugConsole.LogError($"Error confirming the store transaction: Uknown store tab type. {e.StackTrace.CleanupStackTrace()}"); return false; } var itemsToRemove = new List(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs index 4b17d15d3..8a9d4e8c1 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs @@ -206,9 +206,9 @@ namespace Barotrauma transferMenuButton.RectTransform.AbsoluteOffset = new Point(0, -pos - transferMenu.Rect.Height); } GameSession.UpdateTalentNotificationIndicator(talentPointNotification); - if (Character.Controlled is { } controlled && talentResetButton != null && talentApplyButton != null) + if (Character.Controlled?.Info is { } characterInfo && talentResetButton != null && talentApplyButton != null) { - int talentCount = selectedTalents.Count - controlled.Info.GetUnlockedTalentsInTree().Count(); + int talentCount = selectedTalents.Count - characterInfo.GetUnlockedTalentsInTree().Count(); talentResetButton.Enabled = talentApplyButton.Enabled = talentCount > 0; if (talentApplyButton.Enabled && talentApplyButton.FlashTimer <= 0.0f) { @@ -1918,14 +1918,18 @@ namespace Barotrauma skillListBox = new GUIListBox(new RectTransform(new Vector2(1f, 1f - skillBlock.RectTransform.RelativeSize.Y), skillLayout.RectTransform), style: null); CreateSkillList(controlledCharacter, info, skillListBox); - if (controlledCharacter != null) + new GUIFrame(new RectTransform(new Vector2(1f, 1f), contentLayout.RectTransform), style: "HorizontalLine"); + + GUIListBox talentTreeListBox = new GUIListBox(new RectTransform(new Vector2(1f, 0.6f), contentLayout.RectTransform, Anchor.TopCenter), isHorizontal: true, style: null); + + if (controlledCharacter == null) + { + talentTreeListBox.Enabled = false; + } + else { if (!TalentTree.JobTalentTrees.TryGet(info.Job.Prefab.Identifier, out TalentTree talentTree)) { return; } - new GUIFrame(new RectTransform(new Vector2(1f, 1f), contentLayout.RectTransform), style: "HorizontalLine"); - - GUIListBox talentTreeListBox = new GUIListBox(new RectTransform(new Vector2(1f, 0.6f), contentLayout.RectTransform, Anchor.TopCenter), isHorizontal: true, style: null); - selectedTalents = info.GetUnlockedTalentsInTree().ToList(); List subTreeNames = new List(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/UpgradeStore.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/UpgradeStore.cs index ad7358468..08a186081 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/UpgradeStore.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/UpgradeStore.cs @@ -874,7 +874,7 @@ namespace Barotrauma itemPrefab.SwappableItem.CanBeBought && itemPrefab.SwappableItem.SwapIdentifier.Equals(item.Prefab.SwappableItem.SwapIdentifier, StringComparison.OrdinalIgnoreCase)).Cast(); - var linkedItems = Campaign.UpgradeManager.GetLinkedItemsToSwap(item) ?? new List() { item }; + var linkedItems = UpgradeManager.GetLinkedItemsToSwap(item) ?? new List() { item }; //create the swap entry only for one of the items (the one with the smallest ID) if (linkedItems.Min(it => it.ID) < item.ID) { return; } @@ -910,7 +910,7 @@ namespace Barotrauma GUILayoutGroup buttonLayout = new GUILayoutGroup(rectT(1f, 1f, toggleButton.Frame), isHorizontal: true); LocalizedString slotText = ""; - if (linkedItems.Count > 1) + if (linkedItems.Count() > 1) { slotText = TextManager.GetWithVariable("weaponslot", "[number]", string.Join(", ", linkedItems.Select(it => (swappableEntities.IndexOf(it) + 1).ToString()))); } @@ -982,7 +982,7 @@ namespace Barotrauma bool isPurchased = item.AvailableSwaps.Contains(replacement); - int price = isPurchased || replacement == item.Prefab ? 0 : replacement.SwappableItem.GetPrice(Campaign.Map?.CurrentLocation) * linkedItems.Count; + int price = isPurchased || replacement == item.Prefab ? 0 : replacement.SwappableItem.GetPrice(Campaign.Map?.CurrentLocation) * linkedItems.Count(); frames.Add(CreateUpgradeEntry(rectT(1f, 0.25f, parent.Content), replacement.UpgradePreviewSprite, replacement.Name, replacement.Description, price, replacement, @@ -998,7 +998,7 @@ namespace Barotrauma { LocalizedString promptBody = TextManager.GetWithVariables(isPurchased ? "upgrades.itemswappromptbody" : "upgrades.purchaseitemswappromptbody", ("[itemtoinstall]", replacement.Name), - ("[amount]", (replacement.SwappableItem.GetPrice(Campaign?.Map?.CurrentLocation) * linkedItems.Count).ToString())); + ("[amount]", (replacement.SwappableItem.GetPrice(Campaign?.Map?.CurrentLocation) * linkedItems.Count()).ToString())); currectConfirmation = EventEditorScreen.AskForConfirmation(TextManager.Get("Upgrades.PurchasePromptTitle"), promptBody, () => { if (GameMain.NetworkMember != null) @@ -1042,7 +1042,7 @@ namespace Barotrauma } if (toggleButton.Selected) { - var linkedItems = Campaign.UpgradeManager.GetLinkedItemsToSwap(item); + var linkedItems = UpgradeManager.GetLinkedItemsToSwap(item); foreach (var itemPreview in itemPreviews) { itemPreview.Value.OutlineColor = itemPreview.Value.Color = linkedItems.Contains(itemPreview.Key) ? GUIStyle.Orange : previewWhite; @@ -1391,7 +1391,7 @@ namespace Barotrauma { if (selectedUpgradeCategoryLayout != null) { - var linkedItems = HoveredEntity is Item hoveredItem ? Campaign.UpgradeManager.GetLinkedItemsToSwap(hoveredItem) : new List(); + var linkedItems = HoveredEntity is Item hoveredItem ? UpgradeManager.GetLinkedItemsToSwap(hoveredItem) : new List(); if (selectedUpgradeCategoryLayout.FindChild(c => c.UserData is Item item && (item == HoveredEntity || linkedItems.Contains(item)), recursive: true) is GUIButton itemElement) { if (!itemElement.Selected) { itemElement.OnClicked(itemElement, itemElement.UserData); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/CargoManager.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/CargoManager.cs index bbdde97f2..dd4ca114b 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/CargoManager.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/CargoManager.cs @@ -139,7 +139,7 @@ namespace Barotrauma } catch (NotImplementedException e) { - DebugConsole.ShowError($"Error selling items: uknown store tab type \"{sellingMode}\".\n{e.StackTrace.CleanupStackTrace()}"); + DebugConsole.LogError($"Error selling items: uknown store tab type \"{sellingMode}\".\n{e.StackTrace.CleanupStackTrace()}"); return; } bool canAddToRemoveQueue = campaign.IsSinglePlayer && Entity.Spawner != null; @@ -148,7 +148,7 @@ namespace Barotrauma var sellValues = GetSellValuesAtCurrentLocation(storeIdentifier, itemsToSell.Select(i => i.ItemPrefab)); if (!(Location.GetStore(storeIdentifier) is { } store)) { - DebugConsole.ShowError($"Error selling items at {Location}: no store with identifier \"{storeIdentifier}\" exists.\n{Environment.StackTrace.CleanupStackTrace()}"); + DebugConsole.LogError($"Error selling items at {Location}: no store with identifier \"{storeIdentifier}\" exists.\n{Environment.StackTrace.CleanupStackTrace()}"); return; } var storeSpecificSoldItems = GetSoldItems(storeIdentifier, create: true); diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs index a1f3594bf..5820ca4cd 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs @@ -291,29 +291,13 @@ namespace Barotrauma return crewArea.Rect; } - /// - /// Remove the character from the crew (and crew menus). - /// - /// The character to remove - /// If the character info is also removed, the character will not be visible in the round summary. - public void RemoveCharacter(Character character, bool removeInfo = false, bool resetCrewListIndex = true) - { - if (character == null) - { - DebugConsole.ThrowError("Tried to remove a null character from CrewManager.\n" + Environment.StackTrace.CleanupStackTrace()); - return; - } - characters.Remove(character); - if (removeInfo) { characterInfos.Remove(character.Info); } - if (resetCrewListIndex) { ResetCrewListIndex(character); } - } - /// /// Add character to the list without actually adding it to the crew /// public GUIComponent AddCharacterToCrewList(Character character) { if (character == null) { return null; } + if (crewList.Content.Children.Any(c => c.UserData as Character == character)) { return null; } var background = new GUIFrame( new RectTransform(crewListEntrySize, parent: crewList.Content.RectTransform, anchor: Anchor.TopRight), @@ -509,7 +493,15 @@ namespace Barotrauma return background; } - private void SetCharacterComponentTooltip(GUIComponent characterComponent) + public void RemoveCharacterFromCrewList(Character character) + { + if (crewList?.Content.GetChildByUserData(character) is { } component) + { + crewList.RemoveChild(component); + } + } + + private static void SetCharacterComponentTooltip(GUIComponent characterComponent) { if (!(characterComponent?.UserData is Character character)) { return; } if (character.Info?.Job?.Prefab == null) { return; } @@ -2821,7 +2813,7 @@ namespace Barotrauma return node; } - private struct MinimapNodeData + public struct MinimapNodeData { public Order Order; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/MultiPlayerCampaign.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/MultiPlayerCampaign.cs index 6fff8e5e9..94ba1b79a 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/MultiPlayerCampaign.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/MultiPlayerCampaign.cs @@ -761,6 +761,7 @@ namespace Barotrauma item.PendingItemSwap = null; } } + campaign.CampaignUI?.UpgradeStore?.RequestRefresh(); } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/Tutorial.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/Tutorial.cs index 3dbc348f4..a9c7f14e8 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/Tutorial.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/Tutorial.cs @@ -78,7 +78,7 @@ namespace Barotrauma.Tutorials public Action OnClickObjective; - public readonly TutorialSegmentType SegmentType; + public TutorialSegmentType SegmentType { get; private set; } public static Segment CreateInfoBoxSegment(Identifier id, Identifier objectiveTextTag, AutoPlayVideo autoPlayVideo, Text textContent = default, Video videoContent = default) { @@ -119,6 +119,12 @@ namespace Barotrauma.Tutorials ObjectiveText = TextManager.ParseInputTypes(TextManager.Get(objectiveTextTag)); SegmentType = TutorialSegmentType.Objective; } + + public void ConnectMessageBox(Segment messageBoxSegment) + { + SegmentType = TutorialSegmentType.MessageBox; + OnClickObjective = messageBoxSegment.OnClickObjective; + } } private bool completed; @@ -144,6 +150,7 @@ namespace Barotrauma.Tutorials private readonly EventPrefab eventPrefab; private CoroutineHandle tutorialCoroutine; + private CoroutineHandle completedCoroutine; private Character character; @@ -154,7 +161,7 @@ namespace Barotrauma.Tutorials private SubmarineInfo startOutpost = null; - public readonly List<(Entity entity, string iconStyle)> Icons = new List<(Entity entity, string iconStyle)>(); + public readonly List<(Entity entity, Identifier iconStyle)> Icons = new List<(Entity entity, Identifier iconStyle)>(); #endregion @@ -331,13 +338,16 @@ namespace Barotrauma.Tutorials { CoroutineManager.StopCoroutines(tutorialCoroutine); } - GUI.PreventPauseMenuToggle = false; + if (completedCoroutine == null && !CoroutineManager.IsCoroutineRunning(completedCoroutine)) + { + GUI.PreventPauseMenuToggle = false; + } ContentRunning = false; infoBox = null; } - else if (Character.Controlled.IsDead) + else { - CoroutineManager.StartCoroutine(Dead()); + character = Character.Controlled; } } } @@ -385,7 +395,7 @@ namespace Barotrauma.Tutorials if (eventPrefab == null) { - DebugConsole.ShowError($"No tutorial event defined for the tutorial (identifier: \"{TutorialPrefab?.Identifier.ToString() ?? "null"})\""); + DebugConsole.LogError($"No tutorial event defined for the tutorial (identifier: \"{TutorialPrefab?.Identifier.ToString() ?? "null"})\""); yield return CoroutineStatus.Failure; } @@ -399,7 +409,7 @@ namespace Barotrauma.Tutorials } else { - DebugConsole.ShowError($"Failed to create an instance for a tutorial event (identifier: \"{eventPrefab.Identifier}\""); + DebugConsole.LogError($"Failed to create an instance for a tutorial event (identifier: \"{eventPrefab.Identifier}\""); yield return CoroutineStatus.Failure; } @@ -409,10 +419,12 @@ namespace Barotrauma.Tutorials public void Complete() { GameAnalyticsManager.AddDesignEvent($"Tutorial:{Identifier}:Completed"); - CoroutineManager.StartCoroutine(TutorialCompleted()); + completedCoroutine = CoroutineManager.StartCoroutine(TutorialCompleted()); IEnumerable TutorialCompleted() { + while (GUI.PauseMenuOpen) { yield return CoroutineStatus.Running; } + GUI.PreventPauseMenuToggle = true; Character.Controlled.ClearInputs(); Character.Controlled = null; @@ -423,7 +435,7 @@ namespace Barotrauma.Tutorials var endCinematic = new CameraTransition(Submarine.MainSub, GameMain.GameScreen.Cam, null, Alignment.Center, panDuration: FadeOutTime); Completed = true; - while (endCinematic.Running) { yield return null; } + while (endCinematic.Running) { yield return CoroutineStatus.Running; } Stop(); GameMain.MainMenuScreen.ReturnToMainMenu(null, null); @@ -432,16 +444,18 @@ namespace Barotrauma.Tutorials private bool Restart(GUIButton button, object obj) { - GUI.PreventPauseMenuToggle = false; + GUIMessageBox.MessageBoxes.Clear(); + GameMain.MainMenuScreen.ReturnToMainMenu(button, obj); + Start(); return true; } - public void TriggerTutorialSegment(Segment segment) + public void TriggerTutorialSegment(Segment segment, bool connectObjective = false) { if (segment.SegmentType != TutorialSegmentType.InfoBox) { ActiveObjectives.Add(segment); - AddToObjectiveList(segment); + AddToObjectiveList(segment, connectObjective); return; } @@ -488,11 +502,14 @@ namespace Barotrauma.Tutorials } if (GUIStyle.GetComponentStyle("ObjectiveIndicatorCompleted") is GUIComponentStyle style) { + //return if already completed + if (segment.ObjectiveStateIndicator.Style == style) { return; } segment.ObjectiveStateIndicator.ApplyStyle(style); } segment.ObjectiveStateIndicator.Parent.Flash(color: GUIStyle.Green, flashDuration: 0.35f, useRectangleFlash: true); segment.ObjectiveButton.OnClicked = null; segment.ObjectiveButton.CanBeFocused = false; + GameAnalyticsManager.AddDesignEvent($"Tutorial:{Identifier}:{segmentId}:Completed"); } public void RemoveTutorialSegment(Identifier segmentId) @@ -568,8 +585,18 @@ namespace Barotrauma.Tutorials /// /// Adds the segment to the objective list /// - private void AddToObjectiveList(Segment segment) + private void AddToObjectiveList(Segment segment, bool connectExisting = false) { + if (connectExisting) + { + if (ActiveObjectives.Find(o => o.Id == segment.Id) is { } existingSegment) + { + existingSegment.ConnectMessageBox(segment); + SetButtonBehavior(existingSegment); + } + return; + } + var frameRt = new RectTransform(new Vector2(1.0f, 0.1f), objectiveGroup.RectTransform) { AbsoluteOffset = GetObjectiveHiddenPosition(), @@ -595,15 +622,25 @@ namespace Barotrauma.Tutorials frame.RectTransform.IsFixedSize = true; var indicatorRt = new RectTransform(new Point(objectiveGroup.AbsoluteSpacing), frame.RectTransform, isFixedSize: true); - segment.ObjectiveStateIndicator = new GUIImage(indicatorRt, "ObjectiveIndicatorIncomplete");; + segment.ObjectiveStateIndicator = new GUIImage(indicatorRt, "ObjectiveIndicatorIncomplete"); SetTransparent(segment.LinkedTextBlock); segment.ObjectiveButton = new GUIButton(new RectTransform(Vector2.One, segment.LinkedTextBlock.RectTransform, Anchor.TopLeft, Pivot.TopLeft), style: null) { - CanBeFocused = segment.SegmentType != TutorialSegmentType.Objective, - ToolTip = objectiveTextTranslated, - OnClicked = (GUIButton btn, object userdata) => + ToolTip = objectiveTextTranslated + }; + SetButtonBehavior(segment); + SetTransparent(segment.ObjectiveButton); + + frameRt.MoveOverTime(new Point(0, frameRt.AbsoluteOffset.Y), ObjectiveComponentAnimationTime, onDoneMoving: () => objectiveGroup?.Recalculate()); + + static void SetTransparent(GUIComponent component) => component.Color = component.HoverColor = component.PressedColor = component.SelectedColor = Color.Transparent; + + void SetButtonBehavior(Segment segment) + { + segment.ObjectiveButton.CanBeFocused = segment.SegmentType != TutorialSegmentType.Objective; + segment.ObjectiveButton.OnClicked = (GUIButton btn, object userdata) => { if (segment.SegmentType == TutorialSegmentType.InfoBox) { @@ -621,13 +658,8 @@ namespace Barotrauma.Tutorials segment.OnClickObjective?.Invoke(); } return true; - } - }; - SetTransparent(segment.ObjectiveButton); - - frameRt.MoveOverTime(new Point(0, frameRt.AbsoluteOffset.Y), ObjectiveComponentAnimationTime, onDoneMoving: () => objectiveGroup?.Recalculate()); - - static void SetTransparent(GUIComponent component) => component.Color = component.HoverColor = component.PressedColor = component.SelectedColor = Color.Transparent; + }; + } } private void ReplaySegmentVideo(Segment segment) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Door.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Door.cs index 88bd89496..aa7baf8be 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Door.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Door.cs @@ -4,6 +4,7 @@ using Barotrauma.Networking; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using System; +using System.Linq; namespace Barotrauma.Items.Components { @@ -177,7 +178,7 @@ namespace Barotrauma.Items.Components public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1) { - Color color = item.SpriteColor; + Color color = item.GetSpriteColor(withHighlight: true); if (brokenSprite == null) { //broken doors turn black if no broken sprite has been configured @@ -220,11 +221,15 @@ namespace Barotrauma.Items.Components color, 0.0f, doorSprite.Origin, item.Scale, item.SpriteEffects, doorSprite.Depth); } - if (brokenSprite != null && item.Health < item.MaxCondition) + float maxCondition = item.Repairables.Any() ? + item.Repairables.Min(r => r.RepairThreshold) / 100.0f * item.MaxCondition : + item.MaxCondition; + float healthRatio = item.Health / maxCondition; + if (brokenSprite != null && healthRatio < 1.0f) { - Vector2 scale = scaleBrokenSprite ? new Vector2(1.0f - item.Health / item.MaxCondition) : Vector2.One; + Vector2 scale = scaleBrokenSprite ? new Vector2(1.0f - healthRatio) : Vector2.One; if (IsHorizontal) { scale.X = 1; } else { scale.Y = 1; } - float alpha = fadeBrokenSprite ? 1.0f - item.Health / item.MaxCondition : 1.0f; + float alpha = fadeBrokenSprite ? 1.0f - healthRatio : 1.0f; spriteBatch.Draw(brokenSprite.Texture, pos, getSourceRect(brokenSprite, openState, IsHorizontal), color * alpha, 0.0f, brokenSprite.Origin, scale * item.Scale, item.SpriteEffects, diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemComponent.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemComponent.cs index 77e267d14..345411caa 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemComponent.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemComponent.cs @@ -580,7 +580,7 @@ namespace Barotrauma.Items.Components GameMain.Instance.ResolutionChanged += OnResolutionChangedPrivate; } - protected void TryCreateDragHandle() + protected virtual void TryCreateDragHandle() { if (GuiFrame != null && GuiFrameSource.GetAttributeBool("draggable", true)) { @@ -593,6 +593,7 @@ namespace Barotrauma.Items.Components int iconHeight = GUIStyle.ItemFrameMargin.Y / 4; var dragIcon = new GUIImage(new RectTransform(new Point(GuiFrame.Rect.Width, iconHeight), handle.RectTransform, Anchor.TopCenter) { AbsoluteOffset = new Point(0, iconHeight / 2) }, style: "GUIDragIndicatorHorizontal"); + dragIcon.RectTransform.MinSize = new Point(0, iconHeight); handle.ValidatePosition = (RectTransform rectT) => { @@ -622,7 +623,7 @@ namespace Barotrauma.Items.Components }; int buttonHeight = (int)(GUIStyle.ItemFrameMargin.Y * 0.4f); - new GUIButton(new RectTransform(new Point(buttonHeight), handle.RectTransform, Anchor.TopLeft) { AbsoluteOffset = new Point(buttonHeight / 4) }, + new GUIButton(new RectTransform(new Point(buttonHeight), handle.RectTransform, Anchor.TopLeft) { AbsoluteOffset = new Point(buttonHeight / 4), MinSize = new Point(buttonHeight) }, style: "GUIButtonSettings") { OnClicked = (btn, userdata) => diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemContainer.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemContainer.cs index a759f9ac7..d43d947fd 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemContainer.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemContainer.cs @@ -324,6 +324,8 @@ namespace Barotrauma.Items.Components int i = 0; foreach (Item containedItem in Inventory.AllItems) { + if (containedItem?.Sprite == null) { continue; } + if (AutoInteractWithContained) { containedItem.IsHighlighted = item.IsHighlighted; @@ -344,7 +346,7 @@ namespace Barotrauma.Items.Components containedItem.Sprite.Draw( spriteBatch, new Vector2(currentItemPos.X, -currentItemPos.Y), - isWiringMode ? containedItem.GetSpriteColor() * 0.15f : containedItem.GetSpriteColor(), + isWiringMode ? containedItem.GetSpriteColor(withHighlight: true) * 0.15f : containedItem.GetSpriteColor(withHighlight: true), origin, -(containedItem.body == null ? 0.0f : containedItem.body.DrawRotation ), containedItem.Scale, diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Deconstructor.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Deconstructor.cs index 5a31e20c3..dd3b30b66 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Deconstructor.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Deconstructor.cs @@ -96,6 +96,7 @@ namespace Barotrauma.Items.Components var buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.4f, 0.8f), inputArea.RectTransform), childAnchor: Anchor.CenterLeft); activateButton = new GUIButton(new RectTransform(new Vector2(0.95f, 0.8f), buttonContainer.RectTransform), TextManager.Get("DeconstructorDeconstruct"), style: "DeviceButton") { + UserData = UIHighlightAction.ElementId.DeconstructButton, TextBlock = { AutoScaleHorizontal = true }, OnClicked = OnActivateButtonClicked }; @@ -432,17 +433,22 @@ namespace Barotrauma.Items.Components private bool OnActivateButtonClicked(GUIButton button, object obj) { - var disallowedItem = inputContainer.Inventory.FindItem(i => !i.AllowDeconstruct, recursive: false); - if (disallowedItem != null && !DeconstructItemsSimultaneously) + if (!IsActive) { - int index = inputContainer.Inventory.FindIndex(disallowedItem); - if (index >= 0 && index < inputContainer.Inventory.visualSlots.Length) + //don't allow turning on if there's non-deconstructable items in the queue + var disallowedItem = inputContainer.Inventory.FindItem(i => !i.AllowDeconstruct, recursive: false); + if (disallowedItem != null && !DeconstructItemsSimultaneously) { - var slot = inputContainer.Inventory.visualSlots[index]; - slot?.ShowBorderHighlight(GUIStyle.Red, 0.1f, 0.9f); + int index = inputContainer.Inventory.FindIndex(disallowedItem); + if (index >= 0 && index < inputContainer.Inventory.visualSlots.Length) + { + var slot = inputContainer.Inventory.visualSlots[index]; + slot?.ShowBorderHighlight(GUIStyle.Red, 0.1f, 0.9f); + } + return true; } - return true; } + if (GameMain.Client != null) { pendingState = !IsActive; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs index 0f81a432a..16e766617 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs @@ -32,6 +32,8 @@ namespace Barotrauma.Items.Components } private FabricationRecipe selectedItem; + public Identifier SelectedItemIdentifier => SelectedItem?.TargetItem.Identifier ?? Identifier.Empty; + private GUIComponent inSufficientPowerWarning; private FabricationRecipe pendingFabricatedItem; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs index fce57ab20..3b25c87b0 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs @@ -1013,12 +1013,14 @@ namespace Barotrauma.Items.Components } else if (hullData.LinkedHulls.Any()) { - hullData.HullWaterAmount = 0.0f; + float waterVolume = 0.0f; + float totalVolume = 0.0f; foreach (Hull linkedHull in hullData.LinkedHulls) { - hullData.HullWaterAmount += WaterDetector.GetWaterPercentage(linkedHull); + waterVolume += linkedHull.WaterVolume; + totalVolume += linkedHull.Volume; } - hullData.HullWaterAmount /= hullData.LinkedHulls.Count; + hullData.HullWaterAmount = MathHelper.Clamp((int)Math.Ceiling(waterVolume / totalVolume * 100), 0, 100); } else { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Pump.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Pump.cs index 4ca36908c..d3076f105 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Pump.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Pump.cs @@ -1,10 +1,8 @@ using Barotrauma.Networking; using Barotrauma.Particles; using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Graphics; using System; using System.Collections.Generic; -using System.Xml.Linq; namespace Barotrauma.Items.Components { @@ -58,6 +56,7 @@ namespace Barotrauma.Items.Components RelativeOffset = new Vector2(0, 0.1f) }, style: "PowerButton") { + UserData = UIHighlightAction.ElementId.PowerButton, OnClicked = (button, data) => { TargetLevel = null; @@ -114,6 +113,7 @@ namespace Barotrauma.Items.Components return true; } }; + pumpSpeedSlider.Frame.UserData = UIHighlightAction.ElementId.PumpSpeedSlider; var textsArea = new GUIFrame(new RectTransform(new Vector2(1, 0.25f), sliderArea.RectTransform, Anchor.BottomCenter), style: null); var outLabel = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), textsArea.RectTransform, Anchor.CenterLeft), TextManager.Get("PumpOut"), textColor: GUIStyle.TextColorNormal, textAlignment: Alignment.CenterLeft, wrap: false, font: GUIStyle.SubHeadingFont); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Reactor.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Reactor.cs index 30a3a35ba..b229b2142 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Reactor.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Reactor.cs @@ -234,6 +234,7 @@ namespace Barotrauma.Items.Components return false; } }; + FissionRateScrollBar.Frame.UserData = UIHighlightAction.ElementId.FissionRateSlider; TurbineOutputScrollBar = new GUIScrollBar(new RectTransform(sliderSize, rightArea.RectTransform, Anchor.TopCenter) { @@ -252,6 +253,7 @@ namespace Barotrauma.Items.Components return false; } }; + TurbineOutputScrollBar.Frame.UserData = UIHighlightAction.ElementId.TurbineOutputSlider; var buttonArea = new GUILayoutGroup(new RectTransform(new Vector2(1, 0.2f), columnLeft.RectTransform)) { @@ -302,9 +304,10 @@ namespace Barotrauma.Items.Components new GUIFrame(new RectTransform(new Vector2(0.01f, 1.0f), topRightArea.RectTransform), style: "VerticalLine"); - AutoTempSwitch = new GUIButton(new RectTransform(new Vector2(0.15f, 0.9f), topRightArea.RectTransform), + AutoTempSwitch = new GUIButton(new RectTransform(new Vector2(0.15f, 0.9f), topRightArea.RectTransform), style: "SwitchVertical") { + UserData = UIHighlightAction.ElementId.AutoTempSwitch, Enabled = false, Selected = AutoTemp, ClickSound = GUISoundType.UISwitch, @@ -347,6 +350,7 @@ namespace Barotrauma.Items.Components RelativeOffset = new Vector2(0, 0.1f) }, style: "PowerButton") { + UserData = UIHighlightAction.ElementId.PowerButton, OnClicked = (button, data) => { PowerOn = !PowerOn; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs index dbac05552..188c75cf9 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs @@ -216,6 +216,7 @@ namespace Barotrauma.Items.Components var sonarModeArea = new GUIFrame(new RectTransform(new Vector2(1, 0.4f + extraHeight), paddedControlContainer.RectTransform, Anchor.TopCenter), style: null); SonarModeSwitch = new GUIButton(new RectTransform(new Vector2(0.2f, 1), sonarModeArea.RectTransform), string.Empty, style: "SwitchVertical") { + UserData = UIHighlightAction.ElementId.SonarModeSwitch, Selected = false, Enabled = true, ClickSound = GUISoundType.UISwitch, @@ -238,6 +239,7 @@ namespace Barotrauma.Items.Components passiveTickBox = new GUITickBox(new RectTransform(new Vector2(1, 0.45f), sonarModeRightSide.RectTransform, Anchor.TopLeft), TextManager.Get("SonarPassive"), font: GUIStyle.SubHeadingFont, style: "IndicatorLightRedSmall") { + UserData = UIHighlightAction.ElementId.PassiveSonarIndicator, ToolTip = TextManager.Get("SonarTipPassive"), Selected = true, Enabled = false @@ -245,6 +247,7 @@ namespace Barotrauma.Items.Components activeTickBox = new GUITickBox(new RectTransform(new Vector2(1, 0.45f), sonarModeRightSide.RectTransform, Anchor.BottomLeft), TextManager.Get("SonarActive"), font: GUIStyle.SubHeadingFont, style: "IndicatorLightRedSmall") { + UserData = UIHighlightAction.ElementId.ActiveSonarIndicator, ToolTip = TextManager.Get("SonarTipActive"), Selected = false, Enabled = false @@ -279,9 +282,14 @@ namespace Barotrauma.Items.Components }; new GUIFrame(new RectTransform(new Vector2(0.8f, 0.01f), paddedControlContainer.RectTransform, Anchor.Center), style: "HorizontalLine") - { UserData = "horizontalline" }; + { + UserData = "horizontalline" + }; - var directionalModeFrame = new GUIFrame(new RectTransform(new Vector2(1, 0.45f), lowerAreaFrame.RectTransform, Anchor.BottomCenter), style: null); + var directionalModeFrame = new GUIFrame(new RectTransform(new Vector2(1, 0.45f), lowerAreaFrame.RectTransform, Anchor.BottomCenter), style: null) + { + UserData = UIHighlightAction.ElementId.DirectionalSonarFrame + }; directionalModeSwitch = new GUIButton(new RectTransform(new Vector2(0.3f, 0.8f), directionalModeFrame.RectTransform, Anchor.CenterLeft), string.Empty, style: "SwitchHorizontal") { OnClicked = (button, data) => @@ -334,6 +342,18 @@ namespace Barotrauma.Items.Components sonarView.RectTransform.RelativeOffset = new Vector2(0.13f * GUI.RelativeHorizontalAspectRatio, 0); sonarView.RectTransform.SetPosition(Anchor.BottomRight); } + var handle = GuiFrame.GetChild(); + if (handle != null) + { + handle.RectTransform.Parent = controlContainer.RectTransform; + handle.RectTransform.Resize(Vector2.One); + handle.RectTransform.SetAsFirstChild(); + } + } + + protected override void TryCreateDragHandle() + { + base.TryCreateDragHandle(); } private void SetPingDirection(Vector2 direction) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Steering.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Steering.cs index d4d043455..7f06e5dbf 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Steering.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Steering.cs @@ -1,13 +1,11 @@ -using Barotrauma.Networking; +using Barotrauma.Extensions; +using Barotrauma.Networking; using FarseerPhysics; using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Graphics; using System; -using System.Linq; using System.Collections.Generic; -using System.Xml.Linq; -using Barotrauma.Extensions; +using System.Linq; namespace Barotrauma.Items.Components { @@ -141,6 +139,7 @@ namespace Barotrauma.Items.Components var steeringModeArea = new GUIFrame(new RectTransform(new Vector2(1, 0.4f), paddedControlContainer.RectTransform, Anchor.TopLeft), style: null); steeringModeSwitch = new GUIButton(new RectTransform(new Vector2(0.2f, 1), steeringModeArea.RectTransform), string.Empty, style: "SwitchVertical") { + UserData = UIHighlightAction.ElementId.SteeringModeSwitch, Selected = autoPilot, Enabled = true, ClickSound = GUISoundType.UISwitch, @@ -182,6 +181,7 @@ namespace Barotrauma.Items.Components maintainPosTickBox = new GUITickBox(new RectTransform(new Vector2(1, 0.333f), paddedAutoPilotControls.RectTransform, Anchor.TopCenter), TextManager.Get("SteeringMaintainPos"), font: GUIStyle.SmallFont, style: "GUIRadioButton") { + UserData = UIHighlightAction.ElementId.MaintainPosTickBox, Enabled = autoPilot, Selected = maintainPos, OnSelected = tickBox => diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Power/PowerContainer.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Power/PowerContainer.cs index cd6883441..72070c051 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Power/PowerContainer.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Power/PowerContainer.cs @@ -83,7 +83,8 @@ namespace Barotrauma.Items.Components } }; rechargeSpeedSlider.Bar.RectTransform.MaxSize = new Point(rechargeSpeedSlider.Bar.Rect.Height); - + rechargeSpeedSlider.Frame.UserData = UIHighlightAction.ElementId.RechargeSpeedSlider; + // lower area -------------------------- var chargeTextContainer = new GUIFrame(new RectTransform(new Vector2(1, 0.4f), lowerArea.RectTransform), style: null); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Repairable.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Repairable.cs index a6c9f47da..afaeb2dca 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Repairable.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Repairable.cs @@ -165,6 +165,7 @@ namespace Barotrauma.Items.Components repairingText = TextManager.Get("Repairing"); RepairButton = new GUIButton(new RectTransform(new Vector2(0.4f, 1.0f), progressBarHolder.RectTransform, Anchor.TopCenter), repairButtonText) { + UserData = UIHighlightAction.ElementId.RepairButton, OnClicked = (btn, obj) => { requestStartFixAction = FixActions.Repair; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/CustomInterface.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/CustomInterface.cs index cdb1d4ac9..17b151087 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/CustomInterface.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/CustomInterface.cs @@ -135,7 +135,7 @@ namespace Barotrauma.Items.Components } else { - DebugConsole.ShowError($"Error creating a CustomInterface component: unexpected NumberType \"{(ciElement.NumberType.HasValue ? ciElement.NumberType.Value.ToString() : "none")}\""); + DebugConsole.LogError($"Error creating a CustomInterface component: unexpected NumberType \"{(ciElement.NumberType.HasValue ? ciElement.NumberType.Value.ToString() : "none")}\""); } if (numberInput != null) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs index 8950850cc..0c35c29eb 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs @@ -118,7 +118,7 @@ namespace Barotrauma return GetDrawDepth(SpriteDepth + DrawDepthOffset, Sprite); } - public Color GetSpriteColor() + public Color GetSpriteColor(bool withHighlight = false) { Color color = spriteColor; if (Prefab.UseContainedSpriteColor && ownInventory != null) @@ -129,6 +129,17 @@ namespace Barotrauma break; } } + if (withHighlight) + { + if (IsHighlighted && !GUI.DisableItemHighlights && Screen.Selected != GameMain.GameScreen) + { + color = GUIStyle.Orange * Math.Max(GetSpriteColor().A / (float)byte.MaxValue, 0.1f); + } + else if (IsHighlighted && HighlightColor.HasValue) + { + color = Color.Lerp(color, HighlightColor.Value, (MathF.Sin((float)Timing.TotalTime * 3.0f) + 1.0f) / 2.0f); + } + } return color; } @@ -281,9 +292,7 @@ namespace Barotrauma else if (!ShowItems) { return; } } - Color color = IsIncludedInSelection && editing ? GUIStyle.Blue : IsHighlighted && !GUI.DisableItemHighlights && Screen.Selected != GameMain.GameScreen ? GUIStyle.Orange * Math.Max(GetSpriteColor().A / (float) byte.MaxValue, 0.1f) : GetSpriteColor(); - - //if (IsSelected && editing) color = Color.Lerp(color, Color.Gold, 0.5f); + Color color = IsIncludedInSelection && editing ? GUIStyle.Blue : GetSpriteColor(withHighlight: true); bool isWiringMode = editing && SubEditorScreen.TransparentWiringMode && SubEditorScreen.IsWiringMode() && !isWire && parentInventory == null; bool renderTransparent = isWiringMode && GetComponent() == null; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/ItemPrefab.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/ItemPrefab.cs index bab357ba1..8c6aed81b 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/ItemPrefab.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/ItemPrefab.cs @@ -229,6 +229,7 @@ namespace Barotrauma Submarine = Submarine.MainSub }; item.SetTransform(ConvertUnits.ToSimUnits(Submarine.MainSub == null ? item.Position : item.Position - Submarine.MainSub.Position), 0.0f); + item.GetComponent()?.RefreshLinkedGap(); item.FindHull(); item.Submarine = Submarine.MainSub; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Gap.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Gap.cs index 8da238592..77189d2d9 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Gap.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Gap.cs @@ -264,10 +264,6 @@ namespace Barotrauma { Vector2 velocity = flowForce; if (!IsHorizontal) - { - velocity.X = Rand.Range(-100.0f, 100.0f) * open; - } - else { velocity.X *= Rand.Range(1.0f, 3.0f); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/BanList.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/BanList.cs index 8d0f64b01..94e3da6c4 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/BanList.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/BanList.cs @@ -2,7 +2,6 @@ using Microsoft.Xna.Framework; using System; using System.Collections.Generic; -using System.Linq; namespace Barotrauma.Networking { @@ -88,12 +87,13 @@ namespace Barotrauma.Networking TextManager.Get("BanPermanent") : TextManager.GetWithVariable("BanExpires", "[time]", bannedPlayer.ExpirationTime.Value.ToString()), font: GUIStyle.SmallFont); + LocalizedString reason = TextManager.GetServerMessage(bannedPlayer.Reason).Fallback(bannedPlayer.Reason); var reasonText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedPlayerFrame.RectTransform), TextManager.Get("BanReason") + " " + - (string.IsNullOrEmpty(bannedPlayer.Reason) ? TextManager.Get("None") : bannedPlayer.Reason), + (string.IsNullOrEmpty(bannedPlayer.Reason) ? TextManager.Get("None") : reason), font: GUIStyle.SmallFont, wrap: true) { - ToolTip = bannedPlayer.Reason + ToolTip = reason }; paddedPlayerFrame.Recalculate(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/Client.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/Client.cs index 3c72ee33a..21b119c40 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/Client.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/Client.cs @@ -31,12 +31,17 @@ namespace Barotrauma.Networking public bool IsOwner; - public bool AllowKicking; public bool IsDownloading; public float Karma; + public bool AllowKicking => + !IsOwner && + !HasPermission(ClientPermissions.Ban) && + !HasPermission(ClientPermissions.Kick) && + !HasPermission(ClientPermissions.Unban); + public void UpdateSoundPosition() { if (VoipSound == null) { return; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs index c78321411..76040fcd3 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs @@ -362,8 +362,7 @@ namespace Barotrauma.Networking // When this is set to true, we are approved and ready to go canStart = false; - DateTime timeOut = DateTime.Now + new TimeSpan(0, 0, 40); - DateTime reqAuthTime = DateTime.Now + new TimeSpan(0, 0, 0, 0, 200); + DateTime timeOut = DateTime.Now + new TimeSpan(0, 0, 200); // Loop until we are approved LocalizedString connectingText = TextManager.Get("Connecting"); @@ -489,7 +488,10 @@ namespace Barotrauma.Networking } GameAnalyticsManager.AddErrorEventOnce("GameClient.Update:CheckServerMessagesException" + e.TargetSite.ToString(), GameAnalyticsManager.ErrorSeverity.Error, errorMsg); DebugConsole.ThrowError("Error while reading a message from server.", e); - new GUIMessageBox(TextManager.Get("Error"), TextManager.GetWithVariables("MessageReadError", ("[message]", e.Message), ("[targetsite]", e.TargetSite.ToString()))); + new GUIMessageBox(TextManager.Get("Error"), TextManager.GetWithVariables("MessageReadError", ("[message]", e.Message), ("[targetsite]", e.TargetSite.ToString()))) + { + DisplayInLoadingScreens = true + }; Quit(); GameMain.ServerListScreen.Select(); return; @@ -965,10 +967,10 @@ namespace Barotrauma.Networking } AttemptReconnect(disconnectPacket); } - else if (disconnectPacket.ShouldShowMessage) + else { ReturnToPreviousMenu(null, null); - var msgBox = new GUIMessageBox(TextManager.Get(wasConnected ? "ConnectionLost" : "CouldNotConnectToServer"), disconnectPacket.PopupMessage) + new GUIMessageBox(TextManager.Get(wasConnected ? "ConnectionLost" : "CouldNotConnectToServer"), disconnectPacket.PopupMessage) { DisplayInLoadingScreens = true }; @@ -1033,6 +1035,7 @@ namespace Barotrauma.Networking if (ClientPeer != null) { //restore the previous list of content packages so we can reconnect immediately without having to recheck that the packages match + ClientPeer.ContentPackageOrderReceived = true; ClientPeer.ServerContentPackages = prevContentPackages; } } @@ -1721,6 +1724,7 @@ namespace Barotrauma.Networking string subName = inc.ReadString(); string subHash = inc.ReadString(); byte subClass = inc.ReadByte(); + bool isShuttle = inc.ReadBoolean(); bool requiredContentPackagesInstalled = inc.ReadBoolean(); var matchingSub = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == subName && s.MD5Hash.StringRepresentation == subHash); @@ -1730,6 +1734,7 @@ namespace Barotrauma.Networking { SubmarineClass = (SubmarineClass)subClass }; + if (isShuttle) { matchingSub.AddTag(SubmarineTag.Shuttle); } } matchingSub.RequiredContentPackagesInstalled = requiredContentPackagesInstalled; ServerSubmarines.Add(matchingSub); @@ -1780,7 +1785,6 @@ namespace Barotrauma.Networking AccountInfo = tc.AccountInfo, Muted = tc.Muted, InGame = tc.InGame, - AllowKicking = tc.AllowKicking, IsOwner = tc.IsOwner }; otherClients.Add(existingClient); @@ -1795,7 +1799,6 @@ namespace Barotrauma.Networking existingClient.Muted = tc.Muted; existingClient.InGame = tc.InGame; existingClient.IsOwner = tc.IsOwner; - existingClient.AllowKicking = tc.AllowKicking; existingClient.IsDownloading = tc.IsDownloading; GameMain.NetLobbyScreen.SetPlayerNameAndJobPreference(existingClient); if (Screen.Selected != GameMain.NetLobbyScreen && tc.CharacterId > 0) @@ -2404,7 +2407,7 @@ namespace Barotrauma.Networking var subElement = subListChildren.FirstOrDefault(c => ((SubmarineInfo)c.UserData).Name == newSub.Name && ((SubmarineInfo)c.UserData).MD5Hash.StringRepresentation == newSub.MD5Hash.StringRepresentation); - if (subElement == null) continue; + if (subElement == null) { continue; } Color newSubTextColor = new Color(subElement.GetChild().TextColor, 1.0f); subElement.GetChild().TextColor = newSubTextColor; @@ -2429,7 +2432,7 @@ namespace Barotrauma.Networking if (GameMain.NetLobbyScreen.FailedSelectedShuttle.HasValue && GameMain.NetLobbyScreen.FailedSelectedShuttle.Value.Name == newSub.Name && - GameMain.NetLobbyScreen.FailedSelectedShuttle.Value.Name == newSub.MD5Hash.StringRepresentation) + GameMain.NetLobbyScreen.FailedSelectedShuttle.Value.Hash == newSub.MD5Hash.StringRepresentation) { GameMain.NetLobbyScreen.TrySelectSub(newSub.Name, newSub.MD5Hash.StringRepresentation, GameMain.NetLobbyScreen.ShuttleList.ListBox); } @@ -2583,6 +2586,7 @@ namespace Barotrauma.Networking } } ChildServerRelay.ShutDown(); + GUIMessageBox.MessageBoxes.RemoveAll(c => c?.UserData is RoundSummary); characterInfo?.Remove(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/ClientPeer.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/ClientPeer.cs index e6b45ac85..b2cf3a633 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/ClientPeer.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/ClientPeer.cs @@ -49,7 +49,7 @@ namespace Barotrauma.Networking protected abstract void SendMsgInternal(PeerPacketHeaders headers, INetSerializableStruct? body); protected ConnectionInitialization initializationStep; - public bool ContentPackageOrderReceived { get; protected set; } + public bool ContentPackageOrderReceived { get; set; } protected int passwordSalt; protected Steamworks.AuthTicket? steamAuthTicket; private GUIMessageBox? passwordMsgBox; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/SteamP2PClientPeer.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/SteamP2PClientPeer.cs index e817616b2..35e579d2a 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/SteamP2PClientPeer.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/SteamP2PClientPeer.cs @@ -36,6 +36,8 @@ namespace Barotrauma.Networking public override void Start() { + if (isActive) { return; } + ContentPackageOrderReceived = false; steamAuthTicket = SteamManager.GetAuthSessionTicket(); @@ -189,6 +191,7 @@ namespace Barotrauma.Networking if (packet is { SteamId: var steamId, Data: var data }) { OnP2PData(steamId, data, data.Length); + if (!isActive) { return; } receivedBytes += data.Length; } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/SteamP2POwnerPeer.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/SteamP2POwnerPeer.cs index 440e3126e..48fe24b0e 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/SteamP2POwnerPeer.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/SteamP2POwnerPeer.cs @@ -391,7 +391,7 @@ namespace Barotrauma.Networking for (int i = remotePeers.Count - 1; i >= 0; i--) { - DisconnectPeer(remotePeers[i], peerDisconnectPacket); + DisconnectPeer(remotePeers[i], PeerDisconnectPacket.WithReason(DisconnectReason.ServerShutdown)); } Thread.Sleep(100); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerList/PingUtils.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerList/PingUtils.cs index 4f099bf4e..fc23c4df9 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerList/PingUtils.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerList/PingUtils.cs @@ -37,6 +37,7 @@ namespace Barotrauma.Networking switch (serverInfo.Endpoint) { case LidgrenEndpoint { NetEndpoint: { Address: var address } }: + GetIPAddressPing(serverInfo, address, onPingDiscovered); break; case SteamP2PEndpoint steamP2PEndpoint: @@ -132,24 +133,30 @@ namespace Barotrauma.Networking private static void GetIPAddressPing(ServerInfo serverInfo, IPAddress address, Action onPingDiscovered) { - lock (activePings) + if (IPAddress.IsLoopback(address)) { - if (activePings.ContainsKey(address)) { return; } - activePings.Add(address, activePings.Any() ? activePings.Values.Max() + 1 : 0); + serverInfo.Ping = Option.Some(0); + onPingDiscovered(serverInfo); } - - serverInfo.Ping = Option.None(); - - TaskPool.Add($"PingServerAsync ({address})", PingServerAsync(address, 1000), - rtt => + else + { + lock (activePings) { - if (!rtt.TryGetResult(out serverInfo.Ping)) { serverInfo.Ping = Option.None(); } - onPingDiscovered(serverInfo); - lock (activePings) + if (activePings.ContainsKey(address)) { return; } + activePings.Add(address, activePings.Any() ? activePings.Values.Max() + 1 : 0); + } + serverInfo.Ping = Option.None(); + TaskPool.Add($"PingServerAsync ({address})", PingServerAsync(address, 1000), + rtt => { - activePings.Remove(address); - } - }); + if (!rtt.TryGetResult(out serverInfo.Ping)) { serverInfo.Ping = Option.None(); } + onPingDiscovered(serverInfo); + lock (activePings) + { + activePings.Remove(address); + } + }); + } } private static async Task> PingServerAsync(IPAddress ipAddress, int timeOut) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerList/ServerInfo.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerList/ServerInfo.cs index 8b1e9e2d3..adbf863df 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerList/ServerInfo.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerList/ServerInfo.cs @@ -70,7 +70,7 @@ namespace Barotrauma.Networking [Serialize(PlayStyle.Casual, IsPropertySaveable.Yes)] public PlayStyle PlayStyle { get; set; } - public Version GameVersion { get; set; } = new Version(0,0,0,0); + public Version GameVersion { get; set; } = new Version(0, 0, 0, 0); public Option Ping = Option.None(); @@ -200,7 +200,7 @@ namespace Barotrauma.Networking new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), frame.RectTransform), TextManager.AddPunctuation(':', TextManager.Get("ServerListVersion"), - GameVersion.ToString())) + GameVersion == new Version(0, 0, 0, 0) ? TextManager.Get("Unknown") : GameVersion.ToString())) { CanBeFocused = false }; @@ -397,10 +397,10 @@ namespace Barotrauma.Networking public void UpdateInfo(Func valueGetter) { ServerMessage = valueGetter("message") ?? ""; - GameVersion = Version.TryParse(valueGetter("version"), out var version) - ? version - : GameMain.Version; - + if (Version.TryParse(valueGetter("version"), out var version)) + { + GameVersion = version; + } if (int.TryParse(valueGetter("playercount"), out int playerCount)) { PlayerCount = playerCount; } if (int.TryParse(valueGetter("maxplayernum"), out int maxPlayers)) { MaxPlayers = maxPlayers; } if (Enum.TryParse(valueGetter("modeselectionmode"), out SelectionMode modeSelectionMode)) { ModeSelectionMode = modeSelectionMode; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignUI.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignUI.cs index 5fc6deb6c..a405d730e 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignUI.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignUI.cs @@ -346,7 +346,7 @@ namespace Barotrauma }; var missionName = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), mission?.Name ?? TextManager.Get("NoMission"), font: GUIStyle.SubHeadingFont, wrap: true); - // missionName.RectTransform.MinSize = new Point(0, (int)(missionName.Rect.Height * 1.5f)); + missionName.RectTransform.MinSize = new Point(0, GUI.IntScale(15)); if (mission != null) { var tickBox = new GUITickBox(new RectTransform(Vector2.One * 0.9f, missionName.RectTransform, anchor: Anchor.CenterLeft, scaleBasis: ScaleBasis.Smallest) { AbsoluteOffset = new Point((int)missionName.Padding.X, 0) }, label: string.Empty) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs index 01f4b8506..420328bc7 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs @@ -67,7 +67,9 @@ namespace Barotrauma public static readonly Queue WorkshopItemsToUpdate = new Queue(); - private readonly GUIListBox tutorialList; + private GUIImage tutorialBanner; + private GUITextBlock tutorialHeader, tutorialDescription; + private GUIListBox tutorialList; #region Creation public MainMenuScreen(GameMain game) @@ -429,20 +431,7 @@ namespace Barotrauma //---------------------------------------------------------------------- menuTabs[Tab.Tutorials] = new GUIFrame(new RectTransform(relativeSize, GUI.Canvas, anchor, pivot, minSize, maxSize) { RelativeOffset = relativeSpacing }); - - //PLACEHOLDER - tutorialList = new GUIListBox( - new RectTransform(new Vector2(0.95f, 0.85f), menuTabs[Tab.Tutorials].RectTransform, Anchor.TopCenter) { RelativeOffset = new Vector2(0.0f, 0.1f) }) - { - PlaySoundOnSelect = true, - }; - tutorialList.OnSelected += (component, obj) => - { - (obj as Tutorial)?.Start(); - return true; - }; - - CreateTutorialButtons(); + CreateTutorialTab(); this.game = game; @@ -459,24 +448,68 @@ namespace Barotrauma creditsPlayer = new CreditsPlayer(new RectTransform(Vector2.One, creditsContainer.RectTransform), "Content/Texts/Credits.xml"); } - private void CreateTutorialButtons() + private void CreateTutorialTab() { + var tutorialInnerFrame = new GUIFrame(new RectTransform(new Vector2(0.9f, 0.9f), menuTabs[Tab.Tutorials].RectTransform, Anchor.Center), style: "InnerFrame"); + var tutorialContent = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.95f), tutorialInnerFrame.RectTransform, Anchor.Center), isHorizontal: true) { RelativeSpacing = 0.02f, Stretch = true }; + + tutorialList = new GUIListBox(new RectTransform(new Vector2(0.4f, 1.0f), tutorialContent.RectTransform)) + { + PlaySoundOnSelect = true, + OnSelected = (component, obj) => + { + SelectTutorial(obj as Tutorial); + return true; + } + }; + var tutorialPreview = new GUILayoutGroup(new RectTransform(new Vector2(0.6f, 1.0f), tutorialContent.RectTransform)) { RelativeSpacing = 0.05f, Stretch = true }; + var imageContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.6f), tutorialPreview.RectTransform), style: "InnerFrame"); + tutorialBanner = new GUIImage(new RectTransform(Vector2.One, imageContainer.RectTransform), style: null, scaleToFit: true); + + var infoContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.4f), tutorialPreview.RectTransform), style: "GUIFrameListBox"); + var infoContent = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), infoContainer.RectTransform, Anchor.Center), childAnchor: Anchor.TopCenter); + + tutorialHeader = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.75f), infoContent.RectTransform), string.Empty, font: GUIStyle.SubHeadingFont, textAlignment: Alignment.Center); + + var startButton = new GUIButton(new RectTransform(new Vector2(0.5f, 0.0f), infoContent.RectTransform, Anchor.BottomRight), text: TextManager.Get("startgamebutton")) + { + IgnoreLayoutGroups = true, + OnClicked = (component, obj) => + { + (tutorialList.SelectedData as Tutorial)?.Start(); + return true; + } + }; + + Tutorial firstTutorial = null; foreach (var tutorialPrefab in TutorialPrefab.Prefabs.OrderBy(p => p.Order)) { var tutorial = new Tutorial(tutorialPrefab); - var tutorialText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.15f), tutorialList.Content.RectTransform), tutorial.DisplayName, textAlignment: Alignment.Center, font: GUIStyle.LargeFont) + firstTutorial ??= tutorial; + var tutorialText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), tutorialList.Content.RectTransform), tutorial.DisplayName) { - TextColor = GUIStyle.Green, + Padding = new Vector4(30.0f * GUI.Scale, 0,0,0), UserData = tutorial }; + tutorialText.RectTransform.MinSize = new Point(0, (int)(tutorialText.TextSize.Y * 2)); } + GUITextBlock.AutoScaleAndNormalize(tutorialList.Content.Children.Select(c => c as GUITextBlock)); + tutorialList.Select(firstTutorial); + } + + private void SelectTutorial(Tutorial tutorial) + { + tutorialHeader.Text = tutorial.DisplayName; + tutorial.TutorialPrefab.Banner?.EnsureLazyLoaded(); + tutorialBanner.Sprite = tutorial.TutorialPrefab.Banner; + tutorialBanner.Color = tutorial.TutorialPrefab.Banner == null ? Color.Black : Color.White; } public static void UpdateInstanceTutorialButtons() { if (GameMain.MainMenuScreen is not MainMenuScreen menuScreen) { return; } menuScreen.tutorialList.ClearChildren(); - menuScreen.CreateTutorialButtons(); + menuScreen.CreateTutorialTab(); } #endregion @@ -746,12 +779,13 @@ namespace Barotrauma private void UpdateTutorialList() { - foreach (GUITextBlock tutorialText in menuTabs[Tab.Tutorials].GetChild().Content.Children) + foreach (GUITextBlock tutorialText in tutorialList.Content.Children) { var tutorial = (Tutorial)tutorialText.UserData; - tutorialText.Text = CompletedTutorials.Instance.Contains(tutorial.Identifier) ? - TextManager.GetWithVariable("tutorialcompleted", "[tutorialname]", tutorial.DisplayName) : - tutorial.DisplayName; + if (CompletedTutorials.Instance.Contains(tutorial.Identifier) && tutorialText.GetChild() == null) + { + new GUIImage(new RectTransform(new Point((int)(tutorialText.Padding.X * 0.8f)), tutorialText.RectTransform, Anchor.CenterLeft), style: "ObjectiveIndicatorCompleted"); + } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs index 9bff7dac0..0952422e7 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs @@ -1862,7 +1862,7 @@ namespace Barotrauma SubmarineType.Wreck => "Content/Map/Wrecks/{0}", SubmarineType.BeaconStation => "Content/Map/BeaconStations/{0}", SubmarineType.EnemySubmarine => "Content/Map/EnemySubmarines/{0}", - SubmarineType.OutpostModule => "Content/Map/Outposts/{0}", + SubmarineType.OutpostModule => MainSub.Info.FilePath.Contains("RuinModules") ? "Content/Map/RuinModules/{0}" : "Content/Map/Outposts/{0}", _ => throw new InvalidOperationException() }, savePath); modProject.ModVersion = ""; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundManager.cs b/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundManager.cs index 8cfa4a8f7..835981330 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundManager.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundManager.cs @@ -195,13 +195,13 @@ namespace Barotrauma.Sounds GainMultipliers[index] = gain; } } - private Dictionary categoryModifiers; + + private readonly Dictionary categoryModifiers = new Dictionary(); public SoundManager() { loadedSounds = new List(); streamingThread = null; - categoryModifiers = null; sourcePools = new SoundSourcePool[2]; playingChannels[(int)SourcePoolIndex.Default] = new SoundChannel[SOURCE_COUNT]; @@ -235,7 +235,7 @@ namespace Barotrauma.Sounds CompressionDynamicRangeGain = 1.0f; } - private void SetAudioOutputDevice(string deviceName) + private static void SetAudioOutputDevice(string deviceName) { var config = GameSettings.CurrentConfig; config.Audio.AudioOutputDevice = deviceName; @@ -249,7 +249,7 @@ namespace Barotrauma.Sounds DebugConsole.NewMessage($"Attempting to open ALC device \"{deviceName}\""); alcDevice = IntPtr.Zero; - int alcError = Al.NoError; + int alcError; for (int i = 0; i < 3; i++) { alcDevice = Alc.OpenDevice(deviceName); @@ -371,8 +371,10 @@ namespace Barotrauma.Sounds throw new System.IO.FileNotFoundException($"Sound file \"{filePath}\" doesn't exist! Content package \"{(element.ContentPackage?.Name ?? "Unknown")}\"."); } - var newSound = new OggSound(this, filePath, stream, xElement: element); - newSound.BaseGain = element.GetAttributeFloat("volume", 1.0f); + var newSound = new OggSound(this, filePath, stream, xElement: element) + { + BaseGain = element.GetAttributeFloat("volume", 1.0f) + }; float range = element.GetAttributeFloat("range", 1000.0f); newSound.BaseNear = range * 0.4f; newSound.BaseFar = range; @@ -537,14 +539,16 @@ namespace Barotrauma.Sounds { if (Disabled) { return; } category = category.ToLower(); - if (categoryModifiers == null) categoryModifiers = new Dictionary(); - if (!categoryModifiers.ContainsKey(category)) + lock (categoryModifiers) { - categoryModifiers.Add(category, new CategoryModifier(index, gain, false)); - } - else - { - categoryModifiers[category].SetGainMultiplier(index, gain); + if (!categoryModifiers.ContainsKey(category)) + { + categoryModifiers.Add(category, new CategoryModifier(index, gain, false)); + } + else + { + categoryModifiers[category].SetGainMultiplier(index, gain); + } } for (int i = 0; i < playingChannels.Length; i++) @@ -562,23 +566,26 @@ namespace Barotrauma.Sounds } } - public float GetCategoryGainMultiplier(string category, int index=-1) + public float GetCategoryGainMultiplier(string category, int index = -1) { if (Disabled) { return 0.0f; } category = category.ToLower(); - if (categoryModifiers == null || !categoryModifiers.ContainsKey(category)) return 1.0f; - if (index < 0) + lock (categoryModifiers) { - float accumulatedMultipliers = 1.0f; - for (int i = 0; i < categoryModifiers[category].GainMultipliers.Length; i++) + if (categoryModifiers == null || !categoryModifiers.TryGetValue(category, out CategoryModifier categoryModifier)) { return 1.0f; } + if (index < 0) { - accumulatedMultipliers *= categoryModifiers[category].GainMultipliers[i]; + float accumulatedMultipliers = 1.0f; + for (int i = 0; i < categoryModifier.GainMultipliers.Length; i++) + { + accumulatedMultipliers *= categoryModifier.GainMultipliers[i]; + } + return accumulatedMultipliers; + } + else + { + return categoryModifier.GainMultipliers[index]; } - return accumulatedMultipliers; - } - else - { - return categoryModifiers[category].GainMultipliers[index]; } } @@ -587,15 +594,16 @@ namespace Barotrauma.Sounds if (Disabled) { return; } category = category.ToLower(); - - if (categoryModifiers == null) { categoryModifiers = new Dictionary(); } - if (!categoryModifiers.ContainsKey(category)) + lock (categoryModifiers) { - categoryModifiers.Add(category, new CategoryModifier(0, 1.0f, muffle)); - } - else - { - categoryModifiers[category].Muffle = muffle; + if (!categoryModifiers.ContainsKey(category)) + { + categoryModifiers.Add(category, new CategoryModifier(0, 1.0f, muffle)); + } + else + { + categoryModifiers[category].Muffle = muffle; + } } for (int i = 0; i < playingChannels.Length; i++) @@ -618,8 +626,11 @@ namespace Barotrauma.Sounds if (Disabled) { return false; } category = category.ToLower(); - if (categoryModifiers == null || !categoryModifiers.ContainsKey(category)) { return false; } - return categoryModifiers[category].Muffle; + lock (categoryModifiers) + { + if (categoryModifiers == null || !categoryModifiers.TryGetValue(category, out CategoryModifier categoryModifier)) { return false; } + return categoryModifier.Muffle; + } } public void Update() diff --git a/Barotrauma/BarotraumaClient/LinuxClient.csproj b/Barotrauma/BarotraumaClient/LinuxClient.csproj index 50cde2d74..88b2f6e49 100644 --- a/Barotrauma/BarotraumaClient/LinuxClient.csproj +++ b/Barotrauma/BarotraumaClient/LinuxClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.19.5.0 + 0.19.8.0 Copyright © FakeFish 2018-2022 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/MacClient.csproj b/Barotrauma/BarotraumaClient/MacClient.csproj index a6511a7d3..8af641319 100644 --- a/Barotrauma/BarotraumaClient/MacClient.csproj +++ b/Barotrauma/BarotraumaClient/MacClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.19.5.0 + 0.19.8.0 Copyright © FakeFish 2018-2022 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/WindowsClient.csproj b/Barotrauma/BarotraumaClient/WindowsClient.csproj index 97e542512..e0cc010d5 100644 --- a/Barotrauma/BarotraumaClient/WindowsClient.csproj +++ b/Barotrauma/BarotraumaClient/WindowsClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.19.5.0 + 0.19.8.0 Copyright © FakeFish 2018-2022 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaServer/LinuxServer.csproj b/Barotrauma/BarotraumaServer/LinuxServer.csproj index be1d93bfe..27981fc58 100644 --- a/Barotrauma/BarotraumaServer/LinuxServer.csproj +++ b/Barotrauma/BarotraumaServer/LinuxServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.19.5.0 + 0.19.8.0 Copyright © FakeFish 2018-2022 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/MacServer.csproj b/Barotrauma/BarotraumaServer/MacServer.csproj index 52c412583..8403b2fcd 100644 --- a/Barotrauma/BarotraumaServer/MacServer.csproj +++ b/Barotrauma/BarotraumaServer/MacServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.19.5.0 + 0.19.8.0 Copyright © FakeFish 2018-2022 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs b/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs index 953e09573..a3e74f054 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs @@ -512,13 +512,10 @@ namespace Barotrauma msg.WriteByte((byte)TeamID); break; case AddToCrewEventData addToCrewEventData: - msg.WriteByte((byte)addToCrewEventData.TeamType); // team id - ushort[] inventoryItemIDs = addToCrewEventData.InventoryItems.Select(item => item.ID).ToArray(); - msg.WriteUInt16((ushort)inventoryItemIDs.Length); - for (int i = 0; i < inventoryItemIDs.Length; i++) - { - msg.WriteUInt16(inventoryItemIDs[i]); - } + msg.WriteNetSerializableStruct(addToCrewEventData.ItemTeamChange); + break; + case RemoveFromCrewEventData removeFromCrewEventData: + msg.WriteNetSerializableStruct(removeFromCrewEventData.ItemTeamChange); break; case UpdateExperienceEventData _: msg.WriteInt32(Info.ExperiencePoints); diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs index 116010200..370627ddb 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs @@ -1613,6 +1613,7 @@ namespace Barotrauma.Networking outmsg.WriteString(sub.Name); outmsg.WriteString(sub.MD5Hash.ToString()); outmsg.WriteByte((byte)sub.SubmarineClass); + outmsg.WriteBoolean(sub.HasTag(SubmarineTag.Shuttle)); outmsg.WriteBoolean(sub.RequiredContentPackagesInstalled); } @@ -1836,10 +1837,6 @@ namespace Barotrauma.Networking InGame = client.InGame, HasPermissions = client.Permissions != ClientPermissions.None, IsOwner = client.Connection == OwnerConnection, - AllowKicking = client.Connection != OwnerConnection && - !client.HasPermission(ClientPermissions.Ban) && - !client.HasPermission(ClientPermissions.Kick) && - !client.HasPermission(ClientPermissions.Unban), IsDownloading = FileSender.ActiveTransfers.Any(t => t.Connection == client.Connection) }; @@ -3383,7 +3380,7 @@ namespace Barotrauma.Networking private IEnumerable SendClientPermissionsAfterClientListSynced(Client recipient, Client client) { DateTime timeOut = DateTime.Now + new TimeSpan(0, 0, 10); - while (recipient.LastRecvClientListUpdate < LastClientListUpdateID) + while (NetIdUtils.IdMoreRecent(LastClientListUpdateID, recipient.LastRecvClientListUpdate)) { if (DateTime.Now > timeOut || GameMain.Server == null || !connectedClients.Contains(recipient)) { diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/LidgrenServerPeer.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/LidgrenServerPeer.cs index c49417f23..b231a8672 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/LidgrenServerPeer.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/LidgrenServerPeer.cs @@ -225,7 +225,7 @@ namespace Barotrauma.Networking } else if (!packetHeader.IsConnectionInitializationStep()) { - if (!(connectedClients.Find(c => c is LidgrenConnection l && l.NetConnection == lidgrenMsg.SenderConnection) is LidgrenConnection conn)) + if (connectedClients.Find(c => c is LidgrenConnection l && l.NetConnection == lidgrenMsg.SenderConnection) is not LidgrenConnection conn) { if (pendingClient != null) { @@ -373,7 +373,7 @@ namespace Barotrauma.Networking { if (netServer == null) { return; } - if (!(conn is LidgrenConnection lidgrenConn)) { return; } + if (conn is not LidgrenConnection lidgrenConn) { return; } if (connectedClients.Contains(lidgrenConn)) { @@ -432,7 +432,7 @@ namespace Barotrauma.Networking } else { - if (!packet.SteamId.TryUnwrap(out var id) || !(id is SteamId steamId)) + if (!packet.SteamId.TryUnwrap(out var id) || id is not SteamId steamId) { if (requireSteamAuth) { diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/ServerPeer.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/ServerPeer.cs index bc8acf2d6..4e16b15bb 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/ServerPeer.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/ServerPeer.cs @@ -167,7 +167,14 @@ namespace Barotrauma.Networking if (pendingClient.AccountInfo.AccountId.TryUnwrap(out var id)) { banAccountId(id); } pendingClient.AccountInfo.OtherMatchingIds.ForEach(banAccountId); - serverSettings.BanList.BanPlayer(pendingClient.Name ?? "Player", pendingClient.Connection.Endpoint, banReason, duration); + if (pendingClient.AccountInfo.AccountId.TryUnwrap(out var accountId)) + { + serverSettings.BanList.BanPlayer(pendingClient.Name ?? "Player", accountId, banReason, duration); + } + else + { + serverSettings.BanList.BanPlayer(pendingClient.Name ?? "Player", pendingClient.Connection.Endpoint, banReason, duration); + } } protected bool IsPendingClientBanned(PendingClient pendingClient, out string? banReason) diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/SteamP2PServerPeer.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/SteamP2PServerPeer.cs index 8bf68f4b5..b51065601 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/SteamP2PServerPeer.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/SteamP2PServerPeer.cs @@ -1,8 +1,6 @@ #nullable enable using System; using System.Collections.Generic; -using System.Linq; -using Microsoft.Xna.Framework; namespace Barotrauma.Networking { @@ -139,7 +137,27 @@ namespace Barotrauma.Networking pendingClient?.Heartbeat(); connectedClient?.Heartbeat(); - if (serverSettings.BanList.IsBanned(senderSteamId, out string banReason) || + if (packetHeader.IsConnectionInitializationStep()) + { + if (!initialization.HasValue) { return; } + ConnectionInitialization initializationStep = initialization.Value; + + if (pendingClient != null) + { + pendingClient.Connection.SetAccountInfo(new AccountInfo(senderSteamId, sentOwnerSteamId)); + ReadConnectionInitializationStep( + pendingClient, + new ReadWriteMessage(inc.Buffer, inc.BitPosition, inc.LengthBits, false), + initializationStep); + } + else if (initializationStep == ConnectionInitialization.ConnectionStarted) + { + pendingClient = new PendingClient(new SteamP2PConnection(senderSteamId)); + pendingClient.Connection.SetAccountInfo(new AccountInfo(senderSteamId, sentOwnerSteamId)); + pendingClients.Add(pendingClient); + } + } + else if (serverSettings.BanList.IsBanned(senderSteamId, out string banReason) || serverSettings.BanList.IsBanned(sentOwnerSteamId, out banReason)) { if (pendingClient != null) @@ -167,24 +185,6 @@ namespace Barotrauma.Networking //message exists solely as a heartbeat, ignore its contents return; } - else if (packetHeader.IsConnectionInitializationStep()) - { - if (!initialization.HasValue) { return; } - ConnectionInitialization initializationStep = initialization.Value; - - if (pendingClient != null) - { - pendingClient.Connection.SetAccountInfo(new AccountInfo(senderSteamId, sentOwnerSteamId)); - ReadConnectionInitializationStep( - pendingClient, - new ReadWriteMessage(inc.Buffer, inc.BitPosition, inc.LengthBits, false), - initializationStep); - } - else if (initializationStep == ConnectionInitialization.ConnectionStarted) - { - pendingClients.Add(new PendingClient(new SteamP2PConnection(senderSteamId))); - } - } else if (connectedClient != null) { var packet = INetSerializableStruct.Read(inc); @@ -248,7 +248,7 @@ namespace Barotrauma.Networking { if (!started) { return; } - if (!(conn is SteamP2PConnection steamP2PConn)) { return; } + if (conn is not SteamP2PConnection steamP2PConn) { return; } if (!connectedClients.Contains(steamP2PConn) && conn != OwnerConnection) { @@ -256,7 +256,7 @@ namespace Barotrauma.Networking return; } - if (!conn.AccountInfo.AccountId.TryUnwrap(out var connAccountId) || !(connAccountId is SteamId connSteamId)) { return; } + if (!conn.AccountInfo.AccountId.TryUnwrap(out var connAccountId) || connAccountId is not SteamId) { return; } byte[] bufAux = msg.PrepareForSending(compressPastThreshold, out bool isCompressed, out _); @@ -292,9 +292,9 @@ namespace Barotrauma.Networking { if (!started) { return; } - if (!(conn is SteamP2PConnection steamp2pConn)) { return; } + if (conn is not SteamP2PConnection steamp2pConn) { return; } - if (!conn.AccountInfo.AccountId.TryUnwrap(out var connAccountId) || !(connAccountId is SteamId connSteamId)) { return; } + if (!conn.AccountInfo.AccountId.TryUnwrap(out var connAccountId) || connAccountId is not SteamId connSteamId) { return; } SendDisconnectMessage(connSteamId, peerDisconnectPacket); diff --git a/Barotrauma/BarotraumaServer/WindowsServer.csproj b/Barotrauma/BarotraumaServer/WindowsServer.csproj index 68ab0885b..ac8ceabb6 100644 --- a/Barotrauma/BarotraumaServer/WindowsServer.csproj +++ b/Barotrauma/BarotraumaServer/WindowsServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.19.5.0 + 0.19.8.0 Copyright © FakeFish 2018-2022 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs index 62f664d39..3ed35dd0a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs @@ -466,7 +466,12 @@ namespace Barotrauma } } } - steeringManager.Update(Character.AnimController.GetCurrentSpeed(run && Character.CanRun)); + + //if someone is grabbing the bot and the bot isn't trying to run anywhere, let them keep dragging and "control" the bot + if (Character.SelectedBy == null || run) + { + steeringManager.Update(Character.AnimController.GetCurrentSpeed(run && Character.CanRun)); + } bool ignorePlatforms = Character.AnimController.TargetMovement.Y < -0.5f && (-Character.AnimController.TargetMovement.Y > Math.Abs(Character.AnimController.TargetMovement.X)); if (steeringManager == insideSteering) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs index 625a177a7..c0c6f9c69 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs @@ -913,11 +913,14 @@ namespace Barotrauma private void RemoveFollowTarget() { - if (arrestingRegistered) + if (followTargetObjective != null) { - followTargetObjective.Completed -= OnArrestTargetReached; + if (arrestingRegistered) + { + followTargetObjective.Completed -= OnArrestTargetReached; + } + RemoveSubObjective(ref followTargetObjective); } - RemoveSubObjective(ref followTargetObjective); arrestingRegistered = false; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGoTo.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGoTo.cs index ff815f159..d8607e729 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGoTo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGoTo.cs @@ -595,6 +595,10 @@ namespace Barotrauma { return c.CurrentHull; } + else if (target is Structure structure) + { + return Hull.FindHull(structure.Position, useWorldCoordinates: false); + } else if (target is Gap g) { return g.FlowTargetHull; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveLoadItem.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveLoadItem.cs index cfd3e7ca9..b8c94aca0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveLoadItem.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveLoadItem.cs @@ -54,7 +54,7 @@ namespace Barotrauma if (ValidContainableItemIdentifiers.None()) { #if DEBUG - DebugConsole.ShowError($"No valid containable item identifiers found for the Load Item objective targeting {Container}"); + DebugConsole.LogError($"No valid containable item identifiers found for the Load Item objective targeting {Container}"); #endif Abandon = true; return; @@ -250,7 +250,7 @@ namespace Barotrauma catch (NotImplementedException) { #if DEBUG - DebugConsole.ShowError($"Unexpected target condition \"{TargetItemCondition}\" in local function GetConditionBasedProperty"); + DebugConsole.LogError($"Unexpected target condition \"{TargetItemCondition}\" in local function GetConditionBasedProperty"); #endif return 0.0f; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveLoadItems.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveLoadItems.cs index a5b9338dc..c54980f2e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveLoadItems.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveLoadItems.cs @@ -90,7 +90,7 @@ namespace Barotrauma catch (NotImplementedException) { #if DEBUG - DebugConsole.ShowError($"Unexpected target condition \"{targetCondition}\" in AIObjectiveLoadItems.ItemMatchesTargetCondition"); + DebugConsole.LogError($"Unexpected target condition \"{targetCondition}\" in AIObjectiveLoadItems.ItemMatchesTargetCondition"); #endif return false; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveOperateItem.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveOperateItem.cs index 56a453007..aed77cb77 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveOperateItem.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveOperateItem.cs @@ -37,6 +37,8 @@ namespace Barotrauma public Func completionCondition; private bool isDoneOperating; + public float? OverridePriority = null; + protected override float GetPriority() { bool isOrder = objectiveManager.IsOrder(this); @@ -52,7 +54,11 @@ namespace Barotrauma } else { - if (isOrder) + if (OverridePriority.HasValue) + { + Priority = OverridePriority.Value; + } + else if (isOrder) { Priority = objectiveManager.GetOrderPriority(this); } @@ -135,7 +141,7 @@ namespace Barotrauma float value = CumulatedDevotion + (max * PriorityModifier); Priority = MathHelper.Clamp(value, 0, max); } - else + else if (!OverridePriority.HasValue) { float value = CumulatedDevotion + (AIObjectiveManager.LowestOrderPriority * PriorityModifier); float max = AIObjectiveManager.LowestOrderPriority - 1; @@ -204,8 +210,15 @@ namespace Barotrauma { if (!character.IsClimbing && character.CanInteractWith(target.Item, out _, checkLinked: false)) { - HumanAIController.FaceTarget(target.Item); - if (character.SelectedItem != target.Item) + if (target.Item.GetComponent() is not Controller { ControlCharacterPose: true }) + { + HumanAIController.FaceTarget(target.Item); + } + else + { + HumanAIController.SteeringManager.Reset(); + } + if (character.SelectedItem != target.Item && character.SelectedSecondaryItem != target.Item) { target.Item.TryInteract(character, forceSelectKey: true); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRescue.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRescue.cs index c700ff0ad..30d687a2a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRescue.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRescue.cs @@ -278,7 +278,7 @@ namespace Barotrauma float cprSuitability = targetCharacter.Oxygen < 0.0f ? -targetCharacter.Oxygen * 100.0f : 0.0f; //find which treatments are the most suitable to treat the character's current condition - targetCharacter.CharacterHealth.GetSuitableTreatments(currentTreatmentSuitabilities, normalize: false, predictFutureDuration: 10.0f); + targetCharacter.CharacterHealth.GetSuitableTreatments(currentTreatmentSuitabilities, user: character, normalize: false, predictFutureDuration: 10.0f); //check if we already have a suitable treatment for any of the afflictions foreach (Affliction affliction in GetSortedAfflictions(targetCharacter)) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Order.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Order.cs index b9c716c78..042e913cd 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Order.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Order.cs @@ -3,9 +3,8 @@ using Barotrauma.Items.Components; using Microsoft.Xna.Framework; using System; using System.Collections.Generic; -using System.Xml.Linq; -using System.Linq; using System.Collections.Immutable; +using System.Linq; namespace Barotrauma { @@ -151,7 +150,7 @@ namespace Barotrauma public OrderPrefab(ContentXElement orderElement, OrdersFile file) : base(file, orderElement.GetAttributeIdentifier("identifier", "")) { Name = TextManager.Get($"OrderName.{Identifier}"); - ContextualName = TextManager.Get($"OrderNameContextual.{Identifier}"); + ContextualName = TextManager.Get($"OrderNameContextual.{Identifier}").Fallback(Name); string targetItemType = orderElement.GetAttributeString("targetitemtype", ""); if (!string.IsNullOrWhiteSpace(targetItemType)) @@ -435,7 +434,7 @@ namespace Barotrauma } catch (NotImplementedException e) { - DebugConsole.ShowError($"Error creating a new Order instance: unexpected target type \"{targetType}\".\n{e.StackTrace.CleanupStackTrace()}"); + DebugConsole.LogError($"Error creating a new Order instance: unexpected target type \"{targetType}\".\n{e.StackTrace.CleanupStackTrace()}"); return null; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs index 02bb65dd5..e2d69fe08 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs @@ -336,7 +336,7 @@ namespace Barotrauma { ApplyTestPose(); } - else + else if (character.SelectedBy == null) { if (character.LockHands) { @@ -356,7 +356,7 @@ namespace Barotrauma HandIK(rightHand, midPos, CurrentAnimationParams.ArmIKStrength, CurrentAnimationParams.HandIKStrength); HandIK(leftHand, midPos, CurrentAnimationParams.ArmIKStrength, CurrentAnimationParams.HandIKStrength); } - if (Anim != Animation.UsingItem && character.SelectedBy == null) + if (Anim != Animation.UsingItem) { if (Anim != Animation.UsingItemWhileClimbing) { @@ -1716,9 +1716,17 @@ namespace Barotrauma } else if (target is AICharacter && target != Character.Controlled) { - target.AnimController.TargetDir = WorldPosition.X > target.WorldPosition.X ? Direction.Right : Direction.Left; - Vector2 movement = (character.SimPosition + Vector2.UnitX * 0.5f * Dir) - target.SimPosition; - target.AnimController.TargetMovement = movement.LengthSquared() > 0.01f ? movement : Vector2.Zero; + if (target.AnimController.Dir > 0 == WorldPosition.X > target.WorldPosition.X) + { + target.AnimController.LockFlippingUntil = (float)Timing.TotalTime + 0.5f; + } + else + { + target.AnimController.TargetDir = WorldPosition.X > target.WorldPosition.X ? Direction.Right : Direction.Left; + } + //make the target stand 0.5 meters away from this character, on the side they're currently at + Vector2 movement = (character.SimPosition + Vector2.UnitX * 0.5f * Math.Sign(target.SimPosition.X - character.SimPosition.X)) - target.SimPosition; + target.AnimController.TargetMovement = movement.LengthSquared() > 0.01f ? movement : Vector2.Zero; } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Attack.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Attack.cs index ee7cdc224..d5d02d2d4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Attack.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Attack.cs @@ -402,9 +402,8 @@ namespace Barotrauma } else { - string afflictionIdentifier = subElement.GetAttributeString("identifier", "").ToLowerInvariant(); - afflictionPrefab = AfflictionPrefab.Prefabs[afflictionIdentifier]; - if (afflictionPrefab == null) + Identifier afflictionIdentifier = subElement.GetAttributeIdentifier("identifier", ""); + if (!AfflictionPrefab.Prefabs.TryGet(afflictionIdentifier, out afflictionPrefab)) { DebugConsole.ThrowError("Error in Attack (" + parentDebugName + ") - Affliction prefab \"" + afflictionIdentifier + "\" not found."); continue; @@ -430,15 +429,13 @@ namespace Barotrauma Afflictions.Clear(); foreach (var subElement in element.GetChildElements("affliction")) { - AfflictionPrefab afflictionPrefab; Affliction affliction; Identifier afflictionIdentifier = subElement.GetAttributeIdentifier("identifier", ""); - if (!AfflictionPrefab.Prefabs.ContainsKey(afflictionIdentifier)) + if (!AfflictionPrefab.Prefabs.TryGet(afflictionIdentifier, out AfflictionPrefab afflictionPrefab)) { DebugConsole.ThrowError($"Error in an Attack defined in \"{parentDebugName}\" - could not find an affliction with the identifier \"{afflictionIdentifier}\"."); continue; } - afflictionPrefab = AfflictionPrefab.Prefabs[afflictionIdentifier]; affliction = afflictionPrefab.Instantiate(0.0f); affliction.Deserialize(subElement); //backwards compatibility diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs index dcb2deff3..b03d51803 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs @@ -549,6 +549,10 @@ namespace Barotrauma set { lockHandsTimer = MathHelper.Clamp(lockHandsTimer + (value ? 1.0f : -0.5f), 0.0f, 10.0f); + if (value) + { + SelectedCharacter = null; + } #if CLIENT HintManager.OnHandcuffed(this); #endif @@ -599,13 +603,10 @@ namespace Barotrauma get { return selectedCharacter; } set { - if (value == selectedCharacter) return; - if (selectedCharacter != null) - selectedCharacter.selectedBy = null; + if (value == selectedCharacter) { return; } + if (selectedCharacter != null) { selectedCharacter.selectedBy = null; } selectedCharacter = value; - if (selectedCharacter != null) - selectedCharacter.selectedBy = this; - + if (selectedCharacter != null) {selectedCharacter.selectedBy = this; } #if CLIENT CharacterHealth.SetHealthBarVisibility(value == null); #endif @@ -707,6 +708,14 @@ namespace Barotrauma get { return CurrentHull == null || CurrentHull.LethalPressure > 5.0f; } } + /// + /// Can be used by status effects + /// + public AnimController.Animation Anim + { + get { return AnimController?.Anim ?? AnimController.Animation.None; } + } + public const float KnockbackCooldown = 5.0f; public float KnockbackCooldownTimer; @@ -2620,7 +2629,7 @@ namespace Barotrauma if (!AllowInput) { FocusedCharacter = null; - if (SelectedCharacter != null) DeselectCharacter(); + if (SelectedCharacter != null) { DeselectCharacter(); } return; } } @@ -3636,7 +3645,7 @@ namespace Barotrauma string modifiedMessage = ChatMessage.ApplyDistanceEffect(message.Message, message.MessageType.Value, this, Controlled); if (!string.IsNullOrEmpty(modifiedMessage)) { - GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(info.Name, modifiedMessage, message.MessageType.Value, this); + GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(Name, modifiedMessage, message.MessageType.Value, this); } } #endif @@ -4020,6 +4029,7 @@ namespace Barotrauma if (newStun > 0.0f) { SelectedItem = SelectedSecondaryItem = null; + if (SelectedCharacter != null) { DeselectCharacter(); } } HealthUpdateInterval = 0.0f; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterEventData.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterEventData.cs index e8b58578b..9786888d3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterEventData.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterEventData.cs @@ -1,8 +1,9 @@ -using System.Collections.Generic; -using System.Collections.Immutable; -using Barotrauma.Items.Components; -using Barotrauma.Networking; +using Barotrauma.Networking; using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; namespace Barotrauma { @@ -25,9 +26,10 @@ namespace Barotrauma UpdateSkills = 12, UpdateMoney = 13, UpdatePermanentStats = 14, + RemoveFromCrew = 15, MinValue = 0, - MaxValue = 14 + MaxValue = 15 } private interface IEventData : NetEntityEvent.IData @@ -133,18 +135,30 @@ namespace Barotrauma public EventType EventType => EventType.TeamChange; } + [NetworkSerialize] + public readonly record struct ItemTeamChange(CharacterTeamType TeamId, ImmutableArray ItemIds) : INetSerializableStruct; + + public struct AddToCrewEventData : IEventData { public EventType EventType => EventType.AddToCrew; - public readonly CharacterTeamType TeamType; - public readonly ImmutableArray InventoryItems; + public readonly ItemTeamChange ItemTeamChange; public AddToCrewEventData(CharacterTeamType teamType, IEnumerable inventoryItems) { - TeamType = teamType; - InventoryItems = inventoryItems.ToImmutableArray(); + ItemTeamChange = new ItemTeamChange(teamType, inventoryItems.Select(it => it.ID).ToImmutableArray()); + } + } + + public struct RemoveFromCrewEventData : IEventData + { + public EventType EventType => EventType.RemoveFromCrew; + public readonly ItemTeamChange ItemTeamChange; + + public RemoveFromCrewEventData(CharacterTeamType teamType, IEnumerable inventoryItems) + { + ItemTeamChange = new ItemTeamChange(teamType, inventoryItems.Select(it => it.ID).ToImmutableArray()); } - } public struct UpdateExperienceEventData : IEventData diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterNetworking.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterNetworking.cs index 29250acfd..5d8e294ec 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterNetworking.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterNetworking.cs @@ -82,10 +82,10 @@ namespace Barotrauma public UInt16 networkUpdateID; } - private List memInput = new List(); + private readonly List memInput = new List(); - private List memState = new List(); - private List memLocalState = new List(); + private readonly List memState = new List(); + private readonly List memLocalState = new List(); public float healthUpdateTimer; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs index 685429eb9..cd028c9ca 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs @@ -1038,7 +1038,7 @@ namespace Barotrauma /// A dictionary where the key is the identifier of the item and the value the suitability /// If true, the suitability values are normalized between 0 and 1. If not, they're arbitrary values defined in the medical item XML, where negative values are unsuitable, and positive ones suitable. /// If above 0, the method will take into account how much currently active status effects while affect the afflictions in the next x seconds. - public void GetSuitableTreatments(Dictionary treatmentSuitability, bool normalize, Limb limb = null, bool ignoreHiddenAfflictions = false, float predictFutureDuration = 0.0f) + public void GetSuitableTreatments(Dictionary treatmentSuitability, bool normalize, Character user, Limb limb = null, bool ignoreHiddenAfflictions = false, float predictFutureDuration = 0.0f) { //key = item identifier //float = suitability @@ -1062,7 +1062,18 @@ namespace Barotrauma } if (strength <= affliction.Prefab.TreatmentThreshold) { continue; } - if (ignoreHiddenAfflictions && strength < affliction.Prefab.ShowIconThreshold) { continue; } + + if (ignoreHiddenAfflictions) + { + if (user == Character) + { + if (strength < affliction.Prefab.ShowIconThreshold) { continue; } + } + else + { + if (strength < affliction.Prefab.ShowIconToOthersThreshold) { continue; } + } + } foreach (KeyValuePair treatment in affliction.Prefab.TreatmentSuitability) { @@ -1153,10 +1164,10 @@ namespace Barotrauma msg.WriteRangedSingle( MathHelper.Clamp(affliction.Strength, 0.0f, affliction.Prefab.MaxStrength), 0.0f, affliction.Prefab.MaxStrength, 8); - msg.WriteByte((byte)affliction.Prefab.PeriodicEffects.Count()); + msg.WriteByte((byte)affliction.Prefab.PeriodicEffects.Count); foreach (AfflictionPrefab.PeriodicEffect periodicEffect in affliction.Prefab.PeriodicEffects) { - msg.WriteRangedSingle(affliction.PeriodicEffectTimers[periodicEffect], periodicEffect.MinInterval, periodicEffect.MaxInterval, 8); + msg.WriteRangedSingle(affliction.PeriodicEffectTimers[periodicEffect], 0, periodicEffect.MaxInterval, 8); } } @@ -1178,7 +1189,7 @@ namespace Barotrauma msg.WriteRangedSingle( MathHelper.Clamp(affliction.Strength, 0.0f, affliction.Prefab.MaxStrength), 0.0f, affliction.Prefab.MaxStrength, 8); - msg.WriteByte((byte)affliction.Prefab.PeriodicEffects.Count()); + msg.WriteByte((byte)affliction.Prefab.PeriodicEffects.Count); foreach (AfflictionPrefab.PeriodicEffect periodicEffect in affliction.Prefab.PeriodicEffects) { msg.WriteRangedSingle(affliction.PeriodicEffectTimers[periodicEffect], periodicEffect.MinInterval, periodicEffect.MaxInterval, 8); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/DamageModifier.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/DamageModifier.cs index 07f16e007..a71d1e73e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/DamageModifier.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/DamageModifier.cs @@ -86,6 +86,28 @@ namespace Barotrauma { DebugConsole.ThrowError("Error in DamageModifier config (" + parentDebugName + ") - define afflictions using identifiers or types instead of names."); } + foreach (var afflictionType in parsedAfflictionTypes) + { + if (!AfflictionPrefab.Prefabs.Any(p => p.AfflictionType == afflictionType)) + { + createWarningOrError($"Potentially invalid damage modifier in \"{parentDebugName}\". Could not find any afflictions of the type \"{afflictionType}\". Did you mean to use an affliction identifier instead?"); + } + } + foreach (var afflictionIdentifier in parsedAfflictionIdentifiers) + { + if (!AfflictionPrefab.Prefabs.ContainsKey(afflictionIdentifier)) + { + createWarningOrError($"Potentially invalid damage modifier in \"{parentDebugName}\". Could not find any afflictions with the identifier \"{afflictionIdentifier}\". Did you mean to use an affliction type instead?"); + } + } + static void createWarningOrError(string msg) + { +#if DEBUG + DebugConsole.ThrowError(msg); +#else + DebugConsole.AddWarning(msg); +#endif + } } private void ParseAfflictionTypes() diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityTandemFire.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityTandemFire.cs index a534df7f5..d58d73b49 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityTandemFire.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityTandemFire.cs @@ -1,8 +1,4 @@ -using Barotrauma.Items.Components; -using Microsoft.Xna.Framework; -using System.Collections.Generic; -using System.Linq; -using System.Xml.Linq; +using Microsoft.Xna.Framework; namespace Barotrauma.Abilities { @@ -31,7 +27,7 @@ namespace Barotrauma.Abilities } } - if (!SelectedItemHasTag(closestCharacter)) { return; } + if (closestCharacter == null || !SelectedItemHasTag(closestCharacter)) { return; } if (closestDistance < squaredMaxDistance) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs b/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs index 3899632f6..9a7710898 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs @@ -1267,7 +1267,11 @@ namespace Barotrauma } }, () => { - return new[] { FactionPrefab.Prefabs.Select(f => f.Identifier.Value).ToArray() }; + return new[] + { + FactionPrefab.Prefabs.Select(f => f.Identifier.Value).ToArray(), + GameMain.GameSession?.Campaign.Factions.Select(f => f.Prefab.Identifier.ToString()).ToArray() ?? Array.Empty() + }; }, true)); commands.Add(new Command("fixitems", "fixitems: Repairs all items and restores them to full condition.", (string[] args) => @@ -2235,7 +2239,7 @@ namespace Barotrauma } } - public static void ShowError(string msg, Color? color = null) + public static void LogError(string msg, Color? color = null) { color ??= Color.Red; NewMessage(msg, color.Value, isCommand: false, isError: true); @@ -2407,7 +2411,7 @@ namespace Barotrauma } #endif - ShowError(error); + LogError(error); } public static void AddWarning(string warning) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/BinaryOptionAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/BinaryOptionAction.cs index 05f27d592..ae088e51c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/BinaryOptionAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/BinaryOptionAction.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Xml.Linq; namespace Barotrauma { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckAfflictionAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckAfflictionAction.cs index 953d6f31f..ca7fcad9d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckAfflictionAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckAfflictionAction.cs @@ -38,10 +38,12 @@ namespace Barotrauma } IEnumerable afflictions = target.CharacterHealth.GetAllAfflictions().Where(affliction => { - LimbType? limbType = target.CharacterHealth.GetAfflictionLimb(affliction)?.type; - if (limbType == null) { return false; } - - return limbType == TargetLimb && affliction.Strength >= MinStrength; + if (affliction.Prefab.LimbSpecific) + { + LimbType? limbType = target.CharacterHealth.GetAfflictionLimb(affliction)?.type; + if (limbType == null || limbType != TargetLimb) { return false; } + } + return affliction.Strength >= MinStrength; }); if (afflictions.Any(a => a.Identifier == Identifier)) { return true; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckConditionalAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckConditionalAction.cs index 1cda074e2..cce7bd22c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckConditionalAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckConditionalAction.cs @@ -13,7 +13,7 @@ namespace Barotrauma { if (TargetTag.IsEmpty) { - DebugConsole.ShowError($"CheckConditionalAction error: {GetEventName()} uses a CheckConditionalAction with no target tag! This will cause the check to automatically succeed."); + DebugConsole.LogError($"CheckConditionalAction error: {GetEventName()} uses a CheckConditionalAction with no target tag! This will cause the check to automatically succeed."); } foreach (var attribute in element.Attributes()) { @@ -25,7 +25,7 @@ namespace Barotrauma } if (Conditional == null) { - DebugConsole.ShowError($"CheckConditionalAction error: {GetEventName()} uses a CheckConditionalAction with no valid PropertyConditional! This will cause the check to automatically succeed."); + DebugConsole.LogError($"CheckConditionalAction error: {GetEventName()} uses a CheckConditionalAction with no valid PropertyConditional! This will cause the check to automatically succeed."); } static bool IsTargetTagAttribute(XAttribute attribute) => attribute.NameAsIdentifier() == "targettag"; @@ -52,7 +52,7 @@ namespace Barotrauma } if (target == null) { - DebugConsole.ShowError($"CheckConditionalAction error: {GetEventName()} uses a CheckConditionalAction but no valid target was found for tag \"{TargetTag}\"! This will cause the check to automatically succeed."); + DebugConsole.LogError($"CheckConditionalAction error: {GetEventName()} uses a CheckConditionalAction but no valid target was found for tag \"{TargetTag}\"! This will cause the check to automatically succeed."); } if (target == null || Conditional == null) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckConnectionAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckConnectionAction.cs index f2d7af787..56f6b82e2 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckConnectionAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckConnectionAction.cs @@ -18,10 +18,14 @@ class CheckConnectionAction : BinaryOptionAction [Serialize("", IsPropertySaveable.Yes)] public Identifier OtherConnectionName { get; set; } + [Serialize(1, IsPropertySaveable.Yes)] + public int MinAmount { get; set; } + public CheckConnectionAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { } protected override bool? DetermineSuccess() { + int amount = 0; var connectTargets = !ConnectedItemTag.IsEmpty ? ParentEvent.GetTargets(ConnectedItemTag) : Enumerable.Empty(); foreach (var target in ParentEvent.GetTargets(ItemTag)) { @@ -33,27 +37,17 @@ class CheckConnectionAction : BinaryOptionAction if (!IsCorrectConnection(connection, ConnectionName)) { continue; } if (ConnectedItemTag.IsEmpty && OtherConnectionName.IsEmpty) { - if (connection.Wires.Any()) { return true; } + amount += connection.Wires.Count(); + if (amount >= MinAmount) { return true; } continue; } foreach (var wire in connection.Wires) { if (wire.OtherConnection(connection) is not Connection otherConnection) { continue; } - if (ConnectedItemTag.IsEmpty) - { - if (IsCorrectConnection(otherConnection, OtherConnectionName)) { return true; } - } - else if (OtherConnectionName.IsEmpty) - { - if (IsCorrectItem()) { return true; } - } - else - { - if (!IsCorrectConnection(otherConnection, OtherConnectionName)) { continue; } - if (!IsCorrectItem()) { continue; } - return true; - } - + if (!ConnectedItemTag.IsEmpty && !IsCorrectConnection(otherConnection, OtherConnectionName)) { continue; } + if (!ConnectedItemTag.IsEmpty && !IsCorrectItem()) { continue; } + amount++; + if (amount >= MinAmount) { return true; } bool IsCorrectItem() => connectTargets.Contains(otherConnection.Item); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckDataAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckDataAction.cs index 92ff33999..47c205aee 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckDataAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckDataAction.cs @@ -1,7 +1,6 @@ #nullable enable using System; using System.Linq; -using System.Xml.Linq; namespace Barotrauma { @@ -36,15 +35,32 @@ namespace Barotrauma } } + public CheckDataAction(ContentXElement element, string parentDebugString) : base(null, element) + { + if (string.IsNullOrEmpty(Condition)) + { + Condition = element.GetAttributeString("value", string.Empty)!; + if (string.IsNullOrEmpty(Condition)) + { + DebugConsole.ThrowError($"Error in scripted event \"{parentDebugString}\". CheckDataAction with no condition set ({element})."); + } + } + } + + public bool GetSuccess() + { + return DetermineSuccess() ?? false; + } + protected override bool? DetermineSuccess() { - if (!(GameMain.GameSession?.GameMode is CampaignMode campaignMode)) { return false; } + if (GameMain.GameSession?.GameMode is not CampaignMode campaignMode) { return false; } string[] splitString = Condition.Split(' '); - string value = Condition; + string value; if (splitString.Length > 0) { - #warning Is this correct? + //the first part of the string is the operator, skip it value = string.Join(" ", splitString.Skip(1)); } else diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckItemAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckItemAction.cs index dba8364ef..9413193d1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckItemAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckItemAction.cs @@ -1,4 +1,5 @@ -using System; +using Barotrauma.Items.Components; +using System.Collections.Generic; using System.Linq; using System.Xml.Linq; @@ -15,8 +16,19 @@ namespace Barotrauma [Serialize("", IsPropertySaveable.Yes)] public string ItemTags { get; set; } + [Serialize(1, IsPropertySaveable.Yes)] + public int Amount { get; set; } + + [Serialize("", IsPropertySaveable.Yes, description: "Tag to apply to the first target when the check succeeds.")] + public Identifier ApplyTagToTarget { get; set; } + [Serialize(false, IsPropertySaveable.Yes)] public bool RequireEquipped { get; set; } + + [Serialize(-1, IsPropertySaveable.Yes)] + public int ItemContainerIndex { get; set; } + + private readonly IReadOnlyList conditionals; private readonly Identifier[] itemIdentifierSplit; private readonly Identifier[] itemTags; @@ -25,6 +37,19 @@ namespace Barotrauma { itemIdentifierSplit = ItemIdentifiers.Split(',').ToIdentifiers(); itemTags = ItemTags.Split(",").ToIdentifiers(); + var conditionalList = new List(); + foreach (ContentXElement subElement in element.GetChildElements("conditional")) + { + foreach (XAttribute attribute in subElement.Attributes()) + { + if (PropertyConditional.IsValid(attribute)) + { + conditionalList.Add(new PropertyConditional(attribute)); + } + } + break; + } + conditionals = conditionalList; } protected override bool? DetermineSuccess() @@ -35,25 +60,70 @@ namespace Barotrauma { if (target is Character character) { - if (RequireEquipped) + Inventory inventory = character.Inventory; + if (CheckInventory(character.Inventory, character)) { - if (itemTags.Any(tag => character.HasEquippedItem(tag))) { return true; } - if (itemIdentifierSplit.Any(identifier => character.HasEquippedItem(identifier))) { return true; } - return false; + if (!ApplyTagToTarget.IsEmpty) + { + ParentEvent.AddTarget(ApplyTagToTarget, target); + } + return true; } - if (character.Inventory is not CharacterInventory inventory) { continue; } - if (itemTags.Any(tag => inventory.FindItemByTag(tag, recursive: true) is not null)) { return true; } - if (itemIdentifierSplit.Any(identifier => inventory.FindItemByIdentifier(identifier, recursive: true) is not null)) { return true; } } - else if (target is Item item && item.OwnInventory is ItemInventory inventory) + else if (target is Item item) { - if (itemTags.Any(tag => inventory.FindItemByTag(tag, recursive: true) is not null)) { return true; } - if (itemIdentifierSplit.Any(identifier => inventory.FindItemByIdentifier(identifier, recursive: true) is not null)) { return true; } + int i = 0; + foreach (var itemContainer in item.GetComponents()) + { + if (ItemContainerIndex == -1 || i == ItemContainerIndex) + { + if (CheckInventory(itemContainer.Inventory, character: null)) + { + if (!ApplyTagToTarget.IsEmpty) + { + ParentEvent.AddTarget(ApplyTagToTarget, target); + } + return true; + } + } + i++; + } } } return false; } + private bool CheckInventory(Inventory inventory, Character character) + { + if (inventory == null) { return false; } + int count = 0; + foreach (Item item in inventory.FindAllItems(it => itemTags.Any(it.HasTag) || itemIdentifierSplit.Contains(it.Prefab.Identifier))) + { + if (!ConditionalsMatch(item, character)) { continue; } + count++; + if (count >= Amount) { return true; } + } + return false; + } + + private bool ConditionalsMatch(Item item, Character character = null) + { + if (item == null) { return false; } + foreach (PropertyConditional conditional in conditionals) + { + if (!conditional.Matches(item)) + { + return false; + } + } + if (RequireEquipped) + { + if (character == null) { return false; } + return character.HasEquippedItem(item); + } + return true; + } + public override string ToDebugString() { return $"{ToolBox.GetDebugSymbol(HasBeenDetermined())} {nameof(CheckItemAction)} -> (TargetTag: {TargetTag.ColorizeObject()}, " + diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckOrderAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckOrderAction.cs index 0803242f1..6723d3abb 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckOrderAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckOrderAction.cs @@ -11,6 +11,9 @@ namespace Barotrauma [Serialize("", IsPropertySaveable.Yes)] public Identifier OrderOption { get; set; } + [Serialize("", IsPropertySaveable.Yes)] + public Identifier OrderTargetTag { get; set; } + public CheckOrderAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { } protected override bool? DetermineSuccess() @@ -29,20 +32,17 @@ namespace Barotrauma } if (targetCharacter == null) { - DebugConsole.ShowError($"CheckConditionalAction error: {GetEventName()} uses a CheckOrderAction but no valid target character was found for tag \"{TargetTag}\"! This will cause the check to automatically fail."); + DebugConsole.LogError($"CheckConditionalAction error: {GetEventName()} uses a CheckOrderAction but no valid target character was found for tag \"{TargetTag}\"! This will cause the check to automatically fail."); return false; } var currentOrderInfo = targetCharacter.GetCurrentOrderWithTopPriority(); if (currentOrderInfo?.Identifier == OrderIdentifier) { - if (OrderOption.IsEmpty) + if (!OrderTargetTag.IsEmpty) { - return true; - } - else - { - return currentOrderInfo?.Option == OrderOption; + if (currentOrderInfo.TargetEntity is not Item targetItem || !targetItem.HasTag(OrderTargetTag)) { return false; } } + return OrderOption.IsEmpty || currentOrderInfo?.Option == OrderOption; } return false; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckSelectedAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckSelectedAction.cs new file mode 100644 index 000000000..6f1cb5867 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckSelectedAction.cs @@ -0,0 +1,89 @@ +using Barotrauma.Extensions; +using System.Collections.Generic; + +namespace Barotrauma +{ + class CheckSelectedItemAction : BinaryOptionAction + { + public enum SelectedItemType { Primary, Secondary, Any }; + + [Serialize("", IsPropertySaveable.Yes)] + public Identifier CharacterTag { get; set; } + + [Serialize("", IsPropertySaveable.Yes)] + public Identifier TargetTag { get; set; } + + [Serialize(SelectedItemType.Any, IsPropertySaveable.Yes)] + public SelectedItemType ItemType { get; set; } + + public CheckSelectedItemAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { } + + protected override bool? DetermineSuccess() + { + Character character = null; + if (!CharacterTag.IsEmpty) + { + foreach (var t in ParentEvent.GetTargets(CharacterTag)) + { + if (t is Character c) + { + character = c; + break; + } + } + } + if (character == null) + { + DebugConsole.LogError($"CheckSelectedItemAction error: {GetEventName()} uses a CheckSelectedItemAction but no valid character was found for tag \"{CharacterTag}\"! This will cause the check to automatically fail."); + return false; + } + if (!TargetTag.IsEmpty) + { + IEnumerable targets = ParentEvent.GetTargets(TargetTag); + if (targets.None()) + { + DebugConsole.LogError($"CheckSelectedItemAction error: {GetEventName()} uses a CheckSelectedItemAction but no valid targets were found for tag \"{TargetTag}\"! This will cause the check to automatically fail."); + return false; + } + foreach (var target in targets) + { + if (target is not Item targetItem) + { + continue; + } + if (IsSelected(targetItem)) + { + return true; + } + } + return false; + + bool IsSelected(Item item) + { + return ItemType switch + { + SelectedItemType.Any => character.IsAnySelectedItem(item), + SelectedItemType.Primary => character.SelectedItem == item, + SelectedItemType.Secondary => character.SelectedSecondaryItem == item, + _ => false + }; + } + } + else + { + return ItemType switch + { + SelectedItemType.Any => !character.HasSelectedAnyItem, + SelectedItemType.Primary => character.SelectedItem == null, + SelectedItemType.Secondary => character.SelectedSecondaryItem == null, + _ => false + }; + } + } + + private string GetEventName() + { + return ParentEvent?.Prefab?.Identifier is { IsEmpty: false } identifier ? $"the event \"{identifier}\"" : "an unknown event"; + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckSelectedItemAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckSelectedItemAction.cs index 1c9903f6a..6de01c0e8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckSelectedItemAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckSelectedItemAction.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; namespace Barotrauma { - class CheckSelectedItemAction : BinaryOptionAction + class CheckSelectedAction : BinaryOptionAction { public enum SelectedItemType { Primary, Secondary, Any }; @@ -16,7 +16,7 @@ namespace Barotrauma [Serialize(SelectedItemType.Any, IsPropertySaveable.Yes)] public SelectedItemType ItemType { get; set; } - public CheckSelectedItemAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { } + public CheckSelectedAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { } protected override bool? DetermineSuccess() { @@ -34,7 +34,7 @@ namespace Barotrauma } if (character == null) { - DebugConsole.ShowError($"CheckSelectedItemAction error: {GetEventName()} uses a CheckSelectedItemAction but no valid character was found for tag \"{CharacterTag}\"! This will cause the check to automatically fail."); + DebugConsole.LogError($"CheckSelectedItemAction error: {GetEventName()} uses a CheckSelectedItemAction but no valid character was found for tag \"{CharacterTag}\"! This will cause the check to automatically fail."); return false; } if (!TargetTag.IsEmpty) @@ -42,11 +42,16 @@ namespace Barotrauma IEnumerable targets = ParentEvent.GetTargets(TargetTag); if (targets.None()) { - DebugConsole.ShowError($"CheckSelectedItemAction error: {GetEventName()} uses a CheckSelectedItemAction but no valid targets were found for tag \"{TargetTag}\"! This will cause the check to automatically fail."); + DebugConsole.LogError($"CheckSelectedItemAction error: {GetEventName()} uses a CheckSelectedItemAction but no valid targets were found for tag \"{TargetTag}\"! This will cause the check to automatically fail."); return false; } foreach (var target in targets) { + if (target is Character targetCharacter) + { + if (ItemType == SelectedItemType.Any && character.SelectedCharacter == targetCharacter) { return true; } + continue; + } if (target is not Item targetItem) { continue; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/EventAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/EventAction.cs index b01ede512..c0fc93a5b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/EventAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/EventAction.cs @@ -134,7 +134,7 @@ namespace Barotrauma public static EventAction Instantiate(ScriptedEvent scriptedEvent, ContentXElement element) { - Type actionType = null; + Type actionType; try { actionType = Type.GetType("Barotrauma." + element.Name, true, true); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/GodModeAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/GodModeAction.cs index 47ff662f2..e9995190f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/GodModeAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/GodModeAction.cs @@ -1,8 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Xml.Linq; - namespace Barotrauma { class GodModeAction : EventAction @@ -10,6 +5,9 @@ namespace Barotrauma [Serialize(true, IsPropertySaveable.Yes)] public bool Enabled { get; set; } + [Serialize(false, IsPropertySaveable.Yes, description: "Should the character's active afflictions be updated (e.g. applying visual effects of the afflictions)")] + public bool UpdateAfflictions { get; set; } + [Serialize("", IsPropertySaveable.Yes)] public Identifier TargetTag { get; set; } @@ -35,9 +33,16 @@ namespace Barotrauma { if (target != null && target is Character character) { - character.GodMode = Enabled; + if (UpdateAfflictions) + { + character.CharacterHealth.Unkillable = Enabled; + } + else + { + character.GodMode = Enabled; + } } - } + } isFinished = true; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/InventoryHighlightAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/InventoryHighlightAction.cs new file mode 100644 index 000000000..7eecbbbb2 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/InventoryHighlightAction.cs @@ -0,0 +1,33 @@ +namespace Barotrauma; + +partial class InventoryHighlightAction : EventAction +{ + [Serialize("", IsPropertySaveable.Yes)] + public Identifier TargetTag { get; set; } + + [Serialize("", IsPropertySaveable.Yes)] + public Identifier ItemIdentifier { get; set; } + + [Serialize(-1, IsPropertySaveable.Yes)] + public int ItemContainerIndex { get; set; } + + [Serialize(false, IsPropertySaveable.Yes)] + public bool Recursive { get; set; } + + private bool isFinished; + + public InventoryHighlightAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { } + + public override void Update(float deltaTime) + { + if (isFinished) { return; } + UpdateProjSpecific(); + isFinished = true; + } + + partial void UpdateProjSpecific(); + + public override bool IsFinished(ref string goToLabel) => isFinished; + + public override void Reset() => isFinished = false; +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/MessageBoxAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/MessageBoxAction.cs index 2056c9656..2834b8b84 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/MessageBoxAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/MessageBoxAction.cs @@ -2,7 +2,7 @@ namespace Barotrauma { partial class MessageBoxAction : EventAction { - public enum ActionType { Create, Close } + public enum ActionType { Create, ConnectObjective, Close, Clear } [Serialize(ActionType.Create, IsPropertySaveable.Yes)] public ActionType Type { get; set; } @@ -51,7 +51,13 @@ namespace Barotrauma private bool isFinished = false; - public MessageBoxAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { } + public MessageBoxAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) + { + if (Identifier.IsEmpty) + { + Identifier = element.GetAttributeIdentifier("id", Identifier.Empty); + } + } public override void Update(float deltaTime) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/MissionAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/MissionAction.cs index ff3d4d9bb..2c298853d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/MissionAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/MissionAction.cs @@ -55,7 +55,7 @@ namespace Barotrauma if (GameMain.GameSession.GameMode is CampaignMode campaign) { - MissionPrefab prefab = null; + Mission unlockedMission = null; var unlockLocation = FindUnlockLocation(); if (unlockLocation == null && CreateLocationIfNotFound) { @@ -72,27 +72,34 @@ namespace Barotrauma { if (!MissionIdentifier.IsEmpty) { - prefab = unlockLocation.UnlockMissionByIdentifier(MissionIdentifier); + unlockedMission = unlockLocation.UnlockMissionByIdentifier(MissionIdentifier); } else if (!MissionTag.IsEmpty) { - prefab = unlockLocation.UnlockMissionByTag(MissionTag); + unlockedMission = unlockLocation.UnlockMissionByTag(MissionTag); } if (campaign is MultiPlayerCampaign mpCampaign) { mpCampaign.IncrementLastUpdateIdForFlag(MultiPlayerCampaign.NetFlags.MapAndMissions); } - if (prefab != null) + if (unlockedMission != null) { - DebugConsole.NewMessage($"Unlocked mission \"{prefab.Name}\" in the location \"{unlockLocation.Name}\"."); - #if CLIENT - new GUIMessageBox(string.Empty, TextManager.GetWithVariable("missionunlocked", "[missionname]", prefab.Name), - Array.Empty(), type: GUIMessageBox.Type.InGame, icon: prefab.Icon, relativeSize: new Vector2(0.3f, 0.15f), minSize: new Point(512, 128)) + if (unlockedMission.Locations[0] == unlockedMission.Locations[1] || unlockedMission.Locations[1] ==null) { - IconColor = prefab.IconColor + DebugConsole.NewMessage($"Unlocked mission \"{unlockedMission.Name}\" in the location \"{unlockLocation.Name}\"."); + } + else + { + DebugConsole.NewMessage($"Unlocked mission \"{unlockedMission.Name}\" in the connection from \"{unlockedMission.Locations[0].Name}\" to \"{unlockedMission.Locations[1].Name}\"."); + } +#if CLIENT + new GUIMessageBox(string.Empty, TextManager.GetWithVariable("missionunlocked", "[missionname]", unlockedMission.Name), + Array.Empty(), type: GUIMessageBox.Type.InGame, icon: unlockedMission.Prefab.Icon, relativeSize: new Vector2(0.3f, 0.15f), minSize: new Point(512, 128)) + { + IconColor = unlockedMission.Prefab.IconColor }; - #else - NotifyMissionUnlock(prefab); +#else + NotifyMissionUnlock(unlockedMission); #endif } } @@ -138,16 +145,17 @@ namespace Barotrauma { return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(MissionAction)} -> ({(MissionIdentifier.IsEmpty ? MissionTag : MissionIdentifier)})"; } - + #if SERVER - private void NotifyMissionUnlock(MissionPrefab prefab) + private void NotifyMissionUnlock(Mission mission) { foreach (Client client in GameMain.Server.ConnectedClients) { IWriteMessage outmsg = new WriteOnlyMessage(); outmsg.WriteByte((byte)ServerPacketHeader.EVENTACTION); outmsg.WriteByte((byte)EventManager.NetworkEventType.MISSION); - outmsg.WriteIdentifier(prefab.Identifier); + outmsg.WriteIdentifier(mission.Prefab.Identifier); + outmsg.WriteString(mission.Name.Value); GameMain.Server.ServerPeer.Send(outmsg, client.Connection, DeliveryMethod.Reliable); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/NPCChangeTeamAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/NPCChangeTeamAction.cs index 90e6eb540..143563584 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/NPCChangeTeamAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/NPCChangeTeamAction.cs @@ -16,6 +16,9 @@ namespace Barotrauma [Serialize(false, IsPropertySaveable.Yes)] public bool AddToCrew { get; set; } + [Serialize(false, IsPropertySaveable.Yes)] + public bool RemoveFromCrew { get; set; } + private bool isFinished = false; public NPCChangeTeamAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { } @@ -35,34 +38,47 @@ namespace Barotrauma if (AddToCrew && (TeamTag == CharacterTeamType.Team1 || TeamTag == CharacterTeamType.Team2)) { npc.Info.StartItemsGiven = true; - GameMain.GameSession.CrewManager.AddCharacter(npc); + ChangeItemTeam(Submarine.MainSub, true); + if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer) + { + GameMain.NetworkMember.CreateEntityEvent(npc, new Character.AddToCrewEventData(TeamTag, npc.Inventory.AllItems)); + } + } + else if (RemoveFromCrew && (npc.TeamID == CharacterTeamType.Team1 || npc.TeamID == CharacterTeamType.Team2)) + { + npc.Info.StartItemsGiven = true; + GameMain.GameSession.CrewManager.RemoveCharacter(npc, removeInfo: true); + var sub = Submarine.Loaded.FirstOrDefault(s => s.TeamID == TeamTag); + ChangeItemTeam(sub, false); + if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer) + { + GameMain.NetworkMember.CreateEntityEvent(npc, new Character.RemoveFromCrewEventData(TeamTag, npc.Inventory.AllItems)); + } + } + + void ChangeItemTeam(Submarine sub, bool allowStealing) + { foreach (Item item in npc.Inventory.AllItems) { - item.AllowStealing = true; - var wifiComponent = item.GetComponent(); - if (wifiComponent != null) + item.AllowStealing = allowStealing; + if (item.GetComponent() is { } wifiComponent) { wifiComponent.TeamID = TeamTag; } - var idCard = item.GetComponent(); - if (idCard != null) + if (item.GetComponent() is { } idCard) { idCard.TeamID = TeamTag; idCard.SubmarineSpecificID = 0; } } - - WayPoint subWaypoint = - WayPoint.WayPointList.Find(wp => wp.Submarine == Submarine.MainSub && wp.SpawnType == SpawnType.Human && wp.AssignedJob == npc.Info.Job?.Prefab) ?? - WayPoint.WayPointList.Find(wp => wp.Submarine == Submarine.MainSub && wp.SpawnType == SpawnType.Human); + WayPoint subWaypoint = + WayPoint.WayPointList.Find(wp => wp.Submarine == sub && wp.SpawnType == SpawnType.Human && wp.AssignedJob == npc.Info.Job?.Prefab) ?? + WayPoint.WayPointList.Find(wp => wp.Submarine == sub && wp.SpawnType == SpawnType.Human); if (subWaypoint != null) { npc.GiveIdCardTags(subWaypoint, createNetworkEvent: true); } -#if SERVER - GameMain.NetworkMember.CreateEntityEvent(npc, new Character.AddToCrewEventData(TeamTag, npc.Inventory.AllItems)); -#endif } } isFinished = true; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/NPCFollowAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/NPCFollowAction.cs index 7cc2f28ef..cee49e531 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/NPCFollowAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/NPCFollowAction.cs @@ -1,8 +1,5 @@ -using Barotrauma.Extensions; -using Microsoft.Xna.Framework; using System.Collections.Generic; using System.Linq; -using System.Xml.Linq; namespace Barotrauma { @@ -17,6 +14,12 @@ namespace Barotrauma [Serialize(true, IsPropertySaveable.Yes)] public bool Follow { get; set; } + [Serialize(-1, IsPropertySaveable.Yes)] + public int MaxTargets { get; set; } + + [Serialize(true, IsPropertySaveable.Yes)] + public bool AbandonOnReset { get; set; } + private bool isFinished = false; public NPCFollowAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { } @@ -32,6 +35,7 @@ namespace Barotrauma target = ParentEvent.GetTargets(TargetTag).FirstOrDefault(); if (target == null) { return; } + int targetCount = 0; affectedNpcs = ParentEvent.GetTargets(NPCTag).Where(c => c is Character).Select(c => c as Character).ToList(); foreach (var npc in affectedNpcs) { @@ -56,6 +60,11 @@ namespace Barotrauma } } } + targetCount++; + if (MaxTargets > -1 && targetCount >= MaxTargets) + { + break; + } } isFinished = true; } @@ -67,11 +76,11 @@ namespace Barotrauma public override void Reset() { - if (affectedNpcs != null && target != null) + if (affectedNpcs != null && target != null && AbandonOnReset) { foreach (var npc in affectedNpcs) { - if (npc.Removed || !(npc.AIController is HumanAIController humanAiController)) { continue; } + if (npc.Removed || npc.AIController is not HumanAIController humanAiController) { continue; } foreach (var goToObjective in humanAiController.ObjectiveManager.GetActiveObjectives()) { if (goToObjective.Target == target) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/NPCOperateItemAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/NPCOperateItemAction.cs new file mode 100644 index 000000000..b39eb465f --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/NPCOperateItemAction.cs @@ -0,0 +1,122 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Barotrauma +{ + class NPCOperateItemAction : EventAction + { + [Serialize("", IsPropertySaveable.Yes)] + public Identifier NPCTag { get; set; } + + [Serialize("", IsPropertySaveable.Yes)] + public Identifier TargetTag { get; set; } + + [Serialize("", IsPropertySaveable.Yes)] + public Identifier ItemComponentName { get; set; } + + [Serialize("", IsPropertySaveable.Yes)] + public Identifier OrderOption { get; set; } + + [Serialize(false, IsPropertySaveable.Yes)] + public bool RequireEquip { get; set; } + + [Serialize(true, IsPropertySaveable.Yes)] + public bool Operate { get; set; } + + [Serialize(-1, IsPropertySaveable.Yes)] + public int MaxTargets { get; set; } + + [Serialize(true, IsPropertySaveable.Yes)] + public bool AbandonOnReset { get; set; } + + private bool isFinished = false; + + public NPCOperateItemAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { } + + + private List affectedNpcs = null; + private Item target = null; + + public override void Update(float deltaTime) + { + if (isFinished) { return; } + + target = ParentEvent.GetTargets(TargetTag).FirstOrDefault() as Item; + if (target == null) { return; } + + int targetCount = 0; + affectedNpcs = ParentEvent.GetTargets(NPCTag).Where(c => c is Character).Select(c => c as Character).ToList(); + foreach (var npc in affectedNpcs) + { + if (npc.AIController is not HumanAIController humanAiController) { continue; } + + if (Operate) + { + ItemComponentName = "Controller".ToIdentifier(); + var itemComponent = target.Components.FirstOrDefault(ic => ItemComponentName == ic.Name); + if (itemComponent == null) + { + DebugConsole.AddWarning($"Error in NPCOperateItemAction: could not find the component \"{ItemComponentName}\" in item \"{target.Name}\"."); + } + else + { + var newObjective = new AIObjectiveOperateItem(itemComponent, npc, humanAiController.ObjectiveManager, OrderOption, RequireEquip) + { + OverridePriority = 100.0f + }; + humanAiController.ObjectiveManager.AddObjective(newObjective); + humanAiController.ObjectiveManager.WaitTimer = 0.0f; + humanAiController.ObjectiveManager.Objectives.RemoveAll(o => o is AIObjectiveGoTo gotoOjective); + } + } + else + { + foreach (var objective in humanAiController.ObjectiveManager.Objectives) + { + if (objective is AIObjectiveOperateItem operateItemObjective && operateItemObjective.OperateTarget == target) + { + objective.Abandon = true; + } + } + } + targetCount++; + if (MaxTargets > -1 && targetCount >= MaxTargets) + { + break; + } + } + isFinished = true; + } + + public override bool IsFinished(ref string goTo) + { + return isFinished; + } + + public override void Reset() + { + if (affectedNpcs != null && target != null && AbandonOnReset) + { + foreach (var npc in affectedNpcs) + { + if (npc.Removed || npc.AIController is not HumanAIController humanAiController) { continue; } + foreach (var operateItemObjective in humanAiController.ObjectiveManager.GetActiveObjectives()) + { + if (operateItemObjective.OperateTarget == target) + { + operateItemObjective.Abandon = true; + } + } + } + target = null; + affectedNpcs = null; + } + isFinished = false; + } + + public override string ToDebugString() + { + return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(AIObjectiveOperateItem)} -> (NPCTag: {NPCTag.ColorizeObject()}, TargetTag: {TargetTag.ColorizeObject()}, Operate: {Operate.ColorizeObject()})"; + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/NPCWaitAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/NPCWaitAction.cs index aa2bee231..fa3b3d2f8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/NPCWaitAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/NPCWaitAction.cs @@ -29,7 +29,7 @@ namespace Barotrauma foreach (var npc in affectedNpcs) { - if (!(npc.AIController is HumanAIController humanAiController)) { continue; } + if (npc.AIController is not HumanAIController humanAiController) { continue; } if (Wait) { @@ -62,7 +62,7 @@ namespace Barotrauma { foreach (var npc in affectedNpcs) { - if (npc.Removed || !(npc.AIController is HumanAIController)) { continue; } + if (npc.Removed || npc.AIController is not HumanAIController) { continue; } if (gotoObjective != null) { gotoObjective.Abandon = true; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/SpawnAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/SpawnAction.cs index 3bd798efd..657c6af21 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/SpawnAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/SpawnAction.cs @@ -60,6 +60,9 @@ namespace Barotrauma [Serialize(true, IsPropertySaveable.Yes, description: "If false, we won't spawn another character if one with the same identifier has already been spawned.")] public bool AllowDuplicates { get; set; } + [Serialize(100.0f, IsPropertySaveable.Yes)] + public float Offset { get; set; } + [Serialize("", IsPropertySaveable.Yes, "What outpost module tags does the entity prefer to spawn in.")] public string TargetModuleTags { @@ -127,7 +130,7 @@ namespace Barotrauma ISpatialEntity spawnPos = GetSpawnPos(); if (spawnPos != null) { - Entity.Spawner.AddCharacterToSpawnQueue(CharacterPrefab.HumanSpeciesName, OffsetSpawnPos(spawnPos.WorldPosition, 100.0f), humanPrefab.CreateCharacterInfo(), onSpawn: newCharacter => + Entity.Spawner.AddCharacterToSpawnQueue(CharacterPrefab.HumanSpeciesName, OffsetSpawnPos(spawnPos.WorldPosition, Offset), humanPrefab.CreateCharacterInfo(), onSpawn: newCharacter => { if (newCharacter == null) { return; } newCharacter.HumanPrefab = humanPrefab; @@ -162,7 +165,7 @@ namespace Barotrauma ISpatialEntity spawnPos = GetSpawnPos(); if (spawnPos != null) { - Entity.Spawner.AddCharacterToSpawnQueue(SpeciesName, OffsetSpawnPos(spawnPos.WorldPosition, 100.0f), onSpawn: newCharacter => + Entity.Spawner.AddCharacterToSpawnQueue(SpeciesName, OffsetSpawnPos(spawnPos.WorldPosition, Offset), onSpawn: newCharacter => { if (!TargetTag.IsEmpty && newCharacter != null) { @@ -208,7 +211,7 @@ namespace Barotrauma ISpatialEntity spawnPos = GetSpawnPos(); if (spawnPos != null) { - Entity.Spawner.AddItemToSpawnQueue(itemPrefab, OffsetSpawnPos(spawnPos.WorldPosition, 100.0f), onSpawned: onSpawned); + Entity.Spawner.AddItemToSpawnQueue(itemPrefab, OffsetSpawnPos(spawnPos.WorldPosition, Offset), onSpawned: onSpawned); } } else diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TutorialIconAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TutorialIconAction.cs index 317966346..a47d4e932 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TutorialIconAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TutorialIconAction.cs @@ -13,7 +13,7 @@ class TutorialIconAction : EventAction public Identifier TargetTag { get; set; } [Serialize("", IsPropertySaveable.Yes)] - public string IconStyle { get; set; } + public Identifier IconStyle { get; set; } private bool isFinished; @@ -33,7 +33,7 @@ class TutorialIconAction : EventAction } else if(Type == ActionType.Remove) { - tutorialMode.Tutorial?.Icons.RemoveAll(i => i.entity == target && i.iconStyle.Equals(IconStyle, System.StringComparison.OrdinalIgnoreCase)); + tutorialMode.Tutorial?.Icons.RemoveAll(i => i.entity == target && i.iconStyle == IconStyle); } else if (Type == ActionType.RemoveTarget) { @@ -41,7 +41,7 @@ class TutorialIconAction : EventAction } else if (Type == ActionType.RemoveIcon) { - tutorialMode.Tutorial?.Icons.RemoveAll(i => i.iconStyle.Equals(IconStyle, System.StringComparison.OrdinalIgnoreCase)); + tutorialMode.Tutorial?.Icons.RemoveAll(i => i.iconStyle == IconStyle); } else if (Type == ActionType.Clear) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TutorialSegmentAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TutorialSegmentAction.cs index b447875bc..587fb20a7 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TutorialSegmentAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TutorialSegmentAction.cs @@ -8,7 +8,7 @@ namespace Barotrauma public SegmentActionType Type { get; set; } [Serialize("", IsPropertySaveable.Yes)] - public Identifier Id { get; set; } + public Identifier Identifier { get; set; } [Serialize("", IsPropertySaveable.Yes)] public Identifier ObjectiveTag { get; set; } @@ -30,7 +30,13 @@ namespace Barotrauma private bool isFinished; - public TutorialSegmentAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { } + public TutorialSegmentAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) + { + if (Identifier.IsEmpty) + { + Identifier = element.GetAttributeIdentifier("id", Identifier.Empty); + } + } public override void Update(float deltaTime) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/UIHighlightAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/UIHighlightAction.cs new file mode 100644 index 000000000..b990fdf66 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/UIHighlightAction.cs @@ -0,0 +1,62 @@ +namespace Barotrauma; + +partial class UIHighlightAction : EventAction +{ + public enum ElementId + { + None, + RepairButton, + PumpSpeedSlider, + PassiveSonarIndicator, + ActiveSonarIndicator, + SonarModeSwitch, + DirectionalSonarFrame, + SteeringModeSwitch, + MaintainPosTickBox, + AutoTempSwitch, + PowerButton, + FissionRateSlider, + TurbineOutputSlider, + DeconstructButton, + RechargeSpeedSlider, + CPRButton + } + + [Serialize(ElementId.None, IsPropertySaveable.Yes)] + public ElementId Id { get; set; } + + [Serialize("", IsPropertySaveable.Yes)] + public Identifier EntityIdentifier { get; set; } + + [Serialize(OrderCategory.Emergency, IsPropertySaveable.Yes)] + public OrderCategory OrderCategory { get; set; } + + [Serialize("", IsPropertySaveable.Yes)] + public Identifier OrderIdentifier { get; set; } + + [Serialize("", IsPropertySaveable.Yes)] + public Identifier OrderOption { get; set; } + + [Serialize("", IsPropertySaveable.Yes)] + public Identifier OrderTargetTag { get; set; } + + [Serialize(true, IsPropertySaveable.Yes)] + public bool Bounce { get; set; } + + private bool isFinished; + + public UIHighlightAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { } + + public override void Update(float deltaTime) + { + if (isFinished) { return; } + UpdateProjSpecific(); + isFinished = true; + } + + partial void UpdateProjSpecific(); + + public override bool IsFinished(ref string goToLabel) => isFinished; + + public override void Reset() => isFinished = false; +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/AbandonedOutpostMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/AbandonedOutpostMission.cs index 9665b6b90..7e4da0ea5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/AbandonedOutpostMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/AbandonedOutpostMission.cs @@ -329,21 +329,14 @@ namespace Barotrauma } - public override void End() + protected override bool DetermineCompleted() { - completed = State > 0 && State != HostagesKilledState; - if (completed) - { - if (Prefab.LocationTypeChangeOnCompleted != null) - { - ChangeLocationType(Prefab.LocationTypeChangeOnCompleted); - } - GiveReward(); - } - else - { - failed = requireRescue.Any(r => r.Removed || r.IsDead); - } + return State > 0 && State != HostagesKilledState; + } + + protected override void EndMissionSpecific(bool completed) + { + failed = !completed && requireRescue.Any(r => r.Removed || r.IsDead); } } } \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/AlienRuinMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/AlienRuinMission.cs index 2ed692fa5..2bfb34391 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/AlienRuinMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/AlienRuinMission.cs @@ -156,22 +156,21 @@ namespace Barotrauma return true; } - private bool IsItemDestroyed(Item item) => item == null || item.Removed || item.Condition <= 0.0f; + private static bool IsItemDestroyed(Item item) => item == null || item.Removed || item.Condition <= 0.0f; - private bool IsEnemyDefeated(Character enemy) => enemy == null ||enemy.Removed || enemy.IsDead; + private static bool IsEnemyDefeated(Character enemy) => enemy == null ||enemy.Removed || enemy.IsDead; - public override void End() + protected override bool DetermineCompleted() { bool exitingLevel = GameMain.GameSession?.GameMode is CampaignMode campaign ? campaign.GetAvailableTransition() != CampaignMode.TransitionType.None : Submarine.MainSub is { } sub && (sub.AtEndExit || sub.AtStartExit); - if (State > 0 && exitingLevel) - { - GiveReward(); - completed = true; - } + return State > 0 && exitingLevel; + } + protected override void EndMissionSpecific(bool completed) + { failed = !completed && State > 0; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/BeaconMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/BeaconMission.cs index d2770b68b..6e46b3c5b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/BeaconMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/BeaconMission.cs @@ -163,20 +163,16 @@ namespace Barotrauma #endif } - public override void End() + protected override bool DetermineCompleted() { - completed = level.CheckBeaconActive(); - if (completed) + return level.CheckBeaconActive(); + } + + protected override void EndMissionSpecific(bool completed) + { + if (completed && level.LevelData != null) { - if (Prefab.LocationTypeChangeOnCompleted != null) - { - ChangeLocationType(Prefab.LocationTypeChangeOnCompleted); - } - GiveReward(); - if (level.LevelData != null) - { - level.LevelData.IsBeaconActive = true; - } + level.LevelData.IsBeaconActive = true; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CargoMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CargoMission.cs index 58aae000c..7ead29659 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CargoMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CargoMission.cs @@ -219,7 +219,7 @@ namespace Barotrauma else if (sub != this.currentSub || missionsChanged) { this.currentSub = sub; - this.nextRoundSubInfo = sub.Info; + this.nextRoundSubInfo = sub?.Info; DetermineCargo(); } @@ -294,22 +294,21 @@ namespace Barotrauma } } - public override void End() + protected override bool DetermineCompleted() { if (Submarine.MainSub != null && Submarine.MainSub.AtEndExit) { int deliveredItemCount = items.Count(it => IsItemDelivered(it)); if (deliveredItemCount / (float)items.Count >= requiredDeliveryAmount) { - GiveReward(); - completed = true; - if (Prefab.LocationTypeChangeOnCompleted != null) - { - ChangeLocationType(Prefab.LocationTypeChangeOnCompleted); - } + return true; } } + return false; + } + protected override void EndMissionSpecific(bool completed) + { foreach (Item item in items) { if (!item.Removed) { item.Remove(); } @@ -318,7 +317,7 @@ namespace Barotrauma failed = !completed; } - private bool IsItemDelivered(Item item) + private static bool IsItemDelivered(Item item) { if (item.Removed || item.Condition <= 0.0f || Submarine.MainSub == null) { return false; } var submarine = item.Submarine ?? item.GetRootContainer()?.Submarine; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CombatMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CombatMission.cs index 4812d5085..917b31d90 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CombatMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CombatMission.cs @@ -113,15 +113,9 @@ namespace Barotrauma #endif } - public override void End() + protected override bool DetermineCompleted() { - if (GameMain.NetworkMember == null) { return; } - - if (Winner != CharacterTeamType.None) - { - GiveReward(); - completed = true; - } + return Winner != CharacterTeamType.None; } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/EscortMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/EscortMission.cs index e94c4432c..fe7c3ff84 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/EscortMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/EscortMission.cs @@ -305,7 +305,7 @@ namespace Barotrauma return character.LockHands && character.HasTeamChange(TerroristTeamChangeIdentifier); } - public override void End() + protected override bool DetermineCompleted() { if (Submarine.MainSub != null && Submarine.MainSub.AtEndExit) { @@ -321,11 +321,14 @@ namespace Barotrauma if (friendliesSurvived && !terroristsSurvived && !vipDied) { - GiveReward(); - completed = true; + return true; } } + return false; + } + protected override void EndMissionSpecific(bool completed) + { if (!IsClient) { foreach (Character character in characters) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/GoToMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/GoToMission.cs index cb8508f9d..f0fa3a328 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/GoToMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/GoToMission.cs @@ -1,6 +1,4 @@ -using Barotrauma.Networking; - -namespace Barotrauma +namespace Barotrauma { partial class GoToMission : Mission { @@ -11,7 +9,22 @@ namespace Barotrauma protected override void UpdateMissionSpecific(float deltaTime) { - State = 1; + if (Level.Loaded?.Type == LevelData.LevelType.Outpost) + { + State = 1; + } + } + + protected override bool DetermineCompleted() + { + if (Level.Loaded?.Type == LevelData.LevelType.Outpost) + { + return true; + } + else + { + return Submarine.MainSub is { AtEndExit: true }; + } } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MineralMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MineralMission.cs index d0a99fe80..0d136f64f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MineralMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MineralMission.cs @@ -131,13 +131,13 @@ namespace Barotrauma foreach ((Identifier identifier, ResourceCluster cluster) in resourceClusters) { - if (!(MapEntityPrefab.FindByIdentifier(identifier) is ItemPrefab prefab)) + if (MapEntityPrefab.FindByIdentifier(identifier) is not ItemPrefab prefab) { DebugConsole.ThrowError($"Error in MineralMission: couldn't find an item prefab (identifier: \"{identifier}\")"); continue; } - var spawnedResources = level.GenerateMissionResources(prefab, cluster.Amount, positionType, out float rotation); + var spawnedResources = level.GenerateMissionResources(prefab, cluster.Amount, positionType, out float rotation, caves); if (spawnedResources.Count < cluster.Amount) { DebugConsole.ThrowError($"Error in MineralMission: spawned only {spawnedResources.Count}/{cluster.Amount} of {prefab.Name}"); @@ -181,14 +181,16 @@ namespace Barotrauma } } - public override void End() + protected override bool DetermineCompleted() { - if (EnoughHaveBeenCollected()) + return EnoughHaveBeenCollected(); + } + + protected override void EndMissionSpecific(bool completed) + { + failed = !completed && state > 0; + if (completed) { - if (Prefab.LocationTypeChangeOnCompleted != null) - { - ChangeLocationType(Prefab.LocationTypeChangeOnCompleted); - } if (!IsClient) { // When mission is completed successfully, half of the resources will be removed from the player (i.e. given to the outpost as a part of the mission) @@ -198,7 +200,7 @@ namespace Barotrauma if (relevantLevelResources.TryGetValue(identifier, out var availableResources)) { var collectedResources = availableResources.Where(HasBeenCollected); - if (collectedResources.Count() < 1) { continue; } + if (!collectedResources.Any()) { continue; } int handoverCount = (int)MathF.Round(resourceHandoverAmount * collectedResources.Count()); for (int i = 0; i < handoverCount; i++) { @@ -211,8 +213,6 @@ namespace Barotrauma resource.Remove(); } } - GiveReward(); - completed = true; } foreach (var kvp in spawnedResources) { @@ -227,7 +227,6 @@ namespace Barotrauma spawnedResources.Clear(); relevantLevelResources.Clear(); missionClusterPositions.Clear(); - failed = !completed && state > 0; } private void FindRelevantLevelResources() diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs index 497992319..138b3b9f5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs @@ -13,7 +13,8 @@ namespace Barotrauma abstract partial class Mission { public readonly MissionPrefab Prefab; - protected bool completed, failed; + private bool completed; + protected bool failed; protected Level level; @@ -36,7 +37,9 @@ namespace Barotrauma } } - protected bool IsClient => GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient; + protected static bool IsClient => GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient; + + private readonly CheckDataAction completeCheckDataAction; public readonly ImmutableArray Headers; public readonly ImmutableArray Messages; @@ -156,6 +159,12 @@ namespace Barotrauma Locations = locations; + var endConditionElement = prefab.ConfigElement.GetChildElement(nameof(completeCheckDataAction)); + if (endConditionElement != null) + { + completeCheckDataAction = new CheckDataAction(endConditionElement, $"Mission ({prefab.Identifier.ToString()})"); + } + for (int n = 0; n < 2; n++) { string locationName = $"‖color:gui.orange‖{locations[n].Name}‖end‖"; @@ -334,19 +343,27 @@ namespace Barotrauma /// /// End the mission and give a reward if it was completed successfully /// - public virtual void End() + public void End() { - completed = true; + completed = + DetermineCompleted() && + (completeCheckDataAction == null ||completeCheckDataAction.GetSuccess()); if (Prefab.LocationTypeChangeOnCompleted != null) { ChangeLocationType(Prefab.LocationTypeChangeOnCompleted); } GiveReward(); + EndMissionSpecific(completed); } - public void GiveReward() + protected abstract bool DetermineCompleted(); + + protected virtual void EndMissionSpecific(bool completed) { } + + + private void GiveReward() { - if (!(GameMain.GameSession.GameMode is CampaignMode campaign)) { return; } + if (GameMain.GameSession.GameMode is not CampaignMode campaign) { return; } int reward = GetReward(Submarine.MainSub); float baseExperienceGain = reward * 0.09f; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MissionPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MissionPrefab.cs index 1be2ee4d3..92f08baec 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MissionPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MissionPrefab.cs @@ -146,14 +146,24 @@ namespace Barotrauma tags = element.GetAttributeStringArray("tags", Array.Empty(), convertToLowerInvariant: true); - Name = - TextManager.Get($"MissionName.{TextIdentifier}") - .Fallback(TextManager.Get(element.GetAttributeString("name", ""))) - .Fallback(element.GetAttributeString("name", "")); - Description = - TextManager.Get($"MissionDescription.{TextIdentifier}") - .Fallback(TextManager.Get(element.GetAttributeString("description", ""))) - .Fallback(element.GetAttributeString("description", "")); + string nameTag = element.GetAttributeString("name", ""); + Name = TextManager.Get($"MissionName.{TextIdentifier}"); + if (!string.IsNullOrEmpty(nameTag)) + { + Name = Name + .Fallback(TextManager.Get(nameTag)) + .Fallback(nameTag); + } + + string descriptionTag = element.GetAttributeString("description", ""); + Description = + TextManager.Get($"MissionDescription.{TextIdentifier}"); + if (!string.IsNullOrEmpty(descriptionTag)) + { + Description = Description + .Fallback(TextManager.Get(descriptionTag)) + .Fallback(descriptionTag); + } Reward = element.GetAttributeInt("reward", 1); AllowRetry = element.GetAttributeBool("allowretry", false); @@ -167,23 +177,35 @@ namespace Barotrauma Difficulty = Math.Clamp(difficulty, MinDifficulty, MaxDifficulty); } - SuccessMessage = - TextManager.Get($"MissionSuccess.{TextIdentifier}") - .Fallback(TextManager.Get(element.GetAttributeString("successmessage", ""))) - .Fallback(element.GetAttributeString("successmessage", "Mission completed successfully")); - FailureMessage = - TextManager.Get($"MissionFailure.{TextIdentifier}") - .Fallback(TextManager.Get(element.GetAttributeString("missionfailed", ""))) - .Fallback(TextManager.Get("missionfailed")) - .Fallback(GameSettings.CurrentConfig.Language == TextManager.DefaultLanguage ? element.GetAttributeString("failuremessage", "") : ""); + string successMessageTag = element.GetAttributeString("successmessage", ""); + SuccessMessage = TextManager.Get($"MissionSuccess.{TextIdentifier}"); + if (!string.IsNullOrEmpty(successMessageTag)) + { + SuccessMessage = SuccessMessage + .Fallback(TextManager.Get(successMessageTag)) + .Fallback(successMessageTag); + } + SuccessMessage = SuccessMessage.Fallback(TextManager.Get("missioncompleted")); + + string failureMessageTag = element.GetAttributeString("failuremessage", ""); + FailureMessage = TextManager.Get($"MissionFailure.{TextIdentifier}"); + if (!string.IsNullOrEmpty(failureMessageTag)) + { + FailureMessage = FailureMessage + .Fallback(TextManager.Get(failureMessageTag)) + .Fallback(failureMessageTag); + } + FailureMessage = FailureMessage.Fallback(TextManager.Get("missionfailed")); string sonarLabelTag = element.GetAttributeString("sonarlabel", ""); - - SonarLabel = + SonarLabel = TextManager.Get($"MissionSonarLabel.{sonarLabelTag}") .Fallback(TextManager.Get(sonarLabelTag)) - .Fallback(TextManager.Get($"MissionSonarLabel.{TextIdentifier}")) - .Fallback(element.GetAttributeString("sonarlabel", "")); + .Fallback(TextManager.Get($"MissionSonarLabel.{TextIdentifier}")); + if (!string.IsNullOrEmpty(sonarLabelTag)) + { + SonarLabel = SonarLabel.Fallback(sonarLabelTag); + } SonarIconIdentifier = element.GetAttributeIdentifier("sonaricon", ""); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MonsterMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MonsterMission.cs index eb296dad9..9b3641502 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MonsterMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MonsterMission.cs @@ -223,26 +223,26 @@ namespace Barotrauma break; } } - - public override void End() + + protected override bool DetermineCompleted() + { + return state > 0; + } + + protected override void EndMissionSpecific(bool completed) { tempSonarPositions.Clear(); monsters.Clear(); - if (State < 1) { return; } - - if (Prefab.LocationTypeChangeOnCompleted != null) + if (completed) { - ChangeLocationType(Prefab.LocationTypeChangeOnCompleted); - } - GiveReward(); - completed = true; - if (level?.LevelData != null && Prefab.Tags.Any(t => t.Equals("huntinggrounds", StringComparison.OrdinalIgnoreCase))) - { - level.LevelData.HasHuntingGrounds = false; + if (level?.LevelData != null && Prefab.Tags.Contains("huntinggrounds")) + { + level.LevelData.HasHuntingGrounds = false; + } } } - public bool IsEliminated(Character enemy) => + public static bool IsEliminated(Character enemy) => enemy == null || enemy.Removed || enemy.IsDead || diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/NestMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/NestMission.cs index 5a85ee491..bc71d49dc 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/NestMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/NestMission.cs @@ -305,20 +305,13 @@ namespace Barotrauma return true; } - public override void End() + protected override bool DetermineCompleted() + { + return AllItemsDestroyedOrRetrieved(); + } + + protected override void EndMissionSpecific(bool completed) { - if (AllItemsDestroyedOrRetrieved()) - { - GiveReward(); - completed = true; - if (completed) - { - if (Prefab.LocationTypeChangeOnCompleted != null) - { - ChangeLocationType(Prefab.LocationTypeChangeOnCompleted); - } - } - } foreach (Item item in items) { if (item != null && !item.Removed) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/PirateMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/PirateMission.cs index 20d26fbf5..ff33e7ec9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/PirateMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/PirateMission.cs @@ -401,13 +401,13 @@ namespace Barotrauma return character == null || character.Removed || character.Submarine == null || (character.LockHands && character.Submarine == Submarine.MainSub) || character.IsIncapacitated; } - public override void End() + protected override bool DetermineCompleted() + { + return state == 2; + } + + protected override void EndMissionSpecific(bool completed) { - if (state == 2) - { - GiveReward(); - completed = true; - } characters.Clear(); characterItems.Clear(); failed = !completed; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/SalvageMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/SalvageMission.cs index 858e8a74d..0d1b41b98 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/SalvageMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/SalvageMission.cs @@ -242,7 +242,7 @@ namespace Barotrauma Submarine parentSub = item.CurrentHull?.Submarine ?? item.GetRootInventoryOwner()?.Submarine; if (parentSub == null || parentSub.Info.Type != SubmarineType.Player) { - return; + return; } } State = 1; @@ -254,23 +254,16 @@ namespace Barotrauma } } - public override void End() + protected override bool DetermineCompleted() { var root = item?.GetRootContainer() ?? item; - if (root?.CurrentHull?.Submarine == null || (!root.CurrentHull.Submarine.AtEndExit && !root.CurrentHull.Submarine.AtStartExit) || item.Removed) - { - return; - } - - if (Prefab.LocationTypeChangeOnCompleted != null) - { - ChangeLocationType(Prefab.LocationTypeChangeOnCompleted); - } + return root?.CurrentHull?.Submarine != null && (root.CurrentHull.Submarine.AtEndExit || root.CurrentHull.Submarine.AtStartExit) && !item.Removed; + } + protected override void EndMissionSpecific(bool completed) + { item?.Remove(); item = null; - GiveReward(); - completed = true; failed = !completed && state > 0; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/ScanMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/ScanMission.cs index 132767396..a59c69f8d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/ScanMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/ScanMission.cs @@ -247,24 +247,9 @@ namespace Barotrauma } } - public override void End() + protected override bool DetermineCompleted() { - if (State == 2 && AllScannersReturned()) - { - GiveReward(); - completed = true; - } - foreach (var scanner in scanners) - { - if (scanner.Item != null && !scanner.Item.Removed) - { - scanner.OnScanStarted -= OnScanStarted; - scanner.OnScanCompleted -= OnScanCompleted; - scanner.Item.Remove(); - } - } - Reset(); - failed = !completed && state > 0; + return State == 2 && AllScannersReturned(); bool AllScannersReturned() { @@ -285,5 +270,20 @@ namespace Barotrauma return true; } } + + protected override void EndMissionSpecific(bool completed) + { + foreach (var scanner in scanners) + { + if (scanner.Item != null && !scanner.Item.Removed) + { + scanner.OnScanStarted -= OnScanStarted; + scanner.OnScanCompleted -= OnScanCompleted; + scanner.Item.Remove(); + } + } + Reset(); + failed = !completed && state > 0; + } } } \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/MonsterEvent.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/MonsterEvent.cs index e76ab4c70..1bdbade40 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/MonsterEvent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/MonsterEvent.cs @@ -11,7 +11,7 @@ namespace Barotrauma { public readonly Identifier SpeciesName; public readonly int MinAmount, MaxAmount; - private List monsters; + private readonly List monsters = new List(); private readonly float scatter; private readonly float offset; @@ -30,7 +30,7 @@ namespace Barotrauma public readonly int MaxAmountPerLevel = int.MaxValue; - public List Monsters => monsters; + public IReadOnlyList Monsters => monsters; public Vector2? SpawnPos => spawnPos; public bool SpawnPending => spawnPending; @@ -115,7 +115,7 @@ namespace Barotrauma } } - private Submarine GetReferenceSub() + private static Submarine GetReferenceSub() { return EventManager.GetRefEntity() as Submarine ?? Submarine.MainSub; } @@ -147,7 +147,7 @@ namespace Barotrauma DebugConsole.NewMessage("Initialized MonsterEvent (" + SpeciesName + ")", Color.White); } - monsters = new List(); + monsters.Clear(); //+1 because Range returns an integer less than the max value int amount = Rand.Range(MinAmount, MaxAmount + 1); diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/CargoManager.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/CargoManager.cs index cab2c43d6..6fa21d9ae 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/CargoManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/CargoManager.cs @@ -80,7 +80,7 @@ namespace Barotrauma { if (ID != Entity.NullEntityID) { - DebugConsole.ShowError("Error setting SoldItem.ID: ID has already been set and should not be changed."); + DebugConsole.LogError("Error setting SoldItem.ID: ID has already been set and should not be changed."); return; } ID = id; @@ -128,7 +128,7 @@ namespace Barotrauma { if (Item != null) { - DebugConsole.ShowError($"Trying to set SoldEntity.Item, but it's already set!\n{Environment.StackTrace.CleanupStackTrace()}"); + DebugConsole.LogError($"Trying to set SoldEntity.Item, but it's already set!\n{Environment.StackTrace.CleanupStackTrace()}"); return; } Item = item; diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/CrewManager.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/CrewManager.cs index 8671f08d3..06ab5566e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/CrewManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/CrewManager.cs @@ -198,6 +198,34 @@ namespace Barotrauma } } + /// + /// Remove the character from the crew (and crew menus). + /// + /// The character to remove + /// If the character info is also removed, the character will not be visible in the round summary. + public void RemoveCharacter(Character character, bool removeInfo = false, bool resetCrewListIndex = true) + { + if (character == null) + { + DebugConsole.ThrowError("Tried to remove a null character from CrewManager.\n" + Environment.StackTrace.CleanupStackTrace()); + return; + } + characters.Remove(character); + if (removeInfo) + { + characterInfos.Remove(character.Info); +#if CLIENT + RemoveCharacterFromCrewList(character); +#endif + } +#if CLIENT + if (resetCrewListIndex) + { + ResetCrewListIndex(character); + } +#endif + } + public void AddCharacterInfo(CharacterInfo characterInfo) { if (characterInfos.Contains(characterInfo)) diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/Tutorials/TutorialPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/Tutorials/TutorialPrefab.cs index ad6dec773..a9942470b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/Tutorials/TutorialPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/Tutorials/TutorialPrefab.cs @@ -27,6 +27,8 @@ namespace Barotrauma public readonly Identifier EventIdentifier; + public readonly Sprite Banner; + public TutorialPrefab(ContentFile file, ContentXElement element) : base(file, element.GetAttributeIdentifier("identifier", "")) { Order = element.GetAttributeInt("order", int.MaxValue); @@ -50,6 +52,12 @@ namespace Barotrauma StartingItemTags = ImmutableArray.Empty; } + var bannerElement = element.GetChildElement("banner"); + if (bannerElement != null) + { + Banner = new Sprite(bannerElement, lazyLoad: true); + } + EventIdentifier = element.GetChildElement("scriptedevent")?.GetAttributeIdentifier("identifier", "") ?? Identifier.Empty; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/UpgradeManager.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/UpgradeManager.cs index 37d8c9369..a35b86654 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/UpgradeManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/UpgradeManager.cs @@ -412,14 +412,14 @@ namespace Barotrauma #endif } - public List GetLinkedItemsToSwap(Item item) + public static ICollection GetLinkedItemsToSwap(Item item) { - List linkedItems = new List() { item }; + HashSet linkedItems = new HashSet() { item }; foreach (MapEntity linkedEntity in item.linkedTo) { foreach (MapEntity secondLinkedEntity in linkedEntity.linkedTo) { - if (!(secondLinkedEntity is Item linkedItem) || linkedItem == item) { continue; } + if (secondLinkedEntity is not Item linkedItem || linkedItem == item) { continue; } if (linkedItem.AllowSwapping && linkedItem.Prefab.SwappableItem != null && (linkedItem.Prefab.SwappableItem.CanBeBought || item.Prefab.SwappableItem.ReplacementOnUninstall == ((MapEntity)linkedItem).Prefab.Identifier) && linkedItem.Prefab.SwappableItem.SwapIdentifier.Equals(item.Prefab.SwappableItem.SwapIdentifier, StringComparison.OrdinalIgnoreCase)) @@ -709,7 +709,7 @@ namespace Barotrauma SavePendingUpgrades(upgradeManagerElement, PendingUpgrades); } - private void SavePendingUpgrades(XElement? parent, List upgrades) + private static void SavePendingUpgrades(XElement? parent, List upgrades) { if (parent == null) { return; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/DockingPort.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/DockingPort.cs index 4952f01f9..8ef6c05ad 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/DockingPort.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/DockingPort.cs @@ -46,7 +46,7 @@ namespace Barotrauma.Items.Components private float forceLockTimer; //if the submarine isn't in the correct position to lock within this time after docking has been activated, //force the sub to the correct position - const float ForceLockDelay = 1.0f; + const float ForceLockDelay = 1.0f; public int DockingDir { get; set; } @@ -81,12 +81,18 @@ namespace Barotrauma.Items.Components set; } - [Editable, Serialize(DirectionType.None, IsPropertySaveable.No, description: "Which direction the port is allowed to dock in. For example, \"Top\" would mean the port can dock to another port above it.\n"+ + [Editable, Serialize(DirectionType.None, IsPropertySaveable.No, description: "Which direction the port is allowed to dock in. For example, \"Top\" would mean the port can dock to another port above it.\n" + "Normally there's no need to touch this setting, but if you notice the docking position is incorrect (for example due to some unusual docking port configuration without hulls or doors), you can use this to enforce the direction.")] public DirectionType ForceDockingDirection { get; set; } - + public DockingPort DockingTarget { get; private set; } + /// + /// Can be used by status effects + /// + public bool AtStartExit => Item.Submarine is { AtStartExit: true}; + public bool AtEndExit => Item.Submarine is { AtEndExit: true }; + public Door Door { get; private set; } public bool Docked @@ -116,6 +122,8 @@ namespace Barotrauma.Items.Components get { return joint is WeldJoint || DockingTarget?.joint is WeldJoint; } } + public bool AnotherPortInProximity => FindAdjacentPort() != null; + /// /// Automatically cleared after docking -> no need to unregister /// @@ -989,7 +997,7 @@ namespace Barotrauma.Items.Components dockingState = MathHelper.Lerp(dockingState, 0.0f, deltaTime * 10.0f); if (dockingState < 0.01f) { docked = false; } item.SendSignal("0", "state_out"); - item.SendSignal((FindAdjacentPort() != null) ? "1" : "0", "proximity_sensor"); + item.SendSignal(AnotherPortInProximity ? "1" : "0", "proximity_sensor"); } else { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ElectricalDischarger.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ElectricalDischarger.cs index 0d2f9e1e1..8188d52c9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ElectricalDischarger.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ElectricalDischarger.cs @@ -153,7 +153,7 @@ namespace Barotrauma.Items.Components { if (GetAvailableInstantaneousBatteryPower() >= PowerConsumption) { - List batteries = GetConnectedBatteries(); + List batteries = GetDirectlyConnectedBatteries(); float neededPower = PowerConsumption; while (neededPower > 0.0001f && batteries.Count > 0) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Throwable.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Throwable.cs index 40d209d5b..df595ca57 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Throwable.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Throwable.cs @@ -111,6 +111,12 @@ namespace Barotrauma.Items.Components } ApplyStatusEffects(ActionType.OnActive, deltaTime, picker); + //return if the status effect got rid of the picker somehow + if (picker == null || picker.Removed || !picker.HeldItems.Contains(item)) + { + IsActive = false; + return; + } if (item.body.Dir != picker.AnimController.Dir) { item.FlipX(relativeToSub: false); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs index 7ca37cb5e..a1d69ce93 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs @@ -53,7 +53,7 @@ namespace Barotrauma.Items.Components private bool alwaysContainedItemsSpawned; - public ItemInventory Inventory; + public readonly ItemInventory Inventory; private readonly List activeContainedItems = new List(); @@ -189,6 +189,16 @@ namespace Barotrauma.Items.Components [Serialize(false, IsPropertySaveable.No)] public bool RemoveContainedItemsOnDeconstruct { get; set; } + + /// + /// Can be used by status effects to lock the inventory + /// + public bool Locked + { + get { return Inventory.Locked; } + set { Inventory.Locked = value; } + } + private readonly ImmutableArray slotRestrictions; readonly List targets = new List(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Sonar.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Sonar.cs index 07d3b598c..077ae053f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Sonar.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Sonar.cs @@ -136,6 +136,13 @@ namespace Barotrauma.Items.Components public float Zoom { get { return zoom; } + set + { + zoom = MathHelper.Clamp(value, MinZoom, MaxZoom); +#if CLIENT + zoomSlider.BarScroll = MathUtils.InverseLerp(MinZoom, MaxZoom, zoom); +#endif + } } public Mode CurrentMode diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Power/PowerTransfer.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Power/PowerTransfer.cs index f123939d7..8c15f3019 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Power/PowerTransfer.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Power/PowerTransfer.cs @@ -278,7 +278,8 @@ namespace Barotrauma.Items.Components public override float GetConnectionPowerOut(Connection conn, float power, PowerRange minMaxPower, float load) { - return conn == powerOut ? PowerConsumption + ExtraLoad : 0; + //not used in the vanilla game (junction boxes or relays don't output power) + return conn == powerOut ? MathHelper.Max(-(PowerConsumption + ExtraLoad), 0) : 0; } public override bool Pick(Character picker) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Power/Powered.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Power/Powered.cs index 330c6bd50..e355ccb14 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Power/Powered.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Power/Powered.cs @@ -690,43 +690,21 @@ namespace Barotrauma.Items.Components } /// - /// Efficient method to retrieve the batteries connected to the device + /// Returns a list of batteries directly connected to the item /// - /// All connected PowerContainers - protected List GetConnectedBatteries(bool outputOnly = true) + protected List GetDirectlyConnectedBatteries() { List batteries = new List(); - GridInfo supplyingGrid = null; - - //Determine supplying grid, prefer PowerIn connection - if (powerIn != null) + if (item.Connections == null || powerIn == null) { return batteries; } + foreach (Connection recipient in powerIn.Recipients) { - if (powerIn.Grid != null) + if (!recipient.IsPower || !recipient.IsOutput) { continue; } + var battery = recipient.Item?.GetComponent(); + if (battery != null) { - supplyingGrid = powerIn.Grid; + batteries.Add(battery); } } - else if (powerOut != null) - { - if (powerOut.Grid != null) - { - supplyingGrid = powerOut.Grid; - } - } - - if (supplyingGrid != null) - { - //Iterate through all connections to fine powerContainers - foreach (Connection c in supplyingGrid.Connections) - { - PowerContainer pc = c.Item.GetComponent(); - if (pc != null && (!outputOnly || pc.powerOut == c)) - { - batteries.Add(pc); - } - } - } - return batteries; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Quality.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Quality.cs index bd2becdac..fbdb9e7c4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Quality.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Quality.cs @@ -29,18 +29,7 @@ namespace Barotrauma.Items.Components FirepowerMultiplier, StrikingPowerMultiplier, StrikingSpeedMultiplier, - FiringRateMultiplier, - // unused as of now - AttackMultiplier, - // unused as of now - AttackSpeedMultiplier, - ForceDoorsOpenSpeedMultiplier, - RangedSpreadReduction, - ChargeSpeedMultiplier, - MovementSpeedMultiplier, - EffectivenessMultiplier, - PowerOutputMultiplier, - ConsumptionReductionMultiplier, + FiringRateMultiplier } private readonly Dictionary statValues = new Dictionary(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs index b8a0436e3..e486300c6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs @@ -603,6 +603,9 @@ namespace Barotrauma.Items.Components private bool ShouldDeteriorate() { if (Level.IsLoadedFriendlyOutpost) { return false; } +#if CLIENT + if (GameMain.GameSession?.GameMode is TutorialMode) { return false; } +#endif if (LastActiveTime > Timing.TotalTime) { return true; } foreach (ItemComponent ic in item.Components) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/BooleanOperatorComponent/BooleanOperatorComponent.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/BooleanOperatorComponent/BooleanOperatorComponent.cs index 3803e5b16..35b4a9d10 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/BooleanOperatorComponent/BooleanOperatorComponent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/BooleanOperatorComponent/BooleanOperatorComponent.cs @@ -48,6 +48,8 @@ namespace Barotrauma.Items.Components { if (value == null) { return; } output = value; + //reactivate (we may not have been previously sending a signal, but might now) + IsActive = true; if (output.Length > MaxOutputLength && (item.Submarine == null || !item.Submarine.Loading)) { output = output.Substring(0, MaxOutputLength); @@ -63,6 +65,8 @@ namespace Barotrauma.Items.Components { if (value == null) { return; } falseOutput = value; + //reactivate (we may not have been previously sending a signal, but might now) + IsActive = true; if (falseOutput.Length > MaxOutputLength && (item.Submarine == null || !item.Submarine.Loading)) { falseOutput = falseOutput.Substring(0, MaxOutputLength); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/MotionSensor.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/MotionSensor.cs index e278206b7..6ebfe3fc8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/MotionSensor.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/MotionSensor.cs @@ -19,7 +19,8 @@ namespace Barotrauma.Items.Components Human = 1, Monster = 2, Wall = 4, - Any = Human | Monster | Wall, + Pet = 8, + Any = Human | Monster | Wall | Pet, } [Serialize(false, IsPropertySaveable.No, description: "Has the item currently detected movement. Intended to be used by StatusEffect conditionals (setting this value in XML has no effect).")] @@ -253,7 +254,7 @@ namespace Barotrauma.Items.Components } } - if (Target.HasFlag(TargetType.Human) || Target.HasFlag(TargetType.Monster)) + if (Target.HasFlag(TargetType.Human) || Target.HasFlag(TargetType.Pet) || Target.HasFlag(TargetType.Monster)) { foreach (Character c in Character.CharacterList) { @@ -267,7 +268,11 @@ namespace Barotrauma.Items.Components { if (!Target.HasFlag(TargetType.Human)) { continue; } } - else if (!c.IsPet) + else if (c.IsPet) + { + if (!Target.HasFlag(TargetType.Pet)) { continue; } + } + else { if (!Target.HasFlag(TargetType.Monster)) { continue; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs index 18ab28faf..df767e009 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs @@ -702,7 +702,7 @@ namespace Barotrauma.Items.Components if (!ignorePower) { - List batteries = GetConnectedBatteries(); + List batteries = GetDirectlyConnectedBatteries(); float neededPower = GetPowerRequiredToShoot(); // tinkering is currently not factored into the common method as it is checked only when shooting @@ -1066,7 +1066,7 @@ namespace Barotrauma.Items.Components bool canShoot = true; if (!HasPowerToShoot()) { - List batteries = GetConnectedBatteries(); + List batteries = GetDirectlyConnectedBatteries(); float lowestCharge = 0.0f; PowerContainer batteryToLoad = null; foreach (PowerContainer battery in batteries) @@ -1308,9 +1308,13 @@ namespace Barotrauma.Items.Components } } float dist = Vector2.Distance(closestPoint, item.WorldPosition); + + //add one px to make sure the visibility raycast doesn't miss the cell due to the end position being right at the edge of the cell + closestPoint += (closestPoint - item.WorldPosition) / Math.Max(dist, 1); + if (dist > AIRange + 1000) { continue; } float dot = 0; - if (item.Submarine.Velocity != Vector2.Zero) + if (!MathUtils.NearlyEqual(item.Submarine.Velocity, Vector2.Zero)) { dot = Vector2.Dot(Vector2.Normalize(item.Submarine.Velocity), Vector2.Normalize(closestPoint - item.Submarine.WorldPosition)); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs index 278b4a102..fdbe12a51 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs @@ -422,6 +422,8 @@ namespace Barotrauma } } + public Color? HighlightColor; + [Serialize("", IsPropertySaveable.Yes)] @@ -523,6 +525,7 @@ namespace Barotrauma { float prevConditionPercentage = ConditionPercentage; healthMultiplier = MathHelper.Clamp(value, 0.0f, float.PositiveInfinity); + RecalculateConditionValues(); condition = MaxCondition * prevConditionPercentage / 100.0f; RecalculateConditionValues(); } @@ -751,6 +754,9 @@ namespace Barotrauma get { return Prefab.Linkable; } } + public float WorldPositionX => WorldPosition.X; + public float WorldPositionY => WorldPosition.Y; + /// /// Can be used to move the item from XML (e.g. to correct the positions of items whose sprite origin has been changed) /// @@ -1913,8 +1919,11 @@ namespace Barotrauma if (!wasInWater && CurrentHull != null && body != null && body.LinearVelocity.Y < -1.0f) { Splash(); - //slow the item down (not physically accurate, but looks good enough) - body.LinearVelocity *= 0.2f; + if (GetComponent() is not { IsActive: true }) + { + //slow the item down (not physically accurate, but looks good enough) + body.LinearVelocity *= 0.2f; + } } Item container = this.Container; @@ -3340,10 +3349,8 @@ namespace Barotrauma item.PurchasedNewSwap = false; } - float condition = element.GetAttributeFloat("condition", item.MaxCondition); - item.condition = MathHelper.Clamp(condition, 0, item.MaxCondition); + item.condition = MathHelper.Clamp(item.condition, 0, item.MaxCondition); item.lastSentCondition = item.condition; - item.RecalculateConditionValues(); item.SetActiveSprite(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs index 79d80014d..59ee01824 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs @@ -1123,7 +1123,7 @@ namespace Barotrauma { string message = $"Tried to get price info for \"{Identifier}\" with a null store parameter!\n{Environment.StackTrace.CleanupStackTrace()}"; #if DEBUG - DebugConsole.ShowError(message); + DebugConsole.LogError(message); #else DebugConsole.AddWarning(message); GameAnalyticsManager.AddErrorEventOnce("ItemPrefab.GetPriceInfo:StoreParameterNull", GameAnalyticsManager.ErrorSeverity.Error, message); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs index 181f60281..e864b0a9e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs @@ -2932,7 +2932,7 @@ namespace Barotrauma } /// Used by clients to set the rotation for the resources - public List GenerateMissionResources(ItemPrefab prefab, int requiredAmount, PositionType positionType, out float rotation) + public List GenerateMissionResources(ItemPrefab prefab, int requiredAmount, PositionType positionType, out float rotation, IEnumerable targetCaves = null) { var allValidLocations = GetAllValidClusterLocations(); var placedResources = new List(); @@ -2995,6 +2995,12 @@ namespace Barotrauma DebugConsole.ThrowError($"Unexpected PositionType (\"{positionType}\") for mineral mission resources: mineral spawning might not work as expected."); } + if (targetCaves != null && targetCaves.Any()) + { + // If resources are placed inside a cave, make sure all of them are placed inside the same one + allValidLocations.RemoveAll(l => targetCaves.None(c => c.Area.Contains(l.EdgeCenter))); + } + var poi = PositionsOfInterest.GetRandom(p => p.PositionType == positionType, randSync: Rand.RandSync.ServerAndClient); Vector2 poiPos = poi.Position.ToVector2(); allValidLocations.Sort((x, y) => Vector2.DistanceSquared(poiPos, x.EdgeCenter) @@ -3002,7 +3008,10 @@ namespace Barotrauma float maxResourceOverlap = 0.4f; var selectedLocation = allValidLocations.FirstOrDefault(l => Vector2.Distance(l.Edge.Point1, l.Edge.Point2) is float edgeLength && + !l.Edge.OutsideLevel && requiredAmount <= (int)Math.Floor(edgeLength / ((1.0f - maxResourceOverlap) * prefab.Size.X))); + + if (selectedLocation.Edge == null) { //couldn't find a long enough edge, find the largest one @@ -3028,10 +3037,10 @@ namespace Barotrauma static bool IsOnMainPath(ClusterLocation location) => location.Edge.NextToMainPath; static bool IsOnSidePath(ClusterLocation location) => location.Edge.NextToSidePath; static bool IsInCave(ClusterLocation location) => location.Edge.NextToCave; - bool IsInAbyssCave(ClusterLocation location) => location.EdgeCenter.Y > AbyssArea.Bottom; + bool IsInAbyssCave(ClusterLocation location) => location.EdgeCenter.Y < AbyssStart; void RemoveInvalidLocations(Predicate match) { - allValidLocations.RemoveAll(match); + allValidLocations.RemoveAll(l => !match(l)); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/LinkedSubmarine.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/LinkedSubmarine.cs index 46d549e2f..52bc59f55 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/LinkedSubmarine.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/LinkedSubmarine.cs @@ -281,6 +281,10 @@ namespace Barotrauma IdRemap parentRemap = new IdRemap(Submarine.Info.SubmarineElement, Submarine.IdOffset); sub = Submarine.Load(info, false, parentRemap); sub.Info.SubmarineClass = Submarine.Info.SubmarineClass; + if (Submarine.Info.IsOutpost && Submarine.TeamID == CharacterTeamType.FriendlyNPC) + { + sub.TeamID = CharacterTeamType.FriendlyNPC; + } IdRemap childRemap = new IdRemap(saveElement, sub.IdOffset); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Location.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Location.cs index d90c90e99..cb1861ec7 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Location.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Location.cs @@ -700,7 +700,7 @@ namespace Barotrauma #endif } - public MissionPrefab UnlockMissionByIdentifier(Identifier identifier) + public Mission UnlockMissionByIdentifier(Identifier identifier) { if (AvailableMissions.Any(m => m.Prefab.Identifier == identifier)) { return null; } @@ -721,17 +721,17 @@ namespace Barotrauma #if CLIENT GameMain.GameSession?.Campaign?.CampaignUI?.RefreshLocationInfo(); #endif - return missionPrefab; + return mission; } return null; } - public MissionPrefab UnlockMissionByTag(Identifier tag) + public Mission UnlockMissionByTag(Identifier tag) { var matchingMissions = MissionPrefab.Prefabs.Where(mp => mp.Tags.Any(t => t == tag)); if (!matchingMissions.Any()) { - DebugConsole.ThrowError($"Failed to unlock a mission with the tag \"{tag}\": no matching missions not found."); + DebugConsole.ThrowError($"Failed to unlock a mission with the tag \"{tag}\": no matching missions found."); } else { @@ -754,7 +754,7 @@ namespace Barotrauma #if CLIENT GameMain.GameSession?.Campaign?.CampaignUI?.RefreshLocationInfo(); #endif - return missionPrefab; + return mission; } else { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Map.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Map.cs index 925f45e66..ff196b2d6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Map.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Map.cs @@ -462,6 +462,13 @@ namespace Barotrauma } } + //make sure the connections are in the same order on the locations and the Connections list + //otherwise their order will change when loading the game (as they're added to the locations in the same order they're loaded) + foreach (var location in Locations) + { + location.Connections.Sort((c1, c2) => Connections.IndexOf(c1).CompareTo(Connections.IndexOf(c2))); + } + for (int i = Connections.Count - 1; i >= 0; i--) { i = Math.Min(i, Connections.Count - 1); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs index 6ffee8449..5ae404712 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs @@ -1234,7 +1234,7 @@ namespace Barotrauma public List<(ItemContainer container, int freeSlots)> GetCargoContainers() { List<(ItemContainer container, int freeSlots)> containers = new List<(ItemContainer container, int freeSlots)>(); - var connectedSubs = GetConnectedSubs(); + var connectedSubs = GetConnectedSubs().Where(sub => sub.Info?.Type == Info.Type); foreach (Item item in Item.ItemList.ToList()) { if (!connectedSubs.Contains(item.Submarine)) { continue; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineBody.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineBody.cs index b8b2fd544..b9af39add 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineBody.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineBody.cs @@ -447,11 +447,13 @@ namespace Barotrauma private Vector2 CalculateBuoyancy() { + if (Submarine.LockY) { return Vector2.Zero; } + float waterVolume = 0.0f; float volume = 0.0f; foreach (Hull hull in Hull.HullList) { - if (hull.Submarine != submarine) continue; + if (hull.Submarine != submarine) { continue; } waterVolume += hull.WaterVolume; volume += hull.Volume; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/Client.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/Client.cs index 84fb06106..93a7b09eb 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/Client.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/Client.cs @@ -20,7 +20,6 @@ namespace Barotrauma.Networking public bool InGame; public bool HasPermissions; public bool IsOwner; - public bool AllowKicking; public bool IsDownloading; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/NetworkPeerStructs.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/NetworkPeerStructs.cs index 893bed235..0a042c27f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/NetworkPeerStructs.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/NetworkPeerStructs.cs @@ -101,34 +101,45 @@ namespace Barotrauma.Networking } public LocalizedString ChatMessage(Client c) - => DisconnectReason switch + { + LocalizedString message = DisconnectReason switch { DisconnectReason.Disconnected => TextManager.GetWithVariable("ServerMessage.ClientLeftServer", "[client]", c.Name), + DisconnectReason.Banned => TextManager.GetWithVariable("servermessage.bannedfromserver", "[client]", c.Name), + DisconnectReason.Kicked => TextManager.GetWithVariable("servermessage.kickedfromserver", "[client]", c.Name), _ => TextManager.GetWithVariables("ChatMsg.DisconnectedWithReason", ("[client]", c.Name), ("[reason]", TextManager.Get($"ChatMsg.DisconnectReason.{DisconnectReason}"))) }; + if (!string.IsNullOrEmpty(AdditionalInformation) && + DisconnectReason is DisconnectReason.Banned or DisconnectReason.Kicked) + { + message += " "+ TextManager.Get("banreason") + " " + TextManager.GetServerMessage(AdditionalInformation); + } + return message; + } - private LocalizedString msgWithReason + + private LocalizedString MsgWithReason => TextManager.Get($"DisconnectReason.{DisconnectReason}") + "\n\n" - + TextManager.Get("banreason") + " " + AdditionalInformation; + + TextManager.Get("banreason") + " " + TextManager.GetServerMessage(AdditionalInformation); - private LocalizedString serverMessage + private LocalizedString ServerMessage => TextManager.Get($"ServerMessage.{DisconnectReason}"); public LocalizedString PopupMessage => DisconnectReason switch { - DisconnectReason.Banned => msgWithReason, - DisconnectReason.Kicked => msgWithReason, + DisconnectReason.Banned => MsgWithReason, + DisconnectReason.Kicked => MsgWithReason, DisconnectReason.InvalidVersion => TextManager.GetWithVariables("DisconnectMessage.InvalidVersion", ("[version]", AdditionalInformation), ("[clientversion]", GameMain.Version.ToString())), - DisconnectReason.ExcessiveDesyncOldEvent => serverMessage, - DisconnectReason.ExcessiveDesyncRemovedEvent => serverMessage, - DisconnectReason.SyncTimeout => serverMessage, + DisconnectReason.ExcessiveDesyncOldEvent => ServerMessage, + DisconnectReason.ExcessiveDesyncRemovedEvent => ServerMessage, + DisconnectReason.SyncTimeout => ServerMessage, _ => TextManager.Get($"DisconnectReason.{DisconnectReason}").Fallback(TextManager.Get("ConnectionLost")) }; @@ -165,9 +176,6 @@ namespace Barotrauma.Networking or DisconnectReason.TooManyFailedLogins or DisconnectReason.InvalidVersion); - public bool ShouldShowMessage - => DisconnectReason is not DisconnectReason.Disconnected; - private const string lidgrenSeparator = ":hankey:"; /// diff --git a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/DelayedEffect.cs b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/DelayedEffect.cs index 94e6c7bef..6435eebf4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/DelayedEffect.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/DelayedEffect.cs @@ -77,13 +77,13 @@ namespace Barotrauma Projectile projectile = (entity as Item)?.GetComponent(); if (projectile == null) { - DebugConsole.ShowError("Non-projectile using a delaytype of reachcursor"); + DebugConsole.LogError("Non-projectile using a delaytype of reachcursor"); return; } if (projectile.User == null) { - DebugConsole.ShowError("Projectile: '" + projectile.Name + "' missing user to determine distance"); + DebugConsole.LogError("Projectile: '" + projectile.Name + "' missing user to determine distance"); return; } @@ -129,7 +129,7 @@ namespace Barotrauma if (projectile == null) { #if DEBUG - DebugConsole.ShowError("Non-projectile using a delaytype of reachcursor"); + DebugConsole.LogError("Non-projectile using a delaytype of reachcursor"); #endif return; } @@ -137,7 +137,7 @@ namespace Barotrauma if (projectile.User == null) { #if DEBUG - DebugConsole.ShowError("Projectile " + projectile.Name + "missing user"); + DebugConsole.LogError("Projectile " + projectile.Name + "missing user"); #endif return; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs index 9c1ce5c71..4b625e9e8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs @@ -1770,7 +1770,7 @@ namespace Barotrauma case ItemSpawnInfo.SpawnRotationType.Random: if (projectile != null) { - DebugConsole.ShowError("Random rotation is not supported for Projectiles."); + DebugConsole.LogError("Random rotation is not supported for Projectiles."); } else { diff --git a/Barotrauma/BarotraumaShared/changelog.txt b/Barotrauma/BarotraumaShared/changelog.txt index 6dd03983d..49a90c5db 100644 --- a/Barotrauma/BarotraumaShared/changelog.txt +++ b/Barotrauma/BarotraumaShared/changelog.txt @@ -1,401 +1,305 @@ --------------------------------------------------------------------------------------------------------- -v0.19.5.0 +v0.19.8.0 --------------------------------------------------------------------------------------------------------- -Unstable only: -- Progress on the new tutorials, the Roles tutorials in particular. Still a work in progress, but feedback is again more than welcome! -- Cleaned up networking and server list code to make them less error-prone and easier to work with in the future. Should not cause any functional differences, but please let us know if you notice any issues or oddities! -- Fixes and improvements to Camel. -- Fixed skill texts not being colored according to the user's skills in the repair UI. -- Fixed outpost service NPCs' titles (which are also used as the tooltips on the icons) not showing up in the multiplayer campaign. -- Further fixes to dragging animations. -- Fixed warning about duplicate keybinds being displayed for things set to None. -- Fixed clients who've gotten vote kicked getting vote kicked again when they join. -- Fixed favorited localhost servers causing a crash. -- Fixed respawn shuttle setting toggling on and off when adjusting other settings. -- Fixed skills increasing on death if the skill level is under the maximum initial skill. -- Fixes duplicate Captain Hognoses occasionally appearing in the crew list when selecting the Hognose mission in the multiplayer campaign. -- Fixed wrecked coilgun and railgun launch impulses. -- Fixed delayed status effects not working after the parent entity has been removed (breaking many medical items, e.g. ethanol). -- Fixed inability to buy anything from other stores when you've bought something from one of the stores in an outpost in the multiplayer campaign. +- Minor improvements and fixes to the tutorials (e.g. safeguards to prevent dying or getting stuck). +- Fixed Typhon 2's chaingun being placed inside solid walls. +- Fixed Tandem Fire talent causing a crash if there's no allies alive. +- Fixed bots being unable to shoot at ice spires with pulse laser or chaingun. +- Removed outdated Deep Diver loading screen tip. +- Fixed misaligned connection panel interface when repairing a status monitor. +- Fixed Winterhalter battery recharge speed being limited by the relay that supplies power to them, making recharge speed upgrades useless. +- Fixed a networking issue that caused afflictions' periodic effects (e.g. nausea-induced vomiting) to happen too frequently client-side. +- Fixed none of the contextual orders except "wait here" showing the name of the order in the tooltip. +- Fixed inconsistencies in some crawler swarm mission names (large swarms not being described as large). +- Fixed mineral mission resources sometimes spawning in separate caves. +- Fixed minerals mission resources sometimes spawning outside the level. +- Moved handheld sonar's drag icon to a more appropriate position. +- Fixed buoyancy making locked subs slowly move vertically. +- Reactors don't explode if they reach 0 condition without fuel. +- Fixed motion sensor not detecting pets. +- Fixed cargo capacity displayed in the mission selection screen including the cargo containers in the outpost the sub is docked. +- Fixed clients being unable to select the respawn shuttle if they have to download it from the server. +- Fixed duplicate banlist entires when a client gets banned due to an incorrect password. +- Some wire and waypoint cleanup to R-29 and Remora. +- Removed a wall in Remora to allow for better movement inside. +- Changed one railgun shelf to boxes shelf on Remora. +- Added a coilgun and a couple more diving suit cabinets to R-29. +- Fixed "completed initialization before receiving content package order" error when trying to reconnect to a SteamP2P server. -Changes: +--------------------------------------------------------------------------------------------------------- +v0.19.7.0 +--------------------------------------------------------------------------------------------------------- + +Changes and additions: +- Completely remade tutorials. There is now a Basics Tutorial to cover basics like moving, inventory and repairs. More specific tasks are explained in the Roles Tutorial, where every job has their own tutorial to go through, explaining what it means to be e.g. an Engineer or a Captain. +- Added new mining missions, including some in the abyss. +- Reintroduced separate local/radio voice chat keys as a legacy option. Now it's again possible to speak with voice activation by default and use a push-to-talk button for radio, the same way as before, by setting the chat mode to Local and using the new radio voice chat hotkey. +- Device/item UIs can be moved around by dragging. +- Allow using devices while on a ladder or sitting on a chair. +- Changed reactor temperature bar colors (from blue to red). +- Higher quality stun batons cause heavier stun. +- Changed unit load device capacity to 12 (because the sprite has space for 12) and made them waterproof. +- Changed fabricator skill calculations: the most inadequate of the required skills determines the fabrication time (instead of the average). +- Made dying drop a characters' skills towards the maximum initial skill instead of minimum. +- Added a new keybind for opening and closing the chat box. The default bind is B. +- Added a warning if a new keybind overlaps with any of the player's existing binds. +- Overvoltage makes devices perform better, increasing the output of engines, making fabricators, deconstructors and pumps operate faster, electrical discharge coils do more damage, batteries recharge faster and oxygen generators generate more oxygen. Encourages operating the reactor manually and hopefully makes it a little more engaging. +- Added more randomness to junction box overvoltage damage, and made partially damaged boxes take more damage from overvoltage. Prevents all boxes from breaking at the same time, making overvoltage less of a pain to deal with and intentionally overvolting devices more worthwhile. +- Added manual temperature adjustment buttons which immediately increase/decrease the temperature of the reactor for a brief amount of time on manual control (bumps the gauge up/down by a fifth, and the boost fades out in 20 seconds). Allows reacting to load fluctuations very quickly, and conserving fuel by operating the reactor at a lower fission rate – a new benefit to operating reactors manually. +- Signals no longer set the fission and turbine rates of the reactor instantaneously, making automated reactor circuits less overpowered. They are still viable, but especially now with the addition of the extra incentives for operating the reactor manually, they're no longer as clearly the best and most efficient way to operate the reactor, making manual operation more worthwhile. +- Made the "distort" camera effect a little less obtrusive and glitchy-looking (smoother texture + less heavy effect). +- Made water-sensitive materials (lithium, potassium, sodium) spawn in waterproof chemical crates. - Made crates deconstruct much faster to make them easier to get rid of. -- Sonar disruptions hide minerals. -- Gray out RangedWeapon's crosshair when reloading (similar to turret crosshairs). +- Sonar disruptions now hide minerals. +- Grayed out ranged weapons' crosshair when reloading (similar to turret crosshairs). +- Disabled the autodocking prompt (which verifies whether you actually want to dock when docking is initiated by an automated circuit) in single player. +- Improved the way drag is applied on submerged items. Fixes heavy items dropping at unnaturally high speeds in water. +- Added a splash effect when an item falls into water. +- The deconstructor UI shows what the input items deconstruct to (particularly important now with the lossy deconstruction recipes - it can be risky to deconstruct something just to see what materials it gives out if that results in material loss). +- Wall and device repair costs in outposts are calculated based on the amount of damage on your sub, instead of always having a fixed price. +- Inflamed lung doesn't affect characters that don't need oxygen. +- Added swarm behavior for crawler husks. +- Added some more oomph to nuclear explosions. +- Adjust the alpha of the outpost service icons according to distance to make it easier to estimate where the NPC is at. Show the title of the NPC when hovering the cursor over the icon. +- Added "unlockmission" console command. +- Added "setcampaignmetadata" console command (may be useful for modders creating custom scripted events for the campaign). +- Changed how NPC "titles" work. Previously we defined "titles" for the pirates (e.g. "Pirate Lord" and such), and the title replaced the name of the NPC (which made their dialog a little awkward). Now we display both the name and the title over the character, and special outpost NPCs also have titles. +- Gave diving masks to most NPCs. +- Changed the burn overlay formula: now also the non-affected limbs get half of the effect, because the sharp contrast between limbs looked weird. +- Restored the 3-shell Railgun rack as a legacy option. +- Reworded the "respawn with penalty" prompt to make it less confusing: you always get a penalty to your skills when you die now, and Reaper's Tax is an "extra penalty" you get on top of that if you opt to respawn mid-round. The intention behind this is to incur a cost to respawning, as it shouldn't be possible to get unlimited free reinforcements and supplies mid-round. +- Made SIGTERM close the linux server gracefully. +- Made respawn items (suits, scooters) spawn in the respawn shuttle's cabinets when possible. +- Show a healthbar on items (e.g. eggs and thalamus organs) when damaging them with handheld weapons (melee or ranged). -Fixes: -- Fixed clients downloading submarines they already have from the server if the mods those submarines are in are not currently enabled. -- Fixed submarines always saving in the root folder of a local mod, instead of the subfolder they were originally in. -- Fixed Reaper's Tax not stacking. -- Fixed turrets linked to the same loader messing up the upgrade store UI and causing item swaps to cost more than they should. -- Fixed status monitor calculating linked hulls' water amounts incorrectly (displaying the average of their water percentages, which isn't correct if the hulls aren't the same size). -- Fixes to messed up ruin decals in a bunch of ruin modules. -- Fixed a waypoint issue in the Alien_Entrance3 ruin module. -- Removed oxygen tanks from DockingModule_01_Colony. +Submarines: +- Added a new intermediate transport sub, Camel. +- Added submarine tiers. Higher-tier submarines can be upgraded further than lower-tier submarines. +- Overhauled and balanced submarine upgrades. +- Added Large Weapon Hardpoints. +- Added Flak Cannon and Double Coilgun as new Large Weapons. +- Railgun is now considered a Large Weapon. +- Added an upgrade that adds a mineral scanner to nav terminals and sonar monitors.cannon +- Submarine class now affects which upgrades are available for the sub. +- Removed the Deep Diver class: the way we see it, Deep Divers didn't have a clear enough role in the game, especially considering that hull upgrades served pretty much the same purpose. In practice, the only clear benefit of a Deep Diver was being able to get through the very last levels of the campaign, and having to switch to one just for that purpose wasn't fun. Now any submarine with full hull upgrades can get all the way to the end of the campaign. +- Fixed messy wiring in Typhon 2's bottom left hardpoint. +- Winterhalter and Remora are now Scout class ships. +- Added some loose vents and panels to Herja, Winterhalter and Barsuk, fixed invisible "loose panel" (news stand) in Orca 2. +- Fixed floating light component in Orca 2. +- Medical fabricator now consumes 500 power on all submarines, to be consistent with other fabricators. +- Updated prices of all submarines to match tiers. +- Gave Typhon 2 better stats and even more firepower, to outclass the original Typhon. +- Improved R-29's speed and gave it a Flak Cannon. +- Added Large Weapon hardpoints to Berilia to make it a Tier 3 transport. - Tweaked the hulls and waypoints around Herja's top docking hatch to make it easier for bots to reach and weld. - Fixed a waypoint/hull issue in Typhon's stowage compartment (waypoint in such a tight space the bots couldn't reach it). -- Fixed inactive components (components not currently sending any signal) not reactivating if their output is set to a non-empty value. -- Fixed duct block's misaligned broken sprite. + +Balance: +- "Mission cheesing" by repeatedly undocking and redocking to an outpost to reroll the mission events no longer works: new mission events don't reappear until one "world step" has passed (~10 minutes or traversing through one level). +- Balance pass on handheld weapons: adjusted reload times, damages, stun durations, recoil and ammo stack sizes. +- Reduced tools' structure damage (dual-wielded storage containers no longer chew through submarine walls in seconds). +- Increased heavy ruin wall health to make it less easy to cheese your way into the artifact room in ruins. +- Made boomstick fire in bursts of 2 (similar to deadeye carbide) to prevent ridiculous fire rates with quick-reloading. +- Added EMP effect to nuclear depth charges for consistency. +- Pulse Laser and Railgun now have similar power consumption as other turrets. +- Changed how skill levels affect the quality of fabricated items. Previously having a skill level equal to or higher than the item's skill requirement would result in a good quality item, meaning that practically everyone could e.g. fabricate good quality oxygen tanks. Now your skill needs to be >20% from the minimum skill requirement towards 100 (e.g. if the item requires 20 skill to fabricate, 36 results in a higher quality item). +- Reduced PUCS's radiation resistance from 100% to 90%. Complete invulnerability to radiation has way too much potential for exploits and overpowered strategies. +- Adjusted supplies in pirate submarines. +- Turned some weapons' burn damage into explosion damage. +- Made the extra sales from "traveling tradesman" talent stack. +- Terminal ignores empty signals. +- Reduced commonness of molochs (as they can take a lot of time to kill, running into multiple of them can quickly become a chore) +- Removed steel requirement for depth charges. Fabricate decoy depth charges from depth charges, rather than from the base material. +- Reduced the Pulse Laser tri-laser bolt spread. +- Explosions are now calculated differently, using the number of limbs to divide the damage (up to a maximum of 15 limbs). Adjusted explosion damage values to match new calculations. +- Coilgun costs 5000 marks to install, Pulse Laser and Chaingun 6000. Large turrets each cost 7500 each. +- Made mudraptor eggs modestly profitable for farming (decreased cost from shop, increased deconstruction yields). +- Mineral yield and spawn rates rebalance: minerals found are now much more dependent on location (biome, cave, abyss). +- Balanced existing mineral missions: adjusted rewards & required minerals, and required some minerals to be handed over to the outposts as proof of their existence. +- Rebalanced Engine Force values to better match hull size. Most Scouts (Azimuth, Orca2, Remora, Winterhalter) are now faster. Humpback, Typhon and Orca are slightly slower. + +Multiplayer: +- Fixed missions sometimes unlocking in incorrect locations in MP campaign, making them either unselectable or causing a "mission mismatch" error when the round starts. +- Fixed clients downloading submarines they already have from the server if the mods those submarines are in are not currently enabled. +- Significantly sped up file transfers (mods, submarine files, campaign saves). +- Clients who've recently joined (by default 2 minutes) are not allowed to vote to kick others, and vote kicking someone always requires at least 2 votes. +- Servers don't allow selecting hidden jobs (jobs only used by NPCs) as job preferences. +- The minimum kick vote counts are no longer rounded down. Previously if you had for example four players on the server and the minimum vote count set to 60%, kicking would require 2 votes, now it requires 3. +- Fixed inventory and wallet resetting if a campaign round ends when a client's character has spawned, but the client is not currently controlling it (e.g. due to getting kicked to the lobby). +- Fixed spectator checkbox overlapping with the character info if you get kicked to the lobby mid-round. +- Fixed "kick" button staying disabled indefinitely if you vote to kick someone and the vote doesn't go through. +- Fixed Steamworks publish tab showing the "free weekend" message when using Steam family sharing. +- Minor tweaks to the end of PvP missions to make them a little less underwhelming: instead of ending the round immediately when one team is dead (without even giving enough time to see the enemy die), there's a brief delay, a message box and a camera transition to let the players see what happened. +- Fixed PvP team assignment sometimes being wildly imbalanced, even when there were enough players with no preference to make the team sizes equal. +- Fixed clients getting stuck in the loading screen if they happen to disconnect at the right moment between rounds. +- Fixed bank balance not getting corrected if it's become desynced by e.g. client-side commands. +- Fixed server not registering a client's character as disconnected if the client disconnects and reconnects before the round has fully started, causing the client to get stuck as a spectator when they rejoin. +- Fixed clients disabling their client-side-only mods when they join a server. +- Fixed hull/item repairs purchased from an outpost sometimes not getting applied client-side. +- Fixed "missing entity" errors in a specific situation in multiplayer. Occurred when a respawn shuttle was enabled and loaded on the server (= i.e. in a non-outpost level), and a client disconnected and immediately reconnected. This would cause the client to deselect the respawn shuttle and make them start the round without loading one, leading to the "missing entity" issues due to the shuttle only existing server-side. +- Fixed damage visuals not showing on characters who've died off-screen. +- Fixed ability to upgrade the sub when there's a switch pending in multiplayer. +- Fixed friendly fire and karma always showing up as disabled on dedicated servers in the server list. +- Fixed spineling spikes fired by a human with spineling genes not damaging any human characters (enemies in PvP, pirates in pirate missions) when friendly fire is disabled. +- Fixed "invalid ExecuteAttack message: limb index out of bounds" errors when you join a server where a character has fired spineling spikes with spineling genes mid-round. +- Fixed "entity not found" errors if a shuttle or submarine ends up absurdly deep in multiplayer (>100 km). We don't even know how someone managed to pull this off. +- Fixed rapidly clicking on the mission giver sometimes not giving all the available missions when the "Use" input is set to LMB. Happened because the conversation logic didn't check if there's another conversation active, causing the server to show a new conversation when clicking the NPC, without interrupting/continuing the previous conversation. +- Made shockjock event only show for the player triggering the event (making it visible for everyone works kind of weirdly, when the event involves talking to an NPC next to the character who triggered the event). +- Fixed outpost events getting stuck at the last ConversationAction if another client has finished the action. + +Optimization: +- Updated our runtime to .NET 6, which should yield significant performance improvements. Do note that this unfortunately means we'll have to drop support for macOS versions older than 10.15, but we have taken some measures to help the affected Mac players continue having access to Barotrauma. More info here https://store.steampowered.com/news/app/602960/view/3367025204056277713. +- Optimized afflictions that apply other afflictions on the character (e.g. radiation sickness, drunkenness, opiate withdrawal). +- Optimizations to the talent system, particularly when the talent menu is open and when there's a large number of talents (e.g. when using mods that make all talents available to every class). +- Physics optimization: fixed submerged items' physics bodies staying active indefinitely even after they've come to rest due to buoyant forces being applied on them constantly. Now we stop updating bodies that have come to rest on the floor and aren't light enough to float. +- Optimized AI objectives that make bots fetch items (combat, contain item, decontain item, get item). Submarine editor: -- Fixed door gaps not appearing in the sub editor until you select the door - -Modding: -- Added CheckTalentAction, which can be used in events to check whether a target has unlocked a specific talent. -- Fixed ExtraLoad working the wrong way around on PowerTransfer components that generate/consume power (the extra load would supply power to the grid). Does not affect the vanilla game, because neither junction boxes or relays generate or consume power. - ---------------------------------------------------------------------------------------------------------- -v0.19.4.0 ---------------------------------------------------------------------------------------------------------- - -Unstable only: -- Fixed networking issues that prevented joining SteamP2P servers. -- Fixed crashing on startup if GameAnalytics is enabled. -- Fixed sub editor background images not saving if you leave the editor and quit the game without exiting the image editing mode first. -- Camel hull fixes. -- Fixed a draw order issue in unit load device. -- Fixed unit load device's inventory slot layout. -- Fixed submarine tier set in the sub editor not being saved, causing all subs to use the default tier determined by the price. - -Changes: -- Made extra sales from "traveling tradesman" talent stack. +- Fixed door gaps not appearing in the sub editor until you select the door. +- Fixed sub editor background images not saving. +- Fixed turret lightsource rotation not refreshing in the sub editor when flipping the item. +- Fixed prefab placement breaking in the sub editor if LMB is held while moving the cursor outside of the selection panel. +- Fixed several instances of janky UI interactions in the submarine editor: dragging the selection rectangle now works even if the cursor reaches into the prefab list; letting go of a dragged entity works even if the cursor reaches into the prefab list; the dragged entity no longer goes invisible when reaching into the prefab list. +- Made PowerContainer recharge speed always default to 0. +- Fixed adding resizeable items (like ladders) not being registered in the sub editor's command history, preventing undoing it. +- Changed default reactor output from 10,000 kW to 5000 kW. +- Decreased Winterhalter reactor output and increased its fuel consumption rate. +- Fixed some gap issues in Winterhalter. +- Fixed medics not having access to the toxin cabinet in Barsuk. +- Fixed medic, engineer and mechanic spawnpoints having no tags in Typhon. +- Fixed crashing when trying to multi-edit a string value in the sub editor. +- Fixed dragged objects becoming invisible if you bring the cursor over a UI element in the sub editor. +- Fixed screwdrivers and wires in your "inventory" being included in the total item count in the sub editor's wiring mode. +- Fixed entities that were below the cursor when starting to resize a structure staying highlighted during resizing. +- Fixed sub editor treating the autosave interval as minutes instead of seconds (saving every 300 minutes instead of 300 seconds). Fixes: +- Fixed "power flowback" issue in turrets. As of the power rework, wires connected to the same input or output pin of a device are considered to be in the same grid, which in practice meant a turret could be connected to another supercapacitor through the power_in connection of another turret, even if there was no direct connection between the 1st turret and the supercapacitor. Now the turrets (and electrical discharge coils) need to be wired directly to the supercapacitor. +- Fixed brief freezes when monsters spawn mid-round. +- Fixed turrets linked to the same loader messing up the upgrade store UI and causing item swaps to cost more than they should. +- Fixed submarines always saving in the root folder of a local mod, instead of the subfolder they were originally in. +- Fixed Reaper's Tax not stacking. +- Fixes to ruin decals in a bunch of ruin modules. +- Fixed a waypoint issue in the Alien_Entrance3 ruin module. +- Removed oxygen tanks from DockingModule_01_Colony. +- Fixed duct block's misaligned broken sprite. +- Fixed status monitor calculating linked hulls' water amounts incorrectly (displaying the average of their water percentages, which isn't correct if the hulls aren't the same size). +- Fixed inactive components (components not currently sending any signal) not reactivating if their output is set to a non-empty value. +- Fixed missing gap in SecurityModule_02. +- Fixed lack of outpost events in difficulties past 80 (which no longer occur normally but still exist in old saves and mods). +- Fixed lithium and magnesium descriptions. +- Adjusted hulls in DockingModule_02_Colony to prevent bots from jumping off the ledge. +- Fixed motion sensors detecting pets as monsters (pets are now a separate target type). +- Fixed helmets not protecting against concussions. +- Fixed safety harness not protecting against lacerations. +- Fixed increasing an item's HealthMultiplier not increasing the current condition (so e.g. doubling the item's max health would cause it to have 50% condition). +- Fixed successive event dialogs in the same prompt scrolling the prompt back up and then down. +- Fixed missing "pirateclothes" inventory icon. - Made bots better at figuring out which button controls a door when there's some complex circuit involved. Previously the bots would try to find a button connected to any of the door's connections via wires/circuits, now only the toggle and set_state inputs are considered. -- Bots now heavily prefer using buttons linked to the door in the sub editor. Can be used as another way to help the bots figure out which button they should press in situations with multiple buttons and complex door control logic. -- Fixed bots failing to find a path to a couple of spots in Herja. +- Bots now clearly prefer using buttons linked to the door in the sub editor. Can be used as another way to help bots figure out which button they should press in situations with multiple buttons and complex door control logic. +- Fixed bots failing to find a path to a couple of spots on Herja. - Fixed alien materials (physicorium, incendium, fulgurium, dementonite, paralyxis) not being shown on the mineral scanner. - Another fix to cave generation to prevent it from creating impassable paths. - Fixed inability to use manual assignment for bot orders with options. - Fixed all boolean components (And, Or, Xor) using the And Component's tooltip for the "timeframe" property. - Fixed boolean operator component (And, Or, Xor) timeframes not working correctly in some situations (non-zero timeframe, empty false output). The component would deactivate as soon as it stops sending an output, which could prevent some inputs from timing out (meaning that the component could send a signal again as soon as it receives signal A, even if signal B hasn't been received within the timeframe). - Fixed PUCS consuming the medical item inside it when a welding fuel or incendium tank is inserted. - -Multiplayer: -- Fixed inventory and wallet resetting if a campaign round ends when a client's character has spawned, but the client is not currently controlling it (e.g. due to getting kicked to the lobby). -- Fixed spectator checkbox overlapping with the character info if you get kicked to the lobby mid-round. - -Modding: -- Fixed items/structures now falling back to the description defined in the xml even if it's empty, if the description is not defined for the selected language (instead of using English instead). -- Fixed using the "reloadwearables" and "loadwearable" console commands outside the character editor crashing the game. -- Fixed character editor crash if you first reload textures and then recreate the ragdoll. -- Changed how submarine upgrades are calculated, now no longer adds previous levels' costs to the price, but rather relies on higher increasehigh values - ---------------------------------------------------------------------------------------------------------- -v0.19.3.0 ---------------------------------------------------------------------------------------------------------- - -Unstable only: -- Fixed deconstructors and research stations showing what unidentified genetic materials are. -- Fixed fabricator not showing the number of required items on recipes. -- Fixed a crash in Fabriactor.DrawInputOverLay. -- Visual improvements to draggable item UIs. -- Fixed reactor sliders adjusting to the received signals until the received value has been matched, leading to buggy-looking behavior when the sliders adjust by themselves after the signal wires have been disconnected. Now the reactor stops following the signals if nothing is received in 1 second. -- Misc fixes and improvements to Camel. -- Fixed vote-kicked players being unable to rejoin when they're unbanned. - -Changes: -- Updated our runtime to .NET 6, which should yield significant performance improvements. Do note that this unfortunately means we'll have to drop support for macOS versions older than 10.15, but we have taken some measures to help the affected Mac players continue having access to Barotrauma. More info here https://store.steampowered.com/news/app/602960/view/3367025204056277713. -- First version of reworked tutorials. Still very much a work in progress (only the 1st tutorial that teaches you the very basics is close to done), but would still appreciate feedback! -- Changed unit load device capacity to 12 (because the sprite has space for 12) and made them waterproof. -- Changed fabricator skill calculations: the most inadequate of the required skills determines the fabrication time (instead of the average). -- Made dying drop a characters' skills towards the maximum initial skill instead of minimum. -- Added a new keybind for opening and closing the chat box. The default bind is B. -- Added a warning if a new keybind overlaps with any of player's existing binds. -- Balanced existing mineral missions: adjusted rewards & required minerals and required some minerals to be handed over to the outposts as proof of their existence. -- Added new mining missions, including some in the abyss. - -Submarines: -- Added submarine tiers. Higher-tier submarines can be upgraded futher than lower-tier submarines. -- Overhauled and balanced submarine upgrades. -- Added an upgrade that adds a mineral scanner to nav terminals and sonar monitors. -- Submarine class now affects which upgrades are available for the sub. -- Removed the Deep Diver class: the way we see it, Deep Divers didn't have a clear enough role in the game, especially considering that hull upgrades served pretty much the same purpose. In practice, the only clear benefit of a Deep Diver was being able to get through the very last levels of the campaign, and having to switch to one just for that purpose wasn't fun. Now any submarine with full hull upgrades can get all the way to the end of the campaign. - -Fixes: - Fixed a level generation issue that sometimes made the level impassable if there happened to be a cave right above the outpost. - Fixed holes on sloped walls being impossible to pass through when you're swimming straight down/up (or straight right/left depending on the wall): the walls are technically considered either horizontal or vertical (depending on the angle of the slope), and you would have to swim in a direction perpendicular to this "technical" direction of the wall. - Fixed retrying the Hognose mission making a new Hognose join your crew every time. - Fixed idling NPCs sometimes getting stuck on ladders. -- Fixed mirrored turrets being displayed backwards on status monitor. +- Fixed mirrored turrets being displayed backwards on the status monitor. - Fixed character's hands getting "stuck" if you handcuff yourself while dragging someone. - Fixed dragged character's arms not being pulled towards you, making it look like you're dragging them without touching if you run or walk away while dragging. - Fixed dragged bots slowly moving constantly, preventing them from switching to the normal standing pose. - Fixed bots having trouble fixing leaks in multi-hull rooms: they were required to be in the same hull as the leak, which prevented them from fixing leaks in e.g. R-29's bilge. - Fixed combat missions not ending the round if both crews are dead. - Fixed bots stating the name of the character they're firing at with turrets, making it seem like they know the name of every pirate they come across and magically recognize them through the walls of the enemy sub. -- Fixed chaingun rotation speed not being affected by the weapons skill. +- Fixed Chaingun rotation speed not being affected by the weapons skill. - Fixed crashing when using ':' in item assembly names on Linux platforms. - Fixed ImmuneToPressure ability flag being ignored on characters who don't need air (in practice meaning that you can get killed by pressure if you get huskified even if you have a talent that makes you immune to pressure). - Fixed geneticmaterialcrawler_unresearched3 producing mudraptor genes. - -Submarine editor: -- Fixed sub editor background images not saving. - -Optimization: -- Optimized affliction that apply other afflictions on the character (e.g. radiation sickness, drunkenness, opiate withdrawal). -- Physics optimization: fixed submerged items' physics bodies staying active indefinitely even after they've come to rest due to buoyant forces being applied on them constantly. Now we stop updating bodies that have come to rest on the floor and aren't light enough to float. -- Optimized AI objectives that make the bots fetch items (combat, contain item, decontain item, get item). - -Multiplayer: -- Fixed "kick" button staying disabled indefinitely if you vote to kick someone and the vote doesn't go through. -- Fixed Steamworks publish tab showing the "free weekend" message when using Steam family sharing. - -Modding: -- Fixed inability to localize item names if the name is defined directly in the item config. -- Allowed defining where mineral mission resources are spawned using the "positiontype" attribute. The supported types are "MainPath", "SidePath", "Cave", and "AbyssCave". - ---------------------------------------------------------------------------------------------------------- -v0.19.2.0 ---------------------------------------------------------------------------------------------------------- - -Unstable only: -- Fixed an item getting placed in the sub editor when you select one from the entity list. -- Made flak cannon ammo proximity-triggered. -- Made flak cannon's sounds and visual effects more powerful. -- Fixed LightComponents turning themselves on when the round starts. -- Fixed "Input string was not in a correct format" when loading favorite/recent servers with an empty QueryPort. -- Fixed deconstructor showing contained items in the deconstruction output. -- Fixed double coilgun sometimes not playing a sound on every shot. -- Change the cursor to a hand on draggable item UIs. -- Fixed crashing during level generation if abyss resources have not been configured. -- Fixed hull & item repairs (and presumably replacing lost shuttles) not working in single player unless you save and reload. -- Visual improvements to Camel. -- Fixed timer that prevents recently joined players from voting to kick working the wrong way around. -- Fixed inability to resize a resizeable structure when placing it in the sub editor. - -Changes: -- Reintroduced separate local/radio voice chat keys as a legacy option. Now it's again possible to speak with voice activation by default and use a push-to-talk button for radio, the same way as before, by setting the chat mode to Local and using the new radio voice chat hotkey. -- Changed reactor temperature bar colors (from blue to red). -- Higher quality stun batons cause heavier stun. -- Disabled the autodocking prompt (which verifies whether you actually want to dock when docking is initiated by an automated circuit) in single player. -- Minor tweaks to the end of PvP missions to make them a little less underwhelming: instead of ending the round immediately when one team is dead (without even giving enough time to see the enemy die), there's a bried delay, a message box and a camera transition to let the players see what happened. - -Submarines: -- Fixed floating light component in Orca 2. -- Medical fabricator now consumes 500 power on all submarines, to be consistent with other fabricators -- Rebalanced Engine Force values to better match hull size. Most Scouts (Azimuth, Orca2, Remora, Winterhalter) are now faster. Humpback, Typhon and Orca slightly slower. -- Winterhalter and Remora are now Scout class ships, Deep Diver class will be removed. -- Introduced submarine tiers. Submarine tiers and class affect the max level of submarine upgrades that apply / can be bought. -- Updated prices of all submarines to match tiers. -- Gave Typhon 2 better stats and even more firepower, to outclass the original Typhon. -- Improved R-29 speed and gave a Flak cannon -- Added Large weapon hardpoints to Berilia to make it a Tier 3 transport. - -Optimization: -- Optimizations to the talent system, particularly when the talent menu is open and when there's a large number of talents (e.g. when using mods that make all talents available to every class). - -Multiplayer: -- Significantly sped up file transfers (mods, submarine files, campaign saves). -- The minimum kick vote counts are no longer rounded down. Previously if you had for example four players on the server and the minimum vote count set to 60%, kicking would require 2 votes, now it requires 3. - -Submarine editor: -- Fixed turret lightsource rotation not refreshing in the sub editor when flipping the item. -- Fixed turret lightsource rotation not refreshing in the sub editor when flipping the item. - -Fixes: - Fixed linked subs still sometimes getting placed on the wrong side of the docking port when switching subs. -- Fixed PvP team assignment sometimes being wildly imbalanced, even if there's enough players with no preference to make the team sizes equal. - Fixes to ruin door connections, wiring and connection panels. - Fixed "insurance policy" giving the money to the dead character instead of the bank. - Fixed damage to mirrored wall pieces resetting between rounds. - Increased the minimum width of cave tunnels to prevent impassable paths. - Fixed deconstructor input slots becoming unlocked when starting a new round while the deconstructor is running. - -Modding: -- Fixed console errors when trying to check int values with PropertyConditionals. -- Fixed melee weapon's StrikingPowerMultiplier only affecting the afflictions defined in the Attack, not ones defined in the status effect. - ---------------------------------------------------------------------------------------------------------- -v0.19.1.0 ---------------------------------------------------------------------------------------------------------- - -Unstable only: -- Option to lock or reset draggable item UIs. -- Force item UI layout update when resolution changes. Fixes item repositioned item UIs potentially getting left outside the window when switching to a smaller resolution. -- Item UIs can't be dragged under each other. -- Fixed dragging a wire in a connection panel dragging the panel too if you bring the cursor close to the edges of the panel. -- Fixed seated bots being unable to get up from the chair. -- Fixed some more unwired lights in ResearchModule_02_Colony. -- Fixed another minor gap issue in Winterhalter. -- Fixed hull/item repairs and the money spent on them appearing to reset if you join mid-round after repairs have been purchased. -- Fixed light source staying in place after detaching a glowing item (e.g. mineral) from a wall. -- Fixed server trying to place all previously spawned respawn items into containers on every respawn, even if the items have already been removed. Happened because we never cleared respawnItems, and because we used that list when placing new respawn items into containers. - -Changes and additions: -- Overvoltage makes devices perform better, increasing the output of engines, making fabricators, deconstructors and pumps operate faster, electrical discharge coils do more damage, batteries recharge faster and oxygen generators generate more oxygen. Incentivizes operating the reactor manually and hopefully makes it a little more engaging. -- Added more randomness to junction box overvoltage damage, and made partially damaged boxes take more damage from overvoltage. Prevents all boxes from breaking at the same time, making overvoltage less of a pain in the ass to deal with and intentionally overvolting the devices more worthwhile. -- Experimental: Added a way to immediately increase/decrease the temperature of the reactor for a brief amount of time (atm bumps the gauge up/down by a fifth, and the boost fades out in 20 seconds). Allows reacting to load fluctuations very quickly, and to conserve fuel by operating the reactor at a lower fission rate = serves as another incentive to operate it manually. -- Signals no longer set the fission and turbine rates of the reactor instantaneously, making automated reactor circuits less overpowered. They are still viable, but especially now with the addition of the extra incentives for operating the reactor manually, they're no longer as clearly the best and most efficient way to operate the reactor, making manual operation more worthwhile. -- Added a Large Weapon Hardpoint. -- Railgun is now considered a large weapon. -- Added Flak Cannon and Double Coilgun as large weapons. -- Pulse Laser and Railgun now have similar power consumption as other turrets. -- Improved the way drag is applied on submerged items. Fixes heavy items dropping at unnaturally high speeds in water. -- Added a splash effect when an item falls into water. - -Balance: -- Reduced the pulse laser tri-laser bolt spread. -- Explosions are now calculated differently, using the number of limbs to divide the damage (to a max of 15 limbs). Adjusted explosion damage values to match new calculations. -- Coilgun costs 5000 to install, Pulse Laser and Chaingun 6000. Large turrets all cost 7500. -- Make mudraptor eggs slightly viable for farming (decreased cost from shop, increased deconstruction yields) -- Mineral yield and spawn rates rebalance, minerals found are now much more dependant on location (biome, cave, abyss) - -Submarines: -- Added a new intermediate transport sub, Camel. Still WIP, but feedback is welcome. -- Fixed messy wiring in Typhon 2's bottom left hardpoint. -- Added some loose vents and panels to Herja, Winterhalter and Barsuk, fixed invisible "loose panel" (news stand) in Orca 2. - -Fixes: -- Fixed brief freezes when monsters spawn mid-round. +- Fixed Grenade Launcher quality doing basically nothing, because it increased the minuscule amount of blunt force trauma the grenade causes on impact instead of the explosion damage. - Fixed vitality modifiers not being taken into account in the readings in the health interface. For example, gunshot wounds on the head cause a x2 larger vitality drop than on other limbs, but this wasn't displayed on the health interface. - Fixed Planet Neon Sign sprite bleed. -- Fixed Grenade Launcher quality doing basically nothing, because it increased the minuscule amount of blunt force trauma the grenade causes on impact instead of the explosion damage. -- Fixed level resource spawn rate not properly respecting level generations parameters' resource spawn chance values. +- Fixed level resource spawn rate not properly respecting the resource spawn chance values of level generation parameters. - Fixed some text overflows in the hiring menu when using a small HUD scale. - Fixed name on an ID card resetting to the original name if you rename a character and then start a new round. - Fixed handcuffs in the backmost hand being drawn in front of the character. - Fixed water splashes appearing in an incorrect hull when a character's limb moves from a flooded hull to another hull, where the limb is no longer underwater. -- Fixed crashing when a signal causes a wired item to get dropped (e.g. when you attach a detonator to a destructible ice wall and blow it up). +- Fixed crashing when a signal causes a wired item to be dropped (e.g. when you attach a detonator to a destructible ice wall and blow it up). - Oxygen generators and shelves don't fill up oxygen tanks when on fire. Caused repeated explosions when the tank constantly refilled and re-exploded. - Fixed "gene harvester" and "deep sea slayer" working on all enemies, not just monsters. -- Fixed floating point inaccuracy sometimes preventing items from being used as fabrication ingredients (e.g. oxygen generator may sometimes only fill tanks up to something like 99.9998%, which prevented it from being used in recipes that require a full tank). +- Fixed floating point inaccuracy sometimes preventing items from being used as fabrication ingredients (e.g. an oxygen generator may sometimes only fill tanks up to something like 99.9998%, which prevented it from being used in recipes that require a full tank). - Fixed item picking timer (e.g. detaching an item from a wall) ticking down when the game is paused. -- Fixed outpost supply cabs missing the oxygen tank spawns. -- Made the water current outside the levels start from the same point where monsters start heading towards the level, to make sure monsters can't escape too far from sub with a weak engine. +- Fixed outpost supply cabinets missing the oxygen tank spawns. +- Made the water current outside the levels start from the same point where monsters start heading towards the level, to make sure monsters can't escape too far from subs with a weak engine. - Fixed hardened diving knife recipe. - Fixed probability multiplier not being shown in wearable tooltip if the damage multiplier is 1. - Yet another attempt to prevent beacon missions from failing for apparently no reason: sonar monitors won't get damaged by water after the beacon's been activated. -- Don't stop selecting text in a textbox if the cursor goes outside the box. - -Multiplayer: -- Fixed clients getting stuck in the loading screen if they happen to disconnect at the right moment between rounds. -- Fixed bank balance not getting corrected if it's gotten desynced by e.g. client-side commands. -- Fixed server not registering a client's character as disconnected if the client disconnects and reconnects before the round has fully started, causing the client to get stuck as a spectator when they rejoin. -- Fixed clients disabling their client-side-only mods when they join a server. -- Fixed hull/item repairs purchased from an outpost sometimes not getting applied client-side. - -Submarine editor: -- Fixed prefab placement breaking in the sub editor if LMB is held while moving the cursor outside of the selection panel. -- Fixed several instances of janky UI interactions in the submarine editor: dragging selection rectangle now works even if cursor reaches into the prefab list, letting go of a dragged entity works even if cursor reaches into the prefab list, dragged entity no longer goes invisible when reaching into the prefab list. -- Made PowerContainer recharge speed always default to 0. -- Fixed adding resizeable items (like ladders) not being registered in sub editor's command history, preventing undoing it. - -Modding: -- Added DamageMultiplier and LaunchImpulse to Turret. LaunchImpulse is now defined on turrets instead of ammunition (total impulse is the sum of turret + ammunition). -- Added SnapRopeOnNewAttack property to Attacks: allows characters to switch attacks without snapping ropes from previous attacks. -- Added dividebylimbcount to Explosion, which determines whether the damage is spread out among limbs (if set to true). -- UpgradeCategories with no upgrades in them are hidden from the upgrade menu (i.e. if you modify the upgrades so some of the vanilla categories no longer contain any upgrades, those categories won't be shown). -- Fixed affliction names and descriptions being empty if they're not available in the selected language nor configured in the affliction xml directly. -- Fix custom infected husks seeking for any husk infection targeting the matching species instead of checking that also the husk affliction prefab matches the husk. - ---------------------------------------------------------------------------------------------------------- -v0.19.0.0 ---------------------------------------------------------------------------------------------------------- - -Changes: -- Device/item UIs can be moved around by dragging. -- Allow using devices while on a ladder or sitting on a chair. -- Readded the option to have separate push-to-talk binds for local and radio voice chat (by default not bind to anything). -- The deconstructor UI shows what the input items deconstruct to (particularly important now with the lossy deconstruction recipes - it can be risky to deconstruct something just to see what materials it gives out if that results in material loss). -- Wall and device repair costs in outposts are calculated based on the amount of damage in your sub, instead of always having a fixed price. -- Inflamed lung doesn't affect characters that don't need oxygen. -- Added swarm behavior for crawler husks. -- Added some more oomph to nuclear explosions. -- Adjust the alpha of the outpost service icons according to distance to make it easier to estimate where the NPC is at, show the title of the NPC when hovering the cursor over the icon. -- Added "unlockmission" console command. -- Added "setcampaignmetadata" console command (may be useful for modders creating custom scripted events for the campaign?). -- Changed how NPC "titles" work. Previously we defined "titles" for the pirates (e.g. "Pirate Lord" and such), and the title replaced the name of the NPC (which made their dialog a little awkward). Now we display both the name and the title over the character, and special outpost NPCs also have titles. -- Gave diving masks to most NPCs. -- Changed the burn overlay formula: now also the non-affected limbs get half of the effect, because it looks weird if there's a sharp contrast between the limbs. -- Restored the 3 shell railgun rack as a legacy option. -- Reworded the "respawn with penalty" prompt to make it less confusing: you always get a penalty to your skills when you die now, and Reaper's Tax is an "extra penalty" you get on top of that if you opt to respawn mid-round. The intention behind this is to incur a cost to respawning: it shouldn't be possible to get unlimited free reinforcements and supplies mid-round. -- Made SIGTERM close the linux server gracefully. -- Made respawn items (suits, scooters) spawn in the respawn shuttle's cabinets when possible. -- Show a healthbar on items (e.g. eggs and thalamus organs) when damaging them with handheld weapons (melee or ranged). - -Balance: -- "Mission cheesing" by repeatedly undocking and redocking to an outpost to reroll the mission events no longer works: new mission events don't reappear until one "world step" has passed (~10 minutes or traversing through one level). -- Balance pass on handheld weapons: adjusted reload times, damages, stun durations, recoil and ammo stack sizes. -- Reduced tools' structure damages (dual-wielded storage containers no longer chew through submarine walls in seconds). -- Increased heavy ruin wall health to make it less easy to cheese your way in to the artifact room in ruins. -- Made boomstick fire in bursts of 2 (similar to deadeye carbide) to prevent ridiculous fire rates with quick-reloading. -- Added EMP effect to nuclear depth charges for consistency. -- Changed how skill levels affect the quality of fabricated items. Previously having a skill level equal to or higher than the item's skill requirement would result in a good quality item, meaning that practically everyone could e.g. fabricate good quality oxygen tanks. Now your skill needs to be >20% from the minimum skill requirement towards 100, (e.g. if the item requires 20 skill to fabricate, 36 results in a higher quality item). -- Reduced PUCS's radiation resistance from 100% to 90%. Complete invulnerability to radiation has way too much potential for exploits and overpowered strategies. -- Adjusted supplies in pirate submarines. -- Turn some weapons' burn damage into explosion damage (wip). -- Terminal ignores empty signals. -- Reduce commonness of Molochs (as they can take a lot of time to kill, running into multiple of them can quickly become a chore) -- Remove steel requirement for depth charges. Fabricate decoy depth charges from depth charge, rather than base material. - -Multiplayer: -- Clients who've recently joined (by default 2 minutes) are not allowed to vote to kick others, and vote kicking someone always requires at least 2 votes. -- Fixed "missing entity" errors in a specific situation in multiplayer. Occurred when a respawn shuttle was enabled and loaded on the server (= i.e. in a non-outpost level), and a client disconnected and immediately reconnected. This would cause the client to deselect the respawn shuttle, and make them start the round without loading one, leading to the "missing entity" issues due to the shuttle only existing server-side. -- Fixes damage visuals not showing on characters who've died off-screen. -- Servers doent allow selecting hidden jobs (jobs only used by NPCs) as job preferences. -- Fixed ability to upgrade the sub when there's a switch pending in multiplayer. -- Fixed friendly fire and karma always showing up as disabled on dedicated servers in the server list. -- Fixed spineling spikes fired by a human with spineling genes not damaging any human characters (enemies in PvP, pirates in pirate missions) when friendly fire is disabled. -- Fixed "invalid ExecuteAttack message: limb index out of bounds" errors when you join a server where a character has fired spineling spikes with spineling genes mid-round. -- Fixed "entity not found" errors if a shuttle or submarine ends up absurdly deep in multiplayer (> 100 km). I don't even know how someone managed to pull this off. -- Fixed rapidly clicking on the mission giver with the Use input set to LMB sometimes not giving all the available missions. Happened because the conversation logic didn't check if there's another conversation active, causing the server to show a new conversation when clicking the NPC, without interrupting/continuing the previous conversation. -- Made shockjock event only show for the player triggering the event (making it visible for everyone works kind of weirdly, when the event involves talking to an NPC next to the character who triggered the event). -- Fixed outpost events getting stuck at the last ConversationAction if another client has finished the action. - -Submarines: -- Changed default reactor output from 10,000 kW to 5000 kW. -- Decreased Winterhalter reactor output, increased fuel consumption rate. -- Fixed some gap issues in Winterhalter. -- Fixed medics not having access to the toxin cabinet in Barsuk. -- Fixed medic, engineer and mechanic spawnpoints having no tags in Typhon. - -Fixes: +- Fixed text selection in a textbox stopping when the cursor goes outside the box. - Fixed fire, breach and intruder report icons not being shown to anyone. - Fixed missing/unwired lighting in ResearchModule_02_Colony. - Remove particles when switching screens (otherwise e.g. particles from the previous round are still in the level if you happen to be looking at the right spot). - Thalamus or ice walls can't be welded. - Quick-reloading tries to reload the item whose contained items have the lowest condition. In other words, if you've equipped 2 weapons, quick-reloading reloads the one with the least ammo instead of the one that's the first in your inventory. -- Fixed messed up dementonite and depleted fuel tool recipes. +- Fixed erroneous dementonite and depleted fuel tool recipes. - Fixed swapping a scaled turret/hardpoint causing the new one to be misplaced. - Fixed inability to upgrade the sub or do maintenance if you buy and opt to switch to a new sub, and then go to the submarine switch terminal to cancel the switching. - Fixed stolen items becoming non-stolen when deconstructed. -- Fixed ItemContainer UI popping up (with no visible inventory slot) when you pick one up. For example when you pick up a detonator from the floor. +- Fixed ItemContainer UI popping up (with no visible inventory slot) when you pick one up, e.g. picking up a detonator from the floor. - Fixed "[E] Rewire" hover text being shown on attachable items that haven't been attached to a wall (even though they can't be rewired until attached). -- Fixed trying to bind multiple console commands to the same key with the "binkey" command crashing the game. -- Fixed high-quality revolvers having no difference to normal-quality ones. They should get a 10% damage boost per quality level, but didn't due to incorrectly configured quality stats. +- Fixed trying to bind multiple console commands to the same key with the "bindkey" command crashing the game. +- Fixed high-quality revolvers having no difference to normal-quality ones. They should get a 10% damage boost per quality level but didn't, due to incorrectly configured quality stats. - Fixed multiple monster missions sometimes spawning the monsters close to each other, causing them to attack each other. -- Fix monsters sometimes using wrong animation parameters while idling (or moving slowly). +- Fixed monsters sometimes using the wrong animation parameters while idling (or moving slowly). - Fixed nuclear depth decoy using the same sprite as the normal depth decoy. - Fixed fractal guardian VitalityMultipliers being configured incorrectly (using the "type" attribute but with affliction identifiers instead of types). - Fixed incorrectly sized thalamus wall colliders, added background sprites to the walls. - Fixed "tried to overwrite a submarine that's not in a local package" error when loading and trying to save a submarine autosave file. - Fixed location portraits sometimes not showing up in the mission tab. Happened when we initialized the mission tab before the portrait had been loaded. -- Fixed coilguns and chainguns not always playing the firing sound when fired. Happened because their audio clips were so long (albeit mostly silence) that firing the them continously lead to a ton of clips playing simultaneously, exhausting the available audio channels. -- Fixed monster missions' sonar marker being placed incorrectly if a monster ends up inside the sub, making it look as if the monster was far outside the level. This often made it look like the monster was moving away from the sub when trying to "approach it". -- Fixed security officer tutorial getting stuck if you equip the weapons and gear before the objective to do so appears. -- Fixed bandolier (and other items that give bonuses when worn) giving the bonuses when the item is held. +- Fixed Coilguns and Chainguns not always playing the firing sound when fired. Happened because their audio clips were so long (albeit mostly silent) that firing them continuously led to a ton of clips playing simultaneously, exhausting the available audio channels. +- Fixed monster missions' sonar marker being placed incorrectly if a monster ends up inside the sub, making it look as if the monster was far outside the level. This often made it look like the monster was moving away from the sub when trying to approach its position as it appeared on the sonar? +- Fixed bandolier (and other items that give bonuses when worn) giving bonuses when the item is held. - Fixed mod texts being briefly misaligned when scrolling down the list of unpublished mods. - Fixed light sprite rotation not getting refreshed when placing an attachable item on a wall when lighting has been disabled with console commands. - Fixed supercapacitors showing 1% as the initial recharge rate because the recharge rate defaulted to 10. - Fixed some ending options of the "good samaritan" outpost event not ending the event. - Fixed random (non-mission) events disappearing from outposts when you save and quit. -Submarine editor: -- Fixed crashing when trying to multi-edit a string value in the sub editor. -- Fixed dragged object becoming invisible if you bring the cursor over an UI element in the sub editor. -- Fixed screwdrivers and wires in your "inventory" being included in the total item count in the sub editor's wiring mode. -- Fixed entities that were below the cursor when starting to resize a structure staying highlighted during resizing. -- Fixed sub editor treating the autosave interval as minutes instead of seconds (saving every 300 minutes instead of 300 seconds). - Modding: +- The tutorials are now implemented using the scripted event system, and are fully moddable. New tutorials can be implemented in xml or using the event editor, and the system could potentially be used for other types of content too (scripted "scenarios" perhaps?). +- Added DamageMultiplier and LaunchImpulse to Turret. LaunchImpulse is now defined on turrets instead of ammunition (total impulse is the sum of turret + ammunition). +- Added SnapRopeOnNewAttack property to Attacks: allows characters to switch attacks without snapping ropes from previous attacks. +- Added dividebylimbcount to Explosion, which determines whether the damage is spread out among limbs (if set to true). +- UpgradeCategories with no upgrades in them are hidden from the upgrade menu (i.e. if you modify the upgrades so some of the vanilla categories no longer contain any upgrades, those categories won't be shown). +- Added CheckTalentAction, which can be used in events to check whether a target has unlocked a specific talent. +- Changed how submarine upgrades are calculated: now no longer adds previous levels' cost to the price, but rather relies on higher increasehigh values. - Made NPC personality traits a separate content type instead of defining them in the localization files. - Fixed OnDeath status effects defined in afflictions not working. Did not affect any vanilla content. - Fixed crash when controlling a character with more than 10 "Any" inventory slots. Did not affect any vanilla content. - Fixed custom husk appendages' textures failing to load. - Added new properties to StatusEffect's SpawnCharacter feature: Stun, AfflictionOnSpawn, AfflictionStrength, TransferControl, RemovePreviousCharacter, TransferBuffs, TransferAfflictions, TransferInventory. - Fixed bots always choosing their "personality trait" from the first 6 even if more are modded in. +- Fixed affliction names and descriptions being empty if they're not available in the selected language or configured in the affliction .xml file directly. +- Fixed custom husk afflictions not always working properly, because the vanilla husk affliction was sometimes used instead of the custom husk affliction. +- Fixed ExtraLoad working the wrong way around on PowerTransfer components that generate/consume power (the extra load would supply power to the grid). Does not affect the vanilla game, because neither junction boxes or relays generate or consume power. +- Fixed crashing if Afflictions defined in an Attack can't be found. +- Fixed crashing if a Throwable has an OnActive StatusEffect that removes or kills the user. +- Fixed items/structures falling back to the description defined in the .xml even if it's empty, if the description is not defined for the selected language. Now descriptions fall back to English when not defined for the selected language. +- Fixed the "reloadwearables" and "loadwearable" console commands crashing the game when used outside the character editor. +- Fixed character editor crash if you first reload textures and then recreate the ragdoll. +- Fixed inability to localize item names if the name is defined directly in the item config. +- Allowed defining where mineral mission resources are spawned using the "positiontype" attribute. The supported types are "MainPath", "SidePath", "Cave", and "AbyssCave". +- Fixed console errors when trying to check int values with PropertyConditionals. +- Fixed melee weapon's StrikingPowerMultiplier only affecting the afflictions defined in the Attack, not the ones defined in the status effect. --------------------------------------------------------------------------------------------------------- v0.18.15.2 (MacOS only) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 770fb43d8..05b530ad5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -16,12 +16,12 @@ Before you start doing modifications to the code or submitting pull requests to ### Getting started #### Windows and macOS -You need a version of Visual Studio that supports C# 8.0 to compile game. If you don't have a compatible version of Visual Studio installed, you can get the latest version of Visual Studio from the following link: https://visualstudio.microsoft.com/ +You need a version of Visual Studio that supports C# 10 to compile game. If you don't have a compatible version of Visual Studio installed, you can get the latest version of Visual Studio from the following link: https://visualstudio.microsoft.com/ When installing on Windows, make sure you select ".NET desktop development" during the install process to make sure you have the required features to work with Barotrauma. #### Linux -You will need to install the .NET Core 3.0 SDK according to the instructions laid out on Microsoft's docs: https://docs.microsoft.com/en-us/dotnet/core/install/linux-package-manager-ubuntu-1904 +You will need to install the .NET 6 SDK according to the instructions laid out on Microsoft's docs: https://docs.microsoft.com/en-us/dotnet/core/install/linux To edit the source code, we recommend using [Visual Studio Code](https://code.visualstudio.com/) with [Microsoft's C# extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode.csharp). @@ -38,7 +38,7 @@ To develop from Visual Studio, open the solution that corresponds to the platfor You can also use your favorite source editor and build through the command line by navigating to the projects you wish to build and running the following command: `dotnet build [project].csproj -c [Debug/Release] /p:Platform=x64` -To deploy for release, run the scripts in the `Deploy` directory; the resulting binaries you'll want to redistribute should be found at `Barotrauma/bin/Release[Windows/Mac/Linux]/netcoreapp3.0/[win-x64/osx-x64/linux-x64]/publish` +To build for release, run one of the scripts found in the `Deploy` directory. The resulting binaries should be found at `Barotrauma/Deploy/bin/content`. The `BarotraumaShared/Content` folder, which contains Barotrauma's art, item XMLs, sounds, and other assets, is not included in the GitHub repository. If you have a legal copy of the game, you can copy the `Content` folder from the game's files to `BarotraumaShared/Content`. diff --git a/Libraries/GameAnalytics/GA_SDK_NETSTANDARD/GA_SDK_NETSTANDARD.csproj b/Libraries/GameAnalytics/GA_SDK_NETSTANDARD/GA_SDK_NETSTANDARD.csproj index aab701817..6b99d030a 100644 --- a/Libraries/GameAnalytics/GA_SDK_NETSTANDARD/GA_SDK_NETSTANDARD.csproj +++ b/Libraries/GameAnalytics/GA_SDK_NETSTANDARD/GA_SDK_NETSTANDARD.csproj @@ -26,7 +26,7 @@ - + diff --git a/LinuxSolution.sln b/LinuxSolution.sln index 095528d41..6d0f82d4f 100644 --- a/LinuxSolution.sln +++ b/LinuxSolution.sln @@ -41,6 +41,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MonoGame.Framework.Linux.Ne EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LinuxTest", "Barotrauma\BarotraumaTest\LinuxTest.csproj", "{F1B80D94-8BD6-48CE-8D17-BB2A5C98BCA3}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DeployAll", "Deploy\DeployAll\DeployAll.csproj", "{60B82E13-2CDD-4C74-8373-FD7264D6C80B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -195,6 +197,18 @@ Global {F1B80D94-8BD6-48CE-8D17-BB2A5C98BCA3}.Unstable|Any CPU.Build.0 = Debug|Any CPU {F1B80D94-8BD6-48CE-8D17-BB2A5C98BCA3}.Unstable|x64.ActiveCfg = Debug|Any CPU {F1B80D94-8BD6-48CE-8D17-BB2A5C98BCA3}.Unstable|x64.Build.0 = Debug|Any CPU + {60B82E13-2CDD-4C74-8373-FD7264D6C80B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {60B82E13-2CDD-4C74-8373-FD7264D6C80B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {60B82E13-2CDD-4C74-8373-FD7264D6C80B}.Debug|x64.ActiveCfg = Debug|Any CPU + {60B82E13-2CDD-4C74-8373-FD7264D6C80B}.Debug|x64.Build.0 = Debug|Any CPU + {60B82E13-2CDD-4C74-8373-FD7264D6C80B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {60B82E13-2CDD-4C74-8373-FD7264D6C80B}.Release|Any CPU.Build.0 = Release|Any CPU + {60B82E13-2CDD-4C74-8373-FD7264D6C80B}.Release|x64.ActiveCfg = Release|Any CPU + {60B82E13-2CDD-4C74-8373-FD7264D6C80B}.Release|x64.Build.0 = Release|Any CPU + {60B82E13-2CDD-4C74-8373-FD7264D6C80B}.Unstable|Any CPU.ActiveCfg = Debug|Any CPU + {60B82E13-2CDD-4C74-8373-FD7264D6C80B}.Unstable|Any CPU.Build.0 = Debug|Any CPU + {60B82E13-2CDD-4C74-8373-FD7264D6C80B}.Unstable|x64.ActiveCfg = Debug|Any CPU + {60B82E13-2CDD-4C74-8373-FD7264D6C80B}.Unstable|x64.Build.0 = Debug|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -213,6 +227,7 @@ Global {2B0881F6-9C67-4446-A1F2-FC042763A462} = {68B18BE6-9EE0-49DA-AE3A-4C7326F768F9} {33E95A21-E071-4432-819F-AA64CF3EF3F1} = {DE36F45F-F09E-4719-B953-00D148F7722A} {F1B80D94-8BD6-48CE-8D17-BB2A5C98BCA3} = {68B18BE6-9EE0-49DA-AE3A-4C7326F768F9} + {60B82E13-2CDD-4C74-8373-FD7264D6C80B} = {F35DF9BF-0BED-4FEF-A51C-DD83C531882F} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {17032EAB-554B-4B44-A4F6-EFB177ACAB7A} diff --git a/MacSolution.sln b/MacSolution.sln index 20e699038..77418fa58 100644 --- a/MacSolution.sln +++ b/MacSolution.sln @@ -31,6 +31,9 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MacServer", "Barotrauma\BarotraumaServer\MacServer.csproj", "{8C3F4314-E5CA-4563-BEE6-69E97CAA0813}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MacClient", "Barotrauma\BarotraumaClient\MacClient.csproj", "{F17FB469-E9E6-4B1C-B887-4FE709D4D771}" + ProjectSection(ProjectDependencies) = postProject + {8C3F4314-E5CA-4563-BEE6-69E97CAA0813} = {8C3F4314-E5CA-4563-BEE6-69E97CAA0813} + EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MonoGame.Framework.MacOS.NetStandard", "Libraries\MonoGame.Framework\Src\MonoGame.Framework\MonoGame.Framework.MacOS.NetStandard.csproj", "{35DDDA7D-328D-4A5D-BCBB-2E60C830A899}" EndProject @@ -38,6 +41,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Facepunch.Steamworks.Posix" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MacTest", "Barotrauma\BarotraumaTest\MacTest.csproj", "{20BC9336-B439-4BF1-8B65-D587DBF421D1}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DeployAll", "Deploy\DeployAll\DeployAll.csproj", "{36B38D18-3574-4B67-A89C-FD3C2D39F1D6}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -192,6 +197,18 @@ Global {20BC9336-B439-4BF1-8B65-D587DBF421D1}.Unstable|Any CPU.Build.0 = Debug|Any CPU {20BC9336-B439-4BF1-8B65-D587DBF421D1}.Unstable|x64.ActiveCfg = Debug|Any CPU {20BC9336-B439-4BF1-8B65-D587DBF421D1}.Unstable|x64.Build.0 = Debug|Any CPU + {36B38D18-3574-4B67-A89C-FD3C2D39F1D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {36B38D18-3574-4B67-A89C-FD3C2D39F1D6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {36B38D18-3574-4B67-A89C-FD3C2D39F1D6}.Debug|x64.ActiveCfg = Debug|Any CPU + {36B38D18-3574-4B67-A89C-FD3C2D39F1D6}.Debug|x64.Build.0 = Debug|Any CPU + {36B38D18-3574-4B67-A89C-FD3C2D39F1D6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {36B38D18-3574-4B67-A89C-FD3C2D39F1D6}.Release|Any CPU.Build.0 = Release|Any CPU + {36B38D18-3574-4B67-A89C-FD3C2D39F1D6}.Release|x64.ActiveCfg = Release|Any CPU + {36B38D18-3574-4B67-A89C-FD3C2D39F1D6}.Release|x64.Build.0 = Release|Any CPU + {36B38D18-3574-4B67-A89C-FD3C2D39F1D6}.Unstable|Any CPU.ActiveCfg = Debug|Any CPU + {36B38D18-3574-4B67-A89C-FD3C2D39F1D6}.Unstable|Any CPU.Build.0 = Debug|Any CPU + {36B38D18-3574-4B67-A89C-FD3C2D39F1D6}.Unstable|x64.ActiveCfg = Debug|Any CPU + {36B38D18-3574-4B67-A89C-FD3C2D39F1D6}.Unstable|x64.Build.0 = Debug|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -210,6 +227,7 @@ Global {35DDDA7D-328D-4A5D-BCBB-2E60C830A899} = {DE36F45F-F09E-4719-B953-00D148F7722A} {F10CE3BB-26B8-446E-84D2-86D25E850F61} = {DE36F45F-F09E-4719-B953-00D148F7722A} {20BC9336-B439-4BF1-8B65-D587DBF421D1} = {DFD82BBD-8D05-403D-BEBC-F4C1CF783E18} + {36B38D18-3574-4B67-A89C-FD3C2D39F1D6} = {F35DF9BF-0BED-4FEF-A51C-DD83C531882F} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {17032EAB-554B-4B44-A4F6-EFB177ACAB7A} diff --git a/README.md b/README.md index 7943f78d2..e1bc8861d 100644 --- a/README.md +++ b/README.md @@ -20,8 +20,8 @@ If you're interested in working on the code, either to develop mods or to contri ## Prerequisities: ### Windows -- [Visual Studio](https://www.visualstudio.com/vs/community/) with C# 8.0 support (VS 2019 or later recommended) +- [Visual Studio](https://www.visualstudio.com/vs/community/) with C# 10 support (VS 2022 or later recommended) ### Linux -- [.NET Core 3.1 SDK](https://docs.microsoft.com/en-us/dotnet/core/install/linux-package-manager-ubuntu-1904) +- [.NET 6 SDK](https://docs.microsoft.com/en-us/dotnet/core/install/linux) ### macOS -- [Visual Studio 2019 for Mac](https://visualstudio.microsoft.com/vs/mac/) +- [Visual Studio 2022 for Mac](https://visualstudio.microsoft.com/vs/mac/) diff --git a/WindowsSolution.sln b/WindowsSolution.sln index 515450184..54c1ad98e 100644 --- a/WindowsSolution.sln +++ b/WindowsSolution.sln @@ -41,6 +41,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharpFont.NetStandard", "Li EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WindowsTest", "Barotrauma\BarotraumaTest\WindowsTest.csproj", "{C7212AE2-A925-4225-A639-AE0653EF65B0}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DeployAll", "Deploy\DeployAll\DeployAll.csproj", "{C98FE0D0-BC7D-4806-B592-734B53016FD8}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution Libraries\GameAnalytics\GA-SDK-MONO-SHARED\GA-SDK-MONO-SHARED.projitems*{95c4d59d-9be4-4278-b4f8-46c0ba1a3916}*SharedItemsImports = 5 @@ -123,6 +125,12 @@ Global {C7212AE2-A925-4225-A639-AE0653EF65B0}.Release|x64.Build.0 = Release|Any CPU {C7212AE2-A925-4225-A639-AE0653EF65B0}.Unstable|x64.ActiveCfg = Debug|Any CPU {C7212AE2-A925-4225-A639-AE0653EF65B0}.Unstable|x64.Build.0 = Debug|Any CPU + {C98FE0D0-BC7D-4806-B592-734B53016FD8}.Debug|x64.ActiveCfg = Debug|Any CPU + {C98FE0D0-BC7D-4806-B592-734B53016FD8}.Debug|x64.Build.0 = Debug|Any CPU + {C98FE0D0-BC7D-4806-B592-734B53016FD8}.Release|x64.ActiveCfg = Release|Any CPU + {C98FE0D0-BC7D-4806-B592-734B53016FD8}.Release|x64.Build.0 = Release|Any CPU + {C98FE0D0-BC7D-4806-B592-734B53016FD8}.Unstable|x64.ActiveCfg = Debug|Any CPU + {C98FE0D0-BC7D-4806-B592-734B53016FD8}.Unstable|x64.Build.0 = Debug|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -141,6 +149,7 @@ Global {1F318AC4-F808-4130-867F-B98DF9AA8F95} = {DE36F45F-F09E-4719-B953-00D148F7722A} {6911872D-40EF-400C-B0A1-9985A19ED488} = {DE36F45F-F09E-4719-B953-00D148F7722A} {C7212AE2-A925-4225-A639-AE0653EF65B0} = {78A9F0AA-5519-407A-9B72-2A09F5DF7068} + {C98FE0D0-BC7D-4806-B592-734B53016FD8} = {F35DF9BF-0BED-4FEF-A51C-DD83C531882F} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {17032EAB-554B-4B44-A4F6-EFB177ACAB7A}