From a4a3427e4e1dff35d1de1ef8e9079ddb15641b09 Mon Sep 17 00:00:00 2001
From: Markus Isberg <3e849f2e5c@pm.me>
Date: Wed, 12 Mar 2025 12:56:27 +0000
Subject: [PATCH] Unstable 1.8.4.0
---
.../BarotraumaClient/ClientSource/Camera.cs | 15 +-
.../Characters/AI/EnemyAIController.cs | 8 +-
.../Characters/AI/HumanAIController.cs | 22 +-
.../Characters/Animation/Ragdoll.cs | 126 +-
.../ClientSource/Characters/Attack.cs | 2 +-
.../ClientSource/Characters/Character.cs | 106 +-
.../ClientSource/Characters/CharacterHUD.cs | 60 +-
.../ClientSource/Characters/CharacterInfo.cs | 36 +-
.../Characters/CharacterNetworking.cs | 74 +-
.../Characters/Health/AfflictionPsychosis.cs | 2 +-
.../Characters/Health/CharacterHealth.cs | 81 +-
.../Characters/InteractionLabelManager.cs | 17 +-
.../ClientSource/Characters/Jobs/JobPrefab.cs | 152 +-
.../ClientSource/Characters/Limb.cs | 129 +-
.../CircuitBox/CircuitBoxComponent.cs | 2 +-
.../ClientSource/CircuitBox/CircuitBoxUI.cs | 3 +-
.../ContentPackage/ModProject.cs | 4 +-
.../Transition/LegacySteamUgcTransition.cs | 2 +-
.../ClientSource/DebugConsole.cs | 384 +++-
.../Events/EventActions/ConversationAction.cs | 29 +-
.../ClientSource/Events/EventLog.cs | 1 +
.../ClientSource/Events/EventManager.cs | 27 +
.../Missions/AbandonedOutpostMission.cs | 20 +-
.../Events/Missions/CombatMission.cs | 100 +-
.../ClientSource/Events/Missions/Mission.cs | 12 +-
.../Events/Missions/MissionPrefab.cs | 4 +-
.../Events/Missions/SalvageMission.cs | 58 +-
.../Events/Missions/ScanMission.cs | 17 +-
.../ClientSource/Fonts/ScalableFont.cs | 75 +-
.../ClientSource/GUI/ChatBox.cs | 50 +-
.../ClientSource/GUI/DeathPrompt.cs | 70 +-
.../ClientSource/GUI/FileSelection.cs | 55 +-
.../BarotraumaClient/ClientSource/GUI/GUI.cs | 141 +-
.../ClientSource/GUI/GUIButton.cs | 80 +-
.../ClientSource/GUI/GUIComponent.cs | 77 +-
.../ClientSource/GUI/GUIDropDown.cs | 48 +-
.../ClientSource/GUI/GUIListBox.cs | 31 +-
.../ClientSource/GUI/GUIMessageBox.cs | 2 +-
.../ClientSource/GUI/GUIStyle.cs | 12 +-
.../ClientSource/GUI/GUITextBox.cs | 7 +
.../ClientSource/GUI/HRManagerUI.cs | 268 ++-
.../ClientSource/GUI/Store.cs | 42 +-
.../ClientSource/GUI/SubmarineSelection.cs | 4 +-
.../ClientSource/GUI/TabMenu.cs | 282 ++-
.../ClientSource/GUI/TalentMenu.cs | 206 +-
.../ClientSource/GUI/UpgradeStore.cs | 56 +-
.../BarotraumaClient/ClientSource/GameMain.cs | 54 +-
.../ClientSource/GameSession/CargoManager.cs | 2 +-
.../ClientSource/GameSession/CrewManager.cs | 191 +-
.../GameSession/GameModes/CampaignMode.cs | 6 +-
.../GameModes/MultiPlayerCampaign.cs | 87 +-
.../GameModes/SinglePlayerCampaign.cs | 10 +-
.../GameSession/GameModes/TestGameMode.cs | 37 +-
.../GameModes/Tutorials/Tutorial.cs | 4 +-
.../ClientSource/GameSession/GameSession.cs | 28 +-
.../ClientSource/GameSession/HintManager.cs | 4 +-
.../ClientSource/GameSession/PvPMode.cs | 84 +
.../ClientSource/GameSession/RoundSummary.cs | 132 +-
.../ClientSource/Items/CharacterInventory.cs | 21 +-
.../ClientSource/Items/Components/Door.cs | 7 +
.../Items/Components/GeneticMaterial.cs | 38 +-
.../Items/Components/Holdable/Holdable.cs | 8 +-
.../Items/Components/Holdable/RangedWeapon.cs | 35 +-
.../Items/Components/Holdable/Sprayer.cs | 1 +
.../Items/Components/ItemComponent.cs | 106 +-
.../Items/Components/ItemContainer.cs | 107 +-
.../Items/Components/ItemLabel.cs | 2 +-
.../Items/Components/LightComponent.cs | 28 +-
.../Items/Components/Machines/Controller.cs | 45 +-
.../Components/Machines/Deconstructor.cs | 25 +-
.../Items/Components/Machines/Fabricator.cs | 457 +++--
.../Items/Components/Machines/MiniMap.cs | 169 +-
.../Items/Components/Machines/Reactor.cs | 5 +-
.../Items/Components/Machines/Sonar.cs | 109 +-
.../Items/Components/Machines/Steering.cs | 53 +-
.../Items/Components/Projectile.cs | 8 +-
.../Items/Components/RemoteController.cs | 1 +
.../Items/Components/RepairTool.cs | 29 +-
.../Items/Components/Repairable.cs | 2 +
.../ClientSource/Items/Components/Rope.cs | 10 +-
.../Items/Components/Signal/ButtonTerminal.cs | 17 +-
.../Items/Components/Signal/CircuitBox.cs | 20 +-
.../Components/Signal/CustomInterface.cs | 226 ++-
.../Items/Components/Signal/Terminal.cs | 62 +-
.../Items/Components/Signal/WifiComponent.cs | 7 +-
.../Items/Components/Signal/Wire.cs | 9 +-
.../Items/Components/StatusHUD.cs | 69 +-
.../Items/Components/TriggerComponent.cs | 18 +-
.../ClientSource/Items/Components/Turret.cs | 44 +-
.../ClientSource/Items/Inventory.cs | 5 +-
.../ClientSource/Items/Item.cs | 126 +-
.../BarotraumaClient/ClientSource/Map/Gap.cs | 11 +-
.../BarotraumaClient/ClientSource/Map/Hull.cs | 34 +-
.../BackgroundCreatures/BackgroundCreature.cs | 76 +-
.../BackgroundCreatureManager.cs | 48 +-
.../BackgroundCreaturePrefab.cs | 117 +-
.../ClientSource/Map/Levels/CaveGenerator.cs | 124 +-
.../ClientSource/Map/Levels/Level.cs | 79 +-
.../Map/Levels/LevelObjects/LevelObject.cs | 19 +-
.../Levels/LevelObjects/LevelObjectManager.cs | 38 +-
.../ClientSource/Map/Levels/LevelRenderer.cs | 110 +-
.../ClientSource/Map/Levels/LevelWall.cs | 40 +-
.../ClientSource/Map/Lights/ConvexHull.cs | 8 +-
.../ClientSource/Map/Lights/LightManager.cs | 63 +-
.../ClientSource/Map/Lights/LightSource.cs | 53 +-
.../ClientSource/Map/Map/Map.cs | 66 +-
.../ClientSource/Map/Map/Radiation.cs | 36 +-
.../ClientSource/Map/MapEntity.cs | 22 +
.../ClientSource/Map/RoundSound.cs | 14 +-
.../ClientSource/Map/Structure.cs | 47 +-
.../ClientSource/Map/StructurePrefab.cs | 12 +-
.../ClientSource/Map/Submarine.cs | 63 +-
.../ClientSource/Map/SubmarinePreview.cs | 5 +-
.../ClientSource/Map/WayPoint.cs | 37 +-
.../ClientSource/Networking/Client.cs | 2 +-
.../ClientSource/Networking/ConnectCommand.cs | 12 +-
.../Networking/FileTransfer/FileReceiver.cs | 4 +-
.../ClientSource/Networking/GameClient.cs | 481 ++++-
.../ClientEntityEventManager.cs | 38 +-
.../P2PSocket/SteamConnectSocket.cs | 11 +-
.../Primitives/P2PSocket/SteamListenSocket.cs | 2 +-
.../Networking/Primitives/Peers/ClientPeer.cs | 23 +-
.../Primitives/Peers/LidgrenClientPeer.cs | 11 +-
.../ClientSource/Networking/RespawnManager.cs | 103 +-
.../Networking/ServerList/PingUtils.cs | 4 +-
.../Networking/ServerList/ServerInfo.cs | 32 +-
.../SteamDedicatedServerProvider.cs | 3 +-
.../ClientSource/Networking/ServerSettings.cs | 47 +-
.../Networking/ServerSettingsUI.cs | 49 +-
.../Networking/Voip/VoipCapture.cs | 76 +-
.../Networking/Voip/VoipClient.cs | 8 +-
.../ClientSource/Networking/Voting.cs | 173 +-
.../ClientSource/Particles/Particle.cs | 98 +-
.../ClientSource/Particles/ParticleEmitter.cs | 22 +-
.../ClientSource/Particles/ParticleManager.cs | 24 +-
.../ClientSource/Particles/ParticlePrefab.cs | 17 +-
.../ClientSource/Physics/PhysicsBody.cs | 126 +-
.../CampaignSetupUI/CampaignSetupUI.cs | 136 +-
.../MultiPlayerCampaignSetupUI.cs | 126 +-
.../SinglePlayerCampaignSetupUI.cs | 54 +-
.../ClientSource/Screens/CampaignUI.cs | 12 +-
.../CharacterEditor/CharacterEditorScreen.cs | 87 +-
.../Screens/CharacterEditor/Wizard.cs | 2 +-
.../Screens/EventEditor/EventEditorScreen.cs | 4 +-
.../ClientSource/Screens/GameScreen.cs | 192 +-
.../ClientSource/Screens/LevelEditorScreen.cs | 448 ++++-
.../Screens/MainMenuScreen/MainMenuScreen.cs | 43 +-
.../ClientSource/Screens/NetLobbyScreen.cs | 1663 ++++++++++++++---
.../ServerListScreen/ServerListScreen.cs | 37 +-
.../Screens/SpriteEditorScreen.cs | 78 +-
.../ClientSource/Screens/SubEditorScreen.cs | 360 +++-
.../ClientSource/Screens/TestScreen.cs | 7 +-
.../Serialization/SerializableEntityEditor.cs | 11 +-
.../ClientSource/Settings/SettingsMenu.cs | 115 +-
.../ClientSource/Sounds/OggSound.cs | 2 +-
.../ClientSource/Sounds/OpenAL/Alc.cs | 6 +-
.../ClientSource/Sounds/Sound.cs | 13 +-
.../ClientSource/Sounds/SoundChannel.cs | 10 +-
.../ClientSource/Sounds/SoundManager.cs | 101 +-
.../ClientSource/Sounds/SoundPlayer.cs | 159 +-
.../ClientSource/Sounds/SoundPrefab.cs | 14 +-
.../ClientSource/Sounds/VideoSound.cs | 2 +-
.../ClientSource/Sounds/VoipSound.cs | 2 +-
.../ClientSource/SpamServerFilter.cs | 2 +
.../ClientSource/Sprite/DecorativeSprite.cs | 70 +-
.../DeformAnimations/CustomDeformation.cs | 35 +-
.../Sprite/DeformAnimations/Inflate.cs | 2 +-
.../DeformAnimations/JointBendDeformation.cs | 2 +-
.../DeformAnimations/NoiseDeformation.cs | 2 +-
.../DeformAnimations/PositionalDeformation.cs | 2 +-
.../DeformAnimations/SpriteDeformation.cs | 18 +-
.../ClientSource/Sprite/DeformableSprite.cs | 5 +-
.../ClientSource/Sprite/Sprite.cs | 36 +-
.../StatusEffects/StatusEffect.cs | 49 +-
.../ClientSource/Steam/SteamManager.cs | 8 +-
.../ClientSource/Steam/Workshop.cs | 2 +-
.../WorkshopMenu/Mutable/InstalledTab.cs | 269 ++-
.../WorkshopMenu/Mutable/ModListPreset.cs | 2 +-
.../Mutable/MutableWorkshopMenu.cs | 5 +
.../ClientSource/Steam/WorkshopMenu/UiUtil.cs | 10 +
.../Utils/LocalizationCSVtoXML.cs | 4 +-
.../ClientSource/Utils/MathUtils.cs | 19 +
.../ClientSource/Utils/WikiImage.cs | 11 +-
.../Content/Effects/damageshader.xnb | Bin 2340 -> 2341 bytes
.../Content/Effects/damageshader_opengl.xnb | Bin 2274 -> 2274 bytes
.../BarotraumaClient/LinuxClient.csproj | 4 +-
Barotrauma/BarotraumaClient/MacClient.csproj | 4 +-
.../BarotraumaClient/Shaders/damageshader.fx | 6 +-
.../Shaders/damageshader_opengl.fx | 8 +-
.../BarotraumaClient/WindowsClient.csproj | 4 +-
.../BarotraumaServer/LinuxServer.csproj | 4 +-
Barotrauma/BarotraumaServer/MacServer.csproj | 4 +-
.../ServerSource/Characters/Character.cs | 14 +-
.../ServerSource/Characters/CharacterInfo.cs | 10 +-
.../Characters/CharacterNetworking.cs | 104 +-
.../ServerSource/Characters/Limb.cs | 70 +
.../ServerSource/DebugConsole.cs | 145 +-
.../Events/EventActions/ConversationAction.cs | 76 +-
.../ServerSource/Events/EventManager.cs | 26 +-
.../Missions/AbandonedOutpostMission.cs | 4 +-
.../Events/Missions/CombatMission.cs | 252 ++-
.../Events/Missions/SalvageMission.cs | 33 +-
.../Events/Missions/ScanMission.cs | 9 +-
.../BarotraumaServer/ServerSource/GameMain.cs | 12 +-
.../ServerSource/GameSession/CrewManager.cs | 116 +-
.../GameModes/CharacterCampaignData.cs | 11 +-
.../GameModes/MultiPlayerCampaign.cs | 88 +-
.../Items/Components/Holdable/Holdable.cs | 11 +-
.../Items/Components/ItemLabel.cs | 2 +-
.../Items/Components/Machines/Steering.cs | 16 +-
.../Items/Components/Signal/CircuitBox.cs | 10 +-
.../Components/Signal/CustomInterface.cs | 73 +-
.../Items/Components/Signal/WifiComponent.cs | 10 +-
.../ServerSource/Items/Item.cs | 25 +-
.../BarotraumaServer/ServerSource/Map/Hull.cs | 6 +
.../ServerSource/Networking/BanList.cs | 2 +-
.../ServerSource/Networking/ChatMessage.cs | 2 +-
.../ServerSource/Networking/Client.cs | 34 +-
.../Networking/FileTransfer/FileSender.cs | 4 +-
.../ServerSource/Networking/GameServer.cs | 1093 +++++++++--
.../ServerSource/Networking/KarmaManager.cs | 6 +-
.../ServerEntityEventManager.cs | 47 +-
.../Peers/Server/LidgrenServerPeer.cs | 35 +-
.../Primitives/Peers/Server/P2PServerPeer.cs | 4 +-
.../Primitives/Peers/Server/ServerPeer.cs | 13 +
.../ServerSource/Networking/RespawnManager.cs | 436 +++--
.../ServerSource/Networking/ServerSettings.cs | 107 +-
.../ServerSource/Networking/Voting.cs | 35 +-
.../ServerSource/Screens/NetLobbyScreen.cs | 35 +-
.../ServerSource/Steam/SteamManager.cs | 21 +-
.../ServerSource/Traitors/TraitorManager.cs | 11 +-
.../BarotraumaServer/WindowsServer.csproj | 4 +-
.../Data/campaignsettings.xml | 6 +-
.../Data/clientpermissions.xml | 3 -
.../Data/permissionpresets.xml | 3 +
.../Data/permissionpresets_player.xml | 36 +
.../Crawler/Animations/CrawlerRun.xml | 2 +
.../Crawler/Animations/CrawlerSwimFast.xml | 2 +
.../Crawler/Animations/CrawlerSwimSlow.xml | 2 +
.../Crawler/Animations/CrawlerWalk.xml | 2 +
.../Characters/Crawler/Crawler.xml | 77 +
.../Ragdolls/CrawlerDefaultRagdoll.xml | 127 ++
.../Characters/Crawler/crawler.png | Bin 0 -> 220731 bytes
.../Human.xml | 312 ++++
.../Mudraptor.xml | 68 +
.../README.txt | 32 +
.../Spineling_morbusine_m.xml | 1 +
.../Testcrawlerhatchling.xml | 15 +
.../Testcyborgworm_m.xml | 3 +
.../filelist.xml | 9 +
.../RotationAndFlippingTests.sub | Bin 0 -> 9607 bytes
.../filelist.xml | 4 +
.../Animations/HumanRunDivingSuit.xml | 40 +
.../Animations/HumanWalkDivingSuit.xml | 40 +
.../Testhuman/Animations/TesthumanCrouch.xml | 2 +
.../Testhuman/Animations/TesthumanRun.xml | 2 +
.../Animations/TesthumanSwimFast.xml | 2 +
.../Animations/TesthumanSwimSlow.xml | 2 +
.../Testhuman/Animations/TesthumanWalk.xml | 2 +
.../Ragdolls/TesthumanDefaultRagdoll.xml | 130 ++
.../Characters/Testhuman/Testhuman.xml | 308 +++
.../[DebugOnlyTest]Testhuman/README.txt | 8 +
.../[DebugOnlyTest]Testhuman/filelist.xml | 4 +
Barotrauma/BarotraumaShared/README.txt | 4 +-
.../SharedSource/AchievementManager.cs | 208 ++-
.../SharedSource/CachedDistance.cs | 2 +
.../Characters/AI/AIController.cs | 53 +-
.../SharedSource/Characters/AI/AITarget.cs | 2 +-
.../Characters/AI/EnemyAIController.cs | 1102 +++++++----
.../Characters/AI/HumanAIController.cs | 526 +++---
.../Characters/AI/IndoorsSteeringManager.cs | 113 +-
.../SharedSource/Characters/AI/LatchOntoAI.cs | 2 +-
.../Characters/AI/Objectives/AIObjective.cs | 9 +-
.../Objectives/AIObjectiveCheckStolenItems.cs | 56 +-
.../AI/Objectives/AIObjectiveCleanupItem.cs | 16 +-
.../AI/Objectives/AIObjectiveCleanupItems.cs | 2 +
.../AI/Objectives/AIObjectiveCombat.cs | 382 ++--
.../AI/Objectives/AIObjectiveContainItem.cs | 14 +-
.../Objectives/AIObjectiveDeconstructItem.cs | 47 +-
.../Objectives/AIObjectiveDeconstructItems.cs | 6 +-
.../Objectives/AIObjectiveEscapeHandcuffs.cs | 2 +-
.../Objectives/AIObjectiveExtinguishFire.cs | 79 +-
.../Objectives/AIObjectiveFightIntruders.cs | 4 +-
.../Objectives/AIObjectiveFindDivingGear.cs | 150 +-
.../AI/Objectives/AIObjectiveFindSafety.cs | 152 +-
.../AI/Objectives/AIObjectiveFindThieves.cs | 41 +-
.../AI/Objectives/AIObjectiveFixLeak.cs | 12 +-
.../AI/Objectives/AIObjectiveGetItem.cs | 30 +-
.../AI/Objectives/AIObjectiveGetItems.cs | 4 +-
.../AI/Objectives/AIObjectiveGoTo.cs | 211 ++-
.../AI/Objectives/AIObjectiveIdle.cs | 297 +--
.../AI/Objectives/AIObjectiveInspectNoises.cs | 3 +-
.../AI/Objectives/AIObjectiveLoadItem.cs | 23 +-
.../AI/Objectives/AIObjectiveLoadItems.cs | 2 +-
.../AI/Objectives/AIObjectiveLoop.cs | 2 +-
.../AI/Objectives/AIObjectiveManager.cs | 25 +-
...econtainItem.cs => AIObjectiveMoveItem.cs} | 43 +-
.../AI/Objectives/AIObjectiveOperateItem.cs | 102 +-
.../AI/Objectives/AIObjectivePrepare.cs | 2 +-
.../AI/Objectives/AIObjectiveRepairItem.cs | 5 +-
.../AI/Objectives/AIObjectiveRepairItems.cs | 54 +-
.../AI/Objectives/AIObjectiveRescue.cs | 57 +-
.../AI/Objectives/AIObjectiveRescueAll.cs | 23 +-
.../AI/Objectives/AIObjectiveReturn.cs | 33 +-
.../SharedSource/Characters/AI/Order.cs | 8 +-
.../SharedSource/Characters/AI/PetBehavior.cs | 62 +-
.../AI/ShipCommand/ShipIssueWorker.cs | 14 +-
.../AI/ShipCommand/ShipIssueWorkerSteer.cs | 2 +-
.../Characters/AI/Wreck/WreckAI.cs | 177 +-
.../Characters/Animation/AnimController.cs | 369 +++-
.../Animation/FishAnimController.cs | 160 +-
.../Animation/HumanoidAnimController.cs | 313 +---
.../Characters/Animation/Ragdoll.cs | 161 +-
.../SharedSource/Characters/Attack.cs | 68 +-
.../SharedSource/Characters/Character.cs | 840 +++++----
.../Characters/CharacterEventData.cs | 33 +-
.../SharedSource/Characters/CharacterInfo.cs | 163 +-
.../Characters/CharacterNetworking.cs | 17 +-
.../Characters/CharacterPrefab.cs | 7 +-
.../Health/Afflictions/Affliction.cs | 43 +-
.../Health/Afflictions/AfflictionBleeding.cs | 2 +-
.../Health/Afflictions/AfflictionHusk.cs | 120 +-
.../Health/Afflictions/AfflictionPrefab.cs | 52 +-
.../Characters/Health/CharacterHealth.cs | 124 +-
.../Characters/Health/DamageModifier.cs | 28 +-
.../SharedSource/Characters/HumanPrefab.cs | 26 +-
.../SharedSource/Characters/Jobs/Job.cs | 76 +-
.../SharedSource/Characters/Jobs/JobPrefab.cs | 106 +-
.../SharedSource/Characters/Jobs/Skill.cs | 27 +-
.../Characters/Jobs/SkillPrefab.cs | 45 +-
.../SharedSource/Characters/Limb.cs | 49 +-
.../Params/Animation/AnimationParams.cs | 65 +-
.../Characters/Params/CharacterParams.cs | 190 +-
.../Params/Ragdoll/RagdollParams.cs | 66 +-
.../SharedSource/Characters/SkillSettings.cs | 2 +-
.../AbilityConditionals/AbilityCondition.cs | 7 +-
.../AbilityConditionMission.cs | 23 +-
.../AbilityConditionCrewMemberUnconscious.cs | 2 +-
.../AbilityConditionHasDifferentJobs.cs | 5 +-
.../AbilityConditionHasSkill.cs | 11 +-
.../AbilityConditionLevelsBehindHighest.cs | 6 +-
.../AbilityConditionLowestLevel.cs | 2 +-
.../AbilityConditionNoCrewDied.cs | 8 +-
.../AbilityConditionShipFlooded.cs | 3 +-
.../Talents/Abilities/CharacterAbility.cs | 2 +-
.../CharacterAbilityApplyStatusEffects.cs | 20 +-
...ilityApplyStatusEffectsToApprenticeship.cs | 4 +-
.../CharacterAbilityGainSimultaneousSkill.cs | 2 +-
.../CharacterAbilityGiveAffliction.cs | 6 +-
.../CharacterAbilityGivePermanentStat.cs | 2 +-
...haracterAbilityGiveTalentPointsToAllies.cs | 3 +-
.../CharacterAbilityModifyStatToSkill.cs | 4 +-
.../Abilities/CharacterAbilityModifyValue.cs | 2 +
.../CharacterAbilityReduceAffliction.cs | 26 +-
.../CharacterAbilityUpgradeSubmarine.cs | 12 +-
.../CharacterAbilityByTheBook.cs | 8 +-
...erAbilityUnlockApprenticeshipTalentTree.cs | 17 +-
.../CharacterAbilityWarStories.cs | 27 +-
.../AbilityGroups/CharacterAbilityGroup.cs | 2 +-
.../Characters/Talents/TalentPrefab.cs | 7 +
.../Characters/Talents/TalentTree.cs | 6 +-
.../SharedSource/CircuitBox/CircuitBoxWire.cs | 7 +-
.../ContentFile/AfflictionsFile.cs | 11 +-
.../BackgroundCreaturePrefabsFile.cs | 25 +-
.../ContentFile/CharacterFile.cs | 26 +-
.../ContentFile/DisembarkPerkFile.cs | 17 +
.../ContentFile/RandomEventsFile.cs | 8 +-
.../ContentPackage/ContentPackage.cs | 169 +-
.../ContentPackageManager.cs | 22 +-
.../ContentManagement/ContentXElement.cs | 1 +
.../SharedSource/DebugConsole.cs | 1065 ++++++++---
.../DisembarkPerks/DisembarkPerkPrefab.cs | 58 +
.../PerkBehaviors/GiveTalentPointPerk.cs | 20 +
.../DisembarkPerks/PerkBehaviors/PerkBase.cs | 84 +
.../PerkBehaviors/SpawnItemPerk.cs | 210 +++
.../PerkBehaviors/SubItemSwapPerk.cs | 61 +
.../PerkBehaviors/UpgradeSubmarinePerk.cs | 57 +
.../BarotraumaShared/SharedSource/Enums.cs | 51 +-
.../Events/EventActions/AddScoreAction.cs | 84 +
.../EventActions/CheckConditionalAction.cs | 17 +-
.../Events/EventActions/CheckDataAction.cs | 4 +
.../Events/EventActions/CheckItemAction.cs | 5 +-
.../EventActions/CheckVisibilityAction.cs | 2 +-
.../Events/EventActions/ConversationAction.cs | 66 +-
.../Events/EventActions/EventAction.cs | 4 +
.../SharedSource/Events/EventActions/GoTo.cs | 5 +-
.../Events/EventActions/MissionAction.cs | 4 +-
.../Events/EventActions/MissionStateAction.cs | 9 +-
.../EventActions/NPCChangeTeamAction.cs | 63 +-
.../Events/EventActions/NPCFollowAction.cs | 19 +-
.../EventActions/NPCOperateItemAction.cs | 20 +-
.../Events/EventActions/NPCWaitAction.cs | 19 +-
.../Events/EventActions/RemoveItemAction.cs | 2 +-
.../Events/EventActions/SpawnAction.cs | 157 +-
.../Events/EventActions/TagAction.cs | 57 +-
.../Events/EventActions/TriggerAction.cs | 76 +-
.../Events/EventActions/TriggerEventAction.cs | 14 +-
.../Events/EventActions/UnlockPathAction.cs | 36 +-
.../EventActions/WaitForItemUsedAction.cs | 10 +-
.../SharedSource/Events/EventManager.cs | 161 +-
.../SharedSource/Events/EventPrefab.cs | 48 +-
.../SharedSource/Events/EventSet.cs | 61 +-
.../Missions/AbandonedOutpostMission.cs | 195 +-
.../Events/Missions/BeaconMission.cs | 4 +-
.../Events/Missions/CargoMission.cs | 4 +-
.../Events/Missions/CombatMission.cs | 124 +-
.../Events/Missions/EscortMission.cs | 10 +-
.../SharedSource/Events/Missions/Mission.cs | 213 ++-
.../Events/Missions/MissionPrefab.cs | 248 ++-
.../Events/Missions/PirateMission.cs | 181 +-
.../Events/Missions/SalvageMission.cs | 183 +-
.../Events/Missions/ScanMission.cs | 35 +-
.../SharedSource/Events/MonsterEvent.cs | 50 +-
.../SharedSource/Events/ScriptedEvent.cs | 90 +-
.../Extensions/IEnumerableExtensions.cs | 40 +-
.../SharedSource/ForbiddenWordFilter.cs | 2 +-
.../GameAnalytics/GameAnalyticsManager.cs | 27 +
.../GameSession/AutoItemPlacer.cs | 20 +-
.../SharedSource/GameSession/CargoManager.cs | 57 +-
.../SharedSource/GameSession/CrewManager.cs | 91 +-
.../GameSession/GameModes/CampaignMode.cs | 86 +-
.../GameSession/GameModes/CampaignSettings.cs | 18 +-
.../GameSession/GameModes/CoOpMode.cs | 5 +-
.../GameSession/GameModes/MissionMode.cs | 30 +-
.../GameModes/MultiPlayerCampaign.cs | 30 +-
.../GameSession/GameModes/PvPMode.cs | 47 +-
.../GameSession/GameModes/TestGameMode.cs | 19 +
.../SharedSource/GameSession/GameSession.cs | 710 +++++--
.../SharedSource/GameSession/HireManager.cs | 6 +-
.../SharedSource/InputType.cs | 4 +-
.../SharedSource/Items/CharacterInventory.cs | 60 +-
.../Items/Components/DockingPort.cs | 20 +-
.../SharedSource/Items/Components/Door.cs | 57 +-
.../Items/Components/ElectricalDischarger.cs | 2 +-
.../Components/EntitySpawnerComponent.cs | 44 +-
.../Items/Components/GeneticMaterial.cs | 320 +++-
.../SharedSource/Items/Components/Growable.cs | 2 +-
.../Items/Components/Holdable/Holdable.cs | 99 +-
.../Items/Components/Holdable/IdCard.cs | 8 +-
.../Components/Holdable/LevelResource.cs | 6 +-
.../Items/Components/Holdable/MeleeWeapon.cs | 48 +-
.../Items/Components/Holdable/Pickable.cs | 16 +-
.../Items/Components/Holdable/RangedWeapon.cs | 13 +-
.../Items/Components/Holdable/RepairTool.cs | 9 +-
.../Items/Components/ItemComponent.cs | 27 +-
.../Items/Components/ItemContainer.cs | 299 +--
.../SharedSource/Items/Components/Ladder.cs | 3 +-
.../Items/Components/Machines/Controller.cs | 101 +-
.../Components/Machines/Deconstructor.cs | 39 +-
.../Items/Components/Machines/Engine.cs | 4 +-
.../Items/Components/Machines/Fabricator.cs | 44 +-
.../Items/Components/Machines/MiniMap.cs | 2 +-
.../Components/Machines/OxygenGenerator.cs | 2 +-
.../Items/Components/Machines/Pump.cs | 6 +-
.../Items/Components/Machines/Reactor.cs | 14 +-
.../Items/Components/Machines/Sonar.cs | 29 +-
.../Components/Machines/SonarTransducer.cs | 2 +-
.../Items/Components/Machines/Steering.cs | 6 +-
.../SharedSource/Items/Components/Planter.cs | 3 +-
.../Items/Components/Power/PowerContainer.cs | 2 +-
.../Items/Components/Power/PowerTransfer.cs | 23 +-
.../Items/Components/Power/Powered.cs | 13 +-
.../Items/Components/Projectile.cs | 128 +-
.../Items/Components/Repairable.cs | 2 +-
.../SharedSource/Items/Components/Rope.cs | 130 +-
.../Items/Components/Signal/ButtonTerminal.cs | 177 +-
.../Items/Components/Signal/CircuitBox.cs | 23 +-
.../Items/Components/Signal/Connection.cs | 2 +-
.../Signal/ConnectionSelectorComponent.cs | 122 ++
.../Components/Signal/CustomInterface.cs | 216 ++-
.../Signal/DemultiplexerComponent.cs | 57 +
.../Items/Components/Signal/LightComponent.cs | 29 +-
.../Items/Components/Signal/MotionSensor.cs | 132 +-
.../Components/Signal/MultiplexerComponent.cs | 59 +
.../Items/Components/Signal/OxygenDetector.cs | 12 +-
.../Items/Components/Signal/RelayComponent.cs | 1 -
.../Items/Components/Signal/SmokeDetector.cs | 7 +-
.../Items/Components/Signal/Terminal.cs | 16 +-
.../Items/Components/Signal/WifiComponent.cs | 39 +-
.../Items/Components/Signal/Wire.cs | 13 +
.../Items/Components/TriggerComponent.cs | 226 ++-
.../SharedSource/Items/Components/Turret.cs | 110 +-
.../SharedSource/Items/Components/Wearable.cs | 15 +-
.../SharedSource/Items/ContainerTagPrefab.cs | 19 +-
.../SharedSource/Items/Inventory.cs | 68 +-
.../SharedSource/Items/Item.cs | 488 ++++-
.../SharedSource/Items/ItemEventData.cs | 16 +-
.../SharedSource/Items/ItemInventory.cs | 2 +-
.../SharedSource/Items/ItemPrefab.cs | 45 +-
.../SharedSource/Items/RelatedItem.cs | 22 +-
.../Map/Creatures/BallastFloraBehavior.cs | 3 +-
.../SharedSource/Map/Entity.cs | 7 +-
.../SharedSource/Map/Explosion.cs | 30 +-
.../BarotraumaShared/SharedSource/Map/Gap.cs | 99 +-
.../BarotraumaShared/SharedSource/Map/Hull.cs | 8 +-
.../SharedSource/Map/IDamageable.cs | 1 -
.../SharedSource/Map/ISpatialEntity.cs | 112 +-
.../SharedSource/Map/ItemAssemblyPrefab.cs | 3 +-
.../SharedSource/Map/Levels/Biome.cs | 8 +-
.../SharedSource/Map/Levels/CaveGenerator.cs | 110 +-
.../Map/Levels/DestructibleLevelWall.cs | 2 +-
.../SharedSource/Map/Levels/Level.cs | 644 +++++--
.../SharedSource/Map/Levels/LevelData.cs | 83 +-
.../Map/Levels/LevelGenerationParams.cs | 86 +-
.../Levels/LevelObjects/LevelObjectManager.cs | 31 +-
.../Levels/LevelObjects/LevelObjectPrefab.cs | 17 +-
.../Map/Levels/LevelObjects/LevelTrigger.cs | 65 +-
.../SharedSource/Map/Map/Location.cs | 187 +-
.../SharedSource/Map/Map/LocationType.cs | 18 +-
.../SharedSource/Map/Map/Map.cs | 127 +-
.../Map/Map/MapGenerationParams.cs | 15 +-
.../SharedSource/Map/Map/Radiation.cs | 75 +-
.../SharedSource/Map/Map/RadiationParams.cs | 31 +-
.../SharedSource/Map/MapEntity.cs | 17 +
.../SharedSource/Map/MapEntityPrefab.cs | 12 +-
.../Map/Outposts/ExtraSubmarineInfo.cs | 44 +-
.../SharedSource/Map/Outposts/NPCSet.cs | 6 +-
.../Map/Outposts/OutpostGenerationParams.cs | 126 +-
.../Map/Outposts/OutpostGenerator.cs | 378 +++-
.../SharedSource/Map/PriceInfo.cs | 18 +-
.../SharedSource/Map/Structure.cs | 45 +-
.../SharedSource/Map/Submarine.cs | 168 +-
.../SharedSource/Map/SubmarineBody.cs | 28 +-
.../SharedSource/Map/SubmarineInfo.cs | 43 +-
.../SharedSource/Map/WayPoint.cs | 78 +-
.../SharedSource/Networking/ChatMessage.cs | 22 +-
.../SharedSource/Networking/Client.cs | 22 +-
.../Networking/ClientPermissions.cs | 10 +-
.../SharedSource/Networking/EntitySpawner.cs | 62 +-
.../SharedSource/Networking/NetConfig.cs | 14 +-
.../NetEntityEvent/NetEntityEventManager.cs | 16 +-
.../SharedSource/Networking/NetworkMember.cs | 20 +-
.../Networking/OrderChatMessage.cs | 4 +-
.../NetworkConnection/EosP2PConnection.cs | 2 +
.../NetworkConnection/LidgrenConnection.cs | 5 +
.../NetworkConnection/NetworkConnection.cs | 5 +
.../NetworkConnection/PipeConnection.cs | 3 +
.../NetworkConnection/SteamP2PConnection.cs | 5 +-
.../SharedSource/Networking/RespawnManager.cs | 412 ++--
.../SharedSource/Networking/ServerLog.cs | 5 +-
.../SharedSource/Networking/ServerSettings.cs | 228 ++-
.../SharedSource/PerformanceCounter.cs | 45 +-
.../SharedSource/Physics/Physics.cs | 1 +
.../SharedSource/Physics/PhysicsBody.cs | 37 +-
.../Prefabs/IImplementsVariants.cs | 44 +-
.../SharedSource/ProcGen/VoronoiElements.cs | 54 +-
.../SharedSource/Screens/GameScreen.cs | 45 +-
.../SharedSource/Screens/NetLobbyScreen.cs | 4 +
.../Editable/ConditionallyEditable.cs | 11 +-
.../Serialization/Editable/Editable.cs | 10 +-
.../SerializableProperty.cs | 61 +-
.../Serialization/XMLExtensions.cs | 82 +-
.../SharedSource/Settings/GameSettings.cs | 16 +-
.../SharedSource/Sprite/ConditionalSprite.cs | 9 +-
.../SharedSource/Sprite/Sprite.cs | 4 -
.../StatusEffects/DelayedEffect.cs | 39 +-
.../StatusEffects/PropertyConditional.cs | 142 +-
.../StatusEffects/StatusEffect.cs | 386 +++-
.../SharedSource/Steam/SteamManager.cs | 14 +-
.../SharedSource/Steam/Workshop.cs | 3 +-
.../BarotraumaShared/SharedSource/Tags.cs | 47 +-
.../SharedSource/Text/TextManager.cs | 66 +-
.../SharedSource/Text/TextPack.cs | 13 +-
.../Traitors/TraitorEventPrefab.cs | 13 +-
.../SharedSource/Upgrades/UpgradePrefab.cs | 44 +-
.../SharedSource/Utils/Rand.cs | 14 +-
.../SharedSource/Utils/SafeIO.cs | 203 +-
.../SharedSource/Utils/SaveUtil.cs | 345 +++-
.../SharedSource/Utils/ToolBox.cs | 94 +-
Barotrauma/BarotraumaShared/changelog.txt | 651 ++++++-
Barotrauma/BarotraumaShared/hintmanager.xml | 1 +
.../ClientServer/ClientServerTests.cs | 129 ++
.../ClientServer/HeadlessNetworkClient.cs | 62 +
.../BarotraumaTest/CommonnessInfoTests.cs | 6 +-
.../BarotraumaTest/CoordinateSpace2DTests.cs | 5 +-
.../BarotraumaTest/EndpointParseTests.cs | 13 +-
Barotrauma/BarotraumaTest/EnumTests.cs | 49 +-
.../FabricatorQualityRollTests.cs | 8 +-
.../BarotraumaTest/GenericToolBoxTests.cs | 52 +-
...tSerializableStructImplementationChecks.cs | 7 +-
.../INetSerializableStructTests.cs | 24 +-
Barotrauma/BarotraumaTest/LinuxTest.csproj | 3 +-
Barotrauma/BarotraumaTest/MacTest.csproj | 3 +-
Barotrauma/BarotraumaTest/MathUtilsTests.cs | 56 +
Barotrauma/BarotraumaTest/NetIdUtilsTests.cs | 6 +-
.../PropertyConditionalTests.cs | 4 +-
.../SerializableDateTimeTests.cs | 6 +-
Barotrauma/BarotraumaTest/WindowsTest.csproj | 3 +-
HelperScripts/cleanup_obj.sh | 0
.../Extensions/ColorExtensions.cs | 9 +-
.../Extensions/EnumExtensions.cs | 38 +-
.../Primitives/AccountId/EpicAccountId.cs | 3 +
.../BarotraumaCore/Utils/Identifier.cs | 21 +-
.../BarotraumaCore/Utils/MathUtils.cs | 61 +-
.../BarotraumaCore/Utils/ReflectionUtils.cs | 5 +-
.../Generated/SteamStructFunctions.cs | 2 +-
.../Networking/NetAddress.cs | 35 +-
.../Facepunch.Steamworks/Structs/UgcItem.cs | 8 +-
.../Collision/DynamicTree.cs | 34 +-
.../Collision/DynamicTreeBroadPhase.cs | 2 +-
.../Collision/Shapes/CircleShape.cs | 6 +-
.../Decomposition/Seidel/MonotoneMountain.cs | 2 +-
.../Farseer Physics Engine 3.5/Common/Math.cs | 2 +-
.../Common/Maths/Complex.cs | 12 +-
.../Farseer Physics Engine 3.5/Common/Path.cs | 2 +-
.../Common/PhysicsLogic/RealExplosion.cs | 14 +-
.../Common/PhysicsLogic/SimpleExplosion.cs | 4 +-
.../PolygonManipulation/SimplifyTools.cs | 4 +-
.../Common/PolygonTools.cs | 36 +-
.../Common/Vertices.cs | 4 +-
.../Controllers/GravityController.cs | 4 +-
.../Controllers/VelocityLimitController.cs | 2 +-
.../Dynamics/Body.cs | 69 +-
.../Dynamics/ContactManager.cs | 1 -
.../Dynamics/Fixture.cs | 12 +-
.../Dynamics/Joints/Joint.cs | 4 +-
.../Fluids/1/FluidSystem1.cs | 12 +-
.../Fluids/2/FluidSystem2.cs | 6 +-
.../Farseer Physics Engine 3.5/Settings.cs | 2 +-
.../Src/MonoGame.Framework/Display.cs | 11 +
.../Src/MonoGame.Framework/GameWindow.cs | 2 +
...onoGame.Framework.Linux.NetStandard.csproj | 1 +
...onoGame.Framework.MacOS.NetStandard.csproj | 1 +
...oGame.Framework.Windows.NetStandard.csproj | 1 +
.../MonoGame.Framework/SDL/SDLGameWindow.cs | 32 +-
README.md | 2 +-
WindowsSolution.sln | 1 -
627 files changed, 29860 insertions(+), 10018 deletions(-)
create mode 100644 Barotrauma/BarotraumaClient/ClientSource/GameSession/PvPMode.cs
create mode 100644 Barotrauma/BarotraumaServer/ServerSource/Characters/Limb.cs
delete mode 100644 Barotrauma/BarotraumaShared/Data/clientpermissions.xml
create mode 100644 Barotrauma/BarotraumaShared/Data/permissionpresets_player.xml
create mode 100644 Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]Character override and variant tests/Characters/Crawler/Animations/CrawlerRun.xml
create mode 100644 Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]Character override and variant tests/Characters/Crawler/Animations/CrawlerSwimFast.xml
create mode 100644 Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]Character override and variant tests/Characters/Crawler/Animations/CrawlerSwimSlow.xml
create mode 100644 Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]Character override and variant tests/Characters/Crawler/Animations/CrawlerWalk.xml
create mode 100644 Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]Character override and variant tests/Characters/Crawler/Crawler.xml
create mode 100644 Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]Character override and variant tests/Characters/Crawler/Ragdolls/CrawlerDefaultRagdoll.xml
create mode 100644 Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]Character override and variant tests/Characters/Crawler/crawler.png
create mode 100644 Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]Character override and variant tests/Human.xml
create mode 100644 Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]Character override and variant tests/Mudraptor.xml
create mode 100644 Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]Character override and variant tests/README.txt
create mode 100644 Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]Character override and variant tests/Spineling_morbusine_m.xml
create mode 100644 Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]Character override and variant tests/Testcrawlerhatchling.xml
create mode 100644 Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]Character override and variant tests/Testcyborgworm_m.xml
create mode 100644 Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]Character override and variant tests/filelist.xml
create mode 100644 Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]RotationAndFlippingTests/RotationAndFlippingTests.sub
create mode 100644 Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]RotationAndFlippingTests/filelist.xml
create mode 100644 Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]Testhuman/Characters/Testhuman/Animations/HumanRunDivingSuit.xml
create mode 100644 Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]Testhuman/Characters/Testhuman/Animations/HumanWalkDivingSuit.xml
create mode 100644 Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]Testhuman/Characters/Testhuman/Animations/TesthumanCrouch.xml
create mode 100644 Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]Testhuman/Characters/Testhuman/Animations/TesthumanRun.xml
create mode 100644 Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]Testhuman/Characters/Testhuman/Animations/TesthumanSwimFast.xml
create mode 100644 Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]Testhuman/Characters/Testhuman/Animations/TesthumanSwimSlow.xml
create mode 100644 Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]Testhuman/Characters/Testhuman/Animations/TesthumanWalk.xml
create mode 100644 Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]Testhuman/Characters/Testhuman/Ragdolls/TesthumanDefaultRagdoll.xml
create mode 100644 Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]Testhuman/Characters/Testhuman/Testhuman.xml
create mode 100644 Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]Testhuman/README.txt
create mode 100644 Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]Testhuman/filelist.xml
rename Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/{AIObjectiveDecontainItem.cs => AIObjectiveMoveItem.cs} (73%)
create mode 100644 Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/DisembarkPerkFile.cs
create mode 100644 Barotrauma/BarotraumaShared/SharedSource/DisembarkPerks/DisembarkPerkPrefab.cs
create mode 100644 Barotrauma/BarotraumaShared/SharedSource/DisembarkPerks/PerkBehaviors/GiveTalentPointPerk.cs
create mode 100644 Barotrauma/BarotraumaShared/SharedSource/DisembarkPerks/PerkBehaviors/PerkBase.cs
create mode 100644 Barotrauma/BarotraumaShared/SharedSource/DisembarkPerks/PerkBehaviors/SpawnItemPerk.cs
create mode 100644 Barotrauma/BarotraumaShared/SharedSource/DisembarkPerks/PerkBehaviors/SubItemSwapPerk.cs
create mode 100644 Barotrauma/BarotraumaShared/SharedSource/DisembarkPerks/PerkBehaviors/UpgradeSubmarinePerk.cs
create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/AddScoreAction.cs
create mode 100644 Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/TestGameMode.cs
create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/ConnectionSelectorComponent.cs
create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/DemultiplexerComponent.cs
create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/MultiplexerComponent.cs
create mode 100644 Barotrauma/BarotraumaTest/ClientServer/ClientServerTests.cs
create mode 100644 Barotrauma/BarotraumaTest/ClientServer/HeadlessNetworkClient.cs
mode change 100644 => 100755 HelperScripts/cleanup_obj.sh
create mode 100644 Libraries/MonoGame.Framework/Src/MonoGame.Framework/Display.cs
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Camera.cs b/Barotrauma/BarotraumaClient/ClientSource/Camera.cs
index c497c157f..66429655b 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Camera.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Camera.cs
@@ -63,6 +63,12 @@ namespace Barotrauma
private float prevZoom;
public float Shake;
+
+ ///
+ /// Should the camera's transform matrices be automatically updated to match the screen resolution?
+ ///
+ public bool AutoUpdateToScreenResolution = true;
+
public Vector2 ShakePosition { get; private set; }
private float shakeTimer;
@@ -198,10 +204,13 @@ namespace Barotrauma
public void UpdateTransform(bool interpolate = true, bool updateListener = true)
{
- if (GameMain.GraphicsWidth != Resolution.X ||
- GameMain.GraphicsHeight != Resolution.Y)
+ if (AutoUpdateToScreenResolution)
{
- CreateMatrices();
+ if (GameMain.GraphicsWidth != Resolution.X ||
+ GameMain.GraphicsHeight != Resolution.Y)
+ {
+ CreateMatrices();
+ }
}
Vector2 interpolatedPosition = interpolate ? Timing.Interpolate(prevPosition, position) : position;
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/AI/EnemyAIController.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/AI/EnemyAIController.cs
index 9ecd47a4e..bdc1abf4b 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Characters/AI/EnemyAIController.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/AI/EnemyAIController.cs
@@ -42,13 +42,13 @@ namespace Barotrauma
if (wallTarget != null && !IsCoolDownRunning)
{
Vector2 wallTargetPos = wallTarget.Position;
- if (wallTarget.Structure.Submarine != null) { wallTargetPos += wallTarget.Structure.Submarine.Position; }
+ if (wallTarget.Structure.Submarine != null) { wallTargetPos += wallTarget.Structure.Submarine.DrawPosition; }
wallTargetPos.Y = -wallTargetPos.Y;
GUI.DrawRectangle(spriteBatch, wallTargetPos - new Vector2(10.0f, 10.0f), new Vector2(20.0f, 20.0f), Color.Orange, false);
GUI.DrawLine(spriteBatch, pos, wallTargetPos, Color.Orange * 0.5f, 0, 5);
}
GUI.DrawString(spriteBatch, pos - Vector2.UnitY * 60.0f, $"{SelectedAiTarget.Entity}", GUIStyle.Red, Color.Black);
- GUI.DrawString(spriteBatch, pos - Vector2.UnitY * 40.0f, $"{targetValue.FormatZeroDecimal()} (M: {SelectedTargetMemory?.Priority.FormatZeroDecimal()}, P: {SelectedTargetingParams?.Priority.FormatZeroDecimal()})", GUIStyle.Red, Color.Black);
+ GUI.DrawString(spriteBatch, pos - Vector2.UnitY * 40.0f, $"{targetValue.FormatZeroDecimal()} (M: {CurrentTargetMemory?.Priority.FormatZeroDecimal()}, P: {CurrentTargetingParams?.Priority.FormatZeroDecimal()})", GUIStyle.Red, Color.Black);
}
/*GUIStyle.Font.DrawString(spriteBatch, targetValue.ToString(), pos - Vector2.UnitY * 80.0f, GUIStyle.Red);
@@ -73,7 +73,7 @@ namespace Barotrauma
}
GUI.DrawString(spriteBatch, pos - Vector2.UnitY * 80.0f, State.ToString(), stateColor, Color.Black);
- if (State == AIState.Attack && selectedTargetingParams != null && selectedTargetingParams.AttackPattern == AttackPattern.Circle)
+ if (State == AIState.Attack && currentTargetingParams != null && currentTargetingParams.AttackPattern == AttackPattern.Circle)
{
GUI.DrawString(spriteBatch, pos - Vector2.UnitY * 100.0f, CirclePhase.ToString(), stateColor, Color.Black);
}
@@ -134,8 +134,8 @@ namespace Barotrauma
//GUI.DrawLine(spriteBatch, pos, ConvertUnits.ToDisplayUnits(steeringManager.AvoidLookAheadPos.X, -steeringManager.AvoidLookAheadPos.Y), Color.Orange, width: 4);
}
}
+ GUI.DrawLine(spriteBatch, pos, pos + ConvertUnits.ToDisplayUnits(new Vector2(Steering.X, -Steering.Y)), Color.Blue, width: 4);
GUI.DrawLine(spriteBatch, pos, pos + ConvertUnits.ToDisplayUnits(new Vector2(Character.AnimController.TargetMovement.X, -Character.AnimController.TargetMovement.Y)), Color.SteelBlue, width: 2);
- GUI.DrawLine(spriteBatch, pos, pos + ConvertUnits.ToDisplayUnits(new Vector2(Steering.X, -Steering.Y)), Color.Blue, width: 3);
}
}
}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/AI/HumanAIController.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/AI/HumanAIController.cs
index 672fffbb7..c70862a41 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Characters/AI/HumanAIController.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/AI/HumanAIController.cs
@@ -1,6 +1,7 @@
using Microsoft.Xna.Framework;
using FarseerPhysics;
using System;
+using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
@@ -11,7 +12,7 @@ namespace Barotrauma
{
if (Character == Character.Controlled) { return; }
if (!DebugAI) { return; }
- Vector2 pos = Character.WorldPosition;
+ Vector2 pos = Character.DrawPosition;
pos.Y = -pos.Y;
Vector2 textOffset = new Vector2(-40, -160);
textOffset.Y -= Math.Max(ObjectiveManager.CurrentOrders.Count - 1, 0) * 20;
@@ -63,6 +64,25 @@ namespace Barotrauma
stringDrawPos += new Vector2(0, 20);
GUI.DrawString(spriteBatch, stringDrawPos, $"ACTIVE OBJECTIVE: {activeObjective.DebugTag} ({activeObjective.Priority.FormatZeroDecimal()})", Color.White, Color.Black);
}
+ if (currentObjective is AIObjectiveCombat
+ {
+ Weapon: Item weapon,
+ BlockedPositions: List blockedPositions
+ })
+ {
+ Vector2 weaponPos = weapon.DrawPosition;
+ weaponPos.Y = -weaponPos.Y;
+ foreach (Vector2 blockedPosition in blockedPositions)
+ {
+ Vector2 blockedPos = blockedPosition;
+ if (Character.Submarine != null)
+ {
+ blockedPos += Character.Submarine.DrawPosition;
+ }
+ blockedPos.Y = -blockedPos.Y;
+ GUI.DrawLine(spriteBatch, weaponPos, blockedPos, Color.Red);
+ }
+ }
}
Vector2 objectiveStringDrawPos = stringDrawPos + new Vector2(120, 40);
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Animation/Ragdoll.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Animation/Ragdoll.cs
index e6b91667c..2d61423aa 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Animation/Ragdoll.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Animation/Ragdoll.cs
@@ -23,13 +23,13 @@ namespace Barotrauma
partial void UpdateNetPlayerPositionProjSpecific(float deltaTime, float lowestSubPos)
{
- if (character != GameMain.Client.Character || !character.CanMove)
+ if (character != GameMain.Client.Character)
{
//remove states without a timestamp (there may still be ID-based states
//in the list when the controlled character switches to timestamp-based interpolation)
character.MemState.RemoveAll(m => m.Timestamp == 0.0f);
- //use simple interpolation for other players' characters and characters that can't move
+ //use simple interpolation for other players' characters
if (character.MemState.Count > 0)
{
CharacterStateInfo serverPos = character.MemState.Last();
@@ -93,6 +93,9 @@ namespace Barotrauma
character.AnimController.Anim = AnimController.Animation.None;
}
+ character.AnimController.IgnorePlatforms = character.MemState[0].IgnorePlatforms;
+ character.AnimController.overrideTargetMovement = character.MemState[0].TargetMovement;
+
Vector2 newVelocity = Collider.LinearVelocity;
Vector2 newPosition = Collider.SimPosition;
float newRotation = Collider.Rotation;
@@ -103,16 +106,17 @@ namespace Barotrauma
{
newVelocity = newVelocity.ClampLength(100.0f);
if (!MathUtils.IsValid(newVelocity)) { newVelocity = Vector2.Zero; }
- overrideTargetMovement = newVelocity.LengthSquared() > 0.01f ? newVelocity : Vector2.Zero;
Collider.LinearVelocity = newVelocity;
Collider.AngularVelocity = newAngularVelocity;
}
float distSqrd = Vector2.DistanceSquared(newPosition, Collider.SimPosition);
- float errorTolerance = character.CanMove && (!character.IsRagdolled || character.AnimController.IsHangingWithRope) ? 0.01f : 0.2f;
+ float errorTolerance =
+ ColliderControlsMovement && (!character.IsRagdolled || character.AnimController.IsHangingWithRope) ? 0.01f : 0.2f;
if (distSqrd > errorTolerance)
{
- if (distSqrd > 10.0f || !character.CanMove)
+ character.AnimController.BodyInRest = false;
+ if (distSqrd > 10.0f)
{
Collider.TargetRotation = newRotation;
if (distSqrd > 10.0f)
@@ -126,30 +130,35 @@ namespace Barotrauma
}
}
SetPosition(newPosition, lerp: distSqrd < 5.0f, ignorePlatforms: false);
+ //make sure ragdoll isn't stuck at the wrong side of a platform if the movement is controlled by the ragdoll, and the ragdoll has come to rest server-side
+ if (!ColliderControlsMovement && newVelocity.LengthSquared() < 0.01f) { TryPlatformCorrection(newPosition); }
}
- else
+ else if (ColliderControlsMovement)
{
Collider.TargetRotation = newRotation;
Collider.TargetPosition = newPosition;
Collider.MoveToTargetPosition(true);
}
- }
-
- //immobilized characters can't correct their position using AnimController movement
- // -> we need to correct it manually
- if (!character.CanMove)
- {
- float mainLimbDistSqrd = Vector2.DistanceSquared(MainLimb.PullJointWorldAnchorA, Collider.SimPosition);
- float mainLimbErrorTolerance = 0.1f;
- //if the main limb is roughly at the correct position and the collider isn't moving (much at least),
- //don't attempt to correct the position.
- if (mainLimbDistSqrd > mainLimbErrorTolerance || Collider.LinearVelocity.LengthSquared() > 0.05f)
+ else
{
- MainLimb.PullJointWorldAnchorB = Collider.SimPosition;
- MainLimb.PullJointEnabled = true;
+ float mainLimbDistSqrd = Vector2.DistanceSquared(MainLimb.PullJointWorldAnchorA, newPosition);
+ float mainLimbErrorTolerance = character == GameMain.Client.Character ? 0.25f : 0.1f;
MainLimb.body.LinearVelocity = newVelocity;
+ //if the main limb is roughly at the correct position and the collider isn't moving (much at least),
+ //don't attempt to correct the position.
+ if (mainLimbDistSqrd > mainLimbErrorTolerance)
+ {
+ MainLimb.PullJointWorldAnchorB = newPosition;
+ MainLimb.PullJointEnabled = true;
+ if (!ColliderControlsMovement && newVelocity.LengthSquared() < 0.01f) { TryPlatformCorrection(newPosition); }
+ }
}
}
+ else if (!ColliderControlsMovement)
+ {
+ //correct velocity regardless of the positional error
+ MainLimb.body.LinearVelocity = newVelocity;
+ }
}
character.MemLocalState.Clear();
}
@@ -179,12 +188,14 @@ namespace Barotrauma
}
}
- if (character.MemState.Count < 1) return;
+ if (character.MemState.Count < 1) { return; }
- overrideTargetMovement = Vector2.Zero;
+ overrideTargetMovement = null;
CharacterStateInfo serverPos = character.MemState.Last();
+ Collider.LastServerState = serverPos;
+
if (!character.isSynced)
{
SetPosition(serverPos.Position, lerp: false);
@@ -282,18 +293,65 @@ namespace Barotrauma
}
else if (errorMagnitude > 0.01f)
{
- Collider.TargetPosition = Collider.SimPosition + positionError;
- Collider.TargetRotation = Collider.Rotation + rotationError;
- Collider.MoveToTargetPosition(lerp: true);
+ if (ColliderControlsMovement)
+ {
+ Collider.TargetPosition = Collider.SimPosition + positionError;
+ Collider.TargetRotation = Collider.Rotation + rotationError;
+ Collider.MoveToTargetPosition(lerp: true);
+ }
+ else
+ {
+ float mainLimbErrorTolerance = character == GameMain.Client.Character ? 0.25f : 0.1f;
+ //if the main limb is roughly at the correct position and the collider isn't moving (much at least),
+ //don't attempt to correct the position.
+ if (errorMagnitude > mainLimbErrorTolerance)
+ {
+ MainLimb.PullJointWorldAnchorB = MainLimb.SimPosition + positionError;
+ MainLimb.PullJointEnabled = true;
+ if (serverPos.LinearVelocity.LengthSquared() < 0.01f) { TryPlatformCorrection(MainLimb.SimPosition + positionError); }
+ }
+ }
}
}
}
- if (character.MemLocalState.Count > 120) character.MemLocalState.RemoveRange(0, character.MemLocalState.Count - 120);
+ if (character.MemLocalState.Count > 120) { character.MemLocalState.RemoveRange(0, character.MemLocalState.Count - 120); }
character.MemState.Clear();
}
}
+
+ ///
+ /// Attempts to correct the ragdoll to the correct side of a platform if the server position is above the platform and some of the ragdoll's limbs below it client-side, or vice versa.
+ ///
+ private void TryPlatformCorrection(Vector2 serverPos)
+ {
+ float highestPos = limbs.Where(static l => !l.IsSevered).Max(static l => l.SimPosition.Y);
+ highestPos = Math.Max(serverPos.Y, highestPos);
+ float lowestPos = limbs.Where(static l => !l.IsSevered).Min(static l => l.SimPosition.Y);
+ lowestPos = Math.Min(serverPos.Y, lowestPos);
+
+ var platform = Submarine.PickBody(new Vector2(serverPos.X, highestPos), new Vector2(serverPos.X, lowestPos), collisionCategory: Physics.CollisionPlatform, allowInsideFixture: true);
+ if (platform == null) { return; }
+
+ int serverDir = Math.Sign(serverPos.Y - platform.Position.Y);
+ foreach (var limb in limbs)
+ {
+ if (limb.IsSevered) { continue; }
+ int limbDir = Math.Sign(limb.SimPosition.Y - platform.Position.Y);
+
+ const float Margin = 0.01f;
+
+ if (limbDir != serverDir)
+ {
+ limb.body.SetTransformIgnoreContacts(
+ new Vector2(
+ limb.SimPosition.X,
+ serverDir > 0 ? Math.Max(serverPos.Y + Margin + limb.body.GetMaxExtent(), limb.SimPosition.Y) : Math.Min(serverPos.Y - Margin - limb.body.GetMaxExtent(), limb.SimPosition.Y)),
+ limb.Rotation);
+ }
+ }
+ }
partial void ImpactProjSpecific(float impact, Body body)
{
@@ -563,15 +621,19 @@ namespace Barotrauma
void AdjustDepthOffset(Item item)
{
- if (item?.GetComponent() is { ControlCharacterPose: true, UserInCorrectPosition: true } controller && controller.User == character)
+ if (item == null) { return; }
+ foreach (var controller in item.GetComponents())
{
- if (controller.Item.SpriteDepth <= maxDepth || controller.DrawUserBehind)
+ if (controller is { ControlCharacterPose: true, UserInCorrectPosition: true } && controller.User == character)
{
- depthOffset = Math.Max(controller.Item.GetDrawDepth() + 0.0001f - minDepth, -minDepth);
- }
- else
- {
- depthOffset = Math.Max(controller.Item.GetDrawDepth() - 0.0001f - maxDepth, 0.0f);
+ if (controller.Item.SpriteDepth <= maxDepth || controller.DrawUserBehind)
+ {
+ depthOffset = Math.Max(controller.Item.GetDrawDepth() + 0.0001f - minDepth, -minDepth);
+ }
+ else
+ {
+ depthOffset = Math.Max(controller.Item.GetDrawDepth() - 0.0001f - maxDepth, 0.0f);
+ }
}
}
}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Attack.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Attack.cs
index 8f503a225..0a043c1bc 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Attack.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Attack.cs
@@ -48,7 +48,7 @@ namespace Barotrauma
if (sound != null)
{
- SoundPlayer.PlaySound(sound.Sound, worldPosition, sound.Volume, sound.Range, ignoreMuffling: sound.IgnoreMuffling, freqMult: sound.GetRandomFrequencyMultiplier());
+ SoundPlayer.PlaySound(sound, worldPosition);
}
}
}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs
index e35f32c4e..0d0b65cb1 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs
@@ -9,6 +9,7 @@ using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
+using System.Collections.Immutable;
using System.Linq;
namespace Barotrauma
@@ -27,7 +28,8 @@ namespace Barotrauma
protected float lastRecvPositionUpdateTime;
- private float hudInfoHeight = 100.0f;
+ private const float DefaultHudInfoHeight = 78.0f;
+ private float hudInfoHeight = DefaultHudInfoHeight;
private List sounds;
@@ -249,7 +251,9 @@ namespace Barotrauma
public Vector2 Position;
public Vector2 DrawPosition;
public float MoveUpAmount;
- public readonly string Text;
+ public readonly RichString Text;
+ public ImmutableArray? RichTextData { get; private set; }
+
public readonly Character Character;
public readonly Submarine Submarine;
public readonly Vector2 TextSize;
@@ -259,8 +263,10 @@ namespace Barotrauma
public SpeechBubble(Character character, float lifeTime, Color color, string text = "")
{
- Text = ToolBox.WrapText(text, GUI.IntScale(300), GUIStyle.SmallFont.GetFontForStr(text));
+ var richStr = RichString.Rich(text);
+ Text = ToolBox.WrapText(richStr.SanitizedValue, GUI.IntScale(300), GUIStyle.SmallFont.GetFontForStr(text));
TextSize = GUIStyle.SmallFont.MeasureString(Text);
+ RichTextData = richStr.RichTextData;
Character = character;
Position = GetDesiredPosition();
@@ -321,7 +327,6 @@ namespace Barotrauma
///
public void ControlLocalPlayer(float deltaTime, Camera cam, bool moveCam = true)
{
-
if (DisableControls || GUI.InputBlockingMenuOpen)
{
foreach (Key key in keys)
@@ -329,7 +334,7 @@ namespace Barotrauma
if (key == null) { continue; }
key.Reset();
}
- if (GUI.InputBlockingMenuOpen)
+ if (GUI.InputBlockingMenuOpen || ConversationAction.IsDialogOpen)
{
cursorPosition =
Position + PlayerInput.MouseSpeed.ClampLength(10.0f); //apply a little bit of movement to the cursor pos to prevent AFK kicking
@@ -416,6 +421,11 @@ namespace Barotrauma
UpdateLocalCursor(cam);
+ if (IsKeyHit(InputType.ToggleRun))
+ {
+ ToggleRun = !ToggleRun;
+ }
+
Vector2 mouseSimPos = ConvertUnits.ToSimUnits(cursorPosition);
if (GUI.PauseMenuOpen)
{
@@ -471,7 +481,7 @@ namespace Barotrauma
if (!GUI.InputBlockingMenuOpen)
{
if (SelectedItem != null &&
- (SelectedItem.ActiveHUDs.Any(ic => ic.GuiFrame != null && HUD.CloseHUD(ic.GuiFrame.Rect)) ||
+ (SelectedItem.ActiveHUDs.Any(ic => ic.GuiFrame != null && ic.CloseByClickingOutsideGUIFrame && HUD.CloseHUD(ic.GuiFrame.Rect)) ||
((ViewTarget as Item)?.Prefab.FocusOnSelected ?? false) && PlayerInput.KeyHit(Microsoft.Xna.Framework.Input.Keys.Escape)))
{
if (GameMain.Client != null)
@@ -543,7 +553,10 @@ namespace Barotrauma
{
if (attackResult.Damage <= 1.0f) { return; }
}
- PlaySound(CharacterSound.SoundType.Damage, maxInterval: 2);
+ if (AIState != AIState.PlayDead)
+ {
+ PlaySound(CharacterSound.SoundType.Damage, maxInterval: 2);
+ }
}
partial void KillProjSpecific(CauseOfDeathType causeOfDeath, Affliction causeOfDeathAffliction, bool log)
@@ -588,7 +601,6 @@ namespace Barotrauma
}
}
- sounds.ForEach(s => s.Sound?.Dispose());
sounds.Clear();
if (GameMain.GameSession?.CrewManager != null &&
@@ -814,9 +826,12 @@ namespace Barotrauma
PlaySound(CharacterSound.SoundType.Idle);
}
break;
+ case AIState.PlayDead:
+ case AIState.Freeze:
+ case AIState.Hiding:
+ break;
default:
- var petBehavior = enemyAI.PetBehavior;
- if (petBehavior != null &&
+ if (enemyAI.PetBehavior is PetBehavior petBehavior &&
(petBehavior.Happiness < petBehavior.UnhappyThreshold || petBehavior.Hunger > petBehavior.HungryThreshold))
{
PlaySound(CharacterSound.SoundType.Unhappy);
@@ -948,7 +963,9 @@ namespace Barotrauma
Controlled != this &&
Submarine != null &&
Controlled.Submarine == Submarine &&
- GameSettings.CurrentConfig.Graphics.LosMode != LosMode.None)
+ GameSettings.CurrentConfig.Graphics.LosMode != LosMode.None &&
+ //less restrictions on name tag visibility in PvP mode (always show them if the character is visible)
+ GameMain.GameSession?.GameMode is not PvPMode)
{
float yPos = Controlled.AnimController.FloorY - 1.5f;
@@ -965,15 +982,16 @@ namespace Barotrauma
Vector2 pos = DrawPosition;
pos.Y += hudInfoHeight;
- if (CurrentHull != null && DrawPosition.Y > CurrentHull.WorldRect.Y - 130.0f)
+ float paddingBelowCeiling = 30.0f;
+ if (CurrentHull != null && DrawPosition.Y + DefaultHudInfoHeight > CurrentHull.WorldRect.Y - paddingBelowCeiling)
{
- float lowerAmount = DrawPosition.Y - (CurrentHull.WorldRect.Y - 130.0f);
- hudInfoHeight = MathHelper.Lerp(hudInfoHeight, 100.0f - lowerAmount, 0.1f);
+ float lowerAmount = (DrawPosition.Y + DefaultHudInfoHeight) - (CurrentHull.WorldRect.Y - paddingBelowCeiling);
+ hudInfoHeight = MathHelper.Lerp(hudInfoHeight, DefaultHudInfoHeight - lowerAmount, 0.1f);
hudInfoHeight = Math.Max(hudInfoHeight, 20.0f);
}
else
{
- hudInfoHeight = MathHelper.Lerp(hudInfoHeight, 100.0f, 0.1f);
+ hudInfoHeight = MathHelper.Lerp(hudInfoHeight, DefaultHudInfoHeight, 0.1f);
}
pos.Y = -pos.Y;
@@ -1013,6 +1031,8 @@ namespace Barotrauma
CampaignInteractionType == CampaignMode.InteractionType.None ?
MathHelper.Clamp(1.0f - (cursorDist - (hoverRange - fadeOutRange)) / fadeOutRange, 0.2f, 1.0f) :
1.0f;
+ //full name tag visibility in PvP mode to make it easier to tell who's an enemy
+ float nameTextAlpha = GameMain.GameSession?.GameMode is PvPMode ? 1.0f : hudInfoAlpha;
if (!GUI.DisableCharacterNames && hudInfoVisible &&
(controlled == null || this != controlled.FocusedCharacter || IsPet) && cam.Zoom > 0.4f)
@@ -1030,7 +1050,7 @@ namespace Barotrauma
}
Vector2 nameSize = GUIStyle.Font.MeasureString(name);
- Vector2 namePos = new Vector2(pos.X, pos.Y - 10.0f - (5.0f / cam.Zoom)) - nameSize * 0.5f / cam.Zoom;
+ Vector2 namePos = new Vector2(pos.X, pos.Y - 5.0f - (5.0f / cam.Zoom)) - nameSize * 0.5f / cam.Zoom;
Color nameColor = GetNameColor();
Vector2 screenSize = new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
@@ -1057,7 +1077,7 @@ namespace Barotrauma
}
GUIStyle.Font.DrawString(spriteBatch, name, namePos + new Vector2(1.0f / cam.Zoom, 1.0f / cam.Zoom), Color.Black, 0.0f, Vector2.Zero, 1.0f / cam.Zoom, SpriteEffects.None, 0.001f);
- GUIStyle.Font.DrawString(spriteBatch, name, namePos, nameColor * hudInfoAlpha, 0.0f, Vector2.Zero, 1.0f / cam.Zoom, SpriteEffects.None, 0.0f);
+ GUIStyle.Font.DrawString(spriteBatch, name, namePos, nameColor * nameTextAlpha, 0.0f, Vector2.Zero, 1.0f / cam.Zoom, SpriteEffects.None, 0.0f);
if (GameMain.DebugDraw)
{
GUIStyle.Font.DrawString(spriteBatch, ID.ToString(), namePos - new Vector2(0.0f, 20.0f), Color.White);
@@ -1068,15 +1088,18 @@ namespace Barotrauma
if (petBehavior != null && !IsDead && !IsUnconscious)
{
var petStatus = petBehavior.GetCurrentStatusIndicatorType();
- var iconStyle = GUIStyle.GetComponentStyle("PetIcon." + petStatus);
- if (iconStyle != null)
+ if (petStatus != PetBehavior.StatusIndicatorType.None)
{
- Vector2 headPos = AnimController.GetLimb(LimbType.Head)?.body?.DrawPosition ?? DrawPosition + Vector2.UnitY * 100.0f;
- Vector2 iconPos = headPos;
- iconPos.Y = -iconPos.Y;
- var icon = iconStyle.Sprites[GUIComponent.ComponentState.None].First();
- float iconScale = 30.0f / icon.Sprite.size.X / cam.Zoom;
- icon.Sprite.Draw(spriteBatch, iconPos + new Vector2(-35.0f, -25.0f), iconStyle.Color * hudInfoAlpha, scale: iconScale);
+ var iconStyle = GUIStyle.GetComponentStyle("PetIcon." + petStatus);
+ if (iconStyle != null)
+ {
+ Vector2 headPos = AnimController.GetLimb(LimbType.Head)?.body?.DrawPosition ?? DrawPosition + Vector2.UnitY * 100.0f;
+ Vector2 iconPos = headPos;
+ iconPos.Y = -iconPos.Y;
+ var icon = iconStyle.Sprites[GUIComponent.ComponentState.None].First();
+ float iconScale = 30.0f / icon.Sprite.size.X / cam.Zoom;
+ icon.Sprite.Draw(spriteBatch, iconPos + new Vector2(-35.0f, -25.0f), iconStyle.Color * hudInfoAlpha, scale: iconScale);
+ }
}
}
}
@@ -1100,7 +1123,7 @@ namespace Barotrauma
}
}
- if (Params.ShowHealthBar && CharacterHealth.DisplayedVitality < MaxVitality * 0.98f && hudInfoVisible)
+ if (Params.ShowHealthBar && CharacterHealth.DisplayedVitality < MaxVitality * 0.98f && hudInfoVisible && AIState != AIState.PlayDead && AIState != AIState.Hiding)
{
hudInfoAlpha = Math.Max(hudInfoAlpha, Math.Min(CharacterHealth.DamageOverlayTimer, 1.0f));
@@ -1175,7 +1198,7 @@ namespace Barotrauma
Vector2 bubbleSize = bubble.TextSize + Vector2.One * GUI.IntScale(15);
speechBubbleIconSliced.Draw(spriteBatch, new RectangleF(iconPos - bubbleSize / 2, bubbleSize), bubble.Color * Math.Min(bubble.LifeTime, 1.0f) * alpha);
}
- GUI.DrawString(spriteBatch, iconPos - bubble.TextSize / 2, bubble.Text, bubble.Color * Math.Min(bubble.LifeTime, 1.0f) * alpha, font: GUIStyle.SmallFont);
+ GUI.DrawStringWithColors(spriteBatch, iconPos - bubble.TextSize / 2, bubble.Text.SanitizedValue, bubble.Color * Math.Min(bubble.LifeTime, 1.0f) * alpha, bubble.RichTextData, font: GUIStyle.SmallFont);
}
spriteBatch.End();
}
@@ -1233,7 +1256,7 @@ namespace Barotrauma
public Color GetNameColor()
{
CharacterTeamType team = teamID;
- if (Info?.IsDisguisedAsAnother != null)
+ if (Info is { IsDisguisedAsAnother: true })
{
var idCard = Inventory.GetItemInLimbSlot(InvSlotType.Card)?.GetComponent();
if (idCard != null)
@@ -1249,18 +1272,22 @@ namespace Barotrauma
}
}
+ CharacterTeamType myTeam =
+ Controlled?.TeamID ??
+ GameMain.Client?.MyClient?.TeamID ??
+ CharacterTeamType.Team1;
+
Color nameColor = GUIStyle.TextColorNormal;
- if (Controlled != null && team != Controlled.TeamID)
+ if (TeamID == CharacterTeamType.FriendlyNPC)
{
- if (TeamID == CharacterTeamType.FriendlyNPC)
- {
- nameColor = UniqueNameColor ?? Color.SkyBlue;
- }
- else
- {
- nameColor = GUIStyle.Red;
- }
+ nameColor = UniqueNameColor ?? Color.SkyBlue;
}
+ else if (team != myTeam)
+ {
+ //opposing team is red when controlling a character
+ nameColor = GUIStyle.Red;
+ }
+
return nameColor;
}
@@ -1417,7 +1444,10 @@ namespace Barotrauma
partial void OnTalentGiven(TalentPrefab talentPrefab)
{
- AddMessage(TextManager.Get("talentname." + talentPrefab.Identifier).Value, GUIStyle.Yellow, playSound: this == Controlled);
+ if (!talentPrefab.IsHiddenExtraTalent)
+ {
+ AddMessage(TextManager.Get("talentname." + talentPrefab.Identifier).Value, GUIStyle.Yellow, playSound: this == Controlled);
+ }
}
}
}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterHUD.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterHUD.cs
index 633630298..8c95ece49 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterHUD.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterHUD.cs
@@ -11,17 +11,15 @@ namespace Barotrauma
{
partial class CharacterHUD
{
- const float BossHealthBarDuration = 120.0f;
-
- abstract class BossProgressBar
+ abstract class ProgressBar
{
public float FadeTimer;
public readonly GUIComponent TopContainer;
public readonly GUIComponent SideContainer;
- public readonly GUIProgressBar TopHealthBar;
- public readonly GUIProgressBar SideHealthBar;
+ public readonly GUIProgressBar TopBar;
+ public readonly GUIProgressBar SideBar;
public abstract bool Completed { get; }
@@ -33,9 +31,9 @@ namespace Barotrauma
public abstract Color Color { get; }
- public BossProgressBar(LocalizedString label)
+ public ProgressBar(LocalizedString label, float fadeTimer = 120.0f)
{
- FadeTimer = BossHealthBarDuration;
+ FadeTimer = fadeTimer;
TopContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.18f, 0.03f), HUDFrame.RectTransform, Anchor.TopCenter)
{
@@ -43,25 +41,25 @@ namespace Barotrauma
RelativeOffset = new Vector2(0.0f, 0.01f)
}, isHorizontal: false, childAnchor: Anchor.TopCenter);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.4f), TopContainer.RectTransform), label, textAlignment: Alignment.Center, textColor: GUIStyle.Red);
- TopHealthBar = new GUIProgressBar(new RectTransform(new Vector2(1.0f, 0.6f), TopContainer.RectTransform)
+ TopBar = new GUIProgressBar(new RectTransform(new Vector2(1.0f, 0.6f), TopContainer.RectTransform)
{
MinSize = new Point(100, HUDLayoutSettings.HealthBarArea.Size.Y)
}, barSize: 0.0f, style: "CharacterHealthBarCentered")
{
Color = GUIStyle.Red
};
- CreateNumberText(TopHealthBar);
+ CreateNumberText(TopBar);
SideContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), bossHealthContainer.RectTransform)
{
MinSize = new Point(80, 60)
}, isHorizontal: false, childAnchor: Anchor.TopRight);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.3f), SideContainer.RectTransform), label, textAlignment: Alignment.CenterRight, textColor: GUIStyle.Red);
- SideHealthBar = new GUIProgressBar(new RectTransform(new Vector2(1.0f, 0.7f), SideContainer.RectTransform), barSize: 0.0f, style: "CharacterHealthBar")
+ SideBar = new GUIProgressBar(new RectTransform(new Vector2(1.0f, 0.7f), SideContainer.RectTransform), barSize: 0.0f, style: "CharacterHealthBar")
{
Color = GUIStyle.Red
};
- CreateNumberText(SideHealthBar);
+ CreateNumberText(SideBar);
TopContainer.Visible = SideContainer.Visible = false;
TopContainer.CanBeFocused = false;
@@ -88,7 +86,7 @@ namespace Barotrauma
public abstract bool IsDuplicate(object targetObject);
}
- class BossHealthBar : BossProgressBar
+ class HealthBar : ProgressBar
{
public readonly Character Character;
@@ -104,7 +102,7 @@ namespace Barotrauma
public override string NumberToDisplay => string.Empty;
- public BossHealthBar(Character character) : base(character.DisplayName)
+ public HealthBar(Character character) : base(character.DisplayName)
{
Character = character;
}
@@ -115,7 +113,7 @@ namespace Barotrauma
}
}
- class MissionProgressBar : BossProgressBar
+ class MissionProgressBar : ProgressBar
{
public readonly Mission Mission;
@@ -125,13 +123,13 @@ namespace Barotrauma
public override bool Interrupted => Mission.Failed || GameMain.GameSession?.Missions == null || !GameMain.GameSession.Missions.Contains(Mission);
- public override Color Color => GUIStyle.Red;
+ public override Color Color => Mission.Prefab.ProgressBarColor;
public override string NumberToDisplay => Mission.Prefab.ShowProgressInNumbers ?
$"{Mission.State}/{Mission.Prefab.MaxProgressState}" :
string.Empty;
- public MissionProgressBar(Mission mission) : base(mission.Prefab.ProgressBarLabel)
+ public MissionProgressBar(Mission mission) : base(mission.Prefab.ProgressBarLabel, fadeTimer: float.PositiveInfinity)
{
Mission = mission;
}
@@ -150,7 +148,7 @@ namespace Barotrauma
private static readonly List- brokenItems = new List
- ();
private static float brokenItemsCheckTimer;
- private static readonly List bossProgressBars = new List();
+ private static readonly List bossProgressBars = new List();
private static readonly Dictionary cachedHudTexts = new Dictionary();
private static LanguageIdentifier cachedHudTextLanguage = LanguageIdentifier.None;
@@ -394,6 +392,7 @@ namespace Barotrauma
foreach (var target in mission.HudIconTargets)
{
if (target.Submarine != character.Submarine) { continue; }
+ if (target.Removed) { continue; }
float alpha = GetDistanceBasedIconAlpha(target, maxDistance: mission.Prefab.HudIconMaxDistance);
if (alpha <= 0.0f) { continue; }
GUI.DrawIndicator(spriteBatch, target.DrawPosition, cam, 100.0f, mission.Prefab.HudIcon, mission.Prefab.HudIconColor * alpha);
@@ -564,7 +563,7 @@ namespace Barotrauma
float alpha = MathHelper.Lerp(0.3f, 1.0f, distFactor);
GUI.DrawIndicator(
spriteBatch,
- entity.WorldPosition,
+ entity.DrawPosition,
cam,
visibleRange,
style.GetDefaultSprite(),
@@ -592,7 +591,7 @@ namespace Barotrauma
if (Vector2.DistanceSquared(character.Position, item.Position) > 500f * 500f) { continue; }
var body = Submarine.CheckVisibility(character.SimPosition, item.SimPosition, ignoreLevel: true);
if (body != null && body.UserData as Item != item) { continue; }
- GUI.DrawIndicator(spriteBatch, item.WorldPosition + new Vector2(0f, item.RectHeight * 0.65f), cam, new Range(-100f, 500.0f), item.IconStyle.GetDefaultSprite(), item.IconStyle.Color, createOffset: false);
+ GUI.DrawIndicator(spriteBatch, item.DrawPosition + new Vector2(0f, item.RectHeight * 0.65f), cam, new Range(-100f, 500.0f), item.IconStyle.GetDefaultSprite(), item.IconStyle.Color, createOffset: false);
}
}
@@ -752,7 +751,8 @@ namespace Barotrauma
}
textPos.X += 10.0f * GUI.Scale;
- if (!character.FocusedCharacter.IsIncapacitated && character.FocusedCharacter.IsPet)
+ if (!character.FocusedCharacter.IsIncapacitated && character.FocusedCharacter.IsPet &&
+ character.FocusedCharacter.AIController is EnemyAIController enemyAI && enemyAI.PetBehavior.CanPlayWith(character))
{
GUI.DrawString(spriteBatch, textPos, GetCachedHudText("PlayHint", InputType.Use),
GUIStyle.Green, Color.Black, 2, GUIStyle.SmallFont);
@@ -773,7 +773,7 @@ namespace Barotrauma
GUIStyle.Green, Color.Black, 2, GUIStyle.SmallFont);
textPos.Y += textSize.Y;
}
- if (!character.FocusedCharacter.CustomInteractHUDText.IsNullOrEmpty() && character.FocusedCharacter.AllowCustomInteract)
+ if (character.FocusedCharacter.ShouldShowCustomInteractText)
{
GUI.DrawString(spriteBatch, textPos, character.FocusedCharacter.CustomInteractHUDText, GUIStyle.Green, Color.Black, 2, GUIStyle.SmallFont);
textPos.Y += textSize.Y;
@@ -784,7 +784,7 @@ namespace Barotrauma
{
if (character == null || character.IsDead || character.Removed) { return; }
if (bossProgressBars.Any(b => b.IsDuplicate(character))) { return; }
- AddBossProgressBar(new BossHealthBar(character));
+ AddBossProgressBar(new HealthBar(character));
}
public static void ShowMissionProgressBar(Mission mission)
@@ -803,26 +803,26 @@ namespace Barotrauma
bossProgressBars.Clear();
}
- private static void RemoveBossProgressBar(BossProgressBar progressBar)
+ private static void RemoveBossProgressBar(ProgressBar progressBar)
{
progressBar.SideContainer.Parent?.RemoveChild(progressBar.SideContainer);
progressBar.TopContainer.Parent?.RemoveChild(progressBar.TopContainer);
bossProgressBars.Remove(progressBar);
}
- private static void AddBossProgressBar(BossProgressBar progressBar)
+ private static void AddBossProgressBar(ProgressBar progressBar)
{
var healthBarMode = GameMain.NetworkMember?.ServerSettings.ShowEnemyHealthBars ?? GameSettings.CurrentConfig.ShowEnemyHealthBars;
- if (healthBarMode == EnemyHealthBarMode.HideAll)
+ if (healthBarMode == EnemyHealthBarMode.HideAll && progressBar is not MissionProgressBar)
{
return;
}
if (bossProgressBars.Count > 5)
{
- BossProgressBar oldestHealthBar = bossProgressBars.First();
+ ProgressBar oldestHealthBar = bossProgressBars.First();
foreach (var bar in bossProgressBars)
{
- if (bar.TopHealthBar.BarSize < oldestHealthBar.TopHealthBar.BarSize)
+ if (bar.TopBar.BarSize < oldestHealthBar.TopBar.BarSize)
{
oldestHealthBar = bar;
}
@@ -850,7 +850,7 @@ namespace Barotrauma
bossHealthBar.TopContainer.Visible = showTopBar;
bossHealthBar.SideContainer.Visible = !bossHealthBar.TopContainer.Visible;
- bossHealthBar.TopHealthBar.BarSize = bossHealthBar.SideHealthBar.BarSize = bossHealthBar.State;
+ bossHealthBar.TopBar.BarSize = bossHealthBar.SideBar.BarSize = bossHealthBar.State;
float alpha = Math.Min(bossHealthBar.FadeTimer, 1.0f);
if (bossHealthBar.TopContainer.Visible)
@@ -862,7 +862,7 @@ namespace Barotrauma
SetColor(bossHealthBar, bossHealthBar.SideContainer, alpha);
}
- static void SetColor(BossProgressBar bossHealthBar, GUIComponent container, float alpha)
+ static void SetColor(ProgressBar bossHealthBar, GUIComponent container, float alpha)
{
foreach (var component in container.GetAllChildren())
{
@@ -887,7 +887,7 @@ namespace Barotrauma
for (int i = bossProgressBars.Count - 1; i >= 0 ; i--)
{
var bossHealthBar = bossProgressBars[i];
- if (bossHealthBar.FadeTimer <= 0 || healthBarMode == EnemyHealthBarMode.HideAll)
+ if (bossHealthBar.FadeTimer <= 0 || (healthBarMode == EnemyHealthBarMode.HideAll && bossHealthBar is not MissionProgressBar))
{
bossHealthBar.SideContainer.Parent?.RemoveChild(bossHealthBar.SideContainer);
bossHealthBar.TopContainer.Parent?.RemoveChild(bossHealthBar.TopContainer);
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterInfo.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterInfo.cs
index e34cbb98f..5afb4c2d0 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterInfo.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterInfo.cs
@@ -17,7 +17,8 @@ namespace Barotrauma
private static Sprite infoAreaPortraitBG;
public bool LastControlled;
- public int CrewListIndex { get; set; } = -1;
+
+ public int CrewListIndex { get; set; } = int.MaxValue; //default to the bottom of the list
private Sprite disguisedPortrait;
private List disguisedAttachmentSprites;
@@ -32,6 +33,8 @@ namespace Barotrauma
private float tintHighlightThreshold;
private float tintHighlightMultiplier;
+ public bool ShowTalentResetPopupOnOpen = true;
+
public static void Init()
{
infoAreaPortraitBG = GUIStyle.GetComponentStyle("InfoAreaPortraitBG")?.GetDefaultSprite();
@@ -208,7 +211,7 @@ namespace Barotrauma
return frame;
}
- partial void OnSkillChanged(Identifier skillIdentifier, float prevLevel, float newLevel)
+ partial void OnSkillChanged(Identifier skillIdentifier, float prevLevel, float newLevel, bool forceNotification)
{
if (TeamID == CharacterTeamType.FriendlyNPC) { return; }
if (Character.Controlled != null && Character.Controlled.TeamID != TeamID) { return; }
@@ -226,6 +229,18 @@ namespace Barotrauma
specialIncrease ? GUIStyle.Orange : GUIStyle.Green,
playSound: Character == Character.Controlled, skillIdentifier, increase);
}
+ else if (forceNotification)
+ {
+ float change = newLevel - prevLevel;
+ if (Math.Abs(change) > 0.01f)
+ {
+ string sign = change > 0 ? "+" : "-";
+ Character?.AddMessage(
+ $"{sign}{Math.Round(change, 2)} {TextManager.Get("SkillName." + skillIdentifier).Value}",
+ specialIncrease ? GUIStyle.Orange : GUIStyle.Green,
+ playSound: Character == Character.Controlled);
+ }
+ }
}
partial void OnExperienceChanged(int prevAmount, int newAmount)
@@ -511,6 +526,17 @@ namespace Barotrauma
else
{
origin = attachment.Sprite.Origin;
+ if (spriteEffects.HasFlag(SpriteEffects.FlipHorizontally))
+ {
+ origin.X = attachment.Sprite.size.X - origin.X;
+ }
+ if (spriteEffects.HasFlag(SpriteEffects.FlipVertically))
+ {
+ origin.Y = attachment.Sprite.size.Y - origin.Y;
+ }
+ //the portrait's origin is forced to 0,0 (presumably for easier drawing on the UI?), see LoadHeadElement
+ //we need to take that into account here and draw the attachment at where the origin of the "actual" head sprite would be
+ drawPos += HeadSprite.Origin * scale;
}
float depth = attachment.Sprite.Depth;
if (attachment.InheritLimbDepth)
@@ -526,6 +552,8 @@ namespace Barotrauma
string newName = inc.ReadString();
string originalName = inc.ReadString();
bool renamingEnabled = inc.ReadBoolean();
+ BotStatus botStatus = (BotStatus)inc.ReadByte();
+ int salary = inc.ReadInt32();
int tagCount = inc.ReadByte();
HashSet tagSet = new HashSet();
for (int i = 0; i < tagCount; i++)
@@ -576,6 +604,8 @@ namespace Barotrauma
MinReputationToHire = (factionId, minReputationToHire),
RenamingEnabled = renamingEnabled
};
+ ch.BotStatus = botStatus;
+ ch.Salary = salary;
ch.RecreateHead(tagSet.ToImmutableHashSet(), hairIndex, beardIndex, moustacheIndex, faceAttachmentIndex);
ch.Head.SkinColor = skinColor;
ch.Head.HairColor = hairColor;
@@ -586,6 +616,8 @@ namespace Barotrauma
ch.ExperiencePoints = inc.ReadInt32();
ch.AdditionalTalentPoints = inc.ReadRangedInteger(0, MaxAdditionalTalentPoints);
ch.PermanentlyDead = inc.ReadBoolean();
+ ch.TalentRefundPoints = inc.ReadInt32();
+ ch.TalentResetCount = inc.ReadInt32();
return ch;
}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterNetworking.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterNetworking.cs
index ee0137f93..06e788eb3 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterNetworking.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterNetworking.cs
@@ -47,6 +47,7 @@ namespace Barotrauma
SelectedCharacter,
SelectedItem,
SelectedSecondaryItem,
+ AnimController.TargetMovement,
AnimController.Anim);
memLocalState.Add(posInfo);
@@ -56,7 +57,7 @@ namespace Barotrauma
if (IsKeyDown(InputType.Right)) newInput |= InputNetFlags.Right;
if (IsKeyDown(InputType.Up)) newInput |= InputNetFlags.Up;
if (IsKeyDown(InputType.Down)) newInput |= InputNetFlags.Down;
- if (IsKeyDown(InputType.Run)) newInput |= InputNetFlags.Run;
+ if (IsKeyDown(InputType.Run) || ToggleRun) newInput |= InputNetFlags.Run;
if (IsKeyDown(InputType.Crouch)) newInput |= InputNetFlags.Crouch;
if (IsKeyHit(InputType.Select)) newInput |= InputNetFlags.Select; //TODO: clean up the way this input is registered
if (IsKeyHit(InputType.Deselect)) newInput |= InputNetFlags.Deselect;
@@ -68,7 +69,7 @@ namespace Barotrauma
if (IsKeyDown(InputType.Attack)) newInput |= InputNetFlags.Attack;
if (IsKeyDown(InputType.Ragdoll)) newInput |= InputNetFlags.Ragdoll;
- if (AnimController.TargetDir == Direction.Left) newInput |= InputNetFlags.FacingLeft;
+ if (AnimController.Dir < 0) newInput |= InputNetFlags.FacingLeft;
Vector2 relativeCursorPos = cursorPosition - AimRefPosition;
relativeCursorPos.Normalize();
@@ -154,6 +155,9 @@ namespace Barotrauma
case TreatmentEventData _:
msg.WriteBoolean(AnimController.Anim == AnimController.Animation.CPR);
break;
+ case ConfirmRefundEventData _:
+ //do nothing
+ break;
case CharacterStatusEventData _:
//do nothing
break;
@@ -202,12 +206,16 @@ namespace Barotrauma
keys[(int)InputType.Use].Held = useInput;
keys[(int)InputType.Use].SetState(false, useInput);
- bool crouching = msg.ReadBoolean();
if (AnimController is HumanoidAnimController)
{
+ bool crouching = msg.ReadBoolean();
keys[(int)InputType.Crouch].Held = crouching;
keys[(int)InputType.Crouch].SetState(false, crouching);
}
+ else if (AnimController is FishAnimController fishAnim)
+ {
+ fishAnim.Reverse = msg.ReadBoolean();
+ }
bool attackInput = msg.ReadBoolean();
keys[(int)InputType.Attack].Held = attackInput;
@@ -255,17 +263,22 @@ namespace Barotrauma
msg.ReadRangedSingle(-MaxVel, MaxVel, 12));
linearVelocity = NetConfig.Quantize(linearVelocity, -MaxVel, MaxVel, 12);
+ Vector2 targetMovement = new Vector2(
+ msg.ReadRangedSingle(-Ragdoll.MAX_SPEED, Ragdoll.MAX_SPEED, 12),
+ msg.ReadRangedSingle(-Ragdoll.MAX_SPEED, Ragdoll.MAX_SPEED, 12));
+ targetMovement = NetConfig.Quantize(targetMovement, -Ragdoll.MAX_SPEED, Ragdoll.MAX_SPEED, 12);
+
bool fixedRotation = msg.ReadBoolean();
float? rotation = null;
float? angularVelocity = null;
if (!fixedRotation)
{
rotation = msg.ReadSingle();
- float MaxAngularVel = NetConfig.MaxPhysicsBodyAngularVelocity;
- angularVelocity = msg.ReadRangedSingle(-MaxAngularVel, MaxAngularVel, 8);
- angularVelocity = NetConfig.Quantize(angularVelocity.Value, -MaxAngularVel, MaxAngularVel, 8);
+ angularVelocity = msg.ReadSingle();
}
+ bool ignorePlatforms = msg.ReadBoolean();
+
bool readStatus = msg.ReadBoolean();
if (readStatus)
{
@@ -287,7 +300,7 @@ namespace Barotrauma
{
byte happiness = msg.ReadByte();
byte hunger = msg.ReadByte();
- if ((AIController as EnemyAIController)?.PetBehavior is PetBehavior petBehavior)
+ if (AIController is EnemyAIController { PetBehavior: PetBehavior petBehavior })
{
petBehavior.Happiness = (float)happiness / byte.MaxValue * petBehavior.MaxHappiness;
petBehavior.Hunger = (float)hunger / byte.MaxValue * petBehavior.MaxHunger;
@@ -303,13 +316,13 @@ namespace Barotrauma
msg.ReadPadBits();
int index = 0;
- if (GameMain.Client.Character == this && CanMove)
+ if (GameMain.Client.Character == this)
{
var posInfo = new CharacterStateInfo(
pos, rotation,
networkUpdateID,
facingRight ? Direction.Right : Direction.Left,
- selectedCharacter, selectedItem, selectedSecondaryItem, animation);
+ selectedCharacter, selectedItem, selectedSecondaryItem, targetMovement, animation, ignorePlatforms);
while (index < memState.Count && NetIdUtils.IdMoreRecent(posInfo.ID, memState[index].ID))
index++;
@@ -321,7 +334,7 @@ namespace Barotrauma
pos, rotation,
linearVelocity, angularVelocity,
sendingTime, facingRight ? Direction.Right : Direction.Left,
- selectedCharacter, selectedItem, selectedSecondaryItem, animation);
+ selectedCharacter, selectedItem, selectedSecondaryItem, targetMovement, animation, ignorePlatforms);
while (index < memState.Count && posInfo.Timestamp > memState[index].Timestamp)
index++;
@@ -371,6 +384,9 @@ namespace Barotrauma
GameMain.Client.HasSpawned = true;
GameMain.Client.Character = this;
GameMain.LightManager.LosEnabled = true;
+#if DEBUG
+ GameMain.LightManager.LosEnabled = !GameMain.DevMode;
+#endif
GameMain.LightManager.LosAlpha = 1f;
GameMain.Client.WaitForNextRoundRespawn = null;
}
@@ -393,15 +409,16 @@ namespace Barotrauma
break;
case EventType.Status:
ReadStatus(msg);
+ GodMode = msg.ReadBoolean();
break;
case EventType.UpdateSkills:
- int skillCount = msg.ReadByte();
- for (int i = 0; i < skillCount; i++)
+ Identifier skillIdentifier = msg.ReadIdentifier();
+ if (!skillIdentifier.IsEmpty)
{
- Identifier skillIdentifier = msg.ReadIdentifier();
+ bool forceNotification = msg.ReadBoolean();
float skillLevel = msg.ReadSingle();
- info?.SetSkillLevel(skillIdentifier, skillLevel);
- }
+ info?.SetSkillLevel(skillIdentifier, skillLevel, forceNotification: forceNotification);
+ }
break;
case EventType.SetAttackTarget:
case EventType.ExecuteAttack:
@@ -512,7 +529,12 @@ namespace Barotrauma
break;
case EventType.UpdateExperience:
int experienceAmount = msg.ReadInt32();
- info?.SetExperience(experienceAmount);
+ int additionalTalentPoints = msg.ReadInt32();
+ if (info != null)
+ {
+ info.SetExperience(experienceAmount);
+ info.AdditionalTalentPoints = additionalTalentPoints;
+ }
break;
case EventType.UpdateTalents:
ushort talentCount = msg.ReadUInt16();
@@ -527,6 +549,20 @@ namespace Barotrauma
int moneyAmount = msg.ReadInt32();
SetMoney(moneyAmount);
break;
+ case EventType.UpdateTalentRefundPoints:
+ int refundPoints = msg.ReadInt32();
+ if (info != null)
+ {
+ if (refundPoints > info.TalentRefundPoints)
+ {
+ info.ShowTalentResetPopupOnOpen = true;
+ }
+ info.TalentRefundPoints = refundPoints;
+ }
+ break;
+ case EventType.ConfirmTalentRefund:
+ Info?.RefundTalents();
+ break;
case EventType.UpdatePermanentStats:
byte savedStatValueCount = msg.ReadByte();
StatTypes statType = (StatTypes)msg.ReadByte();
@@ -730,7 +766,7 @@ namespace Barotrauma
if (character.IsHuman && character.TeamID != CharacterTeamType.FriendlyNPC && character.TeamID != CharacterTeamType.None)
{
- CharacterInfo duplicateCharacterInfo = GameMain.GameSession.CrewManager.GetCharacterInfos().FirstOrDefault(c => c.ID == info.ID);
+ CharacterInfo duplicateCharacterInfo = GameMain.GameSession.CrewManager.GetCharacterInfos(includeReserveBench: true).FirstOrDefault(c => c.ID == info.ID);
GameMain.GameSession.CrewManager.RemoveCharacterInfo(duplicateCharacterInfo);
if (character.isDead)
{
@@ -750,6 +786,9 @@ namespace Barotrauma
if (!character.IsDead) { Controlled = character; }
GameMain.LightManager.LosEnabled = true;
+#if DEBUG
+ GameMain.LightManager.LosEnabled = !GameMain.DevMode;
+#endif
GameMain.LightManager.LosAlpha = 1f;
GameMain.NetLobbyScreen.CampaignCharacterDiscarded = false;
@@ -817,6 +856,7 @@ namespace Barotrauma
if (IsDead) { Revive(); }
CharacterHealth.ClientRead(msg);
}
+
byte severedLimbCount = msg.ReadByte();
for (int i = 0; i < severedLimbCount; i++)
{
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/AfflictionPsychosis.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/AfflictionPsychosis.cs
index 77672ac7e..c353f07be 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/AfflictionPsychosis.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/AfflictionPsychosis.cs
@@ -180,7 +180,7 @@ namespace Barotrauma
fakeBrokenTimer -= deltaTime;
if (fakeBrokenTimer > 0.0f) { return; }
- foreach (Item item in Item.ItemList)
+ foreach (Item item in Item.RepairableItems)
{
var repairable = item.GetComponent();
if (repairable == null) { continue; }
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs
index 01a89a762..275d5ce4d 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs
@@ -31,8 +31,7 @@ namespace Barotrauma
}
public static Sprite DamageOverlay => DamageOverlayPrefab.Prefabs.ActivePrefab.DamageOverlay;
-
-
+
private Point screenResolution;
private float uiScale, inventoryScale;
@@ -105,6 +104,12 @@ namespace Barotrauma
private GUILayoutGroup treatmentLayout;
private GUIListBox recommendedTreatmentContainer;
+ ///
+ /// Timer for updating visuals (limb tints and overlays) caused by the affliction
+ ///
+ private float updateVisualsTimer = Rand.Range(0.0f, UpdateVisualsInterval);
+ const float UpdateVisualsInterval = 0.5f;
+
private float distortTimer;
// 0-1
@@ -461,15 +466,17 @@ namespace Barotrauma
private void OnAttacked(Character attacker, AttackResult attackResult)
{
if (Math.Abs(attackResult.Damage) < 0.01f) { return; }
- DamageOverlayTimer = MathHelper.Clamp(attackResult.Damage / MaxVitality, DamageOverlayTimer, 1.0f);
- if (healthShadowDelay <= 0.0f) { healthShadowDelay = 1.0f; }
+ if (ShowDamageOverlay)
+ {
+ DamageOverlayTimer = MathHelper.Clamp(attackResult.Damage / MaxVitality, DamageOverlayTimer, 1.0f);
+ float additionalIntensity = MathHelper.Lerp(0, 1, MathUtils.InverseLerp(0, 0.1f, attackResult.Damage / MaxVitality));
+ damageIntensity = MathHelper.Clamp(damageIntensity + additionalIntensity, 0, 1);
+ }
+
+ if (healthShadowDelay <= 0.0f) { healthShadowDelay = 1.0f; }
if (healthBarPulsateTimer <= 0.0f) { healthBarPulsatePhase = 0.0f; }
healthBarPulsateTimer = 1.0f;
-
- float additionalIntensity = MathHelper.Lerp(0, 1, MathUtils.InverseLerp(0, 0.1f, attackResult.Damage / MaxVitality));
- damageIntensity = MathHelper.Clamp(damageIntensity + additionalIntensity, 0, 1);
-
DisplayVitalityDelay = 0.5f;
}
@@ -1036,6 +1043,12 @@ namespace Barotrauma
var affliction = kvp.Key;
affliction.Prefab.AfflictionOverlay?.Draw(spriteBatch, Vector2.Zero, Color.White * affliction.GetAfflictionOverlayMultiplier(), Vector2.Zero, 0.0f,
new Vector2(GameMain.GraphicsWidth / DamageOverlay.size.X, GameMain.GraphicsHeight / DamageOverlay.size.Y));
+
+ var activeEffect = affliction.GetActiveEffect();
+ if (activeEffect is { ThermalOverlayRange: > 0.0f })
+ {
+ StatusHUD.DrawThermalOverlay(spriteBatch, Character, Character, activeEffect.ThermalOverlayColor, activeEffect.ThermalOverlayRange, effectState: (float)Timing.TotalTimeUnpaused, showDeadCharacters: false);
+ }
}
float damageOverlayAlpha = DamageOverlayTimer;
@@ -1130,6 +1143,8 @@ namespace Barotrauma
if (!statusIconVisibleTime.ContainsKey(afflictionPrefab)) { statusIconVisibleTime.Add(afflictionPrefab, 0.0f); }
statusIconVisibleTime[afflictionPrefab] += deltaTime;
+ Color color = GetAfflictionIconColor(afflictionPrefab, affliction);
+
var matchingIcon =
afflictionIconContainer.GetChildByUserData(afflictionPrefab) ??
hiddenAfflictionIconContainer.GetChildByUserData(afflictionPrefab);
@@ -1138,9 +1153,13 @@ namespace Barotrauma
matchingIcon = new GUIButton(new RectTransform(new Point(afflictionIconContainer.Rect.Height), afflictionIconContainer.RectTransform), style: null)
{
UserData = afflictionPrefab,
- ToolTip = affliction.Prefab.Name,
+ ToolTip = $"‖color:{color.ToStringHex()}‖{affliction.Prefab.Name}‖color:end‖",
CanBeSelected = false
};
+ if (affliction.Prefab.ShowDescriptionInTooltip)
+ {
+ matchingIcon.ToolTip = matchingIcon.ToolTip + "\n" + affliction.Prefab.GetDescription(affliction.Strength, AfflictionPrefab.Description.TargetType.Self);
+ }
if (affliction == pressureAffliction)
{
matchingIcon.ToolTip = TextManager.Get("PressureHUDWarning");
@@ -1149,6 +1168,8 @@ namespace Barotrauma
{
matchingIcon.ToolTip = TextManager.Get("OxygenHUDWarning");
}
+ matchingIcon.ToolTip = RichString.Rich(matchingIcon.ToolTip);
+
new GUIImage(new RectTransform(Vector2.One, matchingIcon.RectTransform, Anchor.BottomCenter), afflictionPrefab.Icon, scaleToFit: true)
{
CanBeFocused = false
@@ -1159,7 +1180,7 @@ namespace Barotrauma
matchingIcon.RectTransform.Parent = hiddenAfflictionIconContainer.RectTransform;
}
var image = matchingIcon.GetChild();
- image.Color = GetAfflictionIconColor(afflictionPrefab, affliction);
+ image.Color = color;
image.HoverColor = Color.Lerp(image.Color, Color.White, 0.5f);
if (affliction.DamagePerSecond > 1.0f && matchingIcon.FlashTimer <= 0.0f)
@@ -1380,7 +1401,7 @@ namespace Barotrauma
recommendedTreatmentContainer.Content.ClearChildren();
- float characterSkillLevel = Character.Controlled == null ? 0.0f : Character.Controlled.GetSkillLevel("medical");
+ float characterSkillLevel = Character.Controlled == null ? 0.0f : Character.Controlled.GetSkillLevel(Tags.MedicalSkill);
//key = item identifier
//float = suitability
@@ -1388,7 +1409,9 @@ namespace Barotrauma
GetSuitableTreatments(treatmentSuitability,
user: Character.Controlled,
ignoreHiddenAfflictions: true,
- limb: selectedLimbIndex == -1 ? null : Character.AnimController.Limbs.Find(l => l.HealthIndex == selectedLimbIndex));
+ limb: selectedLimbIndex == -1 ? null : Character.AnimController.Limbs.Find(l => l.HealthIndex == selectedLimbIndex),
+ checkTreatmentSuggestionThreshold: true,
+ checkTreatmentThreshold: false);
foreach (Identifier treatment in treatmentSuitability.Keys.ToList())
{
@@ -1949,7 +1972,6 @@ namespace Barotrauma
}
}
-
private bool ShouldDisplayAfflictionOnLimb(KeyValuePair kvp, LimbHealth limbHealth)
{
if (!kvp.Key.ShouldShowIcon(Character)) { return false; }
@@ -2058,23 +2080,23 @@ namespace Barotrauma
newAfflictions.Add((limbHealths[limbIndex], afflictionPrefab, afflictionStrength));
}
- foreach (KeyValuePair kvp in afflictions)
+ foreach ((Affliction affliction, LimbHealth limbHealth) in afflictions)
{
//deactivate afflictions that weren't included in the network message
- if (!newAfflictions.Any(a => kvp.Key.Prefab == a.afflictionPrefab && kvp.Value == a.limb))
+ if (newAfflictions.None(a => affliction.Prefab == a.afflictionPrefab && limbHealth == a.limb))
{
- kvp.Key.Strength = 0.0f;
+ affliction.Strength = 0.0f;
}
}
foreach (var (limb, afflictionPrefab, strength) in newAfflictions)
{
Affliction existingAffliction = null;
- foreach (KeyValuePair kvp in afflictions)
+ foreach ((Affliction affliction, LimbHealth limbHealth) in afflictions)
{
- if (kvp.Key.Prefab == afflictionPrefab && kvp.Value == limb)
+ if (affliction.Prefab == afflictionPrefab && limbHealth == limb)
{
- existingAffliction = kvp.Key;
+ existingAffliction = affliction;
break;
}
}
@@ -2123,9 +2145,8 @@ namespace Barotrauma
if (!Character.Params.Health.ApplyAfflictionColors) { return; }
- foreach (KeyValuePair kvp in afflictions)
+ foreach ((Affliction affliction, LimbHealth _) in afflictions)
{
- var affliction = kvp.Key;
Color faceTint = affliction.GetFaceTint();
if (faceTint.A > FaceTint.A) { FaceTint = faceTint; }
Color bodyTint = affliction.GetBodyTint();
@@ -2137,17 +2158,23 @@ namespace Barotrauma
{
foreach (Limb limb in Character.AnimController.Limbs)
{
- if (limb.HealthIndex < 0 || limb.HealthIndex >= limbHealths.Count) { continue; }
limb.BurnOverlayStrength = 0.0f;
limb.DamageOverlayStrength = 0.0f;
- foreach (KeyValuePair kvp in afflictions)
+ }
+
+ foreach ((Affliction affliction, LimbHealth limbHealth) in afflictions)
+ {
+ if (affliction.Prefab.BurnOverlayAlpha <= 0.0f && affliction.Prefab.DamageOverlayAlpha <= 0.0f) { continue; }
+
+ float burnStrength = affliction.Strength / Math.Min(affliction.Prefab.MaxStrength, 100) * affliction.Prefab.BurnOverlayAlpha;
+ float damageOverlayStrength = affliction.Strength / Math.Min(affliction.Prefab.MaxStrength, 100) * affliction.Prefab.DamageOverlayAlpha;
+ foreach (Limb limb in Character.AnimController.Limbs)
{
- var affliction = kvp.Key;
- float burnStrength = affliction.Strength / Math.Min(affliction.Prefab.MaxStrength, 100) * affliction.Prefab.BurnOverlayAlpha;
- if (kvp.Value == limbHealths[limb.HealthIndex] || !affliction.Prefab.LimbSpecific)
+ if (limb.HealthIndex < 0 || limb.HealthIndex >= limbHealths.Count) { continue; }
+ if (limbHealth == limbHealths[limb.HealthIndex] || !affliction.Prefab.LimbSpecific)
{
limb.BurnOverlayStrength += burnStrength;
- limb.DamageOverlayStrength += affliction.Strength / Math.Min(affliction.Prefab.MaxStrength, 100) * affliction.Prefab.DamageOverlayAlpha;
+ limb.DamageOverlayStrength += damageOverlayStrength;
}
else
{
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/InteractionLabelManager.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/InteractionLabelManager.cs
index 73e733677..897637950 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Characters/InteractionLabelManager.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/InteractionLabelManager.cs
@@ -17,13 +17,16 @@ public static class InteractionLabelManager
public RectangleF TextRect { get; set; }
+ public RichString Text;
+
public readonly Vector2 OriginalItemPosition;
public bool OverlapPreventionDone;
- public LabelData(Item item, RectangleF textRect, Camera drawCamera)
+ public LabelData(Item item, RectangleF textRect, RichString text, Camera drawCamera)
{
Item = item;
+ Text = text;
TextRect = textRect;
OriginalItemPosition = item.Position;
this.drawCamera = drawCamera;
@@ -106,7 +109,7 @@ public static class InteractionLabelManager
if (labels.None(l => l.Item == interactableInRange))
{
- var labelData = new LabelData(interactableInRange, textRect, cam);
+ var labelData = new LabelData(interactableInRange, textRect, RichString.Rich(interactableInRange.Prefab.Name), cam);
labels.Add(labelData);
}
}
@@ -124,7 +127,7 @@ public static class InteractionLabelManager
private static RectangleF GetLabelRect(Item item, Camera cam)
{
// create rectangle for overlap prevention
- Vector2 itemTextSizeScreen = GUIStyle.SubHeadingFont.MeasureString(item.Name) * LabelScale;
+ Vector2 itemTextSizeScreen = GUIStyle.SubHeadingFont.MeasureString(RichString.Rich(item.Prefab.Name).SanitizedValue) * LabelScale;
Vector2 interactablePosScreen = cam.WorldToScreen(item.Position);
RectangleF textRect = new RectangleF(interactablePosScreen.X, interactablePosScreen.Y, itemTextSizeScreen.X, itemTextSizeScreen.Y);
// center the rectangle on the item
@@ -320,9 +323,11 @@ public static class InteractionLabelManager
GUIStyle.InteractionLabelBackground.Draw(spriteBatch, backgroundRect, color * 0.7f);
- GUIStyle.SubHeadingFont.DrawString(spriteBatch,
- labelData.Item.Name,
- textDrawPosScreen, color, rotation: 0, origin: Vector2.Zero, scale, spriteEffects: SpriteEffects.None, layerDepth: 0.0f,
+ GUIStyle.SubHeadingFont.DrawStringWithColors(spriteBatch,
+ labelData.Text.SanitizedValue,
+ textDrawPosScreen, color, rotation: 0, origin: Vector2.Zero, scale, spriteEffects: SpriteEffects.None,
+ layerDepth: 0.0f,
+ richTextData: labelData.Text.RichTextData,
forceUpperCase: ForceUpperCase.No);
}
}
\ No newline at end of file
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Jobs/JobPrefab.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Jobs/JobPrefab.cs
index 1ac28a552..be656e146 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Jobs/JobPrefab.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Jobs/JobPrefab.cs
@@ -1,85 +1,75 @@
using Microsoft.Xna.Framework;
-using System.Linq;
-using System;
-using System.Xml.Linq;
using System.Collections.Generic;
+using System.Linq;
namespace Barotrauma
{
partial class JobPrefab : PrefabWithUintIdentifier
{
- public GUIButton CreateInfoFrame(out GUIComponent buttonContainer)
+ public GUIButton CreateInfoFrame(bool isPvP, out GUIComponent buttonContainer)
{
- int width = 500, height = 400;
-
+ int windowPixelWidth = 500, windowPixelHeight = 400;
+ Point absoluteWindowSize = new Point((int)(windowPixelWidth * GUI.xScale), (int)(windowPixelHeight * GUI.yScale));
+
GUIButton frameHolder = new GUIButton(new RectTransform(Vector2.One, GUI.Canvas, Anchor.Center), style: null);
new GUIFrame(new RectTransform(GUI.Canvas.RelativeSize, frameHolder.RectTransform, Anchor.Center), style: "GUIBackgroundBlocker");
- GUIFrame frame = new GUIFrame(new RectTransform(new Point(width, height), frameHolder.RectTransform, Anchor.Center));
+ GUIFrame frame = new GUIFrame(new RectTransform(absoluteWindowSize, frameHolder.RectTransform, Anchor.Center));
GUIFrame paddedFrame = new GUIFrame(new RectTransform(new Vector2(0.9f, 0.9f), frame.RectTransform, Anchor.Center), style: null);
+
+ new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.1f), paddedFrame.RectTransform), Name, font: GUIStyle.LargeFont)
+ {
+ CanBeFocused = false
+ };
+
+ var contentList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.75f), paddedFrame.RectTransform) { RelativeOffset = new Vector2(0.0f, 0.1f) })
+ {
+ ScrollBarVisible = true,
+ AutoHideScrollBar = true,
+ CurrentSelectMode = GUIListBox.SelectMode.None,
+ Padding = new Vector4(0, GUI.Scale * 10, 0, 0),
+ Spacing = (int)(GUI.Scale * 5)
+ };
- new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.1f), paddedFrame.RectTransform), Name, font: GUIStyle.LargeFont);
-
- var descriptionBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedFrame.RectTransform) { RelativeOffset = new Vector2(0.0f, 0.15f) },
- Description, font: GUIStyle.SmallFont, wrap: true);
-
- var skillContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.45f, 0.5f), paddedFrame.RectTransform)
- { RelativeOffset = new Vector2(0.0f, 0.2f + descriptionBlock.RectTransform.RelativeSize.Y) });
- new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), skillContainer.RectTransform),
- TextManager.Get("Skills"), font: GUIStyle.LargeFont);
+ var descriptionBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), contentList.Content.RectTransform),
+ Description, font: GUIStyle.SmallFont, wrap: true, textAlignment: Alignment.TopLeft)
+ {
+ CanBeFocused = false,
+ };
+
+ new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), contentList.Content.RectTransform),
+ TextManager.Get("Skills"), font: GUIStyle.LargeFont)
+ {
+ CanBeFocused = false
+ };
+
foreach (SkillPrefab skill in Skills)
{
- new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), skillContainer.RectTransform),
- " - " + TextManager.AddPunctuation(':', TextManager.Get("SkillName." + skill.Identifier), (int)skill.LevelRange.Start + " - " + (int)skill.LevelRange.End),
- font: GUIStyle.SmallFont);
+ var levelRange = skill.GetLevelRange(isPvP);
+
+ string levelStr =
+ levelRange.End > levelRange.Start ?
+ (int)levelRange.Start + " - " + (int)levelRange.End :
+ ((int)levelRange.Start).ToString();
+ new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), contentList.Content.RectTransform),
+ " - " + TextManager.AddPunctuation(':', TextManager.Get("SkillName." + skill.Identifier), levelStr),
+ font: GUIStyle.SmallFont, wrap: true)
+ {
+ CanBeFocused = false
+ };
}
buttonContainer = paddedFrame;
-
- /*if (!ItemIdentifiers.TryGetValue(variant, out var itemIdentifiers)) { return backFrame; }
- var itemContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.45f, 0.5f), paddedFrame.RectTransform, Anchor.TopRight)
- { RelativeOffset = new Vector2(0.0f, 0.2f + descriptionBlock.RectTransform.RelativeSize.Y) })
- {
- Stretch = true
- };
- new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), itemContainer.RectTransform),
- TextManager.Get("Items", "mapentitycategory.equipment"), font: GUIStyle.LargeFont);
- foreach (string identifier in itemIdentifiers.Distinct())
- {
- if (!(MapEntityPrefab.Find(name: null, identifier: identifier) is ItemPrefab itemPrefab)) { continue; }
- int count = itemIdentifiers.Count(i => i == identifier);
- new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), itemContainer.RectTransform),
- " - " + (count == 1 ? itemPrefab.Name : itemPrefab.Name + " x" + count),
- font: GUIStyle.SmallFont);
- }*/
-
+
return frameHolder;
}
-
- public class OutfitPreview
+
+ public IEnumerable GetJobOutfitSprites(CharacterTeamType team, bool isPvPMode)
{
- public readonly List<(Sprite sprite, Vector2 drawOffset)> Sprites;
-
- public Vector2 Dimensions;
-
- public OutfitPreview()
- {
- Sprites = new List<(Sprite sprite, Vector2 drawOffset)>();
- Dimensions = Vector2.One;
- }
-
- public void AddSprite(Sprite sprite, Vector2 drawOffset)
- {
- Sprites.Add((sprite, drawOffset));
- }
- }
-
- public List GetJobOutfitSprites(CharacterInfoPrefab charInfoPrefab, bool useInventoryIcon, out Vector2 maxDimensions)
- {
- List outfitPreviews = new List();
- maxDimensions = Vector2.One;
-
- var equipIdentifiers = Element.GetChildElements("ItemSet").Elements().Where(e => e.GetAttributeBool("outfit", false)).Select(e => e.GetAttributeIdentifier("identifier", ""));
+ var equipIdentifiers = JobItems
+ .SelectMany(kvp => kvp.Value)
+ .Where(j => j.Outfit)
+ .Select(j => j.GetItemIdentifier(team, isPvPMode));
List outfitPrefabs = new List();
foreach (var equipIdentifier in equipIdentifiers)
@@ -88,45 +78,9 @@ namespace Barotrauma
if (itemPrefab != null) { outfitPrefabs.Add(itemPrefab); }
}
- if (!outfitPrefabs.Any()) { return null; }
+ if (!outfitPrefabs.Any()) { return Enumerable.Empty(); }
- for (int i = 0; i < outfitPrefabs.Count; i++)
- {
- var outfitPreview = new OutfitPreview();
-
- if (!ItemSets.TryGetValue(i, out var itemSetElement)) { continue; }
- var previewElement = itemSetElement.GetChildElement("PreviewSprites");
- if (previewElement == null || useInventoryIcon)
- {
- if (outfitPrefabs[i] is ItemPrefab prefab && prefab.InventoryIcon != null)
- {
- outfitPreview.AddSprite(prefab.InventoryIcon, Vector2.Zero);
- outfitPreview.Dimensions = prefab.InventoryIcon.SourceRect.Size.ToVector2();
- maxDimensions.X = MathHelper.Max(maxDimensions.X, outfitPreview.Dimensions.X);
- maxDimensions.Y = MathHelper.Max(maxDimensions.Y, outfitPreview.Dimensions.Y);
- }
- outfitPreviews.Add(outfitPreview);
- continue;
- }
-
- var children = previewElement.Elements().ToList();
- for (int n = 0; n < children.Count; n++)
- {
- var spriteElement = children[n];
- string spriteTexture = charInfoPrefab.ReplaceVars(spriteElement.GetAttributeString("texture", ""), charInfoPrefab.Heads.First());
- var sprite = new Sprite(spriteElement, file: spriteTexture);
- sprite.size = new Vector2(sprite.SourceRect.Width, sprite.SourceRect.Height);
- outfitPreview.AddSprite(sprite, children[n].GetAttributeVector2("offset", Vector2.Zero));
- }
-
- outfitPreview.Dimensions = previewElement.GetAttributeVector2("dims", Vector2.One);
- maxDimensions.X = MathHelper.Max(maxDimensions.X, outfitPreview.Dimensions.X);
- maxDimensions.Y = MathHelper.Max(maxDimensions.Y, outfitPreview.Dimensions.Y);
-
- outfitPreviews.Add(outfitPreview);
- }
-
- return outfitPreviews;
+ return outfitPrefabs.Select(p => p.InventoryIcon ?? p.Sprite);
}
}
}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Limb.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Limb.cs
index 04fb57758..47e90a6cb 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Limb.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Limb.cs
@@ -137,7 +137,16 @@ namespace Barotrauma
{
get
{
- var conditionalSprite = ConditionalSprites.FirstOrDefault(c => c.Exclusive && c.IsActive && c.DeformableSprite != null);
+ // Performance-sensitive, hence implemented without Linq.
+ ConditionalSprite conditionalSprite = null;
+ foreach (ConditionalSprite cs in ConditionalSprites)
+ {
+ if (cs.Exclusive && cs.IsActive && cs.DeformableSprite != null)
+ {
+ conditionalSprite = cs;
+ break;
+ }
+ }
if (conditionalSprite != null)
{
return conditionalSprite.DeformableSprite;
@@ -155,7 +164,16 @@ namespace Barotrauma
{
get
{
- var conditionalSprite = ConditionalSprites.FirstOrDefault(c => c.Exclusive && c.IsActive && c.ActiveSprite != null);
+ // Performance-sensitive, hence implemented without Linq.
+ ConditionalSprite conditionalSprite = null;
+ foreach (ConditionalSprite cs in ConditionalSprites)
+ {
+ if (cs.Exclusive && cs.IsActive && cs.ActiveSprite != null)
+ {
+ conditionalSprite = cs;
+ break;
+ }
+ }
if (conditionalSprite != null)
{
return conditionalSprite.ActiveSprite;
@@ -184,12 +202,6 @@ namespace Barotrauma
public Sprite DamagedSprite { get; private set; }
- public bool Hide
- {
- get => Params.Hide;
- set => Params.Hide = value;
- }
-
public List ConditionalSprites { get; private set; } = new List();
private Dictionary spriteAnimState = new Dictionary();
private Dictionary> DecorativeSpriteGroups = new Dictionary>();
@@ -198,6 +210,7 @@ namespace Barotrauma
{
public float RotationState;
public float OffsetState;
+ public float ScaleState;
public Vector2 RandomOffsetMultiplier = new Vector2(Rand.Range(-1.0f, 1.0f), Rand.Range(-1.0f, 1.0f));
public float RandomRotationFactor = Rand.Range(0.0f, 1.0f);
public float RandomScaleFactor = Rand.Range(0.0f, 1.0f);
@@ -301,7 +314,16 @@ namespace Barotrauma
DamagedSprite = new Sprite(subElement, file: GetSpritePath(subElement, Params.damagedSpriteParams, ref _damagedTexturePath), sourceRectScale: sourceRectScale);
break;
case "conditionalsprite":
- var conditionalSprite = new ConditionalSprite(subElement, GetConditionalTarget(), file: GetSpritePath(subElement, null, ref _texturePath), sourceRectScale: sourceRectScale);
+ string conditionalSpritePath = string.Empty;
+ GetSpritePath(subElement.GetChildElement("sprite") ?? subElement.GetChildElement("deformablesprite") ?? subElement, null, ref conditionalSpritePath);
+ if (conditionalSpritePath.IsNullOrEmpty())
+ {
+ DebugConsole.ThrowError($"Failed to find a sprite path in the conditional sprite defined in {character.SpeciesName}, limb {type}.",
+ contentPackage: subElement.ContentPackage);
+ }
+ var conditionalSprite = new ConditionalSprite(subElement, GetConditionalTarget(),
+ file: conditionalSpritePath,
+ sourceRectScale: sourceRectScale);
ConditionalSprites.Add(conditionalSprite);
if (conditionalSprite.DeformableSprite != null)
{
@@ -475,13 +497,24 @@ namespace Barotrauma
private string _damagedTexturePath;
private string GetSpritePath(ContentXElement element, SpriteParams spriteParams, ref string path)
{
- if (path == null)
+ if (path.IsNullOrEmpty())
{
if (spriteParams != null)
{
- //1. check if the variant file redefines the texture
- ContentPath texturePath = character.Params.VariantFile?.Root?.GetAttributeContentPath("texture", character.Prefab.ContentPackage);
- //2. check if the base prefab defines the texture
+ ContentPath texturePath;
+ //1. check if the limb defines the texture directly
+ var definedTexturePath = element?.GetAttributeContentPath("texture");
+ if (!definedTexturePath.IsNullOrEmpty())
+ {
+ texturePath = definedTexturePath;
+ }
+ else
+ {
+ //2. check if the character file defines the texture directly
+ texturePath = character.Params.VariantFile?.GetRootExcludingOverride()?.GetAttributeContentPath("texture", character.Prefab.ContentPackage);
+ }
+
+ //3. check if the base prefab defines the texture
if (texturePath.IsNullOrEmpty() && !character.Prefab.VariantOf.IsEmpty)
{
Identifier speciesName = character.GetBaseCharacterSpeciesName();
@@ -491,7 +524,7 @@ namespace Barotrauma
texturePath = parentRagdollParams.OriginalElement?.GetAttributeContentPath("texture");
}
- //3. "default case", get the texture from this character's XML
+ //4. "default case", get the texture from this character's XML
texturePath ??= ContentPath.FromRaw(spriteParams.Element.ContentPackage ?? character.Prefab.ContentPackage, spriteParams.GetTexturePath());
path = GetSpritePath(texturePath);
}
@@ -749,9 +782,29 @@ namespace Barotrauma
float herpesStrength = character.CharacterHealth.GetAfflictionStrengthByType(AfflictionPrefab.SpaceHerpesType);
- bool hideLimb = Hide ||
- OtherWearables.Any(w => w.HideLimb) ||
- WearingItems.Any(w => w.HideLimb);
+ bool hideLimb = ShouldHideLimb(this);
+ if (!hideLimb && Params.InheritHiding != LimbType.None)
+ {
+ if (character.AnimController.GetLimb(Params.InheritHiding) is Limb otherLimb)
+ {
+ hideLimb = ShouldHideLimb(otherLimb);
+ }
+ }
+
+ static bool ShouldHideLimb(Limb limb)
+ {
+ if (limb.Hide) { return true; }
+ // Performance-sensitive code -> implemented without Linq
+ foreach (var wearable in limb.OtherWearables)
+ {
+ if (wearable.HideLimb) { return true; }
+ }
+ foreach (var wearable in limb.WearingItems)
+ {
+ if (wearable.HideLimb) { return true; }
+ }
+ return false;
+ }
bool drawHuskSprite = HuskSprite != null && !wearableTypesToHide.Contains(WearableType.Husk);
@@ -771,11 +824,13 @@ namespace Barotrauma
{
if (ActiveDeformations.Any())
{
- var deformation = SpriteDeformation.GetDeformation(ActiveDeformations, deformSprite.Size);
+ var deformation = SpriteDeformation.GetDeformation(ActiveDeformations, deformSprite.Size, flippedHorizontally: IsFlipped, false);
deformSprite.Deform(deformation);
if (LightSource != null && LightSource.DeformableLightSprite != null)
{
- deformation = SpriteDeformation.GetDeformation(ActiveDeformations, deformSprite.Size, dir == Direction.Left);
+ //apparently inversing on the y-axis is only necessary for light sprites (see 345a65ca6)
+ //it's a mystery why this is the case, something to do with sprite flipping being handled differently in light rendering?
+ deformation = SpriteDeformation.GetDeformation(ActiveDeformations, deformSprite.Size, flippedHorizontally: IsFlipped, inverseY: dir == Direction.Left);
LightSource.DeformableLightSprite.Deform(deformation);
}
}
@@ -826,7 +881,7 @@ namespace Barotrauma
var defSprite = conditionalSprite.DeformableSprite;
if (ActiveDeformations.Any())
{
- var deformation = SpriteDeformation.GetDeformation(ActiveDeformations, defSprite.Size);
+ var deformation = SpriteDeformation.GetDeformation(ActiveDeformations, defSprite.Size, flippedHorizontally: IsFlipped);
defSprite.Deform(deformation);
}
else
@@ -883,28 +938,28 @@ namespace Barotrauma
}
depthStep += step;
}
- foreach (WearableSprite wearable in OtherWearables)
+ if (!hideLimb)
{
- if (wearable.Type == WearableType.Husk) { continue; }
- if (wearableTypesToHide.Contains(wearable.Type))
+ foreach (WearableSprite wearable in OtherWearables)
{
- if (wearable.Type == WearableType.Hair)
+ if (wearable.Type == WearableType.Husk) { continue; }
+ if (wearableTypesToHide.Contains(wearable.Type))
{
- if (HairWithHatSprite != null && !hideLimb)
+ // Draws the short hair
+ if (wearable.Type == WearableType.Hair)
{
- DrawWearable(HairWithHatSprite, depthStep, spriteBatch, blankColor, alpha: color.A / 255f, spriteEffect);
- depthStep += step;
- continue;
+ if (HairWithHatSprite != null)
+ {
+ DrawWearable(HairWithHatSprite, depthStep, spriteBatch, blankColor, alpha: color.A / 255f, spriteEffect);
+ depthStep += step;
+ }
}
- }
- else
- {
continue;
}
+ DrawWearable(wearable, depthStep, spriteBatch, blankColor, alpha: color.A / 255f, spriteEffect);
+ //if there are multiple sprites on this limb, make the successive ones be drawn in front
+ depthStep += step;
}
- DrawWearable(wearable, depthStep, spriteBatch, blankColor, alpha: color.A / 255f, spriteEffect);
- //if there are multiple sprites on this limb, make the successive ones be drawn in front
- depthStep += step;
}
}
foreach (WearableSprite wearable in WearingItems)
@@ -952,8 +1007,8 @@ namespace Barotrauma
var ca = (float)Math.Cos(-body.Rotation);
var sa = (float)Math.Sin(-body.Rotation);
Vector2 transformedOffset = new Vector2(ca * offset.X + sa * offset.Y, -sa * offset.X + ca * offset.Y);
- decorativeSprite.Sprite.Draw(spriteBatch, new Vector2(body.DrawPosition.X + transformedOffset.X, -(body.DrawPosition.Y + transformedOffset.Y)), c,
- -body.Rotation + rotation, decorativeSprite.GetScale(spriteAnimState[decorativeSprite].RandomScaleFactor) * Scale, spriteEffect,
+ decorativeSprite.Sprite.Draw(spriteBatch, new Vector2(body.DrawPosition.X + transformedOffset.X, -(body.DrawPosition.Y + transformedOffset.Y)), c, decorativeSprite.Sprite.Origin,
+ -body.Rotation + rotation, decorativeSprite.GetScale(ref spriteAnimState[decorativeSprite].ScaleState, spriteAnimState[decorativeSprite].RandomScaleFactor) * Scale, spriteEffect,
depth: activeSprite.Depth - depthStep);
depthStep += step;
}
@@ -963,7 +1018,7 @@ namespace Barotrauma
new Vector2(body.DrawPosition.X, -body.DrawPosition.Y),
colorWithoutTint * damageOverlayStrength, activeSprite.Origin,
-body.DrawRotation,
- Scale, spriteEffect, activeSprite.Depth - depthStep * Math.Max(1, WearingItems.Count * 2)); // Multiply by 2 to get rid of z-fighting with some clothing combos
+ Scale * TextureScale, spriteEffect, activeSprite.Depth - depthStep * Math.Max(1, WearingItems.Count * 2)); // Multiply by 2 to get rid of z-fighting with some clothing combos
}
}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/CircuitBox/CircuitBoxComponent.cs b/Barotrauma/BarotraumaClient/ClientSource/CircuitBox/CircuitBoxComponent.cs
index bdfc98c6e..d3de65b36 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/CircuitBox/CircuitBoxComponent.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/CircuitBox/CircuitBoxComponent.cs
@@ -86,7 +86,7 @@ namespace Barotrauma
var componentEditor = new SerializableEntityEditor(listBox.Content.RectTransform, ic, inGame: !isEditor, showName: false, titleFont: GUIStyle.SubHeadingFont)
{
- Readonly = CircuitBox.Locked
+ Readonly = CircuitBox.IsLocked()
};
fieldCount += componentEditor.Fields.Count;
diff --git a/Barotrauma/BarotraumaClient/ClientSource/CircuitBox/CircuitBoxUI.cs b/Barotrauma/BarotraumaClient/ClientSource/CircuitBox/CircuitBoxUI.cs
index 17f1e9215..f0cf5ef70 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/CircuitBox/CircuitBoxUI.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/CircuitBox/CircuitBoxUI.cs
@@ -35,7 +35,7 @@ namespace Barotrauma
public List VirtualWires = new();
- public bool Locked => CircuitBox.Locked;
+ public bool Locked => CircuitBox.IsLocked();
public CircuitBoxUI(CircuitBox box)
{
@@ -786,6 +786,7 @@ namespace Barotrauma
if (wireOption.TryUnwrap(out var wire))
{
CircuitBox.RemoveWires(wire.IsSelected ? wireSelection : ImmutableArray.Create(wire));
+ return;
}
switch (nodeOption)
diff --git a/Barotrauma/BarotraumaClient/ClientSource/ContentManagement/ContentPackage/ModProject.cs b/Barotrauma/BarotraumaClient/ClientSource/ContentManagement/ContentPackage/ModProject.cs
index 4c883b07d..8cd18148c 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/ContentManagement/ContentPackage/ModProject.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/ContentManagement/ContentPackage/ModProject.cs
@@ -169,9 +169,9 @@ namespace Barotrauma
return doc;
}
- public void Save(string path)
+ public void Save(string path, bool catchUnauthorizedAccessExceptions = true)
{
- Directory.CreateDirectory(Path.GetDirectoryName(path)!);
+ Directory.CreateDirectory(Path.GetDirectoryName(path)!, catchUnauthorizedAccessExceptions);
ToXDocument().SaveSafe(path);
}
}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/ContentManagement/Transition/LegacySteamUgcTransition.cs b/Barotrauma/BarotraumaClient/ClientSource/ContentManagement/Transition/LegacySteamUgcTransition.cs
index 5f45b7d56..036e488a4 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/ContentManagement/Transition/LegacySteamUgcTransition.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/ContentManagement/Transition/LegacySteamUgcTransition.cs
@@ -265,7 +265,7 @@ namespace Barotrauma.Transition
subs = getFiles(oldSubsPath, "*.sub");
itemAssemblies = getFiles(oldItemAssembliesPath, "*.xml");
- string[] allOldMods = Directory.GetDirectories(oldModsPath, "*", System.IO.SearchOption.TopDirectoryOnly);
+ string[] allOldMods = Directory.GetDirectories(oldModsPath, "*");
var publishedItems = await SteamManager.Workshop.GetPublishedItems();
foreach (var modDir in allOldMods)
diff --git a/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs b/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs
index d6400ca91..f11273dc5 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs
@@ -587,6 +587,39 @@ namespace Barotrauma
}
GameMain.CharacterEditorScreen.Select();
}));
+
+ commands.Add(new Command("settainted", "settainted [true/false]: Sets tainted effect on hovered genetic material.",
+ onExecute: (string[] args) =>
+ {
+
+ if (Character.Controlled == null)
+ {
+ NewMessage("No controlled character!", Color.Red);
+ return;
+ }
+
+ Item focusedItem = Character.Controlled?.FocusedItem ?? Inventory.SelectedSlot?.Item;
+
+ if (focusedItem == null)
+ {
+ NewMessage("No focused item, hover on something!", Color.Red);
+ return;
+ }
+
+ var geneticMaterial = focusedItem.GetComponent();
+
+ if (geneticMaterial == null)
+ {
+ NewMessage("Not hovering on a genetic material!", Color.Red);
+ return;
+ }
+ else
+ {
+ bool newValue = args.None(arg => string.Equals(arg, "false", StringComparison.InvariantCultureIgnoreCase));
+ geneticMaterial.SetTainted(newValue);
+ NewMessage($"Set tainted to {newValue} for {focusedItem.Name}", Color.Yellow);
+ }
+ }, isCheat: true));
commands.Add(new Command("quickstart", "Starts a singleplayer sandbox", (string[] args) =>
{
@@ -625,6 +658,113 @@ namespace Barotrauma
GameMain.MainMenuScreen.QuickStart(fixedSeed: false, subName, difficulty, levelGenerationParams);
}, getValidArgs: () => new[] { SubmarineInfo.SavedSubmarines.Select(s => s.Name).Distinct().OrderBy(s => s).ToArray() }));
+
+ commands.Add(new Command("forcewreck", "forcewreck [wreckname] (optional, ThalamusSpawn)[Random/Forced/Disabled]: When generating levels, ensures a specific wreck is generated. Second optional parameter to control thalamus spawning.", (string[] args) =>
+ {
+ if (args.Length > 0)
+ {
+ var submarineFile = GetSubmarineFile(args[0]);
+
+ if (submarineFile != null)
+ {
+ var matchingSub = SubmarineInfo.SavedSubmarines.FirstOrDefault(i => i.FilePath == submarineFile.Path.Value);
+ if (matchingSub != null)
+ {
+ NewMessage($"Setting ForceWreck to: {matchingSub.Name}, {submarineFile.Path}", color: Color.Yellow);
+
+ LevelData.ConsoleForceWreck = matchingSub;
+ }
+ }
+ else
+ {
+ NewMessage($"Can't find: {args[0]}", color: Color.Red);
+ }
+ }
+
+ if (args.Length > 1)
+ {
+ string forceThalamusArg = args[1];
+ if (Enum.TryParse(forceThalamusArg, ignoreCase: true, out LevelData.ThalamusSpawn result))
+ {
+ NewMessage($"Setting ThalamusSpawn to: {result}", color: Color.Yellow);
+ LevelData.ForceThalamus = result;
+ }
+ else
+ {
+ NewMessage($"Can't parse argument: {forceThalamusArg}", color: Color.Red);
+ }
+ }
+ else
+ {
+ NewMessage($"Setting ThalamusSpawn to: {LevelData.ThalamusSpawn.Random}", color: Color.Yellow);
+ LevelData.ForceThalamus = LevelData.ThalamusSpawn.Random;
+ }
+ },
+ () =>
+ {
+ return new string[][]
+ {
+ ListSubmarineFileNames(),
+ new string[] { LevelData.ThalamusSpawn.Random.ToString(), LevelData.ThalamusSpawn.Forced.ToString(), LevelData.ThalamusSpawn.Disabled.ToString() }
+ };
+ }, isCheat: true));
+
+ commands.Add(new Command("forcebeaconstation|forcebeacon", "forcebeaconstation [station name]: When generating levels, ensures a specific beacon station is generated.", (string[] args) =>
+ {
+ if (args.Length > 0)
+ {
+ var submarineFile = GetSubmarineFile(args[0]);
+
+ if (submarineFile != null)
+ {
+ var matchingSub = SubmarineInfo.SavedSubmarines.FirstOrDefault(i => i.FilePath == submarineFile.Path.Value);
+ if (matchingSub != null)
+ {
+ NewMessage($"Setting ForceBeaconStation to: {matchingSub.Name}, {submarineFile.Path}", color: Color.Yellow);
+
+ LevelData.ConsoleForceBeaconStation = matchingSub;
+ }
+ }
+ else
+ {
+ NewMessage($"Can't find: {args[0]}", color: Color.Red);
+ }
+ }
+ },
+ () =>
+ {
+ return new string[][]
+ {
+ ListSubmarineFileNames()
+ };
+ }, isCheat: true));
+
+ commands.Add(new Command("reloadcontentfile", "reloadcontentfile [filepath]: Reloads a specific content xml file during runtime.", (string[] args) =>
+ {
+ if (args.Length > 0)
+ {
+ string pathArgument = args[0];
+ var contentFile = GetContentFile(pathArgument);
+
+ if (contentFile != null)
+ {
+ NewMessage($"Reloading content file: {pathArgument}", Color.Yellow);
+ contentFile.UnloadFile();
+ contentFile.LoadFile();
+ }
+ else
+ {
+ NewMessage($"Can't find {args[0]} to reload", color:Color.Red);
+ }
+ }
+ },
+ () =>
+ {
+ return new string[][]
+ {
+ ListContentFilePaths()
+ };
+ }, isCheat: true));
commands.Add(new Command("steamnetdebug", "steamnetdebug: Toggles Steamworks networking debug logging.", (string[] args) =>
{
@@ -783,6 +923,7 @@ namespace Barotrauma
AssignRelayToServer("spreadsheetexport", false);
#if DEBUG
AssignRelayToServer("listspamfilters", false);
+ AssignRelayToServer("showitemxml", false);
AssignRelayToServer("crash", false);
AssignRelayToServer("showballastflorasprite", false);
AssignRelayToServer("simulatedlatency", false);
@@ -855,17 +996,11 @@ namespace Barotrauma
AssignOnExecute("teleportcharacter|teleport", (string[] args) =>
{
Vector2 cursorWorldPos = GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition);
- TeleportCharacter(cursorWorldPos, Character.Controlled, args);
+ TeleportCharacter(cursorWorldPos, Character.Controlled, args);
});
- AssignOnExecute("spawn|spawncharacter", (string[] args) =>
- {
- SpawnCharacter(args, GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition), out string errorMsg);
- if (!string.IsNullOrWhiteSpace(errorMsg))
- {
- ThrowError(errorMsg);
- }
- });
+ AssignOnExecute("spawn|spawncharacter", args => SpawnCharacter(args, GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition)));
+ AssignOnExecute("spawnnpc", args => SpawnCharacter(args, GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition), true));
AssignOnExecute("los", (string[] args) =>
{
@@ -1924,7 +2059,7 @@ namespace Barotrauma
addIfMissing($"missionname.{missionId}".ToIdentifier(), language);
}
- if (missionPrefab.Type == MissionType.Combat)
+ if (missionPrefab.Type == Tags.MissionTypeCombat)
{
addIfMissing($"MissionDescriptionNeutral.{missionId}".ToIdentifier(), language);
addIfMissing($"MissionDescription1.{missionId}".ToIdentifier(), language);
@@ -2360,7 +2495,47 @@ namespace Barotrauma
GameMain.SubEditorScreen.LoadSub(wreckedSubmarineInfo);
}));
+ commands.Add(new Command("showitemxml", "showitemxml [item]: Shows the XML configuration of an item in the console and copies it to the clipboard. Useful for debugging variants that partially override the XML of the base item for example.", (string[] args) =>
+ {
+ if (args.Length == 0)
+ {
+ ThrowError("Please specify the name or identifier of the item.");
+ return;
+ }
+
+ string itemNameOrId = args[0].ToLowerInvariant();
+ ItemPrefab itemPrefab =
+ (MapEntityPrefab.FindByName(itemNameOrId) ??
+ MapEntityPrefab.FindByIdentifier(itemNameOrId.ToIdentifier())) as ItemPrefab;
+ if (itemPrefab == null)
+ {
+ ThrowError("Item \"{itemNameOrId}\" not found!");
+ return;
+ }
+ string xmlStr = itemPrefab.ConfigElement.Element.ToString();
+ NewMessage(xmlStr);
+ Clipboard.SetText(xmlStr);
+ }, getValidArgs: () =>
+ {
+ return new string[][]
+ {
+ GetItemNameOrIdParams().ToArray()
+ };
+ }));
+
#if DEBUG
+
+ commands.Add(new Command("unlockachievement", "unlockachievement [identifier]: Unlocks the specified achievement.", (string[] args) =>
+ {
+ if (args.Length < 1)
+ {
+ ThrowError("Please specify the achievement to unlock.");
+ return;
+ }
+ NewMessage($"Unlocked \"{args[0]}\".");
+ AchievementManager.UnlockAchievement(args[0].ToIdentifier());
+ }, isCheat: true));
+
commands.Add(new Command("deathprompt", "Shows the death prompt for testing purposes.", (string[] args) =>
{
DeathPrompt.Create(delay: 1.0f);
@@ -2665,7 +2840,7 @@ namespace Barotrauma
string[] lines;
try
{
- lines = File.ReadAllLines(sourcePath);
+ lines = File.ReadAllLines(sourcePath, catchUnauthorizedAccessExceptions: false);
}
catch (Exception e)
{
@@ -2746,34 +2921,60 @@ namespace Barotrauma
Barotrauma.IO.Validation.SkipValidationInDebugBuilds = false;
}));
- commands.Add(new Command("dumpeventtexts", "dumpeventtexts [filepath]: gets the texts from event files and and writes them into a file along with xml tags that can be used in translation files. If the filepath is omitted, the file is written to Content/Texts/EventTexts.txt", (string[] args) =>
+ commands.Add(new Command("dumpeventtexts", "dumpeventtexts [sourcepath] [destinationpath]: gets the texts from event files and writes them into a file along with xml tags that can be used in translation files. If the filepath arguments are omitted, all event files are gone through and written to Content/Texts/EventTexts.txt", (string[] args) =>
{
- string filePath = args.Length > 0 ? args[0] : "Content/Texts/EventTexts.txt";
+ string sourcePath = args.Length > 0 ? Path.GetFullPath(args[0]) : string.Empty;
+ string destinationPath = args.Length > 1 ? args[1] : "Content/Texts/EventTexts.txt";
List lines = new List();
HashSet docs = new HashSet();
HashSet textIds = new HashSet();
- Dictionary existingTexts = new Dictionary();
-
+ Dictionary existingTexts = new Dictionary();
foreach (EventPrefab eventPrefab in EventSet.GetAllEventPrefabs())
{
- if (eventPrefab is not TraitorEventPrefab) { continue; }
- if (eventPrefab.Identifier.IsEmpty)
- {
- continue;
+ string dir = Path.GetDirectoryName(eventPrefab.FilePath.FullPath);
+ if (!sourcePath.IsNullOrEmpty() &&
+ Path.GetFullPath(eventPrefab.FilePath.FullPath) != sourcePath &&
+ Path.GetDirectoryName(eventPrefab.FilePath.FullPath) != sourcePath)
+ {
+ continue;
}
+ if (eventPrefab.Identifier.IsEmpty) { continue; }
docs.Add(eventPrefab.ConfigElement.Document);
getTextsFromElement(eventPrefab.ConfigElement, lines, eventPrefab.Identifier.Value);
+ NewMessage($"Collecting event texts from event \"{eventPrefab.Identifier}\"...", Color.Cyan);
}
+
+ if (lines.None())
+ {
+ if (sourcePath.IsNullOrEmpty())
+ {
+ ThrowError("Could not find any event texts. Have all the texts already been moved from the event files to the text files?");
+ }
+ else
+ {
+ ThrowError($"Could not find any event texts from \"{sourcePath}\". Are you sure the path is to a valid event xml file or a directory that contains event xml files?");
+ }
+ return;
+ }
+
Barotrauma.IO.Validation.SkipValidationInDebugBuilds = true;
- File.WriteAllLines(filePath, lines);
try
{
- ToolBox.OpenFileWithShell(Path.GetFullPath(filePath));
+ File.WriteAllLines(destinationPath, lines);
}
catch (Exception e)
{
- ThrowError($"Failed to open the file \"{filePath}\".", e);
+ ThrowError($"Failed to write to the file \"{destinationPath}\".", e);
+ }
+ try
+ {
+ ToolBox.OpenFileWithShell(Path.GetFullPath(destinationPath));
+ NewMessage($"Wrote the event texts to a text file in \"{destinationPath}\".", Color.Cyan);
+ }
+ catch (Exception e)
+ {
+ ThrowError($"Failed to open the file \"{destinationPath}\".", e);
}
System.Xml.XmlWriterSettings settings = new System.Xml.XmlWriterSettings
@@ -2783,10 +2984,12 @@ namespace Barotrauma
};
foreach (XDocument doc in docs)
{
- using (var writer = XmlWriter.Create(new System.Uri(doc.BaseUri).LocalPath, settings))
+ string filePath = new System.Uri(doc.BaseUri).LocalPath;
+ using (var writer = XmlWriter.Create(filePath, settings))
{
doc.WriteTo(writer);
writer.Flush();
+ NewMessage($"Updated the event file \"{filePath}\".", Color.Cyan);
}
}
Barotrauma.IO.Validation.SkipValidationInDebugBuilds = false;
@@ -2805,14 +3008,10 @@ namespace Barotrauma
text = subTextElement?.GetAttributeString(textAttribute, null);
textElement = subTextElement;
}
- if (text == null)
- {
- AddWarning("Failed to find text from the element " + element.ToString());
- }
}
string textId = $"EventText.{parentName}";
- if (!string.IsNullOrEmpty(text) && !text.Contains("EventText.", StringComparison.OrdinalIgnoreCase))
+ if (!string.IsNullOrEmpty(text) && !text.StartsWith("EventText.", StringComparison.OrdinalIgnoreCase) && !text.StartsWith("Tutorial.", StringComparison.OrdinalIgnoreCase))
{
if (existingTexts.TryGetValue(text, out string existingTextId))
{
@@ -2982,9 +3181,18 @@ namespace Barotrauma
}));
#if DEBUG
+ commands.Add(new Command("playovervc", "Plays a sound over voice chat.", (args) =>
+ {
+ VoipCapture.Instance?.SetOverrideSound(args.Length > 0 ? args[0] : null);
+ }));
+
commands.Add(new Command("checkduplicates", "Checks the given language for duplicate translation keys and writes to file.", (string[] args) =>
{
- if (args.Length != 1) { return; }
+ if (args.Length != 1)
+ {
+ ThrowError("Please specify a language to check.");
+ return;
+ }
TextManager.CheckForDuplicates(args[0].ToIdentifier().ToLanguageIdentifier());
}));
@@ -3443,9 +3651,7 @@ namespace Barotrauma
}
RagdollParams ragdollParams = character.AnimController.RagdollParams;
ragdollParams.LimbScale = MathHelper.Clamp(value, RagdollParams.MIN_SCALE, RagdollParams.MAX_SCALE);
- var pos = character.WorldPosition;
- character.AnimController.Recreate();
- character.TeleportTo(pos);
+ character.AnimController.RecreateAndRespawn();
}, isCheat: true));
commands.Add(new Command("jointscale", "Define the jointscale for the controlled character. Provide id or name if you want to target another character. Note: the changes are not saved!", (string[] args) =>
@@ -3468,9 +3674,7 @@ namespace Barotrauma
}
RagdollParams ragdollParams = character.AnimController.RagdollParams;
ragdollParams.JointScale = MathHelper.Clamp(value, RagdollParams.MIN_SCALE, RagdollParams.MAX_SCALE);
- var pos = character.WorldPosition;
- character.AnimController.Recreate();
- character.TeleportTo(pos);
+ character.AnimController.RecreateAndRespawn();
}, isCheat: true));
commands.Add(new Command("ragdollscale", "Rescale the ragdoll of the controlled character. Provide id or name if you want to target another character. Note: the changes are not saved!", (string[] args) =>
@@ -3494,9 +3698,7 @@ namespace Barotrauma
RagdollParams ragdollParams = character.AnimController.RagdollParams;
ragdollParams.LimbScale = MathHelper.Clamp(value, RagdollParams.MIN_SCALE, RagdollParams.MAX_SCALE);
ragdollParams.JointScale = MathHelper.Clamp(value, RagdollParams.MIN_SCALE, RagdollParams.MAX_SCALE);
- var pos = character.WorldPosition;
- character.AnimController.Recreate();
- character.TeleportTo(pos);
+ character.AnimController.RecreateAndRespawn();
}, isCheat: true));
commands.Add(new Command("recreateragdoll", "Recreate the ragdoll of the controlled character. Provide id or name if you want to target another character.", (string[] args) =>
@@ -3507,21 +3709,43 @@ namespace Barotrauma
ThrowError("Not controlling any character!");
return;
}
- var pos = character.WorldPosition;
- character.AnimController.Recreate();
- character.TeleportTo(pos);
- }, isCheat: true));
+ character.AnimController.RecreateAndRespawn();
+ }, isCheat: true,
+ getValidArgs: () => new[] { GetSpawnedSpeciesNames() }));
- commands.Add(new Command("resetragdoll", "Reset the ragdoll of the controlled character. Provide id or name if you want to target another character.", (string[] args) =>
+ commands.Add(new Command("resetragdoll", "Reset the ragdoll of the controlled character (and all of the same species). Provide species name if you want to target another character.", (string[] args) =>
{
- var character = (args.Length == 0) ? Character.Controlled : FindMatchingCharacter(args, true);
- if (character == null)
+ IEnumerable characters;
+ if (args.Length == 0)
{
- ThrowError("Not controlling any character!");
+ if (Character.Controlled == null)
+ {
+ ThrowError("Invalid species name! Press [TAB] to get valid options in this context.");
+ return;
+ }
+ // Reset all characters of the same species, because the same params affect them too.
+ characters = FindMatchingSpecies(Character.Controlled.SpeciesName.ToString());
+ }
+ else
+ {
+ characters = FindMatchingSpecies(args);
+ }
+ if (characters.None())
+ {
+ ThrowError("Invalid species name!");
return;
}
- character.AnimController.ResetRagdoll(forceReload: true);
- }, isCheat: true));
+ characters.ForEach(c => c.AnimController.ResetRagdoll());
+ foreach (Character character in characters)
+ {
+ // Variant scale multiplier doesn't work without recreating the ragdoll.
+ if (!character.VariantOf.IsEmpty)
+ {
+ character.AnimController.RecreateAndRespawn();
+ }
+ }
+ }, isCheat: true,
+ getValidArgs: () => new[] { GetSpawnedSpeciesNames() }));
commands.Add(new Command("loadanimation", "Loads an animation variation by name for the controlled character. The animation file has to be in the correct animations folder. Note: the changes are not saved!", (string[] args) =>
{
@@ -3545,6 +3769,29 @@ namespace Barotrauma
string fileName = args[1];
character.AnimController.TryLoadAnimation(animationType, Path.GetFileNameWithoutExtension(fileName), out _, throwErrors: true);
}, isCheat: true));
+
+ commands.Add(new Command("startlocalmptestsession", "startlocalmptestsession [(optional) number of clients, defaults to 2]: starts a new mp test session with multiple clients connected to local dedicated server", (string[] args) =>
+ {
+ // if we are not in main menu, exit out
+ if (Screen.Selected != GameMain.MainMenuScreen)
+ {
+ ThrowError("Must be in main menu to start.");
+ return;
+ }
+
+ // try to parse the number of clients
+ int numClients = 2;
+ if (args.Length > 0)
+ {
+ if (!int.TryParse(args[0], out numClients))
+ {
+ ThrowError("Failed to parse the number of clients.");
+ return;
+ }
+ }
+
+ StartLocalMPSession(numClients);
+ }));
commands.Add(new Command("reloadwearables", "Reloads the sprites of all limbs and wearable sprites (clothing) of the controlled character. Provide id or name if you want to target another character.", args =>
{
@@ -3634,7 +3881,7 @@ namespace Barotrauma
ThrowError("Cannot use the flipx command while playing online.");
return;
}
- if (Submarine.MainSub.SubBody != null) { Submarine.MainSub?.FlipX(); }
+ if (Submarine.MainSub?.SubBody != null) { Submarine.MainSub.FlipX(); }
}, isCheat: true));
commands.Add(new Command("head", "Load the head sprite and the wearables (hair etc). Required argument: head id. Optional arguments: hair index, beard index, moustache index, face attachment index.", args =>
@@ -3997,5 +4244,44 @@ namespace Barotrauma
componentCost += itemPrefab.DefaultPrice.Price;
}
}
+
+ public static void StartLocalMPSession(int numClients = 2)
+ {
+ try
+ {
+ if (Process.GetProcessesByName("DedicatedServer").Length == 0)
+ {
+#if WINDOWS
+ Process.Start("DedicatedServer.exe", arguments: "-multiclienttestmode");
+#else
+ Process.Start("./DedicatedServer", arguments: "-multiclienttestmode");
+#endif
+ System.Threading.Thread.Sleep(1000);
+ }
+
+ GameMain.Client = new GameClient("client1",
+ new LidgrenEndpoint(System.Net.IPAddress.Loopback, NetConfig.DefaultPort), "localhost", Option.None());
+
+ numClients = MathHelper.Clamp(numClients, 1, 4);
+
+ if (numClients > 1)
+ {
+ for (int i = 2; i <= numClients; i++)
+ {
+ System.Threading.Thread.Sleep(1000);
+#if WINDOWS
+ Process.Start("Barotrauma.exe", arguments: "-connect server localhost -username client" + i);
+#else
+ Process.Start("./Barotrauma", arguments: "-connect server localhost -username client" + i);
+#endif
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ DebugConsole.ThrowError("Failed to start the local MP test session", e);
+ }
+
+ }
}
}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/EventActions/ConversationAction.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/EventActions/ConversationAction.cs
index efbdb6712..3a7988c21 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Events/EventActions/ConversationAction.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Events/EventActions/ConversationAction.cs
@@ -36,7 +36,8 @@ namespace Barotrauma
{
return
lastActiveAction != null &&
- lastActiveAction.ParentEvent != ParentEvent &&
+ !lastActiveAction.ParentEvent.IsFinished &&
+ lastActiveAction.ParentEvent != ParentEvent &&
Timing.TotalTime < lastActiveAction.lastActiveTime + duration;
}
@@ -101,6 +102,7 @@ namespace Barotrauma
conversationList.BarScroll = (prevSize - conversationList.Content.Rect.Height) / (conversationList.TotalSize - conversationList.Content.Rect.Height);
conversationList.ScrollToEnd(duration: 0.5f);
lastMessageBox.SetBackgroundIcon(eventSprite);
+ MarkMessageBoxAsLastAction(lastMessageBox);
return;
}
}
@@ -123,16 +125,7 @@ namespace Barotrauma
messageBox.AutoClose = false;
GUIStyle.Apply(messageBox.InnerFrame, "DialogBox");
- if (actionInstance != null)
- {
- lastActiveAction = actionInstance;
- actionInstance.lastActiveTime = Timing.TotalTime;
- actionInstance.dialogBox = messageBox;
- }
- else
- {
- messageBox.UserData = new Pair("ConversationAction", actionId.Value);
- }
+ MarkMessageBoxAsLastAction(messageBox);
int padding = GUI.IntScale(16);
@@ -155,6 +148,20 @@ namespace Barotrauma
};
shadow.SetAsFirstChild();
+ void MarkMessageBoxAsLastAction(GUIMessageBox messageBox)
+ {
+ if (actionInstance != null)
+ {
+ lastActiveAction = actionInstance;
+ actionInstance.lastActiveTime = Timing.TotalTime;
+ actionInstance.dialogBox = messageBox;
+ }
+ else
+ {
+ messageBox.UserData = new Pair("ConversationAction", actionId.Value);
+ }
+ }
+
static void RecalculateLastMessage(GUIListBox conversationList, bool append)
{
if (conversationList.Content.Children.LastOrDefault() is GUILayoutGroup lastElement)
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/EventLog.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/EventLog.cs
index 0db59ddea..ce75680fb 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Events/EventLog.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Events/EventLog.cs
@@ -48,6 +48,7 @@ partial class EventLog
textContent,
difficultyIconCount,
icon, GUIStyle.Red,
+ difficultyTooltipText: null,
out GUIImage missionIcon);
if (traitorResults != null &&
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/EventManager.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/EventManager.cs
index d5687b38a..e5540cfa8 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Events/EventManager.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Events/EventManager.cs
@@ -549,6 +549,33 @@ namespace Barotrauma
}
public void ClientRead(IReadMessage msg)
+ {
+ if (GameMain.GameSession.IsRunning && !GameMain.Instance.LoadingScreenOpen)
+ {
+ ClientApplyNetworkMessage(msg);
+ }
+ else
+ {
+ //if the game session is not currently running (round still loading),
+ //we need to wait because the entities the status effect / conversation / etc targets may not exist yet
+ CoroutineManager.StartCoroutine(ApplyNetworkMessageWhenRoundLoaded(msg));
+ }
+ }
+
+ public IEnumerable ApplyNetworkMessageWhenRoundLoaded(IReadMessage msg)
+ {
+ while (GameMain.GameSession is { IsRunning: false } || GameMain.Instance.LoadingScreenOpen)
+ {
+ yield return new WaitForSeconds(1.0f);
+ }
+ if (GameMain.GameSession != null && GameMain.Client != null)
+ {
+ ClientApplyNetworkMessage(msg);
+ }
+ yield return CoroutineStatus.Success;
+ }
+
+ public void ClientApplyNetworkMessage(IReadMessage msg)
{
NetworkEventType eventType = (NetworkEventType)msg.ReadByte();
switch (eventType)
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/AbandonedOutpostMission.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/AbandonedOutpostMission.cs
index fa15748e0..e3a607d75 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/AbandonedOutpostMission.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/AbandonedOutpostMission.cs
@@ -1,4 +1,4 @@
-using Barotrauma.Extensions;
+using Barotrauma.Extensions;
using Barotrauma.Networking;
namespace Barotrauma
@@ -21,7 +21,12 @@ namespace Barotrauma
}
}
- public override bool DisplayAsCompleted => State > 0 && requireRescue.None();
+ public override bool DisplayAsCompleted =>
+ !DisplayAsFailed &&
+ State > 0 &&
+ //don't display as completed mid-round if there's NPCs to rescue (mission isn't guaranteed to complete yet)
+ requireRescue.None();
+
public override bool DisplayAsFailed => State == HostagesKilledState;
public override void ClientReadInitial(IReadMessage msg)
@@ -47,7 +52,7 @@ namespace Barotrauma
#if CLIENT
if (allowOrderingRescuees)
{
- GameMain.GameSession.CrewManager.AddCharacterToCrewList(character);
+ GameMain.GameSession.CrewManager?.AddCharacterToCrewList(character);
}
#endif
}
@@ -59,10 +64,13 @@ namespace Barotrauma
if (character.Submarine != null && character.AIController is EnemyAIController enemyAi)
{
enemyAi.UnattackableSubmarines.Add(character.Submarine);
- enemyAi.UnattackableSubmarines.Add(Submarine.MainSub);
- foreach (Submarine sub in Submarine.MainSub.DockedTo)
+ if (Submarine.MainSub != null)
{
- enemyAi.UnattackableSubmarines.Add(sub);
+ enemyAi.UnattackableSubmarines.Add(Submarine.MainSub);
+ foreach (Submarine sub in Submarine.MainSub.DockedTo)
+ {
+ enemyAi.UnattackableSubmarines.Add(sub);
+ }
}
}
}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/CombatMission.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/CombatMission.cs
index 291366b9b..6c92275b0 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/CombatMission.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/CombatMission.cs
@@ -1,7 +1,17 @@
-namespace Barotrauma
+using Barotrauma.Networking;
+using Microsoft.Xna.Framework;
+using System.Collections.Generic;
+
+namespace Barotrauma
{
partial class CombatMission : Mission
{
+ private readonly Dictionary clientKills = new Dictionary();
+ private readonly Dictionary clientDeaths = new Dictionary();
+
+ private readonly Dictionary botKills = new Dictionary();
+ private readonly Dictionary botDeaths = new Dictionary();
+
public override LocalizedString Description
{
get
@@ -21,5 +31,93 @@
public override bool DisplayAsCompleted => false;
public override bool DisplayAsFailed => false;
+
+ public override IEnumerable<(LocalizedString Label, Vector2 Position)> SonarLabels
+ {
+ get
+ {
+ if (targetSubmarine == null)
+ {
+ yield break;
+ }
+ else
+ {
+ yield return (targetSubmarineSonarLabel is { Loaded: true } ? targetSubmarineSonarLabel : targetSubmarine.Info.DisplayName, targetSubmarine.WorldPosition);
+ }
+ }
+ }
+
+ public static Color GetTeamColor(CharacterTeamType teamID)
+ {
+ if (teamID == CharacterTeamType.Team1)
+ {
+ return GUIStyle.GetComponentStyle("CoalitionIcon")?.Color ?? GUIStyle.Blue;
+ }
+ else if (teamID == CharacterTeamType.Team2)
+ {
+ return GUIStyle.GetComponentStyle("SeparatistIcon")?.Color ?? GUIStyle.Orange;
+ }
+ return Color.White;
+ }
+
+ public int GetClientKillCount(Client client)
+ {
+ if (clientKills.TryGetValue(client.SessionId, out int kills))
+ {
+ return kills;
+ }
+ return 0;
+ }
+
+ public int GetClientDeathCount(Client client)
+ {
+ if (clientDeaths.TryGetValue(client.SessionId, out int deaths))
+ {
+ return deaths;
+ }
+ return 0;
+ }
+
+ public int GetBotKillCount(CharacterInfo botInfo)
+ {
+ if (botKills.TryGetValue(botInfo.ID, out int kills))
+ {
+ return kills;
+ }
+ return 0;
+ }
+
+ public int GetBotDeathCount(CharacterInfo botInfo)
+ {
+ if (botDeaths.TryGetValue(botInfo.ID, out int deaths))
+ {
+ return deaths;
+ }
+ return 0;
+ }
+
+ public override void ClientRead(IReadMessage msg)
+ {
+ base.ClientRead(msg);
+ Scores[0] = msg.ReadUInt16();
+ Scores[1] = msg.ReadUInt16();
+
+ uint clientCount = msg.ReadVariableUInt32();
+ for (int i = 0; i < clientCount; i++)
+ {
+ byte clientId = msg.ReadByte();
+ clientDeaths[clientId] = (int)msg.ReadVariableUInt32();
+ clientKills[clientId] = (int)msg.ReadVariableUInt32();
+ }
+
+ uint botCount = msg.ReadVariableUInt32();
+ for (int i = 0; i < botCount; i++)
+ {
+ ushort botId = msg.ReadUInt16();
+ botDeaths[botId] = (int)msg.ReadVariableUInt32();
+ botKills[botId] = (int)msg.ReadVariableUInt32();
+ }
+
+ }
}
}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/Mission.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/Mission.cs
index 9081d1751..397097230 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/Mission.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/Mission.cs
@@ -4,7 +4,6 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
-using static Barotrauma.MissionPrefab;
namespace Barotrauma
{
@@ -61,6 +60,16 @@ namespace Barotrauma
return RichString.Rich(TextManager.GetWithVariable("missionreward", "[reward]", "‖color:gui.orange‖" + rewardText + "‖end‖"));
}
+ public RichString GetDifficultyToolTipText()
+ {
+ // 2 skulls give +10% XP, 3 skulls +20% XP and 4 skulls give +30% XP.
+ float xpBonusMultiplier = CalculateDifficultyXPMultiplier();
+ float xpBonusPercentage = (xpBonusMultiplier - 1f) * 100f;
+ int bonusRounded = (int)Math.Round(xpBonusPercentage);
+ LocalizedString tooltipText = TextManager.GetWithVariable(tag: "missiondifficultyxpbonustooltip", varName: "[bonus]", value: bonusRounded.ToString());
+ return RichString.Rich(tooltipText);
+ }
+
public RichString GetReputationRewardText()
{
List reputationRewardTexts = new List();
@@ -117,6 +126,7 @@ namespace Barotrauma
return string.Empty;
}
}
+
partial void DistributeExperienceToCrew(IEnumerable crew, int experienceGain)
{
foreach (Character character in crew)
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/MissionPrefab.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/MissionPrefab.cs
index 0bddcc04f..e69cbb887 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/MissionPrefab.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/MissionPrefab.cs
@@ -49,7 +49,8 @@ namespace Barotrauma
{
return hudIconColor ?? IconColor;
}
- }
+ }
+ public Color ProgressBarColor { get; private set; }
private Sprite hudIcon;
private Color? hudIconColor;
@@ -90,6 +91,7 @@ namespace Barotrauma
}
this.portraits = portraits.ToImmutableArray();
overrideMusicOnState = overrideMusic.ToImmutableDictionary();
+ ProgressBarColor = element.GetAttributeColor(nameof(ProgressBarColor), GUIStyle.Blue);
}
public Identifier GetOverrideMusicType(int state)
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/SalvageMission.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/SalvageMission.cs
index f4af6fdcb..ac06e10fd 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/SalvageMission.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/SalvageMission.cs
@@ -1,4 +1,6 @@
-using Barotrauma.Networking;
+using System.Collections.Generic;
+using System.Linq;
+using Barotrauma.Networking;
using FarseerPhysics;
namespace Barotrauma
@@ -7,26 +9,51 @@ namespace Barotrauma
{
public override bool DisplayAsCompleted => false;
public override bool DisplayAsFailed => false;
+
+ private void TryShowPickedUpMessage() => HandleMessage(ref pickedUpMessage);
private void TryShowRetrievedMessage()
{
if (DetermineCompleted())
{
- if (!allRetrievedMessage.IsNullOrEmpty()) { CreateMessageBox(string.Empty, allRetrievedMessage); }
- //no need to show this again, clear it
- allRetrievedMessage = string.Empty;
+ HandleMessage(ref allRetrievedMessage);
}
else
{
- if (!partiallyRetrievedMessage.IsNullOrEmpty()) { CreateMessageBox(string.Empty, partiallyRetrievedMessage); }
- //no need to show this again, clear it
- partiallyRetrievedMessage = string.Empty;
+ HandleMessage(ref partiallyRetrievedMessage);
}
}
+
+ private void HandleMessage(ref LocalizedString message)
+ {
+ if (!message.IsNullOrEmpty()) { CreateMessageBox(string.Empty, message); }
+ //no need to show this again, clear it
+ message = string.Empty;
+ }
public override void ClientReadInitial(IReadMessage msg)
{
base.ClientReadInitial(msg);
+
+ byte characterCount = msg.ReadByte();
+ for (int i = 0; i < characterCount; i++)
+ {
+ Character character = Character.ReadSpawnData(msg);
+ characters.Add(character);
+ ushort itemCount = msg.ReadUInt16();
+ for (int j = 0; j < itemCount; j++)
+ {
+ Item.ReadSpawnData(msg);
+ }
+ }
+ if (characters.Contains(null))
+ {
+ throw new System.Exception("Error in SalvageMission.ClientReadInitial: character list contains null (mission: " + Prefab.Identifier + ")");
+ }
+ if (characters.Count != characterCount)
+ {
+ throw new System.Exception("Error in SalvageMission.ClientReadInitial: character count does not match the server count (" + characters + " != " + characters.Count + "mission: " + Prefab.Identifier + ")");
+ }
foreach (var target in targets)
{
@@ -81,24 +108,37 @@ namespace Barotrauma
{
base.ClientRead(msg);
bool atLeastOneTargetWasRetrieved = false;
+ bool showPickedUpMsg = false;
int targetCount = msg.ReadByte();
for (int i = 0; i < targetCount; i++)
{
var state = (Target.RetrievalState)msg.ReadByte();
if (i < targets.Count)
{
- bool wasRetrieved = targets[i].Retrieved;
+ Target target = targets[i];
+ bool wasRetrieved = target.Retrieved;
+ bool wasPickedUp = target.State == Target.RetrievalState.PickedUp;
targets[i].State = state;
- if (!wasRetrieved && targets[i].Retrieved)
+ if (!wasRetrieved && target.Retrieved)
{
atLeastOneTargetWasRetrieved = true;
}
+ else if (!wasPickedUp && target.State == Target.RetrievalState.PickedUp)
+ {
+ showPickedUpMsg = true;
+ }
}
}
if (atLeastOneTargetWasRetrieved)
{
TryShowRetrievedMessage();
}
+ if (showPickedUpMsg)
+ {
+ TryShowPickedUpMessage();
+ }
}
+
+ public override IEnumerable HudIconTargets => targets.Where(static t => !t.Retrieved && t.Item.GetRootInventoryOwner() is not Character { IsLocalPlayer: true }).Select(static t => t.Item);
}
}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/ScanMission.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/ScanMission.cs
index 291de4759..7eb69a5e6 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/ScanMission.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/ScanMission.cs
@@ -7,20 +7,7 @@ namespace Barotrauma
{
partial class ScanMission : Mission
{
- public override IEnumerable HudIconTargets
- {
- get
- {
- if (State == 0)
- {
- return scanTargets.Where(kvp => !kvp.Value).Select(kvp => kvp.Key);
- }
- else
- {
- return Enumerable.Empty();
- }
- }
- }
+ public override IEnumerable HudIconTargets => scanTargets.Where(kvp => !kvp.Value).Select(kvp => kvp.Key);
public override bool DisplayAsCompleted => false;
public override bool DisplayAsFailed => false;
@@ -62,7 +49,7 @@ namespace Barotrauma
ushort id = msg.ReadUInt16();
bool scanned = msg.ReadBoolean();
Entity entity = Entity.FindEntityByID(id);
- if (!(entity is WayPoint wayPoint))
+ if (entity is not WayPoint wayPoint)
{
string errorMsg = $"Failed to find a waypoint in ScanMission.ClientReadScanTargetStatus. Entity {id} was {(entity?.ToString() ?? null)}";
DebugConsole.ThrowError(errorMsg);
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Fonts/ScalableFont.cs b/Barotrauma/BarotraumaClient/ClientSource/Fonts/ScalableFont.cs
index 5a41e279f..40532c06a 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Fonts/ScalableFont.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Fonts/ScalableFont.cs
@@ -419,8 +419,7 @@ namespace Barotrauma
if (anyChanges) { textures[^1].SetData(currentDynamicPixelBuffer); }
}
}
-
- // TODO: refactor this further
+
private void HandleNewLineAndAlignment(
string text,
in Vector2 advanceUnit,
@@ -435,23 +434,29 @@ namespace Barotrauma
out uint charIndex,
out bool shouldContinue)
{
- if ((alignment.HasFlag(Alignment.CenterX) || alignment.HasFlag(Alignment.Right)) && (lineWidth < 0.0f || text[i] == '\n'))
+ if (lineWidth < 0.0f || text[i] == '\n')
{
- int startIndex = lineWidth < 0.0f ? i : (i + 1);
- lineWidth = 0.0f;
- for (int j = startIndex; j < text.Length; j++)
+ // Use bitwise operations instead of HasFlag or HasAnyFlag to avoid boxing, as this is performance-sensitive code.
+ bool isHorizontallyCentered = (alignment & Alignment.CenterX) == Alignment.CenterX;
+ bool isAlignedToRight = (alignment & Alignment.Right) == Alignment.Right;
+ if (isHorizontallyCentered || isAlignedToRight)
{
- if (text[j] == '\n') { break; }
- uint chrIndex = text[j];
+ int startIndex = lineWidth < 0.0f ? i : (i + 1);
+ lineWidth = 0.0f;
+ for (int j = startIndex; j < text.Length; j++)
+ {
+ if (text[j] == '\n') { break; }
+ uint chrIndex = text[j];
- var gd2 = GetGlyphData(chrIndex);
- lineWidth += gd2.Advance;
+ var gd2 = GetGlyphData(chrIndex);
+ lineWidth += gd2.Advance;
+ }
+ currentLineOffset = -lineWidth * advanceUnit * scale.X;
+ if (isHorizontallyCentered) { currentLineOffset *= 0.5f; }
+
+ currentLineOffset.X = MathF.Round(currentLineOffset.X);
+ currentLineOffset.Y = MathF.Round(currentLineOffset.Y);
}
- currentLineOffset = -lineWidth * advanceUnit * scale.X;
- if (alignment.HasFlag(Alignment.CenterX)) { currentLineOffset *= 0.5f; }
-
- currentLineOffset.X = MathF.Round(currentLineOffset.X);
- currentLineOffset.Y = MathF.Round(currentLineOffset.Y);
}
if (text[i] == '\n')
{
@@ -493,7 +498,7 @@ namespace Barotrauma
int lineNum = 0;
Vector2 currentPos = position;
- Vector2 advanceUnit = rotation == 0.0f ? Vector2.UnitX : new Vector2((float)Math.Cos(rotation), (float)Math.Sin(rotation));
+ Vector2 advanceUnit = rotation == 0.0f ? Vector2.UnitX : new Vector2(MathF.Cos(rotation), MathF.Sin(rotation));
for (int i = 0; i < text.Length; i++)
{
HandleNewLineAndAlignment(text, advanceUnit, position, scale, alignment, i,
@@ -504,7 +509,7 @@ namespace Barotrauma
GlyphData gd = GetGlyphData(charIndex);
if (gd.TexIndex >= 0)
{
- if (gd.TexIndex < 0 || gd.TexIndex >= textures.Count)
+ if (gd.TexIndex >= textures.Count)
{
throw new ArgumentOutOfRangeException($"Error while rendering text. Texture index was out of range. Text: {text}, char: {charIndex} index: {gd.TexIndex}, texture count: {textures.Count}");
}
@@ -542,6 +547,11 @@ namespace Barotrauma
DynamicRenderAtlas(graphicsDevice, text);
}
+ quadVertices[0].Color = color;
+ quadVertices[1].Color = color;
+ quadVertices[2].Color = color;
+ quadVertices[3].Color = color;
+
Vector2 currentPos = position;
for (int i = 0; i < text.Length; i++)
{
@@ -558,26 +568,33 @@ namespace Barotrauma
if (gd.TexIndex >= 0)
{
float halfCharHeight = gd.TexCoords.Height * 0.5f;
- float slantStrength = 0.35f;
- float topItalicOffset = italics ? ((halfCharHeight - gd.DrawOffset.Y) * slantStrength) + baseHeight * 0.18f : 0.0f;
- float bottomItalicOffset = italics ? ((-halfCharHeight - gd.DrawOffset.Y) * slantStrength) + baseHeight * 0.18f : 0.0f;
-
+ const float slantStrength = 0.35f;
+ float topItalicOffset = 0.0f;
+ float bottomItalicOffset = 0.0f;
+ if (italics)
+ {
+ topItalicOffset = ((halfCharHeight - gd.DrawOffset.Y) * slantStrength) + baseHeight * 0.18f;
+ bottomItalicOffset = ((-halfCharHeight - gd.DrawOffset.Y) * slantStrength) + baseHeight * 0.18f;
+ }
+
Texture2D tex = textures[gd.TexIndex];
+
+ float left = (float)gd.TexCoords.Left / tex.Width;
+ float bottom = (float)gd.TexCoords.Bottom / tex.Height;
+ float top = (float)gd.TexCoords.Top / tex.Height;
+ float right = (float)gd.TexCoords.Right / tex.Width;
+
quadVertices[0].Position = new Vector3(currentPos + gd.DrawOffset + (bottomItalicOffset, gd.TexCoords.Height), 0.0f);
- quadVertices[0].TextureCoordinate = ((float)gd.TexCoords.Left / tex.Width, (float)gd.TexCoords.Bottom / tex.Height);
- quadVertices[0].Color = color;
+ quadVertices[0].TextureCoordinate = new Vector2(left, bottom);
quadVertices[1].Position = new Vector3(currentPos + gd.DrawOffset + (topItalicOffset, 0.0f), 0.0f);
- quadVertices[1].TextureCoordinate = ((float)gd.TexCoords.Left / tex.Width, (float)gd.TexCoords.Top / tex.Height);
- quadVertices[1].Color = color;
+ quadVertices[1].TextureCoordinate = new Vector2(left, top);
quadVertices[2].Position = new Vector3(currentPos + gd.DrawOffset + (gd.TexCoords.Width + bottomItalicOffset, gd.TexCoords.Height), 0.0f);
- quadVertices[2].TextureCoordinate = ((float)gd.TexCoords.Right / tex.Width, (float)gd.TexCoords.Bottom / tex.Height);
- quadVertices[2].Color = color;
+ quadVertices[2].TextureCoordinate = new Vector2(right, bottom);
quadVertices[3].Position = new Vector3(currentPos + gd.DrawOffset + (gd.TexCoords.Width + topItalicOffset, 0.0f), 0.0f);
- quadVertices[3].TextureCoordinate = ((float)gd.TexCoords.Right / tex.Width, (float)gd.TexCoords.Top / tex.Height);
- quadVertices[3].Color = color;
+ quadVertices[3].TextureCoordinate = new Vector2(right, top);
sb.Draw(tex, quadVertices, 0.0f);
}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/ChatBox.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/ChatBox.cs
index 82b582124..c45345891 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/GUI/ChatBox.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/ChatBox.cs
@@ -18,17 +18,40 @@ namespace Barotrauma
public readonly ChatManager ChatManager = new ChatManager();
public bool IsSinglePlayer { get; private set; }
-
+
private bool _toggleOpen = true;
public bool ToggleOpen
{
- get { return _toggleOpen; }
- set
- {
- _toggleOpen = PreferChatBoxOpen = value;
- if (value) { hideableElements.Visible = true; }
- }
+ get => _toggleOpen;
+ set => SetToggleOpenState(value, setPreference: true);
}
+
+ public static ChatBox GetChatBox()
+ {
+ if (GameMain.GameSession?.GameMode is not GameMode gameMode) { return null; }
+ return gameMode.IsSinglePlayer ? GameMain.GameSession.CrewManager?.ChatBox : GameMain.Client?.ChatBox;
+ }
+
+ public static void AutoHideChatBox() => SetChatBoxOpen(false);
+
+ private void SetToggleOpenState(bool value, bool setPreference = true)
+ {
+ _toggleOpen = value;
+ if (setPreference)
+ {
+ PreferChatBoxOpen = value;
+ }
+ if (value) { hideableElements.Visible = true; }
+ }
+
+ public static void ResetChatBoxOpenState() => GetChatBox()?.ResetOpenState();
+
+ public void ResetOpenState() => SetOpen(PreferChatBoxOpen);
+
+ private static void SetChatBoxOpen(bool isOpen) => GetChatBox()?.SetOpen(isOpen);
+
+ private void SetOpen(bool value) => SetToggleOpenState(value, setPreference: false);
+
private float openState;
public static bool PreferChatBoxOpen = true;
@@ -199,7 +222,7 @@ namespace Barotrauma
if (channelMemPending)
{
int.TryParse(channelText.Text, out int newChannel);
- radio.SetChannelMemory(index, newChannel);
+ SetChannelMemory(index, newChannel);
btn.ToolTip = TextManager.GetWithVariables("radiochannelpreset",
("[index]", index.ToString()),
("[channel]", radio.GetChannelMemory(index).ToString()));
@@ -330,7 +353,7 @@ namespace Barotrauma
};
showNewMessagesButton.Visible = false;
- ToggleOpen = PreferChatBoxOpen = GameSettings.CurrentConfig.ChatOpen;
+ SetToggleOpenState(GameSettings.CurrentConfig.ChatOpen, setPreference: true);
}
public void Toggle()
@@ -796,6 +819,15 @@ namespace Barotrauma
}
}
}
+
+ private void SetChannelMemory(int index, int channel)
+ {
+ if (Character.Controlled != null && ChatMessage.CanUseRadio(Character.Controlled, out WifiComponent radio))
+ {
+ radio.SetChannelMemory(index, channel);
+ radio.Item.CreateClientEvent(radio);
+ }
+ }
public void ApplySelectionInputs() => ApplySelectionInputs(InputBox, true, ChatKeyStates.GetChatKeyStates());
diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/DeathPrompt.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/DeathPrompt.cs
index f1cc3b397..4390037fd 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/GUI/DeathPrompt.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/DeathPrompt.cs
@@ -11,13 +11,14 @@ internal class DeathPrompt
{
private static CoroutineHandle? createPromptCoroutine;
+ private GUIFrame? deathPromptFrame;
private GUIComponent? skillPanel;
private GUIComponent? newCharacterPanel;
private GUIComponent? takeOverBotPanel;
private GUIComponent? content;
-
- public static GUIComponent? takeOverBotPanelFrame;
+
+ private static GUIComponent? takeOverBotPanelFrame;
///
/// Private constructor, because these should only be created using the Show method
@@ -58,7 +59,7 @@ internal class DeathPrompt
const float FadeInDuration = 1.0f;
bool permadeath = GameMain.NetworkMember is { ServerSettings.RespawnMode: RespawnMode.Permadeath };
- bool ironman = GameMain.NetworkMember is { ServerSettings: { RespawnMode: RespawnMode.Permadeath, IronmanMode: true } };
+ bool ironman = GameMain.NetworkMember is { ServerSettings.IronmanModeActive: true };
var background = new GUICustomComponent(new RectTransform(Vector2.One, GUI.Canvas, Anchor.Center), onDraw: DrawBackground)
{
@@ -73,11 +74,11 @@ internal class DeathPrompt
foreground.FadeIn(wait: 0, duration: 5.0f);
foreground.Pulsate(startScale: Vector2.One, Vector2.One * 0.8f, duration: 25.0f);
- var frame = new GUIFrame(new RectTransform(new Vector2(0.3f, 0.3f), background.RectTransform, Anchor.Center))
+ deathPromptFrame = new GUIFrame(new RectTransform(new Vector2(0.3f, 0.3f), background.RectTransform, Anchor.Center))
{
UserData = this
};
- frame.FadeIn(wait: 0, duration: FadeInDuration);
+ deathPromptFrame.FadeIn(wait: 0, duration: FadeInDuration);
new GUITextBlock(new RectTransform(new Vector2(0.5f, 0.1f), background.RectTransform, Anchor.TopCenter) { RelativeOffset = new Vector2(0.0f, 0.2f) }, string.Empty, font: GUIStyle.LargeFont, textAlignment: Alignment.TopCenter)
{
@@ -90,7 +91,7 @@ internal class DeathPrompt
}
};
- var content = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 0.8f), frame.RectTransform, Anchor.Center))
+ var content = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 0.8f), deathPromptFrame.RectTransform, Anchor.Center))
{
Stretch = true,
RelativeSpacing = 0.05f
@@ -188,7 +189,7 @@ internal class DeathPrompt
{
if (takeOverBotPanel == null)
{
- CreateTakeOverBotPanel(frame, this);
+ CreateTakeOverBotPanel(deathPromptFrame, this);
}
else
{
@@ -202,7 +203,7 @@ internal class DeathPrompt
}
else
{
- new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), buttonContainerRight.RectTransform), TextManager.Get("deathprompt.respawnnow"))
+ var respawnNowButton = new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), buttonContainerRight.RectTransform), TextManager.Get("deathprompt.respawnnow"))
{
OnClicked = (btn, userdata) =>
{
@@ -211,7 +212,13 @@ internal class DeathPrompt
return true;
},
Enabled = GameMain.NetworkMember is { ServerSettings.RespawnMode: RespawnMode.MidRound }
- }.FadeIn(wait: FadeInInterval * 4, duration: FadeInDuration, alsoChildren: true);
+ };
+ if (GameMain.NetworkMember is { ServerSettings.RespawnMode: RespawnMode.BetweenRounds })
+ {
+ respawnNowButton.ToolTip = TextManager.Get("respawnnotavailable.respawnmode.betweenrounds");
+ }
+
+ respawnNowButton.FadeIn(wait: FadeInInterval * 4, duration: FadeInDuration, alsoChildren: true);
}
//"info buttons" at the bottom
@@ -249,7 +256,7 @@ internal class DeathPrompt
{
if (skillPanel == null)
{
- CreateSkillPanel(frame, GameMain.Client?.Character?.Info ?? GameMain.Client?.CharacterInfo);
+ CreateSkillPanel(deathPromptFrame, GameMain.Client?.Character?.Info ?? GameMain.Client?.CharacterInfo);
}
else
{
@@ -266,7 +273,7 @@ internal class DeathPrompt
{
if (newCharacterPanel == null)
{
- CreateNewCharacterPanel(frame);
+ CreateNewCharacterPanel(deathPromptFrame);
}
else
{
@@ -279,15 +286,6 @@ internal class DeathPrompt
}
}
- //TODO
- /*new GUIButton(new RectTransform(new Vector2(0.4f, 1.0f), infoButtonContainer.RectTransform), "Respawn settings", style: "GUIButtonSmall")
- {
- OnClicked = (btn, userdata) =>
- {
- return true;
- }
- }.FadeIn(wait: FadeInInterval * 5, duration: FadeInDuration, alsoChildren: true);*/
-
this.content = background;
}
@@ -382,8 +380,10 @@ internal class DeathPrompt
{
OnClicked = (btn, userdata) =>
{
- GameMain.NetLobbyScreen.TryDiscardCampaignCharacter(onYes: () =>
- {
+ GameMain.NetLobbyScreen.TryDiscardCampaignCharacter(onYes: () =>
+ {
+ GameMain.Client?.SendCharacterInfo(GameMain.Client.PendingName);
+ GameMain.NetLobbyScreen.CampaignCharacterDiscarded = false;
frame.Parent?.RemoveChild(frame);
newCharacterPanel = null;
});
@@ -465,6 +465,11 @@ internal class DeathPrompt
{
if (botList.SelectedData is CharacterInfo selectedCharacter && GameMain.Client is GameClient client)
{
+ if (!GetAvailableBots().Contains(selectedCharacter)) // Someone may have taken over the bot while the list was open, etc
+ {
+ CreateTakeOverBotPanel(frame, deathPrompt); // Update
+ return true;
+ }
client.SendTakeOverBotRequest(selectedCharacter);
GUIMessageBox.MessageBoxes.Remove(frame.Parent);
deathPrompt?.Close();
@@ -484,15 +489,26 @@ internal class DeathPrompt
return frame;
}
+ public void UpdateBotList()
+ {
+ if (deathPromptFrame != null && takeOverBotPanelFrame != null)
+ {
+ CloseBotPanel();
+ CreateTakeOverBotPanel(deathPromptFrame, deathPrompt: this);
+ }
+ }
+
private static IEnumerable GetAvailableBots()
{
if (GameMain.GameSession?.CrewManager is { } crewManager)
{
- return crewManager.GetCharacterInfos().Where(c =>
- /*either an alive bot */
- c is { Character.IsBot: true, Character.IsDead: false } ||
- /* or a newly hired bot that hasn't spawned yet */
- (c.IsNewHire && c.Character == null));
+ return crewManager.GetCharacterInfos(includeReserveBench: true).Where(c =>
+ // a bot on reserve bench
+ c.IsOnReserveBench ||
+ // an alive bot
+ (c.Character != null && c.Character is { IsBot: true, IsDead: false }) ||
+ // a newly hired bot that hasn't spawned yet
+ (c.Character == null && c.IsNewHire));
}
else
{
diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/FileSelection.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/FileSelection.cs
index d0b10b1e3..c53cc34d1 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/GUI/FileSelection.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/FileSelection.cs
@@ -4,7 +4,6 @@ using System;
using System.Collections.Generic;
using Barotrauma.IO;
using System.Linq;
-using System.Text;
using Barotrauma.Extensions;
namespace Barotrauma
@@ -84,16 +83,31 @@ namespace Barotrauma
{
currentDirectory += "/";
}
- fileSystemWatcher?.Dispose();
- fileSystemWatcher = new System.IO.FileSystemWatcher(currentDirectory)
+ try
{
- Filter = "*",
- NotifyFilter = System.IO.NotifyFilters.LastWrite | System.IO.NotifyFilters.FileName | System.IO.NotifyFilters.DirectoryName
- };
- fileSystemWatcher.Created += OnFileSystemChanges;
- fileSystemWatcher.Deleted += OnFileSystemChanges;
- fileSystemWatcher.Renamed += OnFileSystemChanges;
- fileSystemWatcher.EnableRaisingEvents = true;
+ fileSystemWatcher?.Dispose();
+ fileSystemWatcher = new System.IO.FileSystemWatcher(currentDirectory)
+ {
+ Filter = "*",
+ NotifyFilter = System.IO.NotifyFilters.LastWrite | System.IO.NotifyFilters.FileName | System.IO.NotifyFilters.DirectoryName
+ };
+ fileSystemWatcher.Created += OnFileSystemChanges;
+ fileSystemWatcher.Deleted += OnFileSystemChanges;
+ fileSystemWatcher.Renamed += OnFileSystemChanges;
+ fileSystemWatcher.EnableRaisingEvents = true;
+ }
+ catch (System.IO.FileNotFoundException exception)
+ {
+ DebugConsole.ThrowError("Failed to set the current directory, possibly due to insufficient access permissions.", exception);
+ }
+ catch (ArgumentException exception)
+ {
+ DebugConsole.ThrowError("Failed to set the current directory, possibly because it was deleted.", exception);
+ }
+ catch (Exception exception)
+ {
+ DebugConsole.ThrowError("Failed to set the current directory for an unknown reason.", exception);
+ }
RefreshFileList();
}
}
@@ -218,6 +232,14 @@ namespace Barotrauma
{
if (Directory.Exists(txt))
{
+ var attributes = System.IO.File.GetAttributes(txt);
+ if (attributes.HasAnyFlag(System.IO.FileAttributes.System) || attributes.HasAnyFlag(System.IO.FileAttributes.Hidden))
+ {
+ // System and hidden folders should be filtered out when populating the options, but the user can still write or copy-paste the path in the text field,
+ // which will throw a file not found exception when the file system watcher starts. Therefore, this extra check.
+ tb.Text = CurrentDirectory;
+ return false;
+ }
CurrentDirectory = txt;
return true;
}
@@ -354,20 +376,19 @@ namespace Barotrauma
var directories = Directory.EnumerateDirectories(currentDirectory, "*" + filterBox!.Text + "*");
foreach (var directory in directories)
{
- string txt = directory;
- if (txt.StartsWith(currentDirectory)) { txt = txt.Substring(currentDirectory.Length); }
- if (!txt.EndsWith("/")) { txt += "/"; }
- //get directory info
- DirectoryInfo dirInfo = new DirectoryInfo(directory);
try
{
- //this will throw an exception if the directory can't be opened
- Directory.GetDirectories(directory);
+ //this will intentionally throw an exception if the directory can't be opened
+ System.IO.Directory.GetDirectories(directory);
}
catch (UnauthorizedAccessException)
{
+ // Skip the folders that can't be accessed.
continue;
}
+ string txt = directory;
+ if (txt.StartsWith(currentDirectory)) { txt = txt.Substring(currentDirectory.Length); }
+ if (!txt.EndsWith("/")) { txt += "/"; }
var itemFrame = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), fileList.Content.RectTransform), txt)
{
UserData = ItemIsDirectory.Yes
diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs
index e03511a3e..bc88aaa37 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs
@@ -106,12 +106,35 @@ namespace Barotrauma
public static float VerticalAspectRatio => GameMain.GraphicsHeight / (float)GameMain.GraphicsWidth;
public static float RelativeHorizontalAspectRatio => HorizontalAspectRatio / (ReferenceResolution.X / ReferenceResolution.Y);
public static float RelativeVerticalAspectRatio => VerticalAspectRatio / (ReferenceResolution.Y / ReferenceResolution.X);
+
+ ///
+ /// Returns the difference of the current aspect ratio to the reference aspect ratio (16:9).
+ /// E.g. if the aspect ratio is 16:9, returns 0; if it's 4:3, returns 0.444; if the aspect ratio is 12:5, returns -0.623.
+ ///
+ public static float AspectRatioDifference
+ {
+ get
+ {
+ // ~ 1.777
+ float referenceAspectRatio = ReferenceResolution.X / ReferenceResolution.Y;
+ float aspectRatioDifference = referenceAspectRatio - HorizontalAspectRatio;
+ if (MathUtils.NearlyEqual(aspectRatioDifference, 0))
+ {
+ // Handle possible rounding errors, so that we can trust that this returns 0 when the aspect ratio matches the reference aspect ratio.
+ return 0;
+ }
+ return aspectRatioDifference;
+ }
+ }
+
///
/// A horizontal scaling factor for low aspect ratios (small width relative to height)
///
public static float AspectRatioAdjustment => HorizontalAspectRatio < 1.4f ? (1.0f - (1.4f - HorizontalAspectRatio)) : 1.0f;
public static bool IsUltrawide => HorizontalAspectRatio > 2.3f;
+
+ public static bool IsHUDScaled => GameSettings.CurrentConfig.Graphics.HUDScale > 1 || GameSettings.CurrentConfig.Graphics.InventoryScale > 1;
public static int UIWidth
{
@@ -469,7 +492,7 @@ namespace Barotrauma
"Loaded sounds: " + GameMain.SoundManager.LoadedSoundCount + " (" + GameMain.SoundManager.UniqueLoadedSoundCount + " unique)", Color.White, Color.Black * 0.5f, 0, GUIStyle.SmallFont);
soundTextY += yStep;
- for (int i = 0; i < SoundManager.SOURCE_COUNT; i++)
+ for (int i = 0; i < SoundManager.SourceCount; i++)
{
Color clr = Color.White;
string soundStr = i + ": ";
@@ -625,9 +648,13 @@ namespace Barotrauma
DrawMessages(spriteBatch, cam);
- if (MouseOn != null && !MouseOn.ToolTip.IsNullOrWhiteSpace())
- {
- MouseOn.DrawToolTip(spriteBatch);
+ if (MouseOn != null)
+ {
+ if (!MouseOn.ToolTip.IsNullOrWhiteSpace())
+ {
+ MouseOn.DrawToolTip(spriteBatch);
+ }
+ MouseOn.OnDrawToolTip?.Invoke(MouseOn);
}
if (SubEditorScreen.IsSubEditor())
@@ -1546,7 +1573,7 @@ namespace Barotrauma
private static readonly VertexPositionColorTexture[] donutVerts = new VertexPositionColorTexture[DonutSegments * 4];
public static void DrawDonutSection(
- SpriteBatch sb, Vector2 center, Range radii, float sectionRad, Color clr, float depth = 0.0f)
+ SpriteBatch sb, Vector2 center, Range radii, float sectionRad, Color clr, float depth = 0.0f, float rotationRad = 0.0f)
{
float getRadius(int vertexIndex)
=> (vertexIndex % 4) switch
@@ -1589,7 +1616,7 @@ namespace Barotrauma
for (int vertexIndex = 0; vertexIndex < maxDirectionIndex * 4; vertexIndex++)
{
donutVerts[vertexIndex].Color = clr;
- donutVerts[vertexIndex].Position = new Vector3(center + getDirection(vertexIndex) * getRadius(vertexIndex), 0.0f);
+ donutVerts[vertexIndex].Position = new Vector3(center + Vector2.Transform(getDirection(vertexIndex) * getRadius(vertexIndex), Matrix.CreateRotationZ(rotationRad)), 0.0f);
}
sb.Draw(solidWhiteTexture, donutVerts, depth, count: maxDirectionIndex);
}
@@ -1856,9 +1883,16 @@ namespace Barotrauma
Vector2 pos = new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight) - new Vector2(HUDLayoutSettings.Padding) - 2 * Scale * sheet.FrameSize.ToVector2();
sheet.Draw(spriteBatch, (int)Math.Floor(savingIndicatorSpriteIndex), pos, savingIndicatorColor, origin: Vector2.Zero, rotate: 0.0f, scale: new Vector2(Scale));
}
-#endregion
-#region Element creation
+ public static void DrawCapsule(SpriteBatch sb, Vector2 origin, float length, float radius, float rotation, Color clr, float depth = 0, float thickness = 1)
+ {
+ DrawDonutSection(sb, origin + Vector2.Transform(-new Vector2(length / 2, 0), Matrix.CreateRotationZ(rotation)), new Range(radius - thickness / 2, radius + thickness / 2), MathHelper.Pi, clr, depth, rotation - MathHelper.Pi);
+ DrawRectangle(sb, origin, new Vector2(length, radius * 2), new Vector2(length / 2, radius), rotation, clr, depth, thickness);
+ DrawDonutSection(sb, origin + Vector2.Transform(new Vector2(length / 2, 0), Matrix.CreateRotationZ(rotation)), new Range(radius - thickness / 2, radius + thickness / 2), MathHelper.Pi, clr, depth, rotation);
+ }
+ #endregion
+
+ #region Element creation
public static Texture2D CreateCircle(int radius, bool filled = false)
{
@@ -2488,7 +2522,12 @@ namespace Barotrauma
{
IgnoreLayoutGroups = true,
ToolTip = TextManager.Get("bugreportbutton") + $" (v{GameMain.Version})",
- OnClicked = (btn, userdata) => { GameMain.Instance.ShowBugReporter(); return true; }
+ OnClicked = (btn, userdata) =>
+ {
+ if (PauseMenuOpen) { TogglePauseMenu(); }
+ GameMain.Instance.ShowBugReporter();
+ return true;
+ }
};
CreateButton("PauseMenuResume", buttonContainer, null);
@@ -2524,23 +2563,37 @@ namespace Barotrauma
GameMain.GameSession?.EndRound("");
});
}
- else if (!GameMain.GameSession.GameMode.IsSinglePlayer && GameMain.Client != null && GameMain.Client.HasPermission(ClientPermissions.ManageRound))
+ else if (!GameMain.GameSession.GameMode.IsSinglePlayer && GameMain.Client != null)
{
- bool canSave = GameMain.GameSession.GameMode is CampaignMode && IsFriendlyOutpostLevel();
- if (canSave)
+ //server owner (host) can't return to the lobby without ending the round for everyone
+ if (!GameMain.Client.IsServerOwner)
{
- CreateButton("PauseMenuSaveQuit", buttonContainer, verificationTextTag: "PauseMenuSaveAndReturnToServerLobbyVerification", action: () =>
- {
- GameMain.Client?.RequestRoundEnd(save: true);
- });
+ CreateButton("ReturnToServerlobby", buttonContainer,
+ verificationTextTag: "PauseMenuReturnToServerLobbyVerificationSelf",
+ action: () =>
+ {
+ GameMain.Client?.EndRoundForSelf();
+ });
}
- CreateButton(GameMain.GameSession.GameMode is CampaignMode ? "ReturnToServerlobby" : "EndRound", buttonContainer,
- verificationTextTag: GameMain.GameSession.GameMode is CampaignMode ? "PauseMenuReturnToServerLobbyVerification" : "EndRoundSubNotAtLevelEnd",
- action: () =>
+ if (GameMain.Client.HasPermission(ClientPermissions.ManageRound))
+ {
+ bool canSave = GameMain.GameSession.GameMode is CampaignMode && IsFriendlyOutpostLevel();
+ if (canSave)
{
- GameMain.Client?.RequestRoundEnd(save: false);
- });
+ CreateButton("PauseMenuSaveQuit", buttonContainer, verificationTextTag: "PauseMenuSaveAndReturnToServerLobbyVerification", action: () =>
+ {
+ GameMain.Client?.RequestEndRound(save: true);
+ }, color: GUIStyle.Red);
+ }
+
+ CreateButton("EndRound", buttonContainer,
+ verificationTextTag: GameMain.GameSession.GameMode is CampaignMode ? "PauseMenuReturnToServerLobbyVerification" : "EndRoundSubNotAtLevelEnd",
+ action: () =>
+ {
+ GameMain.Client?.RequestEndRound(save: false);
+ }, color: GUIStyle.Red);
+ }
}
}
@@ -2568,9 +2621,9 @@ namespace Barotrauma
}
- void CreateButton(string textTag, GUIComponent parent, Action action, string verificationTextTag = null)
+ void CreateButton(string textTag, GUIComponent parent, Action action, string verificationTextTag = null, Color? color = null)
{
- new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), parent.RectTransform), TextManager.Get(textTag))
+ var button = new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), parent.RectTransform), TextManager.Get(textTag))
{
OnClicked = (btn, userData) =>
{
@@ -2586,25 +2639,29 @@ namespace Barotrauma
return true;
}
};
+ if (color.HasValue)
+ {
+ button.Color = color.Value;
+ }
}
- void CreateVerificationPrompt(string textTag, Action confirmAction)
+ }
+ public static void CreateVerificationPrompt(string textTag, Action confirmAction)
+ {
+ var msgBox = new GUIMessageBox("", TextManager.Get(textTag),
+ new LocalizedString[] { TextManager.Get("Yes"), TextManager.Get("No") })
{
- var msgBox = new GUIMessageBox("", TextManager.Get(textTag),
- new LocalizedString[] { TextManager.Get("Yes"), TextManager.Get("No") })
- {
- UserData = "verificationprompt",
- DrawOnTop = true
- };
- msgBox.Buttons[0].OnClicked = (_, __) =>
- {
- PauseMenuOpen = false;
- confirmAction?.Invoke();
- return true;
- };
- msgBox.Buttons[0].OnClicked += msgBox.Close;
- msgBox.Buttons[1].OnClicked += msgBox.Close;
- }
+ UserData = "verificationprompt",
+ DrawOnTop = true
+ };
+ msgBox.Buttons[0].OnClicked = (_, __) =>
+ {
+ PauseMenuOpen = false;
+ confirmAction?.Invoke();
+ return true;
+ };
+ msgBox.Buttons[0].OnClicked += msgBox.Close;
+ msgBox.Buttons[1].OnClicked += msgBox.Close;
}
private static bool TogglePauseMenu(GUIButton button, object obj)
@@ -2690,12 +2747,6 @@ namespace Barotrauma
}
}
- public static bool IsFourByThree()
- {
- float aspectRatio = HorizontalAspectRatio;
- return aspectRatio > 1.3f && aspectRatio < 1.4f;
- }
-
public static void SetSavingIndicatorState(bool enabled)
{
if (enabled)
diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIButton.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIButton.cs
index c5e1a2b81..da33ae1cf 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIButton.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIButton.cs
@@ -159,6 +159,34 @@ namespace Barotrauma
}
}
+ private GUIComponent holdOverlay;
+
+ private bool requireHold;
+ public bool RequireHold
+ {
+ get => requireHold;
+ set
+ {
+ requireHold = value;
+ if (value)
+ {
+ holdOverlay ??= new GUIFrame(new RectTransform(new Vector2(0.5f, 1f), Frame.RectTransform, Anchor.CenterLeft), style: null)
+ {
+ Color = GUIStyle.Yellow * 0.33f,
+ CanBeFocused = false,
+ IgnoreLayoutGroups = true,
+ Visible = true
+ };
+ }
+ else if (holdOverlay != null)
+ {
+ holdOverlay.Visible = false;
+ }
+ }
+ }
+ public float HoldDurationSeconds { get; set; } = 5f;
+ private float holdTimer;
+
public bool Pulse { get; set; }
private float pulseTimer;
private float pulseExpand;
@@ -220,7 +248,7 @@ namespace Barotrauma
Rectangle expandRect = Rect;
float expand = (pulseExpand * 20.0f) * GUI.Scale;
expandRect.Inflate(expand, expand);
-
+
GUIStyle.EndRoundButtonPulse.Draw(spriteBatch, expandRect, ToolBox.GradientLerp(pulseExpand, Color.White, Color.White, Color.Transparent));
}
}
@@ -240,6 +268,11 @@ namespace Barotrauma
}
if (PlayerInput.PrimaryMouseButtonHeld())
{
+ if (RequireHold)
+ {
+ holdTimer += deltaTime;
+ }
+
if (OnPressed != null)
{
if (OnPressed())
@@ -254,25 +287,34 @@ namespace Barotrauma
}
else if (PlayerInput.PrimaryMouseButtonClicked())
{
- if (PlaySoundOnSelect)
+ if (!RequireHold || holdTimer > HoldDurationSeconds)
{
- SoundPlayer.PlayUISound(ClickSound);
- }
- if (OnClicked != null)
- {
- if (OnClicked(this, UserData))
+ if (PlaySoundOnSelect)
{
- State = ComponentState.Selected;
+ SoundPlayer.PlayUISound(ClickSound);
+ }
+
+ if (OnClicked != null)
+ {
+ if (OnClicked(this, UserData))
+ {
+ State = ComponentState.Selected;
+ }
+ }
+ else
+ {
+ Selected = !Selected;
}
}
- else
- {
- Selected = !Selected;
- }
+ }
+ else
+ {
+ holdTimer = 0.0f;
}
}
else
{
+ holdTimer = 0.0f;
if (!ExternalHighlight)
{
State = Selected ? ComponentState.Selected : ComponentState.None;
@@ -283,6 +325,20 @@ namespace Barotrauma
}
}
+ if (RequireHold)
+ {
+ float width = MathHelper.Clamp(holdTimer / HoldDurationSeconds, 0f, 1f);
+ if (!MathUtils.NearlyEqual(width, holdOverlay.RectTransform.RelativeSize.X))
+ {
+ holdOverlay.RectTransform.RelativeSize = new Vector2(width, 1f);
+ }
+
+ holdOverlay.Color =
+ holdTimer >= HoldDurationSeconds
+ ? Color.Green * 0.33f
+ : Color.Red * 0.33f;
+ }
+
foreach (GUIComponent child in Children)
{
child.State = State;
diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIComponent.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIComponent.cs
index 8f58c3c11..2abaf9b71 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIComponent.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIComponent.cs
@@ -9,6 +9,7 @@ using Barotrauma.IO;
using RestSharp;
using System.Net;
using Barotrauma.Steam;
+using Steamworks;
namespace Barotrauma
{
@@ -158,6 +159,12 @@ namespace Barotrauma
public Action OnAddedToGUIUpdateList;
+ ///
+ /// Triggers when a tooltip should be draw on the component.
+ /// Note that the callback triggers even if the item has no tooltip (which can be useful for e.g. only contructing the tooltip when needed).
+ ///
+ public Action OnDrawToolTip;
+
public enum ComponentState { None, Hover, Pressed, Selected, HoverSelected };
protected Alignment alignment;
@@ -246,6 +253,57 @@ namespace Barotrauma
{
get { return new Vector2(Rect.Center.X, Rect.Center.Y); }
}
+
+ ///
+ /// Clamps the component's rect position to the specified area. Does not resize the component.
+ ///
+ /// Area to contain the Rect of this component to
+ public void ClampToArea(Rectangle clampArea)
+ {
+ Rectangle componentRect = Rect;
+
+ int x = componentRect.X;
+ int y = componentRect.Y;
+
+ // Adjust the X position
+ if (componentRect.Width <= clampArea.Width)
+ {
+ if (componentRect.Left < clampArea.Left)
+ {
+ x = clampArea.Left;
+ }
+ else if (componentRect.Right > clampArea.Right)
+ {
+ x = clampArea.Right - componentRect.Width;
+ }
+ }
+ else
+ {
+ // Component is wider than clamp area, osition it to overlap as much as possible
+ x = clampArea.Left - (componentRect.Width - clampArea.Width) / 2;
+ }
+
+ // Adjust the Y position
+ if (componentRect.Height <= clampArea.Height)
+ {
+ if (componentRect.Top < clampArea.Top)
+ {
+ y = clampArea.Top;
+ }
+ else if (componentRect.Bottom > clampArea.Bottom)
+ {
+ y = clampArea.Bottom - componentRect.Height;
+ }
+ }
+ else
+ {
+ // Component is taller than clamp area, osition it to overlap as much as possible
+ y = clampArea.Top - (componentRect.Height - clampArea.Height) / 2;
+ }
+
+ Point moveAmount = new Point(x - componentRect.X, y - componentRect.Y);
+ RectTransform.ScreenSpaceOffset += moveAmount;
+ }
protected Rectangle ClampRect(Rectangle r)
{
@@ -1087,6 +1145,8 @@ namespace Barotrauma
FromXML(subElement, component is GUIListBox listBox ? listBox.Content.RectTransform : component.RectTransform);
}
+ component.toolTip = element.GetAttributeString("tooltip", string.Empty);
+
if (element.GetAttributeBool("resizetofitchildren", false))
{
Vector2 relativeResizeScale = element.GetAttributeVector2("relativeresizescale", Vector2.One);
@@ -1129,7 +1189,8 @@ namespace Barotrauma
{
foreach (XAttribute attribute in element.Attributes())
{
- switch (attribute.Name.ToString().ToLowerInvariant())
+ string conditionName = attribute.Name.ToString().ToLowerInvariant();
+ switch (conditionName)
{
case "language":
var languages = element.GetAttributeIdentifierArray(attribute.Name.ToString(), Array.Empty())
@@ -1171,6 +1232,20 @@ namespace Barotrauma
#endif
}
return false;
+ case "mingamelaunches":
+ if (int.TryParse(attribute.Value, out int minLaunches))
+ {
+ return SteamManager.GetStatInt(AchievementStat.GameLaunchCount) > minLaunches;
+ }
+ return false;
+ case "appsubscribed":
+ case "appnotsubscribed":
+ if (SteamManager.IsInitialized &&
+ int.TryParse(attribute.Value, out int appId))
+ {
+ return SteamApps.IsSubscribedToApp(appId) == (conditionName == "appsubscribed");
+ }
+ return false;
}
}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIDropDown.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIDropDown.cs
index 5185b7b63..3116cfd81 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIDropDown.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIDropDown.cs
@@ -9,8 +9,21 @@ namespace Barotrauma
{
public class GUIDropDown : GUIComponent, IKeyboardSubscriber
{
+ /// The component that was selected from the dropdown.
+ /// of the component selected from the dropdown.
public delegate bool OnSelectedHandler(GUIComponent selected, object obj = null);
+ ///
+ /// Triggers when some item is cliecked from the dropdown.
+ /// Note that is not set yet when this callback triggers, and returning false from the callback disallows selecting it.
+ /// If you want to access the new value, use the obj argument.
+ ///
public OnSelectedHandler OnSelected;
+
+ ///
+ /// Triggers after an item has been selected from the dropdown, all validation has been done and the new value has been set.
+ ///
+ public OnSelectedHandler AfterSelected;
+
public OnSelectedHandler OnDropped;
private readonly GUIButton button;
@@ -166,7 +179,7 @@ namespace Barotrauma
public Vector4 Padding => button.TextBlock.Padding;
- public GUIDropDown(RectTransform rectT, LocalizedString text = null, int elementCount = 4, string style = "", bool selectMultiple = false, bool dropAbove = false, Alignment textAlignment = Alignment.CenterLeft) : base(style, rectT)
+ public GUIDropDown(RectTransform rectT, LocalizedString text = null, int elementCount = 4, string style = "", bool selectMultiple = false, bool dropAbove = false, Alignment textAlignment = Alignment.CenterLeft, float listBoxScale = 1) : base(style, rectT)
{
text ??= LocalizedString.EmptyString;
@@ -185,13 +198,21 @@ namespace Barotrauma
Anchor listAnchor = dropAbove ? Anchor.TopCenter : Anchor.BottomCenter;
Pivot listPivot = dropAbove ? Pivot.BottomCenter : Pivot.TopCenter;
- listBox = new GUIListBox(new RectTransform(new Point(Rect.Width, Rect.Height * MathHelper.Clamp(elementCount, 2, 10)), rectT, listAnchor, listPivot)
+ listBox = new GUIListBox(new RectTransform(new Point((int)(Rect.Width * listBoxScale), Rect.Height * MathHelper.Clamp(elementCount, 2, 10)), rectT, listAnchor, listPivot)
{ IsFixedSize = false }, style: null)
{
Enabled = !selectMultiple,
PlaySoundOnSelect = true,
};
- if (!selectMultiple) { listBox.OnSelected = SelectItem; }
+ if (!selectMultiple)
+ {
+ listBox.AfterSelected = (component, obj) =>
+ {
+ SelectItem(component, obj);
+ AfterSelected?.Invoke(component, obj);
+ return true;
+ };
+ }
GUIStyle.Apply(listBox, "GUIListBox", this);
GUIStyle.Apply(listBox.ContentBackground, "GUIListBox", this);
@@ -199,6 +220,8 @@ namespace Barotrauma
{
icon = new GUIImage(new RectTransform(new Vector2(0.6f, 0.6f), button.RectTransform, Anchor.CenterRight, scaleBasis: ScaleBasis.BothHeight) { AbsoluteOffset = new Point(5, 0) }, null, scaleToFit: true);
icon.ApplyStyle(button.Style.ChildStyles["dropdownicon".ToIdentifier()]);
+ //move the text away from the icon
+ button.TextBlock.Padding += new Vector4(0, 0, icon.Rect.Width, 0);
}
currentHighestParent = FindHighestParent();
@@ -249,12 +272,12 @@ namespace Barotrauma
toolTip ??= "";
if (selectMultiple)
{
- var frame = new GUIFrame(new RectTransform(new Point(button.Rect.Width, button.Rect.Height), listBox.Content.RectTransform) { IsFixedSize = false }, style: "ListBoxElement", color: color)
+ var frame = new GUIFrame(new RectTransform(new Point(listBox.Content.Rect.Width, button.Rect.Height), listBox.Content.RectTransform) { IsFixedSize = false }, style: "ListBoxElement", color: color)
{
UserData = userData,
ToolTip = toolTip
};
- new GUITickBox(new RectTransform(new Vector2(1.0f, 0.8f), frame.RectTransform, anchor: Anchor.CenterLeft) { MaxSize = new Point(int.MaxValue, (int)(button.Rect.Height * 0.8f)) }, text)
+ var tickBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.8f), frame.RectTransform, anchor: Anchor.CenterLeft) { MaxSize = new Point(int.MaxValue, (int)(button.Rect.Height * 0.8f)) }, text)
{
UserData = userData,
ToolTip = toolTip,
@@ -266,6 +289,11 @@ namespace Barotrauma
return false;
}
+ if (OnSelected != null && !OnSelected.Invoke(tb.Parent, tb.Parent.UserData))
+ {
+ return false;
+ }
+
List texts = new List();
selectedDataMultiple.Clear();
selectedIndexMultiple.Clear();
@@ -282,8 +310,7 @@ namespace Barotrauma
i++;
}
button.Text = LocalizedString.Join(", ", texts);
- // TODO: The callback is called at least twice, remove this?
- OnSelected?.Invoke(tb.Parent, tb.Parent.UserData);
+ AfterSelected?.Invoke(tb.Parent, SelectedData);
return true;
}
};
@@ -291,7 +318,7 @@ namespace Barotrauma
}
else
{
- return new GUITextBlock(new RectTransform(new Point(button.Rect.Width, button.Rect.Height), listBox.Content.RectTransform) { IsFixedSize = false }, text, style: "ListBoxElement", color: color, textColor: textColor)
+ return new GUITextBlock(new RectTransform(new Point(listBox.Content.Rect.Width, button.Rect.Height), listBox.Content.RectTransform) { IsFixedSize = false }, text, style: "ListBoxElement", color: color, textColor: textColor)
{
UserData = userData,
ToolTip = toolTip
@@ -328,9 +355,8 @@ namespace Barotrauma
}
button.Text = textBlock?.Text ?? "";
}
+ OnSelected?.Invoke(component, obj);
Dropped = false;
- // TODO: OnSelected can be called multiple times and when it shouldn't be called -> turn into an event so that nobody else can call it.
- OnSelected?.Invoke(component, component.UserData);
return true;
}
@@ -344,6 +370,7 @@ namespace Barotrauma
{
listBox.Select(userData);
}
+ AfterSelected?.Invoke(SelectedComponent, SelectedData);
}
public void Select(int index)
@@ -360,6 +387,7 @@ namespace Barotrauma
{
listBox.Select(index);
}
+ AfterSelected?.Invoke(this, SelectedData);
}
private bool wasOpened;
diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIListBox.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIListBox.cs
index aa1a6a882..a96e57bf9 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIListBox.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIListBox.cs
@@ -14,8 +14,17 @@ namespace Barotrauma
protected List selected;
public delegate bool OnSelectedHandler(GUIComponent component, object obj);
+ ///
+ /// Triggers when some element is clicked on the listbox.
+ /// Note that is not set yet when this callback triggers, and returning false from the callback disallows selecting it.
+ ///
public OnSelectedHandler OnSelected;
+ ///
+ /// Triggers after some element has been selected from the listbox.
+ ///
+ public OnSelectedHandler AfterSelected;
+
public delegate object CheckSelectedHandler();
public CheckSelectedHandler CheckSelected;
@@ -1021,7 +1030,7 @@ namespace Barotrauma
while (index < Content.CountChildren)
{
GUIComponent child = Content.GetChild(index);
- if (child.Visible)
+ if (child.Visible && child.CanBeFocused)
{
Select(index, force, GetAutoScroll(!SmoothScroll && autoScroll == AutoScroll.Enabled), takeKeyBoardFocus, playSelectSound);
if (SmoothScroll)
@@ -1040,7 +1049,7 @@ namespace Barotrauma
while (index >= 0)
{
GUIComponent child = Content.GetChild(index);
- if (child.Visible)
+ if (child.Visible && child.CanBeFocused)
{
Select(index, force, GetAutoScroll(!SmoothScroll && autoScroll == AutoScroll.Enabled), takeKeyBoardFocus, playSelectSound);
if (SmoothScroll)
@@ -1151,6 +1160,8 @@ namespace Barotrauma
{
SoundPlayer.PlayUISound(GUISoundType.Select);
}
+
+ AfterSelected?.Invoke(child, SelectedData);
}
public void Select(IEnumerable children)
@@ -1160,8 +1171,9 @@ namespace Barotrauma
selected.Clear();
selected.AddRange(children.Where(c => Content.Children.Contains(c)));
foreach (var child in selected) { OnSelected?.Invoke(child, child.UserData); }
+ AfterSelected?.Invoke(children.FirstOrDefault(), SelectedData);
}
-
+
public void Deselect()
{
Selected = false;
@@ -1172,6 +1184,15 @@ namespace Barotrauma
selected.Clear();
}
+ public void DeselectElement(GUIComponent child)
+ {
+ if (child == null) { return; }
+ if (selected.Contains(child))
+ {
+ selected.Remove(child);
+ }
+ }
+
public void UpdateScrollBarSize()
{
scrollBarNeedsRecalculation = false;
@@ -1268,7 +1289,7 @@ namespace Barotrauma
ContentBackground.DrawManually(spriteBatch, alsoChildren: false);
Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle;
- if (HideChildrenOutsideFrame)
+ if (HideChildrenOutsideFrame && Content.CountChildren > 0)
{
spriteBatch.End();
spriteBatch.GraphicsDevice.ScissorRectangle = Rectangle.Intersect(prevScissorRect, Content.Rect);
@@ -1306,7 +1327,7 @@ namespace Barotrauma
GUI.DrawRectangle(spriteBatch, drawRect, Color.White * 0.5f, thickness: 2f);
}
- if (HideChildrenOutsideFrame)
+ if (HideChildrenOutsideFrame && Content.CountChildren > 0)
{
spriteBatch.End();
spriteBatch.GraphicsDevice.ScissorRectangle = prevScissorRect;
diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIMessageBox.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIMessageBox.cs
index 45b64a5fb..0e9103d47 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIMessageBox.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIMessageBox.cs
@@ -289,7 +289,7 @@ namespace Barotrauma
GUIStyle.Apply(Text, "", this);
Content.Recalculate();
Text.RectTransform.NonScaledSize = Text.RectTransform.MinSize = Text.RectTransform.MaxSize =
- new Point(Text.Rect.Width, Text.Rect.Height);
+ new Point(Text.Rect.Width, Math.Min(Text.Rect.Height, GameMain.GraphicsHeight));
Text.RectTransform.IsFixedSize = true;
if (headerText.IsNullOrWhiteSpace())
{
diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIStyle.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIStyle.cs
index ffb851a97..019548557 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIStyle.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIStyle.cs
@@ -51,6 +51,16 @@ namespace Barotrauma
public readonly static GUISprite InteractionLabelBackground = new GUISprite("InteractionLabelBackground");
public readonly static GUISprite BrokenIcon = new GUISprite("BrokenIcon");
public readonly static GUISprite YouAreHereCircle = new GUISprite("YouAreHereCircle");
+
+ public readonly static GUISprite SubLocationIcon = new GUISprite("SubLocationIcon");
+ public readonly static GUISprite ShuttleIcon = new GUISprite("ShuttleIcon");
+ public readonly static GUISprite WreckIcon = new GUISprite("WreckIcon");
+ public readonly static GUISprite CaveIcon = new GUISprite("CaveIcon");
+ public readonly static GUISprite OutpostIcon = new GUISprite("OutpostIcon");
+ public readonly static GUISprite RuinIcon = new GUISprite("RuinIcon");
+ public readonly static GUISprite EnemyIcon = new GUISprite("EnemyIcon");
+ public readonly static GUISprite CorpseIcon = new GUISprite("CorpseIcon");
+ public readonly static GUISprite BeaconIcon = new GUISprite("BeaconIcon");
public readonly static GUISprite Radiation = new GUISprite("Radiation");
public readonly static GUISpriteSheet RadiationAnimSpriteSheet = new GUISpriteSheet("RadiationAnimSpriteSheet");
@@ -71,7 +81,7 @@ namespace Barotrauma
public readonly static GUISprite EndRoundButtonPulse = new GUISprite("EndRoundButtonPulse");
public readonly static GUISpriteSheet FocusIndicator = new GUISpriteSheet("FocusIndicator");
-
+
public readonly static GUISprite IconOverflowIndicator = new GUISprite("IconOverflowIndicator");
///
diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUITextBox.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUITextBox.cs
index 7dca6fcbc..3d0299054 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUITextBox.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUITextBox.cs
@@ -918,5 +918,12 @@ namespace Barotrauma
DebugConsole.ThrowError($"GUITextBox: Invalid selection: ({exception})");
}
}
+
+ public void ResetDelegates()
+ {
+ OnKeyHit = null;
+ OnEnterPressed = null;
+ OnTextChanged = null;
+ }
}
}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/HRManagerUI.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/HRManagerUI.cs
index b5e0531d3..de339e07e 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/GUI/HRManagerUI.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/HRManagerUI.cs
@@ -38,6 +38,8 @@ namespace Barotrauma
private static bool ReplacingPermanentlyDeadCharacter =>
GameMain.NetworkMember?.ServerSettings is { RespawnMode: RespawnMode.Permadeath, IronmanMode: false } &&
GameMain.Client?.CharacterInfo is { PermanentlyDead: true };
+
+ private static bool ReserveBenchEnabled => GameMain.GameSession?.Campaign is MultiPlayerCampaign;
private bool hadPermissionToHire;
private static bool HasPermissionToHire => ReplacingPermanentlyDeadCharacter ?
@@ -277,13 +279,42 @@ namespace Barotrauma
}
else
{
- PendingHires?.ForEach(ci => AddPendingHire(ci));
+ PendingHires?.ForEach(ci => AddPendingHire(ci, createNetworkMessage: false));
}
SetTotalHireCost();
}
UpdateCrew();
}
+ ///
+ /// This will simply update each of the HR view lists (hireables, pending hires, and crew) from the most up to date information.
+ /// It is a sane version of UpdateLocationView that won't break things even if used outside of whatever arbitrary conditions that one was made for.
+ ///
+ public void RefreshHRView()
+ {
+ if (campaign?.CurrentLocation is not Location currentLocation)
+ {
+ return;
+ }
+
+ if (characterPreviewFrame != null)
+ {
+ characterPreviewFrame.Parent?.RemoveChild(characterPreviewFrame);
+ characterPreviewFrame = null;
+ }
+
+ UpdateHireables(currentLocation);
+
+ if (pendingList != null)
+ {
+ pendingList.Content.ClearChildren();
+ PendingHires?.ForEach(ci => AddPendingHire(ci, checkCrewSizeLimit: false, createNetworkMessage: false)); // don't check limits here, just display the data as it is
+ SetTotalHireCost();
+ }
+
+ UpdateCrew();
+ }
+
public void UpdateHireables()
{
UpdateHireables(campaign?.CurrentLocation);
@@ -329,10 +360,11 @@ namespace Barotrauma
public void UpdateCrew()
{
crewList.Content.Children.ToList().ForEach(c => crewList.Content.RemoveChild(c));
- foreach (CharacterInfo c in GameMain.GameSession.CrewManager.GetCharacterInfos())
+ foreach (CharacterInfo ci in GameMain.GameSession.CrewManager.GetCharacterInfos(includeReserveBench: true))
{
- if (c == null || !((c.Character?.IsBot ?? true) || campaign is SinglePlayerCampaign)) { continue; }
- CreateCharacterFrame(c, crewList);
+ // CrewManager is used to store info on all characters including players, but we only want bots in HR
+ if (ci.Character != null && (ci.Character.IsRemotePlayer || !ci.Character.IsBot)) { continue; }
+ CreateCharacterFrame(ci, crewList);
}
SortCharacters(crewList, SortingMethod.JobAsc);
crewList.UpdateScrollBarSize();
@@ -369,6 +401,10 @@ namespace Barotrauma
((InfoSkill)x.GUIComponent.UserData).SkillLevel.CompareTo(((InfoSkill)y.GUIComponent.UserData).SkillLevel));
if (sortingMethod == SortingMethod.SkillDesc) { list.Content.RectTransform.ReverseChildren(); }
}
+
+ // Always apply this in the end to group by reserve bench status (does nothing if there are no reserve benched bots)
+ list.Content.RectTransform.SortChildren((x, y) =>
+ ((InfoSkill)x.GUIComponent.UserData).CharacterInfo.BotStatus.CompareTo(((InfoSkill)y.GUIComponent.UserData).CharacterInfo.BotStatus));
int? CompareReputationRequirement(GUIComponent c1, GUIComponent c2)
{
@@ -401,6 +437,8 @@ namespace Barotrauma
public GUIComponent CreateCharacterFrame(CharacterInfo characterInfo, GUIListBox listBox, bool hideSalary = false)
{
+ string characterName = listBox == hireableList ? characterInfo.OriginalName : characterInfo.Name;
+
Skill skill = null;
Color? jobColor = null;
if (characterInfo.Job != null)
@@ -415,6 +453,7 @@ namespace Barotrauma
};
GUILayoutGroup mainGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.95f), frame.RectTransform, anchor: Anchor.Center), isHorizontal: true, childAnchor: Anchor.CenterLeft)
{
+ AbsoluteSpacing = 1,
Stretch = true
};
@@ -428,13 +467,15 @@ namespace Barotrauma
GUILayoutGroup nameAndJobGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.4f - portraitWidth, 0.8f), mainGroup.RectTransform)) { CanBeFocused = false };
GUILayoutGroup nameGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.5f), nameAndJobGroup.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft) { CanBeFocused = false };
GUITextBlock nameBlock = new GUITextBlock(new RectTransform(Vector2.One, nameGroup.RectTransform),
- listBox == hireableList ? characterInfo.OriginalName : characterInfo.Name,
+ characterName,
textColor: jobColor, textAlignment: Alignment.BottomLeft)
{
CanBeFocused = false
};
- nameBlock.Text = ToolBox.LimitString(nameBlock.Text, nameBlock.Font, nameBlock.Rect.Width);
-
+ const float smallColumnWidth = 0.6f / 3;
+ const float skillColumnWidth = smallColumnWidth * 0.7f;
+ const float buttonWidth = 0.12f;
+
GUITextBlock jobBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), nameAndJobGroup.RectTransform),
characterInfo.Title ?? characterInfo.Job.Name, textColor: Color.White, font: GUIStyle.SmallFont, textAlignment: Alignment.TopLeft)
{
@@ -449,33 +490,28 @@ namespace Barotrauma
}
}
var fullJobText = jobBlock.Text;
- jobBlock.Text = ToolBox.LimitString(fullJobText, jobBlock.Font, jobBlock.Rect.Width);
- if (jobBlock.Text != fullJobText)
- {
- jobBlock.ToolTip = fullJobText;
- jobBlock.CanBeFocused = true;
- }
- float width = 0.6f / 3;
if (characterInfo.Job != null && skill != null)
{
- GUILayoutGroup skillGroup = new GUILayoutGroup(new RectTransform(new Vector2(width, 0.6f), mainGroup.RectTransform), isHorizontal: true);
+ GUILayoutGroup skillGroup = new GUILayoutGroup(new RectTransform(new Vector2(skillColumnWidth, 0.6f), mainGroup.RectTransform), isHorizontal: true);
float iconWidth = (float)skillGroup.Rect.Height / skillGroup.Rect.Width;
+ new GUITextBlock(new RectTransform(new Vector2(1.0f - iconWidth, 1.0f), skillGroup.RectTransform), ((int)skill.Level).ToString(),
+ textAlignment: Alignment.CenterRight)
+ {
+ Padding = Vector4.Zero,
+ CanBeFocused = false
+ };
GUIImage skillIcon = new GUIImage(new RectTransform(Vector2.One, skillGroup.RectTransform, scaleBasis: ScaleBasis.Smallest), skill.Icon, scaleToFit: true)
{
CanBeFocused = false
};
if (jobColor.HasValue) { skillIcon.Color = jobColor.Value; }
- new GUITextBlock(new RectTransform(new Vector2(1.0f - iconWidth, 1.0f), skillGroup.RectTransform), ((int)skill.Level).ToString(), textAlignment: Alignment.CenterLeft)
- {
- CanBeFocused = false
- };
}
if (!hideSalary)
{
if (listBox != crewList)
{
- new GUITextBlock(new RectTransform(new Vector2(width, 1.0f), mainGroup.RectTransform),
+ new GUITextBlock(new RectTransform(new Vector2(smallColumnWidth, 1.0f), mainGroup.RectTransform),
TextManager.FormatCurrency(ReplacingPermanentlyDeadCharacter ? campaign.NewCharacterCost(characterInfo) : HireManager.GetSalaryFor(characterInfo)),
textAlignment: Alignment.Center)
{
@@ -485,19 +521,24 @@ namespace Barotrauma
else
{
// Just a bit of padding to make list layouts similar
- new GUIFrame(new RectTransform(new Vector2(width, 1.0f), mainGroup.RectTransform), style: null) { CanBeFocused = false };
+ new GUIFrame(new RectTransform(new Vector2(smallColumnWidth, 1.0f), mainGroup.RectTransform), style: null) { CanBeFocused = false };
}
}
if (listBox == hireableList)
{
- var hireButton = new GUIButton(new RectTransform(new Vector2(width, 0.9f), mainGroup.RectTransform), style: "CrewManagementAddButton")
+ var hireButton = new GUIButton(new RectTransform(new Vector2(buttonWidth, 0.9f), mainGroup.RectTransform), style: "CrewManagementAddButton")
{
- ToolTip = TextManager.Get("hirebutton"),
+ ToolTip = TextManager.Get(ReserveBenchEnabled ? "hirebutton.crew" : "hirebutton"),
ClickSound = GUISoundType.Cart,
UserData = characterInfo,
Enabled = CanHire(characterInfo) && !ReplacingPermanentlyDeadCharacter,
- OnClicked = (b, o) => AddPendingHire(o as CharacterInfo)
+ OnClicked = (b, o) =>
+ {
+ var currentCharacterInfo = (CharacterInfo)o;
+ currentCharacterInfo.BotStatus = BotStatus.PendingHireToActiveService;
+ return AddPendingHire(currentCharacterInfo);
+ }
};
hireButton.OnAddedToGUIUpdateList += (GUIComponent btn) =>
{
@@ -505,7 +546,7 @@ namespace Barotrauma
{
return;
}
- if (PendingHires.Count + campaign.CrewManager.GetCharacterInfos().Count() >= CrewManager.MaxCrewSize)
+ if (PendingHires.Count(ci => ci.BotStatus == BotStatus.PendingHireToActiveService) + campaign.CrewManager.GetCharacterInfos().Count() >= CrewManager.MaxCrewSize)
{
if (btn.Enabled)
{
@@ -523,7 +564,7 @@ namespace Barotrauma
if (ReplacingPermanentlyDeadCharacter)
{
bool canHire = CanHire(characterInfo) && campaign.CanAffordNewCharacter(characterInfo);
- var takeoverButton = new GUIButton(new RectTransform(new Vector2(width, 0.9f), mainGroup.RectTransform), style: "CrewManagementTakeControlButton")
+ var takeoverButton = new GUIButton(new RectTransform(new Vector2(buttonWidth, 0.9f), mainGroup.RectTransform), style: "CrewManagementTakeControlButton")
{
ToolTip = canHire ? TextManager.Get("hireandtakecontrol") : TextManager.Get("hireandtakecontroldisabled"),
ClickSound = GUISoundType.ConfirmTransaction,
@@ -554,25 +595,90 @@ namespace Barotrauma
btn.Enabled = canHireCurrently;
};
}
+
+ if (ReserveBenchEnabled && !ReplacingPermanentlyDeadCharacter)
+ {
+ var hireToReserveBenchButton = new GUIButton(new RectTransform(new Vector2(buttonWidth, 0.9f), mainGroup.RectTransform), style: "CrewManagementAddAsReserveButton")
+ {
+ ToolTip = TextManager.Get("hirebutton.reservebench"),
+ ClickSound = GUISoundType.Cart,
+ UserData = characterInfo,
+ Enabled = CanHire(characterInfo),
+ OnClicked = (b, o) =>
+ {
+ var currentCharacterInfo = (CharacterInfo)o;
+ currentCharacterInfo.BotStatus = BotStatus.PendingHireToReserveBench;
+ return AddPendingHire(currentCharacterInfo, checkCrewSizeLimit: false);
+ }
+ };
+ hireToReserveBenchButton.OnAddedToGUIUpdateList += (GUIComponent btn) =>
+ {
+ btn.Visible = ReserveBenchEnabled;
+ btn.Enabled = CanHire(characterInfo) && !ReplacingPermanentlyDeadCharacter;
+ };
+ }
}
else if (listBox == pendingList)
{
- new GUIButton(new RectTransform(new Vector2(width, 0.9f), mainGroup.RectTransform), style: "CrewManagementRemoveButton")
+ if (ReserveBenchEnabled && !ReplacingPermanentlyDeadCharacter)
+ {
+ new GUIButton(new RectTransform(new Vector2(buttonWidth, 0.9f), mainGroup.RectTransform),
+ style: characterInfo.BotStatus == BotStatus.PendingHireToActiveService ? "CrewManagementReserveBenchButtonActive" : "CrewManagementReserveBenchButtonReserve")
+ {
+ UserData = characterInfo,
+ ToolTip = TextManager.Get(characterInfo.BotStatus == BotStatus.PendingHireToActiveService ? "ReserveBenchTogglePendingHire.Active" : "ReserveBenchTogglePendingHire.Reserve"),
+ Enabled = CanHire(characterInfo) && (characterInfo.BotStatus == BotStatus.PendingHireToActiveService || !ActiveServiceFull()), // note that this is a toggle
+ OnClicked = (btn, obj) =>
+ {
+ SelectCharacter(null, null, null);
+ var currentCharacterInfo = (CharacterInfo)obj;
+ GameMain.Client?.ToggleReserveBench(currentCharacterInfo, pendingHire: true);
+ return true;
+ }
+ };
+ }
+
+ new GUIButton(new RectTransform(new Vector2(buttonWidth, 0.9f), mainGroup.RectTransform), style: "CrewManagementRemoveButton")
{
ClickSound = GUISoundType.Cart,
UserData = characterInfo,
- Enabled = CanHire(characterInfo),
+ Enabled = CanHire(characterInfo), // =just check user's rights
OnClicked = (b, o) => RemovePendingHire(o as CharacterInfo)
};
}
else if (listBox == crewList && campaign != null)
{
- var currentCrew = GameMain.GameSession.CrewManager.GetCharacterInfos();
- new GUIButton(new RectTransform(new Vector2(width, 0.9f), mainGroup.RectTransform), style: "CrewManagementFireButton")
+ if (ReserveBenchEnabled && !ReplacingPermanentlyDeadCharacter)
+ {
+ new GUIButton(new RectTransform(new Vector2(buttonWidth, 0.9f), mainGroup.RectTransform),
+ style: characterInfo.BotStatus == BotStatus.ActiveService ? "CrewManagementReserveBenchButtonActive" : "CrewManagementReserveBenchButtonReserve")
+ {
+ UserData = characterInfo,
+ ToolTip = TextManager.Get(characterInfo.BotStatus == BotStatus.ActiveService ? "ReserveBenchToggle.Active" : "ReserveBenchToggle.Reserve"),
+ Enabled = CanHire(characterInfo) && (characterInfo.BotStatus == BotStatus.ActiveService || !ActiveServiceFull()), // note that this is a toggle
+ OnClicked = (btn, obj) =>
+ {
+ SelectCharacter(null, null, null);
+ var currentCharacterInfo = (CharacterInfo)obj;
+ if (currentCharacterInfo.BotStatus == BotStatus.ActiveService && // switching to reserve bench
+ characterInfo.Character != null) // may not have a Character to remove if not spawned this round
+ {
+ GameMain.GameSession.CrewManager.RemoveCharacter(characterInfo.Character, removeInfo: true, resetCrewListIndex: true);
+ }
+ GameMain.Client?.ToggleReserveBench(currentCharacterInfo); // update changes to server
+ return true;
+ }
+ };
+ }
+
+ var cm = GameMain.GameSession.CrewManager;
+ // Can't fire if there's only one character in active service
+ var fireButtonEnabled = HasPermissionToHire && (characterInfo.IsOnReserveBench ||
+ (cm.GetCharacterInfos().Contains(characterInfo) && cm.GetCharacterInfos().Count() > 1));
+ new GUIButton(new RectTransform(new Vector2(buttonWidth, 0.9f), mainGroup.RectTransform), style: "CrewManagementFireButton")
{
UserData = characterInfo,
- //can't fire if there's only one character in the crew
- Enabled = currentCrew.Contains(characterInfo) && currentCrew.Count() > 1 && HasPermissionToHire,
+ Enabled = fireButtonEnabled,
OnClicked = (btn, obj) =>
{
var confirmDialog = new GUIMessageBox(
@@ -587,11 +693,25 @@ namespace Barotrauma
}
};
}
+ else
+ {
+ if (ReserveBenchEnabled && characterInfo.IsOnReserveBench) // Applies to unspecified listings like the death prompt and the bot list after permadeath
+ {
+ new GUIImage(new RectTransform(new Vector2(smallColumnWidth / 2, 0.6f), mainGroup.RectTransform), style: "CrewManagementReserveBenchIconReserve")
+ {
+ ToolTip = TextManager.Get("ReserveBenchStatus.Reserve.WillSpawn")
+ };
+ }
+ else
+ {
+ new GUILayoutGroup(new RectTransform(new Vector2(smallColumnWidth / 2, 0.6f), mainGroup.RectTransform)) { CanBeFocused = false };
+ }
+ }
if (listBox == pendingList || listBox == crewList)
{
nameBlock.RectTransform.Resize(new Point(nameBlock.Rect.Width - nameBlock.Rect.Height, nameBlock.Rect.Height));
- nameBlock.Text = ToolBox.LimitString(nameBlock.Text, nameBlock.Font, nameBlock.Rect.Width);
+ nameBlock.Text = ToolBox.LimitString(characterName, nameBlock.Font, nameBlock.Rect.Width);
nameBlock.RectTransform.Resize(new Point((int)(nameBlock.Padding.X + nameBlock.TextSize.X + nameBlock.Padding.Z), nameBlock.Rect.Height));
Point size = new Point((int)(0.7f * nameBlock.Rect.Height));
new GUIImage(new RectTransform(size, nameGroup.RectTransform), "EditIcon") { CanBeFocused = false };
@@ -605,6 +725,16 @@ namespace Barotrauma
};
}
+ //recalculate everything and truncate texts if needed
+ mainGroup.Recalculate();
+ nameBlock.Text = ToolBox.LimitString(characterName, nameBlock.Font, nameBlock.Rect.Width);
+ jobBlock.Text = ToolBox.LimitString(fullJobText, jobBlock.Font, jobBlock.Rect.Width);
+ if (jobBlock.Text != fullJobText)
+ {
+ jobBlock.ToolTip = fullJobText;
+ jobBlock.CanBeFocused = true;
+ }
+
bool CanHire(CharacterInfo thisCharacterInfo)
{
if (!HasPermissionToHire) { return false; }
@@ -613,6 +743,15 @@ namespace Barotrauma
return frame;
}
+
+ ///
+ /// Is there (going to be) no space left in active service?
+ ///
+ private bool ActiveServiceFull()
+ {
+ return (PendingHires.Count(ci => ci.BotStatus == BotStatus.PendingHireToActiveService) + campaign.CrewManager.GetCharacterInfos().Count())
+ >= CrewManager.MaxCrewSize;
+ }
private bool EnoughReputationToHire(CharacterInfo characterInfo)
{
@@ -656,7 +795,7 @@ namespace Barotrauma
GUILayoutGroup infoLabelGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.4f, 1.0f), infoGroup.RectTransform)) { Stretch = true };
GUILayoutGroup infoValueGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.6f, 1.0f), infoGroup.RectTransform)) { Stretch = true };
float blockHeight = 1.0f / 4;
- new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), infoLabelGroup.RectTransform), TextManager.Get("name"));
+ new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), infoLabelGroup.RectTransform), TextManager.Get("name"), textColor: GUIStyle.TextColorBright);
GUITextBlock nameBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), infoValueGroup.RectTransform), "");
string name = listBox == hireableList ? characterInfo.OriginalName : characterInfo.Name;
nameBlock.Text = ToolBox.LimitString(name, nameBlock.Font, nameBlock.Rect.Width);
@@ -664,17 +803,17 @@ namespace Barotrauma
if (characterInfo.HasSpecifierTags)
{
var menuCategoryVar = characterInfo.Prefab.MenuCategoryVar;
- new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), infoLabelGroup.RectTransform), TextManager.Get(menuCategoryVar));
+ new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), infoLabelGroup.RectTransform), TextManager.Get(menuCategoryVar), textColor: GUIStyle.TextColorBright);
new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), infoValueGroup.RectTransform), TextManager.Get(characterInfo.ReplaceVars($"[{menuCategoryVar}]")));
}
if (characterInfo.Job is Job job)
{
- new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), infoLabelGroup.RectTransform), TextManager.Get("tabmenu.job"));
+ new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), infoLabelGroup.RectTransform), TextManager.Get("tabmenu.job"), textColor: GUIStyle.TextColorBright);
new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), infoValueGroup.RectTransform), job.Name);
}
if (characterInfo.PersonalityTrait is NPCPersonalityTrait trait)
{
- new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), infoLabelGroup.RectTransform), TextManager.Get("PersonalityTrait"));
+ new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), infoLabelGroup.RectTransform), TextManager.Get("PersonalityTrait"), textColor: GUIStyle.TextColorBright);
new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), infoValueGroup.RectTransform), trait.DisplayName);
}
infoLabelGroup.Recalculate();
@@ -727,9 +866,9 @@ namespace Barotrauma
return true;
}
- private bool AddPendingHire(CharacterInfo characterInfo, bool createNetworkMessage = true)
+ private bool AddPendingHire(CharacterInfo characterInfo, bool checkCrewSizeLimit = true, bool createNetworkMessage = true)
{
- if (PendingHires.Count + campaign.CrewManager.GetCharacterInfos().Count() >= CrewManager.MaxCrewSize)
+ if (checkCrewSizeLimit && characterInfo.BotStatus == BotStatus.PendingHireToActiveService && ActiveServiceFull())
{
return false;
}
@@ -792,7 +931,7 @@ namespace Barotrauma
List nonDuplicateHires = new List();
hires.ForEach(hireInfo =>
{
- if (campaign.CrewManager.GetCharacterInfos().None(crewInfo => crewInfo.IsNewHire && crewInfo.GetIdentifierUsingOriginalName() == hireInfo.GetIdentifierUsingOriginalName()))
+ if (campaign.CrewManager.GetCharacterInfos(includeReserveBench: true).None(crewInfo => crewInfo.IsNewHire && crewInfo.GetIdentifierUsingOriginalName() == hireInfo.GetIdentifierUsingOriginalName()))
{
nonDuplicateHires.Add(hireInfo);
}
@@ -806,12 +945,21 @@ namespace Barotrauma
if (!campaign.CanAfford(total)) { return false; }
}
- bool atLeastOneHired = false;
+ bool atLeastOneHiredToActiveDuty = false;
+ bool atLeastOneHiredToReserveBench = false;
foreach (CharacterInfo ci in nonDuplicateHires)
{
+ bool toReserveBench = ci.BotStatus == BotStatus.PendingHireToReserveBench;
if (campaign.TryHireCharacter(campaign.Map.CurrentLocation, ci, takeMoney: takeMoney))
{
- atLeastOneHired = true;
+ if (toReserveBench)
+ {
+ atLeastOneHiredToReserveBench = true;
+ }
+ else
+ {
+ atLeastOneHiredToActiveDuty = true;
+ }
}
else
{
@@ -819,15 +967,27 @@ namespace Barotrauma
}
}
- if (atLeastOneHired)
+ if (atLeastOneHiredToActiveDuty || atLeastOneHiredToReserveBench)
{
UpdateLocationView(campaign.Map.CurrentLocation, true);
SelectCharacter(null, null, null);
if (createNotification)
{
+ LocalizedString msg = string.Empty;
+ if (atLeastOneHiredToActiveDuty)
+ {
+ msg += TextManager.GetWithVariable("crewhiredmessage", "[location]", campaignUI?.Campaign?.Map?.CurrentLocation?.DisplayName);
+ }
+ if (atLeastOneHiredToReserveBench)
+ {
+ if (!msg.IsNullOrEmpty()) { msg += "\n\n"; }
+ msg += GameMain.NetworkMember?.ServerSettings is { RespawnMode: RespawnMode.Permadeath, IronmanMode: false } ?
+ TextManager.Get("crewhiredmessage.reservebench.permadeath") :
+ TextManager.Get( "crewhiredmessage.reservebench");
+ }
+
var dialog = new GUIMessageBox(
- TextManager.Get("newcrewmembers"),
- TextManager.GetWithVariable("crewhiredmessage", "[location]", campaignUI?.Campaign?.Map?.CurrentLocation?.DisplayName),
+ TextManager.Get("newcrewmembers"), msg,
new LocalizedString[] { TextManager.Get("Ok") });
dialog.Buttons[0].OnClicked += dialog.Close;
}
@@ -1034,7 +1194,7 @@ namespace Barotrauma
}
}
- public void SetPendingHires(List characterInfos, Location location)
+ public void SetPendingHires(List characterInfos, bool[] characterInfoReserveBenchStatuses, Location location, bool checkCrewSizeLimit)
{
List oldHires = PendingHires.ToList();
foreach (CharacterInfo pendingHire in oldHires)
@@ -1042,18 +1202,25 @@ namespace Barotrauma
RemovePendingHire(pendingHire, createNetworkMessage: false);
}
PendingHires.Clear();
+ int i = 0;
foreach (UInt16 identifier in characterInfos)
{
CharacterInfo match = location.HireManager.AvailableCharacters.Find(info => info.ID == identifier);
if (match != null)
{
- AddPendingHire(match, createNetworkMessage: false);
+ match.BotStatus = characterInfoReserveBenchStatuses[i] ? BotStatus.PendingHireToReserveBench : BotStatus.PendingHireToActiveService;
+ AddPendingHire(match, checkCrewSizeLimit: checkCrewSizeLimit, createNetworkMessage: false);
+ if (!PendingHires.Contains(match))
+ {
+ DebugConsole.ThrowError("Failed to add a pending hire");
+ }
System.Diagnostics.Debug.Assert(PendingHires.Contains(match));
}
else
{
DebugConsole.ThrowError("Received a hire that doesn't exist.");
}
+ i++;
}
}
@@ -1064,7 +1231,7 @@ namespace Barotrauma
/// When not null tell the server to rename this character. Item1 is the character to rename, Item2 is the new name, Item3 indicates whether the renamed character is already a part of the crew.
/// When not null tell the server to fire this character
/// When set to true will tell the server to validate pending hires
- public void SendCrewState(bool updatePending, (CharacterInfo info, string newName) renameCharacter = default, CharacterInfo firedCharacter = null, bool validateHires = false)
+ public void SendCrewState(bool updatePending = false, (CharacterInfo info, string newName) renameCharacter = default, CharacterInfo firedCharacter = null, bool validateHires = false)
{
if (campaign is MultiPlayerCampaign)
{
@@ -1078,6 +1245,7 @@ namespace Barotrauma
foreach (CharacterInfo pendingHire in PendingHires)
{
msg.WriteUInt16(pendingHire.ID);
+ msg.WriteBoolean(pendingHire.BotStatus == BotStatus.PendingHireToReserveBench);
}
}
@@ -1089,7 +1257,9 @@ namespace Barotrauma
{
msg.WriteUInt16(renameCharacter.info.ID);
msg.WriteString(renameCharacter.newName);
- bool existingCrewMember = campaign.CrewManager?.GetCharacterInfos().Any(ci => ci.ID == renameCharacter.info.ID) ?? false;
+ bool existingCrewMember =
+ campaign.CrewManager is CrewManager crewManager &&
+ crewManager.GetCharacterInfos(includeReserveBench: true).Any(ci => ci.ID == renameCharacter.info.ID);
msg.WriteBoolean(existingCrewMember);
}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/Store.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/Store.cs
index 372f1d499..b635014ad 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/GUI/Store.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/Store.cs
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
+using Microsoft.Xna.Framework.Input;
using PlayerBalanceElement = Barotrauma.CampaignUI.PlayerBalanceElement;
namespace Barotrauma
@@ -1562,7 +1563,7 @@ namespace Barotrauma
bool locationHasDealOnItem = isSellingRelatedList ?
ActiveStore.RequestedGoods.Contains(pi.ItemPrefab) : ActiveStore.DailySpecials.Contains(pi.ItemPrefab);
GUITextBlock nameBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.4f), nameAndQuantityGroup.RectTransform),
- pi.ItemPrefab.Name, font: GUIStyle.SubHeadingFont, textAlignment: Alignment.BottomLeft)
+ RichString.Rich(pi.ItemPrefab.Name), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.BottomLeft)
{
CanBeFocused = false,
Shadow = locationHasDealOnItem,
@@ -1573,15 +1574,24 @@ namespace Barotrauma
if (locationHasDealOnItem)
{
var relativeWidth = (0.9f * nameAndQuantityFrame.Rect.Height) / nameAndQuantityFrame.Rect.Width;
+ Vector2 dealIconSize = new Vector2(relativeWidth, 0.9f) * 0.5f;
var dealIcon = new GUIImage(
- new RectTransform(new Vector2(relativeWidth, 0.9f), nameAndQuantityFrame.RectTransform, anchor: Anchor.CenterLeft)
+ new RectTransform(dealIconSize, nameAndQuantityFrame.RectTransform, anchor: Anchor.CenterRight)
{
AbsoluteOffset = new Point((int)nameBlock.Padding.X, 0)
},
"StoreDealIcon", scaleToFit: true)
{
- CanBeFocused = false
+ CanBeFocused = false,
+ UserData = "StoreDealIcon"
};
+ var dealIconColor = dealIcon.Color;
+ if (forceDisable)
+ {
+ dealIconColor.A = 0;
+ }
+
+ dealIcon.Color = dealIconColor;
dealIcon.SetAsFirstChild();
}
bool isParentOnLeftSideOfInterface = parentComponent == storeBuyList || parentComponent == storeDailySpecialsGroup ||
@@ -1713,7 +1723,7 @@ namespace Barotrauma
mainGroup.Recalculate();
mainGroup.RectTransform.RecalculateChildren(true, true);
amountInput?.LayoutGroup.Recalculate();
- nameBlock.Text = ToolBox.LimitString(nameBlock.Text, nameBlock.Font, nameBlock.Rect.Width);
+ nameBlock.Text = ToolBox.LimitString(nameBlock.Text.SanitizedString, nameBlock.Font, nameBlock.Rect.Width);
mainGroup.RectTransform.Children.ForEach(c => c.IsFixedSize = true);
return frame;
@@ -1795,6 +1805,9 @@ namespace Barotrauma
private void SetItemFrameStatus(GUIComponent itemFrame, bool enabled)
{
+ float full = 1f;
+ float dim = 0.7f;
+ float alpha = (enabled ? full : dim);
if (itemFrame?.UserData is not PurchasedItem pi) { return; }
bool refreshFrameStatus = !pi.IsStoreComponentEnabled.HasValue || pi.IsStoreComponentEnabled.Value != enabled;
if (!refreshFrameStatus) { return; }
@@ -1802,14 +1815,14 @@ namespace Barotrauma
{
if (pi.ItemPrefab?.InventoryIcon != null)
{
- icon.Color = pi.ItemPrefab.InventoryIconColor * (enabled ? 1.0f : 0.5f);
+ icon.Color = pi.ItemPrefab.InventoryIconColor * alpha;
}
else if (pi.ItemPrefab?.Sprite != null)
{
- icon.Color = pi.ItemPrefab.SpriteColor * (enabled ? 1.0f : 0.5f);
+ icon.Color = pi.ItemPrefab.SpriteColor * alpha;
}
};
- var color = Color.White * (enabled ? 1.0f : 0.5f);
+ var color = Color.White * alpha;
if (itemFrame.FindChild("name", recursive: true) is GUITextBlock name)
{
name.TextColor = color;
@@ -1835,7 +1848,7 @@ namespace Barotrauma
}
if (itemFrame.FindChild("price", recursive: true) is GUITextBlock priceBlock)
{
- priceBlock.TextColor = isDiscounted ? storeSpecialColor * (enabled ? 1.0f : 0.5f) : color;
+ priceBlock.TextColor = isDiscounted ? storeSpecialColor * alpha : color;
}
if (itemFrame.FindChild("addbutton", recursive: true) is GUIButton addButton)
{
@@ -1845,6 +1858,10 @@ namespace Barotrauma
{
removeButton.Enabled = enabled;
}
+ if (itemFrame.FindChild("StoreDealIcon", recursive: true) is GUIImage dealIcon)
+ {
+ dealIcon.Color = dealIcon.Color * alpha;
+ }
pi.IsStoreComponentEnabled = enabled;
itemFrame.UserData = pi;
}
@@ -2271,6 +2288,15 @@ namespace Barotrauma
{
updateStopwatch.Restart();
+ if (GameMain.DevMode)
+ {
+ if (PlayerInput.KeyDown(Keys.D0))
+ {
+ CreateUI();
+ needsRefresh = true;
+ }
+ }
+
if (GameMain.GraphicsWidth != resolutionWhenCreated.X || GameMain.GraphicsHeight != resolutionWhenCreated.Y)
{
CreateUI();
diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/SubmarineSelection.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/SubmarineSelection.cs
index 6fc380b99..dac7ac2d0 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/GUI/SubmarineSelection.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/SubmarineSelection.cs
@@ -742,8 +742,8 @@ namespace Barotrauma
private (LocalizedString header, LocalizedString body) GetItemTransferWarningText()
{
- var header = TextManager.Get("itemtransferheader").Fallback("lowfuelheader", useDefaultLanguageIfFound: false);
- var body = TextManager.Get("itemtransferwarning").Fallback("lowfuelwarning", useDefaultLanguageIfFound: false);
+ var header = TextManager.Get("itemtransferheader").Fallback(TextManager.Get("lowfuelheader"), useDefaultLanguageIfFound: false);
+ var body = TextManager.Get("itemtransferwarning").Fallback(TextManager.Get("lowfuelwarning"), useDefaultLanguageIfFound: false);
return (header, body);
}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs
index b71854188..87a7c8cb5 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs
@@ -120,9 +120,20 @@ namespace Barotrauma
{
if (Client == null) { return; }
if (currentPing == Client.Ping) { return; }
- currentPing = Client.Ping;
- textBlock.Text = currentPing.ToString();
- textBlock.TextColor = GetPingColor();
+ if (GameMain.NetworkMember != null && GameMain.NetworkMember.ConnectedClients.Contains(Client))
+ {
+ currentPing = Client.Ping;
+ textBlock.Text = currentPing.ToString();
+ textBlock.TextColor = GetPingColor();
+ textBlock.ToolTip = string.Empty;
+ }
+ else
+ {
+ currentPing = 0;
+ textBlock.Text = "-";
+ textBlock.TextColor = GUIStyle.Red;
+ textBlock.ToolTip = TextManager.Get("causeofdeathdescription.disconnected");
+ }
}
public void TryPermissionIconRefresh(Sprite icon)
@@ -416,7 +427,10 @@ namespace Barotrauma
=> TextManager.GetWithVariable("percentageformat", "[value]", $"{(int)MathF.Round(value)}");
}
- var submarineButton = createTabButton(InfoFrameTab.Submarine, "submarine");
+ if (Submarine.MainSub != null)
+ {
+ createTabButton(InfoFrameTab.Submarine, "submarine");
+ }
var talentsButton = createTabButton(InfoFrameTab.Talents, "tabmenu.character");
talentsButton.OnAddedToGUIUpdateList += (component) =>
@@ -458,17 +472,19 @@ namespace Barotrauma
CreateSubmarineInfo(infoFrameHolder, Submarine.MainSub);
break;
case InfoFrameTab.Talents:
- talentMenu.CreateGUI(infoFrameHolder, Character.Controlled ?? GameMain.Client?.Character);
+ talentMenu.CreateGUI(infoFrameHolder, Character.Controlled?.Info ?? GameMain.Client?.CharacterInfo);
break;
}
}
- private const float jobColumnWidthPercentage = 0.138f,
- characterColumnWidthPercentage = 0.45f,
- pingColumnWidthPercentage = 0.206f,
- walletColumnWidthPercentage = 0.206f;
+ private const float JobColumnWidthPercentage = 0.138f,
+ CharacterColumnWidthPercentage = 0.45f,
+ KillColumnWidthPercentage = 0.1f,
+ DeathColumnWidthPercentage = 0.1f,
+ PingColumnWidthPercentage = 0.15f,
+ WalletColumnWidthPercentage = 0.206f;
- private int jobColumnWidth, characterColumnWidth, pingColumnWidth, walletColumnWidth;
+ private int jobColumnWidth, characterColumnWidth, pingColumnWidth, walletColumnWidth, deathColumnWidth, killColumnWidth;
private void CreateCrewListFrame(GUIFrame crewFrame)
{
@@ -496,11 +512,21 @@ namespace Barotrauma
{
if (teamIDs.Count > 1)
{
- new GUITextBlock(new RectTransform(new Vector2(1.0f, nameHeight), content.RectTransform), CombatMission.GetTeamName(teamIDs[i]), textColor: i == 0 ? GUIStyle.Green : GUIStyle.Orange) { ForceUpperCase = ForceUpperCase.Yes };
+ var nameText = new GUITextBlock(new RectTransform(new Vector2(1.0f, nameHeight), content.RectTransform), CombatMission.GetTeamName(teamIDs[i]), textColor: CombatMission.GetTeamColor(teamIDs[i]))
+ {
+ ForceUpperCase = ForceUpperCase.Yes
+ };
+ var teamIcon = new GUIImage(new RectTransform(Vector2.One, nameText.RectTransform, Anchor.CenterLeft, scaleBasis: ScaleBasis.BothHeight),
+ style: teamIDs[i] == CharacterTeamType.Team2 ? "SeparatistIcon" : "CoalitionIcon")
+ {
+ Color = nameText.TextColor
+ };
+ nameText.Padding = new Vector4(teamIcon.Rect.Width + nameText.Padding.X, nameText.Padding.Y, nameText.Padding.Z, nameText.Padding.W);
}
headerFrames[i] = new GUILayoutGroup(new RectTransform(Vector2.Zero, content.RectTransform, Anchor.TopLeft, Pivot.BottomLeft) { AbsoluteOffset = new Point(2, -1) }, isHorizontal: true)
{
+ Stretch = true,
AbsoluteSpacing = 2,
UserData = i
};
@@ -587,8 +613,8 @@ namespace Barotrauma
sizeMultiplier = (headerFrame.Rect.Width - headerFrame.AbsoluteSpacing * (headerFrame.CountChildren - 1)) / (float)headerFrame.Rect.Width;
- jobButton.RectTransform.RelativeSize = new Vector2(jobColumnWidthPercentage * sizeMultiplier, 1f);
- characterButton.RectTransform.RelativeSize = new Vector2((1f - jobColumnWidthPercentage * sizeMultiplier) * sizeMultiplier, 1f);
+ jobButton.RectTransform.RelativeSize = new Vector2(JobColumnWidthPercentage * sizeMultiplier, 1f);
+ characterButton.RectTransform.RelativeSize = new Vector2((1f - JobColumnWidthPercentage * sizeMultiplier) * sizeMultiplier, 1f);
jobButton.TextBlock.Font = characterButton.TextBlock.Font = GUIStyle.HotkeyFont;
jobButton.CanBeFocused = characterButton.CanBeFocused = false;
@@ -626,7 +652,8 @@ namespace Barotrauma
var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.9f), frame.RectTransform, Anchor.Center), isHorizontal: true)
{
- AbsoluteSpacing = 2
+ AbsoluteSpacing = 2,
+ Stretch = true
};
new GUICustomComponent(new RectTransform(new Point(jobColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform, Anchor.Center), onDraw: (sb, component) => character.Info.DrawJobIcon(sb, component.Rect))
@@ -639,21 +666,29 @@ namespace Barotrauma
GUITextBlock characterNameBlock = new GUITextBlock(new RectTransform(new Point(characterColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform),
ToolBox.LimitString(character.Info.Name, GUIStyle.Font, characterColumnWidth), textAlignment: Alignment.Center, textColor: character.Info.Job.Prefab.UIColor);
+ paddedFrame.Recalculate();
+
linkedGUIList.Add(new LinkedGUI(character, frame, textBlock: null));
}
private void CreateMultiPlayerListContentHolder(GUILayoutGroup headerFrame)
{
bool isCampaign = GameMain.GameSession?.Campaign is MultiPlayerCampaign;
- GUIButton jobButton = new GUIButton(new RectTransform(new Vector2(0f, 1f), headerFrame.RectTransform), TextManager.Get("tabmenu.job"), style: "GUIButtonSmallFreeScale");
- GUIButton characterButton = new GUIButton(new RectTransform(new Vector2(0f, 1f), headerFrame.RectTransform), TextManager.Get("name"), style: "GUIButtonSmallFreeScale");
- GUIButton pingButton = new GUIButton(new RectTransform(new Vector2(0f, 1f), headerFrame.RectTransform), TextManager.Get("serverlistping"), style: "GUIButtonSmallFreeScale");
+ GUIButton jobButton = new GUIButton(new RectTransform(new Vector2(JobColumnWidthPercentage, 1f), headerFrame.RectTransform), TextManager.Get("tabmenu.job"), style: "GUIButtonSmallFreeScale");
+ GUIButton characterButton = new GUIButton(new RectTransform(new Vector2(CharacterColumnWidthPercentage, 1f), headerFrame.RectTransform), TextManager.Get("name"), style: "GUIButtonSmallFreeScale");
+
+ if (GameMain.GameSession?.GameMode is PvPMode)
+ {
+ var killButton = new GUIButton(new RectTransform(new Vector2(KillColumnWidthPercentage, 1f), headerFrame.RectTransform), TextManager.Get("killcount"), style: "GUIButtonSmallFreeScale");
+ killColumnWidth = killButton.Rect.Width;
+ var deathButton = new GUIButton(new RectTransform(new Vector2(DeathColumnWidthPercentage, 1f), headerFrame.RectTransform), TextManager.Get("deathcount"), style: "GUIButtonSmallFreeScale");
+ deathColumnWidth = deathButton.Rect.Width;
+ }
+
+ GUIButton pingButton = new GUIButton(new RectTransform(new Vector2(PingColumnWidthPercentage, 1f), headerFrame.RectTransform), TextManager.Get("serverlistping"), style: "GUIButtonSmallFreeScale");
if (isCampaign)
{
- GUIButton walletButton = new GUIButton(new RectTransform(new Vector2(0f, 1f), headerFrame.RectTransform)
- {
- RelativeSize = new Vector2(walletColumnWidthPercentage * sizeMultiplier, 1f)
- }, TextManager.Get("crewwallet.wallet"), style: "GUIButtonSmallFreeScale")
+ GUIButton walletButton = new GUIButton(new RectTransform(new Vector2(WalletColumnWidthPercentage, 1f), headerFrame.RectTransform), TextManager.Get("crewwallet.wallet"), style: "GUIButtonSmallFreeScale")
{
TextBlock = { Font = GUIStyle.HotkeyFont },
CanBeFocused = false,
@@ -662,15 +697,12 @@ namespace Barotrauma
walletColumnWidth = walletButton.Rect.Width;
}
- sizeMultiplier = (headerFrame.Rect.Width - headerFrame.AbsoluteSpacing * (headerFrame.CountChildren - 1)) / (float)headerFrame.Rect.Width;
-
- jobButton.RectTransform.RelativeSize = new Vector2(jobColumnWidthPercentage * sizeMultiplier, 1f);
- characterButton.RectTransform.RelativeSize = new Vector2((characterColumnWidthPercentage + (isCampaign ? 0 : walletColumnWidthPercentage)) * sizeMultiplier, 1f);
- pingButton.RectTransform.RelativeSize = new Vector2(pingColumnWidthPercentage * sizeMultiplier, 1f);
-
- jobButton.TextBlock.Font = characterButton.TextBlock.Font = pingButton.TextBlock.Font = GUIStyle.HotkeyFont;
- jobButton.CanBeFocused = characterButton.CanBeFocused = pingButton.CanBeFocused = false;
- jobButton.TextBlock.ForceUpperCase = characterButton.TextBlock.ForceUpperCase = pingButton.ForceUpperCase = ForceUpperCase.Yes;
+ foreach (var btn in headerFrame.GetAllChildren())
+ {
+ btn.TextBlock.Font = GUIStyle.HotkeyFont;
+ btn.CanBeFocused = false;
+ btn.ForceUpperCase = ForceUpperCase.Yes;
+ }
jobColumnWidth = jobButton.Rect.Width;
characterColumnWidth = characterButton.Rect.Width;
@@ -688,45 +720,68 @@ namespace Barotrauma
var connectedClients = GameMain.Client.ConnectedClients;
- for (int i = 0; i < teamIDs.Count; i++)
+ for (int teamID = 0; teamID < teamIDs.Count; teamID++)
{
- foreach (Character character in crew.Where(c => c.TeamID == teamIDs[i]))
+ foreach (Character character in crew.Where(c => c.TeamID == teamIDs[teamID]))
{
- if (!(character is AICharacter) && connectedClients.Any(c => c.Character == null && c.Name == character.Name)) { continue; }
- CreateMultiPlayerCharacterElement(character, GameMain.Client.PreviouslyConnectedClients.FirstOrDefault(c => c.Character == character), i);
+ if (character is not AICharacter && connectedClients.Any(c => c.Character == null && c.Name == character.Name)) { continue; }
+ CreateMultiPlayerCharacterElement(character, GameMain.Client.PreviouslyConnectedClients.FirstOrDefault(c => c.Character == character), teamID);
+ }
+
+ foreach (CharacterInfo characterInfo in GameMain.GameSession.CrewManager?.GetReserveBenchInfos() ?? Enumerable.Empty())
+ {
+ CreateMultiPlayerCharacterElement(character: null, client: null, teamID, justCharacterInfo: characterInfo);
}
}
for (int j = 0; j < connectedClients.Count; j++)
{
Client client = connectedClients[j];
- if (!client.InGame || client.Character == null || client.Character.IsDead)
+ if (client.Character == null || client.Character.IsDead)
{
CreateMultiPlayerClientElement(client);
}
}
}
-
- private void CreateMultiPlayerCharacterElement(Character character, Client client, int i)
+
+ /// The character element can be generated based on just a CharacterInfo, and Character and Client can be left null. Otherwise, those are required and the CharacterInfo of the Character is used.
+ private void CreateMultiPlayerCharacterElement(Character character, Client client, int teamID, CharacterInfo justCharacterInfo = null)
{
- GUIFrame frame = new GUIFrame(new RectTransform(new Point(crewListArray[i].Content.Rect.Width, GUI.IntScale(33f)), crewListArray[i].Content.RectTransform), style: "ListBoxElement")
+ CharacterInfo characterInfo = justCharacterInfo ?? character.Info;
+
+ GUIFrame frame = new GUIFrame(new RectTransform(new Point(crewListArray[teamID].Content.Rect.Width, GUI.IntScale(33f)), crewListArray[teamID].Content.RectTransform), style: "ListBoxElement")
{
- UserData = character,
+ UserData = character != null ? character : characterInfo,
Color = (GameMain.NetworkMember != null && GameMain.Client.Character == character) ? OwnCharacterBGColor : Color.Transparent
};
-
- frame.OnSecondaryClicked += (component, data) =>
+
+ if (client != null)
{
- NetLobbyScreen.CreateModerationContextMenu(client);
- return true;
- };
+ frame.OnSecondaryClicked += (component, data) =>
+ {
+ NetLobbyScreen.CreateModerationContextMenu(client);
+ return true;
+ };
+ }
var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.9f), frame.RectTransform, Anchor.Center), isHorizontal: true)
{
- AbsoluteSpacing = 2
+ AbsoluteSpacing = 2,
+ Stretch = true
};
- new GUICustomComponent(new RectTransform(new Point(jobColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform, Anchor.Center), onDraw: (sb, component) => character.Info.DrawJobIcon(sb, component.Rect))
+ new GUICustomComponent(new RectTransform(new Point(jobColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform, Anchor.Center),
+ onDraw: (sb, component) =>
+ {
+ if (client == null)
+ {
+ characterInfo?.DrawJobIcon(sb, component.Rect);
+ }
+ else
+ {
+ DrawClientJobIcon(sb, component.Rect, client);
+ }
+ })
{
CanBeFocused = false,
HoverColor = Color.White,
@@ -736,6 +791,19 @@ namespace Barotrauma
if (client != null)
{
CreateNameWithPermissionIcon(client, paddedFrame, out GUIImage permissionIcon);
+
+ if (GameMain.GameSession?.GameMode is PvPMode)
+ {
+ new GUITextBlock(new RectTransform(new Point(killColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform), string.Empty, textAlignment: Alignment.Center)
+ {
+ TextGetter = () => GameMain.GameSession.Missions.Sum(m => (m as CombatMission)?.GetClientKillCount(client) ?? 0).ToString()
+ };
+ new GUITextBlock(new RectTransform(new Point(deathColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform), string.Empty, textAlignment: Alignment.Center)
+ {
+ TextGetter = () => GameMain.GameSession.Missions.Sum(m => (m as CombatMission)?.GetClientDeathCount(client) ?? 0).ToString()
+ };
+ }
+
linkedGUIList.Add(new LinkedGUI(client, frame,
new GUITextBlock(new RectTransform(new Point(pingColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform), client.Ping.ToString(), textAlignment: Alignment.Center),
permissionIcon));
@@ -743,27 +811,62 @@ namespace Barotrauma
else
{
GUITextBlock characterNameBlock = new GUITextBlock(new RectTransform(new Point(characterColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform),
- ToolBox.LimitString(character.Info.Name, GUIStyle.Font, characterColumnWidth), textAlignment: Alignment.Center, textColor: character.Info.Job.Prefab.UIColor);
+ ToolBox.LimitString(characterInfo.Name, GUIStyle.Font, characterColumnWidth), textAlignment: Alignment.Center, textColor: characterInfo.Job.Prefab.UIColor);
+
+ if (GameMain.GameSession?.GameMode is PvPMode)
+ {
+ new GUITextBlock(new RectTransform(new Point(killColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform), string.Empty, textAlignment: Alignment.Center)
+ {
+ TextGetter = () => GameMain.GameSession.Missions.Sum(m => (m as CombatMission)?.GetBotKillCount(characterInfo) ?? 0).ToString()
+ };
+ new GUITextBlock(new RectTransform(new Point(deathColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform), string.Empty, textAlignment: Alignment.Center)
+ {
+ TextGetter = () => GameMain.GameSession.Missions.Sum(m => (m as CombatMission)?.GetBotDeathCount(characterInfo) ?? 0).ToString()
+ };
+ }
if (character is AICharacter)
{
- linkedGUIList.Add(new LinkedGUI(character, frame,
- new GUITextBlock(new RectTransform(new Point(pingColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform), TextManager.Get("tabmenu.bot"), textAlignment: Alignment.Center) { ForceUpperCase = ForceUpperCase.Yes }));
+ // "BOT" instead of ping (which isn't relevant for bots)
+ linkedGUIList.Add(new LinkedGUI(character, frame,
+ new GUITextBlock(new RectTransform(new Point(pingColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform), TextManager.Get("tabmenu.bot"), textAlignment: Alignment.Center) { ForceUpperCase = ForceUpperCase.Yes }));
}
- else
+ else if (characterInfo.IsOnReserveBench)
{
- linkedGUIList.Add(new LinkedGUI(client: null, frame, textBlock: null, permissionIcon: null));
-
- new GUICustomComponent(new RectTransform(new Point(pingColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform, Anchor.Center), onDraw: (sb, component) => DrawDisconnectedIcon(sb, component.Rect))
+ // Reserve bench icon
+ new GUIImage(new RectTransform(new Point(pingColumnWidth, paddedFrame.Rect.Height - 4), paddedFrame.RectTransform), style: "CrewManagementReserveBenchIconReserve", scaleToFit: true)
{
- CanBeFocused = false,
- HoverColor = Color.White,
- SelectedColor = Color.White
+ ToolTip = TextManager.Get("ReserveBenchStatus.Reserve")
+ };
+ }
+
+ if (characterInfo.IsOnReserveBench)
+ {
+ //black bar to dim out the elements (1px shorter and to the right so it won't dim the left border too)
+ new GUIFrame(
+ new RectTransform(new Point(paddedFrame.Rect.Width - 1, frame.Rect.Height), paddedFrame.RectTransform, Anchor.Center)
+ {
+ AbsoluteOffset = new Point(1, 0)
+ },
+ style: null, color: Color.Black * 0.7f)
+ {
+ IgnoreLayoutGroups = true,
+ CanBeFocused = false
};
}
}
+
+ if (character != null)
+ {
+ CreateWalletCrewFrame(character, paddedFrame);
+ }
+ else if (characterInfo.IsOnReserveBench)
+ {
+ // Empty column for reserve benched bots
+ new GUILayoutGroup(new RectTransform(new Point(walletColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform, Anchor.Center), childAnchor: Anchor.Center) { CanBeFocused = false };
+ }
- CreateWalletCrewFrame(character, paddedFrame);
+ paddedFrame.Recalculate();
}
private void CreateMultiPlayerClientElement(Client client)
@@ -785,11 +888,12 @@ namespace Barotrauma
var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.9f), frame.RectTransform, Anchor.Center), isHorizontal: true)
{
- AbsoluteSpacing = 2
+ AbsoluteSpacing = 2,
+ Stretch = true
};
new GUICustomComponent(new RectTransform(new Point(jobColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform, Anchor.Center),
- onDraw: (sb, component) => DrawNotInGameIcon(sb, component.Rect, client))
+ onDraw: (sb, component) => DrawClientJobIcon(sb, component.Rect, client))
{
CanBeFocused = false,
HoverColor = Color.White,
@@ -797,14 +901,26 @@ namespace Barotrauma
};
CreateNameWithPermissionIcon(client, paddedFrame, out GUIImage permissionIcon);
+
+ if (GameMain.GameSession?.GameMode is PvPMode)
+ {
+ new GUITextBlock(new RectTransform(new Point(killColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform), string.Empty, textAlignment: Alignment.Center)
+ {
+ TextGetter = () => GameMain.GameSession.Missions.Sum(m => (m as CombatMission)?.GetClientKillCount(client) ?? 0).ToString()
+ };
+ new GUITextBlock(new RectTransform(new Point(deathColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform), string.Empty, textAlignment: Alignment.Center)
+ {
+ TextGetter = () => GameMain.GameSession.Missions.Sum(m => (m as CombatMission)?.GetClientDeathCount(client) ?? 0).ToString()
+ };
+ }
+
linkedGUIList.Add(new LinkedGUI(client, frame,
new GUITextBlock(new RectTransform(new Point(pingColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform), client.Ping.ToString(), textAlignment: Alignment.Center),
permissionIcon));
- if (client.Character is { } character)
- {
- CreateWalletCrewFrame(character, paddedFrame);
- }
+ CreateWalletCrewFrame(client.Character, paddedFrame);
+
+ paddedFrame.Recalculate();
}
private int GetTeamIndex(Client client)
@@ -837,12 +953,12 @@ namespace Barotrauma
}
}
- return 0;
+ return teamIDs.IndexOf(client.TeamID);
}
private void CreateWalletCrewFrame(Character character, GUILayoutGroup paddedFrame)
{
- if (!(GameMain.GameSession?.Campaign is MultiPlayerCampaign)) { return; }
+ if (GameMain.GameSession?.Campaign is not MultiPlayerCampaign) { return; }
GUILayoutGroup walletLayout = new GUILayoutGroup(new RectTransform(new Point(walletColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform, Anchor.Center), childAnchor: Anchor.Center)
{
@@ -860,7 +976,7 @@ namespace Barotrauma
ToolTip = TextManager.Get("walletdescription")
};
- if (character.IsBot) { return; }
+ if (character == null || character.IsBot) { return; }
Sprite walletSprite = GUIStyle.CrewWalletIconSmall.Value.Sprite;
@@ -947,7 +1063,6 @@ namespace Barotrauma
float iconWidth = iconSize.X / (float)characterColumnWidth;
int xOffset = (int)(jobColumnWidth + characterNameBlock.TextPos.X - GUIStyle.Font.MeasureString(characterNameBlock.Text).X / 2f - paddedFrame.AbsoluteSpacing - iconWidth * paddedFrame.Rect.Width);
permissionIcon = new GUIImage(new RectTransform(new Vector2(iconWidth, 1f), paddedFrame.RectTransform) { AbsoluteOffset = new Point(xOffset + 2, 0) }, permissionIconSprite) { IgnoreLayoutGroups = true };
-
if (client.Character != null && client.Character.IsDead)
{
@@ -969,18 +1084,15 @@ namespace Barotrauma
}
}
- private void DrawNotInGameIcon(SpriteBatch spriteBatch, Rectangle area, Client client)
+ private void DrawClientJobIcon(SpriteBatch spriteBatch, Rectangle area, Client client)
{
if (client.Spectating)
{
spectateIcon.Draw(spriteBatch, area, Color.White);
}
- else if (client.Character != null && client.Character.IsDead)
+ else if (client.Character != null && client.InGame)
{
- if (client.Character.Info != null)
- {
- client.Character.Info.DrawJobIcon(spriteBatch, area);
- }
+ client.Character.Info?.DrawJobIcon(spriteBatch, area);
}
else
{
@@ -1004,7 +1116,13 @@ namespace Barotrauma
GUIComponent existingPreview = infoFrameHolder.FindChild("SelectedCharacter");
if (existingPreview != null) { infoFrameHolder.RemoveChild(existingPreview); }
-
+
+ if (userData is CharacterInfo { IsOnReserveBench: true })
+ {
+ return true;
+ }
+
+ // Modal info panel that pops up on the right
GUIFrame background = new GUIFrame(new RectTransform(new Vector2(0.543f, 0.69f), infoFrameHolder.RectTransform, Anchor.TopRight, Pivot.TopLeft) { RelativeOffset = new Vector2(-0.061f, 0) })
{
UserData = "SelectedCharacter"
@@ -1028,7 +1146,7 @@ namespace Barotrauma
{
talentButton.OnClicked = (button, o) =>
{
- talentMenu.CreateGUI(infoFrameHolder, character);
+ talentMenu.CreateGUI(infoFrameHolder, character.Info);
return true;
};
}
@@ -1413,7 +1531,7 @@ namespace Barotrauma
var headerArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.322f), paddedFrame.RectTransform), isHorizontal: true);
new GUICustomComponent(new RectTransform(new Vector2(0.425f, 1.0f), headerArea.RectTransform),
- onDraw: (sb, component) => DrawNotInGameIcon(sb, component.Rect, client));
+ onDraw: (sb, component) => DrawClientJobIcon(sb, component.Rect, client));
GUIFont font = paddedFrame.Rect.Width < 280 ? GUIStyle.SmallFont : GUIStyle.Font;
@@ -1503,6 +1621,11 @@ namespace Barotrauma
}
linkedGUIList.Clear();
+
+ foreach (GUIListBox crewList in crewListArray)
+ {
+ crewList.Content.ClearChildren();
+ }
}
private void AddLineToLog(string line, PlayerConnectionChangeType type)
@@ -1587,7 +1710,7 @@ namespace Barotrauma
}
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), locationInfoContainer.RectTransform), location.DisplayName, font: GUIStyle.LargeFont);
- new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), locationInfoContainer.RectTransform), location.Type.Name, font: GUIStyle.SubHeadingFont);
+ new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), locationInfoContainer.RectTransform), location.GetLocationTypeToDisplay().Name, font: GUIStyle.SubHeadingFont);
if (location.Faction?.Prefab != null)
{
@@ -1633,6 +1756,7 @@ namespace Barotrauma
textContent,
mission.Difficulty ?? 0,
mission.Prefab.Icon, mission.Prefab.IconColor,
+ mission.GetDifficultyToolTipText(),
out GUIImage missionIcon);
if (missionIcon != null)
{
@@ -1663,6 +1787,8 @@ namespace Barotrauma
private static void CreateSubmarineInfo(GUIFrame infoFrame, Submarine sub)
{
+ if (sub == null) { return; }
+
GUIFrame subInfoFrame = new GUIFrame(new RectTransform(Vector2.One, infoFrame.RectTransform, Anchor.TopCenter), style: "GUIFrameListBox");
GUIFrame paddedFrame = new GUIFrame(new RectTransform(Vector2.One * 0.97f, subInfoFrame.RectTransform, Anchor.Center), style: null);
@@ -1765,7 +1891,7 @@ namespace Barotrauma
{
parent.Content.ClearChildren();
List skillNames = new List();
- foreach (Skill skill in info.Job.GetSkills())
+ foreach (Skill skill in info.Job.GetSkills().OrderByDescending(static s => s.Level))
{
GUILayoutGroup skillContainer = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.0f), parent.Content.RectTransform), isHorizontal: true) { CanBeFocused = true };
var skillName = new GUITextBlock(new RectTransform(new Vector2(0.7f, 0.0f), skillContainer.RectTransform), TextManager.Get($"skillname.{skill.Identifier}").Fallback(skill.Identifier.Value));
diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/TalentMenu.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/TalentMenu.cs
index 1b6fad62f..6ddc6a467 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/GUI/TalentMenu.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/TalentMenu.cs
@@ -7,6 +7,7 @@ using System.Linq;
using Barotrauma.Extensions;
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using static Barotrauma.TalentTree;
using static Barotrauma.TalentTree.TalentStages;
@@ -83,17 +84,21 @@ namespace Barotrauma
private GUIButton? talentApplyButton,
talentResetButton;
- public void CreateGUI(GUIFrame parent, Character? targetCharacter)
+ private delegate void StartAnimation(RectangleF start, RectangleF end, float duration);
+ private StartAnimation? startAnimation;
+ private GUIComponent? talentMainArea;
+
+ public void CreateGUI(GUIFrame parent, CharacterInfo? characterInfo)
{
+ this.characterInfo = characterInfo;
+ character = characterInfo?.Character;
+
parent.ClearChildren();
talentButtons.Clear();
talentShowCaseButtons.Clear();
talentCornerIcons.Clear();
showCaseTalentFrames.Clear();
- character = targetCharacter;
- characterInfo = targetCharacter?.Info;
-
GUIFrame background = new GUIFrame(new RectTransform(Vector2.One, parent.RectTransform, Anchor.TopCenter), style: "GUIFrameListBox");
int padding = GUI.IntScale(15);
GUIFrame frame = new GUIFrame(new RectTransform(new Point(background.Rect.Width - padding, background.Rect.Height - padding), parent.RectTransform, Anchor.Center), style: null);
@@ -136,7 +141,7 @@ namespace Barotrauma
GUILayoutGroup playerFrame = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.9f), containerFrame.RectTransform, Anchor.TopCenter));
GameMain.NetLobbyScreen.CreatePlayerFrame(playerFrame, alwaysAllowEditing: true, createPendingText: false);
- if (!GameMain.NetLobbyScreen.PermadeathMode)
+ if (!GameMain.NetLobbyScreen.PermadeathMode && GameMain.GameSession?.GameMode is not PvPMode)
{
GUIButton newCharacterBox = new GUIButton(new RectTransform(new Vector2(0.5f, 0.2f), skillLayout.RectTransform, Anchor.BottomRight),
text: GameMain.NetLobbyScreen.CampaignCharacterDiscarded ? TextManager.Get("settings") : TextManager.Get("createnew"), style: "GUIButtonSmall")
@@ -294,7 +299,7 @@ namespace Barotrauma
}
ImmutableHashSet talentsOutsideTree = info.GetUnlockedTalentsOutsideTree().Select(static e => TalentPrefab.TalentPrefabs.Find(c => c.Identifier == e)).ToImmutableHashSet();
- if (talentsOutsideTree.Any())
+ if (talentsOutsideTree.Any(static t => t != null && !t.IsHiddenExtraTalent))
{
//spacing
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.01f), nameLayout.RectTransform), style: null);
@@ -319,6 +324,7 @@ namespace Barotrauma
foreach (var extraTalent in talentsOutsideTree)
{
if (extraTalent is null) { continue; }
+ if (extraTalent.IsHiddenExtraTalent) { continue; }
GUIImage talentImg = new GUIImage(new RectTransform(Vector2.One, extraTalentList.Content.RectTransform, scaleBasis: ScaleBasis.BothHeight), sprite: extraTalent.Icon, scaleToFit: true)
{
ToolTip = RichString.Rich($"‖color:{Color.White.ToStringHex()}‖{extraTalent.DisplayName}‖color:end‖" + "\n\n" + ToolBox.ExtendColorToPercentageSigns(extraTalent.Description.Value)),
@@ -341,7 +347,15 @@ namespace Barotrauma
private void CreateTalentMenu(GUIComponent parent, CharacterInfo info, TalentTree tree)
{
- GUIListBox mainList = new GUIListBox(new RectTransform(new Vector2(1f, 0.9f), parent.RectTransform, anchor: Anchor.TopCenter));
+ talentMainArea = new GUIFrame(new RectTransform(new Vector2(1f, 0.9f), parent.RectTransform, Anchor.TopCenter), style: null );
+
+ GUIListBox mainList = new GUIListBox(new RectTransform(Vector2.One, talentMainArea.RectTransform));
+ startAnimation = CreatePopupAnimationHandler(talentMainArea);
+
+ if (info is { TalentRefundPoints: > 0, ShowTalentResetPopupOnOpen: true })
+ {
+ CreateTalentResetPopup(talentMainArea);
+ }
selectedTalents = info.GetUnlockedTalentsInTree().ToHashSet();
@@ -425,6 +439,130 @@ namespace Barotrauma
}
}
+ private void CreateTalentResetPopup(GUIComponent parent)
+ {
+ int talentResetCount = 0;
+ if (character?.Info != null)
+ {
+ talentResetCount = Math.Min(character.Info.TalentResetCount, character.Info.GetCurrentLevel());
+ }
+ bool hasResetTalentsBefore = talentResetCount > 0;
+ var bgBlocker = new GUIFrame(new RectTransform(Vector2.One, parent.RectTransform, anchor: Anchor.Center), style: "GUIBackgroundBlocker")
+ {
+ IgnoreLayoutGroups = true
+ };
+
+ var popup = new GUIFrame(new RectTransform(new Vector2(0.6f, 0.8f), bgBlocker.RectTransform, Anchor.Center));
+
+ var popupLayout = new GUILayoutGroup(new RectTransform(ToolBox.PaddingSizeParentRelative(popup.RectTransform, 0.95f), popup.RectTransform, Anchor.Center), isHorizontal: false);
+
+ new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.15f), popupLayout.RectTransform), TextManager.Get("talentresetheader"), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.Center);
+ new GUITextBlock(new RectTransform(new Vector2(1.0f, hasResetTalentsBefore ? 0.25f : 0.5f), popupLayout.RectTransform), TextManager.Get("talentresetprompt"), wrap: true);
+
+ if (hasResetTalentsBefore)
+ {
+ new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.25f), popupLayout.RectTransform),
+ TextManager.GetWithVariable("talentresetpromptwarning", "[count]", talentResetCount.ToString()), wrap: true)
+ {
+ TextColor = GUIStyle.Red
+ };
+ }
+
+ var buttonLayout = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.35f), popupLayout.RectTransform), childAnchor: Anchor.CenterLeft, isHorizontal: true);
+
+ var confirmButton = new GUIButton(new RectTransform(new Vector2(0.5f, 1.0f), buttonLayout.RectTransform), TextManager.Get("holdtoconfirm"))
+ {
+ RequireHold = true,
+ HoldDurationSeconds = 1.5f,
+ OnClicked = (button, o) =>
+ {
+ if (character is null || characterInfo is null) { return false; }
+
+ characterInfo.RefundTalents();
+ selectedTalents.Clear();
+ UpdateTalentInfo();
+ bgBlocker.Visible = false;
+ return true;
+ }
+ };
+ var denyButton = new GUIButton(new RectTransform(new Vector2(0.5f, 1.0f), buttonLayout.RectTransform), TextManager.Get("decidelater"))
+ {
+ RequireHold = false,
+ OnClicked = (button, userData) =>
+ {
+ if (talentResetButton is not { } resetButton) { return false; }
+ startAnimation?.Invoke(popup.Rect, resetButton.Rect, 0.25f);
+ resetButton.Flash(GUIStyle.Green);
+ bgBlocker.Visible = false;
+ if (characterInfo != null)
+ {
+ characterInfo.ShowTalentResetPopupOnOpen = false;
+ }
+ return true;
+ }
+ };
+ }
+
+ private static StartAnimation CreatePopupAnimationHandler(GUIComponent parent)
+ {
+ bool drawAnimation = false;
+
+ float animDur = 1f,
+ animTimer = 0f;
+
+ RectangleF drawRect = RectangleF.Empty,
+ animStartRect = RectangleF.Empty,
+ animEndRect = RectangleF.Empty;
+
+ void StartAnimation(RectangleF start, RectangleF end, float duration)
+ {
+ animStartRect = start;
+ animEndRect = end;
+ animTimer = 0;
+ animDur = duration;
+ drawRect = start;
+ drawAnimation = true;
+ }
+
+ void OnDraw(SpriteBatch batch, GUICustomComponent component)
+ {
+ if (!drawAnimation) { return; }
+
+ GUIComponentStyle style = GUIStyle.GetComponentStyle("GUIFrame");
+
+ style.Sprites[GUIComponent.ComponentState.None][0].Draw(batch, drawRect, Color.White);
+ }
+
+ void OnUpdate(float f, GUICustomComponent component)
+ {
+ if (!drawAnimation) { return; }
+
+ animTimer += f;
+ if (animTimer > animDur)
+ {
+ drawRect = animEndRect;
+ drawAnimation = false;
+ return;
+ }
+
+ float lerp = animTimer / animDur;
+
+ drawRect = new RectangleF(
+ MathHelper.Lerp(animStartRect.X, animEndRect.X, lerp),
+ MathHelper.Lerp(animStartRect.Y, animEndRect.Y, lerp),
+ MathHelper.Lerp(animStartRect.Width, animEndRect.Width, lerp),
+ MathHelper.Lerp(animStartRect.Height, animEndRect.Height, lerp));
+ }
+
+ new GUICustomComponent(new RectTransform(Vector2.One, parent.RectTransform), onDraw: OnDraw, onUpdate: OnUpdate)
+ {
+ IgnoreLayoutGroups = true,
+ CanBeFocused = false
+ };
+
+ return StartAnimation;
+ }
+
private void CreateTalentOption(GUIComponent parent, TalentSubTree subTree, int index, TalentOption talentOption, CharacterInfo info, int specializationCount)
{
int elementPadding = GUI.IntScale(8);
@@ -679,6 +817,15 @@ namespace Barotrauma
private bool ResetTalentSelection(GUIButton guiButton, object userData)
{
if (characterInfo is null) { return false; }
+
+ int newTalentCount = selectedTalents.Count - characterInfo.GetUnlockedTalentsInTree().Count();
+ // if we don't have talents selected, and we have points to refund, show the refund popup
+ if (characterInfo.TalentRefundPoints > 0 && newTalentCount == 0)
+ {
+ CreateTalentResetPopup(talentMainArea!);
+ return true;
+ }
+
selectedTalents = characterInfo.GetUnlockedTalentsInTree().ToHashSet();
UpdateTalentInfo();
return true;
@@ -844,12 +991,31 @@ namespace Barotrauma
}
}
+ private static readonly LocalizedString refundText = TextManager.Get("refund"),
+ resetText = TextManager.Get("reset");
+
public void Update()
{
if (characterInfo is null || talentResetButton is null || talentApplyButton is null) { return; }
int talentCount = selectedTalents.Count - characterInfo.GetUnlockedTalentsInTree().Count();
- talentResetButton.Enabled = talentApplyButton.Enabled = talentCount > 0;
+ talentApplyButton.Enabled = character != null && talentCount > 0;
+ talentResetButton.Enabled = character != null && (talentCount > 0 || characterInfo.TalentRefundPoints > 0);
+
+ if (talentCount == 0 && characterInfo.TalentRefundPoints > 0)
+ {
+ if (talentResetButton.FlashTimer <= 0.0f)
+ {
+ talentResetButton.Flash(GUIStyle.Orange);
+ }
+
+ talentResetButton.Text = refundText;
+ }
+ else
+ {
+ talentResetButton.Text = resetText;
+ }
+
if (talentApplyButton.Enabled && talentApplyButton.FlashTimer <= 0.0f)
{
talentApplyButton.Flash(GUIStyle.Orange);
@@ -893,6 +1059,22 @@ namespace Barotrauma
return info.GetIdentifierUsingOriginalName() == ownCharacterInfo.GetIdentifierUsingOriginalName();
}
+ private static bool IsOnSameTeam(CharacterInfo? info)
+ {
+ if (info is null) { return false; }
+
+ CharacterTeamType? ownCharacterTeam = Character.Controlled?.TeamID ?? GameMain.Client?.MyClient?.TeamID;
+ if (ownCharacterTeam is null) { return false; }
+
+ return info.TeamID == ownCharacterTeam;
+ }
+
+ private static bool IsSpectatingInMultiplayer()
+ {
+ if (GameMain.Client?.MyClient is not { } myClient) { return false; }
+ return myClient.Spectating;
+ }
+
public static bool CanManageTalents(CharacterInfo targetInfo)
{
// in singleplayer we can do whatever we want
@@ -901,10 +1083,16 @@ namespace Barotrauma
// always allow managing talents for own character
if (IsOwnCharacter(targetInfo)) { return true; }
+ // disallow managing talents while spectating
+ if (IsSpectatingInMultiplayer()) { return false; }
+
// don't allow controlling non-bot characters
if (targetInfo.Character is not { IsBot: true }) { return false; }
- // lastly check if we have the permission to do this
+ // only allow managing talents for bots on the same team
+ if (!IsOnSameTeam(targetInfo)) { return false; }
+
+ // lastly, check if we have the permission to do this
return GameMain.Client is { } client && client.HasPermission(ClientPermissions.ManageBotTalents);
}
}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/UpgradeStore.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/UpgradeStore.cs
index ea933efde..b57c52277 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/GUI/UpgradeStore.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/UpgradeStore.cs
@@ -127,9 +127,10 @@ namespace Barotrauma
Campaign.OnMoneyChanged.RegisterOverwriteExisting(eventId, _ => RequestRefresh());
}
- public void RequestRefresh()
+ public void RequestRefresh(bool refreshUpgrades = false)
{
needsRefresh = true;
+ if (refreshUpgrades) { SelectTab(UpgradeTab.Upgrade); }
}
private void RefreshAll()
@@ -673,6 +674,10 @@ namespace Barotrauma
}
}
}
+ if (!upgrades.ContainsKey(category) && HasSwappableItems(category))
+ {
+ upgrades.Add(category, new List());
+ }
}
foreach (var (category, prefabs) in upgrades)
@@ -771,19 +776,29 @@ namespace Barotrauma
{
if (Submarine.MainSub == null) { return false; }
subItems ??= GetSubItems();
- return subItems.Any(i =>
- i.Prefab.SwappableItem != null &&
- !i.IsHidden && i.AllowSwapping &&
- (i.Prefab.SwappableItem.CanBeBought || ItemPrefab.Prefabs.Any(ip => ip.SwappableItem?.ReplacementOnUninstall == i.Prefab.Identifier)) &&
- Submarine.MainSub.IsEntityFoundOnThisSub(i, true) && category.ItemTags.Any(t => i.HasTag(t)));
+ return subItems.Any(item => HasSwappableItems(category, item));
}
+ private static bool HasSwappableItems(UpgradeCategory category, Item item)
+ {
+ if (Submarine.MainSub == null) { return false; }
+ return
+ item.Prefab.SwappableItem != null &&
+ !item.IsHidden && item.AllowSwapping &&
+ (item.Prefab.SwappableItem.CanBeBought || ItemPrefab.Prefabs.Any(ip => ip.SwappableItem?.ReplacementOnUninstall == item.Prefab.Identifier)) &&
+ Submarine.MainSub.IsEntityFoundOnThisSub(item, true) && category.ItemTags.Any(t => item.HasTag(t));
+ }
private static List
- GetSubItems() => Submarine.MainSub?.GetItems(true) ?? new List
- ();
private void SelectUpgradeCategory(List prefabs, UpgradeCategory category, Submarine submarine)
{
if (selectedUpgradeCategoryLayout == null) { return; }
+ bool hasSwappableItems = HasSwappableItems(category);
+ bool hasUpgradeModules = prefabs.Count > 0;
+
+ customizeTabOpen = !hasUpgradeModules && hasSwappableItems;
+
customizeTabOpen = false;
GUIComponent[] categoryFrames = GetFrames(category);
@@ -799,9 +814,7 @@ namespace Barotrauma
GUIFrame frame = new GUIFrame(rectT(1.0f, 0.4f, selectedUpgradeCategoryLayout));
GUIFrame paddedFrame = new GUIFrame(rectT(0.93f, 0.9f, frame, Anchor.Center), style: null);
- bool hasSwappableItems = HasSwappableItems(category);
-
- float listHeight = hasSwappableItems ? 0.9f : 1.0f;
+ float listHeight = hasSwappableItems && hasUpgradeModules ? 0.9f : 1.0f;
GUIListBox prefabList = new GUIListBox(rectT(1.0f, listHeight, paddedFrame, Anchor.BottomLeft))
{
@@ -810,7 +823,8 @@ namespace Barotrauma
ScrollBarVisible = true
};
- if (hasSwappableItems)
+ //both swappable items and upgrade modules -> create 2 tabs
+ if (hasSwappableItems && hasUpgradeModules)
{
GUILayoutGroup buttonLayout = new GUILayoutGroup(rectT(1.0f, 0.1f, paddedFrame, anchor: Anchor.TopLeft), isHorizontal: true);
@@ -852,8 +866,15 @@ namespace Barotrauma
return true;
};
}
-
- CreateUpgradePrefabList(prefabList, category, prefabs, submarine);
+ //only either upgrade modules or swappable items -> just create the list
+ else if (hasUpgradeModules)
+ {
+ CreateUpgradePrefabList(prefabList, category, prefabs, submarine);
+ }
+ else if (hasSwappableItems)
+ {
+ CreateSwappableItemList(prefabList, category, submarine);
+ }
}
private void CreateUpgradePrefabList(GUIListBox parent, UpgradeCategory category, List prefabs, Submarine submarine)
@@ -1370,9 +1391,10 @@ namespace Barotrauma
Item[] entitiesOnSub = drawnSubmarine.GetItems(true).Where(i => drawnSubmarine.IsEntityFoundOnThisSub(i, true)).ToArray();
foreach (UpgradeCategory category in UpgradeCategory.Categories)
{
- //hide categories with no upgrades in them
- if (UpgradePrefab.Prefabs.None(p => p.UpgradeCategories.Contains(category))) { continue; }
- if (entitiesOnSub.Any(item => category.CanBeApplied(item, null)))
+ //hide categories with no upgrades or swappables in them
+ bool hasSwappableItems = HasSwappableItems(category);
+ if (!hasSwappableItems && UpgradePrefab.Prefabs.None(p => p.UpgradeCategories.Contains(category))) { continue; }
+ if (hasSwappableItems || entitiesOnSub.Any(item => category.CanBeApplied(item, null)))
{
yield return category;
}
@@ -1534,7 +1556,7 @@ namespace Barotrauma
description.Padding = new Vector4(description.Padding.X, 24 * GUI.Scale, description.Padding.Z, description.Padding.W);
List pointsOfInterest = (from category in UpgradeCategory.Categories from item in submarine.GetItems(UpgradeManager.UpgradeAlsoConnectedSubs)
- where category.CanBeApplied(item, null) && item.IsPlayerTeamInteractable select item).Cast().Distinct().ToList();
+ where (category.CanBeApplied(item, null) || HasSwappableItems(category, item)) && item.IsPlayerTeamInteractable select item).Cast().Distinct().ToList();
List ids = GameMain.GameSession.SubmarineInfo?.LeftBehindDockingPortIDs ?? new List();
pointsOfInterest.AddRange(submarine.GetItems(UpgradeManager.UpgradeAlsoConnectedSubs).Where(item => ids.Contains(item.ID)));
@@ -1799,7 +1821,7 @@ namespace Barotrauma
// Disables the parent and only re-enables if the submarine contains valid items
if (!category.IsWallUpgrade && drawnSubmarine?.Info != null)
{
- if (UpgradePrefab.Prefabs.None(p => p.UpgradeCategories.Contains(category) && p.GetMaxLevel(drawnSubmarine.Info) > 0))
+ if (UpgradePrefab.Prefabs.None(p => p.UpgradeCategories.Contains(category) && p.GetMaxLevel(drawnSubmarine.Info) > 0) && !HasSwappableItems(category))
{
parent.ToolTip = TextManager.Get("upgradecategorynotapplicable");
parent.Enabled = false;
diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs b/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs
index fbfaca6a9..062a0c393 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs
@@ -120,6 +120,7 @@ namespace Barotrauma
private readonly GameTime fixedTime;
public Option ConnectCommand = Option.None();
+ private string clientName;
private static SpriteBatch spriteBatch;
@@ -252,6 +253,16 @@ namespace Barotrauma
try
{
ConnectCommand = Barotrauma.Networking.ConnectCommand.Parse(ConsoleArguments);
+
+ string clientNameFlagArg = args.FirstOrDefault(arg => arg.StartsWith("-username"));
+ if (clientNameFlagArg != null)
+ {
+ int nextIndex = args.IndexOf(clientNameFlagArg) + 1;
+ if (nextIndex < args.Length)
+ {
+ clientName = args[nextIndex];
+ }
+ }
}
catch (IndexOutOfRangeException e)
{
@@ -313,6 +324,8 @@ namespace Barotrauma
GameSettings.SetCurrentConfig(config);
}
+ int display = GameSettings.CurrentConfig.Graphics.Display;
+
GraphicsWidth = GameSettings.CurrentConfig.Graphics.Width;
GraphicsHeight = GameSettings.CurrentConfig.Graphics.Height;
@@ -340,7 +353,7 @@ namespace Barotrauma
GraphicsDeviceManager.PreferredBackBufferFormat = SurfaceFormat.Color;
GraphicsDeviceManager.PreferMultiSampling = false;
GraphicsDeviceManager.SynchronizeWithVerticalRetrace = GameSettings.CurrentConfig.Graphics.VSync;
- SetWindowMode(GameSettings.CurrentConfig.Graphics.DisplayMode);
+ SetWindowMode(GameSettings.CurrentConfig.Graphics.DisplayMode, display);
defaultViewport = new Viewport(0, 0, GraphicsWidth, GraphicsHeight);
@@ -353,8 +366,17 @@ namespace Barotrauma
ResolutionChanged?.Invoke();
}
- public void SetWindowMode(WindowMode windowMode)
+ public void SetWindowMode(WindowMode windowMode, int display)
{
+ // We can't move the monitor while the window is fullscreen because of a restriction in SDL2, so as a workaround we switch to windowed mode first
+ var prevDisplayMode = WindowMode;
+ if (Window.TargetDisplay != display && prevDisplayMode != WindowMode.Windowed)
+ {
+ GraphicsDeviceManager.IsFullScreen = false;
+ GraphicsDeviceManager.ApplyChanges();
+ }
+ Window.TargetDisplay = display;
+
WindowMode = windowMode;
GraphicsDeviceManager.HardwareModeSwitch = windowMode != WindowMode.BorderlessWindowed;
GraphicsDeviceManager.IsFullScreen = windowMode == WindowMode.Fullscreen || windowMode == WindowMode.BorderlessWindowed;
@@ -723,7 +745,7 @@ namespace Barotrauma
fixedTime.IsRunningSlowly = gameTime.IsRunningSlowly;
TimeSpan addTime = new TimeSpan(0, 0, 0, 0, 16);
fixedTime.ElapsedGameTime = addTime;
- fixedTime.TotalGameTime.Add(addTime);
+ fixedTime.TotalGameTime = fixedTime.TotalGameTime.Add(addTime);
base.Update(fixedTime);
PlayerInput.Update(Timing.Step);
@@ -779,7 +801,7 @@ namespace Barotrauma
{
try
{
- SaveUtil.LoadGame(saveFiles.OrderBy(file => file.SaveTime).Last().FilePath);
+ SaveUtil.LoadGame(CampaignDataPath.CreateRegular(saveFiles.OrderBy(file => file.SaveTime).Last().FilePath));
}
catch (Exception e)
{
@@ -804,19 +826,28 @@ namespace Barotrauma
}
MainMenuScreen.Select();
+ string clientNameString = clientName ?? MultiplayerPreferences.Instance.PlayerName.FallbackNullOrEmpty(SteamManager.GetUsername());
+
if (connectCommand.SteamLobbyIdOption.TryUnwrap(out var lobbyId))
{
SteamManager.JoinLobby(lobbyId.Value, joinServer: true);
}
- else if (connectCommand.NameAndP2PEndpointsOption.TryUnwrap(out var nameAndEndpoint)
- && nameAndEndpoint is { ServerName: var serverName, Endpoints: var endpoints })
+ else if ((connectCommand.NameAndP2PEndpointsOption.TryUnwrap(out var nameAndEndpoint) && nameAndEndpoint is { ServerName: var serverName, Endpoints: var endpoints }))
{
- Client = new GameClient(MultiplayerPreferences.Instance.PlayerName.FallbackNullOrEmpty(SteamManager.GetUsername()),
+ Client = new GameClient(clientNameString,
endpoints.Cast().ToImmutableArray(),
string.IsNullOrWhiteSpace(serverName) ? endpoints.First().StringRepresentation : serverName,
Option.None());
}
-
+ else if ((connectCommand.NameAndLidgrenEndpointOption.TryUnwrap(out var nameAndLidgrenEndpoint) && nameAndLidgrenEndpoint is { ServerName: var lidgrenServerName, Endpoint: var endpoint }))
+ {
+ Client = new GameClient(
+ clientNameString,
+ endpoint,
+ string.IsNullOrWhiteSpace(lidgrenServerName) ? endpoint.StringRepresentation : lidgrenServerName,
+ Option.None());
+ }
+
ConnectCommand = Option.None();
}
@@ -1145,7 +1176,7 @@ namespace Barotrauma
}
GameSession.Campaign?.End();
- SaveUtil.SaveGame(GameSession.SavePath);
+ SaveUtil.SaveGame(GameSession.DataPath);
}
if (Client != null)
@@ -1162,11 +1193,12 @@ namespace Barotrauma
GameSession.GameMode?.Preset.Identifier.Value ?? "none",
GameSession.RoundDuration);
string eventId = "QuitRound:" + (GameSession.GameMode?.Preset.Identifier.Value ?? "none") + ":";
- GameAnalyticsManager.AddDesignEvent(eventId + "EventManager:CurrentIntensity", GameSession.EventManager.CurrentIntensity);
+ //disabled to reduce the amount of data we collect through GA
+ /*GameAnalyticsManager.AddDesignEvent(eventId + "EventManager:CurrentIntensity", GameSession.EventManager.CurrentIntensity);
foreach (var activeEvent in GameSession.EventManager.ActiveEvents)
{
GameAnalyticsManager.AddDesignEvent(eventId + "EventManager:ActiveEvents:" + activeEvent.Prefab.Identifier);
- }
+ }*/
GameSession.LogEndRoundStats(eventId);
if (GameSession.GameMode is TutorialMode tutorialMode)
{
diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/CargoManager.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/CargoManager.cs
index 0d1df98e3..751ba60a9 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/CargoManager.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/CargoManager.cs
@@ -166,7 +166,7 @@ namespace Barotrauma
// check if the store can afford the item
if (store.Balance < itemValue) { continue; }
// TODO: Write logic for prioritizing certain items over others (e.g. lone Battery Cell should be preferred over one inside a Stun Baton)
- var matchingItems = sellableItems.Where(i => i.Prefab == item.ItemPrefab);
+ var matchingItems = sellableItems.Where(i => i.Prefab.Identifier == item.ItemPrefabIdentifier);
int count = Math.Min(item.Quantity, matchingItems.Count());
SoldItem.SellOrigin origin = sellingMode == Store.StoreTab.Sell ? SoldItem.SellOrigin.Character : SoldItem.SellOrigin.Submarine;
if (origin == SoldItem.SellOrigin.Character || GameMain.IsSingleplayer)
diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs
index 5a6ae2c90..a6735757e 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs
@@ -28,7 +28,7 @@ namespace Barotrauma
public GUIComponent ReportButtonFrame { get; set; }
private GUIFrame guiFrame;
- private GUIFrame crewArea;
+ private GUILayoutGroup crewArea;
private GUIListBox crewList;
private float crewListOpenState;
private bool _isCrewMenuOpen = true;
@@ -47,7 +47,7 @@ namespace Barotrauma
///
/// This property stores the preference in settings. Don't use for automatic logic.
- /// Use AutoShowCrewList(), AutoHideCrewList(), and ResetCrewList().
+ /// Use AutoHideCrewList(), and ResetCrewList().
///
public bool IsCrewMenuOpen
{
@@ -62,11 +62,9 @@ namespace Barotrauma
public static bool PreferCrewMenuOpen = true;
- public bool AutoShowCrewList() => _isCrewMenuOpen = true;
-
public void AutoHideCrewList() => _isCrewMenuOpen = false;
- public void ResetCrewList() => _isCrewMenuOpen = PreferCrewMenuOpen;
+ public void ResetCrewListOpenState() => _isCrewMenuOpen = PreferCrewMenuOpen;
const float CommandNodeAnimDuration = 0.2f;
@@ -93,12 +91,30 @@ namespace Barotrauma
#region Crew Area
- crewArea = new GUIFrame(HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.CrewArea, guiFrame.RectTransform), style: null, color: Color.Transparent)
+ crewArea = new GUILayoutGroup(HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.CrewArea, guiFrame.RectTransform), childAnchor: Anchor.TopCenter)
{
- CanBeFocused = false
+ Stretch = true
};
crewArea.RectTransform.NonScaledSize = HUDLayoutSettings.CrewArea.Size;
+ for (int i = 0; i < 2; i++)
+ {
+ CharacterTeamType teamId = i == 0 ? CharacterTeamType.Team1 : CharacterTeamType.Team2;
+ var nameText = new GUITextBlock(new RectTransform(new Point(crewArea.Rect.Width - GUI.IntScale(10), GUI.IntScale(30)), crewArea.RectTransform), CombatMission.GetTeamName(teamId), textColor: CombatMission.GetTeamColor(teamId))
+ {
+ ForceUpperCase = ForceUpperCase.Yes,
+ TextGetter = () => CombatMission.GetTeamName(teamId),
+ Visible = false,
+ IgnoreLayoutGroups = true,
+ UserData = teamId
+ };
+ var teamIcon = new GUIImage(new RectTransform(Vector2.One, nameText.RectTransform, Anchor.CenterLeft, scaleBasis: ScaleBasis.BothHeight), style: i == 0 ? "CoalitionIcon" : "SeparatistIcon")
+ {
+ Color = nameText.TextColor
+ };
+ nameText.Padding = new Vector4(teamIcon.Rect.Width + nameText.Padding.X, nameText.Padding.Y, nameText.Padding.Z, nameText.Padding.W);
+ }
+
// AbsoluteOffset is set in UpdateProjectSpecific based on crewListOpenState
crewList = new GUIListBox(new RectTransform(Vector2.One, crewArea.RectTransform), style: null, isScrollBarOnDefaultSide: false)
{
@@ -177,6 +193,12 @@ namespace Barotrauma
ChatBox.InputBox.OnTextChanged += ChatBox.TypingChatMessage;
}
+ else if (GameMain.Client == null)
+ {
+ //this method would throw a non-descriptive nullref exception later when trying to access the chatbox
+ //if we'd try to continue from here, better to throw a more descriptive one at this point
+ throw new InvalidOperationException($"Attempted to initialize {nameof(CrewManager)} for multiplayer, but no multiplayer client is active. Are you trying to load a multiplayer save in singleplayer?");
+ }
#endregion
@@ -290,6 +312,14 @@ namespace Barotrauma
#region Character list management
+ ///
+ /// Note: this is only works client-side. TODO: make it work server-side too?
+ ///
+ public IEnumerable GetCharacters()
+ {
+ return characters;
+ }
+
public Rectangle GetActiveCrewArea()
{
return crewArea.Rect;
@@ -562,9 +592,19 @@ namespace Barotrauma
public bool CharacterClicked(GUIComponent component, object selection)
{
if (!AllowCharacterSwitch) { return false; }
- if (!(selection is Character character) || character.IsDead || character.IsUnconscious) { return false; }
+ if (selection is not Character character || character.IsDead || character.IsUnconscious) { return false; }
if (!character.IsOnPlayerTeam) { return false; }
+ if (GameMain.IsMultiplayer)
+ {
+ if (Character.Controlled == null)
+ {
+ Camera cam = Screen.Selected.Cam;
+ cam.Position = character.DrawPosition;
+ }
+ return true;
+ }
+
SelectCharacter(character);
if (GUI.KeyboardDispatcher.Subscriber == crewList) { GUI.KeyboardDispatcher.Subscriber = null; }
return true;
@@ -631,10 +671,9 @@ namespace Barotrauma
{
if (crewList != this.crewList) { return; }
if (draggedElementData is not Character) { return; }
- if (!IsSinglePlayer) { return; }
if (crewList.HasDraggedElementIndexChanged)
{
- UpdateCrewListIndices();
+ if (IsSinglePlayer) { UpdateCrewListIndices(); }
}
else
{
@@ -645,10 +684,13 @@ namespace Barotrauma
private void ResetCrewListIndex(Character c)
{
if (c?.Info == null) { return; }
- c.Info.CrewListIndex = -1;
- UpdateCrewListIndices();
+ //default to the bottom of the list
+ c.Info.CrewListIndex = int.MaxValue;
}
+ ///
+ /// Refresh the of the characters based on their order in the crew list
+ ///
private void UpdateCrewListIndices()
{
if (crewList == null) { return; }
@@ -661,20 +703,23 @@ namespace Barotrauma
}
}
+ ///
+ /// Order the crew list according to the characters'
+ ///
private void SortCrewList()
{
if (crewList == null) { return; }
crewList.Content.RectTransform.SortChildren((x, y) =>
{
- var infoX = (x.GUIComponent.UserData as Character)?.Info?.CrewListIndex;
- var infoY = (y.GUIComponent.UserData as Character)?.Info?.CrewListIndex;
- if (infoX.HasValue)
+ int? index1 = (x.GUIComponent.UserData as Character)?.Info?.CrewListIndex;
+ int? index2 = (y.GUIComponent.UserData as Character)?.Info?.CrewListIndex;
+ if (index1.HasValue)
{
- return infoY.HasValue ? infoX.Value.CompareTo(infoY.Value) : -1;
+ return index2.HasValue ? index1.Value.CompareTo(index2.Value) : -1;
}
else
{
- return infoY.HasValue ? 1 : 0;
+ return index2.HasValue ? 1 : 0;
}
});
UpdateCrewListIndices();
@@ -1312,6 +1357,7 @@ namespace Barotrauma
{
if (ConversationAction.IsDialogOpen) { return; }
if (!AllowCharacterSwitch) { return; }
+ if (character == null || character.Removed) { return; }
//make the previously selected character wait in place for some time
//(so they don't immediately start idling and walking away from their station)
var aiController = Character.Controlled?.AIController;
@@ -1323,6 +1369,16 @@ namespace Barotrauma
{
GameSession.TabMenuInstance.SelectInfoFrameTab(TabMenu.SelectedTab);
}
+ if (character.SelectedItem?.GetComponent() == null && character.SelectedCharacter == null)
+ {
+ ResetCrewListOpenState();
+ ChatBox.ResetChatBoxOpenState();
+ }
+ else
+ {
+ AutoHideCrewList();
+ ChatBox.AutoHideChatBox();
+ }
}
private int TryAdjustIndex(int amount)
@@ -1340,7 +1396,7 @@ namespace Barotrauma
if (index > lastIndex) { index = 0; }
if (index < 0) { index = lastIndex; }
- if ((crewList.Content.GetChild(index)?.UserData as Character)?.IsOnPlayerTeam ?? false)
+ if (crewList.Content.GetChild(index)?.UserData is Character { IsOnPlayerTeam: true, Removed: false })
{
return index;
}
@@ -1635,6 +1691,18 @@ namespace Barotrauma
{
crewArea.Visible = characters.Count > 0 && CharacterHealth.OpenHealthWindow == null;
+ CharacterTeamType myTeam = Character.Controlled?.TeamID ?? GameMain.Client?.MyClient?.TeamID ?? CharacterTeamType.Team1;
+ if (GameMain.GameSession?.GameMode is PvPMode)
+ {
+ var team1Text = crewArea.GetChildByUserData(CharacterTeamType.Team1);
+ team1Text.Visible = myTeam == CharacterTeamType.Team1;
+ team1Text.IgnoreLayoutGroups = !team1Text.Visible;
+
+ var team2Text = crewArea.GetChildByUserData(CharacterTeamType.Team2);
+ team2Text.Visible = myTeam == CharacterTeamType.Team2;
+ team2Text.IgnoreLayoutGroups = !team2Text.Visible;
+ }
+
foreach (GUIComponent characterComponent in crewList.Content.Children)
{
if (characterComponent.UserData is Character character)
@@ -1645,7 +1713,7 @@ namespace Barotrauma
continue;
}
- characterComponent.Visible = Character.Controlled == null || Character.Controlled.TeamID == character.TeamID;
+ characterComponent.Visible = myTeam == character.TeamID;
if (character.TeamID == CharacterTeamType.FriendlyNPC && Character.Controlled != null &&
(character.CurrentHull == Character.Controlled.CurrentHull || Vector2.DistanceSquared(Character.Controlled.WorldPosition, character.WorldPosition) < 500.0f * 500.0f))
{
@@ -1873,7 +1941,7 @@ namespace Barotrauma
{
get
{
- if (GameMain.GameSession?.CrewManager == null)
+ if (GameMain.GameSession?.CrewManager == null || Screen.Selected is { IsEditor: true })
{
return false;
}
@@ -2010,7 +2078,7 @@ namespace Barotrauma
// Character context works differently to others as we still use the "basic" command interface,
// but the order will be automatically assigned to this character
isContextual = forceContextual;
- if (entityContext is Character character)
+ if (entityContext is Character { Info: not null } character)
{
characterContext = character;
itemContext = null;
@@ -2098,7 +2166,7 @@ namespace Barotrauma
CreateNodeConnectors();
if (Character.Controlled != null)
{
- Character.Controlled.dontFollowCursor = true;
+ Character.Controlled.FollowCursor = false;
}
HintManager.OnShowCommandInterface();
@@ -2242,7 +2310,7 @@ namespace Barotrauma
returnNodeHotkey = expandNodeHotkey = Keys.None;
if (Character.Controlled != null)
{
- Character.Controlled.dontFollowCursor = false;
+ Character.Controlled.FollowCursor = true;
}
}
@@ -2511,7 +2579,7 @@ namespace Barotrauma
// --> Create shortcut node for Steer order
if (CanFitMoreNodes() && ShouldDelegateOrder("steer") && IsNonDuplicateOrderPrefab(OrderPrefab.Prefabs["steer"]) &&
subItems.Find(i => i.HasTag(Tags.NavTerminal) && i.IsPlayerTeamInteractable) is Item nav && characters.None(c => c.SelectedItem == nav) &&
- nav.GetComponent() is Steering steering && steering.Voltage > steering.MinVoltage)
+ nav.GetComponent() is Steering { HasPower: true } steering)
{
var order = new Order(OrderPrefab.Prefabs["steer"], steering.Item, steering);
AddOrderNode(order);
@@ -2625,9 +2693,9 @@ namespace Barotrauma
bool IsNonDuplicateOrder(Order order) => IsNonDuplicateOrderPrefab(order.Prefab, order.Option);
bool IsNonDuplicateOrderPrefab(OrderPrefab orderPrefab, Identifier option = default)
{
- return characterContext == null || (option.IsEmpty ?
+ return characterContext == null || (characterContext.CurrentOrders != null && (option.IsEmpty ?
characterContext.CurrentOrders.None(oi => oi?.Identifier == orderPrefab?.Identifier) :
- characterContext.CurrentOrders.None(oi => oi?.Identifier == orderPrefab?.Identifier && oi.Option == option));
+ characterContext.CurrentOrders.None(oi => oi?.Identifier == orderPrefab?.Identifier && oi?.Option == option)));
}
void AddOrderNodeWithIdentifier(string identifier)
{
@@ -3000,7 +3068,7 @@ namespace Barotrauma
{
optionElement.OnSecondaryClicked = (button, _) => CreateAssignmentNodes(button);
}
- var colorMultiplier = characters.Any(c => c.CurrentOrders.Any(o => o != null &&
+ var colorMultiplier = characters.Any(c => c.CurrentOrders != null && c.CurrentOrders.Any(o => o != null &&
o.Identifier == userData.Order.Identifier &&
o.TargetEntity == userData.Order.TargetEntity)) ? 0.5f : 1f;
CreateNodeIcon(Vector2.One, optionElement.RectTransform, item.Prefab.MinimapIcon ?? order.SymbolSprite, order.Color * colorMultiplier, tooltip: item.Name);
@@ -3654,7 +3722,7 @@ namespace Barotrauma
bool hasLeaks = Character.Controlled.CurrentHull.ConnectedGaps.Any(g => !g.IsRoomToRoom && g.Open > 0.0f);
ToggleReportButton("reportbreach", hasLeaks);
- bool hasIntruders = Character.CharacterList.Any(c => c.CurrentHull == Character.Controlled.CurrentHull && AIObjectiveFightIntruders.IsValidTarget(c, Character.Controlled, false));
+ bool hasIntruders = Character.CharacterList.Any(c => c.CurrentHull == Character.Controlled.CurrentHull && AIObjectiveFightIntruders.IsValidTarget(c, Character.Controlled, targetCharactersInOtherSubs: false));
ToggleReportButton("reportintruders", hasIntruders);
foreach (GUIComponent reportButton in ReportButtonFrame.Children)
@@ -3780,5 +3848,72 @@ namespace Barotrauma
GameMain.GameSession?.CrewManager?.AddOrder(order, fadeOutTime);
}
}
+
+ private class CharacterInfoComparer : IEqualityComparer
+ {
+ public bool Equals(CharacterInfo x, CharacterInfo y)
+ {
+ if (ReferenceEquals(x, y))
+ {
+ return true;
+ }
+
+ if (x is null || y is null)
+ {
+ return false;
+ }
+
+ return x.ID == y.ID;
+ }
+
+ public int GetHashCode(CharacterInfo obj)
+ {
+ return obj.ID;
+ }
+ }
+
+ public bool UpdateReserveBenchIfNeeded(IEnumerable updatedReserveBench)
+ {
+ var newBench = updatedReserveBench.ToHashSet(new CharacterInfoComparer());
+ var currentBench = reserveBench.ToHashSet(new CharacterInfoComparer());
+
+ bool updateNeeded = !newBench.SetEquals(currentBench);
+ if (updateNeeded)
+ {
+ reserveBench.Clear(); // since this is the reserve bench (characters not instantiated), there's no need to retain any references etc
+ reserveBench.AddRange(updatedReserveBench);
+ }
+
+ return updateNeeded;
+ }
+
+ ///
+ /// This will update which CharacterInfos should be in CrewManager and which shouldn't, excluding the reserve bench.
+ /// The CharacterInfos themselves aren't updated, they will only be either added, removed, or kept as-is.
+ ///
+ public bool UpdateCrewManagerIfNecessary(List updatedCrewManager)
+ {
+ // CharacterInfos no longer in the server's CrewManager
+ var toRemove = characterInfos.Where(original => updatedCrewManager.None(updated => updated.ID == original.ID)).ToList();
+ // CharacterInfos that are in the server's CrewManager but not on the client yet
+ var toAdd = updatedCrewManager.Where(updated => characterInfos.None(original => original.ID == updated.ID)).ToList();
+
+ foreach (CharacterInfo characterInfo in toRemove)
+ {
+ if (characterInfo.Character is Character existingCharacter)
+ {
+ if (!existingCharacter.IsBot) { continue; } // on client side players are also stored here, we should skip those in this case
+ RemoveCharacter(characterInfo.Character, removeInfo: true, resetCrewListIndex: true);
+ }
+ else
+ {
+ characterInfos.Remove(characterInfo);
+ }
+ }
+
+ characterInfos.AddRange(toAdd);
+
+ return toRemove.Count > 0 || toAdd.Count > 0;
+ }
}
}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/CampaignMode.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/CampaignMode.cs
index e34b44a4c..c3dbabd08 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/CampaignMode.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/CampaignMode.cs
@@ -366,7 +366,7 @@ namespace Barotrauma
default:
ShowCampaignUI = true;
CampaignUI.SelectTab(npc.CampaignInteractionType, npc);
- CampaignUI.UpgradeStore?.RequestRefresh();
+ CampaignUI.UpgradeStore?.RequestRefresh(refreshUpgrades: true);
break;
}
@@ -395,13 +395,15 @@ namespace Barotrauma
protected void TryEndRoundWithFuelCheck(Action onConfirm, Action onReturnToMapScreen)
{
+ if (Submarine.MainSub == null) { return; }
+
Submarine.MainSub.CheckFuel();
bool lowFuel = Submarine.MainSub.Info.LowFuel;
if (PendingSubmarineSwitch != null)
{
lowFuel = TransferItemsOnSubSwitch ? (lowFuel && PendingSubmarineSwitch.LowFuel) : PendingSubmarineSwitch.LowFuel;
}
- if (Level.IsLoadedFriendlyOutpost && lowFuel && CargoManager.PurchasedItems.None(i => i.Value.Any(pi => pi.ItemPrefab.Tags.Contains("reactorfuel"))))
+ if (Level.IsLoadedFriendlyOutpost && lowFuel && CargoManager.PurchasedItems.None(i => i.Value.Any(pi => pi.ItemPrefab.Tags.Contains(Tags.ReactorFuel))))
{
var extraConfirmationBox =
new GUIMessageBox(TextManager.Get("lowfuelheader"),
diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/MultiPlayerCampaign.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/MultiPlayerCampaign.cs
index 7e6bc711f..9a8190095 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/MultiPlayerCampaign.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/MultiPlayerCampaign.cs
@@ -532,6 +532,7 @@ namespace Barotrauma
bool isFirstRound = msg.ReadBoolean();
byte campaignID = msg.ReadByte();
+ byte roundId = msg.ReadByte();
UInt16 saveID = msg.ReadUInt16();
string mapSeed = msg.ReadString();
@@ -541,7 +542,7 @@ namespace Barotrauma
{
string savePath = SaveUtil.CreateSavePath(SaveUtil.SaveType.Multiplayer);
- GameMain.GameSession = new GameSession(null, savePath, GameModePreset.MultiPlayerCampaign, CampaignSettings.Empty, mapSeed);
+ GameMain.GameSession = new GameSession(null, Option.None, CampaignDataPath.CreateRegular(savePath), GameModePreset.MultiPlayerCampaign, CampaignSettings.Empty, mapSeed);
campaign = (MultiPlayerCampaign)GameMain.GameSession.GameMode;
campaign.CampaignID = campaignID;
GameMain.NetLobbyScreen.ToggleCampaignMode(true);
@@ -553,7 +554,7 @@ namespace Barotrauma
if (requiredFlags.HasFlag(NetFlags.Misc))
{
- DebugConsole.Log("Received campaign update (Misc)");
+ DebugConsole.Log("Received campaign update (Misc), round id: " + roundId);
UInt16 id = msg.ReadUInt16();
bool purchasedHullRepairs = msg.ReadBoolean();
bool purchasedItemRepairs = msg.ReadBoolean();
@@ -571,7 +572,7 @@ namespace Barotrauma
if (requiredFlags.HasFlag(NetFlags.MapAndMissions))
{
- DebugConsole.Log("Received campaign update (MapAndMissions)");
+ DebugConsole.Log("Received campaign update (MapAndMissions), round id: " + roundId);
UInt16 id = msg.ReadUInt16();
bool forceMapUI = msg.ReadBoolean();
bool allowDebugTeleport = msg.ReadBoolean();
@@ -634,7 +635,7 @@ namespace Barotrauma
if (requiredFlags.HasFlag(NetFlags.SubList))
{
- DebugConsole.Log("Received campaign update (SubList)");
+ DebugConsole.Log("Received campaign update (SubList), round id: " + roundId);
UInt16 id = msg.ReadUInt16();
ushort ownedSubCount = msg.ReadUInt16();
List ownedSubIndices = new List();
@@ -679,7 +680,7 @@ namespace Barotrauma
if (requiredFlags.HasFlag(NetFlags.UpgradeManager))
{
- DebugConsole.Log("Received campaign update (UpgradeManager)");
+ DebugConsole.Log("Received campaign update (UpgradeManager), round id: " + roundId);
UInt16 id = msg.ReadUInt16();
ushort pendingUpgradeCount = msg.ReadUInt16();
@@ -737,7 +738,7 @@ namespace Barotrauma
if (requiredFlags.HasFlag(NetFlags.ItemsInBuyCrate))
{
- DebugConsole.Log("Received campaign update (ItemsInBuyCrate)");
+ DebugConsole.Log("Received campaign update (ItemsInBuyCrate), round id: " + roundId);
UInt16 id = msg.ReadUInt16();
var buyCrateItems = ReadPurchasedItems(msg, sender: null);
if (ShouldApply(NetFlags.ItemsInBuyCrate, id, requireUpToDateSave: true))
@@ -753,7 +754,7 @@ namespace Barotrauma
}
if (requiredFlags.HasFlag(NetFlags.ItemsInSellFromSubCrate))
{
- DebugConsole.Log("Received campaign update (ItemsInSellFromSubCrate)");
+ DebugConsole.Log("Received campaign update (ItemsInSellFromSubCrate), round id: " + roundId);
UInt16 id = msg.ReadUInt16();
var subSellCrateItems = ReadPurchasedItems(msg, sender: null);
if (ShouldApply(NetFlags.ItemsInSellFromSubCrate, id, requireUpToDateSave: true))
@@ -769,7 +770,7 @@ namespace Barotrauma
}
if (requiredFlags.HasFlag(NetFlags.PurchasedItems))
{
- DebugConsole.Log("Received campaign update (PuchasedItems)");
+ DebugConsole.Log("Received campaign update (PuchasedItems), round id: " + roundId);
UInt16 id = msg.ReadUInt16();
var purchasedItems = ReadPurchasedItems(msg, sender: null);
if (ShouldApply(NetFlags.PurchasedItems, id, requireUpToDateSave: true))
@@ -785,7 +786,7 @@ namespace Barotrauma
}
if (requiredFlags.HasFlag(NetFlags.SoldItems))
{
- DebugConsole.Log("Received campaign update (SoldItems)");
+ DebugConsole.Log("Received campaign update (SoldItems), round id: " + roundId);
UInt16 id = msg.ReadUInt16();
var soldItems = ReadSoldItems(msg);
if (ShouldApply(NetFlags.SoldItems, id, requireUpToDateSave: true))
@@ -801,7 +802,7 @@ namespace Barotrauma
}
if (requiredFlags.HasFlag(NetFlags.Reputation))
{
- DebugConsole.Log("Received campaign update (Reputation)");
+ DebugConsole.Log("Received campaign update (Reputation), round id: " + roundId);
UInt16 id = msg.ReadUInt16();
Dictionary factionReps = new Dictionary();
byte factionsCount = msg.ReadByte();
@@ -828,7 +829,7 @@ namespace Barotrauma
}
if (requiredFlags.HasFlag(NetFlags.CharacterInfo))
{
- DebugConsole.Log("Received campaign update (CharacterInfo)");
+ DebugConsole.Log("Received campaign update (CharacterInfo), round id: " + roundId);
UInt16 id = msg.ReadUInt16();
bool hasCharacterData = msg.ReadBoolean();
CharacterInfo myCharacterInfo = null;
@@ -837,16 +838,27 @@ namespace Barotrauma
{
myCharacterInfo = CharacterInfo.ClientRead(CharacterPrefab.HumanSpeciesName, msg, requireJobPrefabFound: !waitForModsDownloaded);
}
- if (!waitForModsDownloaded && ShouldApply(NetFlags.CharacterInfo, id, requireUpToDateSave: true))
+ //don't require the correct round ID for the character info if we're in the lobby
+ // = allow updating the character to the latest one in the lobby, even though we've not loaded to the same round as the server
+ if (!waitForModsDownloaded && ShouldApply(NetFlags.CharacterInfo, id, requireUpToDateSave: true, requireCorrectRoundId: Screen.Selected != GameMain.NetLobbyScreen))
{
if (myCharacterInfo != null)
{
GameMain.Client.CharacterInfo = myCharacterInfo;
GameMain.NetLobbyScreen.SetCampaignCharacterInfo(myCharacterInfo);
+ GameMain.GameSession.RefreshAnyOpenPlayerInfo();
}
else
{
+ //don't reset the character info nor the open UI here here,
+ //the client needs it to be able to customize the character they want to next spawn as
GameMain.NetLobbyScreen.SetCampaignCharacterInfo(null);
+ //if we've already discarded our current character and the server is just "verifying" that,
+ //no need to refresh the UI (no changes, refreshing would just throw the client out of the character settings panel)
+ if (!GameMain.NetLobbyScreen.CampaignCharacterDiscarded)
+ {
+ GameMain.GameSession.RefreshAnyOpenPlayerInfo();
+ }
}
}
}
@@ -863,8 +875,14 @@ namespace Barotrauma
}
campaign.SuppressStateSending = false;
- bool ShouldApply(NetFlags flag, UInt16 id, bool requireUpToDateSave)
+ bool ShouldApply(NetFlags flag, UInt16 id, bool requireUpToDateSave, bool requireCorrectRoundId = true)
{
+ if (requireCorrectRoundId && roundId != campaign.RoundID)
+ {
+ DebugConsole.Log($"Received campaing update for a different round (client: {campaign.RoundID}, server: {roundId}), ignoring...");
+ return false;
+ }
+
if (NetIdUtils.IdMoreRecent(id, campaign.GetLastUpdateIdForFlag(flag)) &&
(!requireUpToDateSave || saveID == campaign.LastSaveID))
{
@@ -919,26 +937,42 @@ namespace Barotrauma
ushort pendingHireLength = msg.ReadUInt16();
List pendingHires = new List();
+ bool[] pendingHiresToReserveBench = new bool[pendingHireLength];
for (int i = 0; i < pendingHireLength; i++)
{
pendingHires.Add(msg.ReadUInt16());
+ pendingHiresToReserveBench[i] = msg.ReadBoolean();
}
- ushort hiredLength = msg.ReadUInt16();
List hiredCharacters = new List();
- for (int i = 0; i < hiredLength; i++)
+ List updatedCrewManager = new List();
+ ushort crewLength = msg.ReadUInt16();
+ for (int i = 0; i < crewLength; i++)
{
- CharacterInfo hired = CharacterInfo.ClientRead(CharacterPrefab.HumanSpeciesName, msg);
- hired.Salary = msg.ReadInt32();
- hiredCharacters.Add(hired);
+ CharacterInfo crewMember = CharacterInfo.ClientRead(CharacterPrefab.HumanSpeciesName, msg);
+ if (crewMember.IsNewHire)
+ {
+ hiredCharacters.Add(crewMember);
+ }
+ updatedCrewManager.Add(crewMember);
}
-
+ bool crewManagerUpdated = GameMain.GameSession.CrewManager?.UpdateCrewManagerIfNecessary(updatedCrewManager) ?? false;
+
+ ushort reserveBenchLength = msg.ReadUInt16();
+ List updatedReserveBench = new List();
+ for (int i = 0; i < reserveBenchLength; i++)
+ {
+ CharacterInfo info = CharacterInfo.ClientRead(CharacterPrefab.HumanSpeciesName, msg);
+ updatedReserveBench.Add(info);
+ }
+ bool reserveBenchUpdated = GameMain.GameSession.CrewManager?.UpdateReserveBenchIfNeeded(updatedReserveBench) ?? false;
+
bool renameCrewMember = msg.ReadBoolean();
if (renameCrewMember)
{
UInt16 renamedIdentifier = msg.ReadUInt16();
string newName = msg.ReadString();
- CharacterInfo renamedCharacter = CrewManager.GetCharacterInfos().FirstOrDefault(info => info.ID == renamedIdentifier);
+ CharacterInfo renamedCharacter = CrewManager.GetCharacterInfos(includeReserveBench: true).FirstOrDefault(info => info.ID == renamedIdentifier);
if (renamedCharacter != null)
{
CrewManager.RenameCharacter(renamedCharacter, newName);
@@ -955,7 +989,7 @@ namespace Barotrauma
if (fireCharacter)
{
UInt16 firedIdentifier = msg.ReadUInt16();
- CharacterInfo firedCharacter = CrewManager.GetCharacterInfos().FirstOrDefault(info => info.ID == firedIdentifier);
+ CharacterInfo firedCharacter = CrewManager.GetCharacterInfos(includeReserveBench: true).FirstOrDefault(info => info.ID == firedIdentifier);
// this one might and is allowed to be null since the character is already fired on the original sender's game
if (firedCharacter != null) { CrewManager.FireCharacter(firedCharacter); }
}
@@ -967,8 +1001,8 @@ namespace Barotrauma
{
CampaignUI.HRManagerUI.SetHireables(map.CurrentLocation, availableHires);
if (hiredCharacters.Any()) { CampaignUI.HRManagerUI.ValidateHires(hiredCharacters, takeMoney: false, createNotification: createNotification); }
- CampaignUI.HRManagerUI.SetPendingHires(pendingHires, map.CurrentLocation);
- if (renameCrewMember || fireCharacter) { CampaignUI.HRManagerUI.UpdateCrew(); }
+ //don't check the crew size limit: if the server says someone's hired, then it's so
+ CampaignUI.HRManagerUI.SetPendingHires(pendingHires, pendingHiresToReserveBench, map.CurrentLocation, checkCrewSizeLimit: false);
}
}
else
@@ -979,6 +1013,11 @@ namespace Barotrauma
CurrentLocation?.ForceHireableCharacters(availableHires);
}
+ if (fireCharacter || renameCrewMember || crewManagerUpdated || reserveBenchUpdated)
+ {
+ CampaignUI?.HRManagerUI?.RefreshHRView();
+ GameMain.GameSession?.DeathPrompt?.UpdateBotList();
+ }
}
public void ClientReadMoney(IReadMessage inc)
@@ -1044,7 +1083,7 @@ namespace Barotrauma
return false;
}
- public override void Save(XElement element)
+ public override void Save(XElement element, bool isSavingOnLoading)
{
//do nothing, the clients get the save files from the server
}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/SinglePlayerCampaign.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/SinglePlayerCampaign.cs
index f16ba4ad8..cdd4d140c 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/SinglePlayerCampaign.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/SinglePlayerCampaign.cs
@@ -240,7 +240,7 @@ namespace Barotrauma
if (!savedOnStart)
{
GUI.SetSavingIndicatorState(true);
- SaveUtil.SaveGame(GameMain.GameSession.SavePath);
+ SaveUtil.SaveGame(GameMain.GameSession.DataPath, isSavingOnLoading: true);
savedOnStart = true;
}
@@ -379,7 +379,7 @@ namespace Barotrauma
if (success)
{
// Event history must be registered before ending the round or it will be cleared
- GameMain.GameSession.EventManager.RegisterEventHistory();
+ GameMain.GameSession.EventManager.StoreEventDataAtRoundEnd();
}
GameMain.GameSession.EndRound("", transitionType);
var continueButton = GameMain.GameSession.RoundSummary?.ContinueButton;
@@ -448,7 +448,7 @@ namespace Barotrauma
if (success)
{
GameMain.GameSession.SubmarineInfo = new SubmarineInfo(GameMain.GameSession.Submarine);
- SaveUtil.SaveGame(GameMain.GameSession.SavePath);
+ SaveUtil.SaveGame(GameMain.GameSession.DataPath);
}
else
{
@@ -479,7 +479,7 @@ namespace Barotrauma
protected override void EndCampaignProjSpecific()
{
GameMain.GameSession.SubmarineInfo = new SubmarineInfo(GameMain.GameSession.Submarine);
- SaveUtil.SaveGame(GameMain.GameSession.SavePath);
+ SaveUtil.SaveGame(GameMain.GameSession.DataPath);
GameMain.CampaignEndScreen.Select();
GUI.DisableHUD = false;
GameMain.CampaignEndScreen.OnFinished = () =>
@@ -672,7 +672,7 @@ namespace Barotrauma
}
}
- public override void Save(XElement element)
+ public override void Save(XElement element, bool isSavingOnLoading)
{
XElement modeElement = new XElement("SinglePlayerCampaign",
new XAttribute("purchasedlostshuttles", PurchasedLostShuttles),
diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/TestGameMode.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/TestGameMode.cs
index c945b6103..9dc938aa5 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/TestGameMode.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/TestGameMode.cs
@@ -7,7 +7,7 @@ using Barotrauma.Items.Components;
namespace Barotrauma
{
- class TestGameMode : GameMode
+ partial class TestGameMode : GameMode
{
public Action OnRoundEnd;
@@ -22,18 +22,6 @@ namespace Barotrauma
private GUIButton createEventButton;
- public TestGameMode(GameModePreset preset) : base(preset)
- {
- foreach (JobPrefab jobPrefab in JobPrefab.Prefabs.OrderBy(p => p.Identifier))
- {
- for (int i = 0; i < jobPrefab.InitialCount; i++)
- {
- var variant = Rand.Range(0, jobPrefab.Variants);
- CrewManager.AddCharacterInfo(new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: jobPrefab, variant: variant));
- }
- }
- }
-
public override void Start()
{
base.Start();
@@ -42,17 +30,24 @@ namespace Barotrauma
foreach (Submarine submarine in Submarine.Loaded)
{
submarine.NeutralizeBallast();
- //normally the body would be made static during level generation,
- //but in the test mode we load the outpost/wreck/beacon as if it was a normal sub and need to do this manually
- if (submarine.Info.Type == SubmarineType.Outpost ||
- submarine.Info.Type == SubmarineType.OutpostModule ||
- submarine.Info.Type == SubmarineType.Wreck ||
- submarine.Info.Type == SubmarineType.BeaconStation)
+ switch (submarine.Info.Type)
{
- submarine.PhysicsBody.BodyType = FarseerPhysics.BodyType.Static;
+ case SubmarineType.Outpost:
+ case SubmarineType.OutpostModule:
+ case SubmarineType.Wreck:
+ case SubmarineType.BeaconStation:
+ //normally the body would be made static during level generation,
+ //but in the test mode we load the outpost/wreck/beacon as if it was a normal sub and need to do this manually
+ submarine.PhysicsBody.BodyType = FarseerPhysics.BodyType.Static;
+ if (submarine.Info.ShouldBeRuin)
+ {
+ submarine.Info.Type = SubmarineType.Ruin;
+ }
+ submarine.TeamID = submarine.Info.IsOutpost ? CharacterTeamType.FriendlyNPC : CharacterTeamType.None;
+ break;
}
}
-
+
if (SpawnOutpost)
{
GenerateOutpost(Submarine.MainSub);
diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/Tutorial.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/Tutorial.cs
index 66efaee87..3b6ac9488 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/Tutorial.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/Tutorial.cs
@@ -86,7 +86,7 @@ namespace Barotrauma.Tutorials
yield return CoroutineStatus.Running;
- GameMain.GameSession = new GameSession(subInfo, GameModePreset.Tutorial, missionPrefabs: null);
+ GameMain.GameSession = new GameSession(subInfo, Option.None, GameModePreset.Tutorial, missionPrefabs: null);
(GameMain.GameSession.GameMode as TutorialMode).Tutorial = this;
if (generationParams is not null)
@@ -138,7 +138,7 @@ namespace Barotrauma.Tutorials
character = Character.Create(charInfo, wayPoint.WorldPosition, "", isRemotePlayer: false, hasAi: false);
character.TeamID = CharacterTeamType.Team1;
Character.Controlled = character;
- character.GiveJobItems(null);
+ character.GiveJobItems(isPvPMode: false, null);
var idCard = character.Inventory.FindItemByTag("identitycard".ToIdentifier());
if (idCard == null)
diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameSession.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameSession.cs
index 728c8a7c7..df8b52048 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameSession.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameSession.cs
@@ -110,9 +110,13 @@ namespace Barotrauma
deathChoiceInfoFrame = new GUIFrame(new RectTransform(new Vector2(0.5f, 1.0f), parent: topLeftButtonGroup.RectTransform)
{ MaxSize = new Point(HUDLayoutSettings.ButtonAreaTop.Width / 3, int.MaxValue) }, style: null)
{
+ CanBeFocused = false,
Visible = false
};
- respawnInfoText = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), deathChoiceInfoFrame.RectTransform), "", wrap: true);
+ respawnInfoText = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), deathChoiceInfoFrame.RectTransform), "", wrap: true)
+ {
+ CanBeFocused = false
+ };
deathChoiceButtonContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 1.0f), deathChoiceInfoFrame.RectTransform, Anchor.CenterRight), isHorizontal: true, childAnchor: Anchor.CenterLeft)
{
AbsoluteSpacing = HUDLayoutSettings.Padding,
@@ -189,7 +193,7 @@ namespace Barotrauma
if (GameMain.NetworkMember != null)
{
GameMain.NetLobbyScreen.CharacterAppearanceCustomizationMenu?.AddToGUIUpdateList();
- GameMain.NetLobbyScreen?.JobSelectionFrame?.AddToGUIUpdateList();
+ GameMain.NetLobbyScreen?.JobSelectionFrame?.AddToGUIUpdateList(order: 1);
}
DeathPrompt?.AddToGUIUpdateList();
@@ -282,10 +286,10 @@ namespace Barotrauma
public void SetRespawnInfo(string text, Color textColor, bool waitForNextRoundRespawn, bool hideButtons = false)
{
if (topLeftButtonGroup == null) { return; }
-
+
bool permadeathMode = GameMain.NetworkMember?.ServerSettings is { RespawnMode: RespawnMode.Permadeath };
- bool ironmanMode = GameMain.NetworkMember is { ServerSettings: { RespawnMode: RespawnMode.Permadeath, IronmanMode: true } };
-
+ bool ironmanMode = GameMain.NetworkMember?.ServerSettings is { IronmanModeActive: true };
+
bool hasRespawnOptions;
if (permadeathMode)
{
@@ -335,6 +339,20 @@ namespace Barotrauma
}
}
}
+
+ ///
+ /// If there are any menu panels etc. open that contain information about the current player character, refresh it.
+ /// Useful when the player character changes, e.g. at permadeath, and subsequent taking over of a bot character.
+ ///
+ public void RefreshAnyOpenPlayerInfo()
+ {
+ DebugConsole.NewMessage($"Refreshing any open player info");
+ if (IsTabMenuOpen && TabMenu.SelectedTab == TabMenu.InfoFrameTab.Talents)
+ {
+ TabMenuInstance.SelectInfoFrameTab(TabMenu.InfoFrameTab.Talents);
+ }
+ // TODO: This can be expanded as need arises
+ }
public void Draw(SpriteBatch spriteBatch)
{
diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/HintManager.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/HintManager.cs
index d859a5af5..a308c988c 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/HintManager.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/HintManager.cs
@@ -205,7 +205,7 @@ namespace Barotrauma
if (Character.Controlled.SelectedItem.GetComponent() is Reactor reactor && reactor.PowerOn &&
Character.Controlled.SelectedItem.OwnInventory?.AllItems is IEnumerable
- containedItems &&
- containedItems.Count(i => i.HasTag(Tags.Fuel)) > 1)
+ containedItems.Count(i => i.HasTag(Tags.ReactorFuel)) > 1)
{
if (DisplayHint("onisinteracting.reactorwithextrarods".ToIdentifier())) { return; }
}
@@ -316,7 +316,7 @@ namespace Barotrauma
if (affliction?.Prefab == null) { continue; }
if (affliction.Prefab.IsBuff) { continue; }
if (affliction.Prefab == AfflictionPrefab.OxygenLow) { continue; }
- if (affliction.Prefab == AfflictionPrefab.RadiationSickness && (GameMain.GameSession.Map?.Radiation?.IsEntityRadiated(character) ?? false)) { continue; }
+ if (affliction.Prefab == AfflictionPrefab.RadiationSickness && (GameMain.GameSession.Map?.Radiation?.DepthInRadiation(character) ?? 0) > 0) { continue; }
if (affliction.Strength < affliction.Prefab.ShowIconThreshold) { continue; }
DisplayHint("onafflictiondisplayed".ToIdentifier(),
variables: new[] { ("[key]".ToIdentifier(), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Health)) },
diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/PvPMode.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/PvPMode.cs
new file mode 100644
index 000000000..80a1449ee
--- /dev/null
+++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/PvPMode.cs
@@ -0,0 +1,84 @@
+using Microsoft.Xna.Framework;
+namespace Barotrauma
+{
+ partial class PvPMode : MissionMode
+ {
+ private GUIComponent scoreContainer;
+ private readonly GUITextBlock[] scoreTexts = new GUITextBlock[2];
+ private readonly GUITextBlock[] scoreTextShadows = new GUITextBlock[2];
+ private readonly int[] prevScores = new int[2];
+
+ private void InitUI()
+ {
+ scoreContainer = new GUILayoutGroup(HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.TutorialObjectiveListArea, GUI.Canvas), childAnchor: Anchor.TopRight)
+ {
+ CanBeFocused = false
+ };
+ for (int i = 0; i < 2; i++)
+ {
+ var frame = new GUIFrame(new RectTransform(new Point(scoreContainer.Rect.Width, GUI.IntScale(80)), scoreContainer.RectTransform), style: null)
+ {
+ CanBeFocused = false
+ };
+ new GUIImage(new RectTransform(Vector2.One, frame.RectTransform, Anchor.CenterRight, scaleBasis: ScaleBasis.BothHeight), style: i == 0 ? "CoalitionIcon" : "SeparatistIcon")
+ {
+ CanBeFocused = false
+ };
+ scoreTextShadows[i] = new GUITextBlock(new RectTransform(Vector2.One, frame.RectTransform, Anchor.CenterRight, scaleBasis: ScaleBasis.BothHeight) { AbsoluteOffset = new Point(GUI.IntScale(38), GUI.IntScale(2)) },
+ string.Empty, textColor: GUIStyle.TextColorDark, textAlignment: Alignment.CenterRight, font: GUIStyle.SubHeadingFont)
+ {
+ CanBeFocused = false
+ };
+ scoreTexts[i] = new GUITextBlock(new RectTransform(Vector2.One, frame.RectTransform, Anchor.CenterRight, scaleBasis: ScaleBasis.BothHeight) { AbsoluteOffset = new Point(GUI.IntScale(40), 0) },
+ string.Empty, textAlignment: Alignment.CenterRight, font: GUIStyle.SubHeadingFont)
+ {
+ CanBeFocused = false
+ };
+ }
+ }
+
+ public override void AddToGUIUpdateList()
+ {
+ base.AddToGUIUpdateList();
+
+ if (scoreContainer == null) { InitUI(); }
+
+ scoreContainer.Visible = false;
+ foreach (var mission in Missions)
+ {
+ if (mission is CombatMission combatMission && combatMission.HasWinScore)
+ {
+ for (int i = 0; i < 2; i++)
+ {
+ var scoreText = scoreTexts[i];
+ //one team very close to the win score, start flashing the score
+ if (combatMission.Scores[i] > combatMission.WinScore * 0.9f ||
+ combatMission.Scores[i] == combatMission.WinScore - 1)
+ {
+ if (scoreText.Parent.FlashTimer <= 0.0f)
+ {
+ scoreText.Parent.Flash(GUIStyle.Orange);
+ scoreText.Pulsate(Vector2.One, Vector2.One * 1.2f, scoreText.Parent.FlashTimer);
+ }
+ }
+ if (prevScores[i] != combatMission.Scores[i] || scoreText.Text.IsNullOrEmpty())
+ {
+ scoreText.Text = scoreTextShadows[i].Text = $"{combatMission.Scores[i]}/{combatMission.WinScore}";
+ scoreText.Parent.Flash(GUIStyle.Green);
+ scoreText.Parent.GetAnyChild().Pulsate(Vector2.One, Vector2.One * 1.2f, scoreText.Parent.FlashTimer);
+ SoundPlayer.PlayUISound(GUISoundType.UIMessage);
+ }
+ scoreText.Parent.RectTransform.NonScaledSize =
+ new Point(
+ (int)(scoreText.TextSize.X + scoreText.Padding.X + scoreText.Padding.X) + scoreText.Parent.GetChild().Rect.Width + GUI.IntScale(10),
+ scoreText.Parent.Rect.Height);
+ scoreText.Parent.ForceLayoutRecalculation();
+ prevScores[i] = combatMission.Scores[i];
+ }
+ scoreContainer.Visible = true;
+ }
+ }
+ scoreContainer.AddToGUIUpdateList();
+ }
+ }
+}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/RoundSummary.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/RoundSummary.cs
index 8ab663787..ceab34567 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/RoundSummary.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/RoundSummary.cs
@@ -1,4 +1,5 @@
-using Microsoft.Xna.Framework;
+using Barotrauma.Networking;
+using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
@@ -13,11 +14,13 @@ namespace Barotrauma
private float crewListAnimDelay = 0.25f;
private float missionIconAnimDelay;
- private const float jobColumnWidthPercentage = 0.11f;
- private const float characterColumnWidthPercentage = 0.44f;
- private const float statusColumnWidthPercentage = 0.45f;
+ private const float JobColumnWidthPercentage = 0.05f;
+ private const float CharacterColumnWidthPercentage = 0.35f;
+ private const float StatusColumnWidthPercentage = 0.25f;
+ private const float KillColumnWidthPercentage = 0.05f;
+ private const float DeathColumnWidthPercentage = 0.05f;
- private int jobColumnWidth, characterColumnWidth, statusColumnWidth;
+ private int jobColumnWidth, characterColumnWidth, statusColumnWidth, killColumnWidth, deathColumnWidth;
private readonly List selectedMissions;
private readonly Location startLocation, endLocation;
@@ -109,6 +112,16 @@ namespace Barotrauma
CombatMission.GetTeamName(CharacterTeamType.Team2), textAlignment: Alignment.TopLeft, font: GUIStyle.SubHeadingFont);
crewHeader2.RectTransform.MinSize = new Point(0, GUI.IntScale(crewHeader2.Rect.Height * 2.0f));
CreateCrewList(crewContent2, gameSession.CrewManager.GetCharacterInfos().Where(c => c.TeamID == CharacterTeamType.Team2), traitorResults);
+
+ if (CombatMission.Winner != CharacterTeamType.None)
+ {
+ new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), crewHeader.RectTransform),
+ TextManager.Get(CombatMission.Winner == CharacterTeamType.Team1 ? "pvpmode.victory" : "pvpmode.defeat"), textAlignment: Alignment.TopRight, font: GUIStyle.SubHeadingFont,
+ textColor: CombatMission.Winner == CharacterTeamType.Team1 ? GUIStyle.Green : GUIStyle.Red);
+ new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), crewHeader2.RectTransform),
+ TextManager.Get(CombatMission.Winner == CharacterTeamType.Team2 ? "pvpmode.victory" : "pvpmode.defeat"), textAlignment: Alignment.TopRight, font: GUIStyle.SubHeadingFont,
+ textColor: CombatMission.Winner == CharacterTeamType.Team2 ? GUIStyle.Green : GUIStyle.Red);
+ }
}
//header -------------------------------------------------------------------------------
@@ -238,11 +251,12 @@ namespace Barotrauma
textContent,
mission.Difficulty ?? 0,
mission.Prefab.Icon, mission.Prefab.IconColor,
+ mission.GetDifficultyToolTipText(),
out GUIImage missionIcon);
if (selectedMissions.Contains(mission))
{
- UpdateMissionStateIcon(mission.Completed, missionIcon, animDelay);
+ UpdateMissionStateIcon(mission is CombatMission combatMission ? CombatMission.IsInWinningTeam(GameMain.Client?.Character) : mission.Completed, missionIcon, animDelay);
animDelay += 0.25f;
}
}
@@ -429,13 +443,14 @@ namespace Barotrauma
textContent,
difficultyIconCount: 0,
icon, GUIStyle.Red,
+ difficultyTooltipText: null,
out GUIImage missionIcon);
UpdateMissionStateIcon(traitorResults.VotedCorrectTraitor, missionIcon, iconAnimDelay);
return content;
}
public static GUIComponent CreateMissionEntry(GUIComponent parent, LocalizedString header, List textContent, int difficultyIconCount,
- Sprite icon, Color iconColor, out GUIImage missionIcon)
+ Sprite icon, Color iconColor, RichString difficultyTooltipText, out GUIImage missionIcon)
{
int spacing = GUI.IntScale(5);
@@ -486,7 +501,8 @@ namespace Barotrauma
{
difficultyIndicatorGroup = new GUILayoutGroup(new RectTransform(new Point(missionTextGroup.Rect.Width, defaultLineHeight), parent: missionTextGroup.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft)
{
- AbsoluteSpacing = 1
+ AbsoluteSpacing = 1,
+ CanBeFocused = true
};
difficultyIndicatorGroup.RectTransform.MinSize = new Point(0, defaultLineHeight);
var difficultyColor = Mission.GetDifficultyColor(difficultyIconCount);
@@ -494,8 +510,8 @@ namespace Barotrauma
{
new GUIImage(new RectTransform(Vector2.One, difficultyIndicatorGroup.RectTransform, scaleBasis: ScaleBasis.Smallest), "DifficultyIndicator", scaleToFit: true)
{
- CanBeFocused = false,
- Color = difficultyColor
+ Color = difficultyColor,
+ ToolTip = difficultyTooltipText
};
}
}
@@ -567,7 +583,11 @@ namespace Barotrauma
LocalizedString locationName = Submarine.MainSub is { AtEndExit: true } ? endLocation?.DisplayName : startLocation?.DisplayName;
string textTag;
- if (gameOver)
+ if (gameMode is PvPMode)
+ {
+ textTag = "RoundSummaryRoundHasEnded";
+ }
+ else if (gameOver)
{
textTag = "RoundSummaryGameOver";
}
@@ -596,7 +616,14 @@ namespace Barotrauma
textTag = "RoundSummaryReturnToEmptyLocation";
break;
default:
- textTag = Submarine.MainSub.AtEndExit ? "RoundSummaryProgress" : "RoundSummaryReturn";
+ if (Submarine.MainSub == null)
+ {
+ textTag = "RoundSummaryRoundHasEnded";
+ }
+ else
+ {
+ textTag = Submarine.MainSub.AtEndExit ? "RoundSummaryProgress" : "RoundSummaryReturn";
+ }
break;
}
}
@@ -628,26 +655,34 @@ namespace Barotrauma
{
var headerFrame = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.0f), parent.RectTransform, Anchor.TopCenter, minSize: new Point(0, (int)(30 * GUI.Scale))) { }, isHorizontal: true)
{
- AbsoluteSpacing = 2
+ AbsoluteSpacing = 2,
+ Stretch = true
};
- GUIButton jobButton = new GUIButton(new RectTransform(new Vector2(0f, 1f), headerFrame.RectTransform), TextManager.Get("tabmenu.job"), style: "GUIButtonSmallFreeScale");
- GUIButton characterButton = new GUIButton(new RectTransform(new Vector2(0f, 1f), headerFrame.RectTransform), TextManager.Get("name"), style: "GUIButtonSmallFreeScale");
- GUIButton statusButton = new GUIButton(new RectTransform(new Vector2(0f, 1f), headerFrame.RectTransform), TextManager.Get("label.statuslabel"), style: "GUIButtonSmallFreeScale");
+ GUIButton jobButton = new GUIButton(new RectTransform(new Vector2(JobColumnWidthPercentage, 1f), headerFrame.RectTransform), TextManager.Get("tabmenu.job"), style: "GUIButtonSmallFreeScale");
+ GUIButton characterButton = new GUIButton(new RectTransform(new Vector2(CharacterColumnWidthPercentage, 1f), headerFrame.RectTransform), TextManager.Get("name"), style: "GUIButtonSmallFreeScale");
- float sizeMultiplier = 1.0f;
- //sizeMultiplier = (headerFrame.Rect.Width - headerFrame.AbsoluteSpacing * (headerFrame.CountChildren - 1)) / (float)headerFrame.Rect.Width;
+ if (gameMode is PvPMode && GameMain.NetworkMember?.RespawnManager != null)
+ {
+ var killButton = new GUIButton(new RectTransform(new Vector2(KillColumnWidthPercentage, 1f), headerFrame.RectTransform), TextManager.Get("killcount"), style: "GUIButtonSmallFreeScale");
+ killColumnWidth = killButton.Rect.Width;
+ var deathButton = new GUIButton(new RectTransform(new Vector2(DeathColumnWidthPercentage, 1f), headerFrame.RectTransform), TextManager.Get("deathcount"), style: "GUIButtonSmallFreeScale");
+ deathColumnWidth = deathButton.Rect.Width;
+ }
+ else
+ {
+ GUIButton statusButton = new GUIButton(new RectTransform(new Vector2(StatusColumnWidthPercentage, 1f), headerFrame.RectTransform), TextManager.Get("label.statuslabel"), style: "GUIButtonSmallFreeScale");
+ statusColumnWidth = statusButton.Rect.Width;
+ }
- jobButton.RectTransform.RelativeSize = new Vector2(jobColumnWidthPercentage * sizeMultiplier, 1f);
- characterButton.RectTransform.RelativeSize = new Vector2(characterColumnWidthPercentage * sizeMultiplier, 1f);
- statusButton.RectTransform.RelativeSize = new Vector2(statusColumnWidthPercentage * sizeMultiplier, 1f);
-
- jobButton.TextBlock.Font = characterButton.TextBlock.Font = statusButton.TextBlock.Font = GUIStyle.HotkeyFont;
- jobButton.CanBeFocused = characterButton.CanBeFocused = statusButton.CanBeFocused = false;
- jobButton.TextBlock.ForceUpperCase = characterButton.TextBlock.ForceUpperCase = statusButton.ForceUpperCase = ForceUpperCase.Yes;
+ foreach (var btn in headerFrame.GetAllChildren())
+ {
+ btn.TextBlock.Font = GUIStyle.HotkeyFont;
+ btn.ForceUpperCase = ForceUpperCase.Yes;
+ btn.CanBeFocused = false;
+ }
jobColumnWidth = jobButton.Rect.Width;
characterColumnWidth = characterButton.Rect.Width;
- statusColumnWidth = statusButton.Rect.Width;
GUIListBox crewList = new GUIListBox(new RectTransform(Vector2.One, parent.RectTransform))
{
@@ -658,8 +693,28 @@ namespace Barotrauma
headerFrame.RectTransform.RelativeSize -= new Vector2(crewList.ScrollBar.RectTransform.RelativeSize.X, 0.0f);
+ killCounts.Clear();
+ if (GameMain.NetworkMember != null)
+ {
+ foreach (CharacterInfo characterInfo in characterInfos)
+ {
+ if (characterInfo == null) { continue; }
+ Character character = characterInfo.Character;
+ Client ownerClient = GameMain.NetworkMember.ConnectedClients.FirstOrDefault(c => c.Character == character);
+ int killCount = 0, deathCount = 0;
+ foreach (var mission in selectedMissions)
+ {
+ if (mission is not CombatMission combatMission) { continue; }
+ killCount += ownerClient == null ? combatMission.GetBotKillCount(characterInfo) : combatMission.GetClientKillCount(ownerClient);
+ deathCount += ownerClient == null ? combatMission.GetBotDeathCount(characterInfo) : combatMission.GetClientDeathCount(ownerClient);
+ }
+ killCounts[characterInfo] = killCount;
+ deathCounts[characterInfo] = deathCount;
+ }
+ }
+
float delay = crewListAnimDelay;
- foreach (CharacterInfo characterInfo in characterInfos)
+ foreach (CharacterInfo characterInfo in characterInfos.OrderByDescending(ci => killCounts.GetValueOrDefault(ci)))
{
if (characterInfo == null) { continue; }
CreateCharacterElement(characterInfo, crewList, traitorResults, delay);
@@ -670,6 +725,10 @@ namespace Barotrauma
return crewList;
}
+ private readonly Dictionary killCounts = new();
+ private readonly Dictionary deathCounts = new();
+
+
private void CreateCharacterElement(CharacterInfo characterInfo, GUIListBox listBox, TraitorManager.TraitorResults? traitorResults, float animDelay)
{
GUIFrame frame = new GUIFrame(new RectTransform(new Point(listBox.Content.Rect.Width, GUI.IntScale(45)), listBox.Content.RectTransform), style: "ListBoxElement")
@@ -701,7 +760,7 @@ namespace Barotrauma
Character character = characterInfo.Character;
if (character == null || character.IsDead)
{
- if (character == null && characterInfo.IsNewHire && characterInfo.CauseOfDeath == null)
+ if (character == null && (characterInfo.IsNewHire || characterInfo.BotStatus == BotStatus.ActiveService) && characterInfo.CauseOfDeath == null)
{
statusText = TextManager.Get("CampaignCrew.NewHire");
statusColor = GUIStyle.Blue;
@@ -741,8 +800,21 @@ namespace Barotrauma
}
}
- GUITextBlock statusBlock = new GUITextBlock(new RectTransform(new Point(statusColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform),
- ToolBox.LimitString(statusText.Value, GUIStyle.Font, characterColumnWidth), textAlignment: Alignment.Center, textColor: statusColor);
+ if (gameMode is PvPMode && GameMain.NetworkMember?.RespawnManager != null)
+ {
+ new GUITextBlock(new RectTransform(new Point(killColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform),
+ killCounts.GetValueOrDefault(characterInfo).ToString(), textAlignment: Alignment.Center);
+ new GUITextBlock(new RectTransform(new Point(deathColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform),
+ deathCounts.GetValueOrDefault(characterInfo).ToString(), textAlignment: Alignment.Center);
+ }
+ else
+ {
+ GUITextBlock statusBlock = new GUITextBlock(new RectTransform(new Point(statusColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform),
+ ToolBox.LimitString(statusText.Value, GUIStyle.SmallFont, statusColumnWidth), textAlignment: Alignment.Center, textColor: statusColor, font: GUIStyle.SmallFont)
+ {
+ ToolTip = statusText.Value
+ };
+ }
frame.FadeIn(animDelay, 0.15f);
foreach (var child in frame.GetAllChildren())
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/CharacterInventory.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/CharacterInventory.cs
index c2501c220..8d3ad40d5 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Items/CharacterInventory.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Items/CharacterInventory.cs
@@ -84,7 +84,7 @@ namespace Barotrauma
get { return layout; }
set
{
- if (layout == value) return;
+ if (layout == value) { return; }
layout = value;
SetSlotPositions(layout);
}
@@ -259,8 +259,8 @@ namespace Barotrauma
int spacing = GUI.IntScale(5);
SlotSize = (SlotSpriteSmall.size * UIScale * GUI.AspectRatioAdjustment).ToPoint();
- int bottomOffset = SlotSize.Y + spacing * 2 + ContainedIndicatorHeight;
- int personalSlotY = GameMain.GraphicsHeight - bottomOffset * 2 - spacing * 2 - (int)(UnequippedIndicator.size.Y * UIScale);
+ int bottomOffset = GetBottomOffset(multiplier: 2);
+ int personalSlotY = GetVerticalOffsetFromBottom(multiplier: 2);
if (visualSlots == null) { CreateSlots(); }
if (visualSlots.None()) { return; }
@@ -353,7 +353,15 @@ namespace Barotrauma
case Layout.Left:
{
int x = HUDLayoutSettings.InventoryAreaLower.X;
+ if (!GUI.IsUltrawide && GUI.IsHUDScaled)
+ {
+ // On non-ultra-wide aspect ratios, the inventories can easily overlap with each other, if there's any scaling.
+ // So let's offset the other inventory to the left.
+ const float margin = 100;
+ x -= HUDLayoutSettings.ChatBoxArea.Width - (int)margin;
+ }
int personalSlotX = x;
+ float y = GameMain.GraphicsHeight - bottomOffset;
for (int i = 0; i < SlotPositions.Length; i++)
{
@@ -366,7 +374,7 @@ namespace Barotrauma
}
else
{
- SlotPositions[i] = new Vector2(x, GameMain.GraphicsHeight - bottomOffset);
+ SlotPositions[i] = new Vector2(x, y);
x += visualSlots[i].Rect.Width + spacing;
}
}
@@ -380,7 +388,7 @@ namespace Barotrauma
continue;
}
if (!HideSlot(i) || SlotTypes[i] == InvSlotType.HealthInterface) { continue; }
- SlotPositions[i] = new Vector2(x, GameMain.GraphicsHeight - bottomOffset);
+ SlotPositions[i] = new Vector2(x, y);
x += visualSlots[i].Rect.Width + spacing;
}
}
@@ -446,6 +454,9 @@ namespace Barotrauma
visualSlots[i].DrawOffset = Vector2.Zero;
}
}
+
+ int GetBottomOffset(int multiplier) => SlotSize.Y + spacing * multiplier + ContainedIndicatorHeight;
+ int GetVerticalOffsetFromBottom(int multiplier) => GameMain.GraphicsHeight - (GetBottomOffset(multiplier) + spacing) * multiplier - (int)(UnequippedIndicator.size.Y * UIScale);
}
protected override void ControlInput(Camera cam)
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Door.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Door.cs
index 5ea8a5265..d492bb83d 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Door.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Door.cs
@@ -184,6 +184,13 @@ namespace Barotrauma.Items.Components
{
shakePos = Vector2.Zero;
}
+ if (Character.Controlled is Character character && character.FocusedItem == item)
+ {
+ if ((IsFullyOpen || IsFullyClosed) && MathF.Abs(openState - lastOpenState) > 0)
+ {
+ CharacterHUD.RecreateHudTexts = true;
+ }
+ }
}
public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1, Color? overrideColor = null)
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/GeneticMaterial.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/GeneticMaterial.cs
index 7d5981608..ac03954d2 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/GeneticMaterial.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/GeneticMaterial.cs
@@ -14,6 +14,7 @@ namespace Barotrauma.Items.Components
public override void AddTooltipInfo(ref LocalizedString name, ref LocalizedString description)
{
+ bool mergedMaterialTainted = false;
if (!materialName.IsNullOrEmpty() && item.ContainedItems.Count() > 0)
{
LocalizedString mergedMaterialName = materialName;
@@ -22,11 +23,15 @@ namespace Barotrauma.Items.Components
var containedMaterial = containedItem.GetComponent();
if (containedMaterial == null) { continue; }
mergedMaterialName += ", " + containedMaterial.materialName;
+ if (containedMaterial.Tainted)
+ {
+ mergedMaterialTainted = true;
+ }
}
name = name.Replace(materialName, mergedMaterialName);
}
- if (Tainted)
+ if (Tainted || mergedMaterialTainted)
{
name = TextManager.GetWithVariable("entityname.taintedgeneticmaterial", "[geneticmaterialname]", name);
}
@@ -48,25 +53,46 @@ namespace Barotrauma.Items.Components
description += '\n' + containedDescription;
}
}
+
+ if (GameMain.DevMode && Tainted && selectedTaintedEffect != null)
+ {
+ description = $"{description}\n{selectedTaintedEffect.Name}: {selectedTaintedEffect.GetDescription(0f, AfflictionPrefab.Description.TargetType.OtherCharacter)}";
+ }
}
public void ModifyDeconstructInfo(Deconstructor deconstructor, ref LocalizedString buttonText, ref LocalizedString infoText)
{
if (deconstructor.InputContainer.Inventory.AllItems.Count() == 2)
{
- var otherGeneticMaterial =
- deconstructor.InputContainer.Inventory.AllItems.FirstOrDefault(it => it != item && it.Prefab == item.Prefab)?.GetComponent();
+ var otherItem = deconstructor.InputContainer.Inventory.AllItems.FirstOrDefault(it => it != item);
+ if (otherItem == null)
+ {
+ return;
+ }
+
+ var otherGeneticMaterial = otherItem.GetComponent();
if (otherGeneticMaterial == null)
{
- buttonText = TextManager.Get("researchstation.combine");
- infoText = TextManager.Get("researchstation.combine.infotext");
+ return;
}
- else
+
+ var combineRefineResult = GetCombineRefineResult(otherGeneticMaterial);
+
+ if (combineRefineResult == CombineResult.None)
+ {
+ infoText = TextManager.Get("researchstation.novalidcombination");
+ }
+ else if (combineRefineResult == CombineResult.Refined)
{
buttonText = TextManager.Get("researchstation.refine");
int taintedProbability = (int)(GetTaintedProbabilityOnRefine(otherGeneticMaterial, Character.Controlled) * 100);
infoText = TextManager.GetWithVariable("researchstation.refine.infotext", "[taintedprobability]", taintedProbability.ToString());
}
+ else
+ {
+ buttonText = TextManager.Get("researchstation.combine");
+ infoText = TextManager.Get("researchstation.combine.infotext");
+ }
}
}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Holdable/Holdable.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Holdable/Holdable.cs
index b24344c84..0d532b2c2 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Holdable/Holdable.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Holdable/Holdable.cs
@@ -71,13 +71,13 @@ namespace Barotrauma.Items.Components
}
public override bool ValidateEventData(NetEntityEvent.IData data)
- => TryExtractEventData(data, out _);
+ => TryExtractEventData(data, out _);
public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null)
{
if (!attachable || body == null) { return; }
- var eventData = ExtractEventData(extraData);
+ var eventData = ExtractEventData(extraData);
Vector2 attachPos = eventData.AttachPos;
msg.WriteSingle(attachPos.X);
@@ -94,7 +94,9 @@ namespace Barotrauma.Items.Components
bool shouldBeAttached = msg.ReadBoolean();
Vector2 simPosition = new Vector2(msg.ReadSingle(), msg.ReadSingle());
UInt16 submarineID = msg.ReadUInt16();
+ UInt16 attacherID = msg.ReadUInt16();
Submarine sub = Entity.FindEntityByID(submarineID) as Submarine;
+ Character attacher = Entity.FindEntityByID(attacherID) as Character;
if (shouldBeAttached)
{
@@ -104,6 +106,8 @@ namespace Barotrauma.Items.Components
item.SetTransform(simPosition, 0.0f);
item.Submarine = sub;
AttachToWall();
+ PlaySound(ActionType.OnUse, attacher);
+ ApplyStatusEffects(ActionType.OnUse, (float)Timing.Step, character: attacher, user: attacher);
}
}
else
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Holdable/RangedWeapon.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Holdable/RangedWeapon.cs
index 352ba2c30..7ceb067e3 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Holdable/RangedWeapon.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Holdable/RangedWeapon.cs
@@ -34,6 +34,8 @@ namespace Barotrauma.Items.Components
}
private Vector2 _chargeSoundWindupPitchSlide;
+ public Vector2 BarrelScreenPos => Screen.Selected.Cam.WorldToScreen(item.DrawPosition + ConvertUnits.ToDisplayUnits(TransformedBarrelPos));
+
private readonly List particleEmitters = new List();
private readonly List particleEmitterCharges = new List();
@@ -86,28 +88,19 @@ namespace Barotrauma.Items.Components
currentCrossHairScale = currentCrossHairPointerScale = cam == null ? 1.0f : cam.Zoom;
if (crosshairSprite != null)
{
- Vector2 aimRefWorldPos = character.AimRefPosition;
- if (character.Submarine != null) { aimRefWorldPos += character.Submarine.Position; }
- Vector2 itemPos = cam.WorldToScreen(aimRefWorldPos);
- float rotation = (item.body.Dir == 1.0f) ? item.body.Rotation : item.body.Rotation - MathHelper.Pi;
- Vector2 barrelDir = new Vector2((float)Math.Cos(rotation), -(float)Math.Sin(rotation));
-
- Vector2 mouseDiff = itemPos - PlayerInput.MousePosition;
- crosshairPos = new Vector2(
- MathHelper.Clamp(itemPos.X + barrelDir.X * mouseDiff.Length(), 0, GameMain.GraphicsWidth),
- MathHelper.Clamp(itemPos.Y + barrelDir.Y * mouseDiff.Length(), 0, GameMain.GraphicsHeight));
+ // Set position based on in-world aim
+ Vector2 barrelDir = (MathF.Cos(item.body.TransformedRotation), -MathF.Sin(item.body.TransformedRotation));
+ float mouseDist = Vector2.Distance(BarrelScreenPos, PlayerInput.MousePosition);
+ crosshairPos = Vector2.Clamp(BarrelScreenPos + barrelDir * mouseDist, Vector2.Zero, (GameMain.GraphicsWidth, GameMain.GraphicsHeight));
+ // Resize pointer based on current spread
float spread = GetSpread(character);
- Projectile projectile = FindProjectile();
- if (projectile != null)
- {
- spread += MathHelper.ToRadians(projectile.Spread);
+ if (FindProjectile() is Projectile projectile)
+ {
+ spread += MathHelper.ToRadians(projectile.Spread);
}
-
- float crossHairDist = Vector2.Distance(item.WorldPosition, cam.ScreenToWorld(crosshairPos));
- float spreadDist = (float)Math.Sin(spread) * crossHairDist;
-
- currentCrossHairPointerScale = MathHelper.Clamp(spreadDist / Math.Min(crosshairSprite.size.X, crosshairSprite.size.Y), 0.1f, 10.0f);
+ float spreadAtRange = MathF.Sin(spread) * Vector2.Distance(BarrelScreenPos, crosshairPos);
+ currentCrossHairPointerScale = MathHelper.Clamp(spreadAtRange / Math.Min(crosshairSprite.size.X, crosshairSprite.size.Y), 0.1f, 10f);
}
currentCrossHairScale *= CrossHairScale;
crosshairPointerPos = PlayerInput.MousePosition;
@@ -141,7 +134,7 @@ namespace Barotrauma.Items.Components
{
if (chargeSound != null)
{
- chargeSoundChannel = SoundPlayer.PlaySound(chargeSound.Sound, item.WorldPosition, chargeSound.Volume, chargeSound.Range, ignoreMuffling: chargeSound.IgnoreMuffling, freqMult: chargeSound.GetRandomFrequencyMultiplier());
+ chargeSoundChannel = SoundPlayer.PlaySound(chargeSound, item.WorldPosition, hullGuess: item.CurrentHull);
if (chargeSoundChannel != null) { chargeSoundChannel.Looping = true; }
}
}
@@ -177,6 +170,8 @@ namespace Barotrauma.Items.Components
//don't draw the crosshair if the item is in some other type of equip slot than hands (e.g. assault rifle in the bag slot)
if (!character.HeldItems.Contains(item)) { return; }
+ base.DrawHUD(spriteBatch, character);
+
GUI.HideCursor = (crosshairSprite != null || crosshairPointerSprite != null) &&
GUI.MouseOn == null && !Inventory.IsMouseOnInventory && !GameMain.Instance.Paused;
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Holdable/Sprayer.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Holdable/Sprayer.cs
index 35f3edba3..cb72f3474 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Holdable/Sprayer.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Holdable/Sprayer.cs
@@ -216,6 +216,7 @@ namespace Barotrauma.Items.Components
public override void DrawHUD(SpriteBatch spriteBatch, Character character)
{
if (character == null || !character.IsKeyDown(InputType.Aim)) { return; }
+ base.DrawHUD(spriteBatch, character);
GUI.HideCursor = targetSections.Count > 0;
}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemComponent.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemComponent.cs
index e9f8847e5..aa5e3883a 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemComponent.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemComponent.cs
@@ -62,6 +62,10 @@ namespace Barotrauma.Items.Components
private readonly Dictionary> sounds;
private Dictionary soundSelectionModes;
+ ///
+ /// Starts the timer for delayed client-side corrections () - in other words,
+ /// the client will not attempt to read server updates for this component until the timer elapses.
+ ///
protected float correctionTimer;
public float IsActiveTimer;
@@ -150,6 +154,17 @@ namespace Barotrauma.Items.Components
public GUIFrame GuiFrame { get; set; }
+ ///
+ /// Overlay (just a non-interactable sprite) drawn when the item is selected, equipped or focused to via Controllers (e.g. when operating a turret via a periscope or a camera via a monitor).
+ ///
+ public Sprite HUDOverlay { get; set; }
+
+ public float HUDOverlayAnimSpeed
+ {
+ get;
+ set;
+ }
+
private GUIDragHandle guiFrameDragHandle;
private bool guiFrameUpdatePending;
@@ -161,6 +176,13 @@ namespace Barotrauma.Items.Components
set;
}
+ [Serialize(true, IsPropertySaveable.No)]
+ public bool CloseByClickingOutsideGUIFrame
+ {
+ get;
+ set;
+ }
+
private ItemComponent linkToUIComponent;
[Serialize("", IsPropertySaveable.No)]
public string LinkUIToComponent
@@ -261,7 +283,7 @@ namespace Barotrauma.Items.Components
if (GameMain.Client?.MidRoundSyncing ?? false) { return; }
//above the top boundary of the level (in an inactive respawn shuttle?)
- if (item.Submarine != null && Level.Loaded != null && item.Submarine.WorldPosition.Y > Level.Loaded.Size.Y)
+ if (item.Submarine != null && item.Submarine.IsAboveLevel)
{
return;
}
@@ -294,11 +316,13 @@ namespace Barotrauma.Items.Components
0.01f,
loopingSound.RoundSound.GetRandomFrequencyMultiplier(),
SoundPlayer.ShouldMuffleSound(Character.Controlled, item.WorldPosition, loopingSound.Range, Character.Controlled?.CurrentHull));
- loopingSoundChannel.Looping = true;
- item.CheckNeedsSoundUpdate(this);
- //TODO: tweak
- loopingSoundChannel.Near = loopingSound.Range * 0.4f;
- loopingSoundChannel.Far = loopingSound.Range;
+ if (loopingSoundChannel != null)
+ {
+ loopingSoundChannel.Looping = true;
+ item.CheckNeedsSoundUpdate(this);
+ loopingSoundChannel.Near = loopingSound.Range * 0.4f;
+ loopingSoundChannel.Far = loopingSound.Range;
+ }
}
// Looping sound with manual selection mode should be changed if value of ManuallySelectedSound has changed
@@ -340,7 +364,7 @@ namespace Barotrauma.Items.Components
}
else if (soundSelectionMode == SoundSelectionMode.Manual)
{
- index = Math.Clamp(ManuallySelectedSound, 0, matchingSounds.Count);
+ index = Math.Clamp(ManuallySelectedSound, 0, matchingSounds.Count - 1);
}
else
{
@@ -374,22 +398,20 @@ namespace Barotrauma.Items.Components
float volume = GetSoundVolume(itemSound);
if (volume <= 0.0001f) { return; }
loopingSound = itemSound;
- loopingSoundChannel = loopingSound.RoundSound.Sound.Play(
- new Vector3(position.X, position.Y, 0.0f),
- 0.01f,
- freqMult: itemSound.RoundSound.GetRandomFrequencyMultiplier(),
- muffle: SoundPlayer.ShouldMuffleSound(Character.Controlled, position, loopingSound.Range, Character.Controlled?.CurrentHull));
- loopingSoundChannel.Looping = true;
- //TODO: tweak
- loopingSoundChannel.Near = loopingSound.Range * 0.4f;
- loopingSoundChannel.Far = loopingSound.Range;
+ loopingSoundChannel = SoundPlayer.PlaySound(loopingSound.RoundSound, position, volume: 0.01f, hullGuess: item.CurrentHull);
+ if (loopingSoundChannel != null)
+ {
+ loopingSoundChannel.Looping = true;
+ loopingSoundChannel.Near = loopingSound.Range * 0.4f;
+ loopingSoundChannel.Far = loopingSound.Range;
+ }
}
}
else
{
float volume = GetSoundVolume(itemSound);
if (volume <= 0.0001f) { return; }
- var channel = SoundPlayer.PlaySound(itemSound.RoundSound.Sound, position, volume, itemSound.Range, itemSound.RoundSound.GetRandomFrequencyMultiplier(), item.CurrentHull, ignoreMuffling: itemSound.RoundSound.IgnoreMuffling);
+ var channel = SoundPlayer.PlaySound(itemSound.RoundSound, position, volume, hullGuess: item.CurrentHull);
if (channel != null) { playingOneshotSoundChannels.Add(channel); }
}
}
@@ -416,12 +438,23 @@ namespace Barotrauma.Items.Components
if (sound == null) { return 0.0f; }
if (sound.VolumeProperty == "") { return sound.VolumeMultiplier; }
- if (SerializableProperties.TryGetValue(sound.VolumeProperty, out SerializableProperty property))
+ SerializableProperty property = null;
+ ISerializableEntity targetEntity = null;
+ if (SerializableProperties.TryGetValue(sound.VolumeProperty, out property))
+ {
+ targetEntity = this;
+ }
+ else if (Item.SerializableProperties.TryGetValue(sound.VolumeProperty, out property))
+ {
+ targetEntity = Item;
+ }
+
+ if (property != null)
{
float newVolume;
try
{
- newVolume = property.GetFloatValue(this);
+ newVolume = property.GetFloatValue(targetEntity);
}
catch
{
@@ -470,7 +503,24 @@ namespace Barotrauma.Items.Components
return linkToUIComponent;
}
- public virtual void DrawHUD(SpriteBatch spriteBatch, Character character) { }
+ public virtual void DrawHUD(SpriteBatch spriteBatch, Character character)
+ {
+ if (HUDOverlay != null)
+ {
+ Vector2 screenSize = new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
+ if (HUDOverlay is SpriteSheet spriteSheet)
+ {
+ spriteSheet.Draw(spriteBatch,
+ spriteIndex: (int)(Math.Floor(Timing.TotalTimeUnpaused * HUDOverlayAnimSpeed) % spriteSheet.FrameCount),
+ pos: screenSize / 2, color: Color.White, origin: HUDOverlay.Origin, rotate: 0, scale: screenSize / spriteSheet.FrameSize.ToVector2());
+ }
+ else
+ {
+ HUDOverlay.Draw(spriteBatch,
+ pos: screenSize / 2, color: Color.White, origin: HUDOverlay.Origin, rotate: 0, scale: screenSize / HUDOverlay.size);
+ }
+ }
+ }
public virtual void AddToGUIUpdateList(int order = 0)
{
@@ -513,6 +563,13 @@ namespace Barotrauma.Items.Components
GuiFrameSource = subElement;
ReloadGuiFrame();
break;
+ case "hudoverlayanimated":
+ HUDOverlay = new SpriteSheet(subElement);
+ HUDOverlayAnimSpeed = subElement.GetAttributeFloat("animspeed", 1.0f);
+ break;
+ case "hudoverlay":
+ HUDOverlay = new Sprite(subElement);
+ break;
case "alternativelayout":
AlternativeLayout = GUILayoutSettings.Load(subElement);
break;
@@ -687,6 +744,9 @@ namespace Barotrauma.Items.Components
}),
new ContextMenuOption(TextManager.Get(LockGuiFramePosition ? "item.unlockuiposition" : "item.lockuiposition"), isEnabled: true, onSelected: () =>
{
+ //ensure the offset is set to where the frame is now
+ //(it may have been repositioned by the overlap prevention logic, which doesn't set this offset)
+ GuiFrameOffset = GuiFrame.RectTransform.ScreenSpaceOffset;
LockGuiFramePosition = !LockGuiFramePosition;
guiFrameDragHandle.Enabled = !LockGuiFramePosition;
if (SerializableProperties.TryGetValue(nameof(LockGuiFramePosition).ToIdentifier(), out var property))
@@ -711,7 +771,11 @@ namespace Barotrauma.Items.Components
///
protected virtual void CreateGUI() { }
- //Starts a coroutine that will read the correct state of the component from the NetBuffer when correctionTimer reaches zero.
+ ///
+ /// Starts a coroutine that will read the correct state of the component from the NetBuffer when correctionTimer reaches zero.
+ /// Useful in cases where we a client is constantly adjusting some value, and we don't want state updates from the server to interfere with it
+ /// (e.g. setting the value back to what a client just set it to, when the client has already modified the value further).
+ ///
protected void StartDelayedCorrection(IReadMessage buffer, float sendingTime, bool waitForMidRoundSync = false)
{
if (delayedCorrectionCoroutine != null) { CoroutineManager.StopCoroutines(delayedCorrectionCoroutine); }
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemContainer.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemContainer.cs
index e1658c4ef..353bb290b 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemContainer.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemContainer.cs
@@ -458,54 +458,14 @@ namespace Barotrauma.Items.Components
public void DrawContainedItems(SpriteBatch spriteBatch, float itemDepth, Color? overrideColor = null)
{
- Vector2 transformedItemPos = ItemPos * item.Scale;
- Vector2 transformedItemInterval = ItemInterval * item.Scale;
- Vector2 transformedItemIntervalHorizontal = new Vector2(transformedItemInterval.X, 0.0f);
- Vector2 transformedItemIntervalVertical = new Vector2(0.0f, transformedItemInterval.Y);
+ var rootBody = item.RootContainer?.body ?? item.body;
- if (item.body == null)
- {
- if (item.FlippedX)
- {
- transformedItemPos.X = -transformedItemPos.X;
- transformedItemPos.X += item.Rect.Width;
- transformedItemInterval.X = -transformedItemInterval.X;
- transformedItemIntervalHorizontal.X = -transformedItemIntervalHorizontal.X;
- }
- if (item.FlippedY)
- {
- transformedItemPos.Y = -transformedItemPos.Y;
- transformedItemPos.Y -= item.Rect.Height;
- transformedItemInterval.Y = -transformedItemInterval.Y;
- transformedItemIntervalVertical.Y = -transformedItemIntervalVertical.Y;
- }
- transformedItemPos += new Vector2(item.Rect.X, item.Rect.Y);
- if (item.Submarine != null) { transformedItemPos += item.Submarine.DrawPosition; }
-
- if (Math.Abs(item.RotationRad) > 0.01f)
- {
- Matrix transform = Matrix.CreateRotationZ(-item.RotationRad);
- transformedItemPos = Vector2.Transform(transformedItemPos - item.DrawPosition, transform) + item.DrawPosition;
- transformedItemInterval = Vector2.Transform(transformedItemInterval, transform);
- transformedItemIntervalHorizontal = Vector2.Transform(transformedItemIntervalHorizontal, transform);
- transformedItemIntervalVertical = Vector2.Transform(transformedItemIntervalVertical, transform);
- }
- }
- else
- {
- Matrix transform = Matrix.CreateRotationZ(item.body.DrawRotation);
- if (item.body.Dir == -1.0f)
- {
- transformedItemPos.X = -transformedItemPos.X;
- transformedItemInterval.X = -transformedItemInterval.X;
- transformedItemIntervalHorizontal.X = -transformedItemIntervalHorizontal.X;
- }
-
- transformedItemPos = Vector2.Transform(transformedItemPos, transform);
- transformedItemInterval = Vector2.Transform(transformedItemInterval, transform);
- transformedItemIntervalHorizontal = Vector2.Transform(transformedItemIntervalHorizontal, transform);
- transformedItemPos += item.body.DrawPosition;
- }
+ Vector2 transformedItemPos = GetContainedPosition(
+ drawPosition: true,
+ out Vector2 transformedItemIntervalHorizontal,
+ out Vector2 transformedItemIntervalVertical,
+ out bool flippedX,
+ out bool flippedY);
Vector2 currentItemPos = transformedItemPos;
@@ -514,30 +474,41 @@ namespace Barotrauma.Items.Components
int i = 0;
foreach (ContainedItem contained in containedItems)
{
- Vector2 itemPos = currentItemPos;
-
if (contained.Item?.Sprite == null) { continue; }
-
if (contained.Hide) { continue; }
+
+ Vector2 itemPos = transformedItemPos;
+ int targetSlotIndex = ItemsUseInventoryPlacement ? Inventory.FindIndex(contained.Item) : i;
+ //interval set on both axes -> use a grid layout
+ if (Math.Abs(ItemInterval.X) > 0.001f && Math.Abs(ItemInterval.Y) > 0.001f)
+ {
+ itemPos += transformedItemIntervalHorizontal * (targetSlotIndex % ItemsPerRow);
+ itemPos += transformedItemIntervalVertical * (targetSlotIndex / ItemsPerRow);
+ }
+ else
+ {
+ itemPos += (transformedItemIntervalHorizontal + transformedItemIntervalVertical) * targetSlotIndex;
+ }
+
if (contained.ItemPos.HasValue)
{
Vector2 pos = contained.ItemPos.Value;
if (item.body != null)
{
Matrix transform = Matrix.CreateRotationZ(item.body.DrawRotation);
- pos.X *= item.body.Dir;
+ pos.X *= rootBody.Dir;
itemPos = Vector2.Transform(pos, transform) + item.body.DrawPosition;
}
else
{
itemPos = pos;
// This code is aped based on above. Not tested.
- if (item.FlippedX)
+ if (flippedX)
{
itemPos.X = -itemPos.X;
itemPos.X += item.Rect.Width;
}
- if (item.FlippedY)
+ if (flippedY)
{
itemPos.Y = -itemPos.Y;
itemPos.Y -= item.Rect.Height;
@@ -555,15 +526,15 @@ namespace Barotrauma.Items.Components
}
}
- if (AutoInteractWithContained)
+ if (CanAutoInteractWithContained(contained.Item) && Screen.Selected is not { IsEditor: true })
{
contained.Item.IsHighlighted = item.IsHighlighted;
item.IsHighlighted = false;
}
Vector2 origin = contained.Item.Sprite.Origin;
- if (item.FlippedX) { origin.X = contained.Item.Sprite.SourceRect.Width - origin.X; }
- if (item.FlippedY) { origin.Y = contained.Item.Sprite.SourceRect.Height - origin.Y; }
+ if (flippedX) { origin.X = contained.Item.Sprite.SourceRect.Width - origin.X; }
+ if (flippedY) { origin.Y = contained.Item.Sprite.SourceRect.Height - origin.Y; }
float containedSpriteDepth = ContainedSpriteDepth < 0.0f ? contained.Item.Sprite.Depth : ContainedSpriteDepth;
if (i < containedSpriteDepths.Length)
@@ -571,19 +542,20 @@ namespace Barotrauma.Items.Components
containedSpriteDepth = containedSpriteDepths[i];
}
containedSpriteDepth = itemDepth + (containedSpriteDepth - (item.Sprite?.Depth ?? item.SpriteDepth)) / 10000.0f;
-
+
SpriteEffects spriteEffects = SpriteEffects.None;
float spriteRotation = ItemRotation;
if (contained.Rotation != 0)
{
spriteRotation = contained.Rotation;
}
- bool flipX = (item.body != null && item.body.Dir == -1) || item.FlippedX;
+
+ bool flipX = rootBody is { Dir: -1 } || flippedX;
if (flipX)
{
spriteEffects |= MathUtils.NearlyEqual(spriteRotation % 180, 90.0f) ? SpriteEffects.FlipVertically : SpriteEffects.FlipHorizontally;
}
- bool flipY = item.FlippedY;
+ bool flipY = flippedY;
if (flipY)
{
spriteEffects |= MathUtils.NearlyEqual(spriteRotation % 180, 90.0f) ? SpriteEffects.FlipHorizontally : SpriteEffects.FlipVertically;
@@ -598,7 +570,8 @@ namespace Barotrauma.Items.Components
contained.Item.Scale,
spriteEffects,
depth: containedSpriteDepth);
- contained.Item.DrawDecorativeSprites(spriteBatch, itemPos, flipX,flipY, (contained.Item.body == null ? 0.0f : contained.Item.body.DrawRotation),
+
+ contained.Item.DrawDecorativeSprites(spriteBatch, itemPos, flipX, flipY, (contained.Item.body == null ? 0.0f : contained.Item.body.DrawRotation),
containedSpriteDepth, overrideColor);
foreach (ItemContainer ic in contained.Item.GetComponents())
@@ -608,20 +581,6 @@ namespace Barotrauma.Items.Components
}
i++;
- if (Math.Abs(ItemInterval.X) > 0.001f && Math.Abs(ItemInterval.Y) > 0.001f)
- {
- //interval set on both axes -> use a grid layout
- currentItemPos += transformedItemIntervalHorizontal;
- if (i % ItemsPerRow == 0)
- {
- currentItemPos = transformedItemPos;
- currentItemPos += transformedItemIntervalVertical * (i / ItemsPerRow);
- }
- }
- else
- {
- currentItemPos += transformedItemInterval;
- }
}
}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemLabel.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemLabel.cs
index 61cf70993..f73d34554 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemLabel.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemLabel.cs
@@ -41,7 +41,7 @@ namespace Barotrauma.Items.Components
}
private string text;
- [Serialize("", IsPropertySaveable.Yes, translationTextTag: "Label.", description: "The text displayed in the label.", alwaysUseInstanceValues: true), Editable(100)]
+ [Serialize("", IsPropertySaveable.Yes, translationTextTag: "Label.", description: "The text displayed in the label.", alwaysUseInstanceValues: true), Editable(MaxLength = 100)]
public string Text
{
get { return text; }
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/LightComponent.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/LightComponent.cs
index 8e88d4189..4e637d1dc 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/LightComponent.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/LightComponent.cs
@@ -35,6 +35,7 @@ namespace Barotrauma.Items.Components
{
Light.SpriteScale = Vector2.One * item.Scale;
Light.Position = ParentBody != null ? ParentBody.Position : item.Position;
+ SetLightSourceTransformProjSpecific();
}
partial void SetLightSourceState(bool enabled, float brightness)
@@ -51,27 +52,42 @@ namespace Barotrauma.Items.Components
partial void SetLightSourceTransformProjSpecific()
{
+ Vector2 offset = Vector2.Zero;
+ if (LightOffset != Vector2.Zero)
+ {
+ offset = Vector2.Transform(LightOffset, Matrix.CreateRotationZ(item.FlippedY ? -item.RotationRad - MathHelper.Pi : -item.RotationRad)) * item.Scale;
+ }
+
if (ParentBody != null)
{
Light.ParentBody = ParentBody;
+ Light.OffsetFromBody = offset;
}
else if (turret != null)
{
- Light.Position = new Vector2(item.Rect.X + turret.TransformedBarrelPos.X, item.Rect.Y - turret.TransformedBarrelPos.Y);
+ Light.Position = new Vector2(item.Rect.X + turret.TransformedBarrelPos.X, item.Rect.Y - turret.TransformedBarrelPos.Y) + offset;
}
else if (item.body != null)
{
Light.ParentBody = item.body;
+ Light.OffsetFromBody = offset;
}
else
{
- Light.Position = item.Position;
+ Light.Position = item.Position + offset;
}
PhysicsBody body = Light.ParentBody;
- if (body != null && body.Enabled)
+ if (body != null)
{
Light.Rotation = body.Dir > 0.0f ? body.DrawRotation : body.DrawRotation - MathHelper.Pi;
- Light.LightSpriteEffect = (body.Dir > 0.0f) ? SpriteEffects.None : SpriteEffects.FlipVertically;
+ if (body.Enabled)
+ {
+ Light.LightSpriteEffect = (body.Dir > 0.0f) ? SpriteEffects.None : SpriteEffects.FlipVertically;
+ }
+ else
+ {
+ Light.LightSpriteEffect = item.SpriteEffects;
+ }
}
else
{
@@ -85,11 +101,13 @@ namespace Barotrauma.Items.Components
if (Light?.LightSprite == null) { return; }
if ((item.body == null || item.body.Enabled) && lightBrightness > 0.0f && IsOn && Light.Enabled)
{
+ Vector2 offset = Vector2.Transform(LightOffset, Matrix.CreateRotationZ(item.FlippedY ? -item.RotationRad - MathHelper.Pi : -item.RotationRad)) * item.Scale;
+
Vector2 origin = Light.LightSprite.Origin;
if ((Light.LightSpriteEffect & SpriteEffects.FlipHorizontally) == SpriteEffects.FlipHorizontally) { origin.X = Light.LightSprite.SourceRect.Width - origin.X; }
if ((Light.LightSpriteEffect & SpriteEffects.FlipVertically) == SpriteEffects.FlipVertically) { origin.Y = Light.LightSprite.SourceRect.Height - origin.Y; }
- Vector2 drawPos = item.body?.DrawPosition ?? item.DrawPosition;
+ Vector2 drawPos = item.body?.DrawPosition ?? item.DrawPosition + offset;
Color color = lightColor;
if (Light.OverrideLightSpriteAlpha.HasValue)
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Controller.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Controller.cs
index df20deb01..710dcb9f6 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Controller.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Controller.cs
@@ -6,11 +6,11 @@ namespace Barotrauma.Items.Components
{
partial class Controller : ItemComponent
{
- private bool chatBoxOriginalState;
private bool isHUDsHidden;
public override void DrawHUD(SpriteBatch spriteBatch, Character character)
{
+ base.DrawHUD(spriteBatch, character);
if (focusTarget != null && character.ViewTarget == focusTarget)
{
foreach (ItemComponent ic in focusTarget.Components)
@@ -23,48 +23,31 @@ namespace Barotrauma.Items.Components
}
}
+ public override void AddToGUIUpdateList(int order = 0)
+ {
+ base.AddToGUIUpdateList(order);
+ if (focusTarget != null && Character.Controlled.ViewTarget == focusTarget)
+ {
+ focusTarget.AddToGUIUpdateList(order);
+ }
+ }
+
partial void HideHUDs(bool value)
{
if (isHUDsHidden == value) { return; }
- if (value == true)
+ if (value)
{
GameMain.GameSession?.CrewManager?.AutoHideCrewList();
- ToggleChatBox(false, storeOriginalState: true);
+ ChatBox.AutoHideChatBox();
}
else
{
- GameMain.GameSession?.CrewManager?.ResetCrewList();
- ToggleChatBox(chatBoxOriginalState, storeOriginalState: false);
+ GameMain.GameSession?.CrewManager?.ResetCrewListOpenState();
+ ChatBox.ResetChatBoxOpenState();
}
isHUDsHidden = value;
}
- private void ToggleChatBox(bool value, bool storeOriginalState)
- {
- var crewManager = GameMain.GameSession?.CrewManager;
- if (crewManager == null) { return; }
-
- if (crewManager.IsSinglePlayer)
- {
- if (crewManager.ChatBox != null)
- {
- if (storeOriginalState)
- {
- chatBoxOriginalState = crewManager.ChatBox.ToggleOpen;
- }
- crewManager.ChatBox.ToggleOpen = value;
- }
- }
- else if (GameMain.Client != null)
- {
- if (storeOriginalState)
- {
- chatBoxOriginalState = GameMain.Client.ChatBox.ToggleOpen;
- }
- GameMain.Client.ChatBox.ToggleOpen = value;
- }
- }
-
#if DEBUG
public override void CreateEditingHUD(SerializableEntityEditor editor)
{
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Deconstructor.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Deconstructor.cs
index 0fbbf4c00..24b0ec94b 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Deconstructor.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Deconstructor.cs
@@ -154,12 +154,14 @@ namespace Barotrauma.Items.Components
{
infoArea.Text = TextManager.Get(InfoText).Fallback(InfoText);
}
+
if (IsActive)
{
activateButton.Text = TextManager.Get("DeconstructorCancel");
infoArea.Text = string.Empty;
return;
}
+
bool outputsFound = false;
foreach (var (inputItem, deconstructItem) in GetAvailableOutputs(checkRequiredOtherItems: true))
{
@@ -174,27 +176,34 @@ namespace Barotrauma.Items.Components
}
inputItem.GetComponent()?.ModifyDeconstructInfo(this, ref buttonText, ref infoText);
activateButton.Text = buttonText;
- if (infoArea != null)
- {
- infoArea.Text = infoText;
- }
+ infoArea.Text = infoText;
+
return;
}
}
+
+ LocalizedString activateButtonText = TextManager.Get(ActivateButtonText);
+ activateButton.Enabled = outputsFound || !InputContainer.Inventory.IsEmpty();
+ activateButton.Text = activateButtonText;
+
//no valid outputs found: check if we're missing some required items from the input slots and display a message about it if possible
if (!outputsFound && infoArea != null)
{
foreach (var (inputItem, deconstructItem) in GetAvailableOutputs(checkRequiredOtherItems: false))
{
+ LocalizedString infoText = string.Empty;
if (deconstructItem.RequiredOtherItem.Any() && !string.IsNullOrEmpty(deconstructItem.InfoTextOnOtherItemMissing))
{
LocalizedString missingItemName = TextManager.Get("entityname." + deconstructItem.RequiredOtherItem.First());
- infoArea.Text = TextManager.GetWithVariable(deconstructItem.InfoTextOnOtherItemMissing, "[itemname]", missingItemName);
+ infoText = TextManager.GetWithVariable(deconstructItem.InfoTextOnOtherItemMissing, "[itemname]", missingItemName);
}
+
+ inputItem.GetComponent()?.ModifyDeconstructInfo(this, ref activateButtonText, ref infoText);
+
+ activateButton.Text = activateButtonText;
+ infoArea.Text = infoText;
}
}
- activateButton.Enabled = outputsFound || !InputContainer.Inventory.IsEmpty();
- activateButton.Text = TextManager.Get(ActivateButtonText);
};
}
@@ -415,7 +424,7 @@ namespace Barotrauma.Items.Components
public override void UpdateHUDComponentSpecific(Character character, float deltaTime, Camera cam)
{
- inSufficientPowerWarning.Visible = IsActive && !hasPower;
+ inSufficientPowerWarning.Visible = IsActive && !HasPower;
}
private bool OnActivateButtonClicked(GUIButton button, object obj)
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs
index 0596825f6..f6dcd08c4 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs
@@ -11,11 +11,21 @@ namespace Barotrauma.Items.Components
{
partial class Fabricator : Powered, IServerSerializable, IClientSerializable
{
+ private enum SortBy
+ {
+ Category,
+ Alphabetical,
+ SkillRequirement,
+ Price
+ }
+
private GUIListBox itemList;
private GUIFrame selectedItemFrame;
private GUIFrame selectedItemReqsFrame;
+ private GUILayoutGroup outputTopArea, paddedOutputArea;
+
private GUITextBlock amountTextMax;
private GUIScrollBar amountInput;
@@ -26,6 +36,8 @@ namespace Barotrauma.Items.Components
private GUIButton activateButton;
private GUITextBox itemFilterBox;
+ private GUITickBox availableOnlyTickBox;
+ private GUIDropDown sortByDropdown;
private GUIComponent outputSlot;
private GUIComponent inputInventoryHolder, outputInventoryHolder;
@@ -33,6 +45,9 @@ namespace Barotrauma.Items.Components
private readonly List itemCategoryButtons = new List();
private MapEntityCategory? selectedItemCategory;
+ private GUITextBlock requiresRecipeText;
+ private GUITextBlock nothingToShowText;
+
public FabricationRecipe SelectedItem
{
get { return selectedItem; }
@@ -65,6 +80,12 @@ namespace Barotrauma.Items.Components
[Serialize("vendingmachine.outofstock", IsPropertySaveable.Yes)]
public string FabricationLimitReachedText { get; set; }
+ [Serialize(true, IsPropertySaveable.No)]
+ public bool ShowSortByDropdown { get; set; }
+
+ [Serialize(true, IsPropertySaveable.No)]
+ public bool ShowAvailableOnlyTickBox { get; set; }
+
public override bool RecreateGUIOnResolutionChange => true;
protected override void OnResolutionChanged()
@@ -154,24 +175,24 @@ namespace Barotrauma.Items.Components
};
// === TOP AREA ===
- var topFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.65f), mainFrame.RectTransform), style: "InnerFrameDark");
+ var topFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.8f), mainFrame.RectTransform), style: "InnerFrameDark");
// === ITEM LIST ===
var itemListFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 1.0f), topFrame.RectTransform), childAnchor: Anchor.Center);
- var paddedItemFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.9f), itemListFrame.RectTransform))
+ var paddedItemFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.95f), itemListFrame.RectTransform), isHorizontal: false)
{
- Stretch = true,
- RelativeSpacing = 0.03f
+ Stretch = true
};
var filterArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.15f), paddedItemFrame.RectTransform), isHorizontal: true)
{
- Stretch = true,
- RelativeSpacing = 0.03f,
+ Stretch = true,
+ RelativeSpacing = 0.03f,
UserData = "filterarea"
};
- new GUITextBlock(new RectTransform(new Vector2(0.2f, 1f), filterArea.RectTransform), TextManager.Get("serverlog.filter"), font: GUIStyle.SubHeadingFont)
+ new GUITextBlock(new RectTransform(new Vector2(0.4f, 1f), filterArea.RectTransform), TextManager.Get("serverlog.filter"),
+ font: GUIStyle.SubHeadingFont, textAlignment: Alignment.CenterLeft)
{
- Padding = Vector4.Zero,
+ Padding = Vector4.Zero,
AutoScaleVertical = true
};
itemFilterBox = new GUITextBox(new RectTransform(new Vector2(0.8f, 1.0f), filterArea.RectTransform), createClearButton: true)
@@ -183,29 +204,91 @@ namespace Barotrauma.Items.Components
FilterEntities(selectedItemCategory, text);
return true;
};
+ filterArea.RectTransform.MinSize = new Point(0, itemFilterBox.Rect.Height);
filterArea.RectTransform.MaxSize = new Point(int.MaxValue, itemFilterBox.Rect.Height);
- itemList = new GUIListBox(new RectTransform(new Vector2(1f, 0.9f), paddedItemFrame.RectTransform), style: null)
+ var sortByArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.15f), paddedItemFrame.RectTransform), isHorizontal: true)
+ {
+ Stretch = true,
+ RelativeSpacing = 0.03f,
+ Visible = ShowSortByDropdown,
+ IgnoreLayoutGroups = !ShowSortByDropdown
+ };
+ new GUITextBlock(new RectTransform(new Vector2(0.4f, 1f), sortByArea.RectTransform), TextManager.Get("campaignstore.sortby"),
+ font: GUIStyle.SubHeadingFont, textAlignment: Alignment.CenterLeft)
+ {
+ Padding = Vector4.Zero,
+ AutoScaleVertical = true
+ };
+ sortByDropdown = new GUIDropDown(new RectTransform(new Vector2(0.8f, 1.0f), sortByArea.RectTransform));
+ foreach (SortBy sortBy in Enum.GetValues())
+ {
+ sortByDropdown.AddItem(TextManager.Get("fabricator.sortby." + sortBy), userData: sortBy);
+ }
+ sortByDropdown.Select(index: 0);
+ sortByDropdown.AfterSelected += (GUIComponent selected, object userdata) =>
+ {
+ FilterEntities(selectedItemCategory, itemFilterBox.Text);
+ SortItems(character: Character.Controlled);
+ return true;
+ };
+ sortByArea.RectTransform.MinSize = new Point(0, sortByDropdown.Rect.Height);
+ sortByArea.RectTransform.MaxSize = new Point(int.MaxValue, sortByDropdown.Rect.Height);
+
+ var availableOnlyTickBoxArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.15f), paddedItemFrame.RectTransform), isHorizontal: true)
+ {
+ Stretch = true,
+ Visible = ShowAvailableOnlyTickBox,
+ IgnoreLayoutGroups = !ShowAvailableOnlyTickBox
+ };
+ new GUITextBlock(new RectTransform(new Vector2(0.4f, 1f), availableOnlyTickBoxArea.RectTransform), TextManager.Get("fabricator.onlyshowavailable"),
+ font: GUIStyle.SubHeadingFont, textAlignment: Alignment.CenterLeft)
+ {
+ Padding = Vector4.Zero,
+ AutoScaleVertical = true
+ };
+ availableOnlyTickBox = new GUITickBox(new RectTransform(new Vector2(1.0f), availableOnlyTickBoxArea.RectTransform, scaleBasis: ScaleBasis.BothHeight), label: string.Empty)
+ {
+ ToolTip = TextManager.Get("fabricator.onlyshowavailable.tooltip")
+ };
+ availableOnlyTickBox.OnSelected += (tickbox) =>
+ {
+ FilterEntities(selectedItemCategory, itemFilterBox.Text);
+ return true;
+ };
+ availableOnlyTickBox.RectTransform.MinSize = new Point(availableOnlyTickBox.Rect.Height);
+ availableOnlyTickBox.RectTransform.IsFixedSize = true;
+ availableOnlyTickBoxArea.RectTransform.MinSize = new Point(0, availableOnlyTickBox.Rect.Height);
+ availableOnlyTickBoxArea.RectTransform.MaxSize = new Point(int.MaxValue, availableOnlyTickBox.Rect.Height);
+
+ itemList = new GUIListBox(new RectTransform(new Vector2(1f, 0.8f), paddedItemFrame.RectTransform), style: null)
{
PlaySoundOnSelect = true,
OnSelected = (component, userdata) =>
{
- selectedItem = userdata as FabricationRecipe;
- if (selectedItem != null) { SelectItem(Character.Controlled, selectedItem); }
- return true;
+ if (userdata is FabricationRecipe fabricationRecipe)
+ {
+ selectedItem = fabricationRecipe;
+ SelectItem(Character.Controlled, selectedItem);
+ return true;
+ }
+ else
+ {
+ return false;
+ }
}
};
- // === SEPARATOR === //
- new GUIFrame(new RectTransform(new Vector2(0.01f, 0.9f), topFrame.RectTransform, Anchor.Center), style: "VerticalLine");
+ // === SEPARATOR === //
+ new GUIFrame(new RectTransform(new Vector2(0.01f, 0.9f), topFrame.RectTransform, Anchor.Center), style: "VerticalLine");
// === OUTPUT AREA === //
var outputArea = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 1f), topFrame.RectTransform, Anchor.TopRight), childAnchor: Anchor.Center);
- var paddedOutputArea = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), outputArea.RectTransform));
- var outputTopArea = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.5F), paddedOutputArea.RectTransform, Anchor.Center), isHorizontal: true);
+ paddedOutputArea = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.95f), outputArea.RectTransform)) { Stretch = true };
+ outputTopArea = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.5f), paddedOutputArea.RectTransform, Anchor.Center), isHorizontal: true);
// === OUTPUT SLOT === //
- outputSlot = new GUIFrame(new RectTransform(new Vector2(0.4f, 1f), outputTopArea.RectTransform), style: null);
- outputInventoryHolder = new GUIFrame(new RectTransform(new Vector2(1f, 1.2f), outputSlot.RectTransform, Anchor.BottomCenter), style: null);
+ outputSlot = new GUIFrame(new RectTransform(new Vector2(0.4f, 0.4f), outputTopArea.RectTransform, scaleBasis: ScaleBasis.BothWidth), style: null);
+ outputInventoryHolder = new GUIFrame(new RectTransform(new Vector2(1f, 1.0f), outputSlot.RectTransform, Anchor.BottomCenter), style: null);
new GUICustomComponent(new RectTransform(Vector2.One, outputInventoryHolder.RectTransform), DrawOutputOverLay) { CanBeFocused = false };
// === DESCRIPTION === //
selectedItemFrame = new GUIFrame(new RectTransform(new Vector2(0.6f, 1f), outputTopArea.RectTransform), style: null);
@@ -213,7 +296,7 @@ namespace Barotrauma.Items.Components
selectedItemReqsFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.5f), paddedOutputArea.RectTransform), style: null);
// === BOTTOM AREA === //
- var bottomFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.3f), mainFrame.RectTransform), style: null);
+ var bottomFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.2f), mainFrame.RectTransform), style: null);
if (inputContainer.Capacity > 0)
{
@@ -298,6 +381,33 @@ namespace Barotrauma.Items.Components
CanBeFocused = false
};
CreateRecipes();
+
+ foreach (MapEntityCategory category in itemCategories)
+ {
+ new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.15f), itemList.Content.RectTransform),
+ TextManager.Get("MapEntityCategory." + category), textColor: GUIStyle.TextColorBright)
+ {
+ CanBeFocused = false,
+ UserData = category,
+ Visible = false
+ };
+ }
+
+ requiresRecipeText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.15f), itemList.Content.RectTransform),
+ TextManager.Get("fabricatorrequiresrecipe"), textColor: Color.Red, font: GUIStyle.SubHeadingFont)
+ {
+ AutoScaleHorizontal = true,
+ CanBeFocused = false
+ };
+
+ nothingToShowText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.8f), itemList.Content.RectTransform), TextManager.Get("noitemsheader"),
+ textAlignment: Alignment.Center, textColor: GUIStyle.TextColorDim)
+ {
+ CanBeFocused = false,
+ Visible = false
+ };
+
+ SortItems(character: Character.Controlled);
}
private void RefreshActivateButtonText()
@@ -343,7 +453,8 @@ namespace Barotrauma.Items.Components
};
}
- new GUITextBlock(new RectTransform(new Vector2(0.85f, 1f), container.RectTransform), GetRecipeNameAndAmount(fi))
+ new GUITextBlock(new RectTransform(new Vector2(0.85f, 1f), container.RectTransform),
+ RichString.Rich(GetRecipeNameAndAmount(fi)), font: GUIStyle.SmallFont)
{
Padding = Vector4.Zero,
AutoScaleVertical = true,
@@ -372,17 +483,17 @@ namespace Barotrauma.Items.Components
outputContainer.Inventory.RectTransform = outputInventoryHolder.RectTransform;
}
- private static LocalizedString GetRecipeNameAndAmount(FabricationRecipe fabricationRecipe)
+ private static RichString GetRecipeNameAndAmount(FabricationRecipe fabricationRecipe)
{
if (fabricationRecipe == null) { return ""; }
if (fabricationRecipe.Amount > 1)
{
return TextManager.GetWithVariables("fabricationrecipenamewithamount",
- ("[name]", fabricationRecipe.DisplayName), ("[amount]", fabricationRecipe.Amount.ToString()));
+ ("[name]", RichString.Rich(fabricationRecipe.DisplayName)), ("[amount]", fabricationRecipe.Amount.ToString()));
}
else
{
- return fabricationRecipe.DisplayName;
+ return RichString.Rich(fabricationRecipe.DisplayName);
}
}
@@ -397,73 +508,106 @@ namespace Barotrauma.Items.Components
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));
+ nonItems.ForEach(i => i.Visible = false);
+
+ SortItems(character: null);
+ FilterEntities(selectedItemCategory, itemFilterBox?.Text ?? string.Empty);
+ HideEmptyItemListCategories();
+ }
+
+ private void SortItems(Character character)
+ {
+ SortBy sortBy = (SortBy)sortByDropdown.SelectedData;
itemList.Content.RectTransform.SortChildren((c1, c2) =>
{
var item1 = c1.GUIComponent.UserData as FabricationRecipe;
var item2 = c2.GUIComponent.UserData as FabricationRecipe;
- int itemPlacement1 = calculatePlacement(item1);
- int itemPlacement2 = calculatePlacement(item2);
- if (itemPlacement1 != itemPlacement2)
+ if (item1 == null && item2 == null)
{
- return itemPlacement1 > itemPlacement2 ? -1 : 1;
+ return 0;
+ }
+ else if (item1 == null)
+ {
+ return -1;
+ }
+ else if (item2 == null)
+ {
+ return 1;
}
- int calculatePlacement(FabricationRecipe recipe)
+ bool missingRecipe1 = MissingRequiredRecipe(item1, character);
+ bool missingRecipe2 = MissingRequiredRecipe(item2, character);
+ if (missingRecipe1 != missingRecipe2)
{
- if (recipe.RequiresRecipe && !AnyOneHasRecipeForItem(character, recipe.TargetItem))
- {
- return -2;
- }
- int placement = FabricationDegreeOfSuccess(character, recipe.RequiredSkills) >= 0.5f ? 0 : -1;
- return placement;
+ return missingRecipe1.CompareTo(missingRecipe2);
}
- return string.Compare(item1.DisplayName.Value, item2.DisplayName.Value);
+ switch (sortBy)
+ {
+ case SortBy.Alphabetical:
+ return string.Compare(item1.DisplayName.Value, item2.DisplayName.Value);
+ case SortBy.Category:
+ var category1 = EnumExtensions.GetIndividualFlags(item1.TargetItem.Category).FirstOrDefault();
+ var category2 = EnumExtensions.GetIndividualFlags(item2.TargetItem.Category).FirstOrDefault();
+ if (category1 == category2)
+ {
+ return string.Compare(item1.DisplayName.Value, item2.DisplayName.Value);
+ }
+ return category1.CompareTo(category2);
+ case SortBy.SkillRequirement:
+ float skillRequirement1 = item1.RequiredSkills.Sum(skill => skill.Level);
+ float skillRequirement2 = item2.RequiredSkills.Sum(skill => skill.Level);
+ if (MathUtils.NearlyEqual(skillRequirement1, skillRequirement2))
+ {
+ return string.Compare(item1.DisplayName.Value, item2.DisplayName.Value);
+ }
+ return skillRequirement1.CompareTo(skillRequirement2);
+ case SortBy.Price:
+ float itemValue1 = item1.TargetItem.DefaultPrice?.Price ?? 0;
+ float itemValue2 = item2.TargetItem.DefaultPrice?.Price ?? 0;
+ if (MathUtils.NearlyEqual(itemValue1, itemValue2))
+ {
+ return string.Compare(item1.DisplayName.Value, item2.DisplayName.Value);
+ }
+ return itemValue2.CompareTo(itemValue1);
+ default:
+ throw new NotImplementedException($"Sorting by {sortBy} has not been implemented.");
+ }
});
- var sufficientSkillsText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.15f), itemList.Content.RectTransform),
- TextManager.Get("fabricatorsufficientskills"), textColor: GUIStyle.Green, font: GUIStyle.SubHeadingFont)
+ if (sortBy == SortBy.Category)
{
- AutoScaleHorizontal = true,
- CanBeFocused = false
- };
- sufficientSkillsText.RectTransform.SetAsFirstChild();
+ foreach (var categoryText in itemList.Content.Children.Where(c => c.UserData?.GetType() == typeof(MapEntityCategory)).ToList())
+ {
+ categoryText.RectTransform.SetAsLastChild();
+ var category = (MapEntityCategory)categoryText.UserData;
+ var firstChildWithMatchingCategory = itemList.Content.Children.FirstOrDefault(c => c.UserData is FabricationRecipe recipe && EnumExtensions.GetIndividualFlags(recipe.TargetItem.Category).FirstOrDefault() == category);
+ if (firstChildWithMatchingCategory != null)
+ {
+ categoryText.RectTransform.RepositionChildInHierarchy(itemList.Content.GetChildIndex(firstChildWithMatchingCategory));
+ categoryText.Visible = true;
+ }
+ else
+ {
+ categoryText.Visible = false;
+ }
+ }
+ }
- var insufficientSkillsText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.15f), itemList.Content.RectTransform),
- TextManager.Get("fabricatorinsufficientskills"), textColor: Color.Orange, font: GUIStyle.SubHeadingFont)
+ requiresRecipeText.RectTransform.SetAsLastChild();
+ var firstMissingRecipe = itemList.Content.Children.FirstOrDefault(c => c.UserData is FabricationRecipe recipe && MissingRequiredRecipe(recipe, character));
+ if (firstMissingRecipe != null)
{
- AutoScaleHorizontal = true,
- CanBeFocused = false
- };
- var firstinSufficient = itemList.Content.Children.FirstOrDefault(c => c.UserData is FabricationRecipe fabricableItem && FabricationDegreeOfSuccess(character, fabricableItem.RequiredSkills) < 0.5f);
- if (firstinSufficient != null)
- {
- insufficientSkillsText.RectTransform.RepositionChildInHierarchy(itemList.Content.RectTransform.GetChildIndex(firstinSufficient.RectTransform));
+ requiresRecipeText.RectTransform.RepositionChildInHierarchy(itemList.Content.GetChildIndex(firstMissingRecipe));
+ requiresRecipeText.Visible = true;
}
else
{
- sufficientSkillsText.Visible = insufficientSkillsText.Visible = false;
- sufficientSkillsText.Enabled = insufficientSkillsText.Enabled = false;
+ requiresRecipeText.Visible = false;
}
- var requiresRecipeText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.15f), itemList.Content.RectTransform),
- TextManager.Get("fabricatorrequiresrecipe"), textColor: Color.Red, font: GUIStyle.SubHeadingFont)
- {
- AutoScaleHorizontal = true,
- CanBeFocused = false
- };
- var firstRequiresRecipe = itemList.Content.Children.FirstOrDefault(c =>
- c.UserData is FabricationRecipe fabricableItem &&
- fabricableItem.RequiresRecipe && !AnyOneHasRecipeForItem(character, fabricableItem.TargetItem));
- if (firstRequiresRecipe != null)
- {
- requiresRecipeText.RectTransform.RepositionChildInHierarchy(itemList.Content.RectTransform.GetChildIndex(firstRequiresRecipe.RectTransform));
- }
-
- FilterEntities(selectedItemCategory, itemFilterBox?.Text ?? string.Empty);
HideEmptyItemListCategories();
}
@@ -757,6 +901,9 @@ namespace Barotrauma.Items.Components
private bool FilterEntities(MapEntityCategory? category, string filter)
{
+ bool onlyShowAvailable = availableOnlyTickBox is { Selected: true };
+
+ bool anyVisible = false;
foreach (GUIComponent child in itemList.Content.Children)
{
FabricationRecipe recipe = child.UserData as FabricationRecipe;
@@ -771,16 +918,35 @@ namespace Barotrauma.Items.Components
}
}
+ if (recipe.RequiresRecipe && recipe.HideIfNoRecipe)
+ {
+ if (Character.Controlled != null)
+ {
+ if (!AnyOneHasRecipeForItem(Character.Controlled, recipe.TargetItem))
+ {
+ child.Visible = false;
+ continue;
+ }
+ }
+ }
+
child.Visible =
(string.IsNullOrWhiteSpace(filter) || recipe.DisplayName.Contains(filter, StringComparison.OrdinalIgnoreCase)) &&
- (!category.HasValue || recipe.TargetItem.Category.HasFlag(category.Value));
- }
+ (!category.HasValue || recipe.TargetItem.Category.HasFlag(category.Value)) &&
+ (!onlyShowAvailable || CanBeFabricated(recipe, availableIngredients, Character.Controlled));
+ if (child.Visible)
+ {
+ anyVisible = true;
+ }
+ }
foreach (GUIButton btn in itemCategoryButtons)
{
btn.Selected = (MapEntityCategory?)btn.UserData == selectedItemCategory;
}
HideEmptyItemListCategories();
+ nothingToShowText.Visible = !anyVisible;
+ itemList.UserData = "itemlist";
return true;
}
@@ -788,7 +954,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
+ //go through the elements backwards, and disable the labels if there's no items below them
bool recipeVisible = false;
foreach (GUIComponent child in itemList.Content.Children.Reverse())
{
@@ -810,6 +976,12 @@ namespace Barotrauma.Items.Components
}
}
+ SortBy sortBy = (SortBy)sortByDropdown.SelectedData;
+ if (sortBy != SortBy.Category)
+ {
+ itemList.Content.Children.Where(c => c.UserData?.GetType() == typeof(MapEntityCategory)).ForEach(c => c.Visible = false);
+ }
+
if (visibleElementsChanged)
{
itemList.UpdateScrollBarSize();
@@ -841,8 +1013,8 @@ namespace Barotrauma.Items.Components
private void CreateSelectedItemUI(SelectedRecipe recipe)
{
- var (user, selectedItem, overrideRequiredTime) = recipe;
- int max = Math.Max(selectedItem.TargetItem.GetMaxStackSize(outputContainer.Inventory) / selectedItem.Amount, 1);
+ var (user, selectedRecipe, overrideRequiredTime) = recipe;
+ int max = Math.Max(selectedRecipe.TargetItem.GetMaxStackSize(outputContainer.Inventory) / selectedRecipe.Amount, 1);
if (amountInput != null)
{
@@ -859,18 +1031,59 @@ namespace Barotrauma.Items.Components
selectedItemFrame.ClearChildren();
selectedItemReqsFrame.ClearChildren();
- var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.9f), selectedItemFrame.RectTransform, Anchor.Center)) { RelativeSpacing = 0.03f };
+ var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.9f), selectedItemFrame.RectTransform, Anchor.Center)) { RelativeSpacing = 0.03f, CanBeFocused = true };
var paddedReqFrame = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.9f), selectedItemReqsFrame.RectTransform, Anchor.Center)) { RelativeSpacing = 0.03f };
- LocalizedString itemName = GetRecipeNameAndAmount(selectedItem);
+ LocalizedString itemName = GetRecipeNameAndAmount(selectedRecipe);
LocalizedString name = itemName;
- QualityResult result = GetFabricatedItemQuality(selectedItem, user);
+ QualityResult result = GetFabricatedItemQuality(selectedRecipe, user);
- float quality = selectedItem.Quality ?? result.Quality;
- if (quality > 0 || result.HasRandomQualityRollChance)
+ float minimumQuality = selectedRecipe.Quality ?? result.Quality;
+
+ LocalizedString qualityTooltip = string.Empty;
+ if (result.HasRandomQualityRollChance)
{
- name = TextManager.GetWithVariable("itemname.quality" + (int)quality, "[itemname]", itemName + '\n')
+ float plusOnePercentage = result.TotalPlusOnePercentage;
+ float plusTwoPercentage = result.TotalPlusTwoPercentage;
+
+ string plusOnePercentageText = plusOnePercentage.ToString("F1", CultureInfo.InvariantCulture);
+ string plusTwoPercentageText = plusTwoPercentage.ToString("F1", CultureInfo.InvariantCulture);
+
+ int plusOneQuality = Math.Clamp(result.Quality + 1, min: 0, max: 3);
+ int plusTwoQuality = Math.Clamp(result.Quality + 2, min: 0, max: 3);
+
+ LocalizedString plusOneQualityText = TextManager.Get($"quality{plusOneQuality}");
+ LocalizedString plusTwoQualityText = TextManager.Get($"quality{plusTwoQuality}");
+
+ string localizationTag = plusTwoPercentage > 0f && plusOnePercentage > 0 && plusOneQuality != plusTwoQuality ? "meetsbonusrequirementtwice" : "meetsbonusrequirement";
+
+ var variables = new (string Key, LocalizedString Value)[]
+ {
+ ("[chance]", plusOnePercentageText), ("[quality]", plusOneQualityText),
+ ("[chance2]", plusTwoPercentageText), ("[quality2]", plusTwoQualityText)
+ };
+
+ if (MathUtils.NearlyEqual(plusOnePercentage, 0))
+ {
+ variables = new[] { ("[chance]", plusTwoPercentageText), ("[quality]", plusTwoQualityText) };
+ }
+
+ if (plusOneQuality == plusTwoQuality)
+ {
+ LocalizedString rawPercentage = result.PlusOnePercentage.ToString("F1", CultureInfo.InvariantCulture);
+ variables = new[] { ("[chance]", rawPercentage), ("[quality]", plusOneQualityText) };
+ }
+
+ if (plusOnePercentage >= 100.0f) { minimumQuality = plusOneQuality; }
+ if (plusTwoPercentage >= 100.0f) { minimumQuality = plusTwoQuality; }
+
+ qualityTooltip = TextManager.GetWithVariables(localizationTag, variables);
+ }
+
+ if (minimumQuality > 0 || result.HasRandomQualityRollChance)
+ {
+ name = TextManager.GetWithVariable("itemname.quality" + (int)minimumQuality, "[itemname]", itemName + '\n')
.Fallback(TextManager.GetWithVariable("itemname.quality3", "[itemname]", itemName + '\n'));
}
@@ -884,44 +1097,13 @@ namespace Barotrauma.Items.Components
{
var iconLayout = new GUIFrame(new RectTransform(new Vector2(0.4f, 1f), selectedItemFrame.RectTransform, anchor: Anchor.TopRight), style: null);
var icon = GameSession.CreateNotificationIcon(iconLayout, offset: true);
-
- float percentage1 = result.TotalPlusOnePercentage;
- float percentage2 = result.TotalPlusTwoPercentage;
-
- string chance1text = percentage1.ToString("F1", CultureInfo.InvariantCulture);
- string chance2text = percentage2.ToString("F1", CultureInfo.InvariantCulture);
-
- int quality1 = Math.Clamp(result.Quality + 1, min: 0, max: 3);
- int quality2 = Math.Clamp(result.Quality + 2, min: 0, max: 3);
-
- LocalizedString quality1Text = TextManager.Get($"quality{quality1}");
- LocalizedString quality2Text = TextManager.Get($"quality{quality2}");
-
- string localizationTag = percentage2 > 0f && percentage1 > 0 && quality1 != quality2 ? "meetsbonusrequirementtwice" : "meetsbonusrequirement";
-
- var variables = new (string Key, LocalizedString Value)[]
- {
- ("[chance]", chance1text), ("[quality]", quality1Text),
- ("[chance2]", chance2text), ("[quality2]", quality2Text)
- };
-
- if (MathUtils.NearlyEqual(percentage1, 0))
- {
- variables = new[] { ("[chance]", chance2text), ("[quality]", quality2Text) };
- }
-
- if (quality1 == quality2)
- {
- LocalizedString rawPercentage = result.PlusOnePercentage.ToString("F1", CultureInfo.InvariantCulture);
- variables = new[] { ("[chance]", rawPercentage), ("[quality]", quality1Text) };
- }
-
- LocalizedString qualityTooltip = TextManager.GetWithVariables(localizationTag, variables);
-
icon.ToolTip = RichString.Rich(qualityTooltip);
icon.Visible = icon.CanBeFocused = true;
}
+ outputTopArea.RectTransform.MaxSize = new Point(int.MaxValue, outputInventoryHolder.Rect.Height);
+ paddedOutputArea.Recalculate();
+
nameBlock.Padding = new Vector4(0, nameBlock.Padding.Y, GUI.IntScale(5), nameBlock.Padding.W);
if (nameBlock.TextScale < 0.7f)
{
@@ -932,31 +1114,41 @@ namespace Barotrauma.Items.Components
nameBlock.RectTransform.MinSize = new Point(0, (int)(nameBlock.TextSize.Y * nameBlock.TextScale));
}
- if (!selectedItem.TargetItem.Description.IsNullOrEmpty())
+ bool largeUI = GuiFrame.Rect.Height > GUI.IntScale(500);
+ if (largeUI)
{
- var description = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedFrame.RectTransform),
- RichString.Rich(selectedItem.TargetItem.Description),
- font: GUIStyle.SmallFont, wrap: true);
- description.Padding = new Vector4(0, description.Padding.Y, description.Padding.Z, description.Padding.W);
+ paddedFrame.ChildAnchor = Anchor.CenterLeft;
+ }
- while (description.Rect.Height + nameBlock.Rect.Height > paddedFrame.Rect.Height)
+ if (!selectedRecipe.TargetItem.Description.IsNullOrEmpty())
+ {
+ var descriptionParent = largeUI ? paddedReqFrame : paddedFrame;
+ var description = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), descriptionParent.RectTransform),
+ RichString.Rich(selectedRecipe.TargetItem.Description),
+ font: GUIStyle.SmallFont, wrap: true);
+ if (!largeUI)
+ {
+ description.Padding = new Vector4(0, description.Padding.Y, description.Padding.Z, description.Padding.W);
+ }
+
+ while (description.Rect.Height + nameBlock.Rect.Height > descriptionParent.Rect.Height)
{
var lines = description.WrappedText.Split('\n');
if (lines.Count <= 1) { break; }
var newString = string.Join('\n', lines.Take(lines.Count - 1));
description.Text = newString.Substring(0, newString.Length - 4) + "...";
description.CalculateHeightFromText();
- description.ToolTip = selectedItem.TargetItem.Description;
+ description.ToolTip = selectedRecipe.TargetItem.Description;
}
}
IEnumerable inadequateSkills = Enumerable.Empty();
if (user != null)
{
- inadequateSkills = selectedItem.RequiredSkills.Where(skill => user.GetSkillLevel(skill.Identifier) < Math.Round(skill.Level * SkillRequirementMultiplier));
+ inadequateSkills = selectedRecipe.RequiredSkills.Where(skill => user.GetSkillLevel(skill.Identifier) < Math.Round(skill.Level * SkillRequirementMultiplier));
}
- if (selectedItem.RequiredSkills.Any())
+ if (selectedRecipe.RequiredSkills.Any())
{
LocalizedString text = "";
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedReqFrame.RectTransform),
@@ -965,20 +1157,20 @@ namespace Barotrauma.Items.Components
AutoScaleHorizontal = true,
ToolTip = TextManager.Get("fabricatorrequiredskills.tooltip")
};
- foreach (Skill skill in selectedItem.RequiredSkills)
+ foreach (Skill skill in selectedRecipe.RequiredSkills)
{
text += TextManager.Get("SkillName." + skill.Identifier) + " " + TextManager.Get("Lvl").ToLower() + " " + Math.Round(skill.Level * SkillRequirementMultiplier);
- if (skill != selectedItem.RequiredSkills.Last()) { text += "\n"; }
+ if (skill != selectedRecipe.RequiredSkills.Last()) { text += "\n"; }
}
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedReqFrame.RectTransform), text, font: GUIStyle.SmallFont);
}
- float degreeOfSuccess = user == null ? 0.0f : FabricationDegreeOfSuccess(user, selectedItem.RequiredSkills);
+ float degreeOfSuccess = user == null ? 0.0f : FabricationDegreeOfSuccess(user, selectedRecipe.RequiredSkills);
if (degreeOfSuccess > 0.5f) { degreeOfSuccess = 1.0f; }
float requiredTime = overrideRequiredTime.TryUnwrap(out var time)
? time
- : (user == null ? selectedItem.RequiredTime : GetRequiredTime(selectedItem, user));
+ : (user == null ? selectedRecipe.RequiredTime : GetRequiredTime(selectedRecipe, user));
if ((int)requiredTime > 0)
{
@@ -991,7 +1183,7 @@ namespace Barotrauma.Items.Components
font: GUIStyle.SmallFont);
}
- if (SelectedItem.RequiredMoney > 0)
+ if (selectedRecipe.RequiredMoney > 0)
{
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedReqFrame.RectTransform),
TextManager.Get("subeditor.price"), textColor: ToolBox.GradientLerp(degreeOfSuccess, GUIStyle.Red, Color.Yellow, GUIStyle.Green), font: GUIStyle.SubHeadingFont)
@@ -1000,7 +1192,6 @@ namespace Barotrauma.Items.Components
};
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedReqFrame.RectTransform), TextManager.FormatCurrency(SelectedItem.RequiredMoney),
font: GUIStyle.SmallFont);
-
}
}
@@ -1031,7 +1222,7 @@ namespace Barotrauma.Items.Components
{
if (selectedItem == null) { return false; }
if (fabricatedItem == null &&
- !outputContainer.Inventory.CanBePut(selectedItem.TargetItem, selectedItem.OutCondition * selectedItem.TargetItem.Health))
+ !outputContainer.Inventory.CanProbablyBePut(selectedItem.TargetItem, selectedItem.OutCondition * selectedItem.TargetItem.Health))
{
outputSlot.Flash(GUIStyle.Red);
return false;
@@ -1101,8 +1292,14 @@ namespace Barotrauma.Items.Components
activateButton.Enabled = canBeFabricated;
}
+ bool sufficientSkills = FabricationDegreeOfSuccess(character, recipe.RequiredSkills) >= 0.5f;
+
+ Color baseColor = MissingRequiredRecipe(recipe, character) ?
+ GUIStyle.Red :
+ (sufficientSkills ? GUIStyle.TextColorNormal : GUIStyle.Orange);
+
var childContainer = child.GetChild();
- childContainer.GetChild().TextColor = Color.White * (canBeFabricated ? 1.0f : 0.5f);
+ childContainer.GetChild().TextColor = baseColor * (canBeFabricated ? 1.0f : 0.5f);
childContainer.GetChild().Color = recipe.TargetItem.InventoryIconColor * (canBeFabricated ? 1.0f : 0.5f);
var limitReachedText = child.FindChild(nameof(FabricationLimitReachedText));
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs
index 1a1f2780e..fafa2ee3f 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs
@@ -10,45 +10,27 @@ using Microsoft.Xna.Framework.Input;
namespace Barotrauma.Items.Components
{
- internal readonly struct MiniMapGUIComponent
+ internal readonly record struct MiniMapGUIComponent(GUIComponent RectComponent, GUIComponent BorderComponent)
{
- public readonly GUIComponent RectComponent;
- public readonly GUIComponent BorderComponent;
-
- public MiniMapGUIComponent(GUIComponent rectComponent)
+ public MiniMapGUIComponent(GUIComponent rectComponent) : this(rectComponent, rectComponent)
{
- RectComponent = rectComponent;
- BorderComponent = rectComponent;
}
-
- public MiniMapGUIComponent(GUIComponent frame, GUIComponent linkedHullComponent)
- {
- RectComponent = frame;
- BorderComponent = linkedHullComponent;
- }
-
+
public void Deconstruct(out GUIComponent component, out GUIComponent borderComponent)
{
component = RectComponent;
borderComponent = BorderComponent;
}
}
-
- internal readonly struct MiniMapSprite
+
+ internal readonly record struct MiniMapSprite(Sprite? Sprite, Color Color)
{
- public readonly Sprite? Sprite;
- public readonly Color Color;
-
- public MiniMapSprite(JobPrefab prefab)
+ public MiniMapSprite(JobPrefab prefab) : this(prefab.IconSmall, prefab.UIColor)
{
- Sprite = prefab.IconSmall;
- Color = prefab.UIColor;
}
-
- public MiniMapSprite(Order order)
+
+ public MiniMapSprite(Order order) : this(order.SymbolSprite, order.Color)
{
- Sprite = order.SymbolSprite;
- Color = order.Color;
}
}
@@ -223,7 +205,27 @@ namespace Barotrauma.Items.Components
NoPowerColor = MiniMapBaseColor * 0.1f,
ElectricalBaseColor = GUIStyle.Orange,
NoPowerElectricalColor = ElectricalBaseColor * 0.1f;
-
+
+ // If this is portable, only allow displaying data in the player sub (not enemy subs, ruins, wrecks or other unknown places)
+ private bool IsPortableItemAllowed
+ {
+ get
+ {
+ if (IsUsableOutsidePlayerSub) { return true; }
+ if (item.Submarine == null) { return false; }
+ if (item.GetComponent() is not Pickable handheldItem) { return true; }
+ // This will effectively make sure wherever we are, it belongs to the player
+ return handheldItem.Picker?.TeamID == item.Submarine.TeamID;
+ }
+ }
+
+ [Serialize(false, IsPropertySaveable.No, description: "If this item is portable, should it be usable outside the player submarine?")]
+ public bool IsUsableOutsidePlayerSub
+ {
+ get;
+ set;
+ }
+
partial void InitProjSpecific()
{
hullDatas = new Dictionary();
@@ -425,22 +427,25 @@ namespace Barotrauma.Items.Components
return false;
}
- private bool VisibleOnItemFinder(Item it)
+ private bool VisibleOnItemFinder(Item targetItem)
{
- if (it?.Submarine == null) { return false; }
- if (item.Submarine == null || !item.Submarine.IsEntityFoundOnThisSub(it, includingConnectedSubs: true)) { return false; }
- if (it.NonInteractable || it.IsHidden) { return false; }
- if (it.GetComponent() == null) { return false; }
+ if (targetItem?.Submarine == null || item.Submarine == null) { return false; }
- var holdable = it.GetComponent();
+ if (!IsPortableItemAllowed) { return false; }
+
+ if (!item.Submarine.IsEntityFoundOnThisSub(targetItem, includingConnectedSubs: true)) { return false; }
+ if (targetItem.NonInteractable || targetItem.IsHidden) { return false; }
+ if (targetItem.GetComponent() == null) { return false; }
+
+ var holdable = targetItem.GetComponent();
if (holdable != null && holdable.Attached) { return false; }
- var wire = it.GetComponent();
+ var wire = targetItem.GetComponent();
if (wire != null && wire.Connections.Any(c => c != null)) { return false; }
- if (it.Container?.GetComponent() is { DrawInventory: false } or { AllowAccess: false }) { return false; }
+ if (targetItem.Container?.GetComponent() is { DrawInventory: false } or { AllowAccess: false }) { return false; }
- if (it.HasTag(Tags.TraitorMissionItem)) { return false; }
+ if (targetItem.HasTag(Tags.TraitorMissionItem)) { return false; }
return true;
}
@@ -454,18 +459,24 @@ namespace Barotrauma.Items.Components
searchAutoComplete?.AddToGUIUpdateList(order: order + 1);
}
}
-
- private void CreateHUD()
+
+ private void ClearHUD()
{
subEntities.Clear();
- prevResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
submarineContainer.ClearChildren();
+ displayedSubs.Clear();
+ }
- if (item.Submarine is null)
+ private void RefreshHUD()
+ {
+ ClearHUD();
+
+ if (item.Submarine is null || !IsPortableItemAllowed)
{
- displayedSubs.Clear();
return;
}
+
+ prevResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
scissorComponent = new GUIScissorComponent(new RectTransform(Vector2.One, submarineContainer.RectTransform, Anchor.Center));
miniMapContainer = new GUIFrame(new RectTransform(Vector2.One, scissorComponent.Content.RectTransform, Anchor.Center), style: null) { CanBeFocused = false };
@@ -574,18 +585,25 @@ namespace Barotrauma.Items.Components
public override void UpdateHUDComponentSpecific(Character character, float deltaTime, Camera cam)
{
- //recreate HUD if the subs we should display have changed
- if (item.Submarine == null && displayedSubs.Count > 0 || // item not inside a sub anymore, but display is still showing subs
+ // Refresh HUD (including possibly just clearing it away) if the subs we should display have changed
+ if (item.Submarine == null && displayedSubs.Count > 0 || // item not inside a sub anymore, but display is still showing subs
item.Submarine is { } itemSub &&
(
- !displayedSubs.Contains(itemSub) || // current sub not displayed
- itemSub.DockedTo.Where(s => s.TeamID == item.Submarine.TeamID).Any(s => !displayedSubs.Contains(s) && itemSub.ConnectedDockingPorts[s].IsLocked) || // some of the docked subs not displayed
- displayedSubs.Any(s => s != itemSub && !itemSub.DockedTo.Contains(s)) // displaying a sub that shouldn't be displayed
+ // current sub not displayed
+ !displayedSubs.Contains(itemSub) ||
+ // some of the docked subs not displayed
+ itemSub.DockedTo.Where(s => s.TeamID == item.Submarine.TeamID).Any(s => !displayedSubs.Contains(s) && itemSub.ConnectedDockingPorts[s].IsLocked) ||
+ // displaying a sub that shouldn't be displayed
+ displayedSubs.Any(s => s != itemSub && !itemSub.DockedTo.Contains(s))
) ||
- prevResolution.X != GameMain.GraphicsWidth || prevResolution.Y != GameMain.GraphicsHeight || // resolution changed
- !submarineContainer.Children.Any()) // We lack a GUI
+ // If this item is portable and not in a player sub and using it otherwise is disallowed
+ !IsPortableItemAllowed ||
+ // resolution changed
+ prevResolution.X != GameMain.GraphicsWidth || prevResolution.Y != GameMain.GraphicsHeight ||
+ // We lack a GUI
+ !submarineContainer.Children.Any())
{
- CreateHUD();
+ RefreshHUD();
}
//reset data if we haven't received anything in a while
@@ -737,7 +755,7 @@ namespace Barotrauma.Items.Components
return;
}
- if (Voltage < MinVoltage)
+ if (!HasPower)
{
Vector2 textSize = GUIStyle.Font.MeasureString(noPowerTip);
Vector2 textPos = GuiFrame.Rect.Center.ToVector2();
@@ -747,7 +765,7 @@ namespace Barotrauma.Items.Components
return;
}
- if (currentMode == MiniMapMode.HullStatus && item.Submarine != null)
+ if (currentMode == MiniMapMode.HullStatus && item.Submarine != null && IsPortableItemAllowed)
{
Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle;
spriteBatch.End();
@@ -958,19 +976,19 @@ namespace Barotrauma.Items.Components
MiniMapBlips = positions.ToImmutableHashSet();
- if (searchAutoComplete is null) { return; }
- searchAutoComplete.Visible = false;
+ HideGUIComponent(searchAutoComplete);
}
private void UpdateHUDBack()
{
- if (item.Submarine == null) { return; }
-
- if (hullInfoFrame != null) { hullInfoFrame.Visible = false; }
- reportFrame.Visible = false;
- searchBarFrame.Visible = false;
- electricalFrame.Visible = false;
- miniMapFrame.Visible = false;
+ // Clear up mode-specific elements before checking if drawing should continue, so they'll be gone if not
+ HideModeSpecificFrames();
+
+ if (item.Submarine == null || !IsPortableItemAllowed)
+ {
+ ClearHUD();
+ return;
+ }
switch (currentMode)
{
@@ -988,7 +1006,24 @@ namespace Barotrauma.Items.Components
break;
}
}
-
+
+ private void HideModeSpecificFrames()
+ {
+ HideGUIComponent(hullInfoFrame);
+ HideGUIComponent(reportFrame);
+ HideGUIComponent(searchBarFrame);
+ HideGUIComponent(electricalFrame);
+ HideGUIComponent(miniMapFrame);
+ }
+
+ private static void HideGUIComponent(GUIComponent? component)
+ {
+ if (component != null)
+ {
+ component.Visible = false;
+ }
+ }
+
private void UpdateHullStatus()
{
bool canHoverOverHull = true;
@@ -1007,7 +1042,7 @@ namespace Barotrauma.Items.Components
child.Color = child.OutlineColor = NoPowerDoorColor;
}
- if (Voltage < MinVoltage) { continue; }
+ if (!HasPower) { continue; }
child.Color = child.OutlineColor = DoorIndicatorColor;
if (GUI.MouseOn == child)
@@ -1037,7 +1072,7 @@ namespace Barotrauma.Items.Components
}
}
- if (Voltage < MinVoltage) { continue; }
+ if (!HasPower) { continue; }
hullDatas.TryGetValue(hull, out HullData? hullData);
if (hullData is null)
@@ -1187,7 +1222,7 @@ namespace Barotrauma.Items.Components
component.Color = component.OutlineColor = NoPowerElectricalColor;
}
- if (Voltage < MinVoltage || !miniMapGuiComponent.RectComponent.Visible) { continue; }
+ if (!HasPower || !miniMapGuiComponent.RectComponent.Visible) { continue; }
int durability = (int)(it.Condition / (it.MaxCondition / it.MaxRepairConditionMultiplier) * 100f);
Color color = ToolBox.GradientLerp(durability / 100f, GUIStyle.Red, GUIStyle.Orange, GUIStyle.Green, GUIStyle.Green);
@@ -1229,11 +1264,11 @@ namespace Barotrauma.Items.Components
private void DrawHUDBack(SpriteBatch spriteBatch, GUICustomComponent container)
{
- if (item.Submarine == null) { return; }
+ if (item.Submarine == null || !IsPortableItemAllowed) { return; }
- DrawSubmarine(spriteBatch);
+ DrawSubmarine(spriteBatch);
- if (Voltage < MinVoltage) { return; }
+ if (!HasPower) { return; }
Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle;
spriteBatch.End();
spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Reactor.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Reactor.cs
index 43ec63ae5..ce3d63e73 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Reactor.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Reactor.cs
@@ -475,11 +475,8 @@ namespace Barotrauma.Items.Components
if (sound != null)
{
SoundPlayer.PlaySound(
- sound.Sound,
+ sound,
item.WorldPosition,
- sound.Volume,
- sound.Range,
- freqMult: sound.GetRandomFrequencyMultiplier(),
hullGuess: item.CurrentHull);
}
}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs
index cb5115422..dc0d6dcbf 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs
@@ -144,6 +144,7 @@ namespace Barotrauma.Items.Components
private bool isConnectedToSteering;
private static LocalizedString caveLabel;
+ private static LocalizedString enemyLabel;
[Serialize(false, IsPropertySaveable.Yes)]
@@ -164,6 +165,8 @@ namespace Barotrauma.Items.Components
TextManager.Get("cave").Fallback(
TextManager.Get("missiontype.nest"));
+ enemyLabel = TextManager.Get("enemysubmarine");
+
foreach (var subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
@@ -216,8 +219,9 @@ namespace Barotrauma.Items.Components
Vector2 size = isConnectedToSteering ? controlBoxSize : new Vector2(0.46f, 0.4f);
controlContainer = new GUIFrame(new RectTransform(size, GuiFrame.RectTransform, Anchor.BottomLeft), "ItemUI");
- if (!isConnectedToSteering && !GUI.IsFourByThree())
+ if (!isConnectedToSteering && GUI.AspectRatioDifference <= 0)
{
+ // In wider than 4:3 aspect ratio, we'll limit the max size.
controlContainer.RectTransform.MaxSize = new Point((int)(380 * GUI.xScale), (int)(300 * GUI.yScale));
}
var paddedControlContainer = new GUIFrame(new RectTransform(controlContainer.Rect.Size - GUIStyle.ItemFrameMargin, controlContainer.RectTransform, Anchor.Center)
@@ -1019,6 +1023,38 @@ namespace Barotrauma.Items.Components
cave.StartPos.ToVector2(), transducerCenter,
DisplayScale, center, DisplayRadius);
}
+
+ if (GameMain.NetworkMember is { } networkMember && GameMain.GameSession?.GameMode is PvPMode)
+ {
+ if (networkMember.ServerSettings.TrackOpponentInPvP
+ && Submarine.MainSubs[0] is { } coalitionSub
+ && Submarine.MainSubs[1] is { } separatistSub
+ && Character.Controlled is { } player)
+ {
+ Submarine whichSubToDraw = player.TeamID switch
+ {
+ CharacterTeamType.Team1 => separatistSub,
+ CharacterTeamType.Team2 => coalitionSub,
+ _ => null
+ };
+
+ if (whichSubToDraw != null)
+ {
+ DrawOffsetMarker(spriteBatch,
+ enemyLabel.Value,
+ Tags.Submarine,
+ Tags.Enemy,
+ whichSubToDraw.WorldPosition,
+ transducerCenter,
+ distanceThresholds: new Range(start: MetersToUnits(150), end: MetersToUnits(1600)),
+ offset: new Range(start: MetersToUnits(100), end: MetersToUnits(400)),
+ minOffset: MetersToUnits(10));
+
+ static float MetersToUnits(float m)
+ => m / Physics.DisplayToRealWorldRatio;
+ }
+ }
+ }
}
int missionIndex = 0;
@@ -1042,7 +1078,8 @@ namespace Barotrauma.Items.Components
}
if (HasMineralScanner && UseMineralScanner && CurrentMode == Mode.Active && MineralClusters != null &&
- (item.CurrentHull == null || !DetectSubmarineWalls))
+ (item.CurrentHull == null || !DetectSubmarineWalls) &&
+ HasPower)
{
foreach (var c in MineralClusters)
{
@@ -1076,7 +1113,7 @@ namespace Barotrauma.Items.Components
{
if (!sub.ShowSonarMarker) { continue; }
if (connectedSubs.Contains(sub)) { continue; }
- if (Level.Loaded != null && sub.WorldPosition.Y > Level.Loaded.Size.Y) { continue; }
+ if (sub.IsAboveLevel) { continue; }
if (item.Submarine != null || Character.Controlled != null)
{
@@ -1185,7 +1222,7 @@ namespace Barotrauma.Items.Components
foreach (DockingPort dockingPort in DockingPort.List)
{
- if (Level.Loaded != null && dockingPort.Item.Submarine.WorldPosition.Y > Level.Loaded.Size.Y) { continue; }
+ if (dockingPort.Item.Submarine.IsAboveLevel) { continue; }
if (dockingPort.Item.IsHidden) { continue; }
if (dockingPort.Item.Submarine == null) { continue; }
if (dockingPort.Item.Submarine.Info.IsWreck) { continue; }
@@ -1198,8 +1235,8 @@ namespace Barotrauma.Items.Components
//don't show the docking ports of the opposing team on the sonar
if (item.Submarine != null &&
- item.Submarine != GameMain.NetworkMember?.RespawnManager?.RespawnShuttle &&
- dockingPort.Item.Submarine != GameMain.NetworkMember?.RespawnManager?.RespawnShuttle &&
+ !item.Submarine.IsRespawnShuttle &&
+ !dockingPort.Item.Submarine.IsRespawnShuttle &&
!dockingPort.Item.Submarine.Info.IsOutpost &&
!dockingPort.Item.Submarine.Info.IsBeacon)
{
@@ -1792,8 +1829,47 @@ namespace Barotrauma.Items.Components
sonarBlip.Draw(spriteBatch, center + pos, color * 0.5f * blip.Alpha, sonarBlip.Origin, 0, scale, SpriteEffects.None, 0);
}
+ ///
+ /// Used in DrawOffsetMarker to cache the randomized location of the marker
+ ///
+ private readonly Dictionary cachedLocations = new Dictionary();
+
+ private void DrawOffsetMarker(SpriteBatch spriteBatch, string label, Identifier iconIdentifier, Identifier targetIdentifier, Vector2 worldPosition, Vector2 transducerPosition, Range distanceThresholds, Range offset, float minOffset)
+ {
+ Vector2 pos;
+
+ if (!cachedLocations.TryGetValue(targetIdentifier, out CachedLocation cachedLocation))
+ {
+ cachedLocation = CreateCachedLocation();
+ cachedLocations.Add(targetIdentifier, cachedLocation);
+ pos = cachedLocation.Location;
+ }
+ else
+ {
+ if (Timing.TotalTime > cachedLocation.RecalculationTime)
+ {
+ cachedLocation = CreateCachedLocation();
+ cachedLocations[targetIdentifier] = cachedLocation;
+ }
+
+ pos = cachedLocation.Location;
+ }
+
+ DrawMarker(spriteBatch, label, iconIdentifier, targetIdentifier, pos, transducerPosition, DisplayScale, center, DisplayRadius);
+
+ CachedLocation CreateCachedLocation()
+ {
+ float distance = Vector2.Distance(worldPosition, transducerPosition);
+
+ float maxOffset = MathHelper.Lerp(offset.Start, offset.End, MathHelper.Clamp((distance - distanceThresholds.Start) / (distanceThresholds.End - distanceThresholds.Start), 0.0f, 1.0f));
+
+ Vector2 randomPos = Rand.Vector(Rand.Range(minOffset, maxOffset));
+ return new CachedLocation(worldPosition + randomPos, Timing.TotalTime + Rand.Range(10.0f, 30.0f));
+ }
+ }
+
private void DrawMarker(SpriteBatch spriteBatch, string label, Identifier iconIdentifier, object targetIdentifier, Vector2 worldPosition, Vector2 transducerPosition, float scale, Vector2 center, float radius,
- bool onlyShowTextOnMouseOver = false)
+ bool onlyShowTextOnMouseOver = false)
{
float linearDist = Vector2.Distance(worldPosition, transducerPosition);
float dist = linearDist;
@@ -1903,25 +1979,6 @@ namespace Barotrauma.Items.Components
2, GUIStyle.SmallFont);
}
- protected override void RemoveComponentSpecific()
- {
- base.RemoveComponentSpecific();
- sonarBlip?.Remove();
- pingCircle?.Remove();
- directionalPingCircle?.Remove();
- screenOverlay?.Remove();
- screenBackground?.Remove();
- lineSprite?.Remove();
-
- foreach (var t in targetIcons.Values)
- {
- t.Item1.Remove();
- }
- targetIcons.Clear();
-
- MineralClusters = null;
- }
-
public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null)
{
msg.WriteBoolean(currentMode == Mode.Active);
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Steering.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Steering.cs
index 4d9aabff1..931672145 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Steering.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Steering.cs
@@ -57,24 +57,42 @@ namespace Barotrauma.Items.Components
private GUIMessageBox enterOutpostPrompt, exitOutpostPrompt;
private bool levelStartSelected;
+ [Serialize(defaultValue: false, isSaveable: IsPropertySaveable.Yes, AlwaysUseInstanceValues = true)]
public bool LevelStartSelected
{
- get { return levelStartTickBox.Selected; }
- set { levelStartTickBox.Selected = value; }
+ get
+ {
+ return levelStartTickBox?.Selected ?? levelStartSelected;
+ }
+ set
+ {
+ TrySetTickBoxSelected(levelStartTickBox, ref levelStartSelected, value);
+ }
}
private bool levelEndSelected;
+ [Serialize(defaultValue: false, isSaveable: IsPropertySaveable.Yes, AlwaysUseInstanceValues = true)]
public bool LevelEndSelected
{
- get { return levelEndTickBox.Selected; }
- set { levelEndTickBox.Selected = value; }
+ get { return levelEndTickBox?.Selected ?? levelEndSelected; }
+ set
+ {
+ TrySetTickBoxSelected(levelEndTickBox, ref levelEndSelected, value);
+ }
}
private bool maintainPos;
+ [Serialize(defaultValue: false, isSaveable: IsPropertySaveable.Yes, AlwaysUseInstanceValues = true)]
public bool MaintainPos
{
- get { return maintainPosTickBox.Selected; }
- set { maintainPosTickBox.Selected = value; }
+ get
+ {
+ return maintainPosTickBox?.Selected ?? maintainPos;
+ }
+ set
+ {
+ TrySetTickBoxSelected(maintainPosTickBox, ref maintainPos, value);
+ }
}
private float steerRadius;
@@ -554,7 +572,7 @@ namespace Barotrauma.Items.Components
int x = rect.X;
int y = rect.Y;
- if (Voltage < MinVoltage) { return; }
+ if (!HasPower) { return; }
Rectangle velRect = new Rectangle(x + 20, y + 20, width - 40, height - 40);
Vector2 steeringOrigin = steerArea.Rect.Center.ToVector2();
@@ -759,7 +777,7 @@ namespace Barotrauma.Items.Components
dockingButton.Text = dockText;
}
- if (Voltage < MinVoltage)
+ if (!HasPower)
{
tipContainer.Visible = true;
tipContainer.Text = noPowerTip;
@@ -829,7 +847,7 @@ namespace Barotrauma.Items.Components
}
if (!AutoPilot && Character.DisableControls && GUI.KeyboardDispatcher.Subscriber == null)
{
- steeringAdjustSpeed = character == null ? DefaultSteeringAdjustSpeed : MathHelper.Lerp(0.2f, 1.0f, character.GetSkillLevel("helm") / 100.0f);
+ steeringAdjustSpeed = character == null ? DefaultSteeringAdjustSpeed : MathHelper.Lerp(0.2f, 1.0f, character.GetSkillLevel(Tags.HelmSkill) / 100.0f);
Vector2 input = Vector2.Zero;
if (PlayerInput.KeyDown(InputType.Left)) { input -= Vector2.UnitX; }
if (PlayerInput.KeyDown(InputType.Right)) { input += Vector2.UnitX; }
@@ -909,7 +927,7 @@ namespace Barotrauma.Items.Components
if (targetPort.Docked || targetPort.Item.Submarine == null) { continue; }
if (targetPort.Item.Submarine == controlledSub || targetPort.IsHorizontal != sourcePort.IsHorizontal) { continue; }
if (targetPort.Item.Submarine.DockedTo?.Contains(sourcePort.Item.Submarine) ?? false) { continue; }
- if (Level.Loaded != null && targetPort.Item.Submarine.WorldPosition.Y > Level.Loaded.Size.Y) { continue; }
+ if (targetPort.Item.Submarine.IsAboveLevel) { continue; }
if (sourceDir == targetPort.GetDir()) { continue; }
float dist = Vector2.DistanceSquared(sourcePort.Item.WorldPosition, targetPort.Item.WorldPosition);
@@ -924,6 +942,21 @@ namespace Barotrauma.Items.Components
}
}
+ ///
+ /// Sets the value of the specified tickbox, or if it hasn't been instantiated (yet?), just the value of the backing field.
+ ///
+ private void TrySetTickBoxSelected(GUITickBox tickBox, ref bool backingValue, bool newValue)
+ {
+ if (tickBox == null)
+ {
+ backingValue = newValue;
+ }
+ else
+ {
+ tickBox.Selected = newValue;
+ }
+ }
+
private bool NudgeButtonClicked(GUIButton btn, object userdata)
{
if (!MaintainPos || !AutoPilot)
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Projectile.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Projectile.cs
index 0b551f6bd..4aa4c0ed4 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Projectile.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Projectile.cs
@@ -50,7 +50,7 @@ namespace Barotrauma.Items.Components
Hull hull = Entity.FindEntityByID(hullID) as Hull;
item.Submarine = submarine;
item.CurrentHull = hull;
- item.body.SetTransform(simPosition, item.body.Rotation);
+ item.body.SetTransformIgnoreContacts(simPosition, item.body.Rotation);
switch (targetType)
{
@@ -180,7 +180,11 @@ namespace Barotrauma.Items.Components
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "particleemitter":
- particleEmitters.Add(new ParticleEmitter(subElement));
+ var emitter = new ParticleEmitter(subElement);
+ //backwards compatibility: previously it was not possible to change if the particles use tracer points, they were always used on projectiles
+ //now emitters don't use them by default, except on projectiles
+ emitter.Prefab.Properties.UseTracerPoints = subElement.GetAttributeBool(nameof(emitter.Prefab.Properties.UseTracerPoints), true);
+ particleEmitters.Add(emitter);
break;
}
}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/RemoteController.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/RemoteController.cs
index 31f951899..f573503f7 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/RemoteController.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/RemoteController.cs
@@ -6,6 +6,7 @@ namespace Barotrauma.Items.Components
{
public override void DrawHUD(SpriteBatch spriteBatch, Character character)
{
+ base.DrawHUD(spriteBatch, character);
currentTarget?.DrawHUD(spriteBatch, Screen.Selected.Cam, character);
}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/RepairTool.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/RepairTool.cs
index 1b76a65ef..c7413f039 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/RepairTool.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/RepairTool.cs
@@ -58,7 +58,6 @@ namespace Barotrauma.Items.Components
}
}
-
partial void UseProjSpecific(float deltaTime, Vector2 raystart)
{
foreach (ParticleEmitter particleEmitter in particleEmitters)
@@ -88,25 +87,18 @@ namespace Barotrauma.Items.Components
MathUtils.InverseLerp(targetStructure.Prefab.MinHealth, targetStructure.Health, targetStructure.Health - targetStructure.SectionDamage(sectionIndex)),
GUIStyle.Red, GUIStyle.Green);
- if (progressBar != null) progressBar.Size = new Vector2(60.0f, 20.0f);
-
- Vector2 particlePos = ConvertUnits.ToDisplayUnits(pickedPosition);
- if (targetStructure.Submarine != null) particlePos += targetStructure.Submarine.DrawPosition;
+ if (progressBar != null) { progressBar.Size = new Vector2(60.0f, 20.0f); }
foreach (var emitter in particleEmitterHitStructure)
{
- float particleAngle = item.body.Rotation + MathHelper.ToRadians(BarrelRotation) + ((item.body.Dir > 0.0f) ? 0.0f : MathHelper.Pi);
- emitter.Emit(deltaTime, particlePos, item.CurrentHull, particleAngle + MathHelper.Pi, -particleAngle + MathHelper.Pi);
+ EmitParticle(emitter, deltaTime, pickedPosition, targetStructure.Submarine);
}
}
partial void FixCharacterProjSpecific(Character user, float deltaTime, Character targetCharacter)
{
- Vector2 particlePos = ConvertUnits.ToDisplayUnits(pickedPosition);
- if (targetCharacter.Submarine != null) particlePos += targetCharacter.Submarine.DrawPosition;
foreach (var emitter in particleEmitterHitCharacter)
{
- float particleAngle = item.body.Rotation + MathHelper.ToRadians(BarrelRotation) + ((item.body.Dir > 0.0f) ? 0.0f : MathHelper.Pi);
- emitter.Emit(deltaTime, particlePos, item.CurrentHull, particleAngle + MathHelper.Pi, -particleAngle + MathHelper.Pi);
+ EmitParticle(emitter, deltaTime, pickedPosition, targetCharacter.Submarine);
}
}
@@ -134,15 +126,22 @@ namespace Barotrauma.Items.Components
}
}
- Vector2 particlePos = ConvertUnits.ToDisplayUnits(pickedPosition);
- if (targetItem.Submarine != null) particlePos += targetItem.Submarine.DrawPosition;
foreach ((RelatedItem relatedItem, ParticleEmitter emitter) in particleEmitterHitItem)
{
if (!relatedItem.MatchesItem(targetItem)) { continue; }
- float particleAngle = item.body.Rotation + MathHelper.ToRadians(BarrelRotation) + ((item.body.Dir > 0.0f) ? 0.0f : MathHelper.Pi);
- emitter.Emit(deltaTime, particlePos, item.CurrentHull, particleAngle + MathHelper.Pi, -particleAngle + MathHelper.Pi);
+ EmitParticle(emitter, deltaTime, pickedPosition, targetItem.Submarine);
}
}
+
+ private void EmitParticle(ParticleEmitter emitter, float deltaTime, Vector2 simPosition, Submarine targetSub)
+ {
+ Vector2 particlePos = ConvertUnits.ToDisplayUnits(simPosition);
+ if (targetSub != null) { particlePos += targetSub.DrawPosition; }
+ float particleAngle = item.body.Rotation + MathHelper.ToRadians(BarrelRotation) + ((item.body.Dir > 0.0f) ? 0.0f : MathHelper.Pi);
+ emitter.Emit(deltaTime, particlePos, item.CurrentHull, particleAngle + MathHelper.Pi, -particleAngle + MathHelper.Pi,
+ tracerPoints: new Tuple(item.WorldPosition + TransformedBarrelPos, particlePos));
+ }
+
#if DEBUG
public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1, Color? overrideColor = null)
{
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Repairable.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Repairable.cs
index 5c790d576..e86923ef7 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Repairable.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Repairable.cs
@@ -299,6 +299,8 @@ namespace Barotrauma.Items.Components
public override void DrawHUD(SpriteBatch spriteBatch, Character character)
{
+ base.DrawHUD(spriteBatch, character);
+
IsActive = true;
float defaultMaxCondition = (item.MaxCondition / item.MaxRepairConditionMultiplier);
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Rope.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Rope.cs
index c6b5417c9..32a3d1528 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Rope.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Rope.cs
@@ -52,9 +52,11 @@ namespace Barotrauma.Items.Components
Vector2 sourcePos = GetSourcePos();
+ //need to double the size because this is essentially just the radius, we need the diameter
+ // + some extra margin to be on the safe side
return new Vector2(
Math.Abs(target.DrawPosition.X - sourcePos.X),
- Math.Abs(target.DrawPosition.Y - sourcePos.Y)) * 1.5f;
+ Math.Abs(target.DrawPosition.Y - sourcePos.Y)) * 2.2f;
}
}
@@ -122,7 +124,7 @@ namespace Barotrauma.Items.Components
{
if (turret.BarrelSprite != null)
{
- startPos += new Vector2((float)Math.Cos(turret.Rotation), (float)Math.Sin(turret.Rotation)) * turret.BarrelSprite.size.Y * turret.BarrelSprite.RelativeOrigin.Y * item.Scale * 0.9f;
+ startPos += new Vector2((float)Math.Cos(turret.Rotation), (float)Math.Sin(turret.Rotation)) * turret.BarrelSprite.size.Y * turret.BarrelSprite.RelativeOrigin.Y * turret.Item.Scale * BarrelLengthMultiplier;
}
startPos -= turret.GetRecoilOffset();
}
@@ -227,7 +229,7 @@ namespace Barotrauma.Items.Components
{
if (reelSoundChannel is not { IsPlaying: true })
{
- reelSoundChannel = SoundPlayer.PlaySound(sound.Sound, position, sound.Volume, sound.Range, ignoreMuffling: sound.IgnoreMuffling, freqMult: sound.GetRandomFrequencyMultiplier());
+ reelSoundChannel = SoundPlayer.PlaySound(sound, position);
if (reelSoundChannel != null)
{
reelSoundChannel.Looping = true;
@@ -242,7 +244,7 @@ namespace Barotrauma.Items.Components
}
else
{
- SoundPlayer.PlaySound(sound.Sound, position, sound.Volume, sound.Range, ignoreMuffling: sound.IgnoreMuffling, freqMult: sound.GetRandomFrequencyMultiplier());
+ SoundPlayer.PlaySound(sound, position);
}
}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/ButtonTerminal.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/ButtonTerminal.cs
index 80fbd44db..bea101f3b 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/ButtonTerminal.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/ButtonTerminal.cs
@@ -3,7 +3,6 @@ using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System;
using System.Linq;
-using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
@@ -16,7 +15,7 @@ namespace Barotrauma.Items.Components
partial void InitProjSpecific(ContentXElement element)
{
- terminalButtonStyles = new string[RequiredSignalCount];
+ terminalButtonStyles = new string[requiredSignalCount];
int i = 0;
foreach (var childElement in element.GetChildElements("TerminalButton"))
{
@@ -38,11 +37,11 @@ namespace Barotrauma.Items.Components
};
paddedFrame.OnAddedToGUIUpdateList += (component) =>
{
- bool buttonsEnabled = AllowUsingButtons;
- foreach (var child in component.Children)
+ bool buttonsEnabled = IsActivated;
+ foreach (GUIComponent child in component.Children)
{
- if (!(child is GUIButton)) { continue; }
- if (!(child.UserData is int)) { continue; }
+ if (child is not GUIButton) { continue; }
+ if (child.UserData is not int) { continue; }
child.Enabled = buttonsEnabled;
child.Children.ForEach(c => c.Enabled = buttonsEnabled);
}
@@ -59,7 +58,7 @@ namespace Barotrauma.Items.Components
containerIndicator.OverrideState = itemsContained ? GUIComponent.ComponentState.Selected : GUIComponent.ComponentState.None;
};
- float x = 1.0f / (1 + RequiredSignalCount);
+ float x = 1.0f / (1 + requiredSignalCount);
float y = Math.Min((x * paddedFrame.Rect.Width) / paddedFrame.Rect.Height, 0.5f);
Vector2 relativeSize = new Vector2(x, y);
@@ -69,7 +68,7 @@ namespace Barotrauma.Items.Components
containerIndicator = new GUIImage(new RectTransform(new Vector2(0.5f, 0.5f * (1.0f - y)), containerSection.RectTransform, anchor: Anchor.BottomCenter),
style: "IndicatorLightRed", scaleToFit: true);
- for (int i = 0; i < RequiredSignalCount; i++)
+ for (int i = 0; i < requiredSignalCount; i++)
{
var button = new GUIButton(new RectTransform(relativeSize, paddedFrame.RectTransform), style: null)
{
@@ -111,7 +110,7 @@ namespace Barotrauma.Items.Components
public void ClientEventRead(IReadMessage msg, float sendingTime)
{
- SendSignal(msg.ReadRangedInteger(0, Signals.Length - 1), sender: null, isServerMessage: true);
+ SendSignal(msg.ReadRangedInteger(0, Signals.Length - 1), sender: null, ignoreState: true);
}
}
}
\ No newline at end of file
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/CircuitBox.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/CircuitBox.cs
index 329545f2d..9d8ba4214 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/CircuitBox.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/CircuitBox.cs
@@ -1,4 +1,4 @@
-#nullable enable
+#nullable enable
using System;
using System.Collections.Generic;
@@ -129,7 +129,7 @@ namespace Barotrauma.Items.Components
public void RemoveComponents(IReadOnlyCollection node)
{
- if (Locked) { return; }
+ if (IsLocked()) { return; }
var ids = node.Select(static n => n.ID).ToImmutableArray();
if (GameMain.NetworkMember is null)
@@ -146,7 +146,7 @@ namespace Barotrauma.Items.Components
public void AddWire(CircuitBoxConnection one, CircuitBoxConnection two)
{
- if (Locked) { return; }
+ if (IsLocked()) { return; }
if (GameMain.NetworkMember is null)
{
Connect(one, two, static delegate { }, CircuitBoxWire.SelectedWirePrefab);
@@ -160,7 +160,7 @@ namespace Barotrauma.Items.Components
public void RemoveWires(IReadOnlyCollection wires)
{
- if (Locked) { return; }
+ if (IsLocked()) { return; }
var ids = wires.Select(static w => w.ID).ToImmutableArray();
if (GameMain.NetworkMember is null)
{
@@ -230,7 +230,7 @@ namespace Barotrauma.Items.Components
public void MoveComponent(Vector2 moveAmount, IReadOnlyCollection moveables)
{
- if (Locked) { return; }
+ if (IsLocked()) { return; }
var ids = ImmutableArray.CreateBuilder();
var ios = ImmutableArray.CreateBuilder();
var labelIds = ImmutableArray.CreateBuilder();
@@ -265,7 +265,7 @@ namespace Barotrauma.Items.Components
public void AddComponent(ItemPrefab prefab, Vector2 pos)
{
- if (Locked) { return; }
+ if (IsLocked()) { return; }
if (GameMain.NetworkMember is null)
{
ItemPrefab resource;
@@ -292,7 +292,7 @@ namespace Barotrauma.Items.Components
public void RenameLabel(CircuitBoxLabelNode label, Color color, NetLimitedString header, NetLimitedString body)
{
- if (Locked) { return; }
+ if (IsLocked()) { return; }
if (GameMain.NetworkMember is null)
{
label.EditText(header, body);
@@ -316,7 +316,7 @@ namespace Barotrauma.Items.Components
public void ResizeNode(CircuitBoxNode node, CircuitBoxResizeDirection dir, Vector2 amount)
{
- if (Locked) { return; }
+ if (IsLocked()) { return; }
var resize = node.ResizeBy(dir, amount);
if (GameMain.NetworkMember is null)
{
@@ -341,7 +341,7 @@ namespace Barotrauma.Items.Components
public void AddLabel(Vector2 pos)
{
- if (Locked) { return; }
+ if (IsLocked()) { return; }
if (GameMain.NetworkMember is null)
{
AddLabelInternal(ICircuitBoxIdentifiable.FindFreeID(Labels), GUIStyle.Blue, pos, CircuitBoxLabelNode.DefaultHeaderText, NetLimitedString.Empty);
@@ -353,7 +353,7 @@ namespace Barotrauma.Items.Components
public void RemoveLabel(IReadOnlyCollection labels)
{
- if (Locked) { return; }
+ if (IsLocked()) { return; }
if (!labels.Any()) { return; }
var ids = labels.Select(static n => n.ID).ToImmutableArray();
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/CustomInterface.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/CustomInterface.cs
index dc5b84592..2370fc843 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/CustomInterface.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/CustomInterface.cs
@@ -12,7 +12,9 @@ namespace Barotrauma.Items.Components
private readonly List uiElements = new List();
private GUILayoutGroup uiElementContainer;
- private bool readingNetworkEvent;
+ private bool suppressNetworkEvents;
+
+ private GUIComponent insufficientPowerWarning;
private Point ElementMaxSize => new Point(uiElementContainer.Rect.Width, (int)(65 * GUI.yScale));
@@ -40,7 +42,7 @@ namespace Barotrauma.Items.Components
float elementSize = Math.Min(1.0f / visibleElements.Count(), 1);
foreach (CustomInterfaceElement ciElement in visibleElements)
{
- if (ciElement.HasPropertyName)
+ if (ciElement.InputType is CustomInterfaceElement.InputTypeOption.Number or CustomInterfaceElement.InputTypeOption.Text)
{
var layoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, elementSize), uiElementContainer.RectTransform), isHorizontal: true)
{
@@ -49,7 +51,7 @@ namespace Barotrauma.Items.Components
};
new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), layoutGroup.RectTransform),
TextManager.Get(ciElement.Label).Fallback(ciElement.Label));
- if (!ciElement.IsNumberInput)
+ if (ciElement.InputType is CustomInterfaceElement.InputTypeOption.Text)
{
var textBox = new GUITextBox(new RectTransform(new Vector2(0.5f, 1.0f), layoutGroup.RectTransform), ciElement.Signal, style: "GUITextBoxNoIcon")
{
@@ -68,7 +70,7 @@ namespace Barotrauma.Items.Components
}
else
{
- item.CreateClientEvent(this);
+ CreateClientEventWithCorrectionDelay();
}
};
@@ -98,13 +100,10 @@ namespace Barotrauma.Items.Components
ValueStep = numberInputStep,
OnValueChanged = (ni) =>
{
- if (GameMain.Client == null)
+ ValueChanged(ni.UserData as CustomInterfaceElement, ni.FloatValue);
+ if (!suppressNetworkEvents && GameMain.Client != null)
{
- ValueChanged(ni.UserData as CustomInterfaceElement, ni.FloatValue);
- }
- else if (!readingNetworkEvent)
- {
- item.CreateClientEvent(this);
+ CreateClientEventWithCorrectionDelay();
}
}
};
@@ -124,13 +123,10 @@ namespace Barotrauma.Items.Components
ValueStep = numberInputStep,
OnValueChanged = (ni) =>
{
- if (GameMain.Client == null)
+ ValueChanged(ni.UserData as CustomInterfaceElement, ni.IntValue);
+ if (!suppressNetworkEvents && GameMain.Client != null)
{
- ValueChanged(ni.UserData as CustomInterfaceElement, ni.IntValue);
- }
- else if (!readingNetworkEvent)
- {
- item.CreateClientEvent(this);
+ CreateClientEventWithCorrectionDelay();
}
}
};
@@ -149,7 +145,7 @@ namespace Barotrauma.Items.Components
}
}
}
- else if (ciElement.ContinuousSignal)
+ else if (ciElement.InputType is CustomInterfaceElement.InputTypeOption.TickBox)
{
var tickBox = new GUITickBox(new RectTransform(new Vector2(1.0f, elementSize), uiElementContainer.RectTransform)
{
@@ -160,13 +156,10 @@ namespace Barotrauma.Items.Components
};
tickBox.OnSelected += (tBox) =>
{
- if (GameMain.Client == null)
+ TickBoxToggled(tBox.UserData as CustomInterfaceElement, tBox.Selected);
+ if (!suppressNetworkEvents && GameMain.Client != null)
{
- TickBoxToggled(tBox.UserData as CustomInterfaceElement, tBox.Selected);
- }
- else if (!readingNetworkEvent)
- {
- item.CreateClientEvent(this);
+ CreateClientEventWithCorrectionDelay();
}
return true;
};
@@ -175,7 +168,7 @@ namespace Barotrauma.Items.Components
tickBox.RectTransform.MaxSize = new Point(int.MaxValue, int.MaxValue);
uiElements.Add(tickBox);
}
- else
+ else if (ciElement.InputType is CustomInterfaceElement.InputTypeOption.Button)
{
var btn = new GUIButton(new RectTransform(new Vector2(1.0f, elementSize), uiElementContainer.RectTransform),
TextManager.Get(ciElement.Label).Fallback(ciElement.Label), style: "DeviceButton")
@@ -189,8 +182,10 @@ namespace Barotrauma.Items.Components
{
ButtonClicked(btnElement);
}
- else if (!readingNetworkEvent)
+ else if (!suppressNetworkEvents && GameMain.Client != null)
{
+ //don't use CreateClientEventWithCorrectionDelay here, because buttons have no state,
+ //which means we don't need to worry about server updates interfering with client-side changes to the values in the interface
item.CreateClientEvent(this, new EventData(btnElement));
}
return true;
@@ -203,6 +198,22 @@ namespace Barotrauma.Items.Components
uiElements.Add(btn);
}
}
+
+ if (ShowInsufficientPowerWarning)
+ {
+ insufficientPowerWarning = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.1f), GuiFrame.RectTransform, Anchor.BottomCenter, Pivot.TopCenter) { MinSize = new Point(0, GUI.IntScale(30)) },
+ TextManager.Get("SteeringNoPowerTip"), font: GUIStyle.Font, wrap: true, style: "GUIToolTip", textAlignment: Alignment.Center)
+ {
+ AutoScaleHorizontal = true,
+ Visible = false
+ };
+ }
+
+ void CreateClientEventWithCorrectionDelay()
+ {
+ item.CreateClientEvent(this);
+ correctionTimer = CorrectionDelay;
+ }
}
public override void CreateEditingHUD(SerializableEntityEditor editor)
@@ -253,7 +264,20 @@ namespace Barotrauma.Items.Components
{
if (uiElement.UserData is not CustomInterfaceElement element) { continue; }
bool visible = Screen.Selected == GameMain.SubEditorScreen || element.StatusEffects.Any() || element.HasPropertyName || (element.Connection != null && element.Connection.Wires.Count > 0);
- if (visible) { visibleElementCount++; }
+ if (visible)
+ {
+ visibleElementCount++;
+ if (element.GetValueInterval > 0.0f && correctionTimer <= 0.0f)
+ {
+ element.GetValueTimer -= deltaTime;
+ if (element.GetValueTimer <= 0.0f)
+ {
+ SetSignalToPropertyValue(element);
+ UpdateSignalProjSpecific(uiElement);
+ element.GetValueTimer = element.GetValueInterval;
+ }
+ }
+ }
if (uiElement.Visible != visible)
{
uiElement.Visible = visible;
@@ -274,6 +298,11 @@ namespace Barotrauma.Items.Components
GuiFrame.Visible = visibleElementCount > 0;
uiElementContainer.Recalculate();
}
+
+ if (insufficientPowerWarning != null)
+ {
+ insufficientPowerWarning.Visible = item.GetComponents().Any(p => p.PowerConsumption > 0.0f && p.Voltage < p.MinVoltage);
+ }
}
partial void UpdateLabelsProjSpecific()
@@ -300,7 +329,7 @@ namespace Barotrauma.Items.Components
LocalizedString CreateLabelText(int elementIndex)
{
- var label = customInterfaceElementList[elementIndex].Label;
+ string label = customInterfaceElementList[elementIndex].Label;
return string.IsNullOrWhiteSpace(label) ?
TextManager.GetWithVariable("connection.signaloutx", "[num]", (elementIndex + 1).ToString()) :
TextManager.Get(label).Fallback(label);
@@ -336,22 +365,43 @@ namespace Barotrauma.Items.Components
if (signals == null) { return; }
for (int i = 0; i < signals.Length && i < uiElements.Count; i++)
{
- string signal = customInterfaceElementList[i].Signal;
- if (uiElements[i] is GUITextBox tb)
+ UpdateSignalProjSpecific(uiElements[i]);
+ }
+ }
+
+ private void UpdateSignalProjSpecific(GUIComponent uiElement)
+ {
+ if (uiElement.UserData is not CustomInterfaceElement element) { return; }
+
+ suppressNetworkEvents = true;
+
+ string signal = element.Signal;
+ if (uiElement is GUITextBox tb)
+ {
+ tb.Text = Screen.Selected is { IsEditor: true } ?
+ signal :
+ TextManager.Get(signal).Fallback(signal).Value;
+ }
+ else if (uiElement is GUINumberInput ni)
+ {
+ if (ni.InputType == NumberType.Int)
{
- tb.Text = Screen.Selected is { IsEditor: true } ?
- signal :
- TextManager.Get(signal).Fallback(signal).Value;
- }
- else if (uiElements[i] is GUINumberInput ni)
- {
- if (ni.InputType == NumberType.Int)
+ if (int.TryParse(signal, out int value))
{
- int.TryParse(signal, out int value);
ni.IntValue = value;
}
+ else if (float.TryParse(signal, out float floatValue))
+ {
+ ni.IntValue = (int)MathF.Round(floatValue);
+ }
}
}
+ else if (uiElement is GUITickBox tickBox)
+ {
+ tickBox.Selected = signal.Equals("true", StringComparison.OrdinalIgnoreCase);
+ }
+
+ suppressNetworkEvents = false;
}
public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null)
@@ -360,14 +410,9 @@ namespace Barotrauma.Items.Components
for (int i = 0; i < customInterfaceElementList.Count; i++)
{
var element = customInterfaceElementList[i];
- if (element.HasPropertyName)
+ switch (element.InputType)
{
- if (!element.IsNumberInput)
- {
- msg.WriteString(((GUITextBox)uiElements[i]).Text);
- }
- else
- {
+ case CustomInterfaceElement.InputTypeOption.Number:
switch (element.NumberType)
{
case NumberType.Float:
@@ -378,59 +423,82 @@ namespace Barotrauma.Items.Components
msg.WriteString(((GUINumberInput)uiElements[i]).IntValue.ToString());
break;
}
- }
- }
- else if (element.ContinuousSignal)
- {
- msg.WriteBoolean(((GUITickBox)uiElements[i]).Selected);
- }
- else
- {
- msg.WriteBoolean(extraData is Item.ComponentStateEventData { ComponentData: EventData eventData } && eventData.BtnElement == customInterfaceElementList[i]);
+ break;
+ case CustomInterfaceElement.InputTypeOption.Text:
+ msg.WriteString(((GUITextBox)uiElements[i]).Text);
+ break;
+ case CustomInterfaceElement.InputTypeOption.TickBox:
+ msg.WriteBoolean(((GUITickBox)uiElements[i]).Selected);
+ break;
+ case CustomInterfaceElement.InputTypeOption.Button:
+ msg.WriteBoolean(extraData is Item.ComponentStateEventData { ComponentData: EventData eventData } && eventData.BtnElement == customInterfaceElementList[i]);
+ break;
}
}
}
public void ClientEventRead(IReadMessage msg, float sendingTime)
{
- readingNetworkEvent = true;
+ int msgStartPos = msg.BitPosition;
+ suppressNetworkEvents = true;
try
{
+ string[] stringValues = new string[customInterfaceElementList.Count];
+ bool[] boolValues = new bool[customInterfaceElementList.Count];
for (int i = 0; i < customInterfaceElementList.Count; i++)
{
var element = customInterfaceElementList[i];
- if (element.HasPropertyName)
+ switch (element.InputType)
{
- string newValue = msg.ReadString();
- if (!element.IsNumberInput)
- {
- TextChanged(element, newValue);
- }
- else
- {
+ case CustomInterfaceElement.InputTypeOption.Number:
+ case CustomInterfaceElement.InputTypeOption.Text:
+ stringValues[i] = msg.ReadString();
+ break;
+ case CustomInterfaceElement.InputTypeOption.TickBox:
+ case CustomInterfaceElement.InputTypeOption.Button:
+ boolValues[i] = msg.ReadBoolean();
+ break;
+ }
+ }
+
+ if (correctionTimer > 0.0f)
+ {
+ int msgLength = msg.BitPosition - msgStartPos;
+ msg.BitPosition = msgStartPos;
+ StartDelayedCorrection(msg.ExtractBits(msgLength), sendingTime);
+ return;
+ }
+
+ for (int i = 0; i < customInterfaceElementList.Count; i++)
+ {
+ var element = customInterfaceElementList[i];
+ switch (element.InputType)
+ {
+ case CustomInterfaceElement.InputTypeOption.Number:
switch (element.NumberType)
{
- case NumberType.Int when int.TryParse(newValue, out int value):
+ case NumberType.Int when int.TryParse(stringValues[i], out int value):
ValueChanged(element, value);
break;
- case NumberType.Float when TryParseFloatInvariantCulture(newValue, out float value):
+ case NumberType.Float when TryParseFloatInvariantCulture(stringValues[i], out float value):
ValueChanged(element, value);
break;
}
- }
- }
- else
- {
- bool elementState = msg.ReadBoolean();
- if (element.ContinuousSignal)
- {
- ((GUITickBox)uiElements[i]).Selected = elementState;
- TickBoxToggled(element, elementState);
- }
- else if (elementState)
- {
- ButtonClicked(element);
- }
+ break;
+ case CustomInterfaceElement.InputTypeOption.Text:
+ TextChanged(element, stringValues[i]);
+ break;
+ case CustomInterfaceElement.InputTypeOption.TickBox:
+ bool tickBoxState = boolValues[i];
+ ((GUITickBox)uiElements[i]).Selected = tickBoxState;
+ TickBoxToggled(element, tickBoxState);
+ break;
+ case CustomInterfaceElement.InputTypeOption.Button:
+ if (boolValues[i])
+ {
+ ButtonClicked(element);
+ }
+ break;
}
}
@@ -438,7 +506,7 @@ namespace Barotrauma.Items.Components
}
finally
{
- readingNetworkEvent = false;
+ suppressNetworkEvents = false;
}
}
}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Terminal.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Terminal.cs
index 21cad1ace..f890605ae 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Terminal.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Terminal.cs
@@ -1,6 +1,7 @@
using Barotrauma.Extensions;
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
+using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
@@ -21,13 +22,16 @@ namespace Barotrauma.Items.Components
private GUIListBox historyBox;
private GUITextBlock fillerBlock;
private GUITextBox inputBox;
+ private GUILayoutGroup layoutGroup;
private bool shouldSelectInputBox;
+ private readonly List inputElements = new List();
+
partial void InitProjSpecific(XElement element)
{
float marginMultiplier = element.GetAttributeFloat("marginmultiplier", 1.0f);
- var layoutGroup = new GUILayoutGroup(new RectTransform(GuiFrame.Rect.Size - GUIStyle.ItemFrameMargin.Multiply(marginMultiplier), GuiFrame.RectTransform, Anchor.Center) { AbsoluteOffset = GUIStyle.ItemFrameOffset.Multiply(marginMultiplier) })
+ layoutGroup = new GUILayoutGroup(new RectTransform(GuiFrame.Rect.Size - GUIStyle.ItemFrameMargin.Multiply(marginMultiplier), GuiFrame.RectTransform, Anchor.Center) { AbsoluteOffset = GUIStyle.ItemFrameOffset.Multiply(marginMultiplier) })
{
ChildAnchor = Anchor.TopCenter,
RelativeSpacing = 0.02f,
@@ -39,43 +43,53 @@ namespace Barotrauma.Items.Components
AutoHideScrollBar = this.AutoHideScrollbar
};
- if (!Readonly)
+ inputElements.Add(CreateFillerBlock());
+ inputElements.Add(new GUIFrame(new RectTransform(new Vector2(0.9f, 0.01f), layoutGroup.RectTransform), style: "HorizontalLine"));
+
+ inputBox = new GUITextBox(new RectTransform(new Vector2(1, .1f), layoutGroup.RectTransform), textColor: TextColor)
{
- CreateFillerBlock();
-
- new GUIFrame(new RectTransform(new Vector2(0.9f, 0.01f), layoutGroup.RectTransform), style: "HorizontalLine");
-
- inputBox = new GUITextBox(new RectTransform(new Vector2(1, .1f), layoutGroup.RectTransform), textColor: TextColor)
+ MaxTextLength = MaxMessageLength,
+ OverflowClip = true,
+ OnEnterPressed = (GUITextBox textBox, string text) =>
{
- MaxTextLength = MaxMessageLength,
- OverflowClip = true,
- OnEnterPressed = (GUITextBox textBox, string text) =>
+ if (GameMain.NetworkMember == null)
{
- if (GameMain.NetworkMember == null)
- {
- SendOutput(text);
- }
- else
- {
- item.CreateClientEvent(this, new ClientEventData(text));
- }
- textBox.Text = string.Empty;
- return true;
+ SendOutput(text);
}
- };
- }
+ else
+ {
+ item.CreateClientEvent(this, new ClientEventData(text));
+ }
+ textBox.Text = string.Empty;
+ return true;
+ }
+ };
+ inputElements.Add(inputBox);
+ RefreshInputElements();
+ }
- layoutGroup.Recalculate();
+ ///
+ /// Refreshes the visibility of the input box and the layout of the UI depending on whether the terminal is readonly or not.
+ ///
+ private void RefreshInputElements()
+ {
+ foreach (var inputElement in inputElements)
+ {
+ inputElement.Visible = !_readonly;
+ inputElement.IgnoreLayoutGroups = !inputElement.Visible;
+ }
+ layoutGroup?.Recalculate();
}
// Create fillerBlock to cover historyBox so new values appear at the bottom of historyBox
// This could be removed if GUIListBox supported aligning its children
- public void CreateFillerBlock()
+ public GUIComponent CreateFillerBlock()
{
fillerBlock = new GUITextBlock(new RectTransform(new Vector2(1, 1), historyBox.Content.RectTransform, anchor: Anchor.TopCenter), string.Empty)
{
CanBeFocused = false
};
+ return fillerBlock;
}
private void SendOutput(string input)
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/WifiComponent.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/WifiComponent.cs
index e0e8ebd4a..7bbd26ea0 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/WifiComponent.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/WifiComponent.cs
@@ -21,9 +21,14 @@ namespace Barotrauma.Items.Components
ShapeExtensions.DrawCircle(spriteBatch, pos, range, 32, Color.Cyan * 0.5f, 3);
}
+ public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null)
+ {
+ SharedEventWrite(msg);
+ }
+
public void ClientEventRead(IReadMessage msg, float sendingTime)
{
- Channel = msg.ReadRangedInteger(MinChannel, MaxChannel);
+ SharedEventRead(msg);
}
}
}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Wire.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Wire.cs
index 9bafa3d1a..831a76783 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Wire.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Wire.cs
@@ -106,13 +106,6 @@ namespace Barotrauma.Items.Components
private static int? selectedNodeIndex;
private static int? highlightedNodeIndex;
- [Serialize(0.3f, IsPropertySaveable.No)]
- public float Width
- {
- get;
- set;
- }
-
public Vector2 DrawSize
{
get { return sectionExtents; }
@@ -199,6 +192,8 @@ namespace Barotrauma.Items.Components
return;
}
+ if (Width * wireSprite.size.Y * Screen.Selected.Cam.Zoom < 1.0f) { return; }
+
Vector2 drawOffset = GetDrawOffset() + offset;
float baseDepth = UseSpriteDepth ? item.SpriteDepth : wireSprite.Depth;
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/StatusHUD.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/StatusHUD.cs
index a47e99738..078f49ebd 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/StatusHUD.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/StatusHUD.cs
@@ -175,6 +175,8 @@ namespace Barotrauma.Items.Components
{
if (character == null) { return; }
+ base.DrawHUD(spriteBatch, character);
+
if (OverlayColor.A > 0)
{
GUIStyle.UIGlow.Draw(spriteBatch, new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight),
@@ -206,44 +208,51 @@ namespace Barotrauma.Items.Components
if (ThermalGoggles)
{
- spriteBatch.End();
- GameMain.LightManager.SolidColorEffect.Parameters["color"].SetValue(Color.Red.ToVector4() * (0.3f + MathF.Sin(thermalEffectState) * 0.05f));
- GameMain.LightManager.SolidColorEffect.CurrentTechnique = GameMain.LightManager.SolidColorEffect.Techniques["SolidColorBlur"];
- GameMain.LightManager.SolidColorEffect.Parameters["blurDistance"].SetValue(0.01f + MathF.Sin(thermalEffectState) * 0.005f);
- GameMain.LightManager.SolidColorEffect.CurrentTechnique.Passes[0].Apply();
- spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive, transformMatrix: Screen.Selected.Cam.Transform, effect: GameMain.LightManager.SolidColorEffect);
-
Entity refEntity = equipper;
if (!isEquippable || refEntity == null)
{
refEntity = item;
}
+ DrawThermalOverlay(spriteBatch, refEntity, character, OverlayColor, Range, thermalEffectState, ShowDeadCharacters);
- foreach (Character c in Character.CharacterList)
- {
- if (c == character || !c.Enabled || c.Removed || c.Params.HideInThermalGoggles) { continue; }
- if (!ShowDeadCharacters && c.IsDead) { continue; }
-
- float dist = Vector2.DistanceSquared(refEntity.WorldPosition, c.WorldPosition);
- if (dist > Range * Range) { continue; }
-
- Sprite pingCircle = GUIStyle.UIThermalGlow.Value.Sprite;
- foreach (Limb limb in c.AnimController.Limbs)
- {
- if (limb.Mass < 0.5f && limb != c.AnimController.MainLimb) { continue; }
- float noise1 = PerlinNoise.GetPerlin((thermalEffectState + limb.Params.ID + c.ID) * 0.01f, (thermalEffectState + limb.Params.ID + c.ID) * 0.02f);
- float noise2 = PerlinNoise.GetPerlin((thermalEffectState + limb.Params.ID + c.ID) * 0.01f, (thermalEffectState + limb.Params.ID + c.ID) * 0.008f);
- Vector2 spriteScale = ConvertUnits.ToDisplayUnits(limb.body.GetSize()) / pingCircle.size * (noise1 * 0.5f + 2f);
- Vector2 drawPos = new Vector2(limb.body.DrawPosition.X + (noise1 - 0.5f) * 100, -limb.body.DrawPosition.Y + (noise2 - 0.5f) * 100);
- pingCircle.Draw(spriteBatch, drawPos, 0.0f, scale: Math.Max(spriteScale.X, spriteScale.Y));
- }
- }
-
- spriteBatch.End();
- spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied);
}
}
+ public static void DrawThermalOverlay(SpriteBatch spriteBatch, Entity refEntity, Character user, Color overlayColor, float range, float effectState, bool showDeadCharacters)
+ {
+ spriteBatch.End();
+ float colorIntensityBase = 0.5f; //Multiplies the overlay color by this amount, the higher the value, the more bright/vibrant the color.
+ float colorIntensityVariance = 0.05f; //The variance of the pulse effect affecting the color's brightness/vibrance
+ GameMain.LightManager.SolidColorEffect.Parameters["color"].SetValue(overlayColor.ToVector4() * (colorIntensityBase + MathF.Sin(effectState) * colorIntensityVariance));
+ GameMain.LightManager.SolidColorEffect.CurrentTechnique = GameMain.LightManager.SolidColorEffect.Techniques["SolidColorBlur"];
+ GameMain.LightManager.SolidColorEffect.Parameters["blurDistance"].SetValue(0.01f + MathF.Sin(effectState) * 0.005f);
+ GameMain.LightManager.SolidColorEffect.CurrentTechnique.Passes[0].Apply();
+ spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive, transformMatrix: Screen.Selected.Cam.Transform, effect: GameMain.LightManager.SolidColorEffect);
+
+ foreach (Character c in Character.CharacterList)
+ {
+ if (c == user || !c.Enabled || c.Removed || c.Params.HideInThermalGoggles) { continue; }
+ if (!showDeadCharacters && c.IsDead) { continue; }
+
+ float dist = Vector2.DistanceSquared(refEntity.WorldPosition, c.WorldPosition);
+ if (dist > range * range) { continue; }
+
+ Sprite pingCircle = GUIStyle.UIThermalGlow.Value.Sprite;
+ foreach (Limb limb in c.AnimController.Limbs)
+ {
+ if (limb.Mass < 0.5f && limb != c.AnimController.MainLimb) { continue; }
+ float noise1 = PerlinNoise.GetPerlin((effectState + limb.Params.ID + c.ID) * 0.01f, (effectState + limb.Params.ID + c.ID) * 0.02f);
+ float noise2 = PerlinNoise.GetPerlin((effectState + limb.Params.ID + c.ID) * 0.01f, (effectState + limb.Params.ID + c.ID) * 0.008f);
+ Vector2 spriteScale = ConvertUnits.ToDisplayUnits(limb.body.GetSize()) / pingCircle.size * (noise1 * 0.5f + 2f);
+ Vector2 drawPos = new Vector2(limb.body.DrawPosition.X + (noise1 - 0.5f) * 100, -limb.body.DrawPosition.Y + (noise2 - 0.5f) * 100);
+ pingCircle.Draw(spriteBatch, drawPos, 0.0f, scale: Math.Max(spriteScale.X, spriteScale.Y));
+ }
+ }
+
+ spriteBatch.End();
+ spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied);
+ }
+
private void DrawCharacterInfo(SpriteBatch spriteBatch, Character target, float alpha = 1.0f)
{
Vector2 hudPos = GameMain.GameScreen.Cam.WorldToScreen(target.DrawPosition);
@@ -273,7 +282,7 @@ namespace Barotrauma.Items.Components
}
else
{
- if (!target.CustomInteractHUDText.IsNullOrEmpty() && target.AllowCustomInteract)
+ if (target.ShouldShowCustomInteractText)
{
texts.Add(target.CustomInteractHUDText);
textColors.Add(GUIStyle.Green);
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/TriggerComponent.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/TriggerComponent.cs
index 77fe1ba5a..aafc299b0 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/TriggerComponent.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/TriggerComponent.cs
@@ -1,12 +1,28 @@
using Barotrauma.Networking;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using System;
namespace Barotrauma.Items.Components
{
- partial class TriggerComponent : ItemComponent, IServerSerializable
+ partial class TriggerComponent : ItemComponent, IServerSerializable, IDrawableComponent
{
+ public Vector2 DrawSize =>
+ Vector2.One *
+ (Radius > 0.0f ? Radius * 2 : Math.Max(Width, Height));
+
+ public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1, Color? overrideColor = null)
+ {
+ if (editing)
+ {
+ PhysicsBody.DebugDraw(spriteBatch, Color.LightGray * 0.7f);
+ }
+ }
+
public void ClientEventRead(IReadMessage msg, float sendingTime)
{
CurrentForceFluctuation = msg.ReadRangedSingle(0.0f, 1.0f, 8);
}
+
}
}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Turret.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Turret.cs
index 53117dd8f..885af0a17 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Turret.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Turret.cs
@@ -91,18 +91,19 @@ namespace Barotrauma.Items.Components
{
get
{
- float size = Math.Max(transformedBarrelPos.X, transformedBarrelPos.Y);
- if (barrelSprite != null)
+ float size = Math.Max(transformedBarrelPos.X, transformedBarrelPos.Y);
+ if (railSprite != null && barrelSprite != null)
{
- if (railSprite != null)
- {
- size += Math.Max(Math.Max(barrelSprite.size.X, barrelSprite.size.Y), Math.Max(railSprite.size.X, railSprite.size.Y)) * item.Scale;
- }
- else
- {
- size += Math.Max(barrelSprite.size.X, barrelSprite.size.Y) * item.Scale;
- }
+ size += Math.Max(Math.Max(barrelSprite.size.X, barrelSprite.size.Y), Math.Max(railSprite.size.X, railSprite.size.Y)) * item.Scale;
}
+ else if (railSprite != null)
+ {
+ size += Math.Max(railSprite.size.X, railSprite.size.Y) * item.Scale;
+ }
+ else if (barrelSprite != null)
+ {
+ size += Math.Max(barrelSprite.size.X, barrelSprite.size.Y) * item.Scale;
+ }
return Vector2.One * size * 2;
}
}
@@ -227,14 +228,14 @@ namespace Barotrauma.Items.Components
{
if (moveSoundChannel == null && startMoveSound != null)
{
- moveSoundChannel = SoundPlayer.PlaySound(startMoveSound.Sound, item.WorldPosition, startMoveSound.Volume, startMoveSound.Range, ignoreMuffling: startMoveSound.IgnoreMuffling, freqMult: startMoveSound.GetRandomFrequencyMultiplier());
+ moveSoundChannel = SoundPlayer.PlaySound(startMoveSound, item.WorldPosition, hullGuess: item.CurrentHull);
}
else if (moveSoundChannel == null || !moveSoundChannel.IsPlaying)
{
if (moveSound != null)
{
moveSoundChannel?.FadeOutAndDispose();
- moveSoundChannel = SoundPlayer.PlaySound(moveSound.Sound, item.WorldPosition, moveSound.Volume, moveSound.Range, ignoreMuffling: moveSound.IgnoreMuffling, freqMult: moveSound.GetRandomFrequencyMultiplier());
+ moveSoundChannel = SoundPlayer.PlaySound(moveSound, item.WorldPosition, hullGuess: item.CurrentHull);
if (moveSoundChannel != null) { moveSoundChannel.Looping = true;}
}
}
@@ -246,7 +247,7 @@ namespace Barotrauma.Items.Components
if (endMoveSound != null && moveSoundChannel.Sound != endMoveSound.Sound)
{
moveSoundChannel.FadeOutAndDispose();
- moveSoundChannel = SoundPlayer.PlaySound(endMoveSound.Sound, item.WorldPosition, endMoveSound.Volume, endMoveSound.Range, ignoreMuffling: endMoveSound.IgnoreMuffling, freqMult: endMoveSound.GetRandomFrequencyMultiplier());
+ moveSoundChannel = SoundPlayer.PlaySound(endMoveSound, item.WorldPosition, hullGuess: item.CurrentHull);
if (moveSoundChannel != null) { moveSoundChannel.Looping = false; }
}
else if (!moveSoundChannel.IsPlaying)
@@ -275,7 +276,7 @@ namespace Barotrauma.Items.Components
{
if (chargeSound != null)
{
- chargeSoundChannel = SoundPlayer.PlaySound(chargeSound.Sound, item.WorldPosition, chargeSound.Volume, chargeSound.Range, ignoreMuffling: chargeSound.IgnoreMuffling, freqMult: chargeSound.GetRandomFrequencyMultiplier());
+ chargeSoundChannel = SoundPlayer.PlaySound(chargeSound, item.WorldPosition, hullGuess: item.CurrentHull);
if (chargeSoundChannel != null) { chargeSoundChannel.Looping = true; }
}
}
@@ -385,17 +386,20 @@ namespace Barotrauma.Items.Components
if (item.Condition > 0.0f || !HideBarrelWhenBroken)
{
- railSprite?.Draw(spriteBatch,
+ var currentRailSprite = item.Condition <= 0.0f && railSpriteBroken != null ? railSpriteBroken : railSprite;
+ var currentBarrelSprite = item.Condition <= 0.0f && barrelSpriteBroken != null ? barrelSpriteBroken : barrelSprite;
+
+ currentRailSprite?.Draw(spriteBatch,
drawPos,
overrideColor ?? item.SpriteColor,
Rotation + MathHelper.PiOver2, item.Scale,
- SpriteEffects.None, item.SpriteDepth + (railSprite.Depth - item.Sprite.Depth));
+ SpriteEffects.None, item.SpriteDepth + (currentRailSprite.Depth - item.Sprite.Depth));
- barrelSprite?.Draw(spriteBatch,
+ currentBarrelSprite?.Draw(spriteBatch,
drawPos - GetRecoilOffset() * item.Scale,
overrideColor ?? item.SpriteColor,
Rotation + MathHelper.PiOver2, item.Scale,
- SpriteEffects.None, item.SpriteDepth + (barrelSprite.Depth - item.Sprite.Depth));
+ SpriteEffects.None, item.SpriteDepth + (currentBarrelSprite.Depth - item.Sprite.Depth));
float chargeRatio = currentChargeTime / MaxChargeTime;
@@ -702,12 +706,14 @@ namespace Barotrauma.Items.Components
public override void DrawHUD(SpriteBatch spriteBatch, Character character)
{
+ base.DrawHUD(spriteBatch, character);
+
if (HudTint.A > 0)
{
GUI.DrawRectangle(spriteBatch, new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight),
new Color(HudTint.R, HudTint.G, HudTint.B) * (HudTint.A / 255.0f), true);
}
-
+
GetAvailablePower(out float batteryCharge, out float batteryCapacity);
List- availableAmmo = new List
- ();
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs
index d756c442c..0c73cf49e 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs
@@ -1359,7 +1359,7 @@ namespace Barotrauma
if (selectedInventory.GetItemAt(slotIndex)?.OwnInventory?.Container is { } container &&
container.Inventory.CanBePut(item))
{
- if (!container.AllowDragAndDrop || !container.AllowAccess)
+ if (!container.AllowDragAndDrop || !container.IsAccessible())
{
allowCombine = false;
}
@@ -1603,7 +1603,8 @@ namespace Barotrauma
shadowSprite.Draw(spriteBatch,
new Rectangle(itemPos.ToPoint() - new Point((iconSize / 2 - shadowPadding.X) * textDir - shadowSize.X * textOffset, iconSize / 2 + shadowPadding.Y), shadowSize), Color.Black * 0.8f);
- GUI.DrawString(spriteBatch, textPos + new Vector2(nameSize.X * textOffset, -iconSize / 2), DraggingItems.First().Name, Color.White);
+ var richString = RichString.Rich(DraggingItems.First().Name);
+ GUI.DrawStringWithColors(spriteBatch, textPos + new Vector2(nameSize.X * textOffset, -iconSize / 2), richString.SanitizedValue, Color.White, richString.RichTextData);
GUI.DrawString(spriteBatch, textPos + new Vector2(toolTipSize.X * textOffset, 0), toolTip,
color: toolTipColor,
font: GUIStyle.SmallFont);
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs
index b626f62d4..e70235e79 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs
@@ -130,7 +130,7 @@ namespace Barotrauma
}
}
- public float GetDrawDepth()
+ public override float GetDrawDepth()
{
return GetDrawDepth(SpriteDepth + DrawDepthOffset, Sprite);
}
@@ -287,7 +287,7 @@ namespace Barotrauma
}
else
{
- int padding = 100;
+ int padding = 0;
RectangleF boundingBox = GetTransformedQuad().BoundingAxisAlignedRectangle;
Vector2 min = new Vector2(-boundingBox.Width / 2 - padding, -boundingBox.Height / 2 - padding);
@@ -302,11 +302,11 @@ namespace Barotrauma
}
foreach (DecorativeSprite decorativeSprite in Prefab.DecorativeSprites)
{
- float scale = decorativeSprite.GetScale(spriteAnimState[decorativeSprite].RandomScaleFactor) * Scale;
- min.X = Math.Min(-decorativeSprite.Sprite.size.X * decorativeSprite.Sprite.RelativeOrigin.X * scale, min.X);
- min.Y = Math.Min(-decorativeSprite.Sprite.size.Y * (1.0f - decorativeSprite.Sprite.RelativeOrigin.Y) * scale, min.Y);
- max.X = Math.Max(decorativeSprite.Sprite.size.X * (1.0f - decorativeSprite.Sprite.RelativeOrigin.X) * scale, max.X);
- max.Y = Math.Max(decorativeSprite.Sprite.size.Y * decorativeSprite.Sprite.RelativeOrigin.Y * scale, max.Y);
+ Vector2 scale = decorativeSprite.GetScale(ref spriteAnimState[decorativeSprite].ScaleState, spriteAnimState[decorativeSprite].RandomScaleFactor) * Scale;
+ min.X = Math.Min(-decorativeSprite.Sprite.size.X * decorativeSprite.Sprite.RelativeOrigin.X * scale.X, min.X);
+ min.Y = Math.Min(-decorativeSprite.Sprite.size.Y * (1.0f - decorativeSprite.Sprite.RelativeOrigin.Y) * scale.Y, min.Y);
+ max.X = Math.Max(decorativeSprite.Sprite.size.X * (1.0f - decorativeSprite.Sprite.RelativeOrigin.X) * scale.X, max.X);
+ max.Y = Math.Max(decorativeSprite.Sprite.size.Y * decorativeSprite.Sprite.RelativeOrigin.Y * scale.Y, max.Y);
}
cachedVisibleExtents = extents = new Rectangle(min.ToPoint(), max.ToPoint());
}
@@ -316,6 +316,9 @@ namespace Barotrauma
if (worldPosition.X + extents.X > worldView.Right || worldPosition.X + extents.Width < worldView.X) { return false; }
if (worldPosition.Y + extents.Height < worldView.Y - worldView.Height || worldPosition.Y + extents.Y > worldView.Y) { return false; }
+ if (extents.Width * Screen.Selected.Cam.Zoom < 1.0f) { return false; }
+ if (extents.Height * Screen.Selected.Cam.Zoom < 1.0f) { return false; }
+
return true;
}
@@ -405,7 +408,7 @@ namespace Barotrauma
fadeInBrokenSprite.Sprite.effects ^= SpriteEffects;
}
- if (body == null)
+ if (body == null || body.BodyType == BodyType.Static)
{
if (Prefab.ResizeHorizontal || Prefab.ResizeVertical)
{
@@ -490,7 +493,7 @@ namespace Barotrauma
}
}
var head = holdable.Picker.AnimController.GetLimb(LimbType.Head);
- if (head != null)
+ if (head?.Sprite != null)
{
//ensure the holdable item is always drawn in front of the head no matter what the wearables or whatnot do with the sprite depths
depth =
@@ -523,8 +526,8 @@ namespace Barotrauma
Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState, spriteAnimState[decorativeSprite].RandomOffsetMultiplier, -RotationRad) * Scale;
if (flippedX && Prefab.CanSpriteFlipX) { offset.X = -offset.X; }
if (flippedY && Prefab.CanSpriteFlipY) { offset.Y = -offset.Y; }
- decorativeSprite.Sprite.Draw(spriteBatch, new Vector2(DrawPosition.X + offset.X, -(DrawPosition.Y + offset.Y)), color,
- rotation, decorativeSprite.GetScale(spriteAnimState[decorativeSprite].RandomScaleFactor) * Scale, activeSprite.effects,
+ decorativeSprite.Sprite.Draw(spriteBatch, new Vector2(DrawPosition.X + offset.X, -(DrawPosition.Y + offset.Y)), color, decorativeSprite.Sprite.Origin,
+ rotation, decorativeSprite.GetScale(ref spriteAnimState[decorativeSprite].ScaleState, spriteAnimState[decorativeSprite].RandomScaleFactor) * Scale, activeSprite.effects,
depth: depth + (decorativeSprite.Sprite.Depth - activeSprite.Depth));
}
}
@@ -673,14 +676,8 @@ namespace Barotrauma
origin.Y = -origin.Y + decorativeSprite.Sprite.size.Y;
spriteEffects |= SpriteEffects.FlipVertically;
}
- if (body != null)
- {
- var ca = MathF.Cos(-body.DrawRotation);
- var sa = MathF.Sin(-body.DrawRotation);
- offset = new Vector2(ca * offset.X + sa * offset.Y, -sa * offset.X + ca * offset.Y);
- }
decorativeSprite.Sprite.Draw(spriteBatch, new Vector2(drawPos.X + offset.X, -(drawPos.Y + offset.Y)), decorativeSpriteColor, origin,
- -rotation + spriteRotation, decorativeSprite.GetScale(spriteAnimState[decorativeSprite].RandomScaleFactor) * Scale, spriteEffects,
+ -rotation + spriteRotation, decorativeSprite.GetScale(ref spriteAnimState[decorativeSprite].ScaleState, spriteAnimState[decorativeSprite].RandomScaleFactor) * Scale, spriteEffects,
depth: depth + (decorativeSprite.Sprite.Depth - activeSprite.Depth));
}
}
@@ -795,6 +792,11 @@ namespace Barotrauma
}
}
}
+
+ foreach (var containedItem in ContainedItems)
+ {
+ containedItem.UpdateSpriteStates(deltaTime);
+ }
}
public override void UpdateEditing(Camera cam, float deltaTime)
@@ -1069,12 +1071,18 @@ namespace Barotrauma
foreach (RelatedItem relatedItem in requiredItems)
{
- //TODO: add to localization
var textBlock = new GUITextBlock(new RectTransform(new Point(listBox.Content.Rect.Width, heightScaled)),
- relatedItem.Type.ToString() + " required", font: GUIStyle.SmallFont)
+ TextManager.Get($"{relatedItem.Type}.required").Fallback($"{relatedItem.Type} required"), font: GUIStyle.SmallFont)
{
Padding = new Vector4(10.0f, 0.0f, 10.0f, 0.0f)
};
+
+ var tooltip = TextManager.Get($"{relatedItem.Type}.required.tooltip").Fallback(LocalizedString.EmptyString);
+ if (!tooltip.IsNullOrWhiteSpace())
+ {
+ textBlock.ToolTip = tooltip;
+ }
+
textBlock.RectTransform.IsFixedSize = true;
componentEditor.AddCustomContent(textBlock, 1);
@@ -1543,10 +1551,19 @@ namespace Barotrauma
debugInitialHudPositions.Clear();
foreach (ItemComponent ic in activeHUDs)
{
- if (ic.GuiFrame == null || ic.AllowUIOverlap || ic.GetLinkUIToComponent() != null) { continue; }
- if (!ignoreLocking && ic.LockGuiFramePosition) { continue; }
- //if the frame covers nearly all of the screen, don't trying to prevent overlaps because it'd fail anyway
- if (ic.GuiFrame.Rect.Width >= GameMain.GraphicsWidth * 0.9f && ic.GuiFrame.Rect.Height >= GameMain.GraphicsHeight * 0.9f) { continue; }
+ if (ic.GuiFrame == null || ic.GetLinkUIToComponent() != null) { continue; }
+
+ bool nearlyCoversScreen = ic.GuiFrame.Rect.Width >= GameMain.GraphicsWidth * 0.9f &&
+ ic.GuiFrame.Rect.Height >= GameMain.GraphicsHeight * 0.9f;
+
+ // when we are not using overlap prevention, we still need to clamp the frame to the screen area to
+ // prevent frames becoming inaccessible outside the screen for example after a resolution change
+ if (ic.AllowUIOverlap || (!ignoreLocking && ic.LockGuiFramePosition) || nearlyCoversScreen)
+ {
+ ic.GuiFrame.ClampToArea(new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight));
+ continue;
+ }
+
ic.GuiFrame.RectTransform.ScreenSpaceOffset = ic.GuiFrameOffset;
elementsToMove.Add(ic.GuiFrame);
debugInitialHudPositions.Add(ic.GuiFrame.Rect);
@@ -1744,7 +1761,7 @@ namespace Barotrauma
if (texts.Any() && !recreateHudTexts) { return texts; }
texts.Clear();
- string nameText = Name;
+ string nameText = RichString.Rich(Prefab.Name).SanitizedValue;
if (Prefab.Tags.Contains("identitycard") || Tags.Contains("despawncontainer"))
{
string[] readTags = Tags.Split(',');
@@ -2074,6 +2091,17 @@ namespace Barotrauma
}
}
break;
+ case EventType.SwapItem:
+ ushort newId = msg.ReadUInt16();
+ uint prefabUintId = msg.ReadUInt32();
+ ItemPrefab newPrefab = ItemPrefab.Prefabs.FirstOrDefault(p => p.UintIdentifier == prefabUintId);
+ if (newPrefab is null)
+ {
+ DebugConsole.ThrowError($"Error while reading {EventType.SwapItem} message: could not find an item prefab with the hash {prefabUintId}.");
+ break;
+ }
+ ReplaceFromNetwork(newPrefab, newId);
+ break;
default:
throw new Exception($"Malformed incoming item event: unsupported event type {eventType}");
}
@@ -2171,6 +2199,18 @@ namespace Barotrauma
}
}
+ //if the item is outside the level, but not in a sub, it implies the item is inside a sub server-side but the client failed to properly move it
+ // -> let's correct that by finding the correct sub
+ if (Level.IsPositionAboveLevel(WorldPosition) && Submarine == null)
+ {
+ var newSub = Submarine.FindContainingInLocalCoordinates(ConvertUnits.ToDisplayUnits(body.SimPosition), inflate: 0.0f);
+ if (newSub != null)
+ {
+ Submarine = newSub;
+ FindHull();
+ }
+ }
+
Vector2 displayPos = ConvertUnits.ToDisplayUnits(body.SimPosition);
rect.X = (int)(displayPos.X - rect.Width / 2.0f);
rect.Y = (int)(displayPos.Y + rect.Height / 2.0f);
@@ -2249,7 +2289,13 @@ namespace Barotrauma
if (!components.Contains(ic)) { return; }
var eventData = new ComponentStateEventData(ic, extraData);
- if (!ic.ValidateEventData(eventData)) { throw new Exception($"Component event creation failed: {typeof(T).Name}.{nameof(ItemComponent.ValidateEventData)} returned false"); }
+ if (!ic.ValidateEventData(eventData)) {
+ string errorMsg =
+ $"Client-side component event creation for the item \"{Prefab.Identifier}\" failed: {typeof(T).Name}.{nameof(ItemComponent.ValidateEventData)} returned false. " +
+ $"Data: {extraData?.GetType().ToString() ?? "null"}";
+ GameAnalyticsManager.AddErrorEventOnce($"Item.CreateClientEvent:ValidateEventData:{Prefab.Identifier}", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
+ throw new Exception(errorMsg);
+ }
GameMain.Client.CreateEntityEvent(this, eventData);
}
@@ -2329,15 +2375,15 @@ namespace Barotrauma
ownerSheetIndex = (x, y);
}
- bool tagsChanged = msg.ReadBoolean();
+ bool tagsChanged = msg.ReadBoolean();
string tags = "";
if (tagsChanged)
{
- HashSet addedTags = msg.ReadString().Split(',').ToIdentifiers().ToHashSet();
- HashSet removedTags = msg.ReadString().Split(',').ToIdentifiers().ToHashSet();
+ HashSet addedTags = msg.ReadString().ToIdentifiers().ToHashSet();
+ HashSet removedTags = msg.ReadString().ToIdentifiers().ToHashSet();
if (itemPrefab != null)
{
- tags = string.Join(',',itemPrefab.Tags.Where(t => !removedTags.Contains(t)).Concat(addedTags));
+ tags = string.Join(',', itemPrefab.Tags.Where(t => !removedTags.Contains(t)).Union(addedTags));
}
}
@@ -2455,12 +2501,24 @@ namespace Barotrauma
if (inventory != null)
{
- if (inventorySlotIndex >= 0 && inventorySlotIndex < 255 &&
- inventory.TryPutItem(item, inventorySlotIndex, false, false, null, false))
+ if (inventorySlotIndex is >= 0 and < 255 &&
+ !inventory.TryPutItem(item, inventorySlotIndex, allowSwapping: false, allowCombine: false, user: null, createNetworkEvent: false, ignoreCondition: true) &&
+ inventory.IsSlotEmpty(inventorySlotIndex))
{
- return item;
+ //If the item won't go nicely, force it to the slot. If the server says the item is in the slot, it should go in the slot.
+ //May happen e.g. when a character is configured to spawn with an item that won't normally go in its inventory slots.
+ inventory.ForceToSlot(item, index: inventorySlotIndex);
}
- inventory.TryPutItem(item, null, item.AllowedSlots, false);
+ else
+ {
+ inventory.TryPutItem(item, user: null, allowedSlots: item.AllowedSlots, createNetworkEvent: false);
+ }
+ item.SetTransform(inventory.Owner.SimPosition, 0.0f);
+ item.Submarine = inventory.Owner.Submarine;
+ if (inventory.Owner is Character { Enabled: false } && item.body != null)
+ {
+ item.body.Enabled = false;
+ }
}
return item;
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Gap.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Gap.cs
index 419253352..470094abf 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Map/Gap.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Gap.cs
@@ -20,7 +20,8 @@ namespace Barotrauma
public override bool IsVisible(Rectangle worldView)
{
- return Screen.Selected == GameMain.SubEditorScreen || GameMain.DebugDraw;
+ if (Screen.Selected != GameMain.SubEditorScreen && !GameMain.DebugDraw) { return false; }
+ return base.IsVisible(worldView);
}
public override void Draw(SpriteBatch sb, bool editing, bool back = true)
@@ -95,9 +96,13 @@ namespace Barotrauma
new Vector2(Math.Sign(targetHull.Rect.Center.X - rect.Center.X), 0.0f)
: new Vector2(0.0f, Math.Sign((rect.Y - rect.Height / 2.0f) - (targetHull.Rect.Y - targetHull.Rect.Height / 2.0f)));
- Vector2 arrowPos = new Vector2(WorldRect.Center.X, -(WorldRect.Y - WorldRect.Height / 2));
+ Vector2 arrowPos = new Vector2(WorldRect.Center.X, WorldRect.Y - WorldRect.Height / 2);
+ if (Submarine != null)
+ {
+ arrowPos += (Submarine.DrawPosition - Submarine.Position);
+ }
+ arrowPos.Y = -arrowPos.Y;
arrowPos += new Vector2(dir.X * (WorldRect.Width / 2), dir.Y * (WorldRect.Height / 2));
-
bool invalidDir = false;
if (dir == Vector2.Zero)
{
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Hull.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Hull.cs
index 5841d8dc6..c4fb4318d 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Map/Hull.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Hull.cs
@@ -81,16 +81,12 @@ namespace Barotrauma
public override bool IsVisible(Rectangle worldView)
{
if (BallastFlora != null) { return true; }
-
if (Screen.Selected != GameMain.SubEditorScreen && !GameMain.DebugDraw)
{
if (decals.Count == 0 && paintAmount < minimumPaintAmountToDraw) { return false; }
- Rectangle worldRect = WorldRect;
- if (worldRect.X > worldView.Right || worldRect.Right < worldView.X) { return false; }
- if (worldRect.Y < worldView.Y - worldView.Height || worldRect.Y - worldRect.Height > worldView.Y) { return false; }
}
- return true;
+ return base.IsVisible(worldView);
}
public override bool IsMouseOn(Vector2 position)
@@ -103,12 +99,31 @@ namespace Barotrauma
private GUIComponent CreateEditingHUD(bool inGame = false)
{
+ int heightScaled = GUI.IntScale(20);
editingHUD = new GUIFrame(new RectTransform(new Vector2(0.3f, 0.25f), GUI.Canvas, Anchor.CenterRight) { MinSize = new Point(400, 0) }) { UserData = this };
GUIListBox listBox = new GUIListBox(new RectTransform(new Vector2(0.95f, 0.8f), editingHUD.RectTransform, Anchor.Center), style: null)
{
CanTakeKeyBoardFocus = false
};
- new SerializableEntityEditor(listBox.Content.RectTransform, this, inGame, showName: true, titleFont: GUIStyle.LargeFont);
+ var hullEditor = new SerializableEntityEditor(listBox.Content.RectTransform, this, inGame, showName: true, titleFont: GUIStyle.LargeFont);
+
+ if (!inGame)
+ {
+ if (Linkable)
+ {
+ var linkText = new GUITextBlock(new RectTransform(new Point(editingHUD.Rect.Width, heightScaled), isFixedSize: true), TextManager.Get("HoldToLink"), font: GUIStyle.SmallFont);
+ var hullLinkText = new GUITextBlock(new RectTransform(new Point(editingHUD.Rect.Width, heightScaled), isFixedSize: true), TextManager.Get("hulllinkinfo"), font: GUIStyle.SmallFont);
+ var itemsText = new GUITextBlock(new RectTransform(new Point(editingHUD.Rect.Width, heightScaled), isFixedSize: true), TextManager.Get("AllowedLinks"), font: GUIStyle.SmallFont);
+ LocalizedString allowedItems = AllowedLinks.None() ? TextManager.Get("None") : string.Join(", ", AllowedLinks);
+ itemsText.Text = TextManager.AddPunctuation(':', itemsText.Text, allowedItems);
+ hullEditor.AddCustomContent(linkText, 1);
+ hullEditor.AddCustomContent(hullLinkText, 2);
+ hullEditor.AddCustomContent(itemsText, 3);
+ linkText.TextColor = GUIStyle.Orange;
+ hullLinkText.TextColor = GUIStyle.Orange;
+ itemsText.TextColor = GUIStyle.Orange;
+ }
+ }
PositionEditingHUD();
@@ -389,8 +404,8 @@ namespace Barotrauma
//GUI.DrawRectangle(spriteBatch, new Rectangle((int)fs.LastExtinguishPos.X, (int)-fs.LastExtinguishPos.Y, 5,5), Color.Yellow, true);
}
-
- GUI.DrawLine(spriteBatch, new Vector2(drawRect.X, -WorldSurface), new Vector2(drawRect.Right, -WorldSurface), Color.Cyan * 0.5f);
+ float worldSurface = surface + Submarine.DrawPosition.Y;
+ GUI.DrawLine(spriteBatch, new Vector2(drawRect.X, -worldSurface), new Vector2(drawRect.Right, -worldSurface), Color.Cyan * 0.5f);
for (int i = 0; i < waveY.Length - 1; i++)
{
GUI.DrawLine(spriteBatch,
@@ -563,8 +578,7 @@ namespace Barotrauma
corners[4] = new Vector3(x, bottom, 0.0f);
//bottom right
corners[5] = new Vector3(x + width, bottom, 0.0f);
-
- Vector2[] uvCoords = new Vector2[4];
+
for (int n = 0; n < 4; n++)
{
uvCoords[n] = Vector2.Transform(new Vector2(corners[n].X, -corners[n].Y), transform);
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/BackgroundCreatures/BackgroundCreature.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/BackgroundCreatures/BackgroundCreature.cs
index 7068a7ed6..cecbcef80 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/BackgroundCreatures/BackgroundCreature.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/BackgroundCreatures/BackgroundCreature.cs
@@ -26,7 +26,7 @@ namespace Barotrauma
private Vector3 velocity;
- private float depth;
+ public float Depth { get; private set; }
private float alpha = 1.0f;
@@ -42,6 +42,8 @@ namespace Barotrauma
Vector2 drawPosition;
+ private bool flippedHorizontally;
+
public Vector2[,] CurrentSpriteDeformation
{
get;
@@ -88,6 +90,8 @@ namespace Barotrauma
Rand.Range(-prefab.Speed, prefab.Speed, Rand.RandSync.ClientOnly),
Rand.Range(0.0f, prefab.WanderZAmount, Rand.RandSync.ClientOnly));
+ Depth = Rand.Range(prefab.MinDepth, prefab.MaxDepth, Rand.RandSync.ClientOnly);
+
checkWallsTimer = Rand.Range(0.0f, CheckWallsInterval, Rand.RandSync.ClientOnly);
foreach (var subElement in prefab.Config.Elements())
@@ -104,6 +108,7 @@ namespace Barotrauma
default:
continue;
}
+ int j = 0;
foreach (XElement animationElement in subElement.Elements())
{
SpriteDeformation deformation = null;
@@ -118,7 +123,21 @@ namespace Barotrauma
deformation = SpriteDeformation.Load(animationElement, prefab.Name);
if (deformation != null)
{
+ deformation.Params = Prefab.SpriteDeformations[j].Params;
uniqueSpriteDeformations.Add(deformation);
+ if (prefab.DeformableSprite != null)
+ {
+ if (deformation.Resolution.X > prefab.DeformableSprite.Subdivisions.X ||
+ deformation.Resolution.Y > prefab.DeformableSprite.Subdivisions.Y)
+ {
+ DebugConsole.AddWarning(
+ $"Potential error in background creature {Prefab.Identifier}: deformation {deformation.GetType()} has a larger resolution ({deformation.Resolution})"+
+ $" than the amount of subdivisions on the deformable sprite ({prefab.DeformableSprite.Subdivisions}). Should the sprite be subdivided further to make full use of the deformation?",
+ contentPackage: Prefab.ContentPackage);
+ }
+ }
+
+ j++;
}
}
if (deformation != null)
@@ -127,12 +146,14 @@ namespace Barotrauma
}
}
}
+
+ flashTimer = Rand.Range(0.0f, prefab.FlashInterval, Rand.RandSync.Unsynced);
}
public void Update(float deltaTime)
{
position += new Vector2(velocity.X, velocity.Y) * deltaTime;
- depth = MathHelper.Clamp(depth + velocity.Z * deltaTime, Prefab.MinDepth, Prefab.MaxDepth * 10);
+ Depth = MathHelper.Clamp(Depth + velocity.Z * deltaTime, Prefab.MinDepth, Prefab.MaxDepth);
if (Prefab.FlashInterval > 0.0f)
{
@@ -144,7 +165,7 @@ namespace Barotrauma
else
{
//value goes from 0 to 1 and back to 0 during the flash
- alpha = (float)Math.Sin(-flashTimer / Prefab.FlashDuration * MathHelper.Pi) * PerlinNoise.GetPerlin((float)Timing.TotalTime * 0.1f, (float)Timing.TotalTime * 0.2f);
+ alpha = (float)Math.Sin(-flashTimer / Prefab.FlashDuration * MathHelper.Pi) * PerlinNoise.GetPerlin((float)Timing.TotalTime, (float)Timing.TotalTime * 0.5f);
if (flashTimer < -Prefab.FlashDuration)
{
flashTimer = Prefab.FlashInterval;
@@ -228,7 +249,13 @@ namespace Barotrauma
velocity = Vector3.Lerp(velocity, new Vector3(Steering.X, Steering.Y, velocity.Z), deltaTime);
- UpdateDeformations(deltaTime);
+ //only flip if there's some horizontal movement speed (10% of the creature's speed)
+ //otherwise a creature swimming roughly up/down can flip around very frequently when the horizontal speed fluctuates around 0
+ if (Math.Abs(velocity.X) > Prefab.Speed * 0.1f)
+ {
+ flippedHorizontally = !Prefab.DisableFlipping && velocity.X < 0.0f;
+ }
+ UpdateDeformations(deltaTime, flippedHorizontally);
}
public void DrawLightSprite(SpriteBatch spriteBatch, Camera cam)
@@ -238,12 +265,17 @@ namespace Barotrauma
public void Draw(SpriteBatch spriteBatch, Camera cam)
{
- Draw(spriteBatch,
- cam,
- Prefab.Sprite,
- Prefab.DeformableSprite,
- CurrentSpriteDeformation,
- Color.Lerp(Color.White, Level.Loaded.BackgroundColor, depth / Math.Max(MaxDepth, Prefab.MaxDepth)) * alpha);
+ Color color =
+ Prefab.FadeOut ?
+ Color.Lerp(Color.White, Level.Loaded.BackgroundColor, Depth / Prefab.FadeOutDepth) * alpha :
+ Color.White * alpha;
+
+ Draw(spriteBatch,
+ cam,
+ Prefab.Sprite,
+ Prefab.DeformableSprite,
+ CurrentSpriteDeformation,
+ color);
}
private void Draw(SpriteBatch spriteBatch, Camera cam, Sprite sprite, DeformableSprite deformableSprite, Vector2[,] currentSpriteDeformation, Color color)
@@ -255,7 +287,7 @@ namespace Barotrauma
if (!Prefab.DisableRotation)
{
rotation = MathUtils.VectorToAngle(new Vector2(velocity.X, -velocity.Y));
- if (velocity.X < 0.0f) { rotation -= MathHelper.Pi; }
+ if (flippedHorizontally) { rotation -= MathHelper.Pi; }
}
drawPosition = GetDrawPosition(cam);
@@ -266,8 +298,8 @@ namespace Barotrauma
color,
rotation,
scale,
- Prefab.DisableFlipping || velocity.X > 0.0f ? SpriteEffects.None : SpriteEffects.FlipHorizontally,
- Math.Min(depth / MaxDepth, 1.0f));
+ flippedHorizontally ? SpriteEffects.FlipHorizontally : SpriteEffects.None,
+ Math.Min(Depth / MaxDepth, 1.0f));
if (deformableSprite != null)
{
@@ -280,29 +312,29 @@ namespace Barotrauma
deformableSprite.Reset();
}
deformableSprite?.Draw(cam,
- new Vector3(drawPosition.X, drawPosition.Y, Math.Min(depth / 10000.0f, 1.0f)),
+ new Vector3(drawPosition.X, drawPosition.Y, Math.Min(Depth / 10000.0f, 1.0f)),
deformableSprite.Origin,
rotation,
Vector2.One * scale,
color,
- mirror: Prefab.DisableFlipping || velocity.X <= 0.0f);
+ mirror: flippedHorizontally);
}
}
public Vector2 GetDrawPosition(Camera cam)
{
Vector2 drawPosition = WorldPosition;
- if (depth >= 0)
+ if (Depth >= 0)
{
Vector2 camOffset = drawPosition - cam.WorldViewCenter;
- drawPosition -= camOffset * depth / MaxDepth;
+ drawPosition -= camOffset * Depth / MaxDepth;
}
return drawPosition;
}
public float GetScale()
{
- return Math.Max(1.0f - depth / MaxDepth, 0.05f) * Prefab.Scale;
+ return Math.Max(1.0f - Depth / MaxDepth, 0.05f) * Prefab.Scale;
}
public Rectangle GetExtents(Camera cam)
@@ -328,7 +360,7 @@ namespace Barotrauma
}
}
- private void UpdateDeformations(float deltaTime)
+ private void UpdateDeformations(float deltaTime, bool flippedHorizontally)
{
foreach (SpriteDeformation deformation in uniqueSpriteDeformations)
{
@@ -336,11 +368,13 @@ namespace Barotrauma
}
if (spriteDeformations.Count > 0)
{
- CurrentSpriteDeformation = SpriteDeformation.GetDeformation(spriteDeformations, Prefab.DeformableSprite.Size);
+ CurrentSpriteDeformation = SpriteDeformation.GetDeformation(spriteDeformations, Prefab.DeformableSprite.Size,
+ flippedHorizontally: flippedHorizontally);
}
if (lightSpriteDeformations.Count > 0)
{
- CurrentLightSpriteDeformation = SpriteDeformation.GetDeformation(lightSpriteDeformations, Prefab.DeformableLightSprite.Size);
+ CurrentLightSpriteDeformation = SpriteDeformation.GetDeformation(lightSpriteDeformations, Prefab.DeformableLightSprite.Size,
+ flippedHorizontally: flippedHorizontally);
}
}
}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/BackgroundCreatures/BackgroundCreatureManager.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/BackgroundCreatures/BackgroundCreatureManager.cs
index 1bdf40355..d997412ac 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/BackgroundCreatures/BackgroundCreatureManager.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/BackgroundCreatures/BackgroundCreatureManager.cs
@@ -15,18 +15,19 @@ namespace Barotrauma
private float checkVisibleTimer;
- private readonly List prefabs = new List();
private readonly List creatures = new List();
- public BackgroundCreatureManager(IEnumerable files)
+ private readonly List visibleCreatures = new List();
+
+ public BackgroundCreatureManager()
{
- foreach(var file in files)
+ /*foreach(var file in files)
{
LoadConfig(file.Path);
- }
+ }*/
}
- public BackgroundCreatureManager(string path)
+ /*public BackgroundCreatureManager(string path)
{
DebugConsole.AddWarning($"Couldn't find any BackgroundCreaturePrefabs files, falling back to {path}");
LoadConfig(ContentPath.FromRaw(null, path));
@@ -42,35 +43,34 @@ namespace Barotrauma
if (mainElement.IsOverride())
{
mainElement = mainElement.FirstElement();
- prefabs.Clear();
+ Prefabs.Clear();
DebugConsole.NewMessage($"Overriding all background creatures with '{configPath}'", Color.MediumPurple);
}
- else if (prefabs.Any())
+ else if (Prefabs.Any())
{
DebugConsole.NewMessage($"Loading additional background creatures from file '{configPath}'");
}
foreach (var element in mainElement.Elements())
{
- prefabs.Add(new BackgroundCreaturePrefab(element));
+ Prefabs.Add(new BackgroundCreaturePrefab(element));
};
}
catch (Exception e)
{
DebugConsole.ThrowError(String.Format("Failed to load BackgroundCreatures from {0}", configPath), e);
}
- }
+ }*/
public void SpawnCreatures(Level level, int count, Vector2? position = null)
{
creatures.Clear();
- if (prefabs.Count == 0) { return; }
+ List availablePrefabs = new List(BackgroundCreaturePrefab.Prefabs.OrderBy(p => p.Identifier.Value));
+ if (availablePrefabs.Count == 0) { return; }
count = Math.Min(count, MaxCreatures);
- List availablePrefabs = new List(prefabs);
-
for (int i = 0; i < count; i++)
{
Vector2 pos = Vector2.Zero;
@@ -93,7 +93,7 @@ namespace Barotrauma
pos = (Vector2)position;
}
- var prefab = ToolBox.SelectWeightedRandom(availablePrefabs, availablePrefabs.Select(p => p.GetCommonness(level.GenerationParams)).ToList(), Rand.RandSync.ClientOnly);
+ var prefab = ToolBox.SelectWeightedRandom(availablePrefabs, availablePrefabs.Select(p => p.GetCommonness(level?.LevelData)).ToList(), Rand.RandSync.ClientOnly);
if (prefab == null) { break; }
int amount = Rand.Range(prefab.SwarmMin, prefab.SwarmMax + 1, Rand.RandSync.ClientOnly);
@@ -125,16 +125,27 @@ namespace Barotrauma
{
if (checkVisibleTimer < 0.0f)
{
+ visibleCreatures.Clear();
int margin = 500;
foreach (BackgroundCreature creature in creatures)
{
Rectangle extents = creature.GetExtents(cam);
- bool wasVisible = creature.Visible;
creature.Visible =
extents.Right >= cam.WorldView.X - margin &&
extents.X <= cam.WorldView.Right + margin &&
extents.Bottom >= cam.WorldView.Y - cam.WorldView.Height - margin &&
extents.Y <= cam.WorldView.Y + margin;
+ if (creature.Visible)
+ {
+ //insertion sort according to depth
+ int i = 0;
+ while (i < visibleCreatures.Count)
+ {
+ if (visibleCreatures[i].Depth < creature.Depth) { break; }
+ i++;
+ }
+ visibleCreatures.Insert(i, creature);
+ }
}
checkVisibleTimer = VisibilityCheckInterval;
@@ -144,27 +155,24 @@ namespace Barotrauma
checkVisibleTimer -= deltaTime;
}
- foreach (BackgroundCreature creature in creatures)
+ foreach (BackgroundCreature creature in visibleCreatures)
{
- if (!creature.Visible) { continue; }
creature.Update(deltaTime);
}
}
public void Draw(SpriteBatch spriteBatch, Camera cam)
{
- foreach (BackgroundCreature creature in creatures)
+ foreach (BackgroundCreature creature in visibleCreatures)
{
- if (!creature.Visible) { continue; }
creature.Draw(spriteBatch, cam);
}
}
public void DrawLights(SpriteBatch spriteBatch, Camera cam)
{
- foreach (BackgroundCreature creature in creatures)
+ foreach (BackgroundCreature creature in visibleCreatures)
{
- if (!creature.Visible) { continue; }
creature.DrawLightSprite(spriteBatch, cam);
}
}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/BackgroundCreatures/BackgroundCreaturePrefab.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/BackgroundCreatures/BackgroundCreaturePrefab.cs
index 7c9ef97f8..0ae0019ba 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/BackgroundCreatures/BackgroundCreaturePrefab.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/BackgroundCreatures/BackgroundCreaturePrefab.cs
@@ -1,65 +1,91 @@
-using System.Collections.Generic;
+using Barotrauma.SpriteDeformations;
+using System.Collections.Generic;
using System.Xml.Linq;
namespace Barotrauma
{
- class BackgroundCreaturePrefab
+ class BackgroundCreaturePrefab : Prefab, ISerializableEntity
{
- public readonly Sprite Sprite, LightSprite;
- public readonly DeformableSprite DeformableSprite, DeformableLightSprite;
+ public readonly static PrefabCollection Prefabs = new PrefabCollection();
- public readonly string Name;
+ public Sprite Sprite { get; private set; }
+ public Sprite LightSprite { get; private set; }
+ public DeformableSprite DeformableSprite { get; private set; }
+ public DeformableSprite DeformableLightSprite { get; private set; }
+
+ private readonly string name;
public readonly XElement Config;
- [Serialize(1.0f, IsPropertySaveable.Yes)]
+ [Serialize(1.0f, IsPropertySaveable.Yes), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1000.0f)]
public float Speed { get; private set; }
- [Serialize(0.0f, IsPropertySaveable.Yes)]
+ [Serialize(0.0f, IsPropertySaveable.Yes), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1000.0f, DecimalCount = 3)]
public float WanderAmount { get; private set; }
- [Serialize(0.0f, IsPropertySaveable.Yes)]
+ [Serialize(0.0f, IsPropertySaveable.Yes), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f, DecimalCount = 3)]
public float WanderZAmount { get; private set; }
- [Serialize(1, IsPropertySaveable.Yes)]
+ [Serialize(1, IsPropertySaveable.Yes), Editable(MinValueInt = 0, MaxValueInt = 1000)]
public int SwarmMin { get; private set; }
- [Serialize(1, IsPropertySaveable.Yes)]
+ [Serialize(1, IsPropertySaveable.Yes), Editable(MinValueInt = 0, MaxValueInt = 1000)]
public int SwarmMax { get; private set; }
- [Serialize(200.0f, IsPropertySaveable.Yes)]
+ [Serialize(200.0f, IsPropertySaveable.Yes), Editable(MinValueFloat = 0.0f, MaxValueFloat = 10000.0f)]
public float SwarmRadius { get; private set; }
- [Serialize(0.2f, IsPropertySaveable.Yes)]
+ [Serialize(0.2f, IsPropertySaveable.Yes), Editable(MinValueFloat = 0.0f, MaxValueFloat = 10.0f)]
public float SwarmCohesion { get; private set; }
- [Serialize(10.0f, IsPropertySaveable.Yes)]
+ [Serialize(10.0f, IsPropertySaveable.Yes), Editable(MinValueFloat = 0.0f, MaxValueFloat = 10000.0f)]
public float MinDepth { get; private set; }
- [Serialize(1000.0f, IsPropertySaveable.Yes)]
+ [Serialize(1000.0f, IsPropertySaveable.Yes), Editable(MinValueFloat = 0.0f, MaxValueFloat = 10000.0f)]
public float MaxDepth { get; private set; }
- [Serialize(false, IsPropertySaveable.Yes)]
+ [Serialize(10000.0f, IsPropertySaveable.Yes, description: "Creatures fade out to the background color of the level the further they are from the camera. This value is the depth at which the object becomes \"maximally\" faded out."), Editable]
+ public float FadeOutDepth
+ {
+ get;
+ private set;
+ }
+ [Serialize(true, IsPropertySaveable.Yes), Editable]
+ public bool FadeOut { get; private set; }
+
+ [Serialize(false, IsPropertySaveable.Yes), Editable]
public bool DisableRotation { get; private set; }
- [Serialize(false, IsPropertySaveable.Yes)]
+ [Serialize(false, IsPropertySaveable.Yes), Editable]
public bool DisableFlipping { get; private set; }
- [Serialize(1.0f, IsPropertySaveable.Yes)]
+ [Serialize(1.0f, IsPropertySaveable.Yes), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f)]
public float Scale { get; private set; }
- [Serialize(1.0f, IsPropertySaveable.Yes)]
+ [Serialize(1.0f, IsPropertySaveable.Yes), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f)]
public float Commonness { get; private set; }
- [Serialize(1000, IsPropertySaveable.Yes)]
+ [Serialize(1000, IsPropertySaveable.Yes), Editable(MinValueInt = 0, MaxValueInt = 1000)]
public int MaxCount { get; private set; }
- [Serialize(0.0f, IsPropertySaveable.Yes)]
+ [Serialize(0.0f, IsPropertySaveable.Yes), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1000.0f)]
public float FlashInterval { get; private set; }
- [Serialize(0.0f, IsPropertySaveable.Yes)]
+ [Serialize(0.0f, IsPropertySaveable.Yes), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1000.0f)]
public float FlashDuration { get; private set; }
+ public string Name => name;
+
+ public Dictionary SerializableProperties { get; private set; }
+
+ ///
+ /// Only used for editing sprite deformation parameters. The actual LevelObjects use separate SpriteDeformation instances.
+ ///
+ public List SpriteDeformations
+ {
+ get;
+ private set;
+ } = new List();
///
/// Overrides the commonness of the object in a specific level type.
@@ -67,13 +93,13 @@ namespace Barotrauma
///
public Dictionary OverrideCommonness = new Dictionary();
- public BackgroundCreaturePrefab(ContentXElement element)
+ public BackgroundCreaturePrefab(ContentXElement element, BackgroundCreaturePrefabsFile file) : base(file, ParseIdentifier(element))
{
- Name = element.Name.ToString();
+ name = element.Name.ToString();
Config = element;
- SerializableProperty.DeserializeProperties(this, element);
+ SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
foreach (var subElement in element.Elements())
{
@@ -84,6 +110,14 @@ namespace Barotrauma
break;
case "deformablesprite":
DeformableSprite = new DeformableSprite(subElement, lazyLoad: true);
+ foreach (XElement deformElement in subElement.Elements())
+ {
+ var deformation = SpriteDeformation.Load(deformElement, Name);
+ if (deformation != null)
+ {
+ SpriteDeformations.Add(deformation);
+ }
+ }
break;
case "lightsprite":
LightSprite = new Sprite(subElement, lazyLoad: true);
@@ -102,17 +136,42 @@ namespace Barotrauma
}
}
- public float GetCommonness(LevelGenerationParams generationParams)
+ public static Identifier ParseIdentifier(XElement element)
{
- if (generationParams != null &&
- !generationParams.Identifier.IsEmpty &&
- (OverrideCommonness.TryGetValue(generationParams.Identifier, out float commonness) ||
- (!generationParams.OldIdentifier.IsEmpty && OverrideCommonness.TryGetValue(generationParams.OldIdentifier, out commonness))))
+ Identifier identifier = element.GetAttributeIdentifier("identifier", "");
+ if (identifier.IsEmpty)
+ {
+ identifier = element.NameAsIdentifier();
+ }
+ return identifier;
+ }
+
+ public float GetCommonness(LevelData levelData)
+ {
+ if (levelData?.GenerationParams is not { } generationParams || generationParams.Identifier.IsEmpty)
+ {
+ return Commonness;
+ }
+
+ if (OverrideCommonness.TryGetValue(generationParams.Identifier, out float commonness) || (!generationParams.OldIdentifier.IsEmpty && OverrideCommonness.TryGetValue(generationParams.OldIdentifier, out commonness)) ||
+ OverrideCommonness.TryGetValue(levelData.Biome.Identifier, out commonness))
{
return commonness;
}
return Commonness;
}
+
+ public override void Dispose()
+ {
+ Sprite?.Remove();
+ Sprite = null;
+ LightSprite?.Remove();
+ LightSprite = null;
+ DeformableLightSprite?.Remove();
+ DeformableLightSprite = null;
+ DeformableSprite?.Remove();
+ DeformableSprite = null;
+ }
}
}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/CaveGenerator.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/CaveGenerator.cs
index 9a071dfb8..1bb061414 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/CaveGenerator.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/CaveGenerator.cs
@@ -9,26 +9,56 @@ namespace Barotrauma
{
static partial class CaveGenerator
{
- public static List GenerateWallVertices(List triangles, LevelGenerationParams generationParams, float zCoord)
+ public static List GenerateWallVertices(List triangles, Color color, float zCoord)
{
- var vertices = new List();
+ var vertices = new List();
for (int i = 0; i < triangles.Count; i++)
{
foreach (Vector2 vertex in triangles[i])
{
- Vector2 uvCoords = vertex / generationParams.WallTextureSize;
- vertices.Add(new VertexPositionTexture(new Vector3(vertex, zCoord), uvCoords));
+ vertices.Add(new VertexPositionColor(new Vector3(vertex, zCoord), color));
}
}
-
return vertices;
}
- public static List GenerateWallEdgeVertices(List cells, Level level, float zCoord)
+ ///
+ /// Generates texture coordinates for the vertices based on their positions
+ ///
+ public static VertexPositionColorTexture[] ConvertToTextured(VertexPositionColor[] verts, float textureSize)
{
- float outWardThickness = level.GenerationParams.WallEdgeExpandOutwardsAmount;
+ VertexPositionColorTexture[] texturedVerts = new VertexPositionColorTexture[verts.Length];
+ for (int i = 0; i < verts.Length; i++)
+ {
+ VertexPositionColor vertex = verts[i];
+ texturedVerts[i] = new VertexPositionColorTexture(vertex.Position, vertex.Color, textureCoordinate: Vector2.Zero);
+ }
+ GenerateTextureCoordinates(texturedVerts, textureSize);
+ return texturedVerts;
+ }
- List vertices = new List();
+ ///
+ /// Generates texture coordinates for the vertices based on their positions
+ ///
+ public static void GenerateTextureCoordinates(VertexPositionColorTexture[] verts, float textureSize)
+ {
+ for (int i = 0; i < verts.Length; i++)
+ {
+ VertexPositionColorTexture vertex = verts[i];
+ Vector2 uvCoords = new Vector2(vertex.Position.X, vertex.Position.Y) / textureSize;
+ verts[i] = new VertexPositionColorTexture(verts[i].Position, verts[i].Color, uvCoords);
+ }
+ }
+
+ public static List GenerateWallEdgeVertices(
+ List cells,
+ float expandOutwards, float expandInwards,
+ Color outerColor, Color innerColor,
+ Level level, float zCoord, bool preventExpandThroughCell = false)
+ {
+ float outWardThickness = expandOutwards;
+
+ List vertices = new List();
foreach (VoronoiCell cell in cells)
{
Vector2 minVert = cell.Edges[0].Point1;
@@ -49,7 +79,10 @@ namespace Barotrauma
{
if (!edge.IsSolid) { continue; }
- GraphEdge leftEdge = cell.Edges.Find(e => e != edge && (edge.Point1.NearlyEquals(e.Point1) || edge.Point1.NearlyEquals(e.Point2)));
+ //the left-side edge on this same cell
+ GraphEdge myLeftEdge = cell.Edges.Find(e => e != edge && (edge.Point1.NearlyEquals(e.Point1) || edge.Point1.NearlyEquals(e.Point2)));
+ //the left-side edge on either this cell, or the adjacent one if this is attached to another cell
+ GraphEdge leftEdge = myLeftEdge;
var leftAdjacentCell = leftEdge?.AdjacentCell(cell);
if (leftAdjacentCell != null)
{
@@ -57,7 +90,10 @@ namespace Barotrauma
if (adjEdge != null) { leftEdge = adjEdge; }
}
- GraphEdge rightEdge = cell.Edges.Find(e => e != edge && (edge.Point2.NearlyEquals(e.Point1) || edge.Point2.NearlyEquals(e.Point2)));
+ //the right-side edge on this same cell
+ GraphEdge myRightEdge = cell.Edges.Find(e => e != edge && (edge.Point2.NearlyEquals(e.Point1) || edge.Point2.NearlyEquals(e.Point2)));
+ //the right-side edge on either this cell, or the adjacent one if this is attached to another cell
+ GraphEdge rightEdge = myRightEdge;
var rightAdjacentCell = rightEdge?.AdjacentCell(cell);
if (rightAdjacentCell != null)
{
@@ -67,18 +103,25 @@ namespace Barotrauma
Vector2 leftNormal = Vector2.Zero, rightNormal = Vector2.Zero;
- float inwardThickness1 = level.GenerationParams.WallEdgeExpandInwardsAmount;
- float inwardThickness2 = level.GenerationParams.WallEdgeExpandInwardsAmount;
+ float inwardThickness1 = Math.Min(expandInwards, edge.Length);
+ float inwardThickness2 = inwardThickness1;
if (leftEdge != null && !leftEdge.IsSolid)
{
+ //the left-side edge is non-solid (an edge between two cells, not an actual solid wall edge)
+ // -> expand in the direction of that edge
leftNormal = edge.Point1.NearlyEquals(leftEdge.Point1) ?
Vector2.Normalize(leftEdge.Point2 - leftEdge.Point1) :
Vector2.Normalize(leftEdge.Point1 - leftEdge.Point2);
+ //maximum expansion is half of the size of the edge (otherwise the expansions from different sides of the edge could overlap or even extend "through" the cell)
+ inwardThickness1 = Math.Min(inwardThickness1, leftEdge.Length / 2);
}
else if (leftEdge != null)
{
+ //use the average of this edge's and the adjacent edge's normals
leftNormal = -Vector2.Normalize(edge.GetNormal(cell) + leftEdge.GetNormal(leftAdjacentCell ?? cell));
if (!MathUtils.IsValid(leftNormal)) { leftNormal = -edge.GetNormal(cell); }
+ //maximum expansion is the length of the adjacent edge (more expansion causes the textures to distort)
+ inwardThickness1 = Math.Min(Math.Min(inwardThickness1, leftEdge.Length), myLeftEdge.Length);
}
else
{
@@ -109,11 +152,13 @@ namespace Barotrauma
rightNormal = edge.Point2.NearlyEquals(rightEdge.Point1) ?
Vector2.Normalize(rightEdge.Point2 - rightEdge.Point1) :
Vector2.Normalize(rightEdge.Point1 - rightEdge.Point2);
+ inwardThickness2 = Math.Min(inwardThickness2, rightEdge.Length / 2);
}
else if (rightEdge != null)
{
rightNormal = -Vector2.Normalize(edge.GetNormal(cell) + rightEdge.GetNormal(rightAdjacentCell ?? cell));
if (!MathUtils.IsValid(rightNormal)) { rightNormal = -edge.GetNormal(cell); }
+ inwardThickness2 = Math.Min(Math.Min(inwardThickness2, rightEdge.Length), myRightEdge.Length);
}
else
{
@@ -150,10 +195,51 @@ namespace Barotrauma
point1UV = point1UV / MathHelper.TwoPi * textureRepeatCount;
point2UV = point2UV / MathHelper.TwoPi * textureRepeatCount;
+ //if calculating the UVs based on polar coordinates would result in stretching (using less than 10% of the texture for a wall the size of the texture)
+ //just calculate the UVs based on the length of the wall
+ //(this will mean the textures don't align at point2, but it doesn't seem that noticeable)
+ if ((point2UV - point1UV) * level.GenerationParams.WallEdgeTextureWidth < edge.Length * 0.1f)
+ {
+ point2UV = point1UV + edge.Length / 2 / level.GenerationParams.WallEdgeTextureWidth;
+ }
+
+ //"extruding" inwards, need to make sure we don't make the edge poke through the cell from the other side
+ if (preventExpandThroughCell)
+ {
+ foreach (GraphEdge otherEdge in cell.Edges)
+ {
+ if (otherEdge == edge || Vector2.Dot(otherEdge.GetNormal(cell), edge.GetNormal(cell)) > 0) { continue; }
+ if (otherEdge != leftEdge)
+ {
+ inwardThickness1 = ClampThickness(otherEdge, edge.Point1, leftNormal, inwardThickness1);
+ }
+ if (otherEdge != rightEdge)
+ {
+ inwardThickness2 = ClampThickness(otherEdge, edge.Point2, rightNormal, inwardThickness2);
+ }
+ }
+
+ static float ClampThickness(GraphEdge otherEdge, Vector2 thisPoint, Vector2 thisEdgeNormal, float currThickness)
+ {
+ if (MathUtils.GetLineIntersection(
+ thisPoint, thisPoint + thisEdgeNormal * currThickness,
+ otherEdge.Point1, otherEdge.Point2, areLinesInfinite: false, out Vector2 intersection1))
+ {
+ return Math.Min(currThickness, Vector2.Distance(thisPoint, intersection1));
+ }
+ return currThickness;
+ }
+ }
+
+ //there needs to be some minimum amount of inward thickness,
+ //if the edge texture doesn't extend inside at all you can see through between the edge texture and the solid part of the cell
+ inwardThickness1 = Math.Max(inwardThickness1, Math.Min(100.0f, expandInwards));
+ inwardThickness2 = Math.Max(inwardThickness2, Math.Min(100.0f, expandInwards));
+
for (int i = 0; i < 2; i++)
{
Vector2[] verts = new Vector2[3];
- VertexPositionTexture[] vertPos = new VertexPositionTexture[3];
+ VertexPositionColorTexture[] vertPos = new VertexPositionColorTexture[3];
if (i == 0)
{
@@ -161,9 +247,9 @@ namespace Barotrauma
verts[1] = edge.Point2 - rightNormal * outWardThickness;
verts[2] = edge.Point1 + leftNormal * inwardThickness1;
- vertPos[0] = new VertexPositionTexture(new Vector3(verts[0], zCoord), new Vector2(point1UV, 0.0f));
- vertPos[1] = new VertexPositionTexture(new Vector3(verts[1], zCoord), new Vector2(point2UV, 0.0f));
- vertPos[2] = new VertexPositionTexture(new Vector3(verts[2], zCoord), new Vector2(point1UV, 1.0f));
+ vertPos[0] = new VertexPositionColorTexture(new Vector3(verts[0], zCoord), outerColor, new Vector2(point1UV, 0.0f));
+ vertPos[1] = new VertexPositionColorTexture(new Vector3(verts[1], zCoord), outerColor, new Vector2(point2UV, 0.0f));
+ vertPos[2] = new VertexPositionColorTexture(new Vector3(verts[2], zCoord), innerColor, new Vector2(point1UV, 1.0f));
}
else
{
@@ -172,9 +258,9 @@ namespace Barotrauma
verts[1] = edge.Point2 - rightNormal * outWardThickness;
verts[2] = edge.Point2 + rightNormal * inwardThickness2;
- vertPos[0] = new VertexPositionTexture(new Vector3(verts[0], zCoord), new Vector2(point1UV, 1.0f));
- vertPos[1] = new VertexPositionTexture(new Vector3(verts[1], zCoord), new Vector2(point2UV, 0.0f));
- vertPos[2] = new VertexPositionTexture(new Vector3(verts[2], zCoord), new Vector2(point2UV, 1.0f));
+ vertPos[0] = new VertexPositionColorTexture(new Vector3(verts[0], zCoord), innerColor, new Vector2(point1UV, 1.0f));
+ vertPos[1] = new VertexPositionColorTexture(new Vector3(verts[1], zCoord), outerColor, new Vector2(point2UV, 0.0f));
+ vertPos[2] = new VertexPositionColorTexture(new Vector3(verts[2], zCoord), innerColor, new Vector2(point2UV, 1.0f));
}
vertices.AddRange(vertPos);
}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/Level.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/Level.cs
index 34caf829f..d3d2b8b53 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/Level.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/Level.cs
@@ -47,62 +47,75 @@ namespace Barotrauma
if (renderer == null) { return; }
renderer.DrawDebugOverlay(spriteBatch, cam);
- if (GameMain.DebugDraw && Screen.Selected.Cam.Zoom > 0.1f)
+ if (GameMain.DebugDraw)
{
- foreach (InterestingPosition pos in PositionsOfInterest)
+ if (Screen.Selected.Cam.Zoom > 0.1f)
{
- Color color = Color.Yellow;
- if (pos.PositionType == PositionType.Cave || pos.PositionType == PositionType.AbyssCave)
+ foreach (InterestingPosition pos in PositionsOfInterest)
{
- color = Color.DarkOrange;
- }
- else if (pos.PositionType == PositionType.Ruin)
- {
- color = Color.LightGray;
- }
- if (!pos.IsValid)
- {
- color = Color.Red;
- }
+ Color color = Color.Yellow;
+ if (pos.PositionType == PositionType.Cave || pos.PositionType == PositionType.AbyssCave)
+ {
+ color = Color.DarkOrange;
+ }
+ else if (pos.PositionType == PositionType.Ruin)
+ {
+ 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);
+ GUI.DrawRectangle(spriteBatch, new Vector2(pos.Position.X - 15.0f, -pos.Position.Y - 15.0f), new Vector2(30.0f, 30.0f), color, true);
+ }
+
+ foreach (RuinGeneration.Ruin ruin in Ruins)
+ {
+ Rectangle ruinArea = ruin.Area;
+ ruinArea.Y = -ruinArea.Y - ruinArea.Height;
+
+ GUI.DrawRectangle(spriteBatch, ruinArea, Color.DarkSlateBlue, false, 0, 5);
+ }
}
-
- foreach (RuinGeneration.Ruin ruin in Ruins)
- {
- Rectangle ruinArea = ruin.Area;
- ruinArea.Y = -ruinArea.Y - ruinArea.Height;
-
- GUI.DrawRectangle(spriteBatch, ruinArea, Color.DarkSlateBlue, false, 0, 5);
- }
-
- foreach (var positions in wreckPositions.Values)
+
+ float zoomFactor = MathHelper.Lerp(20, 1, MathUtils.InverseLerp(Screen.Selected.Cam.MinZoom, Screen.Selected.Cam.DefaultZoom, Screen.Selected.Cam.Zoom));
+ foreach ((string debugInfo, List positions) in positionHistory)
{
for (int i = 0; i < positions.Count; i++)
{
float t = (i + 1) / (float)positions.Count;
- float multiplier = MathHelper.Lerp(0, 1, t);
+ float multiplier = MathHelper.Lerp(0.1f, 1, t);
Color color = Color.Red * multiplier;
var pos = positions[i];
pos.Y = -pos.Y;
- var size = new Vector2(100);
- GUI.DrawRectangle(spriteBatch, pos - size / 2, size, color, thickness: 10);
+ var size = new Vector2(200);
+ if (i == 0)
+ {
+ GUI.DrawRectangle(spriteBatch, pos - size, size * 2, Color.Red, thickness: 2 * zoomFactor);
+ GUI.DrawString(spriteBatch, pos - new Vector2(10, 20), debugInfo, Color.White, font: GUIStyle.LargeFont, forceUpperCase: ForceUpperCase.Yes);
+ }
if (i < positions.Count - 1)
{
+ if (i > 0)
+ {
+ GUI.DrawRectangle(spriteBatch, pos - size / 2, size, Color.Red, isFilled: true);
+ }
var nextPos = positions[i + 1];
nextPos.Y = -nextPos.Y;
- GUI.DrawLine(spriteBatch, pos, nextPos, color, width: 10);
+ GUI.DrawLine(spriteBatch, pos, nextPos, color, width: 4 * zoomFactor);
}
}
}
- foreach (var rects in blockedRects.Values)
+ foreach ((Submarine sub, List rects) in blockedRects)
{
- foreach (var rect in rects)
+ foreach (Rectangle t in rects)
{
- Rectangle newRect = rect;
+ Rectangle newRect = t;
newRect.Y = -newRect.Y;
- GUI.DrawRectangle(spriteBatch, newRect, Color.Red, thickness: 5);
+ GUI.DrawRectangle(spriteBatch, newRect, Color.Red * 0.1f, isFilled: true);
+ GUI.DrawString(spriteBatch, newRect.Center.ToVector2(), $"{sub.Info.Name}", Color.White, font: GUIStyle.LargeFont, forceUpperCase: ForceUpperCase.Yes);
}
}
}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/LevelObjects/LevelObject.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/LevelObjects/LevelObject.cs
index 91a25af00..300427deb 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/LevelObjects/LevelObject.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/LevelObjects/LevelObject.cs
@@ -167,7 +167,7 @@ namespace Barotrauma
Prefab.OverrideProperties.Any(p => p != null && (p.Sprites.Any() || p.DeformableSprite != null));
}
- public void Update(float deltaTime)
+ public void Update(float deltaTime, Camera cam)
{
CurrentRotation = Rotation;
if (ActivePrefab.SwingFrequency > 0.0f)
@@ -190,20 +190,24 @@ namespace Barotrauma
ScaleOscillateTimer += deltaTime * ActivePrefab.ScaleOscillationFrequency;
ScaleOscillateTimer = ScaleOscillateTimer % MathHelper.TwoPi;
CurrentScaleOscillation = Vector2.Lerp(CurrentScaleOscillation, ActivePrefab.ScaleOscillation, deltaTime * 10.0f);
-
+
float sin = (float)Math.Sin(ScaleOscillateTimer);
CurrentScale *= new Vector2(
1.0f + sin * CurrentScaleOscillation.X,
- 1.0f + sin * CurrentScaleOscillation.Y);
+ 1.0f + sin * CurrentScaleOscillation.Y);
}
if (LightSources != null)
{
+ Vector2 position2D = new Vector2(Position.X, Position.Y);
+ Vector2 camDiff = position2D - cam.WorldViewCenter;
for (int i = 0; i < LightSources.Length; i++)
{
- if (LightSourceTriggers[i] != null) LightSources[i].Enabled = LightSourceTriggers[i].IsTriggered;
+ if (LightSourceTriggers[i] != null) { LightSources[i].Enabled = LightSourceTriggers[i].IsTriggered; }
LightSources[i].Rotation = -CurrentRotation;
LightSources[i].SpriteScale = CurrentScale;
+ LightSources[i].Position =
+ position2D - camDiff * Position.Z * LevelObjectManager.ParallaxStrength;
}
}
@@ -237,7 +241,10 @@ namespace Barotrauma
{
SoundChannels[i] = roundSound.Sound.Play(roundSound.Volume, roundSound.Range, roundSound.GetRandomFrequencyMultiplier(), soundPos);
}
- SoundChannels[i].Position = new Vector3(soundPos.X, soundPos.Y, 0.0f);
+ if (SoundChannels[i] != null)
+ {
+ SoundChannels[i].Position = new Vector3(soundPos.X, soundPos.Y, 0.0f);
+ }
}
}
else if (SoundChannels[i] != null && SoundChannels[i].IsPlaying)
@@ -259,7 +266,7 @@ namespace Barotrauma
}
deformation.Update(deltaTime);
}
- CurrentSpriteDeformation = SpriteDeformation.GetDeformation(spriteDeformations, ActivePrefab.DeformableSprite.Size);
+ CurrentSpriteDeformation = SpriteDeformation.GetDeformation(spriteDeformations, ActivePrefab.DeformableSprite.Size, flippedHorizontally: false);
if (LightSources != null)
{
foreach (LightSource lightSource in LightSources)
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/LevelObjects/LevelObjectManager.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/LevelObjects/LevelObjectManager.cs
index 1d9f4ecd1..f4b48feb5 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/LevelObjects/LevelObjectManager.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/LevelObjects/LevelObjectManager.cs
@@ -10,9 +10,11 @@ namespace Barotrauma
{
partial class LevelObjectManager
{
- private readonly List visibleObjectsBack = new List();
- private readonly List visibleObjectsMid = new List();
- private readonly List visibleObjectsFront = new List();
+ // Pre-initialized to the max size, so that we don't have to resize the lists at runtime. TODO: Could the capacity (of some collections?) be lower?
+ private readonly List visibleObjectsBack = new List(MaxVisibleObjects);
+ private readonly List visibleObjectsMid = new List(MaxVisibleObjects);
+ private readonly List visibleObjectsFront = new List(MaxVisibleObjects);
+ private readonly HashSet allVisibleObjects = new HashSet(MaxVisibleObjects);
private double NextRefreshTime;
@@ -31,25 +33,41 @@ namespace Barotrauma
visibleObjectsFront.Clear();
}
- partial void UpdateProjSpecific(float deltaTime)
+ partial void UpdateProjSpecific(float deltaTime, Camera cam)
{
foreach (LevelObject obj in visibleObjectsBack)
{
- obj.Update(deltaTime);
+ obj.Update(deltaTime, cam);
}
foreach (LevelObject obj in visibleObjectsMid)
{
- obj.Update(deltaTime);
+ obj.Update(deltaTime, cam);
}
foreach (LevelObject obj in visibleObjectsFront)
{
- obj.Update(deltaTime);
+ obj.Update(deltaTime, cam);
}
}
- public IEnumerable GetVisibleObjects()
+ ///
+ /// Returns all visible objects, but not in order, because internally uses a HashSet.
+ ///
+ public IEnumerable GetAllVisibleObjects()
{
- return visibleObjectsBack.Union(visibleObjectsMid).Union(visibleObjectsFront);
+ allVisibleObjects.Clear();
+ foreach (LevelObject obj in visibleObjectsBack)
+ {
+ allVisibleObjects.Add(obj);
+ }
+ foreach (LevelObject obj in visibleObjectsMid)
+ {
+ allVisibleObjects.Add(obj);
+ }
+ foreach (LevelObject obj in visibleObjectsFront)
+ {
+ allVisibleObjects.Add(obj);
+ }
+ return allVisibleObjects;
}
///
@@ -207,7 +225,7 @@ namespace Barotrauma
activeSprite?.Draw(
spriteBatch,
new Vector2(obj.Position.X, -obj.Position.Y) - camDiff * obj.Position.Z * ParallaxStrength,
- Color.Lerp(obj.Prefab.SpriteColor, obj.Prefab.SpriteColor.Multiply(Level.Loaded.BackgroundTextureColor), obj.Position.Z / 3000.0f),
+ Color.Lerp(obj.Prefab.SpriteColor, obj.Prefab.SpriteColor.Multiply(Level.Loaded.BackgroundTextureColor), obj.Position.Z / obj.Prefab.FadeOutDepth),
activeSprite.Origin,
obj.CurrentRotation,
obj.CurrentScale,
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/LevelRenderer.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/LevelRenderer.cs
index 8b6a6b6fe..02a3d3834 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/LevelRenderer.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/LevelRenderer.cs
@@ -1,4 +1,5 @@
-using Barotrauma.Extensions;
+using Barotrauma.Extensions;
+using Barotrauma.Particles;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
@@ -10,10 +11,25 @@ namespace Barotrauma
{
class LevelWallVertexBuffer : IDisposable
{
- public VertexBuffer WallEdgeBuffer, WallBuffer;
+ ///
+ /// Buffer for the vertices of the "actual" wall texture.
+ ///
+ public VertexBuffer WallBuffer;
+
+ ///
+ /// Buffer for the vertices of the repeating edge texture drawn at the edges of the walls.
+ ///
+ public VertexBuffer WallEdgeBuffer;
+
+ ///
+ /// Buffer for the vertices of the inner, non-textured black part of the wall.
+ ///
+ public VertexBuffer WallInnerBuffer;
+
public readonly Texture2D WallTexture, EdgeTexture;
private VertexPositionColorTexture[] wallVertices;
private VertexPositionColorTexture[] wallEdgeVertices;
+ private VertexPositionColor[] wallInnerVertices;
public bool IsDisposed
{
@@ -21,7 +37,7 @@ namespace Barotrauma
private set;
}
- public LevelWallVertexBuffer(VertexPositionTexture[] wallVertices, VertexPositionTexture[] wallEdgeVertices, Texture2D wallTexture, Texture2D edgeTexture, Color color)
+ public LevelWallVertexBuffer(VertexPositionColorTexture[] wallVertices, VertexPositionColorTexture[] wallEdgeVertices, VertexPositionColor[] wallInnerVertices, Texture2D wallTexture, Texture2D edgeTexture)
{
if (wallVertices.Length == 0)
{
@@ -31,32 +47,41 @@ namespace Barotrauma
{
throw new ArgumentException("Failed to instantiate a LevelWallVertexBuffer (no wall edge vertices).");
}
- this.wallVertices = LevelRenderer.GetColoredVertices(wallVertices, color);
+ this.wallVertices = wallVertices;
WallBuffer = new VertexBuffer(GameMain.Instance.GraphicsDevice, VertexPositionColorTexture.VertexDeclaration, wallVertices.Length, BufferUsage.WriteOnly);
WallBuffer.SetData(this.wallVertices);
WallTexture = wallTexture;
- this.wallEdgeVertices = LevelRenderer.GetColoredVertices(wallEdgeVertices, color);
+ this.wallEdgeVertices = wallEdgeVertices;
WallEdgeBuffer = new VertexBuffer(GameMain.Instance.GraphicsDevice, VertexPositionColorTexture.VertexDeclaration, wallEdgeVertices.Length, BufferUsage.WriteOnly);
WallEdgeBuffer.SetData(this.wallEdgeVertices);
EdgeTexture = edgeTexture;
+
+ if (wallInnerVertices != null)
+ {
+ this.wallInnerVertices = wallInnerVertices;
+ WallInnerBuffer = new VertexBuffer(GameMain.Instance.GraphicsDevice, VertexPositionColor.VertexDeclaration, wallInnerVertices.Length, BufferUsage.WriteOnly);
+ WallInnerBuffer.SetData(this.wallInnerVertices);
+ }
}
- public void Append(VertexPositionTexture[] wallVertices, VertexPositionTexture[] wallEdgeVertices, Color color)
+ public void Append(VertexPositionColorTexture[] newWallVertices, VertexPositionColorTexture[] newWallEdgeVertices, VertexPositionColor[] newWallInnerVertices)
{
- WallBuffer.Dispose();
- WallBuffer = new VertexBuffer(GameMain.Instance.GraphicsDevice, VertexPositionColorTexture.VertexDeclaration, this.wallVertices.Length + wallVertices.Length, BufferUsage.WriteOnly);
- int originalWallVertexCount = this.wallVertices.Length;
- Array.Resize(ref this.wallVertices, originalWallVertexCount + wallVertices.Length);
- Array.Copy(LevelRenderer.GetColoredVertices(wallVertices, color), 0, this.wallVertices, originalWallVertexCount, wallVertices.Length);
- WallBuffer.SetData(this.wallVertices);
+ WallBuffer = Append(WallBuffer, ref wallVertices, newWallVertices, VertexPositionColorTexture.VertexDeclaration);
+ WallEdgeBuffer = Append(WallEdgeBuffer, ref wallEdgeVertices, newWallEdgeVertices, VertexPositionColorTexture.VertexDeclaration);
+ WallInnerBuffer = Append(WallInnerBuffer, ref wallInnerVertices, newWallInnerVertices, VertexPositionColor.VertexDeclaration);
- WallEdgeBuffer.Dispose();
- WallEdgeBuffer = new VertexBuffer(GameMain.Instance.GraphicsDevice, VertexPositionColorTexture.VertexDeclaration, this.wallEdgeVertices.Length + wallEdgeVertices.Length, BufferUsage.WriteOnly);
- int originalWallEdgeVertexCount = this.wallEdgeVertices.Length;
- Array.Resize(ref this.wallEdgeVertices, originalWallEdgeVertexCount + wallEdgeVertices.Length);
- Array.Copy(LevelRenderer.GetColoredVertices(wallEdgeVertices, color), 0, this.wallEdgeVertices, originalWallEdgeVertexCount, wallEdgeVertices.Length);
- WallEdgeBuffer.SetData(this.wallEdgeVertices);
+ static VertexBuffer Append(VertexBuffer buffer, ref T[] currentVertices, T[] newVertices, VertexDeclaration vertexDeclaration) where T : struct, IVertexType
+ {
+ buffer?.Dispose();
+ int originalVertexCount = currentVertices.Length;
+ int newBufferSize = originalVertexCount + newVertices.Length;
+ buffer = new VertexBuffer(GameMain.Instance.GraphicsDevice, vertexDeclaration, newBufferSize, BufferUsage.WriteOnly);
+ Array.Resize(ref currentVertices, newBufferSize);
+ Array.Copy(newVertices, 0, currentVertices, originalVertexCount, newVertices.Length);
+ buffer.SetData(currentVertices);
+ return buffer;
+ }
}
public void Dispose()
@@ -69,7 +94,7 @@ namespace Barotrauma
class LevelRenderer : IDisposable
{
- private static BasicEffect wallEdgeEffect, wallCenterEffect;
+ private static BasicEffect wallEdgeEffect, wallCenterEffect, wallInnerEffect;
private Vector2 waterParticleOffset;
private Vector2 waterParticleVelocity;
@@ -128,7 +153,16 @@ namespace Barotrauma
};
wallCenterEffect.CurrentTechnique = wallCenterEffect.Techniques["BasicEffect_Texture"];
}
-
+
+ if (wallInnerEffect == null)
+ {
+ wallInnerEffect = new BasicEffect(GameMain.Instance.GraphicsDevice)
+ {
+ VertexColorEnabled = true,
+ TextureEnabled = false
+ };
+ wallInnerEffect.CurrentTechnique = wallInnerEffect.Techniques["BasicEffect_Texture"];
+ }
this.level = level;
}
@@ -162,10 +196,7 @@ namespace Barotrauma
if (flashCooldown <= 0.0f)
{
flashTimer = 1.0f;
- if (level.GenerationParams.FlashSound != null)
- {
- level.GenerationParams.FlashSound.Play(1.0f, "default");
- }
+ level.GenerationParams.FlashSound?.Play(1.0f, Sounds.SoundManager.SoundCategoryDefault);
flashCooldown = Rand.Range(level.GenerationParams.FlashInterval.X, level.GenerationParams.FlashInterval.Y, Rand.RandSync.Unsynced);
}
if (flashTimer > 0.0f)
@@ -183,7 +214,7 @@ namespace Barotrauma
//calculate the sum of the forces of nearby level triggers
//and use it to move the water texture and water distortion effect
Vector2 currentWaterParticleVel = level.GenerationParams.WaterParticleVelocity;
- foreach (LevelObject levelObject in level.LevelObjectManager.GetVisibleObjects())
+ foreach (LevelObject levelObject in level.LevelObjectManager.GetAllVisibleObjects())
{
if (levelObject.Triggers == null) { continue; }
//use the largest water flow velocity of all the triggers
@@ -224,22 +255,23 @@ namespace Barotrauma
return verts;
}
- public void SetVertices(VertexPositionTexture[] wallVertices, VertexPositionTexture[] wallEdgeVertices, Texture2D wallTexture, Texture2D edgeTexture, Color color)
+ public void SetVertices(VertexPositionColorTexture[] wallVertices, VertexPositionColorTexture[] wallEdgeVertices, VertexPositionColor[] wallInnerVertices, Texture2D wallTexture, Texture2D edgeTexture)
{
var existingBuffer = vertexBuffers.Find(vb => vb.WallTexture == wallTexture && vb.EdgeTexture == edgeTexture);
if (existingBuffer != null)
{
- existingBuffer.Append(wallVertices, wallEdgeVertices,color);
+ existingBuffer.Append(wallVertices, wallEdgeVertices, wallInnerVertices);
}
else
{
- vertexBuffers.Add(new LevelWallVertexBuffer(wallVertices, wallEdgeVertices, wallTexture, edgeTexture, color));
+ vertexBuffers.Add(new LevelWallVertexBuffer(wallVertices, wallEdgeVertices, wallInnerVertices, wallTexture, edgeTexture));
}
}
public void DrawBackground(SpriteBatch spriteBatch, Camera cam,
LevelObjectManager backgroundSpriteManager = null,
- BackgroundCreatureManager backgroundCreatureManager = null)
+ BackgroundCreatureManager backgroundCreatureManager = null,
+ ParticleManager particleManager = null)
{
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, SamplerState.LinearWrap);
@@ -277,7 +309,7 @@ namespace Barotrauma
spriteBatch.Begin(SpriteSortMode.Deferred,
BlendState.NonPremultiplied,
SamplerState.LinearWrap, DepthStencilState.DepthRead, null, null,
- cam.Transform);
+ cam.Transform);
backgroundSpriteManager?.DrawObjectsBack(spriteBatch, cam);
if (cam.Zoom > 0.05f)
@@ -321,6 +353,9 @@ namespace Barotrauma
color: level.GenerationParams.WaterParticleColor * alpha, textureScale: new Vector2(texScale));
}
}
+
+ GameMain.ParticleManager?.Draw(spriteBatch, inWater: true, inSub: false, ParticleBlendState.AlphaBlend, background: true);
+
spriteBatch.End();
RenderWalls(GameMain.Instance.GraphicsDevice, cam);
@@ -465,7 +500,8 @@ namespace Barotrauma
var wallList = i == 0 ? level.ExtraWalls : level.UnsyncedExtraWalls;
foreach (LevelWall wall in wallList)
{
- if (!(wall is DestructibleLevelWall destructibleWall) || destructibleWall.Destroyed) { continue; }
+ if (wall is not DestructibleLevelWall destructibleWall || destructibleWall.Destroyed) { continue; }
+ if (!wall.IsVisible(cam.WorldView)) { continue; }
wallCenterEffect.Texture = level.GenerationParams.DestructibleWallSprite?.Texture ?? level.GenerationParams.WallSprite.Texture;
wallCenterEffect.World = wall.GetTransform() * transformMatrix;
@@ -491,15 +527,16 @@ namespace Barotrauma
}
}
- wallEdgeEffect.Alpha = 1.0f;
- wallCenterEffect.Alpha = 1.0f;
-
- wallCenterEffect.World = transformMatrix;
- wallEdgeEffect.World = transformMatrix;
+ wallEdgeEffect.Alpha = wallInnerEffect.Alpha = wallCenterEffect.Alpha = 1.0f;
+ wallCenterEffect.World = wallInnerEffect.World = wallEdgeEffect.World = transformMatrix;
//render static walls
foreach (var vertexBuffer in vertexBuffers)
{
+ wallInnerEffect.CurrentTechnique.Passes[0].Apply();
+ graphicsDevice.SetVertexBuffer(vertexBuffer.WallInnerBuffer);
+ graphicsDevice.DrawPrimitives(PrimitiveType.TriangleList, 0, (int)Math.Floor(vertexBuffer.WallInnerBuffer.VertexCount / 3.0f));
+
wallCenterEffect.Texture = vertexBuffer.WallTexture;
wallCenterEffect.CurrentTechnique.Passes[0].Apply();
graphicsDevice.SetVertexBuffer(vertexBuffer.WallBuffer);
@@ -521,6 +558,7 @@ namespace Barotrauma
foreach (LevelWall wall in wallList)
{
if (wall is DestructibleLevelWall) { continue; }
+ if (!wall.IsVisible(cam.WorldView)) { continue; }
//TODO: use LevelWallVertexBuffers for extra walls as well
wallCenterEffect.World = wall.GetTransform() * transformMatrix;
wallCenterEffect.Alpha = wall.Alpha;
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/LevelWall.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/LevelWall.cs
index 5130227b4..1d301e382 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/LevelWall.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/LevelWall.cs
@@ -2,7 +2,6 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
-using System.Collections.Generic;
namespace Barotrauma
{
@@ -24,22 +23,47 @@ namespace Barotrauma
Matrix.CreateTranslation(new Vector3(ConvertUnits.ToDisplayUnits(Body.Position), 0.0f));
}
- public void SetWallVertices(VertexPositionTexture[] wallVertices, VertexPositionTexture[] wallEdgeVertices, Texture2D wallTexture, Texture2D edgeTexture, Color color)
+ public void SetWallVertices(
+ VertexPositionColorTexture[] wallVertices, VertexPositionColorTexture[] wallEdgeVertices,
+ Texture2D wallTexture, Texture2D edgeTexture)
{
if (VertexBuffer != null && !VertexBuffer.IsDisposed) { VertexBuffer.Dispose(); }
- VertexBuffer = new LevelWallVertexBuffer(wallVertices, wallEdgeVertices, wallTexture, edgeTexture, color);
+ VertexBuffer = new LevelWallVertexBuffer(wallVertices, wallEdgeVertices, wallInnerVertices: null, wallTexture, edgeTexture);
}
public void GenerateVertices()
{
float zCoord = this is DestructibleLevelWall ? Rand.Range(0.9f, 1.0f) : 0.9f;
- List wallVertices = CaveGenerator.GenerateWallVertices(triangles, level.GenerationParams, zCoord);
+ var nonTexturedWallVerts =
+ CaveGenerator.GenerateWallVertices(triangles, color, zCoord: 0.9f).ToArray();
+ var wallVerts = CaveGenerator.ConvertToTextured(nonTexturedWallVerts, level.GenerationParams.WallTextureSize);
SetWallVertices(
- wallVertices.ToArray(),
- CaveGenerator.GenerateWallEdgeVertices(Cells, level, zCoord).ToArray(),
+ wallVertices: wallVerts,
+ wallEdgeVertices: CaveGenerator.GenerateWallEdgeVertices(Cells,
+ level.GenerationParams.WallEdgeExpandOutwardsAmount, level.GenerationParams.WallEdgeExpandInwardsAmount,
+ outerColor: color, innerColor: color,
+ level, zCoord)
+ .ToArray(),
level.GenerationParams.WallSprite.Texture,
- level.GenerationParams.WallEdgeSprite.Texture,
- color);
+ level.GenerationParams.WallEdgeSprite.Texture);
+ }
+
+ public bool IsVisible(Rectangle worldView)
+ {
+ RectangleF worldViewInSimUnits = new RectangleF(
+ ConvertUnits.ToSimUnits(worldView.Location.ToVector2()),
+ ConvertUnits.ToSimUnits(worldView.Size.ToVector2()));
+
+ foreach (var fixture in Body.FixtureList)
+ {
+ fixture.GetAABB(out var aabb, 0);
+ Vector2 lowerBound = aabb.LowerBound + Body.Position;
+ if (lowerBound.X > worldViewInSimUnits.Right || lowerBound.Y > worldViewInSimUnits.Y) { continue; }
+ Vector2 upperBound = aabb.UpperBound + Body.Position;
+ if (upperBound.X < worldViewInSimUnits.X || upperBound.Y < worldViewInSimUnits.Y - worldViewInSimUnits.Height) { continue; }
+ return true;
+ }
+ return false;
}
}
}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/ConvexHull.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/ConvexHull.cs
index 31c08ce71..7992ad5f9 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/ConvexHull.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/ConvexHull.cs
@@ -1,5 +1,4 @@
-using Barotrauma.Items.Components;
-using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
@@ -13,6 +12,7 @@ namespace Barotrauma.Lights
public readonly Submarine Submarine;
public HashSet IsHidden = new HashSet();
+ public HashSet HasBeenVisible = new HashSet();
public readonly List List = new List();
public ConvexHullList(Submarine submarine)
@@ -443,10 +443,10 @@ namespace Barotrauma.Lights
public bool Intersects(Rectangle rect)
{
- if (!Enabled) return false;
+ if (!Enabled) { return false; }
Rectangle transformedBounds = BoundingBox;
- if (ParentEntity != null && ParentEntity.Submarine != null)
+ if (ParentEntity is { Submarine: not null })
{
transformedBounds.X += (int)ParentEntity.Submarine.Position.X;
transformedBounds.Y += (int)ParentEntity.Submarine.Position.Y;
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightManager.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightManager.cs
index 9bed09947..b65723a52 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightManager.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightManager.cs
@@ -174,7 +174,7 @@ namespace Barotrauma.Lights
}
private readonly List activeLights = new List(capacity: 100);
- private readonly List activeLightsWithLightVolume = new List(capacity: 100);
+ private readonly List activeShadowCastingLights = new List(capacity: 100);
public static int ActiveLightCount { get; private set; }
@@ -243,6 +243,7 @@ namespace Barotrauma.Lights
}
}
+ /// A render target that contains the structures that should obstruct lights in the background. If not given, damageable walls and hulls are rendered to obstruct the background lights.
public void RenderLightMap(GraphicsDevice graphics, SpriteBatch spriteBatch, Camera cam, RenderTarget2D backgroundObstructor = null)
{
if (!LightingEnabled) { return; }
@@ -273,13 +274,13 @@ namespace Barotrauma.Lights
{
light.ParentBody.UpdateDrawPosition();
- Vector2 pos = light.ParentBody.DrawPosition;
+ Vector2 pos = light.ParentBody.DrawPosition + light.OffsetFromBody;
if (light.ParentSub != null) { pos -= light.ParentSub.DrawPosition; }
light.Position = pos;
}
//above the top boundary of the level (in an inactive respawn shuttle?)
- if (Level.Loaded != null && light.WorldPosition.Y > Level.Loaded.Size.Y) { continue; }
+ if (Level.IsPositionAboveLevel(light.WorldPosition)) { continue; }
float range = light.LightSourceParams.TextureRange;
if (light.LightSprite != null)
@@ -315,19 +316,20 @@ namespace Barotrauma.Lights
}
//find the lights with an active light volume
- activeLightsWithLightVolume.Clear();
+ activeShadowCastingLights.Clear();
foreach (var activeLight in activeLights)
{
+ if (!activeLight.CastShadows) { continue; }
if (activeLight.Range < 1.0f || activeLight.Color.A < 1 || activeLight.CurrentBrightness <= 0.0f) { continue; }
- activeLightsWithLightVolume.Add(activeLight);
+ activeShadowCastingLights.Add(activeLight);
}
//remove some lights with a light volume if there's too many of them
- if (activeLightsWithLightVolume.Count > GameSettings.CurrentConfig.Graphics.VisibleLightLimit && Screen.Selected is { IsEditor: false })
+ if (activeShadowCastingLights.Count > GameSettings.CurrentConfig.Graphics.VisibleLightLimit && Screen.Selected is { IsEditor: false })
{
- for (int i = GameSettings.CurrentConfig.Graphics.VisibleLightLimit; i < activeLightsWithLightVolume.Count; i++)
+ for (int i = GameSettings.CurrentConfig.Graphics.VisibleLightLimit; i < activeShadowCastingLights.Count; i++)
{
- activeLights.Remove(activeLightsWithLightVolume[i]);
+ activeLights.Remove(activeShadowCastingLights[i]);
}
}
activeLights.Sort((l1, l2) => l1.LastRecalculationTime.CompareTo(l2.LastRecalculationTime));
@@ -410,11 +412,21 @@ namespace Barotrauma.Lights
}
spriteBatch.End();
- GameMain.GameScreen.DamageEffect.CurrentTechnique = GameMain.GameScreen.DamageEffect.Techniques["StencilShaderSolidColor"];
- GameMain.GameScreen.DamageEffect.Parameters["solidColor"].SetValue(Color.Black.ToVector4());
- spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.NonPremultiplied, SamplerState.LinearWrap, transformMatrix: spriteBatchTransform, effect: GameMain.GameScreen.DamageEffect);
- Submarine.DrawDamageable(spriteBatch, GameMain.GameScreen.DamageEffect);
- spriteBatch.End();
+ if (backgroundObstructor != null)
+ {
+ spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.NonPremultiplied, SamplerState.LinearWrap, transformMatrix: Matrix.Identity, effect: GameMain.GameScreen.DamageEffect);
+ spriteBatch.Draw(backgroundObstructor, new Rectangle(0, 0,
+ (int)(GameMain.GraphicsWidth * currLightMapScale), (int)(GameMain.GraphicsHeight * currLightMapScale)), Color.Black);
+ spriteBatch.End();
+ }
+ else
+ {
+ GameMain.GameScreen.DamageEffect.CurrentTechnique = GameMain.GameScreen.DamageEffect.Techniques["StencilShaderSolidColor"];
+ GameMain.GameScreen.DamageEffect.Parameters["solidColor"].SetValue(Color.Black.ToVector4());
+ spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.NonPremultiplied, SamplerState.LinearWrap, transformMatrix: spriteBatchTransform, effect: GameMain.GameScreen.DamageEffect);
+ Submarine.DrawDamageable(spriteBatch, GameMain.GameScreen.DamageEffect);
+ spriteBatch.End();
+ }
graphics.BlendState = BlendState.Additive;
@@ -679,11 +691,14 @@ namespace Barotrauma.Lights
}
return visibleHulls;
}
+
+ private static readonly List ShadowVertices = new List(500);
+ private static readonly List PenumbraVertices = new List(500);
public void UpdateObstructVision(GraphicsDevice graphics, SpriteBatch spriteBatch, Camera cam, Vector2 lookAtPosition)
{
if ((!LosEnabled || LosMode == LosMode.None) && ObstructVisionAmount <= 0.0f) { return; }
- if (ViewTarget == null) return;
+ if (ViewTarget == null) { return; }
graphics.SetRenderTarget(LosTexture);
@@ -766,11 +781,11 @@ namespace Barotrauma.Lights
if (convexHulls != null)
{
- List shadowVerts = new List();
- List penumbraVerts = new List();
+ ShadowVertices.Clear();
+ PenumbraVertices.Clear();
foreach (ConvexHull convexHull in convexHulls)
{
- if (!convexHull.Enabled || !convexHull.Intersects(camView)) { continue; }
+ if (!convexHull.Intersects(camView)) { continue; }
Vector2 relativeViewPos = pos;
if (convexHull.ParentEntity?.Submarine != null)
@@ -782,26 +797,26 @@ namespace Barotrauma.Lights
for (int i = 0; i < convexHull.ShadowVertexCount; i++)
{
- shadowVerts.Add(convexHull.ShadowVertices[i]);
+ ShadowVertices.Add(convexHull.ShadowVertices[i]);
}
for (int i = 0; i < convexHull.PenumbraVertexCount; i++)
{
- penumbraVerts.Add(convexHull.PenumbraVertices[i]);
+ PenumbraVertices.Add(convexHull.PenumbraVertices[i]);
}
}
- if (shadowVerts.Count > 0)
+ if (ShadowVertices.Count > 0)
{
ConvexHull.shadowEffect.World = shadowTransform;
ConvexHull.shadowEffect.CurrentTechnique.Passes[0].Apply();
- graphics.DrawUserPrimitives(PrimitiveType.TriangleList, shadowVerts.ToArray(), 0, shadowVerts.Count / 3, VertexPositionColor.VertexDeclaration);
+ graphics.DrawUserPrimitives(PrimitiveType.TriangleList, ShadowVertices.ToArray(), 0, ShadowVertices.Count / 3, VertexPositionColor.VertexDeclaration);
- if (penumbraVerts.Count > 0)
+ if (PenumbraVertices.Count > 0)
{
ConvexHull.penumbraEffect.World = shadowTransform;
ConvexHull.penumbraEffect.CurrentTechnique.Passes[0].Apply();
- graphics.DrawUserPrimitives(PrimitiveType.TriangleList, penumbraVerts.ToArray(), 0, penumbraVerts.Count / 3, VertexPositionTexture.VertexDeclaration);
+ graphics.DrawUserPrimitives(PrimitiveType.TriangleList, PenumbraVertices.ToArray(), 0, PenumbraVertices.Count / 3, VertexPositionTexture.VertexDeclaration);
}
}
}
@@ -827,7 +842,7 @@ namespace Barotrauma.Lights
public void ClearLights()
{
activeLights.Clear();
- activeLightsWithLightVolume.Clear();
+ activeShadowCastingLights.Clear();
lights.Clear();
}
}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightSource.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightSource.cs
index 5e9a1f267..e37bd5193 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightSource.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightSource.cs
@@ -1,4 +1,4 @@
-using Barotrauma.Extensions;
+using Barotrauma.Extensions;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
@@ -245,7 +245,7 @@ namespace Barotrauma.Lights
}
set
{
- if (!needsRecalculation && value)
+ if (value)
{
foreach (ConvexHullList chList in convexHullsInRange)
{
@@ -450,6 +450,8 @@ namespace Barotrauma.Lights
set;
}
+ public Vector2 OffsetFromBody;
+
public DeformableSprite DeformableLightSprite
{
get;
@@ -550,7 +552,6 @@ namespace Barotrauma.Lights
chList.List.Clear();
foreach (var convexHull in fullChList.List)
{
- if (!convexHull.Enabled) { continue; }
if (!MathUtils.CircleIntersectsRectangle(lightPos, TextureRange, convexHull.BoundingBox)) { continue; }
if (lightSourceParams.Directional)
{
@@ -562,9 +563,9 @@ namespace Barotrauma.Lights
// center is in the opposite direction from the ray (cheapest check first)
if (Vector2.Dot(ray, convexHull.BoundingBox.Center.ToVector2() - lightPos) <= 0 &&
/*ray doesn't hit the convex hull*/
- !MathUtils.GetLineRectangleIntersection(lightPos, lightPos + ray, bounds, out _) &&
+ !MathUtils.GetLineWorldRectangleIntersection(lightPos, lightPos + ray, bounds, out _) &&
/*normal vectors of the ray don't hit the convex hull */
- !MathUtils.GetLineRectangleIntersection(lightPos + normal, lightPos - normal, bounds, out _))
+ !MathUtils.GetLineWorldRectangleIntersection(lightPos + normal, lightPos - normal, bounds, out _))
{
continue;
}
@@ -572,6 +573,7 @@ namespace Barotrauma.Lights
chList.List.Add(convexHull);
}
chList.IsHidden.RemoveWhere(ch => !chList.List.Contains(ch));
+ chList.HasBeenVisible.RemoveWhere(ch => !chList.List.Contains(ch));
HullsUpToDate.Add(sub);
}
@@ -592,7 +594,17 @@ namespace Barotrauma.Lights
private void CheckHullsInRange(Submarine sub)
{
//find the list of convexhulls that belong to the sub
- ConvexHullList chList = convexHullsInRange.FirstOrDefault(chList => chList.Submarine == sub);
+
+ // Performance-sensitive code, hence implemented without Linq.
+ ConvexHullList chList = null;
+ foreach (var chl in convexHullsInRange)
+ {
+ if (chl.Submarine == sub)
+ {
+ chList = chl;
+ break;
+ }
+ }
//not found -> create one
if (chList == null)
@@ -604,7 +616,8 @@ namespace Barotrauma.Lights
foreach (var ch in chList.List)
{
- if (ch.LastVertexChangeTime > LastRecalculationTime && !chList.IsHidden.Contains(ch))
+ if (ch.LastVertexChangeTime > LastRecalculationTime &&
+ (!chList.IsHidden.Contains(ch) || chList.HasBeenVisible.Contains(ch)))
{
NeedsRecalculation = true;
break;
@@ -712,8 +725,8 @@ namespace Barotrauma.Lights
{
foreach (ConvexHull hull in chList.List)
{
- if (hull.IsInvalid) { continue; }
- if (!chList.IsHidden.Contains(hull))
+ if (hull.IsInvalid || !hull.Enabled) { continue; }
+ if (!chList.IsHidden.Contains(hull) || chList.HasBeenVisible.Contains(hull))
{
//find convexhull segments that are close enough and facing towards the light source
lock (mutex)
@@ -732,7 +745,17 @@ namespace Barotrauma.Lights
}
foreach (ConvexHull hull in chList.List)
{
- chList.IsHidden.Add(hull);
+ if (!hull.Enabled)
+ {
+ //if the hull is not enabled, we cannot determine if it's visible or hidden from the point of view of the light source
+ //so let's not mark it as hidden, but instead consider it as something that has been visible, so we know to recalculate if/when it becomes enabled again
+ chList.IsHidden.Remove(hull);
+ chList.HasBeenVisible.Add(hull);
+ continue;
+ }
+
+ //mark convex hulls as hidden at this point, they're removed if we find any of the segments to be visible
+ chList.IsHidden.Add(hull);
}
}
@@ -1411,14 +1434,7 @@ namespace Barotrauma.Lights
{
if (conditionals.None()) { return; }
if (conditionalTarget == null) { return; }
- if (logicalOperator == PropertyConditional.LogicalOperatorType.And)
- {
- Enabled = conditionals.All(c => c.Matches(conditionalTarget));
- }
- else
- {
- Enabled = conditionals.Any(c => c.Matches(conditionalTarget));
- }
+ Enabled = PropertyConditional.CheckConditionals(conditionalTarget, conditionals, logicalOperator);
}
public void DebugDrawVertices(SpriteBatch spriteBatch)
@@ -1501,6 +1517,7 @@ namespace Barotrauma.Lights
foreach (var convexHullList in convexHullsInRange)
{
convexHullList.IsHidden.Remove(visibleConvexHull);
+ convexHullList.HasBeenVisible.Add(visibleConvexHull);
}
}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Map/Map.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Map/Map.cs
index a29588def..c2d8b40dc 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Map/Map/Map.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Map/Map.cs
@@ -378,17 +378,24 @@ namespace Barotrauma
bool showReputation = hudVisibility > 0.0f && location.Type.HasOutpost && location.Reputation != null;
+ LocationType locationTypeToDisplay = location.GetLocationTypeToDisplay(out Identifier overrideDescriptionIdentifier);
+
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform), location.DisplayName, font: GUIStyle.LargeFont) { Padding = Vector4.Zero };
if (!location.Type.Name.IsNullOrEmpty())
{
- new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform), location.Type.Name, font: GUIStyle.SubHeadingFont) { Padding = Vector4.Zero };
+ new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform), locationTypeToDisplay.Name, font: GUIStyle.SubHeadingFont) { Padding = Vector4.Zero };
}
CreateSpacing(10);
- if (!location.Type.Description.IsNullOrEmpty())
+ var description = locationTypeToDisplay.Description;
+ if (!overrideDescriptionIdentifier.IsEmpty)
{
- CreateTextWithIcon(location.Type.Description, location.Type.Sprite);
+ description = TextManager.Get(overrideDescriptionIdentifier);
+ }
+ if (!description.IsNullOrEmpty())
+ {
+ CreateTextWithIcon(description, locationTypeToDisplay.Sprite);
}
int highestSubTier = location.HighestSubmarineTierAvailable();
@@ -699,6 +706,7 @@ namespace Barotrauma
CurrentLocation.CreateStores();
ProgressWorld(campaign);
Radiation?.OnStep(1);
+ mapAnimQueue.Clear();
}
else
{
@@ -828,7 +836,7 @@ namespace Barotrauma
if (!rect.Intersects(drawRect)) { continue; }
- Color color = location.Type.SpriteColor;
+ Color color = location.OverrideIconColor ?? location.Type.SpriteColor;
if (!location.Visited) { color = Color.White; }
if (location.Connections.Find(c => c.Locations.Contains(currentDisplayLocation)) == null)
{
@@ -850,6 +858,27 @@ namespace Barotrauma
iconScale *= notificationPulseAmount;
}
+#if DEBUG
+ if (generationParams.ShowStoreInfo)
+ {
+ if (location.Stores == null || location.Stores.None())
+ {
+ color = Color.DarkBlue;
+ }
+ //stores created, but nothing in stock
+ else if (location.Stores.Values.None(s => s.Stock.Any()))
+ {
+ color = Color.Yellow;
+ }
+ else
+ {
+ color = Color.Green;
+ }
+
+ GUI.DrawString(spriteBatch, pos + Vector2.One * 20, "Time since visited: " +location.WorldStepsSinceVisited, Color.Yellow);
+ }
+#endif
+
locationSprite.Draw(spriteBatch, pos, color,
scale: generationParams.LocationIconSize / locationSprite.size.X * iconScale * zoom);
@@ -923,31 +952,32 @@ namespace Barotrauma
if (GameMain.DebugDraw)
{
- Vector2 dPos = pos;
+ //move the debug texts upwards so they don't go under the info panel that appears when highlighted
+ Vector2 dPos = pos + new Vector2(15, -100);
if (location == HighlightedLocation)
{
- dPos.Y -= 80;
- GUI.DrawString(spriteBatch, dPos + new Vector2(15, 32), "Faction: " + (location.Faction?.Prefab.Name ?? "none"), Color.White, Color.Black, font: GUIStyle.SubHeadingFont);
- GUI.DrawString(spriteBatch, dPos + new Vector2(15, 50), "Secondary Faction: " + (location.SecondaryFaction?.Prefab.Name ?? "none"), Color.White, Color.Black, font: GUIStyle.SubHeadingFont);
- dPos.Y += 48;
+ GUI.DrawString(spriteBatch, dPos, "Faction: " + (location.Faction?.Prefab.Name ?? "none"), Color.White, Color.Black, font: GUIStyle.SubHeadingFont);
+ GUI.DrawString(spriteBatch, dPos + new Vector2(0, 18), "Secondary Faction: " + (location.SecondaryFaction?.Prefab.Name ?? "none"), Color.White, Color.Black, font: GUIStyle.SubHeadingFont);
+ dPos.Y += 50;
if (PlayerInput.KeyDown(Keys.LeftShift))
{
- GUI.DrawString(spriteBatch, new Vector2(150,150), "Dist: " +
+ GUI.DrawString(spriteBatch, new Vector2(150, 150), "Dist: " +
GetDistanceToClosestLocationOrConnection(CurrentLocation, int.MaxValue, loc => loc == location), Color.White, Color.Black, font: GUIStyle.SubHeadingFont);
-
}
+ GUI.DrawString(spriteBatch, dPos, $"Difficulty: {location.LevelData.Difficulty.FormatSingleDecimal()}",
+ ToolBox.GradientLerp(location.LevelData.Difficulty / 100.0f, GUIStyle.Blue, GUIStyle.Yellow, GUIStyle.Red), Color.Black * 0.8f, 4, font: GUIStyle.SmallFont);
+
+ dPos.Y += 25;
+ GUI.DrawString(spriteBatch, dPos, $"Biome: {location.LevelData.Biome.DisplayName} ({location.LevelData.GenerationParams.Identifier})", Color.White, Color.Black, font: GUIStyle.SmallFont);
}
- dPos.Y += 48;
- GUI.DrawString(spriteBatch, dPos, $"Difficulty: {location.LevelData.Difficulty.FormatSingleDecimal()}", Color.White, Color.Black * 0.8f, 4, font: GUIStyle.SmallFont);
}
}
}
DrawDecorativeHUD(spriteBatch, rect);
- bool drawRadiationTooltip = true;
-
+ bool drawRadiationTooltip = HighlightedLocation == null;
if (tooltip != null)
{
GUIComponent.DrawToolTip(spriteBatch, tooltip.Value.tip, tooltip.Value.targetArea);
@@ -1056,7 +1086,7 @@ namespace Barotrauma
}
else
{
- if (MathUtils.GetLineRectangleIntersection(start, end, new Rectangle(viewArea.X, viewArea.Y + viewArea.Height, viewArea.Width, viewArea.Height), out Vector2 intersection))
+ if (MathUtils.GetLineWorldRectangleIntersection(start, end, new Rectangle(viewArea.X, viewArea.Y + viewArea.Height, viewArea.Width, viewArea.Height), out Vector2 intersection))
{
if (!viewArea.Contains(start))
{
@@ -1196,7 +1226,9 @@ namespace Barotrauma
Vector2 center = rectCenter + (connection.CenterPos + viewOffset) * zoom;
if (viewArea.Contains(center) && connection.Biome != null)
{
- GUI.DrawString(spriteBatch, center, (connection.LevelData?.GenerationParams?.Identifier ?? connection.Biome.Identifier) + " (" + connection.Difficulty.FormatSingleDecimal() + ")", Color.White);
+ GUI.DrawString(spriteBatch, center - Vector2.UnitX * 50,
+ $"{(connection.LevelData?.GenerationParams?.Identifier ?? connection.Biome.Identifier)} ({connection.Difficulty.FormatSingleDecimal()})",
+ ToolBox.GradientLerp(connection.Difficulty / 100.0f, GUIStyle.Blue, GUIStyle.Yellow, GUIStyle.Red), backgroundColor: Color.Black * 0.7f, font: GUIStyle.SmallFont);
}
}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Map/Radiation.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Map/Radiation.cs
index 8fa773813..125c9e46b 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Map/Map/Radiation.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Map/Radiation.cs
@@ -7,18 +7,18 @@ namespace Barotrauma
{
internal partial class Radiation
{
- private static readonly LocalizedString radiationTooltip = TextManager.Get("RadiationTooltip");
+ private int? radiationMultiplier;
private static float spriteIndex;
- private readonly SpriteSheet? sheet = GUIStyle.RadiationAnimSpriteSheet;
- private int maxFrames => (sheet?.FrameCount ?? 0) + 1;
+ private readonly SpriteSheet? radiationEdgeAnimSheet = GUIStyle.RadiationAnimSpriteSheet;
+ private int maxFrames => (radiationEdgeAnimSheet?.FrameCount ?? 0) + 1;
- private bool isHovingOver;
+ private bool isHoveringOver;
public void Draw(SpriteBatch spriteBatch, Rectangle container, float zoom)
{
if (!Enabled) { return; }
- UISprite? uiSprite = GUIStyle.Radiation;
+ UISprite? radiationMainSprite = GUIStyle.Radiation;
var (offsetX, offsetY) = Map.DrawOffset * zoom;
var (centerX, centerY) = container.Center.ToVector2();
var (halfSizeX, halfSizeY) = new Vector2(container.Width / 2f, container.Height / 2f) * zoom;
@@ -29,31 +29,41 @@ namespace Barotrauma
Vector2 spriteScale = new Vector2(zoom);
- uiSprite?.Sprite.DrawTiled(spriteBatch, topLeft, size, color: Params.RadiationAreaColor, startOffset: Vector2.Zero, textureScale: spriteScale);
+ radiationMainSprite?.Sprite.DrawTiled(spriteBatch, topLeft, size, color: Params.RadiationAreaColor, startOffset: Vector2.Zero, textureScale: spriteScale);
Vector2 topRight = topLeft + Vector2.UnitX * size.X;
int index = 0;
- if (sheet != null)
+ if (radiationEdgeAnimSheet != null)
{
- for (float i = 0; i <= size.Y; i += sheet.FrameSize.Y / 2f * zoom)
+ for (float i = 0; i <= size.Y; i += radiationEdgeAnimSheet.FrameSize.Y / 2f * zoom)
{
bool isEven = ++index % 2 == 0;
- Vector2 origin = new Vector2(0.5f, 0) * sheet.FrameSize.X;
+ Vector2 origin = new Vector2(0.5f, 0) * radiationEdgeAnimSheet.FrameSize.X;
// every other sprite's animation is reversed to make it seem more chaotic
int sprite = (int) MathF.Floor(isEven ? spriteIndex : maxFrames - spriteIndex);
- sheet.Draw(spriteBatch, sprite, topRight + new Vector2(0, i), Params.RadiationBorderTint, origin, 0f, spriteScale);
+ radiationEdgeAnimSheet.Draw(spriteBatch, sprite, topRight + new Vector2(0, i), Params.RadiationBorderTint, origin, 0f, spriteScale);
}
}
- isHovingOver = container.Contains(PlayerInput.MousePosition) && PlayerInput.MousePosition.X < topLeft.X + size.X;
+ radiationMultiplier = null;
+ if (container.Contains(PlayerInput.MousePosition))
+ {
+ float rightEdge = topLeft.X + size.X;
+ float distanceFromRight = rightEdge - PlayerInput.MousePosition.X;
+ if (distanceFromRight >= 0)
+ {
+ radiationMultiplier = Math.Min(4, (int)(distanceFromRight / (Params.RadiationEffectMultipliedPerPixelDistance * zoom)) + 1);
+ }
+ }
}
public void DrawFront(SpriteBatch spriteBatch)
{
- if (isHovingOver)
+ if (radiationMultiplier is int multiplier)
{
- GUIComponent.DrawToolTip(spriteBatch, radiationTooltip, PlayerInput.MousePosition + new Vector2(18 * GUI.Scale));
+ var tooltip = TextManager.GetWithVariable("RadiationTooltip", "[jovianmultiplier]", multiplier.ToString());
+ GUIComponent.DrawToolTip(spriteBatch, tooltip, PlayerInput.MousePosition + new Vector2(18 * GUI.Scale));
}
}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/MapEntity.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/MapEntity.cs
index 6560a4ea1..38549dddf 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Map/MapEntity.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Map/MapEntity.cs
@@ -81,6 +81,16 @@ namespace Barotrauma
public virtual bool IsVisible(Rectangle worldView)
{
+ Rectangle worldRect = WorldRect;
+ if (worldRect.X > worldView.Right || worldRect.Right < worldView.X) { return false; }
+ if (worldRect.Y < worldView.Y - worldView.Height || worldRect.Y - worldRect.Height > worldView.Y) { return false; }
+ //zoomed extremely far out -> no need to render
+ if (Screen.Selected.Cam.Zoom < 0.05f) { return false; }
+ if (worldRect.Width * Screen.Selected.Cam.Zoom < 1.0f ||
+ worldRect.Height * Screen.Selected.Cam.Zoom < 1.0f)
+ {
+ return false;
+ }
return true;
}
@@ -91,6 +101,8 @@ namespace Barotrauma
public virtual void Draw(SpriteBatch spriteBatch, bool editing, bool back = true) { }
+ public virtual float GetDrawDepth() { return 0.0f; }
+
///
/// A method that modifies the draw depth to prevent z-fighting between entities with the same sprite depth
///
@@ -305,6 +317,15 @@ namespace Barotrauma
if (PlayerInput.IsCtrlDown())
{
HashSet clones = Clone(SelectedList.ToList()).Where(c => c != null).ToHashSet();
+
+ if (clones.Count == 1)
+ {
+ if (clones.First() is WayPoint wayPoint && SelectedList.First() is WayPoint originalWaypoint && originalWaypoint.SpawnType == SpawnType.Path)
+ {
+ originalWaypoint.ConnectTo(wayPoint);
+ }
+ }
+
SelectedList = clones;
SelectedList.ForEach(c => c.Move(moveAmount));
SubEditorScreen.StoreCommand(new AddOrDeleteCommand(new List(clones), false));
@@ -1068,6 +1089,7 @@ namespace Barotrauma
}
SubEditorScreen.StoreCommand(new AddOrDeleteCommand(clones, false, handleInventoryBehavior: false));
+ if (Screen.Selected is SubEditorScreen subEditor) { subEditor.ReconstructLayers(); }
}
///
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/RoundSound.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/RoundSound.cs
index ca438606f..ac2eeb854 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Map/RoundSound.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Map/RoundSound.cs
@@ -16,6 +16,8 @@ namespace Barotrauma
public readonly bool Stream;
public readonly bool IgnoreMuffling;
+ public readonly bool MuteBackgroundMusic;
+
public readonly string? Filename;
private RoundSound(ContentXElement element, Sound sound)
@@ -26,6 +28,7 @@ namespace Barotrauma
Range = element.GetAttributeFloat("range", 1000.0f);
Volume = element.GetAttributeFloat("volume", 1.0f);
IgnoreMuffling = element.GetAttributeBool("dontmuffle", false);
+ MuteBackgroundMusic = element.GetAttributeBool("MuteBackgroundMusic", false);
FrequencyMultiplierRange = new Vector2(1.0f);
string freqMultAttr = element.GetAttributeString("frequencymultiplier", element.GetAttributeString("frequency", "1.0"));
@@ -73,9 +76,16 @@ namespace Barotrauma
}
Sound? existingSound = null;
- if (roundSoundByPath.TryGetValue(filename.FullPath, out RoundSound? rs) && rs.Sound is { Disposed: false })
+ if (roundSoundByPath.TryGetValue(filename.FullPath, out RoundSound? rs))
{
- existingSound = rs.Sound;
+ if (rs.Sound is { Disposed: false })
+ {
+ existingSound = rs.Sound;
+ }
+ else
+ {
+ roundSoundByPath.Remove(filename.FullPath);
+ }
}
if (existingSound is null)
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Structure.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Structure.cs
index 5f012c8a0..f995583ef 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Map/Structure.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Structure.cs
@@ -328,11 +328,11 @@ namespace Barotrauma
Vector2 max = new Vector2(worldRect.Right, worldRect.Y + worldRect.Height);
foreach (DecorativeSprite decorativeSprite in Prefab.DecorativeSprites)
{
- float scale = decorativeSprite.GetScale(spriteAnimState[decorativeSprite].RandomScaleFactor) * Scale;
- min.X = Math.Min(worldPos.X - decorativeSprite.Sprite.size.X * decorativeSprite.Sprite.RelativeOrigin.X * scale, min.X);
- max.X = Math.Max(worldPos.X + decorativeSprite.Sprite.size.X * (1.0f - decorativeSprite.Sprite.RelativeOrigin.X) * scale, max.X);
- min.Y = Math.Min(worldPos.Y - decorativeSprite.Sprite.size.Y * (1.0f - decorativeSprite.Sprite.RelativeOrigin.Y) * scale, min.Y);
- max.Y = Math.Max(worldPos.Y + decorativeSprite.Sprite.size.Y * decorativeSprite.Sprite.RelativeOrigin.Y * scale, max.Y);
+ Vector2 scale = decorativeSprite.GetScale(ref spriteAnimState[decorativeSprite].ScaleState, spriteAnimState[decorativeSprite].RandomScaleFactor) * Scale;
+ min.X = Math.Min(worldPos.X - decorativeSprite.Sprite.size.X * decorativeSprite.Sprite.RelativeOrigin.X * scale.X, min.X);
+ max.X = Math.Max(worldPos.X + decorativeSprite.Sprite.size.X * (1.0f - decorativeSprite.Sprite.RelativeOrigin.X) * scale.X, max.X);
+ min.Y = Math.Min(worldPos.Y - decorativeSprite.Sprite.size.Y * (1.0f - decorativeSprite.Sprite.RelativeOrigin.Y) * scale.Y, min.Y);
+ max.Y = Math.Max(worldPos.Y + decorativeSprite.Sprite.size.Y * decorativeSprite.Sprite.RelativeOrigin.Y * scale.Y, max.Y);
}
Vector2 offset = GetCollapseEffectOffset();
min += offset;
@@ -341,6 +341,9 @@ namespace Barotrauma
if (min.X > worldView.Right || max.X < worldView.X) { return false; }
if (min.Y > worldView.Y || max.Y < worldView.Y - worldView.Height) { return false; }
+ Vector2 extents = max - min;
+ if (extents.X * Screen.Selected.Cam.Zoom < 1.0f) { return false; }
+ if (extents.Y * Screen.Selected.Cam.Zoom < 1.0f) { return false; }
return true;
}
@@ -368,7 +371,7 @@ namespace Barotrauma
return SpriteDepthOverrideIsSet ? SpriteOverrideDepth : Prefab.Sprite.Depth;
}
- public float GetDrawDepth()
+ public override float GetDrawDepth()
{
return GetDrawDepth(GetRealDepth(), Prefab.Sprite);
}
@@ -448,7 +451,7 @@ namespace Barotrauma
MathUtils.PositiveModulo(-textureOffset.X, Prefab.BackgroundSprite.SourceRect.Width * TextureScale.X * Scale),
MathUtils.PositiveModulo(-textureOffset.Y, Prefab.BackgroundSprite.SourceRect.Height * TextureScale.Y * Scale));
- float rotationRad = rotationForSprite(this.rotationRad, Prefab.BackgroundSprite);
+ float rotationRad = GetRotationForSprite(this.rotationRad, Prefab.BackgroundSprite);
Prefab.BackgroundSprite.DrawTiled(
spriteBatch,
@@ -489,7 +492,7 @@ namespace Barotrauma
advanceY = advanceY.FlipX();
}
- float sectionSpriteRotationRad = rotationForSprite(this.rotationRad, Prefab.Sprite);
+ float sectionSpriteRotationRad = GetRotationForSprite(this.rotationRad, Prefab.Sprite);
for (int i = 0; i < Sections.Length; i++)
{
@@ -498,16 +501,19 @@ namespace Barotrauma
{
float newCutoff = MathHelper.Lerp(0.0f, 0.65f, Sections[i].damage / MaxHealth);
- if (Math.Abs(newCutoff - Submarine.DamageEffectCutoff) > 0.01f || color != Submarine.DamageEffectColor)
+ if (Math.Abs(newCutoff - Submarine.DamageEffectCutoff) > 0.05f)
{
+ spriteBatch.End();
+ spriteBatch.Begin(SpriteSortMode.BackToFront,
+ BlendState.NonPremultiplied, SamplerState.LinearWrap,
+ null, null,
+ damageEffect,
+ Screen.Selected.Cam.Transform);
+
damageEffect.Parameters["aCutoff"].SetValue(newCutoff);
damageEffect.Parameters["cCutoff"].SetValue(newCutoff * 1.2f);
- damageEffect.Parameters["inColor"].SetValue(color.ToVector4());
-
damageEffect.CurrentTechnique.Passes[0].Apply();
-
Submarine.DamageEffectCutoff = newCutoff;
- Submarine.DamageEffectColor = color;
}
}
if (!HasDamage && i == 0)
@@ -560,15 +566,20 @@ namespace Barotrauma
pos: drawPos.FlipY(),
color: color,
rotate: rotation,
- scale: decorativeSprite.GetScale(spriteAnimState[decorativeSprite].RandomScaleFactor) * Scale,
+ origin: decorativeSprite.Sprite.Origin,
+ scale: decorativeSprite.GetScale(ref spriteAnimState[decorativeSprite].ScaleState, spriteAnimState[decorativeSprite].RandomScaleFactor) * Scale,
spriteEffect: Prefab.Sprite.effects ^ SpriteEffects,
depth: Math.Min(depth + (decorativeSprite.Sprite.Depth - Prefab.Sprite.Depth), 0.999f));
}
}
- static float rotationForSprite(float rotationRad, Sprite sprite)
+ static float GetRotationForSprite(float rotationRad, Sprite sprite)
{
- if (sprite.effects.HasFlag(SpriteEffects.FlipHorizontally) != sprite.effects.HasFlag(SpriteEffects.FlipVertically))
+ // Use bitwise operations instead of HasFlag to avoid boxing, as this is performance-sensitive code.
+ bool flipHorizontally = (sprite.effects & SpriteEffects.FlipHorizontally) == SpriteEffects.FlipHorizontally;
+ bool flipVertically = (sprite.effects & SpriteEffects.FlipVertically) == SpriteEffects.FlipVertically;
+
+ if (flipHorizontally != flipVertically)
{
rotationRad = -rotationRad;
}
@@ -600,6 +611,10 @@ namespace Barotrauma
if (GetSection(i).damage > 0)
{
var textPos = SectionPosition(i, true);
+ if (Submarine != null)
+ {
+ textPos += (Submarine.DrawPosition - Submarine.Position);
+ }
textPos.Y = -textPos.Y;
GUI.DrawString(spriteBatch, textPos, "Damage: " + (int)((GetSection(i).damage / MaxHealth) * 100f) + "%", Color.Yellow);
}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/StructurePrefab.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/StructurePrefab.cs
index 587e21dab..7603d3dd6 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Map/StructurePrefab.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Map/StructurePrefab.cs
@@ -89,9 +89,13 @@ namespace Barotrauma
newRect = Submarine.AbsRect(placePosition, placeSize);
}
- Sprite.DrawTiled(spriteBatch, new Vector2(newRect.X, -newRect.Y), new Vector2(newRect.Width, newRect.Height), textureScale: TextureScale * Scale);
- GUI.DrawRectangle(spriteBatch, new Rectangle(newRect.X - GameMain.GraphicsWidth, -newRect.Y, newRect.Width + GameMain.GraphicsWidth * 2, newRect.Height), Color.White);
- GUI.DrawRectangle(spriteBatch, new Rectangle(newRect.X, -newRect.Y - GameMain.GraphicsHeight, newRect.Width, newRect.Height + GameMain.GraphicsHeight * 2), Color.White);
+ Sprite.DrawTiled(spriteBatch, new Vector2(newRect.X, -newRect.Y), new Vector2(newRect.Width, newRect.Height), textureScale: TextureScale * Scale, color: SpriteColor);
+
+ float thickness = Math.Max(1.0f / cam.Zoom, 1.0f);
+ int zoomInvariantWidth = (int)(GameMain.GraphicsWidth / cam.Zoom);
+ int zoomInvariantHeight = (int)(GameMain.GraphicsHeight / cam.Zoom);
+ GUI.DrawRectangle(spriteBatch, new Rectangle(newRect.X - zoomInvariantWidth, -newRect.Y, newRect.Width + zoomInvariantWidth * 2, newRect.Height), Color.White, thickness: thickness);
+ GUI.DrawRectangle(spriteBatch, new Rectangle(newRect.X, -newRect.Y - zoomInvariantHeight, newRect.Width, newRect.Height + zoomInvariantHeight * 2), Color.White, thickness: thickness);
}
public override void DrawPlacing(SpriteBatch spriteBatch, Rectangle placeRect, float scale = 1.0f, float rotation = 0.0f, SpriteEffects spriteEffects = SpriteEffects.None)
@@ -103,7 +107,7 @@ namespace Barotrauma
spriteBatch,
position,
placeRect.Size.ToVector2(),
- color: Color.White * 0.8f,
+ color: SpriteColor * 0.8f,
origin: placeRect.Size.ToVector2() * 0.5f,
rotation: rotation,
textureScale: TextureScale * scale,
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Submarine.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Submarine.cs
index 490077abb..be8ac5b7b 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Map/Submarine.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Submarine.cs
@@ -134,7 +134,7 @@ namespace Barotrauma
foreach (Submarine sub in Loaded)
{
Rectangle worldBorders = sub.Borders;
- worldBorders.Location += sub.WorldPosition.ToPoint();
+ worldBorders.Location += (sub.DrawPosition + sub.HiddenSubPosition).ToPoint();
worldBorders.Y = -worldBorders.Y;
GUI.DrawRectangle(spriteBatch, worldBorders, Color.White, false, 0, 5);
@@ -159,46 +159,38 @@ namespace Barotrauma
}
public static float DamageEffectCutoff;
- public static Color DamageEffectColor;
- private static readonly List depthSortedDamageable = new List();
public static void DrawDamageable(SpriteBatch spriteBatch, Effect damageEffect, bool editing = false, Predicate predicate = null)
{
- var entitiesToRender = !editing && visibleEntities != null ? visibleEntities : MapEntity.MapEntityList;
-
- depthSortedDamageable.Clear();
-
- //insertion sort according to draw depth
- foreach (MapEntity e in entitiesToRender)
+ if (!editing && visibleEntities != null)
{
- if (e is Structure structure && structure.DrawDamageEffect)
+ foreach (MapEntity e in visibleEntities)
{
- if (predicate != null)
+ if (e is Structure structure && structure.DrawDamageEffect)
{
- if (!predicate(e)) { continue; }
+ if (predicate != null)
+ {
+ if (!predicate(structure)) { continue; }
+ }
+ structure.DrawDamage(spriteBatch, damageEffect, editing);
}
- float drawDepth = structure.GetDrawDepth();
- int i = 0;
- while (i < depthSortedDamageable.Count)
+ }
+ }
+ else
+ {
+ foreach (Structure structure in Structure.WallList)
+ {
+ if (structure.DrawDamageEffect)
{
- float otherDrawDepth = depthSortedDamageable[i].GetDrawDepth();
- if (otherDrawDepth < drawDepth) { break; }
- i++;
+ if (predicate != null)
+ {
+ if (!predicate(structure)) { continue; }
+ }
+ structure.DrawDamage(spriteBatch, damageEffect, editing);
}
- depthSortedDamageable.Insert(i, structure);
}
}
- foreach (Structure s in depthSortedDamageable)
- {
- s.DrawDamage(spriteBatch, damageEffect, editing);
- }
- if (damageEffect != null)
- {
- damageEffect.Parameters["aCutoff"].SetValue(0.0f);
- damageEffect.Parameters["cCutoff"].SetValue(0.0f);
- DamageEffectCutoff = 0.0f;
- }
}
public static void DrawPaintedColors(SpriteBatch spriteBatch, bool editing = false, Predicate predicate = null)
@@ -506,6 +498,16 @@ namespace Barotrauma
warnings.Add(SubEditorScreen.WarningType.WaterInHulls);
Hull.ShowHulls = true;
}
+
+ if (Info.IsWreck)
+ {
+ Point vanillaBrainSize = new Point(204, 204);
+ if (WreckAI.GetPotentialBrainRooms(this, WreckAIConfig.GetRandom(), minSize: vanillaBrainSize).None())
+ {
+ errorMsgs.Add(TextManager.Get("NoSuitableBrainRoomsWarning").Value);
+ warnings.Add(SubEditorScreen.WarningType.NoSuitableBrainRooms);
+ }
+ }
if (!IsWarningSuppressed(SubEditorScreen.WarningType.NotEnoughContainers))
{
@@ -656,6 +658,7 @@ namespace Barotrauma
errorMsgs.Add(TextManager.GetWithVariables("InsufficientFreeConnectionsWarning",
("[doorcount]", doorLinks.ToString()),
("[freeconnectioncount]", (item.Connections[i].MaxWires - wireCount).ToString())).Value);
+ warnings.Add(SubEditorScreen.WarningType.InsufficientFreeConnectionsWarning);
break;
}
}
@@ -836,7 +839,7 @@ namespace Barotrauma
subBody.PositionBuffer.Insert(index, posInfo);
}
}
-
+
public void ClientEventRead(IReadMessage msg, float sendingTime)
{
Identifier layerIdentifier = msg.ReadIdentifier();
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/SubmarinePreview.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/SubmarinePreview.cs
index f544b6dc3..c507940b9 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Map/SubmarinePreview.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Map/SubmarinePreview.cs
@@ -572,8 +572,9 @@ namespace Barotrauma
Vector2 offset = decorativeSprite.GetOffset(ref offsetState, Vector2.Zero) * scale;
if (flippedX) { offset.X = -offset.X; }
if (flippedY) { offset.Y = -offset.Y; }
- decorativeSprite.Sprite.Draw(spriteRecorder, new Vector2(spritePos.X + offset.X, -(spritePos.Y + offset.Y)), color,
- rotationRad + rot, decorativeSprite.GetScale(0f) * scale, prefab.Sprite.effects,
+ float throwAway = 0.0f;
+ decorativeSprite.Sprite.Draw(spriteRecorder, new Vector2(spritePos.X + offset.X, -(spritePos.Y + offset.Y)), color, decorativeSprite.Sprite.Origin,
+ rotationRad + rot, decorativeSprite.GetScale(ref throwAway, 0f) * scale, prefab.Sprite.effects,
depth: Math.Min(depth + (decorativeSprite.Sprite.Depth - prefab.Sprite.Depth), 0.999f));
}
}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/WayPoint.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/WayPoint.cs
index 456dbcc0c..c8c38d34a 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Map/WayPoint.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Map/WayPoint.cs
@@ -15,7 +15,8 @@ namespace Barotrauma
public override bool IsVisible(Rectangle worldView)
{
- return Screen.Selected == GameMain.SubEditorScreen || GameMain.DebugDraw;
+ if (Screen.Selected != GameMain.SubEditorScreen && !GameMain.DebugDraw) { return false; }
+ return base.IsVisible(worldView);
}
public override bool SelectableInEditor
@@ -45,6 +46,10 @@ namespace Barotrauma
}
if (IsHighlighted || IsHighlighted) { clr = Color.Lerp(clr, Color.White, 0.8f); }
+ if (Stairs is { Removed: true }) { Stairs = null; }
+ if (Ladders is { Item.Removed: true }) { Ladders = null; }
+ if (ConnectedGap is { Removed: true }) { ConnectedGap = null; }
+
int iconSize = spawnType == SpawnType.Path ? WaypointSize : SpawnPointSize;
if (ConnectedDoor != null || Ladders != null || Stairs != null || SpawnType != SpawnType.Path)
{
@@ -92,6 +97,11 @@ namespace Barotrauma
if (sprite != null)
{
float spriteScale = iconSize / (float)sprite.SourceRect.Width;
+ if (Ladders == null && ConnectedDoor == null && ConnectedGap != null)
+ {
+ clr = Color.White;
+ spriteScale *= 1.5f;
+ }
sprite.Draw(spriteBatch, drawPos, clr, origin: sprite.size / 2, scale: spriteScale, depth: 0.001f);
sprite2?.Draw(spriteBatch, drawPos + sprite.size * spriteScale * 0.5f, clr, origin: sprite2.size / 2, scale: spriteScale, depth: 0.001f);
}
@@ -100,27 +110,39 @@ namespace Barotrauma
{
AssignedJob.Icon.Draw(spriteBatch, drawPos, AssignedJob.UIColor, scale: iconSize / (float)AssignedJob.Icon.SourceRect.Width * 0.8f, depth: 0.0f);
}
-
- foreach (MapEntity e in linkedTo)
+
+ // alternate line drawing for when cloning the waypoint: line goes from current position to original position, where moving started
+ if (StartMovingPos != Vector2.Zero && SelectedList.Contains(this) && PlayerInput.IsCtrlDown())
{
GUI.DrawLine(spriteBatch,
drawPos,
- new Vector2(e.DrawPosition.X, -e.DrawPosition.Y),
+ new Vector2(StartMovingPos.X, -StartMovingPos.Y),
(IsTraversable ? GUIStyle.Green : Color.Gray) * 0.7f, width: 5, depth: 0.002f);
}
+ else
+ {
+ foreach (MapEntity e in linkedTo)
+ {
+ GUI.DrawLine(spriteBatch,
+ drawPos,
+ new Vector2(e.DrawPosition.X, -e.DrawPosition.Y),
+ (IsTraversable ? GUIStyle.Green : Color.Gray) * 0.7f, width: 5, depth: 0.002f);
+ }
+ }
+
if (ConnectedGap != null)
{
GUI.DrawLine(spriteBatch,
drawPos,
new Vector2(ConnectedGap.DrawPosition.X, -ConnectedGap.DrawPosition.Y),
- GUIStyle.Green * 0.5f, width: 1);
+ Color.White, width: 1);
}
if (Ladders != null)
{
GUI.DrawLine(spriteBatch,
drawPos,
new Vector2(Ladders.Item.DrawPosition.X, -Ladders.Item.DrawPosition.Y),
- GUIStyle.Green * 0.5f, width: 1);
+ Color.White, width: 1);
}
var color = Color.WhiteSmoke;
@@ -419,6 +441,7 @@ namespace Barotrauma
jobDropDown.AddItem(TextManager.Get("Any"), null);
foreach (JobPrefab jobPrefab in JobPrefab.Prefabs)
{
+ if (jobPrefab.Name.IsNullOrWhiteSpace()) { continue; }
jobDropDown.AddItem(jobPrefab.Name, jobPrefab);
}
jobDropDown.SelectItem(AssignedJob);
@@ -432,7 +455,7 @@ namespace Barotrauma
};
propertyBox.OnTextChanged += (textBox, text) =>
{
- tags = text.Split(',').ToIdentifiers().ToHashSet();
+ tags = text.ToIdentifiers().ToHashSet();
return true;
};
propertyBox.OnEnterPressed += (textBox, text) =>
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/Client.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/Client.cs
index 4d31e3c57..80117af12 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Networking/Client.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/Client.cs
@@ -120,7 +120,7 @@ namespace Barotrauma.Networking
if (radioNoiseChannel == null || !radioNoiseChannel.IsPlaying)
{
radioNoiseChannel = SoundPlayer.PlaySound("radiostatic");
- radioNoiseChannel.Category = "voip";
+ radioNoiseChannel.Category = SoundManager.SoundCategoryVoip;
radioNoiseChannel.Looping = true;
}
radioNoiseChannel.Near = VoipSound.Near;
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/ConnectCommand.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/ConnectCommand.cs
index 52a8d476c..a0ce32aef 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Networking/ConnectCommand.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/ConnectCommand.cs
@@ -50,25 +50,29 @@ readonly record struct ConnectCommand(
NameAndLidgrenEndpointOption: endpoint is LidgrenEndpoint lidgrenEndpoint
? Option.Some(new NameAndLidgrenEndpoint(ServerName: serverName, lidgrenEndpoint))
: Option.None,
- SteamLobbyIdOption: Option.None) { }
+ SteamLobbyIdOption: Option.None)
+ { }
public ConnectCommand(string serverName, ImmutableArray endpoints)
: this(
NameAndP2PEndpointsOption: Option.Some(new NameAndP2PEndpoints(ServerName: serverName, Endpoints: endpoints)),
NameAndLidgrenEndpointOption: Option.None,
- SteamLobbyIdOption: Option.None) { }
+ SteamLobbyIdOption: Option.None)
+ { }
public ConnectCommand(string serverName, LidgrenEndpoint endpoint)
: this(
NameAndP2PEndpointsOption: Option.None,
NameAndLidgrenEndpointOption: Option.Some(new NameAndLidgrenEndpoint(ServerName: serverName, Endpoint: endpoint)),
- SteamLobbyIdOption: Option.None) { }
+ SteamLobbyIdOption: Option.None)
+ { }
public ConnectCommand(SteamLobbyId lobbyId)
: this(
NameAndP2PEndpointsOption: Option.None,
NameAndLidgrenEndpointOption: Option.None,
- SteamLobbyIdOption: Option.Some(lobbyId)) { }
+ SteamLobbyIdOption: Option.Some(lobbyId))
+ { }
public static Option Parse(string str)
=> Parse(ToolBox.SplitCommand(str));
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/FileTransfer/FileReceiver.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/FileTransfer/FileReceiver.cs
index 1a9fb2c71..5bda4ceb1 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Networking/FileTransfer/FileReceiver.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/FileTransfer/FileReceiver.cs
@@ -260,7 +260,7 @@ namespace Barotrauma.Networking
{
try
{
- Directory.CreateDirectory(downloadFolder);
+ Directory.CreateDirectory(downloadFolder, catchUnauthorizedAccessExceptions: false);
}
catch (Exception e)
{
@@ -572,7 +572,7 @@ namespace Barotrauma.Networking
{
try
{
- File.Delete(transfer.FilePath);
+ File.Delete(transfer.FilePath, catchUnauthorizedAccessExceptions: false);
}
catch (Exception e)
{
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs
index 3b43b5c1a..3f708532b 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs
@@ -7,9 +7,11 @@ using Microsoft.Xna.Framework.Input;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
+using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using System.Xml.Linq;
+using Barotrauma.PerkBehaviors;
namespace Barotrauma.Networking
{
@@ -38,11 +40,12 @@ namespace Barotrauma.Networking
{
if (string.IsNullOrEmpty(value)) { return; }
Name = value;
- nameId++;
+ ForceNameJobTeamUpdate();
}
- public void ForceNameAndJobUpdate()
+ public void ForceNameJobTeamUpdate()
{
+ // Deviously triggers SendLobbyUpdate() which causes the server to call GameServer.ClientReadLobby()
nameId++;
}
@@ -137,13 +140,14 @@ namespace Barotrauma.Networking
}
}
+ public Client MyClient => ConnectedClients.FirstOrDefault(c => c.SessionId == SessionId);
+
public Option Ping
{
get
{
- Client selfClient = ConnectedClients.FirstOrDefault(c => c.SessionId == SessionId);
- if (selfClient is null || selfClient.Ping == 0) { return Option.None(); }
- return Option.Some(selfClient.Ping);
+ if (MyClient is null || MyClient.Ping == 0) { return Option.None(); }
+ return Option.Some(MyClient.Ping);
}
}
@@ -485,14 +489,13 @@ namespace Barotrauma.Networking
{
if (VoipCapture.Instance.LastEnqueueAudio > DateTime.Now - new TimeSpan(0, 0, 0, 0, milliseconds: 100))
{
- var myClient = ConnectedClients.Find(c => c.SessionId == SessionId);
if (Screen.Selected == GameMain.NetLobbyScreen)
{
- GameMain.NetLobbyScreen.SetPlayerSpeaking(myClient);
+ GameMain.NetLobbyScreen.SetPlayerSpeaking(MyClient);
}
else
{
- GameMain.GameSession?.CrewManager?.SetClientSpeaking(myClient);
+ GameMain.GameSession?.CrewManager?.SetClientSpeaking(MyClient);
}
}
}
@@ -516,10 +519,12 @@ namespace Barotrauma.Networking
{
string errorMsg = "Error while reading a message from server. ";
if (GameMain.Client == null) { errorMsg += "Client disposed."; }
- AppendExceptionInfo(ref errorMsg, e);
- GameAnalyticsManager.AddErrorEventOnce("GameClient.Update:CheckServerMessagesException" + e.TargetSite.ToString(), GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
- DebugConsole.ThrowError(errorMsg);
- new GUIMessageBox(TextManager.Get("Error"), TextManager.GetWithVariables("MessageReadError", ("[message]", e.Message), ("[targetsite]", e.TargetSite.ToString())))
+ AppendExceptionInfo(ref errorMsg, out Entity causingEntity, e);
+
+ string targetSite = e.TargetSite?.ToString() ?? "unknown";
+ GameAnalyticsManager.AddErrorEventOnce("GameClient.Update:CheckServerMessagesException" + targetSite, GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
+ DebugConsole.ThrowError(errorMsg, contentPackage: causingEntity?.ContentPackage);
+ new GUIMessageBox(TextManager.Get("Error"), TextManager.GetWithVariables("MessageReadError", ("[message]", e.Message), ("[targetsite]", targetSite)))
{
DisplayInLoadingScreens = true
};
@@ -579,6 +584,9 @@ namespace Barotrauma.Networking
private readonly List pendingIncomingMessages = new List();
private readonly List incomingMessagesToProcess = new List();
+ private CoroutineHandle startGameCoroutine;
+ private bool requestNewRoundStart;
+
private void ReadDataMessage(IReadMessage inc)
{
ServerPacketHeader header = (ServerPacketHeader)inc.ReadByte();
@@ -659,7 +667,7 @@ namespace Barotrauma.Networking
catch (Exception e)
{
string errorMsg = "Error while reading an ingame update message from server.";
- AppendExceptionInfo(ref errorMsg, e);
+ AppendExceptionInfo(ref errorMsg, out Entity causingEntity, e);
GameAnalyticsManager.AddErrorEventOnce("GameClient.ReadDataMessage:ReadIngameUpdate", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
throw;
}
@@ -689,6 +697,16 @@ namespace Barotrauma.Networking
string subName = inc.ReadString();
string subHash = inc.ReadString();
+ bool hasEnemySub = inc.ReadBoolean();
+
+ string enemySubName = subName;
+ string enemySubHash = subHash;
+ if (hasEnemySub)
+ {
+ enemySubName = inc.ReadString();
+ enemySubHash = inc.ReadString();
+ }
+
bool usingShuttle = inc.ReadBoolean();
string shuttleName = inc.ReadString();
string shuttleHash = inc.ReadString();
@@ -701,16 +719,18 @@ namespace Barotrauma.Networking
campaignUpdateIDs[flag] = inc.ReadUInt16();
}
- IWriteMessage readyToStartMsg = new WriteOnlyMessage();
- readyToStartMsg.WriteByte((byte)ClientPacketHeader.RESPONSE_STARTGAME);
-
if (campaign != null) { campaign.PendingSubmarineSwitch = null; }
GameMain.NetLobbyScreen.UsingShuttle = usingShuttle;
bool readyToStart;
if (campaign == null && campaignID == 0)
{
- readyToStart = GameMain.NetLobbyScreen.TrySelectSub(subName, subHash, GameMain.NetLobbyScreen.SubList) &&
- GameMain.NetLobbyScreen.TrySelectSub(shuttleName, shuttleHash, GameMain.NetLobbyScreen.ShuttleList.ListBox);
+ readyToStart = GameMain.NetLobbyScreen.TrySelectSub(subName, subHash, SelectedSubType.Sub, GameMain.NetLobbyScreen.SubList) &&
+ GameMain.NetLobbyScreen.TrySelectSub(shuttleName, shuttleHash, SelectedSubType.Shuttle, GameMain.NetLobbyScreen.ShuttleList.ListBox);
+
+ if (hasEnemySub && !GameMain.NetLobbyScreen.TrySelectSub(enemySubName, enemySubHash, SelectedSubType.EnemySub, GameMain.NetLobbyScreen.SubList))
+ {
+ readyToStart = false;
+ }
}
else
{
@@ -720,30 +740,63 @@ namespace Barotrauma.Networking
campaign.LastSaveID == campaignSaveID &&
campaignUpdateIDs.All(kvp => campaign.GetLastUpdateIdForFlag(kvp.Key) == kvp.Value);
}
- readyToStartMsg.WriteBoolean(readyToStart);
DebugConsole.Log(readyToStart ? "Ready to start." : "Not ready to start.");
-
- WriteCharacterInfo(readyToStartMsg);
-
- ClientPeer.Send(readyToStartMsg, DeliveryMethod.Reliable);
+ SendStartGameResponse(readyToStart: readyToStart);
if (readyToStart && !CoroutineManager.IsCoroutineRunning("WaitForStartRound"))
{
CoroutineManager.StartCoroutine(NetLobbyScreen.WaitForStartRound(startButton: null), "WaitForStartRound");
}
break;
+ case ServerPacketHeader.WARN_STARTGAME:
+ DebugConsole.Log("Received WARN_STARTGAME packet.");
+
+ RoundStartWarningData warningData = INetSerializableStruct.Read(inc);
+ var team1IncompatiblePerks = ToolBox.UintIdentifierArrayToPrefabCollection(DisembarkPerkPrefab.Prefabs, warningData.Team1IncompatiblePerks);
+ var team2IncompatiblePerks = ToolBox.UintIdentifierArrayToPrefabCollection(DisembarkPerkPrefab.Prefabs, warningData.Team2IncompatiblePerks);
+
+ GameMain.NetLobbyScreen?.ShowStartRoundWarning(SerializableDateTime.UtcNow + TimeSpan.FromSeconds(warningData.RoundStartsAnywaysTimeInSeconds), warningData.Team1Sub, team1IncompatiblePerks, warningData.Team2Sub, team2IncompatiblePerks);
+ break;
+ case ServerPacketHeader.CANCEL_STARTGAME:
+ DebugConsole.Log("Received CANCEL_STARTGAME packet.");
+ GameMain.NetLobbyScreen?.CloseStartRoundWarning();
+ if (GameMain.NetLobbyScreen?.ReadyToStartBox is { } readyToStartBox)
+ {
+ readyToStartBox.Selected = false;
+ SetReadyToStart(readyToStartBox);
+ }
+ break;
case ServerPacketHeader.STARTGAME:
DebugConsole.Log("Received STARTGAME packet.");
- if (Screen.Selected == GameMain.GameScreen && GameMain.GameSession?.GameMode is CampaignMode)
+ if (GameMain.NetLobbyScreen is not { AFKSelected: true } || !ServerSettings.AllowAFK)
{
- //start without a loading screen if playing a campaign round
- CoroutineManager.StartCoroutine(StartGame(inc));
+ if (startGameCoroutine != null && CoroutineManager.IsCoroutineRunning(startGameCoroutine))
+ {
+ DebugConsole.Log("New round started before the previous one had finished loading. Starting a new round once loading the round finishes...");
+ requestNewRoundStart = true;
+ }
+ else
+ {
+ if (Screen.Selected == GameMain.GameScreen && GameMain.GameSession?.GameMode is CampaignMode)
+ {
+ //start without a loading screen if playing a campaign round
+ DebugConsole.Log($"Starting {nameof(StartGame)} coroutine...");
+ startGameCoroutine = CoroutineManager.StartCoroutine(StartGame(inc));
+ }
+ else
+ {
+ GUIMessageBox.CloseAll();
+ DebugConsole.Log($"Starting {nameof(StartGame)} coroutine with a loading screen...");
+ startGameCoroutine = GameMain.Instance.ShowLoading(StartGame(inc), false);
+ }
+ }
}
else
{
- GUIMessageBox.CloseAll();
- GameMain.Instance.ShowLoading(StartGame(inc), false);
+ //reselect to refresh the state of the screen (to indicate the round is running)
+ GameStarted = true;
+ GameMain.NetLobbyScreen?.Select();
}
break;
case ServerPacketHeader.STARTGAMEFINALIZE:
@@ -869,6 +922,9 @@ namespace Barotrauma.Networking
case ServerPacketHeader.EVENTACTION:
GameMain.GameSession?.EventManager.ClientRead(inc);
break;
+ case ServerPacketHeader.SEND_BACKUP_INDICES:
+ GameMain.NetLobbyScreen?.CampaignSetupUI?.OnBackupIndicesReceived(inc);
+ break;
}
}
@@ -887,10 +943,19 @@ namespace Barotrauma.Networking
contentToPreload.AddIfNotNull(file);
}
+ byte roundId = inc.ReadByte();
+
string campaignErrorInfo = string.Empty;
if (GameMain.GameSession?.Campaign is MultiPlayerCampaign campaign)
{
+ if (roundId != campaign.RoundID)
+ {
+ DebugConsole.AddWarning($"Received a StartGameFinalize message for an incorrect round (client: {campaign.RoundID}, server: {roundId}). The server might have started a new round before the client finished loading the previous one.");
+ requestNewRoundStart = true;
+ return;
+ }
campaignErrorInfo = $" Round start save ID: {debugStartGameCampaignSaveID}, last save id: {campaign.LastSaveID}, pending save id: {campaign.PendingSaveID}.";
+
}
GameMain.GameSession.EventManager.PreloadContent(contentToPreload);
@@ -947,13 +1012,15 @@ namespace Barotrauma.Networking
{
if (Level.Loaded.EqualityCheckValues[stage] != levelEqualityCheckValues[stage])
{
- string errorMsg = "Level equality check failed. The level generated at your end doesn't match the level generated by the server" +
- " (client value " + stage + ": " + Level.Loaded.EqualityCheckValues[stage].ToString("X") +
- ", server value " + stage + ": " + levelEqualityCheckValues[stage].ToString("X") +
- ", level value count: " + levelEqualityCheckValues.Count +
- ", seed: " + Level.Loaded.Seed +
- ", sub: " + Submarine.MainSub.Info.Name + " (" + Submarine.MainSub.Info.MD5Hash.ShortRepresentation + ")" +
- ", mirrored: " + Level.Loaded.Mirrored + "). Round init status: " + roundInitStatus + "." + campaignErrorInfo;
+ string errorMsg = "Level equality check failed. The level generated at your end doesn't match the level generated by the server, " +
+ $"(client value {stage}:{Level.Loaded.EqualityCheckValues[stage].ToString("X")}, " +
+ $"server value {stage}: {levelEqualityCheckValues[stage].ToString("X")}, " +
+ $"level value count: {levelEqualityCheckValues.Count}, " +
+ $"seed: {Level.Loaded.Seed}, " +
+ $"missions: {string.Join(", ", GameMain.GameSession.GameMode.Missions.Select(m => m.Prefab.Identifier))}, " +
+ $"sub: {(Submarine.MainSub == null ? "null" : (Submarine.MainSub.Info.Name + " (" + Submarine.MainSub.Info.MD5Hash.ShortRepresentation))}, " +
+ $"mirrored: {Level.Loaded.Mirrored}). Round init status: {roundInitStatus}." +
+ campaignErrorInfo;
GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:LevelsDontMatch" + Level.Loaded.Seed, GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
throw new Exception(errorMsg);
}
@@ -969,9 +1036,24 @@ namespace Barotrauma.Networking
CrewManager.ClientReadActiveOrders(inc);
}
+ if (inc.ReadBoolean())
+ {
+ ApplyDisembarkPerk();
+ }
+
roundInitStatus = RoundInitStatus.Started;
}
+ private void ApplyDisembarkPerk()
+ {
+ var characters = GameSession.GetSessionCrewCharacters(CharacterType.Both);
+
+ ImmutableArray team1Characters = characters.Where(static c => c.TeamID is CharacterTeamType.Team1).ToImmutableArray(),
+ team2Characters = characters.Where(static c => c.TeamID is CharacterTeamType.Team2).ToImmutableArray();
+
+ GameSession.GetPerks().ApplyAll(team1Characters, team2Characters);
+ }
+
///
/// Fires when the ClientPeer gets disconnected from the server. Does not necessarily mean the client is shutting down, we may still be able to reconnect.
///
@@ -1349,6 +1431,8 @@ namespace Barotrauma.Networking
private IEnumerable StartGame(IReadMessage inc)
{
+ DebugConsole.Log($"Running {nameof(StartGame)} coroutine");
+
Character?.Remove();
Character = null;
HasSpawned = false;
@@ -1384,6 +1468,7 @@ namespace Barotrauma.Networking
{
DebugConsole.ThrowError("Game mode \"" + modeIdentifier + "\" not found!");
roundInitStatus = RoundInitStatus.Interrupted;
+ startGameCoroutine = null;
yield return CoroutineStatus.Failure;
}
@@ -1396,11 +1481,16 @@ namespace Barotrauma.Networking
ServerSettings.LockAllDefaultWires = inc.ReadBoolean();
ServerSettings.AllowLinkingWifiToChat = inc.ReadBoolean();
ServerSettings.MaximumMoneyTransferRequest = inc.ReadInt32();
+ ServerSettings.RespawnMode = (RespawnMode)inc.ReadByte();
bool usingShuttle = GameMain.NetLobbyScreen.UsingShuttle = inc.ReadBoolean();
GameMain.LightManager.LosMode = (LosMode)inc.ReadByte();
ServerSettings.ShowEnemyHealthBars = (EnemyHealthBarMode)inc.ReadByte();
bool includesFinalize = inc.ReadBoolean(); inc.ReadPadBits();
+
GameMain.LightManager.LightingEnabled = true;
+#if DEBUG
+ GameMain.LightManager.LightingEnabled = !GameMain.DevMode;
+#endif
ServerSettings.ReadMonsterEnabled(inc);
@@ -1415,25 +1505,47 @@ namespace Barotrauma.Networking
{
string levelSeed = inc.ReadString();
float levelDifficulty = inc.ReadSingle();
+ Identifier biomeId = inc.ReadIdentifier();
string subName = inc.ReadString();
string subHash = inc.ReadString();
string shuttleName = inc.ReadString();
string shuttleHash = inc.ReadString();
+
+ bool hasEnemySub = inc.ReadBoolean();
+ string enemySubName = subName;
+ string enemySubHash = subHash;
+ if (hasEnemySub)
+ {
+ enemySubName = inc.ReadString();
+ enemySubHash = inc.ReadString();
+ }
+
List missionHashes = new List();
int missionCount = inc.ReadByte();
for (int i = 0; i < missionCount; i++)
{
missionHashes.Add(inc.ReadUInt32());
}
- if (!GameMain.NetLobbyScreen.TrySelectSub(subName, subHash, GameMain.NetLobbyScreen.SubList))
+ if (!GameMain.NetLobbyScreen.TrySelectSub(subName, subHash, SelectedSubType.Sub, GameMain.NetLobbyScreen.SubList))
{
roundInitStatus = RoundInitStatus.Interrupted;
+ startGameCoroutine = null;
yield return CoroutineStatus.Success;
}
- if (!GameMain.NetLobbyScreen.TrySelectSub(shuttleName, shuttleHash, GameMain.NetLobbyScreen.ShuttleList.ListBox))
+ if (hasEnemySub)
+ {
+ if (!GameMain.NetLobbyScreen.TrySelectSub(enemySubName, enemySubHash, SelectedSubType.EnemySub, GameMain.NetLobbyScreen.SubList))
+ {
+ roundInitStatus = RoundInitStatus.Interrupted;
+ yield return CoroutineStatus.Success;
+ }
+ }
+
+ if (!GameMain.NetLobbyScreen.TrySelectSub(shuttleName, shuttleHash, SelectedSubType.Shuttle, GameMain.NetLobbyScreen.ShuttleList.ListBox))
{
roundInitStatus = RoundInitStatus.Interrupted;
+ startGameCoroutine = null;
yield return CoroutineStatus.Success;
}
@@ -1463,6 +1575,7 @@ namespace Barotrauma.Networking
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:FailedToSelectSub" + subName, GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
roundInitStatus = RoundInitStatus.Interrupted;
+ startGameCoroutine = null;
yield return CoroutineStatus.Failure;
}
if (GameMain.NetLobbyScreen.SelectedShuttle == null ||
@@ -1475,24 +1588,28 @@ namespace Barotrauma.Networking
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:FailedToSelectShuttle" + shuttleName, GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
roundInitStatus = RoundInitStatus.Interrupted;
+ startGameCoroutine = null;
yield return CoroutineStatus.Failure;
}
var selectedMissions = missionHashes.Select(i => MissionPrefab.Prefabs.Find(p => p.UintIdentifier == i));
- GameMain.GameSession = new GameSession(GameMain.NetLobbyScreen.SelectedSub, gameMode, missionPrefabs: selectedMissions);
- GameMain.GameSession.StartRound(levelSeed, levelDifficulty);
+ var selectedEnemySub = hasEnemySub && GameMain.NetLobbyScreen.SelectedEnemySub is { } enemySub ? Option.Some(enemySub) : Option.None;
+
+ GameMain.GameSession = new GameSession(GameMain.NetLobbyScreen.SelectedSub, selectedEnemySub, gameMode, missionPrefabs: selectedMissions);
+ GameMain.GameSession.StartRound(levelSeed, levelDifficulty, levelGenerationParams: null, forceBiome: biomeId);
}
else
{
- if (!(GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign))
+ if (GameMain.GameSession?.GameMode is not MultiPlayerCampaign campaign)
{
throw new InvalidOperationException("Attempted to start a campaign round when a campaign was not active.");
}
- if (GameMain.GameSession?.CrewManager != null) { GameMain.GameSession.CrewManager.Reset(); }
+ GameMain.GameSession?.CrewManager?.Reset();
byte campaignID = inc.ReadByte();
+ byte roundID = inc.ReadByte();
UInt16 campaignSaveID = inc.ReadUInt16();
int nextLocationIndex = inc.ReadInt32();
int nextConnectionIndex = inc.ReadInt32();
@@ -1505,6 +1622,7 @@ namespace Barotrauma.Networking
DebugConsole.ThrowError("Failed to start campaign round (campaign ID does not match).");
GameMain.NetLobbyScreen.Select();
roundInitStatus = RoundInitStatus.Interrupted;
+ startGameCoroutine = null;
yield return CoroutineStatus.Failure;
}
@@ -1521,6 +1639,7 @@ namespace Barotrauma.Networking
new GUIMessageBox(TextManager.Get("error"), TextManager.Get("campaignsavetransfer.timeout"));
GameMain.NetLobbyScreen.Select();
roundInitStatus = RoundInitStatus.Interrupted;
+ startGameCoroutine = null;
//use success status, even though this is a failure (no need to show a console error because we show it in the message box)
yield return CoroutineStatus.Success;
}
@@ -1534,6 +1653,7 @@ namespace Barotrauma.Networking
DebugConsole.ThrowError("Failed to start campaign round (campaign map not loaded yet).");
GameMain.NetLobbyScreen.Select();
roundInitStatus = RoundInitStatus.Interrupted;
+ startGameCoroutine = null;
yield return CoroutineStatus.Failure;
}
@@ -1547,12 +1667,18 @@ namespace Barotrauma.Networking
if (roundSummary != null)
{
- loadTask = campaign.SelectSummaryScreen(roundSummary, levelData, mirrorLevel, null);
+ loadTask = campaign.SelectSummaryScreen(roundSummary, levelData, mirrorLevel, () =>
+ {
+ DebugConsole.Log($"Set round ID from {campaign.RoundID} to {roundID}.");
+ campaign.RoundID = roundID;
+ });
roundSummary.ContinueButton.Visible = false;
}
else
{
GameMain.GameSession.StartRound(levelData, mirrorLevel, startOutpost: campaign?.GetPredefinedStartOutpost());
+ DebugConsole.Log($"Set round ID from {campaign.RoundID} to {roundID}.");
+ campaign.RoundID = roundID;
}
isOutpost = levelData.Type == LevelData.LevelType.Outpost;
}
@@ -1571,9 +1697,16 @@ namespace Barotrauma.Networking
{
DebugConsole.ThrowError("There was an error initializing the round (disconnected during the StartGame coroutine.)");
roundInitStatus = RoundInitStatus.Error;
+ startGameCoroutine = null;
yield return CoroutineStatus.Failure;
}
+ if (requestNewRoundStart)
+ {
+ RequestNewRoundStart();
+ yield return CoroutineStatus.Success;
+ }
+
roundInitStatus = RoundInitStatus.WaitingForStartGameFinalize;
//wait for up to 30 seconds for the server to send the STARTGAMEFINALIZE message
@@ -1595,6 +1728,12 @@ namespace Barotrauma.Networking
{
while (true)
{
+ if (requestNewRoundStart)
+ {
+ RequestNewRoundStart();
+ yield return CoroutineStatus.Success;
+ }
+
try
{
if (DateTime.Now > requestFinalizeTime)
@@ -1659,17 +1798,21 @@ namespace Barotrauma.Networking
{
DebugConsole.ThrowError(roundInitStatus.ToString());
CoroutineManager.StartCoroutine(EndGame(""));
+ startGameCoroutine = null;
yield return CoroutineStatus.Failure;
}
else
{
+ startGameCoroutine = null;
yield return CoroutineStatus.Success;
}
}
- if (GameMain.GameSession.Submarine.Info.IsFileCorrupted)
+ if (GameMain.GameSession.Submarine != null &&
+ GameMain.GameSession.Submarine.Info.IsFileCorrupted)
{
DebugConsole.ThrowError($"Failed to start a round. Could not load the submarine \"{GameMain.GameSession.Submarine.Info.Name}\".");
+ startGameCoroutine = null;
yield return CoroutineStatus.Failure;
}
@@ -1717,6 +1860,17 @@ namespace Barotrauma.Networking
AddChatMessage(message, ChatMessageType.Server);
yield return CoroutineStatus.Success;
+
+ void RequestNewRoundStart()
+ {
+ GameMain.GameSession?.EndRound("");
+ GameMain.NetLobbyScreen.Select();
+ CoroutineManager.StopCoroutines("LevelTransition");
+ roundInitStatus = RoundInitStatus.Error;
+ startGameCoroutine = null;
+ SendJoinOngoingRequest(joinButton: null);
+ requestNewRoundStart = false;
+ }
}
public IEnumerable EndGame(string endMessage, CampaignMode.TransitionType transitionType = CampaignMode.TransitionType.None, TraitorManager.TraitorResults? traitorResults = null)
@@ -1759,12 +1913,15 @@ namespace Barotrauma.Networking
refSub = Submarine.MainSubs[1];
}
- // Enable characters near the main sub for the endCinematic
- foreach (Character c in Character.CharacterList)
+ if (refSub != null)
{
- if (Vector2.DistanceSquared(refSub.WorldPosition, c.WorldPosition) < MathUtils.Pow2(c.Params.DisableDistance))
+ // Enable characters near the main sub for the endCinematic
+ foreach (Character c in Character.CharacterList)
{
- c.Enabled = true;
+ if (Vector2.DistanceSquared(refSub.WorldPosition, c.WorldPosition) < MathUtils.Pow2(c.Params.DisableDistance))
+ {
+ c.Enabled = true;
+ }
}
}
@@ -1823,6 +1980,7 @@ namespace Barotrauma.Networking
GameStarted = inc.ReadBoolean();
bool allowSpectating = inc.ReadBoolean();
+ bool allowAFK = inc.ReadBoolean();
bool permadeathMode = inc.ReadBoolean();
bool ironmanMode = inc.ReadBoolean();
@@ -1842,7 +2000,7 @@ namespace Barotrauma.Networking
message = TextManager.Get(allowSpectating ? "RoundRunningSpectateEnabled" : "RoundRunningSpectateDisabled");
}
new GUIMessageBox(TextManager.Get("PleaseWait"), message);
- if (!(Screen.Selected is ModDownloadScreen)) { GameMain.NetLobbyScreen.Select(); }
+ if (Screen.Selected is not ModDownloadScreen) { GameMain.NetLobbyScreen.Select(); }
}
}
}
@@ -1851,6 +2009,8 @@ namespace Barotrauma.Networking
{
bool refreshCampaignUI = false;
UInt16 listId = inc.ReadUInt16();
+ GameMain.NetLobbyScreen.Team1Count = inc.ReadByte();
+ GameMain.NetLobbyScreen.Team2Count = inc.ReadByte();
List tempClients = new List();
int clientCount = inc.ReadByte();
for (int i = 0; i < clientCount; i++)
@@ -1883,27 +2043,37 @@ namespace Barotrauma.Networking
existingClient.NameId = tc.NameId;
existingClient.PreferredJob = tc.PreferredJob;
existingClient.PreferredTeam = tc.PreferredTeam;
+ existingClient.TeamID = tc.TeamID;
existingClient.Character = null;
existingClient.Karma = tc.Karma;
existingClient.Muted = tc.Muted;
existingClient.InGame = tc.InGame;
existingClient.IsOwner = tc.IsOwner;
existingClient.IsDownloading = tc.IsDownloading;
- GameMain.NetLobbyScreen.SetPlayerNameAndJobPreference(existingClient);
+ GameMain.NetLobbyScreen.SetPlayerNameAndJobPreference(existingClient); // refresh lobby player list in the local UI
if (Screen.Selected != GameMain.NetLobbyScreen && tc.CharacterId > 0)
{
existingClient.CharacterID = tc.CharacterId;
}
if (existingClient.SessionId == SessionId)
{
+ MultiplayerPreferences.Instance.TeamPreference = existingClient.PreferredTeam;
+ // If a team is already selected, make sure the UI reflects it
+ if (MultiplayerPreferences.Instance.TeamPreference != CharacterTeamType.None)
+ {
+ GameMain.NetLobbyScreen.TeamPreferenceListBox?.Select(MultiplayerPreferences.Instance.TeamPreference);
+ }
+ else
+ {
+ GameMain.NetLobbyScreen.RefreshPvpTeamSelectionButtons();
+ }
existingClient.SetPermissions(permissions, permittedConsoleCommands);
if (!NetIdUtils.IdMoreRecent(nameId, tc.NameId))
{
Name = tc.Name;
nameId = tc.NameId;
}
- if (GameMain.NetLobbyScreen.CharacterNameBox != null &&
- !GameMain.NetLobbyScreen.CharacterNameBox.Selected)
+ if (GameMain.NetLobbyScreen.CharacterNameBox is { Selected: false, Enabled: true })
{
GameMain.NetLobbyScreen.CharacterNameBox.Text = Name;
}
@@ -1950,6 +2120,7 @@ namespace Barotrauma.Networking
Steam.SteamManager.UpdateLobby(ServerSettings);
}
+ GameMain.NetLobbyScreen?.UpdateDisembarkPointListFromServerSettings();
}
if (refreshCampaignUI)
@@ -1960,6 +2131,7 @@ namespace Barotrauma.Networking
campaign.CampaignUI?.HRManagerUI?.RefreshUI();
}
}
+
}
private bool initialUpdateReceived;
@@ -1980,16 +2152,15 @@ namespace Barotrauma.Networking
UInt16 updateID = inc.ReadUInt16();
+
UInt16 settingsLen = inc.ReadUInt16();
byte[] settingsData = inc.ReadBytes(settingsLen);
bool isInitialUpdate = inc.ReadBoolean();
+ DebugConsole.Log($"Received {(isInitialUpdate ? "initial" : string.Empty)} lobby update ID: {updateID}, last ID: {GameMain.NetLobbyScreen.LastUpdateID}.");
+
if (isInitialUpdate)
- {
- if (GameSettings.CurrentConfig.VerboseLogging)
- {
- DebugConsole.NewMessage("Received initial lobby update, ID: " + updateID + ", last ID: " + GameMain.NetLobbyScreen.LastUpdateID, Color.Gray);
- }
+ {
ReadInitialUpdate(inc);
initialUpdateReceived = true;
}
@@ -1997,6 +2168,15 @@ namespace Barotrauma.Networking
string selectSubName = inc.ReadString();
string selectSubHash = inc.ReadString();
+ bool usingEnemySub = inc.ReadBoolean();
+ string selectEnemySubName = selectSubName;
+ string selectEnemySubHash = selectSubHash;
+ if (usingEnemySub)
+ {
+ selectEnemySubName = inc.ReadString();
+ selectEnemySubHash = inc.ReadString();
+ }
+
bool usingShuttle = inc.ReadBoolean();
string selectShuttleName = inc.ReadString();
string selectShuttleHash = inc.ReadString();
@@ -2007,11 +2187,18 @@ namespace Barotrauma.Networking
bool voiceChatEnabled = inc.ReadBoolean();
bool allowSpectating = inc.ReadBoolean();
+ bool allowAFK = inc.ReadBoolean();
float traitorProbability = inc.ReadSingle();
int traitorDangerLevel = inc.ReadRangedInteger(TraitorEventPrefab.MinDangerLevel, TraitorEventPrefab.MaxDangerLevel);
- MissionType missionType = (MissionType)inc.ReadRangedInteger(0, (int)MissionType.All);
+ List missionTypes = new List();
+ uint missionTypeCount = inc.ReadVariableUInt32();
+ for (int i = 0; i < missionTypeCount; i++)
+ {
+ missionTypes.Add(inc.ReadIdentifier());
+ }
+
int modeIndex = inc.ReadByte();
string levelSeed = inc.ReadString();
@@ -2048,12 +2235,19 @@ namespace Barotrauma.Networking
ServerSettings.ServerLog.ServerName = ServerSettings.ServerName;
GameMain.NetLobbyScreen.UsingShuttle = usingShuttle;
- if (!allowSubVoting || GameMain.NetLobbyScreen.SelectedSub == null) { GameMain.NetLobbyScreen.TrySelectSub(selectSubName, selectSubHash, GameMain.NetLobbyScreen.SubList); }
- GameMain.NetLobbyScreen.TrySelectSub(selectShuttleName, selectShuttleHash, GameMain.NetLobbyScreen.ShuttleList.ListBox);
+ if (!allowSubVoting || GameMain.NetLobbyScreen.SelectedSub == null)
+ {
+ GameMain.NetLobbyScreen.TrySelectSub(selectSubName, selectSubHash, SelectedSubType.Sub, GameMain.NetLobbyScreen.SubList);
+ if (usingEnemySub)
+ {
+ GameMain.NetLobbyScreen.TrySelectSub(selectEnemySubName, selectEnemySubHash, SelectedSubType.EnemySub, GameMain.NetLobbyScreen.SubList);
+ }
+ }
+ GameMain.NetLobbyScreen.TrySelectSub(selectShuttleName, selectShuttleHash, SelectedSubType.Shuttle, GameMain.NetLobbyScreen.ShuttleList.ListBox);
GameMain.NetLobbyScreen.SetTraitorProbability(traitorProbability);
GameMain.NetLobbyScreen.SetTraitorDangerLevel(traitorDangerLevel);
- GameMain.NetLobbyScreen.SetMissionType(missionType);
+ GameMain.NetLobbyScreen.SetMissionTypes(missionTypes);
GameMain.NetLobbyScreen.LevelSeed = levelSeed;
GameMain.NetLobbyScreen.SelectMode(modeIndex);
@@ -2071,6 +2265,7 @@ namespace Barotrauma.Networking
}
GameMain.NetLobbyScreen.SetAllowSpectating(allowSpectating);
+ GameMain.NetLobbyScreen.SetAllowAFK(allowAFK);
GameMain.NetLobbyScreen.SetLevelDifficulty(levelDifficulty);
GameMain.NetLobbyScreen.SetBotSpawnMode(botSpawnMode);
GameMain.NetLobbyScreen.SetBotCount(botCount);
@@ -2246,7 +2441,7 @@ namespace Barotrauma.Networking
GameAnalyticsManager.AddErrorEventOnce("GameClient.ReadInGameUpdate", GameAnalyticsManager.ErrorSeverity.Critical, string.Join("\n", errorLines));
throw new Exception(
- $"Exception thrown while reading segment {segment.Identifier} at position {segment.Pointer}." +
+ $"Exception thrown while reading a message of the type \"{segment.Identifier}\" at position {segment.Pointer}." +
(prevSegments.Any() ? $" Previous segments: {string.Join(", ", prevSegments)}" : ""),
ex);
});
@@ -2263,6 +2458,7 @@ namespace Barotrauma.Networking
outmsg.WriteUInt16(GameMain.NetLobbyScreen.LastUpdateID);
outmsg.WriteUInt16(ChatMessage.LastID);
outmsg.WriteUInt16(LastClientListUpdateID);
+ outmsg.WriteBoolean(GameMain.NetLobbyScreen.AFKSelected);
outmsg.WriteUInt16(nameId);
outmsg.WriteString(Name);
var jobPreferences = GameMain.NetLobbyScreen.JobPreferences;
@@ -2409,6 +2605,15 @@ namespace Barotrauma.Networking
msg.WriteUInt16(bot.ID);
ClientPeer?.Send(msg, DeliveryMethod.Reliable);
}
+
+ public void ToggleReserveBench(CharacterInfo bot, bool pendingHire = false)
+ {
+ IWriteMessage msg = new WriteOnlyMessage();
+ msg.WriteByte((byte)ClientPacketHeader.TOGGLE_RESERVE_BENCH);
+ msg.WriteUInt16(bot.ID);
+ msg.WriteBoolean(pendingHire);
+ ClientPeer?.Send(msg, DeliveryMethod.Reliable);
+ }
public void RequestFile(FileTransferType fileType, string file, string fileHash)
{
@@ -2489,14 +2694,20 @@ namespace Barotrauma.Networking
((SubmarineInfo)c.UserData).MD5Hash.StringRepresentation == newSub.MD5Hash.StringRepresentation);
if (subElement == null) { continue; }
- Color newSubTextColor = new Color(subElement.GetChild().TextColor, 1.0f);
- subElement.GetChild().TextColor = newSubTextColor;
-
- if (subElement.GetChildByUserData("classtext") is GUITextBlock classTextBlock)
+ //set the dimmed out submarine info back to normal and update texts
+ if (subElement.FindChild("nametext", recursive: true) is GUITextBlock nameTextBlock)
+ {
+ nameTextBlock.TextColor = new Color(nameTextBlock.TextColor, 1.0f);
+ }
+ if (subElement.FindChild("classtext", recursive: true) is GUITextBlock classTextBlock)
{
- Color newSubClassTextColor = new Color(classTextBlock.TextColor, 0.8f);
classTextBlock.Text = TextManager.Get($"submarineclass.{newSub.SubmarineClass}");
- classTextBlock.TextColor = newSubClassTextColor;
+ classTextBlock.TextColor = new Color(classTextBlock.TextColor, 0.8f);
+ }
+ if (subElement.FindChild("pricetext", recursive: true) is GUITextBlock priceTextBlock)
+ {
+ priceTextBlock.Text = TextManager.GetWithVariable("currencyformat", "[credits]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", newSub.Price));
+ priceTextBlock.TextColor = new Color(priceTextBlock.TextColor, 0.8f);
}
subElement.UserData = newSub;
@@ -2507,14 +2718,22 @@ namespace Barotrauma.Networking
GameMain.NetLobbyScreen.FailedSelectedSub.Value.Name == newSub.Name &&
GameMain.NetLobbyScreen.FailedSelectedSub.Value.Hash == newSub.MD5Hash.StringRepresentation)
{
- GameMain.NetLobbyScreen.TrySelectSub(newSub.Name, newSub.MD5Hash.StringRepresentation, GameMain.NetLobbyScreen.SubList);
+ GameMain.NetLobbyScreen.TrySelectSub(newSub.Name, newSub.MD5Hash.StringRepresentation, SelectedSubType.Sub, GameMain.NetLobbyScreen.SubList);
}
if (GameMain.NetLobbyScreen.FailedSelectedShuttle.HasValue &&
GameMain.NetLobbyScreen.FailedSelectedShuttle.Value.Name == newSub.Name &&
GameMain.NetLobbyScreen.FailedSelectedShuttle.Value.Hash == newSub.MD5Hash.StringRepresentation)
{
- GameMain.NetLobbyScreen.TrySelectSub(newSub.Name, newSub.MD5Hash.StringRepresentation, GameMain.NetLobbyScreen.ShuttleList.ListBox);
+ GameMain.NetLobbyScreen.TrySelectSub(newSub.Name, newSub.MD5Hash.StringRepresentation, SelectedSubType.Shuttle, GameMain.NetLobbyScreen.ShuttleList.ListBox);
+ }
+
+ if (GameMain.NetLobbyScreen.SelectedMode == GameModePreset.PvP &&
+ GameMain.NetLobbyScreen.FailedSelectedEnemySub.HasValue &&
+ GameMain.NetLobbyScreen.FailedSelectedEnemySub.Value.Name == newSub.Name &&
+ GameMain.NetLobbyScreen.FailedSelectedEnemySub.Value.Hash == newSub.MD5Hash.StringRepresentation)
+ {
+ GameMain.NetLobbyScreen.TrySelectSub(newSub.Name, newSub.MD5Hash.StringRepresentation, SelectedSubType.EnemySub, GameMain.NetLobbyScreen.SubList);
}
NetLobbyScreen.FailedSubInfo failedCampaignSub = GameMain.NetLobbyScreen.FailedCampaignSubs.Find(s => s.Name == newSub.Name && s.Hash == newSub.MD5Hash.StringRepresentation);
@@ -2547,20 +2766,20 @@ namespace Barotrauma.Networking
if (GameMain.GameSession?.GameMode is not MultiPlayerCampaign campaign || campaign.CampaignID != campaignID)
{
string savePath = transfer.FilePath;
- GameMain.GameSession = new GameSession(null, savePath, GameModePreset.MultiPlayerCampaign, CampaignSettings.Empty);
+ GameMain.GameSession = new GameSession(null, Option.None, CampaignDataPath.CreateRegular(savePath), GameModePreset.MultiPlayerCampaign, CampaignSettings.Empty);
campaign = (MultiPlayerCampaign)GameMain.GameSession.GameMode;
campaign.CampaignID = campaignID;
GameMain.NetLobbyScreen.ToggleCampaignMode(true);
}
- GameMain.GameSession.SavePath = transfer.FilePath;
+ GameMain.GameSession.DataPath = CampaignDataPath.CreateRegular(transfer.FilePath);
if (GameMain.GameSession.SubmarineInfo == null || campaign.Map == null)
{
string subPath = Path.Combine(SaveUtil.TempPath, gameSessionDocRoot.GetAttributeString("submarine", "")) + ".sub";
GameMain.GameSession.SubmarineInfo = new SubmarineInfo(subPath, "");
}
- campaign.LoadState(GameMain.GameSession.SavePath);
+ campaign.LoadState(GameMain.GameSession.DataPath.LoadPath);
GameMain.GameSession?.SubmarineInfo?.Reload();
GameMain.GameSession?.SubmarineInfo?.CheckSubsLeftBehind();
@@ -2577,7 +2796,7 @@ namespace Barotrauma.Networking
GameMain.NetLobbyScreen.Select();
}
- DebugConsole.Log("Campaign save received (" + GameMain.GameSession.SavePath + "), save ID " + campaign.LastSaveID);
+ DebugConsole.Log("Campaign save received (" + GameMain.GameSession.DataPath + "), save ID " + campaign.LastSaveID);
//decrement campaign update IDs so the server will send us the latest data
//(as there may have been campaign updates after the save file was created)
foreach (MultiPlayerCampaign.NetFlags flag in Enum.GetValues(typeof(MultiPlayerCampaign.NetFlags)))
@@ -2657,8 +2876,12 @@ namespace Barotrauma.Networking
public void WriteCharacterInfo(IWriteMessage msg, string newName = null)
{
msg.WriteBoolean(GameMain.NetLobbyScreen.Spectating);
+ msg.WriteBoolean(GameMain.NetLobbyScreen.CampaignCharacterDiscarded);
+ bool writeInfo = characterInfo != null;
+ msg.WriteBoolean(writeInfo);
msg.WritePadBits();
- if (characterInfo == null) { return; }
+
+ if (!writeInfo) { return; }
var head = characterInfo.Head;
@@ -2858,14 +3081,14 @@ namespace Barotrauma.Networking
///
/// Tell the server to select a submarine (permission required)
///
- public void RequestSelectSub(SubmarineInfo sub, bool isShuttle)
+ public void RequestSelectSub(SubmarineInfo sub, SelectedSubType type)
{
if (!HasPermission(ClientPermissions.SelectSub) || sub == null) { return; }
IWriteMessage msg = new WriteOnlyMessage();
msg.WriteByte((byte)ClientPacketHeader.SERVER_COMMAND);
- msg.WriteUInt16((UInt16)ClientPermissions.SelectSub);
- msg.WriteBoolean(isShuttle); msg.WritePadBits();
+ msg.WriteUInt16((ushort)ClientPermissions.SelectSub);
+ msg.WriteByte((byte)type);
msg.WriteString(sub.MD5Hash.StringRepresentation);
ClientPeer.Send(msg, DeliveryMethod.Reliable);
}
@@ -2909,7 +3132,7 @@ namespace Barotrauma.Networking
ClientPeer.Send(msg, DeliveryMethod.Reliable);
}
- public void SetupLoadCampaign(string saveName)
+ public void SetupLoadCampaign(string filePath, Option backupIndex)
{
if (ClientPeer == null) { return; }
@@ -2920,15 +3143,27 @@ namespace Barotrauma.Networking
msg.WriteByte((byte)ClientPacketHeader.CAMPAIGN_SETUP_INFO);
msg.WriteBoolean(false); msg.WritePadBits();
- msg.WriteString(saveName);
+ msg.WriteString(filePath);
+
+ if (backupIndex.TryUnwrap(out uint index))
+ {
+ msg.WriteBoolean(true);
+ msg.WritePadBits();
+ msg.WriteUInt32(index);
+ }
+ else
+ {
+ msg.WriteBoolean(false);
+ msg.WritePadBits();
+ }
ClientPeer.Send(msg, DeliveryMethod.Reliable);
}
///
- /// Tell the server to end the round (permission required)
+ /// Tell the server to end the round (permission required).
///
- public void RequestRoundEnd(bool save, bool quitCampaign = false)
+ public void RequestEndRound(bool save, bool quitCampaign = false)
{
IWriteMessage msg = new WriteOnlyMessage();
msg.WriteByte((byte)ClientPacketHeader.SERVER_COMMAND);
@@ -2940,7 +3175,31 @@ namespace Barotrauma.Networking
ClientPeer.Send(msg, DeliveryMethod.Reliable);
}
- public bool JoinOnGoingClicked(GUIButton button, object _)
+ ///
+ /// End the round locally (just returning to the lobby without ending the round for everyone).
+ ///
+ public void EndRoundForSelf()
+ {
+ GameMain.GameSession?.EndRound(endMessage: string.Empty, createRoundSummary: false);
+ Submarine.Unload();
+ GameMain.NetLobbyScreen.Select();
+ Character.Controlled = null;
+ WaitForNextRoundRespawn = null;
+ RespawnManager = null;
+
+ EntityEventManager?.Clear();
+ LastSentEntityEventID = 0;
+
+ MyClient.CharacterID = Entity.NullEntityID;
+
+ roundInitStatus = RoundInitStatus.NotStarted;
+
+ IWriteMessage msg = new WriteOnlyMessage();
+ msg.WriteByte((byte)ClientPacketHeader.ENDROUND_SELF);
+ ClientPeer.Send(msg, DeliveryMethod.Reliable);
+ }
+
+ public bool SendJoinOngoingRequest(GUIButton joinButton)
{
MultiPlayerCampaign campaign =
GameMain.NetLobbyScreen.SelectedMode == GameMain.GameSession?.GameMode.Preset ?
@@ -2952,23 +3211,32 @@ namespace Barotrauma.Networking
new GUIMessageBox("", TextManager.Get("campaignfiletransferinprogress"));
return false;
}
- if (button != null) { button.Enabled = false; }
+ if (joinButton != null) { joinButton.Enabled = false; }
if (campaign != null) { LateCampaignJoin = true; }
if (ClientPeer == null) { return false; }
+ //assume we have the required sub files to start the round
+ //(if not, we'll find out when the server sends the STARTGAME message and can initiate a file transfer)
+ SendStartGameResponse(readyToStart: true);
+
+ return false;
+ }
+
+ private void SendStartGameResponse(bool readyToStart)
+ {
IWriteMessage readyToStartMsg = new WriteOnlyMessage();
readyToStartMsg.WriteByte((byte)ClientPacketHeader.RESPONSE_STARTGAME);
//assume we have the required sub files to start the round
//(if not, we'll find out when the server sends the STARTGAME message and can initiate a file transfer)
- readyToStartMsg.WriteBoolean(true);
+ readyToStartMsg.WriteBoolean(readyToStart);
+ readyToStartMsg.WriteBoolean(GameMain.NetLobbyScreen.AFKSelected && ServerSettings.AllowAFK);
WriteCharacterInfo(readyToStartMsg);
ClientPeer.Send(readyToStartMsg, DeliveryMethod.Reliable);
- return false;
}
public bool SetReadyToStart(GUITickBox tickBox)
@@ -3062,7 +3330,8 @@ namespace Barotrauma.Networking
public bool EnterChatMessage(GUITextBox textBox, string message)
{
- textBox.TextColor = ChatMessage.MessageColor[(int)ChatMessageType.Default];
+ var messageType = NetLobbyScreen.TeamChatSelected ? ChatMessageType.Team : ChatMessageType.Default;
+ textBox.TextColor = ChatMessage.MessageColor[(int)messageType];
if (string.IsNullOrWhiteSpace(message))
{
@@ -3070,7 +3339,7 @@ namespace Barotrauma.Networking
return false;
}
chatBox.ChatManager.Store(message);
- SendChatMessage(message);
+ SendChatMessage(message, type: messageType);
if (textBox.DeselectAfterMessage)
{
@@ -3559,9 +3828,9 @@ namespace Barotrauma.Networking
{
errorLines.Add("Submarine: " + GameMain.GameSession.Submarine.Info.Name);
}
- if (GameMain.NetworkMember?.RespawnManager?.RespawnShuttle != null)
+ if (GameMain.NetworkMember?.RespawnManager is { } respawnManager)
{
- errorLines.Add("Respawn shuttle: " + GameMain.NetworkMember.RespawnManager.RespawnShuttle.Info.Name);
+ errorLines.Add("Respawn shuttles: " + string.Join(", ", respawnManager.RespawnShuttles.Select(s => s.Info.Name)));
}
if (Level.Loaded != null)
{
@@ -3616,16 +3885,24 @@ namespace Barotrauma.Networking
eventErrorWritten = true;
}
- private static void AppendExceptionInfo(ref string errorMsg, Exception e)
+ private static void AppendExceptionInfo(ref string errorMsg, out Entity causingEntity, Exception e)
{
if (!errorMsg.EndsWith("\n")) { errorMsg += "\n"; }
+
+ Exception innerMostException = e.GetInnermost();
+ causingEntity = GetCausingEntity(e);
+
+ if (causingEntity != null)
+ {
+ errorMsg += "Entity: " + causingEntity + "\n";
+ }
errorMsg += e.Message + "\n";
- var innermostException = e.GetInnermost();
- if (innermostException != e)
+
+ if (innerMostException != e)
{
// If available, only append the stacktrace of the innermost exception,
// because that's the most important one to fix
- errorMsg += "Inner exception: " + innermostException.Message + "\n" + innermostException.StackTrace.CleanupStackTrace();
+ errorMsg += "Inner exception: " + innerMostException.Message + "\n" + innerMostException.StackTrace.CleanupStackTrace();
}
else
{
@@ -3633,6 +3910,24 @@ namespace Barotrauma.Networking
}
}
+ ///
+ /// Checks if the exception or any of its inner exceptions are EntityEventExceptions, and returns the entity that caused the innermost EntityEventException.
+ ///
+ private static Entity GetCausingEntity(Exception e)
+ {
+ Entity causingEntity = null;
+ Exception currentException = e;
+ while (currentException != null)
+ {
+ if (currentException is EntityEventException entityEventException)
+ {
+ causingEntity = entityEventException.Entity;
+ }
+ currentException = currentException.InnerException;
+ }
+ return causingEntity;
+ }
+
#if DEBUG
public void ForceTimeOut()
{
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/NetEntityEvent/ClientEntityEventManager.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/NetEntityEvent/ClientEntityEventManager.cs
index c50dc2d86..d00026416 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Networking/NetEntityEvent/ClientEntityEventManager.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/NetEntityEvent/ClientEntityEventManager.cs
@@ -154,13 +154,25 @@ namespace Barotrauma.Networking
//16 = entity ID, 8 = msg length
if (msg.BitPosition + 16 + 8 > msg.LengthBits)
{
- string errorMsg = $"Error while reading a message from the server. Entity event data exceeds the size of the buffer (current position: {msg.BitPosition}, length: {msg.LengthBits}).";
+ UInt16 potentialEntityId = Entity.NullEntityID;
+ try
+ {
+ potentialEntityId = msg.ReadUInt16();
+ }
+ catch
+ {
+ //failed to read the ID, do nothing (we would've just used it for the error message)
+ }
+ Entity targetEntity = Entity.FindEntityByID(potentialEntityId);
+
+ string errorMsg = $"Error while reading a message from the server (entity: {targetEntity?.ToString() ?? "unknown"}).";
+ errorMsg += $" Entity event data exceeds the size of the buffer (current position: {msg.BitPosition}, length: {msg.LengthBits}).";
errorMsg += "\nPrevious entities:";
for (int j = tempEntityList.Count - 1; j >= 0; j--)
{
errorMsg += "\n" + (tempEntityList[j] == null ? "NULL" : tempEntityList[j].ToString());
}
- DebugConsole.ThrowError(errorMsg);
+ DebugConsole.ThrowError(errorMsg, contentPackage: targetEntity?.ContentPackage);
return false;
}
@@ -172,7 +184,7 @@ namespace Barotrauma.Networking
if (GameSettings.CurrentConfig.VerboseLogging)
{
DebugConsole.NewMessage("received msg " + thisEventID + " (null entity)",
- Microsoft.Xna.Framework.Color.Orange);
+ Color.Orange);
}
tempEntityList.Add(null);
if (thisEventID == (UInt16)(lastReceivedID + 1)) { lastReceivedID++; }
@@ -187,7 +199,7 @@ namespace Barotrauma.Networking
//skip the event if we've already received it or if the entity isn't found
if (thisEventID != (UInt16)(lastReceivedID + 1) || entity == null)
{
- if (thisEventID != (UInt16) (lastReceivedID + 1))
+ if (thisEventID != (UInt16)(lastReceivedID + 1))
{
if (GameSettings.CurrentConfig.VerboseLogging)
{
@@ -195,7 +207,7 @@ namespace Barotrauma.Networking
"Received msg " + thisEventID + " (waiting for " + (lastReceivedID + 1) + ")",
NetIdUtils.IdMoreRecent(thisEventID, (UInt16)(lastReceivedID + 1))
? GUIStyle.Red
- : Microsoft.Xna.Framework.Color.Yellow);
+ : Color.Yellow);
}
}
else if (entity == null)
@@ -215,12 +227,18 @@ namespace Barotrauma.Networking
if (GameSettings.CurrentConfig.VerboseLogging)
{
DebugConsole.NewMessage("received msg " + thisEventID + " (" + entity.ToString() + ")",
- Microsoft.Xna.Framework.Color.Green);
+ Color.Green);
}
lastReceivedID++;
- ReadEvent(msg, entity, sendingTime);
- msg.ReadPadBits();
-
+ try
+ {
+ ReadEvent(msg, entity, sendingTime);
+ msg.ReadPadBits();
+ }
+ catch (Exception exception)
+ {
+ throw new EntityEventException("Failed to read event." , entity as Entity, exception);
+ }
if (msg.BitPosition != msgPosition + msgLength * 8)
{
var prevEntity = tempEntityList.Count >= 2 ? tempEntityList[tempEntityList.Count - 2] : null;
@@ -231,7 +249,7 @@ namespace Barotrauma.Networking
GameAnalyticsManager.AddErrorEventOnce("ClientEntityEventManager.Read:BitPosMismatch", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
- throw new Exception(errorMsg);
+ throw new EntityEventException(errorMsg, entity as Entity);
}
}
}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/P2PSocket/SteamConnectSocket.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/P2PSocket/SteamConnectSocket.cs
index 47e3b69e5..aceb50a95 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/P2PSocket/SteamConnectSocket.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/P2PSocket/SteamConnectSocket.cs
@@ -74,7 +74,16 @@ sealed class SteamConnectSocket : P2PSocket
{
if (!SteamManager.IsInitialized) { return Result.Failure(new Error(ErrorCode.SteamNotInitialized)); }
- var connectionManager = Steamworks.SteamNetworkingSockets.ConnectRelay(endpoint.SteamId.Value);
+ ConnectionManager connectionManager;
+ try
+ {
+ connectionManager = Steamworks.SteamNetworkingSockets.ConnectRelay(endpoint.SteamId.Value);
+ }
+ catch (ArgumentException e)
+ {
+ DebugConsole.ThrowError("Failed to connect via SteamP2P. Are you logged in to Steam, is the same Steam account already connected to the server?", e);
+ return Result.Failure(new Error(ErrorCode.FailedToCreateSteamP2PSocket));
+ }
if (connectionManager is null) { return Result.Failure(new Error(ErrorCode.FailedToCreateSteamP2PSocket)); }
connectionManager.SetEndpointAndCallbacks(endpoint, callbacks);
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/P2PSocket/SteamListenSocket.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/P2PSocket/SteamListenSocket.cs
index b1e7c8170..6ca70f22d 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/P2PSocket/SteamListenSocket.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/P2PSocket/SteamListenSocket.cs
@@ -68,7 +68,7 @@ sealed class SteamListenSocket : P2PSocket
public override void OnMessage(Steamworks.Data.Connection connection, Steamworks.Data.NetIdentity identity, IntPtr data, int size, long messageNum, long recvTime, int channel)
{
- if (!identity.IsSteamId) { return; }
+ if (!identity.IsSteamId || data == IntPtr.Zero) { return; }
var endpoint = new SteamP2PEndpoint(new SteamId((Steamworks.SteamId)identity));
var dataArray = new byte[size];
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/ClientPeer.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/ClientPeer.cs
index 5ecc23923..7b489b369 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/ClientPeer.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/ClientPeer.cs
@@ -20,6 +20,8 @@ namespace Barotrauma.Networking
public bool AllowModDownloads { get; private set; } = true;
+ public string AutomaticallyAttemptedPassword = string.Empty;
+
public readonly record struct Callbacks(
Callbacks.MessageCallback OnMessageReceived,
Callbacks.DisconnectCallback OnDisconnect,
@@ -39,6 +41,9 @@ namespace Barotrauma.Networking
protected bool IsOwner => ownerKey.IsSome();
protected readonly Option ownerKey;
+ ///
+ /// Has the ClientPeer been started? Set to true in , set to false when shutting the client down .
+ ///
public bool IsActive => isActive;
protected bool isActive;
@@ -102,8 +107,12 @@ namespace Barotrauma.Networking
TaskPool.Add($"{GetType().Name}.{nameof(GetAccountId)}", GetAccountId(), t =>
{
- if (GameMain.Client?.ClientPeer is null) { return; }
-
+ if (!IsActive)
+ {
+ //client has become inactive (cancelled/disconnected while waiting for initialization)
+ return;
+ }
+
if (!t.TryGetResult(out Option accountId))
{
Close(PeerDisconnectPacket.WithReason(DisconnectReason.AuthenticationFailed));
@@ -118,7 +127,7 @@ namespace Barotrauma.Networking
var body = new ClientAuthTicketAndVersionPacket
{
- Name = GameMain.Client.Name,
+ Name = GameMain.Client?.Name ?? "Unknown",
OwnerKey = ownerKey,
AccountId = accountId,
AuthTicket = authTicket,
@@ -177,10 +186,16 @@ namespace Barotrauma.Networking
var passwordPacket = INetSerializableStruct.Read(inc.Message);
if (WaitingForPassword) { return; }
-
+
passwordPacket.Salt.TryUnwrap(out passwordSalt);
passwordPacket.RetriesLeft.TryUnwrap(out var retries);
+ if (!string.IsNullOrWhiteSpace(AutomaticallyAttemptedPassword))
+ {
+ SendPassword(AutomaticallyAttemptedPassword);
+ return;
+ }
+
LocalizedString pwMsg = TextManager.Get("PasswordRequired");
passwordMsgBox?.Close();
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/LidgrenClientPeer.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/LidgrenClientPeer.cs
index 563c5ecd1..c0656420b 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/LidgrenClientPeer.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/LidgrenClientPeer.cs
@@ -224,10 +224,13 @@ namespace Barotrauma.Networking
ToolBox.ThrowIfNull(netPeerConfiguration);
#if DEBUG
- netPeerConfiguration.SimulatedDuplicatesChance = GameMain.Client.SimulatedDuplicatesChance;
- netPeerConfiguration.SimulatedMinimumLatency = GameMain.Client.SimulatedMinimumLatency;
- netPeerConfiguration.SimulatedRandomLatency = GameMain.Client.SimulatedRandomLatency;
- netPeerConfiguration.SimulatedLoss = GameMain.Client.SimulatedLoss;
+ if (GameMain.Client != null)
+ {
+ netPeerConfiguration.SimulatedDuplicatesChance = GameMain.Client.SimulatedDuplicatesChance;
+ netPeerConfiguration.SimulatedMinimumLatency = GameMain.Client.SimulatedMinimumLatency;
+ netPeerConfiguration.SimulatedRandomLatency = GameMain.Client.SimulatedRandomLatency;
+ netPeerConfiguration.SimulatedLoss = GameMain.Client.SimulatedLoss;
+ }
#endif
byte[] bufAux = msg.PrepareForSending(compressPastThreshold, out bool isCompressed, out _);
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/RespawnManager.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/RespawnManager.cs
index e4f182e02..e96674791 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Networking/RespawnManager.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/RespawnManager.cs
@@ -22,6 +22,12 @@ namespace Barotrauma.Networking
get; private set;
}
+ public DateTime ReturnTime { get; private set; }
+ public DateTime RespawnTime { get; private set; }
+ public State CurrentState { get; private set; }
+ public bool ReturnCountdownStarted { get; private set; }
+ public bool RespawnCountdownStarted { get; private set; }
+
public static void ShowDeathPromptIfNeeded(float delay = 1.0f)
{
if (UseDeathPrompt)
@@ -30,13 +36,18 @@ namespace Barotrauma.Networking
}
}
- partial void UpdateTransportingProjSpecific(float deltaTime)
+ partial void UpdateTransportingProjSpecific(TeamSpecificState teamSpecificState, float deltaTime)
{
- if (GameMain.Client?.Character == null || GameMain.Client.Character.Submarine != RespawnShuttle) { return; }
- if (!ReturnCountdownStarted) { return; }
+ if (GameMain.Client?.Character == null ||
+ GameMain.Client.Character.Submarine is not { IsRespawnShuttle: true } ||
+ GameMain.Client.Character.TeamID != teamSpecificState.TeamID)
+ {
+ return;
+ }
+ if (!teamSpecificState.ReturnCountdownStarted) { return; }
//show a warning when there's 20 seconds until the shuttle leaves
- if ((ReturnTime - DateTime.Now).TotalSeconds < 20.0f &&
+ if ((teamSpecificState.ReturnTime - DateTime.Now).TotalSeconds < 20.0f &&
(DateTime.Now - lastShuttleLeavingWarningTime).TotalSeconds > 30.0f)
{
lastShuttleLeavingWarningTime = DateTime.Now;
@@ -46,43 +57,59 @@ namespace Barotrauma.Networking
public void ClientEventRead(IReadMessage msg, float sendingTime)
{
- bool respawnPromptPending = false;
- var newState = (State)msg.ReadRangedInteger(0, Enum.GetNames(typeof(State)).Length);
- switch (newState)
+ var myTeamId = (CharacterTeamType)msg.ReadByte();
+ foreach (var teamSpecificState in teamSpecificStates.Values)
{
- case State.Transporting:
- ReturnCountdownStarted = msg.ReadBoolean();
- maxTransportTime = msg.ReadSingle();
- float transportTimeLeft = msg.ReadSingle();
+ var teamId = (CharacterTeamType)msg.ReadByte();
- ReturnTime = DateTime.Now + new TimeSpan(0, 0, 0, 0, milliseconds: (int)(transportTimeLeft * 1000.0f));
- RespawnCountdownStarted = false;
- if (CurrentState != newState)
- {
- CoroutineManager.StopCoroutines("forcepos");
- }
- break;
- case State.Waiting:
- PendingRespawnCount = msg.ReadUInt16();
- RequiredRespawnCount = msg.ReadUInt16();
- respawnPromptPending = msg.ReadBoolean();
- RespawnCountdownStarted = msg.ReadBoolean();
- ResetShuttle();
- float newRespawnTime = msg.ReadSingle();
- RespawnTime = DateTime.Now + new TimeSpan(0, 0, 0, 0, milliseconds: (int)(newRespawnTime * 1000.0f));
- break;
- case State.Returning:
- RespawnCountdownStarted = false;
- break;
+ bool respawnPromptPending = false;
+ bool clientHasChosenNewBotViaShuttle = false;
+ var newState = (State)msg.ReadRangedInteger(0, Enum.GetNames(typeof(State)).Length);
+ switch (newState)
+ {
+ case State.Transporting:
+ teamSpecificState.ReturnCountdownStarted = msg.ReadBoolean();
+ maxTransportTime = msg.ReadSingle();
+ float transportTimeLeft = msg.ReadSingle();
+ teamSpecificState.ReturnTime = DateTime.Now + new TimeSpan(0, 0, 0, 0, milliseconds: (int)(transportTimeLeft * 1000.0f));
+ teamSpecificState.RespawnCountdownStarted = false;
+ SetShuttleBodyType(teamSpecificState.TeamID, FarseerPhysics.BodyType.Dynamic);
+ break;
+ case State.Waiting:
+ teamSpecificState.PendingRespawnCount = msg.ReadUInt16();
+ teamSpecificState.RequiredRespawnCount = msg.ReadUInt16();
+ respawnPromptPending = msg.ReadBoolean();
+ clientHasChosenNewBotViaShuttle = msg.ReadBoolean();
+ teamSpecificState.RespawnCountdownStarted = msg.ReadBoolean();
+ ResetShuttle(teamSpecificState);
+ float newRespawnTime = msg.ReadSingle();
+ teamSpecificState.RespawnTime = DateTime.Now + new TimeSpan(0, 0, 0, 0, milliseconds: (int)(newRespawnTime * 1000.0f));
+ SetShuttleBodyType(teamSpecificState.TeamID, FarseerPhysics.BodyType.Static);
+ break;
+ case State.Returning:
+ teamSpecificState.RespawnCountdownStarted = false;
+ break;
+ }
+ teamSpecificState.CurrentState = newState;
+
+ if (respawnPromptPending && !clientHasChosenNewBotViaShuttle)
+ {
+ GameMain.Client.HasSpawned = true;
+ DeathPrompt.Create(delay: 1.0f);
+ }
+
+ if (teamId == myTeamId)
+ {
+ PendingRespawnCount = teamSpecificState.PendingRespawnCount;
+ RequiredRespawnCount = teamSpecificState.RequiredRespawnCount;
+ ReturnTime = teamSpecificState.ReturnTime;
+ RespawnTime = teamSpecificState.RespawnTime;
+ CurrentState = teamSpecificState.CurrentState;
+ ReturnCountdownStarted = teamSpecificState.ReturnCountdownStarted;
+ RespawnCountdownStarted = teamSpecificState.RespawnCountdownStarted;
+ }
}
- CurrentState = newState;
-
- if (respawnPromptPending)
- {
- GameMain.Client.HasSpawned = true;
- DeathPrompt.Create(delay: 1.0f);
- }
-
+
msg.ReadPadBits();
}
}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerList/PingUtils.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerList/PingUtils.cs
index b2423ab37..9bfa06aaa 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerList/PingUtils.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerList/PingUtils.cs
@@ -144,9 +144,9 @@ namespace Barotrauma.Networking
var pingLocation = NetPingLocation.TryParseFromString(pingLocationStr);
- if (pingLocation.HasValue && Steamworks.SteamNetworkingUtils.LocalPingLocation.HasValue)
+ if (pingLocation.HasValue)
{
- int ping = Steamworks.SteamNetworkingUtils.LocalPingLocation.Value.EstimatePingTo(pingLocation.Value);
+ int ping = Steamworks.SteamNetworkingUtils.EstimatePingTo(pingLocation.Value);
if (ping < 0) { return Result.Failure(SteamLobbyPingError.PingEstimationFailed); }
return Result.Success(ping);
}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerList/ServerInfo.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerList/ServerInfo.cs
index 1111afa09..0fa5a05b4 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerList/ServerInfo.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerList/ServerInfo.cs
@@ -385,14 +385,24 @@ namespace Barotrauma.Networking
{ MinSize = new Point(0, 15) },
package.Name)
{
- CanBeFocused = false
+ Enabled = false
};
+ packageText.Box.DisabledColor = packageText.Box.Color;
+ packageText.TextBlock.DisabledTextColor = packageText.TextBlock.TextColor;
if (!string.IsNullOrEmpty(package.Hash))
{
- if (ContentPackageManager.AllPackages.Any(contentPackage => contentPackage.Hash.StringRepresentation == package.Hash))
+ if (ContentPackageManager.AllPackages.FirstOrDefault(contentPackage => contentPackage.Hash.StringRepresentation == package.Hash) is { } matchingPackage)
{
packageText.TextColor = GUIStyle.Green;
packageText.Selected = true;
+ matchingPackage.TryFetchUgcDescription(onFinished: (string? description) =>
+ {
+ if (packageText.ToolTip.IsNullOrEmpty() &&
+ !string.IsNullOrEmpty(description))
+ {
+ packageText.ToolTip = description + "...";
+ }
+ });
}
//workshop download link found
else if (package.Id.TryUnwrap(out var ugcId) && ugcId is SteamWorkshopId)
@@ -437,7 +447,7 @@ namespace Barotrauma.Networking
public void UpdateInfo(Func valueGetter)
{
- ServerMessage = valueGetter("message") ?? "";
+ ServerMessage = ExtractServerMessage(valueGetter);
if (Version.TryParse(valueGetter("version"), out var version))
{
GameVersion = version;
@@ -477,6 +487,22 @@ namespace Barotrauma.Networking
}
}
+ private static string ExtractServerMessage(Func valueGetter)
+ {
+ string msg = valueGetter("message") ?? string.Empty;
+ if (!msg.IsNullOrEmpty()) { return msg; }
+
+ int messageIndex = 0;
+ string splitMessage;
+ do
+ {
+ splitMessage = valueGetter($"message{messageIndex}") ?? string.Empty;
+ msg += splitMessage;
+ messageIndex++;
+ } while (!splitMessage.IsNullOrEmpty());
+ return msg;
+ }
+
private static ServerListContentPackageInfo[] ExtractContentPackageInfo(string serverName, Func valueGetter)
{
//workaround to ServerRules queries truncating the values to 255 bytes
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerList/ServerProviders/SteamDedicatedServerProvider.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerList/ServerProviders/SteamDedicatedServerProvider.cs
index e31ccd06f..305172748 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerList/ServerProviders/SteamDedicatedServerProvider.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerList/ServerProviders/SteamDedicatedServerProvider.cs
@@ -1,4 +1,4 @@
-#nullable enable
+#nullable enable
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
@@ -67,6 +67,7 @@ namespace Barotrauma
return null;
});
serverInfo.Checked = true;
+ serverInfo.HasPassword |= entry.Passworded;
onServerDataReceived(serverInfo, this);
});
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerSettings.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerSettings.cs
index b7252310c..082170271 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerSettings.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerSettings.cs
@@ -1,6 +1,7 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
+using System.Collections.Immutable;
using System.ComponentModel;
using System.Linq;
@@ -178,6 +179,11 @@ namespace Barotrauma.Networking
extraCargoPanel.Visible = true;
}
}
+
+ if (ReadPerks(incMsg))
+ {
+ GameMain.NetLobbyScreen?.UpdateDisembarkPointListFromServerSettings();
+ }
}
if (requiredFlags.HasFlag(NetFlags.HiddenSubs))
@@ -194,10 +200,41 @@ namespace Barotrauma.Networking
}
}
+ public static bool HasPermissionToChangePerks()
+ {
+ if (GameMain.Client.HasPermission(Networking.ClientPermissions.ManageSettings)) { return true; }
+
+ bool isPvP = GameMain.NetLobbyScreen?.SelectedMode == GameModePreset.PvP;
+ bool hasSelectedTeam = MultiplayerPreferences.Instance.TeamPreference is CharacterTeamType.Team1 or CharacterTeamType.Team2;
+ var otherClients = GameMain.Client?.ConnectedClients.Where(static c => c.SessionId != GameMain.Client.SessionId).ToImmutableArray() ?? ImmutableArray.Empty;
+
+ if (isPvP)
+ {
+ if (!hasSelectedTeam) { return false; }
+
+ return !otherClients
+ .Where(static c => c.PreferredTeam == MultiplayerPreferences.Instance.TeamPreference)
+ .Any(static c => c.HasPermission(Networking.ClientPermissions.ManageSettings));
+ }
+ else
+ {
+ return !otherClients.Any(static c => c.HasPermission(Networking.ClientPermissions.ManageSettings));
+ }
+ }
+
+ public void ClientAdminWritePerks()
+ {
+ IWriteMessage outMsg = new WriteOnlyMessage();
+
+ outMsg.WriteByte((byte)ClientPacketHeader.SERVER_SETTINGS_PERKS);
+ WritePerks(outMsg);
+ GameMain.Client?.ClientPeer?.Send(outMsg, DeliveryMethod.Reliable);
+ }
+
public void ClientAdminWrite(
NetFlags dataToSend,
- int? missionTypeOr = null,
- int? missionTypeAnd = null,
+ Identifier addedMissionType = default,
+ Identifier removedMissionType = default,
int traitorDangerLevel = 0)
{
if (!GameMain.Client.HasPermission(Networking.ClientPermissions.ManageSettings)) { return; }
@@ -220,7 +257,7 @@ namespace Barotrauma.Networking
outMsg.WriteUInt32(count);
foreach (KeyValuePair prop in changedProperties)
{
- DebugConsole.NewMessage(prop.Value.Name.Value, Color.Lime);
+ DebugConsole.NewMessage($"Changed {prop.Value.Name.Value} to {prop.Value.GUIComponentValue}", Color.Lime);
outMsg.WriteUInt32(prop.Key);
prop.Value.Write(outMsg, prop.Value.GUIComponentValue);
}
@@ -237,8 +274,8 @@ namespace Barotrauma.Networking
if (dataToSend.HasFlag(NetFlags.Misc))
{
- outMsg.WriteRangedInteger(missionTypeOr ?? (int)Barotrauma.MissionType.None, 0, (int)Barotrauma.MissionType.All);
- outMsg.WriteRangedInteger(missionTypeAnd ?? (int)Barotrauma.MissionType.All, 0, (int)Barotrauma.MissionType.All);
+ outMsg.WriteIdentifier(addedMissionType);
+ outMsg.WriteIdentifier(removedMissionType);
outMsg.WriteByte((byte)(traitorDangerLevel + 1));
outMsg.WritePadBits();
}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerSettingsUI.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerSettingsUI.cs
index f2d99e79d..46f9d88e1 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerSettingsUI.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerSettingsUI.cs
@@ -418,8 +418,44 @@ namespace Barotrauma.Networking
var randomizeLevelBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), listBox.Content.RectTransform), TextManager.Get("ServerSettingsRandomizeSeed"));
AssignGUIComponent(nameof(RandomizeSeed), randomizeLevelBox);
- //***********************************************
-
+ // ******* PVP ********************************
+ NetLobbyScreen.CreateSubHeader("gamemode.pvp", listBox.Content);
+
+ var teamSelectModeLabel = new GUITextBlock(
+ new RectTransform(new Vector2(1.0f, 0.05f),
+ listBox.Content.RectTransform),
+ TextManager.Get("TeamSelectionMode"));
+ teamSelectModeLabel.ToolTip = TextManager.Get("TeamSelectionMode.tooltip");
+ var teamSelectionMode = new GUISelectionCarousel(
+ new RectTransform(new Vector2(0.5f, 0.6f),
+ teamSelectModeLabel.RectTransform,
+ Anchor.CenterRight));
+ foreach (PvpTeamSelectionMode teamSelectionModeOption in Enum.GetValues(typeof(PvpTeamSelectionMode)))
+ {
+ var optionName = teamSelectionModeOption.ToString();
+ teamSelectionMode.AddElement(teamSelectionModeOption,
+ TextManager.Get($"TeamSelectionMode.{optionName}"),
+ TextManager.Get($"TeamSelectionMode.{optionName}.tooltip"));
+ }
+ AssignGUIComponent(nameof(PvpTeamSelectionMode), teamSelectionMode);
+
+ var autoBalanceThresholdLabel = new GUITextBlock(
+ new RectTransform(new Vector2(1.0f, 0.05f),
+ listBox.Content.RectTransform),
+ TextManager.Get("AutoBalanceThreshold"));
+ var autoBalanceThresholdTooltip = TextManager.Get("AutoBalanceThreshold.tooltip");
+ autoBalanceThresholdLabel.ToolTip = autoBalanceThresholdTooltip;
+ var autoBalanceThreshold = new GUISelectionCarousel(
+ new RectTransform(new Vector2(0.5f, 0.6f),
+ autoBalanceThresholdLabel.RectTransform,
+ Anchor.CenterRight));
+ autoBalanceThreshold.AddElement(0, TextManager.Get($"AutoBalanceThreshold.Off"), autoBalanceThresholdTooltip);
+ autoBalanceThreshold.AddElement(1, "1", autoBalanceThresholdTooltip);
+ autoBalanceThreshold.AddElement(2, "2", autoBalanceThresholdTooltip);
+ autoBalanceThreshold.AddElement(3, "3", autoBalanceThresholdTooltip);
+ AssignGUIComponent(nameof(PvpAutoBalanceThreshold), autoBalanceThreshold);
+
+ // ******* GAMEPLAY ***************************
NetLobbyScreen.CreateSubHeader("serversettingsroundstab", listBox.Content);
var voiceChatEnabled = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), listBox.Content.RectTransform),
@@ -429,6 +465,12 @@ namespace Barotrauma.Networking
var allowSpecBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), listBox.Content.RectTransform), TextManager.Get("ServerSettingsAllowSpectating"));
AssignGUIComponent(nameof(AllowSpectating), allowSpecBox);
+ var allowAfkBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), listBox.Content.RectTransform), TextManager.Get("ServerSettingsAllowAFK"))
+ {
+ ToolTip = TextManager.Get("ServerSettingsAllowAFK.tooltip")
+ };
+ AssignGUIComponent(nameof(AllowAFK), allowAfkBox);
+
var losModeLabel = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), listBox.Content.RectTransform),
TextManager.Get("LosEffect"));
var losModeSelection = new GUISelectionCarousel(new RectTransform(new Vector2(0.5f, 0.6f), losModeLabel.RectTransform, Anchor.CenterRight));
@@ -485,6 +527,9 @@ namespace Barotrauma.Networking
AssignGUIComponent(nameof(NewCampaignDefaultSalary), defaultSalarySlider);
defaultSalarySlider.OnMoved(defaultSalarySlider, defaultSalarySlider.BarScroll);
+ var pvpDisembarkPoints = NetLobbyScreen.CreateLabeledNumberInput(listBox.Content, "serversettingsdisembarkpoints", 0, 100, "serversettingsdisembarkpointstooltip");
+ AssignGUIComponent(nameof(DisembarkPointAllowance), pvpDisembarkPoints);
+
//--------------------------------------------------------------------------------
// game settings
//--------------------------------------------------------------------------------
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/Voip/VoipCapture.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/Voip/VoipCapture.cs
index c3dbcca81..a87ba866d 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Networking/Voip/VoipCapture.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/Voip/VoipCapture.cs
@@ -202,6 +202,7 @@ namespace Barotrauma.Networking
{
DebugConsole.ThrowError("Capture device has been disconnected. You can select another available device in the settings.");
Disconnected = true;
+ TryRefreshDevice();
break;
}
}
@@ -320,7 +321,7 @@ namespace Barotrauma.Networking
private Sound overrideSound;
private int overridePos;
- private short[] overrideBuf = new short[VoipConfig.BUFFER_SIZE];
+ private readonly short[] overrideBuf = new short[VoipConfig.BUFFER_SIZE];
private void FillBuffer()
{
@@ -331,13 +332,13 @@ namespace Barotrauma.Networking
{
int sampleCount = overrideSound.FillStreamBuffer(overridePos, overrideBuf);
overridePos += sampleCount * 2;
- Array.Copy(overrideBuf, 0, uncompressedBuffer, totalSampleCount, sampleCount);
+ Array.Copy(overrideBuf, 0, uncompressedBuffer, totalSampleCount, Math.Min(sampleCount, uncompressedBuffer.Length - totalSampleCount));
totalSampleCount += sampleCount;
if (sampleCount == 0)
{
overridePos = 0;
- }
+ }
}
int sleepMs = VoipConfig.BUFFER_SIZE * 800 / VoipConfig.FREQUENCY;
Thread.Sleep(sleepMs - 1);
@@ -382,7 +383,14 @@ namespace Barotrauma.Networking
}
else
{
- overrideSound = GameMain.SoundManager.LoadSound(fileName, true);
+ try
+ {
+ overrideSound = GameMain.SoundManager.LoadSound(fileName, true);
+ }
+ catch (Exception e)
+ {
+ DebugConsole.ThrowError($"Failed to load the sound {fileName}.", e);
+ }
}
}
@@ -394,5 +402,65 @@ namespace Barotrauma.Networking
captureThread = null;
if (captureDevice != IntPtr.Zero) { Alc.CaptureCloseDevice(captureDevice); }
}
+
+ public static void TryRefreshDevice()
+ {
+ DebugConsole.NewMessage("Refreshing audio capture device");
+
+ List deviceList = Alc.GetStringList(IntPtr.Zero, Alc.CaptureDeviceSpecifier).ToList();
+ int alcError = Alc.GetError(IntPtr.Zero);
+ if (alcError != Alc.NoError)
+ {
+ DebugConsole.ThrowError("Failed to list available audio input devices: " + alcError.ToString());
+ return;
+ }
+
+ if (deviceList.Any())
+ {
+ string device;
+
+ if (deviceList.Find(n => n.Equals(GameSettings.CurrentConfig.Audio.VoiceCaptureDevice, StringComparison.OrdinalIgnoreCase))
+ is string availablePreviousDevice)
+ {
+ DebugConsole.NewMessage($" Previous device choice available: {availablePreviousDevice}");
+ device = availablePreviousDevice;
+ }
+ else
+ {
+ device = Alc.GetString(IntPtr.Zero, Alc.CaptureDefaultDeviceSpecifier);
+ DebugConsole.NewMessage($" Reverting to default device: {device}");
+ }
+
+ if (string.IsNullOrEmpty(device))
+ {
+ device = deviceList[0];
+ DebugConsole.NewMessage($" No default device found, resorting to first available device: {device}");
+ }
+
+ // Save the new device choice and generate a new voice capture instance with it
+ var currentConfig = GameSettings.CurrentConfig;
+ currentConfig.Audio.VoiceCaptureDevice = device;
+ GameSettings.SetCurrentConfig(currentConfig);
+ if (Instance is VoipCapture currentCaptureInstance)
+ {
+ currentCaptureInstance.Dispose();
+ }
+ Create(GameSettings.CurrentConfig.Audio.VoiceCaptureDevice);
+ }
+
+ // Didn't end up with any capture device, so let's disable voice capture for now
+ if (Instance == null)
+ {
+ DebugConsole.NewMessage($" No devices found, disabling");
+ var currentConfig = GameSettings.CurrentConfig;
+ currentConfig.Audio.VoiceSetting = VoiceMode.Disabled;
+ GameSettings.SetCurrentConfig(currentConfig);
+ }
+
+ if (GUI.SettingsMenuOpen)
+ {
+ SettingsMenu.Instance?.CreateAudioAndVCTab(true);
+ }
+ }
}
}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/Voip/VoipClient.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/Voip/VoipClient.cs
index f87161645..596a03649 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Networking/Voip/VoipClient.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/Voip/VoipClient.cs
@@ -116,12 +116,12 @@ namespace Barotrauma.Networking
bool spectating = Character.Controlled == null;
float rangeMultiplier = spectating ? 2.0f : 1.0f;
WifiComponent senderRadio = null;
+
var messageType =
!client.VoipQueue.ForceLocal &&
ChatMessage.CanUseRadio(client.Character, out senderRadio) &&
- ChatMessage.CanUseRadio(Character.Controlled, out var recipientRadio) &&
- senderRadio.CanReceive(recipientRadio) ?
- ChatMessageType.Radio : ChatMessageType.Default;
+ (spectating || (ChatMessage.CanUseRadio(Character.Controlled, out var recipientRadio) && senderRadio.CanReceive(recipientRadio)))
+ ? ChatMessageType.Radio : ChatMessageType.Default;
client.Character.ShowTextlessSpeechBubble(1.25f, ChatMessage.MessageColor[(int)messageType]);
client.VoipSound.UseRadioFilter = messageType == ChatMessageType.Radio && !GameSettings.CurrentConfig.Audio.DisableVoiceChatFilters;
@@ -149,7 +149,7 @@ namespace Barotrauma.Networking
GameMain.NetLobbyScreen?.SetPlayerSpeaking(client);
GameMain.GameSession?.CrewManager?.SetClientSpeaking(client);
- if ((client.VoipSound.CurrentAmplitude * client.VoipSound.Gain * GameMain.SoundManager.GetCategoryGainMultiplier("voip")) > 0.1f) //TODO: might need to tweak
+ if ((client.VoipSound.CurrentAmplitude * client.VoipSound.Gain * GameMain.SoundManager.GetCategoryGainMultiplier(SoundManager.SoundCategoryVoip)) > 0.1f) //TODO: might need to tweak
{
if (client.Character != null && !client.Character.Removed && !client.Character.IsDead)
{
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/Voting.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/Voting.cs
index 9ca9c34f9..b8fc09f6b 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Networking/Voting.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/Voting.cs
@@ -59,22 +59,77 @@ namespace Barotrauma
switch (voteType)
{
case VoteType.Sub:
- case VoteType.Mode:
- GUIListBox listBox = (voteType == VoteType.Sub) ?
- GameMain.NetLobbyScreen.SubList : GameMain.NetLobbyScreen.ModeList;
+ var subList = GameMain.NetLobbyScreen.SubList;
- foreach (GUIComponent comp in listBox.Content.Children)
+ foreach (GUIComponent comp in subList.Content.Children)
{
- if (comp.FindChild("votes") is GUITextBlock voteText) { comp.RemoveChild(voteText); }
+ TryRemoveVoteText(comp);
+
+ var container = comp.GetChild