From 234fb6bc06e1c192a5820f8cf13e67294a021a62 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Wed, 27 Oct 2021 18:50:57 +0300 Subject: [PATCH] Release v0.15.12.0 --- .../ClientSource/Characters/AI/AITarget.cs | 7 +- .../Characters/AI/EnemyAIController.cs | 6 +- .../Characters/Animation/Ragdoll.cs | 40 +- .../ClientSource/Characters/Attack.cs | 3 +- .../ClientSource/Characters/Character.cs | 252 ++- .../ClientSource/Characters/CharacterHUD.cs | 70 +- .../ClientSource/Characters/CharacterInfo.cs | 790 ++++++-- .../Characters/CharacterNetworking.cs | 53 +- .../Characters/Health/AfflictionHusk.cs | 8 +- .../Characters/Health/CharacterHealth.cs | 1091 +++++------ .../ClientSource/Characters/Limb.cs | 218 ++- .../ClientSource/DebugConsole.cs | 100 +- .../Missions/AbandonedOutpostMission.cs | 1 + .../Events/Missions/AlienRuinMission.cs | 32 + .../Events/Missions/BeaconMission.cs | 12 - .../Events/Missions/CargoMission.cs | 1 + .../Events/Missions/CombatMission.cs | 5 - .../Events/Missions/EscortMission.cs | 2 + .../Events/Missions/MineralMission.cs | 11 +- .../ClientSource/Events/Missions/Mission.cs | 12 +- .../Events/Missions/MissionPrefab.cs | 47 +- .../Events/Missions/MonsterMission.cs | 1 + .../Events/Missions/NestMission.cs | 1 + .../Events/Missions/PirateMission.cs | 1 + .../Events/Missions/SalvageMission.cs | 1 + .../Events/Missions/ScanMission.cs | 67 + .../BarotraumaClient/ClientSource/GUI/GUI.cs | 95 +- .../ClientSource/GUI/GUIComponent.cs | 41 +- .../ClientSource/GUI/GUIDropDown.cs | 6 +- .../ClientSource/GUI/GUIFrame.cs | 6 +- .../ClientSource/GUI/GUIImage.cs | 2 +- .../ClientSource/GUI/GUIListBox.cs | 52 +- .../ClientSource/GUI/GUIProgressBar.cs | 6 +- .../ClientSource/GUI/GUIScissorComponent.cs | 87 + .../ClientSource/GUI/GUIStyle.cs | 58 +- .../ClientSource/GUI/HUDLayoutSettings.cs | 2 +- .../ClientSource/GUI/LoadingScreen.cs | 43 +- .../ClientSource/GUI/ShapeExtensions.cs | 41 +- .../ClientSource/GUI/Store.cs | 109 +- .../ClientSource/GUI/TabMenu.cs | 582 +++++- .../ClientSource/GUI/UISprite.cs | 5 +- .../ClientSource/GUI/UpgradeStore.cs | 33 +- .../BarotraumaClient/ClientSource/GameMain.cs | 48 +- .../ClientSource/GameSession/CargoManager.cs | 31 +- .../ClientSource/GameSession/CrewManager.cs | 820 ++++---- .../GameModes/MultiPlayerCampaign.cs | 6 +- .../ClientSource/GameSession/GameSession.cs | 65 +- .../ClientSource/GameSession/HintManager.cs | 10 +- .../ClientSource/GameSettings.cs | 23 +- .../ClientSource/Items/CharacterInventory.cs | 73 +- .../Components/EntitySpawnerComponent.cs | 61 + .../Items/Components/GeneticMaterial.cs | 86 + .../Items/Components/Holdable/IdCard.cs | 198 +- .../Items/Components/Holdable/RangedWeapon.cs | 63 +- .../Items/Components/ItemComponent.cs | 9 +- .../Items/Components/ItemContainer.cs | 31 +- .../Items/Components/LightComponent.cs | 2 +- .../Components/Machines/Deconstructor.cs | 122 +- .../Items/Components/Machines/Fabricator.cs | 155 +- .../Items/Components/Machines/MiniMap.cs | 1673 +++++++++++++++-- .../Items/Components/Machines/Pump.cs | 13 +- .../Items/Components/Machines/Sonar.cs | 45 +- .../Items/Components/Machines/Steering.cs | 1 + .../Items/Components/Power/PowerContainer.cs | 2 +- .../ClientSource/Items/Components/Quality.cs | 21 + .../Items/Components/RemoteController.cs | 22 + .../Items/Components/Repairable.cs | 96 +- .../ClientSource/Items/Components/Rope.cs | 25 +- .../ClientSource/Items/Components/Scanner.cs | 29 + .../Items/Components/Signal/ButtonTerminal.cs | 115 ++ .../Items/Components/Signal/Connection.cs | 5 + .../Components/Signal/ConnectionPanel.cs | 5 - .../Items/Components/Signal/Terminal.cs | 10 +- .../Items/Components/StatusHUD.cs | 114 +- .../ClientSource/Items/Components/Turret.cs | 20 +- .../ClientSource/Items/Components/Wearable.cs | 12 +- .../ClientSource/Items/Inventory.cs | 138 +- .../ClientSource/Items/Item.cs | 60 +- .../ClientSource/Items/ItemPrefab.cs | 13 +- .../ClientSource/Map/Explosion.cs | 2 + .../BarotraumaClient/ClientSource/Map/Hull.cs | 2 +- .../Map/Levels/Ruins/RuinGenerator.cs | 11 +- .../ClientSource/Map/Lights/ConvexHull.cs | 13 +- .../ClientSource/Map/Lights/LightManager.cs | 50 +- .../ClientSource/Map/Lights/LightSource.cs | 115 +- .../ClientSource/Map/Map/Map.cs | 4 +- .../ClientSource/Map/MapEntity.cs | 104 +- .../ClientSource/Map/Structure.cs | 48 +- .../ClientSource/Map/Submarine.cs | 210 ++- .../ClientSource/Map/SubmarinePreview.cs | 3 + .../ClientSource/Map/WayPoint.cs | 12 +- .../ClientSource/Networking/ChatMessage.cs | 5 +- .../ClientSource/Networking/GameClient.cs | 13 + .../Primitives/Peers/SteamP2PClientPeer.cs | 2 +- .../ClientSource/Networking/ServerInfo.cs | 41 +- .../ClientSource/Networking/SteamManager.cs | 2 +- .../ClientSource/Particles/Particle.cs | 21 +- .../ClientSource/Particles/ParticleEmitter.cs | 2 + .../ClientSource/Particles/ParticleManager.cs | 47 +- .../ClientSource/Particles/ParticlePrefab.cs | 12 +- .../BarotraumaClient/ClientSource/Program.cs | 25 +- .../CampaignSetupUI/CampaignSetupUI.cs | 52 + .../MultiPlayerCampaignSetupUI.cs} | 438 +---- .../SinglePlayerCampaignSetupUI.cs | 837 +++++++++ .../ClientSource/Screens/CampaignUI.cs | 4 +- .../CharacterEditor/CharacterEditorScreen.cs | 117 +- .../Screens/CharacterEditor/Wizard.cs | 16 +- .../Screens/EventEditor/EventEditorScreen.cs | 2 +- .../ClientSource/Screens/GameScreen.cs | 38 +- .../ClientSource/Screens/LevelEditorScreen.cs | 208 +- .../ClientSource/Screens/MainMenuScreen.cs | 51 +- .../ClientSource/Screens/NetLobbyScreen.cs | 649 +++---- .../ClientSource/Screens/ServerListScreen.cs | 35 +- .../Screens/SteamWorkshopScreen.cs | 2 +- .../ClientSource/Screens/SubEditorScreen.cs | 361 ++-- .../ClientSource/Screens/TestScreen.cs | 112 ++ .../Serialization/SerializableEntityEditor.cs | 88 + .../ClientSource/Sounds/SoundPlayer.cs | 38 +- .../ClientSource/Sprite/Sprite.cs | 14 +- .../ClientSource/Utils/SpreadsheetExport.cs | 4 +- .../ClientSource/Utils/ToolBox.cs | 222 ++- .../Content/Effects/blueprintshader.xnb | Bin 0 -> 2462 bytes .../Effects/blueprintshader_opengl.xnb | Bin 0 -> 2010 bytes .../Content/Effects/thresholdtint.xnb | Bin 0 -> 1501 bytes .../Content/Effects/thresholdtint_opengl.xnb | Bin 0 -> 1394 bytes .../BarotraumaClient/LinuxClient.csproj | 6 +- Barotrauma/BarotraumaClient/MacClient.csproj | 8 +- .../BarotraumaClient/Shaders/Content.mgcb | 12 + .../Shaders/Content_opengl.mgcb | 11 + .../Shaders/blueprintshader.fx | 48 + .../Shaders/blueprintshader_opengl.fx | 48 + .../BarotraumaClient/Shaders/thresholdtint.fx | 32 + .../Shaders/thresholdtint_opengl.fx | 32 + .../BarotraumaClient/WindowsClient.csproj | 6 +- .../BarotraumaServer/LinuxServer.csproj | 4 +- Barotrauma/BarotraumaServer/MacServer.csproj | 4 +- .../ServerSource/Characters/Character.cs | 14 + .../ServerSource/Characters/CharacterInfo.cs | 43 +- .../Characters/CharacterNetworking.cs | 76 +- .../ServerSource/DebugConsole.cs | 101 +- .../Missions/AbandonedOutpostMission.cs | 1 + .../Events/Missions/AlienRuinMission.cs | 22 + .../Events/Missions/BeaconMission.cs | 12 - .../Events/Missions/CargoMission.cs | 1 + .../Events/Missions/CombatMission.cs | 5 - .../Events/Missions/EscortMission.cs | 2 + .../Events/Missions/MineralMission.cs | 7 +- .../ServerSource/Events/Missions/Mission.cs | 10 +- .../Events/Missions/MonsterMission.cs | 2 + .../Events/Missions/NestMission.cs | 1 + .../Events/Missions/PirateMission.cs | 2 + .../Events/Missions/SalvageMission.cs | 2 + .../Events/Missions/ScanMission.cs | 37 + .../BarotraumaServer/ServerSource/GameMain.cs | 31 +- .../GameModes/CharacterCampaignData.cs | 60 +- .../GameModes/MultiPlayerCampaign.cs | 78 +- .../Items/Components/GeneticMaterial.cs | 20 + .../Items/Components/Machines/Steering.cs | 6 + .../Items/Components/Repairable.cs | 2 + .../ServerSource/Items/Components/Scanner.cs | 14 + .../Items/Components/Signal/ButtonTerminal.cs | 21 + .../Components/Signal/ConnectionPanel.cs | 2 +- .../Items/Components/Signal/Terminal.cs | 4 +- .../ServerSource/Items/Item.cs | 9 + .../Networking/ChildServerRelay.cs | 10 +- .../ServerSource/Networking/GameServer.cs | 48 +- .../ServerSource/Networking/KarmaManager.cs | 16 +- .../ServerEntityEventManager.cs | 8 +- .../ServerSource/Networking/RespawnManager.cs | 48 +- .../ServerSource/Networking/ServerSettings.cs | 6 +- .../ServerSource/Networking/Voting.cs | 2 +- .../Traitors/Goals/GoalFindItem.cs | 5 +- .../BarotraumaServer/WindowsServer.csproj | 4 +- .../Data/ContentPackages/Vanilla 0.9.xml | 51 + .../Characters/AI/AIController.cs | 169 +- .../SharedSource/Characters/AI/AITarget.cs | 18 +- .../Characters/AI/EnemyAIController.cs | 1298 ++++++++----- .../Characters/AI/HumanAIController.cs | 168 +- .../Characters/AI/IndoorsSteeringManager.cs | 198 +- .../SharedSource/Characters/AI/LatchOntoAI.cs | 212 ++- .../Characters/AI/Objectives/AIObjective.cs | 3 +- .../AI/Objectives/AIObjectiveCleanupItem.cs | 13 +- .../AI/Objectives/AIObjectiveCombat.cs | 89 +- .../Objectives/AIObjectiveExtinguishFire.cs | 8 +- .../Objectives/AIObjectiveExtinguishFires.cs | 2 +- .../Objectives/AIObjectiveFightIntruders.cs | 4 +- .../Objectives/AIObjectiveFindDivingGear.cs | 60 +- .../AI/Objectives/AIObjectiveFindSafety.cs | 54 +- .../AI/Objectives/AIObjectiveGetItem.cs | 2 +- .../AI/Objectives/AIObjectiveGoTo.cs | 103 +- .../AI/Objectives/AIObjectiveIdle.cs | 34 +- .../AI/Objectives/AIObjectiveManager.cs | 40 +- .../AI/Objectives/AIObjectiveRescue.cs | 2 +- .../AI/Objectives/AIObjectiveRescueAll.cs | 2 +- .../AI/Objectives/AIObjectiveReturn.cs | 273 +++ .../SharedSource/Characters/AI/Order.cs | 49 +- .../SharedSource/Characters/AI/PathFinder.cs | 273 +-- .../Characters/AI/SteeringManager.cs | 5 +- .../Characters/AI/SteeringPath.cs | 47 +- .../Characters/AI/Wreck/WreckAI.cs | 4 +- .../Characters/Animation/AnimController.cs | 517 ++++- .../Animation/FishAnimController.cs | 50 +- .../Animation/HumanoidAnimController.cs | 667 ++----- .../Characters/Animation/Ragdoll.cs | 103 +- .../SharedSource/Characters/Attack.cs | 12 +- .../SharedSource/Characters/Character.cs | 612 ++++-- .../SharedSource/Characters/CharacterInfo.cs | 751 ++++++-- .../Health/Afflictions/Affliction.cs | 136 +- .../Health/Afflictions/AfflictionHusk.cs | 68 +- .../Health/Afflictions/AfflictionPrefab.cs | 222 ++- .../Characters/Health/CharacterHealth.cs | 233 ++- .../SharedSource/Characters/HumanPrefab.cs | 8 +- .../SharedSource/Characters/Jobs/Job.cs | 4 +- .../SharedSource/Characters/Jobs/JobPrefab.cs | 24 +- .../SharedSource/Characters/Jobs/Skill.cs | 9 +- .../SharedSource/Characters/Limb.cs | 54 +- .../Params/Animation/AnimationParams.cs | 34 +- .../Params/Animation/FishAnimations.cs | 20 - .../Params/Animation/HumanoidAnimations.cs | 66 +- .../Characters/Params/CharacterParams.cs | 42 +- .../Params/Ragdoll/RagdollParams.cs | 24 +- .../AbilityConditionals/AbilityCondition.cs | 92 + .../AbilityConditionAffliction.cs | 29 + .../AbilityConditionAttackData.cs | 79 + .../AbilityConditionAttackResult.cs | 38 + .../AbilityConditionCharacter.cs | 30 + .../AbilityConditionData.cs | 36 + .../AbilityConditionEvasiveManeuvers.cs | 22 + .../AbilityConditionIsAiming.cs | 60 + .../AbilityConditionItem.cs | 49 + .../AbilityConditionItemOutsideSubmarine.cs | 23 + .../AbilityConditionItemWreck.cs | 23 + .../AbilityConditionLocation.cs | 41 + .../AbilityConditionMission.cs | 38 + .../AbilityConditionReduceAffliction.cs | 33 + .../AbilityConditionSkill.cs | 32 + .../AbilityConditionAboveVitality.cs | 19 + .../AbilityConditionAlliesAboveVitality.cs | 19 + .../AbilityConditionCoauthor.cs | 26 + .../AbilityConditionCrouched.cs | 18 + .../AbilityConditionDataless.cs | 20 + .../AbilityConditionHasAffliction.cs | 31 + .../AbilityConditionHasDifferentJobs.cs | 22 + .../AbilityConditionHasItem.cs | 57 + .../AbilityConditionHasPermanentStat.cs | 29 + .../AbilityConditionHasSkill.cs | 24 + .../AbilityConditionHasStatusTag.cs | 31 + .../AbilityConditionHasVelocity.cs | 20 + .../AbilityConditionInFriendlySubmarine.cs | 15 + .../AbilityConditionInHull.cs | 15 + .../AbilityConditionInWater.cs | 15 + .../AbilityConditionLevelsBehindHighest.cs | 20 + .../AbilityConditionNoCrewDied.cs | 22 + .../AbilityConditionOnMission.cs | 17 + .../AbilityConditionRagdolled.cs | 18 + .../AbilityConditionRunning.cs | 15 + .../AbilityConditionServerRandom.cs | 20 + .../AbilityConditionShipFlooded.cs | 21 + .../Talents/Abilities/AbilityInterfaces.cs | 52 + .../Talents/Abilities/AbilityObjects.cs | 187 ++ .../Talents/Abilities/CharacterAbility.cs | 131 ++ .../Abilities/CharacterAbilityApplyForce.cs | 61 + .../CharacterAbilityApplyStatusEffects.cs | 88 + ...racterAbilityApplyStatusEffectsToAllies.cs | 35 + ...cterAbilityApplyStatusEffectsToAttacker.cs | 20 + ...pplyStatusEffectsToLastOrderedCharacter.cs | 31 + ...rAbilityApplyStatusEffectsToNearestAlly.cs | 35 + ...erAbilityApplyStatusEffectsToRandomAlly.cs | 41 + .../CharacterAbilityGainSimultaneousSkill.cs | 27 + .../CharacterAbilityGiveAffliction.cs | 44 + .../Abilities/CharacterAbilityGiveFlag.cs | 20 + .../Abilities/CharacterAbilityGiveMoney.cs | 46 + .../CharacterAbilityGivePermanentStat.cs | 64 + .../CharacterAbilityGiveResistance.cs | 26 + .../Abilities/CharacterAbilityGiveStat.cs | 21 + .../CharacterAbilityGiveTalentPoints.cs | 23 + .../CharacterAbilityIncreaseSkill.cs | 60 + .../CharacterAbilityModifyAffliction.cs | 36 + .../CharacterAbilityModifyAttackData.cs | 53 + .../Abilities/CharacterAbilityModifyFlag.cs | 34 + .../CharacterAbilityModifyReduceAffliction.cs | 27 + .../CharacterAbilityModifyResistance.cs | 32 + .../Abilities/CharacterAbilityModifyStat.cs | 26 + .../CharacterAbilityModifyStatToFlooding.cs | 34 + .../CharacterAbilityModifyStatToLevel.cs | 35 + .../CharacterAbilityModifyStatToSkill.cs | 52 + .../Abilities/CharacterAbilityModifyValue.cs | 25 + .../Abilities/CharacterAbilityPutItem.cs | 46 + .../CharacterAbilityResetPermanentStat.cs | 30 + .../Abilities/CharacterAbilityRevive.cs | 29 + .../CharacterAbilitySpawnItemsToContainer.cs | 44 + .../Abilities/CharacterAbilityUnlockTree.cs | 31 + .../CharacterAbilityAlienHoarder.cs | 41 + .../CharacterAbilityApprenticeship.cs | 21 + .../CharacterAbilityAtmosMachine.cs | 43 + .../CharacterAbilityBountyHunter.cs | 23 + .../CharacterAbilityByTheBook.cs | 42 + .../CharacterAbilityInsurancePolicy.cs | 31 + .../CharacterAbilityMultitasker.cs | 26 + .../CharacterAbilityPsychoClown.cs | 44 + .../CharacterAbilityRegenerateLoot.cs | 39 + .../CharacterAbilityTandemFire.cs | 43 + .../AbilityGroups/CharacterAbilityGroup.cs | 222 +++ .../CharacterAbilityGroupEffect.cs | 36 + .../CharacterAbilityGroupInterval.cs | 55 + .../Characters/Talents/CharacterTalent.cs | 127 ++ .../Characters/Talents/TalentPrefab.cs | 147 ++ .../Characters/Talents/TalentTree.cs | 276 +++ .../SharedSource/ContentPackage.cs | 10 +- .../SharedSource/CoroutineManager.cs | 7 +- .../SharedSource/DebugConsole.cs | 241 ++- .../BarotraumaShared/SharedSource/Enums.cs | 141 +- .../Events/EventActions/GiveSkillExpAction.cs | 4 +- .../Events/EventActions/GodModeAction.cs | 50 + .../Events/EventActions/ReputationAction.cs | 6 +- .../Events/EventActions/SpawnAction.cs | 20 +- .../Events/EventActions/TriggerAction.cs | 2 +- .../SharedSource/Events/EventManager.cs | 44 +- .../SharedSource/Events/EventPrefab.cs | 2 +- .../SharedSource/Events/EventSet.cs | 86 +- .../Missions/AbandonedOutpostMission.cs | 2 +- .../Events/Missions/AlienRuinMission.cs | 176 ++ .../Events/Missions/BeaconMission.cs | 2 +- .../Events/Missions/CargoMission.cs | 53 +- .../Events/Missions/GoToMission.cs | 11 - .../Events/Missions/MineralMission.cs | 73 +- .../SharedSource/Events/Missions/Mission.cs | 102 +- .../Events/Missions/MissionPrefab.cs | 13 +- .../Events/Missions/PirateMission.cs | 12 +- .../Events/Missions/SalvageMission.cs | 14 +- .../Events/Missions/ScanMission.cs | 286 +++ .../SharedSource/Events/MonsterEvent.cs | 21 +- .../Extensions/ColorExtensions.cs | 6 +- .../Extensions/VectorExtensions.cs | 7 + .../GameSession/AutoItemPlacer.cs | 94 +- .../SharedSource/GameSession/CrewManager.cs | 8 + .../GameSession/Data/Reputation.cs | 22 +- .../GameSession/GameModes/CampaignMode.cs | 75 +- .../GameModes/CharacterCampaignData.cs | 63 +- .../GameModes/MultiPlayerCampaign.cs | 6 + .../SharedSource/GameSession/GameSession.cs | 108 +- .../SharedSource/GameSettings.cs | 267 +-- .../SharedSource/Items/CharacterInventory.cs | 26 +- .../Items/Components/DockingPort.cs | 19 +- .../SharedSource/Items/Components/Door.cs | 2 +- .../Items/Components/ElectricalDischarger.cs | 16 + .../Components/EntitySpawnerComponent.cs | 290 +++ .../Items/Components/GeneticMaterial.cs | 227 +++ .../Items/Components/Holdable/Holdable.cs | 96 +- .../Items/Components/Holdable/IdCard.cs | 95 +- .../Items/Components/Holdable/MeleeWeapon.cs | 136 +- .../Items/Components/Holdable/Pickable.cs | 8 +- .../Items/Components/Holdable/Propulsion.cs | 8 +- .../Items/Components/Holdable/RangedWeapon.cs | 85 +- .../Items/Components/Holdable/RepairTool.cs | 22 +- .../Items/Components/Holdable/Throwable.cs | 10 +- .../Items/Components/ItemComponent.cs | 36 +- .../Items/Components/ItemContainer.cs | 296 ++- .../Components/Machines/Deconstructor.cs | 311 ++- .../Items/Components/Machines/Engine.cs | 34 +- .../Items/Components/Machines/Fabricator.cs | 239 ++- .../Items/Components/Machines/MiniMap.cs | 95 +- .../Components/Machines/OxygenGenerator.cs | 45 +- .../Items/Components/Machines/Pump.cs | 10 + .../Items/Components/Machines/Reactor.cs | 27 +- .../Items/Components/Machines/Steering.cs | 15 +- .../Items/Components/Power/PowerTransfer.cs | 17 + .../Items/Components/Projectile.cs | 48 +- .../SharedSource/Items/Components/Quality.cs | 91 + .../Items/Components/RemoteController.cs | 96 + .../Items/Components/Repairable.cs | 167 +- .../SharedSource/Items/Components/Rope.cs | 186 +- .../SharedSource/Items/Components/Scanner.cs | 90 + .../Items/Components/Signal/ButtonTerminal.cs | 119 ++ .../Items/Components/Signal/Connection.cs | 2 +- .../Components/Signal/ConnectionPanel.cs | 37 +- .../Items/Components/Signal/LightComponent.cs | 9 +- .../Components/Signal/OscillatorComponent.cs | 16 +- .../Components/Signal/RegExFindComponent.cs | 8 +- .../Items/Components/Signal/Terminal.cs | 13 +- .../Items/Components/Signal/WaterDetector.cs | 4 +- .../Items/Components/Signal/WifiComponent.cs | 2 +- .../Items/Components/Signal/Wire.cs | 6 +- .../Items/Components/TriggerComponent.cs | 187 ++ .../SharedSource/Items/Components/Turret.cs | 314 ++-- .../SharedSource/Items/Components/Wearable.cs | 58 +- .../SharedSource/Items/Inventory.cs | 40 +- .../SharedSource/Items/Item.cs | 229 ++- .../SharedSource/Items/ItemInventory.cs | 18 +- .../SharedSource/Items/ItemPrefab.cs | 176 +- .../SharedSource/Items/RelatedItem.cs | 22 +- .../SharedSource/Map/Entity.cs | 75 +- .../SharedSource/Map/Explosion.cs | 91 +- .../BarotraumaShared/SharedSource/Map/Hull.cs | 60 +- .../SharedSource/Map/Levels/Level.cs | 446 +++-- .../Levels/LevelObjects/LevelObjectManager.cs | 37 +- .../Map/Levels/LevelObjects/LevelTrigger.cs | 288 +-- .../SharedSource/Map/Levels/Ruins/BTRoom.cs | 190 -- .../SharedSource/Map/Levels/Ruins/Corridor.cs | 210 --- .../Map/Levels/Ruins/RuinGenerationParams.cs | 439 +---- .../Map/Levels/Ruins/RuinGenerator.cs | 1319 +------------ .../SharedSource/Map/Map/Location.cs | 46 +- .../SharedSource/Map/Map/Map.cs | 26 +- .../SharedSource/Map/Map/Radiation.cs | 2 +- .../SharedSource/Map/MapEntity.cs | 37 +- .../Map/Outposts/OutpostGenerationParams.cs | 34 +- .../Map/Outposts/OutpostGenerator.cs | 84 +- .../Map/Outposts/OutpostModuleInfo.cs | 2 + .../SharedSource/Map/PriceInfo.cs | 14 +- .../SharedSource/Map/Structure.cs | 177 +- .../SharedSource/Map/StructurePrefab.cs | 6 +- .../SharedSource/Map/Submarine.cs | 72 +- .../SharedSource/Map/SubmarineBody.cs | 5 +- .../SharedSource/Map/SubmarineInfo.cs | 4 +- .../SharedSource/Map/WayPoint.cs | 252 ++- .../Networking/ChildServerRelay.cs | 4 +- .../SharedSource/Networking/EntitySpawner.cs | 12 +- .../NetEntityEvent/NetEntityEvent.cs | 4 + .../Networking/OrderChatMessage.cs | 5 +- .../Primitives/Message/IReadMessage.cs | 2 + .../Primitives/Message/IWriteMessage.cs | 2 + .../Networking/Primitives/Message/Message.cs | 99 +- .../SharedSource/Networking/RespawnManager.cs | 16 +- .../SharedSource/Networking/ServerSettings.cs | 13 +- .../SharedSource/Physics/PhysicsBody.cs | 36 +- .../SharedSource/Screens/NetLobbyScreen.cs | 14 +- .../Serialization/SerializableProperty.cs | 15 +- .../Serialization/XMLExtensions.cs | 201 +- .../SharedSource/Sprite/Sprite.cs | 14 +- .../StatusEffects/PropertyConditional.cs | 19 + .../StatusEffects/StatusEffect.cs | 547 ++++-- .../SharedSource/SteamAchievementManager.cs | 6 +- .../SharedSource/TextManager.cs | 55 + .../SharedSource/Upgrades/Upgrade.cs | 2 +- .../SharedSource/Upgrades/UpgradePrefab.cs | 22 +- .../SharedSource/Utils/IdRemap.cs | 36 +- .../SharedSource/Utils/MathUtils.cs | 5 + .../SharedSource/Utils/Range.cs | 44 + .../SharedSource/Utils/SafeIO.cs | 40 +- .../SharedSource/Utils/SaveUtil.cs | 14 +- .../SharedSource/Utils/ToolBox.cs | 69 +- .../SharedSource/Utils/UpdaterUtil.cs | 223 --- .../BarotraumaShared/Submarines/Azimuth.sub | Bin 229143 -> 231424 bytes .../BarotraumaShared/Submarines/Humpback.sub | Bin 207698 -> 207565 bytes .../BarotraumaShared/Submarines/Remora.sub | Bin 279016 -> 279195 bytes .../Submarines/RemoraDrone.sub | Bin 280590 -> 262007 bytes Barotrauma/BarotraumaShared/TintTest.png | Bin 0 -> 91346 bytes Barotrauma/BarotraumaShared/changelog.txt | 1007 ++++++---- Barotrauma/BarotraumaShared/config.xml | 2 +- .../BarotraumaShared/serversettings.xml | 3 +- 450 files changed, 26042 insertions(+), 10457 deletions(-) create mode 100644 Barotrauma/BarotraumaClient/ClientSource/Events/Missions/AlienRuinMission.cs delete mode 100644 Barotrauma/BarotraumaClient/ClientSource/Events/Missions/BeaconMission.cs create mode 100644 Barotrauma/BarotraumaClient/ClientSource/Events/Missions/ScanMission.cs create mode 100644 Barotrauma/BarotraumaClient/ClientSource/GUI/GUIScissorComponent.cs create mode 100644 Barotrauma/BarotraumaClient/ClientSource/Items/Components/EntitySpawnerComponent.cs create mode 100644 Barotrauma/BarotraumaClient/ClientSource/Items/Components/GeneticMaterial.cs create mode 100644 Barotrauma/BarotraumaClient/ClientSource/Items/Components/Quality.cs create mode 100644 Barotrauma/BarotraumaClient/ClientSource/Items/Components/RemoteController.cs create mode 100644 Barotrauma/BarotraumaClient/ClientSource/Items/Components/Scanner.cs create mode 100644 Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/ButtonTerminal.cs create mode 100644 Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI/CampaignSetupUI.cs rename Barotrauma/BarotraumaClient/ClientSource/Screens/{CampaignSetupUI.cs => CampaignSetupUI/MultiPlayerCampaignSetupUI.cs} (51%) create mode 100644 Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI/SinglePlayerCampaignSetupUI.cs create mode 100644 Barotrauma/BarotraumaClient/ClientSource/Screens/TestScreen.cs create mode 100644 Barotrauma/BarotraumaClient/Content/Effects/blueprintshader.xnb create mode 100644 Barotrauma/BarotraumaClient/Content/Effects/blueprintshader_opengl.xnb create mode 100644 Barotrauma/BarotraumaClient/Content/Effects/thresholdtint.xnb create mode 100644 Barotrauma/BarotraumaClient/Content/Effects/thresholdtint_opengl.xnb create mode 100644 Barotrauma/BarotraumaClient/Shaders/blueprintshader.fx create mode 100644 Barotrauma/BarotraumaClient/Shaders/blueprintshader_opengl.fx create mode 100644 Barotrauma/BarotraumaClient/Shaders/thresholdtint.fx create mode 100644 Barotrauma/BarotraumaClient/Shaders/thresholdtint_opengl.fx create mode 100644 Barotrauma/BarotraumaServer/ServerSource/Events/Missions/AlienRuinMission.cs delete mode 100644 Barotrauma/BarotraumaServer/ServerSource/Events/Missions/BeaconMission.cs create mode 100644 Barotrauma/BarotraumaServer/ServerSource/Events/Missions/ScanMission.cs create mode 100644 Barotrauma/BarotraumaServer/ServerSource/Items/Components/GeneticMaterial.cs create mode 100644 Barotrauma/BarotraumaServer/ServerSource/Items/Components/Scanner.cs create mode 100644 Barotrauma/BarotraumaServer/ServerSource/Items/Components/Signal/ButtonTerminal.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveReturn.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityCondition.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionAffliction.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionAttackData.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionAttackResult.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionCharacter.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionData.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionEvasiveManeuvers.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionIsAiming.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionItem.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionItemOutsideSubmarine.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionItemWreck.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionLocation.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionMission.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionReduceAffliction.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionSkill.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionAboveVitality.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionAlliesAboveVitality.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionCoauthor.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionCrouched.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionDataless.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionHasAffliction.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionHasDifferentJobs.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionHasItem.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionHasPermanentStat.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionHasSkill.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionHasStatusTag.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionHasVelocity.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionInFriendlySubmarine.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionInHull.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionInWater.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionLevelsBehindHighest.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionNoCrewDied.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionOnMission.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionRagdolled.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionRunning.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionServerRandom.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionShipFlooded.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityInterfaces.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityObjects.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbility.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyForce.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffects.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffectsToAllies.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffectsToAttacker.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffectsToLastOrderedCharacter.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffectsToNearestAlly.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffectsToRandomAlly.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGainSimultaneousSkill.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveAffliction.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveFlag.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveMoney.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGivePermanentStat.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveResistance.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveStat.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveTalentPoints.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityIncreaseSkill.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyAffliction.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyAttackData.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyFlag.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyReduceAffliction.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyResistance.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyStat.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyStatToFlooding.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyStatToLevel.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyStatToSkill.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyValue.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityPutItem.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityResetPermanentStat.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityRevive.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilitySpawnItemsToContainer.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityUnlockTree.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityAlienHoarder.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityApprenticeship.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityAtmosMachine.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityBountyHunter.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityByTheBook.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityInsurancePolicy.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityMultitasker.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityPsychoClown.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityRegenerateLoot.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityTandemFire.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroup.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroupEffect.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroupInterval.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/CharacterTalent.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/TalentPrefab.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/TalentTree.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/GodModeAction.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Events/Missions/AlienRuinMission.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Events/Missions/ScanMission.cs rename Barotrauma/{BarotraumaClient/ClientSource => BarotraumaShared/SharedSource}/Extensions/ColorExtensions.cs (69%) create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Items/Components/EntitySpawnerComponent.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Items/Components/GeneticMaterial.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Items/Components/Quality.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Items/Components/RemoteController.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Items/Components/Scanner.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/ButtonTerminal.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Items/Components/TriggerComponent.cs delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Ruins/BTRoom.cs delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Ruins/Corridor.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Utils/Range.cs delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/Utils/UpdaterUtil.cs create mode 100644 Barotrauma/BarotraumaShared/TintTest.png diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/AI/AITarget.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/AI/AITarget.cs index cdf6ce5f5..51bf3a451 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/AI/AITarget.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/AI/AITarget.cs @@ -48,8 +48,13 @@ namespace Barotrauma { color = Color.CornflowerBlue; } - else if (Entity is Item) + else if (Entity is Item i) { + if (i.Submarine != null && i.GetComponent() == null) + { + // Don't show items that are inside the submarine, because monsters shouldn't target them when they are inside and the monsters are outside. + return; + } color = Color.CadetBlue; } else diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/AI/EnemyAIController.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/AI/EnemyAIController.cs index e01494501..74548b53d 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/AI/EnemyAIController.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/AI/EnemyAIController.cs @@ -81,10 +81,10 @@ namespace Barotrauma ConvertUnits.ToDisplayUnits(new Vector2(attachJoint.WorldAnchorB.X, -attachJoint.WorldAnchorB.Y)), GUI.Style.Green, 0, 4); } - if (LatchOntoAI.WallAttachPos.HasValue) + if (LatchOntoAI.AttachPos.HasValue) { - //GUI.DrawLine(spriteBatch, pos, - // ConvertUnits.ToDisplayUnits(new Vector2(LatchOntoAI.WallAttachPos.Value.X, -LatchOntoAI.WallAttachPos.Value.Y)), GUI.Style.Green, 0, 3); + GUI.DrawLine(spriteBatch, pos, + ConvertUnits.ToDisplayUnits(new Vector2(LatchOntoAI.AttachPos.Value.X, -LatchOntoAI.AttachPos.Value.Y)), GUI.Style.Green, 0, 3); } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Animation/Ragdoll.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Animation/Ragdoll.cs index 627c70255..37f6c91ed 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Animation/Ragdoll.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Animation/Ragdoll.cs @@ -284,7 +284,7 @@ namespace Barotrauma limb.LastImpactSoundTime = (float)Timing.TotalTime; if (!string.IsNullOrWhiteSpace(limb.HitSoundTag)) { - bool inWater = limb.inWater; + bool inWater = limb.InWater; if (character.CurrentHull != null && character.CurrentHull.Surface > character.CurrentHull.Rect.Y - character.CurrentHull.Rect.Height + 5.0f && limb.SimPosition.Y < ConvertUnits.ToSimUnits(character.CurrentHull.Rect.Y - character.CurrentHull.Rect.Height) + limb.body.GetMaxExtent()) @@ -342,22 +342,41 @@ namespace Barotrauma partial void SetupDrawOrder() { - //make sure every character gets drawn at a distinct "layer" + //make sure every character gets drawn at a distinct "layer" //(instead of having some of the limbs appear behind and some in front of other characters) float startDepth = 0.1f; float increment = 0.001f; foreach (Character otherCharacter in Character.CharacterList) { - if (otherCharacter == character) continue; + if (otherCharacter == character) { continue; } startDepth += increment; } - //make sure each limb has a distinct depth value - List depthSortedLimbs = Limbs.OrderBy(l => l.ActiveSprite == null ? 0.0f : l.ActiveSprite.Depth).ToList(); + //make sure each limb has a distinct depth value + List depthSortedLimbs = Limbs.OrderBy(l => l.DefaultSpriteDepth).ToList(); foreach (Limb limb in Limbs) { - if (limb.ActiveSprite != null) - limb.ActiveSprite.Depth = startDepth + depthSortedLimbs.IndexOf(limb) * 0.00001f; + var sprite = limb.GetActiveSprite(); + if (sprite == null) { continue; } + sprite.Depth = startDepth + depthSortedLimbs.IndexOf(limb) * 0.00001f; + foreach (var conditionalSprite in limb.ConditionalSprites) + { + if (conditionalSprite.Exclusive) + { + conditionalSprite.ActiveSprite.Depth = sprite.Depth; + } + } } + foreach (Limb limb in Limbs) + { + if (limb.ActiveSprite == null) { continue; } + if (limb.Params.InheritLimbDepth == LimbType.None) { continue; } + var matchingLimb = GetLimb(limb.Params.InheritLimbDepth); + if (matchingLimb != null) + { + limb.ActiveSprite.Depth = matchingLimb.ActiveSprite.Depth - 0.0000001f; + } + } + depthSortedLimbs.Reverse(); inversedLimbDrawOrder = depthSortedLimbs.ToArray(); } @@ -562,9 +581,16 @@ namespace Barotrauma if (this is HumanoidAnimController humanoid) { Vector2 pos = ConvertUnits.ToDisplayUnits(humanoid.RightHandIKPos); + if (humanoid.character.Submarine != null) { pos += humanoid.character.Submarine.Position; } GUI.DrawRectangle(spriteBatch, new Rectangle((int)pos.X, (int)-pos.Y, 4, 4), GUI.Style.Green, true); pos = ConvertUnits.ToDisplayUnits(humanoid.LeftHandIKPos); + if (humanoid.character.Submarine != null) { pos += humanoid.character.Submarine.Position; } GUI.DrawRectangle(spriteBatch, new Rectangle((int)pos.X, (int)-pos.Y, 4, 4), GUI.Style.Green, true); + + Vector2 aimPos = humanoid.AimSourceWorldPos; + aimPos.Y = -aimPos.Y; + GUI.DrawLine(spriteBatch, aimPos - Vector2.UnitY * 3, aimPos + Vector2.UnitY * 3, Color.Red); + GUI.DrawLine(spriteBatch, aimPos - Vector2.UnitX * 3, aimPos + Vector2.UnitX * 3, Color.Red); } if (character.MemState.Count > 1) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Attack.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Attack.cs index e994f5954..480a5fd26 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Attack.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Attack.cs @@ -1,5 +1,4 @@ -using Barotrauma.Sounds; -using Barotrauma.Particles; +using Barotrauma.Particles; using Microsoft.Xna.Framework; using System.Xml.Linq; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs index 0135133fe..3ee39ef88 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs @@ -21,7 +21,6 @@ namespace Barotrauma public static bool DebugDrawInteract; protected float soundTimer; - protected float soundInterval; protected float hudInfoTimer = 1.0f; protected bool hudInfoVisible = false; @@ -129,8 +128,52 @@ namespace Barotrauma get { return gibEmitters; } } + private class GUIMessage + { + public string RawText; + public string Identifier; + public string Text; + + private int _value; + public int Value + { + get { return _value; } + set + { + _value = value; + Text = RawText.Replace("[value]", _value.ToString()); + Size = GUI.Font.MeasureString(Text); + } + } + + public Color Color; + public float Lifetime; + public float Timer; + + public Vector2 Size; + + public bool PlaySound; + + public GUIMessage(string rawText, Color color, float delay, string identifier = null, int? value = null) + { + RawText = Text = rawText; + if (value.HasValue) + { + Text = rawText.Replace("[value]", value.Value.ToString()); + Value = value.Value; + } + Timer = -delay; + Size = GUI.Font.MeasureString(Text); + Color = color; + Identifier = identifier; + Lifetime = 3.0f; + } + } + + private List guiMessages = new List(); + public static bool IsMouseOnUI => GUI.MouseOn != null || - (CharacterInventory.IsMouseOnInventory() && !CharacterInventory.DraggingItemToWorld); + (CharacterInventory.IsMouseOnInventory && !CharacterInventory.DraggingItemToWorld); public class ObjectiveEntity { @@ -161,8 +204,7 @@ namespace Barotrauma partial void InitProjSpecific(XElement mainElement) { - soundInterval = mainElement.GetAttributeFloat("soundinterval", 10.0f); - soundTimer = Rand.Range(0.0f, soundInterval); + soundTimer = Rand.Range(0.0f, Params.SoundInterval); sounds = new List(); Params.Sounds.ForEach(s => sounds.Add(new CharacterSound(s))); @@ -390,12 +432,7 @@ namespace Barotrauma { if (attackResult.Damage <= 1.0f) { return; } } - - if (soundTimer < soundInterval * 0.5f) - { - PlaySound(CharacterSound.SoundType.Damage); - soundTimer = soundInterval; - } + PlaySound(CharacterSound.SoundType.Damage, maxInterval: 2); } partial void KillProjSpecific(CauseOfDeathType causeOfDeath, Affliction causeOfDeathAffliction, bool log) @@ -412,7 +449,7 @@ namespace Barotrauma if (GameMain.NetworkMember.RespawnManager?.UseRespawnPrompt ?? false) { - CoroutineManager.InvokeAfter(() => + CoroutineManager.Invoke(() => { if (controlled != null || (!(GameMain.GameSession?.IsRunning ?? false))) { return; } var respawnPrompt = new GUIMessageBox( @@ -470,9 +507,9 @@ namespace Barotrauma } - private List debugInteractablesInRange = new List(); - private List debugInteractablesAtCursor = new List(); - private List> debugInteractablesNearCursor = new List>(); + private readonly List debugInteractablesInRange = new List(); + private readonly List debugInteractablesAtCursor = new List(); + private readonly List<(Item item, float dist)> debugInteractablesNearCursor = new List<(Item item, float dist)>(); /// /// Finds the front (lowest depth) interactable item at a position. "Interactable" in this case means that the character can "reach" the item. @@ -568,7 +605,7 @@ namespace Barotrauma if (distanceToItem > closestItemDistance) { continue; } if (!CanInteractWith(item)) { continue; } - debugInteractablesNearCursor.Add(new Pair(item, 1.0f - distanceToItem / (100.0f * aimAssistModifier))); + debugInteractablesNearCursor.Add((item, 1.0f - distanceToItem / (100.0f * aimAssistModifier))); closestItem = item; closestItemDistance = distanceToItem; } @@ -579,31 +616,20 @@ namespace Barotrauma private Character FindCharacterAtPosition(Vector2 mouseSimPos, float maxDist = 150.0f) { Character closestCharacter = null; - float closestDist = 0.0f; maxDist = ConvertUnits.ToSimUnits(maxDist); - + float closestDist = maxDist * maxDist; foreach (Character c in CharacterList) { if (!CanInteractWith(c, checkVisibility: false) || (c.AnimController?.SimplePhysicsEnabled ?? true)) { continue; } float dist = Vector2.DistanceSquared(mouseSimPos, c.SimPosition); - if (dist < maxDist * maxDist && (closestCharacter == null || dist < closestDist)) + if (dist < closestDist || + (c.CampaignInteractionType != CampaignMode.InteractionType.None && closestCharacter?.CampaignInteractionType == CampaignMode.InteractionType.None && dist * 0.9f < closestDist)) { closestCharacter = c; closestDist = dist; } - - /*FarseerPhysics.Common.Transform transform; - c.AnimController.Collider.FarseerBody.GetTransform(out transform); - for (int i = 0; i < c.AnimController.Collider.FarseerBody.FixtureList.Count; i++) - { - if (c.AnimController.Collider.FarseerBody.FixtureList[i].Shape.TestPoint(ref transform, ref mouseSimPos)) - { - Console.WriteLine("Hit: " + i); - closestCharacter = c; - } - }*/ } return closestCharacter; @@ -636,9 +662,20 @@ namespace Barotrauma } } + foreach (GUIMessage message in guiMessages) + { + bool wasPending = message.Timer < 0.0f; + message.Timer += deltaTime; + if (wasPending && message.Timer >= 0.0f && message.PlaySound) + { + SoundPlayer.PlayUISound(GUISoundType.UIMessage); + } + } + guiMessages.RemoveAll(m => m.Timer >= m.Lifetime); + if (!enabled) { return; } - if (!IsDead && !IsIncapacitated) + if (!IsIncapacitated) { if (soundTimer > 0) { @@ -649,7 +686,14 @@ namespace Barotrauma switch (enemyAI.State) { case AIState.Attack: - PlaySound(CharacterSound.SoundType.Attack); + if (Rand.Value() > 0.5f) + { + PlaySound(CharacterSound.SoundType.Attack); + } + else + { + PlaySound(CharacterSound.SoundType.Idle); + } break; default: var petBehavior = enemyAI.PetBehavior; @@ -660,7 +704,6 @@ namespace Barotrauma else { PlaySound(CharacterSound.SoundType.Idle); - } break; } @@ -748,6 +791,27 @@ namespace Barotrauma CharacterHUD.Draw(spriteBatch, this, cam); if (drawHealth && !CharacterHUD.IsCampaignInterfaceOpen) { CharacterHealth.DrawHUD(spriteBatch); } } + + public void DrawGUIMessages(SpriteBatch spriteBatch, Camera cam) + { + if (info == null || !Enabled || InvisibleTimer > 0.0f) + { + return; + } + + Vector2 messagePos = DrawPosition; + messagePos.Y += hudInfoHeight; + messagePos = cam.WorldToScreen(messagePos) - Vector2.UnitY * GUI.IntScale(60); + foreach (GUIMessage message in guiMessages) + { + if (message.Timer < 0) { continue; } + Vector2 drawPos = messagePos + Vector2.UnitX * (GUI.IntScale(60) - message.Size.X); + drawPos = new Vector2((int)drawPos.X, (int)drawPos.Y); + float alpha = MathHelper.SmoothStep(1.0f, 0.0f, message.Timer / message.Lifetime); + GUI.DrawString(spriteBatch, drawPos, message.Text, message.Color * alpha); + messagePos -= Vector2.UnitY * message.Size.Y * 1.2f; + } + } public virtual void DrawFront(SpriteBatch spriteBatch, Camera cam) { @@ -827,12 +891,12 @@ namespace Barotrauma GUI.DrawLine(spriteBatch, new Vector2(DrawPosition.X, -DrawPosition.Y), new Vector2(item.DrawPosition.X, -item.DrawPosition.Y), Color.White * 0.1f, width: 4); } - foreach (Pair item in debugInteractablesNearCursor) + foreach ((Item item, float dist) in debugInteractablesNearCursor) { GUI.DrawLine(spriteBatch, cursorPos, - new Vector2(item.First.DrawPosition.X, -item.First.DrawPosition.Y), - ToolBox.GradientLerp(item.Second, GUI.Style.Red, GUI.Style.Orange, GUI.Style.Green), width: 2); + new Vector2(item.DrawPosition.X, -item.DrawPosition.Y), + ToolBox.GradientLerp(dist, GUI.Style.Red, GUI.Style.Orange, GUI.Style.Green), width: 2); } } return; @@ -856,6 +920,7 @@ namespace Barotrauma Vector2 nameSize = GUI.Font.MeasureString(name); Vector2 namePos = new Vector2(pos.X, pos.Y - 10.0f - (5.0f / cam.Zoom)) - nameSize * 0.5f / cam.Zoom; + Color nameColor = GetNameColor(); Vector2 screenSize = new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight); Vector2 viewportSize = new Vector2(cam.WorldView.Width, cam.WorldView.Height); @@ -865,18 +930,6 @@ namespace Barotrauma namePos *= viewportSize / screenSize; namePos.X += cam.WorldView.X; namePos.Y -= cam.WorldView.Y; - Color nameColor = Color.White; - if (Controlled != null && TeamID != Controlled.TeamID) - { - if (TeamID == CharacterTeamType.FriendlyNPC) - { - nameColor = UniqueNameColor ?? Color.SkyBlue; - } - else - { - nameColor = GUI.Style.Red; - } - } if (CampaignInteractionType != CampaignMode.InteractionType.None && AllowCustomInteract) { var iconStyle = GUI.Style.GetComponentStyle("CampaignInteractionBubble." + CampaignInteractionType); @@ -931,6 +984,89 @@ namespace Barotrauma } } + public Color GetNameColor() + { + CharacterTeamType team = teamID; + if (Info?.IsDisguisedAsAnother != null) + { + var idCard = Inventory.GetItemInLimbSlot(InvSlotType.Card)?.GetComponent(); + if (idCard != null) + { + if (team == CharacterTeamType.Team2 && idCard.TeamID != CharacterTeamType.Team2) + { + team = CharacterTeamType.Team1; + } + else if (team == CharacterTeamType.Team1 && idCard.TeamID == CharacterTeamType.Team2) + { + team = CharacterTeamType.Team2; + } + } + } + + Color nameColor = GUI.Style.TextColor; + if (Controlled != null && team != Controlled.TeamID) + { + if (TeamID == CharacterTeamType.FriendlyNPC) + { + nameColor = UniqueNameColor ?? Color.SkyBlue; + } + else + { + nameColor = GUI.Style.Red; + } + } + return nameColor; + } + + public void AddMessage(string rawText, Color color, bool playSound, string identifier = null, int? value = null) + { + GUIMessage existingMessage = null; + + float delay = 0.0f; + if (guiMessages.Any()) + { + delay = guiMessages.Min(m => m.Timer) - 0.5f; + if (delay < 0) + { + delay = -delay; + if (guiMessages.Count > 5) + { + //reduce delays if there's lots of messages + guiMessages.Where(m => m.Timer < 0.0f).ForEach(m => m.Timer *= 0.9f); + } + } + else + { + delay = 0; + } + } + + if (identifier != null) + { + existingMessage = guiMessages.Find(m => m.Identifier == identifier && m.Timer < m.Lifetime * 0.5f); + } + if (existingMessage == null || !value.HasValue) + { + var newMessage = new GUIMessage(rawText, color, delay, identifier, value); + guiMessages.Insert(0, newMessage); + if (playSound) + { + if (delay > 0.0f) + { + newMessage.PlaySound = true; + } + else + { + SoundPlayer.PlayUISound(GUISoundType.UIMessage); + } + } + } + else + { + existingMessage.Value += value.Value; + } + } + /// /// Creates a progress bar that's "linked" to the specified object (or updates an existing one if there's one already linked to the object) /// The progress bar will automatically fade out after 1 sec if the method hasn't been called during that time @@ -958,12 +1094,13 @@ namespace Barotrauma private readonly List matchingSounds = new List(); private SoundChannel soundChannel; - public void PlaySound(CharacterSound.SoundType soundType, float soundIntervalFactor = 1.0f) + public void PlaySound(CharacterSound.SoundType soundType, float soundIntervalFactor = 1.0f, float maxInterval = 0) { if (sounds == null || sounds.Count == 0) { return; } if (soundChannel != null && soundChannel.IsPlaying) { return; } if (GameMain.SoundManager?.Disabled ?? true) { return; } - if (soundTimer > soundInterval * soundIntervalFactor) { return; } + if (soundTimer > Params.SoundInterval * soundIntervalFactor) { return; } + if (Params.SoundInterval - soundTimer < maxInterval) { return; } matchingSounds.Clear(); foreach (var s in sounds) { @@ -975,7 +1112,7 @@ namespace Barotrauma var selectedSound = matchingSounds.GetRandom(); if (selectedSound?.Sound == null) { return; } soundChannel = SoundPlayer.PlaySound(selectedSound.Sound, AnimController.WorldPosition, selectedSound.Volume, selectedSound.Range, hullGuess: CurrentHull, ignoreMuffling: selectedSound.IgnoreMuffling); - soundTimer = soundInterval; + soundTimer = Params.SoundInterval; } public void AddActiveObjectiveEntity(Entity entity, Sprite sprite, Color? color = null) @@ -1028,5 +1165,20 @@ namespace Barotrauma Rand.Range(50.0f, 500.0f), null); } } + + partial void OnMoneyChanged(int prevAmount, int newAmount) + { + if (newAmount > prevAmount) + { + int increase = newAmount - prevAmount; + AddMessage("+" + TextManager.GetWithVariable("currencyformat", "[credits]", "[value]"), + GUI.Style.Yellow, playSound: this == Controlled, "money", increase); + } + } + + partial void OnTalentGiven(string talentIdentifier) + { + AddMessage(TextManager.Get("talentname." + talentIdentifier.ToString()), GUI.Style.Yellow, playSound: this == Controlled); + } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterHUD.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterHUD.cs index b208ae945..3c8d3d5a0 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterHUD.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterHUD.cs @@ -100,7 +100,7 @@ namespace Barotrauma } } - private static bool shouldRecreateHudTexts = true; + public static bool ShouldRecreateHudTexts { get; set; } = true; private static bool heldDownShiftWhenGotHudTexts; public static bool IsCampaignInterfaceOpen => @@ -150,7 +150,7 @@ namespace Barotrauma } } - if (character.IsHumanoid && character.SelectedCharacter != null) + if (character.Params.CanInteract && character.SelectedCharacter != null) { character.SelectedCharacter.CharacterHealth.AddToGUIUpdateList(); } @@ -195,7 +195,7 @@ namespace Barotrauma } } - if (character.IsHumanoid && character.SelectedCharacter != null && character.SelectedCharacter.Inventory != null) + if (character.Params.CanInteract && character.SelectedCharacter != null && character.SelectedCharacter.Inventory != null) { if (character.SelectedCharacter.CanInventoryBeAccessed) { @@ -219,7 +219,7 @@ namespace Barotrauma if (focusedItemOverlayTimer <= 0.0f) { focusedItem = null; - shouldRecreateHudTexts = true; + ShouldRecreateHudTexts = true; } } } @@ -285,6 +285,21 @@ namespace Barotrauma i.GetRootInventoryOwner() == i); } + if (GameMain.GameSession != null) + { + foreach (var mission in GameMain.GameSession.Missions) + { + if (!mission.DisplayTargetHudIcons) { continue; } + foreach (var target in mission.HudIconTargets) + { + if (target.Submarine != character.Submarine) { 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); + } + } + } + foreach (Character.ObjectiveEntity objectiveEntity in character.ActiveObjectiveEntities) { DrawObjectiveIndicator(spriteBatch, cam, character, objectiveEntity, 1.0f); @@ -317,7 +332,7 @@ namespace Barotrauma if (focusedItem != character.FocusedItem) { focusedItemOverlayTimer = Math.Min(1.0f, focusedItemOverlayTimer); - shouldRecreateHudTexts = true; + ShouldRecreateHudTexts = true; } focusedItem = character.FocusedItem; } @@ -342,13 +357,13 @@ namespace Barotrauma if (!GUI.DisableItemHighlights && !Inventory.DraggingItemToWorld) { bool shiftDown = PlayerInput.IsShiftDown(); - if (shouldRecreateHudTexts || heldDownShiftWhenGotHudTexts != shiftDown) + if (ShouldRecreateHudTexts || heldDownShiftWhenGotHudTexts != shiftDown) { - shouldRecreateHudTexts = true; + ShouldRecreateHudTexts = true; heldDownShiftWhenGotHudTexts = shiftDown; } - var hudTexts = focusedItem.GetHUDTexts(character, shouldRecreateHudTexts); - shouldRecreateHudTexts = false; + var hudTexts = focusedItem.GetHUDTexts(character, ShouldRecreateHudTexts); + ShouldRecreateHudTexts = false; int dir = Math.Sign(focusedItem.WorldPosition.X - character.WorldPosition.X); @@ -391,7 +406,26 @@ namespace Barotrauma if (npc.CampaignInteractionType == CampaignMode.InteractionType.None || npc.Submarine != character.Submarine || npc.IsDead || npc.IsIncapacitated) { continue; } var iconStyle = GUI.Style.GetComponentStyle("CampaignInteractionIcon." + npc.CampaignInteractionType); - GUI.DrawIndicator(spriteBatch, npc.WorldPosition, cam, npc.CurrentHull == character.CurrentHull ? 500.0f : 100.0f, iconStyle.GetDefaultSprite(), iconStyle.Color); + Range visibleRange = new Range(npc.CurrentHull == Character.Controlled.CurrentHull ? 500.0f : 100.0f, float.PositiveInfinity); + if (npc.CampaignInteractionType == CampaignMode.InteractionType.Examine) + { + //TODO: we could probably do better than just hardcoding + //a check for InteractionType.Examine here. + + if (Vector2.DistanceSquared(character.Position, npc.Position) > 500f * 500f) { continue; } + + var body = Submarine.CheckVisibility(character.SimPosition, npc.SimPosition, ignoreLevel: true); + if (body != null && body.UserData as Character != npc) { continue; } + + visibleRange = new Range(-100f, 500f); + } + GUI.DrawIndicator( + spriteBatch, + npc.WorldPosition, + cam, + visibleRange, + iconStyle.GetDefaultSprite(), + iconStyle.Color); } foreach (Item item in Item.ItemList) @@ -400,7 +434,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 Vector2(-100f, 500.0f), item.IconStyle.GetDefaultSprite(), item.IconStyle.Color, createOffset: false); + 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); } } @@ -471,7 +505,7 @@ namespace Barotrauma if (!character.IsIncapacitated && character.Stun <= 0.0f) { - if (character.IsHumanoid && character.SelectedCharacter != null && character.SelectedCharacter.Inventory != null) + if (character.Params.CanInteract && character.SelectedCharacter != null && character.SelectedCharacter.Inventory != null) { if (character.SelectedCharacter.CanInventoryBeAccessed) { @@ -525,12 +559,7 @@ namespace Barotrauma textPos -= new Vector2(textSize.X / 2, textSize.Y); - Color nameColor = GUI.Style.TextColor; - if (character.TeamID != character.FocusedCharacter.TeamID) - { - nameColor = character.FocusedCharacter.TeamID == CharacterTeamType.FriendlyNPC ? Color.SkyBlue : GUI.Style.Red; - } - + Color nameColor = character.FocusedCharacter.GetNameColor(); GUI.DrawString(spriteBatch, textPos, focusName, nameColor, Color.Black * 0.7f, 2, GUI.SubHeadingFont); textPos.X += 10.0f * GUI.Scale; textPos.Y += GUI.SubHeadingFont.MeasureString(focusName).Y; @@ -544,11 +573,14 @@ namespace Barotrauma if (character.FocusedCharacter.CanBeDragged) { - GUI.DrawString(spriteBatch, textPos, GetCachedHudText("GrabHint", GameMain.Config.KeyBindText(InputType.Grab)), + string text = character.CanEat ? "EatHint" : "GrabHint"; + GUI.DrawString(spriteBatch, textPos, GetCachedHudText(text, GameMain.Config.KeyBindText(InputType.Grab)), GUI.Style.Green, Color.Black, 2, GUI.SmallFont); textPos.Y += largeTextSize.Y; } + if (!character.DisableHealthWindow && + character.IsFriendly(character.FocusedCharacter) && character.FocusedCharacter.CharacterHealth.UseHealthWindow && character.CanInteractWith(character.FocusedCharacter, 160f, false)) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterInfo.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterInfo.cs index e88192195..24ca41abe 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterInfo.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterInfo.cs @@ -17,11 +17,19 @@ namespace Barotrauma public bool LastControlled; + #warning TODO: Refactor private Sprite disguisedPortrait; private List disguisedAttachmentSprites; private Vector2? disguisedSheetIndex; private Sprite disguisedJobIcon; private Color disguisedJobColor; + private Color disguisedHairColor; + private Color disguisedFacialHairColor; + private Color disguisedSkinColor; + + private Sprite tintMask; + private float tintHighlightThreshold; + private float tintHighlightMultiplier; public static void Init() { @@ -29,6 +37,20 @@ namespace Barotrauma new Sprite("Content/UI/InventoryUIAtlas.png", new Rectangle(833, 298, 142, 98), null, 0); } + partial void LoadHeadSpriteProjectSpecific(XElement limbElement) + { + XElement maskElement = limbElement.Element("tintmask"); + if (maskElement != null) + { + string tintMaskPath = maskElement.GetAttributeString("texture", ""); + if (!string.IsNullOrWhiteSpace(tintMaskPath)) + { + tintMask = new Sprite(maskElement, file: Limb.GetSpritePath(tintMaskPath, this)); + tintHighlightThreshold = maskElement.GetAttributeFloat("highlightthreshold", 0.6f); + tintHighlightMultiplier = maskElement.GetAttributeFloat("highlightmultiplier", 0.8f); + } + } + } public GUIComponent CreateInfoFrame(GUIFrame frame, bool returnParent, Sprite permissionIcon = null) { @@ -143,10 +165,10 @@ namespace Barotrauma private void DrawInfoFrameCharacterIcon(SpriteBatch sb, Rectangle componentRect) { - if (headSprite == null) { return; } + if (_headSprite == null) { return; } Vector2 targetAreaSize = componentRect.Size.ToVector2(); - float scale = Math.Min(targetAreaSize.X / headSprite.size.X, targetAreaSize.Y / headSprite.size.Y); - DrawIcon(sb, componentRect.Location.ToVector2() + headSprite.size / 2 * scale, targetAreaSize); + float scale = Math.Min(targetAreaSize.X / _headSprite.size.X, targetAreaSize.Y / _headSprite.size.Y); + DrawIcon(sb, componentRect.Location.ToVector2() + _headSprite.size / 2 * scale, targetAreaSize); } public GUIFrame CreateCharacterFrame(GUIComponent parent, string text, object userData) @@ -165,21 +187,36 @@ namespace Barotrauma return frame; } - partial void OnSkillChanged(string skillIdentifier, float prevLevel, float newLevel, Vector2 textPopupPos) + partial void OnSkillChanged(string skillIdentifier, float prevLevel, float newLevel) { if (TeamID == CharacterTeamType.FriendlyNPC) { return; } if (Character.Controlled != null && Character.Controlled.TeamID != TeamID) { return; } + // if we increased by more than 1 in one increase, then display special color (for talents) + bool specialIncrease = Math.Abs(newLevel - prevLevel) >= 1.0f; + if ((int)newLevel > (int)prevLevel) { int increase = Math.Max((int)newLevel - (int)prevLevel, 1); - GUI.AddMessage( - string.Format("+{0} {1}", increase, TextManager.Get("SkillName." + skillIdentifier)), - GUI.Style.Green, - textPopupPos, - Vector2.UnitY * 10.0f, - playSound: false, - subId: Character?.Submarine?.ID ?? -1); + Character?.AddMessage( + "+[value] "+ TextManager.Get("SkillName." + skillIdentifier), + specialIncrease ? GUI.Style.Orange : GUI.Style.Green, + playSound: Character == Character.Controlled, skillIdentifier, increase); + } + } + + partial void OnExperienceChanged(int prevAmount, int newAmount) + { + if (Character.Controlled != null && Character.Controlled.TeamID != TeamID) { return; } + + GameSession.TabMenuInstance?.OnExperienceChanged(Character); + + if (newAmount > prevAmount) + { + int increase = newAmount - prevAmount; + Character?.AddMessage( + "+[value] " + TextManager.Get("experienceshort"), + GUI.Style.Blue, playSound: Character == Character.Controlled, "exp", increase); } } @@ -187,193 +224,36 @@ namespace Barotrauma { if (idCard.Item.Tags == string.Empty) return; - if (idCard.StoredJobPrefab == null || idCard.StoredPortrait == null) + if (idCard.StoredOwnerAppearance.JobPrefab == null || idCard.StoredOwnerAppearance.Portrait == null) { string[] readTags = idCard.Item.Tags.Split(','); - if (readTags.Length == 0) return; + if (readTags.Length == 0) { return; } - if (idCard.StoredJobPrefab == null) + if (idCard.StoredOwnerAppearance.JobPrefab == null) { - string jobIdTag = readTags.FirstOrDefault(s => s.StartsWith("jobid:")); - - if (jobIdTag != null && jobIdTag.Length > 6) - { - string jobId = jobIdTag.Substring(6); - if (jobId != string.Empty) - { - idCard.StoredJobPrefab = JobPrefab.Get(jobId); - } - } + idCard.StoredOwnerAppearance.ExtractJobPrefab(readTags); } - if (idCard.StoredPortrait == null) + if (idCard.StoredOwnerAppearance.Portrait == null) { - string disguisedGender = string.Empty; - string disguisedRace = string.Empty; - string disguisedHeadSpriteId = string.Empty; - int disguisedHairIndex = -1; - int disguisedBeardIndex = -1; - int disguisedMoustacheIndex = -1; - int disguisedFaceAttachmentIndex = -1; - - foreach (string tag in readTags) - { - string[] s = tag.Split(':'); - - switch (s[0]) - { - case "gender": - disguisedGender = s[1]; - break; - - case "race": - disguisedRace = s[1]; - break; - - case "headspriteid": - disguisedHeadSpriteId = s[1]; - break; - - case "hairindex": - disguisedHairIndex = int.Parse(s[1]); - break; - - case "beardindex": - disguisedBeardIndex = int.Parse(s[1]); - break; - - case "moustacheindex": - disguisedMoustacheIndex = int.Parse(s[1]); - break; - - case "faceattachmentindex": - disguisedFaceAttachmentIndex = int.Parse(s[1]); - break; - - case "sheetindex": - string[] vectorValues = s[1].Split(";"); - idCard.StoredSheetIndex = new Vector2(float.Parse(vectorValues[0]), float.Parse(vectorValues[1])); - break; - } - } - - if (disguisedGender == string.Empty || disguisedRace == string.Empty || disguisedHeadSpriteId == string.Empty) - { - idCard.StoredPortrait = null; - idCard.StoredAttachments = null; - return; - } - - foreach (XElement limbElement in Ragdoll.MainElement.Elements()) - { - if (!limbElement.GetAttributeString("type", "").Equals("head", StringComparison.OrdinalIgnoreCase)) { continue; } - - XElement spriteElement = limbElement.Element("sprite"); - if (spriteElement == null) { continue; } - - string spritePath = spriteElement.Attribute("texture").Value; - - spritePath = spritePath.Replace("[GENDER]", disguisedGender); - spritePath = spritePath.Replace("[RACE]", disguisedRace.ToLowerInvariant()); - spritePath = spritePath.Replace("[HEADID]", disguisedHeadSpriteId); - - string fileName = Path.GetFileNameWithoutExtension(spritePath); - - //go through the files in the directory to find a matching sprite - foreach (string file in Directory.GetFiles(Path.GetDirectoryName(spritePath))) - { - if (!file.EndsWith(".png", StringComparison.OrdinalIgnoreCase)) - { - continue; - } - string fileWithoutTags = Path.GetFileNameWithoutExtension(file); - fileWithoutTags = fileWithoutTags.Split('[', ']').First(); - if (fileWithoutTags != fileName) { continue; } - idCard.StoredPortrait = new Sprite(spriteElement, "", file) { RelativeOrigin = Vector2.Zero }; - break; - } - - break; - } - - if (Wearables != null) - { - XElement disguisedHairElement, disguisedBeardElement, disguisedMoustacheElement, disguisedFaceAttachmentElement; - List disguisedHairs, disguisedBeards, disguisedMoustaches, disguisedFaceAttachments; - - Gender disguisedGenderEnum = disguisedGender == "female" ? Gender.Female : Gender.Male; - Race disguisedRaceEnum = (Race)Enum.Parse(typeof(Race), disguisedRace); - int headSpriteId = int.Parse(disguisedHeadSpriteId); - - float commonness = disguisedGenderEnum == Gender.Female ? 0.05f : 0.2f; - disguisedHairs = AddEmpty(FilterByTypeAndHeadID(FilterElementsByGenderAndRace(wearables, disguisedGenderEnum, disguisedRaceEnum), WearableType.Hair, headSpriteId), WearableType.Hair, commonness); - disguisedBeards = AddEmpty(FilterByTypeAndHeadID(FilterElementsByGenderAndRace(wearables, disguisedGenderEnum, disguisedRaceEnum), WearableType.Beard, headSpriteId), WearableType.Beard); - disguisedMoustaches = AddEmpty(FilterByTypeAndHeadID(FilterElementsByGenderAndRace(wearables, disguisedGenderEnum, disguisedRaceEnum), WearableType.Moustache, headSpriteId), WearableType.Moustache); - disguisedFaceAttachments = AddEmpty(FilterByTypeAndHeadID(FilterElementsByGenderAndRace(wearables, disguisedGenderEnum, disguisedRaceEnum), WearableType.FaceAttachment, headSpriteId), WearableType.FaceAttachment); - - if (IsValidIndex(disguisedHairIndex, disguisedHairs)) - { - disguisedHairElement = disguisedHairs[disguisedHairIndex]; - } - else - { - disguisedHairElement = GetRandomElement(disguisedHairs); - } - if (IsValidIndex(disguisedBeardIndex, disguisedBeards)) - { - disguisedBeardElement = disguisedBeards[disguisedBeardIndex]; - } - else - { - disguisedBeardElement = GetRandomElement(disguisedBeards); - } - - if (IsValidIndex(disguisedMoustacheIndex, disguisedMoustaches)) - { - disguisedMoustacheElement = disguisedMoustaches[disguisedMoustacheIndex]; - } - else - { - disguisedMoustacheElement = GetRandomElement(disguisedMoustaches); - } - if (IsValidIndex(disguisedFaceAttachmentIndex, disguisedFaceAttachments)) - { - disguisedFaceAttachmentElement = disguisedFaceAttachments[disguisedFaceAttachmentIndex]; - } - else - { - disguisedFaceAttachmentElement = GetRandomElement(disguisedFaceAttachments); - } - - idCard.StoredAttachments = new List(); - - disguisedFaceAttachmentElement?.Elements("sprite").ForEach(s => idCard.StoredAttachments.Add(new WearableSprite(s, WearableType.FaceAttachment))); - disguisedBeardElement?.Elements("sprite").ForEach(s => idCard.StoredAttachments.Add(new WearableSprite(s, WearableType.Beard))); - disguisedMoustacheElement?.Elements("sprite").ForEach(s => idCard.StoredAttachments.Add(new WearableSprite(s, WearableType.Moustache))); - disguisedHairElement?.Elements("sprite").ForEach(s => idCard.StoredAttachments.Add(new WearableSprite(s, WearableType.Hair))); - - if (OmitJobInPortraitClothing) - { - JobPrefab.NoJobElement?.Element("PortraitClothing")?.Elements("sprite").ForEach(s => idCard.StoredAttachments.Add(new WearableSprite(s, WearableType.JobIndicator))); - } - else - { - idCard.StoredJobPrefab?.ClothingElement?.Elements("sprite").ForEach(s => idCard.StoredAttachments.Add(new WearableSprite(s, WearableType.JobIndicator))); - } - } + idCard.StoredOwnerAppearance.ExtractAppearance(this, readTags); } } - if (idCard.StoredJobPrefab != null) + if (idCard.StoredOwnerAppearance.JobPrefab != null) { - disguisedJobIcon = idCard.StoredJobPrefab.Icon; - disguisedJobColor = idCard.StoredJobPrefab.UIColor; + disguisedJobIcon = idCard.StoredOwnerAppearance.JobPrefab.Icon; + disguisedJobColor = idCard.StoredOwnerAppearance.JobPrefab.UIColor; } - disguisedPortrait = idCard.StoredPortrait; - disguisedSheetIndex = idCard.StoredSheetIndex; - disguisedAttachmentSprites = idCard.StoredAttachments; + disguisedPortrait = idCard.StoredOwnerAppearance.Portrait; + disguisedSheetIndex = idCard.StoredOwnerAppearance.SheetIndex; + disguisedAttachmentSprites = idCard.StoredOwnerAppearance.Attachments; + + disguisedHairColor = idCard.StoredOwnerAppearance.HairColor; + disguisedFacialHairColor = idCard.StoredOwnerAppearance.FacialHairColor; + disguisedSkinColor = idCard.StoredOwnerAppearance.SkinColor; } partial void LoadAttachmentSprites(bool omitJob) @@ -422,43 +302,107 @@ namespace Barotrauma public void DrawPortrait(SpriteBatch spriteBatch, Vector2 screenPos, Vector2 offset, float targetWidth, bool flip = false, bool evaluateDisguise = false) { - if (evaluateDisguise && IsDisguised) return; + if (evaluateDisguise && IsDisguised) { return; } Vector2? sheetIndex; Sprite portraitToDraw; List attachmentsToDraw; + Color hairColor; + Color facialHairColor; + Color skinColor; + if (!IsDisguisedAsAnother || !evaluateDisguise) { sheetIndex = Head.SheetIndex; portraitToDraw = Portrait; attachmentsToDraw = AttachmentSprites; + + hairColor = Head.HairColor; + facialHairColor = Head.FacialHairColor; + skinColor = Head.SkinColor; } else { sheetIndex = disguisedSheetIndex; portraitToDraw = disguisedPortrait; attachmentsToDraw = disguisedAttachmentSprites; + + hairColor = disguisedHairColor; + facialHairColor = disguisedFacialHairColor; + skinColor = disguisedSkinColor; } if (portraitToDraw != null) { + var currEffect = spriteBatch.GetCurrentEffect(); // Scale down the head sprite 10% float scale = targetWidth * 0.9f / Portrait.size.X; if (sheetIndex.HasValue) { + SetHeadEffect(spriteBatch); portraitToDraw.SourceRect = new Rectangle(CalculateOffset(portraitToDraw, sheetIndex.Value.ToPoint()), portraitToDraw.SourceRect.Size); } - portraitToDraw.Draw(spriteBatch, screenPos + offset, Color.White, portraitToDraw.Origin, scale: scale, spriteEffect: flip ? SpriteEffects.FlipHorizontally : SpriteEffects.None); + portraitToDraw.Draw(spriteBatch, screenPos + offset, skinColor, portraitToDraw.Origin, scale: scale, spriteEffect: flip ? SpriteEffects.FlipHorizontally : SpriteEffects.None); if (attachmentsToDraw != null) { float depthStep = 0.000001f; foreach (var attachment in attachmentsToDraw) { - DrawAttachmentSprite(spriteBatch, attachment, portraitToDraw, sheetIndex, screenPos + offset, scale, depthStep, flip ? SpriteEffects.FlipHorizontally : SpriteEffects.None); + SetAttachmentEffect(spriteBatch, attachment); + DrawAttachmentSprite(spriteBatch, attachment, portraitToDraw, sheetIndex, screenPos + offset, scale, depthStep, GetAttachmentColor(attachment, hairColor, facialHairColor), flip ? SpriteEffects.FlipHorizontally : SpriteEffects.None); depthStep += depthStep; } } + spriteBatch.SwapEffect(currEffect); + } + } + + //TODO: I hate this so much :( + private SpriteBatch.EffectWithParams headEffectParameters; + private Dictionary attachmentEffectParameters + = new Dictionary(); + + private void SetHeadEffect(SpriteBatch spriteBatch) + { + headEffectParameters.Effect ??= GameMain.GameScreen.ThresholdTintEffect; + headEffectParameters.Params ??= new Dictionary(); + headEffectParameters.Params["xBaseTexture"] = HeadSprite.Texture; + headEffectParameters.Params["xTintMaskTexture"] = tintMask?.Texture ?? GUI.WhiteTexture; + headEffectParameters.Params["xCutoffTexture"] = GUI.WhiteTexture; + headEffectParameters.Params["baseToCutoffSizeRatio"] = 1.0f; + headEffectParameters.Params["highlightThreshold"] = tintHighlightThreshold; + headEffectParameters.Params["highlightMultiplier"] = tintHighlightMultiplier; + spriteBatch.SwapEffect(headEffectParameters); + } + + private void SetAttachmentEffect(SpriteBatch spriteBatch, WearableSprite attachment) + { + if (!attachmentEffectParameters.ContainsKey(attachment.Type)) + { + attachmentEffectParameters.Add(attachment.Type, new SpriteBatch.EffectWithParams(GameMain.GameScreen.ThresholdTintEffect, new Dictionary())); + } + var parameters = attachmentEffectParameters[attachment.Type].Params; + parameters["xBaseTexture"] = attachment.Sprite.Texture; + parameters["xTintMaskTexture"] = GUI.WhiteTexture; + parameters["xCutoffTexture"] = GUI.WhiteTexture; + parameters["baseToCutoffSizeRatio"] = 1.0f; + parameters["highlightThreshold"] = tintHighlightThreshold; + parameters["highlightMultiplier"] = tintHighlightMultiplier; + spriteBatch.SwapEffect(attachmentEffectParameters[attachment.Type]); + } + + private Color GetAttachmentColor(WearableSprite attachment, Color hairColor, Color facialHairColor) + { + switch (attachment.Type) + { + case WearableType.Hair: + return hairColor; + case WearableType.Beard: + case WearableType.Moustache: + return facialHairColor; + default: + return Color.White; } } @@ -467,34 +411,28 @@ namespace Barotrauma var headSprite = HeadSprite; if (headSprite != null) { + var currEffect = spriteBatch.GetCurrentEffect(); float scale = Math.Min(targetAreaSize.X / headSprite.size.X, targetAreaSize.Y / headSprite.size.Y); if (Head.SheetIndex.HasValue) { headSprite.SourceRect = new Rectangle(CalculateOffset(headSprite, Head.SheetIndex.Value.ToPoint()), headSprite.SourceRect.Size); } - headSprite.Draw(spriteBatch, screenPos, scale: scale); + SetHeadEffect(spriteBatch); + headSprite.Draw(spriteBatch, screenPos, scale: scale, color: SkinColor); if (AttachmentSprites != null) { float depthStep = 0.000001f; foreach (var attachment in AttachmentSprites) { - DrawAttachmentSprite(spriteBatch, attachment, headSprite, Head.SheetIndex, screenPos, scale, depthStep); + SetAttachmentEffect(spriteBatch, attachment); + DrawAttachmentSprite(spriteBatch, attachment, headSprite, Head.SheetIndex, screenPos, scale, depthStep, GetAttachmentColor(attachment, HairColor, FacialHairColor)); depthStep += depthStep; } } + spriteBatch.SwapEffect(currEffect); } } - public void DrawJobIcon(SpriteBatch spriteBatch, Vector2 pos, float scale = 1.0f, bool evaluateDisguise = false) - { - if (evaluateDisguise && IsDisguised) return; - var icon = !IsDisguisedAsAnother || !evaluateDisguise ? Job?.Prefab?.Icon : disguisedJobIcon; - if (icon == null) { return; } - Color iconColor = !IsDisguisedAsAnother || !evaluateDisguise ? Job.Prefab.UIColor : disguisedJobColor; - - icon.Draw(spriteBatch, pos, iconColor, scale: scale); - } - public void DrawJobIcon(SpriteBatch spriteBatch, Rectangle area, bool evaluateDisguise = false) { if (evaluateDisguise && IsDisguised) return; @@ -505,7 +443,7 @@ namespace Barotrauma icon.Draw(spriteBatch, area.Center.ToVector2(), iconColor, scale: Math.Min(area.Width / (float)icon.SourceRect.Width, area.Height / (float)icon.SourceRect.Height)); } - private void DrawAttachmentSprite(SpriteBatch spriteBatch, WearableSprite attachment, Sprite head, Vector2? sheetIndex, Vector2 drawPos, float scale, float depthStep, SpriteEffects spriteEffects = SpriteEffects.None) + private void DrawAttachmentSprite(SpriteBatch spriteBatch, WearableSprite attachment, Sprite head, Vector2? sheetIndex, Vector2 drawPos, float scale, float depthStep, Color? color = null, SpriteEffects spriteEffects = SpriteEffects.None) { if (attachment.InheritSourceRect) { @@ -522,7 +460,7 @@ namespace Barotrauma attachment.Sprite.SourceRect = head.SourceRect; } } - Vector2 origin = attachment.Sprite.Origin; + Vector2 origin; if (attachment.InheritOrigin) { origin = head.Origin; @@ -537,7 +475,7 @@ namespace Barotrauma { depth = head.Depth - depthStep; } - attachment.Sprite.Draw(spriteBatch, drawPos, Color.White, origin, rotate: 0, scale: scale, depth: depth, spriteEffect: spriteEffects); + attachment.Sprite.Draw(spriteBatch, drawPos, color ?? Color.White, origin, rotate: 0, scale: scale, depth: depth, spriteEffect: spriteEffects); } public static CharacterInfo ClientRead(string speciesName, IReadMessage inc) @@ -552,6 +490,9 @@ namespace Barotrauma int beardIndex = inc.ReadByte(); int moustacheIndex = inc.ReadByte(); int faceAttachmentIndex = inc.ReadByte(); + Color skinColor = inc.ReadColorR8G8B8(); + Color hairColor = inc.ReadColorR8G8B8(); + Color facialHairColor = inc.ReadColorR8G8B8(); string ragdollFile = inc.ReadString(); string jobIdentifier = inc.ReadString(); @@ -577,6 +518,9 @@ namespace Barotrauma ID = infoID, }; ch.RecreateHead(headSpriteID,(Race)race, (Gender)gender, hairIndex, beardIndex, moustacheIndex, faceAttachmentIndex); + ch.SkinColor = skinColor; + ch.HairColor = hairColor; + ch.FacialHairColor = facialHairColor; if (ch.Job != null) { foreach (KeyValuePair skill in skillLevels) @@ -591,7 +535,449 @@ namespace Barotrauma } ch.Job.Skills.RemoveAll(s => !skillLevels.ContainsKey(s.Identifier)); } + + byte savedStatValueCount = inc.ReadByte(); + for (int i = 0; i < savedStatValueCount; i++) + { + int statType = inc.ReadByte(); + string statIdentifier = inc.ReadString(); + float statValue = inc.ReadSingle(); + bool removeOnDeath = inc.ReadBoolean(); + ch.ChangeSavedStatValue((StatTypes)statType, statValue, statIdentifier, removeOnDeath); + } + ch.ExperiencePoints = inc.ReadUInt16(); + ch.AdditionalTalentPoints = inc.ReadUInt16(); return ch; } + + public void CreateIcon(RectTransform rectT) + { + LoadHeadAttachments(); + new GUICustomComponent(rectT, + onDraw: (sb, component) => DrawIcon(sb, component.Rect.Center.ToVector2(), targetAreaSize: component.Rect.Size.ToVector2())); + } + + public class AppearanceCustomizationMenu : IDisposable + { + public readonly CharacterInfo CharacterInfo; + public GUIListBox HeadSelectionList = null; + public bool HasIcon = true; + + public GUIScrollBar.OnMovedHandler OnSliderMoved = null; + public GUIScrollBar.OnMovedHandler OnSliderReleased = null; + public Action OnHeadSwitch = null; + + private readonly GUIComponent parentComponent; + private readonly List characterSprites = new List(); + public GUIButton RandomizeButton; + + public AppearanceCustomizationMenu(CharacterInfo info, GUIComponent parent, bool hasIcon = true) + { + CharacterInfo = info; + parentComponent = parent; + HasIcon = hasIcon; + + RecreateFrameContents(); + } + + public void RecreateFrameContents() + { + var info = CharacterInfo; + + HeadSelectionList = null; + parentComponent.ClearChildren(); + ClearSprites(); + + float contentWidth = HasIcon ? 0.75f : 1.0f; + var listBox = new GUIListBox( + new RectTransform(new Vector2(contentWidth, 1.0f), parentComponent.RectTransform, + Anchor.CenterLeft)) + { CanBeFocused = false, CanTakeKeyBoardFocus = false }; + var content = listBox.Content; + + info.LoadHeadAttachments(); + if (HasIcon) + { + info.CreateIcon( + new RectTransform(new Vector2(0.25f, 1.0f), parentComponent.RectTransform, Anchor.CenterRight) + { RelativeOffset = new Vector2(-0.01f, 0.0f) }); + } + + RectTransform createItemRectTransform(string labelTag, float width = 0.6f) + { + var layoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.166f), content.RectTransform)); + + var label = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), layoutGroup.RectTransform), + TextManager.Get(labelTag), font: GUI.SubHeadingFont); + + var bottomItem = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.5f), layoutGroup.RectTransform), + style: null); + + return new RectTransform(new Vector2(width, 1.0f), bottomItem.RectTransform, Anchor.Center); + } + + RectTransform genderItemRT = createItemRectTransform("Gender", 1.0f); + + GUILayoutGroup genderContainer = + new GUILayoutGroup(genderItemRT, isHorizontal: true) + { + Stretch = true, + RelativeSpacing = 0.05f + }; + + void createGenderButton(Gender gender) + { + new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), genderContainer.RectTransform), + TextManager.Get(gender.ToString()), style: "ListBoxElement") + { + UserData = gender, + OnClicked = OpenHeadSelection, + Selected = info.Gender == gender + }; + } + + createGenderButton(Gender.Male); + createGenderButton(Gender.Female); + + int countAttachmentsOfType(WearableType wearableType) + => info.FilterByTypeAndHeadID( + info.FilterElementsByGenderAndRace(info.Wearables, info.Head.gender, info.Head.race), + wearableType, info.HeadSpriteId).Count(); + + List attachmentSliders = new List(); + void createAttachmentSlider(int initialValue, WearableType wearableType) + { + int attachmentCount = countAttachmentsOfType(wearableType); + if (attachmentCount > 0) + { + var labelTag = wearableType == WearableType.FaceAttachment + ? "FaceAttachment.Accessories" + : $"FaceAttachment.{wearableType}"; + var sliderItemRT = createItemRectTransform(labelTag); + var slider = + new GUIScrollBar(sliderItemRT, style: "GUISlider") + { + Range = new Vector2(0, attachmentCount), + StepValue = 1, + OnMoved = (bar, scroll) => SwitchAttachment(bar, wearableType), + OnReleased = OnSliderReleased, + BarSize = 1.0f / (float)(attachmentCount + 1) + }; + slider.BarScrollValue = initialValue; + attachmentSliders.Add(slider); + } + } + + createAttachmentSlider(info.HairIndex, WearableType.Hair); + createAttachmentSlider(info.BeardIndex, WearableType.Beard); + createAttachmentSlider(info.MoustacheIndex, WearableType.Moustache); + createAttachmentSlider(info.FaceAttachmentIndex, WearableType.FaceAttachment); + + void createColorSelector(string labelTag, IEnumerable<(Color Color, float Commonness)> options, Func getter, + Action setter) + { + var selectorItemRT = createItemRectTransform(labelTag, 0.4f); + var dropdown = + new GUIDropDown(selectorItemRT) + { AllowNonText = true }; + + var listBoxSize = dropdown.ListBox.RectTransform.RelativeSize; + dropdown.ListBox.RectTransform.RelativeSize = new Vector2(listBoxSize.X * 1.75f, listBoxSize.Y); + var dropdownButton = dropdown.GetChild(); + var buttonFrame = + new GUIFrame( + new RectTransform(Vector2.One * 0.7f, dropdownButton.RectTransform, Anchor.CenterLeft) + { RelativeOffset = new Vector2(0.05f, 0.0f) }, style: null); + Color? previewingColor = null; + dropdown.OnSelected = (component, color) => + { + previewingColor = null; + setter((Color)color); + buttonFrame.Color = getter(); + buttonFrame.HoverColor = getter(); + return true; + }; + buttonFrame.Color = getter(); + buttonFrame.HoverColor = getter(); + + dropdown.ListBox.UseGridLayout = true; + foreach (var option in options) + { + var optionElement = + new GUIFrame( + new RectTransform(new Vector2(0.25f, 1.0f / 3.0f), + dropdown.ListBox.Content.RectTransform), + style: "ListBoxElement") + { + UserData = option.Color, + CanBeFocused = true + }; + var colorElement = + new GUIFrame( + new RectTransform(Vector2.One * 0.75f, optionElement.RectTransform, Anchor.Center, + scaleBasis: ScaleBasis.Smallest), + style: null) + { + Color = option.Color, + HoverColor = option.Color, + OutlineColor = Color.Lerp(Color.Black, option.Color, 0.5f), + CanBeFocused = false + }; + } + + var childToSelect = dropdown.ListBox.Content.FindChild(c => (Color)c.UserData == getter()); + dropdown.Select(dropdown.ListBox.Content.GetChildIndex(childToSelect)); + + //The following exists to track mouseover to preview colors before selecting them + new GUICustomComponent(new RectTransform(Vector2.One, buttonFrame.RectTransform), + onUpdate: (deltaTime, component) => + { + if (GUI.MouseOn is GUIFrame { Parent: { } p } hoveredFrame && dropdown.ListBox.Content.IsParentOf(hoveredFrame)) + { + previewingColor ??= getter(); + Color color = (Color)(dropdown.ListBox.Content.FindChild(c => + c == hoveredFrame || c.IsParentOf(hoveredFrame))?.UserData ?? dropdown.SelectedData ?? getter()); + setter(color); + buttonFrame.Color = getter(); + buttonFrame.HoverColor = getter(); + } + else if (previewingColor.HasValue) + { + setter(previewingColor.Value); + buttonFrame.Color = getter(); + buttonFrame.HoverColor = getter(); + previewingColor = null; + } + }, onDraw: null) + { + CanBeFocused = false, + Visible = true + }; + } + + if (countAttachmentsOfType(WearableType.Hair) > 0) + { + createColorSelector($"Customization.{nameof(info.HairColor)}", info.HairColors, + () => info.HairColor, (color) => info.HairColor = color); + } + + if (countAttachmentsOfType(WearableType.Moustache) > 0 || + countAttachmentsOfType(WearableType.Beard) > 0) + { + createColorSelector($"Customization.{nameof(info.FacialHairColor)}", info.FacialHairColors, + () => info.FacialHairColor, (color) => info.FacialHairColor = color); + } + + createColorSelector($"Customization.{nameof(info.SkinColor)}", info.SkinColors, () => info.SkinColor, + (color) => info.SkinColor = color); + + RandomizeButton = new GUIButton(new RectTransform(Vector2.One * 0.12f, + parentComponent.RectTransform, + anchor: Anchor.BottomRight, scaleBasis: ScaleBasis.Smallest) + { RelativeOffset = new Vector2(0.01f, 0.005f) }, style: "RandomizeButton") + { + OnClicked = (button, o) => + { + info.Head = new HeadInfo(); + info.SetGenderAndRace(Rand.RandSync.Unsynced); + info.SetColors(); + + RecreateFrameContents(); + info.RefreshHead(); + OnHeadSwitch?.Invoke(this); + attachmentSliders.ForEach(s => OnSliderMoved?.Invoke(s, s.BarScroll)); + + return false; + } + }; + //force update twice because the listbox is insanely janky + //TODO: fix all of the UI :) + listBox.ForceUpdate(); + listBox.ForceUpdate(); + foreach (var childLayoutGroup in listBox.Content.GetAllChildren()) + { + childLayoutGroup.Recalculate(); + } + } + + private bool OpenHeadSelection(GUIButton button, object userData) + { + Gender selectedGender = (Gender)userData; + + var info = CharacterInfo; + + float characterHeightWidthRatio = info.HeadSprite.size.Y / info.HeadSprite.size.X; + HeadSelectionList ??= new GUIListBox( + new RectTransform( + new Point(parentComponent.Rect.Width, + (int)(parentComponent.Rect.Width * characterHeightWidthRatio * 0.6f)), GUI.Canvas) + { + AbsoluteOffset = new Point(parentComponent.Rect.Right - parentComponent.Rect.Width, + button.Rect.Bottom) + }); + HeadSelectionList.Visible = true; + HeadSelectionList.Content.ClearChildren(); + ClearSprites(); + + parentComponent.RectTransform.SizeChanged += () => + { + if (parentComponent == null || HeadSelectionList?.RectTransform == null || button == null) + { + return; + } + + HeadSelectionList.RectTransform.Resize(new Point(parentComponent.Rect.Width, + (int)(parentComponent.Rect.Width * characterHeightWidthRatio * 0.6f))); + HeadSelectionList.RectTransform.AbsoluteOffset = + new Point(parentComponent.Rect.Right - parentComponent.Rect.Width, button.Rect.Bottom); + }; + + new GUIFrame( + new RectTransform(new Vector2(1.25f, 1.25f), HeadSelectionList.RectTransform, Anchor.Center), + style: "OuterGlow", color: Color.Black) + { + UserData = "outerglow", + CanBeFocused = false + }; + + GUILayoutGroup row = null; + int itemsInRow = 0; + + XElement headElement = info.Ragdoll.MainElement.Elements().FirstOrDefault(e => + e.GetAttributeString("type", "").Equals("head", StringComparison.OrdinalIgnoreCase)); + XElement headSpriteElement = headElement.Element("sprite"); + string spritePathWithTags = headSpriteElement.Attribute("texture").Value; + + var characterConfigElement = info.CharacterConfigElement; + + var heads = info.Heads; + if (heads != null) + { + row = null; + itemsInRow = 0; + foreach (var kvp in heads.Where(kv => kv.Key.Gender == selectedGender)) + { + var headPreset = kvp.Key; + Race race = headPreset.Race; + int headIndex = headPreset.ID; + + string spritePath = spritePathWithTags + .Replace("[GENDER]", selectedGender.ToString().ToLowerInvariant()) + .Replace("[RACE]", race.ToString().ToLowerInvariant()); + + if (!File.Exists(spritePath)) + { + continue; + } + + Sprite headSprite = new Sprite(headSpriteElement, "", spritePath); + headSprite.SourceRect = + new Rectangle(CalculateOffset(headSprite, kvp.Value.ToPoint()), + headSprite.SourceRect.Size); + characterSprites.Add(headSprite); + + if (itemsInRow >= 4 || row == null) + { + row = new GUILayoutGroup( + new RectTransform(new Vector2(1.0f, 0.333f), HeadSelectionList.Content.RectTransform), + true) + { + UserData = selectedGender, + Visible = true + }; + itemsInRow = 0; + } + + var btn = new GUIButton(new RectTransform(new Vector2(0.25f, 1.0f), row.RectTransform), + style: "ListBoxElementSquare") + { + OutlineColor = Color.White * 0.5f, + PressedColor = Color.White * 0.5f, + UserData = new Tuple(selectedGender, race, headIndex), + OnClicked = SwitchHead, + Selected = selectedGender == info.Gender && race == info.Race && headIndex == info.HeadSpriteId, + Visible = true + }; + + new GUIImage(new RectTransform(Vector2.One, btn.RectTransform), headSprite, scaleToFit: true); + itemsInRow++; + } + } + + return false; + } + + private bool SwitchHead(GUIButton button, object obj) + { + var info = CharacterInfo; + Gender gender = ((Tuple)obj).Item1; + Race race = ((Tuple)obj).Item2; + int id = ((Tuple)obj).Item3; + info.Gender = gender; + info.Race = race; + info.Head.HeadSpriteId = id; + RecreateFrameContents(); + OnHeadSwitch?.Invoke(this); + return true; + } + + private bool SwitchAttachment(GUIScrollBar scrollBar, WearableType type) + { + var info = CharacterInfo; + int index = (int)scrollBar.BarScrollValue; + switch (type) + { + case WearableType.Beard: + info.BeardIndex = index; + break; + case WearableType.FaceAttachment: + info.FaceAttachmentIndex = index; + break; + case WearableType.Hair: + info.HairIndex = index; + break; + case WearableType.Moustache: + info.MoustacheIndex = index; + break; + default: + DebugConsole.ThrowError($"Wearable type not implemented: {type}"); + return false; + } + + info.RefreshHead(); + OnSliderMoved?.Invoke(scrollBar, scrollBar.BarScroll); + return true; + } + + public void Update() + { + if (HeadSelectionList != null && PlayerInput.PrimaryMouseButtonDown() && + !GUI.IsMouseOn(HeadSelectionList)) + { + HeadSelectionList.Visible = false; + } + } + + public void AddToGUIUpdateList() + { + HeadSelectionList?.AddToGUIUpdateList(); + } + + private void ClearSprites() + { + foreach (Sprite sprite in characterSprites) { sprite.Remove(); } + characterSprites.Clear(); + } + + public void Dispose() + { + ClearSprites(); + } + + ~AppearanceCustomizationMenu() + { + Dispose(); + } + } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterNetworking.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterNetworking.cs index 0798a00d3..5897105f1 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterNetworking.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterNetworking.cs @@ -119,15 +119,23 @@ namespace Barotrauma switch ((NetEntityEvent.Type)extraData[0]) { case NetEntityEvent.Type.InventoryState: - msg.WriteRangedInteger(0, 0, 3); + msg.WriteRangedInteger(0, 0, 4); Inventory.ClientWrite(msg, extraData); break; case NetEntityEvent.Type.Treatment: - msg.WriteRangedInteger(1, 0, 3); + msg.WriteRangedInteger(1, 0, 4); msg.Write(AnimController.Anim == AnimController.Animation.CPR); break; case NetEntityEvent.Type.Status: - msg.WriteRangedInteger(2, 0, 3); + msg.WriteRangedInteger(2, 0, 4); + break; + case NetEntityEvent.Type.UpdateTalents: + msg.WriteRangedInteger(3, 0, 4); + msg.Write((ushort)characterTalents.Count); + foreach (var unlockedTalent in characterTalents) + { + msg.Write(unlockedTalent.Prefab.UIntIdentifier); + } break; } } @@ -258,7 +266,7 @@ namespace Barotrauma if (readStatus) { ReadStatus(msg); - (AIController as EnemyAIController)?.PetBehavior?.ClientRead(msg); + AIController?.ClientRead(msg); } msg.ReadPadBits(); @@ -291,7 +299,7 @@ namespace Barotrauma break; case ServerNetObject.ENTITY_EVENT: - int eventType = msg.ReadRangedInteger(0, 9); + int eventType = msg.ReadRangedInteger(0, 13); switch (eventType) { case 0: //NetEntityEvent.Type.InventoryState @@ -350,7 +358,7 @@ namespace Barotrauma { string skillIdentifier = msg.ReadString(); float skillLevel = msg.ReadSingle(); - info?.SetSkillLevel(skillIdentifier, skillLevel, Position + Vector2.UnitY * 150.0f); + info?.SetSkillLevel(skillIdentifier, skillLevel); } break; case 4: // NetEntityEvent.Type.SetAttackTarget @@ -382,11 +390,12 @@ namespace Barotrauma } targetLimb = targetCharacter.AnimController.Limbs[targetLimbIndex]; } - if (attackLimb?.attack != null) + if (attackLimb?.attack != null && Controlled != this) { if (eventType == 4) { SetAttackTarget(attackLimb, targetEntity, targetSimPos); + PlaySound(CharacterSound.SoundType.Attack, maxInterval: 3); } else { @@ -450,6 +459,36 @@ namespace Barotrauma } } break; + case 10: //NetEntityEvent.Type.UpdateExperience + int experienceAmount = msg.ReadInt32(); + info?.SetExperience(experienceAmount); + break; + case 11: //NetEntityEvent.Type.UpdateTalents: + ushort talentCount = msg.ReadUInt16(); + for (int i = 0; i < talentCount; i++) + { + bool addedThisRound = msg.ReadBoolean(); + UInt32 talentIdentifier = msg.ReadUInt32(); + GiveTalent(talentIdentifier, addedThisRound); + } + break; + case 12: //NetEntityEvent.Type.UpdateMoney: + int moneyAmount = msg.ReadInt32(); + SetMoney(moneyAmount); + break; + case 13: //NetEntityEvent.Type.UpdatePermanentStats: + byte savedStatValueCount = msg.ReadByte(); + StatTypes statType = (StatTypes)msg.ReadByte(); + info?.ClearSavedStatValues(statType); + for (int i = 0; i < savedStatValueCount; i++) + { + string statIdentifier = msg.ReadString(); + float statValue = msg.ReadSingle(); + bool removeOnDeath = msg.ReadBoolean(); + info?.ChangeSavedStatValue(statType, statValue, statIdentifier, removeOnDeath, setValue: true); + } + break; + } msg.ReadPadBits(); break; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/AfflictionHusk.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/AfflictionHusk.cs index 5835cd675..f4a5f282c 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/AfflictionHusk.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/AfflictionHusk.cs @@ -1,10 +1,4 @@ -using Microsoft.Xna.Framework; -using System.Collections.Generic; -using Barotrauma.IO; -using System.Linq; -using System.Xml.Linq; - -namespace Barotrauma +namespace Barotrauma { partial class AfflictionHusk : Affliction { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs index 4dc901c4f..9ee0fa4ba 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs @@ -30,7 +30,7 @@ namespace Barotrauma get { return alignment; } set { - if (alignment == value) return; + if (alignment == value) { return; } alignment = value; UpdateAlignment(); } @@ -48,116 +48,24 @@ namespace Barotrauma private float bloodParticleTimer; - // healing interface - private GUIFrame healthInterfaceFrame; - private GUIFrame healthWindow; private GUITextBlock deadIndicator; - private GUIComponent lowSkillIndicator; + //private GUIComponent lowSkillIndicator; - private GUILayoutGroup cprLayout; - private GUIFrame cprFrame; private GUIButton cprButton; private GUIListBox afflictionTooltip; private static readonly Color oxygenLowGrainColor = new Color(0.1f, 0.1f, 0.1f, 1f); - private struct HeartratePosition - { - public float Time; - public float Height; - - public HeartratePosition ScaleHeight(float scale) - { - return new HeartratePosition - { - Time = this.Time, - Height = this.Height * scale - }; - } - - public HeartratePosition ScaleTime(float scale) - { - return new HeartratePosition - { - Time = this.Time * scale, - Height = this.Height - }; - } - - public HeartratePosition AddTime(float time) - { - return new HeartratePosition - { - Time = this.Time + time, - Height = this.Height - }; - } - - public static IEnumerable ScaleAndDisplace(IEnumerable positions, float heightScale, float timeScale, float timeAdd) - { - HeartratePosition prevPos = new HeartratePosition - { - Time = 0.0f, - Height = 0.0f - }; - bool wrapped = false; - foreach (HeartratePosition pos in positions) - { - HeartratePosition newPos = pos.ScaleHeight(heightScale).ScaleTime(timeScale).AddTime(timeAdd); - if (newPos.Time > 1.0f) - { - if (!wrapped) - { - yield return new HeartratePosition - { - Time = 1.0f, - Height = (newPos.Height - prevPos.Height) / (newPos.Time - prevPos.Time) * (1.0f - prevPos.Time) + prevPos.Height - }; - yield return new HeartratePosition - { - Time = 0.0f, - Height = (newPos.Height - prevPos.Height) / (newPos.Time - prevPos.Time) * (1.0f - prevPos.Time) + prevPos.Height - }; - wrapped = true; - } - newPos.Time -= 1.0f; - } - prevPos = newPos; - yield return newPos; - } - } - } - private List heartratePositions; - private float currentHeartrateTime; - private float heartbeatTimer; - private static Texture2D heartrateFade; - - private readonly HeartratePosition[] heartbeatPattern = - { - new HeartratePosition() { Time = 0.0f, Height = 0.0f }, - new HeartratePosition() { Time = 0.15f, Height = 0.2f }, - new HeartratePosition() { Time = 0.2f, Height = -0.2f }, - new HeartratePosition() { Time = 0.36f, Height = 0.0f }, - new HeartratePosition() { Time = 0.43f, Height = 0.8f }, - new HeartratePosition() { Time = 0.57f, Height = -0.8f }, - new HeartratePosition() { Time = 0.64f, Height = 0.0f }, - new HeartratePosition() { Time = 0.8f, Height = 0.2f }, - new HeartratePosition() { Time = 0.85f, Height = -0.2f }, - new HeartratePosition() { Time = 1.0f, Height = 0.0f }, - }; - private SpriteSheet limbIndicatorOverlay; private float limbIndicatorOverlayAnimState; private SpriteSheet medUIExtra; private float medUIExtraAnimState; - private GUIComponent draggingMed; - private int highlightedLimbIndex = -1; private int selectedLimbIndex = -1; private LimbHealth currentDisplayedLimb; @@ -166,12 +74,9 @@ namespace Barotrauma private GUIProgressBar healthWindowHealthBarShadow; private GUITextBlock characterName; - private GUIFrame afflictionInfoFrame; private GUIListBox afflictionIconContainer; - private GUIListBox afflictionInfoContainer; private GUILayoutGroup treatmentLayout; private GUIListBox recommendedTreatmentContainer; - private GUITextBlock selectedLimbText; private float distortTimer; @@ -211,7 +116,6 @@ namespace Barotrauma if (prevOpenHealthWindow != null) { - prevOpenHealthWindow.selectedLimbIndex = -1; prevOpenHealthWindow.highlightedLimbIndex = -1; } @@ -255,6 +159,12 @@ namespace Barotrauma get { return cprButton; } } + public GUIComponent InventorySlotContainer + { + get; + private set; + } + public float HealthBarPulsateTimer { get { return healthBarPulsateTimer; } @@ -279,56 +189,118 @@ namespace Barotrauma character.OnAttacked += OnAttacked; - bool horizontal = true; + healthWindow = new GUIFrame(new RectTransform(new Vector2(0.35f, 0.6f), GUI.Canvas, anchor: Anchor.Center, scaleBasis: ScaleBasis.Smallest), style: "GUIFrameListBox"); - healthBarHolder = new GUIFrame(new RectTransform(Point.Zero, GUI.Canvas), style: null) + var healthWindowVerticalLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.95f), healthWindow.RectTransform, Anchor.Center)) { - HoverCursor = CursorState.Hand + Stretch = true }; - healthBarHolder.RectTransform.AbsoluteOffset = HUDLayoutSettings.HealthBarArea.Location; - healthBarHolder.RectTransform.NonScaledSize = HUDLayoutSettings.HealthBarArea.Size; - healthBarHolder.RectTransform.RelativeOffset = Vector2.Zero; - - healthBarShadow = new GUIProgressBar(new RectTransform(Vector2.One, healthBarHolder.RectTransform, Anchor.BottomRight), - barSize: 1.0f, color: Color.Green, style: horizontal ? "CharacterHealthBar" : "GUIProgressBarVertical", showFrame: false) + var nameContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.2f), healthWindowVerticalLayout.RectTransform) { MinSize = new Point(0, 20) }, isHorizontal: true) { - IsHorizontal = horizontal - }; - healthBarShadow.Visible = false; - healthShadowSize = 1.0f; - - healthBar = new GUIProgressBar(new RectTransform(Vector2.One, healthBarHolder.RectTransform, Anchor.BottomRight), - barSize: 1.0f, color: GUI.Style.HealthBarColorHigh, style: horizontal ? "CharacterHealthBar" : "GUIProgressBarVertical") - { - HoverCursor = CursorState.Hand, - ToolTip = TextManager.GetWithVariable("hudbutton.healthinterface", "[key]", GameMain.Config.KeyBindText(InputType.Health)), - Enabled = true, - IsHorizontal = horizontal + Stretch = true }; - healthInterfaceFrame = new GUIFrame(new RectTransform(new Vector2(0.7f, 0.55f), GUI.Canvas, anchor: Anchor.Center, scaleBasis: ScaleBasis.Smallest), style: "ItemUI"); + new GUICustomComponent(new RectTransform(new Vector2(0.2f, 1.0f), nameContainer.RectTransform, Anchor.CenterLeft), + onDraw: (spriteBatch, component) => + { + character.Info?.DrawPortrait(spriteBatch, new Vector2(component.Rect.X, component.Rect.Center.Y - component.Rect.Width / 2), Vector2.Zero, component.Rect.Width, false, character != Character.Controlled); + }); + characterName = new GUITextBlock(new RectTransform(new Vector2(0.6f, 1.0f), nameContainer.RectTransform), "", textAlignment: Alignment.CenterLeft, font: GUI.SubHeadingFont) + { + AutoScaleHorizontal = true + }; + new GUICustomComponent(new RectTransform(new Vector2(0.2f, 1.0f), nameContainer.RectTransform), + onDraw: (spriteBatch, component) => + { + character.Info?.DrawJobIcon(spriteBatch, component.Rect, character != Character.Controlled); + }); - var healthInterfaceLayout = new GUILayoutGroup(new RectTransform(Vector2.One / 1.05f, healthInterfaceFrame.RectTransform, anchor: Anchor.Center), true); - var healthWindowContainer = new GUIFrame(new RectTransform(new Vector2(0.45f, 1.0f), healthInterfaceLayout.RectTransform), style: null); + var healthBarContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.07f), healthWindowVerticalLayout.RectTransform), style: null); + var healthBarIcon = new GUIFrame(new RectTransform(new Vector2(0.095f, 1.0f), healthBarContainer.RectTransform), style: "GUIHealthBarIcon"); + healthWindowHealthBarShadow = new GUIProgressBar(new RectTransform(new Vector2(0.91f, 1.0f), healthBarContainer.RectTransform, Anchor.CenterRight), + barSize: 1.0f, color: GUI.Style.Green, style: "GUIHealthBar") + { + IsHorizontal = true + }; + healthWindowHealthBar = new GUIProgressBar(new RectTransform(new Vector2(0.91f, 1.0f), healthBarContainer.RectTransform, Anchor.CenterRight), + barSize: 1.0f, color: GUI.Style.Green, style: "GUIHealthBar") + { + IsHorizontal = true + }; - //limb selection frame - healthWindow = new GUIFrame(new RectTransform(new Vector2(0.95f, 0.9f), healthWindowContainer.RectTransform, Anchor.CenterRight, Pivot.CenterRight), style: "GUIFrameListBox"); + //spacing + new GUIFrame(new RectTransform(new Vector2(1.0f, 0.05f), healthWindowVerticalLayout.RectTransform), style: null); - var healthWindowVerticalLayout = new GUILayoutGroup(new RectTransform(Vector2.One * 0.95f, healthWindow.RectTransform, Anchor.Center)) + var characterIndicatorArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.95f), healthWindowVerticalLayout.RectTransform), isHorizontal: true) { Stretch = true, - RelativeSpacing = 0.03f + //RelativeSpacing = 0.05f }; - var paddedHealthWindow = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.95f), healthWindowVerticalLayout.RectTransform), isHorizontal: true) + InventorySlotContainer = new GUICustomComponent(new RectTransform(new Vector2(0.1f, 1.0f), characterIndicatorArea.RectTransform, Anchor.TopLeft, Pivot.TopRight), + (spriteBatch, component) => + { + for (int i = 0; i < character.Inventory.Capacity; i++) + { + if (character.Inventory.SlotTypes[i] != InvSlotType.HealthInterface || Character.Controlled != Character) { continue; } + + //don't draw the item if it's being dragged out of the slot + bool drawItem = !Inventory.DraggingItems.Any() || !Character.Inventory.GetItemsAt(i).All(it => Inventory.DraggingItems.Contains(it)) || character.Inventory.visualSlots[i].MouseOn(); + + Inventory.DrawSlot(spriteBatch, Character.Inventory, Character.Inventory.visualSlots[i], Character.Inventory.GetItemAt(i), i, drawItem, Character.Inventory.SlotTypes[i]); + + if (medUIExtra != null) + { + float overlayScale = Math.Min( + Character.Inventory.visualSlots[i].Rect.Width / (float)medUIExtra.FrameSize.X, + Character.Inventory.visualSlots[i].Rect.Height / (float)medUIExtra.FrameSize.Y); + + int frame = (int)medUIExtraAnimState; + + medUIExtra.Draw(spriteBatch, frame, Character.Inventory.visualSlots[i].Rect.Center.ToVector2(), Color.Gray, origin: medUIExtra.FrameSize.ToVector2() / 2, rotate: 0.0f, + scale: Vector2.One * overlayScale); + } + } + }, + (dt, component) => + { + if (!GameMain.Instance.Paused) + { + medUIExtraAnimState = (medUIExtraAnimState + dt * 10.0f) % 16.0f; + } + }); + + + cprButton = new GUIButton(new RectTransform(new Vector2(0.17f, 0.17f), characterIndicatorArea.RectTransform, Anchor.BottomLeft, scaleBasis: ScaleBasis.Smallest), text: "", style: "CPRButton") { - Stretch = true, - RelativeSpacing = 0.03f + OnClicked = (button, userData) => + { + Character selectedCharacter = Character.Controlled?.SelectedCharacter; + if (selectedCharacter == null || (!selectedCharacter.IsUnconscious && selectedCharacter.Stun <= 0.0f)) + { + return false; + } + + Character.Controlled.AnimController.Anim = (Character.Controlled.AnimController.Anim == AnimController.Animation.CPR) ? + AnimController.Animation.None : AnimController.Animation.CPR; + + selectedCharacter.AnimController.ResetPullJoints(); + + if (GameMain.Client != null) + { + GameMain.Client.CreateEntityEvent(Character.Controlled, new object[] { NetEntityEvent.Type.Treatment }); + } + + return true; + }, + ToolTip = TextManager.Get("doctor.cprobjective"), + IgnoreLayoutGroups = true, + Visible = false }; - var limbSelection = new GUICustomComponent(new RectTransform(new Vector2(0.6f, 1.0f), paddedHealthWindow.RectTransform), + var limbSelection = new GUICustomComponent(new RectTransform(new Vector2(0.4f, 1.0f), characterIndicatorArea.RectTransform), (spriteBatch, component) => { DrawHealthWindow(spriteBatch, component.RectTransform.Rect, true); @@ -353,172 +325,52 @@ namespace Barotrauma deadIndicator.AutoScaleHorizontal = true; } - var rightSide = new GUIFrame(new RectTransform(new Vector2(0.4f, 1.0f), paddedHealthWindow.RectTransform), style: null); + afflictionIconContainer = new GUIListBox(new RectTransform(new Vector2(0.25f, 1.0f), characterIndicatorArea.RectTransform), style: null); - new GUICustomComponent(new RectTransform(new Vector2(1.0f, 0.3f), rightSide.RectTransform, Anchor.BottomRight, Pivot.BottomRight), - (sb, component) => - { - if (medUIExtra == null) { return; } - float overlayScale = Math.Min( - component.Rect.Width / (float)medUIExtra.FrameSize.X, - component.Rect.Height / (float)medUIExtra.FrameSize.Y); + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.1f), healthWindowVerticalLayout.RectTransform), + TextManager.Get("SuitableTreatments"), font: GUI.SubHeadingFont, textAlignment: Alignment.BottomCenter); - int frame = (int)medUIExtraAnimState; - - medUIExtra.Draw(sb, frame, component.Rect.Center.ToVector2(), Color.Gray, origin: medUIExtra.FrameSize.ToVector2() / 2, rotate: 0.0f, - scale: Vector2.One * overlayScale); - }, - (dt, component) => - { - if (!GameMain.Instance.Paused) - { - medUIExtraAnimState = (medUIExtraAnimState + dt * 10.0f) % 16.0f; - } - }); - - GUILayoutGroup selectedLimbLayout = new GUILayoutGroup(new RectTransform(Vector2.One, rightSide.RectTransform)); - - selectedLimbText = new GUITextBlock(new RectTransform(new Vector2(0.8f, 0.08f), selectedLimbLayout.RectTransform), "", font: GUI.SubHeadingFont, textAlignment: Alignment.Center) - { - AutoScaleHorizontal = true - }; - - afflictionIconContainer = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.92f), selectedLimbLayout.RectTransform), style: null) - { - KeepSpaceForScrollBar = true - }; - - var healthBarContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.07f), healthWindowVerticalLayout.RectTransform), style: null); - - var healthBarIcon = new GUIFrame(new RectTransform(new Vector2(0.095f, 1.0f), healthBarContainer.RectTransform), style: "GUIHealthBarIcon"); - - healthWindowHealthBarShadow = new GUIProgressBar(new RectTransform(new Vector2(0.91f, 1.0f), healthBarContainer.RectTransform, Anchor.CenterRight), - barSize: 1.0f, color: GUI.Style.Green, style: "GUIHealthBar") - { - IsHorizontal = true - }; - healthWindowHealthBar = new GUIProgressBar(new RectTransform(new Vector2(0.91f, 1.0f), healthBarContainer.RectTransform, Anchor.CenterRight), - barSize: 1.0f, color: GUI.Style.Green, style: "GUIHealthBar") - { - IsHorizontal = true - }; - - //affliction info frame - afflictionInfoFrame = new GUIFrame(new RectTransform(new Vector2(0.55f, 1.0f), healthInterfaceLayout.RectTransform), style: null); - var paddedInfoFrame = new GUIFrame(new RectTransform(new Vector2(0.95f, 0.9f), afflictionInfoFrame.RectTransform, Anchor.Center), style: null); - - var infoLayout = new GUILayoutGroup(new RectTransform(Vector2.One, paddedInfoFrame.RectTransform)) - { - Stretch = true, - RelativeSpacing = 0.03f - }; - - var textContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.6f), infoLayout.RectTransform), style: "GUIFrameListBox"); - - var textLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.98f), textContainer.RectTransform, Anchor.Center, Pivot.Center)) - { - Stretch = true, - RelativeSpacing = 0.03f, - CanBeFocused = true - }; - - textLayout.RectTransform.RelativeOffset = new Vector2(0, 0.025f); - - var nameContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.2f), textLayout.RectTransform) { MinSize = new Point(0, 20) }, isHorizontal: true) - { - Stretch = true - }; - - new GUICustomComponent(new RectTransform(new Vector2(0.2f, 1.0f), nameContainer.RectTransform, Anchor.CenterLeft), - onDraw: (spriteBatch, component) => - { - character.Info?.DrawPortrait(spriteBatch, new Vector2(component.Rect.X, component.Rect.Center.Y - component.Rect.Width / 2), Vector2.Zero, component.Rect.Width, false, openHealthWindow?.Character != Character.Controlled); - }); - characterName = new GUITextBlock(new RectTransform(new Vector2(0.6f, 1.0f), nameContainer.RectTransform), "", textAlignment: Alignment.CenterLeft, font: GUI.SubHeadingFont) - { - AutoScaleHorizontal = true - }; - new GUICustomComponent(new RectTransform(new Vector2(0.2f, 1.0f), nameContainer.RectTransform), - onDraw: (spriteBatch, component) => - { - character.Info?.DrawJobIcon(spriteBatch, component.Rect, openHealthWindow?.Character != Character.Controlled); - }); - - - new GUIFrame(new RectTransform(new Vector2(1.0f, 0.01f), textLayout.RectTransform), style: "HorizontalLine"); - - afflictionInfoContainer = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.4f), textLayout.RectTransform, Anchor.TopLeft), style: null); - - new GUIFrame(new RectTransform(new Vector2(1.0f, 0.01f), textLayout.RectTransform), style: "HorizontalLine"); - - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), textLayout.RectTransform, Anchor.TopLeft), TextManager.Get("SuitableTreatments"), font: GUI.SubHeadingFont); - - treatmentLayout = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.15f), textLayout.RectTransform), true) + treatmentLayout = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.15f), healthWindowVerticalLayout.RectTransform), true) { Stretch = false }; - recommendedTreatmentContainer = new GUIListBox(new RectTransform(new Vector2(0.9f, 1.0f), treatmentLayout.RectTransform, Anchor.Center, Pivot.Center), isHorizontal: true, style: null) + recommendedTreatmentContainer = new GUIListBox(new RectTransform(new Vector2(1.0f, 1.0f), treatmentLayout.RectTransform, Anchor.Center, Pivot.Center), isHorizontal: true, style: null) { - KeepSpaceForScrollBar = false + Spacing = GUI.IntScale(4), + KeepSpaceForScrollBar = false, + ScrollBarVisible = false, + AutoHideScrollBar = false + }; + new GUITextBlock(new RectTransform(Vector2.One, recommendedTreatmentContainer.Content.RectTransform), TextManager.Get("none"), textAlignment: Alignment.Center) + { + CanBeFocused = false }; - lowSkillIndicator = new GUIImage(new RectTransform(new Vector2(0.1f, 1.0f), treatmentLayout.RectTransform, Anchor.TopLeft, Pivot.Center), - style: "GUINotificationButton") + characterIndicatorArea.Recalculate(); + + healthBarHolder = new GUIFrame(new RectTransform(Point.Zero, GUI.Canvas), style: null) + { + HoverCursor = CursorState.Hand + }; + + healthBarHolder.RectTransform.AbsoluteOffset = HUDLayoutSettings.HealthBarArea.Location; + healthBarHolder.RectTransform.NonScaledSize = HUDLayoutSettings.HealthBarArea.Size; + healthBarHolder.RectTransform.RelativeOffset = Vector2.Zero; + + healthBarShadow = new GUIProgressBar(new RectTransform(Vector2.One, healthBarHolder.RectTransform, Anchor.BottomRight), + barSize: 1.0f, color: Color.Green, style: "CharacterHealthBar", showFrame: false) { - ToolTip = TextManager.Get("lowmedicalskillwarning"), - Color = GUI.Style.Orange, - HoverColor = Color.Lerp(GUI.Style.Orange, Color.White, 0.5f), - PressedColor = Color.Lerp(GUI.Style.Orange, Color.White, 0.5f), Visible = false }; - lowSkillIndicator.RectTransform.MaxSize = new Point(lowSkillIndicator.Rect.Height); + healthShadowSize = 1.0f; - var tempFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.01f), textLayout.RectTransform), style: null); - - cprLayout = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.25f), infoLayout.RectTransform), true) + healthBar = new GUIProgressBar(new RectTransform(Vector2.One, healthBarHolder.RectTransform, Anchor.BottomRight), + barSize: 1.0f, color: GUI.Style.HealthBarColorHigh, style: "CharacterHealthBar") { - Stretch = true - }; - - cprFrame = new GUIFrame(new RectTransform(new Vector2(0.7f, 1.0f), cprLayout.RectTransform), style: "GUIFrameListBox"); - - heartrateFade ??= TextureLoader.FromFile("Content/UI/Health/HeartrateFade.png"); - - new GUICustomComponent(new RectTransform(Vector2.One * 0.95f, cprFrame.RectTransform, Anchor.Center), DrawHeartrate, UpdateHeartrate); - - heartbeatTimer = 0.46f; - - heartratePositions = new List - { - heartbeatPattern.First(), - heartbeatPattern.Last() - }; - - cprButton = new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), cprLayout.RectTransform, scaleBasis: ScaleBasis.Smallest), text: "", style: "CPRButton") - { - OnClicked = (button, userData) => - { - Character selectedCharacter = Character.Controlled?.SelectedCharacter; - if (selectedCharacter == null || (!selectedCharacter.IsUnconscious && selectedCharacter.Stun <= 0.0f)) - { - return false; - } - - Character.Controlled.AnimController.Anim = (Character.Controlled.AnimController.Anim == AnimController.Animation.CPR) ? - AnimController.Animation.None : AnimController.Animation.CPR; - - selectedCharacter.AnimController.ResetPullJoints(); - - if (GameMain.Client != null) - { - GameMain.Client.CreateEntityEvent(Character.Controlled, new object[] { NetEntityEvent.Type.Treatment }); - } - - return true; - }, - ToolTip = TextManager.Get("doctor.cprobjective"), - Visible = false + HoverCursor = CursorState.Hand, + ToolTip = TextManager.GetWithVariable("hudbutton.healthinterface", "[key]", GameMain.Config.KeyBindText(InputType.Health)), + Enabled = true }; UpdateAlignment(); @@ -542,8 +394,8 @@ namespace Barotrauma } else { - var causeOfDeath = GetCauseOfDeath(); - Character.Controlled.Kill(causeOfDeath.First, causeOfDeath.Second); + var (type, affliction) = GetCauseOfDeath(); + Character.Controlled.Kill(type, affliction); Character.Controlled = null; } } @@ -568,6 +420,8 @@ namespace Barotrauma } } } + + healthWindowVerticalLayout.Recalculate(); } private void OnAttacked(Character attacker, AttackResult attackResult) @@ -598,15 +452,15 @@ namespace Barotrauma switch (alignment) { case Alignment.Left: - healthInterfaceFrame.RectTransform.SetPosition(Anchor.BottomLeft); + healthWindow.RectTransform.SetPosition(Anchor.BottomLeft); break; case Alignment.Right: - healthInterfaceFrame.RectTransform.SetPosition(Anchor.BottomRight); + healthWindow.RectTransform.SetPosition(Anchor.BottomRight); break; } - healthInterfaceFrame.RectTransform.AbsoluteOffset = new Point(HUDLayoutSettings.Padding, screenResolution.Y - HUDLayoutSettings.ChatBoxArea.Y + HUDLayoutSettings.Padding); - healthInterfaceFrame.RectTransform.RecalculateChildren(false); + healthWindow.RectTransform.AbsoluteOffset = new Point(HUDLayoutSettings.Padding, screenResolution.Y - HUDLayoutSettings.ChatBoxArea.Y + HUDLayoutSettings.Padding); + healthWindow.RectTransform.RecalculateChildren(false); } public void UpdateClientSpecific(float deltaTime) @@ -683,19 +537,33 @@ namespace Barotrauma float particleMaxScale = emitter?.Prefab.Properties.ScaleMax ?? 1; float severity = Math.Min(affliction.Strength / affliction.Prefab.MaxStrength * Character.Params.BleedParticleMultiplier, 1); float bloodParticleSize = MathHelper.Lerp(particleMinScale, particleMaxScale, severity); + + Vector2 velocity = Rand.Vector(affliction.Strength * 0.1f); if (!inWater) { bloodParticleSize *= 2.0f; + velocity = targetLimb.LinearVelocity * 100.0f; } // TODO: use the blood emitter? var blood = GameMain.ParticleManager.CreateParticle( inWater ? Character.Params.BleedParticleWater : Character.Params.BleedParticleAir, - targetLimb.WorldPosition, Rand.Vector(affliction.Strength), 0.0f, Character.AnimController.CurrentHull); + targetLimb.WorldPosition, velocity, 0.0f, Character.AnimController.CurrentHull); - if (blood != null) + if (blood != null && !inWater) { blood.Size *= bloodParticleSize; + if (!string.IsNullOrEmpty(Character.BloodDecalName) && Rand.Range(0.0f, 1.0f) < 0.05f) + { + blood.OnCollision += (Vector2 pos, Hull hull) => + { + var decal = hull?.AddDecal(Character.BloodDecalName, pos, Rand.Range(1.0f, 2.0f), isNetworkEvent: true); + if (decal != null) + { + decal.FadeTimer = decal.LifeTime - decal.FadeOutTime * 2; + } + }; + } } bloodParticleTimer = MathHelper.Lerp(2, 0.5f, severity); } @@ -731,8 +599,13 @@ namespace Barotrauma .FindAll(a => a.ShouldShowIcon(Character) && a.Prefab.Icon != null); currentDisplayedAfflictions.Sort((a1, a2) => { - int dmgPerSecond = Math.Sign(a2.DamagePerSecond - a1.DamagePerSecond); - return dmgPerSecond != 0 ? dmgPerSecond : Math.Sign(a1.Strength - a1.Strength); + int dmgPerSecond = Math.Sign(a1.DamagePerSecond - a2.DamagePerSecond); + if (dmgPerSecond != 0) { return dmgPerSecond; } + return Math.Sign(GetStr(a1) - GetStr(a2)); + static float GetStr(Affliction affliction) + { + return affliction.Strength / affliction.Prefab.MaxStrength * (affliction.Prefab.IsBuff ? 1.0f : 10.0f); + } }); HintManager.OnAfflictionDisplayed(Character, currentDisplayedAfflictions); updateDisplayedAfflictionsTimer = UpdateDisplayedAfflictionsInterval; @@ -785,7 +658,8 @@ namespace Barotrauma if (afflictionGrainStrength > 0.0f) { grainStrength = Math.Max(grainStrength, affliction.GetScreenGrainStrength()); - grainColor = Color.Lerp(grainColor, Color.White, (float)Math.Pow(1.0f - oxygenLowStrength, 2)); + Color afflictionGrainColor = affliction.GetActiveEffect()?.GrainColor ?? Color.White; + grainColor = Color.Lerp(grainColor, afflictionGrainColor, (float)Math.Pow(1.0f - oxygenLowStrength, 2)); } } foreach (LimbHealth limbHealth in limbHealths) @@ -832,7 +706,7 @@ namespace Barotrauma } else if (openHealthWindow == this) { - if (HUD.CloseHUD(healthInterfaceFrame.Rect)) + if (HUD.CloseHUD(healthWindow.Rect)) { //emulate a Health input to get the character to deselect the item server-side if (GameMain.Client != null) @@ -842,9 +716,22 @@ namespace Barotrauma OpenHealthWindow = null; } - if (GUI.MouseOn != null && GUI.MouseOn.UserData is string str && str == "selectaffliction") + foreach (GUIComponent afflictionIcon in afflictionIconContainer.Content.Children) { - Affliction affliction = GUI.MouseOn.Parent.UserData as Affliction; + if (!(afflictionIcon.UserData is Affliction affliction)) { continue; } + if (affliction.AppliedAsFailedTreatmentTime > Timing.TotalTime - 1.0 && afflictionIcon.FlashTimer <= 0.0f) + { + afflictionIcon.Flash(GUI.Style.Red); + } + else if (affliction.AppliedAsSuccessfulTreatmentTime > Timing.TotalTime - 1.0 && afflictionIcon.FlashTimer <= 0.0f) + { + afflictionIcon.Flash(GUI.Style.Green); + } + } + + if (GUI.MouseOn?.UserData is Affliction) + { + Affliction affliction = GUI.MouseOn?.UserData as Affliction; if (afflictionTooltip == null || afflictionTooltip.UserData != affliction) { @@ -858,7 +745,17 @@ namespace Barotrauma int height = afflictionTooltip.Content.Children.Sum(c => c.Rect.Height) + 10; afflictionTooltip.RectTransform.Resize(new Point(afflictionTooltip.Rect.Width, height), true); - afflictionTooltip.RectTransform.AbsoluteOffset = new Point(GUI.MouseOn.Rect.Right, GUI.MouseOn.Rect.Y); + if (Alignment == Alignment.Right) + { + afflictionTooltip.RectTransform.AbsoluteOffset = new Point(GUI.MouseOn.Rect.X, GUI.MouseOn.Rect.Y); + afflictionTooltip.RectTransform.Pivot = Pivot.TopRight; + } + else + { + afflictionTooltip.RectTransform.AbsoluteOffset = new Point(GUI.MouseOn.Rect.Right, GUI.MouseOn.Rect.Y); + afflictionTooltip.RectTransform.Anchor = Anchor.TopLeft; + } + afflictionTooltip.ScrollBarVisible = false; var labelContainer = afflictionTooltip.Content.GetChildByUserData("label"); @@ -901,6 +798,19 @@ namespace Barotrauma UpdateAfflictionContainer(selectedLimb); currentDisplayedLimb = selectedLimb; } + + UpdateAfflictionInfos(displayedAfflictions.Select(d => d.affliction)); + + foreach (GUIComponent component in recommendedTreatmentContainer.Content.Children) + { + var treatmentButton = component.GetChild(); + if (!(treatmentButton?.UserData is ItemPrefab itemPrefab)) { continue; } + treatmentButton.Enabled = Character.Controlled.Inventory.AllItems.Any(it => it.prefab == itemPrefab); + foreach (GUIComponent child in treatmentButton.Children) + { + child.Enabled = treatmentButton.Enabled; + } + } } if (Character.IsDead) @@ -939,16 +849,6 @@ namespace Barotrauma openHealthWindow = null; } - lowSkillIndicator.Visible = Character.Controlled != null && Character.Controlled.GetSkillLevel("medical") < 50.0f; - lowSkillIndicator.IgnoreLayoutGroups = !lowSkillIndicator.Visible; - - recommendedTreatmentContainer.RectTransform.Resize(new Vector2(0.9f, 1.0f)); - lowSkillIndicator.RectTransform.Resize(new Vector2(0.1f, 1.0f)); - - treatmentLayout.Recalculate(); - - lowSkillIndicator.Color = new Color(lowSkillIndicator.Color, MathHelper.Lerp(0.5f, 1.0f, (float)(Math.Sin(Timing.TotalTime * 5.0f) + 1.0f) / 2.0f)); - if (Inventory.DraggingItems.Any()) { if (highlightedLimbIndex > -1) @@ -956,20 +856,6 @@ namespace Barotrauma selectedLimbIndex = highlightedLimbIndex; } } - - if (draggingMed != null) - { - if (!PlayerInput.PrimaryMouseButtonHeld()) - { - OnItemDropped(draggingMed.UserData as Item, ignoreMousePos: false); - draggingMed = null; - } - } - - /*if (GUI.MouseOn?.UserData is Affliction affliction) - { - ShowAfflictionInfo(affliction, afflictionInfoContainer); - }*/ } else { @@ -1024,17 +910,11 @@ namespace Barotrauma && !Character.IsDead && Character.IsKnockedDown && openHealthWindow == this; - cprButton.IgnoreLayoutGroups = !cprButton.Visible; cprButton.Selected = Character.Controlled != null && Character == Character.Controlled.SelectedCharacter && Character.Controlled.AnimController.Anim == AnimController.Animation.CPR; - cprFrame.RectTransform.Resize(new Vector2(0.7f, 1.0f)); - cprButton.RectTransform.Resize(new Vector2(1.0f, 1.0f)); - - cprLayout.Recalculate(); - deadIndicator.Visible = Character.IsDead; } @@ -1043,7 +923,7 @@ namespace Barotrauma if (GUI.DisableHUD) { return; } if (OpenHealthWindow == this) { - healthInterfaceFrame.AddToGUIUpdateList(); + healthWindow.AddToGUIUpdateList(); afflictionTooltip?.AddToGUIUpdateList(); } else if (Character.Controlled == Character && !CharacterHUD.IsCampaignInterfaceOpen) @@ -1071,6 +951,16 @@ namespace Barotrauma UpdateAlignment(); } + foreach (Affliction affliction in afflictions) + { + if (affliction.Prefab.AfflictionOverlay != null) + { + Sprite ScreenAfflictionOverlay = affliction.Prefab.AfflictionOverlay; + ScreenAfflictionOverlay?.Draw(spriteBatch, Vector2.Zero, Color.White * (affliction.GetAfflictionOverlayMultiplier()), Vector2.Zero, 0.0f, + new Vector2(GameMain.GraphicsWidth / DamageOverlay.size.X, GameMain.GraphicsHeight / DamageOverlay.size.Y)); + } + } + float damageOverlayAlpha = DamageOverlayTimer; if (Vitality < MaxVitality * 0.1f) { @@ -1100,22 +990,26 @@ namespace Barotrauma } - private Pair highlightedAfflictionIcon = null; + private (Affliction affliction, string text)? highlightedAfflictionIcon; public void DrawStatusHUD(SpriteBatch spriteBatch) { highlightedAfflictionIcon = null; //Rectangle interactArea = healthBar.Rect; if (Character.Controlled?.SelectedCharacter == null && openHealthWindow == null) { - List> statusIcons = new List>(); - if (Character.CurrentHull == null || Character.CurrentHull.LethalPressure > 5.0f) - statusIcons.Add(new Pair(pressureAffliction, TextManager.Get("PressureHUDWarning"))); + List<(Affliction affliction, string text)> statusIcons = new List<(Affliction affliction, string text)>(); + if (Character.InPressure) + { + statusIcons.Add((pressureAffliction, TextManager.Get("PressureHUDWarning"))); + } if (Character.CurrentHull != null && Character.OxygenAvailable < LowOxygenThreshold && oxygenLowAffliction.Strength < oxygenLowAffliction.Prefab.ShowIconThreshold) - statusIcons.Add(new Pair(oxygenLowAffliction, TextManager.Get("OxygenHUDWarning"))); + { + statusIcons.Add((oxygenLowAffliction, TextManager.Get("OxygenHUDWarning"))); + } foreach (Affliction affliction in currentDisplayedAfflictions) { - statusIcons.Add(new Pair(affliction, affliction.Prefab.Name)); + statusIcons.Add((affliction, affliction.Prefab.Name)); } Vector2 highlightedIconPos = Vector2.Zero; @@ -1132,9 +1026,9 @@ namespace Barotrauma Point pos = new Point(afflictionArea.Right - iconSize, afflictionArea.Top); - foreach (Pair statusIcon in statusIcons) + foreach (var statusIcon in statusIcons) { - Affliction affliction = statusIcon.First; + Affliction affliction = statusIcon.affliction; AfflictionPrefab afflictionPrefab = affliction.Prefab; Rectangle afflictionIconRect = new Rectangle(pos, new Point(iconSize)); @@ -1154,12 +1048,6 @@ namespace Barotrauma GUI.Style.Red * (float)((Math.Sin(affliction.DamagePerSecondTimer * MathHelper.TwoPi - MathHelper.PiOver2) + 1.0f) * 0.5f)); } - /*var slot = GUI.Style.GetComponentStyle("AfflictionIconSlot"); - slot.Sprites[highlightedIcon == statusIcon ? GUIComponent.ComponentState.Hover : GUIComponent.ComponentState.None][0].Draw( - spriteBatch, afflictionIconRect, - highlightedIcon == statusIcon ? slot.HoverColor : slot.Color);*/ - - float alphaMultiplier = highlightedAfflictionIcon == statusIcon ? 1f : 0.8f; afflictionPrefab.Icon?.Draw(spriteBatch, @@ -1177,7 +1065,7 @@ namespace Barotrauma if (highlightedAfflictionIcon != null) { - string nameTooltip = highlightedAfflictionIcon.Second; + string nameTooltip = highlightedAfflictionIcon.Value.text; Vector2 offset = GUI.Font.MeasureString(nameTooltip); GUI.DrawString(spriteBatch, @@ -1242,72 +1130,75 @@ namespace Barotrauma public static Color GetAfflictionIconColor(Affliction affliction) => GetAfflictionIconColor(affliction.Prefab, affliction); + private readonly List<(Affliction affliction, float strength)> displayedAfflictions = new List<(Affliction affliction, float strength)>(); + private void UpdateAfflictionContainer(LimbHealth selectedLimb) { - selectedLimbText.Text = selectedLimb == null ? "" : selectedLimb.Name; - if (selectedLimb == null) { afflictionIconContainer.Content.ClearChildren(); return; } var currentAfflictions = GetMatchingAfflictions(selectedLimb, a => a.ShouldShowIcon(Character)); - var displayedAfflictions = afflictionIconContainer.Content.Children.Select(c => c.UserData as Affliction); - if (currentAfflictions.Any(a => !displayedAfflictions.Contains(a)) || - displayedAfflictions.Any(a => !currentAfflictions.Contains(a))) + if (currentAfflictions.Any(a => !displayedAfflictions.Any(d => d.affliction == a)) || + displayedAfflictions.Any(a => !currentAfflictions.Contains(a.affliction))) { CreateAfflictionInfos(currentAfflictions); + CreateRecommendedTreatments(); + } + //update recommended treatments if the strength of some displayed affliction has changed by > 1 + else if (displayedAfflictions.Any(d => Math.Abs(d.strength - currentAfflictions.First(a => a == d.affliction).Strength) > 1.0f)) + { + CreateRecommendedTreatments(); } - - UpdateAfflictionInfos(displayedAfflictions); } private void CreateAfflictionInfos(IEnumerable afflictions) { afflictionIconContainer.ClearChildren(); - afflictionInfoContainer.ClearChildren(); - afflictionInfoContainer.UserData = null; - recommendedTreatmentContainer.Content.ClearChildren(); - - float characterSkillLevel = Character.Controlled == null ? 0.0f : Character.Controlled.GetSkillLevel("medical"); + displayedAfflictions.Clear(); - //random variance is 200% when the skill is 0 - //no random variance if the skill is 50 or more - float randomVariance = MathHelper.Lerp(2.0f, 0.0f, characterSkillLevel / 50.0f); - - //key = item identifier - //float = suitability - Dictionary treatmentSuitability = new Dictionary(); - GetSuitableTreatments(treatmentSuitability, normalize: true, randomization: randomVariance); - - //Affliction mostSevereAffliction = afflictions.FirstOrDefault(a => !a.Prefab.IsBuff && !afflictions.Any(a2 => !a2.Prefab.IsBuff && a2.Strength > a.Strength)) ?? afflictions.FirstOrDefault(); Affliction mostSevereAffliction = SortAfflictionsBySeverity(afflictions).FirstOrDefault(); GUIButton buttonToSelect = null; foreach (Affliction affliction in afflictions) { - var child = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.25f), afflictionIconContainer.Content.RectTransform, Anchor.TopCenter)) - { - Stretch = true, - UserData = affliction - }; + displayedAfflictions.Add((affliction, affliction.Strength)); - var button = new GUIButton(new RectTransform(new Vector2(1.0f, 0.9f), child.RectTransform), style: null) + var frame = new GUIButton(new RectTransform(new Vector2(1.0f, 0.25f), afflictionIconContainer.Content.RectTransform), style: "ListBoxElement") { - Color = Color.Gray.Multiply(0.1f).Opaque(), - HoverColor = Color.Gray.Multiply(0.4f).Opaque(), - SelectedColor = Color.Gray.Multiply(0.25f).Opaque(), - PressedColor = Color.Gray.Multiply(0.2f).Opaque(), - UserData = "selectaffliction", + UserData = affliction, OnClicked = SelectAffliction }; + new GUIFrame(new RectTransform(Vector2.One, frame.RectTransform), style: "GUIFrameListBox") { CanBeFocused = false }; + + var content = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.85f), frame.RectTransform, Anchor.Center), childAnchor: Anchor.TopCenter) + { + Stretch = true, + CanBeFocused = false + }; + + var progressbarBg = new GUIProgressBar(new RectTransform(new Vector2(1.0f, 0.18f), content.RectTransform), 0.0f, GUI.Style.Green, style: "GUIAfflictionBar") + { + UserData = "afflictionstrengthprediction", + CanBeFocused = false + }; + new GUIProgressBar(new RectTransform(Vector2.One, progressbarBg.RectTransform), 0.0f, Color.Transparent, showFrame: false, style: "GUIAfflictionBar") + { + UserData = "afflictionstrength", + CanBeFocused = false + }; + + //spacing + new GUIFrame(new RectTransform(new Vector2(1.0f, 0.15f), content.RectTransform), style: null) { CanBeFocused = false }; + if (affliction == mostSevereAffliction) { - buttonToSelect = button; + buttonToSelect = frame; } - var afflictionIcon = new GUIImage(new RectTransform(Vector2.One * 0.8f, button.RectTransform, Anchor.Center), affliction.Prefab.Icon, scaleToFit: true) + var afflictionIcon = new GUIImage(new RectTransform(Vector2.One * 0.8f, content.RectTransform), affliction.Prefab.Icon, scaleToFit: true) { Color = GetAfflictionIconColor(affliction), CanBeFocused = false @@ -1316,33 +1207,68 @@ namespace Barotrauma afflictionIcon.HoverColor = Color.Lerp(afflictionIcon.Color, Color.White, 0.6f); afflictionIcon.SelectedColor = Color.Lerp(afflictionIcon.Color, Color.White, 0.5f); - float afflictionVitalityDecrease = affliction.GetVitalityDecrease(this); - - Color afflictionEffectColor = Color.White; - if (afflictionVitalityDecrease > 0.0f) + var nameText = new GUITextBlock(new RectTransform(new Vector2(1.1f, 0.0f), content.RectTransform), + affliction.Prefab.Name, font: GUI.SmallFont, textAlignment: Alignment.BottomCenter) { - afflictionEffectColor = GUI.Style.Red; - } - else if (afflictionVitalityDecrease < 0.0f) - { - afflictionEffectColor = GUI.Style.Green; - } - - var nameText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), child.RectTransform), - affliction.Prefab.Name, font: GUI.SmallFont, textAlignment: Alignment.Center, style: "GUIToolTip"); + CanBeFocused = false + }; nameText.Text = ToolBox.LimitString(nameText.Text, nameText.Font, nameText.Rect.Width); - - new GUIProgressBar(new RectTransform(new Vector2(1.0f, 0.1f), child.RectTransform), 0.0f, afflictionEffectColor, style: "GUIAfflictionBar") + nameText.RectTransform.MinSize = new Point(0, (int)(nameText.TextSize.Y)); + nameText.RectTransform.SizeChanged += () => { - UserData = "afflictionstrength" + nameText.Text = ToolBox.LimitString(nameText.Text, nameText.Font, nameText.Rect.Width); }; - child.Recalculate(); + content.Recalculate(); } - buttonToSelect?.OnClicked(buttonToSelect, "selectaffliction"); - + buttonToSelect?.OnClicked(buttonToSelect, buttonToSelect.UserData); afflictionIconContainer.RecalculateChildren(); + } + + private void CreateRecommendedTreatments() + { + ItemPrefab prevHighlightedItem = null; + if (GUI.MouseOn?.UserData is ItemPrefab && recommendedTreatmentContainer.Content.IsParentOf(GUI.MouseOn)) + { + prevHighlightedItem = (ItemPrefab)GUI.MouseOn.UserData; + } + + recommendedTreatmentContainer.Content.ClearChildren(); + + float characterSkillLevel = Character.Controlled == null ? 0.0f : Character.Controlled.GetSkillLevel("medical"); + + //key = item identifier + //float = suitability + Dictionary treatmentSuitability = new Dictionary(); + GetSuitableTreatments(treatmentSuitability, + normalize: true, + ignoreHiddenAfflictions: true, + limb: selectedLimbIndex == -1 ? null : Character.AnimController.Limbs.Find(l => l.HealthIndex == selectedLimbIndex)); + + foreach (string treatment in treatmentSuitability.Keys.ToList()) + { + //prefer suggestions for items the player has + if (Character.Controlled.Inventory.FindItemByIdentifier(treatment) != null) + { + treatmentSuitability[treatment] *= 10.0f; + } + } + + if (!treatmentSuitability.Any()) + { + new GUITextBlock(new RectTransform(Vector2.One, recommendedTreatmentContainer.Content.RectTransform), TextManager.Get("none"), textAlignment: Alignment.Center) + { + CanBeFocused = false + }; + recommendedTreatmentContainer.ScrollBarVisible = false; + recommendedTreatmentContainer.AutoHideScrollBar = false; + } + else + { + recommendedTreatmentContainer.ScrollBarVisible = true; + recommendedTreatmentContainer.AutoHideScrollBar = true; + } List> treatmentSuitabilities = treatmentSuitability.OrderByDescending(t => t.Value).ToList(); @@ -1353,27 +1279,55 @@ namespace Barotrauma if (count > 5) { break; } if (!(MapEntityPrefab.Find(name: null, identifier: treatment.Key, showErrorMessages: false) is ItemPrefab item)) { continue; } - var itemSlot = new GUIFrame(new RectTransform(new Vector2(1.0f / 7.0f, 1.0f), recommendedTreatmentContainer.Content.RectTransform, Anchor.TopLeft), + var itemSlot = new GUIFrame(new RectTransform(new Vector2(1.0f / 6.0f, 1.0f), recommendedTreatmentContainer.Content.RectTransform, Anchor.TopLeft), style: null) { UserData = item }; - var innerFrame = new GUIFrame(new RectTransform(Vector2.One, itemSlot.RectTransform, Anchor.Center, Pivot.Center, scaleBasis: ScaleBasis.Smallest), style: "GUIFrameListBox") + var innerFrame = new GUIButton(new RectTransform(Vector2.One, itemSlot.RectTransform, Anchor.Center, Pivot.Center, scaleBasis: ScaleBasis.Smallest), style: "SubtreeHeader") { - CanBeFocused = false + UserData = item, + ToolTip = $"‖color:255,255,255,255‖{item.Name}‖color:end‖" + '\n' + item.Description, + DisabledColor = Color.White * 0.1f, + OnClicked = (btn, userdata) => + { + if (!(userdata is ItemPrefab itemPrefab)) { return false; } + var item = Character.Controlled.Inventory.AllItems.FirstOrDefault(it => it.prefab == itemPrefab); + if (item == null) { return false; } + Limb targetLimb = Character.AnimController.Limbs.FirstOrDefault(l => l.HealthIndex == selectedLimbIndex); + item.ApplyTreatment(Character.Controlled, Character, targetLimb); + return true; + } }; + + new GUIImage(new RectTransform(Vector2.One, innerFrame.RectTransform, Anchor.Center), style: "TalentBackgroundGlow") + { + CanBeFocused = false, + Color = GUI.Style.Green, + HoverColor = Color.White, + PressedColor = Color.DarkGray, + SelectedColor = Color.Transparent, + DisabledColor = Color.Transparent + }; + Sprite itemSprite = item.InventoryIcon ?? item.sprite; Color itemColor = itemSprite == item.sprite ? item.SpriteColor : item.InventoryIconColor; var itemIcon = new GUIImage(new RectTransform(new Vector2(0.8f, 0.8f), innerFrame.RectTransform, Anchor.Center), itemSprite, scaleToFit: true) { CanBeFocused = false, - Color = itemColor, + Color = itemColor * 0.9f, HoverColor = itemColor, - SelectedColor = itemColor + SelectedColor = itemColor, + DisabledColor = itemColor * 0.8f }; - itemSlot.ToolTip = item.Name; + + if (item == prevHighlightedItem) + { + innerFrame.State = GUIComponent.ComponentState.Hover; + innerFrame.Children.ForEach(c => c.State = GUIComponent.ComponentState.Hover); + } } recommendedTreatmentContainer.RecalculateChildren(); @@ -1386,10 +1340,18 @@ namespace Barotrauma return dmgPerSecond != 0 ? dmgPerSecond : Math.Sign(second.Strength - first.Strength); }); - //afflictionIconContainer.Content.RectTransform.SortChildren((r1, r2) => - //{ - // return Math.Sign(((Affliction)r2.GUIComponent.UserData).GetVitalityDecrease(this) - ((Affliction)r1.GUIComponent.UserData).GetVitalityDecrease(this)); - //}); + if (count > 0) + { + var treatmentIconSize = recommendedTreatmentContainer.Content.Children.Sum(c => c.Rect.Width + recommendedTreatmentContainer.Spacing); + if (treatmentIconSize < recommendedTreatmentContainer.Content.Rect.Width) + { + var spacing = new GUIFrame(new RectTransform(new Point((recommendedTreatmentContainer.Content.Rect.Width - treatmentIconSize) / 2, 0), recommendedTreatmentContainer.Content.RectTransform), style: null) + { + CanBeFocused = false + }; + spacing.RectTransform.SetAsFirstChild(); + } + } } private void CreateAfflictionInfoElements(GUIComponent parent, Affliction affliction) @@ -1446,7 +1408,6 @@ namespace Barotrauma affliction.Strength / affliction.Prefab.MaxStrength); description.RectTransform.Resize(new Point(description.Rect.Width, (int)(description.TextSize.Y + 10))); - //labelContainer.Recalculate(); int vitalityDecrease = (int)affliction.GetVitalityDecrease(this); if (vitalityDecrease == 0) @@ -1466,21 +1427,7 @@ namespace Barotrauma private bool SelectAffliction(GUIButton button, object userData) { - Affliction affliction = button.Parent.UserData as Affliction; - bool selected = button.Selected; - - afflictionInfoContainer.UserData = null; - afflictionInfoContainer.ClearChildren(); - if (!selected) - { - afflictionInfoContainer.UserData = affliction; - - CreateAfflictionInfoElements(afflictionInfoContainer.Content, affliction); - - afflictionInfoContainer.RecalculateChildren(); - } - foreach (var child in afflictionIconContainer.Content.Children) { GUIButton btn = child.GetChild(); @@ -1493,137 +1440,52 @@ namespace Barotrauma return false; } - private void UpdateHeartrate(float deltaTime, GUICustomComponent component) - { - if (GameMain.Instance.Paused) { return; } - - heartbeatTimer -= deltaTime * 0.5f; - - if (heartbeatTimer <= 0.0f) - { - while (heartbeatTimer <= 0.0f) { heartbeatTimer += 0.5f; } - - IEnumerable newPositions; - if (Character == null || Character.IsDead || Character.IsUnconscious) - { - newPositions = Enumerable.Repeat(new HeartratePosition { Time = currentHeartrateTime, Height = 0.0f }, 1); - } - else - { - newPositions = HeartratePosition.ScaleAndDisplace(heartbeatPattern, 1.0f, 0.1f, currentHeartrateTime); - } - - float visibleRangeStart = currentHeartrateTime - 0.35f; - if (visibleRangeStart < 0.0f) - { - visibleRangeStart += 1.0f; - } - heartratePositions.RemoveAll(hp => (hp.Time < visibleRangeStart || hp.Time > currentHeartrateTime) && - ((hp.Time < visibleRangeStart && hp.Time > currentHeartrateTime) || visibleRangeStart < currentHeartrateTime)); - - heartratePositions.AddRange(newPositions); - - if (!heartratePositions.Any(hp => hp.Time >= 1.0f)) - { - heartratePositions.Add(new HeartratePosition { Time = 1.0f, Height = 0.0f }); - } - if (!heartratePositions.Any(hp => hp.Time <= 0.0f)) - { - heartratePositions.Add(new HeartratePosition { Time = 0.0f, Height = 0.0f }); - } - } - - currentHeartrateTime += deltaTime * 0.5f; - while (currentHeartrateTime >= 1.0f) - { - currentHeartrateTime -= 1.0f; - } - } - - private void DrawHeartrate(SpriteBatch spriteBatch, GUICustomComponent component) - { - Rectangle targetRect = component.Parent.Rect; - targetRect.Location += new Point(6, 6); - targetRect.Size -= new Point(12, 12); - - //GUI.DrawRectangle(spriteBatch, targetRect, Color.Black, true); - - bool first = true; - Vector2 prevPos = Vector2.Zero; - foreach (var heartratePosition in heartratePositions.OrderBy(hp => hp.Time)) - { - Vector2 pos = new Vector2(heartratePosition.Time, -heartratePosition.Height * 0.5f + 0.5f) * targetRect.Size.ToVector2() + targetRect.Location.ToVector2(); - - if (pos.X < targetRect.Left + 1) { pos.X = targetRect.Left + 1; } - if (pos.X > targetRect.Right - 1) { pos.X = targetRect.Right - 1; } - - if (first) - { - first = false; - } - else - { - int thickness = (int)(GUI.Scale * 2.5f); - if (thickness < 1) { thickness = 1; } - GUI.DrawLine(spriteBatch, prevPos, pos, Color.Lime, 0, thickness); - GUI.DrawLine(spriteBatch, prevPos + new Vector2(0.0f, 1.0f), pos + new Vector2(0.0f, 1.0f), Color.Lime * 0.5f, 0, thickness); - GUI.DrawLine(spriteBatch, prevPos - new Vector2(0.0f, 1.0f), pos - new Vector2(0.0f, 1.0f), Color.Lime * 0.5f, 0, thickness); - } - - prevPos = pos; - } - - Rectangle sourceRect = heartrateFade.Bounds; - - Rectangle destinationRectangle = new Rectangle( - new Point((int)(currentHeartrateTime * targetRect.Width) + targetRect.Left - targetRect.Height, targetRect.Top), - new Point((int)(targetRect.Height * ((float)sourceRect.Width / (float)sourceRect.Height)), targetRect.Height)); - - if (destinationRectangle.Left < targetRect.Left) - { - Rectangle destinationRectangle2 = new Rectangle(); - destinationRectangle2.Location = new Point(targetRect.Right - (targetRect.Left - destinationRectangle.Left), targetRect.Top); - destinationRectangle2.Size = new Point(targetRect.Right - destinationRectangle2.Left, targetRect.Height); - - int originalWidth = sourceRect.Width; - sourceRect.Width = (int)(sourceRect.Width * ((float)(destinationRectangle.Right - targetRect.Left) / (float)targetRect.Height)); - sourceRect.X += originalWidth - sourceRect.Width; - - Rectangle sourceRect2 = heartrateFade.Bounds; - sourceRect2.Width -= sourceRect.Width; - spriteBatch.Draw(heartrateFade, destinationRectangle2, sourceRect2, Color.White); - - originalWidth = destinationRectangle.Width; - int newWidth = destinationRectangle.Right - targetRect.Left; - - destinationRectangle.Size = new Point(newWidth, targetRect.Height); - destinationRectangle.X += originalWidth - newWidth; - - GUI.DrawRectangle(spriteBatch, new Rectangle(destinationRectangle.Right, destinationRectangle.Top, - destinationRectangle2.Left - destinationRectangle.Right, destinationRectangle2.Height), Color.Black, true); - } - else - { - GUI.DrawRectangle(spriteBatch, new Rectangle(destinationRectangle.Right, destinationRectangle.Top, - targetRect.Right - destinationRectangle.Right, destinationRectangle.Height), Color.Black, true); - GUI.DrawRectangle(spriteBatch, new Rectangle(targetRect.Left, destinationRectangle.Top, - destinationRectangle.Left - targetRect.Left, destinationRectangle.Height), Color.Black, true); - } - - spriteBatch.Draw(heartrateFade, destinationRectangle, sourceRect, Color.White); - } - private void UpdateAfflictionInfos(IEnumerable afflictions) { + var potentialTreatment = Inventory.DraggingItems.FirstOrDefault(); + if (potentialTreatment == null && GUI.MouseOn?.UserData is ItemPrefab itemPrefab) + { + potentialTreatment = Character.Controlled.Inventory.AllItems.FirstOrDefault(it => it.prefab == itemPrefab); + } + potentialTreatment ??= Inventory.SelectedSlot?.Item; + foreach (Affliction affliction in afflictions) { - var child = afflictionIconContainer.Content.FindChild(affliction); - var afflictionStrengthBar = child.GetChildByUserData("afflictionstrength") as GUIProgressBar; - afflictionStrengthBar.BarSize = affliction.Strength / affliction.Prefab.MaxStrength; - - if (afflictionInfoContainer.UserData == affliction) + float afflictionVitalityDecrease = affliction.GetVitalityDecrease(this); + Color afflictionEffectColor = Color.White; + if (afflictionVitalityDecrease > 0.0f) { - UpdateAfflictionInfo(afflictionInfoContainer.Content, affliction); + afflictionEffectColor = GUI.Style.Red; + } + else if (afflictionVitalityDecrease < 0.0f) + { + afflictionEffectColor = GUI.Style.Green; + } + + var child = afflictionIconContainer.Content.FindChild(affliction); + + var afflictionStrengthPredictionBar = child.GetChild().GetChildByUserData("afflictionstrengthprediction") as GUIProgressBar; + afflictionStrengthPredictionBar.BarSize = 0.0f; + var afflictionStrengthBar = afflictionStrengthPredictionBar.GetChildByUserData("afflictionstrength") as GUIProgressBar; + afflictionStrengthBar.BarSize = affliction.Strength / affliction.Prefab.MaxStrength; + afflictionStrengthBar.Color = afflictionEffectColor; + + float afflictionStrengthPrediction = GetAfflictionStrengthPrediction(potentialTreatment, affliction); + if (!MathUtils.NearlyEqual(afflictionStrengthPrediction, affliction.Strength)) + { + float t = (float)Math.Max(0.5f, (Math.Sin(Timing.TotalTime * 5) + 1.0f) / 2.0f); + if (afflictionStrengthPrediction < affliction.Strength) + { + afflictionStrengthBar.Color = afflictionEffectColor; + afflictionStrengthPredictionBar.Color = GUI.Style.Blue * t; + afflictionStrengthPredictionBar.BarSize = afflictionStrengthBar.BarSize; + afflictionStrengthBar.BarSize = afflictionStrengthPrediction / affliction.Prefab.MaxStrength; + } + else + { + afflictionStrengthPredictionBar.Color = Color.Red * t; + afflictionStrengthPredictionBar.BarSize = afflictionStrengthPrediction / affliction.Prefab.MaxStrength; + } } if (afflictionTooltip != null && afflictionTooltip.UserData == affliction) @@ -1633,6 +1495,32 @@ namespace Barotrauma } } + private float GetAfflictionStrengthPrediction(Item item, Affliction affliction) + { + float strength = affliction.Strength; + if (item == null) { return strength; } + + foreach (ItemComponent ic in item.Components) + { + if (ic.statusEffectLists == null) { continue; } + if (!ic.statusEffectLists.TryGetValue(ActionType.OnUse, out List statusEffects)) { continue; } + foreach (StatusEffect effect in statusEffects) + { + foreach (var reduceAffliction in effect.ReduceAffliction) + { + if (reduceAffliction.affliction != affliction.Identifier && reduceAffliction.affliction != affliction.Prefab.AfflictionType) { continue; } + strength -= reduceAffliction.amount * (effect.Duration > 0 ? effect.Duration : 1.0f); + } + foreach (var addAffliction in effect.Afflictions) + { + if (addAffliction.Prefab != affliction.Prefab) { continue; } + strength += addAffliction.Strength * (effect.Duration > 0 ? effect.Duration : 1.0f); + } + } + } + return strength; + } + private void UpdateAfflictionInfo(GUIComponent parent, Affliction affliction) { var labelContainer = parent.GetChildByUserData("label"); @@ -1664,8 +1552,7 @@ namespace Barotrauma { //items can be dropped outside the health window if (!ignoreMousePos && - !healthWindow.Rect.Contains(PlayerInput.MousePosition) && - !afflictionInfoFrame.Rect.Contains(PlayerInput.MousePosition)) + !healthWindow.Rect.Contains(PlayerInput.MousePosition) ) { return false; } @@ -1687,35 +1574,6 @@ namespace Barotrauma return true; } - - private List GetAvailableMedicalItems() - { - List allInventoryItems = new List(); - allInventoryItems.AddRange(Character.Inventory.AllItems); - if (Character.SelectedCharacter?.Inventory != null && Character.CanAccessInventory(Character.SelectedCharacter.Inventory)) - { - allInventoryItems.AddRange(Character.SelectedCharacter.Inventory.AllItems); - } - if (Character.SelectedBy?.Inventory != null) - { - allInventoryItems.AddRange(Character.SelectedBy.Inventory.AllItems); - } - List medicalItems = new List(); - foreach (Item item in allInventoryItems) - { - foreach (Item containedItem in item.ContainedItems) - { - if (!containedItem.HasTag("medical") && !containedItem.HasTag("chem")) { continue; } - medicalItems.Add(containedItem); - } - - if (!item.HasTag("medical") && !item.HasTag("chem")) { continue; } - medicalItems.Add(item); - } - - return medicalItems.Distinct().ToList(); - } - private void UpdateLimbIndicators(float deltaTime, Rectangle drawArea) { if (!GameMain.Instance.Paused) @@ -1743,10 +1601,6 @@ namespace Barotrauma if (PlayerInput.PrimaryMouseButtonClicked() && highlightedLimbIndex > -1) { selectedLimbIndex = highlightedLimbIndex; - //afflictionContainer.ClearChildren(); - afflictionIconContainer.ClearChildren(); - afflictionInfoContainer.ClearChildren(); - afflictionInfoContainer.UserData = null; } } @@ -1917,30 +1771,23 @@ namespace Barotrauma i++; } - if (selectedLimbIndex > -1 && selectedLimbText != null) + if (selectedLimbIndex > -1 && afflictionIconContainer.Content.CountChildren > 0) { LimbHealth limbHealth = limbHealths[selectedLimbIndex]; if (limbHealth?.IndicatorSprite != null) { Rectangle selectedLimbArea = GetLimbHighlightArea(limbHealth, drawArea); GUI.DrawLine(spriteBatch, - new Vector2(selectedLimbText.Rect.X, selectedLimbText.Rect.Center.Y), + new Vector2(afflictionIconContainer.Rect.X, afflictionIconContainer.Rect.Y), selectedLimbArea.Center.ToVector2(), Color.LightGray * 0.5f, width: 4); } } - - if (draggingMed != null) - { - GUIImage itemImage = draggingMed.GetChild(); - float scale = Math.Min(40.0f / itemImage.Sprite.size.X, 40.0f / itemImage.Sprite.size.Y); - itemImage.Sprite.Draw(spriteBatch, PlayerInput.MousePosition, itemImage.Color, 0, scale); - } } private void DrawLimbAfflictionIcon(SpriteBatch spriteBatch, Affliction affliction, float iconScale, ref Vector2 iconPos) { - if (!affliction.ShouldShowIcon(Character)) { return; } + if (!affliction.ShouldShowIcon(Character) || affliction.Prefab.Icon == null) { return; } Vector2 iconSize = affliction.Prefab.Icon.size * iconScale; float showIconThreshold = Character.Controlled?.CharacterHealth == this ? affliction.Prefab.ShowIconThreshold : affliction.Prefab.ShowIconToOthersThreshold; @@ -1968,9 +1815,9 @@ namespace Barotrauma healthBarHolder.Visible = value; } - private readonly List> newAfflictions = new List>(); - private readonly List> newLimbAfflictions = new List>(); - private readonly List> newPeriodicEffects = new List>(); + private readonly List<(AfflictionPrefab afflictionPrefab, float strength)> newAfflictions = new List<(AfflictionPrefab afflictionPrefab, float strength)>(); + private readonly List<(LimbHealth limb, AfflictionPrefab afflictionPrefab, float strength)> newLimbAfflictions = new List<(LimbHealth limb, AfflictionPrefab afflictionPrefab, float strength)>(); + private readonly List<(AfflictionPrefab.PeriodicEffect effect, float timer)> newPeriodicEffects = new List<(AfflictionPrefab.PeriodicEffect effect, float timer)>(); public void ClientRead(IReadMessage inc) { @@ -1997,41 +1844,41 @@ namespace Barotrauma for (int j = 0; j < periodicAfflictionCount; j++) { float periodicAfflictionTimer = inc.ReadRangedSingle(afflictionPrefab.PeriodicEffects[j].MinInterval, afflictionPrefab.PeriodicEffects[j].MaxInterval, 8); - newPeriodicEffects.Add(new Pair(afflictionPrefab.PeriodicEffects[j], periodicAfflictionTimer)); + newPeriodicEffects.Add((afflictionPrefab.PeriodicEffects[j], periodicAfflictionTimer)); } - newAfflictions.Add(new Pair(afflictionPrefab, afflictionStrength)); + newAfflictions.Add((afflictionPrefab, afflictionStrength)); } foreach (Affliction affliction in afflictions) { //deactivate afflictions that weren't included in the network message - if (!newAfflictions.Any(a => a.First == affliction.Prefab)) + if (!newAfflictions.Any(a => a.afflictionPrefab == affliction.Prefab)) { affliction.Strength = 0.0f; } } - foreach (Pair newAffliction in newAfflictions) + foreach (var (afflictionPrefab, strength) in newAfflictions) { - Affliction existingAffliction = afflictions.Find(a => a.Prefab == newAffliction.First); + Affliction existingAffliction = afflictions.Find(a => a.Prefab == afflictionPrefab); if (existingAffliction == null) { - existingAffliction = newAffliction.First.Instantiate(newAffliction.Second); + existingAffliction = afflictionPrefab.Instantiate(strength); afflictions.Add(existingAffliction); } - existingAffliction.SetStrength(newAffliction.Second); + existingAffliction.SetStrength(strength); if (existingAffliction == stunAffliction) { Character.SetStun(existingAffliction.Strength, true, true); } foreach (var periodicEffect in newPeriodicEffects) { - if (!existingAffliction.Prefab.PeriodicEffects.Contains(periodicEffect.First)) { continue; } + if (!existingAffliction.Prefab.PeriodicEffects.Contains(periodicEffect.effect)) { continue; } //timer has wrapped around, apply the effect - if (periodicEffect.Second - existingAffliction.PeriodicEffectTimers[periodicEffect.First] > periodicEffect.First.MinInterval / 2) + if (periodicEffect.timer - existingAffliction.PeriodicEffectTimers[periodicEffect.effect] > periodicEffect.effect.MinInterval / 2) { - existingAffliction.PeriodicEffectTimers[periodicEffect.First] = periodicEffect.Second; - foreach (StatusEffect effect in periodicEffect.First.StatusEffects) + existingAffliction.PeriodicEffectTimers[periodicEffect.effect] = periodicEffect.timer; + foreach (StatusEffect effect in periodicEffect.effect.StatusEffects) { existingAffliction.ApplyStatusEffect(ActionType.OnActive, effect, deltaTime: 1.0f, this, targetLimb: null); } @@ -2063,9 +1910,9 @@ namespace Barotrauma for (int j = 0; j < periodicAfflictionCount; j++) { float periodicAfflictionTimer = inc.ReadRangedSingle(afflictionPrefab.PeriodicEffects[j].MinInterval, afflictionPrefab.PeriodicEffects[j].MaxInterval, 8); - newPeriodicEffects.Add(new Pair(afflictionPrefab.PeriodicEffects[j], periodicAfflictionTimer)); + newPeriodicEffects.Add((afflictionPrefab.PeriodicEffects[j], periodicAfflictionTimer)); } - newLimbAfflictions.Add(new Triplet(limbHealths[limbIndex], afflictionPrefab, afflictionStrength)); + newLimbAfflictions.Add((limbHealths[limbIndex], afflictionPrefab, afflictionStrength)); } foreach (LimbHealth limbHealth in limbHealths) @@ -2073,33 +1920,33 @@ namespace Barotrauma foreach (Affliction affliction in limbHealth.Afflictions) { //deactivate afflictions that weren't included in the network message - if (!newLimbAfflictions.Any(a => a.First == limbHealth && a.Second == affliction.Prefab)) + if (!newLimbAfflictions.Any(a => a.limb == limbHealth && a.afflictionPrefab == affliction.Prefab)) { affliction.Strength = 0.0f; } } - foreach (Triplet newAffliction in newLimbAfflictions) + foreach (var (limb, afflictionPrefab, strength) in newLimbAfflictions) { - if (newAffliction.First != limbHealth) { continue; } - Affliction existingAffliction = limbHealth.Afflictions.Find(a => a.Prefab == newAffliction.Second); + if (limb != limbHealth) { continue; } + Affliction existingAffliction = limbHealth.Afflictions.Find(a => a.Prefab == afflictionPrefab); if (existingAffliction == null) { - existingAffliction = newAffliction.Second.Instantiate(newAffliction.Third); + existingAffliction = afflictionPrefab.Instantiate(strength); limbHealth.Afflictions.Add(existingAffliction); } - existingAffliction.SetStrength(newAffliction.Third); + existingAffliction.SetStrength(strength); foreach (var periodicEffect in newPeriodicEffects) { - if (!existingAffliction.Prefab.PeriodicEffects.Contains(periodicEffect.First)) { continue; } + if (!existingAffliction.Prefab.PeriodicEffects.Contains(periodicEffect.effect)) { continue; } //timer has wrapped around, apply the effect - if (periodicEffect.Second - existingAffliction.PeriodicEffectTimers[periodicEffect.First] > periodicEffect.First.MinInterval / 2) + if (periodicEffect.timer - existingAffliction.PeriodicEffectTimers[periodicEffect.effect] > periodicEffect.effect.MinInterval / 2) { - existingAffliction.PeriodicEffectTimers[periodicEffect.First] = periodicEffect.Second; - foreach (StatusEffect effect in periodicEffect.First.StatusEffects) + existingAffliction.PeriodicEffectTimers[periodicEffect.effect] = periodicEffect.timer; + foreach (StatusEffect effect in periodicEffect.effect.StatusEffects) { - Limb targetLimb = Character.AnimController.Limbs.FirstOrDefault(l => l.HealthIndex == limbHealths.IndexOf(newAffliction.First)); + Limb targetLimb = Character.AnimController.Limbs.FirstOrDefault(l => l.HealthIndex == limbHealths.IndexOf(limb)); existingAffliction.ApplyStatusEffect(ActionType.OnActive, effect, deltaTime: 1.0f, this, targetLimb: targetLimb); } } @@ -2122,8 +1969,8 @@ namespace Barotrauma if (limbHealths[limb.HealthIndex].Afflictions.Count == 0) continue; foreach (Affliction a in limbHealths[limb.HealthIndex].Afflictions) { - limb.BurnOverlayStrength += a.Strength / a.Prefab.MaxStrength * a.Prefab.BurnOverlayAlpha; - limb.DamageOverlayStrength += a.Strength / a.Prefab.MaxStrength * a.Prefab.DamageOverlayAlpha; + limb.BurnOverlayStrength += a.Strength / Math.Min(a.Prefab.MaxStrength, 100) * a.Prefab.BurnOverlayAlpha; + limb.DamageOverlayStrength += a.Strength / Math.Min(a.Prefab.MaxStrength, 100) * a.Prefab.DamageOverlayAlpha; } limb.BurnOverlayStrength /= limbHealths[limb.HealthIndex].Afflictions.Count; limb.DamageOverlayStrength /= limbHealths[limb.HealthIndex].Afflictions.Count; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Limb.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Limb.cs index f4c76ac1f..2e544f692 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Limb.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Limb.cs @@ -109,6 +109,7 @@ namespace Barotrauma private float wetTimer; private float dripParticleTimer; private float deadTimer; + private Color? randomColor; /// /// Note that different limbs can share the same deformations. @@ -120,6 +121,15 @@ namespace Barotrauma public List ActiveDeformations { get; set; } = new List(); public Sprite Sprite { get; protected set; } + public Sprite TintMask { get; protected set; } + + public Sprite HuskMask { get; protected set; } + public float TintHighlightThreshold { get; protected set; } + public float TintHighlightMultiplier { get; protected set; } + + private SpriteBatch.EffectWithParams tintEffectParams; + private SpriteBatch.EffectWithParams huskSpriteParams; + protected DeformableSprite _deformSprite; @@ -157,6 +167,12 @@ namespace Barotrauma } } + public Sprite GetActiveSprite(bool excludeConditionalSprites = true) + => excludeConditionalSprites ? (_deformSprite != null ? _deformSprite.Sprite : Sprite) + : ActiveSprite; + + public float DefaultSpriteDepth { get; private set; } + public WearableSprite HuskSprite { get; private set; } public WearableSprite HerpesSprite { get; private set; } @@ -273,6 +289,7 @@ namespace Barotrauma DecorativeSpriteGroups[groupID].Add(decorativeSprite); spriteAnimState.Add(decorativeSprite, new SpriteState()); } + TintMask = null; foreach (XElement subElement in element.Elements()) { switch (subElement.Name.ToString().ToLowerInvariant()) @@ -299,15 +316,42 @@ namespace Barotrauma Deformations.AddRange(deformations); NonConditionalDeformations.AddRange(deformations); break; + case "randomcolor": + randomColor = subElement.GetAttributeColorArray("colors", null)?.GetRandom(); + if (randomColor.HasValue) + { + Params.GetSprite().Color = randomColor.Value; + } + break; case "lightsource": LightSource = new LightSource(subElement, GetConditionalTarget()) { ParentBody = body, SpriteScale = Vector2.One * Scale * TextureScale }; + if (randomColor.HasValue) + { + LightSource.Color = new Color(randomColor.Value.R, randomColor.Value.G, randomColor.Value.B, LightSource.Color.A); + } InitialLightSourceColor = LightSource.Color; InitialLightSpriteAlpha = LightSource.OverrideLightSpriteAlpha; break; + case "tintmask": + string tintMaskPath = subElement.GetAttributeString("texture", ""); + if (!string.IsNullOrWhiteSpace(tintMaskPath)) + { + TintMask = new Sprite(subElement, file: GetSpritePath(tintMaskPath)); + TintHighlightThreshold = subElement.GetAttributeFloat("highlightthreshold", 0.6f); + TintHighlightMultiplier = subElement.GetAttributeFloat("highlightmultiplier", 0.8f); + } + break; + case "huskmask": + string huskMaskPath = subElement.GetAttributeString("texture", ""); + if (!string.IsNullOrWhiteSpace(huskMaskPath)) + { + HuskMask = new Sprite(subElement, file: GetSpritePath(huskMaskPath)); + } + break; } ISerializableEntity GetConditionalTarget() @@ -357,6 +401,7 @@ namespace Barotrauma return deformations; } } + DefaultSpriteDepth = GetActiveSprite()?.Depth ?? 0.0f; LightSource?.CheckConditionals(); } @@ -449,20 +494,20 @@ namespace Barotrauma /// /// Get the full path of a limb sprite, taking into account tags, gender and head id /// - private string GetSpritePath(string texturePath) + public static string GetSpritePath(string texturePath, CharacterInfo characterInfo) { string spritePath = texturePath; string spritePathWithTags = spritePath; - if (character.Info != null && character.IsHumanoid) + if (characterInfo != null) { - spritePath = spritePath.Replace("[GENDER]", (character.Info.Gender == Gender.Female) ? "female" : "male"); - spritePath = spritePath.Replace("[RACE]", character.Info.Race.ToString().ToLowerInvariant()); - spritePath = spritePath.Replace("[HEADID]", character.Info.HeadSpriteId.ToString()); + spritePath = spritePath.Replace("[GENDER]", (characterInfo.Gender == Gender.Female) ? "female" : "male"); + spritePath = spritePath.Replace("[RACE]", characterInfo.Race.ToString().ToLowerInvariant()); + spritePath = spritePath.Replace("[HEADID]", characterInfo.HeadSpriteId.ToString()); - if (character.Info.HeadSprite != null && character.Info.SpriteTags.Any()) + if (characterInfo.HeadSprite != null && characterInfo.SpriteTags.Any()) { string tags = ""; - character.Info.SpriteTags.ForEach(tag => tags += "[" + tag + "]"); + characterInfo.SpriteTags.ForEach(tag => tags += "[" + tag + "]"); spritePathWithTags = Path.Combine( Path.GetDirectoryName(spritePath), @@ -472,6 +517,13 @@ namespace Barotrauma return File.Exists(spritePathWithTags) ? spritePathWithTags : spritePath; } + + private string GetSpritePath(string texturePath) + { + if (!character.IsHumanoid) { return texturePath; } + return GetSpritePath(texturePath, character?.Info); + } + partial void LoadParamsProjSpecific() { bool isFlipped = dir == Direction.Left; @@ -538,8 +590,8 @@ namespace Barotrauma { foreach (ParticleEmitter emitter in character.DamageEmitters) { - if (inWater && emitter.Prefab.ParticlePrefab.DrawTarget == ParticlePrefab.DrawTargetType.Air) { continue; } - if (!inWater && emitter.Prefab.ParticlePrefab.DrawTarget == ParticlePrefab.DrawTargetType.Water) { continue; } + if (InWater && emitter.Prefab.ParticlePrefab.DrawTarget == ParticlePrefab.DrawTargetType.Air) { continue; } + if (!InWater && emitter.Prefab.ParticlePrefab.DrawTarget == ParticlePrefab.DrawTargetType.Water) { continue; } ParticlePrefab overrideParticle = null; foreach (DamageModifier damageModifier in result.AppliedDamageModifiers) { @@ -560,8 +612,8 @@ namespace Barotrauma foreach (ParticleEmitter emitter in character.BloodEmitters) { - if (inWater && emitter.Prefab.ParticlePrefab.DrawTarget == ParticlePrefab.DrawTargetType.Air) { continue; } - if (!inWater && emitter.Prefab.ParticlePrefab.DrawTarget == ParticlePrefab.DrawTargetType.Water) { continue; } + if (InWater && emitter.Prefab.ParticlePrefab.DrawTarget == ParticlePrefab.DrawTargetType.Air) { continue; } + if (!InWater && emitter.Prefab.ParticlePrefab.DrawTarget == ParticlePrefab.DrawTargetType.Water) { continue; } emitter.Emit(1.0f, WorldPosition, character.CurrentHull, sizeMultiplier: bloodParticleSize, amountMultiplier: bloodParticleAmount); } } @@ -585,7 +637,7 @@ namespace Barotrauma } } - if (inWater) + if (InWater) { wetTimer = 1.0f; } @@ -632,19 +684,38 @@ namespace Barotrauma RefreshDeformations(); } - public void Draw(SpriteBatch spriteBatch, Camera cam, Color? overrideColor = null) + public void Draw(SpriteBatch spriteBatch, Camera cam, Color? overrideColor = null, bool disableDeformations = false) { float brightness = 1.0f - (burnOverLayStrength / 100.0f) * 0.5f; var spriteParams = Params.GetSprite(); if (spriteParams == null) { return; } - Color color = new Color(spriteParams.Color.R / 255f * brightness, spriteParams.Color.G / 255f * brightness, spriteParams.Color.B / 255f * brightness, spriteParams.Color.A / 255f); + Color clr = spriteParams.Color; + if (!spriteParams.IgnoreTint) + { + clr = clr.Multiply(ragdoll.RagdollParams.Color); + if (character.Info != null) + { + clr = clr.Multiply(character.Info.SkinColor); + } + if (character.CharacterHealth.FaceTint.A > 0 && type == LimbType.Head) + { + clr = Color.Lerp(clr, character.CharacterHealth.FaceTint.Opaque(), character.CharacterHealth.FaceTint.A / 255.0f); + } + if (character.CharacterHealth.BodyTint.A > 0) + { + clr = Color.Lerp(clr, character.CharacterHealth.BodyTint.Opaque(), character.CharacterHealth.BodyTint.A / 255.0f); + } + } + Color color = new Color((byte)(clr.R * brightness), (byte)(clr.G * brightness), (byte)(clr.B * brightness), clr.A); + Color blankColor = new Color(brightness, brightness, brightness, 1); if (deadTimer > 0) { color = Color.Lerp(color, spriteParams.DeadColor, MathUtils.InverseLerp(0, spriteParams.DeadColorTime, deadTimer)); } color = overrideColor ?? color; + blankColor = overrideColor ?? blankColor; if (isSevered) { @@ -667,6 +738,8 @@ namespace Barotrauma OtherWearables.Any(w => w.HideLimb) || wearingItems.Any(w => w != null && w.HideLimb); + bool drawHuskSprite = HuskSprite != null && !wearableTypesToHide.Contains(WearableType.Husk); + var activeSprite = ActiveSprite; if (type == LimbType.Head) { @@ -674,11 +747,12 @@ namespace Barotrauma } body.UpdateDrawPosition(); + float depthStep = 0.000001f; if (!hideLimb) { var deformSprite = DeformSprite; - if (deformSprite != null) + if (deformSprite != null && !disableDeformations) { if (ActiveDeformations.Any()) { @@ -698,7 +772,33 @@ namespace Barotrauma } else { + bool useTintMask = TintMask != null && spriteBatch.GetCurrentEffect() is null; + if (useTintMask) + { + tintEffectParams.Effect ??= GameMain.GameScreen.ThresholdTintEffect; + tintEffectParams.Params ??= new Dictionary(); + var parameters = tintEffectParams.Params; + parameters["xBaseTexture"] = Sprite.Texture; + parameters["xTintMaskTexture"] = TintMask.Texture; + if (drawHuskSprite && HuskMask != null) + { + parameters["xCutoffTexture"] = HuskMask.Texture; + parameters["baseToCutoffSizeRatio"] = (float)Sprite.Texture.Width / (float)HuskMask.Texture.Width; + } + else + { + parameters["xCutoffTexture"] = GUI.WhiteTexture; + parameters["baseToCutoffSizeRatio"] = 1.0f; + } + parameters["highlightThreshold"] = TintHighlightThreshold; + parameters["highlightMultiplier"] = TintHighlightMultiplier; + spriteBatch.SwapEffect(tintEffectParams); + } body.Draw(spriteBatch, activeSprite, color, null, Scale * TextureScale, Params.MirrorHorizontally, Params.MirrorVertically); + if (useTintMask) + { + spriteBatch.SwapEffect(null); + } } // Handle non-exlusive, i.e. additional conditional sprites foreach (var conditionalSprite in ConditionalSprites) @@ -722,7 +822,7 @@ namespace Barotrauma } else { - body.Draw(spriteBatch, conditionalSprite.Sprite, color, null, Scale * TextureScale, Params.MirrorHorizontally, Params.MirrorVertically); + body.Draw(spriteBatch, conditionalSprite.Sprite, color, depth: activeSprite.Depth - (depthStep * 50), Scale * TextureScale, Params.MirrorHorizontally, Params.MirrorVertically); } } } @@ -737,7 +837,7 @@ namespace Barotrauma new Vector2(body.DrawPosition.X, -body.DrawPosition.Y), color * Math.Min(damageOverlayStrength, 1.0f), activeSprite.Origin, -body.DrawRotation, - Scale, spriteEffect, activeSprite.Depth - 0.0000015f); + Scale, spriteEffect, activeSprite.Depth - (depthStep * 90)); } foreach (var decorativeSprite in DecorativeSprites) { @@ -755,9 +855,8 @@ namespace Barotrauma 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, - depth: decorativeSprite.Sprite.Depth); + depth: activeSprite.Depth - (depthStep * 100)); } - float depthStep = 0.000001f; float step = depthStep; WearableSprite onlyDrawable = wearingItems.Find(w => w.HideOtherWearables); if (Params.MirrorHorizontally) @@ -770,23 +869,44 @@ namespace Barotrauma } if (onlyDrawable == null) { - if (HerpesSprite != null && !wearableTypesToHide.Contains(WearableType.Herpes)) + if (HerpesSprite != null && !wearableTypesToHide.Contains(WearableType.Herpes) && herpesStrength > 0) { - DrawWearable(HerpesSprite, depthStep, spriteBatch, color * Math.Min(herpesStrength / 10.0f, 1.0f), spriteEffect); + float alpha = Math.Min(herpesStrength * 2 / 100.0f, 1.0f); + DrawWearable(HerpesSprite, depthStep, spriteBatch, blankColor, alpha: alpha, spriteEffect); + depthStep += step; + } + if (drawHuskSprite) + { + bool useTintEffect = HuskMask != null && spriteBatch.GetCurrentEffect() is null; + if (useTintEffect) + { + huskSpriteParams.Effect ??= GameMain.GameScreen.ThresholdTintEffect; + huskSpriteParams.Params ??= new Dictionary(); + var parameters = huskSpriteParams.Params; + parameters["xCutoffTexture"] = GUI.WhiteTexture; + parameters["baseToCutoffSizeRatio"] = 1.0f; + spriteBatch.SwapEffect(huskSpriteParams); + } + DrawWearable(HuskSprite, depthStep, spriteBatch, color, alpha: color.A / 255f, spriteEffect); + if (useTintEffect) + { + spriteBatch.SwapEffect(null); + } depthStep += step; } foreach (WearableSprite wearable in OtherWearables) { + if (wearable.Type == WearableType.Husk) { continue; } if (wearableTypesToHide.Contains(wearable.Type)) { continue; } - DrawWearable(wearable, depthStep, spriteBatch, color, spriteEffect); + 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) { - if (onlyDrawable != null && onlyDrawable != wearable) continue; - DrawWearable(wearable, depthStep, spriteBatch, color, spriteEffect); + if (onlyDrawable != null && onlyDrawable != wearable && wearable.CanBeHiddenByOtherWearables) { 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; } @@ -936,7 +1056,7 @@ namespace Barotrauma } } - private void DrawWearable(WearableSprite wearable, float depthStep, SpriteBatch spriteBatch, Color color, SpriteEffects spriteEffect) + private void DrawWearable(WearableSprite wearable, float depthStep, SpriteBatch spriteBatch, Color color, float alpha, SpriteEffects spriteEffect) { var sprite = ActiveSprite; if (wearable.InheritSourceRect) @@ -955,7 +1075,7 @@ namespace Barotrauma } } - Vector2 origin = wearable.Sprite.Origin; + Vector2 origin; if (wearable.InheritOrigin) { origin = sprite.Origin; @@ -986,24 +1106,49 @@ namespace Barotrauma Color wearableColor = Color.White; if (wearableItemComponent != null) { - // Draw outer cloths on top of inner cloths. + // Draw outer clothes on top of inner clothes. if (wearableItemComponent.AllowedSlots.Contains(InvSlotType.OuterClothes)) { depth -= depthStep; } if (wearableItemComponent.AllowedSlots.Contains(InvSlotType.Bag)) { - depth -= depthStep * 2; + depth -= depthStep * 4; } wearableColor = wearableItemComponent.Item.GetSpriteColor(); } - float textureScale = wearable.InheritTextureScale ? TextureScale : wearable.Scale; - - wearable.Sprite.Draw(spriteBatch, - new Vector2(body.DrawPosition.X, -body.DrawPosition.Y), - new Color((color.R * wearableColor.R) / (255.0f * 255.0f), (color.G * wearableColor.G) / (255.0f * 255.0f), (color.B * wearableColor.B) / (255.0f * 255.0f)) * ((color.A * wearableColor.A) / (255.0f * 255.0f)), - origin, -body.DrawRotation, - Scale * textureScale, spriteEffect, depth); + else if (character.Info != null) + { + if (wearable.Type == WearableType.Hair) + { + wearableColor = character.Info.HairColor; + } + else if (wearable.Type == WearableType.Beard || wearable.Type == WearableType.Moustache) + { + wearableColor = character.Info.FacialHairColor; + } + } + float scale = wearable.Scale; + if (wearable.InheritScale) + { + if (!wearable.IgnoreTextureScale) + { + scale *= TextureScale; + } + if (!wearable.IgnoreLimbScale) + { + scale *= Params.Scale; + } + if (!wearable.IgnoreRagdollScale) + { + scale *= ragdoll.RagdollParams.LimbScale; + } + } + float rotation = -body.DrawRotation - wearable.Rotation * Dir; + float finalAlpha = alpha * wearableColor.A; + Color finalColor = color.Multiply(wearableColor); + finalColor = new Color(finalColor.R, finalColor.G, finalColor.B, (byte)finalAlpha); + wearable.Sprite.Draw(spriteBatch, new Vector2(body.DrawPosition.X, -body.DrawPosition.Y), finalColor, origin, rotation, scale, spriteEffect, depth); } private WearableSprite GetWearableSprite(WearableType type, bool random = false) @@ -1054,6 +1199,9 @@ namespace Barotrauma HerpesSprite?.Sprite.Remove(); HerpesSprite = null; + + TintMask?.Remove(); + TintMask = null; } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs b/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs index 89805c561..0bb2d3ce3 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs @@ -418,14 +418,14 @@ namespace Barotrauma ShowQuestionPrompt("The automatic hull generation may not work correctly if your submarine uses curved walls. Do you want to continue? Y/N", (option2) => { - if (option2.ToLower() == "y") { GameMain.SubEditorScreen.AutoHull(); } + if (option2.ToLowerInvariant() == "y") { GameMain.SubEditorScreen.AutoHull(); } }); }); } else { ShowQuestionPrompt("The automatic hull generation may not work correctly if your submarine uses curved walls. Do you want to continue? Y/N", - (option) => { if (option.ToLower() == "y") GameMain.SubEditorScreen.AutoHull(); }); + (option) => { if (option.ToLowerInvariant() == "y") GameMain.SubEditorScreen.AutoHull(); }); } })); @@ -476,6 +476,7 @@ namespace Barotrauma if (Screen.Selected == GameMain.SubEditorScreen) { NewMessage("WARNING: Switching directly from the submarine editor to the game view may cause bugs and crashes. Use with caution.", Color.Orange); + Entity.Spawner ??= new EntitySpawner(); } GameMain.GameScreen.Select(); })); @@ -488,6 +489,8 @@ namespace Barotrauma Submarine.MainSub = Submarine.Load(subInfo, true); } GameMain.SubEditorScreen.Select(enableAutoSave: Screen.Selected != GameMain.GameScreen); + Entity.Spawner?.Remove(); + Entity.Spawner = null; }, isCheat: true)); commands.Add(new Command("editparticles|particleeditor", "editparticles/particleeditor: Switch to the Particle Editor to edit particle effects.", (string[] args) => @@ -608,7 +611,7 @@ namespace Barotrauma ShowQuestionPrompt($"Some keybinds may render the game unusable, are you sure you want to make these keybinds persistent? ({Keybinds.Count} keybind(s) assigned) Y/N", (option2) => { - if (option2.ToLower() != "y") + if (option2.ToLowerInvariant() != "y") { NewMessage("Aborted.", GUI.Style.Red); return; @@ -689,9 +692,18 @@ namespace Barotrauma AssignRelayToServer("setskill", true); AssignRelayToServer("readycheck", true); + AssignRelayToServer("givetalent", true); + AssignRelayToServer("unlocktalents", true); + AssignRelayToServer("giveexperience", true); + AssignOnExecute("control", (string[] args) => { - if (args.Length < 1) return; + if (args.Length < 1) { return; } + if (GameMain.NetworkMember != null) + { + GameMain.Client?.SendConsoleCommand("control " + string.Join(' ', args[0])); + return; + } var character = FindMatchingCharacter(args, true); if (character != null) { @@ -1096,9 +1108,35 @@ namespace Barotrauma commands.Add(new Command("load|loadsub", "load [submarine name]: Load a submarine.", (string[] args) => { - if (args.Length == 0) return; - SubmarineInfo subInfo = new SubmarineInfo(string.Join(" ", args)); + if (args.Length == 0) { return; } + + if (GameMain.GameSession != null) + { + ThrowError("The loadsub command cannot be used when a round is running. You should probably be using spawnsub instead."); + return; + } + + string name = string.Join(" ", args); + SubmarineInfo subInfo = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => name.Equals(s.Name, StringComparison.OrdinalIgnoreCase)); + if (subInfo == null) + { + string path = Path.Combine(SubmarineInfo.SavePath, name); + if (!File.Exists(path)) + { + ThrowError($"Could not find a submarine with the name \"{name}\" or in the path {path}."); + return; + } + subInfo = new SubmarineInfo(path); + } + Submarine.Load(subInfo, true); + }, + () => + { + return new string[][] + { + SubmarineInfo.SavedSubmarines.Select(s => s.Name).ToArray() + }; })); commands.Add(new Command("cleansub", "", (string[] args) => @@ -1317,11 +1355,13 @@ namespace Barotrauma continue; } + float avgOutCondition = (deconstructItem.OutConditionMin + deconstructItem.OutConditionMax) / 2; + int? deconstructProductPrice = targetItem.GetMinPrice(); if (deconstructProductPrice.HasValue) { if (!deconstructProductCost.HasValue) { deconstructProductCost = 0; } - deconstructProductCost += (int)(deconstructProductPrice * deconstructItem.OutCondition); + deconstructProductCost += (int)(deconstructProductPrice * avgOutCondition); } if (fabricationRecipe != null) @@ -1331,9 +1371,9 @@ namespace Barotrauma { NewMessage("Deconstructing \"" + itemPrefab.Name + "\" produces \"" + deconstructItem.ItemIdentifier + "\", which isn't required in the fabrication recipe of the item.", Color.Red); } - else if (ingredient.UseCondition && ingredient.MinCondition < deconstructItem.OutCondition) + else if (ingredient.UseCondition && ingredient.MinCondition < avgOutCondition) { - NewMessage($"Deconstructing \"{itemPrefab.Name}\" produces more \"{deconstructItem.ItemIdentifier}\", than what's required to fabricate the item (required: {targetItem.Name} {(int)(ingredient.MinCondition * 100)}%, output: {deconstructItem.ItemIdentifier} {(int)(deconstructItem.OutCondition * 100)}%)", Color.Red); + NewMessage($"Deconstructing \"{itemPrefab.Name}\" produces more \"{deconstructItem.ItemIdentifier}\", than what's required to fabricate the item (required: {targetItem.Name} {(int)(ingredient.MinCondition * 100)}%, output: {deconstructItem.ItemIdentifier} {(int)(avgOutCondition * 100)}%)", Color.Red); } } } @@ -1692,13 +1732,14 @@ namespace Barotrauma //check missing mission texts foreach (var missionPrefab in MissionPrefab.List) { - string nameIdentifier = "missionname." + missionPrefab.Identifier; + string missionId = (missionPrefab.ConfigElement.Attribute("textidentifier") == null ? missionPrefab.Identifier : missionPrefab.ConfigElement.GetAttributeString("textidentifier", string.Empty)); + string nameIdentifier = "missionname." + missionId; if (!tags[language].Contains(nameIdentifier)) { if (!missingTags.ContainsKey(nameIdentifier)) { missingTags[nameIdentifier] = new HashSet(); } missingTags[nameIdentifier].Add(language); } - string descriptionIdentifier = "missiondescription." + missionPrefab.Identifier; + string descriptionIdentifier = "missiondescription." + missionId; if (!tags[language].Contains(descriptionIdentifier)) { if (!missingTags.ContainsKey(descriptionIdentifier)) { missingTags[descriptionIdentifier] = new HashSet(); } @@ -1708,6 +1749,7 @@ namespace Barotrauma foreach (SubmarineInfo sub in SubmarineInfo.SavedSubmarines) { + if (sub.Type != SubmarineType.Player) { continue; } string nameIdentifier = "submarine.name." + sub.Name.ToLowerInvariant(); if (!tags[language].Contains(nameIdentifier)) { @@ -1724,14 +1766,23 @@ namespace Barotrauma foreach (AfflictionPrefab affliction in AfflictionPrefab.List) { - string nameIdentifier = "afflictionname." + affliction.Identifier; + if (affliction.ShowIconThreshold > affliction.MaxStrength && + affliction.ShowIconToOthersThreshold > affliction.MaxStrength && + affliction.ShowInHealthScannerThreshold > affliction.MaxStrength) + { + //hidden affliction, no need for localization + continue; + } + + string afflictionId = affliction.TranslationOverride ?? affliction.Identifier; + string nameIdentifier = "afflictionname." + afflictionId; if (!tags[language].Contains(nameIdentifier)) { if (!missingTags.ContainsKey(nameIdentifier)) { missingTags[nameIdentifier] = new HashSet(); } missingTags[nameIdentifier].Add(language); } - string descriptionIdentifier = "afflictiondescription." + affliction.Identifier; + string descriptionIdentifier = "afflictiondescription." + afflictionId; if (!tags[language].Contains(descriptionIdentifier)) { if (!missingTags.ContainsKey(descriptionIdentifier)) { missingTags[descriptionIdentifier] = new HashSet(); } @@ -1739,6 +1790,29 @@ namespace Barotrauma } } + foreach (var talentTree in TalentTree.JobTalentTrees) + { + foreach (var talentSubTree in talentTree.Value.TalentSubTrees) + { + string nameIdentifier = "talenttree." + talentSubTree.Identifier; + if (!tags[language].Contains(nameIdentifier)) + { + if (!missingTags.ContainsKey(nameIdentifier)) { missingTags[nameIdentifier] = new HashSet(); } + missingTags[nameIdentifier].Add(language); + } + } + } + + foreach (var talent in TalentPrefab.TalentPrefabs) + { + string nameIdentifier = "talentname." + talent.Identifier; + if (!tags[language].Contains(nameIdentifier)) + { + if (!missingTags.ContainsKey(nameIdentifier)) { missingTags[nameIdentifier] = new HashSet(); } + missingTags[nameIdentifier].Add(language); + } + } + //check missing entity names foreach (MapEntityPrefab me in MapEntityPrefab.List) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/AbandonedOutpostMission.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/AbandonedOutpostMission.cs index 55191b4b4..d5161fc4d 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/AbandonedOutpostMission.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/AbandonedOutpostMission.cs @@ -22,6 +22,7 @@ namespace Barotrauma public override void ClientReadInitial(IReadMessage msg) { + base.ClientReadInitial(msg); ushort targetItemCount = msg.ReadUInt16(); for (int i = 0; i < targetItemCount; i++) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/AlienRuinMission.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/AlienRuinMission.cs new file mode 100644 index 000000000..b943081b2 --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/AlienRuinMission.cs @@ -0,0 +1,32 @@ +using Barotrauma.Networking; + +namespace Barotrauma +{ + partial class AlienRuinMission : Mission + { + public override void ClientReadInitial(IReadMessage msg) + { + base.ClientReadInitial(msg); + existingTargets.Clear(); + spawnedTargets.Clear(); + allTargets.Clear(); + ushort existingTargetsCount = msg.ReadUInt16(); + for (int i = 0; i < existingTargetsCount; i++) + { + ushort targetId = msg.ReadUInt16(); + if (targetId == Entity.NullEntityID) { continue; } + Entity target = Entity.FindEntityByID(targetId); + if (target == null) { continue; } + existingTargets.Add(target); + allTargets.Add(target); + } + ushort spawnedTargetsCount = msg.ReadUInt16(); + for (int i = 0; i < spawnedTargetsCount; i++) + { + var enemy = Character.ReadSpawnData(msg); + existingTargets.Add(enemy); + allTargets.Add(enemy); + } + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/BeaconMission.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/BeaconMission.cs deleted file mode 100644 index 584e13d3e..000000000 --- a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/BeaconMission.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Barotrauma.Networking; - -namespace Barotrauma -{ - partial class BeaconMission : Mission - { - public override void ClientReadInitial(IReadMessage msg) - { - return; - } - } -} diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/CargoMission.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/CargoMission.cs index 97bcbc4d5..23b228922 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/CargoMission.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/CargoMission.cs @@ -25,6 +25,7 @@ namespace Barotrauma } public override void ClientReadInitial(IReadMessage msg) { + base.ClientReadInitial(msg); items.Clear(); ushort itemCount = msg.ReadUInt16(); for (int i = 0; i < itemCount; i++) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/CombatMission.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/CombatMission.cs index fc8cafcd4..9fb2b490b 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/CombatMission.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/CombatMission.cs @@ -20,10 +20,5 @@ namespace Barotrauma return descriptions[GameMain.Client.Character.TeamID == CharacterTeamType.Team1 ? 1 : 2]; } } - - public override void ClientReadInitial(IReadMessage msg) - { - //do nothing - } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/EscortMission.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/EscortMission.cs index 5fbdc15a5..230570e5e 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/EscortMission.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/EscortMission.cs @@ -6,6 +6,8 @@ namespace Barotrauma { public override void ClientReadInitial(IReadMessage msg) { + base.ClientReadInitial(msg); + byte characterCount = msg.ReadByte(); for (int i = 0; i < characterCount; i++) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/MineralMission.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/MineralMission.cs index 61e23e9a5..3ce80fd05 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/MineralMission.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/MineralMission.cs @@ -12,6 +12,7 @@ namespace Barotrauma { public override void ClientReadInitial(IReadMessage msg) { + base.ClientReadInitial(msg); byte caveCount = msg.ReadByte(); for (int i = 0; i < caveCount; i++) { @@ -29,7 +30,7 @@ namespace Barotrauma } } - for (int i = 0; i < ResourceClusters.Count; i++) + for (int i = 0; i < resourceClusters.Count; i++) { var amount = msg.ReadByte(); var rotation = msg.ReadSingle(); @@ -41,20 +42,20 @@ namespace Barotrauma h.AttachToWall(); item.Rotation = rotation; } - if (SpawnedResources.TryGetValue(item.Prefab.Identifier, out var resources)) + if (spawnedResources.TryGetValue(item.Prefab.Identifier, out var resources)) { resources.Add(item); } else { - SpawnedResources.Add(item.Prefab.Identifier, new List() { item }); + spawnedResources.Add(item.Prefab.Identifier, new List() { item }); } } } CalculateMissionClusterPositions(); - for(int i = 0; i < ResourceClusters.Count; i++) + for(int i = 0; i < resourceClusters.Count; i++) { var identifier = msg.ReadString(); var count = msg.ReadByte(); @@ -66,7 +67,7 @@ namespace Barotrauma if (!(entity is Item item)) { continue; } resources[j] = item; } - RelevantLevelResources.Add(identifier, resources); + relevantLevelResources.Add(identifier, resources); } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/Mission.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/Mission.cs index 92d36b5fe..e8b3064d4 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/Mission.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/Mission.cs @@ -3,6 +3,7 @@ using Microsoft.Xna.Framework; using System; using System.Collections.Generic; using System.Globalization; +using System.Linq; namespace Barotrauma { @@ -14,6 +15,10 @@ namespace Barotrauma get { return shownMessages; } } + public bool DisplayTargetHudIcons => Prefab.DisplayTargetHudIcons; + + public virtual IEnumerable HudIconTargets => Enumerable.Empty(); + public Color GetDifficultyColor() { int v = Difficulty ?? MissionPrefab.MinDifficulty; @@ -92,11 +97,14 @@ namespace Barotrauma }; } - public void ClientRead(IReadMessage msg) + public virtual void ClientRead(IReadMessage msg) { State = msg.ReadInt16(); } - public abstract void ClientReadInitial(IReadMessage msg); + public virtual void ClientReadInitial(IReadMessage msg) + { + state = msg.ReadInt16(); + } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/MissionPrefab.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/MissionPrefab.cs index 5bc64d50f..e3c6f8633 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/MissionPrefab.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/MissionPrefab.cs @@ -18,13 +18,54 @@ namespace Barotrauma private set; } + public bool DisplayTargetHudIcons + { + get; + private set; + } + + public float HudIconMaxDistance + { + get; + private set; + } + + public Sprite HudIcon + { + get + { + return hudIcon ?? Icon; + } + } + + public Color HudIconColor + { + get + { + return hudIconColor ?? IconColor; + } + } + + private Sprite hudIcon; + private Color? hudIconColor; + partial void InitProjSpecific(XElement element) { + DisplayTargetHudIcons = element.GetAttributeBool("displaytargethudicons", false); + HudIconMaxDistance = element.GetAttributeFloat("hudiconmaxdistance", 1000.0f); foreach (XElement subElement in element.Elements()) { - if (!subElement.Name.ToString().Equals("icon", StringComparison.OrdinalIgnoreCase)) { continue; } - Icon = new Sprite(subElement); - IconColor = subElement.GetAttributeColor("color", Color.White); + string name = subElement.Name.ToString(); + if (name.Equals("icon", StringComparison.OrdinalIgnoreCase)) + { + Icon = new Sprite(subElement); + IconColor = subElement.GetAttributeColor("color", Color.White); + } + else if (name.Equals("hudicon", StringComparison.OrdinalIgnoreCase)) + { + hudIcon = new Sprite(subElement); + hudIconColor = subElement.GetAttributeColor("color"); + } } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/MonsterMission.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/MonsterMission.cs index 162968040..e5aab9f41 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/MonsterMission.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/MonsterMission.cs @@ -6,6 +6,7 @@ namespace Barotrauma { public override void ClientReadInitial(IReadMessage msg) { + base.ClientReadInitial(msg); byte monsterCount = msg.ReadByte(); for (int i = 0; i < monsterCount; i++) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/NestMission.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/NestMission.cs index 9de9c32c0..dcc6ee187 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/NestMission.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/NestMission.cs @@ -8,6 +8,7 @@ namespace Barotrauma { public override void ClientReadInitial(IReadMessage msg) { + base.ClientReadInitial(msg); byte selectedCaveIndex = msg.ReadByte(); nestPosition = new Vector2( msg.ReadSingle(), diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/PirateMission.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/PirateMission.cs index fa71317bc..d111e664c 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/PirateMission.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/PirateMission.cs @@ -6,6 +6,7 @@ namespace Barotrauma { public override void ClientReadInitial(IReadMessage msg) { + base.ClientReadInitial(msg); // duplicate code from escortmission, should possibly be combined, though additional loot items might be added so maybe not byte characterCount = msg.ReadByte(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/SalvageMission.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/SalvageMission.cs index 8755b9d40..6479ddb90 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/SalvageMission.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/SalvageMission.cs @@ -7,6 +7,7 @@ namespace Barotrauma { public override void ClientReadInitial(IReadMessage msg) { + base.ClientReadInitial(msg); bool usedExistingItem = msg.ReadBoolean(); if (usedExistingItem) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/ScanMission.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/ScanMission.cs new file mode 100644 index 000000000..93dadacc4 --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/ScanMission.cs @@ -0,0 +1,67 @@ +using Barotrauma.Networking; +using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; +using System.Linq; + +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 void ClientReadInitial(IReadMessage msg) + { + base.ClientReadInitial(msg); + startingItems.Clear(); + ushort itemCount = msg.ReadUInt16(); + for (int i = 0; i < itemCount; i++) + { + startingItems.Add(Item.ReadSpawnData(msg)); + } + if (startingItems.Contains(null)) + { + throw new Exception($"Error in ScanMission.ClientReadInitial: item list contains null (mission: {Prefab.Identifier})"); + } + if (startingItems.Count != itemCount) + { + throw new Exception($"Error in ScanMission.ClientReadInitial: item count does not match the server count ({itemCount} != {startingItems.Count}, mission: {Prefab.Identifier})"); + } + scanners.Clear(); + GetScanners(); + ClientReadScanTargetStatus(msg); + } + + public override void ClientRead(IReadMessage msg) + { + base.ClientRead(msg); + ClientReadScanTargetStatus(msg); + } + + private void ClientReadScanTargetStatus(IReadMessage msg) + { + scanTargets.Clear(); + byte targetsToScan = msg.ReadByte(); + for (int i = 0; i < targetsToScan; i++) + { + ushort id = msg.ReadUInt16(); + bool scanned = msg.ReadBoolean(); + Entity entity = Entity.FindEntityByID(id); + scanTargets.Add(entity as WayPoint, scanned); + } + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs index 57e963b91..18c65a9f7 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs @@ -130,6 +130,7 @@ namespace Barotrauma public static GUIStyle Style; private static Texture2D t; + public static Texture2D WhiteTexture => t; private static Sprite[] MouseCursorSprites => Style.CursorSprite; private static bool debugDrawSounds, debugDrawEvents, debugDrawMetadata; @@ -163,6 +164,7 @@ namespace Barotrauma public static ScalableFont SubHeadingFont => Style?.SubHeadingFont; public static ScalableFont DigitalFont => Style?.DigitalFont; public static ScalableFont HotkeyFont => Style?.HotkeyFont; + public static ScalableFont MonospacedFont => Style?.MonospacedFont; public static ScalableFont CJKFont { get; private set; } @@ -306,7 +308,7 @@ namespace Barotrauma }); SubmarineIcon = new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(452, 385, 182, 81), new Vector2(0.5f, 0.5f)); - arrow = new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(392, 393, 49, 45), new Vector2(0.5f, 0.5f)); + arrow = new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(393, 393, 49, 45), new Vector2(0.5f, 0.5f)); SpeechBubbleIcon = new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(385, 449, 66, 60), new Vector2(0.5f, 0.5f)); BrokenIcon = new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(898, 386, 123, 123), new Vector2(0.5f, 0.5f)); } @@ -672,6 +674,12 @@ namespace Barotrauma spriteBatch.End(); spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: SamplerStateClamp, rasterizerState: GameMain.ScissorTestEnable); + if (GameMain.GameSession?.CrewManager is { DraggedOrder: { SymbolSprite: { } orderSprite, Color: var color }, DragOrder: true }) + { + float spriteSize = Math.Max(orderSprite.size.X, orderSprite.size.Y); + orderSprite.Draw(spriteBatch, PlayerInput.LatestMousePosition, color, orderSprite.size / 2f, scale: 32f / spriteSize * Scale); + } + var sprite = MouseCursorSprites[(int)MouseCursor] ?? MouseCursorSprites[(int)CursorState.Default]; sprite.Draw(spriteBatch, PlayerInput.LatestMousePosition, Color.White, sprite.Origin, 0f, Scale / 1.5f); @@ -855,11 +863,10 @@ namespace Barotrauma int index = 0; if (updateList.Count > 0) { - index = updateList.Count - 1; - while (updateList[index].UpdateOrder > item.UpdateOrder) + index = updateList.Count; + while (index > 0 && updateList[index-1].UpdateOrder > item.UpdateOrder) { index--; - if (index == 0) { break; } } } if (!updateListSet.Contains(item)) @@ -927,13 +934,14 @@ namespace Barotrauma GUIComponent prevMouseOn = MouseOn; MouseOn = null; int inventoryIndex = -1; - - if (Inventory.IsMouseOnInventory()) + + Inventory.RefreshMouseOnInventory(); + if (Inventory.IsMouseOnInventory) { inventoryIndex = updateList.IndexOf(CharacterHUD.HUDFrame); } - if (!PlayerInput.PrimaryMouseButtonHeld() && !PlayerInput.PrimaryMouseButtonClicked()) + if ((!PlayerInput.PrimaryMouseButtonHeld() && !PlayerInput.PrimaryMouseButtonClicked()) || (prevMouseOn == null && !PlayerInput.SecondaryMouseButtonHeld())) { for (var i = updateList.Count - 1; i > inventoryIndex; i--) { @@ -941,10 +949,9 @@ namespace Barotrauma if (!c.CanBeFocused) { continue; } if (c.MouseRect.Contains(PlayerInput.MousePosition)) { - if ((!PlayerInput.PrimaryMouseButtonHeld() && !PlayerInput.PrimaryMouseButtonClicked()) || c == prevMouseOn) + if ((!PlayerInput.PrimaryMouseButtonHeld() && !PlayerInput.PrimaryMouseButtonClicked()) || c == prevMouseOn || prevMouseOn == null) { MouseOn = c; - var sakdjfnsjkd = c.MouseRect; } break; } @@ -958,7 +965,6 @@ namespace Barotrauma MouseCursor = UpdateMouseCursorState(MouseOn); return MouseOn; } - } private static CursorState UpdateMouseCursorState(GUIComponent c) @@ -1050,23 +1056,26 @@ namespace Barotrauma { if (listBox.DraggedElement != null) { return CursorState.Dragging; } if (listBox.CanDragElements) { return CursorState.Move; } - - var hoverParent = c; - while (true) + + if (listBox.HoverCursor != CursorState.Default) { - if (hoverParent == parent || hoverParent == null) { break; } - if (hoverParent.State == GUIComponent.ComponentState.Hover) { return CursorState.Hand; } - hoverParent = hoverParent.Parent; + var hoverParent = c; + while (true) + { + if (hoverParent == parent || hoverParent == null) { break; } + if (hoverParent.State == GUIComponent.ComponentState.Hover) { return CursorState.Hand; } + hoverParent = hoverParent.Parent; + } } } - + if (parent != null && parent.CanBeFocused) { if (!parent.Rect.Equals(monitorRect)) { return parent.HoverCursor; } } } - - if (Inventory.IsMouseOnInventory()) { return Inventory.GetInventoryMouseCursor(); } + + if (Inventory.IsMouseOnInventory) { return Inventory.GetInventoryMouseCursor(); } var character = Character.Controlled; // ReSharper disable once InvertIf @@ -1343,7 +1352,7 @@ namespace Barotrauma /// Should the indicator move based on the camera position? /// Override the distance-based alpha value with the specified alpha value - public static void DrawIndicator(SpriteBatch spriteBatch, in Vector2 worldPosition, Camera cam, in Vector2 visibleRange, Sprite sprite, in Color color, + public static void DrawIndicator(SpriteBatch spriteBatch, in Vector2 worldPosition, Camera cam, in Range visibleRange, Sprite sprite, in Color color, bool createOffset = true, float scaleMultiplier = 1.0f, float? overrideAlpha = null) { Vector2 diff = worldPosition - cam.WorldViewCenter; @@ -1351,9 +1360,9 @@ namespace Barotrauma float symbolScale = Math.Min(64.0f / sprite.size.X, 1.0f) * scaleMultiplier * Scale; - if (overrideAlpha.HasValue || (dist > visibleRange.X && dist < visibleRange.Y)) + if (overrideAlpha.HasValue || (dist > visibleRange.Start && dist < visibleRange.End)) { - float alpha = overrideAlpha ?? MathUtils.Min((dist - visibleRange.X) / 100.0f, 1.0f - ((dist - visibleRange.Y + 100f) / 100.0f), 1.0f); + float alpha = overrideAlpha ?? MathUtils.Min((dist - visibleRange.Start) / 100.0f, 1.0f - ((dist - visibleRange.End + 100f) / 100.0f), 1.0f); Vector2 targetScreenPos = cam.WorldToScreen(worldPosition); if (!createOffset) @@ -1417,7 +1426,7 @@ namespace Barotrauma public static void DrawIndicator(SpriteBatch spriteBatch, Vector2 worldPosition, Camera cam, float hideDist, Sprite sprite, Color color, bool createOffset = true, float scaleMultiplier = 1.0f, float? overrideAlpha = null) { - DrawIndicator(spriteBatch, worldPosition, cam, new Vector2(hideDist, float.PositiveInfinity), sprite, color, createOffset, scaleMultiplier, overrideAlpha); + DrawIndicator(spriteBatch, worldPosition, cam, new Range(hideDist, float.PositiveInfinity), sprite, color, createOffset, scaleMultiplier, overrideAlpha); } public static void DrawLine(SpriteBatch sb, Vector2 start, Vector2 end, Color clr, float depth = 0.0f, float width = 1) @@ -1520,6 +1529,11 @@ namespace Barotrauma } } + public static void DrawFilledRectangle(SpriteBatch sb, RectangleF rect, Color clr, float depth = 0.0f) + { + DrawFilledRectangle(sb, rect.Location, rect.Size, clr, depth); + } + public static void DrawFilledRectangle(SpriteBatch sb, Vector2 start, Vector2 size, Color clr, float depth = 0.0f) { if (size.X < 0) @@ -2433,14 +2447,43 @@ namespace Barotrauma { if (messages.Any(msg => msg.Text == message)) { return; } messages.Add(new GUIMessage(message, color, lifeTime ?? MathHelper.Clamp(message.Length / 5.0f, 3.0f, 10.0f), font ?? LargeFont)); - if (playSound) SoundPlayer.PlayUISound(GUISoundType.UIMessage); + if (playSound) { SoundPlayer.PlayUISound(GUISoundType.UIMessage); } } public static void AddMessage(string message, Color color, Vector2 pos, Vector2 velocity, float lifeTime = 3.0f, bool playSound = true, GUISoundType soundType = GUISoundType.UIMessage, int subId = -1) { Submarine sub = Submarine.Loaded.FirstOrDefault(s => s.ID == subId); - messages.Add(new GUIMessage(message, color, pos, velocity, lifeTime, Alignment.Center, LargeFont, sub: sub)); - if (playSound) SoundPlayer.PlayUISound(soundType); + + var newMessage = new GUIMessage(message, color, pos, velocity, lifeTime, Alignment.Center, Font, sub: sub); + if (playSound) { SoundPlayer.PlayUISound(soundType); } + bool overlapFound = true; + int tries = 0; + while (overlapFound) + { + overlapFound = false; + foreach (var otherMessage in messages) + { + float xDiff = otherMessage.Pos.X - newMessage.Pos.X; + if (Math.Abs(xDiff) > (newMessage.Size.X + otherMessage.Size.X) / 2) { continue; } + float yDiff = otherMessage.Pos.Y - newMessage.Pos.Y; + if (Math.Abs(yDiff) > (newMessage.Size.Y + otherMessage.Size.Y) / 2) { continue; } + Vector2 moveDir = -(new Vector2(xDiff, yDiff) + Rand.Vector(1.0f)); + if (moveDir.LengthSquared() > 0.0001f) + { + moveDir = Vector2.Normalize(moveDir); + } + else + { + moveDir = Rand.Vector(1.0f); + } + moveDir.Y = -Math.Abs(moveDir.Y); + newMessage.Pos -= Vector2.UnitY * 10; + } + tries++; + if (tries > 20) { break; } + } + + messages.Add(newMessage); } public static void ClearMessages() diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIComponent.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIComponent.cs index ecc75885a..34e99bc03 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIComponent.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIComponent.cs @@ -52,13 +52,13 @@ namespace Barotrauma public GUIComponent GetChild(int index) { - if (index < 0 || index >= CountChildren) return null; + if (index < 0 || index >= CountChildren) { return null; } return RectTransform.GetChild(index).GUIComponent; } public int GetChildIndex(GUIComponent child) { - if (child == null) return -1; + if (child == null) { return -1; } return RectTransform.GetChildIndex(child.RectTransform); } @@ -66,7 +66,7 @@ namespace Barotrauma { foreach (GUIComponent child in Children) { - if (child.UserData == obj || (child.userData != null && child.userData.Equals(obj))) return child; + if (child.UserData == obj || (child.userData != null && child.userData.Equals(obj))) { return child; } } return null; } @@ -175,6 +175,8 @@ namespace Barotrauma public bool GlowOnSelect { get; set; } + public Vector2 UVOffset { get; set; } + private CoroutineHandle pulsateCoroutine; protected Color flashColor; @@ -256,9 +258,9 @@ namespace Barotrauma protected Rectangle ClampRect(Rectangle r) { - if (Parent == null || !ClampMouseRectToParent) return r; + if (Parent == null || !ClampMouseRectToParent) { return r; } Rectangle parentRect = Parent.ClampRect(Parent.Rect); - if (parentRect.Width <= 0 || parentRect.Height <= 0) return Rectangle.Empty; + if (parentRect.Width <= 0 || parentRect.Height <= 0) { return Rectangle.Empty; } if (parentRect.X > r.X) { int diff = parentRect.X - r.X; @@ -281,7 +283,7 @@ namespace Barotrauma int diff = (r.Y + r.Height) - (parentRect.Y + parentRect.Height); r.Height -= diff; } - if (r.Width <= 0 || r.Height <= 0) return Rectangle.Empty; + if (r.Width <= 0 || r.Height <= 0) { return Rectangle.Empty; } return r; } @@ -295,7 +297,7 @@ namespace Barotrauma { get { - if (!CanBeFocused) return Rectangle.Empty; + if (!CanBeFocused) { return Rectangle.Empty; } return ClampMouseRectToParent ? ClampRect(Rect) : Rect; } } @@ -431,7 +433,7 @@ namespace Barotrauma #region Updating public virtual void AddToGUIUpdateList(bool ignoreChildren = false, int order = 0) { - if (!Visible) return; + if (!Visible) { return; } UpdateOrder = order; GUI.AddToUpdateList(this); @@ -463,7 +465,7 @@ namespace Barotrauma /// public void UpdateManually(float deltaTime, bool alsoChildren = false, bool recursive = true) { - if (!Visible) return; + if (!Visible) { return; } AutoUpdate = false; Update(deltaTime); @@ -475,7 +477,7 @@ namespace Barotrauma protected virtual void Update(float deltaTime) { - if (!Visible) return; + if (!Visible) { return; } if (CanBeFocused && OnSecondaryClicked != null) { @@ -555,7 +557,7 @@ namespace Barotrauma /// public virtual void DrawManually(SpriteBatch spriteBatch, bool alsoChildren = false, bool recursive = true) { - if (!Visible) return; + if (!Visible) { return; } AutoDraw = false; Draw(spriteBatch); @@ -598,7 +600,7 @@ namespace Barotrauma protected virtual void Draw(SpriteBatch spriteBatch) { - if (!Visible) return; + if (!Visible) { return; } var rect = Rect; GetBlendedColor(GetColor(State), ref _currentColor); @@ -653,7 +655,7 @@ namespace Barotrauma ? MathUtils.InverseLerp(0, SpriteCrossFadeTime, ToolBox.GetEasing(uiSprite.TransitionMode, spriteFadeTimer)) : 0; if (alphaMultiplier > 0) { - uiSprite.Draw(spriteBatch, rect, previousColor * alphaMultiplier, SpriteEffects); + uiSprite.Draw(spriteBatch, rect, previousColor * alphaMultiplier, SpriteEffects, uvOffset: UVOffset); } } } @@ -667,7 +669,11 @@ namespace Barotrauma ? MathUtils.InverseLerp(SpriteCrossFadeTime, 0, ToolBox.GetEasing(uiSprite.TransitionMode, spriteFadeTimer)) : (_currentColor.A / 255.0f); if (alphaMultiplier > 0) { - uiSprite.Draw(spriteBatch, rect, _currentColor * alphaMultiplier, SpriteEffects); + // * (rect.Location.Y - PlayerInput.MousePosition.Y) / rect.Height + Vector2 offset = new Vector2( + MathUtils.PositiveModulo((int)-UVOffset.X, uiSprite.Sprite.SourceRect.Width), + MathUtils.PositiveModulo((int)-UVOffset.Y, uiSprite.Sprite.SourceRect.Height)); + uiSprite.Draw(spriteBatch, rect, _currentColor * alphaMultiplier, SpriteEffects, uvOffset: offset); } } } @@ -708,7 +714,7 @@ namespace Barotrauma /// public void DrawToolTip(SpriteBatch spriteBatch) { - if (!Visible) return; + if (!Visible) { return; } DrawToolTip(spriteBatch, ToolTip, GUI.MouseOn.Rect, TooltipRichTextData); } @@ -1048,7 +1054,7 @@ namespace Barotrauma { case "language": string[] languages = element.GetAttributeStringArray(attribute.Name.ToString(), new string[0]); - if (!languages.Any(l => GameMain.Config.Language.ToLower() == l.ToLower())) { return false; } + if (!languages.Any(l => GameMain.Config.Language.Equals(l, StringComparison.OrdinalIgnoreCase))) { return false; } break; case "gameversion": var version = new Version(attribute.Value); @@ -1213,8 +1219,7 @@ namespace Barotrauma private static GUIImage LoadGUIImage(XElement element, RectTransform parent) { - Sprite sprite = null; - + Sprite sprite; string url = element.GetAttributeString("url", ""); if (!string.IsNullOrEmpty(url)) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIDropDown.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIDropDown.cs index d1560e7ed..6c2780430 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIDropDown.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIDropDown.cs @@ -23,6 +23,8 @@ namespace Barotrauma private bool selectMultiple; public bool Dropped { get; set; } + + public bool AllowNonText { get; set; } public object SelectedItemData { @@ -318,9 +320,9 @@ namespace Barotrauma if (textBlock == null) { textBlock = component.GetChild(); - if (textBlock == null) return false; + if (textBlock is null && !AllowNonText) { return false; } } - button.Text = textBlock.Text; + button.Text = textBlock?.Text ?? ""; } 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. diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIFrame.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIFrame.cs index 0e5d5ca42..4966580c8 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIFrame.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIFrame.cs @@ -5,8 +5,8 @@ using System.Linq; namespace Barotrauma { public class GUIFrame : GUIComponent - { - public int OutlineThickness { get; set; } + { + public float OutlineThickness { get; set; } public GUIFrame(RectTransform rectT, string style = "", Color? color = null) : base(style, rectT) { @@ -28,7 +28,7 @@ namespace Barotrauma if (OutlineColor != Color.Transparent) { - GUI.DrawRectangle(spriteBatch, Rect, OutlineColor * (OutlineColor.A/255.0f), false, thickness: OutlineThickness); + GUI.DrawRectangle(spriteBatch, Rect, OutlineColor, false, thickness: OutlineThickness); } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIImage.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIImage.cs index 086cc25e7..e7be6a3ec 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIImage.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIImage.cs @@ -174,7 +174,7 @@ namespace Barotrauma if (BlendState != null) { spriteBatch.End(); - spriteBatch.Begin(blendState: BlendState, samplerState: GUI.SamplerState); + spriteBatch.Begin(blendState: BlendState, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable); } if (style != null) diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIListBox.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIListBox.cs index 9761bf213..2a010e707 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIListBox.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIListBox.cs @@ -33,7 +33,7 @@ namespace Barotrauma public GUIFrame Content { get; private set; } public GUIScrollBar ScrollBar { get; private set; } - private Dictionary childVisible = new Dictionary(); + private readonly Dictionary childVisible = new Dictionary(); private int totalSize; private bool childrenNeedsRecalculation; @@ -224,7 +224,7 @@ namespace Barotrauma { if (value == false && canDragElements && draggedElement != null) { - draggedElement = null; + DraggedElement = null; } canDragElements = value; } @@ -233,11 +233,29 @@ namespace Barotrauma private GUIComponent draggedElement; private Rectangle draggedReferenceRectangle; private Point draggedReferenceOffset; + public bool HasDraggedElementIndexChanged { get; private set; } - public GUIComponent DraggedElement => draggedElement; + public GUIComponent DraggedElement + { + get + { + return draggedElement; + } + set + { + if (value == draggedElement) { return; } + draggedElement = value; + HasDraggedElementIndexChanged = false; + } + } private readonly bool isHorizontal; + /// + /// Setting this to true and CanBeFocused to false allows the list background to be unfocusable while the elements can still be interacted with. + /// + public bool CanInteractWhenUnfocusable { get; set; } = false; + /// For horizontal listbox, default side is on the bottom. For vertical, it's on the right. public GUIListBox(RectTransform rectT, bool isHorizontal = false, Color? color = null, string style = "", bool isScrollBarOnDefaultSide = true, bool useMouseDownToSelect = false) : base(style, rectT) { @@ -472,7 +490,7 @@ namespace Barotrauma if (!PlayerInput.PrimaryMouseButtonHeld()) { OnRearranged?.Invoke(this, draggedElement.UserData); - draggedElement = null; + DraggedElement = null; RepositionChildren(); } else @@ -518,6 +536,7 @@ namespace Barotrauma if (currIndex != index) { draggedElement.RectTransform.RepositionChildInHierarchy(currIndex); + HasDraggedElementIndexChanged = true; } return; @@ -556,7 +575,7 @@ namespace Barotrauma if (child == null || !child.Visible) { continue; } // selecting - if (Enabled && CanBeFocused && child.CanBeFocused && child.Rect.Contains(PlayerInput.MousePosition) && GUI.IsMouseOn(child)) + if (Enabled && (CanBeFocused || CanInteractWhenUnfocusable) && child.CanBeFocused && child.Rect.Contains(PlayerInput.MousePosition) && GUI.IsMouseOn(child)) { child.State = ComponentState.Hover; @@ -577,7 +596,7 @@ namespace Barotrauma if (CanDragElements && PlayerInput.PrimaryMouseButtonDown() && GUI.MouseOn == child) { - draggedElement = child; + DraggedElement = child; draggedReferenceRectangle = child.Rect; draggedReferenceOffset = child.RectTransform.AbsoluteOffset; } @@ -608,7 +627,7 @@ namespace Barotrauma { if (child == Content || child == ScrollBar || child == ContentBackground) { continue; } child.AddToGUIUpdateList(ignoreChildren, order); - } + } } foreach (GUIComponent child in Content.Children) @@ -637,7 +656,7 @@ namespace Barotrauma OnAddedToGUIUpdateList?.Invoke(this); return; } - + int lastVisible = 0; for (int i = 0; i < Content.CountChildren; i++) { @@ -681,6 +700,8 @@ namespace Barotrauma } } + public void ForceUpdate() => Update((float)Timing.Step); + protected override void Update(float deltaTime) { if (!Visible) { return; } @@ -750,7 +771,7 @@ namespace Barotrauma } } - if ((GUI.IsMouseOn(this) || GUI.IsMouseOn(ScrollBar)) && AllowMouseWheelScroll && PlayerInput.ScrollWheelSpeed != 0) + if (PlayerInput.ScrollWheelSpeed != 0 && AllowMouseWheelScroll && (FindScrollableParentListBox(GUI.MouseOn) == this || GUI.IsMouseOn(ScrollBar))) { if (SmoothScroll) { @@ -773,7 +794,6 @@ namespace Barotrauma ScrollBar.BarScroll -= (PlayerInput.ScrollWheelSpeed / 500.0f) * BarSize; } } - ScrollBar.Enabled = ScrollBarEnabled && BarSize < 1.0f; if (AutoHideScrollBar) @@ -785,6 +805,13 @@ namespace Barotrauma UpdateDimensions(); } } + + private static GUIListBox FindScrollableParentListBox(GUIComponent target) + { + if (target is GUIListBox listBox && listBox.ScrollBarEnabled && listBox.BarSize < 1.0f) { return listBox; } + if (target?.Parent == null) { return null; } + return FindScrollableParentListBox(target.Parent); + } public void SelectNext(bool force = false, bool autoScroll = true, bool takeKeyBoardFocus = false) { @@ -982,7 +1009,7 @@ namespace Barotrauma if (child == null) { return; } child.RectTransform.Parent = null; if (selected.Contains(child)) { selected.Remove(child); } - if (draggedElement == child) { draggedElement = null; } + if (draggedElement == child) { DraggedElement = null; } UpdateScrollBarSize(); } @@ -999,7 +1026,6 @@ namespace Barotrauma ContentBackground.DrawManually(spriteBatch, alsoChildren: false); Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle; - RasterizerState prevRasterizerState = spriteBatch.GraphicsDevice.RasterizerState; if (HideChildrenOutsideFrame) { spriteBatch.End(); @@ -1027,7 +1053,7 @@ namespace Barotrauma { spriteBatch.End(); spriteBatch.GraphicsDevice.ScissorRectangle = prevScissorRect; - spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: prevRasterizerState); + spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable); } if (ScrollBarVisible) diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIProgressBar.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIProgressBar.cs index 0c373187d..33f42b55d 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIProgressBar.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIProgressBar.cs @@ -79,14 +79,14 @@ namespace Barotrauma new Rectangle( sliderArea.X + (int)sliceBorderSizes.X, sliderArea.Y, - (int)((sliderArea.Width - sliceBorderSizes.X - sliceBorderSizes.Z) * fillAmount), + (int)Math.Round((sliderArea.Width - sliceBorderSizes.X - sliceBorderSizes.Z) * fillAmount), sliderArea.Height) : new Rectangle( sliderArea.X, - (int)(sliderArea.Bottom - (sliderArea.Height - sliceBorderSizes.Y - sliceBorderSizes.W) * fillAmount - sliceBorderSizes.W), + (int)Math.Round(sliderArea.Bottom - (sliderArea.Height - sliceBorderSizes.Y - sliceBorderSizes.W) * fillAmount - sliceBorderSizes.W), sliderArea.Width, - (int)((sliderArea.Height - sliceBorderSizes.Y - sliceBorderSizes.W) * fillAmount)); + (int)Math.Round((sliderArea.Height - sliceBorderSizes.Y - sliceBorderSizes.W) * fillAmount)); sliderRect.Width = Math.Max(sliderRect.Width, 1); sliderRect.Height = Math.Max(sliderRect.Height, 1); diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIScissorComponent.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIScissorComponent.cs new file mode 100644 index 000000000..07119e38a --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIScissorComponent.cs @@ -0,0 +1,87 @@ +using System; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace Barotrauma +{ + public class GUIScissorComponent: GUIComponent + { + public GUIComponent Content; + + public GUIScissorComponent(RectTransform rectT) : base(null, rectT) + { + Content = new GUIFrame(new RectTransform(Vector2.One, rectT), style: null) + { + CanBeFocused = false + }; + } + + protected override void Update(float deltaTime) + { + base.Update(deltaTime); + + foreach (GUIComponent child in Children) + { + if (child == Content) { continue; } + throw new InvalidOperationException($"Children were found in {nameof(GUIScissorComponent)}, Add them to {nameof(GUIScissorComponent)}.{nameof(Content)} instead."); + } + + ClampChildMouseRects(Content); + } + + protected override void Draw(SpriteBatch spriteBatch) + { + if (!Visible) { return; } + + Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle; + RasterizerState prevRasterizerState = spriteBatch.GraphicsDevice.RasterizerState; + + spriteBatch.End(); + spriteBatch.GraphicsDevice.ScissorRectangle = Rectangle.Intersect(prevScissorRect, Rect); + spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable); + + foreach (GUIComponent child in Content.Children) + { + if (!child.Visible) { continue; } + child.DrawManually(spriteBatch, alsoChildren: true, recursive: true); + } + + spriteBatch.End(); + spriteBatch.GraphicsDevice.ScissorRectangle = prevScissorRect; + spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: prevRasterizerState); + } + + private void ClampChildMouseRects(GUIComponent child) + { + child.ClampMouseRectToParent = true; + + if (child is GUIListBox) { return; } + + foreach (GUIComponent grandChild in child.Children) + { + ClampChildMouseRects(grandChild); + } + } + + public override void AddToGUIUpdateList(bool ignoreChildren = false, int order = 0) + { + if (!Visible) { return; } + + UpdateOrder = order; + GUI.AddToUpdateList(this); + + if (ignoreChildren) + { + OnAddedToGUIUpdateList?.Invoke(this); + return; + } + + foreach (GUIComponent child in Content.Children) + { + if (!child.Visible) { continue; } + child.AddToGUIUpdateList(false, order); + } + OnAddedToGUIUpdateList?.Invoke(this); + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIStyle.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIStyle.cs index 169b4d758..6c6224e8f 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIStyle.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIStyle.cs @@ -11,7 +11,7 @@ namespace Barotrauma { private Dictionary componentStyles; - private XElement configElement; + private readonly XElement configElement; private GraphicsDevice graphicsDevice; @@ -25,6 +25,7 @@ namespace Barotrauma public ScalableFont SubHeadingFont { get; private set; } public ScalableFont DigitalFont { get; private set; } public ScalableFont HotkeyFont { get; private set; } + public ScalableFont MonospacedFont { get; private set; } public Dictionary ForceFontUpperCase { @@ -40,12 +41,20 @@ namespace Barotrauma public SpriteSheet SavingIndicator { get; private set; } public UISprite UIGlow { get; private set; } + + public UISprite PingCircle { get; private set; } + public UISprite UIGlowCircular { get; private set; } + public UISprite UIGlowSolidCircular { get; private set; } + public UISprite UIThermalGlow { get; private set; } + public UISprite ButtonPulse { get; private set; } public SpriteSheet FocusIndicator { get; private set; } + public UISprite IconOverflowIndicator { get; private set; } + /// /// General green color used for elements whose colors are set from code /// @@ -82,6 +91,12 @@ namespace Barotrauma public Color TextColorDark { get; private set; } = Color.Black * 0.9f; public Color TextColorDim { get; private set; } = Color.White * 0.6f; + public Color ItemQualityColorPoor { get; private set; } = Color.DarkRed; + public Color ItemQualityColorNormal { get; private set; } = Color.Gray; + public Color ItemQualityColorGood { get; private set; } = Color.LightGreen; + public Color ItemQualityColorExcellent { get; private set; } = Color.LightBlue; + public Color ItemQualityColorMasterwork { get; private set; } = Color.MediumPurple; + public Color ColorReputationVeryLow { get; private set; } = Color.Red; public Color ColorReputationLow { get; private set; } = Color.Orange; public Color ColorReputationNeutral { get; private set; } = Color.White * 0.8f; @@ -235,6 +250,9 @@ namespace Barotrauma case "uiglow": UIGlow = new UISprite(subElement); break; + case "pingcircle": + PingCircle = new UISprite(subElement); + break; case "radiation": RadiationSprite = new UISprite(subElement); break; @@ -244,9 +262,18 @@ namespace Barotrauma case "uiglowcircular": UIGlowCircular = new UISprite(subElement); break; + case "uiglowsolidcircular": + UIGlowSolidCircular = new UISprite(subElement); + break; + case "uithermalglow": + UIThermalGlow = new UISprite(subElement); + break; case "endroundbuttonpulse": ButtonPulse = new UISprite(subElement); break; + case "iconoverflowindicator": + IconOverflowIndicator = new UISprite(subElement); + break; case "focusindicator": FocusIndicator = new SpriteSheet(subElement); break; @@ -277,6 +304,10 @@ namespace Barotrauma DigitalFont = LoadFont(subElement, graphicsDevice); ForceFontUpperCase[DigitalFont] = subElement.GetAttributeBool("forceuppercase", false); break; + case "monospacedfont": + MonospacedFont = LoadFont(subElement, graphicsDevice); + ForceFontUpperCase[MonospacedFont] = subElement.GetAttributeBool("forceuppercase", false); + break; case "hotkeyfont": HotkeyFont = LoadFont(subElement, graphicsDevice); ForceFontUpperCase[HotkeyFont] = subElement.GetAttributeBool("forceuppercase", false); @@ -446,7 +477,7 @@ namespace Barotrauma public void Apply(GUIComponent targetComponent, string styleName = "", GUIComponent parent = null) { - GUIComponentStyle componentStyle = null; + GUIComponentStyle componentStyle = null; if (parent != null) { GUIComponentStyle parentStyle = parent.Style; @@ -461,7 +492,7 @@ namespace Barotrauma return; } } - + string childStyleName = string.IsNullOrEmpty(styleName) ? targetComponent.GetType().Name : styleName; parentStyle.ChildStyles.TryGetValue(childStyleName.ToLowerInvariant(), out componentStyle); } @@ -477,8 +508,25 @@ namespace Barotrauma return; } } - - targetComponent.ApplyStyle(componentStyle); + + targetComponent.ApplyStyle(componentStyle); + } + + public Color GetQualityColor(int quality) + { + switch (quality) + { + case 1: + return ItemQualityColorGood; + case 2: + return ItemQualityColorExcellent; + case 3: + return ItemQualityColorMasterwork; + case -1: + return ItemQualityColorPoor; + default: + return ItemQualityColorNormal; + } } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/HUDLayoutSettings.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/HUDLayoutSettings.cs index 407236e08..69f18c269 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/HUDLayoutSettings.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/HUDLayoutSettings.cs @@ -185,7 +185,7 @@ namespace Barotrauma if (GUI.MouseOn != null) { return false; } //don't close when hovering over an inventory element - if (Inventory.IsMouseOnInventory()) { return false; } + if (Inventory.IsMouseOnInventory) { return false; } bool input = PlayerInput.PrimaryMouseButtonDown() || PlayerInput.SecondaryMouseButtonClicked(); return input && !rect.Contains(PlayerInput.MousePosition); diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/LoadingScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/LoadingScreen.cs index 23b0f9036..3e7e06550 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/LoadingScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/LoadingScreen.cs @@ -81,7 +81,7 @@ namespace Barotrauma private readonly object loadMutex = new object(); private float? loadState; - + public float? LoadState { get @@ -90,8 +90,8 @@ namespace Barotrauma { return loadState; } - } - set + } + set { lock (loadMutex) { @@ -141,7 +141,7 @@ namespace Barotrauma GameMain.Config.EnableSplashScreen = false; } } - + var titleStyle = GUI.Style?.GetComponentStyle("TitleText"); Sprite titleSprite = null; if (!WaitForLanguageSelection && titleStyle != null && titleStyle.Sprites.ContainsKey(GUIComponent.ComponentState.None)) @@ -177,8 +177,8 @@ namespace Barotrauma color: Color.White * noiseStrength * 0.1f, textureScale: Vector2.One * noiseScale); - titleSprite?.Draw(spriteBatch, new Vector2(GameMain.GraphicsWidth * 0.05f, GameMain.GraphicsHeight * 0.125f), - Color.White, origin: new Vector2(0.0f, titleSprite.SourceRect.Height / 2.0f), + titleSprite?.Draw(spriteBatch, new Vector2(GameMain.GraphicsWidth * 0.05f, GameMain.GraphicsHeight * 0.125f), + Color.White, origin: new Vector2(0.0f, titleSprite.SourceRect.Height / 2.0f), scale: GameMain.GraphicsHeight / 2000.0f); if (WaitForLanguageSelection) @@ -193,7 +193,7 @@ namespace Barotrauma if (LoadState == 100.0f) { #if DEBUG - if (GameMain.Config.AutomaticQuickStartEnabled || GameMain.Config.AutomaticCampaignLoadEnabled && GameMain.FirstLoad) + if (GameMain.Config.AutomaticQuickStartEnabled || GameMain.Config.AutomaticCampaignLoadEnabled || GameMain.Config.TestScreenEnabled && GameMain.FirstLoad) { loadText = "QUICKSTARTING ..."; } @@ -211,6 +211,13 @@ namespace Barotrauma if (LoadState != null) { loadText += " " + (int)LoadState + " %"; + +#if DEBUG + if (GameMain.FirstLoad && GameMain.CancelQuickStart) + { + loadText += " (Quickstart aborted)"; + } +#endif } } if (GUI.LargeFont != null) @@ -265,7 +272,7 @@ namespace Barotrauma decorativeGraph.Draw(spriteBatch, (int)(decorativeGraph.FrameCount * noiseVal), new Vector2(GameMain.GraphicsWidth * 0.001f, GameMain.GraphicsHeight * 0.24f), Color.White, Vector2.Zero, 0.0f, decorativeScale, SpriteEffects.FlipVertically); - + decorativeMap.Draw(spriteBatch, (int)(decorativeMap.FrameCount * noiseVal), new Vector2(GameMain.GraphicsWidth * 0.99f, GameMain.GraphicsHeight * 0.66f), Color.White, decorativeMap.FrameSize.ToVector2(), 0.0f, decorativeScale); @@ -281,9 +288,9 @@ namespace Barotrauma } else if (noiseVal < 0.5f) { - randText = - Rand.Int(100).ToString().PadLeft(2, '0') + " " + - Rand.Int(100).ToString().PadLeft(2, '0') + " " + + randText = + Rand.Int(100).ToString().PadLeft(2, '0') + " " + + Rand.Int(100).ToString().PadLeft(2, '0') + " " + Rand.Int(100).ToString().PadLeft(2, '0') + " " + Rand.Int(100).ToString().PadLeft(2, '0'); } @@ -299,12 +306,12 @@ namespace Barotrauma { if (languageSelectionFont == null) { - languageSelectionFont = new ScalableFont("Content/Fonts/NotoSans/NotoSans-Bold.ttf", + languageSelectionFont = new ScalableFont("Content/Fonts/NotoSans/NotoSans-Bold.ttf", (uint)(30 * (GameMain.GraphicsHeight / 1080.0f)), graphicsDevice); } if (languageSelectionFontCJK == null) { - languageSelectionFontCJK = new ScalableFont("Content/Fonts/NotoSans/NotoSansCJKsc-Bold.otf", + languageSelectionFontCJK = new ScalableFont("Content/Fonts/NotoSans/NotoSansCJKsc-Bold.otf", (uint)(30 * (GameMain.GraphicsHeight / 1080.0f)), graphicsDevice, dynamicLoading: true); } if (languageSelectionCursor == null) @@ -320,11 +327,11 @@ namespace Barotrauma var font = TextManager.IsCJK(localizedLanguageName) ? languageSelectionFontCJK : languageSelectionFont; Vector2 textSize = font.MeasureString(localizedLanguageName); - bool hover = - Math.Abs(PlayerInput.MousePosition.X - textPos.X) < textSize.X / 2 && + bool hover = + Math.Abs(PlayerInput.MousePosition.X - textPos.X) < textSize.X / 2 && Math.Abs(PlayerInput.MousePosition.Y - textPos.Y) < textSpacing.Y / 2; - font.DrawString(spriteBatch, localizedLanguageName, textPos - textSize / 2, + font.DrawString(spriteBatch, localizedLanguageName, textPos - textSize / 2, hover ? Color.White : Color.White * 0.6f); if (hover && PlayerInput.PrimaryMouseButtonClicked()) { @@ -394,14 +401,14 @@ namespace Barotrauma LoadState = null; SetSelectedTip(TextManager.Get("LoadingScreenTip", true)); currentBackgroundTexture = LocationType.List.GetRandom()?.GetPortrait(Rand.Int(int.MaxValue))?.Texture; - + while (!drawn) { yield return CoroutineStatus.Running; } CoroutineManager.StartCoroutine(loader); - + yield return CoroutineStatus.Running; while (CoroutineManager.IsCoroutineRunning(loader.ToString())) diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/ShapeExtensions.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/ShapeExtensions.cs index 8bf8adea1..afb00b206 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/ShapeExtensions.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/ShapeExtensions.cs @@ -55,14 +55,47 @@ namespace Barotrauma var texture = GetTexture(spriteBatch); for (var i = 0; i < points.Count - 1; i++) - DrawPolygonEdge(spriteBatch, texture, points[i] + offset, points[i + 1] + offset, color, thickness); + DrawPolygonEdge(spriteBatch, points[i] + offset, points[i + 1] + offset, color, thickness); - DrawPolygonEdge(spriteBatch, texture, points[points.Count - 1] + offset, points[0] + offset, color, + DrawPolygonEdge(spriteBatch, points[points.Count - 1] + offset, points[0] + offset, color, thickness); } + + /// + /// Draws a closed polygon from an array of points + /// + public static void DrawPolygonInner(this SpriteBatch spriteBatch, Vector2 offset, IReadOnlyList points, Color color, float thickness = 1f) + { + if (points.Count == 0) { return; } - private static void DrawPolygonEdge(SpriteBatch spriteBatch, Texture2D texture, Vector2 point1, Vector2 point2, - Color color, float thickness) + if (points.Count == 1) + { + DrawPoint(spriteBatch, points[0], color, (int)thickness); + return; + } + + for (var i = 0; i < points.Count - 1; i++) + { + Vector2 point1 = points[i] + offset, + point2 = points[i + 1] + offset; + + DrawPolygonEdgeInner(spriteBatch, point1, point2, color, thickness); + } + + DrawPolygonEdgeInner(spriteBatch, points[^1] + offset, points[0] + offset, color, thickness); + } + + private static void DrawPolygonEdgeInner(SpriteBatch spriteBatch, Vector2 point1, Vector2 point2, Color color, float thickness) + { + var length = Vector2.Distance(point1, point2) + thickness; + var angle = (float)Math.Atan2(point2.Y - point1.Y, point2.X - point1.X); + var scale = new Vector2(length, thickness); + Vector2 middle = new Vector2((point1.X + point2.X) / 2f, (point1.Y + point2.Y) / 2f); + Texture2D tex = GetTexture(spriteBatch); + spriteBatch.Draw(GetTexture(spriteBatch), middle, null, color, angle, new Vector2(tex.Width / 2f, tex.Height / 2f), scale, SpriteEffects.None, 0); + } + + private static void DrawPolygonEdge(SpriteBatch spriteBatch, Vector2 point1, Vector2 point2, Color color, float thickness) { var length = Vector2.Distance(point1, point2); var angle = (float)Math.Atan2(point2.Y - point1.Y, point2.X - point1.X); diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/Store.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/Store.cs index 5c97c009d..114c2282e 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/Store.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/Store.cs @@ -37,7 +37,7 @@ namespace Barotrauma private Color storeSpecialColor; private GUIListBox shoppingCrateBuyList, shoppingCrateSellList, shoppingCrateSellFromSubList; - private GUITextBlock shoppingCrateTotal; + private GUITextBlock relevantBalanceName, shoppingCrateTotal; private GUIButton clearAllButton, confirmButton; private bool needsRefresh, needsBuyingRefresh, needsSellingRefresh, needsItemsToSellRefresh, needsSellingFromSubRefresh, needsItemsToSellFromSubRefresh; @@ -58,7 +58,6 @@ namespace Barotrauma StoreTab.SellFromSub => false, _ => throw new NotImplementedException() }; - private bool IsSelling => !IsBuying; private GUIListBox ActiveShoppingCrateList => activeTab switch { StoreTab.Buy => shoppingCrateBuyList, @@ -222,16 +221,8 @@ namespace Barotrauma TextScale = 1.1f, TextGetter = () => { - if (CurrentLocation != null) - { - merchantBalanceBlock.TextColor = CurrentLocation.BalanceColor; - return GetCurrencyFormatted(CurrentLocation.StoreCurrentBalance); - } - else - { - merchantBalanceBlock.TextColor = Color.Red; - return GetCurrencyFormatted(0); - } + merchantBalanceBlock.TextColor = CurrentLocation?.BalanceColor ?? Color.Red; + return GetMerchantBalanceText(); } }; @@ -375,28 +366,37 @@ namespace Barotrauma //don't show categories with no buyable items itemCategories.RemoveAll(c => !ItemPrefab.Prefabs.Any(ep => ep.Category.HasFlag(c) && ep.CanBeBought)); itemCategoryButtons.Clear(); + var categoryButton = new GUIButton(new RectTransform(new Point(categoryButtonContainer.Rect.Width, categoryButtonContainer.Rect.Width), categoryButtonContainer.RectTransform), style: "CategoryButton.All") + { + ToolTip = TextManager.Get("MapEntityCategory.All"), + OnClicked = OnClickedCategoryButton + }; + itemCategoryButtons.Add(categoryButton); foreach (MapEntityCategory category in itemCategories) { - var categoryButton = new GUIButton(new RectTransform(new Point(categoryButtonContainer.Rect.Width, categoryButtonContainer.Rect.Width), categoryButtonContainer.RectTransform), + categoryButton = new GUIButton(new RectTransform(new Point(categoryButtonContainer.Rect.Width, categoryButtonContainer.Rect.Width), categoryButtonContainer.RectTransform), style: "CategoryButton." + category) { ToolTip = TextManager.Get("MapEntityCategory." + category), UserData = category, - OnClicked = (btn, userdata) => - { - MapEntityCategory? newCategory = !btn.Selected ? (MapEntityCategory?)userdata : null; - if (newCategory.HasValue) { searchBox.Text = ""; } - if (newCategory != selectedItemCategory) { tabLists[activeTab].ScrollBar.BarScroll = 0f; } - FilterStoreItems(newCategory, searchBox.Text); - return true; - } + OnClicked = OnClickedCategoryButton }; itemCategoryButtons.Add(categoryButton); - categoryButton.RectTransform.SizeChanged += () => + } + bool OnClickedCategoryButton(GUIButton button, object userData) + { + MapEntityCategory? newCategory = !button.Selected ? (MapEntityCategory?)userData : null; + if (newCategory.HasValue) { searchBox.Text = ""; } + if (newCategory != selectedItemCategory) { tabLists[activeTab].ScrollBar.BarScroll = 0f; } + FilterStoreItems(newCategory, searchBox.Text); + return true; + } + foreach (var btn in itemCategoryButtons) + { + btn.RectTransform.SizeChanged += () => { - var sprite = categoryButton.Frame.sprites[GUIComponent.ComponentState.None].First(); - categoryButton.RectTransform.NonScaledSize = - new Point(categoryButton.Rect.Width, (int)(categoryButton.Rect.Width * ((float)sprite.Sprite.SourceRect.Height / sprite.Sprite.SourceRect.Width))); + var sprite = btn.Frame.sprites[GUIComponent.ComponentState.None].First(); + btn.RectTransform.NonScaledSize = new Point(btn.Rect.Width, (int)(btn.Rect.Width * ((float)sprite.Sprite.SourceRect.Height / sprite.Sprite.SourceRect.Width))); }; } @@ -503,11 +503,11 @@ namespace Barotrauma ForceUpperCase = true }; new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), playerBalanceContainer.RectTransform), - "", font: GUI.SubHeadingFont, textAlignment: Alignment.TopRight) + "", textColor: Color.White, font: GUI.SubHeadingFont, textAlignment: Alignment.TopRight) { AutoScaleVertical = true, TextScale = 1.1f, - TextGetter = () => GetCurrencyFormatted(PlayerMoney) + TextGetter = GetPlayerBalanceText }; // Divider ------------------------------------------------ @@ -523,7 +523,7 @@ namespace Barotrauma RelativeSpacing = 0.015f, Stretch = true }; - var shoppingCrateListContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.85f), shoppingCrateInventoryContainer.RectTransform), style: null); + var shoppingCrateListContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.8f), shoppingCrateInventoryContainer.RectTransform), style: null); shoppingCrateBuyList = new GUIListBox(new RectTransform(Vector2.One, shoppingCrateListContainer.RectTransform)) { Visible = false }; shoppingCrateSellList = new GUIListBox(new RectTransform(Vector2.One, shoppingCrateListContainer.RectTransform)) { Visible = false }; if (GameMain.IsSingleplayer) @@ -531,6 +531,21 @@ namespace Barotrauma shoppingCrateSellFromSubList = new GUIListBox(new RectTransform(Vector2.One, shoppingCrateListContainer.RectTransform)) { Visible = false }; } + var relevantBalanceContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), shoppingCrateInventoryContainer.RectTransform), isHorizontal: true) + { + Stretch = true + }; + relevantBalanceName = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), relevantBalanceContainer.RectTransform), "", font: GUI.Font) + { + CanBeFocused = false + }; + new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), relevantBalanceContainer.RectTransform), "", textColor: Color.White, font: GUI.SubHeadingFont, textAlignment: Alignment.Right) + { + CanBeFocused = false, + TextScale = 1.1f, + TextGetter = () => IsBuying ? GetPlayerBalanceText() : GetMerchantBalanceText() + }; + var totalContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), shoppingCrateInventoryContainer.RectTransform), isHorizontal: true) { Stretch = true @@ -576,10 +591,14 @@ namespace Barotrauma resolutionWhenCreated = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight); } - private GUILayoutGroup CreateDealsGroup(GUIListBox parentList) + private string GetMerchantBalanceText() => GetCurrencyFormatted(CurrentLocation?.StoreCurrentBalance ?? 0); + + private string GetPlayerBalanceText() => GetCurrencyFormatted(PlayerMoney); + + private GUILayoutGroup CreateDealsGroup(GUIListBox parentList, int elementCount = 4) { var elementHeight = (int)(GUI.yScale * 80); - var frame = new GUIFrame(new RectTransform(new Point(parentList.Content.Rect.Width, 4 * elementHeight + 3), parent: parentList.Content.RectTransform), style: null); + var frame = new GUIFrame(new RectTransform(new Point(parentList.Content.Rect.Width, elementCount * elementHeight + 3), parent: parentList.Content.RectTransform), style: null); frame.UserData = "deals"; var dealsGroup = new GUILayoutGroup(new RectTransform(Vector2.One, frame.RectTransform, anchor: Anchor.Center), childAnchor: Anchor.TopCenter); var dealsHeader = new GUILayoutGroup(new RectTransform(new Point((int)(0.95f * parentList.Content.Rect.Width), elementHeight), parent: dealsGroup.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft); @@ -604,17 +623,14 @@ namespace Barotrauma { prevLocation.Reputation.OnReputationValueChanged = null; } - - foreach (ItemPrefab itemPrefab in ItemPrefab.Prefabs) + if (ItemPrefab.Prefabs.Any(p => p.CanBeBoughtAtLocation(CurrentLocation, out PriceInfo _))) { - if (itemPrefab.CanBeBoughtAtLocation(CurrentLocation, out PriceInfo _)) + selectedItemCategory = null; + searchBox.Text = ""; + ChangeStoreTab(StoreTab.Buy); + if (newLocation?.Reputation != null) { - ChangeStoreTab(StoreTab.Buy); - if (newLocation?.Reputation != null) - { - newLocation.Reputation.OnReputationValueChanged += () => { needsRefresh = true; }; - } - return; + newLocation.Reputation.OnReputationValueChanged += () => { needsRefresh = true; }; } } } @@ -628,6 +644,7 @@ namespace Barotrauma tabButton.Selected = (StoreTab)tabButton.UserData == activeTab; } sortingDropDown.SelectItem(tabSortingMethods[tab]); + relevantBalanceName.Text = IsBuying ? TextManager.Get("campaignstore.balance") : TextManager.Get("campaignstore.storebalance"); SetShoppingCrateTotalText(); SetClearAllButtonStatus(); SetConfirmButtonBehavior(); @@ -697,7 +714,7 @@ namespace Barotrauma } foreach (GUIButton btn in itemCategoryButtons) { - btn.Selected = category.HasValue && (MapEntityCategory)btn.UserData == selectedItemCategory; + btn.Selected = (MapEntityCategory?)btn.UserData == selectedItemCategory; } list.UpdateScrollBarSize(); } @@ -709,6 +726,8 @@ namespace Barotrauma FilterStoreItems(category, searchBox.Text); } + int prevDailySpecialCount; + private void RefreshStoreBuyList() { float prevBuyListScroll = storeBuyList.BarScroll; @@ -717,11 +736,14 @@ namespace Barotrauma bool hasPermissions = HasPermissions; HashSet existingItemFrames = new HashSet(); - if ((storeDailySpecialsGroup != null) != CurrentLocation.DailySpecials.Any()) + int dailySpecialCount = CurrentLocation?.DailySpecials.Count() ?? 3; + + if ((storeDailySpecialsGroup != null) != CurrentLocation.DailySpecials.Any() || dailySpecialCount != prevDailySpecialCount) { - if (storeDailySpecialsGroup == null) + if (storeDailySpecialsGroup == null || dailySpecialCount != prevDailySpecialCount) { - storeDailySpecialsGroup = CreateDealsGroup(storeBuyList); + storeBuyList.RemoveChild(storeDailySpecialsGroup?.Parent); + storeDailySpecialsGroup = CreateDealsGroup(storeBuyList, 1 + dailySpecialCount); storeDailySpecialsGroup.Parent.SetAsFirstChild(); } else @@ -730,6 +752,7 @@ namespace Barotrauma storeDailySpecialsGroup = null; } storeBuyList.RecalculateChildren(); + prevDailySpecialCount = dailySpecialCount; } foreach (PurchasedItem item in CurrentLocation.StoreStock) diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs index a5239b1b6..6ed653ddc 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using System.Linq; @@ -18,8 +19,8 @@ namespace Barotrauma private static UISprite spectateIcon, disconnectedIcon; private static Sprite ownerIcon, moderatorIcon; - private enum InfoFrameTab { Crew, Mission, Reputation, MyCharacter, Traitor, Submarine }; - private static InfoFrameTab selectedTab; + public enum InfoFrameTab { Crew, Mission, Reputation, Traitor, Submarine, Talents }; + public static InfoFrameTab selectedTab; private GUIFrame infoFrame, contentFrame; private readonly List tabButtons = new List(); @@ -33,6 +34,8 @@ namespace Barotrauma private List teamIDs; private const string inLobbyString = "\u2022 \u2022 \u2022"; + private GUIFrame pendingChangesFrame = null; + public static Color OwnCharacterBGColor = Color.Gold * 0.7f; private class LinkedGUI @@ -48,7 +51,7 @@ namespace Barotrauma private readonly GUIFrame frame; public LinkedGUI(Client client, GUIFrame frame, bool hasCharacter, GUITextBlock textBlock) - { + { this.client = client; this.textBlock = textBlock; this.frame = frame; @@ -125,7 +128,7 @@ namespace Barotrauma public TabMenu() { - if (!initialized) Initialize(); + if (!initialized) { Initialize(); } CreateInfoFrame(selectedTab); SelectInfoFrameTab(null, selectedTab); @@ -133,6 +136,17 @@ namespace Barotrauma public void Update() { + GameSession.UpdateTalentNotificationIndicator(talentPointNotification); + if (Character.Controlled is { } controlled && talentResetButton != null && talentApplyButton != null) + { + int talentCount = selectedTalents.Count - controlled.Info.GetUnlockedTalentsInTree().Count(); + talentResetButton.Enabled = talentApplyButton.Enabled = talentCount > 0; + if (talentApplyButton.Enabled && talentApplyButton.FlashTimer <= 0.0f) + { + talentApplyButton.Flash(GUI.Style.Orange); + } + } + if (selectedTab != InfoFrameTab.Crew) return; if (linkedGUIList == null) return; @@ -182,16 +196,8 @@ namespace Barotrauma new GUIFrame(new RectTransform(GUI.Canvas.RelativeSize, infoFrame.RectTransform, Anchor.Center), style: "GUIBackgroundBlocker"); //this used to be a switch expression but i changed it because it killed enc :( - Vector2 contentFrameSize; - switch (selectedTab) - { - case InfoFrameTab.MyCharacter: - contentFrameSize = new Vector2(0.45f, 0.5f); - break; - default: - contentFrameSize = new Vector2(0.45f, 0.667f); - break; - } + //now it's not even a switch statement anymore :( + Vector2 contentFrameSize = new Vector2(0.45f, 0.667f); contentFrame = new GUIFrame(new RectTransform(contentFrameSize, infoFrame.RectTransform, Anchor.TopCenter, Pivot.TopCenter) { RelativeOffset = new Vector2(0.0f, 0.12f) }); var horizontalLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.958f, 0.943f), contentFrame.RectTransform, Anchor.TopCenter, Pivot.TopCenter) { AbsoluteOffset = new Point(0, GUI.IntScale(25f)) }, isHorizontal: true) @@ -242,6 +248,17 @@ namespace Barotrauma { TextGetter = () => TextManager.GetWithVariable("campaignmoney", "[money]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", campaignMode.Money)) }; + GUIFrame bottomDisclaimerFrame = new GUIFrame(new RectTransform(new Vector2(contentFrameSize.X, 0.1f), infoFrame.RectTransform) + { + AbsoluteOffset = new Point(contentFrame.Rect.X, contentFrame.Rect.Bottom + GUI.IntScale(8)) + }, style: null); + + pendingChangesFrame = new GUIFrame(new RectTransform(Vector2.One, bottomDisclaimerFrame.RectTransform, Anchor.Center), style: null); + + if (GameMain.NetLobbyScreen?.CampaignCharacterDiscarded ?? false) + { + NetLobbyScreen.CreateChangesPendingFrame(pendingChangesFrame); + } } else { @@ -254,10 +271,17 @@ namespace Barotrauma var submarineButton = createTabButton(InfoFrameTab.Submarine, "submarine"); - if (GameMain.NetworkMember != null) + var talentsButton = createTabButton(InfoFrameTab.Talents, "tabmenu.character"); + talentsButton.OnAddedToGUIUpdateList += (component) => { - var myCharacterButton = createTabButton(InfoFrameTab.MyCharacter, "tabmenu.character"); - } + talentsButton.Enabled = Character.Controlled?.Info != null; + if (!talentsButton.Enabled && selectedTab == InfoFrameTab.Talents) + { + SelectInfoFrameTab(null, InfoFrameTab.Crew); + } + }; + + talentPointNotification = GameSession.CreateTalentIconNotification(talentsButton); } private bool SelectInfoFrameTab(GUIButton button, object userData) @@ -289,13 +313,12 @@ namespace Barotrauma if (traitor == null || traitorMission == null) return false; CreateTraitorInfo(infoFrameHolder, traitorMission, traitor); break; - case InfoFrameTab.MyCharacter: - if (GameMain.NetworkMember == null) { return false; } - GameMain.NetLobbyScreen.CreatePlayerFrame(infoFrameHolder); - break; case InfoFrameTab.Submarine: CreateSubmarineInfo(infoFrameHolder, Submarine.MainSub); break; + case InfoFrameTab.Talents: + CreateTalentInfo(infoFrameHolder); + break; } return true; @@ -408,7 +431,7 @@ namespace Barotrauma if (GameMain.IsMultiplayer) { CreateMultiPlayerList(false); - CreateMultiPlayerLogContent(crewFrame); + CreateMultiPlayerLogContent(crewFrame); } else { @@ -599,7 +622,7 @@ namespace Barotrauma AbsoluteSpacing = 2 }; - new GUICustomComponent(new RectTransform(new Point(jobColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform, Anchor.Center), + new GUICustomComponent(new RectTransform(new Point(jobColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform, Anchor.Center), onDraw: (sb, component) => DrawNotInGameIcon(sb, component.Rect, client)) { CanBeFocused = false, @@ -828,7 +851,7 @@ namespace Barotrauma } private static readonly List> storedMessages = new List>(); - + public static void StorePlayerConnectionChangeMessage(ChatMessage message) { if (!GameMain.GameSession?.IsRunning ?? true) { return; } @@ -841,7 +864,7 @@ namespace Barotrauma TabMenu instance = GameSession.TabMenuInstance; instance.AddLineToLog(msg, message.ChangeType); instance.RemoveCurrentElements(); - instance.CreateMultiPlayerList(true); + instance.CreateMultiPlayerList(true); } } @@ -960,7 +983,7 @@ namespace Barotrauma GUIFrame missionDescriptionHolder = new GUIFrame(new RectTransform(Vector2.One, missionList.Content.RectTransform), style: null); GUILayoutGroup missionTextGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.744f, 0f), missionDescriptionHolder.RectTransform, Anchor.CenterLeft) { AbsoluteOffset = new Point(iconSize + spacing, 0) }, false, childAnchor: Anchor.TopLeft) { - AbsoluteSpacing = spacing + AbsoluteSpacing = spacing }; string descriptionText = mission.Description; foreach (string missionMessage in mission.ShownMessages) @@ -988,7 +1011,7 @@ namespace Barotrauma float ySize = missionNameSize.Y + missionDescriptionSize.Y + missionRewardSize.Y + missionReputationSize.Y + missionTextGroup.AbsoluteSpacing * 4; bool displayDifficulty = mission.Difficulty.HasValue; if (displayDifficulty) { ySize += missionRewardSize.Y; } - + missionDescriptionHolder.RectTransform.NonScaledSize = new Point(missionDescriptionHolder.RectTransform.NonScaledSize.X, (int)ySize); missionTextGroup.RectTransform.NonScaledSize = new Point(missionTextGroup.RectTransform.NonScaledSize.X, missionDescriptionHolder.RectTransform.NonScaledSize.Y); @@ -999,8 +1022,8 @@ namespace Barotrauma int iconHeight = Math.Max(missionTextGroup.RectTransform.NonScaledSize.Y, (int)(iconWidth * iconAspectRatio)); Point iconSize = new Point(iconWidth, iconHeight);*/ - new GUIImage(new RectTransform(new Point(iconSize), missionDescriptionHolder.RectTransform), mission.Prefab.Icon, null, true) - { + new GUIImage(new RectTransform(new Point(iconSize), missionDescriptionHolder.RectTransform), mission.Prefab.Icon, null, true) + { Color = mission.Prefab.IconColor, HoverColor = mission.Prefab.IconColor, SelectedColor = mission.Prefab.IconColor, @@ -1159,5 +1182,506 @@ namespace Barotrauma sub.Info.CreateSpecsWindow(specsListBox, GUI.Font, includeTitle: false, includeClass: false, includeDescription: true); } } + private Color unselectedColor = new Color(240, 255, 255, 225); + private Color selectedColor = new Color(220, 255, 220, 225); + private Color ownedColor = new Color(140, 180, 140, 225); + private Color unselectableColor = new Color(100, 100, 100, 225); + private Color pressedColor = new Color(60, 60, 60, 225); + + private readonly List<(GUIButton button, GUIComponent icon)> talentButtons = new List<(GUIButton button, GUIComponent icon)>(); + private readonly List<(string talentTree, int index, GUIImage icon, GUIFrame background, GUIFrame backgroundGlow)> talentCornerIcons = new List<(string talentTree, int index, GUIImage icon, GUIFrame background, GUIFrame backgroundGlow)>(); + private List selectedTalents = new List(); + + private GUITextBlock experienceText; + private GUIProgressBar experienceBar; + private GUITextBlock talentPointText; + private GUIListBox skillListBox; + + private GUIButton talentApplyButton, + talentResetButton; + + private GUIImage talentPointNotification; + + private readonly ImmutableDictionary talentStageStyles = new Dictionary + { + { TalentTree.TalentTreeStageState.Invalid, GUI.Style.GetComponentStyle("TalentTreeLocked") }, + { TalentTree.TalentTreeStageState.Locked, GUI.Style.GetComponentStyle("TalentTreeLocked") }, + { TalentTree.TalentTreeStageState.Unlocked, GUI.Style.GetComponentStyle("TalentTreePurchased") }, + { TalentTree.TalentTreeStageState.Available, GUI.Style.GetComponentStyle("TalentTreeUnlocked") }, + { TalentTree.TalentTreeStageState.Highlighted, GUI.Style.GetComponentStyle("TalentTreeAvailable") }, + }.ToImmutableDictionary(); + + private readonly ImmutableDictionary talentStageBackgroundColors = new Dictionary + { + { TalentTree.TalentTreeStageState.Invalid, new Color(48,48,48,255) }, + { TalentTree.TalentTreeStageState.Locked, new Color(48,48,48,255) }, + { TalentTree.TalentTreeStageState.Unlocked, new Color(24,37,31,255) }, + { TalentTree.TalentTreeStageState.Available, new Color(50,47,33,255) }, + { TalentTree.TalentTreeStageState.Highlighted, new Color(50,47,33,255) }, + }.ToImmutableDictionary(); + + private void CreateTalentInfo(GUIFrame infoFrame) + { + infoFrame.ClearChildren(); + talentButtons.Clear(); + talentCornerIcons.Clear(); + + Character controlledCharacter = Character.Controlled; + if (controlledCharacter == null) { return; } + + GUIFrame talentFrameBackground = new GUIFrame(new RectTransform(Vector2.One, infoFrame.RectTransform, Anchor.TopCenter), style: "GUIFrameListBox"); + int padding = GUI.IntScale(15); + GUIFrame talentFrameContent = new GUIFrame(new RectTransform(new Point(talentFrameBackground.Rect.Width - padding, talentFrameBackground.Rect.Height - padding), infoFrame.RectTransform, Anchor.Center), style: null); + + GUIFrame paddedTalentFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.9f), talentFrameContent.RectTransform, Anchor.Center), style: null); + + GUIFrame talentFrameMain = new GUIFrame(new RectTransform(Vector2.One, paddedTalentFrame.RectTransform), style: null); + + GUIFrame characterSettingsFrame = null; + GUILayoutGroup characterLayout = null; + if (!(GameMain.NetworkMember is null)) + { + characterSettingsFrame = new GUIFrame(new RectTransform(Vector2.One, talentFrameContent.RectTransform), style: null) { Visible = false }; + characterLayout = new GUILayoutGroup(new RectTransform(Vector2.One, characterSettingsFrame.RectTransform)); + GUIFrame containerFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.9f), characterLayout.RectTransform), style: null); + GUIFrame playerFrame = new GUIFrame(new RectTransform(new Vector2(0.9f, 0.7f), containerFrame.RectTransform, Anchor.Center), style: null); + GameMain.NetLobbyScreen.CreatePlayerFrame(playerFrame, alwaysAllowEditing: true, createPendingText: false); + } + + if (controlledCharacter.Info is null) + { + DebugConsole.ThrowError("No character info found for talent UI"); + return; + } + + selectedTalents = controlledCharacter.Info.GetUnlockedTalentsInTree().ToList(); + + GUILayoutGroup talentFrameLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f), talentFrameMain.RectTransform, anchor: Anchor.Center), childAnchor: Anchor.TopCenter) + { + AbsoluteSpacing = GUI.IntScale(5) + }; + + GUILayoutGroup talentInfoLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.25f), talentFrameLayoutGroup.RectTransform, Anchor.Center), isHorizontal: true); + + CharacterInfo info = controlledCharacter.Info; + Job job = info.Job; + + new GUICustomComponent(new RectTransform(new Vector2(0.25f, 1f), talentInfoLayoutGroup.RectTransform), onDraw: (batch, component) => + { + float posY = component.Rect.Center.Y - component.Rect.Width / 2; + info.DrawPortrait(batch, new Vector2(component.Rect.X, posY), Vector2.Zero, component.Rect.Width, false, false); + }); + + GUILayoutGroup nameLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.3f, 1f), talentInfoLayoutGroup.RectTransform)) { RelativeSpacing = 0.05f }; + + Vector2 nameSize = GUI.SubHeadingFont.MeasureString(info.Name); + GUITextBlock nameBlock = new GUITextBlock(new RectTransform(Vector2.One, nameLayout.RectTransform), info.Name, font: GUI.SubHeadingFont) { TextColor = job.Prefab.UIColor }; + nameBlock.RectTransform.NonScaledSize = nameSize.Pad(nameBlock.Padding).ToPoint(); + + Vector2 jobSize = GUI.SmallFont.MeasureString(job.Name); + GUITextBlock jobBlock = new GUITextBlock(new RectTransform(Vector2.One, nameLayout.RectTransform), job.Name, font: GUI.SmallFont) { TextColor = job.Prefab.UIColor }; + jobBlock.RectTransform.NonScaledSize = jobSize.Pad(jobBlock.Padding).ToPoint(); + + string traitString = TextManager.AddPunctuation(':', TextManager.Get("PersonalityTrait"), TextManager.Get("personalitytrait." + info.PersonalityTrait.Name.Replace(" ", ""))); + Vector2 traitSize = GUI.SmallFont.MeasureString(traitString); + GUITextBlock traitBlock = new GUITextBlock(new RectTransform(Vector2.One, nameLayout.RectTransform), traitString, font: GUI.SmallFont); + traitBlock.RectTransform.NonScaledSize = traitSize.Pad(traitBlock.Padding).ToPoint(); + + GUIFrame endocrineFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.35f), nameLayout.RectTransform, Anchor.BottomCenter), style: null); + + if (!(GameMain.NetworkMember is null)) + { + GUIButton newCharacterBox = new GUIButton(new RectTransform(new Vector2(0.675f, 1f), endocrineFrame.RectTransform, Anchor.TopLeft), text: GameMain.NetLobbyScreen.CampaignCharacterDiscarded ? TextManager.Get("settings") : TextManager.Get("createnew")) + { + IgnoreLayoutGroups = true + }; + + newCharacterBox.OnClicked = (button, o) => + { + if (!GameMain.NetLobbyScreen.CampaignCharacterDiscarded) + { + GameMain.NetLobbyScreen.TryDiscardCampaignCharacter(() => + { + newCharacterBox.Text = TextManager.Get("settings"); + + if (pendingChangesFrame != null) + { + NetLobbyScreen.CreateChangesPendingFrame(pendingChangesFrame); + } + OpenMenu(); + }); + return true; + } + + OpenMenu(); + return true; + + void OpenMenu() + { + characterSettingsFrame!.Visible = true; + talentFrameMain.Visible = false; + } + }; + + if (!(characterLayout is null)) + { + GUILayoutGroup characterCloseButtonLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.1f), characterLayout.RectTransform), childAnchor: Anchor.BottomRight); + new GUIButton(new RectTransform(new Vector2(0.4f, 1f), characterCloseButtonLayout.RectTransform), TextManager.Get("ApplySettingsButton")) //TODO: Is this text appropriate for this circumstance for all languages? + { + OnClicked = (button, o) => + { + characterSettingsFrame!.Visible = false; + talentFrameMain.Visible = true; + return true; + } + }; + } + } + + IEnumerable endocrineTalents = info.GetEndocrineTalents().Select(e => TalentPrefab.TalentPrefabs.Find(c => c.Identifier.Equals(e, StringComparison.OrdinalIgnoreCase))); + + if (endocrineTalents.Count() > 0) + { + GUIImage endocrineIcon = new GUIImage(new RectTransform(new Vector2(0.275f, 1f), endocrineFrame.RectTransform, anchor: Anchor.TopRight, scaleBasis: ScaleBasis.Normal), style: "EndocrineReminderIcon") + { + ToolTip = $"{TextManager.Get("afflictionname.endocrineboost")}\n\n{string.Join(", ", endocrineTalents.Select(e => e.DisplayName))}" + }; + } + + GUILayoutGroup skillLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.45f, 1f), talentInfoLayoutGroup.RectTransform)) { Stretch = true }; + + string skillString = TextManager.Get("skills"); + Vector2 skillSize = GUI.SubHeadingFont.MeasureString(skillString); + GUITextBlock skillBlock = new GUITextBlock(new RectTransform(Vector2.One, skillLayout.RectTransform), skillString, font: GUI.SubHeadingFont); + skillBlock.RectTransform.NonScaledSize = skillSize.Pad(skillBlock.Padding).ToPoint(); + + skillListBox = new GUIListBox(new RectTransform(new Vector2(1f, 1f - skillBlock.RectTransform.RelativeSize.Y), skillLayout.RectTransform), style: null); + CreateTalentSkillList(controlledCharacter, skillListBox); + + if (!TalentTree.JobTalentTrees.TryGetValue(controlledCharacter.Info.Job.Prefab.Identifier, out TalentTree talentTree)) { return; } + + new GUIFrame(new RectTransform(new Vector2(1f, 1f), talentFrameLayoutGroup.RectTransform), style: "HorizontalLine"); + + GUIListBox talentTreeListBox = new GUIListBox(new RectTransform(new Vector2(1f, 0.7f), talentFrameLayoutGroup.RectTransform, Anchor.TopCenter), isHorizontal: true, style: null); + + List subTreeNames = new List(); + foreach (var subTree in talentTree.TalentSubTrees) + { + GUIFrame subTreeFrame = new GUIFrame(new RectTransform(new Vector2(0.333f, 1f), talentTreeListBox.Content.RectTransform, anchor: Anchor.TopLeft), style: null); + GUILayoutGroup subTreeLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1f, 1f), subTreeFrame.RectTransform, Anchor.Center), false, childAnchor: Anchor.TopCenter); + + GUIFrame subtreeTitleFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.111f), subTreeLayoutGroup.RectTransform, anchor: Anchor.TopCenter), style: null); + int elementPadding = GUI.IntScale(8); + Point headerSize = subtreeTitleFrame.RectTransform.NonScaledSize; + GUIFrame subTreeTitleBackground = new GUIFrame(new RectTransform(new Point(headerSize.X - elementPadding, headerSize.Y), subtreeTitleFrame.RectTransform, anchor: Anchor.Center), style: "SubtreeHeader"); + subTreeNames.Add(new GUITextBlock(new RectTransform(Vector2.One, subTreeTitleBackground.RectTransform, anchor: Anchor.TopCenter), subTree.DisplayName, font: GUI.SubHeadingFont, textAlignment: Alignment.Center)); + + for (int i = 0; i < 4; i++) + { + GUIFrame talentOptionFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.222f), subTreeLayoutGroup.RectTransform, anchor: Anchor.TopCenter), style: null); + + Point talentFrameSize = talentOptionFrame.RectTransform.NonScaledSize; + + GUIFrame talentBackground = new GUIFrame(new RectTransform(new Point(talentFrameSize.X - elementPadding, talentFrameSize.Y - elementPadding), talentOptionFrame.RectTransform, anchor: Anchor.Center), style: "TalentBackground"); + GUIFrame talentBackgroundHighlight = new GUIFrame(new RectTransform(Vector2.One, talentBackground.RectTransform, anchor: Anchor.Center), style: "TalentBackgroundGlow") { Visible = false }; + + GUIImage cornerIcon = new GUIImage(new RectTransform(new Vector2(0.2f), talentOptionFrame.RectTransform, anchor: Anchor.BottomRight, scaleBasis: ScaleBasis.BothHeight) { MaxSize = new Point(16) }, style: null) + { + CanBeFocused = false + }; + + Point iconSize = cornerIcon.RectTransform.NonScaledSize; + cornerIcon.RectTransform.AbsoluteOffset = new Point(iconSize.X / 2, iconSize.Y / 2); + + if (subTree.TalentOptionStages.Count > i) + { + TalentOption talentOption = subTree.TalentOptionStages[i]; + + GUILayoutGroup talentOptionCenterGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.75f, 0.7f), talentOptionFrame.RectTransform, Anchor.Center), childAnchor: Anchor.CenterLeft); + + GUILayoutGroup talentOptionLayoutGroup = new GUILayoutGroup(new RectTransform(Vector2.One, talentOptionCenterGroup.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft) { Stretch = true }; + + foreach (TalentPrefab talent in talentOption.Talents) + { + GUIFrame talentFrame = new GUIFrame(new RectTransform(Vector2.One, talentOptionLayoutGroup.RectTransform), style: null) + { + CanBeFocused = false + }; + + GUIFrame croppedTalentFrame = new GUIFrame(new RectTransform(Vector2.One, talentFrame.RectTransform, anchor: Anchor.Center, scaleBasis: ScaleBasis.BothHeight), style: null); + + GUIButton talentButton = new GUIButton(new RectTransform(Vector2.One, croppedTalentFrame.RectTransform, anchor: Anchor.Center), style: null) + { + ToolTip = $"{talent.DisplayName}\n\n{talent.Description}", + UserData = talent.Identifier, + PressedColor = pressedColor, + OnClicked = (button, userData) => + { + // deselect other buttons in tier by removing their selected talents from pool + foreach (GUIButton guiButton in talentOptionLayoutGroup.GetAllChildren()) + { + if (guiButton.UserData is string otherTalentIdentifier && guiButton != button) + { + if (!controlledCharacter.HasTalent(otherTalentIdentifier)) + { + selectedTalents.Remove(otherTalentIdentifier); + } + } + } + string talentIdentifier = userData as string; + + if (TalentTree.IsViableTalentForCharacter(controlledCharacter, talentIdentifier, selectedTalents)) + { + if (!selectedTalents.Contains(talentIdentifier)) + { + selectedTalents.Add(talentIdentifier); + } + } + else if (!controlledCharacter.HasTalent(talentIdentifier)) + { + selectedTalents.Remove(talentIdentifier); + } + + UpdateTalentButtons(); + return true; + }, + }; + + talentButton.Color = talentButton.HoverColor = talentButton.PressedColor = talentButton.SelectedColor = Color.Transparent; + + GUIComponent iconImage; + if (talent.Icon is null) + { + iconImage = new GUITextBlock(new RectTransform(Vector2.One, talentButton.RectTransform, anchor: Anchor.Center), text: "???", font: GUI.LargeFont, textAlignment: Alignment.Center, style: null) + { + OutlineColor = GUI.Style.Red, + TextColor = GUI.Style.Red, + PressedColor = unselectableColor, + CanBeFocused = false, + }; + } + else + { + iconImage = new GUIImage(new RectTransform(Vector2.One, talentButton.RectTransform, anchor: Anchor.Center), sprite: talent.Icon, scaleToFit: true) + { + PressedColor = unselectableColor, + CanBeFocused = false, + }; + } + + talentButtons.Add((talentButton, iconImage)); + } + + talentCornerIcons.Add((subTree.Identifier, i, cornerIcon, talentBackground, talentBackgroundHighlight)); + } + } + } + GUITextBlock.AutoScaleAndNormalize(subTreeNames); + + GUILayoutGroup talentBottomFrame = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.07f), talentFrameLayoutGroup.RectTransform, Anchor.TopCenter), isHorizontal: true) { RelativeSpacing = 0.01f }; + + GUILayoutGroup experienceLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.59f, 1f), talentBottomFrame.RectTransform)); + GUIFrame experienceBarFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.5f), experienceLayout.RectTransform), style: null); + + experienceBar = new GUIProgressBar(new RectTransform(new Vector2(1f, 1f), experienceBarFrame.RectTransform, Anchor.CenterLeft), + barSize: controlledCharacter.Info.GetProgressTowardsNextLevel(), color: GUI.Style.Green) + { + IsHorizontal = true + }; + + experienceText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), experienceBarFrame.RectTransform, anchor: Anchor.Center), "", font: GUI.Font, textAlignment: Alignment.CenterRight) + { + Shadow = true + }; + + talentPointText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), experienceLayout.RectTransform, anchor: Anchor.Center), "", font: GUI.SubHeadingFont, parseRichText: true, textAlignment: Alignment.CenterRight) { AutoScaleVertical = true }; + + talentResetButton = new GUIButton(new RectTransform(new Vector2(0.19f, 1f), talentBottomFrame.RectTransform), text: TextManager.Get("reset"), style: "GUIButtonFreeScale") + { + OnClicked = ResetTalentSelection + }; + talentApplyButton = new GUIButton(new RectTransform(new Vector2(0.19f, 1f), talentBottomFrame.RectTransform), text: TextManager.Get("applysettingsbutton"), style: "GUIButtonFreeScale") + { + OnClicked = ApplyTalentSelection, + }; + GUITextBlock.AutoScaleAndNormalize(talentResetButton.TextBlock, talentApplyButton.TextBlock); + + UpdateTalentButtons(); + } + + private void CreateTalentSkillList(Character character, GUIListBox parent) + { + parent.Content.ClearChildren(); + List skillNames = new List(); + foreach (Skill skill in character.Info.Job.Skills) + { + GUILayoutGroup skillContainer = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.2f), parent.Content.RectTransform), isHorizontal: true) { CanBeFocused = false }; + + skillNames.Add(new GUITextBlock(new RectTransform(new Vector2(0.7f, 1f), skillContainer.RectTransform), TextManager.Get($"skillname.{skill.Identifier}", returnNull: true) ?? skill.Identifier)); + new GUITextBlock(new RectTransform(new Vector2(0.15f, 1.0f), skillContainer.RectTransform), Math.Floor(skill.Level).ToString("F0"), textAlignment: Alignment.CenterRight) { Padding = new Vector4(0, 0, 4, 0) }; + + float modifiedSkillLevel = character.GetSkillLevel(skill.Identifier); + if (!MathUtils.NearlyEqual(MathF.Floor(modifiedSkillLevel), MathF.Floor(skill.Level))) + { + int skillChange = (int)MathF.Floor(modifiedSkillLevel - skill.Level); + string stringColor = true switch + { + true when skillChange > 0 => XMLExtensions.ColorToString(GUI.Style.Green), + true when skillChange < 0 => XMLExtensions.ColorToString(GUI.Style.Red), + _ => XMLExtensions.ColorToString(GUI.Style.TextColor) + }; + + string changeText = $"(‖color:{stringColor}‖{(skillChange > 0 ? "+" : string.Empty) + skillChange}‖color:end‖)"; + new GUITextBlock(new RectTransform(new Vector2(0.15f, 1.0f), skillContainer.RectTransform), changeText, parseRichText: true) { Padding = Vector4.Zero }; + } + skillContainer.Recalculate(); + } + + parent.RecalculateChildren(); + GUITextBlock.AutoScaleAndNormalize(skillNames); + } + + private void UpdateTalentButtons() + { + Character controlledCharacter = Character.Controlled; + + experienceText.Text = $"{controlledCharacter.Info.ExperiencePoints - controlledCharacter.Info.GetExperienceRequiredForCurrentLevel()} / {controlledCharacter.Info.GetExperienceRequiredToLevelUp() - controlledCharacter.Info.GetExperienceRequiredForCurrentLevel()}"; + experienceBar.BarSize = controlledCharacter.Info.GetProgressTowardsNextLevel(); + //experienceBar.ToolTip = $"{controlledCharacter.Info.ExperiencePoints - controlledCharacter.Info.GetExperienceRequiredForCurrentLevel()} / {controlledCharacter.Info.GetExperienceRequiredToLevelUp() - controlledCharacter.Info.GetExperienceRequiredForCurrentLevel()}"; + + selectedTalents = TalentTree.CheckTalentSelection(controlledCharacter, selectedTalents); + + string pointsLeft = controlledCharacter.Info.GetAvailableTalentPoints().ToString(); + + int talentCount = selectedTalents.Count - controlledCharacter.Info.GetUnlockedTalentsInTree().Count(); + + if (talentCount > 0) + { + string pointsUsed = $"‖color:{XMLExtensions.ColorToString(GUI.Style.Red)}‖{-talentCount}‖color:end‖"; + string localizedString = TextManager.GetWithVariables("talentmenu.points.spending", new []{ "[amount]", "[used]" }, new []{ pointsLeft, pointsUsed}); + talentPointText.SetRichText(localizedString); + } + else + { + talentPointText.SetRichText(TextManager.GetWithVariable("talentmenu.points", "[amount]", pointsLeft)); + } + + foreach (var (talentTree, index, icon, frame, glow) in talentCornerIcons) + { + TalentTree.TalentTreeStageState state = TalentTree.GetTalentOptionStageState(controlledCharacter, talentTree, index, selectedTalents); + GUIComponentStyle newStyle = talentStageStyles[state]; + icon.ApplyStyle(newStyle); + icon.Color = newStyle.Color; + frame.Color = talentStageBackgroundColors[state]; + glow.Visible = state == TalentTree.TalentTreeStageState.Highlighted; + } + + foreach (var talentButton in talentButtons) + { + string talentIdentifier = talentButton.button.UserData as string; + bool unselectable = !TalentTree.IsViableTalentForCharacter(controlledCharacter, talentIdentifier, selectedTalents) || controlledCharacter.HasTalent(talentIdentifier); + Color newTalentColor = unselectable ? unselectableColor : unselectedColor; + Color hoverColor = Color.White; + + if (controlledCharacter.HasTalent(talentIdentifier)) + { + newTalentColor = GUI.Style.Green; + } + else if (selectedTalents.Contains(talentIdentifier)) + { + newTalentColor = GUI.Style.Orange; + hoverColor = Color.Lerp(GUI.Style.Orange, Color.White, 0.7f); + } + + talentButton.icon.Color = newTalentColor; + talentButton.icon.HoverColor = hoverColor; + } + + CreateTalentSkillList(controlledCharacter, skillListBox); + } + + private void ApplyTalents(Character controlledCharacter) + { + selectedTalents = TalentTree.CheckTalentSelection(controlledCharacter, selectedTalents); + foreach (string talent in selectedTalents) + { + controlledCharacter.GiveTalent(talent); + if (GameMain.Client != null) + { + GameMain.Client.CreateEntityEvent(controlledCharacter, new object[] { NetEntityEvent.Type.UpdateTalents }); + } + } + UpdateTalentButtons(); + } + + private bool ApplyTalentSelection(GUIButton guiButton, object userData) + { + Character controlledCharacter = Character.Controlled; + ApplyTalents(controlledCharacter); + return true; + } + + private bool ResetTalentSelection(GUIButton guiButton, object userData) + { + Character controlledCharacter = Character.Controlled; + selectedTalents = controlledCharacter.Info.GetUnlockedTalentsInTree().ToList(); + UpdateTalentButtons(); + return true; + } + + public void OnExperienceChanged(Character character) + { + if (character != Character.Controlled) { return; } + UpdateTalentButtons(); + } + + private readonly StatTypes[] basicStats = new StatTypes[] + { + StatTypes.MaximumHealthMultiplier, + StatTypes.MovementSpeed, + StatTypes.SwimmingSpeed, + StatTypes.RepairSpeed, + }; + + private readonly StatTypes[] combatStats = new StatTypes[] + { + StatTypes.MeleeAttackMultiplier, + StatTypes.MeleeAttackSpeed, + StatTypes.RangedAttackSpeed, + StatTypes.TurretAttackSpeed, + }; + + private readonly StatTypes[] miscStats = new StatTypes[] + { + StatTypes.ReputationGainMultiplier, + StatTypes.MissionMoneyGainMultiplier, + StatTypes.ExperienceGainMultiplier, + StatTypes.MissionExperienceGainMultiplier, + }; + + private void CreateCharacterSheet(GUILayoutGroup characterInfoColumn) + { + Character controlledCharacter = Character.Controlled; + + CreateRow(basicStats); + CreateRow(combatStats); + CreateRow(miscStats); + + void CreateRow(StatTypes[] statTypes) + { + GUILayoutGroup characterInfoRow = new GUILayoutGroup(new RectTransform(new Vector2(0.33f, 1.0f), characterInfoColumn.RectTransform, anchor: Anchor.TopLeft), childAnchor: Anchor.TopCenter); + foreach (StatTypes statType in statTypes) + { + ShowStat(statType, characterInfoRow); + } + } + + void ShowStat(StatTypes statType, GUILayoutGroup characterInfoRow) + { + GUIFrame textInfoFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.33f), characterInfoRow.RectTransform, Anchor.TopCenter), style: null); + new GUITextBlock(new RectTransform(new Vector2(1f, 1f), textInfoFrame.RectTransform, Anchor.TopLeft), statType.ToString(), font: GUI.SmallFont, textAlignment: Alignment.TopLeft); + new GUITextBlock(new RectTransform(new Vector2(1f, 1f), textInfoFrame.RectTransform, Anchor.TopLeft), (int)(100f * (1 + controlledCharacter.GetStatValue(statType))) + "%", font: GUI.Font, textAlignment: Alignment.TopRight); + } + } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/UISprite.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/UISprite.cs index a6b3ca8dc..4d79d57fd 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/UISprite.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/UISprite.cs @@ -115,8 +115,9 @@ namespace Barotrauma return MathHelper.Clamp(Math.Min(Math.Min(scale.X, scale.Y), GUI.SlicedSpriteScale), minBorderScale, maxBorderScale); } - public void Draw(SpriteBatch spriteBatch, Rectangle rect, Color color, SpriteEffects spriteEffects = SpriteEffects.None) + public void Draw(SpriteBatch spriteBatch, Rectangle rect, Color color, SpriteEffects spriteEffects = SpriteEffects.None, Vector2? uvOffset = null) { + uvOffset ??= Vector2.Zero; if (Sprite.Texture == null) { GUI.DrawRectangle(spriteBatch, rect, Color.Magenta); @@ -157,7 +158,7 @@ namespace Barotrauma else if (Tile) { Vector2 startPos = new Vector2(rect.X, rect.Y); - Sprite.DrawTiled(spriteBatch, startPos, new Vector2(rect.Width, rect.Height), color); + Sprite.DrawTiled(spriteBatch, startPos, new Vector2(rect.Width, rect.Height), color, startOffset: uvOffset); } else { diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/UpgradeStore.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/UpgradeStore.cs index 083551ad0..9f9234a7e 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/UpgradeStore.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/UpgradeStore.cs @@ -1045,10 +1045,10 @@ namespace Barotrauma public static GUIFrame CreateUpgradeFrame(UpgradePrefab prefab, UpgradeCategory category, CampaignMode campaign, RectTransform rectTransform, bool addBuyButton = true) { int price = prefab.Price.GetBuyprice(campaign.UpgradeManager.GetUpgradeLevel(prefab, category), campaign.Map?.CurrentLocation); - return CreateUpgradeEntry(rectTransform, prefab.Sprite, prefab.Name, prefab.Description, price, new CategoryData(category, prefab), addBuyButton); + return CreateUpgradeEntry(rectTransform, prefab.Sprite, prefab.Name, prefab.Description, price, new CategoryData(category, prefab), addBuyButton, upgradePrefab: prefab, currentLevel: campaign.UpgradeManager.GetUpgradeLevel(prefab, category)); } - public static GUIFrame CreateUpgradeEntry(RectTransform parent, Sprite sprite, string title, string body, int price, object? userData, bool addBuyButton = true, bool addProgressBar = true, string buttonStyle = "UpgradeBuyButton") + public static GUIFrame CreateUpgradeEntry(RectTransform parent, Sprite sprite, string title, string body, int price, object? userData, bool addBuyButton = true, bool addProgressBar = true, string buttonStyle = "UpgradeBuyButton", UpgradePrefab upgradePrefab = null, int currentLevel = 0) { float progressBarHeight = 0.25f; @@ -1089,7 +1089,7 @@ namespace Barotrauma //negative price = refund if (price < 0) { formattedPrice = "+" + formattedPrice; } buyButtonLayout = new GUILayoutGroup(rectT(0.2f, 1, prefabLayout), childAnchor: Anchor.TopCenter) { UserData = "buybutton" }; - var priceText = new GUITextBlock(rectT(1, 0.4f, buyButtonLayout), formattedPrice, textAlignment: Alignment.Center); + var priceText = new GUITextBlock(rectT(1, 0.2f, buyButtonLayout), formattedPrice, textAlignment: Alignment.Center); if (price < 0) { priceText.TextColor = GUI.Style.Green; @@ -1099,6 +1099,11 @@ namespace Barotrauma priceText.Text = string.Empty; } new GUIButton(rectT(0.7f, 0.5f, buyButtonLayout), string.Empty, style: buttonStyle) { Enabled = false }; + if (upgradePrefab != null) + { + var increaseText = new GUITextBlock(rectT(1, 0.2f, buyButtonLayout), "", textAlignment: Alignment.Center); + UpdateUpgradePercentageText(increaseText, upgradePrefab, currentLevel); + } } description.CalculateHeightFromText(); @@ -1127,6 +1132,19 @@ namespace Barotrauma return prefabFrame; } + private static void UpdateUpgradePercentageText(GUITextBlock text, UpgradePrefab upgradePrefab, int currentLevel) + { + float nextIncrease = upgradePrefab.IncreaseOnTooltip * (Math.Min(currentLevel + 1, upgradePrefab.MaxLevel)); + if (nextIncrease != 0f) + { + text.Text = $"{Math.Round(nextIncrease, 1)} %"; + if (currentLevel == upgradePrefab.MaxLevel) + { + text.TextColor = Color.Gray; + } + } + } + private void CreateUpgradeEntry(UpgradePrefab prefab, UpgradeCategory category, GUIComponent parent, List? itemsOnSubmarine) { if (Campaign is null) { return; } @@ -1541,7 +1559,9 @@ namespace Barotrauma if (prefabFrame.FindChild("buybutton", true) is { } buttonParent) { - GUITextBlock priceLabel = buttonParent.GetChild(); + List textBlocks = buttonParent.GetAllChildren().ToList(); + + GUITextBlock priceLabel = textBlocks[0]; int price = prefab.Price.GetBuyprice(campaign.UpgradeManager.GetUpgradeLevel(prefab, category), campaign.Map?.CurrentLocation); if (priceLabel != null && !WaitForServerUpdate) @@ -1562,6 +1582,11 @@ namespace Barotrauma button.Enabled = false; } } + GUITextBlock increaseLabel = textBlocks[1]; + if (increaseLabel != null && !WaitForServerUpdate) + { + UpdateUpgradePercentageText(increaseLabel, prefab, currentLevel); + } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs b/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs index 0dbbe81f8..9134bfff2 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs @@ -43,6 +43,7 @@ namespace Barotrauma public static SteamWorkshopScreen SteamWorkshopScreen; public static SubEditorScreen SubEditorScreen; + public static TestScreen TestScreen; public static ParticleEditorScreen ParticleEditorScreen; public static LevelEditorScreen LevelEditorScreen; public static SpriteEditorScreen SpriteEditorScreen; @@ -89,7 +90,16 @@ namespace Barotrauma public static ParticleManager ParticleManager; public static DecalManager DecalManager; - public static World World; + private static World world; + public static World World + { + get + { + if (world == null) { world = new World(new Vector2(0, -9.82f)); } + return world; + } + set { world = value; } + } public static LoadingScreen TitleScreen; private bool loadingScreenOpen; @@ -184,6 +194,8 @@ namespace Barotrauma #if DEBUG public static bool FirstLoad = true; + + public static bool CancelQuickStart; #endif public GameMain(string[] args) @@ -239,7 +251,6 @@ namespace Barotrauma GameMain.ResetFrameTime(); fixedTime = new GameTime(); - World = new World(new Vector2(0, -9.82f)); FarseerPhysics.Settings.AllowSleep = true; FarseerPhysics.Settings.ContinuousPhysics = false; FarseerPhysics.Settings.VelocityIterations = 1; @@ -300,7 +311,7 @@ namespace Barotrauma GraphicsDeviceManager.PreferredBackBufferWidth = GraphicsWidth; GraphicsDeviceManager.PreferredBackBufferHeight = GraphicsHeight; - + GraphicsDeviceManager.ApplyChanges(); if (windowMode == WindowMode.BorderlessWindowed) @@ -567,6 +578,8 @@ namespace Barotrauma ItemPrefab.LoadAll(GetFilesOfType(ContentType.Item)); AfflictionPrefab.LoadAll(GetFilesOfType(ContentType.Afflictions)); SkillSettings.Load(GetFilesOfType(ContentType.SkillSettings)); + TalentPrefab.LoadAll(GetFilesOfType(ContentType.Talents)); + TalentTree.LoadAll(GetFilesOfType(ContentType.TalentTrees)); Order.Init(); EventManagerSettings.Init(); BallastFloraPrefab.LoadAll(GetFilesOfType(ContentType.MapCreature)); @@ -577,7 +590,7 @@ namespace Barotrauma StructurePrefab.LoadAll(GetFilesOfType(ContentType.Structure)); TitleScreen.LoadState = 55.0f; yield return CoroutineStatus.Running; - + UpgradePrefab.LoadAll(GetFilesOfType(ContentType.UpgradeModules)); TitleScreen.LoadState = 56.0f; yield return CoroutineStatus.Running; @@ -590,7 +603,7 @@ namespace Barotrauma ItemAssemblyPrefab.LoadAll(); TitleScreen.LoadState = 60.0f; yield return CoroutineStatus.Running; - + GameModePreset.Init(); SaveUtil.DeleteDownloadedSubs(); @@ -620,6 +633,7 @@ namespace Barotrauma #endif SubEditorScreen = new SubEditorScreen(); + TestScreen = new TestScreen(); TitleScreen.LoadState = 75.0f; yield return CoroutineStatus.Running; @@ -642,7 +656,7 @@ namespace Barotrauma ParticleManager.LoadPrefabs(); TitleScreen.LoadState = 88.0f; LevelObjectPrefab.LoadAll(); - + TitleScreen.LoadState = 90.0f; yield return CoroutineStatus.Running; @@ -792,12 +806,18 @@ namespace Barotrauma } #if DEBUG - if (TitleScreen.LoadState >= 100.0f && !TitleScreen.PlayingSplashScreen && (Config.AutomaticQuickStartEnabled || Config.AutomaticCampaignLoadEnabled) && FirstLoad && !PlayerInput.KeyDown(Keys.LeftShift)) + CancelQuickStart |= PlayerInput.KeyDown(Keys.LeftShift); + + if (TitleScreen.LoadState >= 100.0f && !TitleScreen.PlayingSplashScreen && (Config.AutomaticQuickStartEnabled || Config.AutomaticCampaignLoadEnabled || Config.TestScreenEnabled) && FirstLoad && !CancelQuickStart) { loadingScreenOpen = false; FirstLoad = false; - if (Config.AutomaticQuickStartEnabled) + if (Config.TestScreenEnabled) + { + TestScreen.Select(); + } + else if (Config.AutomaticQuickStartEnabled) { MainMenuScreen.QuickStart(); } @@ -902,8 +922,7 @@ namespace Barotrauma } //open the pause menu if not controlling a character OR if the character has no UIs active that can be closed with ESC else if ((Character.Controlled == null || !itemHudActive()) - //TODO: do we need to check Inventory.SelectedSlot? - && Inventory.SelectedSlot == null && CharacterHealth.OpenHealthWindow == null + && CharacterHealth.OpenHealthWindow == null && !CrewManager.IsCommandInterfaceOpen && !(Screen.Selected is SubEditorScreen editor && !editor.WiringMode && Character.Controlled?.SelectedConstruction != null)) { @@ -914,8 +933,8 @@ namespace Barotrauma static bool itemHudActive() { if (Character.Controlled?.SelectedConstruction == null) { return false; } - return - Character.Controlled.SelectedConstruction.ActiveHUDs.Any(ic => ic.GuiFrame != null) || + return + Character.Controlled.SelectedConstruction.ActiveHUDs.Any(ic => ic.GuiFrame != null) || ((Character.Controlled.ViewTarget as Item)?.Prefab?.FocusOnSelected ?? false); } } @@ -1048,7 +1067,6 @@ namespace Barotrauma spriteBatch.End(); } - sw.Stop(); PerformanceCounter.AddElapsedTicks("Draw total", sw.ElapsedTicks); PerformanceCounter.DrawTimeGraph.Update(sw.ElapsedTicks * 1000.0f / (float)Stopwatch.Frequency); @@ -1079,7 +1097,7 @@ namespace Barotrauma if (save) { GUI.SetSavingIndicatorState(true); - + if (GameSession.Submarine != null && !GameSession.Submarine.Removed) { GameSession.SubmarineInfo = new SubmarineInfo(GameSession.Submarine); @@ -1251,7 +1269,7 @@ namespace Barotrauma string text = TextManager.GetWithVariable("openlinkinbrowserprompt", "[link]", url); string extensionText = TextManager.Get(promptExtensionTag, returnNull: true, useEnglishAsFallBack: false); if (!string.IsNullOrEmpty(extensionText)) - { + { text += $"\n\n{extensionText}"; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/CargoManager.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/CargoManager.cs index cffadc3ee..3010c608a 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/CargoManager.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/CargoManager.cs @@ -48,11 +48,7 @@ namespace Barotrauma var equipmentSlots = new List() { InvSlotType.Head, InvSlotType.InnerClothes, InvSlotType.OuterClothes, InvSlotType.Headset, InvSlotType.Card }; return character.Inventory.FindAllItems(item => { - if (item.SpawnedInOutpost) { return false; } - if (!item.Prefab.AllowSellingWhenBroken && item.ConditionPercentage < 90.0f) { return false; } - if (confirmedSoldEntities.Any(it => it.Item == item)) { return false; } - // There must be no contained items or the contained items must be confirmed as sold - if (!item.ContainedItems.All(it => confirmedSoldEntities.Any(se => se.Item == it))) { return false; } + if (!IsItemSellable(item, confirmedSoldEntities)) { return false; } // Item must be in a non-equipment slot if possible if (!item.AllowedSlots.All(s => equipmentSlots.Contains(s)) && IsInEquipmentSlot(item)) { return false; } // Item must not be contained inside an item in an equipment slot @@ -76,15 +72,10 @@ namespace Barotrauma var confirmedSoldEntities = GetConfirmedSoldEntities(); return Submarine.MainSub.GetItems(true).FindAll(item => { - if (!item.Prefab.CanBeSold) { return false; } - if (item.SpawnedInOutpost) { return false; } - if (!item.Prefab.AllowSellingWhenBroken && item.ConditionPercentage < 90.0f) { return false; } + if (!IsItemSellable(item, confirmedSoldEntities)) { return false; } if (!item.Components.All(c => !(c is Holdable h) || !h.Attachable || !h.Attached)) { return false; } if (!item.Components.All(c => !(c is Wire w) || w.Connections.All(c => c == null))) { return false; } if (!ItemAndAllContainersInteractable(item)) { return false; } - if (confirmedSoldEntities.Any(it => it.Item == item)) { return false; } - // There must be no contained items or the contained items must be confirmed as sold - if (!item.ContainedItems.All(it => confirmedSoldEntities.Any(se => se.Item == it))) { return false; } return true; }).Distinct(); @@ -107,6 +98,24 @@ namespace Barotrauma return SoldEntities.Where(se => se.Status != SoldEntity.SellStatus.Unconfirmed); } + private bool IsItemSellable(Item item, IEnumerable confirmedSoldEntities) + { + if (!item.Prefab.CanBeSold) { return false; } + if (item.SpawnedInOutpost) { return false; } + if (!item.Prefab.AllowSellingWhenBroken && item.ConditionPercentage < 90.0f) { return false; } + if (confirmedSoldEntities.Any(it => it.Item == item)) { return false; } + if (item.OwnInventory?.Container is ItemContainer itemContainer) + { + var containedItems = item.ContainedItems; + if (containedItems.None()) { return true; } + // Allow selling the item if contained items are unsellable and set to be removed on deconstruct + if (itemContainer.RemoveContainedItemsOnDeconstruct && containedItems.All(it => !it.Prefab.CanBeSold)) { return true; } + // Otherwise there must be no contained items or the contained items must be confirmed as sold + if (!containedItems.All(it => confirmedSoldEntities.Any(se => se.Item == it))) { return false; } + } + return true; + } + public void SetItemsInBuyCrate(List items) { ItemsInBuyCrate.Clear(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs index 5cc69eb70..09fe317e9 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs @@ -17,6 +17,13 @@ namespace Barotrauma { private Point screenResolution; + public Order DraggedOrder; + public bool DragOrder; + private bool dropOrder; + private int framesToSkip = 2; + private float dragOrderTreshold; + private Vector2 dragPoint = Vector2.Zero; + #region UI public GUIComponent ReportButtonFrame { get; set; } @@ -93,9 +100,12 @@ namespace Barotrauma { AutoHideScrollBar = false, CanBeFocused = false, + CanDragElements = true, + CanInteractWhenUnfocusable = true, OnSelected = (component, userData) => false, SelectMultiple = false, - Spacing = (int)(GUI.Scale * 10) + Spacing = (int)(GUI.Scale * 10), + OnRearranged = OnCrewListRearranged }; jobIndicatorBackground = new Sprite("Content/UI/CommandUIAtlas.png", new Rectangle(0, 512, 128, 128)); @@ -180,7 +190,7 @@ namespace Barotrauma }; } - var reports = Order.PrefabList.FindAll(o => o.IsReport && o.SymbolSprite != null && !o.Hidden); + List reports = Order.PrefabList.FindAll(o => o.IsReport && o.SymbolSprite != null && !o.Hidden); if (reports.None()) { DebugConsole.ThrowError("No valid orders for report buttons found! Cannot create report buttons. The orders for the report buttons must have 'targetallcharacters' attribute enabled and a valid 'symbolsprite' defined."); @@ -198,25 +208,54 @@ namespace Barotrauma ReportButtonFrame.RectTransform.AbsoluteOffset = new Point(0, -chatBox.ToggleButton.Rect.Height); + CreateReportButtons(this, ReportButtonFrame, reports, false); + + #endregion + + screenResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight); + prevUIScale = GUI.Scale; + _isCrewMenuOpen = GameMain.Config.CrewMenuOpen; + dismissedOrderPrefab ??= Order.GetPrefab("dismissed"); + } + + public static void CreateReportButtons(CrewManager crewManager, GUIComponent parent, List reports, bool isHorizontal) + { //report buttons foreach (Order order in reports) { if (!order.IsReport || order.SymbolSprite == null || order.Hidden) { continue; } - var btn = new GUIButton(new RectTransform(new Point(ReportButtonFrame.Rect.Width), ReportButtonFrame.RectTransform), style: null) + var btn = new GUIButton(new RectTransform(new Point(isHorizontal ? parent.Rect.Height : parent.Rect.Width), parent.RectTransform), style: null) { - OnClicked = (GUIButton button, object userData) => + OnClicked = (button, userData) => { - if (!CanIssueOrders) { return false; } + if (!CanIssueOrders || crewManager?.DraggedOrder != null) { return false; } var sub = Character.Controlled.Submarine; if (sub == null || sub.TeamID != Character.Controlled.TeamID || sub.Info.IsWreck) { return false; } - SetCharacterOrder(null, order, null, CharacterInfo.HighestManualOrderPriority, Character.Controlled); - if (IsSinglePlayer) { HumanAIController.ReportProblem(Character.Controlled, order); } + + if (crewManager != null) + { + crewManager.SetCharacterOrder(null, order, null, CharacterInfo.HighestManualOrderPriority, Character.Controlled); + if (crewManager.IsSinglePlayer) { HumanAIController.ReportProblem(Character.Controlled, order); } + } return true; }, UserData = order, - ToolTip = order.Name, ClampMouseRectToParent = false }; + btn.ToolTip = $"‖color:{XMLExtensions.ColorToString(order.Prefab.Color)}‖{order.Name}‖color:end‖\n{TextManager.Get("draganddropreports")}"; + + if (crewManager != null) + { + btn.OnButtonDown = () => + { + crewManager.dragOrderTreshold = Math.Max(btn.Rect.Width, btn.Rect.Height) / 2f; + crewManager.DraggedOrder = order; + crewManager.dropOrder = false; + crewManager.framesToSkip = 2; + crewManager.dragPoint = btn.Rect.Center.ToVector2(); + return true; + }; + } new GUIFrame(new RectTransform(new Vector2(1.5f), btn.RectTransform, Anchor.Center), "OuterGlowCircular") { @@ -232,17 +271,11 @@ namespace Barotrauma { Color = order.Color, HoverColor = Color.Lerp(order.Color, Color.White, 0.5f), - ToolTip = order.Name, - SpriteEffects = SpriteEffects.FlipHorizontally + ToolTip = btn.RawToolTip, + SpriteEffects = SpriteEffects.FlipHorizontally, + UserData = order }; } - - #endregion - - screenResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight); - prevUIScale = GUI.Scale; - _isCrewMenuOpen = GameMain.Config.CrewMenuOpen; - dismissedOrderPrefab ??= Order.GetPrefab("dismissed"); } #endregion @@ -291,8 +324,19 @@ namespace Barotrauma new RectTransform(crewListEntrySize, parent: crewList.Content.RectTransform, anchor: Anchor.TopRight), style: "CrewListBackground") { - UserData = character + UserData = character, + OnSecondaryClicked = (comp, data) => + { + if (data == null) { return false; } + if (GameMain.NetworkMember?.ConnectedClients?.Find(c => c.Character == data) is Client client) + { + CreateModerationContextMenu(PlayerInput.MousePosition.ToPoint(), client); + return true; + } + return false; + } }; + SetCharacterComponentTooltip(background); var iconRelativeWidth = (float)crewListEntrySize.Y / background.Rect.Width; @@ -310,7 +354,10 @@ namespace Barotrauma var paddingRelativeWidth = 0.35f * commandButtonAbsoluteHeight / background.Rect.Width; // "Padding" to prevent member-specific command button from overlapping job indicator - new GUIFrame(new RectTransform(new Vector2(paddingRelativeWidth, 1.0f), layoutGroup.RectTransform), style: null); + new GUIFrame(new RectTransform(new Vector2(paddingRelativeWidth, 1.0f), layoutGroup.RectTransform), style: null) + { + CanBeFocused = false + }; var jobIconBackground = new GUIImage( new RectTransform(new Vector2(0.8f * iconRelativeWidth, 0.8f), layoutGroup.RectTransform), @@ -320,7 +367,6 @@ namespace Barotrauma CanBeFocused = false, UserData = "job" }; - if (character?.Info?.Job.Prefab?.Icon != null) { new GUIImage( @@ -362,36 +408,6 @@ namespace Barotrauma }; nameBlock.Text = ToolBox.LimitString(character.Name, font, (int)nameBlock.Rect.Width); - var nameActualRealtiveWidth = Math.Min(nameRelativeWidth * background.Rect.Width, 150) / background.Rect.Width; - var characterButton = new GUIButton( - new RectTransform( - new Vector2(paddingRelativeWidth + 0.8f * iconRelativeWidth + nameActualRealtiveWidth + 2 * layoutGroup.RelativeSpacing, 1.0f), - background.RectTransform), - style: null) - { - UserData = character, - OnSecondaryClicked = (comp, data) => - { - if (data == null) { return false; } - if (GameMain.NetworkMember?.ConnectedClients?.Find(c => c.Character == data) is Client client) - { - CreateModerationContextMenu(PlayerInput.MousePosition.ToPoint(), client); - return true; - } - return false; - } - }; - SetCharacterButtonTooltip(characterButton); - - if (IsSinglePlayer) - { - characterButton.OnClicked = CharacterClicked; - } - else - { - characterButton.CanBeSelected = false; - } - new GUIImage( new RectTransform(new Vector2(0.1f * iconRelativeWidth, 0.5f), layoutGroup.RectTransform), style: "VerticalLine") @@ -487,15 +503,15 @@ namespace Barotrauma }; } - private void SetCharacterButtonTooltip(GUIButton characterButton) + private void SetCharacterComponentTooltip(GUIComponent characterComponent) { - var character = (Character)characterButton.UserData; - if (character?.Info?.Job?.Prefab == null) { return; } + if (!(characterComponent?.UserData is Character character)) { return; } + if (character.Info?.Job?.Prefab == null) { return; } string color = XMLExtensions.ColorToString(character.Info.Job.Prefab.UIColor); string tooltip = $"‖color:{color}‖{character.Name} ({character.Info.Job.Name})‖color:end‖"; var richTextData = RichTextData.GetRichTextData(tooltip, out string sanitizedTooltip); - characterButton.ToolTip = sanitizedTooltip; - characterButton.TooltipRichTextData = richTextData; + characterComponent.ToolTip = sanitizedTooltip; + characterComponent.TooltipRichTextData = richTextData; } /// @@ -564,10 +580,18 @@ namespace Barotrauma partial void RenameCharacterProjSpecific(CharacterInfo characterInfo) { if (!(crewList.Content.GetChildByUserData(characterInfo?.Character) is GUIComponent characterComponent)) { return; } + SetCharacterComponentTooltip(characterComponent); if (!(characterComponent.FindChild("name", recursive: true) is GUITextBlock nameBlock)) { return; } nameBlock.Text = ToolBox.LimitString(characterInfo.Name, nameBlock.Font, nameBlock.Rect.Width); - if (!(characterComponent.FindChild(c => c is GUIButton && c.UserData == characterInfo?.Character) is GUIButton characterButton)) { return; } - SetCharacterButtonTooltip(characterButton); + } + + private void OnCrewListRearranged(GUIListBox crewList, object draggedElementData) + { + if (crewList != this.crewList) { return; } + if (!(draggedElementData is Character)) { return; } + if (crewList.HasDraggedElementIndexChanged) { return; } + if (!IsSinglePlayer) { return; } + CharacterClicked(crewList.DraggedElement, draggedElementData); } #endregion @@ -682,18 +706,18 @@ namespace Barotrauma /// Sets the character's current order (if it's close enough to receive messages from orderGiver) and /// displays the order in the crew UI /// - public void SetCharacterOrder(Character character, Order order, string option, int priority, Character orderGiver) + public void SetCharacterOrder(Character character, Order order, string option, int priority, Character orderGiver, Hull targetHull = null) { if (order != null && order.TargetAllCharacters) { - Hull hull = null; + Hull hull = targetHull; if (order.IsReport) { - if (orderGiver?.CurrentHull == null) { return; } - hull = orderGiver.CurrentHull; + if (orderGiver?.CurrentHull == null && hull == null) { return; } + hull ??= orderGiver.CurrentHull; AddOrder(new Order(order.Prefab ?? order, hull, null, orderGiver), order.FadeOutTime); } - else if(order.IsIgnoreOrder) + else if (order.IsIgnoreOrder) { WallSection ws = null; if (order.TargetType == Order.OrderTargetType.Entity && order.TargetEntity is IIgnorable ignorable) @@ -748,7 +772,8 @@ namespace Barotrauma if (IsSinglePlayer) { character.SetOrder(order, option, priority, orderGiver, speak: orderGiver != character); - orderGiver?.Speak(order?.GetChatMessage(character.Name, orderGiver.CurrentHull?.DisplayName, givingOrderToSelf: character == orderGiver, orderOption: option)); + string message = order?.GetChatMessage(character.Name, orderGiver?.CurrentHull?.DisplayName, givingOrderToSelf: character == orderGiver, orderOption: option, priority: priority); + orderGiver?.Speak(message); } else if (orderGiver != null) { @@ -1299,6 +1324,80 @@ namespace Barotrauma return 0; } + private bool CreateOrder(Order order, Hull targetHull = null) + { + var sub = Character.Controlled.Submarine; + + if (sub == null || sub.TeamID != Character.Controlled.TeamID || sub.Info.IsWreck) { return false; } + + SetCharacterOrder(null, order, null, CharacterInfo.HighestManualOrderPriority, Character.Controlled, targetHull); + + if (IsSinglePlayer) + { + HumanAIController.ReportProblem(Character.Controlled, order); + } + + return true; + } + + private void UpdateOrderDrag() + { + if (DraggedOrder is { } order) + { + if (dropOrder) + { + // stinky workaround + if (framesToSkip > 0) + { + framesToSkip--; + } + else + { + Hull hull = null; + + if (GUI.MouseOn is GUIFrame frame) + { + if (frame.UserData is Hull data) + { + hull = data; + } + else if (frame.Parent?.UserData is Hull parentData) + { + hull = parentData; + } + } + + framesToSkip = 2; + dropOrder = false; + DraggedOrder = null; + + if (hull is null && GUI.MouseOn is { Visible: true, CanBeFocused: true }) { return; } + + hull ??= Hull.hullList.FirstOrDefault(h => h.WorldRect.ContainsWorld(Screen.Selected.Cam.ScreenToWorld(PlayerInput.MousePosition))); + CreateOrder(order, hull); + } + } + else + { + DragOrder = DragOrder || Vector2.DistanceSquared(dragPoint, PlayerInput.MousePosition) > dragOrderTreshold * dragOrderTreshold; + + if (!PlayerInput.PrimaryMouseButtonHeld()) + { + if (DragOrder) + { + dropOrder = true; + } + else + { + DraggedOrder = null; + } + dragPoint = Vector2.Zero; + DragOrder = false; + } + } + } + } + partial void UpdateProjectSpecific(float deltaTime) { // Quick selection @@ -1316,6 +1415,8 @@ namespace Barotrauma if (GUI.DisableHUD) { return; } + UpdateOrderDrag(); + #region Command UI WasCommandInterfaceDisabledThisUpdate = false; @@ -1756,7 +1857,8 @@ namespace Barotrauma } } private GUIFrame commandFrame, targetFrame; - private GUIButton centerNode, returnNode, expandNode, shortcutCenterNode; + private GUIButton centerNode, returnNode, expandNode; + private GUIFrame shortcutCenterNode; private readonly List> optionNodes = new List>(); private Keys returnNodeHotkey = Keys.None, expandNodeHotkey = Keys.None; private readonly List shortcutNodes = new List(); @@ -1790,7 +1892,7 @@ namespace Barotrauma private bool isContextual; private readonly List contextualOrders = new List(); private Point shorcutCenterNodeOffset; - private const int maxShortCutNodeCount = 4; + private const int maxShortcutNodeCount = 4; private bool WasCommandInterfaceDisabledThisUpdate { get; set; } public static bool CanIssueOrders @@ -1805,13 +1907,23 @@ namespace Barotrauma } } - private bool CanSomeoneHearCharacter() + private bool CanCharacterBeHeard() { #if DEBUG if (Character.Controlled == null) { return true; } #endif - return Character.Controlled != null && - (characters.Any(c => c != Character.Controlled && c.CanHearCharacter(Character.Controlled)) || GetOrderableFriendlyNPCs().Any(c => c != Character.Controlled && c.CanHearCharacter(Character.Controlled))); + if (Character.Controlled != null) + { + if (characterContext == null) + { + return characters.Any(c => c != Character.Controlled && c.CanHearCharacter(Character.Controlled)) || GetOrderableFriendlyNPCs().Any(c => c != Character.Controlled && c.CanHearCharacter(Character.Controlled)); + } + else + { + return characterContext.CanHearCharacter(Character.Controlled); + } + } + return false; } private Entity FindEntityContext() @@ -2101,6 +2213,8 @@ namespace Barotrauma shortcutNodes.Remove(node); }; RemoveOptionNodes(); + bool wasMinimapVisible = targetFrame != null && targetFrame.Visible; + HideMinimap(); if (returnNode != null) { @@ -2111,12 +2225,9 @@ namespace Barotrauma } // When the mini map is shown, always position the return node on the bottom - List matchingItems = null; - if (node?.UserData is Order order) - { - matchingItems = order.GetMatchingItems(true, interactableFor: characterContext ?? Character.Controlled); - } - var offset = matchingItems != null && matchingItems.Count > 1 ? + bool placeReturnNodeOnTheBottom = wasMinimapVisible || + (node?.UserData is Order order && order.GetMatchingItems(true, interactableFor: characterContext ?? Character.Controlled).Count > 1); + var offset = placeReturnNodeOnTheBottom ? new Point(0, (int)(returnNodeDistanceModifier * nodeDistance)) : node.RectTransform.AbsoluteOffset.Multiply(-returnNodeDistanceModifier); SetReturnNode(centerNode, offset); @@ -2137,12 +2248,7 @@ namespace Barotrauma { if (commandFrame == null) { return false; } RemoveOptionNodes(); - if (targetFrame != null) - { - targetFrame.Visible = false; - nodeConnectors.RectTransform.Parent = commandFrame.RectTransform; - nodeConnectors.RectTransform.RepositionChildInHierarchy(1); - } + HideMinimap(); // TODO: Center node could move to option node instead of being removed commandFrame.RemoveChild(centerNode); SetCenterNode(node); @@ -2163,6 +2269,15 @@ namespace Barotrauma return true; } + private void HideMinimap() + { + if (targetFrame == null || !targetFrame.Visible) { return; } + targetFrame.Visible = false; + // Reset the node connectors to their original parent + nodeConnectors.RectTransform.Parent = commandFrame.RectTransform; + nodeConnectors.RectTransform.RepositionChildInHierarchy(1); + } + private void CreateReturnNodeHotkey() { if (returnNode != null && returnNode.Visible) @@ -2203,6 +2318,7 @@ namespace Barotrauma } node.OnClicked = null; node.OnSecondaryClicked = null; + node.CanBeFocused = false; centerNode = node; } @@ -2219,6 +2335,7 @@ namespace Barotrauma } node.OnClicked = NavigateBackward; node.OnSecondaryClicked = null; + node.CanBeFocused = true; returnNode = node; } @@ -2240,9 +2357,33 @@ namespace Barotrauma { CreateOrderNodes(category); } - else if (userData is Order order) + else if (userData is Order nodeOrder) { - CreateOrderOptions(order); + Submarine submarine = GetTargetSubmarine(); + List matchingItems = null; + if (itemContext == null && nodeOrder.MustSetTarget) + { + matchingItems = nodeOrder.GetMatchingItems(submarine, true, interactableFor: characterContext ?? Character.Controlled); + } + //more than one target item -> create a minimap-like selection with a pic of the sub + if (itemContext == null && !(nodeOrder.TargetEntity is Item) && matchingItems != null && matchingItems.Count > 1) + { + CreateMinimapNodes(nodeOrder, submarine, matchingItems); + } + //only one target (or an order with no particular targets), just show options + else + { + CreateOrderOptionNodes(nodeOrder, itemContext ?? nodeOrder.TargetEntity as Item ?? matchingItems?.FirstOrDefault()); + } + } + else if (userData is (Order minimapOrder, string option) && minimapOrder.HasOptions && string.IsNullOrEmpty(option)) + { + CreateOrderOptionNodes(minimapOrder, minimapOrder.TargetEntity as Item); + } + else + { + DebugConsole.ThrowError($"Unexpected node user data of type {userData.GetType()} when creating command interface nodes"); + return false; } return true; } @@ -2291,7 +2432,7 @@ namespace Barotrauma node.RectTransform.MoveOverTime(offset, CommandNodeAnimDuration); if (Order.OrderCategoryIcons.TryGetValue(category, out Tuple sprite)) { - var tooltip = TextManager.Get("ordercategorytitle." + category.ToString().ToLower()); + var tooltip = TextManager.Get("ordercategorytitle." + category.ToString().ToLowerInvariant()); var categoryDescription = TextManager.Get("ordercategorydescription." + category.ToString(), true); if (!string.IsNullOrWhiteSpace(categoryDescription)) { tooltip += "\n" + categoryDescription; } CreateNodeIcon(Vector2.One, node.RectTransform, sprite.Item1, sprite.Item2, tooltip: tooltip); @@ -2302,99 +2443,91 @@ namespace Barotrauma private void CreateShortcutNodes() { - bool HasAppropriateJob(Character c, string jobId) => c.Info?.Job != null && c.Info.Job.Prefab.AppropriateOrders.Contains(jobId); - - Submarine sub = GetTargetSubmarine(); - + var sub = GetTargetSubmarine(); if (sub == null) { return; } - shortcutNodes.Clear(); - - if (shortcutNodes.Count < maxShortCutNodeCount && - sub.GetItems(false).Find(i => i.HasTag("reactor") && i.IsPlayerTeamInteractable)?.GetComponent() is Reactor reactor) + if (CanFitMoreNodes() && sub.GetItems(false).Find(i => i.HasTag("reactor") && i.IsPlayerTeamInteractable)?.GetComponent() is Reactor reactor) { - var reactorOutput = -reactor.CurrPowerConsumption; + float reactorOutput = -reactor.CurrPowerConsumption; // If player is not an engineer AND the reactor is not powered up AND nobody is using the reactor // ---> Create shortcut node for "Operate Reactor" order's "Power Up" option - if ((Character.Controlled == null || !HasAppropriateJob(Character.Controlled, "operatereactor")) && - reactorOutput < float.Epsilon && characters.None(c => c.SelectedConstruction == reactor.Item)) + if (ShouldDelegateOrder("operatereactor") && reactorOutput < float.Epsilon && characters.None(c => c.SelectedConstruction == reactor.Item)) { var order = new Order(Order.GetPrefab("operatereactor"), reactor.Item, reactor, Character.Controlled); - var option = order.Prefab.Options[0]; - shortcutNodes.Add( - CreateOrderOptionNode(shortcutNodeSize, null, Point.Zero, order, option, order.Prefab.GetOptionName(option), -1)); - } - } - - // TODO: Reconsider the conditions as bot captain can have the nav term selected without operating it - // If player is not a captain AND nobody is using the nav terminal AND the nav terminal is powered up - // --> Create shortcut node for Steer order - if (shortcutNodes.Count < maxShortCutNodeCount && (Character.Controlled == null || !HasAppropriateJob(Character.Controlled, "steer")) && - sub.GetItems(false).Find(i => i.HasTag("navterminal") && i.IsPlayerTeamInteractable) is Item nav && characters.None(c => c.SelectedConstruction == nav) && - nav.GetComponent() is Steering steering && steering.Voltage > steering.MinVoltage) - { - shortcutNodes.Add( - CreateOrderNode(shortcutNodeSize, null, Point.Zero, Order.GetPrefab("steer"), -1)); - } - - // If player is not a security officer AND invaders are reported - // --> Create shorcut node for Fight Intruders order - if (shortcutNodes.Count < maxShortCutNodeCount && (Character.Controlled == null || !HasAppropriateJob(Character.Controlled, "fightintruders")) && - Order.GetPrefab("reportintruders") is Order reportIntruders && ActiveOrders.Any(o => o.First.Prefab == reportIntruders)) - { - shortcutNodes.Add( - CreateOrderNode(shortcutNodeSize, null, Point.Zero, Order.GetPrefab("fightintruders"), -1)); - } - - // If player is not a mechanic AND a breach has been reported - // --> Create shorcut node for Fix Leaks order - if (shortcutNodes.Count < maxShortCutNodeCount && (Character.Controlled == null || !HasAppropriateJob(Character.Controlled, "fixleaks")) && - Order.GetPrefab("reportbreach") is Order reportBreach && ActiveOrders.Any(o => o.First.Prefab == reportBreach)) - { - shortcutNodes.Add( - CreateOrderNode(shortcutNodeSize, null, Point.Zero, Order.GetPrefab("fixleaks"), -1)); - } - - // --> Create shortcut nodes for the Repair orders - if (shortcutNodes.Count < maxShortCutNodeCount && Order.GetPrefab("reportbrokendevices") is Order reportBrokenDevices && ActiveOrders.Any(o => o.First.Prefab == reportBrokenDevices)) - { - // TODO: Doesn't work for player issued reports, because they don't have a target. - int repairNodes = 0; - string tag = "repairelectrical"; - if (shortcutNodes.Count < maxShortCutNodeCount && (Character.Controlled == null || !HasAppropriateJob(Character.Controlled, tag)) && ActiveOrders.Any(o => o.First.Prefab == reportBrokenDevices && o.First.TargetItemComponent is Repairable r && r.requiredSkills.Any(s => s.Identifier == "electrical"))) - { - shortcutNodes.Add(CreateOrderNode(shortcutNodeSize, null, Point.Zero, Order.GetPrefab(tag), -1)); - repairNodes++; - } - tag = "repairmechanical"; - if (shortcutNodes.Count < maxShortCutNodeCount && (Character.Controlled == null || !HasAppropriateJob(Character.Controlled, tag)) && ActiveOrders.Any(o => o.First.Prefab == reportBrokenDevices && o.First.TargetItemComponent is Repairable r && r.requiredSkills.Any(s => s.Identifier == "mechanical"))) - { - shortcutNodes.Add(CreateOrderNode(shortcutNodeSize, null, Point.Zero, Order.GetPrefab(tag), -1)); - repairNodes++; - } - if (repairNodes == 0 && shortcutNodes.Count < maxShortCutNodeCount) - { - tag = "repairsystems"; - if (Character.Controlled == null || !HasAppropriateJob(Character.Controlled, tag)) + string option = order.Prefab.Options[0]; + if (IsNonDuplicateOrder(order, option)) { - shortcutNodes.Add(CreateOrderNode(shortcutNodeSize, null, Point.Zero, Order.GetPrefab(tag), -1)); + shortcutNodes.Add(CreateOrderOptionNode(shortcutNodeSize, null, Point.Zero, order, option, order.Prefab.GetOptionName(option), -1)); } } } - + // TODO: Reconsider the conditions as bot captain can have the nav term selected without operating it + // If player is not a captain AND nobody is using the nav terminal AND the nav terminal is powered up + // --> Create shortcut node for Steer order + if (CanFitMoreNodes() && ShouldDelegateOrder("steer") && Order.GetPrefab("steer") is Order steerOrder && IsNonDuplicateOrder(steerOrder) && + sub.GetItems(false).Find(i => i.HasTag("navterminal") && i.IsPlayerTeamInteractable) is Item nav && characters.None(c => c.SelectedConstruction == nav) && + nav.GetComponent() is Steering steering && steering.Voltage > steering.MinVoltage) + { + shortcutNodes.Add(CreateOrderNode(shortcutNodeSize, null, Point.Zero, steerOrder, -1)); + } + // If player is not a security officer AND invaders are reported + // --> Create shorcut node for Fight Intruders order + if (CanFitMoreNodes() && ShouldDelegateOrder("fightintruders") && + Order.GetPrefab("reportintruders") is Order reportIntruders && ActiveOrders.Any(o => o.First.Prefab == reportIntruders) && + Order.GetPrefab("fightintruders") is Order fightOrder && IsNonDuplicateOrder(fightOrder)) + { + shortcutNodes.Add(CreateOrderNode(shortcutNodeSize, null, Point.Zero, fightOrder, -1)); + } + // If player is not a mechanic AND a breach has been reported + // --> Create shorcut node for Fix Leaks order + if (CanFitMoreNodes() && ShouldDelegateOrder("fixleaks") && Order.GetPrefab("fixleaks") is Order fixLeaksOrder && IsNonDuplicateOrder(fixLeaksOrder) && + Order.GetPrefab("reportbreach") is Order reportBreach && ActiveOrders.Any(o => o.First.Prefab == reportBreach)) + { + shortcutNodes.Add(CreateOrderNode(shortcutNodeSize, null, Point.Zero, fixLeaksOrder, -1)); + } + // --> Create shortcut nodes for the Repair orders + if (CanFitMoreNodes() && Order.GetPrefab("reportbrokendevices") is Order reportBrokenDevices && ActiveOrders.Any(o => o.First.Prefab == reportBrokenDevices)) + { + // TODO: Doesn't work for player issued reports, because they don't have a target. + bool useSpecificRepairOrder = false; + string tag = "repairelectrical"; + if (CanFitMoreNodes() && ShouldDelegateOrder(tag) && + ActiveOrders.Any(o => o.First.Prefab == reportBrokenDevices && o.First.TargetItemComponent is Repairable r && r.requiredSkills.Any(s => s.Identifier == "electrical"))) + { + if (Order.GetPrefab(tag) is Order repairElectricalOrder && IsNonDuplicateOrder(repairElectricalOrder)) + { + shortcutNodes.Add(CreateOrderNode(shortcutNodeSize, null, Point.Zero, repairElectricalOrder, -1)); + } + useSpecificRepairOrder = true; + } + tag = "repairmechanical"; + if (CanFitMoreNodes() && ShouldDelegateOrder(tag) && + ActiveOrders.Any(o => o.First.Prefab == reportBrokenDevices && o.First.TargetItemComponent is Repairable r && r.requiredSkills.Any(s => s.Identifier == "mechanical"))) + { + if (Order.GetPrefab(tag) is Order repairMechanicalOrder && IsNonDuplicateOrder(repairMechanicalOrder)) + { + shortcutNodes.Add(CreateOrderNode(shortcutNodeSize, null, Point.Zero, repairMechanicalOrder, -1)); + } + useSpecificRepairOrder = true; + } + tag = "repairsystems"; + if (!useSpecificRepairOrder && CanFitMoreNodes() && ShouldDelegateOrder(tag) && Order.GetPrefab(tag) is Order repairOrder && IsNonDuplicateOrder(repairOrder)) + { + shortcutNodes.Add(CreateOrderNode(shortcutNodeSize, null, Point.Zero, repairOrder, -1)); + } + } // If fire is reported // --> Create shortcut node for Extinguish Fires order - if (shortcutNodes.Count < maxShortCutNodeCount && ActiveOrders.Any(o => o.First.Prefab == Order.GetPrefab("reportfire"))) + if (CanFitMoreNodes() && Order.GetPrefab("extinguishfires") is Order extinguishOrder && IsNonDuplicateOrder(extinguishOrder) && + ActiveOrders.Any(o => o.First.Prefab == Order.GetPrefab("reportfire"))) { - shortcutNodes.Add( - CreateOrderNode(shortcutNodeSize, null, Point.Zero, Order.GetPrefab("extinguishfires"), -1)); + shortcutNodes.Add(CreateOrderNode(shortcutNodeSize, null, Point.Zero, extinguishOrder, -1)); } - - if (shortcutNodes.Count < maxShortCutNodeCount && characterContext?.Info?.Job?.Prefab?.AppropriateOrders != null) + if (CanFitMoreNodes() && characterContext?.Info?.Job?.Prefab?.AppropriateOrders != null) { foreach (string orderIdentifier in characterContext.Info.Job.Prefab.AppropriateOrders) { - if (Order.GetPrefab(orderIdentifier) is Order orderPrefab && + if (Order.GetPrefab(orderIdentifier) is Order orderPrefab && IsNonDuplicateOrder(orderPrefab) && shortcutNodes.None(n => (n.UserData is Order order && order.Identifier == orderIdentifier) || (n.UserData is Tuple orderWithOption && orderWithOption.Item1.Identifier == orderIdentifier)) && !orderPrefab.IsReport && orderPrefab.Category != null) @@ -2403,22 +2536,19 @@ namespace Barotrauma { shortcutNodes.Add(CreateOrderNode(shortcutNodeSize, null, Point.Zero, orderPrefab, -1)); } - if (shortcutNodes.Count >= maxShortCutNodeCount) { break; } + if (!CanFitMoreNodes()) { break; } } } } - - if (shortcutNodes.Count < maxShortCutNodeCount && characterContext != null && !characterContext.IsDismissed) + if (CanFitMoreNodes() && characterContext != null && !characterContext.IsDismissed) { - shortcutNodes.Add( - CreateOrderNode(shortcutNodeSize, null, Point.Zero, dismissedOrderPrefab, -1)); + shortcutNodes.Add(CreateOrderNode(shortcutNodeSize, null, Point.Zero, dismissedOrderPrefab, -1)); } - if (shortcutNodes.Count < 1) { return; } - - shortcutCenterNode = new GUIButton( - new RectTransform(shortcutCenterNodeSize, parent: commandFrame.RectTransform, anchor: Anchor.Center), - style: null); + shortcutCenterNode = new GUIFrame(new RectTransform(shortcutCenterNodeSize, parent: commandFrame.RectTransform, anchor: Anchor.Center), style: null) + { + CanBeFocused = false + }; CreateNodeIcon(shortcutCenterNode.RectTransform, "CommandShortcutNode"); foreach (GUIComponent c in shortcutCenterNode.Children) { @@ -2427,15 +2557,29 @@ namespace Barotrauma c.SelectedColor = c.Color; } shortcutCenterNode.RectTransform.MoveOverTime(shorcutCenterNodeOffset, CommandNodeAnimDuration); - - var nodeCountForCalculations = shortcutNodes.Count * 2 + 2; - var offsets = MathUtils.GetPointsOnCircumference(Vector2.Zero, 0.75f * nodeDistance, nodeCountForCalculations); - var firstOffsetIndex = nodeCountForCalculations / 2 - 1; + int nodeCountForCalculations = shortcutNodes.Count * 2 + 2; + Vector2[] offsets = MathUtils.GetPointsOnCircumference(Vector2.Zero, 0.75f * nodeDistance, nodeCountForCalculations); + int firstOffsetIndex = nodeCountForCalculations / 2 - 1; for (int i = 0; i < shortcutNodes.Count; i++) { shortcutNodes[i].RectTransform.Parent = commandFrame.RectTransform; shortcutNodes[i].RectTransform.MoveOverTime(shorcutCenterNodeOffset + offsets[firstOffsetIndex - i].ToPoint(), CommandNodeAnimDuration); } + + bool CanFitMoreNodes() + { + return shortcutNodes.Count < maxShortcutNodeCount; + } + static bool ShouldDelegateOrder(string orderIdentifier) + { + return !(Character.Controlled is Character c) || !(c?.Info?.Job != null && c.Info.Job.Prefab.AppropriateOrders.Contains(orderIdentifier)); + } + bool IsNonDuplicateOrder(Order orderPrefab, string option = null) + { + return characterContext == null || (string.IsNullOrEmpty(option) ? + characterContext.CurrentOrders.None(oi => oi.Order?.Identifier == orderPrefab?.Identifier) : + characterContext.CurrentOrders.None(oi => oi.Order?.Identifier == orderPrefab?.Identifier && oi.OrderOption == option)); + } } private void CreateOrderNodes(OrderCategory orderCategory) @@ -2448,7 +2592,7 @@ namespace Barotrauma for (int i = 0; i < orders.Count; i++) { order = orders[i]; - disableNode = !CanSomeoneHearCharacter() || + disableNode = !CanCharacterBeHeard() || (order.MustSetTarget && (order.ItemComponentType != null || order.TargetItems.Length > 0) && order.GetMatchingItems(true, interactableFor: characterContext ?? Character.Controlled).None()); optionNodes.Add(new Tuple( @@ -2596,7 +2740,7 @@ namespace Barotrauma } var offsets = MathUtils.GetPointsOnCircumference(Vector2.Zero, nodeDistance, contextualOrders.Count, MathHelper.ToRadians(90f + 180f / contextualOrders.Count)); - bool disableNode = !CanSomeoneHearCharacter(); + bool disableNode = !CanCharacterBeHeard(); for (int i = 0; i < contextualOrders.Count; i++) { optionNodes.Add(new Tuple( @@ -2621,6 +2765,7 @@ namespace Barotrauma item.GetConnectedComponents(recursive: true).Any(c => c.Item.HasTag(operateWeaponsPrefab.TargetItems))); } + /// Use a negative value (e.g. -1) if there should be no hotkey associated with the node private GUIButton CreateOrderNode(Point size, RectTransform parent, Point offset, Order order, int hotkey, bool disableNode = false, bool checkIfOrderCanBeHeard = true) { var node = new GUIButton( @@ -2633,7 +2778,7 @@ namespace Barotrauma if (checkIfOrderCanBeHeard && !disableNode) { - disableNode = !CanSomeoneHearCharacter(); + disableNode = !CanCharacterBeHeard(); } var mustSetOptionOrTarget = order.HasOptions; @@ -2694,7 +2839,7 @@ namespace Barotrauma if (disableNode) { node.CanBeFocused = icon.CanBeFocused = false; - CreateBlockIcon(node.RectTransform); + CreateBlockIcon(node.RectTransform, tooltip: TextManager.Get(characterContext == null ? "nocharactercanhear" : "thischaractercanthear")); } else if (hotkey >= 0) { @@ -2703,195 +2848,137 @@ namespace Barotrauma return node; } - private void CreateOrderOptions(Order order) + private void CreateMinimapNodes(Order order, Submarine submarine, List matchingItems) { - Submarine submarine = GetTargetSubmarine(); - var matchingItems = (itemContext == null && order.MustSetTarget) ? order.GetMatchingItems(submarine, true, interactableFor: characterContext ?? Character.Controlled) : new List(); - - //more than one target item -> create a minimap-like selection with a pic of the sub - if (itemContext == null && matchingItems.Count > 1) + // TODO: Further adjustments to frameSize calculations + // I just divided the existing sizes by 2 to get it working quickly without it overlapping too much + Point frameSize; + Rectangle subBorders = submarine.GetDockedBorders(); + if (subBorders.Width > subBorders.Height) { - // TODO: Further adjustments to frameSize calculations - // I just divided the existing sizes by 2 to get it working quickly without it overlapping too much - Point frameSize; - Rectangle subBorders = submarine.GetDockedBorders(); - if (subBorders.Width > subBorders.Height) + frameSize.X = Math.Min(GameMain.GraphicsWidth / 2, GameMain.GraphicsWidth - 50) / 2; + //height depends on the dimensions of the sub + frameSize.Y = (int)(frameSize.X * (subBorders.Height / (float)subBorders.Width)); + } + else + { + frameSize.Y = Math.Min((int)(GameMain.GraphicsHeight * 0.6f), GameMain.GraphicsHeight - 50) / 2; + //width depends on the dimensions of the sub + frameSize.X = (int)(frameSize.Y * (subBorders.Width / (float)subBorders.Height)); + } + + // TODO: Use the old targetFrame if possible + targetFrame = new GUIFrame( + new RectTransform(frameSize, parent: commandFrame.RectTransform, anchor: Anchor.Center) { - frameSize.X = Math.Min(GameMain.GraphicsWidth / 2, GameMain.GraphicsWidth - 50) / 2; - //height depends on the dimensions of the sub - frameSize.Y = (int)(frameSize.X * (subBorders.Height / (float)subBorders.Width)); - } - else + AbsoluteOffset = new Point(0, -150), + Pivot = Pivot.BottomCenter + }, + style: "InnerFrame"); + + submarine.CreateMiniMap(targetFrame, pointsOfInterest: matchingItems); + + new GUICustomComponent(new RectTransform(Vector2.One, targetFrame.RectTransform), onDraw: DrawMiniMapOverlay) + { + CanBeFocused = false, + UserData = submarine + }; + + List optionElements = new List(); + foreach (Item item in matchingItems) + { + var itemTargetFrame = targetFrame.Children.First().FindChild(item); + if (itemTargetFrame == null) { continue; } + + var anchor = Anchor.TopLeft; + if (itemTargetFrame.RectTransform.RelativeOffset.X < 0.5f) { - frameSize.Y = Math.Min((int)(GameMain.GraphicsHeight * 0.6f), GameMain.GraphicsHeight - 50) / 2; - //width depends on the dimensions of the sub - frameSize.X = (int)(frameSize.Y * (subBorders.Width / (float)subBorders.Height)); - } - - // TODO: Use the old targetFrame if possible - targetFrame = new GUIFrame( - new RectTransform(frameSize, parent: commandFrame.RectTransform, anchor: Anchor.Center) + if (itemTargetFrame.RectTransform.RelativeOffset.Y < 0.5f) { - AbsoluteOffset = new Point(0, -150), - Pivot = Pivot.BottomCenter - }, - style: "InnerFrame"); - - submarine.CreateMiniMap(targetFrame, pointsOfInterest: matchingItems); - - new GUICustomComponent(new RectTransform(Vector2.One, targetFrame.RectTransform), onDraw: DrawMiniMapOverlay) - { - CanBeFocused = false, - UserData = submarine - }; - - List optionElements = new List(); - foreach (Item item in matchingItems) - { - var itemTargetFrame = targetFrame.Children.First().FindChild(item); - if (itemTargetFrame == null) { continue; } - - var anchor = Anchor.TopLeft; - if (itemTargetFrame.RectTransform.RelativeOffset.X < 0.5f) - { - if (itemTargetFrame.RectTransform.RelativeOffset.Y < 0.5f) - { - anchor = Anchor.BottomRight; - } - else - { - anchor = Anchor.TopRight; - } - } - else if (itemTargetFrame.RectTransform.RelativeOffset.Y < 0.5f) - { - anchor = Anchor.BottomLeft; - } - - GUIComponent optionElement; - if (order.Options.Length > 1) - { - optionElement = new GUIFrame( - new RectTransform( - new Point((int)(250 * GUI.Scale), (int)((40 + order.Options.Length * 40) * GUI.Scale)), - parent: itemTargetFrame.RectTransform, - anchor: anchor), - style: "InnerFrame"); - - new GUIFrame( - new RectTransform(Vector2.One, optionElement.RectTransform, anchor: Anchor.Center), - style: "OuterGlow", - color: Color.Black * 0.7f); - - var optionContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.9f), optionElement.RectTransform, anchor: Anchor.Center)) - { - RelativeSpacing = 0.05f, - Stretch = true - }; - - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.3f), optionContainer.RectTransform), - item?.Name ?? GetOrderNameBasedOnContextuality(order)); - - for (int i = 0; i < order.Options.Length; i++) - { - var optionButton = new GUIButton( - new RectTransform(new Vector2(1.0f, 0.2f), optionContainer.RectTransform), - text: order.GetOptionName(i), style: "GUITextBox") - { - UserData = new Tuple( - item == null ? order : new Order(order, item, order.GetTargetItemComponent(item)), - order.Options[i]), - Font = GUI.SmallFont, - OnClicked = (_, userData) => - { - if (!CanIssueOrders) { return false; } - var o = userData as Tuple; - SetCharacterOrder(characterContext ?? GetCharacterForQuickAssignment(o.Item1), o.Item1, o.Item2, CharacterInfo.HighestManualOrderPriority, Character.Controlled); - DisableCommandUI(); - return true; - } - }; - if (CanOpenManualAssignment(optionButton)) - { - optionButton.OnSecondaryClicked = (button, _) => CreateAssignmentNodes(button); - } - optionNodes.Add(new Tuple(optionButton, Keys.None)); - } + anchor = Anchor.BottomRight; } else { - var userData = new Tuple(item == null ? order : new Order(order, item, order.GetTargetItemComponent(item)), ""); - optionElement = new GUIButton( - new RectTransform( - new Point((int)(50 * GUI.Scale)), - parent: itemTargetFrame.RectTransform, - anchor: anchor), - style: null) - { - UserData = userData, - Font = GUI.SmallFont, - OnClicked = (_, userData) => - { - if (!CanIssueOrders) { return false; } - var o = userData as Tuple; - SetCharacterOrder(characterContext ?? GetCharacterForQuickAssignment(o.Item1), o.Item1, o.Item2, CharacterInfo.HighestManualOrderPriority, Character.Controlled); - DisableCommandUI(); - return true; - } - }; - if (CanOpenManualAssignment(optionElement)) - { - optionElement.OnSecondaryClicked = (button, _) => CreateAssignmentNodes(button); - } - var colorMultiplier = characters.Any(c => c.CurrentOrders.Any(o => o.Order != null && - o.Order.Identifier == userData.Item1.Identifier && - o.Order.TargetEntity == userData.Item1.TargetEntity)) ? 0.5f : 1f; - CreateNodeIcon(Vector2.One, optionElement.RectTransform, item.Prefab.MinimapIcon ?? order.SymbolSprite, order.Color * colorMultiplier, tooltip: item.Name); - optionNodes.Add(new Tuple(optionElement, Keys.None)); + anchor = Anchor.TopRight; } - optionElements.Add(optionElement); + } + else if (itemTargetFrame.RectTransform.RelativeOffset.Y < 0.5f) + { + anchor = Anchor.BottomLeft; } - Rectangle clampArea = new Rectangle(10, 10, GameMain.GraphicsWidth - 20, GameMain.GraphicsHeight - 20); - if (order.Identifier == "operateweapons") + var userData = new Tuple(item == null ? order : new Order(order, item, order.GetTargetItemComponent(item)), ""); + var optionElement = new GUIButton( + new RectTransform( + new Point((int)(50 * GUI.Scale)), + parent: itemTargetFrame.RectTransform, + anchor: anchor), + style: null) { - Rectangle disallowedArea = targetFrame.GetChild().Rect; - Point originalSize = disallowedArea.Size; - disallowedArea.Size = disallowedArea.MultiplySize(0.9f); - disallowedArea.X += (originalSize.X - disallowedArea.Size.X) / 2; - disallowedArea.Y += (originalSize.Y - disallowedArea.Size.Y) / 2; - GUI.PreventElementOverlap(optionElements, new List() { disallowedArea }, clampArea); - nodeConnectors.RectTransform.Parent = targetFrame.RectTransform; - nodeConnectors.RectTransform.SetAsFirstChild(); - } - else + UserData = userData, + Font = GUI.SmallFont, + OnClicked = (button, userData) => + { + if (!CanIssueOrders) { return false; } + var o = userData as Tuple; + if (o.Item1.HasOptions) + { + NavigateForward(button, userData); + } + else + { + SetCharacterOrder(characterContext ?? GetCharacterForQuickAssignment(o.Item1), o.Item1, o.Item2, CharacterInfo.HighestManualOrderPriority, Character.Controlled); + DisableCommandUI(); + } + return true; + } + }; + if (CanOpenManualAssignment(optionElement)) { - GUI.PreventElementOverlap(optionElements, clampArea: clampArea); + optionElement.OnSecondaryClicked = (button, _) => CreateAssignmentNodes(button); } - - var shadow = new GUIFrame( - new RectTransform(targetFrame.Rect.Size + new Point((int)(200 * GUI.Scale)), targetFrame.RectTransform, anchor: Anchor.Center), - style: "OuterGlow", - color: matchingItems.Count > 1 ? Color.Black * 0.9f : Color.Black * 0.7f); - shadow.SetAsFirstChild(); + var colorMultiplier = characters.Any(c => c.CurrentOrders.Any(o => o.Order != null && + o.Order.Identifier == userData.Item1.Identifier && + o.Order.TargetEntity == userData.Item1.TargetEntity)) ? 0.5f : 1f; + CreateNodeIcon(Vector2.One, optionElement.RectTransform, item.Prefab.MinimapIcon ?? order.SymbolSprite, order.Color * colorMultiplier, tooltip: item.Name); + optionNodes.Add(new Tuple(optionElement, Keys.None)); + optionElements.Add(optionElement); } - //only one target (or an order with no particular targets), just show options - else + + Rectangle clampArea = new Rectangle(10, 10, GameMain.GraphicsWidth - 20, GameMain.GraphicsHeight - 20); + Rectangle disallowedArea = targetFrame.GetChild().Rect; + Point originalSize = disallowedArea.Size; + disallowedArea.Size = disallowedArea.MultiplySize(0.9f); + disallowedArea.X += (originalSize.X - disallowedArea.Size.X) / 2; + disallowedArea.Y += (originalSize.Y - disallowedArea.Size.Y) / 2; + GUI.PreventElementOverlap(optionElements, new List() { disallowedArea }, clampArea); + nodeConnectors.RectTransform.Parent = targetFrame.RectTransform; + nodeConnectors.RectTransform.SetAsFirstChild(); + + var shadow = new GUIFrame( + new RectTransform(targetFrame.Rect.Size + new Point((int)(200 * GUI.Scale)), targetFrame.RectTransform, anchor: Anchor.Center), + style: "OuterGlow", + color: matchingItems.Count > 1 ? Color.Black * 0.9f : Color.Black * 0.7f); + shadow.SetAsFirstChild(); + } + + private void CreateOrderOptionNodes(Order order, Item targetItem) + { + if (itemContext != null) { - var item = itemContext != null ? - (order.UseController ? itemContext.GetConnectedComponents().FirstOrDefault()?.Item ?? itemContext.GetConnectedComponents(recursive: true).FirstOrDefault()?.Item : itemContext) : - (matchingItems.Count > 0 ? matchingItems[0] : null); - var o = item == null || !order.IsPrefab ? order : new Order(order, item, order.GetTargetItemComponent(item)); - var offsets = MathUtils.GetPointsOnCircumference(Vector2.Zero, nodeDistance, - GetCircumferencePointCount(order.Options.Length), - GetFirstNodeAngle(order.Options.Length)); - var offsetIndex = 0; - for (int i = 0; i < order.Options.Length; i++) - { - optionNodes.Add(new Tuple( - CreateOrderOptionNode(nodeSize, commandFrame.RectTransform, offsets[offsetIndex++].ToPoint(), o, order.Options[i], order.GetOptionName(i), (i + 1) % 10), - Keys.D0 + (i + 1) % 10)); - } + targetItem = !order.UseController ? itemContext : + itemContext.GetConnectedComponents().FirstOrDefault()?.Item ?? itemContext.GetConnectedComponents(recursive: true).FirstOrDefault()?.Item; + } + var o = (targetItem == null || !order.IsPrefab) ? order : new Order(order, targetItem, order.GetTargetItemComponent(targetItem)); + var offsets = MathUtils.GetPointsOnCircumference(Vector2.Zero, nodeDistance, + GetCircumferencePointCount(order.Options.Length), + GetFirstNodeAngle(order.Options.Length)); + var offsetIndex = 0; + for (int i = 0; i < order.Options.Length; i++) + { + optionNodes.Add(new Tuple( + CreateOrderOptionNode(nodeSize, commandFrame.RectTransform, offsets[offsetIndex++].ToPoint(), o, order.Options[i], order.GetOptionName(i), (i + 1) % 10), + Keys.D0 + (i + 1) % 10)); } } @@ -2924,11 +3011,11 @@ namespace Barotrauma "\n" + (!PlayerInput.MouseButtonsSwapped() ? TextManager.Get("input.leftmouse") : TextManager.Get("input.rightmouse")) + ": " + TextManager.Get("commandui.quickassigntooltip") + "\n" + (!PlayerInput.MouseButtonsSwapped() ? TextManager.Get("input.rightmouse") : TextManager.Get("input.leftmouse")) + ": " + TextManager.Get("commandui.manualassigntooltip")); } - if (!CanSomeoneHearCharacter()) + if (!CanCharacterBeHeard()) { node.CanBeFocused = false; if (icon != null) { icon.CanBeFocused = false; } - CreateBlockIcon(node.RectTransform); + CreateBlockIcon(node.RectTransform, tooltip: TextManager.Get(characterContext == null ? "nocharactercanhear" : "thischaractercanthear")); } else if (hotkey >= 0) { @@ -2990,9 +3077,7 @@ namespace Barotrauma SetCenterNode(clickedOptionNode); node = null; } - targetFrame.Visible = false; - nodeConnectors.RectTransform.Parent = commandFrame.RectTransform; - nodeConnectors.RectTransform.RepositionChildInHierarchy(1); + HideMinimap(); } if (shortcutCenterNode != null) { @@ -3178,7 +3263,7 @@ namespace Barotrauma if (!canHear) { node.CanBeFocused = orderIcon.CanBeFocused = false; - CreateBlockIcon(node.RectTransform); + CreateBlockIcon(node.RectTransform, tooltip: TextManager.Get("thischaractercanthear")); } if (hotkey >= 0) { @@ -3270,14 +3355,23 @@ namespace Barotrauma }; } - private void CreateBlockIcon(RectTransform parent) + private void CreateBlockIcon(RectTransform parent, string tooltip = null) { - new GUIImage(new RectTransform(new Vector2(0.9f), parent, anchor: Anchor.Center), cancelIcon, scaleToFit: true) + var icon = new GUIImage(new RectTransform(new Vector2(0.9f), parent, anchor: Anchor.Center), cancelIcon, scaleToFit: true) { CanBeFocused = false, Color = GUI.Style.Red * nodeColorMultiplier, HoverColor = GUI.Style.Red }; + if (!string.IsNullOrEmpty(tooltip)) + { + icon.ToolTip = tooltip; + string color = XMLExtensions.ColorToString(GUI.Style.Red); + tooltip = $"‖color:{color}‖{tooltip}‖color:end‖"; + var richTextData = RichTextData.GetRichTextData(tooltip, out _); + icon.TooltipRichTextData = richTextData; + icon.CanBeFocused = true; + } } private int GetCircumferencePointCount(int nodes) @@ -3383,15 +3477,15 @@ namespace Barotrauma private bool CanOpenManualAssignment(GUIComponent node) { if (node == null || characterContext != null) { return false; } - if (node.UserData is Tuple orderInfo) + if (node.UserData is (Order minimapOrder, string option)) { - return !orderInfo.Item1.TargetAllCharacters; + return !minimapOrder.TargetAllCharacters && (!minimapOrder.HasOptions || !string.IsNullOrEmpty(option)); } - if (node.UserData is Order order) + if (node.UserData is Order nodeOrder) { - return !order.TargetAllCharacters && !order.HasOptions && - (!order.MustSetTarget || itemContext != null || - order.GetMatchingItems(GetTargetSubmarine(), true, interactableFor: Character.Controlled).Count < 2); + return !nodeOrder.TargetAllCharacters && !nodeOrder.HasOptions && + (!nodeOrder.MustSetTarget || itemContext != null || + nodeOrder.GetMatchingItems(GetTargetSubmarine(), true, interactableFor: Character.Controlled).Count < 2); } return false; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/MultiPlayerCampaign.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/MultiPlayerCampaign.cs index e2a74ba9a..32605d848 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/MultiPlayerCampaign.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/MultiPlayerCampaign.cs @@ -60,7 +60,7 @@ namespace Barotrauma var newCampaignContainer = new GUIFrame(new RectTransform(new Vector2(0.95f, 0.95f), campaignContainer.RectTransform, Anchor.Center), style: null); var loadCampaignContainer = new GUIFrame(new RectTransform(new Vector2(0.95f, 0.95f), campaignContainer.RectTransform, Anchor.Center), style: null); - GameMain.NetLobbyScreen.CampaignSetupUI = new CampaignSetupUI(true, newCampaignContainer, loadCampaignContainer, null, saveFiles); + GameMain.NetLobbyScreen.CampaignSetupUI = new MultiPlayerCampaignSetupUI(newCampaignContainer, loadCampaignContainer, null, saveFiles); var newCampaignButton = new GUIButton(new RectTransform(new Vector2(0.5f, 1.0f), buttonContainer.RectTransform), TextManager.Get("NewCampaign"), style: "GUITabButton") @@ -761,7 +761,7 @@ namespace Barotrauma Faction faction = campaign.Factions.FirstOrDefault(f => f.Prefab.Identifier.Equals(identifier, StringComparison.OrdinalIgnoreCase)); if (faction?.Reputation != null) { - faction.Reputation.Value = rep; + faction.Reputation.SetReputation(rep); } else { @@ -771,7 +771,7 @@ namespace Barotrauma if (reputation.HasValue) { - campaign.Map.CurrentLocation.Reputation.Value = reputation.Value; + campaign.Map.CurrentLocation.Reputation.SetReputation(reputation.Value); campaign?.CampaignUI?.UpgradeStore?.RefreshAll(); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameSession.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameSession.cs index 90aae527a..b5246980c 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameSession.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameSession.cs @@ -21,10 +21,11 @@ namespace Barotrauma { if (GameMain.NetworkMember != null && GameMain.NetLobbyScreen != null) { - if (GameMain.NetLobbyScreen.HeadSelectionList != null) { GameMain.NetLobbyScreen.HeadSelectionList.Visible = false; } + GameMain.NetLobbyScreen.CharacterAppearanceCustomizationMenu?.Dispose(); + GameMain.NetLobbyScreen.CharacterAppearanceCustomizationMenu = null; if (GameMain.NetLobbyScreen.JobSelectionFrame != null) { GameMain.NetLobbyScreen.JobSelectionFrame.Visible = false; } } - if (tabMenu == null && GameMode is TutorialMode == false) + if (tabMenu == null && !(GameMode is TutorialMode) && !ConversationAction.IsDialogOpen) { tabMenu = new TabMenu(); HintManager.OnShowTabMenu(); @@ -34,17 +35,17 @@ namespace Barotrauma tabMenu = null; NetLobbyScreen.JobInfoFrame = null; } - return true; } private GUILayoutGroup topLeftButtonGroup; private GUIButton crewListButton, commandButton, tabMenuButton; + private GUIImage talentPointNotification; private GUIComponent respawnInfoFrame, respawnButtonContainer; private GUITextBlock respawnInfoText; private GUITickBox respawnTickBox; - private GUILayoutGroup TopLeftButtonGroup; + private void CreateTopLeftButtons() { if (topLeftButtonGroup != null) @@ -89,11 +90,11 @@ namespace Barotrauma tabMenuButton = new GUIButton(new RectTransform(buttonSize, parent: topLeftButtonGroup.RectTransform), style: "TabMenuButton") { ToolTip = TextManager.GetWithVariable("hudbutton.tabmenu", "[key]", GameMain.Config.KeyBindText(InputType.InfoTab)), - OnClicked = (button, userData) => - { - return ToggleTabMenu(); - } + OnClicked = (button, userData) => ToggleTabMenu() }; + + talentPointNotification = CreateTalentIconNotification(tabMenuButton); + GameMain.Instance.ResolutionChanged += CreateTopLeftButtons; respawnInfoFrame = new GUIFrame(new RectTransform(new Vector2(0.5f, 1.0f), parent: topLeftButtonGroup.RectTransform) @@ -140,11 +141,41 @@ namespace Barotrauma if (GameMain.NetworkMember != null) { - GameMain.NetLobbyScreen?.HeadSelectionList?.AddToGUIUpdateList(); + GameMain.NetLobbyScreen.CharacterAppearanceCustomizationMenu?.AddToGUIUpdateList(); GameMain.NetLobbyScreen?.JobSelectionFrame?.AddToGUIUpdateList(); } } + public static GUIImage CreateTalentIconNotification(GUIComponent parent, bool offset = true) + { + GUIImage indicator = new GUIImage(new RectTransform(new Vector2(0.45f), parent.RectTransform, anchor: Anchor.TopRight, scaleBasis: ScaleBasis.BothWidth), style: "TalentPointNotification") + { + Visible = false, + CanBeFocused = false + }; + Point notificationSize = indicator.RectTransform.NonScaledSize; + if (offset) + { + indicator.RectTransform.AbsoluteOffset = new Point(-(notificationSize.X / 2), -(notificationSize.Y / 2)); + } + return indicator; + } + + public static void UpdateTalentNotificationIndicator(GUIImage indicator) + { + if (indicator != null) + { + if (Character.Controlled?.Info == null) + { + indicator.Visible = false; + } + else + { + indicator.Visible = Character.Controlled.Info.GetAvailableTalentPoints() > 0; + } + } + } + partial void UpdateProjSpecific(float deltaTime) { if (GUI.DisableHUD) { return; } @@ -159,28 +190,24 @@ namespace Barotrauma else { tabMenu.Update(); - if ((PlayerInput.KeyHit(InputType.InfoTab) || PlayerInput.KeyHit(Microsoft.Xna.Framework.Input.Keys.Escape)) && + if ((PlayerInput.KeyHit(InputType.InfoTab) || PlayerInput.KeyHit(Microsoft.Xna.Framework.Input.Keys.Escape)) && !(GUI.KeyboardDispatcher.Subscriber is GUITextBox)) { ToggleTabMenu(); } } + UpdateTalentNotificationIndicator(talentPointNotification); + if (GameMain.NetworkMember != null) { - if (GameMain.NetLobbyScreen?.HeadSelectionList != null) - { - if (PlayerInput.PrimaryMouseButtonDown() && !GUI.IsMouseOn(GameMain.NetLobbyScreen.HeadSelectionList)) - { - if (GameMain.NetLobbyScreen.HeadSelectionList != null) { GameMain.NetLobbyScreen.HeadSelectionList.Visible = false; } - } - } + GameMain.NetLobbyScreen?.CharacterAppearanceCustomizationMenu?.Update(); if (GameMain.NetLobbyScreen?.JobSelectionFrame != null) { - if (PlayerInput.PrimaryMouseButtonDown() && !GUI.IsMouseOn(GameMain.NetLobbyScreen.JobSelectionFrame)) + if (GameMain.NetLobbyScreen.JobSelectionFrame != null && PlayerInput.PrimaryMouseButtonDown() && !GUI.IsMouseOn(GameMain.NetLobbyScreen.JobSelectionFrame)) { GameMain.NetLobbyScreen.JobList.Deselect(); - if (GameMain.NetLobbyScreen.JobSelectionFrame != null) { GameMain.NetLobbyScreen.JobSelectionFrame.Visible = false; } + GameMain.NetLobbyScreen.JobSelectionFrame.Visible = false; } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/HintManager.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/HintManager.cs index be0df2d3e..b58477409 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/HintManager.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/HintManager.cs @@ -544,7 +544,7 @@ namespace Barotrauma if (Character.Controlled.CurrentHull == null) { return; } if (HumanAIController.IsBallastFloraNoticeable(Character.Controlled, Character.Controlled.CurrentHull)) { - if (DisplayHint("onballastflorainfected")) { return; } + if (IsOnFriendlySub() && DisplayHint("onballastflorainfected")) { return; } } foreach (var gap in Character.Controlled.CurrentHull.ConnectedGaps) { @@ -552,7 +552,7 @@ namespace Barotrauma if (Vector2.DistanceSquared(Character.Controlled.WorldPosition, gap.ConnectedDoor.Item.WorldPosition) > 400 * 400) { continue; } if (!gap.IsRoomToRoom) { - if (!(Character.Controlled.GetEquippedItem("deepdiving", InvSlotType.OuterClothes) is Item)) { continue; } + if (!IsWearingDivingSuit()) { continue; } if (Character.Controlled.IsProtectedFromPressure()) { continue; } if (DisplayHint("divingsuitwarning", extendTextTag: false)) { return; } continue; @@ -561,10 +561,16 @@ namespace Barotrauma { if (me == Character.Controlled.CurrentHull) { continue; } if (!(me is Hull adjacentHull)) { continue; } + if (!IsOnFriendlySub()) { continue; } + if (IsWearingDivingSuit()) { continue; } if (adjacentHull.LethalPressure > 5.0f && DisplayHint("onadjacenthull.highpressure")) { return; } if (adjacentHull.WaterPercentage > 75 && !BallastHulls.Contains(adjacentHull) && DisplayHint("onadjacenthull.highwaterpercentage")) { return; } } + + static bool IsWearingDivingSuit() => Character.Controlled.GetEquippedItem("deepdiving", InvSlotType.OuterClothes) is Item; } + + static bool IsOnFriendlySub() => Character.Controlled.Submarine is Submarine sub && (sub.TeamID == Character.Controlled.TeamID || sub.TeamID == CharacterTeamType.FriendlyNPC); } private static void CheckReminders() diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSettings.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSettings.cs index 200d39720..49b528e7d 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSettings.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSettings.cs @@ -51,7 +51,7 @@ namespace Barotrauma { keyMapping = new KeyOrMouse[Enum.GetNames(typeof(InputType)).Length]; keyMapping[(int)InputType.Run] = new KeyOrMouse(Keys.LeftShift); - keyMapping[(int)InputType.Attack] = new KeyOrMouse(Keys.R); + keyMapping[(int)InputType.Attack] = new KeyOrMouse(Keys.F); keyMapping[(int)InputType.Crouch] = new KeyOrMouse(Keys.LeftControl); keyMapping[(int)InputType.Grab] = new KeyOrMouse(Keys.G); keyMapping[(int)InputType.Health] = new KeyOrMouse(Keys.H); @@ -173,7 +173,7 @@ namespace Barotrauma } } - private void LoadKeyBinds(XElement element) + private void LoadKeyBinds(XElement element, Version gameVersion) { foreach (XAttribute attribute in element.Attributes()) { @@ -183,7 +183,6 @@ namespace Barotrauma keyMapping[(int)InputType.TakeHalfFromInventorySlot] = new KeyOrMouse(Keys.LeftShift); keyMapping[(int)InputType.TakeOneFromInventorySlot] = new KeyOrMouse(Keys.LeftControl); } - if (!Enum.TryParse(attribute.Name.ToString(), true, out InputType inputType)) { continue; } if (int.TryParse(attribute.Value.ToString(), out int mouseButtonInt)) @@ -199,6 +198,13 @@ namespace Barotrauma keyMapping[(int)inputType] = new KeyOrMouse(key); } } + //v0.15 added creature attacks that can be used with a character capable of speaking (with mudraptor or spineling genes), + //which causes the previous attack keybind R to conflict with the radio keybind + // -> automatically change it to F + if (gameVersion < new Version(0, 15, 0, 0)) + { + keyMapping[(int)InputType.Attack] = new KeyOrMouse(Keys.F); + } } private void LoadInventoryKeybinds(XElement element) @@ -223,10 +229,12 @@ namespace Barotrauma private void LoadControls(XDocument doc) { + var gameVersion = new Version(doc.Root.GetAttributeString("gameversion", "0.0.0.0")); + XElement keyMapping = doc.Root.Element("keymapping"); if (keyMapping != null) { - LoadKeyBinds(keyMapping); + LoadKeyBinds(keyMapping, gameVersion); } XElement inventoryKeyMapping = doc.Root.Element("inventorykeymapping"); @@ -1518,6 +1526,12 @@ namespace Barotrauma "Automatic quickstart enabled", "Will the game automatically move on to Quickstart when the game is launched"); + addDebugTickBox( + TestScreenEnabled, + (b) => TestScreenEnabled = b, + "Test screen enabled", + "Will the game automatically move on to a test screen when the game is launched"); + addDebugTickBox( AutomaticCampaignLoadEnabled, (b) => AutomaticCampaignLoadEnabled = b, @@ -1831,6 +1845,7 @@ namespace Barotrauma ic.ParseMsg(); } } + CharacterHUD.ShouldRecreateHudTexts = true; } private void ApplySettings() diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/CharacterInventory.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/CharacterInventory.cs index 516f62488..2e3782d15 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/CharacterInventory.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/CharacterInventory.cs @@ -161,7 +161,7 @@ namespace Barotrauma public override void CreateSlots() { - if (visualSlots == null) { visualSlots = new VisualSlot[capacity]; } + visualSlots ??= new VisualSlot[capacity]; float multiplier = !GUI.IsFourByThree() ? UIScale : UIScale * 0.925f; @@ -174,6 +174,12 @@ namespace Barotrauma (int)SlotPositions[i].X, (int)SlotPositions[i].Y, (int)(slotSprite.size.X * multiplier), (int)(slotSprite.size.Y * multiplier)); + + if (SlotTypes[i] == InvSlotType.HealthInterface && + character.CharacterHealth?.InventorySlotContainer != null) + { + slotRect.Width = slotRect.Height = (int)(character.CharacterHealth.InventorySlotContainer.Rect.Width * 1.2f); + } ItemContainer itemContainer = slots[i].FirstOrDefault()?.GetComponent(); if (itemContainer != null) @@ -238,6 +244,8 @@ namespace Barotrauma { if (visualSlots[i].Disabled || (slots[i].HideIfEmpty && slots[i].Empty())) { return true; } + if (CharacterHealth.OpenHealthWindow != Character.Controlled?.CharacterHealth && SlotTypes[i] == InvSlotType.HealthInterface) { return true; } + if (layout == Layout.Default) { if (PersonalSlots.HasFlag(SlotTypes[i]) && !personalSlotArea.Contains(visualSlots[i].Rect.Center + visualSlots[i].DrawOffset.ToPoint())) { return true; } @@ -315,7 +323,7 @@ namespace Barotrauma case Layout.Default: { int personalSlotCount = SlotTypes.Count(s => PersonalSlots.HasFlag(s)); - int normalSlotCount = SlotTypes.Count(s => !PersonalSlots.HasFlag(s)); + int normalSlotCount = SlotTypes.Count(s => !PersonalSlots.HasFlag(s) && s != InvSlotType.HealthInterface); int x = GameMain.GraphicsWidth / 2 - normalSlotCount * (SlotSize.X + Spacing) / 2; int upperX = HUDLayoutSettings.BottomRightInfoArea.X - SlotSize.X - Spacing * 4 - HideButtonWidth; @@ -359,7 +367,8 @@ namespace Barotrauma int personalSlotX = HUDLayoutSettings.InventoryAreaLower.Right - SlotSize.X - Spacing; for (int i = 0; i < visualSlots.Length; i++) { - if (HideSlot(i)) continue; + if (HideSlot(i) || SlotTypes[i] == InvSlotType.HealthInterface) { continue; } + if (SlotTypes[i] == InvSlotType.RightHand || SlotTypes[i] == InvSlotType.LeftHand) { continue; } if (PersonalSlots.HasFlag(SlotTypes[i])) { //upperX -= slotSize.X + spacing; @@ -371,10 +380,18 @@ namespace Barotrauma } int lowerX = x; + int handSlotX = x; int personalSlotY = GameMain.GraphicsHeight - bottomOffset * 2 - Spacing * 2 - (int)(!GUI.IsFourByThree() ? UnequippedIndicator.size.Y * UIScale * IndicatorScaleAdjustment : UnequippedIndicator.size.Y * UIScale * IndicatorScaleAdjustment * 2f); for (int i = 0; i < SlotPositions.Length; i++) { - if (HideSlot(i)) continue; + if (SlotTypes[i] == InvSlotType.RightHand || SlotTypes[i] == InvSlotType.LeftHand) + { + SlotPositions[i] = new Vector2(handSlotX, personalSlotY); + handSlotX += visualSlots[i].Rect.Width + Spacing; + continue; + } + + if (HideSlot(i) || SlotTypes[i] == InvSlotType.HealthInterface) { continue; } if (PersonalSlots.HasFlag(SlotTypes[i])) { SlotPositions[i] = new Vector2(personalSlotX, personalSlotY); @@ -390,7 +407,8 @@ namespace Barotrauma x = lowerX; for (int i = 0; i < SlotPositions.Length; i++) { - if (!HideSlot(i)) continue; + if (!HideSlot(i) || SlotTypes[i] == InvSlotType.HealthInterface) { continue; } + if (SlotTypes[i] == InvSlotType.RightHand || SlotTypes[i] == InvSlotType.LeftHand) { continue; } x -= visualSlots[i].Rect.Width + Spacing; SlotPositions[i] = new Vector2(x, GameMain.GraphicsHeight - bottomOffset); } @@ -404,7 +422,8 @@ namespace Barotrauma for (int i = 0; i < SlotPositions.Length; i++) { - if (HideSlot(i)) continue; + if (HideSlot(i) || SlotTypes[i] == InvSlotType.HealthInterface) { continue; } + if (SlotTypes[i] == InvSlotType.RightHand || SlotTypes[i] == InvSlotType.LeftHand) { continue; } if (PersonalSlots.HasFlag(SlotTypes[i])) { SlotPositions[i] = new Vector2(personalSlotX, personalSlotY); @@ -416,9 +435,16 @@ namespace Barotrauma x += visualSlots[i].Rect.Width + Spacing; } } + int handSlotX = x - visualSlots[0].Rect.Width - Spacing; for (int i = 0; i < SlotPositions.Length; i++) { - if (!HideSlot(i)) continue; + if (SlotTypes[i] == InvSlotType.RightHand || SlotTypes[i] == InvSlotType.LeftHand) + { + bool rightSlot = SlotTypes[i] == InvSlotType.RightHand; + SlotPositions[i] = new Vector2(rightSlot ? handSlotX : handSlotX - visualSlots[0].Rect.Width - Spacing, personalSlotY); + continue; + } + if (!HideSlot(i) || SlotTypes[i] == InvSlotType.HealthInterface) { continue; } SlotPositions[i] = new Vector2(x, GameMain.GraphicsHeight - bottomOffset); x += visualSlots[i].Rect.Width + Spacing; } @@ -432,7 +458,7 @@ namespace Barotrauma int x = startX, y = startY; for (int i = 0; i < SlotPositions.Length; i++) { - if (HideSlot(i)) continue; + if (HideSlot(i) || SlotTypes[i] == InvSlotType.HealthInterface) { continue; } if (SlotTypes[i] == InvSlotType.Card || SlotTypes[i] == InvSlotType.Headset || SlotTypes[i] == InvSlotType.InnerClothes) { SlotPositions[i] = new Vector2(x, y); @@ -444,7 +470,7 @@ namespace Barotrauma int n = 0; for (int i = 0; i < SlotPositions.Length; i++) { - if (HideSlot(i)) continue; + if (HideSlot(i) || SlotTypes[i] == InvSlotType.HealthInterface) { continue; } if (SlotTypes[i] != InvSlotType.Card && SlotTypes[i] != InvSlotType.Headset && SlotTypes[i] != InvSlotType.InnerClothes) { SlotPositions[i] = new Vector2(x, y); @@ -461,13 +487,23 @@ namespace Barotrauma } break; } - + + if (character.CharacterHealth?.UseHealthWindow ?? false) + { + Vector2 pos = character.CharacterHealth.InventorySlotContainer.Rect.Location.ToVector2(); + for (int i = 0; i < capacity; i++) + { + if (SlotTypes[i] != InvSlotType.HealthInterface) { continue; } + SlotPositions[i] = pos; + pos.Y += visualSlots[i].Rect.Height + Spacing; + } + } + CreateSlots(); if (layout == Layout.Default) { HUDLayoutSettings.InventoryTopY = visualSlots[0].EquipButtonRect.Y - (int)(15 * GUI.Scale); } - } protected override void ControlInput(Camera cam) @@ -652,6 +688,11 @@ namespace Barotrauma { break; } + //if putting an item to a container with a max stack size of 1, only put one item from the stack + if (quickUseAction == QuickUseAction.PutToContainer && (character.SelectedConstruction?.GetComponent()?.MaxStackSize ?? 0) <= 1) + { + break; + } } } @@ -661,7 +702,7 @@ namespace Barotrauma if (item != null) { var slot = visualSlots[i]; - if (item.AllowedSlots.Any(a => a != InvSlotType.Any)) + if (item.AllowedSlots.Any(a => a != InvSlotType.Any && a != InvSlotType.HealthInterface)) { HandleButtonEquipStates(item, slot, deltaTime); } @@ -840,7 +881,9 @@ namespace Barotrauma private QuickUseAction GetQuickUseAction(Item item, bool allowEquip, bool allowInventorySwap, bool allowApplyTreatment) { - if (allowApplyTreatment && CharacterHealth.OpenHealthWindow != null) + if (allowApplyTreatment && CharacterHealth.OpenHealthWindow != null && + //if the item can be equipped in the health interface slot, don't use it as a treatment but try to equip it + !item.AllowedSlots.Contains(InvSlotType.HealthInterface)) { return QuickUseAction.UseTreatment; } @@ -1135,7 +1178,7 @@ namespace Barotrauma for (int i = 0; i < capacity; i++) { - if (HideSlot(i)) { continue; } + if (HideSlot(i) || SlotTypes[i] == InvSlotType.HealthInterface) { continue; } //don't draw the item if it's being dragged out of the slot bool drawItem = !DraggingItems.Any() || !slots[i].Items.All(it => DraggingItems.Contains(it)) || visualSlots[i].MouseOn(); @@ -1189,7 +1232,7 @@ namespace Barotrauma highlightedQuickUseSlot = visualSlots[i]; } - if (!slots[i].First().AllowedSlots.Any(a => a == InvSlotType.Any)) + if (slots[i].First().AllowedSlots.Count() == 1 || SlotTypes[i] == InvSlotType.HealthInterface) { continue; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/EntitySpawnerComponent.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/EntitySpawnerComponent.cs new file mode 100644 index 000000000..cd8d21dcf --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/EntitySpawnerComponent.cs @@ -0,0 +1,61 @@ +using System; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace Barotrauma.Items.Components +{ + internal partial class EntitySpawnerComponent + { + public Vector2 DrawSize => Vector2.Zero; + + public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1) + { + if (!editing) { return; } + + switch (SpawnAreaShape) + { + case AreaShape.Rectangle: + { + RectangleF rect = GetAreaRectangle(SpawnAreaBounds, SpawnAreaOffset, draw: true); + GUI.DrawRectangle(spriteBatch, rect.Location, rect.Size, GUI.Style.Red, isFilled: false, 0f, 4f); + + if (MaximumAmountRangePadding > 0f) + { + rect.Inflate(MaximumAmountRangePadding, MaximumAmountRangePadding); + GUI.DrawRectangle(spriteBatch, rect.Location, rect.Size, GUI.Style.Red, isFilled: false, 0f, 2f); + } + break; + } + case AreaShape.Circle: + Vector2 center = item.WorldPosition; + center.Y = -center.Y; + center += SpawnAreaOffset; + spriteBatch.DrawCircle(center, SpawnAreaRadius, 32, GUI.Style.Red, thickness: 4f); + + if (MaximumAmountRangePadding > 0f) + { + spriteBatch.DrawCircle(center, SpawnAreaRadius + MaximumAmountRangePadding, 32, GUI.Style.Red, thickness: 2f); + } + break; + } + + if (!OnlySpawnWhenCrewInRange) { return; } + + switch (CrewAreaShape) + { + case AreaShape.Rectangle: + { + RectangleF rect = GetAreaRectangle(CrewAreaBounds, CrewAreaOffset, draw: true); + GUI.DrawRectangle(spriteBatch, rect.Location, rect.Size, GUI.Style.Green, isFilled: false, 0f, 4f); + break; + } + case AreaShape.Circle: + Vector2 center = item.WorldPosition; + center.Y = -center.Y; + center += CrewAreaOffset; + spriteBatch.DrawCircle(center, CrewAreaRadius, 32, GUI.Style.Green); + break; + } + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/GeneticMaterial.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/GeneticMaterial.cs new file mode 100644 index 000000000..174fba2b0 --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/GeneticMaterial.cs @@ -0,0 +1,86 @@ +using Barotrauma.Networking; +using Microsoft.Xna.Framework; +using System.Linq; + +namespace Barotrauma.Items.Components +{ + partial class GeneticMaterial : ItemComponent + { + [Serialize(0.0f, false)] + public float TooltipValueMin { get; set; } + + [Serialize(0.0f, false)] + public float TooltipValueMax { get; set; } + + public override void AddTooltipInfo(ref string name, ref string description) + { + if (!string.IsNullOrEmpty(materialName) && item.ContainedItems.Count() > 0) + { + string mergedMaterialName = materialName; + foreach (Item containedItem in item.ContainedItems) + { + var containedMaterial = containedItem.GetComponent(); + if (containedMaterial == null) { continue; } + mergedMaterialName += ", " + containedMaterial.materialName; + } + name = name.Replace(materialName, mergedMaterialName); + } + + if (Tainted) + { + name = TextManager.GetWithVariable("entityname.taintedgeneticmaterial", "[geneticmaterialname]", name); + } + + if (TextManager.ContainsTag("entitydescription." + Item.prefab.Identifier)) + { + int value = (int)MathHelper.Lerp(TooltipValueMin, TooltipValueMax, item.ConditionPercentage / 100.0f); + description = TextManager.GetWithVariable("entitydescription." + Item.prefab.Identifier, "[value]", value.ToString()); + } + foreach (Item containedItem in item.ContainedItems) + { + var containedGeneticMaterial = containedItem.GetComponent(); + if (containedGeneticMaterial == null) { continue; } + string _ = string.Empty; + string containedDescription = containedItem.Description; + containedGeneticMaterial.AddTooltipInfo(ref _, ref containedDescription); + if (!string.IsNullOrEmpty(containedDescription)) + { + description += '\n' + containedDescription; + } + } + } + + public void ModifyDeconstructInfo(Deconstructor deconstructor, ref string buttonText, ref string infoText) + { + if (deconstructor.InputContainer.Inventory.AllItems.Count() == 2) + { + if (!deconstructor.InputContainer.Inventory.AllItems.All(it => it.prefab == item.prefab)) + { + buttonText = TextManager.Get("researchstation.combine"); + infoText = TextManager.Get("researchstation.combine.infotext"); + } + else + { + buttonText = TextManager.Get("researchstation.refine"); + int taintedProbability = (int)(GetTaintedProbabilityOnRefine(Character.Controlled) * 100); + infoText = TextManager.GetWithVariable("researchstation.refine.infotext", "[taintedprobability]", taintedProbability.ToString()); + } + } + } + + public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime) + { + Tainted = msg.ReadBoolean(); + if (Tainted) + { + uint selectedTaintedEffectId = msg.ReadUInt32(); + selectedTaintedEffect = AfflictionPrefab.Prefabs.Find(a => a.UIntIdentifier == selectedTaintedEffectId); + } + else + { + uint selectedEffectId = msg.ReadUInt32(); + selectedEffect = AfflictionPrefab.Prefabs.Find(a => a.UIntIdentifier == selectedEffectId); + } + } + } +} diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Holdable/IdCard.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Holdable/IdCard.cs index 69bc06a85..ebd3e27a8 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Holdable/IdCard.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Holdable/IdCard.cs @@ -1,13 +1,201 @@ -using Microsoft.Xna.Framework; +using System; +using Microsoft.Xna.Framework; using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Xml.Linq; +using Barotrauma.Extensions; +using Barotrauma.IO; namespace Barotrauma.Items.Components { partial class IdCard { - public Sprite StoredPortrait; - public Vector2 StoredSheetIndex; - public JobPrefab StoredJobPrefab; - public List StoredAttachments; + public struct OwnerAppearance + { + public Sprite Portrait; + public Vector2 SheetIndex; + public JobPrefab JobPrefab; + public List Attachments; + public Color HairColor; + public Color FacialHairColor; + public Color SkinColor; + + public void ExtractJobPrefab(string[] tags) + { + string jobIdTag = tags.FirstOrDefault(s => s.StartsWith("jobid:")); + + if (jobIdTag != null && jobIdTag.Length > 6) + { + string jobId = jobIdTag.Substring(6); + if (jobId != string.Empty) + { + JobPrefab = JobPrefab.Get(jobId); + } + } + } + + public void ExtractAppearance(CharacterInfo characterInfo, string[] tags) + { + Gender disguisedGender = Gender.None; + Race disguisedRace = Race.None; + int disguisedHeadSpriteId = -1; + int disguisedHairIndex = -1; + int disguisedBeardIndex = -1; + int disguisedMoustacheIndex = -1; + int disguisedFaceAttachmentIndex = -1; + Color hairColor = Color.Black; + Color facialHairColor = Color.Black; + Color skinColor = Color.Black; + + foreach (string tag in tags) + { + string[] s = tag.Split(':'); + + switch (s[0].ToLowerInvariant()) + { + case "haircolor": + hairColor = XMLExtensions.ParseColor(s[1]); + break; + + case "facialhaircolor": + facialHairColor = XMLExtensions.ParseColor(s[1]); + break; + + case "skincolor": + skinColor = XMLExtensions.ParseColor(s[1]); + break; + + case "gender": + Enum.TryParse(s[1], ignoreCase: true, out disguisedGender); + break; + + case "race": + Enum.TryParse(s[1], ignoreCase: true, out disguisedRace); + break; + + case "headspriteid": + int.TryParse(s[1], NumberStyles.Any, CultureInfo.InvariantCulture, out disguisedHeadSpriteId); + break; + + case "hairindex": + disguisedHairIndex = int.Parse(s[1]); + break; + + case "beardindex": + disguisedBeardIndex = int.Parse(s[1]); + break; + + case "moustacheindex": + disguisedMoustacheIndex = int.Parse(s[1]); + break; + + case "faceattachmentindex": + disguisedFaceAttachmentIndex = int.Parse(s[1]); + break; + + case "sheetindex": + string[] vectorValues = s[1].Split(";"); + SheetIndex = new Vector2(float.Parse(vectorValues[0]), float.Parse(vectorValues[1])); + break; + } + } + + if ((characterInfo.HasGenders && disguisedGender == Gender.None) + || (characterInfo.HasRaces && disguisedRace == Race.None) + || disguisedHeadSpriteId <= 0) + { + Portrait = null; + Attachments = null; + return; + } + + foreach (XElement limbElement in characterInfo.Ragdoll.MainElement.Elements()) + { + if (!limbElement.GetAttributeString("type", "").Equals("head", StringComparison.OrdinalIgnoreCase)) { continue; } + + XElement spriteElement = limbElement.Element("sprite"); + if (spriteElement == null) { continue; } + + string spritePath = spriteElement.Attribute("texture").Value; + + spritePath = spritePath.Replace("[GENDER]", disguisedGender.ToString().ToLowerInvariant()); + spritePath = spritePath.Replace("[RACE]", disguisedRace.ToString().ToLowerInvariant()); + spritePath = spritePath.Replace("[HEADID]", disguisedHeadSpriteId.ToString()); + + string fileName = Path.GetFileNameWithoutExtension(spritePath); + + //go through the files in the directory to find a matching sprite + foreach (string file in Directory.GetFiles(Path.GetDirectoryName(spritePath))) + { + if (!file.EndsWith(".png", StringComparison.OrdinalIgnoreCase)) + { + continue; + } + string fileWithoutTags = Path.GetFileNameWithoutExtension(file); + fileWithoutTags = fileWithoutTags.Split('[', ']').First(); + if (fileWithoutTags != fileName) { continue; } + Portrait = new Sprite(spriteElement, "", file) { RelativeOrigin = Vector2.Zero }; + break; + } + + break; + } + + if (characterInfo.Wearables != null) + { + float baldnessChance = disguisedGender == Gender.Female ? 0.05f : 0.2f; + + List createElementList(WearableType wearableType, float emptyCommonness = 1.0f) + => CharacterInfo.AddEmpty( + characterInfo.FilterByTypeAndHeadID( + characterInfo.FilterElementsByGenderAndRace(characterInfo.Wearables, disguisedGender, disguisedRace), + wearableType, disguisedHeadSpriteId), + wearableType, emptyCommonness); + + var disguisedHairs = createElementList(WearableType.Hair, baldnessChance); + var disguisedBeards = createElementList(WearableType.Beard); + var disguisedMoustaches = createElementList(WearableType.Moustache); + var disguisedFaceAttachments = createElementList(WearableType.FaceAttachment); + + XElement getElementFromList(List list, int index) + => CharacterInfo.IsValidIndex(index, list) + ? list[index] + : characterInfo.GetRandomElement(list); + + var disguisedHairElement = getElementFromList(disguisedHairs, disguisedHairIndex); + var disguisedBeardElement = getElementFromList(disguisedBeards, disguisedBeardIndex); + var disguisedMoustacheElement = getElementFromList(disguisedMoustaches, disguisedMoustacheIndex); + var disguisedFaceAttachmentElement = getElementFromList(disguisedFaceAttachments, disguisedFaceAttachmentIndex); + + Attachments = new List(); + + void loadAttachments(List attachments, XElement element, WearableType wearableType) + { + foreach (var s in element?.Elements("sprite") ?? Enumerable.Empty()) + { + attachments.Add(new WearableSprite(s, wearableType)); + } + } + + loadAttachments(Attachments, disguisedFaceAttachmentElement, WearableType.FaceAttachment); + loadAttachments(Attachments, disguisedBeardElement, WearableType.Beard); + loadAttachments(Attachments, disguisedMoustacheElement, WearableType.Moustache); + loadAttachments(Attachments, disguisedHairElement, WearableType.Hair); + + loadAttachments(Attachments, + characterInfo.OmitJobInPortraitClothing + ? JobPrefab.NoJobElement?.Element("PortraitClothing") + : JobPrefab?.ClothingElement, + WearableType.JobIndicator); + } + + HairColor = hairColor; + FacialHairColor = facialHairColor; + SkinColor = skinColor; + } + } + + public OwnerAppearance StoredOwnerAppearance = default; } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Holdable/RangedWeapon.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Holdable/RangedWeapon.cs index ebe4c63e5..b6eca6599 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Holdable/RangedWeapon.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Holdable/RangedWeapon.cs @@ -7,6 +7,8 @@ using System.Collections.Generic; using Barotrauma.IO; using System.Text; using System.Xml.Linq; +using Barotrauma.Sounds; +using System.Linq; namespace Barotrauma.Items.Components { @@ -18,7 +20,11 @@ namespace Barotrauma.Items.Components protected float currentCrossHairScale, currentCrossHairPointerScale; + private RoundSound chargeSound; + private SoundChannel chargeSoundChannel; + private readonly List particleEmitters = new List(); + private readonly List particleEmitterCharges = new List(); [Serialize(1.0f, false, description: "The scale of the crosshair sprite (if there is one).")] public float CrossHairScale @@ -48,6 +54,12 @@ namespace Barotrauma.Items.Components case "particleemitter": particleEmitters.Add(new ParticleEmitter(subElement)); break; + case "particleemittercharge": + particleEmitterCharges.Add(new ParticleEmitter(subElement)); + break; + case "chargesound": + chargeSound = Submarine.LoadRoundSound(subElement, false); + break; } } } @@ -84,15 +96,62 @@ namespace Barotrauma.Items.Components crosshairPointerPos = PlayerInput.MousePosition; } + partial void UpdateProjSpecific(float deltaTime) + { + float chargeRatio = currentChargeTime / MaxChargeTime; + + switch (currentChargingState) + { + case ChargingState.WindingUp: + case ChargingState.WindingDown: + Vector2 particlePos = item.WorldPosition + ConvertUnits.ToDisplayUnits(TransformedBarrelPos); + float sizeMultiplier = Math.Clamp(chargeRatio, 0.1f, 1f); + foreach (ParticleEmitter emitter in particleEmitterCharges) + { + emitter.Emit(deltaTime, particlePos, hullGuess: null, sizeMultiplier: sizeMultiplier, colorMultiplier: emitter.Prefab.Properties.ColorMultiplier); + } + + if (chargeSoundChannel == null || !chargeSoundChannel.IsPlaying) + { + if (chargeSound != null) + { + chargeSoundChannel = SoundPlayer.PlaySound(chargeSound.Sound, item.WorldPosition, chargeSound.Volume, chargeSound.Range, ignoreMuffling: chargeSound.IgnoreMuffling); + if (chargeSoundChannel != null) chargeSoundChannel.Looping = true; + } + } + else if (chargeSoundChannel != null) + { + chargeSoundChannel.FrequencyMultiplier = MathHelper.Lerp(0.5f, 1.5f, chargeRatio); + } + break; + default: + if (chargeSoundChannel != null) + { + if (chargeSoundChannel.IsPlaying) + { + chargeSoundChannel.FadeOutAndDispose(); + chargeSoundChannel.Looping = false; + } + else + { + chargeSoundChannel = null; + } + } + break; + } + } + public override void DrawHUD(SpriteBatch spriteBatch, Character character) { if (character == null || !character.IsKeyDown(InputType.Aim)) { return; } //camera focused on some other item/device, don't draw the crosshair - if (character.ViewTarget != null && (character.ViewTarget is Item item) && item.Prefab.FocusOnSelected) { return; } + if (character.ViewTarget != null && (character.ViewTarget is Item viewTargetItem) && viewTargetItem.Prefab.FocusOnSelected) { return; } + //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; } GUI.HideCursor = (crosshairSprite != null || crosshairPointerSprite != null) && - GUI.MouseOn == null && !Inventory.IsMouseOnInventory() && !GameMain.Instance.Paused; + GUI.MouseOn == null && !Inventory.IsMouseOnInventory && !GameMain.Instance.Paused; if (GUI.HideCursor) { crosshairSprite?.Draw(spriteBatch, crosshairPos, Color.White, 0, currentCrossHairScale); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemComponent.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemComponent.cs index cc6ef2654..cb663cdcf 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemComponent.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemComponent.cs @@ -246,6 +246,7 @@ namespace Barotrauma.Items.Components public void PlaySound(ActionType type, Character user = null) { if (!hasSoundsOfType[(int)type]) { return; } + if (GameMain.Client?.MidRoundSyncing ?? false) { return; } if (loopingSound != null) { @@ -429,7 +430,7 @@ namespace Barotrauma.Items.Components } foreach (ItemComponent component in item.Components) { - if (component.name.ToLower() == LinkUIToComponent.ToLower()) + if (component.name.Equals(LinkUIToComponent, StringComparison.OrdinalIgnoreCase)) { linkToUIComponent = component; } @@ -443,9 +444,9 @@ namespace Barotrauma.Items.Components public virtual void DrawHUD(SpriteBatch spriteBatch, Character character) { } - public virtual void AddToGUIUpdateList() + public virtual void AddToGUIUpdateList(int order = 0) { - GuiFrame?.AddToGUIUpdateList(); + GuiFrame?.AddToGUIUpdateList(order: order); } public virtual void UpdateHUD(Character character, float deltaTime, Camera cam) { } @@ -620,6 +621,6 @@ namespace Barotrauma.Items.Components } OnResolutionChanged(); } - public virtual void AddTooltipInfo(ref string description) { } + public virtual void AddTooltipInfo(ref string name, ref string description) { } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemContainer.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemContainer.cs index 4da1bb77e..e087b99fa 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemContainer.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemContainer.cs @@ -19,6 +19,8 @@ namespace Barotrauma.Items.Components /// private float[] containedSpriteDepths; + private Sprite[] slotIcons; + public Sprite InventoryTopSprite { get { return inventoryTopSprite; } @@ -58,6 +60,9 @@ namespace Barotrauma.Items.Components [Serialize(null, false)] public string ContainedStateIndicatorStyle { get; set; } + [Serialize(-1, false, description: "Can be used to make the contained state indicator display the condition of the item in a specific slot even when the container's capacity is more than 1.")] + public int ContainedStateIndicatorSlot { get; set; } + [Serialize(true, false, description: "Should an indicator displaying the state of the contained items be displayed on this item's inventory slot. "+ "If this item can only contain one item, the indicator will display the condition of the contained item, otherwise it will indicate how full the item is.")] public bool ShowContainedStateIndicator { get; set; } @@ -85,6 +90,7 @@ namespace Barotrauma.Items.Components partial void InitProjSpecific(XElement element) { + slotIcons = new Sprite[capacity]; foreach (XElement subElement in element.Elements()) { switch (subElement.Name.ToString().ToLowerInvariant()) @@ -104,6 +110,17 @@ namespace Barotrauma.Items.Components case "containedstateindicatorempty": ContainedStateIndicatorEmpty = new Sprite(subElement); break; + case "sloticon": + int index = subElement.GetAttributeInt("slotindex", -1); + Sprite icon = new Sprite(subElement); + for (int i = 0; i < capacity; i++) + { + if (i == index || index == -1) + { + slotIcons[i] = icon; + } + } + break; } } @@ -197,7 +214,7 @@ namespace Barotrauma.Items.Components if (UILabel == string.Empty) { return string.Empty; } if (UILabel != null) { - return TextManager.Get("UILabel." + UILabel); + return TextManager.Get("UILabel." + UILabel, returnNull: true) ?? TextManager.Get(UILabel); } else { @@ -205,6 +222,12 @@ namespace Barotrauma.Items.Components } } + public Sprite GetSlotIcon(int slotIndex) + { + if (slotIndex < 0 || slotIndex >= slotIcons.Length) { return null; } + return slotIcons[slotIndex]; + } + public bool KeepOpenWhenEquippedBy(Character character) { if (!character.CanAccessInventory(Inventory) || @@ -266,7 +289,7 @@ namespace Barotrauma.Items.Components } else { - Matrix transform = Matrix.CreateRotationZ(item.body.Rotation); + Matrix transform = Matrix.CreateRotationZ(item.body.DrawRotation); if (item.body.Dir == -1.0f) { transformedItemPos.X = -transformedItemPos.X; @@ -277,7 +300,7 @@ namespace Barotrauma.Items.Components transformedItemPos = Vector2.Transform(transformedItemPos, transform); transformedItemInterval = Vector2.Transform(transformedItemInterval, transform); transformedItemIntervalHorizontal = Vector2.Transform(transformedItemIntervalHorizontal, transform); - transformedItemPos += item.DrawPosition; + transformedItemPos += item.body.DrawPosition; } Vector2 currentItemPos = transformedItemPos; @@ -319,7 +342,7 @@ namespace Barotrauma.Items.Components new Vector2(currentItemPos.X, -currentItemPos.Y), isWiringMode ? containedItem.GetSpriteColor() * 0.15f : containedItem.GetSpriteColor(), origin, - -(containedItem.body == null ? 0.0f : containedItem.body.DrawRotation + MathHelper.ToRadians(-item.Rotation)), + -(containedItem.body == null ? 0.0f : containedItem.body.DrawRotation ), containedItem.Scale, spriteEffects, depth: containedSpriteDepth); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/LightComponent.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/LightComponent.cs index f58aee17b..bff562d65 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/LightComponent.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/LightComponent.cs @@ -39,7 +39,7 @@ namespace Barotrauma.Items.Components public void Draw(SpriteBatch spriteBatch, bool editing = false, float itemDepth = -1) { - if (Light.LightSprite != null && (item.body == null || item.body.Enabled) && lightBrightness > 0.0f && IsOn) + if (Light.LightSprite != null && (item.body == null || item.body.Enabled) && lightBrightness > 0.0f && IsOn && Light.Enabled) { Vector2 origin = Light.LightSprite.Origin; if ((Light.LightSpriteEffect & SpriteEffects.FlipHorizontally) == SpriteEffects.FlipHorizontally) { origin.X = Light.LightSprite.SourceRect.Width - origin.X; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Deconstructor.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Deconstructor.cs index 711376751..6e6fbe63e 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Deconstructor.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Deconstructor.cs @@ -14,12 +14,22 @@ namespace Barotrauma.Items.Components } private GUIButton activateButton; private GUIComponent inputInventoryHolder, outputInventoryHolder; - private GUICustomComponent inputInventoryOverlay; private GUIComponent inSufficientPowerWarning; private bool pendingState; + private GUITextBlock infoArea; + + [Serialize("DeconstructorDeconstruct", true)] + public string ActivateButtonText { get; set; } + + [Serialize("", true)] + public string InfoText { get; set; } + + [Serialize(0.0f, true)] + public float InfoAreaWidth { get; set; } + partial void InitProjSpecific(XElement element) { CreateGUI(); @@ -39,6 +49,12 @@ namespace Barotrauma.Items.Components RelativeSpacing = 0.08f }; + new GUITextBlock(new RectTransform(new Vector2(1f, 0.07f), paddedFrame.RectTransform), item.Name, font: GUI.SubHeadingFont) + { + TextAlignment = Alignment.Center, + AutoScaleHorizontal = true + }; + var topFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.5f), paddedFrame.RectTransform), style: null); // === INPUT LABEL === // @@ -55,22 +71,23 @@ namespace Barotrauma.Items.Components // === INPUT SLOTS === // inputInventoryHolder = new GUIFrame(new RectTransform(new Vector2(0.7f, 1f), inputArea.RectTransform), style: null); - inputInventoryOverlay = new GUICustomComponent(new RectTransform(Vector2.One, inputInventoryHolder.RectTransform), DrawOverLay, null) { CanBeFocused = false }; + new GUICustomComponent(new RectTransform(Vector2.One, inputInventoryHolder.RectTransform), DrawOverLay, null) { CanBeFocused = false }; // === ACTIVATE BUTTON === // - var buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.4f, 0.7f), inputArea.RectTransform), childAnchor: Anchor.CenterLeft); + var buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.4f, 0.8f), inputArea.RectTransform), childAnchor: Anchor.CenterLeft); activateButton = new GUIButton(new RectTransform(new Vector2(0.95f, 0.8f), buttonContainer.RectTransform), TextManager.Get("DeconstructorDeconstruct"), style: "DeviceButton") { TextBlock = { AutoScaleHorizontal = true }, OnClicked = ToggleActive }; inSufficientPowerWarning = new GUITextBlock(new RectTransform(Vector2.One, activateButton.RectTransform), - TextManager.Get("DeconstructorNoPower"), textColor: GUI.Style.Orange, textAlignment: Alignment.Center, color: Color.Black, style: "OuterGlow") + TextManager.Get("DeconstructorNoPower"), textColor: GUI.Style.Orange, textAlignment: Alignment.Center, color: Color.Black, style: "OuterGlow", wrap: true) { HoverColor = Color.Black, IgnoreLayoutGroups = true, Visible = false, - CanBeFocused = false + CanBeFocused = false, + AutoScaleHorizontal = true }; // === OUTPUT AREA === // @@ -86,8 +103,70 @@ namespace Barotrauma.Items.Components outputLabel.RectTransform.Resize(new Point((int) outputLabel.Font.MeasureString(outputLabel.Text).X, outputLabel.RectTransform.Rect.Height)); new GUIFrame(new RectTransform(Vector2.One, outputLabelArea.RectTransform), style: "HorizontalLine"); - // === OUTPUT SLOTS === // - outputInventoryHolder = new GUIFrame(new RectTransform(new Vector2(1f, 1f), bottomFrame.RectTransform, Anchor.CenterLeft), style: null); + var outputArea = new GUILayoutGroup(new RectTransform(new Vector2(1f, 1f), bottomFrame.RectTransform, Anchor.CenterLeft), childAnchor: Anchor.BottomLeft, isHorizontal: true) { Stretch = true, RelativeSpacing = 0.05f }; + + // === OUTPUT SLOTS === // + outputInventoryHolder = new GUIFrame(new RectTransform(new Vector2(1f - InfoAreaWidth, 1f), outputArea.RectTransform, Anchor.CenterLeft), style: null); + + if (InfoAreaWidth >= 0.0f) + { + var infoAreaContainer = new GUILayoutGroup(new RectTransform(new Vector2(InfoAreaWidth, 0.8f), outputArea.RectTransform), childAnchor: Anchor.CenterLeft); + infoArea = new GUITextBlock(new RectTransform(new Vector2(0.95f, 0.95f), infoAreaContainer.RectTransform), string.Empty, wrap: true); + } + + ActivateButton.OnAddedToGUIUpdateList += (GUIComponent component) => + { + activateButton.Enabled = true; + if (string.IsNullOrEmpty(InfoText)) + { + infoArea.Text = string.Empty; + } + else + { + infoArea.Text = TextManager.Get(InfoText, returnNull: true) ?? InfoText; + } + if (IsActive) + { + activateButton.Text = TextManager.Get("DeconstructorCancel"); + infoArea.Text = string.Empty; + return; + } + bool outputsFound = false; + foreach (var (inputItem, deconstructItem) in GetAvailableOutputs(checkRequiredOtherItems: true)) + { + outputsFound = true; + if (!string.IsNullOrEmpty(deconstructItem.ActivateButtonText)) + { + string buttonText = TextManager.Get(deconstructItem.ActivateButtonText, returnNull: true) ?? deconstructItem.ActivateButtonText; + string infoText = string.Empty; + if (!string.IsNullOrEmpty(deconstructItem.InfoText)) + { + infoText = TextManager.Get(deconstructItem.InfoText, returnNull: true) ?? deconstructItem.InfoText; + } + inputItem.GetComponent()?.ModifyDeconstructInfo(this, ref buttonText, ref infoText); + activateButton.Text = buttonText; + if (infoArea != null) + { + infoArea.Text = infoText; + } + return; + } + } + //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)) + { + if (deconstructItem.RequiredOtherItem.Any() && !string.IsNullOrEmpty(deconstructItem.InfoTextOnOtherItemMissing)) + { + string missingItemName = TextManager.Get("entityname." + deconstructItem.RequiredOtherItem.First(), returnNull: true); + infoArea.Text = TextManager.GetWithVariable(deconstructItem.InfoTextOnOtherItemMissing, "[itemname]", missingItemName); + } + } + } + activateButton.Enabled = outputsFound; + activateButton.Text = TextManager.Get(ActivateButtonText); + }; } public override bool Select(Character character) @@ -126,13 +205,30 @@ namespace Barotrauma.Items.Components private void DrawOverLay(SpriteBatch spriteBatch, GUICustomComponent overlayComponent) { overlayComponent.RectTransform.SetAsLastChild(); - var lastSlot = inputContainer.Inventory.visualSlots.Last(); - GUI.DrawRectangle(spriteBatch, - new Rectangle( - lastSlot.Rect.X, lastSlot.Rect.Y + (int)(lastSlot.Rect.Height * (1.0f - progressState)), - lastSlot.Rect.Width, (int)(lastSlot.Rect.Height * progressState)), - GUI.Style.Green * 0.5f, isFilled: true); + if (!(inputContainer?.Inventory?.visualSlots is { } visualSlots)) { return; } + + if (DeconstructItemsSimultaneously) + { + for (int i = 0; i < InputContainer.Inventory.Capacity; i++) + { + if (InputContainer.Inventory.GetItemAt(i) == null) { continue; } + DrawProgressBar(InputContainer.Inventory.visualSlots[i]); + } + } + else + { + DrawProgressBar(inputContainer.Inventory.visualSlots.Last()); + } + + void DrawProgressBar(VisualSlot slot) + { + GUI.DrawRectangle(spriteBatch, + new Rectangle( + slot.Rect.X, slot.Rect.Y + (int)(slot.Rect.Height * (1.0f - progressState)), + slot.Rect.Width, (int)(slot.Rect.Height * progressState)), + GUI.Style.Green * 0.5f, isFilled: true); + } } public override void UpdateHUD(Character character, float deltaTime, Camera cam) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs index e63a0fb84..53c770df7 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs @@ -37,7 +37,7 @@ namespace Barotrauma.Items.Components private FabricationRecipe pendingFabricatedItem; - private Pair tooltip; + private (Rectangle area, string text)? tooltip; private GUITextBlock requiredTimeBlock; @@ -255,19 +255,22 @@ namespace Barotrauma.Items.Components var item1 = c1.GUIComponent.UserData as FabricationRecipe; var item2 = c2.GUIComponent.UserData as FabricationRecipe; - bool hasSkills1 = FabricationDegreeOfSuccess(character, item1.RequiredSkills) >= 0.5f; - bool hasSkills2 = FabricationDegreeOfSuccess(character, item2.RequiredSkills) >= 0.5f; + int itemPlacement1 = FabricationDegreeOfSuccess(character, item1.RequiredSkills) >= 0.5f ? 0 : -1; + int itemPlacement2 = FabricationDegreeOfSuccess(character, item2.RequiredSkills) >= 0.5f ? 0 : -1; - if (hasSkills1 != hasSkills2) + itemPlacement1 += item1.RequiresRecipe && !character.HasRecipeForItem(item1.TargetItem.Identifier) ? -2 : 0; + itemPlacement2 += item2.RequiresRecipe && !character.HasRecipeForItem(item2.TargetItem.Identifier) ? -2 : 0; + + if (itemPlacement1 != itemPlacement2) { - return hasSkills1 ? -1 : 1; + return itemPlacement1 > itemPlacement2 ? -1 : 1; } return string.Compare(item1.DisplayName, item2.DisplayName); }); var sufficientSkillsText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.15f), itemList.Content.RectTransform), - TextManager.Get("fabricatorsufficientskills", returnNull: true) ?? "Sufficient skills to fabricate", textColor: GUI.Style.Green, font: GUI.SubHeadingFont) + TextManager.Get("fabricatorsufficientskills"), textColor: GUI.Style.Green, font: GUI.SubHeadingFont) { AutoScaleHorizontal = true, CanBeFocused = false @@ -275,7 +278,7 @@ namespace Barotrauma.Items.Components sufficientSkillsText.RectTransform.SetAsFirstChild(); var insufficientSkillsText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.15f), itemList.Content.RectTransform), - TextManager.Get("fabricatorinsufficientskills", returnNull: true) ?? "Insufficient skills to fabricate", textColor: Color.Orange, font: GUI.SubHeadingFont) + TextManager.Get("fabricatorinsufficientskills"), textColor: Color.Orange, font: GUI.SubHeadingFont) { AutoScaleHorizontal = true, CanBeFocused = false @@ -285,6 +288,18 @@ namespace Barotrauma.Items.Components { insufficientSkillsText.RectTransform.RepositionChildInHierarchy(itemList.Content.RectTransform.GetChildIndex(firstinSufficient.RectTransform)); } + + var requiresRecipeText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.15f), itemList.Content.RectTransform), + TextManager.Get("fabricatorrequiresrecipe"), textColor: Color.Red, font: GUI.SubHeadingFont) + { + AutoScaleHorizontal = true, + CanBeFocused = false + }; + var firstRequiresRecipe = itemList.Content.Children.FirstOrDefault(c => c.UserData is FabricationRecipe fabricableItem && (fabricableItem.RequiresRecipe && !character.HasRecipeForItem(fabricableItem.TargetItem.Identifier))); + if (firstRequiresRecipe != null) + { + requiresRecipeText.RectTransform.RepositionChildInHierarchy(itemList.Content.RectTransform.GetChildIndex(firstRequiresRecipe.RectTransform)); + } } private void DrawInputOverLay(SpriteBatch spriteBatch, GUICustomComponent overlayComponent) @@ -297,6 +312,7 @@ namespace Barotrauma.Items.Components int slotIndex = 0; var missingItems = new List(); + foreach (FabricationRecipe.RequiredItem requiredItem in targetItem.RequiredItems) { for (int i = 0; i < requiredItem.Amount; i++) @@ -308,6 +324,8 @@ namespace Barotrauma.Items.Components { missingItems.Remove(missingItems.FirstOrDefault(mi => mi.ItemPrefabs.Contains(item.prefab))); } + var missingCounts = missingItems.GroupBy(missingItem => missingItem).ToDictionary(x => x.Key, x => x.Count()); + missingItems = missingItems.Distinct().ToList(); var availableIngredients = GetAvailableIngredients(); @@ -318,30 +336,30 @@ namespace Barotrauma.Items.Components slotIndex++; } - //highlight suitable ingredients in linked inventories - foreach (Item item in availableIngredients) - { - if (item.ParentInventory != inputContainer.Inventory && IsItemValidIngredient(item, requiredItem)) - { - int availableSlotIndex = item.ParentInventory.FindIndex(item); - //slots are null if the inventory has never been displayed - //(linked item, but the UI is not set to be displayed at the same time) - if (item.ParentInventory.visualSlots != null) - { - if (item.ParentInventory.visualSlots[availableSlotIndex].HighlightTimer <= 0.0f) - { - item.ParentInventory.visualSlots[availableSlotIndex].ShowBorderHighlight(GUI.Style.Green, 0.5f, 0.5f, 0.2f); - if (slotIndex < inputContainer.Capacity) + requiredItem.ItemPrefabs + .Where(requiredPrefab => availableIngredients.ContainsKey(requiredPrefab.Identifier)) + .ForEach(requiredPrefab => { + var availablePrefabs = availableIngredients[requiredPrefab.Identifier]; + + availablePrefabs + .Where(availablePrefab => availablePrefab.ParentInventory != inputContainer.Inventory) + .Where(availablePrefab => availablePrefab.ParentInventory.visualSlots != null) //slots are null if the inventory has never been displayed + .ForEach(availablePrefab => { //(linked item, but the UI is not set to be displayed at the same time) + int availableSlotIndex = availablePrefab.ParentInventory.FindIndex(availablePrefab); + + if (availablePrefab.ParentInventory.visualSlots[availableSlotIndex].HighlightTimer <= 0.0f) { - inputContainer.Inventory.visualSlots[slotIndex].ShowBorderHighlight(GUI.Style.Green, 0.5f, 0.5f, 0.2f); + availablePrefab.ParentInventory.visualSlots[availableSlotIndex].ShowBorderHighlight(GUI.Style.Green, 0.5f, 0.5f, 0.2f); + if (slotIndex < inputContainer.Capacity) + { + inputContainer.Inventory.visualSlots[slotIndex].ShowBorderHighlight(GUI.Style.Green, 0.5f, 0.5f, 0.2f); + } } - } - } - } - } + }); + }); if (slotIndex >= inputContainer.Capacity) { break; } - + var itemIcon = requiredItem.ItemPrefabs.First().InventoryIcon ?? requiredItem.ItemPrefabs.First().sprite; Rectangle slotRect = inputContainer.Inventory.visualSlots[slotIndex].Rect; itemIcon.Draw( @@ -350,11 +368,32 @@ namespace Barotrauma.Items.Components color: requiredItem.ItemPrefabs.First().InventoryIconColor * 0.3f, scale: Math.Min(slotRect.Width / itemIcon.size.X, slotRect.Height / itemIcon.size.Y)); + + if (missingCounts[requiredItem] > 1) + { + Vector2 stackCountPos = new Vector2(slotRect.Right, slotRect.Bottom); + string stackCountText = "x" + missingCounts[requiredItem]; + stackCountPos -= GUI.SmallFont.MeasureString(stackCountText) + new Vector2(4, 2); + GUI.SmallFont.DrawString(spriteBatch, stackCountText, stackCountPos + Vector2.One, Color.Black); + GUI.SmallFont.DrawString(spriteBatch, stackCountText, stackCountPos, Color.White); + } + if (requiredItem.UseCondition && requiredItem.MinCondition < 1.0f) { - GUI.DrawRectangle(spriteBatch, new Rectangle(slotRect.X, slotRect.Bottom - 8, slotRect.Width, 8), Color.Black * 0.8f, true); + DrawConditionBar(spriteBatch, requiredItem.MinCondition); + } + else if (requiredItem.MaxCondition < 1.0f) + { + DrawConditionBar(spriteBatch, requiredItem.MaxCondition); + } + + void DrawConditionBar(SpriteBatch sb, float condition) + { + int spacing = GUI.IntScale(4); + int height = GUI.IntScale(10); + GUI.DrawRectangle(spriteBatch, new Rectangle(slotRect.X + spacing, slotRect.Bottom - spacing - height, slotRect.Width - spacing * 2, height), Color.Black * 0.8f, true); GUI.DrawRectangle(spriteBatch, - new Rectangle(slotRect.X, slotRect.Bottom - 8, (int)(slotRect.Width * requiredItem.MinCondition), 8), + new Rectangle(slotRect.X + spacing, slotRect.Bottom - spacing - height, (int)((slotRect.Width - spacing * 2) * condition), height), GUI.Style.Green * 0.8f, true); } @@ -367,6 +406,10 @@ namespace Barotrauma.Items.Components { toolTipText += " " + (int)Math.Round(requiredItem.MinCondition * 100) + "%"; } + else if(requiredItem.MaxCondition < 1.0f) + { + toolTipText += " 0-" + (int)Math.Round(requiredItem.MaxCondition * 100) + "%"; + } else if (requiredItem.MaxCondition <= 0.0f) { toolTipText = TextManager.GetWithVariable("displayname.emptyitem", "[itemname]", toolTipText); @@ -375,7 +418,7 @@ namespace Barotrauma.Items.Components { toolTipText += '\n' + requiredItem.ItemPrefabs.First().Description; } - tooltip = new Pair(slotRect, toolTipText); + tooltip = (slotRect, toolTipText); } slotIndex++; @@ -415,7 +458,7 @@ namespace Barotrauma.Items.Components if (tooltip != null) { - GUIComponent.DrawToolTip(spriteBatch, tooltip.Second, tooltip.First); + GUIComponent.DrawToolTip(spriteBatch, tooltip.Value.text, tooltip.Value.area); tooltip = null; } } @@ -435,6 +478,22 @@ namespace Barotrauma.Items.Components if (recipe?.DisplayName == null) { continue; } child.Visible = recipe.DisplayName.ToLower().Contains(filter); } + + //go through the elements backwards, and disable the labels ("insufficient skills to fabricate", "recipe required...") if there's no items below them + bool recipeVisible = false; + foreach (GUIComponent child in itemList.Content.Children.Reverse()) + { + if (!(child.UserData is FabricationRecipe recipe)) + { + child.Visible = recipeVisible; + recipeVisible = false; + } + else + { + recipeVisible = child.Visible; + } + } + itemList.UpdateScrollBarSize(); itemList.BarScroll = 0.0f; @@ -470,14 +529,30 @@ namespace Barotrauma.Items.Components }; }*/ + string itemName = GetRecipeNameAndAmount(selectedItem); + string name = itemName; + + float quality = GetFabricatedItemQuality(selectedItem, user); + if (quality > 0) + { + name = TextManager.GetWithVariable("itemname.quality" + (int)quality, "[itemname]", itemName + '\n', fallBackTag: "itemname.quality3"); + } var nameBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedFrame.RectTransform), - GetRecipeNameAndAmount(selectedItem), textAlignment: Alignment.CenterLeft, textColor: Color.Aqua, font: GUI.SubHeadingFont) + name, textAlignment: Alignment.TopLeft, textColor: Color.Aqua, font: GUI.SubHeadingFont, parseRichText: true) { AutoScaleHorizontal = true }; - - nameBlock.Padding = new Vector4(0, nameBlock.Padding.Y, nameBlock.Padding.Z, nameBlock.Padding.W); + nameBlock.Padding = new Vector4(0, nameBlock.Padding.Y, GUI.IntScale(5), nameBlock.Padding.W); + if (nameBlock.TextScale < 0.7f) + { + nameBlock.SetRichText(TextManager.GetWithVariable("itemname.quality" + (int)quality, "[itemname]", itemName, fallBackTag: "itemname.quality3")); + nameBlock.AutoScaleHorizontal = false; + nameBlock.TextScale = 0.7f; + nameBlock.Wrap = true; + nameBlock.SetTextPos(); + nameBlock.RectTransform.MinSize = new Point(0, (int)(nameBlock.TextSize.Y * nameBlock.TextScale)); + } if (!string.IsNullOrWhiteSpace(selectedItem.TargetItem.Description)) { @@ -489,6 +564,7 @@ namespace Barotrauma.Items.Components while (description.Rect.Height + nameBlock.Rect.Height > paddedFrame.Rect.Height) { var lines = description.WrappedText.Split('\n'); + if (lines.Length <= 1) { break; } var newString = string.Join('\n', lines.Take(lines.Length - 1)); description.Text = newString.Substring(0, newString.Length - 4) + "..."; description.CalculateHeightFromText(); @@ -598,10 +674,15 @@ namespace Barotrauma.Items.Components { foreach (GUIComponent child in itemList.Content.Children) { - var itemPrefab = child.UserData as FabricationRecipe; - if (itemPrefab == null) continue; + if (!(child.UserData is FabricationRecipe itemPrefab)) { continue; } - bool canBeFabricated = CanBeFabricated(itemPrefab, availableIngredients); + if (itemPrefab != selectedItem && + (child.Rect.Y > itemList.Rect.Bottom || child.Rect.Bottom < itemList.Rect.Y)) + { + continue; + } + + bool canBeFabricated = CanBeFabricated(itemPrefab, availableIngredients, character); if (itemPrefab == selectedItem) { activateButton.Enabled = canBeFabricated; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs index 0ec56396d..890b2910a 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs @@ -1,103 +1,573 @@ -using Barotrauma.Extensions; -using FarseerPhysics; +#nullable enable +using Barotrauma.Extensions; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; -using System.Xml.Linq; +using Microsoft.Xna.Framework.Input; namespace Barotrauma.Items.Components { + internal readonly struct MiniMapGUIComponent + { + public readonly GUIComponent RectComponent; + public readonly GUIComponent BorderComponent; + + public MiniMapGUIComponent(GUIComponent 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 + { + public readonly Sprite Sprite; + public readonly Color Color; + + public MiniMapSprite(JobPrefab prefab) + { + Sprite = prefab.IconSmall; + Color = prefab.UIColor; + } + + public MiniMapSprite(Order order) + { + Sprite = order.SymbolSprite; + Color = order.Color; + } + } + + internal readonly struct MiniMapHullData + { + public readonly List> Polygon; + public readonly (RectangleF Rect, Hull Hull)[] RectDatas; + public readonly RectangleF Bounds; + public readonly Point ParentSize; + + public MiniMapHullData(List> polygon, RectangleF bounds, Point parentSize, ImmutableArray rects, ImmutableArray hulls) + { + ParentSize = parentSize; + Bounds = bounds; + Polygon = polygon; + int count = Math.Min(rects.Length, hulls.Length); + RectDatas = new (RectangleF Rect, Hull Hull)[count]; + for (int i = 0; i < count; i++) + { + RectDatas[i] = (rects[i], hulls[i]); + } + } + } + + internal enum MiniMapMode + { + None, + HullStatus, + ElectricalView, + ItemFinder + } + + internal readonly struct RelativeEntityRect + { + public readonly Vector2 RelativePosition; + public readonly Vector2 RelativeSize; + + public RelativeEntityRect(RectangleF worldBorders, RectangleF entityRect) + { + RelativePosition = new Vector2((entityRect.X - worldBorders.X) / worldBorders.Width, (worldBorders.Y - entityRect.Y) / worldBorders.Height); + RelativeSize = new Vector2(entityRect.Width / worldBorders.Width, entityRect.Height / worldBorders.Height); + } + + public Vector2 PositionRelativeTo(RectangleF frame, bool skipOffset = false) + { + if (skipOffset) + { + return RelativePosition * frame.Size; + } + + return frame.Location + RelativePosition * frame.Size; + } + + public Vector2 SizeRelativeTo(RectangleF frame) + { + return RelativeSize * frame.Size; + } + + public RectangleF RectangleRelativeTo(RectangleF frame, bool skipOffset = false) + { + return new RectangleF(PositionRelativeTo(frame, skipOffset), SizeRelativeTo(frame)); + } + + public void Deconstruct(out float posX, out float posY, out float sizeX, out float sizeY) + { + posX = RelativePosition.X; + posY = RelativePosition.Y; + sizeX = RelativeSize.X; + sizeY = RelativeSize.Y; + } + } + + internal readonly struct MiniMapSettings + { + public static MiniMapSettings Default = new MiniMapSettings + ( + ignoreOutposts: false, + createHullElements: true, + elementColor: MiniMap.MiniMapBaseColor + ); + + public readonly bool IgnoreOutposts; + public readonly bool CreateHullElements; + public readonly Color ElementColor; + + public MiniMapSettings(bool ignoreOutposts = false, bool createHullElements = false, Color? elementColor = null) + { + IgnoreOutposts = ignoreOutposts; + CreateHullElements = createHullElements; + ElementColor = elementColor ?? MiniMap.MiniMapBaseColor; + } + } + partial class MiniMap : Powered { private GUIFrame submarineContainer; private GUIFrame hullInfoFrame; + private GUIScissorComponent? scissorComponent; + private GUIComponent? miniMapContainer; + private GUIComponent miniMapFrame; + private GUIComponent electricalFrame; + private GUILayoutGroup reportFrame; + private GUILayoutGroup searchBarFrame; + private GUITextBox searchBar; + private GUIComponent? searchAutoComplete; - private GUITextBlock hullNameText, hullBreachText, hullAirQualityText, hullWaterText; + private ItemPrefab? searchedPrefab; - private string noPowerTip = ""; + private GUITextBlock tooltipHeader, tooltipFirstLine, tooltipSecondLine, tooltipThirdLine; + + private string noPowerTip = string.Empty; private readonly List displayedSubs = new List(); private Point prevResolution; + private float cardRefreshTimer; + private const float cardRefreshDelay = 3f; - partial void InitProjSpecific(XElement element) + private readonly HashSet cardsToDraw = new HashSet(); + + private List subEntities = new List(); + + private Texture2D? submarinePreview; + + private MiniMapMode currentMode; + private ImmutableArray modeSwitchButtons; + + private Point elementSize; + + private ImmutableDictionary hullStatusComponents; + private ImmutableDictionary electricalMapComponents; + private ImmutableDictionary electricalChildren; + private ImmutableDictionary doorChildren; + + private ImmutableHashSet? itemsFoundOnSub; + + private ImmutableHashSet? MiniMapBlips; + private float blipState; + private const float maxBlipState = 1f; + + private const float maxZoom = 10f, + minZoom = 0.5f, + defaultZoom = 1f; + + private float zoom = defaultZoom; + + private float Zoom { + get => zoom; + set => zoom = Math.Clamp(value, minZoom, maxZoom); + } + + private Vector2 mapOffset = Vector2.Zero; + private bool dragMap; + private Vector2? dragMapStart; + private const int dragTreshold = 8; + + private bool recalculate; + + public static readonly Color MiniMapBaseColor = new Color(15, 178, 107); + + private static readonly Color WetHullColor = new Color(11, 122, 205), + DoorIndicatorColor = GUI.Style.Green, + NoPowerDoorColor = DoorIndicatorColor * 0.1f, + DefaultNeutralColor = MiniMapBaseColor * 0.8f, + HoverColor = Color.White, + BlueprintBlue = new Color(23, 38, 33), + HullWaterColor = new Color(17, 173, 179) * 0.5f, + HullWaterLineColor = Color.LightBlue * 0.5f, + NoPowerColor = MiniMapBaseColor * 0.1f, + ElectricalBaseColor = GUI.Style.Orange, + NoPowerElectricalColor = ElectricalBaseColor * 0.1f; + + partial void InitProjSpecific() + { + SetDefaultMode(); + noPowerTip = TextManager.Get("SteeringNoPowerTip"); CreateGUI(); } + private void SetDefaultMode() + { + currentMode = true switch + { + true when EnableHullStatus => MiniMapMode.HullStatus, + true when EnableElectricalView => MiniMapMode.ElectricalView, + true when EnableItemFinder => MiniMapMode.ItemFinder, + _ => MiniMapMode.None + }; + } + protected override void CreateGUI() { + GuiFrame.ClearChildren(); + GuiFrame.RectTransform.RelativeOffset = new Vector2(0.05f, 0.0f); GuiFrame.CanBeFocused = true; - new GUICustomComponent(new RectTransform(GuiFrame.Rect.Size - GUIStyle.ItemFrameMargin, GuiFrame.RectTransform, Anchor.Center) { AbsoluteOffset = GUIStyle.ItemFrameOffset }, - DrawHUDBack, null); - submarineContainer = new GUIFrame(new RectTransform(new Vector2(0.95f, 0.9f), GuiFrame.RectTransform, Anchor.Center), style: null); - - new GUICustomComponent(new RectTransform(GuiFrame.Rect.Size - GUIStyle.ItemFrameMargin, GuiFrame.RectTransform, Anchor.Center) { AbsoluteOffset = GUIStyle.ItemFrameOffset }, - DrawHUDFront, null) + var submarineBack = new GUICustomComponent(new RectTransform(GuiFrame.Rect.Size - GUIStyle.ItemFrameMargin, GuiFrame.RectTransform, Anchor.Center) { AbsoluteOffset = GUIStyle.ItemFrameOffset }, DrawHUDBack, null); + GUIFrame paddedContainer = new GUIFrame(new RectTransform(GuiFrame.Rect.Size - GUIStyle.ItemFrameMargin, GuiFrame.RectTransform, Anchor.Center), style: null); + submarineContainer = new GUIFrame(new RectTransform(Vector2.One, paddedContainer.RectTransform, Anchor.Center), style: null); + var submarineFront = new GUICustomComponent(new RectTransform(GuiFrame.Rect.Size - GUIStyle.ItemFrameMargin, GuiFrame.RectTransform, Anchor.Center) { AbsoluteOffset = GUIStyle.ItemFrameOffset }, DrawHUDFront, null) { CanBeFocused = false }; - hullInfoFrame = new GUIFrame(new RectTransform(new Vector2(0.13f, 0.13f), GUI.Canvas, minSize: new Point(250, 150)), - style: "GUIToolTip") + GUILayoutGroup buttonLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 0.15f), paddedContainer.RectTransform) { MaxSize = new Point(int.MaxValue, GUI.IntScale(40)) }, isHorizontal: true) { CanBeFocused = true }; + + modeSwitchButtons = ImmutableArray.Create + ( + new GUIButton(new RectTransform(new Vector2(0.25f, 1.0f), buttonLayout.RectTransform), string.Empty, style: "StatusMonitorButton.HullStatus") { UserData = MiniMapMode.HullStatus, Enabled = EnableHullStatus, ToolTip = TextManager.Get("StatusMonitorButton.HullStatus.Tooltip") }, + new GUIButton(new RectTransform(new Vector2(0.25f, 1.0f), buttonLayout.RectTransform), string.Empty, style: "StatusMonitorButton.ElectricalView") { UserData = MiniMapMode.ElectricalView, Enabled = EnableElectricalView, ToolTip = TextManager.Get("StatusMonitorButton.ElectricalView.Tooltip") }, + new GUIButton(new RectTransform(new Vector2(0.25f, 1.0f), buttonLayout.RectTransform), string.Empty, style: "StatusMonitorButton.ItemFinder") { UserData = MiniMapMode.ItemFinder, Enabled = EnableItemFinder, ToolTip = TextManager.Get("StatusMonitorButton.ItemFinder.Tooltip") } + ); + + foreach (GUIButton button in modeSwitchButtons) + { + button.OnClicked = (btn, o) => + { + if (!(o is MiniMapMode m)) { return false; } + + currentMode = m; + Zoom = defaultZoom; + mapOffset = Vector2.Zero; + recalculate = true; + + foreach (GUIButton otherButton in modeSwitchButtons) + { + otherButton.Selected = false; + } + + btn.Selected = true; + return true; + }; + + if (button.UserData is MiniMapMode buttonMode) + { + button.Selected = currentMode == buttonMode; + } + } + + List reports = Order.PrefabList.FindAll(o => o.IsReport && o.SymbolSprite != null && !o.Hidden); + + GUIFrame bottomFrame = new GUIFrame(new RectTransform(new Vector2(0.5f, 0.15f), paddedContainer.RectTransform, Anchor.BottomCenter) { MaxSize = new Point(int.MaxValue, GUI.IntScale(40)) }, style: null) { CanBeFocused = false }; + + reportFrame = new GUILayoutGroup(new RectTransform(new Vector2(1), bottomFrame.RectTransform), isHorizontal: true) + { + Stretch = true, + AbsoluteSpacing = GUI.IntScale(5) + }; + + if (reports.Any()) + { + CrewManager.CreateReportButtons(GameMain.GameSession?.CrewManager, reportFrame, reports, true); + } + + searchBarFrame = new GUILayoutGroup(new RectTransform(new Vector2(1.5f, 1.0f), bottomFrame.RectTransform, Anchor.Center), isHorizontal: true, childAnchor: Anchor.Center) + { + Visible = false + }; + searchBar = new GUITextBox(new RectTransform(new Vector2(1), searchBarFrame.RectTransform), string.Empty, createClearButton: true, createPenIcon: true) + { + OnEnterPressed = (box, text) => + { + SearchItems(text); + return true; + } + }; + + searchAutoComplete = new GUIFrame(new RectTransform(Vector2.One, GUI.Canvas), style: "GUIToolTip") + { + Visible = false, + CanBeFocused = false + }; + + SetAutoCompletePosition(searchAutoComplete, searchBar); + + GUIListBox listBox = new GUIListBox(new RectTransform(Vector2.One, searchAutoComplete.RectTransform)) + { + OnSelected = (component, o) => + { + if (o is ItemPrefab prefab) + { + searchedPrefab = prefab; + searchBar.TextBlock.Text = prefab.Name; + searchBar.Deselect(); + SearchItems(searchBar.Text); + } + return true; + } + }; + + foreach (ItemPrefab prefab in ItemPrefab.Prefabs.OrderBy(prefab => prefab.Name)) + { + if (prefab.HideInMenus) { continue; } + CreateItemFrame(prefab, listBox.Content.RectTransform); + } + + searchBar.OnDeselected += (sender, key) => + { + searchAutoComplete.Visible = false; + }; + + searchBar.OnSelected += (sender, key) => + { + itemsFoundOnSub = Item.ItemList.Where(it => VisibleOnItemFinder(it)).Select(it => it.Prefab).ToImmutableHashSet(); + }; + + searchBar.OnKeyHit += ControlSearchTooltip; + searchBar.OnTextChanged += UpdateSearchTooltip; + + hullInfoFrame = new GUIFrame(new RectTransform(new Vector2(0.13f, 0.13f), GUI.Canvas, minSize: new Point(250, 150)), style: "GUIToolTip") + { + CanBeFocused = false + + }; + var hullInfoContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.9f), hullInfoFrame.RectTransform, Anchor.Center)) { Stretch = true, RelativeSpacing = 0.05f }; - hullNameText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.4f), hullInfoContainer.RectTransform), "") { Wrap = true }; - hullBreachText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.3f), hullInfoContainer.RectTransform), "") { Wrap = true }; - hullAirQualityText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.3f), hullInfoContainer.RectTransform), "") { Wrap = true }; - hullWaterText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.3f), hullInfoContainer.RectTransform), "") { Wrap = true }; + tooltipHeader = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.4f), hullInfoContainer.RectTransform), string.Empty) { Wrap = true }; + tooltipFirstLine = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.3f), hullInfoContainer.RectTransform), string.Empty) { Wrap = true }; + tooltipSecondLine = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.3f), hullInfoContainer.RectTransform), string.Empty) { Wrap = true }; + tooltipThirdLine = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.3f), hullInfoContainer.RectTransform), string.Empty) { Wrap = true }; hullInfoFrame.Children.ForEach(c => { c.CanBeFocused = false; c.Children.ForEach(c2 => c2.CanBeFocused = false); }); + + submarineBack.RectTransform.MaxSize = + submarineFront.RectTransform.MaxSize = + submarineContainer.RectTransform.MaxSize = + new Point(int.MaxValue, paddedContainer.Rect.Height - bottomFrame.Rect.Height - buttonLayout.Rect.Height); } - public override void AddToGUIUpdateList() + private bool VisibleOnItemFinder(Item it) { - base.AddToGUIUpdateList(); - hullInfoFrame.AddToGUIUpdateList(order: 1); + if (it.Submarine != item.Submarine) { return false; } + if (it.NonInteractable || it.HiddenInGame) { return false; } + if (it.GetComponent() == null) { return false; } + + var holdable = it.GetComponent(); + if (holdable != null && holdable.Attached) { return false; } + + var wire = it.GetComponent(); + if (wire != null && wire.Connections.Any(c => c != null)) { return false; } + + if (it.HasTag("traitormissionitem")) { return false; } + + return true; + } + + public override void AddToGUIUpdateList(int order = 0) + { + base.AddToGUIUpdateList(order); + hullInfoFrame.AddToGUIUpdateList(order: order + 1); + if (currentMode == MiniMapMode.ItemFinder && searchBar.Selected) + { + searchAutoComplete?.AddToGUIUpdateList(order: order + 1); + } } private void CreateHUD() { + subEntities.Clear(); prevResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight); - submarineContainer?.ClearChildren(); + submarineContainer.ClearChildren(); - if (item.Submarine == null) { return; } + if (item.Submarine is null) { return; } + + 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 }; + + ImmutableHashSet hullPointsOfInterest = Item.ItemList.Where(it => it.Submarine == item.Submarine && !it.HiddenInGame && !it.NonInteractable && it.Prefab.ShowInStatusMonitor && it.GetComponent() != null).ToImmutableHashSet(); + miniMapFrame = CreateMiniMap(item.Submarine, submarineContainer, MiniMapSettings.Default, hullPointsOfInterest, out hullStatusComponents); + + IEnumerable electrialPointsOfInterest = Item.ItemList.Where(it => it.Submarine == item.Submarine && !it.HiddenInGame && !it.NonInteractable && it.GetComponent() != null); + electricalFrame = CreateMiniMap(item.Submarine, miniMapContainer, new MiniMapSettings(createHullElements: false), electrialPointsOfInterest, out electricalMapComponents); + + Dictionary electricChildren = new Dictionary(); + + foreach (var (entity, component) in electricalMapComponents) + { + GUIComponent parent = component.RectComponent; + if (!(entity is Item it )) { continue; } + Sprite? sprite = it.Prefab.UpgradePreviewSprite; + if (sprite is null) { continue; } + + GUIImage child = new GUIImage(new RectTransform(Vector2.One, parent.RectTransform, Anchor.Center), sprite) + { + OutlineColor = ElectricalBaseColor, + Color = ElectricalBaseColor, + HoverCursor = CursorState.Hand, + SpriteEffects = item.Rotation > 90.0f && item.Rotation < 270.0f ? SpriteEffects.FlipVertically : SpriteEffects.None + }; + + electricChildren.Add(component, child); + } + + electricalChildren = electricChildren.ToImmutableDictionary(); + + Dictionary doorChilds = new Dictionary(); + + foreach (var (entity, component) in hullStatusComponents) + { + if (!hullPointsOfInterest.Contains(entity)) { continue; } + + const int minSize = 8; + const int borderMaxSize = 2; + + Point size = component.BorderComponent.Rect.Size; + + size.X = Math.Max(size.X, minSize); + size.Y = Math.Max(size.Y, minSize); + float width = Math.Min(borderMaxSize, Math.Min(size.X, size.Y) / 8f); + + GUIFrame frame = new GUIFrame(new RectTransform(size, component.RectComponent.RectTransform, anchor: Anchor.Center), style: "ScanLines", color: DoorIndicatorColor) + { + OutlineColor = GUI.Style.Green, + OutlineThickness = width + }; + doorChilds.Add(component, frame); + } + + doorChildren = doorChilds.ToImmutableDictionary(); + + Rectangle parentRect = miniMapFrame.Rect; - item.Submarine.CreateMiniMap(submarineContainer); displayedSubs.Clear(); displayedSubs.Add(item.Submarine); displayedSubs.AddRange(item.Submarine.DockedTo); + + subEntities = MapEntity.mapEntityList.Where(me => (item.Submarine is { } sub && sub.IsEntityFoundOnThisSub(me, includingConnectedSubs: true, allowDifferentType: false)) && !me.HiddenInGame).OrderByDescending(w => w.SpriteDepth).ToList(); + + BakeSubmarine(item.Submarine, parentRect); + elementSize = GuiFrame.Rect.Size; } public override void UpdateHUD(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 - !displayedSubs.Contains(item.Submarine) || //current sub not displayer - prevResolution.X != GameMain.GraphicsWidth || prevResolution.Y != GameMain.GraphicsHeight || //resolution changed - item.Submarine.DockedTo.Any(s => !displayedSubs.Contains(s)) || //some of the docked subs not diplayed - !submarineContainer.Children.Any() || // We lack a GUI - displayedSubs.Any(s => s != item.Submarine && !item.Submarine.DockedTo.Contains(s))) //displaying a sub that shouldn't be displayed + 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.Any(s => !displayedSubs.Contains(s)) || // some of the docked subs not displayed + displayedSubs.Any(s => s != itemSub && !itemSub.DockedTo.Contains(s)) // displaying a sub that shouldn't be displayed + ) || + prevResolution.X != GameMain.GraphicsWidth || prevResolution.Y != GameMain.GraphicsHeight || // resolution changed + !submarineContainer.Children.Any()) // We lack a GUI { CreateHUD(); } - + + if (scissorComponent != null) + { + if (PlayerInput.PrimaryMouseButtonDown() && currentMode != MiniMapMode.HullStatus) + { + if (GUI.MouseOn == scissorComponent || scissorComponent.IsParentOf(GUI.MouseOn)) + { + dragMapStart = PlayerInput.MousePosition; + } + } + + if (currentMode != MiniMapMode.HullStatus && Math.Abs(PlayerInput.ScrollWheelSpeed) > 0 && (GUI.MouseOn == scissorComponent || scissorComponent.IsParentOf(GUI.MouseOn))) + { + float newZoom = Math.Clamp(Zoom + PlayerInput.ScrollWheelSpeed / 1000.0f * Zoom, minZoom, maxZoom); + float distanceScale = newZoom / Zoom; + mapOffset *= distanceScale; + recalculate |= !MathUtils.NearlyEqual(Zoom, newZoom); + Zoom = newZoom; + } + } + + if (dragMapStart is { } dragStart) + { + if (dragMap || Vector2.DistanceSquared(dragStart, PlayerInput.MousePosition) > GUI.IntScale(dragTreshold * dragTreshold)) + { + mapOffset.X += PlayerInput.MouseSpeed.X; + mapOffset.Y += PlayerInput.MouseSpeed.Y; + + recalculate = true; + dragMap = true; + } + } + + if (!PlayerInput.PrimaryMouseButtonHeld()) + { + dragMapStart = null; + dragMap = false; + } + + if (recalculate) + { + if (miniMapContainer != null) + { + miniMapContainer.RectTransform.LocalScale = new Vector2(Zoom); + miniMapContainer.RectTransform.RecalculateChildren(true, true); + miniMapContainer.RectTransform.AbsoluteOffset = mapOffset.ToPoint(); + } + recalculate = false; + } + + // is there a better way to do this? + if (GuiFrame.Rect.Size != elementSize) + { + CreateGUI(); + elementSize = GuiFrame.Rect.Size; + } + float distort = 1.0f - item.Condition / item.MaxCondition; foreach (HullData hullData in hullDatas.Values) { @@ -107,12 +577,52 @@ namespace Barotrauma.Items.Components hullData.Distort = Rand.Range(0.0f, 1.0f) < distort * distort; if (hullData.Distort) { - hullData.Oxygen = Rand.Range(0.0f, 100.0f); - hullData.Water = Rand.Range(0.0f, 1.0f); + hullData.ReceivedOxygenAmount = Rand.Range(0.0f, 100.0f); + hullData.ReceivedWaterAmount = Rand.Range(0.0f, 1.0f); } hullData.DistortionTimer = Rand.Range(1.0f, 10.0f); } } + + UpdateHUDBack(); + + if (blipState > maxBlipState) + { + blipState = 0; + } + + blipState += deltaTime; + + if (currentMode == MiniMapMode.HullStatus && !EnableHullStatus || + currentMode == MiniMapMode.ElectricalView && !EnableElectricalView || + currentMode == MiniMapMode.ItemFinder && !EnableItemFinder) + { + SetDefaultMode(); + } + + modeSwitchButtons[0].Enabled = EnableHullStatus; + modeSwitchButtons[1].Enabled = EnableElectricalView; + modeSwitchButtons[2].Enabled = EnableItemFinder; + } + + private void UpdateIDCards(Submarine sub) + { + if (hullDatas is null) { return; } + + foreach (HullData data in hullDatas.Values) + { + data.Cards.Clear(); + } + + foreach (Item it in sub.GetItems(true)) + { + if (it is { CurrentHull: { } hull } && it.GetComponent() is { } idCard && idCard.TeamID == sub.TeamID) + { + if (!hullDatas.ContainsKey(hull)) { continue; } + + hullDatas[hull].Cards.Add(idCard); + } + } } private void DrawHUDFront(SpriteBatch spriteBatch, GUICustomComponent container) @@ -121,209 +631,728 @@ namespace Barotrauma.Items.Components { Vector2 textSize = GUI.Font.MeasureString(noPowerTip); Vector2 textPos = GuiFrame.Rect.Center.ToVector2(); + Color noPowerColor = GUI.Style.Orange * (float)Math.Abs(Math.Sin(Timing.TotalTime)); - GUI.DrawString(spriteBatch, textPos - textSize / 2, noPowerTip, - GUI.Style.Orange * (float)Math.Abs(Math.Sin(Timing.TotalTime)), Color.Black * 0.8f, font: GUI.SubHeadingFont); - return; - } - if (!submarineContainer.Children.Any()) { return; } - foreach (GUIComponent child in submarineContainer.Children.FirstOrDefault()?.Children) - { - if (child.UserData is Hull hull) - { - if (hull.Submarine == null || !hull.Submarine.Info.IsOutpost) { continue; } - string text = TextManager.GetWithVariable("MiniMapOutpostDockingInfo", "[outpost]", hull.Submarine.Info.Name); - Vector2 textSize = GUI.Font.MeasureString(text); - Vector2 textPos = child.Center; - if (textPos.X + textSize.X / 2 > submarineContainer.Rect.Right) - textPos.X -= ((textPos.X + textSize.X / 2) - submarineContainer.Rect.Right) + 10 * GUI.xScale; - if (textPos.X - textSize.X / 2 < submarineContainer.Rect.X) - textPos.X += (submarineContainer.Rect.X - (textPos.X - textSize.X / 2)) + 10 * GUI.xScale; - GUI.DrawString(spriteBatch, textPos - textSize / 2, text, - GUI.Style.Orange * (float)Math.Abs(Math.Sin(Timing.TotalTime)), Color.Black * 0.8f); - break; - } - } - } - - private void DrawHUDBack(SpriteBatch spriteBatch, GUICustomComponent container) - { - Hull mouseOnHull = null; - hullInfoFrame.Visible = false; - - foreach (Hull hull in Hull.hullList) - { - var hullFrame = submarineContainer.Children.FirstOrDefault()?.FindChild(hull); - if (hullFrame == null) { continue; } - - if (GUI.MouseOn == hullFrame || hullFrame.IsParentOf(GUI.MouseOn)) - { - mouseOnHull = hull; - } - if (item.Submarine == null || !hasPower) - { - hullFrame.Color = Color.DarkCyan * 0.3f; - hullFrame.Children.First().Color = Color.DarkCyan * 0.3f; - } - } - - if (Voltage < MinVoltage) - { + GUI.DrawString(spriteBatch, textPos - textSize / 2, noPowerTip, noPowerColor, Color.Black * 0.8f, font: GUI.SubHeadingFont); return; } - float scale = 1.0f; - HashSet subs = new HashSet(); - foreach (Hull hull in Hull.hullList) + if (currentMode == MiniMapMode.HullStatus) { - if (hull.Submarine == null) { continue; } - var hullFrame = submarineContainer.Children.FirstOrDefault()?.FindChild(hull); - if (hullFrame == null) { continue; } + Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle; + spriteBatch.End(); + spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable); + spriteBatch.GraphicsDevice.ScissorRectangle = submarineContainer.Rect; - hullFrame.Visible = true; - if (!submarineContainer.Rect.Contains(hullFrame.Rect)) + if (item.Submarine != null) { - if (hull.Submarine.Info.Type != SubmarineType.Player) + var sprite = GUI.Style.UIGlowSolidCircular?.Sprite; + float alpha = (MathF.Sin(blipState / maxBlipState * MathHelper.TwoPi) + 1.5f) * 0.5f; + if (sprite != null) { - hullFrame.Visible = false; - continue; + Vector2 spriteSize = sprite.size; + Rectangle worldBorders = item.Submarine.GetDockedBorders(); + worldBorders.Location += item.Submarine.WorldPosition.ToPoint(); + foreach (Gap gap in Gap.GapList) + { + if (gap.IsRoomToRoom || gap.Submarine != item.Submarine || gap.ConnectedDoor != null) { continue; } + RectangleF entityRect = ScaleRectToUI(gap, miniMapFrame.Rect, worldBorders); + + Vector2 scale = new Vector2(entityRect.Size.X / spriteSize.X, entityRect.Size.Y / spriteSize.Y) * 2.0f; + + Color color = ToolBox.GradientLerp(gap.Open, GUI.Style.HealthBarColorMedium, GUI.Style.HealthBarColorLow) * alpha; + sprite.Draw(spriteBatch, + miniMapFrame.Rect.Location.ToVector2() + entityRect.Center, + color, origin: sprite.Origin, rotate: 0.0f, scale: scale); + } } } - hullDatas.TryGetValue(hull, out HullData hullData); - if (hullData == null) + if (currentMode == MiniMapMode.HullStatus) + { + foreach (var (entity, component) in hullStatusComponents) + { + if (!(entity is Hull hull)) { continue; } + if (!hullDatas.TryGetValue(hull, out HullData? hullData) || hullData is null) { continue; } + DrawHullCards(spriteBatch, hull, hullData, component.RectComponent); + } + } + + spriteBatch.End(); + spriteBatch.GraphicsDevice.ScissorRectangle = prevScissorRect; + spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable); + } + } + + private void ControlSearchTooltip(GUITextBox sender, Keys key) + { + if (searchAutoComplete is null || !searchAutoComplete.Visible) { return; } + GUIListBox listBox = searchAutoComplete.GetChild(); + if (listBox is null) { return; } + + if (key == Keys.Down) + { + listBox.SelectNext(true, autoScroll: true); + } + else if (key == Keys.Up) + { + listBox.SelectPrevious(true, autoScroll: true); + } + else if (key == Keys.Enter) + { + listBox.OnSelected?.Invoke(listBox, listBox.SelectedData); + searchBar.Deselect(); + } + } + + private bool UpdateSearchTooltip(GUITextBox box, string? text) + { + if (text is null || itemsFoundOnSub is null || searchAutoComplete is null) { return false; } + + MiniMapBlips = null; + searchedPrefab = null; + searchAutoComplete.Visible = true; + SetAutoCompletePosition(searchAutoComplete, box); + + GUIListBox? listBox = searchAutoComplete.GetChild(); + if (listBox?.Content is null) { return false; } + + bool first = true; + + int i = 0; + + foreach (GUIComponent component in listBox.Content.Children) + { + component.Visible = false; + if (component.UserData is ItemPrefab { Name: { } prefabName} prefab && itemsFoundOnSub.Contains(prefab)) + { + component.Visible = prefabName.ToLower().Contains(text.ToLower()); + + if (component.Visible && first) + { + listBox.Select(i, force: true, autoScroll: false); + first = false; + } + } + + i++; + } + + listBox.BarScroll = 0f; + listBox.RecalculateChildren(); + + return true; + } + + private void SetAutoCompletePosition(GUIComponent tooltip, GUITextBox box) + { + int height = GuiFrame.Rect.Height / 2; + tooltip.RectTransform.NonScaledSize = new Point(box.Rect.Width, height); + tooltip.RectTransform.ScreenSpaceOffset = new Point(box.Rect.X, box.Rect.Y - height); + } + + private void CreateItemFrame(ItemPrefab prefab, RectTransform parent) + { + Sprite sprite = prefab.InventoryIcon ?? prefab.sprite; + if (sprite is null) { return; } + GUIFrame frame = new GUIFrame(new RectTransform(new Vector2(1f, 0.25f), parent), style: "ListBoxElement") + { + UserData = prefab + }; + + GUILayoutGroup layout = new GUILayoutGroup(new RectTransform(Vector2.One, frame.RectTransform), isHorizontal: true) + { + Stretch = true + }; + new GUIImage(new RectTransform(Vector2.One, layout.RectTransform, scaleBasis: ScaleBasis.BothHeight), sprite) + { + Color = prefab.InventoryIconColor, + UserData = prefab + }; + + var nameText = new GUITextBlock(new RectTransform(Vector2.One, layout.RectTransform), prefab.Name); + nameText.RectTransform.SizeChanged += () => + { + nameText.Text = ToolBox.LimitString(prefab.Name, nameText.Font, nameText.Rect.Width); + }; + } + + private void SearchItems(string text) + { + if (searchedPrefab is null) + { + ItemPrefab? first = ItemPrefab.Prefabs.FirstOrDefault(p => p.Name.ToLower().Equals(text.ToLower())); + + if (first is null) + { + searchBar.Flash(GUI.Style.Red); + return; + } + searchedPrefab = first; + } + + if (item.Submarine is null) { return; } + + HashSet foundItems = new HashSet(); + + foreach (Item it in Item.ItemList) + { + if (!VisibleOnItemFinder(it)) { continue; } + + if (it.Prefab == searchedPrefab) + { + // ignore items on players and hidden inventories + if (it.FindParentInventory(inv => inv is CharacterInventory || inv is ItemInventory { Owner: Item { HiddenInGame: true }}) is { }) { continue; } + + if (it.FindParentInventory(inventory => inventory is ItemInventory { Owner: Item { ParentInventory: null } }) is ItemInventory parent) + { + foundItems.Add((Item)parent.Owner); + } + else + { + foundItems.Add(it); + } + } + } + + + RectangleF dockedBorders = item.Submarine.GetDockedBorders(); + dockedBorders.Location += item.Submarine.WorldPosition; + RectangleF parentRect = miniMapFrame.Rect; + + HashSet positions = new HashSet(); + foreach (Item foundItem in foundItems) + { + RelativeEntityRect scaledRect = new RelativeEntityRect(dockedBorders, foundItem.WorldRect); + Vector2 pos = scaledRect.PositionRelativeTo(parentRect, skipOffset: true) + scaledRect.SizeRelativeTo(parentRect) / 2f; + positions.Add(pos); + } + + MiniMapBlips = positions.ToImmutableHashSet(); + + if (searchAutoComplete is null) { return; } + searchAutoComplete.Visible = false; + } + + private void UpdateHUDBack() + { + if (item.Submarine == null) { return; } + + hullInfoFrame.Visible = false; + reportFrame.Visible = false; + searchBarFrame.Visible = false; + electricalFrame.Visible = false; + miniMapFrame.Visible = false; + + switch (currentMode) + { + case MiniMapMode.HullStatus: + UpdateHullStatus(); + miniMapFrame.Visible = true; + reportFrame.Visible = true; + break; + case MiniMapMode.ElectricalView: + UpdateElectricalView(); + electricalFrame.Visible = true; + break; + case MiniMapMode.ItemFinder: + searchBarFrame.Visible = true; + break; + } + } + + private void UpdateHullStatus() + { + bool canHoverOverHull = true; + + foreach (var (entity, component) in hullStatusComponents) + { + // we are only interested in non-hull components + if (entity is Hull) { continue; } + + GUIComponent rectComponent = component.RectComponent; + + if (doorChildren.TryGetValue(component, out GUIComponent? child) && child != null) + { + if (item.Submarine == null || !hasPower) + { + child.Color = child.OutlineColor = NoPowerDoorColor; + } + + if (Voltage < MinVoltage) { continue; } + + child.Color = child.OutlineColor = DoorIndicatorColor; + if (GUI.MouseOn == child) + { + SetTooltip(rectComponent.Rect.Center, entity.Name, string.Empty, string.Empty, string.Empty); + canHoverOverHull = false; + child.Color = child.OutlineColor = HoverColor; + } + } + } + + foreach (var (entity, (component, borderComponent)) in hullStatusComponents) + { + if (item.Submarine == null || !hasPower) + { + component.Color = borderComponent.OutlineColor = NoPowerColor; + } + + if (Voltage < MinVoltage) { continue; } + + if (!component.Visible) { continue; } + if (!(entity is Hull hull)) { continue; } + + if (!submarineContainer.Rect.Contains(component.Rect)) + { + if (hull.Submarine.Info.Type != SubmarineType.Player) + { + component.Visible = borderComponent.Visible = false; + continue; + } + } + + hullDatas.TryGetValue(hull, out HullData? hullData); + if (hullData is null) { hullData = new HullData(); GetLinkedHulls(hull, hullData.LinkedHulls); hullDatas.Add(hull, hullData); } - - Color neutralColor = Color.DarkCyan; + + Color neutralColor = DefaultNeutralColor; + Color borderColor = neutralColor; + Color componentColor; + if (hull.IsWetRoom) { - neutralColor = new Color(9, 80, 159); + neutralColor = WetHullColor; } if (hullData.Distort) { - hullFrame.Children.First().Color = Color.Lerp(Color.Black, Color.DarkGray * 0.5f, Rand.Range(0.0f, 1.0f)); - hullFrame.Color = neutralColor * 0.5f; + borderComponent.OutlineColor = neutralColor * 0.5f; + component.Color = Color.Lerp(Color.Black, Color.DarkGray * 0.5f, Rand.Range(0.0f, 1.0f)); continue; } - - subs.Add(hull.Submarine); - scale = Math.Min( - hullFrame.Parent.Rect.Width / (float)hull.Submarine.Borders.Width, - hullFrame.Parent.Rect.Height / (float)hull.Submarine.Borders.Height); - - Color borderColor = neutralColor; - - float? gapOpenSum = 0.0f; + + hullData.HullOxygenAmount = RequireOxygenDetectors ? hullData.ReceivedOxygenAmount : hull.OxygenPercentage; + hullData.HullWaterAmount = RequireWaterDetectors ? hullData.ReceivedWaterAmount : Math.Min(hull.WaterVolume / hull.Volume, 1.0f); + + float gapOpenSum = 0.0f; + if (ShowHullIntegrity) { - gapOpenSum = hull.ConnectedGaps.Where(g => !g.IsRoomToRoom).Sum(g => g.Open); - borderColor = Color.Lerp(neutralColor, GUI.Style.Red, Math.Min((float)gapOpenSum, 1.0f)); + float amount = 1f + hullData.LinkedHulls.Count; + gapOpenSum = hull.ConnectedGaps.Concat(hullData.LinkedHulls.SelectMany(h => h.ConnectedGaps)).Where(g => !g.IsRoomToRoom).Sum(g => g.Open) / amount; + borderColor = Color.Lerp(neutralColor, GUI.Style.Red, Math.Min(gapOpenSum, 1.0f)); } - float? oxygenAmount = null; - if (!RequireOxygenDetectors || hullData?.Oxygen != null) + bool isHoveringOver = canHoverOverHull && GUI.MouseOn == component; + + // When drawing tooltip we are only interested in the component we are hovering over + if (isHoveringOver) { - oxygenAmount = RequireOxygenDetectors ? hullData.Oxygen : hull.OxygenPercentage; - GUI.DrawRectangle( - spriteBatch, hullFrame.Rect, - Color.Lerp(GUI.Style.Red * 0.5f, GUI.Style.Green * 0.3f, (float)oxygenAmount / 100.0f), - true); - } + string header = hull.DisplayName; - float? waterAmount = null; - if (!RequireWaterDetectors || hullData.Water != null) - { - waterAmount = RequireWaterDetectors ? hullData.Water : Math.Min(hull.WaterVolume / hull.Volume, 1.0f); - if (hullFrame.Rect.Height * waterAmount > 3.0f) - { - Rectangle waterRect = new Rectangle( - hullFrame.Rect.X, (int)(hullFrame.Rect.Y + hullFrame.Rect.Height * (1.0f - waterAmount)), - hullFrame.Rect.Width, (int)(hullFrame.Rect.Height * waterAmount)); - - waterRect.Inflate(-3, -3); - - GUI.DrawRectangle(spriteBatch, waterRect, new Color(85, 136, 147), true); - GUI.DrawLine(spriteBatch, new Vector2(waterRect.X, waterRect.Y), new Vector2(waterRect.Right, waterRect.Y), Color.LightBlue); - } - } - - if (mouseOnHull == hull || - hullData.LinkedHulls.Contains(mouseOnHull)) - { - borderColor = Color.Lerp(borderColor, Color.White, 0.5f); - hullFrame.Children.First().Color = Color.White; - hullFrame.Color = borderColor; - } - else - { - hullFrame.Children.First().Color = neutralColor * 0.8f; - } - - if (mouseOnHull == hull) - { - hullInfoFrame.RectTransform.ScreenSpaceOffset = hullFrame.Rect.Center; - if (hullInfoFrame.Rect.Right > GameMain.GraphicsWidth) { hullInfoFrame.RectTransform.ScreenSpaceOffset -= new Point(hullInfoFrame.Rect.Width, 0); } - if (hullInfoFrame.Rect.Bottom > GameMain.GraphicsHeight) { hullInfoFrame.RectTransform.ScreenSpaceOffset -= new Point(0, hullInfoFrame.Rect.Height); } - - hullInfoFrame.Visible = true; - hullNameText.Text = hull.DisplayName; + float? oxygenAmount = hullData.HullOxygenAmount, + waterAmount = hullData.HullWaterAmount; foreach (Hull linkedHull in hullData.LinkedHulls) { - gapOpenSum += linkedHull.ConnectedGaps.Where(g => !g.IsRoomToRoom).Sum(g => g.Open); oxygenAmount += linkedHull.OxygenPercentage; waterAmount += Math.Min(linkedHull.WaterVolume / linkedHull.Volume, 1.0f); } + oxygenAmount /= (hullData.LinkedHulls.Count + 1); waterAmount /= (hullData.LinkedHulls.Count + 1); - hullBreachText.Text = gapOpenSum > 0.1f ? TextManager.Get("MiniMapHullBreach") : ""; - hullBreachText.TextColor = GUI.Style.Red; + string line1 = gapOpenSum > 0.1f ? TextManager.Get("MiniMapHullBreach") : string.Empty; + Color line1Color = GUI.Style.Red; - hullAirQualityText.Text = oxygenAmount == null ? TextManager.Get("MiniMapAirQualityUnavailable") : - TextManager.AddPunctuation(':', TextManager.Get("MiniMapAirQuality"), + (int)oxygenAmount + " %"); - hullAirQualityText.TextColor = oxygenAmount == null ? GUI.Style.Red : Color.Lerp(GUI.Style.Red, Color.LightGreen, (float)oxygenAmount / 100.0f); + string line2 = oxygenAmount == null ? + TextManager.Get("MiniMapAirQualityUnavailable") : + TextManager.AddPunctuation(':', TextManager.Get("MiniMapAirQuality"), (int)Math.Round(oxygenAmount.Value) + "%"); + Color line2Color = oxygenAmount == null ? GUI.Style.Red : Color.Lerp(GUI.Style.Red, Color.LightGreen, (float)oxygenAmount / 100.0f); - hullWaterText.Text = waterAmount == null ? TextManager.Get("MiniMapWaterLevelUnavailable") : - TextManager.AddPunctuation(':', TextManager.Get("MiniMapWaterLevel"), (int)(waterAmount * 100.0f) + " %"); - hullWaterText.TextColor = waterAmount == null ? GUI.Style.Red : Color.Lerp(Color.LightGreen, GUI.Style.Red, (float)waterAmount); + string line3 = waterAmount == null ? + TextManager.Get("MiniMapWaterLevelUnavailable") : + TextManager.AddPunctuation(':', TextManager.Get("MiniMapWaterLevel"), (int)Math.Round(waterAmount.Value * 100.0f) + "%"); + Color line3Color = waterAmount == null ? GUI.Style.Red : Color.Lerp(Color.LightGreen, GUI.Style.Red, (float)waterAmount); + + SetTooltip(borderComponent.Rect.Center, header, line1, line2, line3, line1Color, line2Color, line3Color); } - - hullFrame.Color = borderColor; - } - - foreach (Submarine sub in subs) - { - if (sub.HullVertices == null || sub.Info.IsOutpost) { continue; } - - Rectangle worldBorders = sub.GetDockedBorders(); - worldBorders.Location += sub.WorldPosition.ToPoint(); - - scale = Math.Min( - submarineContainer.Rect.Width / (float)worldBorders.Width, - submarineContainer.Rect.Height / (float)worldBorders.Height) * 0.9f; - float displayScale = ConvertUnits.ToDisplayUnits(scale); - Vector2 offset = ConvertUnits.ToSimUnits(sub.WorldPosition - new Vector2(worldBorders.Center.X, worldBorders.Y - worldBorders.Height / 2)); - Vector2 center = container.Rect.Center.ToVector2(); - - for (int i = 0; i < sub.HullVertices.Count; i++) + bool draggingReport = GameMain.GameSession?.CrewManager?.DraggedOrder != null; + // When setting the colors we want to know the linked hulls too or else the linked hull will not realize its being hovered over and reset the border color + foreach (Hull linkedHull in hullData.LinkedHulls) { - Vector2 start = (sub.HullVertices[i] + offset) * displayScale; - start.Y = -start.Y; - Vector2 end = (sub.HullVertices[(i + 1) % sub.HullVertices.Count] + offset) * displayScale; - end.Y = -end.Y; - GUI.DrawLine(spriteBatch, center + start, center + end, Color.DarkCyan * Rand.Range(0.3f, 0.35f), width: (int)(10 * GUI.Scale)); + if (!hullStatusComponents.ContainsKey(linkedHull)) { continue; } + + isHoveringOver |= + canHoverOverHull && + (hullStatusComponents[linkedHull].RectComponent == GUI.MouseOn || (draggingReport && hullStatusComponents[linkedHull].RectComponent.MouseRect.Contains(PlayerInput.MousePosition))); + if (isHoveringOver) { break; } } + + if (isHoveringOver || (draggingReport && component.MouseRect.Contains(PlayerInput.MousePosition))) + { + borderColor = Color.Lerp(borderColor, Color.White, 0.5f); + componentColor = HoverColor; + } + else + { + componentColor = neutralColor * 0.8f; + } + + borderComponent.OutlineColor = borderColor; + component.Color = componentColor; } } - private void GetLinkedHulls(Hull hull, List linkedHulls) + private void UpdateElectricalView() + { + foreach (var (entity, miniMapGuiComponent) in electricalMapComponents) + { + if (!(entity is Item it)) { continue; } + if (!electricalChildren.TryGetValue(miniMapGuiComponent, out GUIComponent component)) { continue; } + + if (item.Submarine == null || !hasPower) + { + component.Color = component.OutlineColor = NoPowerElectricalColor; + } + + if (Voltage < MinVoltage || !miniMapGuiComponent.RectComponent.Visible) { continue; } + + int durability = (int)(it.Condition / (it.MaxCondition / it.MaxRepairConditionMultiplier) * 100f); + Color color = ToolBox.GradientLerp(durability / 100f, GUI.Style.Red, GUI.Style.Orange, GUI.Style.Green, GUI.Style.Green); + + if (GUI.MouseOn == component) + { + string line1 = string.Empty; + string line2 = string.Empty; + + if (it.GetComponent() is { } battery) + { + int batteryCapacity = (int)(battery.Charge / battery.Capacity * 100f); + line2 = TextManager.GetWithVariable("statusmonitor.battery.tooltip", "[amount]", batteryCapacity.ToString()); + } + else if (it.GetComponent() is { } powerTransfer) + { + int current = (int) -powerTransfer.CurrPowerConsumption, + load = (int) powerTransfer.PowerLoad; + + line1 = TextManager.GetWithVariable("statusmonitor.junctioncurrent.tooltip", "[amount]", current.ToString()); + line2 = TextManager.GetWithVariable("statusmonitor.junctionload.tooltip", "[amount]", load.ToString()); + } + + string line3 = TextManager.GetWithVariable("statusmonitor.durability.tooltip", "[amount]", durability.ToString()); + SetTooltip(component.Rect.Center, it.Prefab.Name, line1, line2, line3, line3Color: color); + color = HoverColor; + } + + component.Color = component.OutlineColor = color; + } + } + + private void DrawHUDBack(SpriteBatch spriteBatch, GUICustomComponent container) + { + if (item.Submarine != null) + { + DrawSubmarine(spriteBatch); + } + + if (Voltage < MinVoltage) { return; } + Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle; + spriteBatch.End(); + spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable); + spriteBatch.GraphicsDevice.ScissorRectangle = submarineContainer.Rect; + + if (currentMode == MiniMapMode.ItemFinder) + { + if (MiniMapBlips != null) + { + foreach (Vector2 blip in MiniMapBlips) + { + Vector2 parentSize = miniMapFrame.Rect.Size.ToVector2(); + Sprite pingCircle = GUI.Style.PingCircle.Sprite; + Vector2 targetSize = new Vector2(parentSize.X / 4f); + Vector2 spriteScale = targetSize / pingCircle.size; + float scale = Math.Min(blipState, maxBlipState / 2f); + float alpha = 1.0f - Math.Clamp((blipState - maxBlipState * 0.25f) * 2f, 0f, 1f); + pingCircle.Draw(spriteBatch, electricalFrame.Rect.Location.ToVector2() + blip * Zoom, GUI.Style.Red * alpha, pingCircle.Origin, 0f, spriteScale * scale, SpriteEffects.None); + } + } + } + else + { + bool hullsVisible = currentMode == MiniMapMode.HullStatus; + + foreach (var (entity, component) in hullStatusComponents) + { + if (!(entity is Hull hull)) { continue; } + if (!hullDatas.TryGetValue(hull, out HullData? hullData) || hullData is null) { continue; } + + if (hullData.Distort) { continue; } + + GUIComponent hullFrame = component.RectComponent; + + if (hullsVisible && hullData.HullWaterAmount is { } waterAmount) + { + if (hullFrame.Rect.Height * waterAmount > 3.0f) + { + RectangleF waterRect = new RectangleF(hullFrame.Rect.X, hullFrame.Rect.Y + hullFrame.Rect.Height * (1.0f - waterAmount), hullFrame.Rect.Width, hullFrame.Rect.Height * waterAmount); + + const float width = 1f; + + GUI.DrawFilledRectangle(spriteBatch, waterRect, HullWaterColor); + + if (!MathUtils.NearlyEqual(waterAmount, 1.0f)) + { + Vector2 offset = new Vector2(0, width); + GUI.DrawLine(spriteBatch, waterRect.Location + offset, new Vector2(waterRect.Right, waterRect.Y) + offset, HullWaterLineColor, width: width); + } + } + } + + if (hullsVisible && hullData.HullOxygenAmount is { } oxygenAmount) + { + GUI.DrawRectangle(spriteBatch, hullFrame.Rect, Color.Lerp(GUI.Style.Red * 0.5f, GUI.Style.Green * 0.3f, oxygenAmount / 100.0f), true); + } + } + } + + spriteBatch.End(); + spriteBatch.GraphicsDevice.ScissorRectangle = prevScissorRect; + spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable); + } + + private void SetTooltip(Point pos, string header, string line1, string line2, string line3, Color? line1Color = null, Color? line2Color = null, Color? line3Color = null) + { + hullInfoFrame.RectTransform.ScreenSpaceOffset = pos; + + if (hullInfoFrame.Rect.Left > submarineContainer.Rect.Right) { hullInfoFrame.RectTransform.ScreenSpaceOffset = new Point(submarineContainer.Rect.Right, hullInfoFrame.RectTransform.ScreenSpaceOffset.Y); } + if (hullInfoFrame.Rect.Top > submarineContainer.Rect.Bottom) { hullInfoFrame.RectTransform.ScreenSpaceOffset = new Point(hullInfoFrame.RectTransform.ScreenSpaceOffset.X, submarineContainer.Rect.Bottom); } + + if (hullInfoFrame.Rect.Right > GameMain.GraphicsWidth) { hullInfoFrame.RectTransform.ScreenSpaceOffset -= new Point(hullInfoFrame.Rect.Width, 0); } + if (hullInfoFrame.Rect.Bottom > GameMain.GraphicsHeight) { hullInfoFrame.RectTransform.ScreenSpaceOffset -= new Point(0, hullInfoFrame.Rect.Height); } + + hullInfoFrame.Visible = true; + tooltipHeader.Text = header; + + tooltipFirstLine.Text = line1; + tooltipFirstLine.TextColor = line1Color ?? GUI.Style.TextColor; + + tooltipSecondLine.Text = line2; + tooltipSecondLine.TextColor = line2Color ?? GUI.Style.TextColor; + + tooltipThirdLine.Text = line3; + tooltipThirdLine.TextColor = line3Color ?? GUI.Style.TextColor; + } + + private void BakeSubmarine(Submarine sub, Rectangle container) + { + submarinePreview?.Dispose(); + Rectangle parentRect = new Rectangle(container.X, container.Y, container.Width, container.Height); + const int inflate = 128; + parentRect.Inflate(inflate, inflate); + RenderTarget2D rt = new RenderTarget2D(GameMain.Instance.GraphicsDevice, parentRect.Width, parentRect.Height, false, SurfaceFormat.Color, DepthFormat.None); + + using SpriteBatch spriteBatch = new SpriteBatch(GameMain.Instance.GraphicsDevice); + GameMain.Instance.GraphicsDevice.SetRenderTarget(rt); + GameMain.Instance.GraphicsDevice.Clear(Color.Transparent); + spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable); + Rectangle worldBorders = sub.GetDockedBorders(); + worldBorders.Location += sub.WorldPosition.ToPoint(); + + parentRect.Inflate(-inflate, -inflate); + + foreach (MapEntity entity in subEntities) + { + if (entity is Structure wall) + { + if (wall.IsPlatform) { continue; } + DrawStructure(spriteBatch, wall, parentRect, worldBorders, inflate); + } + + if (entity is Item it) + { + if (it.GetComponent() != null || it.ParentInventory != null) { continue; } + DrawItem(spriteBatch, it, parentRect, worldBorders, inflate); + } + } + + spriteBatch.End(); + GameMain.Instance.GraphicsDevice.SetRenderTarget(null); + submarinePreview = rt; + } + + private void DrawSubmarine(SpriteBatch spriteBatch) + { + Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle; + spriteBatch.End(); + if (submarinePreview is { } texture && miniMapContainer is { } mapContainer) + { + spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, blendState: BlendState.NonPremultiplied, effect: GameMain.GameScreen.BlueprintEffect, rasterizerState: GameMain.ScissorTestEnable); + spriteBatch.GraphicsDevice.ScissorRectangle = submarineContainer.Rect; + + GameMain.GameScreen.BlueprintEffect.Parameters["width"].SetValue((float)texture.Width); + GameMain.GameScreen.BlueprintEffect.Parameters["height"].SetValue((float)texture.Height); + + Color blueprintBlue = BlueprintBlue * currentMode switch { MiniMapMode.HullStatus => 0.1f, MiniMapMode.ElectricalView => 0.1f, _ => 0.5f }; + + Vector2 origin = new Vector2(texture.Width / 2f, texture.Height / 2f); + float scale = currentMode == MiniMapMode.HullStatus ? 1.0f : Zoom; + spriteBatch.Draw(texture, mapContainer.Center, null, blueprintBlue, 0f, origin, scale, SpriteEffects.None, 0f); + + spriteBatch.End(); + } + spriteBatch.GraphicsDevice.ScissorRectangle = prevScissorRect; + spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable); + } + + private static void DrawItem(ISpriteBatch spriteBatch, Item item, Rectangle parent, Rectangle border, int inflate) + { + Sprite sprite = item.Sprite; + if (sprite is null) { return; } + + RectangleF entityRect = ScaleRectToUI(item, parent, border); + + Vector2 spriteScale = new Vector2(entityRect.Size.X / sprite.size.X, entityRect.Size.Y / sprite.size.Y); + Vector2 origin = new Vector2(sprite.Origin.X * spriteScale.X, sprite.Origin.Y * spriteScale.Y); + + if (item.GetComponent() is { } turret) + { + Vector2 drawPos = turret.GetDrawPos(); + drawPos.Y = -drawPos.Y; + if (turret.BarrelSprite is { } barrelSprite) + { + DrawAdditionalSprite(drawPos, barrelSprite, turret.Rotation + MathHelper.PiOver2); + } + } + + Vector2 pos = entityRect.Location + origin; + pos.X += inflate; + pos.Y += inflate; + + sprite.Draw(spriteBatch, pos, item.SpriteColor, sprite.Origin, MathHelper.ToRadians(item.Rotation), spriteScale, item.SpriteEffects); + + void DrawAdditionalSprite(Vector2 basePos, Sprite addSprite, float rotation) + { + RectangleF addRect = ScaleRectToUI(new RectangleF(basePos, addSprite.size * item.Scale), parent, border); + Vector2 addScale = new Vector2(addRect.Size.X / addSprite.size.X, addRect.Size.Y / addSprite.size.Y); + addSprite.Draw(spriteBatch, new Vector2(addRect.Location.X + inflate, addRect.Location.Y + inflate), item.SpriteColor, addSprite.Origin, rotation, addScale, item.SpriteEffects); + } + } + + private static void DrawStructure(ISpriteBatch spriteBatch, Structure structure, Rectangle parent, Rectangle border, int inflate) + { + Sprite sprite = structure.Sprite; + if (sprite is null) { return; } + + RectangleF entityRect = ScaleRectToUI(structure, parent, border); + Vector2 spriteScale = new Vector2(entityRect.Size.X / sprite.size.X, entityRect.Size.Y / sprite.size.Y); + sprite.Draw(spriteBatch, new Vector2(entityRect.Location.X + inflate, entityRect.Location.Y + inflate), structure.SpriteColor, Vector2.Zero, 0f, spriteScale, structure.SpriteEffects); + } + + private static RectangleF ScaleRectToUI(MapEntity entity, RectangleF parentRect, RectangleF worldBorders) + { + return ScaleRectToUI(entity.WorldRect, parentRect, worldBorders); + } + + private static RectangleF ScaleRectToUI(RectangleF rect, RectangleF parentRect, RectangleF worldBorders) + { + RelativeEntityRect relativeRect = new RelativeEntityRect(worldBorders, rect); + return relativeRect.RectangleRelativeTo(parentRect, skipOffset: true); + } + + private void DrawHullCards(SpriteBatch spriteBatch, Hull hull, HullData data, GUIComponent frame) + { + cardsToDraw.Clear(); + + if (GameMain.GameSession?.CrewManager is { ActiveOrders: { } orders }) + { + foreach (var pair in orders) + { + Order order = pair.First; + if (order is { SymbolSprite: { }, TargetEntity: Hull _ } && order.TargetEntity == hull) + { + cardsToDraw.Add(new MiniMapSprite(order)); + } + } + } + + foreach (IdCard card in data.Cards) + { + if (card.GetJob() is { Icon: { }} job) + { + cardsToDraw.Add(new MiniMapSprite(job)); + } + } + + if (!cardsToDraw.Any()) { return; } + + var (centerX, centerY) = frame.Center; + + const float padding = 8f; + float totalWidth = 0f; + + float parentWidth = submarineContainer.Rect.Width / 24f; + + int i = 0; + foreach (MiniMapSprite info in cardsToDraw) + { + float spriteSize = info.Sprite.size.X * (parentWidth / info.Sprite.size.X) + padding; + if (totalWidth + spriteSize > frame.Rect.Width) { break; } + + totalWidth += spriteSize; + i++; + } + + if (i > 0) { totalWidth -= padding; } + + float adjustedCenterX = centerX - totalWidth / 2f; + + float offset = 0; + int amount = 0; + + foreach (MiniMapSprite info in cardsToDraw) + { + Sprite sprite = info.Sprite; + float scale = parentWidth / sprite.size.X; + float spriteSize = sprite.size.X * scale; + float posX = adjustedCenterX + offset; + + if (posX + spriteSize > frame.Rect.X + frame.Rect.Width && amount > 0) + { + int amountLeft = cardsToDraw.Count - amount; + if (amountLeft > 0) + { + string text = $"+{amountLeft}"; // TODO localization + var (sizeX, sizeY) = GUI.SubHeadingFont.MeasureString(text); // TODO expensive, move to a global variable + float maxWidth = Math.Max(sizeX, sizeY); + Vector2 drawPos = new Vector2(frame.Rect.Right - sizeX, frame.Rect.Y - sizeY / 2f); + + UISprite icon = GUI.Style.IconOverflowIndicator; + + const int iconPadding = 4; + icon.Draw(spriteBatch, new Rectangle((int) drawPos.X - iconPadding, (int) drawPos.Y - iconPadding, (int) maxWidth + iconPadding * 2, (int) maxWidth + iconPadding * 2), Color.White, SpriteEffects.None); + + GUI.DrawString(spriteBatch, drawPos, text, GUI.Style.TextColor, font: GUI.SubHeadingFont); + } + break; + } + + float halfSize = spriteSize / 2f; + if (i > 0) { offset += halfSize; } + Vector2 pos = new Vector2(adjustedCenterX + offset, centerY); + sprite.Draw(spriteBatch, pos, info.Color * 0.8f, scale: scale, origin: sprite.size / 2f); + offset += halfSize + padding; + amount++; + } + } + + public static void GetLinkedHulls(Hull hull, List linkedHulls) { foreach (var linkedEntity in hull.linkedTo) { @@ -335,5 +1364,273 @@ namespace Barotrauma.Items.Components } } } + + public static GUIFrame CreateMiniMap(Submarine sub, GUIComponent parent, MiniMapSettings settings) + { + return CreateMiniMap(sub, parent, settings, null, out _); + } + + public static GUIFrame CreateMiniMap(Submarine sub, GUIComponent parent, MiniMapSettings settings, IEnumerable? pointsOfInterest, out ImmutableDictionary elements) + { + if (settings.Equals(default(MiniMapSettings))) + { + throw new ArgumentException($"Provided {nameof(MiniMapSettings)} is not valid, did you mean {nameof(MiniMapSettings)}.{nameof(MiniMapSettings.Default)}?", nameof(settings)); + } + + Dictionary pointsOfInterestCollection = new Dictionary(); + + RectangleF worldBorders = sub.GetDockedBorders(); + worldBorders.Location += sub.WorldPosition; + + // create a container that has the same "aspect ratio" as the sub + float aspectRatio = worldBorders.Width / worldBorders.Height; + float parentAspectRatio = parent.Rect.Width / (float)parent.Rect.Height; + + const float elementPadding = 0.9f; + + Vector2 containerScale = parentAspectRatio > aspectRatio ? new Vector2(aspectRatio / parentAspectRatio, 1.0f) : new Vector2(1.0f, parentAspectRatio / aspectRatio); + + GUIFrame hullContainer = new GUIFrame(new RectTransform(containerScale * elementPadding, parent.RectTransform, Anchor.Center), style: null); + + ImmutableHashSet connectedSubs = sub.GetConnectedSubs().ToImmutableHashSet(); + ImmutableArray hullList = ImmutableArray.Empty; + ImmutableDictionary> combinedHulls = ImmutableDictionary>.Empty; + + if (settings.CreateHullElements) + { + hullList = Hull.hullList.Where(IsPartofSub).ToImmutableArray(); + combinedHulls = CombinedHulls(hullList); + } + + // Make components for non-linked hulls + foreach (Hull hull in hullList.Where(IsStandaloneHull)) + { + RelativeEntityRect relativeRect = new RelativeEntityRect(worldBorders, hull.WorldRect); + + GUIFrame hullFrame = new GUIFrame(new RectTransform(relativeRect.RelativeSize, hullContainer.RectTransform) { RelativeOffset = relativeRect.RelativePosition }, style: "ScanLines", color: settings.ElementColor) + { + OutlineColor = settings.ElementColor, + OutlineThickness = 2, + UserData = hull + }; + + pointsOfInterestCollection.Add(hull, new MiniMapGUIComponent(hullFrame)); + } + + // Make components for linked hulls + foreach (var (mainHull, linkedHulls) in combinedHulls) + { + MiniMapHullData data = ConstructHullPolygon(mainHull, linkedHulls, hullContainer, worldBorders); + + RelativeEntityRect relativeRect = new RelativeEntityRect(worldBorders, data.Bounds); + + float highestY = 0f, + highestX = 0f; + + foreach (var (r, _) in data.RectDatas) + { + float y = r.Y - -r.Height, + x = r.X; + + if (y > highestY) { highestY = y; } + if (x > highestX) { highestX = x; } + } + + Dictionary hullsAndFrames = new Dictionary(); + + foreach (var (snappredRect, hull) in data.RectDatas) + { + RectangleF rect = snappredRect; + rect.Height = -rect.Height; + rect.Y -= rect.Height; + + var (parentW, parentH) = hullContainer.Rect.Size.ToVector2(); + Vector2 size = new Vector2(rect.Width / parentW, rect.Height / parentH); + Vector2 pos = new Vector2(rect.X / parentW, rect.Y / parentH); + + GUIFrame hullFrame = new GUIFrame(new RectTransform(size, hullContainer.RectTransform) { RelativeOffset = pos }, style: "ScanLinesSeamless", color: settings.ElementColor) + { + UserData = hull, + UVOffset = new Vector2(highestX - rect.X, highestY - rect.Y) + }; + + hullsAndFrames.Add(hull, hullFrame); + } + + /* + * This exists because the rectangle of GUIComponents still uses Rectangle instead of RectangleF + * and because of rounding sometimes it creates 1px gaps between which looks nasty so we snap + * the rectangles together if they are 2 pixels apart or less. + */ + foreach (var (hull1, frame1) in hullsAndFrames) + { + Rectangle rect1 = frame1.Rect; + foreach (var (hull2, frame2) in hullsAndFrames) + { + if (hull2 == hull1) { continue; } + + Rectangle rect2 = frame2.Rect; + Point size = frame1.RectTransform.NonScaledSize; + + const int treshold = 2; + + int diffY = rect2.Top - rect1.Bottom; + int diffX = rect2.Left - rect1.Right; + + if (diffY <= treshold && diffY > 0) + { + size.Y += diffY; + } + + if (diffX <= treshold && diffX > 0) + { + size.X += diffX; + } + + frame1.RectTransform.NonScaledSize = size; + } + } + + GUICustomComponent linkedHullFrame = new GUICustomComponent(new RectTransform(relativeRect.RelativeSize, hullContainer.RectTransform) { RelativeOffset = relativeRect.RelativePosition }, (spriteBatch, component) => + { + foreach (List list in data.Polygon) + { + spriteBatch.DrawPolygonInner(hullContainer.Rect.Location.ToVector2(), list, component.OutlineColor, 2f); + } + }, (deltaTime, component) => + { + if (component.Parent.Rect.Size != data.ParentSize) + { + data = ConstructHullPolygon(mainHull, linkedHulls, hullContainer, worldBorders); + } + }) + { + UserData = hullsAndFrames.Values.ToHashSet(), + OutlineColor = settings.ElementColor, + CanBeFocused = false + }; + + foreach (var (hull, component) in hullsAndFrames) + { + pointsOfInterestCollection.Add(hull, new MiniMapGUIComponent(component, linkedHullFrame)); + } + } + + if (pointsOfInterest != null) + { + foreach (MapEntity entity in pointsOfInterest) + { + RelativeEntityRect relativeRect = new RelativeEntityRect(worldBorders, entity.WorldRect); + + GUIFrame poiComponent = new GUIFrame(new RectTransform(relativeRect.RelativeSize, hullContainer.RectTransform) { RelativeOffset = relativeRect.RelativePosition }, style: null) + { + CanBeFocused = false, + UserData = entity + }; + + pointsOfInterestCollection.Add(entity, new MiniMapGUIComponent(poiComponent)); + } + } + + elements = pointsOfInterestCollection.ToImmutableDictionary(); + + return hullContainer; + + bool IsPartofSub(MapEntity entity) + { + if (entity.Submarine != sub && !connectedSubs.Contains(entity.Submarine)) { return false; } + return !settings.IgnoreOutposts || sub.IsEntityFoundOnThisSub(entity, true); + } + + bool IsStandaloneHull(Hull hull) + { + return !combinedHulls.ContainsKey(hull) && !combinedHulls.Values.Any(hh => hh.Contains(hull)); + } + } + + private static ImmutableDictionary> CombinedHulls(ImmutableArray hulls) + { + Dictionary> combinedHulls = new Dictionary>(); + + foreach (Hull hull in hulls) + { + if (combinedHulls.ContainsKey(hull) || combinedHulls.Values.Any(hh => hh.Contains(hull))) { continue; } + + List linkedHulls = new List(); + GetLinkedHulls(hull, linkedHulls); + + linkedHulls.Remove(hull); + + foreach (Hull linkedHull in linkedHulls) + { + if (!combinedHulls.ContainsKey(hull)) + { + combinedHulls.Add(hull, new HashSet()); + } + + combinedHulls[hull].Add(linkedHull); + } + } + + return combinedHulls.ToImmutableDictionary(pair => pair.Key, pair => pair.Value.ToImmutableArray()); + } + + private static MiniMapHullData ConstructHullPolygon(Hull mainHull, ImmutableArray linkedHulls, GUIComponent parent, RectangleF worldBorders) + { + Rectangle parentRect = parent.Rect; + + Dictionary rects = new Dictionary(); + Rectangle worldRect = mainHull.WorldRect; + worldRect.Y = -worldRect.Y; + + rects.Add(mainHull, worldRect); + + foreach (Hull hull in linkedHulls) + { + Rectangle rect = hull.WorldRect; + rect.Y = -rect.Y; + + worldRect = Rectangle.Union(worldRect, rect); + rects.Add(hull, rect); + } + + worldRect.Y = -worldRect.Y; + + List normalizedRects = new List(); + List hullRefs = new List(); + + foreach (var (hull, rect) in rects) + { + Rectangle wRect = rect; + wRect.Y = -wRect.Y; + + var (posX, posY, sizeX, sizeY) = new RelativeEntityRect(worldBorders, wRect); + + RectangleF newRect = new RectangleF(posX * parentRect.Width, posY * parentRect.Height, sizeX * parentRect.Width, sizeY * parentRect.Height); + + normalizedRects.Add(newRect); + hullRefs.Add(hull); + } + + hullRefs.Reverse(); // I have no idea why this is required + + ImmutableArray snappedRectangles = ToolBox.SnapRectangles(normalizedRects, treshold: 1); + + List> polygon = ToolBox.CombineRectanglesIntoShape(snappedRectangles); + + List> scaledPolygon = new List>(); + + foreach (List list in polygon) + { + // scale down the polygon just a tiny bit + var (polySizeX, polySizeY) = ToolBox.GetPolygonBoundingBoxSize(list); + float sizeX = polySizeX - 1f, + sizeY = polySizeY - 1f; + + scaledPolygon.Add(ToolBox.ScalePolygon(list, new Vector2(sizeX / polySizeX, sizeY / polySizeY))); + } + + return new MiniMapHullData(scaledPolygon, worldRect, parentRect.Size, snappedRectangles, hullRefs.ToImmutableArray()); + } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Pump.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Pump.cs index 3877a2ccf..a6b0b74b8 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Pump.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Pump.cs @@ -142,6 +142,7 @@ namespace Barotrauma.Items.Components partial void UpdateProjSpecific(float deltaTime) { + float rotationRad = MathHelper.ToRadians(item.Rotation); if (FlowPercentage < 0.0f) { foreach (var (position, emitter) in pumpOutEmitters) @@ -149,12 +150,13 @@ namespace Barotrauma.Items.Components if (item.CurrentHull != null && item.CurrentHull.Surface < item.Rect.Location.Y + position.Y) { continue; } //only emit "pump out" particles when underwater - Vector2 relativeParticlePos = (item.WorldRect.Location.ToVector2() + position * item.Scale) - item.WorldPosition; - float angle = 0.0f; + Vector2 relativeParticlePos = (item.WorldRect.Location.ToVector2() + position * item.Scale) - item.WorldPosition; + relativeParticlePos = MathUtils.RotatePoint(relativeParticlePos, item.FlippedX ? rotationRad : -rotationRad); + float angle = -rotationRad; if (item.FlippedX) { relativeParticlePos.X = -relativeParticlePos.X; - angle = MathHelper.Pi; + angle += MathHelper.Pi; } if (item.FlippedY) { @@ -170,11 +172,12 @@ namespace Barotrauma.Items.Components foreach (var (position, emitter) in pumpInEmitters) { Vector2 relativeParticlePos = (item.WorldRect.Location.ToVector2() + position * item.Scale) - item.WorldPosition; - float angle = 0.0f; + relativeParticlePos = MathUtils.RotatePoint(relativeParticlePos, item.FlippedX ? rotationRad : -rotationRad); + float angle = -rotationRad; if (item.FlippedX) { relativeParticlePos.X = -relativeParticlePos.X; - angle = MathHelper.Pi; + angle += MathHelper.Pi; } if (item.FlippedY) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs index dbf89512b..44d0ad13c 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs @@ -134,6 +134,14 @@ namespace Barotrauma.Items.Components private static string caveLabel; + + [Serialize(false, false)] + public bool RightLayout + { + get; + set; + } + private bool AllowUsingMineralScanner => HasMineralScanner && !isConnectedToSteering; @@ -316,7 +324,7 @@ namespace Barotrauma.Items.Components "", warningColor, GUI.LargeFont, Alignment.Center); // Setup layout for nav terminal - if (isConnectedToSteering) + if (isConnectedToSteering || RightLayout) { controlContainer.RectTransform.RelativeOffset = controlBoxOffset; controlContainer.RectTransform.SetPosition(Anchor.TopRight); @@ -446,13 +454,8 @@ namespace Barotrauma.Items.Components zoomSlider.BarScroll += PlayerInput.ScrollWheelSpeed / 1000.0f; zoomSlider.OnMoved(zoomSlider, zoomSlider.BarScroll); } - - if (PlayerInput.KeyHit(InputType.Run)) - { - SonarModeSwitch.OnClicked(SonarModeSwitch, null); - } } - + float distort = 1.0f - item.Condition / item.MaxCondition; for (int i = sonarBlips.Count - 1; i >= 0; i--) { @@ -885,7 +888,7 @@ namespace Barotrauma.Items.Components foreach (AITarget aiTarget in AITarget.List) { - if (!aiTarget.Enabled) { continue; } + if (aiTarget.InDetectable) { continue; } if (string.IsNullOrEmpty(aiTarget.SonarLabel) || aiTarget.SoundRange <= 0.0f) { continue; } if (Vector2.DistanceSquared(aiTarget.WorldPosition, transducerCenter) < aiTarget.SoundRange * aiTarget.SoundRange) @@ -1239,7 +1242,7 @@ namespace Barotrauma.Items.Components foreach (AITarget aiTarget in AITarget.List) { float disruption = aiTarget.Entity is Character c ? c.Params.SonarDisruption : aiTarget.SonarDisruption; - if (disruption <= 0.0f || !aiTarget.Enabled) { continue; } + if (disruption <= 0.0f || aiTarget.InDetectable) { continue; } float distSqr = Vector2.DistanceSquared(aiTarget.WorldPosition, pingSource); if (distSqr > worldPingRadiusSqr) { continue; } float disruptionDist = (float)Math.Sqrt(distSqr); @@ -1364,28 +1367,6 @@ namespace Barotrauma.Items.Components blipType : cell.IsDestructible ? BlipType.Destructible : BlipType.Default); } } - - foreach (RuinGeneration.Ruin ruin in Level.Loaded.Ruins) - { - if (!MathUtils.CircleIntersectsRectangle(pingSource, range, ruin.Area)) continue; - - foreach (var ruinShape in ruin.RuinShapes) - { - foreach (RuinGeneration.Line wall in ruinShape.Walls) - { - float cellDot = Vector2.Dot( - Vector2.Normalize(ruinShape.Center - pingSource), - Vector2.Normalize((wall.A + wall.B) / 2.0f - ruinShape.Center)); - if (cellDot > 0) continue; - - CreateBlipsForLine( - wall.A, wall.B, - pingSource, transducerPos, - pingRadius, prevPingRadius, - 100.0f, 1000.0f, range, pingStrength, passive); - } - } - } } foreach (Item item in Item.ItemList) @@ -1634,7 +1615,7 @@ namespace Barotrauma.Items.Components void CalculateDistance() { - pathFinder ??= new PathFinder(WayPoint.WayPointList, indoorsSteering: false); + pathFinder ??= new PathFinder(WayPoint.WayPointList, false); var path = pathFinder.FindPath(ConvertUnits.ToSimUnits(transducerPosition), ConvertUnits.ToSimUnits(worldPosition)); if (!path.Unreachable) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Steering.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Steering.cs index 1f32930b3..b66bcaa5a 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Steering.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Steering.cs @@ -888,6 +888,7 @@ namespace Barotrauma.Items.Components maintainPosOriginIndicator?.Remove(); steeringIndicator?.Remove(); enterOutpostPrompt?.Close(); + pathFinder = null; } public void ClientWrite(IWriteMessage msg, object[] extraData = null) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Power/PowerContainer.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Power/PowerContainer.cs index 4b6c91176..643b2042e 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Power/PowerContainer.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Power/PowerContainer.cs @@ -71,7 +71,7 @@ namespace Barotrauma.Items.Components var chargeText = new GUITextBlock(new RectTransform(new Vector2(0.6f, 1), textArea.RectTransform, Anchor.CenterRight), "", textColor: GUI.Style.TextColor, font: GUI.Font, textAlignment: Alignment.CenterRight) { - TextGetter = () => $"{(int)charge}/{(int)capacity} {kWmin} ({((int)MathUtils.Percentage(charge, capacity)).ToString()} %)" + TextGetter = () => $"{(int)Math.Round(charge)}/{(int)capacity} {kWmin} ({(int)Math.Round(MathUtils.Percentage(charge, capacity))} %)" }; if (chargeText.TextSize.X > chargeText.Rect.Width) { chargeText.Font = GUI.SmallFont; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Quality.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Quality.cs new file mode 100644 index 000000000..8c17e9d90 --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Quality.cs @@ -0,0 +1,21 @@ +using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; +using System.Xml.Linq; + +namespace Barotrauma.Items.Components +{ + partial class Quality : ItemComponent + { + public override void AddTooltipInfo(ref string name, ref string description) + { + foreach (var statValue in statValues) + { + int roundedValue = (int)Math.Round(statValue.Value * qualityLevel * 100); + if (roundedValue == 0) { return; } + string colorStr = XMLExtensions.ColorToString(GUI.Style.Green); + description += $"\n ‖color:{colorStr}‖{roundedValue.ToString("+0;-#")}%‖color:end‖ {TextManager.Get("qualitystattypenames." + statValue.Key.ToString(), true) ?? statValue.Key.ToString()}"; + } + } + } +} diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/RemoteController.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/RemoteController.cs new file mode 100644 index 000000000..6f6d3d740 --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/RemoteController.cs @@ -0,0 +1,22 @@ +using Microsoft.Xna.Framework.Graphics; + +namespace Barotrauma.Items.Components +{ + partial class RemoteController : ItemComponent + { + public override void DrawHUD(SpriteBatch spriteBatch, Character character) + { + currentTarget?.DrawHUD(spriteBatch, Screen.Selected.Cam, character); + } + + public override void UpdateHUD(Character character, float deltaTime, Camera cam) + { + currentTarget?.UpdateHUD(cam, character,deltaTime); + } + + public override void AddToGUIUpdateList(int order = 0) + { + currentTarget?.AddToGUIUpdateList(order: -1); + } + } +} diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Repairable.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Repairable.cs index 0099e8bfb..fbf133ced 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Repairable.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Repairable.cs @@ -15,16 +15,23 @@ namespace Barotrauma.Items.Components public GUIButton SabotageButton { get; private set; } + public GUIButton TinkerButton { get; private set; } + private GUIProgressBar progressBar; - private List particleEmitters = new List(); + private GUITextBlock progressBarOverlayText; + + private GUILayoutGroup extraButtonContainer; + + private readonly List particleEmitters = new List(); //the corresponding particle emitter is active when the condition is within this range - private List particleEmitterConditionRanges = new List(); + private readonly List particleEmitterConditionRanges = new List(); private SoundChannel repairSoundChannel; private string repairButtonText, repairingText; private string sabotageButtonText, sabotagingText; + private string tinkerButtonText, tinkeringText; private FixActions requestStartFixAction; @@ -45,8 +52,24 @@ namespace Barotrauma.Items.Components public override bool ShouldDrawHUD(Character character) { - if (!HasRequiredItems(character, false) || character.SelectedConstruction != item) return false; - return item.ConditionPercentage < RepairThreshold || character.IsTraitor && item.ConditionPercentage > MinSabotageCondition || (CurrentFixer == character && (!item.IsFullCondition || (character.IsTraitor && item.ConditionPercentage > MinSabotageCondition))); + if (!HasRequiredItems(character, false) || character.SelectedConstruction != item) { return false; } + if (character.IsTraitor && item.ConditionPercentage > MinSabotageCondition) { return true; } + + float maxRepairConditionMultiplier = GetMaxRepairConditionMultiplier(character); + if (item.Condition / maxRepairConditionMultiplier < RepairThreshold) { return true; } + + if (CurrentFixer == character) + { + float condition = item.Condition / item.MaxRepairConditionMultiplier; + float maxCondition = item.MaxCondition / item.MaxRepairConditionMultiplier; + if (condition < maxCondition * maxRepairConditionMultiplier) + { + return true; + } + } + if (IsTinkerable(character)) { return true; } + + return false; } partial void InitProjSpecific(XElement element) @@ -85,7 +108,7 @@ namespace Barotrauma.Items.Components } } - private void CreateGUI() + protected override void CreateGUI() { var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 0.75f), GuiFrame.RectTransform, Anchor.Center), childAnchor: Anchor.TopCenter) { @@ -120,6 +143,11 @@ namespace Barotrauma.Items.Components progressBar = new GUIProgressBar(new RectTransform(new Vector2(0.6f, 1.0f), progressBarHolder.RectTransform), color: GUI.Style.Green, barSize: 0.0f, style: "DeviceProgressBar"); + progressBarOverlayText = new GUITextBlock(new RectTransform(Vector2.One, progressBar.RectTransform), string.Empty, font: GUI.SubHeadingFont, textAlignment: Alignment.Center) + { + IgnoreLayoutGroups = true + }; + repairButtonText = TextManager.Get("RepairButton"); repairingText = TextManager.Get("Repairing"); RepairButton = new GUIButton(new RectTransform(new Vector2(0.4f, 1.0f), progressBarHolder.RectTransform, Anchor.TopCenter), repairButtonText) @@ -135,9 +163,16 @@ namespace Barotrauma.Items.Components progressBarHolder.RectTransform.MinSize = RepairButton.RectTransform.MinSize; RepairButton.RectTransform.MinSize = new Point((int)(RepairButton.TextBlock.TextSize.X * 1.2f), RepairButton.RectTransform.MinSize.Y); + extraButtonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.15f), paddedFrame.RectTransform), isHorizontal: true) + { + IgnoreLayoutGroups = true, + Stretch = true, + AbsoluteSpacing = GUI.IntScale(5) + }; + sabotageButtonText = TextManager.Get("SabotageButton"); sabotagingText = TextManager.Get("Sabotaging"); - SabotageButton = new GUIButton(new RectTransform(new Vector2(0.8f, 0.15f), paddedFrame.RectTransform, Anchor.BottomCenter), sabotageButtonText, style: "GUIButtonSmall") + SabotageButton = new GUIButton(new RectTransform(Vector2.One, extraButtonContainer.RectTransform), sabotageButtonText, style: "GUIButtonSmall") { IgnoreLayoutGroups = true, Visible = false, @@ -148,6 +183,22 @@ namespace Barotrauma.Items.Components return true; } }; + + tinkerButtonText = TextManager.Get("TinkerButton", returnNull: true) ?? "Tinker"; + tinkeringText = TextManager.Get("Tinkering", returnNull: true) ?? "Tinkering"; + TinkerButton = new GUIButton(new RectTransform(Vector2.One, extraButtonContainer.RectTransform), tinkerButtonText, style: "GUIButtonSmall") + { + IgnoreLayoutGroups = true, + Visible = false, + OnClicked = (btn, obj) => + { + requestStartFixAction = FixActions.Tinker; + item.CreateClientEvent(this); + return true; + } + }; + + extraButtonContainer.RectTransform.MinSize = new Point(0, SabotageButton.RectTransform.MinSize.Y); } partial void UpdateProjSpecific(float deltaTime) @@ -176,6 +227,7 @@ namespace Barotrauma.Items.Components { case FixActions.Repair: case FixActions.Sabotage: + case FixActions.Tinker: StartRepairing(Character.Controlled, requestStartFixAction); requestStartFixAction = FixActions.None; break; @@ -211,10 +263,24 @@ namespace Barotrauma.Items.Components { IsActive = true; - progressBar.BarSize = item.Condition / item.MaxCondition; + float defaultMaxCondition = (item.MaxCondition / item.MaxRepairConditionMultiplier); + + progressBar.BarSize = item.Condition / defaultMaxCondition; progressBar.Color = ToolBox.GradientLerp(progressBar.BarSize, GUI.Style.Red, GUI.Style.Orange, GUI.Style.Green); - RepairButton.Enabled = (currentFixerAction == FixActions.None || (CurrentFixer == character && currentFixerAction != FixActions.Repair)) && !item.IsFullCondition; + if (item.Condition > defaultMaxCondition) + { + float extraCondition = item.MaxCondition * (item.MaxRepairConditionMultiplier - 1.0f); + progressBar.Color = ToolBox.GradientLerp((item.Condition - defaultMaxCondition) / extraCondition, GUI.Style.ColorReputationHigh, GUI.Style.ColorReputationVeryHigh); + progressBarOverlayText.Visible = true; + progressBarOverlayText.Text = $"{(int)Math.Round((item.Condition / defaultMaxCondition) * 100)}%"; + } + else + { + progressBarOverlayText.Visible = false; + } + + RepairButton.Enabled = (currentFixerAction == FixActions.None || (CurrentFixer == character && currentFixerAction != FixActions.Repair)) && !item.IsFullCondition && item.ConditionPercentage < RepairThreshold; RepairButton.Text = (currentFixerAction == FixActions.None || CurrentFixer != character || currentFixerAction != FixActions.Repair) ? repairButtonText : repairingText + new string('.', ((int)(Timing.TotalTime * 2.0f) % 3) + 1); @@ -226,7 +292,18 @@ namespace Barotrauma.Items.Components sabotageButtonText : sabotagingText + new string('.', ((int)(Timing.TotalTime * 2.0f) % 3) + 1); + TinkerButton.Visible = IsTinkerable(character); + TinkerButton.IgnoreLayoutGroups = !TinkerButton.Visible; + TinkerButton.Enabled = (currentFixerAction == FixActions.None || (CurrentFixer == character && currentFixerAction != FixActions.Tinker)) && CanTinker(character); + TinkerButton.Text = (currentFixerAction == FixActions.None || CurrentFixer != character || currentFixerAction != FixActions.Tinker) ? + tinkerButtonText : + tinkeringText + new string('.', ((int)(Timing.TotalTime * 2.0f) % 3) + 1); + System.Diagnostics.Debug.Assert(GuiFrame.GetChild(0) is GUILayoutGroup, "Repair UI hierarchy has changed, could not find skill texts"); + + extraButtonContainer.Visible = SabotageButton.Visible || TinkerButton.Visible; + extraButtonContainer.IgnoreLayoutGroups = !extraButtonContainer.Visible; + foreach (GUIComponent c in GuiFrame.GetChild(0).Children) { if (!(c.UserData is Skill skill)) continue; @@ -278,9 +355,12 @@ namespace Barotrauma.Items.Components deteriorationTimer = msg.ReadSingle(); deteriorateAlwaysResetTimer = msg.ReadSingle(); DeteriorateAlways = msg.ReadBoolean(); + tinkeringDuration = msg.ReadSingle(); + tinkeringStrength = msg.ReadSingle(); ushort currentFixerID = msg.ReadUInt16(); currentFixerAction = (FixActions)msg.ReadRangedInteger(0, 2); CurrentFixer = currentFixerID != 0 ? Entity.FindEntityByID(currentFixerID) as Character : null; + item.MaxRepairConditionMultiplier = GetMaxRepairConditionMultiplier(CurrentFixer); } public void ClientWrite(IWriteMessage msg, object[] extraData = null) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Rope.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Rope.cs index f68e5f37d..25d45c773 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Rope.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Rope.cs @@ -31,6 +31,9 @@ namespace Barotrauma.Items.Components set; } + [Serialize("0.5,0.5)", false)] + public Vector2 Origin { get; set; } = new Vector2(0.5f, 0.5f); + public Vector2 DrawSize { get @@ -57,7 +60,6 @@ namespace Barotrauma.Items.Components sourcePos = sourceLimb.body.DrawPosition; } return sourcePos; - } partial void InitProjSpecific(XElement element) @@ -81,13 +83,15 @@ namespace Barotrauma.Items.Components public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1) { - if (target == null) { return; } + if (target == null || target.Removed) { return; } + if (target.ParentInventory != null) { return; } Vector2 startPos = GetSourcePos(); startPos.Y = -startPos.Y; if (source is Item sourceItem) { - var turret = sourceItem?.GetComponent(); + var turret = sourceItem.GetComponent(); + var weapon = sourceItem.GetComponent(); if (turret != null) { startPos = new Vector2(sourceItem.WorldRect.X + turret.TransformedBarrelPos.X, -(sourceItem.WorldRect.Y - turret.TransformedBarrelPos.Y)); @@ -96,8 +100,21 @@ namespace Barotrauma.Items.Components 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; } } + else if (weapon != null) + { + Vector2 barrelPos = FarseerPhysics.ConvertUnits.ToDisplayUnits(weapon.TransformedBarrelPos); + barrelPos.Y = -barrelPos.Y; + startPos += barrelPos; + } } - Vector2 endPos = new Vector2(target.DrawPosition.X, -target.DrawPosition.Y); + Vector2 endPos = new Vector2(target.DrawPosition.X, target.DrawPosition.Y); + Vector2 flippedPos = target.Sprite.size * target.Scale * (Origin - new Vector2(0.5f)); + if (target.body.Dir < 0.0f) + { + flippedPos.X = -flippedPos.X; + } + endPos += Vector2.Transform(flippedPos, Matrix.CreateRotationZ(target.body.Rotation)); + endPos.Y = -endPos.Y; if (Snapped) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Scanner.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Scanner.cs new file mode 100644 index 000000000..a44dca68e --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Scanner.cs @@ -0,0 +1,29 @@ +using Barotrauma.Networking; + +namespace Barotrauma.Items.Components +{ + partial class Scanner : ItemComponent, IServerSerializable + { + partial void UpdateProjSpecific() + { + if (Holdable != null && Holdable.Attached && (AlwaysDisplayProgressBar || DisplayProgressBar) && !IsScanCompleted) + { + Character.Controlled?.UpdateHUDProgressBar(this, + item.WorldPosition, + ScanTimer / ScanDuration, + GUI.Style.Red, GUI.Style.Green, + textTag: "progressbar.scanning"); + } + } + + public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime) + { + bool wasScanCompletedPreviously = IsScanCompleted; + scanTimer = msg.ReadSingle(); + if (!wasScanCompletedPreviously && IsScanCompleted) + { + OnScanCompleted?.Invoke(this); + } + } + } +} diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/ButtonTerminal.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/ButtonTerminal.cs new file mode 100644 index 000000000..ff50d0fef --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/ButtonTerminal.cs @@ -0,0 +1,115 @@ +using Barotrauma.Extensions; +using Barotrauma.Networking; +using Microsoft.Xna.Framework; +using System; +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma.Items.Components +{ + partial class ButtonTerminal : ItemComponent, IClientSerializable, IServerSerializable + { + private string[] terminalButtonStyles; + private GUIFrame containerHolder; + private GUIImage containerIndicator; + private GUIComponentStyle indicatorStyleRed, indicatorStyleGreen; + + partial void InitProjSpecific(XElement element) + { + terminalButtonStyles = new string[RequiredSignalCount]; + int i = 0; + foreach (var childElement in element.GetChildElements("TerminalButton")) + { + string style = childElement.GetAttributeString("style", null); + if (style == null) { continue; } + terminalButtonStyles[i++] = style; + } + indicatorStyleRed = GUI.Style.GetComponentStyle("IndicatorLightRed"); + indicatorStyleGreen = GUI.Style.GetComponentStyle("IndicatorLightGreen"); + CreateGUI(); + } + + protected override void CreateGUI() + { + var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.8f), GuiFrame.RectTransform, Anchor.Center), isHorizontal: true, childAnchor: Anchor.CenterLeft) + { + Stretch = true, + RelativeSpacing = 0.08f + }; + paddedFrame.OnAddedToGUIUpdateList += (component) => + { + bool buttonsEnabled = AllowUsingButtons; + foreach (var child in component.Children) + { + if (!(child is GUIButton)) { continue; } + if (!(child.UserData is int)) { continue; } + child.Enabled = buttonsEnabled; + child.Children.ForEach(c => c.Enabled = buttonsEnabled); + } + bool itemsContained = Container.Inventory.AllItems.Any(); + if (itemsContained) + { + var indicatorStyle = buttonsEnabled ? indicatorStyleGreen : indicatorStyleRed; + if (containerIndicator.Style != indicatorStyle) + { + containerIndicator.ApplyStyle(indicatorStyle); + } + } + containerIndicator.OverrideState = itemsContained ? GUIComponent.ComponentState.Selected : GUIComponent.ComponentState.None; + }; + + 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); + + var containerSection = new GUIFrame(new RectTransform(new Vector2(x, 1.0f), paddedFrame.RectTransform), style: null); + var containerSlot = new GUIFrame(new RectTransform(new Vector2(1.0f, y), containerSection.RectTransform, anchor: Anchor.Center), style: null); + containerHolder = new GUIFrame(new RectTransform(new Vector2(1f, 1.2f), containerSlot.RectTransform, Anchor.BottomCenter), style: null); + 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++) + { + var button = new GUIButton(new RectTransform(relativeSize, paddedFrame.RectTransform), style: null) + { + UserData = i, + OnClicked = (button, userData) => + { + if (GameMain.IsSingleplayer) + { + SendSignal((int)userData); + } + else + { + item.CreateClientEvent(this, new object[] { userData }); + } + return true; + } + }; + var image = new GUIImage(new RectTransform(Vector2.One, button.RectTransform), terminalButtonStyles[i], scaleToFit: true); + } + } + + protected override void OnResolutionChanged() + { + base.OnResolutionChanged(); + OnItemLoadedProjSpecific(); + } + + partial void OnItemLoadedProjSpecific() + { + Container.AllowUIOverlap = true; + Container.Inventory.RectTransform = containerHolder.RectTransform; + } + + public void ClientWrite(IWriteMessage msg, object[] extraData = null) + { + Write(msg, extraData); + } + + public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime) + { + SendSignal(msg.ReadRangedInteger(0, Signals.Length - 1), isServerMessage: true); + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Connection.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Connection.cs index 4efc832fc..336769480 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Connection.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Connection.cs @@ -54,6 +54,11 @@ namespace Barotrauma.Items.Components if (wireComponent != null) { equippedWire = wireComponent; + var connectedEnd = equippedWire.OtherConnection(null); + if (connectedEnd?.Item.Submarine != null && panel.Item.Submarine != connectedEnd.Item.Submarine) + { + equippedWire = null; + } } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/ConnectionPanel.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/ConnectionPanel.cs index 1162e4bff..72e8482da 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/ConnectionPanel.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/ConnectionPanel.cs @@ -88,11 +88,6 @@ namespace Barotrauma.Items.Components return character == Character.Controlled && character == user && character.SelectedConstruction == item; } - public override void AddToGUIUpdateList() - { - GuiFrame?.AddToGUIUpdateList(); - } - public override void UpdateHUD(Character character, float deltaTime, Camera cam) { if (character != Character.Controlled || character != user || character.SelectedConstruction != item) { return; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Terminal.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Terminal.cs index 353e4550b..a816f402a 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Terminal.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Terminal.cs @@ -63,11 +63,11 @@ namespace Barotrauma.Items.Components } OutputValue = input; - ShowOnDisplay(input); + ShowOnDisplay(input, addToHistory: true); item.SendSignal(input, "signal_out"); } - partial void ShowOnDisplay(string input, bool addToHistory = true) + partial void ShowOnDisplay(string input, bool addToHistory) { if (addToHistory) { @@ -85,7 +85,7 @@ namespace Barotrauma.Items.Components GUITextBlock newBlock = new GUITextBlock( new RectTransform(new Vector2(1, 0), historyBox.Content.RectTransform, anchor: Anchor.TopCenter), "> " + input, - textColor: Color.LimeGreen, wrap: true) + textColor: Color.LimeGreen, wrap: true, font: UseMonospaceFont ? GUI.MonospacedFont : GUI.GlobalFont) { CanBeFocused = false }; @@ -118,9 +118,9 @@ namespace Barotrauma.Items.Components // This method is overrided instead of the UpdateHUD method because this ensures the input box is selected // even when the terminal component is selected for the very first time. Doing the input box selection in the // UpdateHUD method only selects the input box on every terminal selection except for the very first time. - public override void AddToGUIUpdateList() + public override void AddToGUIUpdateList(int order = 0) { - base.AddToGUIUpdateList(); + base.AddToGUIUpdateList(order: order); if (shouldSelectInputBox) { inputBox.Select(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/StatusHUD.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/StatusHUD.cs index 04313fee8..d974c2044 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/StatusHUD.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/StatusHUD.cs @@ -39,6 +39,34 @@ namespace Barotrauma.Items.Components private set; } + [Serialize(false, false)] + public bool ThermalGoggles + { + get; + private set; + } + + [Serialize(true, false)] + public bool ShowDeadCharacters + { + get; + private set; + } + + [Serialize(true, false)] + public bool ShowTexts + { + get; + private set; + } + + [Serialize("72,119,72,120", false)] + public Color OverlayColor + { + get; + private set; + } + private readonly List visibleCharacters = new List(); private const float UpdateInterval = 0.5f; @@ -48,6 +76,8 @@ namespace Barotrauma.Items.Components private bool isEquippable; + private float thermalEffectState; + public IEnumerable VisibleCharacters { get @@ -80,7 +110,10 @@ namespace Barotrauma.Items.Components { refEntity = item; } - + + thermalEffectState += deltaTime; + thermalEffectState %= 10000.0f; + if (updateTimer > 0.0f) { updateTimer -= deltaTime; @@ -91,6 +124,7 @@ namespace Barotrauma.Items.Components foreach (Character c in Character.CharacterList) { if (c == equipper || !c.Enabled || c.Removed) { continue; } + if (!ShowDeadCharacters && c.IsDead) { continue; } float dist = Vector2.DistanceSquared(refEntity.WorldPosition, c.WorldPosition); if (dist < Range * Range) @@ -123,27 +157,71 @@ namespace Barotrauma.Items.Components { if (character == null) { return; } - GUI.UIGlow.Draw(spriteBatch, new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight), - Color.LightGreen * 0.5f); - - Character closestCharacter = null; - float closestDist = float.PositiveInfinity; - foreach (Character c in visibleCharacters) + if (OverlayColor.A > 0) { - if (c == character || !c.Enabled || c.Removed) { continue; } - - float dist = Vector2.DistanceSquared(GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition), c.WorldPosition); - if (dist < closestDist) - { - closestCharacter = c; - closestDist = dist; - } + GUI.UIGlow.Draw(spriteBatch, new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight), OverlayColor); } - if (closestCharacter != null) + if (ShowTexts) { - float dist = Vector2.Distance(GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition), closestCharacter.WorldPosition); - DrawCharacterInfo(spriteBatch, closestCharacter, 1.0f - MathHelper.Max((dist - (Range - FadeOutRange)) / FadeOutRange, 0.0f)); + Character closestCharacter = null; + float closestDist = float.PositiveInfinity; + foreach (Character c in visibleCharacters) + { + if (c == character || !c.Enabled || c.Removed) { continue; } + + float dist = Vector2.DistanceSquared(GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition), c.WorldPosition); + if (dist < closestDist) + { + closestCharacter = c; + closestDist = dist; + } + } + + if (closestCharacter != null) + { + float dist = Vector2.Distance(GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition), closestCharacter.WorldPosition); + DrawCharacterInfo(spriteBatch, closestCharacter, 1.0f - MathHelper.Max((dist - (Range - FadeOutRange)) / FadeOutRange, 0.0f)); + } + } + + 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; + } + + 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 = GUI.Style.UIThermalGlow.Sprite; + foreach (Limb limb in c.AnimController.Limbs) + { + if (limb.Mass < 1.0f) { 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); } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Turret.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Turret.cs index f28cdf48b..f4ba37137 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Turret.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Turret.cs @@ -177,6 +177,10 @@ namespace Barotrauma.Items.Components partial void LaunchProjSpecific() { recoilTimer = RetractionTime; + if (user != null) + { + recoilTimer /= 1 + user.GetStatValue(StatTypes.TurretAttackSpeed); + } PlaySound(ActionType.OnUse); Vector2 particlePos = GetRelativeFiringPosition(UseFiringOffsetForMuzzleFlash); foreach (ParticleEmitter emitter in particleEmitters) @@ -534,20 +538,20 @@ namespace Barotrauma.Items.Components minRotationWidget.Draw(spriteBatch, (float)Timing.Step); maxRotationWidget.Draw(spriteBatch, (float)Timing.Step); - Vector2 GetDrawPos() - { - Vector2 drawPos = new Vector2(item.Rect.X + transformedBarrelPos.X, item.Rect.Y - transformedBarrelPos.Y); - if (item.Submarine != null) { drawPos += item.Submarine.DrawPosition; } - drawPos.Y = -drawPos.Y; - return drawPos; - } - void UpdateBarrel() { rotation = (minRotation + maxRotation) / 2; } } + public Vector2 GetDrawPos() + { + Vector2 drawPos = new Vector2(item.Rect.X + transformedBarrelPos.X, item.Rect.Y - transformedBarrelPos.Y); + if (item.Submarine != null) { drawPos += item.Submarine.DrawPosition; } + drawPos.Y = -drawPos.Y; + return drawPos; + } + private Widget GetWidget(string id, SpriteBatch spriteBatch, int size = 5, float thickness = 1f, Action initMethod = null) { Vector2 offset = new Vector2(size / 2 + 5, -10); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Wearable.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Wearable.cs index 9692249c8..10d0fc376 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Wearable.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Wearable.cs @@ -5,17 +5,17 @@ namespace Barotrauma.Items.Components { partial class Wearable { - private void GetDamageModifierText(ref string description, float damageMultiplier, string afflictionIdentifier) + private void GetDamageModifierText(ref string description, DamageModifier damageModifier, string afflictionIdentifier) { - int roundedValue = (int)Math.Round((1 - damageMultiplier) * 100); + int roundedValue = (int)Math.Round((1 - damageModifier.DamageMultiplier * damageModifier.ProbabilityMultiplier) * 100); if (roundedValue == 0) { return; } string colorStr = XMLExtensions.ColorToString(GUI.Style.Green); description += $"\n ‖color:{colorStr}‖{roundedValue.ToString("-0;+#")}%‖color:end‖ {AfflictionPrefab.List.FirstOrDefault(ap => ap.Identifier.Equals(afflictionIdentifier, StringComparison.OrdinalIgnoreCase))?.Name ?? afflictionIdentifier}"; } - public override void AddTooltipInfo(ref string description) + public override void AddTooltipInfo(ref string name, ref string description) { - if (damageModifiers.Any(d => !MathUtils.NearlyEqual(d.DamageMultiplier, 1f)) || SkillModifiers.Any()) + if (damageModifiers.Any(d => !MathUtils.NearlyEqual(d.DamageMultiplier, 1f) || !MathUtils.NearlyEqual(d.ProbabilityMultiplier, 1f)) || SkillModifiers.Any()) { description += "\n"; } @@ -31,11 +31,11 @@ namespace Barotrauma.Items.Components foreach (string afflictionIdentifier in damageModifier.ParsedAfflictionIdentifiers) { - GetDamageModifierText(ref description, damageModifier.DamageMultiplier, afflictionIdentifier); + GetDamageModifierText(ref description, damageModifier, afflictionIdentifier); } foreach (string afflictionIdentifier in damageModifier.ParsedAfflictionTypes) { - GetDamageModifierText(ref description, damageModifier.DamageMultiplier, afflictionIdentifier); + GetDamageModifierText(ref description, damageModifier, afflictionIdentifier); } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs index e563f152d..a8d965902 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs @@ -297,9 +297,10 @@ namespace Barotrauma } } + string name = item.Name; foreach (ItemComponent component in item.Components) { - component.AddTooltipInfo(ref description); + component.AddTooltipInfo(ref name, ref description); } if (item.Prefab.ShowContentsInTooltip && item.OwnInventory != null) @@ -315,12 +316,18 @@ namespace Barotrauma string colorStr = XMLExtensions.ColorToString(!item.AllowStealing ? GUI.Style.Red : Color.White); - toolTip = $"‖color:{colorStr}‖{item.Name}‖color:end‖"; + toolTip = $"‖color:{colorStr}‖{name}‖color:end‖"; + if (item.GetComponent() != null) + { + // substring by to get rid of the empty space at start, text file should be adjusted + toolTip += $"\n{TextManager.GetWithVariable("itemname.quality" + item.Quality, "[itemname]", "", fallBackTag: "itemname.quality3")?.Substring(1)}"; + } + if (itemsInSlot.All(it => it.NonInteractable || it.NonPlayerTeamInteractable)) { toolTip += " " + TextManager.Get("connectionlocked"); } - if (!item.IsFullCondition && !item.Prefab.HideConditionBar) + if (!item.IsFullCondition && !item.Prefab.HideConditionInTooltip) { string conditionColorStr = XMLExtensions.ColorToString(ToolBox.GradientLerp(item.Condition / item.MaxCondition, GUI.Style.ColorInventoryEmpty, GUI.Style.ColorInventoryHalf, GUI.Style.ColorInventoryFull)); toolTip += $"‖color:{conditionColorStr}‖ ({(int)item.ConditionPercentage} %)‖color:end‖"; @@ -478,7 +485,7 @@ namespace Barotrauma } if (container == null) { return false; } - return owner.SelectedCharacter != null|| (!(owner is Character character)) || !container.KeepOpenWhenEquippedBy(character) || !owner.HasEquippedItem(container.Item); + return owner.SelectedCharacter != null|| (!(owner is Character character)) || !container.KeepOpenWhenEquippedBy(character) || !owner.HasEquippedItem(container.Item); } protected virtual bool HideSlot(int i) @@ -594,7 +601,10 @@ namespace Barotrauma { var slotRef = new SlotReference(this, slot, slotIndex, isSubSlot, slots[slotIndex].FirstOrDefault()?.GetComponent()?.Inventory); if (Screen.Selected is SubEditorScreen editor && !editor.WiringMode && slotRef.ParentInventory is CharacterInventory) { return; } - selectedSlot = slotRef; + if (CanSelectSlot(slotRef)) + { + selectedSlot = slotRef; + } } if (!DraggingItems.Any()) @@ -667,6 +677,10 @@ namespace Barotrauma if (subInventory.visualSlots == null) { subInventory.CreateSlots(); } canMove = container.MovableFrame && !subInventory.IsInventoryHoverAvailable(Owner as Character, container) && subInventory.originalPos != Point.Zero; + if (this is CharacterInventory characterInventory && characterInventory.CurrentLayout != CharacterInventory.Layout.Default) + { + canMove = false; + } if (canMove) { @@ -715,7 +729,7 @@ namespace Barotrauma spacing = new Vector2(10 * UIScale, (10 + UnequippedIndicator.size.Y) * UIScale); } - int columns = (int)Math.Max(Math.Floor(Math.Sqrt(itemCapacity)), 1); + int columns = MathHelper.Clamp((int)Math.Floor(Math.Sqrt(itemCapacity)), 1, container.SlotsPerRow); while (itemCapacity / columns * (subRect.Height + spacing.Y) > GameMain.GraphicsHeight * 0.5f) { columns++; @@ -826,11 +840,23 @@ namespace Barotrauma return rect.Contains(PlayerInput.MousePosition); } + public static bool IsMouseOnInventory + { + get; private set; + } + + /// + /// Refresh the value of IsMouseOnInventory + /// + public static void RefreshMouseOnInventory() + { + IsMouseOnInventory = DetermineMouseOnInventory(); + } + /// /// Is the mouse on any inventory element (slot, equip button, subinventory...) /// - /// - public static bool IsMouseOnInventory(bool ignoreDraggedItem = false) + private static bool DetermineMouseOnInventory(bool ignoreDraggedItem = false) { if (GameMain.GameSession?.Campaign != null && (GameMain.GameSession.Campaign.ShowCampaignUI || GameMain.GameSession.Campaign.ForceMapUI)) @@ -1112,7 +1138,7 @@ namespace Barotrauma { Character.Controlled.ClearInputs(); - if (!IsMouseOnInventory(ignoreDraggedItem: true) && + if (!DetermineMouseOnInventory(ignoreDraggedItem: true) && CharacterHealth.OpenHealthWindow != null) { bool dropSuccessful = false; @@ -1279,34 +1305,52 @@ namespace Barotrauma DraggingItems.Clear(); } - if (selectedSlot != null) + if (selectedSlot != null && !CanSelectSlot(selectedSlot)) { - if (!selectedSlot.Slot.MouseOn()) + selectedSlot = null; + } + } + + private static bool CanSelectSlot(SlotReference selectedSlot) + { + if (!selectedSlot.Slot.MouseOn()) + { + return false; + } + else + { + var rootOwner = (selectedSlot.ParentInventory?.Owner as Item)?.GetRootInventoryOwner(); + if (selectedSlot.ParentInventory?.Owner != Character.Controlled && + selectedSlot.ParentInventory?.Owner != Character.Controlled.SelectedCharacter && + selectedSlot.ParentInventory?.Owner != Character.Controlled.SelectedConstruction && + !(Character.Controlled.SelectedConstruction?.linkedTo.Contains(selectedSlot.ParentInventory?.Owner) ?? false) && + rootOwner != Character.Controlled && + rootOwner != Character.Controlled.SelectedCharacter && + rootOwner != Character.Controlled.SelectedConstruction && + !(Character.Controlled.SelectedConstruction?.linkedTo.Contains(rootOwner) ?? false)) { - selectedSlot = null; + return false; } - else + var parentItem = (selectedSlot?.ParentInventory?.Owner as Item) ?? selectedSlot?.Item; + if ((parentItem?.GetRootInventoryOwner() is Character ownerCharacter) && + ownerCharacter == Character.Controlled && + CharacterHealth.OpenHealthWindow?.Character != ownerCharacter && + ownerCharacter.Inventory.IsInLimbSlot(parentItem, InvSlotType.HealthInterface)) { - var rootOwner = (selectedSlot.ParentInventory?.Owner as Item)?.GetRootInventoryOwner(); - if (selectedSlot.ParentInventory?.Owner != Character.Controlled && - selectedSlot.ParentInventory?.Owner != Character.Controlled.SelectedCharacter && - selectedSlot.ParentInventory?.Owner != Character.Controlled.SelectedConstruction && - !(Character.Controlled.SelectedConstruction?.linkedTo.Contains(selectedSlot.ParentInventory?.Owner) ?? false) && - rootOwner != Character.Controlled && - rootOwner != Character.Controlled.SelectedCharacter && - rootOwner != Character.Controlled.SelectedConstruction && - !(Character.Controlled.SelectedConstruction?.linkedTo.Contains(rootOwner) ?? false)) - { - selectedSlot = null; - } + highlightedSubInventorySlots.RemoveWhere(s => s.Item == parentItem); + return false; } } + return true; } + protected static Rectangle GetSubInventoryHoverArea(SlotReference subSlot) { Rectangle hoverArea; - if (!subSlot.Inventory.Movable() || Character.Controlled?.Inventory == subSlot.ParentInventory && !Character.Controlled.HasEquippedItem(subSlot.Item)) + if (!subSlot.Inventory.Movable() || + (Character.Controlled?.Inventory == subSlot.ParentInventory && !Character.Controlled.HasEquippedItem(subSlot.Item)) || + (subSlot.ParentInventory is CharacterInventory characterInventory && characterInventory.CurrentLayout != CharacterInventory.Layout.Default)) { hoverArea = subSlot.Slot.Rect; hoverArea.Location += subSlot.Slot.DrawOffset.ToPoint(); @@ -1375,7 +1419,7 @@ namespace Barotrauma float scale = Math.Min(Math.Min(iconSize / sprite.size.X, iconSize / sprite.size.Y), 1.5f); Vector2 itemPos = PlayerInput.MousePosition; - bool mouseOnHealthInterface = CharacterHealth.OpenHealthWindow != null && CharacterHealth.OpenHealthWindow.MouseOnElement; + bool mouseOnHealthInterface = CharacterHealth.OpenHealthWindow != null && CharacterHealth.OpenHealthWindow.MouseOnElement && DraggingItems.Any(it => it.UseInHealthInterface); if ((GUI.MouseOn == null || mouseOnHealthInterface) && selectedSlot == null) { @@ -1434,7 +1478,8 @@ namespace Barotrauma } Color slotColor = Color.White; - if (inventory?.Owner is Item i && !i.IsPlayerTeamInteractable) { slotColor = Color.Gray; } + Item parentItem = inventory?.Owner as Item; + if (parentItem != null && !parentItem.IsPlayerTeamInteractable) { slotColor = Color.Gray; } var itemContainer = item?.GetComponent(); if (itemContainer != null && (itemContainer.InventoryTopSprite != null || itemContainer.InventoryBottomSprite != null)) { @@ -1513,7 +1558,7 @@ namespace Barotrauma var indicatorStyle = GUI.Style.GetComponentStyle("ContainedStateIndicator.Default"); Sprite indicatorSprite = indicatorStyle?.GetDefaultSprite(); Sprite emptyIndicatorSprite = indicatorStyle?.GetSprite(GUIComponent.ComponentState.Hover); - DrawItemStateIndicator(spriteBatch, inventory, indicatorSprite, emptyIndicatorSprite, conditionIndicatorArea, item.Condition / item.MaxCondition); + DrawItemStateIndicator(spriteBatch, inventory, indicatorSprite, emptyIndicatorSprite, conditionIndicatorArea, item.Condition / item.MaxCondition); } if (itemContainer != null && itemContainer.ShowContainedStateIndicator) @@ -1525,14 +1570,14 @@ namespace Barotrauma } else { - var containedItem = itemContainer.Inventory.slots[0].FirstOrDefault(); - containedState = itemContainer.Inventory.Capacity == 1 ? + var containedItem = itemContainer.Inventory.slots[Math.Max(itemContainer.ContainedStateIndicatorSlot, 0)].FirstOrDefault(); + containedState = itemContainer.Inventory.Capacity == 1 || itemContainer.ContainedStateIndicatorSlot > -1 ? (containedItem == null ? 0.0f : containedItem.Condition / containedItem.MaxCondition) : itemContainer.Inventory.slots.Count(i => !i.Empty()) / (float)itemContainer.Inventory.capacity; if (containedItem != null && itemContainer.Inventory.Capacity == 1) { - int maxStackSize = Math.Min(containedItem.Prefab.MaxStackSize, itemContainer.MaxStackSize); - if (maxStackSize > 1) + int maxStackSize = Math.Min(containedItem.Prefab.MaxStackSize, itemContainer.GetMaxStackSize(0)); + if (maxStackSize > 1 || containedItem.Prefab.HideConditionBar) { containedState = itemContainer.Inventory.slots[0].ItemCount / (float)maxStackSize; } @@ -1556,6 +1601,27 @@ namespace Barotrauma DrawItemStateIndicator(spriteBatch, inventory, indicatorSprite, emptyIndicatorSprite, containedIndicatorArea, containedState, pulsate: !usingDefaultSprite && containedState >= 0.0f && containedState < 0.25f && inventory == Character.Controlled?.Inventory && Character.Controlled.HasEquippedItem(item)); } + + if (item.Quality != 0) + { + var style = GUI.Style.GetComponentStyle("InnerGlowSmall"); + if (style == null) + { + GUI.DrawRectangle(spriteBatch, rect, GUI.Style.GetQualityColor(item.Quality) * 0.7f); + } + else + { + style.Sprites[GUIComponent.ComponentState.None].FirstOrDefault()?.Draw(spriteBatch, rect, GUI.Style.GetQualityColor(item.Quality) * 0.5f); + } + } + } + else + { + var slotIcon = parentItem?.GetComponent()?.GetSlotIcon(slotIndex); + if (slotIcon != null) + { + slotIcon.Draw(spriteBatch, rect.Center.ToVector2(), GUI.Style.EquipmentSlotIconColor, scale: Math.Min(rect.Width / slotIcon.size.X, rect.Height / slotIcon.size.Y) * 0.8f); + } } } @@ -1590,7 +1656,7 @@ namespace Barotrauma Color spriteColor = sprite == item.Sprite ? item.GetSpriteColor() : item.GetInventoryIconColor(); if (inventory != null && (inventory.Locked || inventory.slots[slotIndex].Items.All(it => it.NonInteractable || it.NonPlayerTeamInteractable))) { spriteColor *= 0.5f; } - if (CharacterHealth.OpenHealthWindow != null && !item.UseInHealthInterface) + if (CharacterHealth.OpenHealthWindow != null && !item.UseInHealthInterface && !item.AllowedSlots.Contains(InvSlotType.HealthInterface) && item.GetComponent() == null) { spriteColor = Color.Lerp(spriteColor, Color.TransparentBlack, 0.5f); } @@ -1611,9 +1677,9 @@ namespace Barotrauma scale: iconSize.X / stealIcon.size.X); } int maxStackSize = item.Prefab.MaxStackSize; - if (item.Container != null) + if (inventory is ItemInventory itemInventory) { - maxStackSize = Math.Min(maxStackSize, item.Container.GetComponent()?.MaxStackSize ?? maxStackSize); + maxStackSize = Math.Min(maxStackSize, itemInventory.Container.GetMaxStackSize(slotIndex)); } if (maxStackSize > 1 && inventory != null) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs index d675203fd..900731c83 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs @@ -276,7 +276,7 @@ namespace Barotrauma BrokenItemSprite fadeInBrokenSprite = null; float fadeInBrokenSpriteAlpha = 0.0f; - float displayCondition = FakeBroken ? 0.0f : condition; + float displayCondition = FakeBroken ? 0.0f : ConditionPercentage; Vector2 drawOffset = Vector2.Zero; if (displayCondition < MaxCondition) { @@ -326,13 +326,18 @@ namespace Barotrauma size, color: color, textureScale: Vector2.One * Scale, depth: depth); - fadeInBrokenSprite?.Sprite.DrawTiled(spriteBatch, new Vector2(DrawPosition.X - rect.Width / 2, -(DrawPosition.Y + rect.Height / 2)) + fadeInBrokenSprite.Offset.ToVector2() * Scale, size, color: color * fadeInBrokenSpriteAlpha, - textureScale: Vector2.One * Scale, - depth: depth - 0.000001f); + + if (fadeInBrokenSprite != null) + { + float d = Math.Min(depth + (fadeInBrokenSprite.Sprite.Depth - activeSprite.Depth - 0.000001f), 0.999f); + fadeInBrokenSprite.Sprite.DrawTiled(spriteBatch, new Vector2(DrawPosition.X - rect.Width / 2, -(DrawPosition.Y + rect.Height / 2)) + fadeInBrokenSprite.Offset.ToVector2() * Scale, size, color: color * fadeInBrokenSpriteAlpha, + textureScale: Vector2.One * Scale, + depth: d); + } foreach (var decorativeSprite in Prefab.DecorativeSprites) { if (!spriteAnimState[decorativeSprite].IsActive) { continue; } - Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState, spriteAnimState[decorativeSprite].RandomOffsetMultiplier, -rotationRad) * Scale; + Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState, spriteAnimState[decorativeSprite].RandomOffsetMultiplier, flippedX && Prefab.CanSpriteFlipX ? rotationRad : -rotationRad) * Scale; if (flippedX && Prefab.CanSpriteFlipX) { offset.X = -offset.X; } if (flippedY && Prefab.CanSpriteFlipY) { offset.Y = -offset.Y; } decorativeSprite.Sprite.DrawTiled(spriteBatch, @@ -357,7 +362,11 @@ namespace Barotrauma if (color.A > 0) { activeSprite.Draw(spriteBatch, new Vector2(DrawPosition.X, -DrawPosition.Y) + drawOffset, color, origin, rotationRad, Scale, activeSprite.effects, depth); - fadeInBrokenSprite?.Sprite.Draw(spriteBatch, new Vector2(DrawPosition.X, -DrawPosition.Y) + fadeInBrokenSprite.Offset.ToVector2() * Scale, color * fadeInBrokenSpriteAlpha, origin, rotationRad, Scale, activeSprite.effects, depth - 0.000001f); + if (fadeInBrokenSprite != null) + { + float d = Math.Min(depth + (fadeInBrokenSprite.Sprite.Depth - activeSprite.Depth - 0.000001f), 0.999f); + fadeInBrokenSprite.Sprite.Draw(spriteBatch, new Vector2(DrawPosition.X, -DrawPosition.Y) + fadeInBrokenSprite.Offset.ToVector2() * Scale, color * fadeInBrokenSpriteAlpha, origin, rotationRad, Scale, activeSprite.effects, d); + } } if (Infector != null && (Infector.ParentBallastFlora.HasBrokenThrough || BallastFloraBehavior.AlwaysShowBallastFloraSprite)) { @@ -368,7 +377,7 @@ namespace Barotrauma { if (!spriteAnimState[decorativeSprite].IsActive) { continue; } float rot = decorativeSprite.GetRotation(ref spriteAnimState[decorativeSprite].RotationState, spriteAnimState[decorativeSprite].RandomRotationFactor); - Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState, spriteAnimState[decorativeSprite].RandomOffsetMultiplier, -rotationRad) * Scale; + Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState, spriteAnimState[decorativeSprite].RandomOffsetMultiplier, flippedX && Prefab.CanSpriteFlipX ? rotationRad : -rotationRad) * Scale; if (flippedX && Prefab.CanSpriteFlipX) { offset.X = -offset.X; } if (flippedY && Prefab.CanSpriteFlipY) { offset.Y = -offset.Y; } decorativeSprite.Sprite.Draw(spriteBatch, new Vector2(DrawPosition.X + offset.X, -(DrawPosition.Y + offset.Y)), color, @@ -410,8 +419,11 @@ namespace Barotrauma } } body.Draw(spriteBatch, activeSprite, color, depth, Scale); - if (fadeInBrokenSprite != null) { body.Draw(spriteBatch, fadeInBrokenSprite.Sprite, color * fadeInBrokenSpriteAlpha, depth - 0.000001f, Scale); } - + if (fadeInBrokenSprite != null) + { + float d = Math.Min(depth + (fadeInBrokenSprite.Sprite.Depth - activeSprite.Depth - 0.000001f), 0.999f); + body.Draw(spriteBatch, fadeInBrokenSprite.Sprite, color * fadeInBrokenSpriteAlpha, d, Scale); + } foreach (var decorativeSprite in Prefab.DecorativeSprites) { if (!spriteAnimState[decorativeSprite].IsActive) { continue; } @@ -464,6 +476,11 @@ namespace Barotrauma if (GameMain.DebugDraw) { body?.DebugDraw(spriteBatch, Color.White); + if (GetComponent()?.PhysicsBody is PhysicsBody triggerBody) + { + triggerBody.UpdateDrawPosition(); + triggerBody.DebugDraw(spriteBatch, Color.White); + } } if (editing && IsSelected && PlayerInput.KeyDown(Keys.Space)) @@ -704,6 +721,7 @@ namespace Barotrauma new GUIButton(new RectTransform(new Vector2(0.23f, 1.0f), buttonContainer.RectTransform), TextManager.Get("MirrorEntityX"), style: "GUIButtonSmall") { ToolTip = TextManager.Get("MirrorEntityXToolTip"), + Enabled = Prefab.CanFlipX, OnClicked = (button, data) => { foreach (MapEntity me in SelectedList) @@ -717,6 +735,7 @@ namespace Barotrauma new GUIButton(new RectTransform(new Vector2(0.23f, 1.0f), buttonContainer.RectTransform), TextManager.Get("MirrorEntityY"), style: "GUIButtonSmall") { ToolTip = TextManager.Get("MirrorEntityYToolTip"), + Enabled = Prefab.CanFlipY, OnClicked = (button, data) => { foreach (MapEntity me in SelectedList) @@ -1068,7 +1087,7 @@ namespace Barotrauma foreach (Character otherCharacter in Character.CharacterList) { if (otherCharacter != character && - otherCharacter.SelectedConstruction == character.SelectedConstruction) + otherCharacter.SelectedConstruction == this) { ItemInUseWarning.Visible = true; if (mergedHUDRect.Width > GameMain.GraphicsWidth / 2) { mergedHUDRect.Inflate(-GameMain.GraphicsWidth / 4, 0); } @@ -1180,7 +1199,7 @@ namespace Barotrauma return texts; } - public override void AddToGUIUpdateList() + public override void AddToGUIUpdateList(int order = 0) { if (Screen.Selected is SubEditorScreen) { @@ -1194,7 +1213,14 @@ namespace Barotrauma } } - if (Character.Controlled != null && Character.Controlled.SelectedConstruction != this) { return; } + if (Character.Controlled != null && Character.Controlled.SelectedConstruction != this && GetComponent() == null) + { + if (Character.Controlled.SelectedConstruction?.GetComponent()?.TargetItem != this && + !Character.Controlled.HeldItems.Any(it => it.GetComponent()?.TargetItem == this)) + { + return; + } + } bool needsLayoutUpdate = false; foreach (ItemComponent ic in activeHUDs) @@ -1205,7 +1231,7 @@ namespace Barotrauma bool wasUsingAlternativeLayout = ic.UseAlternativeLayout; ic.UseAlternativeLayout = useAlternativeLayout; needsLayoutUpdate |= ic.UseAlternativeLayout != wasUsingAlternativeLayout; - ic.AddToGUIUpdateList(); + ic.AddToGUIUpdateList(order); } if (itemInUseWarning != null && itemInUseWarning.Visible) @@ -1530,6 +1556,7 @@ namespace Barotrauma byte bodyType = msg.ReadByte(); bool spawnedInOutpost = msg.ReadBoolean(); bool allowStealing = msg.ReadBoolean(); + int quality = msg.ReadRangedInteger(0, Items.Components.Quality.MaxQuality); byte teamID = msg.ReadByte(); bool tagsChanged = msg.ReadBoolean(); string tags = ""; @@ -1605,7 +1632,8 @@ namespace Barotrauma item = new Item(itemPrefab, pos, sub, id: itemId) { SpawnedInOutpost = spawnedInOutpost, - AllowStealing = allowStealing + AllowStealing = allowStealing, + Quality = quality }; } catch (Exception e) @@ -1623,6 +1651,10 @@ namespace Barotrauma { wifiComponent.TeamID = (CharacterTeamType)teamID; } + foreach (IdCard idCard in item.GetComponents()) + { + idCard.TeamID = (CharacterTeamType)teamID; + } if (descriptionChanged) { item.Description = itemDesc; } if (tagsChanged) { item.Tags = tags; } var nameTag = item.GetComponent(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/ItemPrefab.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/ItemPrefab.cs index 5854fc17b..d81332c48 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/ItemPrefab.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/ItemPrefab.cs @@ -77,6 +77,13 @@ namespace Barotrauma protected set; } + [Serialize(true, false)] + public bool ShowInStatusMonitor + { + get; + private set; + } + [Serialize("", false)] public string ImpactSoundTag { get; private set; } @@ -84,13 +91,13 @@ namespace Barotrauma public override void UpdatePlacing(Camera cam) { Vector2 position = Submarine.MouseToWorldGrid(cam, Submarine.MainSub); - + if (PlayerInput.SecondaryMouseButtonClicked()) { selected = null; return; } - + var potentialContainer = MapEntity.GetPotentialContainer(position); if (!ResizeHorizontal && !ResizeVertical) @@ -155,7 +162,7 @@ namespace Barotrauma { potentialContainer.IsHighlighted = true; } - + //if (PlayerInput.GetMouseState.RightButton == ButtonState.Pressed) selected = null; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Explosion.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Explosion.cs index 308010cac..57d7b3e44 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Explosion.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Explosion.cs @@ -8,6 +8,8 @@ namespace Barotrauma { partial void ExplodeProjSpecific(Vector2 worldPosition, Hull hull) { + if (GameMain.Client?.MidRoundSyncing ?? false) { return; } + if (shockwave) { GameMain.ParticleManager.CreateParticle("shockwave", worldPosition, diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Hull.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Hull.cs index 2138e1109..13533eb7d 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Hull.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Hull.cs @@ -676,7 +676,7 @@ namespace Barotrauma } else { - remoteBackgroundSections.Add(new BackgroundSection(new Rectangle(0, 0, 1, 1), i, colorStrength, color, 0)); + remoteBackgroundSections.Add(new BackgroundSection(new Rectangle(0, 0, 1, 1), (ushort)i, colorStrength, color, 0)); } } paintAmount = BackgroundSections.Sum(s => s.ColorStrength); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/Ruins/RuinGenerator.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/Ruins/RuinGenerator.cs index 6238f2d5d..bac02e6b7 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/Ruins/RuinGenerator.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/Ruins/RuinGenerator.cs @@ -7,14 +7,9 @@ namespace Barotrauma.RuinGeneration { public void DebugDraw(SpriteBatch spriteBatch) { - foreach (RuinShape shape in allShapes) - { - GUI.DrawString(spriteBatch, new Vector2(shape.Center.X, -shape.Center.Y - 50), shape.DistanceFromEntrance.ToString(), Color.White, Color.Black * 0.5f, font: GUI.LargeFont); - } - foreach (Line line in walls) - { - GUI.DrawLine(spriteBatch, new Vector2(line.A.X, -line.A.Y), new Vector2(line.B.X, -line.B.Y), GUI.Style.Red, 0.0f, 10); - } + Rectangle drawRect = Area; + drawRect.Y = -drawRect.Y - Area.Height; + GUI.DrawRectangle(spriteBatch, drawRect, Color.Cyan, false, 0, 6); } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/ConvexHull.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/ConvexHull.cs index 5c2fa3df5..eccf97ae5 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/ConvexHull.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/ConvexHull.cs @@ -846,16 +846,15 @@ namespace Barotrauma.Lights if (chList.Submarine == null) { list.AddRange(chList.List.FindAll(ch => MathUtils.CircleIntersectsRectangle(lightPos, range, ch.BoundingBox))); - } //light is outside, convexhull inside a sub else { Rectangle subBorders = chList.Submarine.Borders; subBorders.Y -= chList.Submarine.Borders.Height; - if (!MathUtils.CircleIntersectsRectangle(lightPos - chList.Submarine.WorldPosition, range, subBorders)) continue; + if (!MathUtils.CircleIntersectsRectangle(lightPos - chList.Submarine.WorldPosition, range, subBorders)) { continue; } - lightPos -= (chList.Submarine.WorldPosition - chList.Submarine.HiddenSubPosition); + lightPos -= chList.Submarine.WorldPosition - chList.Submarine.HiddenSubPosition; list.AddRange(chList.List.FindAll(ch => MathUtils.CircleIntersectsRectangle(lightPos, range, ch.BoundingBox))); } @@ -865,14 +864,6 @@ namespace Barotrauma.Lights //light is inside, convexhull outside if (chList.Submarine == null) { - lightPos += (ParentSub.WorldPosition - ParentSub.HiddenSubPosition); - HashSet visibleRuins = new HashSet(); - foreach (RuinGeneration.Ruin ruin in Level.Loaded.Ruins) - { - if (!MathUtils.CircleIntersectsRectangle(lightPos, range, ruin.Area)) { continue; } - visibleRuins.Add(ruin); - } - list.AddRange(chList.List.FindAll(ch => ch.ParentEntity?.ParentRuin != null && visibleRuins.Contains(ch.ParentEntity.ParentRuin))); continue; } //light and convexhull are both inside the same sub diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightManager.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightManager.cs index bb8e93052..8ad1a0596 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightManager.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightManager.cs @@ -126,8 +126,8 @@ namespace Barotrauma.Lights } LosTexture?.Dispose(); - LosTexture = new RenderTarget2D(graphics, - (int)(GameMain.GraphicsWidth * GameMain.Config.LightMapScale), + LosTexture = new RenderTarget2D(graphics, + (int)(GameMain.GraphicsWidth * GameMain.Config.LightMapScale), (int)(GameMain.GraphicsHeight * GameMain.Config.LightMapScale), false, SurfaceFormat.Color, DepthFormat.None); } @@ -183,7 +183,7 @@ namespace Barotrauma.Lights activeLights.Clear(); foreach (LightSource light in lights) { - if (!light.Enabled) { continue; } + if (!light.Enabled) { continue; } if ((light.Color.A < 1 || light.Range < 1.0f) && !light.LightSourceParams.OverrideLightSpriteAlpha.HasValue) { continue; } if (light.ParentBody != null) { @@ -197,7 +197,9 @@ namespace Barotrauma.Lights float spriteRange = Math.Max( light.LightSprite.size.X * light.SpriteScale.X * (0.5f + Math.Abs(light.LightSprite.RelativeOrigin.X - 0.5f)), light.LightSprite.size.Y * light.SpriteScale.Y * (0.5f + Math.Abs(light.LightSprite.RelativeOrigin.Y - 0.5f))); - range = Math.Max(spriteRange, range); + + float targetSize = Math.Max(light.LightTextureTargetSize.X, light.LightTextureTargetSize.Y); + range = Math.Max(Math.Max(spriteRange, targetSize), range); } if (!MathUtils.CircleIntersectsRectangle(light.WorldPosition, range, viewRect)) { continue; } activeLights.Add(light); @@ -238,16 +240,16 @@ namespace Barotrauma.Lights //draw a black rectangle on hulls to hide background lights behind subs //--------------------------------------------------------------------------------------------------- - if (backgroundObstructor != null) + /*if (backgroundObstructor != null) { spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied); spriteBatch.Draw(backgroundObstructor, new Rectangle(0, 0, (int)(GameMain.GraphicsWidth * currLightMapScale), (int)(GameMain.GraphicsHeight * currLightMapScale)), Color.Black); spriteBatch.End(); - } + }*/ spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Opaque, transformMatrix: spriteBatchTransform); - Dictionary visibleHulls = GetVisibleHulls(cam); + Dictionary visibleHulls = GetVisibleHulls(cam); foreach (KeyValuePair hull in visibleHulls) { GUI.DrawRectangle(spriteBatch, @@ -258,18 +260,18 @@ namespace Barotrauma.Lights spriteBatch.End(); SolidColorEffect.CurrentTechnique = SolidColorEffect.Techniques["SolidColor"]; - SolidColorEffect.Parameters["color"].SetValue(AmbientLight.ToVector4()); + SolidColorEffect.Parameters["color"].SetValue(AmbientLight.Opaque().ToVector4()); spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, transformMatrix: spriteBatchTransform, effect: SolidColorEffect); Submarine.DrawDamageable(spriteBatch, null); spriteBatch.End(); graphics.BlendState = BlendState.Additive; - + //draw the focused item and character to highlight them, //and light sprites (done before drawing the actual light volumes so we can make characters obstruct the highlights and sprites) //--------------------------------------------------------------------------------------------------- - spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive, transformMatrix: spriteBatchTransform); + spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive, transformMatrix: spriteBatchTransform); foreach (LightSource light in activeLights) { //don't draw limb lights at this point, they need to be drawn after lights have been obstructed by characters @@ -294,8 +296,8 @@ namespace Barotrauma.Lights { if (character.CurrentHull == null || !character.Enabled || !character.IsVisible) { continue; } if (Character.Controlled?.FocusedCharacter == character) { continue; } - Color lightColor = character.CurrentHull.AmbientLight == Color.TransparentBlack ? - Color.Black : + Color lightColor = character.CurrentHull.AmbientLight == Color.TransparentBlack ? + Color.Black : character.CurrentHull.AmbientLight.Multiply(character.CurrentHull.AmbientLight.A / 255.0f).Opaque(); foreach (Limb limb in character.AnimController.Limbs) { @@ -304,7 +306,7 @@ namespace Barotrauma.Lights } } spriteBatch.End(); - + DeformableSprite.Effect.CurrentTechnique = DeformableSprite.Effect.Techniques["DeformShaderSolidVertexColor"]; DeformableSprite.Effect.CurrentTechnique.Passes[0].Apply(); spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, transformMatrix: spriteBatchTransform); @@ -312,8 +314,8 @@ namespace Barotrauma.Lights { if (character.CurrentHull == null || !character.Enabled || !character.IsVisible) { continue; } if (Character.Controlled?.FocusedCharacter == character) { continue; } - Color lightColor = character.CurrentHull.AmbientLight == Color.TransparentBlack ? - Color.Black : + Color lightColor = character.CurrentHull.AmbientLight == Color.TransparentBlack ? + Color.Black : character.CurrentHull.AmbientLight.Multiply(character.CurrentHull.AmbientLight.A / 255.0f).Opaque(); foreach (Limb limb in character.AnimController.Limbs) { @@ -343,9 +345,9 @@ namespace Barotrauma.Lights } lightEffect.World = transform; - + GameMain.ParticleManager.Draw(spriteBatch, false, null, Particles.ParticleBlendState.Additive); - + if (Character.Controlled != null) { DrawHalo(Character.Controlled); @@ -412,7 +414,7 @@ namespace Barotrauma.Lights } } if (highlightedEntities.Count == 0) { return false; } - + //draw characters in light blue first graphics.SetRenderTarget(HighlightMap); SolidColorEffect.CurrentTechnique = SolidColorEffect.Techniques["SolidColor"]; @@ -484,9 +486,9 @@ namespace Barotrauma.Lights //raster pattern on top of everything spriteBatch.Begin(blendState: BlendState.NonPremultiplied, samplerState: SamplerState.LinearWrap); - spriteBatch.Draw(highlightRaster, - new Rectangle(0, 0, HighlightMap.Width, HighlightMap.Height), - new Rectangle(0, 0, (int)(HighlightMap.Width / currLightMapScale * 0.5f), (int)(HighlightMap.Height / currLightMapScale * 0.5f)), + spriteBatch.Draw(highlightRaster, + new Rectangle(0, 0, HighlightMap.Width, HighlightMap.Height), + new Rectangle(0, 0, (int)(HighlightMap.Width / currLightMapScale * 0.5f), (int)(HighlightMap.Height / currLightMapScale * 0.5f)), Color.White * 0.5f); spriteBatch.End(); @@ -542,7 +544,7 @@ namespace Barotrauma.Lights { graphics.Clear(Color.White); } - + //-------------------------------------- @@ -595,9 +597,9 @@ namespace Barotrauma.Lights } } } - graphics.SetRenderTarget(null); + graphics.SetRenderTarget(null); } - + public void ClearLights() { lights.Clear(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightSource.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightSource.cs index 2e44b1875..a47285cca 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightSource.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightSource.cs @@ -104,13 +104,13 @@ namespace Barotrauma.Lights blinkFrequency = MathHelper.Clamp(value, 0.0f, 60.0f); } } - + public float TextureRange { get; private set; } - + public Sprite OverrideLightTexture { get; @@ -137,7 +137,7 @@ namespace Barotrauma.Lights public LightSourceParams(XElement element) { Deserialize(element); - + foreach (XElement subElement in element.Elements()) { switch (subElement.Name.ToString().ToLowerInvariant()) @@ -206,9 +206,9 @@ namespace Barotrauma.Lights private short[] indices; private List hullsInRange; - + public Texture2D texture; - + public SpriteEffects LightSpriteEffect; public Submarine ParentSub; @@ -224,7 +224,7 @@ namespace Barotrauma.Lights private float prevCalculatedRange; private Vector2 prevCalculatedPosition; - //do we need to recheck which convex hulls are within range + //do we need to recheck which convex hulls are within range //(e.g. position or range of the lightsource has changed) public bool NeedsHullCheck = true; //do we need to recalculate the vertices of the light volume @@ -278,7 +278,7 @@ namespace Barotrauma.Lights translateVertices = position - prevCalculatedPosition; return; } - + NeedsHullCheck = true; NeedsRecalculation = true; } @@ -360,7 +360,7 @@ namespace Barotrauma.Lights get; private set; } - + public float Range { get { return lightSourceParams.Range; } @@ -369,13 +369,29 @@ namespace Barotrauma.Lights lightSourceParams.Range = value; if (Math.Abs(prevCalculatedRange - lightSourceParams.Range) < 10.0f) return; - + NeedsHullCheck = true; NeedsRecalculation = true; prevCalculatedRange = lightSourceParams.Range; } } + private Vector2 lightTextureTargetSize; + + public Vector2 LightTextureTargetSize + { + get => lightTextureTargetSize; + set + { + NeedsRecalculation = true; + NeedsHullCheck = true; + lightTextureTargetSize = value; + } + } + + public Vector2 LightTextureOffset { get; set; } + public Vector2 LightTextureScale { get; set; } = Vector2.One; + public float TextureRange { get @@ -386,7 +402,7 @@ namespace Barotrauma.Lights /// /// Background lights are drawn behind submarines and they don't cast shadows. - /// + /// public bool IsBackground { get; @@ -462,7 +478,7 @@ namespace Barotrauma.Lights this.ParentSub = submarine; this.position = position; lightSourceParams = new LightSourceParams(range, color); - CastShadows = true; + CastShadows = true; texture = LightTexture; diffToSub = new Dictionary(); if (addLight) { GameMain.LightManager.AddLight(this); } @@ -494,7 +510,7 @@ namespace Barotrauma.Lights } CurrentBrightness = brightness; } - + /// /// Update the contents of ConvexHullList and check if we need to recalculate vertices /// @@ -509,7 +525,7 @@ namespace Barotrauma.Lights } /// - /// Recheck which convex hulls are in range (if needed), + /// Recheck which convex hulls are in range (if needed), /// and check if we need to recalculate vertices due to changes in the convex hulls /// private void CheckHullsInRange() @@ -561,20 +577,20 @@ namespace Barotrauma.Lights chList.List.Clear(); continue; } - + RefreshConvexHullList(chList, lightPos, sub); } } - else + else { //light is inside, convexhull outside if (sub == null) continue; - + //light and convexhull are both inside the same sub if (sub == ParentSub) { if (NeedsHullCheck) - { + { RefreshConvexHullList(chList, lightPos, sub); } } @@ -582,7 +598,7 @@ namespace Barotrauma.Lights else { if (sub.DockedTo.Contains(ParentSub) && !NeedsHullCheck) continue; - + lightPos -= (sub.Position - ParentSub.Position); Rectangle subBorders = sub.Borders; @@ -642,7 +658,7 @@ namespace Barotrauma.Lights foreach (ConvexHull hull in hulls) { hull.RefreshWorldPositions(); - hull.GetVisibleSegments(drawPos, visibleSegments, ignoreEdges: false); + hull.GetVisibleSegments(drawPos, visibleSegments, ignoreEdges: false); } //add a square-shaped boundary to make sure we've got something to construct the triangles from @@ -829,13 +845,13 @@ namespace Barotrauma.Lights if (intersection2.index < 0) return null; Segment seg1 = visibleSegments[intersection1.index]; Segment seg2 = visibleSegments[intersection2.index]; - + bool isPoint1 = MathUtils.LineToPointDistanceSquared(seg1.Start.WorldPos, seg1.End.WorldPos, p.WorldPos) < 25.0f; bool isPoint2 = MathUtils.LineToPointDistanceSquared(seg2.Start.WorldPos, seg2.End.WorldPos, p.WorldPos) < 25.0f; if (isPoint1 && isPoint2) { - //hit at the current segmentpoint -> place the segmentpoint into the list + //hit at the current segmentpoint -> place the segmentpoint into the list output.Add(p.WorldPos); foreach (ConvexHullList hullList in hullsInRange) @@ -938,7 +954,7 @@ namespace Barotrauma.Lights segment = i; } } - + return (segment, closestIntersection == null ? rayEnd : (Vector2)closestIntersection); } @@ -968,8 +984,8 @@ namespace Barotrauma.Lights overrideTextureDims = new Vector2(OverrideLightTexture.SourceRect.Width, OverrideLightTexture.SourceRect.Height); Vector2 origin = OverrideLightTextureOrigin; - if (LightSpriteEffect == SpriteEffects.FlipHorizontally) - { + if (LightSpriteEffect == SpriteEffects.FlipHorizontally) + { origin.X = OverrideLightTexture.SourceRect.Width - origin.X; cosAngle = -cosAngle; sinAngle = -sinAngle; @@ -981,7 +997,7 @@ namespace Barotrauma.Lights // Add a vertex for the center of the mesh vertices[0] = new VertexPositionColorTexture(new Vector3(position.X, position.Y, 0), Color.White, GetUV(new Vector2(0.5f, 0.5f) + uvOffset, LightSpriteEffect)); - + //hacky fix to exc excessively large light volumes (they used to be up to 4x the range of the light if there was nothing to block the rays). //might want to tweak the raycast logic in a way that this isn't necessary /*float boundRadius = Range * 1.1f / (1.0f - Math.Max(Math.Abs(uvOffset.X), Math.Abs(uvOffset.Y))); @@ -999,7 +1015,7 @@ namespace Barotrauma.Lights for (int i = 0; i < rayCastHits.Count; i++) { Vector2 vertex = rayCastHits[i]; - + //we'll use the previous and next vertices to calculate the normals //of the two segments this vertex belongs to //so we can add new vertices based on these normals @@ -1007,7 +1023,7 @@ namespace Barotrauma.Lights Vector2 nextVertex = rayCastHits[i < rayCastHits.Count - 1 ? i + 1 : 0]; Vector2 rawDiff = vertex - drawPos; - + //calculate normal of first segment Vector2 nDiff1 = vertex - nextVertex; float tx = nDiff1.X; nDiff1.X = -nDiff1.Y; nDiff1.Y = tx; @@ -1015,7 +1031,7 @@ namespace Barotrauma.Lights //if the normal is pointing towards the light origin //rather than away from it, invert it if (Vector2.DistanceSquared(nDiff1, rawDiff) > Vector2.DistanceSquared(-nDiff1, rawDiff)) nDiff1 = -nDiff1; - + //calculate normal of second segment Vector2 nDiff2 = prevVertex - vertex; tx = nDiff2.X; nDiff2.X = -nDiff2.Y; nDiff2.Y = tx; @@ -1112,13 +1128,13 @@ namespace Barotrauma.Lights static Vector2 GetUV(Vector2 vert, SpriteEffects effects) { - if (effects == SpriteEffects.FlipHorizontally) - { - vert.X = 1.0f - vert.X; + if (effects == SpriteEffects.FlipHorizontally) + { + vert.X = 1.0f - vert.X; } - else if (effects == SpriteEffects.FlipVertically) - { - vert.Y = 1.0f - vert.Y; + else if (effects == SpriteEffects.FlipVertically) + { + vert.Y = 1.0f - vert.Y; } else if (effects == (SpriteEffects.FlipHorizontally | SpriteEffects.FlipVertically)) { @@ -1228,10 +1244,19 @@ namespace Barotrauma.Lights } drawPos.Y = -drawPos.Y; - LightSprite.Draw( - spriteBatch, drawPos, - new Color(Color, (lightSourceParams.OverrideLightSpriteAlpha ?? Color.A / 255.0f) * CurrentBrightness), - origin, -Rotation + MathHelper.ToRadians(LightSourceParams.Rotation), SpriteScale, LightSpriteEffect); + Color color = new Color(Color, (lightSourceParams.OverrideLightSpriteAlpha ?? Color.A / 255.0f) * CurrentBrightness); + + if (LightTextureTargetSize != Vector2.Zero) + { + LightSprite.DrawTiled(spriteBatch, drawPos, LightTextureTargetSize, color, startOffset: LightTextureOffset, textureScale: LightTextureScale); + } + else + { + LightSprite.Draw( + spriteBatch, drawPos, + color, + origin, -Rotation + MathHelper.ToRadians(LightSourceParams.Rotation), SpriteScale, LightSpriteEffect); + } } if (GameMain.DebugDraw && Screen.Selected.Cam.Zoom > 0.1f) @@ -1255,7 +1280,7 @@ namespace Barotrauma.Lights GUI.DrawLine(spriteBatch, drawPos - Vector2.One * Range, drawPos + Vector2.One * Range, Color); GUI.DrawLine(spriteBatch, drawPos - new Vector2(1.0f, -1.0f) * Range, drawPos + new Vector2(1.0f, -1.0f) * Range, Color); } - } + } } public void CheckConditionals() @@ -1280,10 +1305,10 @@ namespace Barotrauma.Lights if (!CastShadows) { Texture2D currentTexture = texture ?? LightTexture; - if (OverrideLightTexture != null) { currentTexture = OverrideLightTexture.Texture; } + if (OverrideLightTexture != null) { currentTexture = OverrideLightTexture.Texture; } - Vector2 center = OverrideLightTexture == null ? - new Vector2(currentTexture.Width / 2, currentTexture.Height / 2) : + Vector2 center = OverrideLightTexture == null ? + new Vector2(currentTexture.Width / 2, currentTexture.Height / 2) : OverrideLightTexture.Origin; float scale = Range / (currentTexture.Width / 2.0f); @@ -1291,7 +1316,7 @@ namespace Barotrauma.Lights if (ParentSub != null) { drawPos += ParentSub.DrawPosition; } drawPos.Y = -drawPos.Y; - spriteBatch.Draw(currentTexture, drawPos, null, Color.Multiply(CurrentBrightness), -rotation, center, scale, SpriteEffects.None, 1); + spriteBatch.Draw(currentTexture, drawPos, null, Color.Multiply(CurrentBrightness), -rotation + MathHelper.ToRadians(LightSourceParams.Rotation), center, scale, SpriteEffects.None, 1); return; } @@ -1317,8 +1342,8 @@ namespace Barotrauma.Lights Vector2 offset = ParentSub == null ? Vector2.Zero : ParentSub.DrawPosition; lightEffect.World = - Matrix.CreateTranslation(-new Vector3(position, 0.0f)) * - Matrix.CreateRotationZ(rotateVertices) * + Matrix.CreateTranslation(-new Vector3(position, 0.0f)) * + Matrix.CreateRotationZ(rotateVertices - MathHelper.ToRadians(LightSourceParams.Rotation)) * Matrix.CreateTranslation(new Vector3(position + offset + translateVertices, 0.0f)) * transform; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Map/Map.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Map/Map.cs index 7a29743ad..91eb1ce2a 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Map/Map.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Map/Map.cs @@ -265,7 +265,7 @@ namespace Barotrauma if (!currentDisplayLocation.Discovered) { RemoveFogOfWar(currentDisplayLocation); - currentDisplayLocation.Discovered = true; + currentDisplayLocation.Discover(); if (currentDisplayLocation.MapPosition.X > furthestDiscoveredLocation.MapPosition.X) { furthestDiscoveredLocation = currentDisplayLocation; @@ -426,7 +426,7 @@ namespace Barotrauma Level.Loaded.DebugSetStartLocation(CurrentLocation); Level.Loaded.DebugSetEndLocation(null); - CurrentLocation.Discovered = true; + CurrentLocation.Discover(); OnLocationChanged?.Invoke(prevLocation, CurrentLocation); SelectLocation(-1); if (GameMain.Client == null) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/MapEntity.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/MapEntity.cs index 9fad6fc1c..642813f4f 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/MapEntity.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/MapEntity.cs @@ -6,6 +6,7 @@ using Microsoft.Xna.Framework.Input; using System; using System.Collections.Generic; using System.Linq; +using Barotrauma.Lights; namespace Barotrauma { @@ -17,7 +18,7 @@ namespace Barotrauma private static Vector2 startMovingPos = Vector2.Zero; private static float keyDelay; - + public static Vector2 StartMovingPos => startMovingPos; public event Action Resized; @@ -97,13 +98,13 @@ namespace Barotrauma /// public float GetDrawDepth(float baseDepth, Sprite sprite) { - float depth = baseDepth + float depth = baseDepth //take texture into account to get entities with (roughly) the same base depth and texture to render consecutively to minimize texture swaps + (sprite?.Texture?.SortingKey ?? 0) % 100 * 0.00001f + ID % 100 * 0.000001f; return Math.Min(depth, 1.0f); } - + /// /// Update the selection logic in submarine editor /// @@ -218,7 +219,7 @@ namespace Barotrauma } } } - } + } Vector2 position = cam.ScreenToWorld(PlayerInput.MousePosition); MapEntity highLightedEntity = null; @@ -284,13 +285,13 @@ namespace Barotrauma //mouse released -> move the entities to the new position of the mouse Vector2 moveAmount = position - startMovingPos; - + if (!isShiftDown) { moveAmount.X = (float)(moveAmount.X > 0.0f ? Math.Floor(moveAmount.X / Submarine.GridSize.X) : Math.Ceiling(moveAmount.X / Submarine.GridSize.X)) * Submarine.GridSize.X; moveAmount.Y = (float)(moveAmount.Y > 0.0f ? Math.Floor(moveAmount.Y / Submarine.GridSize.Y) : Math.Ceiling(moveAmount.Y / Submarine.GridSize.Y)) * Submarine.GridSize.Y; } - + if (Math.Abs(moveAmount.X) >= Submarine.GridSize.X || Math.Abs(moveAmount.Y) >= Submarine.GridSize.Y || isShiftDown) { if (!isShiftDown) { moveAmount = Submarine.VectorToWorldGrid(moveAmount); } @@ -321,10 +322,10 @@ namespace Barotrauma else { SoundPlayer.PlayUISound(GUISoundType.PickItemFail); - } + } } } - + SubEditorScreen.StoreCommand(new TransformCommand(new List(SelectedList),SelectedList.Select(entity => entity.Rect).ToList(), oldRects, false)); if (deposited.Any() && deposited.Any(entity => entity is Item)) { @@ -423,20 +424,24 @@ namespace Barotrauma //select wire if both items it's connected to are selected var selectedItems = SelectedList.Where(e => e is Item).Cast().ToList(); - foreach (Item item in selectedItems) + foreach (Item item in Item.ItemList) { - if (item.Connections == null) continue; - foreach (Connection c in item.Connections) - { - foreach (Wire w in c.Wires) - { - if (w == null || SelectedList.Contains(w.Item)) continue; + var wire = item.GetComponent(); + if (wire == null) { continue; } + Item item0 = wire.Connections[0]?.Item; + Item item1 = wire.Connections[1]?.Item; - if (w.OtherConnection(c) != null && SelectedList.Contains(w.OtherConnection(c).Item)) - { - SelectedList.Add(w.Item); - } - } + if (item0 == null && item1 != null) + { + item0 = Item.ItemList.Find(it => it.GetComponent()?.DisconnectedWires.Contains(wire) ?? false); + } + else if (item0 != null && item1 == null) + { + item1 = Item.ItemList.Find(it => it.GetComponent()?.DisconnectedWires.Contains(wire) ?? false); + } + if (item0 != null && item1 != null && SelectedList.Contains(item0) && SelectedList.Contains(item1)) + { + SelectedList.Add(item); } } @@ -453,8 +458,8 @@ namespace Barotrauma { if (PlayerInput.PrimaryMouseButtonHeld() && PlayerInput.KeyUp(Keys.Space) && - PlayerInput.KeyUp(Keys.LeftAlt) && - PlayerInput.KeyUp(Keys.RightAlt) && + PlayerInput.KeyUp(Keys.LeftAlt) && + PlayerInput.KeyUp(Keys.RightAlt) && (highlightedListBox == null || (GUI.MouseOn != highlightedListBox && !highlightedListBox.IsParentOf(GUI.MouseOn)))) { //if clicking a selected entity, start moving it @@ -482,7 +487,7 @@ namespace Barotrauma int xKeysDown = (left + right); int yKeysDown = (up + down); - + if (xKeysDown != 0 || yKeysDown != 0) { keyDelay += (float) Timing.Step; } else { keyDelay = 0; } @@ -512,7 +517,7 @@ namespace Barotrauma bool isShiftDown = PlayerInput.IsShiftDown(); if (!isShiftDown) return null; - + foreach (MapEntity e in mapEntityList) { if (!e.SelectableInEditor ||!(e is Item potentialContainer)) { continue; } @@ -662,7 +667,7 @@ namespace Barotrauma { if (SelectedList.Contains(entity)) { return; } SelectedList.Add(entity); - HandleDoorGapLinks(entity, + HandleDoorGapLinks(entity, onGapFound: (door, gap) => { door.RefreshLinkedGap(); @@ -670,8 +675,8 @@ namespace Barotrauma { SelectedList.Add(gap); } - }, - onDoorFound: (door, gap) => + }, + onDoorFound: (door, gap) => { if (!SelectedList.Contains(door.Item)) { @@ -715,7 +720,7 @@ namespace Barotrauma onGapFound: (door, gap) => SelectedList.Remove(gap), onDoorFound: (door, gap) => SelectedList.Remove(door.Item)); } - + static partial void UpdateAllProjSpecific(float deltaTime) { var entitiesToRender = Submarine.VisibleEntities ?? mapEntityList; @@ -748,7 +753,7 @@ namespace Barotrauma moveAmount.Y = -moveAmount.Y; bool isShiftDown = PlayerInput.IsShiftDown(); - + if (!isShiftDown) { moveAmount.X = (float)(moveAmount.X > 0.0f ? Math.Floor(moveAmount.X / Submarine.GridSize.X) : Math.Ceiling(moveAmount.X / Submarine.GridSize.X)) * Submarine.GridSize.X; @@ -761,21 +766,21 @@ namespace Barotrauma foreach (MapEntity e in SelectedList) { SpriteEffects spriteEffects = SpriteEffects.None; - switch (e) + switch (e) { - case Item item: + case Item item: { if (item.FlippedX && item.Prefab.CanSpriteFlipX) spriteEffects ^= SpriteEffects.FlipHorizontally; if (item.flippedY && item.Prefab.CanSpriteFlipY) spriteEffects ^= SpriteEffects.FlipVertically; break; } - case Structure structure: + case Structure structure: { if (structure.FlippedX && structure.Prefab.CanSpriteFlipX) spriteEffects ^= SpriteEffects.FlipHorizontally; if (structure.flippedY && structure.Prefab.CanSpriteFlipY) spriteEffects ^= SpriteEffects.FlipVertically; break; } - case WayPoint wayPoint: + case WayPoint wayPoint: { Vector2 drawPos = e.WorldPosition; drawPos.Y = -drawPos.Y; @@ -812,7 +817,7 @@ namespace Barotrauma posY = -posY; - Vector2[] corners = + Vector2[] corners = { new Vector2(posX, posY), new Vector2(posX + sizeX, posY), @@ -878,7 +883,7 @@ namespace Barotrauma { MapEntity firstSelected = SelectedList.First(); - float minX = firstSelected.WorldRect.X, + float minX = firstSelected.WorldRect.X, maxX = firstSelected.WorldRect.Right; foreach (MapEntity entity in SelectedList) @@ -903,7 +908,7 @@ namespace Barotrauma foreach (MapEntity entity in SelectedList) { - + minY = Math.Min(minY, entity.WorldRect.Y - entity.WorldRect.Height); maxY = Math.Max(maxY, entity.WorldRect.Y); } @@ -943,21 +948,21 @@ namespace Barotrauma } /// - /// Copy the selected entities to the "clipboard" (copiedList) + /// Copy the selected entities to the "clipboard" (copiedList) /// public static void Copy(List entities) { if (entities.Count == 0) { return; } CopyEntities(entities); } - + /// /// Copy the entities to the "clipboard" (copiedList) and delete them /// public static void Cut(List entities) { if (entities.Count == 0) { return; } - + CopyEntities(entities); SubEditorScreen.StoreCommand(new AddOrDeleteCommand(new List(entities), true)); @@ -989,6 +994,10 @@ namespace Barotrauma clone.Move(moveAmount); clone.Submarine = Submarine.MainSub; } + foreach (MapEntity clone in SelectedList) + { + (clone as Item)?.GetComponent()?.SetContainedItemPositions(); + } SubEditorScreen.StoreCommand(new AddOrDeleteCommand(clones, false, handleInventoryBehavior: false)); } @@ -1012,9 +1021,9 @@ namespace Barotrauma return newEntities; } - public virtual void AddToGUIUpdateList() + public virtual void AddToGUIUpdateList(int order = 0) { - if (editingHUD != null && editingHUD.UserData == this) editingHUD.AddToGUIUpdateList(); + if (editingHUD != null && editingHUD.UserData == this) { editingHUD.AddToGUIUpdateList(order: order); } } public virtual void UpdateEditing(Camera cam, float deltaTime) { } @@ -1049,7 +1058,7 @@ namespace Barotrauma editingHUD.RectTransform.Resize( new Point( - editingHUD.RectTransform.NonScaledSize.X, + editingHUD.RectTransform.NonScaledSize.X, MathHelper.Clamp(contentHeight + padding * 2, 50, maxHeight)), resizeChildren: false); listBox.RectTransform.Resize(new Point(listBox.RectTransform.NonScaledSize.X, editingHUD.RectTransform.NonScaledSize.Y - padding * 2), resizeChildren: false); } @@ -1089,7 +1098,7 @@ namespace Barotrauma { prevRect = new Rectangle(Rect.Location, Rect.Size); } - + Vector2 placePosition = new Vector2(rect.X, rect.Y); Vector2 placeSize = new Vector2(rect.Width, rect.Height); @@ -1140,6 +1149,15 @@ namespace Barotrauma var oldData = new List { prevRect.Value }; SubEditorScreen.StoreCommand(new TransformCommand(new List { this }, newData, oldData, true)); } + + if (this is Structure structure) + { + foreach (LightSource light in structure.Lights) + { + light.LightTextureTargetSize = Rect.Size.ToVector2(); + light.Position = rect.Location.ToVector2(); + } + } prevRect = null; } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Structure.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Structure.cs index 219b290af..b3b2a653f 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Structure.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Structure.cs @@ -14,12 +14,14 @@ namespace Barotrauma { partial class Structure : MapEntity, IDamageable, IServerSerializable { - public static bool ShowWalls = true, ShowStructures = true; + public static bool ShowWalls = true, ShowStructures = true; private List convexHulls; private readonly Dictionary spriteAnimState = new Dictionary(); + public readonly List Lights = new List(); + public override bool SelectableInEditor { get @@ -41,7 +43,7 @@ namespace Barotrauma { get; set; - } + } partial void InitProjSpecific() { @@ -88,7 +90,23 @@ namespace Barotrauma if (editingHUD == null || editingHUD.UserData as Structure != this) { editingHUD = CreateEditingHUD(Screen.Selected != GameMain.SubEditorScreen); - } + } + } + + private void SetLightTextureOffset() + { + Vector2 textOffset = textureOffset; + if (FlippedX) { textOffset.X = -textOffset.X; } + if (FlippedY) { textOffset.Y = -textOffset.Y; } + + foreach (LightSource light in Lights) + { + Vector2 bgOffset = new Vector2( + MathUtils.PositiveModulo((int)-textOffset.X, light.texture.Width), + MathUtils.PositiveModulo((int)-textOffset.Y, light.texture.Height)); + + light.LightTextureOffset = bgOffset; + } } public GUIComponent CreateEditingHUD(bool inGame = false) @@ -175,12 +193,12 @@ namespace Barotrauma buttonContainer.RectTransform.IsFixedSize = true; GUITextBlock.AutoScaleAndNormalize(buttonContainer.Children.Where(c => c is GUIButton).Select(b => ((GUIButton)b).TextBlock)); editor.AddCustomContent(buttonContainer, editor.ContentCount); - + PositionEditingHUD(); return editingHUD; } - + partial void OnImpactProjSpecific(Fixture f1, Fixture f2, Contact contact) { if (!Prefab.Platform && Prefab.StairDirection == Direction.None) @@ -261,19 +279,21 @@ namespace Barotrauma else if (HiddenInGame) { return; } Color color = IsIncludedInSelection && editing ? GUI.Style.Blue : IsHighlighted ? GUI.Style.Orange * Math.Max(spriteColor.A / (float) byte.MaxValue, 0.1f) : spriteColor; - + if (IsSelected && editing) { //color = Color.Lerp(color, Color.Gold, 0.5f); color = spriteColor; + + Vector2 rectSize = rect.Size.ToVector2(); if (BodyWidth > 0.0f) { rectSize.X = BodyWidth; } if (BodyHeight > 0.0f) { rectSize.Y = BodyHeight; } Vector2 bodyPos = WorldPosition + BodyOffset; - GUI.DrawRectangle(spriteBatch, new Vector2(bodyPos.X, -bodyPos.Y), rectSize.X, rectSize.Y, BodyRotation, Color.White, + GUI.DrawRectangle(spriteBatch, new Vector2(bodyPos.X, -bodyPos.Y), rectSize.X, rectSize.Y, BodyRotation, Color.White, thickness: Math.Max(1, (int)(2 / Screen.Selected.Cam.Zoom))); } @@ -305,14 +325,14 @@ namespace Barotrauma } else { - dropShadowOffset = IsHorizontal ? - new Vector2(0.0f, Math.Sign(Submarine.HiddenSubPosition.Y - Position.Y) * 10.0f) : + dropShadowOffset = IsHorizontal ? + new Vector2(0.0f, Math.Sign(Submarine.HiddenSubPosition.Y - Position.Y) * 10.0f) : new Vector2(Math.Sign(Submarine.HiddenSubPosition.X - Position.X) * 10.0f, 0.0f); } } dropShadowOffset.Y = -dropShadowOffset.Y; } - + SpriteEffects oldEffects = Prefab.BackgroundSprite.effects; Prefab.BackgroundSprite.effects ^= SpriteEffects; @@ -372,13 +392,13 @@ namespace Barotrauma if (!HasDamage && i == 0) { drawSection = new Rectangle( - drawSection.X, - drawSection.Y, + drawSection.X, + drawSection.Y, Sections[Sections.Length -1 ].rect.Right - drawSection.X, drawSection.Y - (Sections[Sections.Length - 1].rect.Y - Sections[Sections.Length - 1].rect.Height)); i = Sections.Length; } - + Vector2 sectionOffset = new Vector2( Math.Abs(rect.Location.X - drawSection.Location.X), Math.Abs(rect.Location.Y - drawSection.Location.Y)); @@ -496,7 +516,7 @@ namespace Barotrauma } else { - if (!conditional.Matches(this)) { return false; } + if (!conditional.Matches(this)) { return false; } } return true; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Submarine.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Submarine.cs index 28e2bbdb2..6d4db8e52 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Submarine.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Submarine.cs @@ -7,6 +7,7 @@ using Microsoft.Xna.Framework.Graphics; using System; using System.Collections; using System.Collections.Generic; +using System.Collections.Immutable; using Barotrauma.IO; using System.Linq; using System.Xml.Linq; @@ -177,7 +178,6 @@ namespace Barotrauma //drawing ---------------------------------------------------- private static readonly HashSet visibleSubs = new HashSet(); - private static readonly HashSet visibleRuins = new HashSet(); public static void CullEntities(Camera cam) { visibleSubs.Clear(); @@ -197,24 +197,6 @@ namespace Barotrauma } } - visibleRuins.Clear(); - if (Level.Loaded != null) - { - foreach (Ruin ruin in Level.Loaded.Ruins) - { - Rectangle worldBorders = new Rectangle( - ruin.Area.X - 500, - ruin.Area.Y + ruin.Area.Height + 500, - ruin.Area.Width + 1000, - ruin.Area.Height + 1000); - - if (RectsOverlap(worldBorders, cam.WorldView)) - { - visibleRuins.Add(ruin); - } - } - } - if (visibleEntities == null) { visibleEntities = new List(MapEntity.mapEntityList.Count); @@ -231,10 +213,6 @@ namespace Barotrauma { if (!visibleSubs.Contains(entity.Submarine)) { continue; } } - else if (entity.ParentRuin != null) - { - if (!visibleRuins.Contains(entity.ParentRuin)) { continue; } - } if (entity.IsVisible(worldView)) { visibleEntities.Add(entity); } } @@ -325,7 +303,7 @@ namespace Barotrauma depthSortedDamageable.Insert(i, structure); } } - + foreach (Structure s in depthSortedDamageable) { s.DrawDamage(spriteBatch, damageEffect, editing); @@ -403,6 +381,8 @@ namespace Barotrauma } } + // TODO remove + [Obsolete("Use MiniMap.CreateMiniMap()")] public void CreateMiniMap(GUIComponent parent, IEnumerable pointsOfInterest = null, bool ignoreOutpost = false) { Rectangle worldBorders = GetDockedBorders(); @@ -415,26 +395,124 @@ namespace Barotrauma float scale = 0.9f; GUIFrame hullContainer = new GUIFrame(new RectTransform( - (parentAspectRatio > aspectRatio ? new Vector2(aspectRatio / parentAspectRatio, 1.0f) : new Vector2(1.0f, parentAspectRatio / aspectRatio)) * scale, - parent.RectTransform, Anchor.Center), - style: null); + (parentAspectRatio > aspectRatio ? new Vector2(aspectRatio / parentAspectRatio, 1.0f) : new Vector2(1.0f, parentAspectRatio / aspectRatio)) * scale, + parent.RectTransform, Anchor.Center), + style: null) + { + UserData = "hullcontainer" + }; var connectedSubs = GetConnectedSubs(); - foreach (Hull hull in Hull.hullList) - { - if (hull.Submarine != this && !connectedSubs.Contains(hull.Submarine)) { continue; } - if (ignoreOutpost && !IsEntityFoundOnThisSub(hull, true)) { continue; } + HashSet hullList = Hull.hullList.Where(hull => hull.Submarine == this || connectedSubs.Contains(hull.Submarine)).Where(hull => !ignoreOutpost || IsEntityFoundOnThisSub(hull, true)).ToHashSet(); + + Dictionary> combinedHulls = new Dictionary>(); + + foreach (Hull hull in hullList) + { + if (combinedHulls.ContainsKey(hull) || combinedHulls.Values.Any(hh => hh.Contains(hull))) { continue; } + + List linkedHulls = new List(); + MiniMap.GetLinkedHulls(hull, linkedHulls); + + linkedHulls.Remove(hull); + + foreach (Hull linkedHull in linkedHulls) + { + if (!combinedHulls.ContainsKey(hull)) + { + combinedHulls.Add(hull, new HashSet()); + } + + combinedHulls[hull].Add(linkedHull); + } + } + + foreach (Hull hull in hullList) + { Vector2 relativeHullPos = new Vector2( - (hull.WorldRect.X - worldBorders.X) / (float)worldBorders.Width, + (hull.WorldRect.X - worldBorders.X) / (float)worldBorders.Width, (worldBorders.Y - hull.WorldRect.Y) / (float)worldBorders.Height); Vector2 relativeHullSize = new Vector2(hull.Rect.Width / (float)worldBorders.Width, hull.Rect.Height / (float)worldBorders.Height); - var hullFrame = new GUIFrame(new RectTransform(relativeHullSize, hullContainer.RectTransform) { RelativeOffset = relativeHullPos }, style: "MiniMapRoom", color: Color.DarkCyan * 0.8f) + bool hideHull = combinedHulls.ContainsKey(hull) || combinedHulls.Values.Any(hh => hh.Contains(hull)); + + if (hideHull) { continue; } + + Color color = Color.DarkCyan * 0.8f; + + var hullFrame = new GUIFrame(new RectTransform(relativeHullSize, hullContainer.RectTransform) { RelativeOffset = relativeHullPos }, style: "MiniMapRoom", color: color) { UserData = hull }; - new GUIFrame(new RectTransform(Vector2.One, hullFrame.RectTransform), style: "ScanLines", color: Color.DarkCyan * 0.8f); + + new GUIFrame(new RectTransform(Vector2.One, hullFrame.RectTransform), style: "ScanLines", color: color); + } + + foreach (var (mainHull, linkedHulls) in combinedHulls) + { + MiniMapHullData data = ConstructLinkedHulls(mainHull, linkedHulls, hullContainer, worldBorders); + + Vector2 relativeHullPos = new Vector2( + (data.Bounds.X - worldBorders.X) / worldBorders.Width, + (worldBorders.Y - data.Bounds.Y) / worldBorders.Height); + + Vector2 relativeHullSize = new Vector2(data.Bounds.Width / worldBorders.Width, data.Bounds.Height / worldBorders.Height); + + Color color = Color.DarkCyan * 0.8f; + + float highestY = 0f, + highestX = 0f; + + foreach (var (r, _) in data.RectDatas) + { + float y = r.Y - -r.Height, + x = r.X; + + if (y > highestY) { highestY = y; } + if (x > highestX) { highestX = x; } + } + + HashSet frames = new HashSet(); + + foreach (var (snappredRect, hull) in data.RectDatas) + { + RectangleF rect = snappredRect; + rect.Height = -rect.Height; + rect.Y -= rect.Height; + + var (parentW, parentH) = hullContainer.Rect.Size.ToVector2(); + Vector2 size = new Vector2(rect.Width / parentW, rect.Height / parentH); + // TODO this won't be required if we some day switch RectTransform to use RectangleF + Vector2 pos = new Vector2(rect.X / parentW, rect.Y / parentH); + + GUIFrame hullFrame = new GUIFrame(new RectTransform(size, hullContainer.RectTransform) { RelativeOffset = pos }, style: "ScanLinesSeamless", color: color) + { + UserData = hull, + UVOffset = new Vector2(highestX - rect.X, highestY - rect.Y) + }; + + frames.Add(hullFrame); + } + + new GUICustomComponent(new RectTransform(relativeHullSize, hullContainer.RectTransform) { RelativeOffset = relativeHullPos }, (spriteBatch, component) => + { + foreach (List list in data.Polygon) + { + spriteBatch.DrawPolygonInner(hullContainer.Rect.Location.ToVector2(), list, component.Color, 2f); + } + }, (deltaTime, component) => + { + if (component.Parent.Rect.Size != data.ParentSize) + { + data = ConstructLinkedHulls(mainHull, linkedHulls, hullContainer, worldBorders); + } + }) + { + UserData = frames, + Color = color, + CanBeFocused = false + }; } if (pointsOfInterest != null) @@ -453,6 +531,64 @@ namespace Barotrauma } } + public static MiniMapHullData ConstructLinkedHulls(Hull mainHull, HashSet linkedHulls, GUIComponent parent, Rectangle worldBorders) + { + Rectangle parentRect = parent.Rect; + + Dictionary rects = new Dictionary(); + Rectangle worldRect = mainHull.WorldRect; + worldRect.Y = -worldRect.Y; + + rects.Add(mainHull, worldRect); + + foreach (Hull hull in linkedHulls) + { + Rectangle rect = hull.WorldRect; + rect.Y = -rect.Y; + + worldRect = Rectangle.Union(worldRect, rect); + rects.Add(hull, rect); + } + + worldRect.Y = -worldRect.Y; + + List normalizedRects = new List(); + List hullRefs = new List(); + foreach (var (hull, rect) in rects) + { + Rectangle wRect = rect; + wRect.Y = -wRect.Y; + + var (posX, posY) = new Vector2( + (wRect.X - worldBorders.X) / (float)worldBorders.Width, + (worldBorders.Y - wRect.Y) / (float)worldBorders.Height); + + var (scaleX, scaleY) = new Vector2(wRect.Width / (float)worldBorders.Width, wRect.Height / (float)worldBorders.Height); + + RectangleF newRect = new RectangleF(posX * parentRect.Width, posY * parentRect.Height, scaleX * parentRect.Width, scaleY * parentRect.Height); + + normalizedRects.Add(newRect); + hullRefs.Add(hull); + } + + ImmutableArray snappedRectangles = ToolBox.SnapRectangles(normalizedRects, treshold: 1); + + List> polygon = ToolBox.CombineRectanglesIntoShape(snappedRectangles); + + List> scaledPolygon = new List>(); + + foreach (List list in polygon) + { + var (polySizeX, polySizeY) = ToolBox.GetPolygonBoundingBoxSize(list); + float sizeX = polySizeX - 1f, + sizeY = polySizeY - 1f; + + scaledPolygon.Add(ToolBox.ScalePolygon(list, new Vector2(sizeX / polySizeX, sizeY / polySizeY))); + } + + return new MiniMapHullData(scaledPolygon, worldRect, parentRect.Size, snappedRectangles, hullRefs.ToImmutableArray()); + } + public void CheckForErrors() { List errorMsgs = new List(); @@ -467,7 +603,7 @@ namespace Barotrauma } } - if (Info.Type != SubmarineType.OutpostModule || + if (Info.Type != SubmarineType.OutpostModule || (Info.OutpostModuleInfo?.ModuleFlags.Any(f => !f.Equals("hallwayvertical", StringComparison.OrdinalIgnoreCase) && !f.Equals("hallwayhorizontal", StringComparison.OrdinalIgnoreCase)) ?? true)) { if (!WayPoint.WayPointList.Any(wp => wp.ShouldBeSaved && wp.SpawnType == SpawnType.Path)) @@ -535,7 +671,7 @@ namespace Barotrauma int wireCount = item.Connections[i].Wires.Count(w => w != null); if (doorLinks + wireCount > item.Connections[i].MaxWires) { - errorMsgs.Add(TextManager.GetWithVariables("InsufficientFreeConnectionsWarning", + errorMsgs.Add(TextManager.GetWithVariables("InsufficientFreeConnectionsWarning", new string[] { "[doorcount]", "[freeconnectioncount]" }, new string[] { doorLinks.ToString(), (item.Connections[i].MaxWires - wireCount).ToString() })); break; @@ -632,7 +768,7 @@ namespace Barotrauma return SubEditorScreen.SuppressedWarnings.Contains(type); } } - + public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime) { if (type != ServerNetObject.ENTITY_POSITION) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/SubmarinePreview.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/SubmarinePreview.cs index 68e65bc1f..5083fe7c1 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/SubmarinePreview.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/SubmarinePreview.cs @@ -600,6 +600,8 @@ namespace Barotrauma var prevScissorRect = GameMain.Instance.GraphicsDevice.ScissorRectangle; GameMain.Instance.GraphicsDevice.ScissorRectangle = scissorRectangle; + var prevRasterizerState = GameMain.Instance.GraphicsDevice.RasterizerState; + GameMain.Instance.GraphicsDevice.RasterizerState = GameMain.ScissorTestEnable; spriteRecorder.Render(camera); @@ -643,6 +645,7 @@ namespace Barotrauma spriteBatch.End(); GameMain.Instance.GraphicsDevice.ScissorRectangle = prevScissorRect; + GameMain.Instance.GraphicsDevice.RasterizerState = prevRasterizerState; spriteBatch.Begin(SpriteSortMode.Deferred); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/WayPoint.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/WayPoint.cs index 85da43a34..0696a24a2 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/WayPoint.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/WayPoint.cs @@ -127,6 +127,13 @@ namespace Barotrauma ID.ToString(), new Vector2(DrawPosition.X - 10, -DrawPosition.Y - 30), color); + if (Tunnel?.Type != null) + { + GUI.SmallFont.DrawString(spriteBatch, + Tunnel.Type.ToString(), + new Vector2(DrawPosition.X - 10, -DrawPosition.Y - 45), + color); + } } public override bool IsMouseOn(Vector2 position) @@ -164,10 +171,7 @@ namespace Barotrauma { foreach (MapEntity e in mapEntityList) { - if (e.GetType() != typeof(WayPoint)) continue; - if (e == this) continue; - - if (!Submarine.RectContains(e.Rect, position)) continue; + if (!(e is WayPoint) || e == this || !e.IsHighlighted) { continue; } if (linkedTo.Contains(e)) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/ChatMessage.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/ChatMessage.cs index feee46eb0..67c758aa6 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/ChatMessage.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/ChatMessage.cs @@ -64,7 +64,10 @@ namespace Barotrauma.Networking string orderOption = orderMessageInfo.OrderOption; orderOption ??= orderMessageInfo.OrderOptionIndex.HasValue && orderMessageInfo.OrderOptionIndex >= 0 && orderMessageInfo.OrderOptionIndex < orderPrefab.Options.Length ? orderPrefab.Options[orderMessageInfo.OrderOptionIndex.Value] : ""; - txt = orderPrefab.GetChatMessage(orderMessageInfo.TargetCharacter?.Name, senderCharacter?.CurrentHull?.DisplayName, givingOrderToSelf: orderMessageInfo.TargetCharacter == senderCharacter, orderOption: orderOption); + txt = orderPrefab.GetChatMessage(orderMessageInfo.TargetCharacter?.Name, senderCharacter?.CurrentHull?.DisplayName, + givingOrderToSelf: orderMessageInfo.TargetCharacter == senderCharacter, + orderOption: orderOption, + priority: orderMessageInfo.Priority); if (GameMain.Client.GameStarted && Screen.Selected == GameMain.GameScreen) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs index 363d909a3..85267e5a7 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs @@ -550,6 +550,13 @@ namespace Barotrauma.Networking okButton.OnClicked += msgBox.Close; var cancelButton = msgBox.Buttons[1]; cancelButton.OnClicked += msgBox.Close; + passwordBox.OnEnterPressed += (GUITextBox textBox, string text) => + { + msgBox.Close(); + clientPeer?.SendPassword(passwordBox.Text); + requiresPw = false; + return true; + }; okButton.OnClicked += (GUIButton button, object obj) => { @@ -565,6 +572,8 @@ namespace Barotrauma.Networking GameMain.ServerListScreen.Select(); return true; }; + yield return CoroutineStatus.Running; + passwordBox.Select(); while (GUIMessageBox.MessageBoxes.Contains(msgBox)) { @@ -1456,6 +1465,7 @@ namespace Barotrauma.Networking bool respawnAllowed = inc.ReadBoolean(); serverSettings.AllowDisguises = inc.ReadBoolean(); serverSettings.AllowRewiring = inc.ReadBoolean(); + serverSettings.AllowFriendlyFire = inc.ReadBoolean(); serverSettings.LockAllDefaultWires = inc.ReadBoolean(); serverSettings.AllowRagdollButton = inc.ReadBoolean(); GameMain.NetLobbyScreen.UsingShuttle = inc.ReadBoolean(); @@ -2746,6 +2756,9 @@ namespace Barotrauma.Networking msg.Write((byte)characterInfo.BeardIndex); msg.Write((byte)characterInfo.MoustacheIndex); msg.Write((byte)characterInfo.FaceAttachmentIndex); + msg.WriteColorR8G8B8(characterInfo.SkinColor); + msg.WriteColorR8G8B8(characterInfo.HairColor); + msg.WriteColorR8G8B8(characterInfo.FacialHairColor); var jobPreferences = GameMain.NetLobbyScreen.JobPreferences; int count = Math.Min(jobPreferences.Count, 3); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/SteamP2PClientPeer.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/SteamP2PClientPeer.cs index 8f08b8966..911d2eb23 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/SteamP2PClientPeer.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/SteamP2PClientPeer.cs @@ -288,7 +288,7 @@ namespace Barotrauma.Networking heartbeatTimer = 5.0; #if DEBUG - CoroutineManager.InvokeAfter(() => + CoroutineManager.Invoke(() => { if (GameMain.Client == null) { return; } if (Rand.Range(0.0f, 1.0f) < GameMain.Client.SimulatedLoss && sendType != Steamworks.P2PSend.Reliable) { return; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerInfo.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerInfo.cs index 931cd5d44..ee2ffa7c1 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerInfo.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerInfo.cs @@ -110,12 +110,7 @@ namespace Barotrauma.Networking if (frame == null) { return; } - var previewContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.98f), frame.RectTransform, Anchor.Center)) - { - Stretch = true - }; - - var title = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), previewContainer.RectTransform, Anchor.CenterLeft), ServerName, font: GUI.LargeFont) + var title = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), frame.RectTransform), ServerName, font: GUI.LargeFont) { ToolTip = ServerName }; @@ -141,41 +136,30 @@ namespace Barotrauma.Networking } }; - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), previewContainer.RectTransform), + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), frame.RectTransform), TextManager.AddPunctuation(':', TextManager.Get("ServerListVersion"), string.IsNullOrEmpty(GameVersion) ? TextManager.Get("Unknown") : GameVersion)); - bool hidePlaystyleBanner = previewContainer.Rect.Height < 380 || !PlayStyle.HasValue; + bool hidePlaystyleBanner = !PlayStyle.HasValue; if (!hidePlaystyleBanner) { PlayStyle playStyle = PlayStyle ?? Networking.PlayStyle.Serious; Sprite playStyleBannerSprite = ServerListScreen.PlayStyleBanners[(int)playStyle]; float playStyleBannerAspectRatio = playStyleBannerSprite.SourceRect.Width / playStyleBannerSprite.SourceRect.Height; - var playStyleBanner = new GUIImage(new RectTransform(new Point(previewContainer.Rect.Width, (int)(previewContainer.Rect.Width / playStyleBannerAspectRatio)), previewContainer.RectTransform), + var playStyleBanner = new GUIImage(new RectTransform(new Point(frame.Rect.Width, (int)(frame.Rect.Width / playStyleBannerAspectRatio)), frame.RectTransform), playStyleBannerSprite, null, true); - var playStyleName = new GUITextBlock(new RectTransform(new Vector2(0.15f, 0.0f), playStyleBanner.RectTransform) { RelativeOffset = new Vector2(0.01f, 0.06f) }, + var playStyleName = new GUITextBlock(new RectTransform(new Vector2(0.15f, 0.0f), playStyleBanner.RectTransform) { RelativeOffset = new Vector2(0.0f, 0.06f) }, TextManager.AddPunctuation(':', TextManager.Get("serverplaystyle"), TextManager.Get("servertag."+ playStyle)), textColor: Color.White, font: GUI.SmallFont, textAlignment: Alignment.Center, color: ServerListScreen.PlayStyleColors[(int)playStyle], style: "GUISlopedHeader"); playStyleName.RectTransform.NonScaledSize = (playStyleName.Font.MeasureString(playStyleName.Text) + new Vector2(20, 5) * GUI.Scale).ToPoint(); playStyleName.RectTransform.IsFixedSize = true; - - var serverTypeContainer = new GUIFrame(new RectTransform(new Vector2(0.9f, 0.2f), playStyleBanner.RectTransform, Anchor.BottomLeft, Pivot.BottomLeft), - "MainMenuNotifBackground", Color.Black) - { - CanBeFocused = false, - }; - - var serverType = new GUITextBlock(new RectTransform(Vector2.One, serverTypeContainer.RectTransform, Anchor.CenterLeft), - TextManager.Get((OwnerID != 0 || LobbyID != 0) ? "SteamP2PServer" : "DedicatedServer"), textAlignment: Alignment.CenterLeft); - } - else - { - var serverType = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.1f), previewContainer.RectTransform, Anchor.CenterLeft), - TextManager.Get((OwnerID != 0 || LobbyID != 0) ? "SteamP2PServer" : "DedicatedServer"), textAlignment: Alignment.CenterLeft); } + var serverType = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), frame.RectTransform), + TextManager.Get((OwnerID != 0 || LobbyID != 0) ? "SteamP2PServer" : "DedicatedServer"), textAlignment: Alignment.TopLeft); + serverType.RectTransform.MinSize = new Point(0, (int)(serverType.Rect.Height * 1.5f)); - var content = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.6f), previewContainer.RectTransform)) + var content = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.6f), frame.RectTransform)) { Stretch = true }; @@ -285,7 +269,6 @@ namespace Barotrauma.Networking else usingWhiteList.Selected = UsingWhiteList.Value; - content.RectTransform.SizeChanged += () => { GUITextBlock.AutoScaleAndNormalize(allowSpectating.TextBlock, allowRespawn.TextBlock, usingWhiteList.TextBlock); @@ -294,7 +277,7 @@ namespace Barotrauma.Networking new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform), TextManager.Get("ServerListContentPackages"), textAlignment: Alignment.Center, font: GUI.SubHeadingFont); - var contentPackageList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.2f), content.RectTransform)) { ScrollBarVisible = true }; + var contentPackageList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.3f), frame.RectTransform)) { ScrollBarVisible = true }; if (ContentPackageNames.Count == 0) { new GUITextBlock(new RectTransform(Vector2.One, contentPackageList.Content.RectTransform), TextManager.Get("Unknown"), textAlignment: Alignment.Center) @@ -309,7 +292,7 @@ namespace Barotrauma.Networking var packageText = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.15f), contentPackageList.Content.RectTransform) { MinSize = new Point(0, 15) }, ContentPackageNames[i]) { - Enabled = false + CanBeFocused = false }; if (i < ContentPackageHashes.Count) { @@ -322,7 +305,7 @@ namespace Barotrauma.Networking //workshop download link found if (i < ContentPackageWorkshopIds.Count && ContentPackageWorkshopIds[i] != 0) { - packageText.TextColor = Color.Yellow; + packageText.TextColor = GUI.Style.Yellow; packageText.ToolTip = TextManager.GetWithVariable("ServerListIncompatibleContentPackageWorkshopAvailable", "[contentpackage]", ContentPackageNames[i]); } else //no package or workshop download link found, tough luck diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/SteamManager.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/SteamManager.cs index d9647ac72..13604817e 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/SteamManager.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/SteamManager.cs @@ -1207,7 +1207,7 @@ namespace Barotrauma.Steam foreach (string file in allPackageFiles) { if (file == metaDataFilePath) { continue; } - string relativePath = UpdaterUtil.GetRelativePath(file, item.Directory); + string relativePath = Path.GetRelativePath(item.Directory, file); string fullPath = Path.GetFullPath(relativePath); if (contentPackage.Files.Any(f => { string fp = Path.GetFullPath(f.Path); return fp == fullPath; })) { continue; } nonContentFiles.Add(relativePath); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Particles/Particle.cs b/Barotrauma/BarotraumaClient/ClientSource/Particles/Particle.cs index 3a4852708..6b7cd2e8c 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Particles/Particle.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Particles/Particle.cs @@ -3,7 +3,6 @@ using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using System; using System.Collections.Generic; -using System.Reflection.Metadata; namespace Barotrauma.Particles { @@ -16,6 +15,8 @@ namespace Barotrauma.Particles public delegate void OnChangeHullHandler(Vector2 position, Hull currentHull); public OnChangeHullHandler OnChangeHull; + public OnChangeHullHandler OnCollision; + private Vector2 position; private Vector2 prevPosition; @@ -166,6 +167,7 @@ namespace Barotrauma.Particles HighQualityCollisionDetection = false; OnChangeHull = null; + OnCollision = null; subEmitters.Clear(); hasSubEmitters = false; @@ -340,12 +342,20 @@ namespace Barotrauma.Particles Vector2 collisionNormal = Vector2.Zero; if (velocity.Y < 0.0f && position.Y - prefab.CollisionRadius * size.Y < hullRect.Y - hullRect.Height) { - if (prefab.DeleteOnCollision) { return UpdateResult.Delete; } + if (prefab.DeleteOnCollision) + { + OnCollision?.Invoke(position, currentHull); + return UpdateResult.Delete; + } collisionNormal = new Vector2(0.0f, 1.0f); } else if (velocity.Y > 0.0f && position.Y + prefab.CollisionRadius * size.Y > hullRect.Y) { - if (prefab.DeleteOnCollision) { return UpdateResult.Delete; } + if (prefab.DeleteOnCollision) + { + OnCollision?.Invoke(position, currentHull); + return UpdateResult.Delete; + } collisionNormal = new Vector2(0.0f, -1.0f); } @@ -368,6 +378,7 @@ namespace Barotrauma.Particles handleCollision(gapFound, collisionNormal); } + collisionNormal = Vector2.Zero; if (velocity.X < 0.0f && position.X - prefab.CollisionRadius * size.X < hullRect.X) { if (prefab.DeleteOnCollision) { return UpdateResult.Delete; } @@ -487,6 +498,8 @@ namespace Barotrauma.Particles velocity.Y = Math.Sign(collisionNormal.Y) * Math.Abs(velocity.Y) * prefab.Restitution; } + OnCollision?.Invoke(position, currentHull); + velocity += subVel; } @@ -523,6 +536,8 @@ namespace Barotrauma.Particles velocity.Y *= (1.0f - prefab.Friction); } + OnCollision?.Invoke(position, currentHull); + velocity *= prefab.Restitution; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Particles/ParticleEmitter.cs b/Barotrauma/BarotraumaClient/ClientSource/Particles/ParticleEmitter.cs index f54793630..72d20579a 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Particles/ParticleEmitter.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Particles/ParticleEmitter.cs @@ -138,6 +138,8 @@ namespace Barotrauma.Particles public void Emit(float deltaTime, Vector2 position, Hull hullGuess = null, float angle = 0.0f, float particleRotation = 0.0f, float velocityMultiplier = 1.0f, float sizeMultiplier = 1.0f, float amountMultiplier = 1.0f, Color? colorMultiplier = null, ParticlePrefab overrideParticle = null, Tuple tracerPoints = null) { + if (GameMain.Client?.MidRoundSyncing ?? false) { return; } + if (initialDelay < Prefab.Properties.InitialDelay) { initialDelay += deltaTime; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Particles/ParticleManager.cs b/Barotrauma/BarotraumaClient/ClientSource/Particles/ParticleManager.cs index 9ce02308e..1b5c3fdc0 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Particles/ParticleManager.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Particles/ParticleManager.cs @@ -133,22 +133,41 @@ namespace Barotrauma.Particles public Particle CreateParticle(ParticlePrefab prefab, Vector2 position, Vector2 velocity, float rotation = 0.0f, Hull hullGuess = null, bool drawOnTop = false, float collisionIgnoreTimer = 0f, Tuple tracerPoints = null) { - if (particleCount >= MaxParticles || prefab == null || prefab.Sprites.Count == 0) { return null; } - - // this should be optimized for tracers after prototyping - if (tracerPoints == null) + if (prefab == null || prefab.Sprites.Count == 0) { return null; } + + if (particleCount >= MaxParticles) { - Vector2 particleEndPos = prefab.CalculateEndPosition(position, velocity); - - Vector2 minPos = new Vector2(Math.Min(position.X, particleEndPos.X), Math.Min(position.Y, particleEndPos.Y)); - Vector2 maxPos = new Vector2(Math.Max(position.X, particleEndPos.X), Math.Max(position.Y, particleEndPos.Y)); - - Rectangle expandedViewRect = MathUtils.ExpandRect(cam.WorldView, MaxOutOfViewDist); - - if (minPos.X > expandedViewRect.Right || maxPos.X < expandedViewRect.X) { return null; } - if (minPos.Y > expandedViewRect.Y || maxPos.Y < expandedViewRect.Y - expandedViewRect.Height) { return null; } + for (int i = 0; i < particleCount; i++) + { + if (particles[i].Prefab.Priority < prefab.Priority) + { + RemoveParticle(i); + break; + } + } + if (particleCount >= MaxParticles) { return null; } } + Vector2 particleEndPos = prefab.CalculateEndPosition(position, velocity); + + Vector2 minPos = new Vector2(Math.Min(position.X, particleEndPos.X), Math.Min(position.Y, particleEndPos.Y)); + Vector2 maxPos = new Vector2(Math.Max(position.X, particleEndPos.X), Math.Max(position.Y, particleEndPos.Y)); + + if (tracerPoints != null) + { + minPos = new Vector2( + Math.Min(Math.Min(minPos.X, tracerPoints.Item1.X), tracerPoints.Item2.X), + Math.Min(Math.Min(minPos.Y, tracerPoints.Item1.Y), tracerPoints.Item2.Y)); + maxPos = new Vector2( + Math.Max(Math.Max(maxPos.X, tracerPoints.Item1.X), tracerPoints.Item2.X), + Math.Max(Math.Max(maxPos.Y, tracerPoints.Item1.Y), tracerPoints.Item2.Y)); + } + + Rectangle expandedViewRect = MathUtils.ExpandRect(cam.WorldView, MaxOutOfViewDist); + + if (minPos.X > expandedViewRect.Right || maxPos.X < expandedViewRect.X) { return null; } + if (minPos.Y > expandedViewRect.Y || maxPos.Y < expandedViewRect.Y - expandedViewRect.Height) { return null; } + if (particles[particleCount] == null) particles[particleCount] = new Particle(); particles[particleCount].Init(prefab, position, velocity, rotation, hullGuess, drawOnTop, collisionIgnoreTimer, tracerPoints: tracerPoints); @@ -165,7 +184,7 @@ namespace Barotrauma.Particles public ParticlePrefab FindPrefab(string prefabName) { - return Prefabs.Find(p => p.Identifier == prefabName); + return Prefabs.Find(p => p.Identifier.Equals(prefabName, StringComparison.OrdinalIgnoreCase)); } private void RemoveParticle(int index) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Particles/ParticlePrefab.cs b/Barotrauma/BarotraumaClient/ClientSource/Particles/ParticlePrefab.cs index 104789a59..9882048e8 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Particles/ParticlePrefab.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Particles/ParticlePrefab.cs @@ -180,16 +180,13 @@ namespace Barotrauma.Particles [Editable, Serialize("1.0,1.0", false, description: "The maximum initial size of the particle.")] public Vector2 StartSizeMax { get; private set; } - [Editable] - [Serialize("0.0,0.0", false, description: "How much the size of the particle changes per second. The rate of growth for each particle is randomize between SizeChangeMin and SizeChangeMax.")] + [Editable, Serialize("0.0,0.0", false, description: "How much the size of the particle changes per second. The rate of growth for each particle is randomize between SizeChangeMin and SizeChangeMax.")] public Vector2 SizeChangeMin { get; private set; } - [Editable] - [Serialize("0.0,0.0", false, description: "How much the size of the particle changes per second. The rate of growth for each particle is randomize between SizeChangeMin and SizeChangeMax.")] + [Editable, Serialize("0.0,0.0", false, description: "How much the size of the particle changes per second. The rate of growth for each particle is randomize between SizeChangeMin and SizeChangeMax.")] public Vector2 SizeChangeMax { get; private set; } - [Editable] - [Serialize(0.0f, false, description: "How many seconds it takes for the particle to grow to it's initial size.")] + [Editable, Serialize(0.0f, false, description: "How many seconds it takes for the particle to grow to it's initial size.")] public float GrowTime { get; private set; } //rendering ----------------------------------------- @@ -215,6 +212,9 @@ namespace Barotrauma.Particles [Editable, Serialize(ParticleBlendState.AlphaBlend, false, description: "The type of blending to use when rendering the particle.")] public ParticleBlendState BlendState { get; private set; } + [Editable, Serialize(0, false, description: "Particles with a higher priority can replace lower-priority ones if the maximum number of active particles has been reached.")] + public int Priority { get; private set; } + //animation ----------------------------------------- [Editable(0.0f, float.MaxValue), Serialize(1.0f, false, description: "The duration of the particle's animation cycle (if it's animated).")] diff --git a/Barotrauma/BarotraumaClient/ClientSource/Program.cs b/Barotrauma/BarotraumaClient/ClientSource/Program.cs index 0ee887f01..b8beb4aa6 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Program.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Program.cs @@ -54,9 +54,11 @@ namespace Barotrauma executableDir = Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location); Directory.SetCurrentDirectory(executableDir); SteamManager.Initialize(); + EnableNvOptimus(); Game = new GameMain(args); Game.Run(); Game.Dispose(); + FreeNvOptimus(); CrossThread.ProcessTasks(); } @@ -263,6 +265,27 @@ namespace Barotrauma " if you'd like to help fix this bug, you may post it on Barotrauma's GitHub issue tracker: https://github.com/Regalis11/Barotrauma/issues/", filePath); } } - } + + private static IntPtr nvApi64Dll = IntPtr.Zero; + private static void EnableNvOptimus() + { +#if WINDOWS && X64 + // We force load nvapi64.dll so nvidia gives us the dedicated GPU on optimus laptops. + // This is not a method for getting optimus that is documented by nvidia, but it works, so... + if (NativeLibrary.TryLoad("nvapi64.dll", out nvApi64Dll)) + { + DebugConsole.Log("Loaded nvapi64.dll successfully"); + } #endif } + + private static void FreeNvOptimus() + { + #warning TODO: determine if we can do this safely + //NativeLibrary.Free(nvApi64Dll); + } + + } +#endif + + } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI/CampaignSetupUI.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI/CampaignSetupUI.cs new file mode 100644 index 000000000..81ead93e1 --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI/CampaignSetupUI.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; + +namespace Barotrauma +{ + abstract class CampaignSetupUI + { + protected readonly GUIComponent newGameContainer, loadGameContainer; + + protected GUIListBox subList; + protected GUIListBox saveList; + protected List subTickBoxes; + + protected GUITextBox saveNameBox, seedBox; + + protected GUILayoutGroup subPreviewContainer; + + protected GUIButton loadGameButton; + + public Action StartNewGame; + public Action LoadGame; + + protected enum CategoryFilter { All = 0, Vanilla = 1, Custom = 2 }; + protected CategoryFilter subFilter = CategoryFilter.All; + + public GUIButton StartButton + { + get; + protected set; + } + + public GUITextBlock InitialMoneyText + { + get; + protected set; + } + + public GUITickBox EnableRadiationToggle { get; set; } + public GUILayoutGroup CampaignSettingsContent { get; set; } + + public GUIButton CampaignCustomizeButton { get; set; } + public GUIMessageBox CampaignCustomizeSettings { get; set; } + + public GUITextBlock MaxMissionCountText; + + public CampaignSetupUI(GUIComponent newGameContainer, GUIComponent loadGameContainer) + { + this.newGameContainer = newGameContainer; + this.loadGameContainer = loadGameContainer; + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI/MultiPlayerCampaignSetupUI.cs similarity index 51% rename from Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI.cs rename to Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI/MultiPlayerCampaignSetupUI.cs index 7882cc77b..e02424ee4 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI/MultiPlayerCampaignSetupUI.cs @@ -1,4 +1,4 @@ -using Barotrauma.Tutorials; +using Barotrauma.Tutorials; using Microsoft.Xna.Framework; using System; using System.Collections.Generic; @@ -10,58 +10,17 @@ using Barotrauma.Extensions; namespace Barotrauma { - class CampaignSetupUI + class MultiPlayerCampaignSetupUI : CampaignSetupUI { - private readonly GUIComponent newGameContainer, loadGameContainer; - - private GUIListBox subList; - private GUIListBox saveList; - private List subTickBoxes; - - private readonly GUITextBox saveNameBox, seedBox; - - private readonly GUILayoutGroup subPreviewContainer; - - private GUIButton loadGameButton, deleteMpSaveButton; + private GUIButton deleteMpSaveButton; - public Action StartNewGame; - public Action LoadGame; - - private enum CategoryFilter { All = 0, Vanilla = 1, Custom = 2 }; - private CategoryFilter subFilter = CategoryFilter.All; - - public GUIButton StartButton + public MultiPlayerCampaignSetupUI(GUIComponent newGameContainer, GUIComponent loadGameContainer, IEnumerable submarines, IEnumerable saveFiles = null) + : base(newGameContainer, loadGameContainer) { - get; - private set; - } - - public GUITextBlock InitialMoneyText - { - get; - private set; - } - - public GUITickBox EnableRadiationToggle { get; set; } - public GUILayoutGroup CampaignSettingsContent { get; set; } - - public GUIButton CampaignCustomizeButton { get; set; } - public GUIMessageBox CampaignCustomizeSettings { get; set; } - - public GUITextBlock MaxMissionCountText; - - private readonly bool isMultiplayer; - - public CampaignSetupUI(bool isMultiplayer, GUIComponent newGameContainer, GUIComponent loadGameContainer, IEnumerable submarines, IEnumerable saveFiles = null) - { - this.isMultiplayer = isMultiplayer; - this.newGameContainer = newGameContainer; - this.loadGameContainer = loadGameContainer; - var columnContainer = new GUILayoutGroup(new RectTransform(Vector2.One, newGameContainer.RectTransform), isHorizontal: true) { Stretch = true, - RelativeSpacing = isMultiplayer ? 0.0f : 0.02f + RelativeSpacing = 0.0f }; var leftColumn = new GUILayoutGroup(new RectTransform(Vector2.One, columnContainer.RectTransform)) @@ -70,7 +29,7 @@ namespace Barotrauma RelativeSpacing = 0.015f }; - var rightColumn = new GUILayoutGroup(new RectTransform(isMultiplayer ? Vector2.Zero : new Vector2(1.5f, 1.0f), columnContainer.RectTransform)) + var rightColumn = new GUILayoutGroup(new RectTransform(Vector2.Zero, columnContainer.RectTransform)) { Stretch = true, RelativeSpacing = 0.015f @@ -88,47 +47,11 @@ namespace Barotrauma new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.02f), leftColumn.RectTransform) { MinSize = new Point(0, 20) }, TextManager.Get("MapSeed"), font: GUI.SubHeadingFont); seedBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.05f), leftColumn.RectTransform) { MinSize = new Point(0, 20) }, ToolBox.RandomSeed(8)); - if (!isMultiplayer) - { - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.02f), leftColumn.RectTransform) { MinSize = new Point(0, 20) }, TextManager.Get("SelectedSub"), font: GUI.SubHeadingFont); + // Spacing to fix the multiplayer campaign setup layout + CreateMultiplayerCampaignSubList(leftColumn.RectTransform); - var moddedDropdown = new GUIDropDown(new RectTransform(new Vector2(1f, 0.02f), leftColumn.RectTransform), "", 3); - moddedDropdown.AddItem(TextManager.Get("clientpermission.all"), CategoryFilter.All); - moddedDropdown.AddItem(TextManager.Get("servertag.modded.false"), CategoryFilter.Vanilla); - moddedDropdown.AddItem(TextManager.Get("customrank"), CategoryFilter.Custom); - moddedDropdown.Select(0); - - var filterContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), leftColumn.RectTransform), isHorizontal: true) - { - Stretch = true - }; - - subList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.65f), leftColumn.RectTransform)) { ScrollBarVisible = true }; - - var searchTitle = new GUITextBlock(new RectTransform(new Vector2(0.001f, 1.0f), filterContainer.RectTransform), TextManager.Get("serverlog.filter"), textAlignment: Alignment.CenterLeft, font: GUI.Font); - var searchBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 1.0f), filterContainer.RectTransform, Anchor.CenterRight), font: GUI.Font, createClearButton: true); - filterContainer.RectTransform.MinSize = searchBox.RectTransform.MinSize; - searchBox.OnSelected += (sender, userdata) => { searchTitle.Visible = false; }; - searchBox.OnDeselected += (sender, userdata) => { searchTitle.Visible = true; }; - searchBox.OnTextChanged += (textBox, text) => { FilterSubs(subList, text); return true; }; - - moddedDropdown.OnSelected = (component, data) => - { - searchBox.Text = string.Empty; - subFilter = (CategoryFilter)data; - UpdateSubList(SubmarineInfo.SavedSubmarines); - return true; - }; - - subList.OnSelected = OnSubSelected; - } - else // Spacing to fix the multiplayer campaign setup layout - { - CreateMultiplayerCampaignSubList(leftColumn.RectTransform); - - //spacing - //new GUIFrame(new RectTransform(new Vector2(1.0f, 0.25f), leftColumn.RectTransform), style: null); - } + //spacing + //new GUIFrame(new RectTransform(new Vector2(1.0f, 0.25f), leftColumn.RectTransform), style: null); // New game right side subPreviewContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f), rightColumn.RectTransform)) @@ -137,8 +60,7 @@ namespace Barotrauma }; var buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.12f), - (isMultiplayer ? leftColumn : rightColumn).RectTransform) { MaxSize = new Point(int.MaxValue, 60) }, childAnchor: Anchor.BottomRight, isHorizontal: true); - if (!isMultiplayer) { buttonContainer.IgnoreLayoutGroups = true; } + leftColumn.RectTransform) { MaxSize = new Point(int.MaxValue, 60) }, childAnchor: Anchor.BottomRight, isHorizontal: true); StartButton = new GUIButton(new RectTransform(new Vector2(0.4f, 1f), buttonContainer.RectTransform, Anchor.BottomRight) { MaxSize = new Point(350, 60) }, TextManager.Get("StartCampaignButton")) { @@ -152,16 +74,8 @@ namespace Barotrauma SubmarineInfo selectedSub = null; - if (!isMultiplayer) - { - if (!(subList.SelectedData is SubmarineInfo)) { return false; } - selectedSub = subList.SelectedData as SubmarineInfo; - } - else - { - if (GameMain.NetLobbyScreen.SelectedSub == null) { return false; } - selectedSub = GameMain.NetLobbyScreen.SelectedSub; - } + if (GameMain.NetLobbyScreen.SelectedSub == null) { return false; } + selectedSub = GameMain.NetLobbyScreen.SelectedSub; if (selectedSub.SubmarineClass == SubmarineClass.Undefined) { @@ -177,28 +91,14 @@ namespace Barotrauma return false; } - string savePath = SaveUtil.CreateSavePath(isMultiplayer ? SaveUtil.SaveType.Multiplayer : SaveUtil.SaveType.Singleplayer, saveNameBox.Text); + string savePath = SaveUtil.CreateSavePath(SaveUtil.SaveType.Multiplayer, saveNameBox.Text); bool hasRequiredContentPackages = selectedSub.RequiredContentPackagesInstalled; CampaignSettings settings = new CampaignSettings(); - if (isMultiplayer) - { - settings.RadiationEnabled = GameMain.NetLobbyScreen.IsRadiationEnabled(); - settings.MaxMissionCount = GameMain.NetLobbyScreen.GetMaxMissionCount(); - } - else - { - settings.RadiationEnabled = EnableRadiationToggle?.Selected ?? false; - if (MaxMissionCountText != null && Int32.TryParse(MaxMissionCountText.Text, out int missionCount)) - { - settings.MaxMissionCount = missionCount; - } - else - { - settings.MaxMissionCount = CampaignSettings.DefaultMaxMissionCount; - } - } + settings.RadiationEnabled = GameMain.NetLobbyScreen.IsRadiationEnabled(); + settings.MaxMissionCount = GameMain.NetLobbyScreen.GetMaxMissionCount(); + if (selectedSub.HasTag(SubmarineTag.Shuttle) || !hasRequiredContentPackages) { if (!hasRequiredContentPackages) @@ -213,10 +113,7 @@ namespace Barotrauma if (GUIMessageBox.MessageBoxes.Count == 0) { StartNewGame?.Invoke(selectedSub, savePath, seedBox.Text, settings); - if (isMultiplayer) - { - CoroutineManager.StartCoroutine(WaitForCampaignSetup(), "WaitForCampaignSetup"); - } + CoroutineManager.StartCoroutine(WaitForCampaignSetup(), "WaitForCampaignSetup"); } return true; }; @@ -233,10 +130,7 @@ namespace Barotrauma msgBox.Buttons[0].OnClicked = (button, obj) => { StartNewGame?.Invoke(selectedSub, savePath, seedBox.Text, settings); - if (isMultiplayer) - { - CoroutineManager.StartCoroutine(WaitForCampaignSetup(), "WaitForCampaignSetup"); - } + CoroutineManager.StartCoroutine(WaitForCampaignSetup(), "WaitForCampaignSetup"); return true; }; msgBox.Buttons[0].OnClicked += msgBox.Close; @@ -248,56 +142,27 @@ namespace Barotrauma else { StartNewGame?.Invoke(selectedSub, savePath, seedBox.Text, settings); - if (isMultiplayer) - { - CoroutineManager.StartCoroutine(WaitForCampaignSetup(), "WaitForCampaignSetup"); - } + CoroutineManager.StartCoroutine(WaitForCampaignSetup(), "WaitForCampaignSetup"); } return true; } }; - InitialMoneyText = new GUITextBlock(new RectTransform(new Vector2(isMultiplayer ? 0.6f : 0.3f, 1f), buttonContainer.RectTransform), "", font: isMultiplayer ? GUI.Style.SmallFont : GUI.Style.Font, textColor: GUI.Style.Green) + InitialMoneyText = new GUITextBlock(new RectTransform(new Vector2(0.6f, 1f), buttonContainer.RectTransform), "", font: GUI.Style.SmallFont, textColor: GUI.Style.Green) { TextGetter = () => { int initialMoney = CampaignMode.InitialMoney; - if (isMultiplayer) + if (GameMain.NetLobbyScreen.SelectedSub != null) { - if (GameMain.NetLobbyScreen.SelectedSub != null) - { - initialMoney -= GameMain.NetLobbyScreen.SelectedSub.Price; - } + initialMoney -= GameMain.NetLobbyScreen.SelectedSub.Price; } - else if (subList.SelectedData is SubmarineInfo subInfo) - { - initialMoney -= subInfo.Price; - } - initialMoney = Math.Max(initialMoney, isMultiplayer ? MultiPlayerCampaign.MinimumInitialMoney : 0); + initialMoney = Math.Max(initialMoney, MultiPlayerCampaign.MinimumInitialMoney); return TextManager.GetWithVariable("campaignstartingmoney", "[money]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", initialMoney)); } }; - if (!isMultiplayer) - { - CampaignCustomizeButton = new GUIButton(new RectTransform(new Vector2(0.25f, 1f), buttonContainer.RectTransform, Anchor.CenterLeft), TextManager.Get("SettingsButton")) - { - OnClicked = (tb, userdata) => - { - CreateCustomizeWindow(); - return true; - } - }; - - var disclaimerBtn = new GUIButton(new RectTransform(new Vector2(1.0f, 0.8f), rightColumn.RectTransform, Anchor.TopRight) { AbsoluteOffset = new Point(5) }, style: "GUINotificationButton") - { - IgnoreLayoutGroups = true, - OnClicked = (btn, userdata) => { GameMain.Instance.ShowCampaignDisclaimer(); return true; } - }; - disclaimerBtn.RectTransform.MaxSize = new Point((int)(30 * GUI.Scale)); - } - columnContainer.Recalculate(); leftColumn.Recalculate(); rightColumn.Recalculate(); @@ -306,55 +171,6 @@ namespace Barotrauma UpdateLoadMenu(saveFiles); } - private void CreateCustomizeWindow() - { - CampaignCustomizeSettings = new GUIMessageBox("", "", new string[] { TextManager.Get("OK") }, new Vector2(0.2f, 0.2f)); - CampaignCustomizeSettings.Buttons[0].OnClicked += CampaignCustomizeSettings.Close; - - CampaignSettingsContent = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), CampaignCustomizeSettings.Content.RectTransform, Anchor.TopCenter)) - { - RelativeSpacing = 0.1f - }; - - if (MapGenerationParams.Instance.RadiationParams != null) - { - bool prevRadiationToggleEnabled = EnableRadiationToggle?.Selected ?? true; - EnableRadiationToggle = new GUITickBox(new RectTransform(new Vector2(0.3f, 0.3f), CampaignSettingsContent.RectTransform), TextManager.Get("CampaignOption.EnableRadiation"), font: GUI.Style.Font) - { - Selected = prevRadiationToggleEnabled, - ToolTip = TextManager.Get("campaignoption.enableradiation.tooltip") - }; - } - var maxMissionCountSettingHolder = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.3f), CampaignSettingsContent.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft) - { - Stretch = true, - ToolTip = TextManager.Get("maxmissioncounttooltip") - }; - var maxMissionCountDescription = new GUITextBlock(new RectTransform(new Vector2(0.7f, 0.0f), maxMissionCountSettingHolder.RectTransform), TextManager.Get("maxmissioncount", fallBackTag: "missions"), wrap: true); - var maxMissionCountContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 1.0f), maxMissionCountSettingHolder.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft) { RelativeSpacing = 0.05f, Stretch = true }; - var maxMissionCountButtons = new GUIButton[2]; - maxMissionCountButtons[0] = new GUIButton(new RectTransform(new Vector2(0.15f, 0.8f), maxMissionCountContainer.RectTransform), style: "GUIButtonToggleLeft") - { - OnClicked = (button, obj) => - { - MaxMissionCountText.Text = Math.Clamp(Int32.Parse(MaxMissionCountText.Text) - 1, CampaignSettings.MinMissionCountLimit, CampaignSettings.MaxMissionCountLimit).ToString(); - return true; - } - }; - - string prevMaxMissionCountText = MaxMissionCountText?.Text ?? CampaignSettings.DefaultMaxMissionCount.ToString(); - MaxMissionCountText = new GUITextBlock(new RectTransform(new Vector2(0.7f, 1.0f), maxMissionCountContainer.RectTransform), prevMaxMissionCountText, textAlignment: Alignment.Center, style: "GUITextBox"); - maxMissionCountButtons[1] = new GUIButton(new RectTransform(new Vector2(0.15f, 0.8f), maxMissionCountContainer.RectTransform), style: "GUIButtonToggleRight") - { - OnClicked = (button, obj) => - { - MaxMissionCountText.Text = Math.Clamp(Int32.Parse(MaxMissionCountText.Text) + 1, CampaignSettings.MinMissionCountLimit, CampaignSettings.MaxMissionCountLimit).ToString(); - return true; - } - }; - maxMissionCountContainer.Children.ForEach(c => c.ToolTip = maxMissionCountSettingHolder.ToolTip); - } - private void CreateMultiplayerCampaignSubList(RectTransform parent) { GUILayoutGroup subHolder = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.725f), parent)) @@ -455,40 +271,6 @@ namespace Barotrauma } } - public void RandomizeSeed() - { - seedBox.Text = ToolBox.RandomSeed(8); - } - - private void FilterSubs(GUIListBox subList, string filter) - { - foreach (GUIComponent child in subList.Content.Children) - { - var sub = child.UserData as SubmarineInfo; - if (sub == null) { return; } - child.Visible = string.IsNullOrEmpty(filter) ? true : sub.DisplayName.ToLower().Contains(filter.ToLower()); - } - } - - private bool OnSubSelected(GUIComponent component, object obj) - { - if (subPreviewContainer == null) { return false; } - (subPreviewContainer.Parent as GUILayoutGroup)?.Recalculate(); - subPreviewContainer.ClearChildren(); - - if (!(obj is SubmarineInfo sub)) { return true; } -#if !DEBUG - if (!isMultiplayer && sub.Price > CampaignMode.InitialMoney && !GameMain.DebugDraw) - { - StartButton.Enabled = false; - return false; - } -#endif - StartButton.Enabled = true; - sub.CreatePreviewWindow(subPreviewContainer); - return true; - } - private IEnumerable WaitForCampaignSetup() { GUI.SetCursorWaiting(); @@ -516,24 +298,11 @@ namespace Barotrauma yield return CoroutineStatus.Success; } - public void CreateDefaultSaveName() - { - string savePath = SaveUtil.CreateSavePath(isMultiplayer ? SaveUtil.SaveType.Multiplayer : SaveUtil.SaveType.Singleplayer); - saveNameBox.Text = Path.GetFileNameWithoutExtension(savePath); - } - public void UpdateSubList(IEnumerable submarines) { List subsToShow; - if (!isMultiplayer && subFilter != CategoryFilter.All) - { - subsToShow = submarines.Where(s => s.IsCampaignCompatibleIgnoreClass && s.IsVanillaSubmarine() == (subFilter == CategoryFilter.Vanilla)).ToList(); - } - else - { - string downloadFolder = Path.GetFullPath(SaveUtil.SubmarineDownloadFolder); - subsToShow = submarines.Where(s => s.IsCampaignCompatibleIgnoreClass && Path.GetDirectoryName(Path.GetFullPath(s.FilePath)) != downloadFolder).ToList(); - } + string downloadFolder = Path.GetFullPath(SaveUtil.SubmarineDownloadFolder); + subsToShow = submarines.Where(s => s.IsCampaignCompatibleIgnoreClass && Path.GetDirectoryName(Path.GetFullPath(s.FilePath)) != downloadFolder).ToList(); subsToShow.Sort((s1, s2) => { @@ -596,10 +365,10 @@ namespace Barotrauma if (saveFiles == null) { - saveFiles = SaveUtil.GetSaveFiles(isMultiplayer ? SaveUtil.SaveType.Multiplayer : SaveUtil.SaveType.Singleplayer); + saveFiles = SaveUtil.GetSaveFiles(SaveUtil.SaveType.Multiplayer); } - var leftColumn = new GUILayoutGroup(new RectTransform(isMultiplayer ? new Vector2(1.0f, 0.85f) : new Vector2(0.5f, 1.0f), loadGameContainer.RectTransform), childAnchor: Anchor.TopCenter) + var leftColumn = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.85f), loadGameContainer.RectTransform), childAnchor: Anchor.TopCenter) { Stretch = true, RelativeSpacing = 0.03f @@ -610,27 +379,6 @@ namespace Barotrauma OnSelected = SelectSaveFile }; - if (!isMultiplayer) - { - new GUIButton(new RectTransform(new Vector2(0.6f, 0.08f), leftColumn.RectTransform), TextManager.Get("showinfolder")) - { - OnClicked = (btn, userdata) => - { - try - { - ToolBox.OpenFileWithShell(SaveUtil.SaveFolder); - } - catch (Exception e) - { - new GUIMessageBox( - TextManager.Get("error"), - TextManager.GetWithVariables("showinfoldererror", new string[] { "[folder]", "[errormessage]" }, new string[] { SaveUtil.SaveFolder, e.Message })); - } - return true; - } - }; - } - foreach (string saveFile in saveFiles) { string fileName = saveFile; @@ -649,39 +397,15 @@ namespace Barotrauma bool isCompatible = true; prevSaveFiles ??= new List(); - if (!isMultiplayer) - { - nameText.Text = Path.GetFileNameWithoutExtension(saveFile); - XDocument doc = SaveUtil.LoadGameSessionDoc(saveFile); - if (doc?.Root == null) - { - DebugConsole.ThrowError("Error loading save file \"" + saveFile + "\". The file may be corrupted."); - nameText.TextColor = GUI.Style.Red; - continue; - } - if (doc.Root.GetChildElement("multiplayercampaign") != null) - { - //multiplayer campaign save in the wrong folder -> don't show the save - saveList.Content.RemoveChild(saveFrame); - continue; - } - subName = doc.Root.GetAttributeString("submarine", ""); - saveTime = doc.Root.GetAttributeString("savetime", ""); - isCompatible = SaveUtil.IsSaveFileCompatible(doc); - contentPackageStr = doc.Root.GetAttributeString("selectedcontentpackages", ""); - prevSaveFiles?.Add(saveFile); - } - else - { - prevSaveFiles?.Add(saveFile); - string[] splitSaveFile = saveFile.Split(';'); - saveFrame.UserData = splitSaveFile[0]; - fileName = nameText.Text = Path.GetFileNameWithoutExtension(splitSaveFile[0]); - if (splitSaveFile.Length > 1) { subName = splitSaveFile[1]; } - if (splitSaveFile.Length > 2) { saveTime = splitSaveFile[2]; } - if (splitSaveFile.Length > 3) { contentPackageStr = splitSaveFile[3]; } - } + prevSaveFiles?.Add(saveFile); + string[] splitSaveFile = saveFile.Split(';'); + saveFrame.UserData = splitSaveFile[0]; + fileName = nameText.Text = Path.GetFileNameWithoutExtension(splitSaveFile[0]); + if (splitSaveFile.Length > 1) { subName = splitSaveFile[1]; } + if (splitSaveFile.Length > 2) { saveTime = splitSaveFile[2]; } + if (splitSaveFile.Length > 3) { contentPackageStr = splitSaveFile[3]; } + if (!string.IsNullOrEmpty(saveTime) && long.TryParse(saveTime, out long unixTime)) { DateTime time = ToolBox.Epoch.ToDateTime(unixTime); @@ -748,10 +472,7 @@ namespace Barotrauma { if (string.IsNullOrWhiteSpace(saveList.SelectedData as string)) { return false; } LoadGame?.Invoke(saveList.SelectedData as string); - if (isMultiplayer) - { - CoroutineManager.StartCoroutine(WaitForCampaignSetup(), "WaitForCampaignSetup"); - } + CoroutineManager.StartCoroutine(WaitForCampaignSetup(), "WaitForCampaignSetup"); return true; }, Enabled = false @@ -768,72 +489,13 @@ namespace Barotrauma { string fileName = (string)obj; - if (isMultiplayer) + loadGameButton.Enabled = true; + deleteMpSaveButton.Visible = deleteMpSaveButton.Enabled = GameMain.Client.IsServerOwner; + deleteMpSaveButton.Enabled = GameMain.GameSession?.SavePath != fileName; + if (deleteMpSaveButton.Visible) { - loadGameButton.Enabled = true; - deleteMpSaveButton.Visible = deleteMpSaveButton.Enabled = GameMain.Client.IsServerOwner; - deleteMpSaveButton.Enabled = GameMain.GameSession?.SavePath != fileName; - if (deleteMpSaveButton.Visible) - { - deleteMpSaveButton.UserData = obj as string; - } - return true; + deleteMpSaveButton.UserData = obj as string; } - - XDocument doc = SaveUtil.LoadGameSessionDoc(fileName); - if (doc?.Root == null) - { - DebugConsole.ThrowError("Error loading save file \"" + fileName + "\". The file may be corrupted."); - return false; - } - - loadGameButton.Enabled = SaveUtil.IsSaveFileCompatible(doc); - - RemoveSaveFrame(); - - string subName = doc.Root.GetAttributeString("submarine", ""); - string saveTime = doc.Root.GetAttributeString("savetime", "unknown"); - if (long.TryParse(saveTime, out long unixTime)) - { - DateTime time = ToolBox.Epoch.ToDateTime(unixTime); - saveTime = time.ToString(); - } - - string mapseed = doc.Root.GetAttributeString("mapseed", "unknown"); - - var saveFileFrame = new GUIFrame(new RectTransform(new Vector2(0.45f, 0.6f), loadGameContainer.RectTransform, Anchor.TopRight) - { - RelativeOffset = new Vector2(0.0f, 0.1f) - }, style: "InnerFrame") - { - UserData = "savefileframe" - }; - - var titleText = new GUITextBlock(new RectTransform(new Vector2(0.9f, 0.2f), saveFileFrame.RectTransform, Anchor.TopCenter) - { - RelativeOffset = new Vector2(0, 0.05f) - }, - Path.GetFileNameWithoutExtension(fileName), font: GUI.LargeFont, textAlignment: Alignment.Center); - titleText.Text = ToolBox.LimitString(titleText.Text, titleText.Font, titleText.Rect.Width); - - var layoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 0.5f), saveFileFrame.RectTransform, Anchor.Center) - { - RelativeOffset = new Vector2(0, 0.1f) - }); - - new GUITextBlock(new RectTransform(new Vector2(1, 0), layoutGroup.RectTransform), $"{TextManager.Get("Submarine")} : {subName}", font: GUI.SmallFont); - new GUITextBlock(new RectTransform(new Vector2(1, 0), layoutGroup.RectTransform), $"{TextManager.Get("LastSaved")} : {saveTime}", font: GUI.SmallFont); - new GUITextBlock(new RectTransform(new Vector2(1, 0), layoutGroup.RectTransform), $"{TextManager.Get("MapSeed")} : {mapseed}", font: GUI.SmallFont); - - new GUIButton(new RectTransform(new Vector2(0.4f, 0.15f), saveFileFrame.RectTransform, Anchor.BottomCenter) - { - RelativeOffset = new Vector2(0, 0.1f) - }, TextManager.Get("Delete"), style: "GUIButtonSmall") - { - UserData = fileName, - OnClicked = DeleteSave - }; - return true; } @@ -855,19 +517,5 @@ namespace Barotrauma return true; } - - private void RemoveSaveFrame() - { - GUIComponent prevFrame = null; - foreach (GUIComponent child in loadGameContainer.Children) - { - if (child.UserData as string != "savefileframe") continue; - - prevFrame = child; - break; - } - loadGameContainer.RemoveChild(prevFrame); - } - } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI/SinglePlayerCampaignSetupUI.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI/SinglePlayerCampaignSetupUI.cs new file mode 100644 index 000000000..17616623b --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI/SinglePlayerCampaignSetupUI.cs @@ -0,0 +1,837 @@ +using Barotrauma.Tutorials; +using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; +using Barotrauma.IO; +using System.Linq; +using System.Xml.Linq; +using System.Globalization; +using Barotrauma.Extensions; + +namespace Barotrauma +{ + class SinglePlayerCampaignSetupUI : CampaignSetupUI + { + public CharacterInfo.AppearanceCustomizationMenu[] CharacterMenus { get; private set; } + + private GUIButton nextButton; + private GUILayoutGroup characterInfoColumns; + + public SinglePlayerCampaignSetupUI(GUIComponent newGameContainer, GUIComponent loadGameContainer, IEnumerable submarines, IEnumerable saveFiles = null) + : base(newGameContainer, loadGameContainer) + { + UpdateNewGameMenu(submarines); + UpdateLoadMenu(saveFiles); + } + + private int currentPage = 0; + private GUIListBox pageContainer; + + public void Update() + { + float targetScroll = + (float)currentPage / ((float)pageContainer.Content.CountChildren - 1); + + pageContainer.BarScroll = MathHelper.Lerp(pageContainer.BarScroll, targetScroll, 0.2f); + if (MathUtils.NearlyEqual(pageContainer.BarScroll, targetScroll, 0.001f)) + { + pageContainer.BarScroll = targetScroll; + } + + for (int i=0; i + { + if (c is GUIDropDown dd) + { + dd.Dropped = false; + } + c.CanBeFocused = (i == currentPage); + }); + } + var previewListBox = subPreviewContainer.GetAllChildren().FirstOrDefault(); + previewListBox?.GetAllChildren()?.ForEach(c => + { + c.CanBeFocused = false; + }); + } + + private void UpdateNewGameMenu(IEnumerable submarines) + { + pageContainer = + new GUIListBox(new RectTransform(Vector2.One, newGameContainer.RectTransform), style: null, isHorizontal: true) + { + ScrollBarEnabled = false, + ScrollBarVisible = false, + HoverCursor = CursorState.Default + }; + + GUILayoutGroup createPageLayout() + { + var containerItem = + new GUIFrame(new RectTransform(Vector2.One, pageContainer.Content.RectTransform), style: null); + return new GUILayoutGroup(new RectTransform(Vector2.One * 0.95f, containerItem.RectTransform, + Anchor.Center)); + } + + CreateFirstPage(createPageLayout(), submarines); + CreateSecondPage(createPageLayout()); + + pageContainer.RecalculateChildren(); + pageContainer.GetAllChildren().ForEach(c => + { + c.ClampMouseRectToParent = true; + }); + pageContainer.GetAllChildren().ForEach(dd => + { + dd.ListBox.ClampMouseRectToParent = false; + dd.ListBox.Content.ClampMouseRectToParent = false; + }); + SetPage(0); + } + + private void CreateFirstPage(GUILayoutGroup firstPageLayout, IEnumerable submarines) + { + firstPageLayout.RelativeSpacing = 0.02f; + + var columnContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.9f), firstPageLayout.RectTransform), isHorizontal: true) + { + Stretch = true, + RelativeSpacing = 0.02f + }; + + var leftColumn = new GUILayoutGroup(new RectTransform(Vector2.One, columnContainer.RectTransform)) + { + Stretch = true, + RelativeSpacing = 0.015f + }; + + var rightColumn = new GUILayoutGroup(new RectTransform(new Vector2(1.5f, 1.0f), columnContainer.RectTransform)) + { + Stretch = true, + RelativeSpacing = 0.015f + }; + + columnContainer.Recalculate(); + + // New game left side + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.02f), leftColumn.RectTransform) { MinSize = new Point(0, 20) }, TextManager.Get("SaveName"), font: GUI.SubHeadingFont); + saveNameBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.05f), leftColumn.RectTransform) { MinSize = new Point(0, 20) }, string.Empty) + { + textFilterFunction = (string str) => { return ToolBox.RemoveInvalidFileNameChars(str); } + }; + + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.02f), leftColumn.RectTransform) { MinSize = new Point(0, 20) }, TextManager.Get("MapSeed"), font: GUI.SubHeadingFont); + seedBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.05f), leftColumn.RectTransform) { MinSize = new Point(0, 20) }, ToolBox.RandomSeed(8)); + + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.02f), leftColumn.RectTransform) { MinSize = new Point(0, 20) }, TextManager.Get("SelectedSub"), font: GUI.SubHeadingFont); + + var moddedDropdown = new GUIDropDown(new RectTransform(new Vector2(1f, 0.02f), leftColumn.RectTransform), "", 3); + moddedDropdown.AddItem(TextManager.Get("clientpermission.all"), CategoryFilter.All); + moddedDropdown.AddItem(TextManager.Get("servertag.modded.false"), CategoryFilter.Vanilla); + moddedDropdown.AddItem(TextManager.Get("customrank"), CategoryFilter.Custom); + moddedDropdown.Select(0); + + var filterContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), leftColumn.RectTransform), isHorizontal: true) + { + Stretch = true + }; + + subList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.65f), leftColumn.RectTransform)) { ScrollBarVisible = true }; + + var searchTitle = new GUITextBlock(new RectTransform(new Vector2(0.001f, 1.0f), filterContainer.RectTransform), TextManager.Get("serverlog.filter"), textAlignment: Alignment.CenterLeft, font: GUI.Font); + var searchBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 1.0f), filterContainer.RectTransform, Anchor.CenterRight), font: GUI.Font, createClearButton: true); + filterContainer.RectTransform.MinSize = searchBox.RectTransform.MinSize; + searchBox.OnSelected += (sender, userdata) => { searchTitle.Visible = false; }; + searchBox.OnDeselected += (sender, userdata) => { searchTitle.Visible = true; }; + searchBox.OnTextChanged += (textBox, text) => { FilterSubs(subList, text); return true; }; + + moddedDropdown.OnSelected = (component, data) => + { + searchBox.Text = string.Empty; + subFilter = (CategoryFilter)data; + UpdateSubList(SubmarineInfo.SavedSubmarines); + return true; + }; + + subList.OnSelected = OnSubSelected; + + // New game right side + subPreviewContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f), rightColumn.RectTransform)) + { + Stretch = true + }; + + var firstPageButtonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.08f), + firstPageLayout.RectTransform), childAnchor: Anchor.BottomLeft, isHorizontal: true) + { + RelativeSpacing = 0.025f + }; + + InitialMoneyText = new GUITextBlock(new RectTransform(new Vector2(0.3f, 1f), firstPageButtonContainer.RectTransform), "", font: GUI.Style.Font, textColor: GUI.Style.Green, textAlignment: Alignment.CenterLeft) + { + TextGetter = () => + { + int initialMoney = CampaignMode.InitialMoney; + if (subList.SelectedData is SubmarineInfo subInfo) + { + initialMoney -= subInfo.Price; + } + initialMoney = Math.Max(initialMoney, 0); + return TextManager.GetWithVariable("campaignstartingmoney", "[money]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", initialMoney)); + } + }; + + CampaignCustomizeButton = new GUIButton(new RectTransform(new Vector2(0.25f, 1f), firstPageButtonContainer.RectTransform, Anchor.CenterLeft), TextManager.Get("SettingsButton")) + { + OnClicked = (tb, userdata) => + { + CreateCustomizeWindow(); + return true; + } + }; + + nextButton = new GUIButton(new RectTransform(new Vector2(0.4f, 1f), firstPageButtonContainer.RectTransform, Anchor.BottomRight), TextManager.Get("Next")) + { + OnClicked = (GUIButton btn, object userData) => + { + SetPage(1); + return false; + } + }; + + var disclaimerBtn = new GUIButton(new RectTransform(new Vector2(1.0f, 0.8f), rightColumn.RectTransform, Anchor.TopRight) { AbsoluteOffset = new Point(5) }, style: "GUINotificationButton") + { + IgnoreLayoutGroups = true, + OnClicked = (btn, userdata) => { GameMain.Instance.ShowCampaignDisclaimer(); return true; } + }; + disclaimerBtn.RectTransform.MaxSize = new Point((int)(30 * GUI.Scale)); + + columnContainer.Recalculate(); + leftColumn.Recalculate(); + rightColumn.Recalculate(); + + if (submarines != null) { UpdateSubList(submarines); } + } + + private void CreateSecondPage(GUILayoutGroup secondPageLayout) + { + secondPageLayout.RelativeSpacing = 0.01f; + + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.04f), secondPageLayout.RectTransform), + TextManager.Get("Crew"), font: GUI.Style.SubHeadingFont, textAlignment: Alignment.TopLeft); + + characterInfoColumns = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.86f), secondPageLayout.RectTransform), isHorizontal: true) + { + Stretch = true, + RelativeSpacing = 0.01f + }; + + var secondPageButtonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.08f), + secondPageLayout.RectTransform), childAnchor: Anchor.BottomLeft, isHorizontal: true) + { + RelativeSpacing = 0.2f + }; + + var backButton = new GUIButton(new RectTransform(new Vector2(0.4f, 1f), secondPageButtonContainer.RectTransform, Anchor.BottomRight), TextManager.Get("Back")) + { + OnClicked = (GUIButton btn, object userData) => + { + SetPage(0); + return false; + } + }; + + StartButton = new GUIButton(new RectTransform(new Vector2(0.4f, 1f), secondPageButtonContainer.RectTransform, Anchor.BottomRight), TextManager.Get("StartCampaignButton")) + { + OnClicked = FinishSetup + }; + } + + public void RandomizeCrew() + { + var characterInfos = new List<(CharacterInfo Info, JobPrefab Job)>(); + foreach (JobPrefab jobPrefab in JobPrefab.Prefabs) + { + for (int i = 0; i < jobPrefab.InitialCount; i++) + { + var variant = Rand.Range(0, jobPrefab.Variants); + characterInfos.Add((new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: jobPrefab, variant: variant), jobPrefab)); + } + } + + characterInfoColumns.ClearChildren(); + CharacterMenus?.ForEach(m => m.Dispose()); + CharacterMenus = new CharacterInfo.AppearanceCustomizationMenu[characterInfos.Count]; + + for (int i = 0; i < characterInfos.Count; i++) + { + var subLayout = new GUILayoutGroup(new RectTransform(new Vector2(1.0f / characterInfos.Count, 1.0f), + characterInfoColumns.RectTransform)); + + var (characterInfo, job) = characterInfos[i]; + + characterInfo.CreateIcon(new RectTransform(new Vector2(1.0f, 0.275f), subLayout.RectTransform)); + + var jobTextContainer = + new GUIFrame(new RectTransform(new Vector2(1.0f, 0.05f), subLayout.RectTransform), style: null); + var jobText = new GUITextBlock(new RectTransform(Vector2.One, jobTextContainer.RectTransform), job.Name, job.UIColor); + + var characterName = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.1f), subLayout.RectTransform)) + { + Text = characterInfo.Name, + UserData = "random" + }; + characterName.OnDeselected += (sender, key) => + { + if (string.IsNullOrWhiteSpace(sender.Text)) + { + characterInfo.Name = characterInfo.GetRandomName(Rand.RandSync.Unsynced); + sender.Text = characterInfo.Name; + sender.UserData = "random"; + } + else + { + characterInfo.Name = sender.Text; + sender.UserData = "user"; + } + }; + characterName.OnEnterPressed += (sender, text) => + { + sender.Deselect(); + return false; + }; + + var customizationFrame = + new GUIFrame(new RectTransform(new Vector2(1.0f, 0.6f), subLayout.RectTransform), style: null); + CharacterMenus[i] = + new CharacterInfo.AppearanceCustomizationMenu(characterInfo, customizationFrame, hasIcon: false) + { + OnHeadSwitch = menu => + { + if (characterName.UserData is string ud && ud == "random") + { + characterInfo.Name = characterInfo.GetRandomName(Rand.RandSync.Unsynced); + characterName.Text = characterInfo.Name; + characterName.UserData = "random"; + } + + StealRandomizeButton(menu, jobTextContainer); + } + }; + StealRandomizeButton(CharacterMenus[i], jobTextContainer); + } + } + + private void CreateCustomizeWindow() + { + CampaignCustomizeSettings = new GUIMessageBox("", "", new string[] { TextManager.Get("OK") }, new Vector2(0.2f, 0.2f)); + CampaignCustomizeSettings.Buttons[0].OnClicked += CampaignCustomizeSettings.Close; + + CampaignSettingsContent = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), CampaignCustomizeSettings.Content.RectTransform, Anchor.TopCenter)) + { + RelativeSpacing = 0.1f + }; + + if (MapGenerationParams.Instance.RadiationParams != null) + { + bool prevRadiationToggleEnabled = EnableRadiationToggle?.Selected ?? true; + EnableRadiationToggle = new GUITickBox(new RectTransform(new Vector2(0.3f, 0.3f), CampaignSettingsContent.RectTransform), TextManager.Get("CampaignOption.EnableRadiation"), font: GUI.Style.Font) + { + Selected = prevRadiationToggleEnabled, + ToolTip = TextManager.Get("campaignoption.enableradiation.tooltip") + }; + } + var maxMissionCountSettingHolder = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.3f), CampaignSettingsContent.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft) + { + Stretch = true, + ToolTip = TextManager.Get("maxmissioncounttooltip") + }; + var maxMissionCountDescription = new GUITextBlock(new RectTransform(new Vector2(0.7f, 0.0f), maxMissionCountSettingHolder.RectTransform), TextManager.Get("maxmissioncount", fallBackTag: "missions"), wrap: true); + var maxMissionCountContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 1.0f), maxMissionCountSettingHolder.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft) { RelativeSpacing = 0.05f, Stretch = true }; + var maxMissionCountButtons = new GUIButton[2]; + maxMissionCountButtons[0] = new GUIButton(new RectTransform(new Vector2(0.15f, 0.8f), maxMissionCountContainer.RectTransform), style: "GUIButtonToggleLeft") + { + OnClicked = (button, obj) => + { + MaxMissionCountText.Text = Math.Clamp(Int32.Parse(MaxMissionCountText.Text) - 1, CampaignSettings.MinMissionCountLimit, CampaignSettings.MaxMissionCountLimit).ToString(); + return true; + } + }; + + string prevMaxMissionCountText = MaxMissionCountText?.Text ?? CampaignSettings.DefaultMaxMissionCount.ToString(); + MaxMissionCountText = new GUITextBlock(new RectTransform(new Vector2(0.7f, 1.0f), maxMissionCountContainer.RectTransform), prevMaxMissionCountText, textAlignment: Alignment.Center, style: "GUITextBox"); + maxMissionCountButtons[1] = new GUIButton(new RectTransform(new Vector2(0.15f, 0.8f), maxMissionCountContainer.RectTransform), style: "GUIButtonToggleRight") + { + OnClicked = (button, obj) => + { + MaxMissionCountText.Text = Math.Clamp(Int32.Parse(MaxMissionCountText.Text) + 1, CampaignSettings.MinMissionCountLimit, CampaignSettings.MaxMissionCountLimit).ToString(); + return true; + } + }; + maxMissionCountContainer.Children.ForEach(c => c.ToolTip = maxMissionCountSettingHolder.ToolTip); + } + + private static void StealRandomizeButton(CharacterInfo.AppearanceCustomizationMenu menu, GUIComponent parent) + { + //This is just stupid + var randomizeButton = menu.RandomizeButton; + var oldButton = parent.GetChild(); + parent.RemoveChild(oldButton); + randomizeButton.RectTransform.Parent = parent.RectTransform; + randomizeButton.RectTransform.RelativeSize = Vector2.One * 1.3f; + } + + private bool FinishSetup(GUIButton btn, object userdata) + { + if (string.IsNullOrWhiteSpace(saveNameBox.Text)) + { + saveNameBox.Flash(GUI.Style.Red); + return false; + } + + SubmarineInfo selectedSub = null; + + if (!(subList.SelectedData is SubmarineInfo)) { return false; } + selectedSub = subList.SelectedData as SubmarineInfo; + + if (selectedSub.SubmarineClass == SubmarineClass.Undefined) + { + new GUIMessageBox(TextManager.Get("error"), TextManager.Get("undefinedsubmarineselected")); + return false; + } + + if (string.IsNullOrEmpty(selectedSub.MD5Hash.Hash)) + { + ((GUITextBlock)subList.SelectedComponent).TextColor = Color.DarkRed * 0.8f; + subList.SelectedComponent.CanBeFocused = false; + subList.Deselect(); + return false; + } + + string savePath = SaveUtil.CreateSavePath(SaveUtil.SaveType.Singleplayer, saveNameBox.Text); + bool hasRequiredContentPackages = selectedSub.RequiredContentPackagesInstalled; + + CampaignSettings settings = new CampaignSettings(); + settings.RadiationEnabled = EnableRadiationToggle?.Selected ?? false; + if (MaxMissionCountText != null && Int32.TryParse(MaxMissionCountText.Text, out int missionCount)) + { + settings.MaxMissionCount = missionCount; + } + else + { + settings.MaxMissionCount = CampaignSettings.DefaultMaxMissionCount; + } + + if (selectedSub.HasTag(SubmarineTag.Shuttle) || !hasRequiredContentPackages) + { + if (!hasRequiredContentPackages) + { + var msgBox = new GUIMessageBox(TextManager.Get("ContentPackageMismatch"), + TextManager.GetWithVariable("ContentPackageMismatchWarning", "[requiredcontentpackages]", string.Join(", ", selectedSub.RequiredContentPackages)), + new string[] { TextManager.Get("Yes"), TextManager.Get("No") }); + + msgBox.Buttons[0].OnClicked = msgBox.Close; + msgBox.Buttons[0].OnClicked += (button, obj) => + { + if (GUIMessageBox.MessageBoxes.Count == 0) + { + StartNewGame?.Invoke(selectedSub, savePath, seedBox.Text, settings); + } + return true; + }; + + msgBox.Buttons[1].OnClicked = msgBox.Close; + } + + if (selectedSub.HasTag(SubmarineTag.Shuttle)) + { + var msgBox = new GUIMessageBox(TextManager.Get("ShuttleSelected"), + TextManager.Get("ShuttleWarning"), + new string[] { TextManager.Get("Yes"), TextManager.Get("No") }); + + msgBox.Buttons[0].OnClicked = (button, obj) => + { + StartNewGame?.Invoke(selectedSub, savePath, seedBox.Text, settings); + return true; + }; + msgBox.Buttons[0].OnClicked += msgBox.Close; + + msgBox.Buttons[1].OnClicked = msgBox.Close; + return false; + } + } + else + { + StartNewGame?.Invoke(selectedSub, savePath, seedBox.Text, settings); + } + + return true; + } + + public void RandomizeSeed() + { + seedBox.Text = ToolBox.RandomSeed(8); + } + + private void FilterSubs(GUIListBox subList, string filter) + { + foreach (GUIComponent child in subList.Content.Children) + { + var sub = child.UserData as SubmarineInfo; + if (sub == null) { return; } + child.Visible = string.IsNullOrEmpty(filter) || sub.DisplayName.ToLower().Contains(filter.ToLower()); + } + } + + private bool OnSubSelected(GUIComponent component, object obj) + { + if (subPreviewContainer == null) { return false; } + (subPreviewContainer.Parent as GUILayoutGroup)?.Recalculate(); + subPreviewContainer.ClearChildren(); + + if (!(obj is SubmarineInfo sub)) { return true; } +#if !DEBUG + if (sub.Price > CampaignMode.InitialMoney && !GameMain.DebugDraw) + { + SetPage(0); + nextButton.Enabled = false; + return false; + } +#endif + nextButton.Enabled = true; + sub.CreatePreviewWindow(subPreviewContainer); + return true; + } + + public void CreateDefaultSaveName() + { + string savePath = SaveUtil.CreateSavePath(SaveUtil.SaveType.Singleplayer); + saveNameBox.Text = Path.GetFileNameWithoutExtension(savePath); + } + + public void UpdateSubList(IEnumerable submarines) + { + List subsToShow; + if (subFilter != CategoryFilter.All) + { + subsToShow = submarines.Where(s => s.IsCampaignCompatibleIgnoreClass && s.IsVanillaSubmarine() == (subFilter == CategoryFilter.Vanilla)).ToList(); + } + else + { + string downloadFolder = Path.GetFullPath(SaveUtil.SubmarineDownloadFolder); + subsToShow = submarines.Where(s => s.IsCampaignCompatibleIgnoreClass && Path.GetDirectoryName(Path.GetFullPath(s.FilePath)) != downloadFolder).ToList(); + } + + subsToShow.Sort((s1, s2) => + { + int p1 = s1.Price > CampaignMode.InitialMoney ? 10 : 0; + int p2 = s2.Price > CampaignMode.InitialMoney ? 10 : 0; + return p1.CompareTo(p2) * 100 + s1.Name.CompareTo(s2.Name); + }); + + subList.ClearChildren(); + + foreach (SubmarineInfo sub in subsToShow) + { + var textBlock = new GUITextBlock( + new RectTransform(new Vector2(1, 0.1f), subList.Content.RectTransform) { MinSize = new Point(0, 30) }, + ToolBox.LimitString(sub.DisplayName, GUI.Font, subList.Rect.Width - 65), style: "ListBoxElement") + { + ToolTip = sub.Description, + UserData = sub + }; + + if (!sub.RequiredContentPackagesInstalled) + { + textBlock.TextColor = Color.Lerp(textBlock.TextColor, Color.DarkRed, .5f); + textBlock.ToolTip = TextManager.Get("ContentPackageMismatch") + "\n\n" + textBlock.RawToolTip; + } + + var priceText = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), textBlock.RectTransform, Anchor.CenterRight), + TextManager.GetWithVariable("currencyformat", "[credits]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", sub.Price)), textAlignment: Alignment.CenterRight, font: GUI.SmallFont) + { + TextColor = sub.Price > CampaignMode.InitialMoney ? GUI.Style.Red : textBlock.TextColor * 0.8f, + ToolTip = textBlock.ToolTip + }; +#if !DEBUG + if (!GameMain.DebugDraw) + { + if (sub.Price > CampaignMode.InitialMoney || !sub.IsCampaignCompatible) + { + textBlock.CanBeFocused = false; + textBlock.TextColor *= 0.5f; + } + } +#endif + } + if (SubmarineInfo.SavedSubmarines.Any()) + { + var validSubs = subsToShow.Where(s => s.IsCampaignCompatible && s.Price <= CampaignMode.InitialMoney).ToList(); + if (validSubs.Count > 0) + { + subList.Select(validSubs[Rand.Int(validSubs.Count)]); + } + } + } + + private List prevSaveFiles; + public void UpdateLoadMenu(IEnumerable saveFiles = null) + { + prevSaveFiles?.Clear(); + prevSaveFiles = null; + loadGameContainer.ClearChildren(); + + if (saveFiles == null) + { + saveFiles = SaveUtil.GetSaveFiles(SaveUtil.SaveType.Singleplayer); + } + + var leftColumn = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 1.0f), loadGameContainer.RectTransform), childAnchor: Anchor.TopCenter) + { + Stretch = true, + RelativeSpacing = 0.03f + }; + + saveList = new GUIListBox(new RectTransform(Vector2.One, leftColumn.RectTransform)) + { + OnSelected = SelectSaveFile + }; + + new GUIButton(new RectTransform(new Vector2(0.6f, 0.08f), leftColumn.RectTransform), TextManager.Get("showinfolder")) + { + OnClicked = (btn, userdata) => + { + try + { + ToolBox.OpenFileWithShell(SaveUtil.SaveFolder); + } + catch (Exception e) + { + new GUIMessageBox( + TextManager.Get("error"), + TextManager.GetWithVariables("showinfoldererror", new string[] { "[folder]", "[errormessage]" }, new string[] { SaveUtil.SaveFolder, e.Message })); + } + return true; + } + }; + + foreach (string saveFile in saveFiles) + { + string fileName = saveFile; + string subName = ""; + string saveTime = ""; + string contentPackageStr = ""; + var saveFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.1f), saveList.Content.RectTransform) { MinSize = new Point(0, 45) }, style: "ListBoxElement") + { + UserData = saveFile + }; + + var nameText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), saveFrame.RectTransform), "") + { + CanBeFocused = false + }; + + bool isCompatible = true; + prevSaveFiles ??= new List(); + + nameText.Text = Path.GetFileNameWithoutExtension(saveFile); + XDocument doc = SaveUtil.LoadGameSessionDoc(saveFile); + + if (doc?.Root == null) + { + DebugConsole.ThrowError("Error loading save file \"" + saveFile + "\". The file may be corrupted."); + nameText.TextColor = GUI.Style.Red; + continue; + } + if (doc.Root.GetChildElement("multiplayercampaign") != null) + { + //multiplayer campaign save in the wrong folder -> don't show the save + saveList.Content.RemoveChild(saveFrame); + continue; + } + subName = doc.Root.GetAttributeString("submarine", ""); + saveTime = doc.Root.GetAttributeString("savetime", ""); + isCompatible = SaveUtil.IsSaveFileCompatible(doc); + contentPackageStr = doc.Root.GetAttributeString("selectedcontentpackages", ""); + prevSaveFiles?.Add(saveFile); + if (!string.IsNullOrEmpty(saveTime) && long.TryParse(saveTime, out long unixTime)) + { + DateTime time = ToolBox.Epoch.ToDateTime(unixTime); + saveTime = time.ToString(); + } + if (!string.IsNullOrEmpty(contentPackageStr)) + { + List contentPackagePaths = contentPackageStr.Split('|').ToList(); + if (!GameSession.IsCompatibleWithEnabledContentPackages(contentPackagePaths, out string errorMsg)) + { + nameText.TextColor = GUI.Style.Red; + saveFrame.ToolTip = string.Join("\n", errorMsg, TextManager.Get("campaignmode.contentpackagemismatchwarning")); + } + } + if (!isCompatible) + { + nameText.TextColor = GUI.Style.Red; + saveFrame.ToolTip = TextManager.Get("campaignmode.incompatiblesave"); + } + + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), saveFrame.RectTransform, Anchor.BottomLeft), + text: subName, font: GUI.SmallFont) + { + CanBeFocused = false, + UserData = fileName + }; + + new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), saveFrame.RectTransform), + text: saveTime, textAlignment: Alignment.Right, font: GUI.SmallFont) + { + CanBeFocused = false, + UserData = fileName + }; + } + + saveList.Content.RectTransform.SortChildren((c1, c2) => + { + string file1 = c1.GUIComponent.UserData as string; + string file2 = c2.GUIComponent.UserData as string; + DateTime file1WriteTime = DateTime.MinValue; + DateTime file2WriteTime = DateTime.MinValue; + try + { + file1WriteTime = File.GetLastWriteTime(file1); + } + catch + { + //do nothing - DateTime.MinValue will be used and the element will get sorted at the bottom of the list + }; + try + { + file2WriteTime = File.GetLastWriteTime(file2); + } + catch + { + //do nothing - DateTime.MinValue will be used and the element will get sorted at the bottom of the list + }; + return file2WriteTime.CompareTo(file1WriteTime); + }); + + loadGameButton = new GUIButton(new RectTransform(new Vector2(0.45f, 0.12f), loadGameContainer.RectTransform, Anchor.BottomRight), TextManager.Get("LoadButton")) + { + OnClicked = (btn, obj) => + { + if (string.IsNullOrWhiteSpace(saveList.SelectedData as string)) { return false; } + LoadGame?.Invoke(saveList.SelectedData as string); + return true; + }, + Enabled = false + }; + } + + private bool SelectSaveFile(GUIComponent component, object obj) + { + string fileName = (string)obj; + + XDocument doc = SaveUtil.LoadGameSessionDoc(fileName); + if (doc?.Root == null) + { + DebugConsole.ThrowError("Error loading save file \"" + fileName + "\". The file may be corrupted."); + return false; + } + + loadGameButton.Enabled = SaveUtil.IsSaveFileCompatible(doc); + + RemoveSaveFrame(); + + string subName = doc.Root.GetAttributeString("submarine", ""); + string saveTime = doc.Root.GetAttributeString("savetime", "unknown"); + if (long.TryParse(saveTime, out long unixTime)) + { + DateTime time = ToolBox.Epoch.ToDateTime(unixTime); + saveTime = time.ToString(); + } + + string mapseed = doc.Root.GetAttributeString("mapseed", "unknown"); + + var saveFileFrame = new GUIFrame(new RectTransform(new Vector2(0.45f, 0.6f), loadGameContainer.RectTransform, Anchor.TopRight) + { + RelativeOffset = new Vector2(0.0f, 0.1f) + }, style: "InnerFrame") + { + UserData = "savefileframe" + }; + + var titleText = new GUITextBlock(new RectTransform(new Vector2(0.9f, 0.2f), saveFileFrame.RectTransform, Anchor.TopCenter) + { + RelativeOffset = new Vector2(0, 0.05f) + }, + Path.GetFileNameWithoutExtension(fileName), font: GUI.LargeFont, textAlignment: Alignment.Center); + titleText.Text = ToolBox.LimitString(titleText.Text, titleText.Font, titleText.Rect.Width); + + var layoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 0.5f), saveFileFrame.RectTransform, Anchor.Center) + { + RelativeOffset = new Vector2(0, 0.1f) + }); + + new GUITextBlock(new RectTransform(new Vector2(1, 0), layoutGroup.RectTransform), $"{TextManager.Get("Submarine")} : {subName}", font: GUI.SmallFont); + new GUITextBlock(new RectTransform(new Vector2(1, 0), layoutGroup.RectTransform), $"{TextManager.Get("LastSaved")} : {saveTime}", font: GUI.SmallFont); + new GUITextBlock(new RectTransform(new Vector2(1, 0), layoutGroup.RectTransform), $"{TextManager.Get("MapSeed")} : {mapseed}", font: GUI.SmallFont); + + new GUIButton(new RectTransform(new Vector2(0.4f, 0.15f), saveFileFrame.RectTransform, Anchor.BottomCenter) + { + RelativeOffset = new Vector2(0, 0.1f) + }, TextManager.Get("Delete"), style: "GUIButtonSmall") + { + UserData = fileName, + OnClicked = DeleteSave + }; + + return true; + } + + private bool DeleteSave(GUIButton button, object obj) + { + string saveFile = obj as string; + if (obj == null) { return false; } + + string header = TextManager.Get("deletedialoglabel"); + string body = TextManager.GetWithVariable("deletedialogquestion", "[file]", Path.GetFileNameWithoutExtension(saveFile)); + + EventEditorScreen.AskForConfirmation(header, body, () => + { + SaveUtil.DeleteSave(saveFile); + prevSaveFiles?.RemoveAll(s => s.StartsWith(saveFile)); + UpdateLoadMenu(prevSaveFiles.ToList()); + return true; + }); + + return true; + } + + private void RemoveSaveFrame() + { + GUIComponent prevFrame = null; + foreach (GUIComponent child in loadGameContainer.Children) + { + if (child.UserData as string != "savefileframe") continue; + + prevFrame = child; + break; + } + loadGameContainer.RemoveChild(prevFrame); + } + } +} diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignUI.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignUI.cs index 947077154..27efd044d 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignUI.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignUI.cs @@ -628,7 +628,7 @@ namespace Barotrauma { TextGetter = () => { - return TextManager.AddPunctuation(':', TextManager.Get("Missions"), $"{Campaign.NumberOfMissionsAtLocation(destination)}/{Campaign.Settings.MaxMissionCount}"); + return TextManager.AddPunctuation(':', TextManager.Get("Missions"), $"{Campaign.NumberOfMissionsAtLocation(destination)}/{Campaign.Settings.TotalMaxMissionCount}"); } }; @@ -735,7 +735,7 @@ namespace Barotrauma private void UpdateMaxMissions(Location location) { - hasMaxMissions = Campaign.NumberOfMissionsAtLocation(location) >= Campaign.Settings.MaxMissionCount; + hasMaxMissions = Campaign.NumberOfMissionsAtLocation(location) >= Campaign.Settings.TotalMaxMissionCount; } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/CharacterEditor/CharacterEditorScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/CharacterEditor/CharacterEditorScreen.cs index 9f158fe81..0a553e0a8 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/CharacterEditor/CharacterEditorScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/CharacterEditor/CharacterEditorScreen.cs @@ -182,7 +182,7 @@ namespace Barotrauma.CharacterEditor showSpritesheet = false; isFrozen = false; autoFreeze = false; - limbPairEditing = true; + limbPairEditing = false; uniformScaling = true; lockSpriteOrigin = true; lockSpritePosition = false; @@ -483,24 +483,20 @@ namespace Barotrauma.CharacterEditor // It's possible that the physics are disabled, because the angle widgets handle input logic in the draw method (which they shouldn't) character.AnimController.Collider.PhysEnabled = true; } - if (character.IsHumanoid) + animTestPoseToggle.Enabled = CurrentAnimation.IsGroundedAnimation; + if (animTestPoseToggle.Enabled) { - animTestPoseToggle.Enabled = CurrentAnimation.IsGroundedAnimation; - if (animTestPoseToggle.Enabled) + if (PlayerInput.KeyHit(Keys.X)) { - if (PlayerInput.KeyHit(Keys.X)) - { - SetToggle(animTestPoseToggle, !animTestPoseToggle.Selected); - } - } - else - { - animTestPoseToggle.Selected = false; + SetToggle(animTestPoseToggle, !animTestPoseToggle.Selected); } } + else + { + animTestPoseToggle.Selected = false; + } if (PlayerInput.KeyHit(InputType.Run)) { - // TODO: refactor this horrible hacky index manipulation mess int index = 0; bool isSwimming = character.AnimController.ForceSelectAnimationType == AnimationType.SwimFast || character.AnimController.ForceSelectAnimationType == AnimationType.SwimSlow; bool isMovingFast = character.AnimController.ForceSelectAnimationType == AnimationType.Run || character.AnimController.ForceSelectAnimationType == AnimationType.SwimFast; @@ -508,23 +504,25 @@ namespace Barotrauma.CharacterEditor { if (isSwimming || !character.AnimController.CanWalk) { - index = !character.AnimController.CanWalk ? 0 : (int)AnimationType.SwimSlow - 1; + index = !character.AnimController.CanWalk ? (int)AnimationType.SwimFast : (int)AnimationType.SwimSlow; } else { - index = (int)AnimationType.Walk - 1; + index = (int)AnimationType.Walk; } + index -= 1; } else { if (isSwimming || !character.AnimController.CanWalk) { - index = !character.AnimController.CanWalk ? 1 : (int)AnimationType.SwimFast - 1; + index = !character.AnimController.CanWalk ? (int)AnimationType.SwimSlow : (int)AnimationType.SwimFast; } else { - index = (int)AnimationType.Run - 1; + index = (int)AnimationType.Run; } + index -= 1; } if (animSelection.SelectedIndex != index) { @@ -1072,7 +1070,7 @@ namespace Barotrauma.CharacterEditor { if (jointCreationMode == JointCreationMode.Create) { - jointEndLimb = GetClosestLimbOnSpritesheet(PlayerInput.MousePosition, l => l != null && l != jointStartLimb && l.ActiveSprite != null); + jointEndLimb = GetClosestLimbOnSpritesheet(PlayerInput.MousePosition, l => l != null && l != jointStartLimb && l.ActiveSprite != null && !l.Hidden); if (jointEndLimb != null && PlayerInput.PrimaryMouseButtonClicked()) { Vector2 anchor1 = anchor1Pos.HasValue ? anchor1Pos.Value / spriteSheetZoom : Vector2.Zero; @@ -1085,7 +1083,7 @@ namespace Barotrauma.CharacterEditor } else if (PlayerInput.PrimaryMouseButtonClicked()) { - jointStartLimb = GetClosestLimbOnSpritesheet(PlayerInput.MousePosition, l => selectedLimbs.Contains(l)); + jointStartLimb = GetClosestLimbOnSpritesheet(PlayerInput.MousePosition, l => selectedLimbs.Contains(l) && !l.Hidden); anchor1Pos = GetLimbSpritesheetRect(jointStartLimb).Center.ToVector2() - PlayerInput.MousePosition; jointCreationMode = JointCreationMode.Create; } @@ -1094,7 +1092,7 @@ namespace Barotrauma.CharacterEditor { if (jointCreationMode == JointCreationMode.Create) { - jointEndLimb = GetClosestLimbOnRagdoll(PlayerInput.MousePosition, l => l != null && l != jointStartLimb && l.ActiveSprite != null); + jointEndLimb = GetClosestLimbOnRagdoll(PlayerInput.MousePosition, l => l != null && l != jointStartLimb && l.ActiveSprite != null && !l.Hidden); if (jointEndLimb != null && PlayerInput.PrimaryMouseButtonClicked()) { Vector2 anchor1 = anchor1Pos ?? Vector2.Zero; @@ -1105,7 +1103,7 @@ namespace Barotrauma.CharacterEditor } else if (PlayerInput.PrimaryMouseButtonClicked()) { - jointStartLimb = GetClosestLimbOnRagdoll(PlayerInput.MousePosition, l => selectedLimbs.Contains(l)); + jointStartLimb = GetClosestLimbOnRagdoll(PlayerInput.MousePosition, l => selectedLimbs.Contains(l) && !l.Hidden); anchor1Pos = ConvertUnits.ToDisplayUnits(jointStartLimb.body.FarseerBody.GetLocalPoint(ScreenToSim(PlayerInput.MousePosition))); jointCreationMode = JointCreationMode.Create; } @@ -1185,8 +1183,15 @@ namespace Barotrauma.CharacterEditor private void CreateLimb(XElement newElement) { - var lastLimbElement = RagdollParams.MainElement.Elements("limb").Last(); - lastLimbElement.AddAfterSelf(newElement); + var lastElement = RagdollParams.MainElement.GetChildElements("limb").LastOrDefault(); + if (lastElement != null) + { + lastElement.AddAfterSelf(newElement); + } + else + { + RagdollParams.MainElement.AddFirst(newElement); + } var newLimbParams = new RagdollParams.LimbParams(newElement, RagdollParams); RagdollParams.Limbs.Add(newLimbParams); character.AnimController.Recreate(); @@ -1217,12 +1222,7 @@ namespace Barotrauma.CharacterEditor new XAttribute("limb1anchor", $"{a1.X.Format(2)}, {a1.Y.Format(2)}"), new XAttribute("limb2anchor", $"{a2.X.Format(2)}, {a2.Y.Format(2)}") ); - var lastJointElement = RagdollParams.MainElement.Elements("joint").LastOrDefault(); - if (lastJointElement == null) - { - // If no joints exist, use the last limb element. - lastJointElement = RagdollParams.MainElement.Elements("limb").LastOrDefault(); - } + var lastJointElement = RagdollParams.MainElement.GetChildElements("joint").LastOrDefault() ?? RagdollParams.MainElement.GetChildElements("limb").LastOrDefault(); if (lastJointElement == null) { DebugConsole.ThrowError(GetCharacterEditorTranslation("CantAddJointsNoLimbElements")); @@ -1581,7 +1581,6 @@ namespace Barotrauma.CharacterEditor Character.Controlled = character; SetWallCollisions(character.AnimController.forceStanding); CreateTextures(); - limbPairEditing = character.IsHumanoid; CreateGUI(); ClearWidgets(); ClearSelection(); @@ -1803,6 +1802,7 @@ namespace Barotrauma.CharacterEditor { case AnimationType.Walk: case AnimationType.Run: + case AnimationType.Crouch: if (!ragdollParams.CanWalk) { continue; } break; case AnimationType.SwimSlow: @@ -1819,8 +1819,8 @@ namespace Barotrauma.CharacterEditor { AllFiles.Add(configFilePath); } - limbPairEditing = false; SpawnCharacter(configFilePath, ragdollParams); + limbPairEditing = false; limbsToggle.Selected = true; recalculateColliderToggle.Selected = true; lockSpriteOriginToggle.Selected = false; @@ -2196,7 +2196,7 @@ namespace Barotrauma.CharacterEditor animTestPoseToggle = new GUITickBox(new RectTransform(toggleSize, layoutGroup.RectTransform), GetCharacterEditorTranslation("AnimationTestPose")) { Selected = character.AnimController.AnimationTestPose, - Enabled = character.IsHumanoid, + Enabled = true, OnSelected = box => { character.AnimController.AnimationTestPose = box.Selected; @@ -2599,11 +2599,15 @@ namespace Barotrauma.CharacterEditor var layoutGroupAnimation = new GUILayoutGroup(new RectTransform(Vector2.One, animationControls.RectTransform), childAnchor: Anchor.TopLeft) { CanBeFocused = false }; var animationSelectionElement = new GUIFrame(new RectTransform(new Point(elementSize.X * 2 - (int)(5 * GUI.xScale), elementSize.Y), layoutGroupAnimation.RectTransform), style: null); var animationSelectionText = new GUITextBlock(new RectTransform(new Point(elementSize.X, elementSize.Y), animationSelectionElement.RectTransform), GetCharacterEditorTranslation("SelectedAnimation") + ": ", Color.WhiteSmoke, textAlignment: Alignment.Center); - animSelection = new GUIDropDown(new RectTransform(new Point((int)(100 * GUI.xScale), elementSize.Y), animationSelectionElement.RectTransform, Anchor.TopRight), elementCount: 4); + animSelection = new GUIDropDown(new RectTransform(new Point((int)(100 * GUI.xScale), elementSize.Y), animationSelectionElement.RectTransform, Anchor.TopRight), elementCount: 5); if (character.AnimController.CanWalk) { animSelection.AddItem(AnimationType.Walk.ToString(), AnimationType.Walk); animSelection.AddItem(AnimationType.Run.ToString(), AnimationType.Run); + if (character.IsHumanoid) + { + animSelection.AddItem(AnimationType.Crouch.ToString(), AnimationType.Crouch); + } } animSelection.AddItem(AnimationType.SwimSlow.ToString(), AnimationType.SwimSlow); animSelection.AddItem(AnimationType.SwimFast.ToString(), AnimationType.SwimFast); @@ -2622,25 +2626,15 @@ namespace Barotrauma.CharacterEditor switch (character.AnimController.ForceSelectAnimationType) { case AnimationType.Walk: - character.AnimController.forceStanding = true; - character.ForceRun = false; - if (!wallCollisionsEnabled) - { - SetWallCollisions(true); - } - if (previousAnim != AnimationType.Walk && previousAnim != AnimationType.Run) - { - TeleportTo(spawnPosition); - } - break; case AnimationType.Run: + case AnimationType.Crouch: character.AnimController.forceStanding = true; - character.ForceRun = true; + character.ForceRun = character.AnimController.ForceSelectAnimationType == AnimationType.Run; if (!wallCollisionsEnabled) { SetWallCollisions(true); } - if (previousAnim != AnimationType.Walk && previousAnim != AnimationType.Run) + if (previousAnim != AnimationType.Walk && previousAnim != AnimationType.Run && previousAnim != AnimationType.Crouch) { TeleportTo(spawnPosition); } @@ -2766,7 +2760,7 @@ namespace Barotrauma.CharacterEditor return false; } #endif - if (!string.IsNullOrEmpty(RagdollParams.Texture) && !File.Exists(RagdollParams.Texture)) + if (!character.IsHuman && !string.IsNullOrEmpty(RagdollParams.Texture) && !File.Exists(RagdollParams.Texture)) { DebugConsole.ThrowError($"Invalid texture path: {RagdollParams.Texture}"); return false; @@ -3046,21 +3040,24 @@ namespace Barotrauma.CharacterEditor loadBox.Buttons[1].OnClicked += (btn, data) => { string fileName = Path.GetFileNameWithoutExtension(selectedFile); - if (character.IsHumanoid) + if (character.IsHumanoid && character.AnimController is HumanoidAnimController humanAnimController) { switch (selectedType) { case AnimationType.Walk: - character.AnimController.WalkParams = HumanWalkParams.GetAnimParams(character, fileName); + humanAnimController.WalkParams = HumanWalkParams.GetAnimParams(character, fileName); break; case AnimationType.Run: - character.AnimController.RunParams = HumanRunParams.GetAnimParams(character, fileName); + humanAnimController.RunParams = HumanRunParams.GetAnimParams(character, fileName); + break; + case AnimationType.Crouch: + humanAnimController.HumanCrouchParams = HumanCrouchParams.GetAnimParams(character, fileName); break; case AnimationType.SwimSlow: - character.AnimController.SwimSlowParams = HumanSwimSlowParams.GetAnimParams(character, fileName); + humanAnimController.SwimSlowParams = HumanSwimSlowParams.GetAnimParams(character, fileName); break; case AnimationType.SwimFast: - character.AnimController.SwimFastParams = HumanSwimFastParams.GetAnimParams(character, fileName); + humanAnimController.SwimFastParams = HumanSwimFastParams.GetAnimParams(character, fileName); break; default: DebugConsole.ThrowError(GetCharacterEditorTranslation("AnimationTypeNotImplemented").Replace("[type]", selectedType.ToString())); @@ -3866,7 +3863,7 @@ namespace Barotrauma.CharacterEditor { // Head angle DrawRadialWidget(spriteBatch, SimToScreen(head.SimPosition), animParams.HeadAngle, GetCharacterEditorTranslation("HeadAngle"), Color.White, - angle => TryUpdateAnimParam("headangle", angle), circleRadius: 25, rotationOffset: collider.Rotation + MathHelper.Pi, clockWise: dir < 0, wrapAnglePi: true, holdPosition: true); + angle => TryUpdateAnimParam("headangle", angle), circleRadius: 25, rotationOffset: -collider.Rotation + head.Params.GetSpriteOrientation() * dir, clockWise: dir < 0, wrapAnglePi: true, holdPosition: true); // Head position and leaning Color color = GUI.Style.Red; if (animParams.IsGroundedAnimation) @@ -3975,7 +3972,7 @@ namespace Barotrauma.CharacterEditor } // Torso angle DrawRadialWidget(spriteBatch, SimToScreen(referencePoint), animParams.TorsoAngle, GetCharacterEditorTranslation("TorsoAngle"), Color.White, - angle => TryUpdateAnimParam("torsoangle", angle), rotationOffset: collider.Rotation + MathHelper.Pi, clockWise: dir < 0, wrapAnglePi: true, holdPosition: true); + angle => TryUpdateAnimParam("torsoangle", angle), rotationOffset: -collider.Rotation + torso.Params.GetSpriteOrientation() * dir, clockWise: dir < 0, wrapAnglePi: true, holdPosition: true); Color color = Color.DodgerBlue; if (animParams.IsGroundedAnimation) { @@ -4078,7 +4075,7 @@ namespace Barotrauma.CharacterEditor if (tail != null && fishParams != null) { DrawRadialWidget(spriteBatch, SimToScreen(tail.SimPosition), fishParams.TailAngle, GetCharacterEditorTranslation("TailAngle"), Color.White, - angle => TryUpdateAnimParam("tailangle", angle), circleRadius: 25, rotationOffset: collider.Rotation + MathHelper.Pi, clockWise: dir < 0, wrapAnglePi: true, holdPosition: true); + angle => TryUpdateAnimParam("tailangle", angle), circleRadius: 25, rotationOffset: -collider.Rotation + tail.Params.GetSpriteOrientation() * dir, clockWise: dir < 0, wrapAnglePi: true, holdPosition: true); } // Foot angle if (foot != null) @@ -4104,13 +4101,13 @@ namespace Barotrauma.CharacterEditor fishParams.FootAnglesInRadians[limb.Params.ID] = MathHelper.ToRadians(angle); TryUpdateAnimParam("footangles", fishParams.FootAngles); }, - circleRadius: 25, rotationOffset: collider.Rotation, clockWise: dir < 0, wrapAnglePi: true, autoFreeze: true); + circleRadius: 25, rotationOffset: -collider.Rotation + limb.Params.GetSpriteOrientation() * dir, clockWise: dir < 0, wrapAnglePi: true, autoFreeze: true); } } else if (humanParams != null) { DrawRadialWidget(spriteBatch, SimToScreen(foot.SimPosition), humanParams.FootAngle, GetCharacterEditorTranslation("FootAngle"), Color.White, - angle => TryUpdateAnimParam("footangle", angle), circleRadius: 25, rotationOffset: collider.Rotation + MathHelper.Pi, clockWise: dir < 0, wrapAnglePi: true); + angle => TryUpdateAnimParam("footangle", angle), circleRadius: 25, rotationOffset: -collider.Rotation + foot.Params.GetSpriteOrientation() * dir, clockWise: dir > 0, wrapAnglePi: true); } // Grounded only if (groundedParams != null) @@ -4152,7 +4149,7 @@ namespace Barotrauma.CharacterEditor float offset = 0.1f; w.refresh = () => { - var refPoint = SimToScreen(collider.SimPosition + GetSimSpaceForward() * offset); + var refPoint = SimToScreen(character.AnimController.Collider.SimPosition + GetSimSpaceForward() * offset); var handMovement = ConvertUnits.ToDisplayUnits(humanGroundedParams.HandMoveAmount); w.DrawPos = refPoint + new Vector2(handMovement.X * character.AnimController.Dir, handMovement.Y) * Cam.Zoom; }; @@ -4167,7 +4164,7 @@ namespace Barotrauma.CharacterEditor { if (w.IsSelected) { - GUI.DrawLine(sp, w.DrawPos, SimToScreen(collider.SimPosition + GetSimSpaceForward() * offset), GUI.Style.Green); + GUI.DrawLine(sp, w.DrawPos, SimToScreen(character.AnimController.Collider.SimPosition + GetSimSpaceForward() * offset), GUI.Style.Green); } }; }).Draw(spriteBatch, deltaTime); @@ -4731,7 +4728,7 @@ namespace Barotrauma.CharacterEditor rotation: 0, origin: orig, sourceRectangle: wearable.InheritSourceRect ? limb.ActiveSprite.SourceRect : wearable.Sprite.SourceRect, - scale: (wearable.InheritTextureScale ? 1 : wearable.Scale / RagdollParams.TextureScale) * spriteSheetZoom, + scale: (wearable.InheritScale ? 1 : wearable.Scale / RagdollParams.TextureScale) * spriteSheetZoom, effects: SpriteEffects.None, color: Color.White, layerDepth: 0); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/CharacterEditor/Wizard.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/CharacterEditor/Wizard.cs index 6d6d44024..5c41420ac 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/CharacterEditor/Wizard.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/CharacterEditor/Wizard.cs @@ -262,7 +262,7 @@ namespace Barotrauma.CharacterEditor { FileSelection.OnFileSelected = (file) => { - string relativePath = UpdaterUtil.GetRelativePath(Path.GetFullPath(file), Environment.CurrentDirectory); + string relativePath = Path.GetRelativePath(Environment.CurrentDirectory, Path.GetFullPath(file)); string destinationPath = relativePath; //copy file to XML path if it's not located relative to the game's files @@ -284,7 +284,7 @@ namespace Barotrauma.CharacterEditor } isTextureSelected = true; - texturePathElement.Text = destinationPath; + texturePathElement.Text = destinationPath.CleanUpPath(); }; FileSelection.ClearFileTypeFilters(); FileSelection.AddFileTypeFilter("PNG", "*.png"); @@ -431,7 +431,7 @@ namespace Barotrauma.CharacterEditor box.Header.Font = GUI.LargeFont; box.Content.ChildAnchor = Anchor.TopCenter; box.Content.AbsoluteSpacing = (int)(20 * GUI.Scale); - int elementSize = (int)(30 * GUI.Scale); + int elementSize = (int)(40 * GUI.Scale); var frame = new GUIFrame(new RectTransform(new Point(box.Content.Rect.Width - (int)(80 * GUI.xScale), box.Content.Rect.Height - (int)(200 * GUI.yScale)), box.Content.RectTransform, Anchor.Center), style: null, color: ParamsEditor.Color) { @@ -625,8 +625,12 @@ namespace Barotrauma.CharacterEditor var jointsElement = new GUIFrame(new RectTransform(new Vector2(1, 0.05f), content.RectTransform), style: null) { CanBeFocused = false }; new GUITextBlock(new RectTransform(new Vector2(0.2f, 1f), jointsElement.RectTransform), GetCharacterEditorTranslation("Joints"), font: GUI.SubHeadingFont); var jointButtonElement = new GUIFrame(new RectTransform(new Vector2(0.5f, 1f), jointsElement.RectTransform) - { RelativeOffset = new Vector2(0.15f, 0) }, style: null) - { CanBeFocused = false }; + { + RelativeOffset = new Vector2(0.15f, 0) + }, style: null) + { + CanBeFocused = false + }; var jointsList = new GUIListBox(new RectTransform(new Vector2(1, 0.45f), content.RectTransform)); var removeJointButton = new GUIButton(new RectTransform(new Point(jointButtonElement.Rect.Height, jointButtonElement.Rect.Height), jointButtonElement.RectTransform), style: "GUIMinusButton") { @@ -824,7 +828,7 @@ namespace Barotrauma.CharacterEditor { CanBeFocused = false }; - var group = new GUILayoutGroup(new RectTransform(Vector2.One, limbElement.RectTransform)) { AbsoluteSpacing = 2 }; + var group = new GUILayoutGroup(new RectTransform(Vector2.One, limbElement.RectTransform)) { AbsoluteSpacing = 16 }; var label = new GUITextBlock(new RectTransform(new Point(group.Rect.Width, elementSize), group.RectTransform), name, font: GUI.SubHeadingFont); var idField = new GUIFrame(new RectTransform(new Point(group.Rect.Width, elementSize), group.RectTransform), style: null); var nameField = new GUIFrame(new RectTransform(new Point(group.Rect.Width, elementSize), group.RectTransform), style: null); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/EventEditor/EventEditorScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/EventEditor/EventEditorScreen.cs index 75bbf5cfc..d85bf42ee 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/EventEditor/EventEditorScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/EventEditor/EventEditorScreen.cs @@ -541,7 +541,7 @@ namespace Barotrauma private XElement? ExportXML() { - XElement mainElement = new XElement("ScriptedEvent", new XAttribute("identifier", projectName.RemoveWhitespace().ToLower())); + XElement mainElement = new XElement("ScriptedEvent", new XAttribute("identifier", projectName.RemoveWhitespace().ToLowerInvariant())); EditorNode? startNode = null; foreach (EditorNode eventNode in nodeList.Where(node => node is EventNode || node is SpecialNode)) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/GameScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/GameScreen.cs index a179926ae..9a0675524 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/GameScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/GameScreen.cs @@ -25,6 +25,8 @@ namespace Barotrauma public Effect PostProcessEffect { get; private set; } public Effect GradientEffect { get; private set; } public Effect GrainEffect { get; private set; } + public Effect ThresholdTintEffect { get; private set; } + public Effect BlueprintEffect { get; set; } public GameScreen(GraphicsDevice graphics, ContentManager content) { @@ -37,19 +39,20 @@ namespace Barotrauma CreateRenderTargets(graphics); }; + Effect LoadEffect(string path) + => content.Load(path #if LINUX || OSX - //var blurEffect = content.Load("Effects/blurshader_opengl"); - damageEffect = content.Load("Effects/damageshader_opengl"); - PostProcessEffect = content.Load("Effects/postprocess_opengl"); - GradientEffect = content.Load("Effects/gradientshader_opengl"); - GrainEffect = content.Load("Effects/grainshader_opengl"); -#else - //var blurEffect = content.Load("Effects/blurshader"); - damageEffect = content.Load("Effects/damageshader"); - PostProcessEffect = content.Load("Effects/postprocess"); - GradientEffect = content.Load("Effects/gradientshader"); - GrainEffect = content.Load("Effects/grainshader"); + +"_opengl" #endif + ); + + //var blurEffect = LoadEffect("Effects/blurshader"); + damageEffect = LoadEffect("Effects/damageshader"); + PostProcessEffect = LoadEffect("Effects/postprocess"); + GradientEffect = LoadEffect("Effects/gradientshader"); + GrainEffect = LoadEffect("Effects/grainshader"); + ThresholdTintEffect = LoadEffect("Effects/thresholdtint"); + BlueprintEffect = LoadEffect("Effects/blueprintshader"); damageStencil = TextureLoader.FromFile("Content/Map/walldamage.png"); damageEffect.Parameters["xStencil"].SetValue(damageStencil); @@ -89,8 +92,7 @@ namespace Barotrauma } } - if (GameMain.GameSession != null) GameMain.GameSession.AddToGUIUpdateList(); - + GameMain.GameSession?.AddToGUIUpdateList(); Character.AddAllToGUIUpdateList(); } @@ -136,7 +138,7 @@ namespace Barotrauma for (int i = 0; i < Submarine.MainSubs.Length; i++) { if (Submarine.MainSubs[i] == null) continue; - if (Level.Loaded != null && Submarine.MainSubs[i].WorldPosition.Y < Level.MaxEntityDepth) continue; + if (Level.Loaded != null && Submarine.MainSubs[i].WorldPosition.Y < Level.MaxEntityDepth) { continue; } Vector2 position = Submarine.MainSubs[i].SubBody != null ? Submarine.MainSubs[i].WorldPosition : Submarine.MainSubs[i].HiddenSubPosition; @@ -148,6 +150,14 @@ namespace Barotrauma } } + if (!GUI.DisableHUD) + { + foreach (Character c in Character.CharacterList) + { + c.DrawGUIMessages(spriteBatch, cam); + } + } + GUI.Draw(cam, spriteBatch); spriteBatch.End(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/LevelEditorScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/LevelEditorScreen.cs index 62fcc7ba7..fe25d54b9 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/LevelEditorScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/LevelEditorScreen.cs @@ -36,7 +36,7 @@ namespace Barotrauma private readonly GUITextBox seedBox; - private readonly GUITickBox lightingEnabled, cursorLightEnabled; + private readonly GUITickBox lightingEnabled, cursorLightEnabled, mirrorLevel; private Sprite editingSprite; @@ -74,9 +74,7 @@ namespace Barotrauma ruinParamsList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.1f), paddedLeftPanel.RectTransform)); ruinParamsList.OnSelected += (GUIComponent component, object obj) => { - var ruinGenerationParams = obj as RuinGenerationParams; - editorContainer.ClearChildren(); - new SerializableEntityEditor(editorContainer.Content.RectTransform, ruinGenerationParams, false, true, elementHeight: 20); + CreateOutpostGenerationParamsEditor(obj as OutpostGenerationParams); return true; }; @@ -95,101 +93,7 @@ namespace Barotrauma outpostParamsList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.2f), paddedLeftPanel.RectTransform)); outpostParamsList.OnSelected += (GUIComponent component, object obj) => { - var outpostGenerationParams = obj as OutpostGenerationParams; - editorContainer.ClearChildren(); - var outpostParamsEditor = new SerializableEntityEditor(editorContainer.Content.RectTransform, outpostGenerationParams, false, true, elementHeight: 20); - - // location type ------------------------- - - var locationTypeGroup = new GUILayoutGroup(new RectTransform(new Point(editorContainer.Content.Rect.Width, 20)), isHorizontal: true, childAnchor: Anchor.CenterLeft) - { - Stretch = true - }; - - new GUITextBlock(new RectTransform(new Vector2(0.5f, 1f), locationTypeGroup.RectTransform), TextManager.Get("outpostmoduleallowedlocationtypes"), textAlignment: Alignment.CenterLeft); - HashSet availableLocationTypes = new HashSet { "any" }; - foreach (LocationType locationType in LocationType.List) { availableLocationTypes.Add(locationType.Identifier); } - - var locationTypeDropDown = new GUIDropDown(new RectTransform(new Vector2(0.5f, 1f), locationTypeGroup.RectTransform), - text: string.Join(", ", outpostGenerationParams.AllowedLocationTypes.Select(lt => TextManager.Capitalize(lt)) ?? "any".ToEnumerable()), selectMultiple: true); - foreach (string locationType in availableLocationTypes) - { - locationTypeDropDown.AddItem(TextManager.Capitalize(locationType), locationType); - if (outpostGenerationParams.AllowedLocationTypes.Contains(locationType)) - { - locationTypeDropDown.SelectItem(locationType); - } - } - if (!outpostGenerationParams.AllowedLocationTypes.Any()) - { - locationTypeDropDown.SelectItem("any"); - } - - locationTypeDropDown.OnSelected += (_, __) => - { - outpostGenerationParams.SetAllowedLocationTypes(locationTypeDropDown.SelectedDataMultiple.Cast()); - locationTypeDropDown.Text = ToolBox.LimitString(locationTypeDropDown.Text, locationTypeDropDown.Font, locationTypeDropDown.Rect.Width); - return true; - }; - locationTypeGroup.RectTransform.MinSize = new Point(locationTypeGroup.Rect.Width, locationTypeGroup.RectTransform.Children.Max(c => c.MinSize.Y)); - - outpostParamsEditor.AddCustomContent(locationTypeGroup, 100); - - // module count ------------------------- - - var moduleLabel = new GUITextBlock(new RectTransform(new Point(editorContainer.Content.Rect.Width, (int)(70 * GUI.Scale))), TextManager.Get("submarinetype.outpostmodules"), font: GUI.SubHeadingFont); - outpostParamsEditor.AddCustomContent(moduleLabel, 100); - - foreach (KeyValuePair moduleCount in outpostGenerationParams.ModuleCounts) - { - var moduleCountGroup = new GUILayoutGroup(new RectTransform(new Point(editorContainer.Content.Rect.Width, (int)(25 * GUI.Scale))), isHorizontal: true, childAnchor: Anchor.CenterLeft); - new GUITextBlock(new RectTransform(new Vector2(0.5f, 1f), moduleCountGroup.RectTransform), TextManager.Capitalize(moduleCount.Key), textAlignment: Alignment.CenterLeft); - new GUINumberInput(new RectTransform(new Vector2(0.5f, 1f), moduleCountGroup.RectTransform), GUINumberInput.NumberType.Int) - { - MinValueInt = 0, - MaxValueInt = 100, - IntValue = moduleCount.Value, - OnValueChanged = (numInput) => - { - outpostGenerationParams.SetModuleCount(moduleCount.Key, numInput.IntValue); - if (numInput.IntValue == 0) - { - outpostParamsList.Select(outpostParamsList.SelectedData); - } - } - }; - moduleCountGroup.RectTransform.MinSize = new Point(moduleCountGroup.Rect.Width, moduleCountGroup.RectTransform.Children.Max(c => c.MinSize.Y)); - outpostParamsEditor.AddCustomContent(moduleCountGroup, 100); - } - - // add module count ------------------------- - - var addModuleCountGroup = new GUILayoutGroup(new RectTransform(new Point(editorContainer.Content.Rect.Width, (int)(40 * GUI.Scale))), isHorizontal: true, childAnchor: Anchor.Center); - - HashSet availableFlags = new HashSet(); - foreach (string flag in OutpostGenerationParams.Params.SelectMany(p => p.ModuleCounts.Select(m => m.Key))) { availableFlags.Add(flag); } - foreach (var sub in SubmarineInfo.SavedSubmarines) - { - if (sub.OutpostModuleInfo == null) { continue; } - foreach (string flag in sub.OutpostModuleInfo.ModuleFlags) { availableFlags.Add(flag); } - } - - var moduleTypeDropDown = new GUIDropDown(new RectTransform(new Vector2(0.8f, 0.8f), addModuleCountGroup.RectTransform), - text: TextManager.Get("leveleditor.addmoduletype")); - foreach (string flag in availableFlags) - { - if (outpostGenerationParams.ModuleCounts.Any(mc => mc.Key.Equals(flag, StringComparison.OrdinalIgnoreCase))) { continue; } - moduleTypeDropDown.AddItem(TextManager.Capitalize(flag), flag); - } - moduleTypeDropDown.OnSelected += (_, userdata) => - { - outpostGenerationParams.SetModuleCount(userdata as string, 1); - outpostParamsList.Select(outpostParamsList.SelectedData); - return true; - }; - addModuleCountGroup.RectTransform.MinSize = new Point(addModuleCountGroup.Rect.Width, addModuleCountGroup.RectTransform.Children.Max(c => c.MinSize.Y)); - outpostParamsEditor.AddCustomContent(addModuleCountGroup, 100); - + CreateOutpostGenerationParamsEditor(obj as OutpostGenerationParams); return true; }; @@ -239,10 +143,12 @@ namespace Barotrauma editorContainer = new GUIListBox(new RectTransform(new Vector2(1.0f, 1.0f), paddedRightPanel.RectTransform)); - var seedContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), paddedRightPanel.RectTransform), isHorizontal: true); + var seedContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.02f), paddedRightPanel.RectTransform), isHorizontal: true); new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), seedContainer.RectTransform), TextManager.Get("leveleditor.levelseed")); seedBox = new GUITextBox(new RectTransform(new Vector2(0.5f, 1.0f), seedContainer.RectTransform), ToolBox.RandomSeed(8)); + mirrorLevel = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.02f), paddedRightPanel.RectTransform), TextManager.Get("mirrorentityx")); + new GUIButton(new RectTransform(new Vector2(1.0f, 0.05f), paddedRightPanel.RectTransform), TextManager.Get("leveleditor.generate")) { @@ -253,7 +159,7 @@ namespace Barotrauma GameMain.LightManager.ClearLights(); LevelData levelData = LevelData.CreateRandom(seedBox.Text, generationParams: selectedParams); levelData.ForceOutpostGenerationParams = outpostParamsList.SelectedData as OutpostGenerationParams; - Level.Generate(levelData, mirror: false); + Level.Generate(levelData, mirror: mirrorLevel.Selected); GameMain.LightManager.AddLight(pointerLightSource); if (!wasLevelLoaded || cam.Position.X < 0 || cam.Position.Y < 0 || cam.Position.Y > Level.Loaded.Size.X || cam.Position.Y > Level.Loaded.Size.Y) { @@ -408,7 +314,7 @@ namespace Barotrauma editorContainer.ClearChildren(); ruinParamsList.Content.ClearChildren(); - foreach (RuinGenerationParams genParams in RuinGenerationParams.List) + foreach (RuinGenerationParams genParams in RuinGenerationParams.RuinParams) { new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), ruinParamsList.Content.RectTransform) { MinSize = new Point(0, 20) }, genParams.Name) @@ -500,6 +406,104 @@ namespace Barotrauma } } + private void CreateOutpostGenerationParamsEditor(OutpostGenerationParams outpostGenerationParams) + { + editorContainer.ClearChildren(); + var outpostParamsEditor = new SerializableEntityEditor(editorContainer.Content.RectTransform, outpostGenerationParams, false, true, elementHeight: 20); + + // location type ------------------------- + + var locationTypeGroup = new GUILayoutGroup(new RectTransform(new Point(editorContainer.Content.Rect.Width, 20)), isHorizontal: true, childAnchor: Anchor.CenterLeft) + { + Stretch = true + }; + + new GUITextBlock(new RectTransform(new Vector2(0.5f, 1f), locationTypeGroup.RectTransform), TextManager.Get("outpostmoduleallowedlocationtypes"), textAlignment: Alignment.CenterLeft); + HashSet availableLocationTypes = new HashSet { "any" }; + foreach (LocationType locationType in LocationType.List) { availableLocationTypes.Add(locationType.Identifier); } + + var locationTypeDropDown = new GUIDropDown(new RectTransform(new Vector2(0.5f, 1f), locationTypeGroup.RectTransform), + text: string.Join(", ", outpostGenerationParams.AllowedLocationTypes.Select(lt => TextManager.Capitalize(lt)) ?? "any".ToEnumerable()), selectMultiple: true); + foreach (string locationType in availableLocationTypes) + { + locationTypeDropDown.AddItem(TextManager.Capitalize(locationType), locationType); + if (outpostGenerationParams.AllowedLocationTypes.Contains(locationType)) + { + locationTypeDropDown.SelectItem(locationType); + } + } + if (!outpostGenerationParams.AllowedLocationTypes.Any()) + { + locationTypeDropDown.SelectItem("any"); + } + + locationTypeDropDown.OnSelected += (_, __) => + { + outpostGenerationParams.SetAllowedLocationTypes(locationTypeDropDown.SelectedDataMultiple.Cast()); + locationTypeDropDown.Text = ToolBox.LimitString(locationTypeDropDown.Text, locationTypeDropDown.Font, locationTypeDropDown.Rect.Width); + return true; + }; + locationTypeGroup.RectTransform.MinSize = new Point(locationTypeGroup.Rect.Width, locationTypeGroup.RectTransform.Children.Max(c => c.MinSize.Y)); + + outpostParamsEditor.AddCustomContent(locationTypeGroup, 100); + + // module count ------------------------- + + var moduleLabel = new GUITextBlock(new RectTransform(new Point(editorContainer.Content.Rect.Width, (int)(70 * GUI.Scale))), TextManager.Get("submarinetype.outpostmodules"), font: GUI.SubHeadingFont); + outpostParamsEditor.AddCustomContent(moduleLabel, 100); + + foreach (KeyValuePair moduleCount in outpostGenerationParams.ModuleCounts) + { + var moduleCountGroup = new GUILayoutGroup(new RectTransform(new Point(editorContainer.Content.Rect.Width, (int)(25 * GUI.Scale))), isHorizontal: true, childAnchor: Anchor.CenterLeft); + new GUITextBlock(new RectTransform(new Vector2(0.5f, 1f), moduleCountGroup.RectTransform), TextManager.Capitalize(moduleCount.Key), textAlignment: Alignment.CenterLeft); + new GUINumberInput(new RectTransform(new Vector2(0.5f, 1f), moduleCountGroup.RectTransform), GUINumberInput.NumberType.Int) + { + MinValueInt = 0, + MaxValueInt = 100, + IntValue = moduleCount.Value, + OnValueChanged = (numInput) => + { + outpostGenerationParams.SetModuleCount(moduleCount.Key, numInput.IntValue); + if (numInput.IntValue == 0) + { + outpostParamsList.Select(outpostParamsList.SelectedData); + } + } + }; + moduleCountGroup.RectTransform.MinSize = new Point(moduleCountGroup.Rect.Width, moduleCountGroup.RectTransform.Children.Max(c => c.MinSize.Y)); + outpostParamsEditor.AddCustomContent(moduleCountGroup, 100); + } + + // add module count ------------------------- + + var addModuleCountGroup = new GUILayoutGroup(new RectTransform(new Point(editorContainer.Content.Rect.Width, (int)(40 * GUI.Scale))), isHorizontal: true, childAnchor: Anchor.Center); + + HashSet availableFlags = new HashSet(); + foreach (string flag in OutpostGenerationParams.Params.SelectMany(p => p.ModuleCounts.Select(m => m.Key))) { availableFlags.Add(flag); } + foreach (var sub in SubmarineInfo.SavedSubmarines) + { + if (sub.OutpostModuleInfo == null) { continue; } + foreach (string flag in sub.OutpostModuleInfo.ModuleFlags) { availableFlags.Add(flag); } + } + + var moduleTypeDropDown = new GUIDropDown(new RectTransform(new Vector2(0.8f, 0.8f), addModuleCountGroup.RectTransform), + text: TextManager.Get("leveleditor.addmoduletype")); + foreach (string flag in availableFlags) + { + if (outpostGenerationParams.ModuleCounts.Any(mc => mc.Key.Equals(flag, StringComparison.OrdinalIgnoreCase))) { continue; } + moduleTypeDropDown.AddItem(TextManager.Capitalize(flag), flag); + } + moduleTypeDropDown.OnSelected += (_, userdata) => + { + outpostGenerationParams.SetModuleCount(userdata as string, 1); + outpostParamsList.Select(outpostParamsList.SelectedData); + return true; + }; + addModuleCountGroup.RectTransform.MinSize = new Point(addModuleCountGroup.Rect.Width, addModuleCountGroup.RectTransform.Children.Max(c => c.MinSize.Y)); + outpostParamsEditor.AddCustomContent(addModuleCountGroup, 100); + + } + private void CreateLevelObjectEditor(LevelObjectPrefab levelObjectPrefab) { editorContainer.ClearChildren(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs index dcb4ae206..2a12028a9 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs @@ -40,7 +40,7 @@ namespace Barotrauma private readonly GUIFrame[] menuTabs; - private CampaignSetupUI campaignSetupUI; + private SinglePlayerCampaignSetupUI campaignSetupUI; private GUITextBox serverNameBox, /*portBox, queryPortBox,*/ passwordBox, maxPlayersBox; private GUITickBox isPublicBox, wrongPasswordBanBox, karmaBox; @@ -435,7 +435,7 @@ namespace Barotrauma menuTabs[(int)Tab.Settings] = new GUIFrame(new RectTransform(new Vector2(relativeSize.X, 0.8f), GUI.Canvas, anchor, pivot, minSize, maxSize) { RelativeOffset = relativeSpacing }, style: null); - menuTabs[(int)Tab.NewGame] = new GUIFrame(new RectTransform(relativeSize, GUI.Canvas, anchor, pivot, minSize, maxSize) { RelativeOffset = relativeSpacing }); + menuTabs[(int)Tab.NewGame] = new GUIFrame(new RectTransform(relativeSize * new Vector2(1.0f, 1.15f), GUI.Canvas, anchor, pivot, minSize, maxSize) { RelativeOffset = relativeSpacing }); menuTabs[(int)Tab.LoadGame] = new GUIFrame(new RectTransform(relativeSize, GUI.Canvas, anchor, pivot, minSize, maxSize) { RelativeOffset = relativeSpacing }); CreateCampaignSetupUI(); @@ -594,6 +594,8 @@ namespace Barotrauma GameMain.Instance.ShowCampaignDisclaimer(() => { SelectTab(null, Tab.NewGame); }); return true; } + campaignSetupUI.RandomizeCrew(); + campaignSetupUI.SetPage(0); campaignSetupUI.CreateDefaultSaveName(); campaignSetupUI.RandomizeSeed(); campaignSetupUI.UpdateSubList(SubmarineInfo.SavedSubmarines); @@ -985,24 +987,32 @@ namespace Barotrauma if (selectedTab < Tab.Empty && menuTabs[(int)selectedTab] != null) { menuTabs[(int)selectedTab].AddToGUIUpdateList(); + switch (selectedTab) + { + case Tab.NewGame: + campaignSetupUI.CharacterMenus?.ForEach(m => m.AddToGUIUpdateList()); + break; + } } } public override void Update(double deltaTime) { -#if !DEBUG -#if USE_STEAM +#if !DEBUG && USE_STEAM if (GameMain.Config.UseSteamMatchmaking) { hostServerButton.Enabled = Steam.SteamManager.IsInitialized; } steamWorkshopButton.Enabled = Steam.SteamManager.IsInitialized; -#endif -#else -#if USE_STEAM +#elif USE_STEAM steamWorkshopButton.Enabled = true; #endif -#endif + switch (selectedTab) + { + case Tab.NewGame: + campaignSetupUI.Update(); + break; + } } public void DrawBackground(GraphicsDevice graphics, SpriteBatch spriteBatch) @@ -1119,6 +1129,11 @@ namespace Barotrauma selectedSub = new SubmarineInfo(Path.Combine(SaveUtil.TempPath, selectedSub.Name + ".sub")); GameMain.GameSession = new GameSession(selectedSub, saveName, GameModePreset.SinglePlayerCampaign, settings, mapSeed); + GameMain.GameSession.CrewManager.CharacterInfos.Clear(); + foreach (var characterInfo in campaignSetupUI.CharacterMenus.Select(m => m.CharacterInfo)) + { + GameMain.GameSession.CrewManager.AddCharacterInfo(characterInfo); + } ((SinglePlayerCampaign)GameMain.GameSession.GameMode).LoadNewLevel(); } @@ -1146,7 +1161,7 @@ namespace Barotrauma menuTabs[(int)Tab.NewGame].ClearChildren(); menuTabs[(int)Tab.LoadGame].ClearChildren(); - var innerNewGame = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.9f), menuTabs[(int)Tab.NewGame].RectTransform, Anchor.Center) { RelativeOffset = new Vector2(0.0f, 0.025f) }) + var innerNewGame = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.9f), menuTabs[(int)Tab.NewGame].RectTransform, Anchor.Center)) { Stretch = true, RelativeSpacing = 0.02f @@ -1154,30 +1169,14 @@ namespace Barotrauma var newGameContent = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.95f), innerNewGame.RectTransform, Anchor.Center), style: "InnerFrame"); - var paddedNewGame = new GUIFrame(new RectTransform(new Vector2(0.95f), newGameContent.RectTransform, Anchor.Center), style: null); var paddedLoadGame = new GUIFrame(new RectTransform(new Vector2(0.9f, 0.9f), menuTabs[(int)Tab.LoadGame].RectTransform, Anchor.Center) { AbsoluteOffset = new Point(0, 10) }, style: null); - campaignSetupUI = new CampaignSetupUI(false, paddedNewGame, paddedLoadGame, SubmarineInfo.SavedSubmarines) + campaignSetupUI = new SinglePlayerCampaignSetupUI(newGameContent, paddedLoadGame, SubmarineInfo.SavedSubmarines) { LoadGame = LoadGame, StartNewGame = StartGame }; - - var startButtonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), innerNewGame.RectTransform, Anchor.Center), isHorizontal: true, childAnchor: Anchor.BottomRight) - { - RelativeSpacing = 0.05f - }; - campaignSetupUI.StartButton.RectTransform.Parent = startButtonContainer.RectTransform; - campaignSetupUI.StartButton.RectTransform.MinSize = new Point( - (int)(campaignSetupUI.StartButton.TextBlock.TextSize.X * 1.5f), - campaignSetupUI.StartButton.RectTransform.MinSize.Y); - startButtonContainer.RectTransform.MinSize = new Point(0, campaignSetupUI.StartButton.RectTransform.MinSize.Y); - if (campaignSetupUI.CampaignCustomizeButton != null) - { - campaignSetupUI.CampaignCustomizeButton.RectTransform.Parent = startButtonContainer.RectTransform; - } - campaignSetupUI.InitialMoneyText.RectTransform.Parent = startButtonContainer.RectTransform; } private void CreateHostServerFields() diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs index 7e9f8c97a..40b99af75 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs @@ -12,7 +12,6 @@ namespace Barotrauma { partial class NetLobbyScreen : Screen { - private readonly List characterSprites = new List(); //private readonly List jobPreferenceSprites = new List(); private readonly GUIFrame infoFrame, modeFrame; @@ -65,7 +64,7 @@ namespace Barotrauma private readonly GUITickBox[] missionTypeTickBoxes; private readonly GUIListBox missionTypeList; - + public GUITextBox SeedBox { get; private set; @@ -78,11 +77,12 @@ namespace Barotrauma public static GUIButton JobInfoFrame; private readonly GUITickBox spectateBox; - + private readonly GUIFrame playerInfoContainer; private GUILayoutGroup infoContainer; private GUIComponent changesPendingText; + private bool createPendingChangesText = true; public GUIButton PlayerFrame; private readonly GUIComponent subPreviewContainer; @@ -103,11 +103,12 @@ namespace Barotrauma private GUIFrame characterInfoFrame; private GUIFrame appearanceFrame; - public GUIListBox HeadSelectionList; + public CharacterInfo.AppearanceCustomizationMenu CharacterAppearanceCustomizationMenu; public GUIFrame JobSelectionFrame; + public GUIFrame JobPreferenceContainer; public GUIListBox JobList; - + private float autoRestartTimer; //persistent characterinfo provided by the server @@ -142,7 +143,7 @@ namespace Barotrauma get; private set; } - + public GUITextBox ServerMessage { get; @@ -238,7 +239,7 @@ namespace Barotrauma get { return shuttleList.SelectedData as SubmarineInfo; } } - public CampaignSetupUI CampaignSetupUI; + public MultiPlayerCampaignSetupUI CampaignSetupUI; public List CampaignSubmarines = new List(); // Passed onto the gamesession when created @@ -340,7 +341,7 @@ namespace Barotrauma Stretch = true, RelativeSpacing = panelSpacing }; - + GUILayoutGroup panelHolder = new GUILayoutGroup(new RectTransform(new Vector2(0.7f, 1.0f), panelContainer.RectTransform)) { Stretch = true, @@ -413,7 +414,7 @@ namespace Barotrauma Stretch = true }; FileTransferProgressBar = new GUIProgressBar(new RectTransform(new Vector2(0.6f, 1.0f), fileTransferBottom.RectTransform), 0.0f, Color.DarkGreen); - FileTransferProgressText = new GUITextBlock(new RectTransform(Vector2.One, FileTransferProgressBar.RectTransform), "", + FileTransferProgressText = new GUITextBlock(new RectTransform(Vector2.One, FileTransferProgressBar.RectTransform), "", font: GUI.SmallFont, textAlignment: Alignment.CenterLeft); new GUIButton(new RectTransform(new Vector2(0.4f, 1.0f), fileTransferBottom.RectTransform), TextManager.Get("cancel"), style: "GUIButtonSmall") { @@ -541,7 +542,7 @@ namespace Barotrauma // Chat input - var chatRow = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.07f), socialHolder.RectTransform), + var chatRow = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.07f), socialHolder.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft) { Stretch = true @@ -604,7 +605,7 @@ namespace Barotrauma Font = GUI.SmallFont }; - roundControlsHolder = new GUILayoutGroup(new RectTransform(Vector2.One, bottomBarRight.RectTransform), + roundControlsHolder = new GUILayoutGroup(new RectTransform(Vector2.One, bottomBarRight.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft) { Stretch = true @@ -650,7 +651,7 @@ namespace Barotrauma return true; } }; - clientDisabledElements.Add(autoRestartBoxContainer); + clientDisabledElements.Add(autoRestartBoxContainer); //-------------------------------------------------------------------------------------------------------------------------------- //infoframe contents @@ -659,7 +660,7 @@ namespace Barotrauma //server info ------------------------------------------------------------------ // Server Info Header - GUILayoutGroup lobbyHeader = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.1f), infoFrameContent.RectTransform), + GUILayoutGroup lobbyHeader = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.1f), infoFrameContent.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft) { RelativeSpacing = 0.05f, @@ -813,7 +814,7 @@ namespace Barotrauma shuttleTickBox = new GUITickBox(new RectTransform(Vector2.One, shuttleHolder.RectTransform), TextManager.Get("RespawnShuttle")) { - Selected = true, + Selected = true, OnSelected = (GUITickBox box) => { GameMain.Client.ServerSettings.ClientAdminWrite(ServerSettings.NetFlags.Misc, useRespawnShuttle: box.Selected); @@ -851,13 +852,13 @@ namespace Barotrauma //------------------------------------------------------------------------------------------------------------------ // Gamemode panel //------------------------------------------------------------------------------------------------------------------ - + GUILayoutGroup gameModeBackground = new GUILayoutGroup(new RectTransform(Vector2.One, gameModeContainer.RectTransform), isHorizontal: true) { Stretch = true, RelativeSpacing = 0.01f }; - + GUILayoutGroup gameModeHolder = new GUILayoutGroup(new RectTransform(new Vector2(0.333f, 1.0f), gameModeBackground.RectTransform)) { Stretch = true @@ -874,7 +875,7 @@ namespace Barotrauma { OnSelected = VotableClicked }; - + foreach (GameModePreset mode in GameModePreset.List) { if (mode.IsSinglePlayer) { continue; } @@ -900,7 +901,7 @@ namespace Barotrauma modeTitle.State = modeDescription.State = c.State; }; - new GUIImage(new RectTransform(new Vector2(0.2f, 0.8f), modeFrame.RectTransform, Anchor.CenterLeft) { RelativeOffset = new Vector2(0.02f, 0.0f) }, + new GUIImage(new RectTransform(new Vector2(0.2f, 0.8f), modeFrame.RectTransform, Anchor.CenterLeft) { RelativeOffset = new Vector2(0.02f, 0.0f) }, style: "GameModeIcon." + mode.Identifier, scaleToFit: true); modeFrame.RectTransform.MinSize = new Point(0, (int)(modeContent.Children.Sum(c => c.Rect.Height + modeContent.AbsoluteSpacing) / modeContent.RectTransform.RelativeSize.Y)); @@ -928,17 +929,17 @@ namespace Barotrauma OnClicked = (_, __) => { CoroutineManager.StartCoroutine(WaitForStartRound(ContinueCampaignButton), "WaitForStartRound"); - GameMain.Client?.RequestStartRound(true); - return true; + GameMain.Client?.RequestStartRound(true); + return true; } }; QuitCampaignButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.3f), campaignContent.RectTransform), TextManager.Get("quitbutton"), textAlignment: Alignment.Center) { - OnClicked = (_, __) => + OnClicked = (_, __) => { - GameMain.Client.RequestSelectMode(modeList.Content.GetChildIndex(modeList.Content.GetChildByUserData(GameModePreset.Sandbox))); - return true; + GameMain.Client.RequestSelectMode(modeList.Content.GetChildIndex(modeList.Content.GetChildByUserData(GameModePreset.Sandbox))); + return true; } }; @@ -950,7 +951,7 @@ namespace Barotrauma Stretch = true }; - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.055f), missionHolder.RectTransform) { MinSize = new Point(0, 25) }, + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.055f), missionHolder.RectTransform) { MinSize = new Point(0, 25) }, TextManager.Get("MissionType"), font: GUI.SubHeadingFont); missionTypeList = new GUIListBox(new RectTransform(Vector2.One, missionHolder.RectTransform)) { @@ -981,28 +982,30 @@ namespace Barotrauma Visible = false, CanBeFocused = false }; - continue; } - missionTypeTickBoxes[index] = new GUITickBox(new RectTransform(Vector2.One, frame.RectTransform), - TextManager.Get("MissionType." + missionType.ToString())) + else { - UserData = (int)missionType, - ToolTip = TextManager.Get("MissionTypeDescription." + missionType.ToString(), returnNull: true), - OnSelected = (tickbox) => + missionTypeTickBoxes[index] = new GUITickBox(new RectTransform(Vector2.One, frame.RectTransform), + TextManager.Get("MissionType." + missionType.ToString())) { - int missionTypeOr = tickbox.Selected ? (int)tickbox.UserData : (int)MissionType.None; - int missionTypeAnd = (int)MissionType.All & (!tickbox.Selected ? (~(int)tickbox.UserData) : (int)MissionType.All); - GameMain.Client.ServerSettings.ClientAdminWrite(ServerSettings.NetFlags.Misc, (int)missionTypeOr, (int)missionTypeAnd); - return true; - } - }; - frame.RectTransform.MinSize = missionTypeTickBoxes[index].RectTransform.MinSize; + UserData = (int)missionType, + ToolTip = TextManager.Get("MissionTypeDescription." + missionType.ToString(), returnNull: true), + OnSelected = (tickbox) => + { + int missionTypeOr = tickbox.Selected ? (int)tickbox.UserData : (int)MissionType.None; + int missionTypeAnd = (int)MissionType.All & (!tickbox.Selected ? (~(int)tickbox.UserData) : (int)MissionType.All); + GameMain.Client.ServerSettings.ClientAdminWrite(ServerSettings.NetFlags.Misc, (int)missionTypeOr, (int)missionTypeAnd); + return true; + } + }; + frame.RectTransform.MinSize = missionTypeTickBoxes[index].RectTransform.MinSize; + } index++; } clientDisabledElements.AddRange(missionTypeTickBoxes); //------------------------------------------------------------------ - // settings panel + // settings panel //------------------------------------------------------------------ GUILayoutGroup settingsHolder = new GUILayoutGroup(new RectTransform(new Vector2(0.333f, 1.0f), gameModeBackground.RectTransform)) @@ -1083,7 +1086,7 @@ namespace Barotrauma } }; - traitorProbabilityText = new GUITextBlock(new RectTransform(new Vector2(0.7f, 1.0f), traitorProbContainer.RectTransform), TextManager.Get("No"), + traitorProbabilityText = new GUITextBlock(new RectTransform(new Vector2(0.7f, 1.0f), traitorProbContainer.RectTransform), TextManager.Get("No"), textAlignment: Alignment.Center, style: "GUITextBox"); traitorProbabilityButtons[1] = new GUIButton(new RectTransform(new Vector2(0.15f, 1.0f), traitorProbContainer.RectTransform), style: "GUIButtonToggleRight") { @@ -1249,12 +1252,10 @@ namespace Barotrauma { chatInput.Deselect(); CampaignCharacterDiscarded = false; - HeadSelectionList = null; + + CharacterAppearanceCustomizationMenu?.Dispose(); JobSelectionFrame = null; - foreach (Sprite sprite in characterSprites) { sprite.Remove(); } - characterSprites.Clear(); - /*foreach (Sprite sprite in jobPreferenceSprites) { sprite.Remove(); } jobPreferenceSprites.Clear();*/ } @@ -1263,8 +1264,8 @@ namespace Barotrauma { if (GameMain.NetworkMember == null) { return; } - if (HeadSelectionList != null) { HeadSelectionList.Visible = false; } - if (JobSelectionFrame != null) { JobSelectionFrame.Visible = false; } + CharacterAppearanceCustomizationMenu?.Dispose(); + JobSelectionFrame = null; infoFrameContent.Recalculate(); @@ -1302,7 +1303,7 @@ namespace Barotrauma { spectateButton.Visible = false; } - SetSpectate(spectateBox.Selected); + SetSpectate(spectateBox.Selected); if (GameMain.Client != null) { @@ -1324,7 +1325,7 @@ namespace Barotrauma { publicOrPrivate.Text = isPublic ? TextManager.Get("PublicLobbyTag") : TextManager.Get("PrivateLobbyTag"); } - + public void RefreshEnabledElements() { ServerName.Readonly = !GameMain.Client.HasPermission(ClientPermissions.ManageSettings); @@ -1347,7 +1348,7 @@ namespace Barotrauma button.Enabled = CampaignSetupFrame.Visible && GameMain.Client.HasPermission(ClientPermissions.ManageSettings); } - traitorProbabilityButtons[0].Enabled = traitorProbabilityButtons[1].Enabled = traitorProbabilityText.Enabled = + traitorProbabilityButtons[0].Enabled = traitorProbabilityButtons[1].Enabled = traitorProbabilityText.Enabled = !CampaignFrame.Visible && !CampaignSetupFrame.Visible && GameMain.Client.HasPermission(ClientPermissions.ManageSettings); botCountButtons[0].Enabled = botCountButtons[1].Enabled = GameMain.Client.HasPermission(ClientPermissions.ManageSettings); botSpawnModeButtons[0].Enabled = botSpawnModeButtons[1].Enabled = GameMain.Client.HasPermission(ClientPermissions.ManageSettings); @@ -1360,12 +1361,13 @@ namespace Barotrauma ServerName.Readonly = !GameMain.Client.HasPermission(ClientPermissions.ManageSettings); ServerMessage.Readonly = !GameMain.Client.HasPermission(ClientPermissions.ManageSettings); shuttleTickBox.Enabled = GameMain.Client.HasPermission(ClientPermissions.ManageSettings); - SubList.Enabled = !CampaignFrame.Visible && (GameMain.Client.ServerSettings.Voting.AllowSubVoting || GameMain.Client.HasPermission(ClientPermissions.SelectSub)); + SubList.Enabled = !CampaignFrame.Visible && (GameMain.Client.ServerSettings.Voting.AllowSubVoting || GameMain.Client.HasPermission(ClientPermissions.SelectSub)); shuttleList.Enabled = shuttleList.ButtonEnabled = GameMain.Client.HasPermission(ClientPermissions.SelectSub); ModeList.Enabled = GameMain.Client.ServerSettings.Voting.AllowModeVoting || GameMain.Client.HasPermission(ClientPermissions.SelectMode); LogButtons.Visible = GameMain.Client.HasPermission(ClientPermissions.ServerLog); GameMain.Client.ShowLogButton.Visible = GameMain.Client.HasPermission(ClientPermissions.ServerLog); roundControlsHolder.Children.ForEach(c => c.IgnoreLayoutGroups = !c.Visible); + roundControlsHolder.Children.ForEach(c => c.RectTransform.RelativeSize = Vector2.One); roundControlsHolder.Recalculate(); ReadyToStartBox.Parent.Visible = !GameMain.Client.GameStarted; @@ -1382,7 +1384,7 @@ namespace Barotrauma } public void SetCampaignCharacterInfo(CharacterInfo newCampaignCharacterInfo) - { + { if (newCampaignCharacterInfo != null) { if (CampaignCharacterDiscarded) { return; } @@ -1404,40 +1406,35 @@ namespace Barotrauma UpdatePlayerFrame(characterInfo, allowEditing, playerInfoContainer); } - public void CreatePlayerFrame(GUIComponent parent) + public void CreatePlayerFrame(GUIComponent parent, bool createPendingText = true, bool alwaysAllowEditing = false) { UpdatePlayerFrame( - Character.Controlled?.Info ?? playerInfoContainer.Children?.First().UserData as CharacterInfo, - allowEditing: campaignCharacterInfo == null, - parent: parent); + Character.Controlled?.Info ?? playerInfoContainer.Children?.First().UserData as CharacterInfo, + allowEditing: alwaysAllowEditing || campaignCharacterInfo == null, + parent: parent, + createPendingText: createPendingText); } - private void UpdatePlayerFrame(CharacterInfo characterInfo, bool allowEditing, GUIComponent parent) + private void UpdatePlayerFrame(CharacterInfo characterInfo, bool allowEditing, GUIComponent parent, bool createPendingText = true) { + createPendingChangesText = createPendingText; if (characterInfo == null || CampaignCharacterDiscarded) { characterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, GameMain.Client.Name, null); - characterInfo.RecreateHead( - GameMain.Config.CharacterHeadIndex, - GameMain.Config.CharacterRace, - GameMain.Config.CharacterGender, - GameMain.Config.CharacterHairIndex, - GameMain.Config.CharacterBeardIndex, - GameMain.Config.CharacterMoustacheIndex, - GameMain.Config.CharacterFaceAttachmentIndex); + characterInfo.RecreateHead(GameMain.Config.PlayerCharacterCustomization); GameMain.Client.CharacterInfo = characterInfo; - characterInfo.OmitJobInPortraitClothing = true; + characterInfo.OmitJobInPortraitClothing = false; } parent.ClearChildren(); bool isGameRunning = GameMain.GameSession?.IsRunning ?? false; - infoContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, isGameRunning ? 0.95f : 0.9f), parent.RectTransform, Anchor.BottomCenter), childAnchor: Anchor.TopCenter) + infoContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, isGameRunning ? 0.97f : 0.92f), parent.RectTransform, Anchor.BottomCenter), childAnchor: Anchor.TopCenter) { - RelativeSpacing = 0.025f, + RelativeSpacing = 0.0f, Stretch = true, - UserData = characterInfo + UserData = characterInfo }; bool nameChangePending = isGameRunning && GameMain.Client.PendingName != string.Empty && GameMain.Client?.Character?.Name != GameMain.Client.PendingName; @@ -1453,6 +1450,7 @@ namespace Barotrauma CreateChangesPendingText(); } + CharacterNameBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.065f), infoContainer.RectTransform), !nameChangePending ? characterInfo.Name : GameMain.Client.PendingName, textAlignment: Alignment.Center) { MaxTextLength = Client.MaxNameLength, @@ -1476,7 +1474,10 @@ namespace Barotrauma { GameMain.Client.PendingName = tb.Text; TabMenu.PendingChanges = true; - CreateChangesPendingText(); + if (createPendingText) + { + CreateChangesPendingText(); + } } else { @@ -1484,27 +1485,27 @@ namespace Barotrauma } GameMain.Client.SetName(tb.Text); - }; + } }; - - new GUICustomComponent(new RectTransform(new Vector2(0.6f, 0.16f), infoContainer.RectTransform, Anchor.TopCenter), - onDraw: (sb, component) => characterInfo.DrawIcon(sb, component.Rect.Center.ToVector2(), targetAreaSize: component.Rect.Size.ToVector2())); + //spacing + new GUIFrame(new RectTransform(new Vector2(1.0f, 0.006f), infoContainer.RectTransform), style: null); + if (allowEditing) { - GUILayoutGroup characterInfoTabs = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.04f), infoContainer.RectTransform), isHorizontal: true) + GUILayoutGroup characterInfoTabs = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.016f), infoContainer.RectTransform), isHorizontal: true) { Stretch = true, RelativeSpacing = 0.02f }; - jobPreferencesButton = new GUIButton(new RectTransform(new Vector2(0.5f, 1.33f), characterInfoTabs.RectTransform), + jobPreferencesButton = new GUIButton(new RectTransform(new Vector2(0.5f, 1f), characterInfoTabs.RectTransform), TextManager.Get("JobPreferences"), style: "GUITabButton") { Selected = true, OnClicked = SelectJobPreferencesTab }; - appearanceButton = new GUIButton(new RectTransform(new Vector2(0.5f, 1.33f), characterInfoTabs.RectTransform), + appearanceButton = new GUIButton(new RectTransform(new Vector2(0.5f, 1f), characterInfoTabs.RectTransform), TextManager.Get("CharacterAppearance"), style: "GUITabButton") { OnClicked = SelectAppearanceTab @@ -1517,7 +1518,10 @@ namespace Barotrauma characterInfoFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.2f), infoContainer.RectTransform), style: null); characterInfoFrame.RectTransform.SizeChanged += RecalculateSubDescription; - JobList = new GUIListBox(new RectTransform(Vector2.One, characterInfoFrame.RectTransform), true) + JobPreferenceContainer = new GUIFrame(new RectTransform(Vector2.One, characterInfoFrame.RectTransform), + style: "GUIFrameListBox"); + characterInfo.CreateIcon(new RectTransform(new Vector2(1.0f, 0.4f), JobPreferenceContainer.RectTransform, Anchor.TopCenter) { RelativeOffset = new Vector2(0f, 0.025f) }); + JobList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.6f), JobPreferenceContainer.RectTransform, Anchor.BottomCenter), true) { Enabled = true, OnSelected = (child, obj) => @@ -1553,7 +1557,7 @@ namespace Barotrauma }; } - UpdateJobPreferences(JobList); + UpdateJobPreferences(); appearanceFrame = new GUIFrame(new RectTransform(Vector2.One, characterInfoFrame.RectTransform), style: "GUIFrameListBox") { @@ -1563,6 +1567,8 @@ namespace Barotrauma } else { + characterInfo.CreateIcon(new RectTransform(new Vector2(0.6f, 0.16f), infoContainer.RectTransform, Anchor.TopCenter)); + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoContainer.RectTransform), characterInfo.Job.Name, textAlignment: Alignment.Center, font: GUI.SubHeadingFont, wrap: true) { HoverColor = Color.Transparent, @@ -1587,17 +1593,10 @@ namespace Barotrauma IgnoreLayoutGroups = true, OnClicked = (btn, userdata) => { - var confirmation = new GUIMessageBox(TextManager.Get("NewCampaignCharacterHeader"), TextManager.Get("NewCampaignCharacterText"), - new string[] { TextManager.Get("Yes"), TextManager.Get("No") }); - confirmation.Buttons[0].OnClicked += confirmation.Close; - confirmation.Buttons[0].OnClicked += (btn2, userdata2) => + TryDiscardCampaignCharacter(() => { - CampaignCharacterDiscarded = true; - campaignCharacterInfo = null; UpdatePlayerFrame(null, true, parent); - return true; - }; - confirmation.Buttons[1].OnClicked += confirmation.Close; + }); return true; } }; @@ -1675,10 +1674,25 @@ namespace Barotrauma }; } } - + + public void TryDiscardCampaignCharacter(Action onYes) + { + var confirmation = new GUIMessageBox(TextManager.Get("NewCampaignCharacterHeader"), TextManager.Get("NewCampaignCharacterText"), + new[] { TextManager.Get("Yes"), TextManager.Get("No") }); + confirmation.Buttons[0].OnClicked += confirmation.Close; + confirmation.Buttons[0].OnClicked += (btn2, userdata2) => + { + CampaignCharacterDiscarded = true; + campaignCharacterInfo = null; + onYes(); + return true; + }; + confirmation.Buttons[1].OnClicked += confirmation.Close; + } + private void CreateChangesPendingText() { - if (changesPendingText != null || infoContainer == null) { return; } + if (!createPendingChangesText || changesPendingText != null || infoContainer == null) { return; } changesPendingText = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.065f), infoContainer.Parent.Parent.RectTransform, Anchor.BottomCenter, Pivot.TopCenter) { RelativeOffset = new Vector2(0f, -0.03f) }, style: "OuterGlow") @@ -1686,14 +1700,29 @@ namespace Barotrauma Color = Color.Black, IgnoreLayoutGroups = true }; - var text = new GUITextBlock(new RectTransform(Vector2.One, changesPendingText.RectTransform, Anchor.Center), + var text = new GUITextBlock(new RectTransform(Vector2.One, changesPendingText.RectTransform, Anchor.Center), TextManager.Get("tabmenu.characterchangespending"), textColor: GUI.Style.Orange, textAlignment: Alignment.Center, style: null); changesPendingText.RectTransform.MinSize = new Point((int)(text.TextSize.X * 1.2f), (int)(text.TextSize.Y * 2.0f)); } + public static void CreateChangesPendingFrame(GUIComponent parent) + { + parent.ClearChildren(); + GUIFrame changesPendingFrame = new GUIFrame(new RectTransform(Vector2.One, parent.RectTransform, Anchor.Center), + style: "OuterGlow") + { + Color = Color.Black + }; + new GUITextBlock(new RectTransform(Vector2.One, changesPendingFrame.RectTransform, Anchor.Center), + TextManager.Get("tabmenu.characterchangespending"), textColor: GUI.Style.Orange, textAlignment: Alignment.Center, style: null) + { + AutoScaleHorizontal = true + }; + } + private void CreateJobVariantTooltip(JobPrefab jobPrefab, int variant, GUIComponent parentSlot) { - jobVariantTooltip = new GUIFrame(new RectTransform(new Point((int)(500 * GUI.Scale), (int)(200 * GUI.Scale)), GUI.Canvas, pivot: Pivot.BottomRight), + jobVariantTooltip = new GUIFrame(new RectTransform(new Point((int)(400 * GUI.Scale), (int)(180 * GUI.Scale)), GUI.Canvas, pivot: Pivot.BottomRight), style: "GUIToolTip") { UserData = new Pair(jobPrefab, variant) @@ -1706,26 +1735,17 @@ namespace Barotrauma AbsoluteSpacing = (int)(15 * GUI.Scale) }; - string name = - TextManager.Get("jobname." + jobPrefab.Identifier + (variant + 1), returnNull: true, fallBackTag: "jobname." + jobPrefab.Identifier) ?? - ""; + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform), TextManager.GetWithVariable("startingequipmentname", "[number]", (variant + 1).ToString()), font: GUI.SubHeadingFont, textAlignment: Alignment.Center); - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform), name, font: GUI.SubHeadingFont); - - string description = - TextManager.Get("jobdescription." + jobPrefab.Identifier + (variant + 1), returnNull: true, fallBackTag: "jobdescription." + jobPrefab.Identifier) ?? - ""; - - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform), description, wrap: true, font: GUI.SmallFont); - - var itemIdentifiers = jobPrefab.ItemIdentifiers[variant] - .Distinct() - .Where(id => jobPrefab.ShowItemPreview[variant][id]); + var itemIdentifiers = jobPrefab.PreviewItems[variant] + .Where(it => it.ShowPreview) + .Select(it => it.ItemIdentifier) + .Distinct(); int itemsPerRow = 5; int rows = (int)Math.Max(Math.Ceiling(itemIdentifiers.Count() / (float)itemsPerRow), 1); - new GUICustomComponent(new RectTransform(new Vector2(1.0f, 0.4f * rows), content.RectTransform, Anchor.BottomLeft), + new GUICustomComponent(new RectTransform(new Vector2(1.0f, 0.4f * rows), content.RectTransform, Anchor.BottomCenter), onDraw: (sb, component) => { DrawJobVariantItems(sb, component, new Pair(jobPrefab, variant), itemsPerRow); }); jobVariantTooltip.RectTransform.MinSize = new Point(0, content.RectTransform.Children.Sum(c => c.Rect.Height + content.AbsoluteSpacing)); @@ -1767,7 +1787,7 @@ namespace Barotrauma if (spectateBox.Selected && !allowSpectating) { spectateBox.Selected = false; } // Hide spectate tickbox if spectating is not allowed - spectateBox.Visible = allowSpectating; + spectateBox.Visible = allowSpectating; } public void SetAutoRestart(bool enabled, float timer = 0.0f) @@ -1786,7 +1806,7 @@ namespace Barotrauma if (subList == null) { return; } subList.ClearChildren(); - + foreach (SubmarineInfo sub in submarines) { AddSubmarine(subList, sub); @@ -1872,15 +1892,15 @@ namespace Barotrauma UserData = "classtext", TextColor = subTextBlock.TextColor * 0.8f, ToolTip = subTextBlock.RawToolTip - }; + }; } - + } public bool VotableClicked(GUIComponent component, object userData) { if (GameMain.Client == null) { return false; } - + VoteType voteType; if (component.Parent == GameMain.NetLobbyScreen.SubList.Content) { @@ -1972,7 +1992,7 @@ namespace Barotrauma return true; } - + public void AddPlayer(Client client) { GUITextBlock textBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.1f), playerList.Content.RectTransform) { MinSize = new Point(0, (int)(30 * GUI.Scale)) }, @@ -1996,8 +2016,8 @@ namespace Barotrauma OverrideState = GUIComponent.ComponentState.None, HoverColor = Color.White }; - - new GUIImage(new RectTransform(new Point((int)(textBlock.Rect.Height * 0.8f)), textBlock.RectTransform, Anchor.CenterRight) { AbsoluteOffset = new Point(5, 0) }, + + new GUIImage(new RectTransform(new Point((int)(textBlock.Rect.Height * 0.8f)), textBlock.RectTransform, Anchor.CenterRight) { AbsoluteOffset = new Point(5, 0) }, "GUISoundIconDisabled") { UserData = "soundicondisabled", @@ -2020,7 +2040,8 @@ namespace Barotrauma var playerFrame = (GUITextBlock)PlayerList.Content.FindChild(client); if (playerFrame == null) { return; } playerFrame.Text = client.Name; - + + playerFrame.ToolTip = ""; Color color = Color.White; if (SelectedMode == GameModePreset.PvP) { @@ -2028,15 +2049,28 @@ namespace Barotrauma { case CharacterTeamType.Team1: color = new Color(0, 110, 150, 255); + playerFrame.ToolTip = TextManager.GetWithVariable("teampreference", "[team]", TextManager.Get("teampreference.team1")); break; case CharacterTeamType.Team2: color = new Color(150, 110, 0, 255); + playerFrame.ToolTip = TextManager.GetWithVariable("teampreference", "[team]", TextManager.Get("teampreference.team2")); + break; + default: + playerFrame.ToolTip = TextManager.GetWithVariable("teampreference", "[team]", TextManager.Get("none")); break; } } - else if (JobPrefab.Prefabs.ContainsKey(client.PreferredJob)) + else { - color = JobPrefab.Prefabs[client.PreferredJob].UIColor; + if (JobPrefab.Prefabs.ContainsKey(client.PreferredJob)) + { + color = JobPrefab.Prefabs[client.PreferredJob].UIColor; + playerFrame.ToolTip = TextManager.GetWithVariable("jobpreference", "[job]", JobPrefab.Prefabs[client.PreferredJob].Name); + } + else + { + playerFrame.ToolTip = TextManager.GetWithVariable("jobpreference", "[job]", TextManager.Get("none")); + } } playerFrame.Color = color * 0.4f; playerFrame.HoverColor = color * 0.6f; @@ -2114,16 +2148,16 @@ namespace Barotrauma { Stretch = true }; - - var nameText = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), headerContainer.RectTransform), + + var nameText = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), headerContainer.RectTransform), text: selectedClient.Name, font: GUI.LargeFont); nameText.Text = ToolBox.LimitString(nameText.Text, nameText.Font, (int)(nameText.Rect.Width * 0.95f)); if (hasManagePermissions) { PlayerFrame.UserData = selectedClient; - - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), paddedPlayerFrame.RectTransform), + + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), paddedPlayerFrame.RectTransform), TextManager.Get("Rank"), font: GUI.SubHeadingFont); var rankDropDown = new GUIDropDown(new RectTransform(new Vector2(1.0f, 0.1f), paddedPlayerFrame.RectTransform), TextManager.Get("Rank")) @@ -2163,7 +2197,7 @@ namespace Barotrauma RelativeSpacing = 0.05f }; var permissionLabel = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), permissionLabels.RectTransform), TextManager.Get("Permissions"), font: GUI.SubHeadingFont); - var consoleCommandLabel = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), permissionLabels.RectTransform), + var consoleCommandLabel = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), permissionLabels.RectTransform), TextManager.Get("PermittedConsoleCommands"), wrap: true, font: GUI.SubHeadingFont); GUITextBlock.AutoScaleAndNormalize(permissionLabel, consoleCommandLabel); @@ -2308,7 +2342,7 @@ namespace Barotrauma var buttonAreaTop = myClient ? null : new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.08f), paddedPlayerFrame.RectTransform), isHorizontal: true); var buttonAreaLower = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.08f), paddedPlayerFrame.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft); - + if (!myClient) { if (GameMain.Client.HasPermission(ClientPermissions.Ban)) @@ -2332,7 +2366,7 @@ namespace Barotrauma if (GameMain.Client != null && GameMain.Client.ConnectedClients.Contains(selectedClient)) { - if (GameMain.Client.ServerSettings.Voting.AllowVoteKick && + if (GameMain.Client.ServerSettings.Voting.AllowVoteKick && selectedClient != null && selectedClient.AllowKicking) { var kickVoteButton = new GUIButton(new RectTransform(new Vector2(0.34f, 1.0f), buttonAreaLower.RectTransform), @@ -2399,7 +2433,7 @@ namespace Barotrauma } buttonAreaLower.RectTransform.NonScaledSize = new Point(buttonAreaLower.Rect.Width, buttonAreaLower.RectTransform.Children.Max(c => c.NonScaledSize.Y)); - + if (buttonAreaTop != null) { if (buttonAreaTop.CountChildren == 0) @@ -2432,7 +2466,7 @@ namespace Barotrauma public void KickPlayer(Client client) { if (GameMain.NetworkMember == null || client == null) { return; } - GameMain.Client.CreateKickReasonPrompt(client.Name, false); + GameMain.Client.CreateKickReasonPrompt(client.Name, false); } public void BanPlayer(Client client) @@ -2446,15 +2480,15 @@ namespace Barotrauma if (GameMain.NetworkMember == null || client == null) { return; } GameMain.Client.CreateKickReasonPrompt(client.Name, ban: true, rangeBan: true); } - + public override void AddToGUIUpdateList() { base.AddToGUIUpdateList(); - + //CampaignSetupUI?.AddToGUIUpdateList(); JobInfoFrame?.AddToGUIUpdateList(); - HeadSelectionList?.AddToGUIUpdateList(); + CharacterAppearanceCustomizationMenu?.AddToGUIUpdateList(); JobSelectionFrame?.AddToGUIUpdateList(); } @@ -2524,14 +2558,11 @@ namespace Barotrauma } } - if (HeadSelectionList != null && PlayerInput.PrimaryMouseButtonDown() && !GUI.IsMouseOn(HeadSelectionList)) - { - HeadSelectionList.Visible = false; - } + CharacterAppearanceCustomizationMenu?.Update(); if (JobSelectionFrame != null && PlayerInput.PrimaryMouseButtonDown() && !GUI.IsMouseOn(JobSelectionFrame)) { JobList.Deselect(); - JobSelectionFrame.Visible = false; + JobSelectionFrame.Visible = false; } if (GUI.MouseOn?.UserData is Pair jobPrefab && GUI.MouseOn.Style?.Name == "JobVariantButton") @@ -2557,7 +2588,7 @@ namespace Barotrauma GUI.DrawBackgroundSprite(spriteBatch, backgroundSprite); spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable); - + GUI.Draw(Cam, spriteBatch); spriteBatch.End(); } @@ -2568,7 +2599,7 @@ namespace Barotrauma if (GameMain.NetworkMember?.ServerSettings == null) { return; } PlayStyle playStyle = GameMain.NetworkMember.ServerSettings.PlayStyle; - if ((int)playStyle < 0 || + if ((int)playStyle < 0 || (int)playStyle >= ServerListScreen.PlayStyleBanners.Length) { return; @@ -2594,9 +2625,10 @@ namespace Barotrauma private void DrawJobVariantItems(SpriteBatch spriteBatch, GUICustomComponent component, Pair jobPrefab, int itemsPerRow) { - var itemIdentifiers = jobPrefab.First.ItemIdentifiers[jobPrefab.Second] - .Distinct() - .Where(id => jobPrefab.First.ShowItemPreview[jobPrefab.Second][id]); + var itemIdentifiers = jobPrefab.First.PreviewItems[jobPrefab.Second] + .Where(it => it.ShowPreview) + .Select(it => it.ItemIdentifier) + .Distinct(); Point slotSize = new Point(component.Rect.Height); int spacing = (int)(5 * GUI.Scale); @@ -2615,7 +2647,7 @@ namespace Barotrauma int i = 0; Rectangle tooltipRect = Rectangle.Empty; string tooltip = null; - foreach (string itemIdentifier in itemIdentifiers) + foreach (var itemIdentifier in itemIdentifiers) { if (!(MapEntityPrefab.Find(null, identifier: itemIdentifier, showErrorMessages: false) is ItemPrefab itemPrefab)) { continue; } @@ -2634,7 +2666,7 @@ namespace Barotrauma float iconScale = Math.Min(Math.Min(slotSize.X / icon.size.X, slotSize.Y / icon.size.Y), 2.0f) * 0.9f; icon.Draw(spriteBatch, slotPos + slotSize.ToVector2() * 0.5f, scale: iconScale); - int count = jobPrefab.First.ItemIdentifiers[jobPrefab.Second].Count(id => id == itemIdentifier); + int count = jobPrefab.First.PreviewItems[jobPrefab.Second].Count(it => it.ShowPreview && it.ItemIdentifier == itemIdentifier); if (count > 1) { string itemCountText = "x" + count; @@ -2693,9 +2725,9 @@ namespace Barotrauma msg.RectTransform.SizeChanged += Recalculate; } - if ((prevSize == 1.0f && chatBox.BarScroll == 0.0f) || (prevSize < 1.0f && chatBox.BarScroll == 1.0f)) - { - chatBox.BarScroll = 1.0f; + if ((prevSize == 1.0f && chatBox.BarScroll == 0.0f) || (prevSize < 1.0f && chatBox.BarScroll == 1.0f)) + { + chatBox.BarScroll = 1.0f; } } @@ -2704,212 +2736,56 @@ namespace Barotrauma jobPreferencesButton.Selected = true; appearanceButton.Selected = false; - JobList.Visible = true; + JobPreferenceContainer.Visible = true; appearanceFrame.Visible = false; return false; } - private bool SelectAppearanceTab(GUIButton button, object userData) + private bool SelectAppearanceTab(GUIButton button, object _) { jobPreferencesButton.Selected = false; appearanceButton.Selected = true; - JobList.Visible = false; + JobPreferenceContainer.Visible = false; appearanceFrame.Visible = true; appearanceFrame.ClearChildren(); - if (HeadSelectionList != null) { HeadSelectionList.Visible = false; } - - GUIButton maleButton = null; - GUIButton femaleButton = null; var info = GameMain.Client.CharacterInfo; - - GUILayoutGroup content = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), appearanceFrame.RectTransform, Anchor.Center)) + CharacterAppearanceCustomizationMenu = new CharacterInfo.AppearanceCustomizationMenu(info, appearanceFrame) { - RelativeSpacing = 0.05f - }; - - Vector2 elementSize = new Vector2(1.0f, 0.18f); - - GUILayoutGroup genderContainer = new GUILayoutGroup(new RectTransform(elementSize, content.RectTransform), isHorizontal: true) - { - Stretch = true, - RelativeSpacing = 0.05f - }; - new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), genderContainer.RectTransform), TextManager.Get("Gender"), font: GUI.SubHeadingFont); - maleButton = new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), genderContainer.RectTransform), - TextManager.Get("Male"), style: "ListBoxElement") - { - UserData = Gender.Male, - OnClicked = OpenHeadSelection, - Selected = info.Gender == Gender.Male - }; - femaleButton = new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), genderContainer.RectTransform), - TextManager.Get("Female"), style: "ListBoxElement") - { - UserData = Gender.Female, - OnClicked = OpenHeadSelection, - Selected = info.Gender == Gender.Female - }; - - int hairCount = info.FilterByTypeAndHeadID(info.FilterElementsByGenderAndRace(info.Wearables, info.Head.gender, info.Head.race), WearableType.Hair, info.HeadSpriteId).Count(); - if (hairCount > 0) - { - var label = new GUITextBlock(new RectTransform(elementSize, content.RectTransform), TextManager.Get("FaceAttachment.Hair"), font: GUI.SubHeadingFont); - var hairSlider = new GUIScrollBar(new RectTransform(new Vector2(0.4f, 1.0f), label.RectTransform, Anchor.CenterRight), style: "GUISlider") + OnHeadSwitch = menu => { - Range = new Vector2(0, hairCount), - StepValue = 1, - BarScrollValue = info.HairIndex, - OnMoved = SwitchHair, - OnReleased = SaveHead, - BarSize = 1.0f / (float)(hairCount + 1) - }; - } - - int beardCount = info.FilterByTypeAndHeadID(info.FilterElementsByGenderAndRace(info.Wearables, info.Head.gender, info.Head.race), WearableType.Beard, info.HeadSpriteId).Count(); - if (beardCount > 0) - { - var label = new GUITextBlock(new RectTransform(elementSize, content.RectTransform), TextManager.Get("FaceAttachment.Beard"), font: GUI.SubHeadingFont); - var beardSlider = new GUIScrollBar(new RectTransform(new Vector2(0.4f, 1.0f), label.RectTransform, Anchor.CenterRight), style: "GUISlider") + StoreHead(true); + UpdateJobPreferences(); + SelectAppearanceTab(button, _); + }, + OnSliderMoved = (bar, scroll) => { - Range = new Vector2(0, beardCount), - StepValue = 1, - BarScrollValue = info.BeardIndex, - OnMoved = SwitchBeard, - OnReleased = SaveHead, - BarSize = 1.0f / (float)(beardCount + 1) - }; - } - - int moustacheCount = info.FilterByTypeAndHeadID(info.FilterElementsByGenderAndRace(info.Wearables, info.Head.gender, info.Head.race), WearableType.Moustache, info.HeadSpriteId).Count(); - if (moustacheCount > 0) - { - var label = new GUITextBlock(new RectTransform(elementSize, content.RectTransform), TextManager.Get("FaceAttachment.Moustache"), font: GUI.SubHeadingFont); - var moustacheSlider = new GUIScrollBar(new RectTransform(new Vector2(0.4f, 1.0f), label.RectTransform, Anchor.CenterRight), style: "GUISlider") - { - Range = new Vector2(0, moustacheCount), - StepValue = 1, - BarScrollValue = info.MoustacheIndex, - OnMoved = SwitchMoustache, - OnReleased = SaveHead, - BarSize = 1.0f / (float)(moustacheCount + 1) - }; - } - - int faceAttachmentCount = info.FilterByTypeAndHeadID(info.FilterElementsByGenderAndRace(info.Wearables, info.Head.gender, info.Head.race), WearableType.FaceAttachment, info.HeadSpriteId).Count(); - if (faceAttachmentCount > 0) - { - var label = new GUITextBlock(new RectTransform(elementSize, content.RectTransform), TextManager.Get("FaceAttachment.Accessories"), font: GUI.SubHeadingFont); - var faceAttachmentSlider = new GUIScrollBar(new RectTransform(new Vector2(0.4f, 1.0f), label.RectTransform, Anchor.CenterRight), style: "GUISlider") - { - Range = new Vector2(0, faceAttachmentCount), - StepValue = 1, - BarScrollValue = info.FaceAttachmentIndex, - OnMoved = SwitchFaceAttachment, - OnReleased = SaveHead, - BarSize = 1.0f / (float)(faceAttachmentCount + 1) - }; - } + StoreHead(false); + return false; + }, + OnSliderReleased = SaveHead + }; return false; } - - private bool OpenHeadSelection(GUIButton button, object userData) + + private bool SaveHead(GUIScrollBar scrollBar, float barScroll) => StoreHead(true); + private bool StoreHead(bool save) { - Gender selectedGender = (Gender)userData; - if (HeadSelectionList != null) + GameMain.Config.PlayerCharacterCustomization = GameMain.Client.CharacterInfo.Head; + if (save) { - HeadSelectionList.Visible = true; - foreach (GUIComponent child in HeadSelectionList.Content.Children) + if (GameMain.GameSession?.IsRunning ?? false) { - child.Visible = (Gender)child.UserData == selectedGender; - child.Children.ForEach(c => c.Visible = ((Tuple)c.UserData).Item1 == selectedGender); + TabMenu.PendingChanges = true; + CreateChangesPendingText(); } - return true; + GameMain.Config.SaveNewPlayerConfig(); } - - var info = GameMain.Client.CharacterInfo; - - HeadSelectionList = new GUIListBox( - new RectTransform(new Point(characterInfoFrame.Rect.Width, (characterInfoFrame.Rect.Bottom - button.Rect.Bottom) + characterInfoFrame.Rect.Height * 2), GUI.Canvas) - { - AbsoluteOffset = new Point(characterInfoFrame.Rect.Right - characterInfoFrame.Rect.Width, button.Rect.Bottom) - }); - - characterInfoFrame.RectTransform.SizeChanged += () => - { - if (characterInfoFrame == null || HeadSelectionList?.RectTransform == null || button == null) { return; } - HeadSelectionList.RectTransform.Resize(new Point(characterInfoFrame.Rect.Width, (characterInfoFrame.Rect.Bottom - button.Rect.Bottom) + characterInfoFrame.Rect.Height * 2)); - HeadSelectionList.RectTransform.AbsoluteOffset = new Point(characterInfoFrame.Rect.Right - characterInfoFrame.Rect.Width, button.Rect.Bottom); - if (SelectedSub != null) { CreateSubPreview(SelectedSub); } - }; - - new GUIFrame(new RectTransform(new Vector2(1.25f, 1.25f), HeadSelectionList.RectTransform, Anchor.Center), style: "OuterGlow", color: Color.Black) - { - UserData = "outerglow", - CanBeFocused = false - }; - - GUILayoutGroup row = null; - int itemsInRow = 0; - - XElement headElement = info.Ragdoll.MainElement.Elements().FirstOrDefault(e => e.GetAttributeString("type", "").Equals("head", StringComparison.OrdinalIgnoreCase)); - XElement headSpriteElement = headElement.Element("sprite"); - string spritePathWithTags = headSpriteElement.Attribute("texture").Value; - - var characterConfigElement = info.CharacterConfigElement; - - var heads = info.Heads; - if (heads != null) - { - row = null; - itemsInRow = 0; - foreach (var head in heads) - { - var headPreset = head.Key; - Gender gender = headPreset.Gender; - Race race = headPreset.Race; - int headIndex = headPreset.ID; - - string spritePath = spritePathWithTags - .Replace("[GENDER]", gender.ToString().ToLowerInvariant()) - .Replace("[RACE]", race.ToString().ToLowerInvariant()); - - if (!File.Exists(spritePath)) { continue; } - - Sprite headSprite = new Sprite(headSpriteElement, "", spritePath); - headSprite.SourceRect = new Rectangle(CharacterInfo.CalculateOffset(headSprite, head.Value.ToPoint()), headSprite.SourceRect.Size); - characterSprites.Add(headSprite); - - if (itemsInRow >= 4 || row == null || gender != (Gender)row.UserData) - { - row = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.333f), HeadSelectionList.Content.RectTransform), true) - { - UserData = gender, - Visible = gender == selectedGender - }; - itemsInRow = 0; - } - - var btn = new GUIButton(new RectTransform(new Vector2(0.25f, 1.0f), row.RectTransform), style: "ListBoxElementSquare") - { - OutlineColor = Color.White * 0.5f, - PressedColor = Color.White * 0.5f, - UserData = new Tuple(gender, race, headIndex), - OnClicked = SwitchHead, - Selected = gender == info.Gender && race == info.Race && headIndex == info.HeadSpriteId, - Visible = gender == selectedGender - }; - - new GUIImage(new RectTransform(Vector2.One, btn.RectTransform), headSprite, scaleToFit: true); - itemsInRow++; - } - } - - return false; + return true; } private bool SwitchJob(GUIButton _, object obj) @@ -2942,7 +2818,7 @@ namespace Barotrauma } } - UpdateJobPreferences(JobList); + UpdateJobPreferences(); if (moveToNext) { @@ -2973,14 +2849,14 @@ namespace Barotrauma return true; } - Point frameSize = new Point(characterInfoFrame.Rect.Width, characterInfoFrame.Rect.Height * 2); + Point frameSize = new Point(characterInfoFrame.Rect.Width, (int)(characterInfoFrame.Rect.Height * 2 * 0.6f)); JobSelectionFrame = new GUIFrame(new RectTransform(frameSize, GUI.Canvas, Anchor.TopLeft) { AbsoluteOffset = new Point(characterInfoFrame.Rect.Right - frameSize.X, characterInfoFrame.Rect.Bottom) }, style:"GUIFrameListBox"); characterInfoFrame.RectTransform.SizeChanged += () => { if (characterInfoFrame == null || JobSelectionFrame?.RectTransform == null) { return; } - Point size = new Point(characterInfoFrame.Rect.Width, characterInfoFrame.Rect.Height * 2); + Point size = new Point(characterInfoFrame.Rect.Width, (int)(characterInfoFrame.Rect.Height * 2 * 0.6f)); JobSelectionFrame.RectTransform.Resize(size); JobSelectionFrame.RectTransform.AbsoluteOffset = new Point(characterInfoFrame.Rect.Right - size.X, characterInfoFrame.Rect.Bottom); }; @@ -3097,7 +2973,7 @@ namespace Barotrauma { Pair sprite = outfitPreview.Sprites[j]; float aspectRatio = outfitPreview.Dimensions.Y / outfitPreview.Dimensions.X; - retVal[i][j] = new GUIImage(new RectTransform(new Vector2(0.7f / aspectRatio, 0.7f), innerFrame.RectTransform, Anchor.Center) + retVal[i][j] = new GUIImage(new RectTransform(new Vector2(0.7f / aspectRatio, 0.7f), innerFrame.RectTransform, Anchor.Center) { RelativeOffset = sprite.Second / outfitPreview.Dimensions }, sprite.First, scaleToFit: true) { PressedColor = Color.White, @@ -3136,88 +3012,6 @@ namespace Barotrauma return retVal; } - private bool SwitchHead(GUIButton button, object obj) - { - var info = GameMain.Client.CharacterInfo; - - Gender gender = ((Tuple)obj).Item1; - Race race = ((Tuple)obj).Item2; - int id = ((Tuple)obj).Item3; - - if (gender != info.Gender || race != info.Race || id != info.HeadSpriteId) - { - info.Head = new CharacterInfo.HeadInfo(id, gender, race); - info.ReloadHeadAttachments(); - } - StoreHead(true); - - UpdateJobPreferences(JobList); - - SelectAppearanceTab(button, obj); - - return true; - } - - private bool SaveHead(GUIScrollBar scrollBar, float barScroll) => StoreHead(true); - private bool SwitchHair(GUIScrollBar scrollBar, float barScroll) => SwitchAttachment(scrollBar, WearableType.Hair); - private bool SwitchBeard(GUIScrollBar scrollBar, float barScroll) => SwitchAttachment(scrollBar, WearableType.Beard); - private bool SwitchMoustache(GUIScrollBar scrollBar, float barScroll) => SwitchAttachment(scrollBar, WearableType.Moustache); - private bool SwitchFaceAttachment(GUIScrollBar scrollBar, float barScroll) => SwitchAttachment(scrollBar, WearableType.FaceAttachment); - private bool SwitchAttachment(GUIScrollBar scrollBar, WearableType type) - { - var info = GameMain.Client.CharacterInfo; - int index = (int)scrollBar.BarScrollValue; - switch (type) - { - case WearableType.Beard: - info.Head = new CharacterInfo.HeadInfo(info.HeadSpriteId, info.Gender, info.Race, info.HairIndex, index, info.MoustacheIndex, info.FaceAttachmentIndex); - break; - case WearableType.FaceAttachment: - info.Head = new CharacterInfo.HeadInfo(info.HeadSpriteId, info.Gender, info.Race, info.HairIndex, info.BeardIndex, info.MoustacheIndex, index); - break; - case WearableType.Hair: - info.Head = new CharacterInfo.HeadInfo(info.HeadSpriteId, info.Gender, info.Race, index, info.BeardIndex, info.MoustacheIndex, info.FaceAttachmentIndex); - break; - case WearableType.Moustache: - info.Head = new CharacterInfo.HeadInfo(info.HeadSpriteId, info.Gender, info.Race, info.HairIndex, info.BeardIndex, index, info.FaceAttachmentIndex); - break; - default: - DebugConsole.ThrowError($"Wearable type not implemented: {type}"); - return false; - } - info.ReloadHeadAttachments(); - StoreHead(false); - return true; - } - - private bool StoreHead(bool save) - { - var info = GameMain.Client.CharacterInfo; - var config = GameMain.Config; - - config.CharacterRace = info.Race; - config.CharacterGender = info.Gender; - config.CharacterHeadIndex = info.HeadSpriteId; - config.CharacterHairIndex = info.HairIndex; - config.CharacterBeardIndex = info.BeardIndex; - config.CharacterMoustacheIndex = info.MoustacheIndex; - config.CharacterFaceAttachmentIndex = info.FaceAttachmentIndex; - - if (save) - { - if (GameMain.GameSession?.IsRunning ?? false) - { - TabMenu.PendingChanges = true; - CreateChangesPendingText(); - } - - GameMain.Config.SaveNewPlayerConfig(); - } - - return true; - } - - public void SelectMode(int modeIndex) { if (modeIndex < 0 || modeIndex >= modeList.Content.CountChildren) { return; } @@ -3322,10 +3116,10 @@ namespace Barotrauma ReadyToStartBox.Parent.Visible = !GameMain.Client.GameStarted; - StartButton.Visible = - GameMain.Client.HasPermission(ClientPermissions.ManageRound) && - !GameMain.Client.GameStarted && - !CampaignSetupFrame.Visible && + StartButton.Visible = + GameMain.Client.HasPermission(ClientPermissions.ManageRound) && + !GameMain.Client.GameStarted && + !CampaignSetupFrame.Visible && !CampaignFrame.Visible; } @@ -3390,7 +3184,7 @@ namespace Barotrauma OnClicked = CloseJobInfo }; JobInfoFrame.OnClicked = (btn, userdata) => { if (GUI.MouseOn == btn || GUI.MouseOn == btn.TextBlock) CloseJobInfo(btn, userdata); return true; }; - + return true; } @@ -3400,8 +3194,13 @@ namespace Barotrauma return true; } - private void UpdateJobPreferences(GUIListBox listBox) + private void UpdateJobPreferences() { + GUICustomComponent characterIcon = JobPreferenceContainer.GetChild(); + JobPreferenceContainer.RemoveChild(characterIcon); + GameMain.Client.CharacterInfo.CreateIcon(new RectTransform(new Vector2(1.0f, 0.4f), JobPreferenceContainer.RectTransform, Anchor.TopCenter) { RelativeOffset = new Vector2(0.0f, 0.025f) }); + + GUIListBox listBox = JobPreferenceContainer.GetChild(); /*foreach (Sprite sprite in jobPreferenceSprites) { sprite.Remove(); } jobPreferenceSprites.Clear();*/ @@ -3432,7 +3231,7 @@ namespace Barotrauma variantButton.OnClicked = (btn, obj) => { btn.Parent.UserData = obj; - UpdateJobPreferences(listBox); + UpdateJobPreferences(); return false; }; } @@ -3443,7 +3242,7 @@ namespace Barotrauma style: "GUIButtonInfo") { UserData = jobPrefab, - OnClicked = ViewJobInfo + OnClicked = ViewJobInfo }; // Remove button @@ -3505,10 +3304,10 @@ namespace Barotrauma private GUIButton CreateJobVariantButton(Pair jobPrefab, int variantIndex, int variantCount, GUIComponent slot) { - float relativeSize = 0.2f; + float relativeSize = 0.15f; var btn = new GUIButton(new RectTransform(new Vector2(relativeSize), slot.RectTransform, Anchor.TopCenter, scaleBasis: ScaleBasis.BothHeight) - { RelativeOffset = new Vector2(relativeSize * 1.05f * (variantIndex - (variantCount - 1) / 2.0f), 0.02f) }, + { RelativeOffset = new Vector2(relativeSize * 1.3f * (variantIndex - (variantCount - 1) / 2.0f), 0.02f) }, (variantIndex + 1).ToString(), style: "JobVariantButton") { Selected = jobPrefab.Second == variantIndex, @@ -3539,7 +3338,7 @@ namespace Barotrauma .UserData as SubmarineInfo; //matching sub found and already selected, all good - if (sub != null) + if (sub != null) { if (subList == this.subList) { @@ -3578,7 +3377,7 @@ namespace Barotrauma FailedSelectedSub = null; else FailedSelectedShuttle = null; - + //hashes match, all good if (sub.MD5Hash?.Hash == md5Hash && SubmarineInfo.SavedSubmarines.Contains(sub)) { @@ -3588,7 +3387,7 @@ namespace Barotrauma //------------------------------------------------------------------------------------- //if we get to this point, a matching sub was not found or it has an incorrect MD5 hash - + if (subList == SubList) FailedSelectedSub = new Pair(subName, md5Hash); else @@ -3607,7 +3406,7 @@ namespace Barotrauma } else { - errorMsg = TextManager.GetWithVariables("SubDoesntMatchError", new string[3] { "[subname]" , "[myhash]", "[serverhash]" }, + errorMsg = TextManager.GetWithVariables("SubDoesntMatchError", new string[3] { "[subname]" , "[myhash]", "[serverhash]" }, new string[3] { sub.Name, sub.MD5Hash.ShortHash, Md5Hash.GetShortHash(md5Hash) }) + " "; } @@ -3640,7 +3439,7 @@ namespace Barotrauma { new GUIMessageBox(TextManager.Get("DownloadSubLabel"), errorMsg); } - return false; + return false; } public bool CheckIfCampaignSubMatches(SubmarineInfo serverSubmarine, string deliveryData) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/ServerListScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/ServerListScreen.cs index 5ac93c54e..4430efdb1 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/ServerListScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/ServerListScreen.cs @@ -24,7 +24,8 @@ namespace Barotrauma private GUIFrame menu; private GUIListBox serverList; - private GUIFrame serverPreview; + private GUIFrame serverPreviewContainer; + private GUIListBox serverPreview; private GUIButton joinButton; private ServerInfo selectedServer; @@ -340,11 +341,11 @@ namespace Barotrauma void RecalculateHolder() { float listContainerSubtract = filtersHolder.Visible ? sidebarWidth : 0.0f; - listContainerSubtract += serverPreview.Visible ? sidebarWidth : 0.0f; + listContainerSubtract += serverPreviewContainer.Visible ? sidebarWidth : 0.0f; float toggleButtonsSubtract = 1.1f * filterToggle.Rect.Width / serverListHolder.Rect.Width; listContainerSubtract += filterToggle.Visible ? toggleButtonsSubtract : 0.0f; - listContainerSubtract += serverPreviewToggleButton.Visible ? toggleButtonsSubtract : 0.0f; + listContainerSubtract += serverPreviewContainer.Visible ? toggleButtonsSubtract : 0.0f; serverListContainer.RectTransform.RelativeSize = new Vector2(1.0f - listContainerSubtract, 1.0f); serverListHolder.Recalculate(); @@ -567,17 +568,17 @@ namespace Barotrauma { joinButton.Enabled = true; selectedServer = serverInfo; - if (!serverPreview.Visible) + if (!serverPreviewContainer.Visible) { - serverPreview.RectTransform.RelativeSize = new Vector2(sidebarWidth, 1.0f); + serverPreviewContainer.RectTransform.RelativeSize = new Vector2(sidebarWidth, 1.0f); serverPreviewToggleButton.Visible = true; serverPreviewToggleButton.IgnoreLayoutGroups = false; - serverPreview.Visible = true; - serverPreview.IgnoreLayoutGroups = false; + serverPreviewContainer.Visible = true; + serverPreviewContainer.IgnoreLayoutGroups = false; RecalculateHolder(); } - serverInfo.CreatePreviewWindow(serverPreview); - btn.Children.ForEach(c => c.SpriteEffects = serverPreview.Visible ? SpriteEffects.None : SpriteEffects.FlipHorizontally); + serverInfo.CreatePreviewWindow(serverPreview.Content); + btn.Children.ForEach(c => c.SpriteEffects = serverPreviewContainer.Visible ? SpriteEffects.None : SpriteEffects.FlipHorizontally); } return true; } @@ -592,24 +593,28 @@ namespace Barotrauma Visible = false, OnClicked = (btn, userdata) => { - serverPreview.RectTransform.RelativeSize = new Vector2(0.2f, 1.0f); - serverPreview.Visible = !serverPreview.Visible; - serverPreview.IgnoreLayoutGroups = !serverPreview.Visible; + serverPreviewContainer.RectTransform.RelativeSize = new Vector2(0.2f, 1.0f); + serverPreviewContainer.Visible = !serverPreviewContainer.Visible; + serverPreviewContainer.IgnoreLayoutGroups = !serverPreviewContainer.Visible; RecalculateHolder(); - btn.Children.ForEach(c => c.SpriteEffects = serverPreview.Visible ? SpriteEffects.None : SpriteEffects.FlipHorizontally); + btn.Children.ForEach(c => c.SpriteEffects = serverPreviewContainer.Visible ? SpriteEffects.None : SpriteEffects.FlipHorizontally); return true; } }; - serverPreview = new GUIFrame(new RectTransform(new Vector2(sidebarWidth, 1.0f), serverListHolder.RectTransform, Anchor.Center), style: null) + serverPreviewContainer = new GUIFrame(new RectTransform(new Vector2(sidebarWidth, 1.0f), serverListHolder.RectTransform, Anchor.Center), style: null) { Color = new Color(12, 14, 15, 255) * 0.5f, OutlineColor = Color.Black, IgnoreLayoutGroups = true, Visible = false }; + serverPreview = new GUIListBox(new RectTransform(Vector2.One, serverPreviewContainer.RectTransform, Anchor.Center)) + { + Padding = Vector4.One * 10 * GUI.Scale + }; // Spacing new GUIFrame(new RectTransform(new Vector2(1.0f, 0.02f), bottomRow.RectTransform), style: null); @@ -1697,7 +1702,7 @@ namespace Barotrauma UpdateFriendsList(); serverList.ClearChildren(); - serverPreview.ClearChildren(); + serverPreview.Content.ClearChildren(); joinButton.Enabled = false; selectedServer = null; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/SteamWorkshopScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/SteamWorkshopScreen.cs index e40a03ece..350ff7ac5 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/SteamWorkshopScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/SteamWorkshopScreen.cs @@ -1604,7 +1604,7 @@ namespace Barotrauma if (string.IsNullOrEmpty(file) || !File.Exists(file)) { continue; } string modFolder = Path.GetDirectoryName(itemContentPackage.Path); - string filePathRelativeToModFolder = UpdaterUtil.GetRelativePath(file, Path.Combine(Environment.CurrentDirectory, modFolder)); + string filePathRelativeToModFolder = Path.GetRelativePath(Path.Combine(Environment.CurrentDirectory, modFolder), file); //file is not inside the mod folder, we need to move it if (filePathRelativeToModFolder.StartsWith("..") || diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs index 5b4481583..e72eb2623 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs @@ -21,7 +21,7 @@ namespace Barotrauma { class SubEditorScreen : EditorScreen { - private static readonly string[] crewExperienceLevels = + private static readonly string[] crewExperienceLevels = { "CrewExperienceLow", "CrewExperienceMid", @@ -45,7 +45,7 @@ namespace Barotrauma NonLinkedGaps, TooManyLights } - + public static Vector2 MouseDragStart = Vector2.Zero; private readonly Point defaultPreviewImageSize = new Point(640, 368); @@ -100,6 +100,7 @@ namespace Barotrauma private GUIListBox previouslyUsedList; private GUIFrame undoBufferPanel; + private GUIFrame undoBufferDisclaimer; private GUIListBox undoBufferList; private GUIDropDown linkedSubBox; @@ -138,13 +139,13 @@ namespace Barotrauma //a Character used for picking up and manipulating items private Character dummyCharacter; - + /// /// Prefab used for dragging from the item catalog into inventories /// /// public static MapEntityPrefab DraggedItemPrefab; - + /// /// Currently opened hand-held item container like crates /// @@ -296,7 +297,7 @@ namespace Barotrauma }; new GUIFrame(new RectTransform(new Vector2(0.01f, 0.9f), paddedTopPanel.RectTransform), style: "VerticalLine"); - + new GUIButton(new RectTransform(new Vector2(0.9f, 0.9f), paddedTopPanel.RectTransform, scaleBasis: ScaleBasis.BothHeight), style: "SaveButton") { ToolTip = TextManager.Get("SaveSubButton") + "‖color:125,125,125‖\nCtrl + S‖color:end‖", @@ -478,7 +479,10 @@ namespace Barotrauma { Visible = false }; - undoBufferList = new GUIListBox(new RectTransform(new Vector2(0.925f, 0.9f), undoBufferPanel.RectTransform, Anchor.Center)) + + Vector2 undoSize = new Vector2(0.925f, 0.9f); + + undoBufferList = new GUIListBox(new RectTransform(undoSize, undoBufferPanel.RectTransform, Anchor.Center)) { ScrollBarVisible = true, OnSelected = (_, userData) => @@ -504,11 +508,21 @@ namespace Barotrauma { Undo(amount - 1); } - + return true; } }; - + + undoBufferDisclaimer = new GUIFrame(new RectTransform(undoSize, undoBufferPanel.RectTransform, Anchor.Center), style: null) + { + Color = Color.Black, + Visible = false + }; + new GUITextBlock(new RectTransform(Vector2.One, undoBufferDisclaimer.RectTransform, Anchor.Center), text: TextManager.Get("editor.undounavailable"), textAlignment: Alignment.Center, wrap: true, font: GUI.SubHeadingFont) + { + TextColor = GUI.Style.Orange + }; + UpdateUndoHistoryPanel(); //----------------------------------------------- @@ -516,9 +530,9 @@ namespace Barotrauma showEntitiesPanel = new GUIFrame(new RectTransform(new Vector2(0.1f, 0.5f), GUI.Canvas) { MinSize = new Point(190, 0) - }) - { - Visible = false + }) + { + Visible = false }; GUILayoutGroup paddedShowEntitiesPanel = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.98f), showEntitiesPanel.RectTransform, Anchor.Center)) @@ -599,14 +613,14 @@ namespace Barotrauma List availableSubcategories = new List(); foreach (var prefab in MapEntityPrefab.List) { - if (!string.IsNullOrEmpty(prefab.Subcategory) && !availableSubcategories.Contains(prefab.Subcategory)) - { - availableSubcategories.Add(prefab.Subcategory); + if (!string.IsNullOrEmpty(prefab.Subcategory) && !availableSubcategories.Contains(prefab.Subcategory)) + { + availableSubcategories.Add(prefab.Subcategory); } } foreach (string subcategory in availableSubcategories) { - var tb = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.1f), subcategoryList.Content.RectTransform), + var tb = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.1f), subcategoryList.Content.RectTransform), TextManager.Get("subcategory." + subcategory, returnNull: true) ?? subcategory, font: GUI.SmallFont) { UserData = subcategory, @@ -643,7 +657,7 @@ namespace Barotrauma AbsoluteSpacing = (int)(GUI.Scale * 4) }; - var itemCountText = new GUITextBlock(new RectTransform(new Vector2(0.75f, 0.0f), paddedEntityCountPanel.RectTransform), TextManager.Get("Items"), + var itemCountText = new GUITextBlock(new RectTransform(new Vector2(0.75f, 0.0f), paddedEntityCountPanel.RectTransform), TextManager.Get("Items"), textAlignment: Alignment.CenterLeft, font: GUI.SmallFont); var itemCount = new GUITextBlock(new RectTransform(new Vector2(0.33f, 1.0f), itemCountText.RectTransform, Anchor.TopRight, Pivot.TopLeft), "", textAlignment: Alignment.CenterRight); itemCount.TextGetter = () => @@ -652,7 +666,7 @@ namespace Barotrauma return Item.ItemList.Count.ToString(); }; - var structureCountText = new GUITextBlock(new RectTransform(new Vector2(0.75f, 0.0f), paddedEntityCountPanel.RectTransform), TextManager.Get("Structures"), + var structureCountText = new GUITextBlock(new RectTransform(new Vector2(0.75f, 0.0f), paddedEntityCountPanel.RectTransform), TextManager.Get("Structures"), textAlignment: Alignment.CenterLeft, font: GUI.SmallFont); var structureCount = new GUITextBlock(new RectTransform(new Vector2(0.33f, 1.0f), structureCountText.RectTransform, Anchor.TopRight, Pivot.TopLeft), "", textAlignment: Alignment.CenterRight); structureCount.TextGetter = () => @@ -662,7 +676,7 @@ namespace Barotrauma return count.ToString(); }; - var wallCountText = new GUITextBlock(new RectTransform(new Vector2(0.75f, 0.0f), paddedEntityCountPanel.RectTransform), TextManager.Get("Walls"), + var wallCountText = new GUITextBlock(new RectTransform(new Vector2(0.75f, 0.0f), paddedEntityCountPanel.RectTransform), TextManager.Get("Walls"), textAlignment: Alignment.CenterLeft, font: GUI.SmallFont); var wallCount = new GUITextBlock(new RectTransform(new Vector2(0.33f, 1.0f), wallCountText.RectTransform, Anchor.TopRight, Pivot.TopLeft), "", textAlignment: Alignment.CenterRight); wallCount.TextGetter = () => @@ -670,8 +684,8 @@ namespace Barotrauma wallCount.TextColor = ToolBox.GradientLerp(Structure.WallList.Count / 500.0f, GUI.Style.Green, GUI.Style.Orange, GUI.Style.Red); return Structure.WallList.Count.ToString(); }; - - var lightCountLabel = new GUITextBlock(new RectTransform(new Vector2(0.75f, 0.0f), paddedEntityCountPanel.RectTransform), TextManager.Get("SubEditorLights"), + + var lightCountLabel = new GUITextBlock(new RectTransform(new Vector2(0.75f, 0.0f), paddedEntityCountPanel.RectTransform), TextManager.Get("SubEditorLights"), textAlignment: Alignment.CenterLeft, font: GUI.SmallFont); var lightCountText = new GUITextBlock(new RectTransform(new Vector2(0.33f, 1.0f), lightCountLabel.RectTransform, Anchor.TopRight, Pivot.TopLeft), "", textAlignment: Alignment.CenterRight); lightCountText.TextGetter = () => @@ -685,7 +699,7 @@ namespace Barotrauma lightCountText.TextColor = ToolBox.GradientLerp(lightCount / 250.0f, GUI.Style.Green, GUI.Style.Orange, GUI.Style.Red); return lightCount.ToString(); }; - var shadowCastingLightCountLabel = new GUITextBlock(new RectTransform(new Vector2(0.75f, 0.0f), paddedEntityCountPanel.RectTransform), TextManager.Get("SubEditorShadowCastingLights"), + var shadowCastingLightCountLabel = new GUITextBlock(new RectTransform(new Vector2(0.75f, 0.0f), paddedEntityCountPanel.RectTransform), TextManager.Get("SubEditorShadowCastingLights"), textAlignment: Alignment.CenterLeft, font: GUI.SmallFont, wrap: true); var shadowCastingLightCountText = new GUITextBlock(new RectTransform(new Vector2(0.33f, 1.0f), shadowCastingLightCountLabel.RectTransform, Anchor.TopRight, Pivot.TopLeft), "", textAlignment: Alignment.CenterRight); shadowCastingLightCountText.TextGetter = () => @@ -780,7 +794,7 @@ namespace Barotrauma { if (text == lastFilter) { return true; } lastFilter = text; - FilterEntities(text); + FilterEntities(text); return true; }; @@ -794,7 +808,7 @@ namespace Barotrauma OnClicked = (btn, userdata) => { OpenEntityMenu(null); - return true; + return true; } }); @@ -962,7 +976,7 @@ namespace Barotrauma #if !DEBUG if (ep.HideInMenus) { continue; } #endif - CreateEntityElement(ep, entitiesPerRow, entityListInner.Content); + CreateEntityElement(ep, entitiesPerRow, entityListInner.Content); } entityListInner.UpdateScrollBarSize(); @@ -972,7 +986,7 @@ namespace Barotrauma entityListInner.RectTransform.NonScaledSize = new Point(entityListInner.Rect.Width, contentHeight); entityListInner.RectTransform.MinSize = new Point(0, contentHeight); - entityListInner.Content.RectTransform.SortChildren((i1, i2) => + entityListInner.Content.RectTransform.SortChildren((i1, i2) => string.Compare(((MapEntityPrefab)i1.GUIComponent.UserData). Name, (i2.GUIComponent.UserData as MapEntityPrefab)?.Name, StringComparison.Ordinal)); } @@ -1153,8 +1167,8 @@ namespace Barotrauma { AutoSaveInfo = XMLExtensions.TryLoadXml(autoSaveInfoPath); } - - GameMain.LightManager.AmbientLight = + + GameMain.LightManager.AmbientLight = Level.Loaded?.GenerationParams?.AmbientLightColor ?? new Color(3, 3, 3, 3); @@ -1219,9 +1233,9 @@ namespace Barotrauma { CoroutineManager.StartCoroutine(AutoSaveCoroutine(), "SubEditorAutoSave"); } - + ImageManager.OnEditorSelected(); - + GameAnalyticsManager.SetCustomDimension01("editor"); if (!GameMain.Config.EditorDisclaimerShown) { @@ -1293,10 +1307,10 @@ namespace Barotrauma tempTarget = DateTime.Now; wasPaused = true; } - + if (!GameMain.Instance.Paused && wasPaused) { - wasPaused = false; + wasPaused = false; target = target.AddSeconds((DateTime.Now - tempTarget).TotalSeconds); } yield return CoroutineStatus.Running; @@ -1309,7 +1323,7 @@ namespace Barotrauma } yield return CoroutineStatus.Success; } - + public override void Deselect() { base.Deselect(); @@ -1350,7 +1364,7 @@ namespace Barotrauma dummyCharacter = null; GameMain.World.ProcessChanges(); } - + GUIMessageBox.MessageBoxes.ForEachMod(component => { if (component is GUIMessageBox { Closed: false, UserData: "colorpicker" } msgBox) @@ -1363,7 +1377,7 @@ namespace Barotrauma msgBox.Close(); } }); - + ClearFilter(); } @@ -1429,8 +1443,8 @@ namespace Barotrauma min?.Remove(); } - XElement newElement = new XElement("AutoSave", - new XAttribute("file", filePath), + XElement newElement = new XElement("AutoSave", + new XAttribute("file", filePath), new XAttribute("name", Submarine.MainSub.Info.Name), new XAttribute("time", (ulong)time.TotalSeconds)); AutoSaveInfo.Root.Add(newElement); @@ -1444,7 +1458,7 @@ namespace Barotrauma DebugConsole.ThrowError("Saving auto save info to \"" + autoSaveInfoPath + "\" failed!", e); } }); - + Barotrauma.IO.Validation.SkipValidationInDebugBuilds = false; CrossThread.RequestExecutionOnMainThread(DisplayAutoSavePrompt); } @@ -1522,11 +1536,16 @@ namespace Barotrauma { #if DEBUG var existingFiles = ContentPackage.GetFilesOfType(GameMain.VanillaContent.ToEnumerable(), contentType); + if (contentType == ContentType.OutpostModule) + { + existingFiles = existingFiles.Where(f => f.Path.Contains("Ruin") == Submarine.MainSub.Info.OutpostModuleInfo.ModuleFlags.Contains("ruin")); + } #else var existingFiles = ContentPackage.GetFilesOfType(GameMain.Config.AllEnabledPackages.Where(c => c != GameMain.VanillaContent), contentType); #endif - specialSavePath = existingFiles.FirstOrDefault(f => + specialSavePath = existingFiles.FirstOrDefault(f => Path.GetFullPath(f.Path) != Path.GetFullPath(SubmarineInfo.SavePath) && ContentPackage.IsModFilePathAllowed(f.Path))?.Path; + if (!string.IsNullOrEmpty(specialSavePath)) { specialSavePath = Path.GetDirectoryName(specialSavePath); @@ -1569,7 +1588,7 @@ namespace Barotrauma saveFrame = null; msgBox.Close(); return true; - }; + }; msgBox.Buttons[1].OnClicked = (bt, userdata) => { SaveSubToFile(nameBox.Text); @@ -1592,7 +1611,7 @@ namespace Barotrauma GUI.AddMessage(TextManager.Get("SubNameMissingWarning"), GUI.Style.Red); return false; } - + foreach (var illegalChar in Path.GetInvalidFileNameChars()) { if (!name.Contains(illegalChar)) continue; @@ -1602,7 +1621,7 @@ namespace Barotrauma string savePath = name + ".sub"; string prevSavePath = null; - string directoryName = Submarine.MainSub?.Info?.FilePath == null ? + string directoryName = Submarine.MainSub?.Info?.FilePath == null ? SubmarineInfo.SavePath : Path.GetDirectoryName(Submarine.MainSub.Info.FilePath); if (!string.IsNullOrEmpty(specialSavePath)) { @@ -1721,7 +1740,7 @@ namespace Barotrauma Barotrauma.IO.Validation.SkipValidationInDebugBuilds = false; Submarine.MainSub.CheckForErrors(); - + GUI.AddMessage(TextManager.GetWithVariable("SubSavedNotification", "[filepath]", savePath), GUI.Style.Green); SubmarineInfo.RefreshSavedSub(savePath); @@ -1729,11 +1748,11 @@ namespace Barotrauma string downloadFolder = Path.GetFullPath(SaveUtil.SubmarineDownloadFolder); linkedSubBox.ClearChildren(); - foreach (SubmarineInfo sub in SubmarineInfo.SavedSubmarines) - { + foreach (SubmarineInfo sub in SubmarineInfo.SavedSubmarines) + { if (sub.Type != SubmarineType.Player) { continue; } if (Path.GetDirectoryName(Path.GetFullPath(sub.FilePath)) == downloadFolder) { continue; } - linkedSubBox.AddItem(sub.Name, sub); + linkedSubBox.AddItem(sub.Name, sub); } subNameLabel.Text = ToolBox.LimitString(Submarine.MainSub.Info.Name, subNameLabel.Font, subNameLabel.Rect.Width); } @@ -1744,7 +1763,7 @@ namespace Barotrauma private void CreateSaveScreen(bool quickSave = false) { if (saveFrame != null) { return; } - + if (!quickSave) { CloseItem(); @@ -1757,7 +1776,7 @@ namespace Barotrauma }; new GUIFrame(new RectTransform(GUI.Canvas.RelativeSize, saveFrame.RectTransform, Anchor.Center), style: "GUIBackgroundBlocker"); - + var innerFrame = new GUIFrame(new RectTransform(new Vector2(0.55f, 0.6f), saveFrame.RectTransform, Anchor.Center) { MinSize = new Point(750, 500) }); var paddedSaveFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), innerFrame.RectTransform, Anchor.Center)) { Stretch = true, RelativeSpacing = 0.02f }; @@ -1767,7 +1786,7 @@ namespace Barotrauma var leftColumn = new GUILayoutGroup(new RectTransform(new Vector2(0.55f, 1.0f), columnArea.RectTransform)) { RelativeSpacing = 0.01f, Stretch = true }; var rightColumn = new GUILayoutGroup(new RectTransform(new Vector2(0.42f, 1.0f), columnArea.RectTransform)) { RelativeSpacing = 0.02f, Stretch = true }; - // left column ----------------------------------------------------------------------- + // left column ----------------------------------------------------------------------- var nameHeaderGroup = new GUILayoutGroup(new RectTransform(new Vector2(.975f, 0.03f), leftColumn.RectTransform), true); var saveSubLabel = new GUITextBlock(new RectTransform(new Vector2(.5f, 1f), nameHeaderGroup.RectTransform), @@ -1837,6 +1856,7 @@ namespace Barotrauma subTypeContainer.RectTransform.MinSize = new Point(0, subTypeContainer.RectTransform.Children.Max(c => c.MinSize.Y)); foreach (SubmarineType subType in Enum.GetValues(typeof(SubmarineType))) { + if (subType == SubmarineType.Ruin) { continue; } string textTag = "SubmarineType." + subType; if (subType == SubmarineType.EnemySubmarine && !TextManager.ContainsTag(textTag)) { @@ -1866,13 +1886,14 @@ namespace Barotrauma new GUITextBlock(new RectTransform(new Vector2(0.5f, 1f), outpostModuleGroup.RectTransform), TextManager.Get("outpostmoduletype"), textAlignment: Alignment.CenterLeft); HashSet availableFlags = new HashSet(); foreach (string flag in OutpostGenerationParams.Params.SelectMany(p => p.ModuleCounts.Select(m => m.Key))) { availableFlags.Add(flag); } + foreach (string flag in RuinGeneration.RuinGenerationParams.RuinParams.SelectMany(p => p.ModuleCounts.Select(m => m.Key))) { availableFlags.Add(flag); } foreach (var sub in SubmarineInfo.SavedSubmarines) { if (sub.OutpostModuleInfo == null) { continue; } - foreach (string flag in sub.OutpostModuleInfo.ModuleFlags) + foreach (string flag in sub.OutpostModuleInfo.ModuleFlags) { if (flag == "none") { continue; } - availableFlags.Add(flag); + availableFlags.Add(flag); } } @@ -1881,7 +1902,7 @@ namespace Barotrauma foreach (string flag in availableFlags) { moduleTypeDropDown.AddItem(TextManager.Capitalize(flag), flag); - if (Submarine.MainSub?.Info?.OutpostModuleInfo == null) { continue; } + if (Submarine.MainSub?.Info?.OutpostModuleInfo == null) { continue; } if (Submarine.MainSub.Info.OutpostModuleInfo.ModuleFlags.Contains(flag)) { moduleTypeDropDown.SelectItem(flag); @@ -1890,9 +1911,9 @@ namespace Barotrauma moduleTypeDropDown.OnSelected += (_, __) => { if (Submarine.MainSub?.Info?.OutpostModuleInfo == null) { return false; } - Submarine.MainSub.Info.OutpostModuleInfo.SetFlags(moduleTypeDropDown.SelectedDataMultiple.Cast()); + Submarine.MainSub.Info.OutpostModuleInfo.SetFlags(moduleTypeDropDown.SelectedDataMultiple.Cast()); moduleTypeDropDown.Text = ToolBox.LimitString( - Submarine.MainSub.Info.OutpostModuleInfo.ModuleFlags.Any(f => f != "none") ? moduleTypeDropDown.Text : "None", + Submarine.MainSub.Info.OutpostModuleInfo.ModuleFlags.Any(f => f != "none") ? moduleTypeDropDown.Text : "None", moduleTypeDropDown.Font, moduleTypeDropDown.Rect.Width); return true; }; @@ -1903,11 +1924,11 @@ namespace Barotrauma var allowAttachGroup = new GUILayoutGroup(new RectTransform(new Vector2(.975f, 0.1f), outpostSettingsContainer.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft); new GUITextBlock(new RectTransform(new Vector2(0.5f, 1f), allowAttachGroup.RectTransform), TextManager.Get("outpostmoduleallowattachto"), textAlignment: Alignment.CenterLeft); - + var allowAttachDropDown = new GUIDropDown(new RectTransform(new Vector2(0.5f, 1f), allowAttachGroup.RectTransform), text: string.Join(", ", Submarine.MainSub?.Info?.OutpostModuleInfo?.AllowAttachToModules.Select(s => TextManager.Capitalize(s)) ?? "Any".ToEnumerable()), selectMultiple: true); allowAttachDropDown.AddItem(TextManager.Capitalize("any"), "any"); - if (Submarine.MainSub.Info.OutpostModuleInfo == null || + if (Submarine.MainSub.Info.OutpostModuleInfo == null || !Submarine.MainSub.Info.OutpostModuleInfo.AllowAttachToModules.Any() || Submarine.MainSub.Info.OutpostModuleInfo.AllowAttachToModules.All(s => s.Equals("any", StringComparison.OrdinalIgnoreCase))) { @@ -2069,7 +2090,7 @@ namespace Barotrauma new GUINumberInput(new RectTransform(new Vector2(0.4f, 1.0f), priceGroup.RectTransform), GUINumberInput.NumberType.Int, hidePlusMinusButtons: true) { IntValue = Math.Max(Submarine.MainSub?.Info?.Price ?? basePrice, basePrice), - MinValueInt = basePrice, + MinValueInt = basePrice, MaxValueInt = 999999, OnValueChanged = (numberInput) => { @@ -2204,7 +2225,7 @@ namespace Barotrauma // right column --------------------------------------------------- new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), rightColumn.RectTransform), TextManager.Get("SubPreviewImage"), font: GUI.SubHeadingFont); - + var previewImageHolder = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.5f), rightColumn.RectTransform), style: null) { Color = Color.Black, CanBeFocused = false }; previewImage = new GUIImage(new RectTransform(Vector2.One, previewImageHolder.RectTransform), Submarine.MainSub?.Info.PreviewImage, scaleToFit: true); @@ -2262,7 +2283,7 @@ namespace Barotrauma var settingsLabel = new GUITextBlock(new RectTransform(new Vector2(0.5f, 0.0f), horizontalArea.RectTransform), TextManager.Get("SaveSubDialogSettings"), wrap: true, font: GUI.SmallFont); - var tagContainer = new GUIListBox(new RectTransform(new Vector2(0.5f, 1.0f - settingsLabel.RectTransform.RelativeSize.Y), + var tagContainer = new GUIListBox(new RectTransform(new Vector2(0.5f, 1.0f - settingsLabel.RectTransform.RelativeSize.Y), horizontalArea.RectTransform, Anchor.BottomLeft), style: "InnerFrame"); @@ -2393,9 +2414,9 @@ namespace Barotrauma Stretch = true }; - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedSaveFrame.RectTransform), + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedSaveFrame.RectTransform), TextManager.Get("SaveItemAssemblyDialogHeader"), font: GUI.LargeFont); - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedSaveFrame.RectTransform), + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedSaveFrame.RectTransform), TextManager.Get("SaveItemAssemblyDialogName")); nameBox = new GUITextBox(new RectTransform(new Vector2(0.6f, 0.1f), paddedSaveFrame.RectTransform)); @@ -2563,7 +2584,7 @@ namespace Barotrauma RelativeSpacing = 0.1f, Stretch = true }; - + var searchBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.1f), paddedLoadFrame.RectTransform), font: GUI.Font, createClearButton: true); var searchTitle = new GUITextBlock(new RectTransform(Vector2.One, searchBox.RectTransform), TextManager.Get("serverlog.filter"), textAlignment: Alignment.CenterLeft, font: GUI.Font) @@ -2659,7 +2680,7 @@ namespace Barotrauma deleteButton.Enabled = false; return true; }; - + if (AutoSaveInfo?.Root != null) { @@ -2679,11 +2700,11 @@ namespace Barotrauma DateTime time = DateTime.MinValue.AddSeconds(saveElement.GetAttributeUInt64("time", 0)); TimeSpan difference = DateTime.UtcNow - time; - string tooltip = TextManager.GetWithVariables("subeditor.autosaveage", + string tooltip = TextManager.GetWithVariables("subeditor.autosaveage", new[] { - "[hours]", - "[minutes]", + "[hours]", + "[minutes]", "[seconds]" }, new[] @@ -2701,7 +2722,7 @@ namespace Barotrauma if (totalMinutes < 1) { timeFormat = TextManager.Get("subeditor.savedjustnow"); - } + } else if (totalMinutes > 60) { timeFormat = TextManager.Get("subeditor.savedmorethanhour"); @@ -2710,7 +2731,7 @@ namespace Barotrauma { timeFormat = TextManager.GetWithVariable("subeditor.saveageminutes", "[minutes]", difference.Minutes.ToString()); } - + string entryName = TextManager.GetWithVariables("subeditor.autosaveentry", new []{ "[submarine]", "[saveage]" }, new []{ submarineName, timeFormat }); loadAutoSave.AddItem(entryName, saveElement, tooltip); @@ -2759,13 +2780,13 @@ namespace Barotrauma if (string.IsNullOrWhiteSpace(filePath)) { return; } var loadedSub = Submarine.Load(new SubmarineInfo(filePath), true); - + // set the submarine file path to the "default" value loadedSub.Info.FilePath = Path.Combine(SubmarineInfo.SavePath, $"{TextManager.Get("UnspecifiedSubFileName")}.sub"); loadedSub.Info.Name = TextManager.Get("UnspecifiedSubFileName"); - try + try { - loadedSub.Info.Name = loadedSub.Info.SubmarineElement.GetAttributeString("name", loadedSub.Info.Name); + loadedSub.Info.Name = loadedSub.Info.SubmarineElement.GetAttributeString("name", loadedSub.Info.Name); } catch (Exception e) { @@ -2776,9 +2797,9 @@ namespace Barotrauma Submarine.MainSub.UpdateTransform(); Submarine.MainSub.Info.Name = loadedSub.Info.Name; subNameLabel.Text = ToolBox.LimitString(loadedSub.Info.Name, subNameLabel.Font, subNameLabel.Rect.Width); - + CreateDummyCharacter(); - + cam.Position = Submarine.MainSub.Position + Submarine.MainSub.HiddenSubPosition; loadFrame = null; @@ -2822,10 +2843,10 @@ namespace Barotrauma cam.Position = Submarine.MainSub.Position + Submarine.MainSub.HiddenSubPosition; loadFrame = null; - + if (selectedSub.Info.GameVersion < new Version("0.8.9.0")) { - var adjustLightsPrompt = new GUIMessageBox(TextManager.Get("Warning"), TextManager.Get("AdjustLightsPrompt"), + var adjustLightsPrompt = new GUIMessageBox(TextManager.Get("Warning"), TextManager.Get("AdjustLightsPrompt"), new[] { TextManager.Get("Yes"), TextManager.Get("No") }); adjustLightsPrompt.Buttons[0].OnClicked += adjustLightsPrompt.Close; adjustLightsPrompt.Buttons[0].OnClicked += (btn, userdata) => @@ -2862,9 +2883,9 @@ namespace Barotrauma var msgBox = new GUIMessageBox( TextManager.Get("DeleteDialogLabel"), - TextManager.GetWithVariable("DeleteDialogQuestion", "[file]", sub.Name), + TextManager.GetWithVariable("DeleteDialogQuestion", "[file]", sub.Name), new string[] { TextManager.Get("Yes"), TextManager.Get("Cancel") }); - msgBox.Buttons[0].OnClicked += (btn, userData) => + msgBox.Buttons[0].OnClicked += (btn, userData) => { try { @@ -2880,7 +2901,7 @@ namespace Barotrauma return true; }; msgBox.Buttons[0].OnClicked += msgBox.Close; - msgBox.Buttons[1].OnClicked += msgBox.Close; + msgBox.Buttons[1].OnClicked += msgBox.Close; } private void OpenEntityMenu(MapEntityCategory? entityCategory) @@ -2896,12 +2917,12 @@ namespace Barotrauma } selectedCategory = entityCategory; - + SetMode(Mode.Default); saveFrame = null; loadFrame = null; - + foreach (GUIComponent child in toggleEntityMenuButton.Children) { child.SpriteEffects = entityMenuOpen ? SpriteEffects.None : SpriteEffects.FlipVertically; @@ -2913,15 +2934,15 @@ namespace Barotrauma var innerList = child.GetChild(); foreach (GUIComponent grandChild in innerList.Content.Children) { - grandChild.Visible = true; + grandChild.Visible = true; } } - - if (!string.IsNullOrEmpty(entityFilterBox.Text)) - { - FilterEntities(entityFilterBox.Text); + + if (!string.IsNullOrEmpty(entityFilterBox.Text)) + { + FilterEntities(entityFilterBox.Text); } - + categorizedEntityList.UpdateScrollBarSize(); categorizedEntityList.BarScroll = 0.0f; // categorizedEntityList.Visible = true; @@ -2946,7 +2967,7 @@ namespace Barotrauma } }; categorizedEntityList.UpdateScrollBarSize(); - categorizedEntityList.BarScroll = 0.0f; + categorizedEntityList.BarScroll = 0.0f; return; } @@ -2955,7 +2976,7 @@ namespace Barotrauma filter = filter.ToLower(); foreach (GUIComponent child in allEntityList.Content.Children) { - child.Visible = + child.Visible = (!selectedCategory.HasValue || ((MapEntityPrefab)child.UserData).Category.HasFlag(selectedCategory)) && ((MapEntityPrefab)child.UserData).Name.ToLower().Contains(filter); } @@ -2989,7 +3010,7 @@ namespace Barotrauma MapEntity.DeselectAll(); MapEntity.FilteredSelectedList.Clear(); ClearUndoBuffer(); - + CreateDummyCharacter(); if (newMode == Mode.Wiring) { @@ -3005,14 +3026,14 @@ namespace Barotrauma dummyCharacter.Inventory.AllItems.ForEachMod(it => it.Remove()); dummyCharacter.Remove(); - dummyCharacter = null; + dummyCharacter = null; } private void CreateContextMenu() { if (GUIContextMenu.CurrentContextMenu != null) { return; } - List targets = MapEntity.mapEntityList.Any(me => me.IsHighlighted && !MapEntity.SelectedList.Contains(me)) ? + List targets = MapEntity.mapEntityList.Any(me => me.IsHighlighted && !MapEntity.SelectedList.Contains(me)) ? MapEntity.mapEntityList.Where(me => me.IsHighlighted).ToList() : new List(MapEntity.SelectedList); @@ -3139,7 +3160,7 @@ namespace Barotrauma break; } } - + bool setValues = true; object sliderMutex = new object(), sliderTextMutex = new object(), @@ -3208,7 +3229,7 @@ namespace Barotrauma Point areaSize = new Point(rect.Width, rect.Height / 2); Rectangle newColorRect = new Rectangle(rect.Location, areaSize); Rectangle oldColorRect = new Rectangle(new Point(newColorRect.Left, newColorRect.Bottom), areaSize); - + GUI.DrawRectangle(batch, newColorRect, ToolBox.HSVToRGB(colorPicker.SelectedHue, colorPicker.SelectedSaturation, colorPicker.SelectedValue), isFilled: true); GUI.DrawRectangle(batch, oldColorRect, originalColor, isFilled: true); GUI.DrawRectangle(batch, rect, Color.Black, isFilled: false); @@ -3218,10 +3239,10 @@ namespace Barotrauma hueScrollBar.OnMoved = (bar, scroll) => { SetColor(sliderMutex); return true; }; hueTextBox.OnValueChanged = input => { SetColor(sliderTextMutex); }; - + satScrollBar.OnMoved = (bar, scroll) => { SetColor(sliderMutex); return true; }; satTextBox.OnValueChanged = input => { SetColor(sliderTextMutex); }; - + valueScrollBar.OnMoved = (bar, scroll) => { SetColor(sliderMutex); return true; }; valueTextBox.OnValueChanged = input => { SetColor(sliderTextMutex); }; @@ -3251,7 +3272,7 @@ namespace Barotrauma oldProperties[color].Add(sEntity); } - List affected = entities.Select(t => t.Entity).Where(se => se is MapEntity { Removed: false }).ToList(); + List affected = entities.Select(t => t.Entity).Where(se => se is MapEntity { Removed: false } || se is ItemComponent).ToList(); StoreCommand(new PropertyCommand(affected, property.Name, newColor, oldProperties)); if (MapEntity.EditingHUD != null && (MapEntity.EditingHUD.UserData == entity || (!(entity is ItemComponent ic) || MapEntity.EditingHUD.UserData == ic.Item))) @@ -3273,7 +3294,7 @@ namespace Barotrauma } return true; }; - + cancelButton.OnClicked = (button, o) => { colorPicker.DisposeTextures(); @@ -3301,7 +3322,7 @@ namespace Barotrauma SetSliderTexts(hsv); SetColorPicker(hsv); SetHex(hsv); - } + } else if (source == sliderTextMutex) { Vector3 hsv = new Vector3(hueTextBox.FloatValue * 360f, satTextBox.FloatValue, valueTextBox.FloatValue); @@ -3315,7 +3336,7 @@ namespace Barotrauma SetSliders(hsv); SetSliderTexts(hsv); SetHex(hsv); - } + } else if (source == hexMutex) { Vector3 hsv = ToolBox.RGBToHSV(XMLExtensions.ParseColor(hexValueBox.Text, errorMessages: false)); @@ -3370,7 +3391,7 @@ namespace Barotrauma static string ColorToHex(Color color) => $"#{(color.R << 16 | color.G << 8 | color.B):X6}"; } - + private GUIFrame CreateWiringPanel() { GUIFrame frame = new GUIFrame(new RectTransform(new Vector2(0.03f, 0.35f), GUI.Canvas) @@ -3437,7 +3458,7 @@ namespace Barotrauma dummyCharacter.Inventory.TryPutItem(wire, slotIndex, false, false, dummyCharacter); return true; - + } /// @@ -3453,18 +3474,18 @@ namespace Barotrauma // We teleport our dummy character to the item so it appears as the entity stays still when in reality the dummy is holding it oldItemPosition = itemContainer.SimPosition; TeleportDummyCharacter(oldItemPosition); - + // Override this so we can be sure the container opens var container = itemContainer.GetComponent(); if (container != null) { container.KeepOpenWhenEquipped = true; } - + // We accept any slots except "Any" since that would take priority List allowedSlots = new List(); itemContainer.AllowedSlots.ForEach(type => { if (type != InvSlotType.Any) { allowedSlots.Add(type); } }); - + // Try to place the item in the dummy character's inventory bool success = dummyCharacter.Inventory.TryPutItem(itemContainer, dummyCharacter, allowedSlots); if (success) { OpenedItem = itemContainer; } @@ -3540,7 +3561,7 @@ namespace Barotrauma submarineDescriptionCharacterCount.Text = text.Length + " / " + submarineDescriptionLimit; } - + private bool SelectPrefab(GUIComponent component, object obj) { allEntityList.Deselect(); @@ -3548,7 +3569,7 @@ namespace Barotrauma if (GUI.MouseOn is GUIButton || GUI.MouseOn?.Parent is GUIButton) { return false; } AddPreviouslyUsed(obj as MapEntityPrefab); - + //if selecting a gap/hull/waypoint/spawnpoint, make sure the visibility is toggled on if (obj is CoreEntityPrefab prefab) { @@ -3574,14 +3595,14 @@ namespace Barotrauma { var itemInstance = LoadItemAssemblyInventorySafe(assemblyPrefab); var spawnedItem = false; - + itemInstance.ForEach(newItem => { if (newItem != null) { var placedItem = inv.TryPutItem(newItem, dummyCharacter); spawnedItem |= placedItem; - + if (!placedItem) { // Remove everything inside of the item so we don't get the popup asking if we want to keep the contained items @@ -3779,7 +3800,7 @@ namespace Barotrauma i--; } } - + foreach (MapEntity e in mapEntityList) { Rectangle entRect = e.WorldRect; @@ -3822,7 +3843,7 @@ namespace Barotrauma } } } - + for (int i = 0; i < hullRects.Count;) { Rectangle hullRect = hullRects[i]; @@ -3850,7 +3871,7 @@ namespace Barotrauma if (i >= hullRects.Count) break; } } - + for (int i = hullRects.Count-1; i >= 0;) { Rectangle hullRect = hullRects[i]; @@ -3878,7 +3899,7 @@ namespace Barotrauma if (i < 0) break; } } - + hullRects.Sort((a, b) => { if (a.X < b.X) return -1; @@ -3887,7 +3908,7 @@ namespace Barotrauma if (a.Y > b.Y) return 1; return 0; }); - + for (int i = 0; i < hullRects.Count - 1; i++) { Rectangle rect = hullRects[i]; @@ -3916,7 +3937,7 @@ namespace Barotrauma i--; } } - + for (int i = 0; i < hullRects.Count;i++) { Rectangle rect = hullRects[i]; @@ -3924,7 +3945,7 @@ namespace Barotrauma rect.Height += 32; hullRects[i] = rect; } - + hullRects.Sort((a, b) => { if (a.Y < b.Y) return -1; @@ -3933,7 +3954,7 @@ namespace Barotrauma if (a.X > b.X) return 1; return 0; }); - + for (int i = 0; i < hullRects.Count; i++) { for (int j = i+1; j < hullRects.Count; j++) @@ -3969,7 +3990,7 @@ namespace Barotrauma Gap newGap = new Gap(MapEntityPrefab.Find(null, "gap"), gapRect); } } - + public override void AddToGUIUpdateList() { if (GUI.DisableHUD) { return; } @@ -4013,7 +4034,7 @@ namespace Barotrauma saveFrame?.AddToGUIUpdateList(); } } - + /// /// GUI.MouseOn doesn't get updated while holding primary mouse and we need it to /// @@ -4023,7 +4044,7 @@ namespace Barotrauma return (EntityMenu?.MouseRect.Contains(PlayerInput.MousePosition) ?? false) || (entityCountPanel?.MouseRect.Contains(PlayerInput.MousePosition) ?? false) - || (MapEntity.EditingHUD?.MouseRect.Contains(PlayerInput.MousePosition) ?? false) + || (MapEntity.EditingHUD?.MouseRect.Contains(PlayerInput.MousePosition) ?? false) || (TopPanel?.MouseRect.Contains(PlayerInput.MousePosition) ?? false); } @@ -4087,6 +4108,8 @@ namespace Barotrauma { if (undoBufferPanel == null) { return; } + undoBufferDisclaimer.Visible = mode == Mode.Wiring; + undoBufferList.Content.Children.ForEachMod(component => { undoBufferList.Content.RemoveChild(component); @@ -4150,7 +4173,7 @@ namespace Barotrauma if (WiringMode && dummyCharacter != null) { - Wire equippedWire = + Wire equippedWire = Character.Controlled?.HeldItems.FirstOrDefault(it => it.GetComponent() != null)?.GetComponent() ?? Wire.DraggingWire; @@ -4168,9 +4191,9 @@ namespace Barotrauma } } } - + var highlightedEntities = new List(); - + // ReSharper disable once LoopCanBeConvertedToQuery foreach (Item item in MapEntity.mapEntityList.Where(entity => entity is Item).Cast()) { @@ -4178,11 +4201,11 @@ namespace Barotrauma if (wire == null || !wire.IsMouseOn()) { continue; } highlightedEntities.Add(item); } - + MapEntity.UpdateHighlighting(highlightedEntities, true); } } - + hullVolumeFrame.Visible = MapEntity.SelectedList.Any(s => s is Hull); hullVolumeFrame.RectTransform.AbsoluteOffset = new Point(Math.Max(showEntitiesPanel.Rect.Right, previouslyUsedPanel.Rect.Right), 0); saveAssemblyFrame.Visible = MapEntity.SelectedList.Count > 0; @@ -4200,11 +4223,11 @@ namespace Barotrauma else { var targetWithOffset = new Vector2(camTargetFocus.X, camTargetFocus.Y - offset / 2); - if (Math.Abs(cam.Position.X - targetWithOffset.X) < 1.0f && + if (Math.Abs(cam.Position.X - targetWithOffset.X) < 1.0f && Math.Abs(cam.Position.Y - targetWithOffset.Y) < 1.0f) { camTargetFocus = Vector2.Zero; - } + } else { cam.Position += (targetWithOffset - cam.Position) / cam.MoveSmoothness; @@ -4217,9 +4240,9 @@ namespace Barotrauma undoBufferList.Deselect(); } - if (GUI.KeyboardDispatcher.Subscriber == null - || MapEntity.EditingHUD != null - && GUI.KeyboardDispatcher.Subscriber is GUIComponent sub + if (GUI.KeyboardDispatcher.Subscriber == null + || MapEntity.EditingHUD != null + && GUI.KeyboardDispatcher.Subscriber is GUIComponent sub && MapEntity.EditingHUD.Children.Contains(sub)) { if (PlayerInput.IsCtrlDown() && !WiringMode) @@ -4229,7 +4252,7 @@ namespace Barotrauma // Ctrl+Shift+Z redos while Ctrl+Z undos if (PlayerInput.IsShiftDown()) { Redo(1); } else { Undo(1); } } - + // ctrl+Y redo if (PlayerInput.KeyHit(Keys.Y)) { @@ -4288,7 +4311,7 @@ namespace Barotrauma } } } - + // Focus to selection if (PlayerInput.KeyHit(Keys.F) && mode == Mode.Default) { @@ -4310,7 +4333,7 @@ namespace Barotrauma camTargetFocus = rect.Center.ToVector2(); } } - + if (GameMain.Config.KeyBind(InputType.ToggleInventory).IsHit() && mode == Mode.Default) { toggleEntityMenuButton.OnClicked?.Invoke(toggleEntityMenuButton, toggleEntityMenuButton.UserData); @@ -4396,7 +4419,7 @@ namespace Barotrauma lightComponent.LightColor; lightComponent.Light.LightSpriteEffect = lightComponent.Item.SpriteEffects; } - } + } GameMain.LightManager?.Update((float)deltaTime); } @@ -4432,7 +4455,7 @@ namespace Barotrauma }); } - if (dummyCharacter.SelectedConstruction == null || + if (dummyCharacter.SelectedConstruction == null || dummyCharacter.SelectedConstruction.GetComponent() != null) { if (WiringMode && PlayerInput.IsShiftDown()) @@ -4449,12 +4472,12 @@ namespace Barotrauma var (cursorX, cursorY) = dummyCharacter.CursorPosition; bool isHorizontal = Math.Abs(cursorX - lastNode.X) < Math.Abs(cursorY - lastNode.Y); - + float roundedY = MathUtils.Round(cursorY, Submarine.GridSize.Y / 2.0f); float roundedX = MathUtils.Round(cursorX, Submarine.GridSize.X / 2.0f); - dummyCharacter.CursorPosition = isHorizontal - ? new Vector2(lastNode.X, roundedY) + dummyCharacter.CursorPosition = isHorizontal + ? new Vector2(lastNode.X, roundedY) : new Vector2(roundedX, lastNode.Y); } } @@ -4464,7 +4487,7 @@ namespace Barotrauma { TeleportDummyCharacter(oldItemPosition); } - + if (WiringMode && dummyCharacter?.SelectedConstruction == null) { TeleportDummyCharacter(FarseerPhysics.ConvertUnits.ToSimUnits(dummyCharacter.CursorPosition)); @@ -4486,31 +4509,31 @@ namespace Barotrauma if (inv?.visualSlots != null && !PlayerInput.IsCtrlDown()) { var dragginMouse = MouseDragStart != Vector2.Zero && Vector2.Distance(PlayerInput.MousePosition, MouseDragStart) >= GUI.Scale * 20; - + // So we don't accidentally drag inventory items while doing this if (DraggedItemPrefab != null) { Inventory.DraggingItems.Clear(); } - - switch (DraggedItemPrefab) + + switch (DraggedItemPrefab) { // regular item prefabs - case ItemPrefab itemPrefab when PlayerInput.PrimaryMouseButtonClicked() || dragginMouse: + case ItemPrefab itemPrefab when PlayerInput.PrimaryMouseButtonClicked() || dragginMouse: { bool spawnedItem = false; for (var i = 0; i < inv.Capacity; i++) { var slot = inv.visualSlots[i]; var itemContainer = inv.GetItemAt(i)?.GetComponent(); - + // check if the slot is empty or if we can place the item into a container, for example an oxygen tank into a diving suit if (Inventory.IsMouseOnSlot(slot)) { var newItem = new Item(itemPrefab, Vector2.Zero, Submarine.MainSub); - + if (inv.CanBePutInSlot(itemPrefab, i, condition: null)) { bool placedItem = inv.TryPutItem(newItem, i, false, true, dummyCharacter); spawnedItem |= placedItem; - + if (!placedItem) { newItem.Remove(); @@ -4520,7 +4543,7 @@ namespace Barotrauma { bool placedItem = itemContainer.Inventory.TryPutItem(newItem, dummyCharacter); spawnedItem |= placedItem; - + // try to place the item into the inventory of the item we are hovering over if (!placedItem) { @@ -4564,28 +4587,28 @@ namespace Barotrauma { // load the items var itemInstance = LoadItemAssemblyInventorySafe(assemblyPrefab); - + // counter for items that failed so we so we known that slot remained empty var failedCount = 0; - + for (var j = 0; j < itemInstance.Count(); j++) { var newItem = itemInstance[j]; var newSpot = i + j - failedCount; - + // try to find a valid slot to put the items - while (inv.visualSlots.Length > newSpot) + while (inv.visualSlots.Length > newSpot) { if (inv.GetItemAt(newSpot) == null) { break; } newSpot++; } - + // valid slot found if (inv.visualSlots.Length > newSpot) { var placedItem = inv.TryPutItem(newItem, newSpot, false, true, dummyCharacter); spawnedItems |= placedItem; - + if (!placedItem) { failedCount++; @@ -4598,7 +4621,7 @@ namespace Barotrauma { var placedItem = inv.TryPutItem(newItem, dummyCharacter); spawnedItems |= placedItem; - + // if our while loop didn't find a valid slot then let the inventory decide where to put it as a last resort if (!placedItem) { @@ -4664,11 +4687,11 @@ namespace Barotrauma MeasurePositionStart = cam.ScreenToWorld(PlayerInput.MousePosition); } } - + if (!WiringMode) { bool shouldCloseHud = dummyCharacter?.SelectedConstruction != null && HUD.CloseHUD(dummyCharacter.SelectedConstruction.Rect) && DraggedItemPrefab == null; - + if (MapEntityPrefab.Selected != null && GUI.MouseOn == null) { MapEntityPrefab.Selected.UpdatePlacing(cam); @@ -4685,7 +4708,7 @@ namespace Barotrauma { if (dummyCharacter?.SelectedConstruction == null) { - CreateContextMenu(); + CreateContextMenu(); } DraggedItemPrefab = null; } @@ -4695,11 +4718,11 @@ namespace Barotrauma { CloseItem(); } - } + } MapEntity.UpdateEditor(cam, (float)deltaTime); } - entityMenuOpenState = entityMenuOpen && !WiringMode ? + entityMenuOpenState = entityMenuOpen && !WiringMode ? (float)Math.Min(entityMenuOpenState + deltaTime * 5.0f, 1.0f) : (float)Math.Max(entityMenuOpenState - deltaTime * 5.0f, 0.0f); @@ -4723,7 +4746,7 @@ namespace Barotrauma { saveFrame = null; } - } + } if (dummyCharacter != null) { @@ -4789,28 +4812,28 @@ namespace Barotrauma GUI.DrawLine(spriteBatch, new Vector2(Submarine.MainSub.HiddenSubPosition.X, -cam.WorldView.Y), new Vector2(Submarine.MainSub.HiddenSubPosition.X, -(cam.WorldView.Y - cam.WorldView.Height)), Color.White * 0.5f, 1.0f, (int)(2.0f / cam.Zoom)); GUI.DrawLine(spriteBatch, new Vector2(cam.WorldView.X, -Submarine.MainSub.HiddenSubPosition.Y), new Vector2(cam.WorldView.Right, -Submarine.MainSub.HiddenSubPosition.Y), Color.White * 0.5f, 1.0f, (int)(2.0f / cam.Zoom)); } - Submarine.DrawBack(spriteBatch, true, e => - e is Structure s && - !IsSubcategoryHidden(e.prefab?.Subcategory) && + Submarine.DrawBack(spriteBatch, true, e => + e is Structure s && + !IsSubcategoryHidden(e.prefab?.Subcategory) && (e.SpriteDepth >= 0.9f || s.Prefab.BackgroundSprite != null)); Submarine.DrawPaintedColors(spriteBatch, true); spriteBatch.End(); spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.NonPremultiplied, transformMatrix: cam.Transform); - + // When we "open" a wearable item with inventory it won't get rendered because the dummy character is invisible // So we are drawing a clone of it on the same position if (OpenedItem?.GetComponent() != null) { - OpenedItem.Sprite.Draw(spriteBatch, new Vector2(OpenedItem.DrawPosition.X, -(OpenedItem.DrawPosition.Y)), + OpenedItem.Sprite.Draw(spriteBatch, new Vector2(OpenedItem.DrawPosition.X, -(OpenedItem.DrawPosition.Y)), scale: OpenedItem.Scale, color: OpenedItem.SpriteColor, depth: OpenedItem.SpriteDepth); GUI.DrawRectangle(spriteBatch, new Vector2(OpenedItem.WorldRect.X, -OpenedItem.WorldRect.Y), new Vector2(OpenedItem.Rect.Width, OpenedItem.Rect.Height), Color.White, false, 0, (int)Math.Max(2.0f / cam.Zoom, 1.0f)); } - - Submarine.DrawBack(spriteBatch, true, e => + + Submarine.DrawBack(spriteBatch, true, e => (!(e is Structure) || e.SpriteDepth < 0.9f) && !IsSubcategoryHidden(e.prefab?.Subcategory)); spriteBatch.End(); @@ -4876,7 +4899,7 @@ namespace Barotrauma } } } - + if (dummyCharacter != null) { if (WiringMode) @@ -4913,7 +4936,7 @@ namespace Barotrauma Vector2 offset = new Vector2(GUI.IntScale(24)); GUI.DrawString(spriteBatch, PlayerInput.MousePosition + offset, $"{realWorldDistance}m", GUI.Style.TextColor, font: GUI.SubHeadingFont, backgroundColor: Color.Black, backgroundPadding: 4); } - + spriteBatch.End(); } @@ -4950,13 +4973,13 @@ namespace Barotrauma Submarine.DrawFront(spriteBatch); Submarine.DrawDamageable(spriteBatch, null); spriteBatch.End(); - + GameMain.Instance.GraphicsDevice.SetRenderTarget(null); rt.SaveAsPng(stream, width, height); } - //for some reason setting the rendertarget changes the size of the viewport + //for some reason setting the rendertarget changes the size of the viewport //but it doesn't change back to default when setting it back to null GameMain.Instance.ResetViewPort(); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/TestScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/TestScreen.cs new file mode 100644 index 000000000..38b34c0a0 --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/TestScreen.cs @@ -0,0 +1,112 @@ +#nullable enable +using System; +using System.Linq; +using Barotrauma.Extensions; +using Barotrauma.Items.Components; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +/* + * This screen only exists because I'm going mental without access to EnC on Linux. + * This is fucking stupid and horrible. + * Remember to remove this crap eventually. + * - Markus + */ +namespace Barotrauma +{ + class TestScreen : EditorScreen + { + public override Camera Cam { get; } + + private Item? miniMapItem; + + private Submarine? submarine; + private Character? dummyCharacter; + public static Effect BlueprintEffect; + + private TabMenu tabMenu; + + public TestScreen() + { + Cam = new Camera(); + BlueprintEffect = GameMain.GameScreen.BlueprintEffect; + + new GUIButton(new RectTransform(new Point(256, 256), Frame.RectTransform), "Reload shader") + { + OnClicked = (button, o) => + { + BlueprintEffect.Dispose(); + GameMain.Instance.Content.Unload(); + BlueprintEffect = GameMain.Instance.Content.Load("Effects/blueprintshader_opengl"); + GameMain.GameScreen.BlueprintEffect = BlueprintEffect; + return true; + } + }; + } + + public override void Select() + { + base.Select(); + + if (dummyCharacter is { Removed: false }) + { + dummyCharacter?.Remove(); + } + + dummyCharacter = Character.Create(CharacterPrefab.HumanSpeciesName, Vector2.Zero, "", id: Entity.DummyID, hasAi: false); + dummyCharacter.Info.Job = new Job(JobPrefab.Prefabs.Where(jp => TalentTree.JobTalentTrees.ContainsKey(jp.Identifier)).GetRandom()); + dummyCharacter.Info.Name = "Galldren"; + dummyCharacter.Inventory.CreateSlots(); + + Character.Controlled = dummyCharacter; + GameMain.World.ProcessChanges(); + TabMenu.selectedTab = TabMenu.InfoFrameTab.Talents; + tabMenu = new TabMenu(); + } + + public override void AddToGUIUpdateList() + { + Frame.AddToGUIUpdateList(); + CharacterHUD.AddToGUIUpdateList(dummyCharacter); + dummyCharacter?.SelectedConstruction?.AddToGUIUpdateList(); + tabMenu.AddToGUIUpdateList(); + } + + public override void Update(double deltaTime) + { + base.Update(deltaTime); + tabMenu.Update(); + + if (dummyCharacter is { } dummy) + { + dummy.ControlLocalPlayer((float)deltaTime, Cam, false); + dummy.Control((float)deltaTime, Cam); + } + + GUI.Update((float)deltaTime); + } + + public override void Draw(double deltaTime, GraphicsDevice graphics, SpriteBatch spriteBatch) + { + base.Draw(deltaTime, graphics, spriteBatch); + graphics.Clear(BackgroundColor); + + spriteBatch.Begin(SpriteSortMode.BackToFront, transformMatrix: Cam.Transform); + miniMapItem?.Draw(spriteBatch, false); + if (dummyCharacter is { } dummy) + { + dummyCharacter.DrawFront(spriteBatch, Cam); + dummyCharacter.Draw(spriteBatch, Cam); + } + spriteBatch.End(); + + spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState); + + GUI.Draw(Cam, spriteBatch); + + dummyCharacter?.DrawHUD(spriteBatch, Cam, false); + + spriteBatch.End(); + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaClient/ClientSource/Serialization/SerializableEntityEditor.cs b/Barotrauma/BarotraumaClient/ClientSource/Serialization/SerializableEntityEditor.cs index 986afef6d..1fc36bc42 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Serialization/SerializableEntityEditor.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Serialization/SerializableEntityEditor.cs @@ -271,6 +271,21 @@ namespace Barotrauma } } } + else if (newValue is string[] a) + { + for (int i = 0; i < fields.Length; i++) + { + if (i >= a.Length) { break; } + if (fields[i] is GUITextBox textBox) + { + textBox.Text = a[i]; + if (flash) + { + textBox.Flash(GUI.Style.Green); + } + } + } + } } public SerializableEntityEditor(RectTransform parent, ISerializableEntity entity, bool inGame, bool showName, string style = "", int elementHeight = 24, ScalableFont titleFont = null) @@ -423,6 +438,10 @@ namespace Barotrauma { propertyField = CreateRectangleField(entity, property, r, displayName, toolTip); } + else if(value is string[] a) + { + propertyField = CreateStringArrayField(entity, property, a, displayName, toolTip); + } return propertyField; } @@ -1164,6 +1183,75 @@ namespace Barotrauma return frame; } + public GUIComponent CreateStringArrayField(ISerializableEntity entity, SerializableProperty property, string[] value, string displayName, string toolTip) + { + int elementCount = (value.Length + 1); + var frame = new GUIFrame(new RectTransform(new Point(Rect.Width, elementCount * elementHeight), layoutGroup.RectTransform, isFixedSize: true), color: Color.Transparent); + var label = new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f / elementCount), frame.RectTransform), displayName, font: GUI.SmallFont) + { + ToolTip = toolTip + }; + var editableAttribute = property.GetAttribute(); + var fields = new GUIComponent[value.Length]; + var inputArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, (float)(elementCount - 1) / elementCount), frame.RectTransform, anchor: Anchor.BottomLeft)) + { + RelativeSpacing = 0.01f + }; + elementCount -= 1; + + for (int i = 0; i < value.Length; i++) + { + var element = new GUIFrame(new RectTransform(new Vector2(1.0f, 1.0f / elementCount), inputArea.RectTransform) { MinSize = new Point(50, 0), MaxSize = new Point((int)(0.9f * inputArea.Rect.Width), 50) }, style: null); + var elementLayoutGroup = new GUILayoutGroup(new RectTransform(Vector2.One, element.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft); + // Set the label to be (i + 1) so it's easier to understand for non-programmers + string componentLabel = (i + 1).ToString(); + new GUITextBlock(new RectTransform(new Vector2(0.3f, 1), elementLayoutGroup.RectTransform) { MaxSize = new Point(25, elementLayoutGroup.Rect.Height) }, componentLabel, font: GUI.SmallFont, textAlignment: Alignment.Center); + GUITextBox textBox = new GUITextBox(new RectTransform(new Vector2(0.7f, 1), elementLayoutGroup.RectTransform), text: value[i]) { Font = GUI.SmallFont }; + int comp = i; + textBox.OnEnterPressed += (textBox, text) => OnApply(textBox); + textBox.OnDeselected += (textBox, keys) => OnApply(textBox); + fields[i] = textBox; + + bool OnApply(GUITextBox textBox) + { + // Reserve the semicolon for serializing the value + bool containsForbiddenCharacters = textBox.Text.Contains(';'); + string[] newValue = (string[])property.GetValue(entity); + if (!containsForbiddenCharacters) + { + newValue[comp] = textBox.Text; + if (SetPropertyValue(property, entity, newValue)) + { + TrySendNetworkUpdate(entity, property); + textBox.Flash(color: GUI.Style.Green, flashDuration: 1f); + } + } + else + { + textBox.Text = newValue[comp]; + textBox.Flash(color: GUI.Style.Red, flashDuration: 1f); + } + return true; + } + } + + refresh += () => + { + if (fields.None(f => ((GUITextBox)f).Selected)) + { + string[] value = (string[])property.GetValue(entity); + for (int i = 0; i < fields.Length; i++) + { + ((GUITextBox)fields[i]).Text = value[i]; + } + } + }; + + frame.RectTransform.MinSize = new Point(0, frame.RectTransform.Children.Sum(c => c.MinSize.Y)); + if (!Fields.ContainsKey(property.Name)) { Fields.Add(property.Name, fields); } + return frame; + } + public void CreateTextPicker(string textTag, ISerializableEntity entity, SerializableProperty property, GUITextBox textBox) { var msgBox = new GUIMessageBox("", "", new string[] { TextManager.Get("Cancel") }, new Vector2(0.2f, 0.5f), new Point(300, 400)); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundPlayer.cs b/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundPlayer.cs index de7051846..172454cc2 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundPlayer.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundPlayer.cs @@ -38,6 +38,7 @@ namespace Barotrauma public readonly string File; public readonly string Type; public readonly bool DuckVolume; + public readonly float Volume; public readonly Vector2 IntensityRange; @@ -52,6 +53,7 @@ namespace Barotrauma this.Type = element.GetAttributeString("type", "").ToLowerInvariant(); this.IntensityRange = element.GetAttributeVector2("intensityrange", new Vector2(0.0f, 100.0f)); this.DuckVolume = element.GetAttributeBool("duckvolume", false); + this.Volume = element.GetAttributeFloat("volume", 1.0f); this.ContinueFromPreviousTime = element.GetAttributeBool("continuefromprevioustime", false); this.Element = element; } @@ -816,6 +818,8 @@ namespace Barotrauma } } + int noiseLoopIndex = 1; + updateMusicTimer -= deltaTime; if (updateMusicTimer <= 0.0f) { @@ -825,7 +829,6 @@ namespace Barotrauma GameMain.GameSession.EventManager.CurrentIntensity * 100.0f : 0.0f; IEnumerable suitableMusic = GetSuitableMusicClips(currentMusicType, currentIntensity); - int mainTrackIndex = 0; if (suitableMusic.Count() == 0) { @@ -851,14 +854,12 @@ namespace Barotrauma } } - int noiseLoopIndex = 1; if (Level.Loaded?.Type == LevelData.LevelType.LocationConnection) { // Find background noise loop for the current biome IEnumerable suitableNoiseLoops = Screen.Selected == GameMain.GameScreen ? GetSuitableMusicClips(Level.Loaded.LevelData?.Biome?.Identifier, currentIntensity) : Enumerable.Empty(); - if (suitableNoiseLoops.Count() == 0) { targetMusic[noiseLoopIndex] = null; @@ -874,12 +875,23 @@ namespace Barotrauma targetMusic[noiseLoopIndex] = null; } + IEnumerable suitableTypeAmbiences = GetSuitableMusicClips($"{currentMusicType}ambience", currentIntensity); + int typeAmbienceTrackIndex = 2; + if (suitableTypeAmbiences.None()) + { + targetMusic[typeAmbienceTrackIndex] = null; + } + // Switch the type ambience if nothing playing atm or the currently playing clip is not suitable anymore + else if (targetMusic[typeAmbienceTrackIndex] == null || currentMusic[typeAmbienceTrackIndex] == null || !currentMusic[typeAmbienceTrackIndex].IsPlaying() || suitableTypeAmbiences.None(m => m.File == currentMusic[typeAmbienceTrackIndex].Filename)) + { + targetMusic[typeAmbienceTrackIndex] = suitableTypeAmbiences.GetRandom(); + } + //get the appropriate intensity layers for current situation IEnumerable suitableIntensityMusic = Screen.Selected == GameMain.GameScreen ? GetSuitableMusicClips("intensity", currentIntensity) : Enumerable.Empty(); - - int intensityTrackStartIndex = 2; + int intensityTrackStartIndex = 3; for (int i = intensityTrackStartIndex; i < MaxMusicChannels; i++) { //disable targetmusics that aren't suitable anymore @@ -888,7 +900,6 @@ namespace Barotrauma targetMusic[i] = null; } } - foreach (BackgroundMusic intensityMusic in suitableIntensityMusic) { //already playing, do nothing @@ -917,7 +928,7 @@ namespace Barotrauma { //mute the channel musicChannel[i].Gain = MathHelper.Lerp(musicChannel[i].Gain, 0.0f, MusicLerpSpeed * deltaTime); - if (musicChannel[i].Gain < 0.01f) DisposeMusicChannel(i); + if (musicChannel[i].Gain < 0.01f) { DisposeMusicChannel(i); } } } //something should be playing, but the targetMusic is invalid @@ -932,7 +943,7 @@ namespace Barotrauma if (musicChannel[i] != null && musicChannel[i].IsPlaying) { musicChannel[i].Gain = MathHelper.Lerp(musicChannel[i].Gain, 0.0f, MusicLerpSpeed * deltaTime); - if (musicChannel[i].Gain < 0.01f) DisposeMusicChannel(i); + if (musicChannel[i].Gain < 0.01f) { DisposeMusicChannel(i); } } //channel free now, start playing the correct clip if (currentMusic[i] == null || (musicChannel[i] == null || !musicChannel[i].IsPlaying)) @@ -949,7 +960,7 @@ namespace Barotrauma targetMusic[i] = null; break; } - musicChannel[i] = currentMusic[i].Play(0.0f, "music"); + musicChannel[i] = currentMusic[i].Play(0.0f, i == noiseLoopIndex ? "" : "music"); if (targetMusic[i].ContinueFromPreviousTime) { musicChannel[i].StreamSeekPos = targetMusic[i].PreviousTime; @@ -963,13 +974,13 @@ namespace Barotrauma if (musicChannel[i] == null || !musicChannel[i].IsPlaying) { musicChannel[i]?.Dispose(); - musicChannel[i] = currentMusic[i].Play(0.0f, "music"); + musicChannel[i] = currentMusic[i].Play(0.0f, i == noiseLoopIndex ? "" : "music"); musicChannel[i].Looping = true; } - float targetGain = 1.0f; + float targetGain = targetMusic[i].Volume; if (targetMusic[i].DuckVolume) { - targetGain = (float)Math.Sqrt(1.0f / activeTrackCount); + targetGain *= (float)Math.Sqrt(1.0f / activeTrackCount); } musicChannel[i].Gain = MathHelper.Lerp(musicChannel[i].Gain, targetGain, MusicLerpSpeed * deltaTime); } @@ -1009,7 +1020,8 @@ namespace Barotrauma Screen.Selected == GameMain.SpriteEditorScreen || Screen.Selected == GameMain.SubEditorScreen || Screen.Selected == GameMain.EventEditorScreen || - (Screen.Selected == GameMain.GameScreen && GameMain.GameSession?.GameMode is TestGameMode)) + (Screen.Selected == GameMain.GameScreen && GameMain.GameSession?.GameMode is TestGameMode) || + Screen.Selected == GameMain.NetLobbyScreen) { return "editor"; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Sprite/Sprite.cs b/Barotrauma/BarotraumaClient/ClientSource/Sprite/Sprite.cs index 143a70d25..ed81e93a2 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Sprite/Sprite.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Sprite/Sprite.cs @@ -41,21 +41,13 @@ namespace Barotrauma public Sprite(Texture2D texture, Rectangle? sourceRectangle, Vector2? newOffset, float newRotation = 0.0f, string path = null) { this.texture = texture; - sourceRect = sourceRectangle ?? new Rectangle(0, 0, texture.Width, texture.Height); - offset = newOffset ?? Vector2.Zero; - size = new Vector2(sourceRect.Width, sourceRect.Height); - origin = Vector2.Zero; - effects = SpriteEffects.None; - rotation = newRotation; - FilePath = path; - AddToList(this); } @@ -85,7 +77,7 @@ namespace Barotrauma EnsureLazyLoaded(isAsync: true); } - public void EnsureLazyLoaded(bool isAsync=false) + public void EnsureLazyLoaded(bool isAsync = false) { if (!LazyLoad || texture != null || cannotBeLoaded || loadingAsync) { return; } loadingAsync = isAsync; @@ -177,7 +169,7 @@ namespace Barotrauma } else { - DebugConsole.ThrowError("Sprite \"" + file + "\" not found!"); + DebugConsole.ThrowError($"Sprite \"{file}\" not found! {Environment.StackTrace.CleanupStackTrace()}"); } return null; @@ -382,7 +374,7 @@ namespace Barotrauma { foreach (Sprite s in LoadedSprites) { - if (s.FullPath == FullPath) return; + if (s.FullPath == FullPath) { return; } } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Utils/SpreadsheetExport.cs b/Barotrauma/BarotraumaClient/ClientSource/Utils/SpreadsheetExport.cs index 403a70475..6327b9e26 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Utils/SpreadsheetExport.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Utils/SpreadsheetExport.cs @@ -110,14 +110,14 @@ namespace Barotrauma foreach (StatusEffect statusEffect in successEffects) { float duration = statusEffect.Duration; - onSuccessAfflictions.AddRange(statusEffect.ReduceAffliction.Select(pair => Tuple.Create(GetAfflictionName(pair.First), -pair.Second, duration))); + onSuccessAfflictions.AddRange(statusEffect.ReduceAffliction.Select(pair => Tuple.Create(GetAfflictionName(pair.affliction), -pair.amount, duration))); onSuccessAfflictions.AddRange(statusEffect.Afflictions.Select(affliction => Tuple.Create(affliction.Prefab.Name, affliction.NonClampedStrength, duration))); } foreach (StatusEffect statusEffect in failureEffects) { float duration = statusEffect.Duration; - onFailureAfflictions.AddRange(statusEffect.ReduceAffliction.Select(pair => Tuple.Create(GetAfflictionName(pair.First), -pair.Second, duration))); + onFailureAfflictions.AddRange(statusEffect.ReduceAffliction.Select(pair => Tuple.Create(GetAfflictionName(pair.affliction), -pair.amount, duration))); onFailureAfflictions.AddRange(statusEffect.Afflictions.Select(affliction => Tuple.Create(affliction.Prefab.Name, affliction.NonClampedStrength, duration))); } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Utils/ToolBox.cs b/Barotrauma/BarotraumaClient/ClientSource/Utils/ToolBox.cs index d5847be30..327dac75a 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Utils/ToolBox.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Utils/ToolBox.cs @@ -1,9 +1,10 @@ using Microsoft.Xna.Framework; using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using System.Text; -using System.Text.RegularExpressions; +using Color = Microsoft.Xna.Framework.Color; namespace Barotrauma { @@ -54,6 +55,225 @@ namespace Barotrauma return isInside; } + + public static Vector2 GetPolygonBoundingBoxSize(List verticess) + { + float minX = verticess[0].X; + float maxX = verticess[0].X; + float minY = verticess[0].Y; + float maxY = verticess[0].Y; + + foreach (var (vertX, vertY) in verticess) + { + minX = Math.Min(vertX, minX); + maxX = Math.Max(vertX, maxX); + minY = Math.Min(vertY, minY); + maxY = Math.Max(vertY, maxY); + } + + return new Vector2(maxX - minX, maxY - minY); + } + + public static List ScalePolygon(List vertices, Vector2 scale) + { + List newVertices = new List(); + + Vector2 center = GetPolygonCentroid(vertices); + + foreach (Vector2 vert in vertices) + { + Vector2 centerVector = vert - center; + Vector2 centerVectorScale = centerVector * scale; + Vector2 scaledVector = centerVectorScale + center; + newVertices.Add(scaledVector); + } + + return newVertices; + } + + public static Vector2 GetPolygonCentroid(List poly) + { + float accumulatedArea = 0.0f; + float centerX = 0.0f; + float centerY = 0.0f; + + for (int i = 0, j = poly.Count - 1; i < poly.Count; j = i++) + { + float temp = poly[i].X * poly[j].Y - poly[j].X * poly[i].Y; + accumulatedArea += temp; + centerX += (poly[i].X + poly[j].X) * temp; + centerY += (poly[i].Y + poly[j].Y) * temp; + } + + if (Math.Abs(accumulatedArea) < 1E-7f) { return Vector2.Zero; } // Avoid division by zero + + accumulatedArea *= 3f; + return new Vector2(centerX / accumulatedArea, centerY / accumulatedArea); + } + + public static List SnapVertices(List points, int treshold = 1) + { + Stack toCheck = new Stack(); + List newPoints = new List(); + + foreach (Vector2 point in points) + { + toCheck.Push(point); + } + + while (toCheck.TryPop(out Vector2 point)) + { + Vector2 newPoint = new Vector2(point.X, point.Y); + foreach (Vector2 otherPoint in toCheck.Concat(newPoints)) + { + float diffX = Math.Abs(newPoint.X - otherPoint.X), + diffY = Math.Abs(newPoint.Y - otherPoint.Y); + + if (diffX <= treshold) + { + newPoint.X = Math.Max(newPoint.X, otherPoint.X); + } + + if (diffY <= treshold) + { + newPoint.Y = Math.Max(newPoint.Y, otherPoint.Y); + } + } + newPoints.Add(newPoint); + } + + return newPoints; + } + + public static ImmutableArray SnapRectangles(IEnumerable rects, int treshold = 1) + { + List list = new List(); + + List points = new List(); + + foreach (RectangleF rect in rects) + { + points.Add(new Vector2(rect.Left, rect.Top)); + points.Add(new Vector2(rect.Right, rect.Top)); + points.Add(new Vector2(rect.Right, rect.Bottom)); + points.Add(new Vector2(rect.Left, rect.Bottom)); + } + + points = SnapVertices(points, treshold); + + for (int i = 0; i < points.Count; i += 4) + { + Vector2 topLeft = points[i]; + Vector2 bottomRight = points[i + 2]; + + list.Add(new RectangleF(topLeft, bottomRight - topLeft)); + } + + return list.ToImmutableArray(); + } + + public static List> CombineRectanglesIntoShape(IEnumerable rectangles) + { + List points = + (from point in rectangles.SelectMany(RectangleToPoints) + group point by point + into g + where g.Count() % 2 == 1 + select g.Key) + .ToList(); + + List sortedY = points.OrderBy(p => p.Y).ThenByDescending(p => p.X).ToList(); + List sortedX = points.OrderBy(p => p.X).ThenByDescending(p => p.Y).ToList(); + + Dictionary edgesH = new Dictionary(); + Dictionary edgesV = new Dictionary(); + + int i = 0; + while (i < points.Count) + { + float currY = sortedY[i].Y; + + while (i < points.Count && Math.Abs(sortedY[i].Y - currY) < 0.01f) + { + edgesH[sortedY[i]] = sortedY[i + 1]; + edgesH[sortedY[i + 1]] = sortedY[i]; + i += 2; + } + + } + + i = 0; + + while (i < points.Count) + { + float currX = sortedX[i].X; + while (i < points.Count && Math.Abs(sortedX[i].X - currX) < 0.01f) + { + edgesV[sortedX[i]] = sortedX[i + 1]; + edgesV[sortedX[i + 1]] = sortedX[i]; + i += 2; + } + } + + List> polygons = new List>(); + + while (edgesH.Any()) + { + var (key, _) = edgesH.First(); + List<(Vector2 Point, int Direction)> polygon = new List<(Vector2 Point, int Direction)> { (key, 0) }; + edgesH.Remove(key); + + while (true) + { + var (curr, direction) = polygon[^1]; + + if (direction == 0) + { + Vector2 nextVertex = edgesV[curr]; + edgesV.Remove(curr); + polygon.Add((nextVertex, 1)); + } + else + { + Vector2 nextVertex = edgesH[curr]; + edgesH.Remove(curr); + polygon.Add((nextVertex, 0)); + } + + if (polygon[^1] == polygon[0]) + { + polygon.Remove(polygon[^1]); + break; + } + } + + List poly = polygon.Select(t => t.Point).ToList(); + + foreach (Vector2 vertex in poly) + { + if (edgesH.ContainsKey(vertex)) + { + edgesH.Remove(vertex); + } + + if (edgesV.ContainsKey(vertex)) + { + edgesV.Remove(vertex); + } + } + + polygons.Add(poly); + } + + return polygons; + + static IEnumerable RectangleToPoints(RectangleF rect) + { + (float x1, float y1, float x2, float y2) = (rect.Left, rect.Top, rect.Right, rect.Bottom); + Vector2[] pts = { new Vector2(x1, y1), new Vector2(x2, y1), new Vector2(x2, y2), new Vector2(x1, y2) }; + return pts; + } + } // Convert an RGB value into an HLS value. public static Vector3 RgbToHLS(this Color color) diff --git a/Barotrauma/BarotraumaClient/Content/Effects/blueprintshader.xnb b/Barotrauma/BarotraumaClient/Content/Effects/blueprintshader.xnb new file mode 100644 index 0000000000000000000000000000000000000000..8ef4446712b01dd4e2facd95b6493ab5c4f50f75 GIT binary patch literal 2462 zcmb_eOK%%h6h1SyXC?u~R>cCrq84Jo!m^rBpsf^e?ZhE!6Q^-pnTJH3bkbOL$I)c0 z&TZNGKb4-5H?ZqQu= zWC|?gZ~Ngt=jagpZ*@UEXj#Yby_U>&J$T_mMb@(d~oZlU;cRXbN3Cu^y8!NEdXds zW50xTkP`XJ6}bc5^=@$W5@as1$K$7nb{VWCui=~AW$>?R9vg`96ajxrBx6Z-pf!)N zm&jj4G;Dp5?9lL*qzwO!c-aBs0OR$s4;;l?z>C(~_}0 z@+IC?d8T+*)fd-GJ3gpunhR&AVwYjSEE%US-e^EM1v!JBgeBC^orUV$j6@3+~IRF zkr4y-6CJ}w-HdTEnELQs;}|9nc8xJ{)SXdr5D$5bEIKabhF-!PC?m%3?iijmTH*r7 ze4`;_S-6`X?iucyx<9zH$UW+h$0GH2SGGSvF(o)$#DLyIgVnF|kPs2;eb#a(r{v~~ zl9t1mZ!|=9bCNpC=M4D z|4wo_U*xG&1b=5q`#+If|2nkv1G#6=Gw+v99KZTPEYoIus@k`zYg`qXqaSHv`!tqK zzXpTDkU!_+nALy5FhAp{eFn26GXILsHLo#x29F`DURVcl7ZNz;JrBoO>;lLBoz(fW z_}z=`W)}ZfFG%SnYlJMO2i6O6lXlL7J!|sJ9A29Csrn}l09aa zvo$+=*7Ujdzoz}K>G#IlXm7o~w#k15>=i%dvc+H^&WAcu7VAUB#b0VJ)Wfhlqvt&E zgCq6ZA8pm@>pSbUO{vsw*0yD5wOOgvwpZk|hMWc9n1eag&K$SzMtHkfu@*Y#z;E9>h^DHPyRhSb%>P66)`P>2AI1r+xpJrHABOE95-YRS MIyzbuyop8r0nLmnp#T5? literal 0 HcmV?d00001 diff --git a/Barotrauma/BarotraumaClient/Content/Effects/blueprintshader_opengl.xnb b/Barotrauma/BarotraumaClient/Content/Effects/blueprintshader_opengl.xnb new file mode 100644 index 0000000000000000000000000000000000000000..608cdb04cd7e68fcd4635dcb9ce1e1423230e47a GIT binary patch literal 2010 zcmaJ?+iuf95Oo@*&`3f3gq8ZzLTc<>3JB$;>E)pmRkT8tswy(}I$hQAhV>1P(O8DYKNbwv1c5Ul(CM?(b3GVQ#10Y3EspOtPN! zu6GY$oBKgn0_+|c$;G~*8(nhP!>4?}`T*bslQ7hUsV;izqOUFn>SCxaj$p9|MKhlg z&}kd^T=D46xL<{sPa!5iijo6sHutFLoggfbYAum6f11@Ap9$#wn0s8Trj8Aw#8y)B zu|cNZ1jc774}u#jls7aPb1ocArW6yKBrZePm6$V!%q-^bA9UK^I_(`$C=4(@qzO(` z?_^(U?xOVA)RPpQM-(W~7XXw8;O(VEz+N5~hQ7?gc27^LO*~`j26>Hm8yKrl3ZcWC zui8yk4j9z|Goyz@$#R*JSEa-xElxvbB+WRN3k6dk`eist6BSUA06&0oVWnK0&N`C> z<}6GSgH0$?UoJ`%R#MBQ8ZL%_$|~6?^=K2(P!)wlwdsbcC__iE2H4#@sOE7JpT zQJQg7wPItLeKjw7U7NT6G;dZYc2(vA1x3d)T^NU=vW~(Clr2|eLnlhvOO&0HK4k)# z{|h6htba98?R+Qst{lsZRQ_*@q#Nf3$GDt`Z04l-Y8AESEAy#3q8NtrTlBGTVJ$3E z!MN2KwgyEe)v&d&WI|1aV@a*`sEPS8e9LI?RfNTD@Y%Q1*KET&ah9KJ9exPjn5LR2wXD@EDo9{Ziw2=hDJI7u` S=K?-zTBCJk1%avk#lSxT8&p^T literal 0 HcmV?d00001 diff --git a/Barotrauma/BarotraumaClient/Content/Effects/thresholdtint.xnb b/Barotrauma/BarotraumaClient/Content/Effects/thresholdtint.xnb new file mode 100644 index 0000000000000000000000000000000000000000..e9784b881b0c63d9f9270e3ff60ee5e27df1b5dc GIT binary patch literal 1501 zcmZWp%}*0i5TCaTQpC`v#KZ)Hd+=nMMk6?$=%>3TleOoIm?Do^Iej>4-H#Qu* zY1iF!&C=63M}HxAZD%{3wJld#ZhEO+mm6+b>YLI@X?feSmjUSlQ`(wznucx7-bvq0 zkEh2cr&3zBWx6d#&RVkNI=Y$CO09L%*mx-StM<0EX04WK5{oE+M1DC}i;3g;_iqIy zNab)JDKQdM>LXgLEo5(f9CE%7{VW|!xnF<(_*NIda1KAZ4K)$?jKb>(%-hz~1bkLf zI-NgQW}w1HvNeMlH^V66LXe^;@EH>d>TaMKo<|fvjch?`WFJy2eiX(JMUSn(#-_kV z^#rv~iM|{yz!4PpeGzN>z7qNr`j~@RZBK!Z!Kfeh6dFa0j)Q(`zpnJpQv++{VLdprvlsQ5LF&5mM1WL4h0{ekI^%C zpuq~x<_Yt9p78z1<}-_Ilv3>)xa|a()zz-gxL%*J?62dIpCuCh*|VL2%{hr#XIjvw z0iM_}EfEc#;T>Vgg}B_Sz>jz#HRu4t^Kwd%yX0x!JQsyBsZp#K!`QVqT9l@Y_J5CD zvfGpK5f|p#%Y}2y#b1ercn-hQ;No*&yfTMB^6qz)Xp(4vu`Cg9Ub!~Id(yf&?#(%W zcDP5>$1+iUcs8HjX+Q=N@%3vX9L|n=FkfDoLo&=whwEjmT*JB7TEKy0EYsn0yNB0f zrLtV$OU-;^&=Qn|YdOXehd%YK;E&^+k0Y^uAZR_ zxe0bmk3}6hPv`McvI=%U;>v~gG?QF}n>-CE9QkHesu)7F#;L6?tL*yQR8^f*x oiM^^}xp}?0eIYTt7qt2(EJn_`aapCopJfvLCB4}kC){A7zd04)^e;(ELocMIeX^J%zAIWoOn&5n}itIP`Fw}%8$((>6qu*3Hd_ldvl;< z%w=qz`962!EADt)cj-_`bpezIVwZm9Iuc6ud*(aSGOhO?x^$2PaiV!&@+8(y(508j zT_D^~JR7NdF8eYG0)mh6XP1!S#re%s^2=|0H}+@IIFZ6vI*i71?jAGJK-xapp~N7+ zVR0yYkNfoE^Z4w#-I{6c3QSK!?ujIvQ9n>l{Bav1Bz`~S(i48W)o!g4QOrXhC;ryE z1#$^#R$o~4gdaa29ZFpCTvhyYsAxmQrbfgwKl}Cse;xJHDGiSjN#Z# z@?lsv~nVDv^j|Ikg_!>jkHq#VRSQ$AzV3P76`8E_^Uf7;v+RkV&(hSV#%+ZSf~d^WAAx- uf|TOmtW@{*(-B-2Lnpd_1iVNKskP6>LFEm%T~G0@Bu4Yni6V>qMbUqMuB8V6 literal 0 HcmV?d00001 diff --git a/Barotrauma/BarotraumaClient/LinuxClient.csproj b/Barotrauma/BarotraumaClient/LinuxClient.csproj index d3c3a1c37..2ab72e41c 100644 --- a/Barotrauma/BarotraumaClient/LinuxClient.csproj +++ b/Barotrauma/BarotraumaClient/LinuxClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.14.9.1 + 0.15.12.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma @@ -36,6 +36,7 @@ TRACE;CLIENT;LINUX;USE_STEAM;UNSTABLE x64 ..\bin\$(Configuration)Linux\ + true @@ -48,6 +49,7 @@ TRACE;CLIENT;LINUX;X64;USE_STEAM;UNSTABLE x64 ..\bin\$(Configuration)Linux\ + true @@ -120,7 +122,7 @@ - + diff --git a/Barotrauma/BarotraumaClient/MacClient.csproj b/Barotrauma/BarotraumaClient/MacClient.csproj index be3ce5327..8ed473dd0 100644 --- a/Barotrauma/BarotraumaClient/MacClient.csproj +++ b/Barotrauma/BarotraumaClient/MacClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.14.9.1 + 0.15.12.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma @@ -38,7 +38,8 @@ TRACE;CLIENT;OSX;USE_STEAM;RELEASE;NETCOREAPP;NETCOREAPP3_0;UNSTABLE x64 - ..\bin\$(Configuration)Mac + ..\bin\$(Configuration)Mac + true @@ -51,6 +52,7 @@ TRACE;CLIENT;OSX;X64;USE_STEAM;UNSTABLE x64 ..\bin\$(Configuration)Mac\ + true @@ -121,7 +123,7 @@ - + diff --git a/Barotrauma/BarotraumaClient/Shaders/Content.mgcb b/Barotrauma/BarotraumaClient/Shaders/Content.mgcb index 901a2171c..7d48e26be 100644 --- a/Barotrauma/BarotraumaClient/Shaders/Content.mgcb +++ b/Barotrauma/BarotraumaClient/Shaders/Content.mgcb @@ -67,3 +67,15 @@ /processorParam:DebugMode=Auto /build:grainshader.fx +#begin thresholdtint.fx +/importer:EffectImporter +/processor:EffectProcessor +/processorParam:DebugMode=Auto +/build:thresholdtint.fx + +#begin blueprintshader.fx +/importer:EffectImporter +/processor:EffectProcessor +/processorParam:DebugMode=Auto +/build:blueprintshader.fx + diff --git a/Barotrauma/BarotraumaClient/Shaders/Content_opengl.mgcb b/Barotrauma/BarotraumaClient/Shaders/Content_opengl.mgcb index cb119ed14..82d54dedf 100644 --- a/Barotrauma/BarotraumaClient/Shaders/Content_opengl.mgcb +++ b/Barotrauma/BarotraumaClient/Shaders/Content_opengl.mgcb @@ -67,3 +67,14 @@ /processorParam:DebugMode=Auto /build:grainshader_opengl.fx +#begin blueprintshader_opengl.fx +/importer:EffectImporter +/processor:EffectProcessor +/processorParam:DebugMode=Auto +/build:blueprintshader_opengl.fx + +#begin thresholdtint_opengl.fx +/importer:EffectImporter +/processor:EffectProcessor +/processorParam:DebugMode=Auto +/build:thresholdtint_opengl.fx diff --git a/Barotrauma/BarotraumaClient/Shaders/blueprintshader.fx b/Barotrauma/BarotraumaClient/Shaders/blueprintshader.fx new file mode 100644 index 000000000..87048562e --- /dev/null +++ b/Barotrauma/BarotraumaClient/Shaders/blueprintshader.fx @@ -0,0 +1,48 @@ +// vim:ft=hlsl +sampler TextureSampler : register(s0); + +float width; +float height; + +float3 sobel(float2 uv) +{ + float x = 0; + float y = 0; + + float w = 1.0 / width; + float h = 1.0 / height; + + x += tex2D(TextureSampler, uv + float2(-w, -h)) * -1.0; + x += tex2D(TextureSampler, uv + float2(-w, 0)) * -2.0; + x += tex2D(TextureSampler, uv + float2(-w, h)) * -1.0; + + x += tex2D(TextureSampler, uv + float2( w, -h)) * 1.0; + x += tex2D(TextureSampler, uv + float2( w, 0)) * 2.0; + x += tex2D(TextureSampler, uv + float2( w, h)) * 1.0; + + y += tex2D(TextureSampler, uv + float2(-w, -h)) * -1.0; + y += tex2D(TextureSampler, uv + float2( 0, -h)) * -2.0; + y += tex2D(TextureSampler, uv + float2( w, -h)) * -1.0; + + y += tex2D(TextureSampler, uv + float2(-w, h)) * 1.0; + y += tex2D(TextureSampler, uv + float2( 0, h)) * 2.0; + y += tex2D(TextureSampler, uv + float2( w, h)) * 1.0; + + return sqrt(x * x + y * y); +} + +float4 blueprint(float4 position : SV_POSITION, float4 clr : COLOR0, float2 texCoord : TEXCOORD0) : COLOR0 +{ + float3 s = sobel(texCoord); + float a = tex2D(TextureSampler, texCoord).a; + a *= clr.a; + return float4(clr.r + s.r, clr.g + s.g, clr.b + s.b, a); +} + +technique Blueprint +{ + pass Pass1 + { + PixelShader = compile ps_4_0_level_9_1 blueprint(); + } +} diff --git a/Barotrauma/BarotraumaClient/Shaders/blueprintshader_opengl.fx b/Barotrauma/BarotraumaClient/Shaders/blueprintshader_opengl.fx new file mode 100644 index 000000000..4af0d4b36 --- /dev/null +++ b/Barotrauma/BarotraumaClient/Shaders/blueprintshader_opengl.fx @@ -0,0 +1,48 @@ +// vim:ft=hlsl +sampler TextureSampler : register(s0); + +float width; +float height; + +float3 sobel(float2 uv) +{ + float x = 0; + float y = 0; + + float w = 1.0 / width; + float h = 1.0 / height; + + x += tex2D(TextureSampler, uv + float2(-w, -h)) * -1.0; + x += tex2D(TextureSampler, uv + float2(-w, 0)) * -2.0; + x += tex2D(TextureSampler, uv + float2(-w, h)) * -1.0; + + x += tex2D(TextureSampler, uv + float2( w, -h)) * 1.0; + x += tex2D(TextureSampler, uv + float2( w, 0)) * 2.0; + x += tex2D(TextureSampler, uv + float2( w, h)) * 1.0; + + y += tex2D(TextureSampler, uv + float2(-w, -h)) * -1.0; + y += tex2D(TextureSampler, uv + float2( 0, -h)) * -2.0; + y += tex2D(TextureSampler, uv + float2( w, -h)) * -1.0; + + y += tex2D(TextureSampler, uv + float2(-w, h)) * 1.0; + y += tex2D(TextureSampler, uv + float2( 0, h)) * 2.0; + y += tex2D(TextureSampler, uv + float2( w, h)) * 1.0; + + return sqrt(x * x + y * y); +} + +float4 blueprint(float4 position : SV_POSITION, float4 clr : COLOR0, float2 texCoord : TEXCOORD0) : COLOR0 +{ + float3 s = sobel(texCoord); + float a = tex2D(TextureSampler, texCoord).a; + a *= clr.a; + return float4(clr.r + s.r, clr.g + s.g, clr.b + s.b, a); +} + +technique Blueprint +{ + pass Pass1 + { + PixelShader = compile ps_3_0 blueprint(); + } +} diff --git a/Barotrauma/BarotraumaClient/Shaders/thresholdtint.fx b/Barotrauma/BarotraumaClient/Shaders/thresholdtint.fx new file mode 100644 index 000000000..b70d04055 --- /dev/null +++ b/Barotrauma/BarotraumaClient/Shaders/thresholdtint.fx @@ -0,0 +1,32 @@ +Texture2D xBaseTexture; +sampler BaseTextureSampler = sampler_state { Texture = ; }; +Texture2D xTintMaskTexture; +sampler TintMaskTextureSampler = sampler_state { Texture = ; }; +Texture2D xCutoffTexture; +sampler CutoffTextureSampler = sampler_state { Texture = ; }; + +float highlightThreshold; +float highlightMultiplier; + +float baseToCutoffSizeRatio; + +float4 mainPS(float4 position : SV_POSITION, float4 clr : COLOR0, float2 texCoord : TEXCOORD0) : COLOR0 +{ + float4 baseSample = xBaseTexture.Sample(BaseTextureSampler, texCoord); + float3 tintMaskSample = xTintMaskTexture.Sample(TintMaskTextureSampler, texCoord).rgb; + float cutoffSample = xCutoffTexture.Sample(CutoffTextureSampler, texCoord * baseToCutoffSizeRatio).r; + + float3 highlight = saturate((baseSample.rgb - (highlightThreshold * float3(1,1,1))) * highlightMultiplier); + float3 tinted = saturate(baseSample.rgb * clr.rgb + highlight); + return float4( + (tinted * tintMaskSample) + (baseSample.rgb * (float3(1,1,1) - tintMaskSample)), + baseSample.a * cutoffSample * clr.a); +} + +technique ThresholdTintShader +{ + pass Pass1 + { + PixelShader = compile ps_4_0_level_9_1 mainPS(); + } +} diff --git a/Barotrauma/BarotraumaClient/Shaders/thresholdtint_opengl.fx b/Barotrauma/BarotraumaClient/Shaders/thresholdtint_opengl.fx new file mode 100644 index 000000000..ff693a035 --- /dev/null +++ b/Barotrauma/BarotraumaClient/Shaders/thresholdtint_opengl.fx @@ -0,0 +1,32 @@ +Texture2D xBaseTexture; +sampler BaseTextureSampler = sampler_state { Texture = ; }; +Texture2D xTintMaskTexture; +sampler TintMaskTextureSampler = sampler_state { Texture = ; }; +Texture2D xCutoffTexture; +sampler CutoffTextureSampler = sampler_state { Texture = ; }; + +float highlightThreshold; +float highlightMultiplier; + +float baseToCutoffSizeRatio; + +float4 mainPS(float4 position : SV_POSITION, float4 clr : COLOR0, float2 texCoord : TEXCOORD0) : COLOR0 +{ + float4 baseSample = tex2D(BaseTextureSampler, texCoord); + float3 tintMaskSample = tex2D(TintMaskTextureSampler, texCoord).rgb; + float cutoffSample = tex2D(CutoffTextureSampler, texCoord * baseToCutoffSizeRatio).r; + + float3 highlight = saturate((baseSample.rgb - (highlightThreshold * float3(1,1,1))) * highlightMultiplier); + float3 tinted = saturate(baseSample.rgb * clr.rgb + highlight); + return float4( + (tinted * tintMaskSample) + (baseSample.rgb * (float3(1,1,1) - tintMaskSample)), + baseSample.a * cutoffSample * clr.a); +} + +technique ThresholdTintShader +{ + pass Pass1 + { + PixelShader = compile ps_2_0 mainPS(); + } +} diff --git a/Barotrauma/BarotraumaClient/WindowsClient.csproj b/Barotrauma/BarotraumaClient/WindowsClient.csproj index f5e7a53ed..c8d71c037 100644 --- a/Barotrauma/BarotraumaClient/WindowsClient.csproj +++ b/Barotrauma/BarotraumaClient/WindowsClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.14.9.1 + 0.15.12.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma @@ -40,6 +40,7 @@ TRACE;CLIENT;WINDOWS;USE_STEAM x64 ..\bin\$(Configuration)Windows\ + true @@ -56,6 +57,7 @@ ..\bin\$(Configuration)Windows\ full true + true @@ -124,7 +126,7 @@ - + diff --git a/Barotrauma/BarotraumaServer/LinuxServer.csproj b/Barotrauma/BarotraumaServer/LinuxServer.csproj index 7e2634da0..a08658e40 100644 --- a/Barotrauma/BarotraumaServer/LinuxServer.csproj +++ b/Barotrauma/BarotraumaServer/LinuxServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.14.9.1 + 0.15.12.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 DedicatedServer @@ -36,6 +36,7 @@ TRACE;SERVER;LINUX;USE_STEAM x64 ..\bin\$(Configuration)Linux\ + true @@ -48,6 +49,7 @@ TRACE;SERVER;LINUX;X64;USE_STEAM x64 ..\bin\$(Configuration)Linux\ + true diff --git a/Barotrauma/BarotraumaServer/MacServer.csproj b/Barotrauma/BarotraumaServer/MacServer.csproj index 0dc6815ee..168d57971 100644 --- a/Barotrauma/BarotraumaServer/MacServer.csproj +++ b/Barotrauma/BarotraumaServer/MacServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.14.9.1 + 0.15.12.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 DedicatedServer @@ -41,6 +41,7 @@ x64 ..\bin\ReleaseMac + true @@ -53,6 +54,7 @@ TRACE;SERVER;OSX;X64;USE_STEAM;UNSTABLE x64 ..\bin\$(Configuration)Mac\ + true diff --git a/Barotrauma/BarotraumaServer/ServerSource/Characters/Character.cs b/Barotrauma/BarotraumaServer/ServerSource/Characters/Character.cs index acf0f00de..c0998cfbc 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Characters/Character.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Characters/Character.cs @@ -28,6 +28,15 @@ namespace Barotrauma } } + if (HasAbilityFlag(AbilityFlags.RetainExperienceForNewCharacter)) + { + var ownerClient = GameMain.Server.ConnectedClients.Find(c => c.Character == this); + if (ownerClient != null) + { + (GameMain.GameSession?.GameMode as MultiPlayerCampaign)?.SaveExperiencePoints(ownerClient); + } + } + healthUpdateTimer = 0.0f; if (CauseOfDeath.Killer != null && CauseOfDeath.Killer.IsTraitor && CauseOfDeath.Killer != this) @@ -46,5 +55,10 @@ namespace Barotrauma } } } + + partial void OnMoneyChanged(int prevAmount, int newAmount) + { + GameMain.NetworkMember.CreateEntityEvent(this, new object[] { NetEntityEvent.Type.UpdateMoney }); + } } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterInfo.cs b/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterInfo.cs index f4d7baa52..81abdaf26 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterInfo.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterInfo.cs @@ -2,6 +2,7 @@ using Microsoft.Xna.Framework; using System; using System.Collections.Generic; +using System.Linq; namespace Barotrauma { @@ -9,8 +10,9 @@ namespace Barotrauma { private readonly Dictionary prevSentSkill = new Dictionary(); - partial void OnSkillChanged(string skillIdentifier, float prevLevel, float newLevel, Vector2 textPopupPos) + partial void OnSkillChanged(string skillIdentifier, float prevLevel, float newLevel) { + if (Character == null || Character.Removed) { return; } if (!prevSentSkill.ContainsKey(skillIdentifier)) { prevSentSkill[skillIdentifier] = prevLevel; @@ -22,6 +24,21 @@ namespace Barotrauma } } + partial void OnExperienceChanged(int prevAmount, int newAmount) + { + if (Character == null || Character.Removed) { return; } + if (prevAmount != newAmount) + { + GameMain.NetworkMember.CreateEntityEvent(Character, new object[] { NetEntityEvent.Type.UpdateExperience }); + } + } + + partial void OnPermanentStatChanged(StatTypes statType) + { + if (Character == null || Character.Removed) { return; } + GameMain.NetworkMember.CreateEntityEvent(Character, new object[] { NetEntityEvent.Type.UpdatePermanentStats, statType }); + } + public void ServerWrite(IWriteMessage msg) { msg.Write(ID); @@ -30,10 +47,13 @@ namespace Barotrauma msg.Write((byte)Gender); msg.Write((byte)Race); msg.Write((byte)HeadSpriteId); - msg.Write((byte)Head.HairIndex); - msg.Write((byte)Head.BeardIndex); - msg.Write((byte)Head.MoustacheIndex); - msg.Write((byte)Head.FaceAttachmentIndex); + msg.Write((byte)HairIndex); + msg.Write((byte)BeardIndex); + msg.Write((byte)MoustacheIndex); + msg.Write((byte)FaceAttachmentIndex); + msg.WriteColorR8G8B8(SkinColor); + msg.WriteColorR8G8B8(HairColor); + msg.WriteColorR8G8B8(FacialHairColor); msg.Write(ragdollFileName); if (Job != null) @@ -53,6 +73,19 @@ namespace Barotrauma msg.Write((byte)0); } // TODO: animations + msg.Write((byte)SavedStatValues.SelectMany(s => s.Value).Count()); + foreach (var savedStatValuePair in SavedStatValues) + { + foreach (var savedStatValue in savedStatValuePair.Value) + { + msg.Write((byte)savedStatValuePair.Key); + msg.Write(savedStatValue.StatIdentifier); + msg.Write(savedStatValue.StatValue); + msg.Write(savedStatValue.RemoveOnDeath); + } + } + msg.Write((ushort)ExperiencePoints); + msg.Write((ushort)AdditionalTalentPoints); } } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs b/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs index bcd25ddf9..bc7428d42 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs @@ -238,7 +238,7 @@ namespace Barotrauma break; case ClientNetObject.ENTITY_STATE: - int eventType = msg.ReadRangedInteger(0, 3); + int eventType = msg.ReadRangedInteger(0, 4); switch (eventType) { case 0: @@ -268,8 +268,35 @@ namespace Barotrauma if (IsIncapacitated) { var causeOfDeath = CharacterHealth.GetCauseOfDeath(); - Kill(causeOfDeath.First, causeOfDeath.Second); + Kill(causeOfDeath.type, causeOfDeath.affliction); } + break; + case 3: // NetEntityEvent.Type.UpdateTalents + if (c.Character != this) + { +#if DEBUG + DebugConsole.Log("Received a character update message from a client who's not controlling the character"); +#endif + return; + } + + // get the full list of talents from the player, only give the ones + // that are not already given (or otherwise not viable) + ushort talentCount = msg.ReadUInt16(); + List talentSelection = new List(); + for (int i = 0; i < talentCount; i++) + { + UInt32 talentIdentifier = msg.ReadUInt32(); + var prefab = TalentPrefab.TalentPrefabs.Find(p => p.UIntIdentifier == talentIdentifier); + if (prefab != null) { talentSelection.Add(prefab.Identifier); } + } + talentSelection = TalentTree.CheckTalentSelection(this, talentSelection); + + foreach (string talent in talentSelection) + { + GiveTalent(talent); + } + break; } break; @@ -283,7 +310,7 @@ namespace Barotrauma if (extraData != null) { - const int min = 0, max = 9; + const int min = 0, max = 13; switch ((NetEntityEvent.Type)extraData[0]) { case NetEntityEvent.Type.InventoryState: @@ -394,6 +421,47 @@ namespace Barotrauma msg.Write(inventoryItemIDs[i]); } break; + case NetEntityEvent.Type.UpdateExperience: + msg.WriteRangedInteger(10, min, max); + msg.Write(Info.ExperiencePoints); + break; + case NetEntityEvent.Type.UpdateTalents: + msg.WriteRangedInteger(11, min, max); + msg.Write((ushort)characterTalents.Count); + foreach (var unlockedTalent in characterTalents) + { + msg.Write(unlockedTalent.AddedThisRound); + msg.Write(unlockedTalent.Prefab.UIntIdentifier); + } + break; + case NetEntityEvent.Type.UpdateMoney: + msg.WriteRangedInteger(12, min, max); + msg.Write(GameMain.GameSession.Campaign.Money); + break; + case NetEntityEvent.Type.UpdatePermanentStats: + msg.WriteRangedInteger(13, min, max); + if (Info == null || extraData.Length < 2 || !(extraData[1] is StatTypes statType)) + { + msg.Write((byte)0); + msg.Write((byte)0); + } + else if (!Info.SavedStatValues.ContainsKey(statType)) + { + msg.Write((byte)0); + msg.Write((byte)statType); + } + else + { + msg.Write((byte)Info.SavedStatValues[statType].Count); + msg.Write((byte)statType); + foreach (var savedStatValue in Info.SavedStatValues[statType]) + { + msg.Write(savedStatValue.StatIdentifier); + msg.Write(savedStatValue.StatValue); + msg.Write(savedStatValue.RemoveOnDeath); + } + } + break; default: DebugConsole.ThrowError("Invalid NetworkEvent type for entity " + ToString() + " (" + (NetEntityEvent.Type)extraData[0] + ")"); break; @@ -499,7 +567,7 @@ namespace Barotrauma if (writeStatus) { WriteStatus(tempBuffer); - (AIController as EnemyAIController)?.PetBehavior?.ServerWrite(tempBuffer); + AIController?.ServerWrite(tempBuffer); HealthUpdatePending = false; } diff --git a/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs b/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs index 4e69294a8..2b9b3f857 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs @@ -1187,6 +1187,7 @@ namespace Barotrauma NewMessage("*****************", Color.Lime); GameServer.Log("Console command \"restart\" executed: closing the server...", ServerLog.MessageType.ServerMessage); GameMain.Instance.CloseServer(); + GameMain.Instance.TryStartChildServerRelay(); GameMain.Instance.StartServer(); })); @@ -1676,10 +1677,25 @@ namespace Barotrauma return; } + bool relativeStrength = false; + if (args.Length > 4) + { + bool.TryParse(args[4], out relativeStrength); + } + Character targetCharacter = (args.Length <= 2) ? client.Character : FindMatchingCharacter(args.Skip(2).ToArray()); if (targetCharacter != null) { - targetCharacter.CharacterHealth.ApplyAffliction(targetCharacter.AnimController.MainLimb, afflictionPrefab.Instantiate(afflictionStrength)); + Limb targetLimb = targetCharacter.AnimController.MainLimb; + if (args.Length > 3) + { + targetLimb = targetCharacter.AnimController.Limbs.FirstOrDefault(l => l.type.ToString().Equals(args[3], StringComparison.OrdinalIgnoreCase)); + } + if (relativeStrength) + { + afflictionStrength *= targetCharacter.MaxVitality / afflictionPrefab.MaxStrength; + } + targetCharacter.CharacterHealth.ApplyAffliction(targetLimb ?? targetCharacter.AnimController.MainLimb, afflictionPrefab.Instantiate(afflictionStrength)); } } ); @@ -1716,16 +1732,86 @@ namespace Barotrauma { foreach (Client c in GameMain.Server.ConnectedClients) { - if (c.Character != revivedCharacter) continue; + if (c.Character != revivedCharacter) { continue; } - //clients stop controlling the character when it dies, force control back - GameMain.Server.SetClientCharacter(c, revivedCharacter); + //clients stop controlling the character when it dies, force control back + GameMain.Server.SetClientCharacter(c, revivedCharacter); break; } } } ); + AssignOnClientRequestExecute( + "givetalent", + (Client client, Vector2 cursorWorldPos, string[] args) => + { + if (args.Length == 0) { return; } + Character targetCharacter = (args.Length >= 2) ? FindMatchingCharacter(args.Skip(1).ToArray(), false) : client.Character; + + if (targetCharacter == null) { return; } + + TalentPrefab talentPrefab = TalentPrefab.TalentPrefabs.Find(c => + c.Identifier.Equals(args[0], StringComparison.OrdinalIgnoreCase) || + c.DisplayName.Equals(args[0], StringComparison.OrdinalIgnoreCase)); + if (talentPrefab == null) + { + GameMain.Server.SendConsoleMessage("Couldn't find the talent \"" + args[0] + "\".", client); + return; + } + targetCharacter.GiveTalent(talentPrefab); + NewMessage($"Talent \"{talentPrefab.DisplayName}\" given to \"{targetCharacter.Name}\" by \"{client.Name}\"."); + GameMain.Server.SendConsoleMessage($"Gave talent \"{talentPrefab.DisplayName}\" to \"{targetCharacter.Name}\".", client); + } + ); + + AssignOnClientRequestExecute( + "unlocktalents", + (Client client, Vector2 cursorWorldPos, string[] args) => + { + var targetCharacter = args.Length >= 2 ? FindMatchingCharacter(args.Skip(1).ToArray()) : Character.Controlled; + if (targetCharacter == null) { return; } + + List talentTrees = new List(); + if (args.Length == 0 || args[0].Equals("all", StringComparison.OrdinalIgnoreCase)) + { + talentTrees.AddRange(TalentTree.JobTalentTrees.Values); + } + else + { + var job = JobPrefab.Prefabs.Find(jp => jp.Name != null && jp.Name.Equals(args[0], StringComparison.OrdinalIgnoreCase)); + if (job == null) + { + GameMain.Server.SendConsoleMessage($"Failed to find the job \"{args[0]}\".", client); + return; + } + if (!TalentTree.JobTalentTrees.TryGetValue(job.Identifier, out TalentTree talentTree)) + { + GameMain.Server.SendConsoleMessage($"No talents configured for the job \"{args[0]}\".", client); + return; + } + talentTrees.Add(talentTree); + } + + foreach (var talentTree in talentTrees) + { + foreach (var subTree in talentTree.TalentSubTrees) + { + foreach (var option in subTree.TalentOptionStages) + { + foreach (var talent in option.Talents) + { + targetCharacter.GiveTalent(talent); + NewMessage($"Talent \"{talent.DisplayName}\" given to \"{targetCharacter.Name}\" by \"{client.Name}\"."); + GameMain.Server.SendConsoleMessage($"Gave talent \"{talent.DisplayName}\" to \"{targetCharacter.Name}\".", client); + NewMessage($"Unlocked talent \"{talent.DisplayName}\"."); + } + } + } + } + } + ); + AssignOnClientRequestExecute( "freeze", (Client client, Vector2 cursorWorldPos, string[] args) => @@ -1776,7 +1862,7 @@ namespace Barotrauma "control", (Client client, Vector2 cursorWorldPos, string[] args) => { - if (args.Length < 1) return; + if (args.Length < 1) { return; } var character = FindMatchingCharacter(args, ignoreRemotePlayers: true, allowedRemotePlayer: client); if (character != null) { @@ -2100,6 +2186,7 @@ namespace Barotrauma if (client == null) { GameMain.Server.SendConsoleMessage("Client \"" + args[0] + "\" not found.", senderClient); + return; } var character = FindMatchingCharacter(args.Skip(1).ToArray(), false); @@ -2190,13 +2277,13 @@ namespace Barotrauma { foreach (Skill skill in character.Info.Job.Skills) { - character.Info.SetSkillLevel(skill.Identifier, level, character.WorldPosition); + character.Info.SetSkillLevel(skill.Identifier, level); } GameMain.Server.SendConsoleMessage($"Set all {character.Name}'s skills to {level}", senderClient); } else { - character.Info.SetSkillLevel(skillIdentifier, level, character.WorldPosition); + character.Info.SetSkillLevel(skillIdentifier, level); GameMain.Server.SendConsoleMessage($"Set {character.Name}'s {skillIdentifier} level to {level}", senderClient); } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/AbandonedOutpostMission.cs b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/AbandonedOutpostMission.cs index e649ec598..99e79fc47 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/AbandonedOutpostMission.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/AbandonedOutpostMission.cs @@ -11,6 +11,7 @@ namespace Barotrauma public override void ServerWriteInitial(IWriteMessage msg, Client c) { + base.ServerWriteInitial(msg, c); msg.Write((ushort)spawnedItems.Count); foreach (Item item in spawnedItems) { diff --git a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/AlienRuinMission.cs b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/AlienRuinMission.cs new file mode 100644 index 000000000..dd8138d18 --- /dev/null +++ b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/AlienRuinMission.cs @@ -0,0 +1,22 @@ +using Barotrauma.Networking; + +namespace Barotrauma +{ + partial class AlienRuinMission : Mission + { + public override void ServerWriteInitial(IWriteMessage msg, Client c) + { + base.ServerWriteInitial(msg, c); + msg.Write((ushort)existingTargets.Count); + foreach (var t in existingTargets) + { + msg.Write(t != null ? t.ID : Entity.NullEntityID); + } + msg.Write((ushort)spawnedTargets.Count); + foreach (var t in spawnedTargets) + { + t.WriteSpawnData(msg, t.ID, false); + } + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/BeaconMission.cs b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/BeaconMission.cs deleted file mode 100644 index 0f0a29d29..000000000 --- a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/BeaconMission.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Barotrauma.Networking; - -namespace Barotrauma -{ - partial class BeaconMission : Mission - { - public override void ServerWriteInitial(IWriteMessage msg, Client c) - { - return; - } - } -} diff --git a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/CargoMission.cs b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/CargoMission.cs index e230ab075..10f5ce5ad 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/CargoMission.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/CargoMission.cs @@ -6,6 +6,7 @@ namespace Barotrauma { public override void ServerWriteInitial(IWriteMessage msg, Client c) { + base.ServerWriteInitial(msg, c); msg.Write((ushort)items.Count); foreach (Item item in items) { diff --git a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/CombatMission.cs b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/CombatMission.cs index c7912eb2a..76e83ca8b 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/CombatMission.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/CombatMission.cs @@ -83,10 +83,5 @@ namespace Barotrauma } } } - - public override void ServerWriteInitial(IWriteMessage msg, Client c) - { - //do nothing - } } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/EscortMission.cs b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/EscortMission.cs index 5cb7f95bb..060369f9a 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/EscortMission.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/EscortMission.cs @@ -9,6 +9,8 @@ namespace Barotrauma { public override void ServerWriteInitial(IWriteMessage msg, Client c) { + base.ServerWriteInitial(msg, c); + if (characters.Count == 0) { throw new InvalidOperationException("Server attempted to write escort mission data when no characters had been spawned."); diff --git a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/MineralMission.cs b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/MineralMission.cs index b6555cb25..b3dd48bff 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/MineralMission.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/MineralMission.cs @@ -6,16 +6,17 @@ namespace Barotrauma { public override void ServerWriteInitial(IWriteMessage msg, Client c) { + base.ServerWriteInitial(msg, c); msg.Write((byte)caves.Count); foreach (var cave in caves) { msg.Write((byte)(Level.Loaded == null || !Level.Loaded.Caves.Contains(cave) ? 255 : Level.Loaded.Caves.IndexOf(cave))); } - foreach (var kvp in SpawnedResources) + foreach (var kvp in spawnedResources) { msg.Write((byte)kvp.Value.Count); - var rotation = ResourceClusters[kvp.Key].Second; + var rotation = resourceClusters[kvp.Key].rotation; msg.Write(rotation); foreach (var r in kvp.Value) { @@ -23,7 +24,7 @@ namespace Barotrauma } } - foreach (var kvp in RelevantLevelResources) + foreach (var kvp in relevantLevelResources) { msg.Write(kvp.Key); msg.Write((byte)kvp.Value.Length); diff --git a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/Mission.cs b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/Mission.cs index dbbc96902..fc1b5041c 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/Mission.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/Mission.cs @@ -16,6 +16,14 @@ namespace Barotrauma GameServer.Log(TextManager.Get("MissionInfo") + ": " + header + " - " + message, ServerLog.MessageType.ServerMessage); } - public abstract void ServerWriteInitial(IWriteMessage msg, Client c); + public virtual void ServerWriteInitial(IWriteMessage msg, Client c) + { + msg.Write((ushort)State); + } + + public virtual void ServerWrite(IWriteMessage msg) + { + msg.Write((ushort)State); + } } } \ No newline at end of file diff --git a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/MonsterMission.cs b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/MonsterMission.cs index e46a6bf95..937e23511 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/MonsterMission.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/MonsterMission.cs @@ -7,6 +7,8 @@ namespace Barotrauma { public override void ServerWriteInitial(IWriteMessage msg, Client c) { + base.ServerWriteInitial(msg, c); + if (monsters.Count == 0 && monsterPrefabs.Count > 0) { throw new InvalidOperationException("Server attempted to write monster mission data when no monsters had been spawned."); diff --git a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/NestMission.cs b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/NestMission.cs index d1b702aa1..7d5c88bb1 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/NestMission.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/NestMission.cs @@ -8,6 +8,7 @@ namespace Barotrauma public override void ServerWriteInitial(IWriteMessage msg, Client c) { + base.ServerWriteInitial(msg, c); msg.Write((byte)(selectedCave == null || Level.Loaded == null || !Level.Loaded.Caves.Contains(selectedCave) ? 255 : Level.Loaded.Caves.IndexOf(selectedCave))); msg.Write(nestPosition.X); msg.Write(nestPosition.Y); diff --git a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/PirateMission.cs b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/PirateMission.cs index 52b5687be..4eb529c2e 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/PirateMission.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/PirateMission.cs @@ -9,6 +9,8 @@ namespace Barotrauma { public override void ServerWriteInitial(IWriteMessage msg, Client c) { + base.ServerWriteInitial(msg, c); + // duplicate code from escortmission, should possibly be combined, though additional loot items might be added so maybe not if (characters.Count == 0) { diff --git a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/SalvageMission.cs b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/SalvageMission.cs index 1ba55eee5..292d53ce0 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/SalvageMission.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/SalvageMission.cs @@ -15,6 +15,8 @@ namespace Barotrauma public override void ServerWriteInitial(IWriteMessage msg, Client c) { + base.ServerWriteInitial(msg, c); + msg.Write(usedExistingItem); if (usedExistingItem) { diff --git a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/ScanMission.cs b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/ScanMission.cs new file mode 100644 index 000000000..dc5dbff31 --- /dev/null +++ b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/ScanMission.cs @@ -0,0 +1,37 @@ +using Barotrauma.Networking; + +namespace Barotrauma +{ + partial class ScanMission : Mission + { + public override void ServerWriteInitial(IWriteMessage msg, Client c) + { + base.ServerWriteInitial(msg, c); + msg.Write((ushort)startingItems.Count); + foreach (var item in startingItems) + { + item.WriteSpawnData(msg, + item.ID, + parentInventoryIDs.ContainsKey(item) ? parentInventoryIDs[item] : Entity.NullEntityID, + parentItemContainerIndices.ContainsKey(item) ? parentItemContainerIndices[item] : (byte)0); + } + ServerWriteScanTargetStatus(msg); + } + + public override void ServerWrite(IWriteMessage msg) + { + base.ServerWrite(msg); + ServerWriteScanTargetStatus(msg); + } + + private void ServerWriteScanTargetStatus(IWriteMessage msg) + { + msg.Write((byte)scanTargets.Count); + foreach (var kvp in scanTargets) + { + msg.Write(kvp.Key != null ? kvp.Key.ID : Entity.NullEntityID); + msg.Write(kvp.Value); + } + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaServer/ServerSource/GameMain.cs b/Barotrauma/BarotraumaServer/ServerSource/GameMain.cs index 95b3ee358..ddaefc903 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/GameMain.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/GameMain.cs @@ -18,7 +18,17 @@ namespace Barotrauma { public static readonly Version Version = Assembly.GetEntryAssembly().GetName().Version; - public static World World; + + private static World world; + public static World World + { + get + { + if (world == null) { world = new World(new Vector2(0, -9.82f)); } + return world; + } + set { world = value; } + } public static GameSettings Config; public static GameServer Server; @@ -123,6 +133,8 @@ namespace Barotrauma ItemAssemblyPrefab.LoadAll(); LevelObjectPrefab.LoadAll(); BallastFloraPrefab.LoadAll(GetFilesOfType(ContentType.MapCreature)); + TalentPrefab.LoadAll(GetFilesOfType(ContentType.Talents)); + TalentTree.LoadAll(GetFilesOfType(ContentType.TalentTrees)); GameModePreset.Init(); DecalManager = new DecalManager(); @@ -179,6 +191,20 @@ namespace Barotrauma } } + public bool TryStartChildServerRelay() + { + for (int i = 0; i < CommandLineArgs.Length; i++) + { + switch (CommandLineArgs[i].Trim()) + { + case "-pipes": + ChildServerRelay.Start(CommandLineArgs[i + 2], CommandLineArgs[i + 1]); + return true; + } + } + return false; + } + public void StartServer() { string name = "Server"; @@ -264,7 +290,7 @@ namespace Barotrauma i++; break; case "-pipes": - ChildServerRelay.Start(CommandLineArgs[i + 2], CommandLineArgs[i + 1]); + //handled in TryStartChildServerRelay i += 2; break; } @@ -323,6 +349,7 @@ namespace Barotrauma Hyper.ComponentModel.HyperTypeDescriptionProvider.Add(typeof(Items.Components.ItemComponent)); Hyper.ComponentModel.HyperTypeDescriptionProvider.Add(typeof(Hull)); + TryStartChildServerRelay(); Init(); StartServer(); diff --git a/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/CharacterCampaignData.cs b/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/CharacterCampaignData.cs index 94152023e..9f3f034b7 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/CharacterCampaignData.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/CharacterCampaignData.cs @@ -1,4 +1,6 @@ using Barotrauma.Networking; +using System.Globalization; +using System.Xml.Linq; namespace Barotrauma { @@ -11,11 +13,65 @@ namespace Barotrauma get { return itemData != null; } } - partial void InitProjSpecific(Client client) + public CharacterCampaignData(Client client, bool giveRespawnPenaltyAffliction = false) { + Name = client.Name; ClientEndPoint = client.Connection.EndPointString; SteamID = client.SteamID; CharacterInfo = client.CharacterInfo; + + healthData = new XElement("health"); + client.Character?.CharacterHealth?.Save(healthData); + if (giveRespawnPenaltyAffliction) + { + var respawnPenaltyAffliction = RespawnManager.GetRespawnPenaltyAffliction(); + healthData.Add(new XElement("Affliction", + new XAttribute("identifier", respawnPenaltyAffliction.Identifier), + new XAttribute("strength", respawnPenaltyAffliction.Strength.ToString("G", CultureInfo.InvariantCulture)))); + } + if (client.Character?.Inventory != null) + { + itemData = new XElement("inventory"); + Character.SaveInventory(client.Character.Inventory, itemData); + } + OrderData = new XElement("orders"); + if (client.CharacterInfo != null) + { + CharacterInfo.SaveOrderData(client.CharacterInfo, OrderData); + } + } + + + public CharacterCampaignData(XElement element) + { + Name = element.GetAttributeString("name", "Unnamed"); + ClientEndPoint = element.GetAttributeString("endpoint", null) ?? element.GetAttributeString("ip", ""); + string steamID = element.GetAttributeString("steamid", ""); + if (!string.IsNullOrEmpty(steamID)) + { + ulong.TryParse(steamID, out ulong parsedID); + SteamID = parsedID; + } + + foreach (XElement subElement in element.Elements()) + { + switch (subElement.Name.ToString().ToLowerInvariant()) + { + case "character": + case "characterinfo": + CharacterInfo = new CharacterInfo(subElement); + break; + case "inventory": + itemData = subElement; + break; + case "health": + healthData = subElement; + break; + case "orders": + OrderData = subElement; + break; + } + } } public bool MatchesClient(Client client) @@ -56,6 +112,6 @@ namespace Barotrauma public void ApplyOrderData(Character character) { CharacterInfo.ApplyOrderData(character, OrderData); - } + } } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs b/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs index 3996fdbeb..7dafc70d0 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs @@ -27,6 +27,29 @@ namespace Barotrauma public bool GameOver { get; private set; } + class SavedExperiencePoints + { + public readonly ulong SteamID; + public readonly string EndPoint; + public readonly int ExperiencePoints; + + public SavedExperiencePoints(Client client) + { + SteamID = client.SteamID; + EndPoint = client.Connection.EndPointString; + ExperiencePoints = client.Character?.Info?.ExperiencePoints ?? 0; + } + + public SavedExperiencePoints(XElement element) + { + SteamID = element.GetAttributeUInt64("steamid", 0); + EndPoint = element.GetAttributeString("endpoint", string.Empty); + ExperiencePoints = element.GetAttributeInt("points", 0); + } + } + + private readonly List savedExperiencePoints = new List(); + public override bool Paused { get { return ForceMapUI || CoroutineManager.IsCoroutineRunning("LevelTransition"); } @@ -155,6 +178,20 @@ namespace Barotrauma c.InGame && (IsOwner(c) || c.HasPermission(ClientPermissions.ManageCampaign))); } + public void SaveExperiencePoints(Client client) + { + ClearSavedExperiencePoints(client); + savedExperiencePoints.Add(new SavedExperiencePoints(client)); + } + public int GetSavedExperiencePoints(Client client) + { + return savedExperiencePoints.Find(s => s.SteamID != 0 && client.SteamID == s.SteamID || client.EndpointMatches(s.EndPoint))?.ExperiencePoints ?? 0; + } + public void ClearSavedExperiencePoints(Client client) + { + savedExperiencePoints.RemoveAll(s => s.SteamID != 0 && client.SteamID == s.SteamID || client.EndpointMatches(s.EndPoint)); + } + public void LoadPets() { if (petsElement != null) @@ -163,7 +200,7 @@ namespace Barotrauma } } - public void SaveInventories() + public void SavePlayers() { List prevCharacterData = new List(characterData); //client character has spawned this round -> remove old data (and replace with an up-to-date one if the client still has a character) @@ -172,7 +209,12 @@ namespace Barotrauma //refresh the character data of clients who are still in the server foreach (Client c in GameMain.Server.ConnectedClients) { - if (c.HasSpawned && c.CharacterInfo != null && c.CharacterInfo.CauseOfDeath != null && c.CharacterInfo.CauseOfDeath?.Type != CauseOfDeathType.Disconnected) + if (c.Character != null && c.Character.Info == null) + { + c.Character = null; + } + + if (c.HasSpawned && c.CharacterInfo != null && c.CharacterInfo.CauseOfDeath != null && c.CharacterInfo.CauseOfDeath.Type != CauseOfDeathType.Disconnected) { //the client has opted to spawn this round with Reaper's Tax if (c.WaitForNextRoundRespawn.HasValue && !c.WaitForNextRoundRespawn.Value) @@ -183,14 +225,15 @@ namespace Barotrauma continue; } } - if (c.Character?.Info == null) { continue; } - if (c.Character.IsDead && c.Character.CauseOfDeath?.Type != CauseOfDeathType.Disconnected) + var characterInfo = c.Character?.Info ?? c.CharacterInfo; + if (characterInfo == null) { continue; } + if (c.CharacterInfo.CauseOfDeath != null && characterInfo.CauseOfDeath.Type != CauseOfDeathType.Disconnected) { - continue; + RespawnManager.ReduceCharacterSkills(characterInfo); } - c.CharacterInfo = c.Character.Info; + c.CharacterInfo = characterInfo; characterData.RemoveAll(cd => cd.MatchesClient(c)); - characterData.Add(new CharacterCampaignData(c)); + characterData.Add(new CharacterCampaignData(c)); } //refresh the character data of clients who aren't in the server anymore @@ -259,6 +302,16 @@ namespace Barotrauma Map.ProgressWorld(transitionType, (float)(Timing.TotalTime - GameMain.GameSession.RoundStartTime)); bool success = GameMain.Server.ConnectedClients.Any(c => c.InGame && c.Character != null && !c.Character.IsDead); + if (success) + { + foreach (Client c in GameMain.Server.ConnectedClients) + { + if (c.Character?.HasAbilityFlag(AbilityFlags.RetainExperienceForNewCharacter) ?? false) + { + (GameMain.GameSession?.GameMode as MultiPlayerCampaign)?.SaveExperiencePoints(c); + } + } + } GameMain.GameSession.EndRound("", traitorResults, transitionType); @@ -266,7 +319,7 @@ namespace Barotrauma if (success) { - SaveInventories(); + SavePlayers(); yield return CoroutineStatus.Running; @@ -965,6 +1018,15 @@ namespace Barotrauma // save bots CrewManager.SaveMultiplayer(modeElement); + XElement savedExperiencePointsElement = new XElement("SavedExperiencePoints"); + foreach (var savedExperiencePoint in savedExperiencePoints) + { + savedExperiencePointsElement.Add(new XElement("Point", + new XAttribute("steamid", savedExperiencePoint.SteamID), + new XAttribute("endpoint", savedExperiencePoint?.EndPoint ?? string.Empty), + new XAttribute("points", savedExperiencePoint.ExperiencePoints))); + } + // save available submarines XElement availableSubsElement = new XElement("AvailableSubs"); for (int i = 0; i < GameMain.NetLobbyScreen.CampaignSubmarines.Count; i++) diff --git a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/GeneticMaterial.cs b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/GeneticMaterial.cs new file mode 100644 index 000000000..460c5ec46 --- /dev/null +++ b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/GeneticMaterial.cs @@ -0,0 +1,20 @@ +using Barotrauma.Networking; + +namespace Barotrauma.Items.Components +{ + partial class GeneticMaterial : ItemComponent + { + public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null) + { + msg.Write(tainted); + if (tainted) + { + msg.Write(selectedTaintedEffect?.UIntIdentifier ?? 0); + } + else + { + msg.Write(selectedEffect?.UIntIdentifier ?? 0); + } + } + } +} diff --git a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Machines/Steering.cs b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Machines/Steering.cs index 7b29551ae..472cf14b9 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Machines/Steering.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Machines/Steering.cs @@ -16,6 +16,12 @@ namespace Barotrauma.Items.Components set { unsentChanges = value; } } + protected override void RemoveComponentSpecific() + { + base.RemoveComponentSpecific(); + pathFinder = null; + } + public void ServerRead(ClientNetObject type, IReadMessage msg, Barotrauma.Networking.Client c) { diff --git a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Repairable.cs b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Repairable.cs index 3cf56ec69..129d6e622 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Repairable.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Repairable.cs @@ -38,6 +38,8 @@ namespace Barotrauma.Items.Components msg.Write(deteriorationTimer); msg.Write(deteriorateAlwaysResetTimer); msg.Write(DeteriorateAlways); + msg.Write(tinkeringDuration); + msg.Write(tinkeringStrength); msg.Write(CurrentFixer == null ? (ushort)0 : CurrentFixer.ID); msg.WriteRangedInteger((int)currentFixerAction, 0, 2); } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Scanner.cs b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Scanner.cs new file mode 100644 index 000000000..337b721b0 --- /dev/null +++ b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Scanner.cs @@ -0,0 +1,14 @@ +using Barotrauma.Networking; + +namespace Barotrauma.Items.Components +{ + partial class Scanner : ItemComponent, IServerSerializable + { + private float LastSentScanTimer { get; set; } + + public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null) + { + msg.Write(scanTimer); + } + } +} diff --git a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Signal/ButtonTerminal.cs b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Signal/ButtonTerminal.cs new file mode 100644 index 000000000..056410165 --- /dev/null +++ b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Signal/ButtonTerminal.cs @@ -0,0 +1,21 @@ +using Barotrauma.Networking; + +namespace Barotrauma.Items.Components +{ + partial class ButtonTerminal : ItemComponent, IClientSerializable, IServerSerializable + { + public void ServerRead(ClientNetObject type, IReadMessage msg, Client c) + { + int signalIndex = msg.ReadRangedInteger(0, Signals.Length - 1); + if (!item.CanClientAccess(c)) { return; } + if (!SendSignal(signalIndex)) { return; } + GameServer.Log($"{GameServer.CharacterLogName(c.Character)} sent a signal \"{Signals[signalIndex]}\" from {item.Name}", ServerLog.MessageType.ItemInteraction); + item.CreateServerEvent(this, new object[] { signalIndex }); + } + + public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null) + { + Write(msg, extraData); + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Signal/ConnectionPanel.cs b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Signal/ConnectionPanel.cs index b66f144ff..aa3466eb1 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Signal/ConnectionPanel.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Signal/ConnectionPanel.cs @@ -76,7 +76,7 @@ namespace Barotrauma.Items.Components var panel2 = selectedWire.Connections[1]?.ConnectionPanel; if (panel2 != null && panel2 != this) { panel2.item.CreateServerEvent(panel2); } - CoroutineManager.InvokeAfter(() => + CoroutineManager.Invoke(() => { item.CreateServerEvent(this); if (panel1 != null && panel1 != this) { panel1.item.CreateServerEvent(panel1); } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Signal/Terminal.cs b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Signal/Terminal.cs index 1382bdc27..517f51201 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Signal/Terminal.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Signal/Terminal.cs @@ -19,13 +19,13 @@ namespace Barotrauma.Items.Components GameServer.Log(GameServer.CharacterLogName(c.Character) + " entered \"" + newOutputValue + "\" on " + item.Name, ServerLog.MessageType.ItemInteraction); OutputValue = newOutputValue; - ShowOnDisplay(newOutputValue); + ShowOnDisplay(newOutputValue, addToHistory: true); item.SendSignal(newOutputValue, "signal_out"); item.CreateServerEvent(this); } } - partial void ShowOnDisplay(string input, bool addToHistory = true) + partial void ShowOnDisplay(string input, bool addToHistory) { if (addToHistory) { diff --git a/Barotrauma/BarotraumaServer/ServerSource/Items/Item.cs b/Barotrauma/BarotraumaServer/ServerSource/Items/Item.cs index ce354ecaf..c7b6eb206 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Items/Item.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Items/Item.cs @@ -282,6 +282,7 @@ namespace Barotrauma msg.Write(body == null ? (byte)0 : (byte)body.BodyType); msg.Write(SpawnedInOutpost); msg.Write(AllowStealing); + msg.WriteRangedInteger(Quality, 0, Items.Components.Quality.MaxQuality); byte teamID = 0; foreach (WifiComponent wifiComponent in GetComponents()) @@ -289,6 +290,14 @@ namespace Barotrauma teamID = (byte)wifiComponent.TeamID; break; } + if (teamID == 0) + { + foreach (IdCard idCard in GetComponents()) + { + teamID = (byte)idCard.TeamID; + break; + } + } msg.Write(teamID); bool tagsChanged = tags.Count != prefab.Tags.Count || !tags.All(t => prefab.Tags.Contains(t)); diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/ChildServerRelay.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/ChildServerRelay.cs index 6226d0b2c..584342950 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/ChildServerRelay.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/ChildServerRelay.cs @@ -1,12 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using Barotrauma.IO; -using System.IO.Pipes; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Win32.SafeHandles; +using System.IO.Pipes; namespace Barotrauma.Networking { diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs index 232ecf417..1ed8e43cc 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs @@ -1181,6 +1181,10 @@ namespace Barotrauma.Networking { c.Character.ServerRead(objHeader, inc, c); } + else + { + DebugConsole.AddWarning($"Received character inputs from a client who's not controlling a character ({c.Name})."); + } break; case ClientNetObject.ENTITY_STATE: entityEventManager.Read(inc, c); @@ -1318,7 +1322,7 @@ namespace Barotrauma.Networking Log("Client \"" + GameServer.ClientLogName(sender) + "\" ended the round.", ServerLog.MessageType.ServerMessage); if (mpCampaign != null && Level.IsLoadedOutpost) { - mpCampaign.SaveInventories(); + mpCampaign.SavePlayers(); GameMain.GameSession.SubmarineInfo = new SubmarineInfo(GameMain.GameSession.Submarine); SaveUtil.SaveGame(GameMain.GameSession.SavePath); } @@ -2354,8 +2358,16 @@ namespace Barotrauma.Networking characterData.ApplyHealthData(spawnedCharacter); characterData.ApplyOrderData(spawnedCharacter); spawnedCharacter.GiveIdCardTags(mainSubWaypoints[i]); + spawnedCharacter.LoadTalents(); + characterData.HasSpawned = true; } + if (GameMain.GameSession?.GameMode is MultiPlayerCampaign mpCampaign && spawnedCharacter.Info != null) + { + spawnedCharacter.Info.SetExperience(Math.Max(spawnedCharacter.Info.ExperiencePoints, mpCampaign.GetSavedExperiencePoints(teamClients[i]))); + mpCampaign.ClearSavedExperiencePoints(teamClients[i]); + } + spawnedCharacter.OwnerClientEndPoint = teamClients[i].Connection.EndPointString; spawnedCharacter.OwnerClientName = teamClients[i].Name; } @@ -2366,6 +2378,8 @@ namespace Barotrauma.Networking spawnedCharacter.TeamID = teamID; spawnedCharacter.GiveJobItems(mainSubWaypoints[i]); spawnedCharacter.GiveIdCardTags(mainSubWaypoints[i]); + // talents are only avilable for players in online sessions, but modders or someone else might want to have them loaded anyway + spawnedCharacter.LoadTalents(); } } @@ -2431,6 +2445,7 @@ namespace Barotrauma.Networking roundStartTime = DateTime.Now; + startGameCoroutine = null; yield return CoroutineStatus.Success; } @@ -2455,6 +2470,7 @@ namespace Barotrauma.Networking msg.Write(serverSettings.AllowRespawn && missionAllowRespawn); msg.Write(serverSettings.AllowDisguises); msg.Write(serverSettings.AllowRewiring); + msg.Write(serverSettings.AllowFriendlyFire); msg.Write(serverSettings.LockAllDefaultWires); msg.Write(serverSettings.AllowRagdollButton); msg.Write(serverSettings.UseRespawnShuttle); @@ -2619,8 +2635,8 @@ namespace Barotrauma.Networking } } - Submarine.Unload(); entityEventManager.Clear(); + Submarine.Unload(); GameMain.NetLobbyScreen.Select(); Log("Round ended.", ServerLog.MessageType.ServerMessage); @@ -3145,28 +3161,19 @@ namespace Barotrauma.Networking public void SendOrderChatMessage(OrderChatMessage message) { if (message.Sender == null || message.Sender.SpeechImpediment >= 100.0f) { return; } - //ChatMessageType messageType = ChatMessage.CanUseRadio(message.Sender) ? ChatMessageType.Radio : ChatMessageType.Default; - //check which clients can receive the message and apply distance effects foreach (Client client in ConnectedClients) { - string modifiedMessage = message.Text; - - if (message.Sender != null && - client.Character != null && !client.Character.IsDead) + if (message.Sender != null && client.Character != null && !client.Character.IsDead) { //too far to hear the msg -> don't send if (!client.Character.CanHearCharacter(message.Sender)) { continue; } } - SendDirectChatMessage(new OrderChatMessage(message.Order, message.OrderOption, message.OrderPriority, message.TargetEntity, message.TargetCharacter, message.Sender), client); } - - string myReceivedMessage = message.Text; - - if (!string.IsNullOrWhiteSpace(myReceivedMessage)) + if (!string.IsNullOrWhiteSpace(message.Text)) { - AddChatMessage(new OrderChatMessage(message.Order, message.OrderOption, message.OrderPriority, myReceivedMessage, message.TargetEntity, message.TargetCharacter, message.Sender)); + AddChatMessage(new OrderChatMessage(message.Order, message.OrderOption, message.OrderPriority, message.Text, message.TargetEntity, message.TargetCharacter, message.Sender)); } } @@ -3462,15 +3469,15 @@ namespace Barotrauma.Networking } catch (Exception e) { - //gender = Gender.Male; - //race = Race.White; - //headSpriteId = 0; DebugConsole.Log("Received invalid characterinfo from \"" + sender.Name + "\"! { " + e.Message + " }"); } int hairIndex = message.ReadByte(); int beardIndex = message.ReadByte(); int moustacheIndex = message.ReadByte(); int faceAttachmentIndex = message.ReadByte(); + Color skinColor = message.ReadColorR8G8B8(); + Color hairColor = message.ReadColorR8G8B8(); + Color facialHairColor = message.ReadColorR8G8B8(); List> jobPreferences = new List>(); int count = message.ReadByte(); @@ -3487,6 +3494,9 @@ namespace Barotrauma.Networking sender.CharacterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, sender.Name); sender.CharacterInfo.RecreateHead(headSpriteId, race, gender, hairIndex, beardIndex, moustacheIndex, faceAttachmentIndex); + sender.CharacterInfo.SkinColor = skinColor; + sender.CharacterInfo.HairColor = hairColor; + sender.CharacterInfo.FacialHairColor = facialHairColor; if (jobPreferences.Count > 0) { @@ -3786,7 +3796,7 @@ namespace Barotrauma.Networking return preferredClient; } - public void UpdateMissionState(Mission mission, int state) + public void UpdateMissionState(Mission mission) { foreach (var client in connectedClients) { @@ -3794,7 +3804,7 @@ namespace Barotrauma.Networking msg.Write((byte)ServerPacketHeader.MISSION); int missionIndex = GameMain.GameSession.GetMissionIndex(mission); msg.Write((byte)(missionIndex == -1 ? 255: missionIndex)); - msg.Write((ushort)state); + mission?.ServerWrite(msg); serverPeer.Send(msg, client.Connection, DeliveryMethod.Reliable); } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/KarmaManager.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/KarmaManager.cs index e4976ad1a..b0f520261 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/KarmaManager.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/KarmaManager.cs @@ -518,14 +518,22 @@ namespace Barotrauma AdjustKarma(character, karmaIncrease, "Repaired item"); } - public void OnReactorOverHeating(Character character, float deltaTime) + public void OnReactorOverHeating(Item reactor, Character character, float deltaTime) { - AdjustKarma(character, -ReactorOverheatKarmaDecrease * deltaTime, "Caused reactor to overheat"); + if (reactor?.Submarine == null || character == null) { return; } + if (reactor.Submarine.TeamID == CharacterTeamType.FriendlyNPC || reactor.Submarine.TeamID == character.TeamID) + { + AdjustKarma(character, -ReactorOverheatKarmaDecrease * deltaTime, "Caused reactor to overheat"); + } } - public void OnReactorMeltdown(Character character) + public void OnReactorMeltdown(Item reactor, Character character) { - AdjustKarma(character, -ReactorMeltdownKarmaDecrease, "Caused a reactor meltdown"); + if (reactor?.Submarine == null || character == null) { return; } + if (reactor.Submarine.TeamID == CharacterTeamType.FriendlyNPC || reactor.Submarine.TeamID == character.TeamID) + { + AdjustKarma(character, -ReactorMeltdownKarmaDecrease, "Caused a reactor meltdown"); + } } public void OnExtinguishingFire(Character character, float deltaTime) diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/NetEntityEvent/ServerEntityEventManager.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/NetEntityEvent/ServerEntityEventManager.cs index dd851251d..95aa0e83f 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/NetEntityEvent/ServerEntityEventManager.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/NetEntityEvent/ServerEntityEventManager.cs @@ -44,11 +44,11 @@ namespace Barotrauma.Networking class ServerEntityEventManager : NetEntityEventManager { - private List events; + private readonly List events; //list of unique events (i.e. !IsDuplicate) created during the round //used for syncing clients who join mid-round - private List uniqueEvents; + private readonly List uniqueEvents; private UInt16 lastSentToAll; private UInt16 lastSentToAnyone; @@ -90,11 +90,11 @@ namespace Barotrauma.Networking } } - private List bufferedEvents; + private readonly List bufferedEvents; private UInt16 ID; - private GameServer server; + private readonly GameServer server; private double lastEventCountHighWarning; diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/RespawnManager.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/RespawnManager.cs index 7f97a81d9..1d2ab8e2e 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/RespawnManager.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/RespawnManager.cs @@ -8,6 +8,11 @@ namespace Barotrauma.Networking { partial class RespawnManager : Entity, IServerSerializable { + /// + /// How much skills drop towards the job's default skill levels when respawning midround in the campaign + /// + const float SkillReductionOnCampaignMidroundRespawn = 0.75f; + private DateTime despawnTime; private float shuttleEmptyTimer; @@ -284,6 +289,8 @@ namespace Barotrauma.Networking partial void RespawnCharactersProjSpecific(Vector2? shuttlePos) { + respawnedCharacters.Clear(); + var respawnSub = RespawnShuttle ?? Submarine.MainSub; MultiPlayerCampaign campaign = GameMain.GameSession.GameMode as MultiPlayerCampaign; @@ -300,7 +307,7 @@ namespace Barotrauma.Networking if (matchingData != null && !matchingData.HasSpawned) { c.CharacterInfo = matchingData.CharacterInfo; - } + } //all characters are in Team 1 in game modes/missions with only one team. //if at some point we add a game mode with multiple teams where respawning is possible, this needs to be reworked @@ -355,8 +362,28 @@ namespace Barotrauma.Networking characterInfos[i].ClearCurrentOrders(); - var character = Character.Create(characterInfos[i], shuttleSpawnPoints[i].WorldPosition, characterInfos[i].Name, isRemotePlayer: !bot, hasAi: bot); + bool forceSpawnInMainSub = false; + if (!bot && campaign != null) + { + var matchingData = campaign?.GetClientCharacterData(clients[i]); + if (matchingData != null) + { + if (!matchingData.HasSpawned) + { + forceSpawnInMainSub = true; + } + else + { + ReduceCharacterSkills(characterInfos[i]); + } + } + } + + var character = Character.Create(characterInfos[i], (forceSpawnInMainSub ? mainSubSpawnPoints[i] : shuttleSpawnPoints[i]).WorldPosition, characterInfos[i].Name, isRemotePlayer: !bot, hasAi: bot); character.TeamID = CharacterTeamType.Team1; + character.LoadTalents(); + + respawnedCharacters.Add(character); if (bot) { @@ -364,6 +391,12 @@ namespace Barotrauma.Networking } else { + if (GameMain.GameSession?.GameMode is MultiPlayerCampaign mpCampaign && character.Info != null) + { + character.Info.SetExperience(Math.Max(character.Info.ExperiencePoints, mpCampaign.GetSavedExperiencePoints(clients[i]))); + mpCampaign.ClearSavedExperiencePoints(clients[i]); + } + //tell the respawning client they're no longer a traitor if (GameMain.Server.TraitorManager?.Traitors != null && clients[i].Character != null) { @@ -455,6 +488,17 @@ namespace Barotrauma.Networking } } + public static void ReduceCharacterSkills(CharacterInfo characterInfo) + { + if (characterInfo?.Job == null) { return; } + foreach (Skill skill in characterInfo.Job.Skills) + { + var skillPrefab = characterInfo.Job.Prefab.Skills.Find(s => skill.Prefab == s); + if (skillPrefab == null) { continue; } + skill.Level = MathHelper.Lerp(skill.Level, skillPrefab.LevelRange.X, SkillReductionOnCampaignMidroundRespawn); + } + } + public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null) { msg.WriteRangedInteger((int)CurrentState, 0, Enum.GetNames(typeof(State)).Length); diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/ServerSettings.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/ServerSettings.cs index affe37a85..5da3ddd5f 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/ServerSettings.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/ServerSettings.cs @@ -163,9 +163,7 @@ namespace Barotrauma.Networking RadiationEnabled = incMsg.ReadBoolean(); int maxMissionCount = MaxMissionCount + incMsg.ReadByte() - 1; - if (maxMissionCount < CampaignSettings.MinMissionCountLimit) maxMissionCount = CampaignSettings.MaxMissionCountLimit; - if (maxMissionCount > CampaignSettings.MaxMissionCountLimit) maxMissionCount = CampaignSettings.MinMissionCountLimit; - MaxMissionCount = maxMissionCount; + MaxMissionCount = MathHelper.Clamp(maxMissionCount, CampaignSettings.MinMissionCountLimit, CampaignSettings.MaxMissionCountLimit); changed |= true; } @@ -267,7 +265,7 @@ namespace Barotrauma.Networking "192-255", "384-591", "1024-1279", - "19968-40959","13312-19903","131072-15043983","15043985-173791","173824-178207","178208-183983","63744-64255","194560-195103" //CJK + "19968-21327","21329-40959","13312-19903","131072-173791","173824-178207","178208-183983","63744-64255","194560-195103" //CJK }; string[] allowedClientNameCharsStr = doc.Root.GetAttributeStringArray("AllowedClientNameChars", defaultAllowedClientNameChars); diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/Voting.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/Voting.cs index acb320756..889f82de4 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/Voting.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/Voting.cs @@ -92,7 +92,7 @@ namespace Barotrauma case VoteType.Mode: string modeIdentifier = inc.ReadString(); GameModePreset mode = GameModePreset.List.Find(gm => gm.Identifier == modeIdentifier); - if (!mode.Votable) { break; } + if (mode == null || !mode.Votable) { break; } sender.SetVote(voteType, mode); break; case VoteType.EndRound: diff --git a/Barotrauma/BarotraumaServer/ServerSource/Traitors/Goals/GoalFindItem.cs b/Barotrauma/BarotraumaServer/ServerSource/Traitors/Goals/GoalFindItem.cs index 582a853fe..a95f200bc 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Traitors/Goals/GoalFindItem.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Traitors/Goals/GoalFindItem.cs @@ -185,7 +185,10 @@ namespace Barotrauma { existingItems.Add(item); } - Entity.Spawner.AddToSpawnQueue(targetPrefab, targetContainer.OwnInventory); + Entity.Spawner.AddToSpawnQueue(targetPrefab, targetContainer.OwnInventory, null, item => + { + item.AddTag("traitormissionitem"); + }); target = null; } else if (allowExisting) diff --git a/Barotrauma/BarotraumaServer/WindowsServer.csproj b/Barotrauma/BarotraumaServer/WindowsServer.csproj index 7e59b495e..575a80c8f 100644 --- a/Barotrauma/BarotraumaServer/WindowsServer.csproj +++ b/Barotrauma/BarotraumaServer/WindowsServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.14.9.1 + 0.15.12.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 DedicatedServer @@ -38,6 +38,7 @@ TRACE;SERVER;WINDOWS;USE_STEAM x64 ..\bin\$(Configuration)Windows\ + true @@ -54,6 +55,7 @@ ..\bin\$(Configuration)Windows\ full true + true diff --git a/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.9.xml b/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.9.xml index 19f21b7a6..b2bb40191 100644 --- a/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.9.xml +++ b/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.9.xml @@ -23,6 +23,7 @@ + @@ -63,6 +64,12 @@ + + + + + + @@ -73,6 +80,7 @@ + @@ -80,6 +88,8 @@ + + @@ -147,6 +157,13 @@ + + + + + + + @@ -155,6 +172,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -261,4 +305,11 @@ + + + + + + + \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AIController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AIController.cs index 4e3f6b91e..719417830 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AIController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AIController.cs @@ -1,7 +1,10 @@ -using Microsoft.Xna.Framework; +using Barotrauma.Extensions; +using Barotrauma.Items.Components; +using Barotrauma.Networking; +using FarseerPhysics; +using Microsoft.Xna.Framework; using System; using System.Collections.Generic; -using Barotrauma.Items.Components; using System.Linq; namespace Barotrauma @@ -94,14 +97,31 @@ namespace Barotrauma } } - protected bool HasValidPath(bool requireNonDirty = false) => - steeringManager is IndoorsSteeringManager pathSteering && pathSteering.CurrentPath != null && !pathSteering.CurrentPath.Finished && !pathSteering.CurrentPath.Unreachable && (!requireNonDirty || !pathSteering.IsPathDirty); + public bool HasValidPath(bool requireNonDirty = false, bool requireUnfinished = true) => + steeringManager is IndoorsSteeringManager pathSteering && + pathSteering.CurrentPath != null && + (!requireUnfinished || !pathSteering.CurrentPath.Finished) && + !pathSteering.CurrentPath.Unreachable && + (!requireNonDirty || !pathSteering.IsPathDirty); + + public bool IsCurrentPathUnreachable => steeringManager is IndoorsSteeringManager pathSteering && !pathSteering.IsPathDirty && pathSteering.CurrentPath != null && pathSteering.CurrentPath.Unreachable; + public bool IsCurrentPathFinished => steeringManager is IndoorsSteeringManager pathSteering && !pathSteering.IsPathDirty && pathSteering.CurrentPath != null && pathSteering.CurrentPath.Finished; + + protected readonly float colliderWidth; + protected readonly float minGapSize; + protected readonly float colliderLength; + protected readonly float avoidLookAheadDistance; public AIController (Character c) { Character = c; hullVisibilityTimer = Rand.Range(0f, hullVisibilityTimer); Enabled = true; + var size = Character.AnimController.Collider.GetSize(); + colliderWidth = size.X; + colliderLength = size.Y; + avoidLookAheadDistance = Math.Max(Math.Max(colliderWidth, colliderLength) * 3, 1.5f); + minGapSize = ConvertUnits.ToDisplayUnits(Math.Min(colliderWidth, colliderLength)); } public virtual void OnAttacked(Character attacker, AttackResult attackResult) { } @@ -326,7 +346,148 @@ namespace Barotrauma unequippedItems.Clear(); } + #region Escape + public abstract bool Escape(float deltaTime); + + public Gap EscapeTarget { get; private set; } + + private readonly float escapeTargetSeekInterval = 2; + private float escapeTimer; + protected bool allGapsSearched; + protected readonly HashSet unreachableGaps = new HashSet(); + protected bool UpdateEscape(float deltaTime, bool canAttackDoors) + { + IndoorsSteeringManager pathSteering = SteeringManager as IndoorsSteeringManager; + if (allGapsSearched) + { + escapeTimer -= deltaTime; + if (escapeTimer <= 0) + { + allGapsSearched = false; + } + } + if (Character.CurrentHull != null && pathSteering != null) + { + // Seek exit if inside + if (!allGapsSearched) + { + float closestDistance = 0; + foreach (Gap gap in Gap.GapList) + { + if (gap == null || gap.Removed) { continue; } + if (EscapeTarget == gap) { continue; } + if (unreachableGaps.Contains(gap)) { continue; } + if (gap.Submarine != Character.Submarine) { continue; } + if (gap.IsRoomToRoom) { continue; } + float multiplier = 1; + var door = gap.ConnectedDoor; + if (door != null) + { + if (!door.CanBeTraversed) + { + if (!door.HasAccess(Character)) + { + if (!canAttackDoors) { continue; } + // Treat doors that don't have access to like they were farther, because it will take time to break them. + multiplier = 5; + } + } + } + else + { + if (gap.Open < 1) { continue; } + if (gap.Size < minGapSize) { continue; } + } + if (gap.FlowTargetHull == Character.CurrentHull) + { + // If the gap is in the same room, it's close enough. + EscapeTarget = gap; + break; + } + float distance = Vector2.DistanceSquared(Character.WorldPosition, gap.WorldPosition) * multiplier; + if (EscapeTarget == null || distance < closestDistance) + { + EscapeTarget = gap; + closestDistance = distance; + } + } + allGapsSearched = true; + escapeTimer = escapeTargetSeekInterval; + } + else if (EscapeTarget != null && EscapeTarget.FlowTargetHull != Character.CurrentHull) + { + if (IsCurrentPathUnreachable) + { + unreachableGaps.Add(EscapeTarget); + EscapeTarget = null; + allGapsSearched = false; + } + } + } + if (EscapeTarget != null) + { + var door = EscapeTarget.ConnectedDoor; + bool isClosedDoor = door != null && !door.IsOpen; + Vector2 diff = EscapeTarget.WorldPosition - Character.WorldPosition; + float sqrDist = diff.LengthSquared(); + bool isClose = sqrDist < MathUtils.Pow2(100); + if (Character.CurrentHull == null || isClose && !isClosedDoor || pathSteering == null || IsCurrentPathUnreachable || IsCurrentPathFinished) + { + // Very close to the target, outside, or at the end of the path -> try to steer through the gap + SteeringManager.Reset(); + pathSteering?.ResetPath(); + Vector2 dir = Vector2.Normalize(diff); + if (Character.CurrentHull == null || isClose) + { + // Outside -> steer away from the target + if (EscapeTarget.FlowTargetHull != null) + { + SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(EscapeTarget.WorldPosition - EscapeTarget.FlowTargetHull.WorldPosition)); + } + else + { + SteeringManager.SteeringManual(deltaTime, -dir); + } + } + else + { + // Still inside -> steer towards the target + SteeringManager.SteeringManual(deltaTime, dir); + } + return sqrDist < MathUtils.Pow2(250); + } + else if (pathSteering != null) + { + pathSteering.SteeringSeek(EscapeTarget.SimPosition, weight: 1, minGapSize); + } + else + { + SteeringManager.SteeringSeek(EscapeTarget.SimPosition, 10); + } + } + else + { + // Can't find the target + EscapeTarget = null; + allGapsSearched = false; + unreachableGaps.Clear(); + } + return false; + } + + public void ResetEscape() + { + EscapeTarget = null; + allGapsSearched = false; + unreachableGaps.Clear(); + } + + #endregion + protected virtual void OnStateChanged(AIState from, AIState to) { } protected virtual void OnTargetChanged(AITarget previousTarget, AITarget newTarget) { } + + public virtual void ClientRead(IReadMessage msg) { } + public virtual void ServerWrite(IWriteMessage msg) { } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AITarget.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AITarget.cs index 7479ae3da..b105f540e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AITarget.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AITarget.cs @@ -92,7 +92,16 @@ namespace Barotrauma public string SonarLabel; public string SonarIconIdentifier; - public bool Enabled => SoundRange > 0 || SightRange > 0; + private bool inDetectable; + + /// + /// Should be reset to false each frame and kept indetectable by e.g. a status effect. + /// + public bool InDetectable + { + get => inDetectable || (SoundRange <= 0 && SightRange <= 0); + set => inDetectable = value; + } public float MinSoundRange, MinSightRange; public float MaxSoundRange = 100000, MaxSightRange = 100000; @@ -181,14 +190,15 @@ namespace Barotrauma public void Update(float deltaTime) { - if (Enabled && !Static && FadeOutTime > 0) + InDetectable = false; + if (!Static && FadeOutTime > 0) { // The aitarget goes silent/invisible if the components don't keep it active - if (!StaticSight) + if (!StaticSight && SightRange > 0) { DecreaseSightRange(deltaTime); } - if (!StaticSound) + if (!StaticSound && SoundRange > 0) { DecreaseSoundRange(deltaTime); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs index ba65f0c2c..440bf76ce 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs @@ -1,5 +1,6 @@ using Barotrauma.Extensions; using Barotrauma.Items.Components; +using Barotrauma.Networking; using FarseerPhysics; using FarseerPhysics.Dynamics; using Microsoft.Xna.Framework; @@ -10,7 +11,7 @@ using System.Linq; namespace Barotrauma { - public enum AIState { Idle, Attack, Escape, Eat, Flee, Avoid, Aggressive, PassiveAggressive, Protect, Observe, Freeze, Follow } + public enum AIState { Idle, Attack, Escape, Eat, Flee, Avoid, Aggressive, PassiveAggressive, Protect, Observe, Freeze, Follow, FleeTo, Patrol } public enum AttackPattern { Straight, Sweep, Circle } @@ -37,6 +38,12 @@ namespace Barotrauma PreviousState = _state; OnStateChanged(_state, value); _state = value; + if (_state == AIState.Attack) + { +#if CLIENT + Character.PlaySound(CharacterSound.SoundType.Attack, maxInterval: 3); +#endif + } } } @@ -50,8 +57,8 @@ namespace Barotrauma private readonly float updateTargetsInterval = 1; private readonly float updateMemoriesInverval = 1; private readonly float attackLimbResetInterval = 2; - - private readonly float avoidLookAheadDistance; + // Min priority for the memorized targets. The actual value fades gradually, unless kept fresh by selecting the target. + private const float minPriority = 10; private IndoorsSteeringManager PathSteering => insideSteering as IndoorsSteeringManager; private SteeringManager outsideSteering, insideSteering; @@ -82,6 +89,10 @@ namespace Barotrauma { _previousAttackingLimb = _attackingLimb; } + if (_attackingLimb != null && value != _attackingLimb && _attackingLimb.attack.CoolDownTimer > 0) + { + SetAimTimer(); + } _attackingLimb = value; attackVector = null; Reverse = _attackingLimb != null && _attackingLimb.attack.Reverse; @@ -104,20 +115,21 @@ namespace Barotrauma lastAttackUpdateTime = Timing.TotalTime; } } - + + public AITargetMemory SelectedTargetMemory => selectedTargetMemory; private AITargetMemory selectedTargetMemory; private float targetValue; private CharacterParams.TargetParams selectedTargetingParams; private Dictionary targetMemories; - private readonly float colliderWidth; - private readonly float colliderLength; private readonly int requiredHoleCount; private bool canAttackWalls; + public bool CanAttackDoors => canAttackDoors; private bool canAttackDoors; private bool canAttackCharacters; + public float PriorityFearIncrement => priorityFearIncreasement; private readonly float priorityFearIncreasement = 2; private readonly float memoryFadeTime = 0.5f; @@ -136,6 +148,8 @@ namespace Barotrauma private CirclePhase CirclePhase; private float currentAttackIntensity; + private CoroutineHandle disableTailCoroutine; + private readonly IEnumerable myBodies; public LatchOntoAI LatchOntoAI { get; private set; } @@ -167,7 +181,7 @@ namespace Barotrauma get { //can't enter a submarine when attached to something - return Character.AnimController.CanEnterSubmarine && (LatchOntoAI == null || !LatchOntoAI.IsAttached); + return Character.AnimController.CanEnterSubmarine && (LatchOntoAI == null || !LatchOntoAI.IsAttachedToSub); } } @@ -178,7 +192,7 @@ namespace Barotrauma //can't flip when attached to something, when eating, or reversing or in a (relatively) small room return !Reverse && (State != AIState.Eat || Character.SelectedCharacter == null) && - (LatchOntoAI == null || !LatchOntoAI.IsAttached) && + (LatchOntoAI == null || !LatchOntoAI.IsAttachedToSub) && (Character.CurrentHull == null || !Character.AnimController.InWater || Math.Min(Character.CurrentHull.Size.X, Character.CurrentHull.Size.Y) > ConvertUnits.ToDisplayUnits(Math.Max(colliderLength, colliderWidth))); } } @@ -286,16 +300,12 @@ namespace Barotrauma ReevaluateAttacks(); outsideSteering = new SteeringManager(this); - insideSteering = new IndoorsSteeringManager(this, Character.IsHumanoid, canAttackDoors); + insideSteering = new IndoorsSteeringManager(this, Character.Params.AI.CanOpenDoors, canAttackDoors); steeringManager = outsideSteering; State = AIState.Idle; - var size = Character.AnimController.Collider.GetSize(); - colliderWidth = size.X; - colliderLength = size.Y; requiredHoleCount = (int)Math.Ceiling(ConvertUnits.ToDisplayUnits(colliderWidth) / Structure.WallSectionSize); - avoidLookAheadDistance = Math.Max(Math.Max(colliderWidth, colliderLength) * 3, 1.5f); myBodies = Character.AnimController.Limbs.Select(l => l.body.FarseerBody); } @@ -394,18 +404,27 @@ namespace Barotrauma public void SelectTarget(AITarget target, float priority) { SelectedAiTarget = target; - selectedTargetMemory = GetTargetMemory(target, true); + selectedTargetMemory = GetTargetMemory(target, addIfNotFound: true); selectedTargetMemory.Priority = priority; ignoredTargets.Remove(target); } private float movementMargin; + private void ReleaseDragTargets() + { + if (Character.Inventory != null) + { + Character.HeldItems.ForEach(i => i.GetComponent()?.GetRope()?.Snap()); + } + } + public override void Update(float deltaTime) { if (DisableEnemyAI) { return; } base.Update(deltaTime); UpdateTriggers(deltaTime); + Character.ClearInputs(); bool ignorePlatforms = Character.AnimController.TargetMovement.Y < -0.5f && (-Character.AnimController.TargetMovement.Y > Math.Abs(Character.AnimController.TargetMovement.X)); if (steeringManager == insideSteering) @@ -431,10 +450,10 @@ namespace Barotrauma if (Math.Abs(Character.AnimController.movement.X) > 0.1f && !Character.AnimController.InWater && (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer || Character.Controlled == Character)) { - if (SelectedAiTarget?.Entity != null || escapeTarget != null) + if (SelectedAiTarget?.Entity != null || EscapeTarget != null) { - Entity t = SelectedAiTarget?.Entity ?? escapeTarget; - float referencePos = Vector2.DistanceSquared(Character.WorldPosition, t.WorldPosition) > 100 * 100 && HasValidPath(true) ? PathSteering.CurrentPath.CurrentNode.WorldPosition.X : t.WorldPosition.X; + Entity t = SelectedAiTarget?.Entity ?? EscapeTarget; + float referencePos = Vector2.DistanceSquared(Character.WorldPosition, t.WorldPosition) > 100 * 100 && HasValidPath(requireNonDirty: true) ? PathSteering.CurrentPath.CurrentNode.WorldPosition.X : t.WorldPosition.X; Character.AnimController.TargetDir = Character.WorldPosition.X < referencePos ? Direction.Right : Direction.Left; } else @@ -442,10 +461,9 @@ namespace Barotrauma Character.AnimController.TargetDir = Character.AnimController.movement.X > 0.0f ? Direction.Right : Direction.Left; } } - if (isStateChanged) { - if (State == AIState.Idle) + if (State == AIState.Idle || State == AIState.Patrol) { stateResetTimer -= deltaTime; if (stateResetTimer <= 0) @@ -505,7 +523,9 @@ namespace Barotrauma selectedTargetingParams = targetingParams; State = targetingParams.State; } - if (SelectedAiTarget?.Entity != null && !IsLatchedOnSub && State == AIState.Attack || State == AIState.Aggressive || State == AIState.PassiveAggressive) + if (SelectedAiTarget?.Entity != null && + (LatchOntoAI == null || !LatchOntoAI.IsAttached || wallTarget != null) && + (State == AIState.Attack || State == AIState.Aggressive || State == AIState.PassiveAggressive)) { UpdateWallTarget(requiredHoleCount); } @@ -538,7 +558,7 @@ namespace Barotrauma } else { - if (Character.Submarine != null) + if (Character.Submarine != null && Character.Params.UsePathFinding) { if (steeringManager != insideSteering) { @@ -566,6 +586,9 @@ namespace Barotrauma case AIState.Idle: UpdateIdle(deltaTime); break; + case AIState.Patrol: + UpdatePatrol(deltaTime); + break; case AIState.Attack: run = !IsCoolDownRunning || AttackingLimb != null && AttackingLimb.attack.FullSpeedAfterAttack; UpdateAttack(deltaTime); @@ -576,7 +599,7 @@ namespace Barotrauma case AIState.Escape: case AIState.Flee: run = true; - UpdateEscape(deltaTime); + Escape(deltaTime); break; case AIState.Avoid: case AIState.PassiveAggressive: @@ -593,7 +616,7 @@ namespace Barotrauma run = true; if (State == AIState.Avoid) { - UpdateEscape(deltaTime); + Escape(deltaTime); } else { @@ -628,6 +651,7 @@ namespace Barotrauma break; case AIState.Protect: case AIState.Follow: + case AIState.FleeTo: if (SelectedAiTarget == null || SelectedAiTarget.Entity == null || SelectedAiTarget.Entity.Removed) { State = AIState.Idle; @@ -641,9 +665,9 @@ namespace Barotrauma { Character c = a.Character; if (c.IsDead || c.Removed) { return false; } - if (!IsFriendly(Character, c)) { return true; } + if (!Character.IsFriendly(c)) { return true; } // Only apply the threshold to friendly characters - return a.Damage >= selectedTargetingParams.DamageThreshold; + return a.Damage >= selectedTargetingParams.Threshold; } Character attacker = targetCharacter.LastAttackers.LastOrDefault(IsValid)?.Character; if (attacker != null) @@ -656,17 +680,75 @@ namespace Barotrauma } } float sqrDist = Vector2.DistanceSquared(WorldPosition, SelectedAiTarget.WorldPosition); - float reactDist = selectedTargetingParams != null && selectedTargetingParams.ReactDistance > 0 ? selectedTargetingParams.ReactDistance : GetPerceivingRange(SelectedAiTarget); - if (sqrDist > Math.Pow(reactDist + movementMargin, 2)) + float reactDist = GetPerceivingRange(SelectedAiTarget); + Vector2 offset = Vector2.Zero; + if (selectedTargetingParams != null) { - movementMargin = reactDist; + if (selectedTargetingParams.ReactDistance > 0) + { + reactDist = selectedTargetingParams.ReactDistance; + } + offset = selectedTargetingParams.Offset; + } + if (offset != Vector2.Zero) + { + reactDist += offset.Length(); + } + if (sqrDist > MathUtils.Pow2(reactDist + movementMargin)) + { + movementMargin = State == AIState.FleeTo ? 0 : reactDist; run = true; UpdateFollow(deltaTime); } else { movementMargin = MathHelper.Clamp(movementMargin -= deltaTime, 0, reactDist); - UpdateIdle(deltaTime); + if (State == AIState.FleeTo) + { + SteeringManager.Reset(); + Character.AnimController.TargetMovement = Vector2.Zero; + if (Character.AnimController.InWater) + { + float force = Character.AnimController.Collider.Mass / 10; + Character.AnimController.Collider.MoveToPos(SelectedAiTarget.Entity.SimPosition + ConvertUnits.ToSimUnits(offset), force); + if (SelectedAiTarget.Entity is Item item) + { + float rotation = item.Rotation; + Character.AnimController.Collider.SmoothRotate(rotation, Character.AnimController.SwimFastParams.SteerTorque); + var mainLimb = Character.AnimController.MainLimb; + if (mainLimb.type == LimbType.Head) + { + mainLimb.body.SmoothRotate(rotation, Character.AnimController.SwimFastParams.HeadTorque); + } + else + { + mainLimb.body.SmoothRotate(rotation, Character.AnimController.SwimFastParams.TorsoTorque); + } + } + if (disableTailCoroutine == null && SelectedAiTarget.Entity is Item i && i.HasTag("guardianshelter")) + { + if (!CoroutineManager.IsCoroutineRunning(disableTailCoroutine)) + { + disableTailCoroutine = CoroutineManager.Invoke(() => + { + if (Character != null && !Character.Removed) + { + Character.AnimController.HideAndDisable(LimbType.Tail, ignoreCollisions: false); + } + }, 1f); + } + } + Character.AnimController.ApplyPose( + new Vector2(0, -1), + new Vector2(0, -1), + new Vector2(0, -1), + new Vector2(0, -1), footMoveForce: 1); + } + } + else + { + UpdateIdle(deltaTime); + } } break; case AIState.Observe: @@ -759,6 +841,10 @@ namespace Barotrauma private void UpdateIdle(float deltaTime, bool followLastTarget = true) { + if (AIParams.PatrolFlooded || AIParams.PatrolDry) + { + State = AIState.Patrol; + } var pathSteering = SteeringManager as IndoorsSteeringManager; if (pathSteering == null) { @@ -816,138 +902,129 @@ namespace Barotrauma } } - #endregion + private readonly List targetHulls = new List(); + private readonly List hullWeights = new List(); - #region Escape - private readonly float escapeTargetSeekInterval = 2; - private float escapeTimer; - private Gap escapeTarget; - private bool allGapsSearched; - private readonly HashSet unreachableGaps = new HashSet(); - private void UpdateEscape(float deltaTime) + private Hull patrolTarget; + private float newPatrolTargetTimer; + private float patrolTimerMargin; + private readonly float newPatrolTargetIntervalMin = 5; + private readonly float newPatrolTargetIntervalMax = 30; + private bool searchingNewHull; + + private void UpdatePatrol(float deltaTime, bool followLastTarget = true) { - if (SelectedAiTarget != null && (SelectedAiTarget.Entity == null || SelectedAiTarget.Entity.Removed)) + if (SteeringManager is IndoorsSteeringManager pathSteering) { - State = AIState.Idle; - return; - } - else if (selectedTargetMemory != null && SelectedAiTarget?.Entity is Character) - { - selectedTargetMemory.Priority += deltaTime * priorityFearIncreasement; - } - IndoorsSteeringManager pathSteering = SteeringManager as IndoorsSteeringManager; - bool hasValidPath = pathSteering?.CurrentPath != null && !pathSteering.IsPathDirty && !pathSteering.CurrentPath.Unreachable; - if (allGapsSearched) - { - escapeTimer -= deltaTime; - if (escapeTimer <= 0) + if (patrolTarget == null || IsCurrentPathUnreachable || IsCurrentPathFinished) { - allGapsSearched = false; + newPatrolTargetTimer = Math.Min(newPatrolTargetTimer, newPatrolTargetIntervalMin); } - } - if (Character.CurrentHull != null && pathSteering != null) - { - // Seek exit if inside - if (!allGapsSearched) + if (newPatrolTargetTimer > 0) { - float closestDistance = 0; - foreach (Gap gap in Gap.GapList) + newPatrolTargetTimer -= deltaTime; + } + else + { + if (!searchingNewHull) { - if (gap == null || gap.Removed) { continue; } - if (escapeTarget == gap) { continue; } - if (unreachableGaps.Contains(gap)) { continue; } - if (gap.Submarine != Character.Submarine) { continue; } - if (gap.IsRoomToRoom) { continue; } - float multiplier = 1; - var door = gap.ConnectedDoor; - if (door != null) + searchingNewHull = true; + FindTargetHulls(); + } + else if (targetHulls.Any()) + { + patrolTarget = ToolBox.SelectWeightedRandom(targetHulls, hullWeights, Rand.RandSync.Unsynced); + var path = PathSteering.PathFinder.FindPath(Character.SimPosition, patrolTarget.SimPosition, Character.Submarine, minGapSize: minGapSize * 1.5f, nodeFilter: n => PatrolNodeFilter(n)); + if (path.Unreachable) { - if (!door.CanBeTraversed) - { - if (!door.HasAccess(Character)) - { - if (!canAttackDoors) { continue; } - // Treat doors that don't have access to like they were farther, because it will take time to break them. - multiplier = 5; - } - } + //can't go to this room, remove it from the list and try another room + int index = targetHulls.IndexOf(patrolTarget); + targetHulls.RemoveAt(index); + hullWeights.RemoveAt(index); + PathSteering.Reset(); + patrolTarget = null; + patrolTimerMargin += 0.5f; + patrolTimerMargin = Math.Min(patrolTimerMargin, newPatrolTargetIntervalMin); + newPatrolTargetTimer = Math.Min(newPatrolTargetIntervalMin, patrolTimerMargin); } else { - if (gap.Open < 1) { continue; } - bool canGetThrough = ConvertUnits.ToDisplayUnits(colliderWidth) < gap.Size; - if (!canGetThrough) { continue; } - } - if (gap.FlowTargetHull == Character.CurrentHull) - { - // If the gap is in the same room, it's close enough. - escapeTarget = gap; - break; - } - float distance = Vector2.DistanceSquared(Character.WorldPosition, gap.WorldPosition) * multiplier; - if (escapeTarget == null || distance < closestDistance) - { - escapeTarget = gap; - closestDistance = distance; + PathSteering.SetPath(path); + patrolTimerMargin = 0; + newPatrolTargetTimer = newPatrolTargetIntervalMax * Rand.Range(0.5f, 1.5f); + searchingNewHull = false; } } - allGapsSearched = true; - escapeTimer = escapeTargetSeekInterval; - } - else if (escapeTarget != null && escapeTarget.FlowTargetHull != Character.CurrentHull) - { - if (pathSteering.CurrentPath != null && !pathSteering.IsPathDirty && pathSteering.CurrentPath.Unreachable) + else { - unreachableGaps.Add(escapeTarget); - escapeTarget = null; - allGapsSearched = false; + // Couldn't find a valid hull + newPatrolTargetTimer = newPatrolTargetIntervalMax; + searchingNewHull = false; } } - } - if (escapeTarget != null && Character.CurrentHull != null && Vector2.DistanceSquared(Character.SimPosition, escapeTarget.SimPosition) > 0.5f) - { - if (hasValidPath && pathSteering.CurrentPath.Finished) + if (patrolTarget != null && pathSteering.CurrentPath != null && !pathSteering.CurrentPath.Finished && !pathSteering.CurrentPath.Unreachable) { - // Steer manually towards the gap - SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(escapeTarget.WorldPosition - Character.WorldPosition)); - } - else if (SelectedAiTarget?.Entity is Character targetCharacter && targetCharacter.CurrentHull == Character.CurrentHull) - { - // Steer away from the target if in the same room - Vector2 escapeDir = Vector2.Normalize(SelectedAiTarget != null ? WorldPosition - SelectedAiTarget.WorldPosition : Character.AnimController.TargetMovement); - if (!MathUtils.IsValid(escapeDir)) { escapeDir = Vector2.UnitY; } - SteeringManager.SteeringManual(deltaTime, escapeDir); + PathSteering.SteeringSeek(Character.GetRelativeSimPosition(patrolTarget), weight: 1, minGapWidth: minGapSize * 1.5f, nodeFilter: n => PatrolNodeFilter(n)); return; } - else if (pathSteering != null) + } + + bool PatrolNodeFilter(PathNode n) => + AIParams.PatrolFlooded && (Character.CurrentHull == null || n.Waypoint.CurrentHull == null || n.Waypoint.CurrentHull.WaterPercentage >= 80) || + AIParams.PatrolDry && Character.CurrentHull != null && n.Waypoint.CurrentHull != null && n.Waypoint.CurrentHull.WaterPercentage <= 50; + + UpdateIdle(deltaTime, followLastTarget); + } + + private void FindTargetHulls() + { + if (Character.Submarine == null) { return; } + if (Character.CurrentHull == null) { return; } + targetHulls.Clear(); + hullWeights.Clear(); + float hullMinSize = ConvertUnits.ToDisplayUnits(Math.Max(colliderLength, colliderWidth) * 2); + bool checkWaterLevel = !AIParams.PatrolFlooded || !AIParams.PatrolDry; + foreach (var hull in Hull.hullList) + { + if (hull.Submarine == null) { continue; } + if (hull.Submarine.TeamID != Character.Submarine.TeamID) { continue; } + if (!Character.Submarine.IsConnectedTo(hull.Submarine)) { continue; } + if (hull.RectWidth < hullMinSize || hull.RectHeight < hullMinSize) { continue; } + if (checkWaterLevel) { - if (hasValidPath && canAttackDoors) + if (AIParams.PatrolDry) { - var door = pathSteering.CurrentPath.CurrentNode?.ConnectedDoor ?? pathSteering.CurrentPath.NextNode?.ConnectedDoor; - if (door != null && !door.CanBeTraversed && !door.HasAccess(Character)) - { - if (SelectedAiTarget != door.Item.AiTarget || State != AIState.Attack) - { - SelectTarget(door.Item.AiTarget, selectedTargetMemory.Priority); - State = AIState.Attack; - return; - } - } + if (hull.WaterPercentage > 50) { continue; } + } + if (AIParams.PatrolFlooded) + { + if (hull.WaterPercentage < 80) { continue; } } } - SteeringManager.SteeringSeek(escapeTarget.SimPosition, 10); - } - else - { - escapeTarget = null; - allGapsSearched = false; - Vector2 escapeDir = Vector2.Normalize(SelectedAiTarget != null ? WorldPosition - SelectedAiTarget.WorldPosition : Character.AnimController.TargetMovement); - if (!MathUtils.IsValid(escapeDir)) escapeDir = Vector2.UnitY; - SteeringManager.SteeringManual(deltaTime, escapeDir); - if (Character.CurrentHull == null) + if (AIParams.PatrolDry && hull.WaterPercentage < 80) { - SteeringManager.SteeringWander(); - SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: avoidLookAheadDistance, weight: 5); + if (Math.Abs(Character.CurrentHull.WorldPosition.Y - hull.WorldPosition.Y) > Character.CurrentHull.CeilingHeight / 2) + { + // Ignore dry hulls that are on a different level + continue; + } + } + if (!targetHulls.Contains(hull)) + { + targetHulls.Add(hull); + float weight = hull.Size.Combine(); + float dist = Vector2.Distance(Character.WorldPosition, hull.WorldPosition); + float optimal = 1000; + float max = 3000; + // Prefer rooms that are far but not too far. + float distanceFactor = dist > optimal ? MathHelper.Lerp(1, 0, MathUtils.InverseLerp(optimal, max, dist)) : MathHelper.Lerp(0, 1, MathUtils.InverseLerp(0, optimal, dist)); + float waterFactor = 1; + if (checkWaterLevel) + { + waterFactor = AIParams.PatrolDry ? MathHelper.Lerp(1, 0, MathUtils.InverseLerp(0, 100, hull.WaterPercentage)) : MathHelper.Lerp(0, 1, MathUtils.InverseLerp(0, 100, hull.WaterPercentage)); + } + weight *= distanceFactor * waterFactor; + hullWeights.Add(weight); } } } @@ -976,7 +1053,7 @@ namespace Barotrauma Character owner = GetOwner(item); if (owner != null) { - if (IsFriendly(Character, owner)) + if (Character.IsFriendly(owner)) { ResetAITarget(); State = AIState.Idle; @@ -1210,7 +1287,7 @@ namespace Barotrauma if (canAttack) { - if (AttackingLimb == null || _previousAiTarget != SelectedAiTarget) + if (AttackingLimb == null || !IsValidAttack(AttackingLimb, Character.GetAttackContexts(), SelectedAiTarget?.Entity as IDamageable)) { AttackingLimb = GetAttackLimb(attackWorldPos); } @@ -1337,7 +1414,7 @@ namespace Barotrauma } else { - canAttack = Character.CharacterList.All(c => c == Character || !IsFriendly(Character, c) || IsFarEnough(c)); + canAttack = Character.CharacterList.All(c => c == Character || !Character.IsFriendly(c) || IsFarEnough(c)); } if (canAttack) { @@ -1356,7 +1433,7 @@ namespace Barotrauma { hitTarget = limb.character; } - if (hitTarget != null && !hitTarget.IsDead && IsFriendly(Character, hitTarget)) + if (hitTarget != null && !hitTarget.IsDead && Character.IsFriendly(hitTarget)) { return true; } @@ -1394,6 +1471,8 @@ namespace Barotrauma State = AIState.Idle; return; } + + var pathSteering = SteeringManager as IndoorsSteeringManager; if (AttackingLimb != null && AttackingLimb.attack.Retreat) { @@ -1408,7 +1487,7 @@ namespace Barotrauma Vector2 offset = Character.SimPosition - steeringLimb.SimPosition; steerPos += offset; } - if (SteeringManager is IndoorsSteeringManager pathSteering) + if (pathSteering != null) { if (pathSteering.CurrentPath != null) { @@ -1430,244 +1509,298 @@ namespace Barotrauma } } } - // Steer towards the target if in the same room and swimming - if (Character.CurrentHull != null && ((Character.AnimController.InWater || pursue || !Character.AnimController.CanWalk) && - (targetCharacter != null && VisibleHulls.Contains(targetCharacter.CurrentHull)))) + // When pursuing, we don't want to pursue too close + float max = 300; + float margin = AttackingLimb != null ? Math.Min(AttackingLimb.attack.Range * 0.9f, max) : max; + if (!canAttack || distance > margin) { - Vector2 myPos = Character.AnimController.SimplePhysicsEnabled ? Character.SimPosition : steeringLimb.SimPosition; - SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(steerPos - myPos)); + // Steer towards the target if in the same room and swimming + // Ruins have walls/pillars inside hulls and therefore we should navigate around them using the path steering. + if (Character.CurrentHull != null && + Character.Submarine != null && !Character.Submarine.Info.IsRuin && + (Character.AnimController.InWater || pursue || !Character.AnimController.CanWalk) && + targetCharacter != null && VisibleHulls.Contains(targetCharacter.CurrentHull)) + { + Vector2 myPos = Character.AnimController.SimplePhysicsEnabled ? Character.SimPosition : steeringLimb.SimPosition; + SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(steerPos - myPos)); + } + else + { + pathSteering.SteeringSeek(steerPos, weight: 2, + minGapWidth: minGapSize, + startNodeFilter: n => (n.Waypoint.CurrentHull == null) == (Character.CurrentHull == null), + checkVisiblity: true); + + if (!pathSteering.IsPathDirty && pathSteering.CurrentPath.Unreachable) + { + State = AIState.Idle; + IgnoreTarget(SelectedAiTarget); + ResetAITarget(); + return; + } + } } else { - pathSteering.SteeringSeek(steerPos, 2, startNodeFilter: n => (n.Waypoint.CurrentHull == null) == (Character.CurrentHull == null), checkVisiblity: true); - if (!pathSteering.IsPathDirty && pathSteering.CurrentPath.Unreachable) + if (AttackingLimb.attack.Ranged) { - State = AIState.Idle; - IgnoreTarget(SelectedAiTarget); - ResetAITarget(); - return; + float dir = Character.AnimController.Dir; + if (dir > 0 && attackWorldPos.X > AttackingLimb.WorldPosition.X + margin || dir < 0 && attackWorldPos.X < AttackingLimb.WorldPosition.X - margin) + { + SteeringManager.Reset(); + } + else + { + // Too close + UpdateFallBack(attackWorldPos, deltaTime, followThrough: false); + } + } + else + { + // Close enough + SteeringManager.Reset(); } } } else { - SteeringManager.SteeringSeek(steerPos, 5); + pathSteering.SteeringSeek(steerPos, weight: 5, minGapWidth: minGapSize); } } else { - switch (selectedTargetingParams.AttackPattern) + // Sweeping and circling doesn't work well inside + if (Character.CurrentHull == null) { - case AttackPattern.Sweep: - if (selectedTargetingParams.SweepDistance > 0) - { - if (distance <= 0) + switch (selectedTargetingParams.AttackPattern) + { + case AttackPattern.Sweep: + if (selectedTargetingParams.SweepDistance > 0) { - distance = (attackWorldPos - WorldPosition).Length(); - } - float amplitude = MathHelper.Lerp(0, selectedTargetingParams.SweepStrength, MathUtils.InverseLerp(selectedTargetingParams.SweepDistance, 0, distance)); - if (amplitude > 0) - { - sweepTimer += deltaTime * selectedTargetingParams.SweepSpeed; - float sin = (float)Math.Sin(sweepTimer) * amplitude; - steerPos = MathUtils.RotatePointAroundTarget(attackSimPos, SimPosition, sin); - } - else - { - sweepTimer = Rand.Range(-1000, 1000) * selectedTargetingParams.SweepSpeed; - } - } - break; - case AttackPattern.Circle: - if (IsCoolDownRunning) { break; } - if (IsAttackRunning && CirclePhase != CirclePhase.Strike) { break; } - if (selectedTargetingParams == null) { break; } - var targetSub = SelectedAiTarget.Entity?.Submarine; - if (targetSub == null) { break; } - float subSize = Math.Max(targetSub.Borders.Width, targetSub.Borders.Height) / 2; - float sqrDistToSub = Vector2.DistanceSquared(WorldPosition, targetSub.WorldPosition); - switch (CirclePhase) - { - case CirclePhase.Start: - currentAttackIntensity = MathUtils.InverseLerp(AIParams.StartAggression, AIParams.MaxAggression, aggressionIntensity * Rand.Range(0.9f, 1.1f)); - inverseDir = false; - circleDir = GetDirFromHeadingInRadius(); - circleRotation = 0; - strikeTimer = 0; - blockCheckTimer = 0; - breakCircling = false; - float minRotationSpeed = 0.01f * selectedTargetingParams.CircleRotationSpeed; - float maxRotationSpeed = 0.5f * selectedTargetingParams.CircleRotationSpeed; - float minFallBackDistance = selectedTargetingParams.CircleStartDistance * 0.5f; - float maxFallBackDistance = selectedTargetingParams.CircleStartDistance; - // The lower the rotation speed, the slower the progression. Also the distance to the target stays longer. - // So basically if the value is higher, the creature will strike the sub more quickly and with more precision. - circleRotationSpeed = MathHelper.Lerp(minRotationSpeed, maxRotationSpeed, currentAttackIntensity * Rand.Range(0.9f, 1.1f)); - circleFallbackDistance = MathHelper.Lerp(maxFallBackDistance, minFallBackDistance, currentAttackIntensity * Rand.Range(0.9f, 1.1f)); - circleOffset = Rand.Vector(MathHelper.Lerp(selectedTargetingParams.CircleMaxRandomOffset, 0, currentAttackIntensity * Rand.Range(0.9f, 1.1f))); - canAttack = false; - aggressionIntensity = Math.Clamp(aggressionIntensity, AIParams.StartAggression, AIParams.MaxAggression); - if (targetSub.Borders.Width < 1000) + if (distance <= 0) { - breakCircling = true; - CirclePhase = CirclePhase.CloseIn; + distance = (attackWorldPos - WorldPosition).Length(); } - else if (sqrDistToSub > MathUtils.Pow2(subSize + selectedTargetingParams.CircleStartDistance)) + float amplitude = MathHelper.Lerp(0, selectedTargetingParams.SweepStrength, MathUtils.InverseLerp(selectedTargetingParams.SweepDistance, 0, distance)); + if (amplitude > 0) { - CirclePhase = CirclePhase.CloseIn; - } - else if (sqrDistToSub < MathUtils.Pow2(subSize + circleFallbackDistance)) - { - CirclePhase = CirclePhase.FallBack; + sweepTimer += deltaTime * selectedTargetingParams.SweepSpeed; + float sin = (float)Math.Sin(sweepTimer) * amplitude; + steerPos = MathUtils.RotatePointAroundTarget(attackSimPos, SimPosition, sin); } else { - CirclePhase = CirclePhase.Advance; + sweepTimer = Rand.Range(-1000, 1000) * selectedTargetingParams.SweepSpeed; } - break; - case CirclePhase.CloseIn: - if (AttackingLimb != null && distance > 0 && distance < AttackingLimb.attack.Range * GetStrikeDistanceMultiplier(targetSub.Velocity)) - { - strikeTimer = AttackingLimb.attack.CoolDown; - CirclePhase = CirclePhase.Strike; - } - else if (!breakCircling && sqrDistToSub <= MathUtils.Pow2(subSize + selectedTargetingParams.CircleStartDistance / 2) && targetSub.Velocity.LengthSquared() <= MathUtils.Pow2(GetTargetMaxSpeed())) - { - CirclePhase = CirclePhase.Advance; - } - canAttack = false; - break; - case CirclePhase.FallBack: - bool isBlocked = !UpdateFallBack(attackWorldPos, deltaTime, followThrough: false, checkBlocking: true); - if (isBlocked || sqrDistToSub > MathUtils.Pow2(subSize + circleFallbackDistance)) - { - CirclePhase = CirclePhase.Advance; + } + break; + case AttackPattern.Circle: + if (IsCoolDownRunning) { break; } + if (IsAttackRunning && CirclePhase != CirclePhase.Strike) { break; } + if (selectedTargetingParams == null) { break; } + var targetSub = SelectedAiTarget.Entity?.Submarine; + if (targetSub == null) { break; } + float subSize = Math.Max(targetSub.Borders.Width, targetSub.Borders.Height) / 2; + float sqrDistToSub = Vector2.DistanceSquared(WorldPosition, targetSub.WorldPosition); + switch (CirclePhase) + { + case CirclePhase.Start: + currentAttackIntensity = MathUtils.InverseLerp(AIParams.StartAggression, AIParams.MaxAggression, aggressionIntensity * Rand.Range(0.9f, 1.1f)); + inverseDir = false; + circleDir = GetDirFromHeadingInRadius(); + circleRotation = 0; + strikeTimer = 0; + blockCheckTimer = 0; + breakCircling = false; + float minRotationSpeed = 0.01f * selectedTargetingParams.CircleRotationSpeed; + float maxRotationSpeed = 0.5f * selectedTargetingParams.CircleRotationSpeed; + float minFallBackDistance = selectedTargetingParams.CircleStartDistance * 0.5f; + float maxFallBackDistance = selectedTargetingParams.CircleStartDistance; + // The lower the rotation speed, the slower the progression. Also the distance to the target stays longer. + // So basically if the value is higher, the creature will strike the sub more quickly and with more precision. + circleRotationSpeed = MathHelper.Lerp(minRotationSpeed, maxRotationSpeed, currentAttackIntensity * Rand.Range(0.9f, 1.1f)); + circleFallbackDistance = MathHelper.Lerp(maxFallBackDistance, minFallBackDistance, currentAttackIntensity * Rand.Range(0.9f, 1.1f)); + circleOffset = Rand.Vector(MathHelper.Lerp(selectedTargetingParams.CircleMaxRandomOffset, 0, currentAttackIntensity * Rand.Range(0.9f, 1.1f))); + canAttack = false; + aggressionIntensity = Math.Clamp(aggressionIntensity, AIParams.StartAggression, AIParams.MaxAggression); + if (targetSub.Borders.Width < 1000) + { + breakCircling = true; + CirclePhase = CirclePhase.CloseIn; + } + else if (sqrDistToSub > MathUtils.Pow2(subSize + selectedTargetingParams.CircleStartDistance)) + { + CirclePhase = CirclePhase.CloseIn; + } + else if (sqrDistToSub < MathUtils.Pow2(subSize + circleFallbackDistance)) + { + CirclePhase = CirclePhase.FallBack; + } + else + { + CirclePhase = CirclePhase.Advance; + } break; - } - return; - case CirclePhase.Advance: - Vector2 subSpeed = targetSub.Velocity; - float requiredDistMultiplier = 1; - // If the target sub is moving fast, just steer towards the target until close enough to strike - if (breakCircling || subSpeed.LengthSquared() > MathUtils.Pow2(GetTargetMaxSpeed()) || sqrDistToSub > MathUtils.Pow2(subSize + selectedTargetingParams.CircleStartDistance * 1.2f)) - { - CirclePhase = CirclePhase.CloseIn; - } - else - { - circleRotation += deltaTime * circleRotationSpeed * circleDir; - if (circleRotation < -360) + case CirclePhase.CloseIn: + if (AttackingLimb != null && distance > 0 && distance < AttackingLimb.attack.Range * GetStrikeDistanceMultiplier(targetSub.Velocity)) { - circleRotation += 360; + strikeTimer = AttackingLimb.attack.CoolDown; + CirclePhase = CirclePhase.Strike; } - else if (circleRotation > 360) + else if (!breakCircling && sqrDistToSub <= MathUtils.Pow2(subSize + selectedTargetingParams.CircleStartDistance / 2) && targetSub.Velocity.LengthSquared() <= MathUtils.Pow2(GetTargetMaxSpeed())) { - circleRotation -= 360; + CirclePhase = CirclePhase.Advance; } - Vector2 targetPos = attackSimPos + circleOffset; - if (Vector2.DistanceSquared(SimPosition, targetPos) < 100) + canAttack = false; + break; + case CirclePhase.FallBack: + bool isBlocked = !UpdateFallBack(attackWorldPos, deltaTime, followThrough: false, checkBlocking: true); + if (isBlocked || sqrDistToSub > MathUtils.Pow2(subSize + circleFallbackDistance)) { - // Too close to the target point - // When the offset position is outside of the sub it happens that the creature sometimes reaches the target point, - // which makes it continue circling around the point (as supposed) - // But when there is some offset and the offset is too near, this is not what we want. - if (AttackingLimb != null && sqrDistToSub < MathUtils.Pow2(subSize + circleFallbackDistance)) - { - CirclePhase = CirclePhase.Strike; - strikeTimer = AttackingLimb.attack.CoolDown; - } - else - { - CirclePhase = CirclePhase.Start; - } + CirclePhase = CirclePhase.Advance; break; } - steerPos = MathUtils.RotatePointAroundTarget(SimPosition, targetPos, circleRotation); - requiredDistMultiplier = GetStrikeDistanceMultiplier(subSpeed); - if (IsBlocked(deltaTime, steerPos)) + return; + case CirclePhase.Advance: + Vector2 subSpeed = targetSub.Velocity; + float requiredDistMultiplier = 1; + // If the target sub is moving fast, just steer towards the target until close enough to strike + if (breakCircling || subSpeed.LengthSquared() > MathUtils.Pow2(GetTargetMaxSpeed()) || sqrDistToSub > MathUtils.Pow2(subSize + selectedTargetingParams.CircleStartDistance * 1.2f)) { - if (!inverseDir) + CirclePhase = CirclePhase.CloseIn; + } + else + { + circleRotation += deltaTime * circleRotationSpeed * circleDir; + if (circleRotation < -360) { - // First try changing the direction - circleDir = -circleDir; - inverseDir = true; + circleRotation += 360; } - else if (circleRotationSpeed < 1) + else if (circleRotation > 360) { - // Then try increasing the rotation speed to change the movement curve - circleRotationSpeed *= 1.1f; + circleRotation -= 360; } - else if (circleOffset.LengthSquared() > 0.1f) + Vector2 targetPos = attackSimPos + circleOffset; + if (Vector2.DistanceSquared(SimPosition, targetPos) < 100) { - // Then try removing the offset - circleOffset = Vector2.Zero; + // Too close to the target point + // When the offset position is outside of the sub it happens that the creature sometimes reaches the target point, + // which makes it continue circling around the point (as supposed) + // But when there is some offset and the offset is too near, this is not what we want. + if (AttackingLimb != null && sqrDistToSub < MathUtils.Pow2(subSize + circleFallbackDistance)) + { + CirclePhase = CirclePhase.Strike; + strikeTimer = AttackingLimb.attack.CoolDown; + } + else + { + CirclePhase = CirclePhase.Start; + } + break; } - else + steerPos = MathUtils.RotatePointAroundTarget(SimPosition, targetPos, circleRotation); + requiredDistMultiplier = GetStrikeDistanceMultiplier(subSpeed); + if (IsBlocked(deltaTime, steerPos)) { - // If we still fail, just steer towards the target - breakCircling = true; + if (!inverseDir) + { + // First try changing the direction + circleDir = -circleDir; + inverseDir = true; + } + else if (circleRotationSpeed < 1) + { + // Then try increasing the rotation speed to change the movement curve + circleRotationSpeed *= 1.1f; + } + else if (circleOffset.LengthSquared() > 0.1f) + { + // Then try removing the offset + circleOffset = Vector2.Zero; + } + else + { + // If we still fail, just steer towards the target + breakCircling = true; + } } } - } - if (AttackingLimb != null && distance > 0 && distance < AttackingLimb.attack.Range * requiredDistMultiplier && IsFacing(margin: MathHelper.Lerp(0.5f, 0.9f, currentAttackIntensity))) - { - strikeTimer = AttackingLimb.attack.CoolDown; - CirclePhase = CirclePhase.Strike; - } - canAttack = false; - break; - case CirclePhase.Strike: - strikeTimer -= deltaTime; - // just continue the movement forward to make it possible to evade the attack - steerPos = SimPosition + Steering; - if (strikeTimer <= 0) - { - CirclePhase = CirclePhase.Start; - aggressionIntensity += AIParams.AggressionCumulation; - } - break; - } - break; - - bool IsFacing(float margin) - { - float offset = steeringLimb.Params.GetSpriteOrientation() - MathHelper.PiOver2; - Vector2 forward = VectorExtensions.Forward(steeringLimb.body.TransformedRotation - offset * Character.AnimController.Dir); - return Vector2.Dot(Vector2.Normalize(attackWorldPos - WorldPosition), forward) > margin; - } - - float GetStrikeDistanceMultiplier(Vector2 subSpeed) - { - float requiredDistMultiplier = 2; - bool isHeading = Steering != null && Vector2.Dot(Vector2.Normalize(attackWorldPos - WorldPosition), Vector2.Normalize(Steering)) > 0.9f; - if (isHeading) - { - requiredDistMultiplier = selectedTargetingParams.CircleStrikeDistanceMultiplier; - float subSpeedHorizontal = Math.Abs(subSpeed.X); - if (subSpeedHorizontal > 1) - { - // Reduce the required distance if the target is moving. - requiredDistMultiplier -= MathHelper.Lerp(0, Math.Max(selectedTargetingParams.CircleStrikeDistanceMultiplier - 1, 1), Math.Clamp(subSpeedHorizontal / 10, 0, 1)); - if (requiredDistMultiplier < 2) + if (AttackingLimb != null && distance > 0 && distance < AttackingLimb.attack.Range * requiredDistMultiplier && IsFacing(margin: MathHelper.Lerp(0.5f, 0.9f, currentAttackIntensity))) { - requiredDistMultiplier = 2; + strikeTimer = AttackingLimb.attack.CoolDown; + CirclePhase = CirclePhase.Strike; } - } + canAttack = false; + break; + case CirclePhase.Strike: + strikeTimer -= deltaTime; + // just continue the movement forward to make it possible to evade the attack + steerPos = SimPosition + Steering; + if (strikeTimer <= 0) + { + CirclePhase = CirclePhase.Start; + aggressionIntensity += AIParams.AggressionCumulation; + } + break; } - return requiredDistMultiplier; - } + break; - float GetDirFromHeadingInRadius() - { - Vector2 heading = VectorExtensions.Forward(Character.AnimController.Collider.Rotation); - float angle = MathUtils.VectorToAngle(heading); - return angle > MathHelper.Pi || angle < -MathHelper.Pi ? -1 : 1; - } + bool IsFacing(float margin) + { + float offset = steeringLimb.Params.GetSpriteOrientation() - MathHelper.PiOver2; + Vector2 forward = VectorExtensions.Forward(steeringLimb.body.TransformedRotation - offset * Character.AnimController.Dir); + return Vector2.Dot(Vector2.Normalize(attackWorldPos - WorldPosition), forward) > margin; + } - float GetTargetMaxSpeed() => Character.ApplyTemporarySpeedLimits(Character.AnimController.CurrentSwimParams.MovementSpeed * 0.3f); + float GetStrikeDistanceMultiplier(Vector2 subSpeed) + { + float requiredDistMultiplier = 2; + bool isHeading = Steering != null && Vector2.Dot(Vector2.Normalize(attackWorldPos - WorldPosition), Vector2.Normalize(Steering)) > 0.9f; + if (isHeading) + { + requiredDistMultiplier = selectedTargetingParams.CircleStrikeDistanceMultiplier; + float subSpeedHorizontal = Math.Abs(subSpeed.X); + if (subSpeedHorizontal > 1) + { + // Reduce the required distance if the target is moving. + requiredDistMultiplier -= MathHelper.Lerp(0, Math.Max(selectedTargetingParams.CircleStrikeDistanceMultiplier - 1, 1), Math.Clamp(subSpeedHorizontal / 10, 0, 1)); + if (requiredDistMultiplier < 2) + { + requiredDistMultiplier = 2; + } + } + } + return requiredDistMultiplier; + } + + float GetDirFromHeadingInRadius() + { + Vector2 heading = VectorExtensions.Forward(Character.AnimController.Collider.Rotation); + float angle = MathUtils.VectorToAngle(heading); + return angle > MathHelper.Pi || angle < -MathHelper.Pi ? -1 : 1; + } + + float GetTargetMaxSpeed() => Character.ApplyTemporarySpeedLimits(Character.AnimController.CurrentSwimParams.MovementSpeed * 0.3f); + } } - SteeringManager.SteeringSeek(steerPos, 10); - if (SelectedAiTarget?.Entity is Character c && c.Submarine == null || distance == 0 || distance > ConvertUnits.ToDisplayUnits(avoidLookAheadDistance * 2)) + + if (!canAttack || distance > Math.Min(AttackingLimb.attack.Range * 0.9f, 100)) + { + if (pathSteering != null) + { + pathSteering.SteeringSeek(steerPos, weight: 10, minGapWidth: minGapSize); + } + else + { + SteeringManager.SteeringSeek(steerPos, 10); + } + } + else if (AttackingLimb.attack.Ranged) + { + // Too close + UpdateFallBack(attackWorldPos, deltaTime, followThrough: false); + } + if (Character.CurrentHull == null && (SelectedAiTarget?.Entity is Character c && c.Submarine == null || distance == 0 || distance > ConvertUnits.ToDisplayUnits(avoidLookAheadDistance * 2))) { SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: avoidLookAheadDistance, weight: 30); } @@ -1686,12 +1819,39 @@ namespace Barotrauma } } + private bool IsValidAttack(Limb attackingLimb, IEnumerable currentContexts, IDamageable target) + { + if (attackingLimb == null) { return false; } + if (target == null) { return false; } + var attack = attackingLimb.attack; + if (attack == null) { return false; } + if (attack.CoolDownTimer > 0) { return false; } + if (!attack.IsValidContext(currentContexts)) { return false; } + if (!attack.IsValidTarget(target)) { return false; } + if (target is ISerializableEntity se && target is Character) + { + if (attack.Conditionals.Any(c => !c.Matches(se))) { return false; } + } + if (attack.Conditionals.Any(c => c.TargetSelf && !c.Matches(Character))) { return false; } + if (attack.Ranged) + { + // Check that is approximately facing the target + Vector2 attackLimbPos = Character.AnimController.SimplePhysicsEnabled ? Character.WorldPosition : attackingLimb.WorldPosition; + Vector2 toTarget = attackWorldPos - attackLimbPos; + float offset = attackingLimb.Params.GetSpriteOrientation() - MathHelper.PiOver2; + Vector2 forward = VectorExtensions.Forward(attackingLimb.body.TransformedRotation - offset * Character.AnimController.Dir); + float angle = VectorExtensions.Angle(forward, toTarget); + if (angle > MathHelper.ToRadians(attack.RequiredAngle)) { return false; } + } + return true; + } + private readonly List attackLimbs = new List(); private readonly List weights = new List(); private Limb GetAttackLimb(Vector2 attackWorldPos, Limb ignoredLimb = null) { var currentContexts = Character.GetAttackContexts(); - Entity target = wallTarget != null ? wallTarget.Structure : SelectedAiTarget?.Entity; + IDamageable target = wallTarget != null ? wallTarget.Structure : SelectedAiTarget?.Entity as IDamageable; if (target == null) { return null; } Limb selectedLimb = null; float currentPriority = -1; @@ -1699,28 +1859,7 @@ namespace Barotrauma { if (limb == ignoredLimb) { continue; } if (limb.IsSevered || limb.IsStuck) { continue; } - if (limb.Disabled) { continue; } - var attack = limb.attack; - if (attack == null) { continue; } - if (attack.CoolDownTimer > 0) { continue; } - if (!attack.IsValidContext(currentContexts)) { continue; } - if (!attack.IsValidTarget(target as IDamageable)) { continue; } - if (target is ISerializableEntity se && target is Character) - { - if (attack.Conditionals.Any(c => !c.Matches(se))) { continue; } - } - if (attack.Conditionals.Any(c => c.TargetSelf && !c.Matches(Character))) { continue; } - if (attack.Ranged) - { - // Check that is approximately facing the target - Vector2 attackLimbPos = Character.AnimController.SimplePhysicsEnabled ? Character.WorldPosition : limb.WorldPosition; - Vector2 toTarget = attackWorldPos - attackLimbPos; - float offset = limb.Params.GetSpriteOrientation() - MathHelper.PiOver2; - Vector2 forward = VectorExtensions.Forward(limb.body.TransformedRotation - offset * Character.AnimController.Dir); - float angle = VectorExtensions.Angle(forward, toTarget); - if (angle > MathHelper.ToRadians(attack.RequiredAngle)) { continue; } - } - + if (!IsValidAttack(limb, currentContexts, target)) { continue; } if (AIParams.RandomAttack) { attackLimbs.Add(limb); @@ -1764,7 +1903,11 @@ namespace Barotrauma Character.AnimController.ReleaseStuckLimbs(); LatchOntoAI?.DeattachFromBody(reset: true, cooldown: 1); if (attacker == null || attacker.AiTarget == null || attacker.Removed || attacker.IsDead) { return; } - bool isFriendly = IsFriendly(Character, attacker); + if (Character.Params.CanInteract && attackResult.Damage > 10) + { + ReleaseDragTargets(); + } + bool isFriendly = Character.IsFriendly(attacker); if (wasLatched) { State = AIState.Escape; @@ -1775,7 +1918,6 @@ namespace Barotrauma } return; } - if (State == AIState.Flee) { if (!isFriendly) @@ -1840,7 +1982,7 @@ namespace Barotrauma } } - AITargetMemory targetMemory = GetTargetMemory(attacker.AiTarget, true); + AITargetMemory targetMemory = GetTargetMemory(attacker.AiTarget, addIfNotFound: true); targetMemory.Priority += GetRelativeDamage(attackResult.Damage, Character.Vitality) * AIParams.AggressionHurt; // Only allow to react once. Otherwise would attack the target with only a fraction of a cooldown @@ -1878,52 +2020,120 @@ namespace Barotrauma } } + private Item GetEquippedItem(Limb limb) + { + InvSlotType GetInvSlotForLimb() + { + return limb.type switch + { + LimbType.RightHand => InvSlotType.RightHand, + LimbType.LeftHand => InvSlotType.LeftHand, + LimbType.Head => InvSlotType.Head, + _ => InvSlotType.None, + }; + } + var slot = GetInvSlotForLimb(); + if (slot != InvSlotType.None) + { + return Character.Inventory.GetItemInLimbSlot(slot); + } + return null; + } + // 10 dmg, 100 health -> 0.1 private float GetRelativeDamage(float dmg, float vitality) => dmg / Math.Max(vitality, 1.0f); private bool UpdateLimbAttack(float deltaTime, Limb attackingLimb, Vector2 attackSimPos, float distance = -1, Limb targetLimb = null) { if (SelectedAiTarget?.Entity == null) { return false; } - - ActiveAttack = attackingLimb?.attack; - + if (attackingLimb?.attack == null) { return false; } + ActiveAttack = attackingLimb.attack; if (wallTarget != null) { // If the selected target is not the wall target, make the wall target the selected target. var aiTarget = wallTarget.Structure.AiTarget; if (aiTarget != null && SelectedAiTarget != aiTarget) { - SelectTarget(aiTarget, GetTargetMemory(SelectedAiTarget, true).Priority); + SelectTarget(aiTarget, GetTargetMemory(SelectedAiTarget, addIfNotFound: true).Priority); State = AIState.Attack; } } IDamageable damageTarget = wallTarget != null ? wallTarget.Structure : SelectedAiTarget.Entity as IDamageable; if (damageTarget != null) { + if (Character.Params.CanInteract && Character.Inventory != null) + { + // Use equipped items (weapons) + Item item = GetEquippedItem(attackingLimb); + if (item != null) + { + if (item.RequireAimToUse) + { + if (!Aim(deltaTime, damageTarget as ISpatialEntity, item)) + { + // Valid target, but can't shoot -> return true so that it will not be ignored. + return true; + } + } + Character.SetInput(item.IsShootable ? InputType.Shoot : InputType.Use, false, true); + item.Use(deltaTime, Character); + } + } //simulate attack input to get the character to attack client-side Character.SetInput(InputType.Attack, true, true); -#if SERVER - GameMain.NetworkMember.CreateEntityEvent(Character, new object[] + if (!ActiveAttack.IsRunning) { - Networking.NetEntityEvent.Type.SetAttackTarget, - attackingLimb, - (damageTarget as Entity)?.ID ?? Entity.NullEntityID, - damageTarget is Character character && targetLimb != null ? Array.IndexOf(character.AnimController.Limbs, targetLimb) : 0, - SimPosition.X, - SimPosition.Y - }); +#if SERVER + GameMain.NetworkMember.CreateEntityEvent(Character, new object[] + { + Networking.NetEntityEvent.Type.SetAttackTarget, + attackingLimb, + (damageTarget as Entity)?.ID ?? Entity.NullEntityID, + damageTarget is Character character && targetLimb != null ? Array.IndexOf(character.AnimController.Limbs, targetLimb) : 0, + SimPosition.X, + SimPosition.Y + }); +#else + Character.PlaySound(CharacterSound.SoundType.Attack, maxInterval: 3); #endif + } + if (attackingLimb.UpdateAttack(deltaTime, attackSimPos, damageTarget, out AttackResult attackResult, distance, targetLimb)) { - if (damageTarget.Health > 0 && attackResult.Damage > 0) + if (attackingLimb.attack.CoolDownTimer > 0) { + SetAimTimer(); // Managed to hit a living/non-destroyed target. Increase the priority more if the target is low in health -> dies easily/soon - selectedTargetMemory.Priority += GetRelativeDamage(attackResult.Damage, damageTarget.Health) * AIParams.AggressionGreed; + float greed = AIParams.AggressionGreed; + if (!(damageTarget is Character)) + { + // Halve the greed for attacking non-characters. + greed /= 2; + } + selectedTargetMemory.Priority += GetRelativeDamage(attackResult.Damage, damageTarget.Health) * greed; } - else + if (LatchOntoAI != null && SelectedAiTarget.Entity is Character targetCharacter) { - selectedTargetMemory.Priority -= Math.Max(selectedTargetMemory.Priority / 2, 1); - return selectedTargetMemory.Priority > 1; + LatchOntoAI.SetAttachTarget(targetCharacter); + } + if (!attackingLimb.attack.Ranged) + { + if (damageTarget.Health > 0 && attackResult.Damage > 0) + { + // Managed to hit a living/non-destroyed target. Increase the priority more if the target is low in health -> dies easily/soon + float greed = AIParams.AggressionGreed; + if (!(damageTarget is Character)) + { + // Halve the greed for attacking non-characters. + greed /= 2; + } + selectedTargetMemory.Priority += GetRelativeDamage(attackResult.Damage, damageTarget.Health) * greed; + } + else + { + selectedTargetMemory.Priority -= Math.Max(selectedTargetMemory.Priority / 2, 1); + return selectedTargetMemory.Priority > 1; + } } } return true; @@ -1931,6 +2141,64 @@ namespace Barotrauma return false; } + private float aimTimer; + private float visibilityCheckTimer; + private bool canSeeTarget; + private bool Aim(float deltaTime, ISpatialEntity target, Item weapon) + { + if (target == null || weapon == null) { return false; } + Character.CursorPosition = target.WorldPosition; + if (Character.Submarine != null) + { + Character.CursorPosition -= Character.Submarine.Position; + } + visibilityCheckTimer -= deltaTime; + if (visibilityCheckTimer <= 0.0f) + { + canSeeTarget = Character.CanSeeTarget(target); + visibilityCheckTimer = 0.2f; + } + if (!canSeeTarget) + { + SetAimTimer(); + return false; + } + Character.SetInput(InputType.Aim, false, true); + if (aimTimer > 0) + { + aimTimer -= deltaTime; + return false; + } + Vector2 toTarget = target.WorldPosition - weapon.WorldPosition; + float angle = VectorExtensions.Angle(VectorExtensions.Forward(weapon.body.TransformedRotation), toTarget); + float distanceFactor = MathHelper.Lerp(1.0f, 0.1f, MathUtils.InverseLerp(100, 1000, toTarget.Length())); + float margin = MathHelper.PiOver4 * distanceFactor; + if (angle < margin) + { + var collisionCategories = Physics.CollisionCharacter | Physics.CollisionWall | Physics.CollisionLevel; + var pickedBody = Submarine.PickBody(weapon.SimPosition, target.SimPosition, myBodies, collisionCategories, allowInsideFixture: true); + if (pickedBody != null) + { + Character t = null; + if (pickedBody.UserData is Character c) + { + t = c; + } + else if (pickedBody.UserData is Limb limb) + { + t = limb.character; + } + if (t != null && (t == target || !Character.IsFriendly(t))) + { + return true; + } + } + } + return false; + } + + private void SetAimTimer(float timer = 1.5f) => aimTimer = timer * Rand.Range(0.75f, 1.25f); + private readonly float blockCheckInterval = 0.1f; private float blockCheckTimer; private bool isBlocked; @@ -2052,41 +2320,40 @@ namespace Barotrauma State = AIState.Idle; return; } - if (Character.CurrentHull != null && PathSteering != null) + if (Character.CurrentHull != null && steeringManager == insideSteering) { - // Inside - Character targetCharacter = SelectedAiTarget.Entity as Character; + // Inside, but not inside ruins if ((Character.AnimController.InWater || !Character.AnimController.CanWalk) && - (targetCharacter != null && VisibleHulls.Contains(targetCharacter.CurrentHull) || Character.CanSeeTarget(SelectedAiTarget.Entity))) + Character.Submarine != null && !Character.Submarine.Info.IsRuin && + SelectedAiTarget.Entity is Character c && VisibleHulls.Contains(c.CurrentHull)) { // Steer towards the target if in the same room and swimming - Vector2 dir = Vector2.Normalize(SelectedAiTarget.Entity.WorldPosition - Character.WorldPosition); - if (MathUtils.IsValid(dir)) - { - SteeringManager.SteeringManual(deltaTime, dir); - } + SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(SelectedAiTarget.Entity.WorldPosition - Character.WorldPosition)); } else { // Use path finding - SteeringManager.SteeringSeek(Character.GetRelativeSimPosition(SelectedAiTarget.Entity), 2); - if (!PathSteering.IsPathDirty && PathSteering.CurrentPath.Unreachable) - { - // Can't reach - State = AIState.Idle; - return; - } + PathSteering.SteeringSeek(Character.GetRelativeSimPosition(SelectedAiTarget.Entity), weight: 2, minGapWidth: minGapSize); } } else { // Outside SteeringManager.SteeringSeek(Character.GetRelativeSimPosition(SelectedAiTarget.Entity), 5); - if (Character.AnimController.InWater) + } + if (steeringManager is IndoorsSteeringManager pathSteering) + { + if (!pathSteering.IsPathDirty && pathSteering.CurrentPath != null && pathSteering.CurrentPath.Unreachable) { - SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: avoidLookAheadDistance, weight: 15); + // Can't reach + State = AIState.Idle; + IgnoreTarget(SelectedAiTarget); } } + else if (Character.AnimController.InWater) + { + SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: avoidLookAheadDistance, weight: 15); + } } #region Targeting @@ -2105,7 +2372,7 @@ namespace Barotrauma foreach (AITarget aiTarget in AITarget.List) { - if (!aiTarget.Enabled) { continue; } + if (aiTarget.InDetectable) { continue; } if (aiTarget.Entity == null) { continue; } if (ignoredTargets.Contains(aiTarget)) { continue; } if (Level.Loaded != null && aiTarget.WorldPosition.Y > Level.Loaded.Size.Y) @@ -2125,6 +2392,9 @@ namespace Barotrauma string targetingTag = null; if (targetCharacter != null) { + // ignore if target is tagged to be explicitly ignored (Feign Death) + if (targetCharacter.HasAbilityFlag(AbilityFlags.IgnoredByEnemyAI)) { continue; } + if (targetCharacter.IsDead) { targetingTag = "dead"; @@ -2139,7 +2409,7 @@ namespace Barotrauma } else { - if (IsFriendly(Character, targetCharacter)) + if (Character.IsFriendly(targetCharacter)) { continue; } @@ -2186,10 +2456,12 @@ namespace Barotrauma } else { - // Ignore all structures, items, and hulls inside wrecks and beacons + // Ignore all structures, items, and hulls inside these subs. if (aiTarget.Entity.Submarine != null) { - if (aiTarget.Entity.Submarine.Info.IsWreck || aiTarget.Entity.Submarine.Info.IsBeacon || UnattackableSubmarines.Contains(aiTarget.Entity.Submarine)) + if (aiTarget.Entity.Submarine.Info.IsWreck || + aiTarget.Entity.Submarine.Info.IsBeacon || + UnattackableSubmarines.Contains(aiTarget.Entity.Submarine)) { continue; } @@ -2200,6 +2472,7 @@ namespace Barotrauma if (character.CurrentHull != null) { continue; } // Ignore ruins if (hull.Submarine == null) { continue; } + if (hull.Submarine.Info.IsRuin) { continue; } } Door door = null; @@ -2215,14 +2488,6 @@ namespace Barotrauma continue; } } - if (door == null) - { - // Ignore items inside ruins, unless we are in the same hull. We can't target the ruin walls. - if (item.Submarine == null && item.CurrentHull != Character.CurrentHull) - { - continue; - } - } foreach (var prio in AIParams.Targets) { if (item.HasTag(prio.Tag)) @@ -2240,6 +2505,11 @@ namespace Barotrauma else if (targetingFromOutsideToInside) { targetingTag = "room"; + if (item.Submarine?.Info.IsRuin != null) + { + // Ignore ruin items when the creature is outside. + continue; + } } } else if (targetingTag == "nasonov") @@ -2266,6 +2536,7 @@ namespace Barotrauma } if (s.IsPlatform) { continue; } if (s.Submarine == null) { continue; } + if (s.Submarine.Info.IsRuin) { continue; } bool isCharacterInside = character.CurrentHull != null; bool isInnerWall = s.prefab.Tags.Contains("inner"); if (isInnerWall && !isCharacterInside) @@ -2425,11 +2696,24 @@ namespace Barotrauma continue; } } - if (aiTarget.Entity is Item targetItem && targetParams.IgnoreContained && targetItem.ParentInventory != null) { continue; } + if (aiTarget.Entity is Item targetItem) + { + if (targetParams.IgnoreContained && targetItem.ParentInventory != null) { continue; } + if (targetParams.State == AIState.FleeTo) + { + float target = targetParams.Threshold; + if (targetParams.ThresholdMin > 0 && targetParams.ThresholdMax > 0) + { + target = selectedTargetingParams == targetParams ? targetParams.ThresholdMax : targetParams.ThresholdMin; + } + if (character.HealthPercentage > target) + { + continue; + } + } + } valueModifier *= targetParams.Priority; - if (valueModifier == 0.0f) { continue; } - if (targetingTag != "decoy") { if (SwarmBehavior != null && SwarmBehavior.Members.Any()) @@ -2449,7 +2733,7 @@ namespace Barotrauma { if (otherCharacter == character) { continue; } if (otherCharacter.AIController?.SelectedAiTarget != aiTarget) { continue; } - if (!IsFriendly(character, otherCharacter)) { continue; } + if (!character.IsFriendly(otherCharacter)) { continue; } valueModifier /= 2; } } @@ -2463,17 +2747,30 @@ namespace Barotrauma { dist *= 0.9f; } - if (!CanPerceive(aiTarget, dist)) { continue; } + + if (!CanPerceive(aiTarget, dist, checkVisibility: SelectedAiTarget != aiTarget)) + { + continue; + } + + if (SelectedAiTarget == aiTarget) + { + // Stick to the current target + valueModifier *= 1.1f; + } //if the target is very close, the distance doesn't make much difference // -> just ignore the distance and attack whatever has the highest priority dist = Math.Max(dist, 100.0f); - - AITargetMemory targetMemory = GetTargetMemory(aiTarget, true); - if (Character.CurrentHull != null && Math.Abs(toTarget.Y) > Character.CurrentHull.Size.Y) + AITargetMemory targetMemory = GetTargetMemory(aiTarget, addIfNotFound: true); + if (Character.Submarine != null && !Character.Submarine.Info.IsRuin && Character.CurrentHull != null) { - // Inside the sub, treat objects that are up or down, as they were farther away. - dist *= 3; + float diff = Math.Abs(toTarget.Y) - Character.CurrentHull.Size.Y; + if (diff > 0) + { + // Inside the sub, treat objects that are up or down, as they were farther away. + dist *= MathHelper.Clamp(diff / 100, 2, 3); + } } if (targetParams.AttackPattern == AttackPattern.Circle) @@ -2493,6 +2790,12 @@ namespace Barotrauma } } + if (targetCharacter != null && Character.CurrentHull != null && Character.CurrentHull == targetCharacter.CurrentHull) + { + // In the same room with the target character + dist /= 2; + } + // Don't target characters that are outside of the allowed zone, unless chasing or escaping. switch (targetParams.State) { @@ -2527,7 +2830,7 @@ namespace Barotrauma // Don't target items that we own. // This is a rare case, and almost entirely related to Humanhusks, so let's check it last to reduce unnecessary checks (although the check shouldn't be expensive) if (owner == character) { continue; } - if (owner != null && (IsFriendly(Character, owner) || owner.AiTarget != null && ignoredTargets.Contains(owner.AiTarget))) + if (owner != null && (Character.IsFriendly(owner) || owner.AiTarget != null && ignoredTargets.Contains(owner.AiTarget))) { continue; } @@ -2599,7 +2902,7 @@ namespace Barotrauma wall = wallTarget?.Structure; } // The target is not a wall or it's not the same as we are attached to -> release - bool releaseTarget = wall == null || (!wall.Bodies.Contains(LatchOntoAI.AttachJoints[0].BodyB) && wall.Submarine?.PhysicsBody?.FarseerBody != LatchOntoAI.AttachJoints[0].BodyB); + bool releaseTarget = wall?.Bodies == null || (!wall.Bodies.Contains(LatchOntoAI.AttachJoints[0].BodyB) && wall.Submarine?.PhysicsBody?.FarseerBody != LatchOntoAI.AttachJoints[0].BodyB); if (!releaseTarget) { for (int i = 0; i < wall.Sections.Length; i++) @@ -2645,6 +2948,7 @@ namespace Barotrauma wallTarget = null; if (SelectedAiTarget == null) { return; } if (SelectedAiTarget.Entity == null) { return; } + if (!canAttackWalls) { return; } if (HasValidPath(requireNonDirty: true)) { return; } wallHits.Clear(); Structure wall = null; @@ -2847,10 +3151,15 @@ namespace Barotrauma { if (addIfNotFound) { - memory = new AITargetMemory(target, 10); + memory = new AITargetMemory(target, minPriority); targetMemories.Add(target, memory); } } + if (addIfNotFound) + { + // Keep the memory alive. + memory.Priority = Math.Max(memory.Priority, minPriority); + } return memory; } @@ -2862,7 +3171,7 @@ namespace Barotrauma { _selectedAiTarget = null; } - else if (CanPerceive(_selectedAiTarget)) + else if (CanPerceive(_selectedAiTarget, checkVisibility: false)) { var memory = GetTargetMemory(_selectedAiTarget, false); if (memory != null) @@ -3000,7 +3309,7 @@ namespace Barotrauma private void ResetParams(CharacterParams.TargetParams targetParams) { targetParams?.Reset(); - if (selectedTargetingParams == targetParams || State == AIState.Idle) + if (selectedTargetingParams == targetParams || State == AIState.Idle || State == AIState.Patrol) { ResetAITarget(); State = AIState.Idle; @@ -3014,7 +3323,7 @@ namespace Barotrauma { if (!onlyExisting && !tempParams.ContainsKey(tag)) { - if (AIParams.TryAddNewTarget(tag, state, priority ?? 100, out targetParams)) + if (AIParams.TryAddNewTarget(tag, state, priority ?? minPriority, out targetParams)) { tempParams.Add(tag, targetParams); } @@ -3051,6 +3360,7 @@ namespace Barotrauma ChangeParams(target.SpeciesName, state, priority); if (target.IsHuman) { + priority = GetTargetParams("human")?.Priority; // Target also items, because if we are blind and the target doesn't move, we can only perceive the target when it uses items if (state == AIState.Attack || state == AIState.Escape) { @@ -3061,20 +3371,19 @@ namespace Barotrauma { // If the target is shooting from the submarine, we might not perceive it because it doesn't move. // --> Target the submarine too. - if (target.Submarine != null && (canAttackDoors || canAttackWalls)) + if (target.Submarine != null && Character.Submarine == null && (canAttackDoors || canAttackWalls)) { - ChangeParams("room", state, priority); + ChangeParams("room", state, priority * 0.1f); if (canAttackWalls) { - ChangeParams("wall", state, priority); + ChangeParams("wall", state, priority * 0.1f); } if (canAttackDoors) { - ChangeParams("door", state, priority); + ChangeParams("door", state, priority * 0.1f); } } ChangeParams("provocative", state, priority, onlyExisting: true); - ChangeParams("light", state, priority, onlyExisting: true); } } } @@ -3100,12 +3409,16 @@ namespace Barotrauma protected override void OnStateChanged(AIState from, AIState to) { LatchOntoAI?.DeattachFromBody(reset: true); + if (disableTailCoroutine != null) + { + CoroutineManager.StopCoroutines(disableTailCoroutine); + Character.AnimController.RestoreTemporarilyDisabled(); + disableTailCoroutine = null; + } Character.AnimController.ReleaseStuckLimbs(); - escapeTarget = null; AttackingLimb = null; movementMargin = 0; - allGapsSearched = false; - unreachableGaps.Clear(); + ResetEscape(); if (isStateChanged && to == AIState.Idle && from != to) { SetStateResetTimer(); @@ -3117,11 +3430,16 @@ namespace Barotrauma private float GetPerceivingRange(AITarget target) => Math.Max(target.SightRange * Sight, target.SoundRange * Hearing); - private bool CanPerceive(AITarget target, float dist = -1, float distSquared = -1) + private bool CanPerceive(AITarget target, float dist = -1, float distSquared = -1, bool checkVisibility = false) { + bool insideSightRange; + bool insideSoundRange; + checkVisibility = checkVisibility && Character.Submarine != null && target.Entity.Submarine == Character.Submarine; if (dist > 0) { - return dist <= target.SightRange * Sight || dist <= target.SoundRange * Hearing; + insideSightRange = IsInRange(dist, target.SightRange, Sight); + if (!checkVisibility && insideSightRange) { return true; } + insideSoundRange = IsInRange(dist, target.SoundRange, Hearing); } else { @@ -3129,8 +3447,42 @@ namespace Barotrauma { distSquared = Vector2.DistanceSquared(Character.WorldPosition, target.WorldPosition); } - return distSquared <= MathUtils.Pow(target.SightRange * Sight, 2) || distSquared <= MathUtils.Pow(target.SoundRange * Hearing, 2); + insideSightRange = IsInRangeSqr(distSquared, target.SightRange, Sight); + if (!checkVisibility && insideSightRange) { return true; } + insideSoundRange = IsInRangeSqr(distSquared, target.SoundRange, Hearing); } + if (!checkVisibility) + { + return insideSightRange || insideSoundRange; + } + else + { + if (!insideSightRange && !insideSoundRange) { return false; } + // Inside the same submarine -> check whether the target is behind a wall + if (target.Entity is Character c && VisibleHulls.Contains(c.CurrentHull) || target.Entity is Item i && VisibleHulls.Contains(i.CurrentHull)) + { + return insideSightRange || insideSoundRange; + } + else + { + // No line of sight to the target -> Ignore sight and use only half of the sound range + if (dist > 0) + { + return IsInRange(dist, target.SoundRange, Hearing / 2); + } + else + { + if (distSquared < 0) + { + distSquared = Vector2.DistanceSquared(Character.WorldPosition, target.WorldPosition); + } + return IsInRangeSqr(distSquared, target.SoundRange, Hearing / 2); + } + } + } + + bool IsInRange(float dist, float range, float perception) => dist <= range * perception; + bool IsInRangeSqr(float distSquared, float range, float perception) => distSquared <= MathUtils.Pow2(range * perception); } public void ReevaluateAttacks() @@ -3255,6 +3607,72 @@ namespace Barotrauma public bool CanPassThroughHole(Structure wall, int sectionIndex) => CanPassThroughHole(wall, sectionIndex, requiredHoleCount); + public override bool Escape(float deltaTime) + { + if (SelectedAiTarget != null && (SelectedAiTarget.Entity == null || SelectedAiTarget.Entity.Removed)) + { + State = AIState.Idle; + return false; + } + else if (SelectedTargetMemory is AITargetMemory targetMemory && SelectedAiTarget?.Entity is Character) + { + targetMemory.Priority += deltaTime * PriorityFearIncrement; + } + bool isSteeringThroughGap = UpdateEscape(deltaTime, canAttackDoors); + if (!isSteeringThroughGap) + { + if (SelectedAiTarget?.Entity is Character targetCharacter && targetCharacter.CurrentHull == Character.CurrentHull) + { + SteerAwayFromTheEnemy(); + } + else if (canAttackDoors && HasValidPath(requireNonDirty: true, requireUnfinished: true)) + { + var door = PathSteering.CurrentPath.CurrentNode?.ConnectedDoor ?? PathSteering.CurrentPath.NextNode?.ConnectedDoor; + if (door != null && !door.CanBeTraversed && !door.HasAccess(Character)) + { + if (SelectedAiTarget != door.Item.AiTarget || State != AIState.Attack) + { + SelectTarget(door.Item.AiTarget, SelectedTargetMemory.Priority); + State = AIState.Attack; + } + } + } + } + if (EscapeTarget == null) + { + if (SelectedAiTarget?.Entity is Character) + { + SteerAwayFromTheEnemy(); + } + else + { + SteeringManager.SteeringWander(); + if (Character.CurrentHull == null) + { + SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: avoidLookAheadDistance, weight: 5); + } + } + } + return isSteeringThroughGap; + + void SteerAwayFromTheEnemy() + { + if (SelectedAiTarget == null) { return; } + Vector2 escapeDir = Vector2.Normalize(WorldPosition - SelectedAiTarget.WorldPosition); + if (Character.CurrentHull != null && !Character.AnimController.InWater) + { + // Inside + escapeDir = new Vector2(Math.Sign(escapeDir.X), 0); + } + if (!MathUtils.IsValid(escapeDir)) + { + escapeDir = Vector2.UnitY; + } + SteeringManager.Reset(); + SteeringManager.SteeringManual(deltaTime, escapeDir); + } + } + private readonly List targetLimbs = new List(); public Limb GetTargetLimb(Limb attackLimb, Character target, LimbType targetLimbType = LimbType.None) { @@ -3306,7 +3724,17 @@ namespace Barotrauma return null; } - public static bool IsFriendly(Character me, Character other) => other.SpeciesName == me.SpeciesName || other.Params.CompareGroup(me.Params.Group); + public override void ServerWrite(IWriteMessage msg) + { + msg.Write((byte)State); + PetBehavior?.ServerWrite(msg); + } + + public override void ClientRead(IReadMessage msg) + { + State = (AIState)msg.ReadByte(); + PetBehavior?.ClientRead(msg); + } } //the "memory" of the Character diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs index 8c562b697..974af8a20 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs @@ -20,6 +20,11 @@ namespace Barotrauma private float reactTimer; private float unreachableClearTimer; private bool shouldCrouch; + public bool IsInsideCave { get; private set; } + /// + /// Resets each frame + /// + public bool AutoFaceMovement = true; const float reactionTime = 0.3f; const float crouchRaycastInterval = 1; @@ -29,9 +34,6 @@ namespace Barotrauma private float flipTimer; private const float FlipInterval = 0.5f; - private float teamChangeTimer; - private const float TeamChangeInterval = 0.5f; - public const float HULL_SAFETY_THRESHOLD = 40; public const float HULL_LOW_OXYGEN_PERCENTAGE = 30; @@ -52,7 +54,7 @@ namespace Barotrauma private readonly float steeringBufferIncreaseSpeed = 100; private float steeringBuffer; - private readonly float obstacleRaycastInterval = 1; + private readonly float obstacleRaycastIntervalShort = 1, obstacleRaycastIntervalLong = 5; private float obstacleRaycastTimer; private readonly float enemyCheckInterval = 0.2f; @@ -86,6 +88,8 @@ namespace Barotrauma private readonly SteeringManager outsideSteering, insideSteering; + public bool UseIndoorSteeringOutside { get; set; } = false; + public IndoorsSteeringManager PathSteering => insideSteering as IndoorsSteeringManager; public HumanoidAnimController AnimController => Character.AnimController as HumanoidAnimController; @@ -207,33 +211,78 @@ namespace Barotrauma IgnoredItems.Clear(); } - bool IsCloseEnoughToTargetSub(float threshold) => SelectedAiTarget?.Entity?.Submarine is Submarine sub && sub != null && Vector2.DistanceSquared(Character.WorldPosition, sub.WorldPosition) < MathUtils.Pow(Math.Max(sub.Borders.Size.X, sub.Borders.Size.Y) / 2 + threshold, 2); + bool IsCloseEnoughToTarget(float threshold, bool useTargetSub = true) + { + Entity target = SelectedAiTarget?.Entity; + if (target == null) + { + return false; + } + if (useTargetSub) + { + if (target.Submarine is Submarine sub) + { + target = sub; + threshold += Math.Max(sub.Borders.Size.X, sub.Borders.Size.Y) / 2; + } + else + { + return false; + } + } + return Vector2.DistanceSquared(Character.WorldPosition, target.WorldPosition) < MathUtils.Pow(threshold, 2); + } + bool hasValidPath = HasValidPath(); if (Character.Submarine == null) { - if (hasValidPath) + // When the character is outside, far enough from the target, and the direct route is blocked, + // use the indoor steering with the main and side path waypoints to help avoid getting stuck in level walls + if (SelectedAiTarget?.Entity != null && !IsCloseEnoughToTarget(2000, useTargetSub: false)) { obstacleRaycastTimer -= deltaTime; if (obstacleRaycastTimer <= 0) { - obstacleRaycastTimer = obstacleRaycastInterval; - // Swimming outside and using the path finder -> check that the path is not blocked with anything (the path finder doesn't know about other subs). - foreach (var connectedSub in Submarine.MainSub.GetConnectedSubs()) + obstacleRaycastTimer = obstacleRaycastIntervalLong; + Vector2 rayEnd = SelectedAiTarget.Entity.SimPosition; + if (SelectedAiTarget.Entity.Submarine != null) { - if (connectedSub == Submarine.MainSub) { continue; } - Vector2 rayStart = SimPosition - connectedSub.SimPosition; - Vector2 dir = PathSteering.CurrentPath.CurrentNode.WorldPosition - WorldPosition; - Vector2 rayEnd = rayStart + dir.ClampLength(Character.AnimController.Collider.GetLocalFront().Length() * 5); - if (Submarine.CheckVisibility(rayStart, rayEnd, ignoreSubs: true) != null) + rayEnd += SelectedAiTarget.Entity.Submarine.SimPosition; + } + UseIndoorSteeringOutside = Submarine.PickBody(SimPosition, rayEnd, collisionCategory: Physics.CollisionLevel | Physics.CollisionWall) != null; + } + } + else + { + UseIndoorSteeringOutside = false; + if (hasValidPath) + { + obstacleRaycastTimer -= deltaTime; + if (obstacleRaycastTimer <= 0) + { + obstacleRaycastTimer = obstacleRaycastIntervalShort; + // Swimming outside and using the path finder -> check that the path is not blocked with anything (the path finder doesn't know about other subs). + foreach (var connectedSub in Submarine.MainSub.GetConnectedSubs()) { - PathSteering.CurrentPath.Unreachable = true; - break; + if (connectedSub == Submarine.MainSub) { continue; } + Vector2 rayStart = SimPosition - connectedSub.SimPosition; + Vector2 dir = PathSteering.CurrentPath.CurrentNode.WorldPosition - WorldPosition; + Vector2 rayEnd = rayStart + dir.ClampLength(Character.AnimController.Collider.GetLocalFront().Length() * 5); + if (Submarine.CheckVisibility(rayStart, rayEnd, ignoreSubs: true) != null) + { + PathSteering.CurrentPath.Unreachable = true; + break; + } } } } } } + else + { + UseIndoorSteeringOutside = false; + } if (Character.Submarine == null || !IsOnFriendlyTeam(Character.TeamID, Character.Submarine.TeamID) && !Character.IsEscorted) { @@ -273,13 +322,31 @@ namespace Barotrauma } } } - if (Character.Submarine != null || hasValidPath && IsCloseEnoughToTargetSub(maxSteeringBuffer) || IsCloseEnoughToTargetSub(steeringBuffer)) + + // Check whether the character is inside a cave + if (IsInsideCave) + { + // If the character was inside a cave, require them to move a bit further from the area to set the field back to false + // This is to avoid any twitchy behavior with the steering managers + IsInsideCave = Character.CurrentHull == null && Level.Loaded?.Caves.FirstOrDefault(c => + { + var area = c.Area; + area.Inflate(new Vector2(100)); + return area.Contains(Character.WorldPosition); + }) is Level.Cave; + } + else + { + IsInsideCave = Character.CurrentHull == null && Level.Loaded?.Caves.FirstOrDefault(c => c.Area.Contains(Character.WorldPosition)) is Level.Cave; + } + + if (UseIndoorSteeringOutside || IsInsideCave || Character.CurrentHull?.Submarine != null || hasValidPath && IsCloseEnoughToTarget(maxSteeringBuffer) || IsCloseEnoughToTarget(steeringBuffer)) { if (steeringManager != insideSteering) { insideSteering.Reset(); + steeringManager = insideSteering; } - steeringManager = insideSteering; steeringBuffer += steeringBufferIncreaseSpeed * deltaTime; } else @@ -287,8 +354,8 @@ namespace Barotrauma if (steeringManager != outsideSteering) { outsideSteering.Reset(); + steeringManager = outsideSteering; } - steeringManager = outsideSteering; steeringBuffer = minSteeringBuffer; } steeringBuffer = Math.Clamp(steeringBuffer, minSteeringBuffer, maxSteeringBuffer); @@ -419,7 +486,7 @@ namespace Barotrauma Character.SelectedConstruction.SecondaryUse(deltaTime, Character); } } - else if (Math.Abs(Character.AnimController.TargetMovement.X) > 0.1f && !Character.AnimController.InWater) + else if (AutoFaceMovement && Math.Abs(Character.AnimController.TargetMovement.X) > 0.1f && !Character.AnimController.InWater) { newDir = Character.AnimController.TargetMovement.X > 0.0f ? Direction.Right : Direction.Left; } @@ -429,6 +496,7 @@ namespace Barotrauma flipTimer = FlipInterval; } } + AutoFaceMovement = true; MentalStateManager?.Update(deltaTime); ShipCommandManager?.Update(deltaTime); @@ -915,10 +983,10 @@ namespace Barotrauma return false; } - public static void ReportProblem(Character reporter, Order order) + public static void ReportProblem(Character reporter, Order order, Hull targetHull = null) { if (reporter == null || order == null) { return; } - var visibleHulls = new List(reporter.GetVisibleHulls()); + var visibleHulls = targetHull is null ? new List(reporter.GetVisibleHulls()) : new List { targetHull }; foreach (var hull in visibleHulls) { PropagateHullSafety(reporter, hull); @@ -965,16 +1033,19 @@ namespace Barotrauma } if (previousAttackResults.ContainsKey(attacker)) { - foreach (Affliction newAffliction in attackResult.Afflictions) + if (attackResult.Afflictions != null) { - var matchingAffliction = previousAttackResults[attacker].Afflictions.Find(a => a.Prefab == newAffliction.Prefab && a.Source == newAffliction.Source); - if (matchingAffliction == null) + foreach (Affliction newAffliction in attackResult.Afflictions) { - previousAttackResults[attacker].Afflictions.Add(newAffliction); - } - else - { - matchingAffliction.Strength += newAffliction.Strength; + var matchingAffliction = previousAttackResults[attacker].Afflictions.Find(a => a.Prefab == newAffliction.Prefab && a.Source == newAffliction.Source); + if (matchingAffliction == null) + { + previousAttackResults[attacker].Afflictions.Add(newAffliction); + } + else + { + matchingAffliction.Strength += newAffliction.Strength; + } } } previousAttackResults[attacker] = new AttackResult(previousAttackResults[attacker].Afflictions, previousAttackResults[attacker].HitLimb); @@ -991,9 +1062,12 @@ namespace Barotrauma float realDamage = attackResult.Damage; // including poisons etc float totalDamage = realDamage; - foreach (Affliction affliction in attackResult.Afflictions) + if (attackResult.Afflictions != null) { - totalDamage -= affliction.Prefab.KarmaChangeOnApplied * affliction.Strength; + foreach (Affliction affliction in attackResult.Afflictions) + { + totalDamage -= affliction.Prefab.KarmaChangeOnApplied * affliction.Strength; + } } if (totalDamage <= 0.01f) { return; } if (Character.IsBot) @@ -1047,7 +1121,7 @@ namespace Barotrauma { (GameMain.GameSession?.GameMode as CampaignMode)?.OutpostNPCAttacked(Character, attacker, attackResult); // Inform other NPCs - if (cumulativeDamage > 1) + if (cumulativeDamage > 1 || totalDamage >= 10) { InformOtherNPCs(cumulativeDamage); } @@ -1184,7 +1258,7 @@ namespace Barotrauma // Already targeting the attacker -> treat as a more serious threat. cumulativeDamage *= 2; } - if (attackResult.Afflictions.Any(a => a is AfflictionHusk)) + if (attackResult.Afflictions != null && attackResult.Afflictions.Any(a => a is AfflictionHusk)) { cumulativeDamage = 100; } @@ -1240,10 +1314,7 @@ namespace Barotrauma { var objective = new AIObjectiveCombat(Character, target, mode, objectiveManager) { - HoldPosition = - Character.Info?.Job?.Prefab.Identifier == "watchman" || - Character.CurrentHull == null || - Character.IsOnPlayerTeam && !target.IsPlayer && ObjectiveManager.GetActiveObjective()?.Target is Character followTarget && followTarget.IsPlayer, + HoldPosition = Character.Info?.Job?.Prefab.Identifier == "watchman", AbortCondition = abortCondition, allowHoldFire = allowHoldFire, }; @@ -1293,6 +1364,8 @@ namespace Barotrauma ObjectiveManager.WaitTimer = waitDuration; } + public override bool Escape(float deltaTime) => UpdateEscape(deltaTime, canAttackDoors: false); + private void CheckCrouching(float deltaTime) { crouchRaycastTimer -= deltaTime; @@ -1415,7 +1488,7 @@ namespace Barotrauma if (GameMain.GameSession?.Campaign?.Map?.CurrentLocation != null) { var reputationLoss = damageAmount * Reputation.ReputationLossPerWallDamage; - GameMain.GameSession.Campaign.Map.CurrentLocation.Reputation.Value -= reputationLoss; + GameMain.GameSession.Campaign.Map.CurrentLocation.Reputation.AddReputation(-reputationLoss); } if (accumulatedDamage <= WarningThreshold) { return; } @@ -1510,7 +1583,7 @@ namespace Barotrauma var reputationLoss = MathHelper.Clamp( (item.Prefab.GetMinPrice() ?? 0) * Reputation.ReputationLossPerStolenItemPrice, Reputation.MinReputationLossPerStolenItem, Reputation.MaxReputationLossPerStolenItem); - GameMain.GameSession.Campaign.Map.CurrentLocation.Reputation.Value -= reputationLoss; + GameMain.GameSession.Campaign.Map.CurrentLocation.Reputation.AddReputation(-reputationLoss); } item.StolenDuringRound = true; otherCharacter.Speak(TextManager.Get("dialogstealwarning"), null, Rand.Range(0.5f, 1.0f), "thief", 10.0f); @@ -1971,13 +2044,13 @@ namespace Barotrauma if (c.Removed) { continue; } if (c.TeamID != Character.TeamID) { continue; } if (c.IsIncapacitated) { continue; } - other = c; if (c.IsPlayer) { if (c.SelectedConstruction == target.Item) { // If the other character is player, don't try to operate - return true; + other = c; + break; } } else if (c.AIController is HumanAIController operatingAI) @@ -1991,7 +2064,8 @@ namespace Barotrauma if (!isOrder && isTargetOrdered) { // If the other bot is ordered to operate the item, let him do it, unless we are ordered too - return true; + other = c; + break; } else { @@ -2012,18 +2086,20 @@ namespace Barotrauma // Steering is hard-coded -> cannot use the required skills collection defined in the xml if (Character.GetSkillLevel("helm") <= c.GetSkillLevel("helm")) { - return true; + other = c; + break; } } else if (target.DegreeOfSuccess(Character) <= target.DegreeOfSuccess(c)) { - return true; + other = c; + break; } } } } } - return false; + return other != null; bool IsOrderedToOperateThis(AIController ai) => ai is HumanAIController humanAI && humanAI.ObjectiveManager.CurrentOrder is AIObjectiveOperateItem operateOrder && operateOrder.Component.Item == target.Item; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/IndoorsSteeringManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/IndoorsSteeringManager.cs index cb8bdeff7..cf2635e9d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/IndoorsSteeringManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/IndoorsSteeringManager.cs @@ -1,8 +1,8 @@ -using Barotrauma.Items.Components; +using Barotrauma.Extensions; +using Barotrauma.Items.Components; using Microsoft.Xna.Framework; using System; using System.Linq; -using Barotrauma.Extensions; using FarseerPhysics; namespace Barotrauma @@ -15,6 +15,11 @@ namespace Barotrauma private bool canOpenDoors; public bool CanBreakDoors { get; set; } + private bool ShouldBreakDoor(Door door) => + CanBreakDoors && + !door.Item.Indestructible && !door.Item.InvulnerableToDamage && + (door.Item.Submarine == null || door.Item.Submarine.TeamID != character.TeamID); + private Character character; private Vector2 currentTarget; @@ -23,7 +28,7 @@ namespace Barotrauma private float buttonPressCooldown; - const float ButtonPressInterval = 0.5f; + const float ButtonPressInterval = 0.25f; public SteeringPath CurrentPath { @@ -78,7 +83,7 @@ namespace Barotrauma public IndoorsSteeringManager(ISteerable host, bool canOpenDoors, bool canBreakDoors) : base(host) { - pathFinder = new PathFinder(WayPoint.WayPointList.FindAll(wp => wp.SpawnType == SpawnType.Path), indoorsSteering: true); + pathFinder = new PathFinder(WayPoint.WayPointList.FindAll(wp => wp.SpawnType == SpawnType.Path), true); pathFinder.GetNodePenalty = GetNodePenalty; this.canOpenDoors = canOpenDoors; @@ -111,9 +116,14 @@ namespace Barotrauma IsPathDirty = true; } - public void SteeringSeek(Vector2 target, float weight, Func startNodeFilter = null, Func endNodeFilter = null, Func nodeFilter = null, bool checkVisiblity = true) + public void SteeringSeekSimple(Vector2 targetSimPos, float weight = 1) { - steering += CalculateSteeringSeek(target, weight, startNodeFilter, endNodeFilter, nodeFilter, checkVisiblity); + steering += base.DoSteeringSeek(targetSimPos, weight); + } + + public void SteeringSeek(Vector2 target, float weight, float minGapWidth = 0, Func startNodeFilter = null, Func endNodeFilter = null, Func nodeFilter = null, bool checkVisiblity = true) + { + steering += CalculateSteeringSeek(target, weight, minGapWidth, startNodeFilter, endNodeFilter, nodeFilter, checkVisiblity); } /// @@ -158,42 +168,47 @@ namespace Barotrauma } } - private Vector2 CalculateSteeringSeek(Vector2 target, float weight, Func startNodeFilter = null, Func endNodeFilter = null, Func nodeFilter = null, bool checkVisibility = true) + private Vector2 CalculateSteeringSeek(Vector2 target, float weight, float minGapSize = 0, Func startNodeFilter = null, Func endNodeFilter = null, Func nodeFilter = null, bool checkVisibility = true) { - Vector2 targetDiff = target - currentTarget; - if (currentPath != null && currentPath.Nodes.Any()) + bool needsNewPath = currentPath == null || currentPath.Unreachable || currentPath.Finished; + if (!needsNewPath && character.Submarine != null && character.Params.PathFinderPriority > 0.5f) { - //current path calculated relative to a different sub than where the character is now - //take that into account when calculating if the target has moved - Submarine currentPathSub = currentPath?.Nodes.First().Submarine; - if (currentPathSub != character.Submarine && character.Submarine != null) + Vector2 targetDiff = target - currentTarget; + if (currentPath != null && currentPath.Nodes.Any() && character.Submarine != null) { - Vector2 subDiff = character.Submarine.SimPosition - currentPathSub.SimPosition; - targetDiff += subDiff; + //target in a different sub than where the character is now + //take that into account when calculating if the target has moved + Submarine currentPathSub = currentPath?.CurrentNode?.Submarine; + if (currentPathSub == character.Submarine) { currentPathSub = currentPath?.Nodes.LastOrDefault()?.Submarine; } + if (currentPathSub != character.Submarine && targetDiff.LengthSquared() > 1 && currentPathSub != null) + { + Vector2 subDiff = character.Submarine.SimPosition - currentPathSub.SimPosition; + targetDiff += subDiff; + } + } + if (targetDiff.LengthSquared() > 1) + { + needsNewPath = true; } } - bool needsNewPath = character.Params.PathFinderPriority > 0.5f && (currentPath == null || currentPath.Unreachable || targetDiff.LengthSquared() > 1); //find a new path if one hasn't been found yet or the target is different from the current target if (needsNewPath || findPathTimer < -1.0f) { IsPathDirty = true; if (findPathTimer < 0) { + SkipCurrentPathNodes(); currentTarget = target; Vector2 currentPos = host.SimPosition; - if (character != null && character.Submarine == null) + pathFinder.InsideSubmarine = character.Submarine != null && !character.Submarine.Info.IsRuin; + pathFinder.ApplyPenaltyToOutsideNodes = character.Submarine != null && character.PressureProtection <= 0; + var newPath = pathFinder.FindPath(currentPos, target, character.Submarine, "(Character: " + character.Name + ")", minGapSize, startNodeFilter, endNodeFilter, nodeFilter, checkVisibility: checkVisibility); + bool useNewPath = needsNewPath || currentPath == null || currentPath.CurrentNode == null || character.Submarine != null && findPathTimer < -1 && Math.Abs(character.AnimController.TargetMovement.X) <= 0; + if (newPath.Unreachable || newPath.Nodes.None()) { - var targetHull = Hull.FindHull(ConvertUnits.ToDisplayUnits(target), null, false); - if (targetHull != null && targetHull.Submarine != null) - { - currentPos -= targetHull.Submarine.SimPosition; - } + useNewPath = false; } - pathFinder.InsideSubmarine = character.Submarine != null; - pathFinder.ApplyPenaltyToOutsideNodes = character.PressureProtection <= 0; - var newPath = pathFinder.FindPath(currentPos, target, character.Submarine, "(Character: " + character.Name + ")", startNodeFilter, endNodeFilter, nodeFilter, checkVisibility: checkVisibility); - bool useNewPath = needsNewPath || currentPath == null || currentPath.CurrentNode == null || findPathTimer < -1 && Math.Abs(character.AnimController.TargetMovement.X) <= 0; - if (!useNewPath && currentPath != null && currentPath.CurrentNode != null && newPath.Nodes.Any() && !newPath.Unreachable) + else if (!useNewPath && currentPath != null && currentPath.CurrentNode != null) { // Check if the new path is the same as the old, in which case we just ignore it and continue using the old path (or the progress would reset). if (IsIdenticalPath()) @@ -205,7 +220,7 @@ namespace Barotrauma // Use the new path if it has significantly lower cost (don't change the path if it has marginally smaller cost. This reduces navigating backwards due to new path that is calculated from the node just behind us). float t = (float)currentPath.CurrentIndex / (currentPath.Nodes.Count - 1); useNewPath = newPath.Cost < currentPath.Cost * MathHelper.Lerp(0.95f, 0, t); - if (!useNewPath) + if (!useNewPath && character.Submarine != null) { // It's possible that the current path was calculated from a start point that is no longer valid. // Therefore, let's accept also paths with a greater cost than the current, if the current node is much farther than the new start node. @@ -232,12 +247,42 @@ namespace Barotrauma } if (useNewPath) { + if (currentPath != null) + { + CheckDoorsInPath(); + } currentPath = newPath; } float priority = MathHelper.Lerp(3, 1, character.Params.PathFinderPriority); findPathTimer = priority * Rand.Range(1.0f, 1.2f); IsPathDirty = false; return DiffToCurrentNode(); + + void SkipCurrentPathNodes() + { + if (!character.AnimController.InWater || character.Submarine != null) { return; } + if (CurrentPath == null || CurrentPath.Unreachable || CurrentPath.Finished) { return; } + if (CurrentPath.CurrentIndex < 0 || CurrentPath.CurrentIndex >= CurrentPath.Nodes.Count - 1) { return; } + // Check if we could skip ahead to NextNode when the character is swimming and using waypoints outside. + // Do this to optimize the old path before creating and evaluating a new path. + // In general, this is to avoid behavior where: + // a) the character goes back to first reach CurrentNode when the second node would be closer; or + // b) the character moves along the path when they could cut through open space to reduce the total distance. + float pathDistance = Vector2.Distance(character.WorldPosition, CurrentPath.CurrentNode.WorldPosition); + pathDistance += CurrentPath.GetLength(startIndex: CurrentPath.CurrentIndex); + for (int i = CurrentPath.Nodes.Count - 1; i > CurrentPath.CurrentIndex + 1; i--) + { + var waypoint = CurrentPath.Nodes[i]; + float directDistance = Vector2.DistanceSquared(character.WorldPosition, waypoint.WorldPosition); + if (directDistance > (pathDistance * pathDistance) || Submarine.PickBody(host.SimPosition, waypoint.SimPosition, collisionCategory: Physics.CollisionLevel | Physics.CollisionWall) != null) + { + pathDistance -= CurrentPath.GetLength(startIndex: i - 1, endIndex: i); + continue; + } + CurrentPath.SkipToNode(i); + break; + } + } } } @@ -271,25 +316,35 @@ namespace Barotrauma pos2 -= CurrentPath.Nodes.Last().Submarine.SimPosition; } return currentTarget - pos2; - } - if (canOpenDoors && !character.LockHands && buttonPressCooldown <= 0.0f) + } + bool doorsChecked = false; + if (!character.LockHands && buttonPressCooldown <= 0.0f) { CheckDoorsInPath(); + doorsChecked = true; } Vector2 pos = host.SimPosition; - if (character != null && currentPath.CurrentNode != null) + if (character != null && CurrentPath.CurrentNode != null) { - if (CurrentPath.CurrentNode.Submarine != null) + var nodeSub = CurrentPath.CurrentNode.Submarine; + if (nodeSub != null) { if (character.Submarine == null) { - pos -= CurrentPath.CurrentNode.Submarine.SimPosition; + // Going inside + pos -= ConvertUnits.ToSimUnits(nodeSub.Position); } - else if (character.Submarine != currentPath.CurrentNode.Submarine) + else if (character.Submarine != nodeSub) { - pos -= ConvertUnits.ToSimUnits(currentPath.CurrentNode.Submarine.Position - character.Submarine.Position); + // Different subs + pos -= ConvertUnits.ToSimUnits(nodeSub.Position - character.Submarine.Position); } } + else if (character.Submarine != null) + { + // Going outside + pos += ConvertUnits.ToSimUnits(character.Submarine.Position); + } } bool isDiving = character.AnimController.InWater && character.AnimController.HeadInWater; // Only humanoids can climb ladders @@ -362,7 +417,7 @@ namespace Barotrauma } if (isAboveFloor || nextLadderSameAsCurrent) { - currentPath.SkipToNextNode(); + NextNode(!doorsChecked); } } else if (nextLadder != null) @@ -372,7 +427,7 @@ namespace Barotrauma //e.g. no point in going down to reach the starting point of a path when we could go directly to the one above if (Math.Sign(currentPath.CurrentNode.WorldPosition.Y - character.WorldPosition.Y) != Math.Sign(currentPath.NextNode.WorldPosition.Y - character.WorldPosition.Y)) { - currentPath.SkipToNextNode(); + NextNode(!doorsChecked); } } return diff; @@ -394,7 +449,7 @@ namespace Barotrauma float distance = horizontalDistance + verticalDistance; if (ConvertUnits.ToSimUnits(distance) < targetDistance) { - currentPath.SkipToNextNode(); + NextNode(!doorsChecked); } } } @@ -419,7 +474,7 @@ namespace Barotrauma float targetDistance = Math.Max(colliderSize.X / 2 * margin, minWidth / 2); if (horizontalDistance < targetDistance && isAboveFeet && isNotTooHigh && (door == null || door.CanBeTraversed)) { - currentPath.SkipToNextNode(); + NextNode(!doorsChecked); } } if (currentPath.CurrentNode == null) @@ -429,28 +484,51 @@ namespace Barotrauma return currentPath.CurrentNode.SimPosition - pos; } + private void NextNode(bool checkDoors) + { + if (checkDoors) + { + CheckDoorsInPath(); + } + currentPath.SkipToNextNode(); + } + private bool CanAccessDoor(Door door, Func buttonFilter = null) { - if (door.IsOpen || door.IsBroken) { return true; } - if (!door.Item.IsInteractable(character)) { return false; } - if (!CanBreakDoors) + if (door.IsBroken) { return true; } + if (!door.IsOpen) { - if (door.IsStuck || door.IsJammed) { return false; } - if (!canOpenDoors || character.LockHands) { return false; } + if (!door.Item.IsInteractable(character)) { return false; } + if (!ShouldBreakDoor(door)) + { + if (door.IsStuck || door.IsJammed) { return false; } + if (!canOpenDoors || character.LockHands) { return false; } + } } if (door.HasIntegratedButtons) { - return door.HasAccess(character) || CanBreakDoors; + return door.IsOpen || door.HasAccess(character) || ShouldBreakDoor(door); } else { - return door.Item.GetConnectedComponents(true).Any(b => b.HasAccess(character) && (buttonFilter == null || buttonFilter(b))) || CanBreakDoors; + // We'll want this to run each time, because the delegate is used to find a valid button component. + bool canAccessButtons = door.Item.GetConnectedComponents(true).Any(b => b.HasAccess(character) && (buttonFilter == null || buttonFilter(b))); + return canAccessButtons || door.IsOpen || ShouldBreakDoor(door); } } + private Vector2 GetColliderSize() => ConvertUnits.ToDisplayUnits(character.AnimController.Collider.GetSize()); + + private float GetColliderLength() + { + Vector2 colliderSize = character.AnimController.Collider.GetSize(); + return ConvertUnits.ToDisplayUnits(Math.Max(colliderSize.X, colliderSize.Y)); + } + private void CheckDoorsInPath() { - for (int i = 0; i < 2; i++) + if (!canOpenDoors) { return; } + for (int i = 0; i < 5; i++) { WayPoint currentWaypoint = null; WayPoint nextWaypoint = null; @@ -461,17 +539,21 @@ namespace Barotrauma { door = currentPath.Nodes.First().ConnectedDoor; shouldBeOpen = door != null; + if (i > 0) { break; } } else { - if (i == 0) + bool closeDoors = character.IsBot && character.IsInFriendlySub || character.Params.AI != null && character.Params.AI.KeepDoorsClosed; + if (i == 0 || !closeDoors) { currentWaypoint = currentPath.CurrentNode; nextWaypoint = currentPath.NextNode; } else { - currentWaypoint = currentPath.PrevNode; + int previousIndex = currentPath.CurrentIndex - i; + if (previousIndex < 0) { break; } + currentWaypoint = currentPath.Nodes[previousIndex]; nextWaypoint = currentPath.CurrentNode; } if (currentWaypoint?.ConnectedDoor == null) { continue; } @@ -480,24 +562,30 @@ namespace Barotrauma { //the node we're heading towards is the last one in the path, and at a door //the door needs to be open for the character to reach the node - if (currentWaypoint.ConnectedDoor.LinkedGap != null && currentWaypoint.ConnectedDoor.LinkedGap.IsRoomToRoom) + if (currentWaypoint.ConnectedDoor.LinkedGap != null) { - shouldBeOpen = true; - door = currentWaypoint.ConnectedDoor; + // Keep the airlock doors closed, but not in ruins/wrecks + if (currentWaypoint.ConnectedDoor.LinkedGap.IsRoomToRoom || currentWaypoint.Submarine?.Info.IsRuin != null || currentWaypoint.Submarine?.Info.IsWreck != null) + { + shouldBeOpen = true; + door = currentWaypoint.ConnectedDoor; + } } } else { + float colliderLength = GetColliderLength(); door = currentWaypoint.ConnectedDoor; if (door.LinkedGap.IsHorizontal) { int dir = Math.Sign(nextWaypoint.WorldPosition.X - door.Item.WorldPosition.X); - shouldBeOpen = (door.Item.WorldPosition.X - character.WorldPosition.X) * dir > -50.0f; + float size = character.AnimController.InWater ? colliderLength : GetColliderSize().X; + shouldBeOpen = (door.Item.WorldPosition.X - character.WorldPosition.X) * dir > -size; } else { int dir = Math.Sign(nextWaypoint.WorldPosition.Y - door.Item.WorldPosition.Y); - shouldBeOpen = (door.Item.WorldPosition.Y - character.WorldPosition.Y) * dir > -80.0f; + shouldBeOpen = (door.Item.WorldPosition.Y - character.WorldPosition.Y) * dir > -colliderLength; } } } @@ -541,7 +629,7 @@ namespace Barotrauma } else if (closestButton != null) { - if (Vector2.DistanceSquared(closestButton.Item.WorldPosition, character.WorldPosition) < MathUtils.Pow(closestButton.Item.InteractDistance * 2, 2)) + if (Vector2.DistanceSquared(closestButton.Item.WorldPosition, character.WorldPosition) < MathUtils.Pow(closestButton.Item.InteractDistance + GetColliderLength(), 2)) { closestButton.Item.TryInteract(character, false, true); buttonPressCooldown = ButtonPressInterval; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/LatchOntoAI.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/LatchOntoAI.cs index 9ffdcf361..69270a61e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/LatchOntoAI.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/LatchOntoAI.cs @@ -19,16 +19,19 @@ namespace Barotrauma private Body targetBody; private Vector2 attachSurfaceNormal; private Submarine targetSubmarine; + private Character targetCharacter; private readonly Character character; public bool AttachToSub { get; private set; } public bool AttachToWalls { get; private set; } + public bool AttachToCharacters { get; private set; } - private readonly float minDeattachSpeed, maxDeattachSpeed; + private readonly float minDeattachSpeed, maxDeattachSpeed, maxAttachDuration, coolDown; private readonly float damageOnDetach, detachStun; - private float deattachTimer; + private readonly bool weld; + private float deattachCheckTimer; - private Vector2 wallAttachPos; + private Vector2 _attachPos; private float attachCooldown; @@ -38,9 +41,9 @@ namespace Barotrauma private float jointDir; - public List AttachJoints { get; } = new List(); + public List AttachJoints { get; } = new List(); - public Vector2? WallAttachPos + public Vector2? AttachPos { get; private set; @@ -48,18 +51,22 @@ namespace Barotrauma public bool IsAttached => AttachJoints.Count > 0; - public bool IsAttachedToSub => IsAttached && targetSubmarine != null; + public bool IsAttachedToSub => IsAttached && targetSubmarine != null && targetCharacter == null; public LatchOntoAI(XElement element, EnemyAIController enemyAI) { AttachToWalls = element.GetAttributeBool("attachtowalls", false); AttachToSub = element.GetAttributeBool("attachtosub", false); + AttachToCharacters = element.GetAttributeBool("attachtocharacters", false); minDeattachSpeed = element.GetAttributeFloat("mindeattachspeed", 5.0f); maxDeattachSpeed = Math.Max(minDeattachSpeed, element.GetAttributeFloat("maxdeattachspeed", 8.0f)); + maxAttachDuration = element.GetAttributeFloat("maxattachduration", -1.0f); + coolDown = element.GetAttributeFloat("cooldown", 2f); damageOnDetach = element.GetAttributeFloat("damageondetach", 0.0f); detachStun = element.GetAttributeFloat("detachstun", 0.0f); localAttachPos = ConvertUnits.ToSimUnits(element.GetAttributeVector2("localattachpos", Vector2.Zero)); attachLimbRotation = MathHelper.ToRadians(element.GetAttributeFloat("attachlimbrotation", 0.0f)); + weld = element.GetAttributeBool("weld", true); string limbString = element.GetAttributeString("attachlimb", null); attachLimb = enemyAI.Character.AnimController.Limbs.FirstOrDefault(l => string.Equals(l.Name, limbString, StringComparison.OrdinalIgnoreCase)); @@ -81,30 +88,54 @@ namespace Barotrauma public void SetAttachTarget(Structure wall, Vector2 attachPos, Vector2 attachSurfaceNormal) { + if (!AttachToSub) { return; } if (wall == null) { return; } var sub = wall.Submarine; if (sub == null) { return; } + Reset(); targetWall = wall; targetSubmarine = sub; targetBody = targetSubmarine.PhysicsBody.FarseerBody; this.attachSurfaceNormal = attachSurfaceNormal; - wallAttachPos = attachPos; + _attachPos = attachPos; + } + + public void SetAttachTarget(Character target) + { + if (!AttachToCharacters) { return; } + Reset(); + targetCharacter = target; + targetSubmarine = target.Submarine; + targetBody = target.AnimController.Collider.FarseerBody; + attachSurfaceNormal = Vector2.Normalize(character.WorldPosition - target.WorldPosition); } public void Update(EnemyAIController enemyAI, float deltaTime) { if (character.Submarine != null) { - DeattachFromBody(reset: true); - return; + if (targetCharacter != null && targetCharacter.Submarine != targetSubmarine || + character.Submarine != null && targetSubmarine != null && targetCharacter == null) + { + DeattachFromBody(reset: true); + return; + } } - if (AttachJoints.Count > 0) + if (IsAttached) { if (Math.Sign(attachLimb.Dir) != Math.Sign(jointDir)) { - AttachJoints[0].LocalAnchorA = - new Vector2(-AttachJoints[0].LocalAnchorA.X, AttachJoints[0].LocalAnchorA.Y); - AttachJoints[0].ReferenceAngle = -AttachJoints[0].ReferenceAngle; + var attachJoint = AttachJoints[0]; + if (attachJoint is WeldJoint weldJoint) + { + weldJoint.LocalAnchorA = new Vector2(-weldJoint.LocalAnchorA.X, weldJoint.LocalAnchorA.Y); + weldJoint.ReferenceAngle = -weldJoint.ReferenceAngle; + } + else if (attachJoint is RevoluteJoint revoluteJoint) + { + revoluteJoint.LocalAnchorA = new Vector2(-revoluteJoint.LocalAnchorA.X, revoluteJoint.LocalAnchorA.Y); + revoluteJoint.ReferenceAngle = -revoluteJoint.ReferenceAngle; + } jointDir = attachLimb.Dir; } for (int i = 0; i < AttachJoints.Count; i++) @@ -113,31 +144,51 @@ namespace Barotrauma if (Vector2.DistanceSquared(AttachJoints[i].WorldAnchorB, AttachJoints[i].BodyA.Position) > 10.0f * 10.0f) { #if DEBUG - DebugConsole.ThrowError("Limb body of the character \"" + character.Name + "\" is very far from the attach joint anchor -> deattach"); + DebugConsole.Log("Limb body of the character \"" + character.Name + "\" is very far from the attach joint anchor -> deattach"); #endif DeattachFromBody(reset: true); return; } } + if (targetCharacter != null) + { + if (enemyAI.AttackingLimb?.attack == null) + { + DeattachFromBody(reset: true, cooldown: 1); + } + else + { + float range = enemyAI.AttackingLimb.attack.DamageRange * 2f; + if (Vector2.DistanceSquared(targetCharacter.WorldPosition, enemyAI.AttackingLimb.WorldPosition) > range * range) + { + DeattachFromBody(reset: true, cooldown: 1); + } + } + } } if (attachCooldown > 0) { attachCooldown -= deltaTime; } - if (deattachTimer > 0) + if (deattachCheckTimer > 0) { - deattachTimer -= deltaTime; + deattachCheckTimer -= deltaTime; } - Vector2 transformedAttachPos = wallAttachPos; + if (targetCharacter != null) + { + // Own sim pos -> target where we are + _attachPos = character.SimPosition; + } + Vector2 transformedAttachPos = _attachPos; if (character.Submarine == null && targetSubmarine != null) { transformedAttachPos += ConvertUnits.ToSimUnits(targetSubmarine.Position); } if (transformedAttachPos != Vector2.Zero) { - WallAttachPos = transformedAttachPos; + AttachPos = transformedAttachPos; } switch (enemyAI.State) @@ -151,7 +202,7 @@ namespace Barotrauma //check if there are any walls nearby the character could attach to if (raycastTimer < 0.0f) { - wallAttachPos = Vector2.Zero; + _attachPos = Vector2.Zero; var cells = Level.Loaded.GetCells(character.WorldPosition, 1); if (cells.Count > 0) @@ -169,7 +220,7 @@ namespace Barotrauma { attachSurfaceNormal = edge.GetNormal(cell); targetBody = cell.Body; - wallAttachPos = potentialAttachPos; + _attachPos = potentialAttachPos; closestDist = distSqr; } break; @@ -183,21 +234,20 @@ namespace Barotrauma } else { - wallAttachPos = Vector2.Zero; + _attachPos = Vector2.Zero; } - - if (wallAttachPos == Vector2.Zero || targetBody == null) + if (_attachPos == Vector2.Zero || targetBody == null) { DeattachFromBody(reset: false); } else { - float squaredDistance = Vector2.DistanceSquared(character.SimPosition, wallAttachPos); + float squaredDistance = Vector2.DistanceSquared(character.SimPosition, _attachPos); float targetDistance = Math.Max(Math.Max(character.AnimController.Collider.radius, character.AnimController.Collider.width), character.AnimController.Collider.height) * 1.2f; if (squaredDistance < targetDistance * targetDistance) { //close enough to a wall -> attach - AttachToBody(wallAttachPos); + AttachToBody(_attachPos); enemyAI.SteeringManager.Reset(); } else @@ -205,25 +255,22 @@ namespace Barotrauma //move closer to the wall DeattachFromBody(reset: false); enemyAI.SteeringManager.SteeringAvoid(deltaTime, 1.0f, 0.1f); - enemyAI.SteeringManager.SteeringSeek(wallAttachPos); + enemyAI.SteeringManager.SteeringSeek(_attachPos); } } break; case AIState.Attack: case AIState.Aggressive: - if (enemyAI.AttackingLimb != null) + if (enemyAI.IsSteeringThroughGap) { break; } + if (_attachPos == Vector2.Zero) { break; } + if (!AttachToSub && !AttachToCharacters) { break; } + if (enemyAI.AttackingLimb == null) { break; } + if (targetBody == null) { break; } + if (IsAttached && AttachJoints[0].BodyB == targetBody) { break; } + Vector2 referencePos = targetCharacter != null ? targetCharacter.WorldPosition : ConvertUnits.ToDisplayUnits(transformedAttachPos); + if (Vector2.DistanceSquared(referencePos, enemyAI.AttackingLimb.WorldPosition) < enemyAI.AttackingLimb.attack.DamageRange * enemyAI.AttackingLimb.attack.DamageRange) { - if (AttachToSub && !enemyAI.IsSteeringThroughGap && wallAttachPos != Vector2.Zero && targetBody != null) - { - // is not attached or is attached to something else - if (!IsAttached || IsAttached && AttachJoints[0].BodyB != targetBody) - { - if (Vector2.DistanceSquared(ConvertUnits.ToDisplayUnits(transformedAttachPos), enemyAI.AttackingLimb.WorldPosition) < enemyAI.AttackingLimb.attack.DamageRange * enemyAI.AttackingLimb.attack.DamageRange) - { - AttachToBody(transformedAttachPos); - } - } - } + AttachToBody(transformedAttachPos); } break; default: @@ -231,43 +278,51 @@ namespace Barotrauma break; } - if (IsAttached && targetBody != null && targetWall != null && targetSubmarine != null && deattachTimer <= 0.0f) + if (IsAttached && targetBody != null && deattachCheckTimer <= 0.0f) { bool deattach = false; - // Deattach if the wall is broken enough where we are attached to - int targetSection = targetWall.FindSectionIndex(attachLimb.WorldPosition, world: true, clamp: true); - if (enemyAI.CanPassThroughHole(targetWall, targetSection)) + if (maxAttachDuration > 0) { deattach = true; - attachCooldown = 2; + attachCooldown = coolDown; } - if (!deattach) + if (!deattach && targetWall != null && targetSubmarine != null) { - // Deattach if the velocity is high - float velocity = targetSubmarine.Velocity == Vector2.Zero ? 0.0f : targetSubmarine.Velocity.Length(); - deattach = velocity > maxDeattachSpeed; + // Deattach if the wall is broken enough where we are attached to + int targetSection = targetWall.FindSectionIndex(attachLimb.WorldPosition, world: true, clamp: true); + if (enemyAI.CanPassThroughHole(targetWall, targetSection)) + { + deattach = true; + attachCooldown = coolDown; + } if (!deattach) { - if (velocity > minDeattachSpeed) + // Deattach if the velocity is high + float velocity = targetSubmarine.Velocity == Vector2.Zero ? 0.0f : targetSubmarine.Velocity.Length(); + deattach = velocity > maxDeattachSpeed; + if (!deattach) { - float velocityFactor = (maxDeattachSpeed - minDeattachSpeed <= 0.0f) ? - Math.Sign(Math.Abs(velocity) - minDeattachSpeed) : - (Math.Abs(velocity) - minDeattachSpeed) / (maxDeattachSpeed - minDeattachSpeed); - - if (Rand.Range(0.0f, 1.0f) < velocityFactor) + if (velocity > minDeattachSpeed) { - deattach = true; - character.AddDamage(character.WorldPosition, new List() { AfflictionPrefab.InternalDamage.Instantiate(damageOnDetach) }, detachStun, true); - attachCooldown = detachStun * 2; + float velocityFactor = (maxDeattachSpeed - minDeattachSpeed <= 0.0f) ? + Math.Sign(Math.Abs(velocity) - minDeattachSpeed) : + (Math.Abs(velocity) - minDeattachSpeed) / (maxDeattachSpeed - minDeattachSpeed); + + if (Rand.Range(0.0f, 1.0f) < velocityFactor) + { + deattach = true; + character.AddDamage(character.WorldPosition, new List() { AfflictionPrefab.InternalDamage.Instantiate(damageOnDetach) }, detachStun, true); + attachCooldown = Math.Max(detachStun * 2, coolDown); + } } } } + deattachCheckTimer = 5.0f; } if (deattach) { DeattachFromBody(reset: true); } - deattachTimer = 5.0f; } } @@ -315,16 +370,30 @@ namespace Barotrauma } collider.SetTransform(attachPos + attachSurfaceNormal * colliderFront.Length(), MathUtils.VectorToAngle(-attachSurfaceNormal) - MathHelper.PiOver2); - var colliderJoint = new WeldJoint(collider.FarseerBody, targetBody, colliderFront, targetBody.GetLocalPoint(attachPos), false) - { - FrequencyHz = 10.0f, - DampingRatio = 0.5f, - KinematicBodyB = true, - CollideConnected = false, - //Length = 0.1f - }; + Joint colliderJoint = weld ? + new WeldJoint(collider.FarseerBody, targetBody, colliderFront, targetBody.GetLocalPoint(attachPos), false) + { + FrequencyHz = 10.0f, + DampingRatio = 0.5f, + KinematicBodyB = true, + CollideConnected = false, + } : + new RevoluteJoint(collider.FarseerBody, targetBody, colliderFront, targetBody.GetLocalPoint(attachPos), false) + { + MotorEnabled = true, + MaxMotorTorque = 0.25f + } as Joint; + GameMain.World.Add(colliderJoint); - AttachJoints.Add(colliderJoint); + AttachJoints.Add(colliderJoint); + if (targetCharacter != null) + { + targetCharacter.Latchers.Add(this); + } + if (maxAttachDuration > 0) + { + deattachCheckTimer = maxAttachDuration; + } } public void DeattachFromBody(bool reset, float cooldown = 0) @@ -342,14 +411,23 @@ namespace Barotrauma { Reset(); } + if (targetCharacter != null) + { + targetCharacter.Latchers.Remove(this); + } } private void Reset() { + if (targetCharacter != null) + { + targetCharacter.Latchers.Remove(this); + } + targetCharacter = null; targetWall = null; targetSubmarine = null; targetBody = null; - WallAttachPos = null; + AttachPos = null; } private void OnCharacterDeath(Character character, CauseOfDeath causeOfDeath) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjective.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjective.cs index 66376cc1d..23bfc48ad 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjective.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjective.cs @@ -94,8 +94,9 @@ namespace Barotrauma if (_abandon) { #if DEBUG - if (HumanAIController.debugai && objectiveManager.IsOrder(this) && !objectiveManager.IsCurrentOrder()) + if (HumanAIController.debugai && objectiveManager.IsOrder(this) && !objectiveManager.IsCurrentOrder() && !objectiveManager.IsCurrentOrder()) { + // TODO: dismiss throw new Exception("Order abandoned!"); } #endif diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCleanupItem.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCleanupItem.cs index e2ca2f781..42950891c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCleanupItem.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCleanupItem.cs @@ -83,12 +83,13 @@ namespace Barotrauma if (suitableContainer != null) { bool equip = item.GetComponent() != null || - item.AllowedSlots.None(s => - s == InvSlotType.Card || - s == InvSlotType.Head || - s == InvSlotType.Headset || - s == InvSlotType.InnerClothes || - s == InvSlotType.OuterClothes); + item.AllowedSlots.Any(s => s != InvSlotType.Any) && + item.AllowedSlots.None(s => + s == InvSlotType.Card || + s == InvSlotType.Head || + s == InvSlotType.Headset || + s == InvSlotType.InnerClothes || + s == InvSlotType.OuterClothes); TryAddSubObjective(ref decontainObjective, () => new AIObjectiveDecontainItem(character, item, objectiveManager, targetContainer: suitableContainer.GetComponent()) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs index fa7112563..6bdddd863 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs @@ -101,17 +101,17 @@ namespace Barotrauma public enum CombatMode { - Defensive, - Offensive, - Arrest, - Retreat, - None + Defensive, // Use weapons against the enemy, but try to retreat to a safe place + Offensive, // Engage the enemy and keep attacking it + Arrest, // Try to arrest the enemy without using lethal weapons (stunning + handcuffs) + Retreat, // Run to a safe place without attacking the target + None // Don't use } public CombatMode Mode { get; private set; } private bool IsOffensiveOrArrest => initialMode == CombatMode.Offensive || initialMode == CombatMode.Arrest; - private bool TargetEliminated => IsEnemyDisabled || Enemy.IsUnconscious; + private bool TargetEliminated => IsEnemyDisabled || (Enemy.IsUnconscious && Enemy.Params.Health.ConstantHealthRegeneration <= 0.0f); private bool IsEnemyDisabled => Enemy == null || Enemy.Removed || Enemy.IsDead; private float AimSpeed => HumanAIController.AimSpeed; @@ -252,9 +252,40 @@ namespace Barotrauma { case CombatMode.Offensive: case CombatMode.Arrest: - Engage(); + Engage(deltaTime); break; case CombatMode.Defensive: + if (character.IsOnPlayerTeam && !Enemy.IsPlayer && objectiveManager.IsCurrentOrder()) + { + if ((character.CurrentHull == null || character.CurrentHull == Enemy.CurrentHull) && sqrDistance < 200 * 200) + { + Engage(deltaTime); + } + else + { + // Keep following the goto target + var gotoObjective = objectiveManager.GetOrder(); + if (gotoObjective != null) + { + gotoObjective.ForceAct(deltaTime); + if (!character.AnimController.InWater) + { + HumanAIController.FaceTarget(Enemy); + ForceWalk = true; + HumanAIController.AutoFaceMovement = false; + } + } + else + { + SteeringManager.Reset(); + } + } + } + else + { + Retreat(deltaTime); + } + break; case CombatMode.Retreat: Retreat(deltaTime); break; @@ -588,8 +619,9 @@ namespace Barotrauma // assume that it's required for the stun effect // as we can't check the status effect conditions here. var mobileBatteryTag = "mobilebattery"; - var containers = weapon.Item.Components.Where(ic => ic is ItemContainer container && - container.ContainableItems.Any(containable => containable.Identifiers.Any(id => id.Equals(mobileBatteryTag)))); + var containers = weapon.Item.Components.Where(ic => + ic is ItemContainer container && + container.ContainableItemIdentifiers.Contains(mobileBatteryTag)); // If there's no such container, assume that the melee weapon can stun without a battery. return containers.None() || containers.Any(container => (container as ItemContainer)?.Inventory.AllItems.Any(i => i != null && i.HasTag(mobileBatteryTag) && i.Condition > 0.0f) ?? false); @@ -670,6 +702,14 @@ namespace Barotrauma { RemoveSubObjective(ref retreatObjective); } + if (character.Submarine == null && sqrDistance < MathUtils.Pow2(maxDistance)) + { + // Swim away + SteeringManager.Reset(); + SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(character.WorldPosition - Enemy.WorldPosition)); + SteeringManager.SteeringAvoid(deltaTime, 5, weight: 2); + return; + } if (retreatTarget == null || (retreatObjective != null && !retreatObjective.CanBeCompleted)) { if (findHullTimer > 0) @@ -684,7 +724,10 @@ namespace Barotrauma } if (retreatTarget != null && character.CurrentHull != retreatTarget) { - TryAddSubObjective(ref retreatObjective, () => new AIObjectiveGoTo(retreatTarget, character, objectiveManager, false, true), + TryAddSubObjective(ref retreatObjective, () => new AIObjectiveGoTo(retreatTarget, character, objectiveManager, false, true) + { + UsePathingOutside = false + }, onAbandon: () => { if (Enemy != null && HumanAIController.VisibleHulls.Contains(Enemy.CurrentHull)) @@ -703,7 +746,7 @@ namespace Barotrauma } } - private void Engage() + private void Engage(float deltaTime) { if (WeaponComponent == null) { @@ -721,6 +764,21 @@ namespace Barotrauma RemoveSubObjective(ref retreatObjective); RemoveSubObjective(ref seekAmmunitionObjective); RemoveSubObjective(ref seekWeaponObjective); + if (character.Submarine == null && WeaponComponent is MeleeWeapon meleeWeapon) + { + if (sqrDistance > MathUtils.Pow2(meleeWeapon.Range)) + { + // Swim towards the target + SteeringManager.Reset(); + SteeringManager.SteeringSeek(character.GetRelativeSimPosition(Enemy), weight: 10); + SteeringManager.SteeringAvoid(deltaTime, 5, weight: 15); + } + else + { + SteeringManager.Reset(); + } + return; + } if (followTargetObjective != null && followTargetObjective.Target != Enemy) { RemoveFollowTarget(); @@ -728,6 +786,7 @@ namespace Barotrauma TryAddSubObjective(ref followTargetObjective, constructor: () => new AIObjectiveGoTo(Enemy, character, objectiveManager, repeat: true, getDivingGearIfNeeded: true, closeEnough: 50) { + UsePathingOutside = false, IgnoreIfTargetDead = true, DialogueIdentifier = "dialogcannotreachtarget", TargetName = Enemy.DisplayName, @@ -958,14 +1017,15 @@ namespace Barotrauma } if (reloadTimer > 0) { return; } if (holdFireCondition != null && holdFireCondition()) { return; } - float sqrDist = Vector2.DistanceSquared(character.Position, Enemy.Position); + sqrDistance = Vector2.DistanceSquared(character.WorldPosition, Enemy.WorldPosition); + distanceTimer = distanceCheckInterval; if (WeaponComponent is MeleeWeapon meleeWeapon) { bool closeEnough = true; float sqrRange = meleeWeapon.Range * meleeWeapon.Range; if (character.AnimController.InWater) { - if (sqrDist > sqrRange) + if (sqrDistance > sqrRange) { closeEnough = false; } @@ -992,6 +1052,7 @@ namespace Barotrauma if (closeEnough) { UseWeapon(deltaTime); + character.AIController.SteeringManager.Reset(); } else if (!character.IsFacing(Enemy.WorldPosition)) { @@ -1003,7 +1064,7 @@ namespace Barotrauma { if (WeaponComponent is RepairTool repairTool) { - if (sqrDist > repairTool.Range * repairTool.Range) { return; } + if (sqrDistance > repairTool.Range * repairTool.Range) { return; } } float aimFactor = MathHelper.PiOver2 * (1 - AimAccuracy); if (VectorExtensions.Angle(VectorExtensions.Forward(Weapon.body.TransformedRotation), Enemy.Position - Weapon.Position) < MathHelper.PiOver4 + aimFactor) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveExtinguishFire.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveExtinguishFire.cs index b6163032a..251c0362b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveExtinguishFire.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveExtinguishFire.cs @@ -53,9 +53,13 @@ namespace Barotrauma distanceFactor = 1; } float severity = AIObjectiveExtinguishFires.GetFireSeverity(targetHull); - if (severity > 0.5f && !isOrder) + if (severity > 0.75f && !isOrder && + targetHull.RoomName != null && + !targetHull.RoomName.Contains("reactor", StringComparison.OrdinalIgnoreCase) && + !targetHull.RoomName.Contains("engine", StringComparison.OrdinalIgnoreCase) && + !targetHull.RoomName.Contains("command", StringComparison.OrdinalIgnoreCase)) { - // Ignore severe fires unless ordered. (Let the fire drain all the oxygen instead). + // Ignore severe fires to prevent casualities unless ordered to extinguish. Priority = 0; Abandon = true; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveExtinguishFires.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveExtinguishFires.cs index 62787477d..97d9450c2 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveExtinguishFires.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveExtinguishFires.cs @@ -25,7 +25,7 @@ namespace Barotrauma /// /// 0-1 based on the horizontal size of all of the fires in the hull. /// - public static float GetFireSeverity(Hull hull) => MathHelper.Lerp(0, 1, MathUtils.InverseLerp(0, Math.Min(hull.Rect.Width, 1000), hull.FireSources.Sum(fs => fs.Size.X))); + public static float GetFireSeverity(Hull hull) => MathHelper.Lerp(0, 1, MathUtils.InverseLerp(0, 500, hull.FireSources.Sum(fs => fs.Size.X))); protected override IEnumerable GetList() => Hull.hullList; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFightIntruders.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFightIntruders.cs index 14c01cb47..6e13c261d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFightIntruders.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFightIntruders.cs @@ -56,13 +56,15 @@ namespace Barotrauma public static bool IsValidTarget(Character target, Character character) { if (target == null || target.Removed) { return false; } - if (target.IsDead || target.IsUnconscious) { return false; } + if (target.IsDead) { return false; } + if (target.IsUnconscious && target.Params.Health.ConstantHealthRegeneration <= 0.0f) { return false; } if (target == character) { return false; } if (target.Submarine == null) { return false; } if (character.Submarine == null) { return false; } if (target.CurrentHull == null) { return false; } if (HumanAIController.IsFriendly(character, target)) { return false; } if (!character.Submarine.IsConnectedTo(target.Submarine)) { return false; } + if (target.HasAbilityFlag(AbilityFlags.IgnoredByEnemyAI)) { return false; } return true; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindDivingGear.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindDivingGear.cs index 76c51c0d9..c42d0c6f0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindDivingGear.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindDivingGear.cs @@ -39,6 +39,10 @@ namespace Barotrauma return; } targetItem = character.Inventory.FindItemByTag(gearTag, true); + if (targetItem == null && gearTag == LIGHT_DIVING_GEAR) + { + targetItem = character.Inventory.FindItemByTag(HEAVY_DIVING_GEAR, true); + } if (targetItem == null || !character.HasEquippedItem(targetItem, slotType: InvSlotType.OuterClothes | InvSlotType.Head | InvSlotType.InnerClothes) && targetItem.ContainedItems.Any(i => i.HasTag(OXYGEN_SOURCE) && i.Condition > 0)) { TryAddSubObjective(ref getDivingGear, () => @@ -57,23 +61,37 @@ namespace Barotrauma }; }, onAbandon: () => Abandon = true, - onCompleted: () => RemoveSubObjective(ref getDivingGear)); + onCompleted: () => + { + RemoveSubObjective(ref getDivingGear); + if (gearTag == HEAVY_DIVING_GEAR && HumanAIController.HasItem(character, LIGHT_DIVING_GEAR, out IEnumerable masks, requireEquipped: true)) + { + foreach (Item mask in masks) + { + if (mask != targetItem) + { + character.Inventory.TryPutItem(mask, character, CharacterInventory.anySlot); + } + } + } + }); } else { - // Seek oxygen that has at least 10% condition left, if we are inside a friendly sub. - // The margin helps us to survive, because we might need some oxygen before we can find more oxygen. - // When we are venturing outside of our sub, let's just suppose that we have enough oxygen with us and optimize it so that we don't keep switching off half used tanks. - float min = character.Submarine != Submarine.MainSub ? 0.01f : MIN_OXYGEN; + float min = GetMinOxygen(character); if (targetItem.OwnInventory != null && targetItem.OwnInventory.AllItems.None(it => it != null && it.HasTag(OXYGEN_SOURCE) && it.Condition > min)) { TryAddSubObjective(ref getOxygen, () => { if (character.IsOnPlayerTeam) { - if (HumanAIController.HasItem(character, "oxygensource", out _, conditionPercentage: min)) + if (HumanAIController.HasItem(character, OXYGEN_SOURCE, out _, conditionPercentage: min)) { character.Speak(TextManager.Get("dialogswappingoxygentank"), null, 0, "swappingoxygentank", 30.0f); + if (character.Inventory.FindAllItems(i => i.HasTag(OXYGEN_SOURCE) && i.Condition > min).Count == 1) + { + character.Speak(TextManager.Get("dialoglastoxygentank"), null, 0.0f, "dialoglastoxygentank", 30.0f); + } } else { @@ -105,7 +123,7 @@ namespace Barotrauma onAbandon: () => { Abandon = true; - if (remainingTanks > 0 && !HumanAIController.HasItem(character, "oxygensource", out _, conditionPercentage: 0.01f)) + if (remainingTanks > 0 && !HumanAIController.HasItem(character, OXYGEN_SOURCE, out _, conditionPercentage: 0.01f)) { character.Speak(TextManager.Get("dialogcantfindtoxygen"), null, 0, "cantfindoxygen", 30.0f); } @@ -121,7 +139,7 @@ namespace Barotrauma int ReportOxygenTankCount() { if (character.Submarine != Submarine.MainSub) { return 1; } - int remainingOxygenTanks = Submarine.MainSub.GetItems(false).Count(i => i.HasTag("oxygensource") && i.Condition > 1); + int remainingOxygenTanks = Submarine.MainSub.GetItems(false).Count(i => i.HasTag(OXYGEN_SOURCE) && i.Condition > 1); if (remainingOxygenTanks == 0) { character.Speak(TextManager.Get("DialogOutOfOxygenTanks"), null, 0.0f, "outofoxygentanks", 30.0f); @@ -136,17 +154,6 @@ namespace Barotrauma } } - /// - /// Returns false only when no inventory can be found from the item. - /// - public static bool EjectEmptyTanks(Character actor, Item target, out IEnumerable containedItems) - { - containedItems = target.OwnInventory?.AllItems; - if (containedItems == null) { return false; } - AIController.UnequipEmptyItems(actor, target); - return true; - } - public override void Reset() { base.Reset(); @@ -154,5 +161,20 @@ namespace Barotrauma getOxygen = null; targetItem = null; } + + public static float GetMinOxygen(Character character) + { + // Seek oxygen that has at least 10% condition left, if we are inside a friendly sub. + // The margin helps us to survive, because we might need some oxygen before we can find more oxygen. + // When we are venturing outside of our sub, let's just suppose that we have enough oxygen with us and optimize it so that we don't keep switching off half used tanks. + float min = 0.01f; + float minOxygen = character.IsInFriendlySub ? MIN_OXYGEN : min; + if (minOxygen > min && character.Inventory.AllItems.Any(i => i.HasTag("oxygensource") && i.ConditionPercentage >= minOxygen)) + { + // There's a valid oxygen tank in the inventory -> no need to swap the tank too early. + minOxygen = min; + } + return minOxygen; + } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindSafety.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindSafety.cs index e582044fb..2c8f6a40b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindSafety.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindSafety.cs @@ -46,20 +46,25 @@ namespace Barotrauma } if (character.CurrentHull == null) { - Priority = (objectiveManager.IsCurrentOrder() || objectiveManager.HasActiveObjective()) && HumanAIController.HasDivingSuit(character) ? 0 : 100; + Priority = (objectiveManager.IsCurrentOrder() || + objectiveManager.IsCurrentOrder() || + objectiveManager.Objectives.Any(o => o.Priority > 0 && o is AIObjectiveCombat)) + && HumanAIController.HasDivingSuit(character) ? 0 : 100; } else { if (HumanAIController.NeedsDivingGear(character.CurrentHull, out bool needsSuit) && (needsSuit ? - !HumanAIController.HasDivingSuit(character, conditionPercentage: AIObjectiveFindDivingGear.MIN_OXYGEN) : - !HumanAIController.HasDivingGear(character, conditionPercentage: AIObjectiveFindDivingGear.MIN_OXYGEN))) + !HumanAIController.HasDivingSuit(character, conditionPercentage: AIObjectiveFindDivingGear.GetMinOxygen(character)) : + !HumanAIController.HasDivingGear(character, conditionPercentage: AIObjectiveFindDivingGear.GetMinOxygen(character)))) { Priority = 100; } - else if (objectiveManager.IsCurrentOrder() && character.Submarine != null && !HumanAIController.IsOnFriendlyTeam(character.TeamID, character.Submarine.TeamID)) + else if ((objectiveManager.IsCurrentOrder() || objectiveManager.IsCurrentOrder()) && + character.Submarine != null && !HumanAIController.IsOnFriendlyTeam(character.TeamID, character.Submarine.TeamID)) { - // Ordered to follow/hold position inside a hostile sub -> ignore find safety unless we need to find a diving gear + // Ordered to follow, hold position, or return back to main sub inside a hostile sub + // -> ignore find safety unless we need to find a diving gear Priority = 0; } Priority = MathHelper.Clamp(Priority, 0, 100); @@ -126,11 +131,11 @@ namespace Barotrauma bool needsEquipment = false; if (needsDivingSuit) { - needsEquipment = !HumanAIController.HasDivingSuit(character, AIObjectiveFindDivingGear.MIN_OXYGEN); + needsEquipment = !HumanAIController.HasDivingSuit(character, AIObjectiveFindDivingGear.GetMinOxygen(character)); } else if (needsDivingGear) { - needsEquipment = !HumanAIController.HasDivingGear(character, AIObjectiveFindDivingGear.MIN_OXYGEN); + needsEquipment = !HumanAIController.HasDivingGear(character, AIObjectiveFindDivingGear.GetMinOxygen(character)); } if (needsEquipment) { @@ -298,17 +303,21 @@ namespace Barotrauma Hull bestHull = null; float bestValue = 0; + bool bestIsAirlock = false; foreach (Hull hull in Hull.hullList.OrderByDescending(h => EstimateHullSuitability(h))) { if (hull.Submarine == null) { continue; } + // Ruins are mazes filled with water. There's no safe hulls and we don't want to use the resources on it. + if (hull.Submarine.Info.IsRuin) { continue; } if (!allowChangingTheSubmarine && hull.Submarine != character.Submarine) { continue; } if (hull.Rect.Height < ConvertUnits.ToDisplayUnits(character.AnimController.ColliderHeightFromFloor) * 2) { continue; } if (ignoredHulls != null && ignoredHulls.Contains(hull)) { continue; } if (HumanAIController.UnreachableHulls.Contains(hull)) { continue; } float hullSafety = 0; - if (character.CurrentHull != null && character.Submarine != null) + bool hullIsAirlock = false; + bool isCharacterInside = character.CurrentHull != null && character.Submarine != null; + if (isCharacterInside) { - // Inside if (!character.Submarine.IsConnectedTo(hull.Submarine)) { continue; } hullSafety = HumanAIController.GetHullSafety(hull, hull.GetConnectedHulls(true, 1), character); float yDist = Math.Abs(character.WorldPosition.Y - hull.WorldPosition.Y); @@ -325,7 +334,7 @@ namespace Barotrauma continue; } // Don't allow to go outside if not already outside. - var path = PathSteering.PathFinder.FindPath(character.SimPosition, hull.SimPosition, nodeFilter: node => node.Waypoint.CurrentHull != null); + var path = PathSteering.PathFinder.FindPath(character.SimPosition, hull.SimPosition, character.Submarine, nodeFilter: node => node.Waypoint.CurrentHull != null); if (path.Unreachable) { HumanAIController.UnreachableHulls.Add(hull); @@ -343,24 +352,16 @@ namespace Barotrauma } else { - // Outside - if (hull.RoomName != null && hull.RoomName.Contains("airlock", StringComparison.OrdinalIgnoreCase)) + // TODO: could also target gaps that get us inside? + if (hull.IsTaggedAirlock()) + { + hullSafety = 100; + hullIsAirlock = true; + } + else if(!bestIsAirlock && hull.LeadsOutside(character)) { hullSafety = 100; } - else - { - // TODO: could also target gaps that get us inside? - foreach (Item item in Item.ItemList) - { - if (item.CurrentHull != hull && item.HasTag("airlock")) - { - hullSafety = 100; - break; - } - } - } - // TODO: could we get a closest door to the outside and target the flowing hull if no airlock is found? // Huge preference for closer targets float distance = Vector2.DistanceSquared(character.WorldPosition, hull.WorldPosition); float distanceFactor = MathHelper.Lerp(1, 0.2f, MathUtils.InverseLerp(0, MathUtils.Pow(100000, 2), distance)); @@ -372,10 +373,11 @@ namespace Barotrauma hullSafety /= 10; } } - if (hullSafety > bestValue) + if (hullSafety > bestValue || (!isCharacterInside && hullIsAirlock && !bestIsAirlock)) { bestHull = hull; bestValue = hullSafety; + bestIsAirlock = hullIsAirlock; } } return bestHull; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGetItem.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGetItem.cs index 870b10333..dd98bf747 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGetItem.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGetItem.cs @@ -323,7 +323,7 @@ namespace Barotrauma // This is relatively expensive, so let's do this only when it significantly improves the behavior. // Only allow one path find call per frame. hasCalledPathFinder = true; - var path = PathSteering.PathFinder.FindPath(character.SimPosition, item.SimPosition, errorMsgStr: $"AIObjectiveGetItem {character.DisplayName}", nodeFilter: node => node.Waypoint.CurrentHull != null); + var path = PathSteering.PathFinder.FindPath(character.SimPosition, item.SimPosition, character.Submarine, errorMsgStr: $"AIObjectiveGetItem {character.DisplayName}", nodeFilter: node => node.Waypoint.CurrentHull != null); if (path.Unreachable) { continue; } } currItemPriority = itemPriority; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGoTo.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGoTo.cs index 099c63df4..a67611486 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGoTo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGoTo.cs @@ -27,6 +27,7 @@ namespace Barotrauma public bool followControlledCharacter; public bool mimic; public bool SpeakIfFails { get; set; } = true; + public bool UsePathingOutside { get; set; } = true; public float extraDistanceWhileSwimming; public float extraDistanceOutsideSub; @@ -121,13 +122,14 @@ namespace Barotrauma } private readonly float avoidLookAheadDistance = 5; + private readonly float pathWaitingTime = 3; public AIObjectiveGoTo(ISpatialEntity target, Character character, AIObjectiveManager objectiveManager, bool repeat = false, bool getDivingGearIfNeeded = true, float priorityModifier = 1, float closeEnough = 0) : base(character, objectiveManager, priorityModifier) { Target = target; this.repeat = repeat; - waitUntilPathUnreachable = 3.0f; + waitUntilPathUnreachable = pathWaitingTime; this.getDivingGearIfNeeded = getDivingGearIfNeeded; if (Target is Item i) { @@ -159,6 +161,8 @@ namespace Barotrauma } } + public void ForceAct(float deltaTime) => Act(deltaTime); + protected override void Act(float deltaTime) { if (followControlledCharacter) @@ -184,7 +188,6 @@ namespace Barotrauma // Wait character.AIController.SteeringManager.Reset(); } - waitUntilPathUnreachable -= deltaTime; if (!character.IsClimbing) { character.SelectedConstruction = null; @@ -220,11 +223,13 @@ namespace Barotrauma { Abandon = true; } - else if (SteeringManager == PathSteering && PathSteering.CurrentPath != null && PathSteering.CurrentPath.Unreachable && !PathSteering.IsPathDirty) + else if (HumanAIController.IsCurrentPathUnreachable) { + waitUntilPathUnreachable -= deltaTime; SteeringManager.Reset(); if (waitUntilPathUnreachable < 0) { + waitUntilPathUnreachable = pathWaitingTime; if (repeat) { SpeakCannotReach(); @@ -240,7 +245,7 @@ namespace Barotrauma if (getDivingGearIfNeeded && !character.LockHands) { Character followTarget = Target as Character; - bool needsDivingSuit = targetIsOutside; + bool needsDivingSuit = !isInside || targetIsOutside; bool needsDivingGear = needsDivingSuit || HumanAIController.NeedsDivingGear(targetHull, out needsDivingSuit); if (mimic) { @@ -255,7 +260,7 @@ namespace Barotrauma } } bool needsEquipment = false; - float minOxygen = character.Submarine == null ? 0 : AIObjectiveFindDivingGear.MIN_OXYGEN; + float minOxygen = AIObjectiveFindDivingGear.GetMinOxygen(character); if (needsDivingSuit) { needsEquipment = !HumanAIController.HasDivingSuit(character, minOxygen); @@ -323,25 +328,29 @@ namespace Barotrauma } else { - SeekGaps(maxGapDistance); - seekGapsTimer = seekGapsInterval * Rand.Range(0.1f, 1.1f); - if (TargetGap != null) + bool isRuins = character.Submarine?.Info.IsRuin != null || Target.Submarine?.Info.IsRuin != null; + if (!isRuins || !HumanAIController.HasValidPath(requireNonDirty: true, requireUnfinished: true)) { - // Check that nothing is blocking the way - Vector2 rayStart = character.SimPosition; - Vector2 rayEnd = TargetGap.SimPosition; - if (TargetGap.Submarine != null && character.Submarine == null) + SeekGaps(maxGapDistance); + seekGapsTimer = seekGapsInterval * Rand.Range(0.1f, 1.1f); + if (TargetGap != null) { - rayStart -= TargetGap.Submarine.SimPosition; - } - else if (TargetGap.Submarine == null && character.Submarine != null) - { - rayEnd -= character.Submarine.SimPosition; - } - var closestBody = Submarine.CheckVisibility(rayStart, rayEnd, ignoreSubs: true); - if (closestBody != null) - { - TargetGap = null; + // Check that nothing is blocking the way + Vector2 rayStart = character.SimPosition; + Vector2 rayEnd = TargetGap.SimPosition; + if (TargetGap.Submarine != null && character.Submarine == null) + { + rayStart -= TargetGap.Submarine.SimPosition; + } + else if (TargetGap.Submarine == null && character.Submarine != null) + { + rayEnd -= character.Submarine.SimPosition; + } + var closestBody = Submarine.CheckVisibility(rayStart, rayEnd, ignoreSubs: true); + if (closestBody != null) + { + TargetGap = null; + } } } } @@ -365,7 +374,7 @@ namespace Barotrauma if (checkScooterTimer <= 0) { useScooter = false; - checkScooterTimer = checkScooterTime; + checkScooterTimer = checkScooterTime * Rand.Range(0.75f, 1.25f); string scooterTag = "scooter"; string batteryTag = "mobilebattery"; Item scooter = null; @@ -444,18 +453,33 @@ namespace Barotrauma } if (SteeringManager == PathSteering) { + Vector2 targetPos = character.GetRelativeSimPosition(Target); Func nodeFilter = null; if (isInside && !AllowGoingOutside) { nodeFilter = n => n.Waypoint.CurrentHull != null; } + else if (!isInside && HumanAIController.UseIndoorSteeringOutside) + { + nodeFilter = n => n.Waypoint.Submarine == null; + } - PathSteering.SteeringSeek(character.GetRelativeSimPosition(Target), 1, - startNodeFilter: n => (n.Waypoint.CurrentHull == null) == (character.CurrentHull == null), - endNodeFilter, - nodeFilter, - CheckVisibility); - + if (!isInside && !UsePathingOutside) + { + PathSteering.SteeringSeekSimple(character.GetRelativeSimPosition(Target), 10); + if (character.AnimController.InWater) + { + SteeringManager.SteeringAvoid(deltaTime, avoidLookAheadDistance, weight: 15); + } + } + else + { + PathSteering.SteeringSeek(targetPos, weight: 1, + startNodeFilter: n => (n.Waypoint.CurrentHull == null) == (character.CurrentHull == null), + endNodeFilter: endNodeFilter, + nodeFilter: nodeFilter, + checkVisiblity: CheckVisibility); + } if (!isInside && (PathSteering.CurrentPath == null || PathSteering.IsPathDirty || PathSteering.CurrentPath.Unreachable)) { if (useScooter) @@ -501,9 +525,22 @@ namespace Barotrauma { character.CursorPosition -= character.Submarine.Position; } - Vector2 dir = Vector2.Normalize(character.CursorPosition - character.Position); - if (!MathUtils.IsValid(dir)) { dir = Vector2.UnitY; } - SteeringManager.SteeringManual(1.0f, dir); + Vector2 diff = character.CursorPosition - character.Position; + Vector2 dir = Vector2.Normalize(diff); + float sqrDist = diff.LengthSquared(); + if (sqrDist > MathUtils.Pow2(CloseEnough * 1.5f)) + { + SteeringManager.SteeringManual(1.0f, dir); + } + else + { + float dot = Vector2.Dot(dir, VectorExtensions.Forward(character.AnimController.Collider.Rotation + MathHelper.PiOver2)); + bool isFacing = dot > 0.9f; + if (!isFacing && sqrDist > MathUtils.Pow2(CloseEnough)) + { + SteeringManager.SteeringManual(1.0f, dir); + } + } character.SetInput(InputType.Aim, false, true); character.SetInput(InputType.Shoot, false, true); } @@ -511,7 +548,7 @@ namespace Barotrauma private bool useScooter; private float checkScooterTimer; - private readonly float checkScooterTime = 0.2f; + private readonly float checkScooterTime = 0.5f; public Hull GetTargetHull() => GetTargetHull(Target); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveIdle.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveIdle.cs index 1b7b68af7..4be1b47fd 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveIdle.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveIdle.cs @@ -242,9 +242,8 @@ namespace Barotrauma if (!searchingNewHull) { //find all available hulls first - FindTargetHulls(); searchingNewHull = true; - return; + FindTargetHulls(); } else if (targetHulls.Any()) { @@ -252,14 +251,13 @@ namespace Barotrauma currentTarget = ToolBox.SelectWeightedRandom(targetHulls, hullWeights, Rand.RandSync.Unsynced); bool isInWrongSub = (character.TeamID == CharacterTeamType.FriendlyNPC && !character.IsEscorted) && character.Submarine.TeamID != character.TeamID; bool isCurrentHullAllowed = !isInWrongSub && !IsForbidden(character.CurrentHull); - var path = PathSteering.PathFinder.FindPath(character.SimPosition, currentTarget.SimPosition, errorMsgStr: null, nodeFilter: node => + var path = PathSteering.PathFinder.FindPath(character.SimPosition, currentTarget.SimPosition, character.Submarine, nodeFilter: node => { if (node.Waypoint.CurrentHull == null) { return false; } - // Check that there is no unsafe or forbidden hulls on the way to the target + // Check that there is no unsafe hulls on the way to the target if (node.Waypoint.CurrentHull != character.CurrentHull && HumanAIController.UnsafeHulls.Contains(node.Waypoint.CurrentHull)) { return false; } - if (isCurrentHullAllowed && IsForbidden(node.Waypoint.CurrentHull)) { return false; } return true; - }); + }, endNodeFilter: node => !isCurrentHullAllowed | !IsForbidden(node.Waypoint.CurrentHull)); if (path.Unreachable) { //can't go to this room, remove it from the list and try another room @@ -271,31 +269,20 @@ namespace Barotrauma SetTargetTimerLow(); return; } + character.AIController.SelectTarget(currentTarget.AiTarget); + PathSteering.SetPath(path); + SetTargetTimerNormal(); searchingNewHull = false; } else { - // Couldn't find a target for some reason -> reset + // Couldn't find a valid hull SetTargetTimerHigh(); searchingNewHull = false; } - - if (currentTarget != null) - { - character.AIController.SelectTarget(currentTarget.AiTarget); - string errorMsg = null; -#if DEBUG - bool isRoomNameFound = currentTarget.DisplayName != null; - errorMsg = "(Character " + character.Name + " idling, target " + (isRoomNameFound ? currentTarget.DisplayName : currentTarget.ToString()) + ")"; -#endif - var path = PathSteering.PathFinder.FindPath(character.SimPosition, currentTarget.SimPosition, errorMsgStr: errorMsg, nodeFilter: node => node.Waypoint.CurrentHull != null); - PathSteering.SetPath(path); - } - SetTargetTimerNormal(); } newTargetTimer -= deltaTime; - - if (!character.IsClimbing && IsSteeringFinished()) + if (!character.IsClimbing && (PathSteering == null || PathSteering.CurrentPath == null || IsSteeringFinished())) { Wander(deltaTime); } @@ -406,9 +393,10 @@ namespace Barotrauma hullWeights.Clear(); foreach (var hull in Hull.hullList) { + if (character.Submarine == null) { break; } if (HumanAIController.UnsafeHulls.Contains(hull)) { continue; } if (hull.Submarine == null) { continue; } - if (character.Submarine == null) { break; } + if (hull.Submarine.Info.IsRuin || hull.Submarine.Info.IsWreck) { continue; } if (character.TeamID == CharacterTeamType.FriendlyNPC && !character.IsEscorted) { if (hull.Submarine.TeamID != character.TeamID) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveManager.cs index 6c868993d..4cb710919 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveManager.cs @@ -163,7 +163,7 @@ namespace Barotrauma CoroutineManager.StopCoroutines(coroutine); DelayedObjectives.Remove(objective); } - coroutine = CoroutineManager.InvokeAfter(() => + coroutine = CoroutineManager.Invoke(() => { //round ended before the coroutine finished #if CLIENT @@ -233,11 +233,7 @@ namespace Barotrauma if (orderObjective == null) { return; } #if DEBUG // Note: don't automatically remove orders here. Removing orders needs to be done via dismissing. - if (orderObjective.IsCompleted) - { - DebugConsole.NewMessage($"{character.Name}: ORDER {orderObjective.DebugTag} IS COMPLETED. CURRENTLY ALL ORDERS SHOULD BE LOOPING.", Color.Red); - } - else if (!orderObjective.CanBeCompleted) + if (!orderObjective.CanBeCompleted) { DebugConsole.NewMessage($"{character.Name}: ORDER {orderObjective.DebugTag}, CANNOT BE COMPLETED.", Color.Red); } @@ -281,9 +277,9 @@ namespace Barotrauma ForcedOrder?.CalculatePriority(); AIObjective orderWithHighestPriority = null; float highestPriority = 0; - foreach (var currentOrder in CurrentOrders) + for (int i = CurrentOrders.Count - 1; i >= 0; i--) { - var orderObjective = currentOrder.Objective; + var orderObjective = CurrentOrders[i].Objective; if (orderObjective == null) { continue; } orderObjective.CalculatePriority(); if (orderWithHighestPriority == null || orderObjective.Priority > highestPriority) @@ -467,6 +463,11 @@ namespace Barotrauma AllowGoingOutside = character.Submarine == null || (order.TargetSpatialEntity != null && character.Submarine != order.TargetSpatialEntity.Submarine) }; break; + case "return": + newObjective = new AIObjectiveReturn(character, orderGiver, this, priorityModifier: priorityModifier); + newObjective.Abandoned += () => DismissSelf(order, option); + newObjective.Completed += () => DismissSelf(order, option); + break; case "fixleaks": newObjective = new AIObjectiveFixLeaks(character, this, priorityModifier: priorityModifier, prioritizedHull: order.TargetEntity as Hull); break; @@ -586,6 +587,27 @@ namespace Barotrauma return newObjective; } + private void DismissSelf(Order order, string option) + { + var currentOrder = CurrentOrders.FirstOrDefault(oi => oi.MatchesOrder(order, option)); + if (currentOrder.Order == null) + { +#if DEBUG + DebugConsole.ThrowError("Tried to self-dismiss an order, but no matching current order was found"); +#endif + return; + } +#if CLIENT + if (GameMain.GameSession?.CrewManager != null && GameMain.GameSession.CrewManager.IsSinglePlayer) + { + GameMain.GameSession?.CrewManager?.SetCharacterOrder(character, Order.GetPrefab("dismissed"), Order.GetDismissOrderOption(currentOrder), currentOrder.ManualPriority, character); + } +#else + GameMain.Server?.SendOrderChatMessage(new OrderChatMessage(Order.GetPrefab("dismissed"), Order.GetDismissOrderOption(currentOrder), currentOrder.ManualPriority, currentOrder.Order?.TargetSpatialEntity, character, character)); +#endif + } + + private bool IsAllowedToWait() { if (!character.IsOnPlayerTeam) { return false; } @@ -606,6 +628,8 @@ namespace Barotrauma public bool IsActiveObjective() where T : AIObjective => GetActiveObjective() is T; public AIObjective GetActiveObjective() => CurrentObjective?.GetActiveObjective(); + public T GetOrder() where T : AIObjective => CurrentOrders.FirstOrDefault(o => o.Objective is T).Objective as T; + /// /// Returns the last active objective of the specific type. /// diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRescue.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRescue.cs index e9e310f16..66cc99a4a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRescue.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRescue.cs @@ -408,7 +408,7 @@ namespace Barotrauma } bool isCompleted = AIObjectiveRescueAll.GetVitalityFactor(targetCharacter) >= AIObjectiveRescueAll.GetVitalityThreshold(objectiveManager, character, targetCharacter) || - targetCharacter.CharacterHealth.GetAllAfflictions().All(a => a.Strength < a.Prefab.TreatmentThreshold); + targetCharacter.CharacterHealth.GetAllAfflictions().All(a => a.Strength <= a.Prefab.TreatmentThreshold); if (isCompleted && targetCharacter != character && character.IsOnPlayerTeam) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRescueAll.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRescueAll.cs index 73115b133..48936b9f6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRescueAll.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRescueAll.cs @@ -83,7 +83,7 @@ namespace Barotrauma if (character.AIController is HumanAIController humanAI) { if (GetVitalityFactor(target) >= GetVitalityThreshold(humanAI.ObjectiveManager, character, target) || - target.CharacterHealth.GetAllAfflictions().All(a => a.Strength < a.Prefab.TreatmentThreshold)) + target.CharacterHealth.GetAllAfflictions().All(a => a.Strength <= a.Prefab.TreatmentThreshold)) { return false; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveReturn.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveReturn.cs new file mode 100644 index 000000000..78d80ac98 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveReturn.cs @@ -0,0 +1,273 @@ +using Barotrauma.Extensions; +using Microsoft.Xna.Framework; +using System.Collections.Generic; + +namespace Barotrauma +{ + class AIObjectiveReturn : AIObjective + { + public override string Identifier { get; set; } = "return"; + private AIObjectiveGoTo moveInsideObjective, moveInCaveObjective, moveOutsideObjective; + private bool usingEscapeBehavior; + private bool isSteeringThroughGap; + public Submarine ReturnTarget { get; } + + public AIObjectiveReturn(Character character, Character orderGiver, AIObjectiveManager objectiveManager, float priorityModifier = 1.0f) : base(character, objectiveManager, priorityModifier) + { + ReturnTarget = GetReturnTarget(Submarine.MainSubs) ?? GetReturnTarget(Submarine.Loaded); + if (ReturnTarget == null) + { + DebugConsole.ThrowError("Error with a Return objective: no suitable return target found"); + Abandon = true; + } + + Submarine GetReturnTarget(IEnumerable subs) + { + var requiredTeamID = orderGiver?.TeamID ?? character?.TeamID; + Submarine returnTarget = null; + foreach (var sub in subs) + { + if (sub == null) { continue; } + if (sub.TeamID != requiredTeamID) { continue; } + returnTarget = sub; + break; + } + return returnTarget; + } + } + + protected override float GetPriority() + { + if (!Abandon && !IsCompleted && objectiveManager.IsOrder(this)) + { + Priority = objectiveManager.GetOrderPriority(this); + } + else + { + // TODO: Consider if this needs to be addressed + Priority = 0; + } + return Priority; + } + + protected override void Act(float deltaTime) + { + if (ReturnTarget == null) + { + Abandon = true; + return; + } + bool shouldUseEscapeBehavior = false; + if (character.CurrentHull != null || isSteeringThroughGap) + { + if (character.Submarine == null || !character.Submarine.IsConnectedTo(ReturnTarget)) + { + // Character is on another sub that is not connected to the target sub, use the escape behavior to get them out + shouldUseEscapeBehavior = true; + if (!usingEscapeBehavior) + { + HumanAIController.ResetEscape(); + } + isSteeringThroughGap = HumanAIController.Escape(deltaTime); + if (!isSteeringThroughGap && (HumanAIController.EscapeTarget == null || HumanAIController.IsCurrentPathUnreachable)) + { + Abandon = true; + } + } + else if (character.Submarine != ReturnTarget) + { + // Character is on another sub that is connected to the target sub, create a Go To objective to reach the target sub + if (moveInsideObjective == null) + { + Hull targetHull = null; + foreach (var d in ReturnTarget.ConnectedDockingPorts.Values) + { + if (!d.Docked) { continue; } + if (d.DockingTarget == null) { continue; } + if (d.DockingTarget.Item.Submarine != character.Submarine) { continue; } + targetHull = d.Item.CurrentHull; + break; + } + if (targetHull != null && !targetHull.IsTaggedAirlock()) + { + // Target the closest airlock + float closestDist = 0; + Hull airlock = null; + foreach (Hull hull in Hull.hullList) + { + if (hull.Submarine != targetHull.Submarine) { continue; } + if (!hull.IsTaggedAirlock()) { continue; } + float dist = Vector2.DistanceSquared(targetHull.Position, hull.Position); + if (airlock == null || closestDist <= 0 || dist < closestDist) + { + airlock = hull; + closestDist = dist; + } + + } + if (airlock != null) + { + targetHull = airlock; + } + } + if (targetHull != null) + { + RemoveSubObjective(ref moveInCaveObjective); + RemoveSubObjective(ref moveOutsideObjective); + TryAddSubObjective(ref moveInsideObjective, + constructor: () => new AIObjectiveGoTo(targetHull, character, objectiveManager) + { + AllowGoingOutside = true, + endNodeFilter = n => n.Waypoint.Submarine == targetHull.Submarine + }, + onCompleted: () => RemoveSubObjective(ref moveInsideObjective), + onAbandon: () => Abandon = true); + } + else + { +#if DEBUG + DebugConsole.ThrowError("Error with a Return objective: no suitable target for 'moveInsideObjective'"); +#endif + } + } + } + else + { + // Character is on the target sub, the objective is completed + IsCompleted = true; + } + } + else if (!isSteeringThroughGap && moveInCaveObjective == null && moveOutsideObjective == null) + { + if (HumanAIController.IsInsideCave) + { + WayPoint closestOutsideWaypoint = null; + float closestDistance = float.MaxValue; + foreach (var w in WayPoint.WayPointList) + { + if (w.Tunnel != null && w.Tunnel.Type == Level.TunnelType.Cave) { continue; } + if (w.linkedTo.None(l => l is WayPoint linkedWaypoint && linkedWaypoint.Tunnel?.Type == Level.TunnelType.Cave)) { continue; } + float distance = Vector2.DistanceSquared(character.WorldPosition, w.WorldPosition); + if (closestOutsideWaypoint == null || distance < closestDistance) + { + closestOutsideWaypoint = w; + closestDistance = distance; + } + } + if (closestOutsideWaypoint != null) + { + RemoveSubObjective(ref moveInsideObjective); + RemoveSubObjective(ref moveOutsideObjective); + TryAddSubObjective(ref moveInCaveObjective, + constructor: () => new AIObjectiveGoTo(closestOutsideWaypoint, character, objectiveManager) + { + endNodeFilter = n => n.Waypoint == closestOutsideWaypoint, + AllowGoingOutside = true + }, + onCompleted: () => RemoveSubObjective(ref moveInCaveObjective), + onAbandon: () => Abandon = true); + } + else + { +#if DEBUG + DebugConsole.ThrowError("Error with a Return objective: no suitable main or side path node target found for 'moveOutsideObjective'"); +#endif + } + } + else + { + Hull targetHull = null; + float targetDistanceSquared = float.MaxValue; + bool targetIsAirlock = false; + foreach (var hull in ReturnTarget.GetHulls(false)) + { + bool hullIsAirlock = hull.IsTaggedAirlock(); + if(hullIsAirlock || (!targetIsAirlock && hull.LeadsOutside(character))) + { + float distanceSquared = Vector2.DistanceSquared(character.WorldPosition, hull.WorldPosition); + if (targetHull == null || distanceSquared < targetDistanceSquared) + { + targetHull = hull; + targetDistanceSquared = distanceSquared; + targetIsAirlock = hullIsAirlock; + } + } + } + if (targetHull != null) + { + RemoveSubObjective(ref moveInsideObjective); + RemoveSubObjective(ref moveInCaveObjective); + TryAddSubObjective(ref moveOutsideObjective, + constructor: () => new AIObjectiveGoTo(targetHull, character, objectiveManager) + { + AllowGoingOutside = true + }, + onCompleted: () => RemoveSubObjective(ref moveOutsideObjective), + onAbandon: () => Abandon = true); + } + else + { +#if DEBUG + DebugConsole.ThrowError("Error with a Return objective: no suitable target for 'moveOutsideObjective'"); +#endif + } + } + } + else + { + if (HumanAIController.IsInsideCave) + { + RemoveSubObjective(ref moveOutsideObjective); + } + else + { + RemoveSubObjective(ref moveInCaveObjective); + } + } + usingEscapeBehavior = shouldUseEscapeBehavior; + } + + protected override bool CheckObjectiveSpecific() + { + if (IsCompleted) + { + return true; + } + if (ReturnTarget == null) + { + Abandon = true; + return false; + } + if (character.Submarine == ReturnTarget) + { + IsCompleted = true; + } + return IsCompleted; + } + + public override void Reset() + { + base.Reset(); + moveInsideObjective = null; + moveInCaveObjective = null; + moveOutsideObjective = null; + usingEscapeBehavior = false; + isSteeringThroughGap = false; + HumanAIController.ResetEscape(); + } + + protected override void OnAbandon() + { + base.OnAbandon(); + SteeringManager?.Reset(); + if (character.IsOnPlayerTeam && objectiveManager.CurrentOrder == objectiveManager.CurrentObjective) + { + string msg = TextManager.Get("dialogcannotreturn", returnNull: true); + if (msg != null) + { + character.Speak(msg, identifier: "dialogcannotreturn", minDurationBetweenSimilar: 5.0f); + } + } + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Order.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Order.cs index 09a73221f..57b024e16 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Order.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Order.cs @@ -141,6 +141,7 @@ namespace Barotrauma public Entity TargetEntity; public ItemComponent TargetItemComponent; public readonly bool UseController; + public readonly string[] ControllerTags; public Controller ConnectedController; public Character OrderGiver; @@ -309,6 +310,7 @@ namespace Barotrauma color = orderElement.GetAttributeColor("color"); FadeOutTime = orderElement.GetAttributeFloat("fadeouttime", 0.0f); UseController = orderElement.GetAttributeBool("usecontroller", false); + ControllerTags = orderElement.GetAttributeStringArray("controllertags", new string[0]); TargetAllCharacters = orderElement.GetAttributeBool("targetallcharacters", false); AppropriateJobs = orderElement.GetAttributeStringArray("appropriatejobs", new string[0]); Options = orderElement.GetAttributeStringArray("options", new string[0]); @@ -380,6 +382,7 @@ namespace Barotrauma SymbolSprite = prefab.SymbolSprite; Color = prefab.Color; UseController = prefab.UseController; + ControllerTags = prefab.ControllerTags; TargetAllCharacters = prefab.TargetAllCharacters; AppropriateJobs = prefab.AppropriateJobs; FadeOutTime = prefab.FadeOutTime; @@ -399,7 +402,7 @@ namespace Barotrauma { if (UseController) { - ConnectedController = targetItem.Item?.FindController(); + ConnectedController = targetItem.Item?.FindController(tags: ControllerTags); if (ConnectedController == null) { DebugConsole.AddWarning("AI: Tried to use a controller for operating an item, but couldn't find any."); @@ -450,19 +453,37 @@ namespace Barotrauma return false; } - public string GetChatMessage(string targetCharacterName, string targetRoomName, bool givingOrderToSelf, string orderOption = "") + public string GetChatMessage(string targetCharacterName, string targetRoomName, bool givingOrderToSelf, string orderOption = "", int? priority = null) { - orderOption ??= ""; - - string messageTag = (givingOrderToSelf && !TargetAllCharacters ? "OrderDialogSelf." : "OrderDialog.") + Identifier; - if (Identifier != "dismissed" && !string.IsNullOrEmpty(orderOption)) { messageTag += "." + orderOption; } - - if (targetCharacterName == null) { targetCharacterName = ""; } - if (targetRoomName == null) { targetRoomName = ""; } - string msg = TextManager.GetWithVariables(messageTag, new string[2] { "[name]", "[roomname]" }, new string[2] { targetCharacterName, targetRoomName }, new bool[2] { false, true }, true); - if (msg == null) { return ""; } - - return msg; + priority ??= CharacterInfo.HighestManualOrderPriority; + // If the order has a lesser priority, it means we are rearranging character orders + if (!TargetAllCharacters && priority != CharacterInfo.HighestManualOrderPriority && Identifier != "dismissed") + { + return TextManager.GetWithVariable("rearrangedorders", "[name]", targetCharacterName ?? string.Empty, returnNull: true) ?? string.Empty; + } + string messageTag = $"{(givingOrderToSelf && !TargetAllCharacters ? "OrderDialogSelf" : "OrderDialog")}"; + messageTag += $".{Identifier}"; + if (!string.IsNullOrEmpty(orderOption)) + { + if (Identifier != "dismissed") + { + messageTag += $".{orderOption}"; + } + else + { + string[] splitOption = orderOption.Split('.'); + if (splitOption.Length > 0) + { + messageTag += $".{splitOption[0]}"; + } + } + } + string msg = TextManager.GetWithVariables(messageTag, + new string[2] { "[name]", "[roomname]" }, + new string[2] { targetCharacterName ?? string.Empty, targetRoomName ?? string.Empty }, + formatCapitals: new bool[2] { false, true }, + returnNull: true); + return msg ?? string.Empty; } /// @@ -505,7 +526,7 @@ namespace Barotrauma if (item.NonInteractable) { continue; } if (ItemComponentType != null && item.Components.None(c => c.GetType() == ItemComponentType)) { continue; } Controller controller = null; - if (UseController && !item.TryFindController(out controller)) { continue; } + if (UseController && !item.TryFindController(out controller, tags: ControllerTags)) { continue; } if (interactableFor != null && (!item.IsInteractable(interactableFor) || (UseController && !controller.Item.IsInteractable(interactableFor)))) { continue; } matchingItems.Add(item); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PathFinder.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PathFinder.cs index 82b4ed49b..e46839e24 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PathFinder.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PathFinder.cs @@ -1,4 +1,5 @@ -using Microsoft.Xna.Framework; +using Barotrauma.Extensions; +using Microsoft.Xna.Framework; using System; using System.Collections.Generic; using System.Linq; @@ -78,6 +79,31 @@ namespace Barotrauma return nodeList; } + + private bool? blocked; + public bool IsBlocked() + { + if (blocked.HasValue) { return blocked.Value; } + + blocked = false; + + if (Waypoint.Submarine != null) { return blocked.Value; } + if (Waypoint.Tunnel?.Type != Level.TunnelType.Cave) { return blocked.Value; } + foreach (var w in Level.Loaded.ExtraWalls) + { + if (!(w is DestructibleLevelWall d)) { return blocked.Value; } + if (d.Destroyed) { return blocked.Value; } + if (!d.IsPointInside(Waypoint.Position)) { return blocked.Value; } + blocked = true; + break; + } + return blocked.Value; + } + + public void ResetBlocked() + { + blocked = null; + } } class PathFinder @@ -86,96 +112,116 @@ namespace Barotrauma public GetNodePenaltyHandler GetNodePenalty; private readonly List nodes; - public readonly bool IndoorsSteering; + private readonly bool isCharacter; public bool InsideSubmarine { get; set; } public bool ApplyPenaltyToOutsideNodes { get; set; } - public PathFinder(List wayPoints, bool indoorsSteering = false) + public PathFinder(List wayPoints, bool isCharacter) { - nodes = PathNode.GenerateNodes(wayPoints.FindAll(w => w.Submarine != null == indoorsSteering), removeOrphans: true); - + var filtered = isCharacter ? wayPoints : wayPoints.FindAll(w => w.Submarine == null); + nodes = PathNode.GenerateNodes(filtered, removeOrphans: true); foreach (WayPoint wp in wayPoints) { - wp.linkedTo.CollectionChanged += WaypointLinksChanged; + wp.OnLinksChanged += WaypointLinksChanged; } - - IndoorsSteering = indoorsSteering; + this.isCharacter = isCharacter; } - void WaypointLinksChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) + void WaypointLinksChanged(WayPoint wp) { if (Submarine.Unloading) { return; } - var waypoints = sender as IEnumerable; + var node = nodes.Find(n => n.Waypoint == wp); + if (node == null) { return; } - foreach (MapEntity me in waypoints) + for (int i = node.connections.Count - 1; i >= 0; i--) { - WayPoint wp = me as WayPoint; - if (me == null) { continue; } - - var node = nodes.Find(n => n.Waypoint == wp); - if (node == null) { return; } - - if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Remove) + //remove connection if the waypoint isn't connected anymore + if (wp.linkedTo.FirstOrDefault(l => l == node.connections[i].Waypoint) == null) { - for (int i = node.connections.Count - 1; i >= 0; i--) - { - //remove connection if the waypoint isn't connected anymore - if (wp.linkedTo.FirstOrDefault(l => l == node.connections[i].Waypoint) == null) - { - node.connections.RemoveAt(i); - node.distances.RemoveAt(i); - } - } + node.connections.RemoveAt(i); + node.distances.RemoveAt(i); } - else if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add) + } + + for (int i = 0; i < wp.linkedTo.Count; i++) + { + if (!(wp.linkedTo[i] is WayPoint connected)) { continue; } + + //already connected, continue + if (node.connections.Any(n => n.Waypoint == connected)) { continue; } + + var matchingNode = nodes.Find(n => n.Waypoint == connected); + if (matchingNode == null) { - for (int i = 0; i < wp.linkedTo.Count; i++) - { - if (!(wp.linkedTo[i] is WayPoint connected)) { continue; } - - //already connected, continue - if (node.connections.Any(n => n.Waypoint == connected)) { continue; } - - var matchingNode = nodes.Find(n => n.Waypoint == connected); - if (matchingNode == null) - { #if DEBUG - DebugConsole.ThrowError("Waypoint connections were changed, no matching path node found in PathFinder"); + DebugConsole.ThrowError("Waypoint connections were changed, no matching path node found in PathFinder"); #endif - return; - } - - node.connections.Add(matchingNode); - node.distances.Add(Vector2.Distance(node.Position, matchingNode.Position)); - } + return; } + + node.connections.Add(matchingNode); + node.distances.Add(Vector2.Distance(node.Position, matchingNode.Position)); } } - private static readonly List sortedNodes = new List(); + private readonly List sortedNodes = new List(); - public SteeringPath FindPath(Vector2 start, Vector2 end, Submarine hostSub = null, string errorMsgStr = null, Func startNodeFilter = null, Func endNodeFilter = null, Func nodeFilter = null, bool checkVisibility = true) + public SteeringPath FindPath(Vector2 start, Vector2 end, Submarine hostSub = null, string errorMsgStr = null, float minGapSize = 0, Func startNodeFilter = null, Func endNodeFilter = null, Func nodeFilter = null, bool checkVisibility = true) { + foreach (PathNode node in nodes) + { + node.ResetBlocked(); + } + //sort nodes roughly according to distance sortedNodes.Clear(); foreach (PathNode node in nodes) { node.TempPosition = node.Position; - if (hostSub != null) + var wpSub = node.Waypoint.Submarine; + if (hostSub != null && wpSub == null) { - Vector2 diff = hostSub.SimPosition - node.Waypoint.Submarine.SimPosition; - node.TempPosition -= diff; + // inside and targeting outside + node.TempPosition -= hostSub.SimPosition; + } + else if (wpSub != null && hostSub != null && wpSub != hostSub) + { + // different subs + node.TempPosition -= hostSub.SimPosition - wpSub.SimPosition; + } + else if (hostSub == null && wpSub != null) + { + // Outside and targeting inside + node.TempPosition += wpSub.SimPosition; } float xDiff = Math.Abs(start.X - node.TempPosition.X); float yDiff = Math.Abs(start.Y - node.TempPosition.Y); - if (yDiff > 1.0f && node.Waypoint.Ladders == null && node.Waypoint.Stairs == null) { yDiff += 10.0f; } - node.TempDistance = xDiff + (InsideSubmarine ? yDiff * 10.0f : yDiff); //higher cost for vertical movement when inside the sub + if (InsideSubmarine && !(node.Waypoint.Submarine?.Info?.IsRuin ?? false)) + { + //higher cost for vertical movement when inside the sub + if (yDiff > 1.0f && node.Waypoint.Ladders == null && node.Waypoint.Stairs == null) + { + yDiff += 10.0f; + } + node.TempDistance = xDiff + yDiff * 10.0f; + } + else + { + node.TempDistance = xDiff + yDiff; + } //much higher cost to waypoints that are outside if (node.Waypoint.CurrentHull == null && ApplyPenaltyToOutsideNodes) { node.TempDistance *= 10.0f; } + //optimization: + //node extremely far, don't try to use it as a start node + if (node.TempDistance > 800.0f) + { + continue; + } + //prefer nodes that are closer to the end position node.TempDistance += (Math.Abs(end.X - node.TempPosition.X) + Math.Abs(end.Y - node.TempPosition.Y)) / 100.0f; @@ -187,31 +233,39 @@ namespace Barotrauma sortedNodes.Insert(i, node); } + bool IsWaypointVisible(PathNode node, Vector2 rayStart, bool checkVisibility = true) + { + //if searching for a path inside the sub, make sure the waypoint is visible + if (checkVisibility && isCharacter) + { + if (node.Waypoint.isObstructed) { return false; } + var body = Submarine.PickBody(rayStart, node.TempPosition, + collisionCategory: Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionStairs); + if (body != null) + { + if (body.UserData is Submarine) { return false; } + if (body.UserData is Structure s && !s.IsPlatform) { return false; } + if (body.UserData is Item && body.FixtureList[0].CollisionCategories.HasFlag(Physics.CollisionWall)) { return false; } + } + } + return true; + } + //find the most suitable start node, starting from the ones that are the closest PathNode startNode = null; foreach (PathNode node in sortedNodes) { - if (startNode == null || node.TempDistance < startNode.TempDistance) + if (nodeFilter != null && !nodeFilter(node)) { continue; } + if (startNodeFilter != null && !startNodeFilter(node)) { continue; } + // Always check the visibility for the start node + if (!IsWaypointVisible(node, start)) { continue; } + if (node.IsBlocked()) { continue; } + if (node.Waypoint.ConnectedGap != null) { - if (nodeFilter != null && !nodeFilter(node)) { continue; } - if (startNodeFilter != null && !startNodeFilter(node)) { continue; } - //if searching for a path inside the sub, make sure the waypoint is visible - if (IndoorsSteering) - { - if (node.Waypoint.isObstructed) { continue; } - - // Always check the visibility for the start node - var body = Submarine.PickBody( - start, node.TempPosition, null, - Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionStairs); - if (body != null) - { - if (body.UserData is Structure && !((Structure)body.UserData).IsPlatform) { continue; } - if (body.UserData is Item && body.FixtureList[0].CollisionCategories.HasFlag(Physics.CollisionWall)) { continue; } - } - } - startNode = node; + if (!CanFitThroughGap(node.Waypoint.ConnectedGap, minGapSize)) { continue; } } + startNode = node; + break; } if (startNode == null) @@ -252,28 +306,17 @@ namespace Barotrauma PathNode endNode = null; foreach (PathNode node in sortedNodes) { - if (endNode == null || node.TempDistance < endNode.TempDistance) + if (nodeFilter != null && !nodeFilter(node)) { continue; } + if (endNodeFilter != null && !endNodeFilter(node)) { continue; } + // Only check the visibility for the end node when allowed (fix leaks) + if (!IsWaypointVisible(node, end, checkVisibility: checkVisibility)) { continue; } + if (node.IsBlocked()) { continue; } + if (node.Waypoint.ConnectedGap != null) { - if (nodeFilter != null && !nodeFilter(node)) { continue; } - if (endNodeFilter != null && !endNodeFilter(node)) { continue; } - if (IndoorsSteering) - { - if (node.Waypoint.isObstructed) { continue; } - //if searching for a path inside the sub, make sure the waypoint is visible - if (checkVisibility) - { - // Only check the visibility for the end node when allowed (fix leaks) - var body = Submarine.PickBody(end, node.TempPosition, null, - Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionStairs); - if (body != null) - { - if (body.UserData is Structure && !((Structure)body.UserData).IsPlatform) { continue; } - if (body.UserData is Item && body.FixtureList[0].CollisionCategories.HasFlag(Physics.CollisionWall)) { continue; } - } - } - } - endNode = node; + if (!CanFitThroughGap(node.Waypoint.ConnectedGap, minGapSize)) { continue; } } + endNode = node; + break; } if (endNode == null) @@ -284,40 +327,12 @@ namespace Barotrauma return new SteeringPath(true); } - var path = FindPath(startNode, endNode, nodeFilter, errorMsgStr); + var path = FindPath(startNode, endNode, nodeFilter, errorMsgStr, minGapSize); return path; } - public SteeringPath FindPath(WayPoint start, WayPoint end) - { - PathNode startNode = null, endNode = null; - foreach (PathNode node in nodes) - { - if (node.Waypoint == start) - { - startNode = node; - if (endNode != null) { break; } - } - if (node.Waypoint == end) - { - endNode = node; - if (startNode != null) { break; } - } - } - - if (startNode == null || endNode == null) - { -#if DEBUG - DebugConsole.NewMessage("Pathfinding error, couldn't find matching pathnodes to waypoints.", Color.DarkRed); -#endif - return new SteeringPath(true); - } - - return FindPath(startNode, endNode); - } - - private SteeringPath FindPath(PathNode start, PathNode end, Func filter = null, string errorMsgStr = "") + private SteeringPath FindPath(PathNode start, PathNode end, Func filter = null, string errorMsgStr = "", float minGapSize = 0) { if (start == end) { @@ -342,14 +357,16 @@ namespace Barotrauma float dist = float.MaxValue; foreach (PathNode node in nodes) { - if (node.state != 1) { continue; } - if (IndoorsSteering && node.Waypoint.isObstructed) { continue; } + if (node.state != 1 || node.F > dist) { continue; } + if (isCharacter && node.Waypoint.isObstructed) { continue; } if (filter != null && !filter(node)) { continue; } - if (node.F < dist) + if (node.IsBlocked()) { continue; } + if (node.Waypoint.ConnectedGap != null) { - dist = node.F; - currNode = node; - } + if (!CanFitThroughGap(node.Waypoint.ConnectedGap, minGapSize)) { continue; } + } + dist = node.F; + currNode = node; } if (currNode == null || currNode == end) { break; } @@ -451,6 +468,8 @@ namespace Barotrauma return path; } + + private bool CanFitThroughGap(Gap gap, float minWidth) => gap.IsHorizontal ? gap.RectHeight > minWidth : gap.RectWidth > minWidth; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/SteeringManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/SteeringManager.cs index 780ec45e4..d9039091d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/SteeringManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/SteeringManager.cs @@ -57,7 +57,10 @@ namespace Barotrauma public void SteeringManual(float deltaTime, Vector2 velocity) { - steering += velocity; + if (MathUtils.IsValid(velocity)) + { + steering += velocity; + } } public void Reset() diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/SteeringPath.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/SteeringPath.cs index 79c8dbefa..24a3feb2b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/SteeringPath.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/SteeringPath.cs @@ -1,4 +1,5 @@ using Microsoft.Xna.Framework; +using System; using System.Collections.Generic; namespace Barotrauma @@ -24,16 +25,47 @@ namespace Barotrauma if (Unreachable) { return float.PositiveInfinity; } if (!totalLength.HasValue) { - totalLength = 0.0f; - for (int i = 0; i < nodes.Count - 1; i++) - { - totalLength += Vector2.Distance(nodes[i].WorldPosition, nodes[i + 1].WorldPosition); - } + CalculateTotalLength(); } return totalLength.Value; } } + public float GetLength(int? startIndex = null, int? endIndex = null) + { + if (Unreachable) { return float.PositiveInfinity; } + startIndex ??= 0; + endIndex ??= Nodes.Count - 1; + if (startIndex == 0 && endIndex == Nodes.Count - 1) + { + return TotalLength; + } + if (!totalLength.HasValue) + { + CalculateTotalLength(); + } + float length = 0.0f; + for (int i = startIndex.Value; i < endIndex.Value; i++) + { + length += nodeDistances[i]; + } + return length; + } + + private void CalculateTotalLength() + { + totalLength = 0.0f; + nodeDistances.Clear(); + for (int i = 0; i < nodes.Count - 1; i++) + { + float distance = Vector2.Distance(nodes[i].WorldPosition, nodes[i + 1].WorldPosition); + totalLength += distance; + nodeDistances.Add(distance); + } + } + + private readonly List nodeDistances = new List(); + public SteeringPath(bool unreachable = false) { nodes = new List(); @@ -107,6 +139,11 @@ namespace Barotrauma currentIndex++; } + public void SkipToNode(int nodeIndex) + { + currentIndex = nodeIndex; + } + public WayPoint CheckProgress(Vector2 simPosition, float minSimDistance = 0.1f) { if (nodes.Count == 0 || currentIndex > nodes.Count - 1) { return null; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Wreck/WreckAI.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Wreck/WreckAI.cs index 3d5ac5a49..dc6e181ae 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Wreck/WreckAI.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Wreck/WreckAI.cs @@ -161,8 +161,8 @@ namespace Barotrauma for (int i = 0; i < container.Inventory.Capacity; i++) { if (container.Inventory.GetItemAt(i) != null) { continue; } - if (MapEntityPrefab.List.GetRandom(e => e is ItemPrefab i && container.CanBeContained(i) && - Config.ForbiddenAmmunition.None(id => id.Equals(i.Identifier, StringComparison.OrdinalIgnoreCase)), Rand.RandSync.Server) is ItemPrefab ammoPrefab) + if (MapEntityPrefab.List.GetRandom(e => e is ItemPrefab ip && container.CanBeContained(ip, i) && + Config.ForbiddenAmmunition.None(id => id.Equals(ip.Identifier, StringComparison.OrdinalIgnoreCase)), Rand.RandSync.Server) is ItemPrefab ammoPrefab) { Item ammo = new Item(ammoPrefab, container.Item.WorldPosition, Wreck); if (!container.Inventory.TryPutItem(ammo, i, allowSwapping: false, allowCombine: false, user: null, createNetworkEvent: false)) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/AnimController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/AnimController.cs index 702670425..bbaba7ba8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/AnimController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/AnimController.cs @@ -1,12 +1,31 @@ -using FarseerPhysics; +using Barotrauma.Items.Components; +using FarseerPhysics; using Microsoft.Xna.Framework; -using System.Collections.Generic; using System; +using System.Collections.Generic; +using System.Linq; +using Barotrauma.Extensions; namespace Barotrauma { abstract class AnimController : Ragdoll { + public Vector2 RightHandIKPos { get; protected set; } + public Vector2 LeftHandIKPos { get; protected set; } + + protected LimbJoint rightShoulder, leftShoulder; + protected float upperArmLength, forearmLength; + protected float useItemTimer; + protected bool aiming; + protected bool wasAiming; + protected bool aimingMelee; + protected bool wasAimingMelee; + + public bool IsAiming => wasAiming; + public bool IsAimingMelee => wasAimingMelee; + + public float ArmLength => upperArmLength + forearmLength; + public abstract GroundedMovementParams WalkParams { get; set; } public abstract GroundedMovementParams RunParams { get; set; } public abstract SwimParams SwimSlowParams { get; set; } @@ -42,6 +61,10 @@ namespace Barotrauma } else { + if (this is HumanoidAnimController humanAnimController && humanAnimController.Crouching) + { + return humanAnimController.HumanCrouchParams; + } return IsMovingFast ? RunParams : WalkParams; } } @@ -56,14 +79,14 @@ namespace Barotrauma } else { - return IsMovingFast? SwimFastParams : SwimSlowParams; + return IsMovingFast ? SwimFastParams : SwimSlowParams; } } } public bool CanWalk => RagdollParams.CanWalk; public bool IsMovingBackwards => !InWater && Math.Sign(targetMovement.X) == -Math.Sign(Dir); - + // TODO: define death anim duration in XML protected float deathAnimTimer, deathAnimDuration = 5.0f; @@ -96,7 +119,12 @@ namespace Barotrauma { if (CanWalk) { - return new List { WalkParams, RunParams, SwimSlowParams, SwimFastParams }; + var anims = new List { WalkParams, RunParams, SwimSlowParams, SwimFastParams }; + if (this is HumanoidAnimController humanAnimController) + { + anims.Add(humanAnimController.HumanCrouchParams); + } + return anims; } else { @@ -146,15 +174,11 @@ namespace Barotrauma public AnimController(Character character, string seed, RagdollParams ragdollParams = null) : base(character, seed, ragdollParams) { } - public virtual void UpdateAnim(float deltaTime) { } + public abstract void UpdateAnim(float deltaTime); - public virtual void HoldItem(float deltaTime, Item item, Vector2[] handlePos, Vector2 holdPos, Vector2 aimPos, bool aim, float holdAngle, float itemAngleRelativeToHoldAngle = 0.0f) { } + public abstract void DragCharacter(Character target, float deltaTime); - public virtual void DragCharacter(Character target, float deltaTime) { } - - public virtual void UpdateUseItem(bool allowMovement, Vector2 handWorldPos) { } - - public float GetSpeed(AnimationType type) + public virtual float GetSpeed(AnimationType type) { GroundedMovementParams movementParams; switch (type) @@ -207,7 +231,14 @@ namespace Barotrauma } else { - animType = AnimationType.Walk; + if (this is HumanoidAnimController humanAnimController && humanAnimController.Crouching) + { + animType = AnimationType.Crouch; + } + else + { + animType = AnimationType.Walk; + } } } return GetSpeed(animType); @@ -221,6 +252,12 @@ namespace Barotrauma return WalkParams; case AnimationType.Run: return RunParams; + case AnimationType.Crouch: + if (this is HumanoidAnimController humanAnimController) + { + return humanAnimController.HumanCrouchParams; + } + throw new NotImplementedException(type.ToString()); case AnimationType.SwimSlow: return SwimSlowParams; case AnimationType.SwimFast: @@ -231,5 +268,459 @@ namespace Barotrauma throw new NotImplementedException(type.ToString()); } } + + public void UpdateUseItem(bool allowMovement, Vector2 handWorldPos) + { + useItemTimer = 0.5f; + Anim = Animation.UsingConstruction; + + if (!allowMovement) + { + TargetMovement = Vector2.Zero; + TargetDir = handWorldPos.X > character.WorldPosition.X ? Direction.Right : Direction.Left; + float sqrDist = Vector2.DistanceSquared(character.WorldPosition, handWorldPos); + if (sqrDist > MathUtils.Pow(ConvertUnits.ToDisplayUnits(upperArmLength + forearmLength), 2)) + { + TargetMovement = Vector2.Normalize(handWorldPos - character.WorldPosition) * GetCurrentSpeed(false) * Math.Max(character.SpeedMultiplier, 1); + } + } + + if (!character.Enabled) { return; } + + Vector2 handSimPos = ConvertUnits.ToSimUnits(handWorldPos); + if (character.Submarine != null) + { + handSimPos -= character.Submarine.SimPosition; + } + + var leftHand = GetLimb(LimbType.LeftHand); + if (leftHand != null) + { + leftHand.Disabled = true; + leftHand.PullJointEnabled = true; + leftHand.PullJointWorldAnchorB = handSimPos; + } + + var rightHand = GetLimb(LimbType.RightHand); + if (rightHand != null) + { + rightHand.Disabled = true; + rightHand.PullJointEnabled = true; + rightHand.PullJointWorldAnchorB = handSimPos; + } + } + + public void Grab(Vector2 rightHandPos, Vector2 leftHandPos) + { + for (int i = 0; i < 2; i++) + { + Limb pullLimb = (i == 0) ? GetLimb(LimbType.LeftHand) : GetLimb(LimbType.RightHand); + + pullLimb.Disabled = true; + + pullLimb.PullJointEnabled = true; + pullLimb.PullJointWorldAnchorB = (i == 0) ? rightHandPos : leftHandPos; + pullLimb.PullJointMaxForce = 500.0f; + } + } + + private Direction previousDirection; + private readonly Vector2[] transformedHandlePos = new Vector2[2]; + //TODO: refactor this method, it's way too convoluted + public void HoldItem(float deltaTime, Item item, Vector2[] handlePos, Vector2 holdPos, Vector2 aimPos, bool aim, float holdAngle, float itemAngleRelativeToHoldAngle = 0.0f, bool aimMelee = false) + { + aimingMelee = aimMelee; + if (character.Stun > 0.0f || character.IsIncapacitated) + { + aim = false; + } + + //calculate the handle positions + Matrix itemTransfrom = Matrix.CreateRotationZ(item.body.Rotation); + float horizontalOffset = ConvertUnits.ToSimUnits((item.Sprite.size.X / 2 - item.Sprite.Origin.X) * item.Scale); + + //handlePos[0] = ConvertUnits.ToSimUnits(new Vector2(-45,25) * 0.5f); + //handlePos[1] = ConvertUnits.ToSimUnits(new Vector2(-65,30) * 0.5f); + + transformedHandlePos[0] = Vector2.Transform(new Vector2(handlePos[0].X + horizontalOffset, handlePos[0].Y), itemTransfrom); + transformedHandlePos[1] = Vector2.Transform(new Vector2(handlePos[1].X + horizontalOffset, handlePos[1].Y), itemTransfrom); + + Limb torso = GetLimb(LimbType.Torso) ?? MainLimb; + Limb leftHand = GetLimb(LimbType.LeftHand); + Limb rightHand = GetLimb(LimbType.RightHand); + + Vector2 itemPos = aim ? aimPos : holdPos; + + var controller = character.SelectedConstruction?.GetComponent(); + bool usingController = controller != null && !controller.AllowAiming; + bool isClimbing = character.IsClimbing && Math.Abs(character.AnimController.TargetMovement.Y) > 0.01f; + float itemAngle; + Holdable holdable = item.GetComponent(); + float torsoRotation = torso.Rotation; + + Item rightHandItem = character.Inventory?.GetItemInLimbSlot(InvSlotType.RightHand); + bool equippedInRightHand = rightHandItem == item && rightHand != null && !rightHand.IsSevered; + Item leftHandItem = character.Inventory?.GetItemInLimbSlot(InvSlotType.LeftHand); + bool equippedInLefthand = leftHandItem == item && leftHand != null && !leftHand.IsSevered; + if (aim && !isClimbing && !usingController && character.Stun <= 0.0f && itemPos != Vector2.Zero && !character.IsIncapacitated) + { + Vector2 mousePos = ConvertUnits.ToSimUnits(character.SmoothedCursorPosition); + Vector2 diff = holdable.Aimable ? (mousePos - AimSourceSimPos) * Dir : Vector2.UnitX; + holdAngle = MathUtils.VectorToAngle(new Vector2(diff.X, diff.Y * Dir)) - torsoRotation * Dir; + holdAngle += GetAimWobble(rightHand, leftHand, item); + itemAngle = torsoRotation + holdAngle * Dir; + + if (holdable.ControlPose) + { + //if holding two items that should control the characters' pose, let the item in the right hand do it + bool anotherItemControlsPose = equippedInLefthand && rightHandItem != item && (rightHandItem?.GetComponent()?.ControlPose ?? false); + if (!anotherItemControlsPose) + { + var head = GetLimb(LimbType.Head); + if (head != null) + { + head.body.SmoothRotate(itemAngle, force: 30 * head.Mass); + } + if (TargetMovement == Vector2.Zero && inWater) + { + torso.body.AngularVelocity -= torso.body.AngularVelocity * 0.1f; + torso.body.ApplyForce(torso.body.LinearVelocity * -0.5f); + } + } + aiming = true; + } + } + else + { + if (holdable.UseHandRotationForHoldAngle) + { + if (equippedInRightHand) + { + itemAngle = rightHand.Rotation + holdAngle * Dir; + } + else if (equippedInLefthand) + { + itemAngle = leftHand.Rotation + holdAngle * Dir; + } + else + { + itemAngle = torsoRotation + holdAngle * Dir; + } + } + else + { + itemAngle = torsoRotation + holdAngle * Dir; + } + } + + if (rightShoulder == null) { return; } + Vector2 transformedHoldPos = rightShoulder.WorldAnchorA; + if (itemPos == Vector2.Zero || isClimbing || usingController) + { + if (equippedInRightHand) + { + transformedHoldPos = rightHand.PullJointWorldAnchorA - transformedHandlePos[0]; + itemAngle = rightHand.Rotation + (holdAngle - rightHand.Params.GetSpriteOrientation() + MathHelper.PiOver2) * Dir; + } + else if (equippedInLefthand) + { + transformedHoldPos = leftHand.PullJointWorldAnchorA - transformedHandlePos[1]; + itemAngle = leftHand.Rotation + (holdAngle - leftHand.Params.GetSpriteOrientation() + MathHelper.PiOver2) * Dir; + } + } + else + { + if (equippedInRightHand) + { + transformedHoldPos = rightShoulder.WorldAnchorA; + rightHand.Disabled = true; + } + if (equippedInLefthand) + { + if (leftShoulder == null) { return; } + transformedHoldPos = leftShoulder.WorldAnchorA; + leftHand.Disabled = true; + } + itemPos.X *= Dir; + transformedHoldPos += Vector2.Transform(itemPos, Matrix.CreateRotationZ(itemAngle)); + } + + item.body.ResetDynamics(); + + Vector2 currItemPos = equippedInRightHand ? + rightHand.PullJointWorldAnchorA - transformedHandlePos[0] : + leftHand.PullJointWorldAnchorA - transformedHandlePos[1]; + + if (!MathUtils.IsValid(currItemPos)) + { + string errorMsg = "Attempted to move the item \"" + item + "\" to an invalid position in HumanidAnimController.HoldItem: " + + currItemPos + ", rightHandPos: " + rightHand.PullJointWorldAnchorA + ", leftHandPos: " + leftHand.PullJointWorldAnchorA + + ", handlePos[0]: " + handlePos[0] + ", handlePos[1]: " + handlePos[1] + + ", transformedHandlePos[0]: " + transformedHandlePos[0] + ", transformedHandlePos[1]:" + transformedHandlePos[1] + + ", item pos: " + item.SimPosition + ", itemAngle: " + itemAngle + + ", collider pos: " + character.SimPosition; + DebugConsole.Log(errorMsg); + GameAnalyticsManager.AddErrorEventOnce( + "HumanoidAnimController.HoldItem:InvalidPos:" + character.Name + item.Name, + GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + errorMsg); + + return; + } + + if (holdable.Pusher != null) + { + if (character.Stun > 0.0f || character.IsIncapacitated) + { + holdable.Pusher.Enabled = false; + } + else + { + if (!holdable.Pusher.Enabled) + { + holdable.Pusher.Enabled = true; + holdable.Pusher.ResetDynamics(); + holdable.Pusher.SetTransform(currItemPos, itemAngle); + } + else + { + holdable.Pusher.TargetPosition = currItemPos; + holdable.Pusher.TargetRotation = holdAngle * Dir; + + holdable.Pusher.MoveToTargetPosition(true); + + currItemPos = holdable.Pusher.SimPosition; + itemAngle = holdable.Pusher.Rotation; + } + } + } + float targetAngle = MathUtils.WrapAngleTwoPi(itemAngle + itemAngleRelativeToHoldAngle * Dir); + float currentRotation = MathUtils.WrapAngleTwoPi(item.body.Rotation); + float itemRotation = MathHelper.SmoothStep(currentRotation, targetAngle, deltaTime * 25); + if (previousDirection != dir || Math.Abs(targetAngle - currentRotation) > MathHelper.Pi) + { + itemRotation = targetAngle; + } + item.SetTransform(currItemPos, itemRotation, setPrevTransform: false); + previousDirection = dir; + + if (!isClimbing && !character.IsIncapacitated && itemPos != Vector2.Zero && (aim || !holdable.UseHandRotationForHoldAngle)) + { + for (int i = 0; i < 2; i++) + { + if (!character.Inventory.IsInLimbSlot(item, i == 0 ? InvSlotType.RightHand : InvSlotType.LeftHand)) { continue; } +#if DEBUG + if (handlePos[i].LengthSquared() > ArmLength) + { + DebugConsole.AddWarning($"Aim position for the item {item.Name} may be incorrect (further than the length of the character's arm)"); + } +#endif + HandIK( + i == 0 ? rightHand : leftHand, transformedHoldPos + transformedHandlePos[i], + CurrentAnimationParams.ArmIKStrength, + CurrentAnimationParams.HandIKStrength, + maxAngularVelocity: 15.0f); + } + } + } + + private float GetAimWobble(Limb rightHand, Limb leftHand, Item heldItem) + { + float wobbleStrength = 0.0f; + if (character.Inventory?.GetItemInLimbSlot(InvSlotType.RightHand) == heldItem) + { + wobbleStrength += Character.CharacterHealth.GetLimbDamage(rightHand, afflictionType: "damage"); + } + if (character.Inventory?.GetItemInLimbSlot(InvSlotType.LeftHand) == heldItem) + { + wobbleStrength += Character.CharacterHealth.GetLimbDamage(leftHand, afflictionType: "damage"); + } + if (wobbleStrength <= 0.1f) { return 0.0f; } + wobbleStrength = (float)Math.Min(wobbleStrength, 1.0f); + + float lowFreqNoise = PerlinNoise.GetPerlin((float)Timing.TotalTime / 320.0f, (float)Timing.TotalTime / 240.0f) - 0.5f; + float highFreqNoise = PerlinNoise.GetPerlin((float)Timing.TotalTime / 40.0f, (float)Timing.TotalTime / 50.0f) - 0.5f; + + return (lowFreqNoise * 1.0f + highFreqNoise * 0.1f) * wobbleStrength; + } + + public void HandIK(Limb hand, Vector2 pos, float armTorque = 1.0f, float handTorque = 1.0f, float maxAngularVelocity = float.PositiveInfinity) + { + Vector2 shoulderPos; + + Limb arm, forearm; + if (hand.type == LimbType.LeftHand) + { + if (leftShoulder == null) { return; } + shoulderPos = leftShoulder.WorldAnchorA; + arm = GetLimb(LimbType.LeftArm); + forearm = GetLimb(LimbType.LeftForearm); + LeftHandIKPos = pos; + } + else + { + if (rightShoulder == null) { return; } + shoulderPos = rightShoulder.WorldAnchorA; + arm = GetLimb(LimbType.RightArm); + forearm = GetLimb(LimbType.RightForearm); + RightHandIKPos = pos; + } + if (arm == null) { return; } + + //distance from shoulder to holdpos + float c = Vector2.Distance(pos, shoulderPos); + c = MathHelper.Clamp(c, Math.Abs(upperArmLength - forearmLength), forearmLength + upperArmLength - 0.01f); + + float armAngle = MathUtils.VectorToAngle(pos - shoulderPos) + arm.Params.GetSpriteOrientation() - MathHelper.PiOver2; + float upperArmAngle = MathUtils.SolveTriangleSSS(forearmLength, upperArmLength, c) * Dir; + float lowerArmAngle = MathUtils.SolveTriangleSSS(upperArmLength, forearmLength, c) * Dir; + + //make sure the arm angle "has the same number of revolutions" as the arm + while (arm.Rotation - armAngle > MathHelper.Pi) + { + armAngle += MathHelper.TwoPi; + } + while (arm.Rotation - armAngle < -MathHelper.Pi) + { + armAngle -= MathHelper.TwoPi; + } + + if (arm?.body != null && Math.Abs(arm.body.AngularVelocity) < maxAngularVelocity) + { + arm.body.SmoothRotate(armAngle - upperArmAngle, 100.0f * armTorque * arm.Mass, wrapAngle: false); + } + float forearmAngle = armAngle + lowerArmAngle; + if (forearm?.body != null && Math.Abs(forearm.body.AngularVelocity) < maxAngularVelocity) + { + forearm.body.SmoothRotate(forearmAngle, 100.0f * handTorque * forearm.Mass, wrapAngle: false); + } + if (hand?.body != null && Math.Abs(hand.body.AngularVelocity) < maxAngularVelocity) + { + float handAngle = forearm != null ? forearmAngle : armAngle; + hand.body.SmoothRotate(handAngle, 10.0f * handTorque * hand.Mass, wrapAngle: false); + } + } + + public void ApplyPose(Vector2 leftHandPos, Vector2 rightHandPos, Vector2 leftFootPos, Vector2 rightFootPos, float footMoveForce = 10) + { + var leftHand = GetLimb(LimbType.LeftHand); + var rightHand = GetLimb(LimbType.RightHand); + var waist = GetLimb(LimbType.Waist) ?? GetLimb(LimbType.Torso); + if (waist == null) { return; } + Vector2 midPos = waist.SimPosition; + if (leftHand != null) + { + leftHand.Disabled = true; + leftHandPos.X *= Dir; + leftHandPos += midPos; + HandIK(leftHand, leftHandPos); + } + if (rightHand != null) + { + rightHand.Disabled = true; + rightHandPos.X *= Dir; + rightHandPos += midPos; + HandIK(rightHand, rightHandPos); + } + var leftFoot = GetLimb(LimbType.LeftFoot); + if (leftFoot != null) + { + leftFoot.Disabled = true; + leftFootPos = new Vector2(waist.SimPosition.X + leftFootPos.X * Dir, GetColliderBottom().Y + leftFootPos.Y); + MoveLimb(leftFoot, leftFootPos, Math.Abs(leftFoot.SimPosition.X - leftFootPos.X) * footMoveForce * leftFoot.Mass, true); + } + var rightFoot = GetLimb(LimbType.RightFoot); + if (rightFoot != null) + { + rightFoot.Disabled = true; + rightFootPos = new Vector2(waist.SimPosition.X + rightFootPos.X * Dir, GetColliderBottom().Y + rightFootPos.Y); + MoveLimb(rightFoot, rightFootPos, Math.Abs(rightFoot.SimPosition.X - rightFootPos.X) * footMoveForce * rightFoot.Mass, true); + } + } + + public void ApplyTestPose() + { + var waist = GetLimb(LimbType.Waist) ?? GetLimb(LimbType.Torso); + if (waist != null) + { + ApplyPose( + new Vector2(-0.75f, -0.2f), + new Vector2(0.75f, -0.2f), + new Vector2(-WalkParams.StepSize.X * 0.5f, -0.1f * RagdollParams.JointScale), + new Vector2(WalkParams.StepSize.X * 0.5f, -0.1f * RagdollParams.JointScale)); + } + } + + protected void CalculateArmLengths() + { + //calculate arm and forearm length (atm this assumes that both arms are the same size) + Limb rightForearm = GetLimb(LimbType.RightForearm); + Limb rightHand = GetLimb(LimbType.RightHand); + if (rightHand == null) { return; } + + rightShoulder = GetJointBetweenLimbs(LimbType.Torso, LimbType.RightArm) ?? GetJointBetweenLimbs(LimbType.Head, LimbType.RightArm) ?? GetJoint(LimbType.RightArm, new LimbType[] { LimbType.RightHand, LimbType.RightForearm }); + leftShoulder = GetJointBetweenLimbs(LimbType.Torso, LimbType.LeftArm) ?? GetJointBetweenLimbs(LimbType.Head, LimbType.LeftArm) ?? GetJoint(LimbType.LeftArm, new LimbType[] { LimbType.LeftHand, LimbType.LeftForearm }); + + Vector2 localAnchorShoulder = Vector2.Zero; + Vector2 localAnchorElbow = Vector2.Zero; + if (rightShoulder != null) + { + localAnchorShoulder = rightShoulder.LimbA.type == LimbType.RightArm ? rightShoulder.LocalAnchorA : rightShoulder.LocalAnchorB; + } + LimbJoint rightElbow = rightForearm == null ? + GetJointBetweenLimbs(LimbType.RightArm, LimbType.RightHand) : + GetJointBetweenLimbs(LimbType.RightArm, LimbType.RightForearm); + if (rightElbow != null) + { + localAnchorElbow = rightElbow.LimbA.type == LimbType.RightArm ? rightElbow.LocalAnchorA : rightElbow.LocalAnchorB; + } + upperArmLength = Vector2.Distance(localAnchorShoulder, localAnchorElbow); + if (rightElbow != null) + { + if (rightForearm == null) + { + forearmLength = Vector2.Distance( + rightHand.PullJointLocalAnchorA, + rightElbow.LimbA.type == LimbType.RightHand ? rightElbow.LocalAnchorA : rightElbow.LocalAnchorB); + } + else + { + LimbJoint rightWrist = GetJointBetweenLimbs(LimbType.RightForearm, LimbType.RightHand); + if (rightWrist != null) + { + forearmLength = Vector2.Distance( + rightElbow.LimbA.type == LimbType.RightForearm ? rightElbow.LocalAnchorA : rightElbow.LocalAnchorB, + rightWrist.LimbA.type == LimbType.RightForearm ? rightWrist.LocalAnchorA : rightWrist.LocalAnchorB); + + forearmLength += Vector2.Distance( + rightHand.PullJointLocalAnchorA, + rightWrist.LimbA.type == LimbType.RightHand ? rightWrist.LocalAnchorA : rightWrist.LocalAnchorB); + } + } + } + } + + protected LimbJoint GetJointBetweenLimbs(LimbType limbTypeA, LimbType limbTypeB) + { + return LimbJoints.FirstOrDefault(lj => + (lj.LimbA.type == limbTypeA && lj.LimbB.type == limbTypeB) || + (lj.LimbB.type == limbTypeA && lj.LimbA.type == limbTypeB)); + } + + protected LimbJoint GetJoint(LimbType matchingType, IEnumerable ignoredTypes) + { + return LimbJoints.FirstOrDefault(lj => + lj.LimbA.type == matchingType && ignoredTypes.None(t => lj.LimbB.type == t) || + lj.LimbB.type == matchingType && ignoredTypes.None(t => lj.LimbB.type == t)); + } + + public override void Recreate(RagdollParams ragdollParams = null) + { + base.Recreate(ragdollParams); + if (Character.Params.CanInteract) + { + CalculateArmLengths(); + } + } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/FishAnimController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/FishAnimController.cs index 0ca3c9c1c..c37d0d8b1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/FishAnimController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/FishAnimController.cs @@ -97,9 +97,9 @@ namespace Barotrauma public new FishSwimParams CurrentSwimParams => base.CurrentSwimParams as FishSwimParams; public float? TailAngle => GetValidOrNull(CurrentAnimationParams, CurrentFishAnimation?.TailAngleInRadians); - public float FootTorque => CurrentFishAnimation.FootTorque; - public float HeadTorque => CurrentFishAnimation.HeadTorque; - public float TorsoTorque => CurrentFishAnimation.TorsoTorque; + public float FootTorque => CurrentAnimationParams.FootTorque; + public float HeadTorque => CurrentAnimationParams.HeadTorque; + public float TorsoTorque => CurrentAnimationParams.TorsoTorque; public float TailTorque => CurrentFishAnimation.TailTorque; public float HeadMoveForce => CurrentGroundedParams.HeadMoveForce; public float TorsoMoveForce => CurrentGroundedParams.TorsoMoveForce; @@ -139,7 +139,7 @@ namespace Barotrauma if (MainLimb == null) { return; } var mainLimb = MainLimb; - levitatingCollider = true; + levitatingCollider = !IsHanging; if (!character.CanMove) { @@ -192,6 +192,11 @@ namespace Barotrauma strongestImpact = 0.0f; } + if (aiming) + { + TargetMovement = TargetMovement.ClampLength(2); + } + if (inWater && !forceStanding) { Collider.FarseerBody.FixedRotation = false; @@ -202,7 +207,7 @@ namespace Barotrauma if (CurrentGroundedParams != null) { //rotate collider back upright - float standAngle = dir == Direction.Right ? CurrentGroundedParams.ColliderStandAngleInRadians : -CurrentGroundedParams.ColliderStandAngleInRadians; + float standAngle = CurrentGroundedParams.ColliderStandAngleInRadians * Dir; if (Math.Abs(MathUtils.GetShortestAngle(Collider.Rotation, standAngle)) > 0.001f) { Collider.AngularVelocity = MathUtils.GetShortestAngle(Collider.Rotation, standAngle) * 60.0f; @@ -215,16 +220,19 @@ namespace Barotrauma } UpdateWalkAnim(deltaTime); } - if (character.SelectedCharacter != null) { DragCharacter(character.SelectedCharacter, deltaTime); + return; + } + if (character.AnimController.AnimationTestPose) + { + ApplyTestPose(); } - //don't flip when simply physics is enabled if (SimplePhysicsEnabled) { return; } - if (!character.IsRemotelyControlled && (character.AIController == null || character.AIController.CanFlip)) + if (!character.IsRemotelyControlled && (character.AIController == null || character.AIController.CanFlip) && !aiming) { if (!inWater || (CurrentSwimParams != null && CurrentSwimParams.Mirror)) { @@ -289,6 +297,10 @@ namespace Barotrauma { flipTimer = 0.0f; } + wasAiming = aiming; + aiming = false; + wasAimingMelee = aimingMelee; + aimingMelee = false; } private bool CanDrag(Character target) @@ -362,7 +374,7 @@ namespace Barotrauma { //pull the character's mouth to the target character (again with a fluctuating force) float pullStrength = (float)(Math.Sin(eatTimer) * Math.Max(Math.Sin(eatTimer * 0.5f), 0.0f)); - mouthLimb.body.ApplyForce(limbDiff * mouthLimb.Mass * 50.0f * pullStrength, maxVelocity: NetConfig.MaxPhysicsBodyVelocity); + mouthLimb.body.ApplyForce(limbDiff * mouthLimb.Mass * 50.0f * pullStrength); } else { @@ -389,11 +401,17 @@ namespace Barotrauma } if (eatTimer % 1.0f < 0.5f && (eatTimer - deltaTime * eatSpeed) % 1.0f > 0.5f) { - bool CanBeSevered(LimbJoint j) => !j.IsSevered && j.CanBeSevered && j.LimbA != null && !j.LimbA.IsSevered && j.LimbB != null && !j.LimbB.IsSevered; + static bool CanBeSevered(LimbJoint j) => !j.IsSevered && j.CanBeSevered && j.LimbA != null && !j.LimbA.IsSevered && j.LimbB != null && !j.LimbB.IsSevered; //keep severing joints until there is only one limb left var nonSeveredJoints = target.AnimController.LimbJoints.Where(CanBeSevered); if (nonSeveredJoints.None()) { + //small monsters don't eat the contents of the character's inventory + if (Mass < target.AnimController.Mass) + { + target.Inventory?.AllItemsMod.ForEach(it => it?.Drop(dropper: null)); + } + //only one limb left, the character is now full eaten Entity.Spawner?.AddToRemoveQueue(target); @@ -442,6 +460,16 @@ namespace Barotrauma //limbs are disabled when simple physics is enabled, no need to move them if (SimplePhysicsEnabled) { return; } mainLimb.PullJointEnabled = true; + + if (aiming && movement.Length() <= 0.1f) + { + Vector2 mousePos = ConvertUnits.ToSimUnits(character.CursorPosition); + Vector2 diff = (mousePos - (GetLimb(LimbType.Torso) ?? MainLimb).SimPosition) * Dir; + TargetMovement = new Vector2(0.0f, -0.1f); + float newRotation = MathUtils.VectorToAngle(diff); + Collider.SmoothRotate(newRotation, CurrentSwimParams.SteerTorque * character.SpeedMultiplier); + } + if (!isMoving) { WalkPos = MathHelper.SmoothStep(WalkPos, MathHelper.PiOver2, deltaTime * 5); @@ -787,7 +815,7 @@ namespace Barotrauma { limb.body.SmoothRotate(MainLimb.Rotation + MathHelper.ToRadians(limb.Params.ConstantAngle) * Dir, limb.Mass * limb.Params.ConstantTorque, wrapAngle: true); } - if (limb.Params.BlinkFrequency > 0) + if (limb.Params.BlinkFrequency > 0 && !limb.Params.OnlyBlinkInWater) { limb.UpdateBlink(deltaTime, MainLimb.Rotation); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs index 7e04b454f..37a5a7029 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs @@ -2,7 +2,6 @@ using FarseerPhysics; using Microsoft.Xna.Framework; using System; -using System.Collections.Generic; using System.Linq; using Barotrauma.Extensions; using Barotrauma.Networking; @@ -73,6 +72,20 @@ namespace Barotrauma set { _humanRunParams = value; } } + private HumanCrouchParams _humanCrouchParams; + public HumanCrouchParams HumanCrouchParams + { + get + { + if (_humanCrouchParams == null) + { + _humanCrouchParams = HumanCrouchParams.GetDefaultAnimParams(character); + } + return _humanCrouchParams; + } + set { _humanCrouchParams = value; } + } + private HumanSwimSlowParams _humanSwimSlowParams; public HumanSwimSlowParams HumanSwimSlowParams { @@ -102,8 +115,11 @@ namespace Barotrauma } public new HumanGroundedParams CurrentGroundedParams => base.CurrentGroundedParams as HumanGroundedParams; + public new HumanSwimParams CurrentSwimParams => base.CurrentSwimParams as HumanSwimParams; + public IHumanAnimation CurrentHumanAnimParams => CurrentAnimationParams as IHumanAnimation; + public override GroundedMovementParams WalkParams { get { return HumanWalkParams; } @@ -130,27 +146,8 @@ namespace Barotrauma public bool Crouching; - private float upperArmLength = 0.0f, forearmLength = 0.0f; - - public float ArmLength => upperArmLength + forearmLength; - - public Vector2 RightHandIKPos - { - get; - private set; - } - public Vector2 LeftHandIKPos - { - get; - private set; - } - - private LimbJoint rightShoulder, leftShoulder; - private float upperLegLength = 0.0f, lowerLegLength = 0.0f; - private bool aiming; - private readonly float movementLerp; private float cprAnimTimer; @@ -161,43 +158,9 @@ namespace Barotrauma //prevents rapid switches between swimming/walking if the water level is fluctuating around the minimum swimming depth private float swimmingStateLockTimer; - private float useItemTimer; - - public override float? TorsoPosition - { - get - { - return Crouching && !swimming ? CurrentGroundedParams.CrouchingTorsoPos * RagdollParams.JointScale : base.TorsoPosition; - } - } - - public override float? HeadPosition - { - get - { - return Crouching && !swimming ? CurrentGroundedParams.CrouchingHeadPos * RagdollParams.JointScale : base.HeadPosition; - } - } - - public override float? TorsoAngle - { - get - { - return Crouching && !swimming ? MathHelper.ToRadians(CurrentGroundedParams.CrouchingTorsoAngle) : base.TorsoAngle; - } - } - - public override float? HeadAngle - { - get - { - return Crouching && !swimming ? MathHelper.ToRadians(CurrentGroundedParams.CrouchingHeadAngle) : base.HeadAngle; - } - } - public float HeadLeanAmount => CurrentGroundedParams.HeadLeanAmount; public float TorsoLeanAmount => CurrentGroundedParams.TorsoLeanAmount; - public Vector2 FootMoveOffset => (Crouching ? CurrentGroundedParams.CrouchingFootMoveOffset : CurrentGroundedParams.FootMoveOffset) * RagdollParams.JointScale; + public Vector2 FootMoveOffset => CurrentGroundedParams.FootMoveOffset * RagdollParams.JointScale; public float LegBendTorque => CurrentGroundedParams.LegBendTorque * RagdollParams.JointScale; public Vector2 HandMoveOffset => CurrentGroundedParams.HandMoveOffset * RagdollParams.JointScale; @@ -207,7 +170,7 @@ namespace Barotrauma { get { - float shoulderHeight = Collider.height / 2.0f - 0.1f; + float shoulderHeight = Collider.height / 2.0f; if (inWater) { shoulderHeight += 0.4f; @@ -229,61 +192,12 @@ namespace Barotrauma movementLerp = RagdollParams.MainElement.GetAttributeFloat("movementlerp", 0.4f); } - public override void Recreate(RagdollParams ragdollParams) + public override void Recreate(RagdollParams ragdollParams = null) { base.Recreate(ragdollParams); - CalculateArmLengths(); CalculateLegLengths(); } - private void CalculateArmLengths() - { - //calculate arm and forearm length (atm this assumes that both arms are the same size) - Limb rightForearm = GetLimb(LimbType.RightForearm); - Limb rightHand = GetLimb(LimbType.RightHand); - if (rightHand == null) { return; } - - rightShoulder = GetJointBetweenLimbs(LimbType.Torso, LimbType.RightArm); - leftShoulder = GetJointBetweenLimbs(LimbType.Torso, LimbType.LeftArm); - Vector2 localAnchorShoulder = Vector2.Zero; - Vector2 localAnchorElbow = Vector2.Zero; - if (rightShoulder != null) - { - localAnchorShoulder = rightShoulder.LimbA.type == LimbType.RightArm ? rightShoulder.LocalAnchorA : rightShoulder.LocalAnchorB; - } - LimbJoint rightElbow = rightForearm == null ? - GetJointBetweenLimbs(LimbType.RightArm, LimbType.RightHand) : - GetJointBetweenLimbs(LimbType.RightArm, LimbType.RightForearm); - if (rightElbow != null) - { - localAnchorElbow = rightElbow.LimbA.type == LimbType.RightArm ? rightElbow.LocalAnchorA : rightElbow.LocalAnchorB; - } - upperArmLength = Vector2.Distance(localAnchorShoulder, localAnchorElbow); - if (rightElbow != null) - { - if (rightForearm == null) - { - forearmLength = Vector2.Distance( - rightHand.PullJointLocalAnchorA, - rightElbow.LimbA.type == LimbType.RightHand ? rightElbow.LocalAnchorA : rightElbow.LocalAnchorB); - } - else - { - LimbJoint rightWrist = GetJointBetweenLimbs(LimbType.RightForearm, LimbType.RightHand); - if (rightWrist != null) - { - forearmLength = Vector2.Distance( - rightElbow.LimbA.type == LimbType.RightForearm ? rightElbow.LocalAnchorA : rightElbow.LocalAnchorB, - rightWrist.LimbA.type == LimbType.RightForearm ? rightWrist.LocalAnchorA : rightWrist.LocalAnchorB); - - forearmLength += Vector2.Distance( - rightHand.PullJointLocalAnchorA, - rightElbow.LimbA.type == LimbType.RightHand ? rightElbow.LocalAnchorA : rightElbow.LocalAnchorB); - } - } - } - } - private void CalculateLegLengths() { //calculate upper and lower leg length (atm this assumes that both legs are the same size) @@ -291,7 +205,7 @@ namespace Barotrauma LimbType lowerLegType = LimbType.RightLeg; LimbType footType = LimbType.RightFoot; - var waistJoint = GetJointBetweenLimbs(LimbType.Waist, upperLegType); + var waistJoint = GetJointBetweenLimbs(LimbType.Waist, upperLegType) ?? GetJointBetweenLimbs(LimbType.Torso, upperLegType); Vector2 localAnchorWaist = Vector2.Zero; Vector2 localAnchorKnee = Vector2.Zero; if (waistJoint != null) @@ -314,22 +228,17 @@ namespace Barotrauma ankleJoint.LimbA.type == footType ? ankleJoint.LocalAnchorA : ankleJoint.LocalAnchorB, GetLimb(footType).PullJointLocalAnchorA); } - private LimbJoint GetJointBetweenLimbs(LimbType limbTypeA, LimbType limbTypeB) - { - return LimbJoints.FirstOrDefault(lj => - (lj.LimbA.type == limbTypeA && lj.LimbB.type == limbTypeB) || - (lj.LimbB.type == limbTypeA && lj.LimbA.type == limbTypeB)); - } public override void UpdateAnim(float deltaTime) { if (Frozen) return; if (MainLimb == null) { return; } - levitatingCollider = true; + levitatingCollider = !IsHanging; ColliderIndex = Crouching && !swimming ? 1 : 0; if (character.SelectedConstruction?.GetComponent()?.ControlCharacterPose ?? false || - (ForceSelectAnimationType != AnimationType.Walk && ForceSelectAnimationType != AnimationType.NotDefined)) + character.SelectedConstruction?.GetComponent() != null || + (ForceSelectAnimationType != AnimationType.Crouch && ForceSelectAnimationType != AnimationType.NotDefined)) { Crouching = false; ColliderIndex = 0; @@ -432,42 +341,25 @@ namespace Barotrauma midPos += Vector2.Transform(new Vector2(-0.3f * Dir, -0.2f), torsoTransform); if (rightHand.PullJointEnabled) midPos = (midPos + rightHand.PullJointWorldAnchorB) / 2.0f; - - HandIK(rightHand, midPos); - HandIK(leftHand, midPos); + HandIK(rightHand, midPos, CurrentAnimationParams.ArmIKStrength, CurrentAnimationParams.HandIKStrength); + HandIK(leftHand, midPos, CurrentAnimationParams.ArmIKStrength, CurrentAnimationParams.HandIKStrength); } else if (character.AnimController.AnimationTestPose) { - var leftHand = GetLimb(LimbType.LeftHand); - var rightHand = GetLimb(LimbType.RightHand); - var waist = GetLimb(LimbType.Waist) ?? GetLimb(LimbType.Torso); - rightHand.Disabled = true; - leftHand.Disabled = true; - Vector2 midPos = waist.SimPosition; - HandIK(rightHand, midPos + new Vector2(-1, -0.2f) * Dir); - HandIK(leftHand, midPos + new Vector2(1, -0.2f) * Dir); - - var leftFoot = GetLimb(LimbType.LeftFoot); - var rightFoot = GetLimb(LimbType.RightFoot); - rightFoot.Disabled = true; - leftFoot.Disabled = true; - // The code here is a bit obscure, but it's pretty much copy-pasted from the block that is used for crouching. - for (int i = -1; i < 2; i += 2) - { - Vector2 footPos = GetColliderBottom(); - footPos = new Vector2(waist.SimPosition.X + Math.Sign(WalkParams.StepSize.X * i) * Dir * 0.3f, footPos.Y - 0.1f * RagdollParams.JointScale); - var foot = i == -1 ? rightFoot : leftFoot; - MoveLimb(foot, footPos, Math.Abs(foot.SimPosition.X - footPos.X) * 100.0f, true); - } + ApplyTestPose(); } else { - if (Anim != Animation.UsingConstruction) ResetPullJoints(); + if (Anim != Animation.UsingConstruction) + { + ResetPullJoints(); + } } if (SimplePhysicsEnabled) { UpdateStandingSimple(); + IsHanging = false; return; } @@ -531,9 +423,11 @@ namespace Barotrauma { limb.Disabled = false; } - + wasAiming = aiming; aiming = false; - if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer) return; + wasAimingMelee = aimingMelee; + aimingMelee = false; + IsHanging = false; } void UpdateStanding() @@ -628,7 +522,7 @@ namespace Barotrauma Collider.LinearVelocity.Y > 0.0f ? Collider.LinearVelocity.Y * 0.5f : Collider.LinearVelocity.Y); } - getUpForce = getUpForce * Math.Max(head.SimPosition.Y - colliderPos.Y, 0.5f); + getUpForce *= Math.Max(head.SimPosition.Y - colliderPos.Y, 0.5f); torso.PullJointEnabled = true; head.PullJointEnabled = true; @@ -700,9 +594,12 @@ namespace Barotrauma float torsoAngle = TorsoAngle.Value; float herpesStrength = character.CharacterHealth.GetAfflictionStrength("spaceherpes"); torsoAngle -= herpesStrength / 150.0f; - torso.body.SmoothRotate(torsoAngle * Dir, 50.0f); + torso.body.SmoothRotate(torsoAngle * Dir, CurrentGroundedParams.TorsoTorque); + } + if (HeadAngle.HasValue) + { + head.body.SmoothRotate(HeadAngle.Value * Dir, CurrentGroundedParams.HeadTorque); } - if (HeadAngle.HasValue) head.body.SmoothRotate(HeadAngle.Value * Dir, 50.0f); if (!onGround) { @@ -733,12 +630,23 @@ namespace Barotrauma Vector2 footPos = stepSize * -i; footPos += new Vector2(Math.Sign(movement.X) * FootMoveOffset.X, FootMoveOffset.Y); - if (footPos.Y < 0.0f) footPos.Y = -0.15f; + if (footPos.Y < 0.0f) { footPos.Y = -0.15f; } //make the character limp if the feet are damaged float footAfflictionStrength = character.CharacterHealth.GetAfflictionStrength("damage", foot, true); footPos.X *= MathHelper.Lerp(1.0f, 0.75f, MathHelper.Clamp(footAfflictionStrength / 50.0f, 0.0f, 1.0f)); + if (CurrentGroundedParams.FootLiftHorizontalFactor > 0) + { + // Calculate the foot y dynamically based on the foot position relative to the waist, + // so that the foot aims higher when it's behind the waist and lower when it's in the front. + float xDiff = (foot.SimPosition.X - waistPos.X + FootMoveOffset.X) * Dir; + float min = MathUtils.InverseLerp(1, 0, CurrentGroundedParams.FootLiftHorizontalFactor); + float max = 1 + MathUtils.InverseLerp(0, 1, CurrentGroundedParams.FootLiftHorizontalFactor); + float xFactor = MathHelper.Lerp(min, max, MathUtils.InverseLerp(RagdollParams.JointScale, -RagdollParams.JointScale, xDiff)); + footPos.Y *= xFactor; + } + if (onSlope && Stairs == null) { footPos.Y *= 2.0f; @@ -760,7 +668,7 @@ namespace Barotrauma foot.DebugTargetPos = colliderPos + footPos; MoveLimb(foot, colliderPos + footPos, CurrentGroundedParams.FootMoveStrength); FootIK(foot, colliderPos + footPos, - CurrentGroundedParams.LegBendTorque, CurrentGroundedParams.FootRotateStrength, CurrentGroundedParams.FootAngleInRadians); + CurrentGroundedParams.LegBendTorque, CurrentGroundedParams.FootTorque, CurrentGroundedParams.FootAngleInRadians); } } @@ -776,58 +684,55 @@ namespace Barotrauma if (rightHand != null && !rightHand.Disabled) { - HandIK(rightHand, torso.SimPosition + posAddition + - new Vector2( - -handPos.X, - (Math.Sign(walkPosX) == Math.Sign(Dir)) ? handPos.Y : lowerY), CurrentGroundedParams.HandMoveStrength); + HandIK(rightHand, + torso.SimPosition + posAddition + new Vector2(-handPos.X, (Math.Sign(walkPosX) == Math.Sign(Dir)) ? handPos.Y : lowerY), + CurrentGroundedParams.ArmMoveStrength, CurrentGroundedParams.HandMoveStrength); } - if (leftHand != null && !leftHand.Disabled) { - HandIK(leftHand, torso.SimPosition + posAddition + - new Vector2( - handPos.X, - (Math.Sign(walkPosX) == Math.Sign(-Dir)) ? handPos.Y : lowerY), CurrentGroundedParams.HandMoveStrength); + HandIK(leftHand, + torso.SimPosition + posAddition + new Vector2(handPos.X, (Math.Sign(walkPosX) == Math.Sign(-Dir)) ? handPos.Y : lowerY), + CurrentGroundedParams.ArmMoveStrength, CurrentGroundedParams.HandMoveStrength); } - } else { for (int i = -1; i < 2; i += 2) { Vector2 footPos = colliderPos; - if (Crouching) { - footPos = new Vector2( - Math.Sign(stepSize.X * i) * Dir * 0.4f, - colliderPos.Y); + footPos = new Vector2(Math.Sign(stepSize.X * i) * Dir * 0.35f, colliderPos.Y); if (Math.Sign(footPos.X) != Math.Sign(Dir)) { //lift the foot at the back up a bit footPos.Y += 0.15f; } - footPos.X += torso.SimPosition.X; + footPos.X += colliderPos.X; } else { footPos = new Vector2(colliderPos.X + stepSize.X * i * 0.2f, colliderPos.Y - 0.1f); } - if (Stairs == null) { footPos.Y = Math.Max(Math.Min(FloorY, footPos.Y + 0.5f), footPos.Y); } - var foot = i == -1 ? rightFoot : leftFoot; - if (foot != null && !foot.Disabled) { foot.DebugRefPos = colliderPos; foot.DebugTargetPos = footPos; - MoveLimb(foot, footPos, CurrentGroundedParams.FootMoveStrength); - FootIK(foot, footPos, - CurrentGroundedParams.LegBendTorque, CurrentGroundedParams.FootRotateStrength, CurrentGroundedParams.FootAngleInRadians); + float footMoveForce = CurrentGroundedParams.FootMoveStrength; + float legBendTorque = CurrentGroundedParams.LegBendTorque; + if (Crouching) + { + // Keeps the pose + legBendTorque = 100; + footMoveForce *= 2; + } + MoveLimb(foot, footPos, footMoveForce); + FootIK(foot, footPos, legBendTorque, CurrentGroundedParams.FootTorque, CurrentGroundedParams.FootAngleInRadians); } } @@ -843,18 +748,26 @@ namespace Barotrauma var arm = GetLimb(armType); if (arm != null && Math.Abs(arm.body.AngularVelocity) < 10.0f) { - arm.body.SmoothRotate(MathHelper.Clamp(-arm.body.AngularVelocity, -0.1f, 0.1f), arm.Mass * 10.0f); + arm.body.SmoothRotate(MathHelper.Clamp(-arm.body.AngularVelocity, -0.5f, 0.5f), arm.Mass * 50.0f * CurrentGroundedParams.ArmMoveStrength); } //get the elbow to a neutral rotation if (Math.Abs(hand.body.AngularVelocity) < 10.0f) { - LimbJoint elbow = GetJointBetweenLimbs(armType, hand.type) ?? GetJointBetweenLimbs(armType, foreArmType); + var forearm = GetLimb(foreArmType) ?? hand; + LimbJoint elbow = GetJointBetweenLimbs(armType, foreArmType) ?? GetJointBetweenLimbs(armType, hand.type); if (elbow != null) { - hand.body.ApplyTorque(MathHelper.Clamp(-elbow.JointAngle, -MathHelper.PiOver2, MathHelper.PiOver2) * hand.Mass * 10.0f); + float diff = elbow.JointAngle - (Dir > 0 ? elbow.LowerLimit : elbow.UpperLimit); + forearm.body.ApplyTorque(MathHelper.Clamp(-diff, -MathHelper.PiOver2, MathHelper.PiOver2) * forearm.Mass * 100.0f * CurrentGroundedParams.ArmMoveStrength); } } + // Try to keep the wrist straight + LimbJoint wrist = GetJointBetweenLimbs(foreArmType, hand.type); + if (wrist != null) + { + hand.body.ApplyTorque(MathHelper.Clamp(-wrist.JointAngle, -MathHelper.PiOver2, MathHelper.PiOver2) * hand.Mass * 100f * CurrentGroundedParams.HandMoveStrength); + } } } } @@ -904,8 +817,8 @@ namespace Barotrauma { foreach (Gap gap in currentHull.ConnectedGaps) { - if (gap.IsHorizontal || gap.Open <= 0.0f) continue; - if (Collider.SimPosition.X < ConvertUnits.ToSimUnits(gap.Rect.X) || Collider.SimPosition.X > ConvertUnits.ToSimUnits(gap.Rect.Right)) continue; + if (gap.IsHorizontal || gap.Open <= 0.0f) { continue; } + if (Collider.SimPosition.X < ConvertUnits.ToSimUnits(gap.Rect.X) || Collider.SimPosition.X > ConvertUnits.ToSimUnits(gap.Rect.Right)) { continue; } //if the gap is above us and leads outside, there's no surface to limit the movement if (!gap.IsRoomToRoom && gap.Position.Y > currentHull.Position.Y) @@ -925,7 +838,7 @@ namespace Barotrauma } } - surfaceLimiter = ConvertUnits.ToDisplayUnits(Collider.SimPosition.Y + 0.4f) - surfacePos; + surfaceLimiter = ConvertUnits.ToDisplayUnits(Collider.SimPosition.Y + 1.0f) - surfacePos; surfaceLimiter = Math.Max(1.0f, surfaceLimiter); if (surfaceLimiter > 50.0f) { return; } } @@ -938,25 +851,29 @@ namespace Barotrauma float rotation = MathHelper.WrapAngle(Collider.Rotation); rotation = MathHelper.ToDegrees(rotation); - if (rotation < 0.0f) rotation += 360; - + if (rotation < 0.0f) + { + rotation += 360; + } if (!character.IsRemotelyControlled && !aiming && Anim != Animation.UsingConstruction && !(character.SelectedConstruction?.GetComponent()?.ControlCharacterPose ?? false)) { if (rotation > 20 && rotation < 170) + { TargetDir = Direction.Left; + } else if (rotation > 190 && rotation < 340) + { TargetDir = Direction.Right; + } } - float targetSpeed = TargetMovement.Length(); - if (targetSpeed > 0.1f) { if (!aiming) { float newRotation = MathUtils.VectorToAngle(TargetMovement) - MathHelper.PiOver2; - Collider.SmoothRotate(newRotation, 5.0f * character.SpeedMultiplier); + Collider.SmoothRotate(newRotation, CurrentSwimParams.SteerTorque * character.SpeedMultiplier); } } else @@ -965,11 +882,9 @@ namespace Barotrauma { Vector2 mousePos = ConvertUnits.ToSimUnits(character.CursorPosition); Vector2 diff = (mousePos - torso.SimPosition) * Dir; - TargetMovement = new Vector2(0.0f, -0.1f); - float newRotation = MathUtils.VectorToAngle(diff); - Collider.SmoothRotate(newRotation, 5.0f * character.SpeedMultiplier); + Collider.SmoothRotate(newRotation, CurrentSwimParams.SteerTorque * character.SpeedMultiplier); } } @@ -979,19 +894,19 @@ namespace Barotrauma if (TorsoAngle.HasValue) { - torso.body.SmoothRotate(Collider.Rotation + TorsoAngle.Value * Dir, CurrentSwimParams.SteerTorque); + torso.body.SmoothRotate(Collider.Rotation + TorsoAngle.Value * Dir, CurrentSwimParams.TorsoTorque); } else { - torso.body.SmoothRotate(Collider.Rotation, CurrentSwimParams.SteerTorque); + torso.body.SmoothRotate(Collider.Rotation, CurrentSwimParams.TorsoTorque); } if (HeadAngle.HasValue) { - head.body.SmoothRotate(Collider.Rotation + HeadAngle.Value * Dir, CurrentSwimParams.SteerTorque); + head.body.SmoothRotate(Collider.Rotation + HeadAngle.Value * Dir, CurrentSwimParams.HeadTorque); } else { - head.body.SmoothRotate(Collider.Rotation, CurrentSwimParams.SteerTorque); + head.body.SmoothRotate(Collider.Rotation, CurrentSwimParams.HeadTorque); } //dont try to move upwards if head is already out of water @@ -1014,7 +929,7 @@ namespace Barotrauma head.body.ApplyTorque(Dir); } - movement.Y = movement.Y - (surfaceLimiter - 1.0f) * 0.01f; + movement.Y = movement.Y * (1.0f - ((surfaceLimiter - 1.0f) / 50.0f)); } bool isNotRemote = true; @@ -1032,25 +947,25 @@ namespace Barotrauma float legMoveMultiplier = 1.0f; if (movement.LengthSquared() < 0.001f) { - //TODO: expose these? + // Swimming in place (TODO: expose?) legMoveMultiplier = 0.3f; legCyclePos += 0.4f; handCyclePos += 0.1f; } - var waist = GetLimb(LimbType.Waist); + var waist = GetLimb(LimbType.Waist) ?? GetLimb(LimbType.Torso); footPos = waist == null ? Vector2.Zero : waist.SimPosition - new Vector2((float)Math.Sin(-Collider.Rotation), (float)Math.Cos(-Collider.Rotation)) * (upperLegLength + lowerLegLength); Vector2 transformedFootPos = new Vector2((float)Math.Sin(legCyclePos / CurrentSwimParams.LegCycleLength) * CurrentSwimParams.LegMoveAmount * legMoveMultiplier, 0.0f); transformedFootPos = Vector2.Transform(transformedFootPos, Matrix.CreateRotationZ(Collider.Rotation)); - float torque = CurrentSwimParams.FootRotateStrength * character.SpeedMultiplier * (1.2f - character.GetLegPenalty()); + float legTorque = CurrentSwimParams.LegTorque * character.SpeedMultiplier * (1.2f - character.GetLegPenalty()); if (rightFoot != null && !rightFoot.Disabled) { - FootIK(rightFoot, footPos - transformedFootPos, torque, torque, CurrentSwimParams.FootAngleInRadians); + FootIK(rightFoot, footPos - transformedFootPos, legTorque, CurrentSwimParams.FootTorque, CurrentSwimParams.FootAngleInRadians); } if (leftFoot != null && !leftFoot.Disabled) { - FootIK(leftFoot, footPos + transformedFootPos, torque, torque, CurrentSwimParams.FootAngleInRadians); + FootIK(leftFoot, footPos + transformedFootPos, legTorque, CurrentSwimParams.FootTorque, CurrentSwimParams.FootAngleInRadians); } handPos = (torso.SimPosition + head.SimPosition) / 2.0f; @@ -1059,7 +974,7 @@ namespace Barotrauma // -> hands just float around if ((!headInWater && TargetMovement.X == 0.0f && TargetMovement.Y > 0) || TargetMovement.LengthSquared() < 0.001f) { - handPos += MathUtils.RotatePoint(Vector2.UnitX * Dir * 0.6f, torso.Rotation); + handPos += MathUtils.RotatePoint(Vector2.UnitX * Dir * 0.2f, torso.Rotation); float wobbleAmount = 0.1f; @@ -1067,20 +982,20 @@ namespace Barotrauma { MoveLimb(rightHand, new Vector2( handPos.X + (float)Math.Sin(handCyclePos / 1.5f) * wobbleAmount, - handPos.Y + (float)Math.Sin(handCyclePos / 3.5f) * wobbleAmount - 0.25f), 1.5f); + handPos.Y + (float)Math.Sin(handCyclePos / 3.5f) * wobbleAmount - 0.25f), CurrentSwimParams.ArmMoveStrength); } if (leftHand != null && !leftHand.Disabled) { MoveLimb(leftHand, new Vector2( handPos.X + (float)Math.Sin(handCyclePos / 2.0f) * wobbleAmount, - handPos.Y + (float)Math.Sin(handCyclePos / 3.0f) * wobbleAmount - 0.25f), 1.5f); + handPos.Y + (float)Math.Sin(handCyclePos / 3.0f) * wobbleAmount - 0.25f), CurrentSwimParams.ArmMoveStrength); } return; } - handPos += head.LinearVelocity * 0.1f; + handPos += head.LinearVelocity.ClampLength(1.0f) * 0.1f; // Not sure why the params has to be flipped, but it works. var handMoveAmount = CurrentSwimParams.HandMoveAmount.Flip(); @@ -1095,8 +1010,18 @@ namespace Barotrauma Vector2 rightHandPos = new Vector2(-handPosX, -handPosY) + handMoveOffset; rightHandPos.X = (Dir == 1.0f) ? Math.Max(0.3f, rightHandPos.X) : Math.Min(-0.3f, rightHandPos.X); rightHandPos = Vector2.Transform(rightHandPos, rotationMatrix); - - HandIK(rightHand, handPos + rightHandPos, CurrentSwimParams.HandMoveStrength * character.SpeedMultiplier * (1 - Character.GetRightHandPenalty())); + float speedMultiplier = Math.Min(character.SpeedMultiplier * (1 - Character.GetRightHandPenalty()), 1.0f); + if (character.Inventory != null && character.Inventory.GetItemInLimbSlot(InvSlotType.RightHand) != null) + { + speedMultiplier = Math.Min(speedMultiplier, 0.1f); + } + HandIK(rightHand, handPos + rightHandPos, CurrentSwimParams.ArmMoveStrength * speedMultiplier, CurrentSwimParams.HandMoveStrength * speedMultiplier); + // Try to keep the wrist straight + LimbJoint wrist = GetJointBetweenLimbs(LimbType.RightForearm, LimbType.RightHand); + if (wrist != null) + { + rightHand.body.ApplyTorque(MathHelper.Clamp(-wrist.JointAngle, -MathHelper.PiOver2, MathHelper.PiOver2) * rightHand.Mass * 100f * CurrentSwimParams.HandMoveStrength); + } } if (leftHand != null && !leftHand.Disabled) @@ -1104,8 +1029,18 @@ namespace Barotrauma Vector2 leftHandPos = new Vector2(handPosX, handPosY) + handMoveOffset; leftHandPos.X = (Dir == 1.0f) ? Math.Max(0.3f, leftHandPos.X) : Math.Min(-0.3f, leftHandPos.X); leftHandPos = Vector2.Transform(leftHandPos, rotationMatrix); - - HandIK(leftHand, handPos + leftHandPos, CurrentSwimParams.HandMoveStrength * character.SpeedMultiplier * (1 - Character.GetLeftHandPenalty())); + float speedMultiplier = Math.Min(character.SpeedMultiplier * (1 - Character.GetLeftHandPenalty()), 1.0f); + if (character.Inventory != null && character.Inventory.GetItemInLimbSlot(InvSlotType.LeftHand) != null) + { + speedMultiplier = Math.Min(speedMultiplier, 0.1f); + } + HandIK(leftHand, handPos + leftHandPos, CurrentSwimParams.ArmMoveStrength * speedMultiplier, CurrentSwimParams.HandMoveStrength * speedMultiplier); + // Try to keep the wrist straight + LimbJoint wrist = GetJointBetweenLimbs(LimbType.LeftForearm, LimbType.LeftHand); + if (wrist != null) + { + leftHand.body.ApplyTorque(MathHelper.Clamp(-wrist.JointAngle, -MathHelper.PiOver2, MathHelper.PiOver2) * leftHand.Mass * 100f * CurrentSwimParams.HandMoveStrength); + } } } @@ -1161,15 +1096,16 @@ namespace Barotrauma } float bottomPos = Collider.SimPosition.Y - ColliderHeightFromFloor - Collider.radius - Collider.height / 2.0f; + float headPos = HeadPosition ?? 0; + float torsoPos = TorsoPosition ?? 0; + MoveLimb(head, new Vector2(ladderSimPos.X - 0.2f * Dir, bottomPos + headPos), 10.5f); + MoveLimb(torso, new Vector2(ladderSimPos.X - 0.35f * Dir, bottomPos + torsoPos), 10.5f); - MoveLimb(head, new Vector2(ladderSimPos.X - 0.35f * Dir, bottomPos + WalkParams.HeadPosition), 10.5f); - MoveLimb(torso, new Vector2(ladderSimPos.X - 0.35f * Dir, bottomPos + WalkParams.TorsoPosition), 10.5f); - - Collider.MoveToPos(new Vector2(ladderSimPos.X - 0.1f * Dir, Collider.SimPosition.Y), 10.5f); + Collider.MoveToPos(new Vector2(ladderSimPos.X - 0.1f * Dir, Collider.SimPosition.Y), 10.5f); Vector2 handPos = new Vector2( ladderSimPos.X, - bottomPos + WalkParams.TorsoPosition + movement.Y * 0.1f - ladderSimPos.Y); + bottomPos + torsoPos + movement.Y * 0.1f - ladderSimPos.Y); //prevent the hands from going above the top of the ladders handPos.Y = Math.Min(-0.5f, handPos.Y); @@ -1245,8 +1181,9 @@ namespace Barotrauma if (character.SimPosition.Y > ladderSimPos.Y) { climbForce.Y = Math.Min(0.0f, climbForce.Y); } //apply forces to the collider to move the Character up/down - Collider.ApplyForce((climbForce * 20.0f + subSpeed * 50.0f) * Collider.Mass, maxVelocity: NetConfig.MaxPhysicsBodyVelocity); - head.body.SmoothRotate(0.0f); + Collider.ApplyForce((climbForce * 20.0f + subSpeed * 50.0f) * Collider.Mass); + float movementMultiplier = targetMovement.Y < 0 ? 0 : 1; + head.body.SmoothRotate(MathHelper.PiOver4 * movementMultiplier * Dir, WalkParams.HeadTorque); if (!character.SelectedConstruction.Prefab.Triggers.Any()) { @@ -1385,6 +1322,8 @@ namespace Barotrauma { target.Oxygen += deltaTime * 0.5f; //Stabilize them } + + bool powerfulCPR = character.HasAbilityFlag(AbilityFlags.PowerfulCPR); int skill = (int)character.GetSkillLevel("medical"); //pump for 15 seconds (cprAnimTimer 0-15), then do mouth-to-mouth for 2 seconds (cprAnimTimer 15-17) @@ -1401,13 +1340,19 @@ namespace Barotrauma { if (target.Oxygen < -10.0f) { - //stabilize the oxygen level but don't allow it to go positive and revive the character yet - float stabilizationAmount = skill * CPRSettings.StabilizationPerSkill; - stabilizationAmount = MathHelper.Clamp(stabilizationAmount, CPRSettings.StabilizationMin, CPRSettings.StabilizationMax); - character.Oxygen -= (1.0f / stabilizationAmount) * deltaTime; //Worse skill = more oxygen required - if (character.Oxygen > 0.0f) target.Oxygen += stabilizationAmount * deltaTime; //we didn't suffocate yet did we - - //DebugConsole.NewMessage("CPR Us: " + character.Oxygen + " Them: " + target.Oxygen + " How good we are: restore " + cpr + " use " + (30.0f - cpr), Color.Aqua); + if (powerfulCPR) + { + //prevent the patient from suffocating no matter how fast their oxygen level is dropping + target.Oxygen = Math.Max(target.Oxygen, -10.0f); + } + else + { + //stabilize the oxygen level but don't allow it to go positive and revive the character yet + float stabilizationAmount = skill * CPRSettings.StabilizationPerSkill; + stabilizationAmount = MathHelper.Clamp(stabilizationAmount, CPRSettings.StabilizationMin, CPRSettings.StabilizationMax); + character.Oxygen -= 1.0f / stabilizationAmount * deltaTime; //Worse skill = more oxygen required + if (character.Oxygen > 0.0f) { target.Oxygen += stabilizationAmount * deltaTime; } //we didn't suffocate yet did we + } } } } @@ -1442,6 +1387,8 @@ namespace Barotrauma reviveChance = (float)Math.Pow(reviveChance, CPRSettings.ReviveChanceExponent); reviveChance = MathHelper.Clamp(reviveChance, CPRSettings.ReviveChanceMin, CPRSettings.ReviveChanceMax); + if (powerfulCPR) { reviveChance *= 2.0f; } + if (Rand.Range(0.0f, 1.0f, Rand.RandSync.Server) <= reviveChance) { //increase oxygen and clamp it above zero @@ -1463,7 +1410,7 @@ namespace Barotrauma target.CharacterHealth.CalculateVitality(); if (wasCritical && target.Vitality > 0.0f && Timing.TotalTime > lastReviveTime + 10.0f) { - character.Info?.IncreaseSkillLevel("medical", SkillSettings.Current.SkillIncreasePerCprRevive, character.Position + Vector2.UnitY * 150.0f); + character.Info?.IncreaseSkillLevel("medical", SkillSettings.Current.SkillIncreasePerCprRevive); SteamAchievementManager.OnCharacterRevived(target, character); lastReviveTime = (float)Timing.TotalTime; #if SERVER @@ -1622,7 +1569,10 @@ namespace Barotrauma { Vector2 pullLimbAnchor = targetLimb.SimPosition; pullLimb.PullJointMaxForce = 5000.0f; - targetMovement *= MathHelper.Clamp(Mass / target.Mass, 0.5f, 1.0f); + if (!character.HasAbilityFlag(AbilityFlags.MoveNormallyWhileDragging)) + { + targetMovement *= MathHelper.Clamp(Mass / target.Mass, 0.5f, 1.0f); + } Vector2 shoulderPos = rightShoulder.WorldAnchorA; Vector2 dragDir = inWater ? Vector2.Normalize(targetLimb.SimPosition - shoulderPos) : Vector2.UnitY; @@ -1679,7 +1629,7 @@ namespace Barotrauma } //limit movement if moving away from the target - if (Vector2.Dot(target.WorldPosition - WorldPosition, targetMovement) < 0) + if (!character.HasAbilityFlag(AbilityFlags.MoveNormallyWhileDragging) && Vector2.Dot(target.WorldPosition - WorldPosition, targetMovement) < 0) { targetMovement *= MathHelper.Clamp(1.5f - dist, 0.0f, 1.0f); } @@ -1698,224 +1648,6 @@ namespace Barotrauma } } - public void Grab(Vector2 rightHandPos, Vector2 leftHandPos) - { - for (int i = 0; i < 2; i++) - { - Limb pullLimb = (i == 0) ? GetLimb(LimbType.LeftHand) : GetLimb(LimbType.RightHand); - - pullLimb.Disabled = true; - - pullLimb.PullJointEnabled = true; - pullLimb.PullJointWorldAnchorB = (i == 0) ? rightHandPos : leftHandPos; - pullLimb.PullJointMaxForce = 500.0f; - } - } - - //TODO: refactor this method, it's way too convoluted - public override void HoldItem(float deltaTime, Item item, Vector2[] handlePos, Vector2 holdPos, Vector2 aimPos, bool aim, float holdAngle, float itemAngleRelativeToHoldAngle = 0.0f) - { - if (character.Stun > 0.0f || character.IsIncapacitated) - { - aim = false; - } - - //calculate the handle positions - Matrix itemTransfrom = Matrix.CreateRotationZ(item.body.Rotation); - // TODO: don't create new arrays, reuse - Vector2[] transformedHandlePos = new Vector2[2]; - transformedHandlePos[0] = Vector2.Transform(handlePos[0], itemTransfrom); - transformedHandlePos[1] = Vector2.Transform(handlePos[1], itemTransfrom); - - Limb head = GetLimb(LimbType.Head); - Limb torso = GetLimb(LimbType.Torso); - Limb leftHand = GetLimb(LimbType.LeftHand); - Limb rightHand = GetLimb(LimbType.RightHand); - - // TODO: Remove this. Provide the position in params. - Vector2 itemPos = aim ? aimPos : holdPos; - - var controller = character.SelectedConstruction?.GetComponent(); - bool usingController = controller != null && !controller.AllowAiming; - bool isClimbing = character.IsClimbing && Math.Abs(character.AnimController.TargetMovement.Y) > 0.01f; - - float itemAngle; - - Holdable holdable = item.GetComponent(); - - if (!isClimbing && !usingController && character.Stun <= 0.0f && aim && itemPos != Vector2.Zero && !character.IsIncapacitated) - { - Vector2 mousePos = ConvertUnits.ToSimUnits(character.SmoothedCursorPosition); - - Vector2 diff = holdable.Aimable ? (mousePos - AimSourceSimPos) * Dir : Vector2.UnitX; - - holdAngle = MathUtils.VectorToAngle(new Vector2(diff.X, diff.Y * Dir)) - torso.body.Rotation * Dir; - - itemAngle = (torso.body.Rotation + holdAngle * Dir); - - if (holdable.ControlPose) - { - head?.body.SmoothRotate(itemAngle); - - if (TargetMovement == Vector2.Zero && inWater) - { - torso.body.AngularVelocity -= torso.body.AngularVelocity * 0.1f; - torso.body.ApplyForce(torso.body.LinearVelocity * -0.5f); - } - - aiming = true; - } - - } - else - { - itemAngle = torso.body.Rotation + holdAngle * Dir; - } - - Vector2 transformedHoldPos = rightShoulder.WorldAnchorA; - if (itemPos == Vector2.Zero || isClimbing || usingController) - { - if (character.Inventory?.GetItemInLimbSlot(InvSlotType.RightHand) == item) - { - if (rightHand == null || rightHand.IsSevered) { return; } - transformedHoldPos = rightHand.PullJointWorldAnchorA - transformedHandlePos[0]; - itemAngle = (rightHand.Rotation + (holdAngle - MathHelper.PiOver2) * Dir); - } - else if (character.Inventory?.GetItemInLimbSlot(InvSlotType.LeftHand) == item) - { - if (leftHand == null || leftHand.IsSevered) { return; } - transformedHoldPos = leftHand.PullJointWorldAnchorA - transformedHandlePos[1]; - itemAngle = (leftHand.Rotation + (holdAngle - MathHelper.PiOver2) * Dir); - } - } - else - { - if (character.Inventory?.GetItemInLimbSlot(InvSlotType.RightHand) == item) - { - if (rightHand == null || rightHand.IsSevered) { return; } - transformedHoldPos = rightShoulder.WorldAnchorA; - rightHand.Disabled = true; - } - if (character.Inventory?.GetItemInLimbSlot(InvSlotType.LeftHand) == item) - { - if (leftHand == null || leftHand.IsSevered) { return; } - transformedHoldPos = leftShoulder.WorldAnchorA; - leftHand.Disabled = true; - } - - itemPos.X *= Dir; - transformedHoldPos += Vector2.Transform(itemPos, Matrix.CreateRotationZ(itemAngle)); - } - - item.body.ResetDynamics(); - - Vector2 currItemPos = (character.Inventory?.GetItemInLimbSlot(InvSlotType.RightHand) == item) ? - rightHand.PullJointWorldAnchorA - transformedHandlePos[0] : - leftHand.PullJointWorldAnchorA - transformedHandlePos[1]; - - if (!MathUtils.IsValid(currItemPos)) - { - string errorMsg = "Attempted to move the item \"" + item + "\" to an invalid position in HumanidAnimController.HoldItem: " + - currItemPos + ", rightHandPos: " + rightHand.PullJointWorldAnchorA + ", leftHandPos: " + leftHand.PullJointWorldAnchorA + - ", handlePos[0]: " + handlePos[0] + ", handlePos[1]: " + handlePos[1] + - ", transformedHandlePos[0]: " + transformedHandlePos[0] + ", transformedHandlePos[1]:" + transformedHandlePos[1] + - ", item pos: " + item.SimPosition + ", itemAngle: " + itemAngle + - ", collider pos: " + character.SimPosition; - DebugConsole.Log(errorMsg); - GameAnalyticsManager.AddErrorEventOnce( - "HumanoidAnimController.HoldItem:InvalidPos:" + character.Name + item.Name, - GameAnalyticsSDK.Net.EGAErrorSeverity.Error, - errorMsg); - - return; - } - - if (holdable.Pusher != null) - { - if (character.Stun > 0.0f || character.IsIncapacitated) - { - holdable.Pusher.Enabled = false; - } - else - { - if (!holdable.Pusher.Enabled) - { - holdable.Pusher.Enabled = true; - holdable.Pusher.ResetDynamics(); - holdable.Pusher.SetTransform(currItemPos, itemAngle); - } - else - { - holdable.Pusher.TargetPosition = currItemPos; - holdable.Pusher.TargetRotation = holdAngle * Dir; - - holdable.Pusher.MoveToTargetPosition(true); - - currItemPos = holdable.Pusher.SimPosition; - itemAngle = holdable.Pusher.Rotation; - } - } - } - - item.SetTransform(currItemPos, itemAngle + itemAngleRelativeToHoldAngle * Dir, setPrevTransform: false); - - if (!isClimbing && !character.IsIncapacitated && itemPos != Vector2.Zero) - { - for (int i = 0; i < 2; i++) - { - if (!character.Inventory.IsInLimbSlot(item, i == 0 ? InvSlotType.RightHand : InvSlotType.LeftHand)) { continue; } - HandIK(i == 0 ? rightHand : leftHand, transformedHoldPos + transformedHandlePos[i]); - } - } - } - - private void HandIK(Limb hand, Vector2 pos, float force = 1.0f) - { - Vector2 shoulderPos; - - Limb arm, forearm; - if (hand.type == LimbType.LeftHand) - { - if (leftShoulder == null) { return; } - shoulderPos = leftShoulder.WorldAnchorA; - arm = GetLimb(LimbType.LeftArm); - forearm = GetLimb(LimbType.LeftForearm); - LeftHandIKPos = pos; - } - else - { - if (rightShoulder == null) { return; } - shoulderPos = rightShoulder.WorldAnchorA; - arm = GetLimb(LimbType.RightArm); - forearm = GetLimb(LimbType.RightForearm); - RightHandIKPos = pos; - } - if (arm == null) { return; } - - //distance from shoulder to holdpos - float c = Vector2.Distance(pos, shoulderPos); - c = MathHelper.Clamp(c, Math.Abs(upperArmLength - forearmLength), forearmLength + upperArmLength - 0.01f); - - float armAngle = MathUtils.VectorToAngle(pos - shoulderPos) + MathHelper.PiOver2; - - float upperArmAngle = MathUtils.SolveTriangleSSS(forearmLength, upperArmLength, c) * Dir; - float lowerArmAngle = MathUtils.SolveTriangleSSS(upperArmLength, forearmLength, c) * Dir; - - //make sure the arm angle "has the same number of revolutions" as the arm - while (arm.Rotation - armAngle > MathHelper.Pi) - { - armAngle += MathHelper.TwoPi; - } - while (arm.Rotation - armAngle < -MathHelper.Pi) - { - armAngle -= MathHelper.TwoPi; - } - - arm?.body.SmoothRotate((armAngle - upperArmAngle), 20.0f * force * arm.Mass, wrapAngle: false); - forearm?.body.SmoothRotate((armAngle + lowerArmAngle), 20.0f * force * forearm.Mass, wrapAngle: false); - hand?.body.SmoothRotate((armAngle + lowerArmAngle), 100.0f * force * hand.Mass, wrapAngle: false); - } - private void FootIK(Limb foot, Vector2 pos, float legTorque, float footTorque, float footAngle) { if (!MathUtils.IsValid(pos)) @@ -1939,12 +1671,12 @@ namespace Barotrauma upperLeg = GetLimb(LimbType.RightThigh); lowerLeg = GetLimb(LimbType.RightLeg); } - var torso = GetLimb(LimbType.Torso); - var waist = GetJointBetweenLimbs(LimbType.Waist, upperLeg.type); + Limb torso = GetLimb(LimbType.Torso); + LimbJoint waistJoint = GetJointBetweenLimbs(LimbType.Waist, upperLeg.type) ?? GetJointBetweenLimbs(LimbType.Torso, upperLeg.type); Vector2 waistPos = Vector2.Zero; - if (waist != null) + if (waistJoint != null) { - waistPos = waist.LimbA == upperLeg ? waist.WorldAnchorA : waist.WorldAnchorB; + waistPos = waistJoint.LimbA == upperLeg ? waistJoint.WorldAnchorA : waistJoint.WorldAnchorB; } //distance from waist joint to the target position @@ -1982,47 +1714,6 @@ namespace Barotrauma foot.body.SmoothRotate((legAngle - (lowerLegAngle + footAngle) * Dir), foot.Mass * footTorque, wrapAngle: false); } - public override void UpdateUseItem(bool allowMovement, Vector2 handWorldPos) - { - useItemTimer = 0.5f; - Anim = Animation.UsingConstruction; - - if (!allowMovement) - { - TargetMovement = Vector2.Zero; - TargetDir = handWorldPos.X > character.WorldPosition.X ? Direction.Right : Direction.Left; - float sqrDist = Vector2.DistanceSquared(character.WorldPosition, handWorldPos); - if (sqrDist > MathUtils.Pow(ConvertUnits.ToDisplayUnits(upperArmLength + forearmLength), 2)) - { - TargetMovement = Vector2.Normalize(handWorldPos - character.WorldPosition) * GetCurrentSpeed(false) * Math.Max(character.SpeedMultiplier, 1); - } - } - - if (!character.Enabled) { return; } - - Vector2 handSimPos = ConvertUnits.ToSimUnits(handWorldPos); - if (character.Submarine != null) - { - handSimPos -= character.Submarine.SimPosition; - } - - var leftHand = GetLimb(LimbType.LeftHand); - if (leftHand != null) - { - leftHand.Disabled = true; - leftHand.PullJointEnabled = true; - leftHand.PullJointWorldAnchorB = handSimPos; - } - - var rightHand = GetLimb(LimbType.RightHand); - if (rightHand != null) - { - rightHand.Disabled = true; - rightHand.PullJointEnabled = true; - rightHand.PullJointWorldAnchorB = handSimPos; - } - } - public override void Flip() { base.Flip(); @@ -2041,7 +1732,8 @@ namespace Barotrauma { heldItem.FlipX(relativeToSub: false); } - heldItem.FlipX(relativeToSub: false); + // TODO: was this added by a mistake? + //heldItem.FlipX(relativeToSub: false); } foreach (Limb limb in Limbs) @@ -2100,5 +1792,18 @@ namespace Barotrauma } } + public override float GetSpeed(AnimationType type) + { + if (type == AnimationType.Crouch) + { + if (!CanWalk) + { + DebugConsole.ThrowError($"{character.SpeciesName} cannot crouch!"); + return 0; + } + return IsMovingBackwards ? HumanCrouchParams.MovementSpeed * HumanCrouchParams.BackwardsMovementMultiplier : HumanCrouchParams.MovementSpeed; + } + return base.GetSpeed(type); + } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs index 43d9dc327..5913768c7 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs @@ -125,7 +125,8 @@ namespace Barotrauma protected float surfaceY; protected bool inWater, headInWater; - public bool onGround; + protected bool onGround; + public bool OnGround => onGround; private Vector2 lastFloorCheckPos; private bool lastFloorCheckIgnoreStairs, lastFloorCheckIgnorePlatforms; @@ -395,12 +396,18 @@ namespace Barotrauma if (character.IsHusk && character.Params.UseHuskAppendage) { + bool inEditor = false; +#if CLIENT + inEditor = Screen.Selected == GameMain.CharacterEditorScreen; +#endif + var characterPrefab = CharacterPrefab.FindByFilePath(character.ConfigPath); if (characterPrefab?.XDocument != null) { var mainElement = characterPrefab.XDocument.Root.IsOverride() ? characterPrefab.XDocument.Root.FirstElement() : characterPrefab.XDocument.Root; foreach (var huskAppendage in mainElement.GetChildElements("huskappendage")) { + if (!inEditor && huskAppendage.GetAttributeBool("onlyfromafflictions", false)) { continue; } AfflictionHusk.AttachHuskAppendage(character, huskAppendage.GetAttributeString("affliction", string.Empty), huskAppendage, ragdoll: this); } } @@ -881,7 +888,7 @@ namespace Barotrauma /// if false, force is applied to the position of pullJoint - protected void MoveLimb(Limb limb, Vector2 pos, float amount, bool pullFromCenter = false) + public void MoveLimb(Limb limb, Vector2 pos, float amount, bool pullFromCenter = false) { limb.MoveToPos(pos, amount, pullFromCenter); } @@ -968,8 +975,7 @@ namespace Barotrauma Vector2 newSubPos = newHull.Submarine == null ? Vector2.Zero : newHull.Submarine.Position; Vector2 prevSubPos = currentHull.Submarine == null ? Vector2.Zero : currentHull.Submarine.Position; - Teleport(ConvertUnits.ToSimUnits(prevSubPos - newSubPos), - Vector2.Zero); + Teleport(ConvertUnits.ToSimUnits(prevSubPos - newSubPos), Vector2.Zero); } } @@ -1093,10 +1099,11 @@ namespace Barotrauma } public bool forceStanding; + public bool forceNotStanding; public void Update(float deltaTime, Camera cam) { - if (!character.Enabled || Frozen || Invalid) { return; } + if (!character.Enabled || character.Removed || Frozen || Invalid || Collider == null || Collider.Removed) { return; } while (impactQueue.Count > 0) { @@ -1205,24 +1212,24 @@ namespace Barotrauma //the room where the ragdoll is in is used as the "guess", meaning that it's checked first Hull limbHull = currentHull == null ? null : Hull.FindHull(limb.WorldPosition, currentHull); - bool prevInWater = limb.inWater; - limb.inWater = false; + bool prevInWater = limb.InWater; + limb.InWater = false; if (forceStanding) { - limb.inWater = false; + limb.InWater = false; } else if (limbHull == null) { //limb isn't in any room -> it's in the water - limb.inWater = true; + limb.InWater = true; if (limb.type == LimbType.Head) headInWater = true; } else if (limbHull.WaterVolume > 0.0f && Submarine.RectContains(limbHull.Rect, limb.Position)) { if (limb.Position.Y < limbHull.Surface) { - limb.inWater = true; + limb.InWater = true; surfaceY = limbHull.Surface; if (limb.type == LimbType.Head) { @@ -1230,7 +1237,7 @@ namespace Barotrauma } } //the limb has gone through the surface of the water - if (Math.Abs(limb.LinearVelocity.Y) > 5.0f && limb.inWater != prevInWater) + if (Math.Abs(limb.LinearVelocity.Y) > 5.0f && limb.InWater != prevInWater) { Splash(limb, limbHull); @@ -1264,6 +1271,7 @@ namespace Barotrauma } } UpdateProjSpecific(deltaTime, cam); + forceNotStanding = false; } private void CheckBodyInRest(float deltaTime) @@ -1340,19 +1348,19 @@ namespace Barotrauma string errorMsg = null; if (!MathUtils.IsValid(body.SimPosition) || Math.Abs(body.SimPosition.X) > 1e10f || Math.Abs(body.SimPosition.Y) > 1e10f) { - errorMsg = GetBodyName() + " position invalid (" + body.SimPosition + ", character: " + character.Name + "), resetting the ragdoll."; + errorMsg = GetBodyName() + " position invalid (" + body.SimPosition + ", character: " + character.Name + ")."; } else if (!MathUtils.IsValid(body.LinearVelocity) || Math.Abs(body.LinearVelocity.X) > 1000f || Math.Abs(body.LinearVelocity.Y) > 1000f) { - errorMsg = GetBodyName() + " velocity invalid (" + body.LinearVelocity + ", character: " + character.Name + "), resetting the ragdoll."; + errorMsg = GetBodyName() + " velocity invalid (" + body.LinearVelocity + ", character: " + character.Name + ")."; } else if (!MathUtils.IsValid(body.Rotation)) { - errorMsg = GetBodyName() + " rotation invalid (" + body.Rotation + ", character: " + character.Name + "), resetting the ragdoll."; + errorMsg = GetBodyName() + " rotation invalid (" + body.Rotation + ", character: " + character.Name + ")."; } else if (!MathUtils.IsValid(body.AngularVelocity) || Math.Abs(body.AngularVelocity) > 1000f) { - errorMsg = GetBodyName() + " angular velocity invalid (" + body.AngularVelocity + ", character: " + character.Name + "), resetting the ragdoll."; + errorMsg = GetBodyName() + " angular velocity invalid (" + body.AngularVelocity + ", character: " + character.Name + ")."; } if (errorMsg != null) { @@ -1424,9 +1432,10 @@ namespace Barotrauma //throwing conscious/moving characters around takes more force -> double the flow force if (character.CanMove) { flowForce *= 2.0f; } + flowForce *= 1 - Math.Clamp(character.GetStatValue(StatTypes.FlowResistance), 0f, 1f); float flowForceMagnitude = flowForce.Length(); - float limbMultipier = limbs.Count(l => l.inWater) / (float)limbs.Length; + float limbMultipier = limbs.Count(l => l.InWater) / (float)limbs.Length; //if the force strong enough, stun the character to let it get thrown around by the water if ((flowForceMagnitude * limbMultipier) - flowStunTolerance > StunForceThreshold) { @@ -1460,11 +1469,11 @@ namespace Barotrauma if (flowForce.LengthSquared() > 0.001f) { - Collider.ApplyForce(flowForce, maxVelocity: NetConfig.MaxPhysicsBodyVelocity); + Collider.ApplyForce(flowForce); foreach (Limb limb in limbs) { - if (!limb.inWater) { continue; } - limb.body.ApplyForce(flowForce, maxVelocity: NetConfig.MaxPhysicsBodyVelocity); + if (!limb.InWater) { continue; } + limb.body.ApplyForce(flowForce); } } } @@ -1497,7 +1506,6 @@ namespace Barotrauma if (TorsoPosition.HasValue && MathUtils.IsValid(TorsoPosition.Value)) { height = Math.Max(height, TorsoPosition.Value); } Vector2 rayEnd = rayStart - new Vector2(0.0f, height); - Vector2 onGroundRayEnd = rayStart - Vector2.UnitY * (Collider.height * 0.5f + Collider.radius + ColliderHeightFromFloor * 1.2f); Vector2 colliderBottomDisplay = ConvertUnits.ToDisplayUnits(GetColliderBottom()); Fixture standOnFloorFixture = null; @@ -1562,7 +1570,7 @@ namespace Barotrauma return closestFraction; }, rayStart, rayEnd, Physics.CollisionStairs | Physics.CollisionPlatform | Physics.CollisionWall | Physics.CollisionLevel); - if (standOnFloorFixture != null) + if (standOnFloorFixture != null && !IsHanging) { standOnFloorY = rayStart.Y + (rayEnd.Y - rayStart.Y) * standOnFloorFraction; if (rayStart.Y - standOnFloorY < Collider.height * 0.5f + Collider.radius + ColliderHeightFromFloor * 1.2f) @@ -1578,7 +1586,25 @@ namespace Barotrauma if (closestFraction == 1) //raycast didn't hit anything { floorNormal = Vector2.UnitY; - return (currentHull == null) ? -1000.0f : ConvertUnits.ToSimUnits(currentHull.Rect.Y - currentHull.Rect.Height); + if (CurrentHull == null) + { + return -1000.0f; + } + else + { + float hullBottom = currentHull.Rect.Y - currentHull.Rect.Height; + //check if there's a connected hull below + foreach (var gap in currentHull.ConnectedGaps) + { + if (!gap.IsRoomToRoom || gap.Open < 1.0f || gap.ConnectedDoor != null || gap.IsHorizontal) { continue; } + if (WorldPosition.X > gap.WorldRect.X && WorldPosition.X < gap.WorldRect.Right && gap.WorldPosition.Y < WorldPosition.Y) + { + var lowerHull = gap.linkedTo[0] == currentHull ? gap.linkedTo[1] : gap.linkedTo[0]; + hullBottom = Math.Min(hullBottom, lowerHull.Rect.Y - lowerHull.Rect.Height); + } + } + return ConvertUnits.ToSimUnits(hullBottom); + } } else { @@ -1599,6 +1625,13 @@ namespace Barotrauma } if (MainLimb == null) { return; } + if (Character.AIController is EnemyAIController enemyAI && enemyAI.LatchOntoAI != null && enemyAI.LatchOntoAI.IsAttached) + { + enemyAI.LatchOntoAI.DeattachFromBody(reset: true); + } + Character.Latchers.ForEachMod(l => l.DeattachFromBody(reset: true)); + Character.Latchers.Clear(); + Vector2 limbMoveAmount = forceMainLimbToCollider ? simPosition - MainLimb.SimPosition : simPosition - Collider.SimPosition; if (lerp) { @@ -1622,6 +1655,16 @@ namespace Barotrauma } } + public bool IsHanging { get; protected set; } + + public void Hang() + { + ResetPullJoints(); + onGround = false; + levitatingCollider = false; + IsHanging = true; + } + protected void TrySetLimbPosition(Limb limb, Vector2 original, Vector2 simPosition, bool lerp = false, bool ignorePlatforms = true) { Vector2 movePos = simPosition; @@ -1814,9 +1857,23 @@ namespace Barotrauma public void ReleaseStuckLimbs() { - Limbs.ForEach(l => l.Release()); + // Commented out, because stuck limbs is not a feature that we currently use, as it would require that we sync all the limbs, which we don't do. + //Limbs.ForEach(l => l.Release()); } + public void HideAndDisable(LimbType limbType, float duration = 0, bool ignoreCollisions = true) + { + foreach (var limb in Limbs) + { + if (limb.type == limbType) + { + limb.HideAndDisable(duration, ignoreCollisions); + } + } + } + + public void RestoreTemporarilyDisabled() => Limbs.ForEach(l => l.ReEnable()); + public void Remove() { if (Limbs != null) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Attack.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Attack.cs index c89b02b76..e07f73f0c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Attack.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Attack.cs @@ -8,7 +8,8 @@ namespace Barotrauma public enum HitDetection { Distance, - Contact + Contact, + None } public enum AttackContext @@ -271,6 +272,9 @@ namespace Barotrauma statusEffect.SetUser(user); } } + + // used for talents/ability conditions + public Item SourceItem { get; } public List GetMultipliedAfflictions(float multiplier) { @@ -320,6 +324,10 @@ namespace Barotrauma Penetration = Penetration; } + public Attack(XElement element, string parentDebugName, Item sourceItem) : this(element, parentDebugName) + { + SourceItem = sourceItem; + } public Attack(XElement element, string parentDebugName) { Deserialize(element); @@ -445,7 +453,7 @@ namespace Barotrauma DamageParticles(deltaTime, worldPosition); - var attackResult = target.AddDamage(attacker, worldPosition, this, deltaTime, playSound); + var attackResult = target?.AddDamage(attacker, worldPosition, this, deltaTime, playSound) ?? new AttackResult(); var effectType = attackResult.Damage > 0.0f ? ActionType.OnUse : ActionType.OnFailure; if (targetCharacter != null && targetCharacter.IsDead) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs index 8c2506cc9..8edc9b797 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs @@ -9,6 +9,7 @@ using System.Xml.Linq; using Barotrauma.Items.Components; using FarseerPhysics.Dynamics; using Barotrauma.Extensions; +using Barotrauma.Abilities; #if SERVER using System.Text; #endif @@ -128,6 +129,8 @@ namespace Barotrauma } } + public readonly HashSet Latchers = new HashSet(); + protected readonly Dictionary activeTeamChanges = new Dictionary(); protected ActiveTeamChange currentTeamChange; const string OriginalTeamIdentifier = "original"; @@ -250,6 +253,8 @@ namespace Barotrauma private readonly List lastAttackers = new List(); public IEnumerable LastAttackers => lastAttackers; public Character LastAttacker => lastAttackers.LastOrDefault()?.Character; + public Character LastOrderedCharacter { get; private set; } + public Character SecondLastOrderedCharacter { get; private set; } public Entity LastDamageSource; @@ -261,6 +266,7 @@ namespace Barotrauma public readonly CharacterParams Params; public string SpeciesName => Params.SpeciesName; + public string Group => Params.Group; public bool IsHumanoid => Params.Humanoid; public bool IsHusk => Params.Husk; @@ -306,7 +312,14 @@ namespace Barotrauma public string TraitorCurrentObjective = ""; public bool IsHuman => SpeciesName.Equals(CharacterPrefab.HumanSpeciesName, StringComparison.OrdinalIgnoreCase); + + /// + /// Can be used by status effects to check the character's gender + /// public bool IsMale => Info != null && Info.HasGenders && Info.Gender == Gender.Male; + /// + /// Can be used by status effects to check the character's gender + /// public bool IsFemale => Info != null && Info.HasGenders && Info.Gender == Gender.Female; private float attackCoolDown; @@ -461,10 +474,7 @@ namespace Barotrauma } } - public bool AllowInput - { - get { return Stun <= 0.0f && !IsDead && !IsIncapacitated; } - } + public bool AllowInput => !Removed && !IsIncapacitated && Stun <= 0.0f; public bool CanMove { @@ -475,11 +485,10 @@ namespace Barotrauma return true; } } + public bool CanInteract => AllowInput && Params.CanInteract && !LockHands; - public bool CanInteract - { - get { return AllowInput && IsHumanoid && !LockHands && !Removed && !IsIncapacitated; } - } + // Eating is not implemented for humanoids. If we implement that at some point, we could remove this restriction. + public bool CanEat => !IsHumanoid && Params.CanEat && AllowInput && AnimController.GetLimb(LimbType.Head) != null; public Vector2 CursorPosition { @@ -581,6 +590,14 @@ namespace Barotrauma } } + /// + /// Can be used by status effects to check whether the characters is in a high-pressure environment + /// + public bool InPressure + { + get { return CurrentHull == null || CurrentHull.LethalPressure > 5.0f; } + } + public const float KnockbackCooldown = 5.0f; public float KnockbackCooldownTimer; @@ -594,6 +611,7 @@ namespace Barotrauma get { if (IsUnconscious) { return true; } + if (IsDead) { return true; } return CharacterHealth.Afflictions.Any(a => a.Prefab.AfflictionType == "paralysis" && a.Strength >= a.Prefab.MaxStrength); } } @@ -628,7 +646,7 @@ namespace Barotrauma public float Stun { - get { return IsRagdolled ? 1.0f : CharacterHealth.Stun; } + get { return IsRagdolled && !AnimController.IsHanging ? 1.0f : CharacterHealth.Stun; } set { if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; } @@ -640,22 +658,14 @@ namespace Barotrauma public bool DisableHealthWindow; - public float Vitality - { - get { return CharacterHealth.Vitality; } - } - - public float Health - { - get { return CharacterHealth.Vitality; } - } - + // These properties needs to be exposed for status effects + public float Vitality => CharacterHealth.Vitality; + public float Health => Vitality; public float HealthPercentage => CharacterHealth.HealthPercentage; - - public float MaxVitality - { - get { return CharacterHealth.MaxVitality; } - } + public float MaxVitality => CharacterHealth.MaxVitality; + public float MaxHealth => MaxVitality; + public AIState AIState => AIController is EnemyAIController enemyAI ? enemyAI.State : AIState.Idle; + public bool IsLatched => AIController is EnemyAIController enemyAI && enemyAI.LatchOntoAI != null && enemyAI.LatchOntoAI.IsAttached; public float Bloodloss { @@ -773,8 +783,6 @@ namespace Barotrauma } } - public bool IsObserving => AIController is EnemyAIController enemyAI && enemyAI.Enabled && enemyAI.State == AIState.Observe; - public bool EnableDespawn { get; set; } = true; public CauseOfDeath CauseOfDeath @@ -1134,7 +1142,7 @@ namespace Barotrauma { nonHuskedSpeciesName = AfflictionHusk.GetNonHuskedSpeciesName(speciesName, matchingAffliction); } - if (ragdollParams == null) + if (ragdollParams == null && prefab.VariantOf == null) { string name = Params.UseHuskAppendage ? nonHuskedSpeciesName : speciesName; ragdollParams = IsHumanoid ? RagdollParams.GetDefaultRagdollParams(name) : RagdollParams.GetDefaultRagdollParams(name) as RagdollParams; @@ -1176,6 +1184,7 @@ namespace Barotrauma { LoadHeadAttachments(); } + ApplyStatusEffects(ActionType.OnSpawn, 1.0f); } partial void InitProjSpecific(XElement mainElement); @@ -1371,13 +1380,18 @@ namespace Barotrauma } } } - private List wearableItems = new List(); public float GetSkillLevel(string skillIdentifier) { if (Info?.Job == null) { return 0.0f; } float skillLevel = Info.Job.GetSkillLevel(skillIdentifier); + // apply multipliers first so that multipliers only affect base skill value + foreach (Affliction affliction in CharacterHealth.GetAllAfflictions()) + { + skillLevel *= affliction.GetSkillMultiplier(); + } + if (skillIdentifier != null) { for (int i = 0; i < Inventory.Capacity; i++) @@ -1392,10 +1406,8 @@ namespace Barotrauma } } - foreach (Affliction affliction in CharacterHealth.GetAllAfflictions()) - { - skillLevel *= affliction.GetSkillMultiplier(); - } + skillLevel += GetStatValue(GetSkillStatType(skillIdentifier)); + return skillLevel; } @@ -1432,9 +1444,9 @@ namespace Barotrauma // - dragging someone // - crouching // - moving backwards - public bool CanRun => (SelectedCharacter == null || !SelectedCharacter.CanBeDragged) && + public bool CanRun => (SelectedCharacter == null || !SelectedCharacter.CanBeDragged || HasAbilityFlag(AbilityFlags.MoveNormallyWhileDragging)) && (!(AnimController is HumanoidAnimController) || !((HumanoidAnimController)AnimController).Crouching) && - !AnimController.IsMovingBackwards; + !AnimController.IsMovingBackwards && !HasAbilityFlag(AbilityFlags.MustWalk); public Vector2 ApplyMovementLimits(Vector2 targetMovement, float currentSpeed) { @@ -1595,7 +1607,7 @@ namespace Barotrauma return Math.Clamp(reduction, 0, 1f); } - private float CalculateMovementPenalty(Limb limb, float sum, float max = 0.4f) + private float CalculateMovementPenalty(Limb limb, float sum, float max = 0.8f) { if (limb != null) { @@ -1628,7 +1640,7 @@ namespace Barotrauma float max; if (AnimController is HumanoidAnimController) { - max = AnimController.InWater ? 0.5f : 0.7f; + max = AnimController.InWater ? 0.5f : 0.8f; } else { @@ -1664,13 +1676,13 @@ namespace Barotrauma AnimController.IgnorePlatforms = AnimController.TargetMovement.Y < -0.1f; } - if (AnimController is HumanoidAnimController) + if (AnimController is HumanoidAnimController humanAnimController) { - ((HumanoidAnimController)AnimController).Crouching = IsKeyDown(InputType.Crouch); + humanAnimController.Crouching = humanAnimController.ForceSelectAnimationType == AnimationType.Crouch || IsKeyDown(InputType.Crouch); } if (!aiControlled && - AnimController.onGround && + AnimController.OnGround && !AnimController.InWater && AnimController.Anim != AnimController.Animation.UsingConstruction && AnimController.Anim != AnimController.Animation.CPR && @@ -1734,8 +1746,12 @@ namespace Barotrauma } else if (IsKeyDown(InputType.Attack)) { - if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) + if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient && Controlled != this) { + if ((currentAttackTarget.DamageTarget as Entity)?.Removed ?? false) + { + currentAttackTarget = default(AttackTargetData); + } currentAttackTarget.AttackLimb?.UpdateAttack(deltaTime, currentAttackTarget.AttackPos, currentAttackTarget.DamageTarget, out _); } else if (IsPlayer) @@ -2044,7 +2060,7 @@ namespace Barotrauma return false; } - public Item GetEquippedItem(string tagOrIdentifier, InvSlotType? slotType = null) + public Item GetEquippedItem(string tagOrIdentifier = null, InvSlotType? slotType = null) { if (Inventory == null) { return null; } for (int i = 0; i < Inventory.Capacity; i++) @@ -2059,7 +2075,7 @@ namespace Barotrauma } var item = Inventory.GetItemAt(i); if (item == null) { continue; } - if (item.Prefab.Identifier == tagOrIdentifier || item.HasTag(tagOrIdentifier)) { return item; } + if (tagOrIdentifier == null || item.Prefab.Identifier == tagOrIdentifier || item.HasTag(tagOrIdentifier)) { return item; } } return null; } @@ -2076,11 +2092,10 @@ namespace Barotrauma return SelectedCharacter == owner && owner.CanInventoryBeAccessed; } - if (inventory.Owner is Item) + if (inventory.Owner is Item item) { - var owner = (Item)inventory.Owner; - if (!CanInteractWith(owner) && !owner.linkedTo.Any(lt => lt is Item item && item.DisplaySideBySideWhenLinked && CanInteractWith(item))) { return false; } - ItemContainer container = owner.GetComponents().FirstOrDefault(ic => ic.Inventory == inventory); + if (!CanInteractWith(item) && !item.linkedTo.Any(lt => lt is Item item && item.DisplaySideBySideWhenLinked && CanInteractWith(item))) { return false; } + ItemContainer container = item.GetComponents().FirstOrDefault(ic => ic.Inventory == inventory); if (container != null && !container.HasRequiredItems(this, addMessage: false)) { return false; } } return true; @@ -2219,6 +2234,12 @@ namespace Barotrauma } } + if (SelectedConstruction?.GetComponent()?.TargetItem == item || + HeldItems.Any(it => it.GetComponent()?.TargetItem == item)) + { + return true; + } + if (item.InteractDistance == 0.0f && !item.Prefab.Triggers.Any()) { return false; } Pickable pickableComponent = item.GetComponent(); @@ -2365,9 +2386,9 @@ namespace Barotrauma { if (!IsMouseOnUI && (ViewTarget == null || ViewTarget == this)) { - if (findFocusedTimer <= 0.0f || Screen.Selected == GameMain.SubEditorScreen) + if ((findFocusedTimer <= 0.0f || Screen.Selected == GameMain.SubEditorScreen) && (!PlayerInput.PrimaryMouseButtonHeld() || Barotrauma.Inventory.DraggingItemToWorld)) { - FocusedCharacter = CanInteract ? FindCharacterAtPosition(mouseSimPos) : null; + FocusedCharacter = CanInteract || CanEat ? FindCharacterAtPosition(mouseSimPos) : null; if (FocusedCharacter != null && !CanSeeCharacter(FocusedCharacter)) { FocusedCharacter = null; } float aimAssist = GameMain.Config.AimAssistAmount * (AnimController.InWater ? 1.5f : 1.0f); if (HeldItems.Any(it => it?.GetComponent()?.IsActive ?? false)) @@ -2397,7 +2418,7 @@ namespace Barotrauma var head = AnimController.GetLimb(LimbType.Head); bool headInWater = head == null ? AnimController.InWater : - head.inWater; + head.InWater; //climb ladders automatically when pressing up/down inside their trigger area Ladder currentLadder = SelectedConstruction?.GetComponent(); if ((SelectedConstruction == null || currentLadder != null) && @@ -2445,7 +2466,7 @@ namespace Barotrauma { DeselectCharacter(); } - else if (FocusedCharacter != null && IsKeyHit(InputType.Grab) && FocusedCharacter.CanBeDragged && CanInteract) + else if (FocusedCharacter != null && IsKeyHit(InputType.Grab) && FocusedCharacter.CanBeDragged && (CanInteract || FocusedCharacter.IsDead && CanEat)) { SelectCharacter(FocusedCharacter); } @@ -2624,6 +2645,11 @@ namespace Barotrauma UpdateAttackers(deltaTime); + foreach (var characterTalent in characterTalents) + { + characterTalent.UpdateTalent(deltaTime); + } + if (IsDead) { return; } if (GameMain.NetworkMember != null) @@ -2688,6 +2714,11 @@ namespace Barotrauma ApplyStatusEffects(AnimController.InWater ? ActionType.InWater : ActionType.NotInWater, deltaTime); ApplyStatusEffects(ActionType.OnActive, deltaTime); + if (aiTarget != null) + { + aiTarget.InDetectable = false; + } + UpdateControlled(deltaTime, cam); //Health effects @@ -2711,14 +2742,18 @@ namespace Barotrauma //Do ragdoll shenanigans before Stun because it's still technically a stun, innit? Less network updates for us! bool allowRagdoll = GameMain.NetworkMember?.ServerSettings?.AllowRagdollButton ?? true; - bool tooFastToUnragdoll = AnimController.Collider.LinearVelocity.LengthSquared() > 5.0f * 5.0f; + bool tooFastToUnragdoll = AnimController.Collider.LinearVelocity.LengthSquared() > 8.0f * 8.0f; + bool wasRagdolled = false; + bool selfRagdolled = false; + if (IsForceRagdolled) { IsRagdolled = IsForceRagdolled; } else if (this != Controlled) { - IsRagdolled = IsKeyDown(InputType.Ragdoll); + wasRagdolled = IsRagdolled; + IsRagdolled = selfRagdolled = IsKeyDown(InputType.Ragdoll); } //Keep us ragdolled if we were forced or we're too speedy to unragdoll else if (allowRagdoll && (!IsRagdolled || !tooFastToUnragdoll)) @@ -2730,20 +2765,30 @@ namespace Barotrauma } else { - bool wasRagdolled = IsRagdolled; - IsRagdolled = IsKeyDown(InputType.Ragdoll); //Handle this here instead of Control because we can stop being ragdolled ourselves - if (wasRagdolled != IsRagdolled) { ragdollingLockTimer = 0.25f; } + wasRagdolled = IsRagdolled; + IsRagdolled = selfRagdolled = IsKeyDown(InputType.Ragdoll); //Handle this here instead of Control because we can stop being ragdolled ourselves + if (wasRagdolled != IsRagdolled) { ragdollingLockTimer = 0.5f; } } } + if (!wasRagdolled && IsRagdolled) + { + if (selfRagdolled) + { + CheckTalents(AbilityEffectType.OnSelfRagdoll); + } + // currently does not work when you are stunned, like it should + CheckTalents(AbilityEffectType.OnRagdoll); + } + lowPassMultiplier = MathHelper.Lerp(lowPassMultiplier, 1.0f, 0.1f); //ragdoll button if (IsRagdolled || !CanMove) { - if (AnimController is HumanoidAnimController) + if (AnimController is HumanoidAnimController humanAnimController) { - ((HumanoidAnimController)AnimController).Crouching = false; + humanAnimController.Crouching = false; } AnimController.ResetPullJoints(); SelectedConstruction = null; @@ -2821,7 +2866,7 @@ namespace Barotrauma // If the damage is very low, let's not forget so quickly, or we can't cumulate the damage from repair tools (high frequency, low damage) reduction *= 0.5f; } - enemy.Damage = Math.Max(0.0f, enemy.Damage-reduction); + enemy.Damage = Math.Max(0.0f, enemy.Damage - reduction); } } } @@ -3061,30 +3106,47 @@ namespace Barotrauma //set the character order only if the character is close enough to hear the message if (!force && orderGiver != null && !CanHearCharacter(orderGiver)) { return; } - if (order.OrderGiver != orderGiver) + if (order != null && order.OrderGiver != orderGiver) { order.OrderGiver = orderGiver; } - // If there's another character operating the same device, make them dismiss themself - if (order != null && order.Category == OrderCategory.Operate && order.TargetEntity != null) + switch (order?.Category) { - foreach (var character in CharacterList) - { - if (character == this) { continue; } - if (character.TeamID != TeamID) { continue; } - if (!(character.AIController is HumanAIController)) { continue; } - if (!HumanAIController.IsActive(character)) { continue; } - foreach (var currentOrder in character.CurrentOrders) + case OrderCategory.Operate when order?.TargetEntity != null: + // If there's another character operating the same device, make them dismiss themself + foreach (var character in CharacterList) + { + if (character == this) { continue; } + if (character.TeamID != TeamID) { continue; } + if (!(character.AIController is HumanAIController)) { continue; } + if (!HumanAIController.IsActive(character)) { continue; } + foreach (var currentOrder in character.CurrentOrders) + { + if (currentOrder.Order == null) { continue; } + if (currentOrder.Order.Category != OrderCategory.Operate) { continue; } + if (currentOrder.Order.Identifier != order.Identifier) { continue; } + if (currentOrder.Order.TargetEntity != order.TargetEntity) { continue; } + character.SetOrder(Order.GetPrefab("dismissed"), Order.GetDismissOrderOption(currentOrder), currentOrder.ManualPriority, character, speak: speak, force: force); + break; + } + } + break; + case OrderCategory.Movement: + // If there character has another movement order, dismiss that order + OrderInfo? orderToReplace = null; + foreach (var currentOrder in CurrentOrders) { if (currentOrder.Order == null) { continue; } - if (currentOrder.Order.Category != OrderCategory.Operate) { continue; } - if (currentOrder.Order.Identifier != order.Identifier) { continue; } - if (currentOrder.Order.TargetEntity != order.TargetEntity) { continue; } - character.SetOrder(Order.GetPrefab("dismissed"), Order.GetDismissOrderOption(currentOrder), currentOrder.ManualPriority, character, speak: speak, force: force); + if (currentOrder.Order.Category != OrderCategory.Movement) { continue; } + orderToReplace = currentOrder; break; } - } + if (orderToReplace.HasValue) + { + SetOrder(Order.GetPrefab("dismissed"), Order.GetDismissOrderOption(orderToReplace.Value), orderToReplace.Value.ManualPriority, this, speak: speak, force: force); + } + break; } // Prevent adding duplicate orders @@ -3092,6 +3154,19 @@ namespace Barotrauma OrderInfo newOrderInfo = new OrderInfo(order, orderOption, priority); AddCurrentOrder(newOrderInfo); + + if (orderGiver != null) + { + var abilityOrderedCharacter = new AbilityCharacter(this); + orderGiver.CheckTalents(AbilityEffectType.OnGiveOrder, abilityOrderedCharacter); + + if (orderGiver.LastOrderedCharacter != this) + { + orderGiver.SecondLastOrderedCharacter = orderGiver.LastOrderedCharacter; + orderGiver.LastOrderedCharacter = this; + } + } + if (AIController is HumanAIController humanAI) { humanAI.SetOrder(order, orderOption, priority, orderGiver, speak); @@ -3337,9 +3412,40 @@ namespace Barotrauma float attackImpulse = attack.TargetImpulse + attack.TargetForce * deltaTime; + AbilityAttackData attackData = new AbilityAttackData(attack, this); + if (attacker != null) + { + attackData.Attacker = attacker; + attacker.CheckTalents(AbilityEffectType.OnAttack, attackData); + CheckTalents(AbilityEffectType.OnAttacked, attackData); + attackData.DamageMultiplier *= 1 + attacker.GetStatValue(StatTypes.AttackMultiplier); + if (attacker.TeamID == TeamID) + { + attackData.DamageMultiplier *= 1 + attacker.GetStatValue(StatTypes.TeamAttackMultiplier); + } + } + + IEnumerable attackAfflictions; + + if (attackData.Afflictions != null) + { + attackAfflictions = attackData.Afflictions.Union(attack.Afflictions.Keys); + } + else + { + attackAfflictions = attack.Afflictions.Keys; + } + var attackResult = targetLimb == null ? - AddDamage(worldPosition, attack.Afflictions.Keys, attack.Stun, playSound, attackImpulse, out limbHit, attacker, attack.DamageMultiplier) : - DamageLimb(worldPosition, targetLimb, attack.Afflictions.Keys, attack.Stun, playSound, attackImpulse, attacker, attack.DamageMultiplier, penetration: penetration); + AddDamage(worldPosition, attackAfflictions, attack.Stun, playSound, attackImpulse, out limbHit, attacker, attack.DamageMultiplier * attackData.DamageMultiplier) : + DamageLimb(worldPosition, targetLimb, attackAfflictions, attack.Stun, playSound, attackImpulse, attacker, attack.DamageMultiplier * attackData.DamageMultiplier, penetration: penetration + attackData.AddedPenetration); + + if (attacker != null) + { + var abilityAttackResult = new AbilityAttackResult(attackResult); + attacker.CheckTalents(AbilityEffectType.OnAttackResult, abilityAttackResult); + CheckTalents(AbilityEffectType.OnAttackedResult, abilityAttackResult); + } if (limbHit == null) { return new AttackResult(); } Vector2 forceWorld = attack.TargetImpulseWorld + attack.TargetForceWorld; @@ -3425,9 +3531,9 @@ namespace Barotrauma } } - public AttackResult AddDamage(Vector2 worldPosition, IEnumerable afflictions, float stun, bool playSound, float attackImpulse = 0.0f, Character attacker = null) + public AttackResult AddDamage(Vector2 worldPosition, IEnumerable afflictions, float stun, bool playSound, float attackImpulse = 0.0f, Character attacker = null, float damageMultiplier = 1f) { - return AddDamage(worldPosition, afflictions, stun, playSound, attackImpulse, out _, attacker); + return AddDamage(worldPosition, afflictions, stun, playSound, attackImpulse, out _, attacker, damageMultiplier: damageMultiplier); } public AttackResult AddDamage(Vector2 worldPosition, IEnumerable afflictions, float stun, bool playSound, float attackImpulse, out Limb hitLimb, Character attacker = null, float damageMultiplier = 1) @@ -3436,11 +3542,6 @@ namespace Barotrauma if (Removed) { return new AttackResult(); } - if (attacker != null && GameMain.NetworkMember != null && !GameMain.NetworkMember.ServerSettings.AllowFriendlyFire) - { - if (attacker.TeamID == TeamID) { return new AttackResult(); } - } - float closestDistance = 0.0f; foreach (Limb limb in AnimController.Limbs) { @@ -3457,6 +3558,13 @@ namespace Barotrauma public void RecordKill(Character target) { + var abilityCharacter = new AbilityCharacter(target); + foreach (Character attackerCrewmember in GetFriendlyCrew(this)) + { + attackerCrewmember.CheckTalents(AbilityEffectType.OnCrewKillCharacter, abilityCharacter); + } + CheckTalents(AbilityEffectType.OnKillCharacter, abilityCharacter); + if (!IsOnPlayerTeam) { return; } if (GameMain.Config.KilledCreatures.Any(name => name.Equals(target.SpeciesName, StringComparison.OrdinalIgnoreCase))) { return; } GameMain.Config.KilledCreatures.Add(target.SpeciesName); @@ -3496,7 +3604,11 @@ namespace Barotrauma if (attacker != null && attacker != this && GameMain.NetworkMember != null && !GameMain.NetworkMember.ServerSettings.AllowFriendlyFire) { - if (attacker.TeamID == TeamID) { return new AttackResult(); } + if (attacker.TeamID == TeamID) + { + afflictions = afflictions.Where(a => !a.Prefab.IsBuff); + if (!afflictions.Any()) { return new AttackResult(); } + } } #if CLIENT @@ -3524,7 +3636,7 @@ namespace Barotrauma bool wasDead = IsDead; Vector2 simPos = hitLimb.SimPosition + ConvertUnits.ToSimUnits(dir); float prevVitality = CharacterHealth.Vitality; - AttackResult attackResult = hitLimb.AddDamage(simPos, afflictions, playSound, damageMultiplier: damageMultiplier, penetration: penetration); + AttackResult attackResult = hitLimb.AddDamage(simPos, afflictions, playSound, damageMultiplier: damageMultiplier, penetration: penetration, attacker: attacker); CharacterHealth.ApplyDamage(hitLimb, attackResult, allowStacking); if (attacker != this) { @@ -3551,6 +3663,7 @@ namespace Barotrauma ApplyStatusEffects(ActionType.OnDamaged, 1.0f); hitLimb.ApplyStatusEffects(ActionType.OnDamaged, 1.0f); } + return attackResult; } @@ -3567,16 +3680,14 @@ namespace Barotrauma { float attackerSkillLevel = attacker.GetSkillLevel("weapons"); attacker.Info?.IncreaseSkillLevel("weapons", - -healthChange * SkillSettings.Current.SkillIncreasePerHostileDamage / Math.Max(attackerSkillLevel, 1.0f), - attacker.Position + Vector2.UnitY * 100.0f); + -healthChange * SkillSettings.Current.SkillIncreasePerHostileDamage / Math.Max(attackerSkillLevel, 1.0f)); } } else if (healthChange > 0.0f) { float attackerSkillLevel = attacker.GetSkillLevel("medical"); attacker.Info?.IncreaseSkillLevel("medical", - healthChange * SkillSettings.Current.SkillIncreasePerFriendlyHealed / Math.Max(attackerSkillLevel, 1.0f), - attacker.Position + Vector2.UnitY * 100.0f); + healthChange * SkillSettings.Current.SkillIncreasePerFriendlyHealed / Math.Max(attackerSkillLevel, 1.0f)); } } @@ -3590,6 +3701,7 @@ namespace Barotrauma { if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient && !isNetworkMessage) { return; } if (Screen.Selected != GameMain.GameScreen) { return; } + if (newStun > 0 && Params.Health.StunImmunity) { return; } if ((newStun <= Stun && !allowStunDecrease) || !MathUtils.IsValid(newStun)) { return; } if (Math.Sign(newStun) != Math.Sign(Stun)) { @@ -3611,10 +3723,7 @@ namespace Barotrauma if (statusEffect.type != actionType) { continue; } if (statusEffect.type == ActionType.OnDamaged) { - if (statusEffect.AllowedAfflictions != null && (LastDamage.Afflictions == null || LastDamage.Afflictions.None(a => statusEffect.AllowedAfflictions.Contains(a.Prefab.AfflictionType) || statusEffect.AllowedAfflictions.Contains(a.Prefab.Identifier)))) - { - continue; - } + if (!statusEffect.HasRequiredAfflictions(LastDamage)) { continue; } if (statusEffect.OnlyPlayerTriggered) { if (LastAttacker == null || !LastAttacker.IsPlayer) @@ -3677,7 +3786,7 @@ namespace Barotrauma } } - private void Implode(bool isNetworkMessage = false) + public void Implode(bool isNetworkMessage = false) { if (CharacterHealth.Unkillable || GodMode || IsDead) { return; } @@ -3720,6 +3829,7 @@ namespace Barotrauma foreach (var joint in AnimController.LimbJoints) { + if (joint.LimbA.type == LimbType.Head || joint.LimbB.type == LimbType.Head) { continue; } if (joint.revoluteJoint != null) { joint.revoluteJoint.LimitEnabled = false; @@ -3775,6 +3885,9 @@ namespace Barotrauma causeOfDeathAffliction?.Source ?? LastAttacker, LastDamageSource); OnDeath?.Invoke(this, CauseOfDeath); + var abilityKiller = new AbilityCharacter(CauseOfDeath.Killer); + CheckTalents(AbilityEffectType.OnDieToCharacter, abilityKiller); + if (GameMain.GameSession != null && Screen.Selected == GameMain.GameScreen) { SteamAchievementManager.OnCharacterKilled(this, CauseOfDeath); @@ -3782,13 +3895,20 @@ namespace Barotrauma KillProjSpecific(causeOfDeath, causeOfDeathAffliction, log); - if (info != null) { info.CauseOfDeath = CauseOfDeath; } + if (info != null) + { + info.CauseOfDeath = CauseOfDeath; + info.MissionsCompletedSinceDeath = 0; + } AnimController.movement = Vector2.Zero; AnimController.TargetMovement = Vector2.Zero; - foreach (Item heldItem in HeldItems.ToList()) + if (!LockHands) { - heldItem.Drop(this); + foreach (Item heldItem in HeldItems.ToList()) + { + heldItem.Drop(this); + } } SelectedConstruction = null; @@ -3805,12 +3925,17 @@ namespace Barotrauma if (GameMain.GameSession != null) { + if (GameMain.GameSession.Campaign != null && TeamID == CharacterTeamType.Team1 && !IsAssistant) + { + GameMain.GameSession.Campaign.CrewHasDied = true; + } + GameMain.GameSession.KillCharacter(this); } } partial void KillProjSpecific(CauseOfDeathType causeOfDeath, Affliction causeOfDeathAffliction, bool log); - public void Revive() + public void Revive(bool removeAllAfflictions = true) { if (Removed) { @@ -3821,7 +3946,14 @@ namespace Barotrauma aiTarget?.Remove(); aiTarget = new AITarget(this); - CharacterHealth.RemoveAllAfflictions(); + if (removeAllAfflictions) + { + CharacterHealth.RemoveAllAfflictions(); + } + else + { + CharacterHealth.RemoveNegativeAfflictions(); + } SetAllDamage(0.0f, 0.0f, 0.0f); Oxygen = 100.0f; Bloodloss = 0.0f; @@ -3842,7 +3974,10 @@ namespace Barotrauma foreach (Limb limb in AnimController.Limbs) { #if CLIENT - if (limb.LightSource != null) limb.LightSource.Color = limb.InitialLightSourceColor; + if (limb.LightSource != null) + { + limb.LightSource.Color = limb.InitialLightSourceColor; + } #endif limb.body.Enabled = true; limb.IsSevered = false; @@ -4164,12 +4299,8 @@ namespace Barotrauma } if (Submarine == null && target.Submarine != null) { - if (AIController == null || !(AIController.SteeringManager is IndoorsSteeringManager)) - { - // outside and targeting inside - // doesn't work with inside steering - targetPos += target.Submarine.SimPosition; - } + // outside and targeting inside + targetPos += target.Submarine.SimPosition; } else if (Submarine != null && target.Submarine == null) { @@ -4203,8 +4334,277 @@ namespace Barotrauma public bool IsProtectedFromPressure() { - return PressureProtection >= (Level.Loaded?.GetRealWorldDepth(WorldPosition.Y) ?? 1.0f); + return HasAbilityFlag(AbilityFlags.ImmuneToPressure) || PressureProtection >= (Level.Loaded?.GetRealWorldDepth(WorldPosition.Y) ?? 1.0f); } + + // Talent logic begins here. Should be encapsulated to its own controller soon + + private readonly List characterTalents = new List(); + + public void LoadTalents() + { + List toBeRemoved = null; + foreach (string talent in info.UnlockedTalents) + { + if (!GiveTalent(talent, addingFirstTime: false)) + { + DebugConsole.AddWarning(Name + " had talent that did not exist! Removing talent from CharacterInfo."); + toBeRemoved ??= new List(); + toBeRemoved.Add(talent); + } + } + + if (toBeRemoved != null) + { + foreach (string removeTalent in toBeRemoved) + { + Info.UnlockedTalents.Remove(removeTalent); + } + } + } + + public bool GiveTalent(string talentIdentifier, bool addingFirstTime = true) + { + TalentPrefab talentPrefab = TalentPrefab.TalentPrefabs.Find(c => c.Identifier.Equals(talentIdentifier, StringComparison.OrdinalIgnoreCase)); + if (talentPrefab == null) + { + DebugConsole.AddWarning($"Tried to add talent by identifier {talentIdentifier} to character {Name}, but no such talent exists."); + return false; + } + return GiveTalent(talentPrefab, addingFirstTime); + } + + public bool GiveTalent(UInt32 talentIdentifier, bool addingFirstTime = true) + { + TalentPrefab talentPrefab = TalentPrefab.TalentPrefabs.Find(c => c.UIntIdentifier == talentIdentifier); + if (talentPrefab == null) + { + DebugConsole.AddWarning($"Tried to add talent by identifier {talentIdentifier} to character {Name}, but no such talent exists."); + return false; + } + return GiveTalent(talentPrefab, addingFirstTime); + } + + public bool GiveTalent(TalentPrefab talentPrefab, bool addingFirstTime = true) + { + if (info == null) { return false; } + info.UnlockedTalents.Add(talentPrefab.Identifier); + if (characterTalents.Any(t => t.Prefab == talentPrefab)) { return false; } + +#if SERVER + GameMain.NetworkMember.CreateEntityEvent(this, new object[] { NetEntityEvent.Type.UpdateTalents }); +#endif + CharacterTalent characterTalent = new CharacterTalent(talentPrefab, this); + characterTalent.ActivateTalent(addingFirstTime); + characterTalents.Add(characterTalent); + characterTalent.AddedThisRound = addingFirstTime; + + if (addingFirstTime) + { + OnTalentGiven(talentPrefab.Identifier); + } + return true; + } + + public bool HasTalent(string identifier) + { + return info.UnlockedTalents.Contains(identifier); + } + + public static IEnumerable GetFriendlyCrew(Character character) + { + return CharacterList.Where(c => HumanAIController.IsFriendly(character, c, onlySameTeam: true) && !c.IsDead); + } + + public bool HasTalents() + { + return characterTalents.Any(); + } + + public void CheckTalents(AbilityEffectType abilityEffectType, AbilityObject abilityObject) + { + foreach (var characterTalent in characterTalents) + { + characterTalent.CheckTalent(abilityEffectType, abilityObject); + } + } + + public void CheckTalents(AbilityEffectType abilityEffectType) + { + foreach (var characterTalent in characterTalents) + { + characterTalent.CheckTalent(abilityEffectType, null); + } + } + + public bool HasRecipeForItem(string recipeIdentifier) + { + return characterTalents.Any(t => t.UnlockedRecipes.Contains(recipeIdentifier)); + } + + /// + /// Shows visual notification of money gained by the specific player. Useful for mid-mission monetary gains. + /// + public void GiveMoney(int amount) + { + if (!(GameMain.GameSession?.Campaign is CampaignMode campaign)) { return; } + if (amount <= 0) { return; } + + int prevAmount = campaign.Money; + campaign.Money += amount; + OnMoneyChanged(prevAmount, campaign.Money); + } + + public void SetMoney(int amount) + { + if (!(GameMain.GameSession?.Campaign is CampaignMode campaign)) { return; } + if (amount == campaign.Money) { return; } + + int prevAmount = campaign.Money; + campaign.Money = amount; + OnMoneyChanged(prevAmount, campaign.Money); + } + + partial void OnMoneyChanged(int prevAmount, int newAmount); + partial void OnTalentGiven(string talentIdentifier); + + /// + /// This dictionary is used for stats that are required very frequently. Not very performant, but easier to develop with for now. + /// If necessary, the approach of using a dictionary could be replaced by an encapsulated class that contains the stats as attributes. + /// + private readonly Dictionary statValues = new Dictionary(); + + /// + /// A dictionary with temporary values, updated when the character equips/unequips wearables. Used to reduce unnecessary inventory checking. + /// + private readonly Dictionary wearableStatValues = new Dictionary(); + + public float GetStatValue(StatTypes statType) + { + if (!IsHuman) { return 0f; } + + float statValue = 0f; + if (statValues.TryGetValue(statType, out float value)) + { + statValue += value; + } + if (CharacterHealth != null) + { + statValue += CharacterHealth.GetStatValue(statType); + } + if (Info != null) + { + // could be optimized by instead updating the Character.cs statvalues dictionary whenever the CharacterInfo.cs values change + statValue += Info.GetSavedStatValue(statType); + } + if (wearableStatValues.TryGetValue(statType, out float wearableValue)) + { + statValue += wearableValue; + } + + return statValue; + } + + public void OnWearablesChanged() + { + wearableStatValues.Clear(); + for (int i = 0; i < Inventory.Capacity; i++) + { + if (Inventory.SlotTypes[i] != InvSlotType.Any && Inventory.SlotTypes[i] != InvSlotType.LeftHand && Inventory.SlotTypes[i] != InvSlotType.RightHand + && Inventory.GetItemAt(i)?.GetComponent() is Wearable wearable) + { + foreach (var statValuePair in wearable.WearableStatValues) + { + if (wearableStatValues.ContainsKey(statValuePair.Key)) + { + wearableStatValues[statValuePair.Key] += statValuePair.Value; + } + else + { + wearableStatValues.Add(statValuePair.Key, statValuePair.Value); + } + } + } + } + } + + public void ChangeStat(StatTypes statType, float value) + { + if (statValues.ContainsKey(statType)) + { + statValues[statType] += value; + } + else + { + statValues.Add(statType, value); + } + } + + public static StatTypes GetSkillStatType(string skillIdentifier) + { + // Using this method to translate between skill identifiers and stat types. Feel free to replace it if there's a better way + switch (skillIdentifier) + { + case "electrical": + return StatTypes.ElectricalSkillBonus; + case "helm": + return StatTypes.HelmSkillBonus; + case "mechanical": + return StatTypes.MechanicalSkillBonus; + case "medical": + return StatTypes.MedicalSkillBonus; + case "weapons": + return StatTypes.WeaponsSkillBonus; + default: + return StatTypes.None; + } + } + + private readonly List abilityFlags = new List(); + + public void AddAbilityFlag(AbilityFlags abilityFlag) + { + abilityFlags.Add(abilityFlag); + } + + public void RemoveAbilityFlag(AbilityFlags abilityFlag) + { + abilityFlags.Remove(abilityFlag); + } + + public bool HasAbilityFlag(AbilityFlags abilityFlag) + { + return abilityFlags.Contains(abilityFlag) || CharacterHealth.HasFlag(abilityFlag); + } + + private readonly Dictionary abilityResistances = new Dictionary(); + + public float GetAbilityResistance(AfflictionPrefab affliction) + { + return abilityResistances.TryGetValue(affliction.Identifier, out float value) ? value : abilityResistances.TryGetValue(affliction.AfflictionType, out float typeValue) ? typeValue : 1f; + } + + public void ChangeAbilityResistance(string resistanceId, float value) + { + if (abilityResistances.ContainsKey(resistanceId)) + { + abilityResistances[resistanceId] *= value; + } + else + { + abilityResistances.Add(resistanceId, value); + } + } + + /// + /// Compares just the species name and the group, ignores teams. There's a more complex version found in HumanAIController.cs + /// + public bool IsFriendly(Character other) => IsFriendly(this, other); + + /// + /// Compares just the species name and the group, ignores teams. There's a more complex version found in HumanAIController.cs + /// + public static bool IsFriendly(Character me, Character other) => other.SpeciesName == me.SpeciesName || other.Params.CompareGroup(me.Params.Group); } class ActiveTeamChange diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs index 14e7f34c5..c193a3918 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs @@ -1,19 +1,19 @@ using Barotrauma.Extensions; using Barotrauma.Items.Components; -using Barotrauma.Networking; using Microsoft.Xna.Framework; using System; using System.Collections.Generic; +using System.Collections.Immutable; using Barotrauma.IO; using System.Linq; using System.Xml.Linq; +using Barotrauma.Abilities; namespace Barotrauma { public enum Gender { None, Male, Female }; public enum Race { None, White, Black, Brown, Asian }; - // TODO: Generating the HeadInfo could be simplified. partial class CharacterInfo { public class HeadInfo @@ -24,15 +24,7 @@ namespace Barotrauma get { return _headSpriteId; } set { - _headSpriteId = value; - if (_headSpriteId < (int)headSpriteRange.X) - { - _headSpriteId = (int)headSpriteRange.Y; - } - if (_headSpriteId > (int)headSpriteRange.Y) - { - _headSpriteId = (int)headSpriteRange.X; - } + _headSpriteId = Math.Max(Math.Clamp(value, (int)headSpriteRange.X, (int)headSpriteRange.Y), 1); GetSpriteSheetIndex(); } } @@ -41,6 +33,10 @@ namespace Barotrauma public Gender gender; public Race race; + public Color HairColor; + public Color FacialHairColor; + public Color SkinColor; + public int HairIndex { get; set; } = -1; public int BeardIndex { get; set; } = -1; public int MoustacheIndex { get; set; } = -1; @@ -73,11 +69,11 @@ namespace Barotrauma FaceAttachmentIndex = -1; } - private void GetSpriteSheetIndex() + public void GetSpriteSheetIndex() { if (heads != null && heads.Any()) { - var matchingHead = heads.Keys.FirstOrDefault(h => h.Gender == gender && h.Race == race && h.ID == _headSpriteId); + var matchingHead = heads.Keys.FirstOrDefault(h => h.ID == HeadSpriteId && IsMatchingGender(h.Gender, gender) && IsMatchingRace(h.Race, race)); if (matchingHead != null) { if (heads.TryGetValue(matchingHead, out Vector2 index)) @@ -98,14 +94,13 @@ namespace Barotrauma if (head != value && value != null) { head = value; - if (head.race == Race.None) + if (!IsValidRace(head.race)) { head.race = GetRandomRace(Rand.RandSync.Unsynced); } CalculateHeadSpriteRange(); Head.HeadSpriteId = value.HeadSpriteId; - HeadSprite = null; - AttachmentSprites = null; + RefreshHeadSprites(); } } } @@ -156,7 +151,9 @@ namespace Barotrauma public bool HasNickname => Name != OriginalName; public string OriginalName { get; private set; } + public string Name; + public string DisplayName { get @@ -215,30 +212,56 @@ namespace Barotrauma public int Salary; - private Sprite headSprite; + public int ExperiencePoints { get; private set; } + + public HashSet UnlockedTalents { get; private set; } = new HashSet(); + + /// + /// Endocrine boosters can unlock talents outside the user's talent tree. This method is used to cull them from the selection + /// + public IEnumerable GetUnlockedTalentsInTree() + { + if (!TalentTree.JobTalentTrees.TryGetValue(Job.Prefab.Identifier, out TalentTree talentTree)) { return Enumerable.Empty(); } + + return UnlockedTalents.Where(t => talentTree.TalentIsInTree(t)); + } + + /// + /// Endocrine boosters can unlock talents outside the user's talent tree. This method is used to specifically get them + /// + public IEnumerable GetEndocrineTalents() + { + if (!TalentTree.JobTalentTrees.TryGetValue(Job.Prefab.Identifier, out TalentTree talentTree)) { return Enumerable.Empty(); } + + return UnlockedTalents.Where(t => !talentTree.TalentIsInTree(t)); + } + + public int AdditionalTalentPoints { get; set; } + + private Sprite _headSprite; public Sprite HeadSprite { get { - if (headSprite == null) + if (_headSprite == null) { LoadHeadSprite(); } #if CLIENT - if (headSprite != null) + if (_headSprite != null) { - CalculateHeadPosition(headSprite); + CalculateHeadPosition(_headSprite); } #endif - return headSprite; + return _headSprite; } private set { - if (headSprite != null) + if (_headSprite != null) { - headSprite.Remove(); + _headSprite.Remove(); } - headSprite = value; + _headSprite = value; } } @@ -284,6 +307,7 @@ namespace Barotrauma Character.CharacterHealth.ApplyAffliction(Character.AnimController.GetLimb(LimbType.Head), AfflictionPrefab.List.FirstOrDefault(a => a.Identifier.Equals("disguised", StringComparison.OrdinalIgnoreCase)).Instantiate(100f)); } + idCard ??= Character.Inventory?.GetItemInLimbSlot(InvSlotType.Card)?.GetComponent(); if (idCard != null) { #if CLIENT @@ -291,19 +315,6 @@ namespace Barotrauma #endif return; } - - if (Character.Inventory != null) - { - idCard = Character.Inventory.GetItemInLimbSlot(InvSlotType.Card)?.GetComponent(); - if (idCard != null) - { -#if CLIENT - GetDisguisedSprites(idCard); -#endif - return; - } - - } } #if CLIENT @@ -380,29 +391,31 @@ namespace Barotrauma set { Head.HeadSpriteId = value; - HeadSprite = null; - AttachmentSprites = null; ResetHeadAttachments(); + RefreshHeadSprites(); } } public readonly bool HasGenders; + public readonly bool HasRaces; public Gender Gender { get { return Head.gender; } set { - if (Head.gender == value) return; + Gender previousValue = Head.gender; Head.gender = value; - if (Head.gender == Gender.None) + if (!IsValidGender(Head.gender)) { - Head.gender = Gender.Male; + Head.gender = GetDefaultGender(); + } + if (Head.gender != previousValue) + { + CalculateHeadSpriteRange(); + ResetHeadAttachments(); + RefreshHeadSprites(); } - CalculateHeadSpriteRange(); - ResetHeadAttachments(); - HeadSprite = null; - AttachmentSprites = null; } } @@ -411,28 +424,82 @@ namespace Barotrauma get { return Head.race; } set { - if (Head.race == value) { return; } + Race previousValue = Head.race; Head.race = value; - if (Head.race == Race.None) + if (!IsValidRace(Head.race)) { - Head.race = Race.White; + Head.race = GetDefaultRace(); + } + if (Head.race != previousValue) + { + CalculateHeadSpriteRange(); + ResetHeadAttachments(); + RefreshHeadSprites(); } - CalculateHeadSpriteRange(); - ResetHeadAttachments(); - HeadSprite = null; - AttachmentSprites = null; } } - public int HairIndex { get => Head.HairIndex; set => Head.HairIndex = value; } - public int BeardIndex { get => Head.BeardIndex; set => Head.BeardIndex = value; } - public int MoustacheIndex { get => Head.MoustacheIndex; set => Head.MoustacheIndex = value; } - public int FaceAttachmentIndex { get => Head.FaceAttachmentIndex; set => Head.FaceAttachmentIndex = value; } + private bool IsValidRace(Race race) => HasRaces ? race != Race.None : race == Race.None; - public XElement HairElement { get => Head.HairElement; set => Head.HairElement = value; } - public XElement BeardElement { get => Head.BeardElement; set => Head.BeardElement = value; } - public XElement MoustacheElement { get => Head.MoustacheElement; set => Head.MoustacheElement = value; } - public XElement FaceAttachment { get => Head.FaceAttachment; set => Head.FaceAttachment = value; } + private bool IsValidGender(Gender gender) => HasGenders ? gender != Gender.None : gender == Gender.None; + + private Gender GetDefaultGender() => HasGenders ? Gender.Male : Gender.None; + + private Race GetDefaultRace() => HasRaces ? Race.White : Race.None; + + public int HairIndex + { + get => Head.HairIndex; + set => Head.HairIndex = value; + } + + public int BeardIndex + { + get => Head.BeardIndex; + set => Head.BeardIndex = value; + } + + public int MoustacheIndex + { + get => Head.MoustacheIndex; + set => Head.MoustacheIndex = value; + } + + public int FaceAttachmentIndex + { + get => Head.FaceAttachmentIndex; + set => Head.FaceAttachmentIndex = value; + } + + public readonly ImmutableArray<(Color Color, float Commonness)> HairColors; + public readonly ImmutableArray<(Color Color, float Commonness)> FacialHairColors; + public readonly ImmutableArray<(Color Color, float Commonness)> SkinColors; + + public Color HairColor + { + get => Head.HairColor; + set => Head.HairColor = value; + } + + public Color FacialHairColor + { + get => Head.FacialHairColor; + set => Head.FacialHairColor = value; + } + + public Color SkinColor + { + get => Head.SkinColor; + set => Head.SkinColor = value; + } + + public XElement HairElement => Head.HairElement; + + public XElement BeardElement => Head.BeardElement; + + public XElement MoustacheElement => Head.MoustacheElement; + + public XElement FaceAttachment => Head.FaceAttachment; private RagdollParams ragdoll; public RagdollParams Ragdoll @@ -455,6 +522,9 @@ namespace Barotrauma public bool IsAttachmentsLoaded => HairIndex > -1 && BeardIndex > -1 && MoustacheIndex > -1 && FaceAttachmentIndex > -1; + // talent-relevant values + public int MissionsCompletedSinceDeath = 0; + // Used for creating the data public CharacterInfo(string speciesName, string name = "", string originalName = "", JobPrefab jobPrefab = null, string ragdollFileName = null, int variant = 0, Rand.RandSync randSync = Rand.RandSync.Unsynced, string npcIdentifier = "") { @@ -470,16 +540,15 @@ namespace Barotrauma if (doc == null) { return; } CharacterConfigElement = doc.Root.IsOverride() ? doc.Root.FirstElement() : doc.Root; // TODO: support for variants - head = new HeadInfo(); + Head = new HeadInfo(); HasGenders = CharacterConfigElement.GetAttributeBool("genders", false); - if (HasGenders) - { - Head.gender = GetRandomGender(randSync); - } - Head.race = GetRandomRace(randSync); - CalculateHeadSpriteRange(); - Head.HeadSpriteId = GetRandomHeadID(randSync); + HasRaces = CharacterConfigElement.GetAttributeBool("races", false); + SetGenderAndRace(randSync); Job = (jobPrefab == null) ? Job.Random(Rand.RandSync.Unsynced) : new Job(jobPrefab, variant); + HairColors = CharacterConfigElement.GetAttributeTupleArray("haircolors", new (Color, float)[] { (Color.WhiteSmoke, 100f) }).ToImmutableArray(); + FacialHairColors = CharacterConfigElement.GetAttributeTupleArray("facialhaircolors", new (Color, float)[] { (Color.WhiteSmoke, 100f) }).ToImmutableArray(); + SkinColors = CharacterConfigElement.GetAttributeTupleArray("skincolors", new (Color, float)[] { (new Color(255, 215, 200, 255), 100f) }).ToImmutableArray(); + SetColors(); if (!string.IsNullOrEmpty(name)) { @@ -492,23 +561,7 @@ namespace Barotrauma else { name = ""; - if (CharacterConfigElement.Element("name") != null) - { - string firstNamePath = CharacterConfigElement.Element("name").GetAttributeString("firstname", ""); - if (firstNamePath != "") - { - firstNamePath = firstNamePath.Replace("[GENDER]", (Head.gender == Gender.Female) ? "female" : "male"); - Name = ToolBox.GetRandomLine(firstNamePath, randSync); - } - - string lastNamePath = CharacterConfigElement.Element("name").GetAttributeString("lastname", ""); - if (lastNamePath != "") - { - lastNamePath = lastNamePath.Replace("[GENDER]", (Head.gender == Gender.Female) ? "female" : "male"); - if (Name != "") Name += " "; - Name += ToolBox.GetRandomLine(lastNamePath, randSync); - } - } + Name = GetRandomName(randSync); } OriginalName = !string.IsNullOrEmpty(originalName) ? originalName : Name; personalityTrait = NPCPersonalityTrait.GetRandom(name + HeadSpriteId); @@ -520,6 +573,65 @@ namespace Barotrauma LoadHeadAttachments(); } + public string GetRandomName(Rand.RandSync randSync) + { + string name = ""; + if (CharacterConfigElement.Element("name") != null) + { + string firstNamePath = CharacterConfigElement.Element("name").GetAttributeString("firstname", ""); + if (firstNamePath != "") + { + firstNamePath = firstNamePath.Replace("[GENDER]", (Head.gender == Gender.Female) ? "female" : "male"); + name = ToolBox.GetRandomLine(firstNamePath, randSync); + } + + string lastNamePath = CharacterConfigElement.Element("name").GetAttributeString("lastname", ""); + if (lastNamePath != "") + { + lastNamePath = lastNamePath.Replace("[GENDER]", (Head.gender == Gender.Female) ? "female" : "male"); + if (name != "") { name += " "; } + name += ToolBox.GetRandomLine(lastNamePath, randSync); + } + } + + return name; + } + + public static Color SelectRandomColor(in ImmutableArray<(Color Color, float Commonness)> array) + => ToolBox.SelectWeightedRandom(array, array.Select(p => p.Commonness).ToArray(), Rand.RandSync.Unsynced) + .Color; + + private void SetGenderAndRace(Rand.RandSync randSync) + { + Head.gender = GetRandomGender(randSync); + Head.race = GetRandomRace(randSync); + CalculateHeadSpriteRange(); + HeadSpriteId = GetRandomHeadID(randSync); + } + + private void SetColors() + { + HairColor = SelectRandomColor(HairColors); + FacialHairColor = SelectRandomColor(FacialHairColors); + SkinColor = SelectRandomColor(SkinColors); + } + + private void CheckColors() + { + if (HairColor == Color.Black) + { + HairColor = SelectRandomColor(HairColors); + } + if (FacialHairColor == Color.Black) + { + FacialHairColor = SelectRandomColor(FacialHairColors); + } + if (SkinColor == Color.Black) + { + SkinColor = SelectRandomColor(SkinColors); + } + } + // Used for loading the data public CharacterInfo(XElement infoElement) { @@ -527,9 +639,11 @@ namespace Barotrauma idCounter++; Name = infoElement.GetAttributeString("name", ""); OriginalName = infoElement.GetAttributeString("originalname", null); - string genderStr = infoElement.GetAttributeString("gender", "male").ToLowerInvariant(); Salary = infoElement.GetAttributeInt("salary", 1000); - Enum.TryParse(infoElement.GetAttributeString("race", "White"), true, out Race race); + ExperiencePoints = infoElement.GetAttributeInt("experiencepoints", 0); + UnlockedTalents = new HashSet(infoElement.GetAttributeStringArray("unlockedtalents", new string[0], convertToLowerInvariant: true)); + AdditionalTalentPoints = infoElement.GetAttributeInt("additionaltalentpoints", 0); + Enum.TryParse(infoElement.GetAttributeString("race", "None"), true, out Race race); Enum.TryParse(infoElement.GetAttributeString("gender", "None"), true, out Gender gender); _speciesName = infoElement.GetAttributeString("speciesname", null); XDocument doc = null; @@ -547,14 +661,19 @@ namespace Barotrauma // TODO: support for variants CharacterConfigElement = doc.Root.IsOverride() ? doc.Root.FirstElement() : doc.Root; HasGenders = CharacterConfigElement.GetAttributeBool("genders", false); - if (HasGenders && gender == Gender.None) + HasRaces = CharacterConfigElement.GetAttributeBool("hasraces", false); + if (!IsValidGender(gender)) { gender = GetRandomGender(Rand.RandSync.Unsynced); } - else if (!HasGenders) + if (!IsValidRace(race)) { - gender = Gender.None; + race = GetRandomRace(Rand.RandSync.Unsynced); } + HairColors = CharacterConfigElement.GetAttributeTupleArray("haircolors", new (Color, float)[] { (Color.WhiteSmoke, 100f) }).ToImmutableArray(); + FacialHairColors = CharacterConfigElement.GetAttributeTupleArray("facialhaircolors", new (Color, float)[] { (Color.WhiteSmoke, 100f) }).ToImmutableArray(); + SkinColors = CharacterConfigElement.GetAttributeTupleArray("skincolors", new (Color, float)[] { (new Color(255, 215, 200, 255), 100f) }).ToImmutableArray(); + RecreateHead( infoElement.GetAttributeInt("headspriteid", 1), race, @@ -564,6 +683,37 @@ namespace Barotrauma infoElement.GetAttributeInt("moustacheindex", -1), infoElement.GetAttributeInt("faceattachmentindex", -1)); + //backwards compatibility + if (infoElement.Attribute("skincolor") == null && infoElement.Attribute("race") != null) + { + string raceStr = infoElement.GetAttributeString("race", string.Empty); + Race obsoleteRace = Race.None; + Enum.TryParse(raceStr, ignoreCase: true, out obsoleteRace); + switch (obsoleteRace) + { + case Race.White: + case Race.None: + SkinColor = new Color(255, 215, 200, 255); + break; + case Race.Brown: + SkinColor = new Color(158, 95, 72, 255); + break; + case Race.Black: + SkinColor = new Color(153, 75, 42, 255); + break; + case Race.Asian: + SkinColor = new Color(191, 116, 61, 255); + break; + } + } + else + { + SkinColor = infoElement.GetAttributeColor("skincolor", Color.Black); + } + HairColor = infoElement.GetAttributeColor("haircolor", Color.Black); + FacialHairColor = infoElement.GetAttributeColor("facialhaircolor", Color.Black); + CheckColors(); + if (string.IsNullOrEmpty(Name)) { if (CharacterConfigElement.Element("name") != null) @@ -596,20 +746,68 @@ namespace Barotrauma if (!string.IsNullOrEmpty(personalityName)) { personalityTrait = NPCPersonalityTrait.List.Find(p => p.Name == personalityName); - } + } + + MissionsCompletedSinceDeath = infoElement.GetAttributeInt("missionscompletedsincedeath", 0); + foreach (XElement subElement in infoElement.Elements()) { - if (subElement.Name.ToString().Equals("job", StringComparison.OrdinalIgnoreCase)) + bool jobCreated = false; + if (subElement.Name.ToString().Equals("job", StringComparison.OrdinalIgnoreCase) && !jobCreated) { Job = new Job(subElement); - break; + jobCreated = true; + // there used to be a break here, but it had to be removed to make room for statvalues + // using the jobCreated boolean to make sure that only the first job found is created + } + else if (subElement.Name.ToString().Equals("savedstatvalues", StringComparison.OrdinalIgnoreCase)) + { + foreach (XElement savedStat in subElement.Elements()) + { + string statTypeString = savedStat.GetAttributeString("stattype", "").ToLowerInvariant(); + if (!Enum.TryParse(statTypeString, true, out StatTypes statType)) + { + DebugConsole.ThrowError("Invalid stat type type \"" + statTypeString + "\" when loading character data in CharacterInfo!"); + continue; + } + + float value = savedStat.GetAttributeFloat("statvalue", 0f); + if (value == 0f) { continue; } + + string statIdentifier = savedStat.GetAttributeString("statidentifier", "").ToLowerInvariant(); + if (string.IsNullOrEmpty(statIdentifier)) + { + DebugConsole.ThrowError("Stat identifier not specified for Stat Value when loading character data in CharacterInfo!"); + return; + } + + bool removeOnDeath = savedStat.GetAttributeBool("removeondeath", true); + ChangeSavedStatValue(statType, value, statIdentifier, removeOnDeath); + } } } LoadHeadAttachments(); } - public Gender GetRandomGender(Rand.RandSync randSync) => (Rand.Range(0.0f, 1.0f, randSync) < CharacterConfigElement.GetAttributeFloat("femaleratio", 0.5f)) ? Gender.Female : Gender.Male; - public Race GetRandomRace(Rand.RandSync randSync) => new Race[] { Race.White, Race.Black, Race.Asian }.GetRandom(randSync); + public Gender GetRandomGender(Rand.RandSync randSync) + { + if (HasGenders) + { + return (Rand.Range(0.0f, 1.0f, randSync) < CharacterConfigElement.GetAttributeFloat("femaleratio", 0.5f)) ? Gender.Female : Gender.Male; + } + return Gender.None; + } + + public Race GetRandomRace(Rand.RandSync randSync) + { + if (HasRaces) + { + return new Race[] { Race.White, Race.Black, Race.Asian }.GetRandom(randSync); + } + return Race.None; + } + + public int GetRandomHeadID(Rand.RandSync randSync) => Head.headSpriteRange != Vector2.Zero ? Rand.Range((int)Head.headSpriteRange.X, (int)Head.headSpriteRange.Y + 1, randSync) : 0; private List hairs; @@ -676,10 +874,13 @@ namespace Barotrauma { if (elements == null) { return elements; } return elements.Where(w => - Enum.TryParse(w.GetAttributeString("gender", "None"), true, out Gender g) && g == gender && - Enum.TryParse(w.GetAttributeString("race", "None"), true, out Race r) && r == race); + IsMatchingGender(Enum.Parse(w.GetAttributeString("gender", "None"), ignoreCase: true), gender) && + IsMatchingRace(Enum.Parse(w.GetAttributeString("race", "None"), ignoreCase: true), race)); } + public static bool IsMatchingGender(Gender gender, Gender myGender) => gender == Gender.None || gender == myGender; + public static bool IsMatchingRace(Race race, Race myRace) => race == Race.None || race == myRace; + private void LoadHeadPresets() { if (CharacterConfigElement == null) { return; } @@ -708,9 +909,16 @@ namespace Barotrauma // If there are any head presets defined, use them. if (heads.Any()) { - var ids = heads.Keys.Where(h => h.Race == Race && h.Gender == Gender).Select(w => w.ID); + var ids = heads.Keys.Where(h => IsMatchingRace(Race, h.Race) && IsMatchingGender(Gender, h.Gender)).Select(w => w.ID); ids = ids.OrderBy(id => id); - Head.headSpriteRange = new Vector2(ids.First(), ids.Last()); + if (ids.Any()) + { + Head.headSpriteRange = new Vector2(ids.First(), ids.Last()); + } + else + { + DebugConsole.ThrowError($"[CharacterInfo] Couldn't find a head definition that matches {Race} and {Gender}!"); + } } // Else we calculate the range from the wearables. if (Head.headSpriteRange == Vector2.Zero) @@ -743,26 +951,72 @@ namespace Barotrauma } } + public void RecreateHead(HeadInfo headInfo) + { + RecreateHead( + headInfo.HeadSpriteId, + headInfo.race, + headInfo.gender, + headInfo.HairIndex, + headInfo.BeardIndex, + headInfo.MoustacheIndex, + headInfo.FaceAttachmentIndex); + + SkinColor = headInfo.SkinColor; + HairColor = headInfo.HairColor; + FacialHairColor = headInfo.FacialHairColor; + CheckColors(); + } + + /// + /// Recreates the head info and checks that everything is valid. + /// public void RecreateHead(int headID, Race race, Gender gender, int hairIndex, int beardIndex, int moustacheIndex, int faceAttachmentIndex) { - if (HasGenders && gender == Gender.None) + if (!IsValidGender(gender)) { gender = GetRandomGender(Rand.RandSync.Unsynced); } - else if (!HasGenders) + if (!IsValidRace(race)) { - gender = Gender.None; + race = GetRandomRace(Rand.RandSync.Unsynced); } if (heads == null) { LoadHeadPresets(); } - head = new HeadInfo(headID, gender, race, hairIndex, beardIndex, moustacheIndex, faceAttachmentIndex); + Color skin = Color.Black; + Color hair = Color.Black; + Color facialHair = Color.Black; + if (head != null) + { + skin = head.SkinColor; + hair = head.HairColor; + facialHair = head.FacialHairColor; + } + head = new HeadInfo(headID, gender, race, hairIndex, beardIndex, moustacheIndex, faceAttachmentIndex) + { + SkinColor = skin, + HairColor = hair, + FacialHairColor = facialHair + }; CalculateHeadSpriteRange(); ReloadHeadAttachments(); + RefreshHead(); } - public void LoadHeadSprite() + /// + /// Reloads the head sprite and the attachment sprites. + /// + public void RefreshHead() + { + ReloadHeadAttachments(); + RefreshHeadSprites(); + } + + partial void LoadHeadSpriteProjectSpecific(XElement limbElement); + + private void LoadHeadSprite() { foreach (XElement limbElement in Ragdoll.MainElement.Elements()) { @@ -772,6 +1026,7 @@ namespace Barotrauma if (spriteElement == null) { continue; } string spritePath = spriteElement.Attribute("texture").Value; + if (string.IsNullOrEmpty(spritePath)) { continue; } spritePath = spritePath.Replace("[GENDER]", (Head.gender == Gender.Female) ? "female" : "male"); spritePath = spritePath.Replace("[RACE]", Head.race.ToString().ToLowerInvariant()); @@ -779,6 +1034,8 @@ namespace Barotrauma string fileName = Path.GetFileNameWithoutExtension(spritePath); + if (string.IsNullOrEmpty(fileName)) { continue; } + //go through the files in the directory to find a matching sprite foreach (string file in Directory.GetFiles(Path.GetDirectoryName(spritePath))) { @@ -803,13 +1060,12 @@ namespace Barotrauma break; } + LoadHeadSpriteProjectSpecific(limbElement); + break; } } - /// - /// Loads only the elements according to the indices, not the sprites. - /// public void LoadHeadAttachments() { if (Wearables != null) @@ -871,7 +1127,7 @@ namespace Barotrauma } } - private static List AddEmpty(IEnumerable elements, WearableType type, float commonness = 1) + public static List AddEmpty(IEnumerable elements, WearableType type, float commonness = 1) { // Let's add an empty element so that there's a chance that we don't get any actual element -> allows bald and beardless guys, for example. var emptyElement = new XElement("EmptyWearable", type.ToString(), new XAttribute("commonness", commonness)); @@ -880,9 +1136,9 @@ namespace Barotrauma return list; } - private XElement GetRandomElement(IEnumerable elements) + public XElement GetRandomElement(IEnumerable elements) { - var filtered = elements.Where(e => IsWearableAllowed(e)); + var filtered = elements.Where(IsWearableAllowed); if (filtered.Count() == 0) { return null; } var element = ToolBox.SelectWeightedRandom(filtered.ToList(), GetWeights(filtered).ToList(), Rand.RandSync.Unsynced); return element == null || element.Name == "Empty" ? null : element; @@ -907,7 +1163,7 @@ namespace Barotrauma return true; } - private static bool IsValidIndex(int index, List list) => index >= 0 && index < list.Count; + public static bool IsValidIndex(int index, List list) => index >= 0 && index < list.Count; private static IEnumerable GetWeights(IEnumerable elements) => elements.Select(h => h.GetAttributeFloat("commonness", 1f)); @@ -926,7 +1182,7 @@ namespace Barotrauma return (int)(salary * Job.Prefab.PriceMultiplier); } - public void IncreaseSkillLevel(string skillIdentifier, float increase, Vector2 pos) + public void IncreaseSkillLevel(string skillIdentifier, float increase, bool gainedFromApprenticeship = false) { if (Job == null || (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) || Character == null) { return; } @@ -935,15 +1191,29 @@ namespace Barotrauma increase *= SkillSettings.Current.AssistantSkillIncreaseMultiplier; } + increase *= 1f + Character.GetStatValue(StatTypes.SkillGainSpeed); + float prevLevel = Job.GetSkillLevel(skillIdentifier); - Job.IncreaseSkillLevel(skillIdentifier, increase); + Job.IncreaseSkillLevel(skillIdentifier, increase, Character.HasAbilityFlag(AbilityFlags.GainSkillPastMaximum)); float newLevel = Job.GetSkillLevel(skillIdentifier); - OnSkillChanged(skillIdentifier, prevLevel, newLevel, pos); + if ((int)newLevel > (int)prevLevel) + { + // assume we are getting at least 1 point in skill, since this logic only runs in such cases + float increaseSinceLastSkillPoint = MathHelper.Max(increase, 1f); + var abilitySkillGain = new AbilitySkillGain(increaseSinceLastSkillPoint, skillIdentifier, Character, gainedFromApprenticeship); + Character.CheckTalents(AbilityEffectType.OnGainSkillPoint, abilitySkillGain); + foreach (Character character in Character.GetFriendlyCrew(Character)) + { + character.CheckTalents(AbilityEffectType.OnAllyGainSkillPoint, abilitySkillGain); + } + } + + OnSkillChanged(skillIdentifier, prevLevel, newLevel); } - public void SetSkillLevel(string skillIdentifier, float level, Vector2 pos) + public void SetSkillLevel(string skillIdentifier, float level) { if (Job == null) { return; } @@ -951,17 +1221,102 @@ namespace Barotrauma if (skill == null) { Job.Skills.Add(new Skill(skillIdentifier, level)); - OnSkillChanged(skillIdentifier, 0.0f, level, pos); + OnSkillChanged(skillIdentifier, 0.0f, level); } else { float prevLevel = skill.Level; skill.Level = level; - OnSkillChanged(skillIdentifier, prevLevel, skill.Level, pos); + OnSkillChanged(skillIdentifier, prevLevel, skill.Level); } } - partial void OnSkillChanged(string skillIdentifier, float prevLevel, float newLevel, Vector2 textPopupPos); + partial void OnSkillChanged(string skillIdentifier, float prevLevel, float newLevel); + + public void GiveExperience(int amount, bool isMissionExperience = false) + { + int prevAmount = ExperiencePoints; + + var experienceGainMultiplier = new AbilityValue(1f); + if (isMissionExperience) + { + Character?.CheckTalents(AbilityEffectType.OnGainMissionExperience, experienceGainMultiplier); + } + experienceGainMultiplier.Value += Character?.GetStatValue(StatTypes.ExperienceGainMultiplier) ?? 0; + + amount = (int)(amount * experienceGainMultiplier.Value); + if (amount < 0) { return; } + + ExperiencePoints += amount; + OnExperienceChanged(prevAmount, ExperiencePoints); + } + + public void SetExperience(int newExperience) + { + if (newExperience < 0) { return; } + + int prevAmount = ExperiencePoints; + ExperiencePoints = newExperience; + OnExperienceChanged(prevAmount, ExperiencePoints); + } + + const int BaseExperienceRequired = -50; + const int AddedExperienceRequiredPerLevel = 500; + + public int GetTotalTalentPoints() + { + return GetCurrentLevel() + AdditionalTalentPoints - 1; + } + + public int GetAvailableTalentPoints() + { + // hashset always has at least 1 + return Math.Max(GetTotalTalentPoints() - GetUnlockedTalentsInTree().Count(), 0); + } + + public float GetProgressTowardsNextLevel() + { + float progress = (ExperiencePoints - GetExperienceRequiredForCurrentLevel()) / (GetExperienceRequiredToLevelUp() - GetExperienceRequiredForCurrentLevel()); + return progress; + } + + public float GetExperienceRequiredForCurrentLevel() + { + GetCurrentLevel(out int experienceRequired); + return experienceRequired; + } + + public float GetExperienceRequiredToLevelUp() + { + int level = GetCurrentLevel(out int experienceRequired); + return experienceRequired + ExperienceRequiredPerLevel(level); + } + + public int GetCurrentLevel() + { + return GetCurrentLevel(out _); + } + + private int GetCurrentLevel(out int experienceRequired) + { + int level = 1; + experienceRequired = 0; + while (experienceRequired + ExperienceRequiredPerLevel(level) <= ExperiencePoints) + { + experienceRequired += ExperienceRequiredPerLevel(level); + level++; + } + return level; + } + + private int ExperienceRequiredPerLevel(int level) + { + return BaseExperienceRequired + AddedExperienceRequiredPerLevel * level; + } + + partial void OnExperienceChanged(int prevAmount, int newAmount); + + partial void OnPermanentStatChanged(StatTypes statType); public void Rename(string newName) { @@ -996,20 +1351,27 @@ namespace Barotrauma new XAttribute("name", Name), new XAttribute("originalname", OriginalName), new XAttribute("speciesname", SpeciesName), - new XAttribute("gender", Head.gender == Gender.Male ? "male" : "female"), + new XAttribute("gender", Head.gender.ToString()), new XAttribute("race", Head.race.ToString()), new XAttribute("salary", Salary), + new XAttribute("experiencepoints", ExperiencePoints), + new XAttribute("unlockedtalents", string.Join(",", UnlockedTalents)), + new XAttribute("additionaltalentpoints", AdditionalTalentPoints), new XAttribute("headspriteid", HeadSpriteId), new XAttribute("hairindex", HairIndex), new XAttribute("beardindex", BeardIndex), new XAttribute("moustacheindex", MoustacheIndex), new XAttribute("faceattachmentindex", FaceAttachmentIndex), + new XAttribute("skincolor", XMLExtensions.ColorToString(SkinColor)), + new XAttribute("haircolor", XMLExtensions.ColorToString(HairColor)), + new XAttribute("facialhaircolor", XMLExtensions.ColorToString(FacialHairColor)), new XAttribute("startitemsgiven", StartItemsGiven), new XAttribute("ragdoll", ragdollFileName), new XAttribute("personality", personalityTrait == null ? "" : personalityTrait.Name)); - // TODO: animations? + charElement.Add(new XAttribute("missionscompletedsincedeath", MissionsCompletedSinceDeath)); + if (Character != null) { if (Character.AnimController.CurrentHull != null) @@ -1020,6 +1382,27 @@ namespace Barotrauma Job.Save(charElement); + XElement savedStatElement = new XElement("savedstatvalues"); + foreach (var statValuePair in SavedStatValues) + { + foreach (var savedStat in statValuePair.Value) + { + if (savedStat.StatValue == 0f) { continue; } + if (savedStat.RemoveAfterRound) { continue; } + + savedStatElement.Add(new XElement("savedstatvalue", + new XAttribute("stattype", statValuePair.Key.ToString()), + new XAttribute("statidentifier", savedStat.StatIdentifier), + new XAttribute("statvalue", savedStat.StatValue), + new XAttribute("removeondeath", savedStat.RemoveOnDeath) + )); + } + } + + + + charElement.Add(savedStatElement); + parentElement.Add(charElement); return charElement; } @@ -1295,13 +1678,19 @@ namespace Barotrauma if (healthData != null) { character?.CharacterHealth.Load(healthData); } } - public void ReloadHeadAttachments() + /// + /// Reloads the attachment xml elements according to the indices. Doesn't reload the sprites. + /// + private void ReloadHeadAttachments() { ResetLoadedAttachments(); LoadHeadAttachments(); } - public void ResetHeadAttachments() + /// + /// Loads only the elements according to the indices, not the sprites. + /// + private void ResetHeadAttachments() { ResetAttachmentIndices(); ResetLoadedAttachments(); @@ -1332,5 +1721,121 @@ namespace Barotrauma Portrait = null; AttachmentSprites = null; } + + private void RefreshHeadSprites() + { + HeadSprite = null; + AttachmentSprites = null; + } + + // This could maybe be a LookUp instead? + public readonly Dictionary> SavedStatValues = new Dictionary>(); + + public void ClearSavedStatValues() + { + foreach (StatTypes statType in SavedStatValues.Keys) + { + OnPermanentStatChanged(statType); + } + SavedStatValues.Clear(); + } + + public void ClearSavedStatValues(StatTypes statType) + { + SavedStatValues.Remove(statType); + OnPermanentStatChanged(statType); + } + + public void ResetSavedStatValue(string statIdentifier) + { + foreach (StatTypes statType in SavedStatValues.Keys) + { + bool changed = false; + foreach (SavedStatValue savedStatValue in SavedStatValues[statType]) + { + if (savedStatValue.StatIdentifier != statIdentifier) { continue; } + if (MathUtils.NearlyEqual(savedStatValue.StatValue, 0.0f)) { continue; } + savedStatValue.StatValue = 0.0f; + changed = true; + } + if (changed) { OnPermanentStatChanged(statType); } + } + } + + public float GetSavedStatValue(StatTypes statType) + { + if (SavedStatValues.TryGetValue(statType, out var statValues)) + { + return statValues.Sum(v => v.StatValue); + } + else + { + return 0f; + } + } + public float GetSavedStatValue(StatTypes statType, string statIdentifier) + { + if (SavedStatValues.TryGetValue(statType, out var statValues)) + { + return statValues.Where(s => s.StatIdentifier.Equals(statIdentifier, StringComparison.OrdinalIgnoreCase)).Sum(v => v.StatValue); + } + else + { + return 0f; + } + } + + public void ChangeSavedStatValue(StatTypes statType, float value, string statIdentifier, bool removeOnDeath, bool removeAfterRound = false, float maxValue = float.MaxValue, bool setValue = false) + { + if (!SavedStatValues.ContainsKey(statType)) + { + SavedStatValues.Add(statType, new List()); + } + + bool changed = false; + if (SavedStatValues[statType].FirstOrDefault(s => s.StatIdentifier == statIdentifier) is SavedStatValue savedStat) + { + float prevValue = savedStat.StatValue; + savedStat.StatValue = setValue ? value : MathHelper.Min(savedStat.StatValue + value, maxValue); + changed = !MathUtils.NearlyEqual(savedStat.StatValue, prevValue); + } + else + { + SavedStatValues[statType].Add(new SavedStatValue(statIdentifier, MathHelper.Min(value, maxValue), removeOnDeath, removeAfterRound)); + changed = true; + } + if (changed) { OnPermanentStatChanged(statType); } + } + } + + public class SavedStatValue + { + public string StatIdentifier { get; set; } + public float StatValue { get; set; } + public bool RemoveOnDeath { get; set; } + public bool RemoveAfterRound { get; set; } + + public SavedStatValue(string statIdentifier, float value, bool removeOnDeath, bool retainAfterRound) + { + StatValue = value; + RemoveOnDeath = removeOnDeath; + StatIdentifier = statIdentifier; + RemoveAfterRound = retainAfterRound; + } + } + + class AbilitySkillGain : AbilityObject, IAbilityValue, IAbilityString, IAbilityCharacter + { + public AbilitySkillGain(float value, string abilityString, Character character, bool gainedFromApprenticeship) + { + Value = value; + String = abilityString; + Character = character; + GainedFromApprenticeship = gainedFromApprenticeship; + } + public Character Character { get; set; } + public float Value { get; set; } + public string String { get; set; } + public bool GainedFromApprenticeship { get; set; } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/Affliction.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/Affliction.cs index 82f03ec56..347b52096 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/Affliction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/Affliction.cs @@ -17,6 +17,8 @@ namespace Barotrauma public float PendingAdditionStrength { get; set; } public float AdditionStrength { get; set; } + private float fluctuationTimer; + protected float _strength; [Serialize(0f, true), Editable] @@ -56,6 +58,8 @@ namespace Barotrauma public readonly Dictionary PeriodicEffectTimers = new Dictionary(); + public double AppliedAsSuccessfulTreatmentTime, AppliedAsFailedTreatmentTime; + /// /// Which character gave this affliction /// @@ -123,7 +127,7 @@ namespace Barotrauma float amount = MathHelper.Lerp( currentEffect.MinGrainStrength, currentEffect.MaxGrainStrength, - (Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength)); + (Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength)) * GetScreenEffectFluctuation(currentEffect); if (Prefab.GrainBurst > 0 && AdditionStrength > amount) { @@ -138,12 +142,12 @@ namespace Barotrauma if (Strength < Prefab.ActivationThreshold) { return 0.0f; } AfflictionPrefab.Effect currentEffect = GetActiveEffect(); if (currentEffect == null) { return 0.0f; } - if (currentEffect.MaxScreenDistortStrength - currentEffect.MinScreenDistortStrength < 0.0f) { return 0.0f; } + if (currentEffect.MaxScreenDistort - currentEffect.MinScreenDistort < 0.0f) { return 0.0f; } return MathHelper.Lerp( - currentEffect.MinScreenDistortStrength, - currentEffect.MaxScreenDistortStrength, - (Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength)); + currentEffect.MinScreenDistort, + currentEffect.MaxScreenDistort, + (Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength)) * GetScreenEffectFluctuation(currentEffect); } public float GetRadialDistortStrength() @@ -151,12 +155,12 @@ namespace Barotrauma if (Strength < Prefab.ActivationThreshold) { return 0.0f; } AfflictionPrefab.Effect currentEffect = GetActiveEffect(); if (currentEffect == null) { return 0.0f; } - if (currentEffect.MaxRadialDistortStrength - currentEffect.MinRadialDistortStrength < 0.0f) { return 0.0f; } + if (currentEffect.MaxRadialDistort - currentEffect.MinRadialDistort < 0.0f) { return 0.0f; } return MathHelper.Lerp( - currentEffect.MinRadialDistortStrength, - currentEffect.MaxRadialDistortStrength, - (Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength)); + currentEffect.MinRadialDistort, + currentEffect.MaxRadialDistort, + (Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength)) * GetScreenEffectFluctuation(currentEffect); } public float GetChromaticAberrationStrength() @@ -164,11 +168,50 @@ namespace Barotrauma if (Strength < Prefab.ActivationThreshold) { return 0.0f; } AfflictionPrefab.Effect currentEffect = GetActiveEffect(); if (currentEffect == null) { return 0.0f; } - if (currentEffect.MaxChromaticAberrationStrength - currentEffect.MinChromaticAberrationStrength < 0.0f) { return 0.0f; } + if (currentEffect.MaxChromaticAberration - currentEffect.MinChromaticAberration < 0.0f) { return 0.0f; } return MathHelper.Lerp( - currentEffect.MinChromaticAberrationStrength, - currentEffect.MaxChromaticAberrationStrength, + currentEffect.MinChromaticAberration, + currentEffect.MaxChromaticAberration, + (Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength)) * GetScreenEffectFluctuation(currentEffect); + } + + public float GetAfflictionOverlayMultiplier() + { + //If the overlay's alpha progresses linearly, then don't worry about affliction effects. + if (Prefab.AfflictionOverlayAlphaIsLinear) { return (Strength / Prefab.MaxStrength); } + if (Strength < Prefab.ActivationThreshold) { return 0.0f; } + AfflictionPrefab.Effect currentEffect = GetActiveEffect(); + if (currentEffect == null) { return 0.0f; } + if (currentEffect.MaxAfflictionOverlayAlphaMultiplier - currentEffect.MinAfflictionOverlayAlphaMultiplier < 0.0f) { return 0.0f; } + + return MathHelper.Lerp( + currentEffect.MinAfflictionOverlayAlphaMultiplier, + currentEffect.MaxAfflictionOverlayAlphaMultiplier, + (Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength)); + } + + public Color GetFaceTint() + { + if (Strength < Prefab.ActivationThreshold) { return Color.TransparentBlack; } + AfflictionPrefab.Effect currentEffect = GetActiveEffect(); + if (currentEffect == null) { return Color.TransparentBlack; } + + return Color.Lerp( + currentEffect.MinFaceTint, + currentEffect.MaxFaceTint, + (Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength)); + } + + public Color GetBodyTint() + { + if (Strength < Prefab.ActivationThreshold) { return Color.TransparentBlack; } + AfflictionPrefab.Effect currentEffect = GetActiveEffect(); + if (currentEffect == null) { return Color.TransparentBlack; } + + return Color.Lerp( + currentEffect.MinBodyTint, + currentEffect.MaxBodyTint, (Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength)); } @@ -177,12 +220,18 @@ namespace Barotrauma if (Strength < Prefab.ActivationThreshold) { return 0.0f; } AfflictionPrefab.Effect currentEffect = GetActiveEffect(); if (currentEffect == null) { return 0.0f; } - if (currentEffect.MaxScreenBlurStrength - currentEffect.MinScreenBlurStrength < 0.0f) { return 0.0f; } + if (currentEffect.MaxScreenBlur - currentEffect.MinScreenBlur < 0.0f) { return 0.0f; } return MathHelper.Lerp( - currentEffect.MinScreenBlurStrength, - currentEffect.MaxScreenBlurStrength, - (Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength)); + currentEffect.MinScreenBlur, + currentEffect.MaxScreenBlur, + (Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength)) * GetScreenEffectFluctuation(currentEffect); + } + + private float GetScreenEffectFluctuation(AfflictionPrefab.Effect currentEffect) + { + if (currentEffect == null || currentEffect.ScreenEffectFluctuationFrequency <= 0.0f) { return 1.0f; } + return ((float)Math.Sin(fluctuationTimer * MathHelper.TwoPi) + 1.0f) * 0.5f; } public float GetSkillMultiplier() @@ -210,14 +259,17 @@ namespace Barotrauma } } - public float GetResistance(string afflictionId) + public float GetResistance(AfflictionPrefab affliction) { if (Strength < Prefab.ActivationThreshold) { return 0.0f; } AfflictionPrefab.Effect currentEffect = GetActiveEffect(); if (currentEffect == null) { return 0.0f; } - if (currentEffect.MaxResistance - currentEffect.MinResistance <= 0.0f) { return 0.0f; } - if (afflictionId != null && afflictionId != currentEffect.ResistanceFor) { return 0.0f; } - + if (!currentEffect.ResistanceFor.Any(r => + r.Equals(affliction.Identifier, StringComparison.OrdinalIgnoreCase) || + r.Equals(affliction.AfflictionType, StringComparison.OrdinalIgnoreCase))) + { + return 0.0f; + } return MathHelper.Lerp( currentEffect.MinResistance, currentEffect.MaxResistance, @@ -229,14 +281,39 @@ namespace Barotrauma if (Strength < Prefab.ActivationThreshold) { return 1.0f; } AfflictionPrefab.Effect currentEffect = GetActiveEffect(); if (currentEffect == null) { return 1.0f; } - if (currentEffect.MaxSpeedMultiplier - currentEffect.MinSpeedMultiplier <= 0.0f) { return 1.0f; } - return MathHelper.Lerp( currentEffect.MinSpeedMultiplier, currentEffect.MaxSpeedMultiplier, (Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength)); } + public float GetStatValue(StatTypes statType) + { + if (!(GetViableEffect() is AfflictionPrefab.Effect currentEffect)) { return 0.0f; } + + if (currentEffect.AfflictionStatValues.TryGetValue(statType, out var value)) + { + return MathHelper.Lerp( + value.minValue, + value.maxValue, + (Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength)); + } + return 0.0f; + } + + public bool HasFlag(AbilityFlags flagType) + { + if (!(GetViableEffect() is AfflictionPrefab.Effect currentEffect)) { return false; } + + return currentEffect.AfflictionAbilityFlags.Contains(flagType); + } + + private AfflictionPrefab.Effect GetViableEffect() + { + if (Strength < Prefab.ActivationThreshold) { return null; } + return GetActiveEffect(); + } + public virtual void Update(CharacterHealth characterHealth, Limb targetLimb, float deltaTime) { foreach (AfflictionPrefab.PeriodicEffect periodicEffect in Prefab.PeriodicEffects) @@ -262,13 +339,20 @@ namespace Barotrauma AfflictionPrefab.Effect currentEffect = GetActiveEffect(); if (currentEffect == null) { return; } + fluctuationTimer += deltaTime * currentEffect.ScreenEffectFluctuationFrequency; + fluctuationTimer %= 1.0f; + if (currentEffect.StrengthChange < 0) // Reduce diminishing of buffs if boosted { - _strength += currentEffect.StrengthChange * deltaTime * StrengthDiminishMultiplier; + float durationMultiplier = 1 / (1 + (Prefab.IsBuff ? characterHealth.Character.GetStatValue(StatTypes.BuffDurationMultiplier) + : characterHealth.Character.GetStatValue(StatTypes.DebuffDurationMultiplier))); + + _strength += currentEffect.StrengthChange * deltaTime * StrengthDiminishMultiplier * durationMultiplier; + } - else // Reduce strengthening of afflictions if resistant + else if (currentEffect.StrengthChange > 0) // Reduce strengthening of afflictions if resistant { - _strength += currentEffect.StrengthChange * deltaTime * (1f - characterHealth.GetResistance(Prefab.Identifier)); + _strength += currentEffect.StrengthChange * deltaTime * (1f - characterHealth.GetResistance(Prefab)); } // Don't use the property, because it's virtual and some afflictions like husk overload it for external use. _strength = MathHelper.Clamp(_strength, 0.0f, Prefab.MaxStrength); @@ -306,6 +390,8 @@ namespace Barotrauma private readonly List targets = new List(); public void ApplyStatusEffect(ActionType type, StatusEffect statusEffect, float deltaTime, CharacterHealth characterHealth, Limb targetLimb) { + if (type == ActionType.OnDamaged && !statusEffect.HasRequiredAfflictions(characterHealth.Character.LastDamage)) { return; } + statusEffect.SetUser(Source); if (statusEffect.HasTargetType(StatusEffect.TargetType.Character)) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionHusk.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionHusk.cs index 10907fe96..3878de39b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionHusk.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionHusk.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Xml.Linq; using System; using Barotrauma.Extensions; +using Microsoft.Xna.Framework; namespace Barotrauma { @@ -21,6 +22,8 @@ namespace Barotrauma private Character character; + private bool stun = true; + private readonly List huskInfection = new List(); [Serialize(0f, true), Editable] @@ -34,6 +37,11 @@ namespace Barotrauma float threshold = _strength > ActiveThreshold ? ActiveThreshold + 1 : DormantThreshold - 1; float max = Math.Max(threshold, previousValue); _strength = Math.Clamp(value, 0, max); + stun = GameMain.GameSession?.IsRunning ?? true; + if (previousValue > 0.0f && value <= 0.0f) + { + DeactivateHusk(); + } } } @@ -51,8 +59,12 @@ namespace Barotrauma } } - private float DormantThreshold => Prefab.MaxStrength * 0.5f; - private float ActiveThreshold => Prefab.MaxStrength * 0.75f; + private float DormantThreshold => (Prefab as AfflictionPrefabHusk)?.DormantThreshold ?? Prefab.MaxStrength * 0.5f; + private float ActiveThreshold => (Prefab as AfflictionPrefabHusk)?.ActiveThreshold ?? Prefab.MaxStrength * 0.75f; + + private float TransitionThreshold => (Prefab as AfflictionPrefabHusk)?.TransitionThreshold ?? Prefab.MaxStrength * 0.75f; + + private float TransformThresholdOnDeath => (Prefab as AfflictionPrefabHusk)?.TransformThresholdOnDeath ?? ActiveThreshold; public AfflictionHusk(AfflictionPrefab prefab, float strength) : base(prefab, strength) { } @@ -83,9 +95,9 @@ namespace Barotrauma } State = InfectionState.Transition; } - else if (Strength < Prefab.MaxStrength) + else if (Strength < TransitionThreshold) { - if (State != InfectionState.Active) + if (State != InfectionState.Active && stun) { character.SetStun(Rand.Range(2, 4)); } @@ -139,6 +151,7 @@ namespace Barotrauma private void DeactivateHusk() { + if (character?.AnimController == null || character.Removed) { return; } if (Prefab is AfflictionPrefabHusk { NeedsAir: false }) { character.NeedsAir = character.Params.MainElement.GetAttributeBool("needsair", false); @@ -161,7 +174,7 @@ namespace Barotrauma private void CharacterDead(Character character, CauseOfDeath causeOfDeath) { if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; } - if (Strength < ActiveThreshold || character.Removed) + if (Strength < TransformThresholdOnDeath || character.Removed) { UnsubscribeFromDeathEvent(); return; @@ -205,6 +218,14 @@ namespace Barotrauma XElement parentElement = new XElement("CharacterInfo"); XElement infoElement = character.Info?.Save(parentElement); CharacterInfo huskCharacterInfo = infoElement == null ? null : new CharacterInfo(infoElement); + + if (huskCharacterInfo != null) + { + var bodyTint = GetBodyTint(); + huskCharacterInfo.SkinColor = + Color.Lerp(huskCharacterInfo.SkinColor, bodyTint.Opaque(), bodyTint.A / 255.0f); + } + var husk = Character.Create(huskedSpeciesName, character.WorldPosition, ToolBox.RandomSeed(8), huskCharacterInfo, isRemotePlayer: false, hasAi: true); if (husk.Info != null) { @@ -212,6 +233,25 @@ namespace Barotrauma husk.Info.TeamID = CharacterTeamType.None; } + if (Prefab is AfflictionPrefabHusk huskPrefab) + { + if (huskPrefab.ControlHusk) + { +#if SERVER + var client = GameMain.Server?.ConnectedClients.FirstOrDefault(c => c.CharacterInfo.Character == character); + if (client != null) + { + GameMain.Server.SetClientCharacter(client, husk); + } +#else + if (!character.IsRemotelyControlled && character == Character.Controlled) + { + Character.Controlled = husk; + } +#endif + } + } + foreach (Limb limb in husk.AnimController.Limbs) { if (limb.type == LimbType.None) @@ -229,15 +269,19 @@ namespace Barotrauma } } + if ((Prefab as AfflictionPrefabHusk)?.TransferBuffs ?? false) + { + foreach (Affliction affliction in character.CharacterHealth.Afflictions) + { + if (affliction.Prefab.IsBuff) + { + husk.CharacterHealth.ApplyAffliction(null, affliction.Prefab.Instantiate(affliction.Strength)); + } + } + } + if (character.Inventory != null && husk.Inventory != null) { - if (character.Inventory.Capacity != husk.Inventory.Capacity) - { - string errorMsg = "Failed to move items from the source character's inventory into a husk's inventory (inventory sizes don't match)"; - DebugConsole.ThrowError(errorMsg); - GameAnalyticsManager.AddErrorEventOnce("AfflictionHusk.CreateAIHusk:InventoryMismatch", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); - yield return CoroutineStatus.Success; - } for (int i = 0; i < character.Inventory.Capacity && i < husk.Inventory.Capacity; i++) { character.Inventory.GetItemsAt(i).ForEachMod(item => husk.Inventory.TryPutItem(item, i, true, false, null)); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionPrefab.cs index 0606a8368..f527262ad 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionPrefab.cs @@ -1,10 +1,10 @@ -using Microsoft.Xna.Framework; +using Barotrauma.Abilities; +using Microsoft.Xna.Framework; using System; using System.Collections.Generic; +using System.Linq; using System.Reflection; using System.Xml.Linq; -using System.Linq; -using System.Security.Cryptography; namespace Barotrauma { @@ -91,9 +91,16 @@ namespace Barotrauma AttachLimbType = LimbType.None; } + TransferBuffs = element.GetAttributeBool("transferbuffs", true); SendMessages = element.GetAttributeBool("sendmessages", true); CauseSpeechImpediment = element.GetAttributeBool("causespeechimpediment", true); NeedsAir = element.GetAttributeBool("needsair", false); + ControlHusk = element.GetAttributeBool("controlhusk", false); + + DormantThreshold = element.GetAttributeFloat("dormantthreshold", MaxStrength * 0.5f); + ActiveThreshold = element.GetAttributeFloat("activethreshold", MaxStrength * 0.75f); + TransitionThreshold = element.GetAttributeFloat("transitionthreshold", MaxStrength); + TransformThresholdOnDeath = element.GetAttributeFloat("transformthresholdondeath", ActiveThreshold); } // Use any of these to define which limb the appendage is attached to. @@ -102,13 +109,18 @@ namespace Barotrauma public readonly string AttachLimbName; public readonly LimbType AttachLimbType; + public float ActiveThreshold, DormantThreshold, TransitionThreshold; + public float TransformThresholdOnDeath; + public readonly string HuskedSpeciesName; public readonly string[] TargetSpecies; public const string Tag = "[speciesname]"; + public readonly bool TransferBuffs; public readonly bool SendMessages; public readonly bool CauseSpeechImpediment; public readonly bool NeedsAir; + public readonly bool ControlHusk; } class AfflictionPrefab : IPrefab, IDisposable, IHasUintIdentifier @@ -116,83 +128,123 @@ namespace Barotrauma public class Effect { //this effect is applied when the strength is within this range - public float MinStrength, MaxStrength; + [Serialize(0.0f, false)] + public float MinStrength { get; private set; } + + [Serialize(0.0f, false)] + public float MaxStrength { get; private set; } + + [Serialize(0.0f, false)] + public float MinVitalityDecrease { get; private set; } + + [Serialize(0.0f, false)] + public float MaxVitalityDecrease { get; private set; } - public readonly float MinVitalityDecrease = 0.0f; - public readonly float MaxVitalityDecrease = 0.0f; - //how much the strength of the affliction changes per second - public readonly float StrengthChange = 0.0f; + [Serialize(0.0f, false)] + public float StrengthChange { get; private set; } - public readonly bool MultiplyByMaxVitality; + [Serialize(false, false)] + public bool MultiplyByMaxVitality { get; private set; } - public float MinScreenBlurStrength, MaxScreenBlurStrength; - public float MinScreenDistortStrength, MaxScreenDistortStrength; - public float MinGrainStrength, MaxGrainStrength; - public float MinRadialDistortStrength, MaxRadialDistortStrength; - public float MinChromaticAberrationStrength, MaxChromaticAberrationStrength; - public float MinSpeedMultiplier, MaxSpeedMultiplier; - public float MinBuffMultiplier, MaxBuffMultiplier; + [Serialize(0.0f, false)] + public float MinScreenBlur { get; private set; } - public float MinSkillMultiplier, MaxSkillMultiplier; + [Serialize(0.0f, false)] + public float MaxScreenBlur { get; private set; } - public float MinResistance, MaxResistance; - public string ResistanceFor; - public string DialogFlag; + [Serialize(0.0f, false)] + public float MinScreenDistort { get; private set; } + + [Serialize(0.0f, false)] + public float MaxScreenDistort { get; private set; } + + [Serialize(0.0f, false)] + public float MinRadialDistort { get; private set; } + + [Serialize(0.0f, false)] + public float MaxRadialDistort { get; private set; } + + [Serialize(0.0f, false)] + public float MinChromaticAberration { get; private set; } + + [Serialize(0.0f, false)] + public float MaxChromaticAberration { get; private set; } + + [Serialize("255,255,255,255", false)] + public Color GrainColor { get; private set; } + + [Serialize(0.0f, false)] + public float MinGrainStrength { get; private set; } + + [Serialize(0.0f, false)] + public float MaxGrainStrength { get; private set; } + + [Serialize(0.0f, false)] + public float ScreenEffectFluctuationFrequency { get; private set; } + + [Serialize(1.0f, false)] + public float MinAfflictionOverlayAlphaMultiplier { get; private set; } + + [Serialize(1.0f, false)] + public float MaxAfflictionOverlayAlphaMultiplier { get; private set; } + + [Serialize(1.0f, false)] + public float MinBuffMultiplier { get; private set; } + + [Serialize(1.0f, false)] + public float MaxBuffMultiplier { get; private set; } + + [Serialize(1.0f, false)] + public float MinSpeedMultiplier { get; private set; } + + [Serialize(1.0f, false)] + public float MaxSpeedMultiplier { get; private set; } + + [Serialize(1.0f, false)] + public float MinSkillMultiplier { get; private set; } + + [Serialize(1.0f, false)] + public float MaxSkillMultiplier { get; private set; } + + private readonly string[] resistanceFor; + public IEnumerable ResistanceFor + { + get { return resistanceFor; } + } + + [Serialize(0.0f, false)] + public float MinResistance { get; private set; } + + [Serialize(0.0f, false)] + public float MaxResistance { get; private set; } + + [Serialize("", false)] + public string DialogFlag { get; private set; } + + [Serialize("0,0,0,0", false)] + public Color MinFaceTint { get; private set; } + + [Serialize("0,0,0,0", false)] + public Color MaxFaceTint { get; private set; } + + [Serialize("0,0,0,0", false)] + public Color MinBodyTint { get; private set; } + + [Serialize("0,0,0,0", false)] + public Color MaxBodyTint { get; private set; } + + public readonly Dictionary AfflictionStatValues = new Dictionary(); + public readonly HashSet AfflictionAbilityFlags = new HashSet(); //statuseffects applied on the character when the affliction is active public readonly List StatusEffects = new List(); public Effect(XElement element, string parentDebugName) { - MinStrength = element.GetAttributeFloat("minstrength", 0); - MaxStrength = element.GetAttributeFloat("maxstrength", 0); + SerializableProperty.DeserializeProperties(this, element); - MultiplyByMaxVitality = element.GetAttributeBool("multiplybymaxvitality", false); - - MinVitalityDecrease = element.GetAttributeFloat("minvitalitydecrease", 0.0f); - MaxVitalityDecrease = element.GetAttributeFloat("maxvitalitydecrease", 0.0f); - MaxVitalityDecrease = Math.Max(MinVitalityDecrease, MaxVitalityDecrease); - - MinScreenDistortStrength = element.GetAttributeFloat("minscreendistort", 0.0f); - MaxScreenDistortStrength = element.GetAttributeFloat("maxscreendistort", 0.0f); - MaxScreenDistortStrength = Math.Max(MinScreenDistortStrength, MaxScreenDistortStrength); - - MinRadialDistortStrength = element.GetAttributeFloat("minradialdistort", 0.0f); - MaxRadialDistortStrength = element.GetAttributeFloat("maxradialdistort", 0.0f); - MaxRadialDistortStrength = Math.Max(MinRadialDistortStrength, MaxRadialDistortStrength); - - MinChromaticAberrationStrength = element.GetAttributeFloat("minchromaticaberration", 0.0f); - MaxChromaticAberrationStrength = element.GetAttributeFloat("maxchromaticaberration", 0.0f); - MaxChromaticAberrationStrength = Math.Max(MinChromaticAberrationStrength, MaxChromaticAberrationStrength); - - MinGrainStrength = element.GetAttributeFloat(nameof(MinGrainStrength).ToLower(), 0.0f); - MaxGrainStrength = element.GetAttributeFloat(nameof(MaxGrainStrength).ToLower(), 0.0f); - MaxGrainStrength = Math.Max(MinGrainStrength, MaxGrainStrength); - - MinScreenBlurStrength = element.GetAttributeFloat("minscreenblur", 0.0f); - MaxScreenBlurStrength = element.GetAttributeFloat("maxscreenblur", 0.0f); - MaxScreenBlurStrength = Math.Max(MinScreenBlurStrength, MaxScreenBlurStrength); - - MinSkillMultiplier = element.GetAttributeFloat("minskillmultiplier", 1.0f); - MaxSkillMultiplier = element.GetAttributeFloat("maxskillmultiplier", 1.0f); - - ResistanceFor = element.GetAttributeString("resistancefor", ""); - MinResistance = element.GetAttributeFloat("minresistance", 0.0f); - MaxResistance = element.GetAttributeFloat("maxresistance", 0.0f); - MaxResistance = Math.Max(MinResistance, MaxResistance); - - MinSpeedMultiplier = element.GetAttributeFloat("minspeedmultiplier", 1.0f); - MaxSpeedMultiplier = element.GetAttributeFloat("maxspeedmultiplier", 1.0f); - MaxSpeedMultiplier = Math.Max(MinSpeedMultiplier, MaxSpeedMultiplier); - - MinBuffMultiplier = element.GetAttributeFloat("minbuffmultiplier", 1.0f); - MaxBuffMultiplier = element.GetAttributeFloat("maxbuffmultiplier", 1.0f); - MaxBuffMultiplier = Math.Max(MinBuffMultiplier, MaxBuffMultiplier); - - DialogFlag = element.GetAttributeString("dialogflag", ""); - - StrengthChange = element.GetAttributeFloat("strengthchange", 0.0f); + resistanceFor = element.GetAttributeStringArray("resistancefor", new string[0], convertToLowerInvariant: true); foreach (XElement subElement in element.Elements()) { @@ -201,6 +253,19 @@ namespace Barotrauma case "statuseffect": StatusEffects.Add(StatusEffect.Load(subElement, parentDebugName)); break; + case "statvalue": + var statType = CharacterAbilityGroup.ParseStatType(subElement.GetAttributeString("stattype", ""), parentDebugName); + + float defaultValue = subElement.GetAttributeFloat("value", 0f); + float minValue = subElement.GetAttributeFloat("minvalue", defaultValue); + float maxValue = subElement.GetAttributeFloat("maxvalue", defaultValue); + + AfflictionStatValues.TryAdd(statType, (minValue, maxValue)); + break; + case "abilityflag": + var flagType = CharacterAbilityGroup.ParseFlagType(subElement.GetAttributeString("flagtype", ""), parentDebugName); + AfflictionAbilityFlags.Add(flagType); + break; } } } @@ -317,6 +382,9 @@ namespace Barotrauma public readonly Sprite Icon; public readonly Color[] IconColors; + public readonly Sprite AfflictionOverlay; + public readonly bool AfflictionOverlayAlphaIsLinear; + private readonly List effects = new List(); private readonly List periodicEffects = new List(); @@ -576,6 +644,11 @@ namespace Barotrauma Description = TextManager.Get("AfflictionDescription." + translationId, true) ?? element.GetAttributeString("description", ""); IsBuff = element.GetAttributeBool("isbuff", false); + if (element.Attribute("nameidentifier") != null) + { + Name = TextManager.Get(element.GetAttributeString("nameidentifier", string.Empty), returnNull: true) ?? Name; + } + LimbSpecific = element.GetAttributeBool("limbspecific", false); if (!LimbSpecific) { @@ -590,7 +663,7 @@ namespace Barotrauma ShowIconThreshold = element.GetAttributeFloat("showiconthreshold", Math.Max(ActivationThreshold, 0.05f)); ShowIconToOthersThreshold = element.GetAttributeFloat("showicontoothersthreshold", ShowIconThreshold); MaxStrength = element.GetAttributeFloat("maxstrength", 100.0f); - GrainBurst = element.GetAttributeFloat(nameof(GrainBurst).ToLower(), 0.0f); + GrainBurst = element.GetAttributeFloat(nameof(GrainBurst).ToLowerInvariant(), 0.0f); ShowInHealthScannerThreshold = element.GetAttributeFloat("showinhealthscannerthreshold", Math.Max(ActivationThreshold, 0.05f)); TreatmentThreshold = element.GetAttributeFloat("treatmentthreshold", Math.Max(ActivationThreshold, 5.0f)); @@ -604,6 +677,7 @@ namespace Barotrauma SelfCauseOfDeathDescription = TextManager.Get("AfflictionCauseOfDeathSelf." + translationId, true) ?? element.GetAttributeString("selfcauseofdeathdescription", ""); IconColors = element.GetAttributeColorArray("iconcolors", null); + AfflictionOverlayAlphaIsLinear = element.GetAttributeBool("afflictionoverlayalphaislinear", false); AchievementOnRemoved = element.GetAttributeString("achievementonremoved", ""); foreach (XElement subElement in element.Elements()) @@ -613,6 +687,18 @@ namespace Barotrauma case "icon": Icon = new Sprite(subElement); break; + case "afflictionoverlay": + AfflictionOverlay = new Sprite(subElement); + break; + case "statvalue": + DebugConsole.ThrowError($"Error in affliction \"{Identifier}\" - stat values should be configured inside the affliction's effects."); + break; + case "effect": + case "periodiceffect": + break; + default: + DebugConsole.AddWarning($"Unrecognized element in affliction \"{Identifier}\" ({subElement.Name})"); + break; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs index ad2d820c5..9980cb396 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs @@ -6,6 +6,7 @@ using System.Xml.Linq; using Barotrauma.Networking; using Barotrauma.Extensions; using System.Globalization; +using Barotrauma.Abilities; namespace Barotrauma { @@ -116,15 +117,15 @@ namespace Barotrauma private set => Character.Params.Health.CrushDepth = value; } - private List limbHealths = new List(); + private readonly List limbHealths = new List(); //non-limb-specific afflictions - private List afflictions = new List(); + private readonly List afflictions = new List(); /// /// Note: returns only the non-limb-secific afflictions. Use GetAllAfflictions or some other method for getting also the limb-specific afflictions. /// public IEnumerable Afflictions => afflictions; - private HashSet irremovableAfflictions = new HashSet(); + private readonly HashSet irremovableAfflictions = new HashSet(); private Affliction bloodlossAffliction; private Affliction oxygenLowAffliction; private Affliction pressureAffliction; @@ -132,7 +133,7 @@ namespace Barotrauma public bool IsUnconscious { - get { return Vitality <= 0.0f || Character.IsDead; } + get { return (Vitality <= 0.0f || Character.IsDead) && !Character.HasAbilityFlag(AbilityFlags.AlwaysStayConscious); } } public float PressureKillDelay { get; private set; } = 5.0f; @@ -151,6 +152,7 @@ namespace Barotrauma max += Character.Info.Job.Prefab.VitalityModifier; } max *= Character.StaticHealthMultiplier; + max *= 1f + Character.GetStatValue(StatTypes.MaximumHealthMultiplier); return max * Character.HealthMultiplier; } } @@ -167,6 +169,20 @@ namespace Barotrauma } } + public Color DefaultFaceTint = Color.TransparentBlack; + + public Color FaceTint + { + get; + private set; + } + + public Color BodyTint + { + get; + private set; + } + public float OxygenAmount { get @@ -190,7 +206,11 @@ namespace Barotrauma public float Stun { get { return stunAffliction.Strength; } - set { stunAffliction.Strength = MathHelper.Clamp(value, 0.0f, stunAffliction.Prefab.MaxStrength); } + set + { + if (Character.GodMode) { return; } + stunAffliction.Strength = MathHelper.Clamp(value, 0.0f, stunAffliction.Prefab.MaxStrength); + } } public float StunTimer { get; private set; } @@ -265,6 +285,12 @@ namespace Barotrauma private LimbHealth GetMatchingLimbHealth(Limb limb) => limb == null ? null : limbHealths[limb.HealthIndex]; private LimbHealth GetMatchingLimbHealth(Affliction affliction) => GetMatchingLimbHealth(Character.AnimController.GetLimb(affliction.Prefab.IndicatorLimb, excludeSevered: false)); + /// + /// Returns the limb afflictions and non-limbspecific afflictions that are set to be displayed on this limb. + /// + private IEnumerable GetMatchingAfflictions(LimbHealth limb) + => limb.Afflictions.Union(afflictions.Where(a => GetMatchingLimbHealth(a) == limb)); + /// /// Returns the limb afflictions and non-limbspecific afflictions that are set to be displayed on this limb. /// @@ -401,7 +427,7 @@ namespace Barotrauma return strength; } - public void ApplyAffliction(Limb targetLimb, Affliction affliction) + public void ApplyAffliction(Limb targetLimb, Affliction affliction, bool allowStacking = true) { if (!affliction.Prefab.IsBuff && Unkillable || Character.GodMode) { return; } if (affliction.Prefab.LimbSpecific) @@ -411,35 +437,51 @@ namespace Barotrauma //if a limb-specific affliction is applied to no specific limb, apply to all limbs foreach (LimbHealth limbHealth in limbHealths) { - AddLimbAffliction(limbHealth, affliction); + AddLimbAffliction(limbHealth, affliction, allowStacking: allowStacking); } } else { - AddLimbAffliction(targetLimb, affliction); + AddLimbAffliction(targetLimb, affliction, allowStacking: allowStacking); } } else { - AddAffliction(affliction); + AddAffliction(affliction, allowStacking: allowStacking); } } - public float GetResistance(string resistanceId) + public float GetResistance(AfflictionPrefab affliction) { float resistance = 0.0f; for (int i = 0; i < afflictions.Count; i++) { - if (!afflictions[i].Prefab.IsBuff) continue; - float temp = afflictions[i].GetResistance(resistanceId); - if (temp > resistance) resistance = temp; + resistance += afflictions[i].GetResistance(affliction); } + return 1 - ((1 - resistance) * Character.GetAbilityResistance(affliction)); + } - return resistance; + public float GetStatValue(StatTypes statType) + { + float value = 0f; + for (int i = 0; i < afflictions.Count; i++) + { + value += afflictions[i].GetStatValue(statType); + } + return value; + } + + public bool HasFlag(AbilityFlags flagType) + { + for (int i = 0; i < afflictions.Count; i++) + { + if (afflictions[i].HasFlag(flagType)) { return true; } + } + return false; } private readonly List matchingAfflictions = new List(); - public void ReduceAffliction(Limb targetLimb, string affliction, float amount) + public void ReduceAffliction(Limb targetLimb, string affliction, float amount, ActionType? treatmentAction = null) { matchingAfflictions.Clear(); matchingAfflictions.AddRange(afflictions); @@ -468,6 +510,14 @@ namespace Barotrauma for (int i = matchingAfflictions.Count - 1; i >= 0; i--) { var matchingAffliction = matchingAfflictions[i]; + + // this logic runs very often, so culling unnecessary object creation and talent checking with this method + if (Character.HasTalents()) + { + var afflictionReduction = new AbilityValueAffliction(reduceAmount, matchingAffliction); + Character.CheckTalents(AbilityEffectType.OnReduceAffliction, afflictionReduction); + } + if (matchingAffliction.Strength < reduceAmount) { float surplus = reduceAmount - matchingAffliction.Strength; @@ -482,6 +532,17 @@ namespace Barotrauma { matchingAffliction.Strength -= reduceAmount; amount -= reduceAmount; + if (treatmentAction != null) + { + if (treatmentAction.Value == ActionType.OnUse) + { + matchingAffliction.AppliedAsSuccessfulTreatmentTime = Timing.TotalTime; + } + else if (treatmentAction.Value == ActionType.OnFailure) + { + matchingAffliction.AppliedAsFailedTreatmentTime = Timing.TotalTime; + } + } } } CalculateVitality(); @@ -539,9 +600,9 @@ namespace Barotrauma else { // Instead of using the limbhealth count here, I think it's best to define the max vitality per limb roughly with a constant value. - // Therefore with e.g. 80 health, the max damage per limb would be 20. - // Having at least 20 damage on both legs would cause maximum limping. - float max = MaxVitality / 4; + // Therefore with e.g. 80 health, the max damage per limb would be 40. + // Having at least 40 damage on both legs would cause maximum limping. + float max = MaxVitality / 2; if (string.IsNullOrEmpty(afflictionType)) { float damage = GetAfflictionStrength("damage", limb, true); @@ -572,6 +633,22 @@ namespace Barotrauma CalculateVitality(); } + public void RemoveNegativeAfflictions() + { + // also don't remove genetic effects, even if they're negative + foreach (LimbHealth limbHealth in limbHealths) + { + limbHealth.Afflictions.RemoveAll(a => !a.Prefab.IsBuff && a.Prefab.AfflictionType != "geneticmaterialbuff" && a.Prefab.AfflictionType != "geneticmaterialdebuff"); + } + + afflictions.RemoveAll(a => !irremovableAfflictions.Contains(a) && !a.Prefab.IsBuff && a.Prefab.AfflictionType != "geneticmaterialbuff" && a.Prefab.AfflictionType != "geneticmaterialdebuff"); + foreach (Affliction affliction in irremovableAfflictions) + { + affliction.Strength = 0.0f; + } + CalculateVitality(); + } + private void AddLimbAffliction(Limb limb, Affliction newAffliction, bool allowStacking = true) { if (!newAffliction.Prefab.LimbSpecific || limb == null) { return; } @@ -593,7 +670,7 @@ namespace Barotrauma { if (newAffliction.Prefab == affliction.Prefab) { - float newStrength = newAffliction.Strength * (100.0f / MaxVitality) * (1f - GetResistance(affliction.Prefab.Identifier)); + float newStrength = newAffliction.Strength * (100.0f / MaxVitality) * (1f - GetResistance(affliction.Prefab)); if (allowStacking) { // Add the existing strength @@ -615,7 +692,7 @@ namespace Barotrauma //create a new instance of the affliction to make sure we don't use the same instance for multiple characters //or modify the affliction instance of an Attack or a StatusEffect var copyAffliction = newAffliction.Prefab.Instantiate( - Math.Min(newAffliction.Prefab.MaxStrength, newAffliction.Strength * (100.0f / MaxVitality) * (1f - GetResistance(newAffliction.Prefab.Identifier))), + Math.Min(newAffliction.Prefab.MaxStrength, newAffliction.Strength * (100.0f / MaxVitality) * (1f - GetResistance(newAffliction.Prefab))), newAffliction.Source); limbHealth.Afflictions.Add(copyAffliction); @@ -637,6 +714,7 @@ namespace Barotrauma private void AddAffliction(Affliction newAffliction, bool allowStacking = true) { if (!DoesBleed && newAffliction is AfflictionBleeding) { return; } + if (Character.Params.Health.StunImmunity && newAffliction.Prefab.AfflictionType == "stun") { return; } if (!Character.NeedsOxygen && newAffliction.Prefab == AfflictionPrefab.OxygenLow) { return; } if (newAffliction.Prefab is AfflictionPrefabHusk huskPrefab) { @@ -649,7 +727,7 @@ namespace Barotrauma { if (newAffliction.Prefab == affliction.Prefab) { - float newStrength = newAffliction.Strength * (100.0f / MaxVitality) * (1f - GetResistance(affliction.Prefab.Identifier)); + float newStrength = newAffliction.Strength * (100.0f / MaxVitality) * (1f - GetResistance(affliction.Prefab)); if (allowStacking) { // Add the existing strength @@ -671,7 +749,7 @@ namespace Barotrauma //create a new instance of the affliction to make sure we don't use the same instance for multiple characters //or modify the affliction instance of an Attack or a StatusEffect afflictions.Add(newAffliction.Prefab.Instantiate( - Math.Min(newAffliction.Prefab.MaxStrength, newAffliction.Strength * (100.0f / MaxVitality) * (1f - GetResistance(newAffliction.Prefab.Identifier))), + Math.Min(newAffliction.Prefab.MaxStrength, newAffliction.Strength * (100.0f / MaxVitality) * (1f - GetResistance(newAffliction.Prefab))), source: newAffliction.Source)); Character.HealthUpdateInterval = 0.0f; @@ -683,8 +761,6 @@ namespace Barotrauma } } - partial void UpdateProjSpecific(float deltaTime); - partial void UpdateLimbAfflictionOverlays(); public void Update(float deltaTime) @@ -693,6 +769,8 @@ namespace Barotrauma StunTimer = Stun > 0 ? StunTimer + deltaTime : 0; + if (Character.GodMode) { return; } + for (int i = 0; i < limbHealths.Count; i++) { for (int j = limbHealths[i].Afflictions.Count - 1; j >= 0; j--) @@ -720,11 +798,11 @@ namespace Barotrauma Character.StackSpeedMultiplier(affliction.GetSpeedMultiplier()); } } - + for (int i = afflictions.Count - 1; i >= 0; i--) { var affliction = afflictions[i]; - if (irremovableAfflictions.Contains(affliction)) continue; + if (irremovableAfflictions.Contains(affliction)) { continue; } if (affliction.Strength <= 0.0f) { SteamAchievementManager.OnAfflictionRemoved(affliction, Character); @@ -738,9 +816,21 @@ namespace Barotrauma affliction.DamagePerSecondTimer += deltaTime; Character.StackSpeedMultiplier(affliction.GetSpeedMultiplier()); } - - UpdateLimbAfflictionOverlays(); + Character.StackSpeedMultiplier(1f + Character.GetStatValue(StatTypes.MovementSpeed)); + + // maybe a bit of a hacky way to do this. should inquire if there is a better way. M61T + if (Character.InWater) + { + Character.StackSpeedMultiplier(1f + Character.GetStatValue(StatTypes.SwimmingSpeed)); + } + else + { + Character.StackSpeedMultiplier(1f + Character.GetStatValue(StatTypes.WalkingSpeed)); + } + + UpdateLimbAfflictionOverlays(); + UpdateSkinTint(); CalculateVitality(); if (Vitality <= MinVitality) @@ -749,6 +839,32 @@ namespace Barotrauma } } + private void UpdateSkinTint() + { + FaceTint = DefaultFaceTint; + BodyTint = Color.TransparentBlack; + + for (int i = 0; i < limbHealths.Count; i++) + { + for (int j = limbHealths[i].Afflictions.Count - 1; j >= 0; j--) + { + var affliction = limbHealths[i].Afflictions[j]; + Color faceTint = affliction.GetFaceTint(); + if (faceTint.A > FaceTint.A) { FaceTint = faceTint; } + Color bodyTint = affliction.GetBodyTint(); + if (bodyTint.A > BodyTint.A) { BodyTint = bodyTint; } + } + } + for (int i = 0; i < afflictions.Count; i++) + { + var affliction = afflictions[i]; + Color faceTint = affliction.GetFaceTint(); + if (faceTint.A > FaceTint.A) { FaceTint = faceTint; } + Color bodyTint = affliction.GetBodyTint(); + if (bodyTint.A > BodyTint.A) { BodyTint = bodyTint; } + } + } + private void UpdateOxygen(float deltaTime) { if (!Character.NeedsOxygen) { return; } @@ -761,7 +877,12 @@ namespace Barotrauma } else { - OxygenAmount = MathHelper.Clamp(OxygenAmount + deltaTime * (Character.OxygenAvailable < InsufficientOxygenThreshold ? -5.0f : 10.0f), -100.0f, 100.0f); + float decreaseSpeed = -5.0f; + float increaseSpeed = 10.0f; + float oxygenlowResistance = GetResistance(oxygenLowAffliction.Prefab); + decreaseSpeed *= (1f - oxygenlowResistance); + increaseSpeed *= (1f + oxygenlowResistance); + OxygenAmount = MathHelper.Clamp(OxygenAmount + deltaTime * (Character.OxygenAvailable < InsufficientOxygenThreshold ? decreaseSpeed : increaseSpeed), -100.0f, 100.0f); } UpdateOxygenProjSpecific(prevOxygen, deltaTime); @@ -782,8 +903,6 @@ namespace Barotrauma Vitality = MaxVitality; if (Unkillable || Character.GodMode) { return; } - float damageResistanceMultiplier = 1f - GetResistance("damage"); - foreach (LimbHealth limbHealth in limbHealths) { foreach (Affliction affliction in limbHealth.Afflictions) @@ -799,7 +918,6 @@ namespace Barotrauma { vitalityDecrease *= limbHealth.VitalityTypeMultipliers[type]; } - vitalityDecrease *= damageResistanceMultiplier; Vitality -= vitalityDecrease; affliction.CalculateDamagePerSecond(vitalityDecrease); } @@ -808,7 +926,6 @@ namespace Barotrauma foreach (Affliction affliction in afflictions) { float vitalityDecrease = affliction.GetVitalityDecrease(this); - vitalityDecrease *= damageResistanceMultiplier; Vitality -= vitalityDecrease; affliction.CalculateDamagePerSecond(vitalityDecrease); } @@ -825,8 +942,9 @@ namespace Barotrauma { if (Unkillable || Character.GodMode) { return; } - var causeOfDeath = GetCauseOfDeath(); - Character.Kill(causeOfDeath.First, causeOfDeath.Second); + var (type, affliction) = GetCauseOfDeath(); + UpdateSkinTint(); + Character.Kill(type, affliction); #if CLIENT DisplayVitalityDelay = 0.0f; DisplayedVitality = Vitality; @@ -859,7 +977,7 @@ namespace Barotrauma } } - public Pair GetCauseOfDeath() + public (CauseOfDeathType type, Affliction affliction) GetCauseOfDeath() { List currentAfflictions = GetAllAfflictions(true); @@ -880,7 +998,7 @@ namespace Barotrauma causeOfDeath = Character.AnimController.InWater ? CauseOfDeathType.Drowning : CauseOfDeathType.Suffocation; } - return new Pair(causeOfDeath, strongestAffliction); + return (causeOfDeath, strongestAffliction); } // TODO: this method is called a lot (every half second) -> optimize, don't create new class instances and lists every time! @@ -926,15 +1044,16 @@ namespace Barotrauma /// A dictionary where the key is the identifier of the item and the value the suitability /// If true, the suitability values are normalized between 0 and 1. If not, they're arbitrary values defined in the medical item XML, where negative values are unsuitable, and positive ones suitable. /// Amount of randomization to apply to the values (0 = the values are accurate, 1 = the values are completely random) - public void GetSuitableTreatments(Dictionary treatmentSuitability, bool normalize, float randomization = 0.0f) + public void GetSuitableTreatments(Dictionary treatmentSuitability, bool normalize, Limb limb = null, bool ignoreHiddenAfflictions = false, float randomization = 0.0f) { //key = item identifier //float = suitability treatmentSuitability.Clear(); float minSuitability = -10, maxSuitability = 10; - foreach (Affliction affliction in GetAllAfflictions()) + foreach (Affliction affliction in getAfflictions(limb)) { - if (affliction.Strength < affliction.Prefab.TreatmentThreshold) { continue; } + if (affliction.Strength <= affliction.Prefab.TreatmentThreshold) { continue; } + if (ignoreHiddenAfflictions && affliction.Strength < affliction.Prefab.ShowIconThreshold) { continue; } foreach (KeyValuePair treatment in affliction.Prefab.TreatmentSuitability) { if (!treatmentSuitability.ContainsKey(treatment.Key)) @@ -965,10 +1084,22 @@ namespace Barotrauma treatmentSuitability[treatment] += Rand.Range(-100.0f, 100.0f) * randomization; } } + + IEnumerable getAfflictions(Limb limb) + { + if (limb == null) + { + return GetAllAfflictions(); + } + else + { + return GetMatchingAfflictions(GetMatchingLimbHealth(limb)); + } + } } private readonly List activeAfflictions = new List(); - private readonly List> limbAfflictions = new List>(); + private readonly List<(LimbHealth limbHealth, Affliction affliction)> limbAfflictions = new List<(LimbHealth limbHealth, Affliction affliction)>(); public void ServerWrite(IWriteMessage msg) { activeAfflictions.Clear(); @@ -999,22 +1130,22 @@ namespace Barotrauma foreach (Affliction limbAffliction in limbHealth.Afflictions) { if (limbAffliction.Strength <= 0.0f || limbAffliction.Strength < limbAffliction.Prefab.ActivationThreshold) continue; - limbAfflictions.Add(new Pair(limbHealth, limbAffliction)); + limbAfflictions.Add((limbHealth, limbAffliction)); } } msg.Write((byte)limbAfflictions.Count); - foreach (var limbAffliction in limbAfflictions) + foreach (var (limbHealth, affliction) in limbAfflictions) { - msg.WriteRangedInteger(limbHealths.IndexOf(limbAffliction.First), 0, limbHealths.Count - 1); - msg.Write(limbAffliction.Second.Prefab.UIntIdentifier); + msg.WriteRangedInteger(limbHealths.IndexOf(limbHealth), 0, limbHealths.Count - 1); + msg.Write(affliction.Prefab.UIntIdentifier); msg.WriteRangedSingle( - MathHelper.Clamp(limbAffliction.Second.Strength, 0.0f, limbAffliction.Second.Prefab.MaxStrength), - 0.0f, limbAffliction.Second.Prefab.MaxStrength, 8); - msg.Write((byte)limbAffliction.Second.Prefab.PeriodicEffects.Count()); - foreach (AfflictionPrefab.PeriodicEffect periodicEffect in limbAffliction.Second.Prefab.PeriodicEffects) + MathHelper.Clamp(affliction.Strength, 0.0f, affliction.Prefab.MaxStrength), + 0.0f, affliction.Prefab.MaxStrength, 8); + msg.Write((byte)affliction.Prefab.PeriodicEffects.Count()); + foreach (AfflictionPrefab.PeriodicEffect periodicEffect in affliction.Prefab.PeriodicEffects) { - msg.WriteRangedSingle(limbAffliction.Second.PeriodicEffectTimers[periodicEffect], periodicEffect.MinInterval, periodicEffect.MaxInterval, 8); + msg.WriteRangedSingle(affliction.PeriodicEffectTimers[periodicEffect], periodicEffect.MinInterval, periodicEffect.MaxInterval, 8); } } } @@ -1030,7 +1161,7 @@ namespace Barotrauma /// Automatically filters out buffs. /// public static IEnumerable SortAfflictionsBySeverity(IEnumerable afflictions, bool excludeBuffs = true) => - afflictions.Where(a => !excludeBuffs || !a.Prefab.IsBuff).OrderByDescending(a => a.DamagePerSecond).ThenByDescending(a => a.Strength); + afflictions.Where(a => !excludeBuffs || !a.Prefab.IsBuff).OrderByDescending(a => a.DamagePerSecond).ThenByDescending(a => a.Strength / a.Prefab.MaxStrength); public void Save(XElement healthElement) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/HumanPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/HumanPrefab.cs index f87c976ed..e1dfcebc8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/HumanPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/HumanPrefab.cs @@ -217,10 +217,6 @@ namespace Barotrauma if (item.Prefab.Identifier == "idcard" || item.Prefab.Identifier == "idcardwreck") { item.AddTag("name:" + character.Name); - if (Level.Loaded != null) - { - item.ReplaceTag("wreck_id", Level.Loaded.GetWreckIDTag("wreck_id", submarine)); - } var job = character.Info?.Job; if (job != null) { @@ -229,6 +225,10 @@ namespace Barotrauma IdCard idCardComponent = item.GetComponent(); idCardComponent?.Initialize(character.Info); + if (submarine != null && (submarine.Info.IsWreck || submarine.Info.IsOutpost)) + { + idCardComponent.SubmarineSpecificID = submarine.SubmarineSpecificIDTag; + } var idCardTags = itemElement.GetAttributeStringArray("tags", new string[0]); foreach (string tag in idCardTags) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/Job.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/Job.cs index d8cd67162..2841d305e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/Job.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/Job.cs @@ -89,11 +89,11 @@ namespace Barotrauma return (skill == null) ? 0.0f : skill.Level; } - public void IncreaseSkillLevel(string skillIdentifier, float increase) + public void IncreaseSkillLevel(string skillIdentifier, float increase, bool increasePastMax) { if (skills.TryGetValue(skillIdentifier, out Skill skill)) { - skill.Level += increase; + skill.IncreaseSkill(increase, increasePastMax); } else { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/JobPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/JobPrefab.cs index c918b7363..5bb3e5286 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/JobPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/JobPrefab.cs @@ -68,9 +68,20 @@ namespace Barotrauma } } + public class PreviewItem + { + public readonly string ItemIdentifier; + public readonly bool ShowPreview; + + public PreviewItem(string itemIdentifier, bool showPreview) + { + ItemIdentifier = itemIdentifier; + ShowPreview = showPreview; + } + } + public readonly Dictionary ItemSets = new Dictionary(); - public readonly Dictionary> ItemIdentifiers = new Dictionary>(); - public readonly Dictionary> ShowItemPreview = new Dictionary>(); + public readonly Dictionary> PreviewItems = new Dictionary>(); public readonly List Skills = new List(); public readonly List AutonomousObjectives = new List(); public readonly List AppropriateOrders = new List(); @@ -220,8 +231,7 @@ namespace Barotrauma { case "itemset": ItemSets.Add(variant, subElement); - ItemIdentifiers[variant] = new List(); - ShowItemPreview[variant] = new Dictionary(); + PreviewItems[variant] = new List(); loadItemIdentifiers(subElement, variant); variant++; break; @@ -264,8 +274,7 @@ namespace Barotrauma } else { - ItemIdentifiers[variant].Add(itemIdentifier); - ShowItemPreview[variant][itemIdentifier] = itemElement.GetAttributeBool("showpreview", true); + PreviewItems[variant].Add(new PreviewItem(itemIdentifier, itemElement.GetAttributeBool("showpreview", true))); } loadItemIdentifiers(itemElement, variant); } @@ -275,7 +284,8 @@ namespace Barotrauma Skills.Sort((x,y) => y.LevelRange.X.CompareTo(x.LevelRange.X)); - ClothingElement = element.GetChildElement("PortraitClothing"); + // Disabled on purpose, TODO: remove all references? + //ClothingElement = element.GetChildElement("PortraitClothing"); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/Skill.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/Skill.cs index f439cefff..f3a502403 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/Skill.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/Skill.cs @@ -7,11 +7,18 @@ namespace Barotrauma private float level; public string Identifier { get; } + + public const float MaximumSkill = 100.0f; public float Level { get { return level; } - set { level = MathHelper.Clamp(value, 0.0f, 100.0f); } + set { level = value; } + } + + public void IncreaseSkill(float value, bool increasePastMax) + { + level = MathHelper.Clamp(level + value, 0.0f, increasePastMax ? float.MaxValue : MaximumSkill); } private Sprite icon; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Limb.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Limb.cs index 92087ad79..9731b08d5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Limb.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Limb.cs @@ -11,6 +11,7 @@ using System.Xml.Linq; using Barotrauma.Networking; using LimbParams = Barotrauma.RagdollParams.LimbParams; using JointParams = Barotrauma.RagdollParams.JointParams; +using Barotrauma.Abilities; namespace Barotrauma { @@ -217,9 +218,9 @@ namespace Barotrauma public Vector2 StepOffset => ConvertUnits.ToSimUnits(Params.StepOffset) * ragdoll.RagdollParams.JointScale; - public bool inWater; + public bool InWater { get; set; } - private readonly FixedMouseJoint pullJoint; + private FixedMouseJoint pullJoint; public readonly LimbType type; @@ -534,14 +535,19 @@ namespace Barotrauma public string Name => Params.Name; - // Exposed for status effects + // These properties are exposed for status effects public bool IsDead => character.IsDead; + public float Health => character.Health; + public float HealthPercentage => character.HealthPercentage; + public AIState AIState => character.AIController is EnemyAIController enemyAI ? enemyAI.State : AIState.Idle; public bool CanBeSeveredAlive { get { if (character.IsHumanoid) { return false; } + // TODO: We might need this or solve the cases where a limb is severed while holding on to an item + //if (character.Params.CanInteract) { return false; } if (this == character.AnimController.MainLimb) { return false; } if (character.AnimController.CanWalk) { @@ -683,7 +689,7 @@ namespace Barotrauma private readonly List appliedDamageModifiers = new List(); private readonly List tempModifiers = new List(); private readonly List afflictionsCopy = new List(); - public AttackResult AddDamage(Vector2 simPosition, IEnumerable afflictions, bool playSound, float damageMultiplier = 1, float penetration = 0f) + public AttackResult AddDamage(Vector2 simPosition, IEnumerable afflictions, bool playSound, float damageMultiplier = 1, float penetration = 0f, Character attacker = null) { appliedDamageModifiers.Clear(); afflictionsCopy.Clear(); @@ -741,7 +747,11 @@ namespace Barotrauma { newAffliction.SetStrength(affliction.NonClampedStrength); } - + if (attacker != null) + { + var abilityAffliction = new AbilityAfflictionCharacter(newAffliction, character); + attacker.CheckTalents(AbilityEffectType.OnAddDamageAffliction, abilityAffliction); + } if (applyAffliction) { afflictionsCopy.Add(newAffliction); @@ -749,6 +759,10 @@ namespace Barotrauma appliedDamageModifiers.AddRange(tempModifiers); } var result = new AttackResult(afflictionsCopy, this, appliedDamageModifiers); + if (result.Afflictions.None()) + { + playSound = false; + } AddDamageProjSpecific(playSound, result); float bleedingDamage = 0; @@ -797,7 +811,7 @@ namespace Barotrauma { UpdateProjSpecific(deltaTime); - if (inWater) + if (InWater) { body.ApplyWaterForces(); } @@ -840,20 +854,25 @@ namespace Barotrauma attack?.UpdateCoolDown(deltaTime); } + private bool temporarilyDisabled; private float reEnableTimer = -1; - public void HideAndDisable(float duration = 0) + public void HideAndDisable(float duration = 0, bool ignoreCollisions = true) { + if (Hidden || Disabled) { return; } + if (ignoreCollisions && IgnoreCollisions) { return; } + temporarilyDisabled = true; Hidden = true; Disabled = true; - IgnoreCollisions = true; + IgnoreCollisions = ignoreCollisions; if (duration > 0) { reEnableTimer = duration; } } - private void ReEnable() + public void ReEnable() { + if (!temporarilyDisabled) { return; } Hidden = false; Disabled = false; IgnoreCollisions = false; @@ -868,7 +887,7 @@ namespace Barotrauma /// public bool UpdateAttack(float deltaTime, Vector2 attackSimPos, IDamageable damageTarget, out AttackResult attackResult, float distance = -1, Limb targetLimb = null) { - attackResult = default(AttackResult); + attackResult = default; Vector2 simPos = ragdoll.SimplePhysicsEnabled ? character.SimPosition : SimPosition; float dist = distance > -1 ? distance : ConvertUnits.ToDisplayUnits(Vector2.Distance(simPos, attackSimPos)); bool wasRunning = attack.IsRunning; @@ -971,7 +990,7 @@ namespace Barotrauma wasHit = damageTarget != null; } - if (wasHit) + if (wasHit || attack.HitDetectionType == HitDetection.None) { if (character == Character.Controlled || GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient) { @@ -1132,10 +1151,7 @@ namespace Barotrauma if (statusEffect.type != actionType) { continue; } if (statusEffect.type == ActionType.OnDamaged) { - if (statusEffect.AllowedAfflictions != null && (character.LastDamage.Afflictions == null || character.LastDamage.Afflictions.None(a => statusEffect.AllowedAfflictions.Contains(a.Prefab.AfflictionType) || statusEffect.AllowedAfflictions.Contains(a.Prefab.Identifier)))) - { - continue; - } + if (!statusEffect.HasRequiredAfflictions(character.LastDamage)) { continue; } if (statusEffect.OnlyPlayerTriggered) { if (character.LastAttacker == null || !character.LastAttacker.IsPlayer) @@ -1263,6 +1279,14 @@ namespace Barotrauma { body?.Remove(); body = null; + if (pullJoint != null) + { + if (GameMain.World.JointList.Contains(pullJoint)) + { + GameMain.World.Remove(pullJoint); + } + pullJoint = null; + } Release(); RemoveProjSpecific(); Removed = true; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Animation/AnimationParams.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Animation/AnimationParams.cs index 649d9afcd..ff46272dc 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Animation/AnimationParams.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Animation/AnimationParams.cs @@ -11,11 +11,12 @@ namespace Barotrauma { public enum AnimationType { - NotDefined, - Walk, - Run, - SwimSlow, - SwimFast + NotDefined = 0, + Walk = 1, + Run = 2, + Crouch = 3, + SwimSlow = 4, + SwimFast = 5 } abstract class GroundedMovementParams : AnimationParams @@ -56,12 +57,15 @@ namespace Barotrauma { [Serialize(25.0f, true, description: "Turning speed (or rather a force applied on the main collider to make it turn). Note that you can set a limb-specific steering forces too (additional)."), Editable(MinValueFloat = 0, MaxValueFloat = 1000, ValueStep = 1)] public float SteerTorque { get; set; } + + [Serialize(25.0f, true, description: "How much torque is used to move the legs."), Editable(MinValueFloat = 0, MaxValueFloat = 1000, ValueStep = 1)] + public float LegTorque { get; set; } } abstract class AnimationParams : EditableParams, IMemorizable { public string SpeciesName { get; private set; } - public bool IsGroundedAnimation => AnimationType == AnimationType.Walk || AnimationType == AnimationType.Run; + public bool IsGroundedAnimation => AnimationType == AnimationType.Walk || AnimationType == AnimationType.Run || AnimationType == AnimationType.Crouch; public bool IsSwimAnimation => AnimationType == AnimationType.SwimSlow || AnimationType == AnimationType.SwimFast; protected static Dictionary> allAnimations = new Dictionary>(); @@ -110,11 +114,27 @@ namespace Barotrauma } } } + public float TorsoAngleInRadians { get; private set; } = float.NaN; + [Serialize(50.0f, true, description: "How much torque is used to rotate the head to the correct orientation."), Editable(MinValueFloat = 0, MaxValueFloat = 1000, ValueStep = 1)] + public float HeadTorque { get; set; } + + [Serialize(50.0f, true, description: "How much torque is used to rotate the torso to the correct orientation."), Editable(MinValueFloat = 0, MaxValueFloat = 1000, ValueStep = 1)] + public float TorsoTorque { get; set; } + + [Serialize(25.0f, true, description: "How much torque is used to rotate the feet to the correct orientation."), Editable(MinValueFloat = 0, MaxValueFloat = 1000, ValueStep = 1)] + public float FootTorque { get; set; } + [Serialize(AnimationType.NotDefined, true), Editable] public virtual AnimationType AnimationType { get; protected set; } + [Serialize(1f, true, description: "How much force is used to rotate the arms to the IK position."), Editable(MinValueFloat = 0, MaxValueFloat = 10, DecimalCount = 2)] + public float ArmIKStrength { get; set; } + + [Serialize(1f, true, description: "How much force is used to rotate the hands to the IK position."), Editable(MinValueFloat = 0, MaxValueFloat = 10, DecimalCount = 2)] + public float HandIKStrength { get; set; } + public static string GetDefaultFileName(string speciesName, AnimationType animType) => $"{speciesName.CapitaliseFirstInvariant()}{animType}"; public static string GetDefaultFile(string speciesName, AnimationType animType) => Path.Combine(GetFolder(speciesName), $"{GetDefaultFileName(speciesName, animType)}.xml"); @@ -402,6 +422,8 @@ namespace Barotrauma return typeof(HumanWalkParams); case AnimationType.Run: return typeof(HumanRunParams); + case AnimationType.Crouch: + return typeof(HumanCrouchParams); case AnimationType.SwimSlow: return typeof(HumanSwimSlowParams); case AnimationType.SwimFast: diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Animation/FishAnimations.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Animation/FishAnimations.cs index 07c3bf980..c2257379b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Animation/FishAnimations.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Animation/FishAnimations.cs @@ -87,18 +87,9 @@ namespace Barotrauma [Serialize(8.0f, true, description: "How much force is used to move the feet to the correct position."), Editable(MinValueFloat = 0, MaxValueFloat = 100)] public float FootMoveForce { get; set; } - [Serialize(50.0f, true, description: "How much torque is used to rotate the head to the correct orientation."), Editable(MinValueFloat = 0, MaxValueFloat = 1000, ValueStep = 1)] - public float HeadTorque { get; set; } - - [Serialize(50.0f, true, description: "How much torque is used to rotate the torso to the correct orientation."), Editable(MinValueFloat = 0, MaxValueFloat = 1000, ValueStep = 1)] - public float TorsoTorque { get; set; } - [Serialize(50.0f, true, description: "How much torque is used to rotate the tail to the correct orientation."), Editable(MinValueFloat = 0, MaxValueFloat = 1000, ValueStep = 1)] public float TailTorque { get; set; } - [Serialize(25.0f, true, description: "How much torque is used to rotate the feet to the correct orientation."), Editable(MinValueFloat = 0, MaxValueFloat = 1000, ValueStep = 1)] - public float FootTorque { get; set; } - [Serialize(0.0f, true, description: "Optional torque that's constantly applied to legs."), Editable(MinValueFloat = 0, MaxValueFloat = 1000)] public float LegTorque { get; set; } @@ -173,20 +164,12 @@ namespace Barotrauma [Editable, Serialize(true, true, description: "Should the character face towards the direction it's heading.")] public bool RotateTowardsMovement { get; set; } - [Serialize(25.0f, true, description: "How much torque is used to rotate the torso to the correct orientation."), Editable(MinValueFloat = 0, MaxValueFloat = 2000, ValueStep = 1)] - public float TorsoTorque { get; set; } - - [Serialize(25.0f, true, description: "How much torque is used to rotate the head to the correct orientation."), Editable(MinValueFloat = 0, MaxValueFloat = 2000, ValueStep = 1)] - public float HeadTorque { get; set; } - [Serialize(50.0f, true, description: "How much torque is used to rotate the tail to the correct orientation."), Editable(MinValueFloat = 0, MaxValueFloat = 2000, ValueStep = 1)] public float TailTorque { get; set; } [Serialize(1f, true, description: "Multiplier applied based on the angle difference between the tail and the main limb. Increasing the value prevents snake-like characters from getting tangled on themselves. Default = 1 (no boost)"), Editable(MinValueFloat = 1, MaxValueFloat = 100)] public float TailTorqueMultiplier { get; set; } - [Serialize(25.0f, true, description: "How much torque is used to rotate the feet to the correct orientation."), Editable(MinValueFloat = 0, MaxValueFloat = 1000, ValueStep = 1)] - public float FootTorque { get; set; } [Serialize(null, true), Editable] public string FootAngles @@ -224,10 +207,7 @@ namespace Barotrauma Dictionary FootAnglesInRadians { get; set; } float TailAngle { get; set; } float TailAngleInRadians { get; } - float HeadTorque { get; set; } - float TorsoTorque { get; set; } float TailTorque { get; set; } - float FootTorque { get; set; } bool Flip { get; set; } float FlipCooldown { get; set; } float FlipDelay { get; set; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Animation/HumanoidAnimations.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Animation/HumanoidAnimations.cs index 639b7f46e..4192bd331 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Animation/HumanoidAnimations.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Animation/HumanoidAnimations.cs @@ -24,6 +24,17 @@ namespace Barotrauma public override void StoreSnapshot() => StoreSnapshot(); } + class HumanCrouchParams : HumanGroundedParams + { + public static HumanCrouchParams GetDefaultAnimParams(Character character) => GetDefaultAnimParams(character, AnimationType.Crouch); + public static HumanCrouchParams GetAnimParams(Character character, string fileName = null) + { + return GetAnimParams(character.SpeciesName, AnimationType.Crouch, fileName); + } + + public override void StoreSnapshot() => StoreSnapshot(); + } + class HumanSwimFastParams: HumanSwimParams { public static HumanSwimFastParams GetDefaultAnimParams(Character character) => GetDefaultAnimParams(character, AnimationType.SwimFast); @@ -58,9 +69,6 @@ namespace Barotrauma [Serialize("0.5, 0.1", true), Editable(DecimalCount = 2)] public Vector2 HandMoveAmount { get; set; } - [Serialize(0.5f, true), Editable(MinValueFloat = 0, MaxValueFloat = 10, DecimalCount = 2)] - public float HandMoveStrength { get; set; } - [Serialize(5.0f, true), Editable] public float HandCycleSpeed { get; set; } @@ -81,36 +89,17 @@ namespace Barotrauma } public float FootAngleInRadians { get; private set; } - [Serialize(25.0f, true, description: "How much torque is used to rotate the feet to the correct orientation."), Editable(MinValueFloat = 0, MaxValueFloat = 100)] - public float FootRotateStrength { get; set; } + [Serialize(1f, true, description: "How much force is used to move the arms."), Editable(MinValueFloat = 0, MaxValueFloat = 20, DecimalCount = 2)] + public float ArmMoveStrength { get; set; } + + [Serialize(1f, true, description: "How much force is used to move the hands."), Editable(MinValueFloat = 0, MaxValueFloat = 10, DecimalCount = 2)] + public float HandMoveStrength { get; set; } } abstract class HumanGroundedParams : GroundedMovementParams, IHumanAnimation { [Serialize(0.3f, true, description: "How much force is used to force the character upright."), Editable(MinValueFloat = 0, MaxValueFloat = 1, DecimalCount = 2)] public float GetUpForce { get; set; } - - // -- TODO: use a separate clip for crawling -> replace these when implemented. - - [Serialize(0.65f, true, description: "Height of the torso when crouching."), Editable(MinValueFloat = 0, MaxValueFloat = 5, DecimalCount = 2)] - public float CrouchingTorsoPos { get; set; } - - [Serialize(0.65f, true, description: "Height of the head when crouching."), Editable(MinValueFloat = 0, MaxValueFloat = 5, DecimalCount = 2)] - public float CrouchingHeadPos { get; set; } - - /// - /// In degrees - /// - [Serialize(-10f, true, description: "Angle of the torso when crouching."), Editable(MinValueFloat = -360, MaxValueFloat = 360)] - public float CrouchingTorsoAngle { get; set; } - - /// - /// In degrees - /// - [Serialize(-10f, true, description: "Angle of the head when crouching."), Editable(MinValueFloat = -360, MaxValueFloat = 360)] - public float CrouchingHeadAngle { get; set; } - - // -- [Serialize(0.25f, true, description: "How much the character's head leans forwards when moving."), Editable(DecimalCount = 2)] public float HeadLeanAmount { get; set; } @@ -121,6 +110,9 @@ namespace Barotrauma [Serialize(15.0f, true, description: "How much force is used to move the feet to the correct position."), Editable(MinValueFloat = 0, MaxValueFloat = 100)] public float FootMoveStrength { get; set; } + [Serialize(0f, true, description: "How much the horizontal difference of waist and the foot positions has an effect to lifting the foot."), Editable(DecimalCount = 2, ValueStep = 0.1f, MinValueFloat = 0f, MaxValueFloat = 1f)] + public float FootLiftHorizontalFactor { get; set; } + /// /// In degrees. /// @@ -135,15 +127,9 @@ namespace Barotrauma } public float FootAngleInRadians { get; private set; } - [Serialize(20.0f, true, description: "How much torque is used to rotate the feet to the correct orientation."), Editable(MinValueFloat = 0, MaxValueFloat = 100)] - public float FootRotateStrength { get; set; } - [Serialize("0.0, 0.0", true, description: "Added to the calculated foot positions, e.g. a value of {-1.0, 0.0f} would make the character \"drag\" their feet one unit behind them."), Editable(DecimalCount = 2)] public Vector2 FootMoveOffset { get; set; } - [Serialize("0.0, 0.0", true, description: "Added to the calculated foot positions, e.g. a value of {-1.0, 0.0f} would make the character \"drag\" their feet one unit behind them."), Editable(DecimalCount = 2)] - public Vector2 CrouchingFootMoveOffset { get; set; } - [Serialize(10.0f, true, description: "How much torque is used to bend the characters legs when taking a step."), Editable(MinValueFloat = 0, MaxValueFloat = 100)] public float LegBendTorque { get; set; } @@ -153,17 +139,23 @@ namespace Barotrauma [Serialize("-0.15, 0.0", true, description: "Added to the calculated hand positions, e.g. a value of {-1.0, 0.0f} would make the character \"drag\" their hands one unit behind them."), Editable(DecimalCount = 2)] public Vector2 HandMoveOffset { get; set; } - [Serialize(0.7f, true, description: "How much force is used to move the hands."), Editable(MinValueFloat = 0, MaxValueFloat = 2, DecimalCount = 2)] - public float HandMoveStrength { get; set; } - [Serialize(-1.0f, true, description: "The position of the hands is clamped below this (relative to the position of the character's torso)."), Editable(DecimalCount = 2)] public float HandClampY { get; set; } + + [Serialize(1f, true, description: "How much force is used to move the arms."), Editable(MinValueFloat = 0, MaxValueFloat = 10, DecimalCount = 2)] + public float ArmMoveStrength { get; set; } + + [Serialize(1f, true, description: "How much force is used to move the hands."), Editable(MinValueFloat = 0, MaxValueFloat = 10, DecimalCount = 2)] + public float HandMoveStrength { get; set; } } public interface IHumanAnimation { float FootAngle { get; set; } float FootAngleInRadians { get; } - float FootRotateStrength { get; set; } + + float ArmMoveStrength { get; set; } + + float HandMoveStrength { get; set; } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/CharacterParams.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/CharacterParams.cs index 40e20c196..39016b7d6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/CharacterParams.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/CharacterParams.cs @@ -34,6 +34,9 @@ namespace Barotrauma [Serialize(false, true), Editable(ReadOnly = true)] public bool HasInfo { get; private set; } + [Serialize(false, true, description: "Can the creature interact with items?"), Editable] + public bool CanInteract { get; private set; } + [Serialize(false, true), Editable] public bool Husk { get; private set; } @@ -70,15 +73,24 @@ namespace Barotrauma [Serialize(1f, true), Editable] public float BleedParticleMultiplier { get; private set; } + [Serialize(true, true, description: "Can the creature eat bodies? Used by player controlled creatures to allow them to eat. Currently applicable only to non-humanoids. To allow an AI controller to eat, just add an ai target with the state \"eat\""), Editable] + public bool CanEat { get; set; } + [Serialize(10f, true, description: "How effectively/easily the character eats other characters. Affects the forces, the amount of particles, and the time required before the target is eaten away"), Editable(MinValueFloat = 1, MaxValueFloat = 1000, ValueStep = 1)] public float EatingSpeed { get; set; } + [Serialize(true, true), Editable] + public bool UsePathFinding { get; set; } + [Serialize(1f, true, "Decreases the intensive path finding call frequency. Set to a lower value for insignificant creatures to improve performance."), Editable(minValue: 0f, maxValue: 1f)] public float PathFinderPriority { get; set; } [Serialize(false, true), Editable] public bool HideInSonar { get; set; } + [Serialize(false, true), Editable] + public bool HideInThermalGoggles { get; set; } + [Serialize(0f, true), Editable] public float SonarDisruption { get; set; } @@ -88,6 +100,9 @@ namespace Barotrauma [Serialize(25000f, true, "If the character is farther than this (in pixels) from the sub and the players, it will be disabled. The halved value is used for triggering simple physics where the ragdoll is disabled and only the main collider is updated."), Editable(MinValueFloat = 10000f, MaxValueFloat = 100000f)] public float DisableDistance { get; set; } + [Serialize(10f, true, "How frequent the recurring idle and attack sounds are?"), Editable(MinValueFloat = 1f, MaxValueFloat = 100f)] + public float SoundInterval { get; set; } + public readonly string File; public XDocument VariantFile { get; private set; } @@ -448,6 +463,9 @@ namespace Barotrauma [Serialize(0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 10, DecimalCount = 2)] public float HealthRegenerationWhenEating { get; private set; } + [Serialize(false, true), Editable] + public bool StunImmunity { get; set; } + // TODO: limbhealths, sprite? public HealthParams(XElement element, CharacterParams character) : base(element, character) { } @@ -547,15 +565,24 @@ namespace Barotrauma [Serialize(false, true, description: "If enabled, the character chooses randomly from the available attacks. The priority is used as a weight for weighted random."), Editable] public bool RandomAttack { get; private set; } - [Serialize(false, true, description:"Does the creature know how to open doors (still requires a proper ID card). Only applies on humanoids. Humans can always open doors (They don't use this AI definition)."), Editable] + [Serialize(false, true, description:"Does the creature know how to open doors (still requires a proper ID card). Humans can always open doors (They don't use this AI definition)."), Editable] public bool CanOpenDoors { get; private set; } + [Serialize(false, true, description: "Does the creature close the doors behind it. Humans don't use this AI definition."), Editable] + public bool KeepDoorsClosed { get; private set; } + [Serialize(true, true, "Is the creature allowed to navigate from and into the depths of the abyss? When enabled, the creatures will try to avoid the depths."), Editable] public bool AvoidAbyss { get; set; } [Serialize(false, true, "Does the creature try to keep in the abyss? Has effect only when AvoidAbyss is false."), Editable] public bool StayInAbyss { get; set; } + [Serialize(false, true, "Does the creature patrol the flooded hulls while idling inside a friendly submarine?"), Editable] + public bool PatrolFlooded { get; set; } + + [Serialize(false, true, "Does the creature patrol the dry hulls while idling inside a friendly submarine?"), Editable] + public bool PatrolDry { get; set; } + [Serialize(0f, true, description: ""), Editable] public float StartAggression { get; private set; } @@ -676,8 +703,17 @@ namespace Barotrauma [Serialize(false, true), Editable] public bool IgnoreIncapacitated { get; set; } - [Serialize(0f, true, description: "How much damage the protected target should take from an attacker before the creature starts defending it."), Editable] - public float DamageThreshold { get; private set; } + [Serialize(0f, true, description: "A generic threshold. For example, how much damage the protected target should take from an attacker before the creature starts defending it."), Editable] + public float Threshold { get; private set; } + + [Serialize(-1f, true, description: "A generic min threshold. Not used if set to negative."), Editable] + public float ThresholdMin { get; private set; } + + [Serialize(-1f, true, description: "A generic max threshold. Not used if set to negative."), Editable] + public float ThresholdMax { get; private set; } + + [Serialize("0.0, 0.0", true), Editable] + public Vector2 Offset { get; private set; } [Serialize(AttackPattern.Straight, true), Editable] public AttackPattern AttackPattern { get; set; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Ragdoll/RagdollParams.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Ragdoll/RagdollParams.cs index 9c41dddb0..ff4f961f9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Ragdoll/RagdollParams.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Ragdoll/RagdollParams.cs @@ -34,7 +34,10 @@ namespace Barotrauma [Serialize("", true, description: "Default path for the limb sprite textures. Used only if the limb specific path for the limb is not defined"), Editable] public string Texture { get; set; } - [Serialize(0.0f, true, description: "The orientation of the sprites as drawn on the sprite sheet. Can be overridden by setting a value for Limb's 'Sprite Orientation'. Used mainly for animations and widgets."), Editable(-360, 360)] + [Serialize("1.0,1.0,1.0,1.0", true), Editable()] + public Color Color { get; set; } + + [Serialize(0.0f, true, description: "The orientation of the sprites as drawn on the sprite sheet. Can be overridden by setting a value for Limb's 'Sprite Orientation'."), Editable(-360, 360)] public float SpritesheetOrientation { get; set; } public bool IsSpritesheetOrientationHorizontal @@ -556,7 +559,7 @@ namespace Barotrauma } } - public override string GenerateName() => $"Limb {ID}"; + public override string GenerateName() => Type != LimbType.None ? $"{Type} ({ID})" : $"Limb {ID}"; public SpriteParams GetSprite() => deformSpriteParams ?? normalSpriteParams; @@ -569,12 +572,14 @@ namespace Barotrauma /// /// The orientation of the sprite as drawn on the sprite sheet (in radians). /// - public float GetSpriteOrientation() => MathHelper.ToRadians(float.IsNaN(SpriteOrientation) ? Ragdoll.SpritesheetOrientation : SpriteOrientation); + public float GetSpriteOrientation() => MathHelper.ToRadians(GetSpriteOrientationInDegrees()); + + public float GetSpriteOrientationInDegrees() => float.IsNaN(SpriteOrientation) ? Ragdoll.SpritesheetOrientation : SpriteOrientation; [Serialize("", true), Editable] public string Notes { get; set; } - [Serialize(1f, true), Editable] + [Serialize(1f, true), Editable(DecimalCount = 2)] public float Scale { get; set; } [Serialize(true, true, description: "Does the limb flip when the character flips?"), Editable()] @@ -589,9 +594,12 @@ namespace Barotrauma [Serialize(false, true, description: "Disable drawing for this limb."), Editable()] public bool Hide { get; set; } - [Serialize(float.NaN, true, description: "The orientation of the sprite as drawn on the sprite sheet. Overrides the value defined in the Ragdoll settings. Used mainly for animations and widgets."), Editable(-360, 360, ValueStep = 90, DecimalCount = 0)] + [Serialize(float.NaN, true, description: "The orientation of the sprite as drawn on the sprite sheet. Overrides the value defined in the Ragdoll settings."), Editable(-360, 360, ValueStep = 90, DecimalCount = 0)] public float SpriteOrientation { get; set; } + [Serialize(LimbType.None, true, description: "If set, the limb sprite will use the same sprite depth as the specified limb. Generally only useful for limbs that get added on the ragdoll on the fly (e.g. extra limbs added via gene splicing).")] + public LimbType InheritLimbDepth { get; set; } + [Serialize(0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 500)] public float SteerForce { get; set; } @@ -674,6 +682,9 @@ namespace Barotrauma [Serialize(50f, true), Editable] public float BlinkForce { get; set; } + [Serialize(false, true), Editable] + public bool OnlyBlinkInWater { get; set; } + [Serialize(TransitionMode.Linear, true), Editable] public TransitionMode BlinkTransitionIn { get; private set; } @@ -889,6 +900,9 @@ namespace Barotrauma [Serialize("", true), Editable()] public string Texture { get; set; } + [Serialize(false, true), Editable()] + public bool IgnoreTint { get; set; } + [Serialize("1.0,1.0,1.0,1.0", true), Editable()] public Color Color { get; set; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityCondition.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityCondition.cs new file mode 100644 index 000000000..959cf4148 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityCondition.cs @@ -0,0 +1,92 @@ +using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + abstract class AbilityCondition + { + protected CharacterTalent characterTalent; + protected Character character; + protected bool invert; + + public virtual bool AllowClientSimulation => true; + + public AbilityCondition(CharacterTalent characterTalent, XElement conditionElement) + { + this.characterTalent = characterTalent; + character = characterTalent.Character; + invert = conditionElement.GetAttributeBool("invert", false); + } + public abstract bool MatchesCondition(AbilityObject abilityObject); + public abstract bool MatchesCondition(); + + + // tools + protected enum TargetType + { + Any = 0, + Enemy = 1, + Ally = 2, + NotSelf = 3, + Alive = 4, + Monster = 5, + InFriendlySubmarine = 6, + }; + + protected List ParseTargetTypes(string[] targetTypeStrings) + { + List targetTypes = new List(); + foreach (string targetTypeString in targetTypeStrings) + { + TargetType targetType = TargetType.Any; + if (!Enum.TryParse(targetTypeString, true, out targetType)) + { + DebugConsole.ThrowError("Invalid target type type \"" + targetTypeString + "\" in CharacterTalent (" + characterTalent.DebugIdentifier + ")"); + } + targetTypes.Add(targetType); + } + return targetTypes; + } + + protected bool IsViableTarget(IEnumerable targetTypes, Character targetCharacter) + { + if (targetCharacter == null) { return false; } + + bool isViable = true; + foreach (TargetType targetType in targetTypes) + { + if (!IsViableTarget(targetType, targetCharacter)) + { + isViable = false; + break; + } + } + return isViable; + } + + private bool IsViableTarget(TargetType targetType, Character targetCharacter) + { + switch (targetType) + { + case TargetType.Enemy: + return !HumanAIController.IsFriendly(character, targetCharacter); + case TargetType.Ally: + return HumanAIController.IsFriendly(character, targetCharacter); + case TargetType.NotSelf: + return targetCharacter != character; + case TargetType.Alive: + return !targetCharacter.IsDead; + case TargetType.Monster: + return !targetCharacter.IsHuman; + case TargetType.InFriendlySubmarine: + return targetCharacter.Submarine != null && targetCharacter.Submarine.TeamID == character.TeamID; + default: + return true; + } + } + + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionAffliction.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionAffliction.cs new file mode 100644 index 000000000..4d909aa81 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionAffliction.cs @@ -0,0 +1,29 @@ +using Barotrauma.Items.Components; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class AbilityConditionAffliction : AbilityConditionData + { + private readonly string[] afflictions; + public AbilityConditionAffliction(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) + { + afflictions = conditionElement.GetAttributeStringArray("afflictions", new string[0], convertToLowerInvariant: true); + } + + protected override bool MatchesConditionSpecific(AbilityObject abilityObject) + { + if ((abilityObject as IAbilityAffliction)?.Affliction is Affliction affliction) + { + return afflictions.Any(a => a == affliction.Identifier); + } + else + { + LogAbilityConditionError(abilityObject, typeof(IAbilityAttackResult)); + return false; + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionAttackData.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionAttackData.cs new file mode 100644 index 000000000..ee5fedac0 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionAttackData.cs @@ -0,0 +1,79 @@ +using Barotrauma.Items.Components; +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class AbilityConditionAttackData : AbilityConditionData + { + private enum WeaponType + { + Any = 0, + Melee = 1, + Ranged = 2 + }; + + private readonly string itemIdentifier; + private readonly string[] tags; + private readonly WeaponType weapontype; + public AbilityConditionAttackData(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) + { + itemIdentifier = conditionElement.GetAttributeString("itemidentifier", ""); + tags = conditionElement.GetAttributeStringArray("tags", new string[0], convertToLowerInvariant: true); + switch (conditionElement.GetAttributeString("weapontype", "")) + { + case "melee": + weapontype = WeaponType.Melee; + break; + case "ranged": + weapontype = WeaponType.Ranged; + break; + } + } + + protected override bool MatchesConditionSpecific(AbilityObject abilityObject) + { + if (abilityObject is AbilityAttackData attackData) + { + Item item = attackData?.SourceAttack?.SourceItem; + + if (item == null) + { + DebugConsole.AddWarning($"Source Item was not found in {this} for talent {characterTalent.DebugIdentifier}!"); + return false; + } + + if (!string.IsNullOrEmpty(itemIdentifier)) + { + if (item.prefab.Identifier != itemIdentifier) + { + return false; + } + } + + if (tags.Any()) + { + if (!tags.All(t => item.HasTag(t))) + { + return false; + } + } + + switch (weapontype) + { + case WeaponType.Melee: + return item.GetComponent() != null; + case WeaponType.Ranged: + return item.GetComponent() != null; + } + + return true; + } + else + { + LogAbilityConditionError(abilityObject, typeof(AbilityAttackData)); + return false; + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionAttackResult.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionAttackResult.cs new file mode 100644 index 000000000..58616eac5 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionAttackResult.cs @@ -0,0 +1,38 @@ +using Barotrauma.Items.Components; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class AbilityConditionAttackResult : AbilityConditionData + { + private readonly List targetTypes; + private readonly string[] afflictions; + public AbilityConditionAttackResult(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) + { + targetTypes = ParseTargetTypes(conditionElement.GetAttributeStringArray("targettypes", new string[0], convertToLowerInvariant: true)); + afflictions = conditionElement.GetAttributeStringArray("afflictions", new string[0], convertToLowerInvariant: true); + } + + protected override bool MatchesConditionSpecific(AbilityObject abilityObject) + { + if ((abilityObject as IAbilityAttackResult)?.AttackResult is AttackResult attackResult) + { + if (!IsViableTarget(targetTypes, attackResult.HitLimb?.character)) { return false; } + + if (afflictions.Any()) + { + if (attackResult.Afflictions == null || !afflictions.Any(a => attackResult.Afflictions.Select(c => c.Identifier).Contains(a))) { return false; } + } + + return true; + } + else + { + LogAbilityConditionError(abilityObject, typeof(IAbilityAttackResult)); + return false; + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionCharacter.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionCharacter.cs new file mode 100644 index 000000000..2670ea21e --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionCharacter.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class AbilityConditionCharacter : AbilityConditionData + { + private readonly List targetTypes; + + public AbilityConditionCharacter(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) + { + targetTypes = ParseTargetTypes(conditionElement.GetAttributeStringArray("targettypes", new string[0], convertToLowerInvariant: true)); + } + + protected override bool MatchesConditionSpecific(AbilityObject abilityObject) + { + if ((abilityObject as IAbilityCharacter)?.Character is Character character) + { + if (!IsViableTarget(targetTypes, character)) { return false; } + + return true; + } + else + { + LogAbilityConditionError(abilityObject, typeof(IAbilityCharacter)); + return false; + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionData.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionData.cs new file mode 100644 index 000000000..fe3608df4 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionData.cs @@ -0,0 +1,36 @@ +using System; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + abstract class AbilityConditionData : AbilityCondition + { + /// + /// Some conditions rely on specific ability data that is integrally connected to the AbilityEffectType. + /// This is done in order to avoid having to create duplicate ability behavior, such as if an ability needs to trigger + /// a common ability effect but in specific circumstances. These conditions could also be partially replaced by + /// more explicit AbilityEffectType enums, but this would introduce bloat and overhead to integral game logic + /// when instead said logic can be made to only run when required using these conditions. + /// + /// These conditions will return an error if used outside their limited intended use. + /// + public AbilityConditionData(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) { } + + protected void LogAbilityConditionError(AbilityObject abilityObject, Type expectedData) + { + DebugConsole.ThrowError($"Used data-reliant ability condition when data is incompatible! Expected {expectedData}, but received {abilityObject}"); + } + + protected abstract bool MatchesConditionSpecific(AbilityObject abilityObject); + public override bool MatchesCondition() + { + DebugConsole.ThrowError("Used data-reliant ability condition in a state-based ability! This is not allowed."); + return false; + } + public override bool MatchesCondition(AbilityObject abilityObject) + { + if (abilityObject is null) { return invert; } + return invert ? !MatchesConditionSpecific(abilityObject) : MatchesConditionSpecific(abilityObject); + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionEvasiveManeuvers.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionEvasiveManeuvers.cs new file mode 100644 index 000000000..2e1204fba --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionEvasiveManeuvers.cs @@ -0,0 +1,22 @@ +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class AbilityConditionEvasiveManeuvers : AbilityConditionData + { + public AbilityConditionEvasiveManeuvers(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) { } + + protected override bool MatchesConditionSpecific(AbilityObject abilityObject) + { + if ((abilityObject as IAbilitySubmarine)?.Submarine is Submarine submarine && (abilityObject as IAbilityCharacter)?.Character is Character attackingCharacter) + { + return submarine.TeamID == character.TeamID && character.Submarine == submarine && attackingCharacter.TeamID != character.TeamID; + } + else + { + LogAbilityConditionError(abilityObject, typeof(IAbilitySubmarine)); + return false; + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionIsAiming.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionIsAiming.cs new file mode 100644 index 000000000..26a04a1a7 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionIsAiming.cs @@ -0,0 +1,60 @@ +using Barotrauma.Items.Components; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class AbilityConditionIsAiming : AbilityConditionDataless + { + private enum WeaponType + { + Any = 0, + Melee = 1, + Ranged = 2 + }; + + private readonly bool hittingCountsAsAiming; + + private readonly WeaponType weapontype; + public AbilityConditionIsAiming(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) + { + hittingCountsAsAiming = conditionElement.GetAttributeBool("hittingcountsasaiming", false); + switch (conditionElement.GetAttributeString("weapontype", "")) + { + case "melee": + weapontype = WeaponType.Melee; + break; + case "ranged": + weapontype = WeaponType.Ranged; + break; + } + } + + protected override bool MatchesConditionSpecific() + { + if (character.AnimController is HumanoidAnimController animController) + { + foreach (Item item in character.HeldItems) + { + switch (weapontype) + { + case WeaponType.Melee: + var meleeWeapon = item.GetComponent(); + if (meleeWeapon != null) + { + if (animController.IsAimingMelee || (meleeWeapon.Hitting && hittingCountsAsAiming)) { return true; } + } + break; + case WeaponType.Ranged: + if (animController.IsAiming && item.GetComponent() != null) { return true; } + break; + default: + if (animController.IsAiming || animController.IsAimingMelee) { return true; } + break; + } + } + } + + return false; + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionItem.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionItem.cs new file mode 100644 index 000000000..d4eb985d2 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionItem.cs @@ -0,0 +1,49 @@ +using System; +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class AbilityConditionItem : AbilityConditionData + { + private readonly string[] identifiers; + private readonly string[] tags; + + public AbilityConditionItem(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) + { + identifiers = conditionElement.GetAttributeStringArray("identifiers", Array.Empty(), convertToLowerInvariant: true); + tags = conditionElement.GetAttributeStringArray("tags", Array.Empty(), convertToLowerInvariant: true); + } + + protected override bool MatchesConditionSpecific(AbilityObject abilityObject) + { + ItemPrefab itemPrefab = null; + if ((abilityObject as IAbilityItemPrefab)?.ItemPrefab is ItemPrefab abilityItemPrefab) + { + itemPrefab = abilityItemPrefab; + } + else if ((abilityObject as IAbilityItem)?.Item is Item abilityItem) + { + itemPrefab = abilityItem.Prefab; + } + + if (itemPrefab != null) + { + if (identifiers.Any()) + { + if (!identifiers.Any(t => itemPrefab.Identifier == t)) + { + return false; + } + } + + return !tags.Any() || tags.Any(t => itemPrefab.Tags.Any(p => t == p)); + } + else + { + LogAbilityConditionError(abilityObject, typeof(IAbilityItemPrefab)); + return false; + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionItemOutsideSubmarine.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionItemOutsideSubmarine.cs new file mode 100644 index 000000000..d23794f56 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionItemOutsideSubmarine.cs @@ -0,0 +1,23 @@ +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class AbilityConditionItemOutsideSubmarine : AbilityConditionData + { + + public AbilityConditionItemOutsideSubmarine(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) { } + + protected override bool MatchesConditionSpecific(AbilityObject abilityObject) + { + if ((abilityObject as IAbilityItem)?.Item is Item item) + { + return item.Submarine == null || item.Submarine.TeamID != character.Info.TeamID; + } + else + { + LogAbilityConditionError(abilityObject, typeof(IAbilityItem)); + return false; + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionItemWreck.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionItemWreck.cs new file mode 100644 index 000000000..81d1b1d06 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionItemWreck.cs @@ -0,0 +1,23 @@ +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class AbilityConditionItemWreck : AbilityConditionData + { + + public AbilityConditionItemWreck(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) { } + + protected override bool MatchesConditionSpecific(AbilityObject abilityObject) + { + if ((abilityObject as IAbilityItem)?.Item is Item item) + { + return item.Submarine?.Info?.IsWreck ?? false; + } + else + { + LogAbilityConditionError(abilityObject, typeof(IAbilityItem)); + return false; + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionLocation.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionLocation.cs new file mode 100644 index 000000000..b2d70b0b3 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionLocation.cs @@ -0,0 +1,41 @@ +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class AbilityConditionLocation : AbilityConditionData + { + private readonly bool? hasOutpost; + private readonly string[] locationIdentifiers; + + public AbilityConditionLocation(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) + { + if (conditionElement.Attribute("hasoutpost") != null) + { + hasOutpost = conditionElement.GetAttributeBool("hasoutpost", false); + } + locationIdentifiers = conditionElement.GetAttributeStringArray("locationtype", new string[0]); + } + + protected override bool MatchesConditionSpecific(AbilityObject abilityObject) + { + if (abilityObject is IAbilityLocation abilityLocation) + { + if (locationIdentifiers.Any()) + { + if (!locationIdentifiers.Contains(abilityLocation.Location.Type.Identifier)) { return false; } + } + if (hasOutpost.HasValue) + { + if (hasOutpost.Value != abilityLocation.Location.HasOutpost()) { return false; } + } + return true; + } + else + { + LogAbilityConditionError(abilityObject, typeof(IAbilityItemPrefab)); + return false; + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionMission.cs new file mode 100644 index 000000000..f7f0ffed4 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionMission.cs @@ -0,0 +1,38 @@ +using System; +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class AbilityConditionMission : AbilityConditionData + { + private readonly MissionType missionType; + public AbilityConditionMission(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) + { + string missionTypeString = conditionElement.GetAttributeString("missiontype", "None"); + if (!Enum.TryParse(missionTypeString, out missionType)) + { + DebugConsole.ThrowError("Error in AbilityConditionMission \"" + characterTalent.DebugIdentifier + "\" - \"" + missionTypeString + "\" is not a valid mission type."); + return; + } + if (missionType == MissionType.None) + { + DebugConsole.ThrowError("Error in AbilityConditionMission \"" + characterTalent.DebugIdentifier + "\" - mission type cannot be none."); + return; + } + } + + protected override bool MatchesConditionSpecific(AbilityObject abilityObject) + { + if ((abilityObject as IAbilityMission)?.Mission is Mission mission) + { + return mission.Prefab.Type == missionType; + } + else + { + LogAbilityConditionError(abilityObject, typeof(IAbilityMission)); + return false; + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionReduceAffliction.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionReduceAffliction.cs new file mode 100644 index 000000000..1068da08b --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionReduceAffliction.cs @@ -0,0 +1,33 @@ +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class AbilityConditionReduceAffliction : AbilityConditionData + { + private readonly string[] allowedTypes; + private readonly string identifier; + + public AbilityConditionReduceAffliction(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) + { + allowedTypes = conditionElement.GetAttributeStringArray("allowedtypes", new string[0], convertToLowerInvariant: true); + identifier = conditionElement.GetAttributeString("identifier", ""); + } + + protected override bool MatchesConditionSpecific(AbilityObject abilityObject) + { + if ((abilityObject as IAbilityAffliction)?.Affliction is Affliction affliction) + { + if (allowedTypes.Find(c => c == affliction.Prefab.AfflictionType) == null) { return false; } + + if (!string.IsNullOrEmpty(identifier) && affliction.Prefab.Identifier != identifier) { return false; } + + return true; + } + else + { + LogAbilityConditionError(abilityObject, typeof(IAbilityAffliction)); + return false; + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionSkill.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionSkill.cs new file mode 100644 index 000000000..5c368df8f --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionSkill.cs @@ -0,0 +1,32 @@ +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class AbilityConditionSkill : AbilityConditionData + { + private readonly string skillIdentifier; + + public AbilityConditionSkill(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) + { + skillIdentifier = conditionElement.GetAttributeString("skillidentifier", "").ToLowerInvariant(); + } + + private bool MatchesConditionSpecific(string skillIdentifier) + { + return this.skillIdentifier == skillIdentifier; + } + + protected override bool MatchesConditionSpecific(AbilityObject abilityObject) + { + if ((abilityObject as IAbilityString)?.String is string skillIdentifier) + { + return MatchesConditionSpecific(skillIdentifier); + } + else + { + LogAbilityConditionError(abilityObject, typeof(IAbilityString)); + return false; + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionAboveVitality.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionAboveVitality.cs new file mode 100644 index 000000000..9fe8fa1a4 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionAboveVitality.cs @@ -0,0 +1,19 @@ +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class AbilityConditionAboveVitality : AbilityConditionDataless + { + private readonly float vitalityPercentage; + + public AbilityConditionAboveVitality(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) + { + vitalityPercentage = conditionElement.GetAttributeFloat("vitalitypercentage", 0f); + } + + protected override bool MatchesConditionSpecific() + { + return character.HealthPercentage / 100f > vitalityPercentage; + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionAlliesAboveVitality.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionAlliesAboveVitality.cs new file mode 100644 index 000000000..29256ab7c --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionAlliesAboveVitality.cs @@ -0,0 +1,19 @@ +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class AbilityConditionAlliesAboveVitality : AbilityConditionDataless + { + float vitalityPercentage; + + public AbilityConditionAlliesAboveVitality(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) + { + vitalityPercentage = conditionElement.GetAttributeFloat("vitalitypercentage", 0f); + } + protected override bool MatchesConditionSpecific() + { + return Character.GetFriendlyCrew(character).All(c => c.HealthPercentage / 100f >= vitalityPercentage); + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionCoauthor.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionCoauthor.cs new file mode 100644 index 000000000..7525427eb --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionCoauthor.cs @@ -0,0 +1,26 @@ +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class AbilityConditionCoauthor : AbilityConditionDataless + { + private readonly string jobIdentifier; + + public AbilityConditionCoauthor(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) + { + jobIdentifier = conditionElement.GetAttributeString("jobidentifier", string.Empty); + } + + protected override bool MatchesConditionSpecific() + { + if (character.SelectedCharacter is Character otherCharacter) + { + if (!otherCharacter.HasJob(jobIdentifier)) { return false; } + if (!(character.SelectedBy == otherCharacter)) { return false; } + return true; + } + return false; + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionCrouched.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionCrouched.cs new file mode 100644 index 000000000..cd96edb58 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionCrouched.cs @@ -0,0 +1,18 @@ +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class AbilityConditionCrouched : AbilityConditionDataless + { + + public AbilityConditionCrouched(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) + { + } + + protected override bool MatchesConditionSpecific() + { + return character.AnimController is HumanoidAnimController humanoidAnimController && humanoidAnimController.Crouching; + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionDataless.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionDataless.cs new file mode 100644 index 000000000..ad7007fd6 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionDataless.cs @@ -0,0 +1,20 @@ +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + abstract class AbilityConditionDataless : AbilityCondition + { + public AbilityConditionDataless(CharacterTalent characterTalent, XElement conditionElement) : base (characterTalent, conditionElement) { } + + protected abstract bool MatchesConditionSpecific(); + public override bool MatchesCondition() + { + return invert ? !MatchesConditionSpecific() : MatchesConditionSpecific(); + } + + public override bool MatchesCondition(AbilityObject abilityObject) + { + return invert ? !MatchesConditionSpecific() : MatchesConditionSpecific(); + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionHasAffliction.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionHasAffliction.cs new file mode 100644 index 000000000..9f449e43c --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionHasAffliction.cs @@ -0,0 +1,31 @@ +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class AbilityConditionHasAffliction : AbilityConditionDataless + { + private string afflictionIdentifier; + private float minimumPercentage; + + + public AbilityConditionHasAffliction(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) + { + afflictionIdentifier = conditionElement.GetAttributeString("afflictionidentifier", ""); + minimumPercentage = conditionElement.GetAttributeFloat("minimumpercentage", 0f); + } + + protected override bool MatchesConditionSpecific() + { + if (!string.IsNullOrEmpty(afflictionIdentifier)) + { + var affliction = character.CharacterHealth.GetAffliction(afflictionIdentifier); + + if (affliction == null) { return false; } + + return minimumPercentage <= affliction.Strength / affliction.Prefab.MaxStrength; + } + return false; + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionHasDifferentJobs.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionHasDifferentJobs.cs new file mode 100644 index 000000000..0f1707d3d --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionHasDifferentJobs.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class AbilityConditionHasDifferentJobs : AbilityConditionDataless + { + private readonly int amount; + public AbilityConditionHasDifferentJobs(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) + { + amount = conditionElement.GetAttributeInt("amount", 0); + } + + protected override bool MatchesConditionSpecific() + { + IEnumerable crewmembers = Character.GetFriendlyCrew(character); + int differentCrewAmount = crewmembers.Select(c => c.Info?.Job?.Prefab.Identifier).Distinct().Count(); + return differentCrewAmount >= amount; + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionHasItem.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionHasItem.cs new file mode 100644 index 000000000..8f4fc7c35 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionHasItem.cs @@ -0,0 +1,57 @@ +using Barotrauma.Items.Components; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class AbilityConditionHasItem : AbilityConditionDataless + { + // not used for anything atm, will be used for clown subclass + private readonly string[] tags; + private InvSlotType? invSlotType; + bool requireAll; + + private List items = new List(); + + public AbilityConditionHasItem(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) + { + tags = conditionElement.GetAttributeStringArray("tags", new string[0], convertToLowerInvariant: true); + requireAll = conditionElement.GetAttributeBool("requireall", false); + //this.invSlotType = invSlotType; + } + + protected override bool MatchesConditionSpecific() + { + items.Clear(); + if (tags.Any()) + { + foreach (string tag in tags) + { + // there is a better method, should use that instead + if (character.GetEquippedItem(tag, invSlotType) is Item foundItem) + { + items.Add(foundItem); + } + } + + } + else + { + if (character.GetEquippedItem(null, invSlotType) is Item foundItem) + { + items.Add(foundItem); + } + } + + if (requireAll) + { + return (items.Count >= tags.Count()); + } + else + { + return items.Any(); + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionHasPermanentStat.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionHasPermanentStat.cs new file mode 100644 index 000000000..2c3b26a5c --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionHasPermanentStat.cs @@ -0,0 +1,29 @@ +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class AbilityConditionHasPermanentStat : AbilityConditionDataless + { + private readonly string statIdentifier; + private readonly StatTypes statType; + private readonly float min; + + public AbilityConditionHasPermanentStat(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) + { + statIdentifier = conditionElement.GetAttributeString("statidentifier", string.Empty); + if (string.IsNullOrEmpty(statIdentifier)) + { + DebugConsole.ThrowError($"No stat identifier defined for {this} in talent {characterTalent.DebugIdentifier}!"); + } + string statTypeName = conditionElement.GetAttributeString("stattype", string.Empty); + statType = string.IsNullOrEmpty(statTypeName) ? StatTypes.None : CharacterAbilityGroup.ParseStatType(statTypeName, characterTalent.DebugIdentifier); + min = conditionElement.GetAttributeFloat("min", 0f); + } + + protected override bool MatchesConditionSpecific() + { + return character.Info.GetSavedStatValue(statType, statIdentifier) >= min; + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionHasSkill.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionHasSkill.cs new file mode 100644 index 000000000..60d5da1f7 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionHasSkill.cs @@ -0,0 +1,24 @@ +using Barotrauma.Items.Components; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class AbilityConditionHasSkill : AbilityConditionDataless + { + private readonly string skillIdentifier; + private readonly float minValue; + + public AbilityConditionHasSkill(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) + { + skillIdentifier = conditionElement.GetAttributeString("skillidentifier", string.Empty); + minValue = conditionElement.GetAttributeFloat("minvalue", 0f); + } + + protected override bool MatchesConditionSpecific() + { + return character.GetSkillLevel(skillIdentifier) >= minValue; + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionHasStatusTag.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionHasStatusTag.cs new file mode 100644 index 000000000..2a22f2098 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionHasStatusTag.cs @@ -0,0 +1,31 @@ +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class AbilityConditionHasStatusTag : AbilityConditionDataless + { + private readonly string tag; + + + public AbilityConditionHasStatusTag(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) + { + tag = conditionElement.GetAttributeString("tag", ""); + if (string.IsNullOrEmpty(tag)) + { + DebugConsole.AddWarning($"Error in talent \"{characterTalent.Prefab.OriginalName}\" - tag not defined in AbilityConditionHasStatusTag."); + } + } + + protected override bool MatchesConditionSpecific() + { + if (!string.IsNullOrEmpty(tag)) + { + return + StatusEffect.DurationList.Any(d => d.Targets.Contains(character) && d.Parent.HasTag(tag)) || + DelayedEffect.DelayList.Any(d => d.Targets.Contains(character) && d.Parent.HasTag(tag)); + } + return false; + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionHasVelocity.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionHasVelocity.cs new file mode 100644 index 000000000..d3aa75bde --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionHasVelocity.cs @@ -0,0 +1,20 @@ +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class AbilityConditionHasVelocity : AbilityConditionDataless + { + private readonly float velocity; + + public AbilityConditionHasVelocity(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) + { + velocity = conditionElement.GetAttributeFloat("velocity", 0f); + } + + protected override bool MatchesConditionSpecific() + { + return character.AnimController.Collider.LinearVelocity.LengthSquared() > velocity * velocity; + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionInFriendlySubmarine.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionInFriendlySubmarine.cs new file mode 100644 index 000000000..28f27ed9b --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionInFriendlySubmarine.cs @@ -0,0 +1,15 @@ + +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class AbilityConditionInFriendlySubmarine : AbilityConditionDataless + { + public AbilityConditionInFriendlySubmarine(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) { } + + protected override bool MatchesConditionSpecific() + { + return character.Submarine?.TeamID == character.TeamID; + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionInHull.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionInHull.cs new file mode 100644 index 000000000..e08291e6b --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionInHull.cs @@ -0,0 +1,15 @@ + +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class AbilityConditionInHull : AbilityConditionDataless + { + public AbilityConditionInHull(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) { } + + protected override bool MatchesConditionSpecific() + { + return character.CurrentHull != null; + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionInWater.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionInWater.cs new file mode 100644 index 000000000..d93731514 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionInWater.cs @@ -0,0 +1,15 @@ + +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class AbilityConditionInWater : AbilityConditionDataless + { + public AbilityConditionInWater(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) { } + + protected override bool MatchesConditionSpecific() + { + return character.InWater; + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionLevelsBehindHighest.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionLevelsBehindHighest.cs new file mode 100644 index 000000000..f2c4b2fb7 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionLevelsBehindHighest.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class AbilityConditionLevelsBehindHighest : AbilityConditionDataless + { + private readonly int levelsBehind; + public AbilityConditionLevelsBehindHighest(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) + { + levelsBehind = conditionElement.GetAttributeInt("levelsbehind", 0); + } + + protected override bool MatchesConditionSpecific() + { + return Character.GetFriendlyCrew(character).Where(c => c.Info != null && (c.Info.GetCurrentLevel() - character.Info.GetCurrentLevel() >= levelsBehind)).Any(); + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionNoCrewDied.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionNoCrewDied.cs new file mode 100644 index 000000000..bb4390106 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionNoCrewDied.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class AbilityConditionNoCrewDied : AbilityConditionDataless + { + public AbilityConditionNoCrewDied(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) + { + } + + protected override bool MatchesConditionSpecific() + { + if (GameMain.GameSession?.Campaign is CampaignMode campaign) + { + return !campaign.CrewHasDied; + } + return true; + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionOnMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionOnMission.cs new file mode 100644 index 000000000..dac9a3f1a --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionOnMission.cs @@ -0,0 +1,17 @@ +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class AbilityConditionOnMission : AbilityConditionDataless + { + public AbilityConditionOnMission(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) + { + } + + protected override bool MatchesConditionSpecific() + { + return Level.Loaded?.Type != LevelData.LevelType.Outpost; + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionRagdolled.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionRagdolled.cs new file mode 100644 index 000000000..192ea6f4f --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionRagdolled.cs @@ -0,0 +1,18 @@ +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class AbilityConditionRagdolled : AbilityConditionDataless + { + + public AbilityConditionRagdolled(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) + { + } + + protected override bool MatchesConditionSpecific() + { + return character.IsRagdolled || character.Stun > 0f || character.IsIncapacitated; + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionRunning.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionRunning.cs new file mode 100644 index 000000000..3186b852f --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionRunning.cs @@ -0,0 +1,15 @@ + +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class AbilityConditionRunning : AbilityConditionDataless + { + public AbilityConditionRunning(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) { } + + protected override bool MatchesConditionSpecific() + { + return character.AnimController is HumanoidAnimController animController && animController.IsMovingFast; + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionServerRandom.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionServerRandom.cs new file mode 100644 index 000000000..5b582f799 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionServerRandom.cs @@ -0,0 +1,20 @@ +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class AbilityConditionServerRandom : AbilityConditionDataless + { + private readonly float randomChance = 0f; + public override bool AllowClientSimulation => false; + + public AbilityConditionServerRandom(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) + { + randomChance = conditionElement.GetAttributeFloat("randomchance", 1f); + } + + protected override bool MatchesConditionSpecific() + { + return randomChance >= Rand.Range(0f, 1f, Rand.RandSync.Unsynced); + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionShipFlooded.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionShipFlooded.cs new file mode 100644 index 000000000..9a99f4ce8 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionShipFlooded.cs @@ -0,0 +1,21 @@ +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class AbilityConditionShipFlooded : AbilityConditionDataless + { + private readonly float floodPercentage; + public AbilityConditionShipFlooded(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) + { + floodPercentage = conditionElement.GetAttributeFloat("floodpercentage", 0f); + } + + protected override bool MatchesConditionSpecific() + { + if (!character.IsInFriendlySub) { return false; } + float currentFloodPercentage = character.Submarine.GetHulls(false).Average(h => h.WaterPercentage); + return currentFloodPercentage / 100 > floodPercentage; + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityInterfaces.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityInterfaces.cs new file mode 100644 index 000000000..8c552ad82 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityInterfaces.cs @@ -0,0 +1,52 @@ +namespace Barotrauma.Abilities +{ + interface IAbilityItemPrefab + { + public ItemPrefab ItemPrefab { get; set; } + } + + interface IAbilityItem + { + public Item Item { get; set; } + } + + interface IAbilityValue + { + public float Value { get; set; } + } + + interface IAbilityMission + { + public Mission Mission { get; set; } + } + + interface IAbilityLocation + { + public Location Location { get; set; } + } + + interface IAbilityCharacter + { + public Character Character { get; set; } + } + + interface IAbilityString + { + public string String { get; set; } + } + + interface IAbilityAffliction + { + public Affliction Affliction { get; set; } + } + + interface IAbilityAttackResult + { + public AttackResult AttackResult { get; set; } + } + + interface IAbilitySubmarine + { + public Submarine Submarine { get; set; } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityObjects.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityObjects.cs new file mode 100644 index 000000000..49431bb1e --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityObjects.cs @@ -0,0 +1,187 @@ +using System.Collections.Generic; + +namespace Barotrauma.Abilities +{ + abstract class AbilityObject + { + // kept as blank for now, as we are using a composition and only using this object to enforce parameter types + } + + class AbilityCharacter : AbilityObject, IAbilityCharacter + { + public AbilityCharacter(Character character) + { + Character = character; + } + public Character Character { get; set; } + } + + class AbilityItem : AbilityObject, IAbilityItem + { + public AbilityItem(Item item) + { + Item = item; + } + public Item Item { get; set; } + } + + class AbilityValue : AbilityObject, IAbilityValue + { + public AbilityValue(float value) + { + Value = value; + } + public float Value { get; set; } + } + + class AbilityAffliction : AbilityObject, IAbilityAffliction + { + public AbilityAffliction(Affliction affliction) + { + Affliction = affliction; + } + public Affliction Affliction { get; set; } + } + + class AbilityAfflictionCharacter : AbilityObject, IAbilityAffliction, IAbilityCharacter + { + public AbilityAfflictionCharacter(Affliction affliction, Character character) + { + Affliction = affliction; + Character = character; + } + public Character Character { get; set; } + public Affliction Affliction { get; set; } + } + + class AbilityValueItem : AbilityObject, IAbilityValue, IAbilityItemPrefab + { + public AbilityValueItem(float value, ItemPrefab itemPrefab) + { + Value = value; + ItemPrefab = itemPrefab; + } + public float Value { get; set; } + public ItemPrefab ItemPrefab { get; set; } + } + + class AbilityItemPrefabItem : AbilityObject, IAbilityItem, IAbilityItemPrefab + { + public AbilityItemPrefabItem(Item item, ItemPrefab itemPrefab) + { + Item = item; + ItemPrefab = itemPrefab; + } + public Item Item { get; set; } + public ItemPrefab ItemPrefab { get; set; } + } + + class AbilityValueString : AbilityObject, IAbilityValue, IAbilityString + { + public AbilityValueString(float value, string abilityString) + { + Value = value; + String = abilityString; + } + public float Value { get; set; } + public string String { get; set; } + } + + class AbilityStringCharacter : AbilityObject, IAbilityCharacter, IAbilityString + { + public AbilityStringCharacter(string abilityString, Character character) + { + String = abilityString; + Character = character; + } + public Character Character { get; set; } + public string String { get; set; } + } + + class AbilityValueAffliction : AbilityObject, IAbilityValue, IAbilityAffliction + { + public AbilityValueAffliction(float value, Affliction affliction) + { + Value = value; + Affliction = affliction; + } + public float Value { get; set; } + public Affliction Affliction { get; set; } + } + + class AbilityValueMission : AbilityObject, IAbilityValue, IAbilityMission + { + public AbilityValueMission(float value, Mission mission) + { + Value = value; + Mission = mission; + } + public float Value { get; set; } + public Mission Mission { get; set; } + } + + class AbilityLocation : AbilityObject, IAbilityLocation + { + public AbilityLocation(Location location) + { + Location = location; + } + + public Location Location { get; set; } + } + + // this is an exception class that should only be passed in this form, so classes that use it should cast into it directly + class AbilityAttackData : AbilityObject, IAbilityCharacter + { + public float DamageMultiplier { get; set; } = 1f; + public float AddedPenetration { get; set; } = 0f; + public List Afflictions { get; set; } + public Attack SourceAttack { get; } + public Character Character { get; set; } + public Character Attacker { get; set; } + + public AbilityAttackData(Attack sourceAttack, Character character) + { + SourceAttack = sourceAttack; + Character = character; + } + } + + class AbilityApplyTreatment : AbilityObject, IAbilityCharacter, IAbilityItem + { + public Character Character { get; set; } + + public Character User { get; set; } + + public Item Item { get; set; } + + public AbilityApplyTreatment(Character user, Character target, Item item) + { + Character = target; + User = user; + Item = item; + } + } + + class AbilityAttackResult : AbilityObject, IAbilityAttackResult + { + public AttackResult AttackResult { get; set; } + + public AbilityAttackResult(AttackResult attackResult) + { + AttackResult = attackResult; + } + } + + class AbilityCharacterSubmarine : AbilityObject, IAbilityCharacter, IAbilitySubmarine + { + public AbilityCharacterSubmarine(Character character, Submarine submarine) + { + Character = character; + Submarine = submarine; + } + public Character Character { get; set; } + public Submarine Submarine { get; set; } + } + +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbility.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbility.cs new file mode 100644 index 000000000..650bf1863 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbility.cs @@ -0,0 +1,131 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + abstract class CharacterAbility + { + public CharacterAbilityGroup CharacterAbilityGroup { get; } + public CharacterTalent CharacterTalent { get; } + public Character Character { get; } + + public bool RequiresAlive { get; } + + public virtual bool AllowClientSimulation => false; + public virtual bool AppliesEffectOnIntervalUpdate => false; + + private const float DefaultEffectTime = 1.0f; + + // currently resets if the character dies. would need to be stored in a dictionary of sorts to maintain through death + + + /// + /// Used primarily for StatusEffects. Default to constant outside interval abilities. + /// + protected float EffectDeltaTime => CharacterAbilityGroup is CharacterAbilityGroupInterval abilityGroupInterval ? abilityGroupInterval.TimeSinceLastUpdate : DefaultEffectTime; + + public CharacterAbility(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) + { + CharacterAbilityGroup = characterAbilityGroup; + CharacterTalent = characterAbilityGroup.CharacterTalent; + Character = CharacterTalent.Character; + RequiresAlive = abilityElement.GetAttributeBool("requiresalive", true); + } + + public bool IsViable() + { + if (!AllowClientSimulation && GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return false; } + if (RequiresAlive && Character.IsDead) { return false; } + return true; + } + + public virtual void InitializeAbility(bool addingFirstTime) { } + + public virtual void UpdateCharacterAbility(bool conditionsMatched, float timeSinceLastUpdate) + { + // may need a separate Update for changing state on non-interval-based abilities + if (AppliesEffectOnIntervalUpdate) + { + if (conditionsMatched) + { + ApplyEffect(); + } + } + else + { + VerifyState(conditionsMatched, timeSinceLastUpdate); + } + } + + protected virtual void VerifyState(bool conditionsMatched, float timeSinceLastUpdate) + { + DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}: Ability {this} does not have an implementation for VerifyState! This ability does not work in interval ability groups."); + } + + public void ApplyAbilityEffect(AbilityObject abilityObject) + { + if (abilityObject is null) + { + ApplyEffect(); + } + else + { + ApplyEffect(abilityObject); + } + } + + protected virtual void ApplyEffect() + { + DebugConsole.AddWarning($"Ability {this} used improperly! This ability does not have a definition for ApplyEffect"); + } + + protected virtual void ApplyEffect(AbilityObject abilityObject) + { + DebugConsole.AddWarning($"Ability {this} used improperly! This ability does not take a parameter for ApplyEffect"); + } + + protected void LogabilityObjectMismatch() + { + DebugConsole.ThrowError($"Incompatible ability! Ability {this} is incompatitible with this type of ability effect type."); + } + + // XML + public static CharacterAbility Load(XElement abilityElement, CharacterAbilityGroup characterAbilityGroup, bool errorMessages = true) + { + Type abilityType; + string type = abilityElement.Name.ToString().ToLowerInvariant(); + try + { + abilityType = Type.GetType("Barotrauma.Abilities." + type + "", false, true); + if (abilityType == null) + { + if (errorMessages) DebugConsole.ThrowError("Could not find the CharacterAbility \"" + type + "\" (" + characterAbilityGroup.CharacterTalent.DebugIdentifier + ")"); + return null; + } + } + catch (Exception e) + { + if (errorMessages) DebugConsole.ThrowError("Could not find the CharacterAbility \"" + type + "\" (" + characterAbilityGroup.CharacterTalent.DebugIdentifier + ")", e); + return null; + } + + object[] args = { characterAbilityGroup, abilityElement }; + CharacterAbility characterAbility; + + try + { + characterAbility = (CharacterAbility)Activator.CreateInstance(abilityType, args); + } + catch (TargetInvocationException e) + { + DebugConsole.ThrowError("Error while creating an instance of a CharacterAbility of the type " + abilityType + ".", e.InnerException); + return null; + } + + return characterAbility; + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyForce.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyForce.cs new file mode 100644 index 000000000..7dde00097 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyForce.cs @@ -0,0 +1,61 @@ +using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class CharacterAbilityApplyForce : CharacterAbility + { + private readonly float force; + private readonly float maxVelocity; + + private readonly string afflictionIdentifier; + + private readonly HashSet limbTypes = new HashSet(); + + public override bool AppliesEffectOnIntervalUpdate => true; + public CharacterAbilityApplyForce(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) + { + force = abilityElement.GetAttributeFloat("force", 0f); + maxVelocity = abilityElement.GetAttributeFloat("maxvelocity", 10f); + afflictionIdentifier = abilityElement.GetAttributeString("afflictionidentifier", ""); + + string[] limbTypesStr = abilityElement.GetAttributeStringArray("limbtypes", new string[0]); + foreach (string limbTypeStr in limbTypesStr) + { + if (Enum.TryParse(limbTypeStr, out LimbType limbType)) + { + limbTypes.Add(limbType); + } + else + { + DebugConsole.ThrowError($"Error in talent \"{characterAbilityGroup.CharacterTalent.DebugIdentifier}\" - \"{limbTypeStr}\" is not a valid limb type."); + } + } + } + + protected override void ApplyEffect() + { + float strength = 1.0f; + if (!string.IsNullOrEmpty(afflictionIdentifier)) + { + Affliction affliction = Character.CharacterHealth.GetAffliction(afflictionIdentifier); + if (affliction == null) { return; } + strength = affliction.Strength / affliction.Prefab.MaxStrength; + } + + foreach (Limb limb in Character.AnimController.Limbs) + { + if (limb.IsSevered || limb.Removed) { continue; } + if (limbTypes.Any()) + { + if (!limbTypes.Contains(limb.type)) { continue; } + } + if (Character.AnimController.TargetMovement.LengthSquared() < 0.001f) { continue; } + limb.body.ApplyForce(Vector2.Normalize(limb.Mass * Character.AnimController.TargetMovement) * force * strength, maxVelocity); + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffects.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffects.cs new file mode 100644 index 000000000..a59955d4d --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffects.cs @@ -0,0 +1,88 @@ +using System.Collections.Generic; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class CharacterAbilityApplyStatusEffects : CharacterAbility + { + public override bool AppliesEffectOnIntervalUpdate => true; + public override bool AllowClientSimulation => true; + + protected readonly List statusEffects; + + private readonly bool nearbyCharactersAppliesToSelf; + private readonly bool nearbyCharactersAppliesToAllies; + private readonly bool applyToSelected; + + readonly List targets = new List(); + + public CharacterAbilityApplyStatusEffects(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) + { + statusEffects = CharacterAbilityGroup.ParseStatusEffects(CharacterTalent, abilityElement.GetChildElement("statuseffects")); + applyToSelected = abilityElement.GetAttributeBool("applytoselected", false); + nearbyCharactersAppliesToSelf = abilityElement.GetAttributeBool("nearbycharactersappliestoself", true); + nearbyCharactersAppliesToAllies = abilityElement.GetAttributeBool("nearbycharactersappliestoallies", true); + } + + protected void ApplyEffectSpecific(Character targetCharacter) + { + foreach (var statusEffect in statusEffects) + { + if (statusEffect.HasTargetType(StatusEffect.TargetType.UseTarget)) + { + // currently used to spawn items on the targeted character + statusEffect.SetUser(targetCharacter); + statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, targetCharacter, targetCharacter); + } + else if (statusEffect.HasTargetType(StatusEffect.TargetType.NearbyCharacters)) + { + targets.Clear(); + targets.AddRange(statusEffect.GetNearbyTargets(targetCharacter.WorldPosition, targets)); + if (!nearbyCharactersAppliesToSelf) + { + targets.RemoveAll(c => c == Character); + } + if (!nearbyCharactersAppliesToAllies) + { + targets.RemoveAll(c => c is Character otherCharacter && HumanAIController.IsFriendly(otherCharacter, Character)); + } + statusEffect.SetUser(Character); + statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, targetCharacter, targets); + } + else if (statusEffect.HasTargetType(StatusEffect.TargetType.Character)) + { + statusEffect.SetUser(Character); + statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, Character, targetCharacter); + } + else + { + statusEffect.SetUser(Character); + statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, Character, Character); + } + } + } + protected override void ApplyEffect() + { + if (applyToSelected && Character.SelectedCharacter is Character selectedCharacter) + { + ApplyEffectSpecific(selectedCharacter); + } + else + { + ApplyEffectSpecific(Character); + } + } + + protected override void ApplyEffect(AbilityObject abilityObject) + { + if ((abilityObject as IAbilityCharacter)?.Character is Character targetCharacter) + { + ApplyEffectSpecific(targetCharacter); + } + else + { + ApplyEffect(); + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffectsToAllies.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffectsToAllies.cs new file mode 100644 index 000000000..9cbaa17eb --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffectsToAllies.cs @@ -0,0 +1,35 @@ +using Microsoft.Xna.Framework; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class CharacterAbilityApplyStatusEffectsToAllies : CharacterAbilityApplyStatusEffects + { + private readonly bool allowSelf; + private readonly float maxDistance = float.MaxValue; + + public CharacterAbilityApplyStatusEffectsToAllies(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) + { + allowSelf = abilityElement.GetAttributeBool("allowself", true); + maxDistance = abilityElement.GetAttributeFloat("maxdistance", float.MaxValue); + } + + + protected override void ApplyEffect() + { + IEnumerable chosenCharacters = Character.GetFriendlyCrew(Character).Where(c => allowSelf || c != Character); + + foreach (Character character in chosenCharacters) + { + if (maxDistance < float.MaxValue) + { + if (Vector2.DistanceSquared(character.WorldPosition, Character.WorldPosition) > maxDistance * maxDistance) { continue; } + } + ApplyEffectSpecific(character); + } + } + + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffectsToAttacker.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffectsToAttacker.cs new file mode 100644 index 000000000..efca07622 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffectsToAttacker.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class CharacterAbilityApplyStatusEffectsToAttacker : CharacterAbilityApplyStatusEffects + { + public CharacterAbilityApplyStatusEffectsToAttacker(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) + { + } + + protected override void ApplyEffect(AbilityObject abilityObject) + { + if ((abilityObject as AbilityAttackData)?.Attacker is Character attacker) + { + ApplyEffectSpecific(attacker); + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffectsToLastOrderedCharacter.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffectsToLastOrderedCharacter.cs new file mode 100644 index 000000000..fc4291453 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffectsToLastOrderedCharacter.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class CharacterAbilityApplyStatusEffectsToLastOrderedCharacter : CharacterAbilityApplyStatusEffects + { + public CharacterAbilityApplyStatusEffectsToLastOrderedCharacter(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) + { + } + + protected override void ApplyEffect() + { + if (IsViableTarget(Character.LastOrderedCharacter)) + { + ApplyEffectSpecific(Character.LastOrderedCharacter); + } + if (Character.HasAbilityFlag(AbilityFlags.AllowSecondOrderedTarget) && IsViableTarget(Character.SecondLastOrderedCharacter)) + { + ApplyEffectSpecific(Character.SecondLastOrderedCharacter); + } + } + + private bool IsViableTarget(Character targetCharacter) + { + if (targetCharacter == null || targetCharacter.Removed) { return false; } + if (targetCharacter == Character) { return false; } + return true; + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffectsToNearestAlly.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffectsToNearestAlly.cs new file mode 100644 index 000000000..a0701c782 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffectsToNearestAlly.cs @@ -0,0 +1,35 @@ +using Microsoft.Xna.Framework; +using System; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class CharacterAbilityApplyStatusEffectsToNearestAlly : CharacterAbilityApplyStatusEffects + { + protected float squaredMaxDistance; + public CharacterAbilityApplyStatusEffectsToNearestAlly(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) + { + squaredMaxDistance = MathF.Pow(abilityElement.GetAttributeFloat("maxdistance", float.MaxValue), 2); + } + + protected override void ApplyEffect() + { + Character closestCharacter = null; + float closestDistance = float.MaxValue; + + foreach (Character crewCharacter in Character.GetFriendlyCrew(Character)) + { + if (crewCharacter != Character && Vector2.DistanceSquared(Character.WorldPosition, crewCharacter.WorldPosition) is float tempDistance && tempDistance < closestDistance) + { + closestCharacter = crewCharacter; + closestDistance = tempDistance; + } + } + + if (closestDistance < squaredMaxDistance) + { + ApplyEffectSpecific(closestCharacter); + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffectsToRandomAlly.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffectsToRandomAlly.cs new file mode 100644 index 000000000..0f1fd20b2 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffectsToRandomAlly.cs @@ -0,0 +1,41 @@ +using Barotrauma.Extensions; +using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class CharacterAbilityApplyStatusEffectsToRandomAlly : CharacterAbilityApplyStatusEffects + { + private readonly float squaredMaxDistance; + private readonly bool allowDifferentSub; + private readonly bool allowSelf; + + public override bool AllowClientSimulation => false; + + public CharacterAbilityApplyStatusEffectsToRandomAlly(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) + { + squaredMaxDistance = MathF.Pow(abilityElement.GetAttributeFloat("maxdistance", float.MaxValue), 2); + allowDifferentSub = abilityElement.GetAttributeBool("mustbeonsamesub", true); + allowSelf = abilityElement.GetAttributeBool("allowself", true); + } + + protected override void ApplyEffect() + { + Character chosenCharacter = null; + + chosenCharacter = Character.GetFriendlyCrew(Character).Where(c => + (allowSelf || c != Character) && + (allowDifferentSub || c.Submarine == Character.Submarine) && + Vector2.DistanceSquared(Character.WorldPosition, c.WorldPosition) is float tempDistance && + tempDistance < squaredMaxDistance).GetRandom(); + + if (chosenCharacter == null) { return; } + + ApplyEffectSpecific(chosenCharacter); + } + + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGainSimultaneousSkill.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGainSimultaneousSkill.cs new file mode 100644 index 000000000..43fef2a11 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGainSimultaneousSkill.cs @@ -0,0 +1,27 @@ +using Microsoft.Xna.Framework; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class CharacterAbilityGainSimultaneousSkill : CharacterAbility + { + private string skillIdentifier; + + public CharacterAbilityGainSimultaneousSkill(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) + { + skillIdentifier = abilityElement.GetAttributeString("skillidentifier", "").ToLowerInvariant(); + } + + protected override void ApplyEffect(AbilityObject abilityObject) + { + if ((abilityObject as IAbilityValue)?.Value is float skillIncrease) + { + Character.Info?.IncreaseSkillLevel(skillIdentifier, skillIncrease); + } + else + { + LogabilityObjectMismatch(); + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveAffliction.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveAffliction.cs new file mode 100644 index 000000000..5f56b433a --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveAffliction.cs @@ -0,0 +1,44 @@ +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class CharacterAbilityGiveAffliction : CharacterAbility + { + private readonly string afflictionId; + private readonly float strength; + private readonly string multiplyStrengthBySkill; + private readonly bool setValue; + + public CharacterAbilityGiveAffliction(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) + { + afflictionId = abilityElement.GetAttributeString("afflictionid", abilityElement.GetAttributeString("affliction", string.Empty)); + strength = abilityElement.GetAttributeFloat("strength", 0f); + multiplyStrengthBySkill = abilityElement.GetAttributeString("multiplystrengthbyskill", string.Empty); + setValue = abilityElement.GetAttributeBool("setvalue", false); + + if (string.IsNullOrEmpty(afflictionId)) + { + DebugConsole.ThrowError("Error in CharacterAbilityGiveAffliction - affliction identifier not set."); + } + } + + protected override void ApplyEffect(AbilityObject abilityObject) + { + if (abilityObject is IAbilityCharacter character) + { + var afflictionPrefab = AfflictionPrefab.Prefabs.Find(a => a.Identifier.Equals(afflictionId, System.StringComparison.OrdinalIgnoreCase)); + if (afflictionPrefab == null) + { + DebugConsole.ThrowError($"Error in CharacterAbilityGiveAffliction - could not find an affliction with the identifier \"{afflictionId}\"."); + return; + } + float strength = this.strength; + if (!string.IsNullOrEmpty(multiplyStrengthBySkill)) + { + strength *= Character.GetSkillLevel(multiplyStrengthBySkill); + } + character.Character.CharacterHealth.ApplyAffliction(null, afflictionPrefab.Instantiate(strength), allowStacking: !setValue); + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveFlag.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveFlag.cs new file mode 100644 index 000000000..76b3960ea --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveFlag.cs @@ -0,0 +1,20 @@ +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class CharacterAbilityGiveFlag : CharacterAbility + { + private readonly AbilityFlags abilityFlag; + + // this and resistance giving should probably be moved directly to charactertalent attributes, as they don't need to interact with either ability group types + public CharacterAbilityGiveFlag(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) + { + abilityFlag = CharacterAbilityGroup.ParseFlagType(abilityElement.GetAttributeString("flagtype", ""), CharacterTalent.DebugIdentifier); + } + + public override void InitializeAbility(bool addingFirstTime) + { + Character.AddAbilityFlag(abilityFlag); + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveMoney.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveMoney.cs new file mode 100644 index 000000000..9c4dd581a --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveMoney.cs @@ -0,0 +1,46 @@ +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class CharacterAbilityGiveMoney : CharacterAbility + { + public override bool AppliesEffectOnIntervalUpdate => true; + + private readonly int amount; + private readonly string scalingStatIdentifier; + + public CharacterAbilityGiveMoney(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) + { + amount = abilityElement.GetAttributeInt("amount", 0); + scalingStatIdentifier = abilityElement.GetAttributeString("scalingstatidentifier", string.Empty); + } + + private void ApplyEffectSpecific(Character targetCharacter) + { + float multiplier = 1f; + if (!string.IsNullOrEmpty(scalingStatIdentifier)) + { + multiplier = 0 + Character.Info.GetSavedStatValue(StatTypes.None, scalingStatIdentifier); + } + + targetCharacter.GiveMoney((int)(multiplier * amount)); + } + + protected override void ApplyEffect(AbilityObject abilityObject) + { + if ((abilityObject as IAbilityCharacter)?.Character is Character targetCharacter) + { + ApplyEffectSpecific(targetCharacter); + } + else + { + ApplyEffectSpecific(Character); + } + } + + protected override void ApplyEffect() + { + ApplyEffectSpecific(Character); + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGivePermanentStat.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGivePermanentStat.cs new file mode 100644 index 000000000..6fb4887b3 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGivePermanentStat.cs @@ -0,0 +1,64 @@ +using Barotrauma.Extensions; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class CharacterAbilityGivePermanentStat : CharacterAbility + { + private readonly string statIdentifier; + private readonly StatTypes statType; + private readonly float value; + private readonly float maxValue; + private readonly bool targetAllies; + private readonly bool removeOnDeath; + private readonly bool giveOnAddingFirstTime; + private readonly bool setValue; + + //private readonly float maximumValue; + public override bool AllowClientSimulation => true; + public override bool AppliesEffectOnIntervalUpdate => true; + + public CharacterAbilityGivePermanentStat(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) + { + statIdentifier = abilityElement.GetAttributeString("statidentifier", "").ToLowerInvariant(); + string statTypeName = abilityElement.GetAttributeString("stattype", string.Empty); + statType = string.IsNullOrEmpty(statTypeName) ? StatTypes.None : CharacterAbilityGroup.ParseStatType(statTypeName, CharacterTalent.DebugIdentifier); + value = abilityElement.GetAttributeFloat("value", 0f); + maxValue = abilityElement.GetAttributeFloat("maxvalue", float.MaxValue); + targetAllies = abilityElement.GetAttributeBool("targetallies", false); + removeOnDeath = abilityElement.GetAttributeBool("removeondeath", true); + giveOnAddingFirstTime = abilityElement.GetAttributeBool("giveonaddingfirsttime", characterAbilityGroup.AbilityEffectType == AbilityEffectType.None); + setValue = abilityElement.GetAttributeBool("setvalue", false); + } + + public override void InitializeAbility(bool addingFirstTime) + { + if (giveOnAddingFirstTime && addingFirstTime) + { + ApplyEffectSpecific(); + } + } + + protected override void ApplyEffect(AbilityObject abilityObject) + { + ApplyEffectSpecific(); + } + + protected override void ApplyEffect() + { + ApplyEffectSpecific(); + } + + private void ApplyEffectSpecific() + { + if (targetAllies) + { + Character.GetFriendlyCrew(Character).ForEach(c => c?.Info.ChangeSavedStatValue(statType, value, statIdentifier, removeOnDeath, maxValue: maxValue, setValue: setValue)); + } + else + { + Character?.Info.ChangeSavedStatValue(statType, value, statIdentifier, removeOnDeath, maxValue: maxValue, setValue: setValue); + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveResistance.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveResistance.cs new file mode 100644 index 000000000..253dd787b --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveResistance.cs @@ -0,0 +1,26 @@ +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class CharacterAbilityGiveResistance : CharacterAbility + { + private readonly string resistanceId; + private readonly float multiplier; + + public CharacterAbilityGiveResistance(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) + { + resistanceId = abilityElement.GetAttributeString("resistanceid", abilityElement.GetAttributeString("resistance", string.Empty)); + multiplier = abilityElement.GetAttributeFloat("multiplier", 1f); // rename this to resistance for consistency + + if (string.IsNullOrEmpty(resistanceId)) + { + DebugConsole.ThrowError("Error in CharacterAbilityGiveResistance - resistance identifier not set."); + } + } + + public override void InitializeAbility(bool addingFirstTime) + { + Character.ChangeAbilityResistance(resistanceId, multiplier); + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveStat.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveStat.cs new file mode 100644 index 000000000..c999d3999 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveStat.cs @@ -0,0 +1,21 @@ +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class CharacterAbilityGiveStat : CharacterAbility + { + private readonly StatTypes statType; + private readonly float value; + + public CharacterAbilityGiveStat(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) + { + statType = CharacterAbilityGroup.ParseStatType(abilityElement.GetAttributeString("stattype", ""), CharacterTalent.DebugIdentifier); + value = abilityElement.GetAttributeFloat("value", 0f); + } + + public override void InitializeAbility(bool addingFirstTime) + { + Character.ChangeStat(statType, value); + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveTalentPoints.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveTalentPoints.cs new file mode 100644 index 000000000..8ba1c9ef9 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveTalentPoints.cs @@ -0,0 +1,23 @@ +using Barotrauma.Extensions; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class CharacterAbilityGiveTalentPoints : CharacterAbility + { + private readonly int amount; + + public CharacterAbilityGiveTalentPoints(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) + { + amount = abilityElement.GetAttributeInt("amount", 0); + } + + public override void InitializeAbility(bool addingFirstTime) + { + if (addingFirstTime && Character.Info != null) + { + Character.Info.AdditionalTalentPoints += amount; + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityIncreaseSkill.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityIncreaseSkill.cs new file mode 100644 index 000000000..d93519de0 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityIncreaseSkill.cs @@ -0,0 +1,60 @@ +using Barotrauma.Extensions; +using Microsoft.Xna.Framework; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class CharacterAbilityIncreaseSkill : CharacterAbility + { + public override bool AppliesEffectOnIntervalUpdate => true; + + private readonly string skillIdentifier; + private readonly float skillIncrease; + + public CharacterAbilityIncreaseSkill(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) + { + skillIdentifier = abilityElement.GetAttributeString("skillidentifier", "").ToLowerInvariant(); + skillIncrease = abilityElement.GetAttributeFloat("skillincrease", 0f); + + if (string.IsNullOrEmpty(skillIdentifier)) + { + DebugConsole.ThrowError($"Error in talent \"{characterAbilityGroup.CharacterTalent.DebugIdentifier}\" - skill identifier not defined in CharacterAbilityIncreaseSkill."); + } + if (MathUtils.NearlyEqual(skillIncrease, 0)) + { + DebugConsole.AddWarning($"Possible error in talent \"{characterAbilityGroup.CharacterTalent.DebugIdentifier}\" - skill increase set to 0."); + } + } + + protected override void ApplyEffect() + { + ApplyEffectSpecific(Character); + } + + protected override void ApplyEffect(AbilityObject abilityObject) + { + if ((abilityObject as IAbilityCharacter)?.Character is Character character) + { + ApplyEffectSpecific(character); + } + else + { + ApplyEffectSpecific(Character); + } + } + + private void ApplyEffectSpecific(Character character) + { + if (skillIdentifier.Equals("random")) + { + var skill = character.Info?.Job?.Skills?.GetRandom(); + if (skill == null) { return; } + character.Info?.IncreaseSkillLevel(skill.Identifier, skillIncrease); + } + else + { + character.Info?.IncreaseSkillLevel(skillIdentifier, skillIncrease); + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyAffliction.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyAffliction.cs new file mode 100644 index 000000000..5d073a068 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyAffliction.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class CharacterAbilityModifyAffliction : CharacterAbility + { + private readonly string[] afflictionIdentifiers; + + private readonly float addedMultiplier; + + public CharacterAbilityModifyAffliction(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) + { + afflictionIdentifiers = abilityElement.GetAttributeStringArray("afflictionidentifiers", new string[0], convertToLowerInvariant: true); + addedMultiplier = abilityElement.GetAttributeFloat("addedmultiplier", 0f); + } + + protected override void ApplyEffect(AbilityObject abilityObject) + { + if ((abilityObject as IAbilityAffliction)?.Affliction is Affliction affliction) + { + foreach (string afflictionIdentifier in afflictionIdentifiers) + { + if (affliction.Identifier == afflictionIdentifier) + { + affliction.Strength *= 1 + addedMultiplier; + } + } + } + else + { + LogabilityObjectMismatch(); + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyAttackData.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyAttackData.cs new file mode 100644 index 000000000..2e742bb3f --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyAttackData.cs @@ -0,0 +1,53 @@ +using System.Collections.Generic; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class CharacterAbilityModifyAttackData : CharacterAbility + { + private readonly List afflictions; + + private readonly float addedDamageMultiplier; + private readonly float addedPenetration; + private readonly bool implode; + + public CharacterAbilityModifyAttackData(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) + { + if (abilityElement.GetChildElement("afflictions") is XElement afflictionElements) + { + afflictions = CharacterAbilityGroup.ParseAfflictions(CharacterTalent, afflictionElements); + } + addedDamageMultiplier = abilityElement.GetAttributeFloat("addeddamagemultiplier", 0f); + addedPenetration = abilityElement.GetAttributeFloat("addedpenetration", 0f); + implode = abilityElement.GetAttributeBool("implode", false); + } + + protected override void ApplyEffect(AbilityObject abilityObject) + { + if (abilityObject is AbilityAttackData attackData) + { + if (attackData.Afflictions == null) + { + attackData.Afflictions = afflictions; + } + else + { + attackData.Afflictions.AddRange(afflictions); + } + attackData.DamageMultiplier += addedDamageMultiplier; + attackData.AddedPenetration += addedPenetration; + + if (implode) + { + // might have issues, as the method used to be private and only used for pressure death + attackData.Character?.Implode(); + } + + } + else + { + LogabilityObjectMismatch(); + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyFlag.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyFlag.cs new file mode 100644 index 000000000..d9953bf23 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyFlag.cs @@ -0,0 +1,34 @@ +using System; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class CharacterAbilityModifyFlag : CharacterAbility + { + private readonly AbilityFlags abilityFlag; + + private bool lastState; + + public CharacterAbilityModifyFlag(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) + { + abilityFlag = CharacterAbilityGroup.ParseFlagType(abilityElement.GetAttributeString("flagtype", ""), CharacterTalent.DebugIdentifier); + } + + protected override void VerifyState(bool conditionsMatched, float timeSinceLastUpdate) + { + if (conditionsMatched != lastState) + { + if (conditionsMatched) + { + Character.AddAbilityFlag(abilityFlag); + } + else + { + Character.RemoveAbilityFlag(abilityFlag); + } + + lastState = conditionsMatched; + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyReduceAffliction.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyReduceAffliction.cs new file mode 100644 index 000000000..affb06085 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyReduceAffliction.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class CharacterAbilityModifyReduceAffliction : CharacterAbility + { + float addedAmountMultiplier; + + public CharacterAbilityModifyReduceAffliction(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) + { + addedAmountMultiplier = abilityElement.GetAttributeFloat("addedamountmultiplier", 0f); + } + + protected override void ApplyEffect(AbilityObject abilityObject) + { + if (abilityObject is AbilityValueAffliction afflictionReduceAmount) + { + afflictionReduceAmount.Affliction.Strength -= addedAmountMultiplier * afflictionReduceAmount.Value; + } + else + { + LogabilityObjectMismatch(); + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyResistance.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyResistance.cs new file mode 100644 index 000000000..4317f6745 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyResistance.cs @@ -0,0 +1,32 @@ +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class CharacterAbilityModifyResistance : CharacterAbility + { + private readonly string resistanceId; + private readonly float resistance; + bool lastState; + + // should probably be split to different classes + public CharacterAbilityModifyResistance(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) + { + resistanceId = abilityElement.GetAttributeString("resistanceid", ""); + resistance = abilityElement.GetAttributeFloat("resistance", 1f); + + if (string.IsNullOrEmpty(resistanceId)) + { + DebugConsole.ThrowError("Error in CharacterAbilityModifyResistance - resistance identifier not set."); + } + } + + public override void UpdateCharacterAbility(bool conditionsMatched, float timeSinceLastUpdate) + { + if (conditionsMatched != lastState) + { + Character.ChangeAbilityResistance(resistanceId, conditionsMatched ? resistance : 1 / resistance); + lastState = conditionsMatched; + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyStat.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyStat.cs new file mode 100644 index 000000000..c61a5a646 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyStat.cs @@ -0,0 +1,26 @@ +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class CharacterAbilityModifyStat : CharacterAbility + { + private readonly StatTypes statType; + private readonly float value; + bool lastState; + + public CharacterAbilityModifyStat(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) + { + statType = CharacterAbilityGroup.ParseStatType(abilityElement.GetAttributeString("stattype", ""), CharacterTalent.DebugIdentifier); + value = abilityElement.GetAttributeFloat("value", 0f); + } + + protected override void VerifyState(bool conditionsMatched, float timeSinceLastUpdate) + { + if (conditionsMatched != lastState) + { + Character.ChangeStat(statType, conditionsMatched ? value : -value); + lastState = conditionsMatched; + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyStatToFlooding.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyStatToFlooding.cs new file mode 100644 index 000000000..20dbf654d --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyStatToFlooding.cs @@ -0,0 +1,34 @@ +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class CharacterAbilityModifyStatToFlooding : CharacterAbility + { + private readonly StatTypes statType; + private readonly float maxValue; + private float lastValue = 0f; + + public CharacterAbilityModifyStatToFlooding(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) + { + statType = CharacterAbilityGroup.ParseStatType(abilityElement.GetAttributeString("stattype", ""), CharacterTalent.DebugIdentifier); + maxValue = abilityElement.GetAttributeFloat("maxvalue", 0f); + } + + protected override void VerifyState(bool conditionsMatched, float timeSinceLastUpdate) + { + Character.ChangeStat(statType, -lastValue); + + if (conditionsMatched && Character.IsInFriendlySub) + { + float currentFloodPercentage = Character.Submarine.GetHulls(false).Average(h => h.WaterPercentage); + lastValue = currentFloodPercentage / 100f * maxValue; + Character.ChangeStat(statType, lastValue); + } + else + { + lastValue = 0f; + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyStatToLevel.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyStatToLevel.cs new file mode 100644 index 000000000..a8630dc2c --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyStatToLevel.cs @@ -0,0 +1,35 @@ +using Microsoft.Xna.Framework; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class CharacterAbilityModifyStatToLevel : CharacterAbility + { + private readonly StatTypes statType; + private readonly float statPerLevel; + private readonly int maxLevel; + private float lastValue = 0f; + + public CharacterAbilityModifyStatToLevel(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) + { + statType = CharacterAbilityGroup.ParseStatType(abilityElement.GetAttributeString("stattype", ""), CharacterTalent.DebugIdentifier); + statPerLevel = abilityElement.GetAttributeFloat("statperlevel", 0f); + maxLevel = abilityElement.GetAttributeInt("maxlevel", int.MaxValue); + } + + protected override void VerifyState(bool conditionsMatched, float timeSinceLastUpdate) + { + Character.ChangeStat(statType, -lastValue); + if (conditionsMatched) + { + int level = MathHelper.Min(Character?.Info.GetCurrentLevel() ?? 0, maxLevel); + lastValue = statPerLevel * level; + Character.ChangeStat(statType, lastValue); + } + else + { + lastValue = 0f; + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyStatToSkill.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyStatToSkill.cs new file mode 100644 index 000000000..44435e95d --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyStatToSkill.cs @@ -0,0 +1,52 @@ +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class CharacterAbilityModifyStatToSkill : CharacterAbility + { + private readonly StatTypes statType; + private readonly float maxValue; + private readonly string skillIdentifier; + private readonly bool useAll; + private float lastValue = 0f; + + public CharacterAbilityModifyStatToSkill(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) + { + statType = CharacterAbilityGroup.ParseStatType(abilityElement.GetAttributeString("stattype", ""), CharacterTalent.DebugIdentifier); + maxValue = abilityElement.GetAttributeFloat("maxvalue", 0f); + skillIdentifier = abilityElement.GetAttributeString("skillidentifier", string.Empty); + useAll = skillIdentifier == "all"; + } + + protected override void VerifyState(bool conditionsMatched, float timeSinceLastUpdate) + { + Character.ChangeStat(statType, -lastValue); + + if (conditionsMatched) + { + float skillTotal = 0f; + + if (useAll && Character.Info?.Job != null) + { + foreach (Skill skill in Character.Info.Job.Skills) + { + skillTotal += Character.GetSkillLevel(skill.Identifier); + } + skillTotal /= Character.Info.Job.Skills.Count; + } + else + { + skillTotal = Character.GetSkillLevel(skillIdentifier); + } + + lastValue = skillTotal / 100f * maxValue; + Character.ChangeStat(statType, lastValue); + } + else + { + lastValue = 0f; + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyValue.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyValue.cs new file mode 100644 index 000000000..59a203a7f --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyValue.cs @@ -0,0 +1,25 @@ +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class CharacterAbilityModifyValue : CharacterAbility + { + private readonly float addedValue; + private readonly float multiplyValue; + + public CharacterAbilityModifyValue(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) + { + addedValue = abilityElement.GetAttributeFloat("addedvalue", 0f); + multiplyValue = abilityElement.GetAttributeFloat("multiplyvalue", 1f); + } + + protected override void ApplyEffect(AbilityObject abilityObject) + { + if (abilityObject is IAbilityValue abilityValue) + { + abilityValue.Value += addedValue; + abilityValue.Value *= multiplyValue; + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityPutItem.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityPutItem.cs new file mode 100644 index 000000000..e80bf63db --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityPutItem.cs @@ -0,0 +1,46 @@ +using Microsoft.Xna.Framework; +using System.Collections.Generic; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class CharacterAbilityPutItem : CharacterAbility + { + private readonly string itemIdentifier; + private readonly int amount; + public override bool AppliesEffectOnIntervalUpdate => true; + public CharacterAbilityPutItem(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) + { + itemIdentifier = abilityElement.GetAttributeString("itemidentifier", ""); + amount = abilityElement.GetAttributeInt("amount", 1); + } + + protected override void ApplyEffect() + { + if (string.IsNullOrEmpty(itemIdentifier)) + { + DebugConsole.ThrowError("Cannot put item in inventory - itemIdentifier not defined."); + return; + } + + ItemPrefab itemPrefab = ItemPrefab.Find(null, itemIdentifier); + if (itemPrefab == null) + { + DebugConsole.ThrowError("Cannot put item in inventory - item prefab " + itemIdentifier + " not found."); + return; + } + for (int i = 0; i < amount; i++) + { + if (GameMain.GameSession?.RoundEnding ?? true) + { + Item item = new Item(itemPrefab, Character.WorldPosition, Character.Submarine); + Character.Inventory.TryPutItem(item, Character, new List() { InvSlotType.Any }); + } + else + { + Entity.Spawner.AddToSpawnQueue(itemPrefab, Character.Inventory); + } + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityResetPermanentStat.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityResetPermanentStat.cs new file mode 100644 index 000000000..3716a6fbc --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityResetPermanentStat.cs @@ -0,0 +1,30 @@ +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class CharacterAbilityResetPermanentStat : CharacterAbility + { + private readonly string statIdentifier; + public override bool AppliesEffectOnIntervalUpdate => true; + public override bool AllowClientSimulation => true; + + public CharacterAbilityResetPermanentStat(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) + { + statIdentifier = abilityElement.GetAttributeString("statidentifier", "").ToLowerInvariant(); + } + protected override void ApplyEffect(AbilityObject abilityObject) + { + ApplyEffectSpecific(); + } + + protected override void ApplyEffect() + { + ApplyEffectSpecific(); + } + + private void ApplyEffectSpecific() + { + Character?.Info.ResetSavedStatValue(statIdentifier); + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityRevive.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityRevive.cs new file mode 100644 index 000000000..7ed61e90f --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityRevive.cs @@ -0,0 +1,29 @@ +using Microsoft.Xna.Framework; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class CharacterAbilityRevive : CharacterAbility + { + public override bool AppliesEffectOnIntervalUpdate => true; + + public CharacterAbilityRevive(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) + { + } + + private void ApplyEffectSpecific() + { + Character.Revive(removeAllAfflictions: false); + } + + protected override void ApplyEffect() + { + ApplyEffectSpecific(); + } + + protected override void ApplyEffect(AbilityObject abilityObject) + { + ApplyEffectSpecific(); + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilitySpawnItemsToContainer.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilitySpawnItemsToContainer.cs new file mode 100644 index 000000000..b13e97638 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilitySpawnItemsToContainer.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class CharacterAbilitySpawnItemsToContainer : CharacterAbility + { + // currently used only for spawning items to containers + + private readonly List statusEffects; + private readonly List openedContainers = new List(); + private readonly float randomChance; + private readonly bool oncePerContainer; + + public CharacterAbilitySpawnItemsToContainer(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) + { + statusEffects = CharacterAbilityGroup.ParseStatusEffects(CharacterTalent, abilityElement.GetChildElement("statuseffects")); + randomChance = abilityElement.GetAttributeFloat("randomchance", 1f); + oncePerContainer = abilityElement.GetAttributeBool("oncepercontainer", false); + } + + protected override void ApplyEffect(AbilityObject abilityObject) + { + if ((abilityObject as IAbilityItem)?.Item is Item item) + { + if (oncePerContainer) + { + if (openedContainers.Contains(item)) { return; } + openedContainers.Add(item); + } + if (randomChance < Rand.Range(0f, 1f, Rand.RandSync.Unsynced)) { return; } + + foreach (var statusEffect in statusEffects) + { + statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, item, item); + } + } + else + { + LogabilityObjectMismatch(); + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityUnlockTree.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityUnlockTree.cs new file mode 100644 index 000000000..b9f26160c --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityUnlockTree.cs @@ -0,0 +1,31 @@ +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class CharacterAbilityUnlockTree : CharacterAbility + { + public CharacterAbilityUnlockTree(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) + { + } + + public override void InitializeAbility(bool addingFirstTime) + { + if (!addingFirstTime) { return; } + if (!TalentTree.JobTalentTrees.TryGetValue(Character.Info.Job.Prefab.Identifier, out TalentTree talentTree)) { return; } + + var subTree = talentTree.TalentSubTrees.Find(t => t.TalentOptionStages.Any(ts => ts.Talents.Contains(CharacterTalent.Prefab))); + if (subTree != null) + { + foreach (var talentOption in subTree.TalentOptionStages) + { + foreach (var talent in talentOption.Talents) + { + if (talent == CharacterTalent.Prefab) { continue; } + Character.GiveTalent(talent); + } + } + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityAlienHoarder.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityAlienHoarder.cs new file mode 100644 index 000000000..7c7141a25 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityAlienHoarder.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class CharacterAbilityAlienHoarder : CharacterAbility + { + private readonly float addedDamageMultiplierPerItem; + private readonly float maxAddedDamageMultiplier; + private readonly string[] tags; + + public CharacterAbilityAlienHoarder(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) + { + addedDamageMultiplierPerItem = abilityElement.GetAttributeFloat("addeddamagemultiplierperitem", 0f); + maxAddedDamageMultiplier = abilityElement.GetAttributeFloat("maxaddedddamagemultiplier", float.MaxValue); + tags = abilityElement.GetAttributeStringArray("tags", Array.Empty(), convertToLowerInvariant: true); + } + + protected override void ApplyEffect(AbilityObject abilityObject) + { + if (abilityObject is AbilityAttackData attackData) + { + float totalAddedDamageMultiplier = 0f; + foreach (Item item in Character.Inventory.AllItems) + { + if (tags.Any(t => item.Prefab.Tags.Any(p => t == p))) + { + totalAddedDamageMultiplier += addedDamageMultiplierPerItem; + } + } + attackData.DamageMultiplier += Math.Min(totalAddedDamageMultiplier, maxAddedDamageMultiplier); + } + else + { + LogabilityObjectMismatch(); + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityApprenticeship.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityApprenticeship.cs new file mode 100644 index 000000000..95466f08d --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityApprenticeship.cs @@ -0,0 +1,21 @@ +using Microsoft.Xna.Framework; +using System; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class CharacterAbilityApprenticeship : CharacterAbility + { + public CharacterAbilityApprenticeship(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) + { + } + + protected override void ApplyEffect(AbilityObject abilityObject) + { + if (abilityObject is AbilitySkillGain abilitySkillGain && !abilitySkillGain.GainedFromApprenticeship && abilitySkillGain.Character != Character) + { + Character.Info?.IncreaseSkillLevel(abilitySkillGain.String, 1.0f, gainedFromApprenticeship: true); + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityAtmosMachine.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityAtmosMachine.cs new file mode 100644 index 000000000..5ab9361b8 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityAtmosMachine.cs @@ -0,0 +1,43 @@ +using System; +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class CharacterAbilityAtmosMachine : CharacterAbility + { + private readonly float addedValue; + private readonly float multiplyValue; + private readonly string[] tags; + private readonly int maxMultiplyCount; + + public CharacterAbilityAtmosMachine(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) + { + addedValue = abilityElement.GetAttributeFloat("addedvalue", 0f); + multiplyValue = abilityElement.GetAttributeFloat("multiplyvalue", 1f); + tags = abilityElement.GetAttributeStringArray("tags", Array.Empty(), convertToLowerInvariant: true); + maxMultiplyCount = abilityElement.GetAttributeInt("maxmultiplycount", int.MaxValue); + } + + protected override void ApplyEffect(AbilityObject abilityObject) + { + if (abilityObject is IAbilityValue abilityValue) + { + int multiplyCount = 0; + + foreach (Item item in Item.ItemList) + { + if (item.Prefab.Tags.Any(t => tags.Contains(t))) + { + multiplyCount++; + if (multiplyCount == maxMultiplyCount) + { + break; + } + } + } + abilityValue.Value += addedValue * multiplyCount; + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityBountyHunter.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityBountyHunter.cs new file mode 100644 index 000000000..417b93972 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityBountyHunter.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class CharacterAbilityBountyHunter : CharacterAbility + { + private float vitalityPercentage; + + public CharacterAbilityBountyHunter(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) + { + vitalityPercentage = abilityElement.GetAttributeFloat("vitalitypercentage", 0f); + } + + protected override void ApplyEffect(AbilityObject abilityObject) + { + if ((abilityObject as IAbilityCharacter)?.Character is Character character) + { + Character.GiveMoney((int)(vitalityPercentage * character.MaxVitality)); + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityByTheBook.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityByTheBook.cs new file mode 100644 index 000000000..8bfe07b9d --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityByTheBook.cs @@ -0,0 +1,42 @@ +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class CharacterAbilityByTheBook : CharacterAbility + { + private readonly int moneyAmount; + private readonly int experienceAmount; + private readonly int max; + + public CharacterAbilityByTheBook(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) + { + moneyAmount = abilityElement.GetAttributeInt("moneyamount", 0); + experienceAmount = abilityElement.GetAttributeInt("experienceamount", 0); + max = abilityElement.GetAttributeInt("max", 0); + } + + protected override void ApplyEffect() + { + IEnumerable enemyCharacters = Character.CharacterList.Where(c => c.TeamID == CharacterTeamType.None); + + int timesGiven = 0; + foreach (Character enemyCharacter in enemyCharacters) + { + if (!enemyCharacter.IsHuman) { continue; } + if (enemyCharacter.Submarine == null || enemyCharacter.Submarine != Submarine.MainSub) { continue; } + if (enemyCharacter.IsDead) { continue; } + if (!enemyCharacter.LockHands) { continue; } + if (timesGiven > max) { continue; } + Character.GiveMoney(moneyAmount); + foreach (Character character in Character.GetFriendlyCrew(Character)) + { + character.Info?.GiveExperience(experienceAmount); + } + timesGiven++; + } + + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityInsurancePolicy.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityInsurancePolicy.cs new file mode 100644 index 000000000..1583bd53b --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityInsurancePolicy.cs @@ -0,0 +1,31 @@ +using Barotrauma.Networking; +using Microsoft.Xna.Framework; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class CharacterAbilityInsurancePolicy : CharacterAbility + { + public override bool AppliesEffectOnIntervalUpdate => true; + + private readonly int moneyPerMission; + + private static List clientsAlreadyUsed = new List(); + + public CharacterAbilityInsurancePolicy(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) + { + moneyPerMission = abilityElement.GetAttributeInt("moneypermission", 0); + } + + protected override void ApplyEffect() + { + if (Character?.Info is CharacterInfo info) + { + + Character.GiveMoney(moneyPerMission * info.MissionsCompletedSinceDeath); + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityMultitasker.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityMultitasker.cs new file mode 100644 index 000000000..339b5c47f --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityMultitasker.cs @@ -0,0 +1,26 @@ +using Microsoft.Xna.Framework; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class CharacterAbilityMultitasker : CharacterAbility + { + private string lastSkillIdentifier; + + public CharacterAbilityMultitasker(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) + { + } + + protected override void ApplyEffect(AbilityObject abilityObject) + { + if ((abilityObject as IAbilityString)?.String is string skillIdentifier) + { + if (skillIdentifier != lastSkillIdentifier) + { + lastSkillIdentifier = skillIdentifier; + Character.Info?.IncreaseSkillLevel(skillIdentifier, 1.0f); + } + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityPsychoClown.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityPsychoClown.cs new file mode 100644 index 000000000..61e9d9cf6 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityPsychoClown.cs @@ -0,0 +1,44 @@ +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class CharacterAbilityPsychoClown : CharacterAbility + { + private StatTypes statType; + private float maxValue; + private string afflictionIdentifier; + private float lastValue = 0f; + + public CharacterAbilityPsychoClown(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) + { + statType = CharacterAbilityGroup.ParseStatType(abilityElement.GetAttributeString("stattype", ""), CharacterTalent.DebugIdentifier); + maxValue = abilityElement.GetAttributeFloat("maxvalue", 0f); + afflictionIdentifier = abilityElement.GetAttributeString("afflictionidentifier", ""); + } + + protected override void VerifyState(bool conditionsMatched, float timeSinceLastUpdate) + { + // managing state this way seems liable to cause bugs, maybe instead create abstraction to reset these values more safely + // talents cannot be removed while in active play because of the lack of this, for example + Character.ChangeStat(statType, -lastValue); + + if (conditionsMatched) + { + var affliction = Character.CharacterHealth.GetAffliction(afflictionIdentifier); + + float afflictionStrength = 0f; + if (affliction != null) + { + afflictionStrength = affliction.Strength / affliction.Prefab.MaxStrength; + } + + lastValue = afflictionStrength * maxValue; + Character.ChangeStat(statType, lastValue); + } + else + { + lastValue = 0f; + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityRegenerateLoot.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityRegenerateLoot.cs new file mode 100644 index 000000000..574d9d7b1 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityRegenerateLoot.cs @@ -0,0 +1,39 @@ +using Barotrauma.Items.Components; +using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class CharacterAbilityRegenerateLoot : CharacterAbility + { + // separate random chance used for the ability itself to prevent the player + // from opening/reopening a container until it spawns loot + private readonly float randomChance; + + // not maintained through death, so it's possible for players to respawn and re-loot chests + // seems like a minor issue for now + private readonly List openedContainers = new List(); + + public CharacterAbilityRegenerateLoot(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) + { + randomChance = abilityElement.GetAttributeFloat("randomchance", 1f); + } + + protected override void ApplyEffect(AbilityObject abilityObject) + { + if ((abilityObject as IAbilityItem)?.Item is Item item) + { + if (openedContainers.Contains(item)) { return; } + openedContainers.Add(item); + if (randomChance < Rand.Range(0f, 1f, Rand.RandSync.Unsynced)) { return; } + + if (item.GetComponent() is ItemContainer itemContainer) + { + AutoItemPlacer.RegenerateLoot(item.Submarine, itemContainer); + } + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityTandemFire.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityTandemFire.cs new file mode 100644 index 000000000..75668b0ca --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityTandemFire.cs @@ -0,0 +1,43 @@ +using Barotrauma.Items.Components; +using Microsoft.Xna.Framework; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class CharacterAbilityTandemFire : CharacterAbilityApplyStatusEffectsToNearestAlly + { + // this should just be its own class, misleading to inherit here + private string tag; + public CharacterAbilityTandemFire(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) + { + tag = abilityElement.GetAttributeString("tag", ""); + } + + protected override void ApplyEffect() + { + if (Character.SelectedConstruction == null || !Character.SelectedConstruction.HasTag(tag)) { return; } + + Character closestCharacter = null; + float closestDistance = float.MaxValue; + + foreach (Character crewCharacter in Character.GetFriendlyCrew(Character)) + { + if (crewCharacter != Character && Vector2.DistanceSquared(Character.SimPosition, Character.GetRelativeSimPosition(crewCharacter)) is float tempDistance && tempDistance < closestDistance) + { + closestCharacter = crewCharacter; + closestDistance = tempDistance; + } + } + + if (closestCharacter.SelectedConstruction == null || !closestCharacter.SelectedConstruction.HasTag(tag)) { return; } + + if (closestDistance < squaredMaxDistance) + { + ApplyEffectSpecific(Character); + ApplyEffectSpecific(closestCharacter); + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroup.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroup.cs new file mode 100644 index 000000000..7c96b3d17 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroup.cs @@ -0,0 +1,222 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + abstract class CharacterAbilityGroup + { + public CharacterTalent CharacterTalent { get; } + public Character Character { get; } + + // currently only used to turn off simulation if random conditions are in use + public bool IsActive { get; private set; } = true; + + public readonly AbilityEffectType AbilityEffectType; + + protected int maxTriggerCount { get; } + protected int timesTriggered = 0; + + + // add support for OR conditions? + protected readonly List abilityConditions = new List(); + + // separate dictionaries for each type of characterability? + protected readonly List characterAbilities = new List(); + + public CharacterAbilityGroup(AbilityEffectType abilityEffectType, CharacterTalent characterTalent, XElement abilityElementGroup) + { + AbilityEffectType = abilityEffectType; + CharacterTalent = characterTalent; + Character = CharacterTalent.Character; + maxTriggerCount = abilityElementGroup.GetAttributeInt("maxtriggercount", int.MaxValue); + foreach (XElement subElement in abilityElementGroup.Elements()) + { + switch (subElement.Name.ToString().ToLowerInvariant()) + { + case "abilities": + LoadAbilities(subElement); + break; + case "conditions": + LoadConditions(subElement); + break; + } + } + } + + public void ActivateAbilityGroup(bool addingFirstTime) + { + foreach (var characterAbility in characterAbilities) + { + characterAbility.InitializeAbility(addingFirstTime); + } + } + + public void LoadConditions(XElement conditionElements) + { + foreach (XElement conditionElement in conditionElements.Elements()) + { + AbilityCondition newCondition = ConstructCondition(CharacterTalent, conditionElement); + + if (newCondition == null) + { + DebugConsole.ThrowError($"AbilityCondition was not found in talent {CharacterTalent.DebugIdentifier}!"); + return; + } + + if (!newCondition.AllowClientSimulation && GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) + { + IsActive = false; + } + + abilityConditions.Add(newCondition); + } + } + + public void AddAbility(CharacterAbility characterAbility) + { + if (characterAbility == null) + { + DebugConsole.ThrowError($"Trying to add null ability for talent {CharacterTalent.DebugIdentifier}!"); + return; + } + + characterAbilities.Add(characterAbility); + } + + // XML + private AbilityCondition ConstructCondition(CharacterTalent characterTalent, XElement conditionElement, bool errorMessages = true) + { + AbilityCondition newCondition = null; + + Type conditionType; + string type = conditionElement.Name.ToString().ToLowerInvariant(); + try + { + conditionType = Type.GetType("Barotrauma.Abilities." + type + "", false, true); + if (conditionType == null) + { + if (errorMessages) DebugConsole.ThrowError("Could not find the component \"" + type + "\" (" + characterTalent.DebugIdentifier + ")"); + return null; + } + } + catch (Exception e) + { + if (errorMessages) DebugConsole.ThrowError("Could not find the component \"" + type + "\" (" + characterTalent.DebugIdentifier + ")", e); + return null; + } + + object[] args = { characterTalent, conditionElement }; + + try + { + newCondition = (AbilityCondition)Activator.CreateInstance(conditionType, args); + } + catch (TargetInvocationException e) + { + DebugConsole.ThrowError("Error while creating an instance of an ability condition of the type " + conditionType + ".", e.InnerException); + return null; + } + + if (newCondition == null) + { + DebugConsole.ThrowError("Error while creating an instance of an ability condition of the type " + conditionType + ", instance was null"); + return null; + } + + return newCondition; + } + + private void LoadAbilities(XElement abilityElements) + { + foreach (XElement abilityElementGroup in abilityElements.Elements()) + { + AddAbility(ConstructAbility(abilityElementGroup, CharacterTalent)); + } + } + + private CharacterAbility ConstructAbility(XElement abilityElement, CharacterTalent characterTalent) + { + CharacterAbility newAbility = CharacterAbility.Load(abilityElement, this); + + if (newAbility == null) + { + DebugConsole.ThrowError($"Unable to create an ability for {characterTalent.DebugIdentifier}!"); + return null; + } + + return newAbility; + } + + public static List ParseStatusEffects(CharacterTalent characterTalent, XElement statusEffectElements) + { + if (statusEffectElements == null) + { + DebugConsole.ThrowError("StatusEffect list was not found in talent " + characterTalent.DebugIdentifier); + return null; + } + + List statusEffects = new List(); + + foreach (XElement statusEffectElement in statusEffectElements.Elements()) + { + var statusEffect = StatusEffect.Load(statusEffectElement, characterTalent.DebugIdentifier); + statusEffects.Add(statusEffect); + } + + return statusEffects; + } + + public static StatTypes ParseStatType(string statTypeString, string debugIdentifier) + { + if (!Enum.TryParse(statTypeString, true, out StatTypes statType)) + { + DebugConsole.ThrowError("Invalid stat type type \"" + statTypeString + "\" in CharacterTalent (" + debugIdentifier + ")"); + } + return statType; + } + + public static List ParseAfflictions(CharacterTalent characterTalent, XElement afflictionElements) + { + if (afflictionElements == null) + { + DebugConsole.ThrowError("Affliction list was not found in talent " + characterTalent.DebugIdentifier); + return null; + } + + List afflictions = new List(); + + // similar logic to affliction creation in statuseffects + // might be worth unifying + + foreach (XElement afflictionElement in afflictionElements.Elements()) + { + string afflictionIdentifier = afflictionElement.GetAttributeString("identifier", "").ToLowerInvariant(); + AfflictionPrefab afflictionPrefab = AfflictionPrefab.List.FirstOrDefault(ap => ap.Identifier.ToLowerInvariant() == afflictionIdentifier); + if (afflictionPrefab == null) + { + DebugConsole.ThrowError("Error in CharacterTalent (" + characterTalent.DebugIdentifier + ") - Affliction prefab with the identifier \"" + afflictionIdentifier + "\" not found."); + continue; + } + + Affliction afflictionInstance = afflictionPrefab.Instantiate(afflictionElement.GetAttributeFloat(1.0f, "amount", "strength")); + afflictionInstance.Probability = afflictionElement.GetAttributeFloat(1.0f, "probability"); + afflictions.Add(afflictionInstance); + } + + return afflictions; + } + + public static AbilityFlags ParseFlagType(string flagTypeString, string debugIdentifier) + { + AbilityFlags flagType = AbilityFlags.None; + if (!Enum.TryParse(flagTypeString, true, out flagType)) + { + DebugConsole.ThrowError("Invalid flag type type \"" + flagTypeString + "\" in CharacterTalent (" + debugIdentifier + ")"); + } + return flagType; + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroupEffect.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroupEffect.cs new file mode 100644 index 000000000..e4d488103 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroupEffect.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class CharacterAbilityGroupEffect : CharacterAbilityGroup + { + public CharacterAbilityGroupEffect(AbilityEffectType abilityEffectType, CharacterTalent characterTalent, XElement abilityElementGroup) : + base(abilityEffectType, characterTalent, abilityElementGroup) { } + + public void CheckAbilityGroup(AbilityObject abilityObject) + { + if (!IsActive) { return; } + if (IsApplicable(abilityObject)) + { + foreach (var characterAbility in characterAbilities) + { + if (characterAbility.IsViable()) + { + characterAbility.ApplyAbilityEffect(abilityObject); + } + } + timesTriggered++; + } + } + + private bool IsApplicable(AbilityObject abilityObject) + { + if (timesTriggered >= maxTriggerCount) { return false; } + return abilityConditions.All(c => c.MatchesCondition(abilityObject)); + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroupInterval.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroupInterval.cs new file mode 100644 index 000000000..c7a302149 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroupInterval.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class CharacterAbilityGroupInterval : CharacterAbilityGroup + { + private float interval { get; set; } + public float TimeSinceLastUpdate { get; private set; } + + private float effectDelay; + private float effectDelayTimer; + + + public CharacterAbilityGroupInterval(AbilityEffectType abilityEffectType, CharacterTalent characterTalent, XElement abilityElementGroup) : + base(abilityEffectType, characterTalent, abilityElementGroup) + { + // too many overlapping intervals could cause hitching? maybe randomize a little + interval = abilityElementGroup.GetAttributeFloat("interval", 0f); + effectDelay = abilityElementGroup.GetAttributeFloat("effectdelay", 0f); + } + public void UpdateAbilityGroup(float deltaTime) + { + if (!IsActive) { return; } + TimeSinceLastUpdate += deltaTime; + if (TimeSinceLastUpdate >= interval) + { + bool conditionsMatched = IsApplicable(); + effectDelayTimer = conditionsMatched ? effectDelayTimer + TimeSinceLastUpdate : 0f; + conditionsMatched &= effectDelayTimer >= effectDelay; + + foreach (var characterAbility in characterAbilities) + { + if (characterAbility.IsViable()) + { + characterAbility.UpdateCharacterAbility(conditionsMatched, TimeSinceLastUpdate); + } + } + if (conditionsMatched) + { + timesTriggered++; + } + TimeSinceLastUpdate = 0; + } + } + private bool IsApplicable() + { + if (timesTriggered >= maxTriggerCount) { return false; } + return abilityConditions.All(c => c.MatchesCondition()); + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/CharacterTalent.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/CharacterTalent.cs new file mode 100644 index 000000000..3a79e1b2a --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/CharacterTalent.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; +using Barotrauma.Abilities; + +namespace Barotrauma +{ + class CharacterTalent + { + public Character Character { get; } + public string DebugIdentifier { get; } + + public readonly TalentPrefab Prefab; + + public bool AddedThisRound = true; + + private readonly Dictionary> characterAbilityGroupEffectDictionary = new Dictionary>(); + + private readonly List characterAbilityGroupIntervals = new List(); + + // works functionally but a missing recipe is not represented on GUI side. this might be better placed in the character class itself, though it might be fine here as well + public List UnlockedRecipes { get; } = new List(); + + public CharacterTalent(TalentPrefab talentPrefab, Character character) + { + Character = character; + + Prefab = talentPrefab; + XElement element = talentPrefab.ConfigElement; + DebugIdentifier = talentPrefab.OriginalName; + + foreach (XElement subElement in element.Elements()) + { + switch (subElement.Name.ToString().ToLowerInvariant()) + { + case "abilitygroupeffect": + LoadAbilityGroupEffect(subElement); + break; + case "abilitygroupinterval": + LoadAbilityGroupInterval(subElement); + break; + case "addedrecipe": + if (subElement.GetAttributeString("itemidentifier", string.Empty) is string recipeIdentifier && recipeIdentifier != string.Empty) + { + UnlockedRecipes.Add(recipeIdentifier); + } + else + { + DebugConsole.ThrowError("No recipe identifier defined for talent " + DebugIdentifier); + } + break; + } + } + } + + public virtual void UpdateTalent(float deltaTime) + { + foreach (var characterAbilityGroupInterval in characterAbilityGroupIntervals) + { + characterAbilityGroupInterval.UpdateAbilityGroup(deltaTime); + } + } + + public void CheckTalent(AbilityEffectType abilityEffectType, AbilityObject abilityObject) + { + if (characterAbilityGroupEffectDictionary.TryGetValue(abilityEffectType, out var characterAbilityGroups)) + { + foreach (var characterAbilityGroup in characterAbilityGroups) + { + characterAbilityGroup.CheckAbilityGroup(abilityObject); + } + } + } + + public void ActivateTalent(bool addingFirstTime) + { + foreach (var characterAbilityGroups in characterAbilityGroupEffectDictionary.Values) + { + foreach (var characterAbilityGroup in characterAbilityGroups) + { + characterAbilityGroup.ActivateAbilityGroup(addingFirstTime); + } + } + } + + // XML logic + private void LoadAbilityGroupInterval(XElement abilityGroup) + { + characterAbilityGroupIntervals.Add(new CharacterAbilityGroupInterval(AbilityEffectType.Undefined, this, abilityGroup)); + } + + private void LoadAbilityGroupEffect(XElement abilityGroup) + { + AbilityEffectType abilityEffectType = ParseAbilityEffectType(this, abilityGroup.GetAttributeString("abilityeffecttype", "none")); + AddAbilityGroupEffect(new CharacterAbilityGroupEffect(abilityEffectType, this, abilityGroup), abilityEffectType); + } + + public void AddAbilityGroupEffect(CharacterAbilityGroupEffect characterAbilityGroup, AbilityEffectType abilityEffectType = AbilityEffectType.None) + { + if (characterAbilityGroupEffectDictionary.TryGetValue(abilityEffectType, out var characterAbilityList)) + { + characterAbilityList.Add(characterAbilityGroup); + } + else + { + List characterAbilityGroups = new List(); + characterAbilityGroups.Add(characterAbilityGroup); + characterAbilityGroupEffectDictionary.Add(abilityEffectType, characterAbilityGroups); + } + } + + public static AbilityEffectType ParseAbilityEffectType(CharacterTalent characterTalent, string abilityEffectTypeString) + { + if (!Enum.TryParse(abilityEffectTypeString, true, out AbilityEffectType abilityEffectType)) + { + DebugConsole.ThrowError("Invalid ability effect type \"" + abilityEffectTypeString + "\" in CharacterTalent (" + characterTalent.DebugIdentifier + ")"); + } + if (abilityEffectType == AbilityEffectType.Undefined) + { + DebugConsole.ThrowError("Ability effect type not defined in CharacterTalent (" + characterTalent.DebugIdentifier + ")"); + } + + return abilityEffectType; + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/TalentPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/TalentPrefab.cs new file mode 100644 index 000000000..277af65b0 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/TalentPrefab.cs @@ -0,0 +1,147 @@ +using System; +using System.Collections.Generic; +using System.Xml.Linq; + +namespace Barotrauma +{ + class TalentPrefab : IPrefab, IDisposable, IHasUintIdentifier + { + public string Identifier { get; private set; } + public string OriginalName => Identifier; + public ContentPackage ContentPackage { get; private set; } + public string FilePath { get; private set; } + + public string DisplayName { get; private set; } + + public string Description { get; private set; } + + public readonly Sprite Icon; + + public static readonly PrefabCollection TalentPrefabs = new PrefabCollection(); + + public XElement ConfigElement + { + get; + private set; + } + + public TalentPrefab(XElement element, string filePath) + { + FilePath = filePath; + ConfigElement = element; + Identifier = element.GetAttributeString("identifier", "noidentifier"); + DisplayName = TextManager.Get("talentname." + Identifier, returnNull: true) ?? Identifier; + this.CalculatePrefabUIntIdentifier(TalentPrefabs); + + foreach (XElement subElement in element.Elements()) + { + switch (subElement.Name.ToString().ToLowerInvariant()) + { + case "icon": + Icon = new Sprite(subElement); + break; + case "description": + string tempDescription = Description; + TextManager.ConstructDescription(ref tempDescription, subElement); + Description = tempDescription; + break; + } + } + + if (string.IsNullOrEmpty(Description)) + { + if (element.Attribute("description") != null) + { + string description = element.GetAttributeString("description", string.Empty); + Description = TextManager.Get(description, returnNull: true) ?? description; + } + else + { + Description = TextManager.Get("talentdescription." + Identifier, returnNull: true) ?? string.Empty; + } + } + +#if DEBUG + if (!TextManager.ContainsTag("talentname." + Identifier)) + { + DebugConsole.AddWarning($"Name for the talent \"{Identifier}\" not found in the text files."); + } + if (string.IsNullOrEmpty(Description)) + { + DebugConsole.AddWarning($"Description for the talent \"{Identifier}\" not configured"); + } + if (Description.Contains('[')) + { + DebugConsole.ThrowError($"Description for the talent \"{Identifier}\" contains brackets - was some variable not replaced correctly? ({Description})"); + } +#endif + } + + private bool disposed = false; + public void Dispose() + { + if (disposed) { return; } + disposed = true; + TalentPrefabs.Remove(this); + } + + /// + /// Unique identifier that's generated by hashing the prefab's string identifier. + /// Used to reduce the amount of bytes needed to write talent data into network messages in multiplayer. + /// + public uint UIntIdentifier { get; set; } + + public static void RemoveByFile(string filePath) => TalentPrefabs.RemoveByFile(filePath); + + public static void LoadFromFile(ContentFile file) + { + DebugConsole.Log("Loading talent prefab: " + file.Path); + RemoveByFile(file.Path); + + XDocument doc = XMLExtensions.TryLoadXml(file.Path); + if (doc == null) { return; } + + var rootElement = doc.Root; + switch (rootElement.Name.ToString().ToLowerInvariant()) + { + case "talent": + TalentPrefabs.Add(new TalentPrefab(rootElement, file.Path), false); + break; + case "talents": + foreach (var element in rootElement.Elements()) + { + if (element.IsOverride()) + { + var itemElement = element.GetChildElement("talent"); + if (itemElement != null) + { + TalentPrefabs.Add(new TalentPrefab(rootElement, file.Path), true); + } + else + { + DebugConsole.ThrowError($"Cannot find a talent element from the children of the override element defined in {file.Path}"); + } + } + else + { + TalentPrefabs.Add(new TalentPrefab(element, file.Path), false); + } + } + break; + default: + DebugConsole.ThrowError($"Invalid XML root element: '{rootElement.Name.ToString()}' in {file.Path}"); + break; + } + } + + public static void LoadAll(IEnumerable files) + { + DebugConsole.Log("Loading talent prefabs: "); + + foreach (ContentFile file in files) + { + LoadFromFile(file); + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/TalentTree.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/TalentTree.cs new file mode 100644 index 000000000..02377cba0 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/TalentTree.cs @@ -0,0 +1,276 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma +{ + class TalentTree + { + public enum TalentTreeStageState + { + Invalid, + Locked, + Unlocked, + Available, + Highlighted + } + + public static readonly Dictionary JobTalentTrees = new Dictionary(); + + public readonly List TalentSubTrees = new List(); + + public XElement ConfigElement + { + get; + private set; + } + + public TalentTree(XElement element, string filePath) + { + ConfigElement = element; + + string jobIdentifier = element.GetAttributeString("jobidentifier", "").ToLowerInvariant(); + + if (string.IsNullOrEmpty(jobIdentifier)) + { + DebugConsole.ThrowError($"No job defined for talent tree in \"{filePath}\"!"); + return; + } + + foreach (XElement subTreeElement in element.GetChildElements("subtree")) + { + TalentSubTrees.Add(new TalentSubTree(subTreeElement)); + } + + // talents found and unlocked using the identifier wihin the talent tree, so no duplicates may occur + HashSet duplicateSet = new HashSet(); + foreach (string talent in TalentSubTrees.SelectMany(s => s.TalentOptionStages.SelectMany(o => o.Talents.Select(t => t.Identifier)))) + { + TalentPrefab talentPrefab = TalentPrefab.TalentPrefabs.Find(c => c.Identifier.Equals(talent, StringComparison.OrdinalIgnoreCase)); + if (talentPrefab == null) + { + DebugConsole.AddWarning($"Talent tree for job {jobIdentifier} contains non-existent talent {talent}! Talent tree not added."); + return; + } + if (!duplicateSet.Add(talent)) + { + DebugConsole.ThrowError($"Talent tree for job {jobIdentifier} contains duplicate talent {talent}! Talent tree not added."); + return; + } + } + + if (!JobTalentTrees.TryAdd(jobIdentifier, this)) + { + DebugConsole.ThrowError($"Could not add talent tree for job {jobIdentifier}! A talent tree for this job is already likely defined"); + } + } + + public bool TalentIsInTree(string talentIdentifier) + { + return TalentSubTrees.SelectMany(s => s.TalentOptionStages.SelectMany(o => o.Talents.Select(t => t.Identifier))).Any(c => c == talentIdentifier); + } + + public static void LoadFromFile(ContentFile file) + { + DebugConsole.Log("Loading talent tree: " + file.Path); + + XDocument doc = XMLExtensions.TryLoadXml(file.Path); + if (doc == null) { return; } + + var rootElement = doc.Root; + switch (rootElement.Name.ToString().ToLowerInvariant()) + { + case "talenttree": + new TalentTree(rootElement, file.Path); + break; + case "talenttrees": + foreach (var element in rootElement.Elements()) + { + if (element.IsOverride()) + { + var treeElement = element.GetChildElement("talenttree"); + if (treeElement != null) + { + new TalentTree(rootElement, file.Path); + } + else + { + DebugConsole.ThrowError($"Cannot find a talent tree element from the children of the override element defined in {file.Path}"); + } + } + else + { + new TalentTree(element, file.Path); + } + } + break; + default: + DebugConsole.ThrowError($"Invalid XML root element: '{rootElement.Name}' in {file.Path}"); + break; + } + } + + public static void LoadAll(IEnumerable files) + { + DebugConsole.Log("Loading talent tree: "); + + foreach (ContentFile file in files) + { + LoadFromFile(file); + } + } + + public static bool IsViableTalentForCharacter(Character character, string talentIdentifier) + { + return IsViableTalentForCharacter(character, talentIdentifier, character?.Info?.UnlockedTalents ?? Enumerable.Empty()); + } + + // i hate this function - markus + public static TalentTreeStageState GetTalentOptionStageState(Character character, string subTreeIdentifier, int index, List selectedTalents) + { + if (character?.Info?.Job.Prefab is null) { return TalentTreeStageState.Invalid; } + + if (!JobTalentTrees.TryGetValue(character.Info.Job.Prefab.Identifier, out TalentTree talentTree)) { return TalentTreeStageState.Invalid; } + + TalentSubTree subTree = talentTree.TalentSubTrees.FirstOrDefault(tst => tst.Identifier == subTreeIdentifier); + + if (subTree == null) { return TalentTreeStageState.Invalid; } + + TalentOption targetTalentOption = subTree.TalentOptionStages[index]; + + if (targetTalentOption.Talents.Any(t => character.HasTalent(t.Identifier))) + { + return TalentTreeStageState.Unlocked; + } + + if (targetTalentOption.Talents.Any(t => selectedTalents.Contains(t.Identifier))) + { + return TalentTreeStageState.Highlighted; + } + + bool hasTalentInLastTier = true; + bool isLastTalentPurchased = true; + + int lastindex = index - 1; + if (lastindex >= 0) + { + TalentOption lastLatentOption = subTree.TalentOptionStages[lastindex]; + hasTalentInLastTier = lastLatentOption.Talents.Any(HasTalent); + isLastTalentPurchased = lastLatentOption.Talents.Any(t => character.HasTalent(t.Identifier)); + } + + if (!hasTalentInLastTier) + { + return TalentTreeStageState.Locked; + } + + bool hasPointsForNewTalent = character.Info.GetTotalTalentPoints() - selectedTalents.Count > 0; + + if (hasPointsForNewTalent) + { + return isLastTalentPurchased ? TalentTreeStageState.Highlighted : TalentTreeStageState.Available; + } + + return TalentTreeStageState.Locked; + + bool HasTalent(TalentPrefab t) + { + return selectedTalents.Contains(t.Identifier); + } + } + + + public static bool IsViableTalentForCharacter(Character character, string talentIdentifier, IEnumerable selectedTalents) + { + if (character?.Info?.Job.Prefab == null) { return false; } + if (character.Info.GetTotalTalentPoints() - selectedTalents.Count() <= 0) { return false; } + + if (!JobTalentTrees.TryGetValue(character.Info.Job.Prefab.Identifier, out TalentTree talentTree)) { return false; } + + foreach (var subTree in talentTree.TalentSubTrees) + { + foreach (var talentOptionStage in subTree.TalentOptionStages) + { + bool hasTalentInThisTier = talentOptionStage.Talents.Any(t => selectedTalents.Contains(t.Identifier)); + if (!hasTalentInThisTier) + { + if (talentOptionStage.Talents.Any(t => t.Identifier == talentIdentifier)) + { + return true; + } + else + { + break; + } + } + } + } + + return false; + } + + public static List CheckTalentSelection(Character controlledCharacter, IEnumerable selectedTalents) + { + List viableTalents = new List(); + bool canStillUnlock = true; + // keep trying to unlock talents until none of the talents are unlockable + while (canStillUnlock && selectedTalents.Any()) + { + canStillUnlock = false; + foreach (string talent in selectedTalents) + { + if (IsViableTalentForCharacter(controlledCharacter, talent, viableTalents)) + { + viableTalents.Add(talent); + canStillUnlock = true; + } + } + } + return viableTalents; + } + } + + class TalentSubTree + { + public string Identifier { get; } + + public string DisplayName { get; } + + public readonly List TalentOptionStages = new List(); + + public TalentSubTree(XElement subTreeElement) + { + Identifier = subTreeElement.GetAttributeString("identifier", ""); + + DisplayName = TextManager.Get("talenttree." + Identifier, returnNull: true) ?? Identifier; + + foreach (XElement talentOptionsElement in subTreeElement.GetChildElements("talentoptions")) + { + TalentOptionStages.Add(new TalentOption(talentOptionsElement, Identifier)); + } + } + + } + + class TalentOption + { + public readonly List Talents = new List(); + + public TalentOption(XElement talentOptionsElement, string debugIdentifier) + { + foreach (XElement talentOptionElement in talentOptionsElement.GetChildElements("talentoption")) + { + string identifier = talentOptionElement.GetAttributeString("identifier", string.Empty); + + if (!TalentPrefab.TalentPrefabs.ContainsKey(identifier)) + { + DebugConsole.ThrowError($"Error in talent tree \"{debugIdentifier}\" - could not find a talent with the identifier \"{identifier}\"."); + return; + } + Talents.Add(TalentPrefab.TalentPrefabs[identifier]); + } + } + } + +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/ContentPackage.cs b/Barotrauma/BarotraumaShared/SharedSource/ContentPackage.cs index ba609f244..dfcd2b32d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/ContentPackage.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/ContentPackage.cs @@ -51,7 +51,9 @@ namespace Barotrauma WreckAIConfig, UpgradeModules, MapCreature, - EnemySubmarine + EnemySubmarine, + Talents, + TalentTrees, } public class ContentPackage @@ -103,7 +105,8 @@ namespace Barotrauma ContentType.Corpses, ContentType.UpgradeModules, ContentType.MapCreature, - ContentType.EnemySubmarine + ContentType.EnemySubmarine, + ContentType.Talents, }; //at least one file of each these types is required in core content packages @@ -135,7 +138,8 @@ namespace Barotrauma ContentType.Orders, ContentType.Corpses, ContentType.UpgradeModules, - ContentType.EnemySubmarine + ContentType.EnemySubmarine, + ContentType.Talents, }; public static IEnumerable CorePackageRequiredFiles diff --git a/Barotrauma/BarotraumaShared/SharedSource/CoroutineManager.cs b/Barotrauma/BarotraumaShared/SharedSource/CoroutineManager.cs index f149339d6..2ee6801ac 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/CoroutineManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/CoroutineManager.cs @@ -58,12 +58,7 @@ namespace Barotrauma return handle; } - public static CoroutineHandle Invoke(Action action) - { - return StartCoroutine(DoInvokeAfter(action, 0.0f)); - } - - public static CoroutineHandle InvokeAfter(Action action, float delay) + public static CoroutineHandle Invoke(Action action, float delay = 0f) { return StartCoroutine(DoInvokeAfter(action, delay)); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs b/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs index acaa5db14..859d6f2fd 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs @@ -191,11 +191,6 @@ namespace Barotrauma GameMain.NetworkMember.ShowNetStats = !GameMain.NetworkMember.ShowNetStats; })); - commands.Add(new Command("createfilelist", "", (string[] args) => - { - UpdaterUtil.SaveFileList("filelist.xml"); - })); - commands.Add(new Command("spawn|spawncharacter", "spawn [creaturename/jobname] [near/inside/outside/cursor] [team (0-3)]: Spawn a creature at a random spawnpoint (use the second parameter to only select spawnpoints near/inside/outside the submarine). You can also enter the name of a job (e.g. \"Mechanic\") to spawn a character with a specific job and the appropriate equipment.", null, () => { @@ -217,7 +212,7 @@ namespace Barotrauma }; }, isCheat: true)); - commands.Add(new Command("spawnitem", "spawnitem [itemname/itemidentifier] [cursor/inventory/cargo/random/[name]]: Spawn an item at the position of the cursor, in the inventory of the controlled character, in the inventory of the client with the given name, or at a random spawnpoint if the last parameter is omitted or \"random\".", + commands.Add(new Command("spawnitem", "spawnitem [itemname/itemidentifier] [cursor/inventory/cargo/random/[name]] [amount]: Spawn an item at the position of the cursor, in the inventory of the controlled character, in the inventory of the client with the given name, or at a random spawnpoint if the last parameter is omitted or \"random\".", (string[] args) => { try @@ -580,7 +575,7 @@ namespace Barotrauma } })); - commands.Add(new Command("dumptofile", "", (string[] args) => + commands.Add(new Command("dumptofile", "findentityids [filename]: Outputs the contents of the debug console into a text file in the game folder. If the filename argument is omitted, \"consoleOutput.txt\" is used as the filename.", (string[] args) => { string filename = "consoleOutput.txt"; if (args.Length > 0) { filename = string.Join(" ", args); } @@ -607,7 +602,7 @@ namespace Barotrauma } })); - commands.Add(new Command("giveaffliction", "giveaffliction [affliction name] [affliction strength] [character name]: Add an affliction to a character. If the name parameter is omitted, the affliction is added to the controlled character.", (string[] args) => + commands.Add(new Command("giveaffliction", "giveaffliction [affliction name] [affliction strength] [character name] [limb type] [use relative strength]: Add an affliction to a character. If the name parameter is omitted, the affliction is added to the controlled character.", (string[] args) => { if (args.Length < 2) { return; } @@ -627,19 +622,24 @@ namespace Barotrauma } bool relativeStrength = false; - if (args.Length > 2) + if (args.Length > 4) { - bool.TryParse(args[2], out relativeStrength); + bool.TryParse(args[4], out relativeStrength); } - Character targetCharacter = (relativeStrength || args.Length <= 2) ? Character.Controlled : FindMatchingCharacter(args.Skip(2).ToArray()); + Character targetCharacter = args.Length <= 2 ? Character.Controlled : FindMatchingCharacter(new string[] { args[2] }); if (targetCharacter != null) { + Limb targetLimb = targetCharacter.AnimController.MainLimb; + if (args.Length > 3) + { + targetLimb = targetCharacter.AnimController.Limbs.FirstOrDefault(l => l.type.ToString().Equals(args[3], StringComparison.OrdinalIgnoreCase)); + } if (relativeStrength) { afflictionStrength *= targetCharacter.MaxVitality / afflictionPrefab.MaxStrength; } - targetCharacter.CharacterHealth.ApplyAffliction(targetCharacter.AnimController.MainLimb, afflictionPrefab.Instantiate(afflictionStrength)); + targetCharacter.CharacterHealth.ApplyAffliction(targetLimb ?? targetCharacter.AnimController.MainLimb, afflictionPrefab.Instantiate(afflictionStrength)); } }, () => @@ -648,7 +648,8 @@ namespace Barotrauma { AfflictionPrefab.List.Select(a => a.Name).ToArray(), new string[] { "1" }, - Character.CharacterList.Select(c => c.Name).ToArray() + Character.CharacterList.Select(c => c.Name).ToArray(), + Enum.GetNames(typeof(LimbType)).ToArray() }; }, isCheat: true)); @@ -806,13 +807,13 @@ namespace Barotrauma { foreach (Skill skill in character.Info.Job.Skills) { - character.Info.SetSkillLevel(skill.Identifier, level, character.WorldPosition); + character.Info.SetSkillLevel(skill.Identifier, level); } NewMessage($"Set all {character.Name}'s skills to {level}", Color.Green); } else { - character.Info.SetSkillLevel(skillIdentifier, level, character.WorldPosition); + character.Info.SetSkillLevel(skillIdentifier, level); NewMessage($"Set {character.Name}'s {skillIdentifier} level to {level}", Color.Green); } } @@ -833,9 +834,129 @@ namespace Barotrauma commands.Add(new Command("water|editwater", "water/editwater: Toggle water editing. Allows adding water into rooms by holding the left mouse button and removing it by holding the right mouse button.", (string[] args) => { Hull.EditWater = !Hull.EditWater; - NewMessage(Hull.EditWater ? "Water editing on" : "Water editing off", Color.White); + NewMessage(Hull.EditWater ? "Water editing on" : "Water editing off", Color.White); }, isCheat: true)); + commands.Add(new Command("givetalent", "givetalent [talent] [player]: give the talent to the specified character. If the character argument is omitted, the talent is given to the controlled character.", (string[] args) => + { + if (args.Length == 0) { return; } + var character = args.Length >= 2 ? FindMatchingCharacter(args.Skip(1).ToArray()) : Character.Controlled; + if (character != null) + { + TalentPrefab talentPrefab = TalentPrefab.TalentPrefabs.Find(c => + c.Identifier.Equals(args[0], StringComparison.OrdinalIgnoreCase) || + c.DisplayName.Equals(args[0], StringComparison.OrdinalIgnoreCase)); + if (talentPrefab == null) + { + ThrowError($"Couldn't find the talent \"{args[0]}\"."); + return; + } + character.GiveTalent(talentPrefab); + NewMessage($"Gave talent \"{talentPrefab.DisplayName}\" to \"{character.Name}\"."); + } + }, + () => + { + List talentNames = new List(); + foreach (TalentPrefab talent in TalentPrefab.TalentPrefabs) + { + talentNames.Add(talent.DisplayName); + } + + return new string[][] + { + talentNames.ToArray(), + Character.CharacterList.Select(c => c.Name).Distinct().ToArray() + }; + }, isCheat: true)); + + commands.Add(new Command("unlocktalents", "unlocktalents [all/[jobname]] [character]: give the specified character all the talents of the specified class", (string[] args) => + { + var character = args.Length >= 2 ? FindMatchingCharacter(args.Skip(1).ToArray()) : Character.Controlled; + if (character == null) { return; } + + List talentTrees = new List(); + if (args.Length == 0 || args[0].Equals("all", StringComparison.OrdinalIgnoreCase)) + { + talentTrees.AddRange(TalentTree.JobTalentTrees.Values); + } + else + { + var job = JobPrefab.Prefabs.Find(jp => jp.Name != null && jp.Name.Equals(args[0], StringComparison.OrdinalIgnoreCase)); + if (job == null) + { + ThrowError($"Failed to find the job \"{args[0]}\"."); + return; + } + if (!TalentTree.JobTalentTrees.TryGetValue(job.Identifier, out TalentTree talentTree)) + { + ThrowError($"No talents configured for the job \"{args[0]}\"."); + return; + } + talentTrees.Add(talentTree); + } + + foreach (var talentTree in talentTrees) + { + foreach (var subTree in talentTree.TalentSubTrees) + { + foreach (var option in subTree.TalentOptionStages) + { + foreach (var talent in option.Talents) + { + character.GiveTalent(talent); + NewMessage($"Unlocked talent \"{talent.DisplayName}\"."); + } + } + } + } + }, + () => + { + List availableArgs = new List() { "All" }; + availableArgs.AddRange(JobPrefab.Prefabs.Select(j => j.Name)); + return new string[][] + { + availableArgs.ToArray(), + Character.CharacterList.Select(c => c.Name).Distinct().ToArray() + }; + }, isCheat: true)); + + commands.Add(new Command("giveexperience", "giveexperience [amount] [character]: Give experience to character.", (string[] args) => + { + if (args.Length < 1) + { + NewMessage($"Missing arguments. Expected at least 1 but got {args.Length} (experience, name)"); + return; + } + + string experienceString = args[0]; + var character = FindMatchingCharacter(args.Skip(1).ToArray()) ?? Character.Controlled; + + if (character?.Info == null) + { + NewMessage("Character is not valid."); + return; + } + + if (int.TryParse(experienceString, NumberStyles.Number, CultureInfo.InvariantCulture, out int experience)) + { + character.Info.GiveExperience(experience); + NewMessage($"Gave {character.Name} {experience} experience"); + } + else + { + NewMessage($"{experienceString} is not a valid value. Expected number."); + } + }, isCheat: true, getValidArgs: () => + { + return new[] + { + new string[] { "100" }, + Character.CharacterList.Select(c => c.Name).Distinct().ToArray(), + }; + })); + commands.Add(new Command("fire|editfire", "fire/editfire: Allows putting up fires by left clicking.", (string[] args) => { Hull.EditFire = !Hull.EditFire; @@ -947,7 +1068,7 @@ namespace Barotrauma string subName = GameMain.Config.QuickStartSubmarineName; if (!string.IsNullOrEmpty(subName)) { - selectedSub = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name.ToLower() == subName.ToLower()); + selectedSub = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name.Equals(subName, StringComparison.OrdinalIgnoreCase)); } int count = 0; @@ -1013,7 +1134,7 @@ namespace Barotrauma if (args.Length == 0) { return; } if (float.TryParse(args[0], NumberStyles.Any, CultureInfo.InvariantCulture, out float reputation)) { - campaign.Map.CurrentLocation.Reputation.Value = reputation; + campaign.Map.CurrentLocation.Reputation.SetReputation(reputation); } else { @@ -1040,7 +1161,7 @@ namespace Barotrauma { if (float.TryParse(args[1], NumberStyles.Any, CultureInfo.InvariantCulture, out float reputation)) { - faction.Reputation.Value = reputation; + faction.Reputation.SetReputation(reputation); } else { @@ -1368,7 +1489,7 @@ namespace Barotrauma NewMessage((GameMain.GameSession.Map.AllowDebugTeleport ? "Enabled" : "Disabled") + " teleportation on the campaign map.", Color.White); }, isCheat: true)); - commands.Add(new Command("money", "", args => + commands.Add(new Command("money", "money [amount]: Gives the specified amount of money to the crew when a campaign is active.", args => { if (args.Length == 0) { return; } if (GameMain.GameSession?.GameMode is CampaignMode campaign) @@ -1488,8 +1609,8 @@ namespace Barotrauma { if (args.Length > 0) { - string packageName = string.Join(" ", args).ToLower(); - var package = GameMain.Config.AllEnabledPackages.FirstOrDefault(p => p.Name.ToLower() == packageName); + string packageName = string.Join(" ", args); + var package = GameMain.Config.AllEnabledPackages.FirstOrDefault(p => p.Name.Equals(packageName, StringComparison.OrdinalIgnoreCase)); if (package == null) { ThrowError("Content package \"" + packageName + "\" not found."); @@ -1763,13 +1884,15 @@ namespace Barotrauma } return; } -#if !DEBUG if (!IsCommandPermitted(splitCommand[0].ToLowerInvariant(), GameMain.Client)) { - ThrowError("You're not permitted to use the command \"" + splitCommand[0].ToLowerInvariant() + "\"!"); +#if DEBUG + AddWarning($"You're not permitted to use the command \"{splitCommand[0].ToLowerInvariant()}\". Executing the command anyway because this is a debug build."); +#else + ThrowError($"You're not permitted to use the command \"{splitCommand[0].ToLowerInvariant()}\"!"); return; - } #endif + } } #endif @@ -1971,9 +2094,18 @@ namespace Barotrauma return; } + int amount = 1; if (args.Length > 1) { - switch (args.Last()) + string spawnLocation = args.Last(); + if (args.Length > 2) + { + spawnLocation = args[^2]; + if (!int.TryParse(args[^1], NumberStyles.Any, CultureInfo.InvariantCulture, out amount)) { amount = 1; } + amount = Math.Min(amount, 100); + } + + switch (spawnLocation) { case "cursor": spawnPos = cursorPos; @@ -2001,37 +2133,40 @@ namespace Barotrauma spawnPos = wp == null ? Vector2.Zero : wp.WorldPosition; } - if (spawnPos != null) + for (int i = 0; i < amount; i++) { - if (Entity.Spawner == null) + if (spawnPos != null) { - new Item(itemPrefab, spawnPos.Value, null); - } - else - { - Entity.Spawner?.AddToSpawnQueue(itemPrefab, spawnPos.Value); - } - } - else if (spawnInventory != null) - { - if (Entity.Spawner == null) - { - var spawnedItem = new Item(itemPrefab, Vector2.Zero, null); - spawnInventory.TryPutItem(spawnedItem, null, spawnedItem.AllowedSlots); - onItemSpawned(spawnedItem); - } - else - { - Entity.Spawner?.AddToSpawnQueue(itemPrefab, spawnInventory, onSpawned: onItemSpawned); - } - - static void onItemSpawned(Item item) - { - if (item.ParentInventory?.Owner is Character character) + if (Entity.Spawner == null) { - foreach (WifiComponent wifiComponent in item.GetComponents()) + new Item(itemPrefab, spawnPos.Value, null); + } + else + { + Entity.Spawner?.AddToSpawnQueue(itemPrefab, spawnPos.Value); + } + } + else if (spawnInventory != null) + { + if (Entity.Spawner == null) + { + var spawnedItem = new Item(itemPrefab, Vector2.Zero, null); + spawnInventory.TryPutItem(spawnedItem, null, spawnedItem.AllowedSlots); + onItemSpawned(spawnedItem); + } + else + { + Entity.Spawner?.AddToSpawnQueue(itemPrefab, spawnInventory, onSpawned: onItemSpawned); + } + + static void onItemSpawned(Item item) + { + if (item.ParentInventory?.Owner is Character character) { - wifiComponent.TeamID = character.TeamID; + foreach (WifiComponent wifiComponent in item.GetComponents()) + { + wifiComponent.TeamID = character.TeamID; + } } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Enums.cs b/Barotrauma/BarotraumaShared/SharedSource/Enums.cs index d0a91b108..3ac2e2dbf 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Enums.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Enums.cs @@ -12,16 +12,135 @@ public enum ActionType { - Always, OnPicked, OnUse, OnSecondaryUse, - OnWearing, OnContaining, OnContained, OnNotContained, - OnActive, OnFailure, OnBroken, - OnFire, InWater, NotInWater, - OnImpact, - OnEating, - OnDamaged, - OnSevered, - OnProduceSpawned, - OnOpen, OnClose, - OnDeath = OnBroken, + Always = 0, OnPicked = 1, OnUse = 2, OnSecondaryUse = 3, + OnWearing = 4, OnContaining = 5, OnContained = 6, OnNotContained = 7, + OnActive = 8, OnFailure = 9, OnBroken = 10, + OnFire = 11, InWater = 12, NotInWater = 13, + OnImpact = 14, + OnEating = 15, + OnDamaged = 16, + OnSevered = 17, + OnProduceSpawned = 18, + OnOpen = 19, OnClose = 20, + OnSpawn = 21, + OnSuccess = 22, + OnAbility = 23, + OnDeath = OnBroken } + + public enum AbilityEffectType + { + Undefined, + None, + OnAttack, + OnAttackResult, + OnAttacked, + OnAttackedResult, + OnGainSkillPoint, + OnAllyGainSkillPoint, + OnRepairComplete, + OnItemFabricationSkillGain, + OnItemFabricatedAmount, + OnAllyItemFabricatedAmount, + OnOpenItemContainer, + OnUseRangedWeapon, + OnReduceAffliction, + OnAddDamageAffliction, + OnSelfRagdoll, + OnRagdoll, + OnRoundEnd, + OnAnyMissionCompleted, + OnAllMissionsCompleted, + OnGiveOrder, + OnCrewKillCharacter, + OnKillCharacter, + OnDieToCharacter, + OnAllyGainMissionExperience, + OnGainMissionExperience, + OnGainMissionMoney, + OnLocationDiscovered, + OnItemDeconstructed, + OnItemDeconstructedMaterial, + OnItemDeconstructedInventory, + OnStopTinkering, + OnItemPicked, + OnGeneticMaterialCombinedOrRefined, + OnCrewGeneticMaterialCombinedOrRefined, + AfterSubmarineAttacked, + OnApplyTreatment, + } + + public enum StatTypes + { + None, + // Skills + ElectricalSkillBonus, + HelmSkillBonus, + MechanicalSkillBonus, + MedicalSkillBonus, + WeaponsSkillBonus, + // Character attributes + MaximumHealthMultiplier, + MovementSpeed, + WalkingSpeed, + SwimmingSpeed, + BuffDurationMultiplier, + DebuffDurationMultiplier, + MedicalItemEffectivenessMultiplier, + FlowResistance, + // Combat + AttackMultiplier, + TeamAttackMultiplier, + RangedAttackSpeed, + TurretAttackSpeed, + TurretPowerCostReduction, + TurretChargeSpeed, + MeleeAttackSpeed, + MeleeAttackMultiplier, + RangedAttackMultiplier, + RangedSpreadReduction, + // Utility + RepairSpeed, + DeconstructorSpeedMultiplier, + RepairToolStructureRepairMultiplier, + RepairToolStructureDamageMultiplier, + RepairToolDeattachTimeMultiplier, + MaxRepairConditionMultiplierMechanical, + MaxRepairConditionMultiplierElectrical, + IncreaseFabricationQuality, + GeneticMaterialRefineBonus, + GeneticMaterialTaintedProbabilityReductionOnCombine, + SkillGainSpeed, + // Tinker + TinkeringDuration, + TinkeringStrength, + TinkeringDamage, + // Misc + ReputationGainMultiplier, + MissionMoneyGainMultiplier, + ExperienceGainMultiplier, + MissionExperienceGainMultiplier, + ExtraMissionCount, + ExtraSpecialSalesCount, + ApplyTreatmentsOnSelfFraction, + MaxAttachableCount, + } + + public enum AbilityFlags + { + None, + MustWalk, + ImmuneToPressure, + IgnoredByEnemyAI, + MoveNormallyWhileDragging, + CanTinker, + CanTinkerFabricatorsAndDeconstructors, + TinkeringPowersDevices, + GainSkillPastMaximum, + RetainExperienceForNewCharacter, + AllowSecondOrderedTarget, + PowerfulCPR, + AlwaysStayConscious, + } + } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/GiveSkillExpAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/GiveSkillExpAction.cs index 17ab504c6..2da290284 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/GiveSkillExpAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/GiveSkillExpAction.cs @@ -1,6 +1,4 @@ using Microsoft.Xna.Framework; -using System; -using System.Collections.Generic; using System.Linq; using System.Xml.Linq; @@ -42,7 +40,7 @@ namespace Barotrauma var targets = ParentEvent.GetTargets(TargetTag).Where(e => e is Character).Select(e => e as Character); foreach (var target in targets) { - target.Info?.IncreaseSkillLevel(Skill?.ToLowerInvariant(), Amount, target.Position + Vector2.UnitY * 150.0f); + target.Info?.IncreaseSkillLevel(Skill?.ToLowerInvariant(), Amount); } isFinished = true; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/GodModeAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/GodModeAction.cs new file mode 100644 index 000000000..aecef1c64 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/GodModeAction.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma +{ + class GodModeAction : EventAction + { + [Serialize(true, true)] + public bool Enabled { get; set; } + + [Serialize("", true)] + public string TargetTag { get; set; } + + public GodModeAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element) { } + + private bool isFinished = false; + + public override bool IsFinished(ref string goTo) + { + return isFinished; + } + + public override void Reset() + { + isFinished = false; + } + + public override void Update(float deltaTime) + { + if (isFinished) { return; } + var targets = ParentEvent.GetTargets(TargetTag); + foreach (var target in targets) + { + if (target != null && target is Character character) + { + character.GodMode = Enabled; + } + } + isFinished = true; + } + + public override string ToDebugString() + { + return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(GodModeAction)} -> (TargetTag: {TargetTag.ColorizeObject()}, " + + (Enabled ? "Enable godmode" : "Disable godmode"); + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/ReputationAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/ReputationAction.cs index 2ed053c81..d4f86a1f0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/ReputationAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/ReputationAction.cs @@ -50,7 +50,7 @@ namespace Barotrauma Faction faction = campaign.Factions.Find(faction1 => faction1.Prefab.Identifier.Equals(Identifier, StringComparison.OrdinalIgnoreCase)); if (faction != null) { - faction.Reputation.Value += Increase; + faction.Reputation.AddReputation(Increase); } else { @@ -64,14 +64,14 @@ namespace Barotrauma Location location = campaign.Map.CurrentLocation; if (location != null) { - location.Reputation.Value += Increase; + location.Reputation.AddReputation(Increase); IEnumerable locations = location.Connections.SelectMany(c => c.Locations).Distinct().Where(l => l != null && l != location); foreach (Location connectedLocation in locations) { Debug.Assert(connectedLocation.Reputation != null, "connectedLocation.Reputation != null"); if (connectedLocation.Reputation != null) { - connectedLocation.Reputation.Value += (Increase / 4); + connectedLocation.Reputation.AddReputation(Increase / 4); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/SpawnAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/SpawnAction.cs index 4737eba63..d42fb1cf6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/SpawnAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/SpawnAction.cs @@ -226,11 +226,11 @@ namespace Barotrauma List potentialItems = SpawnLocation switch { SpawnLocationType.MainSub => Item.ItemList.FindAll(it => it.Submarine == Submarine.MainSub), - SpawnLocationType.MainPath => Item.ItemList.FindAll(it => it.Submarine == null && it.ParentRuin == null), - SpawnLocationType.Outpost => Item.ItemList.FindAll(it => it.Submarine != null && it.Submarine.Info.IsOutpost), - SpawnLocationType.Wreck => Item.ItemList.FindAll(it => it.Submarine != null && it.Submarine.Info.IsWreck), - SpawnLocationType.Ruin => Item.ItemList.FindAll(it => it.ParentRuin != null), - SpawnLocationType.BeaconStation => Item.ItemList.FindAll(it => it.Submarine != null && it.Submarine.Info.IsBeacon), + SpawnLocationType.MainPath => Item.ItemList.FindAll(it => it.Submarine == null), + SpawnLocationType.Outpost => Item.ItemList.FindAll(it => it.Submarine?.Info != null && it.Submarine.Info.IsOutpost), + SpawnLocationType.Wreck => Item.ItemList.FindAll(it => it.Submarine?.Info != null && it.Submarine.Info.IsWreck), + SpawnLocationType.Ruin => Item.ItemList.FindAll(it => it.Submarine?.Info != null && it.Submarine.Info.IsRuin), + SpawnLocationType.BeaconStation => Item.ItemList.FindAll(it => it.Submarine?.Info != null && it.Submarine.Info.IsBeacon), _ => throw new NotImplementedException() }; @@ -252,11 +252,11 @@ namespace Barotrauma List potentialSpawnPoints = spawnLocation switch { SpawnLocationType.MainSub => WayPoint.WayPointList.FindAll(wp => wp.Submarine == Submarine.MainSub && wp.CurrentHull != null), - SpawnLocationType.MainPath => WayPoint.WayPointList.FindAll(wp => wp.Submarine == null && wp.ParentRuin == null), - SpawnLocationType.Outpost => WayPoint.WayPointList.FindAll(wp => wp.Submarine != null && wp.CurrentHull != null && wp.Submarine.Info.IsOutpost), - SpawnLocationType.Wreck => WayPoint.WayPointList.FindAll(wp => wp.Submarine != null && wp.Submarine.Info.IsWreck), - SpawnLocationType.Ruin => WayPoint.WayPointList.FindAll(wp => wp.ParentRuin != null), - SpawnLocationType.BeaconStation => WayPoint.WayPointList.FindAll(wp => wp.Submarine != null && wp.Submarine.Info.IsBeacon), + SpawnLocationType.MainPath => WayPoint.WayPointList.FindAll(wp => wp.Submarine == null), + SpawnLocationType.Outpost => WayPoint.WayPointList.FindAll(wp => wp.Submarine?.Info != null && wp.CurrentHull != null && wp.Submarine.Info.IsOutpost), + SpawnLocationType.Wreck => WayPoint.WayPointList.FindAll(wp => wp.Submarine?.Info != null && wp.Submarine.Info.IsWreck), + SpawnLocationType.Ruin => WayPoint.WayPointList.FindAll(wp => wp.Submarine?.Info != null && wp.Submarine.Info.IsRuin), + SpawnLocationType.BeaconStation => WayPoint.WayPointList.FindAll(wp => wp.Submarine?.Info != null && wp.Submarine.Info.IsBeacon), _ => throw new NotImplementedException() }; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TriggerAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TriggerAction.cs index 97bd587e8..fea12aa2a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TriggerAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TriggerAction.cs @@ -122,7 +122,7 @@ namespace Barotrauma { npcOrItem = npc; npc.CampaignInteractionType = CampaignMode.InteractionType.Examine; - npc.RequireConsciousnessForCustomInteract = false; + npc.RequireConsciousnessForCustomInteract = DisableIfTargetIncapacitated; #if CLIENT npc.SetCustomInteract( (speaker, player) => { if (e1 == speaker) { Trigger(speaker, player); } else { Trigger(player, speaker); } }, diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs index 3f8a0a93a..0e0e9a77c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs @@ -103,7 +103,7 @@ namespace Barotrauma selectedEvents.Clear(); activeEvents.Clear(); - pathFinder = new PathFinder(WayPoint.WayPointList, indoorsSteering: false); + pathFinder = new PathFinder(WayPoint.WayPointList, false); totalPathLength = 0.0f; if (level != null) { @@ -179,7 +179,7 @@ namespace Barotrauma if (eventSet == null) { return; } if (eventSet.OncePerOutpost) { - foreach (EventPrefab ep in eventSet.EventPrefabs.Select(e => e.prefab)) + foreach (EventPrefab ep in eventSet.EventPrefabs.SelectMany(e => e.Prefabs)) { if (!level.LevelData.NonRepeatableEvents.Contains(ep)) { @@ -380,9 +380,13 @@ namespace Barotrauma { pendingEventSets.Clear(); selectedEvents.Clear(); + activeEvents.Clear(); + QueuedEvents.Clear(); preloadedSprites.ForEach(s => s.Remove()); preloadedSprites.Clear(); + + pathFinder = null; } private float CalculateCommonness(EventPrefab eventPrefab, float baseCommonness) @@ -430,23 +434,31 @@ namespace Barotrauma } } - var suitablePrefabs = eventSet.EventPrefabs.FindAll(e => - string.IsNullOrEmpty(e.prefab.BiomeIdentifier) || - e.prefab.BiomeIdentifier.Equals(level.LevelData?.Biome?.Identifier, StringComparison.OrdinalIgnoreCase)); + bool isPrefabSuitable(EventPrefab p) + => string.IsNullOrEmpty(p.BiomeIdentifier) || + p.BiomeIdentifier.Equals(level.LevelData?.Biome?.Identifier, StringComparison.OrdinalIgnoreCase); + + var suitablePrefabSubsets = eventSet.EventPrefabs + .FindAll(p => p.Prefabs.Any(isPrefabSuitable)); for (int i = 0; i < applyCount; i++) { if (eventSet.ChooseRandom) { - if (suitablePrefabs.Count > 0) + if (suitablePrefabSubsets.Count > 0) { - var unusedEvents = new List<(EventPrefab prefab, float commonness, float probability)>(suitablePrefabs); + var unusedEvents = suitablePrefabSubsets.ToList(); for (int j = 0; j < eventSet.EventCount; j++) { - if (unusedEvents.All(e => CalculateCommonness(e.prefab, e.commonness) <= 0.0f)) { break; } - (EventPrefab eventPrefab, float commonness, float probability) = ToolBox.SelectWeightedRandom(unusedEvents, unusedEvents.Select(e => CalculateCommonness(e.prefab, e.commonness)).ToList(), rand); - if (eventPrefab != null && rand.NextDouble() <= probability) + if (unusedEvents.All(e => e.Prefabs.All(p => CalculateCommonness(p, e.Commonness) <= 0.0f))) { break; } + EventSet.SubEventPrefab subEventPrefab = ToolBox.SelectWeightedRandom(unusedEvents, unusedEvents.Select(e => e.Prefabs.Max(p => CalculateCommonness(p, e.Commonness))).ToList(), rand); + (IEnumerable eventPrefabs, float commonness, float probability) = subEventPrefab; + if (eventPrefabs != null && rand.NextDouble() <= probability) { + var finalPrefabs = eventPrefabs.Where(isPrefabSuitable).ToArray(); + var finalPrefabCommonnesses = finalPrefabs.Select(p => p.Commonness).ToArray(); + var eventPrefab = ToolBox.SelectWeightedRandom(finalPrefabs, finalPrefabCommonnesses, rand); + var newEvent = eventPrefab.CreateInstance(); if (newEvent == null) { continue; } newEvent.Init(true); @@ -461,7 +473,7 @@ namespace Barotrauma selectedEvents.Add(eventSet, new List()); } selectedEvents[eventSet].Add(newEvent); - unusedEvents.Remove((eventPrefab, commonness, probability)); + unusedEvents.Remove(subEventPrefab); } } } @@ -476,9 +488,13 @@ namespace Barotrauma } else { - foreach ((EventPrefab eventPrefab, float commonness, float probability) in suitablePrefabs) + foreach ((IEnumerable eventPrefabs, float commonness, float probability) in suitablePrefabSubsets) { if (rand.NextDouble() > probability) { continue; } + + var finalPrefabs = eventPrefabs.Where(isPrefabSuitable).ToArray(); + var finalPrefabCommonnesses = finalPrefabs.Select(p => p.Commonness).ToArray(); + var eventPrefab = ToolBox.SelectWeightedRandom(finalPrefabs, finalPrefabCommonnesses, rand); var newEvent = eventPrefab.CreateInstance(); if (newEvent == null) { continue; } newEvent.Init(true); @@ -862,8 +878,9 @@ namespace Barotrauma private float CalculateDistanceTraveled() { - if (level == null) { return 0.0f; } + if (level == null || pathFinder == null) { return 0.0f; } var refEntity = GetRefEntity(); + if (refEntity == null) { return 0.0f; } Vector2 target = ConvertUnits.ToSimUnits(level.EndPosition); var steeringPath = pathFinder.FindPath(ConvertUnits.ToSimUnits(refEntity.WorldPosition), target); if (steeringPath.Unreachable || float.IsPositiveInfinity(totalPathLength)) @@ -976,6 +993,7 @@ namespace Barotrauma return false; case SubmarineType.Wreck: case SubmarineType.BeaconStation: + case SubmarineType.Ruin: return true; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventPrefab.cs index b7632a54e..3938f6db0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventPrefab.cs @@ -40,7 +40,7 @@ namespace Barotrauma BiomeIdentifier = ConfigElement.GetAttributeString("biome", string.Empty); Commonness = element.GetAttributeFloat("commonness", 1.0f); Probability = Math.Clamp(element.GetAttributeFloat(1.0f, "probability", "spawnprobability"), 0, 1); - TriggerEventCooldown = element.GetAttributeBool("triggereventcooldown", true); + TriggerEventCooldown = element.GetAttributeBool("triggereventcooldown", EventType != typeof(ScriptedEvent)); UnlockPathEvent = element.GetAttributeBool("unlockpathevent", false); UnlockPathTooltip = element.GetAttributeString("unlockpathtooltip", "lockedpathtooltip"); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventSet.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventSet.cs index 54d748b8e..7431b31b3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventSet.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventSet.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using System.Xml.Linq; using Barotrauma.Extensions; @@ -48,10 +49,10 @@ namespace Barotrauma List eventPrefabs = new List(PrefabList); foreach (var eventSet in List) { - eventPrefabs.AddRange(eventSet.EventPrefabs.Select(ep => ep.prefab)); + eventPrefabs.AddRange(eventSet.EventPrefabs.SelectMany(ep => ep.Prefabs)); foreach (var childSet in eventSet.ChildSets) { - eventPrefabs.AddRange(childSet.EventPrefabs.Select(ep => ep.prefab)); + eventPrefabs.AddRange(childSet.EventPrefabs.SelectMany(ep => ep.Prefabs)); } } return eventPrefabs; @@ -98,7 +99,48 @@ namespace Barotrauma public readonly Dictionary Commonness; - public readonly List<(EventPrefab prefab, float commonness, float probability)> EventPrefabs; + public struct SubEventPrefab + { + public SubEventPrefab(string debugIdentifier, string[] prefabIdentifiers, float? commonness, float? probability) + { + EventPrefab tryFindPrefab(string id) + { + var prefab = PrefabList.Find(p => p.Identifier.Equals(id, StringComparison.OrdinalIgnoreCase)); + if (prefab is null) + { + DebugConsole.ThrowError($"Error in event set \"{debugIdentifier}\" - could not find the event prefab \"{id}\"."); + } + return prefab; + } + + this.Prefabs = prefabIdentifiers + .Select(tryFindPrefab) + .Where(p => p != null) + .ToImmutableArray(); + this.Commonness = commonness ?? this.Prefabs.Select(p => p.Commonness).Max(); + this.Probability = probability ?? this.Prefabs.Select(p => p.Probability).Max(); + } + + public SubEventPrefab(EventPrefab prefab, float commonness, float probability) + { + Prefabs = prefab.ToEnumerable().ToImmutableArray(); + Commonness = commonness; + Probability = probability; + } + + public readonly ImmutableArray Prefabs; + public readonly float Commonness; + public readonly float Probability; + + public void Deconstruct(out IEnumerable prefabs, out float commonness, out float probability) + { + prefabs = Prefabs; + commonness = Commonness; + probability = Probability; + } + } + + public readonly List EventPrefabs; public readonly List ChildSets; @@ -112,7 +154,7 @@ namespace Barotrauma { DebugIdentifier = element.GetAttributeString("identifier", null) ?? debugIdentifier; Commonness = new Dictionary(); - EventPrefabs = new List<(EventPrefab prefab, float commonness, float probability)>(); + EventPrefabs = new List(); ChildSets = new List(); BiomeIdentifier = element.GetAttributeString("biome", string.Empty); @@ -178,23 +220,20 @@ namespace Barotrauma //an element with just an identifier = reference to an event prefab if (!subElement.HasElements && subElement.Attributes().First().Name.ToString().Equals("identifier", StringComparison.OrdinalIgnoreCase)) { - string identifier = subElement.GetAttributeString("identifier", ""); - var prefab = PrefabList.Find(p => p.Identifier.Equals(identifier, StringComparison.OrdinalIgnoreCase)); - if (prefab == null) - { - DebugConsole.ThrowError($"Error in event set \"{debugIdentifier}\" - could not find the event prefab \"{identifier}\"."); - } - else - { - float commonness = subElement.GetAttributeFloat("commonness", prefab.Commonness); - float probability = subElement.GetAttributeFloat("probability", prefab.Probability); - EventPrefabs.Add((prefab, commonness, probability)); - } + string[] identifiers = subElement.GetAttributeStringArray("identifier", Array.Empty()); + + float commonness = subElement.GetAttributeFloat("commonness", -1f); + float probability = subElement.GetAttributeFloat("probability", -1f); + EventPrefabs.Add(new SubEventPrefab( + debugIdentifier, + identifiers, + commonness>=0f ? commonness : (float?)null, + probability>=0f ? probability : (float?)null)); } else { var prefab = new EventPrefab(subElement); - EventPrefabs.Add((prefab, prefab.Commonness, prefab.Probability)); + EventPrefabs.Add(new SubEventPrefab(prefab, prefab.Commonness, prefab.Probability)); } break; } @@ -346,13 +385,13 @@ namespace Barotrauma { if (thisSet.ChooseRandom) { - var unusedEvents = new List<(EventPrefab prefab, float commonness, float probability)>(thisSet.EventPrefabs); + var unusedEvents = thisSet.EventPrefabs.ToList(); for (int i = 0; i < thisSet.EventCount; i++) { - var eventPrefab = ToolBox.SelectWeightedRandom(unusedEvents, unusedEvents.Select(e => e.commonness).ToList(), Rand.RandSync.Unsynced); - if (eventPrefab.prefab != null) + var eventPrefab = ToolBox.SelectWeightedRandom(unusedEvents, unusedEvents.Select(e => e.Commonness).ToList(), Rand.RandSync.Unsynced); + if (eventPrefab.Prefabs.Any(p => p != null)) { - AddEvent(stats, eventPrefab.prefab); + AddEvents(stats, eventPrefab.Prefabs); unusedEvents.Remove(eventPrefab); } } @@ -361,7 +400,7 @@ namespace Barotrauma { foreach (var eventPrefab in thisSet.EventPrefabs) { - AddEvent(stats, eventPrefab.prefab); + AddEvents(stats, eventPrefab.Prefabs); } } foreach (var childSet in thisSet.ChildSets) @@ -370,6 +409,9 @@ namespace Barotrauma } } + static void AddEvents(EventDebugStats stats, IEnumerable eventPrefabs) + => eventPrefabs.ForEach(p => AddEvent(stats, p)); + static void AddEvent(EventDebugStats stats, EventPrefab eventPrefab) { if (eventPrefab.EventType == typeof(MonsterEvent)) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/AbandonedOutpostMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/AbandonedOutpostMission.cs index 55e7366cf..7869cd1b0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/AbandonedOutpostMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/AbandonedOutpostMission.cs @@ -306,7 +306,7 @@ namespace Barotrauma case 0: if (items.All(it => it.Removed || it.Condition <= 0.0f) && - requireKill.All(c => c.Removed || c.IsDead) && + requireKill.All(c => c.Removed || c.IsDead || (c.LockHands && c.Submarine == Submarine.MainSub)) && requireRescue.All(c => c.Submarine?.Info.Type == SubmarineType.Player)) { State = 1; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/AlienRuinMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/AlienRuinMission.cs new file mode 100644 index 000000000..993b0cd02 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/AlienRuinMission.cs @@ -0,0 +1,176 @@ +using Barotrauma.Extensions; +using Barotrauma.RuinGeneration; +using Microsoft.Xna.Framework; +using System.Collections.Generic; +using System.Linq; + +namespace Barotrauma +{ + partial class AlienRuinMission : Mission + { + private readonly string[] targetItemIdentifiers; + private readonly string[] targetEnemyIdentifiers; + private readonly int minEnemyCount; + private readonly HashSet existingTargets = new HashSet(); + private readonly HashSet spawnedTargets = new HashSet(); + private readonly HashSet allTargets = new HashSet(); + + private Ruin TargetRuin { get; set; } + + public override IEnumerable SonarPositions + { + get + { + if (State == 0) + { + return allTargets.Where(t => (t is Item i && !IsItemDestroyed(i)) || (t is Character c && !IsEnemyDefeated(c))).Select(t => t.WorldPosition); + } + else + { + return Enumerable.Empty(); + } + } + } + + public AlienRuinMission(MissionPrefab prefab, Location[] locations, Submarine sub) : base(prefab, locations, sub) + { + targetItemIdentifiers = prefab.ConfigElement.GetAttributeStringArray("targetitems", new string[0], convertToLowerInvariant: true); + targetEnemyIdentifiers = prefab.ConfigElement.GetAttributeStringArray("targetenemies", new string[0], convertToLowerInvariant: true); + minEnemyCount = prefab.ConfigElement.GetAttributeInt("minenemycount", 0); + } + + protected override void StartMissionSpecific(Level level) + { + existingTargets.Clear(); + spawnedTargets.Clear(); + allTargets.Clear(); + if (IsClient) { return; } + TargetRuin = Level.Loaded?.Ruins?.GetRandom(randSync: Rand.RandSync.Server); + if (TargetRuin == null) + { + DebugConsole.ThrowError($"Failed to initialize an Alien Ruin mission (\"{Prefab.Identifier}\"): level contains no alien ruins"); + return; + } + if (targetItemIdentifiers.Length < 1 && targetEnemyIdentifiers.Length < 1) + { + DebugConsole.ThrowError($"Failed to initialize an Alien Ruin mission (\"{Prefab.Identifier}\"): no target identifiers set in the mission definition"); + return; + } + foreach (var item in Item.ItemList) + { + if (!targetItemIdentifiers.Contains(item.Prefab.Identifier)) { continue; } + if (item.Submarine != TargetRuin.Submarine) { continue; } + existingTargets.Add(item); + allTargets.Add(item); + } + int existingEnemyCount = 0; + foreach (var character in Character.CharacterList) + { + if (string.IsNullOrEmpty(character.SpeciesName)) { continue; } + if (!targetEnemyIdentifiers.Contains(character.SpeciesName.ToLowerInvariant())) { continue; } + if (character.Submarine != TargetRuin.Submarine) { continue; } + existingTargets.Add(character); + allTargets.Add(character); + existingEnemyCount++; + } + if (existingEnemyCount < minEnemyCount) + { + var enemyPrefabs = new HashSet(); + foreach (string identifier in targetEnemyIdentifiers) + { + var prefab = CharacterPrefab.FindBySpeciesName(identifier); + if (prefab != null) + { + enemyPrefabs.Add(prefab); + } + else + { + DebugConsole.ThrowError($"Error in an Alien Ruin mission (\"{Prefab.Identifier}\"): could not find a character prefab with the species \"{identifier}\""); + } + } + if (enemyPrefabs.None()) + { + DebugConsole.ThrowError($"Error in an Alien Ruin mission (\"{Prefab.Identifier}\"): no enemy species defined that could be used to spawn more ({minEnemyCount - existingEnemyCount}) enemies"); + return; + } + for (int i = 0; i < (minEnemyCount - existingEnemyCount); i++) + { + var prefab = enemyPrefabs.GetRandom(); + var spawnPos = TargetRuin.Submarine.GetWaypoints(false).GetRandom(w => w.CurrentHull != null)?.WorldPosition; + if (!spawnPos.HasValue) + { + DebugConsole.ThrowError($"Error in an Alien Ruin mission (\"{Prefab.Identifier}\"): no valid spawn positions could be found for the additional ({minEnemyCount - existingEnemyCount}) enemies to be spawned"); + return; + } + var newEnemy = Character.Create(prefab.Identifier, spawnPos.Value, ToolBox.RandomSeed(8), createNetworkEvent: false); + spawnedTargets.Add(newEnemy); + allTargets.Add(newEnemy); + } + } +#if DEBUG + DebugConsole.NewMessage("********** CLEAR RUIN MISSION INFO **********"); + DebugConsole.NewMessage($"Existing item targets: {existingTargets.Count - existingEnemyCount}"); + DebugConsole.NewMessage($"Existing enemy targets: {existingEnemyCount}"); + DebugConsole.NewMessage($"Spawned enemy targets: {spawnedTargets.Count}"); +#endif + } + + protected override void UpdateMissionSpecific(float deltaTime) + { + if (IsClient) { return; } + switch (State) + { + case 0: + if (!AllTargetsEliminated()) { return; } + State = 1; + break; + case 1: + if (!Submarine.MainSub.AtEndExit && !Submarine.MainSub.AtStartExit) { return; } + State = 2; + break; + } + } + + private bool AllTargetsEliminated() + { + foreach (var target in allTargets) + { + if (target is Item targetItem) + { + if (!IsItemDestroyed(targetItem)) + { + return false; + } + } + else if (target is Character targetEnemy) + { + if (!IsEnemyDefeated(targetEnemy)) + { + return false; + } + } +#if DEBUG + else + { + DebugConsole.ThrowError($"Error in Alien Ruin mission (\"{Prefab.Identifier}\"): unexpected target of type {target?.GetType()?.ToString()}"); + } +#endif + } + return true; + } + + private bool IsItemDestroyed(Item item) => item == null || item.Removed || item.Condition <= 0.0f; + + private bool IsEnemyDefeated(Character enemy) => enemy == null ||enemy.Removed || enemy.IsDead; + + public override void End() + { + if (State == 2) + { + GiveReward(); + completed = true; + } + failed = !completed && State > 0; + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/BeaconMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/BeaconMission.cs index 463da3632..3fc58b81a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/BeaconMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/BeaconMission.cs @@ -91,7 +91,7 @@ namespace Barotrauma int amount = Rand.Range(monsterCountRange.X, monsterCountRange.Y + 1); for (int i = 0; i < amount; i++) { - CoroutineManager.InvokeAfter(() => + CoroutineManager.Invoke(() => { //round ended before the coroutine finished if (GameMain.GameSession == null || Level.Loaded == null) { return; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CargoMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CargoMission.cs index dcc9d3279..d51ccd3fd 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CargoMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CargoMission.cs @@ -140,6 +140,12 @@ namespace Barotrauma public override int GetReward(Submarine sub) { + // If we are not at the location of the mission, skip the calculation of the reward + if (GameMain.GameSession?.StartLocation != Locations[0]) + { + return calculatedReward; + } + bool missionsChanged = false; if (GameMain.GameSession?.StartLocation?.SelectedMissions != null) { @@ -192,55 +198,14 @@ namespace Barotrauma if (requiredDeliveryAmount <= 0.0f) { requiredDeliveryAmount = 1.0f; } } - private ItemPrefab FindItemPrefab(XElement element) - { - ItemPrefab itemPrefab; - if (element.Attribute("name") != null) - { - DebugConsole.ThrowError("Error in cargo mission \"" + Name + "\" - use item identifiers instead of names to configure the items."); - string itemName = element.GetAttributeString("name", ""); - itemPrefab = MapEntityPrefab.Find(itemName) as ItemPrefab; - if (itemPrefab == null) - { - DebugConsole.ThrowError("Couldn't spawn item for cargo mission: item prefab \"" + itemName + "\" not found"); - } - } - else - { - string itemIdentifier = element.GetAttributeString("identifier", ""); - itemPrefab = MapEntityPrefab.Find(null, itemIdentifier) as ItemPrefab; - if (itemPrefab == null) - { - DebugConsole.ThrowError("Couldn't spawn item for cargo mission: item prefab \"" + itemIdentifier + "\" not found"); - } - } - return itemPrefab; - } - - private void LoadItemAsChild(XElement element, Item parent) { ItemPrefab itemPrefab = FindItemPrefab(element); - WayPoint cargoSpawnPos = WayPoint.GetRandom(SpawnType.Cargo, null, Submarine.MainSub, useSyncedRand: true); - if (cargoSpawnPos == null) - { - DebugConsole.ThrowError("Couldn't spawn items for cargo mission, cargo spawnpoint not found"); - return; - } + Vector2? position = GetCargoSpawnPosition(itemPrefab, out Submarine cargoRoomSub); + if (!position.HasValue) { return; } - var cargoRoom = cargoSpawnPos.CurrentHull; - if (cargoRoom == null) - { - DebugConsole.ThrowError("A waypoint marked as Cargo must be placed inside a room!"); - return; - } - - Vector2 position = new Vector2( - cargoSpawnPos.Position.X + Rand.Range(-20.0f, 20.0f, Rand.RandSync.Server), - cargoRoom.Rect.Y - cargoRoom.Rect.Height + itemPrefab.Size.Y / 2); - - var item = new Item(itemPrefab, position, cargoRoom.Submarine) + var item = new Item(itemPrefab, position.Value, cargoRoomSub) { SpawnedInOutpost = true, AllowStealing = false diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/GoToMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/GoToMission.cs index 3b077be2c..cb8508f9d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/GoToMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/GoToMission.cs @@ -13,16 +13,5 @@ namespace Barotrauma { State = 1; } - -#if CLIENT - public override void ClientReadInitial(IReadMessage msg) - { - } -#elif SERVER - - public override void ServerWriteInitial(IWriteMessage msg, Client c) - { - } -#endif } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MineralMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MineralMission.cs index 04278f4d6..e407ba68d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MineralMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MineralMission.cs @@ -9,10 +9,10 @@ namespace Barotrauma { partial class MineralMission : Mission { - private Dictionary> ResourceClusters { get; } = new Dictionary>(); - private Dictionary> SpawnedResources { get; } = new Dictionary>(); - private Dictionary RelevantLevelResources { get; } = new Dictionary(); - private List> MissionClusterPositions { get; } = new List>(); + private readonly Dictionary resourceClusters = new Dictionary(); + private readonly Dictionary> spawnedResources = new Dictionary>(); + private readonly Dictionary relevantLevelResources = new Dictionary(); + private readonly List> missionClusterPositions = new List>(); private readonly HashSet caves = new HashSet(); @@ -20,8 +20,8 @@ namespace Barotrauma { get { - return MissionClusterPositions - .Where(p => SpawnedResources.ContainsKey(p.Item1) && AnyAreUncollected(SpawnedResources[p.Item1])) + return missionClusterPositions + .Where(p => spawnedResources.ContainsKey(p.Item1) && AnyAreUncollected(spawnedResources[p.Item1])) .Select(p => p.Item2); } } @@ -33,53 +33,53 @@ namespace Barotrauma { var identifier = c.GetAttributeString("identifier", null); if (string.IsNullOrWhiteSpace(identifier)) { continue; } - if (ResourceClusters.ContainsKey(identifier)) + if (resourceClusters.ContainsKey(identifier)) { - ResourceClusters[identifier].First++; + resourceClusters[identifier] = (resourceClusters[identifier].amount + 1, resourceClusters[identifier].rotation); } else { - ResourceClusters.Add(identifier, new Pair(1, 0.0f)); + resourceClusters.Add(identifier, (1, 0.0f)); } } } protected override void StartMissionSpecific(Level level) { - if (SpawnedResources.Any()) + if (spawnedResources.Any()) { #if DEBUG - throw new Exception($"SpawnedResources.Count > 0 ({SpawnedResources.Count})"); + throw new Exception($"SpawnedResources.Count > 0 ({spawnedResources.Count})"); #else DebugConsole.AddWarning("Spawned resources list was not empty at the start of a mineral mission. The mission instance may not have been ended correctly on previous rounds."); - SpawnedResources.Clear(); + spawnedResources.Clear(); #endif } - if (RelevantLevelResources.Any()) + if (relevantLevelResources.Any()) { #if DEBUG - throw new Exception($"RelevantLevelResources.Count > 0 ({RelevantLevelResources.Count})"); + throw new Exception($"RelevantLevelResources.Count > 0 ({relevantLevelResources.Count})"); #else DebugConsole.AddWarning("Relevant level resources list was not empty at the start of a mineral mission. The mission instance may not have been ended correctly on previous rounds."); - RelevantLevelResources.Clear(); + relevantLevelResources.Clear(); #endif } - if (MissionClusterPositions.Any()) + if (missionClusterPositions.Any()) { #if DEBUG - throw new Exception($"MissionClusterPositions.Count > 0 ({MissionClusterPositions.Count})"); + throw new Exception($"MissionClusterPositions.Count > 0 ({missionClusterPositions.Count})"); #else DebugConsole.AddWarning("Mission cluster positions list was not empty at the start of a mineral mission. The mission instance may not have been ended correctly on previous rounds."); - MissionClusterPositions.Clear(); + missionClusterPositions.Clear(); #endif } caves.Clear(); if (IsClient) { return; } - foreach (var kvp in ResourceClusters) + foreach (var kvp in resourceClusters) { var prefab = ItemPrefab.Find(null, kvp.Key); if (prefab == null) @@ -88,15 +88,14 @@ namespace Barotrauma "couldn't find an item prefab with the identifier " + kvp.Key); continue; } - var spawnedResources = level.GenerateMissionResources(prefab, kvp.Value.First, out float rotation); - if (spawnedResources.Count < kvp.Value.First) + var spawnedResources = level.GenerateMissionResources(prefab, kvp.Value.amount, out float rotation); + if (spawnedResources.Count < kvp.Value.amount) { DebugConsole.ThrowError("Error in MineralMission - " + - "spawned " + spawnedResources.Count + "/" + kvp.Value.First + " of " + prefab.Name); + "spawned " + spawnedResources.Count + "/" + kvp.Value.amount + " of " + prefab.Name); } if (spawnedResources.None()) { continue; } - SpawnedResources.Add(kvp.Key, spawnedResources); - kvp.Value.Second = rotation; + this.spawnedResources.Add(kvp.Key, spawnedResources); foreach (Level.Cave cave in Level.Loaded.Caves) { @@ -142,7 +141,7 @@ namespace Barotrauma GiveReward(); completed = true; } - foreach (var kvp in SpawnedResources) + foreach (var kvp in spawnedResources) { foreach (var i in kvp.Value) { @@ -152,33 +151,33 @@ namespace Barotrauma } } } - SpawnedResources.Clear(); - RelevantLevelResources.Clear(); - MissionClusterPositions.Clear(); + spawnedResources.Clear(); + relevantLevelResources.Clear(); + missionClusterPositions.Clear(); failed = !completed && state > 0; } private void FindRelevantLevelResources() { - RelevantLevelResources.Clear(); - foreach (var identifier in ResourceClusters.Keys) + relevantLevelResources.Clear(); + foreach (var identifier in resourceClusters.Keys) { var items = Item.ItemList.Where(i => i.Prefab.Identifier == identifier && i.Submarine == null && i.ParentInventory == null && (!(i.GetComponent() is Holdable h) || (h.Attachable && h.Attached))) .ToArray(); - RelevantLevelResources.Add(identifier, items); + relevantLevelResources.Add(identifier, items); } } private bool EnoughHaveBeenCollected() { - foreach (var kvp in ResourceClusters) + foreach (var kvp in resourceClusters) { - if (RelevantLevelResources.TryGetValue(kvp.Key, out var availableResources)) + if (relevantLevelResources.TryGetValue(kvp.Key, out var availableResources)) { var collected = availableResources.Count(r => HasBeenCollected(r)); - var needed = kvp.Value.First; + var needed = kvp.Value.amount; if (collected < needed) { return false; } } else @@ -210,8 +209,8 @@ namespace Barotrauma private void CalculateMissionClusterPositions() { - MissionClusterPositions.Clear(); - foreach (var kvp in SpawnedResources) + missionClusterPositions.Clear(); + foreach (var kvp in spawnedResources) { if (kvp.Value.None()) { continue; } var pos = Vector2.Zero; @@ -222,7 +221,7 @@ namespace Barotrauma itemCount++; } pos /= itemCount; - MissionClusterPositions.Add(new Tuple(kvp.Key, pos)); + missionClusterPositions.Add(new Tuple(kvp.Key, pos)); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs index 77747d13a..507d7b61f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs @@ -1,4 +1,6 @@ -using Microsoft.Xna.Framework; +using Barotrauma.Abilities; +using Barotrauma.Extensions; +using Microsoft.Xna.Framework; using System; using System.Collections.Generic; using System.Globalization; @@ -25,7 +27,7 @@ namespace Barotrauma state = value; TryTriggerEvents(state); #if SERVER - GameMain.Server?.UpdateMissionState(this, state); + GameMain.Server?.UpdateMissionState(this); #endif ShowMessage(State); } @@ -343,19 +345,57 @@ namespace Barotrauma public void GiveReward() { if (!(GameMain.GameSession.GameMode is CampaignMode campaign)) { return; } - campaign.Money += GetReward(Submarine.MainSub); + int reward = GetReward(Submarine.MainSub); + + float baseExperienceGain = reward * 0.09f; + + float difficultyMultiplier = 1 + level.Difficulty / 100f; + baseExperienceGain *= difficultyMultiplier; + + IEnumerable crewCharacters = GameSession.GetSessionCrewCharacters(); + + // use multipliers here so that we can easily add them together without introducing multiplicative XP stacking + var experienceGainMultiplier = new AbilityValue(1f); + crewCharacters.ForEach(c => c.CheckTalents(AbilityEffectType.OnAllyGainMissionExperience, experienceGainMultiplier)); + crewCharacters.ForEach(c => experienceGainMultiplier.Value += c.GetStatValue(StatTypes.MissionExperienceGainMultiplier)); + + int experienceGain = (int)(baseExperienceGain * experienceGainMultiplier.Value); +#if CLIENT + foreach (Character character in crewCharacters) + { + character.Info?.GiveExperience(experienceGain, isMissionExperience: true); + } +#else + foreach (Barotrauma.Networking.Client c in GameMain.Server.ConnectedClients) + { + //give the experience to the stored characterinfo if the client isn't currently controlling a character + (c.Character?.Info ?? c.CharacterInfo)?.GiveExperience(experienceGain, isMissionExperience: true); + } +#endif + + // apply money gains afterwards to prevent them from affecting XP gains + var moneyGainMission = new AbilityValueMission(1f, this); + crewCharacters.ForEach(c => c.CheckTalents(AbilityEffectType.OnGainMissionMoney, moneyGainMission)); + crewCharacters.ForEach(c => moneyGainMission.Value += c.GetStatValue(StatTypes.MissionMoneyGainMultiplier)); + + campaign.Money += (int)(reward * moneyGainMission.Value); + + foreach (Character character in crewCharacters) + { + character.Info.MissionsCompletedSinceDeath++; + } foreach (KeyValuePair reputationReward in ReputationRewards) { if (reputationReward.Key.Equals("location", StringComparison.OrdinalIgnoreCase)) { - Locations[0].Reputation.Value += reputationReward.Value; - Locations[1].Reputation.Value += reputationReward.Value; + Locations[0].Reputation.AddReputation(reputationReward.Value); + Locations[1].Reputation.AddReputation(reputationReward.Value); } else { Faction faction = campaign.Factions.Find(faction1 => faction1.Prefab.Identifier.Equals(reputationReward.Key, StringComparison.OrdinalIgnoreCase)); - if (faction != null) { faction.Reputation.Value += reputationReward.Value; } + if (faction != null) { faction.Reputation.AddReputation(reputationReward.Value); } } } @@ -443,5 +483,55 @@ namespace Barotrauma return spawnedCharacter; } + + protected ItemPrefab FindItemPrefab(XElement element) + { + ItemPrefab itemPrefab; + if (element.Attribute("name") != null) + { + DebugConsole.ThrowError($"Error in mission \"{Name}\" - use item identifiers instead of names to configure the items"); + string itemName = element.GetAttributeString("name", ""); + itemPrefab = MapEntityPrefab.Find(itemName) as ItemPrefab; + if (itemPrefab == null) + { + DebugConsole.ThrowError($"Couldn't spawn item for mission \"{Name}\": item prefab \"{itemName}\" not found"); + } + } + else + { + string itemIdentifier = element.GetAttributeString("identifier", ""); + itemPrefab = MapEntityPrefab.Find(null, itemIdentifier) as ItemPrefab; + if (itemPrefab == null) + { + DebugConsole.ThrowError($"Couldn't spawn item for mission \"{Name}\": item prefab \"{itemIdentifier}\" not found"); + } + } + return itemPrefab; + } + + protected Vector2? GetCargoSpawnPosition(ItemPrefab itemPrefab, out Submarine cargoRoomSub) + { + cargoRoomSub = null; + + WayPoint cargoSpawnPos = WayPoint.GetRandom(SpawnType.Cargo, null, Submarine.MainSub, useSyncedRand: true); + if (cargoSpawnPos == null) + { + DebugConsole.ThrowError($"Couldn't spawn items for mission \"{Name}\": no waypoints marked as Cargo were found"); + return null; + } + + var cargoRoom = cargoSpawnPos.CurrentHull; + if (cargoRoom == null) + { + DebugConsole.ThrowError($"Couldn't spawn items for mission \"{Name}\": waypoints marked as Cargo must be placed inside a room"); + return null; + } + + cargoRoomSub = cargoRoom.Submarine; + + return new Vector2( + cargoSpawnPos.Position.X + Rand.Range(-20.0f, 20.0f, Rand.RandSync.Server), + cargoRoom.Rect.Y - cargoRoom.Rect.Height + itemPrefab.Size.Y / 2); + } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MissionPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MissionPrefab.cs index 80be751bf..0fa150677 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MissionPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MissionPrefab.cs @@ -22,7 +22,9 @@ namespace Barotrauma Escort = 0x100, Pirate = 0x200, GoTo = 0x400, - All = Salvage | Monster | Cargo | Beacon | Nest | Mineral | Combat | AbandonedOutpost | Escort | Pirate | GoTo + ScanAlienRuins = 0x800, + ClearAlienRuins = 0x1000, + All = Salvage | Monster | Cargo | Beacon | Nest | Mineral | Combat | AbandonedOutpost | Escort | Pirate | GoTo | ScanAlienRuins | ClearAlienRuins } partial class MissionPrefab @@ -40,7 +42,9 @@ namespace Barotrauma { MissionType.AbandonedOutpost, typeof(AbandonedOutpostMission) }, { MissionType.Escort, typeof(EscortMission) }, { MissionType.Pirate, typeof(PirateMission) }, - { MissionType.GoTo, typeof(GoToMission) } + { MissionType.GoTo, typeof(GoToMission) }, + { MissionType.ScanAlienRuins, typeof(ScanMission) }, + { MissionType.ClearAlienRuins, typeof(AlienRuinMission) } }; public static readonly Dictionary PvPMissionClasses = new Dictionary() { @@ -372,6 +376,11 @@ namespace Barotrauma var connection = from.Connections.Find(c => c.Locations.Contains(from) && c.Locations.Contains(to)); if (connection?.LevelData == null || !connection.LevelData.HasBeaconStation || connection.LevelData.IsBeaconActive) { return false; } } + else if (Type == MissionType.ScanAlienRuins || Type == MissionType.ClearAlienRuins) + { + var connection = from.Connections.Find(c => c.Locations.Contains(from) && c.Locations.Contains(to)); + if (connection?.LevelData == null || connection.LevelData.GenerationParams.RuinCount < 1) { return false; } + } return false; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/PirateMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/PirateMission.cs index 6d7319de6..e9d3cef40 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/PirateMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/PirateMission.cs @@ -180,7 +180,11 @@ namespace Barotrauma var path = pathFinder.FindPath(ConvertUnits.ToSimUnits(patrolPos), ConvertUnits.ToSimUnits(preferredSpawnPos)); if (!path.Unreachable) { - preferredSpawnPos = path.Nodes[Rand.Range(0, path.Nodes.Count - 1)].WorldPosition; // spawn the sub in a random point in the path if possible + var validNodes = path.Nodes.FindAll(n => !Level.Loaded.ExtraWalls.Any(w => w.Cells.Any(c => c.IsPointInside(n.WorldPosition)))); + if (validNodes.Any()) + { + preferredSpawnPos = validNodes.GetRandom().WorldPosition; // spawn the sub in a random point in the path if possible + } } int graceDistance = 500; // the sub still spawns awkwardly close to walls, so this helps. could also be given as a parameter instead @@ -382,11 +386,11 @@ namespace Barotrauma State = newState; } - private bool CheckWinState() => !IsClient && (characters.All(m => !Survived(m))); + private bool CheckWinState() => !IsClient && characters.All(m => DeadOrCaptured(m)); - private bool Survived(Character character) + private bool DeadOrCaptured(Character character) { - return character != null && !character.Removed && !character.IsDead; + return character != null && !character.Removed && (character.IsDead || (character.LockHands && character.Submarine == Submarine.MainSub)); } public override void End() diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/SalvageMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/SalvageMission.cs index 36f611211..5f628660c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/SalvageMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/SalvageMission.cs @@ -126,12 +126,12 @@ namespace Barotrauma item = suitableItems.FirstOrDefault(it => Vector2.DistanceSquared(it.WorldPosition, position) < 1000.0f); break; case Level.PositionType.Ruin: - item = suitableItems.FirstOrDefault(it => it.ParentRuin != null && it.ParentRuin.Area.Contains(position)); - break; case Level.PositionType.Wreck: foreach (Item it in suitableItems) { - if (it.Submarine == null || it.Submarine.Info.Type != SubmarineType.Wreck) { continue; } + if (it.Submarine?.Info == null) { continue; } + if (spawnPositionType == Level.PositionType.Ruin && it.Submarine.Info.Type != SubmarineType.Ruin) { continue; } + if (spawnPositionType == Level.PositionType.Wreck && it.Submarine.Info.Type != SubmarineType.Wreck) { continue; } Rectangle worldBorders = it.Submarine.Borders; worldBorders.Location += it.Submarine.WorldPosition.ToPoint(); if (Submarine.RectContains(worldBorders, it.WorldPosition)) @@ -178,10 +178,10 @@ namespace Barotrauma { case Level.PositionType.Cave: case Level.PositionType.MainPath: - if (it.Submarine != null || it.ParentRuin != null) { continue; } + if (it.Submarine != null) { continue; } break; case Level.PositionType.Ruin: - if (it.ParentRuin == null) { continue; } + if (it.Submarine?.Info == null || !it.Submarine.Info.IsRuin) { continue; } break; case Level.PositionType.Wreck: if (it.Submarine == null || it.Submarine.Info.Type != SubmarineType.Wreck) { continue; } @@ -247,8 +247,8 @@ namespace Barotrauma public override void End() { - var root = item.GetRootContainer() ?? item; - if (root.CurrentHull?.Submarine == null || (!root.CurrentHull.Submarine.AtEndExit && !root.CurrentHull.Submarine.AtStartExit) || item.Removed) + var root = item?.GetRootContainer() ?? item; + if (root?.CurrentHull?.Submarine == null || (!root.CurrentHull.Submarine.AtEndExit && !root.CurrentHull.Submarine.AtStartExit) || item.Removed) { return; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/ScanMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/ScanMission.cs new file mode 100644 index 000000000..c84e43647 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/ScanMission.cs @@ -0,0 +1,286 @@ +using Barotrauma.Extensions; +using Barotrauma.Items.Components; +using Barotrauma.RuinGeneration; +using Microsoft.Xna.Framework; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma +{ + partial class ScanMission : Mission + { + private readonly XElement itemConfig; + private readonly List startingItems = new List(); + private readonly List scanners = new List(); + private readonly Dictionary parentInventoryIDs = new Dictionary(); + private readonly Dictionary parentItemContainerIndices = new Dictionary(); + private readonly int targetsToScan; + private readonly Dictionary scanTargets = new Dictionary(); + private readonly HashSet newTargetsScanned = new HashSet(); + private readonly float minTargetDistance; + + + private Ruin TargetRuin { get; set; } + + private bool AllTargetsScanned + { + get + { + return scanTargets.Any() && scanTargets.All(kvp => kvp.Value); + } + } + + public override IEnumerable SonarPositions + { + get + { + if (State > 0) + { + return Enumerable.Empty(); + } + else if (scanTargets.Any()) + { + return scanTargets + .Where(kvp => !kvp.Value) + .Select(kvp => kvp.Key.WorldPosition); + } + else + { + return Enumerable.Empty(); + } + + } + } + + public ScanMission(MissionPrefab prefab, Location[] locations, Submarine sub) : base(prefab, locations, sub) + { + itemConfig = prefab.ConfigElement.Element("Items"); + targetsToScan = prefab.ConfigElement.GetAttributeInt("targets", 1); + minTargetDistance = prefab.ConfigElement.GetAttributeFloat("mintargetdistance", 0.0f); + } + + protected override void StartMissionSpecific(Level level) + { + Reset(); + + if (IsClient) { return; } + + if (itemConfig == null) + { + DebugConsole.ThrowError("Failed to initialize a Scan mission: item config is not set"); + return; + } + + foreach (var element in itemConfig.Elements()) + { + LoadItem(element, null); + } + GetScanners(); + + TargetRuin = Level.Loaded?.Ruins?.GetRandom(randSync: Rand.RandSync.Server); + if (TargetRuin == null) + { + DebugConsole.ThrowError("Failed to initialize a Scan mission: level contains no alien ruins"); + return; + } + + var ruinWaypoints = TargetRuin.Submarine.GetWaypoints(false); + ruinWaypoints.RemoveAll(wp => wp.CurrentHull == null); + if (ruinWaypoints.Count < targetsToScan) + { + DebugConsole.ThrowError($"Failed to initialize a Scan mission: target ruin has less waypoints than required as scan targets ({ruinWaypoints.Count} < {targetsToScan})"); + return; + } + var availableWaypoints = new List(); + float minTargetDistanceSquared = minTargetDistance * minTargetDistance; + for (int tries = 0; tries < 15; tries++) + { + scanTargets.Clear(); + availableWaypoints.Clear(); + availableWaypoints.AddRange(ruinWaypoints); + for (int i = 0; i < targetsToScan; i++) + { + var selectedWaypoint = availableWaypoints.GetRandom(randSync: Rand.RandSync.Server); + scanTargets.Add(selectedWaypoint, false); + availableWaypoints.Remove(selectedWaypoint); + if (i < (targetsToScan - 1)) + { + availableWaypoints.RemoveAll(wp => wp.CurrentHull == selectedWaypoint.CurrentHull); + availableWaypoints.RemoveAll(wp => Vector2.DistanceSquared(wp.WorldPosition, selectedWaypoint.WorldPosition) < minTargetDistanceSquared); + if (availableWaypoints.None()) + { +#if DEBUG + DebugConsole.ThrowError($"Error initializing a Scan mission: not enough targets available on try #{tries + 1} to reach the required scan target count (current targets: {scanTargets.Count}, required targets: {targetsToScan})"); +#endif + break; + } + } + } + if (scanTargets.Count >= targetsToScan) + { +#if DEBUG + DebugConsole.NewMessage($"Successfully initialized a Scan mission: targets set on try #{tries + 1}", Color.Green); +#endif + break; + } + if ((tries + 1) % 5 == 0) + { + float reducedMinTargetDistance = (1.0f - (((tries + 1) / 5) * 0.1f)) * minTargetDistance; + minTargetDistanceSquared = reducedMinTargetDistance * reducedMinTargetDistance; +#if DEBUG + DebugConsole.NewMessage($"Reducing minimum distance between Scan mission targets (new min: {reducedMinTargetDistance}) to reach the required target count", Color.Yellow); +#endif + } + } + if (scanTargets.Count < targetsToScan) + { + DebugConsole.ThrowError($"Error initializing a Scan mission: not enough targets (current targets: {scanTargets.Count}, required targets: {targetsToScan})"); + } + } + + private void Reset() + { + startingItems.Clear(); + parentInventoryIDs.Clear(); + parentItemContainerIndices.Clear(); + scanners.Clear(); + TargetRuin = null; + scanTargets.Clear(); + } + + private void LoadItem(XElement element, Item parent) + { + var itemPrefab = FindItemPrefab(element); + Vector2? position = GetCargoSpawnPosition(itemPrefab, out Submarine cargoRoomSub); + if (!position.HasValue) { return; } + var item = new Item(itemPrefab, position.Value, cargoRoomSub); + item.FindHull(); + startingItems.Add(item); + if (parent?.GetComponent() is ItemContainer itemContainer) + { + parentInventoryIDs.Add(item, parent.ID); + parentItemContainerIndices.Add(item, (byte)parent.GetComponentIndex(itemContainer)); + parent.Combine(item, user: null); + } + foreach (XElement subElement in element.Elements()) + { + int amount = subElement.GetAttributeInt("amount", 1); + for (int i = 0; i < amount; i++) + { + LoadItem(subElement, item); + } + } + } + + private void GetScanners() + { + foreach (var startingItem in startingItems) + { + if (startingItem.GetComponent() is Scanner scanner) + { + scanner.OnScanStarted += OnScanStarted; + if (!IsClient) + { + scanner.OnScanCompleted += OnScanCompleted; + } + scanners.Add(scanner); + } + } + } + + private void OnScanStarted(Scanner scanner) + { + float scanRadiusSquared = scanner.ScanRadius * scanner.ScanRadius; + foreach (var kvp in scanTargets) + { + if (!IsValidScanPosition(scanner, kvp, scanRadiusSquared)) { continue; } + scanner.DisplayProgressBar = true; + break; + } + } + + private void OnScanCompleted(Scanner scanner) + { + if (IsClient) { return; } + newTargetsScanned.Clear(); + float scanRadiusSquared = scanner.ScanRadius * scanner.ScanRadius; + foreach (var kvp in scanTargets) + { + if (!IsValidScanPosition(scanner, kvp, scanRadiusSquared)) { continue; } + newTargetsScanned.Add(kvp.Key); + } + foreach (var wp in newTargetsScanned) + { + scanTargets[wp] = true; + } +#if SERVER + // Server should make sure that the clients' scan target status is in-sync + GameMain.Server?.UpdateMissionState(this); +#endif + } + + private bool IsValidScanPosition(Scanner scanner, KeyValuePair scanStatus, float scanRadiusSquared) + { + if (scanStatus.Value) { return false; } + if (scanStatus.Key.Submarine != scanner.Item.Submarine) { return false; } + if (Vector2.DistanceSquared(scanStatus.Key.WorldPosition, scanner.Item.WorldPosition) > scanRadiusSquared) { return false; } + return true; + } + + protected override void UpdateMissionSpecific(float deltaTime) + { + if (IsClient) { return; } + switch (State) + { + case 0: + if (!AllTargetsScanned) { return; } + State = 1; + break; + case 1: + if (!Submarine.MainSub.AtEndExit && !Submarine.MainSub.AtStartExit) { return; } + State = 2; + break; + } + } + + public override void End() + { + if (State == 2 && AllScannersReturned()) + { + GiveReward(); + completed = true; + } + foreach (var scanner in scanners) + { + if (scanner.Item != null && !scanner.Item.Removed) + { + scanner.OnScanStarted -= OnScanStarted; + scanner.OnScanCompleted -= OnScanCompleted; + scanner.Item.Remove(); + } + } + Reset(); + failed = !completed && state > 0; + + bool AllScannersReturned() + { + foreach (var scanner in scanners) + { + if (scanner?.Item == null || scanner.Item.Removed) { return false; } + var owner = scanner.Item.GetRootInventoryOwner(); + if (owner.Submarine != null && owner.Submarine.Info.Type == SubmarineType.Player) + { + continue; + } + else if (owner is Character c && c.Info != null && GameMain.GameSession.CrewManager.CharacterInfos.Contains(c.Info)) + { + continue; + } + return false; + } + return true; + } + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/MonsterEvent.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/MonsterEvent.cs index 3edb99bdd..4cfd77e2f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/MonsterEvent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/MonsterEvent.cs @@ -21,10 +21,11 @@ namespace Barotrauma private bool disallowed; private readonly Level.PositionType spawnPosType; + private readonly string spawnPointTag; private bool spawnPending; - private int maxAmountPerLevel = int.MaxValue; + private readonly int maxAmountPerLevel = int.MaxValue; public List Monsters => monsters; public Vector2? SpawnPos => spawnPos; @@ -87,6 +88,8 @@ namespace Barotrauma spawnPosType = Level.PositionType.Abyss; } + spawnPointTag = prefab.ConfigElement.GetAttributeString("spawnpointtag", string.Empty); + offset = prefab.ConfigElement.GetAttributeFloat("offset", 0); scatter = Math.Clamp(prefab.ConfigElement.GetAttributeFloat("scatter", 500), 0, 3000); @@ -285,19 +288,19 @@ namespace Barotrauma spawnPos = chosenPosition.Position.ToVector2(); if (chosenPosition.Submarine != null || chosenPosition.Ruin != null) { - var spawnPoint = WayPoint.GetRandom(SpawnType.Enemy, sub: chosenPosition.Submarine, ruin: chosenPosition.Ruin, useSyncedRand: false); - if (spawnPoint != null) + var spawnPoint = + WayPoint.GetRandom(SpawnType.Enemy, sub: chosenPosition.Submarine ?? chosenPosition.Ruin?.Submarine, useSyncedRand: false, spawnPointTag: spawnPointTag); + if (spawnPoint != null) { - System.Diagnostics.Debug.Assert(spawnPoint.Submarine == chosenPosition.Submarine); - System.Diagnostics.Debug.Assert(spawnPoint.ParentRuin == chosenPosition.Ruin); - spawnPos = spawnPoint.WorldPosition; + System.Diagnostics.Debug.Assert(spawnPoint.Submarine == (chosenPosition.Submarine ?? chosenPosition.Ruin?.Submarine)); + spawnPos = spawnPoint.WorldPosition; } else - { + { //no suitable position found, disable the event spawnPos = null; Finished(); - return; + return; } } else if ((chosenPosition.PositionType == Level.PositionType.MainPath || chosenPosition.PositionType == Level.PositionType.SidePath) @@ -448,7 +451,7 @@ namespace Barotrauma for (int i = 0; i < amount; i++) { string seed = Level.Loaded.Seed + i.ToString(); - CoroutineManager.InvokeAfter(() => + CoroutineManager.Invoke(() => { //round ended before the coroutine finished if (GameMain.GameSession == null || Level.Loaded == null) { return; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Extensions/ColorExtensions.cs b/Barotrauma/BarotraumaShared/SharedSource/Extensions/ColorExtensions.cs similarity index 69% rename from Barotrauma/BarotraumaClient/ClientSource/Extensions/ColorExtensions.cs rename to Barotrauma/BarotraumaShared/SharedSource/Extensions/ColorExtensions.cs index 3de8f949c..89fc4cefa 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Extensions/ColorExtensions.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Extensions/ColorExtensions.cs @@ -1,5 +1,4 @@ using Microsoft.Xna.Framework; -using System; namespace Barotrauma.Extensions { @@ -12,6 +11,11 @@ namespace Barotrauma.Extensions new Color((byte)(color.R * value), (byte)(color.G * value), (byte)(color.B * value), (byte)(color.A * value)); } + public static Color Multiply(this Color thisColor, Color color) + { + return new Color((byte)(thisColor.R * color.R / 255f), (byte)(thisColor.G * color.G / 255f), (byte)(thisColor.B * color.B / 255f), (byte)(thisColor.A * color.A / 255f)); + } + public static Color Opaque(this Color color) { return new Color(color.R, color.G, color.B, (byte)255); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Extensions/VectorExtensions.cs b/Barotrauma/BarotraumaShared/SharedSource/Extensions/VectorExtensions.cs index 479b031d3..25c6dba97 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Extensions/VectorExtensions.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Extensions/VectorExtensions.cs @@ -92,5 +92,12 @@ namespace Barotrauma.Extensions { return MathUtils.NearlyEqual(v.X, other.X) && MathUtils.NearlyEqual(v.Y, other.Y); } + + public static Vector2 Pad(this Vector2 v, Vector4 padding) + { + v.X += padding.X + padding.Z; + v.Y += padding.Y + padding.W; + return v; + } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/AutoItemPlacer.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/AutoItemPlacer.cs index c93b28077..8f0c37464 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/AutoItemPlacer.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/AutoItemPlacer.cs @@ -8,14 +8,17 @@ namespace Barotrauma { static class AutoItemPlacer { - private static readonly List spawnedItems = new List(); - public static bool OutputDebugInfo = false; + /// + /// If we are spawning in an area where difficulty should not be a factor, assume difficulty is at the exact "middle" + /// + public const float DefaultDifficultyModifier = 0f; + public static void PlaceIfNeeded() { if (GameMain.NetworkMember != null && !GameMain.NetworkMember.IsServer) { return; } - + for (int i = 0; i < Submarine.MainSubs.Length; i++) { if (Submarine.MainSubs[i] == null || Submarine.MainSubs[i].Info.InitialSuppliesSpawned) { continue; } @@ -24,14 +27,18 @@ namespace Barotrauma Place(subs); subs.ForEach(s => s.Info.InitialSuppliesSpawned = true); } - + + float difficultyModifier = GetLevelDifficultyModifier(); foreach (var sub in Submarine.Loaded) { - if (sub.Info.Type == SubmarineType.Wreck || - sub.Info.Type == SubmarineType.BeaconStation) + if (sub.Info.Type == SubmarineType.Player || + sub.Info.Type == SubmarineType.Outpost || + sub.Info.Type == SubmarineType.OutpostModule || + sub.Info.Type == SubmarineType.EnemySubmarine) { - Place(sub.ToEnumerable()); + continue; } + Place(sub.ToEnumerable(), difficultyModifier: difficultyModifier); } if (Level.Loaded?.StartOutpost != null && Level.Loaded.Type == LevelData.LevelType.Outpost) @@ -41,7 +48,23 @@ namespace Barotrauma } } - private static void Place(IEnumerable subs) + private const float MaxDifficultyModifier = 0.2f; + + /// + /// Spawn probability of loot is modified by difficulty, -20% less loot at 0% difficulty and +20% loot at 100% difficulty. + /// + private static float GetLevelDifficultyModifier() + { + return Math.Clamp(Level.Loaded?.Difficulty is float difficulty ? (difficulty / 100f) * (MaxDifficultyModifier * 2) - MaxDifficultyModifier : DefaultDifficultyModifier, -MaxDifficultyModifier, MaxDifficultyModifier); + } + + public static void RegenerateLoot(Submarine sub, ItemContainer regeneratedContainer) + { + // Level difficulty currently doesn't affect regenerated loot for the sake of simplicity + Place(sub.ToEnumerable(), regeneratedContainer: regeneratedContainer); + } + + private static void Place(IEnumerable subs, ItemContainer regeneratedContainer = null, float difficultyModifier = DefaultDifficultyModifier) { if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { @@ -49,19 +72,29 @@ namespace Barotrauma return; } + List spawnedItems = new List(100); + int itemCountApprox = MapEntityPrefab.List.Count() / 3; var containers = new List(70 + 30 * subs.Count()); var prefabsWithContainer = new List(itemCountApprox / 3); var prefabsWithoutContainer = new List(itemCountApprox); var removals = new List(); - foreach (Item item in Item.ItemList) + // generate loot only for a specific container if defined + if (regeneratedContainer != null) { - if (!subs.Contains(item.Submarine)) { continue; } - if (item.GetRootInventoryOwner() is Character) { continue; } - containers.AddRange(item.GetComponents()); + containers.Add(regeneratedContainer); + } + else + { + foreach (Item item in Item.ItemList) + { + if (!subs.Contains(item.Submarine)) { continue; } + if (item.GetRootInventoryOwner() is Character) { continue; } + containers.AddRange(item.GetComponents()); + } + containers.Shuffle(Rand.RandSync.Server); } - containers.Shuffle(Rand.RandSync.Server); foreach (MapEntityPrefab prefab in MapEntityPrefab.List) { @@ -77,7 +110,6 @@ namespace Barotrauma } } - spawnedItems.Clear(); var validContainers = new Dictionary(); prefabsWithContainer.Shuffle(Rand.RandSync.Server); // Spawn items that have an ItemContainer component first so we can fill them up with items if needed (oxygen tanks inside the spawned diving masks, etc) @@ -152,8 +184,10 @@ namespace Barotrauma } foreach (var validContainer in validContainers) { - if (SpawnItem(itemPrefab, containers, validContainer)) + var newItems = SpawnItem(itemPrefab, containers, validContainer, difficultyModifier); + if (newItems.Any()) { + spawnedItems.AddRange(newItems); success = true; } } @@ -184,14 +218,22 @@ namespace Barotrauma return validContainers; } - private static bool SpawnItem(ItemPrefab itemPrefab, List containers, KeyValuePair validContainer) + private static readonly (int quality, float commonness)[] qualityCommonnesses = new (int quality, float commonness)[Quality.MaxQuality + 1] { - bool success = false; - if (Rand.Value(Rand.RandSync.Server) > validContainer.Value.SpawnProbability) { return false; } + (0, 0.85f), + (1, 0.125f), + (2, 0.0225f), + (3, 0.0025f), + }; + + private static List SpawnItem(ItemPrefab itemPrefab, List containers, KeyValuePair validContainer, float difficultyModifier) + { + List spawnedItems = new List(); + if (Rand.Value(Rand.RandSync.Server) > validContainer.Value.SpawnProbability * (1f + difficultyModifier)) { return spawnedItems; } // Don't add dangerously reactive materials in thalamus wrecks if (validContainer.Key.Item.Submarine.WreckAI != null && itemPrefab.Tags.Contains("explodesinwater")) { - return false; + return spawnedItems; } int amount = Rand.Range(validContainer.Value.MinAmount, validContainer.Value.MaxAmount + 1, Rand.RandSync.Server); for (int i = 0; i < amount; i++) @@ -201,11 +243,20 @@ namespace Barotrauma containers.Remove(validContainer.Key); break; } - if (!validContainer.Key.Inventory.CanBePut(itemPrefab)) { break; } + + var existingItem = validContainer.Key.Inventory.AllItems.FirstOrDefault(it => it.prefab == itemPrefab); + int quality = + existingItem?.Quality ?? + ToolBox.SelectWeightedRandom( + qualityCommonnesses.Select(q => q.quality).ToList(), + qualityCommonnesses.Select(q => q.commonness).ToList(), + Rand.RandSync.Server); + if (!validContainer.Key.Inventory.CanBePut(itemPrefab, quality: quality)) { break; } var item = new Item(itemPrefab, validContainer.Key.Item.Position, validContainer.Key.Item.Submarine) { SpawnedInOutpost = validContainer.Key.Item.SpawnedInOutpost, AllowStealing = validContainer.Key.Item.AllowStealing, + Quality = quality, OriginalModuleIndex = validContainer.Key.Item.OriginalModuleIndex, OriginalContainerIndex = Item.ItemList.Where(it => it.Submarine == validContainer.Key.Item.Submarine && it.OriginalModuleIndex == validContainer.Key.Item.OriginalModuleIndex).ToList().IndexOf(validContainer.Key.Item) @@ -217,9 +268,8 @@ namespace Barotrauma spawnedItems.Add(item); validContainer.Key.Inventory.TryPutItem(item, null, createNetworkEvent: false); containers.AddRange(item.GetComponents()); - success = true; } - return success; + return spawnedItems; } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/CrewManager.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/CrewManager.cs index 8eed7fcfa..5aa20f202 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/CrewManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/CrewManager.cs @@ -237,6 +237,9 @@ namespace Barotrauma { CharacterInfo.ApplyHealthData(character, character.Info.HealthData); } + + character.LoadTalents(); + character.GiveIdCardTags(spawnWaypoints[i]); character.Info.StartItemsGiven = true; if (character.Info.OrderData != null) @@ -486,6 +489,11 @@ namespace Barotrauma continue; } } + if (orderInfo.Order.TargetEntity == null || (orderInfo.Order.IsIgnoreOrder && ignoreTarget == null)) + { + // The order target doesn't exist anymore, just discard the loaded order + continue; + } if (ignoreTarget != null) { ignoreTarget.OrderedToBeIgnored = true; diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/Data/Reputation.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/Data/Reputation.cs index 9944fd2bc..85a3ef2f4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/Data/Reputation.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/Data/Reputation.cs @@ -1,5 +1,6 @@ using Microsoft.Xna.Framework; using System; +using System.Linq; namespace Barotrauma { @@ -31,7 +32,7 @@ namespace Barotrauma public float Value { get => Math.Min(MaxReputation, Metadata.GetFloat(metaDataIdentifier, InitialReputation)); - set + private set { if (MathUtils.NearlyEqual(Value, value)) { return; } Metadata.SetValue(metaDataIdentifier, Math.Clamp(value, MinReputation, MaxReputation)); @@ -40,6 +41,25 @@ namespace Barotrauma } } + public void SetReputation(float newReputation) + { + Value = newReputation; + } + + public void AddReputation(float reputationChange) + { + if (reputationChange > 0f) + { + float reputationGainMultiplier = 1f; + foreach (Character character in GameSession.GetSessionCrewCharacters()) + { + reputationGainMultiplier += character.GetStatValue(StatTypes.ReputationGainMultiplier); + } + reputationChange *= reputationGainMultiplier; + } + Value += reputationChange; + } + public Action OnReputationValueChanged; public static Action OnAnyReputationValueChanged; diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs index 9c7e037e4..a1dfda365 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs @@ -1,12 +1,11 @@ using Barotrauma.Items.Components; +using Barotrauma.Networking; using FarseerPhysics; using Microsoft.Xna.Framework; using System; using System.Collections.Generic; using System.Linq; using System.Xml.Linq; -using Barotrauma.Networking; -using Barotrauma.Extensions; namespace Barotrauma { @@ -17,7 +16,15 @@ namespace Barotrauma // Anything that uses this field I wasn't sure if actually needed the proper campaign settings to be passed down public static CampaignSettings Unsure = Empty; public bool RadiationEnabled { get; set; } - public int MaxMissionCount { get; set; } + + public int TotalMaxMissionCount => MaxMissionCount + GetAddedMissionCount(); + + private int maxMissionCount; + public int MaxMissionCount + { + get { return maxMissionCount; } + set { maxMissionCount = MathHelper.Clamp(value, MinMissionCountLimit, MaxMissionCountLimit); } + } public const int DefaultMaxMissionCount = 2; public const int MaxMissionCountLimit = 10; @@ -25,14 +32,16 @@ namespace Barotrauma public CampaignSettings(IReadMessage inc) { + maxMissionCount = DefaultMaxMissionCount; RadiationEnabled = inc.ReadBoolean(); MaxMissionCount = inc.ReadInt32(); } - + public CampaignSettings(XElement element) { - RadiationEnabled = element.GetAttributeBool(nameof(RadiationEnabled).ToLower(), true); - MaxMissionCount = element.GetAttributeInt(nameof(MaxMissionCount).ToLower(), DefaultMaxMissionCount); + maxMissionCount = DefaultMaxMissionCount; + RadiationEnabled = element.GetAttributeBool(nameof(RadiationEnabled).ToLowerInvariant(), true); + MaxMissionCount = element.GetAttributeInt(nameof(MaxMissionCount).ToLowerInvariant(), DefaultMaxMissionCount); } public void Serialize(IWriteMessage msg) @@ -41,9 +50,19 @@ namespace Barotrauma msg.Write(MaxMissionCount); } + public int GetAddedMissionCount() + { + int count = 0; + foreach (Character character in GameSession.GetSessionCrewCharacters()) + { + count += (int)character.GetStatValue(StatTypes.ExtraMissionCount); + } + return count; + } + public XElement Save() { - return new XElement(nameof(CampaignSettings), new XAttribute(nameof(RadiationEnabled).ToLower(), RadiationEnabled), new XAttribute(nameof(MaxMissionCount).ToLower().ToLower(), MaxMissionCount)); + return new XElement(nameof(CampaignSettings), new XAttribute(nameof(RadiationEnabled).ToLowerInvariant(), RadiationEnabled), new XAttribute(nameof(MaxMissionCount).ToLowerInvariant(), MaxMissionCount)); } } @@ -226,6 +245,8 @@ namespace Barotrauma PurchasedLostShuttles = false; var connectedSubs = Submarine.MainSub.GetConnectedSubs(); wasDocked = Level.Loaded.StartOutpost != null && connectedSubs.Contains(Level.Loaded.StartOutpost); + + ResetTalentData(); } public void InitCampaignData() @@ -522,6 +543,7 @@ namespace Barotrauma if (Level.Loaded.EndOutpost == null) { Submarine closestSub = Submarine.FindClosest(Level.Loaded.EndExitPosition, ignoreOutposts: true, ignoreRespawnShuttle: true, teamType: leavingPlayers.FirstOrDefault()?.TeamID); + if (closestSub == null) { return null; } return closestSub.DockedTo.Contains(Submarine.MainSub) ? Submarine.MainSub : closestSub; } else @@ -546,13 +568,16 @@ namespace Barotrauma public override void End(CampaignMode.TransitionType transitionType = CampaignMode.TransitionType.None) { List takenItems = new List(); - foreach (Item item in Item.ItemList) + if (Level.Loaded?.Type == LevelData.LevelType.Outpost) { - if (!item.SpawnedInOutpost || item.OriginalModuleIndex < 0) { continue; } - var owner = item.GetRootInventoryOwner(); - if ((!(owner?.Submarine?.Info?.IsOutpost ?? false)) || (owner is Character character && character.TeamID == CharacterTeamType.Team1) || item.Submarine == null || !item.Submarine.Info.IsOutpost) + foreach (Item item in Item.ItemList) { - takenItems.Add(item); + if (!item.SpawnedInOutpost || item.OriginalModuleIndex < 0) { continue; } + var owner = item.GetRootInventoryOwner(); + if ((!(owner?.Submarine?.Info?.IsOutpost ?? false)) || (owner is Character character && character.TeamID == CharacterTeamType.Team1) || item.Submarine == null || !item.Submarine.Info.IsOutpost) + { + takenItems.Add(item); + } } } if (map != null && CargoManager != null) @@ -642,16 +667,7 @@ namespace Barotrauma } foreach (Location location in Map.Locations) { - if (location.Type != location.OriginalType) - { - location.ChangeType(location.OriginalType); - location.PendingLocationTypeChange = null; - } - location.CreateStore(force: true); - location.ClearMissions(); - location.Discovered = false; - location.LevelData?.EventHistory?.Clear(); - location.UnlockInitialMissions(); + location.Reset(); } Map.SetLocation(Map.Locations.IndexOf(Map.StartLocation)); Map.SelectLocation(-1); @@ -846,7 +862,7 @@ namespace Barotrauma Location location = Map?.CurrentLocation; if (location != null) { - location.Reputation.Value -= attackResult.Damage * Reputation.ReputationLossPerNPCDamage; + location.Reputation.AddReputation(-attackResult.Damage * Reputation.ReputationLossPerNPCDamage); } } @@ -898,15 +914,24 @@ namespace Barotrauma { foreach (Location location in currentLocation.Connections.Select(c => c.OtherLocation(currentLocation))) { - if (NumberOfMissionsAtLocation(location) > Settings.MaxMissionCount) + if (NumberOfMissionsAtLocation(location) > Settings.TotalMaxMissionCount) { DebugConsole.AddWarning($"Client {sender.Name} had too many missions selected for location {location.Name}! Count was {NumberOfMissionsAtLocation(location)}. Deselecting extra missions."); - foreach (Mission mission in currentLocation.SelectedMissions.Where(m => m.Locations[1] == location).Skip(Settings.MaxMissionCount).ToList()) + foreach (Mission mission in currentLocation.SelectedMissions.Where(m => m.Locations[1] == location).Skip(Settings.TotalMaxMissionCount).ToList()) { currentLocation.DeselectMission(mission); } } } } + + // Talent relevant data, only stored for the duration of the mission + private void ResetTalentData() + { + CrewHasDied = false; + } + + public bool CrewHasDied { get; set; } + } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CharacterCampaignData.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CharacterCampaignData.cs index 163255fdd..0df3b6f0c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CharacterCampaignData.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CharacterCampaignData.cs @@ -1,6 +1,4 @@ -using Barotrauma.Networking; -using System.Globalization; -using System.Xml.Linq; +using System.Xml.Linq; namespace Barotrauma { @@ -29,65 +27,6 @@ namespace Barotrauma private XElement healthData; public XElement OrderData { get; private set; } - partial void InitProjSpecific(Client client); - public CharacterCampaignData(Client client, bool giveRespawnPenaltyAffliction = false) - { - Name = client.Name; - InitProjSpecific(client); - - healthData = new XElement("health"); - client.Character?.CharacterHealth?.Save(healthData); - if (giveRespawnPenaltyAffliction) - { - var respawnPenaltyAffliction = RespawnManager.GetRespawnPenaltyAffliction(); - healthData.Add(new XElement("Affliction", - new XAttribute("identifier", respawnPenaltyAffliction.Identifier), - new XAttribute("strength", respawnPenaltyAffliction.Strength.ToString("G", CultureInfo.InvariantCulture)))); - } - if (client.Character?.Inventory != null) - { - itemData = new XElement("inventory"); - Character.SaveInventory(client.Character.Inventory, itemData); - } - OrderData = new XElement("orders"); - if (client.Character != null) - { - CharacterInfo.SaveOrderData(client.Character.Info, OrderData); - } - } - - public CharacterCampaignData(XElement element) - { - Name = element.GetAttributeString("name", "Unnamed"); - ClientEndPoint = element.GetAttributeString("endpoint", null) ?? element.GetAttributeString("ip", ""); - string steamID = element.GetAttributeString("steamid", ""); - if (!string.IsNullOrEmpty(steamID)) - { - ulong.TryParse(steamID, out ulong parsedID); - SteamID = parsedID; - } - - foreach (XElement subElement in element.Elements()) - { - switch (subElement.Name.ToString().ToLowerInvariant()) - { - case "character": - case "characterinfo": - CharacterInfo = new CharacterInfo(subElement); - break; - case "inventory": - itemData = subElement; - break; - case "health": - healthData = subElement; - break; - case "orders": - OrderData = subElement; - break; - } - } - } - public void Refresh(Character character) { healthData = new XElement("health"); diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/MultiPlayerCampaign.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/MultiPlayerCampaign.cs index b5bb43eca..739f6f112 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/MultiPlayerCampaign.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/MultiPlayerCampaign.cs @@ -173,6 +173,12 @@ namespace Barotrauma if (matchingSub != null) { availableSubs.Add(matchingSub); } } break; + case "savedexperiencepoints": + foreach (XElement savedExp in subElement.Elements()) + { + savedExperiencePoints.Add(new SavedExperiencePoints(savedExp)); + } + break; #endif } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs index b17a7ec74..9523e5299 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs @@ -29,6 +29,8 @@ namespace Barotrauma public bool IsRunning { get; private set; } + public bool RoundEnding { get; private set; } + public Level Level { get; private set; } public LevelData LevelData { get; private set; } @@ -450,9 +452,11 @@ namespace Barotrauma StatusEffect.StopAll(); #if CLIENT +#if !DEBUG GameMain.LightManager.LosEnabled = GameMain.Client == null || GameMain.Client.CharacterInfo != null; +#endif if (GameMain.LightManager.LosEnabled) { GameMain.LightManager.LosAlpha = 1f; } - if (GameMain.Client == null) GameMain.LightManager.LosMode = GameMain.Config.LosMode; + if (GameMain.Client == null) { GameMain.LightManager.LosMode = GameMain.Config.LosMode; } #endif LevelData = level?.LevelData; Level = level; @@ -654,43 +658,89 @@ namespace Barotrauma partial void UpdateProjSpecific(float deltaTime); + public static IEnumerable GetSessionCrewCharacters() + { +#if SERVER + return GameMain.Server.ConnectedClients.Select(c => c.Character).Where(c => c?.Info != null && !c.IsDead); +#else + if (GameMain.GameSession == null) { return Enumerable.Empty(); } + return GameMain.GameSession.CrewManager.GetCharacters().Where(c => c?.Info != null && !c.IsDead); +#endif + } + public void EndRound(string endMessage, List traitorResults = null, CampaignMode.TransitionType transitionType = CampaignMode.TransitionType.None) { - foreach (Mission mission in missions) - { - mission.End(); - } -#if CLIENT - if (GUI.PauseMenuOpen) - { - GUI.TogglePauseMenu(); - } - GUI.PreventPauseMenuToggle = true; + RoundEnding = true; - if (!(GameMode is TestGameMode) && Screen.Selected == GameMain.GameScreen && RoundSummary != null) + try { - GUI.ClearMessages(); - GUIMessageBox.MessageBoxes.RemoveAll(mb => mb.UserData is RoundSummary); - GUIFrame summaryFrame = RoundSummary.CreateSummaryFrame(this, endMessage, traitorResults, transitionType); - GUIMessageBox.MessageBoxes.Add(summaryFrame); - RoundSummary.ContinueButton.OnClicked = (_, __) => { GUIMessageBox.MessageBoxes.Remove(summaryFrame); return true; }; - } + IEnumerable crewCharacters = GetSessionCrewCharacters(); - if (GameMain.NetLobbyScreen != null) GameMain.NetLobbyScreen.OnRoundEnded(); - TabMenu.OnRoundEnded(); - GUIMessageBox.MessageBoxes.RemoveAll(mb => mb.UserData as string == "ConversationAction" || ReadyCheck.IsReadyCheck(mb)); -#endif - SteamAchievementManager.OnRoundEnded(this); + foreach (Mission mission in missions) + { + mission.End(); + } - GameMode?.End(transitionType); - EventManager?.EndRound(); - StatusEffect.StopAll(); - missions.Clear(); - IsRunning = false; + foreach (Character character in crewCharacters) + { + character.CheckTalents(AbilityEffectType.OnRoundEnd); + } + + if (missions.Any()) + { + if (missions.Any(m => m.Completed)) + { + foreach (Character character in crewCharacters) + { + character.CheckTalents(AbilityEffectType.OnAnyMissionCompleted); + } + } + + if (missions.All(m => m.Completed)) + { + foreach (Character character in crewCharacters) + { + character.CheckTalents(AbilityEffectType.OnAllMissionsCompleted); + } + } + } #if CLIENT - HintManager.OnRoundEnded(); + if (GUI.PauseMenuOpen) + { + GUI.TogglePauseMenu(); + } + GUI.PreventPauseMenuToggle = true; + + if (!(GameMode is TestGameMode) && Screen.Selected == GameMain.GameScreen && RoundSummary != null) + { + GUI.ClearMessages(); + GUIMessageBox.MessageBoxes.RemoveAll(mb => mb.UserData is RoundSummary); + GUIFrame summaryFrame = RoundSummary.CreateSummaryFrame(this, endMessage, traitorResults, transitionType); + GUIMessageBox.MessageBoxes.Add(summaryFrame); + RoundSummary.ContinueButton.OnClicked = (_, __) => { GUIMessageBox.MessageBoxes.Remove(summaryFrame); return true; }; + } + + if (GameMain.NetLobbyScreen != null) { GameMain.NetLobbyScreen.OnRoundEnded(); } + TabMenu.OnRoundEnded(); + GUIMessageBox.MessageBoxes.RemoveAll(mb => mb.UserData as string == "ConversationAction" || ReadyCheck.IsReadyCheck(mb)); #endif + SteamAchievementManager.OnRoundEnded(this); + + GameMode?.End(transitionType); + EventManager?.EndRound(); + StatusEffect.StopAll(); + missions.Clear(); + IsRunning = false; + +#if CLIENT + HintManager.OnRoundEnded(); +#endif + } + finally + { + RoundEnding = false; + } } public void KillCharacter(Character character) diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSettings.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSettings.cs index d5cc99aea..b8b37bcc3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSettings.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSettings.cs @@ -143,14 +143,7 @@ namespace Barotrauma return true; } - public int CharacterHeadIndex { get; set; } - public int CharacterHairIndex { get; set; } - public int CharacterBeardIndex { get; set; } - public int CharacterMoustacheIndex { get; set; } - public int CharacterFaceAttachmentIndex { get; set; } - - public Gender CharacterGender { get; set; } - public Race CharacterRace { get; set; } + internal CharacterInfo.HeadInfo PlayerCharacterCustomization { get; set; } private float aimAssistAmount; public float AimAssistAmount @@ -307,6 +300,7 @@ namespace Barotrauma public bool AutomaticQuickStartEnabled { get; set; } public bool AutomaticCampaignLoadEnabled { get; set; } public bool TextManagerDebugModeEnabled { get; set; } + public bool TestScreenEnabled { get; set; } public bool ModBreakerMode { get; set; } #endif @@ -548,6 +542,12 @@ namespace Barotrauma case ContentType.Text: TextManager.LoadTextPack(file.Path); break; + case ContentType.Talents: + TalentPrefab.LoadFromFile(file); + break; + case ContentType.TalentTrees: + TalentTree.LoadFromFile(file); + break; #if CLIENT case ContentType.Particles: GameMain.ParticleManager?.LoadPrefabsFromFile(file); @@ -594,6 +594,12 @@ namespace Barotrauma case ContentType.Text: TextManager.RemoveTextPack(file.Path); break; + case ContentType.Talents: + TalentPrefab.LoadFromFile(file); + break; + case ContentType.TalentTrees: + TalentTree.LoadFromFile(file); + break; #if CLIENT case ContentType.Particles: GameMain.ParticleManager?.RemovePrefabsByFile(file.Path); @@ -703,7 +709,6 @@ namespace Barotrauma public string MasterServerUrl { get; set; } public string RemoteContentUrl { get; set; } public bool AutoCheckUpdates { get; set; } - public bool WasGameUpdated { get; set; } private string playerName; public string PlayerName @@ -796,13 +801,6 @@ namespace Barotrauma LoadDefaultConfig(); - if (WasGameUpdated) - { - UpdaterUtil.CleanOldFiles(); - WasGameUpdated = false; - SaveNewDefaultConfig(); - } - LoadPlayerConfig(); } @@ -827,7 +825,6 @@ namespace Barotrauma MasterServerUrl = doc.Root.GetAttributeString("masterserverurl", MasterServerUrl); RemoteContentUrl = doc.Root.GetAttributeString("remotecontenturl", RemoteContentUrl); - WasGameUpdated = doc.Root.GetAttributeBool("wasgameupdated", WasGameUpdated); VerboseLogging = doc.Root.GetAttributeBool("verboselogging", VerboseLogging); SaveDebugConsoleLogs = doc.Root.GetAttributeBool("savedebugconsolelogs", SaveDebugConsoleLogs); AutoUpdateWorkshopItems = doc.Root.GetAttributeBool("autoupdateworkshopitems", AutoUpdateWorkshopItems); @@ -851,176 +848,6 @@ namespace Barotrauma UnsavedSettings = false; } - private void SaveNewDefaultConfig() - { - XDocument doc = new XDocument(); - - if (doc.Root == null) - { - doc.Add(new XElement("config")); - } - - doc.Root.Add( - new XAttribute("language", TextManager.Language), - new XAttribute("masterserverurl", MasterServerUrl), - new XAttribute("remotecontenturl", RemoteContentUrl), - new XAttribute("autocheckupdates", AutoCheckUpdates), - new XAttribute("musicvolume", musicVolume), - new XAttribute("soundvolume", soundVolume), - new XAttribute("microphonevolume", microphoneVolume), - new XAttribute("voicechatvolume", voiceChatVolume), - new XAttribute("voicechatcutoffprevention", VoiceChatCutoffPrevention), - new XAttribute("verboselogging", VerboseLogging), - new XAttribute("savedebugconsolelogs", SaveDebugConsoleLogs), - new XAttribute("submarineautosave", EnableSubmarineAutoSave), - new XAttribute("maxautosaves", MaximumAutoSaves), - new XAttribute("autosaveintervalseconds", AutoSaveIntervalSeconds), - new XAttribute("subeditorbackground", XMLExtensions.ColorToString(SubEditorBackgroundColor)), - new XAttribute("subeditorundobuffer", SubEditorMaxUndoBuffer), - new XAttribute("enablesplashscreen", EnableSplashScreen), - new XAttribute("usesteammatchmaking", UseSteamMatchmaking), - new XAttribute("quickstartsub", QuickStartSubmarineName), - new XAttribute("requiresteamauthentication", RequireSteamAuthentication), - new XAttribute("aimassistamount", aimAssistAmount), - new XAttribute("tutorialskipwarning", ShowTutorialSkipWarning)); - - if (!ShowUserStatisticsPrompt) - { - doc.Root.Add(new XAttribute("senduserstatistics", sendUserStatistics)); - } - - if (WasGameUpdated) - { - doc.Root.Add(new XAttribute("wasgameupdated", true)); - } - - XElement gMode = doc.Root.Element("graphicsmode"); - if (gMode == null) - { - gMode = new XElement("graphicsmode"); - doc.Root.Add(gMode); - } - if (GraphicsWidth == 0 || GraphicsHeight == 0) - { - gMode.ReplaceAttributes(new XAttribute("displaymode", windowMode)); - } - else - { - gMode.ReplaceAttributes( - new XAttribute("width", GraphicsWidth), - new XAttribute("height", GraphicsHeight), - new XAttribute("vsync", VSyncEnabled), - new XAttribute("framelimit", Timing.FrameLimit), - new XAttribute("displaymode", windowMode)); - } - - XElement gSettings = doc.Root.Element("graphicssettings"); - if (gSettings == null) - { - gSettings = new XElement("graphicssettings"); - doc.Root.Add(gSettings); - } - - gSettings.ReplaceAttributes( - new XAttribute("particlelimit", ParticleLimit), - new XAttribute("lightmapscale", LightMapScale), - new XAttribute("chromaticaberration", ChromaticAberrationEnabled), - new XAttribute("losmode", LosMode), - new XAttribute("hudscale", HUDScale), - new XAttribute("inventoryscale", InventoryScale)); - - foreach (ContentPackage contentPackage in ContentPackage.CorePackages) - { - if (contentPackage.Path.Contains(VanillaContentPackagePath)) - { - doc.Root.Add(new XElement("contentpackages", new XElement("core", new XAttribute("name", contentPackage.Name)))); - break; - } - } - -#if CLIENT - var keyMappingElement = new XElement("keymapping"); - doc.Root.Add(keyMappingElement); - for (int i = 0; i < keyMapping.Length; i++) - { - KeyOrMouse bind = keyMapping[i]; - if (bind.MouseButton == MouseButton.None) - { - keyMappingElement.Add(new XAttribute(((InputType)i).ToString(), bind.Key)); - } - else - { - keyMappingElement.Add(new XAttribute(((InputType)i).ToString(), bind.MouseButton)); - } - } - - var inventoryKeyMappingElement = new XElement("inventorykeymapping"); - doc.Root.Add(inventoryKeyMappingElement); - for (int i = 0; i < inventoryKeyMapping.Length; i++) - { - KeyOrMouse bind = inventoryKeyMapping[i]; - if (bind.MouseButton == MouseButton.None) - { - inventoryKeyMappingElement.Add(new XAttribute($"slot{i}", bind.Key)); - } - else - { - inventoryKeyMappingElement.Add(new XAttribute($"slot{i}", bind.MouseButton)); - } - } -#endif - - var gameplay = new XElement("gameplay"); - var jobPreferences = new XElement("jobpreferences"); - foreach (Pair job in JobPreferences) - { - XElement jobElement = new XElement("job"); - jobElement.Add(new XAttribute("identifier", job.First)); - jobElement.Add(new XAttribute("variant", job.Second)); - jobPreferences.Add(jobElement); - } - gameplay.Add(jobPreferences); - - var teamPreference = new XElement("teampreference"); - teamPreference.Add(new XAttribute("team", TeamPreference.ToString())); - gameplay.Add(teamPreference); - - doc.Root.Add(gameplay); - - var playerElement = new XElement("player", - new XAttribute("name", playerName ?? ""), - new XAttribute("headindex", CharacterHeadIndex), - new XAttribute("gender", CharacterGender), - new XAttribute("race", CharacterRace), - new XAttribute("hairindex", CharacterHairIndex), - new XAttribute("beardindex", CharacterBeardIndex), - new XAttribute("moustacheindex", CharacterMoustacheIndex), - new XAttribute("faceattachmentindex", CharacterFaceAttachmentIndex)); - doc.Root.Add(playerElement); - - System.Xml.XmlWriterSettings settings = new System.Xml.XmlWriterSettings - { - Indent = true, - OmitXmlDeclaration = true, - NewLineOnAttributes = true - }; - - try - { - using (var writer = XmlWriter.Create(SavePath, settings)) - { - doc.WriteTo(writer); - writer.Flush(); - } - } - catch (Exception e) - { - DebugConsole.ThrowError("Saving game settings failed.", e); - GameAnalyticsManager.AddErrorEventOnce("GameSettings.Save:SaveFailed", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, - "Saving game settings failed.\n" + e.Message + "\n" + e.StackTrace.CleanupStackTrace()); - } - } - #region Load PlayerConfig public void LoadPlayerConfig() { @@ -1115,6 +942,7 @@ namespace Barotrauma } doc.Root.Add( + new XAttribute("gameversion", GameMain.Version.ToString()), new XAttribute("language", TextManager.Language), new XAttribute("masterserverurl", MasterServerUrl), new XAttribute("autocheckupdates", AutoCheckUpdates), @@ -1147,6 +975,7 @@ namespace Barotrauma new XAttribute("disableingamehints", DisableInGameHints) #if DEBUG , new XAttribute("automaticquickstartenabled", AutomaticQuickStartEnabled) + , new XAttribute(nameof(TestScreenEnabled).ToLower(), TestScreenEnabled) , new XAttribute("automaticcampaignloadenabled", AutomaticCampaignLoadEnabled) , new XAttribute("textmanagerdebugmodeenabled", TextManagerDebugModeEnabled) , new XAttribute("modbreakermode", ModBreakerMode) @@ -1307,15 +1136,20 @@ namespace Barotrauma gameplay.Add(jobPreferences); doc.Root.Add(gameplay); - var playerElement = new XElement("player", - new XAttribute("name", playerName ?? ""), - new XAttribute("headindex", CharacterHeadIndex), - new XAttribute("gender", CharacterGender), - new XAttribute("race", CharacterRace), - new XAttribute("hairindex", CharacterHairIndex), - new XAttribute("beardindex", CharacterBeardIndex), - new XAttribute("moustacheindex", CharacterMoustacheIndex), - new XAttribute("faceattachmentindex", CharacterFaceAttachmentIndex)); + var playerElement = new XElement("player", new XAttribute("name", playerName ?? "")); + if (PlayerCharacterCustomization != null) + { + playerElement.SetAttributeValue("headindex", PlayerCharacterCustomization.HeadSpriteId); + if (PlayerCharacterCustomization.gender != Gender.None) { playerElement.SetAttributeValue("gender", PlayerCharacterCustomization.gender); } + if (PlayerCharacterCustomization.race != Race.None) { playerElement.SetAttributeValue("race", PlayerCharacterCustomization.race); } + playerElement.SetAttributeValue("hairindex", PlayerCharacterCustomization.HairIndex); + playerElement.SetAttributeValue("beardindex", PlayerCharacterCustomization.BeardIndex); + playerElement.SetAttributeValue("moustacheindex", PlayerCharacterCustomization.MoustacheIndex); + playerElement.SetAttributeValue("faceattachmentindex", PlayerCharacterCustomization.FaceAttachmentIndex); + playerElement.SetAttributeValue("skincolor", XMLExtensions.ColorToString(PlayerCharacterCustomization.SkinColor)); + playerElement.SetAttributeValue("haircolor", XMLExtensions.ColorToString(PlayerCharacterCustomization.HairColor)); + playerElement.SetAttributeValue("facialhaircolor", XMLExtensions.ColorToString(PlayerCharacterCustomization.FacialHairColor)); + } doc.Root.Add(playerElement); #if CLIENT @@ -1402,6 +1236,7 @@ namespace Barotrauma DisableInGameHints = doc.Root.GetAttributeBool("disableingamehints", DisableInGameHints); #if DEBUG AutomaticQuickStartEnabled = doc.Root.GetAttributeBool("automaticquickstartenabled", AutomaticQuickStartEnabled); + TestScreenEnabled = doc.Root.GetAttributeBool(nameof(TestScreenEnabled).ToLower(), TestScreenEnabled); AutomaticCampaignLoadEnabled = doc.Root.GetAttributeBool("automaticcampaignloadenabled", AutomaticCampaignLoadEnabled); TextManagerDebugModeEnabled = doc.Root.GetAttributeBool("textmanagerdebugmodeenabled", TextManagerDebugModeEnabled); ModBreakerMode = doc.Root.GetAttributeBool("modbreakermode", ModBreakerMode); @@ -1433,23 +1268,22 @@ namespace Barotrauma if (playerElement != null) { playerName = playerElement.GetAttributeString("name", playerName); - CharacterHeadIndex = playerElement.GetAttributeInt("headindex", CharacterHeadIndex); - if (Enum.TryParse(playerElement.GetAttributeString("gender", "none"), true, out Gender g)) + int head = playerElement.GetAttributeInt("headindex", -1); + Enum.TryParse(playerElement.GetAttributeString("gender", "none"), true, out Gender gender); + Enum.TryParse(playerElement.GetAttributeString("race", "none"), true, out Race race); + int hair = playerElement.GetAttributeInt("hairindex", -1); + int beard = playerElement.GetAttributeInt("beardindex", -1); + int moustache = playerElement.GetAttributeInt("moustacheindex", -1); + int faceAttachment = playerElement.GetAttributeInt("faceattachmentindex", -1); + Color skinColor = playerElement.GetAttributeColor("skincolor", Color.Black); + Color hairColor = playerElement.GetAttributeColor("haircolor", Color.Black); + Color facialHairColor = playerElement.GetAttributeColor("facialhaircolor", Color.Black); + PlayerCharacterCustomization = new CharacterInfo.HeadInfo(head, gender, race, hair, beard, moustache, faceAttachment) { - CharacterGender = g; - } - if (Enum.TryParse(playerElement.GetAttributeString("race", "white"), true, out Race r)) - { - CharacterRace = r; - } - else - { - CharacterRace = Race.White; - } - CharacterHairIndex = playerElement.GetAttributeInt("hairindex", CharacterHairIndex); - CharacterBeardIndex = playerElement.GetAttributeInt("beardindex", CharacterBeardIndex); - CharacterMoustacheIndex = playerElement.GetAttributeInt("moustacheindex", CharacterMoustacheIndex); - CharacterFaceAttachmentIndex = playerElement.GetAttributeInt("faceattachmentindex", CharacterFaceAttachmentIndex); + SkinColor = skinColor, + HairColor = hairColor, + FacialHairColor = facialHairColor + }; } } @@ -1655,13 +1489,7 @@ namespace Barotrauma UseSteamMatchmaking = true; RequireSteamAuthentication = true; QuickStartSubmarineName = string.Empty; - CharacterHeadIndex = 1; - CharacterHairIndex = -1; - CharacterBeardIndex = -1; - CharacterMoustacheIndex = -1; - CharacterFaceAttachmentIndex = -1; - CharacterGender = Gender.None; - CharacterRace = Race.White; + PlayerCharacterCustomization = null; aimAssistAmount = 0.5f; EnableMouseLook = true; EnableRadialDistortion = true; @@ -1686,7 +1514,6 @@ namespace Barotrauma Language = "English"; } MasterServerUrl = "http://www.undertowgames.com/baromaster"; - WasGameUpdated = false; VerboseLogging = false; SaveDebugConsoleLogs = false; AutoUpdateWorkshopItems = true; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/CharacterInventory.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/CharacterInventory.cs index 49bfdb3e8..0aa9fc4fc 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/CharacterInventory.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/CharacterInventory.cs @@ -9,7 +9,7 @@ namespace Barotrauma [Flags] public enum InvSlotType { - None = 0, Any = 1, RightHand = 2, LeftHand = 4, Head = 8, InnerClothes = 16, OuterClothes = 32, Headset = 64, Card = 128, Bag = 256 + None = 0, Any = 1, RightHand = 2, LeftHand = 4, Head = 8, InnerClothes = 16, OuterClothes = 32, Headset = 64, Card = 128, Bag = 256, HealthInterface = 512 }; partial class CharacterInventory : Inventory @@ -87,7 +87,9 @@ namespace Barotrauma continue; } - Entity.Spawner?.AddToSpawnQueue(itemPrefab, this, ignoreLimbSlots: subElement.GetAttributeBool("forcetoslot", false)); + string slotString = subElement.GetAttributeString("slot", "None"); + InvSlotType slot = Enum.TryParse(slotString, ignoreCase: true, out InvSlotType s) ? s : InvSlotType.None; + Entity.Spawner?.AddToSpawnQueue(itemPrefab, this, ignoreLimbSlots: subElement.GetAttributeBool("forcetoslot", false), slot: slot); } } @@ -139,10 +141,10 @@ namespace Barotrauma (SlotTypes[i] == InvSlotType.Any || slots[i].ItemCount < 1); } - public override bool CanBePutInSlot(ItemPrefab itemPrefab, int i, float? condition) + public override bool CanBePutInSlot(ItemPrefab itemPrefab, int i, float? condition, int? quality = null) { return - base.CanBePutInSlot(itemPrefab, i, condition) && + base.CanBePutInSlot(itemPrefab, i, condition, quality) && (SlotTypes[i] == InvSlotType.Any || slots[i].ItemCount < 1); } @@ -291,11 +293,25 @@ namespace Barotrauma { currentSlot = i; if (allowedSlots.Any(a => a.HasFlag(SlotTypes[i]))) - inSuitableSlot = true; + { + if ((SlotTypes[i] == InvSlotType.RightHand || SlotTypes[i] == InvSlotType.LeftHand) && !allowedSlots.Contains(SlotTypes[i])) + { + //allowed slot = InvSlotType.RightHand | InvSlotType.LeftHand + // -> make sure the item is in both hand slots + inSuitableSlot = IsInLimbSlot(item, InvSlotType.RightHand) && IsInLimbSlot(item, InvSlotType.LeftHand); + } + else + { + inSuitableSlot = true; + } + } else if (!allowedSlots.Any(a => a.HasFlag(SlotTypes[i]))) + { inWrongSlot = true; + } } } + //all good if (inSuitableSlot && !inWrongSlot) { return true; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/DockingPort.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/DockingPort.cs index a5e681c9a..e2d433d8c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/DockingPort.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/DockingPort.cs @@ -204,8 +204,8 @@ namespace Barotrauma.Items.Components target.InitializeLinks(); - if (!item.linkedTo.Contains(target.item)) item.linkedTo.Add(target.item); - if (!target.item.linkedTo.Contains(item)) target.item.linkedTo.Add(item); + if (!item.linkedTo.Contains(target.item)) { item.linkedTo.Add(target.item); } + if (!target.item.linkedTo.Contains(item)) { target.item.linkedTo.Add(item); } if (!target.item.Submarine.DockedTo.Contains(item.Submarine)) target.item.Submarine.ConnectedDockingPorts.Add(item.Submarine, target); if (!item.Submarine.DockedTo.Contains(target.item.Submarine)) item.Submarine.ConnectedDockingPorts.Add(target.item.Submarine, this); @@ -291,7 +291,7 @@ namespace Barotrauma.Items.Components List removedEntities = item.linkedTo.Where(e => e.Removed).ToList(); - foreach (MapEntity removed in removedEntities) item.linkedTo.Remove(removed); + foreach (MapEntity removed in removedEntities) { item.linkedTo.Remove(removed); } if (!item.linkedTo.Any(e => e is Hull) && !DockingTarget.item.linkedTo.Any(e => e is Hull)) { @@ -306,9 +306,8 @@ namespace Barotrauma.Items.Components if (myWayPoint != null && targetWayPoint != null) { myWayPoint.FindHull(); - myWayPoint.linkedTo.Add(targetWayPoint); targetWayPoint.FindHull(); - targetWayPoint.linkedTo.Add(myWayPoint); + myWayPoint.ConnectTo(targetWayPoint); } } } @@ -597,8 +596,9 @@ namespace Barotrauma.Items.Components { hullRects[i].X -= expand; hullRects[i].Width += expand * 2; - hullRects[i].Location -= MathUtils.ToPoint((subs[i].WorldPosition - subs[i].HiddenSubPosition)); + hullRects[i].Location -= MathUtils.ToPoint(subs[i].WorldPosition - subs[i].HiddenSubPosition); hulls[i] = new Hull(MapEntityPrefab.Find(null, "hull"), hullRects[i], subs[i]); + hulls[i].RoomName = IsHorizontal ? "entityname.dockingport" : "entityname.dockinghatch"; hulls[i].AddToGrid(subs[i]); hulls[i].FreeID(); @@ -716,8 +716,9 @@ namespace Barotrauma.Items.Components { hullRects[i].Y += expand; hullRects[i].Height += expand * 2; - hullRects[i].Location -= MathUtils.ToPoint((subs[i].WorldPosition - subs[i].HiddenSubPosition)); + hullRects[i].Location -= MathUtils.ToPoint(subs[i].WorldPosition - subs[i].HiddenSubPosition); hulls[i] = new Hull(MapEntityPrefab.Find(null, "hull"), hullRects[i], subs[i]); + hulls[i].RoomName = IsHorizontal ? "entityname.dockingport" : "entityname.dockinghatch"; hulls[i].AddToGrid(subs[i]); hulls[i].FreeID(); @@ -873,8 +874,10 @@ namespace Barotrauma.Items.Components { myWayPoint.FindHull(); myWayPoint.linkedTo.Remove(targetWayPoint); + myWayPoint.OnLinksChanged?.Invoke(myWayPoint); targetWayPoint.FindHull(); targetWayPoint.linkedTo.Remove(myWayPoint); + targetWayPoint.OnLinksChanged?.Invoke(targetWayPoint); } } @@ -1058,7 +1061,7 @@ namespace Barotrauma.Items.Components } } - if (!item.linkedTo.Any()) return; + if (!item.linkedTo.Any()) { return; } List linked = new List(item.linkedTo); foreach (MapEntity entity in linked) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Door.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Door.cs index 99927ca74..bf6875202 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Door.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Door.cs @@ -67,7 +67,7 @@ namespace Barotrauma.Items.Components private bool isBroken; - public bool CanBeTraversed => (IsOpen || IsBroken) && !IsJammed && !IsStuck; + public bool CanBeTraversed => (IsOpen || IsBroken) && !IsJammed && !IsStuck && !Impassable; public bool IsBroken { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ElectricalDischarger.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ElectricalDischarger.cs index a86e3f923..279fa113d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ElectricalDischarger.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ElectricalDischarger.cs @@ -184,6 +184,7 @@ namespace Barotrauma.Items.Components { foreach ((Character character, Node node) in charactersInRange) { + if (character == null || character.Removed) { continue; } character.ApplyAttack(null, node.WorldPosition, attack, 1.0f); } } @@ -475,6 +476,21 @@ namespace Barotrauma.Items.Components } } + public override void ReceiveSignal(Signal signal, Connection connection) + { + switch (connection.Name) + { + case "activate": + case "use": + case "trigger_in": + if (signal.value != "0") + { + item.Use(1.0f, null); + } + break; + } + } + protected override void RemoveComponentSpecific() { base.RemoveComponentSpecific(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/EntitySpawnerComponent.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/EntitySpawnerComponent.cs new file mode 100644 index 000000000..e73ea86f1 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/EntitySpawnerComponent.cs @@ -0,0 +1,290 @@ +#nullable enable + +using System; +using System.Linq; +using System.Xml.Linq; +using Barotrauma.Extensions; +using Microsoft.Xna.Framework; + +namespace Barotrauma.Items.Components +{ + internal partial class EntitySpawnerComponent : ItemComponent, IDrawableComponent + { + public enum AreaShape + { + Rectangle, + Circle + } + + [Editable, Serialize("", true, "Identifier of the item to spawn, does nothing if SpeciesName is set. Separate by comma to have multiple items spawn at random.")] + public string? ItemIdentifier { get; set; } + + [Editable, Serialize("", true, "Species name of the creature to spawn, takes priority if ItemIdentifier is set. Separate by comma to have multiple creatures spawn at random.")] + public string? SpeciesName { get; set; } + + [Editable, Serialize(true, true, "Only spawn if crew members are within certain area")] + public bool OnlySpawnWhenCrewInRange { get; set; } + + [Editable, Serialize(AreaShape.Rectangle, true, "Shape of the area where crew members need to stay")] + public AreaShape CrewAreaShape { get; set; } + + [Editable(MaxValueFloat = int.MaxValue, MinValueFloat = 0, ValueStep = 10f), Serialize("500,500", true, "Size of the rectangle where crew members need to stay. Does nothing if CrewAreaShape is set to Circle")] + public Vector2 CrewAreaBounds { get; set; } + + [Editable(MaxValueFloat = int.MaxValue, MinValueFloat = 0, ValueStep = 10f), Serialize(500f, true, "Radius of the circle to spawn stuff in. Does nothing if CrewAreaShape is set to Rectangle")] + public float CrewAreaRadius { get; set; } + + [Editable(MaxValueFloat = int.MaxValue, MinValueFloat = int.MinValue, ValueStep = 10f), Serialize("0,0", true, "Offset of the crew area from the center of the item")] + public Vector2 CrewAreaOffset { get; set; } + + [Editable, Serialize(AreaShape.Rectangle, true, "Shape of the area where enemies or items are spawned")] + public AreaShape SpawnAreaShape { get; set; } + + [Editable(MaxValueFloat = int.MaxValue, MinValueFloat = 0, ValueStep = 10f), Serialize("500,500", true, "Size of the rectangle where items or creatures will be spawned. Does nothing if SpawnAreaShape is set to Circle")] + public Vector2 SpawnAreaBounds { get; set; } + + [Editable(MaxValueFloat = int.MaxValue, MinValueFloat = 0, ValueStep = 10f), Serialize(500f, true, "Radius of the circle where items or creatures will be spawned. Does nothing if SpawnAreaShape is set to Rectangle")] + public float SpawnAreaRadius { get; set; } + + [Editable(MaxValueFloat = int.MaxValue, MinValueFloat = int.MinValue, ValueStep = 10f), Serialize("0,0", true, "Offset of the spawn area from the center of the item")] + public Vector2 SpawnAreaOffset { get; set; } + + [Editable(MaxValueFloat = int.MaxValue, MinValueFloat = int.MinValue, ValueStep = 1f), Serialize("10,40", true, "Time range between spawn attempts in seconds. Set both to a negative value to disable automatic spawning.")] + public Vector2 SpawnTimerRange { get; set; } + + [Editable(MaxValueFloat = int.MaxValue, MinValueFloat = 1f, ValueStep = 1f, DecimalCount = 0), Serialize("1,3", true, "Minumum and maximum amount of items or creatures to spawn in one attempt")] + public Vector2 SpawnAmountRange { get; set; } + + [Editable(MinValueInt = int.MinValue, MaxValueInt = int.MaxValue), Serialize(8, true, "Amount of items or creatures in the spawn area that will prevent further items or creatures from being spawned")] + public int MaximumAmount { get; set; } + + [Editable(MaxValueFloat = int.MaxValue, MinValueFloat = int.MinValue, ValueStep = 10f), Serialize(500f, true, "Inflate the circle of rectangle by this value to extend the area that counts towards the maximum amount of items or enemies to be spawned")] + public float MaximumAmountRangePadding { get; set; } + + [Serialize(true, true, "")] + public bool CanSpawn { get; set; } = true; + + private float SpawnTimer; + private float? SpawnTimerGoal; + + public EntitySpawnerComponent(Item item, XElement element) : base(item, element) + { + IsActive = true; + } + + public override void OnItemLoaded() + { + if (!string.IsNullOrWhiteSpace(ItemIdentifier)) + { + string[] allItems = ItemIdentifier.Split(','); + foreach (string itemIdentifier in allItems) + { + string trimmedString = itemIdentifier.Trim(); + + bool found = false; + + foreach (ItemPrefab prefab in ItemPrefab.Prefabs) + { + if (string.Equals(trimmedString, prefab.Identifier, StringComparison.OrdinalIgnoreCase)) + { + found = true; + break; + } + } + + if (!found) + { + DebugConsole.ThrowError($"Error loading {nameof(EntitySpawnerComponent)} - item prefab \"" + name + "\" (identifier \"" + trimmedString + "\") not found."); + } + } + } + + base.OnItemLoaded(); + } + + public override void Update(float deltaTime, Camera cam) + { + base.Update(deltaTime, cam); + + item.SendSignal(CanSpawn ? "1" : "0", "state_out"); + + if (GameMain.NetworkMember is { IsClient: true }) { return; } + + float minTime = Math.Min(SpawnTimerRange.X, SpawnTimerRange.Y), + maxTime = Math.Max(SpawnTimerRange.X, SpawnTimerRange.Y); + + if (minTime < 0 && maxTime < 0) { return; } + + SpawnTimerGoal ??= Rand.Range(minTime, maxTime, Rand.RandSync.Unsynced); + + SpawnTimer += deltaTime; + + if (SpawnTimer > SpawnTimerGoal) + { + Spawn(); + SpawnTimerGoal = null; + SpawnTimer = 0; + } + } + + public override void ReceiveSignal(Signal signal, Connection connection) + { + bool isNonZero = signal.value != "0"; + bool isClient = GameMain.NetworkMember is { IsClient: true }; + + switch (connection.Name) + { + case "set_state": + CanSpawn = isNonZero; + break; + case "toggle" when isNonZero: + CanSpawn = !CanSpawn; + break; + case "trigger_in" when isNonZero && !isClient: + Spawn(); + break; + } + } + + private RectangleF GetAreaRectangle(Vector2 size, Vector2 offset, bool draw) + { + Vector2 pos = item.WorldPosition; + if (draw) + { + pos.Y = -pos.Y; + } + + pos += offset; + RectangleF rect = new RectangleF(pos.X - size.X / 2f, pos.Y - size.Y / 2f, size.X, size.Y); + return rect; + } + + private bool CanSpawnMore() + { + if (!CanSpawn) { return false; } + + if (OnlySpawnWhenCrewInRange) + { + if (!Character.CharacterList.Any(c => !c.IsDead && c.IsOnPlayerTeam && IsInRange(c.WorldPosition, crewArea: true, rangePad: false))) + { + return false; + } + } + + if (MaximumAmount < 0) { return true; } + + int amount; + + if (!string.IsNullOrWhiteSpace(SpeciesName)) + { + amount = Character.CharacterList.Count(c => !c.IsDead && c.SpeciesName.Equals(SpeciesName, StringComparison.OrdinalIgnoreCase) && IsInRange(c.WorldPosition, crewArea: false, rangePad: true)); + } + else if (!string.IsNullOrWhiteSpace(ItemIdentifier)) + { + amount = Item.ItemList.Count(it => it.Submarine == item.Submarine && it.Prefab.Identifier.Equals(ItemIdentifier, StringComparison.OrdinalIgnoreCase) && IsInRange(it.WorldPosition, crewArea: false, rangePad: true)); + } + else + { + return false; + } + + return amount < MaximumAmount; + } + + private bool IsInRange(Vector2 worldPos, bool crewArea = false, bool rangePad = false) + { + Vector2 offset = crewArea ? CrewAreaOffset : SpawnAreaOffset; + offset.Y = -offset.Y; + switch (crewArea ? CrewAreaShape : SpawnAreaShape) + { + case AreaShape.Circle: + Vector2 center = item.WorldPosition + offset; + float distance = (crewArea ? CrewAreaRadius : SpawnAreaRadius) + (rangePad ? MaximumAmountRangePadding : 0); + return Vector2.DistanceSquared(worldPos, center) < distance * distance; + + case AreaShape.Rectangle: + RectangleF rect = GetAreaRectangle(crewArea ? CrewAreaBounds : SpawnAreaBounds, offset, draw: false); + if (rangePad) + { + rect.Inflate(MaximumAmountRangePadding, MaximumAmountRangePadding); + } + + return rect.Contains(worldPos); + } + + return false; + } + + public void Spawn() + { + if (!CanSpawnMore()) { return; } + + int minAmount = Math.Min((int)SpawnAmountRange.X, (int)SpawnAmountRange.Y), + maxAmount = Math.Max((int)SpawnAmountRange.X, (int)SpawnAmountRange.Y); + + int amount = Rand.Range(minAmount, maxAmount, Rand.RandSync.Unsynced); + + Vector2 offset = SpawnAreaOffset; + offset.Y = -offset.Y; + + switch (SpawnAreaShape) + { + case AreaShape.Circle: + { + var (x, y) = item.WorldPosition + offset; + + for (int i = 0; i < Math.Max(1, amount); i++) + { + float angle = Rand.Range(-MathHelper.TwoPi, MathHelper.TwoPi); + float distance = Rand.Range(0, SpawnAreaRadius, Rand.RandSync.Unsynced); + Vector2 spawnPos = new Vector2(x + distance * (float)Math.Cos(angle), y + distance * (float)Math.Sin(angle)); + + SpawnEntity(spawnPos); + } + break; + } + case AreaShape.Rectangle: + { + RectangleF rect = GetAreaRectangle(SpawnAreaBounds, offset, draw: false); + + for (int i = 0; i < Math.Max(1, amount); i++) + { + float minX = Math.Min(rect.Left, rect.Right), + maxX = Math.Max(rect.Left, rect.Right), + minY = Math.Min(rect.Top, rect.Bottom), + maxY = Math.Max(rect.Top, rect.Bottom); + + Vector2 spawnPos = new Vector2(Rand.Range(minX, maxX, Rand.RandSync.Unsynced), Rand.Range(minY, maxY, Rand.RandSync.Unsynced)); + + SpawnEntity(spawnPos); + } + break; + } + } + + void SpawnEntity(Vector2 pos) + { + if (!string.IsNullOrWhiteSpace(SpeciesName)) + { + string[] allSpecies = SpeciesName.Split(','); + string species = allSpecies.GetRandom().Trim(); + Entity.Spawner?.AddToSpawnQueue(species, pos); + } + else if (!string.IsNullOrWhiteSpace(ItemIdentifier)) + { + string[] allItems = ItemIdentifier.Split(','); + string itemIdentifier = allItems.GetRandom().Trim(); + ItemPrefab? prefab = ItemPrefab.Find(null, itemIdentifier); + if (prefab is null) { return; } + + if (item.Submarine is { } sub) + { + pos -= sub.Position; + } + + Entity.Spawner?.AddToSpawnQueue(prefab, pos, item.Submarine); + } + } + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/GeneticMaterial.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/GeneticMaterial.cs new file mode 100644 index 000000000..ae34d86fe --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/GeneticMaterial.cs @@ -0,0 +1,227 @@ +using System; +using System.Collections.Generic; +using System.Xml.Linq; +using System.Linq; +using Barotrauma.Extensions; +using Barotrauma.Networking; +using Microsoft.Xna.Framework; + +namespace Barotrauma.Items.Components +{ + partial class GeneticMaterial : ItemComponent, IServerSerializable + { + private readonly string materialName; + + private Character targetCharacter; + private AfflictionPrefab selectedEffect, selectedTaintedEffect; + + [Serialize("", true)] + public string Effect + { + get; + set; + } + + [Serialize("geneticmaterialdebuff", true)] + public string TaintedEffect + { + get; + set; + } + + private bool tainted; + [Serialize(false, true)] + public bool Tainted + { + get { return tainted; } + private set + { + if (!value) { return; } + tainted = true; + item.AllowDeconstruct = false; + if (!string.IsNullOrEmpty(TaintedEffect)) + { + selectedTaintedEffect = AfflictionPrefab.Prefabs.Where(a => + a.Identifier.Equals(TaintedEffect, StringComparison.OrdinalIgnoreCase) || + a.AfflictionType.Equals(TaintedEffect, StringComparison.OrdinalIgnoreCase)).GetRandom(); + } + } + } + + //only for saving the selected tainted effect + [Serialize("", true)] + public string SelectedTaintedEffect + { + get { return selectedTaintedEffect?.Identifier ?? string.Empty; } + private set + { + if (string.IsNullOrEmpty(value)) { return; } + selectedTaintedEffect = AfflictionPrefab.Prefabs.Find(a => a.Identifier == value); + } + } + + public GeneticMaterial(Item item, XElement element) + : base(item, element) + { + string nameId = element.GetAttributeString("nameidentifier", ""); + if (!string.IsNullOrEmpty(nameId)) + { + materialName = TextManager.Get(nameId); + } + if (!string.IsNullOrEmpty(Effect)) + { + selectedEffect = AfflictionPrefab.Prefabs.Where(a => + a.Identifier.Equals(Effect, StringComparison.OrdinalIgnoreCase) || + a.AfflictionType.Equals(Effect, StringComparison.OrdinalIgnoreCase)).GetRandom(); + } + } + + [Serialize(3.0f, false)] + public float ConditionIncreaseOnCombineMin { get; set; } + + [Serialize(8.0f, false)] + public float ConditionIncreaseOnCombineMax { get; set; } + + public bool CanBeCombinedWith(GeneticMaterial otherGeneticMaterial) + { + return !tainted && otherGeneticMaterial != null && !otherGeneticMaterial.tainted && item.AllowDeconstruct && otherGeneticMaterial.item.AllowDeconstruct; + } + + public override void Equip(Character character) + { + if (character == null) { return; } + IsActive = true; + + if (targetCharacter != null) { return; } + + if (tainted) + { + if (selectedTaintedEffect != null) + { + float selectedTaintedEffectStrength = item.ConditionPercentage / 100.0f * selectedTaintedEffect.MaxStrength; + character.CharacterHealth.ApplyAffliction(null, selectedTaintedEffect.Instantiate(selectedTaintedEffectStrength)); + var existingAffliction = character.CharacterHealth.GetAllAfflictions().FirstOrDefault(a => a.Prefab == selectedTaintedEffect); + if (existingAffliction != null) + { + existingAffliction.Strength = selectedTaintedEffectStrength; + } + targetCharacter = character; +#if SERVER + item.CreateServerEvent(this); +#endif + } + } + if (selectedEffect != null) + { + ApplyStatusEffects(ActionType.OnWearing, 1.0f); + float selectedEffectStrength = item.ConditionPercentage / 100.0f * selectedEffect.MaxStrength; + character.CharacterHealth.ApplyAffliction(null, selectedEffect.Instantiate(selectedEffectStrength)); + var existingAffliction = character.CharacterHealth.GetAllAfflictions().FirstOrDefault(a => a.Prefab == selectedEffect); + if (existingAffliction != null) + { + existingAffliction.Strength = selectedEffectStrength; + } + targetCharacter = character; +#if SERVER + item.CreateServerEvent(this); +#endif + } + foreach (Item containedItem in item.ContainedItems) + { + containedItem.GetComponent()?.Equip(character); + } + } + + public override void Update(float deltaTime, Camera cam) + { + base.Update(deltaTime, cam); + if (targetCharacter != null) + { + var rootContainer = item.GetRootContainer(); + if (!targetCharacter.HasEquippedItem(item) && + (rootContainer == null || !targetCharacter.HasEquippedItem(rootContainer) || !targetCharacter.Inventory.IsInLimbSlot(rootContainer, InvSlotType.HealthInterface))) + { + item.ApplyStatusEffects(ActionType.OnSevered, 1.0f, targetCharacter); + targetCharacter.CharacterHealth.ReduceAffliction(null, selectedEffect.Identifier, selectedEffect.MaxStrength); + if (tainted) + { + targetCharacter.CharacterHealth.ReduceAffliction(null, selectedTaintedEffect.Identifier, selectedTaintedEffect.MaxStrength); + } + targetCharacter = null; + IsActive = false; + } + } + } + + public bool Combine(GeneticMaterial otherGeneticMaterial, Character user) + { + if (!CanBeCombinedWith(otherGeneticMaterial)) { return false; } + + float conditionIncrease = Rand.Range(ConditionIncreaseOnCombineMin, ConditionIncreaseOnCombineMax); + conditionIncrease += user.GetStatValue(StatTypes.GeneticMaterialRefineBonus); + if (item.Prefab == otherGeneticMaterial.item.Prefab) + { + item.Condition = Math.Max(item.Condition, otherGeneticMaterial.item.Condition) + conditionIncrease; + float taintedProbability = GetTaintedProbabilityOnRefine(user); + if (taintedProbability >= Rand.Range(0.0f, 1.0f)) + { + MakeTainted(); + } + return true; + } + else + { + item.Condition = otherGeneticMaterial.Item.Condition = + (item.Condition + otherGeneticMaterial.Item.Condition) / 2.0f + conditionIncrease; + item.OwnInventory?.TryPutItem(otherGeneticMaterial.Item, user: null); + item.AllowDeconstruct = false; + otherGeneticMaterial.Item.AllowDeconstruct = false; + if (GetTaintedProbabilityOnCombine(user) >= Rand.Range(0.0f, 1.0f)) + { + MakeTainted(); + } + return false; + } + } + + private float GetTaintedProbabilityOnRefine(Character user) + { + if (user == null) { return 1.0f; } + float probability = MathHelper.Lerp(0.0f, 0.99f, item.Condition / 100.0f); + probability *= MathHelper.Lerp(1.0f, 0.25f, DegreeOfSuccess(user)); + return MathHelper.Clamp(probability, 0.0f, 1.0f); + } + + private float GetTaintedProbabilityOnCombine(Character user) + { + if (user == null) { return 1.0f; } + float probability = 1.0f - user.GetStatValue(StatTypes.GeneticMaterialTaintedProbabilityReductionOnCombine); + return MathHelper.Clamp(probability, 0.0f, 1.0f); + } + + private void MakeTainted() + { + if (GameMain.NetworkMember?.IsClient ?? false) { return; } + Tainted = true; +#if SERVER + item.CreateServerEvent(this); +#endif + } + + public static string TryCreateName(ItemPrefab prefab, XElement element) + { + foreach (XElement subElement in element.Elements()) + { + if (subElement.Name.ToString().Equals(nameof(GeneticMaterial), StringComparison.OrdinalIgnoreCase)) + { + string nameId = subElement.GetAttributeString("nameidentifier", ""); + if (!string.IsNullOrEmpty(nameId)) + { + return prefab.Name.Replace("[type]", TextManager.Get(nameId, returnNull: true) ?? nameId); + } + } + } + return prefab.Name; + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Holdable.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Holdable.cs index cbfc92b1a..e88c44e43 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Holdable.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Holdable.cs @@ -33,7 +33,7 @@ namespace Barotrauma.Items.Components private bool attachable, attached, attachedByDefault; private Voronoi2.VoronoiCell attachTargetCell; - private readonly PhysicsBody body; + private PhysicsBody body; public PhysicsBody Pusher { get; @@ -79,6 +79,13 @@ namespace Barotrauma.Items.Components set; } + [Serialize(false, false, description: "Use the hand rotation instead of torso rotation for the item hold angle. Enable this if you want the item just to follow with the arm when not aiming instead of forcing the arm to a hold pose.")] + public bool UseHandRotationForHoldAngle + { + get; + set; + } + [Serialize(false, false, description: "Can the item be attached to walls.")] public bool Attachable { @@ -93,6 +100,13 @@ namespace Barotrauma.Items.Components set; } + [Serialize(false, false, description: "Can the item only be attached in limited amount? Uses permanent stat values to check for legibility.")] + public bool LimitedAttachable + { + get; + set; + } + [Serialize(false, false, description: "Should the item be attached to a wall by default when it's placed in the submarine editor.")] public bool AttachedByDefault { @@ -154,7 +168,8 @@ namespace Barotrauma.Items.Components BodyType = BodyType.Dynamic, CollidesWith = Physics.CollisionCharacter, CollisionCategories = Physics.CollisionItemBlocking, - Enabled = false + Enabled = false, + UserData = "Holdable.Pusher" }; Pusher.FarseerBody.OnCollision += OnPusherCollision; Pusher.FarseerBody.FixedRotation = false; @@ -205,7 +220,6 @@ namespace Barotrauma.Items.Components } } } - characterUsable = element.GetAttributeBool("characterusable", true); } @@ -247,6 +261,7 @@ namespace Barotrauma.Items.Components private void Drop(bool dropConnectedWires, Character dropper) { + GetRope()?.Snap(); if (dropConnectedWires) { DropConnectedWires(dropper); @@ -558,6 +573,15 @@ namespace Barotrauma.Items.Components PickKey = InputType.Select; } + public override void ParseMsg() + { + base.ParseMsg(); + if (Attachable) + { + prevMsg = DisplayMsg; + } + } + public override bool Use(float deltaTime, Character character = null) { if (!attachable || item.body == null) { return character == null || (character.IsKeyDown(InputType.Aim) && characterUsable); } @@ -567,6 +591,28 @@ namespace Barotrauma.Items.Components if (!character.IsKeyDown(InputType.Aim)) { return false; } if (!CanBeAttached(character)) { return false; } + if (LimitedAttachable) + { + if (character?.Info == null) + { + DebugConsole.AddWarning("Character without CharacterInfo attempting to attach a limited attachable item!"); + return false; + } + Vector2 attachPos = GetAttachPosition(character, useWorldCoordinates: true); + Structure attachTarget = Structure.GetAttachTarget(attachPos); + + int maxAttachableCount = (int)character.Info.GetSavedStatValue(StatTypes.MaxAttachableCount, item.Prefab.Identifier); + int currentlyAttachedCount = Item.ItemList.Count( + i => i.Submarine == attachTarget?.Submarine && i.GetComponent() is Holdable holdable && holdable.Attached && i.Prefab.Identifier == item.prefab.Identifier); + if (currentlyAttachedCount >= maxAttachableCount) + { +#if CLIENT + GUI.AddMessage($"{TextManager.Get("itemmsgtotalnumberlimited")} ({currentlyAttachedCount}/{maxAttachableCount})", Color.Red); +#endif + return false; + } + } + if (GameMain.NetworkMember != null) { if (character != Character.Controlled) @@ -596,10 +642,13 @@ namespace Barotrauma.Items.Components item.Drop(character); item.SetTransform(ConvertUnits.ToSimUnits(GetAttachPosition(character)), 0.0f, findNewHull: false); } + AttachToWall(); } + return true; + } - AttachToWall(); - + public override bool SecondaryUse(float deltaTime, Character character = null) + { return true; } @@ -661,6 +710,20 @@ namespace Barotrauma.Items.Components Update(deltaTime, cam); } + public Rope GetRope() + { + var rangedWeapon = Item.GetComponent(); + if (rangedWeapon != null) + { + var lastProjectile = rangedWeapon.LastProjectile; + if (lastProjectile != null) + { + return lastProjectile.Item.GetComponent(); + } + } + return null; + } + public override void Update(float deltaTime, Camera cam) { if (attachTargetCell != null) @@ -715,9 +778,18 @@ namespace Barotrauma.Items.Components scaledHandlePos[1] = handlePos[1] * item.Scale; bool aim = picker.IsKeyDown(InputType.Aim) && aimPos != Vector2.Zero && picker.CanAim; picker.AnimController.HoldItem(deltaTime, item, scaledHandlePos, holdPos + swing, aimPos + swing, aim, holdAngle); + if (!aim) + { + var rope = GetRope(); + if (rope != null && rope.SnapWhenNotAimed && rope.Item.ParentInventory == null) + { + rope.Snap(); + } + } } else { + GetRope()?.Snap(); Limb equipLimb = null; if (picker.Inventory.IsInLimbSlot(item, InvSlotType.Headset) || picker.Inventory.IsInLimbSlot(item, InvSlotType.Head)) { @@ -780,7 +852,19 @@ namespace Barotrauma.Items.Components DeattachFromWall(); } } - + + protected override void RemoveComponentSpecific() + { + base.RemoveComponentSpecific(); + attachTargetCell = null; + if (Pusher != null) + { + Pusher.Remove(); + Pusher = null; + } + body = null; + } + public override XElement Save(XElement parentElement) { if (!attachable) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/IdCard.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/IdCard.cs index d426b1a70..8fcf6d265 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/IdCard.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/IdCard.cs @@ -1,11 +1,29 @@ -using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework; using System.Collections.Generic; +using System.Linq; using System.Xml.Linq; namespace Barotrauma.Items.Components { partial class IdCard : Pickable { + [Serialize(CharacterTeamType.None, true, alwaysUseInstanceValues: true)] + public CharacterTeamType TeamID + { + get; + set; + } + + [Serialize(0, true, alwaysUseInstanceValues: true)] + public int SubmarineSpecificID + { + get; + set; + } + + private JobPrefab cachedJobPrefab; + private string cachedName; + public IdCard(Item item, XElement element) : base(item, element) { @@ -13,29 +31,33 @@ namespace Barotrauma.Items.Components public void Initialize(CharacterInfo info) { - if (info == null) return; + if (info == null) { return; } if (info.Job?.Prefab != null) { item.AddTag("jobid:" + info.Job.Prefab.Identifier); } + TeamID = info.TeamID; + var head = info.Head; - if (info != null && head != null) - { - item.AddTag("gender:" + head.gender.ToString().ToLowerInvariant()); - item.AddTag("race:" + head.race.ToString()); - item.AddTag("headspriteid:" + info.HeadSpriteId.ToString()); - item.AddTag("hairindex:" + head.HairIndex); - item.AddTag("beardindex:" + head.BeardIndex); - item.AddTag("moustacheindex:" + head.MoustacheIndex); - item.AddTag("faceattachmentindex:" + head.FaceAttachmentIndex); + if (head == null) { return; } + + if (info.HasGenders) { item.AddTag($"gender:{head.gender.ToString().ToLowerInvariant()}"); } + if (info.HasRaces) { item.AddTag($"race:{head.race}"); } + item.AddTag($"headspriteid:{info.HeadSpriteId}"); + item.AddTag($"hairindex:{head.HairIndex}"); + item.AddTag($"beardindex:{head.BeardIndex}"); + item.AddTag($"moustacheindex:{head.MoustacheIndex}"); + item.AddTag($"faceattachmentindex:{head.FaceAttachmentIndex}"); + item.AddTag($"haircolor:{head.HairColor.ToStringHex()}"); + item.AddTag($"facialhaircolor:{head.FacialHairColor.ToStringHex()}"); + item.AddTag($"skincolor:{head.SkinColor.ToStringHex()}"); - if (head.SheetIndex != null) - { - item.AddTag("sheetindex:" + head.SheetIndex.Value.X + ";" + head.SheetIndex.Value.Y); - } + if (head.SheetIndex != null) + { + item.AddTag($"sheetindex:{head.SheetIndex.Value.X};{head.SheetIndex.Value.Y}"); } } @@ -50,5 +72,48 @@ namespace Barotrauma.Items.Components base.Unequip(character); character.Info?.CheckDisguiseStatus(true, this); } + + public JobPrefab GetJob() + { + if (cachedJobPrefab != null) + { + return cachedJobPrefab; + } + + foreach (string tag in item.GetTags()) + { + if (tag.StartsWith("jobid:")) + { + string jobIdentifier = tag.Split(':').Last(); + if (JobPrefab.Get(jobIdentifier) is { } jobPrefab) + { + cachedJobPrefab = jobPrefab; + return jobPrefab; + } + } + } + + return null; + } + + public string GetName() + { + if (cachedName != null) + { + return cachedName; + } + + foreach (string tag in item.GetTags()) + { + if (tag.StartsWith("name:")) + { + string ownerName = tag.Split(':').Last(); + cachedName = ownerName; + return ownerName; + } + } + + return null; + } } } \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/MeleeWeapon.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/MeleeWeapon.cs index f59597c61..299afe98d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/MeleeWeapon.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/MeleeWeapon.cs @@ -50,6 +50,17 @@ namespace Barotrauma.Items.Components set; } + [Editable, Serialize(true, false)] + public bool Swing { get; set; } + + [Editable, Serialize("2.0, 0.0", false)] + public Vector2 SwingPos { get; set; } + + [Editable, Serialize("3.0, -1.0", false)] + public Vector2 SwingForce { get; set; } + + public bool Hitting { get { return hitting; } } + /// /// Defines items that boost the weapon functionality, like battery cell for stun batons. /// @@ -61,12 +72,13 @@ namespace Barotrauma.Items.Components foreach (XElement subElement in element.Elements()) { if (!subElement.Name.ToString().Equals("attack", StringComparison.OrdinalIgnoreCase)) { continue; } - Attack = new Attack(subElement, item.Name + ", MeleeWeapon"); - Attack.DamageRange = item.body == null ? 10.0f : ConvertUnits.ToDisplayUnits(item.body.GetMaxExtent()); + Attack = new Attack(subElement, item.Name + ", MeleeWeapon", item) + { + DamageRange = item.body == null ? 10.0f : ConvertUnits.ToDisplayUnits(item.body.GetMaxExtent()) + }; } item.IsShootable = true; - // TODO: should define this in xml if we have melee weapons that don't require aim to use - item.RequireAimToUse = true; + item.RequireAimToUse = element.Parent.GetAttributeBool("requireaimtouse", true); PreferredContainedItems = element.GetAttributeStringArray("preferredcontaineditems", new string[0], convertToLowerInvariant: true); } @@ -80,6 +92,9 @@ namespace Barotrauma.Items.Components public override bool Use(float deltaTime, Character character = null) { if (character == null || reloadTimer > 0.0f) { return false; } +#if CLIENT + if (!Item.RequireAimToUse && character.IsPlayer && (GUI.MouseOn != null || character.Inventory.visualSlots.Any(s => s.MouseOn()) || Inventory.DraggingItems.Any())) { return false; } +#endif if (Item.RequireAimToUse && !character.IsKeyDown(InputType.Aim) || hitting) { return false; } //don't allow hitting if the character is already hitting with another weapon @@ -92,10 +107,10 @@ namespace Barotrauma.Items.Components SetUser(character); - if (hitPos < MathHelper.PiOver4) { return false; } + if (Item.RequireAimToUse && hitPos < MathHelper.PiOver4) { return false; } ActivateNearbySleepingCharacters(); - reloadTimer = reload; + reloadTimer = reload / (1 + character.GetStatValue(StatTypes.MeleeAttackSpeed)); item.body.FarseerBody.CollisionCategories = Physics.CollisionProjectile; item.body.FarseerBody.CollidesWith = Physics.CollisionCharacter | Physics.CollisionWall; @@ -103,20 +118,28 @@ namespace Barotrauma.Items.Components item.body.FarseerBody.IsBullet = true; item.body.PhysEnabled = true; - if (!character.AnimController.InWater) + if (Swing && !character.AnimController.InWater) { foreach (Limb l in character.AnimController.Limbs) { if (l.IsSevered) { continue; } - if (l.type == LimbType.LeftFoot || l.type == LimbType.LeftThigh || l.type == LimbType.LeftLeg) { continue; } - if (l.type == LimbType.Head || l.type == LimbType.Torso) + Vector2 force = new Vector2(character.AnimController.Dir * SwingForce.X, SwingForce.Y) * l.Mass; + switch (l.type) { - l.body.ApplyLinearImpulse(new Vector2(character.AnimController.Dir * 7.0f, -4.0f)); + case LimbType.Torso: + force *= 2; + break; + case LimbType.Legs: + case LimbType.LeftFoot: + case LimbType.LeftThigh: + case LimbType.LeftLeg: + case LimbType.RightFoot: + case LimbType.RightThigh: + case LimbType.RightLeg: + force = Vector2.Zero; + break; } - else - { - l.body.ApplyLinearImpulse(new Vector2(character.AnimController.Dir * 5.0f, -2.0f)); - } + l.body.ApplyLinearImpulse(force); } } @@ -152,9 +175,16 @@ namespace Barotrauma.Items.Components public override void Update(float deltaTime, Camera cam) { - if (!item.body.Enabled) { impactQueue.Clear(); return; } - if (picker == null && !picker.HeldItems.Contains(item)) { impactQueue.Clear(); IsActive = false; } - + if (!item.body.Enabled) + { + impactQueue.Clear(); + return; + } + if (picker == null && !picker.HeldItems.Contains(item)) + { + impactQueue.Clear(); + IsActive = false; + } while (impactQueue.Count > 0) { var impact = impactQueue.Dequeue(); @@ -162,38 +192,48 @@ namespace Barotrauma.Items.Components } //in case handling the impact does something to the picker if (picker == null) { return; } - reloadTimer -= deltaTime; - if (reloadTimer < 0) { reloadTimer = 0; } - - if (!picker.IsKeyDown(InputType.Aim) && !hitting) { hitPos = 0.0f; } - + if (reloadTimer < 0) + { + reloadTimer = 0; + } + if (!picker.IsKeyDown(InputType.Aim) && !hitting) + { + hitPos = 0.0f; + } ApplyStatusEffects(ActionType.OnActive, deltaTime, picker); - - if (item.body.Dir != picker.AnimController.Dir) { item.FlipX(relativeToSub: false); } - + if (item.body.Dir != picker.AnimController.Dir) + { + item.FlipX(relativeToSub: false); + } AnimController ac = picker.AnimController; - - //TODO: refactor the hitting logic (get rid of the magic numbers, make it possible to use different kinds of animations for different items) if (!hitting) { - bool aim = picker.AllowInput && picker.IsKeyDown(InputType.Aim) && reloadTimer <= 0 && picker.CanAim; + bool aim = item.RequireAimToUse && picker.AllowInput && picker.IsKeyDown(InputType.Aim) && reloadTimer <= 0 && picker.CanAim; if (aim) { - hitPos = MathUtils.WrapAnglePi(Math.Min(hitPos + deltaTime * 5f, MathHelper.PiOver4)); - ac.HoldItem(deltaTime, item, handlePos, aimPos, Vector2.Zero, false, hitPos, holdAngle + hitPos); + hitPos = MathUtils.WrapAnglePi(Math.Min(hitPos + deltaTime * 3f, MathHelper.PiOver4)); + ac.HoldItem(deltaTime, item, handlePos, aimPos, Vector2.Zero, aim: false, hitPos, holdAngle + hitPos, aimMelee: true); } else { hitPos = 0; - ac.HoldItem(deltaTime, item, handlePos, holdPos, Vector2.Zero, false, holdAngle); + ac.HoldItem(deltaTime, item, handlePos, holdPos, Vector2.Zero, aim: false, holdAngle); } } else { - hitPos = MathUtils.WrapAnglePi(hitPos - deltaTime * 15f); - ac.HoldItem(deltaTime, item, handlePos, new Vector2(2, 0), Vector2.Zero, false, hitPos, holdAngle + hitPos); // aimPos not used -> zero (new Vector2(-0.3f, 0.2f)), holdPos new Vector2(0.6f, -0.1f) - if (hitPos < -MathHelper.PiOver2) + // TODO: We might want to make this configurable + hitPos -= deltaTime * 15f; + if (Swing) + { + ac.HoldItem(deltaTime, item, handlePos, SwingPos, Vector2.Zero, aim: false, hitPos, holdAngle); + } + else + { + ac.HoldItem(deltaTime, item, handlePos, holdPos, Vector2.Zero, aim: false, holdAngle); + } + if (hitPos < -MathHelper.Pi) { RestoreCollision(); hitting = false; @@ -266,16 +306,10 @@ namespace Barotrauma.Items.Components return false; } - Character targetCharacter = null; - Limb targetLimb = null; - Structure targetStructure = null; - Item targetItem = null; - - if (f2.Body.UserData is Limb) + if (f2.Body.UserData is Limb targetLimb) { - targetLimb = (Limb)f2.Body.UserData; if (targetLimb.IsSevered || targetLimb.character == null || targetLimb.character == User) { return false; } - targetCharacter = targetLimb.character; + var targetCharacter = targetLimb.character; if (targetCharacter == picker) { return false; } if (AllowHitMultiple) { @@ -287,9 +321,8 @@ namespace Barotrauma.Items.Components } hitTargets.Add(targetCharacter); } - else if (f2.Body.UserData is Character) + else if (f2.Body.UserData is Character targetCharacter) { - targetCharacter = (Character)f2.Body.UserData; if (targetCharacter == picker || targetCharacter == User) { return false; } targetLimb = targetCharacter.AnimController.GetLimb(LimbType.Torso); //Otherwise armor can be bypassed in strange ways if (AllowHitMultiple) @@ -302,9 +335,8 @@ namespace Barotrauma.Items.Components } hitTargets.Add(targetCharacter); } - else if (f2.Body.UserData is Structure) + else if (f2.Body.UserData is Structure targetStructure) { - targetStructure = (Structure)f2.Body.UserData; if (AllowHitMultiple) { if (hitTargets.Contains(targetStructure)) { return true; } @@ -315,9 +347,8 @@ namespace Barotrauma.Items.Components } hitTargets.Add(targetStructure); } - else if (f2.Body.UserData is Item) + else if (f2.Body.UserData is Item targetItem) { - targetItem = (Item)f2.Body.UserData; if (AllowHitMultiple) { if (hitTargets.Contains(targetItem)) { return true; } @@ -350,12 +381,11 @@ namespace Barotrauma.Items.Components Limb targetLimb = target.UserData as Limb; Character targetCharacter = targetLimb?.character ?? target.UserData as Character; - Structure targetStructure = target.UserData as Structure; - Item targetItem = target.UserData as Item; - if (Attack != null) { Attack.SetUser(User); + Attack.DamageMultiplier = 1 + User.GetStatValue(StatTypes.MeleeAttackMultiplier); + Attack.DamageMultiplier *= 1.0f + item.GetQualityModifier(Quality.StatType.AttackMultiplier); if (targetLimb != null) { @@ -369,12 +399,12 @@ namespace Barotrauma.Items.Components targetCharacter.LastDamageSource = item; Attack.DoDamage(User, targetCharacter, item.WorldPosition, 1.0f); } - else if (targetStructure != null) + else if (target.UserData is Structure targetStructure) { if (targetStructure.Removed) { return; } Attack.DoDamage(User, targetStructure, item.WorldPosition, 1.0f); } - else if (targetItem != null && targetItem.Prefab.DamagedByMeleeWeapons && targetItem.Condition > 0) + else if (target.UserData is Item targetItem && targetItem.Prefab.DamagedByMeleeWeapons && targetItem.Condition > 0) { if (targetItem.Removed) { return; } Attack.DoDamage(User, targetItem, item.WorldPosition, 1.0f); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Pickable.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Pickable.cs index dbac3d4fb..49abae0d4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Pickable.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Pickable.cs @@ -1,4 +1,5 @@ -using Barotrauma.Networking; +using Barotrauma.Abilities; +using Barotrauma.Networking; using Microsoft.Xna.Framework; using System; using System.Collections.Generic; @@ -73,12 +74,15 @@ namespace Barotrauma.Items.Components if (PickingTime > 0.0f) { + var abilityPickingTime = new AbilityValueItem(PickingTime, item.Prefab); + picker.CheckTalents(AbilityEffectType.OnItemPicked, abilityPickingTime); + if ((picker.PickingItem == null || picker.PickingItem == item) && PickingTime <= float.MaxValue) { #if SERVER item.CreateServerEvent(this); #endif - pickingCoroutine = CoroutineManager.StartCoroutine(WaitForPick(picker, PickingTime)); + pickingCoroutine = CoroutineManager.StartCoroutine(WaitForPick(picker, abilityPickingTime.Value)); } return false; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Propulsion.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Propulsion.cs index 9e6ad682a..39e61fcb0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Propulsion.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Propulsion.cs @@ -66,18 +66,18 @@ namespace Barotrauma.Items.Components foreach (Limb limb in character.AnimController.Limbs) { if (limb.WearingItems.Find(w => w.WearableComponent.Item == item) == null) { continue; } - limb.body.ApplyForce(propulsion, maxVelocity: NetConfig.MaxPhysicsBodyVelocity); + limb.body.ApplyForce(propulsion); } - character.AnimController.Collider.ApplyForce(propulsion, maxVelocity: NetConfig.MaxPhysicsBodyVelocity); + character.AnimController.Collider.ApplyForce(propulsion); if (character.Inventory.IsInLimbSlot(item, InvSlotType.RightHand)) { - character.AnimController.GetLimb(LimbType.RightHand)?.body.ApplyForce(propulsion, maxVelocity: NetConfig.MaxPhysicsBodyVelocity); + character.AnimController.GetLimb(LimbType.RightHand)?.body.ApplyForce(propulsion); } if (character.Inventory.IsInLimbSlot(item, InvSlotType.LeftHand)) { - character.AnimController.GetLimb(LimbType.LeftHand)?.body.ApplyForce(propulsion, maxVelocity: NetConfig.MaxPhysicsBodyVelocity); + character.AnimController.GetLimb(LimbType.LeftHand)?.body.ApplyForce(propulsion); } #if CLIENT diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RangedWeapon.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RangedWeapon.cs index 8a73e00a7..a07db8e19 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RangedWeapon.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RangedWeapon.cs @@ -1,10 +1,11 @@ -using Barotrauma.Networking; +using Barotrauma.Abilities; +using Barotrauma.Networking; using FarseerPhysics; -using FarseerPhysics.Collision; using FarseerPhysics.Dynamics; using Microsoft.Xna.Framework; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Xml.Linq; @@ -52,17 +53,38 @@ namespace Barotrauma.Items.Components set; } + [Serialize(0f, true, description: "The time required for a charge-type turret to charge up before able to fire.")] + public float MaxChargeTime + { + get; + private set; + } + + private enum ChargingState + { + Inactive, + WindingUp, + WindingDown, + } + private ChargingState currentChargingState; + public Vector2 TransformedBarrelPos { get { Matrix bodyTransform = Matrix.CreateRotationZ(item.body.Rotation); Vector2 flippedPos = barrelPos; - if (item.body.Dir < 0.0f) flippedPos.X = -flippedPos.X; - return Vector2.Transform(flippedPos, bodyTransform); + if (item.body.Dir < 0.0f) { flippedPos.X = -flippedPos.X; } + return Vector2.Transform(flippedPos, bodyTransform) * item.Scale; } } - + + + public Projectile LastProjectile { get; private set; } + + private float currentChargeTime; + private bool tryingToCharge; + public RangedWeapon(Item item, XElement element) : base(item, element) { @@ -88,10 +110,41 @@ namespace Barotrauma.Items.Components if (ReloadTimer < 0.0f) { ReloadTimer = 0.0f; - IsActive = false; + // was this an optimization or related to something else? it cannot occur for charge-type weapons + //IsActive = false; + if (MaxChargeTime == 0.0f) + { + IsActive = false; + return; + } } + + float previousChargeTime = currentChargeTime; + + float chargeDeltaTime = tryingToCharge && ReloadTimer <= 0f ? deltaTime : -deltaTime; + currentChargeTime = Math.Clamp(currentChargeTime + chargeDeltaTime, 0f, MaxChargeTime); + + tryingToCharge = false; + + if (currentChargeTime == 0f) + { + currentChargingState = ChargingState.Inactive; + } + else if (currentChargeTime < previousChargeTime) + { + currentChargingState = ChargingState.WindingDown; + } + else + { + // if we are charging up or at maxed charge, remain winding up + currentChargingState = ChargingState.WindingUp; + } + + UpdateProjSpecific(deltaTime); } + partial void UpdateProjSpecific(float deltaTime); + private float GetSpread(Character user) { float degreeOfFailure = 1.0f - DegreeOfSuccess(user); @@ -102,11 +155,20 @@ namespace Barotrauma.Items.Components private readonly List limbBodies = new List(); public override bool Use(float deltaTime, Character character = null) { + tryingToCharge = true; if (character == null || character.Removed) { return false; } if ((item.RequireAimToUse && !character.IsKeyDown(InputType.Aim)) || ReloadTimer > 0.0f) { return false; } + if (currentChargeTime < MaxChargeTime) { return false; } IsActive = true; - ReloadTimer = reload; + ReloadTimer = reload / (1 + character?.GetStatValue(StatTypes.RangedAttackSpeed) ?? 0f); + currentChargeTime = 0f; + + if (character != null) + { + var abilityItem = new AbilityItem(item); + character.CheckTalents(AbilityEffectType.OnUseRangedWeapon, abilityItem); + } if (item.AiTarget != null) { @@ -136,7 +198,13 @@ namespace Barotrauma.Items.Components Vector2 barrelPos = TransformedBarrelPos + item.body.SimPosition; float rotation = (Item.body.Dir == 1.0f) ? Item.body.Rotation : Item.body.Rotation - MathHelper.Pi; float spread = GetSpread(character) * Rand.Range(-0.5f, 0.5f); - projectile.Shoot(character, character.AnimController.AimSourceSimPos, barrelPos, rotation + spread, ignoredBodies: limbBodies.ToList(), createNetworkEvent: false); + var lastProjectile = LastProjectile; + if (lastProjectile != projectile) + { + lastProjectile?.Item.GetComponent()?.Snap(); + } + float damageMultiplier = 1f + item.GetQualityModifier(Quality.StatType.AttackMultiplier); + projectile.Shoot(character, character.AnimController.AimSourceSimPos, barrelPos, rotation + spread, ignoredBodies: limbBodies.ToList(), createNetworkEvent: false, damageMultiplier); projectile.Item.GetComponent()?.Attach(Item, projectile.Item); if (i == 0) { @@ -145,6 +213,7 @@ namespace Barotrauma.Items.Components projectile.Item.body.ApplyTorque(projectile.Item.body.Mass * degreeOfFailure * Rand.Range(-10.0f, 10.0f)); Item.RemoveContained(projectile.Item); } + LastProjectile = projectile; } LaunchProjSpecific(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RepairTool.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RepairTool.cs index ddf06b33f..bc9c658ae 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RepairTool.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RepairTool.cs @@ -523,7 +523,20 @@ namespace Barotrauma.Items.Components ApplyStatusEffectsOnTarget(user, deltaTime, ActionType.OnUse, new ISerializableEntity[] { targetStructure }); FixStructureProjSpecific(user, deltaTime, targetStructure, sectionIndex); - targetStructure.AddDamage(sectionIndex, -StructureFixAmount * degreeOfSuccess, user); + + float structureFixAmount = StructureFixAmount; + if (structureFixAmount >= 0f) + { + structureFixAmount *= 1 + user.GetStatValue(StatTypes.RepairToolStructureRepairMultiplier); + structureFixAmount *= 1 + item.GetQualityModifier(Quality.StatType.RepairToolStructureRepairMultiplier); + } + else + { + structureFixAmount *= 1 + user.GetStatValue(StatTypes.RepairToolStructureDamageMultiplier); + structureFixAmount *= 1 + item.GetQualityModifier(Quality.StatType.RepairToolStructureDamageMultiplier); + } + + targetStructure.AddDamage(sectionIndex, -structureFixAmount * degreeOfSuccess, user); //if the next section is small enough, apply the effect to it as well //(to make it easier to fix a small "left-over" section) @@ -535,7 +548,7 @@ namespace Barotrauma.Items.Components (nextSectionLength > 0 && nextSectionLength < Structure.WallSectionSize * 0.3f)) { //targetStructure.HighLightSection(sectionIndex + i); - targetStructure.AddDamage(sectionIndex + i, -StructureFixAmount * degreeOfSuccess); + targetStructure.AddDamage(sectionIndex + i, -structureFixAmount * degreeOfSuccess); } } return true; @@ -606,7 +619,8 @@ namespace Barotrauma.Items.Components levelResource.requiredItems.Any() && levelResource.HasRequiredItems(user, addMessage: false)) { - levelResource.DeattachTimer += deltaTime; + float addedDetachTime = deltaTime * (1f + user.GetStatValue(StatTypes.RepairToolDeattachTimeMultiplier)) * (1f + item.GetQualityModifier(Quality.StatType.RepairToolDeattachTimeMultiplier)); + levelResource.DeattachTimer += addedDetachTime; #if CLIENT Character.Controlled?.UpdateHUDProgressBar( this, @@ -698,7 +712,7 @@ namespace Barotrauma.Items.Components humanAnim.Crouching = true; } } - if (dist > reach * 0.8f || dist > reach * 0.5f && character.AnimController.Limbs.Any(l => l.inWater)) + if (dist > reach * 0.8f || dist > reach * 0.5f && character.AnimController.Limbs.Any(l => l.InWater)) { // Steer closer if (character.AIController.SteeringManager is IndoorsSteeringManager indoorSteering) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Throwable.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Throwable.cs index 9dbf4dd16..66b0e88f0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Throwable.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Throwable.cs @@ -123,18 +123,18 @@ namespace Barotrauma.Items.Components if (aim) { throwPos = MathUtils.WrapAnglePi(System.Math.Min(throwPos + deltaTime * 5.0f, MathHelper.PiOver2)); - ac.HoldItem(deltaTime, item, handlePos, aimPos, Vector2.Zero, false, throwPos); + ac.HoldItem(deltaTime, item, handlePos, aimPos, Vector2.Zero, aim: false, throwPos); } else { throwPos = 0; - ac.HoldItem(deltaTime, item, handlePos, holdPos, Vector2.Zero, false, holdAngle); + ac.HoldItem(deltaTime, item, handlePos, holdPos, Vector2.Zero, aim: false, holdAngle); } } else { throwPos = MathUtils.WrapAnglePi(throwPos - deltaTime * 15.0f); - ac.HoldItem(deltaTime, item, handlePos, aimPos, Vector2.Zero, false, throwPos); + ac.HoldItem(deltaTime, item, handlePos, aimPos, Vector2.Zero, aim: false, throwPos); if (throwPos < 0) { @@ -169,8 +169,8 @@ namespace Barotrauma.Items.Components item.body.FarseerBody.IsBullet = true; midAir = true; - ac.GetLimb(LimbType.Head).body.ApplyLinearImpulse(throwVector * 10.0f, maxVelocity: NetConfig.MaxPhysicsBodyVelocity); - ac.GetLimb(LimbType.Torso).body.ApplyLinearImpulse(throwVector * 10.0f, maxVelocity: NetConfig.MaxPhysicsBodyVelocity); + ac.GetLimb(LimbType.Head)?.body.ApplyLinearImpulse(throwVector * 10.0f, maxVelocity: NetConfig.MaxPhysicsBodyVelocity); + ac.GetLimb(LimbType.Torso)?.body.ApplyLinearImpulse(throwVector * 10.0f, maxVelocity: NetConfig.MaxPhysicsBodyVelocity); Limb rightHand = ac.GetLimb(LimbType.RightHand); item.body.AngularVelocity = rightHand.body.AngularVelocity; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemComponent.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemComponent.cs index 22f334166..be0b65af9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemComponent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemComponent.cs @@ -711,7 +711,27 @@ namespace Barotrauma.Items.Components bool CheckItems(RelatedItem relatedItem, IEnumerable itemList) { - bool Predicate(Item it) => it != null && it.Condition > 0.0f && relatedItem.MatchesItem(it); + bool Predicate(Item it) + { + if (it == null || it.Condition <= 0.0f || !relatedItem.MatchesItem(it)) { return false; } + if (item.Submarine != null) + { + var idCard = it.GetComponent(); + if (idCard != null) + { + //id cards don't work in enemy subs (except on items that only require the default "idcard" tag) + if (idCard.TeamID != CharacterTeamType.None && idCard.TeamID != item.Submarine.TeamID && relatedItem.Identifiers.Any(id => id != "idcard")) + { + return false; + } + else if (idCard.SubmarineSpecificID != 0 && item.Submarine.SubmarineSpecificIDTag != idCard.SubmarineSpecificID) + { + return false; + } + } + } + return true; + }; bool shouldBreak = false; bool inEditor = false; #if CLIENT @@ -747,7 +767,7 @@ namespace Barotrauma.Items.Components } } - public void ApplyStatusEffects(ActionType type, float deltaTime, Character character = null, Limb targetLimb = null, Entity useTarget = null, Character user = null, Vector2? worldPosition = null) + public void ApplyStatusEffects(ActionType type, float deltaTime, Character character = null, Limb targetLimb = null, Entity useTarget = null, Character user = null, Vector2? worldPosition = null, float applyOnUserFraction = 0.0f) { if (statusEffectLists == null) { return; } @@ -759,7 +779,13 @@ namespace Barotrauma.Items.Components { if (broken && !effect.AllowWhenBroken && effect.type != ActionType.OnBroken) { continue; } if (user != null) { effect.SetUser(user); } - item.ApplyStatusEffect(effect, type, deltaTime, character, targetLimb, useTarget, false, false, worldPosition); + item.ApplyStatusEffect(effect, type, deltaTime, character, targetLimb, useTarget, isNetworkEvent: false, checkCondition: false, worldPosition); + if (user != null && applyOnUserFraction > 0.0f && effect.HasTargetType(StatusEffect.TargetType.Character)) + { + effect.AfflictionMultiplier = applyOnUserFraction; + item.ApplyStatusEffect(effect, type, deltaTime, user, targetLimb == null ? null : user.AnimController.GetLimb(targetLimb.type), useTarget, false, false, worldPosition); + effect.AfflictionMultiplier = 1.0f; + } reducesCondition |= effect.ReducesItemCondition(); } //if any of the effects reduce the item's condition, set the user for OnBroken effects as well @@ -939,7 +965,7 @@ namespace Barotrauma.Items.Components } } - public void ParseMsg() + public virtual void ParseMsg() { string msg = TextManager.Get(Msg, true); if (msg != null) @@ -962,7 +988,7 @@ namespace Barotrauma.Items.Components AIObjectiveContainItem containObjective = null; if (character.AIController is HumanAIController aiController) { - containObjective = new AIObjectiveContainItem(character, container.GetContainableItemIdentifiers.ToArray(), container, currentObjective.objectiveManager, spawnItemIfNotFound: spawnItemIfNotFound) + containObjective = new AIObjectiveContainItem(character, container.ContainableItemIdentifiers.ToArray(), container, currentObjective.objectiveManager, spawnItemIfNotFound: spawnItemIfNotFound) { targetItemCount = itemCount, Equip = equip, diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs index 5502221ae..27b8542ff 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs @@ -5,6 +5,8 @@ using System.Linq; using System.Xml.Linq; using Barotrauma.Extensions; using FarseerPhysics; +using System.Collections.Immutable; +using Barotrauma.Abilities; namespace Barotrauma.Items.Components { @@ -23,6 +25,28 @@ namespace Barotrauma.Items.Components } } + class SlotRestrictions + { + public readonly int MaxStackSize; + public readonly List ContainableItems; + + public SlotRestrictions(int maxStackSize, List containableItems) + { + MaxStackSize = maxStackSize; + ContainableItems = containableItems; + } + + public bool MatchesItem(Item item) + { + return ContainableItems == null || ContainableItems.Count == 0 || ContainableItems.Any(c => c.MatchesItem(item)); + } + + public bool MatchesItem(ItemPrefab itemPrefab) + { + return ContainableItems == null || ContainableItems.Count == 0 || ContainableItems.Any(c => c.MatchesItem(itemPrefab)); + } + } + private bool alwaysContainedItemsSpawned; public ItemInventory Inventory; @@ -37,7 +61,7 @@ namespace Barotrauma.Items.Components public int Capacity { get { return capacity; } - set { capacity = Math.Max(value, 1); } + set { capacity = Math.Max(value, 0); } } //how many items can be contained @@ -62,17 +86,12 @@ namespace Barotrauma.Items.Components } } -#if DEBUG - [Editable] -#endif [Serialize("0.0,0.0", false, description: "The position where the contained items get drawn at (offset from the upper left corner of the sprite in pixels).")] public Vector2 ItemPos { get; set; } -#if DEBUG - [Editable] -#endif [Serialize("0.0,0.0", false, description: "The interval at which the contained items are spaced apart from each other (in pixels).")] public Vector2 ItemInterval { get; set; } + [Serialize(100, false, description: "How many items are placed in a row before starting a new row.")] public int ItemsPerRow { get; set; } @@ -90,6 +109,12 @@ namespace Barotrauma.Items.Components set; } + [Serialize(true, false)] + public bool AllowSwappingContainedItems + { + get; + set; + } [Serialize(false, false, description: "If set to true, interacting with this item will make the character interact with the contained item(s), automatically picking them up if they can be picked up.")] public bool AutoInteractWithContained @@ -98,6 +123,9 @@ namespace Barotrauma.Items.Components set; } + [Serialize(true, false)] + public bool AllowAccess { get; set; } + [Serialize(false, false)] public bool AccessOnlyWhenBroken { get; set; } @@ -140,13 +168,29 @@ namespace Barotrauma.Items.Components set; } + [Serialize(false, false, description: "Should the items be injected into the user.")] + public bool AutoInject + { + get; + set; + } + + [Serialize(0.5f, false, description: "The health threshold that the user must reach in order to activate the autoinjection.")] + public float AutoInjectThreshold + { + get; + set; + } + [Serialize(false, false)] public bool RemoveContainedItemsOnDeconstruct { get; set; } + private SlotRestrictions[] slotRestrictions; + public bool ShouldBeContained(string[] identifiersOrTags, out bool isRestrictionsDefined) { isRestrictionsDefined = containableRestrictions.Any(); - if (ContainableItems.None(ri => ri.MatchesItem(item))) { return false; } + if (slotRestrictions.None(s => s.MatchesItem(item))) { return false; } if (!isRestrictionsDefined) { return true; } return identifiersOrTags.Any(id => containableRestrictions.Any(r => r == id)); } @@ -154,22 +198,22 @@ namespace Barotrauma.Items.Components public bool ShouldBeContained(Item item, out bool isRestrictionsDefined) { isRestrictionsDefined = containableRestrictions.Any(); - if (ContainableItems.None(ri => ri.MatchesItem(item))) { return false; } + if (slotRestrictions.None(s => s.MatchesItem(item))) { return false; } if (!isRestrictionsDefined) { return true; } return containableRestrictions.Any(id => item.Prefab.Identifier == id || item.HasTag(id)); } - public List ContainableItems { get; private set; } = new List(); - - public IEnumerable GetContainableItemIdentifiers => ContainableItems.SelectMany(ri => ri.Identifiers); + private ImmutableHashSet containableItemIdentifiers; + public IEnumerable ContainableItemIdentifiers => containableItemIdentifiers; public override bool RecreateGUIOnResolutionChange => true; public ItemContainer(Item item, XElement element) - : base (item, element) + : base(item, element) { - Inventory = new ItemInventory(item, this, capacity, SlotsPerRow); - + int totalCapacity = capacity; + + List containableItems = null; foreach (XElement subElement in element.Elements()) { switch (subElement.Name.ToString().ToLowerInvariant()) @@ -181,34 +225,95 @@ namespace Barotrauma.Items.Components DebugConsole.ThrowError("Error in item config \"" + item.ConfigFile + "\" - containable with no identifiers."); continue; } - ContainableItems.Add(containable); + containableItems ??= new List(); + containableItems.Add(containable); + break; + case "subcontainer": + totalCapacity += subElement.GetAttributeInt("capacity", 1); break; } } + Inventory = new ItemInventory(item, this, totalCapacity, SlotsPerRow); + slotRestrictions = new SlotRestrictions[totalCapacity]; + for (int i = 0; i < capacity; i++) + { + slotRestrictions[i] = new SlotRestrictions(maxStackSize, containableItems); + } + int subContainerIndex = capacity; + foreach (XElement subElement in element.Elements()) + { + if (subElement.Name.ToString().ToLowerInvariant() != "subcontainer") { continue; } + + int subCapacity = subElement.GetAttributeInt("capacity", 1); + int subMaxStackSize = subElement.GetAttributeInt("maxstacksize", maxStackSize); + + List subContainableItems = null; + foreach (XElement subSubElement in subElement.Elements()) + { + if (subSubElement.Name.ToString().ToLowerInvariant() != "containable") { continue; } + + RelatedItem containable = RelatedItem.Load(subSubElement, returnEmpty: false, parentDebugName: item.Name); + if (containable == null) + { + DebugConsole.ThrowError("Error in item config \"" + item.ConfigFile + "\" - containable with no identifiers."); + continue; + } + subContainableItems ??= new List(); + subContainableItems.Add(containable); + } + + for (int i = subContainerIndex; i < subContainerIndex + subCapacity; i++) + { + slotRestrictions[i] = new SlotRestrictions(subMaxStackSize, subContainableItems); + } + subContainerIndex += subCapacity; + } + capacity = totalCapacity; InitProjSpecific(element); } + public int GetMaxStackSize(int slotIndex) + { + if (slotIndex < 0 || slotIndex >= capacity) + { + return 0; + } + return slotRestrictions[slotIndex].MaxStackSize; + } + partial void InitProjSpecific(XElement element); public void OnItemContained(Item containedItem) { item.SetContainedItemPositions(); - - RelatedItem ri = ContainableItems.Find(x => x.MatchesItem(containedItem)); - if (ri != null) + + int index = Inventory.FindIndex(containedItem); + if (index >= 0 && index < slotRestrictions.Length) { - activeContainedItems.RemoveAll(i => i.Item == containedItem); - foreach (StatusEffect effect in ri.statusEffects) + if (slotRestrictions[index].ContainableItems != null) { - activeContainedItems.Add(new ActiveContainedItem(containedItem, effect, ri.ExcludeBroken)); + activeContainedItems.RemoveAll(i => i.Item == containedItem); + foreach (var containableItem in slotRestrictions[index].ContainableItems) + { + if (!containableItem.MatchesItem(containedItem)) { continue; } + foreach (StatusEffect effect in containableItem.statusEffects) + { + activeContainedItems.Add(new ActiveContainedItem(containedItem, effect, containableItem.ExcludeBroken)); + } + } } - } + } //no need to Update() if this item has no statuseffects and no physics body IsActive = activeContainedItems.Count > 0 || Inventory.AllItems.Any(it => it.body != null); } + public override void Move(Vector2 amount) + { + SetContainedItemPositions(); + } + public void OnItemRemoved(Item containedItem) { activeContainedItems.RemoveAll(i => i.Item == containedItem); @@ -219,13 +324,24 @@ namespace Barotrauma.Items.Components public bool CanBeContained(Item item) { - if (ContainableItems.Count == 0) { return true; } - return ContainableItems.Find(c => c.MatchesItem(item)) != null; + return slotRestrictions.Any(s => s.MatchesItem(item)); } + + public bool CanBeContained(Item item, int index) + { + if (index < 0 || index >= capacity) { return false; } + return slotRestrictions[index].MatchesItem(item); + } + public bool CanBeContained(ItemPrefab itemPrefab) { - if (ContainableItems.Count == 0) { return true; } - return ContainableItems.Find(c => c.MatchesItem(itemPrefab)) != null; + return slotRestrictions.Any(s => s.MatchesItem(itemPrefab)); + } + + public bool CanBeContained(ItemPrefab itemPrefab, int index) + { + if (index < 0 || index >= capacity) { return false; } + return slotRestrictions[index].MatchesItem(itemPrefab); } readonly List targets = new List(); @@ -237,9 +353,24 @@ namespace Barotrauma.Items.Components SpawnAlwaysContainedItems(); } - if (item.ParentInventory is CharacterInventory) + if (item.ParentInventory is CharacterInventory ownerInventory) { item.SetContainedItemPositions(); + + if (AutoInject) + { + if (ownerInventory?.Owner is Character ownerCharacter && + ownerCharacter.HealthPercentage / 100f <= AutoInjectThreshold && + ownerCharacter.HasEquippedItem(item)) + { + foreach (Item item in Inventory.AllItemsMod) + { + item.ApplyStatusEffects(ActionType.OnUse, 1.0f, ownerCharacter); + item.GetComponent()?.Equip(ownerCharacter); + } + } + } + } else if (item.body != null && item.body.Enabled && @@ -256,6 +387,7 @@ namespace Barotrauma.Items.Components foreach (var activeContainedItem in activeContainedItems) { Item contained = activeContainedItem.Item; + if (activeContainedItem.ExcludeBroken && contained.Condition <= 0.0f) { continue; } StatusEffect effect = activeContainedItem.StatusEffect; @@ -275,11 +407,12 @@ namespace Barotrauma.Items.Components public override bool HasRequiredItems(Character character, bool addMessage, string msg = null) { - return (!AccessOnlyWhenBroken || Item.Condition <= 0) && base.HasRequiredItems(character, addMessage, msg); + return AllowAccess && (!AccessOnlyWhenBroken || Item.Condition <= 0) && base.HasRequiredItems(character, addMessage, msg); } public override bool Select(Character character) { + if (!AllowAccess) { return false; } if (item.Container != null) { return false; } if (AccessOnlyWhenBroken) { @@ -299,11 +432,15 @@ namespace Barotrauma.Items.Components } } } + var abilityItem = new AbilityItem(item); + character.CheckTalents(AbilityEffectType.OnOpenItemContainer, abilityItem); + return base.Select(character); } public override bool Pick(Character picker) { + if (!AllowAccess) { return false; } if (AccessOnlyWhenBroken) { if (item.Condition > 0) @@ -331,7 +468,7 @@ namespace Barotrauma.Items.Components public override bool Combine(Item item, Character user) { if (!AllowDragAndDrop && user != null) { return false; } - if (!ContainableItems.Any(it => it.MatchesItem(item))) { return false; } + if (!slotRestrictions.Any(s => s.MatchesItem(item))) { return false; } if (user != null && !user.CanAccessInventory(Inventory)) { return false; } if (Inventory.TryPutItem(item, user)) @@ -361,52 +498,65 @@ namespace Barotrauma.Items.Components Vector2 transformedItemInterval = ItemInterval * item.Scale; Vector2 transformedItemIntervalHorizontal = new Vector2(transformedItemInterval.X, 0.0f); Vector2 transformedItemIntervalVertical = new Vector2(0.0f, transformedItemInterval.Y); - if (item.body == null) + + if (ItemPos == Vector2.Zero && ItemInterval == Vector2.Zero) { - 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 (Math.Abs(item.Rotation) > 0.01f) - { - Matrix transform = Matrix.CreateRotationZ(MathHelper.ToRadians(-item.Rotation)); - transformedItemPos = Vector2.Transform(transformedItemPos, transform); - transformedItemInterval = Vector2.Transform(transformedItemInterval, transform); - transformedItemIntervalHorizontal = Vector2.Transform(transformedItemIntervalHorizontal, transform); - transformedItemIntervalVertical = Vector2.Transform(transformedItemIntervalVertical, transform); - } + transformedItemPos = item.Position; } else { - Matrix transform = Matrix.CreateRotationZ(item.body.Rotation); - if (item.body.Dir == -1.0f) + if (item.body == null) { - transformedItemPos.X = -transformedItemPos.X; - transformedItemInterval.X = -transformedItemInterval.X; - transformedItemIntervalHorizontal.X = -transformedItemIntervalHorizontal.X; + 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 (Math.Abs(item.Rotation) > 0.01f) + { + Matrix transform = Matrix.CreateRotationZ(MathHelper.ToRadians(-item.Rotation)); + transformedItemPos = Vector2.Transform(transformedItemPos - item.Position, transform) + item.Position; + transformedItemInterval = Vector2.Transform(transformedItemInterval, transform); + transformedItemIntervalHorizontal = Vector2.Transform(transformedItemIntervalHorizontal, transform); + transformedItemIntervalVertical = Vector2.Transform(transformedItemIntervalVertical, transform); + } } - transformedItemPos = Vector2.Transform(transformedItemPos, transform); - transformedItemInterval = Vector2.Transform(transformedItemInterval, transform); - transformedItemIntervalHorizontal = Vector2.Transform(transformedItemIntervalHorizontal, transform); - transformedItemPos += item.Position; - } + else + { + Matrix transform = Matrix.CreateRotationZ(item.body.Rotation); + 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.Position; + } + } float currentRotation = itemRotation; if (item.body != null) { + currentRotation *= item.body.Dir; currentRotation += item.body.Rotation; } + else + { + currentRotation += MathHelper.ToRadians(-item.Rotation); + } int i = 0; Vector2 currentItemPos = transformedItemPos; @@ -461,6 +611,8 @@ namespace Barotrauma.Items.Components public override void OnItemLoaded() { + Inventory.AllowSwappingContainedItems = AllowSwappingContainedItems; + containableItemIdentifiers = slotRestrictions.SelectMany(s => s.ContainableItems?.SelectMany(ri => ri.Identifiers) ?? Enumerable.Empty()).ToImmutableHashSet(); if (item.Submarine == null || !item.Submarine.Loading) { SpawnAlwaysContainedItems(); @@ -488,7 +640,21 @@ namespace Barotrauma.Items.Components } itemIds = null; } - SpawnAlwaysContainedItems(); + + //outpost and ruins are loaded in multiple stages (each module is loaded separately) + //spawning items at this point during the generation will cause ID overlaps with the entities in the modules loaded afterwards + //so let's not spawn them at this point, but in the 1st Update() + if (item.Submarine?.Info != null && (item.Submarine.Info.IsOutpost || item.Submarine.Info.IsRuin)) + { + if (SpawnWithId.Length > 0) + { + IsActive = true; + } + } + else + { + SpawnAlwaysContainedItems(); + } } private void SpawnAlwaysContainedItems() diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Deconstructor.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Deconstructor.cs index 575401584..e3eb12bcd 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Deconstructor.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Deconstructor.cs @@ -1,4 +1,5 @@ -using Barotrauma.Extensions; +using Barotrauma.Abilities; +using Barotrauma.Extensions; using Barotrauma.Networking; using System; using System.Collections.Generic; @@ -14,6 +15,12 @@ namespace Barotrauma.Items.Components private bool hasPower; + private Character user; + + private float userDeconstructorSpeedMultiplier = 1.0f; + + private const float TinkeringSpeedIncrease = 1.5f; + private ItemContainer inputContainer, outputContainer; public ItemContainer InputContainer @@ -25,7 +32,10 @@ namespace Barotrauma.Items.Components { get { return outputContainer; } } - + + [Serialize(false, true)] + public bool DeconstructItemsSimultaneously { get; set; } + [Editable, Serialize(1.0f, true)] public float DeconstructionSpeed { get; set; } @@ -81,65 +91,177 @@ namespace Barotrauma.Items.Components if (powerConsumption <= 0.0f) { Voltage = 1.0f; } progressTimer += deltaTime * Math.Min(Voltage, 1.0f); - var targetItem = inputContainer.Inventory.LastOrDefault(); - if (targetItem == null) { return; } - - float deconstructTime = targetItem.Prefab.DeconstructItems.Any() ? targetItem.Prefab.DeconstructTime / DeconstructionSpeed : 1.0f; - - progressState = Math.Min(progressTimer / deconstructTime, 1.0f); - if (progressTimer > deconstructTime) + float tinkeringStrength = 0f; + if (repairable.IsTinkering) { - // In multiplayer, the server handles the deconstruction into new items - if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; } + tinkeringStrength = repairable.TinkeringStrength; + } + // doesn't quite work properly, remaining time changes if tinkering stops + float deconstructionSpeedModifier = userDeconstructorSpeedMultiplier * (1f + tinkeringStrength * TinkeringSpeedIncrease); - if (targetItem.Prefab.RandomDeconstructionOutput) + if (DeconstructItemsSimultaneously) + { + float deconstructTime = 0.0f; + foreach (Item targetItem in inputContainer.Inventory.AllItems) { - int amount = targetItem.Prefab.RandomDeconstructionOutputAmount; - List deconstructItemIndexes = new List(); - for (int i = 0; i < targetItem.Prefab.DeconstructItems.Count; i++) - { - deconstructItemIndexes.Add(i); - } - List commonness = targetItem.Prefab.DeconstructItems.Select(i => i.Commonness).ToList(); - List products = new List(); - - for (int i = 0; i < amount; i++) - { - if (deconstructItemIndexes.Count < 1) { break; } - var itemIndex = ToolBox.SelectWeightedRandom(deconstructItemIndexes, commonness, Rand.RandSync.Unsynced); - products.Add(targetItem.Prefab.DeconstructItems[itemIndex]); - var removeIndex = deconstructItemIndexes.IndexOf(itemIndex); - deconstructItemIndexes.RemoveAt(removeIndex); - commonness.RemoveAt(removeIndex); - } - foreach (DeconstructItem deconstructProduct in products) - { - CreateDeconstructProduct(deconstructProduct); - } + deconstructTime += targetItem.Prefab.DeconstructTime / (DeconstructionSpeed * deconstructionSpeedModifier); } - else + + progressState = Math.Min(progressTimer / deconstructTime, 1.0f); + if (progressTimer > deconstructTime) { - foreach (DeconstructItem deconstructProduct in targetItem.Prefab.DeconstructItems) + List items = inputContainer.Inventory.AllItems.ToList(); + foreach (Item targetItem in items) { - CreateDeconstructProduct(deconstructProduct); + if ((Entity.Spawner?.IsInRemoveQueue(targetItem) ?? false) || !inputContainer.Inventory.AllItems.Contains(targetItem)) { continue; } + var validDeconstructItems = targetItem.Prefab.DeconstructItems.FindAll(it => + (it.RequiredDeconstructor.Length == 0 || it.RequiredDeconstructor.Any(r => item.HasTag(r) || item.Prefab.Identifier.Equals(r, StringComparison.OrdinalIgnoreCase))) && + (it.RequiredOtherItem.Length == 0 || it.RequiredOtherItem.Any(r => items.Any(it => it != targetItem && (it.HasTag(r) || it.Prefab.Identifier.Equals(r, StringComparison.OrdinalIgnoreCase)))))); + + ProcessItem(targetItem, items, validDeconstructItems, allowRemove: validDeconstructItems.Any() || !targetItem.Prefab.DeconstructItems.Any()); + } +#if SERVER + item.CreateServerEvent(this); +#endif + progressTimer = 0.0f; + progressState = 0.0f; + + } + } + else + { + var targetItem = inputContainer.Inventory.LastOrDefault(); + if (targetItem == null) { return; } + + var validDeconstructItems = targetItem.Prefab.DeconstructItems.FindAll(it => + it.RequiredDeconstructor.Length == 0 || it.RequiredDeconstructor.Any(r => item.HasTag(r) || item.Prefab.Identifier.Equals(r, StringComparison.OrdinalIgnoreCase))); + + float deconstructTime = validDeconstructItems.Any() ? targetItem.Prefab.DeconstructTime / (DeconstructionSpeed * deconstructionSpeedModifier) : 1.0f; + + progressState = Math.Min(progressTimer / deconstructTime, 1.0f); + if (progressTimer > deconstructTime) + { + ProcessItem(targetItem, inputContainer.Inventory.AllItemsMod, validDeconstructItems, allowRemove: validDeconstructItems.Any() || !targetItem.Prefab.DeconstructItems.Any()); + +#if SERVER + item.CreateServerEvent(this); +#endif + progressTimer = 0.0f; + progressState = 0.0f; + + } + } + } + + private void ProcessItem(Item targetItem, IEnumerable inputItems, List validDeconstructItems, bool allowRemove = true) + { + // In multiplayer, the server handles the deconstruction into new items + if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; } + + if (user != null && !user.Removed) + { + var abilityTargetItem = new AbilityItem(targetItem); + user.CheckTalents(AbilityEffectType.OnItemDeconstructed, abilityTargetItem); + } + + if (targetItem.Prefab.RandomDeconstructionOutput) + { + int amount = targetItem.Prefab.RandomDeconstructionOutputAmount; + List deconstructItemIndexes = new List(); + for (int i = 0; i < validDeconstructItems.Count; i++) + { + deconstructItemIndexes.Add(i); + } + List commonness = validDeconstructItems.Select(i => i.Commonness).ToList(); + List products = new List(); + + for (int i = 0; i < amount; i++) + { + if (deconstructItemIndexes.Count < 1) { break; } + var itemIndex = ToolBox.SelectWeightedRandom(deconstructItemIndexes, commonness, Rand.RandSync.Unsynced); + products.Add(validDeconstructItems[itemIndex]); + var removeIndex = deconstructItemIndexes.IndexOf(itemIndex); + deconstructItemIndexes.RemoveAt(removeIndex); + commonness.RemoveAt(removeIndex); + } + + foreach (DeconstructItem deconstructProduct in products) + { + CreateDeconstructProduct(deconstructProduct, inputItems); + } + } + else + { + foreach (DeconstructItem deconstructProduct in validDeconstructItems) + { + CreateDeconstructProduct(deconstructProduct, inputItems); + } + } + + void CreateDeconstructProduct(DeconstructItem deconstructProduct, IEnumerable inputItems) + { + float percentageHealth = targetItem.Condition / targetItem.MaxCondition; + + if (percentageHealth <= deconstructProduct.MinCondition || percentageHealth > deconstructProduct.MaxCondition) { return; } + + if (!(MapEntityPrefab.Find(null, deconstructProduct.ItemIdentifier) is ItemPrefab itemPrefab)) + { + DebugConsole.ThrowError("Tried to deconstruct item \"" + targetItem.Name + "\" but couldn't find item prefab \"" + deconstructProduct.ItemIdentifier + "\"!"); + return; + } + + float condition = deconstructProduct.CopyCondition ? + percentageHealth * itemPrefab.Health : + itemPrefab.Health * Rand.Range(deconstructProduct.OutConditionMin, deconstructProduct.OutConditionMax); + + if (DeconstructItemsSimultaneously && deconstructProduct.RequiredOtherItem.Length > 0) + { + foreach (Item otherItem in inputItems) + { + if (targetItem == otherItem) { continue; } + if (deconstructProduct.RequiredOtherItem.Any(r => otherItem.HasTag(r) || r.Equals(otherItem.Prefab.Identifier, StringComparison.OrdinalIgnoreCase))) + { + user.CheckTalents(AbilityEffectType.OnGeneticMaterialCombinedOrRefined); + foreach (Character character in Character.GetFriendlyCrew(user)) + { + character.CheckTalents(AbilityEffectType.OnCrewGeneticMaterialCombinedOrRefined); + } + + var geneticMaterial1 = targetItem.GetComponent(); + var geneticMaterial2 = otherItem.GetComponent(); + if (geneticMaterial1 != null && geneticMaterial2 != null) + { + if (geneticMaterial1.Combine(geneticMaterial2, user)) + { + inputContainer.Inventory.RemoveItem(otherItem); + OutputContainer.Inventory.RemoveItem(otherItem); + Entity.Spawner.AddToRemoveQueue(otherItem); + } + allowRemove = false; + return; + } + inputContainer.Inventory.RemoveItem(otherItem); + OutputContainer.Inventory.RemoveItem(otherItem); + Entity.Spawner.AddToRemoveQueue(otherItem); + } } } - void CreateDeconstructProduct(DeconstructItem deconstructProduct) + int amount = 1; + + if (user != null && !user.Removed) { - float percentageHealth = targetItem.Condition / targetItem.Prefab.Health; - if (percentageHealth <= deconstructProduct.MinCondition || percentageHealth > deconstructProduct.MaxCondition) { return; } + var itemsCreated = new AbilityValueItem(amount, targetItem.Prefab); + user.CheckTalents(AbilityEffectType.OnItemDeconstructedMaterial, itemsCreated); + amount = (int)itemsCreated.Value; - if (!(MapEntityPrefab.Find(null, deconstructProduct.ItemIdentifier) is ItemPrefab itemPrefab)) - { - DebugConsole.ThrowError("Tried to deconstruct item \"" + targetItem.Name + "\" but couldn't find item prefab \"" + deconstructProduct.ItemIdentifier + "\"!"); - return; - } - - float condition = deconstructProduct.CopyCondition ? - percentageHealth * itemPrefab.Health : - itemPrefab.Health * deconstructProduct.OutCondition; + // used to spawn items directly into the deconstructor + var itemContainer = new AbilityItemPrefabItem(item, targetItem.Prefab); + user.CheckTalents(AbilityEffectType.OnItemDeconstructedInventory, itemContainer); + } + for (int i = 0; i < amount; i++) + { Entity.Spawner.AddToSpawnQueue(itemPrefab, outputContainer.Inventory, condition, onSpawned: (Item spawnedItem) => { for (int i = 0; i < outputContainer.Capacity; i++) @@ -153,36 +275,31 @@ namespace Barotrauma.Items.Components PutItemsToLinkedContainer(); }); } + } - if (targetItem.Prefab.AllowDeconstruct) + if (targetItem.AllowDeconstruct && allowRemove) + { + //drop all items that are inside the deconstructed item + foreach (ItemContainer ic in targetItem.GetComponents()) { - //drop all items that are inside the deconstructed item - foreach (ItemContainer ic in targetItem.GetComponents()) - { - if (ic?.Inventory == null || ic.RemoveContainedItemsOnDeconstruct) { continue; } - ic.Inventory.AllItemsMod.ForEach(containedItem => outputContainer.Inventory.TryPutItem(containedItem, user: null)); - } - inputContainer.Inventory.RemoveItem(targetItem); - Entity.Spawner.AddToRemoveQueue(targetItem); - MoveInputQueue(); - PutItemsToLinkedContainer(); + if (ic?.Inventory == null || ic.RemoveContainedItemsOnDeconstruct) { continue; } + ic.Inventory.AllItemsMod.ForEach(containedItem => outputContainer.Inventory.TryPutItem(containedItem, user: null)); + } + inputContainer.Inventory.RemoveItem(targetItem); + Entity.Spawner.AddToRemoveQueue(targetItem); + MoveInputQueue(); + PutItemsToLinkedContainer(); + } + else + { + if (!outputContainer.Inventory.CanBePut(targetItem) || (Entity.Spawner?.IsInRemoveQueue(targetItem) ?? false)) + { + targetItem.Drop(dropper: null); } else { - if (!outputContainer.Inventory.CanBePut(targetItem)) - { - targetItem.Drop(dropper: null); - } - else - { - outputContainer.Inventory.TryPutItem(targetItem, user: null, createNetworkEvent: true); - } + outputContainer.Inventory.TryPutItem(targetItem, user: null, createNetworkEvent: true); } -#if SERVER - item.CreateServerEvent(this); -#endif - progressTimer = 0.0f; - progressState = 0.0f; } } @@ -190,7 +307,7 @@ namespace Barotrauma.Items.Components { if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; } if (outputContainer.Inventory.IsEmpty()) { return; } - + foreach (MapEntity linkedTo in item.linkedTo) { if (linkedTo is Item linkedItem) @@ -201,7 +318,7 @@ namespace Barotrauma.Items.Components if (itemContainer == null) { continue; } outputContainer.Inventory.AllItemsMod.ForEach(containedItem => itemContainer.Inventory.TryPutItem(containedItem, user: null, createNetworkEvent: true)); } - } + } } /// @@ -221,14 +338,54 @@ namespace Barotrauma.Items.Components } } + private IEnumerable<(Item item, DeconstructItem output)> GetAvailableOutputs(bool checkRequiredOtherItems = true) + { + var items = inputContainer.Inventory.AllItems; + foreach (Item inputItem in items) + { + if (!inputItem.AllowDeconstruct) { continue; } + foreach (var deconstructItem in inputItem.Prefab.DeconstructItems) + { + if (deconstructItem.RequiredDeconstructor.Length > 0) + { + if (!deconstructItem.RequiredDeconstructor.Any(r => item.HasTag(r) || item.Prefab.Identifier.Equals(r, StringComparison.OrdinalIgnoreCase))) { continue; } + } + if (deconstructItem.RequiredOtherItem.Length > 0 && checkRequiredOtherItems) + { + if (!deconstructItem.RequiredOtherItem.Any(r => items.Any(it => it.HasTag(r) || it.Prefab.Identifier.Equals(r, StringComparison.OrdinalIgnoreCase)))) { continue; } + bool validOtherItemFound = false; + foreach (Item otherInputItem in items) + { + if (otherInputItem == inputItem) { continue; } + if (!deconstructItem.RequiredOtherItem.Any(r => otherInputItem.HasTag(r) || otherInputItem.Prefab.Identifier.Equals(r, StringComparison.OrdinalIgnoreCase))) { continue; } + + var geneticMaterial1 = inputItem.GetComponent(); + var geneticMaterial2 = otherInputItem.GetComponent(); + if (geneticMaterial1 != null && geneticMaterial2 != null) + { + if (!geneticMaterial1.CanBeCombinedWith(geneticMaterial2)) { continue; } + } + validOtherItemFound = true; + } + if (!validOtherItemFound) { continue; } + } + yield return (inputItem, deconstructItem); + } + } + } + private void SetActive(bool active, Character user = null) { PutItemsToLinkedContainer(); + this.user = user; + if (inputContainer.Inventory.IsEmpty()) { active = false; } IsActive = active; currPowerConsumption = IsActive ? powerConsumption : 0.0f; + userDeconstructorSpeedMultiplier = user != null ? 1f + user.GetStatValue(StatTypes.DeconstructorSpeedMultiplier) : 1f; + #if SERVER if (user != null) { @@ -241,10 +398,6 @@ namespace Barotrauma.Items.Components progressState = 0.0f; } -#if CLIENT - activateButton.Text = TextManager.Get(IsActive ? "DeconstructorCancel" : "DeconstructorDeconstruct"); -#endif - inputContainer.Inventory.Locked = IsActive; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Engine.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Engine.cs index 0e9457394..266c28fff 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Engine.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Engine.cs @@ -73,6 +73,8 @@ namespace Barotrauma.Items.Components } } + private const float TinkeringForceIncrease = 1.5f; + public Engine(Item item, XElement element) : base(item, element) { @@ -113,31 +115,35 @@ namespace Barotrauma.Items.Components Force = MathHelper.Lerp(force, (Voltage < MinVoltage) ? 0.0f : targetForce, 0.1f); if (Math.Abs(Force) > 1.0f) { + float voltageFactor = MinVoltage <= 0.0f ? 1.0f : Math.Min(Voltage, 1.0f); + float currForce = force * voltageFactor; + float condition = item.Condition / item.MaxCondition; + // Broken engine makes more noise. + float noise = Math.Abs(currForce) * MathHelper.Lerp(1.5f, 1f, condition); + UpdateAITargets(noise); //arbitrary multiplier that was added to changes in submarine mass without having to readjust all engines float forceMultiplier = 0.1f; if (User != null) { forceMultiplier *= MathHelper.Lerp(0.5f, 2.0f, (float)Math.Sqrt(User.GetSkillLevel("helm") / 100)); } + currForce *= maxForce * forceMultiplier; + if (item.GetComponent() is Repairable repairable && repairable.IsTinkering) + { + currForce *= 1f + repairable.TinkeringStrength * TinkeringForceIncrease; + } - float voltageFactor = MinVoltage <= 0.0f ? 1.0f : Math.Min(Voltage, 1.0f); - Vector2 currForce = new Vector2(force * maxForce * forceMultiplier * voltageFactor, 0.0f); //less effective when in a bad condition - currForce *= MathHelper.Lerp(0.5f, 2.0f, item.Condition / item.MaxCondition); + currForce *= MathHelper.Lerp(0.5f, 2.0f, condition); if (item.Submarine.FlippedX) { currForce *= -1; } - item.Submarine.ApplyForce(currForce); + Vector2 forceVector = new Vector2(currForce, 0); + item.Submarine.ApplyForce(forceVector); UpdatePropellerDamage(deltaTime); - float maxChangeSpeed = 0.5f; - float modifier = 2; - float noise = MathUtils.NearlyEqual(0.0f, maxForce) ? 0.0f : currForce.Length() * forceMultiplier * modifier / maxForce; - float min = Math.Max(1 - maxChangeSpeed, 0); - float max = 1 + maxChangeSpeed; - UpdateAITargets(Math.Clamp(noise, min, max), deltaTime); #if CLIENT particleTimer -= deltaTime; if (particleTimer <= 0.0f) { - Vector2 particleVel = -currForce.ClampLength(5000.0f) / 5.0f; + Vector2 particleVel = -forceVector.ClampLength(5000.0f) / 5.0f; GameMain.ParticleManager.CreateParticle("bubbles", item.WorldPosition + PropellerPos * item.Scale, particleVel * Rand.Range(0.9f, 1.1f), 0.0f, item.CurrentHull); @@ -147,14 +153,14 @@ namespace Barotrauma.Items.Components } } - private void UpdateAITargets(float increaseSpeed, float deltaTime) + private void UpdateAITargets(float noise) { if (item.AiTarget != null) { - item.AiTarget.IncreaseSoundRange(deltaTime, increaseSpeed); + item.AiTarget.SoundRange = MathHelper.Lerp(item.AiTarget.MinSoundRange, item.AiTarget.MaxSoundRange, noise / 100); if (item.CurrentHull != null && item.CurrentHull.AiTarget != null) { - // It's possible that some othe item increases the hull's soundrange more than the engine. + // It's possible that some other item increases the hull's soundrange more than the engine. item.CurrentHull.AiTarget.SoundRange = Math.Max(item.CurrentHull.AiTarget.SoundRange, item.AiTarget.SoundRange); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs index ba0fd4715..461ae0617 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs @@ -4,8 +4,8 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; -using System.Security.Cryptography; using System.Xml.Linq; +using Barotrauma.Abilities; namespace Barotrauma.Items.Components { @@ -32,6 +32,8 @@ namespace Barotrauma.Items.Components [Serialize(1.0f, true)] public float SkillRequirementMultiplier { get; set; } + private const float TinkeringSpeedIncrease = 1.5f; + private enum FabricatorState { Active = 1, @@ -240,7 +242,8 @@ namespace Barotrauma.Items.Components public override void Update(float deltaTime, Camera cam) { - if (fabricatedItem == null || !CanBeFabricated(fabricatedItem)) + var availableIngredients = GetAvailableIngredients(); + if (fabricatedItem == null || !CanBeFabricated(fabricatedItem, availableIngredients, user)) { CancelFabricating(); return; @@ -278,48 +281,91 @@ namespace Barotrauma.Items.Components if (powerConsumption <= 0) { Voltage = 1.0f; } - timeUntilReady -= deltaTime * Math.Min(Voltage, 1.0f); + float tinkeringStrength = 0f; + if (repairable.IsTinkering) + { + tinkeringStrength = repairable.TinkeringStrength; + } + float fabricationSpeedIncrease = 1f + tinkeringStrength * TinkeringSpeedIncrease; + + timeUntilReady -= deltaTime * fabricationSpeedIncrease * Math.Min(Voltage, 1.0f); + UpdateRequiredTimeProjSpecific(); if (timeUntilReady > 0.0f) { return; } if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer) { - var availableIngredients = GetAvailableIngredients(); - foreach (FabricationRecipe.RequiredItem ingredient in fabricatedItem.RequiredItems) - { - for (int i = 0; i < ingredient.Amount; i++) + fabricatedItem.RequiredItems.ForEach(requiredItem => { + for (int usedPrefabsAmount = 0; usedPrefabsAmount < requiredItem.Amount; usedPrefabsAmount++) { - var availableItem = availableIngredients.FirstOrDefault(it => - it != null && ingredient.ItemPrefabs.Contains(it.Prefab) && - it.ConditionPercentage >= ingredient.MinCondition * 100.0f && - it.ConditionPercentage <= ingredient.MaxCondition * 100.0f); - if (availableItem == null) { continue; } - - if (ingredient.UseCondition && availableItem.ConditionPercentage - ingredient.MinCondition * 100 > 0.0f) //Leave it behind with reduced condition if it has enough to stay above 0 + foreach (ItemPrefab requiredPrefab in requiredItem.ItemPrefabs) { - availableItem.Condition -= availableItem.Prefab.Health * ingredient.MinCondition; - continue; + if (!availableIngredients.ContainsKey(requiredPrefab.Identifier)) { continue; } + + var availablePrefabs = availableIngredients[requiredPrefab.Identifier]; + var availablePrefab = availablePrefabs.FirstOrDefault(potentialPrefab => + { + return potentialPrefab.ConditionPercentage >= requiredItem.MinCondition * 100.0f && + potentialPrefab.ConditionPercentage <= requiredItem.MaxCondition * 100.0f; + }); + + if (availablePrefab == null) { continue; } + + if (requiredItem.UseCondition && availablePrefab.ConditionPercentage - requiredItem.MinCondition * 100 > 0.0f) //Leave it behind with reduced condition if it has enough to stay above 0 + { + availablePrefab.Condition -= availablePrefab.Prefab.Health * requiredItem.MinCondition; + continue; + } + + availablePrefabs.Remove(availablePrefab); + Entity.Spawner.AddToRemoveQueue(availablePrefab); + inputContainer.Inventory.RemoveItem(availablePrefab); } - availableIngredients.Remove(availableItem); - Entity.Spawner.AddToRemoveQueue(availableItem); - inputContainer.Inventory.RemoveItem(availableItem); } + }); + + int amountFittingContainer = outputContainer.Inventory.HowManyCanBePut(fabricatedItem.TargetItem, fabricatedItem.OutCondition * fabricatedItem.TargetItem.Health); + + var fabricationValueItem = new AbilityValueItem(fabricatedItem.Amount, fabricatedItem.TargetItem); + + int quality = 0; + if (user?.Info != null) + { + foreach (Character character in Character.CharacterList.Where(c => c.TeamID == user.TeamID)) + { + character.CheckTalents(AbilityEffectType.OnAllyItemFabricatedAmount, fabricationValueItem); + } + user.CheckTalents(AbilityEffectType.OnItemFabricatedAmount, fabricationValueItem); + + quality = GetFabricatedItemQuality(fabricatedItem, user); } - Character tempUser = user; - int amountFittingContainer = outputContainer.Inventory.HowManyCanBePut(fabricatedItem.TargetItem, fabricatedItem.OutCondition * fabricatedItem.TargetItem.Health); - for (int i = 0; i < fabricatedItem.Amount; i++) + var tempUser = user; + for (int i = 0; i < (int)fabricationValueItem.Value; i++) { + float outCondition = fabricatedItem.OutCondition; if (i < amountFittingContainer) { - Entity.Spawner.AddToSpawnQueue(fabricatedItem.TargetItem, outputContainer.Inventory, fabricatedItem.TargetItem.Health * fabricatedItem.OutCondition, - onSpawned: (Item spawnedItem) => { onItemSpawned(spawnedItem, tempUser); }); + Entity.Spawner.AddToSpawnQueue(fabricatedItem.TargetItem, outputContainer.Inventory, fabricatedItem.TargetItem.Health * outCondition, + onSpawned: (Item spawnedItem) => + { + onItemSpawned(spawnedItem, tempUser); + spawnedItem.Quality = quality; + //reset the condition in case the max condition is higher than the prefab's due to e.g. quality modifiers + spawnedItem.Condition = spawnedItem.MaxCondition * outCondition; + }); } else { - Entity.Spawner.AddToSpawnQueue(fabricatedItem.TargetItem, item.Position, item.Submarine, fabricatedItem.TargetItem.Health * fabricatedItem.OutCondition, - onSpawned: (Item spawnedItem) => { onItemSpawned(spawnedItem, tempUser); }); + Entity.Spawner.AddToSpawnQueue(fabricatedItem.TargetItem, item.Position, item.Submarine, fabricatedItem.TargetItem.Health * outCondition, + onSpawned: (Item spawnedItem) => + { + onItemSpawned(spawnedItem, tempUser); + spawnedItem.Quality = quality; + //reset the condition in case the max condition is higher than the prefab's due to e.g. quality modifiers + spawnedItem.Condition = spawnedItem.MaxCondition * outCondition; + }); } } @@ -333,16 +379,19 @@ namespace Barotrauma.Items.Components } } } - if (user?.Info != null && !user.Removed) { foreach (Skill skill in fabricatedItem.RequiredSkills) { float userSkill = user.GetSkillLevel(skill.Identifier); + float addedSkill = skill.Level * SkillSettings.Current.SkillIncreasePerFabricatorRequiredSkill / Math.Max(userSkill, 1.0f); + var addedSkillValue = new AbilityValueString(0f, skill.Identifier); + user.CheckTalents(AbilityEffectType.OnItemFabricationSkillGain, addedSkillValue); + addedSkill += addedSkillValue.Value; + user.Info.IncreaseSkillLevel( skill.Identifier, - skill.Level * SkillSettings.Current.SkillIncreasePerFabricatorRequiredSkill / Math.Max(userSkill, 1.0f), - user.Position + Vector2.UnitY * 150.0f); + addedSkill); } } @@ -363,26 +412,57 @@ namespace Barotrauma.Items.Components } } - partial void UpdateRequiredTimeProjSpecific(); - - private bool CanBeFabricated(FabricationRecipe fabricableItem) + private int GetFabricatedItemQuality(FabricationRecipe fabricatedItem, Character user) { - if (fabricableItem == null) { return false; } - List availableIngredients = GetAvailableIngredients(); - return CanBeFabricated(fabricableItem, availableIngredients); + if (user == null) { return 0; } + if (fabricatedItem.TargetItem.ConfigElement.GetChildElement("Quality") == null) { return 0; } + int quality = 0; + float floatQuality = 0.0f; + foreach (string tag in fabricatedItem.TargetItem.Tags) + { + floatQuality += user.Info.GetSavedStatValue(StatTypes.IncreaseFabricationQuality, tag); + } + quality = (int)floatQuality; + + const int MaxCraftingSkill = 100; + + quality += fabricatedItem.RequiredSkills.All(s => user.GetSkillLevel(s.Identifier) >= MaxCraftingSkill) ? 1 : 0; + quality += FabricationDegreeOfSuccess(user, fabricatedItem.RequiredSkills) >= 0.5f ? 1 : 0; + return quality; } - private bool CanBeFabricated(FabricationRecipe fabricableItem, IEnumerable availableIngredients) + partial void UpdateRequiredTimeProjSpecific(); + + private bool CanBeFabricated(FabricationRecipe fabricableItem, Dictionary> availableIngredients, Character character) { - if (fabricableItem == null) { return false; } - foreach (FabricationRecipe.RequiredItem requiredItem in fabricableItem.RequiredItems) + if (fabricableItem == null) { return false; } + if (fabricableItem.RequiresRecipe && (character == null || !character.HasRecipeForItem(fabricableItem.TargetItem.Identifier))) { return false; } + + return fabricableItem.RequiredItems.All(requiredItem => { - if (availableIngredients.Count(it => IsItemValidIngredient(it, requiredItem)) < requiredItem.Amount) + int availablePrefabsAmount = 0; + foreach (ItemPrefab requiredPrefab in requiredItem.ItemPrefabs) { - return false; + if (!availableIngredients.ContainsKey(requiredPrefab.Identifier)) { continue; } + + var availablePrefabs = availableIngredients[requiredPrefab.Identifier]; + foreach (Item availablePrefab in availablePrefabs) + { + if (availablePrefab.Condition / availablePrefab.Prefab.Health >= requiredItem.MinCondition && + availablePrefab.Condition / availablePrefab.Prefab.Health <= requiredItem.MaxCondition) + { + availablePrefabsAmount++; + } + + if (availablePrefabsAmount >= requiredItem.Amount) + { + return true; + } + } } - } - return true; + + return false; + }); } private float GetRequiredTime(FabricationRecipe fabricableItem, Character user) @@ -416,7 +496,7 @@ namespace Barotrauma.Items.Components /// Get a list of all items available in the input container and linked containers /// /// - private List GetAvailableIngredients() + private Dictionary> GetAvailableIngredients() { List availableIngredients = new List(); availableIngredients.AddRange(inputContainer.Inventory.AllItems); @@ -448,7 +528,19 @@ namespace Barotrauma.Items.Components } #endif - return availableIngredients; + Dictionary> ingredientsDictionary = new Dictionary>(); + for (int i = 0; i < availableIngredients.Count; i++) + { + var itemIdentifier = availableIngredients[i].prefab.Identifier; + if (!ingredientsDictionary.ContainsKey(itemIdentifier)) + { + ingredientsDictionary[itemIdentifier] = new List(availableIngredients.Count); + } + + ingredientsDictionary[itemIdentifier].Add(availableIngredients[i]); + } + + return ingredientsDictionary; } /// @@ -463,40 +555,41 @@ namespace Barotrauma.Items.Components bool isClient = GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient; var availableIngredients = GetAvailableIngredients(); - foreach (var requiredItem in targetItem.RequiredItems) - { + targetItem.RequiredItems.ForEach(requiredItem => { for (int i = 0; i < requiredItem.Amount; i++) { - var matchingItem = availableIngredients.Find(it => !usedItems.Contains(it) && IsItemValidIngredient(it, requiredItem)); - if (matchingItem == null) { continue; } + foreach (ItemPrefab requiredPrefab in requiredItem.ItemPrefabs) + { + if (!availableIngredients.ContainsKey(requiredPrefab.Identifier)) { continue; } - availableIngredients.Remove(matchingItem); - - if (matchingItem.ParentInventory == inputContainer.Inventory) - { - //already in input container, all good - usedItems.Add(matchingItem); - } - else //in another inventory, we need to move the item - { - if (!inputContainer.Inventory.CanBePut(matchingItem)) + var availablePrefabs = availableIngredients[requiredPrefab.Identifier]; + var availablePrefab = availablePrefabs.FirstOrDefault(potentialPrefab => { - var unneededItem = inputContainer.Inventory.AllItems.FirstOrDefault(it => !usedItems.Contains(it)); - unneededItem?.Drop(null, createNetworkEvent: !isClient); - } - inputContainer.Inventory.TryPutItem(matchingItem, user: null, createNetworkEvent: !isClient); - } - } - } - } + return !usedItems.Contains(potentialPrefab) && + potentialPrefab.ConditionPercentage >= requiredItem.MinCondition * 100.0f && + potentialPrefab.ConditionPercentage <= requiredItem.MaxCondition * 100.0f; + }); + if (availablePrefab == null) { continue; } - private bool IsItemValidIngredient(Item item, FabricationRecipe.RequiredItem requiredItem) - { - return - item != null && - requiredItem.ItemPrefabs.Contains(item.prefab) && - item.Condition / item.Prefab.Health >= requiredItem.MinCondition && - item.Condition / item.Prefab.Health <= requiredItem.MaxCondition; + availablePrefabs.Remove(availablePrefab); + + if (availablePrefab.ParentInventory == inputContainer.Inventory) + { + //already in input container, all good + usedItems.Add(availablePrefab); + } + else //in another inventory, we need to move the item + { + if (!inputContainer.Inventory.CanBePut(availablePrefab)) + { + var unneededItem = inputContainer.Inventory.AllItems.FirstOrDefault(it => !usedItems.Contains(it)); + unneededItem?.Drop(null, createNetworkEvent: !isClient); + } + inputContainer.Inventory.TryPutItem(availablePrefab, user: null, createNetworkEvent: !isClient); + } + } + } + }); } public override XElement Save(XElement parentElement) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/MiniMap.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/MiniMap.cs index 8ffa96100..39049b9bd 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/MiniMap.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/MiniMap.cs @@ -7,10 +7,15 @@ namespace Barotrauma.Items.Components { partial class MiniMap : Powered { - class HullData + internal class HullData { - public float? Oxygen; - public float? Water; + public float? HullOxygenAmount, + HullWaterAmount; + + public float? ReceivedOxygenAmount, + ReceivedWaterAmount; + + public readonly HashSet Cards = new HashSet(); public bool Distort; public float DistortionTimer; @@ -45,17 +50,38 @@ namespace Barotrauma.Items.Components set; } + [Editable, Serialize(true, true, description: "Enable hull status mode.")] + public bool EnableHullStatus + { + get; + set; + } + + [Editable, Serialize(true, true, description: "Enable electrical view mode.")] + public bool EnableElectricalView + { + get; + set; + } + + [Editable, Serialize(true, true, description: "Enable item finder mode.")] + public bool EnableItemFinder + { + get; + set; + } + public MiniMap(Item item, XElement element) : base(item, element) { IsActive = true; hullDatas = new Dictionary(); - InitProjSpecific(element); + InitProjSpecific(); } - partial void InitProjSpecific(XElement element); + partial void InitProjSpecific(); - public override void Update(float deltaTime, Camera cam) + public override void Update(float deltaTime, Camera cam) { //periodically reset all hull data //(so that outdated hull info won't be shown if detectors stop sending signals) @@ -65,13 +91,29 @@ namespace Barotrauma.Items.Components { if (!hullData.Distort) { - hullData.Oxygen = null; - hullData.Water = null; + hullData.ReceivedOxygenAmount = null; + hullData.ReceivedWaterAmount = null; } } resetDataTime = DateTime.Now + new TimeSpan(0, 0, 1); } +#if CLIENT + if (cardRefreshTimer > cardRefreshDelay) + { + if (item.Submarine is { } sub) + { + UpdateIDCards(sub); + } + + cardRefreshTimer = 0; + } + else + { + cardRefreshTimer += deltaTime; + } +#endif + currPowerConsumption = powerConsumption; currPowerConsumption *= MathHelper.Lerp(1.5f, 1.0f, item.Condition / item.MaxCondition); @@ -81,7 +123,7 @@ namespace Barotrauma.Items.Components ApplyStatusEffects(ActionType.OnActive, deltaTime, null); } } - + public override bool Pick(Character picker) { return picker != null; @@ -99,30 +141,49 @@ namespace Barotrauma.Items.Components hullDatas.Add(sourceHull, hullData); } - if (hullData.Distort) return; + if (hullData.Distort) { return; } switch (connection.Name) { case "water_data_in": //cheating a bit because water detectors don't actually send the water level + float waterAmount; if (source.GetComponent() == null) { - hullData.Water = Rand.Range(0.0f, 1.0f); + waterAmount = Rand.Range(0.0f, 1.0f); } else { - hullData.Water = Math.Min(sourceHull.WaterVolume / sourceHull.Volume, 1.0f); + waterAmount = Math.Min(sourceHull.WaterVolume / sourceHull.Volume, 1.0f); + } + hullData.ReceivedWaterAmount = waterAmount; + foreach (var linked in sourceHull.linkedTo) + { + if (!(linked is Hull linkedHull)) { continue; } + if (!hullDatas.TryGetValue(linkedHull, out HullData linkedHullData)) + { + linkedHullData = new HullData(); + hullDatas.Add(linkedHull, linkedHullData); + } + linkedHullData.ReceivedWaterAmount = waterAmount; } break; case "oxygen_data_in": - float oxy; - - if (!float.TryParse(signal.value, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out oxy)) + if (!float.TryParse(signal.value, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out float oxy)) { oxy = Rand.Range(0.0f, 100.0f); } - - hullData.Oxygen = oxy; + hullData.ReceivedOxygenAmount = oxy; + foreach (var linked in sourceHull.linkedTo) + { + if (!(linked is Hull linkedHull)) { continue; } + if (!hullDatas.TryGetValue(linkedHull, out HullData linkedHullData)) + { + linkedHullData = new HullData(); + hullDatas.Add(linkedHull, linkedHullData); + } + linkedHullData.ReceivedOxygenAmount = oxy; + } break; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/OxygenGenerator.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/OxygenGenerator.cs index 8e836bcf1..d76ec970c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/OxygenGenerator.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/OxygenGenerator.cs @@ -11,9 +11,12 @@ namespace Barotrauma.Items.Components private float generatedAmount; //key = vent, float = total volume of the hull the vent is in and the hulls connected to it - private Dictionary ventList; + private List<(Vent vent, float hullVolume)> ventList; private float totalHullVolume; + + private float ventUpdateTimer; + const float VentUpdateInterval = 5.0f; public float CurrFlow { @@ -31,6 +34,8 @@ namespace Barotrauma.Items.Components public OxygenGenerator(Item item, XElement element) : base(item, element) { + //randomize update timer so all oxygen generators don't update at the same time + ventUpdateTimer = Rand.Range(0.0f, VentUpdateInterval); IsActive = true; } @@ -64,7 +69,7 @@ namespace Barotrauma.Items.Components //20% condition = 4% CurrFlow *= conditionMult * conditionMult; - UpdateVents(CurrFlow); + UpdateVents(CurrFlow, deltaTime); } public override void UpdateBroken(float deltaTime, Camera cam) @@ -75,7 +80,9 @@ namespace Barotrauma.Items.Components private void GetVents() { - ventList = new Dictionary(); + totalHullVolume = 0.0f; + ventList ??= new List<(Vent vent, float hullVolume)>(); + ventList.Clear(); foreach (MapEntity entity in item.linkedTo) { if (!(entity is Item linkedItem)) { continue; } @@ -83,30 +90,40 @@ namespace Barotrauma.Items.Components Vent vent = linkedItem.GetComponent(); if (vent?.Item.CurrentHull == null) { continue; } - ventList.Add(vent, 0.0f); - foreach (Hull connectedHull in vent.Item.CurrentHull.GetConnectedHulls(includingThis: true, searchDepth: 10, ignoreClosedGaps: true)) - { + totalHullVolume += vent.Item.CurrentHull.Volume; + ventList.Add((vent, vent.Item.CurrentHull.Volume)); + } + + for (int i = 0; i < ventList.Count; i++) + { + Vent vent = ventList[i].vent; + foreach (Hull connectedHull in vent.Item.CurrentHull.GetConnectedHulls(includingThis: false, searchDepth: 3, ignoreClosedGaps: true)) + { + //another vent in the connected hull -> don't add it to this vent's total hull volume + if (ventList.Any(v => v.vent != vent && v.vent.Item.CurrentHull == connectedHull)) { continue; } totalHullVolume += connectedHull.Volume; - ventList[vent] += connectedHull.Volume; + ventList[i] = (ventList[i].vent, ventList[i].hullVolume + connectedHull.Volume); } } } - - private void UpdateVents(float deltaOxygen) + + private void UpdateVents(float deltaOxygen, float deltaTime) { - if (ventList == null) + if (ventList == null || ventUpdateTimer < 0.0f) { GetVents(); + ventUpdateTimer = VentUpdateInterval; } + ventUpdateTimer -= deltaTime; if (!ventList.Any() || totalHullVolume <= 0.0f) { return; } - foreach (KeyValuePair v in ventList) + foreach ((Vent vent, float hullVolume) in ventList) { - if (v.Key?.Item.CurrentHull == null) { continue; } + if (vent.Item.CurrentHull == null) { continue; } - v.Key.OxygenFlow = deltaOxygen * (v.Value / totalHullVolume); - v.Key.IsActive = true; + vent.OxygenFlow = deltaOxygen * (hullVolume / totalHullVolume); + vent.IsActive = true; } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Pump.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Pump.cs index 073ae51cf..351e57ef4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Pump.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Pump.cs @@ -70,6 +70,8 @@ namespace Barotrauma.Items.Components public bool HasPower => IsActive && Voltage >= MinVoltage; public bool IsAutoControlled => pumpSpeedLockTimer > 0.0f || isActiveLockTimer > 0.0f; + private const float TinkeringSpeedIncrease = 1.5f; + public Pump(Item item, XElement element) : base(item, element) { @@ -105,11 +107,19 @@ namespace Barotrauma.Items.Components float powerFactor = Math.Min(currPowerConsumption <= 0.0f || MinVoltage <= 0.0f ? 1.0f : Voltage, 1.0f); currFlow = flowPercentage / 100.0f * maxFlow * powerFactor; + + if (item.GetComponent() is Repairable repairable && repairable.IsTinkering) + { + currFlow *= 1f + repairable.TinkeringStrength * TinkeringSpeedIncrease; + } + //less effective when in a bad condition currFlow *= MathHelper.Lerp(0.5f, 1.0f, item.Condition / item.MaxCondition); item.CurrentHull.WaterVolume += currFlow; if (item.CurrentHull.WaterVolume > item.CurrentHull.Volume) { item.CurrentHull.Pressure += 0.5f; } + + Voltage -= deltaTime; } public void InfectBallast(string identifier, bool allowMultiplePerShip = false) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Reactor.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Reactor.cs index e8866afaf..8655b2618 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Reactor.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Reactor.cs @@ -367,23 +367,19 @@ namespace Barotrauma.Items.Components item.Condition -= fissionRate / 100.0f * fuelConsumptionRate * deltaTime; } } - - if (item.CurrentHull != null) - { - var aiTarget = item.CurrentHull.AiTarget; - if (aiTarget != null && MaxPowerOutput > 0) - { - float range = Math.Abs(currPowerConsumption) / MaxPowerOutput; - float noise = MathHelper.Lerp(aiTarget.MinSoundRange, aiTarget.MaxSoundRange, range); - aiTarget.SoundRange = Math.Max(aiTarget.SoundRange, noise); - } - } - if (item.AiTarget != null && MaxPowerOutput > 0) { var aiTarget = item.AiTarget; float range = Math.Abs(currPowerConsumption) / MaxPowerOutput; aiTarget.SoundRange = MathHelper.Lerp(aiTarget.MinSoundRange, aiTarget.MaxSoundRange, range); + if (item.CurrentHull != null) + { + var hullAITarget = item.CurrentHull.AiTarget; + if (hullAITarget != null) + { + hullAITarget.SoundRange = Math.Max(hullAITarget.SoundRange, aiTarget.SoundRange); + } + } } } @@ -498,15 +494,12 @@ namespace Barotrauma.Items.Components { float prevFireTimer = fireTimer; fireTimer += MathHelper.Lerp(deltaTime * 2.0f, deltaTime, item.Condition / item.MaxCondition); - - #if SERVER if (fireTimer > Math.Min(5.0f, FireDelay / 2) && blameOnBroken?.Character?.SelectedConstruction == item) { - GameMain.Server.KarmaManager.OnReactorOverHeating(blameOnBroken.Character, deltaTime); + GameMain.Server.KarmaManager.OnReactorOverHeating(item, blameOnBroken.Character, deltaTime); } #endif - if (fireTimer >= FireDelay && prevFireTimer < fireDelay) { new FireSource(item.WorldPosition); @@ -595,7 +588,7 @@ namespace Barotrauma.Items.Components GameServer.Log("Reactor meltdown!", ServerLog.MessageType.ItemInteraction); if (GameMain.Server != null) { - GameMain.Server.KarmaManager.OnReactorMeltdown(blameOnBroken?.Character); + GameMain.Server.KarmaManager.OnReactorMeltdown(item, blameOnBroken?.Character); } #endif } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Steering.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Steering.cs index 1d0ad14bb..44e6461b3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Steering.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Steering.cs @@ -1,4 +1,4 @@ -using Barotrauma.Networking; +using Barotrauma.Networking; using FarseerPhysics; using Microsoft.Xna.Framework; using System; @@ -364,6 +364,16 @@ namespace Barotrauma.Items.Components float velY = MathHelper.Lerp((neutralBallastLevel * 100 - 50) * 2, -100 * Math.Sign(targetVelocity.Y), Math.Abs(targetVelocity.Y) / 100.0f); item.SendSignal(new Signal(velY.ToString(CultureInfo.InvariantCulture), sender: user), "velocity_y_out"); + // converts the controlled sub's velocity to km/h and sends it. + if (controlledSub is { } sub) + { + item.SendSignal(new Signal((ConvertUnits.ToDisplayUnits(sub.Velocity.X * Physics.DisplayToRealWorldRatio) * 3.6f).ToString("0.0000", CultureInfo.InvariantCulture), sender: user), "current_velocity_x"); + item.SendSignal(new Signal((ConvertUnits.ToDisplayUnits(sub.Velocity.Y * Physics.DisplayToRealWorldRatio) * -3.6f).ToString("0.0000", CultureInfo.InvariantCulture), sender: user), "current_velocity_y"); + + item.SendSignal(new Signal((sub.WorldPosition.X * Physics.DisplayToRealWorldRatio).ToString("0.0000", CultureInfo.InvariantCulture), sender: user), "current_position_x"); + item.SendSignal(new Signal(sub.RealWorldDepth.ToString("0.0000", CultureInfo.InvariantCulture), sender: user), "current_position_y"); + } + // if our tactical AI pilot has left, revert back to maintaining position if (navigateTactically && (user == null || user.SelectedConstruction != item)) { @@ -382,8 +392,7 @@ namespace Barotrauma.Items.Components float userSkill = Math.Max(user.GetSkillLevel("helm"), 1.0f) / 100.0f; user.Info.IncreaseSkillLevel( "helm", - SkillSettings.Current.SkillIncreasePerSecondWhenSteering / userSkill * deltaTime, - user.Position + Vector2.UnitY * 150.0f); + SkillSettings.Current.SkillIncreasePerSecondWhenSteering / userSkill * deltaTime); } private void UpdateAutoPilot(float deltaTime) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Power/PowerTransfer.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Power/PowerTransfer.cs index 1828f7cee..52c065e75 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Power/PowerTransfer.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Power/PowerTransfer.cs @@ -15,6 +15,9 @@ namespace Barotrauma.Items.Components //a list of connections a given connection is connected to, either directly or via other power transfer components private readonly Dictionary> connectedRecipients = new Dictionary>(); + private float overloadCooldownTimer; + private const float OverloadCooldown = 5.0f; + protected float powerLoad; protected bool isBroken; @@ -173,12 +176,19 @@ namespace Barotrauma.Items.Components Overload = -currPowerConsumption > Math.Max(powerLoad, 200.0f) * maxOverVoltage; if (Overload && (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer)) { + if (overloadCooldownTimer > 0.0f) + { + overloadCooldownTimer -= deltaTime; + return; + } + //damage the item if voltage is too high (except if running as a client) float prevCondition = item.Condition; item.Condition -= deltaTime * 10.0f; if (item.Condition <= 0.0f && prevCondition > 0.0f) { + overloadCooldownTimer = OverloadCooldown; #if CLIENT SoundPlayer.PlaySound("zap", item.WorldPosition, hullGuess: item.CurrentHull); Vector2 baseVel = Rand.Vector(300.0f); @@ -370,5 +380,12 @@ namespace Barotrauma.Items.Components } } } + + protected override void RemoveComponentSpecific() + { + base.RemoveComponentSpecific(); + connectedRecipients?.Clear(); + connectionDirty?.Clear(); + } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs index 1134861ec..8cc3945de 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs @@ -137,6 +137,13 @@ namespace Barotrauma.Items.Components set; } + [Serialize(false, false, description: "Can the item stick even to deflective targets.")] + public bool StickToDeflective + { + get; + set; + } + [Serialize(false, false, description: "Hitscan projectiles cast a ray forwards and immediately hit whatever the ray hits. "+ "It is recommended to use hitscans for very fast-moving projectiles such as bullets, because using extremely fast launch velocities may cause physics glitches.")] public bool Hitscan @@ -201,7 +208,7 @@ namespace Barotrauma.Items.Components foreach (XElement subElement in element.Elements()) { if (!subElement.Name.ToString().Equals("attack", StringComparison.OrdinalIgnoreCase)) { continue; } - Attack = new Attack(subElement, item.Name + ", Projectile"); + Attack = new Attack(subElement, item.Name + ", Projectile", item); } InitProjSpecific(element); } @@ -227,10 +234,14 @@ namespace Barotrauma.Items.Components } } - private void Launch(Character user, Vector2 simPosition, float rotation) + private void Launch(Character user, Vector2 simPosition, float rotation, float damageMultiplier = 1f) { Item.body.ResetDynamics(); Item.SetTransform(simPosition, rotation); + if (Attack != null) + { + Attack.DamageMultiplier = damageMultiplier; + } // Set user for hitscan projectiles to work properly. User = user; // Need to set null for non-characterusable items. @@ -243,7 +254,7 @@ namespace Barotrauma.Items.Components Item.SetTransform(simPosition, rotation + (Item.body.Dir * LaunchRotationRadians)); } - public void Shoot(Character user, Vector2 weaponPos, Vector2 spawnPos, float rotation, List ignoredBodies, bool createNetworkEvent) + public void Shoot(Character user, Vector2 weaponPos, Vector2 spawnPos, float rotation, List ignoredBodies, bool createNetworkEvent, float damageMultiplier = 1f) { //add the limbs of the shooter to the list of bodies to be ignored //so that the player can't shoot himself @@ -264,7 +275,7 @@ namespace Barotrauma.Items.Components projectilePos = newPos; } } - Launch(user, projectilePos, rotation); + Launch(user, projectilePos, rotation, damageMultiplier); if (createNetworkEvent && !Item.Removed && GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer) { #if SERVER @@ -329,7 +340,7 @@ namespace Barotrauma.Items.Components item.AiTarget.SoundRange = item.AiTarget.MaxSoundRange; } - item.Drop(null); + item.Drop(null, createNetworkEvent: false); launchPos = item.SimPosition; @@ -355,6 +366,7 @@ namespace Barotrauma.Items.Components { float rotation = item.body.Rotation; Vector2 simPositon = item.SimPosition; + Vector2 rayStartWorld = item.WorldPosition; item.Drop(null); item.body.Enabled = true; @@ -366,7 +378,6 @@ namespace Barotrauma.Items.Components Vector2 rayStart = simPositon; Vector2 rayEnd = rayStart + dir * 500.0f; - Vector2 rayStartWorld = item.WorldPosition; float worldDist = 1000.0f; #if CLIENT worldDist = Screen.Selected?.Cam?.WorldView.Width ?? GameMain.GraphicsWidth; @@ -578,7 +589,8 @@ namespace Barotrauma.Items.Components if (!removePending) { - ApplyStatusEffects(ActionType.OnActive, deltaTime, null); + Entity useTarget = lastTarget?.Body.UserData is Limb limb ? limb.character : lastTarget?.Body.UserData as Entity; + ApplyStatusEffects(ActionType.OnActive, deltaTime, useTarget: useTarget, user: _user); } if (item.body != null && item.body.FarseerBody.IsBullet) @@ -602,10 +614,17 @@ namespace Barotrauma.Items.Components return; } + //target very far from the item -> update the item's transform to make sure it's inside the same sub as the target (or outside) + if (Math.Abs(stickJoint.JointTranslation) > 100.0f) + { + item.UpdateTransform(); + } + if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer) { if (StickTargetRemoved() || - (!StickPermanently && (stickJoint.JointTranslation < stickJoint.LowerLimit * 0.9f || stickJoint.JointTranslation > stickJoint.UpperLimit * 0.9f))) + (!StickPermanently && (stickJoint.JointTranslation < stickJoint.LowerLimit * 0.9f || stickJoint.JointTranslation > stickJoint.UpperLimit * 0.9f)) || + Math.Abs(stickJoint.JointTranslation) > 100.0f) //failsafe unstick if the target is still extremely far { Unstick(); #if SERVER @@ -623,7 +642,6 @@ namespace Barotrauma.Items.Components return false; } - private bool OnProjectileCollision(Fixture f1, Fixture target, Contact contact) { if (User != null && User.Removed) { User = null; return false; } @@ -694,7 +712,8 @@ namespace Barotrauma.Items.Components } } - readonly List targets = new List(); + private readonly List targets = new List(); + private Fixture lastTarget; private bool HandleProjectileCollision(Fixture target, Vector2 collisionNormal, Vector2 velocity) { @@ -705,6 +724,7 @@ namespace Barotrauma.Items.Components { return false; } + lastTarget = target; float projectileNewSpeed = 0.5f; float projectileDeflectedNewSpeed = 0.1f; @@ -835,14 +855,16 @@ namespace Barotrauma.Items.Components } if (attackResult.AppliedDamageModifiers != null && - attackResult.AppliedDamageModifiers.Any(dm => dm.DeflectProjectiles)) + (attackResult.AppliedDamageModifiers.Any(dm => dm.DeflectProjectiles) && !StickToDeflective)) { item.body.LinearVelocity *= projectileDeflectedNewSpeed; } - else if (Vector2.Dot(velocity, collisionNormal) < 0.0f && hits.Count() >= MaxTargetsToHit && + else if ( // When hitting characters the collision normal seems to sometimes point into wrong direction, resulting in a failed attempt to stick + //Vector2.Dot(Vector2.Normalize(velocity), collisionNormal) < 0.0f && + hits.Count() >= MaxTargetsToHit && target.Body.Mass > item.body.Mass * 0.5f && (DoesStick || - (StickToCharacters && target.Body.UserData is Limb) || + (StickToCharacters && (target.Body.UserData is Limb || target.Body.UserData is Character)) || (StickToStructures && target.Body.UserData is Structure) || (StickToItems && target.Body.UserData is Item))) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Quality.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Quality.cs new file mode 100644 index 000000000..5ffe84c32 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Quality.cs @@ -0,0 +1,91 @@ +using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; +using System.Xml.Linq; + +namespace Barotrauma.Items.Components +{ + partial class Quality : ItemComponent + { + public const int MaxQuality = 3; + + public static readonly float[] QualityCommonnesses = new float[] + { + 0.8f, + 0.15f, + 0.045f, + 0.005f, + }; + + public enum StatType + { + Condition, + ExplosionRadius, + ExplosionDamage, + RepairSpeed, + RepairToolStructureRepairMultiplier, + RepairToolStructureDamageMultiplier, + RepairToolDeattachTimeMultiplier, + // unused as of now + AttackMultiplier, + AttackSpeedMultiplier, + ForceDoorsOpenSpeedMultiplier, + RangedSpreadReduction, + ChargeSpeedMultiplier, + MovementSpeedMultiplier, + // generic stats to be used for various needs, declared just in case (localization) + EffectivenessMultiplier, + PowerOutputMultiplier, + ConsumptionReductionMultiplier, + } + + private readonly Dictionary statValues = new Dictionary(); + + private int qualityLevel; + + [Serialize(0, true)] + public int QualityLevel + { + get { return qualityLevel; } + set + { + if (value == qualityLevel) { return; } + + bool wasInFullCondition = item.IsFullCondition; + qualityLevel = MathHelper.Clamp(value, 0, MaxQuality); + //set the condition to the new max condition + if (wasInFullCondition && statValues.ContainsKey(StatType.Condition)) + { + item.Condition = item.MaxCondition; + } + } + } + + public Quality(Item item, XElement element) : base(item, element) + { + foreach (XElement subElement in element.Elements()) + { + switch (subElement.Name.ToString().ToLower()) + { + case "stattype": + case "statvalue": + case "qualitystat": + string statTypeString = subElement.GetAttributeString("stattype", ""); + if (!Enum.TryParse(statTypeString, true, out StatType statType)) + { + DebugConsole.ThrowError("Invalid stat type type \"" + statTypeString + "\" in item (" + item.prefab.Identifier + ")"); + } + float statValue = subElement.GetAttributeFloat("value", 0f); + statValues.TryAdd(statType, statValue); + break; + } + } + } + + public float GetValue(StatType statType) + { + if (!statValues.ContainsKey(statType)) { return 0.0f; } + return statValues[statType] * qualityLevel; + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/RemoteController.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/RemoteController.cs new file mode 100644 index 000000000..783253c89 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/RemoteController.cs @@ -0,0 +1,96 @@ +using Microsoft.Xna.Framework; +using System.Xml.Linq; + +namespace Barotrauma.Items.Components +{ + partial class RemoteController : ItemComponent + { + [Serialize("", false, description: "Tag or identifier of the item that should be controlled.")] + public string Target + { + get; + private set; + } + + [Serialize(false, false)] + public bool OnlyInOwnSub + { + get; + private set; + } + + [Serialize(10000.0f, false)] + public float Range + { + get; + private set; + } + + public Item TargetItem { get => currentTarget; } + + private Item currentTarget; + private Character currentUser; + private Submarine currentSub; + + public RemoteController(Item item, XElement element) + : base(item, element) + { + } + + public override bool Select(Character character) + { + if (base.Select(character)) + { + FindTarget(character); + return true; + } + return false; + } + + public override void Equip(Character character) + { + FindTarget(character); + } + + public override void Update(float deltaTime, Camera cam) + { + base.Update(deltaTime, cam); + if (currentTarget.Removed || + item.Submarine != currentSub || + Vector2.DistanceSquared(currentTarget.WorldPosition, item.WorldPosition) > Range * Range) + { + FindTarget(currentUser); + } + } + + private void FindTarget(Character user) + { + currentTarget = null; + if (user == null || (item.Submarine == null && OnlyInOwnSub)) + { + IsActive = false; + return; + } + + float closestDist = float.PositiveInfinity; + foreach (Item targetItem in Item.ItemList) + { + if (OnlyInOwnSub) + { + if (targetItem.Submarine != item.Submarine) { continue; } + if (targetItem.Submarine.TeamID != user.TeamID) { continue; } + } + if (!targetItem.HasTag(Target) && targetItem.prefab.Identifier != Target) { continue; } + + float distSqr = Vector2.DistanceSquared(item.WorldPosition, targetItem.WorldPosition); + if (distSqr > Range * Range || distSqr > closestDist) { continue; } + + currentTarget = targetItem; + currentSub = item.Submarine; + closestDist = distSqr; + currentUser = user; + } + IsActive = currentTarget != null; + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs index 26a50ebf7..3ff601550 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs @@ -11,7 +11,7 @@ namespace Barotrauma.Items.Components { partial class Repairable : ItemComponent, IServerSerializable, IClientSerializable { - private string header; + private readonly string header; private float deteriorationTimer; private float deteriorateAlwaysResetTimer; @@ -85,10 +85,10 @@ namespace Barotrauma.Items.Components } private float skillRequirementMultiplier; - + [Serialize(1.0f, true)] - public float SkillRequirementMultiplier - { + public float SkillRequirementMultiplier + { get { return skillRequirementMultiplier; } set { @@ -100,21 +100,30 @@ namespace Barotrauma.Items.Components RecreateGUI(); } #endif - } + } } + public bool IsTinkering { get; private set; } = false; + public float RepairIconThreshold { get { return RepairThreshold / 2; } } public Character CurrentFixer { get; private set; } + private Item currentRepairItem; + + private float tinkeringDuration; + private float tinkeringStrength; + + public float TinkeringStrength => tinkeringStrength; public enum FixActions : int { None = 0, Repair = 1, - Sabotage = 2 + Sabotage = 2, + Tinker = 3, } private FixActions currentFixerAction = FixActions.None; @@ -131,13 +140,13 @@ namespace Barotrauma.Items.Components canBeSelected = true; this.item = item; - header = + header = TextManager.Get(element.GetAttributeString("header", ""), returnNull: true) ?? TextManager.Get(item.Prefab.ConfigElement.GetAttributeString("header", ""), returnNull: true) ?? element.GetAttributeString("name", ""); //backwards compatibility - var repairThresholdAttribute = + var repairThresholdAttribute = element.Attributes().FirstOrDefault(a => a.Name.ToString().Equals("showrepairuithreshold", StringComparison.OrdinalIgnoreCase)) ?? element.Attributes().FirstOrDefault(a => a.Name.ToString().Equals("airepairthreshold", StringComparison.OrdinalIgnoreCase)); if (repairThresholdAttribute != null) @@ -161,12 +170,14 @@ namespace Barotrauma.Items.Components /// /// Check if the character manages to succesfully repair the item /// - public bool CheckCharacterSuccess(Character character) + public bool CheckCharacterSuccess(Character character, Item bestRepairItem) { if (character == null) { return false; } if (statusEffectLists == null || statusEffectLists.None(s => s.Key == ActionType.OnFailure)) { return true; } + if (bestRepairItem != null && bestRepairItem.Prefab.CannotRepairFail) { return true; } + // unpowered (electrical) items can be repaired without a risk of electrical shock if (requiredSkills.Any(s => s != null && s.Identifier.Equals("electrical", StringComparison.OrdinalIgnoreCase)) && item.GetComponent() is Powered powered && powered.Voltage < 0.1f) { return true; } @@ -174,6 +185,10 @@ namespace Barotrauma.Items.Components if (Rand.Range(0.0f, 0.5f) < RepairDegreeOfSuccess(character, requiredSkills)) { return true; } ApplyStatusEffects(ActionType.OnFailure, 1.0f, character); + if (bestRepairItem != null && bestRepairItem.GetComponent() is Holdable h) + { + h.ApplyStatusEffects(ActionType.OnFailure, 1.0f, character); + } return false; } @@ -191,7 +206,7 @@ namespace Barotrauma.Items.Components return ((average + 100.0f) / 2.0f) / 100.0f; } - + public bool StartRepairing(Character character, FixActions action) { if (character == null || character.IsDead || action == FixActions.None) @@ -201,13 +216,19 @@ namespace Barotrauma.Items.Components } else { + Item bestRepairItem = GetBestRepairItem(character); #if SERVER if (CurrentFixer != character || currentFixerAction != action) { - if (!CheckCharacterSuccess(character)) + if (!CheckCharacterSuccess(character, bestRepairItem)) { GameServer.Log($"{GameServer.CharacterLogName(character)} failed to {(action == FixActions.Sabotage ? "sabotage" : "repair")} {item.Name}", ServerLog.MessageType.ItemInteraction); GameMain.Server?.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnFailure, this, character.ID }); + if (bestRepairItem != null && bestRepairItem.GetComponent() is Holdable h) + { + GameMain.Server?.CreateEntityEvent(bestRepairItem, new object[] { NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnFailure, h, character.ID }); + } + return false; } @@ -215,11 +236,32 @@ namespace Barotrauma.Items.Components item.CreateServerEvent(this); } #else - if (GameMain.Client == null && (CurrentFixer != character || currentFixerAction != action) && !CheckCharacterSuccess(character)) { return false; } + if (GameMain.Client == null && (CurrentFixer != character || currentFixerAction != action) && !CheckCharacterSuccess(character, bestRepairItem)) { return false; } #endif CurrentFixer = character; + currentRepairItem = bestRepairItem; CurrentFixerAction = action; + if (action == FixActions.Tinker) + { + tinkeringStrength = 1f + CurrentFixer.GetStatValue(StatTypes.TinkeringStrength); + + if (character.HasAbilityFlag(AbilityFlags.CanTinkerFabricatorsAndDeconstructors) && item.GetComponent() != null || item.GetComponent() != null) + { + // fabricators and deconstructors can be tinkered indefinitely (more or less) + tinkeringDuration = float.MaxValue; + } + else + { + tinkeringDuration = CurrentFixer.GetStatValue(StatTypes.TinkeringDuration); + } + } return true; + + static Item GetBestRepairItem(Character character) + { + return character.HeldItems.OrderByDescending(i => i.Prefab.AddedRepairSpeedMultiplier).FirstOrDefault(); + } + } } @@ -233,12 +275,24 @@ namespace Barotrauma.Items.Components item.CreateServerEvent(this); } #endif + if (currentRepairItem != null) + { + foreach (var ic in currentRepairItem.GetComponents()) + { + ic.ApplyStatusEffects(ActionType.OnSuccess, 1.0f, character); + } + } + if (CurrentFixerAction == FixActions.Tinker) + { + CurrentFixer.CheckTalents(AbilityEffectType.OnStopTinkering); + } CurrentFixer.AnimController.Anim = AnimController.Animation.None; CurrentFixer = null; + currentRepairItem = null; currentFixerAction = FixActions.None; #if CLIENT repairSoundChannel?.FadeOutAndDispose(); - repairSoundChannel = null; + repairSoundChannel = null; #endif return true; } @@ -266,7 +320,10 @@ namespace Barotrauma.Items.Components public override void Update(float deltaTime, Camera cam) { UpdateProjSpecific(deltaTime); - + IsTinkering = false; + + item.SendSignal($"{(int) item.ConditionPercentage}", "condition_out"); + if (CurrentFixer == null) { if (deteriorateAlwaysResetTimer > 0.0f) @@ -314,6 +371,25 @@ namespace Barotrauma.Items.Components return; } + if (currentFixerAction == FixActions.Tinker) + { + tinkeringDuration -= deltaTime; + // not great to interject it here, should be less reliant on returning + + float conditionDecrease = deltaTime * (CurrentFixer.GetStatValue(StatTypes.TinkeringDamage) / item.Prefab.Health) * 100f; + item.Condition -= conditionDecrease; + + if (!CanTinker(CurrentFixer) || tinkeringDuration <= 0f) + { + StopRepairing(CurrentFixer); + } + else + { + IsTinkering = true; + } + return; + } + float successFactor = requiredSkills.Count == 0 ? 1.0f : RepairDegreeOfSuccess(CurrentFixer, requiredSkills); //item must have been below the repair threshold for the player to get an achievement or XP for repairing it @@ -327,6 +403,11 @@ namespace Barotrauma.Items.Components } float fixDuration = MathHelper.Lerp(FixDurationLowSkill, FixDurationHighSkill, successFactor); + fixDuration /= 1 + CurrentFixer.GetStatValue(StatTypes.RepairSpeed) + currentRepairItem?.Prefab.AddedRepairSpeedMultiplier ?? 0f; + fixDuration /= 1 + item.GetQualityModifier(Quality.StatType.RepairSpeed); + + item.MaxRepairConditionMultiplier = GetMaxRepairConditionMultiplier(CurrentFixer); + if (currentFixerAction == FixActions.Repair) { if (fixDuration <= 0.0f) @@ -335,7 +416,8 @@ namespace Barotrauma.Items.Components } else { - float conditionIncrease = deltaTime / (fixDuration / item.MaxCondition); + // scale with prefab's health instead of real health to ensure repair speed remains static with upgrades + float conditionIncrease = deltaTime / (fixDuration / item.Prefab.Health); item.Condition += conditionIncrease; #if SERVER GameMain.Server.KarmaManager.OnItemRepaired(CurrentFixer, this, conditionIncrease); @@ -350,13 +432,13 @@ namespace Barotrauma.Items.Components { float characterSkillLevel = CurrentFixer.GetSkillLevel(skill.Identifier); CurrentFixer.Info?.IncreaseSkillLevel(skill.Identifier, - SkillSettings.Current.SkillIncreasePerRepair / Math.Max(characterSkillLevel, 1.0f), - CurrentFixer.Position + Vector2.UnitY * 100.0f); + SkillSettings.Current.SkillIncreasePerRepair / Math.Max(characterSkillLevel, 1.0f)); } SteamAchievementManager.OnItemRepaired(item, CurrentFixer); + CurrentFixer.CheckTalents(AbilityEffectType.OnRepairComplete); } deteriorationTimer = Rand.Range(MinDeteriorationDelay, MaxDeteriorationDelay); - wasBroken = false; + wasBroken = false; StopRepairing(CurrentFixer); } } @@ -368,7 +450,8 @@ namespace Barotrauma.Items.Components } else { - float conditionDecrease = deltaTime / (fixDuration / item.MaxCondition); + // scale with prefab's health instead of real health to ensure sabotage speed remains static with (any) upgrades + float conditionDecrease = deltaTime / (fixDuration / item.Prefab.Health); item.Condition -= conditionDecrease; } @@ -380,8 +463,7 @@ namespace Barotrauma.Items.Components { float characterSkillLevel = CurrentFixer.GetSkillLevel(skill.Identifier); CurrentFixer.Info?.IncreaseSkillLevel(skill.Identifier, - SkillSettings.Current.SkillIncreasePerSabotage / Math.Max(characterSkillLevel, 1.0f), - CurrentFixer.Position + Vector2.UnitY * 100.0f); + SkillSettings.Current.SkillIncreasePerSabotage / Math.Max(characterSkillLevel, 1.0f)); } deteriorationTimer = 0.0f; @@ -399,6 +481,45 @@ namespace Barotrauma.Items.Components } } + private float GetMaxRepairConditionMultiplier(Character character) + { + if (character == null) { return 1.0f; } + // kind of rough to keep this in update, but seems most robust + if (requiredSkills.Any(s => s != null && s.Identifier.Equals("mechanical", StringComparison.OrdinalIgnoreCase))) + { + return 1 + character.GetStatValue(StatTypes.MaxRepairConditionMultiplierMechanical); + } + if (requiredSkills.Any(s => s != null && s.Identifier.Equals("electrical", StringComparison.OrdinalIgnoreCase))) + { + return 1 + character.GetStatValue(StatTypes.MaxRepairConditionMultiplierElectrical); + } + return 1.0f; + } + + private bool IsTinkerable(Character character) + { + if (!character.HasAbilityFlag(AbilityFlags.CanTinker)) { return false; } + if (item.GetComponent() != null) { return true; } + if (item.GetComponent() != null) { return true; } + if (item.HasTag("turretammosource")) { return true; } + if (!character.HasAbilityFlag(AbilityFlags.CanTinkerFabricatorsAndDeconstructors)) { return false; } + if (item.GetComponent() != null) { return true; } + if (item.GetComponent() != null) { return true; } + return false; + } + + private Affliction GetTinkerExhaustion(Character character) + { + return character.CharacterHealth.GetAffliction("tinkerexhaustion"); + } + + private bool CanTinker(Character character) + { + if (!IsTinkerable(character)) { return false; } + if (GetTinkerExhaustion(character) is Affliction tinkerExhaustion && tinkerExhaustion.Strength <= tinkerExhaustion.Prefab.MaxStrength) { return false; } + return true; + } + partial void UpdateProjSpecific(float deltaTime); public void AdjustPowerConsumption(ref float powerConsumption) @@ -423,7 +544,7 @@ namespace Barotrauma.Items.Components } else if (ic is PowerTransfer pt) { - //power transfer items (junction boxes, relays) don't deteriorate if they're no carrying any power + //power transfer items (junction boxes, relays) don't deteriorate if they're no carrying any power if (Math.Abs(pt.CurrPowerConsumption) > 0.1f) { return true; } } else if (ic is Engine engine) @@ -484,7 +605,7 @@ namespace Barotrauma.Items.Components public override void ReceiveSignal(Signal signal, Connection connection) { //do nothing - //Repairables should always stay active, so we don't want to use the default behavior + //Repairables should always stay active, so we don't want to use the default behavior //where set_active/set_state signals can disable the component } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Rope.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Rope.cs index 1f923cc23..6e9836551 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Rope.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Rope.cs @@ -3,7 +3,6 @@ using FarseerPhysics; using FarseerPhysics.Dynamics; using Microsoft.Xna.Framework; using System; -using System.Linq; using System.Xml.Linq; namespace Barotrauma.Items.Components @@ -54,6 +53,27 @@ namespace Barotrauma.Items.Components set; } + [Serialize(true, false, description: "Should the rope snap when the character drops the aim?")] + public bool SnapWhenNotAimed + { + get; + set; + } + + [Serialize(30.0f, false, description: "How much mass is required for the target to pull the source towards it. Static and kinematic targets are always treated heavy enough.")] + public float TargetMinMass + { + get; + set; + } + + [Serialize(false, false)] + public bool LerpForces + { + get; + set; + } + private bool snapped; public bool Snapped { @@ -75,6 +95,10 @@ namespace Barotrauma.Items.Components } } snapped = value; + if (!snapped) + { + snapTimer = 0; + } } } @@ -85,6 +109,7 @@ namespace Barotrauma.Items.Components partial void InitProjSpecific(XElement element); + public void Snap() => Snapped = true; public void Attach(ISpatialEntity source, Item target) { @@ -92,6 +117,7 @@ namespace Barotrauma.Items.Components System.Diagnostics.Debug.Assert(target != null); this.source = source; this.target = target; + Snapped = false; ApplyStatusEffects(ActionType.OnUse, 1.0f, worldPosition: item.WorldPosition); IsActive = true; } @@ -118,13 +144,15 @@ namespace Barotrauma.Items.Components Vector2 diff = target.WorldPosition - source.WorldPosition; if (diff.LengthSquared() > MaxLength * MaxLength) { - Snapped = true; + Snap(); return; } #if CLIENT item.ResetCachedVisibleSize(); #endif + var projectile = target.GetComponent(); + if (projectile == null) { return; } if (SnapOnCollision) { @@ -135,28 +163,24 @@ namespace Barotrauma.Items.Components collisionCategory: Physics.CollisionLevel | Physics.CollisionWall, customPredicate: (Fixture f) => { - var projectile = target?.GetComponent(); - if (projectile != null) + foreach (Body body in projectile.Hits) { - foreach (Body body in projectile.Hits) + Submarine alreadyHitSub = null; + if (body.UserData is Structure hitStructure) { - Submarine alreadyHitSub = null; - if (body.UserData is Structure hitStructure) - { - alreadyHitSub = hitStructure.Submarine; - } - else if (body.UserData is Submarine hitSub) - { - alreadyHitSub = hitSub; - } - if (alreadyHitSub != null) - { - if (f.Body?.UserData is MapEntity me && me.Submarine == alreadyHitSub) { return false; } - if (f.Body?.UserData as Submarine == alreadyHitSub) { return false; } - } + alreadyHitSub = hitStructure.Submarine; + } + else if (body.UserData is Submarine hitSub) + { + alreadyHitSub = hitSub; + } + if (alreadyHitSub != null) + { + if (f.Body?.UserData is MapEntity me && me.Submarine == alreadyHitSub) { return false; } + if (f.Body?.UserData as Submarine == alreadyHitSub) { return false; } } } - Submarine targetSub = target?.GetComponent()?.StickTarget?.UserData as Submarine ?? target.Submarine; + Submarine targetSub = projectile.StickTarget?.UserData as Submarine ?? target.Submarine; if (f.Body?.UserData is MapEntity mapEntity && mapEntity.Submarine != null) { @@ -175,7 +199,7 @@ namespace Barotrauma.Items.Components return true; }) != null) { - Snapped = true; + Snap(); return; } raycastTimer = 0.0f; @@ -183,27 +207,107 @@ namespace Barotrauma.Items.Components } Vector2 forceDir = diff; - if (forceDir.LengthSquared() > 0.01f) + float distance = diff.Length(); + if (distance > 0.001f) { forceDir = Vector2.Normalize(forceDir); } if (Math.Abs(ProjectilePullForce) > 0.001f) { - var projectile = target.GetComponent(); - projectile?.Item?.body?.ApplyForce(-forceDir * ProjectilePullForce); + projectile.Item?.body?.ApplyForce(-forceDir * ProjectilePullForce); } - if (Math.Abs(SourcePullForce) > 0.001f) + if (projectile.StickTarget != null) { - var sourceBody = GetBodyToPull(source); - sourceBody?.ApplyForce(forceDir * SourcePullForce); - } - - if (Math.Abs(TargetPullForce) > 0.001f) - { - var targetBody = GetBodyToPull(target); - targetBody?.ApplyForce(-forceDir * TargetPullForce); + float targetMass = float.MaxValue; + Character targetCharacter = null; + if (projectile.StickTarget.UserData is Limb targetLimb) + { + targetCharacter = targetLimb.character; + targetMass = targetLimb.ragdoll.Mass; + } + else if (projectile.StickTarget.UserData is Character character) + { + targetCharacter = character; + targetMass = character.Mass; + } + else if (projectile.StickTarget.UserData is Item item) + { + targetMass = projectile.StickTarget.Mass; + } + if (projectile.StickTarget.BodyType != BodyType.Dynamic) + { + targetMass = float.MaxValue; + } + var user = item.GetComponent()?.User; + if (targetMass > TargetMinMass) + { + if (Math.Abs(SourcePullForce) > 0.001f) + { + var sourceBody = GetBodyToPull(source); + if (sourceBody != null) + { + var targetBody = GetBodyToPull(target); + if (targetBody != null && !(targetBody.UserData is Character)) + { + sourceBody.ApplyForce(targetBody.LinearVelocity * sourceBody.Mass); + } + float forceMultiplier = 1; + if (user != null) + { + user.AnimController.Hang(); + if (user.InWater) + { + if (user.IsRagdolled) + { + forceMultiplier = 0; + } + } + else + { + forceMultiplier = user.IsRagdolled ? 0.1f : 0.4f; + // Prevents too easy smashing to the walls + forceDir.X /= 4; + // Prevents rubberbanding up and down + if (forceDir.Y < 0) + { + forceDir.Y = 0; + } + } + if (targetCharacter != null) + { + var myCollider = user.AnimController.Collider; + var targetCollider = targetCharacter.AnimController.Collider; + if (myCollider.LinearVelocity != Vector2.Zero && targetCollider.LinearVelocity != Vector2.Zero) + { + if (Vector2.Dot(Vector2.Normalize(myCollider.LinearVelocity), Vector2.Normalize(targetCollider.LinearVelocity)) < 0) + { + myCollider.ApplyForce(targetCollider.LinearVelocity * targetCollider.Mass); + } + } + } + } + float force = LerpForces ? MathHelper.Lerp(0, SourcePullForce, MathUtils.InverseLerp(0, MaxLength / 2, distance)) * forceMultiplier : SourcePullForce * forceMultiplier; + sourceBody.ApplyForce(forceDir * force); + } + } + } + if (Math.Abs(TargetPullForce) > 0.001f) + { + var targetBody = GetBodyToPull(target); + if (user != null && targetCharacter != null && !user.AnimController.InWater) + { + // Prevents rubberbanding horizontally when dragging a corpse. + if ((forceDir.X < 0) != (user.AnimController.Dir < 0)) + { + forceDir.X = Math.Clamp(forceDir.X, -0.1f, 0.1f); + } + } + float force = LerpForces ? MathHelper.Lerp(0, TargetPullForce, MathUtils.InverseLerp(0, MaxLength / 3, distance)) : TargetPullForce; + targetBody?.ApplyForce(-forceDir * force); + targetCharacter?.AnimController.Collider.ApplyForce(-forceDir * force * 3); + } } } @@ -231,19 +335,23 @@ namespace Barotrauma.Items.Components return ownerCharacter.AnimController.Collider; } var projectile = targetItem.GetComponent(); - if (projectile != null) + if (projectile != null && projectile.StickTarget != null) { - if (projectile.StickTarget?.UserData is Structure structure) + if (projectile.StickTarget.UserData is Structure structure) { return structure.Submarine?.PhysicsBody; } - else if (projectile.StickTarget?.UserData is Submarine sub) + else if (projectile.StickTarget.UserData is Submarine sub) { - return sub?.PhysicsBody; + return sub.PhysicsBody; } - else if (projectile.StickTarget?.UserData is Character character) + else if (projectile.StickTarget.UserData is Item item) { - return character.AnimController.Collider; + return item.body; + } + else if (projectile.StickTarget.UserData is Limb limb) + { + return limb.body; } return null; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Scanner.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Scanner.cs new file mode 100644 index 000000000..9c4998801 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Scanner.cs @@ -0,0 +1,90 @@ +using System; +using System.Xml.Linq; + +namespace Barotrauma.Items.Components +{ + partial class Scanner : ItemComponent + { + [Serialize(1.0f, false, description: "How long it takes for the scan to be completed.")] + public float ScanDuration { get; set; } + [Serialize(0.0f, false, description: "How far along the scan is. When the timer goes above ScanDuration, the scan is completed.")] + public float ScanTimer + { + get + { + return scanTimer; + } + set + { + if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; } + if (Holdable == null) { return; } + bool wasScanCompletedPreviously = IsScanCompleted; + scanTimer = Math.Max(0.0f, value); + if (!wasScanCompletedPreviously && IsScanCompleted) + { + OnScanCompleted?.Invoke(this); + } +#if SERVER + if (wasScanCompletedPreviously != IsScanCompleted || Math.Abs(LastSentScanTimer - scanTimer) > 0.1f) + { + item.CreateServerEvent(this); + LastSentScanTimer = scanTimer; + } +#endif + } + } + [Serialize(1.0f, false, description: "How far the scanner can be from the target for the scan to be successful.")] + public float ScanRadius { get; set; } + [Serialize(true, false, description: "Should the progress bar always be displayed when the item has been attached.")] + public bool AlwaysDisplayProgressBar { get; set; } + + private Holdable Holdable { get; set; } + /// + /// Should the progress bar be displayed. Use when AlwaysDisplayProgressBar is set to false. + /// + public bool DisplayProgressBar { get; set; } = false; + private bool IsScanCompleted => scanTimer >= ScanDuration; + + private float scanTimer; + + public Action OnScanStarted, OnScanCompleted; + + public Scanner(Item item, XElement element) : base(item, element) + { + IsActive = true; + } + + public override void Update(float deltaTime, Camera cam) + { + if (Holdable != null && Holdable.Attachable && Holdable.Attached) + { + if (ScanTimer <= 0.0f) + { + OnScanStarted?.Invoke(this); + } + ScanTimer += deltaTime; + item.AiTarget?.IncreaseSoundRange(deltaTime, speed: 2.0f); + ApplyStatusEffects(ActionType.OnActive, deltaTime); + } + else + { + ScanTimer = 0.0f; + DisplayProgressBar = false; + } + UpdateProjSpecific(); + } + + partial void UpdateProjSpecific(); + + public override void OnItemLoaded() + { + base.OnItemLoaded(); + Holdable = item.GetComponent(); + if (Holdable == null || !Holdable.Attachable) + { + DebugConsole.ThrowError("Error in initializing a Scanner component: an attachable Holdable component is required on the same item and none was found"); + IsActive = false; + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/ButtonTerminal.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/ButtonTerminal.cs new file mode 100644 index 000000000..88883ccca --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/ButtonTerminal.cs @@ -0,0 +1,119 @@ +using Barotrauma.Extensions; +using Barotrauma.Networking; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma.Items.Components +{ + partial class ButtonTerminal : ItemComponent + { + [Editable, Serialize(new string[0], true, description: "Signals sent when the corresponding buttons are pressed.", alwaysUseInstanceValues: true)] + public string[] Signals { get; set; } + [Editable, Serialize("", true, description: "Identifiers or tags of items that, when contained, allow the terminal buttons to be used. Multiple ones should be separated by commas.", alwaysUseInstanceValues: true)] + public string ActivatingItems { get; set; } + + private int RequiredSignalCount { get; set; } + private ItemContainer Container { get; set; } + private HashSet ActivatingItemPrefabs { get; set; } = new HashSet(); + + + private bool AllowUsingButtons => ActivatingItemPrefabs.None() || Container.Inventory.AllItems.Any(i => i != null && ActivatingItemPrefabs.Any(p => p == i.Prefab)); + + public ButtonTerminal(Item item, XElement element) : base(item, element) + { + IsActive = true; + RequiredSignalCount = element.GetChildElements("TerminalButton").Count(c => c.GetAttribute("style") != null); + if (RequiredSignalCount < 1) + { + DebugConsole.ThrowError($"Error in item \"{item.Name}\": no TerminalButton elements defined for the ButtonTerminal component!"); + } + InitProjSpecific(element); + } + + partial void InitProjSpecific(XElement element); + + public override void OnItemLoaded() + { + base.OnItemLoaded(); + + if (Signals == null) + { + Signals = new string[RequiredSignalCount]; + for (int i = 0; i < RequiredSignalCount; i++) + { + Signals[i] = string.Empty; + } + } + else if (Signals.Length != RequiredSignalCount) + { + string[] newSignals = new string[RequiredSignalCount]; + if (Signals.Length < RequiredSignalCount) + { + Signals.CopyTo(newSignals, 0); + for (int i = Signals.Length; i < RequiredSignalCount; i++) + { + newSignals[i] = string.Empty; + } + } + else + { + for (int i = 0; i < RequiredSignalCount; i++) + { + newSignals[i] = Signals[i]; + } + } + Signals = newSignals; + } + + ActivatingItemPrefabs.Clear(); + if (!string.IsNullOrEmpty(ActivatingItems)) + { + foreach (var activatingItem in ActivatingItems.Split(',')) + { + if (MapEntityPrefab.Find(null, identifier: activatingItem, showErrorMessages: false) is ItemPrefab prefab) + { + ActivatingItemPrefabs.Add(prefab); + } + else + { + ItemPrefab.Prefabs.Where(p => p.Tags.Any(t => t.Equals(activatingItem, StringComparison.OrdinalIgnoreCase))) + .ForEach(p => ActivatingItemPrefabs.Add(p)); + } + } + if (ActivatingItemPrefabs.None()) + { + DebugConsole.ThrowError($"Error in item \"{item.Name}\": no activating item prefabs found with identifiers or tags \"{ActivatingItems}\""); + } + } + + var containers = item.GetComponents().ToList(); + if (containers.Count != 1) + { + DebugConsole.ThrowError($"Error in item \"{item.Name}\": the ButtonTerminal component requires exactly one ItemContainer component!"); + return; + } + Container = containers[0]; + + OnItemLoadedProjSpecific(); + } + + partial void OnItemLoadedProjSpecific(); + + private bool SendSignal(int signalIndex, bool isServerMessage = false) + { + if (!isServerMessage && !AllowUsingButtons) { return false; } + string signal = Signals[signalIndex]; + string connectionName = $"signal_out{signalIndex + 1}"; + item.SendSignal(signal, connectionName); + return true; + } + + private void Write(IWriteMessage msg, object[] extraData) + { + if (extraData == null || extraData.Length < 3) { return; } + msg.WriteRangedInteger((int)extraData[2], 0, Signals.Length - 1); + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Connection.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Connection.cs index 09339aabc..90db70255 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Connection.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Connection.cs @@ -101,7 +101,7 @@ namespace Barotrauma.Items.Components foreach (XElement connectionElement in subElement.Elements()) { - string prefabConnectionName = element.GetAttributeString("name", null); + string prefabConnectionName = connectionElement.GetAttributeString("name", null); if (prefabConnectionName == Name) { displayNameTag = connectionElement.GetAttributeString("displayname", ""); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/ConnectionPanel.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/ConnectionPanel.cs index 38c56e427..a52a9600b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/ConnectionPanel.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/ConnectionPanel.cs @@ -134,20 +134,30 @@ namespace Barotrauma.Items.Components foreach (Wire wire in c.Wires) { if (wire == null) { continue; } -#if CLIENT - if (wire.Item.IsSelected) { continue; } -#endif - var wireNodes = wire.GetNodes(); - if (wireNodes.Count == 0) { continue; } + TryMoveWire(wire); + } + } - if (Submarine.RectContains(item.Rect, wireNodes[0] + wireNodeOffset)) - { - wire.MoveNode(0, amount); - } - else if (Submarine.RectContains(item.Rect, wireNodes[wireNodes.Count - 1] + wireNodeOffset)) - { - wire.MoveNode(wireNodes.Count - 1, amount); - } + foreach (var wire in DisconnectedWires) + { + TryMoveWire(wire); + } + + void TryMoveWire(Wire wire) + { +#if CLIENT + if (wire.Item.IsSelected) { return; } +#endif + var wireNodes = wire.GetNodes(); + if (wireNodes.Count == 0) { return; } + + if (Submarine.RectContains(item.Rect, wireNodes[0] + wireNodeOffset)) + { + wire.MoveNode(0, amount); + } + else if (Submarine.RectContains(item.Rect, wireNodes[wireNodes.Count - 1] + wireNodeOffset)) + { + wire.MoveNode(wireNodes.Count - 1, amount); } } } @@ -350,6 +360,7 @@ namespace Barotrauma.Items.Components } } } + Connections.Clear(); #if CLIENT rewireSoundChannel?.FadeOutAndDispose(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/LightComponent.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/LightComponent.cs index a836fab59..90814c3cb 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/LightComponent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/LightComponent.cs @@ -159,7 +159,10 @@ namespace Barotrauma.Items.Components { lightColor = value; #if CLIENT - if (Light != null) Light.Color = IsActive ? lightColor : Color.Transparent; + if (Light != null) + { + Light.Color = IsActive ? lightColor : Color.Transparent; + } #endif } } @@ -233,6 +236,8 @@ namespace Barotrauma.Items.Components } UpdateOnActiveEffects(deltaTime); + if (powerIn == null && powerConsumption > 0.0f) { Voltage -= deltaTime; } + #if CLIENT Light.ParentSub = item.Submarine; #endif @@ -293,8 +298,6 @@ namespace Barotrauma.Items.Components } SetLightSourceState(true, lightBrightness); - - if (powerIn == null && powerConsumption > 0.0f) { Voltage -= deltaTime; } } public override void UpdateBroken(float deltaTime, Camera cam) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/OscillatorComponent.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/OscillatorComponent.cs index 309bfee73..6d92474fe 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/OscillatorComponent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/OscillatorComponent.cs @@ -12,8 +12,10 @@ namespace Barotrauma.Items.Components public enum WaveType { Pulse, + Sawtooth, Sine, Square, + Triangle, } private float frequency; @@ -22,8 +24,11 @@ namespace Barotrauma.Items.Components [InGameEditable, Serialize(WaveType.Pulse, true, description: "What kind of a signal the item outputs." + " Pulse: periodically sends out a signal of 1." + + " Sawtooth: sends out a periodic wave that increases linearly from 0 to 1." + " Sine: sends out a sine wave oscillating between -1 and 1." + - " Square: sends out a signal that alternates between 0 and 1.", alwaysUseInstanceValues: true)] + " Square: sends out a signal that alternates between 0 and 1." + + " Triangle: sends out a wave that alternates between increasing linearly from -1 to 1 and decreasing from 1 to -1.", + alwaysUseInstanceValues: true)] public WaveType OutputType { get; @@ -63,6 +68,10 @@ namespace Barotrauma.Items.Components phase -= pulseInterval; } break; + case WaveType.Sawtooth: + phase = (phase + deltaTime * frequency) % 1.0f; + item.SendSignal(phase.ToString(CultureInfo.InvariantCulture), "signal_out"); + break; case WaveType.Square: phase = (phase + deltaTime * frequency) % 1.0f; item.SendSignal(phase < 0.5f ? "0" : "1", "signal_out"); @@ -71,6 +80,11 @@ namespace Barotrauma.Items.Components phase = (phase + deltaTime * frequency) % 1.0f; item.SendSignal(Math.Sin(phase * MathHelper.TwoPi).ToString(CultureInfo.InvariantCulture), "signal_out"); break; + case WaveType.Triangle: + phase = (phase + deltaTime * frequency) % 1.0f; + float output = 4.0f * MathF.Abs(MathUtils.PositiveModulo(phase - 0.25f, 1.0f) - 0.5f) - 1.0f; + item.SendSignal(output.ToString(CultureInfo.InvariantCulture), "signal_out"); + break; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/RegExFindComponent.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/RegExFindComponent.cs index b2f512ca2..d19349597 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/RegExFindComponent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/RegExFindComponent.cs @@ -80,19 +80,21 @@ namespace Barotrauma.Items.Components public RegExFindComponent(Item item, XElement element) : base(item, element) { + nonContinuousOutputSent = true; IsActive = true; } public override void Update(float deltaTime, Camera cam) { - if (string.IsNullOrWhiteSpace(expression) || regex == null) return; + if (string.IsNullOrWhiteSpace(expression) || regex == null) { return; } + if (!ContinuousOutput && nonContinuousOutputSent) { return; } if (receivedSignal != previousReceivedSignal && receivedSignal != null) { try { Match match = regex.Match(receivedSignal); - previousResult = match.Success; + previousResult = match.Success; previousGroups = UseCaptureGroup && previousResult ? match.Groups : null; previousReceivedSignal = receivedSignal; @@ -133,7 +135,7 @@ namespace Barotrauma.Items.Components { if (!string.IsNullOrEmpty(signalOut)) { item.SendSignal(signalOut, "signal_out"); } } - else if (!nonContinuousOutputSent) + else { if (!string.IsNullOrEmpty(signalOut)) { item.SendSignal(signalOut, "signal_out"); } nonContinuousOutputSent = true; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Terminal.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Terminal.cs index fb93543ab..335827046 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Terminal.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Terminal.cs @@ -41,10 +41,13 @@ namespace Barotrauma.Items.Components set { if (string.IsNullOrEmpty(value)) { return; } - ShowOnDisplay(value); + ShowOnDisplay(value, addToHistory: true); } } + [Editable, Serialize(false, true, description: "The terminal will use a monospace font if this box is ticked.", alwaysUseInstanceValues: true)] + public bool UseMonospaceFont { get; set; } + private string OutputValue { get; set; } public Terminal(Item item, XElement element) @@ -56,7 +59,7 @@ namespace Barotrauma.Items.Components partial void InitProjSpecific(XElement element); - partial void ShowOnDisplay(string input, bool addToHistory = true); + partial void ShowOnDisplay(string input, bool addToHistory); public override void ReceiveSignal(Signal signal, Connection connection) { @@ -67,14 +70,14 @@ namespace Barotrauma.Items.Components } string inputSignal = signal.value.Replace("\\n", "\n"); - ShowOnDisplay(inputSignal); + ShowOnDisplay(inputSignal, addToHistory: true); } public override void OnItemLoaded() { bool isSubEditor = false; #if CLIENT - isSubEditor = Screen.Selected != GameMain.SubEditorScreen || GameMain.GameSession?.GameMode is TestGameMode; + isSubEditor = Screen.Selected == GameMain.SubEditorScreen || GameMain.GameSession?.GameMode is TestGameMode; #endif base.OnItemLoaded(); @@ -107,7 +110,7 @@ namespace Barotrauma.Items.Components { string msg = componentElement.GetAttributeString("msg" + i, null); if (msg == null) { break; } - ShowOnDisplay(msg); + ShowOnDisplay(msg, addToHistory: true); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/WaterDetector.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/WaterDetector.cs index 7c6c61021..763800065 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/WaterDetector.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/WaterDetector.cs @@ -100,9 +100,11 @@ namespace Barotrauma.Items.Components if (item.CurrentHull != null) { - int waterPercentage = MathHelper.Clamp((int)Math.Round(item.CurrentHull.WaterPercentage), 0, 100); + int waterPercentage = MathHelper.Clamp((int)Math.Ceiling(item.CurrentHull.WaterPercentage), 0, 100); item.SendSignal(waterPercentage.ToString(), "water_%"); } + string highPressureOut = (item.CurrentHull == null || item.CurrentHull.LethalPressure > 5.0f) ? "1" : "0"; + item.SendSignal(highPressureOut, "high_pressure"); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/WifiComponent.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/WifiComponent.cs index 200414d89..70d4403b1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/WifiComponent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/WifiComponent.cs @@ -178,7 +178,7 @@ namespace Barotrauma.Items.Components //signal strength diminishes by distance float sentSignalStrength = signal.strength * MathHelper.Clamp(1.0f - (Vector2.Distance(item.WorldPosition, wifiComp.item.WorldPosition) / wifiComp.range), 0.0f, 1.0f); - Signal s = new Signal(signal.value, ++signal.stepsTaken, sender: signal.sender, source: signal.source, + Signal s = new Signal(signal.value, signal.stepsTaken + 1, sender: signal.sender, source: signal.source, power: 0.0f, strength: sentSignalStrength); if (wifiComp.signalOutConnection != null) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Wire.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Wire.cs index 3a054c4e1..2b80a81c8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Wire.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Wire.cs @@ -133,15 +133,15 @@ namespace Barotrauma.Items.Components public bool IsConnectedTo(Item item) { - if (connections[0] != null && connections[0].Item == item) return true; - return (connections[1] != null && connections[1].Item == item); + if (connections[0] != null && connections[0].Item == item) { return true; } + return connections[1] != null && connections[1].Item == item; } public void RemoveConnection(Item item) { for (int i = 0; i < 2; i++) { - if (connections[i] == null || connections[i].Item != item) continue; + if (connections[i] == null || connections[i].Item != item) { continue; } foreach (Wire wire in connections[i].Wires) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/TriggerComponent.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/TriggerComponent.cs new file mode 100644 index 000000000..10ef02bdb --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/TriggerComponent.cs @@ -0,0 +1,187 @@ +using FarseerPhysics; +using FarseerPhysics.Dynamics; +using FarseerPhysics.Dynamics.Contacts; +using Microsoft.Xna.Framework; +using System; +using System.Linq; +using System.Collections.Generic; +using System.Xml.Linq; + +namespace Barotrauma.Items.Components +{ + class TriggerComponent : ItemComponent + { + [Editable, Serialize(0.0f, true, description: "The maximum amount of force applied to the triggering entitites.", alwaysUseInstanceValues: true)] + public float Force { get; set; } + + public PhysicsBody PhysicsBody { get; private set; } + private float Radius { get; set; } + private float RadiusInDisplayUnits { get; set; } + private bool TriggeredOnce { get; set; } + + public bool TriggerActive { get; private set; } + + private readonly LevelTrigger.TriggererType triggeredBy; + private readonly HashSet triggerers = new HashSet(); + private readonly bool triggerOnce; + private readonly List statusEffectTargets = new List(); + /// + /// Effects applied to entities inside the trigger + /// + private readonly List statusEffects = new List(); + /// + /// Attacks applied to entities inside the trigger + /// + private readonly List attacks = new List(); + + public TriggerComponent(Item item, XElement element) : base(item, element) + { + string triggeredByAttribute = element.GetAttributeString("triggeredby", "Character"); + if (!Enum.TryParse(triggeredByAttribute, out triggeredBy)) + { + DebugConsole.ThrowError($"Error in ForceComponent config: \"{triggeredByAttribute}\" is not a valid triggerer type."); + } + triggerOnce = element.GetAttributeBool("triggeronce", false); + string parentDebugName = $"TriggerComponent in {item.Name}"; + foreach (XElement subElement in element.Elements()) + { + switch (subElement.Name.ToString().ToLowerInvariant()) + { + case "statuseffect": + LevelTrigger.LoadStatusEffect(statusEffects, subElement, parentDebugName); + break; + case "attack": + case "damage": + LevelTrigger.LoadAttack(subElement, parentDebugName, triggerOnce, attacks); + break; + } + } + IsActive = true; + } + + public override void OnItemLoaded() + { + base.OnItemLoaded(); + float radiusAttribute = originalElement.GetAttributeFloat("radius", 10.0f); + Radius = ConvertUnits.ToSimUnits(radiusAttribute * item.Scale); + PhysicsBody = new PhysicsBody(0.0f, 0.0f, Radius, 1.5f) + { + BodyType = BodyType.Static, + CollidesWith = LevelTrigger.GetCollisionCategories(triggeredBy), + CollisionCategories = Physics.CollisionWall, + UserData = item + }; + PhysicsBody.FarseerBody.SetIsSensor(true); + PhysicsBody.FarseerBody.OnCollision += OnCollision; + PhysicsBody.FarseerBody.OnSeparation += OnSeparation; + RadiusInDisplayUnits = ConvertUnits.ToDisplayUnits(PhysicsBody.radius); + } + + public override void OnMapLoaded() + { + base.OnMapLoaded(); + PhysicsBody.SetTransformIgnoreContacts(item.SimPosition, 0.0f); + PhysicsBody.Submarine = item.Submarine; + } + + private bool OnCollision(Fixture sender, Fixture other, Contact contact) + { + if (!(LevelTrigger.GetEntity(other) is Entity entity)) { return false; } + if (!LevelTrigger.IsTriggeredByEntity(entity, triggeredBy, mustBeOnSpecificSub: (true, item.Submarine))) { return false; } + triggerers.Add(entity); + return true; + } + + private void OnSeparation(Fixture sender, Fixture other, Contact contact) + { + if (!(LevelTrigger.GetEntity(other) is Entity entity)) + { + return; + } + if (entity is Character character && (!character.Enabled || character.Removed) && triggerers.Contains(entity)) + { + triggerers.Remove(entity); + return; + } + if (LevelTrigger.CheckContactsForOtherFixtures(PhysicsBody, other, entity)) + { + return; + } + triggerers.Remove(entity); + } + + public override void Update(float deltaTime, Camera cam) + { + triggerers.RemoveWhere(t => t.Removed); + LevelTrigger.RemoveDistantTriggerers(PhysicsBody, triggerers, item.WorldPosition); + + if (triggerOnce) + { + if (TriggeredOnce) { return; } + if (triggerers.Count > 0) + { + TriggeredOnce = true; + IsActive = false; + triggerers.Clear(); + } + } + + TriggerActive = triggerers.Any(); + + foreach (Entity triggerer in triggerers) + { + LevelTrigger.ApplyStatusEffects(statusEffects, item.WorldPosition, triggerer, deltaTime, statusEffectTargets); + + if (triggerer is IDamageable damageable) + { + LevelTrigger.ApplyAttacks(attacks, damageable, item.WorldPosition, deltaTime); + } + else if (triggerer is Submarine submarine) + { + LevelTrigger.ApplyAttacks(attacks, item.WorldPosition, deltaTime); + } + + if (Force < 0.01f) + { + // Just ignore very minimal forces + continue; + } + else if (triggerer is Character c) + { + ApplyForce(c.AnimController.Collider); + } + else if (triggerer is Submarine s) + { + ApplyForce(s.SubBody.Body); + } + else if (triggerer is Item i && i.body != null) + { + ApplyForce(i.body); + } + } + + item.SendSignal(IsActive ? "1" : "0", "state_out"); + } + + private void ApplyForce(PhysicsBody body) + { + Vector2 diff = ConvertUnits.ToDisplayUnits(PhysicsBody.SimPosition - body.SimPosition); + if (diff.LengthSquared() < 0.0001f) { return; } + float distanceFactor = LevelTrigger.GetDistanceFactor(body, PhysicsBody, RadiusInDisplayUnits); + if (distanceFactor <= 0.0f) { return; } + Vector2 force = distanceFactor * Force * Vector2.Normalize(diff); + if (force.LengthSquared() < 0.01f) { return; } + body.ApplyForce(force); + } + + public override void Move(Vector2 amount) + { + base.Move(amount); + if (PhysicsBody != null) + { + PhysicsBody.SetTransform(PhysicsBody.SimPosition + ConvertUnits.ToSimUnits(amount), 0.0f); + PhysicsBody.Submarine = item.Submarine; + } + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs index 3ea88ae11..327c23181 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs @@ -64,6 +64,10 @@ namespace Barotrauma.Items.Components private Character currentTarget; const float aiFindTargetInterval = 5.0f; + private const float TinkeringPowerCostReduction = 0.2f; + private const float TinkeringDamageIncrease = 0.2f; + private const float TinkeringReloadDecrease = 0.2f; + public float Rotation { get { return rotation; } @@ -381,6 +385,10 @@ namespace Barotrauma.Items.Components else { float chargeDeltaTime = tryingToCharge ? deltaTime : -deltaTime; + if (chargeDeltaTime > 0f && user != null) + { + chargeDeltaTime *= 1f + user.GetStatValue(StatTypes.TurretChargeSpeed); + } currentChargeTime = Math.Clamp(currentChargeTime + chargeDeltaTime, 0f, MaxChargeTime); } tryingToCharge = false; @@ -430,8 +438,7 @@ namespace Barotrauma.Items.Components if (user?.Info != null && (GameMain.GameSession?.Campaign == null || !Level.IsLoadedOutpost)) { user.Info.IncreaseSkillLevel("weapons", - SkillSettings.Current.SkillIncreasePerSecondWhenOperatingTurret * deltaTime / Math.Max(user.GetSkillLevel("weapons"), 1.0f), - user.Position + Vector2.UnitY * 150.0f); + SkillSettings.Current.SkillIncreasePerSecondWhenOperatingTurret * deltaTime / Math.Max(user.GetSkillLevel("weapons"), 1.0f)); } float rotMidDiff = MathHelper.WrapAngle(rotation - (minRotation + maxRotation) / 2.0f); @@ -504,9 +511,19 @@ namespace Barotrauma.Items.Components return TryLaunch(deltaTime, character); } + public float GetPowerRequiredToShoot() + { + float powerCost = powerConsumption; + if (user != null) + { + powerCost /= (1 + user.GetStatValue(StatTypes.TurretPowerCostReduction)); + } + return powerCost; + } + public bool HasPowerToShoot() { - return GetAvailableBatteryPower() >= powerConsumption; + return GetAvailableBatteryPower() >= GetPowerRequiredToShoot(); } private bool TryLaunch(float deltaTime, Character character = null, bool ignorePower = false) @@ -544,6 +561,8 @@ namespace Barotrauma.Items.Components Projectile launchedProjectile = null; bool loaderBroken = false; + float tinkeringStrength = 0f; + for (int i = 0; i < ProjectileCount; i++) { var projectiles = GetLoadedProjectiles(); @@ -575,6 +594,7 @@ namespace Barotrauma.Items.Components projectiles = GetLoadedProjectiles(); if (projectiles.Any()) { break; } } + } } if (projectiles.Count == 0 && !LaunchWithoutProjectile) @@ -601,10 +621,25 @@ namespace Barotrauma.Items.Components return false; } failedLaunchAttempts = 0; + + foreach (MapEntity e in item.linkedTo) + { + if (!(e is Item linkedItem)) { continue; } + if (!item.prefab.IsLinkAllowed(e.prefab)) { continue; } + if (linkedItem.GetComponent() is Repairable repairable && repairable.IsTinkering && linkedItem.HasTag("turretammosource")) + { + tinkeringStrength = repairable.TinkeringStrength; + } + } + if (!ignorePower) { var batteries = item.GetConnectedComponents(); - float neededPower = powerConsumption; + float neededPower = GetPowerRequiredToShoot(); + // tinkering is currently not factored into the common method as it is checked only when shooting + // but this is a minor issue that causes mostly cosmetic woes. might still be worth refactoring later + neededPower /= 1f + (tinkeringStrength * TinkeringPowerCostReduction); + while (neededPower > 0.0001f && batteries.Count > 0) { batteries.RemoveAll(b => b.Charge <= 0.0001f || b.MaxOutPut <= 0.0001f); @@ -622,7 +657,8 @@ namespace Barotrauma.Items.Components } launchedProjectile = projectiles.FirstOrDefault(); - if (launchedProjectile?.Item.Container != null) + Item container = launchedProjectile?.Item.Container; + if (container != null) { var repairable = launchedProjectile?.Item.Container.GetComponent(); if (repairable != null) @@ -637,18 +673,22 @@ namespace Barotrauma.Items.Components { foreach (Projectile projectile in projectiles) { - Launch(projectile.Item, character); + Launch(projectile.Item, character, tinkeringStrength: tinkeringStrength); } } else { - Launch(null, character); + Launch(null, character, tinkeringStrength: tinkeringStrength); } if (item.AiTarget != null) { item.AiTarget.SoundRange = item.AiTarget.MaxSoundRange; // Turrets also have a light component, which handles the sight range. } + if (container != null) + { + ShiftItemsInProjectileContainer(container.GetComponent()); + } } } @@ -672,9 +712,15 @@ namespace Barotrauma.Items.Components return true; } - private void Launch(Item projectile, Character user = null, float? launchRotation = null) + private void Launch(Item projectile, Character user = null, float? launchRotation = null, float tinkeringStrength = 0f) { reload = reloadTime; + reload /= 1f + (tinkeringStrength * TinkeringReloadDecrease); + + if (user != null) + { + reload /= 1 + user.GetStatValue(StatTypes.TurretAttackSpeed); + } if (projectile != null) { @@ -697,7 +743,9 @@ namespace Barotrauma.Items.Components Projectile projectileComponent = projectile.GetComponent(); if (projectileComponent != null) { - projectileComponent.Attacker = user; + projectileComponent.Attacker = projectileComponent.User = user; + projectileComponent.Attack.DamageMultiplier = 1f + (TinkeringDamageIncrease * tinkeringStrength); + projectileComponent.Use(); projectile.GetComponent()?.Attach(item, projectile); projectileComponent.User = user; @@ -725,6 +773,26 @@ namespace Barotrauma.Items.Components partial void LaunchProjSpecific(); + private void ShiftItemsInProjectileContainer(ItemContainer container) + { + if (container == null) { return; } + bool moved; + do + { + moved = false; + for (int i = 1; i < container.Capacity; i++) + { + if (container.Inventory.GetItemAt(i) is Item item1 && container.Inventory.CanBePutInSlot(item1, i - 1)) + { + if (container.Inventory.TryPutItem(item1, i - 1, allowSwapping: false, allowCombine: false, user: null, createNetworkEvent: true)) + { + moved = true; + } + } + } + } while (moved); + } + private float waitTimer; private float disorderTimer; @@ -864,57 +932,26 @@ namespace Barotrauma.Items.Components float turretAngle = -rotation; if (Math.Abs(MathUtils.GetShortestAngle(enemyAngle, turretAngle)) > 0.15f) { return; } } - Vector2 start = ConvertUnits.ToSimUnits(item.WorldPosition); Vector2 end = ConvertUnits.ToSimUnits(target.WorldPosition); + // Check that there's not other entities that shouldn't be targeted (like a friendly sub) between us and the target. + Body worldTarget = CheckLineOfSight(start, end); + bool shoot; if (target.Submarine != null) { start -= target.Submarine.SimPosition; end -= target.Submarine.SimPosition; - } - var collisionCategories = Physics.CollisionWall | Physics.CollisionCharacter | Physics.CollisionItem | Physics.CollisionLevel; - var pickedBody = Submarine.PickBody(start, end, null, collisionCategories, allowInsideFixture: true, - customPredicate: (Fixture f) => - { - if (f.UserData is Item i && i.GetComponent() != null) { return false; } - return !item.StaticFixtures.Contains(f); - }); - if (pickedBody == null) { return; } - Character targetCharacter = null; - if (pickedBody.UserData is Character c) - { - targetCharacter = c; - } - else if (pickedBody.UserData is Limb limb) - { - targetCharacter = limb.character; - } - if (targetCharacter != null) - { - if (targetCharacter.Params.Group.Equals(ai.Config.Entity, StringComparison.OrdinalIgnoreCase)) - { - // Don't shoot friendly characters - return; - } + Body transformedTarget = CheckLineOfSight(start, end); + shoot = CanShoot(transformedTarget, user: null, ai, targetSubmarines) && (worldTarget == null || CanShoot(worldTarget, user: null, ai, targetSubmarines)); } else { - if (pickedBody.UserData is ISpatialEntity e) - { - Submarine sub = e.Submarine; - if (sub == null) { return; } - if (!targetSubmarines) { return; } - if (sub == Item.Submarine) { return; } - // Don't shoot non-player submarines, i.e. wrecks or outposts. - if (!sub.Info.IsPlayer) { return; } - } - else - { - // Hit something else, probably a level wall - return; - } + shoot = CanShoot(worldTarget, user: null, ai, targetSubmarines); + } + if (shoot) + { + TryLaunch(deltaTime, ignorePower: true); } - TryLaunch(deltaTime, ignorePower: true); } public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective) @@ -998,7 +1035,7 @@ namespace Barotrauma.Items.Components container = containerItem.GetComponent(); if (container != null) { break; } } - if (container == null || container.ContainableItems.Count == 0) + if (container == null || !container.ContainableItemIdentifiers.Any()) { if (character.IsOnPlayerTeam) { @@ -1022,7 +1059,7 @@ namespace Barotrauma.Items.Components { if (!character.IsOnPlayerTeam) { return; } if (character.Submarine != Submarine.MainSub) { return; } - string ammoType = container.ContainableItems.First().Identifiers.FirstOrDefault() ?? "ammobox"; + string ammoType = container.ContainableItemIdentifiers.FirstOrDefault() ?? "ammobox"; int remainingAmmo = Submarine.MainSub.GetItems(false).Count(i => i.HasTag(ammoType) && i.Condition > 1); if (remainingAmmo == 0) { @@ -1067,7 +1104,8 @@ namespace Barotrauma.Items.Components { // Ignore dead, friendly, and those that are inside the same sub if (enemy.IsDead || !enemy.Enabled || enemy.Submarine == character.Submarine) { continue; } - // Don't aim monsters that are inside a submarine. + if (enemy.Submarine != null && enemy.Submarine.TeamID == character.Submarine.TeamID) { continue; } + // Don't aim monsters that are inside any submarine. if (!enemy.IsHuman && enemy.CurrentHull != null) { continue; } if (HumanAIController.IsFriendly(character, enemy)) { continue; } float dist = Vector2.DistanceSquared(enemy.WorldPosition, item.WorldPosition); @@ -1091,26 +1129,34 @@ namespace Barotrauma.Items.Components if (closestEnemy != null) { - // Target the closest limb. Doesn't make much difference with smaller creatures, but enables the bots to shoot longer abyss creatures like the endworm. Otherwise they just target the main body = head. targetPos = closestEnemy.WorldPosition; - float closestDist = closestDistance; - foreach (Limb limb in closestEnemy.AnimController.Limbs) + //if the enemy is inside another sub, aim at the room they're in to make it less obvious that the enemy "knows" exactly where the target is + if (closestEnemy.Submarine != null && closestEnemy.CurrentHull != null && closestEnemy.Submarine != item.Submarine) { - if (limb.IsSevered) { continue; } - if (limb.Hidden) { continue; } - if (!CheckTurretAngle(limb.WorldPosition)) { continue; } - float dist = Vector2.DistanceSquared(limb.WorldPosition, item.WorldPosition); - if (dist < closestDist) - { - closestDist = dist; - targetPos = limb.WorldPosition; - } + targetPos = closestEnemy.CurrentHull.WorldPosition; } - if (closestDist > shootDistance * shootDistance) + else { - // Not close enough to shoot - closestEnemy = null; - targetPos = null; + // Target the closest limb. Doesn't make much difference with smaller creatures, but enables the bots to shoot longer abyss creatures like the endworm. Otherwise they just target the main body = head. + float closestDist = closestDistance; + foreach (Limb limb in closestEnemy.AnimController.Limbs) + { + if (limb.IsSevered) { continue; } + if (limb.Hidden) { continue; } + if (!CheckTurretAngle(limb.WorldPosition)) { continue; } + float dist = Vector2.DistanceSquared(limb.WorldPosition, item.WorldPosition); + if (dist < closestDist) + { + closestDist = dist; + targetPos = limb.WorldPosition; + } + } + if (closestDist > shootDistance * shootDistance) + { + // Not close enough to shoot + closestEnemy = null; + targetPos = null; + } } } else if (item.Submarine != null && Level.Loaded != null) @@ -1158,7 +1204,7 @@ namespace Barotrauma.Items.Components continue; } // Allow targeting farther when heading towards the spire (up to 1000 px) - dist -= MathHelper.Lerp(0, 1000, MathUtils.InverseLerp(minAngle, 1, dot)); ; + dist -= MathHelper.Lerp(0, 1000, MathUtils.InverseLerp(minAngle, 1, dot)); if (dist > closestDistance) { continue; } targetPos = closestPoint; closestDistance = dist; @@ -1222,58 +1268,25 @@ namespace Barotrauma.Items.Components if (Math.Abs(MathUtils.GetShortestAngle(enemyAngle, turretAngle)) > maxAngleError) { return false; } - Vector2 start = ConvertUnits.ToSimUnits(item.WorldPosition); - Vector2 end = ConvertUnits.ToSimUnits(targetPos.Value); - if (closestEnemy != null && closestEnemy.Submarine != null) - { - start -= closestEnemy.Submarine.SimPosition; - end -= closestEnemy.Submarine.SimPosition; - } - var collisionCategories = Physics.CollisionWall | Physics.CollisionCharacter | Physics.CollisionItem | Physics.CollisionLevel; - var pickedBody = Submarine.PickBody(start, end, null, collisionCategories, allowInsideFixture: true, - customPredicate: (Fixture f) => - { - if (f.UserData is Item i && i.GetComponent() != null) { return false; } - return !item.StaticFixtures.Contains(f); - }); - if (pickedBody == null) { return false; } - Character targetCharacter = null; - if (pickedBody.UserData is Character c) - { - targetCharacter = c; - } - else if (pickedBody.UserData is Limb limb) - { - targetCharacter = limb.character; - } - if (targetCharacter != null) - { - if (HumanAIController.IsFriendly(character, targetCharacter)) - { - // Don't shoot friendly characters - return false; - } - } - else - { - if (pickedBody.UserData is ISpatialEntity e) - { - Submarine sub = e.Submarine; - if (sub == null) { return false; } - if (sub == Item.Submarine) { return false; } - // Don't shoot non-player submarines, i.e. wrecks or outposts. - if (!sub.Info.IsPlayer) { return false; } - // Don't shoot friendly submarines. - if (sub.TeamID == Item.Submarine.TeamID) { return false; } - } - else if (!(pickedBody.UserData is Voronoi2.VoronoiCell cell && cell.IsDestructible)) - { - // Hit something else, probably a level wall - return false; - } - } if (canShoot) { + Vector2 start = ConvertUnits.ToSimUnits(item.WorldPosition); + Vector2 end = ConvertUnits.ToSimUnits(targetPos.Value); + // Check that there's not other entities that shouldn't be targeted (like a friendly sub) between us and the target. + Body worldTarget = CheckLineOfSight(start, end); + bool shoot; + if (closestEnemy != null && closestEnemy.Submarine != null) + { + start -= closestEnemy.Submarine.SimPosition; + end -= closestEnemy.Submarine.SimPosition; + Body transformedTarget = CheckLineOfSight(start, end); + shoot = CanShoot(transformedTarget, character) && (worldTarget == null || CanShoot(worldTarget, character)); + } + else + { + shoot = CanShoot(worldTarget, character); + } + if (!shoot) { return false; } if (character.IsOnPlayerTeam) { character.Speak(TextManager.Get("DialogFireTurret"), null, 0.0f, "fireturret", 10.0f); @@ -1284,6 +1297,67 @@ namespace Barotrauma.Items.Components return false; } + private bool CanShoot(Body targetBody, Character user = null, WreckAI ai = null, bool targetSubmarines = true) + { + if (targetBody == null) { return false; } + Character targetCharacter = null; + if (targetBody.UserData is Character c) + { + targetCharacter = c; + } + else if (targetBody.UserData is Limb limb) + { + targetCharacter = limb.character; + } + if (targetCharacter != null) + { + if (user != null) + { + if (HumanAIController.IsFriendly(user, targetCharacter)) + { + return false; + } + } + if (ai != null) + { + if (targetCharacter.Params.Group.Equals(ai.Config.Entity, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + } + } + else + { + if (targetBody.UserData is ISpatialEntity e) + { + Submarine sub = e.Submarine ?? e as Submarine; + if (!targetSubmarines && e is Submarine) { return false; } + if (sub == null) { return false; } + if (sub == Item.Submarine) { return false; } + if (sub.Info.IsOutpost || sub.Info.IsWreck || sub.Info.IsBeacon) { return false; } + if (sub.TeamID == Item.Submarine.TeamID) { return false; } + } + else if (!(targetBody.UserData is Voronoi2.VoronoiCell cell && cell.IsDestructible)) + { + // Hit something else, probably a level wall + return false; + } + } + return true; + } + + private Body CheckLineOfSight(Vector2 start, Vector2 end) + { + var collisionCategories = Physics.CollisionWall | Physics.CollisionCharacter | Physics.CollisionItem | Physics.CollisionLevel; + Body pickedBody = Submarine.PickBody(start, end, null, collisionCategories, allowInsideFixture: true, + customPredicate: (Fixture f) => + { + if (f.UserData is Item i && i.GetComponent() != null) { return false; } + return !item.StaticFixtures.Contains(f); + }); + return pickedBody; + } + private Vector2 GetRelativeFiringPosition(bool useOffset = true) { Vector2 transformedFiringOffset = Vector2.Zero; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Wearable.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Wearable.cs index b541c4812..2b4190bb4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Wearable.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Wearable.cs @@ -5,8 +5,8 @@ using Barotrauma.IO; using System.Linq; using System.Xml.Linq; using Barotrauma.Items.Components; -using Barotrauma.Extensions; using Barotrauma.Networking; +using Barotrauma.Abilities; namespace Barotrauma { @@ -46,14 +46,23 @@ namespace Barotrauma public LimbType Limb { get; private set; } public bool HideLimb { get; private set; } public bool HideOtherWearables { get; private set; } + public bool CanBeHiddenByOtherWearables { get; private set; } public List HideWearablesOfType { get; private set; } public bool InheritLimbDepth { get; private set; } - public bool InheritTextureScale { get; private set; } + /// + /// Does the wearable inherit all the scalings of the wearer? Also the wearable's own scale is used! + /// + public bool InheritScale { get; private set; } + public bool IgnoreRagdollScale { get; private set; } + public bool IgnoreLimbScale { get; private set; } + public bool IgnoreTextureScale { get; private set; } public bool InheritOrigin { get; private set; } public bool InheritSourceRect { get; private set; } public float Scale { get; private set; } + public float Rotation { get; private set; } + public LimbType DepthLimb { get; private set; } private Wearable _wearableComponent; public Wearable WearableComponent @@ -110,10 +119,9 @@ namespace Barotrauma case WearableType.Husk: case WearableType.Herpes: Limb = LimbType.Head; - HideLimb = type == WearableType.Husk || type == WearableType.Herpes; HideOtherWearables = false; InheritLimbDepth = true; - InheritTextureScale = true; + InheritScale = true; InheritOrigin = true; InheritSourceRect = true; break; @@ -169,13 +177,27 @@ namespace Barotrauma Limb = (LimbType)Enum.Parse(typeof(LimbType), SourceElement.GetAttributeString("limb", "Head"), true); HideLimb = SourceElement.GetAttributeBool("hidelimb", false); HideOtherWearables = SourceElement.GetAttributeBool("hideotherwearables", false); + CanBeHiddenByOtherWearables = SourceElement.GetAttributeBool("canbehiddenbyotherwearables", true); InheritLimbDepth = SourceElement.GetAttributeBool("inheritlimbdepth", true); - InheritTextureScale = SourceElement.GetAttributeBool("inherittexturescale", false); + var scale = SourceElement.GetAttribute("inheritscale"); + if (scale != null) + { + InheritScale = scale.GetAttributeBool(false); + } + else + { + InheritScale = SourceElement.GetAttributeBool("inherittexturescale", false); + } + IgnoreLimbScale = SourceElement.GetAttributeBool("ignorelimbscale", false); + IgnoreTextureScale = SourceElement.GetAttributeBool("ignoretexturescale", false); + IgnoreRagdollScale = SourceElement.GetAttributeBool("ignoreragdollscale", false); + SourceElement.GetAttributeBool("inherittexturescale", false); InheritOrigin = SourceElement.GetAttributeBool("inheritorigin", false); InheritSourceRect = SourceElement.GetAttributeBool("inheritsourcerect", false); DepthLimb = (LimbType)Enum.Parse(typeof(LimbType), SourceElement.GetAttributeString("depthlimb", "None"), true); Sound = SourceElement.GetAttributeString("sound", ""); Scale = SourceElement.GetAttributeFloat("scale", 1.0f); + Rotation = MathHelper.ToRadians(SourceElement.GetAttributeFloat("rotation", 0.0f)); var index = SourceElement.GetAttributePoint("sheetindex", new Point(-1, -1)); if (index.X > -1 && index.Y > -1) { @@ -210,7 +232,9 @@ namespace Barotrauma.Items.Components private readonly Limb[] limb; private readonly List damageModifiers; - public readonly Dictionary SkillModifiers; + public readonly Dictionary SkillModifiers = new Dictionary(); + + public readonly Dictionary WearableStatValues = new Dictionary(); public IEnumerable DamageModifiers { @@ -266,7 +290,6 @@ namespace Barotrauma.Items.Components this.item = item; damageModifiers = new List(); - SkillModifiers = new Dictionary(); int spriteCount = element.Elements().Count(x => x.Name.ToString() == "sprite"); Variants = element.GetAttributeInt("variants", 0); @@ -280,7 +303,7 @@ namespace Barotrauma.Items.Components int i = 0; foreach (XElement subElement in element.Elements()) { - switch (subElement.Name.ToString().ToLower()) + switch (subElement.Name.ToString().ToLowerInvariant()) { case "sprite": if (subElement.Attribute("texture") == null) @@ -322,6 +345,18 @@ namespace Barotrauma.Items.Components SkillModifiers.TryAdd(skillIdentifier, skillValue); } break; + case "statvalue": + StatTypes statType = CharacterAbilityGroup.ParseStatType(subElement.GetAttributeString("stattype", ""), Name); + float statValue = subElement.GetAttributeFloat("value", 0f); + if (WearableStatValues.ContainsKey(statType)) + { + WearableStatValues[statType] += statValue; + } + else + { + WearableStatValues.TryAdd(statType, statValue); + } + break; } } } @@ -334,6 +369,7 @@ namespace Barotrauma.Items.Components } picker = character; + for (int i = 0; i < wearableSprites.Length; i++ ) { var wearableSprite = wearableSprites[i]; @@ -379,19 +415,19 @@ namespace Barotrauma.Items.Components return i1.WearableComponent.AllowedSlots.Contains(InvSlotType.OuterClothes).CompareTo(i2.WearableComponent.AllowedSlots.Contains(InvSlotType.OuterClothes)); }); } - #if CLIENT equipLimb.UpdateWearableTypesToHide(); #endif } + character.OnWearablesChanged(); } public override void Drop(Character dropper) { + Character previousPicker = picker; Unequip(picker); - base.Drop(dropper); - + previousPicker?.OnWearablesChanged(); picker = null; IsActive = false; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs index a259d4246..c222b6a31 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs @@ -48,8 +48,8 @@ namespace Barotrauma return false; } } - if (items[0].Prefab.Identifier != item.Prefab.Identifier || - items.Count + 1 > item.Prefab.MaxStackSize) + if (items[0].Quality != item.Quality) { return false; } + if (items[0].Prefab.Identifier != item.Prefab.Identifier || items.Count + 1 > item.Prefab.MaxStackSize) { return false; } @@ -57,7 +57,7 @@ namespace Barotrauma return true; } - public bool CanBePut(ItemPrefab itemPrefab, float? condition = null) + public bool CanBePut(ItemPrefab itemPrefab, float? condition = null, int? quality = null) { if (itemPrefab == null) { return false; } if (items.Count > 0) @@ -82,6 +82,11 @@ namespace Barotrauma if (items.Any(it => !it.IsFullCondition)) { return false; } } + if (quality.HasValue) + { + if (items[0].Quality != quality.Value) { return false; } + } + if (items[0].Prefab.Identifier != itemPrefab.Identifier || items.Count + 1 > itemPrefab.MaxStackSize) { @@ -172,6 +177,11 @@ namespace Barotrauma items.Clear(); } + public void RemoveWhere(Func predicate) + { + items.RemoveAll(it => predicate(it)); + } + public bool Any() { return items.Count > 0; @@ -258,6 +268,8 @@ namespace Barotrauma get { return capacity; } } + public bool AllowSwappingContainedItems = true; + public Inventory(Entity owner, int capacity, int slotsPerRow = 5) { this.capacity = capacity; @@ -420,16 +432,16 @@ namespace Barotrauma return slots[i].CanBePut(item, ignoreCondition); } - public bool CanBePut(ItemPrefab itemPrefab, float? condition = null) + public bool CanBePut(ItemPrefab itemPrefab, float? condition = null, int? quality = null) { for (int i = 0; i < capacity; i++) { - if (CanBePutInSlot(itemPrefab, i, condition)) { return true; } + if (CanBePutInSlot(itemPrefab, i, condition, quality)) { return true; } } return false; } - public virtual bool CanBePutInSlot(ItemPrefab itemPrefab, int i, float? condition = null) + public virtual bool CanBePutInSlot(ItemPrefab itemPrefab, int i, float? condition = null, int? quality = null) { if (i < 0 || i >= slots.Length) { return false; } return slots[i].CanBePut(itemPrefab, condition); @@ -496,12 +508,12 @@ namespace Barotrauma var itemInSlot = slots[i].First(); if (itemInSlot.OwnInventory != null && !itemInSlot.OwnInventory.Contains(item) && - (itemInSlot.GetComponent()?.MaxStackSize ?? 0) == 1 && + (itemInSlot.GetComponent()?.GetMaxStackSize(0) ?? 0) == 1 && itemInSlot.OwnInventory.TrySwapping(0, item, user, createNetworkEvent, swapWholeStack: false)) { return true; } - return + return TrySwapping(i, item, user, createNetworkEvent, swapWholeStack: true) || TrySwapping(i, item, user, createNetworkEvent, swapWholeStack: false); } @@ -623,6 +635,8 @@ namespace Barotrauma protected bool TrySwapping(int index, Item item, Character user, bool createNetworkEvent, bool swapWholeStack) { if (item?.ParentInventory == null || !slots[index].Any()) { return false; } + if (slots[index].Items.Any(it => !it.IsInteractable(user))) { return false; } + if (!AllowSwappingContainedItems) { return false; } //swap to InvSlotType.Any if possible Inventory otherInventory = item.ParentInventory; @@ -772,11 +786,17 @@ namespace Barotrauma { for (int j = 0; j < capacity; j++) { - if (slots[j].Contains(item)) { slots[j].RemoveAllItems(); }; + if (slots[j].Contains(item)) + { + slots[j].RemoveWhere(it => existingItems.Contains(it) || stackedItems.Contains(it)); + } } for (int j = 0; j < otherInventory.capacity; j++) { - if (otherInventory.slots[j].Contains(existingItems.FirstOrDefault())) { otherInventory.slots[j].RemoveAllItems(); } + if (otherInventory.slots[j].Contains(existingItems.FirstOrDefault())) + { + otherInventory.slots[j].RemoveWhere(it => existingItems.Contains(it) || stackedItems.Contains(it)); + } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs index 0636069b9..2fd8b35e8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs @@ -11,6 +11,7 @@ using System.Linq; using System.Xml.Linq; using Barotrauma.Extensions; using Barotrauma.MapCreatures.Behavior; +using Barotrauma.Abilities; #if CLIENT using Microsoft.Xna.Framework.Graphics; @@ -36,7 +37,6 @@ namespace Barotrauma set { currentHull = value; - ParentRuin = currentHull?.ParentRuin; } } @@ -97,14 +97,16 @@ namespace Barotrauma private Dictionary connections; - private List repairables; + private readonly List repairables; - private Queue impactQueue = new Queue(); + private Quality qualityComponent; + + private readonly Queue impactQueue = new Queue(); //a dictionary containing lists of the status effects in all the components of the item - private bool[] hasStatusEffectsOfType; - private Dictionary> statusEffectLists; - + private readonly bool[] hasStatusEffectsOfType; + private readonly Dictionary> statusEffectLists; + public Dictionary SerializableProperties { get; protected set; } private bool? hasInGameEditableProperties; @@ -231,7 +233,6 @@ namespace Barotrauma { if (character != null && character.IsOnPlayerTeam) { - return IsPlayerTeamInteractable; } else @@ -253,6 +254,12 @@ namespace Barotrauma { if (!Prefab.AllowRotatingInEditor) { return; } rotationRad = MathHelper.ToRadians(value); +#if CLIENT + if (Screen.Selected == GameMain.SubEditorScreen) + { + SetContainedItemPositions(); + } +#endif } } @@ -448,7 +455,7 @@ namespace Barotrauma } public bool IsFullCondition => MathUtils.NearlyEqual(Condition, MaxCondition); - public float MaxCondition => Prefab.Health * healthMultiplier; + public float MaxCondition => Prefab.Health * healthMultiplier * maxRepairConditionMultiplier * (1.0f + GetQualityModifier(Items.Components.Quality.StatType.Condition)); public float ConditionPercentage => MathUtils.Percentage(Condition, MaxCondition); private float offsetOnSelectedMultiplier = 1.0f; @@ -466,10 +473,16 @@ namespace Barotrauma public float HealthMultiplier { get => healthMultiplier; - set - { - healthMultiplier = value; - } + set { healthMultiplier = MathHelper.Clamp(value, 0.0f, float.PositiveInfinity); } + } + + private float maxRepairConditionMultiplier = 1.0f; + + [Serialize(1.0f, true)] + public float MaxRepairConditionMultiplier + { + get => maxRepairConditionMultiplier; + set { maxRepairConditionMultiplier = MathHelper.Clamp(value, 0.0f, float.PositiveInfinity); } } //the default value should be Prefab.Health, but because we can't use it in the attribute, @@ -540,6 +553,12 @@ namespace Barotrauma set => indestructible = value; } + public bool AllowDeconstruct + { + get; + set; + } + [Editable, Serialize(false, isSaveable: true, "When enabled will prevent the item from taking damage from all sources")] public bool InvulnerableToDamage { get; set; } @@ -613,6 +632,21 @@ namespace Barotrauma get { return Prefab.UseInHealthInterface; } } + public int Quality + { + get + { + return qualityComponent?.QualityLevel ?? 0; + } + set + { + if (qualityComponent != null) + { + qualityComponent.QualityLevel = value; + } + } + } + public bool InWater { get @@ -767,6 +801,8 @@ namespace Barotrauma condition = MaxCondition; lastSentCondition = condition; + AllowDeconstruct = itemPrefab.AllowDeconstruct; + allPropertyObjects.Add(this); XElement element = itemPrefab.ConfigElement; @@ -926,6 +962,8 @@ namespace Barotrauma ownInventory = itemContainer.Inventory; } + qualityComponent = GetComponent(); + InitProjSpecific(); if (callOnItemLoaded) @@ -943,6 +981,9 @@ namespace Barotrauma if (Components.Any(ic => ic is Wire) && Components.All(ic => ic is Wire || ic is Holdable)) { isWire = true; } if (HasTag("logic")) { isLogic = true; } + + ApplyStatusEffects(ActionType.OnSpawn, 1.0f); + Components.ForEach(c => c.ApplyStatusEffects(ActionType.OnSpawn, 1.0f)); } partial void InitProjSpecific(); @@ -1115,7 +1156,12 @@ namespace Barotrauma if (!componentsByType.ContainsKey(typeof(T))) { return Enumerable.Empty(); } return components.Where(c => c is T).Cast(); } - + + public float GetQualityModifier(Quality.StatType statType) + { + return GetComponent()?.GetValue(statType) ?? 0.0f; + } + public void RemoveContained(Item contained) { ownInventory?.RemoveItem(contained); @@ -1277,7 +1323,7 @@ namespace Barotrauma } Submarine = parentInventory.Owner.Submarine; - if (body != null) body.Submarine = Submarine; + if (body != null) { body.Submarine = Submarine; } return CurrentHull; } @@ -1431,7 +1477,7 @@ namespace Barotrauma bool hasTargets = effect.TargetIdentifiers == null; targets.Clear(); - + if (effect.HasTargetType(StatusEffect.TargetType.Contained)) { foreach (Item containedItem in ContainedItems) @@ -1443,6 +1489,11 @@ namespace Barotrauma continue; } + if (effect.TargetSlot > -1) + { + if (OwnInventory.FindIndex(containedItem) != effect.TargetSlot) { continue; } + } + hasTargets = true; targets.Add(containedItem); } @@ -1500,8 +1551,8 @@ namespace Barotrauma { targets.Add(limb); } - - if (Container != null && effect.HasTargetType(StatusEffect.TargetType.Parent)) targets.Add(Container); + + if (Container != null && effect.HasTargetType(StatusEffect.TargetType.Parent)) { targets.Add(Container); } effect.Apply(type, deltaTime, this, targets, worldPosition); } @@ -1561,7 +1612,10 @@ namespace Barotrauma } } - aiTarget?.Update(deltaTime); + if (aiTarget != null) + { + aiTarget.Update(deltaTime); + } if (!isActive) { return; } @@ -1679,10 +1733,18 @@ namespace Barotrauma public void UpdateTransform() { if (body == null) { return; } - Submarine prevSub = Submarine; - FindHull(); + var projectile = GetComponent(); + if (projectile?.StickTarget?.UserData is Limb limb) + { + Submarine = body.Submarine = limb.character?.Submarine; + currentHull = limb.character?.CurrentHull; + } + else + { + FindHull(); + } if (Submarine == null && prevSub != null) { @@ -1749,7 +1811,7 @@ namespace Barotrauma Vector2 drag = body.LinearVelocity * volume; - body.ApplyForce((uplift - drag) * 10.0f, maxVelocity: NetConfig.MaxPhysicsBodyVelocity); + body.ApplyForce((uplift - drag) * 10.0f); //apply simple angular drag body.ApplyTorque(body.AngularVelocity * volume * -0.05f); @@ -1760,6 +1822,12 @@ namespace Barotrauma { if (transformDirty) { return false; } + var projectile = GetComponent(); + if (projectile?.IgnoredBodies != null) + { + if (projectile.IgnoredBodies.Contains(f2.Body)) { return false; } + } + contact.GetWorldManifold(out Vector2 normal, out _); if (contact.FixtureA.Body == f1.Body) { normal = -normal; } float impact = Vector2.Dot(f1.Body.LinearVelocity, -normal); @@ -1819,7 +1887,8 @@ namespace Barotrauma foreach (ItemComponent component in components) { component.FlipX(relativeToSub); - } + } + SetContainedItemPositions(); } public override void FlipY(bool relativeToSub) @@ -1844,6 +1913,7 @@ namespace Barotrauma { component.FlipY(relativeToSub); } + SetContainedItemPositions(); } /// @@ -1903,17 +1973,17 @@ namespace Barotrauma return connectedComponents; } - - public static readonly Pair[] connectionPairs = new Pair[] + + public static readonly (string input, string output)[] connectionPairs = new (string input, string output)[] { - new Pair("power_in", "power_out"), - new Pair("signal_in1", "signal_out1"), - new Pair("signal_in2", "signal_out2"), - new Pair("signal_in3", "signal_out3"), - new Pair("signal_in4", "signal_out4"), - new Pair("signal_in", "signal_out"), - new Pair("signal_in1", "signal_out"), - new Pair("signal_in2", "signal_out") + ("power_in", "power_out"), + ("signal_in1", "signal_out1"), + ("signal_in2", "signal_out2"), + ("signal_in3", "signal_out3"), + ("signal_in4", "signal_out4"), + ("signal_in", "signal_out"), + ("signal_in1", "signal_out"), + ("signal_in2", "signal_out") }; private void GetConnectedComponentsRecursive(Connection c, HashSet alreadySearched, List connectedComponents) where T : ItemComponent @@ -1949,20 +2019,20 @@ namespace Barotrauma recipient.Item.GetConnectedComponentsRecursive(recipient, alreadySearched, connectedComponents); } - foreach (Pair connectionPair in connectionPairs) + foreach ((string input, string output) in connectionPairs) { - if (connectionPair.First == c.Name) + if (input == c.Name) { - var pairedConnection = c.Item.Connections.FirstOrDefault(c2 => c2.Name == connectionPair.Second); + var pairedConnection = c.Item.Connections.FirstOrDefault(c2 => c2.Name == output); if (pairedConnection != null) { if (alreadySearched.Contains(pairedConnection)) { continue; } GetConnectedComponentsRecursive(pairedConnection, alreadySearched, connectedComponents); } } - else if (connectionPair.Second == c.Name) + else if (output == c.Name) { - var pairedConnection = c.Item.Connections.FirstOrDefault(c2 => c2.Name == connectionPair.First); + var pairedConnection = c.Item.Connections.FirstOrDefault(c2 => c2.Name == input); if (pairedConnection != null) { if (alreadySearched.Contains(pairedConnection)) { continue; } @@ -1972,18 +2042,27 @@ namespace Barotrauma } } - public Controller FindController() + public Controller FindController(string[] tags = null) { //try finding the controller with the simpler non-recursive method first var controllers = GetConnectedComponents(); - if (controllers.None()) { controllers = GetConnectedComponents(recursive: true); } - return controllers.Count < 2 ? controllers.FirstOrDefault() : - (controllers.FirstOrDefault(c => c.GetFocusTarget() == this) ?? controllers.FirstOrDefault()); + bool needsTag = tags != null && tags.Length > 0; + if (controllers.None() || (needsTag && controllers.None(c => c.Item.HasTag(tags)))) + { + controllers = GetConnectedComponents(recursive: true); + } + if (needsTag) + { + controllers.RemoveAll(c => !c.Item.HasTag(tags)); + } + return controllers.Count < 2 ? + controllers.FirstOrDefault() : + controllers.FirstOrDefault(c => c.GetFocusTarget() == this) ?? controllers.FirstOrDefault(); } - public bool TryFindController(out Controller controller) + public bool TryFindController(out Controller controller, string[] tags = null) { - controller = FindController(); + controller = FindController(tags: tags); return controller != null; } @@ -2055,8 +2134,6 @@ namespace Barotrauma } while (CoroutineManager.DeltaTime <= 0.0f); delayedSignals.Remove((signal, connection)); - - signal.source = this; connection.SendSignal(signal); yield return CoroutineStatus.Success; @@ -2289,8 +2366,8 @@ namespace Barotrauma public void ApplyTreatment(Character user, Character character, Limb targetLimb) { //can't apply treatment to dead characters - if (character.IsDead) return; - if (!UseInHealthInterface) return; + if (character.IsDead) { return; } + if (!UseInHealthInterface) { return; } #if CLIENT if (GameMain.Client != null) @@ -2300,10 +2377,12 @@ namespace Barotrauma } #endif + float applyOnSelfFraction = user?.GetStatValue(StatTypes.ApplyTreatmentsOnSelfFraction) ?? 0.0f; + bool remove = false; foreach (ItemComponent ic in components) { - if (!ic.HasRequiredContainedItems(user, addMessage: user == Character.Controlled)) continue; + if (!ic.HasRequiredContainedItems(user, addMessage: user == Character.Controlled)) { continue; } bool success = Rand.Range(0.0f, 0.5f) < ic.DegreeOfSuccess(user); ActionType actionType = success ? ActionType.OnUse : ActionType.OnFailure; @@ -2312,7 +2391,19 @@ namespace Barotrauma ic.PlaySound(actionType, user); #endif ic.WasUsed = true; - ic.ApplyStatusEffects(actionType, 1.0f, character, targetLimb, user: user); + ic.ApplyStatusEffects(actionType, 1.0f, character, targetLimb, user: user, applyOnUserFraction: applyOnSelfFraction); + + if (applyOnSelfFraction > 0.0f) + { + //hacky af + ic.statusEffectLists.TryGetValue(actionType, out var effectList); + if (effectList != null) + { + effectList.ForEach(e => e.AfflictionMultiplier = applyOnSelfFraction); + ic.ApplyStatusEffects(actionType, 1.0f, user, targetLimb == null ? null : user.AnimController.GetLimb(targetLimb.type), user: user); + effectList.ForEach(e => e.AfflictionMultiplier = 1.0f); + } + } if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer) { @@ -2322,9 +2413,18 @@ namespace Barotrauma }); } - if (ic.DeleteOnUse) remove = true; + if (ic.DeleteOnUse) { remove = true; } } + if (user != null) + { + var abilityItem = new AbilityApplyTreatment(user, character, this); + user.CheckTalents(AbilityEffectType.OnApplyTreatment, abilityItem); + + } + + + if (remove) { Spawner?.AddToRemoveQueue(this); } } @@ -2390,6 +2490,8 @@ namespace Barotrauma parentInventory.RemoveItem(this); parentInventory = null; } + + SetContainedItemPositions(); } public void Equip(Character character) @@ -2498,6 +2600,14 @@ namespace Barotrauma { msg.Write((int)value); } + else if (value is string[] a) + { + msg.Write(a.Length); + for (int i = 0; i < a.Length; i++) + { + msg.Write(a[i] ?? ""); + } + } else { throw new NotImplementedException("Serializing item properties of the type \"" + value.GetType() + "\" not supported"); @@ -2605,6 +2715,19 @@ namespace Barotrauma logValue = XMLExtensions.RectToString(val); if (allowEditing) { property.TrySetValue(parentObject, val); } } + else if (type == typeof(string[])) + { + int arrayLength = msg.ReadInt32(); + string[] val = new string[arrayLength]; + for (int i = 0; i < arrayLength; i++) + { + val[i] = msg.ReadString(); + } + if (allowEditing) + { + property.TrySetValue(parentObject, val); + } + } else if (typeof(Enum).IsAssignableFrom(type)) { int intVal = msg.ReadInt32(); @@ -2640,7 +2763,7 @@ namespace Barotrauma { CoroutineManager.StopCoroutines(logPropertyChangeCoroutine); } - logPropertyChangeCoroutine = CoroutineManager.InvokeAfter(() => + logPropertyChangeCoroutine = CoroutineManager.Invoke(() => { GameServer.Log($"{sender.Character.Name} set the value \"{property.Name}\" of the item \"{Name}\" to \"{logValue}\".", ServerLog.MessageType.ItemInteraction); }, delay: 1.0f); @@ -3029,6 +3152,8 @@ namespace Barotrauma } } + connections?.Clear(); + if (parentInventory != null) { if (parentInventory is CharacterInventory characterInventory) @@ -3054,6 +3179,8 @@ namespace Barotrauma body = null; } + CurrentHull = null; + if (StaticFixtures != null) { foreach (Fixture fixture in StaticFixtures) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/ItemInventory.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/ItemInventory.cs index d449ad8c9..cf1870762 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/ItemInventory.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/ItemInventory.cs @@ -9,7 +9,7 @@ namespace Barotrauma { partial class ItemInventory : Inventory { - private ItemContainer container; + private readonly ItemContainer container; public ItemContainer Container { get { return container; } @@ -47,23 +47,23 @@ namespace Barotrauma { if (ItemOwnsSelf(item)) { return false; } if (i < 0 || i >= slots.Length) { return false; } - if (!container.CanBeContained(item)) { return false; } - return item != null && slots[i].CanBePut(item, ignoreCondition) && slots[i].ItemCount < container.MaxStackSize; + if (!container.CanBeContained(item, i)) { return false; } + return item != null && slots[i].CanBePut(item, ignoreCondition) && slots[i].ItemCount < container.GetMaxStackSize(i); } - public override bool CanBePutInSlot(ItemPrefab itemPrefab, int i, float? condition) + public override bool CanBePutInSlot(ItemPrefab itemPrefab, int i, float? condition, int? quality = null) { if (i < 0 || i >= slots.Length) { return false; } - if (!container.CanBeContained(itemPrefab)) { return false; } - return itemPrefab != null && slots[i].CanBePut(itemPrefab, condition) && slots[i].ItemCount < container.MaxStackSize; + if (!container.CanBeContained(itemPrefab, i)) { return false; } + return itemPrefab != null && slots[i].CanBePut(itemPrefab, condition, quality) && slots[i].ItemCount < container.GetMaxStackSize(i); } public override int HowManyCanBePut(ItemPrefab itemPrefab, int i, float? condition) { if (itemPrefab == null) { return 0; } if (i < 0 || i >= slots.Length) { return 0; } - if (!container.CanBeContained(itemPrefab)) { return 0; } - return slots[i].HowManyCanBePut(itemPrefab, maxStackSize: Math.Min(itemPrefab.MaxStackSize, container.MaxStackSize), condition); + if (!container.CanBeContained(itemPrefab, i)) { return 0; } + return slots[i].HowManyCanBePut(itemPrefab, maxStackSize: Math.Min(itemPrefab.MaxStackSize, container.GetMaxStackSize(i)), condition); } public override bool IsFull(bool takeStacksIntoAccount = false) @@ -74,7 +74,7 @@ namespace Barotrauma { if (!slots[i].Any()) { return false; } var item = slots[i].FirstOrDefault(); - if (slots[i].ItemCount < Math.Min(item.Prefab.MaxStackSize, container.MaxStackSize)) { return false; } + if (slots[i].ItemCount < Math.Min(item.Prefab.MaxStackSize, container.GetMaxStackSize(i))) { return false; } } } else diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs index c7256157a..122eebb5b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs @@ -4,6 +4,7 @@ using Microsoft.Xna.Framework; using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Globalization; using System.Linq; using System.Xml.Linq; @@ -17,19 +18,39 @@ namespace Barotrauma //maxCondition does > check, meaning that above this max the deconstruct item will be skipped. public readonly float MaxCondition; //Condition of item on creation - public readonly float OutCondition; + public readonly float OutConditionMin, OutConditionMax; //should the condition of the deconstructed item be copied to the output items public readonly bool CopyCondition; + //tag/identifier of the deconstructor(s) that can be used to deconstruct the item into this + public readonly string[] RequiredDeconstructor; + //tag/identifier of other item(s) that that need to be present in the deconstructor to deconstruct the item into this + public readonly string[] RequiredOtherItem; + //text to display on the deconstructor's activate button when this output is available + public readonly string ActivateButtonText; + public readonly string InfoText; + public readonly string InfoTextOnOtherItemMissing; + public float Commonness { get; } - public DeconstructItem(XElement element) + public DeconstructItem(XElement element, string parentDebugName) { ItemIdentifier = element.GetAttributeString("identifier", "notfound"); MinCondition = element.GetAttributeFloat("mincondition", -0.1f); MaxCondition = element.GetAttributeFloat("maxcondition", 1.0f); - OutCondition = element.GetAttributeFloat("outcondition", 1.0f); + OutConditionMin = element.GetAttributeFloat("outconditionmin", element.GetAttributeFloat("outcondition", 1.0f)); + OutConditionMax = element.GetAttributeFloat("outconditionmax", element.GetAttributeFloat("outcondition", 1.0f)); CopyCondition = element.GetAttributeBool("copycondition", false); Commonness = element.GetAttributeFloat("commonness", 1.0f); + if (element.Attribute("copycondition") != null && element.Attribute("outcondition") != null) + { + DebugConsole.AddWarning($"Invalid deconstruction output in \"{parentDebugName}\": the output item \"{ItemIdentifier}\" has the out condition set, but is also set to copy the condition of the deconstructed item. Ignoring the out condition."); + } + RequiredDeconstructor = element.GetAttributeStringArray("requireddeconstructor", new string[0]); + RequiredOtherItem = element.GetAttributeStringArray("requiredotheritem", new string[0]); + ActivateButtonText = element.GetAttributeString("activatebuttontext", string.Empty); + InfoText = element.GetAttributeString("infotext", string.Empty); + InfoTextOnOtherItemMissing = element.GetAttributeString("infotextonotheritemmissing", string.Empty); + } } @@ -67,8 +88,10 @@ namespace Barotrauma public readonly List RequiredItems; public readonly string[] SuitableFabricatorIdentifiers; public readonly float RequiredTime; + public readonly bool RequiresRecipe; public readonly float OutCondition; //Percentage-based from 0 to 1 public readonly List RequiredSkills; + public int Amount { get; } public FabricationRecipe(XElement element, ItemPrefab itemPrefab) @@ -83,6 +106,7 @@ namespace Barotrauma RequiredTime = element.GetAttributeFloat("requiredtime", 1.0f); OutCondition = element.GetAttributeFloat("outcondition", 1.0f); RequiredItems = new List(); + RequiresRecipe = element.GetAttributeBool("requiresrecipe", false); Amount = element.GetAttributeInt("amount", 1); foreach (XElement subElement in element.Elements()) @@ -281,7 +305,7 @@ namespace Barotrauma /// public List Triggers; - private List fabricationRecipeElements = new List(); + private readonly List fabricationRecipeElements = new List(); private readonly Dictionary treatmentSuitability = new Dictionary(); @@ -290,6 +314,8 @@ namespace Barotrauma /// public bool IsOverride; + public readonly ItemPrefab VariantOf; + public XElement ConfigElement { get; @@ -346,6 +372,9 @@ namespace Barotrauma [Serialize(false, false, description: "Hides the condition bar displayed at the bottom of the inventory slot the item is in.")] public bool HideConditionBar { get; set; } + [Serialize(false, false, description: "Hides the condition displayed in the item's tooltip.")] + public bool HideConditionInTooltip { get; set; } + //if true and the item has trigger areas defined, characters need to be within the trigger to interact with the item //if false, trigger areas define areas that can be used to highlight the item [Serialize(true, false)] @@ -513,6 +542,20 @@ namespace Barotrauma private set; } + [Serialize(0.0f, false)] + public float AddedRepairSpeedMultiplier + { + get; + private set; + } + + [Serialize(false, false)] + public bool CannotRepairFail + { + get; + private set; + } + [Serialize(null, false)] public string EquipConfirmationText { get; set; } @@ -732,6 +775,21 @@ namespace Barotrauma name = originalName; identifier = element.GetAttributeString("identifier", ""); + string variantOf = element.GetAttributeString("variantof", ""); + if (!string.IsNullOrEmpty(variantOf)) + { + ItemPrefab basePrefab = Find(null, variantOf); + if (basePrefab == null) + { + DebugConsole.ThrowError($"Failed to load the item variant \"{identifier}\" - could not find the base prefab \"{variantOf}\""); + } + else + { + VariantOf = basePrefab; + ConfigElement = element = CreateVariantXML(element, basePrefab); + } + } + string categoryStr = element.GetAttributeString("category", "Misc"); if (!Enum.TryParse(categoryStr, true, out MapEntityCategory category)) { @@ -787,6 +845,8 @@ namespace Barotrauma } } + name = GeneticMaterial.TryCreateName(this, element); + if (string.IsNullOrEmpty(name)) { DebugConsole.ThrowError($"Unnamed item ({identifier}) in {filePath}!"); @@ -863,7 +923,8 @@ namespace Barotrauma CanSpriteFlipY = subElement.GetAttributeBool("canflipy", true); sprite = new Sprite(subElement, spriteFolder, lazyLoad: true); - if (subElement.Attribute("sourcerect") == null) + if (subElement.Attribute("sourcerect") == null && + subElement.Attribute("sheetindex") == null) { DebugConsole.ThrowError("Warning - sprite sourcerect not configured for item \"" + Name + "\"!"); } @@ -1031,7 +1092,7 @@ namespace Barotrauma DebugConsole.ThrowError("Error in item config \"" + Name + "\" - use item identifiers instead of names to configure the deconstruct items."); continue; } - DeconstructItems.Add(new DeconstructItem(deconstructItem)); + DeconstructItems.Add(new DeconstructItem(deconstructItem, identifier)); } RandomDeconstructionOutputAmount = Math.Min(RandomDeconstructionOutputAmount, DeconstructItems.Count); break; @@ -1044,7 +1105,7 @@ namespace Barotrauma var preferredContainer = new PreferredContainer(subElement); if (preferredContainer.Primary.Count == 0 && preferredContainer.Secondary.Count == 0) { - DebugConsole.ThrowError($"Error in item prefab {Name}: preferred container has no preferences defined ({subElement.ToString()})."); + DebugConsole.ThrowError($"Error in item prefab {Name}: preferred container has no preferences defined ({subElement})."); } else { @@ -1095,11 +1156,8 @@ namespace Barotrauma { DebugConsole.ThrowError("Error in item prefab \"" + Name + "\" - suitable treatments should be defined using item identifiers, not item names."); } - - string treatmentIdentifier = subElement.GetAttributeString("identifier", "").ToLowerInvariant(); - + string treatmentIdentifier = (subElement.GetAttributeString("identifier", null) ?? subElement.GetAttributeString("type", string.Empty)).ToLowerInvariant(); float suitability = subElement.GetAttributeFloat("suitability", 0.0f); - treatmentSuitability.Add(treatmentIdentifier, suitability); break; } @@ -1112,6 +1170,8 @@ namespace Barotrauma DefaultPrice ??= new PriceInfo(GetMinPrice() ?? 0, false); } + HideConditionInTooltip = element.GetAttributeBool("hideconditionintooltip", HideConditionBar); + //backwards compatibility if (categoryStr.Equals("Thalamus", StringComparison.OrdinalIgnoreCase)) { @@ -1313,5 +1373,99 @@ namespace Barotrauma public static bool IsContainerPreferred(IEnumerable preferences, ItemContainer c) => preferences.Any(id => c.Item.Prefab.Identifier == id || c.Item.HasTag(id)); public static bool IsContainerPreferred(IEnumerable preferences, IEnumerable ids) => ids.Any(id => preferences.Contains(id)); + + private XElement CreateVariantXML(XElement variantElement, ItemPrefab basePrefab) + { + XElement newElement = new XElement(variantElement.Name); + newElement.Add(basePrefab.ConfigElement.Attributes()); + newElement.Add(basePrefab.ConfigElement.Elements()); + + ReplaceElement(newElement, variantElement); + + void ReplaceElement(XElement element, XElement replacement) + { + List elementsToRemove = new List(); + foreach (XAttribute attribute in replacement.Attributes()) + { + ReplaceAttribute(element, attribute); + } + foreach (XElement replacementSubElement in replacement.Elements()) + { + int index = replacement.Elements().ToList().FindAll(e => e.Name.ToString().Equals(replacementSubElement.Name.ToString(), StringComparison.OrdinalIgnoreCase)).IndexOf(replacementSubElement); + System.Diagnostics.Debug.Assert(index > -1); + + int i = 0; + bool matchingElementFound = false; + foreach (XElement subElement in element.Elements()) + { + if (!subElement.Name.ToString().Equals(replacementSubElement.Name.ToString(), StringComparison.OrdinalIgnoreCase)) { continue; } + if (i == index) + { + if (!replacementSubElement.HasAttributes && !replacementSubElement.HasElements) + { + //if the replacement is empty (no attributes or child elements) + //remove the element from the variant + elementsToRemove.Add(subElement); + } + else + { + ReplaceElement(subElement, replacementSubElement); + } + matchingElementFound = true; + break; + } + i++; + } + if (!matchingElementFound) + { + element.Add(replacementSubElement); + } + } + elementsToRemove.ForEach(e => e.Remove()); + } + + void ReplaceAttribute(XElement element, XAttribute newAttribute) + { + XAttribute existingAttribute = element.Attributes().FirstOrDefault(a => a.Name.ToString().Equals(newAttribute.Name.ToString(), StringComparison.OrdinalIgnoreCase)); + if (existingAttribute == null) + { + element.Add(newAttribute); + return; + } + float.TryParse(existingAttribute.Value, NumberStyles.Any, CultureInfo.InvariantCulture, out float value); + if (newAttribute.Value.StartsWith('*')) + { + string multiplierStr = newAttribute.Value.Substring(1, newAttribute.Value.Length - 1); + float.TryParse(multiplierStr, NumberStyles.Any, CultureInfo.InvariantCulture, out float multiplier); + if (multiplierStr.Contains('.') || existingAttribute.Value.Contains('.')) + { + existingAttribute.Value = (value * multiplier).ToString("G", CultureInfo.InvariantCulture); + } + else + { + existingAttribute.Value = ((int)(value * multiplier)).ToString(); + } + } + else if (newAttribute.Value.StartsWith('+')) + { + string additionStr = newAttribute.Value.Substring(1, newAttribute.Value.Length - 1); + float.TryParse(additionStr, NumberStyles.Any, CultureInfo.InvariantCulture, out float addition); + if (additionStr.Contains('.') || existingAttribute.Value.Contains('.')) + { + existingAttribute.Value = (value + addition).ToString("G", CultureInfo.InvariantCulture); + } + else + { + existingAttribute.Value = ((int)(value + addition)).ToString(); + } + } + else + { + existingAttribute.Value = newAttribute.Value; + } + } + + return newElement; + } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/RelatedItem.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/RelatedItem.cs index 905b91ff3..ad9b19866 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/RelatedItem.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/RelatedItem.cs @@ -36,11 +36,18 @@ namespace Barotrauma /// public bool ExcludeBroken { get; private set; } + private bool allowVariants = true; + public RelationType Type { get { return type; } } + /// + /// Index of the slot the target must be in when targeting a Contained item + /// + public int TargetSlot = -1; + public string JoinedIdentifiers { get { return string.Join(",", Identifiers); } @@ -77,13 +84,13 @@ namespace Barotrauma { if (item == null) { return false; } if (excludedIdentifiers.Any(id => item.Prefab.Identifier == id || item.HasTag(id))) { return false; } - return Identifiers.Any(id => item.Prefab.Identifier == id || item.HasTag(id)); + return Identifiers.Any(id => item.Prefab.Identifier == id || item.HasTag(id) || (allowVariants && item.Prefab.VariantOf?.Identifier == id)); } public bool MatchesItem(ItemPrefab itemPrefab) { if (itemPrefab == null) { return false; } if (excludedIdentifiers.Any(id => itemPrefab.Identifier == id || itemPrefab.Tags.Contains(id))) { return false; } - return Identifiers.Any(id => itemPrefab.Identifier == id || itemPrefab.Tags.Contains(id)); + return Identifiers.Any(id => itemPrefab.Identifier == id || itemPrefab.Tags.Contains(id) || (allowVariants && itemPrefab.VariantOf?.Identifier == id)); } public RelatedItem(string[] identifiers, string[] excludedIdentifiers) @@ -147,7 +154,9 @@ namespace Barotrauma foreach (Item contained in parentItem.ContainedItems) { + if (TargetSlot > -1 && parentItem.OwnInventory.FindIndex(contained) != TargetSlot) { continue; } if ((!ExcludeBroken || contained.Condition > 0.0f) && MatchesItem(contained)) { return true; } + if (CheckContained(contained)) { return true; } } return false; @@ -160,7 +169,9 @@ namespace Barotrauma new XAttribute("type", type.ToString()), new XAttribute("optional", IsOptional), new XAttribute("ignoreineditor", IgnoreInEditor), - new XAttribute("excludebroken", ExcludeBroken)); + new XAttribute("excludebroken", ExcludeBroken), + new XAttribute("targetslot", TargetSlot), + new XAttribute("allowvariants", allowVariants)); if (excludedIdentifiers.Length > 0) { @@ -223,7 +234,8 @@ namespace Barotrauma RelatedItem ri = new RelatedItem(identifiers, excludedIdentifiers) { - ExcludeBroken = element.GetAttributeBool("excludebroken", true) + ExcludeBroken = element.GetAttributeBool("excludebroken", true), + allowVariants = element.GetAttributeBool("allowvariants", true) }; string typeStr = element.GetAttributeString("type", ""); if (string.IsNullOrEmpty(typeStr)) @@ -271,6 +283,8 @@ namespace Barotrauma ri.IsOptional = element.GetAttributeBool("optional", false); ri.IgnoreInEditor = element.GetAttributeBool("ignoreineditor", false); ri.MatchOnEmpty = element.GetAttributeBool("matchonempty", false); + ri.TargetSlot = element.GetAttributeInt("targetslot", -1); + return ri; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Entity.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Entity.cs index b09272cc1..47525f812 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Entity.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Entity.cs @@ -30,55 +30,44 @@ namespace Barotrauma private bool idFreed; - public virtual bool Removed - { - get; - private set; - } + public virtual bool Removed { get; private set; } - public bool IdFreed - { - get { return idFreed; } - } + public bool IdFreed => idFreed; public readonly ushort ID; - public virtual Vector2 SimPosition + public virtual Vector2 SimPosition => Vector2.Zero; + + public virtual Vector2 Position => Vector2.Zero; + + public virtual Vector2 WorldPosition => Submarine == null ? Position : Submarine.Position + Position; + + public virtual Vector2 DrawPosition => Submarine == null ? Position : Submarine.DrawPosition + Position; + + public Submarine Submarine { get; set; } + + public AITarget AiTarget => aiTarget; + + public bool InDetectable { - get { return Vector2.Zero; } - } - - public virtual Vector2 Position - { - get { return Vector2.Zero; } - } - - public virtual Vector2 WorldPosition - { - get { return Submarine == null ? Position : Submarine.Position + Position; } - } - - public virtual Vector2 DrawPosition - { - get { return Submarine == null ? Position : Submarine.DrawPosition + Position; } - } - - public Submarine Submarine - { - get; - set; - } - - public AITarget AiTarget - { - get { return aiTarget; } - } - - public double SpawnTime - { - get { return spawnTime; } + get + { + if (aiTarget != null) + { + return aiTarget.InDetectable; + } + return false; + } + set + { + if (aiTarget != null) + { + aiTarget.InDetectable = value; + } + } } + public double SpawnTime => spawnTime; private readonly double spawnTime; public Entity(Submarine submarine, ushort id) @@ -88,7 +77,7 @@ namespace Barotrauma if (id != NullEntityID && dictionary.ContainsKey(id)) { - throw new Exception($"ID {id} is taken by {dictionary[id].ToString()}"); + throw new Exception($"ID {id} is taken by {dictionary[id]}"); } //give a unique ID diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Explosion.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Explosion.cs index 4e6759d60..277b55925 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Explosion.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Explosion.cs @@ -13,10 +13,8 @@ namespace Barotrauma { partial class Explosion { - private static readonly List> prevExplosions = new List>(); - public readonly Attack Attack; - + private readonly float force; private readonly float cameraShake, cameraShakeRange; @@ -35,6 +33,11 @@ namespace Barotrauma private readonly float? flashRange; private readonly string decal; private readonly float decalSize; + // used to apply friendly afflictions in an area without effects displaying + private readonly bool abilityExplosion; + private readonly bool applyToSelf; + + private readonly float itemRepairStrength; public float EmpStrength { get; set; } @@ -63,22 +66,26 @@ namespace Barotrauma force = element.GetAttributeFloat("force", 0.0f); - sparks = element.GetAttributeBool("sparks", true); - shockwave = element.GetAttributeBool("shockwave", true); - flames = element.GetAttributeBool("flames", true); - underwaterBubble = element.GetAttributeBool("underwaterbubble", true); - smoke = element.GetAttributeBool("smoke", true); + abilityExplosion = element.GetAttributeBool("abilityexplosion", false); + applyToSelf = element.GetAttributeBool("applytoself", true); - playTinnitus = element.GetAttributeBool("playtinnitus", true); + bool showEffects = !abilityExplosion; + sparks = element.GetAttributeBool("sparks", showEffects); + shockwave = element.GetAttributeBool("shockwave", showEffects); + flames = element.GetAttributeBool("flames", showEffects); + underwaterBubble = element.GetAttributeBool("underwaterbubble", showEffects); + smoke = element.GetAttributeBool("smoke", showEffects); - applyFireEffects = element.GetAttributeBool("applyfireeffects", flames); + playTinnitus = element.GetAttributeBool("playtinnitus", showEffects); + + applyFireEffects = element.GetAttributeBool("applyfireeffects", flames && showEffects); ignoreFireEffectsForTags = element.GetAttributeStringArray("ignorefireeffectsfortags", new string[0], convertToLowerInvariant: true); ignoreCover = element.GetAttributeBool("ignorecover", false); onlyInside = element.GetAttributeBool("onlyinside", false); onlyOutside = element.GetAttributeBool("onlyoutside", false); - flash = element.GetAttributeBool("flash", true); + flash = element.GetAttributeBool("flash", showEffects); flashDuration = element.GetAttributeFloat("flashduration", 0.05f); if (element.Attribute("flashrange") != null) { flashRange = element.GetAttributeFloat("flashrange", 100.0f); } flashColor = element.GetAttributeColor("flashcolor", Color.LightYellow); @@ -86,15 +93,18 @@ namespace Barotrauma EmpStrength = element.GetAttributeFloat("empstrength", 0.0f); BallastFloraDamage = element.GetAttributeFloat("ballastfloradamage", 0.0f); - decal = element.GetAttributeString("decal", ""); + itemRepairStrength = element.GetAttributeFloat("itemrepairstrength", 0.0f); + + decal = element.GetAttributeString("decal", ""); decalSize = element.GetAttributeFloat(1.0f, "decalSize", "decalsize"); - cameraShake = element.GetAttributeFloat("camerashake", Attack.Range * 0.1f); - cameraShakeRange = element.GetAttributeFloat("camerashakerange", Attack.Range); + cameraShake = element.GetAttributeFloat("camerashake", showEffects ? Attack.Range * 0.1f : 0f); + cameraShakeRange = element.GetAttributeFloat("camerashakerange", showEffects ? Attack.Range : 0f); - screenColorRange = element.GetAttributeFloat("screencolorrange", Attack.Range * 0.1f); + screenColorRange = element.GetAttributeFloat("screencolorrange", showEffects ? Attack.Range * 0.1f : 0f); screenColor = element.GetAttributeColor("screencolor", Color.Transparent); screenColorDuration = element.GetAttributeFloat("screencolorduration", 0.1f); + } public void DisableParticles() @@ -107,19 +117,8 @@ namespace Barotrauma underwaterBubble = false; } - public List> GetRecentExplosions(float maxSecondsAgo) - { - return prevExplosions.FindAll(e => e.Third >= Timing.TotalTime - maxSecondsAgo); - } - public void Explode(Vector2 worldPosition, Entity damageSource, Character attacker = null) { - prevExplosions.Add(new Triplet(this, worldPosition, (float)Timing.TotalTime)); - if (prevExplosions.Count > 100) - { - prevExplosions.RemoveAt(0); - } - Hull hull = Hull.FindHull(worldPosition); ExplodeProjSpecific(worldPosition, hull); @@ -129,6 +128,11 @@ namespace Barotrauma } float displayRange = Attack.Range; + if (damageSource is Item sourceItem) + { + displayRange *= 1.0f + sourceItem.GetQualityModifier(Quality.StatType.ExplosionRadius); + Attack.DamageMultiplier *= 1.0f + sourceItem.GetQualityModifier(Quality.StatType.ExplosionDamage); + } Vector2 cameraPos = GameMain.GameScreen.Cam.Position; float cameraDist = Vector2.Distance(cameraPos, worldPosition) / 2.0f; @@ -143,7 +147,7 @@ namespace Barotrauma if (displayRange < 0.1f) { return; } - if (Attack.GetStructureDamage(1.0f) > 0.0f || Attack.GetLevelWallDamage(1.0f) > 0.0f) + if (!MathUtils.NearlyEqual(Attack.GetStructureDamage(1.0f), 0.0f) || !MathUtils.NearlyEqual(Attack.GetLevelWallDamage(1.0f), 0.0f)) { RangedStructureDamage(worldPosition, displayRange, Attack.GetStructureDamage(1.0f), Attack.GetLevelWallDamage(1.0f), attacker); } @@ -180,12 +184,29 @@ namespace Barotrauma } } - if (MathUtils.NearlyEqual(force, 0.0f) && MathUtils.NearlyEqual(Attack.Stun, 0.0f) && MathUtils.NearlyEqual(Attack.GetTotalDamage(false), 0.0f)) + if (itemRepairStrength > 0.0f) + { + float displayRangeSqr = displayRange * displayRange; + foreach (Item item in Item.ItemList) + { + float distSqr = Vector2.DistanceSquared(item.WorldPosition, worldPosition); + if (distSqr > displayRangeSqr) continue; + + float distFactor = 1.0f - (float)Math.Sqrt(distSqr) / displayRange; + //repair repairable items + if (item.Repairables.Any()) + { + item.Condition += itemRepairStrength * distFactor; + } + } + } + + if (MathUtils.NearlyEqual(force, 0.0f) && MathUtils.NearlyEqual(Attack.Stun, 0.0f) && MathUtils.NearlyEqual(Attack.GetTotalDamage(false), 0.0f) && !abilityExplosion) { return; } - DamageCharacters(worldPosition, Attack, force, damageSource, attacker); + DamageCharacters(worldPosition, Attack, force, damageSource, attacker, applyToSelf); if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient) { @@ -195,9 +216,9 @@ namespace Barotrauma float dist = Vector2.Distance(item.WorldPosition, worldPosition); float itemRadius = item.body == null ? 0.0f : item.body.GetMaxExtent(); dist = Math.Max(0.0f, dist - ConvertUnits.ToDisplayUnits(itemRadius)); - if (dist > Attack.Range) { continue; } + if (dist > displayRange) { continue; } - if (dist < Attack.Range * 0.5f && applyFireEffects && !item.FireProof && ignoreFireEffectsForTags.None(t => item.HasTag(t))) + if (dist < displayRange * 0.5f && applyFireEffects && !item.FireProof && ignoreFireEffectsForTags.None(t => item.HasTag(t))) { //don't apply OnFire effects if the item is inside a fireproof container //(or if it's inside a container that's inside a fireproof container, etc) @@ -224,7 +245,7 @@ namespace Barotrauma if (item.Prefab.DamagedByExplosions && !item.Indestructible) { - float distFactor = 1.0f - dist / Attack.Range; + float distFactor = 1.0f - dist / displayRange; float damageAmount = Attack.GetItemDamage(1.0f) * item.Prefab.ExplosionDamageMultiplier; Vector2 explosionPos = worldPosition; @@ -239,7 +260,7 @@ namespace Barotrauma partial void ExplodeProjSpecific(Vector2 worldPosition, Hull hull); - private void DamageCharacters(Vector2 worldPosition, Attack attack, float force, Entity damageSource, Character attacker) + private void DamageCharacters(Vector2 worldPosition, Attack attack, float force, Entity damageSource, Character attacker, bool applyToSelf) { if (attack.Range <= 0.0f) { return; } @@ -254,6 +275,8 @@ namespace Barotrauma { continue; } + if (c == attacker && !applyToSelf) { continue; } + if (onlyInside && c.Submarine == null) { continue; } else if (onlyOutside && c.Submarine != null) { continue; } @@ -317,7 +340,7 @@ namespace Barotrauma //ensures that the attack hits the correct limb and that the direction of the hit can be determined correctly in the AddDamage methods Vector2 dir = worldPosition - limb.WorldPosition; Vector2 hitPos = limb.WorldPosition + (dir.LengthSquared() <= 0.001f ? Rand.Vector(1.0f) : Vector2.Normalize(dir)) * 0.01f; - AttackResult attackResult = c.AddDamage(hitPos, modifiedAfflictions, attack.Stun * distFactor, false, attacker: attacker); + AttackResult attackResult = c.AddDamage(hitPos, modifiedAfflictions, attack.Stun * distFactor, false, attacker: attacker, damageMultiplier: attack.DamageMultiplier); damages.Add(limb, attackResult.Damage); if (attack.StatusEffects != null && attack.StatusEffects.Any()) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs index ee40e20a9..47093bcf6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs @@ -14,8 +14,8 @@ namespace Barotrauma partial class BackgroundSection { public Rectangle Rect; - public int Index; - public int RowIndex; + public ushort Index; + public ushort RowIndex; private Vector4 colorVector4; private Color color; @@ -39,7 +39,7 @@ namespace Barotrauma } } - public BackgroundSection(Rectangle rect, int index, int rowIndex) + public BackgroundSection(Rectangle rect, ushort index, ushort rowIndex) { Rect = rect; Index = index; @@ -53,7 +53,7 @@ namespace Barotrauma Color = DirtColor = Color.Lerp(new Color(10, 10, 10, 100), new Color(54, 57, 28, 200), Noise.X); } - public BackgroundSection(Rectangle rect, int index, float colorStrength, Color color, int rowIndex) + public BackgroundSection(Rectangle rect, ushort index, float colorStrength, Color color, ushort rowIndex) { System.Diagnostics.Debug.Assert(rect.Width > 0 && rect.Height > 0); @@ -471,9 +471,8 @@ namespace Barotrauma { aiTarget = new AITarget(this) { - MinSightRange = 2000, + MinSightRange = 1000, MaxSightRange = 5000, - MaxSoundRange = 5000, SoundRange = 0 }; } @@ -674,6 +673,9 @@ namespace Barotrauma Gap.UpdateHulls(); } + BackgroundSections?.Clear(); + submergedSections?.Clear(); + List fireSourcesToRemove = new List(FireSources); foreach (FireSource fireSource in fireSourcesToRemove) { @@ -784,7 +786,7 @@ namespace Barotrauma if (aiTarget != null) { - aiTarget.SightRange = Submarine == null ? aiTarget.MinSightRange : Submarine.Velocity.Length() / 2 * aiTarget.MaxSightRange; + aiTarget.SightRange = Submarine == null ? aiTarget.MinSightRange : MathHelper.Lerp(aiTarget.MinSightRange, aiTarget.MaxSightRange, Submarine.Velocity.Length() / 10); aiTarget.SoundRange -= deltaTime * 1000.0f; } @@ -932,7 +934,7 @@ namespace Barotrauma foreach (var gap in ConnectedGaps.Where(gap => gap.Open > 0)) { var distance = MathHelper.Max(Vector2.DistanceSquared(item.Position, gap.Position) / 1000, 1f); - item.body.ApplyForce((gap.LerpedFlowForce / distance) * deltaTime, maxVelocity: NetConfig.MaxPhysicsBodyVelocity); + item.body.ApplyForce((gap.LerpedFlowForce / distance) * deltaTime); } } @@ -1241,6 +1243,44 @@ namespace Barotrauma return "RoomName.Sub" + roomPos.ToString(); } + /// + /// Is this hull or any of the items inside it tagged as "airlock"? + /// + public bool IsTaggedAirlock() + { + if (RoomName != null && RoomName.Contains("airlock", StringComparison.OrdinalIgnoreCase)) + { + return true; + } + else + { + foreach (Item item in Item.ItemList) + { + if (item.CurrentHull != this && item.HasTag("airlock")) + { + return true; + } + } + } + return false; + } + + /// + /// Does this hull have any doors leading outside? + /// + /// Used to check if this character has access to the door leading outside + public bool LeadsOutside(Character character) + { + foreach (var gap in ConnectedGaps) + { + if (gap.ConnectedDoor == null) { continue; } + if (gap.IsRoomToRoom) { continue; } + if (!gap.ConnectedDoor.CanBeTraversed && (character == null || !gap.ConnectedDoor.HasAccess(character))) { continue; } + return true; + } + return false; + } + #region BackgroundSections private void CreateBackgroundSections() { @@ -1260,9 +1300,9 @@ namespace Barotrauma { for (int x = 0; x < xBackgroundMax; x++) { - int index = BackgroundSections.Count; + ushort index = (ushort)BackgroundSections.Count; int sector = (int)Math.Floor(index / (float)sectorWidth - xSectors * y) + y / sectorHeight * (int)Math.Ceiling(xSectors); - BackgroundSections.Add(new BackgroundSection(new Rectangle(x * sectionWidth, y * -sectionHeight, sectionWidth, sectionHeight), index, y)); + BackgroundSections.Add(new BackgroundSection(new Rectangle(x * sectionWidth, y * -sectionHeight, sectionWidth, sectionHeight), index, (ushort)y)); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs index 5a0c3bd8c..698b5fbde 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs @@ -528,6 +528,7 @@ namespace Barotrauma //create a tunnel from the lowest point in the main path to the abyss //to ensure there's a way to the abyss in all levels + Tunnel abyssTunnel = null; if (GenerationParams.CreateHoleToAbyss) { Point lowestPoint = mainPath.Nodes.First(); @@ -535,7 +536,7 @@ namespace Barotrauma { if (pathNode.Y < lowestPoint.Y) { lowestPoint = pathNode; } } - var abyssTunnel = new Tunnel( + abyssTunnel = new Tunnel( TunnelType.SidePath, new List() { lowestPoint, new Point(lowestPoint.X, 0) }, minWidth / 2, parentTunnel: mainPath); @@ -546,7 +547,7 @@ namespace Barotrauma for (int j = 0; j < sideTunnelCount; j++) { if (mainPath.Nodes.Count < 4) { break; } - var validTunnels = Tunnels.FindAll(t => t.Type != TunnelType.Cave && t != startPath && t != endPath && t != endHole); + var validTunnels = Tunnels.FindAll(t => t.Type != TunnelType.Cave && t != startPath && t != endPath && t != endHole && t != abyssTunnel); Tunnel tunnelToBranchOff = validTunnels[Rand.Int(validTunnels.Count, Rand.RandSync.Server)]; if (tunnelToBranchOff == null) { tunnelToBranchOff = mainPath; } @@ -559,7 +560,7 @@ namespace Barotrauma Tunnels.Add(new Tunnel(TunnelType.SidePath, sidePathNodes, pathWidth, parentTunnel: tunnelToBranchOff)); } - CalculateTunnelDistanceField(density: 1000); + CalculateTunnelDistanceField(null); GenerateSeaFloorPositions(); GenerateAbyssArea(); GenerateCaves(mainPath); @@ -691,7 +692,10 @@ namespace Barotrauma } } } - GenerateWaypoints(tunnel, parentTunnel: tunnel.ParentTunnel); + + bool connectToParentTunnel = tunnel.Type != TunnelType.Cave || tunnel.ParentTunnel.Type == TunnelType.Cave; + GenerateWaypoints(tunnel, parentTunnel: connectToParentTunnel ? tunnel.ParentTunnel : null); + EnlargePath(tunnel.Cells, tunnel.MinWidth); foreach (var pathCell in tunnel.Cells) { @@ -791,6 +795,15 @@ namespace Barotrauma cells.AddRange(abyssIsland.Cells); } + List ruinPositions = new List(); + for (int i = 0; i < GenerationParams.RuinCount; i++) + { + Point ruinSize = new Point(5000); + ruinPositions.Add(FindPosAwayFromMainPath((Math.Max(ruinSize.X, ruinSize.Y) + mainPath.MinWidth) * 1.2f, asCloseAsPossible: true, + limits: new Rectangle(new Point(ruinSize.X / 2, ruinSize.Y / 2), Size - ruinSize))); + CalculateTunnelDistanceField(ruinPositions); + } + //---------------------------------------------------------------------------------- // initialize the cells that are still left and insert them into the cell grid //---------------------------------------------------------------------------------- @@ -813,7 +826,9 @@ namespace Barotrauma //---------------------------------------------------------------------------------- // mirror if needed //---------------------------------------------------------------------------------- - + + int asdfasdf = Rand.Int(int.MaxValue, Rand.RandSync.Server); + if (mirror) { HashSet mirroredEdges = new HashSet(); @@ -849,6 +864,21 @@ namespace Barotrauma foreach (AbyssIsland island in AbyssIslands) { island.Area = new Rectangle(borders.Width - island.Area.Right, island.Area.Y, island.Area.Width, island.Area.Height); + foreach (var cell in island.Cells) + { + if (!mirroredSites.Contains(cell.Site)) + { + if (cell.Site.Coord.X % GridCellSize < 1.0f && + cell.Site.Coord.X % GridCellSize >= 0.0f) { cell.Site.Coord.X += 1.0f; } + cell.Site.Coord.X = borders.Width - cell.Site.Coord.X; + mirroredSites.Add(cell.Site); + } + } + } + + for (int i = 0; i < ruinPositions.Count; i++) + { + ruinPositions[i] = new Point(borders.Width - ruinPositions[i].X, ruinPositions[i].Y); } foreach (Cave cave in Caves) @@ -896,7 +926,7 @@ namespace Barotrauma startExitPosition.X = borders.Width - startExitPosition.X; endExitPosition.X = borders.Width - endExitPosition.X; - CalculateTunnelDistanceField(density: 1000); + CalculateTunnelDistanceField(ruinPositions); } foreach (VoronoiCell cell in cells) @@ -913,8 +943,23 @@ namespace Barotrauma foreach (Cave cave in Caves) { if (cave.Area.Y > 0) - { - CreatePathToClosestTunnel(cave.StartPos); + { + List cavePathCells = CreatePathToClosestTunnel(cave.StartPos); + + var mainTunnel = cave.Tunnels.Find(t => t.ParentTunnel.Type != TunnelType.Cave); + + WayPoint prevWp = mainTunnel.WayPoints.First(); + if (prevWp != null) + { + for (int i = 0; i < cavePathCells.Count; i++) + { + var newWaypoint = new WayPoint(cavePathCells[i].Center, SpawnType.Path, submarine: null); + ConnectWaypoints(prevWp, newWaypoint, 500.0f); + prevWp = newWaypoint; + } + var closestPathPoint = FindClosestWayPoint(prevWp.WorldPosition, mainTunnel.ParentTunnel.WayPoints); + ConnectWaypoints(prevWp, closestPathPoint, 500.0f); + } } List caveCells = new List(); @@ -940,9 +985,10 @@ namespace Barotrauma //---------------------------------------------------------------------------------- Ruins = new List(); - for (int i = 0; i < GenerationParams.RuinCount; i++) + for (int i = 0; i < ruinPositions.Count; i++) { - GenerateRuin(mainPath, mirror); + Rand.SetSyncedSeed(ToolBox.StringToInt(Seed) + i); + GenerateRuin(ruinPositions[i], mirror); } EqualityCheckValues.Add(Rand.Int(int.MaxValue, Rand.RandSync.Server)); @@ -1004,7 +1050,6 @@ namespace Barotrauma } } - #if CLIENT List<(List cells, Cave parentCave)> cellBatches = new List<(List, Cave)> { @@ -1083,7 +1128,6 @@ namespace Barotrauma } #endif - EqualityCheckValues.Add(Rand.Int(int.MaxValue, Rand.RandSync.Server)); //---------------------------------------------------------------------------------- @@ -1101,6 +1145,11 @@ namespace Barotrauma // connect side paths and cave branches to their parents //---------------------------------------------------------------------------------- + foreach (Ruin ruin in Ruins) + { + GenerateRuinWayPoints(ruin); + } + foreach (Tunnel tunnel in Tunnels) { if (tunnel.ParentTunnel == null) { continue; } @@ -1343,18 +1392,7 @@ namespace Barotrauma if (wayPoints.Count > 1) { - wayPoints[wayPoints.Count - 2].linkedTo.Add(newWaypoint); - newWaypoint.linkedTo.Add(wayPoints[wayPoints.Count - 2]); - } - - for (int n = 0; n < wayPoints.Count; n++) - { - if (wayPoints[n].Position != newWaypoint.Position) { continue; } - - wayPoints[n].linkedTo.Add(newWaypoint); - newWaypoint.linkedTo.Add(wayPoints[n]); - - break; + wayPoints[wayPoints.Count - 2].ConnectTo(newWaypoint); } } @@ -1363,19 +1401,17 @@ namespace Barotrauma //connect to the tunnel we're branching off from if (parentTunnel != null) { - var parentStart = FindClosestWayPoint(wayPoints.First(), parentTunnel); + var parentStart = FindClosestWayPoint(wayPoints.First().WorldPosition, parentTunnel); if (parentStart != null) { - wayPoints.First().linkedTo.Add(parentStart); - parentStart.linkedTo.Add(wayPoints.First()); + wayPoints.First().ConnectTo(parentStart); } if (tunnel.Type != TunnelType.Cave || tunnel.ParentTunnel.Type == TunnelType.Cave) { - var parentEnd = FindClosestWayPoint(wayPoints.Last(), parentTunnel); + var parentEnd = FindClosestWayPoint(wayPoints.Last().WorldPosition, parentTunnel); if (parentEnd != null) { - wayPoints.Last().linkedTo.Add(parentEnd); - parentEnd.linkedTo.Add(wayPoints.Last()); + wayPoints.Last().ConnectTo(parentEnd); } } } @@ -1385,45 +1421,58 @@ namespace Barotrauma { foreach (WayPoint wayPoint in tunnel.WayPoints) { - var closestWaypoint = FindClosestWayPoint(wayPoint, parentTunnel); + var closestWaypoint = FindClosestWayPoint(wayPoint.WorldPosition, parentTunnel); if (closestWaypoint == null) { continue; } if (Submarine.PickBody( ConvertUnits.ToSimUnits(wayPoint.WorldPosition), ConvertUnits.ToSimUnits(closestWaypoint.WorldPosition), collisionCategory: Physics.CollisionLevel | Physics.CollisionWall) == null) { - Vector2 diff = closestWaypoint.WorldPosition - wayPoint.WorldPosition; - float dist = diff.Length(); float step = ConvertUnits.ToDisplayUnits(Steering.AutopilotMinDistToPathNode) * 0.8f; - - WayPoint prevWaypoint = wayPoint; - for (float x = step; x < dist - step; x += step) - { - var newWaypoint = new WayPoint(wayPoint.WorldPosition + (diff / dist * x), SpawnType.Path, submarine: null) - { - Tunnel = tunnel - }; - prevWaypoint.linkedTo.Add(newWaypoint); - newWaypoint.linkedTo.Add(prevWaypoint); - prevWaypoint = newWaypoint; - } - prevWaypoint.linkedTo.Add(closestWaypoint); - closestWaypoint.linkedTo.Add(prevWaypoint); + ConnectWaypoints(wayPoint, closestWaypoint, step).ForEach(wp => wp.Tunnel = tunnel); } } } - private static WayPoint FindClosestWayPoint(WayPoint wayPoint, Tunnel otherTunnel) + private List ConnectWaypoints(WayPoint wp1, WayPoint wp2, float interval) + { + List newWaypoints = new List(); + + Vector2 diff = wp2.WorldPosition - wp1.WorldPosition; + float dist = diff.Length(); + + WayPoint prevWaypoint = wp1; + for (float x = interval; x < dist - interval; x += interval) + { + var newWaypoint = new WayPoint(wp1.WorldPosition + (diff / dist * x), SpawnType.Path, submarine: null); + prevWaypoint.ConnectTo(newWaypoint); + prevWaypoint = newWaypoint; + newWaypoints.Add(newWaypoint); + } + prevWaypoint.ConnectTo(wp2); + + return newWaypoints; + } + + private static WayPoint FindClosestWayPoint(Vector2 worldPosition, Tunnel otherTunnel) + { + return FindClosestWayPoint(worldPosition, otherTunnel.WayPoints); + } + + private static WayPoint FindClosestWayPoint(Vector2 worldPosition, IEnumerable waypoints, Func filter = null) { float closestDist = float.PositiveInfinity; WayPoint closestWayPoint = null; - foreach (WayPoint otherWayPoint in otherTunnel.WayPoints) + foreach (WayPoint otherWayPoint in waypoints) { - float dist = Vector2.DistanceSquared(otherWayPoint.WorldPosition, wayPoint.WorldPosition); + float dist = Vector2.DistanceSquared(otherWayPoint.WorldPosition, worldPosition); if (dist < closestDist) { + if (filter != null) + { + if (!filter(otherWayPoint)) { continue; } + } closestDist = dist; closestWayPoint = otherWayPoint; - } } return closestWayPoint; @@ -1706,7 +1755,7 @@ namespace Barotrauma GenerateCave(caveParams, parentTunnel, cavePos, caveSize); - CalculateTunnelDistanceField(density: 1000); + CalculateTunnelDistanceField(null); } } @@ -1788,84 +1837,172 @@ namespace Barotrauma } } - private void GenerateRuin(Tunnel mainPath, bool mirror) + private void GenerateRuin(Point ruinPos, bool mirror) { - var ruinGenerationParams = RuinGenerationParams.GetRandom(); + var ruinGenerationParams = RuinGenerationParams.GetRandom(Rand.RandSync.Server); - Point ruinSize = new Point( - Rand.Range(ruinGenerationParams.SizeMin.X, ruinGenerationParams.SizeMax.X, Rand.RandSync.Server), - Rand.Range(ruinGenerationParams.SizeMin.Y, ruinGenerationParams.SizeMax.Y, Rand.RandSync.Server)); - int ruinRadius = Math.Max(ruinSize.X, ruinSize.Y) / 2; - - Point ruinPos = FindPosAwayFromMainPath((ruinRadius + mainPath.MinWidth) * 1.2f, asCloseAsPossible: true, - limits: new Rectangle(new Point(ruinSize.X / 2, ruinSize.Y / 2), Size - ruinSize)); - - VoronoiCell closestPathCell = null; - double closestDist = 0.0f; - foreach (VoronoiCell pathCell in mainPath.Cells) + LocationType locationType = StartLocation?.Type; + if (locationType == null) { - double dist = MathUtils.DistanceSquared(pathCell.Site.Coord.X, pathCell.Site.Coord.Y, ruinPos.X, ruinPos.Y); - if (closestPathCell == null || dist < closestDist) + locationType = LocationType.List.GetRandom(Rand.RandSync.Server); + if (ruinGenerationParams.AllowedLocationTypes.Any()) { - closestPathCell = pathCell; - closestDist = dist; + locationType = LocationType.List.Where(lt => + ruinGenerationParams.AllowedLocationTypes.Any(allowedType => + allowedType.Equals("any", StringComparison.OrdinalIgnoreCase) || lt.Identifier.Equals(allowedType, StringComparison.OrdinalIgnoreCase))).GetRandom(Rand.RandSync.Server); } } - - var ruin = new Ruin(closestPathCell, cells, ruinGenerationParams, new Rectangle(ruinPos - new Point(ruinSize.X / 2, ruinSize.Y / 2), ruinSize), mirror); + + var ruin = new Ruin(this, ruinGenerationParams, locationType, ruinPos, mirror); Ruins.Add(ruin); - - ruin.RuinShapes.Sort((shape1, shape2) => shape2.DistanceFromEntrance.CompareTo(shape1.DistanceFromEntrance)); - // TODO: autogenerate waypoints inside the ruins and connect them to the main path in multiple places. - // We need the waypoints for the AI navigation and we could use them for spawning the creatures too. - int waypointCount = 0; - foreach (WayPoint wp in WayPoint.WayPointList) + var tooClose = GetTooCloseCells(ruinPos.ToVector2(), Math.Max(ruin.Area.Width, ruin.Area.Height) * 4); + + foreach (VoronoiCell cell in tooClose) { - if (wp.SpawnType != SpawnType.Enemy || wp.Submarine != null) { continue; } - if (ruin.RuinShapes.Any(rs => rs.Rect.Contains(wp.WorldPosition))) + if (cell.CellType == CellType.Empty) { continue; } + if (ExtraWalls.Any(w => w.Cells.Contains(cell))) { continue; } + foreach (GraphEdge e in cell.Edges) { - PositionsOfInterest.Add(new InterestingPosition(new Point((int)wp.WorldPosition.X, (int)wp.WorldPosition.Y), PositionType.Ruin, ruin: ruin)); - waypointCount++; - } - } - - //not enough waypoints inside ruins -> create some spawn positions manually - for (int i = 0; i < 4 - waypointCount && i < ruin.RuinShapes.Count; i++) - { - PositionsOfInterest.Add(new InterestingPosition(ruin.RuinShapes[i].Rect.Center, PositionType.Ruin, ruin: ruin)); - } - - foreach (RuinShape ruinShape in ruin.RuinShapes) - { - var tooClose = GetTooCloseCells(ruinShape.Rect.Center.ToVector2(), Math.Max(ruinShape.Rect.Width, ruinShape.Rect.Height) * 4); - - foreach (VoronoiCell cell in tooClose) - { - if (cell.CellType == CellType.Empty) { continue; } - if (ExtraWalls.Any(w => w.Cells.Contains(cell))) { continue; } - foreach (GraphEdge e in cell.Edges) + if (ruin.Area.Contains(e.Point1) || ruin.Area.Contains(e.Point2) || + MathUtils.GetLineRectangleIntersection(e.Point1, e.Point2, ruin.Area, out _)) { - Rectangle rect = ruinShape.Rect; - rect.Y += rect.Height; - if (ruinShape.Rect.Contains(e.Point1) || ruinShape.Rect.Contains(e.Point2) || - MathUtils.GetLineRectangleIntersection(e.Point1, e.Point2, rect, out _)) + cell.CellType = CellType.Removed; + for (int x = 0; x < cellGrid.GetLength(0); x++) { - cell.CellType = CellType.Removed; - for (int x = 0; x < cellGrid.GetLength(0); x++) + for (int y = 0; y < cellGrid.GetLength(1); y++) { - for (int y = 0; y < cellGrid.GetLength(1); y++) - { - cellGrid[x, y].Remove(cell); - } + cellGrid[x, y].Remove(cell); } - cells.Remove(cell); - break; } + cells.Remove(cell); + break; } } } - CreatePathToClosestTunnel(ruinPos); + ruin.PathCells = CreatePathToClosestTunnel(ruin.Area.Center); + } + + private void GenerateRuinWayPoints(Ruin ruin) + { + var tooClose = GetTooCloseCells(ruin.Area.Center.ToVector2(), Math.Max(ruin.Area.Width, ruin.Area.Height) * 6); + + List wayPoints = new List(); + float outSideWaypointInterval = 500.0f; + WayPoint[,] cornerWaypoint = new WayPoint[2, 2]; + Rectangle waypointArea = ruin.Area; + waypointArea.Inflate(100, 100); + + //generate waypoints around the ruin + for (int i = 0; i < 2; i++) + { + for (float x = waypointArea.X + outSideWaypointInterval; x < waypointArea.Right - outSideWaypointInterval; x += outSideWaypointInterval) + { + var wayPoint = new WayPoint(new Vector2(x, waypointArea.Y + waypointArea.Height * i), SpawnType.Path, null) + { + Ruin = ruin + }; + wayPoints.Add(wayPoint); + if (x == waypointArea.X + outSideWaypointInterval) + { + cornerWaypoint[i, 0] = wayPoint; + } + else + { + wayPoint.ConnectTo(wayPoints[wayPoints.Count - 2]); + } + } + cornerWaypoint[i, 1] = wayPoints[wayPoints.Count - 1]; + } + + for (int i = 0; i < 2; i++) + { + WayPoint wayPoint = null; + for (float y = waypointArea.Y; y < waypointArea.Y + waypointArea.Height; y += outSideWaypointInterval) + { + wayPoint = new WayPoint(new Vector2(waypointArea.X + waypointArea.Width * i, y), SpawnType.Path, null) + { + Ruin = ruin + }; + wayPoints.Add(wayPoint); + if (y == waypointArea.Y) + { + wayPoint.ConnectTo(cornerWaypoint[0, i]); + } + else + { + wayPoint.ConnectTo(wayPoints[wayPoints.Count - 2]); + } + } + wayPoint.ConnectTo(cornerWaypoint[1, i]); + } + + //remove waypoints that are inside walls + for (int i = wayPoints.Count - 1; i >= 0; i--) + { + WayPoint wp = wayPoints[i]; + var overlappingCell = tooClose.Find(c => c.CellType != CellType.Removed && c.IsPointInside(wp.WorldPosition)); + if (overlappingCell == null) { continue; } + if (wp.linkedTo.Count > 1) + { + WayPoint linked1 = wp.linkedTo[0] as WayPoint; + WayPoint linked2 = wp.linkedTo[1] as WayPoint; + linked1.ConnectTo(linked2); + } + wp.Remove(); + wayPoints.RemoveAt(i); + } + + //connect ruin entrances to the outside waypoints + foreach (Gap g in Gap.GapList) + { + if (g.Submarine != ruin.Submarine || g.IsRoomToRoom || g.linkedTo.Count == 0) { continue; } + var gapWaypoint = WayPoint.WayPointList.Find(wp => wp.ConnectedGap == g); + if (gapWaypoint == null) { continue; } + + //place another waypoint in front of the entrance + Vector2 entranceDir = Vector2.Zero; + if (g.IsHorizontal) + { + entranceDir = Vector2.UnitX * 2 * Math.Sign(g.WorldPosition.X - g.linkedTo[0].WorldPosition.X); + } + else + { + entranceDir = Vector2.UnitY * 2 * Math.Sign(g.WorldPosition.Y - g.linkedTo[0].WorldPosition.Y); + } + var entranceWayPoint = new WayPoint(g.WorldPosition + entranceDir * 64.0f, SpawnType.Path, null) + { + Ruin = ruin + }; + entranceWayPoint.ConnectTo(gapWaypoint); + var closestWp = FindClosestWayPoint(entranceWayPoint.WorldPosition, wayPoints, (wp) => + { + return Submarine.PickBody( + ConvertUnits.ToSimUnits(wp.WorldPosition), + ConvertUnits.ToSimUnits(entranceWayPoint.WorldPosition), collisionCategory: Physics.CollisionLevel | Physics.CollisionWall) == null; + }); + if (closestWp == null) { continue; } + ConnectWaypoints(entranceWayPoint, closestWp, outSideWaypointInterval); + } + + //create a waypoint path from the ruin to the closest tunnel + WayPoint prevWp = FindClosestWayPoint(ruin.PathCells.First().Center, wayPoints, (wp) => + { + return Submarine.PickBody( + ConvertUnits.ToSimUnits(wp.WorldPosition), + ConvertUnits.ToSimUnits(ruin.PathCells.First().Center), collisionCategory: Physics.CollisionLevel | Physics.CollisionWall) == null; + }); + if (prevWp != null) + { + for (int i = 0; i < ruin.PathCells.Count; i++) + { + var newWaypoint = new WayPoint(ruin.PathCells[i].Center, SpawnType.Path, submarine: null); + ConnectWaypoints(prevWp, newWaypoint, outSideWaypointInterval); + prevWp = newWaypoint; + } + var closestPathPoint = FindClosestWayPoint(prevWp.WorldPosition, Tunnels.SelectMany(t => t.WayPoints)); + ConnectWaypoints(prevWp, closestPathPoint, outSideWaypointInterval); + } } private Point FindPosAwayFromMainPath(double minDistance, bool asCloseAsPossible, Rectangle? limits = null) @@ -1891,8 +2028,9 @@ namespace Barotrauma } } - private void CalculateTunnelDistanceField(int density) + private void CalculateTunnelDistanceField(List ruinPositions) { + int density = 1000; distanceField = new List<(Point point, double distance)>(); if (Mirrored) @@ -1927,6 +2065,23 @@ namespace Barotrauma shortestDistSqr = Math.Min(shortestDistSqr, MathUtils.LineSegmentToPointDistanceSquared(tunnel.Nodes[i - 1], tunnel.Nodes[i], point)); } } + if (ruinPositions != null) + { + int ruinSize = 10000; + foreach (Point ruinPos in ruinPositions) + { + double xDiff = Math.Abs(point.X - ruinPos.X); + double yDiff = Math.Abs(point.Y - ruinPos.Y); + if (xDiff < ruinSize || yDiff < ruinSize) + { + shortestDistSqr = 0.0f; + } + else + { + shortestDistSqr = Math.Min(xDiff * xDiff + yDiff * yDiff, shortestDistSqr); + } + } + } shortestDistSqr = Math.Min(shortestDistSqr, MathUtils.DistanceSquared((double)point.X, (double)point.Y, (double)startPosition.X, (double)startPosition.Y)); shortestDistSqr = Math.Min(shortestDistSqr, MathUtils.DistanceSquared((double)point.X, (double)point.Y, (double)startExitPosition.X, (double)borders.Bottom)); shortestDistSqr = Math.Min(shortestDistSqr, MathUtils.DistanceSquared((double)point.X, (double)point.Y, (double)endPosition.X, (double)endPosition.Y)); @@ -2564,10 +2719,18 @@ namespace Barotrauma if (PositionsOfInterest.Any(p => p.PositionType == PositionType.Cave)) { positionType = PositionType.Cave; + if (allValidLocations.Any(l => l.Edge.NextToCave)) + { + allValidLocations.RemoveAll(l => !l.Edge.NextToCave); + } } else if (PositionsOfInterest.Any(p => p.PositionType == PositionType.SidePath)) { positionType = PositionType.SidePath; + if (allValidLocations.Any(l => l.Edge.NextToSidePath)) + { + allValidLocations.RemoveAll(l => !l.Edge.NextToSidePath); + } } var poi = PositionsOfInterest.GetRandom(p => p.PositionType == positionType, randSync: Rand.RandSync.Server); @@ -2946,7 +3109,7 @@ namespace Barotrauma return closestCell; } - private void CreatePathToClosestTunnel(Point pos) + private List CreatePathToClosestTunnel(Point pos) { VoronoiCell closestPathCell = null; double closestDist = 0.0f; @@ -2966,6 +3129,7 @@ namespace Barotrauma //cast a ray from the closest path cell towards the position and remove the cells it hits List validCells = cells.FindAll(c => c.CellType != CellType.Empty && c.CellType != CellType.Removed); + List pathCells = new List() { closestPathCell }; foreach (VoronoiCell cell in validCells) { foreach (GraphEdge e in cell.Edges) @@ -2980,6 +3144,7 @@ namespace Barotrauma cellGrid[x, y].Remove(cell); } } + pathCells.Add(cell); cells.Remove(cell); //go through the edges of this cell and find the ones that are next to a removed cell @@ -3012,12 +3177,12 @@ namespace Barotrauma } } } - - break; - } } + + pathCells.Sort((c1, c2) => { return Vector2.DistanceSquared(c1.Center, pos.ToVector2()).CompareTo(Vector2.DistanceSquared(c2.Center, pos.ToVector2())); }); + return pathCells; } public string GetWreckIDTag(string originalTag, Submarine wreck) @@ -3049,10 +3214,8 @@ namespace Barotrauma var waypoints = WayPoint.WayPointList.Where(wp => wp.Submarine == null && wp.SpawnType == SpawnType.Path && - wp.WorldPosition.X < EndExitPosition.X && !IsCloseToStart(wp.WorldPosition, minDistance) && - !IsCloseToEnd(wp.WorldPosition, minDistance) - ).ToList(); + !IsCloseToEnd(wp.WorldPosition, minDistance)).ToList(); var subDoc = SubmarineInfo.OpenFile(contentFile.Path); Rectangle subBorders = Submarine.GetBorders(subDoc.Root); @@ -3137,7 +3300,7 @@ namespace Barotrauma } tempSW.Stop(); Debug.WriteLine($"Sub {sub.Info.Name} loaded in { tempSW.ElapsedMilliseconds} (ms)"); - sub.SetPosition(spawnPoint, forceUndockFromStaticSubmarines: false); + sub.SetPosition(spawnPoint); wreckPositions.Add(sub, positions); blockedRects.Add(sub, rects); return sub; @@ -3468,7 +3631,7 @@ namespace Barotrauma { locationType = LocationType.List.Where(lt => outpostGenerationParams.AllowedLocationTypes.Any(allowedType => - allowedType.Equals("any", StringComparison.OrdinalIgnoreCase) || lt.Identifier.Equals(allowedType, StringComparison.OrdinalIgnoreCase))).GetRandom(); + allowedType.Equals("any", StringComparison.OrdinalIgnoreCase) || lt.Identifier.Equals(allowedType, StringComparison.OrdinalIgnoreCase))).GetRandom(Rand.RandSync.Server); } } @@ -3649,9 +3812,10 @@ namespace Barotrauma } if (LevelData.IsBeaconActive) { - if (reactorContainer != null && reactorContainer.Inventory.IsEmpty()) + if (reactorContainer != null && reactorContainer.Inventory.IsEmpty() && + reactorContainer.ContainableItemIdentifiers.Any() && ItemPrefab.Prefabs.ContainsKey(reactorContainer.ContainableItemIdentifiers.FirstOrDefault())) { - ItemPrefab fuelPrefab = ItemPrefab.Prefabs[reactorContainer.ContainableItems[0].Identifiers[0]]; + ItemPrefab fuelPrefab = ItemPrefab.Prefabs[reactorContainer.ContainableItemIdentifiers.FirstOrDefault()]; Spawner.AddToSpawnQueue( fuelPrefab, reactorContainer.Inventory, onSpawned: (it) => reactorComponent.PowerUpImmediately()); @@ -3826,7 +3990,7 @@ namespace Barotrauma bool TryGetExtraSpawnPoint(out Vector2 point) { point = Vector2.Zero; - var hull = Hull.hullList.FindAll(h => h.Submarine == wreck).GetRandom(); + var hull = Hull.hullList.FindAll(h => h.Submarine == wreck).GetRandom(Rand.RandSync.Unsynced); if (hull != null) { point = hull.WorldPosition; @@ -3891,12 +4055,39 @@ namespace Barotrauma LevelObjectManager = null; } + AbyssIslands?.Clear(); + AbyssResources?.Clear(); + Caves?.Clear(); + Tunnels?.Clear(); + PathPoints?.Clear(); + PositionsOfInterest?.Clear(); + + wreckPositions?.Clear(); + Wrecks?.Clear(); + + BeaconStation = null; + beaconSonar = null; + StartOutpost = null; + EndOutpost = null; + + blockedRects?.Clear(); + + EntitiesBeforeGenerate?.Clear(); + EqualityCheckValues?.Clear(); + if (Ruins != null) { Ruins.Clear(); Ruins = null; } + bottomPositions?.Clear(); + BottomBarrier = null; + TopBarrier = null; + SeaFloor = null; + + distanceField = null; + if (ExtraWalls != null) { foreach (LevelWall w in ExtraWalls) { w.Dispose(); } @@ -3908,7 +4099,9 @@ namespace Barotrauma UnsyncedExtraWalls = null; } + tempCells?.Clear(); cells = null; + cellGrid = null; if (bodies != null) { @@ -3916,6 +4109,9 @@ namespace Barotrauma bodies = null; } + StartLocation = null; + EndLocation = null; + Loaded = null; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelObjectManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelObjectManager.cs index e4288f792..1d6643992 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelObjectManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelObjectManager.cs @@ -84,25 +84,42 @@ namespace Barotrauma objectGrid = new List[ level.Size.X / GridSize, (level.Size.Y - level.BottomPos) / GridSize]; - + List availableSpawnPositions = new List(); var levelCells = level.GetAllCells(); - availableSpawnPositions.AddRange(GetAvailableSpawnPositions(levelCells, LevelObjectPrefab.SpawnPosType.Wall)); + availableSpawnPositions.AddRange(GetAvailableSpawnPositions(levelCells, LevelObjectPrefab.SpawnPosType.Wall)); availableSpawnPositions.AddRange(GetAvailableSpawnPositions(level.SeaFloor.Cells, LevelObjectPrefab.SpawnPosType.SeaFloor)); - - foreach (RuinGeneration.Ruin ruin in level.Ruins) + + foreach (Structure structure in Structure.WallList) { - foreach (var ruinShape in ruin.RuinShapes) + if (!structure.HasBody || structure.HiddenInGame) { continue; } + if (level.Ruins.Any(r => r.Submarine == structure.Submarine)) { - foreach (var wall in ruinShape.Walls) + if (structure.IsHorizontal) { + bool topHull = Hull.FindHull(structure.WorldPosition + Vector2.UnitY * 64) != null; + bool bottomHull = Hull.FindHull(structure.WorldPosition - Vector2.UnitY * 64) != null; + if (topHull && bottomHull ) { continue; } + availableSpawnPositions.Add(new SpawnPosition( - new GraphEdge(wall.A, wall.B), - (wall.A + wall.B) / 2.0f - ruinShape.Center, + new GraphEdge(new Vector2(structure.WorldRect.X, structure.WorldPosition.Y), new Vector2(structure.WorldRect.Right, structure.WorldPosition.Y)), + bottomHull ? Vector2.UnitY : -Vector2.UnitY, LevelObjectPrefab.SpawnPosType.RuinWall, - ruinShape.GetLineAlignment(wall))); + bottomHull ? Alignment.Bottom : Alignment.Top)); } - } + else + { + bool rightHull = Hull.FindHull(structure.WorldPosition + Vector2.UnitX * 64) != null; + bool leftHull = Hull.FindHull(structure.WorldPosition - Vector2.UnitX * 64) != null; + if (rightHull && leftHull) { continue; } + + availableSpawnPositions.Add(new SpawnPosition( + new GraphEdge(new Vector2(structure.WorldPosition.X, structure.WorldRect.Y), new Vector2(structure.WorldPosition.X, structure.WorldRect.Y - structure.WorldRect.Height)), + leftHull ? Vector2.UnitX : -Vector2.UnitX, + LevelObjectPrefab.SpawnPosType.RuinWall, + leftHull ? Alignment.Left : Alignment.Right)); + } + } } foreach (var posOfInterest in level.PositionsOfInterest) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelTrigger.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelTrigger.cs index 6259d240d..71a30f339 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelTrigger.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelTrigger.cs @@ -13,7 +13,7 @@ namespace Barotrauma partial class LevelTrigger { [Flags] - enum TriggererType + public enum TriggererType { None = 0, Human = 1, @@ -258,13 +258,17 @@ namespace Barotrauma { DebugConsole.ThrowError("Error in LevelTrigger config: \"" + triggeredByStr + "\" is not a valid triggerer type."); } - UpdateCollisionCategories(); + if (PhysicsBody != null) + { + PhysicsBody.CollidesWith = GetCollisionCategories(triggeredBy); + } + TriggerOthersDistance = element.GetAttributeFloat("triggerothersdistance", 0.0f); var tagsArray = element.GetAttributeStringArray("tags", new string[0]); foreach (string tag in tagsArray) { - tags.Add(tag.ToLower()); + tags.Add(tag.ToLowerInvariant()); } if (triggeredBy.HasFlag(TriggererType.OtherTrigger)) @@ -272,30 +276,21 @@ namespace Barotrauma var otherTagsArray = element.GetAttributeStringArray("allowedothertriggertags", new string[0]); foreach (string tag in otherTagsArray) { - allowedOtherTriggerTags.Add(tag.ToLower()); + allowedOtherTriggerTags.Add(tag.ToLowerInvariant()); } } + string debugName = string.IsNullOrEmpty(parentDebugName) ? "LevelTrigger" : $"LevelTrigger in {parentDebugName}"; foreach (XElement subElement in element.Elements()) { switch (subElement.Name.ToString().ToLowerInvariant()) { case "statuseffect": - statusEffects.Add(StatusEffect.Load(subElement, string.IsNullOrEmpty(parentDebugName) ? "LevelTrigger" : "LevelTrigger in "+ parentDebugName)); + LoadStatusEffect(statusEffects, subElement, debugName); break; case "attack": case "damage": - var attack = new Attack(subElement, string.IsNullOrEmpty(parentDebugName) ? "LevelTrigger" : "LevelTrigger in " + parentDebugName); - if (!triggerOnce) - { - var multipliedAfflictions = attack.GetMultipliedAfflictions((float)Timing.Step); - attack.Afflictions.Clear(); - foreach (Affliction affliction in multipliedAfflictions) - { - attack.Afflictions.Add(affliction, null); - } - } - attacks.Add(attack); + LoadAttack(subElement, debugName, triggerOnce, attacks); break; } } @@ -304,16 +299,13 @@ namespace Barotrauma randomTriggerTimer = Rand.Range(0.0f, randomTriggerInterval); } - private void UpdateCollisionCategories() + public static Category GetCollisionCategories(TriggererType triggeredBy) { - if (PhysicsBody == null) return; - var collidesWith = Physics.CollisionNone; if (triggeredBy.HasFlag(TriggererType.Human) || triggeredBy.HasFlag(TriggererType.Creature)) { collidesWith |= Physics.CollisionCharacter; } if (triggeredBy.HasFlag(TriggererType.Item)) { collidesWith |= Physics.CollisionItem | Physics.CollisionProjectile; } if (triggeredBy.HasFlag(TriggererType.Submarine)) { collidesWith |= Physics.CollisionWall; } - - PhysicsBody.CollidesWith = collidesWith; + return collidesWith; } private void CalculateDirectionalForce() @@ -326,33 +318,31 @@ namespace Barotrauma -sa * unrotatedForce.X + ca * unrotatedForce.Y); } - private bool PhysicsBody_OnCollision(Fixture fixtureA, Fixture fixtureB, FarseerPhysics.Dynamics.Contacts.Contact contact) + public static void LoadStatusEffect(List statusEffects, XElement element, string parentDebugName) + { + statusEffects.Add(StatusEffect.Load(element, parentDebugName)); + } + + public static void LoadAttack(XElement element, string parentDebugName, bool triggerOnce, List attacks) + { + var attack = new Attack(element, parentDebugName); + if (!triggerOnce) + { + var multipliedAfflictions = attack.GetMultipliedAfflictions((float)Timing.Step); + attack.Afflictions.Clear(); + foreach (Affliction affliction in multipliedAfflictions) + { + attack.Afflictions.Add(affliction, null); + } + } + attacks.Add(attack); + } + + private bool PhysicsBody_OnCollision(Fixture fixtureA, Fixture fixtureB, Contact contact) { Entity entity = GetEntity(fixtureB); - if (entity == null) return false; - - if (entity is Character character) - { - if (character.CurrentHull != null) return false; - if (character.IsHuman) - { - if (!triggeredBy.HasFlag(TriggererType.Human)) return false; - } - else - { - if (!triggeredBy.HasFlag(TriggererType.Creature)) return false; - } - } - else if (entity is Item item) - { - if (item.CurrentHull != null) return false; - if (!triggeredBy.HasFlag(TriggererType.Item)) return false; - } - else if (entity is Submarine) - { - if (!triggeredBy.HasFlag(TriggererType.Submarine)) return false; - } - + if (entity == null) { return false; } + if (!IsTriggeredByEntity(entity, triggeredBy, mustBeOutside: true)) { return false; } if (!triggerers.Contains(entity)) { if (!IsTriggered) @@ -365,6 +355,34 @@ namespace Barotrauma return true; } + public static bool IsTriggeredByEntity(Entity entity, TriggererType triggeredBy, bool mustBeOutside = false, (bool mustBe, Submarine sub) mustBeOnSpecificSub = default) + { + if (entity is Character character) + { + if (mustBeOutside && character.CurrentHull != null) { return false; } + if (mustBeOnSpecificSub.mustBe && character.Submarine != mustBeOnSpecificSub.sub) { return false; } + if (character.IsHuman) + { + if (!triggeredBy.HasFlag(TriggererType.Human)) { return false; } + } + else + { + if (!triggeredBy.HasFlag(TriggererType.Creature)) { return false; } + } + } + else if (entity is Item item) + { + if (mustBeOutside && item.CurrentHull != null) { return false; } + if (mustBeOnSpecificSub.mustBe && item.Submarine != mustBeOnSpecificSub.sub) { return false; } + if (!triggeredBy.HasFlag(TriggererType.Item)) { return false; } + } + else if (entity is Submarine) + { + if (!triggeredBy.HasFlag(TriggererType.Submarine)) { return false; } + } + return true; + } + private void PhysicsBody_OnSeparation(Fixture fixtureA, Fixture fixtureB, Contact contact) { Entity entity = GetEntity(fixtureB); @@ -379,10 +397,21 @@ namespace Barotrauma return; } + if (CheckContactsForOtherFixtures(PhysicsBody, fixtureB, entity)) { return; } + + if (triggerers.Contains(entity)) + { + TriggererPosition.Remove(entity); + triggerers.Remove(entity); + } + } + + public static bool CheckContactsForOtherFixtures(PhysicsBody triggerBody, Fixture otherFixture, Entity separatingEntity) + { //check if there are contacts with any other fixture of the trigger //(the OnSeparation callback happens when two fixtures separate, //e.g. if a body stops touching the circular fixture at the end of a capsule-shaped body) - foreach (Fixture fixture in PhysicsBody.FarseerBody.FixtureList) + foreach (Fixture fixture in triggerBody.FarseerBody.FixtureList) { ContactEdge contactEdge = fixture.Body.ContactList; while (contactEdge != null) @@ -393,30 +422,24 @@ namespace Barotrauma { if (contactEdge.Contact.FixtureA != fixture && contactEdge.Contact.FixtureB != fixture) { - var otherEntity = GetEntity(contactEdge.Contact.FixtureB == fixtureB ? + var otherEntity = GetEntity(contactEdge.Contact.FixtureB == otherFixture ? contactEdge.Contact.FixtureB : contactEdge.Contact.FixtureA); - if (otherEntity == entity) { return; } + if (otherEntity == separatingEntity) { return true; } } } contactEdge = contactEdge.Next; } } - - if (triggerers.Contains(entity)) - { - TriggererPosition.Remove(entity); - triggerers.Remove(entity); - } + return false; } - private Entity GetEntity(Fixture fixture) + public static Entity GetEntity(Fixture fixture) { if (fixture.Body == null || fixture.Body.UserData == null) { return null; } if (fixture.Body.UserData is Entity entity) { return entity; } if (fixture.Body.UserData is Limb limb) { return limb.character; } if (fixture.Body.UserData is SubmarineBody subBody) { return subBody.Submarine; } - return null; } @@ -452,15 +475,7 @@ namespace Barotrauma triggerers.RemoveWhere(t => t.Removed); - if (PhysicsBody != null) - { - //failsafe to ensure triggerers get removed when they're far from the trigger - float maxExtent = Math.Max(ConvertUnits.ToDisplayUnits(PhysicsBody.GetMaxExtent() * 5), 5000.0f); - triggerers.RemoveWhere(t => - { - return Vector2.Distance(t.WorldPosition, WorldPosition) > maxExtent; - }); - } + RemoveDistantTriggerers(PhysicsBody, triggerers, WorldPosition); bool isNotClient = true; #if CLIENT @@ -525,57 +540,15 @@ namespace Barotrauma foreach (Entity triggerer in triggerers) { - foreach (StatusEffect effect in statusEffects) - { - if (effect.type == ActionType.OnBroken) { continue; } - Vector2? position = null; - if (effect.HasTargetType(StatusEffect.TargetType.This)) { position = WorldPosition; } - if (triggerer is Character character) - { - effect.Apply(effect.type, deltaTime, triggerer, character, position); - if (effect.HasTargetType(StatusEffect.TargetType.Contained) && character.Inventory != null) - { - foreach (Item item in character.Inventory.AllItemsMod) - { - if (item.ContainedItems == null) { continue; } - foreach (Item containedItem in item.ContainedItems) - { - effect.Apply(effect.type, deltaTime, triggerer, containedItem.AllPropertyObjects, position); - } - } - } - } - else if (triggerer is Item item) - { - effect.Apply(effect.type, deltaTime, triggerer, item.AllPropertyObjects, position); - } - if (effect.HasTargetType(StatusEffect.TargetType.NearbyItems) || - effect.HasTargetType(StatusEffect.TargetType.NearbyCharacters)) - { - targets.Clear(); - targets.AddRange(effect.GetNearbyTargets(worldPosition, targets)); - effect.Apply(effect.type, deltaTime, triggerer, targets); - } - } + ApplyStatusEffects(statusEffects, worldPosition, triggerer, deltaTime, targets); if (triggerer is IDamageable damageable) { - foreach (Attack attack in attacks) - { - attack.DoDamage(null, damageable, WorldPosition, deltaTime, false); - } + ApplyAttacks(attacks, damageable, worldPosition, deltaTime); } else if (triggerer is Submarine submarine) { - foreach (Attack attack in attacks) - { - float structureDamage = attack.GetStructureDamage(deltaTime); - if (structureDamage > 0.0f) - { - Explosion.RangedStructureDamage(worldPosition, attack.DamageRange, structureDamage, levelWallDamage: 0.0f); - } - } - + ApplyAttacks(attacks, worldPosition, deltaTime); if (!string.IsNullOrWhiteSpace(InfectIdentifier)) { submarine.AttemptBallastFloraInfection(InfectIdentifier, deltaTime, InfectionChance); @@ -586,16 +559,16 @@ namespace Barotrauma { if (triggerer is Character character) { - ApplyForce(character.AnimController.Collider, deltaTime); + ApplyForce(character.AnimController.Collider); foreach (Limb limb in character.AnimController.Limbs) { if (limb.IsSevered) { continue; } - ApplyForce(limb.body, deltaTime); + ApplyForce(limb.body); } } else if (triggerer is Submarine submarine) { - ApplyForce(submarine.SubBody.Body, deltaTime); + ApplyForce(submarine.SubBody.Body); } } @@ -606,12 +579,84 @@ namespace Barotrauma } } - private void ApplyForce(PhysicsBody body, float deltaTime) + public static void RemoveDistantTriggerers(PhysicsBody physicsBody, HashSet triggerers, Vector2 calculateDistanceTo) + { + //failsafe to ensure triggerers get removed when they're far from the trigger + if (physicsBody == null) { return; } + float maxExtent = Math.Max(ConvertUnits.ToDisplayUnits(physicsBody.GetMaxExtent() * 5), 5000.0f); + triggerers.RemoveWhere(t => + { + return Vector2.Distance(t.WorldPosition, calculateDistanceTo) > maxExtent; + }); + } + + public static void ApplyStatusEffects(List statusEffects, Vector2 worldPosition, Entity triggerer, float deltaTime, List targets) + { + foreach (StatusEffect effect in statusEffects) + { + if (effect.type == ActionType.OnBroken) { return; } + Vector2? position = null; + if (effect.HasTargetType(StatusEffect.TargetType.This)) { position = worldPosition; } + if (triggerer is Character character) + { + effect.Apply(effect.type, deltaTime, triggerer, character, position); + if (effect.HasTargetType(StatusEffect.TargetType.Contained) && character.Inventory != null) + { + foreach (Item item in character.Inventory.AllItemsMod) + { + if (item.ContainedItems == null) { continue; } + foreach (Item containedItem in item.ContainedItems) + { + effect.Apply(effect.type, deltaTime, triggerer, containedItem.AllPropertyObjects, position); + } + } + } + } + else if (triggerer is Item item) + { + effect.Apply(effect.type, deltaTime, triggerer, item.AllPropertyObjects, position); + } + if (effect.HasTargetType(StatusEffect.TargetType.NearbyItems) || effect.HasTargetType(StatusEffect.TargetType.NearbyCharacters)) + { + targets.Clear(); + targets.AddRange(effect.GetNearbyTargets(worldPosition, targets)); + effect.Apply(effect.type, deltaTime, triggerer, targets); + } + } + } + + /// + /// Applies attacks to a damageable. + /// + public static void ApplyAttacks(List attacks, IDamageable damageable, Vector2 worldPosition, float deltaTime) + { + foreach (Attack attack in attacks) + { + attack.DoDamage(null, damageable, worldPosition, deltaTime, false); + } + } + + /// + /// Applies attacks to structures. + /// + public static void ApplyAttacks(List attacks, Vector2 worldPosition, float deltaTime) + { + foreach (Attack attack in attacks) + { + float structureDamage = attack.GetStructureDamage(deltaTime); + if (structureDamage > 0.0f) + { + Explosion.RangedStructureDamage(worldPosition, attack.DamageRange, structureDamage, levelWallDamage: 0.0f); + } + } + } + + private void ApplyForce(PhysicsBody body) { float distFactor = 1.0f; if (ForceFalloff) { - distFactor = 1.0f - ConvertUnits.ToDisplayUnits(Vector2.Distance(body.SimPosition, PhysicsBody.SimPosition)) / ColliderRadius; + distFactor = GetDistanceFactor(body, PhysicsBody, ColliderRadius); if (distFactor < 0.0f) return; } @@ -621,19 +666,19 @@ namespace Barotrauma if (ForceVelocityLimit < 1000.0f) body.ApplyForce(Force * currentForceFluctuation * distFactor, ForceVelocityLimit); else - body.ApplyForce(Force * currentForceFluctuation * distFactor, maxVelocity: NetConfig.MaxPhysicsBodyVelocity); + body.ApplyForce(Force * currentForceFluctuation * distFactor); break; case TriggerForceMode.Acceleration: if (ForceVelocityLimit < 1000.0f) body.ApplyForce(Force * body.Mass * currentForceFluctuation * distFactor, ForceVelocityLimit); else - body.ApplyForce(Force * body.Mass * currentForceFluctuation * distFactor, maxVelocity: NetConfig.MaxPhysicsBodyVelocity); + body.ApplyForce(Force * body.Mass * currentForceFluctuation * distFactor); break; case TriggerForceMode.Impulse: if (ForceVelocityLimit < 1000.0f) body.ApplyLinearImpulse(Force * currentForceFluctuation * distFactor, maxVelocity: ForceVelocityLimit); else - body.ApplyLinearImpulse(Force * currentForceFluctuation * distFactor, maxVelocity: NetConfig.MaxPhysicsBodyVelocity); + body.ApplyLinearImpulse(Force * currentForceFluctuation * distFactor); break; case TriggerForceMode.LimitVelocity: float maxVel = ForceVelocityLimit * currentForceFluctuation * distFactor; @@ -648,6 +693,11 @@ namespace Barotrauma } } + public static float GetDistanceFactor(PhysicsBody triggererBody, PhysicsBody triggerBody, float colliderRadius) + { + return 1.0f - ConvertUnits.ToDisplayUnits(Vector2.Distance(triggererBody.SimPosition, triggerBody.SimPosition)) / colliderRadius; + } + public Vector2 GetWaterFlowVelocity(Vector2 viewPosition) { Vector2 baseVel = GetWaterFlowVelocity(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Ruins/BTRoom.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Ruins/BTRoom.cs deleted file mode 100644 index 1537134e1..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Ruins/BTRoom.cs +++ /dev/null @@ -1,190 +0,0 @@ -using Microsoft.Xna.Framework; -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Barotrauma.RuinGeneration -{ - /// - /// nodes of a binary tree used for generating underwater "dungeons" - /// - class BTRoom : RuinShape - { - private BTRoom[] subRooms; - - public BTRoom Parent - { - get; - private set; - } - - public Corridor Corridor - { - get; - set; - } - - public BTRoom[] SubRooms - { - get { return subRooms; } - } - - public BTRoom Adjacent - { - get; - private set; - } - - public BTRoom(Rectangle rect) - { - this.rect = rect; - } - - public void Split(float minDivRatio, float verticalProbability = 0.5f, int minWidth = 200, int minHeight = 200) - { - bool verticalSplit = Rand.Range(0.0f, rect.Height / (float)rect.Width, Rand.RandSync.Server) < verticalProbability; - if (rect.Width * minDivRatio < minWidth && rect.Height * minDivRatio < minHeight) - { - minDivRatio = 0.5f; - } - else if (rect.Width * minDivRatio < minWidth) - { - verticalSplit = false; - } - else if (rect.Height * minDivRatio < minHeight) - { - verticalSplit = true; - } - - subRooms = new BTRoom[2]; - if (verticalSplit) - { - SplitVertical(minDivRatio); - } - else - { - SplitHorizontal(minDivRatio); - } - - subRooms[0].Parent = this; - subRooms[1].Parent = this; - - subRooms[0].Adjacent = subRooms[1]; - subRooms[1].Adjacent = subRooms[0]; - } - - private void SplitHorizontal(float minDivRatio) - { - float div = Rand.Range(minDivRatio, 1.0f - minDivRatio, Rand.RandSync.Server); - subRooms[0] = new BTRoom(new Rectangle(rect.X, rect.Y, rect.Width, (int)(rect.Height * div))); - subRooms[1] = new BTRoom(new Rectangle(rect.X, rect.Y + subRooms[0].rect.Height, rect.Width, rect.Height - subRooms[0].rect.Height)); - - } - - private void SplitVertical(float minDivRatio) - { - float div = Rand.Range(minDivRatio, 1.0f - minDivRatio, Rand.RandSync.Server); - subRooms[0] = new BTRoom(new Rectangle(rect.X, rect.Y, (int)(rect.Width * div), rect.Height)); - subRooms[1] = new BTRoom(new Rectangle(rect.X + subRooms[0].rect.Width, rect.Y, rect.Width - subRooms[0].rect.Width, rect.Height)); - } - - public override void CreateWalls() - { - Walls = new List - { - new Line(new Vector2(Rect.X, Rect.Y), new Vector2(Rect.Right, Rect.Y)), - new Line(new Vector2(Rect.X, Rect.Bottom), new Vector2(Rect.Right, Rect.Bottom)), - new Line(new Vector2(Rect.X, Rect.Y), new Vector2(Rect.X, Rect.Bottom)), - new Line(new Vector2(Rect.Right, Rect.Y), new Vector2(Rect.Right, Rect.Bottom)) - }; - } - - public void Scale(Vector2 scale) - { - rect.Inflate((scale.X - 1.0f) * 0.5f * rect.Width, (scale.Y - 1.0f) * 0.5f * rect.Height); - } - - public List GetLeaves() - { - return GetLeaves(new List()); - } - - private List GetLeaves(List leaves) - { - if (subRooms == null) - { - leaves.Add(this); - } - else - { - subRooms[0].GetLeaves(leaves); - subRooms[1].GetLeaves(leaves); - } - - return leaves; - } - - public void GenerateCorridors(int minWidth, int maxWidth, List corridors) - { - if (Adjacent != null && Corridor == null) - { - Corridor = new Corridor(this, Rand.Range(minWidth, maxWidth, Rand.RandSync.Server), corridors); - } - - if (subRooms != null) - { - subRooms[0].GenerateCorridors(minWidth, maxWidth, corridors); - subRooms[1].GenerateCorridors(minWidth, maxWidth, corridors); - } - } - - public static void CalculateDistancesFromEntrance(BTRoom entrance, List rooms, List corridors) - { - entrance.CalculateDistanceFromEntrance(0, rooms, new List(corridors)); - } - - private void CalculateDistanceFromEntrance(int currentDist, List rooms, List corridors) - { - DistanceFromEntrance = DistanceFromEntrance == 0 ? currentDist : Math.Min(currentDist, DistanceFromEntrance); - - currentDist++; - - var roomRect = Rect; - roomRect.Inflate(5, 5); - foreach (var corridor in corridors) - { - var corridorRect = corridor.Rect; - corridorRect.Inflate(5, 5); - if (!corridorRect.Intersects(roomRect)) continue; - - corridor.DistanceFromEntrance = corridor.DistanceFromEntrance == 0 ? - DistanceFromEntrance + 1 : - Math.Min(corridor.DistanceFromEntrance, DistanceFromEntrance + 1); - - - List connectedRooms = new List(); - foreach (var otherRoom in rooms) - { - if (otherRoom == this) continue; - if (otherRoom.DistanceFromEntrance > 0 && otherRoom.DistanceFromEntrance < currentDist) continue; - - var otherRoomRect = otherRoom.Rect; - otherRoomRect.Inflate(5, 5); - if (corridorRect.Intersects(otherRoomRect)) { connectedRooms.Add(otherRoom); } - } - - connectedRooms.Sort((r1, r2) => - { - return - (Math.Abs(r1.Rect.Center.X - Rect.Center.X) + Math.Abs(r1.Rect.Center.Y - Rect.Center.Y)) - - (Math.Abs(r2.Rect.Center.X - Rect.Center.X) + Math.Abs(r2.Rect.Center.Y - Rect.Center.Y)); - }); - - for (int i = 0; i < connectedRooms.Count; i++) - { - connectedRooms[i].CalculateDistanceFromEntrance(currentDist + 1 + i, rooms, corridors); - } - } - } - } -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Ruins/Corridor.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Ruins/Corridor.cs deleted file mode 100644 index 7380256ef..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Ruins/Corridor.cs +++ /dev/null @@ -1,210 +0,0 @@ -using Microsoft.Xna.Framework; -using System; -using System.Collections.Generic; - -namespace Barotrauma.RuinGeneration -{ - - class Corridor : RuinShape - { - private readonly bool isHorizontal; - - public bool IsHorizontal - { - get { return isHorizontal; } - } - - public BTRoom[] ConnectedRooms - { - get; - private set; - } - - public Corridor(Rectangle rect) - { - this.rect = rect; - - isHorizontal = rect.Width > rect.Height; - } - - public Corridor(BTRoom room, int width, List corridors) - { - System.Diagnostics.Debug.Assert(room.Adjacent != null); - - ConnectedRooms = new BTRoom[2]; - ConnectedRooms[0] = room; - ConnectedRooms[1] = room.Adjacent; - - Rectangle room1, room2; - - room1 = room.Rect; - room2 = room.Adjacent.Rect; - - isHorizontal = (room1.Right <= room2.X || room2.Right <= room1.X); - - //use the leaves as starting points for the corridor - if (room.SubRooms != null) - { - var leaves1 = room.GetLeaves(); - var leaves2 = room.Adjacent.GetLeaves(); - - var suitableLeaves = GetSuitableLeafRooms(leaves1, leaves2, width, isHorizontal); - if (suitableLeaves == null || suitableLeaves.Length < 2) - { - // No suitable leaves found due to intersections - //DebugConsole.ThrowError("Error while generating ruins. Could not find a suitable position for a corridor. The width of the corridors may be too large compared to the sizes of the rooms."); - return; - } - else - { - ConnectedRooms[0] = suitableLeaves[0]; - ConnectedRooms[1] = suitableLeaves[1]; - } - } - else - { - rect = CalculateRectangle(room1, room2, width, isHorizontal); - if (rect.Width <= 0 || rect.Height <= 0) - { - DebugConsole.ThrowError("Error while generating ruins. Attempted to create a corridor with a width or height of <= 0"); - return; - } - } - - room.Corridor = this; - room.Adjacent.Corridor = this; - - for (int i = corridors.Count - 1; i >= 0; i--) - { - var corridor = corridors[i]; - - if (corridor.rect.Intersects(this.rect)) - { - if (isHorizontal && corridor.isHorizontal) - { - if (this.rect.Width < corridor.rect.Width) - return; - else - corridors.RemoveAt(i); - } - else if (!isHorizontal && !corridor.isHorizontal) - { - if (this.rect.Height < corridor.rect.Height) - return; - else - corridors.RemoveAt(i); - } - } - } - - corridors.Add(this); - } - - public override void CreateWalls() - { - Walls = new List(); - if (IsHorizontal) - { - Walls.Add(new Line(new Vector2(Rect.X, Rect.Y), new Vector2(Rect.Right, Rect.Y))); - Walls.Add(new Line(new Vector2(Rect.X, Rect.Bottom), new Vector2(Rect.Right, Rect.Bottom))); - } - else - { - Walls.Add(new Line(new Vector2(Rect.X, Rect.Y), new Vector2(Rect.X, Rect.Bottom))); - Walls.Add(new Line(new Vector2(Rect.Right, Rect.Y), new Vector2(Rect.Right, Rect.Bottom))); - } - } - - /// - /// Find two rooms which have two face-two-face walls that we can place a corridor in between - /// - /// - private BTRoom[] GetSuitableLeafRooms(List leaves1, List leaves2, int width, bool isHorizontal) - { - int iOffset = Rand.Int(leaves1.Count, Rand.RandSync.Server); - int jOffset = Rand.Int(leaves2.Count, Rand.RandSync.Server); - - for (int iCount = 0; iCount < leaves1.Count; iCount++) - { - int i = (iCount + iOffset) % leaves1.Count; - - for (int jCount = 0; jCount < leaves2.Count; jCount++) - { - int j = (jCount + jOffset) % leaves2.Count; - - if (isHorizontal) - { - if (leaves1[i].Rect.Y > leaves2[j].Rect.Bottom - width) continue; - if (leaves1[i].Rect.Bottom < leaves2[j].Rect.Y + width) continue; - } - else - { - if (leaves1[i].Rect.X > leaves2[j].Rect.Right - width) continue; - if (leaves1[i].Rect.Right < leaves2[j].Rect.X + width) continue; - } - - // Check if the given corridor rect would intersect over a third room - if (CheckForIntersection(leaves1[i], leaves2[j], leaves1, leaves2, width, isHorizontal)) continue; - - return new BTRoom[] { leaves1[i], leaves2[j] }; - } - } - - return null; - } - - private bool CheckForIntersection(BTRoom potential1, BTRoom potential2, List leaves1, List leaves2, int width, bool isHorizontal) - { - Rectangle potentialCorridorRectangle = CalculateRectangle(potential1.Rect, potential2.Rect, width, isHorizontal); - - if (potentialCorridorRectangle.Width <= 0 || potentialCorridorRectangle.Height <= 0) return true; // Invalid rectangle - - for (int i = 0; i < leaves1.Count; i++) - { - if (leaves1[i] == potential1) continue; - if (potentialCorridorRectangle.Intersects(leaves1[i].Rect)) return true; - } - - for (int i = 0; i < leaves2.Count; i++) - { - if (leaves2[i] == potential2) continue; - if (potentialCorridorRectangle.Intersects(leaves2[i].Rect)) return true; - } - - rect = potentialCorridorRectangle; // Save the rectangle that passes the test - return false; - } - - private Rectangle CalculateRectangle(Rectangle rect1, Rectangle rect2, int width, bool isHorizontal) - { - if (isHorizontal) - { - int left = Math.Min(rect1.Right, rect2.Right); - int right = Math.Max(rect1.X, rect2.X); - - int top = Math.Max(rect1.Y, rect2.Y); - //int bottom = Math.Min(room1.Bottom, room2.Bottom); - int yPos = top;//Rand.Range(top, bottom - width, Rand.RandSync.Server); - - return new Rectangle(left, yPos, right - left, width); - } - else if (rect1.Y > rect2.Bottom || rect2.Y > rect1.Bottom) - { - int left = Math.Max(rect1.X, rect2.X); - int right = Math.Min(rect1.Right, rect2.Right); - - int top = Math.Min(rect1.Bottom, rect2.Bottom); - int bottom = Math.Max(rect1.Y, rect2.Y); - - int xPos = Rand.Range(left, right - width, Rand.RandSync.Server); - - return new Rectangle(xPos, top, width, bottom - top); - } - else - { - DebugConsole.ThrowError("wat"); - return new Rectangle(); - } - } - } -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Ruins/RuinGenerationParams.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Ruins/RuinGenerationParams.cs index d84956923..be359698a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Ruins/RuinGenerationParams.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Ruins/RuinGenerationParams.cs @@ -18,9 +18,9 @@ namespace Barotrauma.RuinGeneration Wall, Back, Door, Hatch, Prop } - class RuinGenerationParams : ISerializableEntity + class RuinGenerationParams : OutpostGenerationParams { - public static List List + public static List RuinParams { get { @@ -34,115 +34,27 @@ namespace Barotrauma.RuinGeneration private static List paramsList; - private string filePath; - - private readonly List roomTypeList; - - public string Name => "RuinGenerationParams"; + private readonly string filePath; + + public override string Name => "RuinGenerationParams"; - [Serialize("5000,5000", false), Editable] - public Point SizeMin + + private RuinGenerationParams(XElement element, string filePath) : base(element, filePath) { - get; - set; - } - [Serialize("8000,8000", false), Editable] - public Point SizeMax - { - get; - set; + this.filePath = filePath; } - [Serialize(3, false, description: "The ruin generation algorithm \"splits\" the ruin area into two, splits these areas again, repeats this for some number of times and creates a room at each of the final split areas. This is value determines the minimum number of times the split is done."), Editable(MinValueInt = 1, MaxValueInt = 10)] - public int RoomDivisionIterationsMin - { - get; - set; - } - - [Serialize(4, false, description: "The ruin generation algorithm \"splits\" the ruin area into two, splits these areas again, repeats this for some number of times and creates a room at each of the final split areas. This is value determines the maximum number of times the split is done."), Editable(MinValueInt = 1, MaxValueInt = 10)] - public int RoomDivisionIterationsMax - { - get; - set; - } - - [Serialize(0.5f, false, description: "The probability for the split algorithm to split the area vertically. High values tend to create tall, vertical rooms, and low values wide, horizontal rooms."), Editable(MinValueFloat = 0.1f, MaxValueFloat = 0.9f)] - public float VerticalSplitProbability - { - get; - set; - } - - [Serialize(400, false, description: "The splitting algorithm attempts to keep the width of the split areas larger than this. If the width of the split areas would be smaller than this after a vertical split, the algorithm would do a horizontal split."), Editable] - public int MinSplitWidth - { - get; - set; - } - [Serialize(400, false, description: "The splitting algorithm attempts to keep the height of the split areas larger than this. If the height of the split areas would be smaller than this after a vertical split, the algorithm would do a horizontal split."), Editable] - public int MinSplitHeight - { - get; - set; - } - - [Serialize("0.5,0.9", false, description: "The minimum and maximum width of a room relative to the areas created by the split algorithm."), Editable] - public Vector2 RoomWidthRange - { - get; - set; - } - [Serialize("0.5,0.9", false, description: "The minimum and maximum height of a room relative to the areas created by the split algorithm."), Editable] - public Vector2 RoomHeightRange - { - get; - set; - } - - [Serialize("200,256", false, description: "The minimum and maximum width of the corridors between rooms."), Editable] - public Point CorridorWidthRange - { - get; - set; - } - - public Dictionary SerializableProperties - { - get; - private set; - } = new Dictionary(); - - public IEnumerable RoomTypeList - { - get { return roomTypeList; } - } - - private RuinGenerationParams(XElement element) - { - roomTypeList = new List(); - - if (element != null) - { - foreach (XElement subElement in element.Elements()) - { - roomTypeList.Add(new RuinRoom(subElement)); - } - } - SerializableProperties = SerializableProperty.DeserializeProperties(this, element); - } - - public static RuinGenerationParams GetRandom() + public static RuinGenerationParams GetRandom(Rand.RandSync randSync = Rand.RandSync.Server) { if (paramsList == null) { LoadAll(); } if (paramsList.Count == 0) { DebugConsole.ThrowError("No ruin configuration files found in any content package."); - return new RuinGenerationParams(null); + return new RuinGenerationParams(null, null); } - return paramsList[Rand.Int(paramsList.Count, Rand.RandSync.Server)]; + return paramsList[Rand.Int(paramsList.Count, randSync)]; } private static void LoadAll() @@ -151,23 +63,24 @@ namespace Barotrauma.RuinGeneration foreach (ContentFile configFile in GameMain.Instance.GetFilesOfType(ContentType.RuinConfig)) { XDocument doc = XMLExtensions.TryLoadXml(configFile.Path); - if (doc == null) { continue; } - var mainElement = doc.Root; - if (doc.Root.IsOverride()) + if (doc?.Root == null) { continue; } + + foreach (XElement subElement in doc.Root.Elements()) { - mainElement = doc.Root.FirstElement(); - paramsList.Clear(); - DebugConsole.NewMessage($"Overriding all ruin generation parameters using the file {configFile.Path}.", Color.Yellow); + var mainElement = subElement; + if (subElement.IsOverride()) + { + mainElement = subElement.FirstElement(); + paramsList.Clear(); + DebugConsole.NewMessage($"Overriding all ruin generation parameters using the file {configFile.Path}.", Color.Yellow); + } + else if (paramsList.Any()) + { + DebugConsole.NewMessage($"Adding additional ruin generation parameters from file '{configFile.Path}'"); + } + var newParams = new RuinGenerationParams(mainElement, configFile.Path); + paramsList.Add(newParams); } - else if (paramsList.Any()) - { - DebugConsole.NewMessage($"Adding additional ruin generation parameters from file '{configFile.Path}'"); - } - var newParams = new RuinGenerationParams(mainElement) - { - filePath = configFile.Path - }; - paramsList.Add(newParams); } } @@ -185,11 +98,11 @@ namespace Barotrauma.RuinGeneration NewLineOnAttributes = true }; - foreach (RuinGenerationParams generationParams in List) + foreach (RuinGenerationParams generationParams in RuinParams) { foreach (ContentFile configFile in GameMain.Instance.GetFilesOfType(ContentType.RuinConfig)) { - if (configFile.Path != generationParams.filePath) continue; + if (configFile.Path != generationParams.filePath) { continue; } XDocument doc = XMLExtensions.TryLoadXml(configFile.Path); if (doc == null) { continue; } @@ -205,298 +118,4 @@ namespace Barotrauma.RuinGeneration } } } - - class RuinRoom : ISerializableEntity - { - public enum RoomPlacement - { - Any, - First, - Last - } - - public string Name - { - get; - private set; - } - - [Serialize(1.0f, false), Editable(MinValueFloat = 0.0f, MaxValueFloat = 10.0f)] - public float Commonness { get; private set; } - - public Dictionary SerializableProperties - { - get; - private set; - } = new Dictionary(); - - [Serialize(RoomPlacement.Any, false), Editable] - public RoomPlacement Placement - { - get; - set; - } - - [Serialize(0, false), Editable] - public int PlacementOffset - { - get; - set; - } - - [Serialize(false, false), Editable] - public bool IsCorridor - { - get; - set; - } - - [Serialize(1.0f, false), Editable] - public float MinWaterAmount - { - get; - set; - } - [Serialize(1.0f, false), Editable] - public float MaxWaterAmount - { - get; - set; - } - - private List entityList = new List(); - - public RuinRoom(XElement element) - { - SerializableProperties = SerializableProperty.DeserializeProperties(this, element); - - Name = element.GetAttributeString("name", ""); - - if (element != null) - { - int groupIndex = 0; - LoadEntities(element, ref groupIndex); - } - - void LoadEntities(XElement element2, ref int groupIndex) - { - foreach (XElement subElement in element2.Elements()) - { - if (subElement.Name.ToString().Equals("chooseone", StringComparison.OrdinalIgnoreCase)) - { - groupIndex++; - LoadEntities(subElement, ref groupIndex); - } - else - { - entityList.Add(new RuinEntityConfig(subElement) { SingleGroupIndex = groupIndex }); - } - } - } - } - - public RuinEntityConfig GetRandomEntity(RuinEntityType type, Alignment alignment) - { - var matchingEntities = entityList.FindAll(rs => - rs.Type == type && - rs.Alignment.HasFlag(alignment)); - - if (!matchingEntities.Any()) return null; - - return ToolBox.SelectWeightedRandom( - matchingEntities, - matchingEntities.Select(s => s.Commonness).ToList(), - Rand.RandSync.Server); - } - - public List GetPropList(RuinShape room, Rand.RandSync randSync) - { - Dictionary> propGroups = new Dictionary>(); - foreach (RuinEntityConfig entityConfig in entityList) - { - if (entityConfig.Type != RuinEntityType.Prop) { continue; } - if (room.Rect.Width < entityConfig.MinRoomSize.X || room.Rect.Height < entityConfig.MinRoomSize.Y) { continue; } - if (room.Rect.Width > entityConfig.MaxRoomSize.X || room.Rect.Height > entityConfig.MaxRoomSize.Y) { continue; } - if (!propGroups.ContainsKey(entityConfig.SingleGroupIndex)) - { - propGroups[entityConfig.SingleGroupIndex] = new List(); - } - propGroups[entityConfig.SingleGroupIndex].Add(entityConfig); - } - - List props = new List(); - foreach (KeyValuePair> propGroup in propGroups) - { - if (propGroup.Key == 0) - { - props.AddRange(propGroup.Value); - } - else - { - props.Add(propGroup.Value[Rand.Int(propGroup.Value.Count, randSync)]); - } - } - return props; - } - } - - class RuinEntityConfig : ISerializableEntity - { - public readonly MapEntityPrefab Prefab; - - public enum RelativePlacement - { - SameRoom, - NextRoom, - NextCorridor, - PreviousRoom, - PreviousCorridor, - FirstRoom, - FirstCorridor, - LastRoom, - LastCorridor - } - - public class EntityConnection - { - //which type of room to search for the item to connect to - //sameroom, nextroom, previousroom, firstroom and lastroom are also valid - public string RoomName - { - get; - private set; - } - - public string TargetEntityIdentifier - { - get; - private set; - } - - //Identifier of the item to run the wire from. Only needed in item assemblies to determine which item in the assembly to use. - public string SourceEntityIdentifier - { - get; - private set; - } - - //if set, the connection is done by running a wire from - //(Pair.First = the name of the connection in this item) to (Pair.Second = the name of the connection in the target item) - public Pair WireConnection - { - get; - private set; - } - - public EntityConnection(XElement element) - { - RoomName = element.GetAttributeString("roomname", ""); - TargetEntityIdentifier = element.GetAttributeString("targetentity", ""); - SourceEntityIdentifier = element.GetAttributeString("sourceentity", ""); - foreach (XElement subElement in element.Elements()) - { - if (subElement.Name.ToString().Equals("wire", StringComparison.OrdinalIgnoreCase)) - { - WireConnection = new Pair( - subElement.GetAttributeString("from", ""), - subElement.GetAttributeString("to", "")); - } - } - } - } - - [Serialize(Alignment.Bottom, false), Editable] - public Alignment Alignment { get; private set; } - - [Serialize("0,0", false, description: "Minimum offset from the anchor position, relative to the size of the room." + - " For example, a value of { -0.5,0 } with a Bottom alignment would mean the entity can be placed anywhere between the bottom-left corner of the room and bottom-center."), Editable] - public Vector2 MinOffset { get; private set; } - [Serialize("0,0", false, description: "Maximum offset from the anchor position, relative to the size of the room." + - " For example, a value of { 0.5,0 } with a Bottom alignment would mean the entity can be placed anywhere between the bottom-right corner of the room and bottom-center."), Editable] - public Vector2 MaxOffset { get; private set; } - - [Serialize(RuinEntityType.Prop, false), Editable] - public RuinEntityType Type { get; private set; } - - [Serialize(false, false), Editable] - public bool Expand { get; private set; } - - [Serialize(RelativePlacement.SameRoom, false), Editable] - public RelativePlacement PlacementRelativeToParent { get; private set; } - - [Serialize(1.0f, false), Editable(MinValueFloat = 0.0f, MaxValueFloat = 10.0f)] - public float Commonness { get; private set; } - - [Serialize(1, false)] - public int MinAmount { get; private set; } - [Serialize(1, false)] - public int MaxAmount { get; private set; } - - [Serialize("0,0", false)] - public Point MinRoomSize { get; private set; } - - [Serialize("100000,100000", false)] - public Point MaxRoomSize { get; private set; } - - [Serialize("", false)] - public string TargetContainer { get; private set; } - - public List EntityConnections { get; private set; } = new List(); - - - public int SingleGroupIndex; - - private readonly List childEntities = new List(); - - public IEnumerable ChildEntities - { - get { return childEntities; } - } - - public string Name => Prefab == null ? "null" : Prefab.Name; - - public Dictionary SerializableProperties - { - get; - private set; - } = new Dictionary(); - - public RuinEntityConfig(XElement element) - { - string name = element.GetAttributeString("prefab", ""); - Prefab = MapEntityPrefab.Find(name: null, identifier: name); - - if (Prefab == null) - { - DebugConsole.ThrowError("Loading ruin entity config failed - map entity prefab \"" + name + "\" not found."); - return; - } - - SerializableProperties = SerializableProperty.DeserializeProperties(this, element); - - int gIndex = 0; - LoadChildren(element, ref gIndex); - - void LoadChildren(XElement element2, ref int groupIndex) - { - foreach (XElement subElement in element2.Elements()) - { - switch (subElement.Name.ToString().ToLowerInvariant()) - { - case "connection": - case "entityconnection": - EntityConnections.Add(new EntityConnection(subElement)); - break; - case "chooseone": - groupIndex++; - LoadChildren(subElement, ref groupIndex); - break; - default: - childEntities.Add(new RuinEntityConfig(subElement) { SingleGroupIndex = groupIndex }); - break; - } - } - } - } - } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Ruins/RuinGenerator.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Ruins/RuinGenerator.cs index 4b6b241da..6b2fdba38 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Ruins/RuinGenerator.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Ruins/RuinGenerator.cs @@ -1,212 +1,16 @@ -using FarseerPhysics; +using Barotrauma.Extensions; using Microsoft.Xna.Framework; -using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using Voronoi2; -using Barotrauma.Extensions; -using Barotrauma.Items.Components; namespace Barotrauma.RuinGeneration { - abstract class RuinShape - { - protected Rectangle rect; - - public Rectangle Rect - { - get { return rect; } - } - - public int DistanceFromEntrance - { - get; - set; - } - - public Vector2 Center - { - get { return rect.Center.ToVector2(); } - } - - public RuinRoom RoomType; - - public List Walls; - - public virtual void CreateWalls() { } - - public Alignment GetLineAlignment(Line line) - { - if (line.IsHorizontal) - { - if (line.A.Y > rect.Center.Y && line.B.Y > rect.Center.Y) - { - return Alignment.Bottom; - } - else if (line.A.Y < rect.Center.Y && line.B.Y < rect.Center.Y) - { - return Alignment.Top; - } - } - else - { - if (line.A.X < rect.Center.X && line.B.X < rect.Center.X) - { - return Alignment.Left; - } - else if (line.A.X > rect.Center.X && line.B.X > rect.Center.X) - { - return Alignment.Right; - } - } - - return Alignment.Center; - } - - /// - /// Goes through all the walls of the ruin shape and clips off parts that are inside the rectangle - /// - public void SplitWalls(Rectangle rectangle) - { - List newLines = new List(); - - foreach (Line line in Walls) - { - if (!line.IsHorizontal) //vertical line - { - //line doesn't intersect the rectangle - if (rectangle.X > line.A.X || rectangle.Right < line.A.X || - rectangle.Y > line.B.Y || rectangle.Bottom < line.A.Y) - { - newLines.Add(line); - } - //line completely inside the rectangle, no need to create a wall at all - else if (line.A.Y >= rectangle.Y && line.B.Y <= rectangle.Bottom) - { - continue; - } - //point A is within the rectangle -> cut a portion from the top of the line - else if (line.A.Y >= rectangle.Y && line.A.Y <= rectangle.Bottom) - { - newLines.Add(new Line(new Vector2(line.A.X, rectangle.Bottom), line.B)); - } - //point B is within the rectangle -> cut a portion from the bottom of the line - else if (line.B.Y >= rectangle.Y && line.B.Y <= rectangle.Bottom) - { - newLines.Add(new Line(line.A, new Vector2(line.A.X, rectangle.Y))); - } - //rect is in between the lines -> split the line into two - else - { - newLines.Add(new Line(line.A, new Vector2(line.A.X, rectangle.Y))); - newLines.Add(new Line(new Vector2(line.A.X, rectangle.Bottom), line.B)); - } - } - else - { - //line doesn't intersect the rectangle - if (rectangle.X > line.B.X || rectangle.Right < line.A.X || - rectangle.Y > line.A.Y || rectangle.Bottom < line.A.Y) - { - - newLines.Add(line); - } - else if (line.A.X >= rectangle.X && line.B.X <= rectangle.Right) - { - continue; - } - //point A is within the rectangle -> cut a portion from the left side of the line - else if (line.A.X >= rectangle.X && line.A.X <= rectangle.Right) - { - newLines.Add(new Line(new Vector2(rectangle.Right, line.A.Y), line.B)); - } - //point B is within the rectangle -> cut a portion from the right side of the line - else if (line.B.X >= rectangle.X && line.B.X <= rectangle.Right) - { - newLines.Add(new Line(line.A, new Vector2(rectangle.X, line.A.Y))); - } - //rect is in between the lines -> split the line into two - else - { - newLines.Add(new Line(line.A, new Vector2(rectangle.X, line.A.Y))); - newLines.Add(new Line(new Vector2(rectangle.Right, line.A.Y), line.B)); - } - } - } - - Walls = newLines; - } - - public void MirrorX(Vector2 mirrorOrigin) - { - rect.X = (int)(mirrorOrigin.X + (mirrorOrigin.X - rect.Right)); - for (int i = 0; i < Walls.Count; i++) - { - Walls[i].A = new Vector2(mirrorOrigin.X + (mirrorOrigin.X - Walls[i].A.X), Walls[i].A.Y); - Walls[i].B = new Vector2(mirrorOrigin.X + (mirrorOrigin.X - Walls[i].B.X), Walls[i].B.Y); - - if (Walls[i].B.X < Walls[i].A.X) - { - var temp = Walls[i].A.X; - Walls[i].A.X = Walls[i].B.X; - Walls[i].B.X = temp; - } - } - } - } - - class Line - { - public Vector2 A, B; - - public float Radius; - - public bool IsHorizontal - { - get { return Math.Abs(A.Y - B.Y) < Math.Abs(A.X - B.X); } - } - - public Line(Vector2 a, Vector2 b) - { - Debug.Assert(a.X <= b.X); - Debug.Assert(a.Y <= b.Y); - - A = a; - B = b; - } - } - partial class Ruin { - private List rooms; - private List corridors; + private readonly RuinGenerationParams generationParams; - private List walls; - - private List allShapes; - - private RuinGenerationParams generationParams; - - private BTRoom entranceRoom; - - private List ruinEntities = new List(); - private List doors = new List(); - - public IEnumerable RuinEntities - { - get { return ruinEntities; } - } - - public List RuinShapes - { - get { return allShapes; } - } - - public List Walls - { - get { return walls; } - } + public List PathCells = new List(); public Rectangle Area { @@ -214,1107 +18,54 @@ namespace Barotrauma.RuinGeneration private set; } - public Ruin(VoronoiCell closestPathCell, List caveCells, RuinGenerationParams generationParams, Rectangle area, bool mirror = false) + public Submarine Submarine + { + get; + private set; + } + + public Ruin(Level level, RuinGenerationParams generationParams, Location location, Point position, bool mirror = false) + : this(level, generationParams, location.Type, position, mirror) + { + } + + public Ruin(Level level, RuinGenerationParams generationParams, LocationType locationType, Point position, bool mirror = false) { this.generationParams = generationParams; - Area = area; - corridors = new List(); - rooms = new List(); - walls = new List(); - allShapes = new List(); - Generate(closestPathCell, caveCells, area, mirror); + Generate(level, locationType, position, mirror); } - public void Generate(VoronoiCell closestPathCell, List caveCells, Rectangle area, bool mirror = false) + public void Generate(Level level, LocationType locationType, Point position, bool mirror = false) { - corridors.Clear(); - rooms.Clear(); - - int iterations = Rand.Range(generationParams.RoomDivisionIterationsMin, generationParams.RoomDivisionIterationsMax, Rand.RandSync.Server); - float verticalProbability = generationParams.VerticalSplitProbability; - - BTRoom baseRoom = new BTRoom(area); - rooms = new List { baseRoom }; - - for (int i = 0; i < iterations; i++) - { - rooms.ForEach(l => l.Split(0.3f, verticalProbability, generationParams.MinSplitWidth, generationParams.MinSplitHeight)); - rooms = baseRoom.GetLeaves(); - } - - foreach (BTRoom leaf in rooms) - { - leaf.Scale - ( - new Vector2( - Rand.Range(generationParams.RoomWidthRange.X, generationParams.RoomWidthRange.Y, Rand.RandSync.Server), - Rand.Range(generationParams.RoomHeightRange.X, generationParams.RoomHeightRange.Y, Rand.RandSync.Server)) - ); - } - - baseRoom.GenerateCorridors(generationParams.CorridorWidthRange.X, generationParams.CorridorWidthRange.Y, corridors); - - walls = new List(); - rooms.ForEach(leaf => { leaf.CreateWalls(); }); - - //--------------------------- - - float shortestDistance = 0.0f; - foreach (BTRoom leaf in rooms) - { - Vector2 leafPos = leaf.Rect.Center.ToVector2(); - if (mirror) - { - leafPos.X = area.Center.X + (area.Center.X - leafPos.X); - } - float distance = Vector2.Distance(leafPos, closestPathCell.Center); - if (entranceRoom == null || distance < shortestDistance) - { - entranceRoom = leaf; - shortestDistance = distance; - } - } - - rooms.Remove(entranceRoom); - - //--------------------------- - - foreach (BTRoom leaf in rooms) - { - foreach (Corridor corridor in corridors) - { - leaf.SplitWalls(corridor.Rect); - } - - walls.AddRange(leaf.Walls); - } - - foreach (Corridor corridor in corridors) - { - corridor.CreateWalls(); - - foreach (BTRoom leaf in rooms) - { - corridor.SplitWalls(leaf.Rect); - } - - foreach (Corridor corridor2 in corridors) - { - if (corridor == corridor2) continue; - corridor.SplitWalls(corridor2.Rect); - } - walls.AddRange(corridor.Walls); - } - - BTRoom.CalculateDistancesFromEntrance(entranceRoom, rooms, corridors); - GenerateRuinEntities(caveCells, area, mirror); - } - - public class RuinEntity - { - public readonly RuinEntityConfig Config; - public readonly MapEntity Entity; - public readonly MapEntity Parent; - public readonly RuinShape Room; - - public RuinEntity(RuinEntityConfig config, MapEntity entity, RuinShape room, MapEntity parent = null) - { - Config = config; - Entity = entity; - Room = room; - Parent = parent; - } - } - - private void GenerateRuinEntities(List caveCells, Rectangle ruinArea, bool mirror) - { - var entityGrid = Hull.GenerateEntityGrid(new Rectangle(ruinArea.X, ruinArea.Y + ruinArea.Height, ruinArea.Width, ruinArea.Height)); - doors.Clear(); - - allShapes = new List(rooms); - allShapes.AddRange(corridors); + Submarine = OutpostGenerator.Generate(generationParams, locationType, onlyEntrance: false); + Submarine.Info.Name = $"Ruin ({level.Seed})"; + Submarine.Info.Type = SubmarineType.Ruin; + Submarine.TeamID = CharacterTeamType.None; + Submarine.SetPosition(position.ToVector2()); if (mirror) { - foreach (RuinShape shape in allShapes) - { - shape.MirrorX(ruinArea.Center.ToVector2()); - } + Submarine.FlipX(); } - int maxRoomDistanceFromEntrance = rooms.Max(s => s.DistanceFromEntrance); - int maxCorridorDistanceFromEntrance = corridors.Max(s => s.DistanceFromEntrance); + Rectangle worldBorders = Submarine.Borders; + worldBorders.Location += Submarine.WorldPosition.ToPoint(); + Area = new Rectangle(worldBorders.X, worldBorders.Y - worldBorders.Height, worldBorders.Width, worldBorders.Height); - //assign the room types for the first and last rooms - foreach (RuinRoom roomType in generationParams.RoomTypeList) + List subWaypoints = WayPoint.WayPointList.FindAll(wp => wp.Submarine == Submarine); + int interestingPosCount = 0; + foreach (WayPoint wp in subWaypoints) { - RuinShape selectedRoom = null; - switch (roomType.Placement) - { - case RuinRoom.RoomPlacement.First: - //find the room nearest to the entrance - //there may be multiple ones, choose one that hasn't been assigned yet - selectedRoom = roomType.IsCorridor ? FindFirstRoom(corridors, r => r.RoomType == null) : FindFirstRoom(rooms, r => r.RoomType == null); - - break; - case RuinRoom.RoomPlacement.Last: - //find the room furthest to the entrance - //there may be multiple ones, choose one that hasn't been assigned yet - selectedRoom = roomType.IsCorridor ? FindLastRoom(corridors, r => r.RoomType == null) : FindLastRoom(rooms, r => r.RoomType == null); - break; - } - if (selectedRoom == null) continue; - - //step forwards/backwards from the selected room according to the placement offset - for (int i = 0; i < Math.Abs(roomType.PlacementOffset); i++) - { - selectedRoom = FindNearestRoom( - selectedRoom, - roomType.IsCorridor ? corridors : (IEnumerable)rooms, - roomType.PlacementOffset, - r => r.RoomType == null); - } - - if (selectedRoom != null) selectedRoom.RoomType = roomType; + if (wp.SpawnType != SpawnType.Enemy) { continue; } + level.PositionsOfInterest.Add(new Level.InterestingPosition(wp.WorldPosition.ToPoint(), Level.PositionType.Ruin, this)); + interestingPosCount++; } - //go through the unassigned rooms - foreach (RuinShape room in allShapes) + if (interestingPosCount == 0) { - if (room.RoomType != null) continue; - - room.RoomType = generationParams.RoomTypeList.GetRandom(rt => - rt.IsCorridor == room is Corridor && - rt.Placement == RuinRoom.RoomPlacement.Any, - Rand.RandSync.Server); - - if (room.RoomType == null) - { - DebugConsole.ThrowError("Could not find a suitable room type for a room (is corridor: " + (room is Corridor) + ")"); - } - } - - List hullRects = new List(allShapes.Select(s => s.Rect)); - - //split intersecting hulls into multiple parts to prevent overlaps - for (int i = 0; i < hullRects.Count; i++) - { - if (hullRects[i].Width <= 0 || hullRects[i].Height <= 0) continue; - for (int j = 0; j < hullRects.Count; j++) - { - if (i == j) continue; - if (hullRects[j].Width <= 0 || hullRects[j].Height <= 0) continue; - if (!hullRects[i].Intersects(hullRects[j])) continue; - - //hull i goes through hull j vertically - if (hullRects[i].X >= hullRects[j].X && hullRects[i].Right <= hullRects[j].Right && - hullRects[i].Y <= hullRects[j].Y && hullRects[i].Bottom >= hullRects[j].Bottom) - { - Rectangle rectLeft = new Rectangle(hullRects[j].X, hullRects[j].Y, hullRects[i].X - hullRects[j].X, hullRects[j].Height); - Rectangle rectRight = new Rectangle(hullRects[i].Right, hullRects[j].Y, hullRects[j].Right - hullRects[i].Right, hullRects[j].Height); - hullRects[j] = rectLeft; - hullRects.Add(rectRight); - } - else if //hull i goes through hull j horizontally - (hullRects[i].Y >= hullRects[j].Y && hullRects[i].Bottom <= hullRects[j].Bottom && - hullRects[i].X <= hullRects[j].X && hullRects[i].Right >= hullRects[j].Right) - { - Rectangle rectBottom = new Rectangle(hullRects[j].X, hullRects[j].Y, hullRects[j].Width, hullRects[i].Y - hullRects[j].Y); - Rectangle rectTop = new Rectangle(hullRects[j].X, hullRects[i].Bottom, hullRects[j].Width, hullRects[j].Bottom - hullRects[i].Bottom); - hullRects[j] = rectBottom; - hullRects.Add(rectTop); - } - //upper side of hull i is inside hull j - else if (hullRects[j].Contains(hullRects[i].Location) && hullRects[j].Contains(new Vector2(hullRects[i].Right, hullRects[i].Y))) - { - hullRects[i] = new Rectangle(hullRects[i].X, hullRects[j].Bottom, hullRects[i].Width, hullRects[i].Bottom - hullRects[j].Bottom); - } - //lower side of hull i is inside hull j - else if (hullRects[j].Contains(new Vector2(hullRects[i].X, hullRects[i].Bottom)) && hullRects[j].Contains(new Vector2(hullRects[i].Right, hullRects[i].Bottom))) - { - hullRects[i] = new Rectangle(hullRects[i].X, hullRects[i].Y, hullRects[i].Width, hullRects[j].Y - hullRects[i].Y); - } - //left side of hull i is inside hull j - else if (hullRects[j].Contains(hullRects[i].Location) && hullRects[j].Contains(new Vector2(hullRects[i].X, hullRects[i].Bottom))) - { - hullRects[i] = new Rectangle(hullRects[j].X, hullRects[i].Y, hullRects[i].Right - hullRects[j].X, hullRects[i].Height); - } - //right side of hull i is inside hull j - else if (hullRects[j].Contains(new Vector2(hullRects[i].Right, hullRects[i].Y)) && hullRects[j].Contains(new Vector2(hullRects[i].Right, hullRects[i].Bottom))) - { - hullRects[i] = new Rectangle(hullRects[i].X, hullRects[i].Y, hullRects[j].X - hullRects[i].X, hullRects[i].Height); - } - } - } - - foreach (RuinShape room in allShapes) - { - if (room.RoomType == null) continue; - //generate walls -------------------------------------------------------------- - foreach (Line wall in room.Walls) - { - var ruinEntityConfig = room.RoomType.GetRandomEntity(RuinEntityType.Wall, room.GetLineAlignment(wall)); - if (ruinEntityConfig == null) continue; - - wall.Radius = (wall.A.X == wall.B.X) ? - (ruinEntityConfig.Prefab as StructurePrefab).Size.X * 0.5f : - (ruinEntityConfig.Prefab as StructurePrefab).Size.Y * 0.5f; - - Rectangle rect = new Rectangle( - (int)(wall.A.X - wall.Radius), - (int)(wall.B.Y + wall.Radius), - (int)((wall.B.X - wall.A.X) + wall.Radius * 2.0f), - (int)((wall.B.Y - wall.A.Y) + wall.Radius * 2.0f)); - - //cut a section off from both ends of a horizontal wall to get nicer looking corners - if (wall.A.Y == wall.B.Y) - { - rect.Inflate(-32, 0); - if (rect.Width < Submarine.GridSize.X) continue; - } - - var structure = new Structure(rect, ruinEntityConfig.Prefab as StructurePrefab, null) - { - ShouldBeSaved = false - }; - structure.SetCollisionCategory(Physics.CollisionLevel); - CreateChildEntities(ruinEntityConfig, structure, room); - ruinEntities.Add(new RuinEntity(ruinEntityConfig, structure, room)); - } - - //generate backgrounds -------------------------------------------------------------- - var backgroundConfig = room.RoomType.GetRandomEntity(RuinEntityType.Back, Alignment.Center); - if (backgroundConfig != null) - { - Rectangle backgroundRect = new Rectangle(room.Rect.X, room.Rect.Y + room.Rect.Height, room.Rect.Width, room.Rect.Height); - var backgroundStructure = new Structure(backgroundRect, (backgroundConfig.Prefab as StructurePrefab), null) - { - ShouldBeSaved = false - }; - CreateChildEntities(backgroundConfig, backgroundStructure, room); - ruinEntities.Add(new RuinEntity(backgroundConfig, backgroundStructure, room)); - } - - var submarineBlocker = GameMain.World.CreateRectangle( - ConvertUnits.ToSimUnits(room.Rect.Width), - ConvertUnits.ToSimUnits(room.Rect.Height), - 1, ConvertUnits.ToSimUnits(room.Center)); - - submarineBlocker.BodyType = BodyType.Static; - submarineBlocker.CollisionCategories = Physics.CollisionWall; - submarineBlocker.CollidesWith = Physics.CollisionWall; - submarineBlocker.UserData = "ruinroom"; - - //generate doors -------------------------------------------------------------- - if (room is Corridor corridor) - { - var doorConfig = room.RoomType.GetRandomEntity(corridor.IsHorizontal ? RuinEntityType.Door : RuinEntityType.Hatch, Alignment.Center); - if (corridor != null && doorConfig != null) - { - //find all walls that are parallel to the corridor - var suitableWalls = corridor.IsHorizontal ? - corridor.Walls.FindAll(c => c.A.Y == c.B.Y) : corridor.Walls.FindAll(c => c.A.X == c.B.X); - - if (suitableWalls.Any()) - { - //choose a random wall to place the door next to - Vector2 doorPos = corridor.Center; - var wall = suitableWalls[Rand.Int(suitableWalls.Count, Rand.RandSync.Server)]; - if (corridor.IsHorizontal) - { - doorPos.X = (wall.A.X + wall.B.X) / 2.0f; - } - else - { - doorPos.Y = (wall.A.Y + wall.B.Y) / 2.0f; - } - Item doorItem = null; - if (doorConfig.Prefab is ItemPrefab itemPrefab) - { - doorItem = new Item(doorConfig.Prefab as ItemPrefab, doorPos, null) - { - ShouldBeSaved = false - }; - } - else if (doorConfig.Prefab is ItemAssemblyPrefab itemAssemblyPrefab) - { - var entities = itemAssemblyPrefab.CreateInstance(doorPos, sub: null); - foreach (MapEntity e in entities) - { - if (e is Structure) e.ShouldBeSaved = false; - if (doorItem == null && e is Item item && item.GetComponent() != null) - { - doorItem = item; - } - else - { - ruinEntities.Add(new RuinEntity(doorConfig, e, room)); - } - } - if (doorConfig.Expand) { ExpandEntities(entities); } - //make sure the door gets positioned at the correct place regardless of its position in the item assembly - if (doorItem != null) - { - Vector2 doorOffset = doorPos - doorItem.WorldPosition; - foreach (MapEntity e in entities) - { - e.Move(doorOffset); - Door doorComponent = (e as Item)?.GetComponent(); - if (doorComponent != null && !entities.Contains(doorComponent.LinkedGap)) - { - doorComponent.LinkedGap.Move(doorOffset); - } - } - } - } - else - { - DebugConsole.ThrowError("Failed to create a ruin door. Ruin entity \"" + doorConfig.Name + "\" is marked as a door but is neither an item or an item assembly."); - continue; - } - - Door door = doorItem?.GetComponent(); - if (door == null) - { - DebugConsole.ThrowError("Failed to create a ruin door. Door not found in the ruin entity \"" + doorConfig.Name + "\"."); - continue; - } - - CreateChildEntities(doorConfig, doorItem, corridor); - doors.Add(door); - ruinEntities.Add(new RuinEntity(doorConfig, doorItem, room)); - } - } - } - - //generate props -------------------------------------------------------------- - var props = room.RoomType.GetPropList(room, Rand.RandSync.Server); - foreach (RuinEntityConfig prop in props) - { - int amount = Rand.Range(prop.MinAmount, prop.MaxAmount + 1, Rand.RandSync.Server); - for (int i = 0; i < amount; i++) - { - CreateEntity(prop, room, parent: null); - } - } - } - - foreach (RuinEntity entity in ruinEntities) - { - if (!entity.Room.RoomType.IsCorridor) { continue; } - - Item item = entity.Entity as Item; - Door door = item?.GetComponent(); - if (door == null) { continue; } - - //split the hull the door is inside - for (int i = 0; i < hullRects.Count; i++) - { - Vector2 doorPos = door.Item.WorldPosition; - if (!hullRects[i].Contains(doorPos)) continue; - - if (door.IsHorizontal) - { - Rectangle rectBottom = new Rectangle(hullRects[i].X, hullRects[i].Y, hullRects[i].Width, (int)doorPos.Y - hullRects[i].Y); - Rectangle rectTop = new Rectangle(hullRects[i].X, (int)doorPos.Y, hullRects[i].Width, hullRects[i].Bottom - (int)doorPos.Y); - hullRects[i] = rectBottom; - hullRects.Add(rectTop); - } - else - { - Rectangle rectLeft = new Rectangle(hullRects[i].X, hullRects[i].Y, (int)doorPos.X - hullRects[i].X, hullRects[i].Height); - Rectangle rectRight = new Rectangle((int)doorPos.X, hullRects[i].Y, hullRects[i].Right - (int)doorPos.X, hullRects[i].Height); - hullRects[i] = rectLeft; - hullRects.Add(rectRight); - } - break; - } - } - - //randomize door states (20% open on average) - foreach (Door door in doors) - { - door.IsOpen = Rand.Range(0.0f, 1.0f, Rand.RandSync.Server) < 0.2f; - } - - //create connections between all generated entities --------------------------- - foreach (RuinEntity ruinEntity in ruinEntities) - { - CreateConnections(ruinEntity); - } - - foreach (RuinEntity ruinEntity in ruinEntities) - { - if (ruinEntity.Entity is Item item) - { - foreach (ItemComponent ic in item.Components) - { - // Prevent wiring & interacting - if (ic is ConnectionPanel connectionPanel) - { - connectionPanel.Locked = true; - connectionPanel.CanBeSelected = false; - connectionPanel.Item.ShouldBeSaved = false; - } - // Hide wires - if (ic is Wire wire) - { - wire.Hidden = true; - wire.CanBeSelected = false; - wire.Item.ShouldBeSaved = false; - } - } - } - } - - //create hulls --------------------------- - foreach (Rectangle hullRect in hullRects) - { - if (hullRect.Width <= 0 || hullRect.Height <= 0) continue; - var hull = new Hull(MapEntityPrefab.Find(null, "hull"), - new Rectangle(hullRect.X, hullRect.Y + hullRect.Height, hullRect.Width, hullRect.Height), submarine: null) - { - ParentRuin = this, - ShouldBeSaved = false - }; - RuinShape room = allShapes.Find(s => s.Rect.Contains(hullRect.Center)); - if (room?.RoomType != null) - { - hull.WaterVolume = hull.Volume * Rand.Range(room.RoomType.MinWaterAmount, room.RoomType.MaxWaterAmount, Rand.RandSync.Server); - } - entityGrid.InsertEntity(hull); - } - - //create gaps between hulls --------------------------- - hullRects.Add(entranceRoom.Rect); - for (int i = 0; i < hullRects.Count; i++) - { - if (hullRects[i].Width <= 0 || hullRects[i].Height <= 0) continue; - for (int j = i + 1; j < hullRects.Count; j++) - { - Rectangle? gapRect = null; - if (Math.Abs(hullRects[i].X - hullRects[j].Right) <= 1 && hullYIntersect(hullRects[i], hullRects[j])) - { - gapRect = new Rectangle( - hullRects[i].X - 3, Math.Max(hullRects[i].Y, hullRects[j].Y), - 6, Math.Min(hullRects[i].Bottom, hullRects[j].Bottom) - Math.Max(hullRects[i].Y, hullRects[j].Y)); - } - else if (Math.Abs(hullRects[i].Right - hullRects[j].X) <= 1 && hullYIntersect(hullRects[i], hullRects[j])) - { - gapRect = new Rectangle( - hullRects[i].Right - 3, Math.Max(hullRects[i].Y, hullRects[j].Y), - 6, Math.Min(hullRects[i].Bottom, hullRects[j].Bottom) - Math.Max(hullRects[i].Y, hullRects[j].Y)); - } - else if (Math.Abs(hullRects[i].Y - hullRects[j].Bottom) <= 1 && hullXIntersect(hullRects[i], hullRects[j])) - { - gapRect = new Rectangle( - Math.Max(hullRects[i].X, hullRects[j].X), hullRects[i].Y - 3, - Math.Min(hullRects[i].Right, hullRects[j].Right) - Math.Max(hullRects[i].X, hullRects[j].X), 6); - } - else if (Math.Abs(hullRects[i].Bottom - hullRects[j].Y) <= 1 && hullXIntersect(hullRects[i], hullRects[j])) - { - gapRect = new Rectangle( - Math.Max(hullRects[i].X, hullRects[j].X), hullRects[i].Bottom - 3, - Math.Min(hullRects[i].Right, hullRects[j].Right) - Math.Max(hullRects[i].X, hullRects[j].X), 6); - } - - if (!gapRect.HasValue || gapRect.Value.Width <= 0 || gapRect.Value.Height <= 0) continue; - - //doors create their own gaps, don't create an additional one if there's a door at this - bool doorFound = false; - foreach (Item item in Item.ItemList) - { - var door = item.GetComponent(); - if (door == null) { continue; } - if (Math.Abs(door.Item.WorldPosition.X - gapRect.Value.Center.X) < 5 && - Math.Abs(door.Item.WorldPosition.Y - gapRect.Value.Center.Y) < 5) - { - doorFound = true; - break; - } - } - if (doorFound) { continue; } - - new Gap(new Rectangle(gapRect.Value.X, gapRect.Value.Y + gapRect.Value.Height, gapRect.Value.Width, gapRect.Value.Height), - isHorizontal: gapRect.Value.Height > gapRect.Value.Width, submarine: null) - { - ParentRuin = this, - ShouldBeSaved = false - }; - } - } - - foreach (RuinEntity ruinEntity in ruinEntities) - { - ruinEntity.Entity.ParentRuin = this; - } - - bool hullXIntersect(Rectangle rect1, Rectangle rect2) - { - return - (rect1.X >= rect2.X && rect1.X <= rect2.Right) || - (rect2.X >= rect1.X && rect2.X <= rect1.Right); - } - bool hullYIntersect(Rectangle rect1, Rectangle rect2) - { - return - (rect1.Y >= rect2.Y && rect1.Y <= rect2.Bottom) || - (rect2.Y >= rect1.Y && rect2.Y <= rect1.Bottom); + //make sure there's at least on PositionsOfInterest in the ruins + level.PositionsOfInterest.Add(new Level.InterestingPosition(subWaypoints.GetRandom(Rand.RandSync.Server).WorldPosition.ToPoint(), Level.PositionType.Ruin, this)); } } - - private void CreateEntity(RuinEntityConfig entityConfig, RuinShape room, MapEntity parent) - { - if (room == null) return; - - int leftWallThickness = 32, rightWallThickness = 32; - int topWallThickness = 32, bottomWallThickness = 32; - foreach (Line wall in room.Walls) - { - if (wall.IsHorizontal) - { - if (wall.A.Y > room.Rect.Center.Y) - bottomWallThickness = (int)wall.Radius; - else - topWallThickness = (int)wall.Radius; - } - else - { - if (wall.A.X > room.Rect.Center.X) - rightWallThickness = (int)wall.Radius; - else - leftWallThickness = (int)wall.Radius; - } - } - - Rectangle roomBounds = new Rectangle( - room.Rect.X + leftWallThickness, - room.Rect.Y + bottomWallThickness, - room.Rect.Width - leftWallThickness - rightWallThickness, - room.Rect.Height - topWallThickness - bottomWallThickness); - - Vector2 size = Vector2.Zero; - if (entityConfig.Prefab is StructurePrefab structurePrefab) - { - size = structurePrefab.Size; - } - else if (entityConfig.Prefab is ItemPrefab itemPrefab) - { - size = itemPrefab.Size; - } - else if (entityConfig.Prefab is ItemAssemblyPrefab assemblyPrefab) - { - size = new Vector2(assemblyPrefab.Bounds.Width, assemblyPrefab.Bounds.Height); - - Vector2 boundsMin = new Vector2(-assemblyPrefab.Bounds.X, -assemblyPrefab.Bounds.Y); - Vector2 boundsMax = new Vector2(assemblyPrefab.Bounds.Right, assemblyPrefab.Bounds.Bottom); - - roomBounds = new Rectangle( - (int)(roomBounds.X + boundsMin.X), - (int)(roomBounds.Y + boundsMin.Y), - (int)(roomBounds.Width - boundsMin.X - boundsMax.X), - (int)(roomBounds.Height - boundsMin.Y - boundsMax.Y)); - } - - List potentialAnchorPositions = new List(); - if (entityConfig.Alignment.HasFlag(Alignment.Top)) - { - potentialAnchorPositions.Add(new Vector2(roomBounds.Center.X, roomBounds.Bottom)); - } - if (entityConfig.Alignment.HasFlag(Alignment.Bottom)) - { - potentialAnchorPositions.Add(new Vector2(roomBounds.Center.X, roomBounds.Top)); - } - if (entityConfig.Alignment.HasFlag(Alignment.Right)) - { - potentialAnchorPositions.Add(new Vector2(roomBounds.Right, roomBounds.Center.Y)); - } - if (entityConfig.Alignment.HasFlag(Alignment.Left)) - { - potentialAnchorPositions.Add(new Vector2(roomBounds.X, roomBounds.Center.Y)); - } - if (entityConfig.Alignment.HasFlag(Alignment.Center) || potentialAnchorPositions.Count == 0) - { - potentialAnchorPositions.Add(roomBounds.Center.ToVector2()); - } - - Vector2 position = potentialAnchorPositions[Rand.Int(potentialAnchorPositions.Count, Rand.RandSync.Server)]; - Vector2 minPosition = new Vector2( - position.X + entityConfig.MinOffset.X * roomBounds.Width, - position.Y + entityConfig.MinOffset.Y * roomBounds.Height); - Vector2 maxPosition = new Vector2( - position.X + entityConfig.MaxOffset.X * roomBounds.Width, - position.Y + entityConfig.MaxOffset.Y * roomBounds.Height); - - position = new Vector2( - Rand.Range(minPosition.X, maxPosition.X, Rand.RandSync.Server), - Rand.Range(minPosition.Y, maxPosition.Y, Rand.RandSync.Server)); - position.X = MathHelper.Clamp(position.X, roomBounds.X, roomBounds.Right); - position.Y = MathHelper.Clamp(position.Y, roomBounds.Y, roomBounds.Bottom); - - int iterations = 0; - while (iterations < 100) - { - bool overlapFound = false; - foreach (RuinEntity ruinEntity in ruinEntities) - { - if (ruinEntity.Config.Type == RuinEntityType.Back || ruinEntity.Config.Type == RuinEntityType.Wall) continue; - Vector2 diff = position - ruinEntity.Entity.Position; - if (Math.Abs(diff.X) < (size.X + ruinEntity.Entity.Rect.Width) / 2 && - Math.Abs(diff.Y) < (size.Y + ruinEntity.Entity.Rect.Height) / 2) - { - float dist = diff.Length(); - Vector2 moveDir = dist < 0.01f ? Vector2.UnitY : diff / dist; - - position += moveDir * 100.0f; - - position.X = MathHelper.Clamp(position.X, roomBounds.X, roomBounds.Right); - position.Y = MathHelper.Clamp(position.Y, roomBounds.Y, roomBounds.Bottom); - overlapFound = true; - } - } - iterations++; - if (!overlapFound) { break; } - } - - MapEntity entity = null; - if (entityConfig.Prefab is ItemPrefab) - { - Item container = null; - if (entityConfig.TargetContainer != "") - { - List roomContents = ruinEntities.FindAll(re => re.Room == room); - for (int j = 0; j < roomContents.Count; j++) - { - if (roomContents[j].Entity is Item && (roomContents[j].Entity as Item).HasTag(entityConfig.TargetContainer)) - { - container = roomContents[j].Entity as Item; - break; - } - } - - if (container == null) DebugConsole.ThrowError("No container with tag \"" + entityConfig.TargetContainer + "\" found, placing item in the room"); - } - - if (container != null) - { - entity = new Item((ItemPrefab)entityConfig.Prefab, container.Position, null); - if (container.OwnInventory.TryPutItem(entity as Item, null, createNetworkEvent: false)) - { - CreateChildEntities(entityConfig, entity, room); - ruinEntities.Add(new RuinEntity(entityConfig, entity, room, parent)); - } - else // Removing items that don't fit in the container - { - entity.Remove(); - } - } - else - { - entity = new Item((ItemPrefab)entityConfig.Prefab, position, null); - CreateChildEntities(entityConfig, entity, room); - ruinEntities.Add(new RuinEntity(entityConfig, entity, room, parent)); - } - } - else if (entityConfig.Prefab is ItemAssemblyPrefab itemAssemblyPrefab) - { - var entities = itemAssemblyPrefab.CreateInstance(position, sub: null); - foreach (MapEntity e in entities) - { - if (e is Structure) - { - e.ShouldBeSaved = false; - } - else if (e is Item item) - { - var door = item.GetComponent(); - if (door != null) { doors.Add(door); } - } - ruinEntities.Add(new RuinEntity(entityConfig, e, room, parent)); - } - if (entityConfig.Expand) - { - ExpandEntities(entities); - } - CreateChildEntities(entityConfig, entity, room); - } - else - { - entity = new Structure(new Rectangle( - (int)(position.X - size.X / 2.0f), (int)(position.Y + size.Y / 2.0f), - (int)size.X, (int)size.Y), - entityConfig.Prefab as StructurePrefab, null) - { - ShouldBeSaved = false - }; - if (entityConfig.Expand) - { - ExpandEntities(new List() { entity }); - } - CreateChildEntities(entityConfig, entity, room); - ruinEntities.Add(new RuinEntity(entityConfig, entity, room, parent)); - } - } - - private void CreateChildEntities(RuinEntityConfig parentEntityConfig, MapEntity parentEntity, RuinShape room, Rand.RandSync randSync = Rand.RandSync.Server) - { - Dictionary> propGroups = new Dictionary>(); - foreach (RuinEntityConfig entityConfig in parentEntityConfig.ChildEntities) - { - if (!propGroups.ContainsKey(entityConfig.SingleGroupIndex)) - { - propGroups[entityConfig.SingleGroupIndex] = new List(); - } - propGroups[entityConfig.SingleGroupIndex].Add(entityConfig); - } - - List props = new List(); - foreach (KeyValuePair> propGroup in propGroups) - { - if (propGroup.Key == 0) - { - props.AddRange(propGroup.Value); - } - else - { - props.Add(propGroup.Value[Rand.Int(propGroup.Value.Count, randSync)]); - } - } - - foreach (RuinEntityConfig childEntity in props) - { - var childRoom = FindRoom(childEntity.PlacementRelativeToParent, room); - if (childRoom != null) - { - int amount = Rand.Range(childEntity.MinAmount, childEntity.MaxAmount + 1, Rand.RandSync.Server); - for (int i = 0; i < amount; i++) - { - CreateEntity(childEntity, childRoom, parentEntity); - } - } - } - } - - private void CreateConnections(RuinEntity entity) - { - foreach (RuinEntityConfig.EntityConnection connection in entity.Config.EntityConnections) - { - if (!string.IsNullOrEmpty(connection.SourceEntityIdentifier) && - connection.SourceEntityIdentifier != entity.Entity?.prefab.Identifier) - { - continue; - } - - MapEntity targetEntity = null; - if (connection.TargetEntityIdentifier == "parent") - { - targetEntity = entity.Parent; - } - else if (!string.IsNullOrEmpty(connection.RoomName)) - { - RuinShape targetRoom = null; - if (Enum.TryParse(connection.RoomName, out RuinEntityConfig.RelativePlacement placement)) - { - targetRoom = FindRoom(placement, entity.Room); - } - else - { - targetRoom = allShapes.Find(s => s.RoomType?.Name == connection.RoomName); - } - - if (targetRoom == null) - { - DebugConsole.ThrowError("Error while generating ruins - could not find a room of the type \"" + connection.RoomName + "\"."); - } - else - { - targetEntity = ruinEntities.GetRandom(e => - e.Room == targetRoom && - e.Entity.prefab?.Identifier == connection.TargetEntityIdentifier, Rand.RandSync.Server)?.Entity; - } - } - else - { - targetEntity = ruinEntities.GetRandom(e => e.Entity.prefab?.Identifier == connection.TargetEntityIdentifier, Rand.RandSync.Server)?.Entity; - } - - if (targetEntity == null) continue; - - if (connection.WireConnection != null) - { - Item item = entity.Entity as Item; - if (item == null) - { - DebugConsole.ThrowError("Could not connect a wire to the ruin entity \"" + entity.Entity.Name + "\" - the entity is not an item."); - continue; - } - else if (item.Connections == null) - { - DebugConsole.ThrowError("Could not connect a wire to the ruin entity \"" + entity.Entity.Name + "\" - the item does not have a connection panel component."); - continue; - } - - Item parentItem = entity.Parent as Item; - if (parentItem == null) - { - DebugConsole.ThrowError("Could not connect a wire to the ruin entity \"" + parentItem.Name + "\" - the entity is not an item."); - continue; - } - else if (parentItem.Connections == null) - { - DebugConsole.ThrowError("Could not connect a wire to the ruin entity \"" + parentItem.Name + "\" - the item does not have a connection panel component."); - continue; - } - - //TODO: alien wire prefab w/ custom sprite? - var wirePrefab = MapEntityPrefab.Find(null, "blackwire") as ItemPrefab; - - var conn1 = item.Connections.Find(c => c.Name == connection.WireConnection.First); - if (conn1 == null) - { - DebugConsole.ThrowError("Could not connect a wire to the ruin entity \"" + item.Name + - "\" - the item does not have a connection named \"" + connection.WireConnection.First + "\"."); - continue; - } - var conn2 = parentItem.Connections.Find(c => c.Name == connection.WireConnection.Second); - if (conn2 == null) - { - DebugConsole.ThrowError("Could not connect a wire to the ruin entity \"" + parentItem.Name + - "\" - the item does not have a connection named \"" + connection.WireConnection.Second + "\"."); - continue; - } - - var wire = new Item(wirePrefab, parentItem.WorldPosition, null).GetComponent(); - wire.Item.ShouldBeSaved = false; - conn1.TryAddLink(wire); - wire.Connect(conn1, true); - conn2.TryAddLink(wire); - wire.Connect(conn2, true); - wire.Hidden = true; // Hidden for now - } - else - { - entity.Entity.linkedTo.Add(targetEntity); - targetEntity.linkedTo.Add(entity.Entity); - } - } - } - - private void ExpandEntities(IEnumerable entities) - { - Vector2 xBounds = new Vector2(entities.Min(e => e.Rect.X), entities.Max(e => e.Rect.Right)); - Vector2 yBounds = new Vector2(entities.Min(e => e.Rect.Y - e.Rect.Height), entities.Max(e => e.Rect.Y)); - Vector2 center = new Vector2((xBounds.X + xBounds.Y) / 2.0f, (yBounds.X + yBounds.Y) / 2.0f); - - foreach (MapEntity entity in entities) - { - if (entity is Item item) - { - Vector2 moveTo = StretchPoint(entity.WorldPosition, center, xBounds, yBounds); - Vector2 moveAmount = moveTo - entity.WorldPosition; - var connectionPanel = item.GetComponent(); - connectionPanel?.MoveConnectedWires(moveAmount); - entity.Move(moveAmount); - } - else if (entity is Structure structure) - { - if (!entity.ResizeHorizontal && !entity.ResizeVertical) - { - Vector2 moveTo = StretchPoint(entity.WorldPosition, center, xBounds, yBounds); - entity.Move(moveTo - entity.WorldPosition); - continue; - } - - Vector2 structureBoundsMin = new Vector2(structure.Rect.X, structure.Rect.Y - structure.Rect.Height); - Vector2 structureBoundsMax = new Vector2(structure.Rect.Right, structure.Rect.Y); - - if (structure.ResizeHorizontal) - { - if (structure.Rect.Right > center.X) - { - Vector2 moveTo = StretchPoint( - new Vector2(structureBoundsMax.X, structure.Rect.Y - structure.Rect.Height / 2), - new Vector2(center.X, structure.Rect.Y - structure.Rect.Height / 2), - xBounds, yBounds); - structureBoundsMax.X = moveTo.X; - } - if (structure.Rect.X < center.X) - { - Vector2 moveTo = StretchPoint( - new Vector2(structureBoundsMin.X, structure.Rect.Y - structure.Rect.Height / 2), - new Vector2(center.X, structure.Rect.Y - structure.Rect.Height / 2), - xBounds, yBounds); - structureBoundsMin.X = moveTo.X; - } - } - if (structure.ResizeVertical) - { - if (structure.Rect.Y > center.X) - { - Vector2 moveTo = StretchPoint( - new Vector2(structure.Rect.Center.X, structureBoundsMax.Y), - new Vector2(structure.Rect.Center.X, center.Y), - xBounds, yBounds); - structureBoundsMax.Y = moveTo.Y; - } - if (structure.Rect.Y - structure.Rect.Height < center.Y) - { - Vector2 moveTo = StretchPoint( - new Vector2(structure.Rect.Center.X, structureBoundsMin.Y), - new Vector2(structure.Rect.Center.X, center.Y), - xBounds, yBounds); - structureBoundsMin.Y = moveTo.Y; - } - } - - structure.Rect = new Rectangle( - (int)structureBoundsMin.X, - (int)structureBoundsMax.Y, - (int)(structureBoundsMax.X - structureBoundsMin.X), - (int)(structureBoundsMax.Y - structureBoundsMin.Y)); - } - } - } - - private Vector2 StretchPoint(Vector2 point, Vector2 center, Vector2 xBounds, Vector2 yBounds) - { - Vector2 diff = point - center; - if (diff.LengthSquared() < 0.0001f) return point; - - Vector2? closestIntersection = RayCastWalls(point, Vector2.Normalize(diff)); - - if (!closestIntersection.HasValue) return point; - - Vector2 moveAmount = closestIntersection.Value - point; - Vector2 moveRatio = new Vector2( - Math.Abs(diff.X) / ((xBounds.Y - xBounds.X) * 0.5f), - Math.Abs(diff.Y) / ((yBounds.Y - yBounds.X) * 0.5f)); - return point + new Vector2(moveAmount.X * moveRatio.X, moveAmount.Y * moveRatio.Y); - } - - private Vector2? RayCastWalls(Vector2 worldPosition, Vector2 dir) - { - float rayLength = 10000.0f; - Vector2 rayStart = worldPosition; - Vector2 rayEnd = worldPosition + dir * rayLength; - Vector2? closestIntersection = null; - float closestDist = rayLength * rayLength; - foreach (Line line in walls) - { - if (!MathUtils.GetLineIntersection(line.A, line.B, rayStart, rayEnd, out Vector2 intersection)) { continue; } - - intersection = line.IsHorizontal ? - new Vector2(intersection.X, intersection.Y - Math.Sign(dir.Y) * line.Radius) : - new Vector2(intersection.X - Math.Sign(dir.X) * line.Radius, intersection.Y); - - float dist = Vector2.DistanceSquared(rayStart, intersection); - if (dist < closestDist) - { - closestIntersection = intersection; - closestDist = dist; - } - } - return closestIntersection; - } - - private RuinShape FindRoom(RuinEntityConfig.RelativePlacement placement, RuinShape relativeTo) - { - switch (placement) - { - case RuinEntityConfig.RelativePlacement.SameRoom: - return relativeTo; - case RuinEntityConfig.RelativePlacement.NextRoom: - return FindNearestRoom(relativeTo, rooms, 1); - case RuinEntityConfig.RelativePlacement.NextCorridor: - return FindNearestRoom(relativeTo, corridors, 1); - case RuinEntityConfig.RelativePlacement.PreviousRoom: - return FindNearestRoom(relativeTo, rooms, -1); - case RuinEntityConfig.RelativePlacement.PreviousCorridor: - return FindNearestRoom(relativeTo, corridors, -1); - case RuinEntityConfig.RelativePlacement.FirstRoom: - return FindFirstRoom(rooms); - case RuinEntityConfig.RelativePlacement.FirstCorridor: - return FindFirstRoom(corridors); - case RuinEntityConfig.RelativePlacement.LastRoom: - return FindLastRoom(rooms); - case RuinEntityConfig.RelativePlacement.LastCorridor: - return FindLastRoom(corridors); - default: - throw new NotImplementedException(); - } - } - - /// - /// Find the nearest room relative to a specific room. - /// - /// The room to compare the distance with - /// List of rooms to check (use a list that only contains rooms/corridors if you want a specific types of rooms) - /// Direction to check: 1 = find the next room, -1 = find the previous room - private RuinShape FindNearestRoom(RuinShape relativeTo, IEnumerable roomList, int dir, Func predicate = null) - { - dir = Math.Sign(dir); - RuinShape selectedRoom = null; - foreach (RuinShape room in roomList) - { - if (room == relativeTo) continue; - if (predicate != null && !predicate(room)) continue; - int roomDir = Math.Sign(room.DistanceFromEntrance - relativeTo.DistanceFromEntrance); - - if (roomDir == 0 || roomDir == dir) - { - if (selectedRoom == null) - { - selectedRoom = room; - } - else //room already selected, check if this one is closer - { - //closer than the previously selected room - if (Math.Abs(room.DistanceFromEntrance - relativeTo.DistanceFromEntrance) < - Math.Abs(selectedRoom.DistanceFromEntrance - relativeTo.DistanceFromEntrance)) - { - selectedRoom = room; - } - //same distance measured in room indices, select the room if the actual distance is smaller - else if (room.DistanceFromEntrance == selectedRoom.DistanceFromEntrance && - Vector2.DistanceSquared(relativeTo.Center, room.Center) < Vector2.DistanceSquared(relativeTo.Center, selectedRoom.Center)) - { - selectedRoom = room; - } - } - } - } - return selectedRoom; - } - - private RuinShape FindFirstRoom(IEnumerable roomList, Func predicate = null) - { - if (!roomList.Any()) { return null; } - RuinShape firstRoom = null; - foreach (RuinShape room in roomList) - { - if (predicate != null && !predicate(room)) continue; - if (firstRoom == null || room.DistanceFromEntrance < firstRoom.DistanceFromEntrance) - { - firstRoom = room; - } - } - return firstRoom; - } - - private RuinShape FindLastRoom(IEnumerable roomList, Func predicate = null) - { - if (!roomList.Any()) { return null; } - RuinShape lastRoom = null; - foreach (RuinShape room in roomList) - { - if (predicate != null && !predicate(room)) continue; - if (lastRoom == null || room.DistanceFromEntrance > lastRoom.DistanceFromEntrance) - { - lastRoom = room; - } - } - return lastRoom; - } - } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Location.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Location.cs index 153bfa954..6c208d35b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Location.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Location.cs @@ -60,7 +60,7 @@ namespace Barotrauma private LocationType addInitialMissionsForType; - public bool Discovered; + public bool Discovered { get; private set; } public readonly Dictionary ProximityTimer = new Dictionary(); public (LocationTypeChange typeChange, int delay, MissionPrefab parentMission)? PendingLocationTypeChange; @@ -90,7 +90,7 @@ namespace Barotrauma private const float StoreMaxReputationModifier = 0.1f; private const float StoreSellPriceModifier = 0.8f; - private const float DailySpecialPriceModifier = 0.9f; + private const float DailySpecialPriceModifier = 0.5f; private const float RequestGoodPriceModifier = 1.5f; public const int StoreInitialBalance = 5000; /// @@ -868,6 +868,8 @@ namespace Barotrauma // Adjust by random price modifier price = ((100 + StorePriceModifier) / 100.0f) * price; + price *= priceInfo.BuyingPriceMultiplier; + // Adjust by daily special status if (considerDailySpecials && DailySpecials.Contains(item)) { @@ -1004,12 +1006,22 @@ namespace Barotrauma stockToRemove.ForEach(i => stock.Remove(i)); StoreStock = stock; - if (++StepsSinceSpecialsUpdated >= SpecialsUpdateInterval) + int extraSpecialSalesCount = GetExtraSpecialSalesCount(); + + if (++StepsSinceSpecialsUpdated >= SpecialsUpdateInterval || + DailySpecials.Count() != DailySpecialsCount + extraSpecialSalesCount) { CreateStoreSpecials(); } } + private int GetExtraSpecialSalesCount() + { + var characters = GameSession.GetSessionCrewCharacters(); + if (!characters.Any()) { return 0; } + return characters.Max(c => (int)c.GetStatValue(StatTypes.ExtraSpecialSalesCount)); + } + private void GenerateRandomPriceModifier() { StorePriceModifier = Rand.Range(-StorePriceModifierRange, StorePriceModifierRange); @@ -1033,7 +1045,9 @@ namespace Barotrauma } availableStock.Add(stockItem.ItemPrefab, weight); } - for (int i = 0; i < DailySpecialsCount; i++) + + int extraSpecialSalesCount = GetExtraSpecialSalesCount(); + for (int i = 0; i < DailySpecialsCount + extraSpecialSalesCount; i++) { if (availableStock.None()) { break; } var item = ToolBox.SelectWeightedRandom(availableStock.Keys.ToList(), availableStock.Values.ToList(), Rand.RandSync.Unsynced); @@ -1111,6 +1125,30 @@ namespace Barotrauma return nextStatus; } + public void Discover(bool checkTalents = true) + { + if (Discovered) { return; } + Discovered = true; + if (checkTalents) + { + GameSession.GetSessionCrewCharacters().ForEach(c => c.CheckTalents(AbilityEffectType.OnLocationDiscovered, new Abilities.AbilityLocation(this))); + } + } + + public void Reset() + { + if (Type != OriginalType) + { + ChangeType(OriginalType); + PendingLocationTypeChange = null; + } + CreateStore(force: true); + ClearMissions(); + LevelData?.EventHistory?.Clear(); + UnlockInitialMissions(); + Discovered = false; + } + public XElement Save(Map map, XElement parentElement) { var locationElement = new XElement("location", diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Map.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Map.cs index e49683e88..53342d1aa 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Map.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Map.cs @@ -231,7 +231,7 @@ namespace Barotrauma } System.Diagnostics.Debug.Assert(StartLocation != null, "Start location not assigned after level generation."); - CurrentLocation.Discovered = true; + CurrentLocation.Discover(true); CurrentLocation.CreateStore(); InitProjectSpecific(); @@ -472,17 +472,18 @@ namespace Barotrauma foreach (LocationConnection connection in Connections) { - connection.Difficulty = MathHelper.Clamp((connection.CenterPos.X / Width * 100) + Rand.Range(-10.0f, 0.0f, Rand.RandSync.Server), 1.2f, 100.0f); + float difficulty = GetLevelDifficulty(connection.CenterPos.X / Width); + connection.Difficulty = MathHelper.Clamp(difficulty + Rand.Range(-10.0f, 0.0f, Rand.RandSync.Server), 1.2f, 100.0f); } AssignBiomes(); CreateEndLocation(); - + foreach (Location location in Locations) { location.LevelData = new LevelData(location) { - Difficulty = MathHelper.Clamp(location.MapPosition.X / Width * 100, 0.0f, 100.0f) + Difficulty = MathHelper.Clamp(GetLevelDifficulty(location.MapPosition.X / Width), 0.0f, 100.0f) }; location.UnlockInitialMissions(); } @@ -490,6 +491,14 @@ namespace Barotrauma { connection.LevelData = new LevelData(connection); } + + float GetLevelDifficulty(float areaDifficulty) + { + const float CurveModifier = 1.5f; + const float DifficultyMultiplier = 1.14f; + const float BaseDifficulty = -3f; + return (float)(1 - Math.Pow(1 - areaDifficulty, CurveModifier)) * DifficultyMultiplier * 100f + BaseDifficulty; + } } partial void GenerateLocationConnectionVisuals(); @@ -671,7 +680,7 @@ namespace Barotrauma SelectedConnection.Passed = true; CurrentLocation = SelectedLocation; - CurrentLocation.Discovered = true; + CurrentLocation.Discover(); SelectedLocation = null; CurrentLocation.CreateStore(); @@ -702,7 +711,7 @@ namespace Barotrauma Location prevLocation = CurrentLocation; CurrentLocation = Locations[index]; - CurrentLocation.Discovered = true; + CurrentLocation.Discover(); if (prevLocation != CurrentLocation) { @@ -1055,7 +1064,10 @@ namespace Barotrauma } } location.LoadLocationTypeChange(subElement); - location.Discovered = subElement.GetAttributeBool("discovered", false); + if (subElement.GetAttributeBool("discovered", false)) + { + location.Discover(checkTalents: false); + } if (location.Discovered) { #if CLIENT diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Radiation.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Radiation.cs index fbe5d775f..766ef959e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Radiation.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Radiation.cs @@ -149,7 +149,7 @@ namespace Barotrauma public XElement Save() { XElement element = new XElement(nameof(Radiation)); - SerializableProperty.SerializeProperties(this, element); + SerializableProperty.SerializeProperties(this, element, saveIfDefault: true); return element; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs index 61e14591b..b14ee383e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs @@ -48,8 +48,7 @@ namespace Barotrauma } } - //observable collection because some entities may need to be notified when the collection is modified - public readonly ObservableCollection linkedTo = new ObservableCollection(); + public readonly List linkedTo = new List(); protected bool flippedX, flippedY; public bool FlippedX { get { return flippedX; } } @@ -225,12 +224,6 @@ namespace Barotrauma } } - public RuinGeneration.Ruin ParentRuin - { - get; - set; - } - [Serialize(true, true)] public bool RemoveIfLinkedOutpostDoorInUse { @@ -416,14 +409,13 @@ namespace Barotrauma //connect clone wires to the clone items and refresh links between doors and gaps for (int i = 0; i < clones.Count; i++) { - var cloneItem = clones[i] as Item; - if (cloneItem == null) { continue; } + if (!(clones[i] is Item cloneItem)) { continue; } var door = cloneItem.GetComponent(); door?.RefreshLinkedGap(); var cloneWire = cloneItem.GetComponent(); - if (cloneWire == null) continue; + if (cloneWire == null) { continue; } var originalWire = ((Item)entitiesToClone[i]).GetComponent(); @@ -431,10 +423,23 @@ namespace Barotrauma for (int n = 0; n < 2; n++) { - if (originalWire.Connections[n] == null) { continue; } + if (originalWire.Connections[n] == null) + { + var disconnectedFrom = entitiesToClone.Find(e => e is Item item && (item.GetComponent()?.DisconnectedWires.Contains(originalWire) ?? false)); + if (disconnectedFrom == null) { continue; } + + int disconnectedFromIndex = entitiesToClone.IndexOf(disconnectedFrom); + var disconnectedFromClone = (clones[disconnectedFromIndex] as Item)?.GetComponent(); + if (disconnectedFromClone == null) { continue; } + + disconnectedFromClone.DisconnectedWires.Add(cloneWire); + if (cloneWire.Item.body != null) { cloneWire.Item.body.Enabled = false; } + cloneWire.IsActive = false; + continue; + } var connectedItem = originalWire.Connections[n].Item; - if (connectedItem == null) continue; + if (connectedItem == null) { continue; } //index of the item the wire is connected to int itemIndex = entitiesToClone.IndexOf(connectedItem); @@ -515,7 +520,11 @@ namespace Barotrauma } #endif - if (aiTarget != null) aiTarget.Remove(); + if (aiTarget != null) + { + aiTarget.Remove(); + aiTarget = null; + } if (linkedTo != null) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostGenerationParams.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostGenerationParams.cs index 577750efd..7b29db634 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostGenerationParams.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostGenerationParams.cs @@ -11,7 +11,7 @@ namespace Barotrauma { public static List Params { get; private set; } - public string Name { get; private set; } + public virtual string Name { get; private set; } public string Identifier { get; private set; } @@ -67,6 +67,34 @@ namespace Barotrauma set; } + [Serialize(true, isSaveable: true), Editable] + public bool LockUnusedDoors + { + get; + set; + } + + [Serialize(true, isSaveable: true), Editable] + public bool RemoveUnusedGaps + { + get; + set; + } + + [Serialize(0.0f, isSaveable: true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f)] + public float MinWaterPercentage + { + get; + set; + } + + [Serialize(0.0f, isSaveable: true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f)] + public float MaxWaterPercentage + { + get; + set; + } + [Serialize("", isSaveable: true), Editable] public string ReplaceInRadiation { get; set; } @@ -81,12 +109,14 @@ namespace Barotrauma public Dictionary SerializableProperties { get; private set; } - private OutpostGenerationParams(XElement element, string filePath) + protected OutpostGenerationParams(XElement element, string filePath) { Identifier = element.GetAttributeString("identifier", ""); Name = element.GetAttributeString("name", Identifier); allowedLocationTypes = element.GetAttributeStringArray("allowedlocationtypes", Array.Empty()).ToList(); SerializableProperties = SerializableProperty.DeserializeProperties(this, element); + + if (element == null) { return; } foreach (XElement subElement in element.Elements()) { switch (subElement.Name.ToString().ToLowerInvariant()) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostGenerator.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostGenerator.cs index 2e07a4c11..3b0dfa429 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostGenerator.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostGenerator.cs @@ -77,7 +77,7 @@ namespace Barotrauma locationType = location.GetLocationType(); } - + //load the infos of the outpost module files List outpostModules = new List(); foreach (ContentFile outpostModuleFile in outpostModuleFiles) @@ -85,6 +85,19 @@ namespace Barotrauma var subInfo = new SubmarineInfo(outpostModuleFile.Path); if (subInfo.OutpostModuleInfo != null) { + if (generationParams is RuinGeneration.RuinGenerationParams) + { + //if the module doesn't have the ruin flag or any other flag used in the generation params, don't use it in ruins + if (!subInfo.OutpostModuleInfo.ModuleFlags.Contains("ruin") && + !generationParams.ModuleCounts.Any(m => subInfo.OutpostModuleInfo.ModuleFlags.Contains(m.Key))) + { + continue; + } + } + else if (subInfo.OutpostModuleInfo.ModuleFlags.Contains("ruin")) + { + continue; + } outpostModules.Add(subInfo); } } @@ -162,7 +175,7 @@ namespace Barotrauma selectedModules.Add(new PlacedModule(initialModule, null, OutpostModuleInfo.GapPosition.None)); selectedModules.Last().FulfilledModuleTypes.Add(initialModuleFlag); - AppendToModule(selectedModules.Last(), outpostModules.ToList(), pendingModuleFlags, selectedModules, locationType); + AppendToModule(selectedModules.Last(), outpostModules.ToList(), pendingModuleFlags, selectedModules, locationType, allowExtendBelowInitialModule: generationParams is RuinGeneration.RuinGenerationParams); if (pendingModuleFlags.Any(flag => !flag.Equals("none", StringComparison.OrdinalIgnoreCase))) { remainingTries--; @@ -233,17 +246,23 @@ namespace Barotrauma var selectedModule = selectedModules[i]; sub.Info.GameVersion = selectedModule.Info.GameVersion; var moduleEntities = MapEntity.LoadAll(sub, selectedModule.Info.SubmarineElement, selectedModule.Info.FilePath, idOffset); - idOffset = moduleEntities.Max(e => e.ID); + MapEntity.InitializeLoadedLinks(moduleEntities); - foreach (MapEntity entity in moduleEntities) + foreach (MapEntity entity in moduleEntities.ToList()) { entity.OriginalModuleIndex = i; if (!(entity is Item item)) { continue; } - item.GetComponent()?.RefreshLinkedGap(); + var door = item.GetComponent(); + if (door != null) + { + door.RefreshLinkedGap(); + if (!moduleEntities.Contains(door.LinkedGap)) { moduleEntities.Add(door.LinkedGap); } + } item.GetComponent()?.InitializeLinks(); item.GetComponent()?.OnMapLoaded(); } + idOffset = moduleEntities.Max(e => e.ID); var wallEntities = moduleEntities.Where(e => e is Structure).Cast(); var hullEntities = moduleEntities.Where(e => e is Hull).Cast(); @@ -345,11 +364,33 @@ namespace Barotrauma Submarine.RepositionEntities(module.Offset + sub.HiddenSubPosition, entities[module]); } Gap.UpdateHulls(); - allEntities.AddRange(GenerateHallways(sub, locationType, selectedModules, outpostModules, entities)); + allEntities.AddRange(GenerateHallways(sub, locationType, selectedModules, outpostModules, entities, generationParams is RuinGeneration.RuinGenerationParams)); LinkOxygenGenerators(allEntities); - LockUnusedDoors(selectedModules, entities); + if (generationParams.LockUnusedDoors) + { + LockUnusedDoors(selectedModules, entities, generationParams.RemoveUnusedGaps); + } AlignLadders(selectedModules, entities); PowerUpOutpost(entities.SelectMany(e => e.Value)); + if (generationParams.MaxWaterPercentage > 0.0f) + { + foreach (var entity in allEntities) + { + if (entity is Hull hull) + { + float diff = generationParams.MaxWaterPercentage - generationParams.MinWaterPercentage; + if (diff < 0.01f) + { + // Overfill the hulls to get rid of air pockets in the vertical hallways. Airpockets make it impossible to swim up the hallways. + hull.WaterVolume = hull.Volume * 2; + } + else + { + hull.WaterVolume = hull.Volume * Rand.Range(generationParams.MinWaterPercentage, generationParams.MaxWaterPercentage, Rand.RandSync.Server) * 0.01f; + } + } + } + } } return allEntities; @@ -414,7 +455,8 @@ namespace Barotrauma List pendingModuleFlags, List selectedModules, LocationType locationType, - bool retry = true) + bool retry = true, + bool allowExtendBelowInitialModule = false) { if (pendingModuleFlags.Count == 0) { return true; } @@ -422,8 +464,11 @@ namespace Barotrauma foreach (OutpostModuleInfo.GapPosition gapPosition in GapPositions().Randomize(Rand.RandSync.Server)) { if (currentModule.UsedGapPositions.HasFlag(gapPosition)) { continue; } - //don't continue downwards if it'd extend below the airlock - if (gapPosition == OutpostModuleInfo.GapPosition.Bottom && currentModule.Offset.Y <= 1) { continue; } + if (!allowExtendBelowInitialModule) + { + //don't continue downwards if it'd extend below the airlock + if (gapPosition == OutpostModuleInfo.GapPosition.Bottom && currentModule.Offset.Y <= 1) { continue; } + } if (currentModule.Info.OutpostModuleInfo.GapPositions.HasFlag(gapPosition)) { var newModule = AppendModule(currentModule, GetOpposingGapPosition(gapPosition), availableModules, pendingModuleFlags, selectedModules, locationType); @@ -438,7 +483,7 @@ namespace Barotrauma //try to append to some other module first foreach (PlacedModule otherModule in selectedModules) { - if (AppendToModule(otherModule, availableModules, pendingModuleFlags, selectedModules, locationType, retry: false)) + if (AppendToModule(otherModule, availableModules, pendingModuleFlags, selectedModules, locationType, retry: false, allowExtendBelowInitialModule: allowExtendBelowInitialModule)) { return true; } @@ -454,7 +499,7 @@ namespace Barotrauma //retry currentModule = AppendModule(currentModule.PreviousModule, currentModule.ThisGapPosition, availableModules, pendingModuleFlags, selectedModules, locationType); if (currentModule == null) { break; } - if (AppendToModule(currentModule, availableModules, pendingModuleFlags, selectedModules, locationType, retry: false)) + if (AppendToModule(currentModule, availableModules, pendingModuleFlags, selectedModules, locationType, retry: false, allowExtendBelowInitialModule: allowExtendBelowInitialModule)) { return true; } @@ -676,6 +721,10 @@ namespace Barotrauma else { availableModules = modules.Where(m => m.OutpostModuleInfo.ModuleFlags.Contains(moduleFlag)); + if (moduleFlag != "hallwayhorizontal" && moduleFlag != "hallwayvertical") + { + availableModules = availableModules.Where(m => !m.OutpostModuleInfo.ModuleFlags.Contains("hallwayhorizontal") && !m.OutpostModuleInfo.ModuleFlags.Contains("hallwayvertical")); + } } if (availableModules.Count() == 0) { return null; } @@ -840,7 +889,7 @@ namespace Barotrauma return from.AllowAttachToModules.Any(s => to.ModuleFlags.Contains(s)); } - private static List GenerateHallways(Submarine sub, LocationType locationType, IEnumerable placedModules, IEnumerable availableModules, Dictionary> allEntities) + private static List GenerateHallways(Submarine sub, LocationType locationType, IEnumerable placedModules, IEnumerable availableModules, Dictionary> allEntities, bool isRuin) { //if a hallway is shorter than this, one of the doors at the ends of the hallway is removed const float MinTwoDoorHallwayLength = 32.0f; @@ -1193,14 +1242,13 @@ namespace Barotrauma } } - private static void LockUnusedDoors(IEnumerable placedModules, Dictionary> entities) + private static void LockUnusedDoors(IEnumerable placedModules, Dictionary> entities, bool removeUnusedGaps) { foreach (PlacedModule module in placedModules) { foreach (MapEntity me in entities[module]) { - var gap = me as Gap; - if (gap == null) { continue; } + if (!(me is Gap gap)) { continue; } var door = gap.ConnectedDoor; if (door != null && !door.UseBetweenOutpostModules) { continue; } if (placedModules.Any(m => m.PreviousGap == gap || m.ThisGap == gap)) @@ -1247,11 +1295,11 @@ namespace Barotrauma if (connectionPanel != null) { connectionPanel.Locked = true; } } } - else + else if (removeUnusedGaps) { gap.Remove(); WayPoint.WayPointList.Where(wp => wp.ConnectedGap == gap).ForEachMod(wp => wp.Remove()); - } + } } entities[module].RemoveAll(e => e.Removed); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostModuleInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostModuleInfo.cs index 4b0307e7e..c0c051586 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostModuleInfo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostModuleInfo.cs @@ -89,11 +89,13 @@ namespace Barotrauma if (newFlags.Contains("hallwayhorizontal")) { moduleFlags.Add("hallwayhorizontal"); + if (newFlags.Contains("ruin")) { moduleFlags.Add("ruin"); } return; } if (newFlags.Contains("hallwayvertical")) { moduleFlags.Add("hallwayvertical"); + if (newFlags.Contains("ruin")) { moduleFlags.Add("ruin"); } return; } if (!newFlags.Any()) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/PriceInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/PriceInfo.cs index 78a7e14f3..25dff440e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/PriceInfo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/PriceInfo.cs @@ -24,6 +24,11 @@ namespace Barotrauma /// The item isn't available in stores unless the level's difficulty is above this value /// public readonly int MinLevelDifficulty; + /// + /// The cost of item when sold by the store. Higher modifier means the item costs more to buy from the store. + /// + public readonly float BuyingPriceMultiplier = 1f; + /// /// Support for the old style of determining item prices @@ -34,6 +39,7 @@ namespace Barotrauma { Price = element.GetAttributeInt("buyprice", 0); MinLevelDifficulty = element.GetAttributeInt("minleveldifficulty", 0); + BuyingPriceMultiplier = element.GetAttributeFloat("buyingpricemultiplier", 1f); CanBeBought = true; var minAmount = GetMinAmount(element); MinAvailableAmount = Math.Min(minAmount, CargoManager.MaxQuantity); @@ -42,11 +48,12 @@ namespace Barotrauma MaxAvailableAmount = Math.Max(maxAmount, MinAvailableAmount); } - public PriceInfo(int price, bool canBeBought, int minAmount = 0, int maxAmount = 0, bool canBeSpecial = true, int minLevelDifficulty = 0) + public PriceInfo(int price, bool canBeBought, int minAmount = 0, int maxAmount = 0, bool canBeSpecial = true, int minLevelDifficulty = 0, float buyingPriceMultiplier = 1f) { Price = price; CanBeBought = canBeBought; MinAvailableAmount = Math.Min(minAmount, CargoManager.MaxQuantity); + BuyingPriceMultiplier = buyingPriceMultiplier; maxAmount = Math.Min(maxAmount, CargoManager.MaxQuantity); MaxAvailableAmount = Math.Max(maxAmount, minAmount); MinLevelDifficulty = minLevelDifficulty; @@ -62,6 +69,7 @@ namespace Barotrauma var maxAmount = GetMaxAmount(element); var minLevelDifficulty = element.GetAttributeInt("minleveldifficulty", 0); var canBeSpecial = element.GetAttributeBool("canbespecial", true); + var buyingPriceMultiplier = element.GetAttributeFloat("buyingpricemultiplier", 1f); var priceInfos = new List>(); foreach (XElement childElement in element.GetChildElements("price")) @@ -73,7 +81,7 @@ namespace Barotrauma minAmount: sold ? GetMinAmount(childElement, minAmount) : 0, maxAmount: sold ? GetMaxAmount(childElement, maxAmount) : 0, canBeSpecial, - childElement.GetAttributeInt("minleveldifficulty", minLevelDifficulty)))); + childElement.GetAttributeInt("minleveldifficulty", minLevelDifficulty), childElement.GetAttributeFloat("buyingpricemultiplier", buyingPriceMultiplier)))); } var canBeBoughtAtOtherLocations = soldByDefault && element.GetAttributeBool("soldeverywhere", true); @@ -81,7 +89,7 @@ namespace Barotrauma minAmount: canBeBoughtAtOtherLocations ? minAmount : 0, maxAmount: canBeBoughtAtOtherLocations ? maxAmount : 0, canBeSpecial, - minLevelDifficulty); + minLevelDifficulty, buyingPriceMultiplier); return priceInfos; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs index 23644df10..2907db5ce 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs @@ -8,8 +8,10 @@ using System; using System.Collections.Generic; using System.Linq; using System.Xml.Linq; +using Barotrauma.Abilities; #if CLIENT using Microsoft.Xna.Framework.Graphics; +using Barotrauma.Lights; #endif namespace Barotrauma @@ -47,7 +49,7 @@ namespace Barotrauma const float LeakThreshold = 0.1f; #if CLIENT - private SpriteEffects SpriteEffects = SpriteEffects.None; + public SpriteEffects SpriteEffects = SpriteEffects.None; #endif //dimensions of the wall sections' physics bodies (only used for debug rendering) @@ -96,7 +98,7 @@ namespace Barotrauma { get { return Prefab.Body; } } - + public List Bodies { get; private set; } public bool CastShadow @@ -112,7 +114,7 @@ namespace Barotrauma } private float? maxHealth; - + [Serialize(100.0f, true)] public float MaxHealth { @@ -167,7 +169,7 @@ namespace Barotrauma { get { return prefab.Tags; } } - + protected Color spriteColor; [Editable, Serialize("1.0,1.0,1.0,1.0", true)] public Color SpriteColor @@ -175,7 +177,7 @@ namespace Barotrauma get { return spriteColor; } set { spriteColor = value; } } - + [Editable, Serialize(false, true)] public bool UseDropShadow { @@ -203,8 +205,8 @@ namespace Barotrauma if (!ResizeHorizontal || !ResizeVertical) { - int newWidth = ResizeHorizontal ? rect.Width : (int)(defaultRect.Width * relativeScale); - int newHeight = ResizeVertical ? rect.Height : (int)(defaultRect.Height * relativeScale); + int newWidth = Math.Max(ResizeHorizontal ? rect.Width : (int)(defaultRect.Width * relativeScale), 1); + int newHeight = Math.Max(ResizeVertical ? rect.Height : (int)(defaultRect.Height * relativeScale), 1); Rect = new Rectangle(rect.X, rect.Y, newWidth, newHeight); if (StairDirection != Direction.None) { @@ -215,6 +217,13 @@ namespace Barotrauma UpdateSections(); } } + +#if CLIENT + foreach (LightSource light in Lights) + { + light.SpriteScale = scale * textureScale; + } +#endif } } @@ -230,6 +239,13 @@ namespace Barotrauma textureScale = new Vector2( MathHelper.Clamp(value.X, 0.01f, 10), MathHelper.Clamp(value.Y, 0.01f, 10)); + +#if CLIENT + foreach (LightSource light in Lights) + { + light.LightTextureScale = textureScale * scale; + } +#endif } } @@ -238,7 +254,13 @@ namespace Barotrauma public Vector2 TextureOffset { get { return textureOffset; } - set { textureOffset = value; } + set + { + textureOffset = value; +#if CLIENT + SetLightTextureOffset(); +#endif + } } @@ -281,10 +303,10 @@ namespace Barotrauma secRect.X += value.X; secRect.Y += value.Y; sec.rect = secRect; } - } + } } } - + public float BodyWidth { get { return Prefab.BodyWidth > 0.0f ? Prefab.BodyWidth * scale : rect.Width; } @@ -363,6 +385,12 @@ namespace Barotrauma #if CLIENT convexHulls?.ForEach(x => x.Move(amount)); + + foreach (LightSource light in Lights) + { + light.LightTextureTargetSize = rect.Size.ToVector2(); + light.Position = rect.Location.ToVector2(); + } #endif } @@ -374,7 +402,7 @@ namespace Barotrauma defaultRect = rectangle; maxHealth = sp.Health; - + rect = rectangle; TextureScale = sp.TextureScale; @@ -426,25 +454,60 @@ namespace Barotrauma if (StairDirection != Direction.None) { CreateStairBodies(); - } + } } } SerializableProperties = element != null ? SerializableProperty.DeserializeProperties(this, element) : SerializableProperty.GetProperties(this); - // Only add ai targets automatically to submarine/outpost walls +#if CLIENT + foreach (XElement subElement in sp.ConfigElement.Elements()) + { + if (subElement.Name.ToString().Equals("light", StringComparison.OrdinalIgnoreCase)) + { + Vector2 pos = rect.Location.ToVector2(); + pos.Y += rect.Height; + LightSource light = new LightSource(subElement) + { + ParentSub = Submarine, + Position = rect.Location.ToVector2(), + CastShadows = false, + IsBackground = false, + Color = subElement.GetAttributeColor("lightcolor", Color.White), + SpriteScale = Vector2.One, + Range = 0, + LightTextureTargetSize = rect.Size.ToVector2(), + LightTextureScale = textureScale * scale, + LightSourceParams = + { + Flicker = subElement.GetAttributeFloat("flicker", 0f), + FlickerSpeed = subElement.GetAttributeFloat("flickerspeed", 0f), + PulseAmount = subElement.GetAttributeFloat("pulseamount", 0f), + PulseFrequency = subElement.GetAttributeFloat("pulsefrequency", 0f), + BlinkFrequency = subElement.GetAttributeFloat("blinkfrequency", 0f) + } + }; + + Lights.Add(light); + + SetLightTextureOffset(); + } + } +#endif + + // Only add ai targets automatically to submarine/outpost walls if (aiTarget == null && HasBody && Tags.Contains("wall") && submarine != null && !submarine.Info.IsWreck && !NoAITarget) { aiTarget = new AITarget(this) { - MinSightRange = 2000, - MaxSightRange = 5000, + MinSightRange = 1000, + MaxSightRange = 4000, MaxSoundRange = 0 }; } InsertToList(); - + DebugConsole.Log("Created " + Name + " (" + ID + ")"); } @@ -476,7 +539,7 @@ namespace Barotrauma { Bodies = new List(); bodyDebugDimensions.Clear(); - + float stairAngle = MathHelper.ToRadians(Math.Min(Prefab.StairAngle, 75.0f)); float bodyWidth = ConvertUnits.ToSimUnits(rect.Width / Math.Cos(stairAngle)); @@ -504,9 +567,9 @@ namespace Barotrauma { int xsections = 1, ysections = 1; int width = rect.Width, height = rect.Height; - + if (!HasBody) - { + { if (FlippedX && IsHorizontal) { xsections = (int)Math.Ceiling((float)rect.Width / prefab.sprite.SourceRect.Width); @@ -548,8 +611,8 @@ namespace Barotrauma if (FlippedX || FlippedY) { Rectangle sectionRect = new Rectangle( - FlippedX ? rect.Right - (x + 1) * width : rect.X + x * width, - FlippedY ? rect.Y - rect.Height + (y + 1) * height : rect.Y - y * height, + FlippedX ? rect.Right - (x + 1) * width : rect.X + x * width, + FlippedY ? rect.Y - rect.Height + (y + 1) * height : rect.Y - y * height, width, height); if (FlippedX) @@ -645,8 +708,8 @@ namespace Barotrauma Vector2 transformedMousePos = MathUtils.RotatePointAroundTarget(position, bodyPos, BodyRotation); - return - Math.Abs(transformedMousePos.X - bodyPos.X) < rectSize.X / 2.0f && + return + Math.Abs(transformedMousePos.X - bodyPos.X) < rectSize.X / 2.0f && Math.Abs(transformedMousePos.Y - bodyPos.Y) < rectSize.Y / 2.0f; } else @@ -692,6 +755,10 @@ namespace Barotrauma #if CLIENT if (convexHulls != null) convexHulls.ForEach(x => x.Remove()); + foreach (LightSource light in Lights) + { + light.Remove(); + } #endif } @@ -724,6 +791,10 @@ namespace Barotrauma #if CLIENT if (convexHulls != null) convexHulls.ForEach(x => x.Remove()); + foreach (LightSource light in Lights) + { + light.Remove(); + } #endif } @@ -781,7 +852,7 @@ namespace Barotrauma return (IsHorizontal ? Sections[sectionIndex].rect.Width : Sections[sectionIndex].rect.Height); } - + public override bool AddUpgrade(Upgrade upgrade, bool createNetworkEvent = false) { if (!upgrade.Prefab.IsWallUpgrade) { return false; } @@ -799,7 +870,7 @@ namespace Barotrauma Upgrades.Add(upgrade); upgrade.ApplyUpgrade(); } - + UpdateSections(); return true; @@ -914,7 +985,7 @@ namespace Barotrauma { diffFromCenter = -diffFromCenter; } - + Vector2 sectionPos = Position + new Vector2( (float)Math.Cos(IsHorizontal ? -BodyRotation : MathHelper.PiOver2 - BodyRotation), (float)Math.Sin(IsHorizontal ? -BodyRotation : MathHelper.PiOver2 - BodyRotation)) * diffFromCenter; @@ -925,7 +996,7 @@ namespace Barotrauma } return sectionPos; } - } + } public AttackResult AddDamage(Character attacker, Vector2 worldPosition, Attack attack, float deltaTime, bool playSound = false) { @@ -948,13 +1019,28 @@ namespace Barotrauma GameMain.ParticleManager.CreateParticle("dustcloud", SectionPosition(i), 0.0f, 0.0f); #endif } - } + } #if CLIENT if (playSound && damageAmount > 0) { - SoundPlayer.PlayDamageSound(attack.StructureSoundType, damageAmount, worldPosition, tags: Tags); + string damageSound = Prefab.DamageSound; + if (string.IsNullOrWhiteSpace(damageSound)) + { + damageSound = attack.StructureSoundType; + } + SoundPlayer.PlayDamageSound(damageSound, damageAmount, worldPosition, tags: Tags); } #endif + + if (Submarine != null && damageAmount > 0 && attacker != null) + { + var abilityAttackerSubmarine = new AbilityCharacterSubmarine(attacker, Submarine); + foreach (Character character in Character.CharacterList) + { + character.CheckTalents(AbilityEffectType.AfterSubmarineAttacked, abilityAttackerSubmarine); + } + } + return new AttackResult(damageAmount, null); } @@ -965,7 +1051,7 @@ namespace Barotrauma if (!MathUtils.IsValid(damage)) { return; } damage = MathHelper.Clamp(damage, 0.0f, MaxHealth - Prefab.MinHealth); - + #if SERVER if (GameMain.Server != null && createNetworkEvent && damage != Sections[sectionIndex].damage) { @@ -1011,10 +1097,10 @@ namespace Barotrauma { diffFromCenter = (gapRect.Center.X - this.rect.Center.X) / (float)this.rect.Width * BodyWidth; if (BodyWidth > 0.0f) { gapRect.Width = (int)(BodyWidth * (gapRect.Width / (float)this.rect.Width)); } - if (BodyHeight > 0.0f) - { + if (BodyHeight > 0.0f) + { gapRect.Y = (gapRect.Y - gapRect.Height / 2) + (int)(BodyHeight / 2 + BodyOffset.Y * scale); - gapRect.Height = (int)BodyHeight; + gapRect.Height = (int)BodyHeight; } } else @@ -1023,7 +1109,7 @@ namespace Barotrauma if (BodyWidth > 0.0f) { gapRect.X = gapRect.Center.X + (int)(-BodyWidth / 2 + BodyOffset.X * scale); - gapRect.Width = (int)BodyWidth; + gapRect.Width = (int)BodyWidth; } if (BodyHeight > 0.0f) { gapRect.Height = (int)(BodyHeight * (gapRect.Height / (float)this.rect.Height)); } } @@ -1042,7 +1128,7 @@ namespace Barotrauma gapRect.Y += 10; gapRect.Width += 20; gapRect.Height += 20; - + bool horizontalGap = !IsHorizontal; if (Prefab.BodyRotation != 0.0f) { @@ -1079,7 +1165,7 @@ namespace Barotrauma } float gapOpen = MaxHealth <= 0.0f ? 0.0f : (damage / MaxHealth - LeakThreshold) * (1.0f / (1.0f - LeakThreshold)); - Sections[sectionIndex].gap.Open = gapOpen; + Sections[sectionIndex].gap.Open = gapOpen; } float damageDiff = damage - Sections[sectionIndex].damage; @@ -1095,9 +1181,8 @@ namespace Barotrauma { if (damageDiff < 0.0f) { - attacker.Info?.IncreaseSkillLevel("mechanical", - -damageDiff * SkillSettings.Current.SkillIncreasePerRepairedStructureDamage / Math.Max(attacker.GetSkillLevel("mechanical"), 1.0f), - SectionPosition(sectionIndex)); + attacker.Info?.IncreaseSkillLevel("mechanical", + -damageDiff * SkillSettings.Current.SkillIncreasePerRepairedStructureDamage / Math.Max(attacker.GetSkillLevel("mechanical"), 1.0f)); } } } @@ -1105,7 +1190,7 @@ namespace Barotrauma bool hasHole = SectionBodyDisabled(sectionIndex); if (hadHole == hasHole) { return; } - + UpdateSections(); } @@ -1187,7 +1272,7 @@ namespace Barotrauma if (BodyHeight > 0.0f) rect.Height = Math.Max((int)Math.Round(BodyHeight * (rect.Height / (float)this.rect.Height)), 1); } if (FlippedX) { diffFromCenter = -diffFromCenter; } - + Vector2 bodyOffset = ConvertUnits.ToSimUnits(Prefab.BodyOffset) * scale; if (FlippedX) { bodyOffset.X = -bodyOffset.X; } if (FlippedY) { bodyOffset.Y = -bodyOffset.Y; } @@ -1229,7 +1314,7 @@ namespace Barotrauma } partial void CreateConvexHull(Vector2 position, Vector2 size, float rotation); - + public override void FlipX(bool relativeToSub) { base.FlipX(relativeToSub); @@ -1250,7 +1335,7 @@ namespace Barotrauma CreateStairBodies(); } - + if (HasBody) { CreateSections(); @@ -1377,7 +1462,7 @@ namespace Barotrauma StructurePrefab prefab = null; if (string.IsNullOrEmpty(identifier)) { - //legacy support: + //legacy support: //1. attempt to find a prefab with an empty identifier and a matching name prefab = MapEntityPrefab.Find(name, "") as StructurePrefab; //2. not found, attempt to find a prefab with a matching name @@ -1422,7 +1507,7 @@ namespace Barotrauma } SerializableProperty.SerializeProperties(this, element); - + foreach (var upgrade in Upgrades) { upgrade.Save(element); @@ -1453,7 +1538,7 @@ namespace Barotrauma { if (aiTarget != null) { - aiTarget.SightRange = Submarine == null ? aiTarget.MinSightRange : Submarine.Velocity.Length() / 2 * aiTarget.MaxSightRange; + aiTarget.SightRange = Submarine == null ? aiTarget.MinSightRange : MathHelper.Lerp(aiTarget.MinSightRange, aiTarget.MaxSightRange, Submarine.Velocity.Length() / 10); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/StructurePrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/StructurePrefab.cs index 380df3975..805bc42b8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/StructurePrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/StructurePrefab.cs @@ -167,6 +167,9 @@ namespace Barotrauma private set { size = value; } } + [Serialize("", true)] + public string DamageSound { get; private set; } + public Vector2 ScaledSize => size * Scale; protected Vector2 textureScale = Vector2.One; @@ -291,7 +294,8 @@ namespace Barotrauma { case "sprite": sp.sprite = new Sprite(subElement, lazyLoad: true); - if (subElement.Attribute("sourcerect") == null) + if (subElement.Attribute("sourcerect") == null && + subElement.Attribute("sheetindex") == null) { DebugConsole.ThrowError("Warning - sprite sourcerect not configured for structure \"" + sp.name + "\"!"); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs index 2270e5d69..3273bb4c3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs @@ -249,6 +249,17 @@ namespace Barotrauma get { return subBody?.HullVertices; } } + private int? submarineSpecificIDTag; + public int SubmarineSpecificIDTag + { + get + { + submarineSpecificIDTag ??= ToolBox.StringToInt((Level.Loaded?.Seed ?? "") + Info.Name); + return submarineSpecificIDTag.Value; + } + } + + public bool AtDamageDepth { get @@ -329,48 +340,6 @@ namespace Barotrauma DockedTo.ForEach(s => s.ShowSonarMarker = false); PhysicsBody.FarseerBody.BodyType = BodyType.Static; TeamID = CharacterTeamType.None; - - string defaultTag = Level.Loaded.GetWreckIDTag("wreck_id", this); - ReplaceIDCardTagRequirements("wreck_id", defaultTag); - - foreach (Item item in Item.ItemList) - { - if (item.Submarine != this) { continue; } - if (item.prefab.Identifier == "idcardwreck" || item.prefab.Identifier == "idcard") - { - foreach (string tag in item.GetTags().ToList()) - { - if (tag == "smallitem") { continue; } - string newTag = Level.Loaded.GetWreckIDTag(tag, this); - item.ReplaceTag(tag, newTag); - ReplaceIDCardTagRequirements(tag, newTag); - } - } - } - - void ReplaceIDCardTagRequirements(string oldTag, string newTag) - { - foreach (Item item in Item.ItemList) - { - if (item.Submarine != this) { continue; } - foreach (ItemComponent ic in item.Components) - { - ReplaceIDCardTagRequirement(ic, RelatedItem.RelationType.Picked, oldTag, newTag); - ReplaceIDCardTagRequirement(ic, RelatedItem.RelationType.Equipped, oldTag, newTag); - } - } - } - - static void ReplaceIDCardTagRequirement(ItemComponent ic, RelatedItem.RelationType relationType, string oldTag, string newTag) - { - if (!ic.requiredItems.ContainsKey(relationType)) { return; } - foreach (RelatedItem requiredItem in ic.requiredItems[relationType]) - { - int index = Array.IndexOf(requiredItem.Identifiers, oldTag); - if (index == -1) { continue; } - requiredItem.Identifiers[index] = newTag; - } - } } public WreckAI WreckAI { get; private set; } @@ -945,9 +914,11 @@ namespace Barotrauma mapEntity.Move(-HiddenSubPosition); } + var prevBodyType = subBody.Body.BodyType; Vector2 pos = new Vector2(subBody.Position.X, subBody.Position.Y); subBody.Body.Remove(); subBody = new SubmarineBody(this); + subBody.Body.BodyType = prevBodyType; SetPosition(pos, new List(parents.Where(p => p != this))); if (entityGrid != null) @@ -1460,6 +1431,11 @@ namespace Barotrauma } } } + else if (info.IsRuin) + { + ShowSonarMarker = false; + PhysicsBody.FarseerBody.BodyType = BodyType.Static; + } } if (entityGrid != null) @@ -1718,7 +1694,10 @@ namespace Barotrauma PhysicsBody.RemoveAll(); - GameMain.World.Clear(); + GameMain.World?.Clear(); + GameMain.World = null; + + GC.Collect(); Unloading = false; } @@ -1730,6 +1709,9 @@ namespace Barotrauma subBody?.Remove(); subBody = null; + outdoorNodes?.Clear(); + outdoorNodes = null; + if (GameMain.GameSession?.Campaign?.UpgradeManager != null) { GameMain.GameSession.Campaign.UpgradeManager.OnUpgradesChanged -= ResetCrushDepth; @@ -1743,8 +1725,8 @@ namespace Barotrauma visibleEntities = null; - if (MainSub == this) MainSub = null; - if (MainSubs[1] == this) MainSubs[1] = null; + if (MainSub == this) { MainSub = null; } + if (MainSubs[1] == this) { MainSubs[1] = null; } ConnectedDockingPorts?.Clear(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineBody.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineBody.cs index 5fe93ad3b..49bfbf5f6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineBody.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineBody.cs @@ -452,7 +452,7 @@ namespace Barotrauma public void ApplyForce(Vector2 force) { - Body.ApplyForce(force, maxVelocity: NetConfig.MaxPhysicsBodyVelocity); + Body.ApplyForce(force); } public void SetPosition(Vector2 position) @@ -569,8 +569,7 @@ namespace Barotrauma } var gaps = newHull?.ConnectedGaps ?? Gap.GapList.Where(g => g.Submarine == submarine); - targetPos = character.WorldPosition; - Gap adjacentGap = Gap.FindAdjacent(gaps, targetPos, 500.0f); + Gap adjacentGap = Gap.FindAdjacent(gaps, ConvertUnits.ToDisplayUnits(points[0]), 200.0f); if (adjacentGap == null) { return true; } if (newHull != null) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineInfo.cs index 1a1876939..d8dc6665f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineInfo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineInfo.cs @@ -23,7 +23,7 @@ namespace Barotrauma HideInMenus = 2 } - public enum SubmarineType { Player, Outpost, OutpostModule, Wreck, BeaconStation, EnemySubmarine } + public enum SubmarineType { Player, Outpost, OutpostModule, Wreck, BeaconStation, EnemySubmarine, Ruin } public enum SubmarineClass { Undefined, Scout, Attack, Transport, DeepDiver } partial class SubmarineInfo : IDisposable @@ -96,9 +96,11 @@ namespace Barotrauma public OutpostModuleInfo OutpostModuleInfo { get; set; } public bool IsOutpost => Type == SubmarineType.Outpost || Type == SubmarineType.OutpostModule; + public bool IsWreck => Type == SubmarineType.Wreck; public bool IsBeacon => Type == SubmarineType.BeaconStation; public bool IsPlayer => Type == SubmarineType.Player; + public bool IsRuin => Type == SubmarineType.Ruin; public bool IsCampaignCompatible => IsPlayer && !HasTag(SubmarineTag.Shuttle) && !HasTag(SubmarineTag.HideInMenus) && SubmarineClass != SubmarineClass.Undefined; public bool IsCampaignCompatibleIgnoreClass => IsPlayer && !HasTag(SubmarineTag.Shuttle) && !HasTag(SubmarineTag.HideInMenus); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/WayPoint.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/WayPoint.cs index 2f7152ffa..de504a73e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/WayPoint.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/WayPoint.cs @@ -6,7 +6,6 @@ using System; using System.Collections.Generic; using System.Linq; using System.Xml.Linq; -using Barotrauma.RuinGeneration; using Barotrauma.Extensions; namespace Barotrauma @@ -47,6 +46,7 @@ namespace Barotrauma public Hull CurrentHull { get; private set; } public Level.Tunnel Tunnel; + public RuinGeneration.Ruin Ruin; public SpawnType SpawnType { @@ -54,6 +54,8 @@ namespace Barotrauma set { spawnType = value; } } + public Action OnLinksChanged { get; set; } + public override string Name { get @@ -187,61 +189,141 @@ namespace Barotrauma door.Body.Enabled = true; } } - + bool isFlooded = submarine.Info.IsRuin || submarine.Info.Type == SubmarineType.OutpostModule && submarine.Info.OutpostModuleInfo.ModuleFlags.Contains("ruin"); float diffFromHullEdge = 50; float minDist = 100.0f; float heightFromFloor = 110.0f; float hullMinHeight = 100; + var removals = new List(); foreach (Hull hull in Hull.hullList) { - // Ignore hulls that a human couldn't fit in. - // Doesn't take multi-hull rooms into account, but it's probably best to leave them to be setup manually. - if (hull.Rect.Height < hullMinHeight) { continue; } - // Do five raycasts to check if there's a floor. Don't create waypoints unless we can find a floor. - Body floor = null; - for (int i = 0; i < 5; i++) + if (isFlooded) { - float horizontalOffset = 0; - switch (i) + diffFromHullEdge = 75; + var hullWaypoints = new List(); + float top = hull.Rect.Y; + float bottom = hull.Rect.Y - hull.Rect.Height; + if (hull.Rect.Width < 300 || hull.Rect.Height < 300) { - case 1: - horizontalOffset = hull.RectWidth * 0.2f; - break; - case 2: - horizontalOffset = hull.RectWidth * 0.4f; - break; - case 3: - horizontalOffset = -hull.RectWidth * 0.2f; - break; - case 4: - horizontalOffset = -hull.RectWidth * 0.4f; - break; + // For narrow hulls, create one line of waypoints either horizontally or vertically + if (hull.Rect.Width > hull.Rect.Height) + { + // Horizontal + float y = hull.Rect.Y - hull.Rect.Height / 2; + for (float x = hull.Rect.X + diffFromHullEdge; x <= hull.Rect.Right - diffFromHullEdge; x += minDist) + { + hullWaypoints.Add(new WayPoint(new Vector2(x, y), SpawnType.Path, submarine)); + } + } + else + { + // Vertical + float x = hull.Rect.X + hull.Rect.Width / 2; + for (float y = top - diffFromHullEdge; y >= bottom + diffFromHullEdge; y -= minDist) + { + hullWaypoints.Add(new WayPoint(new Vector2(x, y), SpawnType.Path, submarine)); + } + } + } + if (hullWaypoints.None()) + { + // Try to create a grid-like network of waypoints + for (float x = hull.Rect.X + diffFromHullEdge; x <= hull.Rect.Right - diffFromHullEdge; x += minDist) + { + for (float y = top - diffFromHullEdge; y >= bottom + diffFromHullEdge; y -= minDist) + { + hullWaypoints.Add(new WayPoint(new Vector2(x, y), SpawnType.Path, submarine)); + } + } + if (hullWaypoints.None()) + { + // If that fails, just create one waypoint at the center. + hullWaypoints.Add(new WayPoint(new Vector2(hull.Rect.X + hull.Rect.Width / 2.0f, hull.Rect.Y - hull.Rect.Height / 2), SpawnType.Path, submarine)); + } + foreach (WayPoint wp in hullWaypoints) + { + foreach (Structure wall in Structure.WallList) + { + if (wall.HasBody) + { + // Remove waypoints that are too close/inside the walls. + Rectangle rect = wall.Rect; + rect.Inflate(10, 10); + if (rect.ContainsWorld(wp.Position)) + { + removals.Add(wp); + } + } + } + } + } + // Connect the waypoints + foreach (var wayPoint in hullWaypoints) + { + for (int dir = -1; dir <= 1; dir += 2) + { + WayPoint closest = wayPoint.FindClosest(dir, horizontalSearch: true, new Vector2(minDist * 1.9f, minDist)); + if (closest != null && closest.CurrentHull == wayPoint.CurrentHull) + { + wayPoint.ConnectTo(closest); + } + closest = wayPoint.FindClosest(dir, horizontalSearch: false, new Vector2(minDist, minDist * 1.9f)); + if (closest != null && closest.CurrentHull == wayPoint.CurrentHull) + { + wayPoint.ConnectTo(closest); + } + } } - horizontalOffset = ConvertUnits.ToSimUnits(horizontalOffset); - Vector2 floorPos = new Vector2(hull.SimPosition.X + horizontalOffset, ConvertUnits.ToSimUnits(hull.Rect.Y - hull.RectHeight - 50)); - floor = Submarine.PickBody(new Vector2(hull.SimPosition.X + horizontalOffset, hull.SimPosition.Y), floorPos, collisionCategory: Physics.CollisionWall | Physics.CollisionPlatform, customPredicate: f => !(f.Body.UserData is Submarine)); - if (floor != null) { break; } - } - if (floor == null) { continue; } - float waypointHeight = hull.Rect.Height > heightFromFloor * 2 ? heightFromFloor : hull.Rect.Height / 2; - if (hull.Rect.Width < diffFromHullEdge * 3.0f) - { - new WayPoint(new Vector2(hull.Rect.X + hull.Rect.Width / 2.0f, hull.Rect.Y - hull.Rect.Height + waypointHeight), SpawnType.Path, submarine); } else { - WayPoint prevWaypoint = null; - for (float x = hull.Rect.X + diffFromHullEdge; x <= hull.Rect.Right - diffFromHullEdge; x += minDist) + if (hull.Rect.Height < hullMinHeight) { continue; } + // Do five raycasts to check if there's a floor. Don't create waypoints unless we can find a floor. + Body floor = null; + for (int i = 0; i < 5; i++) { - var wayPoint = new WayPoint(new Vector2(x, hull.Rect.Y - hull.Rect.Height + waypointHeight), SpawnType.Path, submarine); - if (prevWaypoint != null) { wayPoint.ConnectTo(prevWaypoint); } - prevWaypoint = wayPoint; + float horizontalOffset = 0; + switch (i) + { + case 1: + horizontalOffset = hull.RectWidth * 0.2f; + break; + case 2: + horizontalOffset = hull.RectWidth * 0.4f; + break; + case 3: + horizontalOffset = -hull.RectWidth * 0.2f; + break; + case 4: + horizontalOffset = -hull.RectWidth * 0.4f; + break; + } + horizontalOffset = ConvertUnits.ToSimUnits(horizontalOffset); + Vector2 floorPos = new Vector2(hull.SimPosition.X + horizontalOffset, ConvertUnits.ToSimUnits(hull.Rect.Y - hull.RectHeight - 50)); + floor = Submarine.PickBody(new Vector2(hull.SimPosition.X + horizontalOffset, hull.SimPosition.Y), floorPos, collisionCategory: Physics.CollisionWall | Physics.CollisionPlatform, customPredicate: f => !(f.Body.UserData is Submarine)); + if (floor != null) { break; } } - if (prevWaypoint == null) + if (floor == null) { continue; } + float waypointHeight = hull.Rect.Height > heightFromFloor * 2 ? heightFromFloor : hull.Rect.Height / 2; + if (hull.Rect.Width < diffFromHullEdge * 3.0f) { - // Ensure that we always create at least one waypoint per hull. new WayPoint(new Vector2(hull.Rect.X + hull.Rect.Width / 2.0f, hull.Rect.Y - hull.Rect.Height + waypointHeight), SpawnType.Path, submarine); } + else + { + WayPoint previousWaypoint = null; + for (float x = hull.Rect.X + diffFromHullEdge; x <= hull.Rect.Right - diffFromHullEdge; x += minDist) + { + var wayPoint = new WayPoint(new Vector2(x, hull.Rect.Y - hull.Rect.Height + waypointHeight), SpawnType.Path, submarine); + if (previousWaypoint != null) { wayPoint.ConnectTo(previousWaypoint); } + previousWaypoint = wayPoint; + } + if (previousWaypoint == null) + { + // Ensure that we always create at least one waypoint per hull. + new WayPoint(new Vector2(hull.Rect.X + hull.Rect.Width / 2.0f, hull.Rect.Y - hull.Rect.Height + waypointHeight), SpawnType.Path, submarine); + } + } } } @@ -276,7 +358,7 @@ namespace Barotrauma } float outSideWaypointInterval = 100.0f; - if (submarine.Info.Type != SubmarineType.OutpostModule) + if (!isFlooded && submarine.Info.Type != SubmarineType.OutpostModule) { List<(WayPoint, int)> outsideWaypoints = new List<(WayPoint, int)>(); @@ -379,7 +461,6 @@ namespace Barotrauma } } // Remove unwanted points - var removals = new List(); WayPoint previous = null; float tooClose = outSideWaypointInterval / 2; foreach (var wayPoint in outsideWaypoints) @@ -410,7 +491,6 @@ namespace Barotrauma foreach (WayPoint wp in removals) { outsideWaypoints.RemoveAll(w => w.Item1 == wp); - wp.Remove(); } for (int i = 0; i < outsideWaypoints.Count; i++) { @@ -431,41 +511,35 @@ namespace Barotrauma } } } - - List stairList = new List(); - foreach (MapEntity me in mapEntityList) - { - if (!(me is Structure stairs)) { continue; } - - if (stairs.StairDirection != Direction.None) stairList.Add(stairs); - } - - foreach (Structure stairs in stairList) + foreach (Structure wall in Structure.WallList) { + if (wall.StairDirection == Direction.None) { continue; } WayPoint[] stairPoints = new WayPoint[3]; stairPoints[0] = new WayPoint( - new Vector2(stairs.Rect.X - 32.0f, - stairs.Rect.Y - (stairs.StairDirection == Direction.Left ? 80 : stairs.Rect.Height) + heightFromFloor), SpawnType.Path, submarine); + new Vector2(wall.Rect.X - 32.0f, + wall.Rect.Y - (wall.StairDirection == Direction.Left ? 80 : wall.Rect.Height) + heightFromFloor), SpawnType.Path, submarine); stairPoints[1] = new WayPoint( - new Vector2(stairs.Rect.Right + 32.0f, - stairs.Rect.Y - (stairs.StairDirection == Direction.Left ? stairs.Rect.Height : 80) + heightFromFloor), SpawnType.Path, submarine); + new Vector2(wall.Rect.Right + 32.0f, + wall.Rect.Y - (wall.StairDirection == Direction.Left ? wall.Rect.Height : 80) + heightFromFloor), SpawnType.Path, submarine); - for (int i = 0; i < 2; i++ ) + for (int i = 0; i < 2; i++) { for (int dir = -1; dir <= 1; dir += 2) { WayPoint closest = stairPoints[i].FindClosest(dir, horizontalSearch: true, new Vector2(100, 70)); if (closest == null) { continue; } stairPoints[i].ConnectTo(closest); - } + } } - + stairPoints[2] = new WayPoint((stairPoints[0].Position + stairPoints[1].Position) / 2, SpawnType.Path, submarine); stairPoints[0].ConnectTo(stairPoints[2]); stairPoints[2].ConnectTo(stairPoints[1]); } + removals.ForEach(wp => wp.Remove()); + removals.Clear(); foreach (Item item in Item.ItemList) { @@ -603,12 +677,25 @@ namespace Barotrauma { if (gap.IsHorizontal) { - // Too small to walk through - if (gap.Rect.Height < hullMinHeight) { continue; } + if ( isFlooded) + { + // Too small to swim through + if (gap.Rect.Height < 50) { continue; } + } + else + { + // Too small to walk through + if (gap.Rect.Height < hullMinHeight) { continue; } + } + Vector2 pos = new Vector2(gap.Rect.Center.X, gap.Rect.Y - gap.Rect.Height + heightFromFloor); + if (isFlooded) + { + pos.Y = gap.Rect.Y - gap.Rect.Height / 2; + } var wayPoint = new WayPoint(pos, SpawnType.Path, submarine, gap); // The closest waypoint can be quite far if the gap is at an exterior door. - Vector2 tolerance = gap.IsRoomToRoom ? new Vector2(150, 70) : new Vector2(1000, 1000); + Vector2 tolerance = gap.IsRoomToRoom && !isFlooded ? new Vector2(150, 70) : new Vector2(1000, 1000); for (int dir = -1; dir <= 1; dir += 2) { WayPoint closest = wayPoint.FindClosest(dir, horizontalSearch: true, tolerance, gap.ConnectedDoor?.Body.FarseerBody); @@ -621,7 +708,7 @@ namespace Barotrauma else { // Create waypoints on vertical gaps on the outer walls, also hatches. - if (gap.IsRoomToRoom || gap.linkedTo.None(l => l is Hull)) { continue; } + if (!isFlooded && (gap.IsRoomToRoom || gap.linkedTo.None(l => l is Hull))) { continue; } // Too small to swim through if (gap.Rect.Width < 50.0f) { continue; } Vector2 pos = new Vector2(gap.Rect.Center.X, gap.Rect.Y - gap.Rect.Height / 2); @@ -630,11 +717,20 @@ namespace Barotrauma var wayPoint = new WayPoint(pos, SpawnType.Path, submarine, gap); Hull connectedHull = (Hull)gap.linkedTo.First(l => l is Hull); int dir = Math.Sign(connectedHull.Position.Y - gap.Position.Y); - WayPoint closest = wayPoint.FindClosest(dir, horizontalSearch: false, new Vector2(50, 100)); + WayPoint closest = wayPoint.FindClosest(dir, horizontalSearch: false, isFlooded ? new Vector2(500, 500) : new Vector2(50, 100)); if (closest != null) { wayPoint.ConnectTo(closest); } + if (isFlooded) + { + closest = wayPoint.FindClosest(-dir, horizontalSearch: false, isFlooded ? new Vector2(500, 500) : new Vector2(50, 100)); + if (closest != null) + { + wayPoint.ConnectTo(closest); + } + } + // Link to outside for (dir = -1; dir <= 1; dir += 2) { closest = wayPoint.FindClosest(dir, horizontalSearch: true, new Vector2(500, 1000), gap.ConnectedDoor?.Body.FarseerBody, filter: wp => wp.CurrentHull == null); @@ -654,7 +750,7 @@ namespace Barotrauma foreach (WayPoint wp in WayPointList) { - if (wp.CurrentHull == null && wp.Ladders == null && wp.linkedTo.Count < 2) + if (wp.SpawnType == SpawnType.Path && wp.CurrentHull == null && wp.Ladders == null && wp.linkedTo.Count < 2) { DebugConsole.ThrowError($"Couldn't automatically link the waypoint {wp.ID} outside of the submarine. You should do it manually. The waypoint ID is shown in red color."); } @@ -761,17 +857,24 @@ namespace Barotrauma public void ConnectTo(WayPoint wayPoint2) { System.Diagnostics.Debug.Assert(this != wayPoint2); - - if (!linkedTo.Contains(wayPoint2)) { linkedTo.Add(wayPoint2); } - if (!wayPoint2.linkedTo.Contains(this)) { wayPoint2.linkedTo.Add(this); } + if (!linkedTo.Contains(wayPoint2)) + { + OnLinksChanged?.Invoke(this); + linkedTo.Add(wayPoint2); + } + if (!wayPoint2.linkedTo.Contains(this)) + { + wayPoint2.OnLinksChanged?.Invoke(wayPoint2); + wayPoint2.linkedTo.Add(this); + } } - public static WayPoint GetRandom(SpawnType spawnType = SpawnType.Human, JobPrefab assignedJob = null, Submarine sub = null, Ruin ruin = null, bool useSyncedRand = false) + public static WayPoint GetRandom(SpawnType spawnType = SpawnType.Human, JobPrefab assignedJob = null, Submarine sub = null, bool useSyncedRand = false, string spawnPointTag = null) { return WayPointList.GetRandom(wp => wp.Submarine == sub && - wp.ParentRuin == ruin && wp.spawnType == spawnType && + (string.IsNullOrEmpty(spawnPointTag) || wp.Tags.Any(t => t.Equals(spawnPointTag, StringComparison.OrdinalIgnoreCase))) && (assignedJob == null || (assignedJob != null && wp.AssignedJob == assignedJob)), useSyncedRand ? Rand.RandSync.Server : Rand.RandSync.Unsynced); } @@ -986,14 +1089,19 @@ namespace Barotrauma public override void ShallowRemove() { base.ShallowRemove(); - WayPointList.Remove(this); } public override void Remove() { base.Remove(); - + CurrentHull = null; + ConnectedGap = null; + Tunnel = null; + Ruin = null; + Stairs = null; + Ladders = null; + OnLinksChanged = null; WayPointList.Remove(this); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/ChildServerRelay.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/ChildServerRelay.cs index 7402c316d..d43ef274d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/ChildServerRelay.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/ChildServerRelay.cs @@ -89,7 +89,7 @@ namespace Barotrauma.Networking private static int ReadIncomingMsgs() { Task readTask = readStream?.ReadAsync(tempBytes, 0, tempBytes.Length, readCancellationToken.Token); - TimeSpan ts = TimeSpan.FromMilliseconds(100); + TimeSpan timeOut = TimeSpan.FromMilliseconds(100); for (int i = 0; i < 150; i++) { if (shutDown) @@ -99,7 +99,7 @@ namespace Barotrauma.Networking return -1; } - if ((readTask?.IsCompleted ?? true) || (readTask?.Wait(ts) ?? true)) + if ((readTask?.IsCompleted ?? true) || (readTask?.Wait(timeOut) ?? true)) { break; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/EntitySpawner.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/EntitySpawner.cs index 3d6b8ea44..a4f18118c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/EntitySpawner.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/EntitySpawner.cs @@ -1,4 +1,5 @@ -using Barotrauma.Items.Components; +using Barotrauma.Extensions; +using Barotrauma.Items.Components; using Barotrauma.Networking; using Microsoft.Xna.Framework; using System; @@ -28,6 +29,7 @@ namespace Barotrauma public bool SpawnIfInventoryFull = true; public bool IgnoreLimbSlots = false; + public InvSlotType Slot = InvSlotType.None; private readonly Action onSpawned; @@ -73,7 +75,8 @@ namespace Barotrauma { Condition = Condition }; - if (!Inventory.Owner.Removed && !Inventory.TryPutItem(spawnedItem, null, spawnedItem.AllowedSlots)) + var slot = Slot != InvSlotType.None ? Slot.ToEnumerable() : spawnedItem.AllowedSlots; + if (!Inventory.Owner.Removed && !Inventory.TryPutItem(spawnedItem, null, slot)) { if (IgnoreLimbSlots) { @@ -264,7 +267,7 @@ namespace Barotrauma spawnQueue.Enqueue(new ItemSpawnInfo(itemPrefab, position, sub, onSpawned, condition)); } - public void AddToSpawnQueue(ItemPrefab itemPrefab, Inventory inventory, float? condition = null, Action onSpawned = null, bool spawnIfInventoryFull = true, bool ignoreLimbSlots = false) + public void AddToSpawnQueue(ItemPrefab itemPrefab, Inventory inventory, float? condition = null, Action onSpawned = null, bool spawnIfInventoryFull = true, bool ignoreLimbSlots = false, InvSlotType slot = InvSlotType.None) { if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; } if (itemPrefab == null) @@ -277,7 +280,8 @@ namespace Barotrauma spawnQueue.Enqueue(new ItemSpawnInfo(itemPrefab, inventory, onSpawned, condition) { SpawnIfInventoryFull = spawnIfInventoryFull, - IgnoreLimbSlots = ignoreLimbSlots + IgnoreLimbSlots = ignoreLimbSlots, + Slot = slot }); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/NetEntityEvent/NetEntityEvent.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/NetEntityEvent/NetEntityEvent.cs index f0b800508..8396c48a3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/NetEntityEvent/NetEntityEvent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/NetEntityEvent/NetEntityEvent.cs @@ -23,6 +23,10 @@ namespace Barotrauma.Networking TeamChange, ObjectiveManagerState, AddToCrew, + UpdateExperience, + UpdateTalents, + UpdateMoney, + UpdatePermanentStats, } public readonly Entity Entity; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/OrderChatMessage.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/OrderChatMessage.cs index b6eb1674f..dc79afcd9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/OrderChatMessage.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/OrderChatMessage.cs @@ -23,9 +23,12 @@ namespace Barotrauma.Networking /// public int? WallSectionIndex { get; set; } + /// + /// Same as calling , but the text parameter is set using + /// public OrderChatMessage(Order order, string orderOption, int priority, ISpatialEntity targetEntity, Character targetCharacter, Character sender) : this(order, orderOption, priority, - order?.GetChatMessage(targetCharacter?.Name, sender?.CurrentHull?.DisplayName, givingOrderToSelf: targetCharacter == sender, orderOption: orderOption), + order?.GetChatMessage(targetCharacter?.Name, sender?.CurrentHull?.DisplayName, givingOrderToSelf: targetCharacter == sender, orderOption: orderOption, priority: priority), targetEntity, targetCharacter, sender) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/Message/IReadMessage.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/Message/IReadMessage.cs index 8a6d8f9e4..f7e5e730c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/Message/IReadMessage.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/Message/IReadMessage.cs @@ -19,6 +19,8 @@ namespace Barotrauma.Networking Double ReadDouble(); UInt32 ReadVariableUInt32(); String ReadString(); + Microsoft.Xna.Framework.Color ReadColorR8G8B8(); + Microsoft.Xna.Framework.Color ReadColorR8G8B8A8(); int ReadRangedInteger(int min, int max); Single ReadRangedSingle(Single min, Single max, int bitCount); byte[] ReadBytes(int numberOfBytes); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/Message/IWriteMessage.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/Message/IWriteMessage.cs index 653364ae1..16146f8bf 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/Message/IWriteMessage.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/Message/IWriteMessage.cs @@ -15,6 +15,8 @@ namespace Barotrauma.Networking void Write(UInt64 val); void Write(Single val); void Write(Double val); + void WriteColorR8G8B8(Microsoft.Xna.Framework.Color val); + void WriteColorR8G8B8A8(Microsoft.Xna.Framework.Color val); void WriteVariableUInt32(UInt32 val); void Write(string val); void WriteRangedInteger(int val, int min, int max); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/Message/Message.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/Message/Message.cs index a84520c56..deb765620 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/Message/Message.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/Message/Message.cs @@ -5,6 +5,7 @@ using Barotrauma.IO; using System.IO.Compression; using System.Runtime.InteropServices; using System.Text; +using Microsoft.Xna.Framework; namespace Barotrauma.Networking { @@ -138,6 +139,26 @@ namespace Barotrauma.Networking byte[] bytes = BitConverter.GetBytes(val); WriteBytes(ref buf, ref bitPos, bytes, 0, 8); } + + internal static void WriteColorR8G8B8(ref byte[] buf, ref int bitPos, Microsoft.Xna.Framework.Color val) + { + EnsureBufferSize(ref buf, bitPos + 24); + + Write(ref buf, ref bitPos, val.R); + Write(ref buf, ref bitPos, val.G); + Write(ref buf, ref bitPos, val.B); + } + + internal static void WriteColorR8G8B8A8(ref byte[] buf, ref int bitPos, Microsoft.Xna.Framework.Color val) + { + EnsureBufferSize(ref buf, bitPos + 32); + + Write(ref buf, ref bitPos, val.R); + Write(ref buf, ref bitPos, val.G); + Write(ref buf, ref bitPos, val.B); + Write(ref buf, ref bitPos, val.A); + } + internal static void Write(ref byte[] buf, ref int bitPos, string val) { if (string.IsNullOrEmpty(val)) @@ -217,7 +238,7 @@ namespace Barotrauma.Networking { byte retval = NetBitWriter.ReadByte(buf, 1, bitPos); bitPos++; - return (retval > 0 ? true : false); + return retval > 0; } internal static void ReadPadBits(byte[] buf, ref int bitPos) @@ -299,6 +320,23 @@ namespace Barotrauma.Networking return BitConverter.ToDouble(bytes, 0); } + internal static Microsoft.Xna.Framework.Color ReadColorR8G8B8(byte[] buf, ref int bitPos) + { + byte r = ReadByte(buf, ref bitPos); + byte g = ReadByte(buf, ref bitPos); + byte b = ReadByte(buf, ref bitPos); + return new Color(r, g, b, (byte)255); + } + + internal static Microsoft.Xna.Framework.Color ReadColorR8G8B8A8(byte[] buf, ref int bitPos) + { + byte r = ReadByte(buf, ref bitPos); + byte g = ReadByte(buf, ref bitPos); + byte b = ReadByte(buf, ref bitPos); + byte a = ReadByte(buf, ref bitPos); + return new Color(r, g, b, a); + } + internal static UInt32 ReadVariableUInt32(byte[] buf, ref int bitPos) { int bitLength = buf.Length * 8; @@ -482,6 +520,16 @@ namespace Barotrauma.Networking MsgWriter.Write(ref buf, ref seekPos, val); } + public void WriteColorR8G8B8(Color val) + { + MsgWriter.WriteColorR8G8B8(ref buf, ref seekPos, val); + } + + public void WriteColorR8G8B8A8(Color val) + { + MsgWriter.WriteColorR8G8B8A8(ref buf, ref seekPos, val); + } + public void WriteVariableUInt32(UInt32 val) { MsgWriter.WriteVariableUInt32(ref buf, ref seekPos, val); @@ -624,14 +672,28 @@ namespace Barotrauma.Networking } } buf = new byte[decompressedData.Length]; - Array.Copy(decompressedData, 0, buf, 0, decompressedData.Length); + try + { + Array.Copy(decompressedData, 0, buf, 0, decompressedData.Length); + } + catch (ArgumentException e) + { + throw new ArgumentException($"Failed to copy the incoming compressed buffer. Source buffer length: {decompressedData.Length}, start position: {0}, length: {decompressedData.Length}, destination buffer length: {buf.Length}.", e); + } lengthBits = decompressedData.Length * 8; DebugConsole.Log("Decompressing message: " + inLength + " to " + LengthBytes); } else { buf = new byte[inBuf.Length]; - Array.Copy(inBuf, startPos, buf, 0, inLength); + try + { + Array.Copy(inBuf, startPos, buf, 0, inLength); + } + catch (ArgumentException e) + { + throw new ArgumentException($"Failed to copy the incoming uncompressed buffer. Source buffer length: {inBuf.Length}, start position: {startPos}, length: {inLength}, destination buffer length: {buf.Length}.", e); + } lengthBits = inLength * 8; } seekPos = 0; @@ -702,6 +764,17 @@ namespace Barotrauma.Networking return MsgReader.ReadString(buf, ref seekPos); } + public Color ReadColorR8G8B8() + { + return MsgReader.ReadColorR8G8B8(buf, ref seekPos); + } + + public Color ReadColorR8G8B8A8() + { + return MsgReader.ReadColorR8G8B8A8(buf, ref seekPos); + } + + public int ReadRangedInteger(int min, int max) { return MsgReader.ReadRangedInteger(buf, ref seekPos, min, max); @@ -845,6 +918,16 @@ namespace Barotrauma.Networking MsgWriter.Write(ref buf, ref seekPos, val); } + public void WriteColorR8G8B8(Color val) + { + MsgWriter.WriteColorR8G8B8(ref buf, ref seekPos, val); + } + + public void WriteColorR8G8B8A8(Color val) + { + MsgWriter.WriteColorR8G8B8A8(ref buf, ref seekPos, val); + } + public void WriteVariableUInt32(UInt32 val) { MsgWriter.WriteVariableUInt32(ref buf, ref seekPos, val); @@ -936,6 +1019,16 @@ namespace Barotrauma.Networking return MsgReader.ReadString(buf, ref seekPos); } + public Color ReadColorR8G8B8() + { + return MsgReader.ReadColorR8G8B8(buf, ref seekPos); + } + + public Color ReadColorR8G8B8A8() + { + return MsgReader.ReadColorR8G8B8A8(buf, ref seekPos); + } + public int ReadRangedInteger(int min, int max) { return MsgReader.ReadRangedInteger(buf, ref seekPos, min, max); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/RespawnManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/RespawnManager.cs index c0074d353..e4d53fc9c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/RespawnManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/RespawnManager.cs @@ -26,6 +26,9 @@ namespace Barotrauma.Networking //any respawn items left in the shuttle are removed when the shuttle despawns private readonly List respawnItems = new List(); + //characters who spawned during the last respawn + private readonly List respawnedCharacters = new List(); + public bool UsingShuttle { get { return RespawnShuttle != null; } @@ -277,11 +280,17 @@ namespace Barotrauma.Networking hull.BallastFlora?.Kill(); } + Dictionary characterPositions = new Dictionary(); foreach (Character c in Character.CharacterList) { if (c.Submarine != RespawnShuttle) { continue; } + if (!respawnedCharacters.Contains(c)) + { + characterPositions.Add(c, c.WorldPosition); + continue; + } #if CLIENT - if (Character.Controlled == c) Character.Controlled = null; + if (Character.Controlled == c) { Character.Controlled = null; } #endif c.Kill(CauseOfDeathType.Unknown, null, true); c.Enabled = false; @@ -298,6 +307,11 @@ namespace Barotrauma.Networking RespawnShuttle.SetPosition(new Vector2(Level.Loaded.StartPosition.X, Level.Loaded.Size.Y + RespawnShuttle.Borders.Height)); RespawnShuttle.Velocity = Vector2.Zero; + + foreach (var characterPosition in characterPositions) + { + characterPosition.Key.TeleportTo(characterPosition.Value); + } } partial void RespawnCharactersProjSpecific(Vector2? shuttlePos); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/ServerSettings.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/ServerSettings.cs index 8b72e0075..4d335dc73 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/ServerSettings.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/ServerSettings.cs @@ -507,7 +507,7 @@ namespace Barotrauma.Networking } [Serialize(800, true)] - private int LinesPerLogFile + public int LinesPerLogFile { get { @@ -892,8 +892,15 @@ namespace Barotrauma.Networking get; set; } - // we do not serialize this value because it relies on a default setting - public int MaxMissionCount { get; set; } = CampaignSettings.DefaultMaxMissionCount; + + private int maxMissionCount = CampaignSettings.DefaultMaxMissionCount; + + [Serialize(CampaignSettings.DefaultMaxMissionCount, true)] + public int MaxMissionCount + { + get { return maxMissionCount; } + set { maxMissionCount = MathHelper.Clamp(value, CampaignSettings.MinMissionCountLimit, CampaignSettings.MaxMissionCountLimit); } + } public void SetPassword(string password) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Physics/PhysicsBody.cs b/Barotrauma/BarotraumaShared/SharedSource/Physics/PhysicsBody.cs index d09effa0e..81086e854 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Physics/PhysicsBody.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Physics/PhysicsBody.cs @@ -641,18 +641,9 @@ namespace Barotrauma NetConfig.MaxPhysicsBodyAngularVelocity); } - public void ApplyForce(Vector2 force) + public void ApplyForce(Vector2 force, float maxVelocity = NetConfig.MaxPhysicsBodyVelocity) { - if (!IsValidValue(force, "force", -1e10f, 1e10f)) return; - FarseerBody.ApplyForce(force); - } - - /// - /// Apply a force to the body without increasing it's velocity above a specific limit. - /// - public void ApplyForce(Vector2 force, float maxVelocity) - { - if (!IsValidValue(maxVelocity, "max velocity")) return; + if (!IsValidValue(maxVelocity, "max velocity")) { return; } Vector2 velocityAddition = force / Mass * (float)Timing.Step; Vector2 newVelocity = FarseerBody.LinearVelocity + velocityAddition; @@ -666,20 +657,20 @@ namespace Barotrauma force = velocityAddition.ClampLength(maxVelAddition) * Mass / (float)Timing.Step; } - if (!IsValidValue(force, "clamped force", -1e10f, 1e10f)) return; + if (!IsValidValue(force, "clamped force", -1e10f, 1e10f)) { return; } FarseerBody.ApplyForce(force); } public void ApplyForce(Vector2 force, Vector2 point) { - if (!IsValidValue(force, "force", -1e10f, 1e10f)) return; - if (!IsValidValue(point, "point")) return; + if (!IsValidValue(force, "force", -1e10f, 1e10f)) { return; } + if (!IsValidValue(point, "point")) { return; } FarseerBody.ApplyForce(force, point); } public void ApplyTorque(float torque) { - if (!IsValidValue(torque, "torque")) return; + if (!IsValidValue(torque, "torque")) { return; } FarseerBody.ApplyTorque(torque); } @@ -689,8 +680,8 @@ namespace Barotrauma System.Diagnostics.Debug.Assert(Math.Abs(simPosition.X) < 1000000.0f); System.Diagnostics.Debug.Assert(Math.Abs(simPosition.Y) < 1000000.0f); - if (!IsValidValue(simPosition, "position", -1e10f, 1e10f)) return false; - if (!IsValidValue(rotation, "rotation")) return false; + if (!IsValidValue(simPosition, "position", -1e10f, 1e10f)) { return false; } + if (!IsValidValue(rotation, "rotation")) { return false; } FarseerBody.SetTransform(simPosition, rotation); if (setPrevTransform) { SetPrevTransform(simPosition, rotation); } @@ -703,8 +694,8 @@ namespace Barotrauma System.Diagnostics.Debug.Assert(Math.Abs(simPosition.X) < 1000000.0f); System.Diagnostics.Debug.Assert(Math.Abs(simPosition.Y) < 1000000.0f); - if (!IsValidValue(simPosition, "position", -1e10f, 1e10f)) return false; - if (!IsValidValue(rotation, "rotation")) return false; + if (!IsValidValue(simPosition, "position", -1e10f, 1e10f)) { return false; } + if (!IsValidValue(rotation, "rotation")) { return false; } FarseerBody.SetTransformIgnoreContacts(ref simPosition, rotation); if (setPrevTransform) { SetPrevTransform(simPosition, rotation); } @@ -756,12 +747,13 @@ namespace Barotrauma Vector2 vel = FarseerBody.LinearVelocity; Vector2 deltaPos = simPosition - (Vector2)pullPos; -#if DEBUG if (deltaPos.LengthSquared() > 100.0f * 100.0f) { +#if DEBUG DebugConsole.ThrowError("Attempted to move a physics body to an invalid position.\n" + Environment.StackTrace.CleanupStackTrace()); - } #endif + return; + } deltaPos *= force; ApplyLinearImpulse((deltaPos - vel * 0.5f) * FarseerBody.Mass, (Vector2)pullPos); } @@ -788,7 +780,7 @@ namespace Barotrauma dragForce = Math.Min(drag, Mass * 500.0f) * -velDir; } - ApplyForce(dragForce + buoyancy, maxVelocity: NetConfig.MaxPhysicsBodyVelocity); + ApplyForce(dragForce + buoyancy); ApplyTorque(FarseerBody.AngularVelocity * FarseerBody.Mass * -0.08f); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Screens/NetLobbyScreen.cs b/Barotrauma/BarotraumaShared/SharedSource/Screens/NetLobbyScreen.cs index 8cd8dae34..f8ce3190b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Screens/NetLobbyScreen.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Screens/NetLobbyScreen.cs @@ -73,19 +73,7 @@ namespace Barotrauma public int GetMaxMissionCount() { -#if CLIENT - // this seems rather silly, but it matches the radiation enabled check structurally. is this right? - if (maxMissionCountText != null && Int32.TryParse(maxMissionCountText.Text, out int result)) - { - return result; - } - else - { - return 0; - } -#elif SERVER - return GameMain.Server.ServerSettings.MaxMissionCount; -#endif + return GameMain.NetworkMember?.ServerSettings?.MaxMissionCount ?? 0; } public void ToggleTraitorsEnabled(int dir) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Serialization/SerializableProperty.cs b/Barotrauma/BarotraumaShared/SharedSource/Serialization/SerializableProperty.cs index 5002ddf18..1f4ff6489 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Serialization/SerializableProperty.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Serialization/SerializableProperty.cs @@ -88,11 +88,11 @@ namespace Barotrauma return GameMain.NetworkMember?.ServerSettings?.AllowLinkingWifiToChat ?? true; case ConditionType.IsSwappableItem: { - return entity is Item item && item.Prefab.SwappableItem != null; + return entity is Item item && item.Prefab.SwappableItem != null && Screen.Selected == GameMain.SubEditorScreen; } case ConditionType.AllowRotating: { - return entity is Item item && item.Prefab.AllowRotatingInEditor && Screen.Selected == GameMain.SubEditorScreen; + return entity is Item item && item.body == null && item.Prefab.AllowRotatingInEditor && Screen.Selected == GameMain.SubEditorScreen; } } return false; @@ -146,6 +146,7 @@ namespace Barotrauma { typeof(Vector4), "vector4" }, { typeof(Rectangle), "rectangle" }, { typeof(Color), "color" }, + { typeof(string[]), "stringarray" } }; private static readonly Dictionary> cachedProperties = @@ -273,6 +274,9 @@ namespace Barotrauma case "rectangle": PropertyInfo.SetValue(parentObject, XMLExtensions.ParseRect(value, true)); break; + case "stringarray": + PropertyInfo.SetValue(parentObject, XMLExtensions.ParseStringArray(value)); + break; } } @@ -345,6 +349,9 @@ namespace Barotrauma case "rectangle": PropertyInfo.SetValue(parentObject, XMLExtensions.ParseRect((string)value, false)); return true; + case "stringarray": + PropertyInfo.SetValue(parentObject, XMLExtensions.ParseStringArray((string)value)); + break; default: DebugConsole.ThrowError("Failed to set the value of the property \"" + Name + "\" of \"" + parentObject.ToString() + "\" to " + value.ToString()); DebugConsole.ThrowError("(Cannot convert a string to a " + PropertyType.ToString() + ")"); @@ -723,6 +730,10 @@ namespace Barotrauma case "rectangle": stringValue = XMLExtensions.RectToString((Rectangle)value); break; + case "stringarray": + string[] stringArray = (string[])value; + stringValue = stringArray != null ? string.Join(';', stringArray) : ""; + break; default: stringValue = value.ToString(); break; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Serialization/XMLExtensions.cs b/Barotrauma/BarotraumaShared/SharedSource/Serialization/XMLExtensions.cs index 0e6da820c..c66595a6c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Serialization/XMLExtensions.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Serialization/XMLExtensions.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Globalization; +using System.IO; using System.Linq; using System.Text; using System.Xml; @@ -8,21 +10,42 @@ using System.Xml.Linq; using Microsoft.Xna.Framework; using File = Barotrauma.IO.File; using FileStream = Barotrauma.IO.FileStream; +using Path = Barotrauma.IO.Path; namespace Barotrauma { public static class XMLExtensions { - public static string ParseContentPathFromUri(this XObject element) => ToolBox.ConvertAbsoluteToRelativePath(element.BaseUri); + private static ImmutableDictionary> converters + = new Dictionary>() + { + { typeof(string), (str, defVal) => str }, + { typeof(int), (str, defVal) => int.TryParse(str, NumberStyles.Any, CultureInfo.InvariantCulture, out int result) ? result : defVal }, + { typeof(uint), (str, defVal) => uint.TryParse(str, NumberStyles.Any, CultureInfo.InvariantCulture, out uint result) ? result : defVal }, + { typeof(UInt64), (str, defVal) => UInt64.TryParse(str, NumberStyles.Any, CultureInfo.InvariantCulture, out UInt64 result) ? result : defVal }, + { typeof(float), (str, defVal) => float.TryParse(str, NumberStyles.Any, CultureInfo.InvariantCulture, out float result) ? result : defVal }, + { typeof(bool), (str, defVal) => bool.TryParse(str, out bool result) ? result : defVal }, + { typeof(Color), (str, defVal) => ParseColor(str) }, + { typeof(Vector2), (str, defVal) => ParseVector2(str) }, + { typeof(Vector3), (str, defVal) => ParseVector3(str) }, + { typeof(Vector4), (str, defVal) => ParseVector4(str) }, + { typeof(Rectangle), (str, defVal) => ParseRect(str, true) } + }.ToImmutableDictionary(); + + public static string ParseContentPathFromUri(this XObject element) + => !string.IsNullOrWhiteSpace(element.BaseUri) + ? System.IO.Path.GetRelativePath(Environment.CurrentDirectory, element.BaseUri.CleanUpPath()) + : ""; public static readonly XmlReaderSettings ReaderSettings = new XmlReaderSettings { DtdProcessing = DtdProcessing.Prohibit, - XmlResolver = null + XmlResolver = null, + IgnoreWhitespace = true, }; - - public static XmlReader CreateReader(System.IO.Stream stream) - => XmlReader.Create(stream, ReaderSettings); + + public static XmlReader CreateReader(System.IO.Stream stream, string baseUri = "") + => XmlReader.Create(stream, ReaderSettings, baseUri); public static XDocument TryLoadXml(System.IO.Stream stream) { @@ -52,8 +75,8 @@ namespace Barotrauma { ToolBox.IsProperFilenameCase(filePath); using FileStream stream = File.Open(filePath, System.IO.FileMode.Open, System.IO.FileAccess.Read); - using XmlReader reader = CreateReader(stream); - doc = XDocument.Load(reader); + using XmlReader reader = CreateReader(stream, Path.GetFullPath(filePath)); + doc = XDocument.Load(reader, LoadOptions.SetBaseUri); } catch (Exception e) { @@ -79,7 +102,7 @@ namespace Barotrauma try { using FileStream stream = File.Open(filePath, System.IO.FileMode.Open, System.IO.FileAccess.Read); - using XmlReader reader = CreateReader(stream); + using XmlReader reader = CreateReader(stream, Path.GetFullPath(filePath)); doc = XDocument.Load(reader); } catch @@ -87,7 +110,7 @@ namespace Barotrauma return null; } - if (doc.Root == null) return null; + if (doc.Root == null) { return null; } } return doc; @@ -95,20 +118,18 @@ namespace Barotrauma public static object GetAttributeObject(XAttribute attribute) { - if (attribute == null) return null; + if (attribute == null) { return null; } return ParseToObject(attribute.Value.ToString()); } public static object ParseToObject(string value) { - float floatVal; - int intVal; - if (value.Contains(".") && Single.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out floatVal)) + if (value.Contains(".") && Single.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out float floatVal)) { return floatVal; } - if (Int32.TryParse(value, out intVal)) + if (Int32.TryParse(value, out int intVal)) { return intVal; } @@ -129,7 +150,7 @@ namespace Barotrauma public static string GetAttributeString(this XElement element, string name, string defaultValue) { - if (element?.Attribute(name) == null) return defaultValue; + if (element?.Attribute(name) == null) { return defaultValue; } return GetAttributeString(element.Attribute(name), defaultValue); } @@ -141,10 +162,10 @@ namespace Barotrauma public static string[] GetAttributeStringArray(this XElement element, string name, string[] defaultValue, bool trim = true, bool convertToLowerInvariant = false) { - if (element?.Attribute(name) == null) return defaultValue; + if (element?.Attribute(name) == null) { return defaultValue; } string stringValue = element.Attribute(name).Value; - if (string.IsNullOrEmpty(stringValue)) return defaultValue; + if (string.IsNullOrEmpty(stringValue)) { return defaultValue; } string[] splitValue = stringValue.Split(',', ','); @@ -168,11 +189,11 @@ namespace Barotrauma public static float GetAttributeFloat(this XElement element, float defaultValue, params string[] matchingAttributeName) { - if (element == null) return defaultValue; + if (element == null) { return defaultValue; } foreach (string name in matchingAttributeName) { - if (element.Attribute(name) == null) continue; + if (element.Attribute(name) == null) { continue; } float val; try @@ -197,7 +218,7 @@ namespace Barotrauma public static float GetAttributeFloat(this XElement element, string name, float defaultValue) { - if (element?.Attribute(name) == null) return defaultValue; + if (element?.Attribute(name) == null) { return defaultValue; } float val = defaultValue; try @@ -219,7 +240,7 @@ namespace Barotrauma public static float GetAttributeFloat(this XAttribute attribute, float defaultValue) { - if (attribute == null) return defaultValue; + if (attribute == null) { return defaultValue; } float val = defaultValue; @@ -242,10 +263,10 @@ namespace Barotrauma public static float[] GetAttributeFloatArray(this XElement element, string name, float[] defaultValue) { - if (element?.Attribute(name) == null) return defaultValue; + if (element?.Attribute(name) == null) { return defaultValue; } string stringValue = element.Attribute(name).Value; - if (string.IsNullOrEmpty(stringValue)) return defaultValue; + if (string.IsNullOrEmpty(stringValue)) { return defaultValue; } string[] splitValue = stringValue.Split(','); float[] floatValue = new float[splitValue.Length]; @@ -271,13 +292,16 @@ namespace Barotrauma public static int GetAttributeInt(this XElement element, string name, int defaultValue) { - if (element?.Attribute(name) == null) return defaultValue; + if (element?.Attribute(name) == null) { return defaultValue; } int val = defaultValue; try { - val = Int32.Parse(element.Attribute(name).Value, CultureInfo.InvariantCulture); + if (!Int32.TryParse(element.Attribute(name).Value, NumberStyles.Any, CultureInfo.InvariantCulture, out val)) + { + val = (int)float.Parse(element.Attribute(name).Value, CultureInfo.InvariantCulture); + } } catch (Exception e) { @@ -289,7 +313,7 @@ namespace Barotrauma public static uint GetAttributeUInt(this XElement element, string name, uint defaultValue) { - if (element?.Attribute(name) == null) return defaultValue; + if (element?.Attribute(name) == null) { return defaultValue; } uint val = defaultValue; @@ -307,7 +331,7 @@ namespace Barotrauma public static UInt64 GetAttributeUInt64(this XElement element, string name, UInt64 defaultValue) { - if (element?.Attribute(name) == null) return defaultValue; + if (element?.Attribute(name) == null) { return defaultValue; } UInt64 val = defaultValue; @@ -325,7 +349,7 @@ namespace Barotrauma public static UInt64 GetAttributeSteamID(this XElement element, string name, UInt64 defaultValue) { - if (element?.Attribute(name) == null) return defaultValue; + if (element?.Attribute(name) == null) { return defaultValue; } UInt64 val = defaultValue; @@ -343,10 +367,10 @@ namespace Barotrauma public static int[] GetAttributeIntArray(this XElement element, string name, int[] defaultValue) { - if (element?.Attribute(name) == null) return defaultValue; + if (element?.Attribute(name) == null) { return defaultValue; } string stringValue = element.Attribute(name).Value; - if (string.IsNullOrEmpty(stringValue)) return defaultValue; + if (string.IsNullOrEmpty(stringValue)) { return defaultValue; } string[] splitValue = stringValue.Split(','); int[] intValue = new int[splitValue.Length]; @@ -367,10 +391,10 @@ namespace Barotrauma } public static ushort[] GetAttributeUshortArray(this XElement element, string name, ushort[] defaultValue) { - if (element?.Attribute(name) == null) return defaultValue; + if (element?.Attribute(name) == null) { return defaultValue; } string stringValue = element.Attribute(name).Value; - if (string.IsNullOrEmpty(stringValue)) return defaultValue; + if (string.IsNullOrEmpty(stringValue)) { return defaultValue; } string[] splitValue = stringValue.Split(','); ushort[] ushortValue = new ushort[splitValue.Length]; @@ -392,13 +416,13 @@ namespace Barotrauma public static bool GetAttributeBool(this XElement element, string name, bool defaultValue) { - if (element?.Attribute(name) == null) return defaultValue; + if (element?.Attribute(name) == null) { return defaultValue; } return element.Attribute(name).GetAttributeBool(defaultValue); } public static bool GetAttributeBool(this XAttribute attribute, bool defaultValue) { - if (attribute == null) return defaultValue; + if (attribute == null) { return defaultValue; } string val = attribute.Value.ToLowerInvariant().Trim(); if (val == "true") @@ -416,31 +440,31 @@ namespace Barotrauma public static Point GetAttributePoint(this XElement element, string name, Point defaultValue) { - if (element?.Attribute(name) == null) return defaultValue; + if (element?.Attribute(name) == null) { return defaultValue; } return ParsePoint(element.Attribute(name).Value); } public static Vector2 GetAttributeVector2(this XElement element, string name, Vector2 defaultValue) { - if (element?.Attribute(name) == null) return defaultValue; + if (element?.Attribute(name) == null) { return defaultValue; } return ParseVector2(element.Attribute(name).Value); } public static Vector3 GetAttributeVector3(this XElement element, string name, Vector3 defaultValue) { - if (element == null || element.Attribute(name) == null) return defaultValue; + if (element == null || element.Attribute(name) == null) { return defaultValue; } return ParseVector3(element.Attribute(name).Value); } public static Vector4 GetAttributeVector4(this XElement element, string name, Vector4 defaultValue) { - if (element == null || element.Attribute(name) == null) return defaultValue; + if (element == null || element.Attribute(name) == null) { return defaultValue; } return ParseVector4(element.Attribute(name).Value); } public static Color GetAttributeColor(this XElement element, string name, Color defaultValue) { - if (element == null || element.Attribute(name) == null) return defaultValue; + if (element == null || element.Attribute(name) == null) { return defaultValue; } return ParseColor(element.Attribute(name).Value); } @@ -452,35 +476,54 @@ namespace Barotrauma public static Color[] GetAttributeColorArray(this XElement element, string name, Color[] defaultValue) { - if (element?.Attribute(name) == null) return defaultValue; + if (element?.Attribute(name) == null) { return defaultValue; } - string stringValue = element.Attribute(name).Value; - if (string.IsNullOrEmpty(stringValue)) return defaultValue; + string stringValue = element.Attribute(name).Value; + if (string.IsNullOrEmpty(stringValue)) { return defaultValue; } - string[] splitValue = stringValue.Split(';'); - Color[] colorValue = new Color[splitValue.Length]; - for (int i = 0; i < splitValue.Length; i++) + string[] splitValue = stringValue.Split(';'); + Color[] colorValue = new Color[splitValue.Length]; + for (int i = 0; i < splitValue.Length; i++) + { + try { - try - { - Color val = ParseColor(splitValue[i], true); - colorValue[i] = val; - } - catch (Exception e) - { - DebugConsole.ThrowError("Error in " + element + "! ", e); - } + Color val = ParseColor(splitValue[i], true); + colorValue[i] = val; } + catch (Exception e) + { + DebugConsole.ThrowError("Error in " + element + "! ", e); + } + } - return colorValue; + return colorValue; } public static Rectangle GetAttributeRect(this XElement element, string name, Rectangle defaultValue) { - if (element == null || element.Attribute(name) == null) return defaultValue; + if (element == null || element.Attribute(name) == null) { return defaultValue; } return ParseRect(element.Attribute(name).Value, false); } + //TODO: nested tuples and and n-uples where n!=2 are unsupported + public static (T1, T2) GetAttributeTuple(this XElement element, string name, (T1, T2) defaultValue) + { + string strValue = element.GetAttributeString(name, $"({defaultValue.Item1}, {defaultValue.Item2})").Trim(); + + return ParseTuple(strValue, defaultValue); + } + + public static (T1, T2)[] GetAttributeTupleArray(this XElement element, string name, + (T1, T2)[] defaultValue) + { + if (element?.Attribute(name) == null) { return defaultValue; } + + string stringValue = element.Attribute(name).Value; + if (string.IsNullOrEmpty(stringValue)) { return defaultValue; } + + return stringValue.Split(';').Select(s => ParseTuple(s, default)).ToArray(); + } + public static string ElementInnerText(this XElement el) { StringBuilder str = new StringBuilder(); @@ -516,15 +559,33 @@ namespace Barotrauma vector.W.ToString(format, CultureInfo.InvariantCulture); } + [Obsolete("Prefer XMLExtensions.ToStringHex")] public static string ColorToString(Color color) - { - return color.R + "," + color.G + "," + color.B + "," + color.A; - } + => $"{color.R},{color.G},{color.B},{color.A}"; + + public static string ToStringHex(this Color color) + => $"#{color.R:X2}{color.G:X2}{color.B:X2}" + + ((color.A < 255) ? $"{color.A:X2}" : ""); public static string RectToString(Rectangle rect) { return rect.X + "," + rect.Y + "," + rect.Width + "," + rect.Height; } + + public static (T1, T2) ParseTuple(string strValue, (T1, T2) defaultValue) + { + strValue = strValue.Trim(); + //require parentheses + if (strValue[0] != '(' || strValue[^1] != ')') { return defaultValue; } + //remove parentheses + strValue = strValue[1..^1]; + + string[] elems = strValue.Split(','); + if (elems.Length != 2) { return defaultValue; } + + return ((T1)converters[typeof(T1)].Invoke(elems[0], defaultValue.Item1), + (T2)converters[typeof(T2)].Invoke(elems[1], defaultValue.Item2)); + } public static Point ParsePoint(string stringPoint, bool errorMessages = true) { @@ -533,7 +594,7 @@ namespace Barotrauma if (components.Length != 2) { - if (!errorMessages) return point; + if (!errorMessages) { return point; } DebugConsole.ThrowError("Failed to parse the string \"" + stringPoint + "\" to Vector2"); return point; } @@ -551,7 +612,7 @@ namespace Barotrauma if (components.Length != 2) { - if (!errorMessages) return vector; + if (!errorMessages) { return vector; } DebugConsole.ThrowError("Failed to parse the string \"" + stringVector2 + "\" to Vector2"); return vector; } @@ -570,7 +631,7 @@ namespace Barotrauma if (components.Length != 3) { - if (!errorMessages) return vector; + if (!errorMessages) { return vector; } DebugConsole.ThrowError("Failed to parse the string \"" + stringVector3 + "\" to Vector3"); return vector; } @@ -590,7 +651,7 @@ namespace Barotrauma if (components.Length < 3) { - if (errorMessages) DebugConsole.ThrowError("Failed to parse the string \"" + stringVector4 + "\" to Vector4"); + if (errorMessages) { DebugConsole.ThrowError("Failed to parse the string \"" + stringVector4 + "\" to Vector4"); } return vector; } @@ -598,7 +659,9 @@ namespace Barotrauma Single.TryParse(components[1], NumberStyles.Float, CultureInfo.InvariantCulture, out vector.Y); Single.TryParse(components[2], NumberStyles.Float, CultureInfo.InvariantCulture, out vector.Z); if (components.Length > 3) + { Single.TryParse(components[3], NumberStyles.Float, CultureInfo.InvariantCulture, out vector.W); + } return vector; } @@ -638,8 +701,7 @@ namespace Barotrauma { stringColor = stringColor.Substring(1); - int colorInt = 0; - if (int.TryParse(stringColor, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out colorInt)) + if (int.TryParse(stringColor, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out int colorInt)) { if (stringColor.Length == 6) { @@ -656,7 +718,7 @@ namespace Barotrauma if (hexFailed) { - if (errorMessages) DebugConsole.ThrowError("Failed to parse the string \"" + stringColor + "\" to Color"); + if (errorMessages) { DebugConsole.ThrowError("Failed to parse the string \"" + stringColor + "\" to Color"); } return Color.White; } } @@ -686,7 +748,7 @@ namespace Barotrauma string[] strComponents = stringRect.Split(','); if ((strComponents.Length < 3 && requireSize) || strComponents.Length < 2) { - if (errorMessages) DebugConsole.ThrowError("Failed to parse the string \"" + stringRect + "\" to Rectangle"); + if (errorMessages) { DebugConsole.ThrowError("Failed to parse the string \"" + stringRect + "\" to Rectangle"); } return new Rectangle(0, 0, 0, 0); } @@ -713,6 +775,11 @@ namespace Barotrauma return floatArray; } + public static string[] ParseStringArray(string stringArrayValues) + { + return string.IsNullOrEmpty(stringArrayValues) ? new string[0] : stringArrayValues.Split(';'); + } + public static bool IsOverride(this XElement element) => element.Name.ToString().Equals("override", StringComparison.OrdinalIgnoreCase); public static bool IsCharacterVariant(this XElement element) => element.Name.ToString().Equals("charactervariant", StringComparison.OrdinalIgnoreCase); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Sprite/Sprite.cs b/Barotrauma/BarotraumaShared/SharedSource/Sprite/Sprite.cs index 6c0eccc0e..db53a6db6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Sprite/Sprite.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Sprite/Sprite.cs @@ -151,6 +151,12 @@ namespace Barotrauma { sourceVector = overrideElement.GetAttributeVector4("sourcerect", Vector4.Zero); } + if ((overrideElement ?? SourceElement).Attribute("sheetindex") != null) + { + Point sheetElementSize = (overrideElement ?? SourceElement).GetAttributePoint("sheetelementsize", Point.Zero); + Point sheetIndex = (overrideElement ?? SourceElement).GetAttributePoint("sheetindex", Point.Zero); + sourceVector = new Vector4(sheetIndex.X * sheetElementSize.X, sheetIndex.Y * sheetElementSize.Y, sheetElementSize.X, sheetElementSize.Y); + } Compress = SourceElement.GetAttributeBool("compress", true); bool shouldReturn = false; if (!lazyLoad) @@ -236,7 +242,7 @@ namespace Barotrauma { lock (list) { - list.RemoveAll(wRef => !wRef.TryGetTarget(out Sprite s) || s==this); + list.RemoveAll(wRef => !wRef.TryGetTarget(out Sprite s) || s == this); } DisposeTexture(); } @@ -294,6 +300,12 @@ namespace Barotrauma { sourceRect = overrideElement.GetAttributeRect("sourcerect", Rectangle.Empty); } + if ((overrideElement ?? SourceElement).Attribute("sheetindex") != null) + { + Point sheetElementSize = (overrideElement ?? SourceElement).GetAttributePoint("sheetelementsize", Point.Zero); + Point sheetIndex = (overrideElement ?? SourceElement).GetAttributePoint("sheetindex", Point.Zero); + sourceRect = new Rectangle(sheetIndex.X * sheetElementSize.X, sheetIndex.Y * sheetElementSize.Y, sheetElementSize.X, sheetElementSize.Y); + } size = SourceElement.GetAttributeVector2("size", Vector2.One); size.X *= sourceRect.Width; size.Y *= sourceRect.Height; diff --git a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/PropertyConditional.cs b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/PropertyConditional.cs index 6925b0dac..258bd5466 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/PropertyConditional.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/PropertyConditional.cs @@ -60,6 +60,8 @@ namespace Barotrauma // Only used by conditionals targeting an item. By default, containers check the parent item. This allows you to check the grandparent instead. public readonly bool TargetGrandParent; + public readonly bool TargetContainedItem; + // Remove this after refactoring public static bool IsValid(XAttribute attribute) { @@ -112,6 +114,7 @@ namespace Barotrauma TargetContainer = attribute.Parent.GetAttributeBool("targetcontainer", false); TargetSelf = attribute.Parent.GetAttributeBool("targetself", false); TargetGrandParent = attribute.Parent.GetAttributeBool("targetgrandparent", false); + TargetContainedItem = attribute.Parent.GetAttributeBool("targetcontaineditem", false); if (!Enum.TryParse(AttributeName, true, out Type)) { @@ -171,6 +174,22 @@ namespace Barotrauma public bool Matches(ISerializableEntity target) { + if (TargetContainedItem) + { + if (target is Item item) + { + return item.ContainedItems.Any(it => Matches(it)); + } + else if (target is Items.Components.ItemComponent ic) + { + return ic.Item.ContainedItems.Any(it => Matches(it)); + } + else if (target is Character character) + { + return character.Inventory != null && character.Inventory.AllItems.Any(it => Matches(it)); + } + } + switch (Type) { case ConditionType.PropertyValue: diff --git a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs index 41c47a0b7..6bb5586c1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs @@ -137,12 +137,14 @@ namespace Barotrauma public readonly ItemPrefab ItemPrefab; public readonly SpawnPositionType SpawnPosition; + public readonly bool SpawnIfInventoryFull; public readonly float Speed; public readonly float Rotation; public readonly int Count; public readonly float Spread; public readonly SpawnRotationType RotationType; public readonly float AimSpread; + public readonly bool Equip; public ItemSpawnInfo(XElement element, string parentDebugName) { @@ -173,12 +175,14 @@ namespace Barotrauma } } + SpawnIfInventoryFull = element.GetAttributeBool("spawnifinventoryfull", false); Speed = element.GetAttributeFloat("speed", 0.0f); Rotation = element.GetAttributeFloat("rotation", 0.0f); Count = element.GetAttributeInt("count", 1); Spread = element.GetAttributeFloat("spread", 0f); AimSpread = element.GetAttributeFloat("aimspread", 0f); + Equip = element.GetAttributeBool("equip", false); string spawnTypeStr = element.GetAttributeString("spawnposition", "This"); if (!Enum.TryParse(spawnTypeStr, ignoreCase: true, out SpawnPosition)) @@ -193,6 +197,18 @@ namespace Barotrauma } } + public class GiveTalentInfo + { + public string[] TalentIdentifiers; + public bool GiveRandom; + + public GiveTalentInfo(XElement element, string parentDebugName) + { + TalentIdentifiers = element.GetAttributeStringArray("talentidentifiers", new string[0], convertToLowerInvariant: true); + GiveRandom = element.GetAttributeBool("giverandom", false); + } + } + public class CharacterSpawnInfo : ISerializableEntity { public string Name => $"Character Spawn Info ({SpeciesName})"; @@ -223,6 +239,11 @@ namespace Barotrauma private readonly TargetType targetTypes; protected HashSet targetIdentifiers; + /// + /// Index of the slot the target must be in when targeting a Contained item + /// + public int TargetSlot = -1; + private readonly List requiredItems; public readonly string[] propertyNames; @@ -262,7 +283,11 @@ namespace Barotrauma public readonly List Explosions; private readonly List spawnItems; + private readonly bool spawnItemRandomly; private readonly List spawnCharacters; + + public readonly List giveTalentInfos; + private readonly List aiTriggers; private readonly List triggeredEvents; @@ -294,7 +319,12 @@ namespace Barotrauma get { return targetIdentifiers; } } - public HashSet AllowedAfflictions { get; private set; } + /// + /// Which type of afflictions the target must receive for the StatusEffect to be applied. Only valid when the type of the effect is OnDamaged. + /// + private readonly HashSet<(string affliction, float strength)> requiredAfflictions; + + public float AfflictionMultiplier = 1.0f; public List Afflictions { @@ -302,12 +332,17 @@ namespace Barotrauma private set; } + private readonly bool modifyAfflictionsByMaxVitality; + public IEnumerable SpawnCharacters { get { return spawnCharacters; } } - public readonly List> ReduceAffliction; + public readonly List<(string affliction, float amount)> ReduceAffliction; + + private readonly List giveExperiences; + private readonly List<(string identifier, float amount)> giveSkills; public float Duration => duration; @@ -351,18 +386,26 @@ namespace Barotrauma { requiredItems = new List(); spawnItems = new List(); + spawnItemRandomly = element.GetAttributeBool("spawnitemrandomly", false); spawnCharacters = new List(); + giveTalentInfos = new List(); aiTriggers = new List(); Afflictions = new List(); Explosions = new List(); triggeredEvents = new List(); - ReduceAffliction = new List>(); + ReduceAffliction = new List<(string affliction, float amount)>(); + giveExperiences = new List(); + giveSkills = new List<(string, float)>(); + modifyAfflictionsByMaxVitality = element.GetAttributeBool("multiplyafflictionsbymaxvitality", false); + tags = new HashSet(element.GetAttributeString("tags", "").Split(',')); OnlyInside = element.GetAttributeBool("onlyinside", false); OnlyOutside = element.GetAttributeBool("onlyoutside", false); OnlyPlayerTriggered = element.GetAttributeBool("onlyplayertriggered", false); AllowWhenBroken = element.GetAttributeBool("allowwhenbroken", false); + TargetSlot = element.GetAttributeInt("targetslot", -1); + Range = element.GetAttributeFloat("range", 0.0f); Offset = element.GetAttributeVector2("offset", Vector2.Zero); string[] targetLimbNames = element.GetAttributeStringArray("targetlimb", null) ?? element.GetAttributeStringArray("targetlimbs", null); @@ -430,11 +473,12 @@ namespace Barotrauma } break; case "allowedafflictions": + case "requiredafflictions": string[] types = attribute.Value.Split(','); - AllowedAfflictions = new HashSet(); + requiredAfflictions ??= new HashSet<(string, float)>(); for (int i = 0; i < types.Length; i++) { - AllowedAfflictions.Add(types[i].Trim().ToLowerInvariant()); + requiredAfflictions.Add((types[i].Trim().ToLowerInvariant(), 0.0f)); } break; case "duration": @@ -486,13 +530,22 @@ namespace Barotrauma } } + if (duration > 0.0f && !setValue) + { + //a workaround to "tags" possibly meaning either an item's tags or this status effect's tags: + //if the status effect has a duration, assume tags mean this status effect's tags and leave item tags untouched. + propertyAttributes.RemoveAll(a => a.Name.ToString().Equals("tags", StringComparison.OrdinalIgnoreCase)); + } + int count = propertyAttributes.Count; + propertyNames = new string[count]; propertyEffects = new object[count]; int n = 0; foreach (XAttribute attribute in propertyAttributes) { + propertyNames[n] = attribute.Name.ToString().ToLowerInvariant(); propertyEffects[n] = XMLExtensions.GetAttributeObject(attribute); n++; @@ -536,6 +589,16 @@ namespace Barotrauma } requiredItems.Add(newRequiredItem); break; + case "requiredaffliction": + requiredAfflictions ??= new HashSet<(string, float)>(); + string[] ids = subElement.GetAttributeStringArray("identifier", null) ?? subElement.GetAttributeStringArray("type", new string[0]); + foreach (string afflictionId in ids) + { + requiredAfflictions.Add(( + afflictionId, + subElement.GetAttributeFloat("minstrength", 0.0f))); + } + break; case "conditional": foreach (XAttribute attribute in subElement.Attributes()) { @@ -578,7 +641,7 @@ namespace Barotrauma if (subElement.Attribute("name") != null) { DebugConsole.ThrowError("Error in StatusEffect (" + parentDebugName + ") - define afflictions using identifiers or types instead of names."); - ReduceAffliction.Add(new Pair( + ReduceAffliction.Add(( subElement.GetAttributeString("name", "").ToLowerInvariant(), subElement.GetAttributeFloat(1.0f, "amount", "strength", "reduceamount"))); } @@ -589,9 +652,7 @@ namespace Barotrauma if (AfflictionPrefab.List.Any(ap => ap.Identifier == name || ap.AfflictionType == name)) { - ReduceAffliction.Add(new Pair( - name, - subElement.GetAttributeFloat(1.0f, "amount", "strength", "reduceamount"))); + ReduceAffliction.Add((name, subElement.GetAttributeFloat(1.0f, "amount", "strength", "reduceamount"))); } else { @@ -623,9 +684,19 @@ namespace Barotrauma var newSpawnCharacter = new CharacterSpawnInfo(subElement, parentDebugName); if (!string.IsNullOrWhiteSpace(newSpawnCharacter.SpeciesName)) { spawnCharacters.Add(newSpawnCharacter); } break; + case "givetalentinfo": + var newGiveTalentInfo = new GiveTalentInfo(subElement, parentDebugName); + if (newGiveTalentInfo.TalentIdentifiers.Any()) { giveTalentInfos.Add(newGiveTalentInfo); } + break; case "aitrigger": aiTriggers.Add(new AITrigger(subElement)); break; + case "giveexperience": + giveExperiences.Add(subElement.GetAttributeInt("amount", 0)); + break; + case "giveskill": + giveSkills.Add((subElement.GetAttributeString("skillidentifier", ""), subElement.GetAttributeFloat("amount", 0))); + break; } } InitProjSpecific(element, parentDebugName); @@ -651,6 +722,17 @@ namespace Barotrauma return false; } + public bool HasRequiredAfflictions(AttackResult attackResult) + { + if (requiredAfflictions == null) { return true; } + if (attackResult.Afflictions == null) { return false; } + if (attackResult.Afflictions.None(a => requiredAfflictions.Any(a2 => a.Strength >= a2.strength && (a.Identifier == a2.affliction || a.Prefab.AfflictionType == a2.affliction)))) + { + return false; + } + return true; + } + public virtual bool HasRequiredItems(Entity entity) { if (entity == null) { return true; } @@ -742,7 +824,16 @@ namespace Barotrauma { var target = targets.FirstOrDefault(t => t is Item || t is ItemComponent); var targetItem = target as Item ?? (target as ItemComponent)?.Item; - if (targetItem?.ParentInventory == null) { continue; } + if (targetItem?.ParentInventory == null) + { + //if we're checking for inequality, not being inside a valid container counts as success + //(not inside a container = the container doesn't have a specific tag/value) + if (pc.Operator == PropertyConditional.OperatorType.NotEquals) + { + return true; + } + continue; + } var owner = targetItem.ParentInventory.Owner; if (pc.TargetGrandParent && owner is Item ownerItem) { @@ -760,7 +851,7 @@ namespace Barotrauma if (HasRequiredConditions(container.AllPropertyObjects, pc.ToEnumerable(), targetingContainer: true)) { return true; } } } - if (owner is Character character && HasRequiredConditions(character.ToEnumerable(), pc.ToEnumerable(), targetingContainer: true)) { return true; } + if (owner is Character character && HasRequiredConditions(character.ToEnumerable(), pc.ToEnumerable(), targetingContainer: true)) { return true; } } else { @@ -785,7 +876,16 @@ namespace Barotrauma { var target = targets.FirstOrDefault(t => t is Item || t is ItemComponent); var targetItem = target as Item ?? (target as ItemComponent)?.Item; - if (targetItem?.ParentInventory == null) { return false; } + if (targetItem?.ParentInventory == null) + { + //if we're checking for inequality, not being inside a valid container counts as success + //(not inside a container = the container doesn't have a specific tag/value) + if (pc.Operator == PropertyConditional.OperatorType.NotEquals) + { + continue; + } + return false; + } var owner = targetItem.ParentInventory.Owner; if (pc.TargetGrandParent && owner is Item ownerItem) { @@ -1097,13 +1197,10 @@ namespace Barotrauma { if (Rand.Value(Rand.RandSync.Unsynced) > affliction.Probability) { continue; } Affliction newAffliction = affliction; - if (!disableDeltaTime && !setValue) - { - newAffliction = affliction.CreateMultiplied(deltaTime); - } if (target is Character character) { if (character.Removed) { continue; } + newAffliction = GetMultipliedAffliction(affliction, entity, character, deltaTime, modifyAfflictionsByMaxVitality); character.LastDamageSource = entity; foreach (Limb limb in character.AnimController.Limbs) { @@ -1112,6 +1209,7 @@ namespace Barotrauma if (targetLimbs != null && !targetLimbs.Contains(limb.type)) { continue; } AttackResult result = limb.character.DamageLimb(position, limb, newAffliction.ToEnumerable(), stun: 0.0f, playSound: false, attackImpulse: 0.0f, attacker: affliction.Source, allowStacking: !setValue); limb.character.TrySeverLimbJoints(limb, SeverLimbsProbability, disableDeltaTime ? result.Damage : result.Damage / deltaTime, allowBeheading: true); + RegisterTreatmentResults(entity, limb, affliction, result); //only apply non-limb-specific afflictions to the first limb if (!affliction.Prefab.LimbSpecific) { break; } } @@ -1120,14 +1218,15 @@ namespace Barotrauma { if (limb.IsSevered) { continue; } if (limb.character.Removed || limb.Removed) { continue; } + newAffliction = GetMultipliedAffliction(affliction, entity, limb.character, deltaTime, modifyAfflictionsByMaxVitality); AttackResult result = limb.character.DamageLimb(position, limb, newAffliction.ToEnumerable(), stun: 0.0f, playSound: false, attackImpulse: 0.0f, attacker: affliction.Source, allowStacking: !setValue); limb.character.TrySeverLimbJoints(limb, SeverLimbsProbability, disableDeltaTime ? result.Damage : result.Damage / deltaTime, allowBeheading: true); + RegisterTreatmentResults(entity, limb, affliction, result); } } - foreach (Pair reduceAffliction in ReduceAffliction) + foreach (var (affliction, amount) in ReduceAffliction) { - float reduceAmount = disableDeltaTime || setValue ? reduceAffliction.Second : reduceAffliction.Second * deltaTime; Limb targetLimb = null; Character targetCharacter = null; if (target is Character character) @@ -1141,8 +1240,11 @@ namespace Barotrauma } if (targetCharacter != null && !targetCharacter.Removed) { + ActionType? actionType = null; + if (entity is Item item && item.UseInHealthInterface) { actionType = type; } + float reduceAmount = amount * GetAfflictionMultiplier(entity, targetCharacter, deltaTime); float prevVitality = targetCharacter.Vitality; - targetCharacter.CharacterHealth.ReduceAffliction(targetLimb, reduceAffliction.First, reduceAmount); + targetCharacter.CharacterHealth.ReduceAffliction(targetLimb, affliction, reduceAmount, treatmentAction: actionType); if (user != null && user != targetCharacter) { if (!targetCharacter.IsDead) @@ -1181,6 +1283,71 @@ namespace Barotrauma } } } + + if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient) + { + // these effects do not need to be run clientside, as they are replicated from server to clients anyway + + foreach (int giveExperience in giveExperiences) + { + Character targetCharacter = CharacterFromTarget(target); + if (targetCharacter != null && !targetCharacter.Removed) + { + targetCharacter?.Info?.GiveExperience(giveExperience); + } + } + + if (giveSkills.Any()) + { + foreach ((string skillIdentifier, float amount) in giveSkills) + { + Character targetCharacter = CharacterFromTarget(target); + if (targetCharacter != null && !targetCharacter.Removed) + { + if (skillIdentifier?.ToLowerInvariant() == "randomskill") + { + targetCharacter.Info?.IncreaseSkillLevel(GetRandomSkill(), amount); + + string GetRandomSkill() + { + return targetCharacter.Info?.Job?.Skills.Select(s => s.Identifier).GetRandom(); + } + } + else + { + targetCharacter.Info?.IncreaseSkillLevel(skillIdentifier?.ToLowerInvariant(), amount); + } + } + } + } + + if (giveTalentInfos.Any()) + { + Character targetCharacter = CharacterFromTarget(target); + if (targetCharacter?.Info == null) { continue; } + if (!TalentTree.JobTalentTrees.TryGetValue(targetCharacter.Info.Job.Prefab.Identifier, out TalentTree talentTree)) { continue; } + // for the sake of technical simplicity, for now do not allow talents to be given if the character could unlock them in their talent tree as well + IEnumerable disallowedTalents = talentTree.TalentSubTrees.SelectMany(s => s.TalentOptionStages.SelectMany(o => o.Talents.Select(t => t.Identifier))); + + foreach (GiveTalentInfo giveTalentInfo in giveTalentInfos) + { + IEnumerable viableTalents = giveTalentInfo.TalentIdentifiers.Where(s => !targetCharacter.Info.UnlockedTalents.Contains(s) && !disallowedTalents.Contains(s)); + if (viableTalents.None()) { continue; } + + if (giveTalentInfo.GiveRandom) + { + targetCharacter.GiveTalent(viableTalents.GetRandom(), true); + } + else + { + foreach (string talent in viableTalents) + { + targetCharacter.GiveTalent(talent, true); + } + } + } + } + } } if (FireSize > 0.0f && entity != null) @@ -1243,109 +1410,148 @@ namespace Barotrauma }); } } - foreach (ItemSpawnInfo itemSpawnInfo in spawnItems) - { - for (int i = 0; i < itemSpawnInfo.Count; i++) - { - switch (itemSpawnInfo.SpawnPosition) - { - case ItemSpawnInfo.SpawnPositionType.This: - Entity.Spawner.AddToSpawnQueue(itemSpawnInfo.ItemPrefab, position + Rand.Vector(itemSpawnInfo.Spread, Rand.RandSync.Server), onSpawned: newItem => - { - Projectile projectile = newItem.GetComponent(); - if (projectile != null && user != null && sourceBody != null && entity != null) - { - var rope = newItem.GetComponent(); - if (rope != null && sourceBody.UserData is Limb sourceLimb) - { - rope.Attach(sourceLimb, newItem); - } - float spread = MathHelper.ToRadians(Rand.Range(-itemSpawnInfo.AimSpread, itemSpawnInfo.AimSpread)); - var worldPos = sourceBody.Position; - float rotation = itemSpawnInfo.Rotation; - if (user.Submarine != null) - { - worldPos += user.Submarine.Position; - } - switch (itemSpawnInfo.RotationType) - { - case ItemSpawnInfo.SpawnRotationType.Fixed: - rotation = sourceBody.TransformRotation(itemSpawnInfo.Rotation); - break; - case ItemSpawnInfo.SpawnRotationType.Target: - rotation = MathUtils.VectorToAngle(entity.WorldPosition - worldPos); - break; - case ItemSpawnInfo.SpawnRotationType.Limb: - rotation = sourceBody.TransformedRotation; - break; - case ItemSpawnInfo.SpawnRotationType.Collider: - rotation = user.AnimController.Collider.Rotation; - break; - case ItemSpawnInfo.SpawnRotationType.MainLimb: - rotation = user.AnimController.MainLimb.body.TransformedRotation; - break; - default: - throw new NotImplementedException("Not implemented: " + itemSpawnInfo.RotationType); - } - rotation += MathHelper.ToRadians(itemSpawnInfo.Rotation * user.AnimController.Dir); - projectile.Shoot(user, ConvertUnits.ToSimUnits(worldPos), ConvertUnits.ToSimUnits(worldPos), rotation + spread, ignoredBodies: user.AnimController.Limbs.Where(l => !l.IsSevered).Select(l => l.body.FarseerBody).ToList(), createNetworkEvent: true); - } - else - { - newItem.body?.ApplyLinearImpulse(Rand.Vector(1) * itemSpawnInfo.Speed); - newItem.Rotation = itemSpawnInfo.Rotation; - } - }); - break; - case ItemSpawnInfo.SpawnPositionType.ThisInventory: - { - Inventory inventory = null; - if (entity is Character character && character.Inventory != null) - { - inventory = character.Inventory; - } - else if (entity is Item item) - { - inventory = item?.GetComponent()?.Inventory; - } - if (inventory != null && inventory.CanBePut(itemSpawnInfo.ItemPrefab)) - { - Entity.Spawner.AddToSpawnQueue(itemSpawnInfo.ItemPrefab, inventory, spawnIfInventoryFull: false); - } - } - break; - case ItemSpawnInfo.SpawnPositionType.ContainedInventory: - { - Inventory thisInventory = null; - if (entity is Character character) - { - thisInventory = character.Inventory; - } - else if (entity is Item item) - { - thisInventory = item?.GetComponent()?.Inventory; - } - if (thisInventory != null) - { - foreach (Item item in thisInventory.AllItems) - { - Inventory containedInventory = item.GetComponent()?.Inventory; - if (containedInventory != null && containedInventory.CanBePut(itemSpawnInfo.ItemPrefab)) - { - Entity.Spawner.AddToSpawnQueue(itemSpawnInfo.ItemPrefab, containedInventory, spawnIfInventoryFull: false); - } - break; - } - } - } - break; + if (spawnItemRandomly) + { + SpawnItem(spawnItems.GetRandom()); + } + else + { + foreach (ItemSpawnInfo itemSpawnInfo in spawnItems) + { + for (int i = 0; i < itemSpawnInfo.Count; i++) + { + SpawnItem(itemSpawnInfo); } } } + + + void SpawnItem(ItemSpawnInfo chosenItemSpawnInfo) + { + switch (chosenItemSpawnInfo.SpawnPosition) + { + case ItemSpawnInfo.SpawnPositionType.This: + Entity.Spawner.AddToSpawnQueue(chosenItemSpawnInfo.ItemPrefab, position + Rand.Vector(chosenItemSpawnInfo.Spread, Rand.RandSync.Server), onSpawned: newItem => + { + Projectile projectile = newItem.GetComponent(); + if (projectile != null && user != null && sourceBody != null && entity != null) + { + var rope = newItem.GetComponent(); + if (rope != null && sourceBody.UserData is Limb sourceLimb) + { + rope.Attach(sourceLimb, newItem); + } + + float spread = MathHelper.ToRadians(Rand.Range(-chosenItemSpawnInfo.AimSpread, chosenItemSpawnInfo.AimSpread)); + var worldPos = sourceBody.Position; + float rotation = chosenItemSpawnInfo.Rotation; + if (user.Submarine != null) + { + worldPos += user.Submarine.Position; + } + switch (chosenItemSpawnInfo.RotationType) + { + case ItemSpawnInfo.SpawnRotationType.Fixed: + rotation = sourceBody.TransformRotation(chosenItemSpawnInfo.Rotation); + break; + case ItemSpawnInfo.SpawnRotationType.Target: + rotation = MathUtils.VectorToAngle(entity.WorldPosition - worldPos); + break; + case ItemSpawnInfo.SpawnRotationType.Limb: + rotation = sourceBody.TransformedRotation; + break; + case ItemSpawnInfo.SpawnRotationType.Collider: + rotation = user.AnimController.Collider.Rotation; + break; + case ItemSpawnInfo.SpawnRotationType.MainLimb: + rotation = user.AnimController.MainLimb.body.TransformedRotation; + break; + default: + throw new NotImplementedException("Not implemented: " + chosenItemSpawnInfo.RotationType); + } + rotation += MathHelper.ToRadians(chosenItemSpawnInfo.Rotation * user.AnimController.Dir); + projectile.Shoot(user, ConvertUnits.ToSimUnits(worldPos), ConvertUnits.ToSimUnits(worldPos), rotation + spread, ignoredBodies: user.AnimController.Limbs.Where(l => !l.IsSevered).Select(l => l.body.FarseerBody).ToList(), createNetworkEvent: true); + } + else + { + newItem.body?.ApplyLinearImpulse(Rand.Vector(1) * chosenItemSpawnInfo.Speed); + newItem.Rotation = chosenItemSpawnInfo.Rotation; + } + }); + break; + case ItemSpawnInfo.SpawnPositionType.ThisInventory: + { + Inventory inventory = null; + if (entity is Character character && character.Inventory != null) + { + inventory = character.Inventory; + } + else if (entity is Item item) + { + inventory = item?.GetComponent()?.Inventory; + } + if (inventory != null && (inventory.CanBePut(chosenItemSpawnInfo.ItemPrefab) || chosenItemSpawnInfo.SpawnIfInventoryFull)) + { + Entity.Spawner.AddToSpawnQueue(chosenItemSpawnInfo.ItemPrefab, inventory, spawnIfInventoryFull: chosenItemSpawnInfo.SpawnIfInventoryFull, onSpawned: item => + { + if (chosenItemSpawnInfo.Equip && entity is Character character && character.Inventory != null) + { + //if the item is both pickable and wearable, try to wear it instead of picking it up + List allowedSlots = + item.GetComponents().Count() > 1 ? + new List(item.GetComponent()?.AllowedSlots ?? item.GetComponent().AllowedSlots) : + new List(item.AllowedSlots); + allowedSlots.Remove(InvSlotType.Any); + character.Inventory.TryPutItem(item, null, allowedSlots); + } + }); + } + } + break; + case ItemSpawnInfo.SpawnPositionType.ContainedInventory: + { + Inventory thisInventory = null; + if (entity is Character character) + { + thisInventory = character.Inventory; + } + else if (entity is Item item) + { + thisInventory = item?.GetComponent()?.Inventory; + } + if (thisInventory != null) + { + foreach (Item item in thisInventory.AllItems) + { + Inventory containedInventory = item.GetComponent()?.Inventory; + if (containedInventory != null && (containedInventory.CanBePut(chosenItemSpawnInfo.ItemPrefab) || chosenItemSpawnInfo.SpawnIfInventoryFull)) + { + Entity.Spawner.AddToSpawnQueue(chosenItemSpawnInfo.ItemPrefab, containedInventory, spawnIfInventoryFull: chosenItemSpawnInfo.SpawnIfInventoryFull); + } + break; + } + } + } + break; + } + } } ApplyProjSpecific(deltaTime, entity, targets, hull, position, playSound: true); + + static Character CharacterFromTarget(ISerializableEntity target) + { + Character targetCharacter = target as Character; + if (targetCharacter == null) + { + if (target is Limb targetLimb && !targetLimb.Removed) + { + targetCharacter = targetLimb.character; + } + } + return targetCharacter; + } } partial void ApplyProjSpecific(float deltaTime, Entity entity, IEnumerable targets, Hull currentHull, Vector2 worldPosition, bool playSound); @@ -1353,38 +1559,31 @@ namespace Barotrauma private void ApplyToProperty(ISerializableEntity target, SerializableProperty property, object value, float deltaTime) { if (disableDeltaTime || setValue) { deltaTime = 1.0f; } - Type type = value.GetType(); - if (type == typeof(float) || (type == typeof(int) && property.GetValue(target) is float)) + if (value is int || value is float) { - float floatValue = Convert.ToSingle(value) * deltaTime; - if (!setValue) + var propertyValue = property.GetValue(target); + if (propertyValue is float propertyValueF) { - floatValue += (float)property.GetValue(target); + float floatValue = Convert.ToSingle(value) * deltaTime; + if (!setValue) + { + floatValue += propertyValueF; + } + property.TrySetValue(target, floatValue); + return; } - property.TrySetValue(target, floatValue); - } - else if (type == typeof(int) && value is int) - { - int intValue = (int)((int)value * deltaTime); - if (!setValue) + else if (propertyValue is int integer) { - intValue += (int)property.GetValue(target); + int intValue = (int)(Convert.ToInt32(value) * deltaTime); + if (!setValue) + { + intValue += integer; + } + property.TrySetValue(target, intValue); + return; } - property.TrySetValue(target, intValue); - } - else if (type == typeof(bool) && value is bool) - { - property.TrySetValue(target, (bool)value); - } - else if (type == typeof(string)) - { - property.TrySetValue(target, (string)value); - } - else - { - DebugConsole.ThrowError("Couldn't apply value " + value.ToString() + " (" + type + ") to property \"" + property.Name + "\" (" + property.GetValue(target).GetType() + ")! " - + "Make sure the type of the value set in the config files matches the type of the property."); } + property.TrySetValue(target, value); } public static void UpdateAll(float deltaTime) @@ -1426,22 +1625,24 @@ namespace Barotrauma foreach (Affliction affliction in element.Parent.Afflictions) { - Affliction multipliedAffliction = affliction; - if (!element.Parent.disableDeltaTime && !element.Parent.setValue) { multipliedAffliction = affliction.CreateMultiplied(deltaTime); } - + Affliction newAffliction = affliction; if (target is Character character) { if (character.Removed) { continue; } - character.AddDamage(character.WorldPosition, multipliedAffliction.ToEnumerable(), stun: 0.0f, playSound: false, attacker: element.User); + newAffliction = element.Parent.GetMultipliedAffliction(affliction, element.Entity, character, deltaTime, element.Parent.modifyAfflictionsByMaxVitality); + var result = character.AddDamage(character.WorldPosition, newAffliction.ToEnumerable(), stun: 0.0f, playSound: false, attacker: element.User); + element.Parent.RegisterTreatmentResults(element.Entity, result.HitLimb, affliction, result); } else if (target is Limb limb) { if (limb.character.Removed || limb.Removed) { continue; } - limb.character.DamageLimb(limb.WorldPosition, limb, multipliedAffliction.ToEnumerable(), stun: 0.0f, playSound: false, attackImpulse: 0.0f, attacker: element.User); + newAffliction = element.Parent.GetMultipliedAffliction(affliction, element.Entity, limb.character, deltaTime, element.Parent.modifyAfflictionsByMaxVitality); + var result = limb.character.DamageLimb(limb.WorldPosition, limb, newAffliction.ToEnumerable(), stun: 0.0f, playSound: false, attackImpulse: 0.0f, attacker: element.User); + element.Parent.RegisterTreatmentResults(element.Entity, limb, affliction, result); } } - foreach (Pair reduceAffliction in element.Parent.ReduceAffliction) + foreach (var (affliction, amount) in element.Parent.ReduceAffliction) { Limb targetLimb = null; Character targetCharacter = null; @@ -1456,8 +1657,11 @@ namespace Barotrauma } if (targetCharacter != null && !targetCharacter.Removed) { + ActionType? actionType = null; + if (element.Entity is Item item && item.UseInHealthInterface) { actionType = element.Parent.type; } + float reduceAmount = amount * element.Parent.GetAfflictionMultiplier(element.Entity, targetCharacter, deltaTime); float prevVitality = targetCharacter.Vitality; - targetCharacter.CharacterHealth.ReduceAffliction(targetLimb, reduceAffliction.First, reduceAffliction.Second * deltaTime); + targetCharacter.CharacterHealth.ReduceAffliction(targetLimb, affliction, reduceAmount, treatmentAction: actionType); if (element.User != null && element.User != targetCharacter) { if (!targetCharacter.IsDead) @@ -1486,6 +1690,53 @@ namespace Barotrauma } } + private float GetAfflictionMultiplier(Entity entity, Character targetCharacter, float deltaTime) + { + float multiplier = !setValue && !disableDeltaTime ? deltaTime : 1.0f; + if (entity is Item sourceItem && sourceItem.HasTag("medical")) + { + multiplier *= 1 + targetCharacter.GetStatValue(StatTypes.MedicalItemEffectivenessMultiplier); + } + return multiplier * AfflictionMultiplier; + } + + private Affliction GetMultipliedAffliction(Affliction affliction, Entity entity, Character targetCharacter, float deltaTime, bool modifyByMaxVitality) + { + float afflictionMultiplier = GetAfflictionMultiplier(entity, targetCharacter, deltaTime); + if (modifyByMaxVitality) + { + afflictionMultiplier *= targetCharacter.MaxVitality / 100f; + } + + if (!MathUtils.NearlyEqual(afflictionMultiplier, 1.0f)) + { + return affliction.CreateMultiplied(afflictionMultiplier); + } + return affliction; + } + + private void RegisterTreatmentResults(Entity entity, Limb limb, Affliction affliction, AttackResult result) + { + if (entity is Item item && item.UseInHealthInterface && limb != null) + { + foreach (Affliction limbAffliction in limb.character.CharacterHealth.GetAllAfflictions()) + { + if (result.Afflictions != null && result.Afflictions.Any(a => a.Prefab == limbAffliction.Prefab) && + (!affliction.Prefab.LimbSpecific || limb.character.CharacterHealth.GetAfflictionLimb(affliction) == limb)) + { + if (type == ActionType.OnUse) + { + limbAffliction.AppliedAsSuccessfulTreatmentTime = Timing.TotalTime; + } + else if (type == ActionType.OnFailure) + { + limbAffliction.AppliedAsFailedTreatmentTime = Timing.TotalTime; + } + } + } + } + } + static partial void UpdateAllProjSpecific(float deltaTime); public static void StopAll() diff --git a/Barotrauma/BarotraumaShared/SharedSource/SteamAchievementManager.cs b/Barotrauma/BarotraumaShared/SharedSource/SteamAchievementManager.cs index 24c3db01d..f05045e14 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/SteamAchievementManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/SteamAchievementManager.cs @@ -49,7 +49,7 @@ namespace Barotrauma Reactor reactor = item.GetComponent(); if (reactor != null) { roundData.Reactors.Add(reactor); } } - pathFinder = new PathFinder(WayPoint.WayPointList, indoorsSteering: false); + pathFinder = new PathFinder(WayPoint.WayPointList, false); cachedDistances.Clear(); } @@ -192,7 +192,7 @@ namespace Barotrauma static CachedDistance CalculateNewCachedDistance(Character c) { - pathFinder ??= new PathFinder(WayPoint.WayPointList, indoorsSteering: false); + pathFinder ??= new PathFinder(WayPoint.WayPointList, false); var path = pathFinder.FindPath(ConvertUnits.ToSimUnits(c.WorldPosition), ConvertUnits.ToSimUnits(Submarine.MainSub.WorldPosition)); if (path.Unreachable) { return null; } return new CachedDistance(c.WorldPosition, Submarine.MainSub.WorldPosition, path.TotalLength, Timing.TotalTime + Rand.Range(1.0f, 5.0f)); @@ -455,6 +455,8 @@ namespace Barotrauma UnlockAchievement(character, character.Info.Job.Prefab.Identifier + "round"); } } + + pathFinder = null; } private static void UnlockAchievement(Character recipient, string identifier) diff --git a/Barotrauma/BarotraumaShared/SharedSource/TextManager.cs b/Barotrauma/BarotraumaShared/SharedSource/TextManager.cs index c7aa407ea..c19d2db20 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/TextManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/TextManager.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Text; using System.Text.RegularExpressions; using Barotrauma.Extensions; +using System.Xml.Linq; namespace Barotrauma { @@ -443,6 +444,60 @@ namespace Barotrauma } } + /// + /// Constructs a string from XML in a way that allows replacing one or more variables with hard-coded or localized values. Usage example in the method's comments. + /// + public static void ConstructDescription(ref string Description, XElement descriptionElement) + { + /* + + + + + + */ + + if (descriptionElement.GetAttributeBool("linebreak", false)) + { + Description += "\n"; + return; + } + + string descriptionTag = descriptionElement.GetAttributeString("tag", string.Empty); + string extraDescriptionLine = Get(descriptionTag); + if (string.IsNullOrEmpty(extraDescriptionLine)) { return; } + foreach (XElement replaceElement in descriptionElement.Elements()) + { + if (replaceElement.Name.ToString().ToLowerInvariant() != "replace") { continue; } + + string tag = replaceElement.GetAttributeString("tag", string.Empty); + string[] replacementValues = replaceElement.GetAttributeStringArray("value", new string[0]); + string replacementValue = string.Empty; + for (int i = 0; i < replacementValues.Length; i++) + { +#if DEBUG + if (!int.TryParse(replacementValues[i], out int _) && !float.TryParse(replacementValues[i], System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out float __) && !ContainsTag(replacementValues[i])) + { + DebugConsole.AddWarning($"Couldn't find the tag \"{replacementValues[i]}\" in text files for description \"{descriptionTag}\". Is the tag correct?"); + } +#endif + replacementValue += Get(replacementValues[i], returnNull: true) ?? replacementValues[i]; + if (i < replacementValues.Length - 1) + { + replacementValue += ", "; + } + } + if (replaceElement.Attribute("color") != null) + { + string colorStr = replaceElement.GetAttributeString("color", "255,255,255,255"); + replacementValue = $"‖color:{colorStr}‖{replacementValue}‖color:end‖"; + } + extraDescriptionLine = extraDescriptionLine.Replace(tag, replacementValue); + } + if (!string.IsNullOrEmpty(Description)) { Description += "\n"; } + Description += extraDescriptionLine; + } + public static string FormatServerMessage(string textId) { return $"{textId}~"; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Upgrades/Upgrade.cs b/Barotrauma/BarotraumaShared/SharedSource/Upgrades/Upgrade.cs index 1881729df..41039f752 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Upgrades/Upgrade.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Upgrades/Upgrade.cs @@ -147,7 +147,7 @@ namespace Barotrauma /// private static float ApplyPercentage(float value, float amount, int times) { - return times <= 0 ? value : ApplyPercentage(value + (value * amount / 100), amount, --times); + return (1f + (amount / 100f * times)) * value; } public static PropertyReference[] ParseAttributes(IEnumerable attributes, Upgrade upgrade) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Upgrades/UpgradePrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Upgrades/UpgradePrefab.cs index 06f4c29fc..2a62d2b4e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Upgrades/UpgradePrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Upgrades/UpgradePrefab.cs @@ -68,7 +68,13 @@ namespace Barotrauma Name = element.GetAttributeString("name", string.Empty); IsWallUpgrade = element.GetAttributeBool("wallupgrade", false); - if (string.IsNullOrWhiteSpace(Name)) + string nameIdentifier = element.GetAttributeString("nameidentifier", ""); + + if (!string.IsNullOrWhiteSpace(nameIdentifier)) + { + Name = TextManager.Get($"{nameIdentifier}", returnNull: true) ?? string.Empty; + } + else if (string.IsNullOrWhiteSpace(Name)) { Name = TextManager.Get($"UpgradeCategory.{Identifier}", true) ?? string.Empty; } @@ -131,6 +137,8 @@ namespace Barotrauma public string Description { get; } + public float IncreaseOnTooltip { get; } + public string Identifier { get; } public string FilePath { get; } @@ -172,7 +180,13 @@ namespace Barotrauma var targetProperties = new Dictionary(); - if (string.IsNullOrWhiteSpace(Name)) + string nameIdentifier = element.GetAttributeString("nameidentifier", ""); + + if (!string.IsNullOrWhiteSpace(nameIdentifier)) + { + Name = TextManager.Get($"UpgradeName.{nameIdentifier}", returnNull: true) ?? string.Empty; + } + else if (string.IsNullOrWhiteSpace(Name)) { Name = TextManager.Get($"UpgradeName.{Identifier}", returnNull: true) ?? string.Empty; } @@ -182,6 +196,8 @@ namespace Barotrauma Description = TextManager.Get($"UpgradeDescription.{Identifier}", returnNull: true) ?? string.Empty; } + IncreaseOnTooltip = element.GetAttributeFloat("increaseontooltip", 0f); + DebugConsole.Log(" " + Name); foreach (XElement subElement in element.Elements()) @@ -249,7 +265,7 @@ namespace Barotrauma public bool IsDisallowed(Item item) { - return item.disallowedUpgrades.Contains(Identifier); + return item.disallowedUpgrades.Contains(Identifier) || UpgradeCategories.Any(c => item.disallowedUpgrades.Contains(c.Identifier)); } public static UpgradePrefab? Find(string identifier) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Utils/IdRemap.cs b/Barotrauma/BarotraumaShared/SharedSource/Utils/IdRemap.cs index af7097579..6e086afb8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Utils/IdRemap.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Utils/IdRemap.cs @@ -12,7 +12,7 @@ namespace Barotrauma private int maxId; - private readonly List srcRanges; + private readonly List> srcRanges; private readonly int destOffset; public IdRemap(XElement parentElement, int offset) @@ -20,13 +20,13 @@ namespace Barotrauma destOffset = offset; if (parentElement != null && parentElement.HasElements) { - srcRanges = new List(); + srcRanges = new List>(); foreach (XElement subElement in parentElement.Elements()) { int id = subElement.GetAttributeInt("ID", -1); if (id > 0) { InsertId(id); } } - maxId = GetOffsetId(srcRanges.Last().Y) + 1; + maxId = GetOffsetId(srcRanges.Last().End) + 1; } else { @@ -44,38 +44,38 @@ namespace Barotrauma { for (int i = 0; i < srcRanges.Count; i++) { - if (srcRanges[i].X > id) + if (srcRanges[i].Start > id) { - if (srcRanges[i].X == (id + 1)) + if (srcRanges[i].Start == (id + 1)) { - srcRanges[i] = new Point(id, srcRanges[i].Y); - if (i > 0 && srcRanges[i].X == srcRanges[i - 1].Y) + srcRanges[i] = new Range(id, srcRanges[i].End); + if (i > 0 && srcRanges[i].Start == srcRanges[i - 1].End) { - srcRanges[i - 1] = new Point(srcRanges[i - 1].X, srcRanges[i].Y); + srcRanges[i - 1] = new Range(srcRanges[i - 1].Start, srcRanges[i].End); srcRanges.RemoveAt(i); } } else { - srcRanges.Insert(i, new Point(id, id)); + srcRanges.Insert(i, new Range(id, id)); } return; } - else if (srcRanges[i].Y < id) + else if (srcRanges[i].End < id) { - if (srcRanges[i].Y == (id - 1)) + if (srcRanges[i].End == (id - 1)) { - srcRanges[i] = new Point(srcRanges[i].X, id); - if (i < (srcRanges.Count - 1) && srcRanges[i].Y == srcRanges[i + 1].X) + srcRanges[i] = new Range(srcRanges[i].Start, id); + if (i < (srcRanges.Count - 1) && srcRanges[i].End == srcRanges[i + 1].Start) { - srcRanges[i] = new Point(srcRanges[i].X, srcRanges[i + 1].Y); + srcRanges[i] = new Range(srcRanges[i].Start, srcRanges[i + 1].End); srcRanges.RemoveAt(i + 1); } return; } } } - srcRanges.Add(new Point(id, id)); + srcRanges.Add(new Range(id, id)); } public ushort GetOffsetId(XElement element) @@ -92,11 +92,11 @@ namespace Barotrauma int currOffset = destOffset; for (int i = 0; i < srcRanges.Count; i++) { - if (id >= srcRanges[i].X && id <= srcRanges[i].Y) + if (id >= srcRanges[i].Start && id <= srcRanges[i].End) { - return (ushort)(id - srcRanges[i].X + 1 + currOffset); + return (ushort)(id - srcRanges[i].Start + 1 + currOffset); } - currOffset += srcRanges[i].Y - srcRanges[i].X + 1; + currOffset += srcRanges[i].End - srcRanges[i].Start + 1; } return 0; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Utils/MathUtils.cs b/Barotrauma/BarotraumaShared/SharedSource/Utils/MathUtils.cs index 299e6d6dd..77ddd1d2f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Utils/MathUtils.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Utils/MathUtils.cs @@ -29,6 +29,11 @@ namespace Barotrauma return (i % n + n) % n; } + public static float PositiveModulo(float i, float n) + { + return (i % n + n) % n; + } + public static double Distance(double x1, double y1, double x2, double y2) { double dX = x1 - x2; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Utils/Range.cs b/Barotrauma/BarotraumaShared/SharedSource/Utils/Range.cs new file mode 100644 index 000000000..5d380e221 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Utils/Range.cs @@ -0,0 +1,44 @@ +using System; + +namespace Barotrauma +{ + public struct Range where T : IComparable + { + private T start; private T end; + public T Start + { + get { return start; } + set + { + start = value; + VerifyStartLessThanEnd(); + } + } + + public T End + { + get { return end; } + set + { + end = value; + VerifyEndGreaterThanStart(); + } + } + + private void VerifyStartLessThanEnd() + { + if (start.CompareTo(end) > 0) { throw new InvalidOperationException($"Range<{typeof(T).Name}>.Start set to a value greater than End ({start} > {end})"); } + } + + private void VerifyEndGreaterThanStart() + { + if (end.CompareTo(start) < 0) { throw new InvalidOperationException($"Range<{typeof(T).Name}>.End set to a value less than Start ({end} < {start})"); } + } + + public Range(T start, T end) + { + this.start = start; this.end = end; + VerifyEndGreaterThanStart(); + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Utils/SafeIO.cs b/Barotrauma/BarotraumaShared/SharedSource/Utils/SafeIO.cs index 563760152..0621dd858 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Utils/SafeIO.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Utils/SafeIO.cs @@ -23,17 +23,19 @@ namespace Barotrauma.IO { path = System.IO.Path.GetFullPath(path).CleanUpPath(); - string extension = System.IO.Path.GetExtension(path).Replace(" ", ""); - if (unwritableExtensions.Any(e => e.Equals(extension, StringComparison.OrdinalIgnoreCase))) + if (!isDirectory) { - return false; - } - - if (!path.StartsWith(System.IO.Path.GetFullPath("Mods/").CleanUpPath(), StringComparison.OrdinalIgnoreCase) - && (extension.Equals(".dll", StringComparison.OrdinalIgnoreCase) - || extension.Equals(".exe", StringComparison.OrdinalIgnoreCase))) - { - return false; + string extension = System.IO.Path.GetExtension(path).Replace(" ", ""); + if (unwritableExtensions.Any(e => e.Equals(extension, StringComparison.OrdinalIgnoreCase))) + { + return false; + } + if (!path.StartsWith(System.IO.Path.GetFullPath("Mods/").CleanUpPath(), StringComparison.OrdinalIgnoreCase) + && (extension.Equals(".dll", StringComparison.OrdinalIgnoreCase) + || extension.Equals(".exe", StringComparison.OrdinalIgnoreCase))) + { + return false; + } } foreach (string unwritableDir in unwritableDirs) @@ -158,6 +160,11 @@ namespace Barotrauma.IO return System.IO.Path.GetPathRoot(path); } + public static string GetRelativePath(string relativeTo, string path) + { + return System.IO.Path.GetRelativePath(relativeTo, path); + } + public static string GetDirectoryName(string path) { return System.IO.Path.GetDirectoryName(path); @@ -246,6 +253,7 @@ namespace Barotrauma.IO if (!Validation.CanWrite(path, true)) { DebugConsole.ThrowError($"Cannot create directory \"{path}\": modifying the contents of this folder/using this extension is not allowed."); + Validation.CanWrite(path, true); return null; } return System.IO.Directory.CreateDirectory(path); @@ -310,7 +318,11 @@ namespace Barotrauma.IO return System.IO.File.GetLastWriteTime(path); } - public static FileStream Open(string path, System.IO.FileMode mode, System.IO.FileAccess access = System.IO.FileAccess.ReadWrite) + public static FileStream Open( + string path, + System.IO.FileMode mode, + System.IO.FileAccess access = System.IO.FileAccess.ReadWrite, + System.IO.FileShare? share = null) { switch (mode) { @@ -326,10 +338,12 @@ namespace Barotrauma.IO } break; } - return new FileStream(path, System.IO.File.Open(path, mode, + access = !Validation.CanWrite(path, false) ? System.IO.FileAccess.Read : - access)); + access; + var shareVal = share ?? (access == System.IO.FileAccess.Read ? System.IO.FileShare.Read : System.IO.FileShare.None); + return new FileStream(path, System.IO.File.Open(path, mode, access, shareVal)); } public static FileStream OpenRead(string path) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Utils/SaveUtil.cs b/Barotrauma/BarotraumaShared/SharedSource/Utils/SaveUtil.cs index 2227bfdf5..a797bde55 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Utils/SaveUtil.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Utils/SaveUtil.cs @@ -297,20 +297,10 @@ namespace Barotrauma public static void CompressStringToFile(string fileName, string value) { // A. - // Write string to temporary file. - string temp = Path.GetTempFileName(); - File.WriteAllText(temp, value); + // Convert the string to its byte representation. + byte[] b = Encoding.UTF8.GetBytes(value); // B. - // Read file into byte array buffer. - byte[] b; - using (FileStream f = File.Open(temp, System.IO.FileMode.Open)) - { - b = new byte[f.Length]; - f.Read(b, 0, (int)f.Length); - } - - // C. // Use GZipStream to write compressed bytes to target file. using (FileStream f2 = File.Open(fileName, System.IO.FileMode.Create)) using (GZipStream gz = new GZipStream(f2, CompressionMode.Compress, false)) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Utils/ToolBox.cs b/Barotrauma/BarotraumaShared/SharedSource/Utils/ToolBox.cs index fe65006e8..de832af9f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Utils/ToolBox.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Utils/ToolBox.cs @@ -12,6 +12,7 @@ using System.Text; namespace Barotrauma { + [Obsolete("Use named tuples instead.")] public class Pair { public T1 First { get; set; } @@ -24,20 +25,6 @@ namespace Barotrauma } } - public class Triplet - { - public T1 First { get; set; } - public T2 Second { get; set; } - public T3 Third { get; set; } - - public Triplet(T1 first, T2 second, T3 third) - { - First = first; - Second = second; - Third = third; - } - } - public static partial class ToolBox { static internal class Epoch @@ -555,15 +542,6 @@ namespace Barotrauma return hex.ToString(); } - public static string ConvertAbsoluteToRelativePath(string path) - { - string[] splitted = path.Split(new char[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }); - string currentFolder = Environment.CurrentDirectory.Split(new char[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }).Last(); - // Filter out the current folder -> result is "Content/blaahblaah" or "Mods/blaahblaah" etc. - IEnumerable filtered = splitted.SkipWhile(part => part != currentFolder).Skip(1); - return string.Join("/", filtered); - } - public static string EscapeCharacters(string str) { return str.Replace("\\", "\\\\").Replace("\"", "\\\""); @@ -640,11 +618,27 @@ namespace Barotrauma Process.Start(startInfo); } + /// + /// Cleans up a path by replacing backslashes with forward slashes, and + /// optionally corrects the casing of the path. Recommended when serializing + /// paths to a human-readable file to force case correction on all platforms. + /// Also useful when working with paths to files that currently don't exist, + /// i.e. case cannot be corrected. + /// + /// Path to clean up + /// Should the case be corrected to match the filesystem? + /// Directories that the path should be found in, not returned. + /// Path with corrected slashes, and corrected case if requested. public static string CleanUpPathCrossPlatform(this string path, bool correctFilenameCase = true, string directory = "") { if (string.IsNullOrEmpty(path)) { return ""; } - path = path.Replace('\\', '/'); + path = path + .Replace('\\', '/'); + if (path.StartsWith("file:", StringComparison.OrdinalIgnoreCase)) + { + path = path.Substring("file:".Length); + } while (path.IndexOf("//") >= 0) { path = path.Replace("//", "/"); @@ -659,21 +653,24 @@ namespace Barotrauma return path; } + /// + /// Cleans up a path by replacing backslashes with forward slashes, and + /// corrects the casing of the path on non-Windows platforms. Recommended + /// when loading a path from a file, to make sure that it is found on all + /// platforms when attempting to open it. + /// + /// Path to clean up + /// Path with corrected slashes, and corrected case if required by the platform. public static string CleanUpPath(this string path) { - if (string.IsNullOrEmpty(path)) { return ""; } - - path = path.Replace('\\', '/'); - while (path.IndexOf("//") >= 0) - { - path = path.Replace("//", "/"); - } -#if LINUX || OSX - //required on *nix platforms to load in mods made on Windows - string correctedPath = CorrectFilenameCase(path, out _); - if (!string.IsNullOrEmpty(correctedPath)) { path = correctedPath; } + return path.CleanUpPathCrossPlatform( + correctFilenameCase: +#if WINDOWS + false +#else + true #endif - return path; + ); } public static float GetEasing(TransitionMode easing, float t) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Utils/UpdaterUtil.cs b/Barotrauma/BarotraumaShared/SharedSource/Utils/UpdaterUtil.cs deleted file mode 100644 index dcd806d14..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/Utils/UpdaterUtil.cs +++ /dev/null @@ -1,223 +0,0 @@ -using System; -using System.Collections.Generic; -using Barotrauma.IO; -using System.Linq; -using System.Security.Cryptography; -using System.Xml.Linq; - -namespace Barotrauma -{ - public static class UpdaterUtil - { - public const string Version = "1.1"; - - public static void SaveFileList(string filePath) - { - XDocument doc = new XDocument(CreateFileList()); - - doc.SaveSafe(filePath); - } - - public static XElement CreateFileList() - { - XElement root = new XElement("filelist"); - string currentDir = Directory.GetCurrentDirectory(); - - IEnumerable files = Directory.GetFiles(currentDir, "*", System.IO.SearchOption.AllDirectories); - - foreach (string file in files) - { - XElement fileElement = new XElement("file"); - fileElement.Add(new XAttribute("path", GetRelativePath(file, currentDir))); - fileElement.Add(new XAttribute("md5", GetFileMd5Hash(file))); - - root.Add(fileElement); - } - - return root; - } - - public static List GetFileList(XDocument fileListDoc) - { - List fileList = new List(); - - XElement fileListElement = fileListDoc.Root; - - if (fileListElement == null) - { - throw new Exception("Received list of new files was corrupted"); - } - - foreach (XElement file in fileListElement.Elements()) - { - string filePath = file.GetAttributeString("path", ""); - - fileList.Add(filePath); - } - - return fileList; - } - - public static List GetRequiredFiles(XDocument fileListDoc) - { - List requiredFiles = new List(); - - XElement fileList = fileListDoc.Root; - - if (fileList==null) - { - throw new Exception("Received list of new files was corrupted"); - } - - foreach (XElement file in fileList.Elements()) - { - string filePath = file.GetAttributeString("path", ""); - - if (!File.Exists(filePath)) - { - requiredFiles.Add(filePath); - continue; - } - - string md5 = file.GetAttributeString("md5", ""); - - if (GetFileMd5Hash(filePath) != md5) - { - requiredFiles.Add(filePath); - } - } - - return requiredFiles; - } - - private static string GetFileMd5Hash(string filePath) - { - Md5Hash md5Hash = null; - var md5 = MD5.Create(); - using (var stream = File.OpenRead(filePath)) - { - md5Hash = new Md5Hash(md5.ComputeHash(stream)); - } - - return md5Hash.Hash; - } - - public static string GetRelativePath(string filespec, string folder) - { - Uri pathUri = new Uri(filespec); - // Folders must end in a slash - if (!folder.EndsWith(Path.DirectorySeparatorChar.ToString())) - { - folder += Path.DirectorySeparatorChar; - } - Uri folderUri = new Uri(folder); - return Uri.UnescapeDataString(folderUri.MakeRelativeUri(pathUri).ToString().Replace('/', Path.DirectorySeparatorChar)); - } - - /// - /// moves the files in the updatefolder to the install folder - /// if there's an existing file with the same name in the install folder and it can't be removed, - /// it will be renamed as "OLD_[filename]" - /// - /// - public static void InstallUpdatedFiles(string updateFileFolder) - { - IEnumerable files = Directory.GetFiles(updateFileFolder, "*", System.IO.SearchOption.AllDirectories); - - string currentDir = Directory.GetCurrentDirectory(); - - foreach (string file in files) - { - string fileRelPath = GetRelativePath(file, updateFileFolder); - - if (File.Exists(fileRelPath)) - { - try - { - File.Delete(fileRelPath); - } - - //couldn't delete file, probably because it's already in use - catch - { - string oldFileName = Path.Combine(currentDir, Path.GetDirectoryName(fileRelPath), "OLD_"+Path.GetFileName(fileRelPath)); - - if (File.Exists(oldFileName)) File.Delete(oldFileName); - - File.Move(fileRelPath, oldFileName); - } - } - - string directoryName = Path.GetDirectoryName(fileRelPath); - if (!string.IsNullOrWhiteSpace(directoryName)) - { - Directory.CreateDirectory(directoryName); - } - - - System.Diagnostics.Debug.WriteLine("moving: "+file+" -> "+fileRelPath); - File.Move(file, fileRelPath); - } - - Directory.Delete(updateFileFolder, true); - } - - public static void CleanUnnecessaryFiles(List filesToKeep) - { - string currentDir = Directory.GetCurrentDirectory(); - - IEnumerable files = Directory.GetFiles(currentDir, "*", System.IO.SearchOption.AllDirectories); - - foreach (string file in files) - { - string relativePath = GetRelativePath(file, currentDir); - - string dirRoot = relativePath.Split(Path.DirectorySeparatorChar).First(); - if (dirRoot != "Content") continue; - - if (filesToKeep.Contains(relativePath)) continue; - - if (Path.GetFileName(file).Split('_').First() == "OLD") continue; - - System.Diagnostics.Debug.WriteLine("deleting file "+file); - - try - { - File.Delete(file); - } - - catch (Exception e) - { - System.Diagnostics.Debug.WriteLine("Could not delete file \"" + file + "\" (" + e.Message + ")"); - continue; - } - } - } - - - public static void CleanOldFiles() - { - string currentDir = Directory.GetCurrentDirectory(); - - IEnumerable files = Directory.GetFiles(currentDir, "*", System.IO.SearchOption.AllDirectories); - - foreach (string file in files) - { - if (Path.GetFileName(file).Split('_').First() != "OLD") continue; - - System.Diagnostics.Debug.WriteLine("deleting file " + file); - - try - { - File.Delete(file); - } - - catch (Exception e) - { - System.Diagnostics.Debug.WriteLine("Could not delete file \"" + file + "\" (" + e.Message + ")"); - continue; - } - } - } - } -} diff --git a/Barotrauma/BarotraumaShared/Submarines/Azimuth.sub b/Barotrauma/BarotraumaShared/Submarines/Azimuth.sub index 73038ebb92560d39f96cc457e22230d0f625c33d..e41b82691a7f5b297b9762374269c5c23f8de172 100644 GIT binary patch literal 231424 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≶gV#Toft9|LNNg%E~jwCw8Kzm7zMNMA^&WfBF z>uTr5@wIf)!xIWfDH-qa7ScH>@qks)M4fh|cB&=5jf=!dc{k1O;$wNdvX_$tt(#jd zNY8f!3zTJg0zJ@J<7{J7EwhNevu&NIfir=n9@ek05v0^d5a+U`G}&MB+<11mD{?|f zF{^`0FFBL^wekJ0q0r;|le*>#IdP{ZSy`J{n4XU)tDnU4>`y;>!x$q=F>D?CdO5!2mqC;i&R16!$TfB*(Z5 z;qwc=>5-M`XP^rLKXYR!!}#Sv!vHlv%D?wYd8`G991E>F%FgG8Fcu0~YySF5dG|4< zJVykSS1^WhslF7yn0Px(qnPUC>>_GE%>^+6(gQoh7eSpHEwt)14}QFE>eaGne~6=P zIKLd=z)+&Cb14o|fCeo9#dmLcwt4Ai<4Ox&ifiK^l;-Wxql|*Ts{LmJv zG+vH)%|xq>)T&55CLM4l*G!vK@w^hz3dXIK?9VOndJAn>%M6`%9p63}qx_rsR{Cl;8oE1x2MAHqk_!Jz#uv_4pU8sF~VUP%Xu3Y9s zPaf4~WWNs#7b%VtwgA|aa=CAEJn=+3E>OIFwwsb{pRauAU7b`6%u0DbcK5a!F9*}y z&_5F@RCb|VbW?r=B4bQJP8r?mB3~FL!Sa_(7xg-~DaE;i4~iod!=sTQhk6#ts5K05 zNVFD{;yaIZTf7q=zpXhBieA}Z*NZG@w*mEpPOTJ@J$cg&&a{1LUtc<0~VOZiQ2 zTFbP4s5~ySm#Ito#3@R}A#9GZ zdZ1q;S)k6O2id>d^-bsc?;6D|=TQ-O0Di=715jrx(@T3XX8~Udvr0?=X9O$5D=eSj z_i1UX0_J(gpdn1HqHQOcRbld1zfuNldD6<*cq4Z3vnc*e;=x28Fh3|lPrnV!4~q({ zNKd#|akJF5RRS_Ofi5PIxD$ZP-a;!}Rw^8YRQ)GsEju+f=qC1`GLirFbsu7V*Cy)l z(%EGduJ@=hA4NOs^*3T6F5*6TBJT|h@9FwL=!1BI?%r%h4A$p?9B7h?H0W_7K5%b{ zJm%p+vNfbf^JW2+m~LZu_c!rXQIp*f%g(VvEn%#}hG)$6CLBOfWDuPjyl;vWW;QMF zik4#C^pma)Q6N*QDQlhcj25jVf6lB3(yDgB;{UINo(Z7*+~)&m+i)D!SI@u-$?4bl z33g_}fBAQ0eit5m8>=dsq%i5K5md37Y&il6)9Vz2NYF8sy|)CqKdwi$uiMDCE5XiL z=z0!gkPd=3!6tgiBn4c_=;2(vYbhv-j~+ zF-RscI))(lfrJs|hJ zFx^CIMY#77Yf>(>BLd?Gt8S6Jn(Ax&eTtF6ud<-PWu3^bV3o7=<~&}`pi9?G zSNN5zQpdcEz9W->FFFu@90JwrXW$By8*iD;LGe~0zR5R%b#Au2t^=5|4S(c43=qOq z0WtGd2Yvd`jr@v-U%mYtTv31`@ldr_MZ^!_8GHtP+bXNI^<9Py%TXH$Aq^Ni6&mWE zqyNB;4CdbtKyy}ho`dNg0?Rou-@cXGM(OBd@~(`<1%?CTY6EcbS0fxKjL|}4Xoq~v zDbhL9E*QZ|0Yz&HEih)_!KKFwcrKQk*Vid#=1C@o1BlDV0tMXDOOtJkF7M10(&z7< zdOcRpAHGy?D7#dtonn~V*qyDk!80IrlWt`l`4;xx12WJ%d3>>!>kG&}@H6EQcoI<@ z_Zd1+RoXggg?cIp={yLU!89PS>1F+uZ?-w3ctEW89MWF%_#a6y%f-%J5;S93P0;W4 zH}ju8chtOA2@_mDBOFtnG`6nH=Mi`wy`kI=SYj?z{eW-DoN;FKC`s$=<&=ywfUb>u z8|ti;qx1>852$DfDAVVt{@``%)xS~o;Bi78)a|6`$JJj<#1XZ*ZD6BD&!xOhF<>8R zd|I%+^+4(n!L;|J`3}Piu(CP0VudBJ(?+@x*DgbSJ=@=^5AH;T2z`AVG1SSrMXVDg z5C~`=)z5xkVn?kpuP&u`?*dN_N7jYeWqxP! z_VSp99DE%!fiCpePR!V zF=hv0<@Mzw+O*GPpTR3JCNQ(sqV{HVBxQMpLV?)qO`*%z_b*J)!$VYk4=wRRqB5fV zkr*6DEDh$z#FOcTfrmQ0&?MDxm=+#^Q>wk*d8og)VvF@*?QA?l(9FkwD2BBzvP2z* z9X($PVPfx~RB7g8Oxn!b*Ad-I25VDoDXu85zIftelhsgKWZ4=-ysvg50bs@acWn1k ztarPKc?{cw#sEEw)oA_AHh+$aKL#3Cq+@FmD1cP2w8U-rp3(1HyZtqb9{00ZxEGP^ z@Fc*3*X(0KQ0@klHeXz*b4C1fbzfl#`13HCngZzzD13LU;;Bn>jg*vG&7+#2_5VK{ zqAw}l?Aw)UjB=4&&Tvhk&@B6q>@apfULO!G`;~H?4dY&5*BSr%z3p*U;;&Z!7LJOT zwybOUS($93SRM8atUPC(=`-cu<`+TYyooO^Mo#whYA+#|Wg&&dd@;z}k=hlh^Z;Ur zfCNY^rBVV95!QX%oCg0y40NoMydIcTFml-leyX7wI{}8L@>Ysp<@hz)0zfjd_Z9 zmb0T{_aphMD;G3O0h#DSGDu!feXn@JX^1H;u)nLR#hb%#Qd$d>{!MLq4;MrkX!QH; z5eclY!LJc)gb>m;+=&Ur**DEFEGvTvzA-+b?zby|_`e(Mx3Y%vMTBcr$$xSdMj3Cv z(qX*eKyl6CVUlvQ+w9YZOtFoed^O@H0({y;r4q?}Hjm9&=jE~2EQLBM+2kRRY)9Gy zgA@x#xN@HbVTrA}#C&sM@$r984CSE-9taV%id9*YdyX?=hj{njrrt|Ca9^Jkj{zhyIWtWPd> znbJxP|9^}_U41J9^ZISwzuA~>*Kuri1ywt7nWbcR(O!Cd8!A@Iyw8?=%TY8+`2v)q zl2j2pa*|?{z8DO>GYW=;7Y&1oy)v$ss5jfM{FEiZyI+Lyzu$Gp-r*-#gC7`uh0{O# zU-p|lce0{Cj?QCCQ7DR{AH;y1hUA=s;0(!8B*WMDtFC#k(gN-~cc0zrP76q4Z%-f- zN=tnJs{^mIvWE*4ui<}|Rgde{A*{KVr*81?u9v*q|85uYmiw;>5+vSJiK1$a$O}J} z|9(6q0tXrWlkw5PH{KGH_#tuWD->^$tNDUv+0?sL*B~KVlPWh|jq+G26i{km;GyRP zN*9!i0JgzjuUei(T!8QzB?R;%=*j6K+WC5=T9;aj@zqcA>b_zIDh8A#rP#k=d*JveEsnewlwbrRa0k6mPPK! zlSFz=94T_uQrWjA;uVJ7YQJH8_vQ;>1_1V8#4guRMwL=;D^`dpB!+v*~?=ltZvDV_uwM(0UT(>1HL4j5saLCG1zSBf9w3_)KBL==-fj&@UC-y@*!4xEfJ^&2}*o$Ns<&VJfE##;2-0 zpBB5gVE}x$6%x226e^%#zq3SadPl^|esSZTzG5*)(#B#*IAb9wJTN#duN9(ypfd;r zTM?=gwQ;av{xXAJq?eey*jWLB8*m(Ij9TjZ@$Jq-3(N(8=KEgYaIKV)lwYGRo17nX zEV(>ft=x&6HAv|XFQEB;r9q@3FZv4HTv;yjh0EdsD0a||V*lX$9y#pswHqoVGwJJqi$3c2X+MZKwO0Nn*uS^>&rkk2QzjfvXFEX>}`~wt&uN^p(&PNEtQTy zk+q^7MC+~=YP8~lIz&Vms1ktV2?`>^KE-qWM8yQ$B0#L2rygO}+8=al!iQBp!&V|NF@~y#7wmT6Jt0R$%3uGbqOkk@%xd`LcXL=M|>yyI`KFmcC(s@+yusm7o#Mq2NcbUI#5pw zFro)T=$9IhVYF-x3*iKko_Tn-gey9uw126cIz+%;WXdLD=~MRguChP6!ToNdyeY53nLgI?G-s4z#SkM7&~h)!HNJC}Di*@k#X{1m zuGn9n_jZvoH#ozZt?DtE-NTT!n|k=RwA?8#2MO$ri6$%y_|9<>?;h?LlCxZBnv!dx{NxHo4PuYQMg24TpeYyN1}q>m zfziV15q;qF37br13P*PgN&zrJ@XFU^5~o&&RL>9`=(Jqq!3pH=v%u+AX3u4!qv=|{ zAzXl4>D)YE35+~f>+$QCq#pHR9!##b=;z;;16vp;$;7OE&k?PRVyx>HfLv`V1LLp& z;%I|$&|&n2I1gO9*U0ID3nvHmP0Zx8=mx_|5TZE_33TsMeo4O_bsXaN+@Ig6IO@xn zhBPmJdG<&rstrwcZB}D-jrw&GQ?DR+Oq&Q#L+4w0+gMA#(1t^B9;P7Bq*%?AuEfof zf-w{Dxf|GPqk;>GV@BF_KP^x;*AL3}Lzg9)oKq`dqCF9Wk{(Et?MrqiTsJuQ=f&t` zu5KY^5smQn?x)Q*7^A{L?ju*LbdEsfEegkU}? zoL<6vymW?yO;zHRcmHPEXERH*5Aw|CoIbGJC5rH>qh;4vT|8rOQNQxNM(&@!3KSia z;c^TpZ5a+D3zeG?9RQ?NciF>ZC5Ivk>iteP?T4D*GBSt*)48v=-1*x+_O%M?@dAC8 zod#YjJ3Q{~L{)2Egn0d8gTFlYP05|rl>yM3SF5JNYX%eDZ}u!zwmn*P_AQ;oCS|F? zL=9mBRrvmJW>R^%n3I%vu^X=xhyzqbtu}8>NFRQ2z1l?rZG5!5-=$XScR|xMu2Qg= zk0&hvY1@)XBQUK+_Z7*3-_*NxAFGstloK44&)?MjHv36VMi3tBiT_BqUW1b?V0Nem z;f1h<*Xq3>n$jo&2KC^69H22Aqo;QyPiwp)tNRZ%_V%h5`aW!$_??A37n-agK5DYv zTkvg$Bfbvg80SNKhSaMz_a_^_jNZv{Wfi9=t}e0wrvSEf0-mK^G=LS_>4bQY=M@`y zm1u9WXKm1FTMdMw8<&ILY4LEmb-R#VdzL>8e(+QOvs1jHSey}H^k<8}*+_{QS$7uY zbGB)^_Kh6an_ymb>-FxpWDF0vEK||Do)pSKpyEcBE-R}^P-$Ud=bj?4!kzEQ1;Ns$ zjpGfoHV<#{Vge6Ne@63tA~LLbeWum0Eaf{Hul3OHWCB(Hr*MJgp_8wTN&D95BuTFA zdBQNd6NR7*729IZ5~viKBIN$!Zmx$Y5j)}z@3Fwvt0aK>`p;r%8sKSOuisz`3MCr} zyakIw_OSa={fG`Uj)01=cLw858g0tBjGI9OK{BfRhkXX~6Fc-;~2 z@7rY0mJT4<>+?igvn~OWTCN2-hF}y?SYD)Ll-gG8(|I|rWkF8q-!jd@w4^!{U&tk_ z^q=@P0-YQrv_imw1a^>AkSKKc9JZ!P+FI_!{1imS4POPUmKMt# zbArjeFE8H@mQn7v$^js@9t7dI{<{DL1z*9+mEtqf*amrkZY-gHJUMjkLv4X@A&GMy z1fc6z17BVlzf!9z`iU<;o!pyY^E&ofBBNDShU+(l1mj~l-NZWlX_5n~I_7O;aLv|5 zb=ipPevjotub-HOF-f5q^6u}ruj((vFeEUrERi08FFkP&X3ts-{QAEXr;qTwR`|>N>M3^x5(O zne5g%14f?h>ZQD%>K&0-++9D8#&I%G!4+Pwh5?~zCGPaE6CYGsiEWSaTQo&UCW{!#Y#5;Bp z=3a?CSB>fX`7A_I(GzYJ*NHvs3E$2UxTU8+BqNAfAkwS$QqxdSV?s^3F2T0Gq8|d6 zRwA}{Bcmu*@)(A#=e>jgmUOXM{&W{P?|$DHAPhvM3koK%Enj}zl4|CMq1sBP(%VV_ z0p1!B^wgbH0JLrGOW^|bY1AjC9jQ}7xuxG}9`hNkQ7#xi0MIaU(S&R|Cmj`YDy-Yc zS7lAusXGc3QO^9PPwNYwgA%N*V8xyzJ(cq_iG!m_bApV5NntBlNnSz#8vh}QJfc0q z%2mmbvzsjk^s;eX%$4fq%un|kybXE3wL4|r+h(-N5}PnMB_0v>d@&EPA2o@-ree4- z*)lq3VD()EGTb_Go`vWhJs%$$f2S<8l=`1EbQ9hpe4QzE3P*$yVS9vv-ng<%*uIoA zCBG-x!!hz|pT57XJMv}U8&(bE?%hGn*YASpjp@k8f2(B|Il6LxIZap6Ni*hAWL;21 z+B}yx}w=xOmr3K+MpTA)(Sg z>PEfx7vuzx8bI&2$Je^7m43Y^I0Vv=%IJkzHNeAFfg4C*eVw8!&MNtu7Uc3$4s<8L{(DISr+H0 zTdW6Pg@m)+a3t+I8jS{VCzyI*2d&c))f<5cg6Id@5%mr#m8^df`;{e4$ISaGySPlM z{y1FvH=^wYH7LWlWY&R0ocfH4p0nx;K!km}Bi!2h8~@pdkdxmDFt-jQ*_bQkxzy@r z3L{Ym?Y?FTsD!2Whk4hUl}AGW`KS!QaNel3WF)WMkR2O&f4h^>RrJ^VFhPo|Mv|7f z0eNEpCHMDPQP6zm!b?`7F9(QB)_jq#2-kANa*~16=QmQ zzgXMD!xw+ebG0oqU>Ef}jqJ2aq2w+@I=huB1m2>qpQe{jnv2t}eWVz?Ny;RKQV;a! z0q<)G146Q;@);jpmFvZSto)DDw>pLlOAaTk5glXD?l?V@d#JBin9TSvM*$_Wv-AS~ zM#iCJs;hGn7M882{$*Ha#kUc=0jk_rvtdk_!`^@^eq>vkdS zZX((XdqmU$tZmg?;Yn#}2o4@K@KZ4O7S0hVhrQrL*8I4`%qHzLnqWO;;Nc1!g}Coc z`~R}Oa7%*Y%;3Z~O@f2f{{~^5rv?n5oBK6ndYT^b0t_&Z5(I8$K4<`;CCCRcxUohy z_si&?3C>rZ#9=r-a_x2z+LI+ke%+S)KA$YHY{n^?^|rl_LV=+JhSfnO#l;d#j78$@wD>! zz;0wO@YM}e(zC0M>saT5@!eF|3}n5?Oy`{7ed59`MewnxOK!kpx1*{Rhe4h6$Y9Dp z5N#1##of;*X)EE3oPNOB#P=#)0M;K%K+?n%XBa|?83OIHDL)CoSbi3`_3V5i&`NI^ zX*}7N@nF?4!&hbyN;N^SNXiO<*(|2k42|9}LO7gaUtlTe(q9SK?tMElVtAlT0e^26 z(+!oOCo&-F;)@ucSX5sH*|plNoYfI zEDpNtWBMU#2e=(ZQOWk<;TfUVBw6Ii!))MfbOqEbI0Nd=$bPvhWncj9P)oleMb?K; z)KWKN@=qEg{ar>uL*537J0HRzD1`h8mwo~&<<_p41l`Z{G$Zin9Jo^;e7{T)#R&`; zjM?Y;6!VA4C0D0}2Gm0Zhb>X>pO&NuW=Px#L;_b%>)vEI#rqtZ~wpCaf(F7H-r%S1~iZs=-XC+g!+&9EMUZK}tH<+oaQIj?OzDdXV4oE(wJRMlE zQrwb$Y0dKL-h^B(q0z33;gf8j9S%{Ah=7y|M^z-1so$VRUkiDx!*0i*Uk1PU%sEjw zpzRdE8-G+9%&xq<8r)tb(=pcv%)#E^c`cAn za5{^GG)UCPhJB0g2RR7E)svc^id3XxWG3)dSFfO+(DtZc%8(@wT;&fMnNp9S$vyKcwj{ zQaOjSLsD?#c|ngB%bn_ACxC>KJ}V$g$$htB*o`Lyl?)%^7raN}VK>v0h_z+6n&}U? zjvKHvVR^NF_XIJb_8Y~(r9y-%AA_rC>6~P~WI8cG&sFbHXu_Vv&W3S_LKcY_I8`&?I%FKM*C}W|BiSWOkPut z)yDcXpQx(7BDlz5lb6R#Nitu3w;O3l=AWhzli5R z*sdn@cWgu}(}YHT0*jY#-Ev;!E0?w6Q7oso_37y(%YFn4^T*9lFGqg!Iw6ICpLzoz zZe%7zqF1VOL^(*|?*&NIGtFOIA>TRa?Pt?}-nq_mZ%Ha8WNriPi(QNxAxN`a6bWH> z=$sqgwn})o<;|t<-AD5c{9r11GkXloYvmWvuW{+^{(@gtu-*t#a{#|IG?`Yt{*Luy zcqZd<+{AxVpj_~8q`F{9&y6<8Su+FOk{p=bNcG1>+pY<2C-)1hp*wH}C;2Ol+ z0T?KHuMBE;=}0h_4u#ha7I^%WFhK_=@3@<{k!dvzf{VyNQ3x1<%m~APqFQam96x+5 zP)5?_t{(34kQvaeG7n_mD#pKU0Inkfn#}W|#PfE0yJH+xr6J*6k{6(eXg)Z}Kv)i% zTxKd{0=vYRp=J~T)pHcb=^T93S0><_byJEnR&Oe4V76Kbme8Ll#zRlbJazhZ3?@)I zJ+fK314FtG(XX`oQmhD8S8qHwW=aYLDg~$~Sr%rDYoF+A)Ab(9`tZ^LqoY}v;a>=H zuig&JPazc}@ikl18V7~aSV@8mXiI6hy||C+ud>V@`28Ul^V~N}W}UgWt|UEVviV|` zl_hqGh~jXJH5#~Eb0YY!=&{2OV0RW}Q(BE8py3U4ucA;DqVL*Q_*>=9zr`Z@(|lB! zpY#a;v&w_Sfr9X_r}{6O+~3NhHz?PUIqQ84a=n9=1CQ#P{B@9Vh`QlZRA4re=_}CV z-YiiqmrhAYX$!5UZwVp5HK=IcJXrh!w@~81GdCsA!~n;l*#P4l=x!VP0xryv)@GL~ zU_ig`B_fF~0n4@HK=0OL=w&0uWRz5$ z{uG6eWDS>Z)hi&CLsq7KdVh^qnEGoz2|#z_=ob%9pB0xOlalI3ZV`qnc8vD@9r%=3 z(u9NFN{@cBCfNG9klkRZ0vm&_yU!vwi@l#}G0pc|6QTkR+HdyqYyJ272yR)4*xe+c zpT!0iX?Gb)8m#;Kd}Q@X!T$U*JGotN`%?tY5FC}4=)ZpOL16*+{nNojzty&uv}{-; zQrPVm4t}W7J5{SnK+VTP!czE~Hcy9-Btq0lqgom;4C}>bRY39kdf+^Cz&=I1t#kuZ65U*EtQmxpBdE+fMLfA}M7GFJp)KGrnjsgz% zs$Q7w!Zf--b)kMa2;(x9W>78Ip729B#%X*KB28!Q8n0J4c@0t4)DpXq&VK^_qpMq8 zfQKhTu#XL(t^s2E+GyMdcj*Wmi|X1(!2FK_O@Vjv*rQ}qmj&t!c|z*4K-rLK&|AGQpq}!t0>_x~ZW}??M^H;FW%stD znA{6JnN{Q}{23`eaS(8`7hJ75eCkPmLCNxiDWT1Cc3wpdS%yqM7L<}{{j#{dE_LHQ zenAZ%|DIFQ(3bf$Z0~GFbZ&FO1f}Wc0f7OC&VutJFm}e#G5?pZ(^t;9#(A~E{m##= zU|2i`4p=e@uaDcE7+iN#_4o$mm`E6(3X zQ>_Vm3`zG^_O9fAk7(!+@Pe&m`nLTbJ^f68ZD-j_AAi62{x|A|1Al-;Brd@FfHU!y zZcEOg>&d5*Z>ljAP%ax!!CPHq)w$XRil?_D<(Xo*o9bY;9l*gx+z1CXgy9g(mlloY z`LU<}5l9MR)nKihZCi&$?cbyC6hqLp2ZlL&Jd)AYSW{KG*58*#kM}DlY?9kbj_#<# z%b2*)ev{$VG6^bjnP@&iEwlxPv#oJc6r&0dyz}SVDW==TXjBKMBg`sUf6ia1`2n|V zwgVlk!x#D~fWrPx6SayS``uWtXIQ^YsO1oVEy&{&OM^LD9PU5TYsHonh!Sp}1_@a_ zbew$CsIncR`4c)iaHwGL1bv|)P^NKH21NDU657DB?9u^F>{E|OQbLxX+NLQ|wW&{i zj1LL?IQS85A8se^aK`X>rOEL&7|?wakm{v?LPl9 zg%k0I<`S@`Qk@$r!-rd-V&dSbZ7ZYaS*5^^(nwH!c~0POr+zPV>xYWjUv5syG{;Hr zOMb>f=`k5B$sE1{Se$3vV`25MMLF}&pLDGbfQ@wUT621c?zke5yMS%}a?FF+t*OXL zd%ls3lOUZI2->=bpMrknSg(f|7{2dgs1caw2A*rbOK8p&#$N2y^R(5D_cNuAZD*e)tI1w=J!Zwi!CJE^YK| ze_~f?ZwJi$8$=jTdv($sK$uy9oE0^e%X!OUrJGUz5-a~!AwBfyCwID~gw?aekd$&( zFaB1@BAxlt-P*d30}B{((@%wmcg+kd&0|2k!~R2(p$%fb(ZOOQCN= z^G667Px9p;3A%8TZKkK9a}#4zaIE>-Z$cwQZ;fK+#*g`X+{xQ@}Vwahd=t8)@2L z)jMsqLE_eTCzBN0Ivc_nb4OMg1h|_As0pp9dwo17BLu~QT*8wM!s50qN4t&-KT(%0 z;EMl_^W?JF{a90E4cCL$?1=#WCcK_!h570AQJ*RS?cqNHK59wG|^gK z;Rknu${p_SC4XCAS2fK7xKhr$CfKr8eG5GWD+kVc^vbU7%w1{D=rP$DZsD3X zOIaYJ=k>Zl2l6P%o<$z_0-z?nxtIhZO$sDWUd%gE8ozEXRN zfFkfaJx(5Y@oc}VJ%{-x#I({(N1+}hTaos1v)rzwp)eAFrp;j0D%P-a`_1L-7>H+2 z5WN0w-8elG&=PSBXkS3BS^Si&APZ!T)wcE({_OKA_2UnWnj+W3LOwP9OC1hX!6&Qe z&Ju}T_y8{C!wk4q_Vn_&QR}8};I%CI+2fr7xuk^)Qg8Xh`h)F<*^~F{6W$>Offo?C z^klE1-~b+&R+z@(ILNY32<n!xT1QPSn2UR=^=&`2+EIU8HEp_@-OXHBzn`=nT zJpfU3)7y9m>6?6!=x+r~CE&c3sgcA*vt@$pY9dv+c=$e0KSk z$0W-hc&s}fe;O|u-k-1<3dqHzl7w-`mxs4n&!$Dn$)J4#WVoY{!PWYkJnsxssW=u| zTt_3XX|oS{?-WV$ZfDL6&Y{lm1j^g;lY!)OpinXMw)vOOrH&d(_PJUVP{V zyEGG9;q3&;^@EY|1%}ACH9wgKDjCZ4Kt%(ZjUb%?;msIHV1V-l&!4|9=Kk^AveKZE zR?lGMT_L%?6Mek7cL#6#$*1p+=sza&l-%`I-fdtSZ@2*-p)YgRtXQkRL`Y3^y}_wE zTXvdZ^e^&kgx8=6);gV?)>5b=&gDyOZeaAk1vOFGsDPYavmz2xKQT-aDO zvz7Z%I?dJFG7wpU)U&n5Jzzp9*z2+xGAlb8$}J3ZA`_<6h7j-JNDA8IT=^TM z(RM2RAEtGuE}9uLEB-4LwVMz6*?9$v0K~NZyHiIL}KE~~9qKLE=3BT|7uae5Zmds?rx zx9W%Z;fcuW%VhYOWFftKZ!j+pE3Wn<22)P(>xp$}Z!b(`K6 zDkK;a3JELV@v|AsNc)Rnm2Kc7yNzny>+KPK!E+6b6i4C%IW<`AUvgYQ%WyTQ0}``W zgeC|PN+`CX!+*vIM_F#7tBu;`K0d@CZ zE{>lnNQh{>TqlX5z!Zh>6cmvrCZZ9=)JAK`!>{>LU@-@~OaRoAnV);-T`kcI{Ql?> z``{P)3}RK^wo%2Wuz=myEz$c8WG@z^JgVa^5;W8aKX|>Da%YV<30pY+{6U)nOi70N2Z>QT^=58HyC~s-Yqvj!*}9S3fih z9Z)t;@z5*MN6A(6p;Plz?D{;T`9(!Zd5EgR)F-%);9NRNFgd zH=a#MG=F9*K?1PhAk{LM!tQ$OXECs`q*cOeJ#U}*m&-9^2k`k90<{`F32}xnNB;14 zK~sLmtXrv_pWt(K{h=c;fH`d%rUGK24Hpkv7~-1I&nImE79>}3YA-`*0{C4=z<8;(lL8NgbT`Ok2ebkWjHqImNPb-&YD_L|%MDTYBr#23vh76ozl&(6U9EJOmEBqnh zpTli~1}ywh$qmN}nPZXbb^VSgAD>cCI{6Ja_gM0^b4plpR?|sR_*2eo^}Q$vD6>+%N9GDgng7XfXAgY(}D`Q#N`ZH}^mdz)sF^A&to;aCri z5pB056jMyAoglh#s0!J`fsrDS?`Kj>iAXdD#ZA7MQ+7B)IO#yx>XxYzwVv4n`ss$p zUCF|#Q8Pp{Z7*25dZPU(=K%U##z_x!GeEPGRjQE@v|PEAG?DC5KhHS4%(i~u$K~JSz^+|PPtbGt{Q1E(t*R%-Rew#o{)j(5?SkzS!w6};9 zNIxBPyg^V*yf2REDq0wWgl8Ru{w%$rAY{Wji9H+~562?s)GukbchIf)iA{?hsM-yB zw0QeUDulo548lH||NNoR_aaC)!vVN8OPFbXs`t&B%_F{Tp&{$|Cr#g!?}1qAqk_OF zU+Z#f0`gnEcV7WOqVdoA4qp%&9b0j)h7z2Q0V>%{AIB)sU|nN61)X>LeM7uwmd4ZbEW<6CVlnMUq_}7dvm%`#S3;H*F8?+phZIP)rB=u+cQ;d}0 z(yvgSB`XyW!#$rq#PJ(et_p%{({gYL`z%Kz9KVOB$V=}Ow1>IRfGz5{d|%OVV(UoS ztQ%9Hvr-TmhoEwPtYq5-??hNqlFSW3w(SFXA&7+wFtgqVrbZ7d9v~#8?!>?t6i1_hA%v;3t5qj8`Igf~SJ>hSPVfgWLFHnL%ZX-QAUj zvvYAis!Y2p-S^A(2sJ3$lh7K-3JBy3;Rz~UXP6HgV1Y765q5#y7(RjxCZNq;WQ@LV zLRMe{3eQ2$VREbFyLCm^)XG$)ohx!p08l`lt`T#Ra31L?{Z-q@2DZ{d66fr7`n5&z z3#c3hz082hOQ2;zuoXRsRSn|$cejXf#*Uqora1}B!gmP=O>G+b4?v|nvA z|FK~!sHv%CsB*{qNM1+`wd7#p^LI&PUj(`*P202lN>bJy_DBDsGUzY2PsjBA?#It% zYk!~qCF=n(oL&lSnoQ0<2(W!Tiocy{232C1`2^5Cd)d6mhS+qx` zzQ$(lS>A683pNt^^r=l-zoa|xchuT6O@m&#dB4Q^z5#o0a5oCaN^Z-l5PqQ2{uV8i zA1BhAA>+o%f^h9gkv++x-u0|MBcM?f6KY_yy|}fId}^(!kmw zh{ghUZ$QVF`U#D{k69ffItLbYa^V(`Ac;XpEYk$Lv%;>iIq96OsboTZ@4&pB&gnc^ z=c08uyOh+2eYqjMks8~9+ScZ#Ih$vhKKGZ=3D@{|pW9PS-e@BPBpM}@pF_8l@SXWv zn>FP^Yu06TZ|Z*4Z^i$`-`04&o)ZeqY_(tC;mwx4co%O~de{LOZ?HO*Ms02h+ru&2^ z47P6s`c*pbfea+f0-@opCB|>fqH22|l{A?@9%80f7RD)5?ZKLS#Z5N8`QBF|>RhVE z_c!x}o;ssSNiae-VcsIxvKKY=p;!0-W(97oAL%Z?Jan~(ykF6t_jd=}!7n~Zh!^Cb zTWUmfwoA7zEh$OQk#c<>{z3k5e~2BSmdatJpPUoikbojgkA#CZs@n1(lMy&4Kj6;I z%{u>y7*&&y2&_g>wu590oWjeSYQ_Y*Ui<>1J_EfT+Dm!shojT!Q0tpjtGj0JLyqDl z4B1{J108w0$Rc?jw-uQU4)geg(x4&CJo&vF^W6cdhJl49BV~-Ww{8{t5EP$c1-J8x z#Xnt)Hw+O`x!0pqQ)I8U*Zrs$W#-T5{2Gc#lRO=7l@>|^2Zjn22IL0H2vT~fuMbaO zSeCu}ksZ9mj6Xe7hrc@#115ro5}oc_Xf_!C25&ayy?B(mEtCzNcT*_-s4Flbcy-t|}9YLFY z^|kwkfOBarR&_K-!0mCY)tRC2o>pVod;BwUuhNN4z{e5`XDX7huk}ZpZu>12gY4PO zqSA6lmL>HaJpFwVOSn)qx^?XQ5y7G#sPP%*k9bPvGJO%SXS0nV%s@4o%n;G3*Z2kx z!8S{ckmndGgM6=^4sckdCQm1p zRKcpT$`Yfaw_h&3MzawN_4u-;6u88_-n;bzGqa`uIY7q0vF`9qI8J{41*rxIoC^db zv5AHpLuCWMPEj$&1U_+KWXdN{gPZ+@;EJM6mL+i*eSKAnL?bh0ctPypNnF}I= zo|@@|!&}fuEBbL7sLFOAXFa6-@~?SV(qU@$L8*5^>M;DX^#f|ruzO0!mXj{m^|Ag6 z2Mnfx&_|&_nPHi&`D~MqNWhHlr@)LB)vE|+a}vq^Hg+q5$%jtf`4JD461?V)ill?_ zQ+b}}5@GpXE0r527@cbTOEn2i!4+s-A*A8!uCj*H9m{I7Eaig|J1^Nh8`Jf-zsvF1m8#h5KSTD!!IT#Qk6YAVU zwL(G-h?QYg-l6@X9KN2-WHa6A=)Xp0yK4^qov-5^2=SJ)Drj{jIBWFvK6bk|p0OnS zo6`T!PJM|3omY_5k^4#I$(%k4uXeERf9Q73TbLAY7Ft)z*Kk}*11_Dafr32j)z*=v zvfto#h02hEYA;Bk3{G$)6TmR6qB3S!)Hy*^`KA93qR#MMpbSW2jo>#?5%+SVq!HUT zRpr~z^&?&AK#>L+O=w~Ab*w;CcWS_Z`}i`r1QA{xweE(P^z@XT4z}ziWmyUkjQY3v z#MPih^CngejU){{K>q$wg~$j=zio8SN-@ zAqB0+lNN4CynSE%sO0i`m+6xQ9&Zf{xW(3Swm;@i|4Yd36qOEe>O@L4gP1O(R~q?jm*y9L2x#g2nGv%GV}|tcP4|NWbr60ph(|ys zG)4EwqN8KTk@pSnok!UhQ21Bvsc#}&!W@pqc#OWc*dKIq9bPkC6$=eC%bHCE#G%GzsyEQm`?wyvyu+Bbl4 zgimP+~GcKM%_Y zjwKe#T}oeKav0PIrOMQ_J(FTvy80{{GI8>tsSZ@{0=?X~TsZ;N=DWBkkOnr1%CIy5 zG=%Ss>~B|S$mfS5-=={<jYODiI2R6oP+@i`{S(sXZ)O9>G26?{5bQUU(XSp#o@H z-$6-+US2~`yvxWi{oqy=aFd4RF7*WEf+9rI7U1x4t#^h^IuX<%-Z{~TCP~hw#}6R) zmLbt_xhg0J>jdj;S|Y2!sQx8$VB|0nAj9)CkaZ&5=7BV$BIOV6`o`CGZB8-lY7P)j z<242=<5=Vmx^(;uzuo<^enP23eo|+=NiZNDo4&97dm%^^pHkPNh~8ec4Vt_CfXKLF z82I;W&WGKh7Z_y8P?cH>4DS|j(^V00L%<22>MalYRz=ygp12cylq83HPN(%2@fViu zd^P5))stX0V4ow`FfnCE-LsDSYBKOKprBGPz!2uMkFu9~(wU7V^3#1k#dFXfTK^n+ z45E_S<%sez*-87YQ9?@qCyLY2jH*ov5)W{X*5Bd$F1JNm z?=G8hj@XR5RQVHr5v9<9=*PqKil**RXiP?D86)e%g*m8ah5+i27~lNHTPX#- z3Nv>{(=DXRf3*t*=$*5G8#h<*&thOd!&#?kC@)p=9iz+}9KdBiX;hBwChz3%vy_tX zmgAFoB>T*P8OS4q?8e38x8SN;COj#0OCY1&%}*+KC*Sw3bhXPQ|5{wevH#yuGn$cc zTVwx(^QHq;`Txru4CK1^f6~O(;)?R^r4(j400|&z04gMoAQ1@P%hzue7l8wyv4<3L zC;yZQ;m{Gn51;B>Mb6by2NjuN&wP1{b@Ftr1!yp=9W~>DhLBs&_;D5QPhBX7I|DAsnkN< z45z#XX


N`^CoIUAY%N{!5-W#kTY@OLe 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

^C)Z> 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%0A~{6pF| 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 -J*RzX7Y1V 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 ig2vQd?G=AaC;n!GiuVtK>iP%>-(bs diff --git a/Barotrauma/BarotraumaShared/Submarines/Humpback.sub b/Barotrauma/BarotraumaShared/Submarines/Humpback.sub index 879c0d4f25ac4aa794cac744204c4263a4a9a1ca..90a86631d98d15bfee734d435ae17ca89fb03908 100644 GIT binary patch delta 206173 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%0DafVC39Z`Ol4YFOfTtMsiQ(9R27Pm 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 delta 206261 zcmV(xKwv~!I=>c1p-bLsRXp%1JlQw7c zWYT-HH4U^RZ9%B>cWa{1T!TIlb?W<=Ob`Hp7mus|>;L#a|M$6@5dWKw|M_pq-hWSNlC}TsPo1oP(xMpt zjQdtL+g9X%w(-x|Z1uV&L;j~9*X_?({@H5qkYuQeW&P6(;CaRL=d~~6l>GaT0v@## zU9vSt(cORQrmDdcyD@9F`%kliua|4F{YmdX-FW`#lcCs;VoB1!y}SSYhg<)wb@AW8 z|F@>={-k39et#IMKMCkmm;W8cm=_&*LXvIc^56eV;|VSfMUk(6`K7qf$h`{)hWl8QocQlIDf+VyN!k^E8(sF{ zPXg}vA0r>YH;Q?0roYel_rLyRb&<74(*5UOC{9o;iGMOE`>#LK(qzSd{zWqg@-Hw+ z&DJE{KV56U^iRn-6#0MtRf229zy56Z1a7sv$FEE{`pYhL7k z2fa2=(SJ9?|K~@^^`C!H|F4gVYbute82%1T0KdQfpWlrp|9dx>mj&E9g4rK7@bosR z3ed428NeJTe~^EJsTsgP{`0T@x$OQuAwA$tN6Q*(ynUr$$Qz@L%9KJGNTrhOPa-suMw z;pKvNgK64MUB8GQ$J+h_mlQ-rM;S#pT*B#>hT$U17#4+YYvQY_u6bVg7-f|z87~O( z>wopq)jIZt7F-9vw= zm;W^{|36$W@>wGZ=fM$jv9>1@c8KiHec->G* z9sl;LsX-f)j{_Ee(^i!k<;RPXD6aMH%h`hJ2p=J#S3L|)SfAZx*In+v_OgI+%N>Rf zW4D53{|Ev?cA@#Nea)a8w@v(ea~7Y%1!~BCT?RfE|M~n4W=e^j;jVf8+<7YQuboF2 z$?^VeT%9G`xFbhN0Ma;bgm6)WR|Ky>rZ ze8f&Hso?R;n-#xsRGq03)+U%&rB%n5u&Uum4i*`S$MN^w8Bp^sa-nQ?O@tvS!aBrv z)GYJhid0BOH(wmd(Be5Z@w1R=J?VRn7WPtgWyA=;uxzt<2#P(Jbou{`a$gA8F(qg8EudQfci}b`%Z1xp%so z*M$P+co#UDq@Z0N(a*l=>j?T8orP>qTJxJ;KB4%$onN2jP3>dlS$u0J_ZKH#^+~V| zHB7?y(?%YN(aMKz5NG$J*^9l3^_J(MLs+;T`huc3sxR@5DNb6|XcPthy#&*dA+5@N zlQ1BEMVkz`J?OZZw1sek&kw!)9t*59>1e+fRBU6Xz%CwUeI3rO*JL)iK=)ou(bNdA zbKMBG?+Ko{A1>#)yneej)4i(Br-qN%+F#I}?6iL&UoRFw9>;kB{oKQUjfDA#|Hdq{h$2a&QD@i3h!yCXa#B$> zhfu22ah?($a`WpxeAJyppz{*`o;D@#GX5+dh|!y(zmUXj#`rXT4M%*5{bzfqTC1S3 zdsG;cg`G;W`6{Je#`;<%EBz=u)NDH2>n6`7s^+BN|MpW$x{N8b9*{KdoPH9RSnyaJV%0a7 z9iklmVJcr&X^|*SFMS9VAj_uzWKiDj-(eAyp~YnC7bI9k$kk8F$aphffwu&-f=gVK zk7%awN#h_)agNh|7p7Jt{Yb=tI7f$z;OX2_x<2cPfS#JiLq^xH3SKC`>(`6iuA6TpJUr323t1VbnnsHN41%S<<_-icLXJ{NDGU= zKz{)me?wL3Cb=`-c)`a zbTX_pxtzKeZ`UKw%- zqr#FgCBhs_T5N-0if{LG^To!YzY`U0#~7t^k1-T=i_z3^nglgXULyF`{q
$X9H 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≤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� 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 b1ArCR?>;PtPbE1pM*sQ$)?-O@syqz%p8flu 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%0Dd9ZcpTuMN;CNy<|V^ok{sIR{Xwq&3tULX9sDeK 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°Z8X_KyScvaP94 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%0DcqGwL-9z~ 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%0A9UCXP3E&IlG_m|6Sn96*zNYWI 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°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 diff --git a/Barotrauma/BarotraumaShared/Submarines/RemoraDrone.sub b/Barotrauma/BarotraumaShared/Submarines/RemoraDrone.sub index 4751d3dafefb729b4b48dfaa1bd4d2c3da531c1c..4d2f9b30f94f1373248dd1f2943647e87acce5dc 100644 GIT binary patch literal 262007 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%0AZ^F7ti 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%0DX(_3^A(8omLRg5gLslQ(W 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 M07Rq6-ZvEo0N2P_#Q*>R literal 280590 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 GWTX)(8j{5Epc$&+0u|uQ~BQ< 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�L^%q7kK+SgtkJ-}JPqF$IF^nSn=@hxnXHjyY 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 z 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 diff --git a/Barotrauma/BarotraumaShared/TintTest.png b/Barotrauma/BarotraumaShared/TintTest.png new file mode 100644 index 0000000000000000000000000000000000000000..77afc5ae9fb6bff9e517e2023347c9a5ec513924 GIT binary patch literal 91346 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 LNkvXXu0mjfS26k` literal 0 HcmV?d00001 diff --git a/Barotrauma/BarotraumaShared/changelog.txt b/Barotrauma/BarotraumaShared/changelog.txt index a3942d1a0..eefe78651 100644 --- a/Barotrauma/BarotraumaShared/changelog.txt +++ b/Barotrauma/BarotraumaShared/changelog.txt @@ -1,3 +1,218 @@ +--------------------------------------------------------------------------------------------------------- +v0.15.12.0 +--------------------------------------------------------------------------------------------------------- + +- Slightly increased the amount of experience given by missions. +- Fixed crashing when a monster gets huskified. +- Fixed crashing when a client tries to deactivate the husk infection of a character that's been removed. +- Fixed crashing in EnemyAIController.UpdateFollow. +- Fixed console errors when an electrical discharge coil damages a monster that gets instakilled and disappears on death (e.g. swarm feeder). + +--------------------------------------------------------------------------------------------------------- +v0.15.11.0 +--------------------------------------------------------------------------------------------------------- + +- New icons for the new ruin missions. +- Slightly reduced the amount of experience given by missions and increased the amount of experience required to unlock a talent point. +- Made escort missions more common. +- The creature attack keybind is automatically switched from R to the new default keybind F when loading up the new update for the first time. +- Fixes to ruin waypoints. +- Fixes to outdoors pathfinding. +- Fixes to fractal guardians' aiming. + +--------------------------------------------------------------------------------------------------------- +v0.15.10.0 +--------------------------------------------------------------------------------------------------------- + +- Fixed character skills reducing after every round. +- Fixed psychosis artifact doing burn damage when picked up. +- Fixed a bunch of pathfinding issues when bots are trying to navigate out from the ruins and/or return back to the sub. +- Fixed bots not being able to swap oxygen tanks in the ruins. +- Fixed railgun lights having an excessively high power consumption, causing them to immediately drain the supercapacitors. +- Fixed occasional crashes when clearing the item search bar that is already empty in the status monitor. +- Fixed ruin generator sometimes leaving empty space between some of the modules and the hallways connected to them. +- Fixed alien gas vents affecting the monsters inside ruins. +- Fixed background wall not extending all the way to the edge of one of the outpost docking modules. +- Fixed harpoon rope sometimes being drawn when it's already snapped. + +--------------------------------------------------------------------------------------------------------- +v0.15.9.0 +--------------------------------------------------------------------------------------------------------- + +Alien ruin overhaul: +- Overhauled ruins: completely remade sprites, monsters, layouts, items and puzzles. +- New Scan mission: scan an Alien ruin by placing down provided scanners at target locations and take the scanners back to the outpost. +- New Alien Ruin mission: kill the guardians inhabiting the ruin and destroy their pods. +- Added an additional ambience track for the ruins. + +Character overhaul: +- Completely remade character sprites, ragdolls and animations. +- Option to customize the starting crew in the single player campaign. +- More customization options (skin, hair and facial hair colors, more accessories). +- Added a button to randomize character appearance in the character customization menus. + +Health system improvements: +- Streamlined the health interface. +- Allow administering meds by clicking on the "suitable treatments" suggestions in the health interface. +- The health interface displays a prediction of how much a medical item will reduce/increase the afflictions when hovering the cursor over one. +- Certain afflictions can make the characters' face or body change color. +- Physical injuries to the head can cause concussions. +- Improvements to the blood particle effects when a character is bleeding. +- Damage to arms reduces aiming accuracy. +- Crippled legs slow the player down more. + +Talent system: +- The new talent system allows you to unlock things such as special skills, buffs and fabrication recipes in the course of a campaign, with experience points gained from completing missions. +- Three different talent trees for all the character classes. +- Dozens of new items. +- Item quality system: certain talents allow you to fabricate higher-quality versions of items. +- Characters lose some skill points when respawning mid-round. The talent system makes it easier to gain skills and permanent improvements to the character, and this change is intended to balance that out. + +Overhauled status monitor: +- Improved visuals. +- Indicates the locations of the crew's ID cards. +- Indicates the locations of alerts. +- Electrical view, indicating locations and health of junction boxes, reactor and batteries. +- Allows searching for items and indicating the hulls in which they're located. + +Balance changes: +- Reduced loot in wrecks. +- Difficulty affects the amount of loot. +- Reduced the amount of weapons and grenades in wrecks, pirate ships and abandoned outposts. +- Disabled stacking quality-based items (experimental change, feedback is welcome). +- Reduced diving suits damage resistances. +- Buffed vigor and haste. +- Modified characters' base vitalities. +- Adjustments to monster stats. +- Reduced mission experience gains, level difficulty affects mission experience. +- Made welding tools a bit less effective early to compensate for increases to their effectiveness from quality/talents. +- Upgrade system reworked to work better in conjunction with new talents and quality systems. Quality of life upgrades made better or cheaper, hull upgrades are less effective towards the lategame but are better early, reorganized categories. +- Diving suit and human ragdoll damagemodifier changes: the suits now offer less protection, but humans have a bit more natural protection towards physical damage types. +- Adjustments to outpost distribution: natural formations greatly reduced in the 1st zone, cities slightly reduced in the 1st zone, outposts (including specialized ones) increased in the 3rd and 4th zone. +- Made magnesium a little more common in stores and wrecks. + +Additions and changes: +- Gene splicing. You can find alien genetic material inside ruins (and for the time being, wrecks), and use these materials to gain special abilities and buffs. The materials can be processed using a Research Station (which atm can be found in research outposts) and applied on a character using a Gene Splicer. +- Added a new "Return" order for ordering bots to return back to the main submarine. +- Bots can now use level waypoints to help them navigate around when they are outside the submarine. +- Play editor music in the multiplayer lobby. +- Option to specify the amount of items to spawn with the "spawnitem" command. +- Optimized cave vent and ballast flora spore particles. +- Added a 5 second "cooldown" before a junction box broken by overloading can take damage from overloading again. Prevents continuous fires and particles when continuously repairing an overloaded junction box. +- Small monsters don't eat the inventory contents of a character they're eating (the items drop instead). +- Disabled new status monitor features from handheld status monitors. +- Round water and oxygen percentage readings on the status monitor (e.g. 99.999998% shows up as 100% instead of 99%). +- Adjustments to how far creatures can see and hear the submarine and it's devices from. Moving fast now makes more noise, moving slowly less, and the monsters can't see the sub from as far as before. Effectively it should now be more viable tactic to shut the engines down and keep silent. +- Reduce sonar ping's sound range from 10000 to 8000 to make it possible to spot (some) monsters before they target the sub. +- Made a couple of monsters unable to eat characters (hammerheads, terminal cells, leucocytes, molochs, spinelings and watchers). +- Changed default creature attack key to F because R conflicts with the radio keybind. +- Disabled toggling the sonar mode by pressing the Run key. +- Added condition_out pin to various items. +- Bots no longer ignore unconscious targets that regenerate health (i.e. they will finish off downed husks to prevent them from getting back up again). +- Fabricating fuel rods now requires electrical skills instead of mechanical. +- Reactor now requires electrical skills instead of mechanical to repair. +- When the status monitor receives the oxygen/water level for a hull, it registers it on all the linked hulls as well (-> no need to put an oxygen/water detector in all the hulls of a multi-hull room). +- Removed the "burndamage" damage type (not the same as "burn") that was added as a temporary workaround to allow pulse lasers to bypass monster's damage modifiers. +- Changed the look of the skill/xp notifications to accommodate the larger numbers of notifications you can get from talents and skillbooks. +- Added a fabricator and deconstructor to Azimuth and slightly lowered its maximum speed. +- Increased Azimuth's battery out relay max power. +- Temporarily disabled magnesium exploding in water to prevent issues with talents related to it. +- Added "targetlimb" argument to the giveaffliction command (allows applying the affliction to a specific limb). +- Players who wander inside a respawn shuttle don't get automatically killed when the shuttle despawns if they weren't part of the respawning crew. +- Bots no longer ignore severe fires in reactor, engine, or command rooms. The intention for them ignoring the severe fires was to prevent unwanted casualities when the fire can be left untreated and wait for it to fade out when not ordered to extinguish fires. +- Buffs are transferred to AI-controlled husks when a character transforms. +- Projectiles shift to the left in multi-slot loaders when firing. +- Option to make terminals use a monospaced font. +- Player-controlled monsters can now grab and eat bodies. +- Added triangle and sawtooth wave types to oscillator component. +- Added "high_pressure" output to water detector. +- Water detectors round the water percentage output up, so any amount of water will be at least 1%. +- Focus on the password field automatically in the server password prompt and allow submitting it with enter. +- Made pirates a little less accurate when they're operating turrets: they can no longer magically aim exactly at characters inside another sub. +- Biome noise loop volume is tied to sound volume instead of music volume. +- Endworms no longer always bleed to death when their tail is cut. +- Lever state is visualized on its sprite. +- Enabled NVidia Optimus on Windows. + +Fixes: +- Fixed crashing when an attack is applied on a character from a source other than another character, e.g. propeller (unstable only). +- Fixed current_position_y output not working on nav terminals (unstable only). +- Fixed fuel rods having a bullet as a contained indicator (unstable only). +- Removed duplicate welcome messages from humpack's terminal. +- Fixed start and spectate buttons shrinking in the server lobby every time they're hidden and re-enabled. +- Fixed contained items inside contained items not moving when repositioning a container in the sub editor (e.g. when moving a weapon holder that contains a weapon with a magazine). +- Fixed issues with inaccurate tooltips and incorrectly blocked out order nodes in character-specific command interface. +- Fixed contained items' status effects appearing at the top-left corner of the container if the contained items are not visible (e.g. particle-emitting fuel rods would emit the particles from the top-left corner of the reactor instead of the center). +- Fixed hanging wires not getting selected when selecting the items they're connected to. +- Fixed "divide by zero" console error when scaling construction barrier. +- Fixed ability to wire items between two submarines as long as you stay inside the same sub. +- Fixed crew list background blocking mouse input (again). +- Fixed crashing when the majority of the players are controlling characters belonging to a non-player team while the sub is at the end of the level (e.g. if you're alone in the sub and take control of a monster with console commands). +- Fixed cargo missions sometimes only rewarding the players for 1 crate even when transporting more. +- Fixed the "use as treatment" tooltip showing up when trying to drop an item that can't be used as a treatment on the health interface. +- Fixed characters in the transition phase of a husk infection (i.e. after the stinger has appeared) getting stunned at the start of every round. +- Fixed inability to adjust max mission count in a dedicated server. +- Fixed light components staying powered indefinitely when in a container or inventory (didn't seem to be noticeable on any other vanilla items than sonar beacons, which stayed active indefinitely). +- Fixed some outpost events being possible to activate even if the target NPC is dead. +- Fixed ability to swap contained non-interactable items. +- Fixed crash when loading a container that has no containable restrictions and contains items (e.g. if you put items in a deconstructor and start a new round). +- Fixed bots not swapping oxygen tanks when they are outside and going to a target that is inside. +- Fixed issues with bot combat behavior when outside the submarine. +- Fixed ability to hold 2-handed items with one hand by trying to insert them into an occupied slot in a container that can't hold the item. +- Fixed misaligned nav terminal and status monitor in pirate humpback. +- Fixed inability to install/update mods that have periods in the name. +- Fixed nav terminals "current_position_x" output being in pixels when "current_position_y" is in meters. +- Fixed minerals sometimes spawning in unreachable spots in mining missions (on cells that are next to a cave, but at the wrong side of that cell if there's empty space behind it). +- Fixed items' "allow swapping" property being editable in-game. +- Fixed RegEx components with a non-continuous output always sending a signal out after being loaded. +- Fixed pirate subs sometimes spawning inside floating ice chunks. +- Fixed tracer particles not starting from the position of ranged weapons' barrel. +- Fixed inability to open the pause menu when the cursor is over an inventory slot. +- Fixed handcuffs dropping off from characters' hands when they die or turn into a husk. +- Fixed loadsub command. +- Cap the amount argument of the spawnitem command to 100 to prevent freezing/crashing when trying to spawn a ridiculous amount of the item. +- Fixed "infiltration" event getting stuck on one of the conversation options. +- Fixed signal source being wrong on delayed electrical signals (= signals that were delayed for the next frame after they'd passed through 10 steps). Most noticeably affected status monitors that need to know which oxygen/water detector a signal came from. +- Fixed WifiComponents delaying the signals based on the number of receivers, not how many steps the signal has actually taken, contributing to the previous issue. +- Hopefully fixed an oversight in the sub editor where changing ItemComponent colors with the HSV picker would create an error in the console. +- Fixed paralyzant (and many other meds that don't do direct damage) not triggering guards. +- Fixed sonar monitor's UI being unnecessarily small. +- Fixed contained items inside contained items (e.g. magazines in a rifle on a weapon holder) not rotating in the sub editor. +- The overdosed NPC in the "good samaritan" event can't die until the player has triggered the event (completing the event after the NPC had already died made no sense). +- Fixed console errors when an item a bot has been ordered to target was removed between rounds (e.g. an ignore order targeting a mission item that gets removed at the end of the round). +- Fixes to oxygen generator logic: the generator now periodically recalculates how to distribute the oxygen between the vents, as opposed to doing it once at the start of the round. Just doing it once caused issues if there were e.g. vents or doors that are initially open between the rooms. +- Fixed characters sometimes getting "stuck" when swimming in partially filled multi-hull rooms. Happened because the bottom of the current hull was used as the "floor" if the actual floor was too far below, even if there was another hull below the current one, causing the ragdoll to switch to walking animation and being unable to move because it's not touching the floor (unstable only). +- Fixed outpost events always unlocking the same escort mission. +- The hints about flooded rooms and ballast flora aren't shown in ruins, wrecks or enemy subs. +- Fixed "stowaway" event triggering an event cooldown, preventing monsters from spawning at the beginning of the round. +- Fixed clients (excluding the host) always considering friendly fire to be disabled, leading to minor cosmetic desyncs when a player applies afflictions on another one (i.e. there was a brief delay before the afflictions update client-side). +- Fixed inability to apply buffs on the crew when friendly fire is disabled. +- Fixed ItemContainers only applying the StatusEffects from the first matching Containable, even if there's multiple. Prevented the artifact-specific effects of artifact holder from executing. +- Fixed "giveaffliction" command's limbtype argument not working in multiplayer. +- Fixed "linesperlogfile" server setting doing nothing. +- Fixed discharge coils not working when triggered by via a wired button. +- Fixed hatch waypoint and platforms on Remora Drone. +- Memory usage optimizations. +- Fixed bots shooting enemies even when there's a friendly sub between them and the target. +- Bots take their masks off when if they have successfully equipped a suit. +- Fixed a pathfinding issue in Remora caused by too sparse waypoint distribution. +- Fixed disguises not changing the color of a character's name when hovering the cursor over the character. +- Fixed monsters' attack sounds never playing in multiplayer. + +Modding: +- Implemented an item variant system that works similar to the character variants: you can create new items that inherit the properties of another item and only modify specific aspects of it, reducing the amount of duplicate XML code. See "Depleted Fuel Rod" in engineer_talent_items.xml for an usage example. +- Option to configure minimum damage for OnDamage status effects that require a specific type of affliction (see the "vigor on damage" affliction for an usage example). +- Option to make afflictions draw a full-screen overlay when active. +- Option to make property conditionals target contained items using the attribute targetcontaineditem="true". +- Added support for tileable light textures for Structures by using XML element that has the same syntax as does for Items. +- Added "InPressure" property to characters. +- Fixed hidden items appearing in the job loadout preview if there are other items of the same type that are not hidden (didn't affect any vanilla loadouts). +- Removed error message when trying to transfer items to a husk monster and inventory sizes don't match +- Submarine upgrades can be disallowed by category instead of having to do it separately for each upgrade in the sub editor. +- Fixed a modding related crash when trying to apply a property value of a wrong type using status effects. +- Option to create custom husk infections where player control carries over to the transformed creature. +- Display a console warning when an item's deconstruct output defines an out condition and is also set to copy the condition of the deconstructed item. + --------------------------------------------------------------------------------------------------------- v0.14.9.1 --------------------------------------------------------------------------------------------------------- @@ -75,7 +290,7 @@ Fixes: - Fixed hull properties not carrying over when copying hulls in the sub editor. - Fixed occasional "collection was modified" exception in CargoMission.DetermineCargo. Happened if the client received an updated campaign save while trying to load the sub between rounds. - Fixed a broken waypoint in Berilia's cargo bay. -- Fixed seeds sometimes vanishing when trying to plant them in MP. +- Fixed seeds sometimes vanishing when trying to plant them in MP. - Fixed planter boxes displaying the "uproot" message when empty. - Fixed depth charges going through doors and hatches. - Fixed ability to dock docking hatches to ports and vice versa. @@ -187,7 +402,7 @@ Changes: - Increased the stun of smg rounds from 0.125 to 0.15 to give it a bit more stopping power. - Lowered Husks' health regeneration and bleeding reduction. Crawler Husks now regenerate too. Lowered their health a bit to compensate it. - Hammerhead and Golden Hammerhead: Increased health, added some protection on claws and the tail end. Slightly increase the slow swimming speed and animations. The claws don't break anymore when shot with coilgun. -- Hammerheads and Husks don't avoid gun fire anymore. Pets now avoid gun fire. +- Hammerheads and Husks don't avoid gun fire anymore. Pets now avoid gun fire. - Removed Endworm's weak point in the mouth. - Most outpost events no longer trigger automatically, but require interacting with a specific item/character. - Taking items that contain stolen items counts as stealing, so you can't for example put a toolbox inside an outpost cabinet, load it full of items and then take it. @@ -355,7 +570,7 @@ Campaign changes: - Beacon stations are shown on the campaign map. - Visual changes to the map to make it a bit more intuitive. - When you enter a level with a beacon station, you always get an optional side objective to restore it even if you haven't selected a beacon mission. -- Added radiation on the campaign map: the intensity of the radiation around Jupiter is slowly increasing, which is forcing Europans to delve deeper under the ice. In practice, the radiation gradually destroys the outposts starting from the left side of the map, making it more dangerous and costly to stay in these areas. The intention behind this is to prevent players from farming resources indefinitely in the low-difficulty areas of the game before proceeding further. +- Added radiation on the campaign map: the intensity of the radiation around Jupiter is slowly increasing, which is forcing Europans to delve deeper under the ice. In practice, the radiation gradually destroys the outposts starting from the left side of the map, making it more dangerous and costly to stay in these areas. The intention behind this is to prevent players from farming resources indefinitely in the low-difficulty areas of the game before proceeding further. - Gating progress between biomes: you need a certain amount of money or reputation before you can enter the next biome. - Reworked exit points at uninhabited locations: there's a hole/tunnel above the start/end of the level the sub needs to enter to leave. @@ -612,7 +827,7 @@ v0.12.0.1 --------------------------------------------------------------------------------------------------------- - Adjustments and balancing to monster spawns. -- Modifed meds, buffs and poisons fabrication times. +- Modified meds, buffs and poisons fabrication times. - Potential fix to occasional disconnects with an "index was outside the bounds of the array (ENTITY_POSITION)" error message. Happened when lots of items and characters were being created and removed in rapid succession, for example when using turrets against large numbers of enemies. - Fixed a rare crash caused by an "index out of range" exception in Hull.Update after loading or mirroring certain custom submarines. - Fixed submarine class not affecting the depth at which a submarine starts taking pressure damage. @@ -740,7 +955,7 @@ Bots: - Bots should now properly respect ignored targets also when they are already targeting the item when you tell them to ignore it. - Fixed bots not equipping diving gear when the oxygen level is low and there is no leaks/water in the hull, causing them to suffocate. - Fixed bots "forgetting" autonomous operate (operate reactor or steer) orders if the objectives happen to fail. -- Fixed bots sometimes incorrectly abandoning a movement objective, when the path requires a diving gear. +- Fixed bots sometimes incorrectly abandoning a movement objective, when the path requires a diving gear. - Bots should now know how to get back inside through gaps in the hull. - Fixed NPCs not being able to repower the reactor if the player somehow manages to unpower it. - Bots now run when they are ordered to clean up things. @@ -873,7 +1088,7 @@ v0.11.0.10 - Fixed crashing when entering a new level in the campaign when an inactive pump has been infected with ballast flora. - Fixed ballast flora branches respawning instantly if they're destroyed while they're growing towards a target. - Fixed crashing when attempting to place components outside of the submarine in test mode. -- Fixed inability to rewire beacon stations when rewiring is disabled on the server. +- Fixed inability to rewire beacon stations when rewiring is disabled on the server. - Fixed repair tools that aren't held causing a crash upon use (only affects modded items). - Fixed raycast weapons (revolvers, shotguns, SMGs) sometimes not hitting monsters in specific areas outside the sub. - Fixed submarine's price field being difficult to edit in the sub editor due to the value getting clamped above the minimum price while typing in the box. @@ -1525,7 +1740,7 @@ v0.10.3.0 --------------------------------------------------------------------------------------------------------- - Fixed SalvageMission not spawning the item if the mission has been attempted previously, causing an "attempted to pick up a removed item" error when trying to pick up the artifact/logbook. -- Fixed bots going to operate the reactor/navterminal in the main sub when they are inside the outpost. Now they should only be allowed to do this when ordered to. +- Fixed bots going to operate the reactor/navterminal in the main sub when they are inside the outpost. Now they should only be allowed to do this when ordered to. - Fixed mechanic tutorial getting softlocked if the oxygen tanks are put in the deconstructor without putting them in the player inventory first (e.g. by putting them inside a diving mask and moving them from there to the deconstructor). - Fixed "failed to spawn item, component index out of range" error when an item that originally spawned in a container has been moved inside another container whose ItemContainer component doesn't have the same index as the previous one (e.g. when moving items from cabinets in a wreck into a toolbox). - Fixed dialog prompts in the "clownrelations1" and "engineers_are_special" events being displayed to all players in the server. @@ -1678,7 +1893,7 @@ Additions and changes: - Added 2 new moloch variants: Black Moloch and Moloch Pupa. - Reworked Moloch. - Overhauled level layouts and events (longer and more difficult levels). -- Added two new afflictions: medical items and poisons cause organ damage instead of internal damage and explosions cause deep tissue injuries. Both are functionally identical to internal damage, and treated with the same items. +- Added two new afflictions: medical items and poisons cause organ damage instead of internal damage and explosions cause deep tissue injuries. Both are functionally identical to internal damage, and treated with the same items. - Added DXT5 texture compression to reduce memory consumption. Slightly increases loading times; if you're not short on memory, you may want to disable the compression from the game settings. - Added partial dismemberment for live creatures. Currently enabled only for non-humanoids. (Dismembering dead bodies was already in the game). - Destructible shells/armor -> Moloch's shell can now be destroyed. @@ -1713,7 +1928,7 @@ Additions and changes: - Hulls can be multiedited in the sub editor. - Placed down wires can now be re-equipped in the sub editor by double clicking a loose end. - Added charging docks to Remora. -- Adjusted how pixel sizes are converted to meters (which are used to display the submarine's dimensions and distances on the navigation terminal). Previously 100 pixels corresponded to 1 meter, now it's 80px -> 1m, making the human characters about 1.75m tall. +- Adjusted how pixel sizes are converted to meters (which are used to display the submarine's dimensions and distances on the navigation terminal). Previously 100 pixels corresponded to 1 meter, now it's 80px -> 1m, making the human characters about 1.75m tall. - Distance calculations on the navigation terminal take the shape of the path into account instead of just using the direct distance to the target. - Made improvements to the manual order assignment by adding always visible name labels, displaying indicators for characters' current orders, and repositioning the nodes. - Reduced the damage range of fires, characters don't take damage from fires if there's a closed door or a wall in between. @@ -1929,7 +2144,7 @@ Bugfixes: - Fixed enablecheats command not being relayed to server. - Fixed light component and alarm siren/buzzer states occasionally getting desynced. - Fixed inability to enter the sub through very small hulls. -- Fixed antibiotics not giving husk infection resistance when shot from a syringe gun. +- Fixed antibiotics not giving husk infection resistance when shot from a syringe gun. - Fixed text overflows in the player management panel in the server lobby in languages other than English. - Fixed searchlight toggle doing nothing. - Fixed hulls that have minuscule amounts of water in them (too small to be even rendered) being able to trigger InWater effects and water footstep sounds. @@ -2089,7 +2304,7 @@ UI/UX improvements: - Periscopes can be deselected by pressing esc. - Fabricators can pull ingredients directly from the user's inventory without having to place them in the fabricator's input slots. - Lock the on/off switch in the pump interface when the state is controlled by signals, same with the engine slider. -- 1 second cooldown before doors can be opened/closed after someone else has opened/closed them. Makes it less likely for doors to be opened/closed accidentally when multiple people are trying to use them at the same time. +- 1 second cooldown before doors can be opened/closed after someone else has opened/closed them. Makes it less likely for doors to be opened/closed accidentally when multiple people are trying to use them at the same time. - Show a warning if trying to start a campaign for the first time without playing the tutorials. - Diving suits and fire extinguishers are not automatically picked up from the lockers/brackets when clicking on them to make it less likely to accidentally pick them up. Instead, clicking on them opens the inventory of the container, the same way when interacting with e.g. a steel cabinet. - Subinventories (= inventories inside items, toolboxes for example) open/close faster and cannot be interacted with until fully open. @@ -2195,7 +2410,7 @@ AI: - Replaced the generic "cannot reach target" messages with context-specific and more descriptive messages. - Bots now take the other bots into account when they evaluate the importance of the tasks. Fixes multiple bots going to fix the same leaks or repair the same items. - Bots should now abandon the combat objective only when not fleeing from an enemy. If they fail to flee from an enemy, they will fight (or avoid) instead. -- Fixed bots loading the turrets only with the default ammunition. +- Fixed bots loading the turrets only with the default ammunition. - Fixed multiple bots trying to navigate the submarine at the same time. - Fixed pathfinding applying 10x more penalty on vertical distance when the host is outside (should only apply inside). - Fixed bots starting the path from obstructed waypoints or waypoints that are inside when they are outside or vice versa. @@ -2235,7 +2450,7 @@ Monsters: - Fixed Mudraptors sometimes squeezing themselves towards doors without being able to attack them. - Fixed monsters not reacting to being fired with turrets unless they can target the attacker. - Fixed minor slipping in Mudraptor's walking animation. -- Weapons and tools now have ai targets that are only activated when the items are used -> shooting monsters should make you much more attractive target than just swimming peacefully around. +- Weapons and tools now have ai targets that are only activated when the items are used -> shooting monsters should make you much more attractive target than just swimming peacefully around. Multiplayer: - Fixed a bunch of bugs that caused "missing entity" errors. However, there are many different reasons the error can occur, so even though we have not run into the issue anymore during out testing rounds, there is still a chance it may occur in some situations. @@ -2351,7 +2566,7 @@ Electricity fixes: - Fixed LightComponents being toggled twice when they receive a signal to the toggle connection. Misc fixes: -- Fixed "last used" listbox overlapping with the entity visibility tickboxes in the submarine editor on low resolutions. +- Fixed "last used" listbox overlapping with the entity visibility tickboxes in the submarine editor on low resolutions. - Fixed misaligned colliders on the "Shell A Cap 0 deg A/B" wall pieces. - Fixed links disappearing between linked subs and docking ports when loading in the sub editor. - Fixed loading freezing for up to 10 seconds if the game cannot fetch the remote content for the main menu (update notifications, changelogs, etc). @@ -2403,9 +2618,9 @@ Misc additions and changes: - A new submarine, Kastrull. - 6 new traitor missions with much more varied objectives. - More variants of all job outfits. -- 30 new character face sprites. +- 30 new character face sprites. - Overhauled job assignment logic to make the job distribution a little more balanced. Now each client gets assigned one of the spawnpoints (and its associated job) according to their job preference, which means that if the sub for example has 2x as many engineer spawnpoints than medic spawnpoints, there tend to be 2x more engineers. When playing with a mod that adds new jobs to the game, spawnpoints that have no job associated with them are considered spawnpoints for the non-vanilla jobs. - - We would like to get feedback from players about this: does the job assignment seem fair, are people generally getting the jobs they want? + - We would like to get feedback from players about this: does the job assignment seem fair, are people generally getting the jobs they want? - New logic components: sin, cos, tan, asin, acos, atan, modulo, round, ceil, floor and factorial. - Improved autopilot: much faster and less likely to get stuck. - We would like to get feedback from players about this: Is there still incentive to steer manually? @@ -2617,8 +2832,8 @@ tags, it will replace the vanilla wrench. - The husk affliction can now be modified and applied on any character. - Support for multiple simultaneous husk afflictions based on the same system. - Support for multiple husk appendages. Made the attachment limb configurable in the affliction definition. -- Additional content package validity checks during startup: make sure all XML files in the package can -be loaded, and disable the package if they can't. Fixes tons of console errors on startup when a mod with +- Additional content package validity checks during startup: make sure all XML files in the package can +be loaded, and disable the package if they can't. Fixes tons of console errors on startup when a mod with invalid XML files is enabled. - Don't allow publishing workshop items that contain invalid XML files. - Don't allow selecting invalid content packages in the settings menu. @@ -2643,7 +2858,7 @@ drawn on the character. - Exposed structure sound types. - Exposed the mouth position. - Exposed the angular damping and density for limbs (removed mass, which was not used). -- Added a default texture path for ragdolls so that the texture can only be defined once. Limb specific +- Added a default texture path for ragdolls so that the texture can only be defined once. Limb specific texture definitions override this. - Allow to define a group for different species. The characters in the same group are friendly to each other. - DecorativeSprites can now be used on character limbs (like on items). @@ -2665,7 +2880,7 @@ by holding shift. - Added "pause" console command (only usable in single player). Bugfixes: -- Fixed crashing when the recharge speed of a PowerContainer with no interface (e.g. Alien Generator) is +- Fixed crashing when the recharge speed of a PowerContainer with no interface (e.g. Alien Generator) is adjusted by a signal or a bot. - Fixed docking interface becoming active on navigation terminals when any of the submarine's docking ports are close to another docking port, even if the terminal in question is not wired to that port. @@ -2673,18 +2888,18 @@ ports are close to another docking port, even if the terminal in question is not - Fixed clients not creating a download prompt when a sub they don't have is selected by vote. - Fixed traitor missions almost always placing the mission-related items inside the same containers. - Fixed traitor goal durations being displayed as "duration(xx) seconds" instead of "xx seconds". -- Fixed Traitor's "find an item" objectives not being considered complete if the target item is inside +- Fixed Traitor's "find an item" objectives not being considered complete if the target item is inside another item within the traitor's inventory. -- Fixed server using the provided campaign savefile name as-is (without the required .save file extension) -when starting a new campaign through the console. Caused clients to throw "File transfer failed (wrong +- Fixed server using the provided campaign savefile name as-is (without the required .save file extension) +when starting a new campaign through the console. Caused clients to throw "File transfer failed (wrong file extension """!)" errors and prevented them from receiving the save files. -- Fixed servers being able to start the round multiple times by spamming the "start" console command +- Fixed servers being able to start the round multiple times by spamming the "start" console command before loading the round finishes. - Fixed rewiring sound playing whenever a remote player is using a rewireable device. - Fixed subinventories not opening when grabbing another character with no items in the corresponding slot. -- Fixed draggable inventories getting stuck to a half-open state if the item is equipped when +- Fixed draggable inventories getting stuck to a half-open state if the item is equipped when the inventory is opening/closing. -- Fixed light sprites being mirrored when the item is mirrored, even if the mirroring the item's sprite +- Fixed light sprites being mirrored when the item is mirrored, even if the mirroring the item's sprite had been disabled (e.g. junction boxes). - Fixed motion sensor detection area not being flipped when the item is mirrored. - Fixed status monitor not mirroring rooms on the display in mirrored subs. @@ -2695,12 +2910,12 @@ had been disabled (e.g. junction boxes). - Fixed damaged item sprites that are set to fade in according to the damage always being drawn at full opacity. - Fixed spectators being distributed into teams in combat missions, potentially leading to imbalanced crew sizes. - Notify the client using the "togglekarmatestmode" command about the test mode being enabled/disabled. -- Send karma change notifications when karma has changed by 1 unit or more when test mode is enabled, not +- Send karma change notifications when karma has changed by 1 unit or more when test mode is enabled, not just when an action causes an immediate change of 1 unit or more. - Fixed adjacent sprites bleeding into the platform and topwindow sprites. - Fixed autocompleting submarine/shuttle names when using the submarine/shuttle console commands. - Fixed some items (like sonar beacon) attracting monsters even when they're powered off. -- Fixed inability to open the pause menu if an inventory slot had been highlighted when exiting +- Fixed inability to open the pause menu if an inventory slot had been highlighted when exiting the game screen. - Fixed welded door sprites "twitching" when the submarine moves. - Fixed crashing when a character with no hands or arms drops a holdable item. @@ -2708,12 +2923,12 @@ the game screen. - Traitor missions are considered unsuccessful if the objectives cannot be completed (for example if the submarine doesn't have suitable containers to place the traitor items inside). - Fixed the "Barotrauma" title text staying invisible in the main menu when coming back from the credits. -- Fixed clients not refreshing an item's editing hud when a remote player adjusts the values (i.e. if two -players had selected the same lamp and one changed the color value, the other client wouldn't see +- Fixed clients not refreshing an item's editing hud when a remote player adjusts the values (i.e. if two +players had selected the same lamp and one changed the color value, the other client wouldn't see the value change in the editing hud). - Fixed a couple of the additive light sprites being slightly offset from the lamp. -- Clients don't display client-side vitality changes in healthbars until the actual vitality is received -from the server. Fixes health occasionally dropping and then jumping back up if the client predicts damage +- Clients don't display client-side vitality changes in healthbars until the actual vitality is received +from the server. Fixes health occasionally dropping and then jumping back up if the client predicts damage incorrectly (e.g. if a melee attack hits client-side but doesn't server-side). - Fixed character syncing being very inaccurate when switching to freecam and spectating a character far away from other players. @@ -2767,10 +2982,10 @@ Caused potassium and magnesium to explode continuously client-side when immersed - Fixed occasional "error while reading a message from the server" console errors when joining a server. - Fixed clients occasionally timing out and disconnecting during the loading screens. - Fixed character name box resetting in the server lobby when another client joins or disconnects. -- Fixed "collection was modified" error when a client who's been given control of a bot tries to use +- Fixed "collection was modified" error when a client who's been given control of a bot tries to use the report buttons. - Fixed server hanging when a client joins if the server has a very large number of submarines installed. -- Fixed a bunch of client-side console commands crashing the game if the client disconnects while a question +- Fixed a bunch of client-side console commands crashing the game if the client disconnects while a question prompt is active and then enters something in the console. - Fixed full SteamP2P servers not showing up on the server list regardless of the filters. - Fixed "remove ban" and "range ban" buttons in the server settings menu doing nothing. @@ -2782,17 +2997,17 @@ prompt is active and then enters something in the console. - Fixed scroll values resetting in the multiplayer campaign store menu when purchasing/selling items. Miscellaneous improvements: -- Attempting to drop an item to an empty inventory slot that's not of the right type (e.g. trying to put +- Attempting to drop an item to an empty inventory slot that's not of the right type (e.g. trying to put an extinguisher in the normal inventory slots), automatically moves the item to the correct slot if it's free. - Made irrelevant items (light components, lamps, small automated pumps, etc) non-interactable in the tutorials. -- The first patient cannot be healed in the doctor tutorial until the "take the patient to the medbay" +- The first patient cannot be healed in the doctor tutorial until the "take the patient to the medbay" objective has been completed. - The docking interface does not become active in the captain tutorial when leaving the first outpost. - Added sounds for rewiring and repairing. Miscellaneous fixes: - Fixed skills not having any effect on repair durations. -- Fixed inability to enable content packages created for a version prior to 0.9.2.0 (ones that have the +- Fixed inability to enable content packages created for a version prior to 0.9.2.0 (ones that have the content package in the Data/ContentPackages folder instead of in the mod folder). - Use a welding tool icon to indicate damaged walls in the mechanic tutorial, because the generic repair icon caused some players to think the wall needs to be repaired with a wrench. @@ -2800,13 +3015,13 @@ icon caused some players to think the wall needs to be repaired with a wrench. - Reduced repair durations in the engineer/mechanic tutorials. - Fixed draggable subinventories staying visible when stunned. - Fixed light sprites not being scaled in the sub editor when resizing lamps. -- Fixed contained items not changing their position when flipping the container (e.g. oxygen generator +- Fixed contained items not changing their position when flipping the container (e.g. oxygen generator with some tanks inside). - Fixed projectiles emitting sparks when they hit a character (not just when they hit a structure). - Fixed sonar's zoom slider not moving when autodocking zooms in. - Fixed small creatures being unable to reach waypoints. - Fixed bots getting stuck while trying to repair items (in practice hatches) while holding ladders. -- Hid SMG rounds and coilgun bolts from the sub editor because they're not usable by themselves, but are +- Hid SMG rounds and coilgun bolts from the sub editor because they're not usable by themselves, but are spawned automatically when the SMG/coilgun is fired. - Hid the junction box tutorial variant from the sub editor. - Stun guns and darts can be fabricated and purchased. @@ -2816,21 +3031,21 @@ difficult to hit the crawler after it falls to the ground). - Added confirmation popups when exiting the sub editor or character editor to prevent losing unsaved work. - Fixed reactor warning lights being clickable and becoming invisible when pressed. - Fixed CPR button becoming invisible when pressed. -- Fixed water occasionally flowing only through one gap in a hull, for example water only draining -through a hole at a one side of a room even if there is another hole at the other side of the room. +- Fixed water occasionally flowing only through one gap in a hull, for example water only draining +through a hole at a one side of a room even if there is another hole at the other side of the room. - Fixed scrollbars not resizing when filtering lists (e.g. the submarine list in the "new game" menu). - Fixed being able to fire flamers (and other repair tools) through walls and doors. - Fixed flamers working underwater when the water is shallow enough for the character to stand. -- Fixed battery recharge rate slider not moving when the recharge rate is set by an incoming signal +- Fixed battery recharge rate slider not moving when the recharge rate is set by an incoming signal or a remote player. - Fixed some connection panels being rewireable in vanilla subs when a wire is equipped. - Fixed some pre-placed body armors and grenades in vanilla subs having an incorrect scale. -- Fixed crashing when a character gains a skill level on a skill that's not initially configured +- Fixed crashing when a character gains a skill level on a skill that's not initially configured for the character class. -- Fixed "Add File" dialog of the Workshop publish screen always using the mod's root folder as the file +- Fixed "Add File" dialog of the Workshop publish screen always using the mod's root folder as the file path even if the file is in a subfolder. - Fixed contentpackage version number not being updated when updating an older workshop item. -- Fixed subinventories closing when items are dropped into or removed from them, which made reloading +- Fixed subinventories closing when items are dropped into or removed from them, which made reloading weapons or putting several items into a container cumbersome. --------------------------------------------------------------------------------------------------------- @@ -2839,7 +3054,7 @@ v0.9.2.2 Karma improvements: - Attacking someone who has just recently attacked you doesn't reduce karma. -- The karma penalty from attacking someone scales according to their karma (i.e. smaller penalty for +- The karma penalty from attacking someone scales according to their karma (i.e. smaller penalty for attacking a griefer whose karma is low). - Damaging characters by performing CPR doesn't decrease karma. - Fixed karma decreasing when moving a wire from a connection to another. @@ -2851,9 +3066,9 @@ value of 50. Bugfixes: - Fixed mechanic tutorial crashing after the welding task when playing in Russian. -- Less restrictive client name symbol constraints. Fixes clients failing to join servers if their name +- Less restrictive client name symbol constraints. Fixes clients failing to join servers if their name contains Chinese symbols for example. -- Fixed inability to enable/update content packages created for a version prior to 0.9.2.0 (ones that have +- Fixed inability to enable/update content packages created for a version prior to 0.9.2.0 (ones that have the content package in the Data/ContentPackages folder instead of in the mod folder). --------------------------------------------------------------------------------------------------------- @@ -2869,20 +3084,20 @@ v0.9.2.1 - Fixed server crashing when attempting to greet a traitor with no objective. - Fixed dead characters being occasionally selected as traitors. - Fixed info menu still displaying the old traitor objective after a traitor dies and respawns. -- Allow traitors to re-use the sabotage button when someone starts to repair the device without having +- Allow traitors to re-use the sabotage button when someone starts to repair the device without having to re-open the interface. - Fixed grenades exploding multiple times when triggered using a detonator. - Scaled down alien materials (oxygenite, sulphurite, fulgurium, etc) and fixed their collider sizes. - Karma doesn't spontaneously decay/increase when dead. - Increased karma penalties for poisoning someone. - Option to kick clients with low karma from the server and ban them if they get kicked more than x times. -- Space herpes randomly toggles inverted controls on/off to make it more difficult to bypass the effect +- Space herpes randomly toggles inverted controls on/off to make it more difficult to bypass the effect by modifying keybinds. -- Fixed workshop item preview image not being refreshed in the publish tab when adding a preview image +- Fixed workshop item preview image not being refreshed in the publish tab when adding a preview image to an item that doesn't have one. -- Fixed "error loading submarine" console error when creating a copy of a submarine, deleting the original +- Fixed "error loading submarine" console error when creating a copy of a submarine, deleting the original sub and then saving the copy. -- Fixed server logging an "attempted to send a chat message to a null client" error when a client tries +- Fixed server logging an "attempted to send a chat message to a null client" error when a client tries to send a private message to a non-existent client. - Enable dynamic range compression and VOIP attenuation by default. - Fixed inability to extinguish fires through holes in walls. @@ -2902,15 +3117,15 @@ Easier server hosting: host to forward their ports. Expanded traitor feature: -- Multi-step traitor missions that not only make things more interesting to the traitors themselves but +- Multi-step traitor missions that not only make things more interesting to the traitors themselves but also to give the rest of the crew a fighting chance of detecting the traitor. Anti-griefing: -- Karma: a system that detects malicious actions and automatically creates a more challenging experience -for griefers, or potentially triggers a kickban. The feature is completely optional and the server hosts -can decide how aggressively it will react to malicious actions. In a nutshell, you can lose karma for doing -dumb things, karma is regained gradually over time and may be increased more rapidly by doing good things, -thus negating false positives and also giving you the chance to redeem yourself. Lose too much karma and +- Karma: a system that detects malicious actions and automatically creates a more challenging experience +for griefers, or potentially triggers a kickban. The feature is completely optional and the server hosts +can decide how aggressively it will react to malicious actions. In a nutshell, you can lose karma for doing +dumb things, karma is regained gradually over time and may be increased more rapidly by doing good things, +thus negating false positives and also giving you the chance to redeem yourself. Lose too much karma and you'll start to experience increasing levels of inconvenience. - Wire griefing is more difficult: wires have to be disconnected from the connection panels at both ends before they can be removed. Disconnected wires can be seen visibly hanging from the device, with noticeable @@ -2931,10 +3146,10 @@ New submarine: Bugfixes: - Fixed crashing when selecting doors or gaps in the sub editor. - Fixed crashing when combining items inside an ItemContainer (e.g. cabinet, deconstructor). -- Fixed shuttles not being able to redock into some submarines with unconventionally positioned docking +- Fixed shuttles not being able to redock into some submarines with unconventionally positioned docking ports. Specifically, if a port needed to be docked to from below but was positioned above the center of the submarine (or vice versa), the docking interface would not activate on the navigation terminal. -- Fixed docking port using the wrong submarine's position for joint adjustment, causing errors when +- Fixed docking port using the wrong submarine's position for joint adjustment, causing errors when docking a submarine with greater mass to one with a smaller mass. - Fixed an issue that caused occasional ID mismatch errors if a client died during a multiplayer campaign round and disconnected before the round ended. @@ -2946,49 +3161,49 @@ campaign round and disconnected before the round ended. - Fixed inability to combine items in multiplayer. - Fixed occasional "index out of range" errors when loading walls that have been set to a non-default scale in the submarine editor. -- Fixed inability to scroll the crew list with the mouse wheel when the cursor is over certain parts +- Fixed inability to scroll the crew list with the mouse wheel when the cursor is over certain parts of the list. - Fixed sonar pings stopping mid-way when active sonar is turned off, which could be exploited to stop the pings before they reach a monster further away from the sub. - Fixed clients not seeing other characters when entering spectator mode after their character has been eaten by a creature. -- Fixed clients not seeing other characters in spectator mode after the distance between the submarine +- Fixed clients not seeing other characters in spectator mode after the distance between the submarine and the client's corpse gets great enough. -- Fixed clients getting stuck to a non-functional game screen if they start a new round before the ending +- Fixed clients getting stuck to a non-functional game screen if they start a new round before the ending cinematic has finished server-side. - Fixed projectiles hitting a character you're standing next to when firing. - Fixed characters automatically equipping handcuffs (i.e. handcuffing themselves) if they were picked up when the only free inventory space was the hand slots. -- Fixed a rare race condition that occasionally caused the game to crash during the loading screen with +- Fixed a rare race condition that occasionally caused the game to crash during the loading screen with a "no text packs available in English!" error message. -- Fixed husk infection being healable with broad-spectrum antibiotics even when the infection has reached +- Fixed husk infection being healable with broad-spectrum antibiotics even when the infection has reached the final stage. -- Fixed attachable items (detonators, electrical components) becoming deattachable without any tools +- Fixed attachable items (detonators, electrical components) becoming deattachable without any tools if the sub is saved after deattaching them. -- Fixed projectile raycasts not taking wall rotation into account, causing the projectiles to hit sloped +- Fixed projectile raycasts not taking wall rotation into account, causing the projectiles to hit sloped walls when standing next to them. -- Fixed PowerTransfer components checking overloads based on the item the recursive power check starts from, +- Fixed PowerTransfer components checking overloads based on the item the recursive power check starts from, not the item that's currently being checked. Caused junction boxes to never break or catch fire in some subs - Fixed crashing in the character editor when no textures were found for the selected character. - Fixed crashing if a humanoid character has no knees or ankles. - Fixed EventManager calculating flooding amount incorrectly, causing floods to only have a very small effect on the intensity value. -- Fixed batteries and supercapacitors being able to provide power through their signal connections +- Fixed batteries and supercapacitors being able to provide power through their signal connections (e.g. "set_charge_rate", "charge_rate_out"). -- Lighting fix: obstruct background lights behind hulls. Previously the background lights would only get -obstructed by background structures, causing light to "bleed through" parts with no structures (e.g. +- Lighting fix: obstruct background lights behind hulls. Previously the background lights would only get +obstructed by background structures, causing light to "bleed through" parts with no structures (e.g. humpback's docking port) and windows in the background walls. - Fixed item loading being interrupted if any item XML cannot be loaded, causing some items not to be loaded if any of the selected content packages are missing files or contain corrupted item XMLs. - Fixed fire sounds not playing when standing inside a fire source. -- Fixed client names being converted to lower case when comparing them to the name written by the sender, +- Fixed client names being converted to lower case when comparing them to the name written by the sender, preventing the message from being sent if the name wasn't written in lower case. - Pass private messages to the client hosting the server when the message is targeted to the server. - Private messages appear in the server log. - Fixed plasma cutter and welding tool hit particles being offset from the flame. -- Fixed steel bar and titanium-aluminium alloy deconstructing to 100% condition items even when degraded +- Fixed steel bar and titanium-aluminium alloy deconstructing to 100% condition items even when degraded (which could be used as an exploit to get infinite coilgun rounds). -- Fixed holdable items staying in the characters hand(s) when swapping them from a hand slot to another +- Fixed holdable items staying in the characters hand(s) when swapping them from a hand slot to another limb slot (for example, when moving a flashlight from a hand slot to the head slot). - Fixed text wrapping not working properly with words that are longer than a single line. - Fixed deconstructor losing some of the material from deconstructed items if the output container couldn't hold all of the new items. @@ -2996,7 +3211,7 @@ limb slot (for example, when moving a flashlight from a hand slot to the head sl - Fixed a crash when retrieving lobby information with an uninitialized IP. - Fixed max player count in server list & duplicate lobby entries. - Fixed arrow keys not changing caret position when typing. -- Fixed doors looking repaired as soon as the condition goes above 0%, even though the collisions aren't +- Fixed doors looking repaired as soon as the condition goes above 0%, even though the collisions aren't re-enabled until the door gets above 50%. - Fixed water not leaking through broken doors. - Fixed inability to throw items through platforms. @@ -3011,26 +3226,26 @@ from "Content/Characters". Fixes mods not being able to load animation/ragdoll f are not defined explicitly in the character configuration file. - Improved performance by deleting dead monsters that are far away from the sub and by disabling physics on dead bodies when they stay still long enough. -- Added option to use conditionals to activate/deactivate ItemComponents. You can take a look at the +- Added option to use conditionals to activate/deactivate ItemComponents. You can take a look at the reactor and junction box config files to see how it's used. - Added some indicator lights to junction boxes and reactors. - Stun batons don't affect large enemies (molochs, hammerheads, endworms, etc). - In wiring mode, items are selected by pressing E instead of clicking. Selecting items with the left mouse button made it very difficult to manipulate the wires because it was easy to accidentally select -some device instead of a wire node. +some device instead of a wire node. - Crate/toolbox inventories stay open and can be moved across the screen when the item is equipped. - Added limits to submarine name and description length. - Delete incomplete file downloads when disconnecting from a server while a file transfer is active. Prevents console errors about corrupted submarine/save files caused by the partially downloaded files. - Increased Humpback's battery capacity. - Added widgets for manipulating coilgun/railgun rotation limits in the sub editor. -- Added a command for setting the value of a property on all selected entities in the sub editor +- Added a command for setting the value of a property on all selected entities in the sub editor (for example, "setentityproperties scale 2" would set the scale of all selected items/structures to 2). -- Show ballast tanks and airlocks in a different color on the status monitor to make it easier to +- Show ballast tanks and airlocks in a different color on the status monitor to make it easier to distinguish which rooms are actually flooding and which are supposed to have water in them. - Option to define multiple inventory variants for a character (e.g. to add variation to monster loot). - Start playing the main menu music during the loading screen. -- More reliable human walk sounds (played at specific points of the walk cycle instead of relying on +- More reliable human walk sounds (played at specific points of the walk cycle instead of relying on impacts between the feet and the floor). - Option to show a 16x16 grid and snap the cursor to it in the sprite editor. - Reactor can be controlled with movement keys. @@ -3050,7 +3265,7 @@ Additions and changes: - Toolboxes can be fabricated and decontructed. - Nerfed nitroglycerin's structure damage and increased the impact tolerance from 4 to 6. - Made alien pistols a little less underwhelming (sounds, recoil, particle effects). -- Deconstructors drop all items that are inside the deconstructed item. Previously they would just get destroyed, +- Deconstructors drop all items that are inside the deconstructed item. Previously they would just get destroyed, making it a potential exploit to destroy items that should not be possible to deconstruct. - Modified welding fuel tank and oxygen tank sprites a bit to differentiate them from each other a bit more. - Modified plasma cutter and welding tool sprites a bit to differentiate them from each other a bit more. @@ -3058,7 +3273,7 @@ making it a potential exploit to destroy items that should not be possible to de - Server list can be sorted according to ping, name, compatibility or any of the other property in the list. - Docking ports of the enemy submarine are not shown on the sonar during combat missions. - Recharge headset batteries between rounds in single player. -- Disable status monitor displays when out of power. +- Disable status monitor displays when out of power. - Reduce server CPU usage. - Update ladder and gap references if a waypoint is moved around in the editor. - More descriptive error messages when publishing a Workshop item fails. @@ -3078,23 +3293,23 @@ making it a potential exploit to destroy items that should not be possible to de changed by editing a parameter called "allowrewiring" in serversettings.xml. Misc bugfixes: -- Fixed the position of an outpost's docking port not being taken into account when determining how to -place it in the level. Caused large submarines sometimes to collide with walls when docked to outposts +- Fixed the position of an outpost's docking port not being taken into account when determining how to +place it in the level. Caused large submarines sometimes to collide with walls when docked to outposts where the port is offset from the center. - Fixed corrupted sub files causing crashes. -- Fixed item editing HUD not appearing on any other item after one item has been selected in-game +- Fixed item editing HUD not appearing on any other item after one item has been selected in-game (e.g. editing the channel on a wifi component prevented editing other items). -- Fixed ladders and other resizeable items reverting to their original size if they're resized and +- Fixed ladders and other resizeable items reverting to their original size if they're resized and the sub is saved and reloaded. - Fixed inability to resize gaps in the sub editor after they've been placed. -- Reactors take other power sources into account when calculating how much power they need to generate. +- Reactors take other power sources into account when calculating how much power they need to generate. Fixes overloads on Humpback when turning on the backup batteries and operating the reactor normally. - Fixed watchmen not retaliating when a character does very small amounts of damage to them. - Fixed bots being able to take handcuffs off from themselves. - Fixed waypoint and spawnpoints being selectable in the sub editor even if they're hidden. - Fixed deconstructing coilgun ammo boxes giving out more materials than the materials required to fabricate them. - Scrollbars can only be dragged if the mouse button is pressed down while the cursor is on the scrollbar, -not by holding the button and moving it on the scrollbar. Fixes accidentally switching from slider +not by holding the button and moving it on the scrollbar. Fixes accidentally switching from slider to another (often happens in the reactor interface). - Fixed crashing when setting limb or joint scale to 0 using console commands. - Fixed dropdowns in the Workshop item publish menu being draw behind the buttons below them. @@ -3102,7 +3317,7 @@ to another (often happens in the reactor interface). - Ignore keyboard inputs (delete, arrow keys, copy/paste) in the sub editor when a textbox is selected. Prevents accidentally deleting items/structures when attempting to delete text from a textbox. - Toolboxes can't be put inside other toolboxes or in the doctor's clothes. -- Fixed items bought from the store during a singleplayer campaign session being deducted from the credits +- Fixed items bought from the store during a singleplayer campaign session being deducted from the credits when save & quitting from the map. - Flares stop emitting particles when inside an inventory. - Fixed oxygen not flowing through horizontal gaps between subs (e.g. between Remora and the drone). @@ -3113,19 +3328,19 @@ being split into separate buttons when playing the game in Chinese. - Fixed "Text Self_CauseOfDeathDescription.Unknown not found" console error if a character gets killed by being inside a respawn shuttle when it despawns. - Fixed sonar not scaling properly when resolution is changed mid-round. -- Fixed fabricators & deconstructors displaying the "insufficient power" warning when power is low +- Fixed fabricators & deconstructors displaying the "insufficient power" warning when power is low (but still high enough for the devices to run). Networking fixes: -- Fixed another ID mismatch problem that occasionally caused clients to get kicked out in multiplayer, +- Fixed another ID mismatch problem that occasionally caused clients to get kicked out in multiplayer, usually with an error message warning about a missing item. - Fixed players always getting range banned when banned by a client. -- Fixed turrets not being aimed correctly in multiplayer if they're in another sub (e.g. the coilgun in +- Fixed turrets not being aimed correctly in multiplayer if they're in another sub (e.g. the coilgun in Remora's drone). - More reliable fabricator and deconstructor syncing. Should fix items disappearing when multiple players attempt to use the device at the same time and strange timing inconsistencies when deconstructing multiple items in succession. -- Fixed all servers showing "unknown" as the game mode in the server list. +- Fixed all servers showing "unknown" as the game mode in the server list. - Fixed server not allowing forward slashes in host messages. - Fixed repair interface sometimes getting stuck close to 100% in multiplayer. @@ -3135,8 +3350,8 @@ Character editor fixes and improvements: - A dropdown for selecting which content package to add the character to. - Allow creating a new content package during character creation. - Added hotkeys 1, 2, 3 for limb, joint, and animation modes respectively. -- Change the logic of deciding on which parameters are shown and when. In the ragdoll mode you can now -see the ragdoll, but also scale it and see the main parameters. Source rects are now longer shown on the +- Change the logic of deciding on which parameters are shown and when. In the ragdoll mode you can now +see the ragdoll, but also scale it and see the main parameters. Source rects are now longer shown on the sprite sheet if limbs mode is disabled. - Automatically select edit limbs mode when a new character is created. - Option to create multiple limbs in the character creation wizard. @@ -3145,7 +3360,7 @@ sprite sheet if limbs mode is disabled. - The spritesheet is shown by default. - Fixed deleted joints not being saved properly. - Only lock the axis for torso/head position when alt is down. -- Disable test pose if the character is not a humanoid. +- Disable test pose if the character is not a humanoid. - Clamp camera offset so that the character is always at least partially visible. --------------------------------------------------------------------------------------------------------- @@ -3156,26 +3371,26 @@ Changes: - Made husks more common. Multiplayer fixes: -- Fixed an ID mismatch problem that occasionally caused clients to get kicked out in the multiplayer +- Fixed an ID mismatch problem that occasionally caused clients to get kicked out in the multiplayer campaign, usually with an error message warning about a missing item. - Fixed servers using a local campaign save path sent by the client setupping the campaign, preventing the campaign from starting up if the path does not exist at the server's end. -- Fixed radio communication occasionally not working between characters in the multiplayer campaign. Characters -that had made it through at least one round were not able to communicate through radio with characters that +- Fixed radio communication occasionally not working between characters in the multiplayer campaign. Characters +that had made it through at least one round were not able to communicate through radio with characters that have just been spawned for the first time. -- Kick votes persist during a multiplayer session even if the client disconnects (= disconnecting and rejoining +- Kick votes persist during a multiplayer session even if the client disconnects (= disconnecting and rejoining just before you get kicked doesn't work anymore). - The server sends a "this client previously used the name xxx" message when a client rejoins with a different name. -- Fixed GameServer.UnbanPlayer passing the name to BanList in lower case even though BanList was case +- Fixed GameServer.UnbanPlayer passing the name to BanList in lower case even though BanList was case sensitive (preventing unbanning clients with the "unban" command if their name is not in lower case). -- Fixed server not saving the whitelist when it's enabled/disabled, causing the setting to revert when +- Fixed server not saving the whitelist when it's enabled/disabled, causing the setting to revert when relaunching the server. -- Fixed structure texture scale and texture offset now being reverting to default values when the +- Fixed structure texture scale and texture offset now being reverting to default values when the submarine is saved by the server, causing them to reset between campaign rounds. -- Fixed client always launching the default "DedicatedServer.exe" even if using a content package that +- Fixed client always launching the default "DedicatedServer.exe" even if using a content package that should replace the server exe. -- Servers only report content packages with files that cause multiplayer incompatibility to the master server. -Fixes servers showing up as incompatible in the server list if they have custom sub files (or other types +- Servers only report content packages with files that cause multiplayer incompatibility to the master server. +Fixes servers showing up as incompatible in the server list if they have custom sub files (or other types of files that don't cause compatibility issues) installed. - Fixed crashing when clients attempted to kick/ban players through the in-game info menu when a round was running. @@ -3186,11 +3401,11 @@ Misc fixes: - Replaced WinForms with SDL which should resolve most of the fullscreen/resolution issues. It's also now possible to change the resolution when in fullscreen without having to restart the game. - Fixes to the DXGI crashes during startup. -- Fixed Character.GetConfigFile searching for the character file from all available content packages, not -just the ones that are currently selected. Caused modded character files to affect the game even when +- Fixed Character.GetConfigFile searching for the character file from all available content packages, not +just the ones that are currently selected. Caused modded character files to affect the game even when the mod was not enabled, leading to various issues such as characters failing to equip items if the mod changes the number or type of inventory slots a character has. -- Fixed a bug in monster/item spawnpoint logic that occasionally caused perfectly valid spawnpoints to be +- Fixed a bug in monster/item spawnpoint logic that occasionally caused perfectly valid spawnpoints to be discarded when there were floating ice chunks in the level, sometimes causing monsters to spawn very far from the submarine (which often lead to the player not running into them at all). - Fixed bots not being able to retaliate when attacked with a repair tool. @@ -3198,7 +3413,7 @@ far from the submarine (which often lead to the player not running into them at - Fixed items applying their status effects twice if they're put into a container by swapping them with another item. For example, replacing a fuel rod in the reactor by dropping a new fuel rod on it caused the reactor behave as if it had two rods in it. -- Switches send out a continuous 0/1 signal that can be flipped by interacting with the switch (as opposed +- Switches send out a continuous 0/1 signal that can be flipped by interacting with the switch (as opposed to working like buttons which send out a pulse when interacted with). - Fixed an issue that occasionally caused the main menu to look distorted when launching the game on Mac. - Fixed crash after opening the file browser from the Workshop menu more than once on Linux. @@ -3206,20 +3421,20 @@ to working like buttons which send out a pulse when interacted with). - Fixed item scales not being saved when the scale is modified in the sub editor. - Fixed incorrect physicorium shell description. - Fixed server list & workshop menu not resizing properly when changing resolution. -- Fixed player input being used to determine whether a character should be holding on to ladders, causing +- Fixed player input being used to determine whether a character should be holding on to ladders, causing AI characters to let go of ladders when holding RMB. - Fixed explosion damage bypassing armor (both creature shells and wearable items). - Fixed OverrideSaveFolder and OverrideMultiplayerSaveFolder settings not being saved. - Fixed order/rept icons "twitching" when the sub moves. or -- Fixed players being able to repair submerged electrical items indefinitely. -- Fixed a "attempted to access a removed ragdoll" console error when a character wearing the health +- Fixed players being able to repair submerged electrical items indefinitely. +- Fixed a "attempted to access a removed ragdoll" console error when a character wearing the health scanner HUD is removed. - Fixed console errors when a battery's or supercapacitor's capacity is set to 0 and the interface is open. - Fixed ability to drop items into secure lockers (or other containers that require specific items) without having access to it. - Automatically move the gaps linked to doors when the door is moved (and vice versa). - Fixed items disappearing when they're dropped into a full container. -- Fixed crashing when an item that requires aim to use is used by something else than a character +- Fixed crashing when an item that requires aim to use is used by something else than a character (e.g. a status effect). - Fixed having a broken device on a sub in the sub editor and switching to Character Mode causing a crash. - Fixed the bottom of the background ice texture not rendering correctly on high resolutions. @@ -3250,28 +3465,28 @@ other jobs even when there's 3 or less players on the server). - Increased default respawn transport time to 5 minutes, decreased respawn interval to 3 minutes. - Allow muting players mid-round through the info menu. - Allow to edit the door opening/closing speeds and increase the defaults. -- Reduce the follow-up distance and require the bot to be in the same room than the player before stopping, +- Reduce the follow-up distance and require the bot to be in the same room than the player before stopping, so that bots with a follow order don't stay on the doorways when the player is trying to enter the airlocks. - Stop "breathing" deformations when the character is dead. - Disable obstructed paths when the submarine docks to another submarine/outpost. -- The location of save files can be changed in config_player.xml using the attributes "overridesavefolder" +- The location of save files can be changed in config_player.xml using the attributes "overridesavefolder" and "overridemultiplayersavefolder". Bugfixes: - Fixed crashing during startup due to faulty OpenAL installations. - Fixed inability to select the voice capture device if the name of the device contains non-latin symbols (Cyrillic or Chinese characters for example). -- Fixed a networking issue that occasionally caused clients to get kicked with a "disconnected due to +- Fixed a networking issue that occasionally caused clients to get kicked with a "disconnected due to excessive desync" error message. - Fixed fires being very hard to put out completely in multiplayer. - Fixed items not being repaired after purchasing repairs in the campaign. -- Removed outdated Launch_BarotraumaServer script from the Linux version (does not work anymore, the +- Removed outdated Launch_BarotraumaServer script from the Linux version (does not work anymore, the dedicated server should be launched by running the file called "DedicatedServer"). - Fixed server owners not being able to join their own server if whitelist is enabled and the owner is not on the whitelist. - Fixed inability to ban clients by their Steam ID using the console. - Fixed all servers showing up as "Round has not started" in the server list. -- Fixed server ignoring the max players value set in the "host server" menu and using the setting +- Fixed server ignoring the max players value set in the "host server" menu and using the setting configured in "serversettings.xml" instead. - Fixed spectators not hearing the living players' voice chat. - VOIP improvements (less crackling and pops). @@ -3279,24 +3494,24 @@ configured in "serversettings.xml" instead. an oversized crew. - Fixed occasional console errors when ending a round (causing the round end summary not to appear). - Waypoint fixes in vanilla subs. -- Disallow shooting and attacking when the cursor is over a UI element (to prevent, for example, +- Disallow shooting and attacking when the cursor is over a UI element (to prevent, for example, accidentally firing a gun when dismissing a message box). - Fixed "file not found" errors on Linux when attempting to enable a mod that defines file paths using a backslash instead of a slash. - Fixed monsters ignoring decoys. -- Fixed store menu switching back to the equipment category every time something is bought/sold in +- Fixed store menu switching back to the equipment category every time something is bought/sold in the multiplayer campaign. - Fixed "push to talk" field going outside the audio settings menu on some aspect ratios. - Fixed long server names overflowing in the server list menu. - Fixed the character saying "OrderDialogSelf.dismissed" when a player removes an order from themselves. -- Fixed subinventory slots going outside the screen when for example grabbing someone with a toolbox +- Fixed subinventory slots going outside the screen when for example grabbing someone with a toolbox in the leftmost inventory slot. - Fixed monsters sometimes spawning inside the floating ice chunks within the levels. - Fixed physicorium shells not being containable in railgun shell racks. - Fixed physicorium ammo box not being containable in coilgun ammunition shelves. - Fixed inability to spawn items in characters' inventories with the "spawnitem" console command. - Fixed health interface not focusing to the most damaged limb when closing and reopening the interface. -- Prevent welding doors shut during the tutorials (as the player doesn't have access to any tools to +- Prevent welding doors shut during the tutorials (as the player doesn't have access to any tools to reopen the door). - Fixed AI pathfinding when the path is from a submarine to another submarine. - Fixed crashing when AI crew tries to find a path out of the ruins. @@ -3312,21 +3527,21 @@ v0.9.0.4 connection. - Fixed clients occasionally failing to spawn items when playing using a different language than the server, which caused them to get kicked. -- Fixed extra cargo failing to spawn in multiplayer when playing using a different language than the server. -- Fixed legacy items failing to load if a sub is saved with a language other than English and the language +- Fixed extra cargo failing to spawn in multiplayer when playing using a different language than the server. +- Fixed legacy items failing to load if a sub is saved with a language other than English and the language then changed to something else. - Fixed excessively small password input box when connecting to servers. -- Fixed a bug that occasionally caused items to drop from the inventory when moving items between +- Fixed a bug that occasionally caused items to drop from the inventory when moving items between inventory slots in the multiplayer. - Prevent junction boxes from getting damaged due to overvoltage in the engineering tutorial. -- Fixed structures getting scaled incorrectly when cloning a structure with a non-default scale in the +- Fixed structures getting scaled incorrectly when cloning a structure with a non-default scale in the submarine editor. - Fixed occasional crashes when leaving a multiplayer session while the "cinematic" at the end of the round is still playing. - Fixed items not being moved to the humanhusk's inventory when a huskified player dies (= clothes and other gear seemed to magically disappear when the character "resurrected" as an AI husk). - Fixed clients not seeing turrets rotating at their end when another client is operating the turret. -- Fixed hitscan projectiles (revolver rounds) going through walls if the weapon is fired while its +- Fixed hitscan projectiles (revolver rounds) going through walls if the weapon is fired while its barrel is partially inside the wall. - Fixed welding tools being able to weld doors and burn characters through walls. - Fixed bots reporting leaks when there are holes in the interior walls. @@ -3366,7 +3581,7 @@ v0.9.0.2 - Playing the splash screens or tutorial videos doesn't require libvlc and libvlccore to be installed on the user's system in the Linux version anymore. -Bugfixes: +Bugfixes: - Fixed a bug that caused frequent desync kicks when playing a multiplayer monster mission. - Fixed private servers showing up in the server list. - Fixed an index out of range error in DoctorTutorial if proceeding too fast to the submarine. @@ -3401,9 +3616,9 @@ v0.9.0.1 - Fixed docking interface button not working in the multiplayer. - Fixed medical doctor tutorial crashing on languages other than English. - Fixed a bug that caused AI characters to occasionally get stuck next to stairways. -- Fixed animation and ragdoll file paths getting messed up if there are multiple content packages +- Fixed animation and ragdoll file paths getting messed up if there are multiple content packages installed that include monsters with the same name. -- Fixed crashing when a StatusEffect causes an item to be used on a target. +- Fixed crashing when a StatusEffect causes an item to be used on a target. - Fixed a gap between Remora and the drone. - Added more supplies to Remora. - Fixed watchman's dialogue getting muffled. @@ -3437,20 +3652,20 @@ Bugfixes: - Fixes crashing when attempting to use symbols such as <, > or | in the save file name. - UI layout/scale fixes on resolutions larger than 1080p. - Fixed crashing when loading the same sub twice in the sub editor. -- Fixed submarines occasionally being rendered at an incorrect position when viewing them in the sub editor +- Fixed submarines occasionally being rendered at an incorrect position when viewing them in the sub editor after they've been used in-game. - Improved the syncing of ragdolled characters. - Fixed characters not receiving impact damage when ragdolled. - Fixed characters' arms occasionally spinning around when standing still. - Fixed tutorial level generation parameters being used in some normal campaign levels (leading to extremely small levels and submarines overlapping with the outposts). -- Fixed characters running more slowly when their torso is in a different hull than the feet (for example -in Humpback's bilge). +- Fixed characters running more slowly when their torso is in a different hull than the feet (for example +in Humpback's bilge). - Fixed character's feet getting stuck to platforms when climbing ladders while holding A/D. - Fixed monsters being able to drag characters through walls. - Fixed items "vanishing" if they move directly from sub to another without going outside first. - Fixed content package hash calculation failing if the package is not enabled and contains new monster files. -- Fixed inability to enable content packages if some of the files included in the package are already in +- Fixed inability to enable content packages if some of the files included in the package are already in the game folder (which may happen, for example, if enabling a content package fails). - Fixed AllowRagdollButton settings not being synced with clients, leading to strange ragdolling behavior client-side if the server has disabled ragdolling. @@ -3503,7 +3718,7 @@ from getting attacked by a ball of overlapping crawlers. - Fixed huge lag spikes when a character tries to escape from an enemy but can't find a path away from it. - Fixed file transfer progress bars not being visible in the server lobby. - Fixed crashing when attempting to start a mission round with mission type set to None. -- Fixed ElectricalDischarger electricity effect staying visible if the item breaks or the component +- Fixed ElectricalDischarger electricity effect staying visible if the item breaks or the component is deactivated from outside (e.g. via a StatusEffect or the parent component). - Fixed specular maps being rendered on top of characters when outside the sub. - Fixed excessively bright lights around sonar flora and lava vents. @@ -3511,7 +3726,7 @@ is deactivated from outside (e.g. via a StatusEffect or the parent component). - Fixed inability to scroll through long texts in the sub editor's textboxes. - Fixed clients not being able to see other characters in spectator if they've died far away from the sub. - Fixed non-latin characters not being displayed correctly in Workshop item texts. -- Don't prevent selecting items in the sub editor when the cursor is on a wire node, because it makes it +- Don't prevent selecting items in the sub editor when the cursor is on a wire node, because it makes it very difficult (or impossible) to select small items in the wiring mode. - Fixed crashing when attempting to use the "spawnitem" command when a round is not running. @@ -3561,27 +3776,27 @@ v0.8.9.9 Additions and changes: - New control scheme: items are selected by left clicking, deselected with right click or esc, and held -items are used on devices by pressing E (e.g. when rewiring with a screwdriver or repairing something +items are used on devices by pressing E (e.g. when rewiring with a screwdriver or repairing something with a wrench). The new controls are somewhat experimental; the intention is to make them more intuitive to new players. You can still switch back to the legacy control scheme from the game settings. - Set default radio chat keybind to R and creature attack keybind to Mouse3. -- MODDERS, PLEASE NOTE: Moved crafting recipes from the fabricator xml to the xmls of the items. Makes it -possible for modders to add new craftable items without having to modify the fabricators. +- MODDERS, PLEASE NOTE: Moved crafting recipes from the fabricator xml to the xmls of the items. Makes it +possible for modders to add new craftable items without having to modify the fabricators. - Some menu layout improvements. -- Camera movement is disabled completely when an item interface is open (not just when the cursor is on +- Camera movement is disabled completely when an item interface is open (not just when the cursor is on the interface). - Option to disable the camera pan/zoom effects from the game settings. - Option to set a custom preview image for subs. - Allow aiming on ladders when not moving. - Characters play Entrance of the Gladiators on the guitar when wearing a clown mask. -- Display a warning on the status monitor when docked to an outpost ("Docked to X, undock before attempting +- Display a warning on the status monitor when docked to an outpost ("Docked to X, undock before attempting to maneuver the submarine"). - Improvements to the line of sight effect. Prevents ugly-looking artifacts in spots where two wall pieces meet. -- The server gives the "None" permissions to new clients, allowing server hosts to automatically give +- The server gives the "None" permissions to new clients, allowing server hosts to automatically give specific permissions to all clients. - Increased submarine masses to make it less easy for characters to push them around. -- Ping direction is shown on the sonar display when adjusting the direction slider even if directional ping +- Ping direction is shown on the sonar display when adjusting the direction slider even if directional ping is not enabled. - Tweaked charybdis' AI, attacks and animations. - Nuclear explosions cause radiation sickness. @@ -3594,11 +3809,11 @@ is not enabled. - Added blood particle effects when under high pressure. - Some optimization to reduce loading times. - Added a search bar to fabricators. -- Increased the range of docking port sounds and added a subtle camera shake when locking the ports to make +- Increased the range of docking port sounds and added a subtle camera shake when locking the ports to make it more noticeable when a sub docks. - Made all new medical items fabricable. -- Automatically put the currently equipped item in the inventory (no matter if it's one or two handed) when -picking up items that require two hands. +- Automatically put the currently equipped item in the inventory (no matter if it's one or two handed) when +picking up items that require two hands. - Job preferences can be edited mid-round in the info menu. - Slightly reduced the amount of oxygen characters consume from hulls. - Enemies don't attack outposts or targets inside it anymore. @@ -3608,31 +3823,31 @@ Multiplayer fixes: keep welding, honking a bike horn or whatever else they were doing until the server kills the character. - More reliable throw StatusEffect (= grenade explosion) syncing. Fixes clients not seeing explosions at their end. -- More reliable item wall attaching syncing. -- Servers don't attempt to send position updates for items that have no enabled physics body (e.g. attached +- More reliable item wall attaching syncing. +- Servers don't attempt to send position updates for items that have no enabled physics body (e.g. attached items). Fixes "received a position update for an item with no physics body" console errors when attaching items to walls. - Fixed spectate button staying visible when a round ends while a client is in the lobby. - Fixed remote characters sliding slowly to the left client-side when standing in place. -- Fixed server creating "attempted to create a network event for an item that hasn't been fully initialized +- Fixed server creating "attempted to create a network event for an item that hasn't been fully initialized yet" console errors when spawning LightComponents mid-round. - Fixes monsters flipping around way too often client-side (especially when inside the sub). Bugfixes: -- Fixed wire connections that have been done mid-round not working properly. +- Fixed wire connections that have been done mid-round not working properly. - Fixed crashing when attempting to speak as a monster in single player. - Fixed linked subs not getting docked correctly when loading a saved game. - Fixed turrets not working if they're placed inside the submarine. - Fixed calyxanide not being usable in syringe guns. - Explosive harpoons disappear after exploding. -- Emptying the "required items" field of an item in the sub editor now removes the item requirements (instead +- Emptying the "required items" field of an item in the sub editor now removes the item requirements (instead of using the default ones). - Fixed crashing if a fabricator finishes creating an item after the user has been removed (e.g. eaten). - Fixed crashing if none of the selected content packages contain location portraits suitable for the main menu. - Fixed projectiles not applying status effects on impact if they have no attack defined. - Fixed thorium rods not being usable in the reactor. -- Conditionals return a match when checking status tag inequality and the target has no status tags (e.g. -checking if a character doesn't have a StatusEffect with a "poison" tag returns true even if the character +- Conditionals return a match when checking status tag inequality and the target has no status tags (e.g. +checking if a character doesn't have a StatusEffect with a "poison" tag returns true even if the character has no active StatusEffects). - Fixed severed limbs occasionally noclipping into the submarine. - Fixed large engine emitting smoke before it becomes repairable. @@ -3642,12 +3857,12 @@ v0.8.9.8 --------------------------------------------------------------------------------------------------------- Additions and changes: -- Improved tutorial - better videos, instructional texts, objective list that suggest what you should do +- Improved tutorial - better videos, instructional texts, objective list that suggest what you should do next, option to rewatch the videos and re-read the instructions. - Overhauled charybdis (still a work in progress though). -- Automatically grab adjacent ladders when the top/bottom of the current ladder is reached. Makes moving -through docking ports a little less confusing. -- Option to configure when afflictions become visible with the health scanner by adding +- Automatically grab adjacent ladders when the top/bottom of the current ladder is reached. Makes moving +through docking ports a little less confusing. +- Option to configure when afflictions become visible with the health scanner by adding a "ShowInHealthScannerThreshold" attribute to the affliction. - Added labels next to periscopes in Humpback and Dugong. - Modified Humpback's bilge to make it easier for AI characters to fix. @@ -3660,14 +3875,14 @@ a "ShowInHealthScannerThreshold" attribute to the affliction. the wires without having to disconnect them). - Decreased structure damage done by frag grenades and made them disappear after they've exploded. - Batteries output charge values as integers. -- Made damaged junction boxes less sensitive to overvoltage. Nearly broken junction boxes were barely able -to handle any overvoltage, leading to chain reaction where one junction box breaking causes the grid to be +- Made damaged junction boxes less sensitive to overvoltage. Nearly broken junction boxes were barely able +to handle any overvoltage, leading to chain reaction where one junction box breaking causes the grid to be overloaded, and the rest of the boxes start taking damage at an increasing speed. - Reactors don't cool down when underwater anymore. -- Removed minimum conditions from battery deconstruction output (= deconstructing an empty battery still +- Removed minimum conditions from battery deconstruction output (= deconstructing an empty battery still gives the materials used to craft the battery). - Made a bunch of ItemContainer UI panels larger. -- Items can be dragged and dropped directly from the inventory into containers without having to select +- Items can be dragged and dropped directly from the inventory into containers without having to select the container first. - Plants can be picked up from the environment without any tools. - Added more help texts to highlighted items ("[E] Interact", "[E] Climb"...) @@ -3681,24 +3896,24 @@ excessive amounts of network events. - Fixed clients being unable to start a campaign using a submarine that's not in the default Submarine folder at the server's side. - Fixed loading submarine files and campaign saves occasionally failing when running multiple instances -of the game from the same install location (for example, a dedicated server executable and a client +of the game from the same install location (for example, a dedicated server executable and a client executable). -- Don't transfer files through the network when sending them to the owner of the server (i.e. a client +- Don't transfer files through the network when sending them to the owner of the server (i.e. a client hosting directly from the main executable). - Fixed fires and water occasionally getting out of sync between a client using the fire/water console commands and the server. - Fixed clients disconnecting with an "unknown object header" error if they fail to read a network event (when they should instead report the error to the server and wait for a message that contains a more descriptive error). -- Campaign fix: clear missions from locations that change their type, and all adjacent locations. Not -clearing them caused missions to still be available when they logically shouldn't be (e.g. a transport +- Campaign fix: clear missions from locations that change their type, and all adjacent locations. Not +clearing them caused missions to still be available when they logically shouldn't be (e.g. a transport mission from an uninhabited location to another) and syncing issues in multiplayer. - Disable campaign start button if a round is already running when joining. -- Fixed clients being unable to end campaign rounds at all if the sub isn't at the start/end outpost +- Fixed clients being unable to end campaign rounds at all if the sub isn't at the start/end outpost (regardless if they have the permission to end the round or not). -- Fixed campaign characters still being displayed in the server lobby after the game mode has been +- Fixed campaign characters still being displayed in the server lobby after the game mode has been changed to something else. -- Fixed items in the characters inventory always starting at 100% condition client-side even if they had +- Fixed items in the characters inventory always starting at 100% condition client-side even if they had deteriorated during the previous round. - Fixed LevelResource (mineral, plant, etc) deattach timers not being synced with clients. - AI characters can take out excess fuel rods from the reactor when needed. @@ -3707,8 +3922,8 @@ deteriorated during the previous round. Bugfixes: - Fixed almost all items using default repair duration values (10 seconds with high skills, 100 seconds with low skills) instead of the ones configured in the item XMLs. -- Nuclear shells and nuclear depth charges disappear after they've exploded. -- Fixed "trying to add a dead character to crewmanager" errors when attempting to revive a character +- Nuclear shells and nuclear depth charges disappear after they've exploded. +- Fixed "trying to add a dead character to crewmanager" errors when attempting to revive a character killed by some other affliction than internal damage, bleeding or burns. - Take the position of a sub's docking port into account when determining where to place outposts. Previously the outposts were simply placed midway between the adjacent walls, which occasionally caused @@ -3716,7 +3931,7 @@ problems with submarines whose docking port is close to the bow or tail. - Fixed a bug in relay components that caused a bunch of issues in power grids that utilize relays: Relays would receive the full amount of power from the grid regardless of the load of the devices connected to the power_out connection, causing unnecessary overloads and fires. -- Fixed batteries being able to draw power through relay components that are connected directly to +- Fixed batteries being able to draw power through relay components that are connected directly to a power source, even if the relay isn't on. - Don't allow steering the sub with WASD when a textbox is selected. - Use the SpriteColor of the item when drawing the moving parts of turrets and doors. @@ -3727,16 +3942,16 @@ something inside the sub. - Fixed characters always being created in the default folder in the character editor. - Monsters don't target doors/hatches at the exterior of the sub when inside or inner doors when outside. - Don't display disabled limbs on sonar (i.e. severed limbs that have "faded out"). -- Close the save/load dialogs when leaving the sub editor. Otherwise they'll still be visible when +- Close the save/load dialogs when leaving the sub editor. Otherwise they'll still be visible when re-entering the editor, and saving at that point will overwrite the previously loaded sub with an empty one. -- Removing an item after it's been combined doesn't trigger the OnBroken StatusEffects (e.g. combining two +- Removing an item after it's been combined doesn't trigger the OnBroken StatusEffects (e.g. combining two half-full flash powder jars doesn't cause them to explode). - Fixed welding tools and plasma cutters not hitting targets if the barrel is inside the target (e.g. if trying to weld a completely broken wall with the cutter partially inside the wall). - Fixed very small mineral colliders that made them extremely hard to hit with the plasma cutter. - Fixed items with no sprite crashing the game (now they just cause a console error). -- Don't allow autointeracting with contained items (e.g. picking up an ammunition box from a loader) -if another item is currently selected. Makes it less likely for players to accidentally pick up items +- Don't allow autointeracting with contained items (e.g. picking up an ammunition box from a loader) +if another item is currently selected. Makes it less likely for players to accidentally pick up items from containers when they deselect another item. - Fixed characters not letting go of the character they're grabbing when the health interface is closed by clicking outside the window. @@ -3754,7 +3969,7 @@ Additions and changes: - Clients communicate syncing errors to the server, and the server logs a more descriptive error about what went wrong. Should make it easier to diagnose disconnection issues from now on. - Ending a multiplayer campaign round by talking to watchman doesn't require any special permissions. -- Server automatically ends rounds if there have been no players alive in 60 seconds and respawning +- Server automatically ends rounds if there have been no players alive in 60 seconds and respawning is not allowed during the round. - Added a button for resetting an entity's properties to the default values to the sub editor. - Updated handheld sonar UI graphics. @@ -3764,11 +3979,11 @@ Bugfixes: - Fixed a networking bug that caused the server to send item state changes to the clients before sending a message about the item being spawned. For example, spawning any item with a LightComponent would always cause clients to get disconnected. -- Changes to the way the clients are put in sync with the server when joining mid-round. Should make it +- Changes to the way the clients are put in sync with the server when joining mid-round. Should make it less likely for clients to get disconnected immediately after starting a round. -- StatusEffects only apply non-limb-specific afflictions to one limb even if targeting the whole character. -Fixes drugs like fentanyl and morphine being way too harmful due to the oxygen loss affliction being -applied once per every limb. +- StatusEffects only apply non-limb-specific afflictions to one limb even if targeting the whole character. +Fixes drugs like fentanyl and morphine being way too harmful due to the oxygen loss affliction being +applied once per every limb. - Fixed TargetItemComponentName not working in StatusEffect conditionals (making it impossible to create conditionals that target a specific component of an item). - Made all of the new medical items combinable and usable in a syringe gun (assuming the drug is in a syringe). @@ -3780,12 +3995,12 @@ conditionals that target a specific component of an item). - Fixed flares not activating by left clicking. - Fixed affliction icons flickering rapidly in the health interface and above the health bar if their strength is fluctuating around the threshold where the icon becomes visible. -- Fixed dedicated server crashing when typing in more text than can fit on one line. -- Fixed enemies "fleeing" after they have been shot. There was a steering issue when they targeted characters +- Fixed dedicated server crashing when typing in more text than can fit on one line. +- Fixed enemies "fleeing" after they have been shot. There was a steering issue when they targeted characters inside the sub while being outside. - Fixed Hammerhead attack causing warping. - Fixed incorrect submarine and level seed in server logs when playing campaign mode. -- Hide the start button from the campaign UI if the client doesn't have the permission to manage +- Hide the start button from the campaign UI if the client doesn't have the permission to manage the campaign or rounds. --------------------------------------------------------------------------------------------------------- @@ -3805,8 +4020,8 @@ themselves alive and less likely to get stuck. - New signal items (divide, multiply, subtract, memory, equals, greater than, color, xor). - Option to adjust microphone volume in multiplayer. - Added a console commands for changing the gender and race of the character. -- More intuitive BrokenSprite condition logic: a BrokenSprite with a MaxCondition of 50 will start -fading in at 50 (and be fully visible when the condition drops to 0 or down to the MaxCondition of +- More intuitive BrokenSprite condition logic: a BrokenSprite with a MaxCondition of 50 will start +fading in at 50 (and be fully visible when the condition drops to 0 or down to the MaxCondition of the next BrokenSprite). - Added Mirror X/Y buttons to editing HUDs and tooltips that tell about the keyboard shortcuts. @@ -3821,15 +4036,15 @@ knowing that you'd dropped it. - Fixed "play yourself" always toggling to true when a round ends. - Fixed missing item names in the extra cargo menu. - Fixed traitor rounds failing to start if the server is not hosted by a client. -- Fixed console command aliases not being taken into account in GameClient.HasConsoleCommandPermission -(meaning that the client needed a permission for each name variant of a command, making it impossible +- Fixed console command aliases not being taken into account in GameClient.HasConsoleCommandPermission +(meaning that the client needed a permission for each name variant of a command, making it impossible to for example use "fixwalls" instead of "fixhulls"). -- Made the "control" console command usable to clients. -- Show the "ready to start" tickbox in the server lobby even if the client has the permission to start +- Made the "control" console command usable to clients. +- Show the "ready to start" tickbox in the server lobby even if the client has the permission to start the round. - Fixed server lobby screen not showing the names of the submarines the client doesn't have. - Fixed inability to select the respawn shuttle as a client host. -- Fixed VoipCapture creating new "could not start voice capture" popups constantly if there's no +- Fixed VoipCapture creating new "could not start voice capture" popups constantly if there's no suitable capture device. - Fixed crashing when starting a round if a submarine name contains underscores. - Fixed clients console errors when attempting to modify the properties of an ItemComponent in-game @@ -3840,29 +4055,29 @@ suitable capture device. Misc bugfixes: - Audio fixes (less snap, crackle and pop). - Fixed particle "jitter" when the submarine was moving fast. -- Fixed damage modifiers affecting all afflictions if they use affliction types instead of affliction -identifiers. +- Fixed damage modifiers affecting all afflictions if they use affliction types instead of affliction +identifiers. - Fixed end round vote text going outside the screen if there's a 2-digit amount of votes. - Fixed StatusEffects only applying afflictions to one limb even if the target is "Character" instead of "Limb". - Disable audio instead of crashing if no audio device is found. - Fixed item interfaces getting repositioned every frame when the editing HUD is open. -- Fixed held items clipping with the sleeves of the character (e.g. when holding a revolver while an +- Fixed held items clipping with the sleeves of the character (e.g. when holding a revolver while an uniform is equipped). - Fixed being able to levitate by spamming the ragdoll button. - Fixed dead characters draining oxygen tanks inside diving suits/masks. - Fixed reactor gauges getting messed up if the optimal fission rate is more than 100% (which may happen if the power consumption is larger than what the reactor can generate). - Fixed mud raptors not having an inventory (nor lootable items). -- Fixed inability to interact with any items when aim assist is set to 0%. -- Fixed info panel flickering out and Tab getting "inverted" (= info panel shown when tab is not being held) +- Fixed inability to interact with any items when aim assist is set to 0%. +- Fixed info panel flickering out and Tab getting "inverted" (= info panel shown when tab is not being held) when selecting crew members in the panel. - Fixed characters arms occasionally getting stuck above their shoulders. -- Fixed wire nodes occasionally being created at the wrong end of a wire (e.g. when moving a wire between -connections in a connection panel, the wire stretched from the device at the other end of the wire to +- Fixed wire nodes occasionally being created at the wrong end of a wire (e.g. when moving a wire between +connections in a connection panel, the wire stretched from the device at the other end of the wire to the device that's being rewired). Misc: -- Changed the way arguments are given to the "setclientcharacter" command (no semicolon to separate the +- Changed the way arguments are given to the "setclientcharacter" command (no semicolon to separate the names, quotation marks have to be used for multi-word names just like with any other command). - Show the amount of credits in the crew tab of the campaign menu. - Don't spawn new monsters if docked to the start outpost or within 50 meters of the start/end of the level. @@ -3880,7 +4095,7 @@ Bugfixes: used to fetch the texts from the language files instead of the actual texts). - Fixed AI orders that target a specific item (such as the order to power up the reactor) not working in multiplayer. -- Fixed crashes when attempting to use voice capture or change voice capture settings when there are no +- Fixed crashes when attempting to use voice capture or change voice capture settings when there are no suitable capture devices available. - Fixed clients not being notified when an AI character shuts down the reactor. - Fixed deconstructors staying active without power in multiplayer. @@ -3908,13 +4123,13 @@ two separate server applications. - Option to randomize your job preferences in the server lobby. - Fixed a server timing issue that occasionally caused the server to kick clients due to desync when a round starts. - Fixed occasional server-side "maximum packet size exceeded" errors. -- Require the players to either dock with the ending outpost or to get the sub close and enter the outpost before +- Require the players to either dock with the ending outpost or to get the sub close and enter the outpost before automatically ending the round. Bugfixes: - Fixed crashing if the round ends while the health window is open. -- Fixed incorrect item panel positioning in the crew command interface when the sub is docked to something. -- Fixed crashing when an incompatible content package is selected in config.xml or if the content package +- Fixed incorrect item panel positioning in the crew command interface when the sub is docked to something. +- Fixed crashing when an incompatible content package is selected in config.xml or if the content package cannot be found. - Fixed screen distortion effects on Linux. - Fixed non-character key input on Linux (arrow keys, tab, etc). @@ -3928,21 +4143,21 @@ slots in uniforms. - Fixed AI not reloading coilguns if an empty box of ammunition is inserted in the loader. - Fixed incorrect deusizine scale. - Fixed turret light toggle not doing anything. -- Fixed character skills that aren't defined in the job xml never increasing, resulting in all jobs except +- Fixed character skills that aren't defined in the job xml never increasing, resulting in all jobs except the captain always having a helm skill of 0. - Fixed flashlight & scooter light cones being "clipped". - Fixed StatusEffects bypassing limb damage modifiers. - Fixed waypoints not getting connected between docking ports on some subs. - Fixed target identifiers being bypassed when a StatusEffect is set to target nearby items or characters. -- Fixed the "insufficient skills to use the item" text popping up if a character doesn't have sufficient -skills to operate one of the item's components, even if the component was not interacted with (e.g. captains -got a warning about not being able to use the connection panel of a nav terminal, even if they didn't select +- Fixed the "insufficient skills to use the item" text popping up if a character doesn't have sufficient +skills to operate one of the item's components, even if the component was not interacted with (e.g. captains +got a warning about not being able to use the connection panel of a nav terminal, even if they didn't select the connection panel). Steam Workshop: - Update installed workshop items automatically on startup. - Allow adding submarines to workshop items with the "add file" dialog. -- If creating an update for a workshop item that's currently installed, use the installed version instead +- If creating an update for a workshop item that's currently installed, use the installed version instead of the one downloaded from the workshop. Additions: @@ -3954,20 +4169,20 @@ Additions: - Display linked hulls as one room on the status monitor. - Tons of new sound effects. - Display the controlled character in the crew interface. -- Option to "give orders" to the character you're controlling. In single player it can be useful if you want -the controlled character to keep doing something when switching to another one, in the multiplayer it can be +- Option to "give orders" to the character you're controlling. In single player it can be useful if you want +the controlled character to keep doing something when switching to another one, in the multiplayer it can be used to let others know what you're doing. - Added a weak spot to Moloch's bladder. -- Baby Moloch, doo doo doo doo doo doo +- Baby Moloch, doo doo doo doo doo doo - Added damage particles to Mud Raptors and Molochs. -- Added "minimum velocity" property to to motion sensors. Allows making sensors that, for example, keep a door open +- Added "minimum velocity" property to to motion sensors. Allows making sensors that, for example, keep a door open when a character is standing in the doorway. - Option to choose whether to use AND/OR logic in StatusEffects with multiple conditionals. Defaults to AND. -- Added a 1 second "cooldown" to water detector state switches to prevent alarms from toggling on and off constantly +- Added a 1 second "cooldown" to water detector state switches to prevent alarms from toggling on and off constantly when the water level is fluctuating around the position of the detector. - Added scram option (reactor shutdown) to the nav consoles in the vanilla subs. - Support for binding Mouse4, Mouse5 and MouseWheel. -- Made Hammerhead and Mudraptor attracted to light. +- Made Hammerhead and Mudraptor attracted to light. - New husk sprite (still WIP). Misc: @@ -3977,17 +4192,17 @@ repaired. - Miscellaneous optimization. - Removed the info button from the top-left corner - the info menu is now opened with TAB. - Changed default chat/radio keybinds to T and Y. -- Welding tools repair all the walls within the range of the raycast, not just the first wall the raycast hits. +- Welding tools repair all the walls within the range of the raycast, not just the first wall the raycast hits. Makes it easier to repair overlapping and multi-layered walls. - Decreased the range of passive sonar - previously there was often no reason to use the active sonar because the passive mode showed the area around the sub so clearly. -- Health scanner shows all active afflictions (not just those that are visible in the health interface). +- Health scanner shows all active afflictions (not just those that are visible in the health interface). Allows detecting afflictions at an earlier stage, making the item much more useful. - Nerfed the structure damage done by Molochs and Crawlers. - Reduced creature HP across the board. - Increased the amount of minerals in levels. - Increased flare burn time, making them more useful as path markers during exploration of ruins. -- RepairTool damage is configured using StatusEffects and Afflictions instead of the "limbfixamount" attribute +- RepairTool damage is configured using StatusEffects and Afflictions instead of the "limbfixamount" attribute that always does burn damage. - Made headsets craftable. - Battery output doesn't start dropping until the charge is below 10%. @@ -4016,17 +4231,17 @@ mission configuration despite the item being removed. - Made coilgun ammunition boxes craftable and purchaseable, coilgun bolts cannot be purchased anymore. - Fixed AI-controlled husk not spawning when a huskified player dies. - Fixed AI crew occasionally going outside to fix leaks. -- Fixed server failing to sync clients who join the server after a character has been removed during +- Fixed server failing to sync clients who join the server after a character has been removed during the round (e.g. eaten, turned into a husk). - Fixed server-side console errors when clients attempt to use a fabricator. - Display Steam authentication errors in the server logs. - Fixed status effects with a ReduceAffliction value of 0 freezing the game. - Fixed sliders not moving in the battery/supercapacitor interface when an AI character is operating it. -- Fixed chatbox being deselected in the net lobby when receiving a lobby update from the server (i.e. +- Fixed chatbox being deselected in the net lobby when receiving a lobby update from the server (i.e. whenever the server host changes any setting). - Fixed OnBroken status effects firing in the submarine editor when an item's condition is set to zero (for example, reactors exploding and breaking all the nearby walls). -- Fixed file number being added to the file extension of debug console log files ("file123.txt (2)" +- Fixed file number being added to the file extension of debug console log files ("file123.txt (2)" instead of "file123 (2).txt"). - Fixed battery positioning in charging docks. - Fixed crashing when ending a single player round while a character is outside the sub. @@ -4034,21 +4249,21 @@ instead of "file123 (2).txt"). - Fixed fire sounds persisting in menus. - Fixed the layout of the extra cargo menu in server settings. - Fixed depth charges disappearing from loaders when interacting them with both hand slots full. -- Fixed StatusEffects not being able to target item components. Caused doors to be impossible to weld -and most likely other issues with item StatusEffects as well. +- Fixed StatusEffects not being able to target item components. Caused doors to be impossible to weld +and most likely other issues with item StatusEffects as well. - Artifacts spawn in artifact holders again. - Fixes to "attempted to move pulljoint extremely far" errors which occasionally caused severe problems in syncing characters' positions. - Fixed a bug that occasionally caused monsters to spawn very close to the submarine in monster missions. -- Fixed servers occasionally starting the round multiple times when automatically starting the game via -autorestart or clients being ready. +- Fixed servers occasionally starting the round multiple times when automatically starting the game via +autorestart or clients being ready. - Fixed up-to-date content packages being reported as incompatible in the Steam workshop menu. - Changed the default radio chat hotkey to T. - Fixed the line of sight effect not working on ruins when looking at them from inside a sub. - Fixed fabricator allowing new items to be created when the output is not empty, resulting in wasted materials. - Fixed servers reporting incorrect player counts in the server list. - Fixed order messages not being visible in single player if the character issuing the order has no headset. -- Fixed riot shields retaining their pushing ability even when the user is stunned or unconscious. +- Fixed riot shields retaining their pushing ability even when the user is stunned or unconscious. - Fixed rubber ducks not floating like a good duck should. - Prevent locations from being generated too close to each other in the campaign map. - Fixed battery and supercapacitor charges not staying in sync between the server and clients. @@ -4056,7 +4271,7 @@ autorestart or clients being ready. - Fixed non-downloaded workshop items showing zero as the file size. - Fixed spectate button staying disabled if starting a round fails (due to a missing sub file for example). - Fixed crashing when teleporting characters from a submarine to ruins in multiplayer. -- Fixed automatic temperature control setting turbine output above 100 if the power consumption is higher +- Fixed automatic temperature control setting turbine output above 100 if the power consumption is higher than what the reactor can generate. Caused "failed to write an event for the entity" errors in multiplayer. - Fixed AI characters attempting to treat dead characters. @@ -4078,7 +4293,7 @@ with fires inside them. - Fixed the husk appendage not appearing on huskified humans. - Fixed order/report messages being flagged as spam way too easily, causing frequent spam kicks. - Fixed sliders buttons being invisible while pressed in device interfaces. -- Fixed an item being spawned in the submarine editor when selecting an item from the menu while another +- Fixed an item being spawned in the submarine editor when selecting an item from the menu while another one is already selected. - Fixed submarine colliders not taking into account the body offsets of the wall structures, causing some items outside the submarine's walls to be impossible to interact with (the most noticeable being the @@ -4094,39 +4309,39 @@ button that opens Orca's airlock from the outside). v0.8.9.1 (closed alpha) --------------------------------------------------------------------------------------------------------- -Too many changes to list here. :) We've been working on this update for a year now, with a team of about +Too many changes to list here. :) We've been working on this update for a year now, with a team of about a dozen people (as opposed to a couple of devs working on it on their free time). -Almost every aspect of the game has been improved to some extent - some polished a bit, some went through +Almost every aspect of the game has been improved to some extent - some polished a bit, some went through a more major overhaul and many things are completely new. This is by no means a complete list, but here's some of the new things: - A full graphics overhaul: almost all of the sprites has been polished or completely remade. - Improved random event system that tries to keep the overall difficulty of the game at certain level, -delaying additional monster spawns if there's already lots of things going on, or spawning more when +delaying additional monster spawns if there's already lots of things going on, or spawning more when there's a more quiet moment. - Improved difficulty system: now the difficulty level has a much more noticeable effect on the gameplay. - General difficulty balancing all across the board: we've tried to make the difficulty curve more approachable to new players while still keeping things challenging for more experienced players on higher difficulty levels. - More varied levels, environmental hazards. -- A new more detailed health system with things such as limb-specific injuries, addictions, overdoses, +- A new more detailed health system with things such as limb-specific injuries, addictions, overdoses, mental issues... The system is also highly moddable, and makes it much easier to implement things such as hunger mechanics, more varied poisons or stat-boosting items. - Completely redesigned in-game HUD (the inventory, crew command interface, chat, etc). - Redesigned crafting system. - Minerals scattered across the level (can be used for crafting). -- A command/report system that can be used to communicate with your crew more effectively (in both single +- A command/report system that can be used to communicate with your crew more effectively (in both single player and multiplayer). - Tons of additions to alien ruins (traps, puzzles, non-flooded rooms). - Improved AI (both the crew AI and the enemy AIs). -- NPC dialog (including random chatter and context-specific lines that make it easier to keep track of +- NPC dialog (including random chatter and context-specific lines that make it easier to keep track of what the crew is doing). - Most of the device interfaces have been redesigned to make them easier to use (and nicer to look at!). - Many additions to the campaign mode (still a work in progress though). -- Overhauled the skill system: now every character can generally do anything (repair devices, fabricate +- Overhauled the skill system: now every character can generally do anything (repair devices, fabricate new items, apply medical treatments), but characters with higher skill levels will do things more efficiently. -- Skill progression in the campaign mode: characters' skills gradually increase, making them more valuable with +- Skill progression in the campaign mode: characters' skills gradually increase, making them more valuable with each completed round. - New music composed specifically for the game. - Overhauled audio. @@ -4149,9 +4364,9 @@ v0.8.2.3 - Fixed a bunch of bugs that were causing "attempted to apply an invalid force/impulse to a physics body" errors. - Fixed a bunch of bugs that were causing "attempted to move a pulljoint extremely far" errors. -- Fixed DebugConsole selecting non-command lines if up/down is pressed when there are no commands in the -console. -- Fixed inventory syncing not working on the controlled character's inventory if the character is +- Fixed DebugConsole selecting non-command lines if up/down is pressed when there are no commands in the +console. +- Fixed inventory syncing not working on the controlled character's inventory if the character is unconscious or wearing handcuffs. - Verify that the launched exe belongs to the currently selected content package when starting up the game. - Fixed console messages that have been created before initializing the debug console not being present @@ -4170,18 +4385,18 @@ causing the camera not to follow the character and preventing the player from gi - Fixed a few ragdoll animation bugs that caused "attempted to move pulljoint anchor extremely far" errors. - Fixed AI characters (most often mantises) being able to attack through walls. - Fixed alien ruins occasionally overlapping with each other or being above the upper boundary of the level. -- Docking ports automatically stretch the hulls between them to cover the area between the docked subs. -Otherwise there may be areas uncovered by hulls if the docking port is positioned slightly outside the -extents of the submarine's hulls, causing characters to implode or get thrown back when they try to pass +- Docking ports automatically stretch the hulls between them to cover the area between the docked subs. +Otherwise there may be areas uncovered by hulls if the docking port is positioned slightly outside the +extents of the submarine's hulls, causing characters to implode or get thrown back when they try to pass from sub to another. -- Fixed client-side docking ports creating duplicate bodies on doors, causing characters to collide with +- Fixed client-side docking ports creating duplicate bodies on doors, causing characters to collide with an invisible door when trying to move between docked subs (until the server forces them through it). -- Fixed characters occasionally getting teleported outside the sub for a few frames when moving between +- Fixed characters occasionally getting teleported outside the sub for a few frames when moving between docked subs. - Fixed status effects that deplete oxygen affecting characters that don't need air to breathe. - More error logging to diagnose syncing errors. - Melee weapons can only hit one character per swing (makes stun batons and medical syringes less OP). -- Option to make RegEx component only send a signal when it receives a signal (not continuously according +- Option to make RegEx component only send a signal when it receives a signal (not continuously according to the last received signal). - Added FalseOutput property to RegEx components. @@ -4194,10 +4409,10 @@ v0.8.2.1 another. - Fixed dragged characters staying floating mid-air after they've been dragged up/down ladders. - Attempt to fix items occasionally dropping client-side when moving them from an inventory to another. -- Fixed incorrect rotation of welding tools and other 2-handed items that are held in one hand when not +- Fixed incorrect rotation of welding tools and other 2-handed items that are held in one hand when not aiming. The items were rotated according to the left hand, but positioned on the right hand. - Spaces and exclamation marks are allowed in client names by default. -- Hitscan weapons like the revolver can hit targets outside the sub when firing from the inside and +- Hitscan weapons like the revolver can hit targets outside the sub when firing from the inside and vice versa. - Fixed crashing when attempting to clone linked submarines in the sub editor. - Fixed crashing when attempting to clone items with non-default required items. @@ -4211,56 +4426,56 @@ v0.8.2.0 --------------------------------------------------------------------------------------------------------- Networking additions: - - Added a server setting for selecting which symbols are allowed in client names (see + - Added a server setting for selecting which symbols are allowed in client names (see AllowedClientNameChars in the server settings file). - Custom servers can modify all editable item properties mid-round, not just in-game editable ones. - Clients can be given access to server logs. - Respawn durations can be changed mid-round. - Servers have the option to disable the disguise feature. - Increased midround syncing timeout. - + Misc changes: - Levels are mirrored when travelling through the backwards in the campaign mode. - Added colliders to railguns (so they cannot go through walls or enemy subs anymore). - - Melee weapons can hit multiple targets on one swing. Fixes weapons occasionally not hitting + - Melee weapons can hit multiple targets on one swing. Fixes weapons occasionally not hitting the target in tight spaces due to touching the ceiling/walls first. - - The voltage required for a PowerTransfer item to take damage and the probability for a fire can be + - The voltage required for a PowerTransfer item to take damage and the probability for a fire can be configured in the item xmls. - Docking ports and hatches aren't damaged by excess voltage. - Added more color variants of wires. - Characters point the harpoon gun down when not aiming. - Added parameter autocompletion to the kill command. - - Added a property that can be used to lock connection panels but still keep the panel rewireable + - Added a property that can be used to lock connection panels but still keep the panel rewireable in the submarine editor. - Items outside the sub cannot be deattached from walls. - - Fabricators show the list of required items even if the character does not have the skills to craft + - Fabricators show the list of required items even if the character does not have the skills to craft the item. Networking bugfixes: - - Fixed file transfers failing if the client disconnects during an active transfer, rejoins and + - Fixed file transfers failing if the client disconnects during an active transfer, rejoins and attempts to receive the same file. - - Fixed a bug in door syncing that caused the door states to differ between the server and clients + - Fixed a bug in door syncing that caused the door states to differ between the server and clients in some subs with more complex door wiring setups. - - Fixed clients being able to spam kick votes (duplicate votes were not counted but caused unnecessary + - Fixed clients being able to spam kick votes (duplicate votes were not counted but caused unnecessary chat messages to be sent). - - Fixed item conditions occasionally not matching exactly between the server and clients, causing - issues such as not clients not being able to fabricate items due to the condition being slightly + - Fixed item conditions occasionally not matching exactly between the server and clients, causing + issues such as not clients not being able to fabricate items due to the condition being slightly below the minimum condition at their end. Bugfixes: - Added a workaround to a MonoGame bug that makes the screen turn white when alt-tabbing out of fullscreen. - Fixed docking ports flooding for no reason in some custom subs. - Fixed LightComponents staying active on broken items. - - Fixed railguns and depth charge tubes being directly usable by characters (= they could be launched + - Fixed railguns and depth charge tubes being directly usable by characters (= they could be launched simply by selecting them and left clicking, without the need to use a railgun controller). - Fixed items salvaged from ruins not being saved in the campaign mode. - - Fixed LOS effect being brighter than the ambient light in some of the darker levels, causing + - Fixed LOS effect being brighter than the ambient light in some of the darker levels, causing the player to see obstructed areas better than unobstructed ones. - Fixed severed limbs staying disabled when a dismembered character is revived using console commands. - Fixed characters holding non-aimable two-handed items such as railgun shells in one hand when aiming. - Fixed stereo sounds not being loaded correctly. - Fixed modified sprite colors not working correctly on worn items. - - Fixed modified maximum recharge speeds of PowerContainers resetting to the default value after + - Fixed modified maximum recharge speeds of PowerContainers resetting to the default value after saving and reloading. - Fixed handcuffed players being able to perform CPR and grab/drag bodies. - Fixed diving suit's damage modifiers being bypassed if the character gets hit in the waist. @@ -4271,13 +4486,13 @@ v0.8.1.12 --------------------------------------------------------------------------------------------------------- - Fixed connectionpanel syncing (wires dropping client-side during rewiring). -- Characters stay alive for 30 seconds after a client disconnects, and if the client rejoins during that -time they regain control of the character. +- Characters stay alive for 30 seconds after a client disconnects, and if the client rejoins during that +time they regain control of the character. - Changing the sprite color of an item also affects the color when the item is being worn. - Cloned items keep the value of the "required items" field of the original item. - Fixed crashes caused by gaps that are not connected to anything. - Removed the unused and non-functional monitor item. -- Made medical and toxic cabinets waterproof (= potassium and other water-sensitive items inside them +- Made medical and toxic cabinets waterproof (= potassium and other water-sensitive items inside them are not affected by water). - Fixed docking ports causing flooding in some custom subs. - Fixed medical items with an immediate effect (such as calyxanide) not working when a player uses them @@ -4298,22 +4513,22 @@ Networking fixes: clients to get kicked due to desync. - Fixed client-side error messages when respawning without a respawn shuttle. - Fixed some issues in inventory and connection panel syncing when joining mid-round. - - Fixed fabricated items always appearing to be in full condition client-side (e.g. oxygen tanks which + - Fixed fabricated items always appearing to be in full condition client-side (e.g. oxygen tanks which should be empty after being fabricated). - - Fixed attachable items dropping on the ground client-side when deattaching them (but still staying + - Fixed attachable items dropping on the ground client-side when deattaching them (but still staying in the inventory of the character detaching them). - Fixed items occasionally dropping instead of being moved to another inventory client-side. - The "traitorlist" command is usable by clients who have the permission to use it. Misc bugfixes: - - Fixed characters occasionally going inside/through obstacle when leaving a submarine that's right + - Fixed characters occasionally going inside/through obstacle when leaving a submarine that's right next to another submarine or a level wall. - More physics error checks and logging. - Railgun controllers can be used over wifi components. - Characters attach items to the walls at the position of their hand, not at the center of the body. - Wifi components can't communicate with the enemy sub in combat missions. - - Fixed the previously selected location staying selected but start button staying disabled when - returning to the lobby screen in the single player campaign. Made it impossible to progress without + - Fixed the previously selected location staying selected but start button staying disabled when + returning to the lobby screen in the single player campaign. Made it impossible to progress without restarting if there were no other selectable locations. - Fixed holdable components reverting their RequiredItems back to the prefab values during loading. - Fixed wall-attached sections of a wire not rendering when the item is being rewired outside the sub. @@ -4323,11 +4538,11 @@ Misc bugfixes: v0.8.1.10 --------------------------------------------------------------------------------------------------------- -- Fixed bugs in wall hole creation logic and docking port syncing which caused entity ID mismatches and +- Fixed bugs in wall hole creation logic and docking port syncing which caused entity ID mismatches and "unknown object header" errors. - Fixed errors when attempting to buy too many items of a given type to fit in one container. - Fixed crashing when attempting to buy items that don't spawn in a container. -- Fixed crashing when attempting to generate hulls with the "autohull" command when there are no walls +- Fixed crashing when attempting to generate hulls with the "autohull" command when there are no walls or doors in the sub. - Fixed docking ports creating duplicate hulls and gaps during loading. - Fixed missions resetting to the initial ones when loading a campaign. @@ -4340,14 +4555,14 @@ v0.8.1.9 --------------------------------------------------------------------------------------------------------- - Fixed a bug in docking port syncing that caused entity ID mismatches and "unknown object header" errors. -- Fixed a bug that occasionally caused entity ID mismatches and "unknown object header" errors when +- Fixed a bug that occasionally caused entity ID mismatches and "unknown object header" errors when a respawn shuttle got damaged before returning back to the starting location. - Fixed error messages when attempting to use the console commands "\" or "\n". - Fixed clients not syncing the position of their controlled character with the server when dead/unconscious. - Fixed swimming ragdolls "dropping down" if the server freezes them due to a connection error. - Fixed excessive "attempted to apply invalid velocity to a physics body" console errors. - Fixed crashing when selecting a level seed that has no background portrait defined. -- Fixed crashing if the player clicks yes on the "download sub from the server" prompt after +- Fixed crashing if the player clicks yes on the "download sub from the server" prompt after returning to the main menu. --------------------------------------------------------------------------------------------------------- @@ -4384,7 +4599,7 @@ them (despite the server allowing voting if the client has had a character earli use the position of the client's cursor. - Fixed crashing if a wire is used by a statuseffect (for example if a detonator tries to trigger a wire contained inside it). -- Fixed GameAnalytics being stopped if the dedicated server is restarted with the "restart" console command.- +- Fixed GameAnalytics being stopped if the dedicated server is restarted with the "restart" console command.- - Fixed wiring items outside the submarine. - Fixed chatbox discarding the second chat message instead of the first one when the maximum number of chat messages is reached. @@ -4405,7 +4620,7 @@ select whether you want to send the information or not. - Devices outside the submarine can be rewired in-game (not just in the sub editor). - Fixed a crash caused by vision obstruction logic. - Fixed clients being unable to give non-permanent or range bans. -- Clients are allowed to vote to end the round if they have spawned at some point during the round, +- Clients are allowed to vote to end the round if they have spawned at some point during the round, even if the character they controlled doesn't exist anymore. - Dedicated servers can give clients the permission to use console commands that aren't available in for dedicated server (e.g. los, lights, control) @@ -4414,7 +4629,7 @@ for dedicated server (e.g. los, lights, control) the command instead of the host's character. - Spawnitem can be used to spawn items in the inventory of a specific character. - Fixed explosions with an EMP value only damaging reactors (when they should only ignore reactors). -- Fire can only explode oxygen tanks that are >25% full (otherwise the condition of the tank just drops +- Fire can only explode oxygen tanks that are >25% full (otherwise the condition of the tank just drops to 0). Prevents infinite explosions when an oxygen generator is on fire with oxygen tanks inside. - Fixed projectiles with a damage range of 0 not applying their structuredamage value to structures. - Items with a physics body can be used as pumps, so now it's possible to make portable items that remove @@ -4434,12 +4649,12 @@ v0.8.1.3 --------------------------------------------------------------------------------------------------------- - Fixed server-side crashes during job assignment if a client hasn't sent any job preferences. -- Fixed crashing if the selected respawn shuttle doesn't have a navigation terminal or any other item +- Fixed crashing if the selected respawn shuttle doesn't have a navigation terminal or any other item with a Steering component. -- Fixed InWater status effects triggering when an item is fabricated, causing issues such as +- Fixed InWater status effects triggering when an item is fabricated, causing issues such as water-sensitive items to breaking/exploding immediately after being fabricated. - Fixed motion sensors sending out signals even if the output is set to nothing. -- Fixed crashing when a round starts if the sub has been saved while a fabricator was running. +- Fixed crashing when a round starts if the sub has been saved while a fabricator was running. - Fixed explosives not detonating inside railgun shells. - Fixed characters spawning inside the respawn shuttle if no suitable spawnpoint is found inside the main submarine. @@ -4463,7 +4678,7 @@ v0.8.1.2 v0.8.1.1 --------------------------------------------------------------------------------------------------------- -- Fixed explosives going off when a character holds them in their hand and left clicks, causing a crash +- Fixed explosives going off when a character holds them in their hand and left clicks, causing a crash if done in the submarine editor. --------------------------------------------------------------------------------------------------------- @@ -4472,17 +4687,17 @@ v0.8.1.0 Items: - Added searchlights. - - Explosives, chemicals and medical items disappear when their condition falls to 0 + - Explosives, chemicals and medical items disappear when their condition falls to 0 (i.e. when they're fully used). - Railguns cannot be fired when not being aimed. - - Removed the need for batteries in diving suits. The light stays on as long as the suit is worn + - Removed the need for batteries in diving suits. The light stays on as long as the suit is worn by a living character. - Junction boxes only take damage underwater when they're powered up. Bugfixes: - - Fixed a bug that occasionally caused some characters to not be removed at the end of a round, causing - various bugs and crashes on successive rounds (the most common ones being server-side crashes and - constant "attempted to access a potentially removed ragdoll" console errors). + - Fixed a bug that occasionally caused some characters to not be removed at the end of a round, causing + various bugs and crashes on successive rounds (the most common ones being server-side crashes and + constant "attempted to access a potentially removed ragdoll" console errors). - Fixed camera shake continuing indefinitely if a character falls unconscious due to impact damage. - Fixed item removal by right clicking not being synced with clients. - Fixed being able to gain karma by welding fixed walls. @@ -4494,14 +4709,14 @@ Bugfixes: - Fixed the EndRound music clip occasionally looping forever after a round ends. - Fixed player-controlled creatures being able to damage themselves. - Fixed repair tools causing damage to the user regardless of the character's skills. - - Attempt to fix characters occasionally getting launched out of the sub at lightspeed when the sub + - Attempt to fix characters occasionally getting launched out of the sub at lightspeed when the sub crashes into something. - Fixed StatusEffects not working on child ItemComponents. - Wearables apply OnWearing StatusEffects in all the components of an item, not just the Wearable component. - Fixed Equals/NotEquals conditional comparisons. Misc additions: - - Added console commands for giving the clients ranks, showing their current permissions and + - Added console commands for giving the clients ranks, showing their current permissions and giving/revoking the permission to use specific console commands. - Option to set an automatic ban duration for vote kicked players. - Option to log debug console output into a text file. @@ -4524,7 +4739,7 @@ when entities were removed mid-round. - Fixed clients getting desynced if the server ends a campaign and starts a new one. - Fixed the "campaign view" button staying visible in the server lobby after the campaign has ended. - Fixed message boxes appearing behind the campaign setup menu in the server lobby. -- Fixed round summaries always showing the game over text in multiplayer if the submarine didn't progress +- Fixed round summaries always showing the game over text in multiplayer if the submarine didn't progress to the next location. - Fixed missing spark particles when welding/cutting a wall. - Fixed plasma cutters not affecting hatches, alien doors or duct blocks. @@ -4534,20 +4749,20 @@ instead of a limb. - Fixed crashing when attempting to perform CPR on a headless character or AS a headless character. - Fixed attachable items being deattachable with the select key instead of the use key. - Fixed clients being unable to open doors with crowbars. -- Fixed items attached mid-round by other clients or the host being impossible to interact with +- Fixed items attached mid-round by other clients or the host being impossible to interact with and occasionally being attached to an incorrect position. - Fixed batteries not getting recharged in charging docks. - Fixed monsters being able to spawn under the ocean floor in levels where the floor is high up. - Fixed effects of the medical items not being stackable, meaning that successive usages of a medicine did not have an effect until the effect of the first dose has worn off. -- Fixed items in itemcontainers (e.g. shells in railgun loaders, batteries in recharging docks) always +- Fixed items in itemcontainers (e.g. shells in railgun loaders, batteries in recharging docks) always being rendered with the default sprite color. - Fixed crashing when clicking the "refreshing server list" text in the server list menu. - Fixed dedicated servers not resetting votes when a round ends. - Fixed interaction areas of some items being incorrect in the dedicated server. - Fixed the removal of items that get deleted after being used not being synced. -- Ladder waypoint generation fix: waypoints are not just placed at the top and bottom of the ladders, -but above every platform along the ladders (-> waypoints work correctly on ladders spanning through +- Ladder waypoint generation fix: waypoints are not just placed at the top and bottom of the ladders, +but above every platform along the ladders (-> waypoints work correctly on ladders spanning through multiple floors). --------------------------------------------------------------------------------------------------------- @@ -4579,13 +4794,13 @@ v0.8.0.2 - Sound configuration files are included in content packages. - Fixed "file not found" errors when a character is wearing footwear with no configured footstep sounds. - MODDERS PLEASE NOTE: hit sounds on limbs and wearable items must now use tags instead of direct paths -to the sound file. New sound files and tags can be added by editing the sound configuration files. +to the sound file. New sound files and tags can be added by editing the sound configuration files. --------------------------------------------------------------------------------------------------------- v0.8.0.1 --------------------------------------------------------------------------------------------------------- -- Fixed crashing when creating items with a PowerTransfer component mid-round (e.g. when fabricating +- Fixed crashing when creating items with a PowerTransfer component mid-round (e.g. when fabricating a relay component) - Fixed dead/spectator chatting in multiplayer. - Chatboxes can be deselected with the chat hotkey again. @@ -4604,9 +4819,9 @@ v0.8.0.0 - Made ambient lighting much darker and added a subtle glow around the player. - Made partially damaged walls leak much more slowly. -- Nerfed wall damage. Crawlers, mantises, threshers and coelanths now take much more time to tear through +- Nerfed wall damage. Crawlers, mantises, threshers and coelanths now take much more time to tear through the hull and collisions with the level cause less damage. -- Moved a bunch of hard-coded texts to an xml file "Content/Texts.xml". Full translation support is still +- Moved a bunch of hard-coded texts to an xml file "Content/Texts.xml". Full translation support is still in progress, but now it should be possible to translate most of the in-game texts without recompiling the game. - Optimized rendering. - More accurate endworm attack hit detection. @@ -4651,7 +4866,7 @@ Items: - Made more items craftable and deconstructable. - Added sprites for broken doors, hatches and junction boxes. - Health Scanner HUD displays causes of death. - - Health Scanner HUD only shows the status of the visible character that the cursor is closest to + - Health Scanner HUD only shows the status of the visible character that the cursor is closest to prevent multiple characters from cluttering the screen - Added "smallitem" tag to revolver rounds (can be placed in cabinets and pockets now). - Option to determine which types of targets a projectile can stick to. @@ -4660,29 +4875,29 @@ Items: - Characters can hold flashlights in their mouths. - Added an inventory slot for ID cards. - If a character is wearing an item that obscures their face, the game will either hide their name or - show the name that's in the ID card the character is wearing. + show the name that's in the ID card the character is wearing. - The owner of an ID card is stated in the description of the card. - - Fabricating items requires the ingredients to have a specific minimum condition (can't use + - Fabricating items requires the ingredients to have a specific minimum condition (can't use already-used consumables to craft something). - Deconstructing an item that's not in a full condition may prevent some deconstruction products from appearing. - Optimized electricity/signal logic. - Grenades can be triggered by detonators. - Bought cargo spawns in containers instead of being scattered across the floor. - - Players can't use other items when a railgun controller is selected. Prevents accidentally firing + - Players can't use other items when a railgun controller is selected. Prevents accidentally firing weapons or hitting people with something while using the railguns. - Item editing menus display color values as 0-255 instead of 0-1. - - Reactor temperature has to be critical for 30 seconds before the reactor explodes, giving the crew - more time to deal with griefers or incompetent reactor operators. The reactors also have an output + - Reactor temperature has to be critical for 30 seconds before the reactor explodes, giving the crew + more time to deal with griefers or incompetent reactor operators. The reactors also have an output connection that sends out a signal when the temperature is critical. Multiplayer additions: - - Added an optional "karma system". Harming other players, damaging structures and blowing up the reactor - reduces karma, while repairing things causes it to increase. A too low karma level prevents the players + - Added an optional "karma system". Harming other players, damaging structures and blowing up the reactor + reduces karma, while repairing things causes it to increase. A too low karma level prevents the players from choosing specific jobs - Clients can be given permission to use specific console commands. - Client permission presets: the clients can be assigned as moderators or admins which have specific pre-configured permissions. New presets can be added by editing Data/permissionpresets.xml. - - Option to have more than one traitor. Traitors also now get a set of code words that can be used to + - Option to have more than one traitor. Traitors also now get a set of code words that can be used to secretly identify other traitors. - Added an option to make players spawn directly in the main submarine instead of the respawn shuttle. - Keybinds are disabled when the chatbox is active. Now it's possible to use normal letter/number keys @@ -4693,24 +4908,24 @@ Multiplayer additions: - Cutting and repairing walls is included in server logs. Multiplayer bugfixes: - - Fixed clients getting disconnected due to desync when a new monster is spawned mid-round by + - Fixed clients getting disconnected due to desync when a new monster is spawned mid-round by a repeating monster event. - Fixed modified clients being able to chat while unconscious due to the lack of server-side checks. - Fixed modified clients being able to disconnect locked wires due to the lack of server-side checks. - Fixed chat messages being assigned to the wrong sender when their bodies have been eaten. - Fixed crashing when setting a server filter while the game is refreshing the server list. - - Fixed wires not being dropped server-side when a player drops a connected wire without dragging it + - Fixed wires not being dropped server-side when a player drops a connected wire without dragging it to their inventory first. - Fixed 5th server being impossible to select in the server list. - Improvements to character position syncing. - Re-enabled logic for preventing players from using visually similar names. - - Fixed client-side null exception when the client is in the lobby and a round ends with the mission + - Fixed client-side null exception when the client is in the lobby and a round ends with the mission successfully completed. - Fixed clients being able to votekick/kick/ban themselves in the server lobby. - Fixed "selected mode" and "mission type" settings not being saved. - Level seed randomization can be toggled on and off via the debug console. - Fixed dedicated server not randomizing the submarine or game mode even if randomization is enabled. - - Lighting is forced back on when a client starts a round (-> can't disable lighting by using the + - Lighting is forced back on when a client starts a round (-> can't disable lighting by using the console command before joining a server). Bugfixes: @@ -4739,11 +4954,11 @@ Bugfixes: - Fixed being able to use a wrench on multiple items at the same time. - Fixed double-clicking items in corpses putting them in their hands instead of your own inventory. - Fixed some level seeds generating a tiny enclosed cave that makes it impossible to reach the destination. - - Fixed opened and broken doors being ignored during waypoint generation, causing waypoint connections + - Fixed opened and broken doors being ignored during waypoint generation, causing waypoint connections to go through doors which prevented AI characters from opening them. - Fixed modified structure colors not being cloned. - Fixed modified wall colors only being visible in the submarine editor. - - Fixed items being dropped when attempting to place them in an itemcontainer slot that's on a normal + - Fixed items being dropped when attempting to place them in an itemcontainer slot that's on a normal inventory slot. - Fixed stack overflow exceptions caused by signal loops between junction boxes. - Fixed submarine editor crashing when attempting to use illegal characters in the filename. @@ -4766,7 +4981,7 @@ v0.7.0.1 - Removed serverconfig.xml (the dedicated server now uses the same config file as the normal game). - Updated the vanilla content package to version 0.7. - Fixed entity linking in the submarine editor. -- Fixed railgun HUD crashing the game if the railgun is linked to an item that does not have an +- Fixed railgun HUD crashing the game if the railgun is linked to an item that does not have an ItemContainer component (i.e. any item that can't contain other items). - Fixed exceptions when the player dies in the tutorial. - Fixed the start popup saying the host is the target if the host has been selected as the traitor. @@ -4806,7 +5021,7 @@ Particles: Bugfixes: - Fixed the "DXGI_ERROR_DEVICE_REMOVED" crashes on specific GPUs when the loading reaches 80%. - Fixed crashes when projectiles stuck to items on dedicated server. - - Fixed a bunch of bugs that caused crashes when a character was removed mid-round (for example when + - Fixed a bunch of bugs that caused crashes when a character was removed mid-round (for example when a character turns into a husk). - Fixed a bug that occasionally caused swimming creatures to flip around constantly. - Fixed a bug that caused creatures to be able to sever limb joints that shouldn't be possible to sever, @@ -4819,7 +5034,7 @@ Bugfixes: - Fixed AI characters never making any sounds in multiplayer. - Fixed inability to rebind keys to Mouse2 via the settings menu. - Fixed destroyed doors being impossible to repair. - - Fixed creatures seeking towards an incorrect position when trying to eat something (causing larger + - Fixed creatures seeking towards an incorrect position when trying to eat something (causing larger creatures like threshers and coelanths to swim around the target without ever reaching it). Items: @@ -4830,7 +5045,7 @@ Items: - Added sprites for destroyed doors/hatches. - Added a HUD that shows the charge of the supercapacitors and the amount of shells left when using a railgun. - - Inventory slots are be highlighted even if the cursor is within the empty space between them. Now + - Inventory slots are be highlighted even if the cursor is within the empty space between them. Now items can't be accidentally dropped by releasing the mouse button between the slots. - Sounds for picking up and dropping items. - Medical scanner displays husk infections. @@ -4838,7 +5053,7 @@ Items: - Optimizations and minor visual changes to the sonar display. - Support for hitscan projectiles. - Fixed ranged weapons launching projectiles with an incorrect rotation. - - The spread of ranged weapons can be adjusted (separate values for normal spread and spread when + - The spread of ranged weapons can be adjusted (separate values for normal spread and spread when the item is being used by a character with an inadequate skill level). - Heavier harpoon gun recoil and impulse when the spear hits something. @@ -4851,7 +5066,7 @@ Items: Misc: - Flowing water pushes characters around much more heavily. - Warning texts when water pressure is increasing to dangerous levels and when running out of oxygen. - - Made the damage range of limb attacks configurable (instead of having it always be half of the distance + - Made the damage range of limb attacks configurable (instead of having it always be half of the distance at which the attack activates) and tweaked the damage ranges of all the creature attacks. - Option to filter the server list based on a bunch of criteria. - Added a radio chat hotkey. @@ -4867,18 +5082,18 @@ v0.6.1.4 - Fixed reactors staying operational after the fuel rods run out. - Fixed spawning items at the cursor via the debug console. - Fixed job assignment logic causing crashes if the maximum amount of players per job has been reached -on the top 3 preferences of a client (which can happen on modded servers that have several jobs with a +on the top 3 preferences of a client (which can happen on modded servers that have several jobs with a player limit). - Fixed errors during job assignment if a client or the host is controlling a non-human character. - The server log menu remembers the state of the filters when toggling it. - Fixed level generation errors in some specific seeds that caused the game to create ruin walls with a negative width/height (example seed: cBLgZ2im). -- Fixed a submarine position syncing issue that caused erratic physics behavior on some specific +- Fixed a submarine position syncing issue that caused erratic physics behavior on some specific multi-part subs, because the game only moved one part of the sub and the parts docked to it, not taking into account that more parts may be docked to the docked parts. - Handcuffed AI characters can't climb ladders. - Fixed crashing when a huskified human is killed by an explosion. -- Added some GPU info to the crash reports and extra debug logging to hopefully diagnose the +- Added some GPU info to the crash reports and extra debug logging to hopefully diagnose the SharpDXExceptions during startup. --------------------------------------------------------------------------------------------------------- @@ -4887,7 +5102,7 @@ v0.6.1.3 - Fixed hulls not being rendered in the submarine editor. - Crouching is synced between the server and the clients. -- Plasma cutters and welding tools ignore platforms and stairs - placing a platform on a wall doesn't +- Plasma cutters and welding tools ignore platforms and stairs - placing a platform on a wall doesn't prevent welding/cutting the wall anymore. - Using the SetClientCharacter console command forces the client's line of sight effect back on. - Fixed null reference exceptions when syncing docking ports that haven't been docked to anything. @@ -4901,7 +5116,7 @@ v0.6.1.2 - Dedicated servers can use autorestart. - Fixed dedicated servers ignoring armor. - Fixed console messages not appearing in the crash reports if the game crashes during loading. -- Attachable items (buttons, electrical components, etc) are automatically attached to walls when placed +- Attachable items (buttons, electrical components, etc) are automatically attached to walls when placed in the submarine editor. - Fixed crashing if no matching character is found when using the SetClientCharacter console command. - Fixed wires disconnecting from a connection panel when a player moves any of the wires. @@ -4932,13 +5147,13 @@ Multiplayer: - Job preferences don't reset when quitting the game. - Added MessageBox chat message type. Allows custom servers to display custom message boxes to the clients. - Logging when a character throws an item. - - Logging which items are contained inside items characters use on themselves (e.g. which meds are + - Logging which items are contained inside items characters use on themselves (e.g. which meds are inside a medical syringe). - Logging which type of projectile was launched from a railgun and which items were contained inside it. - - More descriptive wiring logging: the logs don't list all the wires in a connection panel but only + - More descriptive wiring logging: the logs don't list all the wires in a connection panel but only the changes players do to the wiring. -Monsters: +Monsters: - Some creatures can hunt for smaller creatures (including humans) and eat them. - Tweaked enemy AI to make their attacks less likely to miss. - Some creatures flee when their health decreases below a specific threshold. @@ -4950,26 +5165,26 @@ Monsters: - The camera zooms further out when controlling a large non-humanoid character. Misc: - - Improved item interaction logic: highlighting items is more precise, with items directly under + - Improved item interaction logic: highlighting items is more precise, with items directly under the cursor taking priority. - Characters can be dismembered by creatures and explosions. - New blood particles. - Blood, explosion and fire decals. - - Added an artifact that attracts creatures. - - Detached buttons and electrical components can be picked up just like any other item, instead of + - Added an artifact that attracts creatures. + - Detached buttons and electrical components can be picked up just like any other item, instead of having to use a wrench and wait for the item to "detach". - Wires can't be connected to detached items. - Debug commands can be autocompleted using tab. - Added a debug command for creating explosions. - -Bugfixes: + +Bugfixes: - Fixed "loading was interrupted due to an error" crashes on startup. - Fixed "destination array was not long enough" errors in AddToGUIUpdateList. - Fixed error messages when a character gets stunned for over 60 seconds in multiplayer. - Characters don't consume oxygen from rooms when wearing a diving mask or a diving suit. - Fixed occasionally seeing through walls when swimming outside a submarine. - Fixed crashes during map generation caused by very large wall cells near the entrance of the level. - - When highlighting a wire in a connection panel, the physical wire and the items connected to it are + - When highlighting a wire in a connection panel, the physical wire and the items connected to it are highlighted. - Fixed crashing when selecting a sonar monitor in a submarine with no hulls. - Fixed submarine/shuttle lists occasionally appearing empty after joining a server. @@ -4986,7 +5201,7 @@ v0.6.0.2 - Fixed a bug that caused non-interactable checkboxes to always appear unchecked. - Skill level syncing fix: the syncing isn't dependent on the order of the characters skills anymore. - IP addresses are included in all login error messages and the errors are also logged to the debug console. -- Servers end rounds if all players are either dead or unconscious when autorestart is on (instead of +- Servers end rounds if all players are either dead or unconscious when autorestart is on (instead of waiting for all players to die) - Fixed nuclear shells and depth charges exploding immediately when launched. - Fixed a bug that prevented any broken items from being repaired in the single player. @@ -4999,8 +5214,8 @@ v0.6.0.1 - Readded spam filter. - Servers log the automatic temperature control setting of nuclear reactors. -- If a client fails to start a round (due to a missing sub file or an error, for example), their character -is automatically killed. This prevents situations where a team can't win a combat mission due to a +- If a client fails to start a round (due to a missing sub file or an error, for example), their character +is automatically killed. This prevents situations where a team can't win a combat mission due to a disabled, invisible character in the opposing team. - Fixed clients occasionally displaying the "crew has been defeated" message immediately after a combat mission starts. @@ -5033,7 +5248,7 @@ UI: - multi-line chat messages don't overlap Items: - - passive sonar: when not active, the sonar shows nearby sources of sound and a faint outline of the + - passive sonar: when not active, the sonar shows nearby sources of sound and a faint outline of the structures around them. Now it's much easier to monitor how much noise the submarine is making and to hide from enemies. - new sonar visuals @@ -5043,7 +5258,7 @@ Items: - buttons created in fabricators work now Submarine editor: - - items/structures that have been copy-pasted from another submarine don't disappear when saving and + - items/structures that have been copy-pasted from another submarine don't disappear when saving and loading the sub - fixed crashes when attempting to load a submarine with no walls - placing a resizable structure with a height/width of zero is not allowed @@ -5061,7 +5276,7 @@ Misc: - heal and revive commands can also be used on other characters than the controlled one - fixed fires occasionally causing incorrect sound clips to loop continuously - AI controlled crew members are better at avoiding hazards such as water and fire - - swimming animation fix: characters don't swim with their legs extended up over their shoulders + - swimming animation fix: characters don't swim with their legs extended up over their shoulders after a sharp turn --------------------------------------------------------------------------------------------------------- @@ -5106,7 +5321,7 @@ v0.5.4.3 - a new enemy - some new sound effects by Omniary - some structure-specific damage sounds -- the size of docked subs is taken into account when determining the spawn position of the sub (large +- the size of docked subs is taken into account when determining the spawn position of the sub (large multi-part subs shouldn't spawn inside walls anymore) - explosion damage is calculated based on the distance to the closest surface of a limb instead of the center position of the limb (i.e. large monsters can be damaged by smaller explosions) @@ -5126,13 +5341,13 @@ v0.5.4.2 --------------------------------------------------------------------------------------------------------- - fixed crashes when removing nodes from a wire (i.e. right clicking with a wire equipped) -- fixed inventory not being drawn in the correct position if switching to a character who's been +- fixed inventory not being drawn in the correct position if switching to a character who's been dragged/grabbed by some other character - fixed wires becoming disconnected when copypasting them - wire nodes can't be moved when connecting wires to a connection panel - fixed repeating crash messageboxes if the game fails to resolve a SharpDX exception on startup - fixed crashing when switching to wiring mode while editing some value of an item -- fixed keyboard focus staying in textboxes after the textbox has been hidden (for example, +- fixed keyboard focus staying in textboxes after the textbox has been hidden (for example, the input fields in the submarine saving prompt) - fixed error message spam if a docking port is linked to another port in the same sub - submarine lists in the editor, main menu and server menu are updated when new subs are saved/received @@ -5141,7 +5356,7 @@ the input fields in the submarine saving prompt) - the size of the docked subs is taken into account when generating the level - fixed autorestart timer not resetting at the clients' end if the server fails to start a shift and resets the timer -- docked subs are forced to correct positions during loading (subs won't get stuck inside each other +- docked subs are forced to correct positions during loading (subs won't get stuck inside each other even if the submarines are slightly overlapping in the editor) - all sounds are paused when switching to submarine editor @@ -5159,19 +5374,19 @@ Bugfixes: - fixed a bug that occasionally caused crashing when the game happens to generate a very small level Sub editor: - - structures/items that are behind something else can be selected using a listbox that appears - when hovering the cursor over them - - wires have to be selected by clicking before any of the points can be moved (makes it possible + - structures/items that are behind something else can be selected using a listbox that appears + when hovering the cursor over them + - wires have to be selected by clicking before any of the points can be moved (makes it possible to move the correct wire even if it's overlapping with other wires) - the selected wire is renderer over all structures - points can be added to wires by clicking while holding ctrl - disabled music Misc: - - some rendering optimization + - some rendering optimization - pathfinding and waypoint generation improvements - made mantises more aggressive - - water flows more slowly through partially damaged walls + - water flows more slowly through partially damaged walls --------------------------------------------------------------------------------------------------------- v0.5.4.0 @@ -5180,12 +5395,12 @@ v0.5.4.0 Submarine editor: - copy, paste and cut functionality - items/structures can be copied by holding ctrl while dragging - - it's possible to move a wire by moving both items it's connected to (without having to move each + - it's possible to move a wire by moving both items it's connected to (without having to move each individual point of the wire separately) - - "hull volume helper" which makes it easier to select a suitable ballast tank size and + - "hull volume helper" which makes it easier to select a suitable ballast tank size and NeutralBallastLevel setting in the navigation terminal - equipped items are removed when switching from wiring mode to character mode or vice versa - - no need to wait when deattaching items from the walls with a wrench + - no need to wait when deattaching items from the walls with a wrench Bugfixes: @@ -5193,18 +5408,18 @@ Bugfixes: - UI elements (buttons, textboxes, etc) can't be clicked through each other anymore - fixed a bug that caused crashes when deattaching items from walls - fixed a game-crashing particle bug - - fixed respawned characters getting assigned to a different team than the rest of the characters + - fixed respawned characters getting assigned to a different team than the rest of the characters (causing them to be displayed separately in the crew menu) - pathfinding/autopilot fixes Misc: - - server hosts can give players special privileges (kick, ban, end round) + - server hosts can give players special privileges (kick, ban, end round) - saving the contents of the server info box and the traitor setting - - changes to battery logic: they can now be used to cover the entire power consumption of the + - changes to battery logic: they can now be used to cover the entire power consumption of the electrical grid (assuming their maximum output is high enough) - - added "artifact holders" to alien ruins (which can also be used for turning artifacts into power + - added "artifact holders" to alien ruins (which can also be used for turning artifacts into power sources if installed in a sub) - - changes to character collider behavior: crouching changes the size of the collider and it's + - changes to character collider behavior: crouching changes the size of the collider and it's easier to step over small obstacles @@ -5224,7 +5439,7 @@ v0.5.3.3 - fixed a bug that caused crashes after a husk-infected player died - disabled the "zoom effect" when under pressure as a huskified human -- only a limited number of messages are kept in the debug console (prevents performance issues if large +- only a limited number of messages are kept in the debug console (prevents performance issues if large amounts of messages are added) - some item and electricity logic optimization - fixed "sprite tigerthresher not found" errors in the Linux version @@ -5262,7 +5477,7 @@ Changes to ragdoll movement/animation logic: - characters are less likely to take impact damage by stumbling in stairs - (+ makes working on the new improved netcode much easier) - ladders can be slid down by holding the sprint key - + Submarine Editor: - zoom now works relative to the mouse's position rather than the center of the screen - fixed selection rectangle not being visible when dragging from bottom right to top left @@ -5274,7 +5489,7 @@ Items: - a "glow effect" when moving items between inventory slots - option to select which location the autopilot should navigate to - fabricator UI shows item descriptions and items that can't be fabricated are grayed out - + Bugfixes: - attempt to fix "DXGI_ERROR_NOT_CURRENTLY_AVAILABLE" errors on startup - fixed water flow sounds taking up all the audio channels and preventing other sounds from playing @@ -5286,13 +5501,13 @@ Bugfixes: - waypoint generation and pathfinding bugfixes Misc: - - improved line of sight effect (instead of a solid black "fog of war", a faint image of the + - improved line of sight effect (instead of a solid black "fog of war", a faint image of the surrounding rooms can be seen through walls) - less ambient light, and it gets darker when diving deeper - - a hull-specific ambient light system: light sources increase the amount of light inside rooms, + - a hull-specific ambient light system: light sources increase the amount of light inside rooms, preventing shadows from looking unnaturally dark in fully lit submarines - option to disable vsync - - added a near-indestructible alien ruin wall variant - breaking through the walls with a railgun + - added a near-indestructible alien ruin wall variant - breaking through the walls with a railgun or a plasma cutter is not always an option anymore - added a parallax effect to the particles floating in the ocean @@ -5314,7 +5529,7 @@ Improved MiniMap (now called "Status Monitor"): - the single player map shows which locations have been visited and the passageways that have been used - minor visual improvements to the single player campaign menus - huskification bugfixes -- oxygen isn't distributed through gaps or vents that are underwater (i.e. air pockets can form when the +- oxygen isn't distributed through gaps or vents that are underwater (i.e. air pockets can form when the sub is flooding) - molochs (or other large creatures) can't push the sub around as easily anymore @@ -5335,7 +5550,7 @@ v0.5.1.2 --------------------------------------------------------------------------------------------------------- - hacked clients can't join a full server or change the name of their character anymore -- option to choose which character to control using the "control" command when there are multiple +- option to choose which character to control using the "control" command when there are multiple characters/creatures with the same name - a console command for spawning items - the server logs show who sent each chat message @@ -5387,7 +5602,7 @@ v0.5.0.3 v0.5.0.2 --------------------------------------------------------------------------------------------------------- -- more server-side sanity checks to prevent (desynced or hacking) players from doing things their +- more server-side sanity checks to prevent (desynced or hacking) players from doing things their characters shouldn't be able to do - fixed collision issues at docking ports (such as shooting up in the air when trying to drop down into a docked shuttle while shuttle hatch is closed) @@ -5431,12 +5646,12 @@ Items: - changes to the logic that determines which item is being highlighted - now it's much easier to select specific items in cramped subs - highlighted items glow (so it's easier to see which item you're targeting in the dark) - - fixed an electricity bug that sometimes caused parts of the grid to not carry any power after + - fixed an electricity bug that sometimes caused parts of the grid to not carry any power after a junction box has been broken and repaired - option to choose the output of a signal check component when the signal doesn't match - fixed fire extinquishers - item search bar in the submarine editor - - fixed cargo items spawning in incorrect positions (which occasionally caused some serious problems + - fixed cargo items spawning in incorrect positions (which occasionally caused some serious problems if the item happened to be a crate full of nitroglycerin) - flares burn longer - fixed flashes from explosions/sparks/flares occasionally ''staying on'' @@ -5590,7 +5805,7 @@ v0.4.0.0 DOCTORS: - medical doctors (can fabricate various drugs/chemicals and give CPR to unconscious characters) - - changes to the dying logic: characters will be unconscious when their health or oxygen goes below 0, + - changes to the dying logic: characters will be unconscious when their health or oxygen goes below 0, and die when it drops to -100 - medical syringes can be used on other characters - any chemicals can be inserted in medical syringes @@ -5601,7 +5816,7 @@ Items: - junction boxes, sonar monitors, navigation terminals and engines break if they're underwater long enough - reactor cools down if it's underwater (multiple fuel rods are required to bring the temperature back up) - forces are applied to items (not just characters) when the submarine hits something - - changes to the logic for distributing oxygen through vents: the oxygen generator pushes more oxygen + - changes to the logic for distributing oxygen through vents: the oxygen generator pushes more oxygen to larger rooms instead of dividing the oxygen output equally between vents - autopilot bugfixes @@ -5617,7 +5832,7 @@ Items: - a bunch of new sprites Multiplayer: - - fixed a bug that caused the server to resend a ton of messages to a client who's been temporarily + - fixed a bug that caused the server to resend a ton of messages to a client who's been temporarily disconnected, causing syncing issues to every player - fixed syncing issues related to items breaking (eg junction boxes being broken only for some players) - fixed dead monsters occasionally ''teleporting'' inside the sub in multiplayer @@ -5673,16 +5888,16 @@ v0.3.5.0 - items float and can be moved around by flowing water - wiring mode which makes wiring more convenient in the editor - networking bugfixes and improvements -- changes to the logic that determines how far the monsters can see/hear the submarine from - now it's +- changes to the logic that determines how far the monsters can see/hear the submarine from - now it's possible to evade some monsters by turning off noisy devices and/or stopping the submarine -- invisible entities (items inside cabinets, hulls/gaps when they've been hidden) can't be highlighted +- invisible entities (items inside cabinets, hulls/gaps when they've been hidden) can't be highlighted or selected in the editor - fixed monster/item spawnpoints being placed in unreachable locations - relay and delay components - fixed lights not being positioned correctly on moving items - added a ''set_color'' connection to light components - ladders outside the sub can be climbed -- changes to drowning/suffocation logic: amount of oxygen drops at a fixed rate instead of effects +- changes to drowning/suffocation logic: amount of oxygen drops at a fixed rate instead of effects "stacking" (e.g. when wearing a diving suit with no oxygen tank in a room with low oxygen) - fixed projectiles not colliding with the submarine when shot from the outside @@ -5698,7 +5913,7 @@ v0.3.4.2 v0.3.4.1 --------------------------------------------------------------------------------------------------------- -- fixed a major bug in the networking code, which caused the server to incorrectly determine the order +- fixed a major bug in the networking code, which caused the server to incorrectly determine the order of messages received from different clients and discard valid messages - fixed levels with the same seed appearing different between the Linux and Windows versions - creatures spawned using the console are synced with clients @@ -5812,8 +6027,8 @@ a round v0.3.2.0 --------------------------------------------------------------------------------------------------------- -- server logs -- server admins have the option to send messages only to dead players and spectators (/d [message]) or +- server logs +- server admins have the option to send messages only to dead players and spectators (/d [message]) or to one specific player (/name [message]) - more reliable door syncing - railgun syncing bugfixes @@ -5860,7 +6075,7 @@ v0.3.1.2 v0.3.1.1 --------------------------------------------------------------------------------------------------------- -- fixed a major bug that caused item/monster ID mismatches between the server and the clients, which +- fixed a major bug that caused item/monster ID mismatches between the server and the clients, which accounted for many of the monster/inventory/item syncing issues - improved player position syncing @@ -5896,7 +6111,7 @@ v0.3.0.4 - fixed "submarine not found" errors which occurred in multiplayer if the filename didn't match the name of the submarine - fixed new structures not lining up with existing ones if switching to editor while a round is running -- fixed a bug in shadow rendering which caused memory leaks +- fixed a bug in shadow rendering which caused memory leaks - the autoupdater only checks the Content folder when deleting files that don't belong to the latest version (i.e. the autoupdater won't delete your mods as long as they aren't saved in the Content folder) - molochs and endworms are immune to bleeding! @@ -5906,9 +6121,9 @@ v0.3.0.3 --------------------------------------------------------------------------------------------------------- - fixed selecting stairs and items outside the sub in editor -- fixed crashing when pressing the ''start'' button while no route is chosen in single player +- fixed crashing when pressing the ''start'' button while no route is chosen in single player - fixed fire syncing -- fixed another bug that crashed the game if in the lobby when a round ends +- fixed another bug that crashed the game if in the lobby when a round ends - camera keeps moving with the sub when typing into chatbox in spectator mode --------------------------------------------------------------------------------------------------------- @@ -5932,7 +6147,7 @@ v0.3.0.1 - fixed a bug that made it impossible to fix broken walls after saving and reloading - fixed crashing when trying to place ladders when no submarine has been loaded - trying to generate waypoints for an empty sub won't crash the game anymore -- when opening the crew commander menu for the first time, there's a text notifying about the hotkey for +- when opening the crew commander menu for the first time, there's a text notifying about the hotkey for opening/closing the menu @@ -5969,7 +6184,7 @@ Items: Submarines: - a new sub, Nehalennia - the collider of the submarine now matches the shape of the hull - - the airlock pumps in each sub are set to pump water out instead of just turning the pump on when pressing + - the airlock pumps in each sub are set to pump water out instead of just turning the pump on when pressing the button outside the airlock Submarine editor: @@ -5977,7 +6192,7 @@ Submarine editor: - tickboxes for hiding hulls, gaps, waypoints and links between items - a list of the most recently used items/structures - placed wires are much easier to move around - - more accurate staircase selecting (the ''bounding box'' of the staircase won't prevent selecting items that + - more accurate staircase selecting (the ''bounding box'' of the staircase won't prevent selecting items that are behind it anymore) - visible indicators for railgun rotation limits @@ -6015,7 +6230,7 @@ Multiplayer: - major changes to the networking code: better lag compensation, more reliable item/character syncing, lower bandwidth consumption - spectator mode - + Submarine: - overloading the electrical grid or the reactor may cause fires @@ -6036,7 +6251,7 @@ Items: Misc: - fixed placing ladders and labels in sub editor - fixed a couple of game-crashing bugs in submarine saving - + --------------------------------------------------------------------------------------------------------- v0.2.5 --------------------------------------------------------------------------------------------------------- @@ -6045,7 +6260,7 @@ Multiplayer: - option to randomly select level seed, submarine and/or game mode - players can be allowed to vote for the next sub and game mode - option to choose character's head - + Submarine: - pressure damage if the submarine dives too deep - added the missing mechanic spawnpoint missing to Aegir @@ -6057,7 +6272,7 @@ Items: - diving suits and mask now obstruct vision when worn - nicer looking sonar monitor -Misc: +Misc: - the levels aren't just enclosed tunnels anymore and it's possible to dive much deeper - settings menu - better UI scaling on small resolutions @@ -6080,7 +6295,7 @@ Multiplayer: - the "fix list" when repairing items is synced between clients, so the reactor can actually be fixed now - more networking optimization - bans can be removed by using a button under the player list, not just by editing the bannedplayers.xml file - + Items: - wires are removed from connection panels when they're deleted in the editor - doors can be rewired from either side @@ -6147,7 +6362,7 @@ v0.2.2 Multiplayer: - network statistics view which can be enabled by opening the debug console (F3) and entering "netstats" (only works if you're running a server) - - updated to latest version of Lidgren networking library, which may or may not have an effect + - updated to latest version of Lidgren networking library, which may or may not have an effect on the chat lag issues Items: @@ -6177,7 +6392,7 @@ Items: - broken doors can only be fixed by mechanics - fixed a bug that sometimes made it impossible to pick/select items after reattaching them on a wall - wires are disconnected and dropped if the item at either end is removed - + --------------------------------------------------------------------------------------------------------- v0.2 --------------------------------------------------------------------------------------------------------- @@ -6226,16 +6441,16 @@ Misc: added in future versions, including one for making custom subs) - an auto-updater in the launcher - the game generates a detailed report if it crashes - - physics optimization (i.e. using simplified physics and animation for off-screen characters and + - physics optimization (i.e. using simplified physics and animation for off-screen characters and disabling them entirely if they're far enough) - - lighting optimization (caching the lights/shadows if a light source hasn't moved instead of + - lighting optimization (caching the lights/shadows if a light source hasn't moved instead of recalculating them every frame) - two new background music tracks - better looking explosions - better looking water particle effects - minor UI improvements - better UI scaling on different resolutions - - health/oxygen bar improvements and status icons for bleeding and water pressure + - health/oxygen bar improvements and status icons for bleeding and water pressure - gap-hull connections are visible in the sub editor - pumps don't have to be manually connected to a hull in the editor anymore, they automatically empty/fill the hull they're inside @@ -6254,7 +6469,7 @@ Multiplayer: to a specific position - a window that displays some network statistics when hosting a server (can be activated by entering "debugview" to the debug console) - + --------------------------------------------------------------------------------------------------------- v0.1.3.1 --------------------------------------------------------------------------------------------------------- @@ -6272,12 +6487,12 @@ Multiplayer: to fly back to it as they try to move away Items: - - putting items inside other items works properly now (i.e. by pulling a spear to the same slot as + - putting items inside other items works properly now (i.e. by pulling a spear to the same slot as a harpoon, not the other way around) - C4 blocks loaded inside a railgun shell won't explode inside the submarine when firing the railgun - fixed another game-crashing railgun bug - fixed a bug that caused characters to spawn with an incorrect number of items - + --------------------------------------------------------------------------------------------------------- v0.1.2 --------------------------------------------------------------------------------------------------------- @@ -6292,7 +6507,7 @@ Items: Other: - optimized lightning and "line of sight" rendering - - an unfinished tutorial which can currently only be accessed by entering "tutorial" into the + - an unfinished tutorial which can currently only be accessed by entering "tutorial" into the debug console --------------------------------------------------------------------------------------------------------- @@ -6301,7 +6516,7 @@ v0.1.1 Multiplayer: - player names are shown - - assigning jobs and selecting job preferences works now (jobs are assigned when the round starts) + - assigning jobs and selecting job preferences works now (jobs are assigned when the round starts) - a menu that shows the crew members and their jobs and skills - reduced lag spikes - fixed a bug that caused disconnected players to stay in the player list diff --git a/Barotrauma/BarotraumaShared/config.xml b/Barotrauma/BarotraumaShared/config.xml index 62d4cca5c..4f6ecf964 100644 --- a/Barotrauma/BarotraumaShared/config.xml +++ b/Barotrauma/BarotraumaShared/config.xml @@ -31,7 +31,7 @@ Down="S" Left="A" Right="D" - Attack="R" + Attack="F" Run="LeftShift" Crouch="LeftControl" InfoTab="Tab" diff --git a/Barotrauma/BarotraumaShared/serversettings.xml b/Barotrauma/BarotraumaShared/serversettings.xml index 9b96f6b02..34f4c94b2 100644 --- a/Barotrauma/BarotraumaShared/serversettings.xml +++ b/Barotrauma/BarotraumaShared/serversettings.xml @@ -11,7 +11,7 @@ autorestart="false" LevelDifficulty="20" AllowedRandomMissionTypes="Random,Salvage,Monster,Cargo,Combat" - AllowedClientNameChars="32-33,38-46,48-57,65-90,91-91,93-93,95-122,192-255,384-591,1024-1279,19968-40959,13312-19903,131072-15043983,15043985-173791,173824-178207,178208-183983,63744-64255,194560-195103" + AllowedClientNameChars="32-33,38-46,48-57,65-90,91-91,93-93,95-122,192-255,384-591,1024-1279,19968-21327,21329-40959,13312-19903,131072-173791,173824-178207,178208-183983,63744-64255,194560-195103" ServerMessage="" tickrate="20" randomizeseed="True" @@ -24,6 +24,7 @@ startwhenclientsreadyratio="0.8" allowspectating="True" saveserverlogs="True" + linesperlogfile="800" allowragdollbutton="True" allowfiletransfers="True" voicechatenabled="True"