From 8a2e2ea0ae197ed759caf27e69aa3103cfb60701 Mon Sep 17 00:00:00 2001 From: Markus Isberg Date: Fri, 10 Nov 2023 17:45:19 +0200 Subject: [PATCH] Unstable 1.2.1.0 --- .../ClientSource/Characters/Character.cs | 5 +- .../CircuitBox/CircuitBoxConnection.cs | 13 +- .../ClientSource/DebugConsole.cs | 18 +- .../Events/EventActions/ConversationAction.cs | 3 +- .../ClientSource/Events/EventManager.cs | 2 +- .../Events/Missions/CargoMission.cs | 3 +- .../ClientSource/GUI/GUIMessageBox.cs | 12 + .../ClientSource/GUI/GUINumberInput.cs | 67 ++--- .../ClientSource/GUI/Store.cs | 76 +++-- .../ClientSource/GUI/UISprite.cs | 2 +- .../ClientSource/GUI/UpgradeStore.cs | 6 +- .../ClientSource/GameSession/CargoManager.cs | 2 +- .../GameSession/GameModes/CampaignMode.cs | 10 + .../GameModes/MultiPlayerCampaign.cs | 2 +- .../ClientSource/GameSession/RoundSummary.cs | 2 +- .../Items/Components/GeneticMaterial.cs | 6 +- .../Items/Components/ItemComponent.cs | 9 +- .../Items/Components/ItemContainer.cs | 3 +- .../Items/Components/Machines/Fabricator.cs | 16 +- .../Items/Components/Machines/Sonar.cs | 61 ++-- .../Items/Components/Machines/Steering.cs | 9 +- .../Items/Components/RepairTool.cs | 2 +- .../Items/Components/Signal/CircuitBox.cs | 2 +- .../Components/Signal/CustomInterface.cs | 7 +- .../Items/Components/StatusHUD.cs | 6 +- .../ClientSource/Items/Inventory.cs | 33 ++- .../ClientSource/Items/Item.cs | 31 +- .../ClientSource/Items/ItemPrefab.cs | 20 +- .../BarotraumaClient/ClientSource/Map/Hull.cs | 43 ++- .../BackgroundCreatureManager.cs | 2 +- .../ClientSource/Map/Levels/Level.cs | 6 +- .../ClientSource/Map/Lights/LightSource.cs | 2 +- .../ClientSource/Map/Map/Radiation.cs | 2 +- .../ClientSource/Map/MapEntity.cs | 143 +++++++-- .../ClientSource/Map/MapEntityPrefab.cs | 2 +- .../ClientSource/Map/RoundSound.cs | 20 +- .../ClientSource/Map/Structure.cs | 38 ++- .../ClientSource/Map/StructurePrefab.cs | 13 +- .../ClientSource/Map/Submarine.cs | 14 +- .../ClientSource/Networking/Client.cs | 6 +- .../ClientSource/Networking/GameClient.cs | 25 +- .../Networking/ServerList/ServerInfo.cs | 28 +- .../ClientSource/Networking/ServerSettings.cs | 4 + .../ClientSource/Particles/ParticleEmitter.cs | 22 +- .../ClientSource/Particles/ParticlePrefab.cs | 3 +- .../BarotraumaClient/ClientSource/Program.cs | 3 + .../ClientSource/Screens/MainMenuScreen.cs | 115 ++++++-- .../ClientSource/Screens/NetLobbyScreen.cs | 47 ++- .../ServerListScreen/ServerListScreen.cs | 2 +- .../ClientSource/Screens/SubEditorScreen.cs | 271 ++++++++++-------- .../Serialization/SerializableEntityEditor.cs | 31 +- .../ClientSource/Sounds/OggSound.cs | 2 +- .../ClientSource/Sounds/Sound.cs | 6 +- .../ClientSource/Sprite/Sprite.cs | 43 ++- .../ClientSource/Steam/Lobby.cs | 4 + .../ClientSource/Utils/TextureLoader.cs | 9 +- .../BarotraumaClient/LinuxClient.csproj | 2 +- Barotrauma/BarotraumaClient/MacClient.csproj | 2 +- .../BarotraumaClient/WindowsClient.csproj | 2 +- .../BarotraumaServer/LinuxServer.csproj | 2 +- Barotrauma/BarotraumaServer/MacServer.csproj | 2 +- .../ServerSource/DebugConsole.cs | 130 ++++++--- .../Events/EventActions/EventLogAction.cs | 3 +- .../BarotraumaServer/ServerSource/GameMain.cs | 25 +- .../ServerSource/GameSession/CargoManager.cs | 32 +-- .../GameSession/GameModes/CampaignMode.cs | 12 +- .../GameModes/CharacterCampaignData.cs | 16 +- .../GameModes/MultiPlayerCampaign.cs | 59 +++- .../Items/Components/Signal/CircuitBox.cs | 9 +- .../ServerSource/Items/Inventory.cs | 3 +- .../BarotraumaServer/ServerSource/Map/Hull.cs | 4 +- .../ServerSource/Networking/ChatMessage.cs | 115 ++++---- .../ServerSource/Networking/GameServer.cs | 90 +++--- .../ServerSource/Networking/ServerSettings.cs | 2 +- .../ServerSource/Networking/Voting.cs | 12 +- .../ServerSource/Steam/SteamManager.cs | 15 +- .../ServerSource/Traitors/TraitorManager.cs | 12 +- .../BarotraumaServer/WindowsServer.csproj | 2 +- .../Data/campaignsettings.xml | 12 +- .../Characters/AI/EnemyAIController.cs | 15 +- .../Characters/AI/HumanAIController.cs | 35 ++- .../Characters/AI/Objectives/AIObjective.cs | 11 - .../Objectives/AIObjectiveCheckStolenItems.cs | 160 +++++++++++ .../AI/Objectives/AIObjectiveFindThieves.cs | 152 ++++++++++ .../AI/Objectives/AIObjectiveLoop.cs | 2 +- .../AI/Objectives/AIObjectiveManager.cs | 4 + .../SharedSource/Characters/AI/Order.cs | 3 +- .../Characters/Animation/AnimController.cs | 3 +- .../Animation/HumanoidAnimController.cs | 205 +++++++------ .../Characters/Animation/Ragdoll.cs | 13 +- .../SharedSource/Characters/Attack.cs | 68 +++-- .../SharedSource/Characters/Character.cs | 110 ++++--- .../SharedSource/Characters/CharacterInfo.cs | 19 +- .../Characters/CharacterPrefab.cs | 3 +- .../Health/Afflictions/AfflictionHusk.cs | 27 +- .../Health/Afflictions/AfflictionPrefab.cs | 27 +- .../Characters/Health/CharacterHealth.cs | 25 +- .../Characters/Health/DamageModifier.cs | 13 +- .../SharedSource/Characters/HumanPrefab.cs | 13 +- .../SharedSource/Characters/Jobs/Job.cs | 5 +- .../SharedSource/Characters/Jobs/JobPrefab.cs | 3 +- .../Params/Animation/AnimationParams.cs | 3 +- .../Params/Animation/FishAnimations.cs | 3 +- .../Characters/Params/CharacterParams.cs | 9 +- .../Characters/Params/EditableParams.cs | 6 +- .../Params/Ragdoll/RagdollParams.cs | 50 ++-- .../AbilityConditionals/AbilityCondition.cs | 5 +- .../AbilityConditionAffliction.cs | 5 +- .../AbilityConditionAttackData.cs | 3 +- .../AbilityConditionCharacter.cs | 5 +- .../AbilityConditionData.cs | 6 +- .../AbilityConditionItem.cs | 3 +- .../AbilityConditionMission.cs | 3 +- .../AbilityConditionHasPermanentStat.cs | 3 +- .../AbilityConditionHasStatusTag.cs | 3 +- .../Talents/Abilities/CharacterAbility.cs | 26 +- .../Abilities/CharacterAbilityApplyForce.cs | 3 +- ...ilityApplyStatusEffectsToApprenticeship.cs | 3 +- .../CharacterAbilityGainSimultaneousSkill.cs | 3 +- .../CharacterAbilityGiveAffliction.cs | 6 +- .../CharacterAbilityGiveExperience.cs | 16 +- .../Abilities/CharacterAbilityGiveMoney.cs | 3 +- .../CharacterAbilityGivePermanentStat.cs | 3 +- .../CharacterAbilityGiveReputation.cs | 6 +- .../CharacterAbilityGiveResistance.cs | 6 +- .../CharacterAbilityGiveTalentPoints.cs | 3 +- ...haracterAbilityGiveTalentPointsToAllies.cs | 3 +- .../CharacterAbilityIncreaseSkill.cs | 6 +- .../Abilities/CharacterAbilityMarkAsLooted.cs | 3 +- .../CharacterAbilityModifyResistance.cs | 6 +- .../Abilities/CharacterAbilityModifyValue.cs | 3 +- .../Abilities/CharacterAbilityPutItem.cs | 9 +- .../CharacterAbilityReduceAffliction.cs | 3 +- .../CharacterAbilityResetPermanentStat.cs | 3 +- .../CharacterAbilitySetMetadataInt.cs | 3 +- ...erAbilityUnlockApprenticeshipTalentTree.cs | 3 +- .../AbilityGroups/CharacterAbilityGroup.cs | 44 ++- .../Characters/Talents/CharacterTalent.cs | 17 +- .../Characters/Talents/TalentPrefab.cs | 3 +- .../Characters/Talents/TalentTree.cs | 12 +- .../ContentFile/AfflictionsFile.cs | 8 +- .../ContentFile/CharacterFile.cs | 7 +- .../ContentFile/ContentFile.cs | 3 +- .../ContentFile/GenericPrefabFile.cs | 2 +- .../ContentFile/NPCConversationsFile.cs | 1 + .../ContentFile/OrdersFile.cs | 3 +- .../ContentFile/RandomEventsFile.cs | 3 +- .../ContentManagement/ContentFile/TextFile.cs | 8 + .../ContentPackage/ContentPackage.cs | 5 +- .../ContentManagement/ContentXElement.cs | 3 +- .../SharedSource/DebugConsole.cs | 110 ++++--- .../SharedSource/Events/ArtifactEvent.cs | 10 +- .../SharedSource/Events/Event.cs | 2 +- .../EventActions/CheckConditionalAction.cs | 9 +- .../Events/EventActions/CheckDataAction.cs | 9 +- .../Events/EventActions/CheckItemAction.cs | 6 +- .../Events/EventActions/CheckOrderAction.cs | 3 +- .../EventActions/CheckReputationAction.cs | 8 +- .../EventActions/CheckSelectedAction.cs | 4 +- .../CheckTraitorEventStateAction.cs | 3 +- .../EventActions/CheckTraitorVoteAction.cs | 3 +- .../Events/EventActions/ConversationAction.cs | 3 +- .../Events/EventActions/CountTargetsAction.cs | 6 +- .../Events/EventActions/EventAction.cs | 11 +- .../Events/EventActions/EventLogAction.cs | 6 +- .../EventActions/EventObjectiveAction.cs | 6 +- .../Events/EventActions/GiveExpAction.cs | 3 +- .../Events/EventActions/GiveSkillExpAction.cs | 3 +- .../Events/EventActions/MissionAction.cs | 15 +- .../Events/EventActions/MissionStateAction.cs | 3 +- .../EventActions/ModifyLocationAction.cs | 9 +- .../EventActions/NPCChangeTeamAction.cs | 3 +- .../Events/EventActions/RNGAction.cs | 6 +- .../Events/EventActions/ReputationAction.cs | 6 +- .../SetTraitorEventStateAction.cs | 3 +- .../Events/EventActions/SkillCheckAction.cs | 3 +- .../Events/EventActions/SpawnAction.cs | 9 +- .../Events/EventActions/TagAction.cs | 66 +++-- .../Events/EventActions/TriggerEventAction.cs | 3 +- .../EventActions/TutorialHighlightAction.cs | 3 +- .../WaitForItemFabricatedAction.cs | 3 +- .../SharedSource/Events/EventManager.cs | 3 +- .../SharedSource/Events/EventPrefab.cs | 38 ++- .../SharedSource/Events/EventSet.cs | 109 ++++++- .../Missions/AbandonedOutpostMission.cs | 16 +- .../Events/Missions/AlienRuinMission.cs | 18 +- .../Events/Missions/BeaconMission.cs | 4 +- .../Events/Missions/CargoMission.cs | 5 +- .../Events/Missions/EndMission.cs | 21 +- .../Events/Missions/EscortMission.cs | 11 +- .../Events/Missions/MineralMission.cs | 6 +- .../SharedSource/Events/Missions/Mission.cs | 26 +- .../Events/Missions/MissionPrefab.cs | 18 +- .../Events/Missions/MonsterMission.cs | 12 +- .../Events/Missions/NestMission.cs | 9 +- .../Events/Missions/PirateMission.cs | 54 ++-- .../Events/Missions/SalvageMission.cs | 15 +- .../Events/Missions/ScanMission.cs | 15 +- .../SharedSource/Events/MonsterEvent.cs | 95 ++++-- .../SharedSource/Events/ScriptedEvent.cs | 17 +- .../SharedSource/GameSession/CargoManager.cs | 174 +++++++---- .../SharedSource/GameSession/CrewManager.cs | 2 +- .../GameSession/GameModes/CampaignMode.cs | 59 +++- .../GameSession/GameModes/CampaignSettings.cs | 6 + .../GameModes/MultiPlayerCampaign.cs | 7 +- .../GameSession/UpgradeManager.cs | 34 ++- .../SharedSource/Items/CharacterInventory.cs | 15 +- .../SharedSource/Items/Components/Door.cs | 27 ++ .../Items/Components/ElectricalDischarger.cs | 3 +- .../Items/Components/GeneticMaterial.cs | 6 +- .../SharedSource/Items/Components/Growable.cs | 3 +- .../Items/Components/Holdable/RangedWeapon.cs | 3 +- .../Items/Components/Holdable/RepairTool.cs | 8 +- .../Items/Components/Holdable/Throwable.cs | 2 +- .../Items/Components/ItemComponent.cs | 44 ++- .../Items/Components/ItemContainer.cs | 9 +- .../Items/Components/Machines/Controller.cs | 5 +- .../Items/Components/Machines/Fabricator.cs | 14 +- .../SharedSource/Items/Components/Planter.cs | 3 + .../Items/Components/Power/PowerTransfer.cs | 2 +- .../Items/Components/Projectile.cs | 3 +- .../SharedSource/Items/Components/Quality.cs | 3 +- .../SharedSource/Items/Components/Scanner.cs | 3 +- .../Items/Components/Signal/ButtonTerminal.cs | 3 +- .../Items/Components/Signal/CircuitBox.cs | 51 +++- .../Items/Components/TriggerComponent.cs | 3 +- .../SharedSource/Items/Components/Wearable.cs | 3 +- .../SharedSource/Items/Inventory.cs | 8 +- .../SharedSource/Items/Item.cs | 126 ++++++-- .../SharedSource/Items/ItemInventory.cs | 4 +- .../SharedSource/Items/ItemPrefab.cs | 130 ++++++--- .../SharedSource/Items/RelatedItem.cs | 4 +- .../SharedSource/Map/DummyFireSource.cs | 3 +- .../SharedSource/Map/Explosion.cs | 64 +++-- .../SharedSource/Map/FireSource.cs | 17 +- .../BarotraumaShared/SharedSource/Map/Gap.cs | 36 ++- .../BarotraumaShared/SharedSource/Map/Hull.cs | 6 +- .../SharedSource/Map/IDamageable.cs | 2 +- .../Map/Levels/DestructibleLevelWall.cs | 2 +- .../SharedSource/Map/Levels/Level.cs | 101 +++++-- .../Map/Levels/LevelObjects/LevelObject.cs | 2 +- .../Levels/LevelObjects/LevelObjectManager.cs | 3 +- .../SharedSource/Map/Map/Location.cs | 44 +-- .../Map/Map/LocationTypeChange.cs | 14 +- ...onStationInfo.cs => ExtraSubmarineInfo.cs} | 64 +++-- .../Map/Outposts/OutpostGenerationParams.cs | 2 +- .../SharedSource/Map/Structure.cs | 181 ++++++++---- .../SharedSource/Map/StructurePrefab.cs | 9 +- .../SharedSource/Map/Submarine.cs | 9 +- .../SharedSource/Map/SubmarineBody.cs | 5 +- .../SharedSource/Map/SubmarineInfo.cs | 18 +- .../SharedSource/Networking/BanList.cs | 10 + .../SharedSource/Networking/Client.cs | 6 +- .../SharedSource/Networking/ServerSettings.cs | 7 + .../Editable/ConditionallyEditable.cs | 66 +++++ .../Serialization/Editable/Editable.cs | 55 ++++ .../SerializableProperty.cs | 135 +-------- .../Serialization/StructSerialization.cs | 2 +- .../SharedSource/Settings/GameSettings.cs | 27 +- .../SharedSource/Sprite/Sprite.cs | 3 +- .../StatusEffects/PropertyConditional.cs | 4 +- .../StatusEffects/StatusEffect.cs | 101 ++++--- .../BarotraumaShared/SharedSource/Tags.cs | 4 +- .../Traitors/TraitorEventPrefab.cs | 5 +- .../SharedSource/Upgrades/Upgrade.cs | 3 +- .../SharedSource/Upgrades/UpgradePrefab.cs | 26 +- .../SharedSource/Utils/MathUtils.cs | 48 ++-- Barotrauma/BarotraumaShared/changelog.txt | 80 ++++++ 268 files changed, 4076 insertions(+), 1843 deletions(-) create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCheckStolenItems.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindThieves.cs rename Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/{BeaconStationInfo.cs => ExtraSubmarineInfo.cs} (54%) create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Serialization/Editable/ConditionallyEditable.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Serialization/Editable/Editable.cs rename Barotrauma/BarotraumaShared/SharedSource/Serialization/{ => SerializableProperty}/SerializableProperty.cs (91%) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs index 115a95e77..a223fe878 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs @@ -336,7 +336,10 @@ namespace Barotrauma float pressure = AnimController.CurrentHull == null ? 100.0f : AnimController.CurrentHull.LethalPressure; if (pressure > 0.0f) { - float zoomInEffectStrength = MathHelper.Clamp(pressure / 100.0f, 0.1f, 1.0f); + //lerp in during the 1st second of the pressure timer so the zoom doesn't + //"flicker" in and out if the pressure fluctuates around the minimum threshold + float timerMultiplier = (PressureTimer / 100.0f); + float zoomInEffectStrength = MathHelper.Clamp(pressure / 100.0f * timerMultiplier, 0.0f, 1.0f); cam.Zoom = MathHelper.Lerp(cam.Zoom, cam.DefaultZoom + (Math.Max(pressure, 10) / 150.0f) * Rand.Range(0.9f, 1.1f), zoomInEffectStrength); diff --git a/Barotrauma/BarotraumaClient/ClientSource/CircuitBox/CircuitBoxConnection.cs b/Barotrauma/BarotraumaClient/ClientSource/CircuitBox/CircuitBoxConnection.cs index 7fea1d1f1..ea89c9f57 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/CircuitBox/CircuitBoxConnection.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/CircuitBox/CircuitBoxConnection.cs @@ -8,24 +8,25 @@ namespace Barotrauma { internal abstract partial class CircuitBoxConnection { - public string Name => Label.Value.Value; + public string Name => Connection.Name; + public CircuitBoxLabel Label { get; private set; } private Sprite? knobSprite, screwSprite, connectorSprite; - private static int padding => GUI.IntScale(8); + private static int Padding => GUI.IntScale(8); private Option tooltip = Option.None; private partial void InitProjSpecific(CircuitBox circuitBox) { - Label = new CircuitBoxLabel(Connection.Name, GUIStyle.SubHeadingFont); + Label = new CircuitBoxLabel(Connection.DisplayName, GUIStyle.SubHeadingFont); knobSprite = circuitBox.ConnectionSprite; screwSprite = circuitBox.ConnectionScrewSprite; connectorSprite = circuitBox.WireConnectorSprite; - Length = Rect.Width + padding + Label.Size.X; + Length = Rect.Width + Padding + Label.Size.X; } public void Draw(SpriteBatch spriteBatch, Vector2 drawPos, Vector2 parentPos, Color color) @@ -41,11 +42,11 @@ namespace Barotrauma float xPos; if (IsOutput) { - xPos = drawRect.Left - padding - Label.Size.X; + xPos = drawRect.Left - Padding - Label.Size.X; } else { - xPos = drawRect.Right + padding; + xPos = drawRect.Right + Padding; } Vector2 stringPos = new Vector2(xPos, drawRect.Center.Y - Label.Size.Y / 2f); diff --git a/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs b/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs index d355c7cb9..de3f40011 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs @@ -34,7 +34,7 @@ namespace Barotrauma bool allowCheats = GameMain.NetworkMember == null && (GameMain.GameSession?.GameMode is TestGameMode || Screen.Selected is { IsEditor: true }); if (!allowCheats && !CheatsEnabled && IsCheat) { - NewMessage("You need to enable cheats using the command \"enablecheats\" before you can use the command \"" + names[0] + "\".", Color.Red); + NewMessage("You need to enable cheats using the command \"enablecheats\" before you can use the command \"" + Names[0] + "\".", Color.Red); #if USE_STEAM NewMessage("Enabling cheats will disable Steam achievements during this play session.", Color.Red); #endif @@ -215,9 +215,9 @@ namespace Barotrauma SoundPlayer.PlayUISound(GUISoundType.Select); } - private static bool IsCommandPermitted(string command, GameClient client) + private static bool IsCommandPermitted(Identifier command, GameClient client) { - switch (command) + switch (command.Value.ToLowerInvariant()) { case "kick": return client.HasPermission(ClientPermissions.Kick); @@ -304,7 +304,7 @@ namespace Barotrauma } }; var textBlock = new GUITextBlock(new RectTransform(new Point(listBox.Content.Rect.Width - 5, 0), textContainer.RectTransform, Anchor.TopLeft) { AbsoluteOffset = new Point(2, 2) }, - msg.Text, textAlignment: Alignment.TopLeft, font: GUIStyle.SmallFont, wrap: true) + RichString.Rich(msg.Text), textAlignment: Alignment.TopLeft, font: GUIStyle.SmallFont, wrap: true) { CanBeFocused = false, TextColor = msg.Color @@ -346,7 +346,7 @@ namespace Barotrauma CanBeFocused = false }; var textBlock = new GUITextBlock(new RectTransform(new Point(listBox.Content.Rect.Width - 170, 0), textContainer.RectTransform, Anchor.TopRight) { AbsoluteOffset = new Point(20, 0) }, - command.help, textAlignment: Alignment.TopLeft, font: GUIStyle.SmallFont, wrap: true) + command.Help, textAlignment: Alignment.TopLeft, font: GUIStyle.SmallFont, wrap: true) { CanBeFocused = false, TextColor = Color.White @@ -354,7 +354,7 @@ namespace Barotrauma textContainer.RectTransform.NonScaledSize = new Point(textContainer.RectTransform.NonScaledSize.X, textBlock.RectTransform.NonScaledSize.Y + 5); textBlock.SetTextPos(); new GUITextBlock(new RectTransform(new Point(150, textContainer.Rect.Height), textContainer.RectTransform), - command.names[0], textAlignment: Alignment.TopLeft); + command.Names[0].Value, textAlignment: Alignment.TopLeft); listBox.UpdateScrollBarSize(); listBox.BarScroll = 1.0f; @@ -364,7 +364,7 @@ namespace Barotrauma private static void AssignOnClientExecute(string names, Action onClientExecute) { - Command command = commands.Find(c => c.names.Intersect(names.Split('|')).Count() > 0); + Command command = commands.Find(c => c.Names.Intersect(names.Split('|').ToIdentifiers()).Any()); if (command == null) { throw new Exception("AssignOnClientExecute failed. Command matching the name(s) \"" + names + "\" not found."); @@ -378,7 +378,7 @@ namespace Barotrauma private static void AssignRelayToServer(string names, bool relay) { - Command command = commands.Find(c => c.names.Intersect(names.Split('|')).Count() > 0); + Command command = commands.Find(c => c.Names.Intersect(names.Split('|').ToIdentifiers()).Any()); if (command == null) { DebugConsole.Log("Could not assign to relay to server: " + names); @@ -706,6 +706,8 @@ namespace Barotrauma AssignRelayToServer("showmoney", true); AssignRelayToServer("setskill", true); AssignRelayToServer("readycheck", true); + commands.Add(new Command("debugjobassignment", "", (string[] args) => { })); + AssignRelayToServer("debugjobassignment", true); AssignRelayToServer("givetalent", true); AssignRelayToServer("unlocktalents", true); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/EventActions/ConversationAction.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/EventActions/ConversationAction.cs index 45893dc8b..efbdb6712 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Events/EventActions/ConversationAction.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/EventActions/ConversationAction.cs @@ -374,13 +374,12 @@ namespace Barotrauma btn.RectTransform.MinSize = new Point(0, (int)(btn.TextBlock.Rect.Height * 1.2f)); } - textContent.RectTransform.MinSize = new Point(0, textContent.Children.Sum(c => c.Rect.Height) + GUI.IntScale(16)); + textContent.RectTransform.MinSize = new Point(0, textContent.Children.Sum(c => c.Rect.Height + textContent.AbsoluteSpacing) + GUI.IntScale(16)); content.RectTransform.MinSize = new Point(0, content.Children.Sum(c => c.Rect.Height)); // Recalculate the text size as it is scaled up and no longer matching the text height due to the textContent's minSize increasing textBlock.CalculateHeightFromText(); textBlock.TextAlignment = Alignment.TopLeft; - //content.RectTransform.MinSize = new Point(0, textContent.Rect.Height); return buttons; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/EventManager.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/EventManager.cs index 27007040d..6b408b9a3 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Events/EventManager.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/EventManager.cs @@ -47,7 +47,7 @@ namespace Barotrauma } float theoreticalMaxMonsterStrength = 10000; - float relativeMaxMonsterStrength = theoreticalMaxMonsterStrength * (GameMain.GameSession?.LevelData?.Difficulty ?? 0f) / 100; + float relativeMaxMonsterStrength = theoreticalMaxMonsterStrength * (GameMain.GameSession?.Level?.Difficulty ?? 0f) / 100; float absoluteMonsterStrength = monsterStrength / theoreticalMaxMonsterStrength; float relativeMonsterStrength = monsterStrength / relativeMaxMonsterStrength; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/CargoMission.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/CargoMission.cs index 1a9941f42..3e9fdd7cb 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/CargoMission.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/CargoMission.cs @@ -51,7 +51,8 @@ namespace Barotrauma if (requiredDeliveryAmount == 0) { requiredDeliveryAmount = items.Count; } if (requiredDeliveryAmount > items.Count) { - DebugConsole.AddWarning($"Error in mission \"{Prefab.Identifier}\". Required delivery amount is {requiredDeliveryAmount} but there's only {items.Count} items to deliver."); + DebugConsole.AddWarning($"Error in mission \"{Prefab.Identifier}\". Required delivery amount is {requiredDeliveryAmount} but there's only {items.Count} items to deliver.", + contentPackage: Prefab.ContentPackage); requiredDeliveryAmount = items.Count; } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIMessageBox.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIMessageBox.cs index c5e6fda31..de557c274 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIMessageBox.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIMessageBox.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Linq; using Barotrauma.Extensions; +using Microsoft.Xna.Framework.Input; namespace Barotrauma { @@ -81,6 +82,8 @@ namespace Barotrauma public bool FlashOnAutoCloseCondition { get; set; } + public Action OnEnterPressed { get; set; } + public Type MessageBoxType => type; public static GUIComponent VisibleBox => MessageBoxes.LastOrDefault(); @@ -89,6 +92,10 @@ namespace Barotrauma : this(headerText, text, new LocalizedString[] { "OK" }, relativeSize, minSize, type: type) { this.Buttons[0].OnClicked = Close; + OnEnterPressed = () => + { + Buttons[0].OnClicked(Buttons[0], Buttons[0].UserData); + }; } public GUIMessageBox(RichString headerText, RichString text, LocalizedString[] buttons, @@ -516,6 +523,11 @@ namespace Barotrauma protected override void Update(float deltaTime) { + if (PlayerInput.KeyHit(Keys.Enter)) + { + OnEnterPressed?.Invoke(); + } + if (Draggable) { GUIComponent parent = GUI.MouseOn?.Parent?.Parent; diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUINumberInput.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUINumberInput.cs index 9edfae736..488f9a83d 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUINumberInput.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUINumberInput.cs @@ -18,6 +18,20 @@ namespace Barotrauma public GUIButton PlusButton { get; private set; } public GUIButton MinusButton { get; private set; } + private void UpdatePlusMinusButtonVisibility() + { + if (ForceShowPlusMinusButtons + || inputType == NumberType.Int + || (inputType == NumberType.Float && MinValueFloat > float.MinValue && MaxValueFloat < float.MaxValue)) + { + ShowPlusMinusButtons(); + } + else + { + HidePlusMinusButtons(); + } + } + private NumberType inputType; public NumberType InputType { @@ -26,15 +40,7 @@ namespace Barotrauma { if (inputType == value) { return; } inputType = value; - if (inputType == NumberType.Int || - (inputType == NumberType.Float && MinValueFloat > float.MinValue && MaxValueFloat < float.MaxValue)) - { - ShowPlusMinusButtons(); - } - else - { - HidePlusMinusButtons(); - } + UpdatePlusMinusButtonVisibility(); } } @@ -46,15 +52,7 @@ namespace Barotrauma { minValueFloat = value; ClampFloatValue(); - if (inputType == NumberType.Int || - (inputType == NumberType.Float && MinValueFloat > float.MinValue && MaxValueFloat < float.MaxValue)) - { - ShowPlusMinusButtons(); - } - else - { - HidePlusMinusButtons(); - } + UpdatePlusMinusButtonVisibility(); } } public float? MaxValueFloat @@ -64,15 +62,7 @@ namespace Barotrauma { maxValueFloat = value; ClampFloatValue(); - if (inputType == NumberType.Int || - (inputType == NumberType.Float && MinValueFloat > float.MinValue && MaxValueFloat < float.MaxValue)) - { - ShowPlusMinusButtons(); - } - else - { - HidePlusMinusButtons(); - } + UpdatePlusMinusButtonVisibility(); } } @@ -96,6 +86,19 @@ namespace Barotrauma } } + private bool forceShowPlusMinusButtons; + + public bool ForceShowPlusMinusButtons + { + get { return forceShowPlusMinusButtons; } + set + { + if (forceShowPlusMinusButtons == value) { return; } + forceShowPlusMinusButtons = value; + UpdatePlusMinusButtonVisibility(); + } + } + private int decimalsToDisplay = 1; public int DecimalsToDisplay { @@ -184,7 +187,7 @@ namespace Barotrauma /// public bool WrapAround; - public float valueStep; + public float ValueStep; private float pressedTimer; private readonly float pressedDelay = 0.5f; @@ -339,12 +342,12 @@ namespace Barotrauma { if (inputType == NumberType.Int) { - IntValue -= valueStep > 0 ? (int)valueStep : 1; + IntValue -= ValueStep > 0 ? (int)ValueStep : 1; ClampIntValue(); } else if (maxValueFloat.HasValue && minValueFloat.HasValue) { - FloatValue -= valueStep > 0 ? valueStep : Round(); + FloatValue -= ValueStep > 0 ? ValueStep : Round(); ClampFloatValue(); } } @@ -353,12 +356,12 @@ namespace Barotrauma { if (inputType == NumberType.Int) { - IntValue += valueStep > 0 ? (int)valueStep : 1; + IntValue += ValueStep > 0 ? (int)ValueStep : 1; ClampIntValue(); } else if (inputType == NumberType.Float) { - FloatValue += valueStep > 0 ? valueStep : Round(); + FloatValue += ValueStep > 0 ? ValueStep : Round(); ClampFloatValue(); } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/Store.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/Store.cs index 34b935f84..270c2e6ec 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/Store.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/Store.cs @@ -341,9 +341,9 @@ namespace Barotrauma }; var panelMaxWidth = (int)(GUI.xScale * (GUI.HorizontalAspectRatio < 1.4f ? 650 : 560)); - var storeContent = new GUILayoutGroup(new RectTransform(new Vector2(0.45f, 1.0f), campaignUI.GetTabContainer(CampaignMode.InteractionType.Store).RectTransform) + var storeContent = new GUILayoutGroup(new RectTransform(new Vector2(0.45f, 1.0f), campaignUI.GetTabContainer(CampaignMode.InteractionType.Store).RectTransform, Anchor.BottomLeft) { - MaxSize = new Point(panelMaxWidth, campaignUI.GetTabContainer(CampaignMode.InteractionType.Store).Rect.Height) + MaxSize = new Point(panelMaxWidth, campaignUI.GetTabContainer(CampaignMode.InteractionType.Store).Rect.Height - HUDLayoutSettings.ButtonAreaTop.Bottom) }) { Stretch = true, @@ -583,9 +583,9 @@ namespace Barotrauma // Shopping Crate ------------------------------------------------------------------------------------------------------------------------------------------ - var shoppingCrateContent = new GUILayoutGroup(new RectTransform(new Vector2(0.45f, 1.0f), campaignUI.GetTabContainer(CampaignMode.InteractionType.Store).RectTransform, anchor: Anchor.TopRight) + var shoppingCrateContent = new GUILayoutGroup(new RectTransform(new Vector2(0.45f, 1.0f), campaignUI.GetTabContainer(CampaignMode.InteractionType.Store).RectTransform, anchor: Anchor.BottomRight) { - MaxSize = new Point(panelMaxWidth, campaignUI.GetTabContainer(CampaignMode.InteractionType.Store).Rect.Height) + MaxSize = new Point(panelMaxWidth, campaignUI.GetTabContainer(CampaignMode.InteractionType.Store).Rect.Height - HUDLayoutSettings.ButtonAreaTop.Bottom) }) { Stretch = true, @@ -922,15 +922,12 @@ namespace Barotrauma { if (itemPrefab.CanBeBoughtFrom(ActiveStore, out PriceInfo priceInfo) && itemPrefab.CanCharacterBuy()) { - bool isDailySpecial = ActiveStore.DailySpecials.Contains(itemPrefab); var itemFrame = isDailySpecial ? storeDailySpecialsGroup.FindChild(c => c.UserData is PurchasedItem pi && pi.ItemPrefab == itemPrefab) : storeBuyList.Content.FindChild(c => c.UserData is PurchasedItem pi && pi.ItemPrefab == itemPrefab); - if (CargoManager.GetPurchasedItem(ActiveStore, itemPrefab) is { } purchasedItem) - { - quantity = Math.Max(quantity - purchasedItem.Quantity, 0); - } + + quantity = Math.Max(quantity - CargoManager.GetPurchasedItemCount(ActiveStore, itemPrefab), 0); if (CargoManager.GetBuyCrateItem(ActiveStore, itemPrefab) is { } buyCrateItem) { quantity = Math.Max(quantity - buyCrateItem.Quantity, 0); @@ -1245,9 +1242,9 @@ namespace Barotrauma int totalPrice = 0; if (ActiveStore != null) { - foreach (PurchasedItem item in items) + foreach (PurchasedItem item in items.ToList()) { - if (!(item.ItemPrefab.GetPriceInfo(ActiveStore) is { } priceInfo)) { continue; } + if (item.ItemPrefab.GetPriceInfo(ActiveStore) is not { } priceInfo) { continue; } GUINumberInput numInput = null; if (!(listBox.Content.FindChild(c => c.UserData is PurchasedItem pi && pi.ItemPrefab.Identifier == item.ItemPrefab.Identifier) is { } itemFrame)) { @@ -1749,7 +1746,7 @@ namespace Barotrauma } // Add items already purchased - CargoManager?.GetPurchasedItems(ActiveStore).ForEach(pi => AddNonEmptyOwnedItems(pi)); + CargoManager?.GetPurchasedItems(ActiveStore).Where(pi => !pi.DeliverImmediately).ForEach(pi => AddNonEmptyOwnedItems(pi)); ownedItemsUpdateTimer = 0.0f; @@ -1959,14 +1956,13 @@ namespace Barotrauma } catch (NotImplementedException e) { - DebugConsole.LogError($"Error getting item availability: Uknown store tab type. {e.StackTrace.CleanupStackTrace()}"); + DebugConsole.LogError($"Error getting item availability: Unknown store tab type. {e.StackTrace.CleanupStackTrace()}"); } if (list != null && list.Find(i => i.ItemPrefab == itemPrefab) is PurchasedItem item) { if (mode == StoreTab.Buy) { - var purchasedItem = CargoManager.GetPurchasedItem(ActiveStore, item.ItemPrefab); - if (purchasedItem != null) { return Math.Max(item.Quantity - purchasedItem.Quantity, 0); } + return Math.Max(item.Quantity - CargoManager.GetPurchasedItemCount(ActiveStore, item.ItemPrefab), 0); } return item.Quantity; } @@ -2093,13 +2089,49 @@ namespace Barotrauma } itemsToRemove.ForEach(i => itemsToPurchase.Remove(i)); if (itemsToPurchase.None() || Balance < totalPrice) { return false; } - CargoManager.PurchaseItems(ActiveStore.Identifier, itemsToPurchase, true); - GameMain.Client?.SendCampaignState(); - var dialog = new GUIMessageBox( - TextManager.Get("newsupplies"), - TextManager.GetWithVariable("suppliespurchasedmessage", "[location]", campaignUI?.Campaign?.Map?.CurrentLocation?.Name), - new LocalizedString[] { TextManager.Get("Ok") }); - dialog.Buttons[0].OnClicked += dialog.Close; + + if (CampaignMode.AllowImmediateItemDelivery()) + { + var deliveryPrompt = new GUIMessageBox( + TextManager.Get("newsupplies"), + TextManager.Get("suppliespurchased.deliverymethod"), + new LocalizedString[] + { + TextManager.Get("suppliespurchased.deliverymethod.deliverimmediately"), + TextManager.Get("suppliespurchased.deliverymethod.delivertosub") + }); + deliveryPrompt.Buttons[0].OnClicked = (btn, userdata) => + { + ConfirmPurchase(deliverImmediately: true); + deliveryPrompt.Close(); + return true; + }; + deliveryPrompt.Buttons[1].OnClicked = (btn, userdata) => + { + ConfirmPurchase(deliverImmediately: false); + deliveryPrompt.Close(); + return true; + }; + } + else + { + ConfirmPurchase(deliverImmediately: false); + } + + void ConfirmPurchase(bool deliverImmediately) + { + itemsToPurchase.ForEach(it => it.DeliverImmediately = deliverImmediately); + CargoManager.PurchaseItems(ActiveStore.Identifier, itemsToPurchase, removeFromCrate: true); + GameMain.Client?.SendCampaignState(); + if (!deliverImmediately) + { + var dialog = new GUIMessageBox( + TextManager.Get("newsupplies"), + TextManager.GetWithVariable("suppliespurchasedmessage", "[location]", campaignUI?.Campaign?.Map?.CurrentLocation?.Name)); + dialog.Buttons[0].OnClicked += dialog.Close; + } + } + return false; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/UISprite.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/UISprite.cs index 90c860332..1e8b12e76 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/UISprite.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/UISprite.cs @@ -163,7 +163,7 @@ namespace Barotrauma else if (Tile) { Vector2 startPos = new Vector2(rect.X, rect.Y); - Sprite.DrawTiled(spriteBatch, startPos, new Vector2(rect.Width, rect.Height), color, startOffset: uvOffset); + Sprite.DrawTiled(spriteBatch, startPos, new Vector2(rect.Width, rect.Height), color: color, startOffset: uvOffset); } else { diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/UpgradeStore.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/UpgradeStore.cs index cff4b0a2f..3a8e2cb68 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/UpgradeStore.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/UpgradeStore.cs @@ -1110,7 +1110,7 @@ namespace Barotrauma public static UpgradeFrame CreateUpgradeFrame(UpgradePrefab prefab, UpgradeCategory category, CampaignMode campaign, RectTransform rectTransform, bool addBuyButton = true) { - int price = prefab.Price.GetBuyPrice(campaign.UpgradeManager.GetUpgradeLevel(prefab, category), campaign.Map?.CurrentLocation, characterList); + int price = prefab.Price.GetBuyPrice(prefab, campaign.UpgradeManager.GetUpgradeLevel(prefab, category), campaign.Map?.CurrentLocation, characterList); return CreateUpgradeEntry(rectTransform, prefab.Sprite, prefab.Name, prefab.Description, price, new CategoryData(category, prefab), addBuyButton, upgradePrefab: prefab, currentLevel: campaign.UpgradeManager.GetUpgradeLevel(prefab, category)); } @@ -1267,7 +1267,7 @@ namespace Barotrauma { LocalizedString promptBody = TextManager.GetWithVariables("Upgrades.PurchasePromptBody", ("[upgradename]", prefab.Name), - ("[amount]", prefab.Price.GetBuyPrice(Campaign.UpgradeManager.GetUpgradeLevel(prefab, category), Campaign.Map?.CurrentLocation, characterList).ToString())); + ("[amount]", prefab.Price.GetBuyPrice(prefab, Campaign.UpgradeManager.GetUpgradeLevel(prefab, category), Campaign.Map?.CurrentLocation, characterList).ToString())); currectConfirmation = EventEditorScreen.AskForConfirmation(TextManager.Get("Upgrades.PurchasePromptTitle"), promptBody, () => { if (GameMain.NetworkMember != null) @@ -1682,7 +1682,7 @@ namespace Barotrauma GUITextBlock priceLabel = (GUITextBlock)buttonParent.FindChild(UpgradeStoreUserData.PriceLabel, recursive: true); priceLabel.Visible = true; - int price = prefab.Price.GetBuyPrice(campaign.UpgradeManager.GetUpgradeLevel(prefab, category), campaign.Map?.CurrentLocation, characterList); + int price = prefab.Price.GetBuyPrice(prefab, campaign.UpgradeManager.GetUpgradeLevel(prefab, category), campaign.Map?.CurrentLocation, characterList); if (!WaitForServerUpdate) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/CargoManager.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/CargoManager.cs index 4655e13d5..0d1df98e3 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/CargoManager.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/CargoManager.cs @@ -147,7 +147,7 @@ namespace Barotrauma } catch (NotImplementedException e) { - DebugConsole.LogError($"Error selling items: uknown store tab type \"{sellingMode}\".\n{e.StackTrace.CleanupStackTrace()}"); + DebugConsole.LogError($"Error selling items: unknown store tab type \"{sellingMode}\".\n{e.StackTrace.CleanupStackTrace()}"); return; } bool canAddToRemoveQueue = campaign.IsSinglePlayer && Entity.Spawner != null; diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/CampaignMode.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/CampaignMode.cs index 28556fd9d..4c143b2ee 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/CampaignMode.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/CampaignMode.cs @@ -121,6 +121,16 @@ namespace Barotrauma { return AllowedToManageCampaign(ClientPermissions.ManageMoney); } + + public static bool AllowImmediateItemDelivery() + { + if (GameMain.Client == null) { return true; } + return + GameMain.Client.ServerSettings.AllowImmediateItemDelivery || + GameMain.Client.HasPermission(ClientPermissions.ManageCampaign) || + GameMain.Client.IsServerOwner; + } + protected GUIButton CreateEndRoundButton() { int buttonWidth = (int)(450 * GUI.xScale * (GUI.IsUltrawide ? 3.0f : 1.0f)); diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/MultiPlayerCampaign.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/MultiPlayerCampaign.cs index a3be9801c..8c408cf95 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/MultiPlayerCampaign.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/MultiPlayerCampaign.cs @@ -535,7 +535,7 @@ namespace Barotrauma bool refreshCampaignUI = false; - if (!(GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign) || campaignID != campaign.CampaignID) + if (GameMain.GameSession?.GameMode is not MultiPlayerCampaign campaign || campaignID != campaign.CampaignID) { string savePath = SaveUtil.CreateSavePath(SaveUtil.SaveType.Multiplayer); diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/RoundSummary.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/RoundSummary.cs index 96fb2522a..33f02d0fe 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/RoundSummary.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/RoundSummary.cs @@ -98,7 +98,7 @@ namespace Barotrauma if (gameSession.Missions.Any(m => m is CombatMission)) { crewHeader.Text = CombatMission.GetTeamName(CharacterTeamType.Team1); - GUIFrame crewFrame2 = new GUIFrame(new RectTransform(new Vector2(0.35f, 0.45f), background.RectTransform, Anchor.TopCenter, minSize: new Point(minWidth, minHeight))); + GUIFrame crewFrame2 = new GUIFrame(new RectTransform(crewFrame.RectTransform.RelativeSize, background.RectTransform, Anchor.TopCenter, minSize: new Point(minWidth, minHeight))); rightPanels.Add(crewFrame2); GUIFrame crewFrameInner2 = new GUIFrame(new RectTransform(new Point(crewFrame2.Rect.Width - padding * 2, crewFrame2.Rect.Height - padding * 2), crewFrame2.RectTransform, Anchor.Center), style: "InnerFrame"); var crewContent2 = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.95f), crewFrameInner2.RectTransform, Anchor.Center)) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/GeneticMaterial.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/GeneticMaterial.cs index 590e908f9..7d5981608 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/GeneticMaterial.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/GeneticMaterial.cs @@ -54,7 +54,9 @@ namespace Barotrauma.Items.Components { if (deconstructor.InputContainer.Inventory.AllItems.Count() == 2) { - if (!deconstructor.InputContainer.Inventory.AllItems.All(it => it.Prefab == item.Prefab)) + var otherGeneticMaterial = + deconstructor.InputContainer.Inventory.AllItems.FirstOrDefault(it => it != item && it.Prefab == item.Prefab)?.GetComponent(); + if (otherGeneticMaterial == null) { buttonText = TextManager.Get("researchstation.combine"); infoText = TextManager.Get("researchstation.combine.infotext"); @@ -62,7 +64,7 @@ namespace Barotrauma.Items.Components else { buttonText = TextManager.Get("researchstation.refine"); - int taintedProbability = (int)(GetTaintedProbabilityOnRefine(Character.Controlled) * 100); + int taintedProbability = (int)(GetTaintedProbabilityOnRefine(otherGeneticMaterial, Character.Controlled) * 100); infoText = TextManager.GetWithVariable("researchstation.refine.infotext", "[taintedprobability]", taintedProbability.ToString()); } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemComponent.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemComponent.cs index 32dca442a..b90e5bac4 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemComponent.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemComponent.cs @@ -497,7 +497,8 @@ namespace Barotrauma.Items.Components case "guiframe": if (subElement.GetAttribute("rect") != null) { - DebugConsole.ThrowError($"Error in item config \"{item.ConfigFilePath}\" - GUIFrame defined as rect, use RectTransform instead."); + DebugConsole.ThrowError($"Error in item config \"{item.ConfigFilePath}\" - GUIFrame defined as rect, use RectTransform instead.", + contentPackage: subElement.ContentPackage); break; } GuiFrameSource = subElement; @@ -516,7 +517,8 @@ namespace Barotrauma.Items.Components if (filePath.IsNullOrEmpty()) { DebugConsole.ThrowError( - $"Error when instantiating item \"{item.Name}\" - sound with no file path set"); + $"Error when instantiating item \"{item.Name}\" - sound with no file path set", + contentPackage: subElement.ContentPackage); break; } @@ -528,7 +530,8 @@ namespace Barotrauma.Items.Components } catch (Exception e) { - DebugConsole.ThrowError($"Invalid sound type \"{typeStr}\" in item \"{item.Prefab.Identifier}\"!", e); + DebugConsole.ThrowError($"Invalid sound type \"{typeStr}\" in item \"{item.Prefab.Identifier}\"!", e, + contentPackage: subElement.ContentPackage); break; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemContainer.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemContainer.cs index 830703d01..35799ed55 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemContainer.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemContainer.cs @@ -158,7 +158,8 @@ namespace Barotrauma.Items.Components IndicatorStyle = GUIStyle.GetComponentStyle("ContainedStateIndicator." + ContainedStateIndicatorStyle); if (ContainedStateIndicator != null || ContainedStateIndicatorEmpty != null) { - DebugConsole.AddWarning($"Item \"{item.Name}\" defines both a contained state indicator style and a custom indicator sprite. Will use the custom sprite..."); + DebugConsole.AddWarning($"Item \"{item.Name}\" defines both a contained state indicator style and a custom indicator sprite. Will use the custom sprite...", + contentPackage: item.Prefab.ContentPackage); } } if (GuiFrame == null) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs index c86d674e5..f300ae562 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs @@ -393,6 +393,8 @@ namespace Barotrauma.Items.Components partial void SelectProjSpecific(Character character) { + if (character != Character.Controlled) { return; } + var nonItems = itemList.Content.Children.Where(c => c.UserData is not FabricationRecipe).ToList(); nonItems.ForEach(i => itemList.Content.RemoveChild(i)); @@ -784,6 +786,7 @@ namespace Barotrauma.Items.Components private void HideEmptyItemListCategories() { + bool visibleElementsChanged = false; //go through the elements backwards, and disable the labels ("insufficient skills to fabricate", "recipe required...") if there's no items below them bool recipeVisible = false; foreach (GUIComponent child in itemList.Content.Children.Reverse()) @@ -792,7 +795,11 @@ namespace Barotrauma.Items.Components { if (child.Enabled) { - child.Visible = recipeVisible; + if (child.Visible != recipeVisible) + { + child.Visible = recipeVisible; + visibleElementsChanged = true; + } } recipeVisible = false; } @@ -802,8 +809,11 @@ namespace Barotrauma.Items.Components } } - itemList.UpdateScrollBarSize(); - itemList.BarScroll = 0.0f; + if (visibleElementsChanged) + { + itemList.UpdateScrollBarSize(); + itemList.BarScroll = 0.0f; + } } public bool ClearFilter() diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs index e06de7a92..a5d5766f1 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs @@ -66,7 +66,15 @@ namespace Barotrauma.Items.Components private float prevPassivePingRadius; private Vector2 center; - private float displayScale; + + /// + /// Current scale of the display, taking zoom into account. In other words, the scaling factor of world coordinates to coordinates on the display. + /// + public float DisplayScale + { + get; + private set; + } = 1.0f; private const float DisruptionUpdateInterval = 0.2f; private float disruptionUpdateTimer; @@ -751,9 +759,9 @@ namespace Barotrauma.Items.Components { var activePing = activePings[pingIndex]; float pingRadius = DisplayRadius * activePing.State / zoom; - if (disruptionUpdateTimer <= 0.0f) { UpdateDisruptions(transducerCenter, pingRadius / displayScale); } + if (disruptionUpdateTimer <= 0.0f) { UpdateDisruptions(transducerCenter, pingRadius / DisplayScale); } Ping(transducerCenter, transducerCenter, - pingRadius, activePing.PrevPingRadius, displayScale, range / zoom, passive: false, pingStrength: 2.0f); + pingRadius, activePing.PrevPingRadius, DisplayScale, range / zoom, passive: false, pingStrength: 2.0f); activePing.PrevPingRadius = pingRadius; } if (disruptionUpdateTimer <= 0.0f) @@ -770,7 +778,7 @@ namespace Barotrauma.Items.Components if (c.Params.HideInSonar) { continue; } if (!c.IsUnconscious && c.Params.DistantSonarRange > 0.0f && - ((c.WorldPosition - transducerCenter) * displayScale).LengthSquared() > DisplayRadius * DisplayRadius) + ((c.WorldPosition - transducerCenter) * DisplayScale).LengthSquared() > DisplayRadius * DisplayRadius) { Vector2 targetVector = c.WorldPosition - transducerCenter; if (targetVector.LengthSquared() > MathUtils.Pow2(c.Params.DistantSonarRange)) { continue; } @@ -818,7 +826,7 @@ namespace Barotrauma.Items.Components if (dist > prevPassivePingRadius * Range && dist <= passivePingRadius * Range && Rand.Int(sonarBlips.Count) < 500) { Ping(t.WorldPosition, transducerCenter, - t.SoundRange * displayScale, 0, displayScale, range, + t.SoundRange * DisplayScale, 0, DisplayScale, range, passive: true, pingStrength: 0.5f, needsToBeInSector: t); if (t.IsWithinSector(transducerCenter)) { @@ -857,7 +865,7 @@ namespace Barotrauma.Items.Components displayBorderSize = 0.2f; center = rect.Center.ToVector2(); DisplayRadius = (rect.Width / 2.0f) * (1.0f - displayBorderSize); - displayScale = DisplayRadius / range * zoom; + DisplayScale = DisplayRadius / range * zoom; screenBackground?.Draw(spriteBatch, center, 0.0f, rect.Width / screenBackground.size.X); @@ -972,7 +980,7 @@ namespace Barotrauma.Items.Components aiTarget.SonarIconIdentifier, aiTarget, aiTarget.WorldPosition, transducerCenter, - displayScale, center, DisplayRadius * 0.975f); + DisplayScale, center, DisplayRadius * 0.975f); } } @@ -987,7 +995,7 @@ namespace Barotrauma.Items.Components (Level.Loaded.StartOutpost != null ? "outpost" : "location").ToIdentifier(), "startlocation", Level.Loaded.StartExitPosition, transducerCenter, - displayScale, center, DisplayRadius); + DisplayScale, center, DisplayRadius); } if (Level.Loaded is { EndLocation.Type.ShowSonarMarker: true, Type: LevelData.LevelType.LocationConnection }) @@ -997,7 +1005,7 @@ namespace Barotrauma.Items.Components (Level.Loaded.EndOutpost != null ? "outpost" : "location").ToIdentifier(), "endlocation", Level.Loaded.EndExitPosition, transducerCenter, - displayScale, center, DisplayRadius); + DisplayScale, center, DisplayRadius); } for (int i = 0; i < Level.Loaded.Caves.Count; i++) @@ -1009,7 +1017,7 @@ namespace Barotrauma.Items.Components "cave".ToIdentifier(), "cave" + i, cave.StartPos.ToVector2(), transducerCenter, - displayScale, center, DisplayRadius); + DisplayScale, center, DisplayRadius); } } @@ -1026,7 +1034,7 @@ namespace Barotrauma.Items.Components mission.SonarIconIdentifier, "mission" + missionIndex + ":" + i, position, transducerCenter, - displayScale, center, DisplayRadius * 0.95f); + DisplayScale, center, DisplayRadius * 0.95f); } i++; } @@ -1059,7 +1067,7 @@ namespace Barotrauma.Items.Components DrawMarker(spriteBatch, i.Name, "mineral".ToIdentifier(), "mineralcluster" + i, c.center, transducerCenter, - displayScale, center, DisplayRadius * 0.95f, + DisplayScale, center, DisplayRadius * 0.95f, onlyShowTextOnMouseOver: true); } } @@ -1088,19 +1096,19 @@ namespace Barotrauma.Items.Components (sub.Info.HasTag(SubmarineTag.Shuttle) ? "shuttle" : "submarine").ToIdentifier(), sub, sub.WorldPosition, transducerCenter, - displayScale, center, DisplayRadius * 0.95f); + DisplayScale, center, DisplayRadius * 0.95f); } if (GameMain.DebugDraw) { var steering = item.GetComponent(); - steering?.DebugDrawHUD(spriteBatch, transducerCenter, displayScale, DisplayRadius, center); + steering?.DebugDrawHUD(spriteBatch, transducerCenter, DisplayScale, DisplayRadius, center); } } private void DrawOwnSubmarineBorders(SpriteBatch spriteBatch, Vector2 transducerCenter, float signalStrength) { - float simScale = displayScale * Physics.DisplayToSimRation * zoom; + float simScale = DisplayScale * Physics.DisplayToSimRation; foreach (Submarine submarine in Submarine.Loaded) { @@ -1167,7 +1175,7 @@ namespace Barotrauma.Items.Components private void DrawDockingPorts(SpriteBatch spriteBatch, Vector2 transducerCenter, float signalStrength) { - float scale = displayScale * zoom; + float scale = DisplayScale; Steering steering = item.GetComponent(); if (steering != null && steering.DockingModeEnabled && steering.ActiveDockingSource != null) @@ -1219,7 +1227,7 @@ namespace Barotrauma.Items.Components private void DrawDockingIndicator(SpriteBatch spriteBatch, Steering steering, ref Vector2 transducerCenter) { - float scale = displayScale * zoom; + float scale = DisplayScale; Vector2 worldFocusPos = (steering.ActiveDockingSource.Item.WorldPosition + steering.DockingTarget.Item.WorldPosition) / 2.0f; worldFocusPos.X = steering.DockingTarget.Item.WorldPosition.X; @@ -1591,7 +1599,7 @@ namespace Barotrauma.Items.Components { lineStep /= zoom; zStep /= zoom; - range *= displayScale; + range *= DisplayScale; float length = (point1 - point2).Length(); Vector2 lineDir = (point2 - point1) / length; for (float x = 0; x < length; x += lineStep * Rand.Range(0.8f, 1.2f)) @@ -1602,12 +1610,12 @@ namespace Barotrauma.Items.Components //ignore if outside the display Vector2 transducerDiff = point - transducerPos; - Vector2 transducerDisplayDiff = transducerDiff * displayScale; + Vector2 transducerDisplayDiff = transducerDiff * DisplayScale / zoom; if (transducerDisplayDiff.LengthSquared() > DisplayRadius * DisplayRadius) { continue; } //ignore if the point is not within the ping Vector2 pointDiff = point - pingSource; - Vector2 displayPointDiff = pointDiff * displayScale; + Vector2 displayPointDiff = pointDiff * DisplayScale / zoom; float displayPointDistSqr = displayPointDiff.LengthSquared(); if (displayPointDistSqr < prevPingRadius * prevPingRadius || displayPointDistSqr > pingRadius * pingRadius) { continue; } @@ -1628,9 +1636,9 @@ namespace Barotrauma.Items.Components float displayPointDist = (float)Math.Sqrt(displayPointDistSqr); float alpha = pingStrength * Rand.Range(1.5f, 2.0f); - for (float z = 0; z < DisplayRadius - transducerDist * displayScale; z += zStep) + for (float z = 0; z < DisplayRadius - transducerDist * DisplayScale; z += zStep) { - Vector2 pos = point + Rand.Vector(150.0f / zoom) + pingDirection * z / displayScale; + Vector2 pos = point + Rand.Vector(150.0f / zoom) + pingDirection * z / DisplayScale; float fadeTimer = alpha * (1.0f - displayPointDist / range); if (needsToBeInSector != null) @@ -1697,7 +1705,7 @@ namespace Barotrauma.Items.Components private bool CheckBlipVisibility(SonarBlip blip, Vector2 transducerPos) { - Vector2 pos = (blip.Position - transducerPos) * displayScale * zoom; + Vector2 pos = (blip.Position - transducerPos) * DisplayScale; pos.Y = -pos.Y; float posDistSqr = pos.LengthSquared(); @@ -1731,7 +1739,7 @@ namespace Barotrauma.Items.Components } if (currentPingIndex != -1 && activePings[currentPingIndex].IsDirectional) { - var pos = (resourcePos - transducerPos) * displayScale * zoom; + var pos = (resourcePos - transducerPos) * DisplayScale; pos.Y = -pos.Y; var length = pos.Length(); var dir = pos / length; @@ -1749,7 +1757,7 @@ namespace Barotrauma.Items.Components float distort = 1.0f - item.Condition / item.MaxCondition; - Vector2 pos = (blip.Position - transducerPos) * displayScale * zoom; + Vector2 pos = (blip.Position - transducerPos) * DisplayScale; pos.Y = -pos.Y; if (Rand.Range(0.5f, 2.0f) < distort) { pos.X = -pos.X; } @@ -1825,14 +1833,13 @@ namespace Barotrauma.Items.Components Vector2 position = worldPosition - transducerPosition; - position *= zoom; position *= scale; position.Y = -position.Y; float textAlpha = MathHelper.Clamp(1.5f - dist / 50000.0f, 0.5f, 1.0f); Vector2 dir = Vector2.Normalize(position); - Vector2 markerPos = (linearDist * zoom * scale > radius) ? dir * radius : position; + Vector2 markerPos = (linearDist * scale > radius) ? dir * radius : position; markerPos += center; markerPos.X = (int)markerPos.X; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Steering.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Steering.cs index fd5be2193..c3827e5b5 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Steering.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Steering.cs @@ -589,7 +589,8 @@ namespace Barotrauma.Items.Components Sonar sonar = item.GetComponent(); if (sonar != null && controlledSub != null) { - Vector2 displayPosToMaintain = ((posToMaintain.Value - controlledSub.WorldPosition)) / sonar.Range * sonar.DisplayRadius * sonar.Zoom; + Vector2 displayPosToMaintain = ((posToMaintain.Value - controlledSub.WorldPosition)) * sonar.DisplayScale; + displayPosToMaintain.Y = -displayPosToMaintain.Y; displayPosToMaintain = displayPosToMaintain.ClampLength(velRect.Width / 2); displayPosToMaintain = steerArea.Rect.Center.ToVector2() + displayPosToMaintain; @@ -670,14 +671,14 @@ namespace Barotrauma.Items.Components pos2.Y = -pos2.Y; pos2 += center; - GUI.DrawLine(spriteBatch, - pos1, + GUI.DrawLine(spriteBatch, + pos1, pos2, GUIStyle.Red * 0.6f, width: 3); if (obstacle.Intersection.HasValue) { - Vector2 intersectionPos = (obstacle.Intersection.Value - transducerCenter) *displayScale; + Vector2 intersectionPos = (obstacle.Intersection.Value - transducerCenter) * displayScale; intersectionPos.Y = -intersectionPos.Y; intersectionPos += center; GUI.DrawRectangle(spriteBatch, intersectionPos - Vector2.One * 2, Vector2.One * 4, GUIStyle.Red); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/RepairTool.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/RepairTool.cs index 8f58322ae..1b76a65ef 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/RepairTool.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/RepairTool.cs @@ -151,7 +151,7 @@ namespace Barotrauma.Items.Components GUI.DrawLine(spriteBatch, new Vector2(debugRayStartPos.X, -debugRayStartPos.Y), new Vector2(debugRayEndPos.X, -debugRayEndPos.Y), - Color.Yellow); + Color.Yellow, width: 3f); } } #endif diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/CircuitBox.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/CircuitBox.cs index 7dafcf617..3f466be5c 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/CircuitBox.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/CircuitBox.cs @@ -269,7 +269,7 @@ namespace Barotrauma.Items.Components resource = ItemPrefab.Prefabs[Tags.FPGACircuit]; } - AddComponentInternal(ICircuitBoxIdentifiable.FindFreeID(Components), prefab, resource, pos, static delegate { }); + AddComponentInternal(ICircuitBoxIdentifiable.FindFreeID(Components), prefab, resource, pos, Character.Controlled, onItemSpawned: null); return; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/CustomInterface.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/CustomInterface.cs index c821da993..dc5b84592 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/CustomInterface.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/CustomInterface.cs @@ -95,7 +95,7 @@ namespace Barotrauma.Items.Components MaxValueFloat = numberInputMax, FloatValue = Math.Clamp(floatSignal, numberInputMin, numberInputMax), DecimalsToDisplay = ciElement.NumberInputDecimalPlaces, - valueStep = numberInputStep, + ValueStep = numberInputStep, OnValueChanged = (ni) => { if (GameMain.Client == null) @@ -121,7 +121,7 @@ namespace Barotrauma.Items.Components MinValueInt = numberInputMin, MaxValueInt = numberInputMax, IntValue = Math.Clamp(intSignal, numberInputMin, numberInputMax), - valueStep = numberInputStep, + ValueStep = numberInputStep, OnValueChanged = (ni) => { if (GameMain.Client == null) @@ -137,7 +137,8 @@ namespace Barotrauma.Items.Components } else { - DebugConsole.LogError($"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")}\"", + contentPackage: item.Prefab.ContentPackage); } if (numberInput != null) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/StatusHUD.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/StatusHUD.cs index 698db9c7c..a2f10cce8 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/StatusHUD.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/StatusHUD.cs @@ -349,8 +349,8 @@ namespace Barotrauma.Items.Components } GUI.DrawString(spriteBatch, hudPos, texts[0].Value, textColors[0] * alpha, Color.Black * 0.7f * alpha, 2, GUIStyle.SubHeadingFont, ForceUpperCase.No); - hudPos.X += 5.0f; - hudPos.Y += 24.0f * GameSettings.CurrentConfig.Graphics.TextScale; + hudPos.X += 5.0f * GUI.Scale; + hudPos.Y += GUIStyle.SubHeadingFont.MeasureString(texts[0].Value).Y; hudPos.X = (int)hudPos.X; hudPos.Y = (int)hudPos.Y; @@ -358,7 +358,7 @@ namespace Barotrauma.Items.Components for (int i = 1; i < texts.Count; i++) { GUI.DrawString(spriteBatch, hudPos, texts[i], textColors[i] * alpha, Color.Black * 0.7f * alpha, 2, GUIStyle.SmallFont); - hudPos.Y += (int)(18.0f * GameSettings.CurrentConfig.Graphics.TextScale); + hudPos.Y += (int)(GUIStyle.SubHeadingFont.MeasureString(texts[i].Value).Y); } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs index ac8f2e935..5163a7196 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs @@ -258,7 +258,7 @@ namespace Barotrauma else { LocalizedString description = item.Description; - if (item.HasTag(Tags.IdCard) || item.HasTag(Tags.DespawnContainer)) + if (item.HasTag(Tags.IdCardTag) || item.HasTag(Tags.DespawnContainer)) { string[] readTags = item.Tags.Split(','); string idName = null; @@ -1505,14 +1505,28 @@ namespace Barotrauma int stackAmount = DraggingItems.Count; if (selectedSlot?.ParentInventory != null) { - stackAmount = Math.Min( - stackAmount, - selectedSlot.ParentInventory.HowManyCanBePut(draggedItem.Prefab, selectedSlot.SlotIndex, draggedItem.Condition)); + if (selectedSlot.Item?.OwnInventory != null) + { + int maxAmountPerSlot = 0; + for (int i = 0; i < SelectedSlot.Item.OwnInventory.Capacity; i++) + { + maxAmountPerSlot = Math.Max( + maxAmountPerSlot, + selectedSlot.Item.OwnInventory.HowManyCanBePut(draggedItem.Prefab, i, draggedItem.Condition, ignoreItemsInSlot: true)); + } + stackAmount = Math.Min(stackAmount, maxAmountPerSlot); + } + else + { + stackAmount = Math.Min( + stackAmount, + selectedSlot.ParentInventory.HowManyCanBePut(draggedItem.Prefab, selectedSlot.SlotIndex, draggedItem.Condition, ignoreItemsInSlot: true)); + } } Vector2 stackCountPos = itemPos + Vector2.One * iconSize * 0.25f; string stackCountText = "x" + stackAmount; GUIStyle.SmallFont.DrawString(spriteBatch, stackCountText, stackCountPos + Vector2.One, Color.Black); - GUIStyle.SmallFont.DrawString(spriteBatch, stackCountText, stackCountPos, GUIStyle.TextColorBright); + GUIStyle.SmallFont.DrawString(spriteBatch, stackCountText, stackCountPos, GUIStyle.TextColorBright); } } } @@ -1908,6 +1922,15 @@ namespace Barotrauma foreach (UInt16 id in receivedItemIDs[i]) { if (Entity.FindEntityByID(id) is not Item item || slots[i].Contains(item)) { continue; } + + if (Owner is Item thisItem && thisItem.Container == item) + { + //if this item is inside the item we're trying to contain inside it, we need to drop it (both items can't be inside each other!) + //can happen when a player swaps the items to be "the other way around", and we receive a message about the contained item + //before the message about the "parent item" being placed in some other inventory (like the player's inventory) + thisItem.Drop(null); + } + if (!TryPutItem(item, i, false, false, null, false)) { ForceToSlot(item, i); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs index 0f42a313c..e2cb908f9 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs @@ -132,9 +132,9 @@ namespace Barotrauma return GetDrawDepth(SpriteDepth + DrawDepthOffset, Sprite); } - public Color GetSpriteColor(bool withHighlight = false) + public Color GetSpriteColor(Color? defaultColor = null, bool withHighlight = false) { - Color color = spriteColor; + Color color = defaultColor ?? spriteColor; if (Prefab.UseContainedSpriteColor && ownInventory != null) { foreach (Item item in ContainedItems) @@ -333,9 +333,7 @@ namespace Barotrauma else if (!ShowItems) { return; } } - Color color = - overrideColor ?? - (IsIncludedInSelection && editing ? GUIStyle.Blue : GetSpriteColor(withHighlight: true)); + Color color = GetSpriteColor(spriteColor); bool isWiringMode = editing && SubEditorScreen.TransparentWiringMode && SubEditorScreen.IsWiringMode() && !isWire && parentInventory == null; bool renderTransparent = isWiringMode && GetComponent() == null; @@ -406,12 +404,14 @@ namespace Barotrauma foreach (var decorativeSprite in Prefab.DecorativeSprites) { if (!spriteAnimState[decorativeSprite].IsActive) { continue; } + + Color decorativeSpriteColor = GetSpriteColor(decorativeSprite.Color); Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState, spriteAnimState[decorativeSprite].RandomOffsetMultiplier, flippedX && Prefab.CanSpriteFlipX ? RotationRad : -RotationRad) * Scale; if (flippedX && Prefab.CanSpriteFlipX) { offset.X = -offset.X; } if (flippedY && Prefab.CanSpriteFlipY) { offset.Y = -offset.Y; } decorativeSprite.Sprite.DrawTiled(spriteBatch, new Vector2(DrawPosition.X + offset.X - rect.Width / 2, -(DrawPosition.Y + offset.Y + rect.Height / 2)), - size, color: color, + size, color: decorativeSpriteColor, textureScale: Vector2.One * Scale, depth: Math.Min(depth + (decorativeSprite.Sprite.Depth - activeSprite.Depth), 0.999f)); } @@ -437,13 +437,15 @@ namespace Barotrauma foreach (var decorativeSprite in Prefab.DecorativeSprites) { if (!spriteAnimState[decorativeSprite].IsActive) { continue; } + + Color decorativeSpriteColor = GetSpriteColor(decorativeSprite.Color); float rot = decorativeSprite.GetRotation(ref spriteAnimState[decorativeSprite].RotationState, spriteAnimState[decorativeSprite].RandomRotationFactor); bool flipX = flippedX && Prefab.CanSpriteFlipX; bool flipY = flippedY && Prefab.CanSpriteFlipY; Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState, spriteAnimState[decorativeSprite].RandomOffsetMultiplier, flipX ^ flipY ? RotationRad : -RotationRad) * Scale; if (flipX) { offset.X = -offset.X; } if (flipY) { offset.Y = -offset.Y; } - decorativeSprite.Sprite.Draw(spriteBatch, new Vector2(DrawPosition.X + offset.X, -(DrawPosition.Y + offset.Y)), color, + decorativeSprite.Sprite.Draw(spriteBatch, new Vector2(DrawPosition.X + offset.X, -(DrawPosition.Y + offset.Y)), decorativeSpriteColor, RotationRad + rot, decorativeSprite.GetScale(spriteAnimState[decorativeSprite].RandomScaleFactor) * Scale, activeSprite.effects, depth: Math.Min(depth + (decorativeSprite.Sprite.Depth - activeSprite.Depth), 0.999f)); } @@ -618,6 +620,13 @@ namespace Barotrauma } return origin; } + + Color GetSpriteColor(Color defaultColor) + { + return + overrideColor ?? + (IsIncludedInSelection && editing ? GUIStyle.Blue : this.GetSpriteColor(defaultColor: defaultColor, withHighlight: true)); + } } partial void OnCollisionProjSpecific(float impact) @@ -852,7 +861,7 @@ namespace Barotrauma CanBeFocused = true }; - new GUIButton(new RectTransform(new Vector2(0.23f, 1.0f), buttonContainer.RectTransform), TextManager.Get("MirrorEntityX"), style: "GUIButtonSmall") + var mirrorX = new GUIButton(new RectTransform(new Vector2(0.23f, 1.0f), buttonContainer.RectTransform), TextManager.Get("MirrorEntityX"), style: "GUIButtonSmall") { ToolTip = TextManager.Get("MirrorEntityXToolTip"), Enabled = Prefab.CanFlipX, @@ -863,10 +872,12 @@ namespace Barotrauma me.FlipX(relativeToSub: false); } if (!SelectedList.Contains(this)) { FlipX(relativeToSub: false); } + ColorFlipButton(button, FlippedX); return true; } }; - new GUIButton(new RectTransform(new Vector2(0.23f, 1.0f), buttonContainer.RectTransform), TextManager.Get("MirrorEntityY"), style: "GUIButtonSmall") + ColorFlipButton(mirrorX, FlippedX); + var mirrorY = new GUIButton(new RectTransform(new Vector2(0.23f, 1.0f), buttonContainer.RectTransform), TextManager.Get("MirrorEntityY"), style: "GUIButtonSmall") { ToolTip = TextManager.Get("MirrorEntityYToolTip"), Enabled = Prefab.CanFlipY, @@ -877,9 +888,11 @@ namespace Barotrauma me.FlipY(relativeToSub: false); } if (!SelectedList.Contains(this)) { FlipY(relativeToSub: false); } + ColorFlipButton(button, FlippedY); return true; } }; + ColorFlipButton(mirrorY, FlippedY); if (Sprite != null) { var reloadTextureButton = new GUIButton(new RectTransform(new Vector2(0.23f, 1.0f), buttonContainer.RectTransform), TextManager.Get("ReloadSprite"), style: "GUIButtonSmall"); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/ItemPrefab.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/ItemPrefab.cs index 49c3eb0e3..995e56880 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/ItemPrefab.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/ItemPrefab.cs @@ -321,10 +321,8 @@ namespace Barotrauma } else { - if (ResizeHorizontal) - placeSize.X = Math.Max(position.X - placePosition.X, Size.X); - if (ResizeVertical) - placeSize.Y = Math.Max(placePosition.Y - position.Y, Size.Y); + if (ResizeHorizontal) { placeSize.X = Math.Max(position.X - placePosition.X, Size.X); } + if (ResizeVertical) { placeSize.Y = Math.Max(placePosition.Y - position.Y, Size.Y); } if (PlayerInput.PrimaryMouseButtonReleased()) { @@ -369,15 +367,23 @@ namespace Barotrauma } } - public override void DrawPlacing(SpriteBatch spriteBatch, Rectangle placeRect, float scale = 1.0f, SpriteEffects spriteEffects = SpriteEffects.None) + public override void DrawPlacing(SpriteBatch spriteBatch, Rectangle placeRect, float scale = 1.0f, float rotation = 0.0f, SpriteEffects spriteEffects = SpriteEffects.None) { if (!ResizeHorizontal && !ResizeVertical) { - Sprite.Draw(spriteBatch, new Vector2(placeRect.Center.X, -(placeRect.Y - placeRect.Height / 2)), SpriteColor * 0.8f, scale: scale); + sprite.Draw(spriteBatch, new Vector2(placeRect.Center.X, -(placeRect.Y - placeRect.Height / 2)), SpriteColor * 0.8f, scale: scale, rotate: rotation); } else { - Sprite.DrawTiled(spriteBatch, new Vector2(placeRect.X, -placeRect.Y), placeRect.Size.ToVector2(), SpriteColor * 0.8f); + Vector2 position = placeRect.Location.ToVector2(); + Vector2 placeSize = placeRect.Size.ToVector2(); + sprite?.DrawTiled( + spriteBatch, + new Vector2(position.X, -position.Y), + placeSize, + rotation: rotation, + textureScale: Vector2.One * scale, + color: SpriteColor * 0.8f); } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Hull.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Hull.cs index 5eb9bcd95..b4786982f 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Hull.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Hull.cs @@ -277,12 +277,21 @@ namespace Barotrauma Rectangle drawRect = Submarine == null ? rect : new Rectangle((int)(Submarine.DrawPosition.X + rect.X), (int)(Submarine.DrawPosition.Y + rect.Y), rect.Width, rect.Height); - if ((IsSelected || IsHighlighted) && editing) + if (editing) { + if (IsSelected || IsHighlighted) + { + GUI.DrawRectangle(spriteBatch, + new Vector2(drawRect.X, -drawRect.Y), + new Vector2(rect.Width, rect.Height), + (IsHighlighted ? Color.LightBlue * 0.8f : GUIStyle.Red * 0.5f) * alpha, false, 0, (int)Math.Max(5.0f / Screen.Selected.Cam.Zoom, 1.0f)); + } + + float waterHeight = WaterVolume / rect.Width; GUI.DrawRectangle(spriteBatch, - new Vector2(drawRect.X, -drawRect.Y), - new Vector2(rect.Width, rect.Height), - (IsHighlighted ? Color.LightBlue * 0.8f : GUIStyle.Red * 0.5f) * alpha, false, 0, (int)Math.Max(5.0f / Screen.Selected.Cam.Zoom, 1.0f)); + new Vector2(drawRect.X, -drawRect.Y + drawRect.Height - waterHeight), + new Vector2(drawRect.Width, waterHeight), + Color.Blue * 0.25f, isFilled: true); } GUI.DrawRectangle(spriteBatch, @@ -300,13 +309,27 @@ namespace Barotrauma " - Oxygen: " + ((int)OxygenPercentage), new Vector2(drawRect.X + 5, -drawRect.Y + 5), Color.White); GUIStyle.SmallFont.DrawString(spriteBatch, waterVolume + " / " + Volume, new Vector2(drawRect.X + 5, -drawRect.Y + 20), Color.White); - GUI.DrawRectangle(spriteBatch, new Rectangle(drawRect.Center.X, -drawRect.Y + drawRect.Height / 2, 10, (int)(100 * Math.Min(waterVolume / Volume, 1.0f))), Color.Cyan, true); - if (WaterVolume > Volume) + if (WaterVolume > 0) { - float maxExcessWater = Volume * MaxCompress; - GUI.DrawRectangle(spriteBatch, new Rectangle(drawRect.Center.X, -drawRect.Y + drawRect.Height / 2, 10, (int)(100 * (waterVolume - Volume) / maxExcessWater)), GUIStyle.Red, true); + drawProgressBar(50, new Point(0, 0), Math.Min(waterVolume / Volume, 1.0f), Color.Cyan); + if (WaterVolume > Volume) + { + float maxExcessWater = Volume * MaxCompress; + drawProgressBar(50, new Point(0, 0), (waterVolume - Volume) / maxExcessWater, GUIStyle.Red); + } + } + if (lethalPressure > 0) + { + drawProgressBar(50, new Point(20, 0), lethalPressure / 100.0f, Color.Red); + } + + void drawProgressBar(int height, Point offset, float fillAmount, Color color) + { + GUI.DrawRectangle(spriteBatch, new Rectangle(drawRect.Center.X - 2 + offset.X, -drawRect.Y - 2 + drawRect.Height / 2 + offset.Y, 14, height+4), Color.Black * 0.8f, depth: 0.01f, isFilled: true); + + int barHeight = (int)(fillAmount * height); + GUI.DrawRectangle(spriteBatch, new Rectangle(drawRect.Center.X + offset.X, -drawRect.Y + drawRect.Height / 2 + height - barHeight + offset.Y, 10, barHeight), color, isFilled: true); } - GUI.DrawRectangle(spriteBatch, new Rectangle(drawRect.Center.X, -drawRect.Y + drawRect.Height / 2, 10, 100), Color.Black); foreach (FireSource fs in FireSources) { @@ -732,7 +755,7 @@ namespace Barotrauma var newFire = i < FireSources.Count ? FireSources[i] : - new FireSource(Submarine == null ? pos : pos + Submarine.Position, null, true); + new FireSource(Submarine == null ? pos : pos + Submarine.Position, sourceCharacter: null, isNetworkMessage: true); newFire.Position = pos; newFire.Size = new Vector2(size, newFire.Size.Y); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/BackgroundCreatures/BackgroundCreatureManager.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/BackgroundCreatures/BackgroundCreatureManager.cs index 7dc0b6ad4..1bdf40355 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/BackgroundCreatures/BackgroundCreatureManager.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/BackgroundCreatures/BackgroundCreatureManager.cs @@ -43,7 +43,7 @@ namespace Barotrauma { mainElement = mainElement.FirstElement(); prefabs.Clear(); - DebugConsole.NewMessage($"Overriding all background creatures with '{configPath}'", Color.Yellow); + DebugConsole.NewMessage($"Overriding all background creatures with '{configPath}'", Color.MediumPurple); } else if (prefabs.Any()) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/Level.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/Level.cs index 38299827b..5c54904ba 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/Level.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/Level.cs @@ -53,7 +53,7 @@ namespace Barotrauma foreach (InterestingPosition pos in PositionsOfInterest) { Color color = Color.Yellow; - if (pos.PositionType == PositionType.Cave) + if (pos.PositionType == PositionType.Cave || pos.PositionType == PositionType.AbyssCave) { color = Color.DarkOrange; } @@ -61,6 +61,10 @@ namespace Barotrauma { color = Color.LightGray; } + if (!pos.IsValid) + { + color = Color.Red; + } GUI.DrawRectangle(spriteBatch, new Vector2(pos.Position.X - 15.0f, -pos.Position.Y - 15.0f), new Vector2(30.0f, 30.0f), color, true); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightSource.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightSource.cs index 7606b40c6..777b0ebd9 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightSource.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightSource.cs @@ -1318,7 +1318,7 @@ namespace Barotrauma.Lights if (LightTextureTargetSize != Vector2.Zero) { - LightSprite.DrawTiled(spriteBatch, drawPos, LightTextureTargetSize, color, startOffset: LightTextureOffset, textureScale: LightTextureScale); + LightSprite.DrawTiled(spriteBatch, drawPos, LightTextureTargetSize, color: color, startOffset: LightTextureOffset, textureScale: LightTextureScale); } else { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Map/Radiation.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Map/Radiation.cs index 2ed19962f..5f8fdb8ef 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Map/Radiation.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Map/Radiation.cs @@ -29,7 +29,7 @@ namespace Barotrauma Vector2 spriteScale = new Vector2(zoom); - uiSprite.Sprite.DrawTiled(spriteBatch, topLeft, size, Params.RadiationAreaColor, Vector2.Zero, textureScale: spriteScale); + uiSprite.Sprite.DrawTiled(spriteBatch, topLeft, size, color: Params.RadiationAreaColor, startOffset: Vector2.Zero, textureScale: spriteScale); Vector2 topRight = topLeft + Vector2.UnitX * size.X; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/MapEntity.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/MapEntity.cs index 7fefcda46..a9ab8cc23 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/MapEntity.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/MapEntity.cs @@ -8,6 +8,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using Barotrauma.Lights; +using Microsoft.Xna.Framework; namespace Barotrauma { @@ -65,9 +66,7 @@ namespace Barotrauma disableSelect = value; if (disableSelect) { - startMovingPos = Vector2.Zero; - selectionSize = Vector2.Zero; - selectionPos = Vector2.Zero; + StopSelection(); } } } @@ -494,6 +493,13 @@ namespace Barotrauma } } + public static void StopSelection() + { + startMovingPos = Vector2.Zero; + selectionSize = Vector2.Zero; + selectionPos = Vector2.Zero; + } + public static Vector2 GetNudgeAmount(bool doHold = true) { Vector2 nudgeAmount = Vector2.Zero; @@ -792,12 +798,16 @@ namespace Barotrauma foreach (MapEntity e in SelectedList) { SpriteEffects spriteEffects = SpriteEffects.None; + float spriteRotation = 0.0f; + float rectangleRotation = 0.0f; switch (e) { case Item item: { if (item.FlippedX && item.Prefab.CanSpriteFlipX) { spriteEffects ^= SpriteEffects.FlipHorizontally; } - if (item.flippedY && item.Prefab.CanSpriteFlipY) { spriteEffects ^= SpriteEffects.FlipVertically; } + if (item.FlippedY && item.Prefab.CanSpriteFlipY) { spriteEffects ^= SpriteEffects.FlipVertically; } + spriteRotation = MathHelper.ToRadians(item.Rotation); + rectangleRotation = spriteRotation; var wire = item.GetComponent(); if (wire != null && wire.Item.body != null && !wire.Item.body.Enabled) { @@ -809,7 +819,10 @@ namespace Barotrauma case Structure structure: { if (structure.FlippedX && structure.Prefab.CanSpriteFlipX) { spriteEffects ^= SpriteEffects.FlipHorizontally; } - if (structure.flippedY && structure.Prefab.CanSpriteFlipY) { spriteEffects ^= SpriteEffects.FlipVertically; } + if (structure.FlippedY && structure.Prefab.CanSpriteFlipY) { spriteEffects ^= SpriteEffects.FlipVertically; } + spriteRotation = MathHelper.ToRadians(structure.Rotation); + rectangleRotation = spriteRotation; + if (structure.FlippedX != structure.FlippedY) { rectangleRotation = -rectangleRotation; } break; } case WayPoint wayPoint: @@ -831,11 +844,12 @@ namespace Barotrauma } } e.Prefab?.DrawPlacing(spriteBatch, - new Rectangle(e.WorldRect.Location + new Point((int)moveAmount.X, (int)-moveAmount.Y), e.WorldRect.Size), e.Scale, spriteEffects); + new Rectangle(e.WorldRect.Location + new Point((int)moveAmount.X, (int)-moveAmount.Y), e.WorldRect.Size), e.Scale, spriteRotation, spriteEffects: spriteEffects); GUI.DrawRectangle(spriteBatch, - new Vector2(e.WorldRect.X, -e.WorldRect.Y) + moveAmount, - new Vector2(e.rect.Width, e.rect.Height), - Color.White, false, 0, (int)Math.Max(3.0f / GameScreen.Selected.Cam.Zoom, 2.0f)); + center: e.WorldRect.Center.ToVector2().FlipY() + moveAmount + new Vector2(0f, e.WorldRect.Height), + width: e.WorldRect.Width, height: e.WorldRect.Height, + rotation: rectangleRotation, clr: Color.White, + depth: 0f, thickness: Math.Max(3.0f / GameScreen.Selected.Cam.Zoom, 2.0f)); } //stop dragging the "selection rectangle" @@ -877,6 +891,23 @@ namespace Barotrauma spriteBatch.DrawLine(corners[3] + offset, corners[0] - offset, color, thickness); } + protected static void ColorFlipButton(GUIButton btn, bool flip) + { + var color = flip ? GUIStyle.Green : Color.White; + var hsv = ToolBox.RGBToHSV(color); + + // Boost saturation and reduce value a bit because our default colors are too muted for this button's style + var hsvBase = hsv; + hsvBase.Y *= 4f; + hsvBase.Z *= 0.8f; + btn.Color = ToolBox.HSVToRGB(hsvBase.X, hsvBase.Y, hsvBase.Z); + btn.SelectedColor = ToolBox.HSVToRGB(hsvBase.X, hsvBase.Y, hsvBase.Z); + + var hsvHover = hsv; + hsvHover.Z *= 1.2f; + btn.HoverColor = ToolBox.HSVToRGB(hsvHover.X, hsvHover.Y, hsvHover.Z); + } + public static List FilteredSelectedList { get; private set; } = new List(); public static void UpdateEditor(Camera cam, float deltaTime) @@ -1105,6 +1136,25 @@ namespace Barotrauma public virtual void DrawEditing(SpriteBatch spriteBatch, Camera cam) { } + private float RotationRad + => MathHelper.ToRadians( + this switch + { + Structure s => s.Rotation, + Item it => it.Rotation, + _ => 0.0f + }); + + private Vector2 GetEditingHandlePos(int x, int y, Camera cam) + { + Vector2 handleDiff = new Vector2(x * (rect.Width * 0.5f), y * (rect.Height * 0.5f)); + var rotation = -RotationRad; + handleDiff = MathUtils.RotatePoint(handleDiff, rotation); + if (FlippedX) { handleDiff = handleDiff.FlipX(); } + if (FlippedY) { handleDiff = handleDiff.FlipY(); } + return cam.WorldToScreen(Position + handleDiff); + } + float ResizeHandleSize => 10 * GUI.Scale; float ResizeHandleHighlightDistance => 8 * GUI.Scale; @@ -1119,9 +1169,10 @@ namespace Barotrauma { for (int y = startY; y < 2; y += 2) { - Vector2 handlePos = cam.WorldToScreen(Position + new Vector2(x * (rect.Width * 0.5f), y * (rect.Height * 0.5f))); + Vector2 handlePos = GetEditingHandlePos(x, y, cam); bool highlighted = Vector2.DistanceSquared(PlayerInput.MousePosition, handlePos) < ResizeHandleHighlightDistance * ResizeHandleHighlightDistance; + if (highlighted && PlayerInput.PrimaryMouseButtonDown()) { selectionPos = Vector2.Zero; @@ -1138,44 +1189,83 @@ namespace Barotrauma { if (prevRect == null) { - prevRect = new Rectangle(Rect.Location, Rect.Size); + prevRect = Rect; } - Vector2 placePosition = new Vector2(rect.X, rect.Y); - Vector2 placeSize = new Vector2(rect.Width, rect.Height); + Vector2 placePosition = prevRect.Value.Location.ToVector2(); + Vector2 placeSize = prevRect.Value.Size.ToVector2(); - Vector2 mousePos = Submarine.MouseToWorldGrid(cam, Submarine.MainSub, round: true); - - if (PlayerInput.IsShiftDown()) + static Vector2 flipThenRotate(Vector2 point, Vector2 center, float angle, bool flipX, bool flipY) { - mousePos = cam.ScreenToWorld(PlayerInput.MousePosition); + if (flipX) { point = (point - center).FlipX() + center; } + if (flipY) { point = (point - center).FlipY() + center; } + + point = MathUtils.RotatePointAroundTarget(point, center, angle); + + return point; + } + + static Vector2 rotateThenFlip(Vector2 point, Vector2 center, float angle, bool flipX, bool flipY) + { + point = MathUtils.RotatePointAroundTarget(point, center, angle); + + if (flipX) { point = (point - center).FlipX() + center; } + if (flipY) { point = (point - center).FlipY() + center; } + + return point; + } + + Vector2 mousePos = cam.ScreenToWorld(PlayerInput.MousePosition); + Vector2 prevPos = placePosition; + Vector2 prevOppositeCorner = prevPos + placeSize.FlipY(); + Vector2 prevCenter = placePosition + placeSize.FlipY() * 0.5f; + mousePos = flipThenRotate(mousePos, prevCenter, RotationRad, FlippedX, FlippedY); + + if (!PlayerInput.IsShiftDown()) + { + mousePos = Submarine.VectorToWorldGrid(mousePos, Submarine.MainSub, round: true); } if (resizeDirX > 0) { - mousePos.X = Math.Max(mousePos.X, rect.X + Submarine.GridSize.X); + mousePos.X = Math.Max(mousePos.X, prevRect.Value.X + Submarine.GridSize.X); placeSize.X = mousePos.X - placePosition.X; } else if (resizeDirX < 0) { - mousePos.X = Math.Min(mousePos.X, rect.Right - Submarine.GridSize.X); + mousePos.X = Math.Min(mousePos.X, prevRect.Value.Right - Submarine.GridSize.X); placeSize.X = MathF.Round((placePosition.X + placeSize.X) - mousePos.X); placePosition.X = MathF.Round(mousePos.X); } if (resizeDirY < 0) { - mousePos.Y = Math.Min(mousePos.Y, rect.Y - Submarine.GridSize.Y); + mousePos.Y = Math.Min(mousePos.Y, prevRect.Value.Y - Submarine.GridSize.Y); placeSize.Y = placePosition.Y - mousePos.Y; } else if (resizeDirY > 0) { - mousePos.Y = Math.Max(mousePos.Y, rect.Y - rect.Height + Submarine.GridSize.X); + mousePos.Y = Math.Max(mousePos.Y, prevRect.Value.Y - prevRect.Value.Height + Submarine.GridSize.Y); - placeSize.Y = mousePos.Y - (rect.Y - rect.Height); + placeSize.Y = mousePos.Y - (prevRect.Value.Y - prevRect.Value.Height); placePosition.Y = mousePos.Y; } + Vector2 newPos = placePosition; + Vector2 newOppositeCorner = placePosition + placeSize.FlipY(); + + Vector2 transformedCornerDiff = rotateThenFlip(newPos-prevPos, Vector2.Zero, -RotationRad, FlippedX, FlippedY); + Vector2 transformedOppositeCornerDiff = rotateThenFlip(newOppositeCorner-prevOppositeCorner, Vector2.Zero, -RotationRad, FlippedX, FlippedY); + + Vector2 newPosTransformed = rotateThenFlip(prevPos, prevCenter, -RotationRad, FlippedX, FlippedY) + + transformedCornerDiff; + Vector2 newOppositeTransformed = rotateThenFlip(prevOppositeCorner, prevCenter, -RotationRad, FlippedX, FlippedY) + + transformedOppositeCornerDiff; + Vector2 newTransformedCenter = (newPosTransformed + newOppositeTransformed) * 0.5f; + + var newDiff = (newOppositeCorner - newPos) * 0.5f; + placePosition = newTransformedCenter - newDiff; + if ((int)placePosition.X != rect.X || (int)placePosition.Y != rect.Y || (int)placeSize.X != rect.Width || (int)placeSize.Y != rect.Height) { Rect = new Rectangle((int)placePosition.X, (int)placePosition.Y, (int)placeSize.X, (int)placeSize.Y); @@ -1210,15 +1300,16 @@ namespace Barotrauma IsHighlighted = true; int startX = ResizeHorizontal ? -1 : 0; - int StartY = ResizeVertical ? -1 : 0; + int startY = ResizeVertical ? -1 : 0; for (int x = startX; x < 2; x += 2) { - for (int y = StartY; y < 2; y += 2) + for (int y = startY; y < 2; y += 2) { - Vector2 handlePos = cam.WorldToScreen(Position + new Vector2(x * (rect.Width * 0.5f), y * (rect.Height * 0.5f))); + Vector2 handlePos = GetEditingHandlePos(x, y, cam); bool highlighted = Vector2.DistanceSquared(PlayerInput.MousePosition, handlePos) < ResizeHandleHighlightDistance * ResizeHandleHighlightDistance; + var color = Color.White * (highlighted ? 1.0f : 0.6f); if (highlighted && !PlayerInput.PrimaryMouseButtonHeld()) { GUI.MouseCursor = CursorState.Hand; @@ -1226,7 +1317,7 @@ namespace Barotrauma GUI.DrawRectangle(spriteBatch, handlePos - new Vector2(ResizeHandleSize / 2), new Vector2(ResizeHandleSize), - Color.White * (highlighted ? 1.0f : 0.6f), + color, true, 0, (int)Math.Max(1.5f / GameScreen.Selected.Cam.Zoom, 1.0f)); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/MapEntityPrefab.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/MapEntityPrefab.cs index d70dc4087..5007fbecc 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/MapEntityPrefab.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/MapEntityPrefab.cs @@ -84,7 +84,7 @@ namespace Barotrauma } } - public virtual void DrawPlacing(SpriteBatch spriteBatch, Rectangle drawRect, float scale = 1.0f, SpriteEffects spriteEffects = SpriteEffects.None) + public virtual void DrawPlacing(SpriteBatch spriteBatch, Rectangle drawRect, float scale = 1.0f, float rotation = 0.0f, SpriteEffects spriteEffects = SpriteEffects.None) { if (Submarine.MainSub != null) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/RoundSound.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/RoundSound.cs index 18e2bef75..1b3c99d28 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/RoundSound.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/RoundSound.cs @@ -1,10 +1,9 @@ #nullable enable +using Barotrauma.Sounds; +using Microsoft.Xna.Framework; using System; using System.Collections.Generic; using System.Globalization; -using System.Xml.Linq; -using Barotrauma.Sounds; -using Microsoft.Xna.Framework; namespace Barotrauma { @@ -45,7 +44,8 @@ namespace Barotrauma } if (FrequencyMultiplierRange.Y > 4.0f) { - DebugConsole.ThrowError($"Loaded frequency range exceeds max value: {FrequencyMultiplierRange} (original string was \"{freqMultAttr}\")"); + DebugConsole.ThrowError($"Loaded frequency range exceeds max value: {FrequencyMultiplierRange} (original string was \"{freqMultAttr}\")", + contentPackage: element.ContentPackage); } IgnoreMuffling = element.GetAttributeBool("dontmuffle", false); } @@ -65,7 +65,8 @@ namespace Barotrauma if (filename is null) { string errorMsg = "Error when loading round sound (" + element + ") - file path not set"; - DebugConsole.ThrowError(errorMsg); + DebugConsole.ThrowError(errorMsg, + contentPackage: element.ContentPackage); GameAnalyticsManager.AddErrorEventOnce("RoundSound.LoadRoundSound:FilePathEmpty" + element.ToString(), GameAnalyticsManager.ErrorSeverity.Error, errorMsg + "\n" + Environment.StackTrace.CleanupStackTrace()); return null; } @@ -86,7 +87,8 @@ namespace Barotrauma catch (System.IO.FileNotFoundException e) { string errorMsg = "Failed to load sound file \"" + filename + "\" (file not found)."; - DebugConsole.ThrowError(errorMsg, e); + DebugConsole.ThrowError(errorMsg, e, + contentPackage: element.ContentPackage); if (!ContentPackageManager.ModsEnabled) { GameAnalyticsManager.AddErrorEventOnce("RoundSound.LoadRoundSound:FileNotFound" + filename, GameAnalyticsManager.ErrorSeverity.Error, errorMsg + "\n" + Environment.StackTrace.CleanupStackTrace()); @@ -96,7 +98,8 @@ namespace Barotrauma catch (System.IO.InvalidDataException e) { string errorMsg = "Failed to load sound file \"" + filename + "\" (invalid data)."; - DebugConsole.ThrowError(errorMsg, e); + DebugConsole.ThrowError(errorMsg, e, + contentPackage: element.ContentPackage); GameAnalyticsManager.AddErrorEventOnce("RoundSound.LoadRoundSound:InvalidData" + filename, GameAnalyticsManager.ErrorSeverity.Error, errorMsg + "\n" + Environment.StackTrace.CleanupStackTrace()); return null; } @@ -123,7 +126,8 @@ namespace Barotrauma catch (System.IO.FileNotFoundException e) { string errorMsg = "Failed to load sound file \"" + roundSound.Filename + "\"."; - DebugConsole.ThrowError(errorMsg, e); + DebugConsole.ThrowError(errorMsg, e, + contentPackage: roundSound.Sound?.XElement?.ContentPackage); GameAnalyticsManager.AddErrorEventOnce("RoundSound.LoadRoundSound:FileNotFound" + roundSound.Filename, GameAnalyticsManager.ErrorSeverity.Error, errorMsg + "\n" + Environment.StackTrace.CleanupStackTrace()); return; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Structure.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Structure.cs index 3c28cbe17..d1453f665 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Structure.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Structure.cs @@ -150,7 +150,8 @@ namespace Barotrauma Stretch = true, RelativeSpacing = 0.01f }; - new GUIButton(new RectTransform(new Vector2(0.23f, 1.0f), buttonContainer.RectTransform), TextManager.Get("MirrorEntityX"), style: "GUIButtonSmall") + + var mirrorX = new GUIButton(new RectTransform(new Vector2(0.23f, 1.0f), buttonContainer.RectTransform), TextManager.Get("MirrorEntityX"), style: "GUIButtonSmall") { ToolTip = TextManager.Get("MirrorEntityXToolTip"), OnClicked = (button, data) => @@ -160,10 +161,12 @@ namespace Barotrauma me.FlipX(relativeToSub: false); } if (!SelectedList.Contains(this)) { FlipX(relativeToSub: false); } + ColorFlipButton(button, FlippedX); return true; } }; - new GUIButton(new RectTransform(new Vector2(0.23f, 1.0f), buttonContainer.RectTransform), TextManager.Get("MirrorEntityY"), style: "GUIButtonSmall") + ColorFlipButton(mirrorX, FlippedX); + var mirrorY = new GUIButton(new RectTransform(new Vector2(0.23f, 1.0f), buttonContainer.RectTransform), TextManager.Get("MirrorEntityY"), style: "GUIButtonSmall") { ToolTip = TextManager.Get("MirrorEntityYToolTip"), OnClicked = (button, data) => @@ -173,9 +176,11 @@ namespace Barotrauma me.FlipY(relativeToSub: false); } if (!SelectedList.Contains(this)) { FlipY(relativeToSub: false); } + ColorFlipButton(button, FlippedY); return true; } }; + ColorFlipButton(mirrorY, FlippedY); new GUIButton(new RectTransform(new Vector2(0.23f, 1.0f), buttonContainer.RectTransform), TextManager.Get("ReloadSprite"), style: "GUIButtonSmall") { OnClicked = (button, data) => @@ -357,8 +362,10 @@ namespace Barotrauma Prefab.BackgroundSprite.DrawTiled( spriteBatch, - new Vector2(rect.X + drawOffset.X, -(rect.Y + drawOffset.Y)), + new Vector2(rect.X + rect.Width / 2 + drawOffset.X, -(rect.Y - rect.Height / 2 + drawOffset.Y)), new Vector2(rect.Width, rect.Height), + rotation: rotationRad, + origin: rect.Size.ToVector2() * new Vector2(0.5f, 0.5f), color: Prefab.BackgroundSpriteColor, textureScale: TextureScale * Scale, startOffset: backGroundOffset, @@ -368,8 +375,10 @@ namespace Barotrauma { Prefab.BackgroundSprite.DrawTiled( spriteBatch, - new Vector2(rect.X + drawOffset.X, -(rect.Y + drawOffset.Y)) + dropShadowOffset, + new Vector2(rect.X + rect.Width / 2 + drawOffset.X, -(rect.Y - rect.Height / 2 + drawOffset.Y)) + dropShadowOffset, new Vector2(rect.Width, rect.Height), + rotation: rotationRad, + origin: rect.Size.ToVector2() * new Vector2(0.5f, 0.5f), color: Color.Black * 0.5f, textureScale: TextureScale * Scale, startOffset: backGroundOffset, @@ -385,6 +394,13 @@ namespace Barotrauma SpriteEffects oldEffects = Prefab.Sprite.effects; Prefab.Sprite.effects ^= SpriteEffects; + Vector2 advanceX = MathUtils.RotatedUnitXRadians(this.rotationRad).FlipY(); + Vector2 advanceY = advanceX.YX().FlipX(); + if (FlippedX != FlippedY) + { + advanceX = advanceX.FlipY(); + advanceY = advanceY.FlipX(); + } for (int i = 0; i < Sections.Length; i++) { Rectangle drawSection = Sections[i].rect; @@ -409,7 +425,7 @@ namespace Barotrauma drawSection = new Rectangle( drawSection.X, drawSection.Y, - Sections[Sections.Length -1 ].rect.Right - drawSection.X, + Sections[Sections.Length - 1].rect.Right - drawSection.X, drawSection.Y - (Sections[Sections.Length - 1].rect.Y - Sections[Sections.Length - 1].rect.Height)); i = Sections.Length; } @@ -424,10 +440,18 @@ namespace Barotrauma sectionOffset.X += MathUtils.PositiveModulo((int)-textureOffset.X, Prefab.Sprite.SourceRect.Width); sectionOffset.Y += MathUtils.PositiveModulo((int)-textureOffset.Y, Prefab.Sprite.SourceRect.Height); + Vector2 pos = new Vector2(drawSection.X, drawSection.Y); + pos -= rect.Location.ToVector2(); + pos = advanceX * pos.X + advanceY * pos.Y; + pos += rect.Location.ToVector2(); + pos = new Vector2(pos.X + rect.Width / 2 + drawOffset.X, -(pos.Y - rect.Height / 2 + drawOffset.Y)); + Prefab.Sprite.DrawTiled( spriteBatch, - new Vector2(drawSection.X + drawOffset.X, -(drawSection.Y + drawOffset.Y)), + pos, new Vector2(drawSection.Width, drawSection.Height), + rotation: rotationRad, + origin: rect.Size.ToVector2() * new Vector2(0.5f, 0.5f), color: color, startOffset: sectionOffset, depth: depth, @@ -437,7 +461,7 @@ namespace Barotrauma foreach (var decorativeSprite in Prefab.DecorativeSprites) { if (!spriteAnimState[decorativeSprite].IsActive) { continue; } - float rotation = decorativeSprite.GetRotation(ref spriteAnimState[decorativeSprite].RotationState, spriteAnimState[decorativeSprite].RandomRotationFactor); + float rotation = decorativeSprite.GetRotation(ref spriteAnimState[decorativeSprite].RotationState, spriteAnimState[decorativeSprite].RandomRotationFactor) + this.rotationRad; Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState, spriteAnimState[decorativeSprite].RandomOffsetMultiplier) * Scale; decorativeSprite.Sprite.Draw(spriteBatch, new Vector2(DrawPosition.X + offset.X, -(DrawPosition.Y + offset.Y)), color, rotation, decorativeSprite.GetScale(spriteAnimState[decorativeSprite].RandomScaleFactor) * Scale, Prefab.Sprite.effects, diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/StructurePrefab.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/StructurePrefab.cs index f031e7fab..06e17348a 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/StructurePrefab.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/StructurePrefab.cs @@ -94,16 +94,21 @@ namespace Barotrauma GUI.DrawRectangle(spriteBatch, new Rectangle(newRect.X, -newRect.Y - GameMain.GraphicsHeight, newRect.Width, newRect.Height + GameMain.GraphicsHeight * 2), Color.White); } - public override void DrawPlacing(SpriteBatch spriteBatch, Rectangle placeRect, float scale = 1.0f, SpriteEffects spriteEffects = SpriteEffects.None) + public override void DrawPlacing(SpriteBatch spriteBatch, Rectangle placeRect, float scale = 1.0f, float rotation = 0.0f, SpriteEffects spriteEffects = SpriteEffects.None) { SpriteEffects oldEffects = Sprite.effects; Sprite.effects ^= spriteEffects; + var position = placeRect.Location.ToVector2().FlipY(); + position += placeRect.Size.ToVector2() * 0.5f; + Sprite.DrawTiled( spriteBatch, - new Vector2(placeRect.X, -placeRect.Y), - new Vector2(placeRect.Width, placeRect.Height), - color: Color.White * 0.8f, + position, + placeRect.Size.ToVector2(), + color: Color.White * 0.8f, + origin: placeRect.Size.ToVector2() * 0.5f, + rotation: rotation, textureScale: TextureScale * scale); Sprite.effects = oldEffects; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Submarine.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Submarine.cs index 6d330e43e..3d9e53dc6 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Submarine.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Submarine.cs @@ -25,7 +25,7 @@ namespace Barotrauma /// /// Margin applied around the view area when culling entities (i.e. entities that are this far outside the view are still considered visible) /// - private const int CullMargin = 500; + private const int CullMargin = 50; /// /// Update entity culling when any corner of the view has moved more than this /// @@ -713,18 +713,12 @@ namespace Barotrauma return GameMain.LightManager.Lights.Count(l => l.CastShadows && !l.IsBackground) - disabledItemLightCount; } - public static Vector2 MouseToWorldGrid(Camera cam, Submarine sub, bool round = false) + public static Vector2 MouseToWorldGrid(Camera cam, Submarine sub, Vector2? mousePos = null, bool round = false) { - Vector2 position = PlayerInput.MousePosition; + Vector2 position = mousePos ?? PlayerInput.MousePosition; position = cam.ScreenToWorld(position); - Vector2 worldGridPos = VectorToWorldGrid(position, round); - - if (sub != null) - { - worldGridPos.X += sub.Position.X % GridSize.X; - worldGridPos.Y += sub.Position.Y % GridSize.Y; - } + Vector2 worldGridPos = VectorToWorldGrid(position, sub, round); return worldGridPos; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/Client.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/Client.cs index 6a5bfb3ee..318176714 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/Client.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/Client.cs @@ -122,12 +122,12 @@ namespace Barotrauma.Networking VoipSound = null; } - public void SetPermissions(ClientPermissions permissions, IEnumerable permittedConsoleCommands) + public void SetPermissions(ClientPermissions permissions, IEnumerable permittedConsoleCommands) { List permittedCommands = new List(); - foreach (string commandName in permittedConsoleCommands) + foreach (Identifier commandName in permittedConsoleCommands) { - var consoleCommand = DebugConsole.Commands.Find(c => c.names.Contains(commandName)); + var consoleCommand = DebugConsole.Commands.Find(c => c.Names.Contains(commandName)); if (consoleCommand != null) { permittedCommands.Add(consoleCommand); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs index 36740fb62..6fa7ec90f 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs @@ -65,7 +65,7 @@ namespace Barotrauma.Networking public bool LateCampaignJoin = false; private ClientPermissions permissions = ClientPermissions.None; - private List permittedConsoleCommands = new List(); + private List permittedConsoleCommands = new List(); private bool connected; @@ -170,9 +170,9 @@ namespace Barotrauma.Networking internal readonly struct PermissionChangedEvent { public readonly ClientPermissions NewPermissions; - public readonly ImmutableArray NewPermittedConsoleCommands; + public readonly ImmutableArray NewPermittedConsoleCommands; - public PermissionChangedEvent(ClientPermissions newPermissions, IReadOnlyList newPermittedConsoleCommands) + public PermissionChangedEvent(ClientPermissions newPermissions, IReadOnlyList newPermittedConsoleCommands) { NewPermissions = newPermissions; NewPermittedConsoleCommands = newPermittedConsoleCommands.ToImmutableArray(); @@ -1211,11 +1211,11 @@ namespace Barotrauma.Networking targetClient?.SetPermissions(permissions, permittedCommands); if (clientId == SessionId) { - SetMyPermissions(permissions, permittedCommands.Select(command => command.names[0])); + SetMyPermissions(permissions, permittedCommands.Select(command => command.Names[0])); } } - private void SetMyPermissions(ClientPermissions newPermissions, IEnumerable permittedConsoleCommands) + private void SetMyPermissions(ClientPermissions newPermissions, IEnumerable permittedConsoleCommands) { if (!(this.permittedConsoleCommands.Any(c => !permittedConsoleCommands.Contains(c)) || permittedConsoleCommands.Any(c => !this.permittedConsoleCommands.Contains(c)))) @@ -1227,7 +1227,7 @@ namespace Barotrauma.Networking permissions.HasFlag(ClientPermissions.ManageRound) != newPermissions.HasFlag(ClientPermissions.ManageRound); permissions = newPermissions; - this.permittedConsoleCommands = new List(permittedConsoleCommands); + this.permittedConsoleCommands = permittedConsoleCommands.ToList(); //don't show the "permissions changed" popup if the client owns the server if (!IsServerOwner) { @@ -1265,10 +1265,10 @@ namespace Barotrauma.Networking var commandsLabel = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), rightColumn.RectTransform), TextManager.Get("PermittedConsoleCommands"), wrap: true, font: GUIStyle.SubHeadingFont); var commandList = new GUIListBox(new RectTransform(new Vector2(1.0f, 1.0f), rightColumn.RectTransform)); - foreach (string permittedCommand in permittedConsoleCommands) + foreach (Identifier permittedCommand in permittedConsoleCommands) { new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), commandList.Content.RectTransform, minSize: new Point(0, 15)), - permittedCommand, font: GUIStyle.SmallFont) + permittedCommand.Value, font: GUIStyle.SmallFont) { CanBeFocused = false }; @@ -1348,6 +1348,7 @@ namespace Barotrauma.Networking bool respawnAllowed = inc.ReadBoolean(); ServerSettings.AllowDisguises = inc.ReadBoolean(); ServerSettings.AllowRewiring = inc.ReadBoolean(); + ServerSettings.AllowImmediateItemDelivery = inc.ReadBoolean(); ServerSettings.AllowFriendlyFire = inc.ReadBoolean(); ServerSettings.LockAllDefaultWires = inc.ReadBoolean(); ServerSettings.AllowLinkingWifiToChat = inc.ReadBoolean(); @@ -2551,18 +2552,18 @@ namespace Barotrauma.Networking return permissions.HasFlag(permission); } - public bool HasConsoleCommandPermission(string commandName) + public bool HasConsoleCommandPermission(Identifier commandName) { if (!permissions.HasFlag(ClientPermissions.ConsoleCommands)) { return false; } - if (permittedConsoleCommands.Any(c => c.Equals(commandName, StringComparison.OrdinalIgnoreCase))) { return true; } + if (permittedConsoleCommands.Contains(commandName)) { return true; } //check aliases foreach (DebugConsole.Command command in DebugConsole.Commands) { - if (command.names.Contains(commandName)) + if (command.Names.Contains(commandName)) { - if (command.names.Intersect(permittedConsoleCommands).Any()) { return true; } + if (command.Names.Intersect(permittedConsoleCommands).Any()) { return true; } break; } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerList/ServerInfo.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerList/ServerInfo.cs index f7c5d0a44..ab0cc11ea 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerList/ServerInfo.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerList/ServerInfo.cs @@ -75,6 +75,9 @@ namespace Barotrauma.Networking [Serialize("", IsPropertySaveable.Yes)] public LanguageIdentifier Language { get; set; } + [Serialize("", IsPropertySaveable.Yes)] + public string SelectedSub { get; set; } = string.Empty; + public Version GameVersion { get; set; } = new Version(0, 0, 0, 0); public Option Ping = Option.None(); @@ -104,6 +107,8 @@ namespace Barotrauma.Networking public ImmutableArray ContentPackages; + public int ContentPackageCount; + public bool IsModded => ContentPackages.Any(p => !GameMain.VanillaContent.NameMatches(p.Name)); public ServerInfo(Endpoint endpoint) @@ -309,6 +314,14 @@ namespace Barotrauma.Networking TextManager.Get(GameMode.IsEmpty ? "Unknown" : "GameMode." + GameMode).Fallback(GameMode.Value), textAlignment: Alignment.Right); + if (!string.IsNullOrEmpty(SelectedSub)) + { + var submarineText = new GUITextBlock(new RectTransform(new Vector2(1.0f, elementHeight), content.RectTransform), TextManager.Get("Submarine")); + new GUITextBlock(new RectTransform(Vector2.One, submarineText.RectTransform), + SelectedSub, + textAlignment: Alignment.Right); + } + GUITextBlock playStyleText = new GUITextBlock(new RectTransform(new Vector2(1.0f, elementHeight), content.RectTransform), TextManager.Get("serverplaystyle")); new GUITextBlock(new RectTransform(Vector2.One, playStyleText.RectTransform), TextManager.Get("servertag." + playStyle), textAlignment: Alignment.Right); @@ -385,6 +398,15 @@ namespace Barotrauma.Networking } } } + if (ContentPackageCount > ContentPackages.Length) + { + new GUITextBlock( + new RectTransform(new Vector2(1.0f, 0.15f), contentPackageList.Content.RectTransform) { MinSize = new Point(0, 15) }, + TextManager.GetWithVariable("workshopitemdownloadprompttruncated", "[number]", (ContentPackageCount - ContentPackages.Length).ToString())) + { + CanBeFocused = false + }; + } } // ----------------------------------------------------------------------------- @@ -423,14 +445,16 @@ namespace Barotrauma.Networking AllowSpectating = getBool("allowspectating"); AllowRespawn = getBool("allowrespawn"); VoipEnabled = getBool("voicechatenabled"); - GameMode = valueGetter("gamemode")?.ToIdentifier() ?? Identifier.Empty; if (float.TryParse(valueGetter("traitors"), NumberStyles.Any, CultureInfo.InvariantCulture, out float traitorProbability)) { TraitorProbability = traitorProbability; } if (Enum.TryParse(valueGetter("playstyle"), out PlayStyle playStyle)) { PlayStyle = playStyle; } Language = valueGetter("language")?.ToLanguageIdentifier() ?? LanguageIdentifier.None; + SelectedSub = valueGetter("submarine") ?? string.Empty; ContentPackages = ExtractContentPackageInfo(ServerName, valueGetter).ToImmutableArray(); - + ContentPackageCount = ContentPackages.Length; + if (int.TryParse(valueGetter("packagecount"), out int packageCount)) { ContentPackageCount = packageCount; } + bool getBool(string key) { string? data = valueGetter(key); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerSettings.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerSettings.cs index 82f553984..fa077332e 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerSettings.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerSettings.cs @@ -936,6 +936,10 @@ namespace Barotrauma.Networking TextManager.Get("ServerSettingsAllowVoteKick")); GetPropertyData(nameof(AllowVoteKick)).AssignGUIComponent(voteKickBox); + var allowImmediateItemDeliveryBox = new GUITickBox(new RectTransform(new Vector2(0.48f, 0.05f), tickBoxContainer.Content.RectTransform), + TextManager.Get("ServerSettingsImmediateItemDelivery")); + GetPropertyData(nameof(AllowImmediateItemDelivery)).AssignGUIComponent(allowImmediateItemDeliveryBox); + GUITextBlock.AutoScaleAndNormalize(tickBoxContainer.Content.Children.Select(c => ((GUITickBox)c).TextBlock)); tickBoxContainer.RectTransform.MinSize = new Point(0, (int)(tickBoxContainer.Content.Children.First().Rect.Height * 2.0f + tickBoxContainer.Padding.Y + tickBoxContainer.Padding.W)); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Particles/ParticleEmitter.cs b/Barotrauma/BarotraumaClient/ClientSource/Particles/ParticleEmitter.cs index 4bd6e7605..2eac72fa2 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Particles/ParticleEmitter.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Particles/ParticleEmitter.cs @@ -1,4 +1,5 @@ -using Microsoft.Xna.Framework; +#nullable enable +using Microsoft.Xna.Framework; using System; using System.Collections.Generic; using System.Xml.Linq; @@ -148,7 +149,7 @@ namespace Barotrauma.Particles Prefab = prefab; } - public void Emit(float deltaTime, Vector2 position, Hull hullGuess = null, float angle = 0.0f, float particleRotation = 0.0f, float velocityMultiplier = 1.0f, float sizeMultiplier = 1.0f, float amountMultiplier = 1.0f, Color? colorMultiplier = null, ParticlePrefab overrideParticle = null, bool mirrorAngle = false, Tuple tracerPoints = null) + public void Emit(float deltaTime, Vector2 position, Hull? hullGuess = null, float angle = 0.0f, float particleRotation = 0.0f, float velocityMultiplier = 1.0f, float sizeMultiplier = 1.0f, float amountMultiplier = 1.0f, Color? colorMultiplier = null, ParticlePrefab? overrideParticle = null, bool mirrorAngle = false, Tuple? tracerPoints = null) { if (GameMain.Client?.MidRoundSyncing ?? false) { return; } @@ -191,16 +192,17 @@ namespace Barotrauma.Particles burstEmitTimer = Prefab.Properties.EmitInterval; for (int i = 0; i < Prefab.Properties.ParticleAmount * amountMultiplier; i++) { - Emit(position, hullGuess, angle, particleRotation, velocityMultiplier, sizeMultiplier, colorMultiplier, overrideParticle, tracerPoints: tracerPoints); + Emit(position, hullGuess, angle, particleRotation, velocityMultiplier, sizeMultiplier, colorMultiplier, overrideParticle, mirrorAngle, tracerPoints: tracerPoints); } } - private void Emit(Vector2 position, Hull hullGuess, float angle, float particleRotation, float velocityMultiplier, float sizeMultiplier, Color? colorMultiplier = null, ParticlePrefab overrideParticle = null, bool mirrorAngle = false, Tuple tracerPoints = null) + private void Emit(Vector2 position, Hull? hullGuess, float angle, float particleRotation, float velocityMultiplier, float sizeMultiplier, Color? colorMultiplier = null, ParticlePrefab? overrideParticle = null, bool mirrorAngle = false, Tuple? tracerPoints = null) { var particlePrefab = overrideParticle ?? Prefab.ParticlePrefab; if (particlePrefab == null) { - DebugConsole.AddWarning($"Could not find the particle prefab \"{Prefab.ParticlePrefabName}\"."); + DebugConsole.AddWarning($"Could not find the particle prefab \"{Prefab.ParticlePrefabName}\".", + contentPackage: Prefab.ContentPackage); return; } @@ -271,7 +273,7 @@ namespace Barotrauma.Particles { public readonly Identifier ParticlePrefabName; - public ParticlePrefab ParticlePrefab + public ParticlePrefab? ParticlePrefab { get { @@ -282,12 +284,16 @@ namespace Barotrauma.Particles public readonly ParticleEmitterProperties Properties; - public bool DrawOnTop => Properties.DrawOnTop || ParticlePrefab.DrawOnTop; + public readonly ContentPackage? ContentPackage; + + public bool DrawOnTop => Properties.DrawOnTop || ParticlePrefab is { DrawOnTop: true }; public ParticleEmitterPrefab(ContentXElement element) { - Properties = new ParticleEmitterProperties(element); + if (element == null) { throw new ArgumentNullException(nameof(element)); } + Properties = new ParticleEmitterProperties(element!); ParticlePrefabName = element.GetAttributeIdentifier("particle", ""); + ContentPackage = element.ContentPackage; } public ParticleEmitterPrefab(ParticlePrefab prefab, ParticleEmitterProperties properties) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Particles/ParticlePrefab.cs b/Barotrauma/BarotraumaClient/ClientSource/Particles/ParticlePrefab.cs index c550fbfee..050ee9ded 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Particles/ParticlePrefab.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Particles/ParticlePrefab.cs @@ -244,7 +244,8 @@ namespace Barotrauma.Particles if (Sprites.Count == 0) { - DebugConsole.ThrowError($"Particle prefab \"{Name}\" in the file \"{file}\" has no sprites defined!"); + DebugConsole.ThrowError($"Particle prefab \"{Name}\" in the file \"{file}\" has no sprites defined!", + contentPackage: element.ContentPackage); } //if velocity change in water is not given, it defaults to the normal velocity change diff --git a/Barotrauma/BarotraumaClient/ClientSource/Program.cs b/Barotrauma/BarotraumaClient/ClientSource/Program.cs index a3d44b5d6..4dd29adfa 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Program.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Program.cs @@ -261,6 +261,9 @@ namespace Barotrauma { crashHeader += " " + exception.TargetSite.ToString(); } + //log the message separately, so the same error messages get grouped as the same error in GA + //(the full crash report tends to always have some differences between clients, so they get displayed separately) + GameAnalyticsManager.AddErrorEvent(GameAnalyticsManager.ErrorSeverity.Critical, crashHeader); GameAnalyticsManager.AddErrorEvent(GameAnalyticsManager.ErrorSeverity.Critical, crashHeader + "\n\n" + sb.ToString()); GameAnalyticsManager.ShutDown(); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs index bd977496b..143100d4d 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs @@ -76,6 +76,8 @@ namespace Barotrauma private GUITextBlock tutorialHeader, tutorialDescription; private GUIListBox tutorialList; + private GUIComponent versionMismatchWarning; + #region Creation public MainMenuScreen(GameMain game) { @@ -105,6 +107,28 @@ namespace Barotrauma } }; + versionMismatchWarning = new GUIFrame(new RectTransform(new Vector2(0.7f, 0.065f), Frame.RectTransform) { AbsoluteOffset = new Point(GUI.IntScale(15)) }, style: "InnerFrame", color: GUIStyle.Red) + { + IgnoreLayoutGroups = true, + Visible = false + }; + var versionMismatchContent = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), versionMismatchWarning.RectTransform, Anchor.Center), isHorizontal: true) + { + RelativeSpacing = 0.05f, + }; + new GUIImage(new RectTransform(new Vector2(1.0f), versionMismatchContent.RectTransform, scaleBasis: ScaleBasis.Smallest), style: "GUINotificationButton") + { + Color = GUIStyle.Orange + }; + new GUITextBlock(new RectTransform(new Vector2(0.85f, 1.0f), versionMismatchContent.RectTransform), + TextManager.GetWithVariables("versionmismatchwarning", + ("[gameversion]", GameMain.Version.ToString()), + ("[contentversion]", ContentPackageManager.VanillaCorePackage.GameVersion.ToString())), + wrap: true) + { + TextColor = GUIStyle.Orange + }; + new GUIImage(new RectTransform(new Vector2(0.4f, 0.25f), Frame.RectTransform, Anchor.BottomRight) { RelativeOffset = new Vector2(0.08f, 0.05f), AbsoluteOffset = new Point(-8, -8) }, style: "TitleText") @@ -587,7 +611,9 @@ namespace Barotrauma GameMain.SubEditorScreen?.ClearBackedUpSubInfo(); Submarine.Unload(); - + + versionMismatchWarning.Visible = GameMain.Version < ContentPackageManager.VanillaCorePackage.GameVersion; + ResetButtonStates(null); } @@ -663,7 +689,18 @@ namespace Barotrauma .ToArray(); foreach (var newServerExe in newServerExes) { - serverExecutableDropdown.AddItem($"{newServerExe.ContentPackage.Name} - {Path.GetFileNameWithoutExtension(newServerExe.Path.Value)}", userData: newServerExe); + var serverExeEntry = serverExecutableDropdown.AddItem($"{newServerExe.ContentPackage.Name} - {Path.GetFileNameWithoutExtension(newServerExe.Path.Value)}", userData: newServerExe); + if (newServerExe.ContentPackage.GameVersion < GameMain.VanillaContent.GameVersion) + { + serverExeEntry.ToolTip = + TextManager.GetWithVariables("versionmismatchwarning", + ("[gameversion]", newServerExe.ContentPackage.GameVersion.ToString()), + ("[contentversion]", GameMain.VanillaContent.GameVersion.ToString())); + if (serverExeEntry is GUITextBlock serverExeText) + { + serverExeText.TextColor = GUIStyle.Red; + } + } } serverExecutableDropdown.ListBox.Content.Children.ForEach(c => { @@ -1472,34 +1509,58 @@ namespace Barotrauma { OnClicked = (btn, userdata) => { - string name = serverNameBox.Text; - if (string.IsNullOrEmpty(name)) - { - serverNameBox.Flash(); - return false; - } - - if (isPublicBox.Selected && ForbiddenWordFilter.IsForbidden(name, out string forbiddenWord)) - { - var msgBox = new GUIMessageBox("", - TextManager.GetWithVariables("forbiddenservernameverification", ("[forbiddenword]", forbiddenWord), ("[servername]", name)), - new LocalizedString[] { TextManager.Get("yes"), TextManager.Get("no") }); - msgBox.Buttons[0].OnClicked += (_, __) => - { - TryStartServer(); - msgBox.Close(); - return true; - }; - msgBox.Buttons[1].OnClicked += msgBox.Close; - } - else - { - TryStartServer(); - } - + CheckServerName(); return true; } }; + + void CheckServerName() + { + string name = serverNameBox.Text; + if (string.IsNullOrEmpty(name)) + { + serverNameBox.Flash(); + return; + } + if (isPublicBox.Selected && ForbiddenWordFilter.IsForbidden(name, out string forbiddenWord)) + { + var msgBox = new GUIMessageBox("", + TextManager.GetWithVariables("forbiddenservernameverification", ("[forbiddenword]", forbiddenWord), ("[servername]", name)), + new LocalizedString[] { TextManager.Get("yes"), TextManager.Get("no") }); + msgBox.Buttons[0].OnClicked += (_, __) => + { + CheckServerExe(); + msgBox.Close(); + return true; + }; + msgBox.Buttons[1].OnClicked += msgBox.Close; + return; + } + CheckServerExe(); + } + + void CheckServerExe() + { + if (serverExecutableDropdown?.SelectedData is ServerExecutableFile serverExe && + serverExe.ContentPackage.GameVersion < GameMain.VanillaContent.GameVersion) + { + var msgBox = new GUIMessageBox(string.Empty, + TextManager.GetWithVariables("versionmismatchwarning", + ("[gameversion]", serverExe.ContentPackage.GameVersion.ToString()), + ("[contentversion]", GameMain.VanillaContent.GameVersion.ToString())) + "\n\n"+ + TextManager.GetWithVariable("versionmismatch.verifylaunch", "[exename]", serverExe.ContentPackage.Name), + new LocalizedString[] { TextManager.Get("yes"), TextManager.Get("no") }); + msgBox.Buttons[0].OnClicked += (_, __) => + { + TryStartServer(); + msgBox.Close(); + return true; + }; + msgBox.Buttons[1].OnClicked += msgBox.Close; + return; + } + TryStartServer(); + } } private void SetServerPlayStyle(PlayStyle playStyle) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs index f004779d1..7e8b006c9 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs @@ -2389,10 +2389,20 @@ namespace Barotrauma options.Add(kickOption); } - options.Add(new ContextMenuOption("Ban", isEnabled: canBan, onSelected: delegate + if (GameMain.Client?.ServerSettings?.BanList?.BannedPlayers?.Any(bp => bp.MatchesClient(client)) ?? false) { - GameMain.Client?.CreateKickReasonPrompt(client.Name, true); - })); + options.Add(new ContextMenuOption("clientpermission.unban", isEnabled: canBan, onSelected: delegate + { + GameMain.Client?.UnbanPlayer(client.Name); + })); + } + else + { + options.Add(new ContextMenuOption("Ban", isEnabled: canBan, onSelected: delegate + { + GameMain.Client?.CreateKickReasonPrompt(client.Name, true); + })); + } GUIContextMenu.CreateContextMenu(null, client.Name, headerColor: clientColor, options.ToArray()); } @@ -2591,11 +2601,11 @@ namespace Barotrauma foreach (DebugConsole.Command command in DebugConsole.Commands) { var commandTickBox = new GUITickBox(new RectTransform(new Vector2(0.15f, 0.15f), commandList.Content.RectTransform), - command.names[0], font: GUIStyle.SmallFont) + command.Names[0].Value, font: GUIStyle.SmallFont) { Selected = selectedClient.PermittedConsoleCommands.Contains(command), Enabled = !myClient, - ToolTip = command.help, + ToolTip = command.Help, UserData = command }; commandTickBox.OnSelected += (GUITickBox tickBox) => @@ -2630,12 +2640,25 @@ namespace Barotrauma { if (GameMain.Client.HasPermission(ClientPermissions.Ban)) { - var banButton = new GUIButton(new RectTransform(new Vector2(0.34f, 1.0f), buttonAreaTop.RectTransform), - TextManager.Get("Ban")) + GUIButton banButton; + if (GameMain.Client?.ServerSettings?.BanList?.BannedPlayers?.Any(bp => bp.MatchesClient(selectedClient)) ?? false) { - UserData = selectedClient - }; - banButton.OnClicked = (bt, userdata) => { BanPlayer(selectedClient); return true; }; + banButton = new GUIButton(new RectTransform(new Vector2(0.34f, 1.0f), buttonAreaTop.RectTransform), + TextManager.Get("clientpermission.unban")) + { + UserData = selectedClient + }; + banButton.OnClicked = (bt, userdata) => { GameMain.Client?.UnbanPlayer(selectedClient.Name); return true; }; + } + else + { + banButton = new GUIButton(new RectTransform(new Vector2(0.34f, 1.0f), buttonAreaTop.RectTransform), + TextManager.Get("Ban")) + { + UserData = selectedClient + }; + banButton.OnClicked = (bt, userdata) => { BanPlayer(selectedClient); return true; }; + } banButton.OnClicked += ClosePlayerFrame; } @@ -3147,12 +3170,12 @@ namespace Barotrauma GUIButton jobButton = null; var availableJobs = JobPrefab.Prefabs.Where(jobPrefab => - jobPrefab.MaxNumber > 0 && JobList.Content.Children.All(c => !(c.UserData is JobVariant prefab) || prefab.Prefab != jobPrefab) + !jobPrefab.HiddenJob && jobPrefab.MaxNumber > 0 && JobList.Content.Children.All(c => c.UserData is not JobVariant prefab || prefab.Prefab != jobPrefab) ).Select(j => new JobVariant(j, 0)); availableJobs = availableJobs.Concat( JobPrefab.Prefabs.Where(jobPrefab => - jobPrefab.MaxNumber > 0 && JobList.Content.Children.Any(c => (c.UserData is JobVariant prefab) && prefab.Prefab == jobPrefab) + !jobPrefab.HiddenJob && jobPrefab.MaxNumber > 0 && JobList.Content.Children.Any(c => (c.UserData is JobVariant prefab) && prefab.Prefab == jobPrefab) ).Select(j => (JobVariant)JobList.Content.FindChild(c => (c.UserData is JobVariant prefab) && prefab.Prefab == j).UserData)); availableJobs = availableJobs.ToList(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/ServerListScreen/ServerListScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/ServerListScreen/ServerListScreen.cs index d9a3de1c8..f08ce1d14 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/ServerListScreen/ServerListScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/ServerListScreen/ServerListScreen.cs @@ -655,7 +655,7 @@ namespace Barotrauma ScrollBarVisible = true, OnSelected = (btn, obj) => { - if (!(obj is ServerInfo serverInfo)) { return false; } + if (obj is not ServerInfo serverInfo) { return false; } joinButton.Enabled = true; selectedServer = Option.Some(serverInfo); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs index 1fd3ca751..a48cb05d6 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs @@ -1289,7 +1289,8 @@ namespace Barotrauma if (legacy) { textBlock.TextColor *= 0.6f; } if (name.IsNullOrEmpty()) { - DebugConsole.AddWarning($"Entity \"{ep.Identifier.Value}\" has no name!"); + DebugConsole.AddWarning($"Entity \"{ep.Identifier.Value}\" has no name!", + contentPackage: ep.ContentPackage); textBlock.Text = frame.ToolTip = ep.Identifier.Value; textBlock.TextColor = GUIStyle.Red; } @@ -2365,49 +2366,58 @@ namespace Barotrauma //--------------------------------------- - var beaconSettingsContainer = new GUILayoutGroup(new RectTransform(Vector2.One, subTypeDependentSettingFrame.RectTransform)) + var extraSettingsContainer = new GUILayoutGroup(new RectTransform(new Vector2(1, 0.5f), subTypeDependentSettingFrame.RectTransform)) { CanBeFocused = true, Visible = false, Stretch = true }; - // ------------------- - - var beaconMinDifficultyGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.25f), beaconSettingsContainer.RectTransform), isHorizontal: true) + var minDifficultyGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.25f), extraSettingsContainer.RectTransform), isHorizontal: true) { Stretch = true }; - new GUITextBlock(new RectTransform(new Vector2(0.6f, 1.0f), beaconMinDifficultyGroup.RectTransform), + new GUITextBlock(new RectTransform(new Vector2(0.6f, 1.0f), minDifficultyGroup.RectTransform), TextManager.Get("minleveldifficulty"), textAlignment: Alignment.CenterLeft, wrap: true); - var numInput = new GUINumberInput(new RectTransform(new Vector2(0.4f, 1.0f), beaconMinDifficultyGroup.RectTransform), NumberType.Int) + var numInput = new GUINumberInput(new RectTransform(new Vector2(0.4f, 1.0f), minDifficultyGroup.RectTransform), NumberType.Int) { - IntValue = (int)(MainSub?.Info?.BeaconStationInfo?.MinLevelDifficulty ?? 0), + IntValue = (int)(MainSub?.Info?.GetExtraSubmarineInfo?.MinLevelDifficulty ?? 0), MinValueInt = 0, MaxValueInt = 100, OnValueChanged = (numberInput) => { - MainSub.Info.BeaconStationInfo.MinLevelDifficulty = numberInput.IntValue; + MainSub.Info.GetExtraSubmarineInfo.MinLevelDifficulty = numberInput.IntValue; } }; - beaconMinDifficultyGroup.RectTransform.MaxSize = numInput.TextBox.RectTransform.MaxSize; - var beaconMaxDifficultyGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.25f), beaconSettingsContainer.RectTransform), isHorizontal: true) + minDifficultyGroup.RectTransform.MaxSize = numInput.TextBox.RectTransform.MaxSize; + var maxDifficultyGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.25f), extraSettingsContainer.RectTransform), isHorizontal: true) { Stretch = true }; - new GUITextBlock(new RectTransform(new Vector2(0.6f, 1.0f), beaconMaxDifficultyGroup.RectTransform), + new GUITextBlock(new RectTransform(new Vector2(0.6f, 1.0f), maxDifficultyGroup.RectTransform), TextManager.Get("maxleveldifficulty"), textAlignment: Alignment.CenterLeft, wrap: true); - numInput = new GUINumberInput(new RectTransform(new Vector2(0.4f, 1.0f), beaconMaxDifficultyGroup.RectTransform), NumberType.Int) + numInput = new GUINumberInput(new RectTransform(new Vector2(0.4f, 1.0f), maxDifficultyGroup.RectTransform), NumberType.Int) { - IntValue = (int)(MainSub?.Info?.BeaconStationInfo?.MaxLevelDifficulty ?? 100), + IntValue = (int)(MainSub?.Info?.GetExtraSubmarineInfo?.MaxLevelDifficulty ?? 100), MinValueInt = 0, MaxValueInt = 100, OnValueChanged = (numberInput) => { - MainSub.Info.BeaconStationInfo.MaxLevelDifficulty = numberInput.IntValue; + MainSub.Info.GetExtraSubmarineInfo.MaxLevelDifficulty = numberInput.IntValue; } }; - beaconMaxDifficultyGroup.RectTransform.MaxSize = numInput.TextBox.RectTransform.MaxSize; + maxDifficultyGroup.RectTransform.MaxSize = numInput.TextBox.RectTransform.MaxSize; + + + //--------------------------------------- + + var beaconSettingsContainer = new GUILayoutGroup(new RectTransform(Vector2.One, extraSettingsContainer.RectTransform)) + { + CanBeFocused = true, + Visible = false, + Stretch = true + }; + new GUITickBox(new RectTransform(new Vector2(1.0f, 0.25f), beaconSettingsContainer.RectTransform), TextManager.Get("allowdamagedwalls")) { Selected = MainSub?.Info?.BeaconStationInfo?.AllowDamagedWalls ?? true, @@ -2669,8 +2679,13 @@ namespace Barotrauma { MainSub.Info.BeaconStationInfo ??= new BeaconStationInfo(MainSub.Info); } + else if (type == SubmarineType.Wreck) + { + MainSub.Info.WreckInfo ??= new WreckInfo(MainSub.Info); + } previewImageButtonHolder.Children.ForEach(c => c.Enabled = MainSub.Info.AllowPreviewImage); outpostSettingsContainer.Visible = type == SubmarineType.OutpostModule; + extraSettingsContainer.Visible = type == SubmarineType.BeaconStation || type == SubmarineType.Wreck; beaconSettingsContainer.Visible = type == SubmarineType.BeaconStation; subSettingsContainer.Visible = type == SubmarineType.Player; return true; @@ -4439,6 +4454,7 @@ namespace Barotrauma MapEntity.SelectEntity(itemContainer); dummyCharacter.SelectedItem = itemContainer; FilterEntities(entityFilterBox.Text); + MapEntity.StopSelection(); } /// @@ -5556,11 +5572,32 @@ namespace Barotrauma dummyCharacter.Submarine = MainSub; } - // Deposit item from our "infinite stack" into inventory slots - var inv = dummyCharacter?.SelectedItem?.OwnInventory; - if (inv?.visualSlots != null && !PlayerInput.IsCtrlDown()) + if (dummyCharacter?.SelectedItem != null) { - var dragginMouse = MouseDragStart != Vector2.Zero && Vector2.Distance(PlayerInput.MousePosition, MouseDragStart) >= GUI.Scale * 20; + // Deposit item from our "infinite stack" into inventory slots + TryDragItemsToItem(dummyCharacter.SelectedItem); + foreach (Item linkedItem in dummyCharacter.SelectedItem.linkedTo.OfType()) + { + if (linkedItem.OwnInventory?.visualSlots != null) + { + TryDragItemsToItem(linkedItem); + } + } + } + + void TryDragItemsToItem(Item item) + { + foreach (ItemContainer ic in item.GetComponents()) + { + TryDragItemsToInventory(ic.Inventory); + } + } + + void TryDragItemsToInventory(Inventory inv) + { + if (PlayerInput.IsCtrlDown()) { return; } + + var draggingMouse = MouseDragStart != Vector2.Zero && Vector2.Distance(PlayerInput.MousePosition, MouseDragStart) >= GUI.Scale * 20; // So we don't accidentally drag inventory items while doing this if (DraggedItemPrefab != null) { Inventory.DraggingItems.Clear(); } @@ -5568,134 +5605,134 @@ namespace Barotrauma switch (DraggedItemPrefab) { // regular item prefabs - case ItemPrefab itemPrefab when PlayerInput.PrimaryMouseButtonClicked() || dragginMouse: - { - bool spawnedItem = false; - for (var i = 0; i < inv.Capacity; i++) + case ItemPrefab itemPrefab when PlayerInput.PrimaryMouseButtonClicked() || draggingMouse: { - var slot = inv.visualSlots[i]; - var itemContainer = inv.GetItemAt(i)?.GetComponent(); - - // check if the slot is empty or if we can place the item into a container, for example an oxygen tank into a diving suit - if (Inventory.IsMouseOnSlot(slot)) + bool spawnedItem = false; + for (var i = 0; i < inv.Capacity; i++) { - var newItem = new Item(itemPrefab, Vector2.Zero, MainSub); + var slot = inv.visualSlots[i]; + var itemContainer = inv.GetItemAt(i)?.GetComponent(); - if (inv.CanBePutInSlot(itemPrefab, i, condition: null)) + // check if the slot is empty or if we can place the item into a container, for example an oxygen tank into a diving suit + if (Inventory.IsMouseOnSlot(slot)) { - bool placedItem = inv.TryPutItem(newItem, i, false, true, dummyCharacter); - spawnedItem |= placedItem; + var newItem = new Item(itemPrefab, Vector2.Zero, MainSub); - if (!placedItem) + if (inv.CanBePutInSlot(itemPrefab, i, condition: null)) { - newItem.Remove(); + bool placedItem = inv.TryPutItem(newItem, i, false, true, dummyCharacter); + spawnedItem |= placedItem; + + if (!placedItem) + { + newItem.Remove(); + } } - } - else if (itemContainer != null && itemContainer.Inventory.CanBePut(itemPrefab)) - { - bool placedItem = itemContainer.Inventory.TryPutItem(newItem, dummyCharacter); - spawnedItem |= placedItem; - - // try to place the item into the inventory of the item we are hovering over - if (!placedItem) + else if (itemContainer != null && itemContainer.Inventory.CanBePut(itemPrefab)) { - newItem.Remove(); + bool placedItem = itemContainer.Inventory.TryPutItem(newItem, dummyCharacter); + spawnedItem |= placedItem; + + // try to place the item into the inventory of the item we are hovering over + if (!placedItem) + { + newItem.Remove(); + } + else + { + slot.ShowBorderHighlight(GUIStyle.Green, 0.1f, 0.4f); + } } else { - slot.ShowBorderHighlight(GUIStyle.Green, 0.1f, 0.4f); + newItem.Remove(); + slot.ShowBorderHighlight(GUIStyle.Red, 0.1f, 0.4f); + } + + if (!newItem.Removed) + { + BulkItemBufferInUse = ItemAddMutex; + BulkItemBuffer.Add(new AddOrDeleteCommand(new List { newItem }, false)); + } + + if (!draggingMouse) + { + SoundPlayer.PlayUISound(spawnedItem ? GUISoundType.PickItem : GUISoundType.PickItemFail); } } - else - { - newItem.Remove(); - slot.ShowBorderHighlight(GUIStyle.Red, 0.1f, 0.4f); - } - - if (!newItem.Removed) - { - BulkItemBufferInUse = ItemAddMutex; - BulkItemBuffer.Add(new AddOrDeleteCommand(new List { newItem }, false)); - } - - if (!dragginMouse) - { - SoundPlayer.PlayUISound(spawnedItem ? GUISoundType.PickItem : GUISoundType.PickItemFail); - } } + break; } - break; - } // item assemblies case ItemAssemblyPrefab assemblyPrefab when PlayerInput.PrimaryMouseButtonClicked(): - { - bool spawnedItems = false; - for (var i = 0; i < inv.visualSlots.Length; i++) { - var slot = inv.visualSlots[i]; - var item = inv?.GetItemAt(i); - var itemContainer = item?.GetComponent(); - if (item == null && Inventory.IsMouseOnSlot(slot)) + bool spawnedItems = false; + for (var i = 0; i < inv.visualSlots.Length; i++) { - // load the items - var itemInstance = LoadItemAssemblyInventorySafe(assemblyPrefab); - - // counter for items that failed so we so we known that slot remained empty - var failedCount = 0; - - for (var j = 0; j < itemInstance.Count(); j++) + var slot = inv.visualSlots[i]; + var item = inv?.GetItemAt(i); + var itemContainer = item?.GetComponent(); + if (item == null && Inventory.IsMouseOnSlot(slot)) { - var newItem = itemInstance[j]; - var newSpot = i + j - failedCount; + // load the items + var itemInstance = LoadItemAssemblyInventorySafe(assemblyPrefab); - // try to find a valid slot to put the items - while (inv.visualSlots.Length > newSpot) + // counter for items that failed so we so we known that slot remained empty + var failedCount = 0; + + for (var j = 0; j < itemInstance.Count; j++) { - if (inv.GetItemAt(newSpot) == null) { break; } - newSpot++; - } + var newItem = itemInstance[j]; + var newSpot = i + j - failedCount; - // valid slot found - if (inv.visualSlots.Length > newSpot) - { - var placedItem = inv.TryPutItem(newItem, newSpot, false, true, dummyCharacter); - spawnedItems |= placedItem; - - if (!placedItem) + // try to find a valid slot to put the items + while (inv.visualSlots.Length > newSpot) { - failedCount++; - // delete the included items too so we don't get a popup asking if we want to keep them - newItem?.OwnInventory?.DeleteAllItems(); - newItem.Remove(); + if (inv.GetItemAt(newSpot) == null) { break; } + newSpot++; + } + + // valid slot found + if (inv.visualSlots.Length > newSpot) + { + var placedItem = inv.TryPutItem(newItem, newSpot, false, true, dummyCharacter); + spawnedItems |= placedItem; + + if (!placedItem) + { + failedCount++; + // delete the included items too so we don't get a popup asking if we want to keep them + newItem?.OwnInventory?.DeleteAllItems(); + newItem.Remove(); + } + } + else + { + var placedItem = inv.TryPutItem(newItem, dummyCharacter); + spawnedItems |= placedItem; + + // if our while loop didn't find a valid slot then let the inventory decide where to put it as a last resort + if (!placedItem) + { + // delete the included items too so we don't get a popup asking if we want to keep them + newItem?.OwnInventory?.DeleteAllItems(); + newItem.Remove(); + } } } - else + + List placedEntities = itemInstance.Where(it => !it.Removed).Cast().ToList(); + if (placedEntities.Any()) { - var placedItem = inv.TryPutItem(newItem, dummyCharacter); - spawnedItems |= placedItem; - - // if our while loop didn't find a valid slot then let the inventory decide where to put it as a last resort - if (!placedItem) - { - // delete the included items too so we don't get a popup asking if we want to keep them - newItem?.OwnInventory?.DeleteAllItems(); - newItem.Remove(); - } + BulkItemBufferInUse = ItemAddMutex; + BulkItemBuffer.Add(new AddOrDeleteCommand(placedEntities, false)); } } - - List placedEntities = itemInstance.Where(it => !it.Removed).Cast().ToList(); - if (placedEntities.Any()) - { - BulkItemBufferInUse = ItemAddMutex; - BulkItemBuffer.Add(new AddOrDeleteCommand(placedEntities, false)); - } } - } - SoundPlayer.PlayUISound(spawnedItems ? GUISoundType.PickItem : GUISoundType.PickItemFail); - break; - } + SoundPlayer.PlayUISound(spawnedItems ? GUISoundType.PickItem : GUISoundType.PickItemFail); + break; + } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Serialization/SerializableEntityEditor.cs b/Barotrauma/BarotraumaClient/ClientSource/Serialization/SerializableEntityEditor.cs index 8cd9300f5..38f110c95 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Serialization/SerializableEntityEditor.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Serialization/SerializableEntityEditor.cs @@ -571,16 +571,37 @@ namespace Barotrauma numberInput.MinValueFloat = editableAttribute.MinValueFloat; numberInput.MaxValueFloat = editableAttribute.MaxValueFloat; numberInput.DecimalsToDisplay = editableAttribute.DecimalCount; - numberInput.valueStep = editableAttribute.ValueStep; + numberInput.ValueStep = editableAttribute.ValueStep; + numberInput.ForceShowPlusMinusButtons = editableAttribute.ForceShowPlusMinusButtons; numberInput.FloatValue = value; - numberInput.OnValueChanged += (numInput) => + numberInput.OnValueChanged += numInput => { if (SetPropertyValue(property, entity, numInput.FloatValue)) { TrySendNetworkUpdate(entity, property); } }; + + // Lots of UI boilerplate to handle all(?) cases where the property's setter may be called + // and modify the input value (e.g. rotation value wrapping) + void HandleSetterModifyingInput(GUINumberInput numInput) + { + var inputFloatValue = numInput.FloatValue; + var resultingFloatValue = property.GetFloatValue(entity); + if (!MathUtils.NearlyEqual(resultingFloatValue, inputFloatValue)) + { + numInput.FloatValue = resultingFloatValue; + } + } + bool HandleSetterModifyingInputOnButtonPressed() { HandleSetterModifyingInput(numberInput); return true; } + bool HandleSetterModifyingInputOnButtonClicked(GUIButton _, object __) { HandleSetterModifyingInput(numberInput); return true; } + + numberInput.OnValueEntered += HandleSetterModifyingInput; + numberInput.PlusButton.OnPressed += HandleSetterModifyingInputOnButtonPressed; + numberInput.PlusButton.OnClicked += HandleSetterModifyingInputOnButtonClicked; + numberInput.MinusButton.OnPressed += HandleSetterModifyingInputOnButtonPressed; + numberInput.MinusButton.OnClicked += HandleSetterModifyingInputOnButtonClicked; refresh += () => { if (!numberInput.TextBox.Selected) { numberInput.FloatValue = (float)property.GetValue(entity); } @@ -859,7 +880,7 @@ namespace Barotrauma numberInput.MinValueFloat = editableAttribute.MinValueFloat; numberInput.MaxValueFloat = editableAttribute.MaxValueFloat; numberInput.DecimalsToDisplay = editableAttribute.DecimalCount; - numberInput.valueStep = editableAttribute.ValueStep; + numberInput.ValueStep = editableAttribute.ValueStep; if (i == 0) numberInput.FloatValue = value.X; @@ -930,7 +951,7 @@ namespace Barotrauma numberInput.MinValueFloat = editableAttribute.MinValueFloat; numberInput.MaxValueFloat = editableAttribute.MaxValueFloat; numberInput.DecimalsToDisplay = editableAttribute.DecimalCount; - numberInput.valueStep = editableAttribute.ValueStep; + numberInput.ValueStep = editableAttribute.ValueStep; if (i == 0) numberInput.FloatValue = value.X; @@ -1006,7 +1027,7 @@ namespace Barotrauma numberInput.MinValueFloat = editableAttribute.MinValueFloat; numberInput.MaxValueFloat = editableAttribute.MaxValueFloat; numberInput.DecimalsToDisplay = editableAttribute.DecimalCount; - numberInput.valueStep = editableAttribute.ValueStep; + numberInput.ValueStep = editableAttribute.ValueStep; if (i == 0) numberInput.FloatValue = value.X; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Sounds/OggSound.cs b/Barotrauma/BarotraumaClient/ClientSource/Sounds/OggSound.cs index f816a71a8..25ea1f177 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Sounds/OggSound.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Sounds/OggSound.cs @@ -16,7 +16,7 @@ namespace Barotrauma.Sounds private short[] sampleBuffer = Array.Empty(); private short[] muffleBuffer = Array.Empty(); - public OggSound(SoundManager owner, string filename, bool stream, XElement xElement) : base(owner, filename, + public OggSound(SoundManager owner, string filename, bool stream, ContentXElement xElement) : base(owner, filename, stream, true, xElement) { var reader = new VorbisReader(Filename); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Sounds/Sound.cs b/Barotrauma/BarotraumaClient/ClientSource/Sounds/Sound.cs index 8d2ea80c6..8c815b9c0 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Sounds/Sound.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Sounds/Sound.cs @@ -18,7 +18,7 @@ namespace Barotrauma.Sounds public readonly string Filename; - public readonly XElement XElement; + public readonly ContentXElement XElement; public readonly bool Stream; @@ -60,14 +60,14 @@ namespace Barotrauma.Sounds public float BaseNear; public float BaseFar; - public Sound(SoundManager owner, string filename, bool stream, bool streamsReliably, XElement xElement = null, bool getFullPath = true) + public Sound(SoundManager owner, string filename, bool stream, bool streamsReliably, ContentXElement xElement = null, bool getFullPath = true) { Owner = owner; Filename = getFullPath ? Path.GetFullPath(filename.CleanUpPath()).CleanUpPath() : filename; Stream = stream; StreamsReliably = streamsReliably; XElement = xElement; - sourcePoolIndex = XElement.GetAttributeEnum("sourcepool", SoundManager.SourcePoolIndex.Default); + sourcePoolIndex = XElement?.GetAttributeEnum("sourcepool", SoundManager.SourcePoolIndex.Default) ?? SoundManager.SourcePoolIndex.Default; BaseGain = 1.0f; BaseNear = 100.0f; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Sprite/Sprite.cs b/Barotrauma/BarotraumaClient/ClientSource/Sprite/Sprite.cs index e599e9c41..f76e97b23 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Sprite/Sprite.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Sprite/Sprite.cs @@ -111,7 +111,7 @@ namespace Barotrauma partial void LoadTexture(ref Vector4 sourceVector, ref bool shouldReturn) { - texture = LoadTexture(FilePath.Value, Compress); + texture = LoadTexture(FilePath.Value, Compress, contentPackage: SourceElement?.ContentPackage); if (texture == null) { @@ -175,7 +175,7 @@ namespace Barotrauma return; } texture.Dispose(); - texture = TextureLoader.FromFile(FilePath.Value, Compress); + texture = TextureLoader.FromFile(FilePath.Value, Compress, contentPackage: SourceElement?.ContentPackage); Identifier pathKey = FullPath.ToIdentifier(); if (textureRefCounts.ContainsKey(pathKey)) { @@ -195,7 +195,7 @@ namespace Barotrauma sourceRect = new Rectangle(0, 0, texture.Width, texture.Height); } - public static Texture2D LoadTexture(string file, bool compress = true) + public static Texture2D LoadTexture(string file, bool compress = true, ContentPackage contentPackage = null) { if (string.IsNullOrWhiteSpace(file)) { @@ -221,11 +221,11 @@ namespace Barotrauma if (!ToolBox.IsProperFilenameCase(file)) { #if DEBUG - DebugConsole.ThrowError("Texture file \"" + file + "\" has incorrect case!"); + DebugConsole.ThrowError("Texture file \"" + file + "\" has incorrect case!", contentPackage: contentPackage); #endif } - Texture2D newTexture = TextureLoader.FromFile(file, compress); + Texture2D newTexture = TextureLoader.FromFile(file, compress, contentPackage: contentPackage); lock (list) { if (!textureRefCounts.TryAdd(fullPath, @@ -284,17 +284,35 @@ namespace Barotrauma } } - public void DrawTiled(ISpriteBatch spriteBatch, Vector2 position, Vector2 targetSize, + public void DrawTiled(ISpriteBatch spriteBatch, Vector2 position, Vector2 targetSize, float rotation = 0f, Vector2? origin = null, Color? color = null, Vector2? startOffset = null, Vector2? textureScale = null, float? depth = null) { if (Texture == null) { return; } + + bool flipHorizontal = (effects & SpriteEffects.FlipHorizontally) != 0; + bool flipVertical = (effects & SpriteEffects.FlipVertically) != 0; + + float addedRotation = rotation + this.rotation; + if (flipHorizontal != flipVertical) { addedRotation = -addedRotation; } + + Vector2 advanceX = addedRotation == 0.0f ? Vector2.UnitX : new Vector2((float)Math.Cos(addedRotation), (float)Math.Sin(addedRotation)); + Vector2 advanceY = new Vector2(-advanceX.Y, advanceX.X); + //Init optional values Vector2 drawOffset = startOffset ?? Vector2.Zero; Vector2 scale = textureScale ?? Vector2.One; Color drawColor = color ?? Color.White; + Vector2 transformedOrigin = origin ?? Vector2.Zero; - bool flipHorizontal = (effects & SpriteEffects.FlipHorizontally) != 0; - bool flipVertical = (effects & SpriteEffects.FlipVertically) != 0; + transformedOrigin = advanceX * transformedOrigin.X + advanceY * transformedOrigin.Y; + + void drawSection(Vector2 slicePos, Rectangle sliceRect) + { + Vector2 transformedPos = slicePos - position; + transformedPos = advanceX * transformedPos.X + advanceY * transformedPos.Y; + transformedPos += position - transformedOrigin; + spriteBatch.Draw(texture, transformedPos, sliceRect, drawColor, addedRotation, Vector2.Zero, scale, effects, depth ?? this.depth); + } //wrap the drawOffset inside the sourceRect drawOffset.X = (drawOffset.X / scale.X) % sourceRect.Width; @@ -368,8 +386,8 @@ namespace Barotrauma { slicePos.Y += flippedDrawOffset.Y; } - - spriteBatch.Draw(texture, slicePos, sliceRect, drawColor, rotation, Vector2.Zero, scale, effects, depth ?? this.depth); + + drawSection(slicePos, sliceRect); currDrawPosition.X = slicePos.X + sliceWidth; } } @@ -416,7 +434,7 @@ namespace Barotrauma sliceRect.Y = SourceRect.Y; sliceRect.Height = (int)(sliceHeight / scale.Y); - spriteBatch.Draw(texture, slicePos, sliceRect, drawColor, rotation, Vector2.Zero, scale, effects, depth ?? this.depth); + drawSection(slicePos, sliceRect); currDrawPosition.Y = slicePos.Y + sliceHeight; } @@ -433,8 +451,7 @@ namespace Barotrauma } } - spriteBatch.Draw(texture, currDrawPosition, - texPerspective, drawColor, rotation, Vector2.Zero, scale, effects, depth ?? this.depth); + drawSection(currDrawPosition, texPerspective); currDrawPosition.Y += texPerspective.Height * scale.Y; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Steam/Lobby.cs b/Barotrauma/BarotraumaClient/ClientSource/Steam/Lobby.cs index 2748b691c..90c86e709 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Steam/Lobby.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Steam/Lobby.cs @@ -120,6 +120,10 @@ namespace Barotrauma.Steam currentLobby?.SetData("playstyle", serverSettings.PlayStyle.ToString()); currentLobby?.SetData("gamemode", GameMain.NetLobbyScreen?.SelectedMode?.Identifier.Value ?? ""); currentLobby?.SetData("language", serverSettings.Language.ToString()); + if (GameMain.NetLobbyScreen?.SelectedSub != null) + { + currentLobby?.SetData("submarine", GameMain.NetLobbyScreen.SelectedSub.Name); + } DebugConsole.Log("Lobby updated!"); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Utils/TextureLoader.cs b/Barotrauma/BarotraumaClient/ClientSource/Utils/TextureLoader.cs index 1fcbab5fb..3ab6a1a90 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Utils/TextureLoader.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Utils/TextureLoader.cs @@ -151,13 +151,13 @@ namespace Barotrauma output[outputOffset + 10] = (byte)((g2_565 << 5) | b2_565); } - public static Texture2D FromFile(string path, bool compress = true, bool mipmap = false) + public static Texture2D FromFile(string path, bool compress = true, bool mipmap = false, ContentPackage contentPackage = null) { using FileStream fileStream = File.OpenRead(path); - return FromStream(fileStream, path, compress, mipmap); + return FromStream(fileStream, path, compress, mipmap, contentPackage); } - public static Texture2D FromStream(System.IO.Stream stream, string path = null, bool compress = true, bool mipmap = false) + public static Texture2D FromStream(System.IO.Stream stream, string path = null, bool compress = true, bool mipmap = false, ContentPackage contentPackage = null) { try { @@ -176,7 +176,8 @@ namespace Barotrauma } else { - DebugConsole.AddWarning($"Could not compress a texture because the dimensions aren't a multiple of 4 (path: {path ?? "null"}, size: {width}x{height})"); + DebugConsole.AddWarning($"Could not compress a texture because the dimensions aren't a multiple of 4 (path: {path ?? "null"}, size: {width}x{height})", + contentPackage); } } diff --git a/Barotrauma/BarotraumaClient/LinuxClient.csproj b/Barotrauma/BarotraumaClient/LinuxClient.csproj index 9a1fe727a..e31bc063d 100644 --- a/Barotrauma/BarotraumaClient/LinuxClient.csproj +++ b/Barotrauma/BarotraumaClient/LinuxClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 1.1.18.1 + 1.2.1.0 Copyright © FakeFish 2018-2023 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/MacClient.csproj b/Barotrauma/BarotraumaClient/MacClient.csproj index 7c9514990..42698bdfd 100644 --- a/Barotrauma/BarotraumaClient/MacClient.csproj +++ b/Barotrauma/BarotraumaClient/MacClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 1.1.18.1 + 1.2.1.0 Copyright © FakeFish 2018-2023 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/WindowsClient.csproj b/Barotrauma/BarotraumaClient/WindowsClient.csproj index a388bc1f5..38e7e8161 100644 --- a/Barotrauma/BarotraumaClient/WindowsClient.csproj +++ b/Barotrauma/BarotraumaClient/WindowsClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 1.1.18.1 + 1.2.1.0 Copyright © FakeFish 2018-2023 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaServer/LinuxServer.csproj b/Barotrauma/BarotraumaServer/LinuxServer.csproj index 7fd3c7774..093a855f3 100644 --- a/Barotrauma/BarotraumaServer/LinuxServer.csproj +++ b/Barotrauma/BarotraumaServer/LinuxServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 1.1.18.1 + 1.2.1.0 Copyright © FakeFish 2018-2023 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/MacServer.csproj b/Barotrauma/BarotraumaServer/MacServer.csproj index 1f5c8ac99..04527ab69 100644 --- a/Barotrauma/BarotraumaServer/MacServer.csproj +++ b/Barotrauma/BarotraumaServer/MacServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 1.1.18.1 + 1.2.1.0 Copyright © FakeFish 2018-2023 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs b/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs index fd59111ec..532c057e7 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs @@ -34,8 +34,8 @@ namespace Barotrauma { if (!CheatsEnabled && IsCheat) { - NewMessage("Client \"" + client.Name + "\" attempted to use the command \"" + names[0] + "\". Cheats must be enabled using \"enablecheats\" before the command can be used.", Color.Red); - GameMain.Server.SendConsoleMessage("You need to enable cheats using the command \"enablecheats\" before you can use the command \"" + names[0] + "\".", client, Color.Red); + NewMessage("Client \"" + client.Name + "\" attempted to use the command \"" + Names[0] + "\". Cheats must be enabled using \"enablecheats\" before the command can be used.", Color.Red); + GameMain.Server.SendConsoleMessage("You need to enable cheats using the command \"enablecheats\" before you can use the command \"" + Names[0] + "\".", client, Color.Red); #if USE_STEAM NewMessage("Enabling cheats will disable Steam achievements during this play session.", Color.Red); @@ -317,7 +317,7 @@ namespace Barotrauma private static void AssignOnClientRequestExecute(string names, Action onClientRequestExecute) { - var matchingCommand = commands.Find(c => c.names.Intersect(names.Split('|')).Count() > 0); + var matchingCommand = commands.Find(c => c.Names.Intersect(names.Split('|').ToIdentifiers()).Any()); if (matchingCommand == null) { throw new Exception("AssignOnClientRequestExecute failed. Command matching the name(s) \"" + names + "\" not found."); @@ -654,8 +654,10 @@ namespace Barotrauma ShowQuestionPrompt("Console command permissions to grant to \"" + client.Name + "\"? You may enter multiple commands separated with a space, or \"all\" to allow using any console command.", (commandsStr) => { - string[] splitCommands = commandsStr.Split(' '); - bool giveAll = splitCommands.Length > 0 && splitCommands[0].Equals("all", StringComparison.OrdinalIgnoreCase); + Identifier[] splitCommands = commandsStr.Split(' ') + .Select(s => s.Trim()) + .ToIdentifiers().ToArray(); + bool giveAll = splitCommands.Length > 0 && splitCommands[0] == "all"; List grantedCommands = new List(); if (giveAll) @@ -664,13 +666,12 @@ namespace Barotrauma } else { - for (int i = 0; i < splitCommands.Length; i++) + foreach (Identifier command in splitCommands) { - splitCommands[i] = splitCommands[i].Trim().ToLowerInvariant(); - Command matchingCommand = commands.Find(c => c.names.Contains(splitCommands[i])); + Command matchingCommand = commands.Find(c => c.Names.Contains(command)); if (matchingCommand == null) { - ThrowError("Could not find the command \"" + splitCommands[i] + "\"!"); + ThrowError("Could not find the command \"" + command + "\"!"); } else { @@ -688,7 +689,7 @@ namespace Barotrauma } else if (grantedCommands.Count > 0) { - NewMessage("Gave the client \"" + client.Name + "\" the permission to use console commands " + string.Join(", ", grantedCommands.Select(c => c.names[0])) + ".", Color.White); + NewMessage("Gave the client \"" + client.Name + "\" the permission to use console commands " + string.Join(", ", grantedCommands.Select(c => c.Names[0])) + ".", Color.White); } }, args, 1); @@ -717,22 +718,23 @@ namespace Barotrauma ShowQuestionPrompt("Console command permissions to revoke from \"" + client.Name + "\"? You may enter multiple commands separated with a space.", (commandsStr) => { - string[] splitCommands = commandsStr.Split(' '); + Identifier[] splitCommands = commandsStr.Split(' ') + .Select(s => s.Trim()) + .ToIdentifiers().ToArray(); List revokedCommands = new List(); - bool revokeAll = splitCommands.Length > 0 && splitCommands[0].Equals("all", StringComparison.OrdinalIgnoreCase); + bool revokeAll = splitCommands.Length > 0 && splitCommands[0] == "all"; if (revokeAll) { revokedCommands.AddRange(commands); } else { - for (int i = 0; i < splitCommands.Length; i++) + foreach (Identifier command in splitCommands) { - splitCommands[i] = splitCommands[i].Trim().ToLowerInvariant(); - Command matchingCommand = commands.Find(c => c.names.Contains(splitCommands[i])); + Command matchingCommand = commands.Find(c => c.Names.Contains(command)); if (matchingCommand == null) { - ThrowError("Could not find the command \"" + splitCommands[i] + "\"!"); + ThrowError("Could not find the command \"" + command + "\"!"); } else { @@ -749,7 +751,7 @@ namespace Barotrauma } else if (revokedCommands.Any()) { - NewMessage("Revoked \"" + client.Name + "\"'s permission to use the console commands " + string.Join(", ", revokedCommands.Select(c => c.names[0])) + ".", Color.White); + NewMessage("Revoked \"" + client.Name + "\"'s permission to use the console commands " + string.Join(", ", revokedCommands.Select(c => c.Names[0])) + ".", Color.White); } }, args, 1); }); @@ -793,7 +795,7 @@ namespace Barotrauma NewMessage("Permitted console commands:", Color.White); foreach (Command permittedCommand in client.PermittedConsoleCommands) { - NewMessage(" - " + permittedCommand.names[0], Color.White); + NewMessage(" - " + permittedCommand.Names[0], Color.White); } } } @@ -1156,6 +1158,23 @@ namespace Barotrauma } ); + commands.Add(new Command("debugjobassignment", "debugjobassignment: Shows information about how jobs were assigned for the most recent round.", (string[] args) => + { + if (GameMain.Server == null) { return; } + foreach (var debugMsg in GameMain.Server.JobAssignmentDebugLog) + { + NewMessage(debugMsg, Color.Cyan); + } + })); + AssignOnClientRequestExecute("debugjobassignment", (Client client, Vector2 cursorWorldPos, string[] args) => + { + if (GameMain.Server == null) { return; } + foreach (var debugMsg in GameMain.Server.JobAssignmentDebugLog) + { + GameMain.Server.SendConsoleMessage(debugMsg, client); + } + }); + commands.Add(new Command("setpassword|setserverpassword|password", "setpassword [password]: Changes the password of the server that's being hosted.", (string[] args) => { if (GameMain.Server == null) { return; } @@ -1432,7 +1451,6 @@ namespace Barotrauma GameMain.Server.PrintSenderTransters(); })); - commands.Add(new Command("forcelocationtypechange", "", (string[] args) => { if (GameMain.Server == null || GameMain.GameSession?.Campaign == null) { return; } @@ -1568,6 +1586,19 @@ namespace Barotrauma GameMain.Server.SendChatMessage(ToolBox.RandomSeed(msgLength), ChatMessageType.Default); } })); + + commands.Add(new Command("multiclienttestmode", "Makes the server assign campaign characters based on the name of the client and the character, as opposed to just checking the account ID or address. Useful for testing the campaign with multiple clients running locally.", (string[] args) => + { + CharacterCampaignData.RequireClientNameMatch = !CharacterCampaignData.RequireClientNameMatch; + if (CharacterCampaignData.RequireClientNameMatch) + { + NewMessage("Enabled RequireClientNameMatch (clients' names must match their campaign character)"); + } + else + { + NewMessage("Disabled RequireClientNameMatch"); + } + })); #endif AssignOnClientRequestExecute( @@ -1751,17 +1782,32 @@ namespace Barotrauma { Submarine.MainSub.SetPosition(Level.Loaded.StartPosition - Vector2.UnitY * Submarine.MainSub.Borders.Height); } - else + else if (args[0].Equals("end", StringComparison.OrdinalIgnoreCase)) { Submarine.MainSub.SetPosition(Level.Loaded.EndPosition - Vector2.UnitY * Submarine.MainSub.Borders.Height); } + else if (args[0].Equals("endoutpost", StringComparison.OrdinalIgnoreCase)) + { + Submarine.MainSub.SetPosition(Level.Loaded.EndExitPosition - Vector2.UnitY * Submarine.MainSub.Borders.Height); + var submarineDockingPort = DockingPort.List.FirstOrDefault(d => d.Item.Submarine == Submarine.MainSub); + if (Level.Loaded?.EndOutpost == null) + { + NewMessage("Can't teleport the sub to the end outpost (no outpost at the end of the level).", Color.Red); + return; + } + var outpostDockingPort = DockingPort.List.FirstOrDefault(d => d.Item.Submarine == Level.Loaded.EndOutpost); + if (submarineDockingPort != null && outpostDockingPort != null) + { + submarineDockingPort.Dock(outpostDockingPort); + } + } } ); AssignOnClientRequestExecute("togglecampaignteleport", (Client client, Vector2 cursorWorldPos, string[] args) => { - if (!(GameMain.GameSession?.Campaign is MultiPlayerCampaign mpCampaign)) + if (GameMain.GameSession?.Campaign is not MultiPlayerCampaign mpCampaign) { GameMain.Server.SendConsoleMessage("No campaign active.", client, Color.Red); return; @@ -2171,21 +2217,21 @@ namespace Barotrauma } List grantedCommands = new List(); - string[] splitCommands = args.Skip(1).ToArray(); - bool giveAll = splitCommands.Length > 0 && splitCommands[0].Equals("all", StringComparison.OrdinalIgnoreCase); + Identifier[] splitCommands = args.Skip(1) + .Select(s => s.Trim()).ToIdentifiers().ToArray(); + bool giveAll = splitCommands.Length > 0 && splitCommands[0] == "all"; if (giveAll) { grantedCommands.AddRange(commands); } else { - for (int i = 0; i < splitCommands.Length; i++) + foreach (Identifier command in splitCommands) { - splitCommands[i] = splitCommands[i].Trim().ToLowerInvariant(); - Command matchingCommand = commands.Find(c => c.names.Contains(splitCommands[i])); + Command matchingCommand = commands.Find(c => c.Names.Contains(command)); if (matchingCommand == null) { - GameMain.Server.SendConsoleMessage("Could not find the command \"" + splitCommands[i] + "\"!", senderClient, Color.Red); + GameMain.Server.SendConsoleMessage("Could not find the command \"" + command + "\"!", senderClient, Color.Red); } else { @@ -2204,7 +2250,7 @@ namespace Barotrauma } else if (grantedCommands.Count > 0) { - GameMain.Server.SendConsoleMessage("Gave the client \"" + client.Name + "\" the permission to use console commands " + string.Join(", ", grantedCommands.Select(c => c.names[0])) + ".", senderClient); + GameMain.Server.SendConsoleMessage("Gave the client \"" + client.Name + "\" the permission to use console commands " + string.Join(", ", grantedCommands.Select(c => c.Names[0])) + ".", senderClient); } } ); @@ -2227,21 +2273,21 @@ namespace Barotrauma return; } List revokedCommands = new List(); - string[] splitCommands = args.Skip(1).ToArray(); - bool revokeAll = splitCommands.Length > 0 && splitCommands[0].Equals("all", StringComparison.OrdinalIgnoreCase); + Identifier[] splitCommands = args.Skip(1) + .Select(s => s.Trim()).ToIdentifiers().ToArray(); + bool revokeAll = splitCommands.Length > 0 && splitCommands[0] == "all"; if (revokeAll) { revokedCommands.AddRange(commands); } else { - for (int i = 0; i < splitCommands.Length; i++) + foreach (Identifier command in splitCommands) { - splitCommands[i] = splitCommands[i].Trim().ToLowerInvariant(); - Command matchingCommand = commands.Find(c => c.names.Contains(splitCommands[i])); + Command matchingCommand = commands.Find(c => c.Names.Contains(command)); if (matchingCommand == null) { - GameMain.Server.SendConsoleMessage("Could not find the command \"" + splitCommands[i] + "\"!", senderClient, Color.Red); + GameMain.Server.SendConsoleMessage("Could not find the command \"" + command + "\"!", senderClient, Color.Red); } else { @@ -2256,14 +2302,14 @@ namespace Barotrauma client.RemovePermission(ClientPermissions.ConsoleCommands); } GameMain.Server.UpdateClientPermissions(client); - GameMain.Server.SendConsoleMessage("Revoked \"" + client.Name + "\"'s permission to use the console commands " + string.Join(", ", revokedCommands.Select(c => c.names[0])) + ".", senderClient); + GameMain.Server.SendConsoleMessage("Revoked \"" + client.Name + "\"'s permission to use the console commands " + string.Join(", ", revokedCommands.Select(c => c.Names[0])) + ".", senderClient); if (revokeAll) { GameMain.Server.SendConsoleMessage("Revoked \"" + client.Name + "\"'s permission to use console commands.", senderClient); } else if (revokedCommands.Count > 0) { - GameMain.Server.SendConsoleMessage("Revoked \"" + client.Name + "\"'s permission to use the console commands " + string.Join(", ", revokedCommands.Select(c => c.names[0])) + ".", senderClient); + GameMain.Server.SendConsoleMessage("Revoked \"" + client.Name + "\"'s permission to use the console commands " + string.Join(", ", revokedCommands.Select(c => c.Names[0])) + ".", senderClient); } } ); @@ -2308,7 +2354,7 @@ namespace Barotrauma GameMain.Server.SendConsoleMessage("Permitted console commands:", senderClient); foreach (Command permittedCommand in client.PermittedConsoleCommands) { - GameMain.Server.SendConsoleMessage(" - " + permittedCommand.names[0], senderClient); + GameMain.Server.SendConsoleMessage(" - " + permittedCommand.Names[0], senderClient); } } } @@ -2585,10 +2631,10 @@ namespace Barotrauma } string[] splitCommand = ToolBox.SplitCommand(command); - Command matchingCommand = commands.Find(c => c.names.Contains(splitCommand[0].ToLowerInvariant())); + Command matchingCommand = commands.Find(c => c.Names.Contains(splitCommand[0].ToIdentifier())); if (matchingCommand != null && !client.PermittedConsoleCommands.Contains(matchingCommand) && client.Connection != GameMain.Server.OwnerConnection) { - GameMain.Server.SendConsoleMessage("You are not permitted to use the command\"" + matchingCommand.names[0] + "\"!", client, Color.Red); + GameMain.Server.SendConsoleMessage("You are not permitted to use the command\"" + matchingCommand.Names[0] + "\"!", client, Color.Red); GameServer.Log(GameServer.ClientLogName(client) + " attempted to execute the console command \"" + command + "\" without a permission to use the command.", ServerLog.MessageType.ConsoleUsage); return; } @@ -2612,14 +2658,14 @@ namespace Barotrauma } catch (Exception e) { - ThrowError("Executing the command \"" + matchingCommand.names[0] + "\" by request from \"" + GameServer.ClientLogName(client) + "\" failed.", e); + ThrowError("Executing the command \"" + matchingCommand.Names[0] + "\" by request from \"" + GameServer.ClientLogName(client) + "\" failed.", e); } } static partial void ShowHelpMessage(Command command) { - NewMessage(command.names[0], Color.Cyan); - NewMessage(command.help, Color.Gray); + NewMessage(command.Names[0].Value, Color.Cyan); + NewMessage(command.Help, Color.Gray); } } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Events/EventActions/EventLogAction.cs b/Barotrauma/BarotraumaServer/ServerSource/Events/EventActions/EventLogAction.cs index 976984e76..66e28c861 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Events/EventActions/EventLogAction.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Events/EventActions/EventLogAction.cs @@ -27,7 +27,8 @@ partial class EventLogAction : EventAction } else { - DebugConsole.AddWarning($"{target} is not a valid target for an EventLogAction. The target should be a character."); + DebugConsole.AddWarning($"{target} is not a valid target for an EventLogAction. The target should be a character.", + ParentEvent.Prefab.ContentPackage); } } if (eventLog.TryAddEntry(ParentEvent.Prefab.Identifier, Id, displayText, targetClients) && ShowInServerLog) diff --git a/Barotrauma/BarotraumaServer/ServerSource/GameMain.cs b/Barotrauma/BarotraumaServer/ServerSource/GameMain.cs index d21e80fbd..7b2abfa96 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/GameMain.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/GameMain.cs @@ -118,26 +118,13 @@ namespace Barotrauma private void CheckContentPackage() { - //TODO: reimplement using only core package? - /*foreach (ContentPackage contentPackage in Config.AllEnabledPackages) + if (Version < VanillaContent.GameVersion) { - var exePaths = contentPackage.GetFilesOfType(ContentType.ServerExecutable); - if (exePaths.Count() > 0 && AppDomain.CurrentDomain.FriendlyName != exePaths.First()) - { - DebugConsole.NewMessage(AppDomain.CurrentDomain.FriendlyName); - DebugConsole.ShowQuestionPrompt(TextManager.GetWithVariables("IncorrectExe", new string[2] { "[selectedpackage]", "[exename]" }, new string[2] { contentPackage.Name, exePaths.First() }), - (option) => - { - if (option.ToLower() == "y" || option.ToLower() == "yes") - { - string fullPath = Path.GetFullPath(exePaths.First()); - ToolBox.OpenFileWithShell(fullPath); - ShouldRun = false; - } - }); - break; - } - }*/ + DebugConsole.ThrowError( + TextManager.GetWithVariables("versionmismatchwarning", + ("[gameversion]", Version.ToString()), + ("[contentversion]", VanillaContent.GameVersion.ToString()))); + } } public void StartServer() diff --git a/Barotrauma/BarotraumaServer/ServerSource/GameSession/CargoManager.cs b/Barotrauma/BarotraumaServer/ServerSource/GameSession/CargoManager.cs index 79597042e..f3737955a 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/GameSession/CargoManager.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/GameSession/CargoManager.cs @@ -2,27 +2,12 @@ using System.Collections.Generic; using System.Linq; using Barotrauma.Networking; +using System.Text; namespace Barotrauma { partial class CargoManager { - public void SellBackPurchasedItems(Identifier storeIdentifier, List itemsToSell, Client client) - { - // Check all the prices before starting the transaction to make sure the modifiers stay the same for the whole transaction - var buyValues = GetBuyValuesAtCurrentLocation(storeIdentifier, itemsToSell.Select(i => i.ItemPrefab)); - var store = Location.GetStore(storeIdentifier); - if (store == null) { return; } - var storeSpecificItems = GetPurchasedItems(storeIdentifier); - foreach (var item in itemsToSell) - { - var itemValue = item.Quantity * buyValues[item.ItemPrefab]; - store.Balance -= itemValue; - campaign.GetWallet(client).Give(itemValue); - storeSpecificItems?.Remove(item); - } - } - public void BuyBackSoldItems(Identifier storeIdentifier, List itemsToBuy, Client client) { var store = Location.GetStore(storeIdentifier); @@ -80,6 +65,21 @@ namespace Barotrauma OnSoldItemsChanged?.Invoke(this); } + public void LogNewItemPurchases(Identifier storeIdentifier, List newItems, Client client) + { + StringBuilder sb = new StringBuilder(); + int price = 0; + Dictionary buyValues = GetBuyValuesAtCurrentLocation(storeIdentifier, newItems.Select(i => i.ItemPrefab)); + foreach (PurchasedItem item in newItems) + { + int itemValue = item.Quantity * buyValues[item.ItemPrefab]; + GameAnalyticsManager.AddMoneySpentEvent(itemValue, GameAnalyticsManager.MoneySink.Store, item.ItemPrefab.Identifier.Value); + sb.Append($"\n - {item.ItemPrefab.Name} x{item.Quantity}"); + price += itemValue; + } + GameServer.Log($"{NetworkMember.ClientLogName(client, client?.Name ?? "Unknown")} purchased {newItems.Count} item(s) for {TextManager.FormatCurrency(price)}{sb.ToString()}", ServerLog.MessageType.Money); + } + public void ClearSoldItemsProjSpecific() { SoldItems.Clear(); diff --git a/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/CampaignMode.cs b/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/CampaignMode.cs index f3d53216a..7aa80a84e 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/CampaignMode.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/CampaignMode.cs @@ -1,5 +1,4 @@ -using Barotrauma.Extensions; -using Barotrauma.Networking; +using Barotrauma.Networking; namespace Barotrauma { @@ -27,6 +26,15 @@ namespace Barotrauma AnyOneAllowedToManageCampaign(permissions); } + public static bool AllowImmediateItemDelivery(Client client) + { + if (client == null || GameMain.Server == null) { return false; } + return + GameMain.Server.ServerSettings.AllowImmediateItemDelivery || + client.HasPermission(ClientPermissions.ManageCampaign) || + client.Connection == GameMain.Server.OwnerConnection; + } + public static bool AllowedToManageWallets(Client client) { return AllowedToManageCampaign(client, ClientPermissions.ManageMoney); diff --git a/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/CharacterCampaignData.cs b/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/CharacterCampaignData.cs index ddc0ae1d5..5b2daa59b 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/CharacterCampaignData.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/CharacterCampaignData.cs @@ -6,6 +6,14 @@ namespace Barotrauma { partial class CharacterCampaignData { +#if DEBUG + /// + /// If enabled, client names must match the name of the character. Useful for testing the campaign with multiple clients running locally: + /// without this, the clients would all get assigned the same character due to all of them having the same AccountId or Address. + /// + public static bool RequireClientNameMatch = false; +#endif + public bool HasSpawned; public bool HasItemData @@ -76,7 +84,7 @@ namespace Barotrauma { case "character": case "characterinfo": - CharacterInfo = new CharacterInfo(subElement); + CharacterInfo = new CharacterInfo(new ContentXElement(contentPackage: null, subElement)); break; case "inventory": itemData = subElement; @@ -103,6 +111,12 @@ namespace Barotrauma } else { +#if DEBUG + if (RequireClientNameMatch) + { + return ClientAddress == client.Connection.Endpoint.Address && client.Name == Name; + } +#endif return ClientAddress == client.Connection.Endpoint.Address; } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs b/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs index e1b3de615..69a72d1b7 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs @@ -806,7 +806,7 @@ namespace Barotrauma UInt16 itemToRemoveID = msg.ReadUInt16(); Identifier itemToInstallIdentifier = msg.ReadIdentifier(); ItemPrefab itemToInstall = itemToInstallIdentifier.IsEmpty ? null : ItemPrefab.Find(string.Empty, itemToInstallIdentifier); - if (!(Entity.FindEntityByID(itemToRemoveID) is Item itemToRemove)) { continue; } + if (Entity.FindEntityByID(itemToRemoveID) is not Item itemToRemove) { continue; } purchasedItemSwaps.Add(new PurchasedItemSwap(itemToRemove, itemToInstall)); } @@ -894,7 +894,7 @@ namespace Barotrauma int availableQuantity = map.CurrentLocation.Stores[store.Key].Stock.Find(s => s.ItemPrefab == item.ItemPrefab)?.Quantity ?? 0; int alreadyPurchasedQuantity = CargoManager.GetBuyCrateItem(store.Key, item.ItemPrefab)?.Quantity ?? 0 + - CargoManager.GetPurchasedItem(store.Key, item.ItemPrefab)?.Quantity ?? 0; + CargoManager.GetPurchasedItemCount(store.Key, item.ItemPrefab); item.Quantity = MathHelper.Clamp(item.Quantity, 0, availableQuantity - alreadyPurchasedQuantity); CargoManager.ModifyItemQuantityInBuyCrate(store.Key, item.ItemPrefab, item.Quantity, sender); } @@ -905,9 +905,41 @@ namespace Barotrauma { prevPurchasedItems.Add(kvp.Key, new List(kvp.Value)); } - foreach (var kvp in prevPurchasedItems) + + foreach (var storeId in purchasedItems.Keys) { - CargoManager.SellBackPurchasedItems(kvp.Key, kvp.Value, sender); + DebugConsole.Log($"Purchased items ({storeId}):\n"); + if (prevPurchasedItems.TryGetValue(storeId, out var alreadyPurchased)) + { + var delivered = alreadyPurchased.Where(it => it.Delivered); + var notDelivered = alreadyPurchased.Where(it => !it.Delivered); + if (delivered.Any()) + { + DebugConsole.Log($" Already delivered:\n" + string.Concat(delivered.Select(it => $" - {it.ItemPrefab.Name} (x{it.Quantity})"))); + } + if (notDelivered.Any()) + { + DebugConsole.Log($" Already purchased:\n" + string.Concat(notDelivered.Where(it => !it.Delivered).Select(it => $" - {it.ItemPrefab.Name} (x{it.Quantity})"))); + } + } + DebugConsole.Log($" New purchases:"); + foreach (var purchasedItem in purchasedItems[storeId]) + { + if (purchasedItem.Delivered) { continue; } + int quantity = purchasedItem.Quantity; + if (alreadyPurchased != null) + { + quantity -= alreadyPurchased.Where(it => it.DeliverImmediately == purchasedItem.DeliverImmediately && it.ItemPrefab == purchasedItem.ItemPrefab).Sum(it => it.Quantity); + } + if (quantity > 0) + { + DebugConsole.Log($" - {purchasedItem.ItemPrefab.Name} (x{quantity})"); + } + } + } + foreach (var storeId in soldItems.Keys) + { + DebugConsole.Log($"Sold items:\n" + string.Concat(soldItems[storeId].Select(it => $" - {it.ItemPrefab.Name}"))); } foreach (var kvp in purchasedItems) @@ -916,17 +948,23 @@ namespace Barotrauma var purchasedItemList = kvp.Value; foreach (var purchasedItem in purchasedItemList) { + int desiredQuantity = purchasedItem.Quantity; + if (prevPurchasedItems.TryGetValue(storeId, out var alreadyPurchasedList) && + alreadyPurchasedList.FirstOrDefault(p => p.ItemPrefab == purchasedItem.ItemPrefab) is { } alreadyPurchased) + { + desiredQuantity -= alreadyPurchased.Quantity; + } int availableQuantity = map.CurrentLocation.Stores[storeId].Stock.Find(s => s.ItemPrefab == purchasedItem.ItemPrefab)?.Quantity ?? 0; - purchasedItem.Quantity = Math.Min(purchasedItem.Quantity, availableQuantity); - } - CargoManager.PurchaseItems(storeId, purchasedItemList, false, sender); + purchasedItem.Quantity = Math.Min(desiredQuantity, availableQuantity); + } + CargoManager.PurchaseItems(storeId, purchasedItemList, removeFromCrate: false, client: sender); } foreach (var (storeIdentifier, items) in CargoManager.PurchasedItems) { if (!prevPurchasedItems.ContainsKey(storeIdentifier)) { - CargoManager.OnNewItemsPurchased(storeIdentifier, items, sender); + CargoManager.LogNewItemPurchases(storeIdentifier, items, sender); continue; } @@ -941,7 +979,6 @@ namespace Barotrauma newItems.Add(item); continue; } - if (matching.Quantity < item.Quantity) { newItems.Add(new PurchasedItem(item.ItemPrefab, item.Quantity - matching.Quantity, sender)); @@ -950,7 +987,7 @@ namespace Barotrauma if (newItems.Any()) { - CargoManager.OnNewItemsPurchased(storeIdentifier, newItems, sender); + CargoManager.LogNewItemPurchases(storeIdentifier, newItems, sender); } } @@ -1015,7 +1052,7 @@ namespace Barotrauma UpgradeManager.PurchaseUpgrade(prefab, category, client: sender); // unstable logging - int price = prefab.Price.GetBuyPrice(UpgradeManager.GetUpgradeLevel(prefab, category), Map?.CurrentLocation, characterList); + int price = prefab.Price.GetBuyPrice(prefab, UpgradeManager.GetUpgradeLevel(prefab, category), Map?.CurrentLocation, characterList); int level = UpgradeManager.GetUpgradeLevel(prefab, category); GameServer.Log($"SERVER: Purchased level {level} {category.Identifier}.{prefab.Identifier} for {price}", ServerLog.MessageType.ServerMessage); } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Signal/CircuitBox.cs b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Signal/CircuitBox.cs index 010111c87..b0b24e75c 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Signal/CircuitBox.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Signal/CircuitBox.cs @@ -1,11 +1,11 @@ #nullable enable +using Barotrauma.Networking; +using Microsoft.Xna.Framework; using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; -using Barotrauma.Networking; -using Microsoft.Xna.Framework; namespace Barotrauma.Items.Components { @@ -138,7 +138,7 @@ namespace Barotrauma.Items.Components return; } - bool result = AddComponentInternal(id, prefab, resource.Prefab, data.Position, it => + bool result = AddComponentInternal(id, prefab, resource.Prefab, data.Position, c.Character, it => { CreateServerEvent(new CircuitBoxServerCreateComponentEvent(it.ID, resource.Prefab.UintIdentifier, id, data.Position)); }); @@ -304,7 +304,8 @@ namespace Barotrauma.Items.Components private void ThrowError(string message, Client c) { - DebugConsole.ThrowError(message); + DebugConsole.ThrowError(message, + contentPackage: item.Prefab.ContentPackage); SendToClient(CircuitBoxOpcode.Error, new CircuitBoxErrorEvent(message), c); } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Items/Inventory.cs b/Barotrauma/BarotraumaServer/ServerSource/Items/Inventory.cs index a063286d5..d2afe45b1 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Items/Inventory.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Items/Inventory.cs @@ -110,7 +110,8 @@ namespace Barotrauma (pickable.IsAttached && !pickable.PickingDone) || item.AllowedSlots.None()) { - DebugConsole.AddWarning($"Client {c.Name} tried to pick up a non-pickable item \"{item}\" (parent inventory: {item.ParentInventory?.Owner.ToString() ?? "null"})"); + DebugConsole.AddWarning($"Client {c.Name} tried to pick up a non-pickable item \"{item}\" (parent inventory: {item.ParentInventory?.Owner.ToString() ?? "null"})", + item.Prefab.ContentPackage); continue; } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Map/Hull.cs b/Barotrauma/BarotraumaServer/ServerSource/Map/Hull.cs index bdd772286..814152a26 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Map/Hull.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Map/Hull.cs @@ -124,7 +124,7 @@ namespace Barotrauma out NetworkFireSource[] newFireSources); if (!c.HasPermission(ClientPermissions.ConsoleCommands) || - !c.PermittedConsoleCommands.Any(command => command.names.Contains("fire") || command.names.Contains("editfire"))) + !c.PermittedConsoleCommands.Any(command => command.Names.Contains("fire".ToIdentifier()) || command.Names.Contains("editfire".ToIdentifier()))) { return; } @@ -138,7 +138,7 @@ namespace Barotrauma var newFire = i < FireSources.Count ? FireSources[i] : - new FireSource(Submarine == null ? pos : pos + Submarine.Position, null, true); + new FireSource(Submarine == null ? pos : pos + Submarine.Position, sourceCharacter: null, isNetworkMessage: true); newFire.Position = pos; newFire.Size = new Vector2(size, newFire.Size.Y); diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/ChatMessage.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/ChatMessage.cs index e9216ab7b..93151a357 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/ChatMessage.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/ChatMessage.cs @@ -80,58 +80,10 @@ namespace Barotrauma.Networking { c.LastSentChatMessages.RemoveRange(0, c.LastSentChatMessages.Count - 10); } - - float similarity = 0.0f; - for (int i = 0; i < c.LastSentChatMessages.Count; i++) - { - float closeFactor = 1.0f / (c.LastSentChatMessages.Count - i); - - if (string.IsNullOrEmpty(txt)) - { - similarity += closeFactor; - } - else - { - int levenshteinDist = ToolBox.LevenshteinDistance(txt, c.LastSentChatMessages[i]); - similarity += Math.Max((txt.Length - levenshteinDist) / (float)txt.Length * closeFactor, 0.0f); - } - } - //order/report messages can be sent a little faster than normal messages without triggering the spam filter - if (orderMsg != null) - { - similarity *= 0.25f; - } - - bool isSpamExempt = RateLimiter.IsExempt(c); - - if (similarity + c.ChatSpamSpeed > 5.0f && !isSpamExempt) - { - GameMain.Server.KarmaManager.OnSpamFilterTriggered(c); - - c.ChatSpamCount++; - if (c.ChatSpamCount > 3) - { - //kick for spamming too much - GameMain.Server.KickClient(c, TextManager.Get("SpamFilterKicked").Value); - } - else - { - ChatMessage denyMsg = Create("", TextManager.Get("SpamFilterBlocked").Value, ChatMessageType.Server, null); - c.ChatSpamTimer = 10.0f; - GameMain.Server.SendDirectChatMessage(denyMsg, c); - } - return; - } - - c.ChatSpamSpeed += similarity + 0.5f; - - if (c.ChatSpamTimer > 0.0f && !isSpamExempt) - { - ChatMessage denyMsg = Create("", TextManager.Get("SpamFilterBlocked").Value, ChatMessageType.Server, null); - c.ChatSpamTimer = 10.0f; - GameMain.Server.SendDirectChatMessage(denyMsg, c); - return; - } + //order/report messages can be sent a little faster than normal messages without triggering the spam filter; + float similarityMultiplier = orderMsg != null ? 0.25f : 1.0f; + HandleSpamFilter(c, txt, out bool flaggedAsSpam, similarityMultiplier); + if (flaggedAsSpam) { return; } if (type == ChatMessageType.Order) { @@ -177,6 +129,65 @@ namespace Barotrauma.Networking } } + /// + /// Increase the client's chat spam speed and check whether the spam filter should kick in + /// + public static void HandleSpamFilter(Client c, string messageText, out bool flaggedAsSpam, float similarityMultiplier = 1.0f) + { + float similarity = 0.0f; + for (int i = 0; i < c.LastSentChatMessages.Count; i++) + { + float closeFactor = 1.0f / (c.LastSentChatMessages.Count - i); + + if (string.IsNullOrEmpty(messageText)) + { + similarity += closeFactor; + } + else + { + int levenshteinDist = ToolBox.LevenshteinDistance(messageText, c.LastSentChatMessages[i]); + similarity += Math.Max((messageText.Length - levenshteinDist) / (float)messageText.Length * closeFactor, 0.0f); + } + } + + similarity *= similarityMultiplier; + + bool isSpamExempt = RateLimiter.IsExempt(c); + + if (similarity + c.ChatSpamSpeed > 5.0f && !isSpamExempt) + { + GameMain.Server.KarmaManager.OnSpamFilterTriggered(c); + + c.ChatSpamCount++; + if (c.ChatSpamCount > 3) + { + //kick for spamming too much + GameMain.Server.KickClient(c, TextManager.Get("SpamFilterKicked").Value); + } + else + { + ChatMessage denyMsg = Create("", TextManager.Get("SpamFilterBlocked").Value, ChatMessageType.Server, null); + c.ChatSpamTimer = 10.0f; + GameMain.Server.SendDirectChatMessage(denyMsg, c); + } + flaggedAsSpam = true; + return; + } + + c.ChatSpamSpeed += similarity + 0.5f; + + if (c.ChatSpamTimer > 0.0f && !isSpamExempt) + { + ChatMessage denyMsg = Create("", TextManager.Get("SpamFilterBlocked").Value, ChatMessageType.Server, null); + c.ChatSpamTimer = 10.0f; + GameMain.Server.SendDirectChatMessage(denyMsg, c); + flaggedAsSpam = true; + return; + } + + flaggedAsSpam = false; + } + public int EstimateLengthBytesServer(Client c) { int length = 1 + //(byte)ServerNetObject.CHAT_MESSAGE diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs index 34f6cd497..efd98d89e 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs @@ -369,6 +369,9 @@ namespace Barotrauma.Networking if (!character.ClientDisconnected) { continue; } Client owner = connectedClients.Find(c => (c.Character == null || c.Character == character) && character.IsClientOwner(c)); + bool canOwnerTakeControl = + owner != null && owner.InGame && !owner.NeedsMidRoundSync && + (!ServerSettings.AllowSpectating || !owner.SpectateOnly); if (!character.IsDead) { character.KillDisconnectedTimer += deltaTime; @@ -379,18 +382,19 @@ namespace Barotrauma.Networking character.Kill(CauseOfDeathType.Disconnected, null); continue; } - if (owner != null && owner.InGame && !owner.NeedsMidRoundSync && - (!ServerSettings.AllowSpectating || !owner.SpectateOnly)) + if (canOwnerTakeControl) { SetClientCharacter(owner, character); } } - else if (owner != null && + else if (canOwnerTakeControl && character.CauseOfDeath?.Type == CauseOfDeathType.Disconnected && character.CharacterHealth.VitalityDisregardingDeath > 0) { + //create network event immediately to ensure the character is revived client-side + //before the client gains control of it (normally status events are created periodically) + character.Revive(removeAfflictions: false, createNetworkEvent: true); SetClientCharacter(owner, character); - character.Revive(removeAfflictions: false); } } @@ -2534,7 +2538,7 @@ namespace Barotrauma.Networking { spawnList.Add(new PurchasedItem(kvp.Key, kvp.Value, buyer: null)); } - CargoManager.CreateItems(spawnList, sub, cargoManager: null); + CargoManager.DeliverItemsToSub(spawnList, sub, cargoManager: null); } } @@ -2581,6 +2585,7 @@ namespace Barotrauma.Networking msg.WriteBoolean(ServerSettings.AllowRespawn && missionAllowRespawn); msg.WriteBoolean(ServerSettings.AllowDisguises); msg.WriteBoolean(ServerSettings.AllowRewiring); + msg.WriteBoolean(ServerSettings.AllowImmediateItemDelivery); msg.WriteBoolean(ServerSettings.AllowFriendlyFire); msg.WriteBoolean(ServerSettings.LockAllDefaultWires); msg.WriteBoolean(ServerSettings.AllowLinkingWifiToChat); @@ -3706,8 +3711,12 @@ namespace Barotrauma.Networking } } + public readonly List JobAssignmentDebugLog = new List(); + public void AssignJobs(List unassigned) { + JobAssignmentDebugLog.Clear(); + var jobList = JobPrefab.Prefabs.ToList(); unassigned = new List(unassigned); unassigned = unassigned.OrderBy(sp => Rand.Int(int.MaxValue)).ToList(); @@ -3729,10 +3738,11 @@ namespace Barotrauma.Networking //remove already assigned clients from unassigned unassigned.RemoveAll(u => campaignAssigned.ContainsKey(u)); //add up to assigned client count - foreach (KeyValuePair clientJob in campaignAssigned) + foreach ((Client client, Job job) in campaignAssigned) { - assignedClientCount[clientJob.Value.Prefab]++; - clientJob.Key.AssignedJob = new JobVariant(clientJob.Value.Prefab, clientJob.Value.Variant); + assignedClientCount[job.Prefab]++; + client.AssignedJob = new JobVariant(job.Prefab, job.Variant); + JobAssignmentDebugLog.Add($"Client {client.Name} has an existing campaign character, keeping the job {job.Name}."); } } @@ -3751,6 +3761,7 @@ namespace Barotrauma.Networking { if (unassigned[i].JobPreferences.Count == 0) { continue; } if (!unassigned[i].JobPreferences.Any() || !unassigned[i].JobPreferences[0].Prefab.AllowAlways) { continue; } + JobAssignmentDebugLog.Add($"Client {unassigned[i].Name} has {unassigned[i].JobPreferences[0].Prefab.Name} as their first preference, assigning it because the job is always allowed."); unassigned[i].AssignedJob = unassigned[i].JobPreferences[0]; unassigned.RemoveAt(i); } @@ -3769,6 +3780,7 @@ namespace Barotrauma.Networking Client client = FindClientWithJobPreference(unassigned, jobPrefab, forceAssign: false); if (client != null) { + JobAssignmentDebugLog.Add($"At least {jobPrefab.MinNumber} {jobPrefab.Name} required. Assigning {client.Name} as a {jobPrefab.Name} (has the job in their preferences)."); AssignJob(client, jobPrefab); } } @@ -3780,7 +3792,11 @@ namespace Barotrauma.Networking { if (unassigned.Count == 0) { break; } if (jobPrefab.MinNumber < 1 || assignedClientCount[jobPrefab] >= jobPrefab.MinNumber) { continue; } - AssignJob(FindClientWithJobPreference(unassigned, jobPrefab, forceAssign: true), jobPrefab); + var client = FindClientWithJobPreference(unassigned, jobPrefab, forceAssign: true); + JobAssignmentDebugLog.Add( + $"At least {jobPrefab.MinNumber} {jobPrefab.Name} required. "+ + $"A random client needs to be assigned because no one has the job in their preferences. Assigning {client.Name} as a {jobPrefab.Name}."); + AssignJob(client, jobPrefab); } } @@ -3798,32 +3814,6 @@ namespace Barotrauma.Networking } } - List availableSpawnPoints = WayPoint.WayPointList.FindAll(wp => - wp.SpawnType == SpawnType.Human && - wp.Submarine != null && wp.Submarine.TeamID == teamID); - - /*bool canAssign = false; - do - { - canAssign = false; - foreach (WayPoint spawnPoint in unassignedSpawnPoints) - { - if (unassigned.Count == 0) { break; } - - JobPrefab job = spawnPoint.AssignedJob ?? JobPrefab.List.Values.GetRandom(); - if (assignedClientCount[job] >= job.MaxNumber) { continue; } - - Client assignedClient = FindClientWithJobPreference(unassigned, job, true); - if (assignedClient != null) - { - assignedClient.AssignedJob = job; - assignedClientCount[job]++; - unassigned.Remove(assignedClient); - canAssign = true; - } - } - } while (unassigned.Count > 0 && canAssign);*/ - // Attempt to give the clients a job they have in their job preferences. // First evaluate all the primary preferences, then all the secondary etc. for (int preferenceIndex = 0; preferenceIndex < 3; preferenceIndex++) @@ -3834,12 +3824,17 @@ namespace Barotrauma.Networking if (preferenceIndex >= client.JobPreferences.Count) { continue; } var preferredJob = client.JobPreferences[preferenceIndex]; JobPrefab jobPrefab = preferredJob.Prefab; - if (assignedClientCount[jobPrefab] >= jobPrefab.MaxNumber || client.Karma < jobPrefab.MinKarma) + if (assignedClientCount[jobPrefab] >= jobPrefab.MaxNumber) { - //can't assign this job if maximum number has reached or the clien't karma is too low + JobAssignmentDebugLog.Add($"{client.Name} has {jobPrefab.Name} as their {preferenceIndex + 1}. preference. Cannot assign, maximum number of the job has been reached."); continue; } - + if (client.Karma < jobPrefab.MinKarma) + { + JobAssignmentDebugLog.Add($"{client.Name} has {jobPrefab.Name} as their {preferenceIndex + 1}. preference. Cannot assign, karma too low ({client.Karma} < {jobPrefab.MinKarma})."); + continue; + } + JobAssignmentDebugLog.Add($"{client.Name} has {jobPrefab.Name} as their {preferenceIndex + 1}. preference. Assigning {client.Name} as a {jobPrefab.Name}."); client.AssignedJob = preferredJob; assignedClientCount[jobPrefab]++; unassigned.RemoveAt(i); @@ -3855,7 +3850,9 @@ namespace Barotrauma.Networking //all jobs taken, give a random job if (remainingJobs.Count == 0) { - DebugConsole.ThrowError("Failed to assign a suitable job for \"" + c.Name + "\" (all jobs already have the maximum numbers of players). Assigning a random job..."); + string errorMsg = $"Failed to assign a suitable job for \"{c.Name}\" (all jobs already have the maximum numbers of players). Assigning a random job..."; + DebugConsole.ThrowError(errorMsg); + JobAssignmentDebugLog.Add(errorMsg); int jobIndex = Rand.Range(0, jobList.Count); int skips = 0; while (c.Karma < jobList[jobIndex].MinKarma) @@ -3871,19 +3868,20 @@ namespace Barotrauma.Networking assignedClientCount[c.AssignedJob.Prefab]++; } //if one of the client's preferences is still available, give them that job - else if (c.JobPreferences.Any(jp => remainingJobs.Contains(jp.Prefab))) + else if (c.JobPreferences.FirstOrDefault(jp => remainingJobs.Contains(jp.Prefab)) is { } remainingJob) { - foreach (JobVariant preferredJob in c.JobPreferences) - { - c.AssignedJob = preferredJob; - assignedClientCount[preferredJob.Prefab]++; - break; - } + JobAssignmentDebugLog.Add( + $"{c.Name} has {remainingJob.Prefab.Name} as their {c.JobPreferences.IndexOf(remainingJob) + 1}. preference, and it is still available."+ + $" Assigning {c.Name} as a {remainingJob.Prefab.Name}."); + c.AssignedJob = remainingJob; + assignedClientCount[remainingJob.Prefab]++; } else //none of the client's preferred jobs available, choose a random job { c.AssignedJob = new JobVariant(remainingJobs[Rand.Range(0, remainingJobs.Count)], 0); assignedClientCount[c.AssignedJob.Prefab]++; + JobAssignmentDebugLog.Add( + $"No suitable jobs available for {c.Name} (karma {c.Karma}). Assigning a random job: {c.AssignedJob.Prefab.Name}."); } } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/ServerSettings.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/ServerSettings.cs index ec0e8e101..7f29c7662 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/ServerSettings.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/ServerSettings.cs @@ -611,7 +611,7 @@ namespace Barotrauma.Networking { foreach (DebugConsole.Command command in clientPermission.PermittedCommands) { - clientElement.Add(new XElement("command", new XAttribute("name", command.names[0]))); + clientElement.Add(new XElement("command", new XAttribute("name", command.Names[0]))); } } doc.Root.Add(clientElement); diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/Voting.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/Voting.cs index 5afb8d321..81c472caf 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/Voting.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/Voting.cs @@ -345,9 +345,15 @@ namespace Barotrauma sender.SetVote(voteType, client); if (client?.Character != null) { - GameMain.Server.SendChatMessage( - TextManager.GetWithVariable("traitor.blamebutton.dialog", "[name]", client.Character.DisplayName).Value, - ChatMessageType.Radio, senderClient: sender, senderCharacter: sender.Character); + string msg = TextManager.GetWithVariable("traitor.blamebutton.dialog", "[name]", client.Character.DisplayName).Value; + ChatMessage.HandleSpamFilter(sender, msg, out bool flaggedAsSpam); + if (!flaggedAsSpam) + { + GameMain.Server.SendChatMessage( + msg, + ChatMessageType.Radio, senderClient: sender, senderCharacter: sender.Character); + sender.LastSentChatMessages.Add(msg); + } } } break; diff --git a/Barotrauma/BarotraumaServer/ServerSource/Steam/SteamManager.cs b/Barotrauma/BarotraumaServer/ServerSource/Steam/SteamManager.cs index e0967195b..039e30109 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Steam/SteamManager.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Steam/SteamManager.cs @@ -58,15 +58,20 @@ namespace Barotrauma.Steam Steamworks.SteamServer.SetKey("message", server.ServerSettings.ServerMessageText); Steamworks.SteamServer.SetKey("version", GameMain.Version.ToString()); Steamworks.SteamServer.SetKey("playercount", server.ConnectedClients.Count.ToString()); + + //a2s seems to break if too much data is added (seems to be related to MTU?) + //let's restrict the number of packages to 10, clients can use packagecount to tell when the list has been truncated + const int MaxPackagesToList = 10; int index = 0; - foreach (var contentPackage in contentPackages) + foreach (var contentPackage in contentPackages.Take(MaxPackagesToList)) { string ugcIdStr = contentPackage.UgcId.TryUnwrap(out var ugcId) ? ugcId.StringRepresentation : string.Empty; Steamworks.SteamServer.SetKey( - $"contentpackage{index}", - contentPackage.Name+","+ contentPackage.Hash.StringRepresentation + "," + ugcIdStr); + $"contentpackage{index}", + contentPackage.Name + "," + contentPackage.Hash.StringRepresentation + "," + ugcIdStr); index++; } + Steamworks.SteamServer.SetKey("packagecount", contentPackages.Count().ToString()); Steamworks.SteamServer.SetKey("modeselectionmode", server.ServerSettings.ModeSelectionMode.ToString()); Steamworks.SteamServer.SetKey("subselectionmode", server.ServerSettings.SubSelectionMode.ToString()); Steamworks.SteamServer.SetKey("voicechatenabled", server.ServerSettings.VoiceChatEnabled.ToString()); @@ -79,6 +84,10 @@ namespace Barotrauma.Steam Steamworks.SteamServer.SetKey("gamemode", server.ServerSettings.GameModeIdentifier.Value); Steamworks.SteamServer.SetKey("playstyle", server.ServerSettings.PlayStyle.ToString()); Steamworks.SteamServer.SetKey("language", server.ServerSettings.Language.ToString()); + if (GameMain.NetLobbyScreen?.SelectedSub != null) + { + Steamworks.SteamServer.SetKey("submarine", GameMain.NetLobbyScreen.SelectedSub.Name); + } Steamworks.SteamServer.DedicatedServer = true; diff --git a/Barotrauma/BarotraumaServer/ServerSource/Traitors/TraitorManager.cs b/Barotrauma/BarotraumaServer/ServerSource/Traitors/TraitorManager.cs index 696d36f9d..55eed416d 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Traitors/TraitorManager.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Traitors/TraitorManager.cs @@ -224,7 +224,8 @@ namespace Barotrauma var selectedTraitor = SelectRandomTraitor(); if (selectedTraitor == null) { - DebugConsole.ThrowError($"Could not find a suitable traitor for the event \"{selectedPrefab.Identifier}\"."); + DebugConsole.ThrowError($"Could not find a suitable traitor for the event \"{selectedPrefab.Identifier}\".", + contentPackage: selectedPrefab.ContentPackage); return false; } CreateTraitorEvent(eventManager, selectedPrefab, selectedTraitor); @@ -263,7 +264,8 @@ namespace Barotrauma { DebugConsole.ThrowError( $"Error in traitor event {traitorEvent.Prefab.Identifier}. Not enough players to choose {amountToChoose} secondary traitors."+ - $"Make sure the {nameof(traitorEvent.Prefab.MinPlayerCount)} of the event is high enough to support to desired amount of secondary traitors."); + $"Make sure the {nameof(traitorEvent.Prefab.MinPlayerCount)} of the event is high enough to support to desired amount of secondary traitors.", + contentPackage: traitorEvent.Prefab.ContentPackage); amountToChoose = viableTraitors.Count; } @@ -352,7 +354,8 @@ namespace Barotrauma } else { - DebugConsole.ThrowError($"Failed to create an instance of the traitor event prefab \"{selectedPrefab.Identifier}\"!"); + DebugConsole.ThrowError($"Failed to create an instance of the traitor event prefab \"{selectedPrefab.Identifier}\"!", + contentPackage: selectedPrefab.ContentPackage); } } @@ -365,7 +368,8 @@ namespace Barotrauma var traitor = SelectRandomTraitor(); if (traitor == null) { - DebugConsole.ThrowError($"Could not find a suitable traitor for the event \"{traitorEventPrefab.Identifier}\"."); + DebugConsole.ThrowError($"Could not find a suitable traitor for the event \"{traitorEventPrefab.Identifier}\".", + contentPackage: traitorEventPrefab.ContentPackage); return; } CreateTraitorEvent(eventManager, traitorEventPrefab, traitor); diff --git a/Barotrauma/BarotraumaServer/WindowsServer.csproj b/Barotrauma/BarotraumaServer/WindowsServer.csproj index 72495dd7f..be16280af 100644 --- a/Barotrauma/BarotraumaServer/WindowsServer.csproj +++ b/Barotrauma/BarotraumaServer/WindowsServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 1.1.18.1 + 1.2.1.0 Copyright © FakeFish 2018-2023 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaShared/Data/campaignsettings.xml b/Barotrauma/BarotraumaShared/Data/campaignsettings.xml index ef782ae23..a9be7381b 100644 --- a/Barotrauma/BarotraumaShared/Data/campaignsettings.xml +++ b/Barotrauma/BarotraumaShared/Data/campaignsettings.xml @@ -12,7 +12,9 @@ StartingBalanceAmount="High" StartItemSet="easy" MaxMissionCount="3" - Difficulty="Easy"/> + Difficulty="Easy" + MinStolenItemInspectionProbability="0.2" + MaxStolenItemInspectionProbability="0.9"/> + Difficulty="Medium" + MinStolenItemInspectionProbability="0.3" + MaxStolenItemInspectionProbability="0.9"/> + Difficulty="Hard" + MinStolenItemInspectionProbability="0.4" + MaxStolenItemInspectionProbability="1.0"/> \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs index 9495c3e08..adbf7e933 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs @@ -262,7 +262,8 @@ namespace Barotrauma if (aiElements.Count == 0) { - DebugConsole.ThrowError("Error in file \"" + c.Params.File + "\" - no AI element found."); + DebugConsole.ThrowError("Error in file \"" + c.Params.File + "\" - no AI element found.", + contentPackage: c.Prefab?.ContentPackage); outsideSteering = new SteeringManager(this); insideSteering = new IndoorsSteeringManager(this, false, false); return; @@ -330,7 +331,8 @@ namespace Barotrauma _aiParams = Character.Params.AI; if (_aiParams == null) { - DebugConsole.ThrowError($"No AI Params defined for {Character.SpeciesName}. AI disabled."); + DebugConsole.ThrowError($"No AI Params defined for {Character.SpeciesName}. AI disabled.", + contentPackage: Character.Prefab.ContentPackage); Enabled = false; _aiParams = new CharacterParams.AIParams(null, Character.Params); } @@ -2503,7 +2505,8 @@ namespace Barotrauma Limb mouthLimb = Character.AnimController.GetLimb(LimbType.Head); if (mouthLimb == null) { - DebugConsole.ThrowError("Character \"" + Character.SpeciesName + "\" failed to eat a target (No head limb defined)"); + DebugConsole.ThrowError("Character \"" + Character.SpeciesName + "\" failed to eat a target (No head limb defined)", + contentPackage: Character.Prefab.ContentPackage); State = AIState.Idle; return; } @@ -2540,7 +2543,11 @@ namespace Barotrauma item.body.LinearVelocity -= velocity * 0.25f; bool wasBroken = item.Condition <= 0.0f; item.LastEatenTime = (float)Timing.TotalTimeUnpaused; - item.AddDamage(Character, item.WorldPosition, new Attack(0.0f, 0.0f, 0.0f, 0.0f, 0.02f * Character.Params.EatingSpeed), deltaTime); + item.AddDamage(Character, + item.WorldPosition, + new Attack(0.0f, 0.0f, 0.0f, 0.0f, 0.02f * Character.Params.EatingSpeed), + impulseDirection: Vector2.Zero, + deltaTime); Character.ApplyStatusEffects(ActionType.OnEating, deltaTime); if (item.Condition <= 0.0f) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs index 43378e91a..95bae3353 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs @@ -167,10 +167,6 @@ namespace Barotrauma public HumanAIController(Character c) : base(c) { - if (!c.IsHuman) - { - throw new Exception($"Tried to create a human ai controller for a non-human: {c.SpeciesName}!"); - } insideSteering = new IndoorsSteeringManager(this, true, false); outsideSteering = new SteeringManager(this); objectiveManager = new AIObjectiveManager(c); @@ -1800,7 +1796,7 @@ namespace Barotrauma if (!TriggerSecurity(otherHumanAI, combatMode)) { // Else call the others - foreach (Character security in Character.CharacterList.Where(c => c.TeamID == otherCharacter.TeamID).OrderByDescending(c => Vector2.DistanceSquared(character.WorldPosition, c.WorldPosition))) + foreach (Character security in Character.CharacterList.Where(c => c.TeamID == otherCharacter.TeamID).OrderBy(c => Vector2.DistanceSquared(character.WorldPosition, c.WorldPosition))) { if (!TriggerSecurity(security.AIController as HumanAIController, combatMode)) { @@ -1861,16 +1857,11 @@ namespace Barotrauma } if (!someoneSpoke) { - if (!item.StolenDuringRound && - Level.Loaded?.Type == LevelData.LevelType.Outpost && - GameMain.GameSession?.Campaign?.Map?.CurrentLocation != null) + if (!item.StolenDuringRound) { - var reputationLoss = MathHelper.Clamp( - (item.Prefab.GetMinPrice() ?? 0) * Reputation.ReputationLossPerStolenItemPrice, - Reputation.MinReputationLossPerStolenItem, Reputation.MaxReputationLossPerStolenItem); - GameMain.GameSession.Campaign.Map.CurrentLocation.Reputation?.AddReputation(-reputationLoss); + ApplyStealingReputationLoss(item); + item.StolenDuringRound = true; } - item.StolenDuringRound = true; otherCharacter.Speak(TextManager.Get("dialogstealwarning").Value, null, Rand.Range(0.5f, 1.0f), "thief".ToIdentifier(), 10.0f); someoneSpoke = true; #if CLIENT @@ -1881,7 +1872,7 @@ namespace Barotrauma if (!TriggerSecurity(otherHumanAI)) { // Else call the others - foreach (Character security in Character.CharacterList.Where(c => c.TeamID == otherCharacter.TeamID).OrderByDescending(c => Vector2.DistanceSquared(thief.WorldPosition, c.WorldPosition))) + foreach (Character security in Character.CharacterList.Where(c => c.TeamID == otherCharacter.TeamID).OrderBy(c => Vector2.DistanceSquared(thief.WorldPosition, c.WorldPosition))) { if (TriggerSecurity(security.AIController as HumanAIController)) { @@ -1902,6 +1893,10 @@ namespace Barotrauma if (humanAI == null) { return false; } if (!humanAI.Character.IsSecurity) { return false; } if (humanAI.ObjectiveManager.IsCurrentObjective()) { return false; } + if (humanAI.ObjectiveManager.GetObjective() is { } findThieves) + { + findThieves.InspectEveryone(); + } humanAI.AddCombatObjective(AIObjectiveCombat.CombatMode.Arrest, thief, delay: GetReactionTime(), abortCondition: obj => thief.Inventory.FindItem(it => it != null && it.StolenDuringRound, true) == null, onAbort: () => @@ -1919,6 +1914,18 @@ namespace Barotrauma } } + public static void ApplyStealingReputationLoss(Item item) + { + if (Level.Loaded?.Type == LevelData.LevelType.Outpost && + GameMain.GameSession?.Campaign?.Map?.CurrentLocation != null) + { + var reputationLoss = MathHelper.Clamp( + (item.Prefab.GetMinPrice() ?? 0) * Reputation.ReputationLossPerStolenItemPrice, + Reputation.MinReputationLossPerStolenItem, Reputation.MaxReputationLossPerStolenItem); + GameMain.GameSession.Campaign.Map.CurrentLocation.Reputation?.AddReputation(-reputationLoss); + } + } + // 0.225 - 0.375 private static float GetReactionTime() => reactionTime * Rand.Range(0.75f, 1.25f); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjective.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjective.cs index 884a07263..c9dd03d88 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjective.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjective.cs @@ -197,17 +197,6 @@ namespace Barotrauma } } - /// - /// This method allows multiple subobjectives of same type. Use with caution. - /// - public void AddSubObjectiveInQueue(AIObjective objective) - { - if (!subObjectives.Contains(objective)) - { - subObjectives.Add(objective); - } - } - public void RemoveSubObjective(ref T objective) where T : AIObjective { if (objective != null) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCheckStolenItems.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCheckStolenItems.cs new file mode 100644 index 000000000..cd63e7856 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCheckStolenItems.cs @@ -0,0 +1,160 @@ +#nullable enable +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Barotrauma +{ + class AIObjectiveCheckStolenItems : AIObjective + { + public override Identifier Identifier { get; set; } = "check stolen items".ToIdentifier(); + public override bool AllowOutsideSubmarine => false; + public override bool AllowInAnySub => false; + + public float FindStolenItemsProbability = 1.0f; + + enum State + { + GotoTarget, + Inspect, + Warn, + Done + } + + private float inspectDelay; + private float warnDelay; + + private State currentState; + + public readonly Character TargetCharacter; + + private AIObjectiveGoTo? goToObjective; + + private readonly List stolenItems = new List(); + + public AIObjectiveCheckStolenItems(Character character, Character targetCharacter, AIObjectiveManager objectiveManager, float priorityModifier = 1) : + base(character, objectiveManager, priorityModifier) + { + TargetCharacter = targetCharacter; + inspectDelay = 5.0f; + warnDelay = 5.0f; + } + + public override bool IsLoop + { + get => false; + set => throw new Exception("Trying to set the value for IsLoop from: " + Environment.StackTrace.CleanupStackTrace()); + } + + protected override bool CheckObjectiveSpecific() => false; + + protected override float GetPriority() + { + if (!Abandon && !IsCompleted && objectiveManager.IsOrder(this)) + { + Priority = objectiveManager.GetOrderPriority(this); + } + else + { + Priority = AIObjectiveManager.LowestOrderPriority - 1; + } + return Priority; + } + + public void ForceComplete() + { + IsCompleted = true; + } + + protected override void Act(float deltaTime) + { + switch (currentState) + { + case State.GotoTarget: + TryAddSubObjective(ref goToObjective, + constructor: () => + { + return new AIObjectiveGoTo(TargetCharacter, character, objectiveManager, repeat: false) + { + SpeakIfFails = false + }; + }, + onCompleted: () => + { + RemoveSubObjective(ref goToObjective); + currentState = State.Inspect; + stolenItems.Clear(); + TargetCharacter.Inventory.FindAllItems(it => it.SpawnedInCurrentOutpost && !it.AllowStealing, recursive: true, stolenItems); + character.Speak(TextManager.Get("dialogcheckstolenitems").Value); + }, + onAbandon: () => + { + Abandon = true; + }); + break; + case State.Inspect: + Inspect(deltaTime); + break; + case State.Warn: + Warn(deltaTime); + break; + } + } + + private void Inspect(float deltaTime) + { + if (inspectDelay > 0.0f) + { + character.SelectCharacter(TargetCharacter); + inspectDelay -= deltaTime; + return; + } + + if (stolenItems.Any() && + Rand.Range(0.0f, 1.0f, Rand.RandSync.Unsynced) < FindStolenItemsProbability) + { + character.Speak(TextManager.Get("dialogcheckstolenitems.warn").Value); + currentState = State.Warn; + } + else + { + character.Speak(TextManager.Get("dialogcheckstolenitems.nostolenitems").Value); + currentState = State.Done; + IsCompleted = true; + } + character.DeselectCharacter(); + } + + private void Warn(float deltaTime) + { + if (warnDelay > 0.0f) + { + warnDelay -= deltaTime; + return; + } + var stolenItemsOnCharacter = stolenItems.Where(it => it.GetRootInventoryOwner() == TargetCharacter); + if (stolenItemsOnCharacter.Any()) + { + character.Speak(TextManager.Get("dialogcheckstolenitems.arrest").Value); + HumanAIController.AddCombatObjective(AIObjectiveCombat.CombatMode.Arrest, TargetCharacter); + foreach (var stolenItem in stolenItemsOnCharacter) + { + HumanAIController.ApplyStealingReputationLoss(stolenItem); + } + } + else + { + character.Speak(TextManager.Get("dialogcheckstolenitems.comply").Value); + } + foreach (var item in stolenItems) + { + HumanAIController.ObjectiveManager.AddObjective(new AIObjectiveGetItem(character, item, objectiveManager, equip: false) + { + BasePriority = 10 + }); + } + currentState = State.Done; + IsCompleted = true; + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindThieves.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindThieves.cs new file mode 100644 index 000000000..bff65d6b5 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindThieves.cs @@ -0,0 +1,152 @@ +#nullable enable +using Microsoft.Xna.Framework; +using System.Collections.Generic; +using System.Linq; + +namespace Barotrauma +{ + class AIObjectiveFindThieves : AIObjectiveLoop + { + public override Identifier Identifier { get; set; } = "find thieves".ToIdentifier(); + protected override float IgnoreListClearInterval => 30; + public override bool IgnoreUnsafeHulls => true; + + protected override float TargetUpdateTimeMultiplier => 1.0f; + + const float DefaultInspectDistance = 200.0f; + + /// + /// How close the NPC must be to the target to the inspect them? You can use high values to make the NPC + /// systematically go through targets no matter where they are, and low values to check targets they happen to come across. + /// + public float InspectDistance = DefaultInspectDistance; + + private float? overrideInspectProbability; + /// + /// Chance of inspecting a valid target. The NPC won't try to inspect that target again for + /// regardless if the target is inspected or not. + /// + public float InspectProbability + { + get + { + if (overrideInspectProbability.HasValue) + { + return overrideInspectProbability.Value; + } + if (GameMain.GameSession?.Campaign is { } campaign) + { + if (campaign.Map?.CurrentLocation?.Reputation is { } reputation) + { + return MathHelper.Lerp( + campaign.Settings.MaxStolenItemInspectionProbability, + campaign.Settings.MinStolenItemInspectionProbability, + reputation.NormalizedValue); + } + } + + return 0.2f; + } + } + + /// + /// When did the character last inspect whether some other character has stolen items on them? + /// + private static readonly Dictionary lastInspectionTimes = new Dictionary(); + + private readonly float inspectionInterval = 120.0f; + + public AIObjectiveFindThieves(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1) + : base(character, objectiveManager, priorityModifier) { } + + protected override bool Filter(Character target) + { + if (!IsValidTarget(target, character)) { return false; } + if (Vector2.DistanceSquared(target.WorldPosition, character.WorldPosition) > InspectDistance * InspectDistance) { return false; } + if (lastInspectionTimes.TryGetValue(target, out double lastInspectionTime)) + { + if (Timing.TotalTime < lastInspectionTime + inspectionInterval) + { + return false; + } + } + return true; + } + + protected override IEnumerable GetList() => Character.CharacterList; + + protected override float TargetEvaluation() + { + return subObjectives.Any() ? 50 : 0; + } + + public void InspectEveryone() + { + lastInspectionTimes.Clear(); + overrideInspectProbability = 1.0f; + InspectDistance = DefaultInspectDistance * 2; + } + + protected override AIObjective ObjectiveConstructor(Character target) + { + var checkStolenItemsObjective = new AIObjectiveCheckStolenItems(character, target, objectiveManager); + if (Rand.Range(0.0f, 1.0f, Rand.RandSync.Unsynced) >= InspectProbability) + { + checkStolenItemsObjective.ForceComplete(); + lastInspectionTimes[target] = Timing.TotalTime; + } + return checkStolenItemsObjective; + } + + private float checkVisibleStolenItemsTimer; + private const float CheckVisibleStolenItemsInterval = 5.0f; + + public override void Update(float deltaTime) + { + base.Update(deltaTime); + if (checkVisibleStolenItemsTimer > 0.0f) + { + checkVisibleStolenItemsTimer -= deltaTime; + return; + } + foreach (var target in Character.CharacterList) + { + if (!IsValidTarget(target, character)) { continue; } + //if we spot someone wearing or holding stolen items, immediately check them (with 100% chance of spotting the stolen items) + if (target.Inventory.AllItems.Any(it => it.SpawnedInCurrentOutpost && !it.AllowStealing && target.HasEquippedItem(it)) && + character.CanSeeTarget(target)) + { + AIObjectiveCheckStolenItems? existingObjective = + objectiveManager.GetActiveObjectives().FirstOrDefault(o => o.TargetCharacter == target); + if (existingObjective == null) + { + objectiveManager.AddObjective(new AIObjectiveCheckStolenItems(character, target, objectiveManager)); + lastInspectionTimes[target] = Timing.TotalTime; + } + + } + } + checkVisibleStolenItemsTimer = CheckVisibleStolenItemsInterval; + } + + private bool IsValidTarget(Character target, Character character) + { + if (target == null || target.Removed) { return false; } + if (target.IsIncapacitated) { return false; } + if (target == character) { return false; } + if (target.Submarine == null) { return false; } + if (character.Submarine == null) { return false; } + if (target.CurrentHull == null) { return false; } + if (target.Submarine != character.Submarine) { return false; } + //only player's crew can steal, ignore other teams + if (!target.IsOnPlayerTeam) { return false; } + if (target.IsArrested) { return false; } + return true; + } + + protected override void OnObjectiveCompleted(AIObjective objective, Character target) + { + lastInspectionTimes[target] = Timing.TotalTime; + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveLoop.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveLoop.cs index 5419eedf7..2a939ab76 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveLoop.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveLoop.cs @@ -178,7 +178,7 @@ namespace Barotrauma if (!objectiveManager.IsOrder(this)) { // Battery or pump states cannot currently be reported (not implemented) and therefore we must ignore them -> the bots always know if they require attention. - bool ignore = this is AIObjectiveChargeBatteries || this is AIObjectivePumpWater; + bool ignore = this is AIObjectiveChargeBatteries || this is AIObjectivePumpWater || this is AIObjectiveFindThieves; if (!ignore && !ReportedTargets.Contains(target)) { continue; } } if (!Filter(target)) { continue; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveManager.cs index 5d1aa4a61..42c31a65c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveManager.cs @@ -142,6 +142,7 @@ namespace Barotrauma prevIdleObjective.PreferredOutpostModuleTypes.ForEach(t => newIdleObjective.PreferredOutpostModuleTypes.Add(t)); } AddObjective(newIdleObjective); + int objectiveCount = Objectives.Count; foreach (var autonomousObjective in character.Info.Job.Prefab.AutonomousObjectives) { @@ -549,6 +550,9 @@ namespace Barotrauma case "escapehandcuffs": newObjective = new AIObjectiveEscapeHandcuffs(character, this, priorityModifier: priorityModifier); break; + case "findthieves": + newObjective = new AIObjectiveFindThieves(character, this, priorityModifier: priorityModifier); + break; case "prepareforexpedition": newObjective = new AIObjectivePrepare(character, this, order.GetTargetItems(order.Option), order.RequireItems) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Order.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Order.cs index 21c5aeb35..4c862b4aa 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Order.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Order.cs @@ -441,7 +441,8 @@ namespace Barotrauma } catch (NotImplementedException e) { - DebugConsole.LogError($"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()}", + contentPackage: ContentPackage); return null; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/AnimController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/AnimController.cs index 47e3ec73d..ad724f0bd 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/AnimController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/AnimController.cs @@ -552,7 +552,8 @@ namespace Barotrauma #if DEBUG if (handlePos[i].LengthSquared() > ArmLength) { - DebugConsole.AddWarning($"Aim position for the item {item.Name} may be incorrect (further than the length of the character's arm)"); + DebugConsole.AddWarning($"Aim position for the item {item.Name} may be incorrect (further than the length of the character's arm)", + item.Prefab.ContentPackage); } #endif HandIK( diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs index 91924e625..4004391cc 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs @@ -150,7 +150,7 @@ namespace Barotrauma private readonly float movementLerp; - private float cprAnimTimer,cprPump; + private float cprAnimTimer, cprPumpTimer; private float fallingProneAnimTimer; const float FallingProneAnimDuration = 1.0f; @@ -243,14 +243,17 @@ namespace Barotrauma if (MainLimb == null) { return; } levitatingCollider = !IsHanging; - if ((character.SelectedItem?.GetComponent()?.ControlCharacterPose ?? false) || - (character.SelectedSecondaryItem?.GetComponent()?.ControlCharacterPose ?? false) || - character.SelectedSecondaryItem?.GetComponent() != null || - (ForceSelectAnimationType != AnimationType.Crouch && ForceSelectAnimationType != AnimationType.NotDefined)) + if (onGround && character.CanMove) { - Crouching = false; + if ((character.SelectedItem?.GetComponent()?.ControlCharacterPose ?? false) || + (character.SelectedSecondaryItem?.GetComponent()?.ControlCharacterPose ?? false) || + character.SelectedSecondaryItem?.GetComponent() != null || + (ForceSelectAnimationType != AnimationType.Crouch && ForceSelectAnimationType != AnimationType.NotDefined)) + { + Crouching = false; + } + ColliderIndex = Crouching && !swimming ? 1 : 0; } - ColliderIndex = Crouching && !swimming ? 1 : 0; //stun (= disable the animations) if the ragdoll receives a large enough impact if (strongestImpact > 0.0f) @@ -276,7 +279,7 @@ namespace Barotrauma if (!character.CanMove) { - if (fallingProneAnimTimer < FallingProneAnimDuration) + if (fallingProneAnimTimer < FallingProneAnimDuration && onGround) { fallingProneAnimTimer += deltaTime; UpdateFallingProne(1.0f); @@ -285,7 +288,12 @@ namespace Barotrauma Collider.FarseerBody.FixedRotation = false; if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient) { - Collider.Enabled = false; + if (Collider.Enabled) + { + //deactivating the collider -> make the main limb inherit the collider's velocity because it'll control the movement now + MainLimb.body.LinearVelocity = Collider.LinearVelocity; + Collider.Enabled = false; + } Collider.LinearVelocity = MainLimb.LinearVelocity; Collider.SetTransformIgnoreContacts(MainLimb.SimPosition, MainLimb.Rotation); //reset pull joints to prevent the character from "hanging" mid-air if pull joints had been active when the character was still moving @@ -386,6 +394,12 @@ namespace Barotrauma DragCharacter(character.SelectedCharacter, deltaTime); } + if (Anim != Animation.CPR) + { + cprAnimTimer = 0.0f; + cprPumpTimer = 0.0f; + } + switch (Anim) { case Animation.Climbing: @@ -648,14 +662,6 @@ namespace Barotrauma if (!onGround) { - Vector2 move = torso.PullJointWorldAnchorB - torso.SimPosition; - - foreach (Limb limb in Limbs) - { - if (limb.IsSevered) { continue; } - MoveLimb(limb, limb.SimPosition + move, 15.0f, true); - } - return; } @@ -1318,14 +1324,14 @@ namespace Barotrauma } } - void UpdateFallingProne(float strength) + void UpdateFallingProne(float strength, bool moveHands = true, bool moveTorso = true, bool moveLegs = true) { if (strength <= 0.0f) { return; } Limb head = GetLimb(LimbType.Head); Limb torso = GetLimb(LimbType.Torso); - if (head != null && head.LinearVelocity.LengthSquared() > 1.0f && !head.IsSevered) + if (moveHands && head != null && head.LinearVelocity.LengthSquared() > 1.0f && !head.IsSevered) { //if the head is moving, try to protect it with the hands Limb leftHand = GetLimb(LimbType.LeftHand); @@ -1347,7 +1353,7 @@ namespace Barotrauma //make the torso tip over //otherwise it tends to just drop straight down, pinning the characters legs in a weird pose - if (!InWater) + if (moveTorso && !InWater) { //prefer tipping over in the same direction the torso is rotating //or moving @@ -1358,27 +1364,30 @@ namespace Barotrauma } //attempt to make legs stay in a straight line with the torso to prevent the character from doing a split - for (int i = 0; i < 2; i++) + if (moveLegs) { - var thigh = i == 0 ? GetLimb(LimbType.LeftThigh) : GetLimb(LimbType.RightThigh); - if (thigh == null) { continue; } - if (thigh.IsSevered) { continue; } - float thighDiff = Math.Abs(MathUtils.GetShortestAngle(torso.Rotation, thigh.Rotation)); - float diff = torso.Rotation - thigh.Rotation; - if (MathUtils.IsValid(diff)) + for (int i = 0; i < 2; i++) { - float thighTorque = thighDiff * thigh.Mass * Math.Sign(diff) * 5.0f; - thigh.body.ApplyTorque(thighTorque * strength); - } + var thigh = i == 0 ? GetLimb(LimbType.LeftThigh) : GetLimb(LimbType.RightThigh); + if (thigh == null) { continue; } + if (thigh.IsSevered) { continue; } + float thighDiff = Math.Abs(MathUtils.GetShortestAngle(torso.Rotation, thigh.Rotation)); + float diff = torso.Rotation - thigh.Rotation; + if (MathUtils.IsValid(diff)) + { + float thighTorque = thighDiff * thigh.Mass * Math.Sign(diff) * 5.0f; + thigh.body.ApplyTorque(thighTorque * strength); + } - var leg = i == 0 ? GetLimb(LimbType.LeftLeg) : GetLimb(LimbType.RightLeg); - if (leg == null || leg.IsSevered) { continue; } - float legDiff = Math.Abs(MathUtils.GetShortestAngle(torso.Rotation, leg.Rotation)); - diff = torso.Rotation - leg.Rotation; - if (MathUtils.IsValid(diff)) - { - float legTorque = legDiff * leg.Mass * Math.Sign(diff) * 5.0f; - leg.body.ApplyTorque(legTorque * strength); + var leg = i == 0 ? GetLimb(LimbType.LeftLeg) : GetLimb(LimbType.RightLeg); + if (leg == null || leg.IsSevered) { continue; } + float legDiff = Math.Abs(MathUtils.GetShortestAngle(torso.Rotation, leg.Rotation)); + diff = torso.Rotation - leg.Rotation; + if (MathUtils.IsValid(diff)) + { + float legTorque = legDiff * leg.Mass * Math.Sign(diff) * 5.0f; + leg.body.ApplyTorque(legTorque * strength); + } } } } @@ -1398,7 +1407,8 @@ namespace Barotrauma Crouching = true; - Vector2 diff = target.SimPosition - character.SimPosition; + Vector2 offset = Vector2.UnitX * -Dir * 0.75f; + Vector2 diff = (target.SimPosition + offset) - character.SimPosition; Limb targetHead = target.AnimController.GetLimb(LimbType.Head); Limb targetTorso = target.AnimController.GetLimb(LimbType.Torso); if (targetTorso == null) @@ -1412,7 +1422,23 @@ namespace Barotrauma Vector2 headDiff = targetHead == null ? diff : targetHead.SimPosition - character.SimPosition; targetMovement = new Vector2(diff.X, 0.0f); + const float CloseEnough = 0.1f; + if (Math.Abs(targetMovement.X) < CloseEnough) + { + targetMovement.X = 0.0f; + } + TargetDir = headDiff.X > 0.0f ? Direction.Right : Direction.Left; + //if the target's in some weird pose, we may not be able to flip it so it's facing up, + //so let's only try it once so we don't end up constantly flipping it + if (cprAnimTimer <= 0.0f && target.AnimController.Direction == TargetDir) + { + target.AnimController.Flip(); + } + (target.AnimController as HumanoidAnimController)?.UpdateFallingProne(strength: 1.0f, moveHands: false, moveTorso: false); + + head.Disabled = true; + torso.Disabled = true; UpdateStanding(); @@ -1443,73 +1469,64 @@ namespace Barotrauma } } - //pump for 15 seconds (cprAnimTimer 0-15), then do mouth-to-mouth for 2 seconds (cprAnimTimer 15-17) - if (cprAnimTimer > 15.0f && targetHead != null && head != null) + //Serverside code + if (GameMain.NetworkMember is not { IsClient: true }) { - float yPos = (float)Math.Sin(cprAnimTimer) * 0.2f; - head.PullJointWorldAnchorB = new Vector2(targetHead.SimPosition.X, targetHead.SimPosition.Y + 0.3f + yPos); + if (target.Oxygen < -10.0f) + { + //stabilize the oxygen level but don't allow it to go positive and revive the character yet + float stabilizationAmount = skill * CPRSettings.Active.StabilizationPerSkill; + stabilizationAmount = MathHelper.Clamp(stabilizationAmount, CPRSettings.Active.StabilizationMin, CPRSettings.Active.StabilizationMax); + character.Oxygen -= 1.0f / stabilizationAmount * deltaTime; //Worse skill = more oxygen required + if (character.Oxygen > 0.0f) { target.Oxygen += stabilizationAmount * deltaTime; } //we didn't suffocate yet did we + } + } + + if (targetHead != null && head != null) + { + head.PullJointWorldAnchorB = new Vector2(targetHead.SimPosition.X, targetHead.SimPosition.Y + 0.8f); head.PullJointEnabled = true; - torso.PullJointWorldAnchorB = new Vector2(torso.SimPosition.X, colliderPos.Y + (TorsoPosition.Value - 0.2f)); - torso.PullJointEnabled = true; - - //Serverside code - if (GameMain.NetworkMember is not { IsClient: true }) - { - if (target.Oxygen < -10.0f) - { - //stabilize the oxygen level but don't allow it to go positive and revive the character yet - float stabilizationAmount = skill * CPRSettings.Active.StabilizationPerSkill; - stabilizationAmount = MathHelper.Clamp(stabilizationAmount, CPRSettings.Active.StabilizationMin, CPRSettings.Active.StabilizationMax); - character.Oxygen -= 1.0f / stabilizationAmount * deltaTime; //Worse skill = more oxygen required - if (character.Oxygen > 0.0f) { target.Oxygen += stabilizationAmount * deltaTime; } //we didn't suffocate yet did we - } - } } - else + + torso.PullJointWorldAnchorB = new Vector2(torso.SimPosition.X, colliderPos.Y + (TorsoPosition.Value - 0.1f)); + torso.PullJointEnabled = true; + + if (cprPumpTimer >= 1) { - if (targetHead != null && head != null) + torso.body.ApplyLinearImpulse(new Vector2(0, -20f), maxVelocity: NetConfig.MaxPhysicsBodyVelocity); + targetTorso.body.ApplyLinearImpulse(new Vector2(0, -20f), maxVelocity: NetConfig.MaxPhysicsBodyVelocity); + cprPumpTimer = 0; + + if (skill < CPRSettings.Active.DamageSkillThreshold) { - head.PullJointWorldAnchorB = new Vector2(targetHead.SimPosition.X, targetHead.SimPosition.Y + 0.8f); - head.PullJointEnabled = true; + target.LastDamageSource = null; + target.DamageLimb( + targetTorso.WorldPosition, targetTorso, + new[] { CPRSettings.Active.InsufficientSkillAffliction.Instantiate((CPRSettings.Active.DamageSkillThreshold - skill) * CPRSettings.Active.DamageSkillMultiplier, source: character) }, + stun: 0.0f, + playSound: true, + attackImpulse: Vector2.Zero, + attacker: null); } - - torso.PullJointWorldAnchorB = new Vector2(torso.SimPosition.X, colliderPos.Y + (TorsoPosition.Value - 0.1f)); - torso.PullJointEnabled = true; - - if (cprPump >= 1) + //need to CPR for at least a couple of seconds before the target can be revived + //(reviving the target when the CPR has barely started looks strange) + if (cprAnimTimer > 2.0f && GameMain.NetworkMember is not { IsClient: true }) { - torso.body.ApplyLinearImpulse(new Vector2(0, -20f), maxVelocity: NetConfig.MaxPhysicsBodyVelocity); - targetTorso.body.ApplyLinearImpulse(new Vector2(0, -20f), maxVelocity: NetConfig.MaxPhysicsBodyVelocity); - cprPump = 0; + float reviveChance = skill * CPRSettings.Active.ReviveChancePerSkill; + reviveChance = (float)Math.Pow(reviveChance, CPRSettings.Active.ReviveChanceExponent); + reviveChance = MathHelper.Clamp(reviveChance, CPRSettings.Active.ReviveChanceMin, CPRSettings.Active.ReviveChanceMax); + reviveChance *= 1f + cprBoost; - if (skill < CPRSettings.Active.DamageSkillThreshold) + if (Rand.Range(0.0f, 1.0f, Rand.RandSync.ServerAndClient) <= reviveChance) { - target.LastDamageSource = null; - target.DamageLimb( - targetTorso.WorldPosition, targetTorso, - new[] { CPRSettings.Active.InsufficientSkillAffliction.Instantiate((CPRSettings.Active.DamageSkillThreshold - skill) * CPRSettings.Active.DamageSkillMultiplier, source: character) }, - 0.0f, true, 0.0f, attacker: null); - } - if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient) //Serverside code - { - float reviveChance = skill * CPRSettings.Active.ReviveChancePerSkill; - reviveChance = (float)Math.Pow(reviveChance, CPRSettings.Active.ReviveChanceExponent); - reviveChance = MathHelper.Clamp(reviveChance, CPRSettings.Active.ReviveChanceMin, CPRSettings.Active.ReviveChanceMax); - - reviveChance *= 1f + cprBoost; - - if (Rand.Range(0.0f, 1.0f, Rand.RandSync.ServerAndClient) <= reviveChance) - { - //increase oxygen and clamp it above zero - // -> the character should be revived if there are no major afflictions in addition to lack of oxygen - target.Oxygen = Math.Max(target.Oxygen + 10.0f, 10.0f); - } + //increase oxygen and clamp it above zero + // -> the character should be revived if there are no major afflictions in addition to lack of oxygen + target.Oxygen = Math.Max(target.Oxygen + 10.0f, 10.0f); } } - cprPump += deltaTime; } - - cprAnimTimer = (cprAnimTimer + deltaTime) % 17; + cprPumpTimer += deltaTime; + cprAnimTimer += deltaTime; //got the character back into a non-critical state, increase medical skill //BUT only if it has been more than 10 seconds since the character revived someone diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs index c9ac1f88b..048d81e17 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs @@ -437,7 +437,18 @@ namespace Barotrauma foreach (var huskAppendage in mainElement.GetChildElements("huskappendage")) { if (!inEditor && huskAppendage.GetAttributeBool("onlyfromafflictions", false)) { continue; } - AfflictionHusk.AttachHuskAppendage(character, huskAppendage.GetAttributeIdentifier("affliction", Identifier.Empty), huskAppendage, ragdoll: this); + + Identifier afflictionIdentifier = huskAppendage.GetAttributeIdentifier("affliction", Identifier.Empty); + if (!AfflictionPrefab.Prefabs.TryGet(afflictionIdentifier, out AfflictionPrefab affliction) || + affliction is not AfflictionPrefabHusk matchingAffliction) + { + DebugConsole.ThrowError($"Could not find an affliction of type 'huskinfection' that matches the affliction '{afflictionIdentifier}'!", + contentPackage: huskAppendage.ContentPackage); + } + else + { + AfflictionHusk.AttachHuskAppendage(character, matchingAffliction, huskAppendage, ragdoll: this); + } } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Attack.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Attack.cs index f3397c089..8bb5d51ff 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Attack.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Attack.cs @@ -1,11 +1,12 @@ -using Microsoft.Xna.Framework; +using Barotrauma.Items.Components; +using Microsoft.Xna.Framework; +using System; using System.Collections.Generic; using System.Linq; using System.Xml.Linq; -using Barotrauma.Items.Components; namespace Barotrauma -{ +{ public enum HitDetection { Distance, @@ -391,7 +392,8 @@ namespace Barotrauma element.GetAttribute("burndamage") != null || element.GetAttribute("bleedingdamage") != null) { - DebugConsole.ThrowError("Error in Attack (" + parentDebugName + ") - Define damage as afflictions instead of using the damage attribute (e.g. )."); + DebugConsole.ThrowError("Error in Attack (" + parentDebugName + ") - Define damage as afflictions instead of using the damage attribute (e.g. ).", + contentPackage: element.ContentPackage); } //if level wall damage is not defined, default to the structure damage @@ -414,12 +416,14 @@ namespace Barotrauma AfflictionPrefab afflictionPrefab; if (subElement.GetAttribute("name") != null) { - DebugConsole.ThrowError("Error in Attack (" + parentDebugName + ") - define afflictions using identifiers instead of names."); + DebugConsole.ThrowError("Error in Attack (" + parentDebugName + ") - define afflictions using identifiers instead of names.", + contentPackage: element.ContentPackage); string afflictionName = subElement.GetAttributeString("name", "").ToLowerInvariant(); afflictionPrefab = AfflictionPrefab.List.FirstOrDefault(ap => ap.Name.Equals(afflictionName, System.StringComparison.OrdinalIgnoreCase)); if (afflictionPrefab == null) { - DebugConsole.ThrowError("Error in Attack (" + parentDebugName + ") - Affliction prefab \"" + afflictionName + "\" not found."); + DebugConsole.ThrowError("Error in Attack (" + parentDebugName + ") - Affliction prefab \"" + afflictionName + "\" not found.", + contentPackage: element.ContentPackage); continue; } } @@ -428,7 +432,8 @@ namespace Barotrauma Identifier afflictionIdentifier = subElement.GetAttributeIdentifier("identifier", ""); if (!AfflictionPrefab.Prefabs.TryGet(afflictionIdentifier, out afflictionPrefab)) { - DebugConsole.ThrowError("Error in Attack (" + parentDebugName + ") - Affliction prefab \"" + afflictionIdentifier + "\" not found."); + DebugConsole.ThrowError("Error in Attack (" + parentDebugName + ") - Affliction prefab \"" + afflictionIdentifier + "\" not found.", + contentPackage: element.ContentPackage); continue; } } @@ -441,7 +446,7 @@ namespace Barotrauma } partial void InitProjSpecific(ContentXElement element); - public void ReloadAfflictions(XElement element, string parentDebugName) + public void ReloadAfflictions(ContentXElement element, string parentDebugName) { Afflictions.Clear(); foreach (var subElement in element.GetChildElements("affliction")) @@ -450,13 +455,14 @@ namespace Barotrauma Identifier afflictionIdentifier = subElement.GetAttributeIdentifier("identifier", ""); 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}\"."); + DebugConsole.ThrowError($"Error in an Attack defined in \"{parentDebugName}\" - could not find an affliction with the identifier \"{afflictionIdentifier}\".", + contentPackage: element.ContentPackage); continue; } affliction = afflictionPrefab.Instantiate(0.0f); affliction.Deserialize(subElement); //backwards compatibility - if (subElement.Attribute("amount") != null && subElement.Attribute("strength") == null) + if (subElement.GetAttribute("amount") != null && subElement.GetAttribute("strength") == null) { affliction.Strength = subElement.GetAttributeFloat("amount", 0.0f); } @@ -465,7 +471,7 @@ namespace Barotrauma } } - public void Serialize(XElement element) + public void Serialize(ContentXElement element) { SerializableProperty.SerializeProperties(this, element, true); foreach (var affliction in Afflictions) @@ -477,7 +483,7 @@ namespace Barotrauma } } - public void Deserialize(XElement element, string parentDebugName) + public void Deserialize(ContentXElement element, string parentDebugName) { SerializableProperties = SerializableProperty.DeserializeProperties(this, element); ReloadAfflictions(element, parentDebugName); @@ -497,8 +503,9 @@ namespace Barotrauma SetUser(attacker); DamageParticles(deltaTime, worldPosition); - - var attackResult = target?.AddDamage(attacker, worldPosition, this, deltaTime, playSound) ?? new AttackResult(); + + Vector2 impulseDirection = GetImpulseDirection(target as ISpatialEntity, worldPosition, SourceItem); + var attackResult = target?.AddDamage(attacker, worldPosition, this, impulseDirection, deltaTime, playSound) ?? new AttackResult(); var conditionalEffectType = attackResult.Damage > 0.0f ? ActionType.OnSuccess : ActionType.OnFailure; var additionalEffectType = ActionType.OnUse; if (targetCharacter != null && targetCharacter.IsDead) @@ -606,7 +613,7 @@ namespace Barotrauma float penetration = Penetration; RangedWeapon weapon = - SourceItem?.GetComponent() ?? + SourceItem?.GetComponent() ?? SourceItem?.GetComponent()?.Launcher?.GetComponent(); float? penetrationValue = weapon?.Penetration; if (penetrationValue.HasValue) @@ -614,7 +621,8 @@ namespace Barotrauma penetration += penetrationValue.Value; } - var attackResult = targetLimb.character.ApplyAttack(attacker, worldPosition, this, deltaTime, playSound, targetLimb, penetration); + Vector2 impulseDirection = GetImpulseDirection(targetLimb, worldPosition, SourceItem); + var attackResult = targetLimb.character.ApplyAttack(attacker, worldPosition, this, deltaTime, impulseDirection, playSound, targetLimb, penetration); var conditionalEffectType = attackResult.Damage > 0.0f ? ActionType.OnSuccess : ActionType.OnFailure; foreach (StatusEffect effect in statusEffects) @@ -666,6 +674,34 @@ namespace Barotrauma return attackResult; } + private Vector2 GetImpulseDirection(ISpatialEntity target, Vector2 sourceWorldPosition, Item sourceItem) + { + Vector2 impulseDirection = Vector2.Zero; + if (target != null) + { + impulseDirection = target.WorldPosition - sourceWorldPosition; + } + + if (sourceItem?.body != null && sourceItem.body.Enabled && sourceItem.body.LinearVelocity.LengthSquared() > 0.0f) + { + impulseDirection = sourceItem.body.LinearVelocity; + } + else + { + var projectileComponent = sourceItem?.GetComponent(); + if (projectileComponent != null) + { + impulseDirection = new Vector2(MathF.Cos(SourceItem.Rotation), MathF.Sin(SourceItem.Rotation)); + } + } + + if (impulseDirection.LengthSquared() > 0.0001f) + { + impulseDirection = Vector2.Normalize(impulseDirection); + } + return impulseDirection; + } + public float AttackTimer { get; private set; } public float CoolDownTimer { get; set; } public float CurrentRandomCoolDown { get; private set; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs index 2aeee3c03..d2f146fa7 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs @@ -1098,6 +1098,15 @@ namespace Barotrauma set { CharacterHealth.Unkillable = value; } } + /// + /// Is the health interface available on this character? Can be used by status effects + /// + public bool UseHealthWindow + { + get { return CharacterHealth.UseHealthWindow; } + set { CharacterHealth.UseHealthWindow = value; } + } + public CampaignMode.InteractionType CampaignInteractionType; public Identifier MerchantIdentifier; @@ -1223,19 +1232,15 @@ namespace Barotrauma public static Character Create(CharacterPrefab prefab, Vector2 position, string seed, CharacterInfo characterInfo = null, ushort id = Entity.NullEntityID, bool isRemotePlayer = false, bool hasAi = true, bool createNetworkEvent = true, RagdollParams ragdoll = null, bool spawnInitialItems = true) { Character newCharacter = null; - if (prefab.Identifier != CharacterPrefab.HumanSpeciesName) + if (prefab.Identifier != CharacterPrefab.HumanSpeciesName || hasAi) { var aiCharacter = new AICharacter(prefab, position, seed, characterInfo, id, isRemotePlayer, ragdoll, spawnInitialItems); - var ai = new EnemyAIController(aiCharacter, seed); + + var ai = (prefab.Identifier == CharacterPrefab.HumanSpeciesName || aiCharacter.Params.UseHumanAI) ? + new HumanAIController(aiCharacter) as AIController : + new EnemyAIController(aiCharacter, seed); aiCharacter.SetAI(ai); - newCharacter = aiCharacter; - } - else if (hasAi) - { - var aiCharacter = new AICharacter(prefab, position, seed, characterInfo, id, isRemotePlayer, ragdoll, spawnInitialItems); - var ai = new HumanAIController(aiCharacter); - aiCharacter.SetAI(ai); - newCharacter = aiCharacter; + newCharacter = aiCharacter; } else { @@ -1282,7 +1287,8 @@ namespace Barotrauma { if (!VariantOf.IsEmpty) { - DebugConsole.ThrowError("The variant system does not yet support humans, sorry. It does support other humanoids though!"); + DebugConsole.ThrowError("The variant system does not yet support humans, sorry. It does support other humanoids though!", + contentPackage: Prefab.ContentPackage); } if (characterInfo == null) { @@ -1406,7 +1412,8 @@ namespace Barotrauma if (matchingAffliction == null || nonHuskedSpeciesName.IsEmpty) { DebugConsole.ThrowError($"Cannot find a husk infection that matches {speciesName}! Please make sure that the speciesname is added as 'targets' in the husk affliction prefab definition!\n" - + "Note that all the infected speciesnames and files must stick the following pattern: [nonhuskedspeciesname][huskedspeciesname]. E.g. Humanhusk, Crawlerhusk, or Humancustomhusk, or Crawlerzombie. Not \"Customhumanhusk!\" or \"Zombiecrawler\""); + + "Note that all the infected speciesnames and files must stick the following pattern: [nonhuskedspeciesname][huskedspeciesname]. E.g. Humanhusk, Crawlerhusk, or Humancustomhusk, or Crawlerzombie. Not \"Customhumanhusk!\" or \"Zombiecrawler\"", + contentPackage: Prefab.ContentPackage); // Crashes if we fail to create a ragdoll -> Let's just use some ragdoll so that the user sees the error msg. nonHuskedSpeciesName = IsHumanoid ? CharacterPrefab.HumanSpeciesName : "crawler".ToIdentifier(); speciesName = nonHuskedSpeciesName; @@ -2407,6 +2414,11 @@ namespace Barotrauma } else if (body.UserData is Item item) { + if (item.GetComponent() is { HasWindow: true } door) + { + if (door.IsPositionOnWindow(ConvertUnits.ToDisplayUnits(Submarine.LastPickedPosition))) { return false; } + } + return item != target; } return true; @@ -2768,9 +2780,17 @@ namespace Barotrauma if (!item.Prefab.InteractThroughWalls && Screen.Selected != GameMain.SubEditorScreen && !insideTrigger) { var body = Submarine.CheckVisibility(SimPosition, itemPosition, ignoreLevel: true); - if (body != null && body.UserData as Item != item && (body.UserData as ItemComponent)?.Item != item && Submarine.LastPickedFixture?.UserData as Item != item) - { - return false; + if (body != null) + { + var otherItem = body.UserData as Item ?? (body.UserData as ItemComponent)?.Item; + if (otherItem != item && + (body.UserData as ItemComponent)?.Item != item && + /*allow interacting through open doors (e.g. duct blocks' colliders stay active despite being open)*/ + otherItem?.GetComponent() is not { IsOpen: true } && + Submarine.LastPickedFixture?.UserData as Item != item) + { + return false; + } } } @@ -2797,7 +2817,12 @@ namespace Barotrauma public void DeselectCharacter() { if (SelectedCharacter == null) { return; } - SelectedCharacter.AnimController?.ResetPullJoints(); + if (!SelectedCharacter.AllowInput) + { + //we cannot reset the pull joints if the target is conscious (moving on its own), + //that'd interfere with its animations + SelectedCharacter.AnimController?.ResetPullJoints(); + } SelectedCharacter = null; } @@ -3301,10 +3326,7 @@ namespace Barotrauma IsRagdolled = IsKeyDown(InputType.Ragdoll); //Handle this here instead of Control because we can stop being ragdolled ourselves if (wasRagdolled != IsRagdolled) { ragdollingLockTimer = 0.2f; } } - if (IsRagdolled) - { - SetInput(InputType.Ragdoll, false, true); - } + SetInput(InputType.Ragdoll, false, IsRagdolled); } if (!wasRagdolled && IsRagdolled) { @@ -3980,15 +4002,15 @@ namespace Barotrauma CharacterHealth.SetAllDamage(damageAmount, bleedingDamageAmount, burnDamageAmount); } - public AttackResult AddDamage(Character attacker, Vector2 worldPosition, Attack attack, float deltaTime, bool playSound = true) + public AttackResult AddDamage(Character attacker, Vector2 worldPosition, Attack attack, Vector2 impulseDirection, float deltaTime, bool playSound = true) { - return ApplyAttack(attacker, worldPosition, attack, deltaTime, playSound, null); + return ApplyAttack(attacker, worldPosition, attack, deltaTime, impulseDirection, playSound); } /// /// Apply the specified attack to this character. If the targetLimb is not specified, the limb closest to worldPosition will receive the damage. /// - public AttackResult ApplyAttack(Character attacker, Vector2 worldPosition, Attack attack, float deltaTime, bool playSound = false, Limb targetLimb = null, float penetration = 0f) + public AttackResult ApplyAttack(Character attacker, Vector2 worldPosition, Attack attack, float deltaTime, Vector2 impulseDirection, bool playSound = false, Limb targetLimb = null, float penetration = 0f) { if (Removed) { @@ -4000,7 +4022,16 @@ namespace Barotrauma Limb limbHit = targetLimb; - float attackImpulse = attack.TargetImpulse + attack.TargetForce * attack.ImpactMultiplier * deltaTime; + float impulseMagnitude = (attack.TargetImpulse + attack.TargetForce * attack.ImpactMultiplier) * deltaTime; + + Vector2 attackImpulse = Vector2.Zero; + if (Math.Abs(impulseMagnitude) > 0.0f) + { + impulseDirection = impulseDirection.LengthSquared() > 0.0001f ? + Vector2.Normalize(impulseDirection) : + Vector2.UnitX; + attackImpulse = impulseDirection * impulseMagnitude; + } AbilityAttackData attackData = new AbilityAttackData(attack, this, attacker); IEnumerable attackAfflictions; @@ -4129,12 +4160,12 @@ namespace Barotrauma } } - public AttackResult AddDamage(Vector2 worldPosition, IEnumerable afflictions, float stun, bool playSound, float attackImpulse = 0.0f, Character attacker = null, float damageMultiplier = 1f) + public AttackResult AddDamage(Vector2 worldPosition, IEnumerable afflictions, float stun, bool playSound, Vector2? attackImpulse = null, Character attacker = null, float damageMultiplier = 1f) { - return AddDamage(worldPosition, afflictions, stun, playSound, attackImpulse, out _, attacker, damageMultiplier: damageMultiplier); + return AddDamage(worldPosition, afflictions, stun, playSound, attackImpulse ?? Vector2.Zero, out _, attacker, damageMultiplier: damageMultiplier); } - public AttackResult AddDamage(Vector2 worldPosition, IEnumerable afflictions, float stun, bool playSound, float attackImpulse, out Limb hitLimb, Character attacker = null, float damageMultiplier = 1) + public AttackResult AddDamage(Vector2 worldPosition, IEnumerable afflictions, float stun, bool playSound, Vector2 attackImpulse, out Limb hitLimb, Character attacker = null, float damageMultiplier = 1) { hitLimb = null; @@ -4167,7 +4198,7 @@ namespace Barotrauma CreatureMetrics.RecordKill(target.SpeciesName); } - public AttackResult DamageLimb(Vector2 worldPosition, Limb hitLimb, IEnumerable afflictions, float stun, bool playSound, float attackImpulse, Character attacker = null, float damageMultiplier = 1, bool allowStacking = true, float penetration = 0f, bool shouldImplode = false) + public AttackResult DamageLimb(Vector2 worldPosition, Limb hitLimb, IEnumerable afflictions, float stun, bool playSound, Vector2 attackImpulse, Character attacker = null, float damageMultiplier = 1, bool allowStacking = true, float penetration = 0f, bool shouldImplode = false) { if (Removed) { return new AttackResult(); } @@ -4200,18 +4231,17 @@ namespace Barotrauma } Vector2 dir = hitLimb.WorldPosition - worldPosition; - if (Math.Abs(attackImpulse) > 0.0f) + if (attackImpulse.LengthSquared() > 0.0f) { Vector2 diff = dir; if (diff == Vector2.Zero) { diff = Rand.Vector(1.0f); } - Vector2 impulse = Vector2.Normalize(diff) * attackImpulse; Vector2 hitPos = hitLimb.SimPosition + ConvertUnits.ToSimUnits(diff); - hitLimb.body.ApplyLinearImpulse(impulse, hitPos, maxVelocity: NetConfig.MaxPhysicsBodyVelocity * 0.5f); + hitLimb.body.ApplyLinearImpulse(attackImpulse, hitPos, maxVelocity: NetConfig.MaxPhysicsBodyVelocity * 0.5f); var mainLimb = hitLimb.character.AnimController.MainLimb; if (hitLimb != mainLimb) { // Always add force to mainlimb - mainLimb.body.ApplyLinearImpulse(impulse, hitPos, maxVelocity: NetConfig.MaxPhysicsBodyVelocity); + mainLimb.body.ApplyLinearImpulse(attackImpulse, hitPos, maxVelocity: NetConfig.MaxPhysicsBodyVelocity); } } bool wasDead = IsDead; @@ -4656,7 +4686,7 @@ namespace Barotrauma } partial void KillProjSpecific(CauseOfDeathType causeOfDeath, Affliction causeOfDeathAffliction, bool log); - public void Revive(bool removeAfflictions = true) + public void Revive(bool removeAfflictions = true, bool createNetworkEvent = false) { if (Removed) { @@ -4705,7 +4735,11 @@ namespace Barotrauma limb.IsSevered = false; } - GameMain.GameSession?.ReviveCharacter(this); + GameMain.GameSession?.ReviveCharacter(this); + if (createNetworkEvent && GameMain.NetworkMember is { IsServer: true }) + { + GameMain.NetworkMember.CreateEntityEvent(this, new CharacterStatusEventData()); + } } public override void Remove() @@ -4986,7 +5020,9 @@ namespace Barotrauma float maxDistance = 1000f; foreach (var hull in adjacentHulls) { - if (hull.ConnectedGaps.Any(g => g.Open > 0.9f && g.linkedTo.Contains(CurrentHull) && + if (hull.ConnectedGaps.Any(g => + (g.Open > 0.9f || g.ConnectedDoor is { HasWindow: true }) && + g.linkedTo.Contains(CurrentHull) && Vector2.DistanceSquared(g.WorldPosition, WorldPosition) < Math.Pow(maxDistance / 2, 2))) { if (Vector2.DistanceSquared(hull.WorldPosition, WorldPosition) < Math.Pow(maxDistance, 2)) @@ -5005,7 +5041,7 @@ namespace Barotrauma else { if (h.ConnectedGaps.Any(g => - g.Open > 0.9f && + (g.Open > 0.9f || g.ConnectedDoor is { HasWindow: true }) && Vector2.DistanceSquared(g.WorldPosition, WorldPosition) < Math.Pow(maxDistance / 2, 2) && CanSeeTarget(g))) { @@ -5027,7 +5063,7 @@ namespace Barotrauma public bool IsEngineer => HasJob("engineer"); public bool IsMechanic => HasJob("mechanic"); public bool IsMedic => HasJob("medicaldoctor"); - public bool IsSecurity => HasJob("securityofficer") || HasJob("vipsecurityofficer"); + public bool IsSecurity => HasJob("securityofficer") || HasJob("vipsecurityofficer") || HasJob("outpostsecurityofficer"); public bool IsAssistant => HasJob("assistant"); public bool IsWatchman => HasJob("watchman"); public bool IsVip => HasJob("prisoner"); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs index 61f4df9c7..0752b6b3f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs @@ -767,7 +767,7 @@ namespace Barotrauma } // Used for loading the data - public CharacterInfo(XElement infoElement, Identifier npcIdentifier = default) + public CharacterInfo(ContentXElement infoElement, Identifier npcIdentifier = default) { ID = idCounter; idCounter++; @@ -1311,12 +1311,12 @@ namespace Barotrauma OnExperienceChanged(prevAmount, ExperiencePoints); } - const int BaseExperienceRequired = -50; + const int BaseExperienceRequired = 450; const int AddedExperienceRequiredPerLevel = 500; public int GetTotalTalentPoints() { - return GetCurrentLevel() + AdditionalTalentPoints - 1; + return GetCurrentLevel() + AdditionalTalentPoints; } public int GetAvailableTalentPoints() @@ -1342,16 +1342,19 @@ namespace Barotrauma return experienceRequired + ExperienceRequiredPerLevel(level); } + /// + /// How much more experience does the character need to reach the specified level? + /// public int GetExperienceRequiredForLevel(int level) { - int currentLevel = GetCurrentLevel(out int experienceRequired); + int currentLevel = GetCurrentLevel(); if (currentLevel >= level) { return 0; } - int required = experienceRequired; - for (int i = currentLevel + 1; i <= level; i++) + int required = 0; + for (int i = 0; i < level; i++) { required += ExperienceRequiredPerLevel(i); } - return required; + return required - ExperiencePoints; } public int GetCurrentLevel() @@ -1361,7 +1364,7 @@ namespace Barotrauma private int GetCurrentLevel(out int experienceRequired) { - int level = 1; + int level = 0; experienceRequired = 0; while (experienceRequired + ExperienceRequiredPerLevel(level) <= ExperiencePoints) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterPrefab.cs index 2dfb6ecf5..acae26e4a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterPrefab.cs @@ -95,7 +95,8 @@ namespace Barotrauma name = ParseName(mainElement, file); if (name == Identifier.Empty) { - DebugConsole.ThrowError($"No species name defined for: {file.Path}"); + DebugConsole.ThrowError($"No species name defined for: {file.Path}", + contentPackage: file.ContentPackage); return false; } return true; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionHusk.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionHusk.cs index 20a19d64f..4136b6381 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionHusk.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionHusk.cs @@ -75,7 +75,8 @@ namespace Barotrauma HuskPrefab = prefab as AfflictionPrefabHusk; if (HuskPrefab == null) { - DebugConsole.ThrowError("Error in husk affliction definition: the prefab is of wrong type!"); + DebugConsole.ThrowError("Error in husk affliction definition: the prefab is of wrong type!", + contentPackage: prefab.ContentPackage); } } @@ -197,7 +198,7 @@ namespace Barotrauma huskInfection.Add(AfflictionPrefab.InternalDamage.Instantiate(random * 10 * deltaTime / limbCount)); character.LastDamageSource = null; float force = applyForce ? random * 0.5f * limb.Mass : 0; - character.DamageLimb(limb.WorldPosition, limb, huskInfection, 0, false, force); + character.DamageLimb(limb.WorldPosition, limb, huskInfection, 0, false, Rand.Vector(force)); } } @@ -205,7 +206,7 @@ namespace Barotrauma { if (huskAppendage == null && character.Params.UseHuskAppendage) { - huskAppendage = AttachHuskAppendage(character, Prefab.Identifier); + huskAppendage = AttachHuskAppendage(character, Prefab as AfflictionPrefabHusk); } if (Prefab is AfflictionPrefabHusk { NeedsAir: false }) @@ -285,13 +286,14 @@ namespace Barotrauma if (prefab == null) { - DebugConsole.ThrowError("Failed to turn character \"" + character.Name + "\" into a husk - husk config file not found."); + DebugConsole.ThrowError("Failed to turn character \"" + character.Name + "\" into a husk - husk config file not found.", + contentPackage: Prefab.ContentPackage); yield return CoroutineStatus.Success; } XElement parentElement = new XElement("CharacterInfo"); XElement infoElement = character.Info?.Save(parentElement); - CharacterInfo huskCharacterInfo = infoElement == null ? null : new CharacterInfo(infoElement); + CharacterInfo huskCharacterInfo = infoElement == null ? null : new CharacterInfo(new ContentXElement(Prefab.ContentPackage, infoElement)); if (huskCharacterInfo != null) { @@ -371,31 +373,28 @@ namespace Barotrauma yield return CoroutineStatus.Success; } - public static List AttachHuskAppendage(Character character, Identifier afflictionIdentifier, ContentXElement appendageDefinition = null, Ragdoll ragdoll = null) + public static List AttachHuskAppendage(Character character, AfflictionPrefabHusk matchingAffliction, ContentXElement appendageDefinition = null, Ragdoll ragdoll = null) { var appendage = new List(); - if (!(AfflictionPrefab.List.FirstOrDefault(ap => ap.Identifier == afflictionIdentifier) is AfflictionPrefabHusk matchingAffliction)) - { - DebugConsole.ThrowError($"Could not find an affliction of type 'huskinfection' that matches the affliction '{afflictionIdentifier}'!"); - return appendage; - } Identifier nonhuskedSpeciesName = GetNonHuskedSpeciesName(character.SpeciesName, matchingAffliction); Identifier huskedSpeciesName = GetHuskedSpeciesName(nonhuskedSpeciesName, matchingAffliction); CharacterPrefab huskPrefab = CharacterPrefab.FindBySpeciesName(huskedSpeciesName); if (huskPrefab?.ConfigElement == null) { - DebugConsole.ThrowError($"Failed to find the config file for the husk infected species with the species name '{huskedSpeciesName}'!"); + DebugConsole.ThrowError($"Failed to find the config file for the husk infected species with the species name '{huskedSpeciesName}'!", + contentPackage: matchingAffliction.ContentPackage); return appendage; } var mainElement = huskPrefab.ConfigElement; var element = appendageDefinition; if (element == null) { - element = mainElement.GetChildElements("huskappendage").FirstOrDefault(e => e.GetAttributeIdentifier("affliction", Identifier.Empty) == afflictionIdentifier); + element = mainElement.GetChildElements("huskappendage").FirstOrDefault(e => e.GetAttributeIdentifier("affliction", Identifier.Empty) == matchingAffliction.Identifier); } if (element == null) { - DebugConsole.ThrowError($"Error in '{huskPrefab.FilePath}': Failed to find a huskappendage that matches the affliction with an identifier '{afflictionIdentifier}'!"); + DebugConsole.ThrowError($"Error in '{huskPrefab.FilePath}': Failed to find a huskappendage that matches the affliction with an identifier '{matchingAffliction.Identifier}'!", + contentPackage: matchingAffliction.ContentPackage); return appendage; } ContentPath pathToAppendage = element.GetAttributeContentPath("path") ?? ContentPath.Empty; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionPrefab.cs index 5d46b8207..6f5ac8417 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionPrefab.cs @@ -170,11 +170,13 @@ namespace Barotrauma if (DormantThreshold > ActiveThreshold) { - DebugConsole.ThrowError($"Error in \"{Identifier}\": {nameof(DormantThreshold)} is greater than {nameof(ActiveThreshold)} ({DormantThreshold} > {ActiveThreshold})"); + DebugConsole.ThrowError($"Error in \"{Identifier}\": {nameof(DormantThreshold)} is greater than {nameof(ActiveThreshold)} ({DormantThreshold} > {ActiveThreshold})", + contentPackage: element.ContentPackage); } if (ActiveThreshold > TransitionThreshold) { - DebugConsole.ThrowError($"Error in \"{Identifier}\": {nameof(ActiveThreshold)} is greater than {nameof(TransitionThreshold)} ({ActiveThreshold} > {TransitionThreshold})"); + DebugConsole.ThrowError($"Error in \"{Identifier}\": {nameof(ActiveThreshold)} is greater than {nameof(TransitionThreshold)} ({ActiveThreshold} > {TransitionThreshold})", + contentPackage: element.ContentPackage); } TransformThresholdOnDeath = element.GetAttributeFloat("transformthresholdondeath", ActiveThreshold); @@ -440,13 +442,15 @@ namespace Barotrauma AbilityFlags flagType = subElement.GetAttributeEnum("flagtype", AbilityFlags.None); if (flagType is AbilityFlags.None) { - DebugConsole.ThrowError($"Error in affliction \"{parentDebugName}\" - invalid ability flag type \"{subElement.GetAttributeString("flagtype", "")}\"."); + DebugConsole.ThrowError($"Error in affliction \"{parentDebugName}\" - invalid ability flag type \"{subElement.GetAttributeString("flagtype", "")}\".", + contentPackage: element.ContentPackage); continue; } AfflictionAbilityFlags |= flagType; break; case "affliction": - DebugConsole.AddWarning($"Error in affliction \"{parentDebugName}\" - additional afflictions caused by the affliction should be configured inside status effects."); + DebugConsole.AddWarning($"Error in affliction \"{parentDebugName}\" - additional afflictions caused by the affliction should be configured inside status effects.", + contentPackage: element.ContentPackage); break; } } @@ -537,14 +541,16 @@ namespace Barotrauma } else if (TextTag.IsEmpty) { - DebugConsole.ThrowError($"Error in affliction \"{affliction.Identifier}\" - no text defined for one of the descriptions."); + DebugConsole.ThrowError($"Error in affliction \"{affliction.Identifier}\" - no text defined for one of the descriptions.", + contentPackage: element.ContentPackage); } MinStrength = element.GetAttributeFloat(nameof(MinStrength), 0.0f); MaxStrength = element.GetAttributeFloat(nameof(MaxStrength), 100.0f); if (MinStrength >= MaxStrength) { - DebugConsole.ThrowError($"Error in affliction \"{affliction.Identifier}\" - max strength is not larger than min."); + DebugConsole.ThrowError($"Error in affliction \"{affliction.Identifier}\" - max strength is not larger than min.", + contentPackage: element.ContentPackage); } Target = element.GetAttributeEnum(nameof(Target), TargetType.Any); } @@ -953,7 +959,8 @@ namespace Barotrauma AfflictionOverlay = new Sprite(subElement); break; case "statvalue": - DebugConsole.ThrowError($"Error in affliction \"{Identifier}\" - stat values should be configured inside the affliction's effects."); + DebugConsole.ThrowError($"Error in affliction \"{Identifier}\" - stat values should be configured inside the affliction's effects.", + contentPackage: element.ContentPackage); break; case "effect": case "periodiceffect": @@ -962,7 +969,8 @@ namespace Barotrauma descriptions.Add(new Description(subElement, this)); break; default: - DebugConsole.AddWarning($"Unrecognized element in affliction \"{Identifier}\" ({subElement.Name})"); + DebugConsole.AddWarning($"Unrecognized element in affliction \"{Identifier}\" ({subElement.Name})", + contentPackage: element.ContentPackage); break; } } @@ -1046,7 +1054,8 @@ namespace Barotrauma var b = effects[j]; if (a.MinStrength < b.MaxStrength && b.MinStrength < a.MaxStrength) { - DebugConsole.AddWarning($"Affliction \"{Identifier}\" contains effects with overlapping strength ranges. Only one effect can be active at a time, meaning one of the effects won't work."); + DebugConsole.AddWarning($"Affliction \"{Identifier}\" contains effects with overlapping strength ranges. Only one effect can be active at a time, meaning one of the effects won't work.", + ContentPackage); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs index d7567b91c..3df905b80 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs @@ -49,7 +49,8 @@ namespace Barotrauma case "vitalitymultiplier": if (subElement.GetAttribute("name") != null) { - DebugConsole.ThrowError("Error in character health config (" + characterHealth.Character.Name + ") - define vitality multipliers using affliction identifiers or types instead of names."); + DebugConsole.ThrowError("Error in character health config (" + characterHealth.Character.Name + ") - define vitality multipliers using affliction identifiers or types instead of names.", + contentPackage: element.ContentPackage); continue; } var vitalityMultipliers = subElement.GetAttributeIdentifierArray("identifier", null) ?? subElement.GetAttributeIdentifierArray("identifiers", null); @@ -61,7 +62,8 @@ namespace Barotrauma VitalityMultipliers.Add(vitalityMultiplier, multiplier); if (AfflictionPrefab.Prefabs.None(p => p.Identifier == vitalityMultiplier)) { - DebugConsole.AddWarning($"Potentially incorrectly defined vitality multiplier in \"{characterHealth.Character.Name}\". Could not find any afflictions with the identifier \"{vitalityMultiplier}\". Did you mean to define the afflictions by type instead?"); + DebugConsole.AddWarning($"Potentially incorrectly defined vitality multiplier in \"{characterHealth.Character.Name}\". Could not find any afflictions with the identifier \"{vitalityMultiplier}\". Did you mean to define the afflictions by type instead?", + contentPackage: element.ContentPackage); } } } @@ -74,13 +76,15 @@ namespace Barotrauma VitalityTypeMultipliers.Add(vitalityTypeMultiplier, multiplier); if (AfflictionPrefab.Prefabs.None(p => p.AfflictionType == vitalityTypeMultiplier)) { - DebugConsole.AddWarning($"Potentially incorrectly defined vitality multiplier in \"{characterHealth.Character.Name}\". Could not find any afflictions of the type \"{vitalityTypeMultiplier}\". Did you mean to define the afflictions by identifier instead?"); + DebugConsole.AddWarning($"Potentially incorrectly defined vitality multiplier in \"{characterHealth.Character.Name}\". Could not find any afflictions of the type \"{vitalityTypeMultiplier}\". Did you mean to define the afflictions by identifier instead?", + contentPackage: element.ContentPackage); } } } if (vitalityMultipliers == null && VitalityTypeMultipliers == null) { - DebugConsole.ThrowError($"Error in character health config {characterHealth.Character.Name}: affliction identifier(s) or type(s) not defined in the \"VitalityMultiplier\" elements!"); + DebugConsole.ThrowError($"Error in character health config {characterHealth.Character.Name}: affliction identifier(s) or type(s) not defined in the \"VitalityMultiplier\" elements!", + contentPackage: element.ContentPackage); } break; } @@ -148,11 +152,6 @@ namespace Barotrauma return minVitality; } return vitality; - - } - private set - { - vitality = value; } } @@ -254,7 +253,7 @@ namespace Barotrauma public CharacterHealth(Character character) { this.Character = character; - Vitality = 100.0f; + vitality = 100.0f; DoesBleed = true; UseHealthWindow = false; @@ -271,7 +270,7 @@ namespace Barotrauma this.Character = character; InitIrremovableAfflictions(); - Vitality = UnmodifiedMaxVitality; + vitality = UnmodifiedMaxVitality; minVitality = character.IsHuman ? -100.0f : 0.0f; @@ -971,7 +970,7 @@ namespace Barotrauma public void CalculateVitality() { - Vitality = MaxVitality; + vitality = MaxVitality; IsParalyzed = false; if (Unkillable || Character.GodMode) { return; } @@ -984,7 +983,7 @@ namespace Barotrauma { vitalityDecrease *= GetVitalityMultiplier(affliction, limbHealth); } - Vitality -= vitalityDecrease; + vitality -= vitalityDecrease; affliction.CalculateDamagePerSecond(vitalityDecrease); if (affliction.Strength >= affliction.Prefab.MaxStrength && diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/DamageModifier.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/DamageModifier.cs index 03fb3ad7d..dcdd8ddb4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/DamageModifier.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/DamageModifier.cs @@ -79,12 +79,13 @@ namespace Barotrauma public ref readonly ImmutableArray ParsedAfflictionTypes => ref parsedAfflictionTypes; - public DamageModifier(XElement element, string parentDebugName, bool checkErrors = true) + public DamageModifier(ContentXElement element, string parentDebugName, bool checkErrors = true) { Deserialize(element); - if (element.Attribute("afflictionnames") != null) + if (element.GetAttribute("afflictionnames") != null) { - DebugConsole.ThrowError("Error in DamageModifier config (" + parentDebugName + ") - define afflictions using identifiers or types instead of names."); + DebugConsole.ThrowError("Error in DamageModifier config (" + parentDebugName + ") - define afflictions using identifiers or types instead of names.", + contentPackage: element.ContentPackage); } if (checkErrors) { @@ -108,12 +109,12 @@ namespace Barotrauma } } - static void createWarningOrError(string msg) + void createWarningOrError(string msg) { #if DEBUG - DebugConsole.ThrowError(msg); + DebugConsole.ThrowError(msg, contentPackage: element.ContentPackage); #else - DebugConsole.AddWarning(msg); + DebugConsole.AddWarning(msg, contentPackage: element.ContentPackage); #endif } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/HumanPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/HumanPrefab.cs index d5c10cbef..630e18265 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/HumanPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/HumanPrefab.cs @@ -117,8 +117,8 @@ namespace Barotrauma public XElement Element { get; protected set; } - public readonly List<(XElement element, float commonness)> ItemSets = new List<(XElement element, float commonness)>(); - public readonly List<(XElement element, float commonness)> CustomCharacterInfos = new List<(XElement element, float commonness)>(); + public readonly List<(ContentXElement element, float commonness)> ItemSets = new List<(ContentXElement element, float commonness)>(); + public readonly List<(ContentXElement element, float commonness)> CustomCharacterInfos = new List<(ContentXElement element, float commonness)>(); public readonly Identifier NpcSetIdentifier; @@ -196,7 +196,7 @@ namespace Barotrauma var spawnItems = ToolBox.SelectWeightedRandom(ItemSets, it => it.commonness, randSync).element; if (spawnItems != null) { - foreach (XElement itemElement in spawnItems.GetChildElements("item")) + foreach (ContentXElement itemElement in spawnItems.GetChildElements("item")) { int amount = itemElement.GetAttributeInt("amount", 1); for (int i = 0; i < amount; i++) @@ -239,14 +239,15 @@ namespace Barotrauma return characterInfo; } - public static void InitializeItem(Character character, XElement itemElement, Submarine submarine, HumanPrefab humanPrefab, WayPoint spawnPoint = null, Item parentItem = null, bool createNetworkEvents = true) + public static void InitializeItem(Character character, ContentXElement itemElement, Submarine submarine, HumanPrefab humanPrefab, WayPoint spawnPoint = null, Item parentItem = null, bool createNetworkEvents = true) { ItemPrefab itemPrefab; string itemIdentifier = itemElement.GetAttributeString("identifier", ""); itemPrefab = MapEntityPrefab.FindByIdentifier(itemIdentifier.ToIdentifier()) as ItemPrefab; if (itemPrefab == null) { - DebugConsole.ThrowError("Tried to spawn \"" + humanPrefab?.Identifier + "\" with the item \"" + itemIdentifier + "\". Matching item prefab not found."); + DebugConsole.ThrowError("Tried to spawn \"" + humanPrefab?.Identifier + "\" with the item \"" + itemIdentifier + "\". Matching item prefab not found.", + contentPackage: itemElement?.ContentPackage); return; } Item item = new Item(itemPrefab, character.Position, null); @@ -301,7 +302,7 @@ namespace Barotrauma wifiComponent.TeamID = character.TeamID; } parentItem?.Combine(item, user: null); - foreach (XElement childItemElement in itemElement.Elements()) + foreach (ContentXElement childItemElement in itemElement.Elements()) { int amount = childItemElement.GetAttributeInt("amount", 1); for (int i = 0; i < amount; i++) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/Job.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/Job.cs index 887743c78..411ff78b0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/Job.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/Job.cs @@ -47,13 +47,14 @@ namespace Barotrauma } } - public Job(XElement element) + public Job(ContentXElement element) { Identifier identifier = element.GetAttributeIdentifier("identifier", ""); JobPrefab p; if (!JobPrefab.Prefabs.ContainsKey(identifier)) { - DebugConsole.ThrowError($"Could not find the job {identifier}. Giving the character a random job."); + DebugConsole.ThrowError($"Could not find the job {identifier}. Giving the character a random job.", + contentPackage: element.ContentPackage); p = JobPrefab.Random(Rand.RandSync.Unsynced); } else diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/JobPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/JobPrefab.cs index e311800bc..e93e38566 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/JobPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/JobPrefab.cs @@ -43,7 +43,8 @@ namespace Barotrauma Priority = element.GetAttributeFloat("priority", -1f); if (Priority < 0) { - DebugConsole.AddWarning($"The 'priority' attribute is missing from the the item repair priorities definition in {element} of {file.Path}."); + DebugConsole.AddWarning($"The 'priority' attribute is missing from the the item repair priorities definition in {element} of {file.Path}.", + ContentPackage); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Animation/AnimationParams.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Animation/AnimationParams.cs index f2859aaa2..d88356d60 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Animation/AnimationParams.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Animation/AnimationParams.cs @@ -266,7 +266,8 @@ namespace Barotrauma } else { - DebugConsole.ThrowError($"[AnimationParams] Failed to load an animation {a} at {selectedFile} of type {animType} for the character {speciesName}"); + DebugConsole.ThrowError($"[AnimationParams] Failed to load an animation {a} at {selectedFile} of type {animType} for the character {speciesName}", + contentPackage: characterPrefab.ContentPackage); } return a; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Animation/FishAnimations.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Animation/FishAnimations.cs index e902ba353..6a5c7aab6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Animation/FishAnimations.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Animation/FishAnimations.cs @@ -63,7 +63,8 @@ namespace Barotrauma { if (!character.AnimController.CanWalk) { - DebugConsole.ThrowError($"{character.SpeciesName} cannot use run animations!"); + DebugConsole.ThrowError($"{character.SpeciesName} cannot use run animations!", + contentPackage: character.Prefab.ContentPackage); return false; } return true; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/CharacterParams.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/CharacterParams.cs index 0fedaedf3..289940df0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/CharacterParams.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/CharacterParams.cs @@ -50,6 +50,9 @@ namespace Barotrauma [Serialize(false, IsPropertySaveable.Yes, description: "Can the creature live without water or does it die on dry land?"), Editable] public bool NeedsWater { get; set; } + [Serialize(false, IsPropertySaveable.Yes, description: "Note: non-humans with a human AI aren't fully supported. Enabling this on a non-human character may lead to issues.")] + public bool UseHumanAI { get; set; } + [Serialize(false, IsPropertySaveable.Yes, description: "Is this creature an artificial creature, like robot or machine that shouldn't be affected by afflictions that affect only organic creatures? Overrides DoesBleed."), Editable] public bool IsMachine { get; set; } @@ -556,7 +559,8 @@ namespace Barotrauma DebugConsole.AddWarning($"Character \"{character.SpeciesName}\" has a negative crush depth. "+ "Previously the crush depths were defined as display units (e.g. -30000 would correspond to 300 meters below the level), "+ "but now they're in meters (e.g. 3000 would correspond to a depth of 3000 meters displayed on the nav terminal). "+ - $"Changing the crush depth from {CrushDepth} to {newCrushDepth}."); + $"Changing the crush depth from {CrushDepth} to {newCrushDepth}.", + element.ContentPackage); CrushDepth = newCrushDepth; } } @@ -708,7 +712,8 @@ namespace Barotrauma if (HasTag(tag)) { target = null; - DebugConsole.AddWarning($"Trying to add multiple targets with the same tag ('{tag}') defined! Only the first will be used!"); + DebugConsole.AddWarning($"Trying to add multiple targets with the same tag ('{tag}') defined! Only the first will be used!", + targetElement.ContentPackage); return false; } else diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/EditableParams.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/EditableParams.cs index 03f56b285..72ad781a8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/EditableParams.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/EditableParams.cs @@ -84,12 +84,14 @@ namespace Barotrauma doc = XMLExtensions.TryLoadXml(Path); if (doc == null) { - DebugConsole.ThrowError("[EditableParams] The document is null! Failed to load the parameters."); + DebugConsole.ThrowError("[EditableParams] The document is null! Failed to load the parameters.", + contentPackage: file.ContentPackage); return false; } if (MainElement == null) { - DebugConsole.ThrowError("[EditableParams] The main element is null! Failed to load the parameters."); + DebugConsole.ThrowError("[EditableParams] The main element is null! Failed to load the parameters.", + contentPackage: file.ContentPackage); return false; } IsLoaded = Deserialize(MainElement); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Ragdoll/RagdollParams.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Ragdoll/RagdollParams.cs index 9187430ae..26e8ccfec 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Ragdoll/RagdollParams.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Ragdoll/RagdollParams.cs @@ -106,7 +106,8 @@ namespace Barotrauma CharacterPrefab prefab = CharacterPrefab.Find(p => p.Identifier == speciesName && (contentPackage == null || p.ContentFile.ContentPackage == contentPackage)); if (prefab?.ConfigElement == null) { - DebugConsole.ThrowError($"Failed to find config file for '{speciesName}' (content package {contentPackage?.Name ?? "null"})"); + DebugConsole.ThrowError($"Failed to find config file for '{speciesName}'", + contentPackage: contentPackage); return string.Empty; } return GetFolder(prefab.ConfigElement, prefab.ContentFile.Path.Value); @@ -183,7 +184,8 @@ namespace Barotrauma } if (error != null) { - DebugConsole.ThrowError(error); + DebugConsole.ThrowError(error, + contentPackage: prefab?.ContentPackage); } } if (selectedFile == null) @@ -444,7 +446,8 @@ namespace Barotrauma { if (source.MainElement == null) { - DebugConsole.ThrowError("[RagdollParams] The source XML Element of the given RagdollParams is null!"); + DebugConsole.ThrowError("[RagdollParams] The source XML Element of the given RagdollParams is null!", + contentPackage: source.MainElement?.ContentPackage); return; } Deserialize(source.MainElement, alsoChildren: false); @@ -453,7 +456,8 @@ namespace Barotrauma // TODO: cannot currently undo joint/limb deletion. if (sourceSubParams.Count != subParams.Count) { - DebugConsole.ThrowError("[RagdollParams] The count of the sub params differs! Failed to revert to the previous snapshot! Please reset the ragdoll to undo the changes."); + DebugConsole.ThrowError("[RagdollParams] The count of the sub params differs! Failed to revert to the previous snapshot! Please reset the ragdoll to undo the changes.", + contentPackage: source.MainElement?.ContentPackage); return; } for (int i = 0; i < subParams.Count; i++) @@ -461,7 +465,8 @@ namespace Barotrauma var subSubParams = subParams[i].SubParams; if (subSubParams.Count != sourceSubParams[i].SubParams.Count) { - DebugConsole.ThrowError("[RagdollParams] The count of the sub sub params differs! Failed to revert to the previous snapshot! Please reset the ragdoll to undo the changes."); + DebugConsole.ThrowError("[RagdollParams] The count of the sub sub params differs! Failed to revert to the previous snapshot! Please reset the ragdoll to undo the changes.", + contentPackage: source.MainElement?.ContentPackage); return; } subParams[i].Deserialize(sourceSubParams[i].Element, recursive: false); @@ -890,14 +895,14 @@ namespace Barotrauma #if CLIENT public DecorativeSprite DecorativeSprite { get; private set; } - public override bool Deserialize(XElement element = null, bool recursive = true) + public override bool Deserialize(ContentXElement element = null, bool recursive = true) { base.Deserialize(element, recursive); DecorativeSprite.SerializableProperties = SerializableProperty.DeserializeProperties(DecorativeSprite, element ?? Element); return SerializableProperties != null; } - public override bool Serialize(XElement element = null, bool recursive = true) + public override bool Serialize(ContentXElement element = null, bool recursive = true) { base.Serialize(element, recursive); SerializableProperty.SerializeProperties(DecorativeSprite, element ?? Element); @@ -985,7 +990,8 @@ namespace Barotrauma deformation = new PositionalDeformationParams(deformationElement); break; default: - DebugConsole.ThrowError($"SpriteDeformationParams not implemented: '{typeName}'"); + DebugConsole.ThrowError($"SpriteDeformationParams not implemented: '{typeName}'", + contentPackage: element.ContentPackage); break; } if (deformation != null) @@ -1000,14 +1006,14 @@ namespace Barotrauma #if CLIENT public Dictionary Deformations { get; private set; } - public override bool Deserialize(XElement element = null, bool recursive = true) + public override bool Deserialize(ContentXElement element = null, bool recursive = true) { base.Deserialize(element, recursive); Deformations.ForEach(d => d.Key.SerializableProperties = SerializableProperty.DeserializeProperties(d.Key, d.Value)); return SerializableProperties != null; } - public override bool Serialize(XElement element = null, bool recursive = true) + public override bool Serialize(ContentXElement element = null, bool recursive = true) { base.Serialize(element, recursive); Deformations.ForEach(d => SerializableProperty.SerializeProperties(d.Key, d.Value)); @@ -1098,14 +1104,14 @@ namespace Barotrauma } #if CLIENT - public override bool Deserialize(XElement element = null, bool recursive = true) + public override bool Deserialize(ContentXElement element = null, bool recursive = true) { base.Deserialize(element, recursive); LightSource.Deserialize(element ?? Element); return SerializableProperties != null; } - public override bool Serialize(XElement element = null, bool recursive = true) + public override bool Serialize(ContentXElement element = null, bool recursive = true) { base.Serialize(element, recursive); LightSource.Serialize(element ?? Element); @@ -1130,14 +1136,14 @@ namespace Barotrauma Attack = new Attack(element, ragdoll.SpeciesName.Value); } - public override bool Deserialize(XElement element = null, bool recursive = true) + public override bool Deserialize(ContentXElement element = null, bool recursive = true) { base.Deserialize(element, recursive); Attack.Deserialize(element ?? Element, parentDebugName: Ragdoll?.SpeciesName.ToString() ?? "null"); return SerializableProperties != null; } - public override bool Serialize(XElement element = null, bool recursive = true) + public override bool Serialize(ContentXElement element = null, bool recursive = true) { base.Serialize(element, recursive); Attack.Serialize(element ?? Element); @@ -1182,14 +1188,14 @@ namespace Barotrauma DamageModifier = new DamageModifier(element, ragdoll.SpeciesName.Value); } - public override bool Deserialize(XElement element = null, bool recursive = true) + public override bool Deserialize(ContentXElement element = null, bool recursive = true) { base.Deserialize(element, recursive); DamageModifier.Deserialize(element ?? Element); return SerializableProperties != null; } - public override bool Serialize(XElement element = null, bool recursive = true) + public override bool Serialize(ContentXElement element = null, bool recursive = true) { base.Serialize(element, recursive); DamageModifier.Serialize(element ?? Element); @@ -1218,7 +1224,7 @@ namespace Barotrauma public virtual string Name { get; set; } public Dictionary SerializableProperties { get; private set; } public ContentXElement Element { get; set; } - public XElement OriginalElement { get; protected set; } + public ContentXElement OriginalElement { get; protected set; } public List SubParams { get; set; } = new List(); public RagdollParams Ragdoll { get; private set; } @@ -1230,14 +1236,14 @@ namespace Barotrauma public SubParam(ContentXElement element, RagdollParams ragdoll) { Element = element; - OriginalElement = new XElement(element); + OriginalElement = new ContentXElement(element.ContentPackage, element); Ragdoll = ragdoll; SerializableProperties = SerializableProperty.DeserializeProperties(this, element); } - public virtual bool Deserialize(XElement element = null, bool recursive = true) + public virtual bool Deserialize(ContentXElement element = null, bool recursive = true) { - element = element ?? Element; + element ??= Element; SerializableProperties = SerializableProperty.DeserializeProperties(this, element); if (recursive) { @@ -1246,9 +1252,9 @@ namespace Barotrauma return SerializableProperties != null; } - public virtual bool Serialize(XElement element = null, bool recursive = true) + public virtual bool Serialize(ContentXElement element = null, bool recursive = true) { - element = element ?? Element; + element ??= Element; SerializableProperty.SerializeProperties(this, element, true); if (recursive) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityCondition.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityCondition.cs index ad4b4df25..5b74ede1e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityCondition.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityCondition.cs @@ -13,7 +13,7 @@ namespace Barotrauma.Abilities public AbilityCondition(CharacterTalent characterTalent, ContentXElement conditionElement) { - this.characterTalent = characterTalent; + this.characterTalent = characterTalent ?? throw new ArgumentNullException(nameof(characterTalent)); character = characterTalent.Character; invert = conditionElement.GetAttributeBool("invert", false); } @@ -40,7 +40,8 @@ namespace Barotrauma.Abilities { if (!Enum.TryParse(targetTypeString, true, out TargetType targetType)) { - DebugConsole.ThrowError("Invalid target type type \"" + targetTypeString + "\" in CharacterTalent (" + characterTalent.DebugIdentifier + ")"); + DebugConsole.ThrowError("Invalid target type type \"" + targetTypeString + "\" in CharacterTalent (" + characterTalent.DebugIdentifier + ")", + contentPackage: characterTalent.Prefab.ContentPackage); } targetTypes.Add(targetType); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionAffliction.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionAffliction.cs index ebd077561..961cfcf71 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionAffliction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionAffliction.cs @@ -1,7 +1,4 @@ -using Barotrauma.Items.Components; -using System.Collections.Generic; -using System.Linq; -using System.Xml.Linq; +using System.Linq; namespace Barotrauma.Abilities { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionAttackData.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionAttackData.cs index 77cc53bad..905adea68 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionAttackData.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionAttackData.cs @@ -34,7 +34,8 @@ namespace Barotrauma.Abilities string weaponTypeStr = conditionElement.GetAttributeString("weapontype", "Any"); if (!Enum.TryParse(weaponTypeStr, ignoreCase: true, out weapontype)) { - DebugConsole.ThrowError($"Error in talent \"{characterTalent.DebugIdentifier}\": \"{weaponTypeStr}\" is not a valid weapon type."); + DebugConsole.ThrowError($"Error in talent \"{characterTalent.DebugIdentifier}\": \"{weaponTypeStr}\" is not a valid weapon type.", + contentPackage: conditionElement.ContentPackage); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionCharacter.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionCharacter.cs index 26af153b2..8b8fe05cb 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionCharacter.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionCharacter.cs @@ -17,7 +17,7 @@ namespace Barotrauma.Abilities conditionElement.GetAttributeStringArray("targettypes", conditionElement.GetAttributeStringArray("targettype", Array.Empty()))); - foreach (XElement subElement in conditionElement.Elements()) + foreach (ContentXElement subElement in conditionElement.Elements()) { if (subElement.NameAsIdentifier() == "conditional") { @@ -27,7 +27,8 @@ namespace Barotrauma.Abilities if (!targetTypes.Any() && !conditionals.Any()) { - DebugConsole.ThrowError($"Error in talent \"{characterTalent}\". No target types or conditionals defined - the condition will match any character."); + DebugConsole.ThrowError($"Error in talent \"{characterTalent}\". No target types or conditionals defined - the condition will match any character.", + contentPackage: conditionElement.ContentPackage); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionData.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionData.cs index 426156bec..c8d3495de 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionData.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionData.cs @@ -17,13 +17,15 @@ namespace Barotrauma.Abilities protected void LogAbilityConditionError(AbilityObject abilityObject, Type expectedData) { - DebugConsole.ThrowError($"Used data-reliant ability condition when data is incompatible! Expected {expectedData}, but received {abilityObject} in talent {characterTalent.DebugIdentifier}"); + DebugConsole.ThrowError($"Used data-reliant ability condition when data is incompatible! Expected {expectedData}, but received {abilityObject} in talent {characterTalent.DebugIdentifier}", + contentPackage: characterTalent.Prefab.ContentPackage); } protected abstract bool MatchesConditionSpecific(AbilityObject abilityObject); public override bool MatchesCondition() { - DebugConsole.ThrowError($"Used data-reliant ability condition in a state-based ability in talent {characterTalent.DebugIdentifier}! This is not allowed."); + DebugConsole.ThrowError($"Used data-reliant ability condition in a state-based ability in talent {characterTalent.DebugIdentifier}! This is not allowed.", + contentPackage: characterTalent.Prefab.ContentPackage); return false; } public override bool MatchesCondition(AbilityObject abilityObject) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionItem.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionItem.cs index e4580fadd..309c60d80 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionItem.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionItem.cs @@ -19,7 +19,8 @@ namespace Barotrauma.Abilities if (identifiers.None() && tags.None() && category == MapEntityCategory.None) { - DebugConsole.ThrowError($"Error in talent \"{characterTalent}\". No identifiers, tags or category defined."); + DebugConsole.ThrowError($"Error in talent \"{characterTalent}\". No identifiers, tags or category defined.", + contentPackage: conditionElement.ContentPackage); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionMission.cs index f92523e10..71ae4f7dd 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionMission.cs @@ -22,7 +22,8 @@ namespace Barotrauma.Abilities { if (!isAffiliated) { - DebugConsole.ThrowError($"Error in AbilityConditionMission \"{characterTalent.DebugIdentifier}\" - \"{missionTypeString}\" is not a valid mission type."); + DebugConsole.ThrowError($"Error in AbilityConditionMission \"{characterTalent.DebugIdentifier}\" - \"{missionTypeString}\" is not a valid mission type.", + contentPackage: conditionElement.ContentPackage); } continue; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionHasPermanentStat.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionHasPermanentStat.cs index 237e15b5f..96ed33dab 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionHasPermanentStat.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionHasPermanentStat.cs @@ -12,7 +12,8 @@ statIdentifier = conditionElement.GetAttributeIdentifier("statidentifier", Identifier.Empty); if (statIdentifier.IsEmpty) { - DebugConsole.ThrowError($"No stat identifier defined for {this} in talent {characterTalent.DebugIdentifier}!"); + DebugConsole.ThrowError($"No stat identifier defined for {this} in talent {characterTalent.DebugIdentifier}!", + contentPackage: conditionElement.ContentPackage); } string statTypeName = conditionElement.GetAttributeString("stattype", string.Empty); statType = string.IsNullOrEmpty(statTypeName) ? StatTypes.None : CharacterAbilityGroup.ParseStatType(statTypeName, characterTalent.DebugIdentifier); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionHasStatusTag.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionHasStatusTag.cs index 2ee0a66ee..c9a48c4eb 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionHasStatusTag.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionHasStatusTag.cs @@ -13,7 +13,8 @@ namespace Barotrauma.Abilities tag = conditionElement.GetAttributeIdentifier("tag", Identifier.Empty); if (tag.IsEmpty) { - DebugConsole.AddWarning($"Error in talent \"{characterTalent.Prefab.OriginalName}\" - tag not defined in AbilityConditionHasStatusTag."); + DebugConsole.AddWarning($"Error in talent \"{characterTalent.Prefab.OriginalName}\" - tag not defined in AbilityConditionHasStatusTag.", + characterTalent.Prefab.ContentPackage); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbility.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbility.cs index 5a7d22598..7ccc4e036 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbility.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbility.cs @@ -26,7 +26,7 @@ namespace Barotrauma.Abilities public CharacterAbility(CharacterAbilityGroup characterAbilityGroup, ContentXElement abilityElement) { - CharacterAbilityGroup = characterAbilityGroup; + CharacterAbilityGroup = characterAbilityGroup ?? throw new ArgumentNullException(nameof(characterAbilityGroup)); CharacterTalent = characterAbilityGroup.CharacterTalent; Character = CharacterTalent.Character; RequiresAlive = abilityElement.GetAttributeBool("requiresalive", true); @@ -59,7 +59,8 @@ namespace Barotrauma.Abilities protected virtual void VerifyState(bool conditionsMatched, float timeSinceLastUpdate) { - DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}: Ability {this} does not have an implementation for VerifyState! This ability does not work in interval ability groups."); + DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}: Ability {this} does not have an implementation for VerifyState! This ability does not work in interval ability groups.", + contentPackage: CharacterTalent.Prefab.ContentPackage); } public void ApplyAbilityEffect(AbilityObject abilityObject) @@ -76,17 +77,20 @@ namespace Barotrauma.Abilities protected virtual void ApplyEffect() { - DebugConsole.AddWarning($"Ability {this} used improperly! This ability does not have a definition for ApplyEffect in talent {CharacterTalent.DebugIdentifier}"); + DebugConsole.AddWarning($"Ability {this} used improperly! This ability does not have a definition for ApplyEffect in talent {CharacterTalent.DebugIdentifier}", + CharacterTalent.Prefab.ContentPackage); } protected virtual void ApplyEffect(AbilityObject abilityObject) { - DebugConsole.AddWarning($"Ability {this} used improperly! This ability does not take a parameter for ApplyEffect in talent {CharacterTalent.DebugIdentifier}"); + DebugConsole.AddWarning($"Ability {this} used improperly! This ability does not take a parameter for ApplyEffect in talent {CharacterTalent.DebugIdentifier}", + CharacterTalent.Prefab.ContentPackage); } protected void LogAbilityObjectMismatch() { - DebugConsole.ThrowError($"Incompatible ability! Ability {this} is incompatitible with this type of ability effect type in talent {CharacterTalent.DebugIdentifier}"); + DebugConsole.ThrowError($"Incompatible ability! Ability {this} is incompatitible with this type of ability effect type in talent {CharacterTalent.DebugIdentifier}", + contentPackage: CharacterTalent.Prefab.ContentPackage); } // XML @@ -99,13 +103,18 @@ namespace Barotrauma.Abilities abilityType = Type.GetType("Barotrauma.Abilities." + type + "", false, true); if (abilityType == null) { - if (errorMessages) DebugConsole.ThrowError("Could not find the CharacterAbility \"" + type + "\" (" + characterAbilityGroup.CharacterTalent.DebugIdentifier + ")"); + if (errorMessages) DebugConsole.ThrowError("Could not find the CharacterAbility \"" + type + "\" (" + characterAbilityGroup.CharacterTalent.DebugIdentifier + ")", + contentPackage: abilityElement.ContentPackage); return null; } } catch (Exception e) { - if (errorMessages) DebugConsole.ThrowError("Could not find the CharacterAbility \"" + type + "\" (" + characterAbilityGroup.CharacterTalent.DebugIdentifier + ")", e); + if (errorMessages) + { + DebugConsole.ThrowError("Could not find the CharacterAbility \"" + type + "\" (" + characterAbilityGroup.CharacterTalent.DebugIdentifier + ")", e, + contentPackage: abilityElement.ContentPackage); + } return null; } @@ -118,7 +127,8 @@ namespace Barotrauma.Abilities } catch (TargetInvocationException e) { - DebugConsole.ThrowError("Error while creating an instance of a CharacterAbility of the type " + abilityType + ".", e.InnerException); + DebugConsole.ThrowError("Error while creating an instance of a CharacterAbility of the type " + abilityType + ".", e.InnerException, + contentPackage: abilityElement.ContentPackage); return null; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyForce.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyForce.cs index 0b350c514..0ca9c74d0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyForce.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyForce.cs @@ -30,7 +30,8 @@ namespace Barotrauma.Abilities } else { - DebugConsole.ThrowError($"Error in talent \"{characterAbilityGroup.CharacterTalent.DebugIdentifier}\" - \"{limbTypeStr}\" is not a valid limb type."); + DebugConsole.ThrowError($"Error in talent \"{characterAbilityGroup.CharacterTalent.DebugIdentifier}\" - \"{limbTypeStr}\" is not a valid limb type.", + contentPackage: abilityElement.ContentPackage); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffectsToApprenticeship.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffectsToApprenticeship.cs index 27d4afe94..453ad0d09 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffectsToApprenticeship.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffectsToApprenticeship.cs @@ -21,7 +21,8 @@ namespace Barotrauma.Abilities JobPrefab? apprenticeJob = GetApprenticeJob(Character, jobPrefabList); if (apprenticeJob is null) { - DebugConsole.ThrowError($"{nameof(CharacterAbilityUnlockApprenticeshipTalentTree)}: Could not find apprentice job for character {Character.Name}"); + DebugConsole.ThrowError($"{nameof(CharacterAbilityUnlockApprenticeshipTalentTree)}: Could not find apprentice job for character {Character.Name}", + contentPackage: CharacterTalent.Prefab.ContentPackage); return; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGainSimultaneousSkill.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGainSimultaneousSkill.cs index 0cd2b4857..2b98992fa 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGainSimultaneousSkill.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGainSimultaneousSkill.cs @@ -14,7 +14,8 @@ targetAllies = abilityElement.GetAttributeBool("targetallies", false); if (skillIdentifier.IsEmpty) { - DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}: skill identifier not defined."); + DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}: skill identifier not defined.", + contentPackage: abilityElement.ContentPackage); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveAffliction.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveAffliction.cs index 13bae5e96..3b9653393 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveAffliction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveAffliction.cs @@ -16,7 +16,8 @@ if (afflictionId.IsEmpty) { - DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, CharacterAbilityGiveAffliction - affliction identifier not set."); + DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, CharacterAbilityGiveAffliction - affliction identifier not set.", + contentPackage: abilityElement.ContentPackage); } } @@ -27,7 +28,8 @@ var afflictionPrefab = AfflictionPrefab.Prefabs.Find(a => a.Identifier == afflictionId); if (afflictionPrefab == null) { - DebugConsole.ThrowError($"Error in CharacterAbilityGiveAffliction - could not find an affliction with the identifier \"{afflictionId}\"."); + DebugConsole.ThrowError($"Error in CharacterAbilityGiveAffliction - could not find an affliction with the identifier \"{afflictionId}\".", + contentPackage: CharacterTalent.Prefab.ContentPackage); return; } float strength = this.strength; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveExperience.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveExperience.cs index 5686f777a..56807217a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveExperience.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveExperience.cs @@ -14,23 +14,25 @@ internal sealed class CharacterAbilityGiveExperience : CharacterAbility if (amount == 0 && level == 0) { - DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier} - no exp amount or level defined in {nameof(CharacterAbilityGiveExperience)}."); + DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier} - no exp amount or level defined in {nameof(CharacterAbilityGiveExperience)}.", + contentPackage: abilityElement.ContentPackage); } if (amount > 0 && level > 0) { - DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier} - {nameof(CharacterAbilityGiveExperience)} defines both an exp amount and a level."); + DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier} - {nameof(CharacterAbilityGiveExperience)} defines both an exp amount and a level.", + contentPackage: abilityElement.ContentPackage); } } private void ApplyEffectSpecific(Character targetCharacter) { - if (amount != 0) - { - targetCharacter.Info?.GiveExperience(amount); - } if (level > 0) { - targetCharacter.Info?.GiveExperience(targetCharacter.Info.GetExperienceRequiredForLevel(level)); + targetCharacter.Info?.GiveExperience(targetCharacter.Info.GetExperienceRequiredForLevel(level) + amount); + } + else if (amount != 0) + { + targetCharacter.Info?.GiveExperience(amount); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveMoney.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveMoney.cs index a45e92b1d..2b5f00421 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveMoney.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveMoney.cs @@ -14,7 +14,8 @@ if (amount == 0) { - DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, CharacterAbilityGiveMoney - amount of money set to 0."); + DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, CharacterAbilityGiveMoney - amount of money set to 0.", + contentPackage: abilityElement.ContentPackage); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGivePermanentStat.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGivePermanentStat.cs index a2a94cf37..c046863a6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGivePermanentStat.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGivePermanentStat.cs @@ -28,7 +28,8 @@ statIdentifier = abilityElement.GetAttributeIdentifier("statidentifier", Identifier.Empty); if (statIdentifier.IsEmpty) { - DebugConsole.ThrowError($"Error in talent \"{CharacterTalent.DebugIdentifier}\" - stat identifier not defined."); + DebugConsole.ThrowError($"Error in talent \"{CharacterTalent.DebugIdentifier}\" - stat identifier not defined.", + contentPackage: abilityElement.ContentPackage); } string statTypeName = abilityElement.GetAttributeString("stattype", string.Empty); statType = string.IsNullOrEmpty(statTypeName) ? StatTypes.None : CharacterAbilityGroup.ParseStatType(statTypeName, CharacterTalent.DebugIdentifier); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveReputation.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveReputation.cs index 6d3777d53..a0bfb0f34 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveReputation.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveReputation.cs @@ -13,11 +13,13 @@ namespace Barotrauma.Abilities amount = abilityElement.GetAttributeFloat("amount", 0f); if (factionIdentifier.IsEmpty) { - DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, faction identifier not defined."); + DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, faction identifier not defined.", + contentPackage: abilityElement.ContentPackage); } if (amount == 0) { - DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, amount of reputation to give is 0."); + DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, amount of reputation to give is 0.", + contentPackage: abilityElement.ContentPackage); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveResistance.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveResistance.cs index 9df7fc87b..062032ca7 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveResistance.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveResistance.cs @@ -12,11 +12,13 @@ if (resistanceId.IsEmpty) { - DebugConsole.ThrowError("Error in CharacterAbilityGiveResistance - resistance identifier not set."); + DebugConsole.ThrowError("Error in CharacterAbilityGiveResistance - resistance identifier not set.", + contentPackage: abilityElement.ContentPackage); } if (MathUtils.NearlyEqual(multiplier, 1)) { - DebugConsole.AddWarning($"Possible error in talent {CharacterTalent.DebugIdentifier} - multiplier set to 1, which will do nothing."); + DebugConsole.AddWarning($"Possible error in talent {CharacterTalent.DebugIdentifier} - multiplier set to 1, which will do nothing.", + contentPackage: abilityElement.ContentPackage); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveTalentPoints.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveTalentPoints.cs index e61e3981b..ea40eb130 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveTalentPoints.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveTalentPoints.cs @@ -9,7 +9,8 @@ amount = abilityElement.GetAttributeInt("amount", 0); if (amount == 0) { - DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, amount of talent points to give is 0."); + DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, amount of talent points to give is 0.", + contentPackage: abilityElement.ContentPackage); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveTalentPointsToAllies.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveTalentPointsToAllies.cs index 2b4dd4cac..f3f2d91cc 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveTalentPointsToAllies.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveTalentPointsToAllies.cs @@ -11,7 +11,8 @@ namespace Barotrauma.Abilities amount = abilityElement.GetAttributeInt("amount", 0); if (amount == 0) { - DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, amount of talent points to give is 0."); + DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, amount of talent points to give is 0.", + contentPackage: abilityElement.ContentPackage); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityIncreaseSkill.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityIncreaseSkill.cs index 2e1816bd4..4618bcbeb 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityIncreaseSkill.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityIncreaseSkill.cs @@ -16,11 +16,13 @@ namespace Barotrauma.Abilities if (skillIdentifier.IsEmpty) { - DebugConsole.ThrowError($"Error in talent \"{characterAbilityGroup.CharacterTalent.DebugIdentifier}\" - skill identifier not defined in CharacterAbilityIncreaseSkill."); + DebugConsole.ThrowError($"Error in talent \"{characterAbilityGroup.CharacterTalent.DebugIdentifier}\" - skill identifier not defined in CharacterAbilityIncreaseSkill.", + contentPackage: abilityElement.ContentPackage); } if (MathUtils.NearlyEqual(skillIncrease, 0)) { - DebugConsole.AddWarning($"Possible error in talent \"{characterAbilityGroup.CharacterTalent.DebugIdentifier}\" - skill increase set to 0."); + DebugConsole.AddWarning($"Possible error in talent \"{characterAbilityGroup.CharacterTalent.DebugIdentifier}\" - skill increase set to 0.", + contentPackage: abilityElement.ContentPackage); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityMarkAsLooted.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityMarkAsLooted.cs index 45ddb19fb..703b07c48 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityMarkAsLooted.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityMarkAsLooted.cs @@ -8,7 +8,8 @@ namespace Barotrauma.Abilities identifier = abilityElement.GetAttributeIdentifier("identifier", Identifier.Empty); if (identifier.IsEmpty) { - DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, identifier is empty in {nameof(CharacterAbilityMarkAsLooted)}."); + DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, identifier is empty in {nameof(CharacterAbilityMarkAsLooted)}.", + contentPackage: abilityElement.ContentPackage); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyResistance.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyResistance.cs index 3c1ec2272..4cee5bb66 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyResistance.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyResistance.cs @@ -15,11 +15,13 @@ if (resistanceId.IsEmpty) { - DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier} - resistance identifier not set in {nameof(CharacterAbilityModifyResistance)}."); + DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier} - resistance identifier not set in {nameof(CharacterAbilityModifyResistance)}.", + contentPackage: abilityElement.ContentPackage); } if (MathUtils.NearlyEqual(multiplier, 1.0f)) { - DebugConsole.AddWarning($"Possible error in talent {CharacterTalent.DebugIdentifier} - resistance set to 1, which will do nothing."); + DebugConsole.AddWarning($"Possible error in talent {CharacterTalent.DebugIdentifier} - resistance set to 1, which will do nothing.", + contentPackage: abilityElement.ContentPackage); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyValue.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyValue.cs index 57ed31b3b..4168ec6a1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyValue.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyValue.cs @@ -11,7 +11,8 @@ multiplyValue = abilityElement.GetAttributeFloat("multiplyvalue", 1f); if (MathUtils.NearlyEqual(addedValue, 0.0f) && MathUtils.NearlyEqual(multiplyValue, 1.0f)) { - DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, {nameof(CharacterAbilityModifyValue)} - added value is 0 and multiplier is 1, the ability will do nothing."); + DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, {nameof(CharacterAbilityModifyValue)} - added value is 0 and multiplier is 1, the ability will do nothing.", + contentPackage: abilityElement.ContentPackage); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityPutItem.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityPutItem.cs index ba7ef06eb..346c075b7 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityPutItem.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityPutItem.cs @@ -11,7 +11,8 @@ amount = abilityElement.GetAttributeInt("amount", 1); if (itemIdentifier.IsEmpty) { - DebugConsole.ThrowError($"Error in talent \"{characterAbilityGroup.CharacterTalent.DebugIdentifier}\" - itemIdentifier not defined."); + DebugConsole.ThrowError($"Error in talent \"{characterAbilityGroup.CharacterTalent.DebugIdentifier}\" - itemIdentifier not defined.", + contentPackage: abilityElement.ContentPackage); } } @@ -19,14 +20,16 @@ { if (itemIdentifier.IsEmpty) { - DebugConsole.ThrowError("Cannot put item in inventory - itemIdentifier not defined."); + DebugConsole.ThrowError("Cannot put item in inventory - itemIdentifier not defined.", + contentPackage: CharacterTalent.Prefab.ContentPackage); return; } ItemPrefab itemPrefab = ItemPrefab.Find(null, itemIdentifier); if (itemPrefab == null) { - DebugConsole.ThrowError("Cannot put item in inventory - item prefab " + itemIdentifier + " not found."); + DebugConsole.ThrowError("Cannot put item in inventory - item prefab " + itemIdentifier + " not found.", + contentPackage: CharacterTalent.Prefab.ContentPackage); return; } for (int i = 0; i < amount; i++) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityReduceAffliction.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityReduceAffliction.cs index 14a84d337..4f64d8bfd 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityReduceAffliction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityReduceAffliction.cs @@ -14,7 +14,8 @@ namespace Barotrauma.Abilities if (afflictionId.IsEmpty) { - DebugConsole.ThrowError($"Error in {nameof(CharacterAbilityReduceAffliction)} - affliction identifier not set."); + DebugConsole.ThrowError($"Error in {nameof(CharacterAbilityReduceAffliction)} - affliction identifier not set.", + contentPackage: abilityElement.ContentPackage); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityResetPermanentStat.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityResetPermanentStat.cs index 3e85a16dd..e1dac6415 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityResetPermanentStat.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityResetPermanentStat.cs @@ -12,7 +12,8 @@ namespace Barotrauma.Abilities statIdentifier = abilityElement.GetAttributeIdentifier("statidentifier", Identifier.Empty); if (statIdentifier.IsEmpty) { - DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, {nameof(CharacterAbilityResetPermanentStat)} - statIdentifier is empty."); + DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, {nameof(CharacterAbilityResetPermanentStat)} - statIdentifier is empty.", + contentPackage: abilityElement.ContentPackage); } } protected override void ApplyEffect(AbilityObject abilityObject) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilitySetMetadataInt.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilitySetMetadataInt.cs index 3953cce9f..daa34f959 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilitySetMetadataInt.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilitySetMetadataInt.cs @@ -13,7 +13,8 @@ namespace Barotrauma.Abilities value = abilityElement.GetAttributeInt("value", 0); if (identifier.IsEmpty) { - DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, {nameof(CharacterAbilitySetMetadataInt)} - identifier is empty."); + DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, {nameof(CharacterAbilitySetMetadataInt)} - identifier is empty.", + contentPackage: abilityElement.ContentPackage); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityUnlockApprenticeshipTalentTree.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityUnlockApprenticeshipTalentTree.cs index 7a4ceeb07..7ce696992 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityUnlockApprenticeshipTalentTree.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityUnlockApprenticeshipTalentTree.cs @@ -24,7 +24,8 @@ namespace Barotrauma.Abilities JobPrefab? apprentice = CharacterAbilityApplyStatusEffectsToApprenticeship.GetApprenticeJob(Character, JobPrefab.Prefabs.ToImmutableHashSet()); if (apprentice is null) { - DebugConsole.ThrowError($"{nameof(CharacterAbilityUnlockApprenticeshipTalentTree)}: Could not find apprentice job for character {Character.Name}"); + DebugConsole.ThrowError($"{nameof(CharacterAbilityUnlockApprenticeshipTalentTree)}: Could not find apprentice job for character {Character.Name}", + contentPackage: CharacterTalent.Prefab.ContentPackage); return; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroup.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroup.cs index 6b59a5825..cb33943db 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroup.cs @@ -31,7 +31,7 @@ namespace Barotrauma.Abilities public CharacterAbilityGroup(AbilityEffectType abilityEffectType, CharacterTalent characterTalent, ContentXElement abilityElementGroup) { AbilityEffectType = abilityEffectType; - CharacterTalent = characterTalent; + CharacterTalent = characterTalent ?? throw new ArgumentNullException(nameof(characterTalent)); Character = CharacterTalent.Character; maxTriggerCount = abilityElementGroup.GetAttributeInt("maxtriggercount", int.MaxValue); foreach (var subElement in abilityElementGroup.Elements()) @@ -55,7 +55,8 @@ namespace Barotrauma.Abilities case AbilityEffectType.OnDieToCharacter: if (characterAbilities.Any(a => a.RequiresAlive)) { - DebugConsole.AddWarning($"Potential error in talent {characterTalent}: an ability group has the type {AbilityEffectType.OnDieToCharacter}, but includes abilities that require the character to be alive, meaning they will never execute."); + DebugConsole.AddWarning($"Potential error in talent {characterTalent}: an ability group has the type {AbilityEffectType.OnDieToCharacter}, but includes abilities that require the character to be alive, meaning they will never execute.", + characterTalent.Prefab.ContentPackage); } break; } @@ -90,7 +91,8 @@ namespace Barotrauma.Abilities if (newCondition == null) { - DebugConsole.ThrowError($"AbilityCondition was not found in talent {CharacterTalent.DebugIdentifier}!"); + DebugConsole.ThrowError($"AbilityCondition was not found in talent {CharacterTalent.DebugIdentifier}!", + contentPackage: conditionElement.ContentPackage); return; } @@ -107,7 +109,8 @@ namespace Barotrauma.Abilities { if (characterAbility == null) { - DebugConsole.ThrowError($"Trying to add null ability for talent {CharacterTalent.DebugIdentifier}!"); + DebugConsole.ThrowError($"Trying to add null ability for talent {CharacterTalent.DebugIdentifier}!", + contentPackage: CharacterTalent.Prefab.ContentPackage); return; } @@ -118,7 +121,8 @@ namespace Barotrauma.Abilities { if (characterAbility == null) { - DebugConsole.ThrowError($"Trying to add null ability for talent {CharacterTalent.DebugIdentifier}!"); + DebugConsole.ThrowError($"Trying to add null ability for talent {CharacterTalent.DebugIdentifier}!", + contentPackage: CharacterTalent.Prefab.ContentPackage); return; } @@ -135,13 +139,21 @@ namespace Barotrauma.Abilities conditionType = Type.GetType("Barotrauma.Abilities." + type + "", false, true); if (conditionType == null) { - if (errorMessages) DebugConsole.ThrowError("Could not find the component \"" + type + "\" (" + characterTalent.DebugIdentifier + ")"); + if (errorMessages) + { + DebugConsole.ThrowError("Could not find the component \"" + type + "\" (" + characterTalent.DebugIdentifier + ")", + contentPackage: characterTalent.Prefab.ContentPackage); + } return null; } } catch (Exception e) { - if (errorMessages) DebugConsole.ThrowError("Could not find the component \"" + type + "\" (" + characterTalent.DebugIdentifier + ")", e); + if (errorMessages) + { + DebugConsole.ThrowError("Could not find the component \"" + type + "\" (" + characterTalent.DebugIdentifier + ")", e, + contentPackage: characterTalent.Prefab.ContentPackage); + } return null; } @@ -154,13 +166,15 @@ namespace Barotrauma.Abilities } catch (TargetInvocationException e) { - DebugConsole.ThrowError("Error while creating an instance of an ability condition of the type " + conditionType + ".", e.InnerException); + DebugConsole.ThrowError("Error while creating an instance of an ability condition of the type " + conditionType + ".", e.InnerException, + contentPackage: characterTalent.Prefab.ContentPackage); return null; } if (newCondition == null) { - DebugConsole.ThrowError("Error while creating an instance of an ability condition of the type " + conditionType + ", instance was null"); + DebugConsole.ThrowError("Error while creating an instance of an ability condition of the type " + conditionType + ", instance was null", + contentPackage: characterTalent.Prefab.ContentPackage); return null; } @@ -189,7 +203,8 @@ namespace Barotrauma.Abilities if (newAbility == null) { - DebugConsole.ThrowError($"Unable to create an ability for {characterTalent.DebugIdentifier}!"); + DebugConsole.ThrowError($"Unable to create an ability for {characterTalent.DebugIdentifier}!", + contentPackage: characterTalent.Prefab.ContentPackage); return null; } @@ -200,7 +215,8 @@ namespace Barotrauma.Abilities { if (statusEffectElements == null) { - DebugConsole.ThrowError("StatusEffect list was not found in talent " + characterTalent.DebugIdentifier); + DebugConsole.ThrowError("StatusEffect list was not found in talent " + characterTalent.DebugIdentifier, + contentPackage: characterTalent.Prefab.ContentPackage); return null; } @@ -233,7 +249,8 @@ namespace Barotrauma.Abilities { if (afflictionElements == null) { - DebugConsole.ThrowError("Affliction list was not found in talent " + characterTalent.DebugIdentifier); + DebugConsole.ThrowError("Affliction list was not found in talent " + characterTalent.DebugIdentifier, + contentPackage: characterTalent.Prefab.ContentPackage); return null; } @@ -248,7 +265,8 @@ namespace Barotrauma.Abilities AfflictionPrefab afflictionPrefab = AfflictionPrefab.List.FirstOrDefault(ap => ap.Identifier == afflictionIdentifier); if (afflictionPrefab == null) { - DebugConsole.ThrowError("Error in CharacterTalent (" + characterTalent.DebugIdentifier + ") - Affliction prefab with the identifier \"" + afflictionIdentifier + "\" not found."); + DebugConsole.ThrowError("Error in CharacterTalent (" + characterTalent.DebugIdentifier + ") - Affliction prefab with the identifier \"" + afflictionIdentifier + "\" not found.", + contentPackage: characterTalent.Prefab.ContentPackage); continue; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/CharacterTalent.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/CharacterTalent.cs index 0cb53857e..ae7f18849 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/CharacterTalent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/CharacterTalent.cs @@ -23,9 +23,8 @@ namespace Barotrauma public CharacterTalent(TalentPrefab talentPrefab, Character character) { - Character = character; - - Prefab = talentPrefab; + Character = character ?? throw new ArgumentNullException(nameof(character)); + Prefab = talentPrefab ?? throw new ArgumentNullException(nameof(talentPrefab)); var element = talentPrefab.ConfigElement; DebugIdentifier = talentPrefab.OriginalName; @@ -46,7 +45,8 @@ namespace Barotrauma } else { - DebugConsole.ThrowError($"No recipe identifier defined for talent {DebugIdentifier}"); + DebugConsole.ThrowError($"No recipe identifier defined for talent {DebugIdentifier}", + contentPackage: element.ContentPackage); } break; case "addedstoreitem": @@ -56,7 +56,8 @@ namespace Barotrauma } else { - DebugConsole.ThrowError($"No store item identifier defined for talent {DebugIdentifier}"); + DebugConsole.ThrowError($"No store item identifier defined for talent {DebugIdentifier}", + contentPackage: element.ContentPackage); } break; } @@ -146,11 +147,13 @@ namespace Barotrauma { if (!Enum.TryParse(abilityEffectTypeString, true, out AbilityEffectType abilityEffectType)) { - DebugConsole.ThrowError("Invalid ability effect type \"" + abilityEffectTypeString + "\" in CharacterTalent (" + characterTalent.DebugIdentifier + ")"); + DebugConsole.ThrowError("Invalid ability effect type \"" + abilityEffectTypeString + "\" in CharacterTalent (" + characterTalent.DebugIdentifier + ")", + contentPackage: characterTalent?.Prefab?.ContentPackage); } if (abilityEffectType == AbilityEffectType.Undefined) { - DebugConsole.ThrowError("Ability effect type not defined in CharacterTalent (" + characterTalent.DebugIdentifier + ")"); + DebugConsole.ThrowError("Ability effect type not defined in CharacterTalent (" + characterTalent.DebugIdentifier + ")", + contentPackage: characterTalent?.Prefab?.ContentPackage); } return abilityEffectType; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/TalentPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/TalentPrefab.cs index bd105e729..f640dee46 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/TalentPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/TalentPrefab.cs @@ -84,7 +84,8 @@ namespace Barotrauma } catch (Exception e) { - DebugConsole.ThrowError($"Error while loading talent migration for talent \"{Identifier}\".", e); + DebugConsole.ThrowError($"Error while loading talent migration for talent \"{Identifier}\".", e, + element?.ContentPackage); } } break; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/TalentTree.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/TalentTree.cs index 2c1867589..04ac83453 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/TalentTree.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/TalentTree.cs @@ -37,7 +37,8 @@ namespace Barotrauma if (Identifier.IsEmpty) { - DebugConsole.ThrowError($"No job defined for talent tree in \"{file.Path}\"!"); + DebugConsole.ThrowError($"No job defined for talent tree in \"{file.Path}\"!", + contentPackage: element.ContentPackage); return; } @@ -304,7 +305,8 @@ namespace Barotrauma if (RequiredTalents > MaxChosenTalents) { - DebugConsole.ThrowError($"Error in talent tree {debugIdentifier} - MaxChosenTalents is larger than RequiredTalents."); + DebugConsole.ThrowError($"Error in talent tree {debugIdentifier} - MaxChosenTalents is larger than RequiredTalents.", + contentPackage: talentOptionsElement.ContentPackage); } HashSet identifiers = new HashSet(); @@ -333,11 +335,13 @@ namespace Barotrauma if (RequiredTalents > talentIdentifiers.Count) { - DebugConsole.ThrowError($"Error in talent tree {debugIdentifier} - completing a stage of the tree requires more talents than there are in the stage."); + DebugConsole.ThrowError($"Error in talent tree {debugIdentifier} - completing a stage of the tree requires more talents than there are in the stage.", + contentPackage: talentOptionsElement.ContentPackage); } if (MaxChosenTalents > talentIdentifiers.Count) { - DebugConsole.ThrowError($"Error in talent tree {debugIdentifier} - maximum number of talents to choose is larger than the number of talents."); + DebugConsole.ThrowError($"Error in talent tree {debugIdentifier} - maximum number of talents to choose is larger than the number of talents.", + contentPackage: talentOptionsElement.ContentPackage); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/AfflictionsFile.cs b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/AfflictionsFile.cs index e3cfc518c..2069fe68f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/AfflictionsFile.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/AfflictionsFile.cs @@ -50,7 +50,8 @@ namespace Barotrauma if (identifier.IsEmpty) { DebugConsole.ThrowError( - $"No identifier defined for the affliction '{elementName}' in file '{Path}'"); + $"No identifier defined for the affliction '{elementName}' in file '{Path}'", + contentPackage: element?.ContentPackage); return; } @@ -60,12 +61,13 @@ namespace Barotrauma { DebugConsole.NewMessage( $"Overriding an affliction or a buff with the identifier '{identifier}' using the file '{Path}'", - Color.Yellow); + Color.MediumPurple); } else { DebugConsole.ThrowError( - $"Duplicate affliction: '{identifier}' defined in {elementName} of '{Path}'"); + $"Duplicate affliction: '{identifier}' defined in {elementName} of '{Path}'", + contentPackage: element?.ContentPackage); return; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/CharacterFile.cs b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/CharacterFile.cs index e3412c1be..d8269dbd0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/CharacterFile.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/CharacterFile.cs @@ -17,12 +17,12 @@ namespace Barotrauma XDocument doc = XMLExtensions.TryLoadXml(Path); if (doc == null) { - DebugConsole.ThrowError($"Loading character file failed: {Path}"); + DebugConsole.ThrowError($"Loading character file failed: {Path}", contentPackage: ContentPackage); return; } if (CharacterPrefab.Prefabs.AllPrefabs.Any(kvp => kvp.Value.Any(cf => cf?.ContentFile == this))) { - DebugConsole.ThrowError($"Duplicate path: {Path}"); + DebugConsole.ThrowError($"Duplicate path: {Path}", contentPackage: ContentPackage); return; } var mainElement = doc.Root.FromPackage(ContentPackage); @@ -69,7 +69,8 @@ namespace Barotrauma } catch (Exception e) { - DebugConsole.ThrowError($"Failed to preload a ragdoll file for the character \"{characterPrefab.Name}\"", e); + DebugConsole.ThrowError($"Failed to preload a ragdoll file for the character \"{characterPrefab.Name}\"", e, + contentPackage: characterPrefab.ContentPackage); return; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/ContentFile.cs b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/ContentFile.cs index f2470ed6a..b7d896fe6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/ContentFile.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/ContentFile.cs @@ -76,7 +76,8 @@ namespace Barotrauma { DebugConsole.AddWarning( $"The content type \"TraitorMission\" in content package \"{package.Name}\" is no longer supported." + - $" Traitor missions should be implemented using the scripted event system and the content type TraitorEvents."); + $" Traitor missions should be implemented using the scripted event system and the content type TraitorEvents.", + package); } return true; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/GenericPrefabFile.cs b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/GenericPrefabFile.cs index b57133514..ef041dbef 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/GenericPrefabFile.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/GenericPrefabFile.cs @@ -55,7 +55,7 @@ namespace Barotrauma } else { - DebugConsole.ThrowError($"GenericPrefabFile: Invalid {GetType().Name} element: {parentElement.Name} in {Path}"); + DebugConsole.ThrowError($"GenericPrefabFile: Invalid {GetType().Name} element: {parentElement.Name} in {Path}", contentPackage: ContentPackage); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/NPCConversationsFile.cs b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/NPCConversationsFile.cs index f26a17cd7..95ee2d5c5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/NPCConversationsFile.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/NPCConversationsFile.cs @@ -2,6 +2,7 @@ using System.Xml.Linq; namespace Barotrauma { + [NotSyncedInMultiplayer] sealed class NPCConversationsFile : ContentFile { public NPCConversationsFile(ContentPackage contentPackage, ContentPath path) : base(contentPackage, path) { } diff --git a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/OrdersFile.cs b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/OrdersFile.cs index 57273ced6..cb7ca8d87 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/OrdersFile.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/OrdersFile.cs @@ -42,7 +42,8 @@ namespace Barotrauma } else { - DebugConsole.ThrowError($"OrdersFile: Invalid {GetType().Name} element: {parentElement.Name} in {Path}"); + DebugConsole.ThrowError($"OrdersFile: Invalid {GetType().Name} element: {parentElement.Name} in {Path}", + contentPackage: parentElement?.ContentPackage); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/RandomEventsFile.cs b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/RandomEventsFile.cs index 4c25ad989..51518e428 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/RandomEventsFile.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/RandomEventsFile.cs @@ -65,7 +65,8 @@ namespace Barotrauma } else { - DebugConsole.ThrowError($"RandomEventsFile: Invalid {GetType().Name} element: {parentElement.Name} in {Path}"); + DebugConsole.ThrowError($"RandomEventsFile: Invalid {GetType().Name} element: {parentElement.Name} in {Path}", + contentPackage: parentElement.ContentPackage); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/TextFile.cs b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/TextFile.cs index 9f21480f9..68d3b4184 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/TextFile.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/TextFile.cs @@ -4,6 +4,7 @@ using System.Xml.Linq; namespace Barotrauma { + [NotSyncedInMultiplayer] public sealed class TextFile : ContentFile { public TextFile(ContentPackage contentPackage, ContentPath path) : base(contentPackage, path) { } @@ -37,6 +38,13 @@ namespace Barotrauma if (newHashSet.Count != 0) { TextManager.TextPacks.TryAdd(kvp.Key, newHashSet); } } TextManager.IncrementLanguageVersion(); + if (!TextManager.TextPacks.ContainsKey(GameSettings.CurrentConfig.Language)) + { + DebugConsole.AddWarning($"The language {GameSettings.CurrentConfig.Language} is no longer available. Switching to {TextManager.DefaultLanguage}..."); + var config = GameSettings.CurrentConfig; + config.Language = TextManager.DefaultLanguage; + GameSettings.SetCurrentConfig(config); + } } public override void Sort() diff --git a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentPackage/ContentPackage.cs b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentPackage/ContentPackage.cs index 3d78d1da8..c932b4470 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentPackage/ContentPackage.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentPackage/ContentPackage.cs @@ -283,7 +283,7 @@ namespace Barotrauma catch (Exception e) { var innermost = e.GetInnermost(); - DebugConsole.LogError($"Failed to load \"{filesToLoad[i].Path}\": {innermost.Message}\n{innermost.StackTrace}"); + DebugConsole.LogError($"Failed to load \"{filesToLoad[i].Path}\": {innermost.Message}\n{innermost.StackTrace}", contentPackage: this); exception = e; } if (exception != null) @@ -391,7 +391,8 @@ namespace Barotrauma DebugConsole.AddWarning( $"The following errors occurred while loading the content package \"{Name}\". The package might not work correctly.\n" + - string.Join('\n', FatalLoadErrors.Select(errorToStr))); + string.Join('\n', FatalLoadErrors.Select(errorToStr)), + this); static string errorToStr(LoadError error) => error.ToString(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentXElement.cs b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentXElement.cs index c97b534ce..26afb242f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentXElement.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentXElement.cs @@ -70,7 +70,7 @@ namespace Barotrauma public Identifier[] GetAttributeIdentifierArray(string key, Identifier[] def, bool trim = true) => Element.GetAttributeIdentifierArray(key, def, trim); [return: NotNullIfNotNull("def")] public ImmutableHashSet GetAttributeIdentifierImmutableHashSet(string key, ImmutableHashSet? def, bool trim = true) => Element.GetAttributeIdentifierImmutableHashSet(key, def, trim); - + [return: NotNullIfNotNull(parameterName: "def")] public string? GetAttributeString(string key, string? def) => Element.GetAttributeString(key, def); public string GetAttributeStringUnrestricted(string key, string def) => Element.GetAttributeStringUnrestricted(key, def); public string[]? GetAttributeStringArray(string key, string[]? def, bool convertToLowerInvariant = false) => Element.GetAttributeStringArray(key, def, convertToLowerInvariant); @@ -90,6 +90,7 @@ namespace Barotrauma public Color? GetAttributeColor(string key) => Element.GetAttributeColor(key); public Color[]? GetAttributeColorArray(string key, Color[]? def) => Element.GetAttributeColorArray(key, def); public Rectangle GetAttributeRect(string key, in Rectangle def) => Element.GetAttributeRect(key, def); + public Version GetAttributeVersion(string key, Version def) => Element.GetAttributeVersion(key, def); public T GetAttributeEnum(string key, in T def) where T : struct, Enum => Element.GetAttributeEnum(key, def); public (T1, T2) GetAttributeTuple(string key, in (T1, T2) def) => Element.GetAttributeTuple(key, def); public (T1, T2)[] GetAttributeTupleArray(string key, in (T1, T2)[] def) => Element.GetAttributeTupleArray(key, def); diff --git a/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs b/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs index 552fe6a3f..2599ebd65 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs @@ -7,6 +7,7 @@ using Microsoft.Xna.Framework; using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Collections.Immutable; using System.ComponentModel; using System.Globalization; using Barotrauma.IO; @@ -40,8 +41,8 @@ namespace Barotrauma { public partial class Command { - public readonly string[] names; - public readonly string help; + public readonly ImmutableArray Names; + public readonly string Help; public Action OnExecute; @@ -57,8 +58,8 @@ namespace Barotrauma /// public Command(string name, string help, Action onExecute, Func getValidArgs = null, bool isCheat = false) { - names = name.Split('|'); - this.help = help; + Names = name.Split('|').ToIdentifiers().ToImmutableArray(); + this.Help = help; this.OnExecute = onExecute; @@ -76,7 +77,8 @@ namespace Barotrauma #endif if (!allowCheats && !CheatsEnabled && IsCheat) { - NewMessage("You need to enable cheats using the command \"enablecheats\" before you can use the command \"" + names[0] + "\".", Color.Red); + NewMessage( + $"You need to enable cheats using the command \"enablecheats\" before you can use the command \"{Names.First()}\".", Color.Red); #if USE_STEAM NewMessage("Enabling cheats will disable Steam achievements during this play session.", Color.Red); #endif @@ -88,7 +90,7 @@ namespace Barotrauma public override int GetHashCode() { - return names[0].GetHashCode(); + return Names.First().GetHashCode(); } } @@ -164,7 +166,7 @@ namespace Barotrauma private static void AssignOnExecute(string names, Action onExecute) { - var matchingCommand = commands.Find(c => c.names.Intersect(names.Split('|')).Count() > 0); + var matchingCommand = commands.Find(c => c.Names.Intersect(names.Split('|').ToIdentifiers()).Any()); if (matchingCommand == null) { throw new Exception("AssignOnExecute failed. Command matching the name(s) \"" + names + "\" not found."); @@ -187,13 +189,13 @@ namespace Barotrauma { foreach (Command c in commands) { - if (string.IsNullOrEmpty(c.help)) continue; + if (string.IsNullOrEmpty(c.Help)) continue; ShowHelpMessage(c); } } else { - var matchingCommand = commands.Find(c => c.names.Any(name => name == args[0])); + var matchingCommand = commands.Find(c => c.Names.Any(name => name == args[0])); if (matchingCommand == null) { NewMessage("Command " + args[0] + " not found.", Color.Red); @@ -208,7 +210,7 @@ namespace Barotrauma { return new string[][] { - commands.SelectMany(c => c.names).ToArray(), + commands.SelectMany(c => c.Names).Select(n => n.Value).ToArray(), Array.Empty() }; })); @@ -403,7 +405,7 @@ namespace Barotrauma return new string[][] { GameMain.NetworkMember.ConnectedClients.Select(c => c.Name).ToArray(), - commands.Select(c => c.names[0]).Union(new string[]{ "All" }).ToArray() + commands.Select(c => c.Names.First().Value).Union(new []{ "All" }).ToArray() }; })); @@ -415,7 +417,7 @@ namespace Barotrauma return new string[][] { GameMain.NetworkMember.ConnectedClients.Select(c => c.Name).ToArray(), - commands.Select(c => c.names[0]).Union(new string[]{ "All" }).ToArray() + commands.Select(c => c.Names.First().Value).Union(new []{ "All" }).ToArray() }; })); @@ -1133,7 +1135,7 @@ namespace Barotrauma } },null)); - commands.Add(new Command("teleportsub", "teleportsub [start/end/cursor]: Teleport the submarine to the position of the cursor, or the start or end of the level. WARNING: does not take outposts into account, so often leads to physics glitches. Only use for debugging.", (string[] args) => + commands.Add(new Command("teleportsub", "teleportsub [start/end/endoutpost/cursor]: Teleport the submarine to the position of the cursor, or the start or end of the level. The 'endoutpost' argument also automatically docks the sub with the outpost at the end of the level. WARNING: does not take outposts into account, so often leads to physics glitches. Only use for debugging.", (string[] args) => { if (Submarine.MainSub == null) { return; } @@ -1159,7 +1161,7 @@ namespace Barotrauma } Submarine.MainSub.SetPosition(pos); } - else + else if (args[0].Equals("end", StringComparison.OrdinalIgnoreCase)) { if (Level.Loaded == null) { @@ -1172,13 +1174,29 @@ namespace Barotrauma pos -= Vector2.UnitY * (Submarine.MainSub.Borders.Height + Level.Loaded.EndOutpost.Borders.Height) / 2; } Submarine.MainSub.SetPosition(pos); + } + else if (args[0].Equals("endoutpost", StringComparison.OrdinalIgnoreCase)) + { + Submarine.MainSub.SetPosition(Level.Loaded.EndExitPosition - Vector2.UnitY * Submarine.MainSub.Borders.Height); + + var submarineDockingPort = DockingPort.List.FirstOrDefault(d => d.Item.Submarine == Submarine.MainSub); + if (Level.Loaded?.EndOutpost == null) + { + NewMessage("Can't teleport the sub to the end outpost (no outpost at the end of the level).", Color.Red); + return; + } + var outpostDockingPort = DockingPort.List.FirstOrDefault(d => d.Item.Submarine == Level.Loaded.EndOutpost); + if (submarineDockingPort != null && outpostDockingPort != null) + { + submarineDockingPort.Dock(outpostDockingPort); + } } }, () => { return new string[][] { - new string[] { "start", "end", "cursor" } + new string[] { "start", "end", "endoutpost", "cursor" } }; }, isCheat: true)); @@ -1959,7 +1977,7 @@ namespace Barotrauma InitProjectSpecific(); - commands.Sort((c1, c2) => c1.names[0].CompareTo(c2.names[0])); + commands.Sort((c1, c2) => c1.Names.First().CompareTo(c2.Names.First())); } public static string AutoComplete(string command, int increment = 1) @@ -1970,14 +1988,14 @@ namespace Barotrauma //if an argument is given or the last character is a space, attempt to autocomplete the argument if (args.Length > 0 || (splitCommand.Length > 0 && command.Last() == ' ')) { - Command matchingCommand = commands.Find(c => c.names.Contains(splitCommand[0])); - if (matchingCommand == null || matchingCommand.GetValidArgs == null) return command; + Command matchingCommand = commands.Find(c => c.Names.Contains(splitCommand[0].ToIdentifier())); + if (matchingCommand?.GetValidArgs == null) { return command; } int autoCompletedArgIndex = args.Length > 0 && command.Last() != ' ' ? args.Length - 1 : args.Length; //get all valid arguments for the given command string[][] allArgs = matchingCommand.GetValidArgs(); - if (allArgs == null || allArgs.GetLength(0) < autoCompletedArgIndex + 1) return command; + if (allArgs == null || allArgs.GetLength(0) < autoCompletedArgIndex + 1) { return command; } if (string.IsNullOrEmpty(currentAutoCompletedCommand)) { @@ -1989,7 +2007,7 @@ namespace Barotrauma currentAutoCompletedCommand.Trim().Length <= arg.Length && arg.Substring(0, currentAutoCompletedCommand.Trim().Length).ToLower() == currentAutoCompletedCommand.Trim().ToLower()).ToArray(); - if (validArgs.Length == 0) return command; + if (validArgs.Length == 0) { return command; } currentAutoCompletedIndex = MathUtils.PositiveModulo(currentAutoCompletedIndex + increment, validArgs.Length); string autoCompletedArg = validArgs[currentAutoCompletedIndex]; @@ -2010,13 +2028,13 @@ namespace Barotrauma currentAutoCompletedCommand = command; } - List matchingCommands = new List(); + List matchingCommands = new List(); foreach (Command c in commands) { - foreach (string name in c.names) + foreach (var name in c.Names) { - if (currentAutoCompletedCommand.Length > name.Length) continue; - if (currentAutoCompletedCommand == name.Substring(0, currentAutoCompletedCommand.Length)) + if (currentAutoCompletedCommand.Length > name.Value.Length) { continue; } + if (name.StartsWith(currentAutoCompletedCommand)) { matchingCommands.Add(name); } @@ -2026,7 +2044,7 @@ namespace Barotrauma if (matchingCommands.Count == 0) return command; currentAutoCompletedIndex = MathUtils.PositiveModulo(currentAutoCompletedIndex + increment, matchingCommands.Count); - return matchingCommands[currentAutoCompletedIndex]; + return matchingCommands[currentAutoCompletedIndex].Value; } } @@ -2064,9 +2082,9 @@ namespace Barotrauma return; } - string firstCommand = splitCommand[0].ToLowerInvariant(); + Identifier firstCommand = splitCommand[0].ToIdentifier(); - if (!firstCommand.Equals("admin", StringComparison.OrdinalIgnoreCase)) + if (firstCommand != "admin") { NewCommand(command); } @@ -2074,7 +2092,7 @@ namespace Barotrauma #if CLIENT if (GameMain.Client != null) { - Command matchingCommand = commands.Find(c => c.names.Contains(firstCommand)); + Command matchingCommand = commands.Find(c => c.Names.Contains(firstCommand)); if (matchingCommand == null) { //if the command is not defined client-side, we'll relay it anyway because it may be a custom command at the server's side @@ -2095,12 +2113,12 @@ namespace Barotrauma } return; } - if (!IsCommandPermitted(splitCommand[0].ToLowerInvariant(), GameMain.Client)) + if (!IsCommandPermitted(firstCommand, GameMain.Client)) { #if DEBUG - AddWarning($"You're not permitted to use the command \"{splitCommand[0].ToLowerInvariant()}\". Executing the command anyway because this is a debug build."); + AddWarning($"You're not permitted to use the command \"{firstCommand}\". Executing the command anyway because this is a debug build."); #else - ThrowError($"You're not permitted to use the command \"{splitCommand[0].ToLowerInvariant()}\"!"); + ThrowError($"You're not permitted to use the command \"{firstCommand}\"!"); return; #endif } @@ -2110,7 +2128,7 @@ namespace Barotrauma bool commandFound = false; foreach (Command c in commands) { - if (!c.names.Contains(firstCommand)) { continue; } + if (!c.Names.Contains(firstCommand)) { continue; } c.Execute(splitCommand.Skip(1).ToArray()); commandFound = true; break; @@ -2397,8 +2415,13 @@ namespace Barotrauma #endif } - public static void LogError(string msg, Color? color = null) + public static void LogError(string msg, Color? color = null, ContentPackage contentPackage = null) { + if (contentPackage != null) + { + string colorStr = XMLExtensions.ToStringHex(Color.MediumPurple); + msg = $"‖color:{colorStr}‖[{contentPackage.Name}]‖color:end‖ {msg}"; + } color ??= Color.Red; NewMessage(msg, color.Value, isCommand: false, isError: true); } @@ -2515,7 +2538,7 @@ namespace Barotrauma return true; } - public static Command FindCommand(string commandName) => commands.Find(c => c.names.Any(n => n.Equals(commandName, StringComparison.OrdinalIgnoreCase))); + public static Command FindCommand(string commandName) => commands.Find(c => c.Names.Contains(commandName.ToIdentifier())); public static void Log(LocalizedString message) => Log(message?.Value); @@ -2532,8 +2555,13 @@ namespace Barotrauma ThrowError(error.Value, e, createMessageBox, appendStackTrace); } - public static void ThrowError(string error, Exception e = null, bool createMessageBox = false, bool appendStackTrace = false) + public static void ThrowError(string error, Exception e = null, ContentPackage contentPackage = null, bool createMessageBox = false, bool appendStackTrace = false) { + if (contentPackage != null) + { + string color = XMLExtensions.ToStringHex(Color.MediumPurple); + error = $"‖color:{color}‖[{contentPackage.Name}]‖color:end‖ {error}"; + } if (e != null) { error += " {" + e.Message + "}\n"; @@ -2547,7 +2575,7 @@ namespace Barotrauma error += "\n\nInner exception: " + innermost.Message + "\n"; if (innermost.StackTrace != null) { - error += innermost.StackTrace.CleanupStackTrace(); ; + error += innermost.StackTrace.CleanupStackTrace(); } } } @@ -2580,10 +2608,16 @@ namespace Barotrauma errorMsg); } - public static void AddWarning(string warning) + public static void AddWarning(string warning, ContentPackage contentPackage = null) { + warning = $"WARNING: {warning}"; + if (contentPackage != null) + { + string color = XMLExtensions.ToStringHex(Color.MediumPurple); + warning = $"‖color:{color}‖[{contentPackage.Name}]‖color:end‖ {warning}"; + } System.Diagnostics.Debug.WriteLine(warning); - NewMessage($"WARNING: {warning}", Color.Yellow); + NewMessage(warning, Color.Yellow); } #if CLIENT diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/ArtifactEvent.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/ArtifactEvent.cs index 16622dee8..da3967687 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/ArtifactEvent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/ArtifactEvent.cs @@ -34,7 +34,8 @@ namespace Barotrauma { if (prefab.ConfigElement.GetAttribute("itemname") != null) { - DebugConsole.ThrowError("Error in ArtifactEvent - use item identifier instead of the name of the item."); + DebugConsole.ThrowError("Error in ArtifactEvent - use item identifier instead of the name of the item.", + contentPackage: prefab?.ContentPackage); string itemName = prefab.ConfigElement.GetAttributeString("itemname", ""); itemPrefab = MapEntityPrefab.Find(itemName) as ItemPrefab; if (itemPrefab == null) @@ -44,11 +45,12 @@ namespace Barotrauma } else { - string itemIdentifier = prefab.ConfigElement.GetAttributeString("itemidentifier", ""); - itemPrefab = MapEntityPrefab.Find(null, itemIdentifier) as ItemPrefab; + Identifier itemIdentifier = prefab.ConfigElement.GetAttributeIdentifier("itemidentifier", Identifier.Empty); + itemPrefab = MapEntityPrefab.FindByIdentifier(itemIdentifier) as ItemPrefab; if (itemPrefab == null) { - DebugConsole.ThrowError("Error in ArtifactEvent - couldn't find an item prefab with the identifier " + itemIdentifier); + DebugConsole.ThrowError("Error in ArtifactEvent - couldn't find an item prefab with the identifier " + itemIdentifier, + contentPackage: prefab?.ContentPackage); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Event.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Event.cs index 1df74bcb8..0e4358efb 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Event.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Event.cs @@ -39,7 +39,7 @@ namespace Barotrauma public Event(EventPrefab prefab) { - this.prefab = prefab; + this.prefab = prefab ?? throw new ArgumentNullException(nameof(prefab)); } public virtual IEnumerable GetFilesToPreload() diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckConditionalAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckConditionalAction.cs index 5fed3e0ce..d59add1f5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckConditionalAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckConditionalAction.cs @@ -14,12 +14,14 @@ namespace Barotrauma { if (TargetTag.IsEmpty) { - DebugConsole.LogError($"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.", + contentPackage: parentEvent.Prefab.ContentPackage); } Conditional = PropertyConditional.FromXElement(element, IsNotTargetTagAttribute).FirstOrDefault(); if (Conditional == null) { - DebugConsole.LogError($"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.", + contentPackage: parentEvent.Prefab.ContentPackage); } static bool IsNotTargetTagAttribute(XAttribute attribute) => attribute.NameAsIdentifier() != "targettag"; @@ -46,7 +48,8 @@ namespace Barotrauma } if (target == null) { - DebugConsole.LogError($"{nameof(CheckConditionalAction)} error: {GetEventName()} uses a {nameof(CheckConditionalAction)} but no valid target was found for tag \"{TargetTag}\"! This will cause the check to automatically succeed."); + DebugConsole.LogError($"{nameof(CheckConditionalAction)} error: {GetEventName()} uses a {nameof(CheckConditionalAction)} but no valid target was found for tag \"{TargetTag}\"! This will cause the check to automatically succeed.", + contentPackage: ParentEvent.Prefab.ContentPackage); } if (target == null || Conditional == null) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckDataAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckDataAction.cs index df038e70c..f56d50e29 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckDataAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckDataAction.cs @@ -30,7 +30,8 @@ namespace Barotrauma Condition = element.GetAttributeString("value", string.Empty)!; if (string.IsNullOrEmpty(Condition)) { - DebugConsole.ThrowError($"Error in scripted event \"{parentEvent.Prefab.Identifier}\". CheckDataAction with no condition set ({element})."); + DebugConsole.ThrowError($"Error in scripted event \"{parentEvent.Prefab.Identifier}\". CheckDataAction with no condition set ({element}).", + contentPackage: element?.ContentPackage); } } } @@ -42,7 +43,8 @@ namespace Barotrauma Condition = element.GetAttributeString("value", string.Empty)!; if (string.IsNullOrEmpty(Condition)) { - DebugConsole.ThrowError($"Error in scripted event \"{parentDebugString}\". CheckDataAction with no condition set ({element})."); + DebugConsole.ThrowError($"Error in scripted event \"{parentDebugString}\". CheckDataAction with no condition set ({element}).", + contentPackage: element?.ContentPackage); } } } @@ -59,7 +61,8 @@ namespace Barotrauma (Operator, string value) = PropertyConditional.ExtractComparisonOperatorFromConditionString(Condition); if (Operator == PropertyConditional.ComparisonOperatorType.None) { - DebugConsole.ThrowError($"{Condition} is invalid, it should start with an operator followed by a boolean or a floating point value."); + DebugConsole.ThrowError($"{Condition} is invalid, it should start with an operator followed by a boolean or a floating point value.", + contentPackage: ParentEvent?.Prefab?.ContentPackage); return false; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckItemAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckItemAction.cs index 275203b94..727d627dd 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckItemAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckItemAction.cs @@ -77,7 +77,8 @@ namespace Barotrauma ItemIdentifiers.None() && TargetTag.IsEmpty) { - DebugConsole.ThrowError($"Error in event \"{ParentEvent.Prefab.Identifier}\". {nameof(CheckItemAction)} does't define either tags or identifiers of the item to check."); + DebugConsole.ThrowError($"Error in event \"{ParentEvent.Prefab.Identifier}\". {nameof(CheckItemAction)} does't define either tags or identifiers of the item to check.", + contentPackage: element.ContentPackage); } checkPercentage = element.GetAttribute(nameof(RequiredConditionalMatchPercentage)) is not null; if (checkPercentage && conditionals.None()) @@ -86,7 +87,8 @@ namespace Barotrauma } if (Amount != 1 && checkPercentage) { - DebugConsole.ThrowError($"Error in event \"{ParentEvent.Prefab.Identifier}\". Cannot define both '{Amount}' and '{RequiredConditionalMatchPercentage}' in {nameof(CheckItemAction)}."); + DebugConsole.ThrowError($"Error in event \"{ParentEvent.Prefab.Identifier}\". Cannot define both '{Amount}' and '{RequiredConditionalMatchPercentage}' in {nameof(CheckItemAction)}.", + contentPackage: element.ContentPackage); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckOrderAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckOrderAction.cs index 870a7ee9c..39b427ba4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckOrderAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckOrderAction.cs @@ -32,7 +32,8 @@ namespace Barotrauma var targetCharacters = ParentEvent.GetTargets(TargetTag); if (targetCharacters.None()) { - DebugConsole.LogError($"CheckConditionalAction error: {GetEventName()} uses a CheckOrderAction but no valid target characters were found for tag \"{TargetTag}\"! This will cause the check to automatically fail."); + DebugConsole.LogError($"CheckConditionalAction error: {GetEventName()} uses a CheckOrderAction but no valid target characters were found for tag \"{TargetTag}\"! This will cause the check to automatically fail.", + contentPackage: ParentEvent.Prefab.ContentPackage); return false; } foreach (var t in targetCharacters) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckReputationAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckReputationAction.cs index 5f6f19e47..478fb872d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckReputationAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckReputationAction.cs @@ -1,7 +1,5 @@ #nullable enable -using System; using System.Diagnostics; -using System.Xml.Linq; namespace Barotrauma { @@ -31,7 +29,8 @@ namespace Barotrauma } default: { - DebugConsole.ThrowError("CheckReputationAction requires a \"TargetType\" but none were specified."); + DebugConsole.ThrowError("CheckReputationAction requires a \"TargetType\" but none were specified.", + contentPackage: ParentEvent.Prefab.ContentPackage); break; } } @@ -41,7 +40,8 @@ namespace Barotrauma protected override bool GetBool(CampaignMode campaignMode) { - DebugConsole.ThrowError("Boolean comparison cannot be applied to reputations."); + DebugConsole.ThrowError("Boolean comparison cannot be applied to reputations.", + contentPackage: ParentEvent.Prefab.ContentPackage); return false; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckSelectedAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckSelectedAction.cs index c03e7991e..0e673e047 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckSelectedAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckSelectedAction.cs @@ -87,13 +87,13 @@ namespace Barotrauma #if DEBUG void Error(string errorMsg) { - DebugConsole.ThrowError(errorMsg); + DebugConsole.ThrowError(errorMsg, contentPackage: ParentEvent.Prefab.ContentPackage); } #else void Error(string errorMsg) { - DebugConsole.LogError(errorMsg); + DebugConsole.LogError(errorMsg, contentPackage: ParentEvent.Prefab.ContentPackage); } #endif } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckTraitorEventStateAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckTraitorEventStateAction.cs index f3833fa6d..ab2b9fde6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckTraitorEventStateAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckTraitorEventStateAction.cs @@ -17,7 +17,8 @@ namespace Barotrauma } else { - DebugConsole.ThrowError($"Cannot use the action {nameof(CheckTraitorEventStateAction)} in the event \"{parentEvent.Prefab.Identifier}\" because it's not a traitor event."); + DebugConsole.ThrowError($"Cannot use the action {nameof(CheckTraitorEventStateAction)} in the event \"{parentEvent.Prefab.Identifier}\" because it's not a traitor event.", + contentPackage: element.ContentPackage); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckTraitorVoteAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckTraitorVoteAction.cs index 7f8e26526..9ab84cd3e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckTraitorVoteAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckTraitorVoteAction.cs @@ -16,7 +16,8 @@ namespace Barotrauma { if (parentEvent is not TraitorEvent) { - DebugConsole.ThrowError($"Error in event \"{parentEvent.Prefab.Identifier}\" - {nameof(CheckTraitorVoteAction)} can only be used in traitor events."); + DebugConsole.ThrowError($"Error in event \"{parentEvent.Prefab.Identifier}\" - {nameof(CheckTraitorVoteAction)} can only be used in traitor events.", + contentPackage: element.ContentPackage); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/ConversationAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/ConversationAction.cs index 8092bdb91..7817c012c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/ConversationAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/ConversationAction.cs @@ -116,7 +116,8 @@ namespace Barotrauma { DebugConsole.ThrowError( $"Error in {nameof(EventObjectiveAction)} in the event \"{parentEvent.Prefab.Identifier}\"" + - $" - unrecognized child element \"Replace\"."); + $" - unrecognized child element \"Replace\".", + contentPackage: element.ContentPackage); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CountTargetsAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CountTargetsAction.cs index c2287af04..70b6f74b9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CountTargetsAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CountTargetsAction.cs @@ -61,14 +61,16 @@ namespace Barotrauma } if (MinAmount > MaxAmount && MaxAmount > -1) { - DebugConsole.ThrowError($"Error in event \"{ParentEvent.Prefab.Identifier}\". {MinAmount} is larger than {MaxAmount} in {nameof(CountTargetsAction)}."); + DebugConsole.ThrowError($"Error in event \"{ParentEvent.Prefab.Identifier}\". {MinAmount} is larger than {MaxAmount} in {nameof(CountTargetsAction)}.", + contentPackage: element.ContentPackage); } } else { if (MinPercentageRelativeToTarget < 0.0f && MaxPercentageRelativeToTarget < 0.0f) { - DebugConsole.ThrowError($"Error in event \"{ParentEvent.Prefab.Identifier}\". Comparing to another target, but neither {nameof(MinPercentageRelativeToTarget)} or {nameof(MaxPercentageRelativeToTarget)} is set."); + DebugConsole.ThrowError($"Error in event \"{ParentEvent.Prefab.Identifier}\". Comparing to another target, but neither {nameof(MinPercentageRelativeToTarget)} or {nameof(MaxPercentageRelativeToTarget)} is set.", + contentPackage: element.ContentPackage); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/EventAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/EventAction.cs index dd086ec74..4585edbc4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/EventAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/EventAction.cs @@ -36,7 +36,8 @@ namespace Barotrauma { if (e.Name.ToString().Equals("statuseffect", StringComparison.OrdinalIgnoreCase)) { - DebugConsole.ThrowError($"Error in event prefab \"{scriptedEvent.Prefab.Identifier}\". Status effect configured as a sub action (text: \"{Text}\"). Please configure status effects as child elements of a StatusEffectAction."); + DebugConsole.ThrowError($"Error in event prefab \"{scriptedEvent.Prefab.Identifier}\". Status effect configured as a sub action (text: \"{Text}\"). Please configure status effects as child elements of a StatusEffectAction.", + contentPackage: elem.ContentPackage); continue; } var action = Instantiate(scriptedEvent, e); @@ -102,7 +103,7 @@ namespace Barotrauma public EventAction(ScriptedEvent parentEvent, ContentXElement element) { - ParentEvent = parentEvent; + ParentEvent = parentEvent ?? throw new ArgumentNullException(nameof(parentEvent)); SerializableProperty.DeserializeProperties(this, element); } @@ -147,7 +148,8 @@ namespace Barotrauma } catch { - DebugConsole.ThrowError($"Could not find an {nameof(EventAction)} class of the type \"{element.Name}\"."); + DebugConsole.ThrowError($"Could not find an {nameof(EventAction)} class of the type \"{element.Name}\".", + contentPackage: element.ContentPackage); return null; } @@ -162,7 +164,8 @@ namespace Barotrauma } catch (Exception ex) { - DebugConsole.ThrowError(ex.InnerException != null ? ex.InnerException.ToString() : ex.ToString()); + DebugConsole.ThrowError(ex.InnerException != null ? ex.InnerException.ToString() : ex.ToString(), + contentPackage: element.ContentPackage); return null; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/EventLogAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/EventLogAction.cs index 7fc3af8b5..c6f24003d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/EventLogAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/EventLogAction.cs @@ -24,7 +24,8 @@ namespace Barotrauma { if (Id == Identifier.Empty) { - DebugConsole.ThrowError($"Error in event \"{parentEvent.Prefab.Identifier}\". {nameof(EventLogAction)} with no id."); + DebugConsole.ThrowError($"Error in event \"{parentEvent.Prefab.Identifier}\". {nameof(EventLogAction)} with no id.", + contentPackage: element.ContentPackage); } //append the target tag so logs targeted to different players don't interfere with each other even if they use the same Id Id = (Id.ToString() + TargetTag).ToIdentifier(); @@ -42,7 +43,8 @@ namespace Barotrauma { if (Text.IsNullOrEmpty()) { - DebugConsole.ThrowError($"Error in event \"{parentEvent.Prefab.Identifier}\". {nameof(EventLogAction)} with no text set ({element})."); + DebugConsole.ThrowError($"Error in event \"{parentEvent.Prefab.Identifier}\". {nameof(EventLogAction)} with no text set ({element}).", + contentPackage: element.ContentPackage); } else { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/EventObjectiveAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/EventObjectiveAction.cs index 17316466f..82cbb6ee8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/EventObjectiveAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/EventObjectiveAction.cs @@ -49,13 +49,15 @@ namespace Barotrauma { DebugConsole.ThrowError( $"Error in {nameof(EventObjectiveAction)} in the event \"{parentEvent.Prefab.Identifier}\""+ - $" - {nameof(TextTag)} will do nothing unless the action triggers a message box or a video."); + $" - {nameof(TextTag)} will do nothing unless the action triggers a message box or a video.", + contentPackage: element.ContentPackage); } if (element.GetChildElement("Replace") != null) { DebugConsole.ThrowError( $"Error in {nameof(EventObjectiveAction)} in the event \"{parentEvent.Prefab.Identifier}\"" + - $" - unrecognized child element \"Replace\"."); + $" - unrecognized child element \"Replace\".", + contentPackage: element.ContentPackage); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/GiveExpAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/GiveExpAction.cs index 7031d0daf..f2899ff8a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/GiveExpAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/GiveExpAction.cs @@ -14,7 +14,8 @@ namespace Barotrauma { if (TargetTag.IsEmpty) { - DebugConsole.ThrowError($"Error in event \"{parentEvent.Prefab.Identifier}\": {nameof(GiveExpAction)} without a target tag (the action needs to know whose skill to check)."); + DebugConsole.ThrowError($"Error in event \"{parentEvent.Prefab.Identifier}\": {nameof(GiveExpAction)} without a target tag (the action needs to know whose skill to check).", + contentPackage: element.ContentPackage); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/GiveSkillExpAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/GiveSkillExpAction.cs index b051966cd..8f8cc7d0b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/GiveSkillExpAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/GiveSkillExpAction.cs @@ -17,7 +17,8 @@ namespace Barotrauma { if (TargetTag.IsEmpty) { - DebugConsole.ThrowError($"Error in event \"{parentEvent.Prefab.Identifier}\": {nameof(GiveSkillExpAction)} without a target tag (the action needs to know whose skill to check)."); + DebugConsole.ThrowError($"Error in event \"{parentEvent.Prefab.Identifier}\": {nameof(GiveSkillExpAction)} without a target tag (the action needs to know whose skill to check).", + contentPackage: element.ContentPackage); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/MissionAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/MissionAction.cs index 3973643a5..d87c58be1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/MissionAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/MissionAction.cs @@ -37,11 +37,13 @@ namespace Barotrauma { if (MissionIdentifier.IsEmpty && MissionTag.IsEmpty) { - DebugConsole.ThrowError($"Error in event \"{parentEvent.Prefab.Identifier}\": neither MissionIdentifier or MissionTag has been configured."); + DebugConsole.ThrowError($"Error in event \"{parentEvent.Prefab.Identifier}\": neither MissionIdentifier or MissionTag has been configured.", + contentPackage: element.ContentPackage); } if (!MissionIdentifier.IsEmpty && !MissionTag.IsEmpty) { - DebugConsole.ThrowError($"Error in event \"{parentEvent.Prefab.Identifier}\": both MissionIdentifier or MissionTag have been configured. The tag will be ignored."); + DebugConsole.ThrowError($"Error in event \"{parentEvent.Prefab.Identifier}\": both MissionIdentifier or MissionTag have been configured. The tag will be ignored.", + contentPackage: element.ContentPackage); } LocationTypes = element.GetAttributeIdentifierArray("locationtype", Array.Empty()).ToImmutableArray(); random = new MTRandom(parentEvent.RandomSeed); @@ -103,11 +105,13 @@ namespace Barotrauma { if (!MissionIdentifier.IsEmpty) { - unlockedMission = unlockLocation.UnlockMissionByIdentifier(MissionIdentifier); + unlockedMission = unlockLocation.UnlockMissionByIdentifier(MissionIdentifier, + invokingContentPackage: ParentEvent.Prefab.ContentPackage); } else if (!MissionTag.IsEmpty) { - unlockedMission = unlockLocation.UnlockMissionByTag(MissionTag, random); + unlockedMission = unlockLocation.UnlockMissionByTag(MissionTag, random, + invokingContentPackage: ParentEvent.Prefab.ContentPackage); } if (campaign is MultiPlayerCampaign mpCampaign) { @@ -139,7 +143,8 @@ namespace Barotrauma } else { - DebugConsole.AddWarning($"Failed to find a suitable location to unlock the mission \"{missionDebugId}\" (LocationType: {string.Join(", ", LocationTypes)}, MinLocationDistance: {MinLocationDistance}, UnlockFurtherOnMap: {UnlockFurtherOnMap})"); + DebugConsole.AddWarning($"Failed to find a suitable location to unlock the mission \"{missionDebugId}\" (LocationType: {string.Join(", ", LocationTypes)}, MinLocationDistance: {MinLocationDistance}, UnlockFurtherOnMap: {UnlockFurtherOnMap})", + ParentEvent.Prefab.ContentPackage); } } isFinished = true; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/MissionStateAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/MissionStateAction.cs index d6deb9b1c..02b726aa8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/MissionStateAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/MissionStateAction.cs @@ -24,7 +24,8 @@ namespace Barotrauma State = element.GetAttributeInt("value", State); if (MissionIdentifier.IsEmpty) { - DebugConsole.ThrowError($"Error in event \"{parentEvent.Prefab.Identifier}\": MissionIdentifier has not been configured."); + DebugConsole.ThrowError($"Error in event \"{parentEvent.Prefab.Identifier}\": MissionIdentifier has not been configured.", + contentPackage: element.ContentPackage); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/ModifyLocationAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/ModifyLocationAction.cs index 013b48771..cd044361b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/ModifyLocationAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/ModifyLocationAction.cs @@ -43,7 +43,8 @@ namespace Barotrauma var faction = campaign.Factions.Find(f => f.Prefab.Identifier == Faction); if (faction == null) { - DebugConsole.ThrowError($"Error in ModifyLocationAction ({ParentEvent.Prefab.Identifier}): could not find a faction with the identifier \"{Faction}\"."); + DebugConsole.ThrowError($"Error in ModifyLocationAction ({ParentEvent.Prefab.Identifier}): could not find a faction with the identifier \"{Faction}\".", + contentPackage: ParentEvent?.Prefab?.ContentPackage); } else { @@ -55,7 +56,8 @@ namespace Barotrauma var secondaryFaction = campaign.Factions.Find(f => f.Prefab.Identifier == SecondaryFaction); if (secondaryFaction == null) { - DebugConsole.ThrowError($"Error in ModifyLocationAction ({ParentEvent.Prefab.Identifier}): could not find a faction with the identifier \"{SecondaryFaction}\"."); + DebugConsole.ThrowError($"Error in ModifyLocationAction ({ParentEvent.Prefab.Identifier}): could not find a faction with the identifier \"{SecondaryFaction}\".", + contentPackage: ParentEvent.Prefab.ContentPackage); } else { @@ -67,7 +69,8 @@ namespace Barotrauma var locationType = LocationType.Prefabs.Find(lt => lt.Identifier == Type); if (locationType == null) { - DebugConsole.ThrowError($"Error in ModifyLocationAction ({ParentEvent.Prefab.Identifier}): could not find a location type with the identifier \"{Type}\"."); + DebugConsole.ThrowError($"Error in ModifyLocationAction ({ParentEvent.Prefab.Identifier}): could not find a location type with the identifier \"{Type}\".", + contentPackage: ParentEvent.Prefab.ContentPackage); } else if (!location.LocationTypeChangesBlocked) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/NPCChangeTeamAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/NPCChangeTeamAction.cs index 64861f6e4..239312aff 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/NPCChangeTeamAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/NPCChangeTeamAction.cs @@ -30,7 +30,8 @@ namespace Barotrauma var enums = Enum.GetValues(typeof(CharacterTeamType)).Cast(); if (!enums.Contains(TeamID)) { - DebugConsole.ThrowError($"Error in {nameof(NPCChangeTeamAction)} in the event {ParentEvent.Prefab.Identifier}. \"{TeamID}\" is not a valid Team ID. Valid values are {string.Join(',', Enum.GetNames(typeof(CharacterTeamType)))}."); + DebugConsole.ThrowError($"Error in {nameof(NPCChangeTeamAction)} in the event {ParentEvent.Prefab.Identifier}. \"{TeamID}\" is not a valid Team ID. Valid values are {string.Join(',', Enum.GetNames(typeof(CharacterTeamType)))}.", + contentPackage: element.ContentPackage); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/RNGAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/RNGAction.cs index 3ab365409..a9e4ffea3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/RNGAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/RNGAction.cs @@ -13,11 +13,13 @@ namespace Barotrauma { if (Chance >= 1.0f) { - DebugConsole.ThrowError($"Incorrectly configured RNG Action in event \"{parentEvent.Prefab.Identifier}\". Probability is 1.0 (100%) or more, the action will always succeed."); + DebugConsole.ThrowError($"Incorrectly configured RNG Action in event \"{parentEvent.Prefab.Identifier}\". Probability is 1.0 (100%) or more, the action will always succeed.", + contentPackage: element.ContentPackage); } else if (Chance <= 0.0f) { - DebugConsole.ThrowError($"Incorrectly configured RNG Action in event \"{parentEvent.Prefab.Identifier}\". Probability is 0 or less, the action will never succeed."); + DebugConsole.ThrowError($"Incorrectly configured RNG Action in event \"{parentEvent.Prefab.Identifier}\". Probability is 0 or less, the action will never succeed.", + contentPackage: element.ContentPackage); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/ReputationAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/ReputationAction.cs index 41c9391f2..f161a63fa 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/ReputationAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/ReputationAction.cs @@ -54,7 +54,8 @@ namespace Barotrauma } else { - DebugConsole.ThrowError($"Faction with the identifier \"{Identifier}\" was not found."); + DebugConsole.ThrowError($"Faction with the identifier \"{Identifier}\" was not found.", + contentPackage: ParentEvent.Prefab.ContentPackage); } break; @@ -66,7 +67,8 @@ namespace Barotrauma } default: { - DebugConsole.ThrowError("ReputationAction requires a \"TargetType\" but none were specified."); + DebugConsole.ThrowError("ReputationAction requires a \"TargetType\" but none were specified.", + contentPackage: ParentEvent.Prefab.ContentPackage); break; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/SetTraitorEventStateAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/SetTraitorEventStateAction.cs index 5361d5696..af798fefc 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/SetTraitorEventStateAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/SetTraitorEventStateAction.cs @@ -14,7 +14,8 @@ namespace Barotrauma } else { - DebugConsole.ThrowError($"Cannot use the action {nameof(SetTraitorEventStateAction)} in the event \"{parentEvent.Prefab.Identifier}\" because it's not a traitor event."); + DebugConsole.ThrowError($"Cannot use the action {nameof(SetTraitorEventStateAction)} in the event \"{parentEvent.Prefab.Identifier}\" because it's not a traitor event.", + contentPackage: element.ContentPackage); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/SkillCheckAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/SkillCheckAction.cs index c74ddea95..b3376e4a3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/SkillCheckAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/SkillCheckAction.cs @@ -23,7 +23,8 @@ namespace Barotrauma { if (TargetTag.IsEmpty) { - DebugConsole.ThrowError($"Error in event \"{parentEvent.Prefab.Identifier}\": SkillCheckAction without a target tag (the action needs to know whose skill to check)."); + DebugConsole.ThrowError($"Error in event \"{parentEvent.Prefab.Identifier}\": SkillCheckAction without a target tag (the action needs to know whose skill to check).", + contentPackage: element.ContentPackage); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/SpawnAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/SpawnAction.cs index e9c3a624e..15a58b8c0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/SpawnAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/SpawnAction.cs @@ -105,7 +105,8 @@ namespace Barotrauma { DebugConsole.ThrowError( $"Error in even \"{(parentEvent.Prefab?.Identifier.ToString() ?? "unknown")}\". " + - $"The attribute \"submarinetype\" is not valid in {nameof(SpawnAction)}. Did you mean {nameof(SpawnLocation)}?"); + $"The attribute \"submarinetype\" is not valid in {nameof(SpawnAction)}. Did you mean {nameof(SpawnLocation)}?", + contentPackage: ParentEvent.Prefab.ContentPackage); } } @@ -233,7 +234,8 @@ namespace Barotrauma { if (MapEntityPrefab.FindByIdentifier(ItemIdentifier) is not ItemPrefab itemPrefab) { - DebugConsole.ThrowError("Error in SpawnAction (item prefab \"" + ItemIdentifier + "\" not found)"); + DebugConsole.ThrowError("Error in SpawnAction (item prefab \"" + ItemIdentifier + "\" not found)", + contentPackage: ParentEvent.Prefab.ContentPackage); } else { @@ -256,7 +258,8 @@ namespace Barotrauma if (spawnInventory == null) { - DebugConsole.ThrowError($"Could not spawn \"{ItemIdentifier}\" in target inventory \"{TargetInventory}\" - matching target not found."); + DebugConsole.ThrowError($"Could not spawn \"{ItemIdentifier}\" in target inventory \"{TargetInventory}\" - matching target not found.", + contentPackage: ParentEvent.Prefab.ContentPackage); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TagAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TagAction.cs index e608b7cde..7374e3466 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TagAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TagAction.cs @@ -36,7 +36,19 @@ namespace Barotrauma private bool isFinished = false; - private bool targetNotFound = false; + /// + /// If the action tags some entities directly (not trying to find targets on the fly), + /// we may be able to determine that targets can not be found even if we'd recheck + /// + private bool cantFindTargets = false; + + /// + /// If the TagAction adds a target predicate (a criteria that keeps finding targets on the fly), + /// we must keep checking if targets have been found to determine if the action can continue or not + /// + private bool mustRecheckTargets = false; + + private bool taggingDone = false; public TagAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { @@ -198,6 +210,7 @@ namespace Barotrauma else { ParentEvent.AddTargetPredicate(tag, predicate); + mustRecheckTargets = true; } } @@ -205,7 +218,7 @@ namespace Barotrauma { if (entities.None()) { - targetNotFound = true; + cantFindTargets = true; return; } if (ChoosePercentage > 0.0f) @@ -230,7 +243,7 @@ namespace Barotrauma { if (entities.None()) { - targetNotFound = true; + cantFindTargets = true; return; } @@ -250,7 +263,7 @@ namespace Barotrauma { if (entities.None()) { - targetNotFound = true; + cantFindTargets = true; return; } ParentEvent.AddTarget(tag, entities.GetRandomUnsynced()); @@ -260,26 +273,30 @@ namespace Barotrauma public override void Update(float deltaTime) { - if (isFinished || targetNotFound) { return; } + if (isFinished || cantFindTargets) { return; } - string[] criteriaSplit = Criteria.Split(';'); - - targetNotFound = false; - foreach (string entry in criteriaSplit) + if (!taggingDone) { - string[] kvp = entry.Split(':'); - Identifier key = kvp[0].Trim().ToIdentifier(); - Identifier value = kvp.Length > 1 ? kvp[1].Trim().ToIdentifier() : Identifier.Empty; - if (Taggers.TryGetValue(key, out Action tagger)) + cantFindTargets = false; + string[] criteriaSplit = Criteria.Split(';'); + foreach (string entry in criteriaSplit) { - tagger(value); - } - else - { - string errorMessage = $"Error in TagAction (event \"{ParentEvent.Prefab.Identifier}\") - unrecognized target criteria \"{key}\"."; - DebugConsole.ThrowError(errorMessage); - GameAnalyticsManager.AddErrorEventOnce($"TagAction.Update:InvalidCriteria_{ParentEvent.Prefab.Identifier}_{key}", GameAnalyticsManager.ErrorSeverity.Error, errorMessage); + string[] kvp = entry.Split(':'); + Identifier key = kvp[0].Trim().ToIdentifier(); + Identifier value = kvp.Length > 1 ? kvp[1].Trim().ToIdentifier() : Identifier.Empty; + if (Taggers.TryGetValue(key, out Action tagger)) + { + tagger(value); + } + else + { + string errorMessage = $"Error in TagAction (event \"{ParentEvent.Prefab.Identifier}\") - unrecognized target criteria \"{key}\"."; + DebugConsole.ThrowError(errorMessage, + contentPackage: ParentEvent.Prefab?.ContentPackage); + GameAnalyticsManager.AddErrorEventOnce($"TagAction.Update:InvalidCriteria_{ParentEvent.Prefab.Identifier}_{key}", GameAnalyticsManager.ErrorSeverity.Error, errorMessage); + } } + taggingDone = true; } if (ContinueIfNoTargetsFound) @@ -288,7 +305,14 @@ namespace Barotrauma } else { - isFinished = !targetNotFound; + if (mustRecheckTargets) + { + isFinished = ParentEvent.GetTargets(Tag).Any(); + } + else + { + isFinished = !cantFindTargets; + } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TriggerEventAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TriggerEventAction.cs index 51e265f83..ae6a8c75c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TriggerEventAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TriggerEventAction.cs @@ -36,7 +36,8 @@ var eventPrefab = EventSet.GetEventPrefab(Identifier); if (eventPrefab == null) { - DebugConsole.ThrowError($"Error in TriggerEventAction - could not find an event with the identifier {Identifier}."); + DebugConsole.ThrowError($"Error in TriggerEventAction - could not find an event with the identifier {Identifier}.", + contentPackage: ParentEvent.Prefab.ContentPackage); } else { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TutorialHighlightAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TutorialHighlightAction.cs index 190f7fdc0..e227f401d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TutorialHighlightAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TutorialHighlightAction.cs @@ -14,7 +14,8 @@ partial class TutorialHighlightAction : EventAction { if (GameMain.NetworkMember != null) { - DebugConsole.ThrowError($"Error in event \"{parentEvent.Prefab.Identifier}\": {nameof(TutorialHighlightAction)} is not supported in multiplayer."); + DebugConsole.ThrowError($"Error in event \"{parentEvent.Prefab.Identifier}\": {nameof(TutorialHighlightAction)} is not supported in multiplayer.", + contentPackage: element.ContentPackage); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/WaitForItemFabricatedAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/WaitForItemFabricatedAction.cs index 601981cb6..89106db36 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/WaitForItemFabricatedAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/WaitForItemFabricatedAction.cs @@ -28,7 +28,8 @@ namespace Barotrauma { if (ItemTag.IsEmpty && ItemIdentifier.IsEmpty) { - DebugConsole.ThrowError($"Error in event \"{ParentEvent.Prefab.Identifier}\". {nameof(WaitForItemFabricatedAction)} does't define either a tag or an identifier of the item to check."); + DebugConsole.ThrowError($"Error in event \"{ParentEvent.Prefab.Identifier}\". {nameof(WaitForItemFabricatedAction)} does't define either a tag or an identifier of the item to check.", + contentPackage: element.ContentPackage); } foreach (var item in Item.ItemList) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs index c7c89bd26..183ba0a2b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs @@ -518,7 +518,8 @@ namespace Barotrauma { foreach (Identifier missingId in subEventPrefab.GetMissingIdentifiers()) { - DebugConsole.ThrowError($"Error in event set \"{eventSet.Identifier}\" ({eventSet.ContentFile?.ContentPackage?.Name ?? "null"}) - could not find an event prefab with the identifier \"{missingId}\"."); + DebugConsole.ThrowError($"Error in event set \"{eventSet.Identifier}\" ({eventSet.ContentFile?.ContentPackage?.Name ?? "null"}) - could not find an event prefab with the identifier \"{missingId}\".", + contentPackage: eventSet.ContentPackage); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventPrefab.cs index e1c076d3c..721f1bcb0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventPrefab.cs @@ -10,16 +10,48 @@ namespace Barotrauma public readonly ContentXElement ConfigElement; public readonly Type EventType; + + /// + /// The probability for the event to do something if it gets selected. For example, the probability for a MonsterEvent to spawn the monster(s). + /// public readonly float Probability; + + /// + /// When this event occurs, should it trigger the event cooldown during which no new events are triggered? + /// public readonly bool TriggerEventCooldown; + + /// + /// The commonness of the event (i.e. how likely it is for this specific event to be chosen from the event set it's configured in). + /// Only valid if the event set is configured to choose a random event (as opposed to just executing all the events in the set). + /// public readonly float Commonness; + + /// + /// If set, the event set can only be chosen in this biome. + /// public readonly Identifier BiomeIdentifier; + + /// + /// If set, the event set can only be chosen in locations that belong to this faction. + /// public readonly Identifier Faction; public readonly LocalizedString Name; + /// + /// If set, this event is used as an event that can unlock a path to the next biome. + /// public readonly bool UnlockPathEvent; + + /// + /// Only valid if UnlockPathEvent is set to true. The tooltip displayed on the pathway this event is blocking. + /// public readonly string UnlockPathTooltip; + + /// + /// Only valid if UnlockPathEvent is set to true. The reputation requirement displayed on the pathway this event is blocking. + /// public readonly int UnlockPathReputation; public static EventPrefab Create(ContentXElement element, RandomEventsFile file, Identifier fallbackIdentifier = default) @@ -44,12 +76,14 @@ namespace Barotrauma EventType = Type.GetType("Barotrauma." + ConfigElement.Name, true, true); if (EventType == null) { - DebugConsole.ThrowError("Could not find an event class of the type \"" + ConfigElement.Name + "\"."); + DebugConsole.ThrowError("Could not find an event class of the type \"" + ConfigElement.Name + "\".", + contentPackage: element.ContentPackage); } } catch { - DebugConsole.ThrowError("Could not find an event class of the type \"" + ConfigElement.Name + "\"."); + DebugConsole.ThrowError("Could not find an event class of the type \"" + ConfigElement.Name + "\".", + contentPackage: element.ContentPackage); } Name = TextManager.Get($"eventname.{Identifier}").Fallback(Identifier.ToString()); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventSet.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventSet.cs index e43b63056..b68a159dc 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventSet.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventSet.cs @@ -23,6 +23,10 @@ namespace Barotrauma } #endif + /// + /// Event sets are sets of random events that occur within a level (most commonly, monster spawns and scripted events). + /// Event sets can also be nested: a "parent set" can choose from several "subsets", either randomly or by some kind of criteria. + /// sealed class EventSet : Prefab { internal class EventDebugStats @@ -78,21 +82,48 @@ namespace Barotrauma return GetAllEventPrefabs().Find(prefab => prefab.Identifier == identifier); } + /// + /// If enabled, this set can only be chosen in the campaign mode. + /// public readonly bool IsCampaignSet; - //0-100 - public readonly float MinLevelDifficulty, MaxLevelDifficulty; + /// + /// The difficulty of the current level must be equal to or higher than this for this set to be chosen. + /// + public readonly float MinLevelDifficulty; + /// + /// The difficulty of the current level must be equal to or less than this for this set to be chosen. + /// + public readonly float MaxLevelDifficulty; + /// + /// If set, the event set can only be chosen in this biome. + /// public readonly Identifier BiomeIdentifier; + /// + /// If set, the event set can only be chosen in this type of level (outpost level or a connection between outpost levels). + /// public readonly LevelData.LevelType LevelType; + /// + /// If set, the event set can only be chosen in locations of this type. + /// public readonly ImmutableArray LocationTypeIdentifiers; + /// + /// If set, the event set can only be chosen in locations that belong to this faction. + /// public readonly Identifier Faction; + /// + /// If set, one event, or a sub event set, is chosen randomly from this set. + /// public readonly bool ChooseRandom; + /// + /// Only valid if ChooseRandom is enabled. How many random events to choose from the set? + /// private readonly int eventCount = 1; public readonly int SubSetCount = 1; private readonly Dictionary overrideEventCount = new Dictionary(); @@ -102,47 +133,100 @@ namespace Barotrauma /// public readonly bool Exhaustible; + /// + /// The event set won't become active until the submarine has travelled at least this far. A value between 0-1, where 0 is the beginning of the level and 1 the end of the level (e.g. 0.5 would mean the sub needs to be half-way through the level). + /// public readonly float MinDistanceTraveled; + + /// + /// The event set won't become active until the round has lasted at least this many seconds. + /// public readonly float MinMissionTime; //the events in this set are delayed if the current EventManager intensity is not between these values public readonly float MinIntensity, MaxIntensity; + /// + /// If the event is not allowed at start, it won't become active until the submarine has moved at least 50 meters away from the beginning of the level. Only valid in LocationConnections (levels between locations). + /// public readonly bool AllowAtStart; + /// + /// Normally an event (such as a monster spawn) triggers a cooldown during which no new events are created. This can be used to ignore the cooldown. + /// public readonly bool IgnoreCoolDown; + /// + /// Should this event set trigger the event cooldown (during which no new events are created) when it becomes active? + /// + public readonly bool TriggerEventCooldown; + + /// + /// Normally events can only trigger if the intensity of the situation is low enough (e.g. you won't get new monster spawns if the submarine is already facing a disaster). This can be used to ignore the intensity. + /// public readonly bool IgnoreIntensity; - public readonly bool PerRuin, PerCave, PerWreck; + /// + /// The set is applied once per each ruin in the level. Can be used to ensure there's a consistent amount of monster spawns in the ruins in the level regardless of how many there are (and that no ruin monsters spawn if there are no ruins). + /// + public readonly bool PerRuin; + + /// + /// The set is applied once per each cave in the level. Can be used to ensure there's a consistent amount of monster spawns in the cave in the level regardless of how many there are (and that no cave monsters spawn if there are no caves). + /// + public readonly bool PerCave; + + /// + /// The set is applied once per each wreck in the level. Can be used to ensure there's a consistent amount of monster spawns in the wreck in the level regardless of how many there are (and that no wreck monsters spawn if there are no wreck). + /// + public readonly bool PerWreck; + + /// + /// If enabled, this event will not be applied if the level contains hunting grounds. + /// public readonly bool DisableInHuntingGrounds; /// - /// If true, events from this set can only occur once in the level. + /// If enabled, events from this set can only occur once in the level. /// public readonly bool OncePerLevel; + /// + /// Should the event set be delayed if at least half of the crew is away from the submarine? The maximum amount of time the events can get delayed is defined in event manager settings () + /// public readonly bool DelayWhenCrewAway; - public readonly bool TriggerEventCooldown; - + /// + /// Additive sets are important to be aware of when creating custom event sets! If an additive set gets chosen for a level, the game will also select a non-additive one. + /// This means you can for example configure an additive set that spawns custom monsters (and make it very common if you want the monsters to spawn frequently), which will spawn those custom + /// monsters in addition to the vanilla monsters spawned by vanilla sets, without you having to add your custom monsters to every single vanilla set. + /// public readonly bool Additive; + /// + /// The commonness of the event set (i.e. how likely it is for this specific set to be chosen). + /// public readonly float DefaultCommonness; public readonly ImmutableDictionary OverrideCommonness; + /// + /// If set, the event set can trigger again after this amount of seconds has passed since it last triggered. + /// public readonly float ResetTime; /// - /// Used to force an event set based on how many other locations have been discovered before this. (Used for campaign tutorial event sets.) + /// Used to force an event set based on how many other locations have been discovered before this (used for campaign tutorial event sets). /// public readonly int ForceAtDiscoveredNr; /// - /// Used to force an event set based on how many other outposts have been visited before this. (Used for campaign tutorial event sets.) + /// Used to force an event set based on how many other outposts have been visited before this (used for campaign tutorial event sets). /// public readonly int ForceAtVisitedNr; + /// + /// If enabled, this set can only occur when the campaign tutorial is enabled (generally used for the tutorial events). + /// public readonly bool CampaignTutorialOnly; public readonly struct SubEventPrefab @@ -224,7 +308,8 @@ namespace Barotrauma } else { - DebugConsole.AddWarning($"{file.Path}: All root EventSets should have an identifier"); + DebugConsole.AddWarning($"{file.Path}: All root EventSets should have an identifier", + file.ContentPackage); } } @@ -264,7 +349,8 @@ namespace Barotrauma string levelTypeStr = element.GetAttributeString("leveltype", parentSet?.LevelType.ToString() ?? "LocationConnection"); if (!Enum.TryParse(levelTypeStr, true, out LevelType)) { - DebugConsole.ThrowError($"Error in event set \"{Identifier}\". \"{levelTypeStr}\" is not a valid level type."); + DebugConsole.ThrowError($"Error in event set \"{Identifier}\". \"{levelTypeStr}\" is not a valid level type.", + contentPackage: element.ContentPackage); } Faction = element.GetAttributeIdentifier(nameof(Faction), Identifier.Empty); @@ -304,7 +390,8 @@ namespace Barotrauma ForceAtVisitedNr = element.GetAttributeInt(nameof(ForceAtVisitedNr), -1); if (ForceAtDiscoveredNr >= 0 && ForceAtVisitedNr >= 0) { - DebugConsole.ThrowError($"Error with event set \"{Identifier}\" - both ForceAtDiscoveredNr and ForceAtVisitedNr are defined, this could lead to unexpected behavior"); + DebugConsole.ThrowError($"Error with event set \"{Identifier}\" - both ForceAtDiscoveredNr and ForceAtVisitedNr are defined, this could lead to unexpected behavior", + contentPackage: element.ContentPackage); } DefaultCommonness = element.GetAttributeFloat("commonness", 1.0f); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/AbandonedOutpostMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/AbandonedOutpostMission.cs index a208a7e50..53ead40a1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/AbandonedOutpostMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/AbandonedOutpostMission.cs @@ -123,7 +123,8 @@ namespace Barotrauma var itemsToDestroy = Item.ItemList.FindAll(it => it.Submarine?.Info.Type != SubmarineType.Player && it.HasTag(itemTag)); if (!itemsToDestroy.Any()) { - DebugConsole.ThrowError($"Error in mission \"{Prefab.Identifier}\". Could not find an item with the tag \"{itemTag}\"."); + DebugConsole.ThrowError($"Error in mission \"{Prefab.Identifier}\". Could not find an item with the tag \"{itemTag}\".", + contentPackage: Prefab.ContentPackage); } else { @@ -135,10 +136,11 @@ namespace Barotrauma { foreach (XElement element in itemConfig.Elements()) { - string itemIdentifier = element.GetAttributeString("identifier", ""); - if (!(MapEntityPrefab.Find(null, itemIdentifier) is ItemPrefab itemPrefab)) + Identifier itemIdentifier = element.GetAttributeIdentifier("identifier", Identifier.Empty); + if (MapEntityPrefab.FindByIdentifier(itemIdentifier) is not ItemPrefab itemPrefab) { - DebugConsole.ThrowError("Couldn't spawn item for outpost destroy mission: item prefab \"" + itemIdentifier + "\" not found"); + DebugConsole.ThrowError("Couldn't spawn item for outpost destroy mission: item prefab \"" + itemIdentifier + "\" not found", + contentPackage: Prefab.ContentPackage); continue; } @@ -189,7 +191,8 @@ namespace Barotrauma HumanPrefab humanPrefab = GetHumanPrefabFromElement(element); if (humanPrefab == null) { - DebugConsole.ThrowError($"Couldn't spawn a human character for abandoned outpost mission: human prefab \"{element.GetAttributeString("identifier", string.Empty)}\" not found"); + DebugConsole.ThrowError($"Couldn't spawn a human character for abandoned outpost mission: human prefab \"{element.GetAttributeString("identifier", string.Empty)}\" not found", + contentPackage: Prefab.ContentPackage); continue; } for (int i = 0; i < count; i++) @@ -203,7 +206,8 @@ namespace Barotrauma var characterPrefab = CharacterPrefab.FindBySpeciesName(speciesName); if (characterPrefab == null) { - DebugConsole.ThrowError($"Couldn't spawn a character for abandoned outpost mission: character prefab \"{speciesName}\" not found"); + DebugConsole.ThrowError($"Couldn't spawn a character for abandoned outpost mission: character prefab \"{speciesName}\" not found", + contentPackage: Prefab.ContentPackage); continue; } for (int i = 0; i < count; i++) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/AlienRuinMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/AlienRuinMission.cs index 1a5a8cb3a..d0c52b245 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/AlienRuinMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/AlienRuinMission.cs @@ -51,12 +51,14 @@ namespace Barotrauma TargetRuin = Level.Loaded?.Ruins?.GetRandom(randSync: Rand.RandSync.ServerAndClient); if (TargetRuin == null) { - DebugConsole.ThrowError($"Failed to initialize an Alien Ruin mission (\"{Prefab.Identifier}\"): level contains no alien ruins"); + DebugConsole.ThrowError($"Failed to initialize an Alien Ruin mission (\"{Prefab.Identifier}\"): level contains no alien ruins", + contentPackage: Prefab.ContentPackage); return; } if (targetItemIdentifiers.Length < 1 && targetEnemyIdentifiers.Length < 1) { - DebugConsole.ThrowError($"Failed to initialize an Alien Ruin mission (\"{Prefab.Identifier}\"): no target identifiers set in the mission definition"); + DebugConsole.ThrowError($"Failed to initialize an Alien Ruin mission (\"{Prefab.Identifier}\"): no target identifiers set in the mission definition", + contentPackage: Prefab.ContentPackage); return; } foreach (var item in Item.ItemList) @@ -88,12 +90,14 @@ namespace Barotrauma } else { - DebugConsole.ThrowError($"Error in an Alien Ruin mission (\"{Prefab.Identifier}\"): could not find a character prefab with the species \"{identifier}\""); + DebugConsole.ThrowError($"Error in an Alien Ruin mission (\"{Prefab.Identifier}\"): could not find a character prefab with the species \"{identifier}\"", + contentPackage: Prefab.ContentPackage); } } if (enemyPrefabs.None()) { - DebugConsole.ThrowError($"Error in an Alien Ruin mission (\"{Prefab.Identifier}\"): no enemy species defined that could be used to spawn more ({minEnemyCount - existingEnemyCount}) enemies"); + DebugConsole.ThrowError($"Error in an Alien Ruin mission (\"{Prefab.Identifier}\"): no enemy species defined that could be used to spawn more ({minEnemyCount - existingEnemyCount}) enemies", + contentPackage: Prefab.ContentPackage); return; } for (int i = 0; i < (minEnemyCount - existingEnemyCount); i++) @@ -102,7 +106,8 @@ namespace Barotrauma var spawnPos = TargetRuin.Submarine.GetWaypoints(false).GetRandomUnsynced(w => w.CurrentHull != null)?.WorldPosition; if (!spawnPos.HasValue) { - DebugConsole.ThrowError($"Error in an Alien Ruin mission (\"{Prefab.Identifier}\"): no valid spawn positions could be found for the additional ({minEnemyCount - existingEnemyCount}) enemies to be spawned"); + DebugConsole.ThrowError($"Error in an Alien Ruin mission (\"{Prefab.Identifier}\"): no valid spawn positions could be found for the additional ({minEnemyCount - existingEnemyCount}) enemies to be spawned", + contentPackage: Prefab.ContentPackage); return; } var newEnemy = Character.Create(prefab.Identifier, spawnPos.Value, ToolBox.RandomSeed(8), createNetworkEvent: false); @@ -151,7 +156,8 @@ namespace Barotrauma #if DEBUG else { - DebugConsole.ThrowError($"Error in Alien Ruin mission (\"{Prefab.Identifier}\"): unexpected target of type {target?.GetType()?.ToString()}"); + DebugConsole.ThrowError($"Error in Alien Ruin mission (\"{Prefab.Identifier}\"): unexpected target of type {target?.GetType()?.ToString()}", + contentPackage: Prefab.ContentPackage); } #endif } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/BeaconMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/BeaconMission.cs index 224dc40f1..3f22e082b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/BeaconMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/BeaconMission.cs @@ -46,6 +46,7 @@ namespace Barotrauma } sonarLabel = TextManager.Get("beaconstationsonarlabel"); + DebugConsole.NewMessage("Initialized beacon mission: " + prefab.Identifier, Color.LightSkyBlue, debugOnly: true); } private void LoadMonsters(XElement monsterElement, MonsterSet set) @@ -65,7 +66,8 @@ namespace Barotrauma } else { - DebugConsole.ThrowError($"Error in beacon mission \"{Prefab.Identifier}\". Could not find a character prefab with the name \"{speciesName}\"."); + DebugConsole.ThrowError($"Error in beacon mission \"{Prefab.Identifier}\". Could not find a character prefab with the name \"{speciesName}\".", + contentPackage: Prefab.ContentPackage); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CargoMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CargoMission.cs index 71dcf88a7..2d40a2fc5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CargoMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CargoMission.cs @@ -238,7 +238,8 @@ namespace Barotrauma if (itemConfig == null) { - DebugConsole.ThrowError("Failed to initialize items for cargo mission (itemConfig == null)"); + DebugConsole.ThrowError("Failed to initialize items for cargo mission (itemConfig == null)", + contentPackage: Prefab.ContentPackage); return; } @@ -262,7 +263,7 @@ namespace Barotrauma SpawnedInCurrentOutpost = true, AllowStealing = false }; - item.AddTag("cargomission"); + item.AddTag(Tags.CargoMissionItem); item.AddTag(Prefab.Identifier); foreach (var tag in Prefab.Tags) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/EndMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/EndMission.cs index 8eaba7820..1ffc09b93 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/EndMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/EndMission.cs @@ -110,12 +110,14 @@ namespace Barotrauma bossPrefab = CharacterPrefab.FindBySpeciesName(speciesName); if (bossPrefab == null) { - DebugConsole.ThrowError($"Error in end mission \"{prefab.Identifier}\". Could not find a character prefab with the name \"{speciesName}\"."); + DebugConsole.ThrowError($"Error in end mission \"{prefab.Identifier}\". Could not find a character prefab with the name \"{speciesName}\".", + contentPackage: Prefab.ContentPackage); } } else { - DebugConsole.ThrowError($"Error in end mission \"{prefab.Identifier}\". Monster file not set."); + DebugConsole.ThrowError($"Error in end mission \"{prefab.Identifier}\". Monster file not set.", + contentPackage: Prefab.ContentPackage); } Identifier minionName = prefab.ConfigElement.GetAttributeIdentifier("minionfile", Identifier.Empty); @@ -124,7 +126,8 @@ namespace Barotrauma minionPrefab = CharacterPrefab.FindBySpeciesName(minionName); if (minionPrefab == null) { - DebugConsole.ThrowError($"Error in end mission \"{prefab.Identifier}\". Could not find a character prefab with the name \"{speciesName}\"."); + DebugConsole.ThrowError($"Error in end mission \"{prefab.Identifier}\". Could not find a character prefab with the name \"{speciesName}\".", + contentPackage: Prefab.ContentPackage); } } @@ -137,7 +140,8 @@ namespace Barotrauma projectilePrefab = MapEntityPrefab.FindByIdentifier(projectileId) as ItemPrefab; if (projectilePrefab == null) { - DebugConsole.ThrowError($"Error in end mission \"{prefab.Identifier}\". Could not find an item prefab with the name \"{projectileId}\"."); + DebugConsole.ThrowError($"Error in end mission \"{prefab.Identifier}\". Could not find an item prefab with the name \"{projectileId}\".", + contentPackage: Prefab.ContentPackage); } } @@ -152,7 +156,8 @@ namespace Barotrauma bossSpawnPoint = WayPoint.WayPointList.FirstOrDefault(wp => wp.Tags.Contains(spawnPointTag)); if (bossSpawnPoint == null) { - DebugConsole.ThrowError($"Error in end mission \"{Prefab.Identifier}\". Could not find a spawn point \"{spawnPointTag}\"."); + DebugConsole.ThrowError($"Error in end mission \"{Prefab.Identifier}\". Could not find a spawn point \"{spawnPointTag}\".", + contentPackage: Prefab.ContentPackage); return; } if (!IsClient) @@ -171,14 +176,16 @@ namespace Barotrauma } if (destructibleItemTag.IsEmpty) { - DebugConsole.ThrowError($"Error in end mission \"{Prefab.Identifier}\". Destructible item tag not set."); + DebugConsole.ThrowError($"Error in end mission \"{Prefab.Identifier}\". Destructible item tag not set.", + contentPackage: Prefab.ContentPackage); return; } destructibleItems.Clear(); destructibleItems.AddRange(Item.ItemList.FindAll(it => it.HasTag(destructibleItemTag))); if (destructibleItems.None()) { - DebugConsole.ThrowError($"Error in end mission \"{Prefab.Identifier}\". Could not find any destructible items with the tag \"{spawnPointTag}\"."); + DebugConsole.ThrowError($"Error in end mission \"{Prefab.Identifier}\". Could not find any destructible items with the tag \"{spawnPointTag}\".", + contentPackage: Prefab.ContentPackage); return; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/EscortMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/EscortMission.cs index 10fa64cb8..fa1cad95d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/EscortMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/EscortMission.cs @@ -77,7 +77,8 @@ namespace Barotrauma { if (inMission) { - DebugConsole.ThrowError("MainSub was null when trying to retrieve submarine size for determining escorted character count!"); + DebugConsole.ThrowError("MainSub was null when trying to retrieve submarine size for determining escorted character count!", + contentPackage: Prefab.ContentPackage); } return 1; } @@ -180,7 +181,8 @@ namespace Barotrauma if (scalingCharacterCount * characterConfig.Elements().Count() != characters.Count) { - DebugConsole.AddWarning("Character count did not match expected character count in InitCharacters of EscortMission"); + DebugConsole.AddWarning("Character count did not match expected character count in InitCharacters of EscortMission", + Prefab.ContentPackage); return; } int i = 0; @@ -220,7 +222,8 @@ namespace Barotrauma if (characterConfig == null) { - DebugConsole.ThrowError("Failed to initialize characters for escort mission (characterConfig == null)"); + DebugConsole.ThrowError("Failed to initialize characters for escort mission (characterConfig == null)", + contentPackage: Prefab.ContentPackage); return; } @@ -258,7 +261,7 @@ namespace Barotrauma { character.Speak(TextManager.Get("dialogterroristannounce").Value, null, Rand.Range(0.5f, 3f)); } - XElement randomElement = itemConfig.Elements().GetRandomUnsynced(e => e.GetAttributeFloat(0f, "mindifficulty") <= Level.Loaded.Difficulty); + ContentXElement randomElement = itemConfig.Elements().GetRandomUnsynced(e => e.GetAttributeFloat(0f, "mindifficulty") <= Level.Loaded.Difficulty); if (randomElement != null) { HumanPrefab.InitializeItem(character, randomElement, character.Submarine, humanPrefab: null, createNetworkEvents: true); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MineralMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MineralMission.cs index fbbdc98c1..561fed7d5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MineralMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MineralMission.cs @@ -119,14 +119,16 @@ namespace Barotrauma { if (MapEntityPrefab.FindByIdentifier(identifier) is not ItemPrefab prefab) { - DebugConsole.ThrowError($"Error in MineralMission: couldn't find an item prefab (identifier: \"{identifier}\")"); + DebugConsole.ThrowError($"Error in MineralMission: couldn't find an item prefab (identifier: \"{identifier}\")", + contentPackage: Prefab.ContentPackage); continue; } var spawnedResources = level.GenerateMissionResources(prefab, amount, positionType, caves); if (spawnedResources.Count < amount) { - DebugConsole.ThrowError($"Error in MineralMission: spawned only {spawnedResources.Count}/{amount} of {prefab.Name}"); + DebugConsole.ThrowError($"Error in MineralMission: spawned only {spawnedResources.Count}/{amount} of {prefab.Name}", + contentPackage: Prefab.ContentPackage); } if (spawnedResources.None()) { continue; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs index b2976762c..717befcb9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs @@ -347,7 +347,8 @@ namespace Barotrauma var eventPrefab = EventSet.GetAllEventPrefabs().Find(p => p.Identifier == trigger.EventIdentifier); if (eventPrefab == null) { - DebugConsole.ThrowError($"Mission \"{Name}\" failed to trigger an event (couldn't find an event with the identifier \"{trigger.EventIdentifier}\")."); + DebugConsole.ThrowError($"Mission \"{Name}\" failed to trigger an event (couldn't find an event with the identifier \"{trigger.EventIdentifier}\").", + contentPackage: Prefab.ContentPackage); return; } if (GameMain.GameSession?.EventManager != null) @@ -378,7 +379,7 @@ namespace Barotrauma catch (Exception e) { string errorMsg = "Unknown error while giving mission rewards."; - DebugConsole.ThrowError(errorMsg, e); + DebugConsole.ThrowError(errorMsg, e, contentPackage: Prefab.ContentPackage); GameAnalyticsManager.AddErrorEventOnce("Mission.End:GiveReward", GameAnalyticsManager.ErrorSeverity.Error, errorMsg + "\n" + e.StackTrace); #if SERVER GameMain.Server?.SendChatMessage(errorMsg + "\n" + e.StackTrace, Networking.ChatMessageType.Error); @@ -547,7 +548,8 @@ namespace Barotrauma { if (element.Attribute("name") != null) { - DebugConsole.ThrowError("Error in mission \"" + Name + "\" - use character identifiers instead of names to configure the characters."); + DebugConsole.ThrowError($"Error in mission \"{Name}\" - use character identifiers instead of names to configure the characters.", + contentPackage: Prefab.ContentPackage); return null; } @@ -556,7 +558,8 @@ namespace Barotrauma HumanPrefab humanPrefab = NPCSet.Get(characterFrom, characterIdentifier); if (humanPrefab == null) { - DebugConsole.ThrowError($"Couldn't spawn character for mission: character prefab \"{characterIdentifier}\" not found in the NPC set \"{characterFrom}\"."); + DebugConsole.ThrowError($"Couldn't spawn character for mission: character prefab \"{characterIdentifier}\" not found in the NPC set \"{characterFrom}\".", + contentPackage: Prefab.ContentPackage); return null; } @@ -587,12 +590,14 @@ namespace Barotrauma ItemPrefab itemPrefab; if (element.Attribute("name") != null) { - DebugConsole.ThrowError($"Error in mission \"{Name}\" - use item identifiers instead of names to configure the items"); + DebugConsole.ThrowError($"Error in mission \"{Name}\" - use item identifiers instead of names to configure the items", + contentPackage: Prefab.ContentPackage); string itemName = element.GetAttributeString("name", ""); itemPrefab = MapEntityPrefab.Find(itemName) as ItemPrefab; if (itemPrefab == null) { - DebugConsole.ThrowError($"Couldn't spawn item for mission \"{Name}\": item prefab \"{itemName}\" not found"); + DebugConsole.ThrowError($"Couldn't spawn item for mission \"{Name}\": item prefab \"{itemName}\" not found", + contentPackage: Prefab.ContentPackage); } } else @@ -601,7 +606,8 @@ namespace Barotrauma itemPrefab = MapEntityPrefab.Find(null, itemIdentifier) as ItemPrefab; if (itemPrefab == null) { - DebugConsole.ThrowError($"Couldn't spawn item for mission \"{Name}\": item prefab \"{itemIdentifier}\" not found"); + DebugConsole.ThrowError($"Couldn't spawn item for mission \"{Name}\": item prefab \"{itemIdentifier}\" not found", + contentPackage: Prefab.ContentPackage); } } return itemPrefab; @@ -614,14 +620,16 @@ namespace Barotrauma WayPoint cargoSpawnPos = WayPoint.GetRandom(SpawnType.Cargo, null, Submarine.MainSub, useSyncedRand: true); if (cargoSpawnPos == null) { - DebugConsole.ThrowError($"Couldn't spawn items for mission \"{Name}\": no waypoints marked as Cargo were found"); + DebugConsole.ThrowError($"Couldn't spawn items for mission \"{Name}\": no waypoints marked as Cargo were found", + contentPackage: Prefab.ContentPackage); return null; } var cargoRoom = cargoSpawnPos.CurrentHull; if (cargoRoom == null) { - DebugConsole.ThrowError($"Couldn't spawn items for mission \"{Name}\": waypoints marked as Cargo must be placed inside a room"); + DebugConsole.ThrowError($"Couldn't spawn items for mission \"{Name}\": waypoints marked as Cargo must be placed inside a room", + contentPackage: Prefab.ContentPackage); return null; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MissionPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MissionPrefab.cs index ef4c76774..4df37a741 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MissionPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MissionPrefab.cs @@ -94,8 +94,19 @@ namespace Barotrauma DataRewards = new List<(Identifier Identifier, object Value, SetDataAction.OperationType OperationType)>(); public readonly int Commonness; + /// + /// Displayed difficulty (indicator) + /// public readonly int? Difficulty; public const int MinDifficulty = 1, MaxDifficulty = 4; + /// + /// The actual minimum difficulty of the level allowed for this mission to trigger. + /// + public readonly int MinLevelDifficulty = 0; + /// + /// The actual maximum difficulty of the level allowed for this mission to trigger. + /// + public readonly int MaxLevelDifficulty = 100; public readonly int Reward; @@ -211,6 +222,10 @@ namespace Barotrauma int difficulty = element.GetAttributeInt("difficulty", MinDifficulty); Difficulty = Math.Clamp(difficulty, MinDifficulty, MaxDifficulty); } + MinLevelDifficulty = element.GetAttributeInt(nameof(MinLevelDifficulty), MinLevelDifficulty); + MaxLevelDifficulty = element.GetAttributeInt(nameof(MaxLevelDifficulty), MaxLevelDifficulty); + MinLevelDifficulty = Math.Clamp(MinLevelDifficulty, 0, Math.Min(MaxLevelDifficulty, 100)); + MaxLevelDifficulty = Math.Clamp(MaxLevelDifficulty, Math.Max(MinLevelDifficulty, 0), 100); ShowProgressBar = element.GetAttributeBool(nameof(ShowProgressBar), false); ShowProgressInNumbers = element.GetAttributeBool(nameof(ShowProgressInNumbers), false); @@ -372,7 +387,8 @@ namespace Barotrauma } if (constructor == null) { - DebugConsole.ThrowError($"Failed to find a constructor for the mission type \"{Type}\"!"); + DebugConsole.ThrowError($"Failed to find a constructor for the mission type \"{Type}\"!", + contentPackage: element.ContentPackage); } InitProjSpecific(element); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MonsterMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MonsterMission.cs index 8b3d03131..4b3e1a2b8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MonsterMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MonsterMission.cs @@ -48,7 +48,8 @@ namespace Barotrauma } else { - DebugConsole.ThrowError($"Error in monster mission \"{prefab.Identifier}\". Could not find a character prefab with the name \"{speciesName}\"."); + DebugConsole.ThrowError($"Error in monster mission \"{prefab.Identifier}\". Could not find a character prefab with the name \"{speciesName}\".", + contentPackage: prefab.ContentPackage); } } @@ -78,7 +79,8 @@ namespace Barotrauma } else { - DebugConsole.ThrowError($"Error in monster mission \"{prefab.Identifier}\". Could not find a character prefab with the name \"{speciesName}\"."); + DebugConsole.ThrowError($"Error in monster mission \"{prefab.Identifier}\". Could not find a character prefab with the name \"{speciesName}\".", + contentPackage: prefab.ContentPackage); } } @@ -118,19 +120,19 @@ namespace Barotrauma float minDistBetweenMonsterMissions = 10000; float mindDistFromSub = Level.Loaded.Size.X * 0.3f; var monsterMissions = GameMain.GameSession.Missions.Select(e => e as MonsterMission).Where(m => m != null && m != this && m.spawnPos.HasValue); - if (!Level.Loaded.TryGetInterestingPosition(useSyncedRand: true, spawnPosType, mindDistFromSub, out Vector2 spawnPos, + if (!Level.Loaded.TryGetInterestingPosition(useSyncedRand: true, spawnPosType, mindDistFromSub, out Level.InterestingPosition spawnPos, filter: p => monsterMissions.None(m => Vector2.DistanceSquared(p.Position.ToVector2(), m.spawnPos.Value) < minDistBetweenMonsterMissions * minDistBetweenMonsterMissions), suppressWarning: true)) { Level.Loaded.TryGetInterestingPosition(useSyncedRand: true, spawnPosType, mindDistFromSub, out spawnPos); } - this.spawnPos = spawnPos; + this.spawnPos = spawnPos.Position.ToVector2(); foreach (var (character, amountRange) in monsterPrefabs) { int amount = Rand.Range(amountRange.X, amountRange.Y + 1); for (int i = 0; i < amount; i++) { - monsters.Add(Character.Create(character.Identifier, spawnPos, ToolBox.RandomSeed(8), createNetworkEvent: false)); + monsters.Add(Character.Create(character.Identifier, this.spawnPos.Value, ToolBox.RandomSeed(8), createNetworkEvent: false)); } } InitializeMonsters(monsters); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/NestMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/NestMission.cs index 78f840cf9..1c766a7fc 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/NestMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/NestMission.cs @@ -85,7 +85,8 @@ namespace Barotrauma } else { - DebugConsole.ThrowError($"Error in monster mission \"{prefab.Identifier}\". Could not find a character prefab with the name \"{speciesName}\"."); + DebugConsole.ThrowError($"Error in monster mission \"{prefab.Identifier}\". Could not find a character prefab with the name \"{speciesName}\".", + contentPackage: Prefab.ContentPackage); } } @@ -174,7 +175,8 @@ namespace Barotrauma var itemIdentifier = subElement.GetAttributeIdentifier("identifier", Identifier.Empty); if (MapEntityPrefab.FindByIdentifier(itemIdentifier) is not ItemPrefab itemPrefab) { - DebugConsole.ThrowError("Couldn't spawn item for nest mission: item prefab \"" + itemIdentifier + "\" not found"); + DebugConsole.ThrowError("Couldn't spawn item for nest mission: item prefab \"" + itemIdentifier + "\" not found", + contentPackage: Prefab.ContentPackage); continue; } @@ -285,7 +287,8 @@ namespace Barotrauma } if (Level.Loaded.IsPositionInsideWall(nestPosition)) { - DebugConsole.AddWarning($"Error in nest mission \"{Prefab.Identifier}\": nest position was inside a wall ({nestPosition})."); + DebugConsole.AddWarning($"Error in nest mission \"{Prefab.Identifier}\": nest position was inside a wall ({nestPosition}).", + Prefab.ContentPackage); } monsterPrefabs.Clear(); break; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/PirateMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/PirateMission.cs index 80392f0ef..e87e70696 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/PirateMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/PirateMission.cs @@ -11,9 +11,9 @@ namespace Barotrauma { partial class PirateMission : Mission { - private readonly XElement submarineTypeConfig; - private readonly XElement characterConfig; - private readonly XElement characterTypeConfig; + private readonly ContentXElement submarineTypeConfig; + private readonly ContentXElement characterConfig; + private readonly ContentXElement characterTypeConfig; private readonly float addedMissionDifficultyPerPlayer; private float missionDifficulty; @@ -103,7 +103,8 @@ namespace Barotrauma var characterTypeElement = characterTypeConfig.Elements().FirstOrDefault(e => e.GetAttributeString("typeidentifier", string.Empty) == characterId); if (characterTypeElement == null) { - DebugConsole.ThrowError($"Error in mission \"{prefab.Identifier}\". Could not find a character type element for the character \"{characterId}\"."); + DebugConsole.ThrowError($"Error in mission \"{prefab.Identifier}\". Could not find a character type element for the character \"{characterId}\".", + contentPackage: Prefab.ContentPackage); } } //make sure all defined character types can be found from human prefabs @@ -116,7 +117,8 @@ namespace Barotrauma HumanPrefab humanPrefab = NPCSet.Get(characterFrom, characterIdentifier); if (humanPrefab == null) { - DebugConsole.ThrowError($"Error in mission \"{prefab.Identifier}\". Character prefab \"{characterIdentifier}\" not found in the NPC set \"{characterFrom}\"."); + DebugConsole.ThrowError($"Error in mission \"{prefab.Identifier}\". Character prefab \"{characterIdentifier}\" not found in the NPC set \"{characterFrom}\".", + contentPackage: Prefab.ContentPackage); } } } @@ -151,7 +153,8 @@ namespace Barotrauma ContentPath submarinePath = submarineConfig.GetAttributeContentPath("path", Prefab.ContentPackage); if (submarinePath.IsNullOrEmpty()) { - DebugConsole.ThrowError($"No path used for submarine for the pirate mission \"{Prefab.Identifier}\"!"); + DebugConsole.ThrowError($"No path used for submarine for the pirate mission \"{Prefab.Identifier}\"!", + contentPackage: Prefab.ContentPackage); return; } @@ -165,7 +168,8 @@ namespace Barotrauma if (contentFile == null) { - DebugConsole.ThrowError($"No submarine file found from the path {submarinePath}!"); + DebugConsole.ThrowError($"No submarine file found from the path {submarinePath}!", + contentPackage: Prefab.ContentPackage); return; } @@ -201,16 +205,28 @@ namespace Barotrauma private void CreateMissionPositions(out Vector2 preferredSpawnPos) { - Vector2 patrolPos = enemySub.WorldPosition; + Vector2 patrolPos = Level.Loaded.EndPosition; Point subSize = enemySub.GetDockedBorders().Size; - if (!Level.Loaded.TryGetInterestingPosition(true, Level.PositionType.MainPath, Level.Loaded.Size.X * 0.3f, out preferredSpawnPos)) + preferredSpawnPos = Level.Loaded.EndPosition; + + if (Level.Loaded.TryGetInterestingPosition(true, Level.PositionType.MainPath, Level.Loaded.Size.X * 0.3f, out var potentialSpawnPos)) { - DebugConsole.ThrowError("Could not spawn pirate submarine in an interesting location! " + this); + preferredSpawnPos = potentialSpawnPos.Position.ToVector2(); } - if (!Level.Loaded.TryGetInterestingPositionAwayFromPoint(true, Level.PositionType.MainPath, Level.Loaded.Size.X * 0.3f, out patrolPos, preferredSpawnPos, minDistFromPoint: 10000f)) + else { - DebugConsole.ThrowError("Could not give pirate submarine an interesting location to patrol to! " + this); + DebugConsole.ThrowError("Could not spawn pirate submarine in an interesting location! " + this, + contentPackage: Prefab.ContentPackage); + } + if (Level.Loaded.TryGetInterestingPositionAwayFromPoint(true, Level.PositionType.MainPath, Level.Loaded.Size.X * 0.3f, out var potentialPatrolPos, preferredSpawnPos, minDistFromPoint: 10000f)) + { + patrolPos = potentialPatrolPos.Position.ToVector2(); + } + else + { + DebugConsole.ThrowError("Could not give pirate submarine an interesting location to patrol to! " + this, + contentPackage: Prefab.ContentPackage); } patrolPos = enemySub.FindSpawnPos(patrolPos, subSize); @@ -266,7 +282,8 @@ namespace Barotrauma if (characterConfig == null) { - DebugConsole.ThrowError("Failed to initialize characters for escort mission (characterConfig == null)"); + DebugConsole.ThrowError("Failed to initialize characters for escort mission (characterConfig == null)", + contentPackage: Prefab.ContentPackage); return; } @@ -281,7 +298,7 @@ namespace Barotrauma Random rand = new MTRandom(ToolBox.StringToInt(levelData.Seed)); bool commanderAssigned = false; - foreach (XElement element in characterConfig.Elements()) + foreach (ContentXElement element in characterConfig.Elements()) { // it is possible to get more than the "max" amount of characters if the modified difficulty is high enough; this is intentional // if necessary, another "hard max" value could be used to clamp the value for performance/gameplay concerns @@ -293,7 +310,8 @@ namespace Barotrauma if (characterType == null) { - DebugConsole.ThrowError($"No character types defined in CharacterTypes for a declared type identifier in mission \"{Prefab.Identifier}\"."); + DebugConsole.ThrowError($"No character types defined in CharacterTypes for a declared type identifier in mission \"{Prefab.Identifier}\".", + contentPackage: element.ContentPackage); return; } @@ -356,12 +374,12 @@ namespace Barotrauma { DebugConsole.ThrowError(submarineInfo == null ? $"Error in PirateMission: enemy sub was not created (submarineInfo == null)." : - $"Error in PirateMission: enemy sub was not created."); + $"Error in PirateMission: enemy sub was not created.", + contentPackage: Prefab.ContentPackage); return; } - Vector2 spawnPos = Level.Loaded.EndPosition; // in case TryGetInterestingPosition fails, though this should not happen - CreateMissionPositions(out spawnPos); // patrol positions are not explicitly replicated, instead they are acquired the same way the server acquires them + CreateMissionPositions(out Vector2 spawnPos); // patrol positions are not explicitly replicated, instead they are acquired the same way the server acquires them #if DEBUG if (IsClient) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/SalvageMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/SalvageMission.cs index 7c54af4ab..c8184730c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/SalvageMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/SalvageMission.cs @@ -106,12 +106,14 @@ namespace Barotrauma if (element.GetAttribute("itemname") != null) { - DebugConsole.ThrowError("Error in SalvageMission - use item identifier instead of the name of the item."); + DebugConsole.ThrowError("Error in SalvageMission - use item identifier instead of the name of the item.", + contentPackage: element.ContentPackage); string itemName = element.GetAttributeString("itemname", ""); ItemPrefab = MapEntityPrefab.Find(itemName) as ItemPrefab; if (ItemPrefab == null && ExistingItemTag.IsEmpty) { - DebugConsole.ThrowError($"Error in SalvageMission: couldn't find an item prefab with the name \"{itemName}\""); + DebugConsole.ThrowError($"Error in SalvageMission: couldn't find an item prefab with the name \"{itemName}\"", + contentPackage: element.ContentPackage); } } else @@ -128,7 +130,8 @@ namespace Barotrauma } if (ItemPrefab == null && ExistingItemTag.IsEmpty) { - DebugConsole.ThrowError($"Error in SalvageMission - couldn't find an item prefab with the identifier \"{itemIdentifier}\""); + DebugConsole.ThrowError($"Error in SalvageMission - couldn't find an item prefab with the identifier \"{itemIdentifier}\"", + contentPackage: element.ContentPackage); } } @@ -286,7 +289,8 @@ namespace Barotrauma { if (target.ItemPrefab == null && target.ContainerTag.IsEmpty) { - DebugConsole.ThrowError($"Failed to find a target item for the mission \"{Prefab.Identifier}\". Item tag: {target.ExistingItemTag}"); + DebugConsole.ThrowError($"Failed to find a target item for the mission \"{Prefab.Identifier}\". Item tag: {target.ExistingItemTag}", + contentPackage: Prefab.ContentPackage); continue; } target.Item = new Item(target.ItemPrefab, position, null); @@ -379,7 +383,8 @@ namespace Barotrauma if (target.Item == null) { #if DEBUG - DebugConsole.ThrowError("Error in salvage mission " + Prefab.Identifier + " (item was null)"); + DebugConsole.ThrowError("Error in salvage mission " + Prefab.Identifier + " (item was null)", + contentPackage: Prefab.ContentPackage); #endif return; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/ScanMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/ScanMission.cs index 364675efb..8ee06a07d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/ScanMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/ScanMission.cs @@ -64,7 +64,8 @@ namespace Barotrauma if (itemConfig == null) { - DebugConsole.ThrowError("Failed to initialize a Scan mission: item config is not set"); + DebugConsole.ThrowError("Failed to initialize a Scan mission: item config is not set", + contentPackage: Prefab.ContentPackage); return; } @@ -77,7 +78,8 @@ namespace Barotrauma TargetRuin = Level.Loaded?.Ruins?.GetRandom(randSync: Rand.RandSync.ServerAndClient); if (TargetRuin == null) { - DebugConsole.ThrowError("Failed to initialize a Scan mission: level contains no alien ruins"); + DebugConsole.ThrowError("Failed to initialize a Scan mission: level contains no alien ruins", + contentPackage: Prefab.ContentPackage); return; } @@ -85,7 +87,8 @@ namespace Barotrauma ruinWaypoints.RemoveAll(wp => wp.CurrentHull == null); if (ruinWaypoints.Count < targetsToScan) { - DebugConsole.ThrowError($"Failed to initialize a Scan mission: target ruin has less waypoints than required as scan targets ({ruinWaypoints.Count} < {targetsToScan})"); + DebugConsole.ThrowError($"Failed to initialize a Scan mission: target ruin has less waypoints than required as scan targets ({ruinWaypoints.Count} < {targetsToScan})", + contentPackage: Prefab.ContentPackage); return; } var availableWaypoints = new List(); @@ -107,7 +110,8 @@ namespace Barotrauma if (availableWaypoints.None()) { #if DEBUG - DebugConsole.ThrowError($"Error initializing a Scan mission: not enough targets available on try #{tries + 1} to reach the required scan target count (current targets: {scanTargets.Count}, required targets: {targetsToScan})"); + DebugConsole.ThrowError($"Error initializing a Scan mission: not enough targets available on try #{tries + 1} to reach the required scan target count (current targets: {scanTargets.Count}, required targets: {targetsToScan})", + contentPackage: Prefab.ContentPackage); #endif break; } @@ -131,7 +135,8 @@ namespace Barotrauma } if (scanTargets.Count < targetsToScan) { - DebugConsole.ThrowError($"Error initializing a Scan mission: not enough targets (current targets: {scanTargets.Count}, required targets: {targetsToScan})"); + DebugConsole.ThrowError($"Error initializing a Scan mission: not enough targets (current targets: {scanTargets.Count}, required targets: {targetsToScan})", + contentPackage: Prefab.ContentPackage); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/MonsterEvent.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/MonsterEvent.cs index 71eebca71..03f144e2c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/MonsterEvent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/MonsterEvent.cs @@ -10,14 +10,45 @@ namespace Barotrauma { class MonsterEvent : Event { + /// + /// The name of the species to spawn + /// public readonly Identifier SpeciesName; - public readonly int MinAmount, MaxAmount; + + /// + /// Minimum amount of monsters to spawn. You can also use "Amount" if you want to spawn a fixed number of monsters. + /// + public readonly int MinAmount; + /// + /// Maximum amount of monsters to spawn. You can also use "Amount" if you want to spawn a fixed number of monsters. + /// + public readonly int MaxAmount; + private readonly List monsters = new List(); + /// + /// The monsters are spawned at least this distance away from the players and submarines. + /// public readonly float SpawnDistance; + + /// + /// Amount of random variance in the spawn position, in pixels. Can be used to prevent all the monsters from spawning at the exact same position. + /// private readonly float scatter; + + /// + /// Used for offsetting the spawns towards the end position of the level, so that they spawn farther afront the sub than normally. In pixels. + /// private readonly float offset; + + /// + /// Delay between spawning the monsters. Only relevant if the event spawns more than one monster. + /// private readonly float delayBetweenSpawns; + + /// + /// Number seconds before the event resets after all the monsters are dead. Can be used to make the event spawn monsters multiple times. + /// private float resetTime; private float resetTimer; @@ -25,11 +56,22 @@ namespace Barotrauma private bool disallowed; + /// + /// Where should the monster spawn? + /// public readonly Level.PositionType SpawnPosType; + + /// + /// If set, the monsters will spawn at a spawnpoint that has this tag. Only relevant for events that spawn monsters in a submarine, beacon station, wreck, outpost or ruin. + /// private readonly string spawnPointTag; private bool spawnPending, spawnReady; + /// + /// Maximum number of the specific type of monster in the entire level. Can be used to prevent the event from spawning more monsters if there's + /// already enough of that type of monster, e.g. spawned by another event or by a mission. + /// public readonly int MaxAmountPerLevel = int.MaxValue; public IReadOnlyList Monsters => monsters; @@ -82,13 +124,7 @@ namespace Barotrauma MaxAmountPerLevel = prefab.ConfigElement.GetAttributeInt("maxamountperlevel", int.MaxValue); - var spawnPosTypeStr = prefab.ConfigElement.GetAttributeString("spawntype", ""); - if (string.IsNullOrWhiteSpace(spawnPosTypeStr) || - !Enum.TryParse(spawnPosTypeStr, true, out SpawnPosType)) - { - SpawnPosType = Level.PositionType.MainPath; - } - + SpawnPosType = prefab.ConfigElement.GetAttributeEnum("spawntype", Level.PositionType.MainPath); //backwards compatibility if (prefab.ConfigElement.GetAttributeBool("spawndeep", false)) { @@ -127,7 +163,8 @@ namespace Barotrauma var file = CharacterPrefab.FindBySpeciesName(SpeciesName)?.ContentFile; if (file == null) { - DebugConsole.ThrowError($"Failed to find config file for species \"{SpeciesName}\""); + DebugConsole.ThrowError($"Failed to find config file for species \"{SpeciesName}\".", + contentPackage: Prefab.ContentPackage); yield break; } else @@ -159,7 +196,8 @@ namespace Barotrauma Character createdCharacter = Character.Create(SpeciesName, Vector2.Zero, seed, characterInfo: null, isRemotePlayer: false, hasAi: true, createNetworkEvent: true, throwErrorIfNotFound: false); if (createdCharacter == null) { - DebugConsole.AddWarning($"Error in MonsterEvent: failed to spawn the character \"{SpeciesName}\". Content package: \"{prefab.ConfigElement?.ContentPackage?.Name ?? "unknown"}\"."); + DebugConsole.AddWarning($"Error in MonsterEvent: failed to spawn the character \"{SpeciesName}\". Content package: \"{prefab.ConfigElement?.ContentPackage?.Name ?? "unknown"}\".", + Prefab.ContentPackage); disallowed = true; continue; } @@ -355,29 +393,28 @@ namespace Barotrauma { if (offset > 0) { - Vector2 dir; - var waypoints = WayPoint.WayPointList.FindAll(wp => wp.Submarine == null && wp.Ruin == null); - var nearestWaypoint = waypoints.OrderBy(wp => Vector2.DistanceSquared(wp.WorldPosition, spawnPos.Value)).FirstOrDefault(); - if (nearestWaypoint != null) + var tunnelType = chosenPosition.PositionType == Level.PositionType.MainPath ? Level.TunnelType.MainPath : Level.TunnelType.SidePath; + var waypoints = WayPoint.WayPointList.FindAll(wp => + wp.Submarine == null && + wp.Ruin == null && + wp.Tunnel?.Type == tunnelType && + wp.WorldPosition.X > spawnPos.Value.X); + + if (waypoints.None()) { - int currentIndex = waypoints.IndexOf(nearestWaypoint); - var nextWaypoint = waypoints[Math.Min(currentIndex + 20, waypoints.Count - 1)]; - dir = Vector2.Normalize(nextWaypoint.WorldPosition - nearestWaypoint.WorldPosition); - // Ensure that the spawn position is not offset to the left. - if (dir.X < 0) - { - dir.X = 0; - } + DebugConsole.AddWarning($"Failed to find a spawn position offset from {spawnPos.Value}.", + Prefab.ContentPackage); } else { - dir = new Vector2(1, Rand.Range(-1f, 1f)); - } - Vector2 targetPos = spawnPos.Value + dir * offset; - var targetWaypoint = waypoints.OrderBy(wp => Vector2.DistanceSquared(wp.WorldPosition, targetPos)).FirstOrDefault(); - if (targetWaypoint != null) - { - spawnPos = targetWaypoint.WorldPosition; + float offsetSqr = offset * offset; + //find the waypoint whose distance from the spawnPos is closest to the desired offset + var targetWaypoint = waypoints.OrderBy(wp => + Math.Abs(Vector2.DistanceSquared(wp.WorldPosition, spawnPos.Value) - offsetSqr)).FirstOrDefault(); + if (targetWaypoint != null) + { + spawnPos = targetWaypoint.WorldPosition; + } } } // Ensure that the position is not inside a submarine (in practice wrecks). diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/ScriptedEvent.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/ScriptedEvent.cs index 85c735b0d..7503af5c5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/ScriptedEvent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/ScriptedEvent.cs @@ -50,7 +50,8 @@ namespace Barotrauma } if (elementId == "statuseffect") { - DebugConsole.ThrowError($"Error in event prefab \"{prefab.Identifier}\". Status effect configured as an action. Please configure status effects as child elements of a StatusEffectAction."); + DebugConsole.ThrowError($"Error in event prefab \"{prefab.Identifier}\". Status effect configured as an action. Please configure status effects as child elements of a StatusEffectAction.", + contentPackage: prefab.ContentPackage); continue; } var action = EventAction.Instantiate(this, element); @@ -59,7 +60,8 @@ namespace Barotrauma if (!Actions.Any()) { - DebugConsole.ThrowError($"Scripted event \"{prefab.Identifier}\" has no actions. The event will do nothing."); + DebugConsole.ThrowError($"Scripted event \"{prefab.Identifier}\" has no actions. The event will do nothing.", + contentPackage: prefab.ContentPackage); } requiredDestinationTypes = prefab.ConfigElement.GetAttributeStringArray("requireddestinationtypes", null); @@ -69,8 +71,9 @@ namespace Barotrauma foreach (var gotoAction in allActions.OfType()) { if (allActions.None(a => a is Label label && label.Name == gotoAction.Name)) - { - DebugConsole.ThrowError($"Error in event \"{prefab.Identifier}\". Could not find a label matching the GoTo \"{gotoAction.Name}\"."); + { + DebugConsole.ThrowError($"Error in event \"{prefab.Identifier}\". Could not find a label matching the GoTo \"{gotoAction.Name}\".", + contentPackage: prefab.ContentPackage); } } @@ -108,7 +111,8 @@ namespace Barotrauma if (target is Character character) { return character.Name; } if (target is Hull hull) { return hull.DisplayName.Value; } if (target is Submarine sub) { return sub.Info.DisplayName.Value; } - DebugConsole.AddWarning($"Failed to get the name of the event target {target} as a replacement for the tag {tag} in an event text."); + DebugConsole.AddWarning($"Failed to get the name of the event target {target} as a replacement for the tag {tag} in an event text.", + prefab.ContentPackage); return target.ToString(); } else @@ -349,7 +353,8 @@ namespace Barotrauma } if (CurrentActionIndex == -1) { - DebugConsole.AddWarning($"Could not find the GoTo label \"{goTo}\" in the event \"{Prefab.Identifier}\". Ending the event."); + DebugConsole.AddWarning($"Could not find the GoTo label \"{goTo}\" in the event \"{Prefab.Identifier}\". Ending the event.", + prefab.ContentPackage); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/CargoManager.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/CargoManager.cs index f7273d22c..9f6cbf6dc 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/CargoManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/CargoManager.cs @@ -26,6 +26,13 @@ namespace Barotrauma public readonly int BuyerCharacterInfoIdentifier; + /// + /// Should the items be given to the buyer immediately, as opposed to spawning them in the sub the next round? + /// + public bool DeliverImmediately { get; set; } + + public bool Delivered; + public PurchasedItem(ItemPrefab itemPrefab, int quantity, int buyerCharacterInfoId) { ItemPrefabIdentifier = itemPrefab.Identifier; @@ -216,9 +223,11 @@ namespace Barotrauma public List GetPurchasedItems(Location.StoreInfo store, bool create = false) => GetPurchasedItems(store?.Identifier ?? Identifier.Empty, create); - public PurchasedItem GetPurchasedItem(Identifier identifier, ItemPrefab prefab) => GetPurchasedItems(identifier)?.FirstOrDefault(i => i.ItemPrefab == prefab); + public int GetPurchasedItemCount(Location.StoreInfo store, ItemPrefab prefab) => + GetPurchasedItemCount(store?.Identifier ?? Identifier.Empty, prefab); - public PurchasedItem GetPurchasedItem(Location.StoreInfo store, ItemPrefab prefab) => GetPurchasedItem(store?.Identifier ?? Identifier.Empty, prefab); + public int GetPurchasedItemCount(Identifier identifier, ItemPrefab prefab) => + GetPurchasedItems(identifier)?.Where(i => i.ItemPrefab == prefab).Sum(it => it.Quantity) ?? 0; public List GetSoldItems(Identifier identifier, bool create = false) => GetItems(identifier, SoldItems, create); @@ -287,23 +296,6 @@ namespace Barotrauma OnItemsInSellFromSubCrateChanged?.Invoke(this); } -#if SERVER - public void OnNewItemsPurchased(Identifier storeIdentifier, List newItems, Client client) - { - StringBuilder sb = new StringBuilder(); - int price = 0; - Dictionary buyValues = GetBuyValuesAtCurrentLocation(storeIdentifier, newItems.Select(i => i.ItemPrefab)); - foreach (PurchasedItem item in newItems) - { - int itemValue = item.Quantity * buyValues[item.ItemPrefab]; - GameAnalyticsManager.AddMoneySpentEvent(itemValue, GameAnalyticsManager.MoneySink.Store, item.ItemPrefab.Identifier.Value); - sb.Append($"\n - {item.ItemPrefab.Name} x{item.Quantity}"); - price += itemValue; - } - GameServer.Log($"{NetworkMember.ClientLogName(client, client?.Name ?? "Unknown")} purchased {newItems.Count} item(s) for {TextManager.FormatCurrency(price)}{sb.ToString()}", ServerLog.MessageType.Money); - } -#endif - public void PurchaseItems(Identifier storeIdentifier, List itemsToPurchase, bool removeFromCrate, Client client = null) { var store = Location.GetStore(storeIdentifier); @@ -314,27 +306,58 @@ namespace Barotrauma var itemsInStoreCrate = GetBuyCrateItems(storeIdentifier, create: true); foreach (PurchasedItem item in itemsToPurchase) { + if (item.Quantity <= 0) { continue; } // Exchange money int itemValue = item.Quantity * buyValues[item.ItemPrefab]; if (!campaign.TryPurchase(client, itemValue)) { continue; } // Add to the purchased items - var purchasedItem = itemsPurchasedFromStore.Find(pi => pi.ItemPrefab == item.ItemPrefab); + var purchasedItem = itemsPurchasedFromStore.Find(pi => pi.ItemPrefab == item.ItemPrefab && pi.DeliverImmediately == item.DeliverImmediately); if (purchasedItem != null) { purchasedItem.Quantity += item.Quantity; } else { - purchasedItem = new PurchasedItem(item.ItemPrefab, item.Quantity, client); + purchasedItem = new PurchasedItem(item.ItemPrefab, item.Quantity, client) { DeliverImmediately = item.DeliverImmediately }; itemsPurchasedFromStore.Add(purchasedItem); } + purchasedItem.Delivered = item.DeliverImmediately; if (GameMain.IsSingleplayer) { GameAnalyticsManager.AddMoneySpentEvent(itemValue, GameAnalyticsManager.MoneySink.Store, item.ItemPrefab.Identifier.Value); } store.Balance += itemValue; - if (removeFromCrate) + } + if (GameMain.NetworkMember is not { IsClient: true }) + { + Character targetCharacter; +#if CLIENT + targetCharacter = Character.Controlled; + if (targetCharacter == null) + { + DebugConsole.ThrowError("Failed to deliver items directly to a character (not controlling a character)."); + } +#else + targetCharacter = client?.Character; + if (targetCharacter == null) + { + DebugConsole.ThrowError($"Failed to deliver items directly to a character ({(client == null ? "client was null" : $"client {client.Name} is not controlling a character")})."); + } +#endif + if (targetCharacter == null) + { + DeliverItemsToSub(itemsToPurchase.Where(it => it.DeliverImmediately), Submarine.MainSub, this); + } + else + { + DeliverItemsToCharacter(itemsToPurchase.Where(it => it.DeliverImmediately), targetCharacter, this); + } + + } + if (removeFromCrate) + { + foreach (PurchasedItem item in itemsToPurchase) { // Remove from the shopping crate if (itemsInStoreCrate.Find(pi => pi.ItemPrefab == item.ItemPrefab) is { } crateItem) @@ -387,9 +410,9 @@ namespace Barotrauma var items = new List(); foreach (var storeSpecificItems in PurchasedItems) { - items.AddRange(storeSpecificItems.Value); + items.AddRange(storeSpecificItems.Value.Where(it => !it.DeliverImmediately)); } - CreateItems(items, Submarine.MainSub, this); + DeliverItemsToSub(items, Submarine.MainSub, this); PurchasedItems.Clear(); OnPurchasedItemsChanged?.Invoke(this); } @@ -440,10 +463,22 @@ namespace Barotrauma if (!item.Components.All(static c => c is not Holdable { Attachable: true, Attached: true })) { return false; } if (!item.Components.All(static c => c is not Wire w || w.Connections.All(static c => c is null))) { return false; } if (!ItemAndAllContainersInteractable(item)) { return false; } - if (item.RootContainer is Item rootContainer && rootContainer.HasTag(Tags.DontSellItems)) { return false; } + if (!AllContainersAllowSellingItems(item)) { return false; } return true; }).Distinct(); + static bool AllContainersAllowSellingItems(Item item) + { + do + { + item = item.Container; + if (item is null) { return true; } + if (item.HasTag(Tags.DontSellItems)) { return false; } + if (item.Components.Any(static c => c.DisallowSellingItemsFromContainer)) { return false; } + } while (item != null); + return true; + } + static bool ItemAndAllContainersInteractable(Item item) { do @@ -501,7 +536,7 @@ namespace Barotrauma => items.Where(it => it.HasTag(Tags.Crate) && !it.NonInteractable && !it.NonPlayerTeamInteractable && !it.HiddenInGame && !it.Removed && (conditional == null || conditional(it))); public static IEnumerable FindReusableCargoContainers(IEnumerable subs, IEnumerable cargoRooms = null) => - FilterCargoCrates(Item.ItemList, it => subs.Contains(it.Submarine) && (cargoRooms == null || cargoRooms.Contains(it.CurrentHull))) + FilterCargoCrates(Item.ItemList, it => subs.Contains(it.Submarine) && !it.HasTag(Tags.CargoMissionItem) && (cargoRooms == null || cargoRooms.Contains(it.CurrentHull))) .Select(it => it.GetComponent()) .Where(c => c != null); @@ -553,9 +588,9 @@ namespace Barotrauma return itemContainer; } - public static void CreateItems(List itemsToSpawn, Submarine sub, CargoManager cargoManager) + public static void DeliverItemsToSub(IEnumerable itemsToSpawn, Submarine sub, CargoManager cargoManager) { - if (itemsToSpawn.Count == 0) { return; } + if (!itemsToSpawn.Any()) { return; } WayPoint wp = WayPoint.GetRandom(SpawnType.Cargo, null, sub); if (wp == null) @@ -571,7 +606,7 @@ namespace Barotrauma return; } - if (sub == Submarine.MainSub) + if (sub == Submarine.MainSub && itemsToSpawn.Any(it => !it.Delivered && it.Quantity > 0)) { #if CLIENT new GUIMessageBox("", @@ -597,37 +632,66 @@ namespace Barotrauma List availableContainers = FindReusableCargoContainers(connectedSubs, FindCargoRooms(connectedSubs)).ToList(); foreach (PurchasedItem pi in itemsToSpawn) { + pi.Delivered = true; Vector2 position = GetCargoPos(cargoRoom, pi.ItemPrefab); - for (int i = 0; i < pi.Quantity; i++) { var item = new Item(pi.ItemPrefab, position, wp.Submarine); var itemContainer = GetOrCreateCargoContainerFor(pi.ItemPrefab, cargoRoom, ref availableContainers); itemContainer?.Inventory.TryPutItem(item, null); - var idCard = item.GetComponent(); - if (cargoManager != null && idCard != null && pi.BuyerCharacterInfoIdentifier != 0) - { - cargoManager.purchasedIDCards.Add((pi, idCard)); - } - itemSpawned(pi, item); + ItemSpawned(pi, item, cargoManager); #if SERVER Entity.Spawner?.CreateNetworkEvent(new EntitySpawner.SpawnEntity(item)); #endif - (itemContainer?.Item ?? item).AssignCampaignInteractionType(CampaignMode.InteractionType.Cargo); - static void itemSpawned(PurchasedItem purchased, Item item) - { - Submarine sub = item.Submarine ?? item.RootContainer?.Submarine; - if (sub != null) - { - foreach (WifiComponent wifiComponent in item.GetComponents()) - { - wifiComponent.TeamID = sub.TeamID; - } - } - } + (itemContainer?.Item ?? item).AssignCampaignInteractionType(CampaignMode.InteractionType.Cargo); + } + } + } + + public static void DeliverItemsToCharacter(IEnumerable itemsToSpawn, Character character, CargoManager cargoManager) + { + if (!itemsToSpawn.Any()) { return; } + + foreach (PurchasedItem pi in itemsToSpawn) + { + pi.Delivered = true; + for (int i = 0; i < pi.Quantity; i++) + { + var item = new Item(pi.ItemPrefab, character.Position, character.Submarine); +#if SERVER + Entity.Spawner?.CreateNetworkEvent(new EntitySpawner.SpawnEntity(item)); +#endif + if (!character.Inventory.TryPutItem(item, user: null, item.AllowedSlots)) + { + foreach (Item containedItem in character.Inventory.AllItemsMod) + { + if (containedItem.OwnInventory != null && + containedItem.OwnInventory.TryPutItem(item, user: null, item.AllowedSlots)) + { + break; + } + } + } + ItemSpawned(pi, item, cargoManager); + } + } + } + private static void ItemSpawned(PurchasedItem purchased, Item item, CargoManager cargoManager) + { + var idCard = item.GetComponent(); + if (cargoManager != null && idCard != null && purchased.BuyerCharacterInfoIdentifier != 0) + { + cargoManager.purchasedIDCards.Add((purchased, idCard)); + } + + Submarine sub = item.Submarine ?? item.RootContainer?.Submarine; + if (sub != null) + { + foreach (WifiComponent wifiComponent in item.GetComponents()) + { + wifiComponent.TeamID = sub.TeamID; } } - itemsToSpawn.Clear(); } private readonly List<(PurchasedItem purchaseInfo, IdCard idCard)> purchasedIDCards = new List<(PurchasedItem purchaseInfo, IdCard idCard)>(); @@ -685,6 +749,7 @@ namespace Barotrauma new XAttribute("id", item.ItemPrefab.Identifier), new XAttribute("qty", item.Quantity), new XAttribute("storeid", storeSpecificItems.Key), + new XAttribute("deliverimmediately", item.DeliverImmediately), new XAttribute("buyer", item.BuyerCharacterInfoIdentifier))); } } @@ -700,17 +765,22 @@ namespace Barotrauma { string prefabId = itemElement.GetAttributeString("id", null); if (string.IsNullOrWhiteSpace(prefabId)) { continue; } - var prefab = ItemPrefab.Prefabs.Find(p => p.Identifier == prefabId); - if (prefab == null) { continue; } + if (!ItemPrefab.Prefabs.TryGet(prefabId.ToIdentifier(), out var prefab)) { continue; } int qty = itemElement.GetAttributeInt("qty", 0); Identifier storeId = itemElement.GetAttributeIdentifier("storeid", "merchant"); + bool deliverImmediately = itemElement.GetAttributeBool("deliverimmediately", false); int buyerId = itemElement.GetAttributeInt("buyer", 0); if (!purchasedItems.TryGetValue(storeId, out var storeItems)) { storeItems = new List(); purchasedItems.Add(storeId, storeItems); } - storeItems.Add(new PurchasedItem(prefab, qty, buyerId)); + storeItems.Add(new PurchasedItem(prefab, qty, buyerId) + { + DeliverImmediately = deliverImmediately, + //must have already been delivered if we had opted for immediate delivery + Delivered = deliverImmediately + }); } } SetPurchasedItems(purchasedItems); diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/CrewManager.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/CrewManager.cs index c2f3a80d4..4f6455dba 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/CrewManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/CrewManager.cs @@ -120,7 +120,7 @@ namespace Barotrauma foreach (var characterElement in element.Elements()) { if (!characterElement.Name.ToString().Equals("character", StringComparison.OrdinalIgnoreCase)) { continue; } - CharacterInfo characterInfo = new CharacterInfo(characterElement); + CharacterInfo characterInfo = new CharacterInfo(new ContentXElement(contentPackage: null, characterElement)); #if CLIENT if (characterElement.GetAttributeBool("lastcontrolled", false)) { characterInfo.LastControlled = true; } characterInfo.CrewListIndex = characterElement.GetAttributeInt("crewlistindex", -1); diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs index fcf412f27..0e5bfb3f0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs @@ -382,22 +382,28 @@ namespace Barotrauma currentLocation.DeselectMission(mission); } } - if (levelData.HasBeaconStation && !levelData.IsBeaconActive) + if (levelData.HasBeaconStation && !levelData.IsBeaconActive && Missions.None(m => m.Prefab.Type == MissionType.Beacon)) { - var beaconMissionPrefabs = MissionPrefab.Prefabs.Where(m => m.Tags.Contains("beaconnoreward")).OrderBy(m => m.UintIdentifier); + var beaconMissionPrefabs = MissionPrefab.Prefabs.Where(m => m.IsSideObjective && m.Type == MissionType.Beacon); if (beaconMissionPrefabs.Any()) { - Random rand = new MTRandom(ToolBox.StringToInt(levelData.Seed)); - var beaconMissionPrefab = ToolBox.SelectWeightedRandom(beaconMissionPrefabs, p => (float)p.Commonness, rand); - if (!Missions.Any(m => m.Prefab.Type == beaconMissionPrefab.Type)) + var filteredMissions = beaconMissionPrefabs.Where(m => levelData.Difficulty >= m.MinLevelDifficulty && levelData.Difficulty <= m.MaxLevelDifficulty); + if (filteredMissions.None()) { - extraMissions.Add(beaconMissionPrefab.Instantiate(Map.SelectedConnection.Locations, Submarine.MainSub)); + DebugConsole.AddWarning($"No suitable beacon mission found matching the level difficulty {levelData.Difficulty}. Ignoring the restriction."); } + else + { + beaconMissionPrefabs = filteredMissions; + } + Random rand = new MTRandom(ToolBox.StringToInt(levelData.Seed)); + var beaconMissionPrefab = ToolBox.SelectWeightedRandom(beaconMissionPrefabs, p => p.Commonness, rand); + extraMissions.Add(beaconMissionPrefab.Instantiate(Map.SelectedConnection.Locations, Submarine.MainSub)); } } if (levelData.HasHuntingGrounds) { - var huntingGroundsMissionPrefabs = MissionPrefab.Prefabs.Where(m => m.Tags.Contains("huntinggrounds")).OrderBy(m => m.UintIdentifier); + var huntingGroundsMissionPrefabs = MissionPrefab.Prefabs.Where(m => m.IsSideObjective && m.Tags.Contains("huntinggrounds")).OrderBy(m => m.UintIdentifier); if (!huntingGroundsMissionPrefabs.Any()) { DebugConsole.AddWarning("Could not find a hunting grounds mission for the level. No mission with the tag \"huntinggrounds\" found."); @@ -862,6 +868,16 @@ namespace Barotrauma } } + //remove ID cards left in duffel bags + foreach (var item in Item.ItemList.ToList()) + { + if (item.HasTag(Tags.IdCardTag) && + (item.Container?.HasTag(Tags.DespawnContainer) ?? false)) + { + item.Remove(); + } + } + foreach (CharacterInfo ci in CrewManager.CharacterInfos.ToList()) { if (ci.CauseOfDeath != null) @@ -1353,6 +1369,7 @@ namespace Barotrauma if (item.HiddenInGame) { continue; } if (!connectedSubs.Contains(item.Submarine)) { continue; } if (item.Prefab.DontTransferBetweenSubs) { continue; } + if (AnyParentInventoryDisableTransfer(item)) { continue; } var rootOwner = item.GetRootInventoryOwner(); if (rootOwner is Character) { continue; } if (rootOwner is Item ownerItem && (ownerItem.NonInteractable || item.NonPlayerTeamInteractable || ownerItem.HiddenInGame)) { continue; } @@ -1362,6 +1379,15 @@ namespace Barotrauma if (item.Components.Any(c => c is Wire w && w.Connections.Any(c => c != null))) { continue; } itemsToTransfer.Add((item, item.Container)); item.Submarine = null; + + static bool AnyParentInventoryDisableTransfer(Item item) + { + if (item.ParentInventory?.Owner is not Item parentOwner) { return false; } + return HasProblematicComponent(parentOwner) || AnyParentInventoryDisableTransfer(parentOwner); + + static bool HasProblematicComponent(Item it) + => it.Components.Any(static c => c.DontTransferInventoryBetweenSubs); + } } foreach (var (item, container) in itemsToTransfer) { @@ -1369,8 +1395,15 @@ namespace Barotrauma { // Drop the item if it's not inside another item set to be transferred. item.Drop(null, createNetworkEvent: false, setTransform: false); + //dropping items sets the sub, set it back to null + item.Submarine = null; + foreach (var itemContainer in item.GetComponents()) + { + itemContainer.Inventory.FindAllItems((_) => true, recursive: true).ForEach(it => it.Submarine = null); + } } } + System.Diagnostics.Debug.Assert(itemsToTransfer.None(it => it.item.Submarine != null), "Item that was set to be transferred was not removed from the sub!"); currentSub.Info.NoItems = true; } // Serialize the current sub @@ -1408,6 +1441,7 @@ namespace Barotrauma { newContainer = newSub.FindContainerFor(item, onlyPrimary: true, checkTransferConditions: true, allowConnectedSubs: true); } + string newContainerName = newContainer == null ? "(null)" : $"{newContainer.Prefab.Identifier} ({newContainer.Tags})"; if (item.Container == null && (newContainer == null || !newContainer.OwnInventory.TryPutItem(item, user: null, createNetworkEvent: false))) { var cargoContainer = CargoManager.GetOrCreateCargoContainerFor(item.Prefab, spawnHull, ref availableContainers); @@ -1416,13 +1450,16 @@ namespace Barotrauma Vector2 simPos = ConvertUnits.ToSimUnits(CargoManager.GetCargoPos(spawnHull, item.Prefab)); item.SetTransform(simPos, 0.0f, findNewHull: false, setPrevTransform: false); } - else if (cargoContainer.Item.Submarine is Submarine containerSub) + else { - // Use the item's sub in case the sub consists of multiple linked subs. - item.Submarine = containerSub; + if (cargoContainer.Item.Submarine is Submarine containerSub) + { + // Use the item's sub in case the sub consists of multiple linked subs. + item.Submarine = containerSub; + } + newContainerName = cargoContainer.Item.Prefab.Identifier.ToString(); } } - string newContainerName = newContainer == null ? "(null)" : $"{newContainer.Prefab.Identifier} ({newContainer.Tags})"; string msg = "Item transfer log error."; if (oldContainer != null) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignSettings.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignSettings.cs index 31aa8aa9e..a9f757191 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignSettings.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignSettings.cs @@ -83,6 +83,12 @@ namespace Barotrauma } } + [Serialize(0.2f, IsPropertySaveable.Yes, description: "How likely it is for security to inspect player characters for stolen items when your reputation is high?")] + public float MinStolenItemInspectionProbability { get; set; } + + [Serialize(0.9f, IsPropertySaveable.Yes, description: "How likely it is for security to inspect player characters for stolen items when your reputation is low?")] + public float MaxStolenItemInspectionProbability { get; set; } + public const int DefaultMaxMissionCount = 2; public const int MaxMissionCountLimit = 10; public const int MinMissionCountLimit = 1; diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/MultiPlayerCampaign.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/MultiPlayerCampaign.cs index a6c56b8f6..a3bce5575 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/MultiPlayerCampaign.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/MultiPlayerCampaign.cs @@ -319,6 +319,7 @@ namespace Barotrauma foreach (var item in storeItems.Value) { msg.WriteIdentifier(item.ItemPrefabIdentifier); + msg.WriteBoolean(item.DeliverImmediately); msg.WriteRangedInteger(item.Quantity, 0, CargoManager.MaxQuantity); } } @@ -336,8 +337,12 @@ namespace Barotrauma for (int j = 0; j < itemCount; j++) { Identifier itemId = msg.ReadIdentifier(); + bool deliverImmediately = msg.ReadBoolean(); +#if SERVER + if (!AllowImmediateItemDelivery(sender)) { deliverImmediately = false; } +#endif int quantity = msg.ReadRangedInteger(0, CargoManager.MaxQuantity); - items[storeId].Add(new PurchasedItem(itemId, quantity, sender)); + items[storeId].Add(new PurchasedItem(itemId, quantity, sender) { DeliverImmediately = deliverImmediately }); } } return items; diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/UpgradeManager.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/UpgradeManager.cs index 5c416862d..fd7a6fcc9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/UpgradeManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/UpgradeManager.cs @@ -177,7 +177,7 @@ namespace Barotrauma return; } - int price = prefab.Price.GetBuyPrice(GetUpgradeLevel(prefab, category), Campaign.Map?.CurrentLocation); + int price = prefab.Price.GetBuyPrice(prefab, GetUpgradeLevel(prefab, category), Campaign.Map?.CurrentLocation); int currentLevel = GetUpgradeLevel(prefab, category); int newLevel = currentLevel + 1; @@ -198,20 +198,23 @@ namespace Barotrauma return result; } - switch (GameMain.NetworkMember) + if (!force) { - case null when Character.Controlled is { } controlled: // singleplayer - if (!TryTakeResources(controlled)) { return; } - break; - case { IsClient: true }: - if (!prefab.HasResourcesToUpgrade(Character.Controlled, newLevel)) { return; } - break; - case { IsServer: true } when client?.Character is { } character: - if (!TryTakeResources(character)) { return; } - break; - default: - DebugConsole.ThrowError($"Tried to purchase \"{prefab.Name}\" without a player."); - return; + switch (GameMain.NetworkMember) + { + case null when Character.Controlled is { } controlled: // singleplayer + if (!TryTakeResources(controlled)) { return; } + break; + case { IsClient: true }: + if (!prefab.HasResourcesToUpgrade(Character.Controlled, newLevel)) { return; } + break; + case { IsServer: true } when client?.Character is { } character: + if (!TryTakeResources(character)) { return; } + break; + default: + DebugConsole.ThrowError($"Tried to purchase \"{prefab.Name}\" without a player."); + return; + } } if (price < 0) @@ -683,7 +686,8 @@ namespace Barotrauma { // automatically fix this if it ever happens? DebugConsole.AddWarning($"The upgrade {newUpgrade.Prefab.Name} in {target.Name} has a different level compared to other items! \n" + - $"Expected level was ${newLevel} but got {newUpgrade.Level} instead."); + $"Expected level was ${newLevel} but got {newUpgrade.Level} instead.", + newUpgrade.Prefab.ContentPackage); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/CharacterInventory.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/CharacterInventory.cs index c6522899c..1a3e665aa 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/CharacterInventory.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/CharacterInventory.cs @@ -48,13 +48,13 @@ namespace Barotrauma private set; } - private static string[] ParseSlotTypes(XElement element) + private static string[] ParseSlotTypes(ContentXElement element) { string slotString = element.GetAttributeString("slots", null); return slotString == null ? Array.Empty() : slotString.Split(','); } - public CharacterInventory(XElement element, Character character, bool spawnInitialItems) + public CharacterInventory(ContentXElement element, Character character, bool spawnInitialItems) : base(character, ParseSlotTypes(element).Length) { this.character = character; @@ -73,7 +73,8 @@ namespace Barotrauma slotTypeNames[i] = slotTypeNames[i].Trim(); if (!Enum.TryParse(slotTypeNames[i], out parsedSlotType)) { - DebugConsole.ThrowError("Error in the inventory config of \"" + character.SpeciesName + "\" - " + slotTypeNames[i] + " is not a valid inventory slot type."); + DebugConsole.ThrowError("Error in the inventory config of \"" + character.SpeciesName + "\" - " + slotTypeNames[i] + " is not a valid inventory slot type.", + contentPackage: element.ContentPackage); } SlotTypes[i] = parsedSlotType; switch (SlotTypes[i]) @@ -99,7 +100,8 @@ namespace Barotrauma DebugConsole.ThrowError( $"Character \"{character.SpeciesName}\" is configured to spawn with so many items it will have less than 2 free inventory slots. " + "This can cause issues with talents that spawn extra loot in monsters' inventories." - + " Consider increasing the inventory size."); + + " Consider increasing the inventory size.", + contentPackage: element.ContentPackage); } #endif @@ -115,7 +117,8 @@ namespace Barotrauma string itemIdentifier = subElement.GetAttributeString("identifier", ""); if (!ItemPrefab.Prefabs.TryGet(itemIdentifier, out var itemPrefab)) { - DebugConsole.ThrowError("Error in character inventory \"" + character.SpeciesName + "\" - item \"" + itemIdentifier + "\" not found."); + DebugConsole.ThrowError("Error in character inventory \"" + character.SpeciesName + "\" - item \"" + itemIdentifier + "\" not found.", + contentPackage: element.ContentPackage); continue; } @@ -131,7 +134,7 @@ namespace Barotrauma if (item != null && item.ParentInventory != this) { string errorMsg = $"Failed to spawn the initial item \"{item.Prefab.Identifier}\" in the inventory of \"{character.SpeciesName}\"."; - DebugConsole.ThrowError(errorMsg); + DebugConsole.ThrowError(errorMsg, contentPackage: element.ContentPackage); GameAnalyticsManager.AddErrorEventOnce("CharacterInventory:FailedToSpawnInitialItem", GameAnalyticsManager.ErrorSeverity.Error, errorMsg); } else if (!character.Enabled) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Door.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Door.cs index 41ff58290..9f80f04ad 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Door.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Door.cs @@ -184,6 +184,8 @@ namespace Barotrauma.Items.Components public bool IsFullyClosed => IsClosed && OpenState <= 0f; + public bool HasWindow => Window != Rectangle.Empty; + [Serialize(false, IsPropertySaveable.No, description: "If the door has integrated buttons, it can be opened by interacting with it directly (instead of using buttons wired to it).")] public bool HasIntegratedButtons { get; private set; } @@ -381,6 +383,31 @@ namespace Barotrauma.Items.Components return false; } + /// + /// Is the given position inside the vertical bounds of the window, and roughly on the door horizontally? Or the other way around if the door opens horizontally. + /// + /// Position in the same coordinate space as the door. + /// Maximum horizontal distance from the door (or vertical if the door opens horizontally) + public bool IsPositionOnWindow(Vector2 position, float maxPerpendicularDistance = 10.0f) + { + if (IsHorizontal) + { + return + position.X >= item.Rect.X + Window.X && + position.X <= item.Rect.X + Window.X + Window.Width && + position.Y >= item.Rect.Y - maxPerpendicularDistance && + position.Y <= item.Rect.Y - item.Rect.Height - maxPerpendicularDistance; + } + else + { + return + position.Y >= item.Rect.Y + Window.Y && + position.Y <= item.Rect.Y + Window.Y + Window.Height && + position.X >= item.Rect.X - maxPerpendicularDistance && + position.Y <= item.Rect.Right + maxPerpendicularDistance; + } + } + public override void Update(float deltaTime, Camera cam) { UpdateProjSpecific(deltaTime); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ElectricalDischarger.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ElectricalDischarger.cs index a1b8e5738..df2d173c0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ElectricalDischarger.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ElectricalDischarger.cs @@ -226,7 +226,8 @@ namespace Barotrauma.Items.Components foreach ((Character character, Node node) in charactersInRange) { if (character == null || character.Removed) { continue; } - character.ApplyAttack(user, node.WorldPosition, attack, MathHelper.Clamp(Voltage, 1.0f, MaxOverVoltageFactor)); + character.ApplyAttack(user, node.WorldPosition, attack, MathHelper.Clamp(Voltage, 1.0f, MaxOverVoltageFactor), + impulseDirection: character.WorldPosition - node.WorldPosition); } } DischargeProjSpecific(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/GeneticMaterial.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/GeneticMaterial.cs index 11b26aee2..2edaabeff 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/GeneticMaterial.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/GeneticMaterial.cs @@ -168,8 +168,8 @@ namespace Barotrauma.Items.Components conditionIncrease += user?.GetStatValue(StatTypes.GeneticMaterialRefineBonus) ?? 0.0f; if (item.Prefab == otherGeneticMaterial.item.Prefab) { + float taintedProbability = GetTaintedProbabilityOnRefine(otherGeneticMaterial, user); item.Condition = Math.Max(item.Condition, otherGeneticMaterial.item.Condition) + conditionIncrease; - float taintedProbability = GetTaintedProbabilityOnRefine(user); if (taintedProbability >= Rand.Range(0.0f, 1.0f)) { MakeTainted(); @@ -221,10 +221,10 @@ namespace Barotrauma.Items.Components return taintedEffectStrength; } - private float GetTaintedProbabilityOnRefine(Character user) + private float GetTaintedProbabilityOnRefine(GeneticMaterial otherGeneticMaterial, Character user) { if (user == null) { return 1.0f; } - float probability = MathHelper.Lerp(0.0f, 0.99f, item.Condition / 100.0f); + float probability = MathHelper.Lerp(0.0f, 0.99f, Math.Max(item.Condition, otherGeneticMaterial.Item.Condition) / 100.0f); probability *= MathHelper.Lerp(1.0f, 0.25f, DegreeOfSuccess(user)); return MathHelper.Clamp(probability, 0.0f, 1.0f); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Growable.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Growable.cs index f15afb13c..846e92df5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Growable.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Growable.cs @@ -59,7 +59,8 @@ namespace Barotrauma.Items.Components StatusEffect effect = StatusEffect.Load(subElement, Prefab?.Name.Value); if (effect.type != ActionType.OnProduceSpawned) { - DebugConsole.ThrowError("Only OnProduceSpawned type can be used in ."); + DebugConsole.ThrowError("Only OnProduceSpawned type can be used in .", + contentPackage: element.ContentPackage); continue; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RangedWeapon.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RangedWeapon.cs index 950e8cfae..315f8cc71 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RangedWeapon.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RangedWeapon.cs @@ -134,7 +134,8 @@ namespace Barotrauma.Items.Components suitableProjectiles = element.GetAttributeIdentifierArray(nameof(suitableProjectiles), Array.Empty()).ToHashSet(); if (ReloadSkillRequirement > 0 && ReloadNoSkill <= reload) { - DebugConsole.AddWarning($"Invalid XML at {item.Name}: ReloadNoSkill is lower or equal than it's reload skill, despite having ReloadSkillRequirement."); + DebugConsole.AddWarning($"Invalid XML at {item.Name}: ReloadNoSkill is lower or equal than it's reload skill, despite having ReloadSkillRequirement.", + item.Prefab.ContentPackage); } InitProjSpecific(element); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RepairTool.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RepairTool.cs index e5dfda8a4..a33359230 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RepairTool.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RepairTool.cs @@ -137,7 +137,8 @@ namespace Barotrauma.Items.Components if (element.GetAttribute("limbfixamount") != null) { - DebugConsole.ThrowError("Error in item \"" + item.Name + "\" - RepairTool damage should be configured using a StatusEffect with Afflictions, not the limbfixamount attribute."); + DebugConsole.ThrowError("Error in item \"" + item.Name + "\" - RepairTool damage should be configured using a StatusEffect with Afflictions, not the limbfixamount attribute.", + contentPackage: element.ContentPackage); } fixableEntities = new HashSet(); @@ -149,7 +150,8 @@ namespace Barotrauma.Items.Components case "fixable": if (subElement.GetAttribute("name") != null) { - DebugConsole.ThrowError("Error in RepairTool " + item.Name + " - use identifiers instead of names to configure fixable entities."); + DebugConsole.ThrowError("Error in RepairTool " + item.Name + " - use identifiers instead of names to configure fixable entities.", + contentPackage: element.ContentPackage); fixableEntities.Add(subElement.GetAttribute("name").Value.ToIdentifier()); } else @@ -536,7 +538,7 @@ namespace Barotrauma.Items.Components { Vector2 displayPos = ConvertUnits.ToDisplayUnits(rayStart + (rayEnd - rayStart) * lastPickedFraction * 0.9f); if (item.CurrentHull.Submarine != null) { displayPos += item.CurrentHull.Submarine.Position; } - new FireSource(displayPos); + new FireSource(displayPos, sourceCharacter: user); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Throwable.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Throwable.cs index 41d9c5df0..e972c855e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Throwable.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Throwable.cs @@ -38,7 +38,7 @@ namespace Barotrauma.Items.Components { if (aimPos == Vector2.Zero) { - aimPos = new Vector2(0.6f, 0.1f); + aimPos = new Vector2(0.45f, 0.1f); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemComponent.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemComponent.cs index c73ea2f41..4e1862e59 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemComponent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemComponent.cs @@ -71,6 +71,18 @@ namespace Barotrauma.Items.Components protected const float CorrectionDelay = 1.0f; protected CoroutineHandle delayedCorrectionCoroutine; + /// + /// If enabled, the contents of the item are not transferred when the player transfers items between subs. + /// Use this if this component uses item containers in a way where removing the item from the container via external means would cause problems. + /// + public virtual bool DontTransferInventoryBetweenSubs => false; + + /// + /// If enabled, the items inside any of the item containers on this item cannot be sold at an outpost. + /// Use in similar cases as . + /// + public virtual bool DisallowSellingItemsFromContainer => false; + [Editable, Serialize(0.0f, IsPropertySaveable.No, description: "How long it takes to pick up the item (in seconds).")] public float PickingTime { @@ -285,7 +297,8 @@ namespace Barotrauma.Items.Components } catch (Exception e) { - DebugConsole.ThrowError("Invalid select key in " + element + "!", e); + DebugConsole.ThrowError("Invalid select key in " + element + "!", e, + contentPackage: element.ContentPackage); } PickKey = InputType.Select; @@ -298,7 +311,8 @@ namespace Barotrauma.Items.Components } catch (Exception e) { - DebugConsole.ThrowError("Invalid pick key in " + element + "!", e); + DebugConsole.ThrowError("Invalid pick key in " + element + "!", e, + contentPackage: element.ContentPackage); } SerializableProperties = SerializableProperty.DeserializeProperties(this, element); @@ -310,7 +324,8 @@ namespace Barotrauma.Items.Components var component = item.Components.Find(ic => ic.Name.Equals(inheritRequiredSkillsFrom, StringComparison.OrdinalIgnoreCase)); if (component == null) { - DebugConsole.ThrowError($"Error in item \"{item.Name}\" - component \"{name}\" is set to inherit its required skills from \"{inheritRequiredSkillsFrom}\", but a component of that type couldn't be found."); + DebugConsole.ThrowError($"Error in item \"{item.Name}\" - component \"{name}\" is set to inherit its required skills from \"{inheritRequiredSkillsFrom}\", but a component of that type couldn't be found.", + contentPackage: element.ContentPackage); } else { @@ -325,7 +340,8 @@ namespace Barotrauma.Items.Components var component = item.Components.Find(ic => ic.Name.Equals(inheritStatusEffectsFrom, StringComparison.OrdinalIgnoreCase)); if (component == null) { - DebugConsole.ThrowError($"Error in item \"{item.Name}\" - component \"{name}\" is set to inherit its StatusEffects from \"{inheritStatusEffectsFrom}\", but a component of that type couldn't be found."); + DebugConsole.ThrowError($"Error in item \"{item.Name}\" - component \"{name}\" is set to inherit its StatusEffects from \"{inheritStatusEffectsFrom}\", but a component of that type couldn't be found.", + contentPackage: element.ContentPackage); } else if (component.statusEffectLists != null) { @@ -360,7 +376,8 @@ namespace Barotrauma.Items.Components case "requiredskills": if (subElement.GetAttribute("name") != null) { - DebugConsole.ThrowError("Error in item config \"" + item.ConfigFilePath + "\" - skill requirement in component " + GetType().ToString() + " should use a skill identifier instead of the name of the skill."); + DebugConsole.ThrowError("Error in item config \"" + item.ConfigFilePath + "\" - skill requirement in component " + GetType().ToString() + " should use a skill identifier instead of the name of the skill.", + contentPackage: element.ContentPackage); continue; } @@ -426,7 +443,8 @@ namespace Barotrauma.Items.Components } else if (!allowEmpty) { - DebugConsole.ThrowError("Error in item config \"" + item.ConfigFilePath + "\" - component " + GetType().ToString() + " requires an item with no identifiers."); + DebugConsole.ThrowError("Error in item config \"" + item.ConfigFilePath + "\" - component " + GetType().ToString() + " requires an item with no identifiers.", + contentPackage: element.ContentPackage); } } @@ -968,7 +986,8 @@ namespace Barotrauma.Items.Components { if (errorMessages) { - DebugConsole.ThrowError($"Could not find the component \"{typeName}\" ({item.Prefab.ContentFile.Path})"); + DebugConsole.ThrowError($"Could not find the component \"{typeName}\" ({item.Prefab.ContentFile.Path})", + contentPackage: element.ContentPackage); } return null; } @@ -977,7 +996,8 @@ namespace Barotrauma.Items.Components { if (errorMessages) { - DebugConsole.ThrowError($"Could not find the component \"{typeName}\" ({item.Prefab.ContentFile.Path})", e); + DebugConsole.ThrowError($"Could not find the component \"{typeName}\" ({item.Prefab.ContentFile.Path})", e, + contentPackage: element.ContentPackage); } return null; } @@ -990,14 +1010,16 @@ namespace Barotrauma.Items.Components if (constructor == null) { DebugConsole.ThrowError( - $"Could not find the constructor of the component \"{typeName}\" ({item.Prefab.ContentFile.Path})"); + $"Could not find the constructor of the component \"{typeName}\" ({item.Prefab.ContentFile.Path})", + contentPackage: element.ContentPackage); return null; } } catch (Exception e) { DebugConsole.ThrowError( - $"Could not find the constructor of the component \"{typeName}\" ({item.Prefab.ContentFile.Path})", e); + $"Could not find the constructor of the component \"{typeName}\" ({item.Prefab.ContentFile.Path})", e, + contentPackage: element.ContentPackage); return null; } ItemComponent ic = null; @@ -1010,7 +1032,7 @@ namespace Barotrauma.Items.Components } catch (TargetInvocationException e) { - DebugConsole.ThrowError($"Error while loading component of the type {type}.", e.InnerException); + DebugConsole.ThrowError($"Error while loading component of the type {type}.", e.InnerException, contentPackage: element.ContentPackage); GameAnalyticsManager.AddErrorEventOnce( $"ItemComponent.Load:TargetInvocationException{item.Name}{element.Name}", GameAnalyticsManager.ErrorSeverity.Error, diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs index db2d49659..015807276 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs @@ -284,7 +284,8 @@ namespace Barotrauma.Items.Components RelatedItem containable = RelatedItem.Load(subElement, returnEmpty: false, parentDebugName: item.Name); if (containable == null) { - DebugConsole.ThrowError("Error in item config \"" + item.ConfigFilePath + "\" - containable with no identifiers."); + DebugConsole.ThrowError("Error in item config \"" + item.ConfigFilePath + "\" - containable with no identifiers.", + contentPackage: element.ContentPackage); continue; } ContainableItems ??= new List(); @@ -321,7 +322,8 @@ namespace Barotrauma.Items.Components RelatedItem containable = RelatedItem.Load(subSubElement, returnEmpty: false, parentDebugName: item.Name); if (containable == null) { - DebugConsole.ThrowError("Error in item config \"" + item.ConfigFilePath + "\" - containable with no identifiers."); + DebugConsole.ThrowError("Error in item config \"" + item.ConfigFilePath + "\" - containable with no identifiers.", + contentPackage: element.ContentPackage); continue; } subContainableItems.Add(containable); @@ -349,7 +351,8 @@ namespace Barotrauma.Items.Components RelatedItem containable = RelatedItem.Load(subElement, returnEmpty: false, parentDebugName: item.Name); if (containable == null) { - DebugConsole.ThrowError("Error when loading containable restrictions for \"" + item.Name + "\" - containable with no identifiers."); + DebugConsole.ThrowError("Error when loading containable restrictions for \"" + item.Name + "\" - containable with no identifiers.", + contentPackage: element.ContentPackage); continue; } ContainableItems[containableIndex] = containable; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Controller.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Controller.cs index caa19f47c..7b04931f6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Controller.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Controller.cs @@ -622,7 +622,7 @@ namespace Barotrauma.Items.Components return element; } - private void LoadLimbPositions(XElement element) + private void LoadLimbPositions(ContentXElement element) { limbPositions.Clear(); foreach (var subElement in element.Elements()) @@ -631,7 +631,8 @@ namespace Barotrauma.Items.Components string limbStr = subElement.GetAttributeString("limb", ""); if (!Enum.TryParse(subElement.GetAttribute("limb").Value, out LimbType limbType)) { - DebugConsole.ThrowError($"Error in item \"{item.Name}\" - {limbStr} is not a valid limb type."); + DebugConsole.ThrowError($"Error in item \"{item.Name}\" - {limbStr} is not a valid limb type.", + contentPackage: element.ContentPackage); } else { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs index ff6719a73..0bc72b64c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs @@ -101,7 +101,8 @@ namespace Barotrauma.Items.Components { if (subElement.Name.ToString().Equals("fabricableitem", StringComparison.OrdinalIgnoreCase)) { - DebugConsole.ThrowError("Error in item " + item.Name + "! Fabrication recipes should be defined in the craftable item's xml, not in the fabricator."); + DebugConsole.ThrowError("Error in item " + item.Name + "! Fabrication recipes should be defined in the craftable item's xml, not in the fabricator.", + contentPackage: element.ContentPackage); break; } } @@ -119,12 +120,16 @@ namespace Barotrauma.Items.Components } } + //the errors below may be caused by a mod overriding a base item instead of this one, log the package of the base item in that case + var packageToLog = itemPrefab.GetParentModPackageOrThisPackage(); + bool recipeInvalid = false; foreach (var requiredItem in recipe.RequiredItems) { if (requiredItem.ItemPrefabs.None()) { - DebugConsole.ThrowError($"Error in the fabrication recipe for \"{itemPrefab.Name}\". Could not find the ingredient \"{requiredItem}\"."); + DebugConsole.ThrowError($"Error in the fabrication recipe for \"{itemPrefab.Name}\". Could not find the ingredient \"{requiredItem}\".", + contentPackage: packageToLog); recipeInvalid = true; } } @@ -132,7 +137,8 @@ namespace Barotrauma.Items.Components if (fabricationRecipes.TryGetValue(recipe.RecipeHash, out var duplicateRecipe)) { - DebugConsole.ThrowError($"Error in the fabrication recipe for \"{itemPrefab.Name}\". Duplicate recipe in \"{duplicateRecipe.TargetItem.Identifier}\"."); + DebugConsole.ThrowError($"Error in the fabrication recipe for \"{itemPrefab.Name}\". Duplicate recipe in \"{duplicateRecipe.TargetItem.Identifier}\".", + contentPackage: packageToLog); continue; } fabricationRecipes.Add(recipe.RecipeHash, recipe); @@ -416,7 +422,7 @@ namespace Barotrauma.Items.Components if (requiredItem.UseCondition && suitableIngredient.ConditionPercentage - requiredItem.MinCondition * 100 > 0.0f) { suitableIngredient.Condition -= suitableIngredient.Prefab.Health * requiredItem.MinCondition; - continue; + break; } if (suitableIngredient.OwnInventory != null) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Planter.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Planter.cs index 2e432496a..4c7cb459e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Planter.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Planter.cs @@ -82,6 +82,9 @@ namespace Barotrauma.Items.Components private List? lightComponents; + // We don't want the seeds to be transferred to a new submarine as seeds are not supposed to leave the container after they have been planted. + public override bool DontTransferInventoryBetweenSubs => true; + public Planter(Item item, ContentXElement element) : base(item, element) { canBePicked = true; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Power/PowerTransfer.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Power/PowerTransfer.cs index f023ff870..c0d788133 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Power/PowerTransfer.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Power/PowerTransfer.cs @@ -407,7 +407,7 @@ namespace Barotrauma.Items.Components } } - if (!(this is RelayComponent)) + if (this is not RelayComponent) { if (PowerConnections.Any(p => !p.IsOutput) && PowerConnections.Any(p => p.IsOutput)) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs index 2af67598e..6e5363695 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs @@ -306,7 +306,8 @@ namespace Barotrauma.Items.Components if (item.body == null) { - DebugConsole.ThrowError($"Error in projectile definition ({item.Name}): No body defined!"); + DebugConsole.ThrowError($"Error in projectile definition ({item.Name}): No body defined!", + contentPackage: element.ContentPackage); return; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Quality.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Quality.cs index a41e1e1c8..03b727bc3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Quality.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Quality.cs @@ -60,7 +60,8 @@ namespace Barotrauma.Items.Components string statTypeString = subElement.GetAttributeString("stattype", ""); if (!Enum.TryParse(statTypeString, true, out StatType statType)) { - DebugConsole.ThrowError("Invalid stat type type \"" + statTypeString + "\" in item (" + ((MapEntity)item).Prefab.Identifier + ")"); + DebugConsole.ThrowError("Invalid stat type type \"" + statTypeString + "\" in item (" + ((MapEntity)item).Prefab.Identifier + ")", + contentPackage: element.ContentPackage); } float statValue = subElement.GetAttributeFloat("value", 0f); statValues.TryAdd(statType, statValue); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Scanner.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Scanner.cs index 1d592e015..bd84d052f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Scanner.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Scanner.cs @@ -82,7 +82,8 @@ namespace Barotrauma.Items.Components Holdable = item.GetComponent(); if (Holdable == null || !Holdable.Attachable) { - DebugConsole.ThrowError("Error in initializing a Scanner component: an attachable Holdable component is required on the same item and none was found"); + DebugConsole.ThrowError("Error in initializing a Scanner component: an attachable Holdable component is required on the same item and none was found", + contentPackage: item.Prefab.ContentPackage); IsActive = false; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/ButtonTerminal.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/ButtonTerminal.cs index c7fd99f51..6902b3b5e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/ButtonTerminal.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/ButtonTerminal.cs @@ -26,7 +26,8 @@ namespace Barotrauma.Items.Components RequiredSignalCount = element.GetChildElements("TerminalButton").Count(c => c.GetAttribute("style") != null); if (RequiredSignalCount < 1) { - DebugConsole.ThrowError($"Error in item \"{item.Name}\": no TerminalButton elements defined for the ButtonTerminal component!"); + DebugConsole.ThrowError($"Error in item \"{item.Name}\": no TerminalButton elements defined for the ButtonTerminal component!", + contentPackage: element.ContentPackage); } InitProjSpecific(element); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/CircuitBox.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/CircuitBox.cs index f02fbebae..d2675e51b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/CircuitBox.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/CircuitBox.cs @@ -26,19 +26,40 @@ namespace Barotrauma.Items.Components public override bool IsActive => true; + // We don't want the components and wires to transfer between subs as it would cause issues. + public override bool DontTransferInventoryBetweenSubs => true; + + // We don't want to sell the components and wires inside the circuit box + public override bool DisallowSellingItemsFromContainer => true; + public Option FindInputOutputConnection(Identifier connectionName) { foreach (CircuitBoxInputConnection input in Inputs) { if (input.Name != connectionName) { continue; } - return Option.Some(input); } foreach (CircuitBoxOutputConnection output in Outputs) { if (output.Name != connectionName) { continue; } + return Option.Some(output); + } + return Option.None; + } + + public Option FindInputOutputConnection(Connection connection) + { + foreach (CircuitBoxInputConnection input in Inputs) + { + if (input.Connection != connection) { continue; } + return Option.Some(input); + } + + foreach (CircuitBoxOutputConnection output in Outputs) + { + if (output.Connection != connection) { continue; } return Option.Some(output); } @@ -338,7 +359,7 @@ namespace Barotrauma.Items.Components return; } - SpawnItem(this, prefab, WireContainer, wire => + SpawnItem(prefab, user: null, container: WireContainer, onSpawned: wire => { AddWireDirect(wireId, prefab, Option.Some(wire), one, two); onItemSpawned(wire); @@ -359,7 +380,7 @@ namespace Barotrauma.Items.Components private void AddWireDirect(ushort id, ItemPrefab prefab, Option backingItem, CircuitBoxConnection one, CircuitBoxConnection two) => Wires.Add(new CircuitBoxWire(this, id, backingItem, one, two, prefab)); - private bool AddComponentInternal(ushort id, ItemPrefab prefab, ItemPrefab usedResource, Vector2 pos, Action onItemSpawned) + private bool AddComponentInternal(ushort id, ItemPrefab prefab, ItemPrefab usedResource, Vector2 pos, Character? user, Action? onItemSpawned) { if (id is ICircuitBoxIdentifiable.NullComponentID) { @@ -373,12 +394,12 @@ namespace Barotrauma.Items.Components return false; } - SpawnItem(this, prefab, ComponentContainer, spawnedItem => + SpawnItem(prefab, user, ComponentContainer, spawnedItem => { Components.Add(new CircuitBoxComponent(id, spawnedItem, pos, this, usedResource)); - onItemSpawned(spawnedItem); + onItemSpawned?.Invoke(spawnedItem); + OnViewUpdateProjSpecific(); }); - OnViewUpdateProjSpecific(); return true; } @@ -646,7 +667,7 @@ namespace Barotrauma.Items.Components return Option.None; } - public static void SpawnItem(CircuitBox circuitBox, ItemPrefab prefab, ItemContainer? container, Action onSpawned) + public static void SpawnItem(ItemPrefab prefab, Character? user, ItemContainer? container, Action onSpawned) { if (container is null) { @@ -655,13 +676,27 @@ namespace Barotrauma.Items.Components if (IsInGame()) { - Entity.Spawner?.AddItemToSpawnQueue(prefab, container.Inventory, onSpawned: onSpawned); + Entity.Spawner?.AddItemToSpawnQueue(prefab, container.Inventory, onSpawned: it => + { + AssignWifiComponentTeam(it, user); + onSpawned(it); + }); return; } Item forceSpawnedItem = new Item(prefab, Vector2.Zero, null); container.Inventory.TryPutItem(forceSpawnedItem, null); onSpawned(forceSpawnedItem); + AssignWifiComponentTeam(forceSpawnedItem, user); + + static void AssignWifiComponentTeam(Item item, Character? user) + { + if (user == null) { return; } + foreach (WifiComponent wifiComponent in item.GetComponents()) + { + wifiComponent.TeamID = user.TeamID; + } + } } public static void RemoveItem(Item item) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/TriggerComponent.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/TriggerComponent.cs index 85e995e5f..d6af54c78 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/TriggerComponent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/TriggerComponent.cs @@ -97,7 +97,8 @@ namespace Barotrauma.Items.Components string triggeredByAttribute = element.GetAttributeString("triggeredby", "Character"); if (!Enum.TryParse(triggeredByAttribute, out triggeredBy)) { - DebugConsole.ThrowError($"Error in ForceComponent config: \"{triggeredByAttribute}\" is not a valid triggerer type."); + DebugConsole.ThrowError($"Error in ForceComponent config: \"{triggeredByAttribute}\" is not a valid triggerer type.", + contentPackage: element.ContentPackage); } triggerOnce = element.GetAttributeBool("triggeronce", false); string parentDebugName = $"TriggerComponent in {item.Name}"; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Wearable.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Wearable.cs index c94cc73e0..41b58165d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Wearable.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Wearable.cs @@ -362,7 +362,8 @@ namespace Barotrauma.Items.Components case "sprite": if (subElement.GetAttribute("texture") == null) { - DebugConsole.ThrowError("Item \"" + item.Name + "\" doesn't have a texture specified!"); + DebugConsole.ThrowError("Item \"" + item.Name + "\" doesn't have a texture specified!", + contentPackage: element.ContentPackage); return; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs index af251d6ce..4c4c9c0a8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs @@ -98,11 +98,11 @@ namespace Barotrauma } /// Defaults to if null - public int HowManyCanBePut(ItemPrefab itemPrefab, int? maxStackSize = null, float? condition = null) + public int HowManyCanBePut(ItemPrefab itemPrefab, int? maxStackSize = null, float? condition = null, bool ignoreItemsInSlot = false) { if (itemPrefab == null) { return 0; } maxStackSize ??= itemPrefab.GetMaxStackSize(inventory); - if (items.Count > 0) + if (items.Count > 0 && !ignoreItemsInSlot) { if (condition.HasValue) { @@ -517,10 +517,10 @@ namespace Barotrauma return count; } - public virtual int HowManyCanBePut(ItemPrefab itemPrefab, int i, float? condition) + public virtual int HowManyCanBePut(ItemPrefab itemPrefab, int i, float? condition, bool ignoreItemsInSlot = false) { if (i < 0 || i >= slots.Length) { return 0; } - return slots[i].HowManyCanBePut(itemPrefab, condition: condition); + return slots[i].HowManyCanBePut(itemPrefab, condition: condition, ignoreItemsInSlot: ignoreItemsInSlot); } /// diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs index 81d0cf2f0..edb9917cc 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs @@ -240,7 +240,21 @@ namespace Barotrauma } } - public Item RootContainer { get; private set; } + private Item rootContainer; + public Item RootContainer + { + get { return rootContainer; } + private set + { + if (value == this) + { + DebugConsole.ThrowError($"Attempted to set the item \"{Prefab.Identifier}\" as it's own root container!\n{Environment.StackTrace.CleanupStackTrace()}"); + rootContainer = null; + return; + } + rootContainer = value; + } + } private bool inWaterProofContainer; @@ -443,7 +457,7 @@ namespace Barotrauma set { if (scale == value) { return; } - scale = MathHelper.Clamp(value, 0.01f, 10.0f); + scale = MathHelper.Clamp(value, Prefab.MinScale, Prefab.MaxScale); float relativeScale = scale / base.Prefab.Scale; @@ -862,7 +876,25 @@ namespace Barotrauma { get { - return ownInventory?.AllItems ?? Enumerable.Empty(); + if (OwnInventories.Length < 2) + { + if (OwnInventory == null) { yield break; } + + foreach (var item in OwnInventory.AllItems) + { + yield return item; + } + } + else + { + foreach (var inventory in OwnInventories) + { + foreach (var item in inventory.AllItems) + { + yield return item; + } + } + } } } @@ -871,6 +903,8 @@ namespace Barotrauma get { return ownInventory; } } + public readonly ImmutableArray OwnInventories = ImmutableArray.Empty; + [Editable, Serialize(false, IsPropertySaveable.Yes, description: "Enable if you want to display the item HUD side by side with another item's HUD, when linked together. " + "Disclaimer: It's possible or even likely that the views block each other, if they were not designed to be viewed together!")] @@ -1057,7 +1091,8 @@ namespace Barotrauma { if (!Physics.TryParseCollisionCategory(collisionCategoryStr, out Category cat)) { - DebugConsole.ThrowError("Invalid collision category in item \"" + Name + "\" (" + collisionCategoryStr + ")"); + DebugConsole.ThrowError("Invalid collision category in item \"" + Name + "\" (" + collisionCategoryStr + ")", + contentPackage: element.ContentPackage); } else { @@ -1192,6 +1227,8 @@ namespace Barotrauma ownInventory = itemContainer.Inventory; } + OwnInventories = GetComponents().Select(ic => ic.Inventory).ToImmutableArray(); + qualityComponent = GetComponent(); IsLadder = GetComponent() != null; @@ -1210,7 +1247,8 @@ namespace Barotrauma var holdables = components.Where(c => c is Holdable); if (holdables.Count() > 1) { - DebugConsole.AddWarning($"Item {Prefab.Identifier} has multiple {nameof(Holdable)} components ({string.Join(", ", holdables.Select(h => h.GetType().Name))})."); + DebugConsole.AddWarning($"Item {Prefab.Identifier} has multiple {nameof(Holdable)} components ({string.Join(", ", holdables.Select(h => h.GetType().Name))}).", + Prefab.ContentPackage); } InsertToList(); @@ -1654,6 +1692,12 @@ namespace Barotrauma while (rootContainer.Container != null) { rootContainer = rootContainer.Container; + if (rootContainer == this) + { + DebugConsole.ThrowError($"Invalid container hierarchy: \"{Prefab.Identifier}\" was contained inside itself!\n{Environment.StackTrace.CleanupStackTrace()}"); + rootContainer = null; + break; + } inWaterProofContainer |= rootContainer.WaterProof; } newRootContainer = rootContainer; @@ -1916,7 +1960,7 @@ namespace Barotrauma } - public AttackResult AddDamage(Character attacker, Vector2 worldPosition, Attack attack, float deltaTime, bool playSound = true) + public AttackResult AddDamage(Character attacker, Vector2 worldPosition, Attack attack, Vector2 impulseDirection, float deltaTime,bool playSound = true) { if (Indestructible || InvulnerableToDamage) { return new AttackResult(); } @@ -2533,8 +2577,7 @@ namespace Barotrauma foreach (Connection c in connectionPanel.Connections) { if (connectionFilter != null && !connectionFilter.Invoke(c)) { continue; } - var recipients = c.Recipients; - foreach (Connection recipient in recipients) + foreach (Connection recipient in c.Recipients) { var component = recipient.Item.GetComponent(); if (component != null && !connectedComponents.Contains(component)) @@ -2587,9 +2630,20 @@ namespace Barotrauma private void GetConnectedComponentsRecursive(Connection c, HashSet alreadySearched, List connectedComponents, bool ignoreInactiveRelays, bool allowTraversingBackwards = true) where T : ItemComponent { alreadySearched.Add(c); - - var recipients = c.Recipients; - foreach (Connection recipient in recipients) + static IEnumerable GetRecipients(Connection c) + { + foreach (Connection recipient in c.Recipients) + { + yield return recipient; + } + //check circuit box inputs/outputs this connection is connected to + foreach (var circuitBoxConnection in c.CircuitBoxConnections) + { + yield return circuitBoxConnection.Connection; + } + } + + foreach (Connection recipient in GetRecipients(c)) { if (alreadySearched.Contains(recipient)) { continue; } var component = recipient.Item.GetComponent(); @@ -2598,23 +2652,53 @@ namespace Barotrauma connectedComponents.Add(component); } - //connected to a wifi component -> see which other wifi components it can communicate with - var wifiComponent = recipient.Item.GetComponent(); - if (wifiComponent != null && wifiComponent.CanTransmit()) + var circuitBox = recipient.Item.GetComponent(); + if (circuitBox != null) { - foreach (var wifiReceiver in wifiComponent.GetTransmittersInRange()) + //if this is a circuit box, check what the connection is connected to inside the box + var potentialCbConnection = circuitBox.FindInputOutputConnection(recipient); + if (potentialCbConnection.TryUnwrap(out var cbConnection)) { - var receiverConnections = wifiReceiver.Item.Connections; - if (receiverConnections == null) { continue; } - foreach (Connection wifiOutput in receiverConnections) + if (cbConnection is CircuitBoxInputConnection inputConnection) { - if ((wifiOutput.IsOutput == recipient.IsOutput) || alreadySearched.Contains(wifiOutput)) { continue; } - GetConnectedComponentsRecursive(wifiOutput, alreadySearched, connectedComponents, ignoreInactiveRelays, allowTraversingBackwards); + foreach (var connectedTo in inputConnection.ExternallyConnectedTo) + { + if (alreadySearched.Contains(connectedTo.Connection)) { continue; } + CheckRecipient(connectedTo.Connection); + } + } + else + { + foreach (var connectedFrom in cbConnection.ExternallyConnectedFrom) + { + if (alreadySearched.Contains(connectedFrom.Connection) || !allowTraversingBackwards) { continue; } + CheckRecipient(connectedFrom.Connection); + } } } } + CheckRecipient(recipient); - recipient.Item.GetConnectedComponentsRecursive(recipient, alreadySearched, connectedComponents, ignoreInactiveRelays, allowTraversingBackwards); + void CheckRecipient(Connection recipient) + { + //connected to a wifi component -> see which other wifi components it can communicate with + var wifiComponent = recipient.Item.GetComponent(); + if (wifiComponent != null && wifiComponent.CanTransmit()) + { + foreach (var wifiReceiver in wifiComponent.GetTransmittersInRange()) + { + var receiverConnections = wifiReceiver.Item.Connections; + if (receiverConnections == null) { continue; } + foreach (Connection wifiOutput in receiverConnections) + { + if ((wifiOutput.IsOutput == recipient.IsOutput) || alreadySearched.Contains(wifiOutput)) { continue; } + GetConnectedComponentsRecursive(wifiOutput, alreadySearched, connectedComponents, ignoreInactiveRelays, allowTraversingBackwards); + } + } + } + + recipient.Item.GetConnectedComponentsRecursive(recipient, alreadySearched, connectedComponents, ignoreInactiveRelays, allowTraversingBackwards); + } } if (ignoreInactiveRelays) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/ItemInventory.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/ItemInventory.cs index 0b8aea197..bc8f2f41f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/ItemInventory.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/ItemInventory.cs @@ -58,12 +58,12 @@ namespace Barotrauma return itemPrefab != null && slots[i].CanBePut(itemPrefab, condition, quality) && slots[i].Items.Count < container.GetMaxStackSize(i); } - public override int HowManyCanBePut(ItemPrefab itemPrefab, int i, float? condition) + public override int HowManyCanBePut(ItemPrefab itemPrefab, int i, float? condition, bool ignoreItemsInSlot = false) { if (itemPrefab == null) { return 0; } if (i < 0 || i >= slots.Length) { return 0; } if (!container.CanBeContained(itemPrefab, i)) { return 0; } - return slots[i].HowManyCanBePut(itemPrefab, maxStackSize: Math.Min(itemPrefab.GetMaxStackSize(this), container.GetMaxStackSize(i)), condition); + return slots[i].HowManyCanBePut(itemPrefab, maxStackSize: Math.Min(itemPrefab.GetMaxStackSize(this), container.GetMaxStackSize(i)), condition, ignoreItemsInSlot); } public override bool IsFull(bool takeStacksIntoAccount = false) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs index dd3abc0ad..ddedb9091 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs @@ -229,7 +229,7 @@ namespace Barotrauma /// public readonly int FabricationLimitMin, FabricationLimitMax; - public FabricationRecipe(XElement element, Identifier itemPrefab) + public FabricationRecipe(ContentXElement element, Identifier itemPrefab) { TargetItemPrefabIdentifier = itemPrefab; var displayNameIdentifier = element.GetAttributeIdentifier("displayname", ""); @@ -245,7 +245,8 @@ namespace Barotrauma OutCondition = element.GetAttributeFloat("outcondition", 1.0f); if (OutCondition > 1.0f) { - DebugConsole.AddWarning($"Error in \"{itemPrefab}\"'s fabrication recipe: out condition is above 100% ({OutCondition * 100})."); + DebugConsole.AddWarning($"Error in \"{itemPrefab}\"'s fabrication recipe: out condition is above 100% ({OutCondition * 100}).", + element.ContentPackage); } var requiredItems = new List(); RequiresRecipe = element.GetAttributeBool("requiresrecipe", false); @@ -267,9 +268,10 @@ namespace Barotrauma switch (subElement.Name.ToString().ToLowerInvariant()) { case "requiredskill": - if (subElement.Attribute("name") != null) + if (subElement.GetAttribute("name") != null) { - DebugConsole.ThrowError("Error in fabricable item " + itemPrefab + "! Use skill identifiers instead of names."); + DebugConsole.ThrowError("Error in fabricable item " + itemPrefab + "! Use skill identifiers instead of names.", + contentPackage: element.ContentPackage); continue; } @@ -283,7 +285,8 @@ namespace Barotrauma Identifier requiredItemTag = subElement.GetAttributeIdentifier("tag", Identifier.Empty); if (requiredItemIdentifier == Identifier.Empty && requiredItemTag == Identifier.Empty) { - DebugConsole.ThrowError("Error in fabricable item " + itemPrefab + "! One of the required items has no identifier or tag."); + DebugConsole.ThrowError("Error in fabricable item " + itemPrefab + "! One of the required items has no identifier or tag.", + contentPackage: element.ContentPackage); continue; } @@ -819,7 +822,7 @@ namespace Barotrauma [Serialize(null, IsPropertySaveable.No)] public string EquipConfirmationText { get; set; } - [Serialize(true, IsPropertySaveable.No, description: "Can the item be rotated in the submarine editor.")] + [Serialize(true, IsPropertySaveable.No, description: "Can the item be rotated in the submarine editor?")] public bool AllowRotatingInEditor { get; set; } [Serialize(false, IsPropertySaveable.No)] @@ -830,7 +833,13 @@ namespace Barotrauma [Serialize(true, IsPropertySaveable.No)] public bool CanFlipY { get; private set; } - + + [Serialize(0.1f, IsPropertySaveable.No)] + public float MinScale { get; private set; } + + [Serialize(10.0f, IsPropertySaveable.No)] + public float MaxScale { get; private set; } + [Serialize(false, IsPropertySaveable.No)] public bool IsDangerous { get; private set; } @@ -843,7 +852,7 @@ namespace Barotrauma } private int maxStackSizeCharacterInventory; - [Serialize(-1, IsPropertySaveable.No)] + [Serialize(-1, IsPropertySaveable.No, description: "Maximum stack size when the item is in a character inventory.")] public int MaxStackSizeCharacterInventory { get { return maxStackSizeCharacterInventory; } @@ -851,7 +860,9 @@ namespace Barotrauma } private int maxStackSizeHoldableOrWearableInventory; - [Serialize(-1, IsPropertySaveable.No)] + [Serialize(-1, IsPropertySaveable.No, description: + "Maximum stack size when the item is inside a holdable or wearable item. "+ + "If not set, defaults to MaxStackSizeCharacterInventory.")] public int MaxStackSizeHoldableOrWearableInventory { get { return maxStackSizeHoldableOrWearableInventory; } @@ -864,15 +875,20 @@ namespace Barotrauma { return maxStackSizeCharacterInventory; } - else if (maxStackSizeHoldableOrWearableInventory > 0 && - inventory?.Owner is Item item && (item.GetComponent() != null || item.GetComponent() != null)) + else if (inventory?.Owner is Item item && + (item.GetComponent() is { Attachable: false } || item.GetComponent() != null)) { - return maxStackSizeHoldableOrWearableInventory; - } - else - { - return maxStackSize; + if (maxStackSizeHoldableOrWearableInventory > 0) + { + return maxStackSizeHoldableOrWearableInventory; + } + else if (maxStackSizeCharacterInventory > 0) + { + //if maxStackSizeHoldableOrWearableInventory is not set, it defaults to maxStackSizeCharacterInventory + return maxStackSizeCharacterInventory; + } } + return maxStackSize; } [Serialize(false, IsPropertySaveable.No)] @@ -880,7 +896,7 @@ namespace Barotrauma public ImmutableHashSet AllowDroppingOnSwapWith { get; private set; } - [Serialize(false, IsPropertySaveable.No)] + [Serialize(false, IsPropertySaveable.No, "If enabled, the item is not transferred when the player transfers items between subs.")] public bool DontTransferBetweenSubs { get; private set; } [Serialize(true, IsPropertySaveable.No)] @@ -1009,7 +1025,8 @@ namespace Barotrauma if (ConfigElement.GetAttribute("cargocontainername") != null) { - DebugConsole.ThrowError($"Error in item prefab \"{ToString()}\" - cargo container should be configured using the item's identifier, not the name."); + DebugConsole.ThrowError($"Error in item prefab \"{ToString()}\" - cargo container should be configured using the item's identifier, not the name.", + contentPackage: ConfigElement.ContentPackage); } SerializableProperty.DeserializeProperties(this, ConfigElement); @@ -1032,6 +1049,7 @@ namespace Barotrauma var levelCommonness = new Dictionary(); var levelQuantity = new Dictionary(); + List loadedRecipes = new List(); foreach (ContentXElement subElement in ConfigElement.Elements()) { switch (subElement.Name.ToString().ToLowerInvariant()) @@ -1046,7 +1064,8 @@ namespace Barotrauma if (subElement.GetAttribute("sourcerect") == null && subElement.GetAttribute("sheetindex") == null) { - DebugConsole.ThrowError($"Warning - sprite sourcerect not configured for item \"{ToString()}\"!"); + DebugConsole.ThrowError($"Warning - sprite sourcerect not configured for item \"{ToString()}\"!", + contentPackage: ConfigElement.ContentPackage); } Size = Sprite.size; @@ -1064,7 +1083,8 @@ namespace Barotrauma if (priceInfo.StoreIdentifier.IsEmpty) { continue; } if (storePrices.ContainsKey(priceInfo.StoreIdentifier)) { - DebugConsole.AddWarning($"Error in item prefab \"{this}\": price for the store \"{priceInfo.StoreIdentifier}\" defined more than once."); + DebugConsole.AddWarning($"Error in item prefab \"{this}\": price for the store \"{priceInfo.StoreIdentifier}\" defined more than once.", + ContentPackage); storePrices[priceInfo.StoreIdentifier] = priceInfo; } else @@ -1077,7 +1097,8 @@ namespace Barotrauma { if (storePrices.ContainsKey(locationType)) { - DebugConsole.AddWarning($"Error in item prefab \"{this}\": price for the location type \"{locationType}\" defined more than once."); + DebugConsole.AddWarning($"Error in item prefab \"{this}\": price for the location type \"{locationType}\" defined more than once.", + ContentPackage); storePrices[locationType] = new PriceInfo(subElement); } else @@ -1095,13 +1116,15 @@ namespace Barotrauma { if (itemElement.Attribute("name") != null) { - DebugConsole.ThrowError($"Error in item config \"{ToString()}\" - use item identifiers instead of names to configure the deconstruct items."); + DebugConsole.ThrowError($"Error in item config \"{ToString()}\" - use item identifiers instead of names to configure the deconstruct items.", + contentPackage: ConfigElement.ContentPackage); continue; } var deconstructItem = new DeconstructItem(itemElement, Identifier); if (deconstructItem.ItemIdentifier.IsEmpty) { - DebugConsole.ThrowError($"Error in item config \"{ToString()}\" - deconstruction output contains an item with no identifier."); + DebugConsole.ThrowError($"Error in item config \"{ToString()}\" - deconstruction output contains an item with no identifier.", + contentPackage: ConfigElement.ContentPackage); continue; } deconstructItems.Add(deconstructItem); @@ -1114,16 +1137,21 @@ namespace Barotrauma var newRecipe = new FabricationRecipe(subElement, Identifier); if (fabricationRecipes.TryGetValue(newRecipe.RecipeHash, out var prevRecipe)) { + //the errors below may be caused by a mod overriding a base item instead of this one, log the package of the base item in that case + var packageToLog = GetParentModPackageOrThisPackage(); + + int prevRecipeIndex = loadedRecipes.IndexOf(prevRecipe); DebugConsole.ThrowError( $"Error in item prefab \"{ToString()}\": " + - $"{prevRecipe.TargetItemPrefabIdentifier} has the same hash as {newRecipe.TargetItemPrefabIdentifier}. " + - $"This will cause issues with fabrication." - ); + $"Fabrication recipe #{loadedRecipes.Count + 1} has the same hash as recipe #{prevRecipeIndex + 1}. This is most likely caused by identical, duplicate recipes. " + + $"This will cause issues with fabrication.", + contentPackage: packageToLog); } else { fabricationRecipes.Add(newRecipe.RecipeHash, newRecipe); } + loadedRecipes.Add(newRecipe); break; case "preferredcontainer": var preferredContainer = new PreferredContainer(subElement); @@ -1132,7 +1160,8 @@ namespace Barotrauma //it's ok for variants to clear the primary and secondary containers to disable the PreferredContainer element if (variantOf == null) { - DebugConsole.ThrowError($"Error in item prefab \"{ToString()}\": preferred container has no preferences defined ({subElement})."); + DebugConsole.ThrowError($"Error in item prefab \"{ToString()}\": preferred container has no preferences defined ({subElement}).", + contentPackage: ConfigElement.ContentPackage); } } else @@ -1182,7 +1211,8 @@ namespace Barotrauma case "suitabletreatment": if (subElement.GetAttribute("name") != null) { - DebugConsole.ThrowError($"Error in item prefab \"{ToString()}\" - suitable treatments should be defined using item identifiers, not item names."); + DebugConsole.ThrowError($"Error in item prefab \"{ToString()}\" - suitable treatments should be defined using item identifiers, not item names.", + contentPackage: ConfigElement.ContentPackage); } Identifier treatmentIdentifier = subElement.GetAttributeIdentifier("identifier", subElement.GetAttributeIdentifier("type", Identifier.Empty)); float suitability = subElement.GetAttributeFloat("suitability", 0.0f); @@ -1191,6 +1221,8 @@ namespace Barotrauma } } + Size = ConfigElement.GetAttributeVector2(nameof(Size), Size); + #if CLIENT ParseSubElementsClient(ConfigElement, variantOf); #endif @@ -1221,7 +1253,7 @@ namespace Barotrauma if (Sprite == null) { - DebugConsole.ThrowError($"Item \"{ToString()}\" has no sprite!"); + DebugConsole.ThrowError($"Item \"{ToString()}\" has no sprite!", contentPackage: ConfigElement.ContentPackage); #if SERVER this.sprite = new Sprite("", Vector2.Zero); this.sprite.SourceRect = new Rectangle(0, 0, 32, 32); @@ -1238,7 +1270,8 @@ namespace Barotrauma if (Identifier == Identifier.Empty) { DebugConsole.ThrowError( - $"Item prefab \"{ToString()}\" has no identifier. All item prefabs have a unique identifier string that's used to differentiate between items during saving and loading."); + $"Item prefab \"{ToString()}\" has no identifier. All item prefabs have a unique identifier string that's used to differentiate between items during saving and loading.", + contentPackage: ConfigElement.ContentPackage); } #if DEBUG @@ -1246,7 +1279,8 @@ namespace Barotrauma { if (!string.IsNullOrEmpty(OriginalName)) { - DebugConsole.AddWarning($"Item \"{(Identifier == Identifier.Empty ? Name : Identifier.Value)}\" has a hard-coded name, and won't be localized to other languages."); + DebugConsole.AddWarning($"Item \"{(Identifier == Identifier.Empty ? Name : Identifier.Value)}\" has a hard-coded name, and won't be localized to other languages.", + ContentPackage); } } #endif @@ -1306,9 +1340,9 @@ namespace Barotrauma { string message = $"Tried to get price info for \"{Identifier}\" with a null store parameter!\n{Environment.StackTrace.CleanupStackTrace()}"; #if DEBUG - DebugConsole.LogError(message); + DebugConsole.LogError(message, contentPackage: ContentPackage); #else - DebugConsole.AddWarning(message); + DebugConsole.AddWarning(message, ContentPackage); GameAnalyticsManager.AddErrorEventOnce("ItemPrefab.GetPriceInfo:StoreParameterNull", GameAnalyticsManager.ErrorSeverity.Error, message); #endif return null; @@ -1515,6 +1549,9 @@ namespace Barotrauma void CheckXML(XElement originalElement, XElement variantElement, XElement result) { + //if either the parent or the variant are non-vanilla, assume the error is coming from that package + var packageToLog = parent.ContentPackage != GameMain.VanillaContent ? parent.ContentPackage : ContentPackage; + if (result == null) { return; } if (result.Name.ToIdentifier() == "RequiredItem" && result.Parent?.Name.ToIdentifier() == "Fabricate") @@ -1528,7 +1565,8 @@ namespace Barotrauma { DebugConsole.AddWarning($"Potential error in item variant \"{Identifier}\": " + $"the item inherits the fabrication requirement of x{originalAmount} \"{originalIdentifier}\" from the base item \"{parent.Identifier}\". " + - $"If this is not intentional, you can use empty elements in the item variant to remove any excess inherited fabrication requirements."); + $"If this is not intentional, you can use empty elements in the item variant to remove any excess inherited fabrication requirements.", + packageToLog); } return; } @@ -1539,7 +1577,8 @@ namespace Barotrauma DebugConsole.AddWarning($"Potential error in item variant \"{Identifier}\": " + $"the base item \"{parent.Identifier}\" requires x{originalAmount} \"{originalIdentifier}\" to fabricate. " + $"The variant only overrides the required item, not the amount, resulting in a requirement of x{originalAmount} \"{resultIdentifier}\". "+ - "Specify the amount in the variant to fix this."); + "Specify the amount in the variant to fix this.", + packageToLog); } } if (originalElement?.Name.ToIdentifier() == "Deconstruct" && @@ -1549,18 +1588,35 @@ namespace Barotrauma variantElement.Elements().Any(e => e.Name.ToIdentifier() == "RequiredItem")) { DebugConsole.AddWarning($"Potential error in item variant \"{Identifier}\": " + - $"the item defines deconstruction recipes using 'RequiredItem' instead of 'Item'. Overriding the base recipe may not work correctly."); + $"the item defines deconstruction recipes using 'RequiredItem' instead of 'Item'. Overriding the base recipe may not work correctly.", + packageToLog); } if (variantElement.Elements().Any(e => e.Name.ToIdentifier() == "Item") && originalElement.Elements().Any(e => e.Name.ToIdentifier() == "RequiredItem")) { DebugConsole.AddWarning($"Potential error in item \"{parent.Identifier}\": " + - $"the item defines deconstruction recipes using 'RequiredItem' instead of 'Item'. The item variant \"{Identifier}\" may not override the base recipe correctly."); + $"the item defines deconstruction recipes using 'RequiredItem' instead of 'Item'. The item variant \"{Identifier}\" may not override the base recipe correctly.", + packageToLog); } } } } + /// + /// If the base prefab this one is a variant of is defined in a non-vanilla package, returns that non-vanilla package. + /// Otherwise returns the package of this prefab. Can be useful for logging errors that may have been caused by a mod overriding + /// the base item. + /// + public ContentPackage GetParentModPackageOrThisPackage() + { + if (ParentPrefab != null && + ParentPrefab.ContentPackage != ContentPackageManager.VanillaCorePackage) + { + return ParentPrefab.ContentPackage; + } + return ContentPackage; + } + public override string ToString() { return $"{Name} (identifier: {Identifier})"; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/RelatedItem.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/RelatedItem.cs index 4e65d9b65..f868f7d3b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/RelatedItem.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/RelatedItem.cs @@ -222,7 +222,7 @@ namespace Barotrauma if (element.GetAttribute("name") != null) { //backwards compatibility + a console warning - DebugConsole.ThrowError($"Error in RelatedItem config (" + (string.IsNullOrEmpty(parentDebugName) ? element.ToString() : parentDebugName) + ") - use item tags or identifiers instead of names."); + DebugConsole.ThrowError($"Error in RelatedItem config (" + (string.IsNullOrEmpty(parentDebugName) ? element.ToString() : parentDebugName) + ") - use item tags or identifiers instead of names.", contentPackage: element.ContentPackage); Identifier[] itemNames = element.GetAttributeIdentifierArray("name", Array.Empty()); //attempt to convert to identifiers and tags List convertedIdentifiers = new List(); @@ -299,7 +299,7 @@ namespace Barotrauma } if (!Enum.TryParse(typeStr, true, out type)) { - DebugConsole.ThrowError("Error in RelatedItem config (" + parentDebugName + ") - \"" + typeStr + "\" is not a valid relation type."); + DebugConsole.ThrowError("Error in RelatedItem config (" + parentDebugName + ") - \"" + typeStr + "\" is not a valid relation type.", contentPackage: element.ContentPackage); type = RelationType.Invalid; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/DummyFireSource.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/DummyFireSource.cs index a50646860..80428221a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/DummyFireSource.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/DummyFireSource.cs @@ -9,7 +9,8 @@ namespace Barotrauma public bool CausedByPsychosis; - public DummyFireSource(Vector2 maxSize, Vector2 worldPosition, Hull spawningHull = null, bool isNetworkMessage = false) : base(worldPosition, spawningHull, isNetworkMessage) + public DummyFireSource(Vector2 maxSize, Vector2 worldPosition, Hull spawningHull = null, bool isNetworkMessage = false) : + base(worldPosition, spawningHull, sourceCharacter: null, isNetworkMessage: isNetworkMessage) { this.maxSize = maxSize; DamagesItems = false; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Explosion.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Explosion.cs index db32a7ec5..3fab10249 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Explosion.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Explosion.cs @@ -130,10 +130,17 @@ namespace Barotrauma /// /// When set to true, the explosion don't deal less damage when the target is behind a solid object. /// - public bool IgnoreCover - { - get; set; - } + public bool IgnoreCover { get; set; } + + /// + /// Does the damage from the explosion decrease with distance from the origin of the explosion? + /// + public bool DistanceFalloff { get; set; } = true; + + /// + /// Structures that don't count as "cover" that reduces damage from the explosion. Only relevant if IgnoreCover is set to false. + /// + public IEnumerable IgnoredCover; /// /// How long the light source created by the explosion lasts. @@ -311,12 +318,15 @@ namespace Barotrauma if (!MathUtils.NearlyEqual(Attack.GetStructureDamage(1.0f), 0.0f) || !MathUtils.NearlyEqual(Attack.GetLevelWallDamage(1.0f), 0.0f)) { - RangedStructureDamage(worldPosition, displayRange, Attack.GetStructureDamage(1.0f), Attack.GetLevelWallDamage(1.0f), attacker, IgnoredSubmarines, Attack.EmitStructureDamageParticles); + RangedStructureDamage(worldPosition, displayRange, Attack.GetStructureDamage(1.0f), Attack.GetLevelWallDamage(1.0f), attacker, + IgnoredSubmarines, + Attack.EmitStructureDamageParticles, + DistanceFalloff); } if (BallastFloraDamage > 0.0f) { - RangedBallastFloraDamage(worldPosition, displayRange, BallastFloraDamage, attacker); + RangedBallastFloraDamage(worldPosition, displayRange, BallastFloraDamage, attacker, DistanceFalloff); } if (EmpStrength > 0.0f) @@ -326,7 +336,7 @@ namespace Barotrauma { float distSqr = Vector2.DistanceSquared(item.WorldPosition, worldPosition); if (distSqr > displayRangeSqr) { continue; } - float distFactor = CalculateDistanceFactor(distSqr, displayRange); + float distFactor = DistanceFalloff ? CalculateDistanceFactor(distSqr, displayRange) : 1.0f; //damage repairable power-consuming items var powered = item.GetComponent(); @@ -362,7 +372,10 @@ namespace Barotrauma float distSqr = Vector2.DistanceSquared(item.WorldPosition, worldPosition); if (distSqr > displayRangeSqr) { continue; } - float distFactor = 1.0f - (float)Math.Sqrt(distSqr) / displayRange; + float distFactor = + DistanceFalloff ? + 1.0f - (float)Math.Sqrt(distSqr) / displayRange : + 1.0f; //repair repairable items if (item.Repairables.Any()) { @@ -415,13 +428,16 @@ namespace Barotrauma if (item.Prefab.DamagedByExplosions && !item.Indestructible) { - float distFactor = 1.0f - dist / displayRange; + float distFactor = + DistanceFalloff ? + 1.0f - dist / displayRange : + 1.0f; float damageAmount = Attack.GetItemDamage(1.0f, item.Prefab.ExplosionDamageMultiplier); Vector2 explosionPos = worldPosition; if (item.Submarine != null) { explosionPos -= item.Submarine.Position; } - damageAmount *= GetObstacleDamageMultiplier(ConvertUnits.ToSimUnits(explosionPos), worldPosition, item.SimPosition); + damageAmount *= GetObstacleDamageMultiplier(ConvertUnits.ToSimUnits(explosionPos), worldPosition, item.SimPosition, IgnoredCover); item.Condition -= damageAmount * distFactor; } } @@ -482,12 +498,15 @@ namespace Barotrauma if (dist > attack.Range) { continue; } - float distFactor = 1.0f - dist / attack.Range; + float distFactor = + DistanceFalloff ? + 1.0f - dist / attack.Range : + 1.0f; //solid obstacles between the explosion and the limb reduce the effect of the explosion if (!IgnoreCover) { - distFactor *= GetObstacleDamageMultiplier(explosionPos, worldPosition, limb.SimPosition); + distFactor *= GetObstacleDamageMultiplier(explosionPos, worldPosition, limb.SimPosition, IgnoredCover); } if (distFactor > 0) { @@ -602,7 +621,8 @@ namespace Barotrauma /// /// Returns a dictionary where the keys are the structures that took damage and the values are the amount of damage taken /// - public static Dictionary RangedStructureDamage(Vector2 worldPosition, float worldRange, float damage, float levelWallDamage, Character attacker = null, IEnumerable ignoredSubmarines = null, bool emitWallDamageParticles = true) + public static Dictionary RangedStructureDamage(Vector2 worldPosition, float worldRange, float damage, float levelWallDamage, Character attacker = null, IEnumerable ignoredSubmarines = null, + bool emitWallDamageParticles = true, bool distanceFalloff = true) { float dist = 600.0f; damagedStructures.Clear(); @@ -616,7 +636,10 @@ namespace Barotrauma { for (int i = 0; i < structure.SectionCount; i++) { - float distFactor = 1.0f - (Vector2.Distance(structure.SectionPosition(i, true), worldPosition) / worldRange); + float distFactor = + distanceFalloff ? + 1.0f - (Vector2.Distance(structure.SectionPosition(i, true), worldPosition) / worldRange) : + 1.0f; if (distFactor <= 0.0f) { continue; } structure.AddDamage(i, damage * distFactor, attacker, emitParticles: emitWallDamageParticles); @@ -680,7 +703,7 @@ namespace Barotrauma return damagedStructures; } - public static void RangedBallastFloraDamage(Vector2 worldPosition, float worldRange, float damage, Character attacker = null) + public static void RangedBallastFloraDamage(Vector2 worldPosition, float worldRange, float damage, Character attacker = null, bool distanceFalloff = true) { List ballastFlorae = new List(); @@ -698,7 +721,10 @@ namespace Barotrauma float branchDist = Vector2.Distance(branchWorldPos, worldPosition); if (branchDist < worldRange) { - float distFactor = 1.0f - (branchDist / worldRange); + float distFactor = + distanceFalloff ? + 1.0f - (branchDist / worldRange) : + 1.0f; if (distFactor <= 0.0f) { return; } Vector2 explosionPos = worldPosition; @@ -715,7 +741,7 @@ namespace Barotrauma } } - private static float GetObstacleDamageMultiplier(Vector2 explosionSimPos, Vector2 explosionWorldPos, Vector2 targetSimPos) + private static float GetObstacleDamageMultiplier(Vector2 explosionSimPos, Vector2 explosionWorldPos, Vector2 targetSimPos, IEnumerable ignoredCover = null) { float damageMultiplier = 1.0f; var obstacles = Submarine.PickBodies(targetSimPos, explosionSimPos, collisionCategory: Physics.CollisionItem | Physics.CollisionItemBlocking | Physics.CollisionWall); @@ -728,6 +754,10 @@ namespace Barotrauma } else if (body.UserData is Structure structure) { + if (ignoredCover != null) + { + if (ignoredCover.Contains(structure)) { continue; } + } int sectionIndex = structure.FindSectionIndex(explosionWorldPos, world: true, clamp: true); if (structure.SectionBodyDisabled(sectionIndex)) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/FireSource.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/FireSource.cs index 6d9577b89..3fa6c8e18 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/FireSource.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/FireSource.cs @@ -91,7 +91,12 @@ namespace Barotrauma get { return hull; } } - public FireSource(Vector2 worldPosition, Hull spawningHull = null, bool isNetworkMessage = false) + /// + /// Which character caused this fire (if any)? + /// + public readonly Character SourceCharacter; + + public FireSource(Vector2 worldPosition, Hull spawningHull = null, Character sourceCharacter = null, bool isNetworkMessage = false) { hull = Hull.FindHull(worldPosition, spawningHull); if (hull == null || worldPosition.Y < hull.WorldSurface) { return; } @@ -109,6 +114,8 @@ namespace Barotrauma position -= Submarine.Position; } + SourceCharacter = sourceCharacter; + #if CLIENT lightSource = new LightSource(this.position, 50.0f, new Color(1.0f, 0.9f, 0.7f), hull?.Submarine); #endif @@ -306,8 +313,12 @@ namespace Barotrauma foreach (Limb limb in c.AnimController.Limbs) { if (limb.IsSevered) { continue; } - c.LastDamageSource = null; - c.DamageLimb(WorldPosition, limb, AfflictionPrefab.Burn.Instantiate(dmg).ToEnumerable(), 0.0f, false, 0.0f); + c.LastDamageSource = SourceCharacter; + c.DamageLimb(WorldPosition, limb, AfflictionPrefab.Burn.Instantiate(dmg).ToEnumerable(), + stun: 0.0f, + playSound: false, + attackImpulse: Vector2.Zero, + attacker: SourceCharacter); } #if CLIENT //let clients display the client-side damage immediately, otherwise they may not be able to react to the damage fast enough diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Gap.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Gap.cs index 4c93da0cc..40e897dea 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Gap.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Gap.cs @@ -109,6 +109,8 @@ namespace Barotrauma public float Size => IsHorizontal ? Rect.Height : Rect.Width; + public float PressureDistributionSpeed => Size / 100.0f * open; + private Door connectedDoor; public Door ConnectedDoor { @@ -427,11 +429,9 @@ namespace Barotrauma if (hull1.WaterVolume <= 0.0 && hull2.WaterVolume <= 0.0) { return; } - float size = IsHorizontal ? rect.Height : rect.Width; - //a variable affecting the water flow through the gap //the larger the gap is, the faster the water flows - float sizeModifier = size / 100.0f * open; + float sizeModifier = Size / 100.0f * open; //horizontal gap (such as a regular door) if (IsHorizontal) @@ -440,7 +440,7 @@ namespace Barotrauma float delta = 0.0f; //water level is above the lower boundary of the gap - if (Math.Max(hull1.Surface + hull1.WaveY[hull1.WaveY.Length - 1], hull2.Surface + subOffset.Y + hull2.WaveY[0]) > rect.Y - size) + if (Math.Max(hull1.Surface + hull1.WaveY[hull1.WaveY.Length - 1], hull2.Surface + subOffset.Y + hull2.WaveY[0]) > rect.Y - Size) { int dir = (hull1.Pressure > hull2.Pressure + subOffset.Y) ? 1 : -1; @@ -569,27 +569,35 @@ namespace Barotrauma if (open > 0.0f) { - if (hull1.WaterVolume > hull1.Volume / Hull.MaxCompress && hull2.WaterVolume > hull2.Volume / Hull.MaxCompress) + if (hull1.WaterVolume > hull1.Volume / Hull.MaxCompress && + hull2.WaterVolume > hull2.Volume / Hull.MaxCompress) { + //both hulls full -> distribute pressure float avgLethality = (hull1.LethalPressure + hull2.LethalPressure) / 2.0f; - hull1.LethalPressure = avgLethality; - hull2.LethalPressure = avgLethality; + changePressure(hull1, avgLethality, PressureDistributionSpeed, deltaTime); + changePressure(hull2, avgLethality, PressureDistributionSpeed, deltaTime); + + static void changePressure(Hull hull, float target, float speed, float deltaTime) + { + float diff = target - hull.LethalPressure; + float maxChange = Hull.PressureBuildUpSpeed * speed * deltaTime; + hull.LethalPressure += MathHelper.Clamp(diff, -maxChange, maxChange); + } } else { - hull1.LethalPressure -= Hull.PressureDropSpeed * deltaTime; - hull2.LethalPressure -= Hull.PressureDropSpeed * deltaTime; + //either hull not full -> pressure drops + hull1.LethalPressure -= Hull.PressureDropSpeed * PressureDistributionSpeed * deltaTime; + hull2.LethalPressure -= Hull.PressureDropSpeed * PressureDistributionSpeed * deltaTime; } } } void UpdateRoomToOut(float deltaTime, Hull hull1) { - float size = IsHorizontal ? rect.Height : rect.Width; - //a variable affecting the water flow through the gap //the larger the gap is, the faster the water flows - float sizeModifier = size * open * open; + float sizeModifier = Size * open * open; float delta = 500.0f * sizeModifier * deltaTime; @@ -642,7 +650,7 @@ namespace Barotrauma } else { - hull1.LethalPressure += ((Submarine != null && Submarine.AtDamageDepth) ? 100.0f : Hull.PressureBuildUpSpeed) * deltaTime; + hull1.LethalPressure += ((Submarine != null && Submarine.AtDamageDepth) ? 100.0f : Hull.PressureBuildUpSpeed) * PressureDistributionSpeed * deltaTime; } } else @@ -657,7 +665,7 @@ namespace Barotrauma } if (hull1.WaterVolume >= hull1.Volume / Hull.MaxCompress) { - hull1.LethalPressure += ((Submarine != null && Submarine.AtDamageDepth) ? 100.0f : Hull.PressureBuildUpSpeed) * deltaTime; + hull1.LethalPressure += ((Submarine != null && Submarine.AtDamageDepth) ? 100.0f : Hull.PressureBuildUpSpeed) * PressureDistributionSpeed * deltaTime; } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs index 7de5bf629..7f899899a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs @@ -1016,7 +1016,11 @@ namespace Barotrauma if (waterVolume < Volume) { - LethalPressure -= PressureDropSpeed * deltaTime; + //pressure drop speed is inversely proportionate to water percentage + //= pressure drops very fast if the hull is nowhere near full + float waterVolumeFactor = Math.Max((100.0f - WaterPercentage) / 10.0f, 1.0f); + LethalPressure -= + PressureDropSpeed * waterVolumeFactor * deltaTime; if (WaterVolume <= 0.0f) { #if CLIENT diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/IDamageable.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/IDamageable.cs index 1aeb8ddd4..bc01ab05d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/IDamageable.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/IDamageable.cs @@ -9,7 +9,7 @@ namespace Barotrauma Vector2 WorldPosition { get; } float Health { get; } - AttackResult AddDamage(Character attacker, Vector2 worldPosition, Attack attack, float deltaTime, bool playSound = true); + AttackResult AddDamage(Character attacker, Vector2 worldPosition, Attack attack, Vector2 impulseDirection, float deltaTime, bool playSound = true); public readonly struct AttackEventData diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/DestructibleLevelWall.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/DestructibleLevelWall.cs index f513fb078..4731ca9a0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/DestructibleLevelWall.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/DestructibleLevelWall.cs @@ -89,7 +89,7 @@ namespace Barotrauma partial void AddDamageProjSpecific(float damage, Vector2 worldPosition); - public AttackResult AddDamage(Character attacker, Vector2 worldPosition, Attack attack, float deltaTime, bool playSound = true) + public AttackResult AddDamage(Character attacker, Vector2 worldPosition, Attack attack, Vector2 impulseDirection, float deltaTime, bool playSound = true) { AddDamage(attack.StructureDamage, worldPosition); return new AttackResult(attack.StructureDamage); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs index 2c0a60a8a..106c1354b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs @@ -115,6 +115,20 @@ namespace Barotrauma Ruin = null; Cave = cave; } + + /// + /// Caves, ruins, outposts and similar enclosed areas + /// + /// + public bool IsEnclosedArea() + { + return + PositionType == PositionType.Cave || + PositionType == PositionType.Ruin || + PositionType == PositionType.Outpost || + PositionType == PositionType.BeaconStation || + PositionType == PositionType.AbyssCave; + } } public enum TunnelType @@ -930,6 +944,7 @@ namespace Barotrauma foreach (AbyssIsland abyssIsland in AbyssIslands) { + abyssIsland.Cells.RemoveAll(c => c.CellType == CellType.Path); cells.AddRange(abyssIsland.Cells); } @@ -1726,7 +1741,9 @@ namespace Barotrauma { bool tooClose = false; - if (cell.IsPointInsideAABB(position, margin: minDistance)) + //if the cell is very large, the position can be far away from the edges while being inside the cell + //so we need to check that here too + if (cell.IsPointInside(position)) { tooClose = true; } @@ -3257,13 +3274,13 @@ namespace Barotrauma } Vector2 position = Vector2.Zero; - int tries = 0; do { - TryGetInterestingPosition(true, spawnPosType, minDistFromSubs, out Vector2 startPos, filter); + TryGetInterestingPosition(true, spawnPosType, minDistFromSubs, out InterestingPosition potentialPos, filter); Vector2 offset = Rand.Vector(Rand.Range(0.0f, randomSpread, Rand.RandSync.ServerAndClient), Rand.RandSync.ServerAndClient); + Vector2 startPos = potentialPos.Position.ToVector2(); if (!IsPositionInsideWall(startPos + offset)) { startPos += offset; @@ -3271,14 +3288,18 @@ namespace Barotrauma Vector2 endPos = startPos - Vector2.UnitY * Size.Y; - if (Submarine.PickBody( - ConvertUnits.ToSimUnits(startPos), - ConvertUnits.ToSimUnits(endPos), - ExtraWalls.Where(w => w.Body?.BodyType == BodyType.Dynamic || w is DestructibleLevelWall).Select(w => w.Body).Union(Submarine.Loaded.Where(s => s.Info.Type == SubmarineType.Player).Select(s => s.PhysicsBody.FarseerBody)), - Physics.CollisionLevel | Physics.CollisionWall) != null) + //try to find a level wall below the position unless the position is indoors + if (!potentialPos.IsEnclosedArea()) { - position = ConvertUnits.ToDisplayUnits(Submarine.LastPickedPosition) + Vector2.Normalize(startPos - endPos) * offsetFromWall; - break; + if (Submarine.PickBody( + ConvertUnits.ToSimUnits(startPos), + ConvertUnits.ToSimUnits(endPos), + ExtraWalls.Where(w => w.Body?.BodyType == BodyType.Dynamic || w is DestructibleLevelWall).Select(w => w.Body).Union(Submarine.Loaded.Where(s => s.Info.Type == SubmarineType.Player).Select(s => s.PhysicsBody.FarseerBody)), + Physics.CollisionLevel | Physics.CollisionWall)?.UserData is VoronoiCell) + { + position = ConvertUnits.ToDisplayUnits(Submarine.LastPickedPosition) + Vector2.Normalize(startPos - endPos) * offsetFromWall; + break; + } } tries++; @@ -3293,25 +3314,25 @@ namespace Barotrauma return position; } - public bool TryGetInterestingPositionAwayFromPoint(bool useSyncedRand, PositionType positionType, float minDistFromSubs, out Vector2 position, Vector2 awayPoint, float minDistFromPoint, Func filter = null) + public bool TryGetInterestingPositionAwayFromPoint(bool useSyncedRand, PositionType positionType, float minDistFromSubs, out InterestingPosition position, Vector2 awayPoint, float minDistFromPoint, Func filter = null) { - bool success = TryGetInterestingPosition(useSyncedRand, positionType, minDistFromSubs, out Point pos, awayPoint, minDistFromPoint, filter); - position = pos.ToVector2(); + position = default; + bool success = TryGetInterestingPosition(useSyncedRand, positionType, minDistFromSubs, out position, awayPoint, minDistFromPoint, filter); return success; } - public bool TryGetInterestingPosition(bool useSyncedRand, PositionType positionType, float minDistFromSubs, out Vector2 position, Func filter = null, bool suppressWarning = false) + public bool TryGetInterestingPosition(bool useSyncedRand, PositionType positionType, float minDistFromSubs, out InterestingPosition position, Func filter = null, bool suppressWarning = false) { - bool success = TryGetInterestingPosition(useSyncedRand, positionType, minDistFromSubs, out Point pos, Vector2.Zero, minDistFromPoint: 0, filter, suppressWarning); - position = pos.ToVector2(); + position = default; + bool success = TryGetInterestingPosition(useSyncedRand, positionType, minDistFromSubs, out position, Vector2.Zero, minDistFromPoint: 0, filter, suppressWarning); return success; } - public bool TryGetInterestingPosition(bool useSyncedRand, PositionType positionType, float minDistFromSubs, out Point position, Vector2 awayPoint, float minDistFromPoint = 0f, Func filter = null, bool suppressWarning = false) + public bool TryGetInterestingPosition(bool useSyncedRand, PositionType positionType, float minDistFromSubs, out InterestingPosition position, Vector2 awayPoint, float minDistFromPoint = 0f, Func filter = null, bool suppressWarning = false) { if (!PositionsOfInterest.Any()) { - position = new Point(Size.X / 2, Size.Y / 2); + position = default; return false; } @@ -3323,7 +3344,20 @@ namespace Barotrauma if (positionType.HasFlag(PositionType.MainPath) || positionType.HasFlag(PositionType.SidePath) || positionType.HasFlag(PositionType.Abyss) || positionType.HasFlag(PositionType.Cave) || positionType.HasFlag(PositionType.AbyssCave)) { - suitablePositions.RemoveAll(p => IsPositionInsideWall(p.Position.ToVector2())); +#if DEBUG + for (int i = 0; i < PositionsOfInterest.Count; i++) + { + var pos = PositionsOfInterest[i]; + if (!suitablePositions.Contains(pos)) { continue; } + if (IsInvalid(pos)) + { + pos.IsValid = false; + PositionsOfInterest[i] = pos; + } + } +#endif + suitablePositions.RemoveAll(p => IsInvalid(p)); + bool IsInvalid(InterestingPosition p) => IsPositionInsideWall(p.Position.ToVector2()); } if (!suitablePositions.Any()) { @@ -3335,7 +3369,7 @@ namespace Barotrauma DebugConsole.ThrowError(errorMsg); #endif } - position = PositionsOfInterest[Rand.Int(PositionsOfInterest.Count, (useSyncedRand ? Rand.RandSync.ServerAndClient : Rand.RandSync.Unsynced))].Position; + position = PositionsOfInterest[Rand.Int(PositionsOfInterest.Count, (useSyncedRand ? Rand.RandSync.ServerAndClient : Rand.RandSync.Unsynced))]; return false; } @@ -3361,14 +3395,14 @@ namespace Barotrauma DebugConsole.ThrowError(errorMsg); #endif float maxDist = 0.0f; - position = suitablePositions.First().Position; + position = suitablePositions.First(); foreach (InterestingPosition pos in suitablePositions) { float dist = Submarine.Loaded.Sum(s => Submarine.MainSubs.Contains(s) ? Vector2.DistanceSquared(s.WorldPosition, pos.Position.ToVector2()) : 0.0f); if (dist > maxDist) { - position = pos.Position; + position = pos; maxDist = dist; } } @@ -3376,7 +3410,7 @@ namespace Barotrauma return false; } - position = farEnoughPositions[Rand.Int(farEnoughPositions.Count, useSyncedRand ? Rand.RandSync.ServerAndClient : Rand.RandSync.Unsynced)].Position; + position = farEnoughPositions[Rand.Int(farEnoughPositions.Count, useSyncedRand ? Rand.RandSync.ServerAndClient : Rand.RandSync.Unsynced)]; return true; } @@ -3933,12 +3967,28 @@ namespace Barotrauma { var totalSW = new Stopwatch(); totalSW.Start(); + var wreckFiles = ContentPackageManager.EnabledPackages.All .SelectMany(p => p.GetFiles()) .OrderBy(f => f.UintIdentifier).ToList(); + + for (int i = wreckFiles.Count - 1; i >= 0; i--) + { + var wreckFile = wreckFiles[i]; + var wreckInfos = SubmarineInfo.SavedSubmarines.Where(i => i.IsWreck); + var matchingInfo = wreckInfos.SingleOrDefault(info => info.FilePath == wreckFile.Path.Value); + Debug.Assert(matchingInfo != null); + if (matchingInfo?.WreckInfo is WreckInfo wreckInfo) + { + if (Difficulty < wreckInfo.MinLevelDifficulty || Difficulty > wreckInfo.MaxLevelDifficulty) + { + wreckFiles.RemoveAt(i); + } + } + } if (wreckFiles.None()) { - DebugConsole.ThrowError("No wreck files found in the selected content packages!"); + DebugConsole.ThrowError($"No wreck files found for the level difficulty {LevelData.Difficulty}!"); Wrecks = new List(); return; } @@ -4235,7 +4285,8 @@ namespace Barotrauma ContentFile contentFile = null; if (!string.IsNullOrEmpty(GenerationParams.ForceBeaconStation)) { - contentFile = beaconStationFiles.OrderBy(b => b.UintIdentifier).FirstOrDefault(f => f.Path == GenerationParams.ForceBeaconStation); + var contentPath = ContentPath.FromRaw(GenerationParams.ContentPackage, GenerationParams.ForceBeaconStation); + contentFile = beaconStationFiles.OrderBy(b => b.UintIdentifier).FirstOrDefault(f => f.Path == contentPath); if (contentFile == null) { DebugConsole.ThrowError($"Failed to find the beacon station \"{GenerationParams.ForceBeaconStation}\". Using a random one instead..."); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelObject.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelObject.cs index 1975dc33d..6d90bcda0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelObject.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelObject.cs @@ -160,7 +160,7 @@ namespace Barotrauma partial void InitProjSpecific(); - public AttackResult AddDamage(Character attacker, Vector2 worldPosition, Attack attack, float deltaTime, bool playSound = true) + public AttackResult AddDamage(Character attacker, Vector2 worldPosition, Attack attack, Vector2 impulseDirection, float deltaTime, bool playSound = true) { if (Health <= 0.0f) { return new AttackResult(0.0f); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelObjectManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelObjectManager.cs index 462235bed..29ca85cf5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelObjectManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelObjectManager.cs @@ -526,12 +526,13 @@ namespace Barotrauma { List spawnPosTypes = new List(4); List availableSpawnPositions = new List(); + bool requireCaveSpawnPos = spawnPosType == LevelObjectPrefab.SpawnPosType.CaveWall; foreach (var cell in cells) { foreach (var edge in cell.Edges) { if (!edge.IsSolid || edge.OutsideLevel) { continue; } - if (spawnPosType != LevelObjectPrefab.SpawnPosType.CaveWall && edge.NextToCave) { continue; } + if (requireCaveSpawnPos != edge.NextToCave) { continue; } Vector2 normal = edge.GetNormal(cell); Alignment edgeAlignment = 0; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Location.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Location.cs index 03ebcb980..cb8c2faf5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Location.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Location.cs @@ -639,7 +639,7 @@ namespace Barotrauma System.Diagnostics.Debug.Assert(Type != null, $"Could not find the location type \"{locationTypeId}\"!"); Type ??= LocationType.Prefabs.First(); - LevelData = new LevelData(element.Element("Level"), clampDifficultyToBiome: true); + LevelData = new LevelData(element.GetChildElement("Level"), clampDifficultyToBiome: true); PortraitId = ToolBox.StringToInt(Name); @@ -659,15 +659,12 @@ namespace Barotrauma if (type == null) { DebugConsole.AddWarning($"Could not find location type \"{identifier}\". Using location type \"None\" instead."); - LocationType.Prefabs.TryGet("None".ToIdentifier(), out type); - if (type == null) - { - type = LocationType.Prefabs.First(); - } + LocationType.Prefabs.TryGet("None".ToIdentifier(), out type); + type ??= LocationType.Prefabs.First(); } if (type != null) { - element.SetAttributeValue("type", type.Identifier); + element.SetAttributeValue("type", type.Identifier.ToString()); } return false; } @@ -776,11 +773,11 @@ namespace Barotrauma { if (Type.MissionIdentifiers.Any()) { - UnlockMissionByIdentifier(Type.MissionIdentifiers.GetRandom(randSync)); + UnlockMissionByIdentifier(Type.MissionIdentifiers.GetRandom(randSync), invokingContentPackage: Type.ContentPackage); } if (Type.MissionTags.Any()) { - UnlockMissionByTag(Type.MissionTags.GetRandom(randSync)); + UnlockMissionByTag(Type.MissionTags.GetRandom(randSync), invokingContentPackage: Type.ContentPackage); } } @@ -798,7 +795,7 @@ namespace Barotrauma AddMission(InstantiateMission(missionPrefab)); } - public Mission UnlockMissionByIdentifier(Identifier identifier) + public Mission UnlockMissionByIdentifier(Identifier identifier, ContentPackage invokingContentPackage = null) { if (AvailableMissions.Any(m => m.Prefab.Identifier == identifier)) { return null; } if (AvailableMissions.Any(m => !m.Prefab.AllowOtherMissionsInLevel)) { return null; } @@ -806,7 +803,8 @@ namespace Barotrauma var missionPrefab = MissionPrefab.Prefabs.Find(mp => mp.Identifier == identifier); if (missionPrefab == null) { - DebugConsole.ThrowError($"Failed to unlock a mission with the identifier \"{identifier}\": matching mission not found."); + DebugConsole.ThrowError($"Failed to unlock a mission with the identifier \"{identifier}\": matching mission not found.", + contentPackage: invokingContentPackage); } else { @@ -823,13 +821,13 @@ namespace Barotrauma return null; } - public Mission UnlockMissionByTag(Identifier tag, Random random = null) + public Mission UnlockMissionByTag(Identifier tag, Random random = null, ContentPackage invokingContentPackage = null) { if (AvailableMissions.Any(m => !m.Prefab.AllowOtherMissionsInLevel)) { return null; } var matchingMissions = MissionPrefab.Prefabs.Where(mp => mp.Tags.Contains(tag)); if (matchingMissions.None()) { - DebugConsole.ThrowError($"Failed to unlock a mission with the tag \"{tag}\": no matching missions found."); + DebugConsole.ThrowError($"Failed to unlock a mission with the tag \"{tag}\": no matching missions found.", contentPackage: invokingContentPackage); } else { @@ -841,7 +839,16 @@ namespace Barotrauma { suitableMissions = unusedMissions; } - + var filteredMissions = suitableMissions.Where(m => LevelData.Difficulty >= m.MinLevelDifficulty && LevelData.Difficulty <= m.MaxLevelDifficulty); + if (filteredMissions.None()) + { + DebugConsole.AddWarning($"No suitable mission matching the level difficulty {LevelData.Difficulty} found with the tag \"{tag}\". Ignoring the restriction.", + contentPackage: invokingContentPackage); + } + else + { + suitableMissions = filteredMissions; + } MissionPrefab missionPrefab = random != null ? ToolBox.SelectWeightedRandom(suitableMissions.OrderBy(m => m.Identifier), m => m.Commonness, random) : @@ -854,12 +861,13 @@ namespace Barotrauma return null; } AddMission(mission); - DebugConsole.NewMessage($"Unlocked a random mission by \"{tag}\".", debugOnly: true); + DebugConsole.NewMessage($"Unlocked a random mission by \"{tag}\": {mission.Prefab.Identifier} (difficulty level: {LevelData.Difficulty})", debugOnly: true); return mission; } else { - DebugConsole.AddWarning($"Failed to unlock a mission with the tag \"{tag}\": all available missions have already been unlocked."); + DebugConsole.AddWarning($"Failed to unlock a mission with the tag \"{tag}\": all available missions have already been unlocked.", + contentPackage: invokingContentPackage); } } @@ -988,11 +996,11 @@ namespace Barotrauma { if (addInitialMissionsForType.MissionIdentifiers.Any()) { - UnlockMissionByIdentifier(addInitialMissionsForType.MissionIdentifiers.GetRandomUnsynced()); + UnlockMissionByIdentifier(addInitialMissionsForType.MissionIdentifiers.GetRandomUnsynced(), invokingContentPackage: Type.ContentPackage); } if (addInitialMissionsForType.MissionTags.Any()) { - UnlockMissionByTag(addInitialMissionsForType.MissionTags.GetRandomUnsynced()); + UnlockMissionByTag(addInitialMissionsForType.MissionTags.GetRandomUnsynced(), invokingContentPackage: Type.ContentPackage); } addInitialMissionsForType = null; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/LocationTypeChange.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/LocationTypeChange.cs index 8d9e03673..94377cf34 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/LocationTypeChange.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/LocationTypeChange.cs @@ -55,7 +55,7 @@ namespace Barotrauma /// public readonly bool RequireHuntingGrounds; - public Requirement(XElement element, LocationTypeChange change) + public Requirement(ContentXElement element, LocationTypeChange change) { RequiredLocations = element.GetAttributeIdentifierArray("requiredlocations", element.GetAttributeIdentifierArray("requiredadjacentlocations", Array.Empty())).ToImmutableArray(); RequiredProximity = Math.Max(element.GetAttributeInt("requiredproximity", 1), 0); @@ -80,13 +80,15 @@ namespace Barotrauma { DebugConsole.AddWarning( $"Invalid location type change in location type \"{change.CurrentType}\". " + - "Probability is configured to increase when near some other type of location, but the RequiredLocations attribute is not set."); + "Probability is configured to increase when near some other type of location, but the RequiredLocations attribute is not set.", + element.ContentPackage); } if (Probability >= 1.0f) { DebugConsole.AddWarning( $"Invalid location type change in location type \"{change.CurrentType}\". " + - "Probability is configured to increase when near some other type of location, but the base probability is already 100%"); + "Probability is configured to increase when near some other type of location, but the base probability is already 100%", + element.ContentPackage); } } } @@ -173,7 +175,7 @@ namespace Barotrauma public readonly Point RequiredDurationRange; - public LocationTypeChange(Identifier currentType, XElement element, bool requireChangeMessages, float defaultProbability = 0.0f) + public LocationTypeChange(Identifier currentType, ContentXElement element, bool requireChangeMessages, float defaultProbability = 0.0f) { CurrentType = currentType; ChangeToType = element.GetAttributeIdentifier("type", element.GetAttributeIdentifier("to", "")); @@ -190,13 +192,13 @@ namespace Barotrauma CooldownAfterChange = Math.Max(element.GetAttributeInt("cooldownafterchange", 0), 0); //backwards compatibility - if (element.Attribute("requiredlocations") != null) + if (element.GetAttribute("requiredlocations") != null) { Requirements.Add(new Requirement(element, this)); } //backwards compatibility - if (element.Attribute("requiredduration") != null) + if (element.GetAttribute("requiredduration") != null) { RequiredDurationRange = new Point(element.GetAttributeInt("requiredduration", 0)); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/BeaconStationInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/ExtraSubmarineInfo.cs similarity index 54% rename from Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/BeaconStationInfo.cs rename to Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/ExtraSubmarineInfo.cs index feec0ea3d..c7b6155c7 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/BeaconStationInfo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/ExtraSubmarineInfo.cs @@ -3,13 +3,11 @@ using System.Xml.Linq; namespace Barotrauma { - class BeaconStationInfo : ISerializableEntity + abstract class ExtraSubmarineInfo : ISerializableEntity { - [Serialize(true, IsPropertySaveable.Yes), Editable] - public bool AllowDamagedWalls { get; set; } + public string Name { get; protected set; } - [Serialize(true, IsPropertySaveable.Yes), Editable] - public bool AllowDisconnectedWires { get; set; } + public Dictionary SerializableProperties { get; protected set; } [Serialize(0.0f, IsPropertySaveable.Yes), Editable] public float MinLevelDifficulty { get; set; } @@ -17,26 +15,19 @@ namespace Barotrauma [Serialize(100.0f, IsPropertySaveable.Yes), Editable] public float MaxLevelDifficulty { get; set; } - [Serialize(Level.PlacementType.Bottom, IsPropertySaveable.Yes), Editable] - public Level.PlacementType Placement { get; set; } - - public string Name { get; private set; } - - public Dictionary SerializableProperties { get; private set; } - - public BeaconStationInfo(SubmarineInfo submarineInfo, XElement element) + public ExtraSubmarineInfo(SubmarineInfo submarineInfo, XElement element) { - Name = $"BeaconStationInfo ({submarineInfo.Name})"; + Name = $"{nameof(ExtraSubmarineInfo)} ({submarineInfo.Name})"; SerializableProperties = SerializableProperty.DeserializeProperties(this, element); } - public BeaconStationInfo(SubmarineInfo submarineInfo) + public ExtraSubmarineInfo(SubmarineInfo submarineInfo) { - Name = $"BeaconStationInfo ({submarineInfo.Name})"; + Name = $"{nameof(ExtraSubmarineInfo)} ({submarineInfo.Name})"; SerializableProperties = SerializableProperty.DeserializeProperties(this); } - public BeaconStationInfo(BeaconStationInfo original) + public ExtraSubmarineInfo(ExtraSubmarineInfo original) { Name = original.Name; SerializableProperties = new Dictionary(); @@ -55,4 +46,43 @@ namespace Barotrauma SerializableProperty.SerializeProperties(this, element); } } + + class BeaconStationInfo : ExtraSubmarineInfo + { + [Serialize(true, IsPropertySaveable.Yes), Editable] + public bool AllowDamagedWalls { get; set; } + + [Serialize(true, IsPropertySaveable.Yes), Editable] + public bool AllowDisconnectedWires { get; set; } + + [Serialize(Level.PlacementType.Bottom, IsPropertySaveable.Yes), Editable] + public Level.PlacementType Placement { get; set; } + + public BeaconStationInfo(SubmarineInfo submarineInfo, XElement element) : base(submarineInfo, element) + { + Name = $"{nameof(BeaconStationInfo)} ({submarineInfo.Name})"; + } + + public BeaconStationInfo(SubmarineInfo submarineInfo) : base(submarineInfo) + { + Name = $"{nameof(BeaconStationInfo)} ({submarineInfo.Name})"; + } + + public BeaconStationInfo(BeaconStationInfo original) : base(original) { } + } + + class WreckInfo : ExtraSubmarineInfo + { + public WreckInfo(SubmarineInfo submarineInfo, XElement element) : base(submarineInfo, element) + { + Name = $"{nameof(WreckInfo)} ({submarineInfo.Name})"; + } + + public WreckInfo(SubmarineInfo submarineInfo) : base(submarineInfo) + { + Name = $"{nameof(WreckInfo)} ({submarineInfo.Name})"; + } + + public WreckInfo(WreckInfo original) : base(original) { } + } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostGenerationParams.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostGenerationParams.cs index b55acd04e..7bb55ccd6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostGenerationParams.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostGenerationParams.cs @@ -249,7 +249,7 @@ namespace Barotrauma } else { - DebugConsole.ThrowError($"Error in outpost generation parameters \"{Identifier}\". \"{levelTypeStr}\" is not a valid level type."); + DebugConsole.ThrowError($"Error in outpost generation parameters \"{Identifier}\". \"{levelTypeStr}\" is not a valid level type.", contentPackage: element.ContentPackage); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs index a2abd562e..10e72f4ca 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs @@ -111,7 +111,7 @@ namespace Barotrauma set; } - public bool IsHorizontal { get; private set; } + public bool IsHorizontal { get; } public int SectionCount { @@ -240,6 +240,25 @@ namespace Barotrauma } } + protected float rotationRad = 0f; + [ConditionallyEditable(ConditionallyEditable.ConditionType.AllowRotating, DecimalCount = 3, ForceShowPlusMinusButtons = true, ValueStep = 0.1f), Serialize(0.0f, IsPropertySaveable.Yes)] + public float Rotation + { + get => MathHelper.ToDegrees(rotationRad); + set + { + rotationRad = MathHelper.WrapAngle(MathHelper.ToRadians(value)); + if (StairDirection != Direction.None) + { + CreateStairBodies(); + } + else if (Prefab.Body) + { + CreateSections(); + UpdateSections(); + } + } + } protected Vector2 textureScale = Vector2.One; @@ -336,9 +355,18 @@ namespace Barotrauma { get { - float rotation = MathHelper.ToRadians(Prefab.BodyRotation); - if (FlippedX) rotation = -MathHelper.Pi - rotation; - if (FlippedY) rotation = -rotation; + float rotation = MathHelper.ToRadians(Prefab.BodyRotation) + this.rotationRad; + if (IsHorizontal) + { + if (FlippedX) { rotation = -MathHelper.Pi - rotation; } + if (FlippedY) { rotation = -rotation; } + } + else + { + if (FlippedX) { rotation = -rotation; } + if (FlippedY) { rotation = -MathHelper.Pi -rotation; } + } + rotation = MathHelper.WrapAngle(rotation); return rotation; } } @@ -350,6 +378,10 @@ namespace Barotrauma get { Vector2 bodyOffset = Prefab.BodyOffset; + if (rotationRad != 0f) + { + bodyOffset = MathUtils.RotatePoint(bodyOffset, -rotationRad); + } if (FlippedX) { bodyOffset.X = -bodyOffset.X; } if (FlippedY) { bodyOffset.Y = -bodyOffset.Y; } return bodyOffset; @@ -567,9 +599,14 @@ namespace Barotrauma Body newBody = GameMain.World.CreateRectangle(bodyWidth, bodyHeight, 1.5f); + var rotationWithFlip = FlippedX ^ FlippedY ? -rotationRad : rotationRad; + newBody.BodyType = BodyType.Static; - Vector2 stairPos = new Vector2(Position.X, rect.Y - rect.Height + stairHeight / 2.0f); - newBody.Rotation = (StairDirection == Direction.Right) ? stairAngle : -stairAngle; + Vector2 stairRectHeightDiff = new Vector2(0f, stairHeight / 2.0f - rect.Height / 2.0f); + stairRectHeightDiff = MathUtils.RotatePoint(stairRectHeightDiff, -rotationWithFlip); + if (FlippedY) { stairRectHeightDiff = -stairRectHeightDiff; } + Vector2 stairPos = new Vector2(Position.X, rect.Y - rect.Height / 2.0f) + stairRectHeightDiff; + newBody.Rotation = ((StairDirection == Direction.Right) ? stairAngle : -stairAngle) - rotationWithFlip; newBody.CollisionCategories = Physics.CollisionStairs; newBody.Friction = 0.8f; newBody.UserData = this; @@ -696,17 +733,6 @@ namespace Barotrauma } } - private static Vector2[] CalculateExtremes(Rectangle sectionRect) - { - Vector2[] corners = new Vector2[4]; - corners[0] = new Vector2(sectionRect.X, sectionRect.Y - sectionRect.Height); - corners[1] = new Vector2(sectionRect.X, sectionRect.Y); - corners[2] = new Vector2(sectionRect.Right, sectionRect.Y); - corners[3] = new Vector2(sectionRect.Right, sectionRect.Y - sectionRect.Height); - - return corners; - } - /// /// Checks if there's a structure items can be attached to at the given position and returns it. /// @@ -727,8 +753,6 @@ namespace Barotrauma public override bool IsMouseOn(Vector2 position) { - if (!base.IsMouseOn(position)) { return false; } - if (StairDirection == Direction.None) { Vector2 rectSize = rect.Size.ToVector2(); @@ -745,14 +769,19 @@ namespace Barotrauma } else { + Vector2 transformedMousePos = MathUtils.RotatePointAroundTarget( + position, + WorldRect.Location.ToVector2() + WorldRect.Size.ToVector2().FlipY() * 0.5f, + BodyRotation); + if (!Submarine.RectContains(WorldRect, position)) { return false; } if (StairDirection == Direction.Left) { - return MathUtils.LineToPointDistanceSquared(new Vector2(WorldRect.X, WorldRect.Y), new Vector2(WorldRect.Right, WorldRect.Y - WorldRect.Height), position) < 1600.0f; + return MathUtils.LineToPointDistanceSquared(new Vector2(WorldRect.X, WorldRect.Y), new Vector2(WorldRect.Right, WorldRect.Y - WorldRect.Height), transformedMousePos) < 1600.0f; } else { - return MathUtils.LineToPointDistanceSquared(new Vector2(WorldRect.X, WorldRect.Y - rect.Height), new Vector2(WorldRect.Right, WorldRect.Y), position) < 1600.0f; + return MathUtils.LineToPointDistanceSquared(new Vector2(WorldRect.X, WorldRect.Y - rect.Height), new Vector2(WorldRect.Right, WorldRect.Y), transformedMousePos) < 1600.0f; } } } @@ -934,11 +963,19 @@ namespace Barotrauma for (int i = 1; i <= particleAmount; i++) { var worldRect = section.WorldRect; + var directionUnitX = MathUtils.RotatedUnitXRadians(BodyRotation); + var directionUnitY = directionUnitX.YX().FlipX(); Vector2 particlePos = new Vector2( - Rand.Range(worldRect.X, worldRect.Right + 1), - Rand.Range(worldRect.Y - worldRect.Height, worldRect.Y + 1)); + Rand.Range(0, worldRect.Width + 1), + Rand.Range(-worldRect.Height, 1)); + particlePos -= worldRect.Size.ToVector2().FlipY() * 0.5f; - var particle = GameMain.ParticleManager.CreateParticle(Prefab.DamageParticle, particlePos, Rand.Vector(Rand.Range(1.0f, 50.0f)), collisionIgnoreTimer: 1f); + var particlePosFinal = SectionPosition(sectionIndex, world: true); + particlePosFinal += particlePos.X * directionUnitX + particlePos.Y * directionUnitY; + + var particle = GameMain.ParticleManager.CreateParticle(Prefab.DamageParticle, + position: particlePosFinal, + velocity: Rand.Vector(Rand.Range(1.0f, 50.0f)), collisionIgnoreTimer: 1f); if (particle == null) break; } } @@ -960,14 +997,23 @@ namespace Barotrauma //if the sub has been flipped horizontally, the first section may be smaller than wallSectionSize //and we need to adjust the position accordingly - if (Sections[0].rect.Width < WallSectionSize) + if (IsHorizontal) { - displayPos.X += WallSectionSize - Sections[0].rect.Width; + if (Sections[0].rect.Width < WallSectionSize) + { + displayPos += DirectionUnit * (WallSectionSize - Sections[0].rect.Width); + } + } + else + { + if (Sections[0].rect.Height < WallSectionSize) + { + displayPos += DirectionUnit * (WallSectionSize - Sections[0].rect.Height); + } } - int index = IsHorizontal ? - (int)Math.Floor((displayPos.X - rect.X) / WallSectionSize) : - (int)Math.Floor((rect.Y - displayPos.Y) / WallSectionSize); + var leftmostPos = Position - DirectionUnit * (IsHorizontal ? Rect.Width : Rect.Height) * 0.5f; + int index = (int)Math.Floor(Vector2.Dot(DirectionUnit, displayPos - leftmostPos) / WallSectionSize); if (clamp) { @@ -987,6 +1033,17 @@ namespace Barotrauma return Sections[sectionIndex].damage; } + protected Vector2 DirectionUnit + { + get + { + var rotation = IsHorizontal ? -BodyRotation : -MathHelper.PiOver2 - BodyRotation; + if (IsHorizontal && FlippedX) { rotation += MathF.PI; } + if (!IsHorizontal && FlippedY) { rotation += MathF.PI; } + return MathUtils.RotatedUnitXRadians(rotation); + } + } + public Vector2 SectionPosition(int sectionIndex, bool world = false) { if (sectionIndex < 0 || sectionIndex >= Sections.Length) @@ -994,7 +1051,7 @@ namespace Barotrauma return Vector2.Zero; } - if (Prefab.BodyRotation == 0.0f) + if (MathUtils.NearlyEqual(BodyRotation, 0f)) { Vector2 sectionPos = new Vector2( Sections[sectionIndex].rect.X + Sections[sectionIndex].rect.Width / 2.0f, @@ -1017,15 +1074,10 @@ namespace Barotrauma else { diffFromCenter = ((sectionRect.Y - sectionRect.Height / 2) - (rect.Y - rect.Height / 2)) / (float)rect.Height * BodyHeight; - } - if (FlippedX) - { diffFromCenter = -diffFromCenter; } - Vector2 sectionPos = Position + new Vector2( - (float)Math.Cos(IsHorizontal ? -BodyRotation : MathHelper.PiOver2 - BodyRotation), - (float)Math.Sin(IsHorizontal ? -BodyRotation : MathHelper.PiOver2 - BodyRotation)) * diffFromCenter; + Vector2 sectionPos = Position + DirectionUnit * diffFromCenter; if (world && Submarine != null) { @@ -1035,13 +1087,22 @@ namespace Barotrauma } } - public AttackResult AddDamage(Character attacker, Vector2 worldPosition, Attack attack, float deltaTime, bool playSound = false) + public AttackResult AddDamage(Character attacker, Vector2 worldPosition, Attack attack, Vector2 impulseDirection, float deltaTime, bool playSound = false) { if (Submarine != null && Submarine.GodMode) { return new AttackResult(0.0f, null); } if (!Prefab.Body || Prefab.Platform || Indestructible) { return new AttackResult(0.0f, null); } Vector2 transformedPos = worldPosition; - if (Submarine != null) transformedPos -= Submarine.Position; + if (Submarine != null) { transformedPos -= Submarine.Position; } + + if (!MathUtils.NearlyEqual(BodyRotation, 0f)) + { + var center = Rect.Location.ToVector2() + Rect.Size.ToVector2().FlipY() * 0.5f; + var rotation = BodyRotation; + if (IsHorizontal && FlippedX) { rotation += MathF.PI; } + if (!IsHorizontal && FlippedY) { rotation += MathF.PI; } + transformedPos = MathUtils.RotatePointAroundTarget(transformedPos, center, rotation); + } float damageAmount = 0.0f; for (int i = 0; i < SectionCount; i++) @@ -1143,6 +1204,7 @@ namespace Barotrauma gapRect.Y = (gapRect.Y - gapRect.Height / 2) + (int)(BodyHeight / 2 + BodyOffset.Y * scale); gapRect.Height = (int)BodyHeight; } + if (FlippedX) { diffFromCenter = -diffFromCenter; } } else { @@ -1153,8 +1215,8 @@ namespace Barotrauma gapRect.Width = (int)BodyWidth; } if (BodyHeight > 0.0f) { gapRect.Height = (int)(BodyHeight * (gapRect.Height / (float)this.rect.Height)); } + if (FlippedY) { diffFromCenter = -diffFromCenter; } } - if (FlippedX) { diffFromCenter = -diffFromCenter; } if (Math.Abs(BodyRotation) > 0.01f) { @@ -1170,14 +1232,26 @@ namespace Barotrauma gapRect.Width += 20; gapRect.Height += 20; - bool horizontalGap = !IsHorizontal; + bool rotatedEnoughToChangeOrientation = (MathUtils.WrapAngleTwoPi(rotationRad - MathHelper.PiOver4) % MathHelper.Pi < MathHelper.PiOver2); + if (rotatedEnoughToChangeOrientation) + { + var center = gapRect.Location + gapRect.Size.FlipY() / new Point(2); + var topLeft = gapRect.Location; + var diff = topLeft - center; + diff = diff.FlipY().YX().FlipY(); + var newTopLeft = diff + center; + gapRect = new Rectangle(newTopLeft, gapRect.Size.YX()); + } + bool horizontalGap = rotatedEnoughToChangeOrientation + ? IsHorizontal + : !IsHorizontal; bool diagonalGap = false; - if (Prefab.BodyRotation != 0.0f) + if (!MathUtils.NearlyEqual(BodyRotation, 0f)) { //rotation within a 90 deg sector (e.g. 100 -> 10, 190 -> 10, -10 -> 80) float sectorizedRotation = MathUtils.WrapAngleTwoPi(BodyRotation) % MathHelper.PiOver2; //diagonal if 30 < angle < 60 - diagonalGap = sectorizedRotation > MathHelper.Pi / 6 && sectorizedRotation < MathHelper.Pi / 3; + diagonalGap = sectorizedRotation is > MathHelper.Pi / 6 and < MathHelper.Pi / 3; //gaps on the lower half of a diagonal wall are horizontal, ones on the upper half are vertical if (diagonalGap) { @@ -1245,7 +1319,7 @@ namespace Barotrauma private static void CreateWallDamageExplosion(Gap gap, Character attacker) { - const float explosionRange = 750.0f; + const float explosionRange = 500.0f; float explosionStrength = gap.Open; var linkedHull = gap.linkedTo.FirstOrDefault() as Hull; @@ -1264,20 +1338,22 @@ namespace Barotrauma if (explosionOnBroken == null) { - explosionOnBroken = new Explosion(explosionRange, force: 10.0f, damage: 0.0f, structureDamage: 0.0f, itemDamage: 0.0f); + explosionOnBroken = new Explosion(explosionRange, force: 5.0f, damage: 0.0f, structureDamage: 0.0f, itemDamage: 0.0f); if (AfflictionPrefab.Prefabs.TryGet("lacerations".ToIdentifier(), out AfflictionPrefab lacerations)) { - explosionOnBroken.Attack.Afflictions.Add(lacerations.Instantiate(3.0f), null); + explosionOnBroken.Attack.Afflictions.Add(lacerations.Instantiate(5.0f), null); } else { - explosionOnBroken.Attack.Afflictions.Add(AfflictionPrefab.InternalDamage.Instantiate(3.0f), null); + explosionOnBroken.Attack.Afflictions.Add(AfflictionPrefab.InternalDamage.Instantiate(5.0f), null); } - explosionOnBroken.IgnoreCover = true; + explosionOnBroken.IgnoreCover = false; explosionOnBroken.OnlyInside = true; + explosionOnBroken.DistanceFalloff = false; explosionOnBroken.DisableParticles(); } + explosionOnBroken.IgnoredCover = gap.ConnectedWall?.ToEnumerable(); explosionOnBroken.Attack.Range = explosionRange * gap.Open; explosionOnBroken.Attack.DamageMultiplier = explosionStrength; explosionOnBroken.Attack.Stun = MathHelper.Clamp(explosionStrength, 0.5f, 1.0f); @@ -1335,7 +1411,7 @@ namespace Barotrauma { hasHoles = true; - if (!mergedSections.Any()) continue; + if (!mergedSections.Any()) { continue; } var mergedRect = GenerateMergedRect(mergedSections); mergedSections.Clear(); CreateRectBody(mergedRect, createConvexHull: true); @@ -1371,18 +1447,17 @@ namespace Barotrauma diffFromCenter = (rect.Center.X - this.rect.Center.X) / (float)this.rect.Width * BodyWidth; if (BodyWidth > 0.0f) rect.Width = Math.Max((int)Math.Round(BodyWidth * (rect.Width / (float)this.rect.Width)), 1); if (BodyHeight > 0.0f) rect.Height = (int)BodyHeight; + if (FlippedX) { diffFromCenter = -diffFromCenter; } } else { diffFromCenter = ((rect.Y - rect.Height / 2) - (this.rect.Y - this.rect.Height / 2)) / (float)this.rect.Height * BodyHeight; if (BodyWidth > 0.0f) rect.Width = (int)BodyWidth; if (BodyHeight > 0.0f) rect.Height = Math.Max((int)Math.Round(BodyHeight * (rect.Height / (float)this.rect.Height)), 1); + if (FlippedY) { diffFromCenter = -diffFromCenter; } } - if (FlippedX) { diffFromCenter = -diffFromCenter; } - Vector2 bodyOffset = ConvertUnits.ToSimUnits(Prefab.BodyOffset) * scale; - if (FlippedX) { bodyOffset.X = -bodyOffset.X; } - if (FlippedY) { bodyOffset.Y = -bodyOffset.Y; } + Vector2 bodyOffset = ConvertUnits.ToSimUnits(BodyOffset) * scale; Body newBody = GameMain.World.CreateRectangle( ConvertUnits.ToSimUnits(rect.Width), @@ -1396,7 +1471,7 @@ namespace Barotrauma newBody.UserData = this; Vector2 structureCenter = ConvertUnits.ToSimUnits(Position); - if (BodyRotation != 0.0f) + if (!MathUtils.NearlyEqual(BodyRotation, 0f)) { Vector2 pos = structureCenter + bodyOffset + new Vector2( (float)Math.Cos(IsHorizontal ? -BodyRotation : MathHelper.PiOver2 - BodyRotation), diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/StructurePrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/StructurePrefab.cs index 941ddab07..a089421ab 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/StructurePrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/StructurePrefab.cs @@ -67,6 +67,9 @@ namespace Barotrauma [Serialize(false, IsPropertySaveable.No, description: "Can items like signal components be attached on this structure? Should be enabled on structures like decorative background walls.")] public bool AllowAttachItems { get; private set; } + [Serialize(true, IsPropertySaveable.No, description: "Can the structure be rotated in the submarine editor?")] + public bool AllowRotatingInEditor { get; set; } + [Serialize(0.0f, IsPropertySaveable.No)] public float MinHealth { get; private set; } @@ -297,14 +300,16 @@ namespace Barotrauma if (Identifier == Identifier.Empty) { DebugConsole.ThrowError( - "Structure prefab \"" + Name + "\" has no identifier. All structure prefabs have a unique identifier string that's used to differentiate between items during saving and loading."); + "Structure prefab \"" + Name.Value + "\" has no identifier. All structure prefabs have a unique identifier string that's used to differentiate between items during saving and loading.", + contentPackage: ContentPackage); } #if DEBUG if (!Category.HasFlag(MapEntityCategory.Legacy) && !HideInMenus) { if (!string.IsNullOrEmpty(OriginalName)) { - DebugConsole.AddWarning($"Structure \"{(Identifier == Identifier.Empty ? Name : Identifier.Value)}\" has a hard-coded name, and won't be localized to other languages."); + DebugConsole.AddWarning($"Structure \"{(Identifier == Identifier.Empty ? Name : Identifier.Value)}\" has a hard-coded name, and won't be localized to other languages.", + ContentPackage); } } #endif diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs index fad619258..459307b73 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs @@ -624,7 +624,7 @@ namespace Barotrauma //math/physics stuff ---------------------------------------------------- - public static Vector2 VectorToWorldGrid(Vector2 position, bool round = false) + public static Vector2 VectorToWorldGrid(Vector2 position, Submarine sub = null, bool round = false) { if (round) { @@ -636,6 +636,12 @@ namespace Barotrauma position.X = MathF.Floor(position.X / GridSize.X) * GridSize.X; position.Y = MathF.Ceiling(position.Y / GridSize.Y) * GridSize.Y; } + + if (sub != null) + { + position.X += sub.Position.X % GridSize.X; + position.Y += sub.Position.Y % GridSize.Y; + } return position; } @@ -1840,6 +1846,7 @@ namespace Barotrauma FilePath = filePath, OutpostModuleInfo = Info.OutpostModuleInfo != null ? new OutpostModuleInfo(Info.OutpostModuleInfo) : null, BeaconStationInfo = Info.BeaconStationInfo != null ? new BeaconStationInfo(Info.BeaconStationInfo) : null, + WreckInfo = Info.WreckInfo != null ? new WreckInfo(Info.WreckInfo) : null, Name = Path.GetFileNameWithoutExtension(filePath) }; #if CLIENT diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineBody.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineBody.cs index e0d060c04..9976f1f51 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineBody.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineBody.cs @@ -800,7 +800,10 @@ namespace Barotrauma float damageAmount = contactDot * Body.Mass / limb.character.Mass; limb.character.LastDamageSource = submarine; limb.character.DamageLimb(ConvertUnits.ToDisplayUnits(collision.ImpactPos), limb, - AfflictionPrefab.ImpactDamage.Instantiate(damageAmount).ToEnumerable(), 0.0f, true, 0.0f); + AfflictionPrefab.ImpactDamage.Instantiate(damageAmount).ToEnumerable(), + stun: 0.0f, + playSound: true, + attackImpulse: Vector2.Zero); if (limb.character.IsDead) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineInfo.cs index a83578cfa..a71c4f3fb 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineInfo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineInfo.cs @@ -122,6 +122,9 @@ namespace Barotrauma public OutpostModuleInfo OutpostModuleInfo { get; set; } public BeaconStationInfo BeaconStationInfo { get; set; } + public WreckInfo WreckInfo { get; set; } + + public ExtraSubmarineInfo GetExtraSubmarineInfo => BeaconStationInfo ?? WreckInfo as ExtraSubmarineInfo; public bool IsOutpost => Type == SubmarineType.Outpost || Type == SubmarineType.OutpostModule; @@ -320,10 +323,14 @@ namespace Barotrauma { OutpostModuleInfo = new OutpostModuleInfo(original.OutpostModuleInfo); } - if (original.BeaconStationInfo != null) + else if (original.BeaconStationInfo != null) { BeaconStationInfo = new BeaconStationInfo(original.BeaconStationInfo); } + else if (original.WreckInfo != null) + { + WreckInfo = new WreckInfo(original.WreckInfo); + } #if CLIENT PreviewImage = original.PreviewImage != null ? new Sprite(original.PreviewImage) : null; #endif @@ -410,6 +417,10 @@ namespace Barotrauma { BeaconStationInfo = new BeaconStationInfo(this, SubmarineElement); } + else if (Type == SubmarineType.Wreck) + { + WreckInfo = new WreckInfo(this, SubmarineElement); + } } } @@ -589,6 +600,11 @@ namespace Barotrauma BeaconStationInfo.Save(newElement); BeaconStationInfo = new BeaconStationInfo(this, newElement); } + else if (Type == SubmarineType.Wreck) + { + WreckInfo.Save(newElement); + WreckInfo = new WreckInfo(this, newElement); + } XDocument doc = new XDocument(newElement); doc.Root.Add(new XAttribute("name", Name)); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/BanList.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/BanList.cs index dbb4c9276..c19ff9637 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/BanList.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/BanList.cs @@ -14,6 +14,16 @@ namespace Barotrauma.Networking public readonly string Reason; public Option ExpirationTime; public readonly UInt32 UniqueIdentifier; + + public bool MatchesClient(Client client) + { + if (client == null) { return false; } + if (AddressOrAccountId.TryGet(out AccountId bannedAccountId) && client.AccountId.TryUnwrap(out AccountId? accountId)) + { + return bannedAccountId.Equals(accountId); + } + return false; + } } partial class BanList diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/Client.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/Client.cs index b3b47676e..34af27022 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/Client.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/Client.cs @@ -218,7 +218,7 @@ namespace Barotrauma.Networking msg.WriteUInt16((UInt16)PermittedConsoleCommands.Count); foreach (DebugConsole.Command command in PermittedConsoleCommands) { - msg.WriteString(command.names[0]); + msg.WriteIdentifier(command.Names[0]); } } } @@ -240,8 +240,8 @@ namespace Barotrauma.Networking UInt16 commandCount = inc.ReadUInt16(); for (int i = 0; i < commandCount; i++) { - string commandName = inc.ReadString(); - var consoleCommand = DebugConsole.Commands.Find(c => c.names.Contains(commandName)); + Identifier commandName = inc.ReadIdentifier(); + var consoleCommand = DebugConsole.Commands.Find(c => c.Names.Contains(commandName)); if (consoleCommand != null) { permittedCommands.Add(consoleCommand); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/ServerSettings.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/ServerSettings.cs index b6dfb0840..2a782728c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/ServerSettings.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/ServerSettings.cs @@ -644,6 +644,13 @@ namespace Barotrauma.Networking set; } + [Serialize(true, IsPropertySaveable.Yes)] + public bool AllowImmediateItemDelivery + { + get; + set; + } + [Serialize(false, IsPropertySaveable.Yes)] public bool LockAllDefaultWires { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Serialization/Editable/ConditionallyEditable.cs b/Barotrauma/BarotraumaShared/SharedSource/Serialization/Editable/ConditionallyEditable.cs new file mode 100644 index 000000000..5229bf5b5 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Serialization/Editable/ConditionallyEditable.cs @@ -0,0 +1,66 @@ +using System; +using Barotrauma.Items.Components; + +namespace Barotrauma; + +[AttributeUsage(AttributeTargets.Property)] +sealed class ConditionallyEditable : Editable +{ + public ConditionallyEditable(ConditionType conditionType, bool onlyInEditors = true) + { + this.conditionType = conditionType; + this.onlyInEditors = onlyInEditors; + } + private readonly ConditionType conditionType; + + private readonly bool onlyInEditors; + + public enum ConditionType + { + //These need to exist at compile time, so it is a little awkward + //I would love to see a better way to do this + AllowLinkingWifiToChat, + IsSwappableItem, + AllowRotating, + Attachable, + HasBody, + Pickable, + OnlyByStatusEffectsAndNetwork, + HasIntegratedButtons, + IsToggleableController, + HasConnectionPanel + } + + public bool IsEditable(ISerializableEntity entity) + { + if (onlyInEditors && Screen.Selected is { IsEditor: false }) { return false; } + + return conditionType switch + { + ConditionType.AllowLinkingWifiToChat + => GameMain.NetworkMember is not { ServerSettings.AllowLinkingWifiToChat: false }, + ConditionType.IsSwappableItem + => entity is Item item && item.Prefab.SwappableItem != null, + ConditionType.AllowRotating + => (entity is Item { body: null } item && item.Prefab.AllowRotatingInEditor) + || (entity is Structure structure && structure.Prefab.AllowRotatingInEditor), + ConditionType.Attachable + => entity is Holdable { Attachable: true }, + ConditionType.HasBody + => entity is Structure { HasBody: true } or Item { body: not null }, + ConditionType.Pickable + => entity is Item item && item.GetComponent() != null, + ConditionType.OnlyByStatusEffectsAndNetwork + => GameMain.NetworkMember is { IsServer: true }, + ConditionType.HasIntegratedButtons + => entity is Door { HasIntegratedButtons: true }, + ConditionType.IsToggleableController + => entity is Controller { IsToggle: true } controller && controller.Item.GetComponent() != null, + ConditionType.HasConnectionPanel + => (entity is Item item && item.GetComponent() != null) + || (entity is ItemComponent ic && ic.Item.GetComponent() != null), + _ + => false + }; + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Serialization/Editable/Editable.cs b/Barotrauma/BarotraumaShared/SharedSource/Serialization/Editable/Editable.cs new file mode 100644 index 000000000..5786df7b1 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Serialization/Editable/Editable.cs @@ -0,0 +1,55 @@ +using System; +using Barotrauma.Items.Components; + +namespace Barotrauma; + +[AttributeUsage(AttributeTargets.Property)] +class Editable : Attribute +{ + public int MaxLength; + public int DecimalCount = 1; + + public int MinValueInt = int.MinValue, MaxValueInt = int.MaxValue; + public float MinValueFloat = float.MinValue, MaxValueFloat = float.MaxValue; + public bool ForceShowPlusMinusButtons = false; + public float ValueStep; + + /// + /// Labels of the components of a vector property (defaults to x,y,z,w) + /// + public string[] VectorComponentLabels; + + /// + /// If a translation can't be found for the property name, this tag is used instead + /// + public string FallBackTextTag; + + /// + /// Currently implemented only for int and bool fields. TODO: implement the remaining types (SerializableEntityEditor) + /// + public bool ReadOnly; + + public Editable(int maxLength = 20) + { + MaxLength = maxLength; + } + + public Editable(int minValue, int maxValue) + { + MinValueInt = minValue; + MaxValueInt = maxValue; + } + + public Editable(float minValue, float maxValue, int decimals = 1) + { + MinValueFloat = minValue; + MaxValueFloat = maxValue; + DecimalCount = decimals; + } +} + +[AttributeUsage(AttributeTargets.Property)] +sealed class InGameEditable : Editable +{ +} + diff --git a/Barotrauma/BarotraumaShared/SharedSource/Serialization/SerializableProperty.cs b/Barotrauma/BarotraumaShared/SharedSource/Serialization/SerializableProperty/SerializableProperty.cs similarity index 91% rename from Barotrauma/BarotraumaShared/SharedSource/Serialization/SerializableProperty.cs rename to Barotrauma/BarotraumaShared/SharedSource/Serialization/SerializableProperty/SerializableProperty.cs index 3e477e0e2..1c26b59df 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Serialization/SerializableProperty.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Serialization/SerializableProperty/SerializableProperty.cs @@ -15,135 +15,6 @@ using Barotrauma.Networking; namespace Barotrauma { - [AttributeUsage(AttributeTargets.Property)] - class Editable : Attribute - { - public int MaxLength; - public int DecimalCount = 1; - - public int MinValueInt = int.MinValue, MaxValueInt = int.MaxValue; - public float MinValueFloat = float.MinValue, MaxValueFloat = float.MaxValue; - public float ValueStep; - - /// - /// Labels of the components of a vector property (defaults to x,y,z,w) - /// - public string[] VectorComponentLabels; - - /// - /// If a translation can't be found for the property name, this tag is used instead - /// - public string FallBackTextTag; - - /// - /// Currently implemented only for int and bool fields. TODO: implement the remaining types (SerializableEntityEditor) - /// - public bool ReadOnly; - - public Editable(int maxLength = 20) - { - MaxLength = maxLength; - } - - public Editable(int minValue, int maxValue) - { - MinValueInt = minValue; - MaxValueInt = maxValue; - } - - public Editable(float minValue, float maxValue, int decimals = 1) - { - MinValueFloat = minValue; - MaxValueFloat = maxValue; - DecimalCount = decimals; - } - } - - [AttributeUsage(AttributeTargets.Property)] - class InGameEditable : Editable - { - } - - [AttributeUsage(AttributeTargets.Property)] - class ConditionallyEditable : Editable - { - public ConditionallyEditable(ConditionType conditionType, bool onlyInEditors = true) - { - this.conditionType = conditionType; - this.onlyInEditors = onlyInEditors; - } - private readonly ConditionType conditionType; - - private readonly bool onlyInEditors; - - public enum ConditionType - { - //These need to exist at compile time, so it is a little awkward - //I would love to see a better way to do this - AllowLinkingWifiToChat, - IsSwappableItem, - AllowRotating, - Attachable, - HasBody, - Pickable, - OnlyByStatusEffectsAndNetwork, - HasIntegratedButtons, - IsToggleableController, - HasConnectionPanel - } - - public bool IsEditable(ISerializableEntity entity) - { - if (onlyInEditors && Screen.Selected is { IsEditor: false }) { return false; } - switch (conditionType) - { - case ConditionType.AllowLinkingWifiToChat: - return GameMain.NetworkMember?.ServerSettings?.AllowLinkingWifiToChat ?? true; - case ConditionType.IsSwappableItem: - { - return entity is Item item && item.Prefab.SwappableItem != null; - } - case ConditionType.AllowRotating: - { - return entity is Item item && item.body == null && item.Prefab.AllowRotatingInEditor; - } - case ConditionType.Attachable: - { - return entity is Holdable holdable && holdable.Attachable; - } - case ConditionType.HasBody: - { - return entity is Structure { HasBody: true } || entity is Item { body: not null }; - } - case ConditionType.Pickable: - { - return entity is Item item && item.GetComponent() != null; - } - case ConditionType.HasIntegratedButtons: - { - return entity is Door door && door.HasIntegratedButtons; - } - case ConditionType.OnlyByStatusEffectsAndNetwork: -#if SERVER - return true; -#else - return false; -#endif - case ConditionType.IsToggleableController: - { - return entity is Controller controller && controller.IsToggle && controller.Item.GetComponent() != null; - } - case ConditionType.HasConnectionPanel: - { - return - (entity is Item item && item.GetComponent() != null) || - (entity is ItemComponent ic && ic.Item.GetComponent() != null); - } - } - return false; - } - } - public enum IsPropertySaveable { Yes, @@ -151,7 +22,7 @@ namespace Barotrauma } [AttributeUsage(AttributeTargets.Property)] - public class Serialize : Attribute + public sealed class Serialize : Attribute { public readonly object DefaultValue; public readonly IsPropertySaveable IsSaveable; @@ -182,9 +53,9 @@ namespace Barotrauma } } - public class SerializableProperty + public sealed class SerializableProperty { - private readonly static ImmutableDictionary supportedTypes = new Dictionary + private static readonly ImmutableDictionary supportedTypes = new Dictionary { { typeof(bool), "bool" }, { typeof(int), "int" }, diff --git a/Barotrauma/BarotraumaShared/SharedSource/Serialization/StructSerialization.cs b/Barotrauma/BarotraumaShared/SharedSource/Serialization/StructSerialization.cs index 409e5d5b1..1121e33d9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Serialization/StructSerialization.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Serialization/StructSerialization.cs @@ -114,7 +114,7 @@ namespace Barotrauma self = (T)boxedSelf; } - public static void TryDeserialize(this object boxedSelf, FieldInfo field, XElement element) + private static void TryDeserialize(this object boxedSelf, FieldInfo field, XElement element) { string fieldName = field.Name.ToLowerInvariant(); string valueStr = element.GetAttributeString(fieldName, field.GetValue(boxedSelf)?.ToString() ?? ""); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Settings/GameSettings.cs b/Barotrauma/BarotraumaShared/SharedSource/Settings/GameSettings.cs index 8c044c67d..7d7012dcd 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Settings/GameSettings.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Settings/GameSettings.cs @@ -548,6 +548,19 @@ namespace Barotrauma currentConfig.Graphics.VSync != newConfig.Graphics.VSync || currentConfig.Graphics.DisplayMode != newConfig.Graphics.DisplayMode; +#if CLIENT + bool keybindsChanged = false; + foreach (var kvp in newConfig.KeyMap.Bindings) + { + if (!currentConfig.KeyMap.Bindings.TryGetValue(kvp.Key, out var existingBinding) || + existingBinding != kvp.Value) + { + keybindsChanged = true; + break; + } + } +#endif + currentConfig = newConfig; #if CLIENT @@ -575,7 +588,19 @@ namespace Barotrauma HUDLayoutSettings.CreateAreas(); GameMain.GameSession?.HUDScaleChanged(); } - + + if (keybindsChanged) + { + foreach (var item in Item.ItemList) + { + foreach (var ic in item.Components) + { + //parse messages because they may contain keybind texts + ic.ParseMsg(); + } + } + } + GameMain.SoundManager?.ApplySettings(); #endif if (languageChanged) { TextManager.ClearCache(); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Sprite/Sprite.cs b/Barotrauma/BarotraumaShared/SharedSource/Sprite/Sprite.cs index bb39a43b5..8d3d19ab4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Sprite/Sprite.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Sprite/Sprite.cs @@ -306,7 +306,8 @@ namespace Barotrauma } if (file == "") { - DebugConsole.ThrowError("Sprite " + SourceElement + " doesn't have a texture specified!"); + DebugConsole.ThrowError("Sprite " + SourceElement.Element + " doesn't have a texture specified!", + contentPackage: SourceElement.ContentPackage); return false; } if (!string.IsNullOrEmpty(path)) diff --git a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/PropertyConditional.cs b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/PropertyConditional.cs index 8d14f4ce7..3c34fd89a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/PropertyConditional.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/PropertyConditional.cs @@ -196,7 +196,7 @@ namespace Barotrauma /// public readonly bool TargetContainedItem; - public static IEnumerable FromXElement(XElement element, Predicate? predicate = null) + public static IEnumerable FromXElement(ContentXElement element, Predicate? predicate = null) { var targetItemComponent = element.GetAttributeString(nameof(TargetItemComponent), ""); var targetContainer = element.GetAttributeBool(nameof(TargetContainer), false); @@ -218,7 +218,7 @@ namespace Barotrauma var (comparisonOperator, attributeValueString) = ExtractComparisonOperatorFromConditionString(attribute.Value); if (string.IsNullOrWhiteSpace(attributeValueString)) { - DebugConsole.ThrowError($"Conditional attribute value is empty: {element}"); + DebugConsole.ThrowError($"Conditional attribute value is empty: {element}", contentPackage: element.ContentPackage); continue; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs index 0f9370041..67226d137 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs @@ -227,17 +227,17 @@ namespace Barotrauma public bool InheritEventTags { get; private set; } - public ItemSpawnInfo(XElement element, string parentDebugName) + public ItemSpawnInfo(ContentXElement element, string parentDebugName) { - if (element.Attribute("name") != null) + if (element.GetAttribute("name") != null) { //backwards compatibility - DebugConsole.ThrowError("Error in StatusEffect config (" + element.ToString() + ") - use item identifier instead of the name."); + DebugConsole.ThrowError("Error in StatusEffect config (" + element.ToString() + ") - use item identifier instead of the name.", contentPackage: element.ContentPackage); string itemPrefabName = element.GetAttributeString("name", ""); ItemPrefab = ItemPrefab.Prefabs.Find(m => m.NameMatches(itemPrefabName, StringComparison.InvariantCultureIgnoreCase) || m.Tags.Contains(itemPrefabName)); if (ItemPrefab == null) { - DebugConsole.ThrowError("Error in StatusEffect \"" + parentDebugName + "\" - item prefab \"" + itemPrefabName + "\" not found."); + DebugConsole.ThrowError("Error in StatusEffect \"" + parentDebugName + "\" - item prefab \"" + itemPrefabName + "\" not found.", contentPackage: element.ContentPackage); } } else @@ -246,12 +246,12 @@ namespace Barotrauma if (string.IsNullOrEmpty(itemPrefabIdentifier)) itemPrefabIdentifier = element.GetAttributeString("identifiers", ""); if (string.IsNullOrEmpty(itemPrefabIdentifier)) { - DebugConsole.ThrowError("Invalid item spawn in StatusEffect \"" + parentDebugName + "\" - identifier not found in the element \"" + element.ToString() + "\""); + DebugConsole.ThrowError("Invalid item spawn in StatusEffect \"" + parentDebugName + "\" - identifier not found in the element \"" + element.ToString() + "\".", contentPackage: element.ContentPackage); } ItemPrefab = ItemPrefab.Prefabs.Find(m => m.Identifier == itemPrefabIdentifier); if (ItemPrefab == null) { - DebugConsole.ThrowError("Error in StatusEffect config - item prefab with the identifier \"" + itemPrefabIdentifier + "\" not found."); + DebugConsole.ThrowError("Error in StatusEffect config - item prefab with the identifier \"" + itemPrefabIdentifier + "\" not found.", contentPackage: element.ContentPackage); return; } } @@ -332,7 +332,7 @@ namespace Barotrauma /// public readonly bool TriggerTalents; - public GiveSkill(XElement element, string parentDebugName) + public GiveSkill(ContentXElement element, string parentDebugName) { SkillIdentifier = element.GetAttributeIdentifier(nameof(SkillIdentifier), Identifier.Empty); Amount = element.GetAttributeFloat(nameof(Amount), 0); @@ -340,7 +340,7 @@ namespace Barotrauma if (SkillIdentifier == Identifier.Empty) { - DebugConsole.ThrowError($"GiveSkill StatusEffect did not have a skill identifier defined in {parentDebugName}!"); + DebugConsole.ThrowError($"GiveSkill StatusEffect did not have a skill identifier defined in {parentDebugName}!", contentPackage: element.ContentPackage); } } } @@ -410,12 +410,12 @@ namespace Barotrauma [Serialize(false, IsPropertySaveable.No)] public bool InheritEventTags { get; private set; } - public CharacterSpawnInfo(XElement element, string parentDebugName) + public CharacterSpawnInfo(ContentXElement element, string parentDebugName) { SerializableProperties = SerializableProperty.DeserializeProperties(this, element); if (SpeciesName.IsEmpty) { - DebugConsole.ThrowError($"Invalid character spawn ({Name}) in StatusEffect \"{parentDebugName}\" - identifier not found in the element \"{element}\""); + DebugConsole.ThrowError($"Invalid character spawn ({Name}) in StatusEffect \"{parentDebugName}\" - identifier not found in the element \"{element}\".", contentPackage: element.ContentPackage); } } } @@ -798,7 +798,7 @@ namespace Barotrauma { if (!Enum.TryParse(s, true, out TargetType targetType)) { - DebugConsole.ThrowError($"Invalid target type \"{s}\" in StatusEffect ({parentDebugName})"); + DebugConsole.ThrowError($"Invalid target type \"{s}\" in StatusEffect ({parentDebugName})", contentPackage: element.ContentPackage); } else { @@ -837,7 +837,7 @@ namespace Barotrauma case "type": if (!Enum.TryParse(attribute.Value, true, out type)) { - DebugConsole.ThrowError($"Invalid action type \"{attribute.Value}\" in StatusEffect ({parentDebugName})"); + DebugConsole.ThrowError($"Invalid action type \"{attribute.Value}\" in StatusEffect ({parentDebugName})", contentPackage: element.ContentPackage); } break; case "targettype": @@ -866,11 +866,11 @@ namespace Barotrauma case "comparison": if (!Enum.TryParse(attribute.Value, ignoreCase: true, out conditionalLogicalOperator)) { - DebugConsole.ThrowError($"Invalid conditional comparison type \"{attribute.Value}\" in StatusEffect ({parentDebugName})"); + DebugConsole.ThrowError($"Invalid conditional comparison type \"{attribute.Value}\" in StatusEffect ({parentDebugName})", contentPackage: element.ContentPackage); } break; case "sound": - DebugConsole.ThrowError($"Error in StatusEffect ({parentDebugName}): sounds should be defined as child elements of the StatusEffect, not as attributes."); + DebugConsole.ThrowError($"Error in StatusEffect ({parentDebugName}): sounds should be defined as child elements of the StatusEffect, not as attributes.", contentPackage: element.ContentPackage); break; case "range": if (!HasTargetType(TargetType.NearbyCharacters) && !HasTargetType(TargetType.NearbyItems)) @@ -951,7 +951,7 @@ namespace Barotrauma RelatedItem newRequiredItem = RelatedItem.Load(subElement, returnEmpty: false, parentDebugName: parentDebugName); if (newRequiredItem == null) { - DebugConsole.ThrowError("Error in StatusEffect config - requires an item with no identifiers."); + DebugConsole.ThrowError("Error in StatusEffect config - requires an item with no identifiers.", contentPackage: element.ContentPackage); continue; } requiredItems.Add(newRequiredItem); @@ -973,12 +973,12 @@ namespace Barotrauma AfflictionPrefab afflictionPrefab; if (subElement.GetAttribute("name") != null) { - DebugConsole.ThrowError("Error in StatusEffect (" + parentDebugName + ") - define afflictions using identifiers instead of names."); + DebugConsole.ThrowError("Error in StatusEffect (" + parentDebugName + ") - define afflictions using identifiers instead of names.", contentPackage: element.ContentPackage); string afflictionName = subElement.GetAttributeString("name", ""); afflictionPrefab = AfflictionPrefab.List.FirstOrDefault(ap => ap.Name.Equals(afflictionName, StringComparison.OrdinalIgnoreCase)); if (afflictionPrefab == null) { - DebugConsole.ThrowError("Error in StatusEffect (" + parentDebugName + ") - Affliction prefab \"" + afflictionName + "\" not found."); + DebugConsole.ThrowError("Error in StatusEffect (" + parentDebugName + ") - Affliction prefab \"" + afflictionName + "\" not found.", contentPackage: element.ContentPackage); continue; } } @@ -988,7 +988,7 @@ namespace Barotrauma afflictionPrefab = AfflictionPrefab.List.FirstOrDefault(ap => ap.Identifier == afflictionIdentifier); if (afflictionPrefab == null) { - DebugConsole.ThrowError("Error in StatusEffect (" + parentDebugName + ") - Affliction prefab with the identifier \"" + afflictionIdentifier + "\" not found."); + DebugConsole.ThrowError("Error in StatusEffect (" + parentDebugName + ") - Affliction prefab with the identifier \"" + afflictionIdentifier + "\" not found.", contentPackage: element.ContentPackage); continue; } } @@ -1001,7 +1001,7 @@ namespace Barotrauma case "reduceaffliction": if (subElement.GetAttribute("name") != null) { - DebugConsole.ThrowError("Error in StatusEffect (" + parentDebugName + ") - define afflictions using identifiers or types instead of names."); + DebugConsole.ThrowError("Error in StatusEffect (" + parentDebugName + ") - define afflictions using identifiers or types instead of names.", contentPackage: element.ContentPackage); ReduceAffliction.Add(( subElement.GetAttributeIdentifier("name", ""), subElement.GetAttributeFloat(1.0f, "amount", "strength", "reduceamount"))); @@ -1016,7 +1016,7 @@ namespace Barotrauma } else { - DebugConsole.ThrowError("Error in StatusEffect (" + parentDebugName + ") - Affliction prefab with the identifier or type \"" + name + "\" not found."); + DebugConsole.ThrowError("Error in StatusEffect (" + parentDebugName + ") - Affliction prefab with the identifier or type \"" + name + "\" not found.", contentPackage: element.ContentPackage); } } break; @@ -1374,6 +1374,10 @@ namespace Barotrauma if (OnlyOutside && character.CurrentHull != null) { return false; } if (TargetIdentifiers == null) { return true; } if (TargetIdentifiers.Contains("character")) { return true; } + if (TargetIdentifiers.Contains("monster")) + { + return !character.IsHuman && character.Group != CharacterPrefab.HumanSpeciesName; + } return TargetIdentifiers.Contains(character.SpeciesName); } @@ -1606,28 +1610,7 @@ namespace Barotrauma } } } - if (removeItem) - { - for (int i = 0; i < targets.Count; i++) - { - if (targets[i] is Item item) { Entity.Spawner?.AddItemToRemoveQueue(item); } - } - } - if (removeCharacter) - { - for (int i = 0; i < targets.Count; i++) - { - var target = targets[i]; - if (target is Character character) - { - Entity.Spawner?.AddEntityToRemoveQueue(character); - } - else if (target is Limb limb) - { - Entity.Spawner?.AddEntityToRemoveQueue(limb.character); - } - } - } + if (breakLimb || hideLimb) { for (int i = 0; i < targets.Count; i++) @@ -1718,7 +1701,7 @@ namespace Barotrauma if (limb.Removed) { continue; } if (limb.IsSevered) { continue; } if (targetLimbs != null && !targetLimbs.Contains(limb.type)) { continue; } - AttackResult result = limb.character.DamageLimb(position, limb, newAffliction.ToEnumerable(), stun: 0.0f, playSound: false, attackImpulse: 0.0f, attacker: affliction.Source, allowStacking: !setValue); + AttackResult result = limb.character.DamageLimb(position, limb, newAffliction.ToEnumerable(), stun: 0.0f, playSound: false, attackImpulse: Vector2.Zero, attacker: affliction.Source, allowStacking: !setValue); limb.character.TrySeverLimbJoints(limb, SeverLimbsProbability, disableDeltaTime ? result.Damage : result.Damage / deltaTime, allowBeheading: true, attacker: affliction.Source); RegisterTreatmentResults(user, entity as Item, limb, affliction, result); //only apply non-limb-specific afflictions to the first limb @@ -1731,7 +1714,7 @@ namespace Barotrauma if (limb.character.Removed || limb.Removed) { continue; } if (targetLimbs != null && !targetLimbs.Contains(limb.type)) { continue; } newAffliction = GetMultipliedAffliction(affliction, entity, limb.character, deltaTime, multiplyAfflictionsByMaxVitality); - AttackResult result = limb.character.DamageLimb(position, limb, newAffliction.ToEnumerable(), stun: 0.0f, playSound: false, attackImpulse: 0.0f, attacker: affliction.Source, allowStacking: !setValue); + AttackResult result = limb.character.DamageLimb(position, limb, newAffliction.ToEnumerable(), stun: 0.0f, playSound: false, attackImpulse: Vector2.Zero, attacker: affliction.Source, allowStacking: !setValue); limb.character.TrySeverLimbJoints(limb, SeverLimbsProbability, disableDeltaTime ? result.Damage : result.Damage / deltaTime, allowBeheading: true, attacker: affliction.Source); RegisterTreatmentResults(user, entity as Item, limb, affliction, result); } @@ -1899,7 +1882,7 @@ namespace Barotrauma if (FireSize > 0.0f && entity != null) { - var fire = new FireSource(position, hull); + var fire = new FireSource(position, hull, sourceCharacter: user); fire.Size = new Vector2(FireSize, fire.Size.Y); } @@ -2266,8 +2249,8 @@ namespace Barotrauma { OnItemSpawned(newItem, chosenItemSpawnInfo); }); + break; } - break; } } } @@ -2292,6 +2275,30 @@ namespace Barotrauma ApplyProjSpecific(deltaTime, entity, targets, hull, position, playSound: true); + //do this last - the entities spawned by the effect might need the entity for something, so better to remove it last + if (removeItem) + { + for (int i = 0; i < targets.Count; i++) + { + if (targets[i] is Item item) { Entity.Spawner?.AddItemToRemoveQueue(item); } + } + } + if (removeCharacter) + { + for (int i = 0; i < targets.Count; i++) + { + var target = targets[i]; + if (target is Character character) + { + Entity.Spawner?.AddEntityToRemoveQueue(character); + } + else if (target is Limb limb) + { + Entity.Spawner?.AddEntityToRemoveQueue(limb.character); + } + } + } + if (oneShot) { Disabled = true; @@ -2406,7 +2413,7 @@ namespace Barotrauma { if (limb.character.Removed || limb.Removed) { continue; } newAffliction = element.Parent.GetMultipliedAffliction(affliction, element.Entity, limb.character, deltaTime, element.Parent.multiplyAfflictionsByMaxVitality); - var result = limb.character.DamageLimb(limb.WorldPosition, limb, newAffliction.ToEnumerable(), stun: 0.0f, playSound: false, attackImpulse: 0.0f, attacker: element.User); + var result = limb.character.DamageLimb(limb.WorldPosition, limb, newAffliction.ToEnumerable(), stun: 0.0f, playSound: false, attackImpulse: Vector2.Zero, attacker: element.User); element.Parent.RegisterTreatmentResults(element.Parent.user, element.Entity as Item, limb, affliction, result); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Tags.cs b/Barotrauma/BarotraumaShared/SharedSource/Tags.cs index 282c7d503..53282a021 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Tags.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Tags.cs @@ -45,7 +45,7 @@ public static class Tags public static readonly Identifier ToolItem = "tool".ToIdentifier(); public static readonly Identifier LogicItem = "logic".ToIdentifier(); public static readonly Identifier NavTerminal = "navterminal".ToIdentifier(); - public static readonly Identifier IdCard = "identitycard".ToIdentifier(); + public static readonly Identifier IdCardTag = "identitycard".ToIdentifier(); public static readonly Identifier WireItem = "wire".ToIdentifier(); public static readonly Identifier ChairItem = "chair".ToIdentifier(); public static readonly Identifier ArtifactHolder = "artifactholder".ToIdentifier(); @@ -55,6 +55,8 @@ public static class Tags public static readonly Identifier DontSellItems = "dontsellitems".ToIdentifier(); public static readonly Identifier CargoContainer = "cargocontainer".ToIdentifier(); + public static readonly Identifier CargoMissionItem = "cargomission".ToIdentifier(); + public static readonly Identifier ItemIgnoredByAI = "ignorebyai".ToIdentifier(); public static readonly Identifier GuardianShelter = "guardianshelter".ToIdentifier(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Traitors/TraitorEventPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Traitors/TraitorEventPrefab.cs index e941208e9..1353c1bbf 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Traitors/TraitorEventPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Traitors/TraitorEventPrefab.cs @@ -25,7 +25,8 @@ namespace Barotrauma MissionType = element.GetAttributeEnum(nameof(MissionType), MissionType.None); if (MissionIdentifier.IsEmpty && MissionTag.IsEmpty && MissionType == MissionType.None) { - DebugConsole.ThrowError($"Error in traitor event \"{prefab.Identifier}\". Mission requirement with no {nameof(MissionIdentifier)}, {nameof(MissionTag)} or {nameof(MissionType)}."); + DebugConsole.ThrowError($"Error in traitor event \"{prefab.Identifier}\". Mission requirement with no {nameof(MissionIdentifier)}, {nameof(MissionTag)} or {nameof(MissionType)}.", + contentPackage: prefab.ContentPackage); } } @@ -72,7 +73,7 @@ namespace Barotrauma //feels a little weird to have something this specific here, but couldn't think of a better way to implement this public ImmutableArray RequiredItemConditionals; - public LevelRequirement(XElement element, TraitorEventPrefab prefab) + public LevelRequirement(ContentXElement element, TraitorEventPrefab prefab) { levelType = element.GetAttributeEnum(nameof(LevelType), LevelType.Any); LocationTypes = element.GetAttributeIdentifierArray("locationtype", Array.Empty()).ToImmutableArray(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Upgrades/Upgrade.cs b/Barotrauma/BarotraumaShared/SharedSource/Upgrades/Upgrade.cs index 88731d001..920dbb269 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Upgrades/Upgrade.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Upgrades/Upgrade.cs @@ -333,7 +333,8 @@ namespace Barotrauma { DebugConsole.AddWarning($"Failed to save upgrade \"{Prefab.Name}\" on {TargetEntity.Name} because property reference \"{propertyRef.Name}\" is missing original values. \n" + "Upgrades should always call Upgrade.ApplyUpgrade() or manually set the original value in a property reference after they have been added. \n" + - "If you are not a developer submit a bug report at https://github.com/Regalis11/Barotrauma/issues/."); + "If you are not a developer submit a bug report at https://github.com/Regalis11/Barotrauma/issues/.", + Prefab.ContentPackage); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Upgrades/UpgradePrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Upgrades/UpgradePrefab.cs index 9114c6f6a..8365930bb 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Upgrades/UpgradePrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Upgrades/UpgradePrefab.cs @@ -18,12 +18,8 @@ namespace Barotrauma public readonly int IncreaseHigh; - public readonly UpgradePrefab Prefab; - public UpgradePrice(UpgradePrefab prefab, ContentXElement element) { - Prefab = prefab; - IncreaseLow = UpgradePrefab.ParsePercentage(element.GetAttributeString("increaselow", string.Empty)!, "IncreaseLow".ToIdentifier(), element, suppressWarnings: prefab.SuppressWarnings); @@ -34,20 +30,21 @@ namespace Barotrauma if (BasePrice == -1) { - if (prefab.SuppressWarnings) + if (!prefab.SuppressWarnings) { DebugConsole.AddWarning($"Price attribute \"baseprice\" is not defined for {prefab?.Identifier}.\n " + - "The value has been assumed to be '1000'."); + "The value has been assumed to be '1000'.", + prefab!.ContentPackage); BasePrice = 1000; } } } - public int GetBuyPrice(int level, Location? location = null, ImmutableHashSet? characterList = null) + public int GetBuyPrice(UpgradePrefab prefab, int level, Location? location = null, ImmutableHashSet? characterList = null) { float price = BasePrice; - int maxLevel = Prefab.MaxLevel; + int maxLevel = prefab.MaxLevel; float lerpAmount = maxLevel is 0 ? level // avoid division by 0 @@ -342,7 +339,8 @@ namespace Barotrauma { DebugConsole.AddWarning($"Upgrade \"{prefab.Identifier}\" is affecting a property that is also being affected by \"{matchingPrefab.Identifier}\".\n" + "This is unsupported and might yield unexpected results if both upgrades are applied at the same time to the same item.\n" + - "Add the attribute suppresswarnings=\"true\" to your XML element to disable this warning if you know what you're doing."); + "Add the attribute suppresswarnings=\"true\" to your XML element to disable this warning if you know what you're doing.", + prefab.ContentPackage); } } } @@ -398,11 +396,11 @@ namespace Barotrauma public UpgradePrefab(ContentXElement element, UpgradeModulesFile file) : base(element, file) { - Name = element.GetAttributeString("name", string.Empty)!; - Description = element.GetAttributeString("description", string.Empty)!; - MaxLevel = element.GetAttributeInt("maxlevel", 1); - SuppressWarnings = element.GetAttributeBool("supresswarnings", false); - HideInMenus = element.GetAttributeBool("hideinmenus", false); + Name = element.GetAttributeString(nameof(Name), string.Empty)!; + Description = element.GetAttributeString(nameof(Description), string.Empty)!; + MaxLevel = element.GetAttributeInt(nameof(MaxLevel), 1); + SuppressWarnings = element.GetAttributeBool(nameof(SuppressWarnings), false); + HideInMenus = element.GetAttributeBool(nameof(HideInMenus), false); SourceElement = element; var targetProperties = new Dictionary(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Utils/MathUtils.cs b/Barotrauma/BarotraumaShared/SharedSource/Utils/MathUtils.cs index 195cba7fc..8deb9ec70 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Utils/MathUtils.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Utils/MathUtils.cs @@ -412,41 +412,29 @@ namespace Barotrauma return false; } - /*public static List GetLineRectangleIntersections(Vector2 a1, Vector2 a2, Rectangle rect) - { - List intersections = new List(); + public static Vector2 FlipX(this Vector2 vector) + => new Vector2(-vector.X, vector.Y); - Vector2? intersection = GetAxisAlignedLineIntersection(a1, a2, - new Vector2(rect.X, rect.Y), - new Vector2(rect.Right, rect.Y), - true); + public static Vector2 FlipY(this Vector2 vector) + => new Vector2(vector.X, -vector.Y); - if (intersection != null) intersections.Add((Vector2)intersection); + public static Vector2 YX(this Vector2 vector) + => new Vector2(x: vector.Y, y: vector.X); + + public static Point FlipY(this Point point) + => new Point(point.X, -point.Y); - intersection = GetAxisAlignedLineIntersection(a1, a2, - new Vector2(rect.X, rect.Y - rect.Height), - new Vector2(rect.Right, rect.Y - rect.Height), - true); + public static Point YX(this Point point) + => new Point(x: point.Y, y: point.X); + + public static Vector2 RotatedUnitXRadians(float radians) + => new Vector2(MathF.Cos(radians), MathF.Sin(radians)); - if (intersection != null) intersections.Add((Vector2)intersection); - - intersection = GetAxisAlignedLineIntersection(a1, a2, - new Vector2(rect.X, rect.Y), - new Vector2(rect.X, rect.Y - rect.Height), - false); - - if (intersection != null) intersections.Add((Vector2)intersection); - - intersection = GetAxisAlignedLineIntersection(a1, a2, - new Vector2(rect.Right, rect.Y), - new Vector2(rect.Right, rect.Y - rect.Height), - false); - - if (intersection != null) intersections.Add((Vector2)intersection); - - return intersections; - }*/ + public static Vector2 RotatedUnitYRadians(float radians) + => RotatedUnitXRadians(radians).YX().FlipX(); + public static Vector2 Round(this Vector2 vector) + => new Vector2((int)MathF.Round(vector.X), (int)MathF.Round(vector.Y)); /// /// Get the intersections between a line (either infinite or a line segment) and a circle diff --git a/Barotrauma/BarotraumaShared/changelog.txt b/Barotrauma/BarotraumaShared/changelog.txt index ab028f246..2adfd257b 100644 --- a/Barotrauma/BarotraumaShared/changelog.txt +++ b/Barotrauma/BarotraumaShared/changelog.txt @@ -1,3 +1,83 @@ +------------------------------------------------------------------------------------------------------------------------------------------------- +v1.2.1.0 +------------------------------------------------------------------------------------------------------------------------------------------------- + +Changes: +- Structures can now be rotated in the sub editor! +- Option to get the items you're buying from an outpost store in your inventory immediately, as opposed to them being delivered to the sub at the start of the round. There's also now an anti-griefing setting for disabling this, in case you want to make sure a griefer can't easily gain access to dangerous items in the outposts. Players with campaign management permissions aren't affected by the setting. +- When running an outdated executable (i.e. a mod that overrides the game executable, but hasn't been updated yet after a new vanilla update has been released), there's a notification in the main menu and the "host server" menu warning you about the potential issues caused by running an outdated executable with the other up-to-date vanilla files. + +Fixes: +- Fixed dedicated servers with lots of mods enabled not showing up in the server browser. +- Fixed ability to sell components from inside circuit boxes in outposts. + +------------------------------------------------------------------------------------------------------------------------------------------------- +v1.2.0.0 +------------------------------------------------------------------------------------------------------------------------------------------------- + +Changes: +- Outpost security is now better at catching thieves: they can do random inspections to check for stolen items (more frequently if your reputation is low or if they've caught someone in your crew stealing), and notice if you're visibly carrying or wearing any stolen items. +- Bots can see through windowed walls (also making it easier for them to spot stealing). +- Added bilge pumps and duct blocks throughout outposts. Otherwise the outposts can never drain if the player causes a flood. +- Removed karma restrictions from jobs. This feature wasn't communicated anywhere, and it often just seemed like a bug when someone didn't get assign the job they wanted due to their karma being too low. +- Console errors and warnings caused by modded content include the name of the mod to make it easier to diagnose which mod is causing some issue or whether it's an issue in the vanilla game. +- Added bullet casing particles to firearms. +- Adjusted default throwing pose a bit: characters don't extend their arm as high up, because it could cause the throwable item to hit the ceiling in roughly door-height rooms. +- Optimized labels (large labels with a scaled down texture in particular were unnecessarily heavy). +- Optimized Watcher's status effects. +- Most message boxes can be closed by pressing enter (more specifically, all boxes that are created with just the "ok" button). +- ID cards left in a duffel bag are automatically removed at the end of the round. +- Added a light to handheld sonar to indicate when it's on + made the ping a bit higher in pitch to differentiate it from the sub's sonar. +- Reduced the forces applied on characters by fractal guardian's melee attacks. The previous values were so high they often lead to characters getting stunlocked when the attacks threw them against the walls of the ruin. + +Multiplayer: +- Fixed Text and NPCConversation files being required to be in sync between clients and the server, meaning if you were using a mod that changes the game to a different language, it'd get disabled when you joined a server that's not using the mod. +- Fixed lack of unban option in the "manage client" menu and the client context menu. + +Fixes: +- Fixed outpost NPCs not caring about the players starting fires. +- Fixed purchased items sometimes spawning in the cargo crates when you've got a cargo mission active, making them potentially very hard to find. +- Fixed a couple of inaccurate talent and mission descriptions in Russian. +- Fixed inability to select a component you've just placed in the circuit box until you move the component for the first time. +- Fixed health scanner HUD texts overlapping on high resolutions. +- Fixed "maintain position" marker being misplaced on the navigation terminal on some resolutions. +- Fixed chance of tainting the genetic material when refining sometimes being displayed incorrectly on research stations. +- Disable the health interface of the patient in the "good samaritan" event. Prevents being able to heal the opiate overdose "normally" before triggering the event. +- Fixed item hover texts (e.g. "[E] Repair") not refreshing when changing keybinds. +- Fixed fabricator listbox scroll always resetting to the top when someone selects the fabricator. +- Fixed bandoliers not affecting pulse laser fire rate. + +Modding: +- Fixed some ConversationAction options going outside the bounds of the dialog if there's a very large number of them. +- Fixed inability to use %ModDir% in LevelGenerationParams.ForceBeaconStation. +- Fixed status effects configured to spawn an item in ContainedInventory not spawning the item if it can't go inside the first item in the contained inventory. + +------------------------------------------------------------------------------------------------------------------------------------------------- +v1.1.19.3 +------------------------------------------------------------------------------------------------------------------------------------------------- + +- Fixed shrapnel from flak cannon ammo always launching upwards. +- Fixed wall damage shrapnel going through walls and being a little excessive overall. +- Fixes to pressure distribution logic: fixes pressure sometimes being lethal in breached rooms that weren't full of water. +- Fixed clients not regaining control of their braindead character after rejoining if the character's taken any amount of damage while braindead. +- Fixes to inconsistent stack sizes (e.g. certain ammo types stacking up to 12 in character inventories, but only 8 holdable/wearable items like backpacks and toolbelts. +- Fixed console errors when switching subs with circuit boxes on board. +- Fixed items that are inside a container that doesn't get transferred getting duplicated during item transfer when switching subs. +- Fixed bots not cleaning up circuit boxes from the floor. +- Fixed stack size being displayed incorrectly in the tooltip when dragging a stack of items to an already-occupied slot. +- Fixed nav terminal's docking button not working if the signal is routed from the terminal to the docking port through a circuit box. +- Fixed double-clicking a component while a circuit box is equipped making the component vanish inside the circuit box. +- Fixed chat messages sent when accusing someone as a traitor not triggering the spam filter. +- Fixed connection names not being translated in circuit boxes (always showed up in English). +- Fixed right-side crew panel overlapping with the mission panel in the PvP mode round summary. +- Fixed depleted and fulgurium fuel rods stacking up to 32 (should be 8 like all other fuel rods). + +Modding: +- Added "UseHumanAI" property to characters (can be used to enable the human AI on characters other than humans). While using the human AI on non-humans isn't a fully supported or tested feature, it was previously possible to do that by creating a human prefab using a different species than human, but that no longer worked as of the Treacherous Tides update. +- Attachable holdable items don't count as "HoldableOrWearableInventories". Fixes e.g. tanks only stacking up to 1 in things like movable cabinets and shelves. Does not affect any vanilla content. +- Fixed fabricator reducing the condition of all the available ingredients if the crafting recipe reduces condition instead of consuming the whole item. +- Fixed TagAction continuing in some situations when it can't find targets, even if it's been configured not to with the "ContinueIfNoTargetsFound" attribute. + ------------------------------------------------------------------------------------------------------------------------------------------------- v1.1.18.1 -------------------------------------------------------------------------------------------------------------------------------------------------