From 501e02c026b7b8a9b60a5f6572d53945495b681b Mon Sep 17 00:00:00 2001 From: Markus Isberg <3e849f2e5c@pm.me> Date: Thu, 26 Aug 2021 21:08:21 +0900 Subject: [PATCH 01/12] Unstable 0.1500.0.0 --- .../ClientSource/Characters/Attack.cs | 3 +- .../ClientSource/Characters/Character.cs | 125 +- .../ClientSource/Characters/CharacterHUD.cs | 35 +- .../ClientSource/Characters/CharacterInfo.cs | 39 +- .../Characters/CharacterNetworking.cs | 36 +- .../Characters/Health/CharacterHealth.cs | 74 +- .../ClientSource/DebugConsole.cs | 9 +- .../BarotraumaClient/ClientSource/GUI/GUI.cs | 41 +- .../ClientSource/GUI/GUIComponent.cs | 41 +- .../ClientSource/GUI/GUIFrame.cs | 2 +- .../ClientSource/GUI/GUIListBox.cs | 36 +- .../ClientSource/GUI/GUIScissorComponent.cs | 87 + .../ClientSource/GUI/GUIStyle.cs | 16 + .../ClientSource/GUI/HUDLayoutSettings.cs | 2 +- .../ClientSource/GUI/LoadingScreen.cs | 2 +- .../ClientSource/GUI/ShapeExtensions.cs | 41 +- .../ClientSource/GUI/Store.cs | 93 +- .../ClientSource/GUI/TabMenu.cs | 321 +++- .../ClientSource/GUI/UISprite.cs | 5 +- .../BarotraumaClient/ClientSource/GameMain.cs | 24 +- .../ClientSource/GameSession/CargoManager.cs | 31 +- .../ClientSource/GameSession/CrewManager.cs | 786 +++++---- .../GameModes/MultiPlayerCampaign.cs | 4 +- .../ClientSource/GameSession/GameSession.cs | 5 +- .../ClientSource/GameSettings.cs | 6 + .../ClientSource/Items/CharacterInventory.cs | 30 +- .../Items/Components/Holdable/RangedWeapon.cs | 58 +- .../Items/Components/ItemComponent.cs | 3 +- .../Components/Machines/Deconstructor.cs | 3 +- .../Items/Components/Machines/Fabricator.cs | 78 +- .../Items/Components/Machines/MiniMap.cs | 1530 +++++++++++++++-- .../Items/Components/Machines/Pump.cs | 13 +- .../Items/Components/Machines/Steering.cs | 1 + .../Items/Components/Power/PowerContainer.cs | 2 +- .../Items/Components/Repairable.cs | 27 +- .../Items/Components/Signal/Terminal.cs | 2 +- .../ClientSource/Items/Components/Turret.cs | 16 +- .../ClientSource/Items/Inventory.cs | 28 +- .../ClientSource/Items/Item.cs | 8 +- .../ClientSource/Map/Explosion.cs | 2 + .../BarotraumaClient/ClientSource/Map/Hull.cs | 2 +- .../ClientSource/Map/Lights/LightManager.cs | 6 +- .../ClientSource/Map/Submarine.cs | 176 +- .../ClientSource/Networking/ChatMessage.cs | 5 +- .../ClientSource/Networking/GameClient.cs | 9 + .../ClientSource/Networking/ServerInfo.cs | 41 +- .../ClientSource/Networking/SteamManager.cs | 2 +- .../ClientSource/Particles/Particle.cs | 20 +- .../ClientSource/Particles/ParticleEmitter.cs | 2 + .../ClientSource/Particles/ParticleManager.cs | 45 +- .../ClientSource/Particles/ParticlePrefab.cs | 12 +- .../BarotraumaClient/ClientSource/Program.cs | 25 +- .../ClientSource/Screens/CampaignSetupUI.cs | 2 +- .../ClientSource/Screens/CampaignUI.cs | 4 +- .../Screens/CharacterEditor/Wizard.cs | 2 +- .../Screens/EventEditor/EventEditorScreen.cs | 2 +- .../ClientSource/Screens/GameScreen.cs | 3 + .../ClientSource/Screens/NetLobbyScreen.cs | 20 +- .../ClientSource/Screens/ServerListScreen.cs | 35 +- .../Screens/SteamWorkshopScreen.cs | 2 +- .../ClientSource/Screens/TestScreen.cs | 125 ++ .../ClientSource/Sounds/SoundPlayer.cs | 17 +- .../ClientSource/Utils/ToolBox.cs | 222 ++- .../Content/Effects/blueprintshader.xnb | Bin 0 -> 2462 bytes .../Effects/blueprintshader_opengl.xnb | Bin 0 -> 2010 bytes .../BarotraumaClient/LinuxClient.csproj | 2 +- Barotrauma/BarotraumaClient/MacClient.csproj | 2 +- .../BarotraumaClient/Shaders/Content.mgcb | 6 + .../Shaders/Content_opengl.mgcb | 5 + .../Shaders/blueprintshader.fx | 48 + .../Shaders/blueprintshader_opengl.fx | 48 + .../BarotraumaClient/WindowsClient.csproj | 2 +- .../BarotraumaServer/LinuxServer.csproj | 2 +- Barotrauma/BarotraumaServer/MacServer.csproj | 2 +- .../ServerSource/Characters/Character.cs | 5 + .../ServerSource/Characters/CharacterInfo.cs | 20 + .../Characters/CharacterNetworking.cs | 51 +- .../ServerSource/DebugConsole.cs | 1 + .../BarotraumaServer/ServerSource/GameMain.cs | 31 +- .../GameModes/CharacterCampaignData.cs | 2 +- .../Items/Components/Machines/Steering.cs | 6 + .../ServerSource/Items/Item.cs | 8 + .../Networking/ChildServerRelay.cs | 10 +- .../ServerSource/Networking/GameServer.cs | 22 +- .../ServerEntityEventManager.cs | 8 +- .../ServerSource/Networking/RespawnManager.cs | 19 +- .../Traitors/Goals/GoalFindItem.cs | 5 +- .../BarotraumaServer/WindowsServer.csproj | 2 +- .../Data/ContentPackages/Vanilla 0.9.xml | 18 + .../Characters/AI/AIController.cs | 8 +- .../Characters/AI/EnemyAIController.cs | 94 +- .../Characters/AI/HumanAIController.cs | 23 +- .../Characters/AI/IndoorsSteeringManager.cs | 9 +- .../AI/Objectives/AIObjectiveCombat.cs | 17 +- .../Objectives/AIObjectiveExtinguishFire.cs | 8 +- .../Objectives/AIObjectiveExtinguishFires.cs | 2 +- .../Objectives/AIObjectiveFightIntruders.cs | 1 + .../Objectives/AIObjectiveFindDivingGear.cs | 36 +- .../AI/Objectives/AIObjectiveIdle.cs | 27 +- .../SharedSource/Characters/AI/Order.cs | 49 +- .../SharedSource/Characters/AI/PathFinder.cs | 65 +- .../Animation/FishAnimController.cs | 1 + .../Animation/HumanoidAnimController.cs | 44 +- .../SharedSource/Characters/Attack.cs | 21 + .../SharedSource/Characters/Character.cs | 354 +++- .../SharedSource/Characters/CharacterInfo.cs | 217 ++- .../Health/Afflictions/Affliction.cs | 26 +- .../Health/Afflictions/AfflictionHusk.cs | 37 +- .../Health/Afflictions/AfflictionPrefab.cs | 156 +- .../Characters/Health/CharacterHealth.cs | 67 +- .../SharedSource/Characters/HumanPrefab.cs | 8 +- .../SharedSource/Characters/Limb.cs | 14 +- .../Characters/Params/CharacterParams.cs | 6 + .../AbilityConditionals/AbilityCondition.cs | 89 + .../AbilityConditionAttackData.cs | 79 + .../AbilityConditionAttackResult.cs | 38 + .../AbilityConditionCharacter.cs | 30 + .../AbilityConditionData.cs | 39 + .../AbilityConditionEvasiveManeuvers.cs | 22 + .../AbilityConditionHandsomeStranger.cs | 27 + .../AbilityConditionItem.cs | 50 + .../AbilityConditionReduceAffliction.cs | 33 + .../AbilityConditionScavenger.cs | 22 + .../AbilityConditionAboveVitality.cs | 20 + .../AbilityConditionAlliesAboveVitality.cs | 19 + .../AbilityConditionCrouched.cs | 18 + .../AbilityConditionDataless.cs | 24 + .../AbilityConditionHasAffliction.cs | 31 + .../AbilityConditionHasDifferentJobs.cs | 22 + .../AbilityConditionHasItem.cs | 57 + .../AbilityConditionInWater.cs | 15 + .../AbilityConditionMission.cs | 38 + .../AbilityConditionNoCrewDied.cs | 22 + .../AbilityConditionOnMission.cs | 17 + .../AbilityConditionRagdolled.cs | 18 + .../AbilityConditionRunning.cs | 15 + .../AbilityConditionServerRandom.cs | 24 + .../AbilityConditionShipFlooded.cs | 21 + .../Talents/Abilities/CharacterAbility.cs | 142 ++ .../Abilities/CharacterAbilityApplyForce.cs | 34 + .../CharacterAbilityApplyStatusEffects.cs | 43 + ...rAbilityApplyStatusEffectsToNearestAlly.cs | 36 + ...erAbilityApplyStatusEffectsToRandomAlly.cs | 40 + .../Abilities/CharacterAbilityGiveFlag.cs | 20 + .../CharacterAbilityGiveMissionCount.cs | 21 + .../Abilities/CharacterAbilityGiveMoney.cs | 22 + .../CharacterAbilityGivePermanentStat.cs | 49 + .../CharacterAbilityGiveResistance.cs | 21 + .../Abilities/CharacterAbilityGiveStat.cs | 22 + .../CharacterAbilityIncreaseSkill.cs | 41 + .../CharacterAbilityModifyAffliction.cs | 36 + .../CharacterAbilityModifyAttackData.cs | 44 + .../Abilities/CharacterAbilityModifyFlag.cs | 34 + .../CharacterAbilityModifyReduceAffliction.cs | 27 + .../CharacterAbilityModifyResistance.cs | 27 + .../Abilities/CharacterAbilityModifyStat.cs | 26 + .../Abilities/CharacterAbilityModifyValue.cs | 46 + .../Abilities/CharacterAbilityPutItem.cs | 46 + .../CharacterAbilityResetPermanentStat.cs | 29 + .../CharacterAbilityApprenticeship.cs | 21 + .../CharacterAbilityBountyHunter.cs | 23 + .../CharacterAbilityIndustrialRevolution.cs | 30 + .../CharacterAbilityInsurancePolicy.cs | 54 + .../CharacterAbilityMultitasker.cs | 26 + .../CharacterAbilityPsychoClown.cs | 44 + .../CharacterAbilityRegenerateLoot.cs | 30 + .../CharacterAbilityStonewall.cs | 42 + .../CharacterAbilityTandemFire.cs | 42 + .../CharacterAbilityTaskmaster.cs | 39 + .../AbilityGroups/CharacterAbilityGroup.cs | 206 +++ .../CharacterAbilityGroupEffect.cs | 33 + .../CharacterAbilityGroupInterval.cs | 48 + .../Characters/Talents/CharacterTalent.cs | 127 ++ .../Characters/Talents/TalentPrefab.cs | 102 ++ .../Characters/Talents/TalentTree.cs | 219 +++ .../SharedSource/ContentPackage.cs | 10 +- .../SharedSource/DebugConsole.cs | 96 +- .../BarotraumaShared/SharedSource/Enums.cs | 73 + .../Events/EventActions/ReputationAction.cs | 6 +- .../SharedSource/Events/EventManager.cs | 4 + .../SharedSource/Events/Missions/Mission.cs | 33 +- .../GameSession/AutoItemPlacer.cs | 43 +- .../SharedSource/GameSession/CrewManager.cs | 3 + .../GameSession/Data/Reputation.cs | 22 +- .../GameSession/GameModes/CampaignMode.cs | 32 +- .../SharedSource/GameSession/GameSession.cs | 98 +- .../SharedSource/GameSettings.cs | 30 +- .../Items/Components/DockingPort.cs | 19 +- .../Items/Components/ElectricalDischarger.cs | 15 + .../Items/Components/Holdable/Holdable.cs | 16 +- .../Items/Components/Holdable/IdCard.cs | 65 +- .../Items/Components/Holdable/MeleeWeapon.cs | 4 +- .../Items/Components/Holdable/RangedWeapon.cs | 61 +- .../Items/Components/ItemComponent.cs | 22 +- .../Items/Components/ItemContainer.cs | 33 +- .../Items/Components/Machines/Engine.cs | 6 + .../Items/Components/Machines/Fabricator.cs | 185 +- .../Items/Components/Machines/MiniMap.cs | 73 +- .../Items/Components/Machines/Pump.cs | 6 + .../Items/Components/Machines/Steering.cs | 15 +- .../Items/Components/Power/PowerTransfer.cs | 7 + .../Items/Components/Projectile.cs | 2 +- .../Items/Components/Repairable.cs | 56 +- .../Components/Signal/ConnectionPanel.cs | 1 + .../Components/Signal/OscillatorComponent.cs | 16 +- .../Components/Signal/RegExFindComponent.cs | 7 +- .../Items/Components/Signal/Terminal.cs | 3 + .../Items/Components/Signal/WaterDetector.cs | 4 +- .../SharedSource/Items/Components/Turret.cs | 287 ++-- .../SharedSource/Items/Components/Wearable.cs | 19 +- .../SharedSource/Items/Item.cs | 55 +- .../SharedSource/Items/ItemPrefab.cs | 139 +- .../SharedSource/Map/Explosion.cs | 63 +- .../BarotraumaShared/SharedSource/Map/Hull.cs | 15 +- .../SharedSource/Map/Levels/Level.cs | 40 +- .../Map/Levels/LevelObjects/LevelTrigger.cs | 4 +- .../SharedSource/Map/Map/Radiation.cs | 2 +- .../SharedSource/Map/MapEntity.cs | 9 +- .../SharedSource/Map/Structure.cs | 11 +- .../SharedSource/Map/Submarine.cs | 65 +- .../SharedSource/Map/SubmarineBody.cs | 3 +- .../SharedSource/Map/WayPoint.cs | 23 +- .../Networking/ChildServerRelay.cs | 4 +- .../NetEntityEvent/NetEntityEvent.cs | 3 + .../Networking/OrderChatMessage.cs | 5 +- .../SharedSource/Networking/RespawnManager.cs | 16 +- .../SharedSource/Networking/ServerSettings.cs | 2 +- .../Serialization/XMLExtensions.cs | 116 +- .../StatusEffects/StatusEffect.cs | 120 +- .../SharedSource/SteamAchievementManager.cs | 2 + .../SharedSource/Upgrades/UpgradePrefab.cs | 2 +- .../SharedSource/Utils/IdRemap.cs | 36 +- .../SharedSource/Utils/MathUtils.cs | 5 + .../SharedSource/Utils/Range.cs | 44 + .../SharedSource/Utils/SafeIO.cs | 5 + .../SharedSource/Utils/SaveUtil.cs | 14 +- .../SharedSource/Utils/ToolBox.cs | 62 +- .../SharedSource/Utils/UpdaterUtil.cs | 223 --- .../BarotraumaShared/Submarines/Humpback.sub | Bin 207698 -> 207745 bytes .../BarotraumaShared/Submarines/Remora.sub | Bin 279016 -> 279195 bytes .../Submarines/RemoraDrone.sub | Bin 280590 -> 262007 bytes Barotrauma/BarotraumaShared/changelog.txt | 53 + .../BarotraumaShared/serversettings.xml | 1 + .../Dynamics/World.cs | 3 + Libraries/XNATypes/RectangleF.cs | 551 ++++++ 245 files changed, 9775 insertions(+), 2034 deletions(-) create mode 100644 Barotrauma/BarotraumaClient/ClientSource/GUI/GUIScissorComponent.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/Shaders/blueprintshader.fx create mode 100644 Barotrauma/BarotraumaClient/Shaders/blueprintshader_opengl.fx create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityCondition.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/AbilityConditionHandsomeStranger.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/AbilityConditionReduceAffliction.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionScavenger.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/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/AbilityConditionInWater.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionMission.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/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/CharacterAbilityApplyStatusEffectsToNearestAlly.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffectsToRandomAlly.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveFlag.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveMissionCount.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/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/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/CustomAbilities/CharacterAbilityApprenticeship.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityBountyHunter.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityIndustrialRevolution.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/CharacterAbilityStonewall.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityTandemFire.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityTaskmaster.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/Utils/Range.cs delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/Utils/UpdaterUtil.cs create mode 100644 Libraries/XNATypes/RectangleF.cs 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..31fc357b6 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; @@ -130,7 +129,7 @@ namespace Barotrauma } public static bool IsMouseOnUI => GUI.MouseOn != null || - (CharacterInventory.IsMouseOnInventory() && !CharacterInventory.DraggingItemToWorld); + (CharacterInventory.IsMouseOnInventory && !CharacterInventory.DraggingItemToWorld); public class ObjectiveEntity { @@ -161,8 +160,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 +388,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) @@ -470,9 +463,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 +561,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 +572,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; @@ -638,7 +620,7 @@ namespace Barotrauma if (!enabled) { return; } - if (!IsDead && !IsIncapacitated) + if (!IsIncapacitated) { if (soundTimer > 0) { @@ -649,7 +631,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 +649,6 @@ namespace Barotrauma else { PlaySound(CharacterSound.SoundType.Idle); - } break; } @@ -827,12 +815,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 +844,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 +854,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 +908,40 @@ 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; + } + /// /// 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 +969,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 +987,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 +1040,20 @@ namespace Barotrauma Rand.Range(50.0f, 500.0f), null); } } + + partial void OnMoneyChanged(int prevAmount, int newAmount) + { + if (newAmount > prevAmount) + { + int increase = newAmount - prevAmount; + GUI.AddMessage( + "+" + TextManager.GetWithVariable("currencyformat", "[credits]", increase.ToString()), + GUI.Style.Yellow, + Position + Vector2.UnitY * 150.0f, + Vector2.UnitY * 10.0f, + playSound: true, + subId: Submarine?.ID ?? -1);; + } + } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterHUD.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterHUD.cs index b208ae945..89859485a 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterHUD.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterHUD.cs @@ -391,7 +391,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 +419,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); } } @@ -525,12 +544,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 +558,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 1d2892d72..c27aab975 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterInfo.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterInfo.cs @@ -170,15 +170,37 @@ namespace Barotrauma 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, + string.Format("+{0} {1}", increase, TextManager.Get("SkillName." + skillIdentifier)), + specialIncrease ? GUI.Style.Orange : GUI.Style.Green, textPopupPos, Vector2.UnitY * 10.0f, - playSound: false, + playSound: specialIncrease, + subId: Character?.Submarine?.ID ?? -1); + } + } + + partial void OnExperienceChanged(int prevAmount, int newAmount, Vector2 textPopupPos) + { + if (Character.Controlled != null && Character.Controlled.TeamID != TeamID) { return; } + + GameSession.TabMenuInstance?.OnExperienceChanged(Character); + + if (newAmount > prevAmount) + { + int increase = newAmount - prevAmount; + GUI.AddMessage( + string.Format("+{0} {1}", increase, TextManager.Get("experienceshort")), + GUI.Style.Blue, + textPopupPos, + Vector2.UnitY * 10.0f, + playSound: true, subId: Character?.Submarine?.ID ?? -1); } } @@ -591,6 +613,17 @@ 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); + } + return ch; } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterNetworking.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterNetworking.cs index 0798a00d3..34a5e6378 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, 12); switch (eventType) { case 0: //NetEntityEvent.Type.InventoryState @@ -387,6 +395,7 @@ namespace Barotrauma if (eventType == 4) { SetAttackTarget(attackLimb, targetEntity, targetSimPos); + PlaySound(CharacterSound.SoundType.Attack, maxInterval: 3); } else { @@ -450,6 +459,23 @@ 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++) + { + UInt32 talentIdentifier = msg.ReadUInt32(); + GiveTalent(talentIdentifier); + } + break; + case 12: //NetEntityEvent.Type.UpdateMoney: + int moneyAmount = msg.ReadInt32(); + SetMoney(moneyAmount); + break; + } msg.ReadPadBits(); break; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs index 4dc901c4f..d7b75bbd3 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs @@ -543,7 +543,7 @@ namespace Barotrauma else { var causeOfDeath = GetCauseOfDeath(); - Character.Controlled.Kill(causeOfDeath.First, causeOfDeath.Second); + Character.Controlled.Kill(causeOfDeath.type, causeOfDeath.affliction); Character.Controlled = null; } } @@ -683,19 +683,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); } @@ -1968,9 +1982,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 +2011,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 +2077,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 +2087,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); } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs b/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs index 89805c561..69c8a1836 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(); }); } })); @@ -608,7 +608,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,6 +689,9 @@ namespace Barotrauma AssignRelayToServer("setskill", true); AssignRelayToServer("readycheck", true); + AssignRelayToServer("givetalent", true); + AssignRelayToServer("giveexperience", true); + AssignOnExecute("control", (string[] args) => { if (args.Length < 1) return; diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs index 57e963b91..e3141a7b8 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs @@ -163,6 +163,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 +307,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 +673,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); @@ -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) { 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,7 +1056,7 @@ namespace Barotrauma { if (listBox.DraggedElement != null) { return CursorState.Dragging; } if (listBox.CanDragElements) { return CursorState.Move; } - + var hoverParent = c; while (true) { @@ -1059,14 +1065,14 @@ namespace Barotrauma 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 +1349,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 +1357,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 +1423,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 +1526,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) 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/GUIFrame.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIFrame.cs index 0e5d5ca42..8e5b77664 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIFrame.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIFrame.cs @@ -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/GUIListBox.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIListBox.cs index 9761bf213..ff2d3c4a7 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,8 +233,21 @@ 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; @@ -472,7 +485,7 @@ namespace Barotrauma if (!PlayerInput.PrimaryMouseButtonHeld()) { OnRearranged?.Invoke(this, draggedElement.UserData); - draggedElement = null; + DraggedElement = null; RepositionChildren(); } else @@ -518,6 +531,7 @@ namespace Barotrauma if (currIndex != index) { draggedElement.RectTransform.RepositionChildInHierarchy(currIndex); + HasDraggedElementIndexChanged = true; } return; @@ -577,7 +591,7 @@ namespace Barotrauma if (CanDragElements && PlayerInput.PrimaryMouseButtonDown() && GUI.MouseOn == child) { - draggedElement = child; + DraggedElement = child; draggedReferenceRectangle = child.Rect; draggedReferenceOffset = child.RectTransform.AbsoluteOffset; } @@ -750,7 +764,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 +787,6 @@ namespace Barotrauma ScrollBar.BarScroll -= (PlayerInput.ScrollWheelSpeed / 500.0f) * BarSize; } } - ScrollBar.Enabled = ScrollBarEnabled && BarSize < 1.0f; if (AutoHideScrollBar) @@ -785,6 +798,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 +1002,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(); } 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..cd921c935 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIStyle.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIStyle.cs @@ -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,11 +41,16 @@ 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 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 @@ -235,6 +241,9 @@ namespace Barotrauma case "uiglow": UIGlow = new UISprite(subElement); break; + case "pingcircle": + PingCircle = new UISprite(subElement); + break; case "radiation": RadiationSprite = new UISprite(subElement); break; @@ -247,6 +256,9 @@ namespace Barotrauma case "endroundbuttonpulse": ButtonPulse = new UISprite(subElement); break; + case "iconoverflowindicator": + IconOverflowIndicator = new UISprite(subElement); + break; case "focusindicator": FocusIndicator = new SpriteSheet(subElement); break; @@ -277,6 +289,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); 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..5a84a234f 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/LoadingScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/LoadingScreen.cs @@ -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 ..."; } 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..934f6b001 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,6 +591,10 @@ namespace Barotrauma resolutionWhenCreated = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight); } + private string GetMerchantBalanceText() => GetCurrencyFormatted(CurrentLocation?.StoreCurrentBalance ?? 0); + + private string GetPlayerBalanceText() => GetCurrencyFormatted(PlayerMoney); + private GUILayoutGroup CreateDealsGroup(GUIListBox parentList) { var elementHeight = (int)(GUI.yScale * 80); @@ -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(); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs index a5239b1b6..57c248466 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs @@ -18,7 +18,7 @@ namespace Barotrauma private static UISprite spectateIcon, disconnectedIcon; private static Sprite ownerIcon, moderatorIcon; - private enum InfoFrameTab { Crew, Mission, Reputation, MyCharacter, Traitor, Submarine }; + private enum InfoFrameTab { Crew, Mission, Reputation, MyCharacter, Traitor, Submarine, Talents }; private static InfoFrameTab selectedTab; private GUIFrame infoFrame, contentFrame; @@ -258,6 +258,8 @@ namespace Barotrauma { var myCharacterButton = createTabButton(InfoFrameTab.MyCharacter, "tabmenu.character"); } + + var talentsButton = createTabButton(InfoFrameTab.Talents, "tabmenu.talents"); } private bool SelectInfoFrameTab(GUIButton button, object userData) @@ -296,6 +298,9 @@ namespace Barotrauma case InfoFrameTab.Submarine: CreateSubmarineInfo(infoFrameHolder, Submarine.MainSub); break; + case InfoFrameTab.Talents: + CreateTalentInfo(infoFrameHolder); + break; } return true; @@ -1159,5 +1164,319 @@ 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, GUIImage background, GUIImage icon)> talentButtons = new List<(GUIButton button, GUIImage background, GUIImage icon)>(); + private List selectedTalents = new List(); + private GUITextBlock talentTitleText; + private GUITextBlock talentDescriptionText; + private GUITextBlock talentPointsText; + + private GUITextBlock experienceText; + private Color experienceBackgroundColor = new Color(255, 255, 255, 155); + + private GUIProgressBar experienceBar; + + private void CreateTalentInfo(GUIFrame infoFrame) + { + infoFrame.ClearChildren(); + talentButtons.Clear(); + + 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); + + Character controlledCharacter = Character.Controlled; + + if (controlledCharacter.Info == null) + { + DebugConsole.ThrowError("No character info found for talent UI"); + return; + } + + selectedTalents = controlledCharacter.Info.UnlockedTalents.ToList(); + + GUILayoutGroup talentFrameLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f), talentFrameContent.RectTransform, anchor: Anchor.Center), childAnchor: Anchor.TopCenter) + { + AbsoluteSpacing = GUI.IntScale(5) + }; + + GUILayoutGroup talentInfoLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.325f), talentFrameLayoutGroup.RectTransform, Anchor.Center), childAnchor: Anchor.TopCenter); + + GUIFrame talentTitleFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.25f), talentInfoLayoutGroup.RectTransform, Anchor.TopCenter), style: null); + + talentTitleText = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), talentTitleFrame.RectTransform, Anchor.TopLeft), "", font: GUI.LargeFont); + talentPointsText = new GUITextBlock(new RectTransform(new Vector2(0.25f, 1.0f), talentTitleFrame.RectTransform, Anchor.TopRight), "", font: GUI.Font, textAlignment: Alignment.Center); + + GUIFrame talentDescriptionFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.4f), talentInfoLayoutGroup.RectTransform, Anchor.TopCenter), style: null); + + talentDescriptionText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), talentDescriptionFrame.RectTransform, Anchor.TopLeft), "", font: GUI.Font, textAlignment: Alignment.TopLeft, wrap: true); + + GUIFrame characterInfoFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.3f), talentInfoLayoutGroup.RectTransform, Anchor.TopLeft), style: null); + GUILayoutGroup characterInfoColumn = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f), characterInfoFrame.RectTransform, anchor: Anchor.TopLeft), childAnchor: Anchor.TopLeft, isHorizontal: true); + + CreateCharacterSheet(characterInfoColumn); + + if (!TalentTree.JobTalentTrees.TryGetValue(controlledCharacter.Info.Job.Prefab.Identifier, out TalentTree talentTree)) { return; } + + GUIListBox talentTreeListBox = new GUIListBox(new RectTransform(new Vector2(1f, 0.6f), talentFrameLayoutGroup.RectTransform, Anchor.TopCenter), isHorizontal: true, style: null); + + int spacing = GUI.IntScale(5); + + 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: "SubtreeHeader"); + + new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), subtreeTitleFrame.RectTransform, anchor: Anchor.TopCenter), subTree.Identifier, font: GUI.LargeFont, 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: "TalentOptionFrame"); + GUIImage talentBackground = new GUIImage(new RectTransform(Vector2.One, talentOptionFrame.RectTransform, anchor: Anchor.Center), style: "TalentBackground") + { + CanBeFocused = false, + Color = unselectableColor, + }; + + if (subTree.TalentOptionStages.Count > i) + { + TalentOption talentOption = subTree.TalentOptionStages[i]; + + GUILayoutGroup talentOptionLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1f, 1f), talentOptionFrame.RectTransform, Anchor.CenterLeft), isHorizontal: true, childAnchor: Anchor.CenterLeft) + { + Stretch = true, + }; + + foreach (Talent talent in talentOption.Talents) + { + int optionPadding = GUI.IntScale(10); + GUIFrame talentFrame = new GUIFrame(new RectTransform(new Point(talentOptionFrame.Rect.Width, talentOptionFrame.Rect.Height - optionPadding), talentOptionLayoutGroup.RectTransform), style: null) + { + CanBeFocused = false, + }; + new GUIImage(new RectTransform(Vector2.One, talentFrame.RectTransform, anchor: Anchor.Center), style: "TalentFrameBackground") + { + CanBeFocused = false, + }; + GUIImage iconImage = null; + + GUIButton talentButton = new GUIButton(new RectTransform(Vector2.One, talentFrame.RectTransform, anchor: Anchor.Center), style: "TalentFrame") + { + ToolTip = $"{TextManager.Get("talentname." + talent.Identifier, returnNull: true) ?? talent.Identifier} \n\n{TextManager.Get("talentdescription." + talent.Identifier, returnNull: true) ?? string.Empty}", + UserData = talent.Identifier, + PressedColor = pressedColor, + OnClicked = (button, userData) => + { + talentTitleText.Text = TextManager.Get("talentname." + talent.Identifier, returnNull: true) ?? string.Empty; + talentDescriptionText.Text = TextManager.Get("talentdescription." + talent.Identifier, returnNull: true) ?? string.Empty; + + // 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; + }, + }; + + + int iconPadding = GUI.IntScale(15); + iconImage = new GUIImage(new RectTransform(new Point(talentFrame.Rect.Width - iconPadding, talentFrame.Rect.Height - iconPadding), talentFrame.RectTransform, anchor: Anchor.Center), sprite: talent.Icon, scaleToFit: true) + { + PressedColor = unselectableColor, + CanBeFocused = false, + }; + + talentButtons.Add((talentButton, talentBackground, iconImage)); + } + } + } + } + + GUIFrame talentBottomFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.07f), talentFrameLayoutGroup.RectTransform, Anchor.TopCenter), style: null); + + GUIFrame experienceBarFrame = new GUIFrame(new RectTransform(new Vector2(0.775f, 0.75f), talentBottomFrame.RectTransform, Anchor.TopCenter), style: null); + + experienceBar = new GUIProgressBar(new RectTransform(new Vector2(1f, 1f), experienceBarFrame.RectTransform, Anchor.CenterLeft), + barSize: controlledCharacter.Info.GetProgressTowardsNextLevel(), color: Color.White, style: "ExperienceBar") + { + IsHorizontal = true + }; + + GUIImage experienceTextBackground = new GUIImage(new RectTransform(new Vector2(0.2f, 0.475f), experienceBarFrame.RectTransform, anchor: Anchor.Center), style: "ExperienceTextBackground"); + + experienceText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), experienceTextBackground.RectTransform, anchor: Anchor.Center), "", font: GUI.Font, textColor: Color.White, textAlignment: Alignment.Center); + + new GUIButton(new RectTransform(new Vector2(0.1f, 0.3f), talentBottomFrame.RectTransform, anchor: Anchor.TopRight), text: TextManager.Get("applysettingsbutton")) + { + OnClicked = ApplyTalentSelection, + }; + + new GUIButton(new RectTransform(new Vector2(0.1f, 0.3f), talentBottomFrame.RectTransform, anchor: Anchor.TopLeft), text: TextManager.Get("reset")) + { + OnClicked = ResetTalentSelection, + }; + + UpdateTalentButtons(); + } + + private void UpdateTalentButtons() + { + Character controlledCharacter = Character.Controlled; + + talentPointsText.Text = $"{TextManager.Get("talentpointsleft")}{controlledCharacter.Info.GetAvailableTalentPoints()}"; + 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); + + foreach (var talentButton in talentButtons) + { + talentButton.background.Color = unselectableColor; + } + + 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; + + if (controlledCharacter.HasTalent(talentIdentifier)) + { + newTalentColor = ownedColor; + } + else if (selectedTalents.Contains(talentIdentifier)) + { + newTalentColor = selectedColor; + } + + talentButton.button.Color = newTalentColor; + talentButton.button.SelectedColor = newTalentColor; + talentButton.button.HoverColor = newTalentColor; + talentButton.button.DisabledColor = newTalentColor; + + talentButton.icon.Color = newTalentColor; + + // update background color as well, if not defined yet + if (talentButton.background.Color == unselectableColor) + { + talentButton.background.Color = newTalentColor; + } + } + } + + + 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.UnlockedTalents.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.MaximumHealthMultiplier, + StatTypes.MovementSpeed, + StatTypes.SwimmingSpeed, + StatTypes.RepairSpeed, + }; + + 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/GameMain.cs b/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs index 0dbbe81f8..e22ee76cb 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; @@ -239,7 +249,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; @@ -567,6 +576,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)); @@ -620,6 +631,7 @@ namespace Barotrauma #endif SubEditorScreen = new SubEditorScreen(); + TestScreen = new TestScreen(); TitleScreen.LoadState = 75.0f; yield return CoroutineStatus.Running; @@ -792,12 +804,16 @@ namespace Barotrauma } #if DEBUG - if (TitleScreen.LoadState >= 100.0f && !TitleScreen.PlayingSplashScreen && (Config.AutomaticQuickStartEnabled || Config.AutomaticCampaignLoadEnabled) && FirstLoad && !PlayerInput.KeyDown(Keys.LeftShift)) + if (TitleScreen.LoadState >= 100.0f && !TitleScreen.PlayingSplashScreen && (Config.AutomaticQuickStartEnabled || Config.AutomaticCampaignLoadEnabled || Config.TestScreenEnabled) && FirstLoad && !PlayerInput.KeyDown(Keys.LeftShift)) { loadingScreenOpen = false; FirstLoad = false; - if (Config.AutomaticQuickStartEnabled) + if (Config.TestScreenEnabled) + { + TestScreen.Select(); + } + else if (Config.AutomaticQuickStartEnabled) { MainMenuScreen.QuickStart(); } 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..ac4b7ed69 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; } @@ -92,10 +99,11 @@ namespace Barotrauma crewList = new GUIListBox(new RectTransform(Vector2.One, crewArea.RectTransform), style: null, isScrollBarOnDefaultSide: false) { AutoHideScrollBar = false, - CanBeFocused = false, + CanDragElements = 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 +188,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,19 +206,36 @@ namespace Barotrauma ReportButtonFrame.RectTransform.AbsoluteOffset = new Point(0, -chatBox.ToggleButton.Rect.Height); + CreateReports(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 CreateReports(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; } 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, @@ -218,6 +243,19 @@ namespace Barotrauma ClampMouseRectToParent = false }; + 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") { Color = GUI.Style.Red * 0.8f, @@ -236,13 +274,6 @@ namespace Barotrauma SpriteEffects = SpriteEffects.FlipHorizontally }; } - - #endregion - - screenResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight); - prevUIScale = GUI.Scale; - _isCrewMenuOpen = GameMain.Config.CrewMenuOpen; - dismissedOrderPrefab ??= Order.GetPrefab("dismissed"); } #endregion @@ -291,8 +322,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 +352,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 +365,6 @@ namespace Barotrauma CanBeFocused = false, UserData = "job" }; - if (character?.Info?.Job.Prefab?.Icon != null) { new GUIImage( @@ -362,36 +406,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 +501,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 +578,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,15 +704,15 @@ 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) @@ -748,7 +770,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 +1322,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 +1413,8 @@ namespace Barotrauma if (GUI.DisableHUD) { return; } + UpdateOrderDrag(); + #region Command UI WasCommandInterfaceDisabledThisUpdate = false; @@ -1756,7 +1855,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 +1890,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 @@ -2101,6 +2201,8 @@ namespace Barotrauma shortcutNodes.Remove(node); }; RemoveOptionNodes(); + bool wasMinimapVisible = targetFrame != null && targetFrame.Visible; + HideMinimap(); if (returnNode != null) { @@ -2111,12 +2213,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 +2236,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 +2257,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 +2306,7 @@ namespace Barotrauma } node.OnClicked = null; node.OnSecondaryClicked = null; + node.CanBeFocused = false; centerNode = node; } @@ -2219,6 +2323,7 @@ namespace Barotrauma } node.OnClicked = NavigateBackward; node.OnSecondaryClicked = null; + node.CanBeFocused = true; returnNode = node; } @@ -2240,9 +2345,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 +2420,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 +2431,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 +2524,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 +2545,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) @@ -2621,6 +2753,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( @@ -2694,7 +2827,7 @@ namespace Barotrauma if (disableNode) { node.CanBeFocused = icon.CanBeFocused = false; - CreateBlockIcon(node.RectTransform); + CreateBlockIcon(node.RectTransform, tooltip: TextManager.Get("nocharactercanhear")); } else if (hotkey >= 0) { @@ -2703,195 +2836,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)); } } @@ -2928,7 +3003,7 @@ namespace Barotrauma { node.CanBeFocused = false; if (icon != null) { icon.CanBeFocused = false; } - CreateBlockIcon(node.RectTransform); + CreateBlockIcon(node.RectTransform, tooltip: TextManager.Get("nocharactercanhear")); } else if (hotkey >= 0) { @@ -2990,9 +3065,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 +3251,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 +3343,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 +3465,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..99526244b 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/MultiPlayerCampaign.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/MultiPlayerCampaign.cs @@ -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..86db15f21 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameSession.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameSession.cs @@ -24,7 +24,7 @@ namespace Barotrauma if (GameMain.NetLobbyScreen.HeadSelectionList != null) { GameMain.NetLobbyScreen.HeadSelectionList.Visible = false; } 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,7 +34,6 @@ namespace Barotrauma tabMenu = null; NetLobbyScreen.JobInfoFrame = null; } - return true; } @@ -44,7 +43,7 @@ namespace Barotrauma private GUIComponent respawnInfoFrame, respawnButtonContainer; private GUITextBlock respawnInfoText; private GUITickBox respawnTickBox; - private GUILayoutGroup TopLeftButtonGroup; + private void CreateTopLeftButtons() { if (topLeftButtonGroup != null) diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSettings.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSettings.cs index 200d39720..00f03cf5b 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSettings.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSettings.cs @@ -1518,6 +1518,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, diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/CharacterInventory.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/CharacterInventory.cs index 516f62488..953594247 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; @@ -359,7 +359,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)) { continue; } + if (SlotTypes[i] == InvSlotType.RightHand || SlotTypes[i] == InvSlotType.LeftHand) { continue; } if (PersonalSlots.HasFlag(SlotTypes[i])) { //upperX -= slotSize.X + spacing; @@ -371,10 +372,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)) { continue; } if (PersonalSlots.HasFlag(SlotTypes[i])) { SlotPositions[i] = new Vector2(personalSlotX, personalSlotY); @@ -390,7 +399,8 @@ namespace Barotrauma x = lowerX; for (int i = 0; i < SlotPositions.Length; i++) { - if (!HideSlot(i)) continue; + if (!HideSlot(i)) { 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 +414,8 @@ namespace Barotrauma for (int i = 0; i < SlotPositions.Length; i++) { - if (HideSlot(i)) continue; + if (HideSlot(i)) { continue; } + if (SlotTypes[i] == InvSlotType.RightHand || SlotTypes[i] == InvSlotType.LeftHand) { continue; } if (PersonalSlots.HasFlag(SlotTypes[i])) { SlotPositions[i] = new Vector2(personalSlotX, personalSlotY); @@ -416,9 +427,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)) { continue; } SlotPositions[i] = new Vector2(x, GameMain.GraphicsHeight - bottomOffset); x += visualSlots[i].Rect.Width + Spacing; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Holdable/RangedWeapon.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Holdable/RangedWeapon.cs index ebe4c63e5..08ffe00cf 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Holdable/RangedWeapon.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Holdable/RangedWeapon.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using Barotrauma.IO; using System.Text; using System.Xml.Linq; +using Barotrauma.Sounds; namespace Barotrauma.Items.Components { @@ -18,7 +19,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 +53,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,6 +95,51 @@ 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; } @@ -92,7 +148,7 @@ namespace Barotrauma.Items.Components if (character.ViewTarget != null && (character.ViewTarget is Item item) && item.Prefab.FocusOnSelected) { 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..faaa76c27 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; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Deconstructor.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Deconstructor.cs index 711376751..24149ae69 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Deconstructor.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Deconstructor.cs @@ -126,7 +126,8 @@ namespace Barotrauma.Items.Components private void DrawOverLay(SpriteBatch spriteBatch, GUICustomComponent overlayComponent) { overlayComponent.RectTransform.SetAsLastChild(); - var lastSlot = inputContainer.Inventory.visualSlots.Last(); + if (!(inputContainer?.Inventory?.visualSlots is { } visualSlots)) { return; } + var lastSlot = visualSlots.Last(); GUI.DrawRectangle(spriteBatch, new Rectangle( diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs index e63a0fb84..f85f13a2a 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs @@ -255,12 +255,15 @@ 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); @@ -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", returnNull: true) ?? "Requires recipe to fabricate", 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,6 +368,16 @@ 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); @@ -601,7 +629,7 @@ namespace Barotrauma.Items.Components var itemPrefab = child.UserData as FabricationRecipe; if (itemPrefab == null) continue; - bool canBeFabricated = CanBeFabricated(itemPrefab, availableIngredients); + 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..0e9e7b897 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs @@ -1,63 +1,379 @@ -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 Component; + public readonly GUIComponent BorderComponent; + + public MiniMapGUIComponent(GUIComponent component) + { + Component = component; + BorderComponent = component; + } + + public MiniMapGUIComponent(GUIComponent frame, GUIComponent linkedHullComponent) + { + Component = frame; + BorderComponent = linkedHullComponent; + } + + public void Deconstruct(out GUIComponent component, out GUIComponent borderComponent) + { + component = Component; + 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, + HullCondition, + 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 ImmutableHashSet itemsFoundOnSub; + + private ImmutableHashSet? MiniMapBlips; + private float blipState; + private const float maxBlipState = 1f; + + private const float maxZoom = 2f, + 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 = Color.DarkCyan; + + private static readonly Color WetHullColor = new Color(9, 80, 159), + DefaultNeutralColor = MiniMapBaseColor * 0.8f, + HoverColor = Color.White, + BlueprintBlue = new Color(48, 87, 255), + HullWaterColor = new Color(85, 136, 147), + HullWaterLineColor = Color.LightBlue, + 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 EnableHullCondition => MiniMapMode.HullCondition, + true when EnableItemFinder => MiniMapMode.ItemFinder, + _ => MiniMapMode.None + }; + } + protected override void CreateGUI() { 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 }, DrawHUDBack, null); + GUIFrame paddedContainer = new GUIFrame(new RectTransform(new Vector2(0.95f, 0.9f), GuiFrame.RectTransform, Anchor.Center), style: null); + submarineContainer = new GUIFrame(new RectTransform(Vector2.One, paddedContainer.RectTransform, Anchor.Center), style: null); - new GUICustomComponent(new RectTransform(GuiFrame.Rect.Size - GUIStyle.ItemFrameMargin, GuiFrame.RectTransform, Anchor.Center) { AbsoluteOffset = GUIStyle.ItemFrameOffset }, - DrawHUDFront, null) + 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.2f), paddedContainer.RectTransform), isHorizontal: true); + + modeSwitchButtons = ImmutableArray.Create + ( + new GUIButton(new RectTransform(new Vector2(0.25f, 0.5f), 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, 0.5f), buttonLayout.RectTransform), string.Empty, style: "StatusMonitorButton.ElectricalView") { UserData = MiniMapMode.ElectricalView, Enabled = EnableHullCondition, ToolTip = TextManager.Get("StatusMonitorButton.ElectricalView.Tooltip") }, + new GUIButton(new RectTransform(new Vector2(0.25f, 0.5f), buttonLayout.RectTransform), string.Empty, style: "StatusMonitorButton.HullCondition") { UserData = MiniMapMode.HullCondition, Enabled = EnableHullCondition, ToolTip = TextManager.Get("StatusMonitorButton.HullCondition.Tooltip") }, + new GUIButton(new RectTransform(new Vector2(0.25f, 0.5f), 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), style: null) { CanBeFocused = false }; + + reportFrame = new GUILayoutGroup(new RectTransform(new Vector2(1), bottomFrame.RectTransform), isHorizontal: true) + { + AbsoluteSpacing = (int)(5 * GUI.Scale) + }; + + if (reports.Any()) + { + CrewManager.CreateReports(GameMain.GameSession?.CrewManager, reportFrame, reports, true); + } + + searchBarFrame = new GUILayoutGroup(new RectTransform(new Vector2(1), bottomFrame.RectTransform), isHorizontal: true, childAnchor: Anchor.Center); + 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 + }; + + SetTooltipPosition(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)) + { + CreateItemFrame(prefab, listBox.Content.RectTransform); + } + + searchBar.OnDeselected += (sender, key) => + { + searchAutoComplete.Visible = false; + }; + + searchBar.OnSelected += (sender, key) => + { + itemsFoundOnSub = Item.ItemList.Where(it => it.Submarine == item.Submarine && !it.NonInteractable && !it.HiddenInGame && it.Components.OfType().Any()).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 => { @@ -70,34 +386,141 @@ namespace Barotrauma.Items.Components { base.AddToGUIUpdateList(); hullInfoFrame.AddToGUIUpdateList(order: 1); + if (currentMode == MiniMapMode.ItemFinder && searchBar.Selected) + { + searchAutoComplete.AddToGUIUpdateList(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 }; + + miniMapFrame = CreateMiniMap(item.Submarine, miniMapContainer, MiniMapSettings.Default, null, out hullStatusComponents); + + IEnumerable pointsOfInterest = Item.ItemList.Where(it => it.Submarine == item.Submarine && !it.HiddenInGame && !it.NonInteractable && it.GetComponent() != null); + electricalFrame = CreateMiniMap(item.Submarine, miniMapContainer, new MiniMapSettings(createHullElements: false), pointsOfInterest, out electricalMapComponents); + + Dictionary electricChildren = new Dictionary(); + + foreach (var (entity, component) in electricalMapComponents) + { + GUIComponent parent = component.Component; + 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(); + + Rectangle parentRect = miniMapFrame.Rect; - item.Submarine.CreateMiniMap(submarineContainer); displayedSubs.Clear(); displayedSubs.Add(item.Submarine); displayedSubs.AddRange(item.Submarine.DockedTo); + + subEntities = MapEntity.mapEntityList.Where(me => me.Submarine == item.Submarine && !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 (PlayerInput.PrimaryMouseButtonDown()) + { + if (GUI.MouseOn == scissorComponent || scissorComponent.IsParentOf(GUI.MouseOn)) + { + dragMapStart = PlayerInput.MousePosition; + } + } + + float newZoom = Zoom; + + if (Math.Abs(PlayerInput.ScrollWheelSpeed) > 0 && (GUI.MouseOn == scissorComponent || scissorComponent.IsParentOf(GUI.MouseOn))) + { + newZoom = Math.Clamp(Zoom + PlayerInput.ScrollWheelSpeed / 1000.0f * Zoom, minZoom, maxZoom); + float distanceScale = newZoom / Zoom; + mapOffset *= distanceScale; + } + + recalculate |= !MathUtils.NearlyEqual(Zoom, newZoom); + Zoom = newZoom; + + Vector2 elementScale = new Vector2(Zoom); + + 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 |= PlayerInput.MouseSpeed != Vector2.Zero; + dragMap = true; + } + } + + var (maxWidth, maxHeight) = miniMapContainer.Rect.Size.ToVector2() / 2f / Zoom; + + mapOffset.X = Math.Clamp(mapOffset.X, -maxWidth, maxWidth); + mapOffset.Y = Math.Clamp(mapOffset.Y, -maxHeight, maxHeight); + + if (!PlayerInput.PrimaryMouseButtonHeld()) + { + dragMapStart = null; + dragMap = false; + } + + if (recalculate) + { + miniMapContainer.RectTransform.LocalScale = elementScale; + 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) + { + if (item.Submarine is { } sub) + { + BakeSubmarine(sub, miniMapFrame.Rect); + } + elementSize = GuiFrame.Rect.Size; + } + float distort = 1.0f - item.Condition / item.MaxCondition; foreach (HullData hullData in hullDatas.Values) { @@ -107,223 +530,717 @@ 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.HullCondition && !EnableHullCondition || + currentMode == MiniMapMode.ItemFinder && !EnableItemFinder) + { + SetDefaultMode(); + } + + modeSwitchButtons[0].Enabled = EnableHullStatus; + modeSwitchButtons[1].Enabled = EnableElectricalView; + modeSwitchButtons[2].Enabled = EnableHullCondition; + modeSwitchButtons[3].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) { + // TODO remove + if (currentMode == MiniMapMode.HullCondition) + { + const string wipText = "work in progress"; + Vector2 textSize = GUI.LargeFont.MeasureString(wipText); + Vector2 textPos = GuiFrame.Rect.Center.ToVector2(); + + GUI.DrawString(spriteBatch, textPos - textSize / 2, wipText.ToUpper(), GUI.Style.Orange, Color.Black * 0.8f, backgroundPadding: 8, font: GUI.LargeFont); + } + if (Voltage < MinVoltage) { 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); + GUI.DrawString(spriteBatch, textPos - textSize / 2, noPowerTip, noPowerColor, Color.Black * 0.8f, font: GUI.SubHeadingFont); return; } - if (!submarineContainer.Children.Any()) { return; } - foreach (GUIComponent child in submarineContainer.Children.FirstOrDefault()?.Children) + + if (currentMode == MiniMapMode.HullStatus) { - if (child.UserData is Hull hull) + Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle; + spriteBatch.End(); + spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable); + spriteBatch.GraphicsDevice.ScissorRectangle = submarineContainer.Rect; + + foreach (var (entity, component) in hullStatusComponents) { - 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; + if (!(entity is Hull hull)) { continue; } + if (!hullDatas.TryGetValue(hull, out HullData? hullData) || hullData is null) { continue; } + DrawHullCards(spriteBatch, hull, hullData, component.Component); } - } + + spriteBatch.End(); + spriteBatch.GraphicsDevice.ScissorRectangle = prevScissorRect; + spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable); + } } - private void DrawHUDBack(SpriteBatch spriteBatch, GUICustomComponent container) + private void ControlSearchTooltip(GUITextBox sender, Keys key) { - Hull mouseOnHull = null; - hullInfoFrame.Visible = false; + if (!searchAutoComplete.Visible) { return; } + GUIListBox listBox = searchAutoComplete.GetChild(); + if (listBox is null) { return; } - foreach (Hull hull in Hull.hullList) + if (key == Keys.Down) { - 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; - } + listBox.SelectNext(true, autoScroll: true); } - - if (Voltage < MinVoltage) + else if (key == Keys.Up) { - return; + listBox.SelectPrevious(true, autoScroll: true); } - - float scale = 1.0f; - HashSet subs = new HashSet(); - foreach (Hull hull in Hull.hullList) + else if (key == Keys.Enter) { - if (hull.Submarine == null) { continue; } - var hullFrame = submarineContainer.Children.FirstOrDefault()?.FindChild(hull); - if (hullFrame == null) { continue; } + listBox.OnSelected?.Invoke(listBox, listBox.SelectedData); + searchBar.Deselect(); + } + } - hullFrame.Visible = true; - if (!submarineContainer.Rect.Contains(hullFrame.Rect)) + private bool UpdateSearchTooltip(GUITextBox box, string text) + { + MiniMapBlips = null; + searchedPrefab = null; + searchAutoComplete.Visible = true; + SetTooltipPosition(searchAutoComplete, box); + + GUIListBox listBox = searchAutoComplete.GetChild(); + if (listBox 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 prefab && itemsFoundOnSub.Contains(prefab)) { - if (hull.Submarine.Info.Type != SubmarineType.Player) + component.Visible = prefab.Name.ToLower().Contains(text.ToLower()); + + if (component.Visible && first) { - hullFrame.Visible = false; - continue; + listBox.Select(i, force: true, autoScroll: false); + first = false; } } - hullDatas.TryGetValue(hull, out HullData hullData); - if (hullData == null) + i++; + } + + listBox.BarScroll = 0f; + listBox.RecalculateChildren(); + + return true; + } + + private void SetTooltipPosition(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); + new GUIImage(new RectTransform(Vector2.One, layout.RectTransform, scaleBasis: ScaleBasis.BothHeight), sprite) + { + Color = prefab.InventoryIconColor + }; + + new GUITextBlock(new RectTransform(Vector2.One, layout.RectTransform), prefab.Name, font: GUI.SubHeadingFont); + layout.UserData = prefab; + } + + private void SearchItems(string text) + { + if (searchedPrefab is null) + { + Console.WriteLine("Bruh"); + 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 (it.Submarine != item.Submarine) { continue; } + if (it.HiddenInGame || it.NonInteractable) { continue; } + if (it.GetComponent() is { Connections: { } conn} && conn.Any()) { continue; } + if (it.HasTag("traitormissionitem")) { 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) / Zoom; + positions.Add(pos); + } + + MiniMapBlips = positions.ToImmutableHashSet(); + + searchAutoComplete.Visible = false; + } + + private void UpdateHUDBack() + { + hullInfoFrame.Visible = false; + electricalFrame.Visible = false; + miniMapFrame.Visible = false; + reportFrame.Visible = false; + searchBarFrame.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() + { + foreach (var (entity, (component, borderComponent)) in hullStatusComponents) + { + if (item.Submarine == null || !hasPower) + { + component.Color = NoPowerColor; + 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 = 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)oxygenAmount + " %"); + 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)(waterAmount * 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++) + // 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 |= hullStatusComponents[linkedHull].Component == GUI.MouseOn; + if (isHoveringOver) { break; } } + + if (isHoveringOver) + { + 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.Component.Visible) { continue; } + + int durability = (int)(it.Condition / it.MaxCondition * 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) + { + Rectangle parentRect = container.Rect; + if (miniMapFrame is { } miniMap) { parentRect = miniMap.Rect; } + + DrawSubmarine(spriteBatch, parentRect); + } + + 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, miniMapFrame.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.Component; + + 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); + + GUI.DrawFilledRectangle(spriteBatch, waterRect, HullWaterColor); + GUI.DrawLine(spriteBatch, waterRect.Location, new Vector2(waterRect.Right, waterRect.Y), HullWaterLineColor); + } + } + + 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 parentRect) + { + Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle; + spriteBatch.End(); + if (submarinePreview is { } texture) + { + 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); + spriteBatch.Draw(texture, parentRect.Center.ToVector2(), null, blueprintBlue, 0f, origin, Zoom, 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; + + int i = 0; + foreach (MiniMapSprite info in cardsToDraw) + { + float spriteSize = info.Sprite.size.X * (frame.Rect.Height / info.Sprite.size.Y) + 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 = frame.Rect.Height / sprite.size.Y; + 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, 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 +1252,270 @@ 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(); + ImmutableHashSet hullList = ImmutableHashSet.Empty; + ImmutableDictionary> combinedHulls = ImmutableDictionary>.Empty; + + if (settings.CreateHullElements) + { + hullList = Hull.hullList.Where(IsPartofSub).ToImmutableHashSet(); + 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(ImmutableHashSet 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.ToImmutableHashSet()); + } + + private static MiniMapHullData ConstructHullPolygon(Hull mainHull, ImmutableHashSet 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); + } + + 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()); + } } } 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/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/Repairable.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Repairable.cs index 0099e8bfb..a770a45cb 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Repairable.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Repairable.cs @@ -15,6 +15,8 @@ namespace Barotrauma.Items.Components public GUIButton SabotageButton { get; private set; } + public GUIButton TinkerButton { get; private set; } + private GUIProgressBar progressBar; private List particleEmitters = new List(); @@ -25,6 +27,7 @@ namespace Barotrauma.Items.Components private string repairButtonText, repairingText; private string sabotageButtonText, sabotagingText; + private string tinkerButtonText, tinkeringText; private FixActions requestStartFixAction; @@ -46,7 +49,7 @@ 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))); + return item.ConditionPercentage < RepairThreshold || character.IsTraitor && item.ConditionPercentage > MinSabotageCondition || (CurrentFixer == character && (!item.IsFullCondition || (character.IsTraitor && item.ConditionPercentage > MinSabotageCondition))) || CanTinker(character); } partial void InitProjSpecific(XElement element) @@ -148,6 +151,20 @@ namespace Barotrauma.Items.Components return true; } }; + + tinkerButtonText = "Tinker"; + tinkeringText = "Tinkering"; + TinkerButton = new GUIButton(new RectTransform(new Vector2(0.8f, 0.15f), paddedFrame.RectTransform, Anchor.BottomCenter), tinkerButtonText, style: "GUIButtonSmall") + { + IgnoreLayoutGroups = true, + Visible = false, + OnClicked = (btn, obj) => + { + requestStartFixAction = FixActions.Tinker; + item.CreateClientEvent(this); + return true; + } + }; } partial void UpdateProjSpecific(float deltaTime) @@ -176,6 +193,7 @@ namespace Barotrauma.Items.Components { case FixActions.Repair: case FixActions.Sabotage: + case FixActions.Tinker: StartRepairing(Character.Controlled, requestStartFixAction); requestStartFixAction = FixActions.None; break; @@ -226,6 +244,13 @@ namespace Barotrauma.Items.Components sabotageButtonText : sabotagingText + new string('.', ((int)(Timing.TotalTime * 2.0f) % 3) + 1); + TinkerButton.Visible = CanTinker(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 && CanTinker(character)) ? + 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"); foreach (GUIComponent c in GuiFrame.GetChild(0).Children) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Terminal.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Terminal.cs index 353e4550b..fe20dca85 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Terminal.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Terminal.cs @@ -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 }; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Turret.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Turret.cs index f28cdf48b..e78bdf3b2 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Turret.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Turret.cs @@ -534,20 +534,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/Inventory.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs index e563f152d..21ac893a5 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs @@ -478,7 +478,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) @@ -667,6 +667,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) { @@ -826,11 +830,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 +1128,7 @@ namespace Barotrauma { Character.Controlled.ClearInputs(); - if (!IsMouseOnInventory(ignoreDraggedItem: true) && + if (!DetermineMouseOnInventory(ignoreDraggedItem: true) && CharacterHealth.OpenHealthWindow != null) { bool dropSuccessful = false; @@ -1306,7 +1322,9 @@ namespace Barotrauma 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(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs index d675203fd..237c062d3 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs @@ -332,7 +332,7 @@ namespace Barotrauma 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, @@ -368,7 +368,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, @@ -1623,6 +1623,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/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/Lights/LightManager.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightManager.cs index bb8e93052..e4c73b52c 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightManager.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightManager.cs @@ -238,13 +238,13 @@ 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); @@ -258,7 +258,7 @@ 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(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Submarine.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Submarine.cs index 28e2bbdb2..04701beb5 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; @@ -403,6 +404,8 @@ namespace Barotrauma } } + // TODO remove + [Obsolete("Use MiniMap.CreateMiniMap()")] public void CreateMiniMap(GUIComponent parent, IEnumerable pointsOfInterest = null, bool ignoreOutpost = false) { Rectangle worldBorders = GetDockedBorders(); @@ -417,24 +420,125 @@ namespace Barotrauma 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); + 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, (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 + const float padding = 0.001f; + size.X += padding; + size.Y += padding; + 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 +557,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(); 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..bc8cc1075 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)) { 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..7747a7b16 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); } @@ -487,6 +497,8 @@ namespace Barotrauma.Particles velocity.Y = Math.Sign(collisionNormal.Y) * Math.Abs(velocity.Y) * prefab.Restitution; } + OnCollision?.Invoke(position, currentHull); + velocity += subVel; } @@ -523,6 +535,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..182e826f9 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); 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.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI.cs index 7882cc77b..32a158105 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI.cs @@ -466,7 +466,7 @@ namespace Barotrauma { var sub = child.UserData as SubmarineInfo; if (sub == null) { return; } - child.Visible = string.IsNullOrEmpty(filter) ? true : sub.DisplayName.ToLower().Contains(filter.ToLower()); + child.Visible = string.IsNullOrEmpty(filter) || sub.DisplayName.ToLower().Contains(filter.ToLower()); } } 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/Wizard.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/CharacterEditor/Wizard.cs index 6d6d44024..e5a343cbe 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 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..a2420730e 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/GameScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/GameScreen.cs @@ -25,6 +25,7 @@ namespace Barotrauma public Effect PostProcessEffect { get; private set; } public Effect GradientEffect { get; private set; } public Effect GrainEffect { get; private set; } + public Effect BlueprintEffect { get; set; } public GameScreen(GraphicsDevice graphics, ContentManager content) { @@ -43,12 +44,14 @@ namespace Barotrauma PostProcessEffect = content.Load("Effects/postprocess_opengl"); GradientEffect = content.Load("Effects/gradientshader_opengl"); GrainEffect = content.Load("Effects/grainshader_opengl"); + BlueprintEffect = content.Load("Effects/blueprintshader_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"); + BlueprintEffect = content.Load("Effects/blueprintshader"); #endif damageStencil = TextureLoader.FromFile("Content/Map/walldamage.png"); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs index 7e9f8c97a..071349710 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs @@ -2020,7 +2020,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 +2029,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; 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/TestScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/TestScreen.cs new file mode 100644 index 000000000..0deb694c4 --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/TestScreen.cs @@ -0,0 +1,125 @@ +#nullable enable +using System; +using System.Linq; +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; + + 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(); + } + + // ???????? + submarine = new Submarine(SubmarineInfo.SavedSubmarines.FirstOrDefault(info => info.Name.Equals("Kastrull", StringComparison.OrdinalIgnoreCase))); + miniMapItem = new Item(ItemPrefab.Find(null, "statusmonitor"), Vector2.Zero, submarine); + MiniMap miniMap = miniMapItem.GetComponent(); + miniMap.PowerConsumption = 0; + + dummyCharacter = Character.Create(CharacterPrefab.HumanSpeciesName, Vector2.Zero, "", id: Entity.DummyID, hasAi: false); + dummyCharacter.Info.Name = "Galldren"; + dummyCharacter.Inventory.CreateSlots(); + + Character.Controlled = dummyCharacter; + GameMain.World.ProcessChanges(); + } + + public override void AddToGUIUpdateList() + { + Frame.AddToGUIUpdateList(); + CharacterHUD.AddToGUIUpdateList(dummyCharacter); + dummyCharacter?.SelectedConstruction?.AddToGUIUpdateList(); + } + + public override void Update(double deltaTime) + { + base.Update(deltaTime); + + if (dummyCharacter is { } dummy && miniMapItem is { } item) + { + if (dummy.SelectedConstruction != item) + { + dummy.SelectedConstruction = item; + } + dummy.SelectedConstruction?.UpdateHUD(Cam, dummy, (float)deltaTime); + Vector2 pos = FarseerPhysics.ConvertUnits.ToSimUnits(item.Position); + foreach (Limb limb in dummy.AnimController.Limbs) + { + limb.body.SetTransform(pos, 0.0f); + } + + if (dummy.AnimController?.Collider is { } collider) + { + collider.SetTransform(pos, 0); + } + + dummy.ControlLocalPlayer((float)deltaTime, Cam, false); + dummy.Control((float)deltaTime, Cam); + dummy.Submarine = submarine; + } + } + + 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/Sounds/SoundPlayer.cs b/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundPlayer.cs index de7051846..f6e679850 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) { @@ -851,7 +855,6 @@ namespace Barotrauma } } - int noiseLoopIndex = 1; if (Level.Loaded?.Type == LevelData.LevelType.LocationConnection) { // Find background noise loop for the current biome @@ -917,7 +920,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 +935,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 +952,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 +966,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); } 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/LinuxClient.csproj b/Barotrauma/BarotraumaClient/LinuxClient.csproj index 62c0cd40a..dac5130ed 100644 --- a/Barotrauma/BarotraumaClient/LinuxClient.csproj +++ b/Barotrauma/BarotraumaClient/LinuxClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.14.9.0 + 0.1500.0.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/MacClient.csproj b/Barotrauma/BarotraumaClient/MacClient.csproj index ac537775c..6d8757d5b 100644 --- a/Barotrauma/BarotraumaClient/MacClient.csproj +++ b/Barotrauma/BarotraumaClient/MacClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.14.9.0 + 0.1500.0.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/Shaders/Content.mgcb b/Barotrauma/BarotraumaClient/Shaders/Content.mgcb index 901a2171c..e88794f28 100644 --- a/Barotrauma/BarotraumaClient/Shaders/Content.mgcb +++ b/Barotrauma/BarotraumaClient/Shaders/Content.mgcb @@ -67,3 +67,9 @@ /processorParam:DebugMode=Auto /build:grainshader.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..16d516848 100644 --- a/Barotrauma/BarotraumaClient/Shaders/Content_opengl.mgcb +++ b/Barotrauma/BarotraumaClient/Shaders/Content_opengl.mgcb @@ -67,3 +67,8 @@ /processorParam:DebugMode=Auto /build:grainshader_opengl.fx +#begin blueprintshader_opengl.fx +/importer:EffectImporter +/processor:EffectProcessor +/processorParam:DebugMode=Auto +/build:blueprintshader_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/WindowsClient.csproj b/Barotrauma/BarotraumaClient/WindowsClient.csproj index c597f6633..bc67e5f17 100644 --- a/Barotrauma/BarotraumaClient/WindowsClient.csproj +++ b/Barotrauma/BarotraumaClient/WindowsClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.14.9.0 + 0.1500.0.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaServer/LinuxServer.csproj b/Barotrauma/BarotraumaServer/LinuxServer.csproj index 4c90c5c2e..d563f8dfa 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.0 + 0.1500.0.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/MacServer.csproj b/Barotrauma/BarotraumaServer/MacServer.csproj index 403968026..79640545b 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.0 + 0.1500.0.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/ServerSource/Characters/Character.cs b/Barotrauma/BarotraumaServer/ServerSource/Characters/Character.cs index acf0f00de..f9f51b713 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Characters/Character.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Characters/Character.cs @@ -46,5 +46,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..148bfeda3 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 { @@ -22,6 +23,14 @@ namespace Barotrauma } } + partial void OnExperienceChanged(int prevAmount, int newAmount, Vector2 textPopupPos) + { + if (Math.Abs(prevAmount - newAmount) > 0) + { + GameMain.NetworkMember.CreateEntityEvent(Character, new object[] { NetEntityEvent.Type.UpdateExperience }); + } + } + public void ServerWrite(IWriteMessage msg) { msg.Write(ID); @@ -53,6 +62,17 @@ 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); + } + } } } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs b/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs index bcd25ddf9..f5c86cae2 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 = 12; switch ((NetEntityEvent.Type)extraData[0]) { case NetEntityEvent.Type.InventoryState: @@ -394,6 +421,22 @@ 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.Prefab.UIntIdentifier); + } + break; + case NetEntityEvent.Type.UpdateMoney: + msg.WriteRangedInteger(12, min, max); + msg.Write(GameMain.GameSession.Campaign.Money); + break; default: DebugConsole.ThrowError("Invalid NetworkEvent type for entity " + ToString() + " (" + (NetEntityEvent.Type)extraData[0] + ")"); break; @@ -499,7 +542,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..9401b8cfb 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(); })); 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..21c9f7c8e 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/CharacterCampaignData.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/CharacterCampaignData.cs @@ -56,6 +56,6 @@ namespace Barotrauma public void ApplyOrderData(Character character) { CharacterInfo.ApplyOrderData(character, OrderData); - } + } } } 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/Item.cs b/Barotrauma/BarotraumaServer/ServerSource/Items/Item.cs index ce354ecaf..c393668e2 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Items/Item.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Items/Item.cs @@ -289,6 +289,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..2df0be84f 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs @@ -2354,6 +2354,8 @@ namespace Barotrauma.Networking characterData.ApplyHealthData(spawnedCharacter); characterData.ApplyOrderData(spawnedCharacter); spawnedCharacter.GiveIdCardTags(mainSubWaypoints[i]); + spawnedCharacter.LoadTalents(); + characterData.HasSpawned = true; } spawnedCharacter.OwnerClientEndPoint = teamClients[i].Connection.EndPointString; @@ -2366,6 +2368,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 +2435,7 @@ namespace Barotrauma.Networking roundStartTime = DateTime.Now; + startGameCoroutine = null; yield return CoroutineStatus.Success; } @@ -2619,8 +2624,8 @@ namespace Barotrauma.Networking } } - Submarine.Unload(); entityEventManager.Clear(); + Submarine.Unload(); GameMain.NetLobbyScreen.Select(); Log("Round ended.", ServerLog.MessageType.ServerMessage); @@ -3145,28 +3150,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)); } } 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..d162db6f0 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/RespawnManager.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/RespawnManager.cs @@ -284,6 +284,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 +302,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 +357,21 @@ 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 && !matchingData.HasSpawned) + { + forceSpawnInMainSub = true; + } + } + + 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) { 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 d5b2ff207..879a52022 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.0 + 0.1500.0.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.9.xml b/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.9.xml index 19f21b7a6..f5c2f7d19 100644 --- a/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.9.xml +++ b/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.9.xml @@ -63,6 +63,11 @@ + + + + + @@ -147,6 +152,12 @@ + + + + + + @@ -261,4 +272,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..168edfa9e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AIController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AIController.cs @@ -1,7 +1,8 @@ -using Microsoft.Xna.Framework; +using Barotrauma.Items.Components; +using Barotrauma.Networking; +using Microsoft.Xna.Framework; using System; using System.Collections.Generic; -using Barotrauma.Items.Components; using System.Linq; namespace Barotrauma @@ -328,5 +329,8 @@ namespace Barotrauma 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/EnemyAIController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs index ba65f0c2c..6a86228e6 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; @@ -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,6 +57,8 @@ namespace Barotrauma private readonly float updateTargetsInterval = 1; private readonly float updateMemoriesInverval = 1; private readonly float attackLimbResetInterval = 2; + // Min priority for the memorized targets. The actual value fades gradually, unless kept fresh by selecting the target. + private const float minPriority = 10; private readonly float avoidLookAheadDistance; @@ -394,7 +403,7 @@ 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); } @@ -641,7 +650,7 @@ 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; } @@ -976,7 +985,7 @@ namespace Barotrauma Character owner = GetOwner(item); if (owner != null) { - if (IsFriendly(Character, owner)) + if (Character.IsFriendly(owner)) { ResetAITarget(); State = AIState.Idle; @@ -1337,7 +1346,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 +1365,7 @@ namespace Barotrauma { hitTarget = limb.character; } - if (hitTarget != null && !hitTarget.IsDead && IsFriendly(Character, hitTarget)) + if (hitTarget != null && !hitTarget.IsDead && Character.IsFriendly(hitTarget)) { return true; } @@ -1764,7 +1773,7 @@ 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); + bool isFriendly = Character.IsFriendly(attacker); if (wasLatched) { State = AIState.Escape; @@ -1840,7 +1849,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 @@ -1884,16 +1893,15 @@ namespace Barotrauma 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; } } @@ -1902,23 +1910,35 @@ namespace Barotrauma { //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) { +#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) { // 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 { @@ -2125,6 +2145,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 +2162,7 @@ namespace Barotrauma } else { - if (IsFriendly(Character, targetCharacter)) + if (Character.IsFriendly(targetCharacter)) { continue; } @@ -2449,7 +2472,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; } } @@ -2469,7 +2492,7 @@ namespace Barotrauma // -> just ignore the distance and attack whatever has the highest priority dist = Math.Max(dist, 100.0f); - AITargetMemory targetMemory = GetTargetMemory(aiTarget, true); + AITargetMemory targetMemory = GetTargetMemory(aiTarget, addIfNotFound: true); if (Character.CurrentHull != null && Math.Abs(toTarget.Y) > Character.CurrentHull.Size.Y) { // Inside the sub, treat objects that are up or down, as they were farther away. @@ -2527,7 +2550,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 +2622,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++) @@ -2847,10 +2870,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; } @@ -3014,7 +3042,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 +3079,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 +3090,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); } } } @@ -3306,7 +3334,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..a67aa81f3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs @@ -915,10 +915,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); @@ -1415,7 +1415,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 +1510,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 +1971,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 +1991,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 +2013,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..918287729 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/IndoorsSteeringManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/IndoorsSteeringManager.cs @@ -161,12 +161,13 @@ namespace Barotrauma private Vector2 CalculateSteeringSeek(Vector2 target, float weight, Func startNodeFilter = null, Func endNodeFilter = null, Func nodeFilter = null, bool checkVisibility = true) { Vector2 targetDiff = target - currentTarget; - if (currentPath != null && currentPath.Nodes.Any()) + if (currentPath != null && currentPath.Nodes.Any() && character.Submarine != null) { - //current path calculated relative to a different sub than where the character is now + //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?.Nodes.First().Submarine; - if (currentPathSub != character.Submarine && character.Submarine != null) + 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; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs index fa7112563..79842539a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs @@ -101,11 +101,11 @@ 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; } @@ -958,14 +958,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; } @@ -1003,7 +1004,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..a28df6cf9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFightIntruders.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFightIntruders.cs @@ -63,6 +63,7 @@ namespace Barotrauma 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..012c3a858 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindDivingGear.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindDivingGear.cs @@ -57,7 +57,20 @@ 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 { @@ -71,9 +84,13 @@ namespace Barotrauma { 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 +122,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 +138,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 +153,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(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveIdle.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveIdle.cs index 1b7b68af7..3be3dab7e 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()) { @@ -255,11 +254,10 @@ namespace Barotrauma var path = PathSteering.PathFinder.FindPath(character.SimPosition, currentTarget.SimPosition, errorMsgStr: null, 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,30 +269,19 @@ 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()) { Wander(deltaTime); 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..21d8cebe4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PathFinder.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PathFinder.cs @@ -97,64 +97,51 @@ namespace Barotrauma foreach (WayPoint wp in wayPoints) { - wp.linkedTo.CollectionChanged += WaypointLinksChanged; + wp.OnLinksChanged += WaypointLinksChanged; } IndoorsSteering = indoorsSteering; } - 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) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/FishAnimController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/FishAnimController.cs index 0ca3c9c1c..475a16a08 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/FishAnimController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/FishAnimController.cs @@ -219,6 +219,7 @@ namespace Barotrauma if (character.SelectedCharacter != null) { DragCharacter(character.SelectedCharacter, deltaTime); + return; } //don't flip when simply physics is enabled diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs index 7e04b454f..1e910d566 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs @@ -938,19 +938,23 @@ 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) @@ -965,9 +969,7 @@ 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); } @@ -1622,7 +1624,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 +1684,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); } @@ -1750,8 +1755,9 @@ namespace Barotrauma Vector2 diff = holdable.Aimable ? (mousePos - AimSourceSimPos) * Dir : Vector2.UnitX; holdAngle = MathUtils.VectorToAngle(new Vector2(diff.X, diff.Y * Dir)) - torso.body.Rotation * Dir; + holdAngle += GetAimWobble(rightHand, leftHand, item); - itemAngle = (torso.body.Rotation + holdAngle * Dir); + itemAngle = torso.body.Rotation + holdAngle * Dir; if (holdable.ControlPose) { @@ -1869,6 +1875,26 @@ namespace Barotrauma } } + 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; + } + private void HandIK(Limb hand, Vector2 pos, float force = 1.0f) { Vector2 shoulderPos; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Attack.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Attack.cs index c89b02b76..dc2f44f07 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Attack.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Attack.cs @@ -74,6 +74,20 @@ namespace Barotrauma } } + class AttackData + { + public float DamageMultiplier { get; set; } = 1f; + public float AddedPenetration { get; set; } = 0f; + public List Afflictions { get; set; } + public Attack SourceAttack { get; } + + public AttackData(Attack sourceAttack) + { + SourceAttack = sourceAttack; + } + + } + partial class Attack : ISerializableEntity { [Serialize(AttackContext.Any, true, description: "The attack will be used only in this context."), Editable] @@ -271,6 +285,9 @@ namespace Barotrauma statusEffect.SetUser(user); } } + + // used for talents/ability conditions + public Item SourceItem { get; } public List GetMultipliedAfflictions(float multiplier) { @@ -320,6 +337,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); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs index 8c2506cc9..c97e76569 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs @@ -461,10 +461,7 @@ namespace Barotrauma } } - public bool AllowInput - { - get { return Stun <= 0.0f && !IsDead && !IsIncapacitated; } - } + public bool AllowInput => !Removed && !IsIncapacitated && Stun <= 0.0f; public bool CanMove { @@ -476,10 +473,10 @@ namespace Barotrauma } } - public bool CanInteract - { - get { return AllowInput && IsHumanoid && !LockHands && !Removed && !IsIncapacitated; } - } + public bool CanInteract => AllowInput && IsHumanoid && !LockHands; + + // 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 { @@ -773,8 +770,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 @@ -1378,6 +1373,12 @@ namespace Barotrauma 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 +1393,8 @@ namespace Barotrauma } } - foreach (Affliction affliction in CharacterHealth.GetAllAfflictions()) - { - skillLevel *= affliction.GetSkillMultiplier(); - } + skillLevel += GetStatValue(GetSkillStatType(skillIdentifier)); + return skillLevel; } @@ -1432,9 +1431,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 +1594,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 +1627,7 @@ namespace Barotrauma float max; if (AnimController is HumanoidAnimController) { - max = AnimController.InWater ? 0.5f : 0.7f; + max = AnimController.InWater ? 0.5f : 0.8f; } else { @@ -2044,7 +2043,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 +2058,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; } @@ -2365,9 +2364,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)) @@ -2445,7 +2444,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 +2623,11 @@ namespace Barotrauma UpdateAttackers(deltaTime); + foreach (var characterTalent in characterTalents) + { + characterTalent.UpdateTalent(deltaTime); + } + if (IsDead) { return; } if (GameMain.NetworkMember != null) @@ -2712,6 +2716,9 @@ 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 wasRagdolled = false; + bool selfRagdolled = false; + if (IsForceRagdolled) { IsRagdolled = IsForceRagdolled; @@ -2730,12 +2737,17 @@ namespace Barotrauma } else { - bool wasRagdolled = IsRagdolled; - IsRagdolled = IsKeyDown(InputType.Ragdoll); //Handle this here instead of Control because we can stop being ragdolled ourselves + 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.25f; } } } + if (!wasRagdolled && IsRagdolled && selfRagdolled) + { + CheckTalents(AbilityEffectType.OnSelfRagdoll); + } + lowPassMultiplier = MathHelper.Lerp(lowPassMultiplier, 1.0f, 0.1f); //ragdoll button @@ -3092,6 +3104,12 @@ namespace Barotrauma OrderInfo newOrderInfo = new OrderInfo(order, orderOption, priority); AddCurrentOrder(newOrderInfo); + + if (orderGiver != null) + { + orderGiver.CheckTalents(AbilityEffectType.OnGiveOrder, this); + } + if (AIController is HumanAIController humanAI) { humanAI.SetOrder(order, orderOption, priority, orderGiver, speak); @@ -3337,9 +3355,25 @@ namespace Barotrauma float attackImpulse = attack.TargetImpulse + attack.TargetForce * deltaTime; + AttackData attackData = new AttackData(attack); + attacker.CheckTalents(AbilityEffectType.OnAttack, attackData); + CheckTalents(AbilityEffectType.OnAttacked, attackData); + attackData.DamageMultiplier *= (1 + attacker.GetStatValue(StatTypes.AttackMultiplier)); + + 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 (limbHit == null) { return new AttackResult(); } Vector2 forceWorld = attack.TargetImpulseWorld + attack.TargetForceWorld; @@ -3457,6 +3491,11 @@ namespace Barotrauma public void RecordKill(Character target) { + foreach (Character attackerCrewmember in GetFriendlyCrew(this)) + { + attackerCrewmember.CheckTalents(AbilityEffectType.OnCrewKillCharacter, target); + } + if (!IsOnPlayerTeam) { return; } if (GameMain.Config.KilledCreatures.Any(name => name.Equals(target.SpeciesName, StringComparison.OrdinalIgnoreCase))) { return; } GameMain.Config.KilledCreatures.Add(target.SpeciesName); @@ -3524,7 +3563,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 +3590,9 @@ namespace Barotrauma ApplyStatusEffects(ActionType.OnDamaged, 1.0f); hitLimb.ApplyStatusEffects(ActionType.OnDamaged, 1.0f); } + + attacker?.CheckTalents(AbilityEffectType.OnAttackResult, attackResult); + return attackResult; } @@ -3775,6 +3817,8 @@ namespace Barotrauma causeOfDeathAffliction?.Source ?? LastAttacker, LastDamageSource); OnDeath?.Invoke(this, CauseOfDeath); + CheckTalents(AbilityEffectType.OnDieToCharacter, CauseOfDeath.Killer); + if (GameMain.GameSession != null && Screen.Selected == GameMain.GameScreen) { SteamAchievementManager.OnCharacterKilled(this, CauseOfDeath); @@ -3782,7 +3826,11 @@ namespace Barotrauma KillProjSpecific(causeOfDeath, causeOfDeathAffliction, log); - if (info != null) { info.CauseOfDeath = CauseOfDeath; } + if (info != null) + { + info.CauseOfDeath = CauseOfDeath; + info.ResetSavedStatValues(); + } AnimController.movement = Vector2.Zero; AnimController.TargetMovement = Vector2.Zero; @@ -3805,6 +3853,11 @@ namespace Barotrauma if (GameMain.GameSession != null) { + if (GameMain.GameSession.Campaign != null && TeamID == CharacterTeamType.Team1 && !IsAssistant) + { + GameMain.GameSession.Campaign.CrewHasDied = true; + } + GameMain.GameSession.KillCharacter(this); } } @@ -4203,8 +4256,249 @@ 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); + } + + private bool GiveTalent(TalentPrefab talentPrefab, bool addingFirstTime = true) + { + if (addingFirstTime) + { + if (!info.UnlockedTalents.Add(talentPrefab.Identifier)) { return false; } + } + + DebugConsole.AddWarning("added " + talentPrefab.OriginalName); + CharacterTalent characterTalent = new CharacterTalent(talentPrefab, this); + characterTalent.ActivateTalent(addingFirstTime); + characterTalents.Add(characterTalent); + +#if SERVER + GameMain.NetworkMember.CreateEntityEvent(this, new object[] { NetEntityEvent.Type.UpdateTalents }); +#endif + + 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 void CheckTalents(AbilityEffectType abilityEffectType, object abilityData) + { + foreach (var characterTalent in characterTalents) + { + characterTalent.CheckTalent(abilityEffectType, abilityData); + } + } + + public void CheckTalents(AbilityEffectType abilityEffectType) + { + foreach (var characterTalent in characterTalents) + { + characterTalent.CheckTalent(abilityEffectType, (object)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); + + /// + /// 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(); + + 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); + } + + //replace by updating the character wearable stat values when equipping or unequipping wearables + for (int i = 0; i < Inventory.Capacity; i++) + { + if (Inventory.SlotTypes[i] != InvSlotType.Any && Inventory.GetItemAt(i)?.GetComponent() is Wearable wearable) + { + if (wearable.WearableStatValues.TryGetValue(statType, out float wearableValue)) + { + statValue += wearableValue; + } + } + } + + return statValue; + } + + public void ChangeStat(StatTypes statType, float value) + { + if (statValues.ContainsKey(statType)) + { + statValues[statType] += value; + } + else + { + statValues.Add(statType, value); + } + } + + private 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); + } + + private readonly Dictionary abilityResistances = new Dictionary(); + + public float GetAbilityResistance(string resistanceId) + { + return abilityResistances.TryGetValue(resistanceId, out float value) ? value : 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..9526ce3a4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using Barotrauma.IO; using System.Linq; using System.Xml.Linq; +using Barotrauma.Abilities; namespace Barotrauma { @@ -215,6 +216,12 @@ namespace Barotrauma public int Salary; + public int ExperiencePoints { get; private set; } + + public HashSet UnlockedTalents { get; private set; } = new HashSet(); + + public int AdditionalTalentPoints { get; private set; } + private Sprite headSprite; public Sprite HeadSprite { @@ -529,6 +536,8 @@ namespace Barotrauma OriginalName = infoElement.GetAttributeString("originalname", null); string genderStr = infoElement.GetAttributeString("gender", "male").ToLowerInvariant(); Salary = infoElement.GetAttributeInt("salary", 1000); + ExperiencePoints = infoElement.GetAttributeInt("experiencepoints", 0); + UnlockedTalents = new HashSet(infoElement.GetAttributeStringArray("unlockedtalents", new string[0], convertToLowerInvariant: true)); Enum.TryParse(infoElement.GetAttributeString("race", "White"), true, out Race race); Enum.TryParse(infoElement.GetAttributeString("gender", "None"), true, out Gender gender); _speciesName = infoElement.GetAttributeString("speciesname", null); @@ -599,10 +608,38 @@ namespace Barotrauma } 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(); @@ -939,6 +976,15 @@ namespace Barotrauma Job.IncreaseSkillLevel(skillIdentifier, increase); float newLevel = Job.GetSkillLevel(skillIdentifier); + if ((int)newLevel > (int)prevLevel) + { + Character.CheckTalents(AbilityEffectType.OnGainSkillPoint, skillIdentifier); + + foreach (Character character in Character.GetFriendlyCrew(Character)) + { + character.CheckTalents(AbilityEffectType.OnAllyGainSkillPoint, (skillIdentifier, Character)); + } + } OnSkillChanged(skillIdentifier, prevLevel, newLevel, pos); } @@ -963,6 +1009,90 @@ namespace Barotrauma partial void OnSkillChanged(string skillIdentifier, float prevLevel, float newLevel, Vector2 textPopupPos); + public void GiveExperience(int amount, float popupOffset = 0f, bool isMissionExperience = false) + { + int prevAmount = ExperiencePoints; + + var experienceGainMultiplier = new AbilityValue(1f); + if (isMissionExperience) + { + Character.CheckTalents(AbilityEffectType.OnGainMissionExperience, experienceGainMultiplier); + } + experienceGainMultiplier.Value += Character.GetStatValue(StatTypes.ExperienceGainMultiplier); + + amount = (int)(amount * experienceGainMultiplier.Value); + + if (amount < 0) { return; } + + ExperiencePoints += amount; + OnExperienceChanged(prevAmount, ExperiencePoints, Character.Position + Vector2.UnitY * (150.0f + popupOffset)); + } + + public void SetExperience(int newExperience) + { + if (newExperience < 0) { return; } + + int prevAmount = ExperiencePoints; + ExperiencePoints = newExperience; + OnExperienceChanged(prevAmount, ExperiencePoints, Character.Position + Vector2.UnitY * 150.0f); + } + + const int BaseExperienceRequired = 150; + const int AddedExperienceRequiredPerLevel = 350; + + public int GetTotalTalentPoints() + { + return GetCurrentLevel() + AdditionalTalentPoints - 1; + } + + public int GetAvailableTalentPoints() + { + // hashset always has at least 1 + return Math.Max(GetTotalTalentPoints() - UnlockedTalents.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, Vector2 textPopupPos); + public void Rename(string newName) { if (string.IsNullOrEmpty(newName)) { return; } @@ -999,6 +1129,8 @@ namespace Barotrauma new XAttribute("gender", Head.gender == Gender.Male ? "male" : "female"), new XAttribute("race", Head.race.ToString()), new XAttribute("salary", Salary), + new XAttribute("experiencepoints", ExperiencePoints), + new XAttribute("unlockedtalents", string.Join(",", UnlockedTalents)), new XAttribute("headspriteid", HeadSpriteId), new XAttribute("hairindex", HairIndex), new XAttribute("beardindex", BeardIndex), @@ -1020,6 +1152,24 @@ 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; } + + 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; } @@ -1332,5 +1482,68 @@ namespace Barotrauma Portrait = null; AttachmentSprites = null; } + + // This could maybe be a LookUp instead? + private readonly Dictionary> savedStatValues = new Dictionary>(); + + public void ResetSavedStatValues() + { + foreach (var savedStatValue in savedStatValues.SelectMany(s => s.Value)) + { + if (savedStatValue.RemoveOnDeath) + { + savedStatValue.StatValue = 0f; + } + } + } + + public void ResetSavedStatValue(string statIdentifier) + { + savedStatValues.SelectMany(s => s.Value).Where(s => s.StatIdentifier == statIdentifier).ForEach(v => v.StatValue = 0f); + } + + public float GetSavedStatValue(StatTypes statType) + { + if (savedStatValues.TryGetValue(statType, out var statValues)) + { + return statValues.Sum(v => v.StatValue); + } + else + { + return 0f; + } + } + + public void ChangeSavedStatValue(StatTypes statType, float value, string statIdentifier, bool removeOnDeath) + { + if (!savedStatValues.ContainsKey(statType)) + { + savedStatValues.Add(statType, new List()); + } + + if (savedStatValues[statType].FirstOrDefault(s => s.StatIdentifier == statIdentifier) is SavedStatValue savedStat) + { + savedStat.StatValue += value; + savedStat.RemoveOnDeath = removeOnDeath; + } + else + { + savedStatValues[statType].Add(new SavedStatValue(statIdentifier, value, removeOnDeath)); + } + } + } + + public class SavedStatValue + { + public string StatIdentifier { get; set; } + public float StatValue { get; set; } + public bool RemoveOnDeath { get; set; } + + public SavedStatValue(string statIdentifier, float value, bool removeOnDeath) + { + StatValue = value; + RemoveOnDeath = removeOnDeath; + StatIdentifier = statIdentifier; + } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/Affliction.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/Affliction.cs index 82f03ec56..96b550b47 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/Affliction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/Affliction.cs @@ -237,6 +237,26 @@ namespace Barotrauma (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; + } + + 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) @@ -264,7 +284,11 @@ namespace Barotrauma 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 { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionHusk.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionHusk.cs index 10907fe96..82f12e892 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionHusk.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionHusk.cs @@ -212,6 +212,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 +248,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..619fb81f0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionPrefab.cs @@ -5,6 +5,7 @@ using System.Reflection; using System.Xml.Linq; using System.Linq; using System.Security.Cryptography; +using Barotrauma.Abilities; namespace Barotrauma { @@ -91,9 +92,11 @@ 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); } // Use any of these to define which limb the appendage is attached to. @@ -106,9 +109,11 @@ namespace Barotrauma 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 +121,93 @@ 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 MinScreenBlurStrength { get; private set; } - public float MinSkillMultiplier, MaxSkillMultiplier; + [Serialize(0.0f, false)] + public float MaxScreenBlurStrength { get; private set; } - public float MinResistance, MaxResistance; - public string ResistanceFor; - public string DialogFlag; + [Serialize(0.0f, false)] + public float MinScreenDistortStrength { get; private set; } + + [Serialize(0.0f, false)] + public float MaxScreenDistortStrength { get; private set; } + + [Serialize(0.0f, false)] + public float MinRadialDistortStrength { get; private set; } + + [Serialize(0.0f, false)] + public float MaxRadialDistortStrength { get; private set; } + + [Serialize(0.0f, false)] + public float MinChromaticAberrationStrength { get; private set; } + + [Serialize(0.0f, false)] + public float MaxChromaticAberrationStrength { get; private set; } + + [Serialize(0.0f, false)] + public float MinGrainStrength { get; private set; } + + [Serialize(0.0f, false)] + public float MaxGrainStrength { 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; } + + [Serialize("", false)] + public string ResistanceFor { get; private set; } + + [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; } + + public readonly Dictionary AfflictionStatValues = new Dictionary(); //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); - - 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); + SerializableProperty.DeserializeProperties(this, element); foreach (XElement subElement in element.Elements()) { @@ -201,6 +216,15 @@ 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; } } } @@ -590,7 +614,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)); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs index ad2d820c5..a4908115e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs @@ -116,15 +116,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; @@ -151,6 +151,7 @@ namespace Barotrauma max += Character.Info.Job.Prefab.VitalityModifier; } max *= Character.StaticHealthMultiplier; + max *= 1f + Character.GetStatValue(StatTypes.MaximumHealthMultiplier); return max * Character.HealthMultiplier; } } @@ -434,10 +435,21 @@ namespace Barotrauma float temp = afflictions[i].GetResistance(resistanceId); if (temp > resistance) resistance = temp; } + resistance = 1 - ((1 - resistance) * Character.GetAbilityResistance(resistanceId)); 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; + } + private readonly List matchingAfflictions = new List(); public void ReduceAffliction(Limb targetLimb, string affliction, float amount) { @@ -468,6 +480,11 @@ namespace Barotrauma for (int i = matchingAfflictions.Count - 1; i >= 0; i--) { var matchingAffliction = matchingAfflictions[i]; + + // kind of bad to create a tuple every time, but I can't think of another way to easily do this + var afflictionReduction = (matchingAffliction, reduceAmount); + Character.CheckTalents(AbilityEffectType.OnReduceAffliction, afflictionReduction); + if (matchingAffliction.Strength < reduceAmount) { float surplus = reduceAmount - matchingAffliction.Strength; @@ -539,9 +556,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); @@ -738,7 +755,15 @@ namespace Barotrauma affliction.DamagePerSecondTimer += deltaTime; Character.StackSpeedMultiplier(affliction.GetSpeedMultiplier()); } - + + 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)); + } + UpdateLimbAfflictionOverlays(); CalculateVitality(); @@ -825,8 +850,8 @@ namespace Barotrauma { if (Unkillable || Character.GodMode) { return; } - var causeOfDeath = GetCauseOfDeath(); - Character.Kill(causeOfDeath.First, causeOfDeath.Second); + var (type, affliction) = GetCauseOfDeath(); + Character.Kill(type, affliction); #if CLIENT DisplayVitalityDelay = 0.0f; DisplayedVitality = Vitality; @@ -859,7 +884,7 @@ namespace Barotrauma } } - public Pair GetCauseOfDeath() + public (CauseOfDeathType type, Affliction affliction) GetCauseOfDeath() { List currentAfflictions = GetAllAfflictions(true); @@ -880,7 +905,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! @@ -968,7 +993,7 @@ namespace Barotrauma } 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 +1024,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); } } } 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/Limb.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Limb.cs index 92087ad79..ed33edcd9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Limb.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Limb.cs @@ -219,7 +219,7 @@ namespace Barotrauma public bool inWater; - private readonly FixedMouseJoint pullJoint; + private FixedMouseJoint pullJoint; public readonly LimbType type; @@ -683,7 +683,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 +741,7 @@ namespace Barotrauma { newAffliction.SetStrength(affliction.NonClampedStrength); } - + attacker?.CheckTalents(AbilityEffectType.OnAddDamageAffliction, newAffliction); if (applyAffliction) { afflictionsCopy.Add(newAffliction); @@ -1263,6 +1263,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/CharacterParams.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/CharacterParams.cs index 40e20c196..59e92989f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/CharacterParams.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/CharacterParams.cs @@ -70,6 +70,9 @@ 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; } @@ -88,6 +91,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; } 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..80b4be072 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityCondition.cs @@ -0,0 +1,89 @@ +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(object abilityData); + public abstract bool MatchesCondition(); + + + // tools + protected enum TargetType + { + Any = 0, + Enemy = 1, + Ally = 2, + NotSelf = 3, + Alive = 4, + Monster = 5, + }; + + 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; + default: + return true; + } + } + + } +} 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..2a80e9122 --- /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 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(object abilityData) + { + if (abilityData is AttackData 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(abilityData, typeof(AttackData)); + 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..909c694f2 --- /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(object abilityData) + { + if (abilityData is AttackResult attackResult) + { + if (!IsViableTarget(targetTypes, attackResult.HitLimb?.character)) { return false; } + + if (afflictions.Any()) + { + if (!afflictions.Any(a => attackResult.Afflictions.Select(c => c.Identifier).Contains(a))) { return false; } + } + + return true; + } + else + { + LogAbilityConditionError(abilityData, typeof(AttackData)); + 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..1254f8058 --- /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(object abilityData) + { + if (abilityData is Character character) + { + if (!IsViableTarget(targetTypes, character)) { return false; } + + return true; + } + else + { + LogAbilityConditionError(abilityData, typeof(Character)); + 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..d80a6257d --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionData.cs @@ -0,0 +1,39 @@ +using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +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(T abilityData, Type expectedData) + { + DebugConsole.ThrowError($"Used data-reliant ability condition when data is incompatible! Expected {expectedData}, but received {abilityData}"); + } + + protected abstract bool MatchesConditionSpecific(object abilityData); + 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(object abilityData) + { + if (abilityData is null) { return invert; } + return invert ? !MatchesConditionSpecific(abilityData) : MatchesConditionSpecific(abilityData); + } + } +} 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..44336e5b8 --- /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(object abilityData) + { + if (abilityData is Submarine submarine) + { + return submarine.TeamID == character.TeamID && character.Submarine == submarine; + } + else + { + LogAbilityConditionError(abilityData, typeof(Submarine)); + return false; + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionHandsomeStranger.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionHandsomeStranger.cs new file mode 100644 index 000000000..55ee4fa06 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionHandsomeStranger.cs @@ -0,0 +1,27 @@ +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class AbilityConditionHandsomeStranger : AbilityConditionData + { + string skillIdentifier; + + public AbilityConditionHandsomeStranger(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) + { + skillIdentifier = conditionElement.GetAttributeString("skillidentifier", "").ToLowerInvariant(); + } + + protected override bool MatchesConditionSpecific(object abilityData) + { + if (abilityData is string skillIdentifier) + { + return this.skillIdentifier == skillIdentifier; + } + else + { + LogAbilityConditionError(abilityData, typeof(string)); + 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..cfa1db21c --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionItem.cs @@ -0,0 +1,50 @@ +using System; +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class AbilityConditionItem : AbilityConditionData + { + private readonly string identifier; + private readonly string[] tags; + + public AbilityConditionItem(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) + { + identifier = conditionElement.GetAttributeString("identifier", string.Empty).ToLowerInvariant(); + tags = conditionElement.GetAttributeStringArray("tags", Array.Empty(), convertToLowerInvariant: true); + } + + protected override bool MatchesConditionSpecific(object abilityData) + { + ItemPrefab item = null; + if (abilityData is Item tempItem) + { + item = tempItem.Prefab; + } + // this and other instances of this type of casting will be refactored + else if (abilityData is (ItemPrefab itemPrefab, object _)) + { + item = itemPrefab; + } + + if (item != null) + { + if (!string.IsNullOrEmpty(identifier)) + { + if (item.Identifier != identifier) + { + return false; + } + } + + return tags.Any(t => item.Tags.Any(p => t == p)); + } + else + { + LogAbilityConditionError(abilityData, typeof(Item)); + 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..24044dc0e --- /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(object abilityData) + { + if (abilityData is (Affliction affliction, float reduceAmount)) + { + 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(abilityData, typeof((Affliction, float))); + return false; + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionScavenger.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionScavenger.cs new file mode 100644 index 000000000..2156ccae1 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionScavenger.cs @@ -0,0 +1,22 @@ +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class AbilityConditionScavenger : AbilityConditionData + { + public AbilityConditionScavenger(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) { } + + protected override bool MatchesConditionSpecific(object abilityData) + { + if (abilityData is Item item) + { + return item.Submarine != character.Submarine; + } + else + { + LogAbilityConditionError(abilityData, typeof(Item)); + 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..6543c7b32 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionAboveVitality.cs @@ -0,0 +1,20 @@ +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class AbilityConditionAboveVitality : AbilityConditionDataless + { + 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/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..023fe029f --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionDataless.cs @@ -0,0 +1,24 @@ +using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +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(object abilityData) + { + 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/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/AbilityConditionMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionMission.cs new file mode 100644 index 000000000..f27ecf4c1 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/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(object abilityData) + { + if (abilityData is (Mission mission, AbilityValue missionAbilityValue)) + { + return mission.Prefab.Type == missionType; + } + else + { + LogAbilityConditionError(abilityData, typeof((Mission, AbilityValue))); + return false; + } + } + } +} 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..3cc8ae4f5 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionServerRandom.cs @@ -0,0 +1,24 @@ +using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class AbilityConditionServerRandom : AbilityConditionDataless + { + private 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..ba5f10ccc --- /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.Submarine == null || character.Submarine.TeamID != character.TeamID) { 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/CharacterAbility.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbility.cs new file mode 100644 index 000000000..739e7ced1 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbility.cs @@ -0,0 +1,142 @@ +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 virtual bool RequiresAlive => true; + public virtual bool AllowClientSimulation => false; + public virtual bool AppliesEffectOnIntervalUpdate => false; + + private const float DefaultEffectTime = 1.0f; + + /// + /// 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; + } + + 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($"Ability {this} does not have an implementation for VerifyState! This ability does not work in interval ability groups."); + } + + public void ApplyAbilityEffect(object abilityData) + { + if (abilityData is null) + { + ApplyEffect(); + } + else + { + ApplyEffect(abilityData); + } + } + + protected virtual void ApplyEffect() + { + DebugConsole.AddWarning($"Ability {this} used improperly! This ability does not have a definition for ApplyEffect"); + } + + protected virtual void ApplyEffect(object abilityData) + { + DebugConsole.AddWarning($"Ability {this} used improperly! This ability does not take a parameter for ApplyEffect"); + } + + protected void LogAbilityDataMismatch() + { + 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; + } + + DebugConsole.AddWarning("Instantiated " + characterAbility + " for talent " + characterAbilityGroup.CharacterTalent.DebugIdentifier); + return characterAbility; + } + 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; + } + + public static float DistanceToSquaredDistance(float distance) + { + return distance * distance; + } + + } +} 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..3b35a274a --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyForce.cs @@ -0,0 +1,34 @@ +using Microsoft.Xna.Framework; +using System.Collections.Generic; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class CharacterAbilityApplyForce : CharacterAbility + { + private readonly float impulseStrength; + private readonly float maxVelocity; + + private readonly string afflictionIdentifier; + public override bool AppliesEffectOnIntervalUpdate => true; + public CharacterAbilityApplyForce(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) + { + impulseStrength = abilityElement.GetAttributeFloat("impulsestrength", 0f); + maxVelocity = abilityElement.GetAttributeFloat("maxvelocity", 10f); + + afflictionIdentifier = abilityElement.GetAttributeString("afflictionidentifier", ""); + } + + protected override void ApplyEffect() + { + Affliction affliction = Character.CharacterHealth.GetAffliction(afflictionIdentifier); + + if (affliction == null) { return; } + + foreach (Limb limb in Character.AnimController.Limbs) + { + limb.body.ApplyForce(Vector2.Normalize(limb.Mass * Character.AnimController.TargetMovement) * impulseStrength * (affliction.Strength / affliction.Prefab.MaxStrength), 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..e370e94b3 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffects.cs @@ -0,0 +1,43 @@ +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; + + public CharacterAbilityApplyStatusEffects(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) + { + statusEffects = CharacterAbilityGroup.ParseStatusEffects(CharacterTalent, abilityElement.GetChildElement("statuseffects")); + } + + protected void ApplyEffectSpecific(Character targetCharacter) + { + foreach (var statusEffect in statusEffects) + { + statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, Character, targetCharacter); + } + } + + protected override void ApplyEffect() + { + ApplyEffectSpecific(Character); + } + + protected override void ApplyEffect(object abilityData) + { + if (abilityData is Character targetCharacter) + { + ApplyEffectSpecific(targetCharacter); + } + else + { + ApplyEffect(); + } + } + } +} 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..a12622816 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffectsToNearestAlly.cs @@ -0,0 +1,36 @@ +using Microsoft.Xna.Framework; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class CharacterAbilityApplyStatusEffectsToNearestAlly : CharacterAbilityApplyStatusEffects + { + protected float squaredMaxDistance; + public CharacterAbilityApplyStatusEffectsToNearestAlly(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) + { + squaredMaxDistance = DistanceToSquaredDistance(abilityElement.GetAttributeFloat("maxdistance", float.MaxValue)); + } + + protected override void ApplyEffect() + { + 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 (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..8fe1ea29d --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffectsToRandomAlly.cs @@ -0,0 +1,40 @@ +using Barotrauma.Extensions; +using Microsoft.Xna.Framework; +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 = DistanceToSquaredDistance(abilityElement.GetAttributeFloat("maxdistance", float.MaxValue)); + 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.SimPosition, Character.GetRelativeSimPosition(c)) is float tempDistance && + tempDistance < squaredMaxDistance).GetRandom(); + + if (chosenCharacter == null) { return; } + + ApplyEffectSpecific(chosenCharacter); + } + + } +} 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..921807085 --- /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 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 = ParseFlagType(abilityElement.GetAttributeString("flagtype", ""), CharacterTalent.DebugIdentifier); + } + + public override void InitializeAbility(bool addingFirstTime) + { + Character.AddAbilityFlag(abilityFlag); + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveMissionCount.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveMissionCount.cs new file mode 100644 index 000000000..629202f93 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveMissionCount.cs @@ -0,0 +1,21 @@ +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class CharacterAbilityGiveMissionCount : CharacterAbility + { + private readonly int amount; + + public CharacterAbilityGiveMissionCount(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) + { + amount = abilityElement.GetAttributeInt("amount", 0); + } + + public override void InitializeAbility(bool addingFirstTime) + { + if (!addingFirstTime) { return; } + if (!(GameMain.GameSession?.Campaign is CampaignMode campaign)) { return; } + campaign.Settings.AddedMissionCount += amount; + } + } +} 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..86e1cb093 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveMoney.cs @@ -0,0 +1,22 @@ +using Microsoft.Xna.Framework; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class CharacterAbilityGiveMoney : CharacterAbility + { + public override bool AppliesEffectOnIntervalUpdate => true; + + private int amount; + + public CharacterAbilityGiveMoney(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) + { + amount = abilityElement.GetAttributeInt("amount", 0); + } + + protected override void ApplyEffect() + { + Character.GiveMoney(amount); + } + } +} 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..2a8797b4e --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGivePermanentStat.cs @@ -0,0 +1,49 @@ +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 bool targetAllies; + private readonly bool removeOnDeath; + //private readonly float maximumValue; + + public override bool AppliesEffectOnIntervalUpdate => true; + + public CharacterAbilityGivePermanentStat(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) + { + statIdentifier = abilityElement.GetAttributeString("statidentifier", "").ToLowerInvariant(); + statType = CharacterAbilityGroup.ParseStatType(abilityElement.GetAttributeString("stattype", ""), CharacterTalent.DebugIdentifier); + value = abilityElement.GetAttributeFloat("value", 0f); + targetAllies = abilityElement.GetAttributeBool("targetallies", false); + removeOnDeath = abilityElement.GetAttributeBool("removeondeath", true); + //maximumValue = abilityElement.GetAttributeFloat("maximumvalue", float.MaxValue); + } + + protected override void ApplyEffect(object abilityData) + { + ApplyEffectSpecific(); + } + + protected override void ApplyEffect() + { + ApplyEffectSpecific(); + } + + private void ApplyEffectSpecific() + { + if (targetAllies) + { + Character.GetFriendlyCrew(Character).ForEach(c => c?.Info.ChangeSavedStatValue(statType, value, statIdentifier, removeOnDeath)); + } + else + { + Character?.Info.ChangeSavedStatValue(statType, value, statIdentifier, removeOnDeath); + } + } + } +} 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..5d9cd7fee --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveResistance.cs @@ -0,0 +1,21 @@ +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class CharacterAbilityGiveResistance : CharacterAbility + { + private string resistanceId; + private float resistance; + + public CharacterAbilityGiveResistance(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) + { + resistanceId = abilityElement.GetAttributeString("resistanceid", ""); + resistance = abilityElement.GetAttributeFloat("resistance", 1f); + } + + public override void InitializeAbility(bool addingFirstTime) + { + Character.ChangeAbilityResistance(resistanceId, resistance); + } + } +} 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..3b55618d7 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveStat.cs @@ -0,0 +1,22 @@ +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class CharacterAbilityGiveStat : CharacterAbility + { + private StatTypes statType; + private float value; + + // 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 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/CharacterAbilityIncreaseSkill.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityIncreaseSkill.cs new file mode 100644 index 000000000..44caf675a --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityIncreaseSkill.cs @@ -0,0 +1,41 @@ +using Microsoft.Xna.Framework; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class CharacterAbilityIncreaseSkill : CharacterAbility + { + public override bool AppliesEffectOnIntervalUpdate => true; + + private string skillIdentifier; + private float skillIncrease; + + public CharacterAbilityIncreaseSkill(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) + { + skillIdentifier = abilityElement.GetAttributeString("skillidentifier", "").ToLowerInvariant(); + skillIncrease = abilityElement.GetAttributeFloat("skillincrease", 0f); + } + + protected override void ApplyEffect() + { + ApplyEffectSpecific(Character); + } + + protected override void ApplyEffect(object abilityData) + { + if (abilityData is Character character) + { + ApplyEffectSpecific(character); + } + else + { + ApplyEffectSpecific(Character); + } + } + + private void ApplyEffectSpecific(Character character) + { + character.Info?.IncreaseSkillLevel(skillIdentifier, skillIncrease, character.Position + Vector2.UnitY * 175.0f); + } + } +} 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..84aa32f90 --- /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(object abilityData) + { + if (abilityData is Affliction affliction) + { + foreach (string afflictionIdentifier in afflictionIdentifiers) + { + if (affliction.Identifier == afflictionIdentifier) + { + affliction.Strength *= 1 + addedMultiplier; + } + } + } + else + { + LogAbilityDataMismatch(); + } + } + } +} 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..6ee9dc1dc --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyAttackData.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class CharacterAbilityModifyAttackData : CharacterAbility + { + private readonly List afflictions; + + float addedDamageMultiplier; + float addedPenetration; + + 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); + } + + protected override void ApplyEffect(object abilityData) + { + if (abilityData is AttackData attackData) + { + if (attackData.Afflictions == null) + { + attackData.Afflictions = afflictions; + } + else + { + attackData.Afflictions.AddRange(afflictions); + } + attackData.DamageMultiplier += addedDamageMultiplier; + attackData.AddedPenetration += addedPenetration; + } + else + { + LogAbilityDataMismatch(); + } + } + } +} 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..4ab462ccf --- /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 AbilityFlags abilityFlag; + + private bool lastState; + + public CharacterAbilityModifyFlag(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) + { + abilityFlag = 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..828714266 --- /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(object abilityData) + { + if (abilityData is (Affliction affliction, float reduceAmount)) + { + affliction.Strength -= addedAmountMultiplier * reduceAmount; + } + else + { + LogAbilityDataMismatch(); + } + } + } +} 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..4e44403d7 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyResistance.cs @@ -0,0 +1,27 @@ +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class CharacterAbilityModifyResistance : CharacterAbility + { + private string resistanceId; + private 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); + } + + 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/CharacterAbilityModifyValue.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyValue.cs new file mode 100644 index 000000000..7f27c334f --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyValue.cs @@ -0,0 +1,46 @@ +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class CharacterAbilityModifyValue : CharacterAbility + { + private float addedValue; + private float multiplierValue; + + public CharacterAbilityModifyValue(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) + { + addedValue = abilityElement.GetAttributeFloat("addedvalue", 0f); + multiplierValue = abilityElement.GetAttributeFloat("multipliervalue", 1f); + } + + protected override void ApplyEffect(object abilityData) + { + if (abilityData is AbilityValue abilityValue) + { + ApplyEffectSpecific(abilityValue); + } + else if (abilityData is (object _, AbilityValue tupleAbilityValue)) + { + ApplyEffectSpecific(tupleAbilityValue); + } + } + + private void ApplyEffectSpecific(AbilityValue abilityValue) + { + abilityValue.Value += addedValue; + abilityValue.Value *= multiplierValue; + } + + } + + // this seems like a real silly way to have to pass values by reference into these same interfaces + // if more of these are required, maybe there should be an additional set of interfaces to easily pass values by reference instead + class AbilityValue + { + public float Value { get; set; } + public AbilityValue(float value) + { + Value = value; + } + } +} 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..eb57809e5 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityResetPermanentStat.cs @@ -0,0 +1,29 @@ +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class CharacterAbilityResetPermanentStat : CharacterAbility + { + private readonly string statIdentifier; + public override bool RequiresAlive => false; + + public CharacterAbilityResetPermanentStat(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) + { + statIdentifier = abilityElement.GetAttributeString("statidentifier", "").ToLowerInvariant(); + } + protected override void ApplyEffect(object abilityData) + { + ApplyEffectSpecific(); + } + + protected override void ApplyEffect() + { + ApplyEffectSpecific(); + } + + private void ApplyEffectSpecific() + { + Character?.Info.ResetSavedStatValue(statIdentifier); + } + } +} 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..808ca4b0b --- /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(object abilityData) + { + if (abilityData is (string skillIdentifier, Character character) && character != Character) + { + character.Info?.IncreaseSkillLevel(skillIdentifier, 1.0f, character.Position + Vector2.UnitY * 175.0f); + } + } + } +} 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..fde6e081e --- /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(object abilityData) + { + if (abilityData is Character character) + { + Character.GiveMoney((int)(vitalityPercentage * character.MaxVitality)); + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityIndustrialRevolution.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityIndustrialRevolution.cs new file mode 100644 index 000000000..324bf919d --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityIndustrialRevolution.cs @@ -0,0 +1,30 @@ +using Barotrauma.Items.Components; +using Microsoft.Xna.Framework; +using System.Collections.Generic; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class CharacterAbilityIndustrialRevolution : CharacterAbility + { + float addedFabricationSpeed; + + public CharacterAbilityIndustrialRevolution(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) + { + addedFabricationSpeed = abilityElement.GetAttributeFloat("addedfabricationspeed", 0f); + } + + public override void UpdateCharacterAbility(bool conditionsMatched, float timeSinceLastUpdate) + { + if (conditionsMatched) + { + // not necessarily the cleanest or performant way, but at least this shouldn't break anything. + // must be done every frame in order to work. + if (Character.SelectedConstruction?.GetComponent() is Fabricator fabricator && fabricator.IsActive) + { + fabricator.FabricationSpeedMultiplier += addedFabricationSpeed; + } + } + } + } +} 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..9241769e4 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityInsurancePolicy.cs @@ -0,0 +1,54 @@ +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; + public override bool RequiresAlive => false; + + private readonly int moneyPerLevel; + private bool hasOccurred = false; + + private static List clientsAlreadyUsed = new List(); + + public CharacterAbilityInsurancePolicy(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) + { + moneyPerLevel = abilityElement.GetAttributeInt("moneyperlevel", 0); + } + + protected override void ApplyEffect() + { + if (Character?.Info is CharacterInfo info && !hasOccurred) + { + if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer) + { + foreach (Client client in GameMain.NetworkMember.ConnectedClients) + { + if (client.Character == Character && clientsAlreadyUsed.Contains(client)) { return; } + } + } + + Character.GiveMoney(moneyPerLevel * info.GetCurrentLevel()); + hasOccurred = true; + + // this is an ugly way to do this, but this effect should not occur more than once per round for a client + // this seemed like the simplest way to do it since characters are instantiated from scratch each time + if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer) + { + foreach (Client client in GameMain.NetworkMember.ConnectedClients) + { + if (client.Character == Character) + { + clientsAlreadyUsed.Add(client); + } + } + } + } + } + } +} 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..0017b6d98 --- /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(object abilityData) + { + if (abilityData is string skillIdentifier) + { + if (skillIdentifier != lastSkillIdentifier) + { + lastSkillIdentifier = skillIdentifier; + Character.Info?.IncreaseSkillLevel(skillIdentifier, 1.0f, Character.Position + Vector2.UnitY * 175.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..4e7ac7225 --- /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 value; + private string afflictionIdentifier; + private float lastValue = 0f; + + public CharacterAbilityPsychoClown(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) + { + statType = CharacterAbilityGroup.ParseStatType(abilityElement.GetAttributeString("stattype", ""), CharacterTalent.DebugIdentifier); + value = abilityElement.GetAttributeFloat("value", 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 * value; + 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..5a2ff174e --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityRegenerateLoot.cs @@ -0,0 +1,30 @@ +using Barotrauma.Items.Components; +using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class CharacterAbilityRegenerateLoot : CharacterAbility + { + List openedContainers = new List(); + + public CharacterAbilityRegenerateLoot(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) + { + } + + protected override void ApplyEffect(object abilityData) + { + if (abilityData is Item item && !openedContainers.Contains(item)) + { + openedContainers.Add(item); + + if (item.GetComponent() is ItemContainer itemContainer) + { + AutoItemPlacer.RegenerateLoot(item.Submarine, itemContainer); + } + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityStonewall.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityStonewall.cs new file mode 100644 index 000000000..6597c6973 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityStonewall.cs @@ -0,0 +1,42 @@ +using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class CharacterAbilityStonewall : CharacterAbility + { + private readonly List statusEffects; + private readonly List statusEffectsReset; + private int maxEnemyCount; + private float squaredDistance; + + public CharacterAbilityStonewall(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) + { + statusEffects = CharacterAbilityGroup.ParseStatusEffects(CharacterTalent, abilityElement.GetChildElement("statuseffects")); + statusEffectsReset = CharacterAbilityGroup.ParseStatusEffects(CharacterTalent, abilityElement.GetChildElement("statuseffectsreset")); + maxEnemyCount = abilityElement.GetAttributeInt("maxenemycount", 0); + squaredDistance = DistanceToSquaredDistance(abilityElement.GetAttributeFloat("distance", 0)); + } + + protected override void VerifyState(bool conditionsMatched, float timeSinceLastUpdate) + { + int numberOfEnemiesInRange = Character.CharacterList.Where(c => !HumanAIController.IsFriendly(Character, c) && !c.IsDead && Vector2.DistanceSquared(Character.SimPosition, Character.GetRelativeSimPosition(c)) < squaredDistance).Count(); + + foreach (var statusEffect in statusEffectsReset) + { + statusEffect.Apply(ActionType.OnAbility, 1f, Character, Character); + } + + if (conditionsMatched && numberOfEnemiesInRange > 0) + { + foreach (var statusEffect in statusEffects) + { + statusEffect.Apply(ActionType.OnAbility, Math.Min(numberOfEnemiesInRange, maxEnemyCount), Character, Character); + } + } + } + } +} 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..284ec4ae6 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityTandemFire.cs @@ -0,0 +1,42 @@ +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 + { + 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 || !Character.SelectedConstruction.HasTag(tag)) { return; } + + if (closestDistance < squaredMaxDistance) + { + ApplyEffectSpecific(Character); + ApplyEffectSpecific(closestCharacter); + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityTaskmaster.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityTaskmaster.cs new file mode 100644 index 000000000..d54950e74 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityTaskmaster.cs @@ -0,0 +1,39 @@ +using System.Collections.Generic; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class CharacterAbilityTaskmaster : CharacterAbility + { + private readonly List statusEffects; + private readonly List statusEffectsRemove; + + private Character lastCharacter; + + public CharacterAbilityTaskmaster(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) + { + statusEffects = CharacterAbilityGroup.ParseStatusEffects(CharacterTalent, abilityElement.GetChildElement("statuseffects")); + statusEffectsRemove = CharacterAbilityGroup.ParseStatusEffects(CharacterTalent, abilityElement.GetChildElement("statuseffectsremove")); + } + + protected override void ApplyEffect(object abilityData) + { + if (abilityData is Character targetCharacter) + { + if (targetCharacter == Character) { return; } + + foreach (var statusEffect in statusEffectsRemove) + { + statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, Character, lastCharacter); + } + + foreach (var statusEffect in statusEffects) + { + statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, Character, targetCharacter); + } + + lastCharacter = targetCharacter; + } + } + } +} 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..c49fa439c --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroup.cs @@ -0,0 +1,206 @@ +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; + + // 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(CharacterTalent characterTalent, XElement abilityElementGroup) + { + CharacterTalent = characterTalent; + Character = CharacterTalent.Character; + + 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) + { + StatTypes statType; + if (!Enum.TryParse(statTypeString, true, out 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; + } + } +} 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..f49851390 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroupEffect.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class CharacterAbilityGroupEffect : CharacterAbilityGroup + { + public CharacterAbilityGroupEffect(CharacterTalent characterTalent, XElement abilityElementGroup) : base(characterTalent, abilityElementGroup) { } + + public void CheckAbilityGroup(object abilityData) + { + if (!IsActive) { return; } + if (IsApplicable(abilityData)) + { + foreach (var characterAbility in characterAbilities) + { + if (characterAbility.IsViable()) + { + characterAbility.ApplyAbilityEffect(abilityData); + } + } + } + } + + private bool IsApplicable(object abilityData) + { + return abilityConditions.All(c => c.MatchesCondition(abilityData)); + } + } +} 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..6597bbcd6 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroupInterval.cs @@ -0,0 +1,48 @@ +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(CharacterTalent characterTalent, XElement abilityElementGroup) : base(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); + } + } + TimeSinceLastUpdate = 0; + } + } + private bool IsApplicable() + { + 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..9c64f85ea --- /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; + + 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, object abilityData) + { + if (characterAbilityGroupEffectDictionary.TryGetValue(abilityEffectType, out var characterAbilityGroups)) + { + foreach (var characterAbilityGroup in characterAbilityGroups) + { + characterAbilityGroup.CheckAbilityGroup(abilityData); + } + } + } + + 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) + { + string name = abilityGroup.Name.ToString().ToLowerInvariant(); + characterAbilityGroupIntervals.Add(new CharacterAbilityGroupInterval(this, abilityGroup)); + } + + private void LoadAbilityGroupEffect(XElement abilityGroup) + { + AbilityEffectType abilityEffectType = ParseAbilityEffectType(this, abilityGroup.GetAttributeString("abilityeffecttype", "none")); + AddAbilityGroupEffect(new CharacterAbilityGroupEffect(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) + { + AbilityEffectType abilityEffectType = AbilityEffectType.Undefined; + if (!Enum.TryParse(abilityEffectTypeString, true, out 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..5c42eb8e7 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/TalentPrefab.cs @@ -0,0 +1,102 @@ +using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +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 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"); + this.CalculatePrefabUIntIdentifier(TalentPrefabs); + } + + 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..2a2f67bdf --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/TalentTree.cs @@ -0,0 +1,219 @@ +using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma +{ + class TalentTree + { + public static readonly Dictionary JobTalentTrees = new Dictionary(); + + public readonly List TalentSubTrees = new List(); + + private static HashSet subtreeTalents = new HashSet(); + + public XElement ConfigElement + { + get; + private set; + } + + public TalentTree(XElement element, string filePath) + { + ConfigElement = element; + + string jobIdentifier = element.GetAttributeString("jobidentifier", ""); + + if (string.IsNullOrEmpty(jobIdentifier)) + { + DebugConsole.ThrowError("No job defined for talent tree!"); + 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 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.ToString()}' 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()); + } + + + 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 readonly List TalentOptionStages = new List(); + + public TalentSubTree(XElement subTreeElement) + { + Identifier = subTreeElement.GetAttributeString("identifier", ""); + + foreach (XElement talentOptionsElement in subTreeElement.GetChildElements("talentoptions")) + { + TalentOptionStages.Add(new TalentOption(talentOptionsElement)); + } + } + + } + + class TalentOption + { + public readonly List Talents = new List(); + + public TalentOption(XElement talentOptionsElement) + { + foreach (XElement talentOptionElement in talentOptionsElement.GetChildElements("talentoption")) + { + Talents.Add(new Talent(talentOptionElement)); + } + } + } + + class Talent + { + public readonly string Identifier; + public readonly Sprite Icon; + public Talent(XElement talentOptionElement) + { + Identifier = talentOptionElement.GetAttributeString("identifier", ""); + foreach (XElement subElement in talentOptionElement.Elements()) + { + switch (subElement.Name.ToString().ToLowerInvariant()) + { + case "icon": + Icon = new Sprite(subElement); + break; + } + } + } + } + +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/ContentPackage.cs b/Barotrauma/BarotraumaShared/SharedSource/ContentPackage.cs index dbfb4cd19..ed67c56be 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/DebugConsole.cs b/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs index acaa5db14..e768d963d 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, () => { @@ -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]: 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; } @@ -632,14 +627,21 @@ namespace Barotrauma bool.TryParse(args[2], out relativeStrength); } - Character targetCharacter = (relativeStrength || args.Length <= 2) ? Character.Controlled : FindMatchingCharacter(args.Skip(2).ToArray()); + Character targetCharacter = (relativeStrength || 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 +650,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)); @@ -833,9 +836,68 @@ 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", "give [player] testing [talent]", (string[] args) => + { + if (args.Length < 2) return; + var character = FindMatchingCharacter(args.Skip(1).ToArray()) ?? Character.Controlled; + if (character != null) + { + character.GiveTalent(args[0]); + } + }, + () => + { + List talentNames = new List(); + foreach (TalentPrefab itemPrefab in TalentPrefab.TalentPrefabs) + { + talentNames.Add(itemPrefab.Identifier); + } + + return new string[][] + { + talentNames.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 +1009,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 +1075,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 +1102,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 +1430,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 +1550,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."); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Enums.cs b/Barotrauma/BarotraumaShared/SharedSource/Enums.cs index d0a91b108..c127555da 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Enums.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Enums.cs @@ -23,5 +23,78 @@ OnProduceSpawned, OnOpen, OnClose, OnDeath = OnBroken, + OnSuccess, + OnAbility, } + + public enum AbilityEffectType + { + Undefined, + None, + OnAttack, + OnAttackResult, + OnAttacked, + OnGainSkillPoint, + OnAllyGainSkillPoint, + OnRepairComplete, + OnItemFabricationSkillGain, + OnItemFabricatedAmount, + OnAllyItemFabricatedAmount, + OnOpenItemContainer, + OnUseRangedWeapon, + OnReduceAffliction, + OnAddDamageAffliction, + OnSelfRagdoll, + OnAnyMissionCompleted, + OnAllMissionsCompleted, + OnGiveOrder, + OnCrewKillCharacter, + OnDieToCharacter, + OnAllyGainMissionExperience, + OnGainMissionExperience, + OnGainMissionMoney, + AfterSubmarineAttacked, + } + + public enum StatTypes + { + None, + // Skills + ElectricalSkillBonus, + HelmSkillBonus, + MechanicalSkillBonus, + MedicalSkillBonus, + WeaponsSkillBonus, + // Character attributes + MaximumHealthMultiplier, + MovementSpeed, + SwimmingSpeed, + BuffDurationMultiplier, + DebuffDurationMultiplier, + // Combat + AttackMultiplier, + RangedAttackSpeed, + TurretAttackSpeed, + MeleeAttackSpeed, + SpreadMultiplier, + // Utility + RepairSpeed, + // Misc + ReputationGainMultiplier, + MissionMoneyGainMultiplier, + ExperienceGainMultiplier, + MissionExperienceGainMultiplier, + + } + + public enum AbilityFlags + { + None, + MustWalk, + ImmuneToPressure, + IgnoredByEnemyAI, + MoveNormallyWhileDragging, + CanTinker, + } + } 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/EventManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs index 3f8a0a93a..c9b03e99c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs @@ -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) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs index 77747d13a..47b9637e2 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; @@ -343,19 +345,40 @@ 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.15f; + + 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)); + + foreach (Character character in crewCharacters) + { + character.Info.GiveExperience((int)(baseExperienceGain * experienceGainMultiplier.Value), isMissionExperience: true); + } + + // apply money gains afterwards to prevent them from affecting XP gains + var moneyGainMultiplier = new AbilityValue(1f); + crewCharacters.ForEach(c => c.CheckTalents(AbilityEffectType.OnGainMissionMoney, (this, moneyGainMultiplier))); + crewCharacters.ForEach(c => moneyGainMultiplier.Value += c.GetStatValue(StatTypes.MissionMoneyGainMultiplier)); + + campaign.Money += (int)(reward * moneyGainMultiplier.Value); 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); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/AutoItemPlacer.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/AutoItemPlacer.cs index c93b28077..127e56d71 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/AutoItemPlacer.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/AutoItemPlacer.cs @@ -8,8 +8,6 @@ namespace Barotrauma { static class AutoItemPlacer { - private static readonly List spawnedItems = new List(); - public static bool OutputDebugInfo = false; public static void PlaceIfNeeded() @@ -41,7 +39,12 @@ namespace Barotrauma } } - private static void Place(IEnumerable subs) + public static void RegenerateLoot(Submarine sub, ItemContainer regeneratedContainer) + { + Place(sub.ToEnumerable(), regeneratedContainer); + } + + private static void Place(IEnumerable subs, ItemContainer regeneratedContainer = null) { if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { @@ -49,19 +52,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 +90,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 +164,10 @@ namespace Barotrauma } foreach (var validContainer in validContainers) { - if (SpawnItem(itemPrefab, containers, validContainer)) + var newItems = SpawnItem(itemPrefab, containers, validContainer); + if (newItems.Any()) { + spawnedItems.AddRange(newItems); success = true; } } @@ -184,14 +198,15 @@ namespace Barotrauma return validContainers; } - private static bool SpawnItem(ItemPrefab itemPrefab, List containers, KeyValuePair validContainer) + private static List SpawnItem(ItemPrefab itemPrefab, List containers, KeyValuePair validContainer) { + List spawnedItems = new List(); bool success = false; - if (Rand.Value(Rand.RandSync.Server) > validContainer.Value.SpawnProbability) { return false; } + if (Rand.Value(Rand.RandSync.Server) > validContainer.Value.SpawnProbability) { 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++) @@ -219,7 +234,7 @@ namespace Barotrauma 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..49e0aa705 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) diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/Data/Reputation.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/Data/Reputation.cs index 9944fd2bc..49f175753 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 Character.CharacterList.Where(c => c.TeamID == CharacterTeamType.Team1)) + { + 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..fdf7fa68a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs @@ -18,6 +18,10 @@ namespace Barotrauma public static CampaignSettings Unsure = Empty; public bool RadiationEnabled { get; set; } public int MaxMissionCount { get; set; } + public int AddedMissionCount { get; set; } + + public int TotalMaxMissionCount => MaxMissionCount + AddedMissionCount; + public const int DefaultMaxMissionCount = 2; public const int MaxMissionCountLimit = 10; @@ -27,23 +31,26 @@ namespace Barotrauma { RadiationEnabled = inc.ReadBoolean(); MaxMissionCount = inc.ReadInt32(); + AddedMissionCount = inc.ReadInt32(); } - + public CampaignSettings(XElement element) { - RadiationEnabled = element.GetAttributeBool(nameof(RadiationEnabled).ToLower(), true); - MaxMissionCount = element.GetAttributeInt(nameof(MaxMissionCount).ToLower(), DefaultMaxMissionCount); + RadiationEnabled = element.GetAttributeBool(nameof(RadiationEnabled).ToLowerInvariant(), true); + MaxMissionCount = element.GetAttributeInt(nameof(MaxMissionCount).ToLowerInvariant(), DefaultMaxMissionCount); + AddedMissionCount = element.GetAttributeInt(nameof(AddedMissionCount).ToLowerInvariant(), 0); } public void Serialize(IWriteMessage msg) { msg.Write(RadiationEnabled); msg.Write(MaxMissionCount); + msg.Write(AddedMissionCount); } 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), new XAttribute(nameof(AddedMissionCount).ToLowerInvariant(), AddedMissionCount)); } } @@ -226,6 +233,8 @@ namespace Barotrauma PurchasedLostShuttles = false; var connectedSubs = Submarine.MainSub.GetConnectedSubs(); wasDocked = Level.Loaded.StartOutpost != null && connectedSubs.Contains(Level.Loaded.StartOutpost); + + ResetTalentData(); } public void InitCampaignData() @@ -846,7 +855,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 +907,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/GameSession.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs index b17a7ec74..0a2e0bdd3 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; } @@ -654,43 +656,83 @@ 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); +#else + return GameMain.GameSession.CrewManager.CharacterInfos.Select(i => i.Character).Where(c => c != null); +#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 = GameSession.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; + if (missions.Any()) + { + if (missions.Any(m => m.Completed)) + { + foreach (CharacterInfo characterInfo in GameMain.GameSession.CrewManager.CharacterInfos) + { + characterInfo.Character?.CheckTalents(AbilityEffectType.OnAnyMissionCompleted); + } + } + + if (missions.All(m => m.Completed)) + { + foreach (CharacterInfo characterInfo in GameMain.GameSession.CrewManager.CharacterInfos) + { + characterInfo.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..2183d6866 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSettings.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSettings.cs @@ -307,6 +307,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 +549,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 +601,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 +716,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 +808,6 @@ namespace Barotrauma LoadDefaultConfig(); - if (WasGameUpdated) - { - UpdaterUtil.CleanOldFiles(); - WasGameUpdated = false; - SaveNewDefaultConfig(); - } - LoadPlayerConfig(); } @@ -827,7 +832,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); @@ -889,11 +893,6 @@ namespace Barotrauma 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) { @@ -1147,6 +1146,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) @@ -1402,6 +1402,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); @@ -1686,7 +1687,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/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/ElectricalDischarger.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ElectricalDischarger.cs index a86e3f923..2d58be590 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ElectricalDischarger.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ElectricalDischarger.cs @@ -475,6 +475,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/Holdable/Holdable.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Holdable.cs index cbfc92b1a..8c48733a9 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; @@ -780,7 +780,19 @@ namespace Barotrauma.Items.Components DeattachFromWall(); } } - + + protected override void RemoveComponentSpecific() + { + base.RemoveComponentSpecific(); + attachTargetCell = null; + if (Pusher != null) + { + GameMain.World.Remove(Pusher.FarseerBody); + 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..d362beb6f 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) { @@ -20,6 +38,8 @@ namespace Barotrauma.Items.Components item.AddTag("jobid:" + info.Job.Prefab.Identifier); } + TeamID = info.TeamID; + var head = info.Head; if (info != null && head != null) @@ -50,5 +70,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..b7174abdf 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/MeleeWeapon.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/MeleeWeapon.cs @@ -61,7 +61,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 + ", MeleeWeapon"); + Attack = new Attack(subElement, item.Name + ", MeleeWeapon", item); Attack.DamageRange = item.body == null ? 10.0f : ConvertUnits.ToDisplayUnits(item.body.GetMaxExtent()); } item.IsShootable = true; @@ -95,7 +95,7 @@ namespace Barotrauma.Items.Components if (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; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RangedWeapon.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RangedWeapon.cs index 8a73e00a7..d628c5d40 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RangedWeapon.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RangedWeapon.cs @@ -5,6 +5,7 @@ using FarseerPhysics.Dynamics; using Microsoft.Xna.Framework; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Xml.Linq; @@ -52,6 +53,21 @@ 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 @@ -62,7 +78,10 @@ namespace Barotrauma.Items.Components return Vector2.Transform(flippedPos, bodyTransform); } } - + + private float currentChargeTime; + private bool tryingToCharge; + public RangedWeapon(Item item, XElement element) : base(item, element) { @@ -88,10 +107,41 @@ namespace Barotrauma.Items.Components if (ReloadTimer < 0.0f) { ReloadTimer = 0.0f; - IsActive = false; + // was this an optimization or related to something else? currently disabled for charge-type weapons + //IsActive = false; + if (MaxChargeTime == 0.0f) + { + IsActive = false; + return; + } } + + float previousChargeTime = currentChargeTime; + + float chargeDeltaTime = tryingToCharge ? 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 +152,16 @@ 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)); + currentChargeTime = 0f; + + character.CheckTalents(AbilityEffectType.OnUseRangedWeapon, item); if (item.AiTarget != null) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemComponent.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemComponent.cs index 22f334166..90fd92739 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 diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs index 5502221ae..336a5b183 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs @@ -140,6 +140,20 @@ 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 rotation in which the contained sprites are drawn (in degrees).")] + public float AutoInjectThreshold + { + get; + set; + } + [Serialize(false, false)] public bool RemoveContainedItemsOnDeconstruct { get; set; } @@ -237,9 +251,23 @@ 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); + } + } + } + } else if (item.body != null && item.body.Enabled && @@ -256,6 +284,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; @@ -299,6 +328,8 @@ namespace Barotrauma.Items.Components } } } + character.CheckTalents(AbilityEffectType.OnOpenItemContainer, item); + return base.Select(character); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Engine.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Engine.cs index 0e9457394..d766f09fa 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Engine.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Engine.cs @@ -122,6 +122,12 @@ namespace Barotrauma.Items.Components float voltageFactor = MinVoltage <= 0.0f ? 1.0f : Math.Min(Voltage, 1.0f); Vector2 currForce = new Vector2(force * maxForce * forceMultiplier * voltageFactor, 0.0f); + + if (item.GetComponent()?.IsTinkering ?? false) + { + currForce *= 2.5f; + } + //less effective when in a bad condition currForce *= MathHelper.Lerp(0.5f, 2.0f, item.Condition / item.MaxCondition); if (item.Submarine.FlippedX) { currForce *= -1; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs index ba0fd4715..8d84d9d26 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 { @@ -24,6 +24,12 @@ namespace Barotrauma.Items.Components private Character user; + public float FabricationSpeedMultiplier + { + get; + set; + } + private ItemContainer inputContainer, outputContainer; [Serialize(1.0f, true)] @@ -240,8 +246,10 @@ 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)) { + FabricationSpeedMultiplier = 1f; CancelFabricating(); return; } @@ -278,38 +286,56 @@ namespace Barotrauma.Items.Components if (powerConsumption <= 0) { Voltage = 1.0f; } - timeUntilReady -= deltaTime * Math.Min(Voltage, 1.0f); + timeUntilReady -= deltaTime * Math.Min(Voltage, 1.0f) * FabricationSpeedMultiplier; + FabricationSpeedMultiplier = 1f; + 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); } - } + }); Character tempUser = user; + int amountFittingContainer = outputContainer.Inventory.HowManyCanBePut(fabricatedItem.TargetItem, fabricatedItem.OutCondition * fabricatedItem.TargetItem.Health); - for (int i = 0; i < fabricatedItem.Amount; i++) + var itemsCreated = new AbilityValue(fabricatedItem.Amount); + foreach (Character character in Character.CharacterList.Where(c => c.TeamID == user.TeamID)) + { + character.CheckTalents(AbilityEffectType.OnAllyItemFabricatedAmount, (fabricatedItem.TargetItem, itemsCreated)); + } + + tempUser.CheckTalents(AbilityEffectType.OnItemFabricatedAmount, (fabricatedItem.TargetItem, itemsCreated)); + + for (int i = 0; i < (int)itemsCreated.Value; i++) { if (i < amountFittingContainer) { @@ -339,9 +365,13 @@ namespace Barotrauma.Items.Components 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 AbilityValue(0f); + user.CheckTalents(AbilityEffectType.OnItemFabricationSkillGain, addedSkillValue); + user.Info.IncreaseSkillLevel( skill.Identifier, - skill.Level * SkillSettings.Current.SkillIncreasePerFabricatorRequiredSkill / Math.Max(userSkill, 1.0f), + addedSkill + addedSkillValue.Value, user.Position + Vector2.UnitY * 150.0f); } } @@ -365,24 +395,36 @@ namespace Barotrauma.Items.Components partial void UpdateRequiredTimeProjSpecific(); - private bool CanBeFabricated(FabricationRecipe fabricableItem) + private bool CanBeFabricated(FabricationRecipe fabricableItem, Dictionary> availableIngredients, Character character) { if (fabricableItem == null) { return false; } - List availableIngredients = GetAvailableIngredients(); - return CanBeFabricated(fabricableItem, availableIngredients); - } + if (fabricableItem.RequiresRecipe && (character == null || !character.HasRecipeForItem(fabricableItem.TargetItem.Identifier))) { return false; } - private bool CanBeFabricated(FabricationRecipe fabricableItem, IEnumerable availableIngredients) - { - if (fabricableItem == null) { return false; } - foreach (FabricationRecipe.RequiredItem requiredItem in fabricableItem.RequiredItems) + 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 +458,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 +490,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 +517,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..38d3c40fa 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,45 @@ 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 hull condition mode.")] + public bool EnableHullCondition + { + 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 +98,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 +130,7 @@ namespace Barotrauma.Items.Components ApplyStatusEffects(ActionType.OnActive, deltaTime, null); } } - + public override bool Pick(Character picker) { return picker != null; @@ -107,11 +156,11 @@ namespace Barotrauma.Items.Components //cheating a bit because water detectors don't actually send the water level if (source.GetComponent() == null) { - hullData.Water = Rand.Range(0.0f, 1.0f); + hullData.ReceivedWaterAmount = Rand.Range(0.0f, 1.0f); } else { - hullData.Water = Math.Min(sourceHull.WaterVolume / sourceHull.Volume, 1.0f); + hullData.ReceivedWaterAmount = Math.Min(sourceHull.WaterVolume / sourceHull.Volume, 1.0f); } break; case "oxygen_data_in": @@ -122,7 +171,7 @@ namespace Barotrauma.Items.Components oxy = Rand.Range(0.0f, 100.0f); } - hullData.Oxygen = oxy; + hullData.ReceivedOxygenAmount = oxy; break; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Pump.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Pump.cs index 073ae51cf..4231489a5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Pump.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Pump.cs @@ -105,6 +105,12 @@ 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()?.IsTinkering ?? false) + { + currFlow *= 2.5f; + } + //less effective when in a bad condition currFlow *= MathHelper.Lerp(0.5f, 1.0f, item.Condition / item.MaxCondition); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Steering.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Steering.cs index 1d0ad14bb..543e9bc1b 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,19 @@ 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. + // TODO: add current_velocity_x and current_velocity_y pins on the navigation terminals and shuttle terminals + // TODO: increase the size of the connection panels of both navigation terminals + + 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.ToString("0.0000", CultureInfo.InvariantCulture), sender: user), "current_position_x"); + item.SendSignal(new Signal(sub.RealWorldDepth.ToString("0.0000", CultureInfo.InvariantCulture), sender: user), "current_depth"); + } + // if our tactical AI pilot has left, revert back to maintaining position if (navigateTactically && (user == null || user.SelectedConstruction != item)) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Power/PowerTransfer.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Power/PowerTransfer.cs index 1828f7cee..a587d47ff 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Power/PowerTransfer.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Power/PowerTransfer.cs @@ -370,5 +370,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..6af8ff629 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs @@ -201,7 +201,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); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs index 26a50ebf7..2b474213b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs @@ -103,18 +103,22 @@ namespace Barotrauma.Items.Components } } + public bool IsTinkering { get; private set; } = false; + public float RepairIconThreshold { get { return RepairThreshold / 2; } } public Character CurrentFixer { get; private set; } + private Item currentRepairItem; public enum FixActions : int { None = 0, Repair = 1, - Sabotage = 2 + Sabotage = 2, + Tinker = 3, } private FixActions currentFixerAction = FixActions.None; @@ -161,12 +165,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; } @@ -201,10 +207,11 @@ 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 }); @@ -215,11 +222,18 @@ 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; return true; + + Item GetBestRepairItem(Character character) + { + return character.HeldItems.OrderByDescending(i => i.Prefab.AddedRepairSpeedMultiplier).FirstOrDefault(); + } + } } @@ -233,8 +247,16 @@ namespace Barotrauma.Items.Components item.CreateServerEvent(this); } #endif + if (currentRepairItem != null) + { + foreach (var ic in currentRepairItem.GetComponents()) + { + ic.ApplyStatusEffects(ActionType.OnSuccess, 1.0f, character); + } + } CurrentFixer.AnimController.Anim = AnimController.Animation.None; CurrentFixer = null; + currentRepairItem = null; currentFixerAction = FixActions.None; #if CLIENT repairSoundChannel?.FadeOutAndDispose(); @@ -266,7 +288,8 @@ namespace Barotrauma.Items.Components public override void Update(float deltaTime, Camera cam) { UpdateProjSpecific(deltaTime); - + IsTinkering = false; + if (CurrentFixer == null) { if (deteriorateAlwaysResetTimer > 0.0f) @@ -314,6 +337,20 @@ namespace Barotrauma.Items.Components return; } + if (currentFixerAction == FixActions.Tinker) + { + // this is a bit code rotty to interject it here, should be less reliant on returning + if (!CanTinker(CurrentFixer)) + { + 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 +364,8 @@ namespace Barotrauma.Items.Components } float fixDuration = MathHelper.Lerp(FixDurationLowSkill, FixDurationHighSkill, successFactor); + fixDuration /= 1 + CurrentFixer.GetStatValue(StatTypes.RepairSpeed) + currentRepairItem?.Prefab.AddedRepairSpeedMultiplier ?? 0f; + if (currentFixerAction == FixActions.Repair) { if (fixDuration <= 0.0f) @@ -354,6 +393,7 @@ namespace Barotrauma.Items.Components CurrentFixer.Position + Vector2.UnitY * 100.0f); } SteamAchievementManager.OnItemRepaired(item, CurrentFixer); + CurrentFixer.CheckTalents(AbilityEffectType.OnRepairComplete); } deteriorationTimer = Rand.Range(MinDeteriorationDelay, MaxDeteriorationDelay); wasBroken = false; @@ -399,6 +439,12 @@ namespace Barotrauma.Items.Components } } + private bool CanTinker(Character character) + { + if (!character.HasAbilityFlag(AbilityFlags.CanTinker)) { return false; } + return true; + } + partial void UpdateProjSpecific(float deltaTime); public void AdjustPowerConsumption(ref float powerConsumption) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/ConnectionPanel.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/ConnectionPanel.cs index 38c56e427..e7648c9ac 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/ConnectionPanel.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/ConnectionPanel.cs @@ -350,6 +350,7 @@ namespace Barotrauma.Items.Components } } } + Connections.Clear(); #if CLIENT rewireSoundChannel?.FadeOutAndDispose(); 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..ed67a0e46 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/RegExFindComponent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/RegExFindComponent.cs @@ -85,14 +85,15 @@ namespace Barotrauma.Items.Components 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 +134,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..540284013 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Terminal.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Terminal.cs @@ -45,6 +45,9 @@ namespace Barotrauma.Items.Components } } + [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) 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/Turret.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs index 3ea88ae11..0bed7d299 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs @@ -544,6 +544,7 @@ namespace Barotrauma.Items.Components Projectile launchedProjectile = null; bool loaderBroken = false; + bool isTinkering = false; for (int i = 0; i < ProjectileCount; i++) { var projectiles = GetLoadedProjectiles(); @@ -575,6 +576,7 @@ namespace Barotrauma.Items.Components projectiles = GetLoadedProjectiles(); if (projectiles.Any()) { break; } } + } } if (projectiles.Count == 0 && !LaunchWithoutProjectile) @@ -601,10 +603,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 && linkedItem.HasTag("turretammosource")) + { + isTinkering = repairable.IsTinkering; + } + } + if (!ignorePower) { var batteries = item.GetConnectedComponents(); float neededPower = powerConsumption; + if (isTinkering) + { + neededPower /= 1.25f; + } while (neededPower > 0.0001f && batteries.Count > 0) { batteries.RemoveAll(b => b.Charge <= 0.0001f || b.MaxOutPut <= 0.0001f); @@ -622,7 +639,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 +655,22 @@ namespace Barotrauma.Items.Components { foreach (Projectile projectile in projectiles) { - Launch(projectile.Item, character); + Launch(projectile.Item, character, isTinkering: isTinkering); } } else { - Launch(null, character); + Launch(null, character, isTinkering: isTinkering); } 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 +694,18 @@ 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, bool isTinkering = false) { reload = reloadTime; + if (isTinkering) + { + reload /= 1.25f; + } + + if (user != null) + { + reload /= 1 + user.GetStatValue(StatTypes.TurretAttackSpeed); + } if (projectile != null) { @@ -698,6 +729,10 @@ namespace Barotrauma.Items.Components if (projectileComponent != null) { projectileComponent.Attacker = user; + if (isTinkering) + { + projectileComponent.Attack.DamageMultiplier *= 1.25f; + } projectileComponent.Use(); projectile.GetComponent()?.Attach(item, projectile); projectileComponent.User = user; @@ -725,6 +760,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 +919,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) @@ -1067,7 +1091,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 +1116,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 +1191,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 +1255,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 +1284,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..baec2c0b0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Wearable.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Wearable.cs @@ -7,6 +7,7 @@ using System.Xml.Linq; using Barotrauma.Items.Components; using Barotrauma.Extensions; using Barotrauma.Networking; +using Barotrauma.Abilities; namespace Barotrauma { @@ -210,7 +211,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 +269,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); @@ -322,6 +324,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 +348,7 @@ namespace Barotrauma.Items.Components } picker = character; + for (int i = 0; i < wearableSprites.Length; i++ ) { var wearableSprite = wearableSprites[i]; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs index 0636069b9..db66ce9f0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs @@ -1903,17 +1903,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 +1949,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 +1972,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; } @@ -3029,6 +3038,8 @@ namespace Barotrauma } } + connections?.Clear(); + if (parentInventory != null) { if (parentInventory is CharacterInventory characterInventory) @@ -3054,6 +3065,8 @@ namespace Barotrauma body = null; } + CurrentHull = null; + if (StaticFixtures != null) { foreach (Fixture fixture in StaticFixtures) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs index c7256157a..e2c81690d 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; @@ -22,7 +23,7 @@ namespace Barotrauma public readonly bool CopyCondition; 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); @@ -30,6 +31,11 @@ namespace Barotrauma OutCondition = 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."); + } } } @@ -67,8 +73,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 +91,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 +290,7 @@ namespace Barotrauma /// public List Triggers; - private List fabricationRecipeElements = new List(); + private readonly List fabricationRecipeElements = new List(); private readonly Dictionary treatmentSuitability = new Dictionary(); @@ -513,6 +522,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 +755,20 @@ 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 + { + ConfigElement = element = CreateVariantXML(element, basePrefab); + } + } + string categoryStr = element.GetAttributeString("category", "Misc"); if (!Enum.TryParse(categoryStr, true, out MapEntityCategory category)) { @@ -1031,7 +1068,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 +1081,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 { @@ -1313,5 +1350,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/Map/Explosion.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Explosion.cs index 4e6759d60..a1958db8e 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; @@ -36,6 +34,8 @@ namespace Barotrauma private readonly string decal; private readonly float decalSize; + private readonly float itemRepairStrength; + public float EmpStrength { get; set; } public float BallastFloraDamage { get; set; } @@ -63,22 +63,24 @@ 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); + bool showEffects = element.GetAttributeBool("showeffects", true); - playTinnitus = element.GetAttributeBool("playtinnitus", true); + 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 +88,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 +112,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); @@ -180,6 +174,23 @@ namespace Barotrauma } } + 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)) { return; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs index ee40e20a9..d99fa611d 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); @@ -674,6 +674,9 @@ namespace Barotrauma Gap.UpdateHulls(); } + BackgroundSections?.Clear(); + submergedSections?.Clear(); + List fireSourcesToRemove = new List(FireSources); foreach (FireSource fireSource in fireSourcesToRemove) { @@ -1260,9 +1263,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..afb5f6086 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs @@ -325,7 +325,6 @@ namespace Barotrauma get { return LevelData.Seed; } } - public static float? ForcedDifficulty; public float Difficulty { @@ -3020,13 +3019,6 @@ namespace Barotrauma } } - public string GetWreckIDTag(string originalTag, Submarine wreck) - { - string shortSeed = ToolBox.StringToInt(LevelData.Seed + wreck?.Info.Name).ToString(); - if (shortSeed.Length > 6) { shortSeed = shortSeed.Substring(0, 6); } - return originalTag + "_" + shortSeed; - } - public bool IsCloseToStart(Vector2 position, float minDist) => IsCloseToStart(position.ToPoint(), minDist); public bool IsCloseToEnd(Vector2 position, float minDist) => IsCloseToEnd(position.ToPoint(), minDist); @@ -3891,12 +3883,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 +3927,9 @@ namespace Barotrauma UnsyncedExtraWalls = null; } + tempCells?.Clear(); cells = null; + cellGrid = null; if (bodies != null) { @@ -3916,6 +3937,9 @@ namespace Barotrauma bodies = null; } + StartLocation = null; + EndLocation = null; + Loaded = null; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelTrigger.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelTrigger.cs index 6259d240d..55850a421 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelTrigger.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelTrigger.cs @@ -264,7 +264,7 @@ namespace Barotrauma 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,7 +272,7 @@ namespace Barotrauma var otherTagsArray = element.GetAttributeStringArray("allowedothertriggertags", new string[0]); foreach (string tag in otherTagsArray) { - allowedOtherTriggerTags.Add(tag.ToLower()); + allowedOtherTriggerTags.Add(tag.ToLowerInvariant()); } } 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..ad5279bc9 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; } } @@ -515,7 +514,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/Structure.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs index 23644df10..80ba583ee 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs @@ -47,7 +47,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) @@ -955,6 +955,15 @@ namespace Barotrauma SoundPlayer.PlayDamageSound(attack.StructureSoundType, damageAmount, worldPosition, tags: Tags); } #endif + + if (Submarine != null && damageAmount > 0) + { + foreach (Character character in Character.CharacterList) + { + character.CheckTalents(AbilityEffectType.AfterSubmarineAttacked, Submarine); + } + } + return new AttackResult(damageAmount, null); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs index 2270e5d69..3e92a9aea 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; } @@ -1718,7 +1687,10 @@ namespace Barotrauma PhysicsBody.RemoveAll(); - GameMain.World.Clear(); + GameMain.World?.Clear(); + GameMain.World = null; + + GC.Collect(); Unloading = false; } @@ -1730,6 +1702,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 +1718,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..c24088ba5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineBody.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineBody.cs @@ -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/WayPoint.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/WayPoint.cs index 2f7152ffa..e6942d2bb 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/WayPoint.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/WayPoint.cs @@ -54,6 +54,8 @@ namespace Barotrauma set { spawnType = value; } } + public Action OnLinksChanged { get; set; } + public override string Name { get @@ -761,9 +763,16 @@ 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) @@ -986,14 +995,18 @@ namespace Barotrauma public override void ShallowRemove() { base.ShallowRemove(); - WayPointList.Remove(this); } public override void Remove() { base.Remove(); - + CurrentHull = null; + ConnectedGap = null; + Tunnel = 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/NetEntityEvent/NetEntityEvent.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/NetEntityEvent/NetEntityEvent.cs index f0b800508..fa35ae9aa 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/NetEntityEvent/NetEntityEvent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/NetEntityEvent/NetEntityEvent.cs @@ -23,6 +23,9 @@ namespace Barotrauma.Networking TeamChange, ObjectiveManagerState, AddToCrew, + UpdateExperience, + UpdateTalents, + UpdateMoney, } 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/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..c68215f53 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 { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Serialization/XMLExtensions.cs b/Barotrauma/BarotraumaShared/SharedSource/Serialization/XMLExtensions.cs index fb24f1a63..00c4f0517 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Serialization/XMLExtensions.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Serialization/XMLExtensions.cs @@ -12,7 +12,7 @@ namespace Barotrauma { public static class XMLExtensions { - public static string ParseContentPathFromUri(this XObject element) => ToolBox.ConvertAbsoluteToRelativePath(element.BaseUri); + public static string ParseContentPathFromUri(this XObject element) => Path.GetRelativePath(Environment.CurrentDirectory, element.BaseUri); public static XDocument TryLoadXml(string filePath) { @@ -52,7 +52,7 @@ namespace Barotrauma return null; } - if (doc.Root == null) return null; + if (doc.Root == null) { return null; } } return doc; @@ -60,20 +60,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; } @@ -94,7 +92,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); } @@ -106,10 +104,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(',', ','); @@ -133,11 +131,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 @@ -162,7 +160,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 @@ -184,7 +182,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; @@ -207,10 +205,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]; @@ -236,13 +234,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) { @@ -254,7 +255,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; @@ -272,7 +273,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; @@ -290,7 +291,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; @@ -308,10 +309,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]; @@ -332,10 +333,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]; @@ -357,13 +358,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") @@ -381,31 +382,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); } @@ -417,32 +418,32 @@ 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); } @@ -498,7 +499,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; } @@ -516,7 +517,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; } @@ -535,7 +536,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; } @@ -555,7 +556,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; } @@ -563,7 +564,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; } @@ -603,8 +606,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) { @@ -621,7 +623,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; } } @@ -651,7 +653,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); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs index 41c47a0b7..faa57d7b1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs @@ -309,6 +309,9 @@ namespace Barotrauma public readonly List> ReduceAffliction; + private readonly List giveExperiences; + private readonly List<(string identifier, float amount)> giveSkills; + public float Duration => duration; //only applicable if targeting NearbyCharacters or NearbyItems @@ -357,6 +360,9 @@ namespace Barotrauma Explosions = new List(); triggeredEvents = new List(); ReduceAffliction = new List>(); + giveExperiences = new List(); + giveSkills = new List<(string, float)>(); + tags = new HashSet(element.GetAttributeString("tags", "").Split(',')); OnlyInside = element.GetAttributeBool("onlyinside", false); OnlyOutside = element.GetAttributeBool("onlyoutside", false); @@ -486,13 +492,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++; @@ -626,6 +641,12 @@ namespace Barotrauma 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); @@ -1181,6 +1202,47 @@ namespace Barotrauma } } } + + int i = 0; + foreach (int giveExperience in giveExperiences) + { + Character targetCharacter = CharacterFromTarget(target); + if (targetCharacter != null && !targetCharacter.Removed) + { + targetCharacter?.Info?.GiveExperience(giveExperience, popupOffset: i * 25f); + i++; + } + } + + if (giveSkills.Any()) + { + foreach ((string skillIdentifier, float amount) in giveSkills) + { + Character targetCharacter = CharacterFromTarget(target); + if (targetCharacter != null && !targetCharacter.Removed) + { + if (skillIdentifier?.ToLowerInvariant() == "randomskill") + { + if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) + { + // don't let clients simulate random skill gain + continue; + } + targetCharacter.Info?.IncreaseSkillLevel(GetRandomSkill(), amount, targetCharacter.Position + Vector2.UnitY * (150.0f + i * 25f)); + + string GetRandomSkill() + { + return targetCharacter.Info?.Job?.Skills.Select(s => s.Identifier).GetRandom(); + } + } + else + { + targetCharacter.Info?.IncreaseSkillLevel(skillIdentifier?.ToLowerInvariant(), amount, targetCharacter.Position + Vector2.UnitY * (150.0f + i * 25f)); + } + i++; + } + } + } } if (FireSize > 0.0f && entity != null) @@ -1346,6 +1408,19 @@ namespace Barotrauma } ApplyProjSpecific(deltaTime, entity, targets, hull, position, playSound: true); + + 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 +1428,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) diff --git a/Barotrauma/BarotraumaShared/SharedSource/SteamAchievementManager.cs b/Barotrauma/BarotraumaShared/SharedSource/SteamAchievementManager.cs index 24c3db01d..dd975c774 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/SteamAchievementManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/SteamAchievementManager.cs @@ -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/Upgrades/UpgradePrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Upgrades/UpgradePrefab.cs index 06f4c29fc..88835bfc0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Upgrades/UpgradePrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Upgrades/UpgradePrefab.cs @@ -249,7 +249,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 201c9d7e9..ac830c575 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Utils/SafeIO.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Utils/SafeIO.cs @@ -138,6 +138,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); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Utils/SaveUtil.cs b/Barotrauma/BarotraumaShared/SharedSource/Utils/SaveUtil.cs index 105395eb2..7f881cad9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Utils/SaveUtil.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Utils/SaveUtil.cs @@ -296,20 +296,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..8d727bee0 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,6 +618,17 @@ 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 ""; } @@ -659,21 +648,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/Humpback.sub b/Barotrauma/BarotraumaShared/Submarines/Humpback.sub index 879c0d4f25ac4aa794cac744204c4263a4a9a1ca..79b880af6f902f324c992d577e9839c294cf4c78 100644 GIT binary patch delta 206572 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 z zAJlq(wTS>^FSd_g;7_2OkDxLla9Oy#2T#F%{aVYns)nl!L>hd12R?ze>g=A1yz$W_ z5c3IIps@Nu*CF63O^9lvVJo7@fV8|)kbVwM`<*Gh%H!hN(l>)DK@9McWXF%H+%yZ( zS(;{EFB}K78e+~xOK6k~209~hWk;)gPr!740`_916oX&Qv3I=@u(!PPg>uw81}#J` zb32%!LtHAP7XipCm)La!eJLivU-z&Xl>UDr#zc9^WBIGeSMv&&&ALdZ?@9pBc}73) z<8>v)D3xa2HC7v@?9182gYPW#&3r#u(d5WFHV)HGpB=j~0s2vJYj!rK;NNFCg?6BS zO7$`hvESl{GS?Mc?Dm7+ELN5lv%UKVXIs$E=N|~F>kI&1Ol$-CXdD5U3J_+vxhT8B zB9Nmr@z~zR{G-6C#$WFf13kpD6CkC9$589N9)nepAZ{Bla7OsvtR)yRXNBv)Yc`%= zLzGnUd+orR!%mY9z3`}5)Z4CKtY!ay2u<`z=NP6Mxp@&nexseJ;(#Rn>bfSSc6@-U0QUG$D1!qqPPzfoufzCH+7{awRA5^Rs-CYD!BLc7{m&LxOG=hB2n3yUkEb!h1z={8Vp92WU zPkt1Q6J<_&n{ifRjTZ$PZIQraB;O+SjE561r}6A)8Ar_W2MX_{=kN&h5=f>2Pwdr6 z>xZP$@UH&DThNAoNW)5d;q@Z`-JnnN0XRZ&;y&_yB9Si~@EVqD&a%Gq>q*hF1R2XO z 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"bNH*p_Fy4k`(en?=}WhL$hJhS^UyKSW7 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%0DN-dFobO7>0~fV1(-xi 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

0-OB85hl`C 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

Y04MlsTx3a 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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/changelog.txt b/Barotrauma/BarotraumaShared/changelog.txt index 02351378a..f5866ba31 100644 --- a/Barotrauma/BarotraumaShared/changelog.txt +++ b/Barotrauma/BarotraumaShared/changelog.txt @@ -1,3 +1,56 @@ +--------------------------------------------------------------------------------------------------------- +v0.1500.0.0 +--------------------------------------------------------------------------------------------------------- + +Additions and changes: +- Groundwork for the upcoming talent system: completing missions gives the characters experience points which can be used to unlock special abilities or buffs. Currently only the captain has talents implemented. +- Improved bot chatter when orders are being given, rearranged, or dismissed. +- Damage to arms reduces aiming accuracy. +- Crippled legs slow the player down more. +- Improvements to the blood particle effects when a character is bleeding. +- 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 fire 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. + +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. + +Fixes: +- 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. +- 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.0 --------------------------------------------------------------------------------------------------------- diff --git a/Barotrauma/BarotraumaShared/serversettings.xml b/Barotrauma/BarotraumaShared/serversettings.xml index 9b96f6b02..38cae3c14 100644 --- a/Barotrauma/BarotraumaShared/serversettings.xml +++ b/Barotrauma/BarotraumaShared/serversettings.xml @@ -24,6 +24,7 @@ startwhenclientsreadyratio="0.8" allowspectating="True" saveserverlogs="True" + linesperlogfile="800" allowragdollbutton="True" allowfiletransfers="True" voicechatenabled="True" diff --git a/Libraries/Farseer Physics Engine 3.5/Dynamics/World.cs b/Libraries/Farseer Physics Engine 3.5/Dynamics/World.cs index e950db8d9..53ce244d8 100644 --- a/Libraries/Farseer Physics Engine 3.5/Dynamics/World.cs +++ b/Libraries/Farseer Physics Engine 3.5/Dynamics/World.cs @@ -1034,6 +1034,7 @@ namespace FarseerPhysics.Dynamics body.DestroyProxies(); for (int i = 0; i < body.FixtureList.Count; i++) { + body.FixtureList[i].UserData = null; if (FixtureRemoved != null) FixtureRemoved(this, body, body.FixtureList[i]); } @@ -1041,6 +1042,8 @@ namespace FarseerPhysics.Dynamics body._world = null; BodyList.Remove(body); + body.UserData = null; + if (BodyRemoved != null) BodyRemoved(this, body); diff --git a/Libraries/XNATypes/RectangleF.cs b/Libraries/XNATypes/RectangleF.cs new file mode 100644 index 000000000..1459e1e90 --- /dev/null +++ b/Libraries/XNATypes/RectangleF.cs @@ -0,0 +1,551 @@ +// MIT License - Copyright (C) The Mono.Xna Team +// This file is subject to the terms and conditions defined in +// file 'LICENSE.txt', which is part of this source code package. + +using System; + +namespace Microsoft.Xna.Framework +{ + public struct RectangleF : IEquatable + { + #region Private Fields + + private static RectangleF emptyRectangle = new RectangleF(); + + #endregion + + #region Public Fields + + ///

+ /// The x coordinate of the top-left corner of this . + /// + + public float X; + + /// + /// The y coordinate of the top-left corner of this . + /// + + public float Y; + + /// + /// The width of this . + /// + + public float Width; + + /// + /// The height of this . + /// + + public float Height; + + #endregion + + #region Public Properties + + /// + /// Returns a with X=0, Y=0, Width=0, Height=0. + /// + public static RectangleF Empty + { + get { return emptyRectangle; } + } + + /// + /// Returns the x coordinate of the left edge of this . + /// + public float Left + { + get { return this.X; } + } + + /// + /// Returns the x coordinate of the right edge of this . + /// + public float Right + { + get { return (this.X + this.Width); } + } + + /// + /// Returns the y coordinate of the top edge of this . + /// + public float Top + { + get { return this.Y; } + } + + /// + /// Returns the y coordinate of the bottom edge of this . + /// + public float Bottom + { + get { return (this.Y + this.Height); } + } + + /// + /// Whether or not this has a and + /// of 0, and a of (0, 0). + /// + public bool IsEmpty + { + get + { + return ((((this.Width == 0) && (this.Height == 0)) && (this.X == 0)) && (this.Y == 0)); + } + } + + /// + /// The top-left coordinates of this . + /// + public Vector2 Location + { + get + { + return new Vector2(this.X, this.Y); + } + set + { + X = value.X; + Y = value.Y; + } + } + + /// + /// The width-height coordinates of this . + /// + public Vector2 Size + { + get + { + return new Vector2(this.Width, this.Height); + } + set + { + Width = value.X; + Height = value.Y; + } + } + + /// + /// A located in the center of this . + /// + /// + /// If or is an odd number, + /// the center point will be rounded down. + /// + public Vector2 Center + { + get + { + return new Vector2(this.X + (this.Width / 2f), this.Y + (this.Height / 2f)); + } + } + + #endregion + + #region Internal Properties + + internal string DebugDisplayString + { + get + { + return string.Concat( + this.X, " ", + this.Y, " ", + this.Width, " ", + this.Height + ); + } + } + + #endregion + + #region Constructors + + /// + /// Creates a new instance of struct, with the specified + /// position, width, and height. + /// + /// The x coordinate of the top-left corner of the created . + /// The y coordinate of the top-left corner of the created . + /// The width of the created . + /// The height of the created . + public RectangleF(float x, float y, float width, float height) + { + this.X = x; + this.Y = y; + this.Width = width; + this.Height = height; + } + + /// + /// Creates a new instance of struct, with the specified + /// location and size. + /// + /// The x and y coordinates of the top-left corner of the created . + /// The width and height of the created . + public RectangleF(Vector2 location, Vector2 size) + { + this.X = location.X; + this.Y = location.Y; + this.Width = size.X; + this.Height = size.Y; + } + + #endregion + + #region Operators + + /// + /// Compares whether two instances are equal. + /// + /// instance on the left of the equal sign. + /// instance on the right of the equal sign. + /// true if the instances are equal; false otherwise. + public static bool operator ==(RectangleF a, RectangleF b) + { + return ((a.X == b.X) && (a.Y == b.Y) && (a.Width == b.Width) && (a.Height == b.Height)); + } + + /// + /// Compares whether two instances are not equal. + /// + /// instance on the left of the not equal sign. + /// instance on the right of the not equal sign. + /// true if the instances are not equal; false otherwise. + public static bool operator !=(RectangleF a, RectangleF b) + { + return !(a == b); + } + + public static implicit operator RectangleF(Rectangle r) => new RectangleF(r.X, r.Y, r.Width, r.Height); + + #endregion + + #region Public Methods + + /// + /// Gets whether or not the provided coordinates lie within the bounds of this . + /// + /// The x coordinate of the point to check for containment. + /// The y coordinate of the point to check for containment. + /// true if the provided coordinates lie inside this ; false otherwise. + public bool Contains(int x, int y) + { + return ((((this.X <= x) && (x < (this.X + this.Width))) && (this.Y <= y)) && (y < (this.Y + this.Height))); + } + + /// + /// Gets whether or not the provided coordinates lie within the bounds of this . + /// + /// The x coordinate of the point to check for containment. + /// The y coordinate of the point to check for containment. + /// true if the provided coordinates lie inside this ; false otherwise. + public bool Contains(float x, float y) + { + return ((((this.X <= x) && (x < (this.X + this.Width))) && (this.Y <= y)) && (y < (this.Y + this.Height))); + } + + /// + /// Gets whether or not the provided lies within the bounds of this . + /// + /// The coordinates to check for inclusion in this . + /// true if the provided lies inside this ; false otherwise. + public bool Contains(Point value) + { + return ((((this.X <= value.X) && (value.X < (this.X + this.Width))) && (this.Y <= value.Y)) && (value.Y < (this.Y + this.Height))); + } + + /// + /// Gets whether or not the provided lies within the bounds of this . + /// + /// The coordinates to check for inclusion in this . + /// true if the provided lies inside this ; false otherwise. As an output parameter. + public void Contains(ref Point value, out bool result) + { + result = ((((this.X <= value.X) && (value.X < (this.X + this.Width))) && (this.Y <= value.Y)) && (value.Y < (this.Y + this.Height))); + } + + /// + /// Gets whether or not the provided lies within the bounds of this . + /// + /// The coordinates to check for inclusion in this . + /// true if the provided lies inside this ; false otherwise. + public bool Contains(Vector2 value) + { + return ((((this.X <= value.X) && (value.X < (this.X + this.Width))) && (this.Y <= value.Y)) && (value.Y < (this.Y + this.Height))); + } + + /// + /// Gets whether or not the provided lies within the bounds of this . + /// + /// The coordinates to check for inclusion in this . + /// true if the provided lies inside this ; false otherwise. As an output parameter. + public void Contains(ref Vector2 value, out bool result) + { + result = ((((this.X <= value.X) && (value.X < (this.X + this.Width))) && (this.Y <= value.Y)) && (value.Y < (this.Y + this.Height))); + } + + /// + /// Gets whether or not the provided lies within the bounds of this . + /// + /// The to check for inclusion in this . + /// true if the provided 's bounds lie entirely inside this ; false otherwise. + public bool Contains(RectangleF value) + { + return ((((this.X <= value.X) && ((value.X + value.Width) <= (this.X + this.Width))) && (this.Y <= value.Y)) && ((value.Y + value.Height) <= (this.Y + this.Height))); + } + + /// + /// Gets whether or not the provided lies within the bounds of this . + /// + /// The to check for inclusion in this . + /// true if the provided 's bounds lie entirely inside this ; false otherwise. As an output parameter. + public void Contains(ref RectangleF value, out bool result) + { + result = ((((this.X <= value.X) && ((value.X + value.Width) <= (this.X + this.Width))) && (this.Y <= value.Y)) && ((value.Y + value.Height) <= (this.Y + this.Height))); + } + + /// + /// Compares whether current instance is equal to specified . + /// + /// The to compare. + /// true if the instances are equal; false otherwise. + public override bool Equals(object obj) + { + return (obj is RectangleF) && this == ((RectangleF)obj); + } + + /// + /// Compares whether current instance is equal to specified . + /// + /// The to compare. + /// true if the instances are equal; false otherwise. + public bool Equals(RectangleF other) + { + return this == other; + } + + /// + /// Gets the hash code of this . + /// + /// Hash code of this . + public override int GetHashCode() + { + unchecked + { + var hash = 17; + hash = hash * 23 + X.GetHashCode(); + hash = hash * 23 + Y.GetHashCode(); + hash = hash * 23 + Width.GetHashCode(); + hash = hash * 23 + Height.GetHashCode(); + return hash; + } + } + + /// + /// Adjusts the edges of this by specified horizontal and vertical amounts. + /// + /// Value to adjust the left and right edges. + /// Value to adjust the top and bottom edges. + public void Inflate(int horizontalAmount, int verticalAmount) + { + X -= horizontalAmount; + Y -= verticalAmount; + Width += horizontalAmount * 2; + Height += verticalAmount * 2; + } + + /// + /// Adjusts the edges of this by specified horizontal and vertical amounts. + /// + /// Value to adjust the left and right edges. + /// Value to adjust the top and bottom edges. + public void Inflate(float horizontalAmount, float verticalAmount) + { + X -= (float)horizontalAmount; + Y -= (float)verticalAmount; + Width += (float)horizontalAmount * 2; + Height += (float)verticalAmount * 2; + } + + /// + /// Adjusts the edges of this by specified horizontal and vertical amounts. + /// + /// Value to adjust the edges. + public void Inflate(Vector2 amount) + { + Inflate(amount.X, amount.Y); + } + + /// + /// Gets whether or not the other intersects with this rectangle. + /// + /// The other rectangle for testing. + /// true if other intersects with this rectangle; false otherwise. + public bool Intersects(RectangleF value) + { + return value.Left < Right && + Left < value.Right && + value.Top < Bottom && + Top < value.Bottom; + } + + + /// + /// Gets whether or not the other intersects with this rectangle. + /// + /// The other rectangle for testing. + /// true if other intersects with this rectangle; false otherwise. As an output parameter. + public void Intersects(ref RectangleF value, out bool result) + { + result = value.Left < Right && + Left < value.Right && + value.Top < Bottom && + Top < value.Bottom; + } + + /// + /// Creates a new that contains overlapping region of two other rectangles. + /// + /// The first . + /// The second . + /// Overlapping region of the two rectangles. + public static RectangleF Intersect(RectangleF value1, RectangleF value2) + { + RectangleF rectangle; + Intersect(ref value1, ref value2, out rectangle); + return rectangle; + } + + /// + /// Creates a new that contains overlapping region of two other rectangles. + /// + /// The first . + /// The second . + /// Overlapping region of the two rectangles as an output parameter. + public static void Intersect(ref RectangleF value1, ref RectangleF value2, out RectangleF result) + { + if (value1.Intersects(value2)) + { + float right_side = MathF.Min(value1.X + value1.Width, value2.X + value2.Width); + float left_side = MathF.Max(value1.X, value2.X); + float top_side = MathF.Max(value1.Y, value2.Y); + float bottom_side = MathF.Min(value1.Y + value1.Height, value2.Y + value2.Height); + result = new RectangleF(left_side, top_side, right_side - left_side, bottom_side - top_side); + } + else + { + result = new RectangleF(0, 0, 0, 0); + } + } + + /// + /// Changes the of this . + /// + /// The x coordinate to add to this . + /// The y coordinate to add to this . + public void Offset(int offsetX, int offsetY) + { + X += offsetX; + Y += offsetY; + } + + /// + /// Changes the of this . + /// + /// The x coordinate to add to this . + /// The y coordinate to add to this . + public void Offset(float offsetX, float offsetY) + { + X += (float)offsetX; + Y += (float)offsetY; + } + + /// + /// Changes the of this . + /// + /// The x and y components to add to this . + public void Offset(Point amount) + { + X += amount.X; + Y += amount.Y; + } + + /// + /// Changes the of this . + /// + /// The x and y components to add to this . + public void Offset(Vector2 amount) + { + X += (float)amount.X; + Y += (float)amount.Y; + } + + /// + /// Returns a representation of this in the format: + /// {X:[] Y:[] Width:[] Height:[]} + /// + /// representation of this . + public override string ToString() + { + return "{X:" + X + " Y:" + Y + " Width:" + Width + " Height:" + Height + "}"; + } + + /// + /// Creates a new that completely contains two other rectangles. + /// + /// The first . + /// The second . + /// The union of the two rectangles. + public static RectangleF Union(RectangleF value1, RectangleF value2) + { + float x = MathF.Min(value1.X, value2.X); + float y = MathF.Min(value1.Y, value2.Y); + return new RectangleF(x, y, + Math.Max(value1.Right, value2.Right) - x, + Math.Max(value1.Bottom, value2.Bottom) - y); + } + + /// + /// Creates a new that completely contains two other rectangles. + /// + /// The first . + /// The second . + /// The union of the two rectangles as an output parameter. + public static void Union(ref RectangleF value1, ref RectangleF value2, out RectangleF result) + { + result.X = Math.Min(value1.X, value2.X); + result.Y = Math.Min(value1.Y, value2.Y); + result.Width = Math.Max(value1.Right, value2.Right) - result.X; + result.Height = Math.Max(value1.Bottom, value2.Bottom) - result.Y; + } + + public void AddPoint(Point point) + { + if (point.X < X) + { + Width += X - point.X; + X = point.X; + } + else if (point.X > Right) + { + Width += point.X - Right; + } + + if (point.Y < Y) + { + Height += Y - point.Y; + Y = point.Y; + } + else if (point.Y > Bottom) + { + Height += point.Y - Bottom; + } + } + + #endregion + } +} From e7b7c1a748ae2ce94e26d9362fa16bd1d9936503 Mon Sep 17 00:00:00 2001 From: Markus Isberg <3e849f2e5c@pm.me> Date: Fri, 3 Sep 2021 21:56:31 +0900 Subject: [PATCH 02/12] Unstable 0.1500.1.0 (BaroDev edition) --- .../ClientSource/Characters/CharacterInfo.cs | 1 + .../Characters/Health/AfflictionHusk.cs | 8 +- .../Characters/Health/CharacterHealth.cs | 723 ++++++------------ .../ClientSource/Characters/Limb.cs | 8 +- .../ClientSource/DebugConsole.cs | 8 +- .../ClientSource/GUI/GUIListBox.cs | 7 +- .../ClientSource/GUI/GUIProgressBar.cs | 6 +- .../ClientSource/GUI/TabMenu.cs | 22 +- .../ClientSource/GameSession/CrewManager.cs | 32 +- .../ClientSource/Items/CharacterInventory.cs | 46 +- .../Items/Components/GeneticMaterial.cs | 74 ++ .../Items/Components/ItemComponent.cs | 2 +- .../Items/Components/ItemContainer.cs | 3 + .../Components/Machines/Deconstructor.cs | 110 ++- .../Items/Components/Machines/MiniMap.cs | 74 +- .../Items/Components/RemoteController.cs | 22 + .../Items/Components/Signal/Connection.cs | 5 + .../Items/Components/Signal/Terminal.cs | 4 +- .../Items/Components/StatusHUD.cs | 92 ++- .../ClientSource/Items/Components/Wearable.cs | 2 +- .../ClientSource/Items/Inventory.cs | 15 +- .../ClientSource/Items/Item.cs | 9 +- .../ClientSource/Map/MapEntity.cs | 28 +- .../ClientSource/Map/Submarine.cs | 27 +- .../ClientSource/Particles/ParticleManager.cs | 2 +- .../ClientSource/Screens/NetLobbyScreen.cs | 1 + .../ClientSource/Screens/TestScreen.cs | 7 +- .../ClientSource/Sounds/SoundPlayer.cs | 3 +- .../ClientSource/Utils/SpreadsheetExport.cs | 4 +- .../BarotraumaClient/LinuxClient.csproj | 2 +- Barotrauma/BarotraumaClient/MacClient.csproj | 2 +- .../BarotraumaClient/WindowsClient.csproj | 2 +- .../BarotraumaServer/LinuxServer.csproj | 2 +- Barotrauma/BarotraumaServer/MacServer.csproj | 2 +- .../ServerSource/Characters/Character.cs | 9 + .../ServerSource/Characters/CharacterInfo.cs | 1 + .../GameModes/MultiPlayerCampaign.cs | 56 ++ .../Items/Components/GeneticMaterial.cs | 20 + .../Items/Components/Signal/Terminal.cs | 4 +- .../ServerSource/Networking/GameServer.cs | 6 + .../ServerSource/Networking/RespawnManager.cs | 6 + .../BarotraumaServer/WindowsServer.csproj | 2 +- .../Data/ContentPackages/Vanilla 0.9.xml | 3 + .../Characters/AI/EnemyAIController.cs | 12 +- .../AI/Objectives/AIObjectiveCombat.cs | 5 +- .../Characters/Animation/AnimController.cs | 2 +- .../Animation/FishAnimController.cs | 8 +- .../Animation/HumanoidAnimController.cs | 15 +- .../Characters/Animation/Ragdoll.cs | 6 + .../SharedSource/Characters/Attack.cs | 19 +- .../SharedSource/Characters/Character.cs | 43 +- .../SharedSource/Characters/CharacterInfo.cs | 33 +- .../Health/Afflictions/Affliction.cs | 64 +- .../Health/Afflictions/AfflictionHusk.cs | 12 +- .../Health/Afflictions/AfflictionPrefab.cs | 37 +- .../Characters/Health/CharacterHealth.cs | 72 +- .../SharedSource/Characters/Jobs/Job.cs | 4 +- .../SharedSource/Characters/Jobs/Skill.cs | 9 +- .../SharedSource/Characters/Limb.cs | 9 +- .../AbilityConditionAttackData.cs | 6 +- .../AbilityConditionAttackResult.cs | 2 +- .../AbilityConditionData.cs | 5 +- .../AbilityConditionHandsomeStranger.cs | 27 - .../AbilityConditionIsAiming.cs | 54 ++ .../AbilityConditionItem.cs | 5 +- .../AbilityConditionReduceAffliction.cs | 8 +- .../AbilityConditionSkill.cs | 32 + .../AbilityConditionAboveVitality.cs | 5 +- .../AbilityConditionCoauthor.cs | 26 + .../AbilityConditionDataless.cs | 6 +- .../AbilityConditionHasPermanentStat.cs | 23 + .../AbilityConditionHasStatusTag.cs | 31 + .../AbilityConditionInFriendlySubmarine.cs | 15 + .../AbilityConditionInHull.cs | 15 + .../AbilityConditionLevelsBehindHighest.cs | 20 + .../AbilityConditionMission.cs | 6 +- .../AbilityConditionServerRandom.cs | 8 +- .../Talents/Abilities/AbilityInterfaces.cs | 32 + .../Talents/Abilities/AbilityObjects.cs | 85 ++ .../Talents/Abilities/CharacterAbility.cs | 13 +- .../CharacterAbilityApplyStatusEffects.cs | 37 +- ...racterAbilityApplyStatusEffectsToAllies.cs | 30 + ...cterAbilityApplyStatusEffectsToAttacker.cs | 20 + ...rAbilityApplyStatusEffectsToNearestAlly.cs | 7 +- ...erAbilityApplyStatusEffectsToRandomAlly.cs | 7 +- .../Abilities/CharacterAbilityGiveMoney.cs | 29 +- .../CharacterAbilityGivePermanentStat.cs | 8 +- .../CharacterAbilityGiveResistance.cs | 13 +- .../Abilities/CharacterAbilityGiveStat.cs | 5 +- .../CharacterAbilityModifyAttackData.cs | 15 +- .../CharacterAbilityModifyResistance.cs | 9 +- .../CharacterAbilityModifyStatToSkill.cs | 52 ++ .../Abilities/CharacterAbilityModifyValue.cs | 31 +- .../CharacterAbilityResetPermanentStat.cs | 2 +- .../Abilities/CharacterAbilityRevive.cs | 29 + .../CharacterAbilityAlienHoarder.cs | 41 + .../CharacterAbilityApprenticeship.cs | 4 +- .../CharacterAbilityByTheBook.cs | 36 + .../CharacterAbilityInsurancePolicy.cs | 31 +- .../CharacterAbilityPsychoClown.cs | 6 +- .../CharacterAbilityRegenerateLoot.cs | 2 + .../CharacterAbilityStonewall.cs | 8 +- .../CharacterAbilityTandemFire.cs | 1 + .../AbilityGroups/CharacterAbilityGroup.cs | 6 +- .../CharacterAbilityGroupEffect.cs | 1 + .../CharacterAbilityGroupInterval.cs | 2 + .../Characters/Talents/CharacterTalent.cs | 3 +- .../Characters/Talents/TalentPrefab.cs | 57 +- .../Characters/Talents/TalentTree.cs | 32 +- .../SharedSource/DebugConsole.cs | 77 +- .../BarotraumaShared/SharedSource/Enums.cs | 19 +- .../SharedSource/Events/Missions/Mission.cs | 13 +- .../GameSession/GameModes/CampaignMode.cs | 1 + .../GameModes/MultiPlayerCampaign.cs | 6 + .../SharedSource/GameSession/GameSession.cs | 17 +- .../SharedSource/Items/CharacterInventory.cs | 2 +- .../Items/Components/GeneticMaterial.cs | 179 +++++ .../Items/Components/Holdable/MeleeWeapon.cs | 3 +- .../Items/Components/Holdable/RangedWeapon.cs | 4 +- .../Items/Components/ItemComponent.cs | 2 +- .../Items/Components/ItemContainer.cs | 213 ++++-- .../Components/Machines/Deconstructor.cs | 287 +++++-- .../Items/Components/Machines/Fabricator.cs | 23 +- .../Items/Components/Machines/Steering.cs | 5 +- .../Items/Components/Power/PowerTransfer.cs | 10 + .../Items/Components/RemoteController.cs | 96 +++ .../Items/Components/Repairable.cs | 2 +- .../Components/Signal/ConnectionPanel.cs | 36 +- .../Items/Components/Signal/Terminal.cs | 10 +- .../Items/Components/Signal/Wire.cs | 6 +- .../SharedSource/Items/Components/Turret.cs | 24 +- .../SharedSource/Items/Components/Wearable.cs | 3 + .../SharedSource/Items/Inventory.cs | 2 +- .../SharedSource/Items/Item.cs | 27 +- .../SharedSource/Items/ItemInventory.cs | 10 +- .../SharedSource/Items/ItemPrefab.cs | 21 +- .../SharedSource/Map/Explosion.cs | 15 +- .../SharedSource/Map/Levels/Level.cs | 5 +- .../SharedSource/Map/Structure.cs | 4 +- .../StatusEffects/StatusEffect.cs | 328 +++++--- .../SharedSource/TextManager.cs | 37 + .../BarotraumaShared/Submarines/Humpback.sub | Bin 207745 -> 207565 bytes Barotrauma/BarotraumaShared/changelog.txt | 37 + 143 files changed, 2928 insertions(+), 1356 deletions(-) create mode 100644 Barotrauma/BarotraumaClient/ClientSource/Items/Components/GeneticMaterial.cs create mode 100644 Barotrauma/BarotraumaClient/ClientSource/Items/Components/RemoteController.cs create mode 100644 Barotrauma/BarotraumaServer/ServerSource/Items/Components/GeneticMaterial.cs delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionHandsomeStranger.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/AbilityConditionSkill.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/AbilityConditionHasPermanentStat.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/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/AbilityConditionLevelsBehindHighest.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/CharacterAbilityApplyStatusEffectsToAllies.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffectsToAttacker.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyStatToSkill.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityRevive.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityAlienHoarder.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityByTheBook.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Items/Components/GeneticMaterial.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Items/Components/RemoteController.cs diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterInfo.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterInfo.cs index c27aab975..c5b38a31e 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterInfo.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterInfo.cs @@ -623,6 +623,7 @@ namespace Barotrauma bool removeOnDeath = inc.ReadBoolean(); ch.ChangeSavedStatValue((StatTypes)statType, statValue, statIdentifier, removeOnDeath); } + ch.ExperiencePoints = inc.ReadUInt16(); return ch; } 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 d7b75bbd3..73e11cd6a 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,108 +48,18 @@ 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; @@ -166,12 +76,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; @@ -255,6 +162,12 @@ namespace Barotrauma get { return cprButton; } } + public GUIComponent InventorySlotContainer + { + get; + private set; + } + public float HealthBarPulsateTimer { get { return healthBarPulsateTimer; } @@ -279,56 +192,90 @@ 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, 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); + }); - 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) - { - Stretch = true, - RelativeSpacing = 0.03f - }; + 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; } - var limbSelection = new GUICustomComponent(new RectTransform(new Vector2(0.6f, 1.0f), paddedHealthWindow.RectTransform), + //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; + } + }); + + var limbSelection = new GUICustomComponent(new RectTransform(new Vector2(0.4f, 1.0f), characterIndicatorArea.RectTransform), (spriteBatch, component) => { DrawHealthWindow(spriteBatch, component.RectTransform.Rect, true); @@ -353,149 +300,31 @@ 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, 0.7f), 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") - { - 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); + characterIndicatorArea.Recalculate(); - 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) - { - 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") + cprButton = new GUIButton(new RectTransform(new Vector2(afflictionIconContainer.RectTransform.RelativeSize.X, 0.3f), characterIndicatorArea.RectTransform, Anchor.BottomRight, scaleBasis: ScaleBasis.Smallest), text: "", style: "CPRButton") { OnClicked = (button, userData) => { @@ -518,9 +347,34 @@ namespace Barotrauma return true; }, ToolTip = TextManager.Get("doctor.cprobjective"), + IgnoreLayoutGroups = true, Visible = false }; + 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) + { + Visible = false + }; + healthShadowSize = 1.0f; + + healthBar = new GUIProgressBar(new RectTransform(Vector2.One, healthBarHolder.RectTransform, Anchor.BottomRight), + barSize: 1.0f, color: GUI.Style.HealthBarColorHigh, style: "CharacterHealthBar") + { + HoverCursor = CursorState.Hand, + ToolTip = TextManager.GetWithVariable("hudbutton.healthinterface", "[key]", GameMain.Config.KeyBindText(InputType.Health)), + Enabled = true + }; + UpdateAlignment(); SuicideButton = new GUIButton(new RectTransform(new Vector2(0.1f, 0.02f), GUI.Canvas, Anchor.TopCenter) @@ -542,8 +396,8 @@ namespace Barotrauma } else { - var causeOfDeath = GetCauseOfDeath(); - Character.Controlled.Kill(causeOfDeath.type, causeOfDeath.affliction); + var (type, affliction) = GetCauseOfDeath(); + Character.Controlled.Kill(type, affliction); Character.Controlled = null; } } @@ -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) @@ -799,7 +653,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) @@ -846,7 +701,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) @@ -856,6 +711,20 @@ namespace Barotrauma OpenHealthWindow = null; } + foreach (GUIComponent afflictionIcon in afflictionIconContainer.Content.Children) + { + if (!(afflictionIcon.UserData is Affliction affliction)) { continue; } + var btn = afflictionIcon.GetChild(); + if (affliction.AppliedAsFailedTreatmentTime > Timing.TotalTime - 1.0 && btn.FlashTimer <= 0.0f) + { + btn.Flash(GUI.Style.Red); + } + else if (affliction.AppliedAsSuccessfulTreatmentTime > Timing.TotalTime - 1.0 && btn.FlashTimer <= 0.0f) + { + btn.Flash(GUI.Style.Green); + } + } + if (GUI.MouseOn != null && GUI.MouseOn.UserData is string str && str == "selectaffliction") { Affliction affliction = GUI.MouseOn.Parent.UserData as Affliction; @@ -872,7 +741,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"); @@ -915,6 +794,13 @@ namespace Barotrauma UpdateAfflictionContainer(selectedLimb); currentDisplayedLimb = selectedLimb; } + + 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); + } } if (Character.IsDead) @@ -953,16 +839,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) @@ -979,11 +855,6 @@ namespace Barotrauma draggingMed = null; } } - - /*if (GUI.MouseOn?.UserData is Affliction affliction) - { - ShowAfflictionInfo(affliction, afflictionInfoContainer); - }*/ } else { @@ -1038,17 +909,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; } @@ -1057,7 +922,7 @@ namespace Barotrauma if (GUI.DisableHUD) { return; } if (OpenHealthWindow == this) { - healthInterfaceFrame.AddToGUIUpdateList(); + healthWindow.AddToGUIUpdateList(); afflictionTooltip?.AddToGUIUpdateList(); } else if (Character.Controlled == Character && !CharacterHUD.IsCampaignInterfaceOpen) @@ -1114,22 +979,22 @@ 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>(); + List<(Affliction affliction, string text)> statusIcons = new List<(Affliction affliction, string text)>(); if (Character.CurrentHull == null || Character.CurrentHull.LethalPressure > 5.0f) - statusIcons.Add(new Pair(pressureAffliction, TextManager.Get("PressureHUDWarning"))); + 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; @@ -1146,9 +1011,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)); @@ -1168,12 +1033,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, @@ -1191,7 +1050,7 @@ namespace Barotrauma if (highlightedAfflictionIcon != null) { - string nameTooltip = highlightedAfflictionIcon.Second; + string nameTooltip = highlightedAfflictionIcon.Value.text; Vector2 offset = GUI.Font.MeasureString(nameTooltip); GUI.DrawString(spriteBatch, @@ -1258,8 +1117,6 @@ namespace Barotrauma private void UpdateAfflictionContainer(LimbHealth selectedLimb) { - selectedLimbText.Text = selectedLimb == null ? "" : selectedLimb.Name; - if (selectedLimb == null) { afflictionIconContainer.Content.ClearChildren(); @@ -1279,28 +1136,30 @@ namespace Barotrauma 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"); - //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); + GetSuitableTreatments(treatmentSuitability, normalize: 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; + } + } - //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)) + var child = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.3f), afflictionIconContainer.Content.RectTransform, Anchor.TopCenter)) { Stretch = true, UserData = affliction @@ -1345,8 +1204,9 @@ namespace Barotrauma 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"); nameText.Text = ToolBox.LimitString(nameText.Text, nameText.Font, nameText.Rect.Width); + nameText.RectTransform.MinSize = new Point(0, (int)(nameText.TextSize.Y * 1.25f)); - new GUIProgressBar(new RectTransform(new Vector2(1.0f, 0.1f), child.RectTransform), 0.0f, afflictionEffectColor, style: "GUIAfflictionBar") + new GUIProgressBar(new RectTransform(new Vector2(1.0f, 0.15f), child.RectTransform), 0.0f, afflictionEffectColor, style: "GUIAfflictionBar") { UserData = "afflictionstrength" }; @@ -1354,6 +1214,21 @@ namespace Barotrauma child.Recalculate(); } + 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; + } + buttonToSelect?.OnClicked(buttonToSelect, "selectaffliction"); afflictionIconContainer.RecalculateChildren(); @@ -1367,15 +1242,25 @@ 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: "GUIButtonRound") { - CanBeFocused = false + UserData = item, + ToolTip = $"‖color:255,255,255,255‖{item.Name}‖color:end‖" + '\n' + item.Description, + 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; + } }; Sprite itemSprite = item.InventoryIcon ?? item.sprite; Color itemColor = itemSprite == item.sprite ? item.SpriteColor : item.InventoryIconColor; @@ -1383,11 +1268,11 @@ namespace Barotrauma itemSprite, scaleToFit: true) { CanBeFocused = false, - Color = itemColor, + Color = itemColor * 0.9f, HoverColor = itemColor, - SelectedColor = itemColor + SelectedColor = itemColor, + DisabledColor = itemColor * 0.7f }; - itemSlot.ToolTip = item.Name; } recommendedTreatmentContainer.RecalculateChildren(); @@ -1399,11 +1284,6 @@ namespace Barotrauma int dmgPerSecond = Math.Sign(second.DamagePerSecond - first.DamagePerSecond); 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)); - //}); } private void CreateAfflictionInfoElements(GUIComponent parent, Affliction affliction) @@ -1460,7 +1340,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) @@ -1480,21 +1359,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(); @@ -1507,126 +1372,6 @@ 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) { foreach (Affliction affliction in afflictions) @@ -1634,12 +1379,6 @@ namespace Barotrauma var child = afflictionIconContainer.Content.FindChild(affliction); var afflictionStrengthBar = child.GetChildByUserData("afflictionstrength") as GUIProgressBar; afflictionStrengthBar.BarSize = affliction.Strength / affliction.Prefab.MaxStrength; - - if (afflictionInfoContainer.UserData == affliction) - { - UpdateAfflictionInfo(afflictionInfoContainer.Content, affliction); - } - if (afflictionTooltip != null && afflictionTooltip.UserData == affliction) { UpdateAfflictionInfo(afflictionTooltip.Content, affliction); @@ -1678,8 +1417,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; } @@ -1701,35 +1439,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) @@ -1757,10 +1466,6 @@ namespace Barotrauma if (PlayerInput.PrimaryMouseButtonClicked() && highlightedLimbIndex > -1) { selectedLimbIndex = highlightedLimbIndex; - //afflictionContainer.ClearChildren(); - afflictionIconContainer.ClearChildren(); - afflictionInfoContainer.ClearChildren(); - afflictionInfoContainer.UserData = null; } } @@ -1931,14 +1636,14 @@ 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); } @@ -1954,7 +1659,7 @@ namespace Barotrauma 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; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Limb.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Limb.cs index f4c76ac1f..c9c4d7300 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Limb.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Limb.cs @@ -632,7 +632,7 @@ 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(); @@ -678,7 +678,7 @@ namespace Barotrauma if (!hideLimb) { var deformSprite = DeformSprite; - if (deformSprite != null) + if (deformSprite != null && !disableDeformations) { if (ActiveDeformations.Any()) { @@ -999,10 +999,12 @@ namespace Barotrauma } float textureScale = wearable.InheritTextureScale ? TextureScale : wearable.Scale; + float rotation = -body.DrawRotation - wearable.Rotation * Dir; + 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, + origin, rotation, Scale * textureScale, spriteEffect, depth); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs b/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs index 69c8a1836..d8cb533ed 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs @@ -1320,11 +1320,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) @@ -1334,9 +1336,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); } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIListBox.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIListBox.cs index ff2d3c4a7..2d0e44691 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIListBox.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIListBox.cs @@ -251,6 +251,11 @@ namespace Barotrauma 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) { @@ -570,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; 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/TabMenu.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs index 57c248466..70c0e9f76 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs @@ -1219,7 +1219,11 @@ namespace Barotrauma GUIFrame characterInfoFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.3f), talentInfoLayoutGroup.RectTransform, Anchor.TopLeft), style: null); GUILayoutGroup characterInfoColumn = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f), characterInfoFrame.RectTransform, anchor: Anchor.TopLeft), childAnchor: Anchor.TopLeft, isHorizontal: true); - CreateCharacterSheet(characterInfoColumn); + // move to a different tab menu + if (GameSettings.VerboseLogging) + { + CreateCharacterSheet(characterInfoColumn); + } if (!TalentTree.JobTalentTrees.TryGetValue(controlledCharacter.Info.Job.Prefab.Identifier, out TalentTree talentTree)) { return; } @@ -1254,7 +1258,7 @@ namespace Barotrauma Stretch = true, }; - foreach (Talent talent in talentOption.Talents) + foreach (TalentPrefab talent in talentOption.Talents) { int optionPadding = GUI.IntScale(10); GUIFrame talentFrame = new GUIFrame(new RectTransform(new Point(talentOptionFrame.Rect.Width, talentOptionFrame.Rect.Height - optionPadding), talentOptionLayoutGroup.RectTransform), style: null) @@ -1269,13 +1273,13 @@ namespace Barotrauma GUIButton talentButton = new GUIButton(new RectTransform(Vector2.One, talentFrame.RectTransform, anchor: Anchor.Center), style: "TalentFrame") { - ToolTip = $"{TextManager.Get("talentname." + talent.Identifier, returnNull: true) ?? talent.Identifier} \n\n{TextManager.Get("talentdescription." + talent.Identifier, returnNull: true) ?? string.Empty}", + ToolTip = $"{talent.DisplayName}\n\n{talent.Description}", UserData = talent.Identifier, PressedColor = pressedColor, OnClicked = (button, userData) => { - talentTitleText.Text = TextManager.Get("talentname." + talent.Identifier, returnNull: true) ?? string.Empty; - talentDescriptionText.Text = TextManager.Get("talentdescription." + talent.Identifier, returnNull: true) ?? string.Empty; + talentTitleText.Text = talent.DisplayName; + talentDescriptionText.Text = talent.Description; // deselect other buttons in tier by removing their selected talents from pool foreach (GUIButton guiButton in talentOptionLayoutGroup.GetAllChildren()) @@ -1440,10 +1444,10 @@ namespace Barotrauma private readonly StatTypes[] combatStats = new StatTypes[] { - StatTypes.MaximumHealthMultiplier, - StatTypes.MovementSpeed, - StatTypes.SwimmingSpeed, - StatTypes.RepairSpeed, + StatTypes.MeleeAttackMultiplier, + StatTypes.MeleeAttackSpeed, + StatTypes.RangedAttackSpeed, + StatTypes.TurretAttackSpeed, }; private readonly StatTypes[] miscStats = new StatTypes[] diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs index ac4b7ed69..520e873f5 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs @@ -99,7 +99,9 @@ namespace Barotrauma crewList = new GUIListBox(new RectTransform(Vector2.One, crewArea.RectTransform), style: null, isScrollBarOnDefaultSide: false) { AutoHideScrollBar = false, + CanBeFocused = false, CanDragElements = true, + CanInteractWhenUnfocusable = true, OnSelected = (component, userData) => false, SelectMultiple = false, Spacing = (int)(GUI.Scale * 10), @@ -770,7 +772,7 @@ namespace Barotrauma if (IsSinglePlayer) { character.SetOrder(order, option, priority, orderGiver, speak: orderGiver != character); - string message = order?.GetChatMessage(character.Name, orderGiver.CurrentHull?.DisplayName, givingOrderToSelf: character == orderGiver, orderOption: option, priority: priority); + string message = order?.GetChatMessage(character.Name, orderGiver?.CurrentHull?.DisplayName, givingOrderToSelf: character == orderGiver, orderOption: option, priority: priority); orderGiver?.Speak(message); } else if (orderGiver != null) @@ -1905,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() @@ -2580,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( @@ -2728,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( @@ -2766,7 +2778,7 @@ namespace Barotrauma if (checkIfOrderCanBeHeard && !disableNode) { - disableNode = !CanSomeoneHearCharacter(); + disableNode = !CanCharacterBeHeard(); } var mustSetOptionOrTarget = order.HasOptions; @@ -2827,7 +2839,7 @@ namespace Barotrauma if (disableNode) { node.CanBeFocused = icon.CanBeFocused = false; - CreateBlockIcon(node.RectTransform, tooltip: TextManager.Get("nocharactercanhear")); + CreateBlockIcon(node.RectTransform, tooltip: TextManager.Get(characterContext == null ? "nocharactercanhear" : "thischaractercanthear")); } else if (hotkey >= 0) { @@ -2999,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, tooltip: TextManager.Get("nocharactercanhear")); + CreateBlockIcon(node.RectTransform, tooltip: TextManager.Get(characterContext == null ? "nocharactercanhear" : "thischaractercanthear")); } else if (hotkey >= 0) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/CharacterInventory.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/CharacterInventory.cs index 953594247..919a36ab8 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/CharacterInventory.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/CharacterInventory.cs @@ -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; } @@ -359,7 +367,7 @@ 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])) { @@ -383,7 +391,7 @@ namespace Barotrauma continue; } - if (HideSlot(i)) { continue; } + if (HideSlot(i) || SlotTypes[i] == InvSlotType.HealthInterface) { continue; } if (PersonalSlots.HasFlag(SlotTypes[i])) { SlotPositions[i] = new Vector2(personalSlotX, personalSlotY); @@ -399,7 +407,7 @@ 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); @@ -414,7 +422,7 @@ 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])) { @@ -436,7 +444,7 @@ namespace Barotrauma SlotPositions[i] = new Vector2(rightSlot ? handSlotX : handSlotX - visualSlots[0].Rect.Width - Spacing, personalSlotY); continue; } - if (!HideSlot(i)) { continue; } + if (!HideSlot(i) || SlotTypes[i] == InvSlotType.HealthInterface) { continue; } SlotPositions[i] = new Vector2(x, GameMain.GraphicsHeight - bottomOffset); x += visualSlots[i].Rect.Width + Spacing; } @@ -450,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); @@ -462,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); @@ -479,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) @@ -679,7 +697,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); } @@ -858,7 +876,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; } @@ -1153,7 +1173,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(); @@ -1207,7 +1227,7 @@ namespace Barotrauma highlightedQuickUseSlot = visualSlots[i]; } - if (!slots[i].First().AllowedSlots.Any(a => a == InvSlotType.Any)) + if (!slots[i].First().AllowedSlots.Any(a => a == InvSlotType.Any) || SlotTypes[i] == InvSlotType.HealthInterface) { continue; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/GeneticMaterial.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/GeneticMaterial.cs new file mode 100644 index 000000000..981c388a2 --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/GeneticMaterial.cs @@ -0,0 +1,74 @@ +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)) + { + string mergedMaterialName = materialName; + foreach (Item containedItem in item.ContainedItems) + { + var containedMaterial = containedItem.GetComponent(); + if (containedMaterial == null) { continue; } + mergedMaterialName += ", " + containedMaterial.materialName; + } + name = TextManager.GetWithVariable("entityname.geneticmaterial", "[type]", 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()); + } + } + + 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/ItemComponent.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemComponent.cs index faaa76c27..5e5a9357a 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemComponent.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemComponent.cs @@ -621,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..4a1049947 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemContainer.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemContainer.cs @@ -58,6 +58,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; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Deconstructor.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Deconstructor.cs index 24149ae69..62c485a67 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Deconstructor.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Deconstructor.cs @@ -14,12 +14,19 @@ 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(0.0f, true)] + public float InfoAreaWidth { get; set; } + partial void InitProjSpecific(XElement element) { CreateGUI(); @@ -39,6 +46,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 +68,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 +100,62 @@ 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"); + 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, 1f), bottomFrame.RectTransform, Anchor.CenterLeft), style: null); + 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; + infoArea.Text = string.Empty; + if (IsActive) + { + activateButton.Text = TextManager.Get("DeconstructorCancel"); + 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 = inputContainer.Inventory.AllItems.Any(); + activateButton.Text = TextManager.Get(ActivateButtonText); + }; } public override bool Select(Character character) @@ -126,14 +194,30 @@ namespace Barotrauma.Items.Components private void DrawOverLay(SpriteBatch spriteBatch, GUICustomComponent overlayComponent) { overlayComponent.RectTransform.SetAsLastChild(); - if (!(inputContainer?.Inventory?.visualSlots is { } visualSlots)) { return; } - var lastSlot = 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/MiniMap.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs index 0e9e7b897..b0aee591b 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs @@ -403,7 +403,7 @@ namespace Barotrauma.Items.Components 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 }; - miniMapFrame = CreateMiniMap(item.Submarine, miniMapContainer, MiniMapSettings.Default, null, out hullStatusComponents); + miniMapFrame = CreateMiniMap(item.Submarine, submarineContainer, MiniMapSettings.Default, null, out hullStatusComponents); IEnumerable pointsOfInterest = Item.ItemList.Where(it => it.Submarine == item.Submarine && !it.HiddenInGame && !it.NonInteractable && it.GetComponent() != null); electricalFrame = CreateMiniMap(item.Submarine, miniMapContainer, new MiniMapSettings(createHullElements: false), pointsOfInterest, out electricalMapComponents); @@ -458,7 +458,7 @@ namespace Barotrauma.Items.Components CreateHUD(); } - if (PlayerInput.PrimaryMouseButtonDown()) + if (PlayerInput.PrimaryMouseButtonDown() && currentMode != MiniMapMode.HullStatus) { if (GUI.MouseOn == scissorComponent || scissorComponent.IsParentOf(GUI.MouseOn)) { @@ -466,20 +466,16 @@ namespace Barotrauma.Items.Components } } - float newZoom = Zoom; - if (Math.Abs(PlayerInput.ScrollWheelSpeed) > 0 && (GUI.MouseOn == scissorComponent || scissorComponent.IsParentOf(GUI.MouseOn))) + if (currentMode != MiniMapMode.HullStatus && Math.Abs(PlayerInput.ScrollWheelSpeed) > 0 && (GUI.MouseOn == scissorComponent || scissorComponent.IsParentOf(GUI.MouseOn))) { - newZoom = Math.Clamp(Zoom + PlayerInput.ScrollWheelSpeed / 1000.0f * Zoom, minZoom, maxZoom); + 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; } - recalculate |= !MathUtils.NearlyEqual(Zoom, newZoom); - Zoom = newZoom; - - Vector2 elementScale = new Vector2(Zoom); - if (dragMapStart is { } dragStart) { if (dragMap || Vector2.DistanceSquared(dragStart, PlayerInput.MousePosition) > GUI.IntScale(dragTreshold * dragTreshold)) @@ -487,16 +483,11 @@ namespace Barotrauma.Items.Components mapOffset.X += PlayerInput.MouseSpeed.X; mapOffset.Y += PlayerInput.MouseSpeed.Y; - recalculate |= PlayerInput.MouseSpeed != Vector2.Zero; + recalculate = true; dragMap = true; } } - var (maxWidth, maxHeight) = miniMapContainer.Rect.Size.ToVector2() / 2f / Zoom; - - mapOffset.X = Math.Clamp(mapOffset.X, -maxWidth, maxWidth); - mapOffset.Y = Math.Clamp(mapOffset.Y, -maxHeight, maxHeight); - if (!PlayerInput.PrimaryMouseButtonHeld()) { dragMapStart = null; @@ -505,10 +496,15 @@ namespace Barotrauma.Items.Components if (recalculate) { - miniMapContainer.RectTransform.LocalScale = elementScale; + miniMapContainer.RectTransform.LocalScale = new Vector2(Zoom); miniMapContainer.RectTransform.RecalculateChildren(true, true); miniMapContainer.RectTransform.AbsoluteOffset = mapOffset.ToPoint(); recalculate = false; + + var (maxWidth, maxHeight) = miniMapContainer.Rect.Size.ToVector2() / 2f / Zoom; + + mapOffset.X = Math.Clamp(mapOffset.X, -maxWidth, maxWidth); + mapOffset.Y = Math.Clamp(mapOffset.Y, -maxHeight, maxHeight); } // is there a better way to do this? @@ -583,6 +579,7 @@ namespace Barotrauma.Items.Components private void DrawHUDFront(SpriteBatch spriteBatch, GUICustomComponent container) { // TODO remove + #warning remove if (currentMode == MiniMapMode.HullCondition) { const string wipText = "work in progress"; @@ -757,7 +754,7 @@ namespace Barotrauma.Items.Components foreach (Item foundItem in foundItems) { RelativeEntityRect scaledRect = new RelativeEntityRect(dockedBorders, foundItem.WorldRect); - Vector2 pos = (scaledRect.PositionRelativeTo(parentRect, skipOffset: true) + scaledRect.SizeRelativeTo(parentRect) / 2f) / Zoom; + Vector2 pos = scaledRect.PositionRelativeTo(parentRect, skipOffset: true) + scaledRect.SizeRelativeTo(parentRect) / 2f; positions.Add(pos); } @@ -873,10 +870,14 @@ namespace Barotrauma.Items.Components string line1 = gapOpenSum > 0.1f ? TextManager.Get("MiniMapHullBreach") : string.Empty; Color line1Color = GUI.Style.Red; - string line2 = oxygenAmount == null ? TextManager.Get("MiniMapAirQualityUnavailable") : TextManager.AddPunctuation(':', TextManager.Get("MiniMapAirQuality"), +(int)oxygenAmount + " %"); + 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); - string line3 = waterAmount == null ? TextManager.Get("MiniMapWaterLevelUnavailable") : TextManager.AddPunctuation(':', TextManager.Get("MiniMapWaterLevel"), (int)(waterAmount * 100.0f) + " %"); + 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); @@ -958,7 +959,7 @@ namespace Barotrauma.Items.Components Rectangle parentRect = container.Rect; if (miniMapFrame is { } miniMap) { parentRect = miniMap.Rect; } - DrawSubmarine(spriteBatch, parentRect); + DrawSubmarine(spriteBatch); } if (Voltage < MinVoltage) { return; } @@ -979,7 +980,7 @@ namespace Barotrauma.Items.Components 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, miniMapFrame.Rect.Location.ToVector2() + (blip * Zoom), GUI.Style.Red * alpha, pingCircle.Origin, 0f, spriteScale * scale, SpriteEffects.None); + pingCircle.Draw(spriteBatch, electricalFrame.Rect.Location.ToVector2() + blip * Zoom, GUI.Style.Red * alpha, pingCircle.Origin, 0f, spriteScale * scale, SpriteEffects.None); } } } @@ -1002,8 +1003,15 @@ namespace Barotrauma.Items.Components { 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); - GUI.DrawLine(spriteBatch, waterRect.Location, new Vector2(waterRect.Right, waterRect.Y), HullWaterLineColor); + + 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); + } } } @@ -1079,7 +1087,7 @@ namespace Barotrauma.Items.Components submarinePreview = rt; } - private void DrawSubmarine(SpriteBatch spriteBatch, Rectangle parentRect) + private void DrawSubmarine(SpriteBatch spriteBatch) { Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle; spriteBatch.End(); @@ -1094,7 +1102,8 @@ namespace Barotrauma.Items.Components 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); - spriteBatch.Draw(texture, parentRect.Center.ToVector2(), null, blueprintBlue, 0f, origin, Zoom, SpriteEffects.None, 0f); + float scale = currentMode == MiniMapMode.HullStatus ? 1.0f : Zoom; + spriteBatch.Draw(texture, miniMapContainer.Center, null, blueprintBlue, 0f, origin, scale, SpriteEffects.None, 0f); spriteBatch.End(); } @@ -1281,12 +1290,12 @@ namespace Barotrauma.Items.Components GUIFrame hullContainer = new GUIFrame(new RectTransform(containerScale * elementPadding, parent.RectTransform, Anchor.Center), style: null); ImmutableHashSet connectedSubs = sub.GetConnectedSubs().ToImmutableHashSet(); - ImmutableHashSet hullList = ImmutableHashSet.Empty; - ImmutableDictionary> combinedHulls = ImmutableDictionary>.Empty; + ImmutableArray hullList = ImmutableArray.Empty; + ImmutableDictionary> combinedHulls = ImmutableDictionary>.Empty; if (settings.CreateHullElements) { - hullList = Hull.hullList.Where(IsPartofSub).ToImmutableHashSet(); + hullList = Hull.hullList.Where(IsPartofSub).ToImmutableArray(); combinedHulls = CombinedHulls(hullList); } @@ -1436,7 +1445,7 @@ namespace Barotrauma.Items.Components } } - private static ImmutableDictionary> CombinedHulls(ImmutableHashSet hulls) + private static ImmutableDictionary> CombinedHulls(ImmutableArray hulls) { Dictionary> combinedHulls = new Dictionary>(); @@ -1460,10 +1469,10 @@ namespace Barotrauma.Items.Components } } - return combinedHulls.ToImmutableDictionary(pair => pair.Key, pair => pair.Value.ToImmutableHashSet()); + return combinedHulls.ToImmutableDictionary(pair => pair.Key, pair => pair.Value.ToImmutableArray()); } - private static MiniMapHullData ConstructHullPolygon(Hull mainHull, ImmutableHashSet linkedHulls, GUIComponent parent, RectangleF worldBorders) + private static MiniMapHullData ConstructHullPolygon(Hull mainHull, ImmutableArray linkedHulls, GUIComponent parent, RectangleF worldBorders) { Rectangle parentRect = parent.Rect; @@ -1500,6 +1509,8 @@ namespace Barotrauma.Items.Components 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); @@ -1508,6 +1519,7 @@ namespace Barotrauma.Items.Components 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; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/RemoteController.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/RemoteController.cs new file mode 100644 index 000000000..bf5405474 --- /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() + { + currentTarget?.AddToGUIUpdateList(); + } + } +} 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/Terminal.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Terminal.cs index fe20dca85..e13440e0e 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) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/StatusHUD.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/StatusHUD.cs index 04313fee8..f9a7b5e9a 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 SeeThroughWalls + { + 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; @@ -91,12 +119,13 @@ 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) { Vector2 diff = c.WorldPosition - refEntity.WorldPosition; - if (Submarine.CheckVisibility(refEntity.SimPosition, refEntity.SimPosition + ConvertUnits.ToSimUnits(diff)) == null) + if (SeeThroughWalls || Submarine.CheckVisibility(refEntity.SimPosition, refEntity.SimPosition + ConvertUnits.ToSimUnits(diff)) == null) { visibleCharacters.Add(c); } @@ -123,27 +152,54 @@ 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 (SeeThroughWalls) + { + spriteBatch.End(); + GameMain.LightManager.SolidColorEffect.Parameters["color"].SetValue(Color.Red.ToVector4() * (0.35f + (float)Math.Sin(Timing.TotalTime * 1.6f) * 0.05f)); + GameMain.LightManager.SolidColorEffect.CurrentTechnique = GameMain.LightManager.SolidColorEffect.Techniques["SolidColorBlur"]; + GameMain.LightManager.SolidColorEffect.Parameters["blurDistance"].SetValue(0.03f + (float)Math.Sin(Timing.TotalTime) * 0.01f); + GameMain.LightManager.SolidColorEffect.CurrentTechnique.Passes[0].Apply(); + spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive, transformMatrix: Screen.Selected.Cam.Transform, effect: GameMain.LightManager.SolidColorEffect); + + foreach (Character c in visibleCharacters) + { + if (c == character || !c.Enabled || c.Removed) { continue; } + foreach (Limb limb in c.AnimController.Limbs) + { + limb.Draw(spriteBatch, Screen.Selected.Cam, disableDeformations: true); + } + } + + spriteBatch.End(); + spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied); } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Wearable.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Wearable.cs index 9692249c8..b489954a0 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Wearable.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Wearable.cs @@ -13,7 +13,7 @@ namespace Barotrauma.Items.Components 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()) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs index 21ac893a5..a4cc3788d 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,7 +316,7 @@ 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 (itemsInSlot.All(it => it.NonInteractable || it.NonPlayerTeamInteractable)) { toolTip += " " + TextManager.Get("connectionlocked"); @@ -719,7 +720,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++; @@ -1543,13 +1544,13 @@ 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); + int maxStackSize = Math.Min(containedItem.Prefab.MaxStackSize, itemContainer.GetMaxStackSize(0)); if (maxStackSize > 1) { containedState = itemContainer.Inventory.slots[0].ItemCount / (float)maxStackSize; @@ -1631,7 +1632,7 @@ namespace Barotrauma int maxStackSize = item.Prefab.MaxStackSize; if (item.Container != null) { - maxStackSize = Math.Min(maxStackSize, item.Container.GetComponent()?.MaxStackSize ?? maxStackSize); + maxStackSize = Math.Min(maxStackSize, item.Container.GetComponent()?.GetMaxStackSize(slotIndex) ?? maxStackSize); } if (maxStackSize > 1 && inventory != null) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs index 237c062d3..1ed06add7 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs @@ -1194,7 +1194,14 @@ namespace Barotrauma } } - if (Character.Controlled != null && Character.Controlled.SelectedConstruction != this) { return; } + if (Character.Controlled != null && Character.Controlled.SelectedConstruction != this) + { + 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) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/MapEntity.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/MapEntity.cs index 9fad6fc1c..a6244b448 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/MapEntity.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/MapEntity.cs @@ -423,20 +423,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); } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Submarine.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Submarine.cs index 04701beb5..9d19ccf5a 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Submarine.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Submarine.cs @@ -326,7 +326,7 @@ namespace Barotrauma depthSortedDamageable.Insert(i, structure); } } - + foreach (Structure s in depthSortedDamageable) { s.DrawDamage(spriteBatch, damageEffect, editing); @@ -418,8 +418,8 @@ 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), + (parentAspectRatio > aspectRatio ? new Vector2(aspectRatio / parentAspectRatio, 1.0f) : new Vector2(1.0f, parentAspectRatio / aspectRatio)) * scale, + parent.RectTransform, Anchor.Center), style: null) { UserData = "hullcontainer" @@ -444,9 +444,9 @@ namespace Barotrauma { if (!combinedHulls.ContainsKey(hull)) { - combinedHulls.Add(hull, new HashSet()); + combinedHulls.Add(hull, new HashSet()); } - + combinedHulls[hull].Add(linkedHull); } } @@ -454,14 +454,14 @@ namespace Barotrauma 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); 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) @@ -477,7 +477,7 @@ namespace Barotrauma MiniMapHullData data = ConstructLinkedHulls(mainHull, linkedHulls, hullContainer, worldBorders); Vector2 relativeHullPos = new Vector2( - (data.Bounds.X - worldBorders.X) / worldBorders.Width, + (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); @@ -507,9 +507,6 @@ namespace Barotrauma 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 - const float padding = 0.001f; - size.X += padding; - size.Y += padding; 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) @@ -586,7 +583,7 @@ namespace Barotrauma wRect.Y = -wRect.Y; var (posX, posY) = new Vector2( - (wRect.X - worldBorders.X) / (float)worldBorders.Width, + (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); @@ -629,7 +626,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)) @@ -697,7 +694,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; @@ -794,7 +791,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/Particles/ParticleManager.cs b/Barotrauma/BarotraumaClient/ClientSource/Particles/ParticleManager.cs index 182e826f9..1b5c3fdc0 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Particles/ParticleManager.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Particles/ParticleManager.cs @@ -184,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/Screens/NetLobbyScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs index 071349710..29699b746 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs @@ -1366,6 +1366,7 @@ namespace Barotrauma 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; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/TestScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/TestScreen.cs index 0deb694c4..050fd29eb 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/TestScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/TestScreen.cs @@ -51,11 +51,16 @@ namespace Barotrauma } // ???????? - submarine = new Submarine(SubmarineInfo.SavedSubmarines.FirstOrDefault(info => info.Name.Equals("Kastrull", StringComparison.OrdinalIgnoreCase))); + submarine = new Submarine(SubmarineInfo.SavedSubmarines.FirstOrDefault(info => info.Name.Equals("Crescent", StringComparison.OrdinalIgnoreCase))); miniMapItem = new Item(ItemPrefab.Find(null, "statusmonitor"), Vector2.Zero, submarine); MiniMap miniMap = miniMapItem.GetComponent(); miniMap.PowerConsumption = 0; + foreach (var hull in Hull.hullList) + { + hull.WaterVolume = hull.Volume / 2f; + } + dummyCharacter = Character.Create(CharacterPrefab.HumanSpeciesName, Vector2.Zero, "", id: Entity.DummyID, hasAi: false); dummyCharacter.Info.Name = "Galldren"; dummyCharacter.Inventory.CreateSlots(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundPlayer.cs b/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundPlayer.cs index f6e679850..fbd068958 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundPlayer.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundPlayer.cs @@ -1012,7 +1012,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/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/LinuxClient.csproj b/Barotrauma/BarotraumaClient/LinuxClient.csproj index dac5130ed..1c1a72de1 100644 --- a/Barotrauma/BarotraumaClient/LinuxClient.csproj +++ b/Barotrauma/BarotraumaClient/LinuxClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.1500.0.0 + 0.1500.1.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/MacClient.csproj b/Barotrauma/BarotraumaClient/MacClient.csproj index 6d8757d5b..34e9c17cd 100644 --- a/Barotrauma/BarotraumaClient/MacClient.csproj +++ b/Barotrauma/BarotraumaClient/MacClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.1500.0.0 + 0.1500.1.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/WindowsClient.csproj b/Barotrauma/BarotraumaClient/WindowsClient.csproj index bc67e5f17..72f5373d5 100644 --- a/Barotrauma/BarotraumaClient/WindowsClient.csproj +++ b/Barotrauma/BarotraumaClient/WindowsClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.1500.0.0 + 0.1500.1.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaServer/LinuxServer.csproj b/Barotrauma/BarotraumaServer/LinuxServer.csproj index d563f8dfa..98e625016 100644 --- a/Barotrauma/BarotraumaServer/LinuxServer.csproj +++ b/Barotrauma/BarotraumaServer/LinuxServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.1500.0.0 + 0.1500.1.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/MacServer.csproj b/Barotrauma/BarotraumaServer/MacServer.csproj index 79640545b..bcaa8358d 100644 --- a/Barotrauma/BarotraumaServer/MacServer.csproj +++ b/Barotrauma/BarotraumaServer/MacServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.1500.0.0 + 0.1500.1.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/ServerSource/Characters/Character.cs b/Barotrauma/BarotraumaServer/ServerSource/Characters/Character.cs index f9f51b713..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) diff --git a/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterInfo.cs b/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterInfo.cs index 148bfeda3..e5863ea30 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterInfo.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterInfo.cs @@ -73,6 +73,7 @@ namespace Barotrauma msg.Write(savedStatValue.RemoveOnDeath); } } + msg.Write((ushort)ExperiencePoints); } } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs b/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs index 3996fdbeb..fef8b769a 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) @@ -259,6 +296,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); @@ -965,6 +1012,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/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/Networking/GameServer.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs index 2df0be84f..575f5c157 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs @@ -2358,6 +2358,12 @@ namespace Barotrauma.Networking 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; } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/RespawnManager.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/RespawnManager.cs index d162db6f0..5430e285c 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/RespawnManager.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/RespawnManager.cs @@ -379,6 +379,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) { diff --git a/Barotrauma/BarotraumaServer/WindowsServer.csproj b/Barotrauma/BarotraumaServer/WindowsServer.csproj index 879a52022..e4aa06d3c 100644 --- a/Barotrauma/BarotraumaServer/WindowsServer.csproj +++ b/Barotrauma/BarotraumaServer/WindowsServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.1500.0.0 + 0.1500.1.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.9.xml b/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.9.xml index f5c2f7d19..a7a1728a0 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,7 @@ + @@ -152,6 +154,7 @@ + diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs index 6a86228e6..a448481c0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs @@ -1915,12 +1915,12 @@ namespace Barotrauma #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 + 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); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs index 79842539a..e554fb317 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs @@ -588,8 +588,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); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/AnimController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/AnimController.cs index 702670425..8d490a3fa 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/AnimController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/AnimController.cs @@ -148,7 +148,7 @@ namespace Barotrauma public virtual 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 virtual void HoldItem(float deltaTime, Item item, Vector2[] handlePos, Vector2 holdPos, Vector2 aimPos, bool aim, float holdAngle, float itemAngleRelativeToHoldAngle = 0.0f, bool aimingMelee = false) { } public virtual void DragCharacter(Character target, float deltaTime) { } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/FishAnimController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/FishAnimController.cs index 475a16a08..a057b932d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/FishAnimController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/FishAnimController.cs @@ -390,11 +390,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); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs index 1e910d566..5032d2a2e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs @@ -150,6 +150,13 @@ namespace Barotrauma private float upperLegLength = 0.0f, lowerLegLength = 0.0f; private bool aiming; + private bool wasAiming; + + private bool aimingMelee; + private bool wasAimingMelee; + + public bool IsAiming => wasAiming; + public bool IsAimingMelee => wasAimingMelee; private readonly float movementLerp; @@ -532,7 +539,10 @@ namespace Barotrauma limb.Disabled = false; } + wasAiming = aiming; aiming = false; + wasAimingMelee = aimingMelee; + aimingMelee = false; if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer) return; } @@ -1718,7 +1728,7 @@ namespace Barotrauma } //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) + public override void HoldItem(float deltaTime, Item item, Vector2[] handlePos, Vector2 holdPos, Vector2 aimPos, bool aim, float holdAngle, float itemAngleRelativeToHoldAngle = 0.0f, bool aimingMelee = false) { if (character.Stun > 0.0f || character.IsIncapacitated) { @@ -1748,6 +1758,8 @@ namespace Barotrauma Holdable holdable = item.GetComponent(); + this.aimingMelee = aimingMelee; + if (!isClimbing && !usingController && character.Stun <= 0.0f && aim && itemPos != Vector2.Zero && !character.IsIncapacitated) { Vector2 mousePos = ConvertUnits.ToSimUnits(character.SmoothedCursorPosition); @@ -1771,7 +1783,6 @@ namespace Barotrauma aiming = true; } - } else { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs index 43d9dc327..3bbb32214 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs @@ -395,12 +395,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); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Attack.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Attack.cs index dc2f44f07..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 @@ -74,20 +75,6 @@ namespace Barotrauma } } - class AttackData - { - public float DamageMultiplier { get; set; } = 1f; - public float AddedPenetration { get; set; } = 0f; - public List Afflictions { get; set; } - public Attack SourceAttack { get; } - - public AttackData(Attack sourceAttack) - { - SourceAttack = sourceAttack; - } - - } - partial class Attack : ISerializableEntity { [Serialize(AttackContext.Any, true, description: "The attack will be used only in this context."), Editable] @@ -466,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 c97e76569..f442f7cc1 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 @@ -1366,7 +1367,6 @@ namespace Barotrauma } } } - private List wearableItems = new List(); public float GetSkillLevel(string skillIdentifier) { @@ -2075,11 +2075,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; @@ -2218,6 +2217,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(); @@ -3355,10 +3360,18 @@ namespace Barotrauma float attackImpulse = attack.TargetImpulse + attack.TargetForce * deltaTime; - AttackData attackData = new AttackData(attack); - attacker.CheckTalents(AbilityEffectType.OnAttack, attackData); - CheckTalents(AbilityEffectType.OnAttacked, attackData); - attackData.DamageMultiplier *= (1 + attacker.GetStatValue(StatTypes.AttackMultiplier)); + 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; @@ -3495,6 +3508,7 @@ namespace Barotrauma { attackerCrewmember.CheckTalents(AbilityEffectType.OnCrewKillCharacter, target); } + CheckTalents(AbilityEffectType.OnKillCharacter, target); if (!IsOnPlayerTeam) { return; } if (GameMain.Config.KilledCreatures.Any(name => name.Equals(target.SpeciesName, StringComparison.OrdinalIgnoreCase))) { return; } @@ -3653,10 +3667,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) @@ -3719,7 +3730,7 @@ namespace Barotrauma } } - private void Implode(bool isNetworkMessage = false) + public void Implode(bool isNetworkMessage = false) { if (CharacterHealth.Unkillable || GodMode || IsDead) { return; } @@ -3829,7 +3840,7 @@ namespace Barotrauma if (info != null) { info.CauseOfDeath = CauseOfDeath; - info.ResetSavedStatValues(); + info.MissionsCompletedSinceDeath = 0; } AnimController.movement = Vector2.Zero; AnimController.TargetMovement = Vector2.Zero; @@ -4434,7 +4445,7 @@ namespace Barotrauma } } - private StatTypes GetSkillStatType(string skillIdentifier) + 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) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs index 9526ce3a4..b4b617328 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs @@ -462,6 +462,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 = "") { @@ -605,7 +608,10 @@ 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()) { bool jobCreated = false; @@ -973,16 +979,17 @@ namespace Barotrauma } float prevLevel = Job.GetSkillLevel(skillIdentifier); - Job.IncreaseSkillLevel(skillIdentifier, increase); + Job.IncreaseSkillLevel(skillIdentifier, increase, Character.HasAbilityFlag(AbilityFlags.GainSkillPastMaximum)); float newLevel = Job.GetSkillLevel(skillIdentifier); + if ((int)newLevel > (int)prevLevel) { Character.CheckTalents(AbilityEffectType.OnGainSkillPoint, skillIdentifier); - foreach (Character character in Character.GetFriendlyCrew(Character)) { - character.CheckTalents(AbilityEffectType.OnAllyGainSkillPoint, (skillIdentifier, Character)); + var abilityStringCharacter = new AbilityStringCharacter(skillIdentifier, Character); + character.CheckTalents(AbilityEffectType.OnAllyGainSkillPoint, abilityStringCharacter); } } @@ -1139,9 +1146,10 @@ namespace Barotrauma 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) @@ -1158,6 +1166,7 @@ namespace Barotrauma 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()), @@ -1168,6 +1177,8 @@ namespace Barotrauma } } + + charElement.Add(savedStatElement); parentElement.Add(charElement); @@ -1496,7 +1507,6 @@ namespace Barotrauma } } } - public void ResetSavedStatValue(string statIdentifier) { savedStatValues.SelectMany(s => s.Value).Where(s => s.StatIdentifier == statIdentifier).ForEach(v => v.StatValue = 0f); @@ -1514,7 +1524,7 @@ namespace Barotrauma } } - public void ChangeSavedStatValue(StatTypes statType, float value, string statIdentifier, bool removeOnDeath) + public void ChangeSavedStatValue(StatTypes statType, float value, string statIdentifier, bool removeOnDeath, bool removeAfterRound = false, float maxValue = float.MaxValue) { if (!savedStatValues.ContainsKey(statType)) { @@ -1523,12 +1533,11 @@ namespace Barotrauma if (savedStatValues[statType].FirstOrDefault(s => s.StatIdentifier == statIdentifier) is SavedStatValue savedStat) { - savedStat.StatValue += value; - savedStat.RemoveOnDeath = removeOnDeath; + savedStat.StatValue = MathHelper.Min(savedStat.StatValue + value, maxValue); } else { - savedStatValues[statType].Add(new SavedStatValue(statIdentifier, value, removeOnDeath)); + savedStatValues[statType].Add(new SavedStatValue(statIdentifier, MathHelper.Min(value, maxValue), removeOnDeath, removeAfterRound)); } } } @@ -1538,12 +1547,14 @@ namespace Barotrauma 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) + public SavedStatValue(string statIdentifier, float value, bool removeOnDeath, bool retainAfterRound) { StatValue = value; RemoveOnDeath = removeOnDeath; StatIdentifier = statIdentifier; + RemoveAfterRound = retainAfterRound; } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/Affliction.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/Affliction.cs index 96b550b47..d67a18fde 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,12 +168,12 @@ 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, - (Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength)); + currentEffect.MinChromaticAberration, + currentEffect.MaxChromaticAberration, + (Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength)) * GetScreenEffectFluctuation(currentEffect); } public float GetScreenBlurStrength() @@ -177,12 +181,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 +220,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,8 +242,6 @@ 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, @@ -282,6 +293,9 @@ 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 { float durationMultiplier = 1 / (1 + (Prefab.IsBuff ? characterHealth.Character.GetStatValue(StatTypes.BuffDurationMultiplier) @@ -290,9 +304,9 @@ namespace Barotrauma _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); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionHusk.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionHusk.cs index 82f12e892..72c3dfae7 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionHusk.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionHusk.cs @@ -34,6 +34,10 @@ namespace Barotrauma float threshold = _strength > ActiveThreshold ? ActiveThreshold + 1 : DormantThreshold - 1; float max = Math.Max(threshold, previousValue); _strength = Math.Clamp(value, 0, max); + if (previousValue > 0.0f && value <= 0.0f) + { + DeactivateHusk(); + } } } @@ -51,8 +55,10 @@ 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; public AfflictionHusk(AfflictionPrefab prefab, float strength) : base(prefab, strength) { } @@ -83,7 +89,7 @@ namespace Barotrauma } State = InfectionState.Transition; } - else if (Strength < Prefab.MaxStrength) + else if (Strength < TransitionThreshold) { if (State != InfectionState.Active) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionPrefab.cs index 619fb81f0..5028df129 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionPrefab.cs @@ -97,6 +97,10 @@ namespace Barotrauma 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); } // Use any of these to define which limb the appendage is attached to. @@ -105,6 +109,8 @@ namespace Barotrauma public readonly string AttachLimbName; public readonly LimbType AttachLimbType; + public float ActiveThreshold, DormantThreshold, TransitionThreshold; + public readonly string HuskedSpeciesName; public readonly string[] TargetSpecies; public const string Tag = "[speciesname]"; @@ -141,28 +147,31 @@ namespace Barotrauma public bool MultiplyByMaxVitality { get; private set; } [Serialize(0.0f, false)] - public float MinScreenBlurStrength { get; private set; } + public float MinScreenBlur { get; private set; } [Serialize(0.0f, false)] - public float MaxScreenBlurStrength { get; private set; } + public float MaxScreenBlur { get; private set; } [Serialize(0.0f, false)] - public float MinScreenDistortStrength { get; private set; } + public float MinScreenDistort { get; private set; } [Serialize(0.0f, false)] - public float MaxScreenDistortStrength { get; private set; } + public float MaxScreenDistort { get; private set; } [Serialize(0.0f, false)] - public float MinRadialDistortStrength { get; private set; } + public float MinRadialDistort { get; private set; } [Serialize(0.0f, false)] - public float MaxRadialDistortStrength { get; private set; } + public float MaxRadialDistort { get; private set; } [Serialize(0.0f, false)] - public float MinChromaticAberrationStrength { get; private set; } + public float MinChromaticAberration { get; private set; } [Serialize(0.0f, false)] - public float MaxChromaticAberrationStrength { get; private set; } + 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; } @@ -170,6 +179,9 @@ namespace Barotrauma [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 MinBuffMultiplier { get; private set; } @@ -188,8 +200,11 @@ namespace Barotrauma [Serialize(1.0f, false)] public float MaxSkillMultiplier { get; private set; } - [Serialize("", false)] - public string ResistanceFor { get; private set; } + private readonly string[] resistanceFor; + public IEnumerable ResistanceFor + { + get { return resistanceFor; } + } [Serialize(0.0f, false)] public float MinResistance { get; private set; } @@ -209,6 +224,8 @@ namespace Barotrauma { SerializableProperty.DeserializeProperties(this, element); + resistanceFor = element.GetAttributeStringArray("resistancefor", new string[0], convertToLowerInvariant: true); + foreach (XElement subElement in element.Elements()) { switch (subElement.Name.ToString().ToLowerInvariant()) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs index a4908115e..a195d5620 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs @@ -266,6 +266,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. /// @@ -426,18 +432,14 @@ namespace Barotrauma } } - 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); } - resistance = 1 - ((1 - resistance) * Character.GetAbilityResistance(resistanceId)); - - return resistance; + return 1 - ((1 - resistance) * Character.GetAbilityResistance(affliction.Identifier)); } public float GetStatValue(StatTypes statType) @@ -451,7 +453,7 @@ namespace Barotrauma } 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); @@ -499,6 +501,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(); @@ -610,7 +623,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 @@ -632,7 +645,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); @@ -666,7 +679,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 @@ -688,7 +701,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; @@ -700,8 +713,6 @@ namespace Barotrauma } } - partial void UpdateProjSpecific(float deltaTime); - partial void UpdateLimbAfflictionOverlays(); public void Update(float deltaTime) @@ -741,7 +752,7 @@ namespace Barotrauma 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); @@ -763,6 +774,10 @@ namespace Barotrauma { Character.StackSpeedMultiplier(1f + Character.GetStatValue(StatTypes.SwimmingSpeed)); } + else + { + Character.StackSpeedMultiplier(1f + Character.GetStatValue(StatTypes.WalkingSpeed)); + } UpdateLimbAfflictionOverlays(); @@ -786,7 +801,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); @@ -807,8 +827,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) @@ -824,7 +842,6 @@ namespace Barotrauma { vitalityDecrease *= limbHealth.VitalityTypeMultipliers[type]; } - vitalityDecrease *= damageResistanceMultiplier; Vitality -= vitalityDecrease; affliction.CalculateDamagePerSecond(vitalityDecrease); } @@ -833,7 +850,6 @@ namespace Barotrauma foreach (Affliction affliction in afflictions) { float vitalityDecrease = affliction.GetVitalityDecrease(this); - vitalityDecrease *= damageResistanceMultiplier; Vitality -= vitalityDecrease; affliction.CalculateDamagePerSecond(vitalityDecrease); } @@ -951,13 +967,13 @@ 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, 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; } foreach (KeyValuePair treatment in affliction.Prefab.TreatmentSuitability) @@ -990,6 +1006,18 @@ 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(); 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/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 ed33edcd9..9afd742bb 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Limb.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Limb.cs @@ -868,7 +868,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 +971,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 +1132,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) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionAttackData.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionAttackData.cs index 2a80e9122..3288b0b7a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionAttackData.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionAttackData.cs @@ -15,7 +15,7 @@ namespace Barotrauma.Abilities private readonly string itemIdentifier; private readonly string[] tags; - private WeaponType weapontype; + private readonly WeaponType weapontype; public AbilityConditionAttackData(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) { itemIdentifier = conditionElement.GetAttributeString("itemidentifier", ""); @@ -33,7 +33,7 @@ namespace Barotrauma.Abilities protected override bool MatchesConditionSpecific(object abilityData) { - if (abilityData is AttackData attackData) + if (abilityData is AbilityAttackData attackData) { Item item = attackData?.SourceAttack?.SourceItem; @@ -71,7 +71,7 @@ namespace Barotrauma.Abilities } else { - LogAbilityConditionError(abilityData, typeof(AttackData)); + LogAbilityConditionError(abilityData, 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 index 909c694f2..ac9eb34df 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionAttackResult.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionAttackResult.cs @@ -30,7 +30,7 @@ namespace Barotrauma.Abilities } else { - LogAbilityConditionError(abilityData, typeof(AttackData)); + LogAbilityConditionError(abilityData, typeof(AbilityAttackData)); 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 index d80a6257d..5135b0924 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionData.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionData.cs @@ -1,7 +1,4 @@ -using Microsoft.Xna.Framework; -using System; -using System.Collections.Generic; -using System.Linq; +using System; using System.Xml.Linq; namespace Barotrauma.Abilities diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionHandsomeStranger.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionHandsomeStranger.cs deleted file mode 100644 index 55ee4fa06..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionHandsomeStranger.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.Xml.Linq; - -namespace Barotrauma.Abilities -{ - class AbilityConditionHandsomeStranger : AbilityConditionData - { - string skillIdentifier; - - public AbilityConditionHandsomeStranger(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) - { - skillIdentifier = conditionElement.GetAttributeString("skillidentifier", "").ToLowerInvariant(); - } - - protected override bool MatchesConditionSpecific(object abilityData) - { - if (abilityData is string skillIdentifier) - { - return this.skillIdentifier == skillIdentifier; - } - else - { - LogAbilityConditionError(abilityData, typeof(string)); - 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..324676a98 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionIsAiming.cs @@ -0,0 +1,54 @@ +using Barotrauma.Items.Components; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class AbilityConditionIsAiming : AbilityConditionDataless + { + private enum WeaponType + { + Any = 0, + Melee = 1, + Ranged = 2 + }; + + private WeaponType weapontype; + public AbilityConditionIsAiming(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) + { + switch (conditionElement.GetAttributeString("weapontype", "")) + { + case "melee": + weapontype = WeaponType.Melee; + break; + case "ranged": + weapontype = WeaponType.Ranged; + break; + } + } + + protected override bool MatchesConditionSpecific() + { + bool aimingCorrectItem = false; + if (character.AnimController is HumanoidAnimController animController) + { + foreach (Item item in character.HeldItems) + { + switch (weapontype) + { + case WeaponType.Melee: + aimingCorrectItem |= item.GetComponent() != null && animController.IsAimingMelee; + break; + case WeaponType.Ranged: + aimingCorrectItem |= item.GetComponent() != null && animController.IsAiming; + break; + default: + aimingCorrectItem |= animController.IsAiming || animController.IsAimingMelee; + break; + } + } + } + + return aimingCorrectItem; + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionItem.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionItem.cs index cfa1db21c..4e6b530cd 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionItem.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionItem.cs @@ -22,10 +22,9 @@ namespace Barotrauma.Abilities { item = tempItem.Prefab; } - // this and other instances of this type of casting will be refactored - else if (abilityData is (ItemPrefab itemPrefab, object _)) + else if (abilityData is IAbilityItemPrefab abilityItemPrefab) { - item = itemPrefab; + item = abilityItemPrefab.ItemPrefab; } if (item != null) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionReduceAffliction.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionReduceAffliction.cs index 24044dc0e..888217b9f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionReduceAffliction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionReduceAffliction.cs @@ -15,17 +15,17 @@ namespace Barotrauma.Abilities protected override bool MatchesConditionSpecific(object abilityData) { - if (abilityData is (Affliction affliction, float reduceAmount)) + if (abilityData is IAbilityAffliction abilityAffliction) { - if (allowedTypes.Find(c => c == affliction.Prefab.AfflictionType) == null) { return false; } + if (allowedTypes.Find(c => c == abilityAffliction.Affliction.Prefab.AfflictionType) == null) { return false; } - if (!string.IsNullOrEmpty(identifier) && affliction.Prefab.Identifier != identifier) { return false; } + if (!string.IsNullOrEmpty(identifier) && abilityAffliction.Affliction.Prefab.Identifier != identifier) { return false; } return true; } else { - LogAbilityConditionError(abilityData, typeof((Affliction, float))); + LogAbilityConditionError(abilityData, 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..37fd19923 --- /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(object abilityData) + { + if ((abilityData as string ?? (abilityData as IAbilityString)?.String) is string skillIdentifier) + { + return MatchesConditionSpecific(skillIdentifier); + } + else + { + LogAbilityConditionError(abilityData, typeof(string)); + 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 index 6543c7b32..9fe8fa1a4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionAboveVitality.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionAboveVitality.cs @@ -1,11 +1,10 @@ -using System.Linq; -using System.Xml.Linq; +using System.Xml.Linq; namespace Barotrauma.Abilities { class AbilityConditionAboveVitality : AbilityConditionDataless { - float vitalityPercentage; + private readonly float vitalityPercentage; public AbilityConditionAboveVitality(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) { 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/AbilityConditionDataless.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionDataless.cs index 023fe029f..de8bccced 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionDataless.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionDataless.cs @@ -1,8 +1,4 @@ -using Microsoft.Xna.Framework; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Xml.Linq; +using System.Xml.Linq; namespace Barotrauma.Abilities { 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..0fee25880 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionHasPermanentStat.cs @@ -0,0 +1,23 @@ +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class AbilityConditionHasPermanentStat : AbilityConditionDataless + { + private readonly StatTypes statType; + private readonly float min; + + public AbilityConditionHasPermanentStat(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) + { + statType = CharacterAbilityGroup.ParseStatType(conditionElement.GetAttributeString("stattype", ""), characterTalent.DebugIdentifier); + min = conditionElement.GetAttributeFloat("min", 0f); + } + + protected override bool MatchesConditionSpecific() + { + // should consider decoupling this from stat values entirely + return character.Info.GetSavedStatValue(statType) >= min; + } + } +} 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/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/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/AbilityConditionMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionMission.cs index f27ecf4c1..29cab2f45 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionMission.cs @@ -24,13 +24,13 @@ namespace Barotrauma.Abilities protected override bool MatchesConditionSpecific(object abilityData) { - if (abilityData is (Mission mission, AbilityValue missionAbilityValue)) + if (abilityData is IAbilityMission abilityMission) { - return mission.Prefab.Type == missionType; + return abilityMission.Mission.Prefab.Type == missionType; } else { - LogAbilityConditionError(abilityData, typeof((Mission, AbilityValue))); + LogAbilityConditionError(abilityData, typeof(IAbilityMission)); return false; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionServerRandom.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionServerRandom.cs index 3cc8ae4f5..5b582f799 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionServerRandom.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionServerRandom.cs @@ -1,14 +1,10 @@ -using Microsoft.Xna.Framework; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Xml.Linq; +using System.Xml.Linq; namespace Barotrauma.Abilities { class AbilityConditionServerRandom : AbilityConditionDataless { - private float randomChance = 0f; + private readonly float randomChance = 0f; public override bool AllowClientSimulation => false; public AbilityConditionServerRandom(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) 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..46478b7b7 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityInterfaces.cs @@ -0,0 +1,32 @@ +namespace Barotrauma.Abilities +{ + interface IAbilityItemPrefab + { + public ItemPrefab ItemPrefab { get; set; } + } + + interface IAbilityValue + { + public float Value { get; set; } + } + + interface IAbilityMission + { + public Mission Mission { get; set; } + } + + interface IAbilityCharacter + { + public Character Character { get; set; } + } + + interface IAbilityString + { + public string String { get; set; } + } + + interface IAbilityAffliction + { + public Affliction Affliction { 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..9247ad159 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityObjects.cs @@ -0,0 +1,85 @@ +using System.Collections.Generic; + +namespace Barotrauma.Abilities +{ + + class AbilityValue : IAbilityValue + { + public AbilityValue(float value) + { + Value = value; + } + public float Value { get; set; } + } + + class AbilityValueItem : IAbilityValue, IAbilityItemPrefab + { + public AbilityValueItem(float value, ItemPrefab itemPrefab) + { + Value = value; + ItemPrefab = itemPrefab; + } + public float Value { get; set; } + public ItemPrefab ItemPrefab { get; set; } + } + + class AbilityValueString : IAbilityValue, IAbilityString + { + public AbilityValueString(float value, string abilityString) + { + Value = value; + String = abilityString; + } + public float Value { get; set; } + public string String { get; set; } + } + + class AbilityStringCharacter : IAbilityCharacter, IAbilityString + { + public AbilityStringCharacter(string abilityString, Character character) + { + String = abilityString; + Character = character; + } + public Character Character { get; set; } + public string String { get; set; } + } + + class AbilityValueAffliction : IAbilityValue, IAbilityAffliction + { + public AbilityValueAffliction(float value, Affliction affliction) + { + Value = value; + Affliction = affliction; + } + public float Value { get; set; } + public Affliction Affliction { get; set; } + } + + class AbilityValueMission : IAbilityValue, IAbilityMission + { + public AbilityValueMission(float value, Mission mission) + { + Value = value; + Mission = mission; + } + public float Value { get; set; } + public Mission Mission { get; set; } + } + + class AbilityAttackData : 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; + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbility.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbility.cs index 739e7ced1..9b4414901 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbility.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbility.cs @@ -12,12 +12,16 @@ namespace Barotrauma.Abilities public CharacterTalent CharacterTalent { get; } public Character Character { get; } - public virtual bool RequiresAlive => true; + 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. /// @@ -28,6 +32,7 @@ namespace Barotrauma.Abilities CharacterAbilityGroup = characterAbilityGroup; CharacterTalent = characterAbilityGroup.CharacterTalent; Character = CharacterTalent.Character; + RequiresAlive = abilityElement.GetAttributeBool("requiresalive", true); } public bool IsViable() @@ -132,11 +137,5 @@ namespace Barotrauma.Abilities } return flagType; } - - public static float DistanceToSquaredDistance(float distance) - { - return distance * distance; - } - } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffects.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffects.cs index e370e94b3..773992306 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffects.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffects.cs @@ -10,16 +10,41 @@ namespace Barotrauma.Abilities protected readonly List statusEffects; + 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); } protected void ApplyEffectSpecific(Character targetCharacter) { foreach (var statusEffect in statusEffects) { - statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, Character, targetCharacter); + if (statusEffect.HasTargetType(StatusEffect.TargetType.UseTarget)) + { + // currently used this 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)); + statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, targetCharacter, targets); + } + else if (statusEffect.HasTargetType(StatusEffect.TargetType.This)) + { + statusEffect.SetUser(Character); + statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, Character, Character); + } + else + { + statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, Character, targetCharacter); + } } } @@ -30,13 +55,17 @@ namespace Barotrauma.Abilities protected override void ApplyEffect(object abilityData) { - if (abilityData is Character targetCharacter) + if (applyToSelected && Character.SelectedCharacter is Character selectedCharacter) + { + ApplyEffectSpecific(selectedCharacter); + } + else if ((abilityData as Character ?? (abilityData as IAbilityCharacter)?.Character) is Character targetCharacter) { ApplyEffectSpecific(targetCharacter); } - else + else { - ApplyEffect(); + 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..2b1115068 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffectsToAllies.cs @@ -0,0 +1,30 @@ +using Barotrauma.Extensions; +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; + + public CharacterAbilityApplyStatusEffectsToAllies(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) + { + allowSelf = abilityElement.GetAttributeBool("allowself", true); + } + + + protected override void ApplyEffect() + { + IEnumerable chosenCharacters = Character.GetFriendlyCrew(Character).Where(c => allowSelf || c != Character); + + foreach (Character character in chosenCharacters) + { + 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..1f346accd --- /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(object abilityData) + { + if ((abilityData as AbilityAttackData)?.Attacker is Character attacker) + { + ApplyEffectSpecific(attacker); + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffectsToNearestAlly.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffectsToNearestAlly.cs index a12622816..a0701c782 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffectsToNearestAlly.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffectsToNearestAlly.cs @@ -1,6 +1,5 @@ using Microsoft.Xna.Framework; -using System.Collections.Generic; -using System.Linq; +using System; using System.Xml.Linq; namespace Barotrauma.Abilities @@ -10,7 +9,7 @@ namespace Barotrauma.Abilities protected float squaredMaxDistance; public CharacterAbilityApplyStatusEffectsToNearestAlly(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) { - squaredMaxDistance = DistanceToSquaredDistance(abilityElement.GetAttributeFloat("maxdistance", float.MaxValue)); + squaredMaxDistance = MathF.Pow(abilityElement.GetAttributeFloat("maxdistance", float.MaxValue), 2); } protected override void ApplyEffect() @@ -20,7 +19,7 @@ namespace Barotrauma.Abilities foreach (Character crewCharacter in Character.GetFriendlyCrew(Character)) { - if (crewCharacter != Character && Vector2.DistanceSquared(Character.SimPosition, Character.GetRelativeSimPosition(crewCharacter)) is float tempDistance && tempDistance < closestDistance) + if (crewCharacter != Character && Vector2.DistanceSquared(Character.WorldPosition, crewCharacter.WorldPosition) is float tempDistance && tempDistance < closestDistance) { closestCharacter = crewCharacter; closestDistance = tempDistance; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffectsToRandomAlly.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffectsToRandomAlly.cs index 8fe1ea29d..0f1fd20b2 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffectsToRandomAlly.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffectsToRandomAlly.cs @@ -1,5 +1,6 @@ using Barotrauma.Extensions; using Microsoft.Xna.Framework; +using System; using System.Collections.Generic; using System.Linq; using System.Xml.Linq; @@ -16,7 +17,7 @@ namespace Barotrauma.Abilities public CharacterAbilityApplyStatusEffectsToRandomAlly(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) { - squaredMaxDistance = DistanceToSquaredDistance(abilityElement.GetAttributeFloat("maxdistance", float.MaxValue)); + squaredMaxDistance = MathF.Pow(abilityElement.GetAttributeFloat("maxdistance", float.MaxValue), 2); allowDifferentSub = abilityElement.GetAttributeBool("mustbeonsamesub", true); allowSelf = abilityElement.GetAttributeBool("allowself", true); } @@ -26,9 +27,9 @@ namespace Barotrauma.Abilities Character chosenCharacter = null; chosenCharacter = Character.GetFriendlyCrew(Character).Where(c => - (allowSelf ||c != Character) && + (allowSelf || c != Character) && (allowDifferentSub || c.Submarine == Character.Submarine) && - Vector2.DistanceSquared(Character.SimPosition, Character.GetRelativeSimPosition(c)) is float tempDistance && + Vector2.DistanceSquared(Character.WorldPosition, c.WorldPosition) is float tempDistance && tempDistance < squaredMaxDistance).GetRandom(); if (chosenCharacter == null) { return; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveMoney.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveMoney.cs index 86e1cb093..4ac33d7c7 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveMoney.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveMoney.cs @@ -7,16 +7,41 @@ namespace Barotrauma.Abilities { public override bool AppliesEffectOnIntervalUpdate => true; - private int amount; + private readonly int amount; + private StatTypes scalingStatType; public CharacterAbilityGiveMoney(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) { amount = abilityElement.GetAttributeInt("amount", 0); + scalingStatType = CharacterAbilityGroup.ParseStatType(abilityElement.GetAttributeString("scalingstattype", "None"), CharacterTalent.DebugIdentifier); + } + + private void ApplyEffectSpecific(Character targetCharacter) + { + float multiplier = 1f; + if (scalingStatType != StatTypes.None) + { + multiplier = 0 + Character.Info.GetSavedStatValue(scalingStatType); + } + + targetCharacter.GiveMoney((int)(multiplier * amount)); + } + + protected override void ApplyEffect(object abilityData) + { + if ((abilityData as Character ?? (abilityData as IAbilityCharacter)?.Character) is Character targetCharacter) + { + ApplyEffectSpecific(targetCharacter); + } + else + { + ApplyEffectSpecific(Character); + } } protected override void ApplyEffect() { - Character.GiveMoney(amount); + ApplyEffectSpecific(Character); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGivePermanentStat.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGivePermanentStat.cs index 2a8797b4e..c8147397c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGivePermanentStat.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGivePermanentStat.cs @@ -8,8 +8,10 @@ namespace Barotrauma.Abilities 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 removeAfterRound; //private readonly float maximumValue; public override bool AppliesEffectOnIntervalUpdate => true; @@ -19,8 +21,10 @@ namespace Barotrauma.Abilities statIdentifier = abilityElement.GetAttributeString("statidentifier", "").ToLowerInvariant(); statType = CharacterAbilityGroup.ParseStatType(abilityElement.GetAttributeString("stattype", ""), CharacterTalent.DebugIdentifier); value = abilityElement.GetAttributeFloat("value", 0f); + maxValue = abilityElement.GetAttributeFloat("maxvalue", float.MaxValue); targetAllies = abilityElement.GetAttributeBool("targetallies", false); removeOnDeath = abilityElement.GetAttributeBool("removeondeath", true); + removeAfterRound = abilityElement.GetAttributeBool("removeafterround", false); //maximumValue = abilityElement.GetAttributeFloat("maximumvalue", float.MaxValue); } @@ -38,11 +42,11 @@ namespace Barotrauma.Abilities { if (targetAllies) { - Character.GetFriendlyCrew(Character).ForEach(c => c?.Info.ChangeSavedStatValue(statType, value, statIdentifier, removeOnDeath)); + Character.GetFriendlyCrew(Character).ForEach(c => c?.Info.ChangeSavedStatValue(statType, value, statIdentifier, removeOnDeath, removeAfterRound, maxValue)); } else { - Character?.Info.ChangeSavedStatValue(statType, value, statIdentifier, removeOnDeath); + Character?.Info.ChangeSavedStatValue(statType, value, statIdentifier, removeOnDeath, removeAfterRound, maxValue); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveResistance.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveResistance.cs index 5d9cd7fee..707d31449 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveResistance.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveResistance.cs @@ -4,18 +4,23 @@ namespace Barotrauma.Abilities { class CharacterAbilityGiveResistance : CharacterAbility { - private string resistanceId; - private float resistance; + private readonly string resistanceId; + private readonly float multiplier; public CharacterAbilityGiveResistance(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) { resistanceId = abilityElement.GetAttributeString("resistanceid", ""); - resistance = abilityElement.GetAttributeFloat("resistance", 1f); + multiplier = abilityElement.GetAttributeFloat("multiplier", 1f); + + if (string.IsNullOrEmpty(resistanceId)) + { + DebugConsole.ThrowError("Error in CharacterAbilityGiveResistance - resistance identifier not set."); + } } public override void InitializeAbility(bool addingFirstTime) { - Character.ChangeAbilityResistance(resistanceId, resistance); + Character.ChangeAbilityResistance(resistanceId, multiplier); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveStat.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveStat.cs index 3b55618d7..c999d3999 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveStat.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveStat.cs @@ -4,10 +4,9 @@ namespace Barotrauma.Abilities { class CharacterAbilityGiveStat : CharacterAbility { - private StatTypes statType; - private float value; + private readonly StatTypes statType; + private readonly float value; - // 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 CharacterAbilityGiveStat(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) { statType = CharacterAbilityGroup.ParseStatType(abilityElement.GetAttributeString("stattype", ""), CharacterTalent.DebugIdentifier); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyAttackData.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyAttackData.cs index 6ee9dc1dc..1d32acc85 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyAttackData.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyAttackData.cs @@ -7,8 +7,9 @@ namespace Barotrauma.Abilities { private readonly List afflictions; - float addedDamageMultiplier; - float addedPenetration; + private readonly float addedDamageMultiplier; + private readonly float addedPenetration; + private readonly bool implode; public CharacterAbilityModifyAttackData(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) { @@ -18,11 +19,12 @@ namespace Barotrauma.Abilities } addedDamageMultiplier = abilityElement.GetAttributeFloat("addeddamagemultiplier", 0f); addedPenetration = abilityElement.GetAttributeFloat("addedpenetration", 0f); + implode = abilityElement.GetAttributeBool("implode", false); } protected override void ApplyEffect(object abilityData) { - if (abilityData is AttackData attackData) + if (abilityData is AbilityAttackData attackData) { if (attackData.Afflictions == null) { @@ -34,6 +36,13 @@ namespace Barotrauma.Abilities } 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 { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyResistance.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyResistance.cs index 4e44403d7..4317f6745 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyResistance.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyResistance.cs @@ -4,8 +4,8 @@ namespace Barotrauma.Abilities { class CharacterAbilityModifyResistance : CharacterAbility { - private string resistanceId; - private float resistance; + private readonly string resistanceId; + private readonly float resistance; bool lastState; // should probably be split to different classes @@ -13,6 +13,11 @@ namespace Barotrauma.Abilities { 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) 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 index 7f27c334f..b4e15f0b3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyValue.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyValue.cs @@ -5,42 +5,21 @@ namespace Barotrauma.Abilities class CharacterAbilityModifyValue : CharacterAbility { private float addedValue; - private float multiplierValue; + private float multiplyValue; public CharacterAbilityModifyValue(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) { addedValue = abilityElement.GetAttributeFloat("addedvalue", 0f); - multiplierValue = abilityElement.GetAttributeFloat("multipliervalue", 1f); + multiplyValue = abilityElement.GetAttributeFloat("multiplyvalue", 1f); } protected override void ApplyEffect(object abilityData) { - if (abilityData is AbilityValue abilityValue) + if (abilityData is IAbilityValue abilityValue) { - ApplyEffectSpecific(abilityValue); + abilityValue.Value += addedValue; + abilityValue.Value *= multiplyValue; } - else if (abilityData is (object _, AbilityValue tupleAbilityValue)) - { - ApplyEffectSpecific(tupleAbilityValue); - } - } - - private void ApplyEffectSpecific(AbilityValue abilityValue) - { - abilityValue.Value += addedValue; - abilityValue.Value *= multiplierValue; - } - - } - - // this seems like a real silly way to have to pass values by reference into these same interfaces - // if more of these are required, maybe there should be an additional set of interfaces to easily pass values by reference instead - class AbilityValue - { - public float Value { get; set; } - public AbilityValue(float value) - { - Value = value; } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityResetPermanentStat.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityResetPermanentStat.cs index eb57809e5..06706568c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityResetPermanentStat.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityResetPermanentStat.cs @@ -5,7 +5,7 @@ namespace Barotrauma.Abilities class CharacterAbilityResetPermanentStat : CharacterAbility { private readonly string statIdentifier; - public override bool RequiresAlive => false; + public override bool AppliesEffectOnIntervalUpdate => true; public CharacterAbilityResetPermanentStat(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) { 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..15772796c --- /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(); + } + + protected override void ApplyEffect() + { + ApplyEffectSpecific(); + } + + protected override void ApplyEffect(object abilityData) + { + ApplyEffectSpecific(); + } + } +} 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..9b498a085 --- /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 int maxAmount; + private readonly string[] tags; + + public CharacterAbilityAlienHoarder(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) + { + addedDamageMultiplierPerItem = abilityElement.GetAttributeFloat("addeddamagemultiplierperitem", 0f); + maxAmount = abilityElement.GetAttributeInt("maxamount", 0); + tags = abilityElement.GetAttributeStringArray("tags", Array.Empty(), convertToLowerInvariant: true); + } + + protected override void ApplyEffect(object abilityData) + { + if (abilityData 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 += addedDamageMultiplierPerItem; + } + else + { + LogAbilityDataMismatch(); + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityApprenticeship.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityApprenticeship.cs index 808ca4b0b..8196229ff 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityApprenticeship.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityApprenticeship.cs @@ -12,9 +12,9 @@ namespace Barotrauma.Abilities protected override void ApplyEffect(object abilityData) { - if (abilityData is (string skillIdentifier, Character character) && character != Character) + if (abilityData is AbilityStringCharacter abilityStringCharacter && abilityStringCharacter.Character != Character) { - character.Info?.IncreaseSkillLevel(skillIdentifier, 1.0f, character.Position + Vector2.UnitY * 175.0f); + Character.Info?.IncreaseSkillLevel(abilityStringCharacter.String, 1.0f, abilityStringCharacter.Character.Position + Vector2.UnitY * 175.0f); } } } 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..de83bf9a9 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityByTheBook.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class CharacterAbilityByTheBook : CharacterAbility + { + private int moneyAmount; + private int max; + + public CharacterAbilityByTheBook(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) + { + moneyAmount = abilityElement.GetAttributeInt("moneyamount", 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); + timesGiven++; + } + + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityInsurancePolicy.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityInsurancePolicy.cs index 9241769e4..1583bd53b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityInsurancePolicy.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityInsurancePolicy.cs @@ -9,45 +9,22 @@ namespace Barotrauma.Abilities class CharacterAbilityInsurancePolicy : CharacterAbility { public override bool AppliesEffectOnIntervalUpdate => true; - public override bool RequiresAlive => false; - private readonly int moneyPerLevel; - private bool hasOccurred = false; + private readonly int moneyPerMission; private static List clientsAlreadyUsed = new List(); public CharacterAbilityInsurancePolicy(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) { - moneyPerLevel = abilityElement.GetAttributeInt("moneyperlevel", 0); + moneyPerMission = abilityElement.GetAttributeInt("moneypermission", 0); } protected override void ApplyEffect() { - if (Character?.Info is CharacterInfo info && !hasOccurred) + if (Character?.Info is CharacterInfo info) { - if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer) - { - foreach (Client client in GameMain.NetworkMember.ConnectedClients) - { - if (client.Character == Character && clientsAlreadyUsed.Contains(client)) { return; } - } - } - Character.GiveMoney(moneyPerLevel * info.GetCurrentLevel()); - hasOccurred = true; - - // this is an ugly way to do this, but this effect should not occur more than once per round for a client - // this seemed like the simplest way to do it since characters are instantiated from scratch each time - if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer) - { - foreach (Client client in GameMain.NetworkMember.ConnectedClients) - { - if (client.Character == Character) - { - clientsAlreadyUsed.Add(client); - } - } - } + Character.GiveMoney(moneyPerMission * info.MissionsCompletedSinceDeath); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityPsychoClown.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityPsychoClown.cs index 4e7ac7225..61e9d9cf6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityPsychoClown.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityPsychoClown.cs @@ -5,14 +5,14 @@ namespace Barotrauma.Abilities class CharacterAbilityPsychoClown : CharacterAbility { private StatTypes statType; - private float value; + 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); - value = abilityElement.GetAttributeFloat("value", 0f); + maxValue = abilityElement.GetAttributeFloat("maxvalue", 0f); afflictionIdentifier = abilityElement.GetAttributeString("afflictionidentifier", ""); } @@ -32,7 +32,7 @@ namespace Barotrauma.Abilities afflictionStrength = affliction.Strength / affliction.Prefab.MaxStrength; } - lastValue = afflictionStrength * value; + lastValue = afflictionStrength * maxValue; Character.ChangeStat(statType, lastValue); } else diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityRegenerateLoot.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityRegenerateLoot.cs index 5a2ff174e..cb82641f5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityRegenerateLoot.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityRegenerateLoot.cs @@ -8,6 +8,8 @@ namespace Barotrauma.Abilities { class CharacterAbilityRegenerateLoot : CharacterAbility { + // not maintained through death, so it's possible for players to respawn and re-loot chests + // seems like a minor issue for now List openedContainers = new List(); public CharacterAbilityRegenerateLoot(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityStonewall.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityStonewall.cs index 6597c6973..cc2aa51c0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityStonewall.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityStonewall.cs @@ -10,20 +10,20 @@ namespace Barotrauma.Abilities { private readonly List statusEffects; private readonly List statusEffectsReset; - private int maxEnemyCount; - private float squaredDistance; + private readonly int maxEnemyCount; + private readonly float squaredDistance; public CharacterAbilityStonewall(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) { statusEffects = CharacterAbilityGroup.ParseStatusEffects(CharacterTalent, abilityElement.GetChildElement("statuseffects")); statusEffectsReset = CharacterAbilityGroup.ParseStatusEffects(CharacterTalent, abilityElement.GetChildElement("statuseffectsreset")); maxEnemyCount = abilityElement.GetAttributeInt("maxenemycount", 0); - squaredDistance = DistanceToSquaredDistance(abilityElement.GetAttributeFloat("distance", 0)); + squaredDistance = MathF.Pow(abilityElement.GetAttributeFloat("distance", 0), 2); } protected override void VerifyState(bool conditionsMatched, float timeSinceLastUpdate) { - int numberOfEnemiesInRange = Character.CharacterList.Where(c => !HumanAIController.IsFriendly(Character, c) && !c.IsDead && Vector2.DistanceSquared(Character.SimPosition, Character.GetRelativeSimPosition(c)) < squaredDistance).Count(); + int numberOfEnemiesInRange = Character.CharacterList.Count(c => !HumanAIController.IsFriendly(Character, c) && !c.IsDead && Vector2.DistanceSquared(Character.WorldPosition, c.WorldPosition) < squaredDistance); foreach (var statusEffect in statusEffectsReset) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityTandemFire.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityTandemFire.cs index 284ec4ae6..e3b3ba0c3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityTandemFire.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityTandemFire.cs @@ -8,6 +8,7 @@ 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) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroup.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroup.cs index c49fa439c..1c5471f24 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroup.cs @@ -14,6 +14,10 @@ namespace Barotrauma.Abilities // currently only used to turn off simulation if random conditions are in use public bool IsActive { get; private set; } = true; + protected int maxTriggerCount { get; } + protected int timesTriggered = 0; + + // add support for OR conditions? protected readonly List abilityConditions = new List(); @@ -24,7 +28,7 @@ namespace Barotrauma.Abilities { CharacterTalent = characterTalent; Character = CharacterTalent.Character; - + maxTriggerCount = abilityElementGroup.GetAttributeInt("maxtriggercount", int.MaxValue); foreach (XElement subElement in abilityElementGroup.Elements()) { switch (subElement.Name.ToString().ToLowerInvariant()) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroupEffect.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroupEffect.cs index f49851390..184c5e8a9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroupEffect.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroupEffect.cs @@ -27,6 +27,7 @@ namespace Barotrauma.Abilities private bool IsApplicable(object abilityData) { + if (timesTriggered >= maxTriggerCount) { return false; } return abilityConditions.All(c => c.MatchesCondition(abilityData)); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroupInterval.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroupInterval.cs index 6597bbcd6..4e3d0896f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroupInterval.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroupInterval.cs @@ -14,6 +14,7 @@ namespace Barotrauma.Abilities private float effectDelay; private float effectDelayTimer; + public CharacterAbilityGroupInterval(CharacterTalent characterTalent, XElement abilityElementGroup) : base(characterTalent, abilityElementGroup) { // too many overlapping intervals could cause hitching? maybe randomize a little @@ -42,6 +43,7 @@ namespace Barotrauma.Abilities } 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 index 9c64f85ea..e9ac4347a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/CharacterTalent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/CharacterTalent.cs @@ -111,8 +111,7 @@ namespace Barotrauma public static AbilityEffectType ParseAbilityEffectType(CharacterTalent characterTalent, string abilityEffectTypeString) { - AbilityEffectType abilityEffectType = AbilityEffectType.Undefined; - if (!Enum.TryParse(abilityEffectTypeString, true, out abilityEffectType)) + if (!Enum.TryParse(abilityEffectTypeString, true, out AbilityEffectType abilityEffectType)) { DebugConsole.ThrowError("Invalid ability effect type \"" + abilityEffectTypeString + "\" in CharacterTalent (" + characterTalent.DebugIdentifier + ")"); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/TalentPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/TalentPrefab.cs index 5c42eb8e7..277af65b0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/TalentPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/TalentPrefab.cs @@ -1,7 +1,5 @@ -using Microsoft.Xna.Framework; -using System; +using System; using System.Collections.Generic; -using System.Linq; using System.Xml.Linq; namespace Barotrauma @@ -13,6 +11,12 @@ namespace Barotrauma 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 @@ -26,7 +30,51 @@ namespace Barotrauma 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; @@ -94,9 +142,6 @@ namespace Barotrauma { LoadFromFile(file); } - } - - } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/TalentTree.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/TalentTree.cs index 2a2f67bdf..6483edaa1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/TalentTree.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/TalentTree.cs @@ -14,6 +14,7 @@ namespace Barotrauma private static HashSet subtreeTalents = new HashSet(); + private const string PlaceholderTalent = "placeholder"; public XElement ConfigElement { get; @@ -41,6 +42,7 @@ namespace Barotrauma HashSet duplicateSet = new HashSet(); foreach (string talent in TalentSubTrees.SelectMany(s => s.TalentOptionStages.SelectMany(o => o.Talents.Select(t => t.Identifier)))) { + if (talent == PlaceholderTalent) { continue; } TalentPrefab talentPrefab = TalentPrefab.TalentPrefabs.Find(c => c.Identifier.Equals(talent, StringComparison.OrdinalIgnoreCase)); if (talentPrefab == null) { @@ -118,6 +120,7 @@ namespace Barotrauma public static bool IsViableTalentForCharacter(Character character, string talentIdentifier, IEnumerable selectedTalents) { + if (talentIdentifier == PlaceholderTalent) { return false; } if (character?.Info?.Job.Prefab == null) { return false; } if (character.Info.GetTotalTalentPoints() - selectedTalents.Count() <= 0) { return false; } @@ -178,7 +181,7 @@ namespace Barotrauma foreach (XElement talentOptionsElement in subTreeElement.GetChildElements("talentoptions")) { - TalentOptionStages.Add(new TalentOption(talentOptionsElement)); + TalentOptionStages.Add(new TalentOption(talentOptionsElement, Identifier)); } } @@ -186,32 +189,19 @@ namespace Barotrauma class TalentOption { - public readonly List Talents = new List(); + public readonly List Talents = new List(); - public TalentOption(XElement talentOptionsElement) + public TalentOption(XElement talentOptionsElement, string debugIdentifier) { foreach (XElement talentOptionElement in talentOptionsElement.GetChildElements("talentoption")) { - Talents.Add(new Talent(talentOptionElement)); - } - } - } - - class Talent - { - public readonly string Identifier; - public readonly Sprite Icon; - public Talent(XElement talentOptionElement) - { - Identifier = talentOptionElement.GetAttributeString("identifier", ""); - foreach (XElement subElement in talentOptionElement.Elements()) - { - switch (subElement.Name.ToString().ToLowerInvariant()) + string identifier = talentOptionElement.GetAttributeString("identifier", string.Empty); + if (!TalentPrefab.TalentPrefabs.ContainsKey(identifier)) { - case "icon": - Icon = new Sprite(subElement); - break; + 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/DebugConsole.cs b/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs index e768d963d..39810a12f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs @@ -212,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 @@ -841,8 +841,8 @@ namespace Barotrauma commands.Add(new Command("givetalent", "give [player] testing [talent]", (string[] args) => { - if (args.Length < 2) return; - var character = FindMatchingCharacter(args.Skip(1).ToArray()) ?? Character.Controlled; + if (args.Length == 0) { return; } + var character = args.Length >= 2 ? FindMatchingCharacter(args.Skip(1).ToArray()) : Character.Controlled; if (character != null) { character.GiveTalent(args[0]); @@ -858,8 +858,8 @@ namespace Barotrauma return new string[][] { - talentNames.ToArray(), - Character.CharacterList.Select(c => c.Name).Distinct().ToArray() + talentNames.ToArray(), + Character.CharacterList.Select(c => c.Name).Distinct().ToArray() }; }, isCheat: true)); @@ -2033,9 +2033,17 @@ 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; } + } + + switch (spawnLocation) { case "cursor": spawnPos = cursorPos; @@ -2063,37 +2071,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 c127555da..346ceb31e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Enums.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Enums.cs @@ -45,14 +45,18 @@ OnReduceAffliction, OnAddDamageAffliction, OnSelfRagdoll, + OnRoundEnd, OnAnyMissionCompleted, OnAllMissionsCompleted, OnGiveOrder, OnCrewKillCharacter, + OnKillCharacter, OnDieToCharacter, OnAllyGainMissionExperience, OnGainMissionExperience, OnGainMissionMoney, + OnItemDeconstructed, + OnItemDeconstructedMaterial, AfterSubmarineAttacked, } @@ -68,23 +72,32 @@ // Character attributes MaximumHealthMultiplier, MovementSpeed, + WalkingSpeed, SwimmingSpeed, BuffDurationMultiplier, DebuffDurationMultiplier, + MedicalItemEffectivenessMultiplier, // Combat AttackMultiplier, + TeamAttackMultiplier, RangedAttackSpeed, TurretAttackSpeed, + TurretPowerCostReduction, MeleeAttackSpeed, - SpreadMultiplier, + MeleeAttackMultiplier, + RangedSpreadReduction, // Utility RepairSpeed, + DeconstructorSpeedMultiplier, // Misc ReputationGainMultiplier, MissionMoneyGainMultiplier, ExperienceGainMultiplier, MissionExperienceGainMultiplier, - + // these should be deprecated and moved to their own implementation, no sense making them share space with stat values + Coathor, + WarriorPoetMissionRuns, + WarriorPoetEnemiesKilled, } public enum AbilityFlags @@ -95,6 +108,8 @@ IgnoredByEnemyAI, MoveNormallyWhileDragging, CanTinker, + GainSkillPastMaximum, + RetainExperienceForNewCharacter } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs index 47b9637e2..d6adbf7b5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs @@ -362,11 +362,16 @@ namespace Barotrauma } // apply money gains afterwards to prevent them from affecting XP gains - var moneyGainMultiplier = new AbilityValue(1f); - crewCharacters.ForEach(c => c.CheckTalents(AbilityEffectType.OnGainMissionMoney, (this, moneyGainMultiplier))); - crewCharacters.ForEach(c => moneyGainMultiplier.Value += c.GetStatValue(StatTypes.MissionMoneyGainMultiplier)); + 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 * moneyGainMultiplier.Value); + campaign.Money += (int)(reward * moneyGainMission.Value); + + foreach (Character character in crewCharacters) + { + character.Info.MissionsCompletedSinceDeath++; + } foreach (KeyValuePair reputationReward in ReputationRewards) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs index fdf7fa68a..4f941e847 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs @@ -531,6 +531,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 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 0a2e0bdd3..8b6cbeb36 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs @@ -661,7 +661,7 @@ namespace Barotrauma #if SERVER return GameMain.Server.ConnectedClients.Select(c => c.Character).Where(c => c.Info != null); #else - return GameMain.GameSession.CrewManager.CharacterInfos.Select(i => i.Character).Where(c => c != null); + return GameMain.GameSession.CrewManager.GetCharacters().Where(c => c.Info != null); #endif } @@ -671,28 +671,33 @@ namespace Barotrauma try { - IEnumerable crewCharacters = GameSession.GetSessionCrewCharacters(); + IEnumerable crewCharacters = GetSessionCrewCharacters(); foreach (Mission mission in missions) { mission.End(); } + foreach (Character character in crewCharacters) + { + character.CheckTalents(AbilityEffectType.OnRoundEnd); + } + if (missions.Any()) { if (missions.Any(m => m.Completed)) { - foreach (CharacterInfo characterInfo in GameMain.GameSession.CrewManager.CharacterInfos) + foreach (Character character in crewCharacters) { - characterInfo.Character?.CheckTalents(AbilityEffectType.OnAnyMissionCompleted); + character.CheckTalents(AbilityEffectType.OnAnyMissionCompleted); } } if (missions.All(m => m.Completed)) { - foreach (CharacterInfo characterInfo in GameMain.GameSession.CrewManager.CharacterInfos) + foreach (Character character in crewCharacters) { - characterInfo.Character?.CheckTalents(AbilityEffectType.OnAllMissionsCompleted); + character.CheckTalents(AbilityEffectType.OnAllMissionsCompleted); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/CharacterInventory.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/CharacterInventory.cs index 49bfdb3e8..8b65d6f30 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 diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/GeneticMaterial.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/GeneticMaterial.cs new file mode 100644 index 000000000..e9aa55e2b --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/GeneticMaterial.cs @@ -0,0 +1,179 @@ +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("", false)] + public string Effect + { + get; + set; + } + + [Serialize("geneticmaterialdebuff", false)] + public string TaintedEffect + { + get; + set; + } + + private bool tainted; + [Serialize(false, false)] + 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("", false)] + 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; + } + + 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)); + 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)); + targetCharacter = character; +#if SERVER + item.CreateServerEvent(this); +#endif + } + } + + public override void Update(float deltaTime, Camera cam) + { + base.Update(deltaTime, cam); + if (targetCharacter != null) + { + if (!targetCharacter.HasEquippedItem(item) && + (item.Container == null || !targetCharacter.HasEquippedItem(item.Container) || !(item.Container.GetComponent()?.AutoInject ?? false))) + { + item.ApplyStatusEffects(ActionType.OnSevered, 1.0f, targetCharacter); + var currentEffect = tainted ? selectedTaintedEffect : selectedEffect; + targetCharacter.CharacterHealth.ReduceAffliction(null, currentEffect.Identifier, currentEffect.MaxStrength); + targetCharacter = null; + IsActive = false; + } + } + } + + public bool Combine(GeneticMaterial otherGeneticMaterial, Character user) + { + if (!CanBeCombinedWith(otherGeneticMaterial)) { return false; } + if (item.Prefab == otherGeneticMaterial.item.Prefab) + { + item.Condition = Math.Max(item.Condition, otherGeneticMaterial.item.Condition) + Rand.Range(ConditionIncreaseOnCombineMin, ConditionIncreaseOnCombineMax); + 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 + Rand.Range(ConditionIncreaseOnCombineMin, ConditionIncreaseOnCombineMax); + item.OwnInventory?.TryPutItem(otherGeneticMaterial.Item, user: null); + 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 probability; + } + + private void MakeTainted() + { + if (GameMain.NetworkMember?.IsClient ?? false) { return; } + Tainted = true; +#if SERVER + item.CreateServerEvent(this); +#endif + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/MeleeWeapon.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/MeleeWeapon.cs index b7174abdf..312df8e44 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/MeleeWeapon.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/MeleeWeapon.cs @@ -181,7 +181,7 @@ namespace Barotrauma.Items.Components if (aim) { hitPos = MathUtils.WrapAnglePi(Math.Min(hitPos + deltaTime * 5f, MathHelper.PiOver4)); - ac.HoldItem(deltaTime, item, handlePos, aimPos, Vector2.Zero, false, hitPos, holdAngle + hitPos); + ac.HoldItem(deltaTime, item, handlePos, aimPos, Vector2.Zero, false, hitPos, holdAngle + hitPos, aimingMelee: true); } else { @@ -356,6 +356,7 @@ namespace Barotrauma.Items.Components if (Attack != null) { Attack.SetUser(User); + Attack.DamageMultiplier = 1 + User.GetStatValue(StatTypes.MeleeAttackMultiplier); if (targetLimb != null) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RangedWeapon.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RangedWeapon.cs index d628c5d40..14d626d16 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RangedWeapon.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RangedWeapon.cs @@ -107,7 +107,7 @@ namespace Barotrauma.Items.Components if (ReloadTimer < 0.0f) { ReloadTimer = 0.0f; - // was this an optimization or related to something else? currently disabled for charge-type weapons + // was this an optimization or related to something else? it cannot occur for charge-type weapons //IsActive = false; if (MaxChargeTime == 0.0f) { @@ -118,7 +118,7 @@ namespace Barotrauma.Items.Components float previousChargeTime = currentChargeTime; - float chargeDeltaTime = tryingToCharge ? deltaTime : -deltaTime; + float chargeDeltaTime = tryingToCharge && ReloadTimer <= 0f ? deltaTime : -deltaTime; currentChargeTime = Math.Clamp(currentChargeTime + chargeDeltaTime, 0f, MaxChargeTime); tryingToCharge = false; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemComponent.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemComponent.cs index 90fd92739..c146ae67e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemComponent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemComponent.cs @@ -982,7 +982,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 336a5b183..76093809d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Xml.Linq; using Barotrauma.Extensions; using FarseerPhysics; +using System.Collections.Immutable; namespace Barotrauma.Items.Components { @@ -23,6 +24,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; @@ -73,6 +96,7 @@ namespace Barotrauma.Items.Components #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,7 +114,6 @@ namespace Barotrauma.Items.Components 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 +121,9 @@ namespace Barotrauma.Items.Components set; } + [Serialize(true, false)] + public bool AllowAccess { get; set; } + [Serialize(false, false)] public bool AccessOnlyWhenBroken { get; set; } @@ -147,7 +173,7 @@ namespace Barotrauma.Items.Components set; } - [Serialize(0.5f, false, description: "The rotation in which the contained sprites are drawn (in degrees).")] + [Serialize(0.5f, false, description: "The health threshold that the user must reach in order to activate the autoinjection.")] public float AutoInjectThreshold { get; @@ -157,10 +183,12 @@ namespace Barotrauma.Items.Components [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)); } @@ -168,22 +196,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()) @@ -195,34 +223,92 @@ 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) + RelatedItem ri = slotRestrictions[index].ContainableItems?.Find(ci => ci.MatchesItem(containedItem)); + if (ri != null) { - activeContainedItems.Add(new ActiveContainedItem(containedItem, effect, ri.ExcludeBroken)); + activeContainedItems.RemoveAll(i => i.Item == containedItem); + foreach (StatusEffect effect in ri.statusEffects) + { + activeContainedItems.Add(new ActiveContainedItem(containedItem, effect, ri.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); @@ -233,13 +319,11 @@ 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(ItemPrefab itemPrefab) { - if (ContainableItems.Count == 0) { return true; } - return ContainableItems.Find(c => c.MatchesItem(itemPrefab)) != null; + return slotRestrictions.Any(s => s.MatchesItem(itemPrefab)); } readonly List targets = new List(); @@ -264,6 +348,7 @@ namespace Barotrauma.Items.Components foreach (Item item in Inventory.AllItemsMod) { item.ApplyStatusEffects(ActionType.OnUse, 1.0f, ownerCharacter); + item.GetComponent()?.Equip(ownerCharacter); } } } @@ -304,11 +389,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) { @@ -335,6 +421,7 @@ namespace Barotrauma.Items.Components public override bool Pick(Character picker) { + if (!AllowAccess) { return false; } if (AccessOnlyWhenBroken) { if (item.Condition > 0) @@ -362,7 +449,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)) @@ -392,50 +479,59 @@ 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, transform); + 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; } @@ -492,6 +588,7 @@ namespace Barotrauma.Items.Components public override void OnItemLoaded() { + containableItemIdentifiers = slotRestrictions.SelectMany(s => s.ContainableItems?.SelectMany(ri => ri.Identifiers) ?? Enumerable.Empty()).ToImmutableHashSet(); if (item.Submarine == null || !item.Submarine.Loading) { SpawnAlwaysContainedItems(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Deconstructor.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Deconstructor.cs index 575401584..3fe8d3b74 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,10 @@ namespace Barotrauma.Items.Components private bool hasPower; + private Character user; + + private float userDeconstructorSpeedMultiplier = 1.0f; + private ItemContainer inputContainer, outputContainer; public ItemContainer InputContainer @@ -25,7 +30,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 +89,149 @@ 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) + if (DeconstructItemsSimultaneously) { - // In multiplayer, the server handles the deconstruction into new items - if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; } - - if (targetItem.Prefab.RandomDeconstructionOutput) + 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); - } - } - else - { - foreach (DeconstructItem deconstructProduct in targetItem.Prefab.DeconstructItems) - { - CreateDeconstructProduct(deconstructProduct); - } + deconstructTime += targetItem.Prefab.DeconstructTime / (DeconstructionSpeed * userDeconstructorSpeedMultiplier); } - void CreateDeconstructProduct(DeconstructItem deconstructProduct) + progressState = Math.Min(progressTimer / deconstructTime, 1.0f); + if (progressTimer > deconstructTime) { - float percentageHealth = targetItem.Condition / targetItem.Prefab.Health; - if (percentageHealth <= deconstructProduct.MinCondition || percentageHealth > deconstructProduct.MaxCondition) { return; } - - if (!(MapEntityPrefab.Find(null, deconstructProduct.ItemIdentifier) is ItemPrefab itemPrefab)) + List items = inputContainer.Inventory.AllItems.ToList(); + foreach (Item targetItem in items) { - DebugConsole.ThrowError("Tried to deconstruct item \"" + targetItem.Name + "\" but couldn't find item prefab \"" + deconstructProduct.ItemIdentifier + "\"!"); - return; + 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.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; - float condition = deconstructProduct.CopyCondition ? - percentageHealth * itemPrefab.Health : - itemPrefab.Health * deconstructProduct.OutCondition; + } + } + 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 : 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 (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); + } + + user.CheckTalents(AbilityEffectType.OnItemDeconstructed, targetItem); + + 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.Prefab.Health; + 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))) + { + 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); + } + } + } + var itemsCreated = new AbilityValue(1f); + user.CheckTalents(AbilityEffectType.OnItemDeconstructedMaterial, (targetItem.Prefab, itemsCreated)); + + int amount = (int)itemsCreated.Value; + + 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 +245,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 +277,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 +288,7 @@ namespace Barotrauma.Items.Components if (itemContainer == null) { continue; } outputContainer.Inventory.AllItemsMod.ForEach(containedItem => itemContainer.Inventory.TryPutItem(containedItem, user: null, createNetworkEvent: true)); } - } + } } /// public AbilityConditionData(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) { } - protected void LogAbilityConditionError(T abilityData, Type expectedData) + protected void LogAbilityConditionError(AbilityObject abilityObject, Type expectedData) { - DebugConsole.ThrowError($"Used data-reliant ability condition when data is incompatible! Expected {expectedData}, but received {abilityData}"); + DebugConsole.ThrowError($"Used data-reliant ability condition when data is incompatible! Expected {expectedData}, but received {abilityObject}"); } - protected abstract bool MatchesConditionSpecific(object abilityData); + 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(object abilityData) + public override bool MatchesCondition(AbilityObject abilityObject) { - if (abilityData is null) { return invert; } - return invert ? !MatchesConditionSpecific(abilityData) : MatchesConditionSpecific(abilityData); + 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 index 44336e5b8..2e1204fba 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionEvasiveManeuvers.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionEvasiveManeuvers.cs @@ -6,15 +6,15 @@ namespace Barotrauma.Abilities { public AbilityConditionEvasiveManeuvers(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) { } - protected override bool MatchesConditionSpecific(object abilityData) + protected override bool MatchesConditionSpecific(AbilityObject abilityObject) { - if (abilityData is Submarine submarine) + if ((abilityObject as IAbilitySubmarine)?.Submarine is Submarine submarine && (abilityObject as IAbilityCharacter)?.Character is Character attackingCharacter) { - return submarine.TeamID == character.TeamID && character.Submarine == submarine; + return submarine.TeamID == character.TeamID && character.Submarine == submarine && attackingCharacter.TeamID != character.TeamID; } else { - LogAbilityConditionError(abilityData, typeof(Submarine)); + LogAbilityConditionError(abilityObject, typeof(IAbilitySubmarine)); 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 index 4e6b530cd..ff6cba362 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionItem.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionItem.cs @@ -15,33 +15,33 @@ namespace Barotrauma.Abilities tags = conditionElement.GetAttributeStringArray("tags", Array.Empty(), convertToLowerInvariant: true); } - protected override bool MatchesConditionSpecific(object abilityData) + protected override bool MatchesConditionSpecific(AbilityObject abilityObject) { - ItemPrefab item = null; - if (abilityData is Item tempItem) + ItemPrefab itemPrefab = null; + if ((abilityObject as IAbilityItemPrefab)?.ItemPrefab is ItemPrefab abilityItemPrefab) { - item = tempItem.Prefab; + itemPrefab = abilityItemPrefab; } - else if (abilityData is IAbilityItemPrefab abilityItemPrefab) + else if ((abilityObject as IAbilityItem)?.Item is Item abilityItem) { - item = abilityItemPrefab.ItemPrefab; + itemPrefab = abilityItem.Prefab; } - if (item != null) + if (itemPrefab != null) { if (!string.IsNullOrEmpty(identifier)) { - if (item.Identifier != identifier) + if (itemPrefab.Identifier != identifier) { return false; } } - return tags.Any(t => item.Tags.Any(p => t == p)); + return tags.Any(t => itemPrefab.Tags.Any(p => t == p)); } else { - LogAbilityConditionError(abilityData, typeof(Item)); + LogAbilityConditionError(abilityObject, typeof(IAbilityItemPrefab)); 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 index 888217b9f..1068da08b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionReduceAffliction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionReduceAffliction.cs @@ -13,19 +13,19 @@ namespace Barotrauma.Abilities identifier = conditionElement.GetAttributeString("identifier", ""); } - protected override bool MatchesConditionSpecific(object abilityData) + protected override bool MatchesConditionSpecific(AbilityObject abilityObject) { - if (abilityData is IAbilityAffliction abilityAffliction) + if ((abilityObject as IAbilityAffliction)?.Affliction is Affliction affliction) { - if (allowedTypes.Find(c => c == abilityAffliction.Affliction.Prefab.AfflictionType) == null) { return false; } + if (allowedTypes.Find(c => c == affliction.Prefab.AfflictionType) == null) { return false; } - if (!string.IsNullOrEmpty(identifier) && abilityAffliction.Affliction.Prefab.Identifier != identifier) { return false; } + if (!string.IsNullOrEmpty(identifier) && affliction.Prefab.Identifier != identifier) { return false; } return true; } else { - LogAbilityConditionError(abilityData, typeof(IAbilityAffliction)); + LogAbilityConditionError(abilityObject, typeof(IAbilityAffliction)); return false; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionScavenger.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionScavenger.cs index 2156ccae1..e1ca65e72 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionScavenger.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionScavenger.cs @@ -4,17 +4,18 @@ namespace Barotrauma.Abilities { class AbilityConditionScavenger : AbilityConditionData { + public AbilityConditionScavenger(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) { } - protected override bool MatchesConditionSpecific(object abilityData) + protected override bool MatchesConditionSpecific(AbilityObject abilityObject) { - if (abilityData is Item item) + if ((abilityObject as IAbilityItem)?.Item is Item item) { - return item.Submarine != character.Submarine; + return item.Submarine == null || item.Submarine.TeamID != character.Info.TeamID; } else { - LogAbilityConditionError(abilityData, typeof(Item)); + LogAbilityConditionError(abilityObject, typeof(IAbilityItem)); return false; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionScrounger.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionScrounger.cs new file mode 100644 index 000000000..9fc5b00cb --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionScrounger.cs @@ -0,0 +1,23 @@ +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class AbilityConditionScrounger : AbilityConditionData + { + + public AbilityConditionScrounger(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/AbilityConditionSkill.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionSkill.cs index 37fd19923..5c368df8f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionSkill.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionSkill.cs @@ -16,15 +16,15 @@ namespace Barotrauma.Abilities return this.skillIdentifier == skillIdentifier; } - protected override bool MatchesConditionSpecific(object abilityData) + protected override bool MatchesConditionSpecific(AbilityObject abilityObject) { - if ((abilityData as string ?? (abilityData as IAbilityString)?.String) is string skillIdentifier) + if ((abilityObject as IAbilityString)?.String is string skillIdentifier) { return MatchesConditionSpecific(skillIdentifier); } else { - LogAbilityConditionError(abilityData, typeof(string)); + LogAbilityConditionError(abilityObject, typeof(IAbilityString)); return false; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionDataless.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionDataless.cs index de8bccced..ad7007fd6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionDataless.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionDataless.cs @@ -12,7 +12,7 @@ namespace Barotrauma.Abilities return invert ? !MatchesConditionSpecific() : MatchesConditionSpecific(); } - public override bool MatchesCondition(object abilityData) + public override bool MatchesCondition(AbilityObject abilityObject) { return invert ? !MatchesConditionSpecific() : MatchesConditionSpecific(); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionMission.cs index 29cab2f45..f7f0ffed4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionMission.cs @@ -22,15 +22,15 @@ namespace Barotrauma.Abilities } } - protected override bool MatchesConditionSpecific(object abilityData) + protected override bool MatchesConditionSpecific(AbilityObject abilityObject) { - if (abilityData is IAbilityMission abilityMission) + if ((abilityObject as IAbilityMission)?.Mission is Mission mission) { - return abilityMission.Mission.Prefab.Type == missionType; + return mission.Prefab.Type == missionType; } else { - LogAbilityConditionError(abilityData, typeof(IAbilityMission)); + LogAbilityConditionError(abilityObject, typeof(IAbilityMission)); return false; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityInterfaces.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityInterfaces.cs index 46478b7b7..5a169edf3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityInterfaces.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityInterfaces.cs @@ -5,6 +5,11 @@ public ItemPrefab ItemPrefab { get; set; } } + interface IAbilityItem + { + public Item Item { get; set; } + } + interface IAbilityValue { public float Value { get; set; } @@ -29,4 +34,14 @@ { 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 index 9247ad159..292be7b94 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityObjects.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityObjects.cs @@ -2,8 +2,30 @@ namespace Barotrauma.Abilities { + class AbilityObject + { + // kept as blank for now, as we are using a composition and only using this object to enforce parameter types + } - class AbilityValue : IAbilityValue + 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) { @@ -12,7 +34,16 @@ namespace Barotrauma.Abilities public float Value { get; set; } } - class AbilityValueItem : IAbilityValue, IAbilityItemPrefab + class AbilityAffliction : AbilityObject, IAbilityAffliction + { + public AbilityAffliction(Affliction affliction) + { + Affliction = affliction; + } + public Affliction Affliction { get; set; } + } + + class AbilityValueItem : AbilityObject, IAbilityValue, IAbilityItemPrefab { public AbilityValueItem(float value, ItemPrefab itemPrefab) { @@ -23,7 +54,7 @@ namespace Barotrauma.Abilities public ItemPrefab ItemPrefab { get; set; } } - class AbilityValueString : IAbilityValue, IAbilityString + class AbilityValueString : AbilityObject, IAbilityValue, IAbilityString { public AbilityValueString(float value, string abilityString) { @@ -34,7 +65,20 @@ namespace Barotrauma.Abilities public string String { get; set; } } - class AbilityStringCharacter : IAbilityCharacter, IAbilityString + class AbilityValueStringCharacter : AbilityObject, IAbilityValue, IAbilityString + { + public AbilityValueStringCharacter(float value, string abilityString, Character character) + { + Value = value; + String = abilityString; + Character = character; + } + public Character Character { get; set; } + public float Value { get; set; } + public string String { get; set; } + } + + class AbilityStringCharacter : AbilityObject, IAbilityCharacter, IAbilityString { public AbilityStringCharacter(string abilityString, Character character) { @@ -45,7 +89,7 @@ namespace Barotrauma.Abilities public string String { get; set; } } - class AbilityValueAffliction : IAbilityValue, IAbilityAffliction + class AbilityValueAffliction : AbilityObject, IAbilityValue, IAbilityAffliction { public AbilityValueAffliction(float value, Affliction affliction) { @@ -56,7 +100,7 @@ namespace Barotrauma.Abilities public Affliction Affliction { get; set; } } - class AbilityValueMission : IAbilityValue, IAbilityMission + class AbilityValueMission : AbilityObject, IAbilityValue, IAbilityMission { public AbilityValueMission(float value, Mission mission) { @@ -67,7 +111,8 @@ namespace Barotrauma.Abilities public Mission Mission { get; set; } } - class AbilityAttackData : IAbilityCharacter + // 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; @@ -82,4 +127,26 @@ namespace Barotrauma.Abilities Character = character; } } + + 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 index 9b4414901..ceaa14ee8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbility.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbility.cs @@ -65,15 +65,15 @@ namespace Barotrauma.Abilities DebugConsole.ThrowError($"Ability {this} does not have an implementation for VerifyState! This ability does not work in interval ability groups."); } - public void ApplyAbilityEffect(object abilityData) + public void ApplyAbilityEffect(AbilityObject abilityObject) { - if (abilityData is null) + if (abilityObject is null) { ApplyEffect(); } else { - ApplyEffect(abilityData); + ApplyEffect(abilityObject); } } @@ -82,12 +82,12 @@ namespace Barotrauma.Abilities DebugConsole.AddWarning($"Ability {this} used improperly! This ability does not have a definition for ApplyEffect"); } - protected virtual void ApplyEffect(object abilityData) + protected virtual void ApplyEffect(AbilityObject abilityObject) { DebugConsole.AddWarning($"Ability {this} used improperly! This ability does not take a parameter for ApplyEffect"); } - protected void LogAbilityDataMismatch() + protected void LogabilityObjectMismatch() { DebugConsole.ThrowError($"Incompatible ability! Ability {this} is incompatitible with this type of ability effect type."); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffects.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffects.cs index 773992306..6dbc6450e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffects.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffects.cs @@ -34,32 +34,33 @@ namespace Barotrauma.Abilities { targets.Clear(); targets.AddRange(statusEffect.GetNearbyTargets(targetCharacter.WorldPosition, targets)); + statusEffect.SetUser(Character); statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, targetCharacter, targets); } - else if (statusEffect.HasTargetType(StatusEffect.TargetType.This)) + 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); } - else - { - statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, Character, targetCharacter); - } } } - protected override void ApplyEffect() { ApplyEffectSpecific(Character); } - protected override void ApplyEffect(object abilityData) + protected override void ApplyEffect(AbilityObject abilityObject) { if (applyToSelected && Character.SelectedCharacter is Character selectedCharacter) { ApplyEffectSpecific(selectedCharacter); } - else if ((abilityData as Character ?? (abilityData as IAbilityCharacter)?.Character) is Character targetCharacter) + else if ((abilityObject as IAbilityCharacter)?.Character is Character targetCharacter) { ApplyEffectSpecific(targetCharacter); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffectsToAttacker.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffectsToAttacker.cs index 1f346accd..efca07622 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffectsToAttacker.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffectsToAttacker.cs @@ -9,9 +9,9 @@ namespace Barotrauma.Abilities { } - protected override void ApplyEffect(object abilityData) + protected override void ApplyEffect(AbilityObject abilityObject) { - if ((abilityData as AbilityAttackData)?.Attacker is Character attacker) + if ((abilityObject as AbilityAttackData)?.Attacker is Character attacker) { ApplyEffectSpecific(attacker); } 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..5de3fb85f --- /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, Character.Position + Vector2.UnitY * 175.0f); + } + else + { + LogabilityObjectMismatch(); + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveMoney.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveMoney.cs index 4ac33d7c7..a10d132c5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveMoney.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveMoney.cs @@ -27,9 +27,9 @@ namespace Barotrauma.Abilities targetCharacter.GiveMoney((int)(multiplier * amount)); } - protected override void ApplyEffect(object abilityData) + protected override void ApplyEffect(AbilityObject abilityObject) { - if ((abilityData as Character ?? (abilityData as IAbilityCharacter)?.Character) is Character targetCharacter) + if ((abilityObject as IAbilityCharacter)?.Character is Character targetCharacter) { ApplyEffectSpecific(targetCharacter); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGivePermanentStat.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGivePermanentStat.cs index c8147397c..55da7e1cc 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGivePermanentStat.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGivePermanentStat.cs @@ -12,6 +12,8 @@ namespace Barotrauma.Abilities private readonly bool targetAllies; private readonly bool removeOnDeath; private readonly bool removeAfterRound; + private readonly bool giveOnAddingFirstTime; + //private readonly float maximumValue; public override bool AppliesEffectOnIntervalUpdate => true; @@ -25,10 +27,19 @@ namespace Barotrauma.Abilities targetAllies = abilityElement.GetAttributeBool("targetallies", false); removeOnDeath = abilityElement.GetAttributeBool("removeondeath", true); removeAfterRound = abilityElement.GetAttributeBool("removeafterround", false); + giveOnAddingFirstTime = abilityElement.GetAttributeBool("giveonaddingfirsttime", false); //maximumValue = abilityElement.GetAttributeFloat("maximumvalue", float.MaxValue); } - protected override void ApplyEffect(object abilityData) + public override void InitializeAbility(bool addingFirstTime) + { + if (giveOnAddingFirstTime && addingFirstTime) + { + ApplyEffectSpecific(); + } + } + + protected override void ApplyEffect(AbilityObject abilityObject) { ApplyEffectSpecific(); } 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 index 44caf675a..830d4c9ce 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityIncreaseSkill.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityIncreaseSkill.cs @@ -21,9 +21,9 @@ namespace Barotrauma.Abilities ApplyEffectSpecific(Character); } - protected override void ApplyEffect(object abilityData) + protected override void ApplyEffect(AbilityObject abilityObject) { - if (abilityData is Character character) + if ((abilityObject as IAbilityCharacter)?.Character is Character character) { ApplyEffectSpecific(character); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyAffliction.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyAffliction.cs index 84aa32f90..5d073a068 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyAffliction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyAffliction.cs @@ -15,9 +15,9 @@ namespace Barotrauma.Abilities addedMultiplier = abilityElement.GetAttributeFloat("addedmultiplier", 0f); } - protected override void ApplyEffect(object abilityData) + protected override void ApplyEffect(AbilityObject abilityObject) { - if (abilityData is Affliction affliction) + if ((abilityObject as IAbilityAffliction)?.Affliction is Affliction affliction) { foreach (string afflictionIdentifier in afflictionIdentifiers) { @@ -29,7 +29,7 @@ namespace Barotrauma.Abilities } else { - LogAbilityDataMismatch(); + LogabilityObjectMismatch(); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyAttackData.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyAttackData.cs index 1d32acc85..2e742bb3f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyAttackData.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyAttackData.cs @@ -22,9 +22,9 @@ namespace Barotrauma.Abilities implode = abilityElement.GetAttributeBool("implode", false); } - protected override void ApplyEffect(object abilityData) + protected override void ApplyEffect(AbilityObject abilityObject) { - if (abilityData is AbilityAttackData attackData) + if (abilityObject is AbilityAttackData attackData) { if (attackData.Afflictions == null) { @@ -46,7 +46,7 @@ namespace Barotrauma.Abilities } else { - LogAbilityDataMismatch(); + LogabilityObjectMismatch(); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyReduceAffliction.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyReduceAffliction.cs index 828714266..affb06085 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyReduceAffliction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyReduceAffliction.cs @@ -12,15 +12,15 @@ namespace Barotrauma.Abilities addedAmountMultiplier = abilityElement.GetAttributeFloat("addedamountmultiplier", 0f); } - protected override void ApplyEffect(object abilityData) + protected override void ApplyEffect(AbilityObject abilityObject) { - if (abilityData is (Affliction affliction, float reduceAmount)) + if (abilityObject is AbilityValueAffliction afflictionReduceAmount) { - affliction.Strength -= addedAmountMultiplier * reduceAmount; + afflictionReduceAmount.Affliction.Strength -= addedAmountMultiplier * afflictionReduceAmount.Value; } else { - LogAbilityDataMismatch(); + LogabilityObjectMismatch(); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyValue.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyValue.cs index b4e15f0b3..8c12ea122 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyValue.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyValue.cs @@ -13,9 +13,9 @@ namespace Barotrauma.Abilities multiplyValue = abilityElement.GetAttributeFloat("multiplyvalue", 1f); } - protected override void ApplyEffect(object abilityData) + protected override void ApplyEffect(AbilityObject abilityObject) { - if (abilityData is IAbilityValue abilityValue) + if (abilityObject is IAbilityValue abilityValue) { abilityValue.Value += addedValue; abilityValue.Value *= multiplyValue; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityResetPermanentStat.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityResetPermanentStat.cs index 06706568c..0957982df 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityResetPermanentStat.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityResetPermanentStat.cs @@ -11,7 +11,7 @@ namespace Barotrauma.Abilities { statIdentifier = abilityElement.GetAttributeString("statidentifier", "").ToLowerInvariant(); } - protected override void ApplyEffect(object abilityData) + protected override void ApplyEffect(AbilityObject abilityObject) { ApplyEffectSpecific(); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityRevive.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityRevive.cs index 15772796c..317d12487 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityRevive.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityRevive.cs @@ -21,7 +21,7 @@ namespace Barotrauma.Abilities ApplyEffectSpecific(); } - protected override void ApplyEffect(object abilityData) + 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..d092e972c --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilitySpawnItemsToContainer.cs @@ -0,0 +1,39 @@ +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; + + public CharacterAbilitySpawnItemsToContainer(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) + { + statusEffects = CharacterAbilityGroup.ParseStatusEffects(CharacterTalent, abilityElement.GetChildElement("statuseffects")); + 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; } + + foreach (var statusEffect in statusEffects) + { + statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, item, item); + } + } + else + { + LogabilityObjectMismatch(); + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityAlienHoarder.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityAlienHoarder.cs index 9b498a085..cabb8a78c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityAlienHoarder.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityAlienHoarder.cs @@ -18,9 +18,9 @@ namespace Barotrauma.Abilities tags = abilityElement.GetAttributeStringArray("tags", Array.Empty(), convertToLowerInvariant: true); } - protected override void ApplyEffect(object abilityData) + protected override void ApplyEffect(AbilityObject abilityObject) { - if (abilityData is AbilityAttackData attackData) + if (abilityObject is AbilityAttackData attackData) { float totalAddedDamageMultiplier = 0f; foreach (Item item in Character.Inventory.AllItems) @@ -34,7 +34,7 @@ namespace Barotrauma.Abilities } else { - LogAbilityDataMismatch(); + LogabilityObjectMismatch(); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityApprenticeship.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityApprenticeship.cs index 8196229ff..b02fbe021 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityApprenticeship.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityApprenticeship.cs @@ -10,11 +10,11 @@ namespace Barotrauma.Abilities { } - protected override void ApplyEffect(object abilityData) + protected override void ApplyEffect(AbilityObject abilityObject) { - if (abilityData is AbilityStringCharacter abilityStringCharacter && abilityStringCharacter.Character != Character) + if ((abilityObject as IAbilityString)?.String is string skillIdentifier && (abilityObject as IAbilityCharacter)?.Character is Character character) { - Character.Info?.IncreaseSkillLevel(abilityStringCharacter.String, 1.0f, abilityStringCharacter.Character.Position + Vector2.UnitY * 175.0f); + Character.Info?.IncreaseSkillLevel(skillIdentifier, 1.0f, character.Position + Vector2.UnitY * 175.0f); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityBountyHunter.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityBountyHunter.cs index fde6e081e..417b93972 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityBountyHunter.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityBountyHunter.cs @@ -12,9 +12,9 @@ namespace Barotrauma.Abilities vitalityPercentage = abilityElement.GetAttributeFloat("vitalitypercentage", 0f); } - protected override void ApplyEffect(object abilityData) + protected override void ApplyEffect(AbilityObject abilityObject) { - if (abilityData is Character character) + if ((abilityObject as IAbilityCharacter)?.Character is Character character) { Character.GiveMoney((int)(vitalityPercentage * character.MaxVitality)); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityMultitasker.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityMultitasker.cs index 0017b6d98..b22d35c46 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityMultitasker.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityMultitasker.cs @@ -11,9 +11,9 @@ namespace Barotrauma.Abilities { } - protected override void ApplyEffect(object abilityData) + protected override void ApplyEffect(AbilityObject abilityObject) { - if (abilityData is string skillIdentifier) + if ((abilityObject as IAbilityString)?.String is string skillIdentifier) { if (skillIdentifier != lastSkillIdentifier) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityRegenerateLoot.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityRegenerateLoot.cs index cb82641f5..574d9d7b1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityRegenerateLoot.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityRegenerateLoot.cs @@ -8,19 +8,26 @@ 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 - List openedContainers = new List(); + private readonly List openedContainers = new List(); public CharacterAbilityRegenerateLoot(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) { + randomChance = abilityElement.GetAttributeFloat("randomchance", 1f); } - protected override void ApplyEffect(object abilityData) + protected override void ApplyEffect(AbilityObject abilityObject) { - if (abilityData is Item item && !openedContainers.Contains(item)) + 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) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityTaskmaster.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityTaskmaster.cs index d54950e74..2ae4b6256 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityTaskmaster.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityTaskmaster.cs @@ -16,9 +16,9 @@ namespace Barotrauma.Abilities statusEffectsRemove = CharacterAbilityGroup.ParseStatusEffects(CharacterTalent, abilityElement.GetChildElement("statuseffectsremove")); } - protected override void ApplyEffect(object abilityData) + protected override void ApplyEffect(AbilityObject abilityObject) { - if (abilityData is Character targetCharacter) + if ((abilityObject as IAbilityCharacter)?.Character is Character targetCharacter) { if (targetCharacter == Character) { return; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroupEffect.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroupEffect.cs index 184c5e8a9..55629172f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroupEffect.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroupEffect.cs @@ -10,25 +10,25 @@ namespace Barotrauma.Abilities { public CharacterAbilityGroupEffect(CharacterTalent characterTalent, XElement abilityElementGroup) : base(characterTalent, abilityElementGroup) { } - public void CheckAbilityGroup(object abilityData) + public void CheckAbilityGroup(AbilityObject abilityObject) { if (!IsActive) { return; } - if (IsApplicable(abilityData)) + if (IsApplicable(abilityObject)) { foreach (var characterAbility in characterAbilities) { if (characterAbility.IsViable()) { - characterAbility.ApplyAbilityEffect(abilityData); + characterAbility.ApplyAbilityEffect(abilityObject); } } } } - private bool IsApplicable(object abilityData) + private bool IsApplicable(AbilityObject abilityObject) { if (timesTriggered >= maxTriggerCount) { return false; } - return abilityConditions.All(c => c.MatchesCondition(abilityData)); + return abilityConditions.All(c => c.MatchesCondition(abilityObject)); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/CharacterTalent.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/CharacterTalent.cs index e9ac4347a..5d8a1587b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/CharacterTalent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/CharacterTalent.cs @@ -60,13 +60,13 @@ namespace Barotrauma } } - public void CheckTalent(AbilityEffectType abilityEffectType, object abilityData) + public void CheckTalent(AbilityEffectType abilityEffectType, AbilityObject abilityObject) { if (characterAbilityGroupEffectDictionary.TryGetValue(abilityEffectType, out var characterAbilityGroups)) { foreach (var characterAbilityGroup in characterAbilityGroups) { - characterAbilityGroup.CheckAbilityGroup(abilityData); + characterAbilityGroup.CheckAbilityGroup(abilityObject); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/ContentPackage.cs b/Barotrauma/BarotraumaShared/SharedSource/ContentPackage.cs index ed67c56be..dfcd2b32d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/ContentPackage.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/ContentPackage.cs @@ -420,7 +420,9 @@ namespace Barotrauma default: try { - XDocument.Load(file.Path); + using FileStream stream = File.Open(file.Path, System.IO.FileMode.Open, System.IO.FileAccess.Read); + using var reader = XMLExtensions.CreateReader(stream); + XDocument.Load(reader); } catch (Exception e) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Enums.cs b/Barotrauma/BarotraumaShared/SharedSource/Enums.cs index 346ceb31e..be70e66b9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Enums.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Enums.cs @@ -57,6 +57,7 @@ OnGainMissionMoney, OnItemDeconstructed, OnItemDeconstructedMaterial, + OnStopTinkering, AfterSubmarineAttacked, } @@ -89,6 +90,7 @@ // Utility RepairSpeed, DeconstructorSpeedMultiplier, + TinkeringDuration, // Misc ReputationGainMultiplier, MissionMoneyGainMultiplier, @@ -108,6 +110,8 @@ IgnoredByEnemyAI, MoveNormallyWhileDragging, CanTinker, + CanTinkerFabricatorsAndDeconstructors, + TinkeringPowersDevices, GainSkillPastMaximum, RetainExperienceForNewCharacter } 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 c9b03e99c..49b16f9a3 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) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CargoMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CargoMission.cs index dcc9d3279..023e1e860 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) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/ForbiddenWordFilter.cs b/Barotrauma/BarotraumaShared/SharedSource/ForbiddenWordFilter.cs index 593283d23..bcdbde9f6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/ForbiddenWordFilter.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/ForbiddenWordFilter.cs @@ -1,5 +1,5 @@ using System.Collections.Generic; -using System.IO; +using Barotrauma.IO; using System.Linq; namespace Barotrauma @@ -16,7 +16,7 @@ namespace Barotrauma { forbiddenWords = File.ReadAllLines(fileListPath).Select(s => s.ToLowerInvariant()).ToHashSet(); } - catch (IOException e) + catch (System.IO.IOException e) { DebugConsole.ThrowError($"Failed to load the list of forbidden words from {fileListPath}.", e); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs index 4f941e847..b1e2c79e8 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,11 +16,17 @@ 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 AddedMissionCount { get; set; } public int TotalMaxMissionCount => MaxMissionCount + AddedMissionCount; + 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; @@ -29,16 +34,18 @@ namespace Barotrauma public CampaignSettings(IReadMessage inc) { + maxMissionCount = DefaultMaxMissionCount; RadiationEnabled = inc.ReadBoolean(); - MaxMissionCount = inc.ReadInt32(); AddedMissionCount = inc.ReadInt32(); + MaxMissionCount = inc.ReadInt32(); } public CampaignSettings(XElement element) { + maxMissionCount = DefaultMaxMissionCount; RadiationEnabled = element.GetAttributeBool(nameof(RadiationEnabled).ToLowerInvariant(), true); - MaxMissionCount = element.GetAttributeInt(nameof(MaxMissionCount).ToLowerInvariant(), DefaultMaxMissionCount); AddedMissionCount = element.GetAttributeInt(nameof(AddedMissionCount).ToLowerInvariant(), 0); + MaxMissionCount = element.GetAttributeInt(nameof(MaxMissionCount).ToLowerInvariant(), DefaultMaxMissionCount); } public void Serialize(IWriteMessage msg) diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs index 8b6cbeb36..cb791155d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs @@ -659,9 +659,9 @@ namespace Barotrauma public static IEnumerable GetSessionCrewCharacters() { #if SERVER - return GameMain.Server.ConnectedClients.Select(c => c.Character).Where(c => c.Info != null); + return GameMain.Server.ConnectedClients.Select(c => c.Character).Where(c => c?.Info != null); #else - return GameMain.GameSession.CrewManager.GetCharacters().Where(c => c.Info != null); + return GameMain.GameSession.CrewManager.GetCharacters().Where(c => c?.Info != null); #endif } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/CharacterInventory.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/CharacterInventory.cs index 8b65d6f30..a4061293b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/CharacterInventory.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/CharacterInventory.cs @@ -291,11 +291,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/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/GeneticMaterial.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/GeneticMaterial.cs index e9aa55e2b..60b304478 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/GeneticMaterial.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/GeneticMaterial.cs @@ -116,7 +116,11 @@ namespace Barotrauma.Items.Components #if SERVER item.CreateServerEvent(this); #endif - } + } + foreach (Item containedItem in item.ContainedItems) + { + containedItem.GetComponent()?.Equip(character); + } } public override void Update(float deltaTime, Camera cam) @@ -124,12 +128,16 @@ namespace Barotrauma.Items.Components base.Update(deltaTime, cam); if (targetCharacter != null) { + var rootContainer = item.GetRootContainer(); if (!targetCharacter.HasEquippedItem(item) && - (item.Container == null || !targetCharacter.HasEquippedItem(item.Container) || !(item.Container.GetComponent()?.AutoInject ?? false))) + (rootContainer == null || !targetCharacter.HasEquippedItem(rootContainer) || !targetCharacter.Inventory.IsInLimbSlot(rootContainer, InvSlotType.HealthInterface))) { item.ApplyStatusEffects(ActionType.OnSevered, 1.0f, targetCharacter); - var currentEffect = tainted ? selectedTaintedEffect : selectedEffect; - targetCharacter.CharacterHealth.ReduceAffliction(null, currentEffect.Identifier, currentEffect.MaxStrength); + targetCharacter.CharacterHealth.ReduceAffliction(null, selectedEffect.Identifier, selectedEffect.MaxStrength); + if (tainted) + { + targetCharacter.CharacterHealth.ReduceAffliction(null, selectedTaintedEffect.Identifier, selectedTaintedEffect.MaxStrength); + } targetCharacter = null; IsActive = false; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RangedWeapon.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RangedWeapon.cs index 14d626d16..73d89fe0f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RangedWeapon.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RangedWeapon.cs @@ -1,4 +1,5 @@ -using Barotrauma.Networking; +using Barotrauma.Abilities; +using Barotrauma.Networking; using FarseerPhysics; using FarseerPhysics.Collision; using FarseerPhysics.Dynamics; @@ -158,10 +159,14 @@ namespace Barotrauma.Items.Components if (currentChargeTime < MaxChargeTime) { return false; } IsActive = true; - ReloadTimer = reload / (1 + character.GetStatValue(StatTypes.RangedAttackSpeed)); + ReloadTimer = reload / (1 + character?.GetStatValue(StatTypes.RangedAttackSpeed) ?? 0f); currentChargeTime = 0f; - character.CheckTalents(AbilityEffectType.OnUseRangedWeapon, item); + if (character != null) + { + var abilityItem = new AbilityItem(item); + character.CheckTalents(AbilityEffectType.OnUseRangedWeapon, abilityItem); + } if (item.AiTarget != null) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs index 76093809d..ba479089d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs @@ -6,6 +6,7 @@ using System.Xml.Linq; using Barotrauma.Extensions; using FarseerPhysics; using System.Collections.Immutable; +using Barotrauma.Abilities; namespace Barotrauma.Items.Components { @@ -114,6 +115,13 @@ 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 { @@ -414,7 +422,8 @@ namespace Barotrauma.Items.Components } } } - character.CheckTalents(AbilityEffectType.OnOpenItemContainer, item); + var abilityItem = new AbilityItem(item); + character.CheckTalents(AbilityEffectType.OnOpenItemContainer, abilityItem); return base.Select(character); } @@ -588,6 +597,7 @@ 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) { @@ -616,7 +626,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 3fe8d3b74..18996185f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Deconstructor.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Deconstructor.cs @@ -106,7 +106,7 @@ namespace Barotrauma.Items.Components 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.HasTag(r) || it.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()); } @@ -148,6 +148,12 @@ namespace Barotrauma.Items.Components // 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; @@ -169,8 +175,6 @@ namespace Barotrauma.Items.Components commonness.RemoveAt(removeIndex); } - user.CheckTalents(AbilityEffectType.OnItemDeconstructed, targetItem); - foreach (DeconstructItem deconstructProduct in products) { CreateDeconstructProduct(deconstructProduct, inputItems); @@ -225,10 +229,15 @@ namespace Barotrauma.Items.Components } } } - var itemsCreated = new AbilityValue(1f); - user.CheckTalents(AbilityEffectType.OnItemDeconstructedMaterial, (targetItem.Prefab, itemsCreated)); - int amount = (int)itemsCreated.Value; + int amount = 1; + + if (user != null && !user.Removed) + { + var itemsCreated = new AbilityValueItem(1f, targetItem.Prefab); + user.CheckTalents(AbilityEffectType.OnItemDeconstructedMaterial, itemsCreated); + amount = (int)itemsCreated.Value; + } for (int i = 0; i < amount; i++) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Engine.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Engine.cs index d766f09fa..e5849b584 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Engine.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Engine.cs @@ -113,37 +113,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)); } - - float voltageFactor = MinVoltage <= 0.0f ? 1.0f : Math.Min(Voltage, 1.0f); - Vector2 currForce = new Vector2(force * maxForce * forceMultiplier * voltageFactor, 0.0f); - - if (item.GetComponent()?.IsTinkering ?? false) + currForce *= maxForce * forceMultiplier; + if (item.GetComponent() is Repairable repairable && repairable.IsTinkering) { currForce *= 2.5f; } //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); @@ -153,14 +151,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 1ea32a0f3..d47c21d23 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs @@ -327,6 +327,7 @@ namespace Barotrauma.Items.Components int amountFittingContainer = outputContainer.Inventory.HowManyCanBePut(fabricatedItem.TargetItem, fabricatedItem.OutCondition * fabricatedItem.TargetItem.Health); var fabricationValueItem = new AbilityValueItem(fabricatedItem.Amount, fabricatedItem.TargetItem); + if (user != null) { foreach (Character character in Character.CharacterList.Where(c => c.TeamID == user.TeamID)) @@ -369,10 +370,11 @@ namespace Barotrauma.Items.Components 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, - addedSkill + addedSkillValue.Value, + addedSkill, user.Position + Vector2.UnitY * 150.0f); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Pump.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Pump.cs index 4231489a5..7b00dd94e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Pump.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Pump.cs @@ -106,7 +106,7 @@ namespace Barotrauma.Items.Components currFlow = flowPercentage / 100.0f * maxFlow * powerFactor; - if (item.GetComponent()?.IsTinkering ?? false) + if (item.GetComponent() is Repairable repairable && repairable.IsTinkering) { currFlow *= 2.5f; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Reactor.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Reactor.cs index e8866afaf..bac4219d8 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); + } + } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs index 37f5833fc..65a531190 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs @@ -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,7 +100,7 @@ namespace Barotrauma.Items.Components RecreateGUI(); } #endif - } + } } public bool IsTinkering { get; private set; } = false; @@ -113,6 +113,8 @@ namespace Barotrauma.Items.Components public Character CurrentFixer { get; private set; } private Item currentRepairItem; + private float tinkeringDuration; + public enum FixActions : int { None = 0, @@ -135,13 +137,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) @@ -197,7 +199,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) @@ -227,6 +229,18 @@ namespace Barotrauma.Items.Components CurrentFixer = character; currentRepairItem = bestRepairItem; CurrentFixerAction = action; + if (action == FixActions.Tinker) + { + 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; Item GetBestRepairItem(Character character) @@ -254,13 +268,17 @@ namespace Barotrauma.Items.Components 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; } @@ -290,6 +308,8 @@ namespace Barotrauma.Items.Components UpdateProjSpecific(deltaTime); IsTinkering = false; + item.SendSignal($"{(int) item.ConditionPercentage}", "condition_out"); + if (CurrentFixer == null) { if (deteriorateAlwaysResetTimer > 0.0f) @@ -339,8 +359,9 @@ namespace Barotrauma.Items.Components if (currentFixerAction == FixActions.Tinker) { + tinkeringDuration -= deltaTime; // not great to interject it here, should be less reliant on returning - if (!CanTinker(CurrentFixer)) + if (!CanTinker(CurrentFixer) || tinkeringDuration <= 0f) { StopRepairing(CurrentFixer); } @@ -396,7 +417,7 @@ namespace Barotrauma.Items.Components CurrentFixer.CheckTalents(AbilityEffectType.OnRepairComplete); } deteriorationTimer = Rand.Range(MinDeteriorationDelay, MaxDeteriorationDelay); - wasBroken = false; + wasBroken = false; StopRepairing(CurrentFixer); } } @@ -439,9 +460,27 @@ namespace Barotrauma.Items.Components } } - private bool CanTinker(Character character) + 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.GetComponent() != null) { 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; } @@ -469,7 +508,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) @@ -530,7 +569,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/Signal/LightComponent.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/LightComponent.cs index a836fab59..f6b290bc3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/LightComponent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/LightComponent.cs @@ -233,6 +233,8 @@ namespace Barotrauma.Items.Components } UpdateOnActiveEffects(deltaTime); + if (powerIn == null && powerConsumption > 0.0f) { Voltage -= deltaTime; } + #if CLIENT Light.ParentSub = item.Submarine; #endif @@ -293,8 +295,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/Turret.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs index ecb5d1cf4..fbf1e9961 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs @@ -742,10 +742,10 @@ namespace Barotrauma.Items.Components Projectile projectileComponent = projectile.GetComponent(); if (projectileComponent != null) { - projectileComponent.Attacker = user; + projectileComponent.Attacker = projectileComponent.User = user; if (isTinkering) { - projectileComponent.Attack.DamageMultiplier *= 1.25f; + projectileComponent.Attack.DamageMultiplier = 1.25f; } projectileComponent.Use(); projectile.GetComponent()?.Attach(item, projectile); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs index f422df2e8..ccb06ad4a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs @@ -258,6 +258,8 @@ namespace Barotrauma get { return capacity; } } + public bool AllowSwappingContainedItems = true; + public Inventory(Entity owner, int capacity, int slotsPerRow = 5) { this.capacity = capacity; @@ -623,6 +625,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; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs index 14fb5af13..6e6cfb9a0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs @@ -230,8 +230,7 @@ namespace Barotrauma public bool IsInteractable(Character character) { if (character != null && character.IsOnPlayerTeam) - { - + { return IsPlayerTeamInteractable; } else diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/RelatedItem.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/RelatedItem.cs index 905b91ff3..2efb1e919 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/RelatedItem.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/RelatedItem.cs @@ -41,6 +41,11 @@ namespace Barotrauma 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); } @@ -147,7 +152,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; @@ -271,6 +278,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/Hull.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs index d99fa611d..ba3774838 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs @@ -471,9 +471,8 @@ namespace Barotrauma { aiTarget = new AITarget(this) { - MinSightRange = 2000, + MinSightRange = 1000, MaxSightRange = 5000, - MaxSoundRange = 5000, SoundRange = 0 }; } @@ -787,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; } @@ -1244,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() { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs index ad5279bc9..d2e77976e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs @@ -415,14 +415,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(); @@ -430,10 +429,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); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs index f099563c6..d142fadbc 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs @@ -8,6 +8,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Xml.Linq; +using Barotrauma.Abilities; #if CLIENT using Microsoft.Xna.Framework.Graphics; #endif @@ -437,8 +438,8 @@ namespace Barotrauma { aiTarget = new AITarget(this) { - MinSightRange = 2000, - MaxSightRange = 5000, + MinSightRange = 1000, + MaxSightRange = 4000, MaxSoundRange = 0 }; } @@ -956,11 +957,12 @@ namespace Barotrauma } #endif - if (Submarine != null && damageAmount > 0) + if (Submarine != null && damageAmount > 0 && attacker != null) { + var abilityAttackerSubmarine = new AbilityCharacterSubmarine(attacker, Submarine); foreach (Character character in Character.CharacterList) { - character.CheckTalents(AbilityEffectType.AfterSubmarineAttacked, Submarine); + character.CheckTalents(AbilityEffectType.AfterSubmarineAttacked, abilityAttackerSubmarine); } } @@ -1462,7 +1464,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/SubmarineInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineInfo.cs index 8e718636a..e97b6fa06 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineInfo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineInfo.cs @@ -96,6 +96,9 @@ namespace Barotrauma public OutpostModuleInfo OutpostModuleInfo { get; set; } public bool IsOutpost => Type == SubmarineType.Outpost || Type == SubmarineType.OutpostModule; + + //TODO: replace when the ruin branch is merged + public bool IsRuin => false; public bool IsWreck => Type == SubmarineType.Wreck; public bool IsBeacon => Type == SubmarineType.BeaconStation; public bool IsPlayer => Type == SubmarineType.Player; @@ -743,7 +746,10 @@ namespace Barotrauma try { stream.Position = 0; - doc = XDocument.Load(stream); //ToolBox.TryLoadXml(file); + using (var reader = XMLExtensions.CreateReader(stream)) + { + doc = XDocument.Load(reader); + } stream.Close(); stream.Dispose(); } @@ -760,9 +766,10 @@ namespace Barotrauma try { ToolBox.IsProperFilenameCase(file); - doc = XDocument.Load(file, LoadOptions.SetBaseUri); + using var stream = File.Open(file, System.IO.FileMode.Open, System.IO.FileAccess.Read); + using var reader = XMLExtensions.CreateReader(stream); + doc = XDocument.Load(reader); } - catch (Exception e) { exception = e; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/ServerSettings.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/ServerSettings.cs index c68215f53..4d335dc73 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/ServerSettings.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/ServerSettings.cs @@ -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/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/XMLExtensions.cs b/Barotrauma/BarotraumaShared/SharedSource/Serialization/XMLExtensions.cs index 00c4f0517..0e594bbe7 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Serialization/XMLExtensions.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Serialization/XMLExtensions.cs @@ -1,5 +1,4 @@ using System; -using Barotrauma.IO; using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -7,20 +6,54 @@ using System.Text; using System.Xml; using System.Xml.Linq; using Microsoft.Xna.Framework; +using File = Barotrauma.IO.File; +using FileStream = Barotrauma.IO.FileStream; namespace Barotrauma { public static class XMLExtensions { - public static string ParseContentPathFromUri(this XObject element) => Path.GetRelativePath(Environment.CurrentDirectory, element.BaseUri); + public static string ParseContentPathFromUri(this XObject element) => System.IO.Path.GetRelativePath(Environment.CurrentDirectory, element.BaseUri); + public static readonly XmlReaderSettings ReaderSettings = new XmlReaderSettings + { + DtdProcessing = DtdProcessing.Prohibit, + XmlResolver = null + }; + + public static XmlReader CreateReader(System.IO.Stream stream) + => XmlReader.Create(stream, ReaderSettings); + + public static XDocument TryLoadXml(System.IO.Stream stream) + { + XDocument doc; + try + { + using XmlReader reader = CreateReader(stream); + doc = XDocument.Load(reader); + } + catch (Exception e) + { + DebugConsole.ThrowError($"Couldn't load xml document from stream!", e); + return null; + } + if (doc?.Root == null) + { + DebugConsole.ThrowError("XML could not be loaded from stream: Document or the root element is invalid!"); + return null; + } + return doc; + } + public static XDocument TryLoadXml(string filePath) { XDocument doc; try { ToolBox.IsProperFilenameCase(filePath); - doc = XDocument.Load(filePath, LoadOptions.SetBaseUri); + using FileStream stream = File.Open(filePath, System.IO.FileMode.Open, System.IO.FileAccess.Read); + using XmlReader reader = CreateReader(stream); + doc = XDocument.Load(reader); } catch (Exception e) { @@ -45,7 +78,9 @@ namespace Barotrauma { try { - doc = XDocument.Load(filePath, LoadOptions.SetBaseUri); + using FileStream stream = File.Open(filePath, System.IO.FileMode.Open, System.IO.FileAccess.Read); + using XmlReader reader = CreateReader(stream); + doc = XDocument.Load(reader); } catch { diff --git a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs index d9ae32ee1..d5a517d5a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs @@ -137,6 +137,7 @@ 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; @@ -173,6 +174,7 @@ namespace Barotrauma } } + SpawnIfInventoryFull = element.GetAttributeBool("spawnifinventoryfull", false); Speed = element.GetAttributeFloat("speed", 0.0f); Rotation = element.GetAttributeFloat("rotation", 0.0f); @@ -312,6 +314,8 @@ namespace Barotrauma private set; } + private bool modifyAfflictionsByMaxVitality; + public IEnumerable SpawnCharacters { get { return spawnCharacters; } @@ -373,6 +377,7 @@ namespace Barotrauma 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); @@ -695,7 +700,7 @@ namespace Barotrauma { 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))) + if (attackResult.Afflictions.None(a => requiredAfflictions.Any(a2 => a.Strength >= a2.strength && (a.Identifier == a2.affliction || a.Prefab.AfflictionType == a2.affliction)))) { return false; } @@ -1151,7 +1156,7 @@ namespace Barotrauma if (target is Character character) { if (character.Removed) { continue; } - newAffliction = GetMultipliedAffliction(affliction, entity, character, deltaTime); + newAffliction = GetMultipliedAffliction(affliction, entity, character, deltaTime, modifyAfflictionsByMaxVitality); character.LastDamageSource = entity; foreach (Limb limb in character.AnimController.Limbs) { @@ -1169,7 +1174,7 @@ namespace Barotrauma { if (limb.IsSevered) { continue; } if (limb.character.Removed || limb.Removed) { continue; } - newAffliction = GetMultipliedAffliction(affliction, entity, limb.character, deltaTime); + 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); @@ -1417,9 +1422,9 @@ namespace Barotrauma { inventory = item?.GetComponent()?.Inventory; } - if (inventory != null && inventory.CanBePut(chosenItemSpawnInfo.ItemPrefab)) + if (inventory != null && (inventory.CanBePut(chosenItemSpawnInfo.ItemPrefab) || chosenItemSpawnInfo.SpawnIfInventoryFull)) { - Entity.Spawner.AddToSpawnQueue(chosenItemSpawnInfo.ItemPrefab, inventory, spawnIfInventoryFull: false); + Entity.Spawner.AddToSpawnQueue(chosenItemSpawnInfo.ItemPrefab, inventory, spawnIfInventoryFull: chosenItemSpawnInfo.SpawnIfInventoryFull); } } break; @@ -1439,9 +1444,9 @@ namespace Barotrauma foreach (Item item in thisInventory.AllItems) { Inventory containedInventory = item.GetComponent()?.Inventory; - if (containedInventory != null && containedInventory.CanBePut(chosenItemSpawnInfo.ItemPrefab)) + if (containedInventory != null && (containedInventory.CanBePut(chosenItemSpawnInfo.ItemPrefab) || chosenItemSpawnInfo.SpawnIfInventoryFull)) { - Entity.Spawner.AddToSpawnQueue(chosenItemSpawnInfo.ItemPrefab, containedInventory, spawnIfInventoryFull: false); + Entity.Spawner.AddToSpawnQueue(chosenItemSpawnInfo.ItemPrefab, containedInventory, spawnIfInventoryFull: chosenItemSpawnInfo.SpawnIfInventoryFull); } break; } @@ -1543,14 +1548,14 @@ namespace Barotrauma if (target is Character character) { if (character.Removed) { continue; } - newAffliction = element.Parent.GetMultipliedAffliction(affliction, element.Entity, character, deltaTime); + 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; } - newAffliction = element.Parent.GetMultipliedAffliction(affliction, element.Entity, limb.character, deltaTime); + 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); } @@ -1614,9 +1619,14 @@ namespace Barotrauma return multiplier; } - private Affliction GetMultipliedAffliction(Affliction affliction, Entity entity, Character targetCharacter, float deltaTime) + 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); diff --git a/Barotrauma/BarotraumaShared/SharedSource/SteamAchievementManager.cs b/Barotrauma/BarotraumaShared/SharedSource/SteamAchievementManager.cs index dd975c774..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)); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Utils/SafeIO.cs b/Barotrauma/BarotraumaShared/SharedSource/Utils/SafeIO.cs index ac830c575..a2b4ba5cd 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Utils/SafeIO.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Utils/SafeIO.cs @@ -1,21 +1,41 @@ using System; using System.Collections.Generic; +using System.Linq; namespace Barotrauma.IO { static class Validation { - static readonly string[] unwritableDirs = new string[] { "Content", "Data/ContentPackages" }; + private static readonly string[] unwritableDirs = new string[] { "Content", "Data/ContentPackages" }; + private static readonly string[] unwritableExtensions = new string[] + { + ".pdb", ".com", ".scr", ".dylib", ".so", ".a", ".app", //executables and libraries (.exe and .dll handled separately in CanWrite) + ".bat", ".sh", //shell scripts + ".json" //deps.json + }; /// /// When set to true, the game is allowed to modify the vanilla content in debug builds. Has no effect in non-debug builds. /// public static bool SkipValidationInDebugBuilds; - public static bool CanWrite(string path) + public static bool CanWrite(string path, bool isDirectory) { path = System.IO.Path.GetFullPath(path).CleanUpPath(); + 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) { string dir = System.IO.Path.GetFullPath(unwritableDir).CleanUpPath(); @@ -38,9 +58,9 @@ namespace Barotrauma.IO { public static void SaveSafe(this System.Xml.Linq.XDocument doc, string path) { - if (!Validation.CanWrite(path)) + if (!Validation.CanWrite(path, false)) { - DebugConsole.ThrowError($"Cannot save XML document to \"{path}\": modifying the files in the folder is not allowed."); + DebugConsole.ThrowError($"Cannot save XML document to \"{path}\": modifying the files in this folder/with this extension is not allowed."); return; } doc.Save(path); @@ -48,9 +68,9 @@ namespace Barotrauma.IO public static void SaveSafe(this System.Xml.Linq.XElement element, string path) { - if (!Validation.CanWrite(path)) + if (!Validation.CanWrite(path, false)) { - DebugConsole.ThrowError($"Cannot save XML element to \"{path}\": modifying the files in the folder is not allowed."); + DebugConsole.ThrowError($"Cannot save XML element to \"{path}\": modifying the files in this folder/with this extension is not allowed."); return; } element.Save(path); @@ -73,9 +93,9 @@ namespace Barotrauma.IO public XmlWriter(string path, System.Xml.XmlWriterSettings settings) { - if (!Validation.CanWrite(path)) + if (!Validation.CanWrite(path, false)) { - DebugConsole.ThrowError($"Cannot write XML document to \"{path}\": modifying the files in the folder is not allowed."); + DebugConsole.ThrowError($"Cannot write XML document to \"{path}\": modifying the files in this folder/with this extension is not allowed."); Writer = null; return; } @@ -228,9 +248,9 @@ namespace Barotrauma.IO public static System.IO.DirectoryInfo CreateDirectory(string path) { - if (!Validation.CanWrite(path)) + if (!Validation.CanWrite(path, true)) { - DebugConsole.ThrowError($"Cannot create directory \"{path}\": modifying the contents of the folder is not allowed."); + DebugConsole.ThrowError($"Cannot create directory \"{path}\": modifying the contents of this folder/using this extension is not allowed."); return null; } return System.IO.Directory.CreateDirectory(path); @@ -238,9 +258,9 @@ namespace Barotrauma.IO public static void Delete(string path, bool recursive=true) { - if (!Validation.CanWrite(path)) + if (!Validation.CanWrite(path, true)) { - DebugConsole.ThrowError($"Cannot delete directory \"{path}\": modifying the contents of the folder is not allowed."); + DebugConsole.ThrowError($"Cannot delete directory \"{path}\": modifying the contents of this folder/using this extension is not allowed."); return; } //TODO: validate recursion? @@ -257,9 +277,9 @@ namespace Barotrauma.IO public static void Copy(string src, string dest, bool overwrite=false) { - if (!Validation.CanWrite(dest)) + if (!Validation.CanWrite(dest, false)) { - DebugConsole.ThrowError($"Cannot copy \"{src}\" to \"{dest}\": modifying the contents of the folder is not allowed."); + DebugConsole.ThrowError($"Cannot copy \"{src}\" to \"{dest}\": modifying the contents of this folder/using this extension is not allowed."); return; } System.IO.File.Copy(src, dest, overwrite); @@ -267,12 +287,12 @@ namespace Barotrauma.IO public static void Move(string src, string dest) { - if (!Validation.CanWrite(src)) + if (!Validation.CanWrite(src, false)) { DebugConsole.ThrowError($"Cannot move \"{src}\" to \"{dest}\": modifying the contents of the source folder is not allowed."); return; } - if (!Validation.CanWrite(dest)) + if (!Validation.CanWrite(dest, false)) { DebugConsole.ThrowError($"Cannot move \"{src}\" to \"{dest}\": modifying the contents of the destination folder is not allowed"); return; @@ -282,9 +302,9 @@ namespace Barotrauma.IO public static void Delete(string path) { - if (!Validation.CanWrite(path)) + if (!Validation.CanWrite(path, false)) { - DebugConsole.ThrowError($"Cannot delete file \"{path}\": modifying the contents of the folder is not allowed."); + DebugConsole.ThrowError($"Cannot delete file \"{path}\": modifying the contents of this folder/using this extension is not allowed."); return; } System.IO.File.Delete(path); @@ -304,15 +324,15 @@ namespace Barotrauma.IO case System.IO.FileMode.OpenOrCreate: case System.IO.FileMode.Append: case System.IO.FileMode.Truncate: - if (!Validation.CanWrite(path)) + if (!Validation.CanWrite(path, false)) { - DebugConsole.ThrowError($"Cannot open \"{path}\" in {mode} mode: modifying the contents of the folder is not allowed."); + DebugConsole.ThrowError($"Cannot open \"{path}\" in {mode} mode: modifying the contents of this folder/using this extension is not allowed."); return null; } break; } return new FileStream(path, System.IO.File.Open(path, mode, - !Validation.CanWrite(path) ? + !Validation.CanWrite(path, false) ? System.IO.FileAccess.Read : access)); } @@ -334,9 +354,9 @@ namespace Barotrauma.IO public static void WriteAllBytes(string path, byte[] contents) { - if (!Validation.CanWrite(path)) + if (!Validation.CanWrite(path, false)) { - DebugConsole.ThrowError($"Cannot write all bytes to \"{path}\": modifying the files in the folder is not allowed."); + DebugConsole.ThrowError($"Cannot write all bytes to \"{path}\": modifying the files in this folder/with this extension is not allowed."); return; } System.IO.File.WriteAllBytes(path, contents); @@ -344,9 +364,9 @@ namespace Barotrauma.IO public static void WriteAllText(string path, string contents, System.Text.Encoding? encoding = null) { - if (!Validation.CanWrite(path)) + if (!Validation.CanWrite(path, false)) { - DebugConsole.ThrowError($"Cannot write all text to \"{path}\": modifying the files in the folder is not allowed."); + DebugConsole.ThrowError($"Cannot write all text to \"{path}\": modifying the files in this folder/with this extension is not allowed."); return; } System.IO.File.WriteAllText(path, contents, encoding ?? System.Text.Encoding.UTF8); @@ -354,9 +374,9 @@ namespace Barotrauma.IO public static void WriteAllLines(string path, IEnumerable contents, System.Text.Encoding? encoding = null) { - if (!Validation.CanWrite(path)) + if (!Validation.CanWrite(path, false)) { - DebugConsole.ThrowError($"Cannot write all lines to \"{path}\": modifying the files in the folder is not allowed."); + DebugConsole.ThrowError($"Cannot write all lines to \"{path}\": modifying the files in this folder/with this extension is not allowed."); return; } System.IO.File.WriteAllLines(path, contents, encoding ?? System.Text.Encoding.UTF8); @@ -396,7 +416,7 @@ namespace Barotrauma.IO { get { - if (!Validation.CanWrite(fileName)) { return false; } + if (!Validation.CanWrite(fileName, false)) { return false; } return innerStream.CanWrite; } } @@ -422,13 +442,13 @@ namespace Barotrauma.IO public override void Write(byte[] buffer, int offset, int count) { - if (Validation.CanWrite(fileName)) + if (Validation.CanWrite(fileName, false)) { innerStream.Write(buffer, offset, count); } else { - DebugConsole.ThrowError($"Cannot write to file \"{fileName}\": modifying the files in the folder is not allowed."); + DebugConsole.ThrowError($"Cannot write to file \"{fileName}\": modifying the files in this folder/with this extension is not allowed."); } } @@ -493,9 +513,9 @@ namespace Barotrauma.IO public void Delete() { - if (!Validation.CanWrite(innerInfo.FullName)) + if (!Validation.CanWrite(innerInfo.FullName, false)) { - DebugConsole.ThrowError($"Cannot delete directory \"{Name}\": modifying the contents of the folder is not allowed."); + DebugConsole.ThrowError($"Cannot delete directory \"{Name}\": modifying the contents of this folder/using this extension is not allowed."); return; } innerInfo.Delete(); @@ -529,9 +549,9 @@ namespace Barotrauma.IO } set { - if (!Validation.CanWrite(innerInfo.FullName)) + if (!Validation.CanWrite(innerInfo.FullName, false)) { - DebugConsole.ThrowError($"Cannot set read-only to {value} for \"{Name}\": modifying the files in the folder is not allowed."); + DebugConsole.ThrowError($"Cannot set read-only to {value} for \"{Name}\": modifying the files in this folder/with this extension is not allowed."); return; } innerInfo.IsReadOnly = value; @@ -540,7 +560,7 @@ namespace Barotrauma.IO public void CopyTo(string dest, bool overwriteExisting = false) { - if (!Validation.CanWrite(dest)) + if (!Validation.CanWrite(dest, false)) { DebugConsole.ThrowError($"Cannot copy \"{Name}\" to \"{dest}\": modifying the contents of the destination folder is not allowed."); return; @@ -550,9 +570,9 @@ namespace Barotrauma.IO public void Delete() { - if (!Validation.CanWrite(innerInfo.FullName)) + if (!Validation.CanWrite(innerInfo.FullName, false)) { - DebugConsole.ThrowError($"Cannot delete file \"{Name}\": modifying the files in the folder is not allowed."); + DebugConsole.ThrowError($"Cannot delete file \"{Name}\": modifying the files in this folder/with this extension is not allowed."); return; } innerInfo.Delete(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Utils/SaveUtil.cs b/Barotrauma/BarotraumaShared/SharedSource/Utils/SaveUtil.cs index 7f881cad9..a797bde55 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Utils/SaveUtil.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Utils/SaveUtil.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using Barotrauma.IO; using System.IO.Compression; @@ -352,8 +353,10 @@ namespace Barotrauma } } - public static bool DecompressFile(string sDir, GZipStream zipStream, ProgressDelegate progress) + private static bool DecompressFile(bool writeFile, string sDir, GZipStream zipStream, ProgressDelegate progress, out string fileName) { + fileName = null; + //Decompress file name byte[] bytes = new byte[sizeof(int)]; int Readed = zipStream.Read(bytes, 0, sizeof(int)); @@ -375,6 +378,8 @@ namespace Barotrauma sb.Append(c); } string sFileName = sb.ToString(); + + fileName = sFileName; progress?.Invoke(sFileName); //Decompress file content @@ -387,6 +392,17 @@ namespace Barotrauma string sFilePath = Path.Combine(sDir, sFileName); string sFinalDir = Path.GetDirectoryName(sFilePath); + + string sDirFull = (string.IsNullOrEmpty(sDir) ? Directory.GetCurrentDirectory() : Path.GetFullPath(sDir)).CleanUpPathCrossPlatform(correctFilenameCase: false); + string sFinalDirFull = (string.IsNullOrEmpty(sFinalDir) ? Directory.GetCurrentDirectory() : Path.GetFullPath(sFinalDir)).CleanUpPathCrossPlatform(correctFilenameCase: false); + + if (!sFinalDirFull.StartsWith(sDirFull, StringComparison.OrdinalIgnoreCase)) + { + throw new InvalidOperationException( + $"Error extracting \"{sFileName}\": cannot be extracted to parent directory"); + } + + if (!writeFile) { return true; } if (!Directory.Exists(sFinalDir)) Directory.CreateDirectory(sFinalDir); @@ -421,7 +437,7 @@ namespace Barotrauma { using (FileStream inFile = File.Open(sCompressedFile, System.IO.FileMode.Open, System.IO.FileAccess.Read)) using (GZipStream zipStream = new GZipStream(inFile, CompressionMode.Decompress, true)) - while (DecompressFile(sDir, zipStream, progress)) { }; + while (DecompressFile(true, sDir, zipStream, progress, out _)) { }; break; } @@ -434,6 +450,35 @@ namespace Barotrauma } } + public static IEnumerable EnumerateContainedFiles(string sCompressedFile) + { + int maxRetries = 4; + HashSet paths = new HashSet(); + for (int i = 0; i <= maxRetries; i++) + { + try + { + using FileStream inFile = File.Open(sCompressedFile, System.IO.FileMode.Open, System.IO.FileAccess.Read); + using GZipStream zipStream = new GZipStream(inFile, CompressionMode.Decompress, true); + while (DecompressFile(false, "", zipStream, null, out string fileName)) + { + paths.Add(fileName); + } + } + catch (System.IO.IOException e) + { + if (i >= maxRetries || !File.Exists(sCompressedFile)) { throw; } + + DebugConsole.NewMessage( + $"Failed to decompress file \"{sCompressedFile}\" for enumeration {{{e.Message}}}, retrying in 250 ms...", + Color.Red); + Thread.Sleep(250); + } + } + + return paths; + } + public static void CopyFolder(string sourceDirName, string destDirName, bool copySubDirs, bool overwriteExisting = false) { // Get the subdirectories for the specified directory. diff --git a/Barotrauma/BarotraumaShared/changelog.txt b/Barotrauma/BarotraumaShared/changelog.txt index 48c9afcb0..babb03713 100644 --- a/Barotrauma/BarotraumaShared/changelog.txt +++ b/Barotrauma/BarotraumaShared/changelog.txt @@ -1,3 +1,50 @@ +--------------------------------------------------------------------------------------------------------- +v0.1500.2.0 +--------------------------------------------------------------------------------------------------------- + +Additions and changes: +- A bunch more talents and talent-related items. +- 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. +- Characters can now only have a single "Movement" category order at a time. +- Added condition_out pin to various items. +- Adjusted the color of the status terminal to be more greener. +- Added door and hatch position indicators to status monitor. +- Made alerts and job icons in status monitor be consistent in size. +- Combined genetic materials show the descriptions of both of the materials in the tooltip. +- The talent menu is disabled when not controlling a human character or playing the campaign. +- Disabled toggling the sonar mode by pressing the Run key. +- Changed default creature attack key to F because R conflicts with the radio keybind. +- 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). + +Fixes: +- 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 insert them into an occupied slot in a container that can't hold the item. +- Fixed genetic materia's effects not always disappearing when unequipping the material (unstable only). +- 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 inability to adjust max mission count in a dedicated server. +- Fixed ID overlaps when loading outpost modules that contain items which spawn with some contained item (e.g. alien battery cells or magazines). +- 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 cargo missions sometimes only rewarding the players for 1 crate even when transporting more. +- Fixed heavy scooter working even if the battery is not in the correct slot, added an icon to the battery slot (unstable only). +- 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 with spineling/raptor genes turning into husks when they die (unstable only). +- Fixed any amount of damage triggering mollusc gene's vigor buff (unstable only), making it easy to max the vigor with tools that do damage every frame (e.g. plasma cutter). +- Fixed genetic materials refining to 100% when combined with stabilozine (unstable only). +- Fixed gene splicer slot's tooltip staying visible when you close the health interface while your cursor is on the slot (unstable only). +- Fixed gene splicer slot sometimes being misaligned when opening the health interface for the 1st time (unstable only). +- Fixed concussion's description being used as its name (unstable only). +- Fixed ability to recursively stack bandoliers, toolbelts and heavy scooters. + +Modding: +- Option to make afflictions draw a full-screen overlay when active. + --------------------------------------------------------------------------------------------------------- v0.1500.1.0 --------------------------------------------------------------------------------------------------------- @@ -55,14 +102,13 @@ Additions and changes: - 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. +- 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. - -Overhauled status monitor: +Overhauled status monitor: - Improved visuals. - Indicates the locations of the crew's ID cards. - Indicates the locations of alerts. @@ -88,6 +134,12 @@ Modding: - 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 +--------------------------------------------------------------------------------------------------------- + +- Fixed an exploit that allowed modified servers to send malicious campaign save files to clients. + --------------------------------------------------------------------------------------------------------- v0.14.9.0 --------------------------------------------------------------------------------------------------------- @@ -159,7 +211,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. @@ -271,7 +323,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. @@ -439,7 +491,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. @@ -824,7 +876,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. @@ -957,7 +1009,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. @@ -1609,7 +1661,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. @@ -1762,7 +1814,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. @@ -1797,7 +1849,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. @@ -2013,7 +2065,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. @@ -2173,7 +2225,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. @@ -2279,7 +2331,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. @@ -2319,7 +2371,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. @@ -2435,7 +2487,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). @@ -2487,9 +2539,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? @@ -2701,8 +2753,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. @@ -2727,7 +2779,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). @@ -2749,7 +2801,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. @@ -2757,18 +2809,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. @@ -2779,12 +2831,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. @@ -2792,12 +2844,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. @@ -2851,10 +2903,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. @@ -2866,17 +2918,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. @@ -2884,13 +2936,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. @@ -2900,21 +2952,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. --------------------------------------------------------------------------------------------------------- @@ -2923,7 +2975,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. @@ -2935,9 +2987,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). --------------------------------------------------------------------------------------------------------- @@ -2953,20 +3005,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. @@ -2986,15 +3038,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 @@ -3015,10 +3067,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. @@ -3030,49 +3082,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. @@ -3080,7 +3132,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. @@ -3095,26 +3147,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. @@ -3134,7 +3186,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. @@ -3142,7 +3194,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. @@ -3162,23 +3214,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. @@ -3186,7 +3238,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). @@ -3197,19 +3249,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. @@ -3219,8 +3271,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. @@ -3229,7 +3281,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. --------------------------------------------------------------------------------------------------------- @@ -3240,26 +3292,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. @@ -3270,11 +3322,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. @@ -3282,7 +3334,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. @@ -3290,20 +3342,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. @@ -3334,28 +3386,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). @@ -3363,24 +3415,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. @@ -3396,21 +3448,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. @@ -3450,7 +3502,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. @@ -3485,9 +3537,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. @@ -3521,20 +3573,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. @@ -3587,7 +3639,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. @@ -3595,7 +3647,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. @@ -3645,27 +3697,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. @@ -3678,11 +3730,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. @@ -3692,31 +3744,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. @@ -3726,12 +3778,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. @@ -3744,14 +3796,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"...) @@ -3765,24 +3817,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. @@ -3791,8 +3843,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 @@ -3800,7 +3852,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. @@ -3811,16 +3863,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. @@ -3838,7 +3890,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. @@ -3848,11 +3900,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). @@ -3864,12 +3916,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. --------------------------------------------------------------------------------------------------------- @@ -3889,8 +3941,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. @@ -3905,15 +3957,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 @@ -3924,29 +3976,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. @@ -3964,7 +4016,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. @@ -3992,13 +4044,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). @@ -4012,21 +4064,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: @@ -4038,20 +4090,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: @@ -4061,17 +4113,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%. @@ -4100,17 +4152,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. @@ -4118,21 +4170,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. @@ -4140,7 +4192,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. @@ -4162,7 +4214,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 @@ -4178,39 +4230,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. @@ -4233,9 +4285,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 @@ -4254,18 +4306,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. @@ -4278,10 +4330,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. @@ -4295,56 +4347,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. @@ -4355,13 +4407,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 @@ -4382,22 +4434,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. @@ -4407,11 +4459,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. @@ -4424,14 +4476,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. --------------------------------------------------------------------------------------------------------- @@ -4468,7 +4520,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. @@ -4489,7 +4541,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) @@ -4498,7 +4550,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 @@ -4518,12 +4570,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. @@ -4547,7 +4599,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. --------------------------------------------------------------------------------------------------------- @@ -4556,17 +4608,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. @@ -4578,14 +4630,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. @@ -4608,7 +4660,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. @@ -4618,20 +4670,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). --------------------------------------------------------------------------------------------------------- @@ -4663,13 +4715,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. @@ -4688,9 +4740,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. @@ -4735,7 +4787,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. @@ -4744,29 +4796,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 @@ -4777,24 +4829,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: @@ -4823,11 +4875,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. @@ -4850,7 +4902,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. @@ -4890,7 +4942,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, @@ -4903,7 +4955,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: @@ -4914,7 +4966,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. @@ -4922,7 +4974,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. @@ -4935,7 +4987,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. @@ -4951,18 +5003,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. --------------------------------------------------------------------------------------------------------- @@ -4971,7 +5023,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. @@ -4985,7 +5037,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. @@ -5016,13 +5068,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. @@ -5034,26 +5086,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. @@ -5070,7 +5122,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. @@ -5083,8 +5135,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. @@ -5117,7 +5169,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 @@ -5127,7 +5179,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 @@ -5145,7 +5197,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 --------------------------------------------------------------------------------------------------------- @@ -5190,7 +5242,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) @@ -5210,13 +5262,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 @@ -5225,7 +5277,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 @@ -5243,19 +5295,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 @@ -5264,12 +5316,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: @@ -5277,18 +5329,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 @@ -5308,7 +5360,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 @@ -5346,7 +5398,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 @@ -5358,7 +5410,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 @@ -5370,13 +5422,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 @@ -5398,7 +5450,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 @@ -5419,7 +5471,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 @@ -5471,7 +5523,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) @@ -5515,12 +5567,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'' @@ -5674,7 +5726,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 @@ -5685,7 +5737,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 @@ -5701,7 +5753,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 @@ -5757,16 +5809,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 @@ -5782,7 +5834,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 @@ -5896,8 +5948,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 @@ -5944,7 +5996,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 @@ -5980,7 +6032,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! @@ -5990,9 +6042,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 --------------------------------------------------------------------------------------------------------- @@ -6016,7 +6068,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 @@ -6053,7 +6105,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: @@ -6061,7 +6113,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 @@ -6099,7 +6151,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 @@ -6120,7 +6172,7 @@ Items: Misc: - fixed placing ladders and labels in sub editor - fixed a couple of game-crashing bugs in submarine saving - + --------------------------------------------------------------------------------------------------------- v0.2.5 --------------------------------------------------------------------------------------------------------- @@ -6129,7 +6181,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 @@ -6141,7 +6193,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 @@ -6164,7 +6216,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 @@ -6231,7 +6283,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: @@ -6261,7 +6313,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 --------------------------------------------------------------------------------------------------------- @@ -6310,16 +6362,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 @@ -6338,7 +6390,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 --------------------------------------------------------------------------------------------------------- @@ -6356,12 +6408,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 --------------------------------------------------------------------------------------------------------- @@ -6376,7 +6428,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 --------------------------------------------------------------------------------------------------------- @@ -6385,7 +6437,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/Libraries/Farseer Physics Engine 3.5/Dynamics/World.cs b/Libraries/Farseer Physics Engine 3.5/Dynamics/World.cs index 53ce244d8..8b1ddfa8d 100644 --- a/Libraries/Farseer Physics Engine 3.5/Dynamics/World.cs +++ b/Libraries/Farseer Physics Engine 3.5/Dynamics/World.cs @@ -999,7 +999,7 @@ namespace FarseerPhysics.Dynamics if (body == null) throw new ArgumentNullException("body"); if (body.World != this) - throw new ArgumentException("You are removing a body that is not in the simulation.", "body"); + throw new ArgumentException($"You are removing a body that is not in the simulation (userdata: {body.UserData?.ToString() ?? "null"}).", "body"); #if USE_AWAKE_BODY_SET Debug.Assert(!AwakeBodySet.Contains(body)); From 5a6bbcc79e2391cfc6a91a7f9aafd4a244c9ad45 Mon Sep 17 00:00:00 2001 From: Markus Isberg <3e849f2e5c@pm.me> Date: Fri, 17 Sep 2021 22:47:21 +0900 Subject: [PATCH 04/12] =?UTF-8?q?0.1500.3.0=20(=F0=9F=97=BF=20edition)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Characters/Health/CharacterHealth.cs | 27 +- .../ClientSource/DebugConsole.cs | 42 ++- .../ClientSource/GUI/GUIStyle.cs | 15 + .../ClientSource/GUI/LoadingScreen.cs | 41 +-- .../ClientSource/GUI/TabMenu.cs | 285 ++++++++++++------ .../BarotraumaClient/ClientSource/GameMain.cs | 24 +- .../ClientSource/GameSession/CrewManager.cs | 16 +- .../ClientSource/Items/CharacterInventory.cs | 2 +- .../Items/Components/Holdable/RangedWeapon.cs | 5 +- .../Items/Components/ItemContainer.cs | 2 +- .../Items/Components/Machines/Fabricator.cs | 41 ++- .../Items/Components/Machines/MiniMap.cs | 137 ++++++--- .../Items/Components/Repairable.cs | 30 +- .../ClientSource/Items/Inventory.cs | 7 +- .../ClientSource/Items/Item.cs | 8 +- .../ClientSource/Screens/NetLobbyScreen.cs | 16 +- .../ClientSource/Screens/TestScreen.cs | 35 +-- .../BarotraumaClient/LinuxClient.csproj | 2 +- Barotrauma/BarotraumaClient/MacClient.csproj | 2 +- .../BarotraumaClient/WindowsClient.csproj | 2 +- .../BarotraumaServer/LinuxServer.csproj | 2 +- Barotrauma/BarotraumaServer/MacServer.csproj | 2 +- .../ServerSource/Items/Item.cs | 1 + .../ServerSource/Networking/ServerSettings.cs | 2 +- .../BarotraumaServer/WindowsServer.csproj | 2 +- .../Characters/AI/Wreck/WreckAI.cs | 4 +- .../Characters/Animation/Ragdoll.cs | 1 + .../SharedSource/Characters/Character.cs | 22 +- .../SharedSource/Characters/CharacterInfo.cs | 13 + .../Characters/Health/CharacterHealth.cs | 3 +- .../AbilityConditionIsAiming.cs | 8 +- .../AbilityConditionItem.cs | 10 +- .../AbilityConditionHasVelocity.cs | 20 ++ .../AbilityConditionShipFlooded.cs | 2 +- .../CharacterAbilityApplyStatusEffects.cs | 8 +- ...pplyStatusEffectsToLastOrderedCharacter.cs | 31 ++ .../CharacterAbilityGivePermanentStat.cs | 3 +- .../CharacterAbilityIncreaseSkill.cs | 28 +- .../CharacterAbilityModifyStatToFlooding.cs | 34 +++ .../Abilities/CharacterAbilityModifyValue.cs | 4 +- .../CharacterAbilityByTheBook.cs | 10 +- .../CharacterAbilityEnigmaMachine.cs | 43 +++ .../CharacterAbilityIndustrialRevolution.cs | 30 -- .../CharacterAbilityTaskmaster.cs | 39 --- .../AbilityGroups/CharacterAbilityGroup.cs | 8 +- .../CharacterAbilityGroupEffect.cs | 3 +- .../CharacterAbilityGroupInterval.cs | 3 +- .../Characters/Talents/CharacterTalent.cs | 5 +- .../Characters/Talents/TalentTree.cs | 80 ++++- .../BarotraumaShared/SharedSource/Enums.cs | 39 ++- .../Missions/AbandonedOutpostMission.cs | 2 +- .../Events/Missions/PirateMission.cs | 6 +- .../Events/Missions/SalvageMission.cs | 4 +- .../Extensions/VectorExtensions.cs | 7 + .../Items/Components/GeneticMaterial.cs | 26 +- .../Items/Components/Holdable/Pickable.cs | 8 +- .../Items/Components/Holdable/RepairTool.cs | 20 +- .../Items/Components/ItemContainer.cs | 21 +- .../Components/Machines/Deconstructor.cs | 11 + .../Items/Components/Machines/Fabricator.cs | 32 +- .../Items/Components/Machines/Pump.cs | 2 + .../SharedSource/Items/Components/Quality.cs | 72 +++++ .../Items/Components/RemoteController.cs | 1 + .../Items/Components/Repairable.cs | 16 +- .../SharedSource/Items/Components/Turret.cs | 4 + .../SharedSource/Items/Components/Wearable.cs | 2 +- .../SharedSource/Items/Item.cs | 50 ++- .../SharedSource/Items/ItemInventory.cs | 6 +- .../SharedSource/Items/ItemPrefab.cs | 5 + .../SharedSource/Map/Explosion.cs | 13 +- .../Serialization/XMLExtensions.cs | 17 +- .../StatusEffects/PropertyConditional.cs | 19 ++ .../SharedSource/Utils/SafeIO.cs | 12 +- Barotrauma/BarotraumaShared/changelog.txt | 29 ++ .../BarotraumaShared/serversettings.xml | 2 +- 75 files changed, 1145 insertions(+), 441 deletions(-) create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionHasVelocity.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffectsToLastOrderedCharacter.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyStatToFlooding.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityEnigmaMachine.cs delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityIndustrialRevolution.cs delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityTaskmaster.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Items/Components/Quality.cs diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs index d6443aee0..2e648839a 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs @@ -801,7 +801,11 @@ namespace Barotrauma { var treatmentButton = component.GetChild(); if (!(treatmentButton?.UserData is ItemPrefab itemPrefab)) { continue; } - treatmentButton.Enabled = Character.Controlled.Inventory.AllItems.Any(it => it.prefab == itemPrefab); + treatmentButton.Enabled = Character.Controlled.Inventory.AllItems.Any(it => it.prefab == itemPrefab); + foreach (GUIComponent child in treatmentButton.Children) + { + child.Enabled = treatmentButton.Enabled; + } } } @@ -1155,7 +1159,10 @@ namespace Barotrauma //key = item identifier //float = suitability Dictionary treatmentSuitability = new Dictionary(); - GetSuitableTreatments(treatmentSuitability, normalize: true, limb: selectedLimbIndex == -1 ? null : Character.AnimController.Limbs.Find(l => l.HealthIndex == selectedLimbIndex)); + 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()) { @@ -1260,10 +1267,11 @@ namespace Barotrauma UserData = item }; - var innerFrame = new GUIButton(new RectTransform(Vector2.One, itemSlot.RectTransform, Anchor.Center, Pivot.Center, scaleBasis: ScaleBasis.Smallest), style: "GUIButtonRound") + var innerFrame = new GUIButton(new RectTransform(Vector2.One, itemSlot.RectTransform, Anchor.Center, Pivot.Center, scaleBasis: ScaleBasis.Smallest), style: "SubtreeHeader") { 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; } @@ -1274,6 +1282,17 @@ namespace Barotrauma return true; } }; + + new GUIImage(new RectTransform(Vector2.One, innerFrame.RectTransform, Anchor.Center), style: "TalentBackgroundGlow") + { + CanBeFocused = false, + Color = Color.White * 0.7f, + 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), @@ -1283,7 +1302,7 @@ namespace Barotrauma Color = itemColor * 0.9f, HoverColor = itemColor, SelectedColor = itemColor, - DisabledColor = itemColor * 0.7f + DisabledColor = itemColor * 0.8f }; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs b/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs index d8cb533ed..988125af4 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs @@ -1697,13 +1697,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(); } @@ -1713,6 +1714,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)) { @@ -1729,14 +1731,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(); } @@ -1744,6 +1755,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/GUI/GUIStyle.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIStyle.cs index bb9e36562..3a6537aa0 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIStyle.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIStyle.cs @@ -41,11 +41,14 @@ namespace Barotrauma public SpriteSheet SavingIndicator { get; private set; } public UISprite UIGlow { get; private set; } + public UISprite TalentGlow { get; private set; } public UISprite PingCircle { get; private set; } public UISprite UIGlowCircular { get; private set; } + public UISprite UIGlowSolidCircular { get; private set; } + public UISprite ButtonPulse { get; private set; } public SpriteSheet FocusIndicator { get; private set; } @@ -88,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.Gray; + public Color ItemQualityColorNormal { get; private set; } = Color.White; + 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; @@ -241,6 +250,9 @@ namespace Barotrauma case "uiglow": UIGlow = new UISprite(subElement); break; + case "talentglow": + TalentGlow = new UISprite(subElement); + break; case "pingcircle": PingCircle = new UISprite(subElement); break; @@ -253,6 +265,9 @@ namespace Barotrauma case "uiglowcircular": UIGlowCircular = new UISprite(subElement); break; + case "uiglowsolidcircular": + UIGlowSolidCircular = new UISprite(subElement); + break; case "endroundbuttonpulse": ButtonPulse = new UISprite(subElement); break; diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/LoadingScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/LoadingScreen.cs index 5a84a234f..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) @@ -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/TabMenu.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs index efee168f0..803f2537c 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, Talents }; - private static InfoFrameTab selectedTab; + public enum InfoFrameTab { Crew, Mission, Reputation, MyCharacter, Traitor, Submarine, Talents }; + public static InfoFrameTab selectedTab; private GUIFrame infoFrame, contentFrame; private readonly List tabButtons = new List(); @@ -48,7 +49,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; @@ -262,7 +263,11 @@ namespace Barotrauma var talentsButton = createTabButton(InfoFrameTab.Talents, "tabmenu.talents"); talentsButton.OnAddedToGUIUpdateList += (GUIComponent component) => { - talentsButton.Enabled = Character.Controlled?.Info != null && GameMain.GameSession?.Campaign != null; + talentsButton.Enabled = Character.Controlled?.Info != null && (GameMain.GameSession?.Campaign != null || Screen.Selected == GameMain.TestScreen || GameMain.GameSession.GameMode is TestGameMode); + if (!talentsButton.Enabled && selectedTab == InfoFrameTab.Talents) + { + SelectInfoFrameTab(null, InfoFrameTab.Crew); + } }; } @@ -417,7 +422,7 @@ namespace Barotrauma if (GameMain.IsMultiplayer) { CreateMultiPlayerList(false); - CreateMultiPlayerLogContent(crewFrame); + CreateMultiPlayerLogContent(crewFrame); } else { @@ -608,7 +613,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, @@ -837,7 +842,7 @@ namespace Barotrauma } private static readonly List> storedMessages = new List>(); - + public static void StorePlayerConnectionChangeMessage(ChatMessage message) { if (!GameMain.GameSession?.IsRunning ?? true) { return; } @@ -850,7 +855,7 @@ namespace Barotrauma TabMenu instance = GameSession.TabMenuInstance; instance.AddLineToLog(msg, message.ChangeType); instance.RemoveCurrentElements(); - instance.CreateMultiPlayerList(true); + instance.CreateMultiPlayerList(true); } } @@ -969,7 +974,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) @@ -997,7 +1002,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); @@ -1008,8 +1013,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, @@ -1174,19 +1179,38 @@ namespace Barotrauma private Color unselectableColor = new Color(100, 100, 100, 225); private Color pressedColor = new Color(60, 60, 60, 225); - private readonly List<(GUIButton button, GUIImage background, GUIImage icon)> talentButtons = new List<(GUIButton button, GUIImage background, GUIImage icon)>(); + private readonly List<(GUIButton button, GUIComponent icon, GUIImage glow)> talentButtons = new List<(GUIButton button, GUIComponent icon, GUIImage glow)>(); + 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 talentTitleText; - private GUITextBlock talentDescriptionText; - private GUITextBlock talentPointsText; private GUITextBlock experienceText; private GUIProgressBar experienceBar; + private GUITextBlock talentPointText; + private GUIListBox skillListBox; + + 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; } @@ -1195,6 +1219,8 @@ namespace Barotrauma 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(0.9f), talentFrameContent.RectTransform, Anchor.Center), style: null); + if (controlledCharacter.Info == null) { DebugConsole.ThrowError("No character info found for talent UI"); @@ -1203,87 +1229,103 @@ namespace Barotrauma selectedTalents = controlledCharacter.Info.UnlockedTalents.ToList(); - GUILayoutGroup talentFrameLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f), talentFrameContent.RectTransform, anchor: Anchor.Center), childAnchor: Anchor.TopCenter) + GUILayoutGroup talentFrameLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f), paddedTalentFrame.RectTransform, anchor: Anchor.Center), childAnchor: Anchor.TopCenter) { AbsoluteSpacing = GUI.IntScale(5) }; - GUILayoutGroup talentInfoLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.325f), talentFrameLayoutGroup.RectTransform, Anchor.Center), childAnchor: Anchor.TopCenter); + GUILayoutGroup talentInfoLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.25f), talentFrameLayoutGroup.RectTransform, Anchor.Center), isHorizontal: true); - GUIFrame talentTitleFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.25f), talentInfoLayoutGroup.RectTransform, Anchor.TopCenter), style: null); + CharacterInfo info = controlledCharacter.Info; + Job job = info.Job; - talentTitleText = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), talentTitleFrame.RectTransform, Anchor.TopLeft), "", font: GUI.LargeFont); - talentPointsText = new GUITextBlock(new RectTransform(new Vector2(0.25f, 1.0f), talentTitleFrame.RectTransform, Anchor.TopRight), "", font: GUI.Font, textAlignment: Alignment.Center); - - GUIFrame talentDescriptionFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.4f), talentInfoLayoutGroup.RectTransform, Anchor.TopCenter), style: null); - - talentDescriptionText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), talentDescriptionFrame.RectTransform, Anchor.TopLeft), "", font: GUI.Font, textAlignment: Alignment.TopLeft, wrap: true); - - GUIFrame characterInfoFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.3f), talentInfoLayoutGroup.RectTransform, Anchor.TopLeft), style: null); - GUILayoutGroup characterInfoColumn = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f), characterInfoFrame.RectTransform, anchor: Anchor.TopLeft), childAnchor: Anchor.TopLeft, isHorizontal: true); - - // move to a different tab menu - if (GameSettings.VerboseLogging) + new GUICustomComponent(new RectTransform(new Vector2(0.25f, 1f), talentInfoLayoutGroup.RectTransform), onDraw: (batch, component) => { - CreateCharacterSheet(characterInfoColumn); - } + float posY = component.Rect.Bottom - component.Rect.Width; + 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.375f, 1f), talentInfoLayoutGroup.RectTransform)); + + 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(); + + GUILayoutGroup skillLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.375f, 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; } - GUIListBox talentTreeListBox = new GUIListBox(new RectTransform(new Vector2(1f, 0.6f), talentFrameLayoutGroup.RectTransform, Anchor.TopCenter), isHorizontal: true, style: null); + new GUIFrame(new RectTransform(new Vector2(1f, 1f), talentFrameLayoutGroup.RectTransform), style: "HorizontalLine"); - int spacing = GUI.IntScale(5); + GUIListBox talentTreeListBox = new GUIListBox(new RectTransform(new Vector2(1f, 0.7f), talentFrameLayoutGroup.RectTransform, Anchor.TopCenter), isHorizontal: true, style: null); 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: "SubtreeHeader"); - - new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), subtreeTitleFrame.RectTransform, anchor: Anchor.TopCenter), subTree.Identifier, font: GUI.LargeFont, textAlignment: Alignment.Center); + 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"); + new GUITextBlock(new RectTransform(Vector2.One, subTreeTitleBackground.RectTransform, anchor: Anchor.TopCenter), subTree.DisplayName, font: GUI.LargeFont, 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: "TalentOptionFrame"); - GUIImage talentBackground = new GUIImage(new RectTransform(Vector2.One, talentOptionFrame.RectTransform, anchor: Anchor.Center), style: "TalentBackground") + 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.25f), talentOptionFrame.RectTransform, anchor: Anchor.BottomRight, scaleBasis: ScaleBasis.BothHeight), style: null) { - CanBeFocused = false, - Color = unselectableColor, + 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 talentOptionLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1f, 1f), talentOptionFrame.RectTransform, Anchor.CenterLeft), isHorizontal: true, childAnchor: Anchor.CenterLeft) - { - Stretch = true, - }; + 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) { - int optionPadding = GUI.IntScale(10); - GUIFrame talentFrame = new GUIFrame(new RectTransform(new Point(talentOptionFrame.Rect.Width, talentOptionFrame.Rect.Height - optionPadding), talentOptionLayoutGroup.RectTransform), style: null) + GUIFrame talentFrame = new GUIFrame(new RectTransform(Vector2.One, talentOptionLayoutGroup.RectTransform), style: null) { CanBeFocused = false, }; - new GUIImage(new RectTransform(Vector2.One, talentFrame.RectTransform, anchor: Anchor.Center), style: "TalentFrameBackground") - { - CanBeFocused = false, - }; - GUIImage iconImage = null; - GUIButton talentButton = new GUIButton(new RectTransform(Vector2.One, talentFrame.RectTransform, anchor: Anchor.Center), style: "TalentFrame") + GUIButton talentButton = new GUIButton(new RectTransform(Vector2.One, talentFrame.RectTransform, anchor: Anchor.Center), style: null) { ToolTip = $"{talent.DisplayName}\n\n{talent.Description}", UserData = talent.Identifier, PressedColor = pressedColor, OnClicked = (button, userData) => { - talentTitleText.Text = talent.DisplayName; - talentDescriptionText.Text = talent.Description; - // deselect other buttons in tier by removing their selected talents from pool foreach (GUIButton guiButton in talentOptionLayoutGroup.GetAllChildren()) { @@ -1314,61 +1356,129 @@ namespace Barotrauma }, }; + talentButton.Color = talentButton.HoverColor = talentButton.PressedColor = talentButton.SelectedColor = Color.Transparent; - int iconPadding = GUI.IntScale(15); - iconImage = new GUIImage(new RectTransform(new Point(talentFrame.Rect.Width - iconPadding, talentFrame.Rect.Height - iconPadding), talentFrame.RectTransform, anchor: Anchor.Center), sprite: talent.Icon, scaleToFit: true) + GUIComponent iconImage; + if (talent.Icon is null) { - PressedColor = unselectableColor, - CanBeFocused = false, - }; + iconImage = new GUITextBlock(new RectTransform(Vector2.One, talentButton.RectTransform, anchor: Anchor.Center, scaleBasis: ScaleBasis.BothHeight), 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, talentBackground, iconImage)); + GUIImage iconGlow = new GUIImage(new RectTransform(Vector2.One, iconImage.RectTransform, anchor: Anchor.Center), sprite: GUI.Style.TalentGlow.Sprite, scaleToFit: true) { Visible = false }; + + talentButtons.Add((talentButton, iconImage, iconGlow)); } + + talentCornerIcons.Add((subTree.Identifier, i, cornerIcon, talentBackground, talentBackgroundHighlight)); } } } - GUIFrame talentBottomFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.07f), talentFrameLayoutGroup.RectTransform, Anchor.TopCenter), style: null); + GUILayoutGroup talentBottomFrame = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.07f), talentFrameLayoutGroup.RectTransform, Anchor.TopCenter), isHorizontal: true) { RelativeSpacing = 0.01f }; - GUIFrame experienceBarFrame = new GUIFrame(new RectTransform(new Vector2(0.775f, 0.75f), talentBottomFrame.RectTransform, Anchor.TopCenter), style: null); + GUILayoutGroup experienceLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.775f, 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: Color.White, style: "ExperienceBar") + barSize: controlledCharacter.Info.GetProgressTowardsNextLevel(), color: GUI.Style.Green) { IsHorizontal = true }; - GUIImage experienceTextBackground = new GUIImage(new RectTransform(new Vector2(0.2f, 0.475f), experienceBarFrame.RectTransform, anchor: Anchor.Center), style: "ExperienceTextBackground"); + experienceText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), experienceBarFrame.RectTransform, anchor: Anchor.Center), "", font: GUI.Font, textAlignment: Alignment.CenterRight); - experienceText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), experienceTextBackground.RectTransform, anchor: Anchor.Center), "", font: GUI.Font, textColor: Color.White, textAlignment: Alignment.Center); + talentPointText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), experienceLayout.RectTransform, anchor: Anchor.Center), "", font: GUI.SubHeadingFont, parseRichText: true, textAlignment: Alignment.CenterRight); - new GUIButton(new RectTransform(new Vector2(0.1f, 0.3f), talentBottomFrame.RectTransform, anchor: Anchor.TopRight), text: TextManager.Get("applysettingsbutton")) - { - OnClicked = ApplyTalentSelection, - }; - - new GUIButton(new RectTransform(new Vector2(0.1f, 0.3f), talentBottomFrame.RectTransform, anchor: Anchor.TopLeft), text: TextManager.Get("reset")) + new GUIButton(new RectTransform(new Vector2(0.1f, 1f), talentBottomFrame.RectTransform), text: TextManager.Get("reset"), style: "GUICharacterInfoButton") { OnClicked = ResetTalentSelection, }; + new GUIButton(new RectTransform(new Vector2(0.1f, 1f), talentBottomFrame.RectTransform), text: TextManager.Get("applysettingsbutton"), style: "GUICharacterInfoButton") + { + OnClicked = ApplyTalentSelection, + }; + UpdateTalentButtons(); } + private void CreateTalentSkillList(Character character, GUIListBox parent) + { + parent.Content.ClearChildren(); + 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 }; + + 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(); + } + private void UpdateTalentButtons() { Character controlledCharacter = Character.Controlled; - talentPointsText.Text = $"{TextManager.Get("talentpointsleft")}{controlledCharacter.Info.GetAvailableTalentPoints()}"; 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); - foreach (var talentButton in talentButtons) + string pointsLeft = controlledCharacter.Info.GetAvailableTalentPoints().ToString(); + + int talentCount = selectedTalents.Count - controlledCharacter.Info.UnlockedTalents.Count; + + if (talentCount > 0) { - talentButton.background.Color = unselectableColor; + 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) @@ -1377,30 +1487,23 @@ namespace Barotrauma bool unselectable = !TalentTree.IsViableTalentForCharacter(controlledCharacter, talentIdentifier, selectedTalents) || controlledCharacter.HasTalent(talentIdentifier); Color newTalentColor = unselectable ? unselectableColor : unselectedColor; + talentButton.glow.Visible = false; + if (controlledCharacter.HasTalent(talentIdentifier)) { - newTalentColor = ownedColor; + newTalentColor = new Color(140,225,140,255); } else if (selectedTalents.Contains(talentIdentifier)) { - newTalentColor = selectedColor; + newTalentColor = new Color(174,164,124,255); + talentButton.glow.Visible = true; } - talentButton.button.Color = newTalentColor; - talentButton.button.SelectedColor = newTalentColor; - talentButton.button.HoverColor = newTalentColor; - talentButton.button.DisabledColor = newTalentColor; - talentButton.icon.Color = newTalentColor; - - // update background color as well, if not defined yet - if (talentButton.background.Color == unselectableColor) - { - talentButton.background.Color = newTalentColor; - } } - } + CreateTalentSkillList(controlledCharacter, skillListBox); + } private void ApplyTalents(Character controlledCharacter) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs b/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs index e22ee76cb..e979ecbeb 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs @@ -194,6 +194,8 @@ namespace Barotrauma #if DEBUG public static bool FirstLoad = true; + + public static bool CancelQuickStart; #endif public GameMain(string[] args) @@ -309,7 +311,7 @@ namespace Barotrauma GraphicsDeviceManager.PreferredBackBufferWidth = GraphicsWidth; GraphicsDeviceManager.PreferredBackBufferHeight = GraphicsHeight; - + GraphicsDeviceManager.ApplyChanges(); if (windowMode == WindowMode.BorderlessWindowed) @@ -588,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; @@ -601,7 +603,7 @@ namespace Barotrauma ItemAssemblyPrefab.LoadAll(); TitleScreen.LoadState = 60.0f; yield return CoroutineStatus.Running; - + GameModePreset.Init(); SaveUtil.DeleteDownloadedSubs(); @@ -654,7 +656,7 @@ namespace Barotrauma ParticleManager.LoadPrefabs(); TitleScreen.LoadState = 88.0f; LevelObjectPrefab.LoadAll(); - + TitleScreen.LoadState = 90.0f; yield return CoroutineStatus.Running; @@ -804,7 +806,9 @@ namespace Barotrauma } #if DEBUG - if (TitleScreen.LoadState >= 100.0f && !TitleScreen.PlayingSplashScreen && (Config.AutomaticQuickStartEnabled || Config.AutomaticCampaignLoadEnabled || Config.TestScreenEnabled) && 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; @@ -812,7 +816,7 @@ namespace Barotrauma if (Config.TestScreenEnabled) { TestScreen.Select(); - } + } else if (Config.AutomaticQuickStartEnabled) { MainMenuScreen.QuickStart(); @@ -930,8 +934,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); } } @@ -1095,7 +1099,7 @@ namespace Barotrauma if (save) { GUI.SetSavingIndicatorState(true); - + if (GameSession.Submarine != null && !GameSession.Submarine.Removed) { GameSession.SubmarineInfo = new SubmarineInfo(GameSession.Submarine); @@ -1267,7 +1271,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/CrewManager.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs index 520e873f5..09fe317e9 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs @@ -208,7 +208,7 @@ namespace Barotrauma ReportButtonFrame.RectTransform.AbsoluteOffset = new Point(0, -chatBox.ToggleButton.Rect.Height); - CreateReports(this, ReportButtonFrame, reports, false); + CreateReportButtons(this, ReportButtonFrame, reports, false); #endregion @@ -218,7 +218,7 @@ namespace Barotrauma dismissedOrderPrefab ??= Order.GetPrefab("dismissed"); } - public static void CreateReports(CrewManager crewManager, GUIComponent parent, List reports, bool isHorizontal) + public static void CreateReportButtons(CrewManager crewManager, GUIComponent parent, List reports, bool isHorizontal) { //report buttons foreach (Order order in reports) @@ -228,22 +228,21 @@ namespace Barotrauma { 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; } 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) { @@ -272,8 +271,9 @@ 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 }; } } @@ -717,7 +717,7 @@ namespace Barotrauma 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) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/CharacterInventory.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/CharacterInventory.cs index 1e365da6b..72434637b 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/CharacterInventory.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/CharacterInventory.cs @@ -1232,7 +1232,7 @@ namespace Barotrauma highlightedQuickUseSlot = visualSlots[i]; } - if (!slots[i].First().AllowedSlots.Any(a => a == InvSlotType.Any) || SlotTypes[i] == InvSlotType.HealthInterface) + if (slots[i].First().AllowedSlots.Count() == 1 || SlotTypes[i] == InvSlotType.HealthInterface) { continue; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Holdable/RangedWeapon.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Holdable/RangedWeapon.cs index 08ffe00cf..b6eca6599 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Holdable/RangedWeapon.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Holdable/RangedWeapon.cs @@ -8,6 +8,7 @@ using Barotrauma.IO; using System.Text; using System.Xml.Linq; using Barotrauma.Sounds; +using System.Linq; namespace Barotrauma.Items.Components { @@ -145,7 +146,9 @@ namespace Barotrauma.Items.Components 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; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemContainer.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemContainer.cs index 5b054daff..d2521a444 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemContainer.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemContainer.cs @@ -214,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 { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs index f85f13a2a..fb9f701ea 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; @@ -270,7 +270,7 @@ namespace Barotrauma.Items.Components }); 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 @@ -278,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 @@ -290,7 +290,7 @@ namespace Barotrauma.Items.Components } var requiresRecipeText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.15f), itemList.Content.RectTransform), - TextManager.Get("fabricatorrequiresrecipe", returnNull: true) ?? "Requires recipe to fabricate", textColor: Color.Red, font: GUI.SubHeadingFont) + TextManager.Get("fabricatorrequiresrecipe"), textColor: Color.Red, font: GUI.SubHeadingFont) { AutoScaleHorizontal = true, CanBeFocused = false @@ -403,7 +403,7 @@ namespace Barotrauma.Items.Components { toolTipText += '\n' + requiredItem.ItemPrefabs.First().Description; } - tooltip = new Pair(slotRect, toolTipText); + tooltip = (slotRect, toolTipText); } slotIndex++; @@ -443,7 +443,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; } } @@ -463,6 +463,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; @@ -498,9 +514,20 @@ namespace Barotrauma.Items.Components }; }*/ + string name = GetRecipeNameAndAmount(selectedItem); + + float quality = 0; + foreach (string tag in selectedItem.TargetItem.Tags) + { + quality += user?.Info?.GetSavedStatValue(StatTypes.IncreaseFabricationQuality, tag) ?? 0; + } + if (quality > 0) + { + name = TextManager.GetWithVariable("itemname.quality" + (int)quality, "[itemname]", name+'\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.CenterLeft, textColor: Color.Aqua, font: GUI.SubHeadingFont, parseRichText: true) { AutoScaleHorizontal = true }; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs index 7c64d0346..0c213a8cb 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs @@ -218,8 +218,8 @@ namespace Barotrauma.Items.Components DefaultNeutralColor = MiniMapBaseColor * 0.8f, HoverColor = Color.White, BlueprintBlue = new Color(23, 38, 33), - HullWaterColor = new Color(17, 173, 179), - HullWaterLineColor = Color.LightBlue, + 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; @@ -307,10 +307,13 @@ namespace Barotrauma.Items.Components if (reports.Any()) { - CrewManager.CreateReports(GameMain.GameSession?.CrewManager, reportFrame, reports, true); + CrewManager.CreateReportButtons(GameMain.GameSession?.CrewManager, reportFrame, reports, true); } - searchBarFrame = new GUILayoutGroup(new RectTransform(new Vector2(1), bottomFrame.RectTransform), isHorizontal: true, childAnchor: Anchor.Center); + 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) => @@ -345,6 +348,7 @@ namespace Barotrauma.Items.Components foreach (ItemPrefab prefab in ItemPrefab.Prefabs.OrderBy(prefab => prefab.Name)) { + if (prefab.HideInMenus) { continue; } CreateItemFrame(prefab, listBox.Content.RectTransform); } @@ -355,7 +359,10 @@ namespace Barotrauma.Items.Components searchBar.OnSelected += (sender, key) => { - itemsFoundOnSub = Item.ItemList.Where(it => it.Submarine == item.Submarine && !it.NonInteractable && !it.HiddenInGame && it.Components.OfType().Any()).Select(it => it.Prefab).ToImmutableHashSet(); + itemsFoundOnSub = Item.ItemList.Where(it => + it.Submarine == item.Submarine && + !it.NonInteractable && !it.HiddenInGame && + (it.GetComponent() != null || it.GetComponent() != null)).Select(it => it.Prefab).ToImmutableHashSet(); }; searchBar.OnKeyHit += ControlSearchTooltip; @@ -487,21 +494,24 @@ namespace Barotrauma.Items.Components CreateHUD(); } - if (PlayerInput.PrimaryMouseButtonDown() && currentMode != MiniMapMode.HullStatus) + if (scissorComponent != null) { - if (GUI.MouseOn == scissorComponent || scissorComponent.IsParentOf(GUI.MouseOn)) + if (PlayerInput.PrimaryMouseButtonDown() && currentMode != MiniMapMode.HullStatus) { - dragMapStart = PlayerInput.MousePosition; + 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 (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) @@ -524,15 +534,13 @@ namespace Barotrauma.Items.Components if (recalculate) { - miniMapContainer.RectTransform.LocalScale = new Vector2(Zoom); - miniMapContainer.RectTransform.RecalculateChildren(true, true); - miniMapContainer.RectTransform.AbsoluteOffset = mapOffset.ToPoint(); + if (miniMapContainer != null) + { + miniMapContainer.RectTransform.LocalScale = new Vector2(Zoom); + miniMapContainer.RectTransform.RecalculateChildren(true, true); + miniMapContainer.RectTransform.AbsoluteOffset = mapOffset.ToPoint(); + } recalculate = false; - - // var (maxWidth, maxHeight) = miniMapContainer.Rect.Size.ToVector2() / 2f; - // - // mapOffset.X = Math.Clamp(mapOffset.X, -maxWidth, maxWidth); - // mapOffset.Y = Math.Clamp(mapOffset.Y, -maxHeight, maxHeight); } // is there a better way to do this? @@ -606,17 +614,6 @@ namespace Barotrauma.Items.Components private void DrawHUDFront(SpriteBatch spriteBatch, GUICustomComponent container) { - // TODO remove - #warning remove - if (currentMode == MiniMapMode.HullCondition) - { - const string wipText = "work in progress"; - Vector2 textSize = GUI.LargeFont.MeasureString(wipText); - Vector2 textPos = GuiFrame.Rect.Center.ToVector2(); - - GUI.DrawString(spriteBatch, textPos - textSize / 2, wipText.ToUpper(), GUI.Style.Orange, Color.Black * 0.8f, backgroundPadding: 8, font: GUI.LargeFont); - } - if (Voltage < MinVoltage) { Vector2 textSize = GUI.Font.MeasureString(noPowerTip); @@ -627,18 +624,45 @@ namespace Barotrauma.Items.Components return; } - if (currentMode == MiniMapMode.HullStatus) + if (currentMode == MiniMapMode.HullStatus || currentMode == MiniMapMode.HullCondition) { Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle; spriteBatch.End(); spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable); spriteBatch.GraphicsDevice.ScissorRectangle = submarineContainer.Rect; - foreach (var (entity, component) in hullStatusComponents) + if (currentMode == MiniMapMode.HullCondition && item.Submarine != null) { - if (!(entity is Hull hull)) { continue; } - if (!hullDatas.TryGetValue(hull, out HullData? hullData) || hullData is null) { continue; } - DrawHullCards(spriteBatch, hull, hullData, component.RectComponent); + var sprite = GUI.Style.UIGlowSolidCircular?.Sprite; + float alpha = (MathF.Sin(blipState / maxBlipState * MathHelper.TwoPi) + 1.5f) * 0.5f; + if (sprite != null) + { + 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); + } + } + } + + 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(); @@ -721,14 +745,21 @@ namespace Barotrauma.Items.Components UserData = prefab }; - GUILayoutGroup layout = new GUILayoutGroup(new RectTransform(Vector2.One, frame.RectTransform), isHorizontal: true); + 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 + Color = prefab.InventoryIconColor, + UserData = prefab }; - new GUITextBlock(new RectTransform(Vector2.One, layout.RectTransform), prefab.Name, font: GUI.SubHeadingFont); - layout.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) @@ -792,15 +823,18 @@ namespace Barotrauma.Items.Components private void UpdateHUDBack() { + if (item.Submarine == null) { return; } + hullInfoFrame.Visible = false; - electricalFrame.Visible = false; - miniMapFrame.Visible = false; reportFrame.Visible = false; searchBarFrame.Visible = false; + electricalFrame.Visible = false; + miniMapFrame.Visible = false; switch (currentMode) { case MiniMapMode.HullStatus: + case MiniMapMode.HullCondition: UpdateHullStatus(); miniMapFrame.Visible = true; reportFrame.Visible = true; @@ -937,16 +971,19 @@ namespace Barotrauma.Items.Components SetTooltip(borderComponent.Rect.Center, header, line1, line2, line3, line1Color, line2Color, line3Color); } + 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) { if (!hullStatusComponents.ContainsKey(linkedHull)) { continue; } - isHoveringOver |= canHoverOverHull && hullStatusComponents[linkedHull].RectComponent == GUI.MouseOn; + isHoveringOver |= + canHoverOverHull && + (hullStatusComponents[linkedHull].RectComponent == GUI.MouseOn || (draggingReport && hullStatusComponents[linkedHull].RectComponent.MouseRect.Contains(PlayerInput.MousePosition))); if (isHoveringOver) { break; } } - if (isHoveringOver) + if (isHoveringOver || (draggingReport && component.MouseRect.Contains(PlayerInput.MousePosition))) { borderColor = Color.Lerp(borderColor, Color.White, 0.5f); componentColor = HoverColor; @@ -1037,7 +1074,7 @@ namespace Barotrauma.Items.Components } else { - bool hullsVisible = currentMode == MiniMapMode.HullStatus; + bool hullsVisible = currentMode == MiniMapMode.HullStatus || currentMode == MiniMapMode.HullCondition; foreach (var (entity, component) in hullStatusComponents) { @@ -1150,7 +1187,7 @@ namespace Barotrauma.Items.Components 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 }; + Color blueprintBlue = BlueprintBlue * currentMode switch { MiniMapMode.HullStatus => 0.1f, MiniMapMode.HullCondition => 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; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Repairable.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Repairable.cs index a960ec531..f68bdc796 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Repairable.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Repairable.cs @@ -19,9 +19,11 @@ namespace Barotrauma.Items.Components private GUIProgressBar progressBar; - private List particleEmitters = new List(); + private GUITextBlock progressBarOverlayText; + + 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; @@ -88,7 +90,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) { @@ -123,6 +125,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) @@ -138,6 +145,7 @@ 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); + 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") @@ -229,9 +237,23 @@ 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); + 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; RepairButton.Text = (currentFixerAction == FixActions.None || CurrentFixer != character || currentFixerAction != FixActions.Repair) ? repairButtonText : diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs index a91cd1cf9..daa10c434 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs @@ -316,12 +316,17 @@ namespace Barotrauma string colorStr = XMLExtensions.ColorToString(!item.AllowStealing ? GUI.Style.Red : Color.White); + if (item.Quality > 0) + { + name = TextManager.GetWithVariable("itemname.quality" + item.Quality, "[itemname]", name, fallBackTag: "itemname.quality3"); + } toolTip = $"‖color:{colorStr}‖{name}‖color:end‖"; + 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‖"; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs index 1ed06add7..ea190c1d8 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs @@ -1068,7 +1068,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); } @@ -1194,7 +1194,7 @@ namespace Barotrauma } } - if (Character.Controlled != null && Character.Controlled.SelectedConstruction != this) + 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)) @@ -1537,6 +1537,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 = ""; @@ -1612,7 +1613,8 @@ namespace Barotrauma item = new Item(itemPrefab, pos, sub, id: itemId) { SpawnedInOutpost = spawnedInOutpost, - AllowStealing = allowStealing + AllowStealing = allowStealing, + Quality = quality }; } catch (Exception e) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs index 29699b746..7944e07d2 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs @@ -1694,7 +1694,7 @@ namespace Barotrauma 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) @@ -1707,17 +1707,7 @@ 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), 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); + 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); var itemIdentifiers = jobPrefab.ItemIdentifiers[variant] .Distinct() @@ -1726,7 +1716,7 @@ namespace Barotrauma 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)); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/TestScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/TestScreen.cs index a806460f5..38b34c0a0 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/TestScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/TestScreen.cs @@ -1,6 +1,7 @@ #nullable enable using System; using System.Linq; +using Barotrauma.Extensions; using Barotrauma.Items.Components; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; @@ -23,6 +24,8 @@ namespace Barotrauma private Character? dummyCharacter; public static Effect BlueprintEffect; + private TabMenu tabMenu; + public TestScreen() { Cam = new Camera(); @@ -50,18 +53,15 @@ namespace Barotrauma dummyCharacter?.Remove(); } - // ???????? - submarine = new Submarine(SubmarineInfo.SavedSubmarines.FirstOrDefault(info => info.Name.Equals("Crescent", StringComparison.OrdinalIgnoreCase))); - miniMapItem = new Item(ItemPrefab.Find(null, "statusmonitor"), Vector2.Zero, submarine); - MiniMap miniMap = miniMapItem.GetComponent(); - miniMap.PowerConsumption = 0; - 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() @@ -69,34 +69,21 @@ namespace Barotrauma 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 && miniMapItem is { } item) + if (dummyCharacter is { } dummy) { - if (dummy.SelectedConstruction != item) - { - dummy.SelectedConstruction = item; - } - dummy.SelectedConstruction?.UpdateHUD(Cam, dummy, (float)deltaTime); - Vector2 pos = FarseerPhysics.ConvertUnits.ToSimUnits(item.Position); - foreach (Limb limb in dummy.AnimController.Limbs) - { - limb.body.SetTransform(pos, 0.0f); - } - - if (dummy.AnimController?.Collider is { } collider) - { - collider.SetTransform(pos, 0); - } - dummy.ControlLocalPlayer((float)deltaTime, Cam, false); dummy.Control((float)deltaTime, Cam); - dummy.Submarine = submarine; } + + GUI.Update((float)deltaTime); } public override void Draw(double deltaTime, GraphicsDevice graphics, SpriteBatch spriteBatch) diff --git a/Barotrauma/BarotraumaClient/LinuxClient.csproj b/Barotrauma/BarotraumaClient/LinuxClient.csproj index 002617924..8bdd4831b 100644 --- a/Barotrauma/BarotraumaClient/LinuxClient.csproj +++ b/Barotrauma/BarotraumaClient/LinuxClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.1500.2.0 + 0.1500.3.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/MacClient.csproj b/Barotrauma/BarotraumaClient/MacClient.csproj index 8ed8a8db7..4dd212c17 100644 --- a/Barotrauma/BarotraumaClient/MacClient.csproj +++ b/Barotrauma/BarotraumaClient/MacClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.1500.2.0 + 0.1500.3.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/WindowsClient.csproj b/Barotrauma/BarotraumaClient/WindowsClient.csproj index f2ef1049a..5df6359c5 100644 --- a/Barotrauma/BarotraumaClient/WindowsClient.csproj +++ b/Barotrauma/BarotraumaClient/WindowsClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.1500.2.0 + 0.1500.3.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaServer/LinuxServer.csproj b/Barotrauma/BarotraumaServer/LinuxServer.csproj index 32989bc6e..5d6dfdf6e 100644 --- a/Barotrauma/BarotraumaServer/LinuxServer.csproj +++ b/Barotrauma/BarotraumaServer/LinuxServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.1500.2.0 + 0.1500.3.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/MacServer.csproj b/Barotrauma/BarotraumaServer/MacServer.csproj index a2648dcd0..26fce29e3 100644 --- a/Barotrauma/BarotraumaServer/MacServer.csproj +++ b/Barotrauma/BarotraumaServer/MacServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.1500.2.0 + 0.1500.3.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/ServerSource/Items/Item.cs b/Barotrauma/BarotraumaServer/ServerSource/Items/Item.cs index c393668e2..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()) diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/ServerSettings.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/ServerSettings.cs index 90271e6e3..5da3ddd5f 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/ServerSettings.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/ServerSettings.cs @@ -265,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/WindowsServer.csproj b/Barotrauma/BarotraumaServer/WindowsServer.csproj index 4d2b9f8d8..6b4075d6e 100644 --- a/Barotrauma/BarotraumaServer/WindowsServer.csproj +++ b/Barotrauma/BarotraumaServer/WindowsServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.1500.2.0 + 0.1500.3.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 DedicatedServer 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/Ragdoll.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs index 3bbb32214..a42e5d81e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs @@ -1430,6 +1430,7 @@ 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; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs index abd381c65..da6c1677b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs @@ -251,6 +251,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; @@ -2720,7 +2722,7 @@ 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() > 2.5f * 2.5f; bool wasRagdolled = false; bool selfRagdolled = false; @@ -3131,6 +3133,12 @@ namespace Barotrauma { 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) @@ -3406,6 +3414,13 @@ namespace Barotrauma 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; if (attacker != null) @@ -3623,11 +3638,6 @@ namespace Barotrauma ApplyStatusEffects(ActionType.OnDamaged, 1.0f); hitLimb.ApplyStatusEffects(ActionType.OnDamaged, 1.0f); } - if (attacker != null) - { - var abilityAttackResult = new AbilityAttackResult(attackResult); - attacker.CheckTalents(AbilityEffectType.OnAttackResult, abilityAttackResult); - } return attackResult; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs index e69408bbd..e4190f0ea 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs @@ -979,6 +979,8 @@ namespace Barotrauma increase *= SkillSettings.Current.AssistantSkillIncreaseMultiplier; } + increase *= 1f + Character.GetStatValue(StatTypes.SkillGainSpeed); + float prevLevel = Job.GetSkillLevel(skillIdentifier); Job.IncreaseSkillLevel(skillIdentifier, increase, Character.HasAbilityFlag(AbilityFlags.GainSkillPastMaximum)); @@ -1527,6 +1529,17 @@ namespace Barotrauma 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) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs index 8f882e2f7..be9258701 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs @@ -971,7 +971,7 @@ namespace Barotrauma /// A dictionary where the key is the identifier of the item and the value the suitability /// If true, the suitability values are normalized between 0 and 1. If not, they're arbitrary values defined in the medical item XML, where negative values are unsuitable, and positive ones suitable. /// 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, Limb limb = null, 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 @@ -980,6 +980,7 @@ namespace Barotrauma foreach (Affliction affliction in getAfflictions(limb)) { 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)) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionIsAiming.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionIsAiming.cs index 324676a98..01de01b61 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionIsAiming.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionIsAiming.cs @@ -7,12 +7,12 @@ namespace Barotrauma.Abilities { private enum WeaponType { - Any = 0, - Melee = 1, + Any = 0, + Melee = 1, Ranged = 2 }; - private WeaponType weapontype; + private readonly WeaponType weapontype; public AbilityConditionIsAiming(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) { switch (conditionElement.GetAttributeString("weapontype", "")) @@ -43,7 +43,7 @@ namespace Barotrauma.Abilities break; default: aimingCorrectItem |= animController.IsAiming || animController.IsAimingMelee; - break; + break; } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionItem.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionItem.cs index ff6cba362..d4eb985d2 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionItem.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionItem.cs @@ -6,12 +6,12 @@ namespace Barotrauma.Abilities { class AbilityConditionItem : AbilityConditionData { - private readonly string identifier; + private readonly string[] identifiers; private readonly string[] tags; public AbilityConditionItem(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) { - identifier = conditionElement.GetAttributeString("identifier", string.Empty).ToLowerInvariant(); + identifiers = conditionElement.GetAttributeStringArray("identifiers", Array.Empty(), convertToLowerInvariant: true); tags = conditionElement.GetAttributeStringArray("tags", Array.Empty(), convertToLowerInvariant: true); } @@ -29,15 +29,15 @@ namespace Barotrauma.Abilities if (itemPrefab != null) { - if (!string.IsNullOrEmpty(identifier)) + if (identifiers.Any()) { - if (itemPrefab.Identifier != identifier) + if (!identifiers.Any(t => itemPrefab.Identifier == t)) { return false; } } - return tags.Any(t => itemPrefab.Tags.Any(p => t == p)); + return !tags.Any() || tags.Any(t => itemPrefab.Tags.Any(p => t == p)); } else { 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/AbilityConditionShipFlooded.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionShipFlooded.cs index ba5f10ccc..9a99f4ce8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionShipFlooded.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionShipFlooded.cs @@ -13,7 +13,7 @@ namespace Barotrauma.Abilities protected override bool MatchesConditionSpecific() { - if (character.Submarine == null || character.Submarine.TeamID != character.TeamID) { return false; } + 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/CharacterAbilityApplyStatusEffects.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffects.cs index 6dbc6450e..9eb8e20e5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffects.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffects.cs @@ -10,6 +10,7 @@ namespace Barotrauma.Abilities protected readonly List statusEffects; + private readonly bool nearbyCharactersAppliesToSelf; private readonly bool applyToSelected; readonly List targets = new List(); @@ -18,6 +19,7 @@ namespace Barotrauma.Abilities { statusEffects = CharacterAbilityGroup.ParseStatusEffects(CharacterTalent, abilityElement.GetChildElement("statuseffects")); applyToSelected = abilityElement.GetAttributeBool("applytoselected", false); + nearbyCharactersAppliesToSelf = abilityElement.GetAttributeBool("nearbycharactersappliestoself", true); } protected void ApplyEffectSpecific(Character targetCharacter) @@ -26,7 +28,7 @@ namespace Barotrauma.Abilities { if (statusEffect.HasTargetType(StatusEffect.TargetType.UseTarget)) { - // currently used this to spawn items on the targeted character + // currently used to spawn items on the targeted character statusEffect.SetUser(targetCharacter); statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, targetCharacter, targetCharacter); } @@ -34,6 +36,10 @@ namespace Barotrauma.Abilities { targets.Clear(); targets.AddRange(statusEffect.GetNearbyTargets(targetCharacter.WorldPosition, targets)); + if (!nearbyCharactersAppliesToSelf) + { + targets.RemoveAll(c => c == Character); + } statusEffect.SetUser(Character); statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, targetCharacter, targets); } 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/CharacterAbilityGivePermanentStat.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGivePermanentStat.cs index 55da7e1cc..ea1a181a2 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGivePermanentStat.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGivePermanentStat.cs @@ -27,8 +27,7 @@ namespace Barotrauma.Abilities targetAllies = abilityElement.GetAttributeBool("targetallies", false); removeOnDeath = abilityElement.GetAttributeBool("removeondeath", true); removeAfterRound = abilityElement.GetAttributeBool("removeafterround", false); - giveOnAddingFirstTime = abilityElement.GetAttributeBool("giveonaddingfirsttime", false); - //maximumValue = abilityElement.GetAttributeFloat("maximumvalue", float.MaxValue); + giveOnAddingFirstTime = abilityElement.GetAttributeBool("giveonaddingfirsttime", characterAbilityGroup.AbilityEffectType == AbilityEffectType.None); } public override void InitializeAbility(bool addingFirstTime) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityIncreaseSkill.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityIncreaseSkill.cs index 830d4c9ce..59b532aaf 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityIncreaseSkill.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityIncreaseSkill.cs @@ -1,4 +1,5 @@ -using Microsoft.Xna.Framework; +using Barotrauma.Extensions; +using Microsoft.Xna.Framework; using System.Xml.Linq; namespace Barotrauma.Abilities @@ -7,13 +8,22 @@ namespace Barotrauma.Abilities { public override bool AppliesEffectOnIntervalUpdate => true; - private string skillIdentifier; - private float skillIncrease; + 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() @@ -35,7 +45,17 @@ namespace Barotrauma.Abilities private void ApplyEffectSpecific(Character character) { - character.Info?.IncreaseSkillLevel(skillIdentifier, skillIncrease, character.Position + Vector2.UnitY * 175.0f); + if (skillIdentifier.Equals("random")) + { + var skill = character.Info?.Job?.Skills?.GetRandom(); + if (skill == null) { return; } + character.Info?.IncreaseSkillLevel(skill.Identifier, skillIncrease, character.Position + Vector2.UnitY * 175.0f); + } + else + { + character.Info?.IncreaseSkillLevel(skillIdentifier, skillIncrease, character.Position + Vector2.UnitY * 175.0f); + } + } } } 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/CharacterAbilityModifyValue.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyValue.cs index 8c12ea122..59a203a7f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyValue.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyValue.cs @@ -4,8 +4,8 @@ namespace Barotrauma.Abilities { class CharacterAbilityModifyValue : CharacterAbility { - private float addedValue; - private float multiplyValue; + private readonly float addedValue; + private readonly float multiplyValue; public CharacterAbilityModifyValue(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityByTheBook.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityByTheBook.cs index de83bf9a9..8bfe07b9d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityByTheBook.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityByTheBook.cs @@ -6,12 +6,14 @@ namespace Barotrauma.Abilities { class CharacterAbilityByTheBook : CharacterAbility { - private int moneyAmount; - private int max; + 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); } @@ -28,6 +30,10 @@ namespace Barotrauma.Abilities 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/CharacterAbilityEnigmaMachine.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityEnigmaMachine.cs new file mode 100644 index 000000000..6b034d24b --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityEnigmaMachine.cs @@ -0,0 +1,43 @@ +using System; +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class CharacterAbilityEnigmaMachine : CharacterAbility + { + private readonly float addedValue; + private readonly float multiplyValue; + private readonly string[] tags; + private readonly int maxMultiplyCount; + + public CharacterAbilityEnigmaMachine(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/CharacterAbilityIndustrialRevolution.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityIndustrialRevolution.cs deleted file mode 100644 index 324bf919d..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityIndustrialRevolution.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Barotrauma.Items.Components; -using Microsoft.Xna.Framework; -using System.Collections.Generic; -using System.Xml.Linq; - -namespace Barotrauma.Abilities -{ - class CharacterAbilityIndustrialRevolution : CharacterAbility - { - float addedFabricationSpeed; - - public CharacterAbilityIndustrialRevolution(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) - { - addedFabricationSpeed = abilityElement.GetAttributeFloat("addedfabricationspeed", 0f); - } - - public override void UpdateCharacterAbility(bool conditionsMatched, float timeSinceLastUpdate) - { - if (conditionsMatched) - { - // not necessarily the cleanest or performant way, but at least this shouldn't break anything. - // must be done every frame in order to work. - if (Character.SelectedConstruction?.GetComponent() is Fabricator fabricator && fabricator.IsActive) - { - fabricator.FabricationSpeedMultiplier += addedFabricationSpeed; - } - } - } - } -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityTaskmaster.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityTaskmaster.cs deleted file mode 100644 index 2ae4b6256..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityTaskmaster.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System.Collections.Generic; -using System.Xml.Linq; - -namespace Barotrauma.Abilities -{ - class CharacterAbilityTaskmaster : CharacterAbility - { - private readonly List statusEffects; - private readonly List statusEffectsRemove; - - private Character lastCharacter; - - public CharacterAbilityTaskmaster(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) - { - statusEffects = CharacterAbilityGroup.ParseStatusEffects(CharacterTalent, abilityElement.GetChildElement("statuseffects")); - statusEffectsRemove = CharacterAbilityGroup.ParseStatusEffects(CharacterTalent, abilityElement.GetChildElement("statuseffectsremove")); - } - - protected override void ApplyEffect(AbilityObject abilityObject) - { - if ((abilityObject as IAbilityCharacter)?.Character is Character targetCharacter) - { - if (targetCharacter == Character) { return; } - - foreach (var statusEffect in statusEffectsRemove) - { - statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, Character, lastCharacter); - } - - foreach (var statusEffect in statusEffects) - { - statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, Character, targetCharacter); - } - - lastCharacter = targetCharacter; - } - } - } -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroup.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroup.cs index 1c5471f24..5ada7de4a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroup.cs @@ -14,6 +14,8 @@ namespace Barotrauma.Abilities // 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; @@ -24,8 +26,9 @@ namespace Barotrauma.Abilities // separate dictionaries for each type of characterability? protected readonly List characterAbilities = new List(); - public CharacterAbilityGroup(CharacterTalent characterTalent, XElement abilityElementGroup) + public CharacterAbilityGroup(AbilityEffectType abilityEffectType, CharacterTalent characterTalent, XElement abilityElementGroup) { + AbilityEffectType = abilityEffectType; CharacterTalent = characterTalent; Character = CharacterTalent.Character; maxTriggerCount = abilityElementGroup.GetAttributeInt("maxtriggercount", int.MaxValue); @@ -168,8 +171,7 @@ namespace Barotrauma.Abilities public static StatTypes ParseStatType(string statTypeString, string debugIdentifier) { - StatTypes statType; - if (!Enum.TryParse(statTypeString, true, out statType)) + if (!Enum.TryParse(statTypeString, true, out StatTypes statType)) { DebugConsole.ThrowError("Invalid stat type type \"" + statTypeString + "\" in CharacterTalent (" + debugIdentifier + ")"); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroupEffect.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroupEffect.cs index 55629172f..7249b69bf 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroupEffect.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroupEffect.cs @@ -8,7 +8,8 @@ namespace Barotrauma.Abilities { class CharacterAbilityGroupEffect : CharacterAbilityGroup { - public CharacterAbilityGroupEffect(CharacterTalent characterTalent, XElement abilityElementGroup) : base(characterTalent, abilityElementGroup) { } + public CharacterAbilityGroupEffect(AbilityEffectType abilityEffectType, CharacterTalent characterTalent, XElement abilityElementGroup) : + base(abilityEffectType, characterTalent, abilityElementGroup) { } public void CheckAbilityGroup(AbilityObject abilityObject) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroupInterval.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroupInterval.cs index 4e3d0896f..379b09f46 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroupInterval.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroupInterval.cs @@ -15,7 +15,8 @@ namespace Barotrauma.Abilities private float effectDelayTimer; - public CharacterAbilityGroupInterval(CharacterTalent characterTalent, XElement abilityElementGroup) : base(characterTalent, abilityElementGroup) + 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); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/CharacterTalent.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/CharacterTalent.cs index 5d8a1587b..703a3c852 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/CharacterTalent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/CharacterTalent.cs @@ -85,14 +85,13 @@ namespace Barotrauma // XML logic private void LoadAbilityGroupInterval(XElement abilityGroup) { - string name = abilityGroup.Name.ToString().ToLowerInvariant(); - characterAbilityGroupIntervals.Add(new CharacterAbilityGroupInterval(this, 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(this, abilityGroup), abilityEffectType); + AddAbilityGroupEffect(new CharacterAbilityGroupEffect(abilityEffectType, this, abilityGroup), abilityEffectType); } public void AddAbilityGroupEffect(CharacterAbilityGroupEffect characterAbilityGroup, AbilityEffectType abilityEffectType = AbilityEffectType.None) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/TalentTree.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/TalentTree.cs index 6483edaa1..744ffb132 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/TalentTree.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/TalentTree.cs @@ -1,5 +1,4 @@ -using Microsoft.Xna.Framework; -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Xml.Linq; @@ -8,13 +7,19 @@ 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(); - private static HashSet subtreeTalents = new HashSet(); - - private const string PlaceholderTalent = "placeholder"; public XElement ConfigElement { get; @@ -35,14 +40,13 @@ namespace Barotrauma foreach (XElement subTreeElement in element.GetChildElements("subtree")) { - TalentSubTrees.Add(new TalentSubTree(subTreeElement)); + 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)))) { - if (talent == PlaceholderTalent) { continue; } TalentPrefab talentPrefab = TalentPrefab.TalentPrefabs.Find(c => c.Identifier.Equals(talent, StringComparison.OrdinalIgnoreCase)); if (talentPrefab == null) { @@ -97,7 +101,7 @@ namespace Barotrauma } break; default: - DebugConsole.ThrowError($"Invalid XML root element: '{rootElement.Name.ToString()}' in {file.Path}"); + DebugConsole.ThrowError($"Invalid XML root element: '{rootElement.Name}' in {file.Path}"); break; } } @@ -117,10 +121,63 @@ namespace Barotrauma 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 (talentIdentifier == PlaceholderTalent) { return false; } if (character?.Info?.Job.Prefab == null) { return false; } if (character.Info.GetTotalTalentPoints() - selectedTalents.Count() <= 0) { return false; } @@ -173,12 +230,16 @@ namespace Barotrauma { 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)); @@ -196,6 +257,7 @@ namespace Barotrauma 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}\"."); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Enums.cs b/Barotrauma/BarotraumaShared/SharedSource/Enums.cs index be70e66b9..20acddd27 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Enums.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Enums.cs @@ -12,16 +12,16 @@ public enum ActionType { - Always, OnPicked, OnUse, OnSecondaryUse, - OnWearing, OnContaining, OnContained, OnNotContained, - OnActive, OnFailure, OnBroken, - OnFire, InWater, NotInWater, - OnImpact, - OnEating, - OnDamaged, - OnSevered, - OnProduceSpawned, - OnOpen, OnClose, + 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, OnDeath = OnBroken, OnSuccess, OnAbility, @@ -34,6 +34,7 @@ OnAttack, OnAttackResult, OnAttacked, + OnAttackedResult, OnGainSkillPoint, OnAllyGainSkillPoint, OnRepairComplete, @@ -57,7 +58,9 @@ OnGainMissionMoney, OnItemDeconstructed, OnItemDeconstructedMaterial, + OnItemDeconstructedRetainProbability, OnStopTinkering, + OnItemPicked, AfterSubmarineAttacked, } @@ -78,26 +81,37 @@ BuffDurationMultiplier, DebuffDurationMultiplier, MedicalItemEffectivenessMultiplier, + FlowResistance, // Combat AttackMultiplier, TeamAttackMultiplier, RangedAttackSpeed, TurretAttackSpeed, TurretPowerCostReduction, + TurretChargeSpeed, MeleeAttackSpeed, MeleeAttackMultiplier, + RangedAttackMultiplier, RangedSpreadReduction, // Utility RepairSpeed, DeconstructorSpeedMultiplier, TinkeringDuration, + RepairToolStructureRepairMultiplier, + RepairToolStructureDamageMultiplier, + RepairToolDeattachTimeMultiplier, + MaxRepairConditionMultiplier, + IncreaseFabricationQuality, + GeneticMaterialRefineBonus, + GeneticMaterialTaintedProbabilityReductionOnCombine, + SkillGainSpeed, // Misc ReputationGainMultiplier, MissionMoneyGainMultiplier, ExperienceGainMultiplier, MissionExperienceGainMultiplier, // these should be deprecated and moved to their own implementation, no sense making them share space with stat values - Coathor, + Coauthor, WarriorPoetMissionRuns, WarriorPoetEnemiesKilled, } @@ -113,7 +127,8 @@ CanTinkerFabricatorsAndDeconstructors, TinkeringPowersDevices, GainSkillPastMaximum, - RetainExperienceForNewCharacter + RetainExperienceForNewCharacter, + AllowSecondOrderedTarget, } } 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/PirateMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/PirateMission.cs index 6d7319de6..2374eefea 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/PirateMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/PirateMission.cs @@ -382,11 +382,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..e2b586e73 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/SalvageMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/SalvageMission.cs @@ -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/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/Items/Components/GeneticMaterial.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/GeneticMaterial.cs index 60b304478..3a1381b97 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/GeneticMaterial.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/GeneticMaterial.cs @@ -60,7 +60,6 @@ namespace Barotrauma.Items.Components } } - public GeneticMaterial(Item item, XElement element) : base(item, element) { @@ -85,7 +84,7 @@ namespace Barotrauma.Items.Components public bool CanBeCombinedWith(GeneticMaterial otherGeneticMaterial) { - return !tainted && otherGeneticMaterial != null && !otherGeneticMaterial.tainted; + return !tainted && otherGeneticMaterial != null && !otherGeneticMaterial.tainted && item.AllowDeconstruct && otherGeneticMaterial.item.AllowDeconstruct; } public override void Equip(Character character) @@ -147,9 +146,12 @@ namespace Barotrauma.Items.Components public bool Combine(GeneticMaterial otherGeneticMaterial, Character user) { if (!CanBeCombinedWith(otherGeneticMaterial)) { return false; } + + float conditionIncrease = Rand.Range(ConditionIncreaseOnCombineMin, ConditionIncreaseOnCombineMax); + conditionIncrease *= 1.0f + user.GetStatValue(StatTypes.GeneticMaterialRefineBonus); if (item.Prefab == otherGeneticMaterial.item.Prefab) { - item.Condition = Math.Max(item.Condition, otherGeneticMaterial.item.Condition) + Rand.Range(ConditionIncreaseOnCombineMin, ConditionIncreaseOnCombineMax); + item.Condition = Math.Max(item.Condition, otherGeneticMaterial.item.Condition) + conditionIncrease; float taintedProbability = GetTaintedProbabilityOnRefine(user); if (taintedProbability >= Rand.Range(0.0f, 1.0f)) { @@ -160,9 +162,14 @@ namespace Barotrauma.Items.Components else { item.Condition = otherGeneticMaterial.Item.Condition = - (item.Condition + otherGeneticMaterial.Item.Condition) / 2.0f + Rand.Range(ConditionIncreaseOnCombineMin, ConditionIncreaseOnCombineMax); + (item.Condition + otherGeneticMaterial.Item.Condition) / 2.0f + conditionIncrease; item.OwnInventory?.TryPutItem(otherGeneticMaterial.Item, user: null); - MakeTainted(); + item.AllowDeconstruct = false; + otherGeneticMaterial.Item.AllowDeconstruct = false; + if (GetTaintedProbabilityOnCombine(user) >= Rand.Range(0.0f, 1.0f)) + { + MakeTainted(); + } return false; } } @@ -172,7 +179,14 @@ namespace Barotrauma.Items.Components 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 probability; + 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() 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/RepairTool.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RepairTool.cs index ddf06b33f..1ed8b6edc 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)) * item.GetQualityModifier(Quality.StatType.RepairToolDeattachTimeMultiplier); + levelResource.DeattachTimer += addedDetachTime; #if CLIENT Character.Controlled?.UpdateHUDProgressBar( this, diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs index ba479089d..f97b2f01f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs @@ -61,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 @@ -86,15 +86,9 @@ 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; } @@ -329,11 +323,24 @@ namespace Barotrauma.Items.Components { 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) { 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(); public override void Update(float deltaTime, Camera cam) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Deconstructor.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Deconstructor.cs index 18996185f..158af353e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Deconstructor.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Deconstructor.cs @@ -256,6 +256,17 @@ namespace Barotrauma.Items.Components } } + if (user != null && !user.Removed) + { + var deconstructItemRetainProbability = new AbilityValueItem(0f, targetItem.Prefab); + user.CheckTalents(AbilityEffectType.OnItemDeconstructedRetainProbability, deconstructItemRetainProbability); + + if (deconstructItemRetainProbability.Value > Rand.Range(0f, 1f, Rand.RandSync.Unsynced)) + { + allowRemove = false; + } + } + if (targetItem.AllowDeconstruct && allowRemove) { //drop all items that are inside the deconstructed item diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs index d47c21d23..f5cb14ba8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs @@ -24,12 +24,6 @@ namespace Barotrauma.Items.Components private Character user; - public float FabricationSpeedMultiplier - { - get; - set; - } - private ItemContainer inputContainer, outputContainer; [Serialize(1.0f, true)] @@ -249,7 +243,6 @@ namespace Barotrauma.Items.Components var availableIngredients = GetAvailableIngredients(); if (fabricatedItem == null || !CanBeFabricated(fabricatedItem, availableIngredients, user)) { - FabricationSpeedMultiplier = 1f; CancelFabricating(); return; } @@ -286,8 +279,7 @@ namespace Barotrauma.Items.Components if (powerConsumption <= 0) { Voltage = 1.0f; } - timeUntilReady -= deltaTime * Math.Min(Voltage, 1.0f) * FabricationSpeedMultiplier; - FabricationSpeedMultiplier = 1f; + timeUntilReady -= deltaTime * Math.Min(Voltage, 1.0f); UpdateRequiredTimeProjSpecific(); @@ -328,13 +320,21 @@ namespace Barotrauma.Items.Components var fabricationValueItem = new AbilityValueItem(fabricatedItem.Amount, fabricatedItem.TargetItem); - if (user != null) + 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); + + float floatQuality = 0.0f; + foreach (string tag in fabricatedItem.TargetItem.Tags) + { + floatQuality += user.Info.GetSavedStatValue(StatTypes.IncreaseFabricationQuality, tag); + } + quality = (int)floatQuality; } var tempUser = user; @@ -343,12 +343,20 @@ namespace Barotrauma.Items.Components if (i < amountFittingContainer) { Entity.Spawner.AddToSpawnQueue(fabricatedItem.TargetItem, outputContainer.Inventory, fabricatedItem.TargetItem.Health * fabricatedItem.OutCondition, - onSpawned: (Item spawnedItem) => { onItemSpawned(spawnedItem, tempUser); }); + onSpawned: (Item spawnedItem) => + { + onItemSpawned(spawnedItem, tempUser); + spawnedItem.Quality = quality; + }); } else { Entity.Spawner.AddToSpawnQueue(fabricatedItem.TargetItem, item.Position, item.Submarine, fabricatedItem.TargetItem.Health * fabricatedItem.OutCondition, - onSpawned: (Item spawnedItem) => { onItemSpawned(spawnedItem, tempUser); }); + onSpawned: (Item spawnedItem) => + { + onItemSpawned(spawnedItem, tempUser); + spawnedItem.Quality = quality; + }); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Pump.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Pump.cs index 7b00dd94e..bacb23c6e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Pump.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Pump.cs @@ -116,6 +116,8 @@ namespace Barotrauma.Items.Components 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/Quality.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Quality.cs new file mode 100644 index 000000000..3bfab45f0 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Quality.cs @@ -0,0 +1,72 @@ +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 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, false)] + public int QualityLevel + { + get { return qualityLevel; } + set { qualityLevel = MathHelper.Clamp(value, 0, MaxQuality); } + } + + 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 index 783253c89..1903a74c0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/RemoteController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/RemoteController.cs @@ -35,6 +35,7 @@ namespace Barotrauma.Items.Components public RemoteController(Item item, XElement element) : base(item, element) { + DrawHudWhenEquipped = false; } public override bool Select(Character character) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs index 65a531190..5fbb40733 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; @@ -182,6 +182,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; } @@ -217,6 +221,11 @@ namespace Barotrauma.Items.Components { 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; } @@ -243,7 +252,7 @@ namespace Barotrauma.Items.Components } return true; - Item GetBestRepairItem(Character character) + static Item GetBestRepairItem(Character character) { return character.HeldItems.OrderByDescending(i => i.Prefab.AddedRepairSpeedMultiplier).FirstOrDefault(); } @@ -386,6 +395,9 @@ 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 = 1 + CurrentFixer.GetStatValue(StatTypes.MaxRepairConditionMultiplier); if (currentFixerAction == FixActions.Repair) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs index fbf1e9961..759893105 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs @@ -383,6 +383,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; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Wearable.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Wearable.cs index be1b859b6..5c3ccc0b8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Wearable.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Wearable.cs @@ -285,7 +285,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) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs index 6e6cfb9a0..119d7ddd5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs @@ -97,13 +97,15 @@ 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; } @@ -447,7 +449,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; @@ -465,12 +467,18 @@ 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, //we'll just use NaN (which does nothing) and set the default value in the constructor/load [Serialize(float.NaN, false), Editable] @@ -618,6 +626,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 @@ -933,6 +956,8 @@ namespace Barotrauma ownInventory = itemContainer.Inventory; } + qualityComponent = GetComponent(); + InitProjSpecific(); if (callOnItemLoaded) @@ -1122,6 +1147,11 @@ 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) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/ItemInventory.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/ItemInventory.cs index bcefbec04..954ab96af 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/ItemInventory.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/ItemInventory.cs @@ -47,14 +47,14 @@ namespace Barotrauma { if (ItemOwnsSelf(item)) { return false; } if (i < 0 || i >= slots.Length) { return false; } - if (!container.CanBeContained(item)) { return false; } + 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) { if (i < 0 || i >= slots.Length) { return false; } - if (!container.CanBeContained(itemPrefab)) { return false; } + if (!container.CanBeContained(itemPrefab, i)) { return false; } return itemPrefab != null && slots[i].CanBePut(itemPrefab, condition) && slots[i].ItemCount < container.GetMaxStackSize(i); } @@ -62,7 +62,7 @@ namespace Barotrauma { if (itemPrefab == null) { return 0; } if (i < 0 || i >= slots.Length) { return 0; } - if (!container.CanBeContained(itemPrefab)) { return 0; } + if (!container.CanBeContained(itemPrefab, i)) { return 0; } return slots[i].HowManyCanBePut(itemPrefab, maxStackSize: Math.Min(itemPrefab.MaxStackSize, container.GetMaxStackSize(i)), condition); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs index 67a117dde..54f1daca4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs @@ -370,6 +370,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)] @@ -1164,6 +1167,8 @@ namespace Barotrauma DefaultPrice ??= new PriceInfo(GetMinPrice() ?? 0, false); } + HideConditionInTooltip = element.GetAttributeBool("hideconditionintooltip", HideConditionBar); + //backwards compatibility if (categoryStr.Equals("Thalamus", StringComparison.OrdinalIgnoreCase)) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Explosion.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Explosion.cs index 4b657de2a..3d0f8ee77 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Explosion.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Explosion.cs @@ -128,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; @@ -142,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); } @@ -211,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) @@ -240,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; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Serialization/XMLExtensions.cs b/Barotrauma/BarotraumaShared/SharedSource/Serialization/XMLExtensions.cs index 0e594bbe7..21fa03573 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Serialization/XMLExtensions.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Serialization/XMLExtensions.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.IO; using System.Linq; using System.Text; using System.Xml; @@ -8,6 +9,7 @@ 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 { @@ -18,11 +20,12 @@ namespace Barotrauma 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 +55,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 +82,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 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/Utils/SafeIO.cs b/Barotrauma/BarotraumaShared/SharedSource/Utils/SafeIO.cs index a2b4ba5cd..aef87c736 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Utils/SafeIO.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Utils/SafeIO.cs @@ -315,7 +315,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) { @@ -331,10 +335,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/changelog.txt b/Barotrauma/BarotraumaShared/changelog.txt index babb03713..9a8ee1ea6 100644 --- a/Barotrauma/BarotraumaShared/changelog.txt +++ b/Barotrauma/BarotraumaShared/changelog.txt @@ -1,3 +1,32 @@ +--------------------------------------------------------------------------------------------------------- +v0.1500.3.0 +--------------------------------------------------------------------------------------------------------- + +Additions and changes: +- More talents and talent-related items (all talent trees now functional and most of the talents implemented). + +Changes: +- Ignore hidden afflictions when determining treatment suggestions to show in the health interface. +- Visualize leaks on the status monitor's hull condition tab (unstable only). +- Added "condition_out" output to outpost O2 generator (unstable only). + +Fixes: +- Fixed crashing when reloading sprites or resetting to prefab in the sub editor. +- Fixed ability to combine unidentified genetic materials with other genetic materials (unstable only). +- Organ damage doesn't cause concussions (unstable only). +- Fixed talent menu being accessible if you leave it open and switch to a game mode where it shouldn't be accessible (unstable only). +- Fixed ability to contain items other than batteries in cargo scooter's battery slot (unstable only). +- Damaging the mudraptor beak given by mudraptor genes damages the head instead of torso, added damage protection to the beak (unstable only). +- Items that are set to be hidden in menus aren't shown in the status monitor's item finder (unstable only). +- Fixed status monitor's item finder not showing wearable items (unstable only). +- Fixed "in use by xxxx" warning being always visible when using a Reactor PDA (unstable only). +- Fixed Reactor PDA rendering over the command interface (unstable only). +- Fixed assault rifle crosshair being drawn when it's in the bag slot (unstable only). +- Fixed equip buttons not being drawn on equipped items that can only be put to other equip slots, but not on the non-limb slots (e.g. assault rifle). + +Modding: +- Option to make property conditionals target contained items using the attribute targetcontaineditem="true". + --------------------------------------------------------------------------------------------------------- v0.1500.2.0 --------------------------------------------------------------------------------------------------------- diff --git a/Barotrauma/BarotraumaShared/serversettings.xml b/Barotrauma/BarotraumaShared/serversettings.xml index 38cae3c14..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" From 3043a9a7bc61227162584c48da42b3eb7e0210a3 Mon Sep 17 00:00:00 2001 From: Markus Isberg <3e849f2e5c@pm.me> Date: Thu, 23 Sep 2021 21:29:31 +0900 Subject: [PATCH 05/12] Unstable 0.1500.4.0 (Shrek edition) --- .../ClientSource/Characters/CharacterInfo.cs | 484 ++++++++++- .../Characters/Health/CharacterHealth.cs | 105 ++- .../ClientSource/Characters/Limb.cs | 165 +++- .../Events/Missions/MineralMission.cs | 10 +- .../BarotraumaClient/ClientSource/GUI/GUI.cs | 19 +- .../ClientSource/GUI/GUIDropDown.cs | 6 +- .../ClientSource/GUI/GUIStyle.cs | 2 +- .../ClientSource/GUI/TabMenu.cs | 167 +++- .../BarotraumaClient/ClientSource/GameMain.cs | 1 - .../GameModes/MultiPlayerCampaign.cs | 2 +- .../ClientSource/GameSession/GameSession.cs | 60 +- .../ClientSource/Items/CharacterInventory.cs | 2 +- .../Items/Components/GeneticMaterial.cs | 4 +- .../Components/Machines/Deconstructor.cs | 2 +- .../Items/Components/Machines/Fabricator.cs | 6 +- .../Items/Components/Machines/MiniMap.cs | 65 +- .../ClientSource/Items/Components/Quality.cs | 21 + .../Items/Components/Repairable.cs | 9 +- .../ClientSource/Items/Inventory.cs | 9 +- .../ClientSource/Map/Map/Map.cs | 4 +- .../ClientSource/Networking/GameClient.cs | 3 + .../CampaignSetupUI/CampaignSetupUI.cs | 52 ++ .../MultiPlayerCampaignSetupUI.cs | 521 ++++++++++++ .../SinglePlayerCampaignSetupUI.cs} | 757 ++++++++---------- .../CharacterEditor/CharacterEditorScreen.cs | 47 +- .../Screens/CharacterEditor/Wizard.cs | 14 +- .../ClientSource/Screens/GameScreen.cs | 26 +- .../ClientSource/Screens/MainMenuScreen.cs | 49 +- .../ClientSource/Screens/NetLobbyScreen.cs | 555 ++++--------- .../ClientSource/Sprite/Sprite.cs | 12 +- .../Content/Effects/thresholdtint.xnb | Bin 0 -> 1501 bytes .../Content/Effects/thresholdtint_opengl.xnb | Bin 0 -> 1394 bytes .../BarotraumaClient/LinuxClient.csproj | 2 +- Barotrauma/BarotraumaClient/MacClient.csproj | 2 +- .../BarotraumaClient/Shaders/Content.mgcb | 6 + .../Shaders/Content_opengl.mgcb | 6 + .../BarotraumaClient/Shaders/thresholdtint.fx | 32 + .../Shaders/thresholdtint_opengl.fx | 32 + .../BarotraumaClient/WindowsClient.csproj | 2 +- .../BarotraumaServer/LinuxServer.csproj | 2 +- Barotrauma/BarotraumaServer/MacServer.csproj | 2 +- .../ServerSource/Characters/CharacterInfo.cs | 11 +- .../Events/Missions/MineralMission.cs | 6 +- .../Items/Components/Repairable.cs | 1 + .../ServerSource/Networking/GameServer.cs | 9 +- .../BarotraumaServer/WindowsServer.csproj | 2 +- .../Data/ContentPackages/Vanilla 0.9.xml | 1 + .../AI/Objectives/AIObjectiveManager.cs | 2 +- .../AI/Objectives/AIObjectiveRescue.cs | 2 +- .../AI/Objectives/AIObjectiveRescueAll.cs | 2 +- .../AI/Objectives/AIObjectiveReturn.cs | 8 +- .../SharedSource/Characters/AI/PathFinder.cs | 67 +- .../Characters/Animation/AnimController.cs | 28 +- .../Animation/FishAnimController.cs | 6 +- .../Animation/HumanoidAnimController.cs | 182 +++-- .../SharedSource/Characters/Character.cs | 16 +- .../SharedSource/Characters/CharacterInfo.cs | 357 +++++++-- .../Health/Afflictions/AfflictionPrefab.cs | 14 + .../Characters/Health/CharacterHealth.cs | 4 +- .../SharedSource/Characters/Jobs/JobPrefab.cs | 3 +- .../SharedSource/Characters/Limb.cs | 2 +- .../Params/Animation/AnimationParams.cs | 18 +- .../Params/Animation/FishAnimations.cs | 20 - .../Params/Animation/HumanoidAnimations.cs | 82 +- .../Params/Ragdoll/RagdollParams.cs | 10 +- .../AbilityConditionals/AbilityCondition.cs | 3 + .../AbilityConditionAffliction.cs | 29 + .../AbilityConditionLocation.cs | 41 + .../Talents/Abilities/AbilityInterfaces.cs | 5 + .../Talents/Abilities/AbilityObjects.cs | 34 +- ...racterAbilityApplyStatusEffectsToAllies.cs | 9 +- .../Abilities/CharacterAbilityGiveMoney.cs | 5 +- .../CharacterAbilityGivePermanentStat.cs | 6 +- .../CharacterAbilityGiveResistance.cs | 2 +- .../CharacterAbilityModifyStatToLevel.cs | 35 + .../CharacterAbilitySpawnItemsToContainer.cs | 9 +- .../Abilities/CharacterAbilityUnlockTree.cs | 30 + ...ine.cs => CharacterAbilityAtmosMachine.cs} | 4 +- .../CharacterAbilityStonewall.cs | 42 - .../CharacterAbilityGroupEffect.cs | 1 + .../CharacterAbilityGroupInterval.cs | 4 + .../Characters/Talents/TalentTree.cs | 2 +- .../BarotraumaShared/SharedSource/Enums.cs | 9 +- .../SharedSource/Events/EventManager.cs | 1 + .../Events/Missions/MineralMission.cs | 73 +- .../Events/Missions/PirateMission.cs | 6 +- .../Extensions/ColorExtensions.cs | 6 +- .../GameSession/GameModes/CampaignMode.cs | 11 +- .../SharedSource/GameSession/GameSession.cs | 5 +- .../SharedSource/GameSettings.cs | 236 +----- .../Items/Components/GeneticMaterial.cs | 34 +- .../Items/Components/Holdable/Holdable.cs | 5 + .../Items/Components/Holdable/MeleeWeapon.cs | 33 +- .../Items/Components/Holdable/RangedWeapon.cs | 3 +- .../Items/Components/Holdable/RepairTool.cs | 2 +- .../Components/Machines/Deconstructor.cs | 31 +- .../Items/Components/Machines/Engine.cs | 4 +- .../Items/Components/Machines/Fabricator.cs | 37 +- .../Items/Components/Machines/Pump.cs | 4 +- .../Items/Components/Machines/Steering.cs | 2 +- .../Items/Components/Projectile.cs | 7 +- .../Items/Components/Repairable.cs | 11 +- .../Components/Signal/RegExFindComponent.cs | 1 + .../SharedSource/Items/Components/Turret.cs | 34 +- .../SharedSource/Items/Components/Wearable.cs | 26 +- .../SharedSource/Items/ItemPrefab.cs | 2 + .../SharedSource/Items/RelatedItem.cs | 3 +- .../SharedSource/Map/Explosion.cs | 2 +- .../SharedSource/Map/Levels/Level.cs | 8 + .../SharedSource/Map/Map/Location.cs | 28 +- .../SharedSource/Map/Map/Map.cs | 11 +- .../SharedSource/Map/PriceInfo.cs | 14 +- .../Primitives/Message/IReadMessage.cs | 2 + .../Primitives/Message/IWriteMessage.cs | 2 + .../Networking/Primitives/Message/Message.cs | 79 ++ .../Serialization/SerializableProperty.cs | 2 +- .../SharedSource/Sprite/Sprite.cs | 2 +- .../SharedSource/TextManager.cs | 20 +- .../SharedSource/Utils/SafeIO.cs | 23 +- Barotrauma/BarotraumaShared/TintTest.png | Bin 0 -> 91346 bytes Barotrauma/BarotraumaShared/changelog.txt | 35 + .../Graphics/SpriteBatch.cs | 191 +++-- .../Graphics/SpriteBatchItem.cs | 3 +- .../Graphics/SpriteBatcher.cs | 32 +- 124 files changed, 3571 insertions(+), 1848 deletions(-) create mode 100644 Barotrauma/BarotraumaClient/ClientSource/Items/Components/Quality.cs create mode 100644 Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI/CampaignSetupUI.cs create mode 100644 Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI/MultiPlayerCampaignSetupUI.cs rename Barotrauma/BarotraumaClient/ClientSource/Screens/{CampaignSetupUI.cs => CampaignSetupUI/SinglePlayerCampaignSetupUI.cs} (51%) 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/thresholdtint.fx create mode 100644 Barotrauma/BarotraumaClient/Shaders/thresholdtint_opengl.fx create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionAffliction.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionLocation.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyStatToLevel.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityUnlockTree.cs rename Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/{CharacterAbilityEnigmaMachine.cs => CharacterAbilityAtmosMachine.cs} (86%) delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityStonewall.cs rename Barotrauma/{BarotraumaClient/ClientSource => BarotraumaShared/SharedSource}/Extensions/ColorExtensions.cs (69%) create mode 100644 Barotrauma/BarotraumaShared/TintTest.png diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterInfo.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterInfo.cs index 893fb4761..2ab8174b8 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterInfo.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterInfo.cs @@ -23,12 +23,30 @@ namespace Barotrauma private Sprite disguisedJobIcon; private Color disguisedJobColor; + private Sprite tintMask; + private float tintHighlightThreshold; + private float tintHighlightMultiplier; + public static void Init() { infoAreaPortraitBG = GUI.Style.GetComponentStyle("InfoAreaPortraitBG")?.GetDefaultSprite(); 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) { @@ -458,6 +476,7 @@ namespace Barotrauma } else { + //TODO: disguise skin and hair colors sheetIndex = disguisedSheetIndex; portraitToDraw = disguisedPortrait; attachmentsToDraw = disguisedAttachmentSprites; @@ -465,22 +484,74 @@ namespace Barotrauma 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), 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) + { + switch (attachment.Type) + { + case WearableType.Hair: + return HairColor; + case WearableType.Beard: + case WearableType.Moustache: + return FacialHairColor; + default: + return Color.White; } } @@ -489,34 +560,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)); 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; @@ -527,7 +592,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) { @@ -544,7 +609,7 @@ namespace Barotrauma attachment.Sprite.SourceRect = head.SourceRect; } } - Vector2 origin = attachment.Sprite.Origin; + Vector2 origin; if (attachment.InheritOrigin) { origin = head.Origin; @@ -559,7 +624,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) @@ -574,6 +639,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(); @@ -599,6 +667,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) @@ -627,5 +698,384 @@ namespace Barotrauma 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 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 content = + new GUIListBox( + new RectTransform(new Vector2(contentWidth, 1.0f), parentComponent.RectTransform, + Anchor.CenterLeft)) + { CanBeFocused = false, CanTakeKeyBoardFocus = false } + .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.2f), 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(); + + 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; + } + } + + 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 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); + dropdown.OnSelected = (component, color) => + { + 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, + CanBeFocused = true + }; + var colorElement = + new GUIFrame( + new RectTransform(Vector2.One * 0.75f, optionElement.RectTransform, Anchor.Center, + scaleBasis: ScaleBasis.Smallest), + style: null) + { + Color = option, + HoverColor = option, + OutlineColor = Color.Lerp(Color.Black, option, 0.5f), + CanBeFocused = false + }; + } + } + + 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); + } + + private bool OpenHeadSelection(GUIButton button, object userData) + { + Gender selectedGender = (Gender)userData; + if (HeadSelectionList != null) + { + HeadSelectionList.Visible = true; + foreach (GUIComponent child in HeadSelectionList.Content.Children) + { + child.Visible = (Gender)child.UserData == selectedGender; + child.Children.ForEach(c => + c.Visible = ((Tuple)c.UserData).Item1 == selectedGender); + } + + return true; + } + + 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) + }); + + 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 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; + } + + 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.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/Health/CharacterHealth.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs index 2e648839a..78fa27ccf 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs @@ -601,8 +601,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; @@ -1131,6 +1136,8 @@ 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) { if (selectedLimb == null) @@ -1139,45 +1146,33 @@ namespace Barotrauma 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); + UpdateAfflictionInfos(displayedAfflictions.Select(d => d.affliction)); } private void CreateAfflictionInfos(IEnumerable afflictions) { afflictionIconContainer.ClearChildren(); - 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; - } - } + displayedAfflictions.Clear(); Affliction mostSevereAffliction = SortAfflictionsBySeverity(afflictions).FirstOrDefault(); GUIButton buttonToSelect = null; foreach (Affliction affliction in afflictions) { + displayedAfflictions.Add((affliction, affliction.Strength)); + var child = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.3f), afflictionIconContainer.Content.RectTransform, Anchor.TopCenter)) { Stretch = true, @@ -1233,6 +1228,39 @@ namespace Barotrauma child.Recalculate(); } + buttonToSelect?.OnClicked(buttonToSelect, "selectaffliction"); + 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) @@ -1248,10 +1276,6 @@ namespace Barotrauma recommendedTreatmentContainer.AutoHideScrollBar = true; } - buttonToSelect?.OnClicked(buttonToSelect, "selectaffliction"); - - afflictionIconContainer.RecalculateChildren(); - List> treatmentSuitabilities = treatmentSuitability.OrderByDescending(t => t.Value).ToList(); int count = 0; @@ -1286,7 +1310,7 @@ namespace Barotrauma new GUIImage(new RectTransform(Vector2.One, innerFrame.RectTransform, Anchor.Center), style: "TalentBackgroundGlow") { CanBeFocused = false, - Color = Color.White * 0.7f, + Color = GUI.Style.Green, HoverColor = Color.White, PressedColor = Color.DarkGray, SelectedColor = Color.Transparent, @@ -1304,6 +1328,12 @@ namespace Barotrauma SelectedColor = itemColor, DisabledColor = itemColor * 0.8f }; + + if (item == prevHighlightedItem) + { + innerFrame.State = GUIComponent.ComponentState.Hover; + innerFrame.Children.ForEach(c => c.State = GUIComponent.ComponentState.Hover); + } } recommendedTreatmentContainer.RecalculateChildren(); @@ -1315,6 +1345,19 @@ namespace Barotrauma int dmgPerSecond = Math.Sign(second.DamagePerSecond - first.DamagePerSecond); return dmgPerSecond != 0 ? dmgPerSecond : Math.Sign(second.Strength - first.Strength); }); + + 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) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Limb.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Limb.cs index c9c4d7300..d221c34b7 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Limb.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Limb.cs @@ -120,6 +120,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; @@ -273,6 +282,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()) @@ -308,6 +318,22 @@ namespace Barotrauma 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() @@ -449,20 +475,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 +498,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; @@ -638,13 +671,24 @@ namespace Barotrauma 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); + } + } + 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 +711,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) { @@ -698,7 +744,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) @@ -770,15 +842,36 @@ 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; } @@ -786,7 +879,7 @@ namespace Barotrauma foreach (WearableSprite wearable in WearingItems) { if (onlyDrawable != null && onlyDrawable != wearable) 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; } @@ -936,7 +1029,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 +1048,7 @@ namespace Barotrauma } } - Vector2 origin = wearable.Sprite.Origin; + Vector2 origin; if (wearable.InheritOrigin) { origin = sprite.Origin; @@ -986,7 +1079,7 @@ 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; @@ -997,15 +1090,38 @@ namespace Barotrauma } wearableColor = wearableItemComponent.Item.GetSpriteColor(); } - float textureScale = wearable.InheritTextureScale ? TextureScale : wearable.Scale; - + 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; - - 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, rotation, - Scale * textureScale, spriteEffect, depth); + 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) @@ -1056,6 +1172,9 @@ namespace Barotrauma HerpesSprite?.Sprite.Remove(); HerpesSprite = null; + + TintMask?.Remove(); + TintMask = null; } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/MineralMission.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/MineralMission.cs index 61e23e9a5..66eb75c8b 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/MineralMission.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/MineralMission.cs @@ -29,7 +29,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 +41,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 +66,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/GUI/GUI.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs index e3141a7b8..a8f8dd950 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; @@ -862,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)) @@ -1057,12 +1057,15 @@ 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; + } } } 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/GUIStyle.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIStyle.cs index 3a6537aa0..f1fd031ff 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; diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs index 803f2537c..d62df1090 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs @@ -19,7 +19,7 @@ namespace Barotrauma private static UISprite spectateIcon, disconnectedIcon; private static Sprite ownerIcon, moderatorIcon; - public enum InfoFrameTab { Crew, Mission, Reputation, MyCharacter, Traitor, Submarine, Talents }; + public enum InfoFrameTab { Crew, Mission, Reputation, Traitor, Submarine, Talents }; public static InfoFrameTab selectedTab; private GUIFrame infoFrame, contentFrame; @@ -34,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 @@ -134,6 +136,13 @@ namespace Barotrauma public void Update() { + GameSession.UpdateTalentNotificationIndicator(talentPointNotification); + if (Character.Controlled is { } controlled && talentResetButton != null && talentApplyButton != null) + { + int talentCount = selectedTalents.Count - controlled.Info.UnlockedTalents.Count; + talentResetButton.Enabled = talentApplyButton.Enabled = talentCount > 0; + } + if (selectedTab != InfoFrameTab.Crew) return; if (linkedGUIList == null) return; @@ -183,16 +192,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) @@ -243,6 +244,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 { @@ -255,20 +267,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"); - } - - var talentsButton = createTabButton(InfoFrameTab.Talents, "tabmenu.talents"); - talentsButton.OnAddedToGUIUpdateList += (GUIComponent component) => - { - talentsButton.Enabled = Character.Controlled?.Info != null && (GameMain.GameSession?.Campaign != null || Screen.Selected == GameMain.TestScreen || GameMain.GameSession.GameMode is TestGameMode); + 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) @@ -300,10 +309,6 @@ 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; @@ -1188,6 +1193,11 @@ namespace Barotrauma 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") }, @@ -1219,9 +1229,22 @@ namespace Barotrauma 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(0.9f), talentFrameContent.RectTransform, Anchor.Center), style: null); + GUIFrame paddedTalentFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.9f), talentFrameContent.RectTransform, Anchor.Center), style: null); - if (controlledCharacter.Info == 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; @@ -1229,7 +1252,7 @@ namespace Barotrauma selectedTalents = controlledCharacter.Info.UnlockedTalents.ToList(); - GUILayoutGroup talentFrameLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f), paddedTalentFrame.RectTransform, anchor: Anchor.Center), childAnchor: Anchor.TopCenter) + GUILayoutGroup talentFrameLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f), talentFrameMain.RectTransform, anchor: Anchor.Center), childAnchor: Anchor.TopCenter) { AbsoluteSpacing = GUI.IntScale(5) }; @@ -1241,11 +1264,11 @@ namespace Barotrauma new GUICustomComponent(new RectTransform(new Vector2(0.25f, 1f), talentInfoLayoutGroup.RectTransform), onDraw: (batch, component) => { - float posY = component.Rect.Bottom - component.Rect.Width; + 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.375f, 1f), talentInfoLayoutGroup.RectTransform)); + 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 }; @@ -1260,7 +1283,56 @@ namespace Barotrauma GUITextBlock traitBlock = new GUITextBlock(new RectTransform(Vector2.One, nameLayout.RectTransform), traitString, font: GUI.SmallFont); traitBlock.RectTransform.NonScaledSize = traitSize.Pad(traitBlock.Padding).ToPoint(); - GUILayoutGroup skillLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.375f, 1f), talentInfoLayoutGroup.RectTransform)) { Stretch = true }; + if (!(GameMain.NetworkMember is null)) + { + GUIButton newCharacterBox = new GUIButton(new RectTransform(Vector2.One, nameLayout.RectTransform, Anchor.BottomCenter), 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("close")) + { + OnClicked = (button, o) => + { + characterSettingsFrame!.Visible = false; + talentFrameMain.Visible = true; + return true; + } + }; + } + } + + 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); @@ -1276,6 +1348,7 @@ namespace Barotrauma 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); @@ -1285,7 +1358,7 @@ namespace Barotrauma 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"); - new GUITextBlock(new RectTransform(Vector2.One, subTreeTitleBackground.RectTransform, anchor: Anchor.TopCenter), subTree.DisplayName, font: GUI.LargeFont, textAlignment: Alignment.Center); + 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++) { @@ -1296,7 +1369,7 @@ namespace Barotrauma 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.25f), talentOptionFrame.RectTransform, anchor: Anchor.BottomRight, scaleBasis: ScaleBasis.BothHeight), style: null) + 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 }; @@ -1316,10 +1389,12 @@ namespace Barotrauma { GUIFrame talentFrame = new GUIFrame(new RectTransform(Vector2.One, talentOptionLayoutGroup.RectTransform), style: null) { - CanBeFocused = false, + CanBeFocused = false }; - GUIButton talentButton = new GUIButton(new RectTransform(Vector2.One, talentFrame.RectTransform, anchor: Anchor.Center), style: null) + 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, @@ -1361,7 +1436,7 @@ namespace Barotrauma GUIComponent iconImage; if (talent.Icon is null) { - iconImage = new GUITextBlock(new RectTransform(Vector2.One, talentButton.RectTransform, anchor: Anchor.Center, scaleBasis: ScaleBasis.BothHeight), text: "???", font: GUI.LargeFont, textAlignment: Alignment.Center, style: 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, @@ -1387,10 +1462,11 @@ namespace Barotrauma } } } + 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.775f, 1f), talentBottomFrame.RectTransform)); + 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), @@ -1399,19 +1475,22 @@ namespace Barotrauma IsHorizontal = true }; - experienceText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), experienceBarFrame.RectTransform, anchor: Anchor.Center), "", font: GUI.Font, textAlignment: Alignment.CenterRight); - - talentPointText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), experienceLayout.RectTransform, anchor: Anchor.Center), "", font: GUI.SubHeadingFont, parseRichText: true, textAlignment: Alignment.CenterRight); - - new GUIButton(new RectTransform(new Vector2(0.1f, 1f), talentBottomFrame.RectTransform), text: TextManager.Get("reset"), style: "GUICharacterInfoButton") + experienceText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), experienceBarFrame.RectTransform, anchor: Anchor.Center), "", font: GUI.Font, textAlignment: Alignment.CenterRight) { - OnClicked = ResetTalentSelection, + Shadow = true }; - new GUIButton(new RectTransform(new Vector2(0.1f, 1f), talentBottomFrame.RectTransform), text: TextManager.Get("applysettingsbutton"), style: "GUICharacterInfoButton") + 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(); } @@ -1419,11 +1498,12 @@ namespace Barotrauma 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 }; - new GUITextBlock(new RectTransform(new Vector2(0.7f, 1f), skillContainer.RectTransform), TextManager.Get($"skillname.{skill.Identifier}", returnNull: true) ?? skill.Identifier); + 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); @@ -1444,6 +1524,7 @@ namespace Barotrauma } parent.RecalculateChildren(); + GUITextBlock.AutoScaleAndNormalize(skillNames); } private void UpdateTalentButtons() diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs b/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs index e979ecbeb..ad9693350 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs @@ -1068,7 +1068,6 @@ namespace Barotrauma spriteBatch.End(); } - sw.Stop(); PerformanceCounter.AddElapsedTicks("Draw total", sw.ElapsedTicks); PerformanceCounter.DrawTimeGraph.Update(sw.ElapsedTicks * 1000.0f / (float)Stopwatch.Frequency); diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/MultiPlayerCampaign.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/MultiPlayerCampaign.cs index 99526244b..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") diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameSession.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameSession.cs index 86db15f21..b5246980c 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameSession.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameSession.cs @@ -21,7 +21,8 @@ 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) && !ConversationAction.IsDialogOpen) @@ -39,6 +40,7 @@ namespace Barotrauma private GUILayoutGroup topLeftButtonGroup; private GUIButton crewListButton, commandButton, tabMenuButton; + private GUIImage talentPointNotification; private GUIComponent respawnInfoFrame, respawnButtonContainer; private GUITextBlock respawnInfoText; @@ -88,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) @@ -139,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; } @@ -158,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/Items/CharacterInventory.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/CharacterInventory.cs index 72434637b..2e3782d15 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/CharacterInventory.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/CharacterInventory.cs @@ -323,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; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/GeneticMaterial.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/GeneticMaterial.cs index 37c6d9553..174fba2b0 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/GeneticMaterial.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/GeneticMaterial.cs @@ -14,7 +14,7 @@ namespace Barotrauma.Items.Components public override void AddTooltipInfo(ref string name, ref string description) { - if (!string.IsNullOrEmpty(materialName)) + if (!string.IsNullOrEmpty(materialName) && item.ContainedItems.Count() > 0) { string mergedMaterialName = materialName; foreach (Item containedItem in item.ContainedItems) @@ -23,7 +23,7 @@ namespace Barotrauma.Items.Components if (containedMaterial == null) { continue; } mergedMaterialName += ", " + containedMaterial.materialName; } - name = TextManager.GetWithVariable("entityname.geneticmaterial", "[type]", mergedMaterialName); + name = name.Replace(materialName, mergedMaterialName); } if (Tainted) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Deconstructor.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Deconstructor.cs index cf62a7d76..6e6fbe63e 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Deconstructor.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Deconstructor.cs @@ -164,7 +164,7 @@ namespace Barotrauma.Items.Components } } } - activateButton.Enabled = inputContainer.Inventory.AllItems.Any(); + activateButton.Enabled = outputsFound; activateButton.Text = TextManager.Get(ActivateButtonText); }; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs index fb9f701ea..fabc40b20 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs @@ -516,11 +516,7 @@ namespace Barotrauma.Items.Components string name = GetRecipeNameAndAmount(selectedItem); - float quality = 0; - foreach (string tag in selectedItem.TargetItem.Tags) - { - quality += user?.Info?.GetSavedStatValue(StatTypes.IncreaseFabricationQuality, tag) ?? 0; - } + float quality = GetFabricatedItemQuality(selectedItem, user); if (quality > 0) { name = TextManager.GetWithVariable("itemname.quality" + (int)quality, "[itemname]", name+'\n', fallBackTag: "itemname.quality3"); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs index 0c213a8cb..59c300a13 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs @@ -246,25 +246,26 @@ namespace Barotrauma.Items.Components 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); - GUIFrame paddedContainer = new GUIFrame(new RectTransform(new Vector2(0.95f, 0.9f), GuiFrame.RectTransform, Anchor.Center), style: 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); - - new GUICustomComponent(new RectTransform(GuiFrame.Rect.Size - GUIStyle.ItemFrameMargin, GuiFrame.RectTransform, Anchor.Center) { AbsoluteOffset = GUIStyle.ItemFrameOffset }, DrawHUDFront, null) + var submarineFront = new GUICustomComponent(new RectTransform(GuiFrame.Rect.Size - GUIStyle.ItemFrameMargin, GuiFrame.RectTransform, Anchor.Center) { AbsoluteOffset = GUIStyle.ItemFrameOffset }, DrawHUDFront, null) { CanBeFocused = false }; - GUILayoutGroup buttonLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 0.2f), paddedContainer.RectTransform), isHorizontal: true); + 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, 0.5f), 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, 0.5f), buttonLayout.RectTransform), string.Empty, style: "StatusMonitorButton.ElectricalView") { UserData = MiniMapMode.ElectricalView, Enabled = EnableHullCondition, ToolTip = TextManager.Get("StatusMonitorButton.ElectricalView.Tooltip") }, - new GUIButton(new RectTransform(new Vector2(0.25f, 0.5f), buttonLayout.RectTransform), string.Empty, style: "StatusMonitorButton.HullCondition") { UserData = MiniMapMode.HullCondition, Enabled = EnableHullCondition, ToolTip = TextManager.Get("StatusMonitorButton.HullCondition.Tooltip") }, - new GUIButton(new RectTransform(new Vector2(0.25f, 0.5f), buttonLayout.RectTransform), string.Empty, style: "StatusMonitorButton.ItemFinder") { UserData = MiniMapMode.ItemFinder, Enabled = EnableItemFinder, ToolTip = TextManager.Get("StatusMonitorButton.ItemFinder.Tooltip") } + 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 = EnableHullCondition, ToolTip = TextManager.Get("StatusMonitorButton.ElectricalView.Tooltip") }, + new GUIButton(new RectTransform(new Vector2(0.25f, 1.0f), buttonLayout.RectTransform), string.Empty, style: "StatusMonitorButton.HullCondition") { UserData = MiniMapMode.HullCondition, Enabled = EnableHullCondition, ToolTip = TextManager.Get("StatusMonitorButton.HullCondition.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) @@ -295,14 +296,15 @@ namespace Barotrauma.Items.Components 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), style: null) + 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) { - AbsoluteSpacing = (int)(5 * GUI.Scale) + Stretch = true, + AbsoluteSpacing = GUI.IntScale(5) }; if (reports.Any()) @@ -359,10 +361,7 @@ namespace Barotrauma.Items.Components searchBar.OnSelected += (sender, key) => { - itemsFoundOnSub = Item.ItemList.Where(it => - it.Submarine == item.Submarine && - !it.NonInteractable && !it.HiddenInGame && - (it.GetComponent() != null || it.GetComponent() != null)).Select(it => it.Prefab).ToImmutableHashSet(); + itemsFoundOnSub = Item.ItemList.Where(it => VisibleOnItemFinder(it)).Select(it => it.Prefab).ToImmutableHashSet(); }; searchBar.OnKeyHit += ControlSearchTooltip; @@ -390,6 +389,28 @@ namespace Barotrauma.Items.Components 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); + } + + private bool VisibleOnItemFinder(Item it) + { + 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() @@ -546,10 +567,7 @@ namespace Barotrauma.Items.Components // is there a better way to do this? if (GuiFrame.Rect.Size != elementSize) { - if (item.Submarine is { } sub) - { - BakeSubmarine(sub, miniMapFrame.Rect); - } + CreateGUI(); elementSize = GuiFrame.Rect.Size; } @@ -782,10 +800,7 @@ namespace Barotrauma.Items.Components foreach (Item it in Item.ItemList) { - if (it.Submarine != item.Submarine) { continue; } - if (it.HiddenInGame || it.NonInteractable) { continue; } - if (it.GetComponent() is { Connections: { } conn} && conn.Any()) { continue; } - if (it.HasTag("traitormissionitem")) { continue; } + if (!VisibleOnItemFinder(it)) { continue; } if (it.Prefab == searchedPrefab) { @@ -794,7 +809,7 @@ namespace Barotrauma.Items.Components if (it.FindParentInventory(inventory => inventory is ItemInventory { Owner: Item { ParentInventory: null } }) is ItemInventory parent) { - foundItems.Add((Item) parent.Owner); + foundItems.Add((Item)parent.Owner); } else { @@ -1165,7 +1180,7 @@ namespace Barotrauma.Items.Components if (entity is Item it) { - if (it.GetComponent() != null || it.ParentInventory != null) { continue; } + if (it.GetComponent() != null || it.ParentInventory != null) { continue; } DrawItem(spriteBatch, it, parentRect, worldBorders, inflate); } } 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/Repairable.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Repairable.cs index f68bdc796..fcdf7f34c 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Repairable.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Repairable.cs @@ -51,7 +51,7 @@ 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))) || CanTinker(character); + return item.ConditionPercentage < RepairThreshold || character.IsTraitor && item.ConditionPercentage > MinSabotageCondition || (CurrentFixer == character && (!item.IsFullCondition || (character.IsTraitor && item.ConditionPercentage > MinSabotageCondition))) || IsTinkerable(character); } partial void InitProjSpecific(XElement element) @@ -162,7 +162,7 @@ namespace Barotrauma.Items.Components tinkerButtonText = "Tinker"; tinkeringText = "Tinkering"; - TinkerButton = new GUIButton(new RectTransform(new Vector2(0.8f, 0.15f), paddedFrame.RectTransform, Anchor.BottomCenter), tinkerButtonText, style: "GUIButtonSmall") + TinkerButton = new GUIButton(new RectTransform(new Vector2(0.8f, 0.15f), paddedFrame.RectTransform, Anchor.BottomCenter), tinkerButtonText) { IgnoreLayoutGroups = true, Visible = false, @@ -254,7 +254,7 @@ namespace Barotrauma.Items.Components progressBarOverlayText.Visible = false; } - RepairButton.Enabled = (currentFixerAction == FixActions.None || (CurrentFixer == character && currentFixerAction != FixActions.Repair)) && !item.IsFullCondition; + 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); @@ -269,7 +269,7 @@ namespace Barotrauma.Items.Components 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 && CanTinker(character)) ? + TinkerButton.Text = (currentFixerAction == FixActions.None || CurrentFixer != character || currentFixerAction != FixActions.Tinker) ? tinkerButtonText : tinkeringText + new string('.', ((int)(Timing.TotalTime * 2.0f) % 3) + 1); @@ -326,6 +326,7 @@ namespace Barotrauma.Items.Components 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; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs index daa10c434..d2a1e999e 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs @@ -316,11 +316,12 @@ namespace Barotrauma string colorStr = XMLExtensions.ColorToString(!item.AllowStealing ? GUI.Style.Red : Color.White); + toolTip = $"‖color:{colorStr}‖{name}‖color:end‖"; if (item.Quality > 0) { - name = TextManager.GetWithVariable("itemname.quality" + item.Quality, "[itemname]", name, fallBackTag: "itemname.quality3"); + // 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)}"; } - toolTip = $"‖color:{colorStr}‖{name}‖color:end‖"; if (itemsInSlot.All(it => it.NonInteractable || it.NonPlayerTeamInteractable)) { @@ -1653,9 +1654,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()?.GetMaxStackSize(slotIndex) ?? maxStackSize); + maxStackSize = Math.Min(maxStackSize, itemInventory.Container.GetMaxStackSize(slotIndex)); } if (maxStackSize > 1 && inventory != null) { 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/Networking/GameClient.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs index bc8cc1075..8dd2d4939 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs @@ -2755,6 +2755,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/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/MultiPlayerCampaignSetupUI.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI/MultiPlayerCampaignSetupUI.cs new file mode 100644 index 000000000..e02424ee4 --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI/MultiPlayerCampaignSetupUI.cs @@ -0,0 +1,521 @@ +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 MultiPlayerCampaignSetupUI : CampaignSetupUI + { + private GUIButton deleteMpSaveButton; + + public MultiPlayerCampaignSetupUI(GUIComponent newGameContainer, GUIComponent loadGameContainer, IEnumerable submarines, IEnumerable saveFiles = null) + : base(newGameContainer, loadGameContainer) + { + var columnContainer = new GUILayoutGroup(new RectTransform(Vector2.One, newGameContainer.RectTransform), isHorizontal: true) + { + Stretch = true, + RelativeSpacing = 0.0f + }; + + var leftColumn = new GUILayoutGroup(new RectTransform(Vector2.One, columnContainer.RectTransform)) + { + Stretch = true, + RelativeSpacing = 0.015f + }; + + var rightColumn = new GUILayoutGroup(new RectTransform(Vector2.Zero, 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)); + + // 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); + + // New game right side + subPreviewContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f), rightColumn.RectTransform)) + { + Stretch = true + }; + + var buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.12f), + 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")) + { + OnClicked = (GUIButton btn, object userData) => + { + if (string.IsNullOrWhiteSpace(saveNameBox.Text)) + { + saveNameBox.Flash(GUI.Style.Red); + return false; + } + + SubmarineInfo selectedSub = null; + + if (GameMain.NetLobbyScreen.SelectedSub == null) { return false; } + selectedSub = GameMain.NetLobbyScreen.SelectedSub; + + 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.Multiplayer, saveNameBox.Text); + bool hasRequiredContentPackages = selectedSub.RequiredContentPackagesInstalled; + + CampaignSettings settings = new CampaignSettings(); + + settings.RadiationEnabled = GameMain.NetLobbyScreen.IsRadiationEnabled(); + settings.MaxMissionCount = GameMain.NetLobbyScreen.GetMaxMissionCount(); + + 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); + CoroutineManager.StartCoroutine(WaitForCampaignSetup(), "WaitForCampaignSetup"); + } + 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); + CoroutineManager.StartCoroutine(WaitForCampaignSetup(), "WaitForCampaignSetup"); + return true; + }; + msgBox.Buttons[0].OnClicked += msgBox.Close; + + msgBox.Buttons[1].OnClicked = msgBox.Close; + return false; + } + } + else + { + StartNewGame?.Invoke(selectedSub, savePath, seedBox.Text, settings); + CoroutineManager.StartCoroutine(WaitForCampaignSetup(), "WaitForCampaignSetup"); + } + + return true; + } + }; + + 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 (GameMain.NetLobbyScreen.SelectedSub != null) + { + initialMoney -= GameMain.NetLobbyScreen.SelectedSub.Price; + } + initialMoney = Math.Max(initialMoney, MultiPlayerCampaign.MinimumInitialMoney); + return TextManager.GetWithVariable("campaignstartingmoney", "[money]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", initialMoney)); + } + }; + + columnContainer.Recalculate(); + leftColumn.Recalculate(); + rightColumn.Recalculate(); + + if (submarines != null) { UpdateSubList(submarines); } + UpdateLoadMenu(saveFiles); + } + + private void CreateMultiplayerCampaignSubList(RectTransform parent) + { + GUILayoutGroup subHolder = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.725f), parent)) + { + RelativeSpacing = 0.005f, + Stretch = true + }; + + var subLabel = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.055f), subHolder.RectTransform) { MinSize = new Point(0, 25) }, TextManager.Get("purchasablesubmarines", fallBackTag: "workshoplabelsubmarines"), font: GUI.SubHeadingFont); + + var filterContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), subHolder.RectTransform), isHorizontal: true) + { + Stretch = 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) => + { + foreach (GUIComponent child in subList.Content.Children) + { + if (!(child.UserData is SubmarineInfo sub)) { continue; } + child.Visible = string.IsNullOrEmpty(text) ? true : sub.DisplayName.ToLower().Contains(text.ToLower()); + } + return true; + }; + + subList = new GUIListBox(new RectTransform(Vector2.One, subHolder.RectTransform)); + subTickBoxes = new List(); + + for (int i = 0; i < GameMain.Client.ServerSubmarines.Count; i++) + { + SubmarineInfo sub = GameMain.Client.ServerSubmarines[i]; + + if (!sub.IsCampaignCompatible) continue; + + var frame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.2f), subList.Content.RectTransform) { MinSize = new Point(0, 20) }, + style: "ListBoxElement") + { + ToolTip = sub.Description, + UserData = sub + }; + + int buttonSize = (int)(frame.Rect.Height * 0.8f); + + GUITickBox tickBox = new GUITickBox(new RectTransform(new Vector2(0.8f, 1.0f), frame.RectTransform, Anchor.CenterLeft), ToolBox.LimitString(sub.DisplayName, GUI.Font, subList.Content.Rect.Width - 65)) + { + UserData = sub, + OnSelected = (GUITickBox box) => + { + GameMain.Client.RequestCampaignSub(box.UserData as SubmarineInfo, box.Selected); + return true; + } + }; + subTickBoxes.Add(tickBox); + tickBox.Selected = GameMain.NetLobbyScreen.CampaignSubmarines.Contains(sub); + + frame.RectTransform.MinSize = new Point(0, tickBox.RectTransform.MinSize.Y); + + var subTextBlock = tickBox.TextBlock; + + var matchingSub = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == sub.Name && s.MD5Hash?.Hash == sub.MD5Hash?.Hash); + if (matchingSub == null) matchingSub = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == sub.Name); + + if (matchingSub == null) + { + subTextBlock.TextColor = new Color(subTextBlock.TextColor, 0.5f); + frame.ToolTip = TextManager.Get("SubNotFound"); + } + else if (matchingSub?.MD5Hash == null || matchingSub.MD5Hash?.Hash != sub.MD5Hash?.Hash) + { + subTextBlock.TextColor = new Color(subTextBlock.TextColor, 0.5f); + frame.ToolTip = TextManager.Get("SubDoesntMatch"); + } + + if (!sub.RequiredContentPackagesInstalled) + { + subTextBlock.TextColor = Color.Lerp(subTextBlock.TextColor, Color.DarkRed, 0.5f); + frame.ToolTip = TextManager.Get("ContentPackageMismatch") + "\n\n" + frame.RawToolTip; + } + + var classText = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), frame.RectTransform, Anchor.CenterRight), + TextManager.Get($"submarineclass.{sub.SubmarineClass}"), textAlignment: Alignment.CenterRight, font: GUI.SmallFont) + { + TextColor = subTextBlock.TextColor * 0.8f, + ToolTip = subTextBlock.RawToolTip + }; + } + } + + public void RefreshMultiplayerCampaignSubUI(List campaignSubs) + { + for (int i = 0; i < subTickBoxes.Count; i++) + { + subTickBoxes[i].Selected = campaignSubs.Contains(subTickBoxes[i].UserData as SubmarineInfo); + } + } + + private IEnumerable WaitForCampaignSetup() + { + GUI.SetCursorWaiting(); + string headerText = TextManager.Get("CampaignStartingPleaseWait"); + var msgBox = new GUIMessageBox(headerText, TextManager.Get("CampaignStarting"), new string[] { TextManager.Get("Cancel") }); + + msgBox.Buttons[0].OnClicked = (btn, userdata) => + { + GameMain.NetLobbyScreen.HighlightMode(GameMain.NetLobbyScreen.SelectedModeIndex); + GameMain.NetLobbyScreen.SelectMode(GameMain.NetLobbyScreen.SelectedModeIndex); + GUI.ClearCursorWait(); + CoroutineManager.StopCoroutines("WaitForCampaignSetup"); + return true; + }; + msgBox.Buttons[0].OnClicked += msgBox.Close; + + DateTime timeOut = DateTime.Now + new TimeSpan(0, 0, 20); + while (Screen.Selected != GameMain.GameScreen && DateTime.Now < timeOut) + { + msgBox.Header.Text = headerText + new string('.', (int)Timing.TotalTime % 3 + 1); + yield return CoroutineStatus.Running; + } + msgBox.Close(); + GUI.ClearCursorWait(); + yield return CoroutineStatus.Success; + } + + public void UpdateSubList(IEnumerable submarines) + { + List subsToShow; + 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.Multiplayer); + } + + var leftColumn = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.85f), loadGameContainer.RectTransform), childAnchor: Anchor.TopCenter) + { + Stretch = true, + RelativeSpacing = 0.03f + }; + + saveList = new GUIListBox(new RectTransform(Vector2.One, leftColumn.RectTransform)) + { + OnSelected = SelectSaveFile + }; + + 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(); + + 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); + 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); + CoroutineManager.StartCoroutine(WaitForCampaignSetup(), "WaitForCampaignSetup"); + return true; + }, + Enabled = false + }; + deleteMpSaveButton = new GUIButton(new RectTransform(new Vector2(0.45f, 0.12f), loadGameContainer.RectTransform, Anchor.BottomLeft), + TextManager.Get("Delete"), style: "GUIButtonSmall") + { + OnClicked = DeleteSave, + Visible = false + }; + } + + private bool SelectSaveFile(GUIComponent component, object obj) + { + string fileName = (string)obj; + + 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; + } + + 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; + } + } +} diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI/SinglePlayerCampaignSetupUI.cs similarity index 51% rename from Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI.cs rename to Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI/SinglePlayerCampaignSetupUI.cs index 32a158105..70dd8fbd8 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI/SinglePlayerCampaignSetupUI.cs @@ -10,58 +10,108 @@ using Barotrauma.Extensions; namespace Barotrauma { - class CampaignSetupUI + class SinglePlayerCampaignSetupUI : CampaignSetupUI { - private readonly GUIComponent newGameContainer, loadGameContainer; + public CharacterInfo.AppearanceCustomizationMenu[] CharacterMenus { get; private set; } - private GUIListBox subList; - private GUIListBox saveList; - private List subTickBoxes; - - private readonly GUITextBox saveNameBox, seedBox; - - private readonly GUILayoutGroup subPreviewContainer; - - private GUIButton loadGameButton, deleteMpSaveButton; - - public Action StartNewGame; - public Action LoadGame; - - private enum CategoryFilter { All = 0, Vanilla = 1, Custom = 2 }; - private CategoryFilter subFilter = CategoryFilter.All; - - public GUIButton StartButton + private GUIButton nextButton; + private GUILayoutGroup characterInfoColumns; + + public SinglePlayerCampaignSetupUI(GUIComponent newGameContainer, GUIComponent loadGameContainer, IEnumerable submarines, IEnumerable saveFiles = null) + : base(newGameContainer, loadGameContainer) { - get; - private set; + UpdateNewGameMenu(submarines); + UpdateLoadMenu(saveFiles); } - public GUITextBlock InitialMoneyText + private int currentPage = 0; + private GUIListBox pageContainer; + + public void Update() { - get; - private set; + 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; + }); } - 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) + private void UpdateNewGameMenu(IEnumerable submarines) { - this.isMultiplayer = isMultiplayer; - this.newGameContainer = newGameContainer; - this.loadGameContainer = loadGameContainer; + pageContainer = + new GUIListBox(new RectTransform(Vector2.One, newGameContainer.RectTransform), style: null, isHorizontal: true) + { + ScrollBarEnabled = false, + ScrollBarVisible = false, + HoverCursor = CursorState.Default + }; - var columnContainer = new GUILayoutGroup(new RectTransform(Vector2.One, newGameContainer.RectTransform), isHorizontal: true) + 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 = isMultiplayer ? 0.0f : 0.02f + RelativeSpacing = 0.02f }; var leftColumn = new GUILayoutGroup(new RectTransform(Vector2.One, columnContainer.RectTransform)) @@ -70,7 +120,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(new Vector2(1.5f, 1.0f), columnContainer.RectTransform)) { Stretch = true, RelativeSpacing = 0.015f @@ -88,47 +138,37 @@ 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); + + 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) { - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.02f), leftColumn.RectTransform) { MinSize = new Point(0, 20) }, TextManager.Get("SelectedSub"), font: GUI.SubHeadingFont); + Stretch = true + }; - 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); + subList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.65f), leftColumn.RectTransform)) { ScrollBarVisible = true }; - var filterContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), leftColumn.RectTransform), isHorizontal: true) - { - Stretch = 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; }; - 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 + moddedDropdown.OnSelected = (component, data) => { - CreateMultiplayerCampaignSubList(leftColumn.RectTransform); + searchBox.Text = string.Empty; + subFilter = (CategoryFilter)data; + UpdateSubList(SubmarineInfo.SavedSubmarines); + return true; + }; - //spacing - //new GUIFrame(new RectTransform(new Vector2(1.0f, 0.25f), leftColumn.RectTransform), style: null); - } + subList.OnSelected = OnSubSelected; // New game right side subPreviewContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f), rightColumn.RectTransform)) @@ -136,176 +176,162 @@ namespace Barotrauma Stretch = true }; - 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; } - - StartButton = new GUIButton(new RectTransform(new Vector2(0.4f, 1f), buttonContainer.RectTransform, Anchor.BottomRight) { MaxSize = new Point(350, 60) }, TextManager.Get("StartCampaignButton")) + var firstPageButtonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.08f), + firstPageLayout.RectTransform), childAnchor: Anchor.BottomLeft, isHorizontal: true) { - OnClicked = (GUIButton btn, object userData) => - { - if (string.IsNullOrWhiteSpace(saveNameBox.Text)) - { - saveNameBox.Flash(GUI.Style.Red); - return false; - } - - 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 (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(isMultiplayer ? SaveUtil.SaveType.Multiplayer : SaveUtil.SaveType.Singleplayer, 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; - } - } - - 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); - if (isMultiplayer) - { - CoroutineManager.StartCoroutine(WaitForCampaignSetup(), "WaitForCampaignSetup"); - } - } - 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); - if (isMultiplayer) - { - CoroutineManager.StartCoroutine(WaitForCampaignSetup(), "WaitForCampaignSetup"); - } - return true; - }; - msgBox.Buttons[0].OnClicked += msgBox.Close; - - msgBox.Buttons[1].OnClicked = msgBox.Close; - return false; - } - } - else - { - StartNewGame?.Invoke(selectedSub, savePath, seedBox.Text, settings); - if (isMultiplayer) - { - CoroutineManager.StartCoroutine(WaitForCampaignSetup(), "WaitForCampaignSetup"); - } - } - - return true; - } + RelativeSpacing = 0.025f }; - 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.3f, 1f), firstPageButtonContainer.RectTransform), "", font: GUI.Style.Font, textColor: GUI.Style.Green, textAlignment: Alignment.CenterLeft) { TextGetter = () => { int initialMoney = CampaignMode.InitialMoney; - if (isMultiplayer) - { - if (GameMain.NetLobbyScreen.SelectedSub != null) - { - initialMoney -= GameMain.NetLobbyScreen.SelectedSub.Price; - } - } - else if (subList.SelectedData is SubmarineInfo subInfo) + if (subList.SelectedData is SubmarineInfo subInfo) { initialMoney -= subInfo.Price; } - initialMoney = Math.Max(initialMoney, isMultiplayer ? MultiPlayerCampaign.MinimumInitialMoney : 0); + initialMoney = Math.Max(initialMoney, 0); return TextManager.GetWithVariable("campaignstartingmoney", "[money]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", initialMoney)); } }; - if (!isMultiplayer) + CampaignCustomizeButton = new GUIButton(new RectTransform(new Vector2(0.25f, 1f), firstPageButtonContainer.RectTransform, Anchor.CenterLeft), TextManager.Get("SettingsButton")) { - CampaignCustomizeButton = new GUIButton(new RectTransform(new Vector2(0.25f, 1f), buttonContainer.RectTransform, Anchor.CenterLeft), TextManager.Get("SettingsButton")) + OnClicked = (tb, userdata) => { - OnClicked = (tb, userdata) => - { - CreateCustomizeWindow(); - return true; - } - }; + 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") + nextButton = new GUIButton(new RectTransform(new Vector2(0.4f, 1f), firstPageButtonContainer.RectTransform, Anchor.BottomRight), TextManager.Get("Next")) + { + OnClicked = (GUIButton btn, object userData) => { - IgnoreLayoutGroups = true, - OnClicked = (btn, userdata) => { GameMain.Instance.ShowCampaignDisclaimer(); return true; } - }; - disclaimerBtn.RectTransform.MaxSize = new Point((int)(30 * GUI.Scale)); - } + 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); } - UpdateLoadMenu(saveFiles); + } + + 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)); + + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), subLayout.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"; + } + } + }; + } + } + private void CreateCustomizeWindow() { CampaignCustomizeSettings = new GUIMessageBox("", "", new string[] { TextManager.Get("OK") }, new Vector2(0.2f, 0.2f)); @@ -355,106 +381,93 @@ namespace Barotrauma maxMissionCountContainer.Children.ForEach(c => c.ToolTip = maxMissionCountSettingHolder.ToolTip); } - private void CreateMultiplayerCampaignSubList(RectTransform parent) + private bool FinishSetup(GUIButton btn, object userdata) { - GUILayoutGroup subHolder = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.725f), parent)) + if (string.IsNullOrWhiteSpace(saveNameBox.Text)) { - RelativeSpacing = 0.005f, - Stretch = true - }; + saveNameBox.Flash(GUI.Style.Red); + return false; + } - var subLabel = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.055f), subHolder.RectTransform) { MinSize = new Point(0, 25) }, TextManager.Get("purchasablesubmarines", fallBackTag: "workshoplabelsubmarines"), font: GUI.SubHeadingFont); + SubmarineInfo selectedSub = null; - var filterContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), subHolder.RectTransform), isHorizontal: true) + if (!(subList.SelectedData is SubmarineInfo)) { return false; } + selectedSub = subList.SelectedData as SubmarineInfo; + + if (selectedSub.SubmarineClass == SubmarineClass.Undefined) { - Stretch = 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) => + new GUIMessageBox(TextManager.Get("error"), TextManager.Get("undefinedsubmarineselected")); + return false; + } + + if (string.IsNullOrEmpty(selectedSub.MD5Hash.Hash)) { - foreach (GUIComponent child in subList.Content.Children) + ((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) { - if (!(child.UserData is SubmarineInfo sub)) { continue; } - child.Visible = string.IsNullOrEmpty(text) ? true : sub.DisplayName.ToLower().Contains(text.ToLower()); - } - return true; - }; + var msgBox = new GUIMessageBox(TextManager.Get("ContentPackageMismatch"), + TextManager.GetWithVariable("ContentPackageMismatchWarning", "[requiredcontentpackages]", string.Join(", ", selectedSub.RequiredContentPackages)), + new string[] { TextManager.Get("Yes"), TextManager.Get("No") }); - subList = new GUIListBox(new RectTransform(Vector2.One, subHolder.RectTransform)); - subTickBoxes = new List(); - - for (int i = 0; i < GameMain.Client.ServerSubmarines.Count; i++) - { - SubmarineInfo sub = GameMain.Client.ServerSubmarines[i]; - - if (!sub.IsCampaignCompatible) continue; - - var frame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.2f), subList.Content.RectTransform) { MinSize = new Point(0, 20) }, - style: "ListBoxElement") - { - ToolTip = sub.Description, - UserData = sub - }; - - int buttonSize = (int)(frame.Rect.Height * 0.8f); - - GUITickBox tickBox = new GUITickBox(new RectTransform(new Vector2(0.8f, 1.0f), frame.RectTransform, Anchor.CenterLeft), ToolBox.LimitString(sub.DisplayName, GUI.Font, subList.Content.Rect.Width - 65)) - { - UserData = sub, - OnSelected = (GUITickBox box) => + msgBox.Buttons[0].OnClicked = msgBox.Close; + msgBox.Buttons[0].OnClicked += (button, obj) => { - GameMain.Client.RequestCampaignSub(box.UserData as SubmarineInfo, box.Selected); + if (GUIMessageBox.MessageBoxes.Count == 0) + { + StartNewGame?.Invoke(selectedSub, savePath, seedBox.Text, settings); + } return true; - } - }; - subTickBoxes.Add(tickBox); - tickBox.Selected = GameMain.NetLobbyScreen.CampaignSubmarines.Contains(sub); + }; - frame.RectTransform.MinSize = new Point(0, tickBox.RectTransform.MinSize.Y); - - var subTextBlock = tickBox.TextBlock; - - var matchingSub = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == sub.Name && s.MD5Hash?.Hash == sub.MD5Hash?.Hash); - if (matchingSub == null) matchingSub = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == sub.Name); - - if (matchingSub == null) - { - subTextBlock.TextColor = new Color(subTextBlock.TextColor, 0.5f); - frame.ToolTip = TextManager.Get("SubNotFound"); - } - else if (matchingSub?.MD5Hash == null || matchingSub.MD5Hash?.Hash != sub.MD5Hash?.Hash) - { - subTextBlock.TextColor = new Color(subTextBlock.TextColor, 0.5f); - frame.ToolTip = TextManager.Get("SubDoesntMatch"); + msgBox.Buttons[1].OnClicked = msgBox.Close; } - if (!sub.RequiredContentPackagesInstalled) + if (selectedSub.HasTag(SubmarineTag.Shuttle)) { - subTextBlock.TextColor = Color.Lerp(subTextBlock.TextColor, Color.DarkRed, 0.5f); - frame.ToolTip = TextManager.Get("ContentPackageMismatch") + "\n\n" + frame.RawToolTip; - } + var msgBox = new GUIMessageBox(TextManager.Get("ShuttleSelected"), + TextManager.Get("ShuttleWarning"), + new string[] { TextManager.Get("Yes"), TextManager.Get("No") }); - var classText = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), frame.RectTransform, Anchor.CenterRight), - TextManager.Get($"submarineclass.{sub.SubmarineClass}"), textAlignment: Alignment.CenterRight, font: GUI.SmallFont) - { - TextColor = subTextBlock.TextColor * 0.8f, - ToolTip = subTextBlock.RawToolTip - }; + 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; + } } - } - - public void RefreshMultiplayerCampaignSubUI(List campaignSubs) - { - for (int i = 0; i < subTickBoxes.Count; i++) + else { - subTickBoxes[i].Selected = campaignSubs.Contains(subTickBoxes[i].UserData as SubmarineInfo); + StartNewGame?.Invoke(selectedSub, savePath, seedBox.Text, settings); } - } + return true; + } + public void RandomizeSeed() { seedBox.Text = ToolBox.RandomSeed(8); @@ -478,54 +491,28 @@ namespace Barotrauma if (!(obj is SubmarineInfo sub)) { return true; } #if !DEBUG - if (!isMultiplayer && sub.Price > CampaignMode.InitialMoney && !GameMain.DebugDraw) + if (sub.Price > CampaignMode.InitialMoney && !GameMain.DebugDraw) { - StartButton.Enabled = false; + SetPage(0); + nextButton.Enabled = false; return false; } #endif - StartButton.Enabled = true; + nextButton.Enabled = true; sub.CreatePreviewWindow(subPreviewContainer); return true; } - private IEnumerable WaitForCampaignSetup() - { - GUI.SetCursorWaiting(); - string headerText = TextManager.Get("CampaignStartingPleaseWait"); - var msgBox = new GUIMessageBox(headerText, TextManager.Get("CampaignStarting"), new string[] { TextManager.Get("Cancel") }); - - msgBox.Buttons[0].OnClicked = (btn, userdata) => - { - GameMain.NetLobbyScreen.HighlightMode(GameMain.NetLobbyScreen.SelectedModeIndex); - GameMain.NetLobbyScreen.SelectMode(GameMain.NetLobbyScreen.SelectedModeIndex); - GUI.ClearCursorWait(); - CoroutineManager.StopCoroutines("WaitForCampaignSetup"); - return true; - }; - msgBox.Buttons[0].OnClicked += msgBox.Close; - - DateTime timeOut = DateTime.Now + new TimeSpan(0, 0, 20); - while (Screen.Selected != GameMain.GameScreen && DateTime.Now < timeOut) - { - msgBox.Header.Text = headerText + new string('.', (int)Timing.TotalTime % 3 + 1); - yield return CoroutineStatus.Running; - } - msgBox.Close(); - GUI.ClearCursorWait(); - yield return CoroutineStatus.Success; - } - public void CreateDefaultSaveName() { - string savePath = SaveUtil.CreateSavePath(isMultiplayer ? SaveUtil.SaveType.Multiplayer : SaveUtil.SaveType.Singleplayer); + string savePath = SaveUtil.CreateSavePath(SaveUtil.SaveType.Singleplayer); saveNameBox.Text = Path.GetFileNameWithoutExtension(savePath); } public void UpdateSubList(IEnumerable submarines) { List subsToShow; - if (!isMultiplayer && subFilter != CategoryFilter.All) + if (subFilter != CategoryFilter.All) { subsToShow = submarines.Where(s => s.IsCampaignCompatibleIgnoreClass && s.IsVanillaSubmarine() == (subFilter == CategoryFilter.Vanilla)).ToList(); } @@ -596,10 +583,10 @@ namespace Barotrauma if (saveFiles == null) { - saveFiles = SaveUtil.GetSaveFiles(isMultiplayer ? SaveUtil.SaveType.Multiplayer : SaveUtil.SaveType.Singleplayer); + saveFiles = SaveUtil.GetSaveFiles(SaveUtil.SaveType.Singleplayer); } - 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(0.5f, 1.0f), loadGameContainer.RectTransform), childAnchor: Anchor.TopCenter) { Stretch = true, RelativeSpacing = 0.03f @@ -610,26 +597,23 @@ namespace Barotrauma OnSelected = SelectSaveFile }; - if (!isMultiplayer) + new GUIButton(new RectTransform(new Vector2(0.6f, 0.08f), leftColumn.RectTransform), TextManager.Get("showinfolder")) { - new GUIButton(new RectTransform(new Vector2(0.6f, 0.08f), leftColumn.RectTransform), TextManager.Get("showinfolder")) + OnClicked = (btn, userdata) => { - OnClicked = (btn, userdata) => + try { - 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; + 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) { @@ -649,39 +633,27 @@ 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 + nameText.Text = Path.GetFileNameWithoutExtension(saveFile); + XDocument doc = SaveUtil.LoadGameSessionDoc(saveFile); + + if (doc?.Root == null) { - 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]; } + 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); @@ -748,38 +720,16 @@ namespace Barotrauma { if (string.IsNullOrWhiteSpace(saveList.SelectedData as string)) { return false; } LoadGame?.Invoke(saveList.SelectedData as string); - if (isMultiplayer) - { - CoroutineManager.StartCoroutine(WaitForCampaignSetup(), "WaitForCampaignSetup"); - } return true; }, Enabled = false }; - deleteMpSaveButton = new GUIButton(new RectTransform(new Vector2(0.45f, 0.12f), loadGameContainer.RectTransform, Anchor.BottomLeft), - TextManager.Get("Delete"), style: "GUIButtonSmall") - { - OnClicked = DeleteSave, - Visible = false - }; } private bool SelectSaveFile(GUIComponent component, object obj) { 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) - { - deleteMpSaveButton.UserData = obj as string; - } - return true; - } - XDocument doc = SaveUtil.LoadGameSessionDoc(fileName); if (doc?.Root == null) { @@ -868,6 +818,5 @@ namespace Barotrauma } loadGameContainer.RemoveChild(prevFrame); } - } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/CharacterEditor/CharacterEditorScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/CharacterEditor/CharacterEditorScreen.cs index 9f158fe81..38e6145a0 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; @@ -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; @@ -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); } @@ -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())); @@ -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 e5a343cbe..5c41420ac 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/CharacterEditor/Wizard.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/CharacterEditor/Wizard.cs @@ -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/GameScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/GameScreen.cs index a2420730e..da8e8bf52 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/GameScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/GameScreen.cs @@ -25,6 +25,7 @@ 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) @@ -38,21 +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"); - BlueprintEffect = content.Load("Effects/blueprintshader_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"); - BlueprintEffect = content.Load("Effects/blueprintshader"); + +"_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); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs index dcb4ae206..6c108709b 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; @@ -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 7944e07d2..62d6c6aad 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)) { @@ -1002,7 +1003,7 @@ namespace Barotrauma clientDisabledElements.AddRange(missionTypeTickBoxes); //------------------------------------------------------------------ - // settings panel + // settings panel //------------------------------------------------------------------ GUILayoutGroup settingsHolder = new GUILayoutGroup(new RectTransform(new Vector2(0.333f, 1.0f), gameModeBackground.RectTransform)) @@ -1083,7 +1084,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 +1250,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 +1262,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 +1301,7 @@ namespace Barotrauma { spectateButton.Visible = false; } - SetSpectate(spectateBox.Selected); + SetSpectate(spectateBox.Selected); if (GameMain.Client != null) { @@ -1324,7 +1323,7 @@ namespace Barotrauma { publicOrPrivate.Text = isPublic ? TextManager.Get("PublicLobbyTag") : TextManager.Get("PrivateLobbyTag"); } - + public void RefreshEnabledElements() { ServerName.Readonly = !GameMain.Client.HasPermission(ClientPermissions.ManageSettings); @@ -1347,7 +1346,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,7 +1359,7 @@ 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); @@ -1383,7 +1382,7 @@ namespace Barotrauma } public void SetCampaignCharacterInfo(CharacterInfo newCampaignCharacterInfo) - { + { if (newCampaignCharacterInfo != null) { if (CampaignCharacterDiscarded) { return; } @@ -1405,29 +1404,24 @@ 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(); @@ -1436,9 +1430,9 @@ namespace Barotrauma infoContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, isGameRunning ? 0.95f : 0.9f), parent.RectTransform, Anchor.BottomCenter), childAnchor: Anchor.TopCenter) { - RelativeSpacing = 0.025f, + RelativeSpacing = 0.015f, Stretch = true, - UserData = characterInfo + UserData = characterInfo }; bool nameChangePending = isGameRunning && GameMain.Client.PendingName != string.Empty && GameMain.Client?.Character?.Name != GameMain.Client.PendingName; @@ -1454,6 +1448,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, @@ -1477,7 +1472,10 @@ namespace Barotrauma { GameMain.Client.PendingName = tb.Text; TabMenu.PendingChanges = true; - CreateChangesPendingText(); + if (createPendingText) + { + CreateChangesPendingText(); + } } else { @@ -1485,15 +1483,12 @@ 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())); 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.02f), infoContainer.RectTransform), isHorizontal: true) { Stretch = true, RelativeSpacing = 0.02f @@ -1518,7 +1513,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)); + JobList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.6f), JobPreferenceContainer.RectTransform, Anchor.BottomCenter), true) { Enabled = true, OnSelected = (child, obj) => @@ -1554,7 +1552,7 @@ namespace Barotrauma }; } - UpdateJobPreferences(JobList); + UpdateJobPreferences(); appearanceFrame = new GUIFrame(new RectTransform(Vector2.One, characterInfoFrame.RectTransform), style: "GUIFrameListBox") { @@ -1564,6 +1562,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, @@ -1588,17 +1588,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; } }; @@ -1676,10 +1669,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") @@ -1687,14 +1695,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)(400 * GUI.Scale), (int)(180 * 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) @@ -1758,7 +1781,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) @@ -1777,7 +1800,7 @@ namespace Barotrauma if (subList == null) { return; } subList.ClearChildren(); - + foreach (SubmarineInfo sub in submarines) { AddSubmarine(subList, sub); @@ -1863,15 +1886,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) { @@ -1963,7 +1986,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)) }, @@ -1987,8 +2010,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", @@ -2119,16 +2142,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")) @@ -2168,7 +2191,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); @@ -2313,7 +2336,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)) @@ -2337,7 +2360,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), @@ -2404,7 +2427,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) @@ -2437,7 +2460,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) @@ -2451,15 +2474,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(); } @@ -2529,14 +2552,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") @@ -2562,7 +2582,7 @@ namespace Barotrauma GUI.DrawBackgroundSprite(spriteBatch, backgroundSprite); spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable); - + GUI.Draw(Cam, spriteBatch); spriteBatch.End(); } @@ -2573,7 +2593,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; @@ -2698,9 +2718,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; } } @@ -2709,212 +2729,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) @@ -2947,7 +2811,7 @@ namespace Barotrauma } } - UpdateJobPreferences(JobList); + UpdateJobPreferences(); if (moveToNext) { @@ -2978,14 +2842,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); }; @@ -3102,7 +2966,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, @@ -3141,88 +3005,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; } @@ -3327,10 +3109,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; } @@ -3395,7 +3177,7 @@ namespace Barotrauma OnClicked = CloseJobInfo }; JobInfoFrame.OnClicked = (btn, userdata) => { if (GUI.MouseOn == btn || GUI.MouseOn == btn.TextBlock) CloseJobInfo(btn, userdata); return true; }; - + return true; } @@ -3405,8 +3187,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)); + + GUIListBox listBox = JobPreferenceContainer.GetChild(); /*foreach (Sprite sprite in jobPreferenceSprites) { sprite.Remove(); } jobPreferenceSprites.Clear();*/ @@ -3437,7 +3224,7 @@ namespace Barotrauma variantButton.OnClicked = (btn, obj) => { btn.Parent.UserData = obj; - UpdateJobPreferences(listBox); + UpdateJobPreferences(); return false; }; } @@ -3448,7 +3235,7 @@ namespace Barotrauma style: "GUIButtonInfo") { UserData = jobPrefab, - OnClicked = ViewJobInfo + OnClicked = ViewJobInfo }; // Remove button @@ -3544,7 +3331,7 @@ namespace Barotrauma .UserData as SubmarineInfo; //matching sub found and already selected, all good - if (sub != null) + if (sub != null) { if (subList == this.subList) { @@ -3583,7 +3370,7 @@ namespace Barotrauma FailedSelectedSub = null; else FailedSelectedShuttle = null; - + //hashes match, all good if (sub.MD5Hash?.Hash == md5Hash && SubmarineInfo.SavedSubmarines.Contains(sub)) { @@ -3593,7 +3380,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 @@ -3612,7 +3399,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) }) + " "; } @@ -3645,7 +3432,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/Sprite/Sprite.cs b/Barotrauma/BarotraumaClient/ClientSource/Sprite/Sprite.cs index 143a70d25..5a9c265f1 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; @@ -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/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 8bdd4831b..cf1f1eb9d 100644 --- a/Barotrauma/BarotraumaClient/LinuxClient.csproj +++ b/Barotrauma/BarotraumaClient/LinuxClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.1500.3.0 + 0.1500.4.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/MacClient.csproj b/Barotrauma/BarotraumaClient/MacClient.csproj index 4dd212c17..af5c2accc 100644 --- a/Barotrauma/BarotraumaClient/MacClient.csproj +++ b/Barotrauma/BarotraumaClient/MacClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.1500.3.0 + 0.1500.4.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/Shaders/Content.mgcb b/Barotrauma/BarotraumaClient/Shaders/Content.mgcb index e88794f28..7d48e26be 100644 --- a/Barotrauma/BarotraumaClient/Shaders/Content.mgcb +++ b/Barotrauma/BarotraumaClient/Shaders/Content.mgcb @@ -67,6 +67,12 @@ /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 diff --git a/Barotrauma/BarotraumaClient/Shaders/Content_opengl.mgcb b/Barotrauma/BarotraumaClient/Shaders/Content_opengl.mgcb index 16d516848..82d54dedf 100644 --- a/Barotrauma/BarotraumaClient/Shaders/Content_opengl.mgcb +++ b/Barotrauma/BarotraumaClient/Shaders/Content_opengl.mgcb @@ -72,3 +72,9 @@ /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/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 5df6359c5..70656497c 100644 --- a/Barotrauma/BarotraumaClient/WindowsClient.csproj +++ b/Barotrauma/BarotraumaClient/WindowsClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.1500.3.0 + 0.1500.4.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaServer/LinuxServer.csproj b/Barotrauma/BarotraumaServer/LinuxServer.csproj index 5d6dfdf6e..d0a633e79 100644 --- a/Barotrauma/BarotraumaServer/LinuxServer.csproj +++ b/Barotrauma/BarotraumaServer/LinuxServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.1500.3.0 + 0.1500.4.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/MacServer.csproj b/Barotrauma/BarotraumaServer/MacServer.csproj index 26fce29e3..19f20cd27 100644 --- a/Barotrauma/BarotraumaServer/MacServer.csproj +++ b/Barotrauma/BarotraumaServer/MacServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.1500.3.0 + 0.1500.4.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterInfo.cs b/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterInfo.cs index ac69ac86c..3729108f9 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterInfo.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterInfo.cs @@ -39,10 +39,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) diff --git a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/MineralMission.cs b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/MineralMission.cs index b6555cb25..8be6f27df 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/MineralMission.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/MineralMission.cs @@ -12,10 +12,10 @@ namespace Barotrauma 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 +23,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/Items/Components/Repairable.cs b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Repairable.cs index 19f03f8c3..129d6e622 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Repairable.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Repairable.cs @@ -39,6 +39,7 @@ namespace Barotrauma.Items.Components 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/Networking/GameServer.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs index 575f5c157..cedb86f86 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs @@ -3464,15 +3464,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(); @@ -3489,6 +3489,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) { diff --git a/Barotrauma/BarotraumaServer/WindowsServer.csproj b/Barotrauma/BarotraumaServer/WindowsServer.csproj index 6b4075d6e..ab6d66a0c 100644 --- a/Barotrauma/BarotraumaServer/WindowsServer.csproj +++ b/Barotrauma/BarotraumaServer/WindowsServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.1500.3.0 + 0.1500.4.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.9.xml b/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.9.xml index a7a1728a0..3a605f435 100644 --- a/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.9.xml +++ b/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.9.xml @@ -118,6 +118,7 @@ + diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveManager.cs index 319c68580..03d672ca5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveManager.cs @@ -464,7 +464,7 @@ namespace Barotrauma }; break; case "return": - newObjective = new AIObjectiveReturn(character, this, priorityModifier: priorityModifier); + newObjective = new AIObjectiveReturn(character, orderGiver, this, priorityModifier: priorityModifier); newObjective.Abandoned += () => DismissSelf(order, option); newObjective.Completed += () => DismissSelf(order, option); break; 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 index e9e52e8c1..86756d255 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveReturn.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveReturn.cs @@ -12,7 +12,7 @@ namespace Barotrauma private bool usingEscapeBehavior; public Submarine ReturnTarget { get; } - public AIObjectiveReturn(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1.0f) : base(character, objectiveManager, priorityModifier) + 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) @@ -23,10 +23,12 @@ namespace Barotrauma Submarine GetReturnTarget(IEnumerable subs) { + var requiredTeamID = orderGiver?.TeamID ?? character?.TeamID; Submarine returnTarget = null; foreach (var sub in subs) { - if (sub?.TeamID != character.TeamID) { continue; } + if (sub == null) { continue; } + if (sub.TeamID != requiredTeamID) { continue; } returnTarget = sub; break; } @@ -229,7 +231,7 @@ namespace Barotrauma protected override void OnAbandon() { base.OnAbandon(); - SteeringManager.Reset(); + SteeringManager?.Reset(); if (character.IsOnPlayerTeam && objectiveManager.CurrentOrder == objectiveManager.CurrentObjective) { string msg = TextManager.Get("dialogcannotreturn", returnNull: true); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PathFinder.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PathFinder.cs index 8ccdff754..c6cf25146 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PathFinder.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PathFinder.cs @@ -24,8 +24,6 @@ namespace Barotrauma public readonly Vector2 Position; public readonly int WayPointID; - public bool blocked; - public override string ToString() { return $"PathNode {WayPointID}"; @@ -81,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 @@ -146,7 +169,10 @@ namespace Barotrauma 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) { - UpdateBlockedNodes(); + foreach (PathNode node in nodes) + { + node.ResetBlocked(); + } //sort nodes roughly according to distance sortedNodes.Clear(); @@ -202,11 +228,11 @@ namespace Barotrauma { if (startNode == null || node.TempDistance < startNode.TempDistance) { - if (node.blocked) { continue; } 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; } startNode = node; } } @@ -251,11 +277,11 @@ namespace Barotrauma { if (endNode == null || node.TempDistance < endNode.TempDistance) { - if (node.blocked) { continue; } 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; } endNode = node; } } @@ -326,15 +352,13 @@ namespace Barotrauma float dist = float.MaxValue; foreach (PathNode node in nodes) { - if (node.state != 1) { continue; } + if (node.state != 1 || node.F > dist) { continue; } if (isCharacter && node.Waypoint.isObstructed) { continue; } - if (node.blocked) { continue; } if (filter != null && !filter(node)) { continue; } - if (node.F < dist) - { - dist = node.F; - currNode = node; - } + if (node.IsBlocked()) { continue; } + + dist = node.F; + currNode = node; } if (currNode == null || currNode == end) { break; } @@ -436,25 +460,6 @@ namespace Barotrauma return path; } - - private void UpdateBlockedNodes() - { - if (!isCharacter) { return; } - foreach (var n in nodes) - { - n.blocked = false; - if (n.Waypoint.Submarine != null) { continue; } - if (n.Waypoint.Tunnel?.Type != Level.TunnelType.Cave) { continue; } - foreach (var w in Level.Loaded.ExtraWalls) - { - if (!(w is DestructibleLevelWall d)) { continue; } - if (d.Destroyed) { continue; } - if (!d.IsPointInside(n.Waypoint.Position)) { continue; } - n.blocked = true; - break; - } - } - } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/AnimController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/AnimController.cs index 8d490a3fa..f03fabde1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/AnimController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/AnimController.cs @@ -42,6 +42,10 @@ namespace Barotrauma } else { + if (this is HumanoidAnimController humanAnimController && humanAnimController.Crouching) + { + return humanAnimController.HumanCrouchParams; + } return IsMovingFast ? RunParams : WalkParams; } } @@ -96,7 +100,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 { @@ -154,7 +163,7 @@ namespace Barotrauma 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 +216,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 +237,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: diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/FishAnimController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/FishAnimController.cs index a057b932d..8edee5073 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; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs index 5032d2a2e..722cc1106 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; } @@ -169,42 +185,9 @@ namespace Barotrauma 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; @@ -298,7 +281,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) @@ -336,7 +319,7 @@ namespace Barotrauma levitatingCollider = true; ColliderIndex = Crouching && !swimming ? 1 : 0; if (character.SelectedConstruction?.GetComponent()?.ControlCharacterPose ?? false || - (ForceSelectAnimationType != AnimationType.Walk && ForceSelectAnimationType != AnimationType.NotDefined)) + (ForceSelectAnimationType != AnimationType.Crouch && ForceSelectAnimationType != AnimationType.NotDefined)) { Crouching = false; ColliderIndex = 0; @@ -439,9 +422,8 @@ 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, CurrentHumanAnimParams.ArmIKStrength, CurrentHumanAnimParams.HandIKStrength); + HandIK(leftHand, midPos, CurrentHumanAnimParams.ArmIKStrength, CurrentHumanAnimParams.HandIKStrength); } else if (character.AnimController.AnimationTestPose) { @@ -638,7 +620,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; @@ -710,9 +692,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) { @@ -743,12 +728,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; @@ -770,7 +766,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); } } @@ -789,7 +785,7 @@ namespace Barotrauma HandIK(rightHand, torso.SimPosition + posAddition + new Vector2( -handPos.X, - (Math.Sign(walkPosX) == Math.Sign(Dir)) ? handPos.Y : lowerY), CurrentGroundedParams.HandMoveStrength); + (Math.Sign(walkPosX) == Math.Sign(Dir)) ? handPos.Y : lowerY), CurrentGroundedParams.ArmMoveStrength, CurrentGroundedParams.HandMoveStrength); } if (leftHand != null && !leftHand.Disabled) @@ -797,16 +793,14 @@ namespace Barotrauma HandIK(leftHand, torso.SimPosition + posAddition + new Vector2( handPos.X, - (Math.Sign(walkPosX) == Math.Sign(-Dir)) ? handPos.Y : lowerY), CurrentGroundedParams.HandMoveStrength); + (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( @@ -817,27 +811,24 @@ namespace Barotrauma //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); + CurrentGroundedParams.LegBendTorque, CurrentGroundedParams.FootTorque, CurrentGroundedParams.FootAngleInRadians); } } @@ -970,7 +961,7 @@ namespace Barotrauma if (!aiming) { float newRotation = MathUtils.VectorToAngle(TargetMovement) - MathHelper.PiOver2; - Collider.SmoothRotate(newRotation, 5.0f * character.SpeedMultiplier); + Collider.SmoothRotate(newRotation, CurrentSwimParams.SteerTorque * character.SpeedMultiplier); } } else @@ -981,7 +972,7 @@ namespace Barotrauma 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); } } @@ -991,19 +982,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 @@ -1044,25 +1035,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; @@ -1071,7 +1062,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; @@ -1079,14 +1070,14 @@ 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; @@ -1107,8 +1098,8 @@ 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 = character.SpeedMultiplier * (1 - Character.GetRightHandPenalty()); + HandIK(rightHand, handPos + rightHandPos, CurrentSwimParams.ArmMoveStrength * speedMultiplier, CurrentSwimParams.HandMoveStrength * speedMultiplier); } if (leftHand != null && !leftHand.Disabled) @@ -1116,8 +1107,8 @@ 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 = character.SpeedMultiplier * (1 - Character.GetLeftHandPenalty()); + HandIK(leftHand, handPos + leftHandPos, CurrentSwimParams.ArmMoveStrength * speedMultiplier, CurrentSwimParams.HandMoveStrength * speedMultiplier); } } @@ -1173,15 +1164,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); @@ -1258,7 +1250,8 @@ namespace Barotrauma //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); + float movementMultiplier = targetMovement.Y < 0 ? 0 : 1; + head.body.SmoothRotate(MathHelper.PiOver4 * movementMultiplier * Dir, WalkParams.HeadTorque); if (!character.SelectedConstruction.Prefab.Triggers.Any()) { @@ -1881,7 +1874,7 @@ namespace Barotrauma 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]); + HandIK(i == 0 ? rightHand : leftHand, transformedHoldPos + transformedHandlePos[i], CurrentHumanAnimParams.ArmIKStrength, CurrentHumanAnimParams.HandIKStrength); } } } @@ -1906,7 +1899,7 @@ namespace Barotrauma return (lowFreqNoise * 1.0f + highFreqNoise * 0.1f) * wobbleStrength; } - private void HandIK(Limb hand, Vector2 pos, float force = 1.0f) + private void HandIK(Limb hand, Vector2 pos, float armTorque = 1.0f, float handTorque = 1.0f) { Vector2 shoulderPos; @@ -1948,9 +1941,11 @@ namespace Barotrauma 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); + arm?.body.SmoothRotate(armAngle - upperArmAngle, 100.0f * armTorque * arm.Mass, wrapAngle: false); + float forearmAngle = armAngle + lowerArmAngle; + forearm?.body.SmoothRotate(forearmAngle, 100.0f * handTorque * forearm.Mass, wrapAngle: false); + float handAngle = forearm != null ? armAngle : forearmAngle; + hand?.body.SmoothRotate(handAngle, 100.0f * handTorque * hand.Mass, wrapAngle: false); } private void FootIK(Limb foot, Vector2 pos, float legTorque, float footTorque, float footAngle) @@ -1976,12 +1971,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 @@ -2137,5 +2132,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/Character.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs index da6c1677b..2d748476e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs @@ -309,8 +309,6 @@ namespace Barotrauma public string TraitorCurrentObjective = ""; public bool IsHuman => SpeciesName.Equals(CharacterPrefab.HumanSpeciesName, StringComparison.OrdinalIgnoreCase); - public bool IsMale => Info != null && Info.HasGenders && Info.Gender == Gender.Male; - public bool IsFemale => Info != null && Info.HasGenders && Info.Gender == Gender.Female; private float attackCoolDown; @@ -1665,9 +1663,9 @@ 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 && @@ -2760,9 +2758,9 @@ namespace Barotrauma //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; @@ -3505,9 +3503,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) @@ -4351,7 +4349,7 @@ namespace Barotrauma return GiveTalent(talentPrefab, addingFirstTime); } - private bool GiveTalent(TalentPrefab talentPrefab, bool addingFirstTime = true) + public bool GiveTalent(TalentPrefab talentPrefab, bool addingFirstTime = true) { if (addingFirstTime) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs index e4190f0ea..1627fac16 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs @@ -1,9 +1,9 @@ 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; @@ -14,7 +14,6 @@ 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 @@ -25,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(); } } @@ -42,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; @@ -74,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)) @@ -99,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(); } } } @@ -157,7 +151,9 @@ namespace Barotrauma public bool HasNickname => Name != OriginalName; public string OriginalName { get; private set; } + public string Name; + public string DisplayName { get @@ -387,29 +383,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; } } @@ -418,28 +416,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 HairColors; + public readonly ImmutableArray FacialHairColors; + public readonly ImmutableArray 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 @@ -480,16 +532,18 @@ 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.gender = GetRandomGender(randSync); + HasRaces = CharacterConfigElement.GetAttributeBool("races", false); Head.race = GetRandomRace(randSync); CalculateHeadSpriteRange(); - Head.HeadSpriteId = GetRandomHeadID(randSync); + HeadSpriteId = GetRandomHeadID(randSync); Job = (jobPrefab == null) ? Job.Random(Rand.RandSync.Unsynced) : new Job(jobPrefab, variant); + HairColors = CharacterConfigElement.GetAttributeColorArray("haircolors", new Color[] { Color.WhiteSmoke }).ToImmutableArray(); + FacialHairColors = CharacterConfigElement.GetAttributeColorArray("facialhaircolors", new Color[] { Color.WhiteSmoke }).ToImmutableArray(); + SkinColors = CharacterConfigElement.GetAttributeColorArray("skincolors", new Color[] { new Color(255, 215, 200, 255) }).ToImmutableArray(); + SetColors(); if (!string.IsNullOrEmpty(name)) { @@ -502,23 +556,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); @@ -530,6 +568,53 @@ 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; + } + + private void SetColors() + { + HairColor = HairColors.GetRandom(); + FacialHairColor = FacialHairColors.GetRandom(); + SkinColor = SkinColors.GetRandom(); + } + + private void CheckColors() + { + if (HairColor == Color.Black) + { + HairColor = HairColors.GetRandom(); + } + if (FacialHairColor == Color.Black) + { + FacialHairColor = FacialHairColors.GetRandom(); + } + if (SkinColor == Color.Black) + { + SkinColor = SkinColors.GetRandom(); + } + } + // Used for loading the data public CharacterInfo(XElement infoElement) { @@ -542,7 +627,7 @@ namespace Barotrauma 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", "White"), true, out Race race); + 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; @@ -560,14 +645,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.GetAttributeColorArray("haircolors", new Color[] { Color.WhiteSmoke }).ToImmutableArray(); + FacialHairColors = CharacterConfigElement.GetAttributeColorArray("facialhaircolors", new Color[] { Color.WhiteSmoke }).ToImmutableArray(); + SkinColors = CharacterConfigElement.GetAttributeColorArray("skincolors", new Color[] { new Color(255, 215, 200, 255) }).ToImmutableArray(); + RecreateHead( infoElement.GetAttributeInt("headspriteid", 1), race, @@ -577,6 +667,11 @@ namespace Barotrauma infoElement.GetAttributeInt("moustacheindex", -1), infoElement.GetAttributeInt("faceattachmentindex", -1)); + SkinColor = infoElement.GetAttributeColor("skincolor", Color.White); + HairColor = infoElement.GetAttributeColor("haircolor", Color.White); + FacialHairColor = infoElement.GetAttributeColor("facialhaircolor", Color.White); + CheckColors(); + if (string.IsNullOrEmpty(Name)) { if (CharacterConfigElement.Element("name") != null) @@ -652,8 +747,25 @@ namespace Barotrauma 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; @@ -720,10 +832,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; } @@ -752,9 +867,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) @@ -787,26 +909,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()) { @@ -816,6 +984,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()); @@ -823,6 +992,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))) { @@ -847,13 +1018,12 @@ namespace Barotrauma break; } + LoadHeadSpriteProjectSpecific(limbElement); + break; } } - /// - /// Loads only the elements according to the indices, not the sprites. - /// public void LoadHeadAttachments() { if (Wearables != null) @@ -1138,7 +1308,7 @@ 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), @@ -1149,6 +1319,9 @@ namespace Barotrauma 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)); @@ -1462,13 +1635,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(); @@ -1500,6 +1679,12 @@ namespace Barotrauma AttachmentSprites = null; } + private void RefreshHeadSprites() + { + HeadSprite = null; + AttachmentSprites = null; + } + // This could maybe be a LookUp instead? private readonly Dictionary> savedStatValues = new Dictionary>(); @@ -1541,7 +1726,7 @@ namespace Barotrauma } } - public void ChangeSavedStatValue(StatTypes statType, float value, string statIdentifier, bool removeOnDeath, bool removeAfterRound = false, float maxValue = float.MaxValue) + 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)) { @@ -1550,7 +1735,7 @@ namespace Barotrauma if (savedStatValues[statType].FirstOrDefault(s => s.StatIdentifier == statIdentifier) is SavedStatValue savedStat) { - savedStat.StatValue = MathHelper.Min(savedStat.StatValue + value, maxValue); + savedStat.StatValue = setValue ? value : MathHelper.Min(savedStat.StatValue + value, maxValue); } else { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionPrefab.cs index 809ccf081..f227a11c0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionPrefab.cs @@ -628,6 +628,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) { @@ -669,6 +674,15 @@ namespace Barotrauma 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 be9258701..83ff1bad4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs @@ -979,7 +979,7 @@ namespace Barotrauma float minSuitability = -10, maxSuitability = 10; 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) { @@ -1088,7 +1088,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/Jobs/JobPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/JobPrefab.cs index c918b7363..da1952365 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/JobPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/JobPrefab.cs @@ -275,7 +275,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/Limb.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Limb.cs index bf6179d52..fa7d525ae 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Limb.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Limb.cs @@ -744,7 +744,7 @@ namespace Barotrauma } if (attacker != null) { - var abilityAffliction = new AbilityAffliction(newAffliction); + var abilityAffliction = new AbilityAfflictionCharacter(newAffliction, character); attacker.CheckTalents(AbilityEffectType.OnAddDamageAffliction, abilityAffliction); } if (applyAffliction) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Animation/AnimationParams.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Animation/AnimationParams.cs index 649d9afcd..eb6290596 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Animation/AnimationParams.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Animation/AnimationParams.cs @@ -14,6 +14,7 @@ namespace Barotrauma NotDefined, Walk, Run, + Crouch, SwimSlow, SwimFast } @@ -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,8 +114,18 @@ 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; } @@ -402,6 +416,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..391c1fff8 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,23 @@ 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; } + + [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; } } 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 +116,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 +133,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 +145,33 @@ 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; } + + [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 interface IHumanAnimation { float FootAngle { get; set; } float FootAngleInRadians { get; } - float FootRotateStrength { get; set; } + + float ArmMoveStrength { get; set; } + + float HandMoveStrength { get; set; } + + float ArmIKStrength { get; set; } + + float HandIKStrength { get; set; } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Ragdoll/RagdollParams.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Ragdoll/RagdollParams.cs index 9c41dddb0..a5be463ca 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Ragdoll/RagdollParams.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Ragdoll/RagdollParams.cs @@ -34,6 +34,9 @@ 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("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'. Used mainly for animations and widgets."), Editable(-360, 360)] public float SpritesheetOrientation { get; set; } @@ -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; @@ -574,7 +577,7 @@ namespace Barotrauma [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()] @@ -889,6 +892,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 index 4bdddab03..959cf4148 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityCondition.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityCondition.cs @@ -33,6 +33,7 @@ namespace Barotrauma.Abilities NotSelf = 3, Alive = 4, Monster = 5, + InFriendlySubmarine = 6, }; protected List ParseTargetTypes(string[] targetTypeStrings) @@ -80,6 +81,8 @@ namespace Barotrauma.Abilities 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/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/AbilityInterfaces.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityInterfaces.cs index 5a169edf3..8c552ad82 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityInterfaces.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityInterfaces.cs @@ -20,6 +20,11 @@ public Mission Mission { get; set; } } + interface IAbilityLocation + { + public Location Location { get; set; } + } + interface IAbilityCharacter { public Character Character { get; set; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityObjects.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityObjects.cs index 292be7b94..6939b18af 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityObjects.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityObjects.cs @@ -43,6 +43,17 @@ namespace Barotrauma.Abilities 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) @@ -54,6 +65,17 @@ namespace Barotrauma.Abilities 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) @@ -65,7 +87,7 @@ namespace Barotrauma.Abilities public string String { get; set; } } - class AbilityValueStringCharacter : AbilityObject, IAbilityValue, IAbilityString + class AbilityValueStringCharacter : AbilityObject, IAbilityValue, IAbilityString, IAbilityCharacter { public AbilityValueStringCharacter(float value, string abilityString, Character character) { @@ -111,6 +133,16 @@ namespace Barotrauma.Abilities 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 { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffectsToAllies.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffectsToAllies.cs index 2b1115068..9cbaa17eb 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffectsToAllies.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffectsToAllies.cs @@ -1,5 +1,4 @@ -using Barotrauma.Extensions; -using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework; using System.Collections.Generic; using System.Linq; using System.Xml.Linq; @@ -9,10 +8,12 @@ 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); } @@ -22,6 +23,10 @@ namespace Barotrauma.Abilities 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/CharacterAbilityGiveMoney.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveMoney.cs index a10d132c5..d02647357 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveMoney.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveMoney.cs @@ -1,5 +1,4 @@ -using Microsoft.Xna.Framework; -using System.Xml.Linq; +using System.Xml.Linq; namespace Barotrauma.Abilities { @@ -8,7 +7,7 @@ namespace Barotrauma.Abilities public override bool AppliesEffectOnIntervalUpdate => true; private readonly int amount; - private StatTypes scalingStatType; + private readonly StatTypes scalingStatType; public CharacterAbilityGiveMoney(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGivePermanentStat.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGivePermanentStat.cs index ea1a181a2..dce3ed4f8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGivePermanentStat.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGivePermanentStat.cs @@ -13,6 +13,7 @@ namespace Barotrauma.Abilities private readonly bool removeOnDeath; private readonly bool removeAfterRound; private readonly bool giveOnAddingFirstTime; + private readonly bool setValue; //private readonly float maximumValue; @@ -28,6 +29,7 @@ namespace Barotrauma.Abilities removeOnDeath = abilityElement.GetAttributeBool("removeondeath", true); removeAfterRound = abilityElement.GetAttributeBool("removeafterround", false); giveOnAddingFirstTime = abilityElement.GetAttributeBool("giveonaddingfirsttime", characterAbilityGroup.AbilityEffectType == AbilityEffectType.None); + setValue = abilityElement.GetAttributeBool("setvalue", false); } public override void InitializeAbility(bool addingFirstTime) @@ -52,11 +54,11 @@ namespace Barotrauma.Abilities { if (targetAllies) { - Character.GetFriendlyCrew(Character).ForEach(c => c?.Info.ChangeSavedStatValue(statType, value, statIdentifier, removeOnDeath, removeAfterRound, maxValue)); + Character.GetFriendlyCrew(Character).ForEach(c => c?.Info.ChangeSavedStatValue(statType, value, statIdentifier, removeOnDeath, removeAfterRound, maxValue, setValue)); } else { - Character?.Info.ChangeSavedStatValue(statType, value, statIdentifier, removeOnDeath, removeAfterRound, maxValue); + Character?.Info.ChangeSavedStatValue(statType, value, statIdentifier, removeOnDeath, removeAfterRound, maxValue, setValue); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveResistance.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveResistance.cs index 707d31449..af08e66bc 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveResistance.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveResistance.cs @@ -9,7 +9,7 @@ namespace Barotrauma.Abilities public CharacterAbilityGiveResistance(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) { - resistanceId = abilityElement.GetAttributeString("resistanceid", ""); + resistanceId = abilityElement.GetAttributeString("resistanceid", abilityElement.GetAttributeString("resistance", string.Empty)); multiplier = abilityElement.GetAttributeFloat("multiplier", 1f); if (string.IsNullOrEmpty(resistanceId)) 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/CharacterAbilitySpawnItemsToContainer.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilitySpawnItemsToContainer.cs index d092e972c..b13e97638 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilitySpawnItemsToContainer.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilitySpawnItemsToContainer.cs @@ -10,19 +10,24 @@ namespace Barotrauma.Abilities 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 (openedContainers.Contains(item)) { return; } - openedContainers.Add(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) 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..c9a5f9364 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityUnlockTree.cs @@ -0,0 +1,30 @@ +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) + { + Character.GiveTalent(talent); + } + } + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityEnigmaMachine.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityAtmosMachine.cs similarity index 86% rename from Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityEnigmaMachine.cs rename to Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityAtmosMachine.cs index 6b034d24b..5ab9361b8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityEnigmaMachine.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityAtmosMachine.cs @@ -4,14 +4,14 @@ using System.Xml.Linq; namespace Barotrauma.Abilities { - class CharacterAbilityEnigmaMachine : CharacterAbility + class CharacterAbilityAtmosMachine : CharacterAbility { private readonly float addedValue; private readonly float multiplyValue; private readonly string[] tags; private readonly int maxMultiplyCount; - public CharacterAbilityEnigmaMachine(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) + public CharacterAbilityAtmosMachine(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) { addedValue = abilityElement.GetAttributeFloat("addedvalue", 0f); multiplyValue = abilityElement.GetAttributeFloat("multiplyvalue", 1f); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityStonewall.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityStonewall.cs deleted file mode 100644 index cc2aa51c0..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityStonewall.cs +++ /dev/null @@ -1,42 +0,0 @@ -using Microsoft.Xna.Framework; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Xml.Linq; - -namespace Barotrauma.Abilities -{ - class CharacterAbilityStonewall : CharacterAbility - { - private readonly List statusEffects; - private readonly List statusEffectsReset; - private readonly int maxEnemyCount; - private readonly float squaredDistance; - - public CharacterAbilityStonewall(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) - { - statusEffects = CharacterAbilityGroup.ParseStatusEffects(CharacterTalent, abilityElement.GetChildElement("statuseffects")); - statusEffectsReset = CharacterAbilityGroup.ParseStatusEffects(CharacterTalent, abilityElement.GetChildElement("statuseffectsreset")); - maxEnemyCount = abilityElement.GetAttributeInt("maxenemycount", 0); - squaredDistance = MathF.Pow(abilityElement.GetAttributeFloat("distance", 0), 2); - } - - protected override void VerifyState(bool conditionsMatched, float timeSinceLastUpdate) - { - int numberOfEnemiesInRange = Character.CharacterList.Count(c => !HumanAIController.IsFriendly(Character, c) && !c.IsDead && Vector2.DistanceSquared(Character.WorldPosition, c.WorldPosition) < squaredDistance); - - foreach (var statusEffect in statusEffectsReset) - { - statusEffect.Apply(ActionType.OnAbility, 1f, Character, Character); - } - - if (conditionsMatched && numberOfEnemiesInRange > 0) - { - foreach (var statusEffect in statusEffects) - { - statusEffect.Apply(ActionType.OnAbility, Math.Min(numberOfEnemiesInRange, maxEnemyCount), Character, Character); - } - } - } - } -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroupEffect.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroupEffect.cs index 7249b69bf..e4d488103 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroupEffect.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroupEffect.cs @@ -23,6 +23,7 @@ namespace Barotrauma.Abilities characterAbility.ApplyAbilityEffect(abilityObject); } } + timesTriggered++; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroupInterval.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroupInterval.cs index 379b09f46..c7a302149 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroupInterval.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroupInterval.cs @@ -39,6 +39,10 @@ namespace Barotrauma.Abilities characterAbility.UpdateCharacterAbility(conditionsMatched, TimeSinceLastUpdate); } } + if (conditionsMatched) + { + timesTriggered++; + } TimeSinceLastUpdate = 0; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/TalentTree.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/TalentTree.cs index 744ffb132..92736e1d1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/TalentTree.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/TalentTree.cs @@ -34,7 +34,7 @@ namespace Barotrauma if (string.IsNullOrEmpty(jobIdentifier)) { - DebugConsole.ThrowError("No job defined for talent tree!"); + DebugConsole.ThrowError($"No job defined for talent tree in \"{filePath}\"!"); return; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Enums.cs b/Barotrauma/BarotraumaShared/SharedSource/Enums.cs index 20acddd27..90d564ef1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Enums.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Enums.cs @@ -56,9 +56,10 @@ OnAllyGainMissionExperience, OnGainMissionExperience, OnGainMissionMoney, + OnLocationDiscovered, OnItemDeconstructed, OnItemDeconstructedMaterial, - OnItemDeconstructedRetainProbability, + OnItemDeconstructedInventory, OnStopTinkering, OnItemPicked, AfterSubmarineAttacked, @@ -96,7 +97,6 @@ // Utility RepairSpeed, DeconstructorSpeedMultiplier, - TinkeringDuration, RepairToolStructureRepairMultiplier, RepairToolStructureDamageMultiplier, RepairToolDeattachTimeMultiplier, @@ -105,6 +105,10 @@ GeneticMaterialRefineBonus, GeneticMaterialTaintedProbabilityReductionOnCombine, SkillGainSpeed, + // Tinker + TinkeringDuration, + TinkeringStrength, + TinkeringDamage, // Misc ReputationGainMultiplier, MissionMoneyGainMultiplier, @@ -114,6 +118,7 @@ Coauthor, WarriorPoetMissionRuns, WarriorPoetEnemiesKilled, + QuickfixRepairCount, } public enum AbilityFlags diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs index 49b16f9a3..ee63377eb 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs @@ -868,6 +868,7 @@ namespace Barotrauma { if (level == 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)) 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/PirateMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/PirateMission.cs index 2374eefea..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 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/GameSession/GameModes/CampaignMode.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs index b1e2c79e8..5d89b421e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs @@ -659,16 +659,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); diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs index cb791155d..afdaf91e6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs @@ -452,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; @@ -661,6 +663,7 @@ namespace Barotrauma #if SERVER return GameMain.Server.ConnectedClients.Select(c => c.Character).Where(c => c?.Info != null); #else + if (GameMain.GameSession == null) { return Enumerable.Empty(); } return GameMain.GameSession.CrewManager.GetCharacters().Where(c => c?.Info != null); #endif } diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSettings.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSettings.cs index 2183d6866..40479d07b 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 @@ -855,171 +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)); - } - - 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() { @@ -1307,15 +1135,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); + playerElement.SetAttributeValue("gender", PlayerCharacterCustomization.gender); + 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 @@ -1434,23 +1267,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", "white"), 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 + }; } } @@ -1656,13 +1488,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; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/GeneticMaterial.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/GeneticMaterial.cs index 3a1381b97..3f65446a4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/GeneticMaterial.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/GeneticMaterial.cs @@ -15,14 +15,14 @@ namespace Barotrauma.Items.Components private Character targetCharacter; private AfflictionPrefab selectedEffect, selectedTaintedEffect; - [Serialize("", false)] + [Serialize("", true)] public string Effect { get; set; } - [Serialize("geneticmaterialdebuff", false)] + [Serialize("geneticmaterialdebuff", true)] public string TaintedEffect { get; @@ -30,7 +30,7 @@ namespace Barotrauma.Items.Components } private bool tainted; - [Serialize(false, false)] + [Serialize(false, true)] public bool Tainted { get { return tainted; } @@ -49,7 +49,7 @@ namespace Barotrauma.Items.Components } //only for saving the selected tainted effect - [Serialize("", false)] + [Serialize("", true)] public string SelectedTaintedEffect { get { return selectedTaintedEffect?.Identifier ?? string.Empty; } @@ -100,6 +100,11 @@ namespace Barotrauma.Items.Components { 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); @@ -111,6 +116,11 @@ namespace Barotrauma.Items.Components 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); @@ -197,5 +207,21 @@ namespace Barotrauma.Items.Components 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 8c48733a9..942985449 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Holdable.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Holdable.cs @@ -603,6 +603,11 @@ namespace Barotrauma.Items.Components return true; } + public override bool SecondaryUse(float deltaTime, Character character = null) + { + return true; + } + private Vector2 GetAttachPosition(Character user, bool useWorldCoordinates = false) { if (user == null) { return useWorldCoordinates ? item.WorldPosition : item.Position; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/MeleeWeapon.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/MeleeWeapon.cs index 312df8e44..cb96a5935 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/MeleeWeapon.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/MeleeWeapon.cs @@ -61,8 +61,10 @@ 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", item); - 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 @@ -266,16 +268,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 +283,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 +297,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 +309,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,13 +343,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) { @@ -370,12 +361,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/RangedWeapon.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RangedWeapon.cs index 73d89fe0f..60c128e6a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RangedWeapon.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RangedWeapon.cs @@ -196,7 +196,8 @@ 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); + 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) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RepairTool.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RepairTool.cs index 1ed8b6edc..4aaceab07 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RepairTool.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RepairTool.cs @@ -619,7 +619,7 @@ namespace Barotrauma.Items.Components levelResource.requiredItems.Any() && levelResource.HasRequiredItems(user, addMessage: false)) { - float addedDetachTime = deltaTime * (1f + user.GetStatValue(StatTypes.RepairToolDeattachTimeMultiplier)) * item.GetQualityModifier(Quality.StatType.RepairToolDeattachTimeMultiplier); + float addedDetachTime = deltaTime * (1f + user.GetStatValue(StatTypes.RepairToolDeattachTimeMultiplier)) * (1f + item.GetQualityModifier(Quality.StatType.RepairToolDeattachTimeMultiplier)); levelResource.DeattachTimer += addedDetachTime; #if CLIENT Character.Controlled?.UpdateHUDProgressBar( diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Deconstructor.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Deconstructor.cs index 158af353e..e779c75ba 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Deconstructor.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Deconstructor.cs @@ -19,6 +19,8 @@ namespace Barotrauma.Items.Components private float userDeconstructorSpeedMultiplier = 1.0f; + private const float TinkeringSpeedIncrease = 1.5f; + private ItemContainer inputContainer, outputContainer; public ItemContainer InputContainer @@ -89,12 +91,20 @@ namespace Barotrauma.Items.Components if (powerConsumption <= 0.0f) { Voltage = 1.0f; } progressTimer += deltaTime * Math.Min(Voltage, 1.0f); + float tinkeringStrength = 0f; + if (repairable.IsTinkering) + { + tinkeringStrength = repairable.TinkeringStrength; + } + // doesn't quite work properly, remaining time changes if tinkering stops + float deconstructionSpeedModifier = userDeconstructorSpeedMultiplier * (1f + tinkeringStrength * TinkeringSpeedIncrease); + if (DeconstructItemsSimultaneously) { float deconstructTime = 0.0f; foreach (Item targetItem in inputContainer.Inventory.AllItems) { - deconstructTime += targetItem.Prefab.DeconstructTime / (DeconstructionSpeed * userDeconstructorSpeedMultiplier); + deconstructTime += targetItem.Prefab.DeconstructTime / (DeconstructionSpeed * deconstructionSpeedModifier); } progressState = Math.Min(progressTimer / deconstructTime, 1.0f); @@ -126,7 +136,7 @@ namespace Barotrauma.Items.Components 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 : 1.0f; + float deconstructTime = validDeconstructItems.Any() ? targetItem.Prefab.DeconstructTime / (DeconstructionSpeed * deconstructionSpeedModifier) : 1.0f; progressState = Math.Min(progressTimer / deconstructTime, 1.0f); if (progressTimer > deconstructTime) @@ -234,9 +244,13 @@ namespace Barotrauma.Items.Components if (user != null && !user.Removed) { - var itemsCreated = new AbilityValueItem(1f, targetItem.Prefab); + var itemsCreated = new AbilityValueItem(amount, targetItem.Prefab); user.CheckTalents(AbilityEffectType.OnItemDeconstructedMaterial, itemsCreated); amount = (int)itemsCreated.Value; + + // 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++) @@ -256,17 +270,6 @@ namespace Barotrauma.Items.Components } } - if (user != null && !user.Removed) - { - var deconstructItemRetainProbability = new AbilityValueItem(0f, targetItem.Prefab); - user.CheckTalents(AbilityEffectType.OnItemDeconstructedRetainProbability, deconstructItemRetainProbability); - - if (deconstructItemRetainProbability.Value > Rand.Range(0f, 1f, Rand.RandSync.Unsynced)) - { - allowRemove = false; - } - } - if (targetItem.AllowDeconstruct && allowRemove) { //drop all items that are inside the deconstructed item diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Engine.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Engine.cs index e5849b584..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) { @@ -128,7 +130,7 @@ namespace Barotrauma.Items.Components currForce *= maxForce * forceMultiplier; if (item.GetComponent() is Repairable repairable && repairable.IsTinkering) { - currForce *= 2.5f; + currForce *= 1f + repairable.TinkeringStrength * TinkeringForceIncrease; } //less effective when in a bad condition diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs index f5cb14ba8..43cba9600 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs @@ -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, @@ -279,7 +281,14 @@ 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(); @@ -329,12 +338,7 @@ namespace Barotrauma.Items.Components } user.CheckTalents(AbilityEffectType.OnItemFabricatedAmount, fabricationValueItem); - float floatQuality = 0.0f; - foreach (string tag in fabricatedItem.TargetItem.Tags) - { - floatQuality += user.Info.GetSavedStatValue(StatTypes.IncreaseFabricationQuality, tag); - } - quality = (int)floatQuality; + quality = GetFabricatedItemQuality(fabricatedItem, user); } var tempUser = user; @@ -404,6 +408,25 @@ namespace Barotrauma.Items.Components } } + private int GetFabricatedItemQuality(FabricationRecipe fabricatedItem, Character user) + { + 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; + } + partial void UpdateRequiredTimeProjSpecific(); private bool CanBeFabricated(FabricationRecipe fabricableItem, Dictionary> availableIngredients, Character character) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Pump.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Pump.cs index bacb23c6e..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) { @@ -108,7 +110,7 @@ namespace Barotrauma.Items.Components if (item.GetComponent() is Repairable repairable && repairable.IsTinkering) { - currFlow *= 2.5f; + currFlow *= 1f + repairable.TinkeringStrength * TinkeringSpeedIncrease; } //less effective when in a bad condition diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Steering.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Steering.cs index 5c8e7e70e..0fdba45ca 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Steering.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Steering.cs @@ -370,7 +370,7 @@ namespace Barotrauma.Items.Components item.SendSignal(new Signal((ConvertUnits.ToDisplayUnits(sub.Velocity.X * Physics.DisplayToRealWorldRatio) * 3.6f).ToString("0.0000", CultureInfo.InvariantCulture), sender: user), "current_velocity_x"); item.SendSignal(new Signal((ConvertUnits.ToDisplayUnits(sub.Velocity.Y * Physics.DisplayToRealWorldRatio) * -3.6f).ToString("0.0000", CultureInfo.InvariantCulture), sender: user), "current_velocity_y"); - item.SendSignal(new Signal(sub.WorldPosition.X.ToString("0.0000", CultureInfo.InvariantCulture), sender: user), "current_position_x"); + 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"); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs index 6af8ff629..07f99e1ea 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs @@ -227,10 +227,11 @@ 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); + Attack.DamageMultiplier = damageMultiplier; // Set user for hitscan projectiles to work properly. User = user; // Need to set null for non-characterusable items. @@ -243,7 +244,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 +265,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 diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs index 5fbb40733..875942fca 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs @@ -114,6 +114,9 @@ namespace Barotrauma.Items.Components private Item currentRepairItem; private float tinkeringDuration; + private float tinkeringStrength; + + public float TinkeringStrength => tinkeringStrength; public enum FixActions : int { @@ -240,6 +243,8 @@ namespace Barotrauma.Items.Components 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) @@ -370,6 +375,10 @@ namespace Barotrauma.Items.Components { tinkeringDuration -= deltaTime; // not great to interject it here, should be less reliant on returning + + float conditionDecrease = deltaTime * (CurrentFixer.GetStatValue(StatTypes.TinkeringDamage) / item.MaxCondition) * 100f; + item.Condition -= conditionDecrease; + if (!CanTinker(CurrentFixer) || tinkeringDuration <= 0f) { StopRepairing(CurrentFixer); @@ -476,8 +485,8 @@ namespace Barotrauma.Items.Components { if (!character.HasAbilityFlag(AbilityFlags.CanTinker)) { return false; } if (item.GetComponent() != null) { return true; } - 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; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/RegExFindComponent.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/RegExFindComponent.cs index ed67a0e46..d19349597 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/RegExFindComponent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/RegExFindComponent.cs @@ -80,6 +80,7 @@ namespace Barotrauma.Items.Components public RegExFindComponent(Item item, XElement element) : base(item, element) { + nonContinuousOutputSent = true; IsActive = true; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs index 759893105..39d5a30ae 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs @@ -64,7 +64,9 @@ namespace Barotrauma.Items.Components private Character currentTarget; const float aiFindTargetInterval = 5.0f; - private const float TinkeringPowerCostReduction = 1.25f; + private const float TinkeringPowerCostReduction = 0.2f; + private const float TinkeringDamageIncrease = 0.2f; + private const float TinkeringReloadDecrease = 0.2f; public float Rotation { @@ -560,7 +562,8 @@ namespace Barotrauma.Items.Components Projectile launchedProjectile = null; bool loaderBroken = false; - bool isTinkering = false; + float tinkeringStrength = 0f; + for (int i = 0; i < ProjectileCount; i++) { var projectiles = GetLoadedProjectiles(); @@ -624,9 +627,9 @@ namespace Barotrauma.Items.Components { if (!(e is Item linkedItem)) { continue; } if (!item.prefab.IsLinkAllowed(e.prefab)) { continue; } - if (linkedItem.GetComponent() is Repairable repairable && linkedItem.HasTag("turretammosource")) + if (linkedItem.GetComponent() is Repairable repairable && repairable.IsTinkering && linkedItem.HasTag("turretammosource")) { - isTinkering = repairable.IsTinkering; + tinkeringStrength = repairable.TinkeringStrength; } } @@ -636,10 +639,8 @@ namespace Barotrauma.Items.Components 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 - if (isTinkering) - { - neededPower /= TinkeringPowerCostReduction; - } + neededPower /= 1f + (tinkeringStrength * TinkeringPowerCostReduction); + while (neededPower > 0.0001f && batteries.Count > 0) { batteries.RemoveAll(b => b.Charge <= 0.0001f || b.MaxOutPut <= 0.0001f); @@ -673,12 +674,12 @@ namespace Barotrauma.Items.Components { foreach (Projectile projectile in projectiles) { - Launch(projectile.Item, character, isTinkering: isTinkering); + Launch(projectile.Item, character, tinkeringStrength: tinkeringStrength); } } else { - Launch(null, character, isTinkering: isTinkering); + Launch(null, character, tinkeringStrength: tinkeringStrength); } if (item.AiTarget != null) { @@ -712,13 +713,10 @@ namespace Barotrauma.Items.Components return true; } - private void Launch(Item projectile, Character user = null, float? launchRotation = null, bool isTinkering = false) + private void Launch(Item projectile, Character user = null, float? launchRotation = null, float tinkeringStrength = 0f) { reload = reloadTime; - if (isTinkering) - { - reload /= 1.25f; - } + reload /= 1f + (tinkeringStrength * TinkeringReloadDecrease); if (user != null) { @@ -747,10 +745,8 @@ namespace Barotrauma.Items.Components if (projectileComponent != null) { projectileComponent.Attacker = projectileComponent.User = user; - if (isTinkering) - { - projectileComponent.Attack.DamageMultiplier = 1.25f; - } + projectileComponent.Attack.DamageMultiplier = 1f + (TinkeringDamageIncrease * tinkeringStrength); + projectileComponent.Use(); projectile.GetComponent()?.Attach(item, projectile); projectileComponent.User = user; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Wearable.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Wearable.cs index 5c3ccc0b8..62995f7c4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Wearable.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Wearable.cs @@ -5,7 +5,6 @@ using Barotrauma.IO; using System.Linq; using System.Xml.Linq; using Barotrauma.Items.Components; -using Barotrauma.Extensions; using Barotrauma.Networking; using Barotrauma.Abilities; @@ -49,7 +48,13 @@ namespace Barotrauma public bool HideOtherWearables { 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; } @@ -113,10 +118,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; @@ -173,7 +177,19 @@ namespace Barotrauma HideLimb = SourceElement.GetAttributeBool("hidelimb", false); HideOtherWearables = SourceElement.GetAttributeBool("hideotherwearables", false); 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); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs index 54f1daca4..ded5ee66a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs @@ -842,6 +842,8 @@ namespace Barotrauma } } + name = GeneticMaterial.TryCreateName(this, element); + if (string.IsNullOrEmpty(name)) { DebugConsole.ThrowError($"Unnamed item ({identifier}) in {filePath}!"); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/RelatedItem.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/RelatedItem.cs index 2efb1e919..c2dd49b6c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/RelatedItem.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/RelatedItem.cs @@ -167,7 +167,8 @@ 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)); if (excludedIdentifiers.Length > 0) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Explosion.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Explosion.cs index 3d0f8ee77..277b55925 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Explosion.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Explosion.cs @@ -340,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/Levels/Level.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs index e32aa7ed7..5fd474745 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs @@ -2563,10 +2563,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); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Location.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Location.cs index 153bfa954..4fedac730 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; @@ -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)) { @@ -1111,6 +1113,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..76b284bdc 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(); @@ -671,7 +671,7 @@ namespace Barotrauma SelectedConnection.Passed = true; CurrentLocation = SelectedLocation; - CurrentLocation.Discovered = true; + CurrentLocation.Discover(); SelectedLocation = null; CurrentLocation.CreateStore(); @@ -702,7 +702,7 @@ namespace Barotrauma Location prevLocation = CurrentLocation; CurrentLocation = Locations[index]; - CurrentLocation.Discovered = true; + CurrentLocation.Discover(); if (prevLocation != CurrentLocation) { @@ -1055,7 +1055,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/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/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..069c59aed 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)) @@ -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); @@ -702,6 +750,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 +904,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 +1005,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/Serialization/SerializableProperty.cs b/Barotrauma/BarotraumaShared/SharedSource/Serialization/SerializableProperty.cs index 5002ddf18..c3e93bdd9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Serialization/SerializableProperty.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Serialization/SerializableProperty.cs @@ -88,7 +88,7 @@ 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: { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Sprite/Sprite.cs b/Barotrauma/BarotraumaShared/SharedSource/Sprite/Sprite.cs index 6c0eccc0e..40d2a1742 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Sprite/Sprite.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Sprite/Sprite.cs @@ -236,7 +236,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(); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/TextManager.cs b/Barotrauma/BarotraumaShared/SharedSource/TextManager.cs index 06710d345..c19d2db20 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/TextManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/TextManager.cs @@ -457,7 +457,14 @@ namespace Barotrauma */ - string extraDescriptionLine = Get(descriptionElement.GetAttributeString("tag", string.Empty)); + 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()) { @@ -468,12 +475,23 @@ namespace Barotrauma 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"; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Utils/SafeIO.cs b/Barotrauma/BarotraumaShared/SharedSource/Utils/SafeIO.cs index aef87c736..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) @@ -251,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); diff --git a/Barotrauma/BarotraumaShared/TintTest.png b/Barotrauma/BarotraumaShared/TintTest.png new file mode 100644 index 0000000000000000000000000000000000000000..77afc5ae9fb6bff9e517e2023347c9a5ec513924 GIT binary patch literal 91346 zcmV(*K;FNJP) zaB^>EX>4U6ba`-PAZ2)IW&i+q+O3^ik|im!W&dpyH3S0;42A;+i1ZA4_`Mb$k(F7G z+a+=!DPv>ccUoXEiU%%&9<5#_~-j6Mx_jeC|eW8WOitq4*8G6`Zoxi{E zafwe{@#XIrKlaA&)%3;|zjR;h)8ntO5V<;qs6F#;8$kwc@&>M#yEjbNF;E+!;);4UHMP{RGj_=dnbR`#d! zLN%r*4z?iV94A*9pOgxlQ={+AbKYbImRHlFCzZDdjDKUw_m4y!kEf`_{L;y{7uqTuXgxy=tqy#g+ia+)8^}ZLRgr zn|5Bg^X$$8dLMD5@r*pmct;&=^bhm-o-cppd%t>p?dvnnG{K2k<~!?bv#+o)rS+`5 z%6eB_ZS{?*?X;hrciHc*yY2qRu7&GoTmR|Tf4*zs_pZgWQ$FzhW7l}<`~H53Ae?0P zj2(+lV8>r~0E3S1*YZ>u9Zr|L`*S}P_N^UiLb#Y(gO&|ZWOD*I+4J?t>UU9Ip zUuXHpNaf|(<-4A`3;q;xW8+qtGYnQLE)2w#{=VAh;)>YEcta7w6e8Jymwyy&bfvlb ze6Jiw;YQ*tKJg{OVj15%uo5iko%5@~u%3WLzPnQGt6+>HewbSBX|x(+%UAeVs)a4} zx5PBdXh|FZNayU}GHhj+bgy^hZ}aB&etG56x#Bi*olksc%(-7|M42FTf1e3hue-OG zSn@rQcMT(A01ta^(227);GP|CFXqG-^La*j$Gc|0`%7q(Fc5*sH>dHk^Z6M#NBhFc zAR8N9!_J}Zr@k{69y2h+JSTQPYnid+cCk3IE`CaIi0tir^Yhi&=T5u~VJq(TZEh>F zi~CD}KwOpm1lwNjZS9$1}$W8>kytT0}-94qf{9AA0#-^Nbn#kKXv z7y7dW=lf{N%W$e0TtZNcriO1!Bu(y|e>>dD> z!UhD^dMz;W0{CVr-^5jBYBPPG6p7o@0-jvn**h!o0K2XscG5pCu`ev`dmg;BaZfc} z>wURhaC&hkEMZ?a`^!z?LKtsb@01k*{5X2yHn1(={VqACwFVvs_+b7QAwtu*VCFyA7iX4*7BpyJ~wD<#r zV0Tt47cr%P&*{PRvGKbC{gWlEdmE_shokZ6^1*mN!Q5xQA`HQ1Jabn-m*u+n0=V2W zw;>z>8dw-V=AKXxEHntSGs^|kpT!xI0P(!{gp*ErEukJXyT+^^u8q0Y#u9Q}>~kJ$ zY8_u@do~noV4V%lv4|?X9j=Bgab3iESR4VcB2nd=P^K8gk0i(Mdouwx4m3)->y3o^w57g$H%jkVIEE_`+o zViR6Ql&XF7#17*~P?m_n&szV)1Y%>O*bUH_zw5WwdSWN2epidnhNKA)ggM?OM>gcJ z7>wo}W8#@$cEq3uruVR1n83yvdm9`3imBBHe#Fl};I#0)1_iB{(fSBdsFzCg<#7Y9 zRwl6yA^XG|96NW}H+#{D>Dl;x#o>+-!Ed7a26P9W_LbNhD}J+31n{^pF34PZcVKLnnL#E5&&dht}^=rwBj9g zqbjcab%2shnQC(5@5cRk77p@u_88&>tIBN(-xvq%7om~m0|Xs-h(HzBHEP~0`peDl zNH~*K-7nS$GhrEkh63tMI3b|QGr5&U3;{NI4b%y|5?)w<5uT6;Q^X6fZV5*(whCA{Ryzl2afZpnK6eKv?So}8pD$@4ZP)< z*wF({KYGLy+khvrfUG025t73;jRrD48v@F%<9e8<{)SBG4;zun7=)9x`c(ic9SBc> z?*Tz^U3}zF#AN&CGjX z`Itgq8+M<72pj>sB1B{IJ7bV7t{XlIs(Med_JKIblemt0Yq+40jc~$_?yAZHo)d3~ zae-YdV1i#<8E!+&gxeMTp50-wK-{?5xQ}b*o{c0f5xn4#)!GPk5LW^YOkx@l7QPmr zDKQCzz5oSbcRwP?oCA{iU^9$V1DP)b zw~k&#BJ@Uu{PDFC$1{kwBBFe^gh{b_;3Y<#UHn4$JxENtS7k@{9 zysN-L4SsKEBwPh2+TYm3|G<|E2qkm?6kcQeh`RU0f!I=_8ByZ`rXg4egFB@Ki2$r- zK`{+)9+PAV2`XS%sSu{XQfnqK>stzAdUM~bV!QA;bTK4{U0^|35b%i2hO!}Ou~xW4 zeg3L5~R+&zFzi*8JZY>p=&p618m@VOM&zfnZp8SVElzZgLA@>xEOYUB{o(2nB9j< zD&zv&Z_40>7O}gmO!JaZY?yucQC((vc1BQUv$tKW{aFIkFS-@)NjrS2TvF3UE;k@BJ@JL_H$Sb?QI%oTq4q4qwEt-$fv#}Oi+`F^|{T5zlLm7&pc5Pi8|dg zqCeh;=QiR)HPJ^zODKJlIh|+-odw|TuL3xjE${ie5&GZ-6isSJGc#AX7OSSqKKyTIhxa+o>B0Z&Aw!2qE@i;HBbR*-s^ecg`-B%nFh z5tj@*>J8j!hE7)*KgfYW#G}Ubvu70P1xULzy@bLT4>Aj##urjF$Z2rrs!6o*PFTtw z5I)%1-H2q!(hcZh(gD+bkSh~R<2@k)SHzG);@Kr--fM&P4%4-=ccK*z;GwI{=7aTi>W84hV+eP3DNH+!s4p@xYFe zBIv4X$kRTV1%b@vOZ9>4<1t7s*4@;rnX!dYKpqhIkU&)S3Is3$g%ry5(YewK8n7Q5 zy}AXW6fy!7Jpzw@AYX){&lK1QSOejR<+{G-CUlR=y1mP2lRx--1v1&!|g@(oNdI`k+guC+-NrLcw@GOXPSUMM;efZQ@PeBCRX3$2TJJ`NJf` zlP~k~_-8N$eB&G}^lTgX+=ze;1%I14g{&1=SSYZ8Pj&O5zi<3gV1;K6*GH*<66Mod+@AF~+FuScDBU+oh?SL1qnqc)8bRMwUpBe79YQdv0xZlaJSHnKazgjbwT2|i1oEP4q+GaSHrWgzHggiPnvo4}ors;5ne6=%Yawru zZ}rdQ=E4TJp6%IIs2JWmuFfWU>cIC0l9$zZ5R)GP3s*Mz2AgEtrYQW{wnZQz6hkdwjOIy~l`NSRK%*0K(RqlP@x8Je&s2!jTY@PZdl!ySn3rhq-u zV8bGoo;?A*JOqO55hUi~{vbjbWaJyq(eRl!Tb^xUYMyW*{940X;l?>cbJyHR{7SgM zB_LN_vY_Mx)Acm4Z2+~WqlgImfcJtYv%sD3%roX~0!Mv})s$!VdVOZ{o5$-s5Z^{N zp@mb&3Oyh*ALo%q2}i#wO$bTA44}nsoT!2{8Q+B{Fjup_e1lq@JN1n3oP6BiZ6jPSS8Ktes zcGKUGnS!^y0n3gW$_-w?7YR@&9(fYyZ?x=#j4x)*L3qfhsbzfyF)`raZHA0QD)WNj zhQl9kfT9-m1PK5-7wOYei5&4}OTUugJ;TE`E^%z49&Bi1ae&>IiYI<-D3pjB1zJbR za3`Uo0|;i^;>n>wV-S5O9kC!Mi;GP!EQ$wu_HjSy5}W&T%HF`krQrih(8xqsMEkPy zFn+8I{?0Q1BDj|cit34BLs8(Zfcr%xK*>T>Jp(F)^o@R*TpxNGyHy98MRbil0F>u` zMmjuX6R%Gza?THF83Dz~Hfo%pr~3>2V!o6C?s|e`N3%zB%o8um#rdz*{4W&05*xKC>T8R626HlK%Qq& zxgm9`=~Y&du)g6_cP|4TU~+_~hI_{NAz64i)2%8YwlZIC(}r=hwO{E|L8)R3P<2ef zhY#L&S_Xo(CpH7we7}lSV*ProZRmui_ZkEoj}LcBk5^aZ4uv1x9rX}d@Q6FIc`=$5 zfXTNN2>hN|=iZ(PQziz#mx)${G1~1Rt_9*Y{_A0zB?nx6MyJ1DddGOpbKZ;hK@}2= z`#hQPS*(pHS35C9b5Pq6UGYO0isd&@d>F1|nFuksD|Y`xsRdkt-3dO+5@M-8{y6c#ZvF9!NZriqQ%b$zn*ar zk%&Hde}Rn}#*o+ACTMWNrJ;a09E5ChiB)k2Z9p6s6kE*2iaJaucH#>}22O-op!>r( z>9_{vgJLF_4UCl+dApgG0oqNIkNw&!KwY7YZ}n)Yo@F6Mebs8E2MUCg7tgR-teChx zhxw1>v7i`DVA!hc8okQ#1R{o+X5vJIfwXgA6|&Scnk(3kGInW7=p@f%SBFm*Zui+` z#LKhihvy-6bz;aY&Iy2eD1#wj-cwR#?M=T+Z}^!yAK#fC%d_@7A&csTsM~6B*cY^i zZ-2#9B;566${|qz5&&#i7{1qwvk?71Uxp)o#5mJ!b)^8Kq|7T z1v1mbFBAd}gXIti#{{4rRv4}grBit(EUM?*L>*QDMS02ua01?c3!3`@4%m_@H8KMk z-q8@YCm+l0Vpj-&Mgkz-zQ|qR3$CMR+5_!+pasLo;qVyr& zTIC|!gUe+&;d>R{sE6?)cy7-s-Q~xVg9BO>5|9;Kge%hnXgu~DL1#p|OAjF0i4PPDtgeBG_FBIIoFE5G+V*0ND!dtuHvvT6?r9?K(>nTlLsL z$0UBf{CCglQlDxdsQ<*Ao~U|du~GjZMg&Zs=Vi6|C(cSB#~=s;k3<2o2%7{|y2mhZ z6`APs5PCkg<}u-S0X#&(qjF$P7CM3v7Q}*1LwSY4!xG^Q!;Cxsua0p)g(a_u55gSS z(7#Ke=B5X@2oSJ>IpB9}4_%2nC7Q`a$U~Y~%OXi{;hlRD2PW!}_{$YTwAiKfd~gnM zG+-xY2|%G}@(CR26GM4eE>mY*uFAm#Da<9Je}`Ld_1Jp>O);pDJ;Xp4Z_`3VZC2SD zMFI}%Q&!oMLx@b7z*PjPZX)mlBl(aYu;7cYij{slSI|99s1G`(K2lC(?Rzq<1WrI0 zIoa_u;?W%k_n@vTW_Cq;6Uc)Gc$BxC5 zo!}Wj|JdKFn+gG(P4meoq$! z2c-Qp83W)GrS`MNQ!9c;{QBx>cu%`!yPp*y-v^{c?gwN+@?D`K?mAcJX%UEgOMeJ@ z{TwXsAhtx0MhmQE!o<>N_0|oOx|_v%3x>)jT~-KNY3iRidWRzx3lA$A(<&QQuEGH^ zk4?OufGtF=X>3HK-~&Lo`3_t}Dd%C$2(9XK))jg*tF^O`fFQ;TWXPm_3DP zFI3DvLsope}IY%Js6BGf4Q}vk+#4GZy{05J2 zFj+!^oCFO_6eP;O=K7XWPd>rxV&pLGweMDJ<0#Szm<&|@?epU5B%vVJ_Ye{adWKrd(C{ciBi$rb3Zk@5GxR?COaE zAWtKi$A%GCH5Vj;TUv=R!Y5?YXsFUGq|Kf*5@kb%FIs@mKuG1|EB}UgO>1R5S!0oQ z!b?Xnp^81f0(Cma*}P&sDSglW9-@+U906#tLoLM#8i346RRLK~5u zPr~a;!_$@*XdF@h&>}yJiarYjiXAC03k?tw1QMhG%eHR9qP|@l3qe54dwNAMOk$J@ z#&c}Y0u<`ETz_UZ=7phyE_iSAsPY^Rt-z|Yyu^LTBCp>s?0)iwR#ybilR78{!GSRi zA6l6=B1_P;3=iQ6+TDYVSqKj(2Z+{VHi7#zT^9~JSn`N4;6%GwiU(qb#}jY( z5Lk+VMGxvABXa^;$Yqq%#Ey!m1K>5(?z5Jhz?iynS$G9Z8*Dy$B5=bms)Y>!6`g>M zM7U`lYQ12gPn&_7OBn8DL$Y0~e zvlDa+O6`)qmizG?6u@mNdd`4|Pv-;#l%9)~=k5a-!?uiQ4$vaOi^a4=3076D2tl*p zQ>>ULu;8}UGA~YF`Mo_#e-IeTRqV+}KBo+hNDuWh<6trr4PEMobVX+Y=jc~!bdbUO zn^v3{4B3a*Qhqc@PzYK*8|wq-xq1N0B}TYFKyw9mg@8S>d<+C(f!e~W(9)%af|&OR z3+soX#oNNxsA|37tO4&u_}{D@F&=N5a1WFol=AV~z?h;k1|GKVoKFQ>CQ4dP$nCNN zRzyW2TeU^xeR4LZ`Cw9(#Q!8k6UGA-nb5Lo@fM0R@oaRG6~=^C?f~+oi?9GU1_tZE zE6{8R*KS5CjQWR@2J?{_>Y0${w%EXg?ze-l?@8aJYo7`M>(}9I%_5h(wZe5X#Abof=^kv#lc=GNqi~~oCruZ!8fkIzw+NkZD{IV{3$do-IVEX2k>Kmk1O zwnOPh7$2o<=FpOGG$`nEeLQ=b@(EfCG|F>K+a5PJZUGk@5`K#e^3dC&BbLmB{G9iD zI*j-+>wT%#vGHWXO|>Wz^0U`NJhsUovX(^>foae5u+vB#=_lb0g=Fu*YpQ#viwG>& zu-V6oK+FAZTqk1rR{mj45-u-tH4iwm=d$dJhqJbSl;>x!E{N<;Pzx!_7A!5Yr{acz z@b=S+eY}*N(1WamxUsFZEtte%RBLQrh55Jjfqg82r;A)z5UZgX&xuC$8P@ef1-y=@ z!Gt{d?-NG#?k`wx#nqK|d5O_8-Y;8w)9VdkAR6G2VWY=3Z(|)!^E?B!*+m2(+=6Bc zc51WtES1X)7)!+UK>vu`@Mr+n<9-u;ySQ&NS&xE+OMNX!v<4Fo0q|k|_xE}@+F)Dng_~oHuUr|B^;FjG z10-3qW`Wpsh{p)?lwPf9M=vC|w}^3Z2?B&klnWPK#d=)Rf~}nl3Q+=rHEvJFphFfo zWb83n_O&9w0*k!!4-dx^fTgHaMY?is;_$Mg@D`zLn)k4AE24%3hep>N1#>sr9Y_5>N4+|o! zPWLw3*~&6wTrB(d{FHxe_?!|#AxuQD;^yf6bHE211y~Tww}*e$t>KhNCDWDG%i!Mp zfS`+^+bXv&vuRRrq&!ZrmMIZv`1JT^)B{N#VxnHHr6tH%yi(5TB>P~gQMEN>T?ABn z)rN&@$pqME-U~|UZUuWR6(J8)IzhA$fXTAc9jg5TAbh#iKrLQobqqr_77K{D?O5&b zREmZBJvj`>mPaC=b!>1>EFid*%lr;9aVjG(>=BOm**Zyp#}-lO1mg`QC>Fcfu()03lXT`HcyPo*)s_$lgbxb|-rn%y z_r+-gz<(KM`7TacEKT^OWE<=UOon$ZRFBEboTUZ38txM?KV3!770pb-l+F<}LGbmx zv9)k3(#j;!A=#J~;HLr_8J5udjQI!R`YaUzcs{0J*D&%GB=ZM$`cg%NY` zgV$@cTckw0{dk!niNkWGz3kt7EsyYEE3bgF9D?4A8~Ue%`+(b`9ZV**Ac0aw*uv`TX>rA{&C>goAlM-RuhP^1y)Y;pO5i zAib;k&}$7_u7Kdrv(Gt+DEAEZRo$me@8-FWr)iPbLLlDTLUrhiC67KMSiN%33r>r} z3frgF2$ZpUqif6X(37Dr9-48LsiDb`OHT(Yxa^k9zxL>fbH7Zv+$bXq(=Ictur-*H zRSDG)$=+3aq+n1L#@HvqU_RzjCoINLZjsp6E-1h`JjdicCbR^1m<9rbR7^IjL5%8T zSzg^0;%5~yJQxzZZe18oRN_Sdv=q)}jol%=w))vpY)YC)cz6k*mr5*P>j*0qX+3SVvQFWx@*Lg=vnHOw zxg?=U46xate$EeW*dJqY}uYJiwb&MfEYv%MM(>@;S*I;{Nyyiin{6IuA>D*2wVfMK6i8UCG*YjEH zRBV(!^+$-G}itZYjOPk!%TOZ^UF^dbMNqCAQeCmEi3-Y1{FEzfSONe4>Bt@ zE(KSzBJSW0n+3i8>2e^{7jwbW+-&odINSREMGxcmVSUXO@O9yx)?j>(ga~BCLyZ2I zAtFD6>-lTu3(!pu@lAI z+T&8KapTG}au8?m&=xVbEN)p2<#2Wdd`XT!Au_krWZm~Uw=b2b06psXD?*sHDCamA zrijt-hDUzDyYmI%vZctHC<5KI^zs9EJSwu3p8&Ma@(_U6;_XeUe{xK|kH^eScw(o< zh1@A@3|hzfC5w=(eoq$DeGX!Ix@MtZFuwwSv6Tzn*SJYI zM-Mg-V`|oF@EddFwTvu8L3WLZQlGKn1=^o}`VkZr4;EXIiZd4An`OjSw{?}QHjcP! zwIkXiB6i$H_f`o&cD!7*dNlP+AOMTB5Z0Uq%uC~P1OqJobbu2-(UW*hblS>#Hy#C+ z-T5>Z)oVXkI%;?4`{(_(CF@*HU9~t>qywv8SreBpcm=}ld3Lno3R)U?9|R-3=K>uq zq(ZyCo(Mq3CeMT1RlA)g>+!vw7QGPxdJoK@u> zL%`;56@AnhJSeey3FX)9iHt?Cu@I;NH=kM1m)I{;U~k1@G(a*vhz*~Pd#m8#3b#Hg zwRXD6shsfGZxAMl=1s1EWlsydJ$iI@1Olo>t3sTuFjaEHVki@{H+H#rxAh^uktz4h zy?qUB<;stGIZQ423M^K$I33jW6bD7OttJF)Q6d(~w^JZU6c+MOO4Z(n&n^bwX5%8L zc~6BGlu}SDw zT9n}3)2|@FRlR0*g}ZkmP_diXJfAH-crXxt*V`!~Vo&e71UAR@dBLw;AReBx zT=tE1{2IQ8^abb9VkT!<$X?RLmGeGOQ6vPvrJqq@&wgzloK6SX$>QR4FvM+VWWeob&;%4YS;5J^<_%ID(Das|_hGmglnK!QRtkESxN8w|V*TU^N!3$dfTH z|68g^8^-T!7fMK;u66Ha8yg)haRzYslF)C+Y`)IQG|Rl8x)B-o1#|!k!wD4-c$n$3 zMYTi+RzWlJhjl%AI+jwW^PP?zjYxEO&2l}Sxuf1&xUEem8Wr$9dyK;d8Pwnla(-0d z4_N?;Q3X)DcAJJiH3^R`*H#xwJWAho3c!@Q^--@IiyJtr;I(?@1#G$IdQKV5Z?f+f zH+;85=)*~CbMO;E84l@qqG_QY+=b8(Oy)MkT?(he8%PhQOb+%84Ik-h_aN*cRkKyw z`8$JpYzus+2f;Cc+%|4SeBRSX0C?ij#bE@997pr4!M0q9c(m7R`31;hEpF%*AZ%ou zH3GJ;*g(@{V7;DMGhwn)-nJ8S3j05uqxIac@*66TcChIT*TQO*k9pNYfMD4u*GF)M z=W)~ZDDiCp0z}N&5%KEniFVAF?(Bi0wq=LMYRiIA`|)Jmm_@cH z5jcfwUIeaoctW|m(-ELLD6bto@~w~-TMSpVWx6G(-^qF!GCjChj?pno@kLzZgmkVR z*OpCx-NS*O@jF&ne+!%+9)2z?uCgmO%H0~jBX+heqMKNHamReTR?`ooqr- z_n2^A(eo6i7`>k1dP3dULR$}C+wh9r@K8yFCbY99XSTJ@_1aF3z!5z9Jy*XFAuOJp zH+zH_&6y!+Lv+4MI!85xC_9i_SFpY2=9~i8U;n+#xv{36u7e|oPF zx3$4%+8^-}H9d-h;RGeWvn-e4b4KG&n~{5Q0JEuYiuAR z4p*1>gsWIFaYMWX=!fQ?1TXGhw~7do0r1rqcO>GOtX|e0BLQL3a4JvZv-`xl;O)aM z^tj<@$sWc#g-J*zq*#Ag9$12NB{5GAm@jmr*}8A7c*Bsx?cuBG@6#LyktHBvM?Ih& zaUVPQ+o@J==0N10r;`kW1Uf!D+-2BUC_DqDdXDZ)`vW%K7D3m9=tBr&o2|M0e&$aR zCG&`H-B&NSW#p%_US~a%woICNU^GCz(Fc52xwdS^y*splmT)1Rqj}5Vqe@Hsv;qDGrgPD1d!3!-r;{6dHEW3j|R$|g!xW&uKgOAmnPV~$b z#Cg`L%a8!O4$sElGDhFwxCA3aQ8W>FT53J_$-QP630`aL zdC=_fH29lP)R1es@HF_8Ln>Dcd=nCZ0rZ~{&RZ+X7EH@A2*0Bs#+N1VeET%7J&pl` z8eRE4XFORdFT**N^=us6D>)RhpB8_DTI7u(1!(4%o3hW}gB-b%V@Gk?blULd=B0pc zwmCx;EILHha~Dp^lTf5hU=Q8Kfe$11{9|ZG_WnBY_A)`@(1kFDLuvp;7V6gJxOXmM zfGoB^z%OUor;xp%LMNmPEWb_Ng2206rt>VsusbKeC$pc=9MwnfU`Y@I@v-3ygS3^z z5K}$d43-k<63cG0%Y2?4rG$JF5OP)A@pJmyT$WX(o=&78e0GQ-Zd*Ktx~++v5F#BE zrF9XVheL84?TeDINP5|vl7HsUz+~((R-U>MV#T?cqy(b zW94yx?|TQw1QFxR2n^Hb)>og(P7CYJ_de{nrIy?c;ux-AxPT2{-NW*E^FlWy0fZz7 zU^Hw5jOgx1iY0WO{ai4kxt#(F<$+#Zw(A2QXMWW4!~m`Nsab#CY?>u$f5@TlON*^? zf)QG!1m=!PQz6= zlt;$|$3&yg0gdJ*XIQ|(d)xr#o*dN*GjZ~%rQsRl1nlrEN6b53ym?0UI?s;F4>k}v zAPm_5Zf6P+l>nBS-cw(e3C|^*eThFk2i+ zLfAqgZi%QxL0TM~(p(Z+q$3eBs?eSn+t1ebGikzGtK6*x=-g+-x=lP7@obr5_oCcR zoyiyPwGmgyhU7q5?%P3u=E;$I*l2UGSp9o_o!ZH>$z_q`ovkuX&JV)JiEB}4@D(t+ zuGyW6XLAjb8ZW`Z0LvhXnt4YPR*&7UV2ejOwc1pX*e>7%khd%GafqpHn$Gis&N^^+ zjH)wmNA{S4-;U@C(SbsxEVzNSXwDhk3hPrKMi7U1Y=T<)xGfv!Vzv+6s$wvTW%X>- z^vbrk#sSFec~&Gl+s878WJZJt@1!cv-C*&4lht%MvrO7bAB^-plS~O-9!=Vg2+c?-&G!E7`Ga zv35P(<(8%Z7wP(TITetnWoAC_uewePr0m4uwojVMpjkXR8>ux*(yYQ9DcDqRZQXO( zmGS<3NCqywI(!Bmk+h=c#e}+}$e(x)Nk<~^O+OjJ>N!N*w$q`(l_c!x9G97$N|rr4 z-V^J5>#&})sMEM2zJfy$_)FT z_buF;h1-M?q0*y5oBN)~!p2XrT8aJRj7N5qqo*4C4tOyrTA%kP8=tpkkR9G@!pG7* zr`1~e_L|v(nDD-+qn6CajyG?d*esKCu;EkL;UD%+Im+4Gd~js9qZbeq9>~A9X-bGm zbd28RDeH$nWk)Jv?zWd&gPCxZOzwVRnB`QE?eS@W0-h21ymj>*xP6QtdY|- zj}{R)7TI*9lX#sY&X?CxEw0D%_1Dh+pJ}r7Pk+ON?Yc^q#_+_*9q?WtnEJv-8E%j$ z0<}SMmmf)th(o6IL))zk$eQ;vR<-~2mDxUy;>klWu)dPlBNTr74Oev#;7+IV5m{SR z4=jt56f6`q6KCme@XUqSvpp+71w!Xk{$CcAHu32)k-7x^3L&>!%;mMk(-W54u94Fn z475E8eL|eWqfA(wI=41v1O38#E&E)i^FWu*=MV)V*7A&%IGfC8|D=D-=fKh*XP2O+ zO-sSaJ!~G1+q}3Xp!G})?ww9LDz)>4K!e%97hq}h5uW(37o`Z)=k4tegEpoyxA$m}x3+QL0q zVGVmOCc|32hjU8-1awGo=pzf0F^aClx8U1U-zA~7h2Al1mV*+1%3IDb@iU>zF=P__4HF4umgxoh|l*I{liwx6**4HQ|oNH`~J}!X61C>V@Yt2tbQ0*Cv z0m*ardRy&u*~)sVH0K$ky~>xMLfIL%o&kymReA47NI(DKZdMPp^MMxGBwi8Gi2aC; zGu$Yjk|aXG1`RwiKULh5y(vyb->}7+rmax}CZ!&4PWs)B5&QCft@bgElJBk?+e?49 z!2%QaU$n#nlW~e&lr$h`?5jDSsK5)mvtCC+|JAEFB-OqtNZ)X-iRT>_h1x%5DO1(F zJS)xzN(T_X|8NhDu(;Q`_K76|=ZoT;IFPLx5RCovX1wQR!QYl!PW<8+*j zW%(V&YN{ysK_#c5&`-MYn7uonY~dkbW7|s~b25)S0s3e!V~9?Ybq0wnyLyXU^}LqT zf;QCSGpU7Cb!cIfGNIFvBRI<^rRCwyp|DK@=!>>D=f(?0*>Hb7?+<5Wd`2grSvLZ| zn{ma|KdbDW6(m+ZhV7DXd);mNa{75ELOE;@NtnQFLZ;Jy9oFgje0!kP`?bg41IFac zZ2-+m1uW0yw(%ggGuxGgbu2Y+cR7RZaZIA&6TY{dkA4$VobZD@!|X~pXDfb+3Z%mF z=Wyx^9SW02wx^=s*K`I#a8Qo3h@465>_j-ivWkrj)dY&V-$o(Nm~G{oqHgu=6&axj zwZSPVn3{v#8?Ru>%?SfP*ze~Jv@_=30wQj&R|ondc!L7~Ax6QWv+cQ#1T~kgA$e3` zY8+YX;HW=5@;~_d`nL~=0Q+F6vd*y(XtwqikF)G>L}_sW-R_ZtobUA_KrK1&oWj~D zTb7Cwgpr*vRx2}AfR26y`+g1xcqT?SEYp{1I)(>3s<#abr_U2m^JHniw|Z-N2r&2- zZamjHjI_AI?y~AJc|V_-<)|Wgk+n58+pFs#qQlHzyyHS>IGNE8+wS|ob&HFc(GOw#lZCt9Xk=T^SYkjtjc(*7p;V3F6M`zV*os_SN`p))e0%IF#eP)^9 zwGa+>(gEy+_psnUTFQYe%I|YV=fl5zA>fDmQ3P#X5N6|;e12(5~njM_mh0pICMLtIkK_o59ny``gJGpSS zHJmw`?6o5k5YI#+2mIcLclaqIe)fe;`oud34dzxIwY)*0+Af^QPrJZxN6OBp>vlnR z%idN)*uGY)mFEV96l3S3DeP#nJZrwTSSTFxymDG$5p8ZO7S^L?VyD()mji;@<{zgF z_Pe>u9z!^P_;apQUAn+}Ppa{92szxx=6;~?_4e1P;8JY;U32|^|Gxgi2U`9bx@YF$ zK|DS0ZH_q?(@mePftN$H7y8gopmA;uLDG*1P#l`|X_nl1{Ng5_+N=)u?7ekVoZGT5 z+PJ$XxCD2%;2PY5TjTC7jT3@faF^ij9^Bo72MZ3t?RM5$d+)X1J@4Fi#(4L?-85tL zH)qwaYSyfp^7Z_x`8W0(v>?gVJR<4_=-l60JCBtfqU!xZM?IHZ^l_j~eye)ki!53* zPqgY&pt1*a_%Ng|de@U7V4Ko&QRhdR4D~z_#%Jq|$ddjuOt8S7!t!k&oak`_P(%kOSopADP1H2!Z;2=rhD!8) zZz5B%!ZdE$c={PN)i>{Y&v{h$PnHlOM?4nglDUyw5l zIvUONr&LHv>@T%DNGx1@7>hn0+d+l1_o}Z+-1c>Xi1C;MB@p*^Vgt5EH?_A*HN?s5 zKDN56?KvSl=S!AU4BfgJ1}aYuc+?PDUDH~u#Ws&n#@kKhVvgFV*wJ`=+1rOE+a}=- zx*pu0`sv1@4wo^S*F^cfYlGOu95sbvsE4qG!yc^P9X1hhU6b(dz5cqr{$ihj(d>sf zkF=<8+<~c?#l8$-|v3xbSc6)!GeMVTV z4lU9!erGhji9fBl*>hxXe#}?-2w6#@)*;$GT!{9;DQ~;+aw4~~it5?HRcQW@H~%uW zcr?C?AoKn#R*SMXgs0lxhGKSZn=7Z}^!>rNh#N7n`Qse&tR?zr8_Rv0Z*;!J>ridU zw}YdSY{mq!R|0-}#HUOj4drc;2>dL@_H!l(8H!~lZ25s%xM#VcK zK;CD;(0KUdrZETbTirs$OZA(G*m)3ULxZ@q6o}~O4MpuVN0}`7%Kz;WJr}!MS3^_- zcHayWu6fa%Pu4Wk^BeS|L4rhP4NPJm&_7Q4Ltm*E`dt|6M>9?Fo74s*8c;^ID!rFv zF2+}e&+DibTDbMR3Q-4x4F)t$hfVCFfk>9D)2Tx73wHzxeEe>po?c()L-UmnMq;K+ zw^c?Oey|_zRA*0L_s;zNas_OWT#51NRYY9S26Z@l#O`SK`0QqJ{rKW{5x8oqgTKHu z1GB|Gs#XcN_@?gzu{CFQIp&p)hEuwtOL{DxOk^2O-V64pPstuwEZ&nACa^(w3U`=% z={`)n%Se;0uepA6xcA5-9?@;#{YlD^{$APEj|;i=)h$vU9@kI?v~4-i)L*<*^7Y_J zfB!rwA^iplL^u(EaOY^?M~?n$twm`O+ONfeYmkhjhu2QIl-qXuHA69z>H~VnJ@3p` zVo^8Tm)VW;*-g?9eS5oV9B{IlU#*EW2Df`HOS9YfWDl;fh8c=qnxWtXfXo(=CZXTZ z`f5*2zJtK0N{lggRAblo>w-M)_L7QbaR{9%^)gdyMEM=>8V)cfL9(8EK!UT~zT*kR z{>@`^p-)43pjk=#R4M1y=7wsO>On;!D+8f%L=YbKFm0CKjDB&Sjk(CaGfJ(_=) zx4k!S)hk^6l4L3DyJiG%+L91UF)<|>F|mJSDgoyw$?%O6l^D&TAfZiIPD-AV z%cm7fw@stY5EwF}VKLwHV}{vcCbpoN2eA?cP6)RQm1&}tuNg6L6D~YGJt6ON;$2tK z6!}2Vj$eD*ZL>EVd=#SUc$SRtJOc$z%%|n=r3d~YXhHDC*vg+$y~sOK0*>xl!@{cE zF|v03PA4(cF5f{3kY5naqI~uU@-Um7;XT6&)09yOpZr9P+)8>uv1l5$!Gov6a6T3r zCo_%}7T#}+H>x)#8!9UxPpF|5bs19Li@47?VO2P=a{GfB`A0(%$D(C4X*3mOY%n|l zXQFaI2zG$;ias*p$h`7F+>-2?%*O7y+$KT}X{h%`NQ&$g z%CpEjh?!bgN_#n(s(LA?0lln&ye8yA?-2z&_`m?RrY=UL9=0}i&U_w%Cjg0MGT?EO=!S$qnfrD#-5+2~fzXbfg{sZ3G#e`V~e8B;3514?NjfI7Wk%f(s zjhFfF{NP%7`G0cTIsauvusxYQj2xI*nOK-@ZU2qK*+s(bU-JG-4reuR1|Mb>Q)hcu zC!nc>o2i`(#ovWG*tk0XU8bwE>F=UH^tLfEV+JerhvdKWNXf`6{gdan8O<$i9sc0> zP5pOD6W~AT99*4j{-7}dGMn0%+JY5v1|zfn8@-FA*}ppI-`exL=6_QJEbgE9|3>`} zd;OuyA9m#vw+FiZ7AhkyNd8-2J`;POr3v33AKAG%fItpYBSsc>BO^u*R(38%BTg>f{V|^WUhfEKF={f7F-&`6R)ZMqqcd zv^6p}Wp=PL|D)n}$MAuh12(PE@8|#{|4|NZ4WF2ksgaAllbXG~jUf4N-AI2!{t;B9 z0)K@IpPW7L55_-;O-+7B++Q&#YGls*$E5)Ce@FbkA*ou}yW9PL!}%BKKT$-ST-@!Q ztQDOUjjc?9F8@8we?|OHBo**z=j`I-Df545)c=D{;7^B@2J_lGdHzkls;T3jSAPyk zHkN;wij?$^VSvvF_^0`ujoeI4{^$a*9sjHXS{T`xn}WyNzk=-_<(B`2BXgRuaI=|! z-4}co$P63-JnW1{W+rTmrtCayY}}mOY%FYldfz|Mo$bwB+>M+}Ma;oI0PbgSaQ)HG zq%?o|AML+u<8ER4+qqcSSQ%M38QECXSXubkx%gOE$^YzaQUT`QBkVs~EAV>^l9%WE zyFCPck5POwza3H4)xp8W($wkS?eq`7`G2AN8~WxdCBiU`oH-4%lrN> zN&rLuN67!kzW*`Tf6VnivcUg{_&?tDA9MYWEbu=f{*QP4|7I@4e{H!rt>p{=AYuLfhXABy;DZa{Tx8@W;C5kfP$(hF zC?B>Js{gR zv%F-eEXRW%AO-lLVJD;^DGZVA&p}}b!FW)f9I!T+ChWwpK;&A6w>q^9Wojxig;sXz zYd0K^8NN6A-ki=IB)5ofG$%M#A7qyZqWZ}P=_q| z3ZNiEt3z%Kpg!w-JY7zL-$cw(i5vuOW#pHZVgo2;wo_B9T7Q;$^z-?{Au-FM-_tDU zKg{!Z3^p0vFYJ#@(;>%@&X8TVIKrZ1<;3K!Z^BAxDTTtPl zi&r;({=0-vDzdiTXJ>J;>vb*`hs;F+zQ=N&MSdqJRnKrTh*pOZp93zCTTi4qqhs2) zSAjEv&^Z8MNMXqHcxWP-p2e}CO8#!1R<)fWI-o5~^TPolA51Kb@UbTWlt(=3JHSgu zs{c7ol}EoQywPcv_v!tT5D(ejP@##F#Pn*A0!~2AL zB62c=XEo#b2XL?E_444?wCb7v{bTRPRw6VK|7oAK%Zu&J&sXb2LRY7@VLHVa=Kyj* zHJo&fNf@MD*^fKvQ-?JtQ7POapYg5WN`8&=Ehq#=W~!l=RhmsCBZ#M))s2^%jhDIN z_etJoLOy35w`o-#kayP`Px(5yczphTcjrHSR25y}vl7H}ZFM3gDG1*?r7+p**OsSn zQ>V8wSqZe?v)EGU_?5Lu;O$7f86opI!MK9wVL9zTi(ucbxCnB*K^r+OeR9Y%r-^5qPa0NC{C->s4 zO!5>Ino#%AF5ec*@y^N4j_OItYz$Y-%ZsP~%d5Ws#luHJ41vc5{-lQH`|dcMt1teu z%&$AMt9Kg{N!=lDIN-Y(0+*3#$k}w|12Jjb4nh#EB|^*guyoy7wt(~jcCAJji~31d zmyi}hh=ydsuR+;WLeGcb{JIA*F9+0vI~PK2;D~bTB1hQt_kZ-)_gG-$7OI)Ty0WxN z4bJsr-kEktk1R>$MaLj>*u+B1?jG5~x%_l{nNu)3JKGFG^2=`$IdaPk<)9p#805la zAoYTLXx_U^xM0qD44QnF%Jw;=Zt7AYdVRXT`qhV}__TAiKlv(NwaAYzs+6fPxZqlm zq>>`{E@QvtrwiY4#tu4UgYD?3!nNT_ z1CpvT>JjP#(e`e0QeeVuF>1{qiH}hDtSje6M)d-(E_tC>KfApNoY8c9MyIey$^u`e`>J{D}&>h^> zFyZbaq{0?FWg84G*O^~H37tc=kvtzrRb?v_?^Mx1dd$ClofYcZ`?(|J=VGhusxmv? zAW}&bC3U1L563|Z=kau*>ak02a9Cj-M~RTx;gB5fXWVpT>uTf_usV)X7Q(l|?i8r_RZPBW7l71>vj6A|V^@uX-xj0L)kyYz#E8*Z*Xzq}W@hHb^3swA z4u+#LwxJn5u$cyNt&wr0)&$Tuc5wT+&U#MzK{ zvAnydFV@Q#?7=Tw2d}pN)J)rB9?|}Q6wGLE8Np8lNI2R;gQ2K2VZbW{y*fzU226)1 zhwYdOOIHuZTlx^FGI>|>ky`9Jt=(iahQ~6ycDg-!&vPP0 z_3|MX`EpmrNUk^%b74|r40LBmmT)K4ffg49CY%pVL_!`kl8hUj3h69zF(M zWea#ZZT&i&JrneQd7_VGHMX-;2u3%C@4-^8ZEK@_icwk<8=2YENWDtUsGaT%^e-j^8+d3 z)=>nKJbAL@Vbyp>SgJ-fUfvKapZTJGXLo^80Bm z-%jwKC31gv=q+1+xoLV%Ss2!~MxMV$o;_5{61@6~e`+^lqwS;@Rgz^Rsd^SZg=}1} z3c(G$TcN0rKawXSEfD7@QDa*a&lC_Tl3-x_Breu>kOD0pPpSCg^K^y|TA~j1csyy^ zw_WYr>-&V&#g*=RXu|lQ@`gwJ{qo56?yT7yk@r;LG}F8b0BfUod=2q!5N=HNa-TVg zfXG@4u=Sua)w;6Hb^m+I2R-tNJ-lQx(r{=Ist9zE)+xGHX?zh82p-57>|jO6?G(c- zRy6}7C!@eahlufOd50hNk8%E26eR3D*ap!P{-@ttcHxLUzz@b8{z|X@7_rSE@5zF{ zl$l8+SFFl4P@Ndxw#F3NOZLSSVW044>y4?_H|^00-u|(5?GKh|DqR~=X+^TK!;eV- zu7Ro3Q>KE%G~v?Bh9y5^9qx1#>)Z;&2v!hg1AuPMVp<}IB>Me}*}lpC=t$>8i@ne4 z>iW%qLb0N4NiV?z$UswOG^=K2;(?#2(1vSFpL;fQ+GGP1h*eQRY&B{=!;RR3qZ=bM|x6SuYzUbsT+BvY9Ie7e#2qgD+G3uWQ zetj#7q-%4C8=B1oh93;whQ)<;Hmznor1CF{mWhuq9cCUxmM<-~J*?XboOEYB1a^l_ zMg~&49R0|kx#fE&c+WB(?Vm1C>^*wl`kyTNorHC&x!ujwLv|eq3We4dG^K$oW2`dj5{+Tn&R0uu> zAJU(W+x@v`F^|kqSV{aB=~td`0yO;g0a6sr*-I1eV7!h~)K;!8RCoyG(6{4b`HM2~7W)}#<7Tlc#sb7*vWKgU>a3Cs3=FZXurl#e zH#c1Db?y^~eqAw?GC7Ihk({BhfJ6SMwGyd>7zky|4{1yaKcj5cFi#}pEIX+y+7`shS1Q7c zmU47cQ~N!{nNb3@Jn_fcnqIuh!=X!QN@^X0sv=@5Ahzgm!)9jV)_3E!&V9{waRBc9 zdB8^7F#_p*slm^yr)!~ayOt!}LZ}CwVRUNssu7*J(rLh3tE_scwDHiCBL**_)I7|F ziKMc0a@Jtv*-Mq0R=${1L5P&!fHugWo7|PMe(4FR_r*KL(m!c4Hi6PQ@;Nds?_9@(7 z20A92Z-HOR^bB4f7B_yaEOy>(6j^E?A0Ov1d{|#wn`f0IMul796_dA3tV2mpHFaBo zCyAh>e24HOQ<@DIp_iK&BwEe&P65Vk-NaYW(n_dQdJ^Y+=hM!bQIuMPt_kC9t1KF9 z+kp#Wyb2vacaamko}#6?e)Pre-!M_hD58Z>`rRM)9T!pd3>&wdx6i|-@_^4sA771# ztV)Z`KP4~?78D!01r)$F(;8dw){CL`Cdl<8*8Pk_C6>_<9VB?XpSO!MrNbf8ZDkqg zX^xcC3=d(7xQAL=UJfi%8A>AVo5p1IkOT;mhJRh+L#=J7EJ-}ze6I!^ABI;h3TI<8 zQ6Tk%7Wc7Hh0C7ne9#o@m63V3V zsxEDMGuqCP7~0koSA^>77yv{v0U7w8O@2pB@6UVC{2Yey*;^P8HkS>aotp~irS$!7 z#s+&-YA^zqx3z?$GS5lq8DSnP8cAl`H z#z@kjy!7t0K}oliZ{nR>M2GON#6sDju?PT5p3S8iN}OSC&_fWbnMgmDAZv@HTkupc z3IvezHX_gjf1zwA>m2XaOYgX(2|E3pQ?$(#@_*iY;7c++EKGEehq@tdYimOnbpRj9 zw#~+l#}+jNw5E-htH>U5NU*1q#l<54ywOw_QE`&(^f~bSI61KmM)!%#DXI()r;(S&^W3JOvPoSkV``{Nt{@Oi ziHl%GSIY&QuiJ@h?I4@khB}!&f90@ZiEsC~&clqiNv0lXqt#la0U~2zu-4tKmr=@W zc2EP63lgQ{pELVZ?3T#2tbOx4sQNEx}Eoqr^$ zObKV);Cp^y89}&b0)2`x4XzS!SW+q|shvmV)>j_I=uuBewJne_2uKW3d84w9O8bff z^7rRuoVkW0d3rGucv(4NH=ja=J(q`S+E|*^Mlxc|aIT&R07BnTfJ!ZC2ExfFb5n*j zaG<6pyh&!%sGd7>DV1#^xlL13YdI)II4bFzv{NcddfRb)}gy0u3|;StGlz&Bmm^sprYhD`$V zXmS$2>8xKSulnDA@%KEe%YHuIto;dfSCZZ3U9)=Fc@ziteyXkxB{`7P7`j9>coXAj zJbX(Ga+xl_4mb9OYKe8l5n6`;C=M+SLN9;(mVz0pXKXSp74=SdLKmKsep+eoZEB5s zgmE$)GNcH^wH6-yaCx)$#8V0>hlP_ck%czjg*d-p2e zle4G0u=i?us7Nk%J!rT=AHMkc*4NZHZEuCKF0Gs4K+c;EcUBgb9a)qjR$8(!esEY7 zWrbRYTBp!P+GvblOR#?cOMNvd?jAp54-Gs? zkbR*F8)n>+yPr`7jySY6V>TwNVNst?Zc8xFoZ;(y2dddbY34Zm61 zoB73Wj~%U?X^cu5WeB<_Wg%x1qxnV|B-SW*K$QQL4MrWnR%cioH*rQw>=jLsW2KYZ zzz(fdT^L+S>s(Hcdxa3z$oJ_-MGRSta=g^Dg1~n@!bxY}^ zS`yYTx3(YYs7EO*iR99$A|*kwmtO!rN4wS1#ncKuANbRW`2Dvazu+V#8C|yRwybXL0VKYf%?iOYU;=9O7lCd zK)8(VY+-iP7qWrj;hn-k%IyZQ)e9=|lmc&}EQD|j_X#=WgBltd?2*Wx_yO0ZVgR@= z@n?znWx$U_o0*xINuS`!zM*i-E0kzBms!0*ma;?0aNrONJ*5}+SSpcf90If|O9#6J zr;rHmzU85Bp};E_|M=mHcZ#R26lw-cUnT6%(A?Hw4K@wDa1BI>SrpW^q}vn|amE4` zhdNA-5u+v!>oNq&TLlXm2~saDpI!Q-yCJEk*$zo+@E*p~II`8_g8b(&G&V&OS*)oU zBb9MvwS3(dEKCsyixrJHbaViwnbIn`3?@#yRM%oYgQPeaONI51%;*3WFYV;gFZ+am zW3QMHeKbQ@gn$xO7cYT7Kb$l9EWGh@=o< zGuHdai+;!S9lQPylbx^0B+qHD=Toz`i&dV7AJAgBxVv((D15gA;9^Y)Mz&&$gCG$0 zmlilr_=C|N@0&FcRfLfUI>H(68dK61izbH4EAuLX6?*&xv-{)=19I&f?-U z5s=S*uiehskKcg+1$|s-hugL6gO7Dv)=k_bJ0bm?i84W1a;z0}F5WWlkSQK8_B*K9 z7RZC&kRO&Fyd$>Eo=PU5xo}7d8-te-VWr)~psn3UC#S_NNc-BSS$&O|frl)GJgvSN z8AnetND;j@H^4CMbQ+H-KU`C>6(n+0!2NF8>1()TJW?*aH~@ommoPjTRD5eVy2I?f zJiRtZ(gmF2SXx<=I1(fWyoIb!qzu_CaR#t+Ot0VnYLpm>OioSk6-_y&fBr0s0sg5L z=!V-2q@K#kmLWE#cyW1m_m1yt*XZ9S!Xp5}^da;5X!J7Qr118zZ$YlgE7WOmPALbZ z)^^OAdvhWTQn(P<9{~&j)8d$f9ranWl~gKJwFOtW0HJditZt{Qu3ar_>m`q0%I6-D zHh#pj7b%El$Z${|K%catl{17Ajo?`2NInP*2^kf<*6mFsT6JnNoj7I{j3wzPz!pY# zY%46SFizj`(y-`T6PsKX!pY;Ci2@~?mV`z>%l8*+3VAaEWPRaI9G5EadZ(Y_=gw{e z@U;MH(Szp`Mn*;uYYK3b!F~&jO$@qR4n<`S(+(_P7IAT5GQlktg0 zE1*Q<`{mdJja_mu>P^?C2@A}nowAlvFisTOPqeCzrXKo`c8ejajn6!i(E)Lx==PB3 z&WiiwYrfl`xT8dACJ2HS@Wlb|C8YtB&}OWdgF|mf8~p3Xu*fhV2`NP2oyOkdiBj2G zvlVh+INm|l6h|pjc}_^glR{yaBH~3&`&OJ>*nOm@yr>Dj1;hcP!0N$U@X-9^M zCC<_|4gg7^Wg+y?dkA_dO);Xj$~THFi0b9GdaM+FDZ?px%I)!~q#!_2ft(6_NIB8e zJ*p`HoF-|WWF=fU^6G=ga62|lUsgKhrVJ+P5vSiN3-iW=ONS&HlIN`UTL=u8uxy6( zOIWSqYQ&uZT-3ZQBHa){XPRnF6(!l!CO^DnG$6b~L5-Y+6!<#v>_0dw1E9Xt%~)S~xOua= zhQ4V`xt=Zr1>=Ae3l)3Z&yXWnya!MshyIEGDHs%^j3cVa7_4v;=Iavv6sr}7KF0d9 zwKBI*{G+VE_eM>5Ol*~(^_C7i^1f{8WOO1|A?9TQmM}P|e3BakSmo=PA=?6 ztB#<=1fM50)4(8YM$#dk7K<|h?=RzG!doj1oGyR#wl~*z z*SSgNTOy3&lzewCe=9+RB619*gDB+T`jIa!5aG$FTYnxft-w(r`HK^x&#;&jE5-#v z`5m$d*N7St_^^017vwwYB?(w3n4hF8kh;4LQQam=@rOjAvYd$;d@@{drMOZ^=tvF( z6c7H78qxh7p7u{J3@YYwb|9Srfc))Pey{U{NFl>@SUVzx_2omMO z2n3LVlh6tiNM${J>Ib!R9@|k}#v`F+4(A zGtD-wp)bM+girJpS#@Fwqa7*)(BY$L{K(^)6)DH~kBZmH*L z6%nR0!c3bkS>j-@7xQV+W0wxxTOQVUJN7Lt4@Zygd{0!UoA}3B0IlkbV#bGQ)lvN< z*YRiln6Ns6X^$~+arN1p^nHd2KAOHz8~8eI|;5C zLJrl$yEDiWFbQ`~dmQ8MEsH=pz=`nn4G50sl)npLcq2Jef}Fzyf>grXbYgyJyk74z zvr>$u#-oq|%&$i|E(!fA3@C?D8XW3)K5MsPPND&HL!6T-*)qD&H5)1&Qnq@bf+#|>VjB@B-BXcV%kX{l~Oy-iJGtXqJ(jfP)u>C@eh9ERk+b6Pr`W4S#z(#!4Ftqfw*1Toe>14fS!S^? zwp1Gu#Yotos{l800Wh^*c4-~iC+f4Appa<|7oo^x_4x|@h0?f~UKE<9hLt1ym`2t% zpAKY*L#_i~GHt`YXAd30nd6g~Q+124fFnuKEdY`AK4PXk=nLD8v8?92ra&zsr$Yw+ zCkn66lu^~RkOuV76q0RSJkRE!reDU9s+;Up5Z}VGM9G(6^a;=J>vB+Yo<$OI0nCsG zkZz6}(-15Wr7#xIU!lC?x;?++HEjZkKGU+M$=+&Vca%PSyus&QI}zDmeh9 z@ib<~*Ys-Cxvlu{jZ~ZLcGjx|Na}ObR)b6mQ8Dgbn$5Oi6r7<$)%coP1PNi5y2{ zLO=$t!ZcZ>Kpdp{i6LrJ;r*a%$2g?u-~l`+Ku(ZGSy|e4dAgp!$k%~vm7sF=w(F<) z#a(LK`Ps%Rv3^$cJbSW=_DSIZ9`DEX{HVA`&q_W~!|#pXJ!15$q-5fAzS0T9%dIrF z=yHJe?QLyoGp!qYj8zn?`B(ZMLR_m*Y0{A0mzgnqFwkLE@k2sK2rz=JiY48n!y7Pm zrRh7qN<-rWDU%)cIOov7A6rY#D5J+u3nEeyfuEdSf6wb7ZuAR&N*59vDhRHpjhb~D zK|a4RFsL_@aMxzQU7;dIx{I)~{ zdOh~odq^L(e!u!n&ifp~ZalyXywde;#v(D)9vVc%%?v~~gtMD{dIO>otT1Biva=Ps zFuga%WCB_?O3pVw+AaVGlpo?$N>2jruR1h{{K>ObeYac>ym06NEkHK(6x0@6kER51 zv2-8Z%q8Wew?AYlwesW#U9Z**5`fqG2G84=YX-^sN4*45cm^ox;2dh|jH1W=3mN!I z2!&v!RCMCNdQu=woOVIUyydoY1v`P+!9JXv;OgA4KK8!EjJmQZVyoggJI=L$K<_HP zPv=v6x!>L5`LL=#jItu@Is3zN*9$Zw-6R5U3jW-jw;OAzIWyBG5*qQ3^yFj<*3nqx zumf`fATa2q%X$B<_vk3>OjFZims&DEMxX+EGs8RDe6pims*twDieA&m_ST^ZKaGsv zw1f(-%KB}Kxj$7y?zv3%T#`BZT zku3AjWDr>BJ6#TA8*gfGHHi>wLW-TbQKs3o4XYaPgd^jyH+QqdY9XG;$mZMu82~HD zv1hGFA8wF2WPNop)*Mkz3WQco(<(Sc=;vxE7MNp!oU4w>p8)JD=hS;}W&&dzn&RwXb9k}gl5Ea=loYaOpS}gBKpI~YH1ktM`iQgR{pTQkNzbqTX zpb6TeqIDj9%zlqT+Skls8oGMKwf4Ez_=hKG+b_5Pq7~=7P4p`}s~3O{-pCwu1He1S z&M10kp^~RWYidSz*7Hy_6{nSOsbRWO76{}!i^B$upsymZ%Y6IJsm=VpPG8T{=;BMU ze-OlGxOUG?fiZJN{8z{IrloZu)OJDQoteF&hXp9OCvIY%YU*difHUs{GM9qB6C|Xc z5@D#mKXoMYiR`hd?X7lQEa_QwxIAL~x;xRWHbX6rvU~GNx z6l9-l%18tkEjA99&Z)}YFr78WMR4#$ixu^;hs^j=?{lb+kOcy3Lrhp1s3%3v7(M&3#6Rb z2)rDOv+C<6v)$Kq^t@F>IMvb4V?0A^dG>SYlTJ%*6Wv6vTZweMJxVY+4bk3dPF&_a zo3BN``yzTC7MUw_ljRNa$DQ^HzZjqnQ=kI0;pg6xLx(@B6@SM=MgwGpC@D@9&_WOo zN>hM@2q@MvKz_%(fmK~l9xez^9weICF&2E?-=hp~$!Xk4@b|kt@Ck@vW`pJtg_Z2e1nX=$Qd5KVW7^U9)g=gvb@~V z6?O4By0nu9l4jJYvf+fCh76et?(2#yRT;uJmTF5BWaK6b^)ZS;M@n?>yBg6dh)wFm zae6JGLVPezy5%r)Apk)s;Y-t)sG_8S<-$dMbjm+dA)c$LQ$K z^o0d0K|xPj5PI;d9Q=2< z^OH~K_iXh;d95rF?H?kiK9#p^y1@&zFq@G0p`%iW0a)NBaY13aZ8(#}K8UY3 zho=xM-O;Peh3~wlQCq!N5cHuotpKmk-Ng`o1j5Wv)s2eb+3xYuhQL`v#GSWdc12hQ ztD;t>v?3JHmo5-Yhj+0q@U2G}8G&?i*a)2Sp6i6~%d20ET9*Wg>JL}-#wj-txWCO&_Em;LH@kC&_w58_zU3Sp*93M$kn0P4rj4T)bOp)w!TtRQ7lg6K%VBJ zsWt5v7jROApWh*9z&4H07!Oj%W7QOf7p(>{B2=1j=malwk#h1J^>~RAfs|r+U9)W1 z#YjJ;+k27O4ljHuvrb%dSpzNKy?VVGG;Lg8ewn>}=^+ugmc6b4s&Gd+Ed;_rNm~<4 zU>FCRPUwSA6bg{Bk$5~gY9C%($j~;BL%+v?jQQlI?>8$y`0@}Zg*rh!+|wPIrILsk zH$Na83}Rx(B>G&$Abl}gpMfHBmADnBebHJ&RMEMXYJC3Hwu$#Z=U@m>9c zNp3_DAuNj1bqXjx_{|`I9YD=rNHFJlF~Aa9q9(xnOQG;u_5Q96Mq%bxyT%{3GZg7n zUd*$RAsVbF9`NKI`95{FW;?3VGUR6l#V-%rQio^@LXm4Xghe&|38KHO0j@pFeBgq(j^_h~%j&8dTRsS^DG>!)k{bpD9GdIb&Wc;R%hIZ0i@MaXhLk*`nS3#baj?VhNaMBa??G;_ zxMSf!hWMXuDuq^|$rzN8leK|+e8F=PMGBR+wIP135yHM6m7A2de#^~rJ!{=jA0j4dWF$2X`^Xzo8ZXVrytv{~_NX;IR4YTop>;znf1a3Vi zcg%?d`C86?9g>$XqHgqa7q-W$?Q z%Ra>GO_ZZ(XM6GJ?%vYa=;y#u3rL1Izl^_xvmZ%~893hBD7ndajeb7;6a-L!;(8J?_g2E5RjigOn z!oD)rGx;Qsj8ZsZV#IGLW%)%|YIxccXG`hV&}1mEtJx!{*h!AJ-n`q_H-ipO70`nMzq(}T85_q)rgg=Tq~@%yv75nt<^w*`v2rG*@#$bAS#Ks{ zC_FO#o?>#Mp|kErdqaCe=a=gjJxR;tU8J(IL#dZviq}ZR3Y9ITFia{7A_6qK&8j*=R=}Zh zCQE+(ZW)?_0d<3urS;ODd-?1Zo{R@_2}D|f%k9nZRHSG!;9nEMK?~Qk%5z}*H3|;_ z0c{63rENA;S2 zTpzskd6+H6B765$n<`S$CMi&=?_xNi)D&5Xu7;V)UeG1n(n|LA+TfM*^`cnlBnwT@ z?M+ns{0XdGe$UJ;g3LDuLfNR?Ic<+hBpGocGM9W^jrj}5CVbRs>n3GOj+}ymb;C_O z*)4OwA5so>rc}+(6a0ZtVT~7QOBPSlg8owew_(gVy}r$}@7tOz4`kjw&Uu!qKy~W? zwm`FZ0Npn;W9sY|+4?Wc#Y}D}o7rt^3$AdezjpGznpz_ADV{6oH0Ax^0kL}6@lft~ zeupp&Uvk$=%Szi;eSVKQm}{IMSwRJ;Em%nm%E%}jS*DiksX5cG(3==me=mcBp3s!K z{wQy_Jubebfq2Ozx=RuB(bMfGqsQ%W)SSURSNy4RB#x~O4(di0tIDVap#E#(x@dtR zqu+P!2b1`7w(~J-*RLDXk~vOlOa_<9AN=xqi86ScAqTT~LRU^W(@$kt@ll2ZOz`(f ztDy~1MEkz+jr1(drKH#>sNir?PeI5wLZcDG*rf^%VgavNg_rWQ%V-V}DZMN{MAe4# z;!+6yfR-%m&1&qI?m=KwgBEyI4%nI=7dpN)fSW-vyv7jbn%KxeJm3&ZzER3*#Z~q# zJ`Z#Rpce@6jHKXY`QBYQ3N+KWg3|3fT&pghk6>kt*@4~S)hZ(zZR3wjEy2j$$_;dB z?Cc)L_xo!x&s!vX=?&yl{`W1}6gbe!`HDj0*JiVEoy?M2??SbT7%|#Ot9NDl=LQB? zq*bR=QmPeN7}U^7tvH;v9v8~gTtrM3AAIkkLTPmIn8zkRgO?K(VePjA9Y4;{Ld}zt zo%KwO&{$^nk#e9uvuXxc8#kf%nOKN?8mjojI9_r039ak#_5SDUXS)m>MUP21t_)~lay(fO9J+u@v4yy;JK)qFgN+(=e zbJj($wU#UDR{jhV4)R>^=MYn=8cikI{bw%dSlVoVOn)SD{d9#Sb1CZ`|p0LLg{O{Dp|yXN?#cv>n% z6Hq)uPsj%&+TiIldgX$rc(HeiE}HK~Y(785_c9sNB^A}r>}G_R@kinexMO}rR_e3R zoxsmfPkDZy^RhLL(NK;QR}MiRBg}P^Z#|;KfbCvWz^{~xnx-2ri>XdrP(CXb_6W2**e(o0wR}x z&E$dibKAf3BqKk&8U~!qmMN6a=nDE@AI$FV(k82y@(}O;;fh16e>YVyzWg`!bOmOL zrbIpXn2(Tz#I1m1z=T7rn&JE}iDS=FBlq(xlBn3!>jlNuz$G#MC7W{TMT^i9+TD6> zj>@r*u&qU`Iee^S9?T&8Fx#h3Fd@m)oVBWX*mcD&U(FBRnm>v`R&`j@s)^kN;2B}; zYSqfcrLaVTcENvn@94K5h5e>IN!LZV!@X@$;^!CfrDp}*5N)#isnn;5@dy53F5$x) zn$u<`(^(|8rQr}L>=|bDLs)R&ZNTakLe32-nl@-mMt^4o$`zDaKM%=5jf_oO@@k(6IrP*HNX?Q5 zk&E8hcc5^3GJ0Q59MB1~+~5qnSa6(4Fv6qJEV>=U3vN88>H}!8wmRVK3$ujKZkj@W zt^vl*)rt+U9{Tol=fAOJ_XR_*-^#peg}`zSn^j=W3|AT}U<=i_N0Ge~m)OjCCyuvn z-j`|OJz9k1?6F6oPglX(X}(^i?aHxj$F# zYPctHGF!%P{rl%vZ+mY8C8AdZ-n+FwmTp@OIrEw3|Nc^rnwU zMW>j4_vxgtUm$*UhpWp6=%n5dTc%!%6~*ZjM0Q+BNj{_I0=))3b_|B4_ty~UX|}k= zcQ%%?J?XhEK~9=*gLCrGSE1|N<14P_F2WzK@O_`E<2F*cdHMIbHGfSi>(QL@VdbGD z3Usy~Vjc9sFEX*FVuV+?^JyszDoiWKu!UHdz?h>10%-2m!jJM~hw`YnF%6sTM@i>~LOqphWVE2^ORQBF+ zb@w_M1;vz(j?NtW>o)tJfdP1thY^*_?d{JbV)g1}JsxWw_VF4dh37!oQbYJ52=)Ig@O_lQ+dJ^eaT5?RC@R8POFo{P}&010=^$5Y)Q6M_6Nh5$jQ9Jj% zAG^FSL%C#W2N6oj887(kP7tZ!cciUZ#7t2+Zi8aWSXY{Iy>m_YlNeGIqC&ua>}%x- zozAfa3*q;?SRNQ5pswo?hn7_xxL8>@g=h>-#?V*=N~}J2LosT2&QO-H^oUY>1a8c) zWEC~6*x_Gz`#lq254u7NvYwg?(D$Z@!BZ; zSc|ed?YxuOUOOWBDoU zY3=jFUV{O9j9!_H8;;un`|U_AhRh3A7+#{fgk9W7nY?#)WC-dHTx6Fo%17^_UV=}{ zyX0(@YE@F{u%gi97@!p(WSelUKAcep@a0hW#3O7IXxjpHlC_WD6v@goJufaoo4dyW z#cmT}YnOoHasREU%kFKZbVlGw7$G?4^)QDV12MFHeqo^`mUCMO1U5qx$ihIS8n8$+ zwA|m{KQ-Z~VPl5f4Gv|r4 z>+SCkiqXqw0+dR5NHC0%ff0VCC&@Pr4K7j)8=z_QqK-`eF{jwJAXuiJQNh$YO-CmV zrbU!ApF@VoE>8$^wDVY#VRPYp{CLXTde#eI_M1#;uC zcqN+}#hn_t+oBV^CUN=J{UcMuSwWXY%mIw&=lzc{wkfLRze?u}W^M5smr*HR2Iw)2 zH39KI+m%{dNFWn_Eq6(5Pa{Z(L)*01c_5!*+zLZoXvC&m7FzVahviD~^N8Xg>TRK^U!UA!cW9ef(mRIXo|NDOA%^u-GJ!~v+zs;PONna+>GWX; zU(zWn%;fKjDbo4woZAaJLXOw+9jo91Ps{C)4ta`_N zGKx=pvK!}7EQ=M2wz07)8UssazTWs|MdwG6#6>U{CHrKT=QFHQf`MSpGnjbx#E%sm z#fK)j-0DXkahQnn5pjQh@_R?>SicHcYNh8G^IsG-zVOh_ntMVt@`wRHlQlbo*&}YH z2_@Y*wI+|2@J1ZF&z`oP-gj$WZ--|u^Jm{<5Mb{%g5EYW?5g;2hc3j;(4yqv#i0)m zKC#?CS`LeUc)PkL8TlU4)z;O80D(%3>6d&`A`c=&0z;ODlN6&70USb^LIcnE`|w6O z!*ZBAkswynZg&Y{|GtCxxd!qle43_(K~)|mSFFFfdOJ)&DD?Y=druMYp+3ZHCVwMuQOqC%Mh zaWz~(@c{tox351Yik~LEx;{8H>fJvsbWcjFwd}br0a`XLe*Q0qhc+PS+0!-P0C6Kc zr6^mVMhGvSxX*tnWW`7aXxV%(ei8h(=kdUmc5Kx1*x{SEv$HFmvujhYbS~4~F7hs; z{xC8|pW0}N?p3a+y-12f+3pDE{~)wd2l`Hcj#v(Nop4Rn28*+Uv*@~pe_tg^J1w1N zQa}HyL^|Z9XQ$;72-p#D^ze2+D6hH$Z3S^<96x3eDg-a-!CUZ-Q*@2NMwQA-;Me>OJP^PbcLo`U#3~uwv z;%6aNsilzAxB`ZPkVTh^)vq)MH@B+IpOYnG6<;uF-mK zE4afC&Co=_og?7>J!mOvS**k&xOLouo16O=Z^u63cUG$oZ}Z>zptFt6jlVW*$?8oE zD6w&Iy?QY$V~bb9iWs_l5IvjU@hgP7#k7q!@T2*y*Oi8xysHXr~YtUO@*LE~>GN%kC zA{?U0@lD%pfN1S}8x9W6T3lRQsU}nSob4RUrmh`XWn;2>lDYz+86b)_CmlKu$VpM> z8GFCS5ZN*Pt&UdfujMC_s-^H-SP$j2Z9SGh7w*YASJUfGtmIG-Wex0Nm9(dEgg>3^ z`owSDlg&uc>;9JOBt_9CKij(<0uoTR_x2a}{mCq+IlZ&rdNfmRTtAe_(Ga)4%98h% zX_QbFNtMiq7paTaFEz*}kne3dm!_tsa)^pL{g`;?lWo<{LCc7T;*dyyI0$CqO2mup z1LF?%i1oO_;L^ZQ!Sh9$uqH#mFDOi1QrwiB+FGSj5`0XuguZ|%{H@&k=&o+B4=>B7Z1k)IgE?mpD!?VPR!;1#{iO{#S^= zf^8m zt&qhBqww#|y@=mTs-wRm2q5B3fEcTROhMefEi&3-0gCCm-MV=@b!W(7X_ zQw^OzUKrW_SgIWe8`^|DTiCbi;ns4M_HUp|^69mZfByMH$47e`n?tKBp))wKl9^s> zu6!Uuo7mc-03lrzaCiXpzFyF%`v?+8$|0B8$43Fjt_MpX8+|)_;uwTr=t!HQ-;VkNAQ?-d%*{3H*%;%6y~Z8N~$ZTzSdZ5pmiI zYMFzcJ`O}5=W5}EbA!ounao#M@Y2BJfA(M)K3b)S)3o8j7XR9-^2o9s z4|xESR!jZ_59*ozL7$GlbNq;z0nfqjy{Cpt68Tiz^``4^@qKHCqLaa{;vZnqF<+Es z^w_)Q{7F||ABSEgy8$S{EEwsw`S9a+dhP-Qy#8fg-Bbq|W(#_K%;s|m`8me~qpG2y zG4Dd!KVG*6%)3{zL{Hq>wi;l3qjllUYNnlJo{d&7gSVU?Bm~r%K;Y|3N?|5p=42?`|~tFbtGX|*?rqs{TuK{ z@%*>?1?VXi;VKkihe0Zm&C*$G@u=n93T=Y>K&_UAhhU}kzj!^kXm;?!!BCS+lKkC! z2F3mqY$|tDwc9wy*?@;%Q*Y?63vZ9H5D61sUt$Wtn&#s5uA4$pQE|J{Dd70ehbbYq z1(lasm6vJ1ji>b-QUCk%eKuQ?^PqYWa&goJQ~WTj9xu0^=&5*P{&jn28t{T7+cE-X zbTF}9Y_D}e0-4}o*Z$SXNd?Ie=bf)`MtVAs5t*jk23!Daq;Y3Yz@?}sx0Nw#z4jX( z2}BZi<3W%SY&-aAQnR)Jv~_B^_D$?JWh7&D3R!qVuCE1g4D-;}CSyd5Ana-6#3tSn zuphmtGI4D7%Zo;Ve~l|=miyc5%iHP6QvIE|E@BbSVWGOhPwjHjA28nWAFo*?|b zHdx=|ccyjVO~w~HE+Z?z#pce+g>>Uxm^TuLE=;UaTQZ~A`>-gj#tv9;U39+tP35Od zK`_ghbC(#3r6mR0Ob&)GLy*9^2;F8B@g&%LSQahSm z&*rQ2X^pZX>s`BNNM0J{0JYA36kTJ>Z`ILaD{I$|_`_OA{Wn3%YtY~GvmWwJlJqx{ z%~@l6Y@X-h80%xCX}+45&jJU}&(AtQWBbU-0LaiSK(D06X+tCO1>^7++D5xgu0$xKqlCdtGl-iZL)5Jw zEWgh?l^3|!z-b^A*SxVl)dt+%hI*4NTLA?EUv&Q4&*Zj+E1~!uLf~B}FdBHwX{Bdo zhRqWLA!YUW#`%0x9D?d%ZGs{U0b$(%xBH>gCWIVN)4|Dfpjl%CnfkZIPsfg zTkHh-0`!{uLx18}hR|-fboD}kXZu9f$21;2RwX4Rlh2<+2=MWpmn_o6!NInT(Ok+D za2zhjmCaqu$}=fvLgvOZW6bz9VPYo)PWdQ|t!%(5gIPKzZB#=yTcL$aoRyO17+*~S zi*A;M`S}w-t1e9#VIIraFu(iMHK#`cq8`Ei`W@}#uiv%topXCy=@BD{1;O8{N@}X; zK*Ygw2bSvB@9(>C#hEtgWy1QT84T-e;vL+cIJg@z)dB=F`~v0t1cPE_@?p$y2kt%i z`zkS^ne5bOFZ3*dTlteUA~&-dzouSKrjCTBf;gh7v81sIlhHtPw%luri<`9a3}f$; z73l7}*qaRD#_5d{D9@j-rmBsCUTzZJU!{z9aLZ%YwzWkA1(bo$ zUdm#pAFq+m9-a#zaNh#VDoiX%1wGOHZckvaYB^;Xih#qJeBA3YCifwx0<`?T&wAe%@3hYMd_!38)iLU*ff z5Q)vI_J0EIb6PrUdp_&&>;Z?0IdU`C_s2+}RJGiAC-EJ1ulde_(-vnl0y?I=iqTZ{ zq^w^UjcLo4W7qg!i@tvcRt>1(;;AeA(HmnKdruWcE1$n`?4oV1gn>+?z|SSQa_D)& zKxCR6C+F2zlZ~fJ$pPO6Db&3WmSPZ6Qz#@vEzf5WQsOSIaX1#?BH5v|!%7~)(rzO5 zI#G6RT6?E-6&=Lscna5a(2a+s|q@29Ia|15(!k8=^YZ3*Fr-r!uC5 z+knjxU*I!e;C*BeA@903O&`aAB&KkfRkjbJO~&>pb2U5`@`6WRW5f<0L_z|LG+IW< zoGkZVEC%%5W6|4ezV4m32j%i8M;TvrPT;|#s>)_5%zZzxkjDs zck&tVc$zq6M!*s1x;Cuu7q6IyKBdz7oYYVVHs!;r-7%N)StunaU#wnNIKyxHkqZs6 zhBe05K%_)I%*rmmx%U?wQ1DboZX!XQ{C7w^0|Q?ld^=xXFZfS z_kDmu2PSvrbeUDZ$sC}(yt8%iC5Er+;ejCc0N5OD^Ti8q+_7S%l+Q5%8JU%p^@~h_ zso?_NAYq{m@Z{>Uv9OpV#}C{$xrDh=Z_)wHxk0k$Up zv9iX1kJQCxDCXzDK=MXCbf=)FmEYD}Kxv--Fqg_LPe+N{@Q(19hL;?%fGYN9%qohh zJWSEM#5;ICt1a#D281#9l#}yVutAJu~8k~%NQ6XQ1NjNgR7H@cZVc=W?^N$ z7#xg<-&wo~$noExFG_YLjRdu%*oKo0sg0^h7ND1e;@YQ&(-`Qc!lR<2qhmsmqKZ}X z-+cm$L&GU8_Xv|=O~&R06dYq`hFXq_3r_LCx++{y-IxGOWlsO^ZrjvCLWQ& zf^?CZxJU75|EsDX4#x9~$gV!!N*c>n5cHg$QV69l)a1zlHQVY)^!L!H2DA4wYQh!s zREH~t(f%z`&^b~#=U?^^N)t0Q$L<%;ma310KsvY#zXlL&)`rGVH z3&b=6Qzd$SzmpKOofk>&JCSV|z>A@2U2Bw8`WkoibHh2gK*;+%1yhz! zwGf2*j+)R6GQ|1IB~#x{UO`@60c65xQAJubx>py^?^)T{X25P{9iiZKkS?svAl^^O z_x5V_L)XDfG1KXzv3#VQT#WCBuo}k3`Kg{fT;qrBwd>91f089LgI!~R(3syQi^hKb z@F36A-_sd!y~o3%SNe{v7Tz<;ymQ(9m-2cB2AXrW0Fcq{YG{xGm?w3Glww8)d;8ou zTbF|0=XQ;HBAlGTKnb^@cRV>c=~}-uE1y*A*`DRNR7a#0ndby5**kN6JF^w) zI}lCsgj{Td3<5Vj_P$d*PDgfiG&SAs0JWo@V%M2Qk-8edzJxFaG=s7RaN`OA zs5UYp|MqxXJrs&566M(*#}I9Ow%&OSL?xiP`}h*Lad!9QgDdTReV}H@mNp|a)erOx z$@{mQ-R_481U`PAr#Y=+tNN2GsQSzHcR2i3Lf!?fgc40SoHW+&#ca+-?$zjv8<+7B z{_&r>g;p9@h%iuctTNvK4Mt8d?D*uQ1Y@K8rkPAeu{+f{`|4XvJ~SD79v=ehY=~5~ z1B#|BGOwQ9gbyCAgnH2QRKk+5_hAr-eFSgKaUNA~mLYZbJ#5ly(O_1M4bQ;H6u>N> zqA%B3#Qf3A`7n6jeQVdP-SH0w1d4x|tXqd?i8Xd~d_s z@TNlarc7mIX=#a_g5PylHYzGLElo=eH)^X-(ExC$n}r) z{hY!Mj13L7#39o@JWNTn0BhF^6q?Q=cnSGn-PB$e(g7E=BHZZwO}==>G1rhAJj^JaS5k^{MRw8?3rbT;He3}S( zDswVTU*lEco+^Lgckg$e^=hbyC_4oW-Tm%*NxNV@d&Ers8+=4x^b&M&xN%oghJVvm zYuhN`aeuzWtH^jLWc=2*B(5+bB_-(fPpV8)K_y4z6tEwQ?B6O!_-WgSvfyFk<3qI4 zt}8?TF%ty(kC@d=qJ6$??k1~@!{-qoX-&q445J#}2X2;#Gg59`5nNyk ztu*&sfosswD8d4qFR)cf*`w|_EZK7MY9-D1yl;9Fg6@kkk9lE1)|Jh8(&7L`ec<>3wJh+_eSP)t zq+vGhd@L;^fUI~EpNdA{s#m^*pEFLDx%cG@H;HvVZSjhP4tLnEN?DRn^6i(-Y{v;AIR zD`iTvt9WPl_(ail*#Ac6M5^C?K4Gb^BE}d_3Et$x0MKJ$a-w?I<5KvB2Pu*i$|$3$ zhXcayM`&E|j<>3e1o#~;oW0`}BM-vpGh#)&Y1mj?nVpS75`Qq7Tx1*Q@i>75N^`H0~dT1r8o4St`ewy}EsCZvBZ?%xu zzg1f`+@h-!(ae(E&;I@fjVlf;?Cfs!OMr!3_P!dfH1O5|j=wO?IFLUxE9+Obu;0P` z#m=fV7el<+ukq(%-p&!ALFN6hmloM|7O95YO7Kb$xLJUkTqAr#x_pznnntGR_6 z?TP(^TXHdk^tD(oaX6eBvRbEDbtZuvzGPV6{fLMX_^F+-mnKXM0uM}A5XQ-XLqIke zeJ-RGRaM)4H$Ru`B{I`U#;!=kRU;_l4BPLv$Cr4bg%DfdhfpbHj-i|o&l-NB18wMM zcts4Bl&uJTjt|1&<_{vik6)f@EG*HkN)!m4w7*)o_?~qd2X5OLKV<~;14$&zh%W(g zTK5==4u@gw7A4_e+sKInMP)8aEN1lIz>MWAOQcp&R>)^r{q(DXEX%GufN#1FOMxNJ z!4b|o3Z@21sfJC3S16z8DKz9T&NHkQPmV5Y?bJ|4K0nv)7(hVovx^0=xa>9#fq4e; zA3X#}q;ZwejGf~7iR0oquA=vSYUW+LH-=75SdMOd9b?siqaCWQpvcH5kB!m~iQh4? zvH9{J2LSn+os}hBKBvCs$a^D%SzcbAo}K;o`*$L9-Bbz@|MV*%3UXmz+;88$1pAW1CPwe@iJ=UH68a>5vqr(DT}0IG4VFL_gUyeXYegwiKu*ysdi&rPDAWBFv?(Dz zfDyy#d2eYxePCx=D8G*h2LjyhT?p`*IwPnDWX#*dD|cV1+m zN0JvSSb8zKpSi;^6YH);A%JTkwuE!15j`UvkUEzL;mpu1bc;hNRqe{{c2n0r6(>cz zihogBHTcWXm-riTzMdb~VBma&~cOcOY2m?#|`hDIIH5~V!pBW0zpBttp?gasNd-|fdXdee}I58 zd6fynFyxZ{{pQmhy7vEO0RYw=aPvBLSZOK)x+I$^6Wo8;Kuz<10K(`UJ$-1e35SsX zy|ee@wavIgR==QAK#8pq*R*nbLtRJ660>D5xmzA;A3g{R3i~6;$%7#ezFn9g`*xB2 z0avSlZQaB(d-}?DIwz+Ec$?)T_so5%JFCjpQ!a48 z(zDWcW_K|Zp0*!hBGC;&5EqYW1I6JW$U|#z)EhOWJJm4H2S2#vwfN?BJ5`47@R?Ww zbB=)C#b8kz0i>>AZ*QN;-ZYq=kwh4e!e@kIkDGr4-^NUqND0P;=pxNm!qQGz!6PQJ ziLs&TQtv%l$I9MM2fcbo^@7yCtCc4^?+$yZRK2!Odb^2@W$r;ucjaLtqhwx0XnQyi zIU?LsgF%*+rg1RoRN0i)eKi~Vj^#0GR#8GDrYIAsl@X`}#AxUQ_$WlUDI<6g zXp~y#@7K(0-tyWv|10x_-LVh0%Pys6%rCkG3D5VnXyRBnXfuVTMr+soG%BE zZJYI~sJGYJJum;Qwf_ls+h-U_^7q`RSh4d!rfaF(*E1)T93LkP-U?QO>4p17)$%pQ znnmIhli6q6{EL(nzh5Mbk7Prfpq^(Zi`**@CI}V}^9xowQP-JUss&(((We+2Lp}7o z^noxAk??xuhqQzpWl@Ocdhz><;$%P`?HE{#SPxSun zT|U%J;`DYVk0@rd9@@Zw9K1D%h*X5#Iv-LfWy%V>QDW4MIr;waZOQM7a-^#M+6L!W){Q@>l0aBlTZd2hm8QFDxOgl=5*Cs0Ag2?qx^ zsX#;h96QiR)7Y=9W~d|`!*%v!NvzFn?QLy8>zF<;r~TkP6CU$gr}#bx9)u_FIZB=D zj*9&`9Q$+h`N+TfinaTSjCW242ku;4OaV5OYp>7F^=r6=6@3Bn-X3zxE(=%;89Jbe zUty9b3={u_OuCjbMG&JSX8+q_s&x2~T2w@`b0@iG!9_|*-?o7**YX^L@AC^Vy;rQs zlvm##v;L1wZ{i)+ho42*qQ^}u2f;`bQY93pe~X%*4?rJ9xX3A-V?{!UcC#>ae__{^akmG8$& za^ks57L<}87|NgNNgX3<{CGjz`f1%0qQDvtU`56^R06i$!Ss8vzkbuTOlf0)m8@LS zZZ3kNx&pA2VZbGp5_{&czGcvU#j9TUDeVjTvCa10Xca3Y)e z>oVrioSngm5Xpm;`P3Rjyfch;?dW|MIq1A>-eUo&U^5{B_bz?F%)nX_PDO!-Qg*syWf9 z>99Ouv~N+!Z|C8xPGPf{kZW3;Z|=LYhoEnt$IY^mP#Z8Xk?LOV*$gIpl~a9kf;qt$ zu!xW+*al_QAMie3rcsU$zy3XPczemZJ1W73S$$bv__^hLUk$8{VJrfj0TDwmQlS;i z5VvMf3{*4I#JtTnXu@GAGEbPyjA!rURA5kMw)33!b)R2@i@lCIQc-(L(?<{7u;ea9 zNYWFZx0p+uHeC@`I*2is6gogCy-txSdbcQv>HnzV1gu4PnJKY^KmXQ!J$7pa@Rg+N zL!UWJv~W2>VJ=zByb~Z|$rc>aFj~0g6&A;&9UwxqEpB8}P*crvjW~=Q*~2s(4v2}-M)OY)-_KL;+aY@hhJib2}Ol!i?Z`{ocUopQN#Zds=a^e}= z^k53F4wzzFZT2HPE$`|FZN^iOZIvNbI+UwFcw1TRw?`3aA0HZikb=xpBTMeupj?vo zx-&txDaGjIDT}@Cd+~|KVqzHTaOZ4s1lviH4FymsSo@?2f*(E7yaHQbsY^j79D=*L zm|3SHvjdc8oF*#idT!B#NNtpT;@t21eOY zBfGIgfYJTo&8}@D)36@rM-4PYxL71Ojl#0o9Zoul76_z@AuLNt6{{}|rq+NhCQ(;u z(5$w-nLdW4{trLn_wY&j1I)-SQR2U$zg5v?XgCk7TvsQ;mrQ!~$phhfFrbR(2O%}6 zxtLLClrdBD+vnlEQ4|7i)9V_OZ$W1PXFEEb2hyI$e~N?QP3_fKXoNhDhiew zEp97sK3Q+9)s4&2F3y}-F;vKz|RF6t)^Tp_5pf)#vjmyC7ob}DjN zwhUj`d)~pkdEt+DvcCwBx7QSl{y42(Et;v6aokM&ha|<0@3KkI=IOAsnefc#sXrk8 zD8M4wx35UHO;I@M)Oqrm1eFpEfeUw&)JwytzCso!-U}@sQhtb7u+>V6wnq%526irU zZHqhR#nHn<*;ObQV4Ulb5`$~dLsiD=yvv_5--NQa??aorxLe=yA}2i&L-!cKlqCf9 zV%T{}vVzV*JnEys%X}QLxT%RY;k9anwh6U((s#}Hzx3gtKtP$7*z<8}IRZBX#qxxx|3p|TdHWOi zGYHPE)roa)yrKw+ZqsPfnt_Ps7cdopf>{a5MtX+K-~_{qDL=ib2Y-f}?GUSkFsMY0 z5uK(EZcFYNbne{=?8xT)M7=JVPf#8FcWSn8G{Y8sFPEhHm$VQ zB&M)CXu|cqhc7ZPmF5Fl6rpJYbbN^L3EOukfDp^%r-P2zEGPs;jH=Y-NX2fh5m*T2 z^|U$^bRf^3DPmKZ;kSJs>2?;>b)HQqw&C}WQ$q{PhAIwkvMXG&E{RI{En5w4NnjdX zD1tLlhP(`R5Z@W}1O|X3NOf9bJA~H%C)Z$SCAeMQ*k9HI)@u3wD-v(OZCs7mo0dTv zH^loXwQ(pl#N=Z0AuI~T@&{+R@P48nCMxA!cDaNqEj%?PW(PM!l=fD(cK z&pa^?Rkf@07%}$G?wwNtPezAW!}_}n=Q(QqPWgZ@BbK#4+%`L~)Z`w{YAW#Y;^l?p z*agiS6C3tKIP;ES)g4yAjEVg787BjZ^}Z^KLQPa{HQjsTzTqJ`X=EBTi6a)$ABWO; z_QA`)WjCXp`dW=Qx|4G5MrfdiY;Rw%oRQ!-K*rbP7F4R=53&B(qz)h&DNB6PL5dd8 z)C*q26b}Uv(&w7O@F9337NCU;u+grwfxEGIT)OV(%Cb~MF=zeI7EhtwYWN2v7AX-T zeu{q|p7bSVv7vP~^cY2DFV=iCpR)h)5{@g-U5Di-_ z=M4~JTCNrx(Om5iOdGkJ1(BIn$XCvEbjW<9_jUH;+_`R!X6@h<%ox!*2V5mg&%?=s zbCW776s_hX$OrBI9YjP-)E!tT6oXE^qGb37c8oSB z=ex4{=c4KepOH9}<)I2s4gN86L|t^E)Iym z^WwG_oS+|Se6|03Xa=pjvElkTAy$Us9i{ua*+48YmK<1)YO_ODT0ssJ4B;^9`GVb$ z5z1ak9hW+d{NtdMiEW9rzx2!O#~yL%op_4s?S-(wWh@Y=1EFOOL_tq!300~LeF3BxedJASf_(nK679EvO7NS<^uKk z^bCqwgSNlVAZfTt+u+xcOM%)!g3yo1=UeY`F<_R652jepA4KdUFS`B_zWF{gG+l5F zcBfrZWZk@yO+Miv(b561`-B)7W=Hrlm1U=UaH4JQA3!$j6RQv-H)gk}hTNux!A@FaX__fmO-s_(IK$whRzo5n+|NaDP`i0+r8fkI?iU5;L2r*N#zFSl2o05=kyQ4aMJ?7^f zxN~U04}M+JHvvLUCl#dyCt?rnM|{U5j-{E1gnXHc()P;j^>JNVM+-~O2&*5Kz=&#q zUM|Xs6?%C+7MbDO{vf>BC!&6OK5=+E zL$mOvcMeGuMf1NW>Om5o<`83sP0k=cqWe--O9TFdMTtWfRTE5Ikqa)jSG*cb)fxO< zcJ|u)6>>I4;eVx-Qf%}aj>Jjp=zRETW2B=y%Vp{Nh$7T>aYCQN25z?6uhY19-pHIAN3hQCS23V!I9ua!To^gh#|sZxGz_G#ILz)l6M&o zT|BGIVfWSRI>>xXP<`(63c)hZd`PRGd4BHrHjvQgmh~ugLC}Xc0_&}8L6r!*8R0BB^nYVVH?ubuLS|CB|m|)e(t5fxh;036zBqqzYQICTZBM806%S1r`NFECRdPLi3pZf6`4ca_D zyhRKngcZZCi_2I<-Q2pLP}BW)+n7wVy_2?JS4-mT&A!OM&{$7zD6f&5RG^{nCSyR# zi@_25n6EKlZqFe=?KIr4qqS51ki(++ko?!gJtdezA|C12r;`US-zRk2DhtpZtT*B- zd9yj$Dz*Vr3deV)*P(?n_^JAR3p;@-x^yH1l$L+v=)$5~!YrTzAqV8ml8wumKk_=K z_-i{PopB^ygM#$3d%r7J{8nNPDYI&}pK2rh#P5Y9V$V$tXu@`hJftSxl;KlyQ1ln* z8kd>=mTT!*(|*b>U-~wrZEaK3*ZHsC)`Uk_N8OMG?QpC;XGrGEW%=|ZQKV`s(~OUS z4}?!-(?+=WJ90GZ(kY^S3r_zn;Fu&FEgO9k4n!QZVeEdg$e9T=xSTxSM{fzk8BKWx zacqKrj~p7X)bqQ&j!E@^;?0)T?g#0ie*ao!fwAtiP|B8%2uH2`_V>@@R=k)Td~If4 zwgjir26bu9rf0B-fgl<*A)igo6YAcxD6{QOQ+yH4bw*6zvlHRY;oR2~km-w{TE-g! zSC!qDM`S$q%&qq-kZbb7#$O)ebmJmHq{qi&g{SeXta9Xkcsx%LmKCOssus6R>vS01 zEdwT)?L%rYu*YC+Rj7Q0`Z6=>mDiqwR4Q{G7bu<+C`iwoPDN8r{e_`Vpo+jsQAh+; zL-xlDTQ1^*ySEaRyG!G^_q*;W5ne%b+XND|cc~m%YnL>jz(bN$(E3&OT{6f3X|sxD z?4Nb>iWQ1s1zQDM=~t3GE5G}`C3cflQZtp)JRHyIG2asj*{%~91XNUNX@&xS&-L+* z=5+Sg^$^|r0gBgMia^P!T8*)4NEM;T)KE+}O=?d2fj%#j<~($ZAN!XG!x=QfivNq9 zr^Q1d(^xVA{$EYEvhkhrC^p%6Y1hg$7C#{PLo zj9(s8y0pBX9q?2ibS^y=_&aCq&hPHa)9bFt+Y*xKlcuc=H|UuYy6;~ps>+teN&a@a zaK%_;Qu(515MQ!GyavOlbV^U#+ukjMdv6GAM)lB_RcX2jIvEPA5a8wYD&YJZ{@)yS z&S__mWuu;OW^ozNCiB0(R}*mGh49?+oC8x~rrq_JONnBz>9Zitib$O(aTA8v9yt)z zKrIKd@j|B4dZ!5i+v|YS%Yf|Ix66X=x8rV=)5_Zb5&|BPm$>cc_Xcdt>BeYjIX9i# z!_8%@yQxpc)S^N|!%bURh^VHmgk`l^)AKwlt%Id zIv6t=v+BwXG1T{Afr2oYQN(G#z!8~r@JCp^#UdeM`X5+K`7RAPweF>^iF=)APl?;!&lEv39wQUP^w{k6R+%28r7hQ z2TZw}6&TV!P2-xUJZ#%7f3ijhg?OfEM44lwr{+;+hb1^+y5U; zX8}~@*1r8sZ+e4tcXuP*DJ9*V(sAf+0qGE=B&E9>K|&BzQo6fErM|`c&41>Mb7syN zXYc(yYpr|T_jUa)#djPjW&|V~(24{+N2|B5tG6>tN)+22-uqyALVv^4$E2!}i$&e+ zctcsa_A$MH^K7f5qhr4J4j5yD{(Ud#eg8Tl1aAEUgvIP=D(G@s3G~RX{hzk>$FwRV z{d=(X$bhXY1RtJ?nl1wO(&akK0rHdR98t~g@zi#^5aL>CZ1nkZ7T5I8AmQTKV{M4X z#qVX;M?3VZ8k7PLoPOYSVeh`>1e>zINvYP~-De3Rb!ns)T8%FHoFP+beQ&SYjsMEr zA^+0iVv%;awG}!+LiZQvy$dW%e}p+xBW%|OjHuaajU7qrF=UHEWu-Vxhy~Ho%2k)o z&bD&5P@Ce_&%bjGw4)@zoVb<)dTBsD7(AgM@o%Qw7|W=(nZ+qa{po_Y@4#%e!U3ru zXuWUXxD6)*%Uygm5{8J)WsEiK8Ci{~OYbYF`9iOCjKFN}b^IqjuYgB2?Tch^VVQ->q9Cv!@xL zEWcd={y4M8>vD-8{xU>%h{VnO2TBMq_g?&YaT=4bMdX(+qa3GJex-8+r<6*SWTnl5 ze|&ZZ99K|k4Ho(@jTchQaA)K=QK5C*gY=hUJTll}f!I)jP+tmZmuC87m96@4ZW2fS0_8v7^ow)r*7?f~iwao5)QqR9F`X!G21q+oI zq$pD7Md^E~7e_bVUGh%d4D&zYc2P}V>CVI(DY4g5V@QMMaGpd6#hKr900q$S3x-^# z6$Fkahr)`zu4<+q&Q(kw5BG+De*80B5E9AxCXYVz)iSA)uj{$yj$$n|rnO4c#Rd9Ld{ady2c`mgV9WtW9-BLs3T^Zjtd z%+kRD6-ddIO9Ko4^?ZoLfLqtb}V{!?}js4IHe%g4ahO=2q1bI%6+8 zLQui4Zum89_HU%v=WQ`Q>x%k&QR!dfawoE6U)P3Nm5|=rzm|D(o;XilY~=)pRU;et zuTv-0`nWP}loP()0LtBah`J6eL|E#1LKj6KyO9yc9cb`D_8b_DT>?86m`RqF(u1S< zBNN)U>rR_mZPxT1Vs&45az~1I>gbtoK010gBt51Q`!P|hU$A?m5hCH5HE0qhxOa#A zUW$kOqR>?Mk@!~7gT3Y=^L`VA$M&do0aSLDk>9> z-Z$SUyN`G3?lYC0i9zrNVf{Su`Kc+2ns!Cono`pk9iM!Rc(s$6SXnoP{z4wte`(01 zgZuF4I#UCkaF@i#Tv8o~+G#)Z0x`Iu8@I%a6Bs>19^!gG>4ro%k2RcMk*i@<5&pV> z|JCgOB6E|TprbGpzO&su1+L;b%rCPn%VCv1vV95p?JC^*17!E zTzxV1n?A!1!Pfvm8(%-D;q%xT9i=Y+H5CC>k_vmZu91+Ik5}N-#xTpDR#HN@f3k^| z8H1g8PE`|K5T8g`o9R#H{wlKv$BR7h5uJ^UjHdOJi@qNKp<#7R&Beg&=sU_7Ei^DB z7y9p%%%%H6{^5KGyGluXqa;=TQkG3$7+!vSR8mP|vP|Y4z#t{Lzh>|ES1ngEzm(6UcFk zx97>}e)!Dkx7fy3N47gHcFm>38s<}5UCo?~pFbME^(#>ktx`;>{S9foaG+>`W%q}n zB>qd&uXVo)XPz4Ip}5o1GPJ`BbQR<@>4zlRv=vaAxb#nUo$h%Ka!URq|Gb5C#K+iw z(wO;XrdU(ULEjw%-R(G0n);S8q<ni5RSF~HB)el;c1(9|^aaR#c1cd;>a^aWk! z>MDkdXh&Q1-!UBG&6Ys>#odt7xB54;M0EJwZS~9=jwyBs(-f1LiSA!UXqUPd5G+DJ zD(Hn)Dqn2Vz7xGn09xUVw|eYwd7^%o1!pHGL9Uv3KTdo!IkG^sGey8L_ottZim8zw zJl&?iEFDlWB5zH!lkkdSt-nsyxb>L)4s?JHBXcoy@q`dR78s95Pi%YLDmA0F=2v)TR}6 z3(;@(zeN^Ji{XC)ii#4Du6J==o;B2QE1R_LWK%Y6g1;1A|u@vn2zk?heDVTXk2qpM4D2N<9I(;LNgd5s& zx8E0-YU4O7zwghe$;nzit}C`vQf|eKQETUjNit=y9m~Q|<(?S43a^fXA>z-yWE@>L zY>}Q!FgUTi1-6Gi=z8uG^)k(q`JWBS$Bi^8*nJEah71HmjRS?r^D64THO7lFF)`Vp zg6=>2Bs_O95bF1)3--V`(mYjtq48%Fv(UUSVc8clptf9J|6(*Pev1zyqJ}0Q%i%?T z)reuSKA=;Ua$u^40tSw7GF+bfOC?;E4CQ99hXcQs6pU=!-?jVc@s3ol>T1pZs74|8 z>n3noSE(W&O|vIX<7KHvhY6}US(BlHqU_{3g~dY8Ok^Tt8kc13|7&MJ$PVCz|o|H`_gkaK`eL^k$@ocyf`aR zr0*Dit%@+SZp-k9DC2NcABw6$6pkq)kAa5AWbQhm%0ndu_d?0n)?;qzLg;f;;*}^7 zM*nMq3b^(YR4KX_K10HDWMB=}9o48aUo-0zaE_kM@73M7zP zSXm8;pZ6m;0_7yADLPj;2AQnS#K7lA{s>o^Cyo9=-!V>0Al<}>b zeD&&+Q%Yw8V=MSq0-4PvCr`b8!_LW~<7{p9o%$yOzgZ}L#{=91T1d>?+}z`lP6m88 zs5(VxN&_F}=5)Io8p1p}sk1#0oq?G9M2!R_blPT*cwo;);`maU$#A;99nY=vRc?lyoGBeU(NCa0O~ znRNTxrv|p}-y^RzXzQS!kp|6BhL>YtmXo504aX-gYbQXP;~p{M?xy_$sVJa|)Cq-; zvMibD>t9%I-RFJVocA|)=%40WKcPCI4@Y))EIu!rr<6DH+UAyaa>74vaC)Dn5mPVZ zY>7Q76T5;OOnBdc;~r`#Jdlv+Rz)$zYt-m+z5o)se(=xf>FLg#dH&wM*GnoIj2IdF znv^o;S-#IQ*kZ$e`M^sRNd;9@Rst9d$C|(Qe?5Ok+&*gl`4&N`_t_f5%A@Kkj6V&_ zeYK%b^5Z`DI4j9Hg!FftX;7ZE1OV2*k37?68_?eo%|K6lL5q zCd#16IQmuQFfM*Qm#PC?%Xr8XrU5&=+WoOb+|js|g|k|yrCMVwRhx9m$D8*wLVLqT z>cFyLQ^Q~CpbYj?gG~4LW#dFLTFQ#d9ZYDt$`6#QcP&+8cLlfIdp+HTHJ3p7>}O>~ z+v|H#to$ACN3^y5ryX}Z_LO*u0Ltw_%)0ivYenW7!}k%>!@ z0<|Q`mUDPQScQ_bysQ*4oeaGW6nk2kxpA(sAv(U;oEoqhA!X%=IC3)~oh;CGpay?E zA$&W|HW8cSez7U%^^zRKix+-Bs3>N-zwaS&})%g^y3a`&p z${a}(Uql2IcqMDWeffRan}nibCX%_Or#(lSP+;g^YiVe(0E@Q$`+@$Mn1bf;WHp>y zhjAGdlnU|qm+{1$BmTA@$%uP$-b?QutrB4jQ3ZD)I?ZkP_mc3*I78MX2Dyc}=)^|D zR}dr&PP}HdW2!AnGR!MYX3`rLrI(7EQW9DYB#Bovk*&(a2zx|Evd!4$2tdY{9dt6S zxbC-lw~K%LV21T{bn_DI1mRSY0LTJE%u>Itsja1fhEG^hT21aKkd0=2gx@0rXrME( ze-Dpv!dwuZk&3ThU$MkXvvby^a$MFgcgQ}RMk7hS85!qg44$>!x4_87vF@j-hQQ9% zocei1tn6->`vXWYVj4Aj@_BKdT;B5mX;n<@&cq-$^)mdS3kt2K_)E zQjjwJj!am3d3hOB6#7uOQoNIslM4_553DbHAYV^2QWoGP(zTepaFVXLc?xi(`=K%< zB$;gPU(V-@n3!O`3w zgixwYgc!!2p&XyHD6F7~7Zh{*xQ0eu`zI=?&hM+!VHSr;=K(+;i-M;N!vP*&1{z@K z{$@y2(R$pRVS*TkRSrwC1xV%yZR9+Ro^v5i|G5w?MCuP@;6HAYxK*&KT z?t~|m)~BVLAn;HAcVRmCb&Keoi4~nRh4QI)dc{{}X}NJ>xndgI7(HA`8VDVAzA#Wq zAh5>R@rTi?#yAHaqh((dkX4@{Fp4icU>gRbc&q}=u_Y*(CKE)b!TbDq;s5}kfZRG_ zpx?1Zr){|uH0cnE3xO&YFGE?b&5+&uV3s~q3FTA9faWORd4*N zM;F--PTv>{D2>(#5qJW0r2y2&9U9`#fBYy2La_bo=FywjUN;e>+t|KOAuLHZw*}jO zd@&OsE9Nhz;!Ubu&6Ro@Z_6~=5|Q>`w+3mD*=VSSVhsqZ?__breC0DPvKS$ervI`u zDKFDyE|mSn)U{;uRIlE#Dw84q#{nU%}#@m15euIy5X9o#Q( zBvm4aFEykz{Uvl62NCX4Q~}%+VTj;aj1Yl23Kp>XXDCN0e4N>N9ioY6AxZ%nbk}!5 z{A5^~EE(Y53+z+HOk5Q2W?b5Ua{rmNdOxOqk0kbc;iJ2$>b13~wiYsCe`>tBJmHt4 z0X|Htq52n>3{n3f>A$1WiKP0J3`6A3&}mG^wZM~tO4aBd^_%sf_RpGtYChT4#1uSMk1TKm4F5QGq|2X`hjC}ettf~Q|;{REC#H+JaBNj ze+n8K9U;!WX{uj4uzE9cUKFF=9W9@Li#N&lJ)O>*C{l@;O)3Eip0Hny?NHMsN-CBM z4SXd?=trhv|DGh#>5j!Wsp*&N{7l}97@QB!o=CHM021tF^_LILG21+V-Ms(OnA4llG2CZ!t>9dV`qy8r`?xx%u~~L4X`4BwOxM$g!VgGYh)20cQsJ`c$cHvTpcHh^-j%E|#`t2AP`eek zAe}J~Rtz^Z814fo9R~MIj(7XaA=dZI{Zb8BF()8+<=-X9gW@5=~I@H6P zm%!QlOj>ewaajT-;U&$eT0qRmqP~%x%vJ(Lxz&4cNsbR+S>V>SNTD6^QbR)sC4#C1 zDO<*C7UMdWlDw6sDuHRjih#H~$!gi9?O^?UBq1T)6ARQJJWT*WB05n6SR%4bH zklMO5y2Lcfu`v`RGb0jm65FyJA&_-A5PDW`RyY6T*|+BEcUGmYg`NSoPX%R4+M3{p zSb;YM2?9FRK34-ek=5?^#$(7tX%;TN+5F5I z%$@&t($1q)YeT*%&ZDFFQuMWQ6c+>IbY=~Ofj~|a@sM3tLc5$f0+a^r6Q1t8#Vcf= z=uIsoWh54k42_yOUj{=NqG%K+9k}cO07@3x>v_s&C4js3@8P$al+Ls*8B!zCD_LHN>fH@Jv^VN+(6FRq*hHhTlv#a2 z$CfqEHt?*3pt9?;jK$-Ji%Jqj#WJ@JLNl~+YqJjB=9pg-@z@$_SL#*`Px6T@dVg!B z*~=4yA;am51>gTsQswk)w*)^!8#qCKI^JBu^N%nw(`QZC7*+lJaeR&nY96CKV~=`` z(Jgm1tw=20n7B{)VJ)WiQL!<}3<@pH376xdGr9a_4G?{46VGr59W%{>0 z^skY%&f*J^azym1zp=NVKvSZARS#{<$w`~BdX@;ZNx&SQ3%;I?4B=c8a>UC$#miyZ z{by`tWhD!aD5&S8t+BD#_C2a1Gw0qVIM@I^%84B`13}%Gt_9|AjdXw3_llW1*|5R7 z8%-jYfg!eg9pEN#2R_`)durm9v;gyUx47r7YJ1Cyd}Vq+PsNgP=SYbls0;p7}uEyqBINoHg5rcjvH@J~n89gm0&a!^(%mwI$`LDol0(_K3( z5$hWo+=(P@0H=p3zMTne`RHbIz#MS(UV^r&8z0Gz6f6<|seaEWP-3D)z=2A^8rW|E z0VH-ypPQTVWpFGQouX-)=OtyR@d;VWBW-^ukY^$JE=?6dr-I)Ov1HOPWukSH(od|? zFimO8qV)w`rXQgBd35v5vBeEksN#WtxV}Qi8I+3iVg9tx@H4pcGFBzXNOsBh1LIT8 zc}IRnfn2baPhI^&qqf{L7AHw;zU>{H?5?lxXVF;F|M)xRPFRknRseS#dBs8l8+Sed zx0MDFzL4O!9|L-B^+WCJkX%AVM|g1?I$x#*Z@=wQ7Y*Arm5aos+3}oRFj-UmljM~; zYmDp8e9y+yw%&?2;n%^3b5y!>V@P>xTbnx=m{mQGAtTc-y9zM8%|X-7Hp#YJ6^P4y ze0;#>?inJSWCRTpoyLi#2GtwbLpSoe{`VZ44n@G>quIB%2j&#uPLi(0R;nF?MZU>4 z!4|3k9YDWAx30GMo%%8$m&<=}N_D{#7GH?9kKy*YIh!$dAaflpmWzfM5#&8n!N0Fz zaOD3;k_gcp#yG>0l@h&^;0j%ipkk`k$8qB#V%zqkU{osp&i55=n0E#nq=ruwNW5Fb zfUN%dRU(w+4MPNdF)*-Ect;+EhA)2*>(lyQMc-Om#A7JqmXhcaaf3cizbM=AaS$#Z z8h{e3W|)CXX}vV`jkEK` z?k)pssWzURx|b-P+(i$d=BOVjaO{hfM*r84eE}D_U%6r1SU2 zta*wIFlB5EqPfsyoW00&iYMD?&YWA>X;6lv!pVp0??R6_Xr>sUUzxSH8LU?c+-;1s zbuBGM`h9a#T0HwM!tx}}IW2EQ9#vbhdQg!_Rb@q>Oqh?rWyHCgVu{c0O~0KeFHwZp!bzgQg4HjuSAAzgMx`Bt zQcFrJLQ8PzfA-qy*lyzzFSmh%@h?t_&CSKdBr&??FVD-odmJ3%wcK0rTa)}-zYl`jt9UJJ;C z#>Ik*`Pwb&QwjAKNNe{de%+rz{42<&&~LVhaF97~5G^|W76*Q!p@AD1k9m1{o7>x& zoR>M?Ec=K~Z~f2yodzB#V%P17npOsK`Sgcq8zU_vL*}AFcHo2K3Hrztx6z%63o{JG z8(w2n3RrPvOSMuw zJw4;N8JH$gMWZq>93B{~0 zvXQHD{k5Bkwh!}$S{_a0)d;E8DQA;|7}@&vlOhD~+{X_hJT>vCxbZ8ih~SJC?`XX- z1CreOS}xD=*MCofKQX68LaVTk3n8KP8_!>X9wI6s(dV%-7)2RB7nDlZBn@>)So~G~ zbj<&8e46(~T^2t?{%`q2ob;&sw*#-jbm_YJ2DkP9h2isAyD0t5J5e!8W!y?!W_q8-aF3#|Nd6*5yyOml|Pi^6?{Zvo>rtT(1&?B&ulWctkGZ z`sbWmJT|DW?||!=eK&^Ww;8+{{?f`=p(U5eqK(#8i{5|Kf z#z|uL8_u^uqMi;l+>T8>-2!k{vYp{tXf_73pvhQoGeH2nC5p_Nne@$bim3p3BgL$< z(8``hXMrng=rq+x{aWK_)~YpKD+rPjiG%xa)&D&NvpSfxEEtk(nkf>NRZF=MGrg*G zR~LX8>9Wo#etma;)b}W0XwVz^eD`g2wgTuyh$I-z#Qq0DD_mylzT#g%jOTZ*_NG~v zTR(_S=fr}a8%$OFdpN|V0q14_5PV{x&s5c`JaCiwEl--+oLr*RFlTbr-C5o!nsz`X z=a5xmg1NeGvCLUmNVS0_24SYV?^o#2Lis0I>$z>JHmXiB`g`%@a)|Pg3tS$W=XfS~ zCcl0y`sujwtw`pwEZ1eJ`pDA8#wLwg5ul#>?z;NkuHAoK)0VhvlDLb07j!({+=L>< z1=h$Lu^pi#2k^+9mosaRKG7;9A-)s;SCN;SD^sKEIo`MpvKtlcRCAYyT69!Vz}pB0tCQg7SB~UF5d9 z*dw+O19`%`2ey@`pLLS4)G-?ePpyTJ_lH>|T=cw!dm873NJ!R5_BtayZbsTHJ zgVn@C%CcbwRN=%WJy4PWD#P>4$?EoWB^&Sv03`x^8sFa0_x#wYVX94dgE$^sM3P4D z@mYy1zEqP?A<`mAeS{UcA)?#t5U4PZG0~PL*EdIfdAF`3YVClmn}E`HPX!SA)MUUz zpU6#Dx?OPBQSi~W3E3g3RezY}ItuD(O0@s*X#3Qn(Luqn$pSO3b1hdhevk>Cr| zZt$sqMrWoKzUcZOOd*+SG81+3C6L@@Uox5Y(FOc)>qwyak2PJ@I_MwEuD#mG2xg}e z>COoEKI5aT#iMTa5>}niKkjYyr(6E*@}nM2eDjIgkokbi^N$|>6E1sUbrN)Den;;v zCW4RG-0E)I{v6v$Y?%w)U)EBlVwPuUZQniausuF4FL^z{X%IbMkBOoxzkp?~g9~Pe zpetr+3tG)qR#tq6I8Xs^gsPV4@095pM+jU__9nKF5^`0=6%+V>gksiCq4S8ygki-u zlN#wOZfenCF~1L$?pGKk5*qinI*)3lY77S$=68Y0Wi@cxRV;VtsT!`3O=U+23q`7* zR!j)23eli{e84Ce@qAr=))72Do;^_jr?|5-X2bx*AhT%ZjfQq26Q@VA44Bd|X=|nz zryuhWuVE}Jmr2z_h=jgDZj_elHw-8OC+t|;?B94`)b7w!APW87<__FA>kD1!`tJ(- z<@lh`zOGacLURpRUjCE2cF%CK(nScA8?&v>&4%o6)_OlUFPEJK-*(;C{@0`8veJ3e zb_M{yf+m&UDMv4EP3CnBGRMH#Tphvci1mM3fSQ^Vt?{wvRzlS+n6U`~5m7Il#CaI@ zIe^-lc0T?)I*@o;3fXlQX`3ksURVG7%p@@Z8(xe7fYcd!|>}gNTJ)psad2M4#{%h+gdrc_PDkh~aycVPi1pZDMFVIv&^Zb91i}`DJ-_*Fub**qr=ZmrS z`mgDI&JPM`%hIie-Y^ux`Emb9lr*rYzRJg(0Rom$*c9>xE)#@uD?gG3~`%LSrv zqd=?7JSjR0M%CQUc`_cx{9r|6V^MZUV6cV7kyx&E?w(MBzf~$-Kz7a}{q5RqkbY(q z2owN?*&D6sxP2bycD9KLlStZHNFpDbfFBYWVp!!*M?P+}(ph ziH{OanNn@#LcRO@edUR$`XTs;$;Y9{sQ!N)oM@KVuiR9G1m@5usIYKqe@56gFOdp; zzMK~l_9XQ3*F`;JN#*pFxSE>B2{{r(wd2sc7COs|tej*vxdunZ%Y>uKsVIC^G&uhW zBZ*K2iq*F@Mshu*rU;;RFR9(=CQ|{mJdB{0s zz;Q!Ji``8%q`&nfEq{;Qp{8o`7*EdqZ*s`vRl#U1RsyJqCH_Q6^sn{=P)CN+vum^J zl(3ZMc{X#l;FE;5Hse`^Z@`uDJ;{n#FhS~R-bN@(`nael2py-YFmUUe7|6B#%mBxj z@A&`ZY*oeMDRol zl$?mz(5`T3)&>Uv2=h=7tfPb`1{AVPso2H<={W})@Y zxe9lCjiTAk%TxQQnu+obbH@ZG3PeXb^coQidQZP*$e?)bi! z&NGf>Ck;5>Bmt`oysl_s<@}E3vh^Brad?K-gMR|V>;fA}1XtdRNi=+3YV+j$?Yvw^ z$k1B3yHNeB=ZwIk>aPA)?yUd*YSpn>!tAh858AV%gWOctAekcdlrGu)QPM`F;dO+7 zw4Fg&&-ffayfM)iQrB+$f`1EL1#SPW4cwXu{kJKM6b5*+To@rd(^nsqQWtVJ7(%}` zGDFsrL{9s=R&O|9H~^$1C@1zTtvH)W$!b@^766EFpls2u(gFLuV@u~Ewxn#NVGR~S znCX8I6=a&Pn2~SXij7kmV}y>djRI${Lfs(tR14U4x1^dERr&#&5$l$|3t|$3$C(BR zi8`=oaex@%5)nZkbx#W3d{I!rQi_G}XW8zATWlHoTu1mQTe$>5U#pMc2Ag3oMzX4x><_)ln!#xikhx#sA`#j{tLIryc zUhSR$VsDOeX=Bs!iIzd8zKIFFEyHqa$N#ve>j)@VlF9wg`$!H0GeZBr@y^Tnpih%} zsev!w;ie&u@HmYKVwUVv$&m94*~=`>MpOE07CP(HEq%fHtN%zK z&oFx+CR}c95FS)>>S3mk1_5F06U6`r4Gw)gIXK&1Q5IFFuU{`1Y=rG=J`55e%)kA@ zEG7gs{}`^dz!f3ak3}rl^Dc5KS>m5a`~Ex?Vq$7Mg(&Cn?+;x?@l(7CDObOK{VD~T zs^ICi-kX|RQBwGC*pXVMg{{{SQOizul9PFFqo%#@`6mZc%(4bE+nBjbx#8|uKK0GbP zvC;@Wd|go7($XT~celJI?p-<*8g%zN$Q{I!MZ%0LwKXzrbTjF}3!;%q*T{tSe9r&b zSCCLPbh0sLsz6R6^wahjyLq?)gi%}D?AHvEaQ*W5aJU<&a-PGuTAJ_LapktT`~+Ba zu9%&)qa2gqmI*(#AB>QGAT>K5v@9bo*fXO9+(92i5>2u#IH;d_2r%RS%uK0g=5ZQVQU zzS9A#5zi-Tc5d#_N!wb|vP;+VtqEM8EG^+_cbG~X{5x_0=^m_sMmE1RVIg7R4g=J6 z%{?b6>3a4P8Vsk3#IJ)FjxetJaHZHv(y$Ia2oKc)99J;rebxi_2zEE|mEIG|n?-kY zM1wzZ%d3%0)$6mRZx;ZVRDk)Y#qjIfVkoxQ^|ua6#G*rkavPtiDJ-^iLb77zUuP0z z*vM8*Ul~3}gkc4m`6opHnm15}Q$dQf(&;$g&y9DHX*e5KXw_ zzE0Lah*b($`uN`HbEHU8M}uF3rz6VX;OOe#6_|3NX64p<1QfTGmifU(Hs5>&kyh6j zSh(K&oNSM2`3Di7Y_CFN0@OY_ZV$QG+=p=!~3n#kB*u;dr4%}ayzy$-;?O@91zZu7o>@^o@_LkGN*3l7sZ~FaxoUQNlF-HJPc40hu zYU!&3MdolW+@Ir-CROSK{jdEplV3~@zVC@jH=(6xi*SoBtxILyZb0olI)qD1F#{LZhLNxE6__#x%O|{ z>S}Amej1q-EOYY$n~oRia#@+iMG%wDrlnJq$V$FDdG>gu>)f3)4k1F3sLf?J6$LL> znk^r@9}zN?+1x;u8lD3CsM8_Qz;ghxRo9N|B;l&PI@VU>vvyC|g)&mg-o;#pux6dM z(CO_f+)+MWVW80mI!6vRn*4&?7qwe%ee(zj13$J)&W#B_Bx3T7j`kP`b zDbMdbQ-*-FC7n{O%KJfcu0qg(s`|;Z+{#WpOobt11TD%jAOoC5ce1PFLAEpD=22z2 zcsOi*b;t2N4-7R7o-JbZv~yS?%}5jhTQf?X*BlvsWd)B(u8+@~MGF#L1biGz(N75D zifjKx$w5+>_*hw?EO^=k+0~Nv?1eRG2@;8os*#m%P#XEE&Y<<vIP=Vj}94e>Vv?S1&!$stuj^MMUnRRkAp~y6Qp%6#nb`_p>hA#z4sDnCLmeCoT^4 z`DOzWn}^;#{du?bcy~4m)CG<$OSVE_o(J|+hZ16@Hs|GNrg(7U#g(Rl!Z+l+fDXmi zG}kwhC9fhbIMV3i4_m_bq-uVFBvY?PhR%f6Q?Zj;X}&P^$5Ncy{Dy8Cm!YCQ=owan z-gGWXx?0Z+OjFwboh)V8*E}u9h+h}|#z%obg!p(p1xPU-)0kD(1a=#``mR2#pfEDx z7!uUgRzpXLj`MQD_g-3dtOz)~EIcfs)CXj%zf zYb7>(UAN4?)k4w%iXWJyP0lznf|GyXuPd^>V-P@fYGPn0us_@h(bC04~o$M+1{sMTfR!8f0TZa6;@yRB(Hv$RB#Kw1xMj<%Zs}H9r`CBY# zWTPNUMWTT@z0sgo#A_dxO$<)_wtBm1ioiXQwSk(p)p)_)i$q@#;p+~d>Bp-*q(*Hn z03j+3W=dqEM5vAN-~~}tS2x>5_02DDp-`UdszqPJE@}>?qBNl1kQbR@h4=E5Rwv%( z2~81S=MQb@f!SbGQ%3JoM3{!ai_~^vW%N^_g=TgCk<-`D1{`Mck?ZRpyP>#1uQ_h@ zPHeXaypQ7GS|1X85|7Z2PMYYFQr33BXHwtnl7HqkJhDX66BtaPojJHI3~!-v0sYy@ z*m_z`o5idv;!OAQMQM4F3BnL^Y`+`zI+j%bWfZ<5^T?0}|HkrbJQ_!Ysm|2qRI1nv z8*i)Dv`FjJ*hH1Ab!*~f2Nvs0DNJPf?T!Tjajdb)w|i4L$m8*aFcMhaaIhD+-9o|Q|nOT|dZ8VAC^hr+y?(+5c z99wvixl>Q(opo_=D4F72xmk@`T1{_OzQ-1Fw{^e z7y8zcVP!qNE+eFvo`*k}uR04J9jtJ2o*q-)Wi!POd(WH1d>v@beHpII48j>jr#0d^Fkr}Vi;H7Hq^f?t3|e+>d)NY%=;Ht76~>DQ(F|p$metUjO>PybJ+W-8uZc^DFEkl6P^oT^t$Zxaq!$p-=AYoQZ$W%X zo>E&~(f(69c~5a}F5%EicF85Tc&*%OGzpb;`c+1(YHI9UsS4X{`lb)5A}+_0^ZVO} z{pqLEv(|j)mf$E#)MJ`}XUYjp6+(U@7%z(ZV_L3D76Nkdd{BU0gKqt#xW-d+~`4 z2N@24#Mn#SOt3R|SU#s-%@$>}U|!?Wdf34se4@e^NT3Q;k=z4oU%|gZn4`N9iLSgM zS6H?Ucc^iz_u>MIjXq>i|ADU~Eu zS68&KZEa0VU?DWOcE%$l4V};pygl?@jHQ`Oe0SBR{(ykBnkwePWTcjj&hR5K zh6qi7%$|z}rk^~fd;nz{y=2nZniYX`_?v2bjM0}@r)ZEZGxH^oOHC`V^`^wfm z#6i;LP^OIfP`T*CZw*4`<`XIo6ei>>$>k?@47b%cWMa04Yc7pbEYsb~krzz)!XVnG$_D@FNVn}L%XkaF`@efXR)@CFId2JTOv@w6IgPnk zlH9F`X0L|69k-oaa-R0BHHwfoeQ!)49Q8UlHt4)NC`Wm#blIhc1`)N&_iFC7QXQ{n zp}&1e?j=GF%dKg_e`vLT1`=`RD~D*$w-FQq6tZ&q9baMASuO zdj0R%Cl+C!EV|~}-I z@ZX0tszuZ=^EaD*?QWMG9Cv$)6?pJ8Ty|$(Zreki?bn#2Ug{H@KkQO!ByMPa1|4r> z4+VFXm48n(J?qpuVkg4l#COP#h==!kzZplX&~CZB&|~vM5fN<_q&|71Ltv@3vj;xsvb_Lx zM&)%<%Zywr+;RrnkgRTCr(W zv2JB(hV=R!h81F{1JTvUn%`N#(|&va1Oj8d2{4TP&)%vVG21iBULDH!Z*W~}L{T?X z7mH7{unS8ANrRYdK?o`A!#S6B1|tkP^bO>9bl68J9fg63Eh^_yrhwE6Cp*WCZWlHw zHYS9At;=W_-;rM)+CY$1p1!gql+N%4J`#%I$77!Z%eJ@EBix2^QYXc3Nuw7_X^iTR z4-Y55-z4sjaCn}-HEMfYXp=Xw)U#w~9Q(Z3(z2$W@VD=tWQ$d=x^-pc z3?Q7hF_fn&aXXlC_6X}47{#!DDkPEbunzcaINA$7V>U_ih5I4%Mk-@MV|}0+NDA2@-<964KHg0s^9Rcii*+o%28Eezp%_4}50kGc(t9yj^A-N*F{1800R|V)G&OXD4GwbI;g|}+Mjv8V#_utIujacBI4Eq-uMgbq-8=V7lej)b^GK+DQve3pRy%Q|L7%>I_NAc z&=%%Byopvp1ZLsq!4QrZU z=Fy5zceLDy-oG#G9w(`+9lwZ7K;BO-!hOsP42Tp$`muS0AuC&Wz)-tQ&d2{Z3!wNlV{^TP>nHR(7W(_9sxg;ib@fv>Yk3n!MXSmga;Mw2Sq98PBKIX~ z9vXsht*7qUp+_d5e1}^-?d8Dj9imC2a~;YVNFa!m>`$UrCVZw!Bh>U4s>s+U2~hD4 zgpkZ-r})w34ab>!b!!-G#HX*pTvk3W-)4vh{cduotO`x<%<|lHpJ_e+UFE-jd$kEp zRJ{8D8i>_je8)Q&I(?8IfLoIWss71}l17JuA+b=FWK_ht@!Ud5& zwl3euU2X+QyNQ2@G+KV<+3=)UnV}$7C_csl8B+_s0;=4>`UM__?AQ(=-dj;U9m$7# zADvU+ESiKIxSBRu2>Q3bEG%-^{_rML3i7R{uc(s73K&N4@X$9CTKcWI^Bi}&Fcc53 zAgk$3919HYG^ONSxA7_o0a3{8KE%ohmcxVhw*z%9IQX)JpLpfXLXR9*G9mWG@5^JR z5*=MUtvpt4JnN%HronJtyy=(a$(w1mG)KJT#nc~#NlFNdFizC*F*H?}xFF2-KTN!^ znlJ|-Nv|USi;#XUa_~SM*JHtRJ*O#0$h}{7KOM9m%I6G!j5%)@DVMig_iMksM4I~M z1GScAXCfQ`(CVnQ2J`|RPQn@F0VIMrt0j8prIcfK6OU+(fi)#I0)rA0ZbE`YoLKm2 z!rF-iXyl$*uxJAoqZm89DGpZ31zsx*={_Lofs*y2FD%x=^zh@jVQUD>L;#Ir$w7@L zz&nc!vxzC(>I%oL$PS4XEaLgb+~TIdx>h)CfAsW^9+#Y2^78>5Lp|LL>c{H38CV`! zmPLJs$j|J^)qFcYl1@q`1NU86gD8Y!F?68wv*Vda`y}tj;2n35qatl{OL9j*7vmxZ zkLes1a;wl}?)1iyANWmOFZ{zMZ*9E?`_9f5%l>N*Jva}rLKTszr&h*G4t}JFK;)$k zdm5<8aZA%z6tkfpsrisP;RjMiGbeQ-3p_2KNpM3xqSPY)=2W055KEt8KW^YZ-Y=-a z@6J^h(|Oyi&cWfXUmuFw+wresWduyn+21;%gc8h88yj`JHB6m7_~0Hv44yIC(r zt@)#jF>EN1lc>e8z6b6!oi>;SE&u~TPf`aGMx_DlKux?uvLipebeG3Cd>HLWBqwcP z=Db4LlY(vaRXkiEvY%R)7Gs$jP3^0bIfWkkp7`=;#fXUam4$;rk9hNi2thLkI9(g< z?0#s~>^sx?Lh1uFhYqKc51ruzX>=rL1^9_Eu1KvfWJ>>DAPOgG5lZFD3P;X-*2NA-3Yf2uhhZ1ZmGv)KHo^6|_#IB>{$#K-@Iv8xn4j z41#V0Z#woRkJ*3R&AV%h+a_?F9N?NaOidh-D)OhKn_)Tl^nC52deWWjxX$e8o1jnq zpWyEBD?Y<~E>hYU2p;sSeLl6O{0Mpjj#2^u795)g?t&^aup=0ZaOEA89lIa80_`KT zQ9xk#&5u&)t)b|FMbSt=>{rd1JfHO=R#UpJFHgJ)?0N6!VK5}{kpH}$SWfqr^9_U; z!(0Y-GxHQJ=|e8n3#r4>!}MWIvJKn8MG*9Qx*l;$Vd@knAe~V=Bt+98{FpdcvwBR7 zNq*R8>P(MA`cS5*6w)V~fJ=h*7v)B!DTkcL??%UQV}DWi?2?p@dj|yE@-yszP_~=v z3s>-5)z5dnCG1>2Z_hsY$0OlY4?IDx?0TZjjnUQ0==N&JktQIGcW}8{cj$_`NuFtP zCg|qRQE(=B9;u3nr{r@O4#Yw5S?E%%MPag5>DxI<%s|I0%f#If z#v!JC`rd&aP#ftNXC~?g6N-S=Lf*{LPJ4%_8+EaE{>-SqW+vW2LUmb7*uFbfu7}Pi6&?^jIb4QF5x`y3^YSgz#8HSl=Xt zJ2Se6i3G~bgo`eprsgMxGL_@%=8Vsa563MbSD5VrmZ{ePqoUw)5s90zQ76u}D|)AC z*#!#d{cFZ1Yo;1cf6Q1rR4P=s23QSA2WaTx08DItz%oe8mJ(}rT*Vne%vG!(>huW4 zpySB-6#0j?LV3boX)TzG&h>0i9v~;6kYnV>GoRXSsyUPnuwH9vf+;OHoM_-CvQWSJ z5^$!~%`NV7xm8AZyc!J{tEW*GCvtVJuh{| zd!+|wkz|qc$P^4HPgc*Sj?vfoBRC@;qoQVI z9ch3J&&e|68n>mv(&J_h_Z->cPE=LZPT;h(S6Z?pWY)oPkJ|pty%34B68!HxHG~BF zk1`boG+!Uu%NtXf!CLH25bEBfYZ+nyWyj1SKoCSiEotzGqScZVVV&G7rJP8*TI&2V zj0D>dc2yk#jdJH-7ZplTXoe6Q{-Z)R6l>3l9FXWq^#-%-eet!-Yk<-{Wrq=e?@j>V zTTkOJKV`LC{ES+Ahr(yzcY*LKGiV2=N&SM30xJUt4W(li+!_!A8l^*cT%I9@Nkl$U zY>`x99w4E_PZ{k=f<*4fb`d;+g+{gyqF&z6ZZTd{yL{aXLopjSIh4G+=sM(5^FvY7 zS_6L|-vPAN%EfgM$|q^0Bw4@C&)#PJdiq#|?Gw^uy*3S+J#--9p|ot_*%pJ7kJ0d% z(vRIDk(ENcts5OtgQS?`#ZBWL#^Z{CtE-Cib~w^wWMI+d2j#IoR_7d9Q-JMYmKnn{ z52c=^cM_G5kXUvc>o9j_re)T;ID48p%f4E7W6+HaPWz8T8W={R%}G|xq^vTC(IxP# z7<-4-0YXiiYk*xqzh^MnVT17mBLVAWIx}Xm2ZXT@7&7HE3=Oa7rNp%02*n- zRbzldG%FpZp1wEuG%<4dvwb=DSKv9}p;bbkxL^n-nLFouY%CB$Y=j(Zh;GQ3 zto>p#PV^b>ssDD+_GNNw_js)f*Js{t+#0w|S+|V}nP6&8bYEXwT--m!?}u%Yw|j0< z_8;!nT2;8{CLJNHt3lJvPCwr4|7dMr{&pP@?7v?(GS^~C#@|4hYf74L)rdmX*k=#t z%~0?D!TMWHRYS#|j6wliTyQ37egqS4icE!04H77ZNi3ujJE0s@Ap~`|On{xLrm#M_ z_fjb#Lq`aDx3j~Tsm6jw1~$qFz2|9*`>Een2-T@K-t^)5_)3t)`U%mD_s@(1=dYV2 zTIb(^y2gaw&>`BFuaG{AAK!;R_^KaDB%;6f491GV%|nRbD&e<`jAofc9yIa3&0nfg zFl}79cBRLhT4mEcH*bq8!XX}4qwk^(`}q&PFY8Zv`wt$HHbBea*X9`z2V>Fe#SuhIBG~TuCh|OJQY8vFfe{{jSwa6(_+Vm=v+TKC}8$%)A#( z)6J>>gn2)68*iPO_%bB$J!aJ;GqLC?2*X(=TU{?o?A-OLvEifVlKn!fSPe?rEf;LwshqFhxobe?zL7Gt;aj%FG}IsT*(SmR zjjt)CYL7`|x-nF!>7nj+Gm@7}_VK_@Pv})@I#0q3!K8D)Nub0O03)z}Y;3w$HG!+C zlNa@UBva+%ry{0omn9SNVyOa)X;D>JNbagm>ORj5_1F3y2SJZbTEY#XV>V6sIoCQ5 z`t)$-w!=QsxE||1C5&CM9w#CO1v5Cc7Q0I5$`FX4vrn)mCFhzk+3*x=$6R{ULC%jJ zEo9oBYAG}_i0|J!f}P1(;_+o8Nx6=~*!@+?`Qy^fm3KrazSghwk?|Vf6~HW<_`Z_B zC5WPQ!?UxXz^zno_I}aSE(?x>r60}F5Dl+CgRfMNPu|*%SGW}8j>()%;7|5v8l|3! zoIgYmig{i5@->^x!jbb`%(YURL)4+u2CwJ|#^E$V45ZMgK*kah2Q^$`=pz&OlNC}! zs|}Jg99GPHB>%ekK5){48b-b9Ln^Y}KKkwsAw{X!Gkt!k<6LAbk|2p|CD}nK^s9x3 znF}Qw_#78ao|vn?p&NsAoGi91iJ0;c9j0UwhNOWonHT{%Bm^rtR@!FGh&W8o^trW> z!RT!J_p*0%d?NJmB+XT?e6(GrKJu=%Ji=oMZ%gD)@N1^~;2}HWakC<4B<>X**vmX~ z>=l(2Uf)zSV?E+Qw&=H6_wr6&a=833tYC+?D$^bMH>cPMh@z=tPJ+PUQv7fj1&X%& zeW+$dQl+?&Kd#ym8=cq6Fit+oFI%4kFqKxASkSB$jAoL2U{$H>_)|-twB^yqrANq- zFxH1=qq~LQ8Y0&eLoSst-miVVr~`qIbi-#2P5krU*cH)3KfU|T<&fxp!9F&*-D?YL zcXwAJaJK&dcxD?jXd83e%J4{&oq9^-i4-wm2|ooCKaV&{iZeka3l_26kz~bVBB(%6 ztx6fnhp53mm~RQeU{erBu?G62D-&{{3!lMsIyCZ>L{B{4%SPqC2~V%>#;L#i^7xz? z&)fqtE7pG5QA2TO5SSlXMd)1r`p@zn3k5i8OG$!6tJ3=jMoAszdqz@LTrJ2yc{ppN z8YcXL=lTrNMqE9%(p=>|>zu-i=kwbjLJsY;y-nYH_;KuwE*U8u#+<9QBVVUhROPmw zA$(DT?1+*oo#c0&Z$?20t%2;UZmVL_tJQLPH7EDH)Gt<|-?-1GKV^^h6q-WJO2mS8%EDwjnhvenQ3eNn9Q8hJve26kvgMtD+zI(+6%b?0j!VN!akZ|Hd zw*-#QDUdSG&oR3Y%&UghmjAjOPFz0z<>Y;Yf%@xneqBi^u8Ok=rhE9@7Cc#p3=!eL?-+o=5|#uqlHK#h@orow^C`$E8)&mW8?OF9aGsBOti}J z-1mXU&s@DnFCty+s;j>8JVPagDm+D;J)~b{a#YZ;W|{HOrK0`Dhn_-ca?p&wjO5wg zZk}>31Vz5R9=QHUDRQtM!ve3lt>S_e(i*= z()Rt8GdXH%H)tu-&748LBNA6dVN{ZAI=P0;!mpYL9Xg6xKpY9l=??kEQicZ$tDUCv zR<-Ocj}Wt4*6w5dA(h+dY1&kUPZmljlu2Wl!Vjc)#5qeSnmbLlC4yWy++0^Gg>IY# zddr;#FK@`rp9#9{R3|_B1u!#ycAg!aq{o@9nZJM)S0FKRS++lsHKiYJ8SMi<+?uA0 zh{w+Oo!O|w+FU3SP*TPoy5LY)0Uwy(G{I1GF*H`Jw;lgzl{9Ff*#cz(I6`WMY+fDZ z__?yH3sZ3%IE4GmB4)72$u(Ccpc!Y}WI2pyrKf~2jXS#w73 z!OX9-Cvr&N>0<*bEPAd_XMvQh3~WNaf^ww`8xNOzBL?4b#aJu!)ZohhU6Qx|B2Ar7 z0>wt8Ymr2mYJ64F%9zDRBU3D+XcVTiTcP>c9glG3NY+WF$W`9!7K!6L7Cs0dc3p^| z@;}I>?xmXdePjKA$K!j%_5OWrV%LlBrfYw{knYGWOz>QNs2w@WdK&x?lRCq(Hg7*B z3E33k6uA2IZ#avWJdBm(s=z(12n#b#AR`M4;44wk*9T!p`uh5hNI?f`3fsD09u~qc z4~)p}ItY^)Ii-I5)bzEvyk)5hoC0=n3A_L?wf8h5GS1Xdj+e6o+qXXuyU*kqvIX># z^<435EF&>O;6Bx~i0GW~Nj2YttXHae2kADp6yK(4qt%3FdN?s=3#(q^`)|!Z?*U8p z>tY!(BqFq(hk+_H5pRK>i%uF!kl@9bk*OL=(%th&M1N@CNW2GNe`y*CQ{vlP(%ABFOU6szx0-fk&!nG~ z6H?MnYXa4$8hof40>OtSTrsCav9UV+L}FTKK1RA{1yy)b_06$Do5Re1Pa8-uufR(d z0G`*x(j|BN5G_K5(IOz~*%eNv;p}kD>X^t0i$v+wWSo}|p|XggF_MXYa5XOGl5Q`F zjY3XdA^eeJYJ@N~K`qDvtzO=rh`^^RE9VyMvGDXvt>aA{a<1d8>sr;pW$y0jQF@-S z(bx(G#sYWdxB+kd&9yP#J-jTg%=by=z}I+g>{#08^3{@LsnJwQs4y5lo@meh$L#4_+_yFh9n-kOO34 z{Pgks*%~@iA~#HE8wo7qw_-1u9&=a4*W32{WK;PFGa#Gzl8GLL_FLjJpJk&=LWZ4^ z){;f0!$@OTM`KO83lvEvVOAlrr6}ct6xh3lhVc=Zvsvv2Vz1Uk_r7meJ-#-Jt&YX9 zkhP^*j$2kFuEtp-;#Wa9kSXM?bqFy?`O^0Rs-hv9yL*(PKbcfP0{E!W@l@XKkOsJf z_J&FzcWy{3L+=U+voTcS(9T=rn&|nAJbV_er@Lr zhIODo_6RYcq3}j9ORD+Xw@Y{G$l=aT>XHS&Uu*9%e=yr1OYVWZ3N6d_|sd>?7gR% z#VlQ@Cj{1+s+Z3)XfztIjBQ&6gS5WmQZ(zd5p!57sIU7nm^>7VV~Hj28vmp_`E&?N zvpbhk`f@ULid;MAuC)XM=k9hP=<;1V%UOAwBWqh2SGE{d=K~}@pGil8w-={;tU__X zE~}S3o&x$XKttb%SzU()JV5-a$>2 z(Vkc;(5`6bd?wGVrS@tysQsQoR(|y}2hfi7R&=e%;Wv&20uu!Ox+)~QMq?MUAUWmF z;*>jKU$gnd)=gaEHPpC>x^x7DUg#?h5tP8gNs8;SNeQ=`&-@w_Ow(<%ro3;ryH*eE zM{*_Y4@3ss1DlM3YHmg}r%wE2gGD##Lpu)6X)C^2OzH?G<>$=eODAx$jJ!FRn+YM{ zeDKk0V}K$>uajE-{rmS*)6Sc8RigV!?dz3v|B@u&+U)|NXMy@#mN_ zeK=N{9td}_sf-{AElC4+1jCFD5BmRrZ0__p_9J-@S)j=ZD^X>R`Ey&AA2i)B2Po2d zB0dr1|6UrB&6jfoqTy%~3FCUhLj}_@*0qd0CAV`0*9lsOT2&s$qM@Ugzo> ziHw$kJtfhMk>^m5iu*Ea(tXzxwg5vHH@6p5kIhU5k8k9b z&l#I<&lZCD2)eFV884%6jp$oI+tFs?W0R(XdGKrjGccU7;OP#^O)Bb5G9LOq7HB$m z@3=i*P6YXw1GUhJ3X~;I>ZFB~ZCq(w?jfdMatL3?lLR|XoPA}HEB?Mq%&!`?hhL5eA6voN8Fk z)G8f0akjS;>^U%lI{g(JF5L8M7thtY-v!se<uXP4pSBw^y)81?`bm91NU!m{S5F{}yFZTr1Vg@E~EqUh*HCf|Jze}DjtzqD;c z$%%vF$7y~7N*5n=f5%&K8oBNj7D+fbKk*XOdN%X#IiWCoqj;0n^WvuH!|M?#KkS~& z`KMTwPS0=qdz$alG z2iMlTm6Ly5o&Dp2-z2wk39Da~0u`Tr!VE}zG5`lY8Gz1VBJ-XmV!hF@#FUt97eYmZKhzWp6&rL54l-}olV-{=15?mOT7 zq|d@xGwV!xFY$wAXQiL#r_HU)<^~2A^&kTD{u<=qOPae`pZz*-Yy6KHv?l=ul0}=U z{EL_OyF7C2GbF3u6sU0(zrGpcJwK^{c5kdxA+@AJHHrOC9==A_Kyjcwtqc4JDU%#=jZJy zzdYHsyxnh(j1}cu`oR}b!F`#B1V3Lt`4GJRk+pJrZM#KO#C0prA=f}^hcU>i-}vg8 zba{0JZw0(gcYY%bL?5-|cOCSCa5aPicryWseIE4kU`Z?Y0a(HC*ciZOe;FTN2`}P0 zj8K2?Wg8gg67YcHkdWH#1O0-0wS>+6k!@HOaPvS)%s1(gl>6$RUYYCJ^Tw2E+4!Q1 z6_5gwZi`z2|5o+ZrzM^<7Nj}W*VTosx%9o9osbZf54j_m)n(=jpaI&-JmG zF+m3YW!s1XX0HdQ=F^>>otm_iHL<%P3x3~I<6lP^cS8a80pb_C%Xho`gk1;3#c9+a zrja(jh6i9(`DfweA71!umPY9LjM7&T&e0EPeKt7aKVH|(-1+$5C%zMcT-^7Au8D*q zTf?BzHL>NV85_Gl?Iu9e(rW7irIV}w26g` zZuvOzk41GU)9q69ZNP>9*-3T!NTfe} zB=T(l7@pe$pKd;HU-h^)`rmeTc7E|`x&0M$*;_u#r}1Fc86exZ6UW@nfH6zLv6olgzoNct|Wus zDitAJPx0@MuDVqR9j-cL@3Q9P9eUzugzSbg1+4%74>RmD-Q4KIwca0DLiW5M?wQ48 z#^vPtmKDhO;vZB)r#N=m6-CUE+HYf?{n4X+`TRWSHA71v-3UgeK4duaP!@I7dvgH( z$m0y#%cIK5N@L(_I0+7f9?XCA5;MwZNJ>h2fha!^7Y4>fg+Sg6xM7B5%^9hKyl?8T zhzKmW;+Jpr^`}L^UWeobjsBgN|s;s7#(3RxcdTGaypyYdU*b70WaCX{A zRMsr$qt3s-gCeNG$nY><<0xTof^O*&h|>r-yYZ`VaUK6BWH0u3Fc^C!(Er{ITv-2r z`VkBmIKo%KZ-;y?aK!7lF!y9_Oa?M7PrBi$I@&6Gw{P$YooTi)DvlY$1=Lu9wWo#oBS0 z)Q7JSXJTxw7gHAYf`(1rK|$NzqD#-{VOuBuC+Tc20sZ*Ap*26r%gtMz(9Jp6crpT5Ej!!AlvQWbu46vWpy)I|m)U zAZECE`})-0K4vHr7yT~&7oJ&AO_-z~v_-r&%?LjGb8(?{=2IAYT!48U!W=-XV_#AK zE~~NEduiNIY0PV@Qk#A1pYRVKeSNy2vC=JC+`91k_(0$>#+4n5gmb0mDD@$?#npk$ z?~0lFV~0HD$2CvVyj@Hyl{3Cg0HfHmI66k#Y5Lwzc{D%VsaML%3PsU?8?hjXS55na z-)-3WTLMRwP`&-;8z^E4c!ujmkY9yUT=TpaB?<8*V58OfQooTQ5LOz0=mssrX)38F z$K}K+1G5U4&ii!@mzwBxY^RMvf#Kd--hgiEG0YtoRMXh}j^Ff^`n1yEv>wc0#Hdvp zHr9}_2jt8^l=p$>_p|dtLaCXDr`<2CDA20&Vt!wv)?BeGs;s3Zb+Ud?qhxWIX5V+? zl@W{r{8GS40@M*#WgqJ+zD+Q0_*>NVPt-LMk&7MM8?ptEEZ1BcM&KXFAf&M`SedpV z^AOr&R8R=X6m-QE*ea}V=EBSX^nrjeBdu_qWf&^t0dS6u9>H~D%**-_S54KnQ-XF4 zl{6AaXu%Hfz&Xb`3Hd1+6ONCz8z;s;2e3~~NM zp_Pb)$x$Ehz1UIG_}yJg97mH_5+gVYfbbun4Klio;zu=M&fX#(LF52@^h=CW=*ks> z0wM_7yNWStF#0H&OaUw=dITwgIX`S^3o%bF&xk&uLgDP+NNmLkS+fK87KKFy9+-Js z3bSr{(O!5O)Xpev$CWqON{~@!K$lMCF~sGIq^^E59nm@ust$618I6r-!z`;q#Q}pz z1mJ@=*^82*WXT~%3PU2zwb`!^+G_j-+P6L?UqI zW&r8LT{wM&;<3f3Lgp7v|{kfK7D=9_Xt!=n?q_FNY?yN zee=)hEU+UJNUrRdFUm+(?K7vaoT9c7YnIGD&4^NXXk&y9S=oGx_R9|u2xYZLmHJr% zoPf`3j@qN>K2m5&eI_u#`r81;hCG-wDvv-YV%A@ukL<_;E6^s~={q5ODXyM1!`__# zt`UDfAjFrD9g&OCX|(g>wj3+RJ6ufaOM?*PLF$ZGlNUV1gdsG;_0)WkqsvX{k17JOP>B{&cNaqncHl;~&m_+DN0ILaP1=-0 z$H`0r@Ygi6>XQQvU+?0eT5NeQD7=>l<8(1~*t(n7#3}_<1>46CX2Pl4YUiB2uL94> z!yqMXiMmCrYBRP!;tqX> zBFc$GZgfQN_DxOoo2QUlV$Fh)Y{_mFKbz%7zvvaj_y1 z4+SL)<8b(JfxneT@U?cAjE$Aclx=vF^Lz^$Ou(# z;Pvy`+z0d3a1}0J%gp&^*O%6Us>MZGrAKMV=Nax>Ms<4Y^+xVeBZ*SP9wgnB{5DQ} zU+Q7f#LfiV)@9&8#U``6DJ3#LEd@HWq;6*bz#st_C`OzEd?BGMaP=O`o$K(HPjZUJ z8gR2SMd=VxQobvk|iX7veMBv9NT|wcB{D@ZmqXCm`KL&kmV)}1}6}rFPX)cAMfFu6j@xt&7-K=?)mxg zRZ1~lDkjKjeH1?WGUJmNjHyK7Ib7wKo)x7@!bAiO-G;V64 zQA}7nhRRYxfPf$-My&2RU&Dg3BE5OEQ6(n&35r3rKVg5np0_CQ?b{!NW>!E%DNKW&x3lRyLEpc2ryAD~t zHN84xya<_7MGDzL4}Po|eHVj~asIOu{D})(6@eWwjnCW*vy0A7l>)M&jeo_o6cuCu zef$vU=zoe~$)8GL_|^uF@!PK7zcdGI;58iQ97z$sxDITp8hoo)=tW;{`i^7TE}=eK zx*Ds*eZ7ygb!q=IlWNmhVEaL!*Bj~FX_2@z%wD;z=Dui)LRjq>;;$bbpk_xx>KBjQ z=g~F_#>?p1{-swherC)T_;0^u444Tv3-&}|@ZjidU(tv*x7W!V=6SjWub&q4+nU4+ z|BGcA9sl#^fC2tSvw^rf;8E0}s9*2|o51}!97r}YFtW1dYw+EM4wxmj5PT;f3e7L5H+!Kbr)Efq9ah7u4r2X#71qz+w1Vu3i#Br@b5oU*jp4_) zg^15ng;P`dS`%1!7k&4;RFuO)J8z$PtdI0M;_;M<%beeWA72GIltX`@^z zKQhn^Qz(`Y1=0|6wK#G$*mL;_K*DHw`Kx#V(9-Y-nDowl^8TmTU~M6<`fCtC3v&Dt zj-@t<16DA6_x+bYr?7Q^`0xKH@1U2#7eviOgBWyBcngnY?&SY2@$dAPZ-6BX1)`^% zPQe<`TY+r};WekE-~&J4`g|W$Xe`go&Y}dc!IqJpnwg=Y_=_2DAyu;G-G+q=&kxro zrM!byV)T(9Eq?+;tRz8`uG&T{sh|9(s&-Z9YN4Z}1G#0VZ(6rN*YXTV7zYs_8Vd1X zXS!lxH_|XeKVhw`Rf*}1FzqL;QVbwqIq-Z8lu@*8#k&O*Nq|~wzr3g@#vp3@;hc_A znNuzs!$=S!1UBOSD-!!2zh532@dIb|i z{#@YfV}lwxa}vzfT3&%%Ez3%f4T9YBGixa?pBi)c+Ei1+57skoPA0UY64nk*XUwZX z#YOG@-QGUYual3Wy45qO`)_7iIyW|KWqc2qNRz>J@Svokir8)@Kr|Rt26%Xkfx5Z3 zg$MM#q@Y^Q$mvNB`dI2vz5PV}l&^7-r5#*HZ3amn4X_970ayI}cdV!h2i+CPUa?+HyWKuNAGzBZfuY5Ldt9`;-J}1u zJ5lgtS+1@(;~FH>*Y7YI)P;Wn>jR%x-NmSS1^)c`^L-@Xj}RW}rhxH^`E)||RtO|S zG=R$u(uDfSIkZ+lzFd@mlGMIm<2ES4137{z!ssCu7;*zNnEe~_TiOE)JnT{I4Y1!Jv14^e*p~2FLVV%#6{811Vr=nG99^c^exCXY7cvp_A0zoaPbcR*tBP_e z8f#XfELH4>rTTC0l4`#rODD!aWOkO2_ISkc=G0uBwxy*d?qk1x(UqfgLa#rRHF{x? zgX>?QSj*lXr`q>NqbIy~fmrtB8|W$O8N#?oQN?7YGMTv_F68CclD$f0_=LFs_u=NH-}w?F#rdymT>q`fm)^e{sNVY6vKQ!O+xbR8tw}<8RXx|(UQ~8?o&d$|3?GLQ74$)%{+~wK19qugjqCH+l zc=!>$43aS*4fZ|A0lx0;-D{)!aItQ_dbBafPmo5z3ZpeJkcfNkZwP+~LVBm(ulhki zJq9NTm1ryJu-JT;GV@VYuY!H1j~=0loMg+2l@vV2pbaD|H-m zf@v_0%;k$++sI` zuI(-NLQ104h1{1`kDj6ZU|g_yG+t7=UpZYZ#Z}S>{A_Eejj6DBZ&$*{Z5f%G!zc3j zdx|h$IIkwB<3iHJfO59FZg!F*ou}06&m?<=0S9+2Kl(?4DYIi#uG~AZ+G^XF%gYs> z%mEh6eTs_`(PiHBekJ^T((8+gM(n7%#Mf7!t#*H70Qrz>q9M3N4=?7$xC7=WJI;0- z-wYE?E!H#BJqJBLLJ~cXKFvnLo{4g+s;Lqlx;}6_cV{m4F>WkZOPcSOGTq23=Yf2p zug&nbldX=bAze?}Avd}I-O3xna2)!Rc{&xG&8(&Ce<0L2U!n!r z;A1uPWiAhh?pYM*-j(G-lP_lgcyq;opq-Egs$aF zNAmf8J|Au>@`NNuoR@sz9?cFr!CbUaW1K^9wj0?pXCnvsC~&ZwJbv zE3E7paYNZy`zx~K*O&k|{f$|Bej@hxw#wrDv5@-z-dZB2=Gwzf+Q}Q(XR{%SDbn9?0OJoUYR< z;{{ZRHMT+@mRd)6Rgjcmr9CXIz03KkMIcNB4WcV6tvQ7k24ZyW8=xvgu<`>8{w4D% zhtfr5LM%f>@Nbj@_Ozt`JDiY zBAsX85l2oYZlKZ+jbZwcET6Qk zsbnK+uuA+up178mwU_7fT6%vw=lB8TE)yFW^W{U1#ShQ)T$)|$wB36w3AQGpu;)`Ui;*nhM!etORy^68Lpjoi@l{Hv{J6_aFB@5^wX-vwVJJT5yu zw{EJXm|wI_np011W2^+fhU^NB)}3Qqx&Zbl6FJtzH1O4t4{$MHA4g%@tRqrua{Q7uUT!^JZsO-(^`lv3Ws zw%A1)QB$;NxfMw*VAGwi>naef4qv6VJ0|P=C0{dO;p9WtF!Ei}jj*9EQ60XNqa8nN z*`0sbFW-Oo6z9on^Xk`(J1rBD~>`?p;h3K>FHmcy2ov9>z zAoJhK-^SMo|49_Wk7|$L~v>}L)fAO zFGZLsGg*(?sj^4oO}U6>{Z`NTs0L4-P82SSNgYRx4IUhD5a z7rz)GP56*j!0WKk;#wX$Z*q0>>h$-!yP%zc{h{ihB6Iw|x6krLS42iSmhYH$4y08o z5h56z8I=bVhhb4tVr_JKHX0N%v{=WcC4zlrbV5Onhh}M?dc7dio zL{;tK#TUMe6&inP000ggNklpk3f)B9L!iC0 za7iimd-J3JyxNydukQeUybUDPApc8nTNY+WP!=F&C|AOtS%Vk|8wN2WRwf^ROUd!K zQtmccx{70}StN?2j$7_Y5BE0)?ezihf3F2JyU6r0u-k?%Bg{xcSwVA@5!VWC+9F4X)Fenli2EmtYHF-O znxOnLlPaBAre87DT~C_>Pn7X z%_=k2t39f#L#j82(zUC!bLu@9`7KJJ#i%%i!VKYciAiz#@a<>sy62&bfAjdNx>CNyWRxxaRkO)?$)VgR4$hTQ+Vq=wXmBZ4mQ`dO6<&^{|YEihc7=);EhSDP&*HxU_t&IpDJ!-K?YJrIlKkS^)8Iv#&kTxy4t@O#J<#*NUsloS0h}zAHa8^?47U^ zM!N!X=s<>%MF5G&(9orzI4_75pvw~4H;~}7bb!Pz6PE-ar4}i*RkD+^ZLY7Co7pyX z)+kr%!HuBQRqNDvk_gHP4w-YKqcy-f&ER^tJU7e9_5TtCtN>y` zPK*>H9BD`=0M?n*5Lo~=Ws=Y#g`r45OdZ&i;4C2JhRO`O94n2m26aoZX2`})G(kYI z(ZmYSr7)mb*@lrl6GM@LgcaBfQ4Vj$GOTgXynM8nQ(qyyJ^)^|c(p^k`Z;~-;};U} zXSrK3*jVJq0Bb160Civ!LF^$Ro(y!Q5mphz8p?Sn2Xu=Dx)8(~(B9^DUS&Hjb8uDC zz650rkRWFO1kwe!sTIlsRu;%zh18X#3uav+$I9pC2(o$+>F6Tz<_gfx0p&q%jNHz{ z=J=_;LVA4wyjDYHFIJ>u$Ps`6*Z~lM_JAF8PK^fgNjOLXx|P5T0ic6~h*W!M53nx5 z2FR&_8IHDGObCG%s6{4gi(8+7~L%c8D+eeC6MO{jUpT z#c*#2QU%0o05Wnj0`&In=w9fj0KGl{UK=7_URsRpl-Fj&F|{4oXps~tCyiK1R!--z zZb0laBf@QI#5#hlg&AsuN+6wwM+7)R7ar|gV8{RC{WDM>2$TbqpMtf9b^=pM-dq7< z&A9m2oa3JV?>|we0Q)vTS|A+?zziM$dJ9nghQ2&{Kkd|;a13LFi;189j>L^&egX&I=&PjH zcL1*ex8Zu!@s#%HgmTbKP9$eb>`rbXY794Bg-lvPsAH1OW}wU6#^U4$Kspl(bZK*W zu0!eoH?_zK*^GcJ5fXtc7TpTa2_Oxmwt<8(>=+ooO)z~2!%hU(zYCOS8D<2`p9Sm~ zn7#v;y$dK#1A7kvIeiyT{CPhG==A~c8s&@*%^FGqNb5hX6=OGMV~i8`4yD z!T=$}B;z5Y^om0g_NMHjz=veeh7Ox%C5hBpGRDX)atxZ0DmEh>k*rD^2nZ6zs-n~a ztVIGdQJE0|LE3k|7~Plw;z_K(SIK))h-G; zl{d)d=t)kKTM?QgNURYgzFm&~z3t1R*9X9BCz5khjE9?Uv6Re$$#BHhSe}fhVvU1R zCaP6pwMlF`YD{3Sj0LHI)J<(OBr3@A{ zi_o@dnCITQsZEkmR~u+MX2mO}qFt%;Gj!e?)Hoz6^2`pU*nv-x! z>xgWcrj5hd>_#wKUk);eR@0PZsRqvHEMQ(pQ7Y>T6GvkUX2Wf35Rp?NUO zC)X&N!ml5;L4SMlYe@1yVo?R=EUEU1@!_g^(pg2Z=nLw1`e23{YU@{RAVO;zEJIk!D&+rvd)9=$#QUZ)T^ zbLPzFuV26Z+f`NVv~A1B$H&;&*@4y?#KfcV2H)r2|1bXZh&pD2|PezOaq+ z)-yAQ5T$7v4In0IaCrEGxLO_P(`U}v;c&ujyNXBq3tg6@*o7?_k8k5MXHG%u0YtLo z48)Mw6(ZZG1fy}q&pr19o_YFSfh<4M5B^>s0I##NXU{%$<;sla^nZcadTD6p%$4%2fE3LaO$%~htVQZZb zGaGHNm>=kBGey_Vaj^fq#1OGHJB4@s?43Lu6{zbg{P^RK;+dzP#P05k=(^R_pSk1q z&+P8@zX9m=0r0w-&1TJQx81f^Rh29j3k(JWR!RYYFf$utP!t8HlmI}CF~~WyF{U%d z_@-&r%jL2h4u_fu5J^6S07+4nn>8~tob$5Ttl0a42ZI6Xx*xqWhpD7U!KiQedViPx%Yd0S@izDP;b_O?!EV3+%3GDMj9U5Rrrsa?>>V=5{P*oKMg8>tfY&IL+wykF7=$u0cAu_XGuh*S1 zrqWt}8^H4-(lYZGlu{4x?d`PyaQ*uAz5;sv6yVMBzylA+M?UfqEh2Y^5ICj8%$$ue zAR=bw+;v@U+m?tZ1JLPYQ9zevNnO`*2q7w^r0Y69JUk>KiU6{6j*Ft87$d5x3Qf~| z8^G^-@BehUT;A6-&4YDaUtX`*ssGq}y%YSVACUR8g@4vtP@P|J*m2L}gYt^MSBz5at3W7U7Wz1~}B4d922F@MF( zcUfyc$jq`_E;)n%oZQ8OjWKwo-4_5dnM^vRlnx<4YaM5^nZ0!B613LX-rm;J=~TY? z&2RFpx8C}N5W*i`ym+y{l(*M=D|P??aNm9R)%V>ls2dCh&tAE5<+Eqcp8d^FfBMt?mfgMHPf7p)z+$n8*REZ= z^z5_GelF*H$vNl7%9tZnXPX={_&5iot>S@(b18;@4oxo@$vC=G#cG>;lhR6cXoEp?eFhD z9Ao^K$z-zr+Sk6;5BOfM*X#9qy-BoQUa!~d^?JQt@5lNtIHg#y&y7>=00000 LNkvXXu0mjfS26k` literal 0 HcmV?d00001 diff --git a/Barotrauma/BarotraumaShared/changelog.txt b/Barotrauma/BarotraumaShared/changelog.txt index 9a8ee1ea6..b84699444 100644 --- a/Barotrauma/BarotraumaShared/changelog.txt +++ b/Barotrauma/BarotraumaShared/changelog.txt @@ -1,3 +1,38 @@ +--------------------------------------------------------------------------------------------------------- +v0.1500.4.0 +--------------------------------------------------------------------------------------------------------- + +- Overhauled character sprites, ragdolls and animations (WIP). +- Option to customize the starting crew in the single player campaign. +- Talent improvements and additions. +- Merged the talent and character tabs in the tab menu. +- Disable deconstructor button when there's no deconstructable items in the input slots (also applies to research terminals which are technically deconstructors). + +Fixes: +- Fixed inability to install/update mods that have periods in the name. +- Fixed stack sizes being displayed incorrectly on items with multiple inventories, e.g. deconstructor (unstable only). +- Fixed depleted fuel not being craftable (unstable only). +- Fixed leftmost inventory slot overlapping with the chatbox (unstable only). +- Fixed medic bots trying to treat genetic afflictions (unstable only). +- Fixed nav terminals "current_position_x" output being in pixels when "current_position_y" is in meters. +- Fixed tall subs overlapping with the buttons on the status monitor (unstable only). +- Fixed status monitor's item finder not finding wires (unstable only). +- Cargo scooters can't be put in toolbelts, crates, bandoliers or each other (unstable only). +- Fixed status monitor elements getting misaligned when 1st viewing it while linked to another interface and then individually or vice versa (unstable only). +- 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 tainted genetic materials becoming untainted when saving and loading (unstable only). +- Fixed genetic material effects' strengths changing when saving and reloading (unstable only). +- Fixed tainted genetic materials sometimes giving the user hammerhead matriarch's genetic effects. +- Fixed inability to tinker loaders (unstable only). +- 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 recommended treatments not changing when the strengths of the displayed afflictions change. +- Fixed cargo scooters working with a battery in an incorrect slot (unstable only). +- Fixed cargo scooters and volatile fulgurium fuel rods being craftable by anyone (unstable only). +- Fixed misaligned nav terminal and status monitor in pirate humpback. +- Fixed crash when ordering friendly NPCs (e.g. hostages) to return to the sub. + --------------------------------------------------------------------------------------------------------- v0.1500.3.0 --------------------------------------------------------------------------------------------------------- diff --git a/Libraries/MonoGame.Framework/Src/MonoGame.Framework/Graphics/SpriteBatch.cs b/Libraries/MonoGame.Framework/Src/MonoGame.Framework/Graphics/SpriteBatch.cs index a2cc3d4e3..d6560df72 100644 --- a/Libraries/MonoGame.Framework/Src/MonoGame.Framework/Graphics/SpriteBatch.cs +++ b/Libraries/MonoGame.Framework/Src/MonoGame.Framework/Graphics/SpriteBatch.cs @@ -3,6 +3,9 @@ // file 'LICENSE.txt', which is part of this source code package. using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; using System.Text; namespace Microsoft.Xna.Framework.Graphics @@ -25,15 +28,58 @@ namespace Microsoft.Xna.Framework.Graphics /// public class SpriteBatch : GraphicsResource, ISpriteBatch { + public struct EffectWithParams + { + private static readonly Dictionary parameterSetters; + private static readonly object[] setterParams = new object[1]; + + static EffectWithParams() + { + parameterSetters = new Dictionary(); + foreach (var method in typeof(EffectParameter).GetMethods()) + { + if (method.Name.Equals("SetValue", StringComparison.InvariantCulture) + && method.GetParameters() is { Length: 1 } parameters) + { + var type = parameters[0].ParameterType; + parameterSetters[type] = method; + foreach (var derived in Assembly.GetAssembly(type).GetTypes().Where(t => t.IsSubclassOf(type))) + { + parameterSetters[derived] = method; + } + } + } + } + + public Effect Effect; + public Dictionary Params; + + public EffectWithParams(Effect effect, Dictionary parameters = null) + { + Effect = effect; + Params = parameters; + } + + internal void Apply() + { + foreach (var (paramName, paramValue) in Params) + { + setterParams[0] = paramValue; + parameterSetters[paramValue.GetType()].Invoke(Effect.Parameters[paramName], setterParams); + } + Effect.CurrentTechnique.Passes[0].Apply(); + } + } + #region Private Fields readonly SpriteBatcher _batcher; SpriteSortMode _sortMode; BlendState _blendState; SamplerState _samplerState; - DepthStencilState _depthStencilState; - RasterizerState _rasterizerState; - Effect _effect; + DepthStencilState _depthStencilState; + RasterizerState _rasterizerState; + EffectWithParams _effect; bool _beginCalled; Effect _spriteEffect; @@ -60,7 +106,7 @@ namespace Microsoft.Xna.Framework.Graphics if (graphicsDevice == null) { throw new ArgumentNullException ("graphicsDevice", FrameworkResources.ResourceCreationWhenDeviceIsNull); - } + } this.GraphicsDevice = graphicsDevice; @@ -107,7 +153,7 @@ namespace Microsoft.Xna.Framework.Graphics _samplerState = samplerState ?? SamplerState.LinearClamp; _depthStencilState = depthStencilState ?? DepthStencilState.None; _rasterizerState = rasterizerState ?? RasterizerState.CullCounterClockwise; - _effect = effect; + _effect = new EffectWithParams(effect); _matrix = transformMatrix; // Setup things now so a user can change them. @@ -119,6 +165,11 @@ namespace Microsoft.Xna.Framework.Graphics _beginCalled = true; } + /// + /// Returns the current effect. + /// + public Effect GetCurrentEffect() => _effect.Effect; + /// /// Flushes all batched text and sprites to the screen. /// @@ -132,18 +183,31 @@ namespace Microsoft.Xna.Framework.Graphics if (_sortMode != SpriteSortMode.Immediate) Setup(); - - _batcher.DrawBatch(_sortMode, _effect); + + _batcher.DrawBatch(_sortMode, _spritePass); } - - void Setup() + + /// + /// Swaps the current effect. + /// + public void SwapEffect(Effect effect = null, Dictionary parameters = null) + { + _effect = new EffectWithParams(effect, parameters); + } + + public void SwapEffect(EffectWithParams effectWithParams) + { + _effect = effectWithParams; + } + + void Setup() { var gd = GraphicsDevice; gd.BlendState = _blendState; gd.DepthStencilState = _depthStencilState; gd.RasterizerState = _rasterizerState; gd.SamplerStates[0] = _samplerState; - + var vp = gd.Viewport; if ((vp.Width != _lastViewport.Width) || (vp.Height != _lastViewport.Height)) { @@ -169,7 +233,7 @@ namespace Microsoft.Xna.Framework.Graphics _spritePass.Apply(); } - + void CheckValid(Texture2D texture) { if (texture == null) @@ -279,6 +343,7 @@ namespace Microsoft.Xna.Framework.Graphics { var item = _batcher.CreateBatchItem(); item.Texture = texture; + item.Effect = _effect; item.SortKey = sortKey; @@ -315,6 +380,7 @@ namespace Microsoft.Xna.Framework.Graphics var item = _batcher.CreateBatchItem(); item.Texture = texture; + item.Effect = _effect; // set SortKey based on SpriteSortMode. switch ( _sortMode ) @@ -332,9 +398,9 @@ namespace Microsoft.Xna.Framework.Graphics item.SortKey = -layerDepth; break; } - + origin = origin * scale; - + float w, h; if (sourceRectangle.HasValue) { @@ -353,7 +419,7 @@ namespace Microsoft.Xna.Framework.Graphics _texCoordTL = Vector2.Zero; _texCoordBR = Vector2.One; } - + if ((effects & SpriteEffects.FlipVertically) != 0) { var temp = _texCoordBR.Y; @@ -366,7 +432,7 @@ namespace Microsoft.Xna.Framework.Graphics _texCoordBR.X = _texCoordTL.X; _texCoordTL.X = temp; } - + if (rotation == 0f) { item.Set(position.X - origin.X, @@ -393,7 +459,7 @@ namespace Microsoft.Xna.Framework.Graphics _texCoordBR, layerDepth); } - + FlushIfNeeded(); } @@ -444,9 +510,10 @@ namespace Microsoft.Xna.Framework.Graphics float layerDepth) { CheckValid(texture); - + var item = _batcher.CreateBatchItem(); item.Texture = texture; + item.Effect = _effect; // set SortKey based on SpriteSortMode. switch ( _sortMode ) @@ -478,7 +545,7 @@ namespace Microsoft.Xna.Framework.Graphics else origin.X = origin.X * (float)destinationRectangle.Width * texture.TexelWidth; if(srcRect.Height != 0) - origin.Y = origin.Y * (float)destinationRectangle.Height / (float)srcRect.Height; + origin.Y = origin.Y * (float)destinationRectangle.Height / (float)srcRect.Height; else origin.Y = origin.Y * (float)destinationRectangle.Height * texture.TexelHeight; } @@ -486,11 +553,11 @@ namespace Microsoft.Xna.Framework.Graphics { _texCoordTL = Vector2.Zero; _texCoordBR = Vector2.One; - + origin.X = origin.X * (float)destinationRectangle.Width * texture.TexelWidth; origin.Y = origin.Y * (float)destinationRectangle.Height * texture.TexelHeight; } - + if ((effects & SpriteEffects.FlipVertically) != 0) { var temp = _texCoordBR.Y; @@ -539,7 +606,7 @@ namespace Microsoft.Xna.Framework.Graphics { if (_sortMode == SpriteSortMode.Immediate) { - _batcher.DrawBatch(_sortMode, _effect); + _batcher.DrawBatch(_sortMode, _spritePass); } } @@ -553,10 +620,11 @@ namespace Microsoft.Xna.Framework.Graphics public void Draw (Texture2D texture, Vector2 position, Rectangle? sourceRectangle, Color color) { CheckValid(texture); - + var item = _batcher.CreateBatchItem(); item.Texture = texture; - + item.Effect = _effect; + // set SortKey based on SpriteSortMode. item.SortKey = _sortMode == SpriteSortMode.Texture ? texture.SortingKey : 0; @@ -600,13 +668,14 @@ namespace Microsoft.Xna.Framework.Graphics public void Draw (Texture2D texture, Rectangle destinationRectangle, Rectangle? sourceRectangle, Color color) { CheckValid(texture); - + var item = _batcher.CreateBatchItem(); item.Texture = texture; - + item.Effect = _effect; + // set SortKey based on SpriteSortMode. item.SortKey = _sortMode == SpriteSortMode.Texture ? texture.SortingKey : 0; - + if (sourceRectangle.HasValue) { var srcRect = sourceRectangle.GetValueOrDefault(); @@ -629,7 +698,7 @@ namespace Microsoft.Xna.Framework.Graphics _texCoordTL, _texCoordBR, 0); - + FlushIfNeeded(); } @@ -642,13 +711,14 @@ namespace Microsoft.Xna.Framework.Graphics public void Draw (Texture2D texture, Vector2 position, Color color) { CheckValid(texture); - + var item = _batcher.CreateBatchItem(); item.Texture = texture; - + item.Effect = _effect; + // set SortKey based on SpriteSortMode. item.SortKey = _sortMode == SpriteSortMode.Texture ? texture.SortingKey : 0; - + item.Set(position.X, position.Y, texture.Width, @@ -670,13 +740,14 @@ namespace Microsoft.Xna.Framework.Graphics public void Draw(Texture2D texture, Rectangle destinationRectangle, Color color) { CheckValid(texture); - + var item = _batcher.CreateBatchItem(); item.Texture = texture; - + item.Effect = _effect; + // set SortKey based on SpriteSortMode. item.SortKey = _sortMode == SpriteSortMode.Texture ? texture.SortingKey : 0; - + item.Set(destinationRectangle.X, destinationRectangle.Y, destinationRectangle.Width, @@ -685,7 +756,7 @@ namespace Microsoft.Xna.Framework.Graphics Vector2.Zero, Vector2.One, 0); - + FlushIfNeeded(); } @@ -699,7 +770,7 @@ namespace Microsoft.Xna.Framework.Graphics public unsafe void DrawString (SpriteFont spriteFont, string text, Vector2 position, Color color) { CheckValid(spriteFont, text); - + float sortKey = (_sortMode == SpriteSortMode.Texture) ? spriteFont.Texture.SortingKey : 0; var offset = Vector2.Zero; @@ -720,7 +791,7 @@ namespace Microsoft.Xna.Framework.Graphics firstGlyphOfLine = true; continue; } - + var currentGlyphIndex = spriteFont.GetGlyphIndexOrDefault(c); var pCurrentGlyph = pGlyphs + currentGlyphIndex; @@ -737,15 +808,16 @@ namespace Microsoft.Xna.Framework.Graphics offset.X += spriteFont.Spacing + pCurrentGlyph->LeftSideBearing; } - var p = offset; + var p = offset; p.X += pCurrentGlyph->Cropping.X; p.Y += pCurrentGlyph->Cropping.Y; p += position; var item = _batcher.CreateBatchItem(); item.Texture = spriteFont.Texture; + item.Effect = _effect; item.SortKey = sortKey; - + _texCoordTL.X = pCurrentGlyph->BoundsInTexture.X * spriteFont.Texture.TexelWidth; _texCoordTL.Y = pCurrentGlyph->BoundsInTexture.Y * spriteFont.Texture.TexelHeight; _texCoordBR.X = (pCurrentGlyph->BoundsInTexture.X + pCurrentGlyph->BoundsInTexture.Width) * spriteFont.Texture.TexelWidth; @@ -759,7 +831,7 @@ namespace Microsoft.Xna.Framework.Graphics _texCoordTL, _texCoordBR, 0); - + offset.X += pCurrentGlyph->Width + pCurrentGlyph->RightSideBearing; } @@ -804,7 +876,7 @@ namespace Microsoft.Xna.Framework.Graphics float rotation, Vector2 origin, Vector2 scale, SpriteEffects effects, float layerDepth) { CheckValid(spriteFont, text); - + float sortKey = 0; // set SortKey based on SpriteSortMode. switch (_sortMode) @@ -831,7 +903,7 @@ namespace Microsoft.Xna.Framework.Graphics if (flippedVert || flippedHorz) { Vector2 size; - + var source = new SpriteFont.CharacterSource(text); spriteFont.MeasureString(ref source, out size); @@ -847,7 +919,7 @@ namespace Microsoft.Xna.Framework.Graphics flipAdjustment.Y = spriteFont.LineSpacing - size.Y; } } - + Matrix transformation = Matrix.Identity; float cos = 0, sin = 0; if (rotation == 0) @@ -866,7 +938,7 @@ namespace Microsoft.Xna.Framework.Graphics transformation.M21 = (flippedVert ? -scale.Y : scale.Y) * (-sin); transformation.M22 = (flippedVert ? -scale.Y : scale.Y) * cos; transformation.M41 = (((flipAdjustment.X - origin.X) * transformation.M11) + (flipAdjustment.Y - origin.Y) * transformation.M21) + position.X; - transformation.M42 = (((flipAdjustment.X - origin.X) * transformation.M12) + (flipAdjustment.Y - origin.Y) * transformation.M22) + position.Y; + transformation.M42 = (((flipAdjustment.X - origin.X) * transformation.M12) + (flipAdjustment.Y - origin.Y) * transformation.M22) + position.Y; } var offset = Vector2.Zero; @@ -916,15 +988,16 @@ namespace Microsoft.Xna.Framework.Graphics Vector2.Transform(ref p, ref transformation, out p); - var item = _batcher.CreateBatchItem(); + var item = _batcher.CreateBatchItem(); item.Texture = spriteFont.Texture; + item.Effect = _effect; item.SortKey = sortKey; - + _texCoordTL.X = pCurrentGlyph->BoundsInTexture.X * spriteFont.Texture.TexelWidth; _texCoordTL.Y = pCurrentGlyph->BoundsInTexture.Y * spriteFont.Texture.TexelHeight; _texCoordBR.X = (pCurrentGlyph->BoundsInTexture.X + pCurrentGlyph->BoundsInTexture.Width) * spriteFont.Texture.TexelWidth; _texCoordBR.Y = (pCurrentGlyph->BoundsInTexture.Y + pCurrentGlyph->BoundsInTexture.Height) * spriteFont.Texture.TexelHeight; - + if ((effects & SpriteEffects.FlipVertically) != 0) { var temp = _texCoordBR.Y; @@ -964,7 +1037,7 @@ namespace Microsoft.Xna.Framework.Graphics _texCoordBR, layerDepth); } - + offset.X += pCurrentGlyph->Width + pCurrentGlyph->RightSideBearing; } @@ -982,7 +1055,7 @@ namespace Microsoft.Xna.Framework.Graphics public unsafe void DrawString (SpriteFont spriteFont, StringBuilder text, Vector2 position, Color color) { CheckValid(spriteFont, text); - + float sortKey = (_sortMode == SpriteSortMode.Texture) ? spriteFont.Texture.SortingKey : 0; var offset = Vector2.Zero; @@ -1020,15 +1093,16 @@ namespace Microsoft.Xna.Framework.Graphics offset.X += spriteFont.Spacing + pCurrentGlyph->LeftSideBearing; } - var p = offset; + var p = offset; p.X += pCurrentGlyph->Cropping.X; p.Y += pCurrentGlyph->Cropping.Y; p += position; - + var item = _batcher.CreateBatchItem(); item.Texture = spriteFont.Texture; + item.Effect = _effect; item.SortKey = sortKey; - + _texCoordTL.X = pCurrentGlyph->BoundsInTexture.X * spriteFont.Texture.TexelWidth; _texCoordTL.Y = pCurrentGlyph->BoundsInTexture.Y * spriteFont.Texture.TexelHeight; _texCoordBR.X = (pCurrentGlyph->BoundsInTexture.X + pCurrentGlyph->BoundsInTexture.Width) * spriteFont.Texture.TexelWidth; @@ -1087,7 +1161,7 @@ namespace Microsoft.Xna.Framework.Graphics float rotation, Vector2 origin, Vector2 scale, SpriteEffects effects, float layerDepth) { CheckValid(spriteFont, text); - + float sortKey = 0; // set SortKey based on SpriteSortMode. switch (_sortMode) @@ -1129,7 +1203,7 @@ namespace Microsoft.Xna.Framework.Graphics flipAdjustment.Y = spriteFont.LineSpacing - size.Y; } } - + Matrix transformation = Matrix.Identity; float cos = 0, sin = 0; if (rotation == 0) @@ -1148,7 +1222,7 @@ namespace Microsoft.Xna.Framework.Graphics transformation.M21 = (flippedVert ? -scale.Y : scale.Y) * (-sin); transformation.M22 = (flippedVert ? -scale.Y : scale.Y) * cos; transformation.M41 = (((flipAdjustment.X - origin.X) * transformation.M11) + (flipAdjustment.Y - origin.Y) * transformation.M21) + position.X; - transformation.M42 = (((flipAdjustment.X - origin.X) * transformation.M12) + (flipAdjustment.Y - origin.Y) * transformation.M22) + position.Y; + transformation.M42 = (((flipAdjustment.X - origin.X) * transformation.M12) + (flipAdjustment.Y - origin.Y) * transformation.M22) + position.Y; } var offset = Vector2.Zero; @@ -1197,16 +1271,17 @@ namespace Microsoft.Xna.Framework.Graphics p.Y += pCurrentGlyph->Cropping.Y; Vector2.Transform(ref p, ref transformation, out p); - - var item = _batcher.CreateBatchItem(); + + var item = _batcher.CreateBatchItem(); item.Texture = spriteFont.Texture; + item.Effect = _effect; item.SortKey = sortKey; - + _texCoordTL.X = pCurrentGlyph->BoundsInTexture.X * (float)spriteFont.Texture.TexelWidth; _texCoordTL.Y = pCurrentGlyph->BoundsInTexture.Y * (float)spriteFont.Texture.TexelHeight; _texCoordBR.X = (pCurrentGlyph->BoundsInTexture.X + pCurrentGlyph->BoundsInTexture.Width) * (float)spriteFont.Texture.TexelWidth; _texCoordBR.Y = (pCurrentGlyph->BoundsInTexture.Y + pCurrentGlyph->BoundsInTexture.Height) * (float)spriteFont.Texture.TexelHeight; - + if ((effects & SpriteEffects.FlipVertically) != 0) { var temp = _texCoordBR.Y; diff --git a/Libraries/MonoGame.Framework/Src/MonoGame.Framework/Graphics/SpriteBatchItem.cs b/Libraries/MonoGame.Framework/Src/MonoGame.Framework/Graphics/SpriteBatchItem.cs index d0ebb45e5..484e521d4 100644 --- a/Libraries/MonoGame.Framework/Src/MonoGame.Framework/Graphics/SpriteBatchItem.cs +++ b/Libraries/MonoGame.Framework/Src/MonoGame.Framework/Graphics/SpriteBatchItem.cs @@ -9,6 +9,7 @@ namespace Microsoft.Xna.Framework.Graphics internal class SpriteBatchItem : IComparable { public Texture2D Texture; + public SpriteBatch.EffectWithParams Effect; public float SortKey; public VertexPositionColorTexture vertexTL; @@ -20,7 +21,7 @@ namespace Microsoft.Xna.Framework.Graphics vertexTL = new VertexPositionColorTexture(); vertexTR = new VertexPositionColorTexture(); vertexBL = new VertexPositionColorTexture(); - vertexBR = new VertexPositionColorTexture(); + vertexBR = new VertexPositionColorTexture(); } public void Set ( float x, float y, float dx, float dy, float w, float h, float sin, float cos, Color color, Vector2 texCoordTL, Vector2 texCoordBR, float depth ) diff --git a/Libraries/MonoGame.Framework/Src/MonoGame.Framework/Graphics/SpriteBatcher.cs b/Libraries/MonoGame.Framework/Src/MonoGame.Framework/Graphics/SpriteBatcher.cs index 036fd783c..948d0078c 100644 --- a/Libraries/MonoGame.Framework/Src/MonoGame.Framework/Graphics/SpriteBatcher.cs +++ b/Libraries/MonoGame.Framework/Src/MonoGame.Framework/Graphics/SpriteBatcher.cs @@ -11,7 +11,7 @@ namespace Microsoft.Xna.Framework.Graphics /// This class handles the queueing of batch items into the GPU by creating the triangle tesselations /// that are used to draw the sprite textures. This class supports int.MaxValue number of sprites to be /// batched and will process them into short.MaxValue groups (strided by 6 for the number of vertices - /// sent to the GPU). + /// sent to the GPU). /// internal class SpriteBatcher { @@ -68,7 +68,7 @@ namespace Microsoft.Xna.Framework.Graphics } /// - /// Reuse a previously allocated SpriteBatchItem from the item pool. + /// Reuse a previously allocated SpriteBatchItem from the item pool. /// if there is none available grow the pool and initialize new items. /// /// @@ -143,12 +143,8 @@ namespace Microsoft.Xna.Framework.Graphics /// overflow the 16 bit array indices for vertices. /// /// The type of depth sorting desired for the rendering. - /// The custom effect to apply to the drawn geometry - public unsafe void DrawBatch(SpriteSortMode sortMode, Effect effect) + public unsafe void DrawBatch(SpriteSortMode sortMode, EffectPass defaultSpritePass) { - if (effect != null && effect.IsDisposed) - throw new ObjectDisposedException("effect"); - // nothing to do if (_batchItemCount == 0) return; @@ -180,6 +176,7 @@ namespace Microsoft.Xna.Framework.Graphics var startIndex = 0; var index = 0; Texture2D tex = null; + SpriteBatch.EffectWithParams effect = default; int numBatchesToProcess = batchCount; if (numBatchesToProcess > MaxBatchSize) @@ -196,12 +193,24 @@ namespace Microsoft.Xna.Framework.Graphics { SpriteBatchItem item = _batchItemList[batchIndex]; // if the texture changed, we need to flush and bind the new texture - var shouldFlush = !ReferenceEquals(item.Texture, tex); + var shouldFlush = + !ReferenceEquals(item.Texture, tex) + || !ReferenceEquals(item.Effect.Effect, effect.Effect) + || !ReferenceEquals(item.Effect.Params, effect.Params); if (shouldFlush) { - FlushVertexArray(startIndex, index, effect, tex); + FlushVertexArray(startIndex, index, effect.Effect, tex); tex = item.Texture; + effect = item.Effect; + if (effect.Effect is null || effect.Params is null) + { + defaultSpritePass.Apply(); + } + else + { + effect.Apply(); + } startIndex = index = 0; vertexArrayPtr = vertexArrayFixedPtr; _device.Textures[0] = tex; @@ -215,15 +224,16 @@ namespace Microsoft.Xna.Framework.Graphics // Release the texture. item.Texture = null; + item.Effect = default; } } // flush the remaining vertexArray data - FlushVertexArray(startIndex, index, effect, tex); + FlushVertexArray(startIndex, index, effect.Effect, tex); // Update our batch count to continue the process of culling down // large batches batchCount -= numBatchesToProcess; } - // return items to the pool. + // return items to the pool. _batchItemCount = 0; } From 08bdfc6cea1a63bf6bb4f2bb5f80a2c4b3b37228 Mon Sep 17 00:00:00 2001 From: Markus Isberg <3e849f2e5c@pm.me> Date: Fri, 1 Oct 2021 23:56:14 +0900 Subject: [PATCH 06/12] =?UTF-8?q?Unstable=200.1500.5.0=20(almost=20forgor?= =?UTF-8?q?=20edition=20=F0=9F=92=80)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Characters/AI/EnemyAIController.cs | 6 +- .../Characters/Animation/Ragdoll.cs | 2 + .../ClientSource/Characters/CharacterHUD.cs | 35 +- .../ClientSource/Characters/CharacterInfo.cs | 354 ++--- .../Characters/Health/CharacterHealth.cs | 234 +-- .../ClientSource/Characters/Limb.cs | 16 +- .../ClientSource/DebugConsole.cs | 31 +- .../Events/Missions/AlienRuinMission.cs | 31 + .../ClientSource/Events/Missions/Mission.cs | 7 +- .../Events/Missions/MissionPrefab.cs | 47 +- .../Events/Missions/ScanMission.cs | 66 + .../ClientSource/GUI/GUIListBox.cs | 6 +- .../ClientSource/GUI/Store.cs | 16 +- .../ClientSource/GUI/TabMenu.cs | 2 +- .../BarotraumaClient/ClientSource/GameMain.cs | 3 +- .../ClientSource/GameSettings.cs | 1 + .../Items/Components/Holdable/IdCard.cs | 198 ++- .../Items/Components/ItemContainer.cs | 4 +- .../Items/Components/Machines/Fabricator.cs | 28 +- .../Items/Components/Machines/Sonar.cs | 28 +- .../Items/Components/Repairable.cs | 22 +- .../ClientSource/Items/Components/Rope.cs | 22 +- .../ClientSource/Items/Components/Scanner.cs | 29 + .../Items/Components/Signal/ButtonTerminal.cs | 114 ++ .../ClientSource/Items/Item.cs | 31 +- .../Map/Levels/Ruins/RuinGenerator.cs | 11 +- .../ClientSource/Map/Lights/ConvexHull.cs | 13 +- .../ClientSource/Map/Lights/LightSource.cs | 2 +- .../ClientSource/Map/Submarine.cs | 23 - .../ClientSource/Map/SubmarinePreview.cs | 3 + .../ClientSource/Map/WayPoint.cs | 5 +- .../SinglePlayerCampaignSetupUI.cs | 17 +- .../CharacterEditor/CharacterEditorScreen.cs | 59 +- .../ClientSource/Screens/LevelEditorScreen.cs | 208 +-- .../ClientSource/Screens/MainMenuScreen.cs | 2 +- .../ClientSource/Screens/NetLobbyScreen.cs | 49 +- .../ClientSource/Screens/SubEditorScreen.cs | 359 ++--- .../Serialization/SerializableEntityEditor.cs | 88 ++ .../ClientSource/Sounds/SoundPlayer.cs | 18 +- .../BarotraumaClient/LinuxClient.csproj | 2 +- Barotrauma/BarotraumaClient/MacClient.csproj | 2 +- .../BarotraumaClient/WindowsClient.csproj | 2 +- .../BarotraumaServer/LinuxServer.csproj | 2 +- Barotrauma/BarotraumaServer/MacServer.csproj | 2 +- .../ServerSource/Characters/CharacterInfo.cs | 1 + .../Events/Missions/AlienRuinMission.cs | 21 + .../ServerSource/Events/Missions/Mission.cs | 5 + .../Events/Missions/ScanMission.cs | 36 + .../ServerSource/Items/Components/Scanner.cs | 14 + .../Items/Components/Signal/ButtonTerminal.cs | 21 + .../ServerSource/Networking/GameServer.cs | 4 +- .../ServerSource/Networking/RespawnManager.cs | 21 +- .../BarotraumaServer/WindowsServer.csproj | 2 +- .../Data/ContentPackages/Vanilla 0.9.xml | 30 +- .../Characters/AI/AIController.cs | 7 +- .../SharedSource/Characters/AI/AITarget.cs | 18 +- .../Characters/AI/EnemyAIController.cs | 858 ++++++++--- .../Characters/AI/HumanAIController.cs | 28 +- .../Characters/AI/IndoorsSteeringManager.cs | 89 +- .../SharedSource/Characters/AI/LatchOntoAI.cs | 210 ++- .../Characters/AI/Objectives/AIObjective.cs | 1 + .../AI/Objectives/AIObjectiveCombat.cs | 2 +- .../Objectives/AIObjectiveFightIntruders.cs | 3 +- .../AI/Objectives/AIObjectiveFindSafety.cs | 2 + .../AI/Objectives/AIObjectiveGoTo.cs | 10 +- .../SharedSource/Characters/AI/PathFinder.cs | 49 +- .../Characters/Animation/AnimController.cs | 467 +++++- .../Animation/FishAnimController.cs | 31 +- .../Animation/HumanoidAnimController.cs | 441 +----- .../Characters/Animation/Ragdoll.cs | 29 +- .../SharedSource/Characters/Character.cs | 74 +- .../SharedSource/Characters/CharacterInfo.cs | 42 +- .../Health/Afflictions/Affliction.cs | 31 + .../Health/Afflictions/AfflictionHusk.cs | 6 + .../Health/Afflictions/AfflictionPrefab.cs | 24 +- .../Characters/Health/CharacterHealth.cs | 46 +- .../SharedSource/Characters/Limb.cs | 2 + .../Params/Animation/AnimationParams.cs | 6 + .../Params/Animation/HumanoidAnimations.cs | 16 - .../Characters/Params/CharacterParams.cs | 27 +- .../Params/Ragdoll/RagdollParams.cs | 8 +- .../AbilityConditionAttackResult.cs | 2 +- ...> AbilityConditionItemOutsideSubmarine.cs} | 4 +- ...ounger.cs => AbilityConditionItemWreck.cs} | 4 +- .../AbilityConditionHasPermanentStat.cs | 12 +- .../AbilityConditionHasSkill.cs | 24 + .../Talents/Abilities/AbilityObjects.cs | 18 +- .../Talents/Abilities/CharacterAbility.cs | 11 +- .../CharacterAbilityGiveAffliction.cs | 44 + .../Abilities/CharacterAbilityGiveFlag.cs | 4 +- .../Abilities/CharacterAbilityGiveMoney.cs | 8 +- .../CharacterAbilityGivePermanentStat.cs | 9 +- .../Abilities/CharacterAbilityModifyFlag.cs | 4 +- .../AbilityGroups/CharacterAbilityGroup.cs | 10 + .../Characters/Talents/TalentTree.cs | 2 +- .../SharedSource/DebugConsole.cs | 57 +- .../BarotraumaShared/SharedSource/Enums.cs | 23 +- .../Events/EventActions/SpawnAction.cs | 20 +- .../SharedSource/Events/EventManager.cs | 1 + .../Events/Missions/AlienRuinMission.cs | 176 +++ .../Events/Missions/CargoMission.cs | 47 +- .../SharedSource/Events/Missions/Mission.cs | 57 +- .../Events/Missions/MissionPrefab.cs | 13 +- .../Events/Missions/SalvageMission.cs | 10 +- .../Events/Missions/ScanMission.cs | 258 ++++ .../SharedSource/Events/MonsterEvent.cs | 5 +- .../GameSession/AutoItemPlacer.cs | 11 +- .../SharedSource/GameSettings.cs | 6 +- .../SharedSource/Items/CharacterInventory.cs | 4 +- .../Items/Components/Holdable/Holdable.cs | 72 +- .../Items/Components/Holdable/IdCard.cs | 30 +- .../Items/Components/Holdable/MeleeWeapon.cs | 92 +- .../Items/Components/Holdable/RangedWeapon.cs | 6 +- .../Items/Components/Holdable/Throwable.cs | 10 +- .../Items/Components/ItemComponent.cs | 12 +- .../Components/Machines/Deconstructor.cs | 6 + .../Items/Components/Machines/Fabricator.cs | 17 +- .../Items/Components/Projectile.cs | 30 +- .../SharedSource/Items/Components/Quality.cs | 2 +- .../Items/Components/Repairable.cs | 10 +- .../SharedSource/Items/Components/Rope.cs | 182 ++- .../SharedSource/Items/Components/Scanner.cs | 90 ++ .../Items/Components/Signal/ButtonTerminal.cs | 119 ++ .../Items/Components/Signal/Connection.cs | 2 +- .../Items/Components/TriggerComponent.cs | 185 +++ .../SharedSource/Items/Components/Wearable.cs | 6 +- .../SharedSource/Items/Item.cs | 56 +- .../SharedSource/Map/Entity.cs | 75 +- .../SharedSource/Map/Levels/Level.cs | 366 +++-- .../Levels/LevelObjects/LevelObjectManager.cs | 37 +- .../Map/Levels/LevelObjects/LevelTrigger.cs | 278 ++-- .../SharedSource/Map/Levels/Ruins/BTRoom.cs | 190 --- .../SharedSource/Map/Levels/Ruins/Corridor.cs | 210 --- .../Map/Levels/Ruins/RuinGenerationParams.cs | 433 +----- .../Map/Levels/Ruins/RuinGenerator.cs | 1319 +---------------- .../SharedSource/Map/Map/Location.cs | 16 +- .../SharedSource/Map/MapEntity.cs | 6 - .../Map/Outposts/OutpostGenerationParams.cs | 34 +- .../Map/Outposts/OutpostGenerator.cs | 70 +- .../Map/Outposts/OutpostModuleInfo.cs | 2 + .../SharedSource/Map/SubmarineInfo.cs | 5 +- .../SharedSource/Map/WayPoint.cs | 226 ++- .../SharedSource/Networking/EntitySpawner.cs | 12 +- .../SharedSource/Physics/PhysicsBody.cs | 5 +- .../Serialization/SerializableProperty.cs | 11 + .../Serialization/XMLExtensions.cs | 13 +- .../SharedSource/Sprite/Sprite.cs | 12 + .../StatusEffects/StatusEffect.cs | 61 +- Barotrauma/BarotraumaShared/changelog.txt | 36 + .../Dynamics/World.cs | 2 - 150 files changed, 5669 insertions(+), 4403 deletions(-) create mode 100644 Barotrauma/BarotraumaClient/ClientSource/Events/Missions/AlienRuinMission.cs create mode 100644 Barotrauma/BarotraumaClient/ClientSource/Events/Missions/ScanMission.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/BarotraumaServer/ServerSource/Events/Missions/AlienRuinMission.cs create mode 100644 Barotrauma/BarotraumaServer/ServerSource/Events/Missions/ScanMission.cs create mode 100644 Barotrauma/BarotraumaServer/ServerSource/Items/Components/Scanner.cs create mode 100644 Barotrauma/BarotraumaServer/ServerSource/Items/Components/Signal/ButtonTerminal.cs rename Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/{AbilityConditionScavenger.cs => AbilityConditionItemOutsideSubmarine.cs} (69%) rename Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/{AbilityConditionScrounger.cs => AbilityConditionItemWreck.cs} (82%) create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionHasSkill.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveAffliction.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Events/Missions/AlienRuinMission.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Events/Missions/ScanMission.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 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..de34b1006 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Animation/Ragdoll.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Animation/Ragdoll.cs @@ -562,8 +562,10 @@ 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); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterHUD.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterHUD.cs index 89859485a..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); @@ -490,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) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterInfo.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterInfo.cs index 2ab8174b8..2027b879d 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterInfo.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterInfo.cs @@ -17,11 +17,15 @@ 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; @@ -161,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) @@ -227,193 +231,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) @@ -462,24 +309,35 @@ 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 { - //TODO: disguise skin and hair colors sheetIndex = disguisedSheetIndex; portraitToDraw = disguisedPortrait; attachmentsToDraw = disguisedAttachmentSprites; + + hairColor = disguisedHairColor; + facialHairColor = disguisedFacialHairColor; + skinColor = disguisedSkinColor; } if (portraitToDraw != null) @@ -492,14 +350,14 @@ namespace Barotrauma SetHeadEffect(spriteBatch); portraitToDraw.SourceRect = new Rectangle(CalculateOffset(portraitToDraw, sheetIndex.Value.ToPoint()), portraitToDraw.SourceRect.Size); } - portraitToDraw.Draw(spriteBatch, screenPos + offset, SkinColor, 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) { SetAttachmentEffect(spriteBatch, attachment); - DrawAttachmentSprite(spriteBatch, attachment, portraitToDraw, sheetIndex, screenPos + offset, scale, depthStep, GetAttachmentColor(attachment), flip ? SpriteEffects.FlipHorizontally : SpriteEffects.None); + DrawAttachmentSprite(spriteBatch, attachment, portraitToDraw, sheetIndex, screenPos + offset, scale, depthStep, GetAttachmentColor(attachment, hairColor, facialHairColor), flip ? SpriteEffects.FlipHorizontally : SpriteEffects.None); depthStep += depthStep; } } @@ -516,7 +374,7 @@ namespace Barotrauma { headEffectParameters.Effect ??= GameMain.GameScreen.ThresholdTintEffect; headEffectParameters.Params ??= new Dictionary(); - headEffectParameters.Params["xBaseTexture"] = headSprite.Texture; + headEffectParameters.Params["xBaseTexture"] = HeadSprite.Texture; headEffectParameters.Params["xTintMaskTexture"] = tintMask?.Texture ?? GUI.WhiteTexture; headEffectParameters.Params["xCutoffTexture"] = GUI.WhiteTexture; headEffectParameters.Params["baseToCutoffSizeRatio"] = 1.0f; @@ -541,15 +399,15 @@ namespace Barotrauma spriteBatch.SwapEffect(attachmentEffectParameters[attachment.Type]); } - private Color GetAttachmentColor(WearableSprite attachment) + private Color GetAttachmentColor(WearableSprite attachment, Color hairColor, Color facialHairColor) { switch (attachment.Type) { case WearableType.Hair: - return HairColor; + return hairColor; case WearableType.Beard: case WearableType.Moustache: - return FacialHairColor; + return facialHairColor; default: return Color.White; } @@ -574,7 +432,7 @@ namespace Barotrauma foreach (var attachment in AttachmentSprites) { SetAttachmentEffect(spriteBatch, attachment); - DrawAttachmentSprite(spriteBatch, attachment, headSprite, Head.SheetIndex, screenPos, scale, depthStep, GetAttachmentColor(attachment)); + DrawAttachmentSprite(spriteBatch, attachment, headSprite, Head.SheetIndex, screenPos, scale, depthStep, GetAttachmentColor(attachment, HairColor, FacialHairColor)); depthStep += depthStep; } } @@ -718,6 +576,7 @@ namespace Barotrauma private readonly GUIComponent parentComponent; private readonly List characterSprites = new List(); + public GUIButton RandomizeButton; public AppearanceCustomizationMenu(CharacterInfo info, GUIComponent parent, bool hasIcon = true) { @@ -737,13 +596,12 @@ namespace Barotrauma ClearSprites(); float contentWidth = HasIcon ? 0.75f : 1.0f; - var content = - new GUIListBox( - new RectTransform(new Vector2(contentWidth, 1.0f), parentComponent.RectTransform, - Anchor.CenterLeft)) - { CanBeFocused = false, CanTakeKeyBoardFocus = false } - .Content; - + 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) { @@ -754,7 +612,7 @@ namespace Barotrauma RectTransform createItemRectTransform(string labelTag, float width = 0.6f) { - var layoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.2f), content.RectTransform)); + 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); @@ -793,6 +651,7 @@ namespace Barotrauma 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); @@ -812,6 +671,7 @@ namespace Barotrauma BarSize = 1.0f / (float)(attachmentCount + 1) }; slider.BarScrollValue = initialValue; + attachmentSliders.Add(slider); } } @@ -869,6 +729,36 @@ namespace Barotrauma 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 + bool previewingColor = false; + 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 = true; + Color color = (Color)(dropdown.ListBox.Content.FindChild(c => + c == hoveredFrame || c.IsParentOf(hoveredFrame))?.UserData ?? dropdown.SelectedData); + setter(color); + buttonFrame.Color = getter(); + buttonFrame.HoverColor = getter(); + } + else if (previewingColor) + { + setter((Color)dropdown.SelectedData); + buttonFrame.Color = getter(); + buttonFrame.HoverColor = getter(); + previewingColor = false; + } + }, onDraw: null) + { + CanBeFocused = false, + Visible = true + }; } if (countAttachmentsOfType(WearableType.Hair) > 0) @@ -886,28 +776,54 @@ namespace Barotrauma 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) => + { + var headPreset = info.Heads.Keys.GetRandom(Rand.RandSync.Unsynced); + info.Head.gender = headPreset.Gender; + info.Head.race = headPreset.Race; + info.Head.HeadSpriteId = headPreset.ID; + + info.Head.HairIndex = Rand.Int(countAttachmentsOfType(WearableType.Hair), Rand.RandSync.Unsynced); + info.Head.BeardIndex = Rand.Int(countAttachmentsOfType(WearableType.Beard), Rand.RandSync.Unsynced); + info.Head.MoustacheIndex = Rand.Int(countAttachmentsOfType(WearableType.Moustache), Rand.RandSync.Unsynced); + info.Head.FaceAttachmentIndex = Rand.Int(countAttachmentsOfType(WearableType.FaceAttachment), Rand.RandSync.Unsynced); + + info.Head.HairColor = info.HairColors.GetRandom(Rand.RandSync.Unsynced); + info.Head.FacialHairColor = info.FacialHairColors.GetRandom(Rand.RandSync.Unsynced); + info.Head.SkinColor = info.SkinColors.GetRandom(Rand.RandSync.Unsynced); + + 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; - if (HeadSelectionList != null) - { - HeadSelectionList.Visible = true; - foreach (GUIComponent child in HeadSelectionList.Content.Children) - { - child.Visible = (Gender)child.UserData == selectedGender; - child.Children.ForEach(c => - c.Visible = ((Tuple)c.UserData).Item1 == selectedGender); - } - - return true; - } var info = CharacterInfo; float characterHeightWidthRatio = info.HeadSprite.size.Y / info.HeadSprite.size.X; - HeadSelectionList = new GUIListBox( + HeadSelectionList ??= new GUIListBox( new RectTransform( new Point(parentComponent.Rect.Width, (int)(parentComponent.Rect.Width * characterHeightWidthRatio * 0.6f)), GUI.Canvas) @@ -915,6 +831,9 @@ namespace Barotrauma AbsoluteOffset = new Point(parentComponent.Rect.Right - parentComponent.Rect.Width, button.Rect.Bottom) }); + HeadSelectionList.Visible = true; + HeadSelectionList.Content.ClearChildren(); + ClearSprites(); parentComponent.RectTransform.SizeChanged += () => { @@ -952,15 +871,14 @@ namespace Barotrauma { row = null; itemsInRow = 0; - foreach (var head in heads) + foreach (var kvp in heads.Where(kv => kv.Key.Gender == selectedGender)) { - var headPreset = head.Key; - Gender gender = headPreset.Gender; + var headPreset = kvp.Key; Race race = headPreset.Race; int headIndex = headPreset.ID; string spritePath = spritePathWithTags - .Replace("[GENDER]", gender.ToString().ToLowerInvariant()) + .Replace("[GENDER]", selectedGender.ToString().ToLowerInvariant()) .Replace("[RACE]", race.ToString().ToLowerInvariant()); if (!File.Exists(spritePath)) @@ -970,18 +888,18 @@ namespace Barotrauma Sprite headSprite = new Sprite(headSpriteElement, "", spritePath); headSprite.SourceRect = - new Rectangle(CharacterInfo.CalculateOffset(headSprite, head.Value.ToPoint()), + new Rectangle(CalculateOffset(headSprite, kvp.Value.ToPoint()), headSprite.SourceRect.Size); characterSprites.Add(headSprite); - if (itemsInRow >= 4 || row == null || gender != (Gender)row.UserData) + if (itemsInRow >= 4 || row == null) { row = new GUILayoutGroup( new RectTransform(new Vector2(1.0f, 0.333f), HeadSelectionList.Content.RectTransform), true) { - UserData = gender, - Visible = gender == selectedGender + UserData = selectedGender, + Visible = true }; itemsInRow = 0; } @@ -991,10 +909,10 @@ namespace Barotrauma { OutlineColor = Color.White * 0.5f, PressedColor = Color.White * 0.5f, - UserData = new Tuple(gender, race, headIndex), + UserData = new Tuple(selectedGender, race, headIndex), OnClicked = SwitchHead, - Selected = gender == info.Gender && race == info.Race && headIndex == info.HeadSpriteId, - Visible = gender == selectedGender + Selected = selectedGender == info.Gender && race == info.Race && headIndex == info.HeadSpriteId, + Visible = true }; new GUIImage(new RectTransform(Vector2.One, btn.RectTransform), headSprite, scaleToFit: true); @@ -1013,7 +931,7 @@ namespace Barotrauma int id = ((Tuple)obj).Item3; info.Gender = gender; info.Race = race; - info.HeadSpriteId = id; + info.Head.HeadSpriteId = id; RecreateFrameContents(); OnHeadSwitch?.Invoke(this); return true; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs index 78fa27ccf..e9a30ca3d 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs @@ -66,8 +66,6 @@ namespace Barotrauma private SpriteSheet medUIExtra; private float medUIExtraAnimState; - private GUIComponent draggingMed; - private int highlightedLimbIndex = -1; private int selectedLimbIndex = -1; private LimbHealth currentDisplayedLimb; @@ -118,7 +116,6 @@ namespace Barotrauma if (prevOpenHealthWindow != null) { - prevOpenHealthWindow.selectedLimbIndex = -1; prevOpenHealthWindow.highlightedLimbIndex = -1; } @@ -207,7 +204,7 @@ namespace Barotrauma 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); + 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) { @@ -216,7 +213,7 @@ namespace Barotrauma 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); + character.Info?.DrawJobIcon(spriteBatch, component.Rect, character != Character.Controlled); }); @@ -275,6 +272,34 @@ namespace Barotrauma } }); + + cprButton = new GUIButton(new RectTransform(new Vector2(0.17f, 0.17f), characterIndicatorArea.RectTransform, Anchor.BottomLeft, 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"), + IgnoreLayoutGroups = true, + Visible = false + }; + var limbSelection = new GUICustomComponent(new RectTransform(new Vector2(0.4f, 1.0f), characterIndicatorArea.RectTransform), (spriteBatch, component) => { @@ -300,7 +325,7 @@ namespace Barotrauma deadIndicator.AutoScaleHorizontal = true; } - afflictionIconContainer = new GUIListBox(new RectTransform(new Vector2(0.25f, 0.7f), characterIndicatorArea.RectTransform), style: null); + afflictionIconContainer = new GUIListBox(new RectTransform(new Vector2(0.25f, 1.0f), characterIndicatorArea.RectTransform), style: null); new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.1f), healthWindowVerticalLayout.RectTransform), TextManager.Get("SuitableTreatments"), font: GUI.SubHeadingFont, textAlignment: Alignment.BottomCenter); @@ -324,33 +349,6 @@ namespace Barotrauma characterIndicatorArea.Recalculate(); - cprButton = new GUIButton(new RectTransform(new Vector2(afflictionIconContainer.RectTransform.RelativeSize.X, 0.3f), characterIndicatorArea.RectTransform, Anchor.BottomRight, 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"), - IgnoreLayoutGroups = true, - Visible = false - }; - healthBarHolder = new GUIFrame(new RectTransform(Point.Zero, GUI.Canvas), style: null) { HoverCursor = CursorState.Hand @@ -721,20 +719,19 @@ namespace Barotrauma foreach (GUIComponent afflictionIcon in afflictionIconContainer.Content.Children) { if (!(afflictionIcon.UserData is Affliction affliction)) { continue; } - var btn = afflictionIcon.GetChild(); - if (affliction.AppliedAsFailedTreatmentTime > Timing.TotalTime - 1.0 && btn.FlashTimer <= 0.0f) + if (affliction.AppliedAsFailedTreatmentTime > Timing.TotalTime - 1.0 && afflictionIcon.FlashTimer <= 0.0f) { - btn.Flash(GUI.Style.Red); + afflictionIcon.Flash(GUI.Style.Red); } - else if (affliction.AppliedAsSuccessfulTreatmentTime > Timing.TotalTime - 1.0 && btn.FlashTimer <= 0.0f) + else if (affliction.AppliedAsSuccessfulTreatmentTime > Timing.TotalTime - 1.0 && afflictionIcon.FlashTimer <= 0.0f) { - btn.Flash(GUI.Style.Green); + afflictionIcon.Flash(GUI.Style.Green); } } - if (GUI.MouseOn != null && GUI.MouseOn.UserData is string str && str == "selectaffliction") + if (GUI.MouseOn?.UserData is Affliction) { - Affliction affliction = GUI.MouseOn.Parent.UserData as Affliction; + Affliction affliction = GUI.MouseOn?.UserData as Affliction; if (afflictionTooltip == null || afflictionTooltip.UserData != affliction) { @@ -802,6 +799,8 @@ namespace Barotrauma currentDisplayedLimb = selectedLimb; } + UpdateAfflictionInfos(displayedAfflictions.Select(d => d.affliction)); + foreach (GUIComponent component in recommendedTreatmentContainer.Content.Children) { var treatmentButton = component.GetChild(); @@ -857,15 +856,6 @@ namespace Barotrauma selectedLimbIndex = highlightedLimbIndex; } } - - if (draggingMed != null) - { - if (!PlayerInput.PrimaryMouseButtonHeld()) - { - OnItemDropped(draggingMed.UserData as Item, ignoreMousePos: false); - draggingMed = null; - } - } } else { @@ -1157,8 +1147,6 @@ namespace Barotrauma { CreateRecommendedTreatments(); } - - UpdateAfflictionInfos(displayedAfflictions.Select(d => d.affliction)); } private void CreateAfflictionInfos(IEnumerable afflictions) @@ -1173,28 +1161,40 @@ namespace Barotrauma { displayedAfflictions.Add((affliction, affliction.Strength)); - var child = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.3f), afflictionIconContainer.Content.RectTransform, Anchor.TopCenter)) + var frame = new GUIButton(new RectTransform(new Vector2(1.0f, 0.25f), afflictionIconContainer.Content.RectTransform), style: "ListBoxElement") { - Stretch = true, - UserData = affliction - }; - - var button = new GUIButton(new RectTransform(new Vector2(1.0f, 0.9f), child.RectTransform), style: null) - { - 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 @@ -1203,32 +1203,22 @@ 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); - nameText.RectTransform.MinSize = new Point(0, (int)(nameText.TextSize.Y * 1.25f)); - - new GUIProgressBar(new RectTransform(new Vector2(1.0f, 0.15f), 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(); } @@ -1448,11 +1438,52 @@ namespace Barotrauma 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) { + float afflictionVitalityDecrease = affliction.GetVitalityDecrease(this); + Color afflictionEffectColor = Color.White; + if (afflictionVitalityDecrease > 0.0f) + { + afflictionEffectColor = GUI.Style.Red; + } + else if (afflictionVitalityDecrease < 0.0f) + { + afflictionEffectColor = GUI.Style.Green; + } + var child = afflictionIconContainer.Content.FindChild(affliction); - var afflictionStrengthBar = child.GetChildByUserData("afflictionstrength") as GUIProgressBar; + + 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) { UpdateAfflictionInfo(afflictionTooltip.Content, affliction); @@ -1460,6 +1491,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"); @@ -1722,13 +1779,6 @@ namespace Barotrauma 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) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Limb.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Limb.cs index d221c34b7..fb1038f97 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Limb.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Limb.cs @@ -679,6 +679,14 @@ namespace Barotrauma { 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); @@ -720,6 +728,7 @@ namespace Barotrauma } body.UpdateDrawPosition(); + float depthStep = 0.000001f; if (!hideLimb) { @@ -794,7 +803,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); } } } @@ -809,7 +818,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) { @@ -827,9 +836,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) diff --git a/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs b/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs index 988125af4..90c057cab 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs @@ -690,6 +690,7 @@ namespace Barotrauma AssignRelayToServer("readycheck", true); AssignRelayToServer("givetalent", true); + AssignRelayToServer("unlocktalents", true); AssignRelayToServer("giveexperience", true); AssignOnExecute("control", (string[] args) => @@ -1099,9 +1100,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) => diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/AlienRuinMission.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/AlienRuinMission.cs new file mode 100644 index 000000000..586f9c37a --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/AlienRuinMission.cs @@ -0,0 +1,31 @@ +using Barotrauma.Networking; + +namespace Barotrauma +{ + partial class AlienRuinMission : Mission + { + public override void ClientReadInitial(IReadMessage 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/Mission.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/Mission.cs index 92d36b5fe..5af7dd9cf 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,7 +97,7 @@ namespace Barotrauma }; } - public void ClientRead(IReadMessage msg) + public virtual void ClientRead(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/ScanMission.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/ScanMission.cs new file mode 100644 index 000000000..bbf3c18f6 --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/ScanMission.cs @@ -0,0 +1,66 @@ +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) + { + 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/GUIListBox.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIListBox.cs index 2d0e44691..f3957f392 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIListBox.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIListBox.cs @@ -627,7 +627,7 @@ namespace Barotrauma { if (child == Content || child == ScrollBar || child == ContentBackground) { continue; } child.AddToGUIUpdateList(ignoreChildren, order); - } + } } foreach (GUIComponent child in Content.Children) @@ -656,7 +656,7 @@ namespace Barotrauma OnAddedToGUIUpdateList?.Invoke(this); return; } - + int lastVisible = 0; for (int i = 0; i < Content.CountChildren; i++) { @@ -700,6 +700,8 @@ namespace Barotrauma } } + public void ForceUpdate() => Update((float)Timing.Step); + protected override void Update(float deltaTime) { if (!Visible) { return; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/Store.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/Store.cs index 934f6b001..114c2282e 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/Store.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/Store.cs @@ -595,10 +595,10 @@ namespace Barotrauma private string GetPlayerBalanceText() => GetCurrencyFormatted(PlayerMoney); - private GUILayoutGroup CreateDealsGroup(GUIListBox parentList) + 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); @@ -726,6 +726,8 @@ namespace Barotrauma FilterStoreItems(category, searchBox.Text); } + int prevDailySpecialCount; + private void RefreshStoreBuyList() { float prevBuyListScroll = storeBuyList.BarScroll; @@ -734,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 @@ -747,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 d62df1090..7af53c66b 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs @@ -1320,7 +1320,7 @@ namespace Barotrauma 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("close")) + 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) => { diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs b/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs index ad9693350..9134bfff2 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs @@ -922,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)) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSettings.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSettings.cs index f42c29829..d3444ed00 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSettings.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSettings.cs @@ -1837,6 +1837,7 @@ namespace Barotrauma ic.ParseMsg(); } } + CharacterHUD.ShouldRecreateHudTexts = true; } private void ApplySettings() 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/ItemContainer.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemContainer.cs index d2521a444..da5c38759 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemContainer.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemContainer.cs @@ -289,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; @@ -300,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; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs index fabc40b20..69b1337f3 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs @@ -380,9 +380,20 @@ namespace Barotrauma.Items.Components 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); } @@ -395,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); @@ -649,8 +664,13 @@ 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; } + + 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) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs index 780de4f8b..c708f6538 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs @@ -132,7 +132,7 @@ namespace Barotrauma.Items.Components private bool isConnectedToSteering; - private static string caveLabel; + private static string caveLabel, ruinLabel; private bool AllowUsingMineralScanner => HasMineralScanner && !isConnectedToSteering; @@ -880,7 +880,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) @@ -1234,7 +1234,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); @@ -1359,28 +1359,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) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Repairable.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Repairable.cs index fcdf7f34c..01db669ab 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Repairable.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Repairable.cs @@ -21,6 +21,8 @@ namespace Barotrauma.Items.Components 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 readonly List particleEmitterConditionRanges = new List(); @@ -145,10 +147,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, @@ -160,9 +168,9 @@ namespace Barotrauma.Items.Components } }; - tinkerButtonText = "Tinker"; - tinkeringText = "Tinkering"; - TinkerButton = new GUIButton(new RectTransform(new Vector2(0.8f, 0.15f), paddedFrame.RectTransform, Anchor.BottomCenter), tinkerButtonText) + 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, @@ -173,6 +181,8 @@ namespace Barotrauma.Items.Components return true; } }; + + extraButtonContainer.RectTransform.MinSize = new Point(0, SabotageButton.RectTransform.MinSize.Y); } partial void UpdateProjSpecific(float deltaTime) @@ -274,6 +284,10 @@ namespace Barotrauma.Items.Components 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; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Rope.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Rope.cs index f68e5f37d..d31ee696e 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) @@ -87,7 +89,8 @@ namespace Barotrauma.Items.Components 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 +99,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 * item.Scale; + } } - 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..cb1d306f9 --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/ButtonTerminal.cs @@ -0,0 +1,114 @@ +using Barotrauma.Extensions; +using Barotrauma.Networking; +using Microsoft.Xna.Framework; +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 = (x * paddedFrame.Rect.Width) / paddedFrame.Rect.Height; + 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 * y), containerSection.RectTransform, anchor: Anchor.Center) { RelativeOffset = new Vector2(0.0f, 0.05f + 0.5f * y) }, + 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/Item.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs index ea190c1d8..79c6f476d 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,9 +326,14 @@ 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; } @@ -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)) { @@ -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)) 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/LightSource.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightSource.cs index 2e44b1875..d86e94f81 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightSource.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightSource.cs @@ -1291,7 +1291,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; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Submarine.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Submarine.cs index 9d19ccf5a..6d4db8e52 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Submarine.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Submarine.cs @@ -178,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(); @@ -198,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); @@ -232,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); } } 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 8e046d7ab..0696a24a2 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/WayPoint.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/WayPoint.cs @@ -171,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/Screens/CampaignSetupUI/SinglePlayerCampaignSetupUI.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI/SinglePlayerCampaignSetupUI.cs index 70dd8fbd8..17616623b 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI/SinglePlayerCampaignSetupUI.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI/SinglePlayerCampaignSetupUI.cs @@ -287,7 +287,9 @@ namespace Barotrauma characterInfo.CreateIcon(new RectTransform(new Vector2(1.0f, 0.275f), subLayout.RectTransform)); - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), subLayout.RectTransform), job.Name, job.UIColor); + 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)) { @@ -327,8 +329,11 @@ namespace Barotrauma characterName.Text = characterInfo.Name; characterName.UserData = "random"; } + + StealRandomizeButton(menu, jobTextContainer); } }; + StealRandomizeButton(CharacterMenus[i], jobTextContainer); } } @@ -381,6 +386,16 @@ namespace Barotrauma 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)) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/CharacterEditor/CharacterEditorScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/CharacterEditor/CharacterEditorScreen.cs index 38e6145a0..e46694429 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/CharacterEditor/CharacterEditorScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/CharacterEditor/CharacterEditorScreen.cs @@ -483,21 +483,18 @@ 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 @@ -1072,7 +1069,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 +1082,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 +1091,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 +1102,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 +1182,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 +1221,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")); @@ -2196,7 +2195,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; @@ -2760,7 +2759,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; @@ -3863,7 +3862,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) @@ -3972,7 +3971,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) { @@ -4075,7 +4074,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) @@ -4101,13 +4100,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) 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 6c108709b..2a12028a9 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs @@ -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(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs index 62d6c6aad..093e093db 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs @@ -982,22 +982,24 @@ 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); @@ -1428,9 +1430,9 @@ namespace Barotrauma 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.015f, + RelativeSpacing = 0.0f, Stretch = true, UserData = characterInfo }; @@ -1486,21 +1488,24 @@ namespace Barotrauma } }; + //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.02f), 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 @@ -1515,7 +1520,7 @@ namespace Barotrauma 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)); + 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, @@ -3191,7 +3196,7 @@ namespace Barotrauma { GUICustomComponent characterIcon = JobPreferenceContainer.GetChild(); JobPreferenceContainer.RemoveChild(characterIcon); - GameMain.Client.CharacterInfo.CreateIcon(new RectTransform(new Vector2(1.0f, 0.4f), JobPreferenceContainer.RectTransform, Anchor.TopCenter)); + 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(); } @@ -3297,10 +3302,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, diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs index 5b4481583..aaad0e557 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); }; @@ -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/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 fbd068958..818c5040e 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundPlayer.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundPlayer.cs @@ -829,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) { @@ -861,7 +860,6 @@ namespace Barotrauma IEnumerable suitableNoiseLoops = Screen.Selected == GameMain.GameScreen ? GetSuitableMusicClips(Level.Loaded.LevelData?.Biome?.Identifier, currentIntensity) : Enumerable.Empty(); - if (suitableNoiseLoops.Count() == 0) { targetMusic[noiseLoopIndex] = null; @@ -877,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[mainTrackIndex] = suitableMusic.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 @@ -891,7 +900,6 @@ namespace Barotrauma targetMusic[i] = null; } } - foreach (BackgroundMusic intensityMusic in suitableIntensityMusic) { //already playing, do nothing diff --git a/Barotrauma/BarotraumaClient/LinuxClient.csproj b/Barotrauma/BarotraumaClient/LinuxClient.csproj index cf1f1eb9d..d37b222fb 100644 --- a/Barotrauma/BarotraumaClient/LinuxClient.csproj +++ b/Barotrauma/BarotraumaClient/LinuxClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.1500.4.0 + 0.1500.5.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/MacClient.csproj b/Barotrauma/BarotraumaClient/MacClient.csproj index af5c2accc..068f93785 100644 --- a/Barotrauma/BarotraumaClient/MacClient.csproj +++ b/Barotrauma/BarotraumaClient/MacClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.1500.4.0 + 0.1500.5.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/WindowsClient.csproj b/Barotrauma/BarotraumaClient/WindowsClient.csproj index 70656497c..bc48977e1 100644 --- a/Barotrauma/BarotraumaClient/WindowsClient.csproj +++ b/Barotrauma/BarotraumaClient/WindowsClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.1500.4.0 + 0.1500.5.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaServer/LinuxServer.csproj b/Barotrauma/BarotraumaServer/LinuxServer.csproj index d0a633e79..dd27b108a 100644 --- a/Barotrauma/BarotraumaServer/LinuxServer.csproj +++ b/Barotrauma/BarotraumaServer/LinuxServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.1500.4.0 + 0.1500.5.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/MacServer.csproj b/Barotrauma/BarotraumaServer/MacServer.csproj index 19f20cd27..6dcdf7a5f 100644 --- a/Barotrauma/BarotraumaServer/MacServer.csproj +++ b/Barotrauma/BarotraumaServer/MacServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.1500.4.0 + 0.1500.5.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterInfo.cs b/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterInfo.cs index 3729108f9..04fa5c705 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterInfo.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterInfo.cs @@ -12,6 +12,7 @@ namespace Barotrauma partial void OnSkillChanged(string skillIdentifier, float prevLevel, float newLevel, Vector2 textPopupPos) { + if (Character == null || Character.Removed) { return; } if (!prevSentSkill.ContainsKey(skillIdentifier)) { prevSentSkill[skillIdentifier] = prevLevel; diff --git a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/AlienRuinMission.cs b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/AlienRuinMission.cs new file mode 100644 index 000000000..4723cb072 --- /dev/null +++ b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/AlienRuinMission.cs @@ -0,0 +1,21 @@ +using Barotrauma.Networking; + +namespace Barotrauma +{ + partial class AlienRuinMission : Mission + { + public override void ServerWriteInitial(IWriteMessage msg, Client 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/Mission.cs b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/Mission.cs index dbbc96902..4faee3e40 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/Mission.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/Mission.cs @@ -17,5 +17,10 @@ namespace Barotrauma } public abstract void ServerWriteInitial(IWriteMessage msg, Client c); + + public virtual void ServerWrite(IWriteMessage msg) + { + msg.Write((ushort)State); + } } } \ No newline at end of file diff --git a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/ScanMission.cs b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/ScanMission.cs new file mode 100644 index 000000000..3d3e4f171 --- /dev/null +++ b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/ScanMission.cs @@ -0,0 +1,36 @@ +using Barotrauma.Networking; + +namespace Barotrauma +{ + partial class ScanMission : Mission + { + public override void ServerWriteInitial(IWriteMessage msg, Client 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/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/Networking/GameServer.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs index cedb86f86..ad6a7c6e9 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs @@ -3791,7 +3791,7 @@ namespace Barotrauma.Networking return preferredClient; } - public void UpdateMissionState(Mission mission, int state) + public void UpdateMissionState(Mission mission) { foreach (var client in connectedClients) { @@ -3799,7 +3799,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/RespawnManager.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/RespawnManager.cs index 5430e285c..8fbaabece 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.5f; + private DateTime despawnTime; private float shuttleEmptyTimer; @@ -361,9 +366,21 @@ namespace Barotrauma.Networking if (!bot && campaign != null) { var matchingData = campaign?.GetClientCharacterData(clients[i]); - if (matchingData != null && !matchingData.HasSpawned) + if (matchingData != null) { - forceSpawnInMainSub = true; + if (!matchingData.HasSpawned) + { + forceSpawnInMainSub = true; + } + else + { + foreach (Skill skill in characterInfos[i].Job.Skills) + { + var skillPrefab = characterInfos[i].Job.Prefab.Skills.Find(s => skill.Prefab == s); + if (skillPrefab == null) { continue; } + skill.Level = MathHelper.Lerp(skill.Level, skillPrefab.LevelRange.X, SkillReductionOnCampaignMidroundRespawn); + } + } } } diff --git a/Barotrauma/BarotraumaServer/WindowsServer.csproj b/Barotrauma/BarotraumaServer/WindowsServer.csproj index ab6d66a0c..dcd61be75 100644 --- a/Barotrauma/BarotraumaServer/WindowsServer.csproj +++ b/Barotrauma/BarotraumaServer/WindowsServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.1500.4.0 + 0.1500.5.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.9.xml b/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.9.xml index 3a605f435..6e068a3f0 100644 --- a/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.9.xml +++ b/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.9.xml @@ -80,6 +80,7 @@ + @@ -87,6 +88,8 @@ + + @@ -118,7 +121,6 @@ - @@ -170,6 +172,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AIController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AIController.cs index 3635b8e7e..daea35d55 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AIController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AIController.cs @@ -104,6 +104,8 @@ namespace Barotrauma (!requireNonDirty || !pathSteering.IsPathDirty); protected readonly float colliderWidth; + protected readonly float minGapSize; + protected readonly float minHullSize; protected readonly float colliderLength; protected readonly float avoidLookAheadDistance; @@ -116,6 +118,8 @@ namespace Barotrauma colliderWidth = size.X; colliderLength = size.Y; avoidLookAheadDistance = Math.Max(Math.Max(colliderWidth, colliderLength) * 3, 1.5f); + minGapSize = ConvertUnits.ToDisplayUnits(Math.Min(colliderWidth, colliderLength)); + minHullSize = ConvertUnits.ToDisplayUnits(Math.Max(colliderLength, colliderWidth) * 1.1f); } public virtual void OnAttacked(Character attacker, AttackResult attackResult) { } @@ -390,8 +394,7 @@ namespace Barotrauma else { if (gap.Open < 1) { continue; } - bool canGetThrough = ConvertUnits.ToDisplayUnits(colliderWidth) < gap.Size; - if (!canGetThrough) { continue; } + if (gap.Size < minGapSize) { continue; } } if (gap.FlowTargetHull == Character.CurrentHull) { 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 241cd47a8..a59a2e529 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs @@ -11,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 } @@ -89,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; @@ -175,7 +179,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); } } @@ -186,7 +190,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))); } } @@ -294,7 +298,7 @@ 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; @@ -405,11 +409,20 @@ namespace Barotrauma 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) @@ -446,10 +459,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) @@ -509,7 +521,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); } @@ -570,6 +584,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); @@ -632,6 +649,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; @@ -647,7 +665,7 @@ namespace Barotrauma if (c.IsDead || c.Removed) { return false; } 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) @@ -663,14 +681,33 @@ namespace Barotrauma float reactDist = selectedTargetingParams != null && selectedTargetingParams.ReactDistance > 0 ? selectedTargetingParams.ReactDistance : GetPerceivingRange(SelectedAiTarget); if (sqrDist > Math.Pow(reactDist + movementMargin, 2)) { - movementMargin = reactDist; + 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; + float force = Character.AnimController.SwimSlowParams.SteerTorque; + Character.AnimController.Collider.MoveToPos(SelectedAiTarget.Entity.SimPosition, force); + if (SelectedAiTarget.Entity is Item item) + { + Character.AnimController.Collider.SmoothRotate(MathHelper.ToRadians(item.Rotation), force); + } + 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: @@ -763,6 +800,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) { @@ -820,6 +861,138 @@ namespace Barotrauma } } + private readonly List targetHulls = new List(); + private readonly List hullWeights = new List(); + + 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 (SteeringManager is IndoorsSteeringManager pathSteering) + { + if (patrolTarget == null || + pathSteering.CurrentPath == null || + !pathSteering.IsPathDirty && (pathSteering.CurrentPath.Finished || pathSteering.CurrentPath.Unreachable)) + { + newPatrolTargetTimer = Math.Min(newPatrolTargetTimer, newPatrolTargetIntervalMin); + } + if (newPatrolTargetTimer > 0) + { + newPatrolTargetTimer -= deltaTime; + } + else + { + if (!searchingNewHull) + { + searchingNewHull = true; + FindTargetHulls(); + } + else if (targetHulls.Any()) + { + patrolTarget = ToolBox.SelectWeightedRandom(targetHulls, hullWeights, Rand.RandSync.Unsynced); + var path = PathSteering.PathFinder.FindPath(Character.SimPosition, patrolTarget.SimPosition, minGapSize: minGapSize * 1.5f, nodeFilter: n => NodeFilter(n) && PatrolNodeFilter(n)); + + if (path.Unreachable) + { + //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 + { + PathSteering.SetPath(path); + patrolTimerMargin = 0; + newPatrolTargetTimer = newPatrolTargetIntervalMax * Rand.Range(0.5f, 1.5f); + searchingNewHull = false; + } + } + else + { + // Couldn't find a valid hull + newPatrolTargetTimer = newPatrolTargetIntervalMax; + searchingNewHull = false; + } + } + if (patrolTarget != null && pathSteering.CurrentPath != null && !pathSteering.CurrentPath.Finished && !pathSteering.CurrentPath.Unreachable) + { + PathSteering.SteeringSeek(Character.GetRelativeSimPosition(patrolTarget), weight: 1, minGapWidth: minGapSize * 1.5f, nodeFilter: n => NodeFilter(n) && PatrolNodeFilter(n)); + return; + } + } + + 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 bool NodeFilter(PathNode n) => n.Waypoint.CurrentHull == null || n.Waypoint.CurrentHull.Rect.Width > minHullSize && n.Waypoint.CurrentHull.Rect.Height > minHullSize; + + 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 (AIParams.PatrolDry) + { + if (hull.WaterPercentage > 50) { continue; } + } + if (AIParams.PatrolFlooded) + { + if (hull.WaterPercentage < 80) { continue; } + } + } + if (AIParams.PatrolDry && hull.WaterPercentage < 80) + { + 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); + } + } + } + #endregion #region Attack @@ -1078,7 +1251,7 @@ namespace Barotrauma if (canAttack) { - if (AttackingLimb == null || _previousAiTarget != SelectedAiTarget) + if (AttackingLimb == null || !IsValidAttack(AttackingLimb, Character.GetAttackContexts(), SelectedAiTarget?.Entity as IDamageable)) { AttackingLimb = GetAttackLimb(attackWorldPos); } @@ -1262,6 +1435,8 @@ namespace Barotrauma State = AIState.Idle; return; } + + var pathSteering = SteeringManager as IndoorsSteeringManager; if (AttackingLimb != null && AttackingLimb.attack.Retreat) { @@ -1276,7 +1451,7 @@ namespace Barotrauma Vector2 offset = Character.SimPosition - steeringLimb.SimPosition; steerPos += offset; } - if (SteeringManager is IndoorsSteeringManager pathSteering) + if (pathSteering != null) { if (pathSteering.CurrentPath != null) { @@ -1298,243 +1473,295 @@ 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 + if (Character.CurrentHull != null && ((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), + nodeFilter: NodeFilter, + 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, NodeFilter); } } 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); + } + } + + if (!canAttack || distance > Math.Min(AttackingLimb.attack.Range * 0.9f, 100)) + { + if (pathSteering != null) + { + pathSteering.SteeringSeek(steerPos, weight: 10, minGapWidth: minGapSize, NodeFilter); + } + else + { + SteeringManager.SteeringSeek(steerPos, 10); + } + } + else if (AttackingLimb.attack.Ranged) + { + // Too close + UpdateFallBack(attackWorldPos, deltaTime, followThrough: false); } - SteeringManager.SteeringSeek(steerPos, 10); if (SelectedAiTarget?.Entity is Character c && c.Submarine == null || distance == 0 || distance > ConvertUnits.ToDisplayUnits(avoidLookAheadDistance * 2)) { SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: avoidLookAheadDistance, weight: 30); @@ -1554,12 +1781,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; @@ -1567,28 +1821,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); @@ -1632,6 +1865,10 @@ namespace Barotrauma Character.AnimController.ReleaseStuckLimbs(); LatchOntoAI?.DeattachFromBody(reset: true, cooldown: 1); if (attacker == null || attacker.AiTarget == null || attacker.Removed || attacker.IsDead) { return; } + if (Character.Params.CanInteract) + { + ReleaseDragTargets(); + } bool isFriendly = Character.IsFriendly(attacker); if (wasLatched) { @@ -1643,7 +1880,6 @@ namespace Barotrauma } return; } - if (State == AIState.Flee) { if (!isFriendly) @@ -1746,6 +1982,26 @@ 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); @@ -1767,6 +2023,24 @@ namespace Barotrauma 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 (!ActiveAttack.IsRunning) @@ -1788,8 +2062,9 @@ namespace Barotrauma 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 float greed = AIParams.AggressionGreed; if (!(damageTarget is Character)) @@ -1799,10 +2074,28 @@ namespace Barotrauma } 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; @@ -1810,6 +2103,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; @@ -1948,7 +2299,7 @@ namespace Barotrauma else { // Use path finding - SteeringManager.SteeringSeek(Character.GetRelativeSimPosition(SelectedAiTarget.Entity), 2); + PathSteering.SteeringSeek(Character.GetRelativeSimPosition(SelectedAiTarget.Entity), weight: 2, minGapWidth: minGapSize, NodeFilter); if (!PathSteering.IsPathDirty && PathSteering.CurrentPath.Unreachable) { // Can't reach @@ -1984,7 +2335,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) @@ -2307,11 +2658,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()) @@ -2347,15 +2711,25 @@ namespace Barotrauma } if (!CanPerceive(aiTarget, dist)) { 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, addIfNotFound: true); - if (Character.CurrentHull != null && Math.Abs(toTarget.Y) > Character.CurrentHull.Size.Y) + 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) @@ -2375,6 +2749,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) { @@ -2887,7 +3267,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; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs index b3f381c2d..5c2c5d3e8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs @@ -1036,16 +1036,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); @@ -1062,9 +1065,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) @@ -1255,7 +1261,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; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/IndoorsSteeringManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/IndoorsSteeringManager.cs index ef353d352..eebf7b0ec 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/IndoorsSteeringManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/IndoorsSteeringManager.cs @@ -2,7 +2,6 @@ using Microsoft.Xna.Framework; using System; using System.Linq; -using Barotrauma.Extensions; using FarseerPhysics; namespace Barotrauma @@ -15,6 +14,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 +27,7 @@ namespace Barotrauma private float buttonPressCooldown; - const float ButtonPressInterval = 0.5f; + const float ButtonPressInterval = 0.25f; public SteeringPath CurrentPath { @@ -111,9 +115,9 @@ 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 SteeringSeek(Vector2 target, float weight, float minGapWidth = 0, Func startNodeFilter = null, Func endNodeFilter = null, Func nodeFilter = null, bool checkVisiblity = true) { - steering += CalculateSteeringSeek(target, weight, startNodeFilter, endNodeFilter, nodeFilter, checkVisiblity); + steering += CalculateSteeringSeek(target, weight, minGapWidth, startNodeFilter, endNodeFilter, nodeFilter, checkVisiblity); } /// @@ -158,7 +162,7 @@ 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) { bool needsNewPath = currentPath == null || currentPath.Unreachable; if (!needsNewPath && character.Submarine != null && character.Params.PathFinderPriority > 0.5f) @@ -200,7 +204,7 @@ namespace Barotrauma } 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); + 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 (!useNewPath && currentPath != null && currentPath.CurrentNode != null && newPath.Nodes.Any() && !newPath.Unreachable) { @@ -241,6 +245,10 @@ namespace Barotrauma } if (useNewPath) { + if (currentPath != null) + { + CheckDoorsInPath(); + } currentPath = newPath; } float priority = MathHelper.Lerp(3, 1, character.Params.PathFinderPriority); @@ -306,10 +314,12 @@ 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?.Submarine != null) @@ -394,7 +404,7 @@ namespace Barotrauma } if (isAboveFloor || nextLadderSameAsCurrent) { - currentPath.SkipToNextNode(); + NextNode(!doorsChecked); } } else if (nextLadder != null) @@ -404,7 +414,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; @@ -426,7 +436,7 @@ namespace Barotrauma float distance = horizontalDistance + verticalDistance; if (ConvertUnits.ToSimUnits(distance) < targetDistance) { - currentPath.SkipToNextNode(); + NextNode(!doorsChecked); } } } @@ -451,7 +461,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) @@ -461,28 +471,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; @@ -493,17 +526,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; } @@ -520,16 +557,18 @@ namespace Barotrauma } 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; } } } @@ -573,7 +612,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..90c63c8b8 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; 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,21 @@ 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); 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 +87,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 +143,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 +201,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 +219,7 @@ namespace Barotrauma { attachSurfaceNormal = edge.GetNormal(cell); targetBody = cell.Body; - wallAttachPos = potentialAttachPos; + _attachPos = potentialAttachPos; closestDist = distSqr; } break; @@ -183,21 +233,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 +254,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 +277,50 @@ 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; } - 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 = 2; + } 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 = detachStun * 2; + } } } } + deattachCheckTimer = 5.0f; } if (deattach) { DeattachFromBody(reset: true); } - deattachTimer = 5.0f; } } @@ -315,16 +368,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 +409,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 503ef0efb..23bfc48ad 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjective.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjective.cs @@ -96,6 +96,7 @@ namespace Barotrauma #if DEBUG 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/AIObjectiveCombat.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs index 187479017..0557f1eb3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs @@ -111,7 +111,7 @@ namespace Barotrauma 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; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFightIntruders.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFightIntruders.cs index a28df6cf9..6e13c261d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFightIntruders.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFightIntruders.cs @@ -56,7 +56,8 @@ 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; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindSafety.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindSafety.cs index 94dba0146..dc172e587 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindSafety.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindSafety.cs @@ -307,6 +307,8 @@ namespace Barotrauma 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; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGoTo.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGoTo.cs index 7c4a2e520..ded911eea 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGoTo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGoTo.cs @@ -461,11 +461,11 @@ namespace Barotrauma nodeFilter = n => n.Waypoint.Tunnel != null; } - PathSteering.SteeringSeek(targetPos, 1, - startNodeFilter: n => (n.Waypoint.CurrentHull == null) == (character.CurrentHull == null), - endNodeFilter, - nodeFilter, - CheckVisibility); + 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)) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PathFinder.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PathFinder.cs index c6cf25146..f91b1d36a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PathFinder.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PathFinder.cs @@ -167,7 +167,7 @@ namespace Barotrauma 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) { @@ -233,6 +233,10 @@ namespace Barotrauma // Always check the visibility for the start node if (!IsWaypointVisible(node, start)) { continue; } if (node.IsBlocked()) { continue; } + if (node.Waypoint.ConnectedGap != null) + { + if (!CanFitThroughGap(node.Waypoint.ConnectedGap, minGapSize)) { continue; } + } startNode = node; } } @@ -282,6 +286,10 @@ namespace Barotrauma // 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 (!CanFitThroughGap(node.Waypoint.ConnectedGap, minGapSize)) { continue; } + } endNode = node; } } @@ -294,40 +302,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) { @@ -356,7 +336,10 @@ namespace Barotrauma if (isCharacter && node.Waypoint.isObstructed) { continue; } if (filter != null && !filter(node)) { continue; } if (node.IsBlocked()) { continue; } - + if (node.Waypoint.ConnectedGap != null) + { + if (!CanFitThroughGap(node.Waypoint.ConnectedGap, minGapSize)) { continue; } + } dist = node.F; currNode = node; } @@ -460,6 +443,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/Animation/AnimController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/AnimController.cs index f03fabde1..4bad55106 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; } @@ -60,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; @@ -155,13 +174,9 @@ 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, bool aimingMelee = false) { } - - public virtual void DragCharacter(Character target, float deltaTime) { } - - public virtual void UpdateUseItem(bool allowMovement, Vector2 handWorldPos) { } + public abstract void DragCharacter(Character target, float deltaTime); public virtual float GetSpeed(AnimationType type) { @@ -253,5 +268,437 @@ 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; + bool equippedInRightHand = character.Inventory?.GetItemInLimbSlot(InvSlotType.RightHand) == item && rightHand != null && !rightHand.IsSevered; + bool equippedInLefthand = character.Inventory?.GetItemInLimbSlot(InvSlotType.LeftHand) == 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) + { + 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); + } + } + } + + 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) + { + 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; + } + + arm?.body.SmoothRotate(armAngle - upperArmAngle, 100.0f * armTorque * arm.Mass, wrapAngle: false); + float forearmAngle = armAngle + lowerArmAngle; + forearm?.body.SmoothRotate(forearmAngle, 100.0f * handTorque * forearm.Mass, wrapAngle: false); + 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, + rightElbow.LimbA.type == LimbType.RightHand ? rightElbow.LocalAnchorA : rightElbow.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 8edee5073..c22e642f0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/FishAnimController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/FishAnimController.cs @@ -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,17 +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)) { @@ -290,6 +297,10 @@ namespace Barotrauma { flipTimer = 0.0f; } + wasAiming = aiming; + aiming = false; + wasAimingMelee = aimingMelee; + aimingMelee = false; } private bool CanDrag(Character target) @@ -449,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); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs index 722cc1106..0eb6e0eb3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs @@ -146,34 +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 bool wasAiming; - - private bool aimingMelee; - private bool wasAimingMelee; - - public bool IsAiming => wasAiming; - public bool IsAimingMelee => wasAimingMelee; - private readonly float movementLerp; private float cprAnimTimer; @@ -184,7 +158,6 @@ 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 float HeadLeanAmount => CurrentGroundedParams.HeadLeanAmount; public float TorsoLeanAmount => CurrentGroundedParams.TorsoLeanAmount; public Vector2 FootMoveOffset => CurrentGroundedParams.FootMoveOffset * RagdollParams.JointScale; @@ -219,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) @@ -304,21 +228,16 @@ 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 || + character.SelectedConstruction?.GetComponent() != null || (ForceSelectAnimationType != AnimationType.Crouch && ForceSelectAnimationType != AnimationType.NotDefined)) { Crouching = false; @@ -422,41 +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, CurrentHumanAnimParams.ArmIKStrength, CurrentHumanAnimParams.HandIKStrength); - HandIK(leftHand, midPos, CurrentHumanAnimParams.ArmIKStrength, CurrentHumanAnimParams.HandIKStrength); + 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; } @@ -520,12 +423,11 @@ namespace Barotrauma { limb.Disabled = false; } - wasAiming = aiming; aiming = false; wasAimingMelee = aimingMelee; aimingMelee = false; - if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer) return; + IsHanging = false; } void UpdateStanding() @@ -844,16 +746,18 @@ 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); } //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); } } } @@ -1099,6 +1003,7 @@ namespace Barotrauma rightHandPos.X = (Dir == 1.0f) ? Math.Max(0.3f, rightHandPos.X) : Math.Min(-0.3f, rightHandPos.X); rightHandPos = Vector2.Transform(rightHandPos, rotationMatrix); float speedMultiplier = character.SpeedMultiplier * (1 - Character.GetRightHandPenalty()); + // Limb hand, Vector2 pos, float force = 1.0f HandIK(rightHand, handPos + rightHandPos, CurrentSwimParams.ArmMoveStrength * speedMultiplier, CurrentSwimParams.HandMoveStrength * speedMultiplier); } @@ -1390,6 +1295,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) @@ -1406,13 +1313,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 + } } } } @@ -1447,6 +1360,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 @@ -1706,248 +1621,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, bool aimingMelee = false) - { - 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(); - - this.aimingMelee = aimingMelee; - - 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; - holdAngle += GetAimWobble(rightHand, leftHand, item); - - 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], CurrentHumanAnimParams.ArmIKStrength, CurrentHumanAnimParams.HandIKStrength); - } - } - } - - 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; - } - - private void HandIK(Limb hand, Vector2 pos, float armTorque = 1.0f, float handTorque = 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, 100.0f * armTorque * arm.Mass, wrapAngle: false); - float forearmAngle = armAngle + lowerArmAngle; - forearm?.body.SmoothRotate(forearmAngle, 100.0f * handTorque * forearm.Mass, wrapAngle: false); - float handAngle = forearm != null ? armAngle : forearmAngle; - hand?.body.SmoothRotate(handAngle, 100.0f * handTorque * hand.Mass, wrapAngle: false); - } - private void FootIK(Limb foot, Vector2 pos, float legTorque, float footTorque, float footAngle) { if (!MathUtils.IsValid(pos)) @@ -2014,47 +1687,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(); @@ -2073,7 +1705,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) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs index a42e5d81e..03f6be102 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; @@ -887,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); } @@ -974,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); } } @@ -1099,6 +1099,7 @@ namespace Barotrauma } public bool forceStanding; + public bool forceNotStanding; public void Update(float deltaTime, Camera cam) { @@ -1270,6 +1271,7 @@ namespace Barotrauma } } UpdateProjSpecific(deltaTime, cam); + forceNotStanding = false; } private void CheckBodyInRest(float deltaTime) @@ -1569,7 +1571,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) @@ -1606,6 +1608,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) { @@ -1629,6 +1638,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; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs index 2d748476e..05ac0b9d6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs @@ -129,6 +129,8 @@ namespace Barotrauma } } + public readonly HashSet Latchers = new HashSet(); + protected readonly Dictionary activeTeamChanges = new Dictionary(); protected ActiveTeamChange currentTeamChange; const string OriginalTeamIdentifier = "original"; @@ -264,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; @@ -473,8 +476,7 @@ namespace Barotrauma return true; } } - - public bool CanInteract => AllowInput && IsHumanoid && !LockHands; + public bool CanInteract => AllowInput && Params.CanInteract && !LockHands; // 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; @@ -592,6 +594,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); } } @@ -626,7 +629,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; } @@ -1172,6 +1175,7 @@ namespace Barotrauma { LoadHeadAttachments(); } + ApplyStatusEffects(ActionType.OnSpawn, 1.0f); } partial void InitProjSpecific(XElement mainElement); @@ -1669,7 +1673,7 @@ namespace Barotrauma } if (!aiControlled && - AnimController.onGround && + AnimController.OnGround && !AnimController.InWater && AnimController.Anim != AnimController.Animation.UsingConstruction && AnimController.Anim != AnimController.Animation.CPR && @@ -2697,6 +2701,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 @@ -2720,7 +2729,7 @@ 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() > 2.5f * 2.5f; + bool tooFastToUnragdoll = AnimController.Collider.LinearVelocity.LengthSquared() > 8.0f * 8.0f; bool wasRagdolled = false; bool selfRagdolled = false; @@ -2838,7 +2847,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); } } } @@ -3514,7 +3523,7 @@ namespace Barotrauma if (Removed) { return new AttackResult(); } - if (attacker != null && GameMain.NetworkMember != null && !GameMain.NetworkMember.ServerSettings.AllowFriendlyFire) + if (attacker != null && attacker != this && GameMain.NetworkMember != null && !GameMain.NetworkMember.ServerSettings.AllowFriendlyFire) { if (attacker.TeamID == TeamID) { return new AttackResult(); } } @@ -3676,6 +3685,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)) { @@ -3876,9 +3886,12 @@ namespace Barotrauma 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; @@ -4435,6 +4448,11 @@ namespace Barotrauma /// 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; } @@ -4453,23 +4471,37 @@ namespace Barotrauma // could be optimized by instead updating the Character.cs statvalues dictionary whenever the CharacterInfo.cs values change statValue += Info.GetSavedStatValue(statType); } - - //replace by updating the character wearable stat values when equipping or unequipping wearables - for (int i = 0; i < Inventory.Capacity; i++) + if (wearableStatValues.TryGetValue(statType, out float wearableValue)) { - if (Inventory.SlotTypes[i] != InvSlotType.Any && Inventory.SlotTypes[i] != InvSlotType.LeftHand && Inventory.SlotTypes[i] != InvSlotType.RightHand - && Inventory.GetItemAt(i)?.GetComponent() is Wearable wearable) - { - if (wearable.WearableStatValues.TryGetValue(statType, out float wearableValue)) - { - statValue += 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)) @@ -4516,7 +4548,7 @@ namespace Barotrauma public bool HasAbilityFlag(AbilityFlags abilityFlag) { - return abilityFlags.Contains(abilityFlag); + return abilityFlags.Contains(abilityFlag) || CharacterHealth.HasFlag(abilityFlag); } private readonly Dictionary abilityResistances = new Dictionary(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs index 1627fac16..d6a817716 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs @@ -218,30 +218,30 @@ namespace Barotrauma public int AdditionalTalentPoints { get; set; } - private Sprite headSprite; + 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; } } @@ -287,6 +287,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 @@ -294,19 +295,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 @@ -1085,7 +1073,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)); @@ -1094,9 +1082,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; @@ -1121,7 +1109,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)); @@ -1219,8 +1207,8 @@ namespace Barotrauma OnExperienceChanged(prevAmount, ExperiencePoints, Character.Position + Vector2.UnitY * 150.0f); } - const int BaseExperienceRequired = 150; - const int AddedExperienceRequiredPerLevel = 350; + const int BaseExperienceRequired = 50; + const int AddedExperienceRequiredPerLevel = 450; public int GetTotalTalentPoints() { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/Affliction.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/Affliction.cs index 9cf760566..347b52096 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/Affliction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/Affliction.cs @@ -191,6 +191,30 @@ namespace Barotrauma (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)); + } + public float GetScreenBlurStrength() { if (Strength < Prefab.ActivationThreshold) { return 0.0f; } @@ -277,6 +301,13 @@ namespace Barotrauma 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; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionHusk.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionHusk.cs index 7007563e0..0cf50429f 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 { @@ -216,6 +217,11 @@ namespace Barotrauma XElement parentElement = new XElement("CharacterInfo"); XElement infoElement = character.Info?.Save(parentElement); CharacterInfo huskCharacterInfo = infoElement == null ? null : new CharacterInfo(infoElement); + + 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) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionPrefab.cs index f227a11c0..f527262ad 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionPrefab.cs @@ -1,11 +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; -using Barotrauma.Abilities; namespace Barotrauma { @@ -223,7 +222,20 @@ namespace Barotrauma [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(); @@ -250,6 +262,10 @@ namespace Barotrauma AfflictionStatValues.TryAdd(statType, (minValue, maxValue)); break; + case "abilityflag": + var flagType = CharacterAbilityGroup.ParseFlagType(subElement.GetAttributeString("flagtype", ""), parentDebugName); + AfflictionAbilityFlags.Add(flagType); + break; } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs index 83ff1bad4..91fb404b5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs @@ -133,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; @@ -169,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 @@ -409,7 +423,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) @@ -419,17 +433,17 @@ 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); } } @@ -453,6 +467,15 @@ namespace Barotrauma 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, ActionType? treatmentAction = null) { @@ -671,6 +694,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) { @@ -725,6 +749,8 @@ namespace Barotrauma StunTimer = Stun > 0 ? StunTimer + deltaTime : 0; + FaceTint = DefaultFaceTint; + for (int i = 0; i < limbHealths.Count; i++) { for (int j = limbHealths[i].Afflictions.Count - 1; j >= 0; j--) @@ -749,10 +775,14 @@ namespace Barotrauma { UpdateBleedingProjSpecific(bleeding, targetLimb, deltaTime); } + Color faceTint = affliction.GetFaceTint(); + if (faceTint.A > FaceTint.A) { FaceTint = faceTint; } + Color bodyTint = affliction.GetBodyTint(); + if (bodyTint.A > BodyTint.A) { BodyTint = bodyTint; } Character.StackSpeedMultiplier(affliction.GetSpeedMultiplier()); } } - + for (int i = afflictions.Count - 1; i >= 0; i--) { var affliction = afflictions[i]; @@ -768,6 +798,10 @@ namespace Barotrauma var affliction = afflictions[i]; affliction.Update(this, null, deltaTime); affliction.DamagePerSecondTimer += deltaTime; + Color faceTint = affliction.GetFaceTint(); + if (faceTint.A > FaceTint.A) { FaceTint = faceTint; } + Color bodyTint = affliction.GetBodyTint(); + if (bodyTint.A > BodyTint.A) { BodyTint = bodyTint; } Character.StackSpeedMultiplier(affliction.GetSpeedMultiplier()); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Limb.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Limb.cs index fa7d525ae..d0df6e71f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Limb.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Limb.cs @@ -543,6 +543,8 @@ namespace Barotrauma 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) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Animation/AnimationParams.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Animation/AnimationParams.cs index eb6290596..26fce7320 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Animation/AnimationParams.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Animation/AnimationParams.cs @@ -129,6 +129,12 @@ namespace Barotrauma [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"); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Animation/HumanoidAnimations.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Animation/HumanoidAnimations.cs index 391c1fff8..4192bd331 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Animation/HumanoidAnimations.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Animation/HumanoidAnimations.cs @@ -94,12 +94,6 @@ namespace Barotrauma [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; } - - [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; } } abstract class HumanGroundedParams : GroundedMovementParams, IHumanAnimation @@ -153,12 +147,6 @@ namespace Barotrauma [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; } - - [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 interface IHumanAnimation @@ -169,9 +157,5 @@ namespace Barotrauma float ArmMoveStrength { get; set; } float HandMoveStrength { get; set; } - - float ArmIKStrength { get; set; } - - float HandIKStrength { get; set; } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/CharacterParams.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/CharacterParams.cs index 59e92989f..e6d40d1bc 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; } @@ -454,6 +457,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) { } @@ -553,15 +559,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; } @@ -682,8 +697,14 @@ 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(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 a5be463ca..3b17f0739 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Ragdoll/RagdollParams.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Ragdoll/RagdollParams.cs @@ -37,7 +37,7 @@ namespace Barotrauma [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'. Used mainly for animations and widgets."), Editable(-360, 360)] + [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 @@ -572,7 +572,9 @@ 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; } @@ -592,7 +594,7 @@ 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(0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 500)] diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionAttackResult.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionAttackResult.cs index a5f321518..58616eac5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionAttackResult.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionAttackResult.cs @@ -23,7 +23,7 @@ namespace Barotrauma.Abilities if (afflictions.Any()) { - if (!afflictions.Any(a => attackResult.Afflictions.Select(c => c.Identifier).Contains(a))) { return false; } + if (attackResult.Afflictions == null || !afflictions.Any(a => attackResult.Afflictions.Select(c => c.Identifier).Contains(a))) { return false; } } return true; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionScavenger.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionItemOutsideSubmarine.cs similarity index 69% rename from Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionScavenger.cs rename to Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionItemOutsideSubmarine.cs index e1ca65e72..d23794f56 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionScavenger.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionItemOutsideSubmarine.cs @@ -2,10 +2,10 @@ namespace Barotrauma.Abilities { - class AbilityConditionScavenger : AbilityConditionData + class AbilityConditionItemOutsideSubmarine : AbilityConditionData { - public AbilityConditionScavenger(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) { } + public AbilityConditionItemOutsideSubmarine(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) { } protected override bool MatchesConditionSpecific(AbilityObject abilityObject) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionScrounger.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionItemWreck.cs similarity index 82% rename from Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionScrounger.cs rename to Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionItemWreck.cs index 9fc5b00cb..81d1b1d06 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionScrounger.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionItemWreck.cs @@ -2,10 +2,10 @@ namespace Barotrauma.Abilities { - class AbilityConditionScrounger : AbilityConditionData + class AbilityConditionItemWreck : AbilityConditionData { - public AbilityConditionScrounger(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) { } + public AbilityConditionItemWreck(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) { } protected override bool MatchesConditionSpecific(AbilityObject abilityObject) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionHasPermanentStat.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionHasPermanentStat.cs index 0fee25880..2c3b26a5c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionHasPermanentStat.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionHasPermanentStat.cs @@ -5,19 +5,25 @@ 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) { - statType = CharacterAbilityGroup.ParseStatType(conditionElement.GetAttributeString("stattype", ""), characterTalent.DebugIdentifier); + 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() { - // should consider decoupling this from stat values entirely - return character.Info.GetSavedStatValue(statType) >= min; + 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/AbilityObjects.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityObjects.cs index 6939b18af..7d47baf54 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityObjects.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityObjects.cs @@ -2,7 +2,7 @@ namespace Barotrauma.Abilities { - class AbilityObject + abstract class AbilityObject { // kept as blank for now, as we are using a composition and only using this object to enforce parameter types } @@ -160,6 +160,22 @@ namespace Barotrauma.Abilities } } + 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; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbility.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbility.cs index ceaa14ee8..ffde9bcdb 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbility.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbility.cs @@ -62,7 +62,7 @@ namespace Barotrauma.Abilities protected virtual void VerifyState(bool conditionsMatched, float timeSinceLastUpdate) { - DebugConsole.ThrowError($"Ability {this} does not have an implementation for VerifyState! This ability does not work in interval ability groups."); + DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}: Ability {this} does not have an implementation for VerifyState! This ability does not work in interval ability groups."); } public void ApplyAbilityEffect(AbilityObject abilityObject) @@ -128,14 +128,5 @@ namespace Barotrauma.Abilities DebugConsole.AddWarning("Instantiated " + characterAbility + " for talent " + characterAbilityGroup.CharacterTalent.DebugIdentifier); return characterAbility; } - 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/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 index 921807085..76b3960ea 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveFlag.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveFlag.cs @@ -4,12 +4,12 @@ namespace Barotrauma.Abilities { class CharacterAbilityGiveFlag : CharacterAbility { - private AbilityFlags abilityFlag; + 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 = ParseFlagType(abilityElement.GetAttributeString("flagtype", ""), CharacterTalent.DebugIdentifier); + abilityFlag = CharacterAbilityGroup.ParseFlagType(abilityElement.GetAttributeString("flagtype", ""), CharacterTalent.DebugIdentifier); } public override void InitializeAbility(bool addingFirstTime) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveMoney.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveMoney.cs index d02647357..9c4dd581a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveMoney.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveMoney.cs @@ -7,20 +7,20 @@ namespace Barotrauma.Abilities public override bool AppliesEffectOnIntervalUpdate => true; private readonly int amount; - private readonly StatTypes scalingStatType; + private readonly string scalingStatIdentifier; public CharacterAbilityGiveMoney(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) { amount = abilityElement.GetAttributeInt("amount", 0); - scalingStatType = CharacterAbilityGroup.ParseStatType(abilityElement.GetAttributeString("scalingstattype", "None"), CharacterTalent.DebugIdentifier); + scalingStatIdentifier = abilityElement.GetAttributeString("scalingstatidentifier", string.Empty); } private void ApplyEffectSpecific(Character targetCharacter) { float multiplier = 1f; - if (scalingStatType != StatTypes.None) + if (!string.IsNullOrEmpty(scalingStatIdentifier)) { - multiplier = 0 + Character.Info.GetSavedStatValue(scalingStatType); + multiplier = 0 + Character.Info.GetSavedStatValue(StatTypes.None, scalingStatIdentifier); } targetCharacter.GiveMoney((int)(multiplier * amount)); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGivePermanentStat.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGivePermanentStat.cs index dce3ed4f8..ffa6df774 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGivePermanentStat.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGivePermanentStat.cs @@ -11,7 +11,6 @@ namespace Barotrauma.Abilities private readonly float maxValue; private readonly bool targetAllies; private readonly bool removeOnDeath; - private readonly bool removeAfterRound; private readonly bool giveOnAddingFirstTime; private readonly bool setValue; @@ -22,12 +21,12 @@ namespace Barotrauma.Abilities public CharacterAbilityGivePermanentStat(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) { statIdentifier = abilityElement.GetAttributeString("statidentifier", "").ToLowerInvariant(); - statType = CharacterAbilityGroup.ParseStatType(abilityElement.GetAttributeString("stattype", ""), CharacterTalent.DebugIdentifier); + 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); - removeAfterRound = abilityElement.GetAttributeBool("removeafterround", false); giveOnAddingFirstTime = abilityElement.GetAttributeBool("giveonaddingfirsttime", characterAbilityGroup.AbilityEffectType == AbilityEffectType.None); setValue = abilityElement.GetAttributeBool("setvalue", false); } @@ -54,11 +53,11 @@ namespace Barotrauma.Abilities { if (targetAllies) { - Character.GetFriendlyCrew(Character).ForEach(c => c?.Info.ChangeSavedStatValue(statType, value, statIdentifier, removeOnDeath, removeAfterRound, maxValue, setValue)); + Character.GetFriendlyCrew(Character).ForEach(c => c?.Info.ChangeSavedStatValue(statType, value, statIdentifier, removeOnDeath, maxValue: maxValue, setValue: setValue)); } else { - Character?.Info.ChangeSavedStatValue(statType, value, statIdentifier, removeOnDeath, removeAfterRound, maxValue, setValue); + Character?.Info.ChangeSavedStatValue(statType, value, statIdentifier, removeOnDeath, maxValue: maxValue, setValue: setValue); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyFlag.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyFlag.cs index 4ab462ccf..d9953bf23 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyFlag.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyFlag.cs @@ -5,13 +5,13 @@ namespace Barotrauma.Abilities { class CharacterAbilityModifyFlag : CharacterAbility { - private AbilityFlags abilityFlag; + private readonly AbilityFlags abilityFlag; private bool lastState; public CharacterAbilityModifyFlag(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) { - abilityFlag = ParseFlagType(abilityElement.GetAttributeString("flagtype", ""), CharacterTalent.DebugIdentifier); + abilityFlag = CharacterAbilityGroup.ParseFlagType(abilityElement.GetAttributeString("flagtype", ""), CharacterTalent.DebugIdentifier); } protected override void VerifyState(bool conditionsMatched, float timeSinceLastUpdate) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroup.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroup.cs index 5ada7de4a..7c96b3d17 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroup.cs @@ -208,5 +208,15 @@ namespace Barotrauma.Abilities 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/TalentTree.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/TalentTree.cs index 92736e1d1..1a96f6f38 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/TalentTree.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/TalentTree.cs @@ -30,7 +30,7 @@ namespace Barotrauma { ConfigElement = element; - string jobIdentifier = element.GetAttributeString("jobidentifier", ""); + string jobIdentifier = element.GetAttributeString("jobidentifier", "").ToLowerInvariant(); if (string.IsNullOrEmpty(jobIdentifier)) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs b/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs index 39810a12f..ab72e7dc2 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs @@ -863,6 +863,60 @@ namespace Barotrauma }; }, isCheat: true)); + commands.Add(new Command("unlocktalents", "unlocktalents [all/[jobname]]: give the controlled characters all the talents of the specified class", (string[] args) => + { + if (Character.Controlled == null) { return; } + if (args.Length == 0 || args[0].Equals("all", StringComparison.OrdinalIgnoreCase)) + { + foreach (var talentTree in TalentTree.JobTalentTrees) + { + foreach (var subTree in talentTree.Value.TalentSubTrees) + { + foreach (var option in subTree.TalentOptionStages) + { + foreach (var talent in option.Talents) + { + Character.Controlled.GiveTalent(talent); + } + } + } + } + } + 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; + } + foreach (var subTree in talentTree.TalentSubTrees) + { + foreach (var option in subTree.TalentOptionStages) + { + foreach (var talent in option.Talents) + { + Character.Controlled.GiveTalent(talent); + } + } + } + } + }, + () => + { + List availableArgs = new List() { "All" }; + availableArgs.AddRange(JobPrefab.Prefabs.Select(j => j.Name)); + return new string[][] + { + availableArgs.ToArray() + }; + }, isCheat: true)); + commands.Add(new Command("giveexperience", "giveexperience [amount] [character]: Give experience to character.", (string[] args) => { if (args.Length < 1) @@ -892,7 +946,7 @@ namespace Barotrauma }, isCheat: true, getValidArgs: () => { return new[] - { + { new string[] { "100" }, Character.CharacterList.Select(c => c.Name).Distinct().ToArray(), }; @@ -2041,6 +2095,7 @@ namespace Barotrauma { spawnLocation = args[^2]; if (!int.TryParse(args[^1], NumberStyles.Any, CultureInfo.InvariantCulture, out amount)) { amount = 1; } + amount = Math.Min(amount, 100); } switch (spawnLocation) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Enums.cs b/Barotrauma/BarotraumaShared/SharedSource/Enums.cs index 90d564ef1..619a77ea3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Enums.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Enums.cs @@ -22,9 +22,10 @@ OnSevered = 17, OnProduceSpawned = 18, OnOpen = 19, OnClose = 20, - OnDeath = OnBroken, - OnSuccess, - OnAbility, + OnSpawn = 21, + OnSuccess = 22, + OnAbility = 23, + OnDeath = OnBroken } public enum AbilityEffectType @@ -62,7 +63,10 @@ OnItemDeconstructedInventory, OnStopTinkering, OnItemPicked, + OnGeneticMaterialCombinedOrRefined, + OnCrewGeneticMaterialCombinedOrRefined, AfterSubmarineAttacked, + OnApplyTreatment, } public enum StatTypes @@ -100,7 +104,8 @@ RepairToolStructureRepairMultiplier, RepairToolStructureDamageMultiplier, RepairToolDeattachTimeMultiplier, - MaxRepairConditionMultiplier, + MaxRepairConditionMultiplierMechanical, + MaxRepairConditionMultiplierElectrical, IncreaseFabricationQuality, GeneticMaterialRefineBonus, GeneticMaterialTaintedProbabilityReductionOnCombine, @@ -114,11 +119,9 @@ MissionMoneyGainMultiplier, ExperienceGainMultiplier, MissionExperienceGainMultiplier, - // these should be deprecated and moved to their own implementation, no sense making them share space with stat values - Coauthor, - WarriorPoetMissionRuns, - WarriorPoetEnemiesKilled, - QuickfixRepairCount, + ExtraSpecialSalesCount, + ApplyTreatmentsOnSelfFraction, + MaxAttachableCount, } public enum AbilityFlags @@ -134,6 +137,8 @@ GainSkillPastMaximum, RetainExperienceForNewCharacter, AllowSecondOrderedTarget, + PowerfulCPR, + AlwaysStayConscious, } } 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/EventManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs index ee63377eb..8fee80718 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs @@ -981,6 +981,7 @@ namespace Barotrauma return false; case SubmarineType.Wreck: case SubmarineType.BeaconStation: + case SubmarineType.Ruin: return true; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/AlienRuinMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/AlienRuinMission.cs new file mode 100644 index 000000000..ad82de75c --- /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 (AllTargetsEliminated()) + { + GiveReward(); + completed = true; + } + failed = !completed && state > 0; + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CargoMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CargoMission.cs index 023e1e860..d51ccd3fd 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CargoMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CargoMission.cs @@ -198,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/Mission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs index d6adbf7b5..bfa584f9c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs @@ -27,7 +27,7 @@ namespace Barotrauma state = value; TryTriggerEvents(state); #if SERVER - GameMain.Server?.UpdateMissionState(this, state); + GameMain.Server?.UpdateMissionState(this); #endif ShowMessage(State); } @@ -347,7 +347,10 @@ namespace Barotrauma if (!(GameMain.GameSession.GameMode is CampaignMode campaign)) { return; } int reward = GetReward(Submarine.MainSub); - float baseExperienceGain = reward * 0.15f; + float baseExperienceGain = reward * 0.1f; + + float difficultyMultiplier = 1 + level.Difficulty / 100f; + baseExperienceGain *= difficultyMultiplier; IEnumerable crewCharacters = GameSession.GetSessionCrewCharacters(); @@ -471,5 +474,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/SalvageMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/SalvageMission.cs index e2b586e73..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; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/ScanMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/ScanMission.cs new file mode 100644 index 000000000..410e7da37 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/ScanMission.cs @@ -0,0 +1,258 @@ +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, minTargetDistanceSquared; + + + 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); + minTargetDistanceSquared = minTargetDistance * minTargetDistance; + } + + 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 availableWaypoints = TargetRuin.Submarine.GetWaypoints(false); + availableWaypoints.RemoveAll(wp => wp.CurrentHull == null); + if (availableWaypoints.Count < targetsToScan) + { + DebugConsole.ThrowError($"Failed to initialize a Scan mission: target ruin has less waypoints than required as scan targets ({availableWaypoints.Count} < {targetsToScan})"); + return; + } + 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()) + { + DebugConsole.ThrowError($"Error initializing a Scan mission: not enough targets available to reach the required scan target count (current targets: {scanTargets.Count}, required targets: {targetsToScan})"); + break; + } + } + } + } + + 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 (AllTargetsScanned && 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..9b52a0651 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/MonsterEvent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/MonsterEvent.cs @@ -285,11 +285,10 @@ 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); + var spawnPoint = WayPoint.GetRandom(SpawnType.Enemy, sub: chosenPosition.Submarine ?? chosenPosition.Ruin?.Submarine, useSyncedRand: false); if (spawnPoint != null) { - System.Diagnostics.Debug.Assert(spawnPoint.Submarine == chosenPosition.Submarine); - System.Diagnostics.Debug.Assert(spawnPoint.ParentRuin == chosenPosition.Ruin); + System.Diagnostics.Debug.Assert(spawnPoint.Submarine == (chosenPosition.Submarine ?? chosenPosition.Ruin?.Submarine)); spawnPos = spawnPoint.WorldPosition; } else diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/AutoItemPlacer.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/AutoItemPlacer.cs index 127e56d71..dbff41f5b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/AutoItemPlacer.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/AutoItemPlacer.cs @@ -22,14 +22,17 @@ namespace Barotrauma Place(subs); subs.ForEach(s => s.Info.InitialSuppliesSpawned = true); } - + 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()); } if (Level.Loaded?.StartOutpost != null && Level.Loaded.Type == LevelData.LevelType.Outpost) diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSettings.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSettings.cs index 40479d07b..56915b462 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSettings.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSettings.cs @@ -1139,8 +1139,8 @@ namespace Barotrauma if (PlayerCharacterCustomization != null) { playerElement.SetAttributeValue("headindex", PlayerCharacterCustomization.HeadSpriteId); - playerElement.SetAttributeValue("gender", PlayerCharacterCustomization.gender); - playerElement.SetAttributeValue("race", PlayerCharacterCustomization.race); + 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); @@ -1269,7 +1269,7 @@ namespace Barotrauma playerName = playerElement.GetAttributeString("name", playerName); int head = playerElement.GetAttributeInt("headindex", -1); Enum.TryParse(playerElement.GetAttributeString("gender", "none"), true, out Gender gender); - Enum.TryParse(playerElement.GetAttributeString("race", "white"), true, out Race race); + 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); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/CharacterInventory.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/CharacterInventory.cs index a4061293b..22f679994 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/CharacterInventory.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/CharacterInventory.cs @@ -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); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Holdable.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Holdable.cs index 942985449..d14b487f5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Holdable.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Holdable.cs @@ -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,25 @@ 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; + } + int maxAttachableCount = (int)character.Info.GetSavedStatValue(StatTypes.MaxAttachableCount, item.Prefab.Identifier); + int currentlyAttachedCount = Item.ItemList.Count( + i => i.Submarine == item.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) @@ -666,6 +709,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) @@ -720,9 +777,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.Snap(); + } + } } else { + GetRope()?.Snap(); Limb equipLimb = null; if (picker.Inventory.IsInLimbSlot(item, InvSlotType.Headset) || picker.Inventory.IsInLimbSlot(item, InvSlotType.Head)) { @@ -792,7 +858,7 @@ namespace Barotrauma.Items.Components attachTargetCell = null; if (Pusher != null) { - GameMain.World.Remove(Pusher.FarseerBody); + Pusher.Remove(); Pusher = null; } body = null; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/IdCard.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/IdCard.cs index d362beb6f..8fcf6d265 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/IdCard.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/IdCard.cs @@ -31,7 +31,7 @@ namespace Barotrauma.Items.Components public void Initialize(CharacterInfo info) { - if (info == null) return; + if (info == null) { return; } if (info.Job?.Prefab != null) { @@ -42,20 +42,22 @@ namespace Barotrauma.Items.Components 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}"); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/MeleeWeapon.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/MeleeWeapon.cs index cb96a5935..cce65f786 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/MeleeWeapon.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/MeleeWeapon.cs @@ -50,6 +50,16 @@ 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; } + + /// /// Defines items that boost the weapon functionality, like battery cell for stun batons. /// @@ -67,8 +77,6 @@ namespace Barotrauma.Items.Components }; } item.IsShootable = true; - // TODO: should define this in xml if we have melee weapons that don't require aim to use - item.RequireAimToUse = true; PreferredContainedItems = element.GetAttributeStringArray("preferredcontaineditems", new string[0], convertToLowerInvariant: true); } @@ -82,6 +90,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 @@ -94,7 +105,7 @@ namespace Barotrauma.Items.Components SetUser(character); - if (hitPos < MathHelper.PiOver4) { return false; } + if (Item.RequireAimToUse && hitPos < MathHelper.PiOver4) { return false; } ActivateNearbySleepingCharacters(); reloadTimer = reload / (1 + character.GetStatValue(StatTypes.MeleeAttackSpeed)); @@ -105,20 +116,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); } } @@ -154,9 +173,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(); @@ -164,37 +190,47 @@ 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, aimingMelee: true); + 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 { + // TODO: We might want to make this configurable 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 (Swing) + { + ac.HoldItem(deltaTime, item, handlePos, SwingPos, Vector2.Zero, aim: false, hitPos, holdAngle + hitPos); + } + else + { + ac.HoldItem(deltaTime, item, handlePos, holdPos, Vector2.Zero, aim: false, holdAngle); + } if (hitPos < -MathHelper.PiOver2) { RestoreCollision(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RangedWeapon.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RangedWeapon.cs index 60c128e6a..ee11e6001 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RangedWeapon.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RangedWeapon.cs @@ -1,7 +1,6 @@ using Barotrauma.Abilities; using Barotrauma.Networking; using FarseerPhysics; -using FarseerPhysics.Collision; using FarseerPhysics.Dynamics; using Microsoft.Xna.Framework; using System; @@ -80,6 +79,9 @@ namespace Barotrauma.Items.Components } } + + public Projectile LastProjectile { get; private set; } + private float currentChargeTime; private bool tryingToCharge; @@ -196,6 +198,7 @@ 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); + 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); @@ -206,6 +209,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/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 c146ae67e..be0b65af9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemComponent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemComponent.cs @@ -767,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; } @@ -779,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 @@ -959,7 +965,7 @@ namespace Barotrauma.Items.Components } } - public void ParseMsg() + public virtual void ParseMsg() { string msg = TextManager.Get(Msg, true); if (msg != null) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Deconstructor.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Deconstructor.cs index e779c75ba..5efe930de 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Deconstructor.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Deconstructor.cs @@ -220,6 +220,12 @@ namespace Barotrauma.Items.Components 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) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs index 43cba9600..528dee25a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs @@ -344,22 +344,27 @@ namespace Barotrauma.Items.Components 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) => - { + 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) => - { + 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; }); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs index 07f99e1ea..0cfd93de8 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 @@ -231,7 +238,10 @@ namespace Barotrauma.Items.Components { Item.body.ResetDynamics(); Item.SetTransform(simPosition, rotation); - Attack.DamageMultiplier = damageMultiplier; + if (Attack != null) + { + Attack.DamageMultiplier = damageMultiplier; + } // Set user for hitscan projectiles to work properly. User = user; // Need to set null for non-characterusable items. @@ -356,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; @@ -367,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; @@ -579,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) @@ -624,7 +635,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; } @@ -695,7 +705,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) { @@ -706,6 +717,7 @@ namespace Barotrauma.Items.Components { return false; } + lastTarget = target; float projectileNewSpeed = 0.5f; float projectileDeflectedNewSpeed = 0.1f; @@ -836,14 +848,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 index 3bfab45f0..aaf95429a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Quality.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Quality.cs @@ -35,7 +35,7 @@ namespace Barotrauma.Items.Components private int qualityLevel; - [Serialize(0, false)] + [Serialize(0, true)] public int QualityLevel { get { return qualityLevel; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs index 875942fca..b0885d400 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs @@ -406,7 +406,15 @@ namespace Barotrauma.Items.Components fixDuration /= 1 + CurrentFixer.GetStatValue(StatTypes.RepairSpeed) + currentRepairItem?.Prefab.AddedRepairSpeedMultiplier ?? 0f; fixDuration /= 1 + item.GetQualityModifier(Quality.StatType.RepairSpeed); - item.MaxRepairConditionMultiplier = 1 + CurrentFixer.GetStatValue(StatTypes.MaxRepairConditionMultiplier); + // kind of rough to keep this in update, but seems most robust + if (requiredSkills.Any(s => s != null && s.Identifier.Equals("mechanical", StringComparison.OrdinalIgnoreCase))) + { + item.MaxRepairConditionMultiplier = 1 + CurrentFixer.GetStatValue(StatTypes.MaxRepairConditionMultiplierMechanical); + } + if (requiredSkills.Any(s => s != null && s.Identifier.Equals("electrical", StringComparison.OrdinalIgnoreCase))) + { + item.MaxRepairConditionMultiplier = 1 + CurrentFixer.GetStatValue(StatTypes.MaxRepairConditionMultiplierElectrical); + } if (currentFixerAction == FixActions.Repair) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Rope.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Rope.cs index 1f923cc23..de4ae2389 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 { @@ -85,6 +105,7 @@ namespace Barotrauma.Items.Components partial void InitProjSpecific(XElement element); + public void Snap() => Snapped = true; public void Attach(ISpatialEntity source, Item target) { @@ -118,14 +139,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) { raycastTimer += deltaTime; @@ -135,28 +157,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 +193,7 @@ namespace Barotrauma.Items.Components return true; }) != null) { - Snapped = true; + Snap(); return; } raycastTimer = 0.0f; @@ -183,27 +201,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 +329,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/TriggerComponent.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/TriggerComponent.cs new file mode 100644 index 000000000..197bdd2c4 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/TriggerComponent.cs @@ -0,0 +1,185 @@ +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); + } + } + } + + 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/Wearable.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Wearable.cs index 62995f7c4..00a144ba0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Wearable.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Wearable.cs @@ -413,19 +413,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/Item.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs index 119d7ddd5..2d59645bb 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; } } @@ -975,6 +975,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(); @@ -1603,7 +1606,10 @@ namespace Barotrauma } } - aiTarget?.Update(deltaTime); + if (aiTarget != null) + { + aiTarget.Update(deltaTime); + } if (!isActive) { return; } @@ -2351,6 +2357,8 @@ namespace Barotrauma } #endif + float applyOnSelfFraction = user?.GetStatValue(StatTypes.ApplyTreatmentsOnSelfFraction) ?? 0.0f; + bool remove = false; foreach (ItemComponent ic in components) { @@ -2363,7 +2371,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) { @@ -2376,6 +2396,15 @@ namespace Barotrauma 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); } } @@ -2549,6 +2578,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"); @@ -2656,6 +2693,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(); 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/Levels/Level.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs index 5fd474745..350fb0795 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs @@ -325,6 +325,7 @@ namespace Barotrauma get { return LevelData.Seed; } } + public static float? ForcedDifficulty; public float Difficulty { @@ -527,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(); @@ -534,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); @@ -545,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; } @@ -558,7 +560,7 @@ namespace Barotrauma Tunnels.Add(new Tunnel(TunnelType.SidePath, sidePathNodes, pathWidth, parentTunnel: tunnelToBranchOff)); } - CalculateTunnelDistanceField(density: 1000); + CalculateTunnelDistanceField(null); GenerateSeaFloorPositions(); GenerateAbyssArea(); GenerateCaves(mainPath); @@ -690,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) { @@ -790,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 //---------------------------------------------------------------------------------- @@ -812,7 +826,9 @@ namespace Barotrauma //---------------------------------------------------------------------------------- // mirror if needed //---------------------------------------------------------------------------------- - + + int asdfasdf = Rand.Int(int.MaxValue, Rand.RandSync.Server); + if (mirror) { HashSet mirroredEdges = new HashSet(); @@ -850,6 +866,11 @@ namespace Barotrauma island.Area = new Rectangle(borders.Width - island.Area.Right, island.Area.Y, island.Area.Width, island.Area.Height); } + 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) { cave.Area = new Rectangle(borders.Width - cave.Area.Right, cave.Area.Y, cave.Area.Width, cave.Area.Height); @@ -895,7 +916,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) @@ -912,8 +933,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(); @@ -939,9 +975,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)); @@ -1003,7 +1040,6 @@ namespace Barotrauma } } - #if CLIENT List<(List cells, Cave parentCave)> cellBatches = new List<(List, Cave)> { @@ -1082,7 +1118,6 @@ namespace Barotrauma } #endif - EqualityCheckValues.Add(Rand.Int(int.MaxValue, Rand.RandSync.Server)); //---------------------------------------------------------------------------------- @@ -1100,6 +1135,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; } @@ -1342,18 +1382,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); } } @@ -1362,19 +1391,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); } } } @@ -1384,45 +1411,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; @@ -1705,7 +1745,7 @@ namespace Barotrauma GenerateCave(caveParams, parentTunnel, cavePos, caveSize); - CalculateTunnelDistanceField(density: 1000); + CalculateTunnelDistanceField(null); } } @@ -1787,84 +1827,145 @@ namespace Barotrauma } } - private void GenerateRuin(Tunnel mainPath, bool mirror) + private void GenerateRuin(Point ruinPos, bool mirror) { var ruinGenerationParams = RuinGenerationParams.GetRandom(); - 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(); } } - - 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); + 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); + 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) { continue; } + var gapWaypoint = WayPoint.WayPointList.Find(wp => wp.ConnectedGap == g); + if (gapWaypoint == null) { continue; } + var closestWp = FindClosestWayPoint(gapWaypoint.WorldPosition, wayPoints); + if (closestWp == null) { continue; } + gapWaypoint.ConnectTo(closestWp); + } + + //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) @@ -1890,8 +1991,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) @@ -1926,6 +2028,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)); @@ -2953,7 +3072,7 @@ namespace Barotrauma return closestCell; } - private void CreatePathToClosestTunnel(Point pos) + private List CreatePathToClosestTunnel(Point pos) { VoronoiCell closestPathCell = null; double closestDist = 0.0f; @@ -2973,6 +3092,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) @@ -2987,6 +3107,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 @@ -3019,12 +3140,19 @@ 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) + { + string shortSeed = ToolBox.StringToInt(LevelData.Seed + wreck?.Info.Name).ToString(); + if (shortSeed.Length > 6) { shortSeed = shortSeed.Substring(0, 6); } + return originalTag + "_" + shortSeed; } public bool IsCloseToStart(Vector2 position, float minDist) => IsCloseToStart(position.ToPoint(), minDist); @@ -3049,10 +3177,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 +3263,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; 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 55850a421..3b401aedb 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,7 +258,11 @@ 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]); @@ -276,26 +280,17 @@ namespace Barotrauma } } + 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; } @@ -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..be1f4811e 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,102 +34,14 @@ 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 - { - get; - set; - } - [Serialize("8000,8000", false), Editable] - public Point SizeMax - { - get; - set; - } - [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 + private RuinGenerationParams(XElement element, string filePath) : base(element, filePath) { - 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); + this.filePath = filePath; } public static RuinGenerationParams GetRandom() @@ -139,7 +51,7 @@ namespace Barotrauma.RuinGeneration 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)]; @@ -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 4fedac730..37c1c3ab8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Location.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Location.cs @@ -1006,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); @@ -1035,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); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs index d2e77976e..b14ee383e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs @@ -224,12 +224,6 @@ namespace Barotrauma } } - public RuinGeneration.Ruin ParentRuin - { - get; - set; - } - [Serialize(true, true)] public bool RemoveIfLinkedOutpostDoorInUse { 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..70e682b0b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostGenerator.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostGenerator.cs @@ -85,6 +85,7 @@ namespace Barotrauma var subInfo = new SubmarineInfo(outpostModuleFile.Path); if (subInfo.OutpostModuleInfo != null) { + if (subInfo.OutpostModuleInfo.ModuleFlags.Contains("ruin") != generationParams is RuinGeneration.RuinGenerationParams) { continue; } outpostModules.Add(subInfo); } } @@ -162,7 +163,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 +234,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 +352,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 +443,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 +452,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 +471,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 +487,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 +709,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 +877,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 +1230,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 +1283,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/SubmarineInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineInfo.cs index e97b6fa06..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 @@ -97,11 +97,10 @@ namespace Barotrauma public bool IsOutpost => Type == SubmarineType.Outpost || Type == SubmarineType.OutpostModule; - //TODO: replace when the ruin branch is merged - public bool IsRuin => false; 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 e6942d2bb..3325ea591 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 @@ -189,61 +188,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); + } + } } } @@ -278,7 +357,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)>(); @@ -381,7 +460,6 @@ namespace Barotrauma } } // Remove unwanted points - var removals = new List(); WayPoint previous = null; float tooClose = outSideWaypointInterval / 2; foreach (var wayPoint in outsideWaypoints) @@ -412,7 +490,6 @@ namespace Barotrauma foreach (WayPoint wp in removals) { outsideWaypoints.RemoveAll(w => w.Item1 == wp); - wp.Remove(); } for (int i = 0; i < outsideWaypoints.Count; i++) { @@ -433,41 +510,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) { @@ -605,12 +676,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); @@ -623,7 +707,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); @@ -632,11 +716,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); @@ -656,7 +749,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."); } @@ -775,11 +868,10 @@ namespace Barotrauma } } - 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) { return WayPointList.GetRandom(wp => wp.Submarine == sub && - wp.ParentRuin == ruin && wp.spawnType == spawnType && (assignedJob == null || (assignedJob != null && wp.AssignedJob == assignedJob)), useSyncedRand ? Rand.RandSync.Server : Rand.RandSync.Unsynced); 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/Physics/PhysicsBody.cs b/Barotrauma/BarotraumaShared/SharedSource/Physics/PhysicsBody.cs index d09effa0e..85868e116 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Physics/PhysicsBody.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Physics/PhysicsBody.cs @@ -756,12 +756,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); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Serialization/SerializableProperty.cs b/Barotrauma/BarotraumaShared/SharedSource/Serialization/SerializableProperty.cs index c3e93bdd9..cb7f26141 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Serialization/SerializableProperty.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Serialization/SerializableProperty.cs @@ -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 21fa03573..780aaa767 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Serialization/XMLExtensions.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Serialization/XMLExtensions.cs @@ -520,10 +520,12 @@ 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:X2}"; public static string RectToString(Rectangle rect) { @@ -718,6 +720,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 40d2a1742..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) @@ -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/StatusEffect.cs b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs index d5a517d5a..d0f70222e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs @@ -144,6 +144,7 @@ namespace Barotrauma public readonly float Spread; public readonly SpawnRotationType RotationType; public readonly float AimSpread; + public readonly bool Equip; public ItemSpawnInfo(XElement element, string parentDebugName) { @@ -181,6 +182,7 @@ namespace Barotrauma 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)) @@ -308,13 +310,15 @@ namespace Barotrauma /// private readonly HashSet<(string affliction, float strength)> requiredAfflictions; + public float AfflictionMultiplier = 1.0f; + public List Afflictions { get; private set; } - private bool modifyAfflictionsByMaxVitality; + private readonly bool modifyAfflictionsByMaxVitality; public IEnumerable SpawnCharacters { @@ -571,11 +575,14 @@ namespace Barotrauma requiredItems.Add(newRequiredItem); break; case "requiredaffliction": - requiredAfflictions ??= new HashSet<(string, float)>(); - requiredAfflictions.Add(( - subElement.GetAttributeString("identifier", string.Empty), - subElement.GetAttributeFloat("minstrength", 0.0f))); + string[] ids = subElement.GetAttributeStringArray("identifier", 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()) @@ -798,7 +805,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) { @@ -816,7 +832,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 { @@ -841,7 +857,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) { @@ -1424,7 +1449,19 @@ namespace Barotrauma } if (inventory != null && (inventory.CanBePut(chosenItemSpawnInfo.ItemPrefab) || chosenItemSpawnInfo.SpawnIfInventoryFull)) { - Entity.Spawner.AddToSpawnQueue(chosenItemSpawnInfo.ItemPrefab, inventory, spawnIfInventoryFull: 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; @@ -1616,7 +1653,7 @@ namespace Barotrauma { multiplier *= 1 + targetCharacter.GetStatValue(StatTypes.MedicalItemEffectivenessMultiplier); } - return multiplier; + return multiplier * AfflictionMultiplier; } private Affliction GetMultipliedAffliction(Affliction affliction, Entity entity, Character targetCharacter, float deltaTime, bool modifyByMaxVitality) @@ -1636,11 +1673,11 @@ namespace Barotrauma private void RegisterTreatmentResults(Entity entity, Limb limb, Affliction affliction, AttackResult result) { - if (entity is Item item && item.UseInHealthInterface) + if (entity is Item item && item.UseInHealthInterface && limb != null) { foreach (Affliction limbAffliction in limb.character.CharacterHealth.GetAllAfflictions()) { - if (result.Afflictions.Any(a => a.Prefab == limbAffliction.Prefab) && + 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) diff --git a/Barotrauma/BarotraumaShared/changelog.txt b/Barotrauma/BarotraumaShared/changelog.txt index b84699444..56e627677 100644 --- a/Barotrauma/BarotraumaShared/changelog.txt +++ b/Barotrauma/BarotraumaShared/changelog.txt @@ -1,3 +1,39 @@ +--------------------------------------------------------------------------------------------------------- +v0.1500.5.0 +--------------------------------------------------------------------------------------------------------- + +Additions and changes: +- 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. +- Improvements and fixes to the overhauled character sprites and animations. +- More talent improvements and additions. +- Balanced loot spawn rates. +- Cap the amount argument of the spawnitem command to 100 to prevent freezing/crashing when trying to spawn a ridiculous amount of the item. +- Nerfed concussions: they now require a larger amount of damage to the head to trigger and slowly heal by themselves. +- Added "unlocktalents [job]" command. +- Don't reset the selected limb when reopening the health interface. +- 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). +- The health interface displays a prediction of how much a medical item will reduce/increase the afflictions. +- Certain afflictions make the characters' face change color a bit. +- Added button to randomize character appearance in the character customization menus. +- Permanently reduce character skills 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. + +Fixes: +- Fixed crash when firing a syring gun (unstable only). +- Fixed crashing when using meds in multiplayer with friendly fire turned off (unstable only). +- Fixed inability to repair with hardened/dementonite wrenches (unstable only). +- Fixed item quality not persisting between rounds (unstable only). +- Fixed fabricator not outputting high-quality items in full condition if the item's quality modifiers increase the max condition (unstable). +- Display the max condition of the required item on the fabricator (i.e. show that depleted fuel needs to be fabricated from depleted fuel rods). +- Fixed holdable components that block players (e.g. mudraptor shells) causing a "you are removing a body that is not in the simulation" exception when ending a round (unstable only). +- Fixed handheld status monitor and electrical monitor UIs popping up when picking up the item. +- 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 ability to crouch on ladders (unstable only). +- Fixed loadsub command. + --------------------------------------------------------------------------------------------------------- v0.1500.4.0 --------------------------------------------------------------------------------------------------------- diff --git a/Libraries/Farseer Physics Engine 3.5/Dynamics/World.cs b/Libraries/Farseer Physics Engine 3.5/Dynamics/World.cs index 8b1ddfa8d..e8b8cbea2 100644 --- a/Libraries/Farseer Physics Engine 3.5/Dynamics/World.cs +++ b/Libraries/Farseer Physics Engine 3.5/Dynamics/World.cs @@ -1042,8 +1042,6 @@ namespace FarseerPhysics.Dynamics body._world = null; BodyList.Remove(body); - body.UserData = null; - if (BodyRemoved != null) BodyRemoved(this, body); From c8943ef9c446dd12d637406a8f4abf396e789193 Mon Sep 17 00:00:00 2001 From: Markus Isberg <3e849f2e5c@pm.me> Date: Sat, 9 Oct 2021 00:22:02 +0900 Subject: [PATCH 07/12] Unstable 0.1500.6.0 (1984 edition) --- .../Characters/Animation/Ragdoll.cs | 28 +- .../ClientSource/Characters/Character.cs | 14 +- .../ClientSource/Characters/CharacterInfo.cs | 26 +- .../Characters/Health/CharacterHealth.cs | 6 +- .../ClientSource/Characters/Limb.cs | 25 +- .../BarotraumaClient/ClientSource/GUI/GUI.cs | 36 ++- .../ClientSource/GUI/GUIStyle.cs | 12 +- .../ClientSource/GUI/TabMenu.cs | 20 +- .../ClientSource/GUI/UpgradeStore.cs | 33 ++- .../Components/EntitySpawnerComponent.cs | 61 ++++ .../Items/Components/ItemContainer.cs | 2 +- .../Items/Components/Machines/MiniMap.cs | 28 +- .../Items/Components/Machines/Sonar.cs | 12 +- .../ClientSource/Items/Components/Rope.cs | 4 +- .../Items/Components/Signal/ButtonTerminal.cs | 5 +- .../Items/Components/StatusHUD.cs | 40 ++- .../ClientSource/Items/Inventory.cs | 4 +- .../ClientSource/Items/Item.cs | 6 +- .../ClientSource/Map/Lights/LightManager.cs | 44 +-- .../ClientSource/Map/Lights/LightSource.cs | 113 ++++--- .../ClientSource/Map/MapEntity.cs | 68 +++-- .../ClientSource/Map/Structure.cs | 48 ++- .../Primitives/Peers/SteamP2PClientPeer.cs | 2 +- .../ClientSource/Screens/SubEditorScreen.cs | 4 +- .../BarotraumaClient/LinuxClient.csproj | 2 +- Barotrauma/BarotraumaClient/MacClient.csproj | 2 +- .../BarotraumaClient/WindowsClient.csproj | 2 +- .../BarotraumaServer/LinuxServer.csproj | 2 +- Barotrauma/BarotraumaServer/MacServer.csproj | 2 +- .../ServerSource/DebugConsole.cs | 74 ++++- .../GameModes/MultiPlayerCampaign.cs | 13 +- .../Components/Signal/ConnectionPanel.cs | 2 +- .../ServerSource/Networking/GameServer.cs | 2 +- .../ServerSource/Networking/RespawnManager.cs | 18 +- .../BarotraumaServer/WindowsServer.csproj | 2 +- .../Data/ContentPackages/Vanilla 0.9.xml | 1 + .../Characters/AI/AIController.cs | 2 - .../Characters/AI/EnemyAIController.cs | 175 ++++++++--- .../Characters/AI/HumanAIController.cs | 5 +- .../SharedSource/Characters/AI/LatchOntoAI.cs | 8 +- .../AI/Objectives/AIObjectiveCombat.cs | 1 + .../AI/Objectives/AIObjectiveManager.cs | 2 +- .../Characters/Animation/AnimController.cs | 56 ++-- .../Animation/FishAnimController.cs | 2 +- .../Animation/HumanoidAnimController.cs | 8 +- .../Characters/Animation/Ragdoll.cs | 32 +- .../SharedSource/Characters/Character.cs | 53 ++-- .../SharedSource/Characters/CharacterInfo.cs | 101 +++++-- .../Characters/Health/CharacterHealth.cs | 39 ++- .../SharedSource/Characters/Limb.cs | 20 +- .../Params/Animation/AnimationParams.cs | 4 +- .../Characters/Params/CharacterParams.cs | 9 + .../Params/Ragdoll/RagdollParams.cs | 6 + .../Talents/Abilities/AbilityObjects.cs | 13 - .../Talents/Abilities/CharacterAbility.cs | 1 - .../Abilities/CharacterAbilityApplyForce.cs | 41 ++- .../CharacterAbilityAlienHoarder.cs | 6 +- .../CharacterAbilityApprenticeship.cs | 4 +- .../SharedSource/CoroutineManager.cs | 7 +- .../SharedSource/DebugConsole.cs | 53 ++-- .../Events/EventActions/GiveSkillExpAction.cs | 2 - .../Events/EventActions/GodModeAction.cs | 50 ++++ .../Events/Missions/BeaconMission.cs | 2 +- .../SharedSource/Events/Missions/Mission.cs | 11 +- .../SharedSource/Events/MonsterEvent.cs | 18 +- .../GameSession/AutoItemPlacer.cs | 57 +++- .../GameSession/GameModes/CampaignMode.cs | 13 +- .../Components/EntitySpawnerComponent.cs | 280 ++++++++++++++++++ .../Items/Components/Holdable/Holdable.cs | 5 +- .../Items/Components/Holdable/MeleeWeapon.cs | 9 +- .../Items/Components/Holdable/RangedWeapon.cs | 4 +- .../Items/Components/Holdable/RepairTool.cs | 2 +- .../Items/Components/ItemContainer.cs | 6 +- .../Items/Components/Machines/MiniMap.cs | 40 ++- .../SharedSource/Items/Components/Quality.cs | 21 +- .../Items/Components/Repairable.cs | 8 +- .../Items/Components/Signal/LightComponent.cs | 5 +- .../Items/Components/Signal/WifiComponent.cs | 2 +- .../SharedSource/Items/Inventory.cs | 4 +- .../SharedSource/Items/Item.cs | 30 +- .../SharedSource/Items/ItemPrefab.cs | 8 +- .../SharedSource/Map/Levels/Level.cs | 8 +- .../Map/Levels/Ruins/RuinGenerationParams.cs | 4 +- .../SharedSource/Map/Map/Location.cs | 2 +- .../SharedSource/Map/Map/Map.cs | 15 +- .../Map/Outposts/OutpostGenerator.cs | 10 +- .../SharedSource/Map/Structure.cs | 146 ++++++--- .../SharedSource/Map/StructurePrefab.cs | 3 +- .../SharedSource/Map/Submarine.cs | 7 + .../SharedSource/Map/WayPoint.cs | 3 +- .../Serialization/SerializableProperty.cs | 2 +- .../Serialization/XMLExtensions.cs | 54 +++- .../StatusEffects/StatusEffect.cs | 49 ++- .../SharedSource/Upgrades/Upgrade.cs | 2 +- .../SharedSource/Upgrades/UpgradePrefab.cs | 20 +- Barotrauma/BarotraumaShared/changelog.txt | 63 +++- 96 files changed, 1817 insertions(+), 559 deletions(-) create mode 100644 Barotrauma/BarotraumaClient/ClientSource/Items/Components/EntitySpawnerComponent.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/GodModeAction.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Items/Components/EntitySpawnerComponent.cs diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Animation/Ragdoll.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Animation/Ragdoll.cs index de34b1006..e42aa0547 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()) @@ -348,16 +348,27 @@ namespace Barotrauma 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; + if (limb.ActiveSprite == null) { continue; } + limb.ActiveSprite.Depth = startDepth + depthSortedLimbs.IndexOf(limb) * 0.00001f; } + 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(); } @@ -567,6 +578,11 @@ namespace Barotrauma 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/Character.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs index 31fc357b6..c5d75f86c 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs @@ -405,7 +405,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( @@ -1052,8 +1052,18 @@ namespace Barotrauma Position + Vector2.UnitY * 150.0f, Vector2.UnitY * 10.0f, playSound: true, - subId: Submarine?.ID ?? -1);; + subId: Submarine?.ID ?? -1); } } + + partial void OnTalentGiven(string talentIdentifier) + { + GUI.AddMessage(TextManager.Get("talentname." + talentIdentifier.ToString()), + GUI.Style.Yellow, + Position + Vector2.UnitY * 150.0f, + Vector2.UnitY * 10.0f, + playSound: true, + subId: Submarine?.ID ?? -1); + } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterInfo.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterInfo.cs index 2027b879d..b85a2b0e2 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterInfo.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterInfo.cs @@ -680,7 +680,7 @@ namespace Barotrauma createAttachmentSlider(info.MoustacheIndex, WearableType.Moustache); createAttachmentSlider(info.FaceAttachmentIndex, WearableType.FaceAttachment); - void createColorSelector(string labelTag, IEnumerable options, Func getter, + void createColorSelector(string labelTag, IEnumerable<(Color Color, float Commonness)> options, Func getter, Action setter) { var selectorItemRT = createItemRectTransform(labelTag, 0.4f); @@ -714,7 +714,7 @@ namespace Barotrauma dropdown.ListBox.Content.RectTransform), style: "ListBoxElement") { - UserData = option, + UserData = option.Color, CanBeFocused = true }; var colorElement = @@ -723,9 +723,9 @@ namespace Barotrauma scaleBasis: ScaleBasis.Smallest), style: null) { - Color = option, - HoverColor = option, - OutlineColor = Color.Lerp(Color.Black, option, 0.5f), + Color = option.Color, + HoverColor = option.Color, + OutlineColor = Color.Lerp(Color.Black, option.Color, 0.5f), CanBeFocused = false }; } @@ -784,19 +784,9 @@ namespace Barotrauma { OnClicked = (button, o) => { - var headPreset = info.Heads.Keys.GetRandom(Rand.RandSync.Unsynced); - info.Head.gender = headPreset.Gender; - info.Head.race = headPreset.Race; - info.Head.HeadSpriteId = headPreset.ID; - - info.Head.HairIndex = Rand.Int(countAttachmentsOfType(WearableType.Hair), Rand.RandSync.Unsynced); - info.Head.BeardIndex = Rand.Int(countAttachmentsOfType(WearableType.Beard), Rand.RandSync.Unsynced); - info.Head.MoustacheIndex = Rand.Int(countAttachmentsOfType(WearableType.Moustache), Rand.RandSync.Unsynced); - info.Head.FaceAttachmentIndex = Rand.Int(countAttachmentsOfType(WearableType.FaceAttachment), Rand.RandSync.Unsynced); - - info.Head.HairColor = info.HairColors.GetRandom(Rand.RandSync.Unsynced); - info.Head.FacialHairColor = info.FacialHairColors.GetRandom(Rand.RandSync.Unsynced); - info.Head.SkinColor = info.SkinColors.GetRandom(Rand.RandSync.Unsynced); + info.Head = new HeadInfo(); + info.SetGenderAndRace(Rand.RandSync.Unsynced); + info.SetColors(); RecreateFrameContents(); info.RefreshHead(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs index e9a30ca3d..945f8acc3 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs @@ -998,10 +998,14 @@ namespace Barotrauma if (Character.Controlled?.SelectedCharacter == null && openHealthWindow == null) { List<(Affliction affliction, string text)> statusIcons = new List<(Affliction affliction, string text)>(); - if (Character.CurrentHull == null || Character.CurrentHull.LethalPressure > 5.0f) + if (Character.InPressure) + { statusIcons.Add((pressureAffliction, TextManager.Get("PressureHUDWarning"))); + } if (Character.CurrentHull != null && Character.OxygenAvailable < LowOxygenThreshold && oxygenLowAffliction.Strength < oxygenLowAffliction.Prefab.ShowIconThreshold) + { statusIcons.Add((oxygenLowAffliction, TextManager.Get("OxygenHUDWarning"))); + } foreach (Affliction affliction in currentDisplayedAfflictions) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Limb.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Limb.cs index fb1038f97..88b7bc21d 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. @@ -166,6 +167,8 @@ namespace Barotrauma } } + public float DefaultSpriteDepth { get; private set; } + public WearableSprite HuskSprite { get; private set; } public WearableSprite HerpesSprite { get; private set; } @@ -309,12 +312,23 @@ 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; @@ -383,6 +397,7 @@ namespace Barotrauma return deformations; } } + DefaultSpriteDepth = ActiveSprite.Depth; LightSource?.CheckConditionals(); } @@ -571,8 +586,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) { @@ -593,8 +608,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); } } @@ -618,7 +633,7 @@ namespace Barotrauma } } - if (inWater) + if (InWater) { wetTimer = 1.0f; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs index a8f8dd950..e7a0a3af5 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs @@ -2447,14 +2447,44 @@ 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, LargeFont, 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 += moveDir * 20; + overlapFound = true; + } + tries++; + if (tries > 20) { break; } + } + + messages.Add(newMessage); } public static void ClearMessages() diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIStyle.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIStyle.cs index f1fd031ff..923728eaa 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIStyle.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIStyle.cs @@ -41,13 +41,13 @@ namespace Barotrauma public SpriteSheet SavingIndicator { get; private set; } public UISprite UIGlow { get; private set; } - public UISprite TalentGlow { 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; } @@ -91,8 +91,8 @@ 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.Gray; - public Color ItemQualityColorNormal { get; private set; } = Color.White; + 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; @@ -250,9 +250,6 @@ namespace Barotrauma case "uiglow": UIGlow = new UISprite(subElement); break; - case "talentglow": - TalentGlow = new UISprite(subElement); - break; case "pingcircle": PingCircle = new UISprite(subElement); break; @@ -268,6 +265,9 @@ namespace Barotrauma case "uiglowsolidcircular": UIGlowSolidCircular = new UISprite(subElement); break; + case "uithermalglow": + UIThermalGlow = new UISprite(subElement); + break; case "endroundbuttonpulse": ButtonPulse = new UISprite(subElement); break; diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs index 7af53c66b..da89b0e4d 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs @@ -141,6 +141,10 @@ namespace Barotrauma { int talentCount = selectedTalents.Count - controlled.Info.UnlockedTalents.Count; talentResetButton.Enabled = talentApplyButton.Enabled = talentCount > 0; + if (talentApplyButton.Enabled && talentApplyButton.FlashTimer <= 0.0f) + { + talentApplyButton.Flash(GUI.Style.Orange); + } } if (selectedTab != InfoFrameTab.Crew) return; @@ -1184,7 +1188,7 @@ namespace Barotrauma 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, GUIImage glow)> talentButtons = new List<(GUIButton button, GUIComponent icon, GUIImage glow)>(); + 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(); @@ -1453,9 +1457,7 @@ namespace Barotrauma }; } - GUIImage iconGlow = new GUIImage(new RectTransform(Vector2.One, iconImage.RectTransform, anchor: Anchor.Center), sprite: GUI.Style.TalentGlow.Sprite, scaleToFit: true) { Visible = false }; - - talentButtons.Add((talentButton, iconImage, iconGlow)); + talentButtons.Add((talentButton, iconImage)); } talentCornerIcons.Add((subTree.Identifier, i, cornerIcon, talentBackground, talentBackgroundHighlight)); @@ -1567,20 +1569,20 @@ namespace Barotrauma string talentIdentifier = talentButton.button.UserData as string; bool unselectable = !TalentTree.IsViableTalentForCharacter(controlledCharacter, talentIdentifier, selectedTalents) || controlledCharacter.HasTalent(talentIdentifier); Color newTalentColor = unselectable ? unselectableColor : unselectedColor; - - talentButton.glow.Visible = false; + Color hoverColor = Color.White; if (controlledCharacter.HasTalent(talentIdentifier)) { - newTalentColor = new Color(140,225,140,255); + newTalentColor = GUI.Style.Green; } else if (selectedTalents.Contains(talentIdentifier)) { - newTalentColor = new Color(174,164,124,255); - talentButton.glow.Visible = true; + 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); 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/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/ItemContainer.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemContainer.cs index da5c38759..e087b99fa 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemContainer.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemContainer.cs @@ -342,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/Machines/MiniMap.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs index 59c300a13..ff08aa3e2 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs @@ -78,7 +78,6 @@ namespace Barotrauma.Items.Components None, HullStatus, ElectricalView, - HullCondition, ItemFinder } @@ -238,7 +237,6 @@ namespace Barotrauma.Items.Components { true when EnableHullStatus => MiniMapMode.HullStatus, true when EnableElectricalView => MiniMapMode.ElectricalView, - true when EnableHullCondition => MiniMapMode.HullCondition, true when EnableItemFinder => MiniMapMode.ItemFinder, _ => MiniMapMode.None }; @@ -263,8 +261,7 @@ namespace Barotrauma.Items.Components 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 = EnableHullCondition, ToolTip = TextManager.Get("StatusMonitorButton.ElectricalView.Tooltip") }, - new GUIButton(new RectTransform(new Vector2(0.25f, 1.0f), buttonLayout.RectTransform), string.Empty, style: "StatusMonitorButton.HullCondition") { UserData = MiniMapMode.HullCondition, Enabled = EnableHullCondition, ToolTip = TextManager.Get("StatusMonitorButton.HullCondition.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") } ); @@ -390,9 +387,9 @@ namespace Barotrauma.Items.Components c.Children.ForEach(c2 => c2.CanBeFocused = false); }); - submarineBack.RectTransform.MaxSize = + submarineBack.RectTransform.MaxSize = submarineFront.RectTransform.MaxSize = - submarineContainer.RectTransform.MaxSize = + submarineContainer.RectTransform.MaxSize = new Point(int.MaxValue, paddedContainer.Rect.Height - bottomFrame.Rect.Height - buttonLayout.Rect.Height); } @@ -493,7 +490,7 @@ namespace Barotrauma.Items.Components displayedSubs.Add(item.Submarine); displayedSubs.AddRange(item.Submarine.DockedTo); - subEntities = MapEntity.mapEntityList.Where(me => me.Submarine == item.Submarine && !me.HiddenInGame).OrderByDescending(w => w.SpriteDepth).ToList(); + 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; @@ -598,7 +595,6 @@ namespace Barotrauma.Items.Components if (currentMode == MiniMapMode.HullStatus && !EnableHullStatus || currentMode == MiniMapMode.ElectricalView && !EnableElectricalView || - currentMode == MiniMapMode.HullCondition && !EnableHullCondition || currentMode == MiniMapMode.ItemFinder && !EnableItemFinder) { SetDefaultMode(); @@ -606,8 +602,7 @@ namespace Barotrauma.Items.Components modeSwitchButtons[0].Enabled = EnableHullStatus; modeSwitchButtons[1].Enabled = EnableElectricalView; - modeSwitchButtons[2].Enabled = EnableHullCondition; - modeSwitchButtons[3].Enabled = EnableItemFinder; + modeSwitchButtons[2].Enabled = EnableItemFinder; } private void UpdateIDCards(Submarine sub) @@ -642,14 +637,14 @@ namespace Barotrauma.Items.Components return; } - if (currentMode == MiniMapMode.HullStatus || currentMode == MiniMapMode.HullCondition) + if (currentMode == MiniMapMode.HullStatus) { 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.HullCondition && item.Submarine != null) + if (item.Submarine != null) { var sprite = GUI.Style.UIGlowSolidCircular?.Sprite; float alpha = (MathF.Sin(blipState / maxBlipState * MathHelper.TwoPi) + 1.5f) * 0.5f; @@ -849,7 +844,6 @@ namespace Barotrauma.Items.Components switch (currentMode) { case MiniMapMode.HullStatus: - case MiniMapMode.HullCondition: UpdateHullStatus(); miniMapFrame.Visible = true; reportFrame.Visible = true; @@ -992,8 +986,8 @@ namespace Barotrauma.Items.Components { if (!hullStatusComponents.ContainsKey(linkedHull)) { continue; } - isHoveringOver |= - canHoverOverHull && + isHoveringOver |= + canHoverOverHull && (hullStatusComponents[linkedHull].RectComponent == GUI.MouseOn || (draggingReport && hullStatusComponents[linkedHull].RectComponent.MouseRect.Contains(PlayerInput.MousePosition))); if (isHoveringOver) { break; } } @@ -1089,7 +1083,7 @@ namespace Barotrauma.Items.Components } else { - bool hullsVisible = currentMode == MiniMapMode.HullStatus || currentMode == MiniMapMode.HullCondition; + bool hullsVisible = currentMode == MiniMapMode.HullStatus; foreach (var (entity, component) in hullStatusComponents) { @@ -1202,7 +1196,7 @@ namespace Barotrauma.Items.Components 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.HullCondition => 0.1f, MiniMapMode.ElectricalView => 0.1f, _ => 0.5f }; + 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; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs index c708f6538..44d0ad13c 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs @@ -132,7 +132,15 @@ namespace Barotrauma.Items.Components private bool isConnectedToSteering; - private static string caveLabel, ruinLabel; + 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); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Rope.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Rope.cs index d31ee696e..b2a025ca9 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Rope.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Rope.cs @@ -83,7 +83,7 @@ namespace Barotrauma.Items.Components public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1) { - if (target == null) { return; } + if (target == null || target.Removed) { return; } Vector2 startPos = GetSourcePos(); startPos.Y = -startPos.Y; @@ -103,7 +103,7 @@ namespace Barotrauma.Items.Components { Vector2 barrelPos = FarseerPhysics.ConvertUnits.ToDisplayUnits(weapon.TransformedBarrelPos); barrelPos.Y = -barrelPos.Y; - startPos += barrelPos * item.Scale; + startPos += barrelPos; } } Vector2 endPos = new Vector2(target.DrawPosition.X, target.DrawPosition.Y); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/ButtonTerminal.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/ButtonTerminal.cs index cb1d306f9..ff50d0fef 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/ButtonTerminal.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/ButtonTerminal.cs @@ -1,6 +1,7 @@ using Barotrauma.Extensions; using Barotrauma.Networking; using Microsoft.Xna.Framework; +using System; using System.Linq; using System.Xml.Linq; @@ -58,13 +59,13 @@ namespace Barotrauma.Items.Components }; float x = 1.0f / (1 + RequiredSignalCount); - float y = (x * paddedFrame.Rect.Width) / paddedFrame.Rect.Height; + 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 * y), containerSection.RectTransform, anchor: Anchor.Center) { RelativeOffset = new Vector2(0.0f, 0.05f + 0.5f * y) }, + 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++) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/StatusHUD.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/StatusHUD.cs index f9a7b5e9a..d974c2044 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/StatusHUD.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/StatusHUD.cs @@ -40,7 +40,7 @@ namespace Barotrauma.Items.Components } [Serialize(false, false)] - public bool SeeThroughWalls + public bool ThermalGoggles { get; private set; @@ -76,6 +76,8 @@ namespace Barotrauma.Items.Components private bool isEquippable; + private float thermalEffectState; + public IEnumerable VisibleCharacters { get @@ -108,7 +110,10 @@ namespace Barotrauma.Items.Components { refEntity = item; } - + + thermalEffectState += deltaTime; + thermalEffectState %= 10000.0f; + if (updateTimer > 0.0f) { updateTimer -= deltaTime; @@ -125,7 +130,7 @@ namespace Barotrauma.Items.Components if (dist < Range * Range) { Vector2 diff = c.WorldPosition - refEntity.WorldPosition; - if (SeeThroughWalls || Submarine.CheckVisibility(refEntity.SimPosition, refEntity.SimPosition + ConvertUnits.ToSimUnits(diff)) == null) + if (Submarine.CheckVisibility(refEntity.SimPosition, refEntity.SimPosition + ConvertUnits.ToSimUnits(diff)) == null) { visibleCharacters.Add(c); } @@ -180,21 +185,38 @@ namespace Barotrauma.Items.Components } } - if (SeeThroughWalls) + if (ThermalGoggles) { spriteBatch.End(); - GameMain.LightManager.SolidColorEffect.Parameters["color"].SetValue(Color.Red.ToVector4() * (0.35f + (float)Math.Sin(Timing.TotalTime * 1.6f) * 0.05f)); + 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.03f + (float)Math.Sin(Timing.TotalTime) * 0.01f); + 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); - foreach (Character c in visibleCharacters) + Entity refEntity = equipper; + if (!isEquippable || refEntity == null) { - if (c == character || !c.Enabled || c.Removed) { continue; } + 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) { - limb.Draw(spriteBatch, Screen.Selected.Cam, disableDeformations: true); + 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)); } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs index d2a1e999e..045117832 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs @@ -317,7 +317,7 @@ namespace Barotrauma string colorStr = XMLExtensions.ColorToString(!item.AllowStealing ? GUI.Style.Red : Color.White); toolTip = $"‖color:{colorStr}‖{name}‖color:end‖"; - if (item.Quality > 0) + 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)}"; @@ -1567,7 +1567,7 @@ namespace Barotrauma if (containedItem != null && itemContainer.Inventory.Capacity == 1) { int maxStackSize = Math.Min(containedItem.Prefab.MaxStackSize, itemContainer.GetMaxStackSize(0)); - if (maxStackSize > 1) + if (maxStackSize > 1 || containedItem.Prefab.HideConditionBar) { containedState = itemContainer.Inventory.slots[0].ItemCount / (float)maxStackSize; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs index 79c6f476d..8e50e4018 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs @@ -721,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) @@ -734,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) @@ -1212,8 +1214,8 @@ namespace Barotrauma } if (Character.Controlled != null && Character.Controlled.SelectedConstruction != this && GetComponent() == null) - { - if (Character.Controlled.SelectedConstruction?.GetComponent()?.TargetItem != this && + { + if (Character.Controlled.SelectedConstruction?.GetComponent()?.TargetItem != this && !Character.Controlled.HeldItems.Any(it => it.GetComponent()?.TargetItem == this)) { return; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightManager.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightManager.cs index e4c73b52c..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); @@ -247,7 +249,7 @@ namespace Barotrauma.Lights }*/ spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Opaque, transformMatrix: spriteBatchTransform); - Dictionary visibleHulls = GetVisibleHulls(cam); + Dictionary visibleHulls = GetVisibleHulls(cam); foreach (KeyValuePair hull in visibleHulls) { GUI.DrawRectangle(spriteBatch, @@ -264,12 +266,12 @@ namespace Barotrauma.Lights 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 d86e94f81..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); @@ -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/MapEntity.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/MapEntity.cs index d0547cdbe..3664afb1d 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)) { @@ -457,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 @@ -486,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; } @@ -516,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; } @@ -666,7 +667,7 @@ namespace Barotrauma { if (SelectedList.Contains(entity)) { return; } SelectedList.Add(entity); - HandleDoorGapLinks(entity, + HandleDoorGapLinks(entity, onGapFound: (door, gap) => { door.RefreshLinkedGap(); @@ -674,8 +675,8 @@ namespace Barotrauma { SelectedList.Add(gap); } - }, - onDoorFound: (door, gap) => + }, + onDoorFound: (door, gap) => { if (!SelectedList.Contains(door.Item)) { @@ -719,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; @@ -752,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; @@ -765,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; @@ -816,7 +817,7 @@ namespace Barotrauma posY = -posY; - Vector2[] corners = + Vector2[] corners = { new Vector2(posX, posY), new Vector2(posX + sizeX, posY), @@ -882,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) @@ -907,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); } @@ -947,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)); @@ -1057,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); } @@ -1097,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); @@ -1148,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/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/Screens/SubEditorScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs index aaad0e557..e72eb2623 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs @@ -1538,7 +1538,7 @@ namespace Barotrauma 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")); + 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); @@ -3272,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))) diff --git a/Barotrauma/BarotraumaClient/LinuxClient.csproj b/Barotrauma/BarotraumaClient/LinuxClient.csproj index d37b222fb..74dd10f01 100644 --- a/Barotrauma/BarotraumaClient/LinuxClient.csproj +++ b/Barotrauma/BarotraumaClient/LinuxClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.1500.5.0 + 0.1500.6.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/MacClient.csproj b/Barotrauma/BarotraumaClient/MacClient.csproj index 068f93785..aec74e517 100644 --- a/Barotrauma/BarotraumaClient/MacClient.csproj +++ b/Barotrauma/BarotraumaClient/MacClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.1500.5.0 + 0.1500.6.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/WindowsClient.csproj b/Barotrauma/BarotraumaClient/WindowsClient.csproj index bc48977e1..45c89c098 100644 --- a/Barotrauma/BarotraumaClient/WindowsClient.csproj +++ b/Barotrauma/BarotraumaClient/WindowsClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.1500.5.0 + 0.1500.6.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaServer/LinuxServer.csproj b/Barotrauma/BarotraumaServer/LinuxServer.csproj index dd27b108a..71e623332 100644 --- a/Barotrauma/BarotraumaServer/LinuxServer.csproj +++ b/Barotrauma/BarotraumaServer/LinuxServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.1500.5.0 + 0.1500.6.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/MacServer.csproj b/Barotrauma/BarotraumaServer/MacServer.csproj index 6dcdf7a5f..bc313d665 100644 --- a/Barotrauma/BarotraumaServer/MacServer.csproj +++ b/Barotrauma/BarotraumaServer/MacServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.1500.5.0 + 0.1500.6.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs b/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs index 9401b8cfb..796c72d08 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs @@ -1717,16 +1717,84 @@ 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) => + { + Character targetCharacter = (args.Length == 0) ? client.Character : FindMatchingCharacter(args, false); + 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) => diff --git a/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs b/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs index fef8b769a..921de6336 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs @@ -200,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) @@ -220,12 +220,13 @@ 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 (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)); } @@ -313,7 +314,7 @@ namespace Barotrauma if (success) { - SaveInventories(); + SavePlayers(); yield return CoroutineStatus.Running; 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/Networking/GameServer.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs index ad6a7c6e9..81e8e11ca 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs @@ -1318,7 +1318,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); } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/RespawnManager.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/RespawnManager.cs index 8fbaabece..41c9a34a9 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/RespawnManager.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/RespawnManager.cs @@ -374,12 +374,7 @@ namespace Barotrauma.Networking } else { - foreach (Skill skill in characterInfos[i].Job.Skills) - { - var skillPrefab = characterInfos[i].Job.Prefab.Skills.Find(s => skill.Prefab == s); - if (skillPrefab == null) { continue; } - skill.Level = MathHelper.Lerp(skill.Level, skillPrefab.LevelRange.X, SkillReductionOnCampaignMidroundRespawn); - } + ReduceCharacterSkills(characterInfos[i]); } } } @@ -493,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/WindowsServer.csproj b/Barotrauma/BarotraumaServer/WindowsServer.csproj index dcd61be75..4dc1f8bf0 100644 --- a/Barotrauma/BarotraumaServer/WindowsServer.csproj +++ b/Barotrauma/BarotraumaServer/WindowsServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.1500.5.0 + 0.1500.6.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.9.xml b/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.9.xml index 6e068a3f0..b2bb40191 100644 --- a/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.9.xml +++ b/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.9.xml @@ -195,6 +195,7 @@ + diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AIController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AIController.cs index daea35d55..d6e3520d9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AIController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AIController.cs @@ -105,7 +105,6 @@ namespace Barotrauma protected readonly float colliderWidth; protected readonly float minGapSize; - protected readonly float minHullSize; protected readonly float colliderLength; protected readonly float avoidLookAheadDistance; @@ -119,7 +118,6 @@ namespace Barotrauma colliderLength = size.Y; avoidLookAheadDistance = Math.Max(Math.Max(colliderWidth, colliderLength) * 3, 1.5f); minGapSize = ConvertUnits.ToDisplayUnits(Math.Min(colliderWidth, colliderLength)); - minHullSize = ConvertUnits.ToDisplayUnits(Math.Max(colliderLength, colliderWidth) * 1.1f); } public virtual void OnAttacked(Character attacker, AttackResult attackResult) { } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs index a59a2e529..c1a35d954 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs @@ -148,6 +148,8 @@ namespace Barotrauma private CirclePhase CirclePhase; private float currentAttackIntensity; + private CoroutineHandle disableTailCoroutine; + private readonly IEnumerable myBodies; public LatchOntoAI LatchOntoAI { get; private set; } @@ -556,7 +558,7 @@ namespace Barotrauma } else { - if (Character.Submarine != null) + if (Character.Submarine != null && Character.Params.UsePathFinding) { if (steeringManager != insideSteering) { @@ -678,8 +680,21 @@ 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) + { + 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; @@ -692,17 +707,43 @@ namespace Barotrauma { SteeringManager.Reset(); Character.AnimController.TargetMovement = Vector2.Zero; - float force = Character.AnimController.SwimSlowParams.SteerTorque; - Character.AnimController.Collider.MoveToPos(SelectedAiTarget.Entity.SimPosition, force); - if (SelectedAiTarget.Entity is Item item) + if (Character.AnimController.InWater) { - Character.AnimController.Collider.SmoothRotate(MathHelper.ToRadians(item.Rotation), force); + 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); } - Character.AnimController.ApplyPose( - new Vector2(0, -1), - new Vector2(0, -1), - new Vector2(0, -1), - new Vector2(0, -1), footMoveForce: 1); } else { @@ -895,7 +936,7 @@ namespace Barotrauma else if (targetHulls.Any()) { patrolTarget = ToolBox.SelectWeightedRandom(targetHulls, hullWeights, Rand.RandSync.Unsynced); - var path = PathSteering.PathFinder.FindPath(Character.SimPosition, patrolTarget.SimPosition, minGapSize: minGapSize * 1.5f, nodeFilter: n => NodeFilter(n) && PatrolNodeFilter(n)); + var path = PathSteering.PathFinder.FindPath(Character.SimPosition, patrolTarget.SimPosition, minGapSize: minGapSize * 1.5f, nodeFilter: n => PatrolNodeFilter(n)); if (path.Unreachable) { @@ -926,7 +967,7 @@ namespace Barotrauma } if (patrolTarget != null && pathSteering.CurrentPath != null && !pathSteering.CurrentPath.Finished && !pathSteering.CurrentPath.Unreachable) { - PathSteering.SteeringSeek(Character.GetRelativeSimPosition(patrolTarget), weight: 1, minGapWidth: minGapSize * 1.5f, nodeFilter: n => NodeFilter(n) && PatrolNodeFilter(n)); + PathSteering.SteeringSeek(Character.GetRelativeSimPosition(patrolTarget), weight: 1, minGapWidth: minGapSize * 1.5f, nodeFilter: n => PatrolNodeFilter(n)); return; } } @@ -938,8 +979,6 @@ namespace Barotrauma UpdateIdle(deltaTime, followLastTarget); } - private bool NodeFilter(PathNode n) => n.Waypoint.CurrentHull == null || n.Waypoint.CurrentHull.Rect.Width > minHullSize && n.Waypoint.CurrentHull.Rect.Height > minHullSize; - private void FindTargetHulls() { if (Character.Submarine == null) { return; } @@ -1479,8 +1518,11 @@ namespace Barotrauma if (!canAttack || distance > margin) { // 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)))) + // 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)); @@ -1489,8 +1531,7 @@ namespace Barotrauma { pathSteering.SteeringSeek(steerPos, weight: 2, minGapWidth: minGapSize, - startNodeFilter: n => (n.Waypoint.CurrentHull == null) == (Character.CurrentHull == null), - nodeFilter: NodeFilter, + startNodeFilter: n => (n.Waypoint.CurrentHull == null) == (Character.CurrentHull == null), checkVisiblity: true); if (!pathSteering.IsPathDirty && pathSteering.CurrentPath.Unreachable) @@ -1526,7 +1567,7 @@ namespace Barotrauma } else { - pathSteering.SteeringSeek(steerPos, weight: 5, minGapWidth: minGapSize, NodeFilter); + pathSteering.SteeringSeek(steerPos, weight: 5, minGapWidth: minGapSize); } } else @@ -1750,7 +1791,7 @@ namespace Barotrauma { if (pathSteering != null) { - pathSteering.SteeringSeek(steerPos, weight: 10, minGapWidth: minGapSize, NodeFilter); + pathSteering.SteeringSeek(steerPos, weight: 10, minGapWidth: minGapSize); } else { @@ -1762,7 +1803,7 @@ namespace Barotrauma // Too close UpdateFallBack(attackWorldPos, deltaTime, followThrough: false); } - if (SelectedAiTarget?.Entity is Character c && c.Submarine == null || distance == 0 || distance > ConvertUnits.ToDisplayUnits(avoidLookAheadDistance * 2)) + 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); } @@ -2282,12 +2323,12 @@ 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); @@ -2299,11 +2340,12 @@ namespace Barotrauma else { // Use path finding - PathSteering.SteeringSeek(Character.GetRelativeSimPosition(SelectedAiTarget.Entity), weight: 2, minGapWidth: minGapSize, NodeFilter); + PathSteering.SteeringSeek(Character.GetRelativeSimPosition(SelectedAiTarget.Entity), weight: 2, minGapWidth: minGapSize); if (!PathSteering.IsPathDirty && PathSteering.CurrentPath.Unreachable) { // Can't reach State = AIState.Idle; + IgnoreTarget(SelectedAiTarget); return; } } @@ -2419,10 +2461,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; } @@ -2433,6 +2477,7 @@ namespace Barotrauma if (character.CurrentHull != null) { continue; } // Ignore ruins if (hull.Submarine == null) { continue; } + if (hull.Submarine.Info.IsRuin) { continue; } } Door door = null; @@ -2448,14 +2493,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)) @@ -2499,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) @@ -2709,7 +2747,11 @@ namespace Barotrauma { dist *= 0.9f; } - if (!CanPerceive(aiTarget, dist)) { continue; } + + if (!CanPerceive(aiTarget, dist, checkVisibility: SelectedAiTarget != aiTarget)) + { + continue; + } if (SelectedAiTarget == aiTarget) { @@ -2720,7 +2762,6 @@ namespace Barotrauma //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, addIfNotFound: true); if (Character.Submarine != null && !Character.Submarine.Info.IsRuin && Character.CurrentHull != null) { @@ -2907,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; @@ -3129,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) @@ -3367,6 +3409,12 @@ 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(); AttackingLimb = null; movementMargin = 0; @@ -3382,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 { @@ -3394,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() diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs index 5c2c5d3e8..2378f2e6a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs @@ -34,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; @@ -1124,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); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/LatchOntoAI.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/LatchOntoAI.cs index 90c63c8b8..69270a61e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/LatchOntoAI.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/LatchOntoAI.cs @@ -26,7 +26,7 @@ namespace Barotrauma public bool AttachToWalls { get; private set; } public bool AttachToCharacters { get; private set; } - private readonly float minDeattachSpeed, maxDeattachSpeed, maxAttachDuration; + private readonly float minDeattachSpeed, maxDeattachSpeed, maxAttachDuration, coolDown; private readonly float damageOnDetach, detachStun; private readonly bool weld; private float deattachCheckTimer; @@ -61,6 +61,7 @@ namespace Barotrauma 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)); @@ -283,6 +284,7 @@ namespace Barotrauma if (maxAttachDuration > 0) { deattach = true; + attachCooldown = coolDown; } if (!deattach && targetWall != null && targetSubmarine != null) { @@ -291,7 +293,7 @@ namespace Barotrauma if (enemyAI.CanPassThroughHole(targetWall, targetSection)) { deattach = true; - attachCooldown = 2; + attachCooldown = coolDown; } if (!deattach) { @@ -310,7 +312,7 @@ namespace Barotrauma { deattach = true; character.AddDamage(character.WorldPosition, new List() { AfflictionPrefab.InternalDamage.Instantiate(damageOnDetach) }, detachStun, true); - attachCooldown = detachStun * 2; + attachCooldown = Math.Max(detachStun * 2, coolDown); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs index 0557f1eb3..1f5d54c45 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs @@ -1048,6 +1048,7 @@ namespace Barotrauma if (closeEnough) { UseWeapon(deltaTime); + character.AIController.SteeringManager.Reset(); } else if (!character.IsFacing(Enemy.WorldPosition)) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveManager.cs index 03d672ca5..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 diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/AnimController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/AnimController.cs index 4bad55106..bbaba7ba8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/AnimController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/AnimController.cs @@ -357,8 +357,11 @@ namespace Barotrauma float itemAngle; Holdable holdable = item.GetComponent(); float torsoRotation = torso.Rotation; - bool equippedInRightHand = character.Inventory?.GetItemInLimbSlot(InvSlotType.RightHand) == item && rightHand != null && !rightHand.IsSevered; - bool equippedInLefthand = character.Inventory?.GetItemInLimbSlot(InvSlotType.LeftHand) == item && leftHand != null && !leftHand.IsSevered; + + 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); @@ -366,17 +369,23 @@ namespace Barotrauma holdAngle = MathUtils.VectorToAngle(new Vector2(diff.X, diff.Y * Dir)) - torsoRotation * Dir; holdAngle += GetAimWobble(rightHand, leftHand, item); itemAngle = torsoRotation + holdAngle * Dir; + if (holdable.ControlPose) { - var head = GetLimb(LimbType.Head); - if (head != null) + //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) { - 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); + 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; } @@ -506,7 +515,11 @@ namespace Barotrauma 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); + HandIK( + i == 0 ? rightHand : leftHand, transformedHoldPos + transformedHandlePos[i], + CurrentAnimationParams.ArmIKStrength, + CurrentAnimationParams.HandIKStrength, + maxAngularVelocity: 15.0f); } } } @@ -531,7 +544,7 @@ namespace Barotrauma return (lowFreqNoise * 1.0f + highFreqNoise * 0.1f) * wobbleStrength; } - public void HandIK(Limb hand, Vector2 pos, float armTorque = 1.0f, float handTorque = 1.0f) + public void HandIK(Limb hand, Vector2 pos, float armTorque = 1.0f, float handTorque = 1.0f, float maxAngularVelocity = float.PositiveInfinity) { Vector2 shoulderPos; @@ -572,11 +585,20 @@ namespace Barotrauma armAngle -= MathHelper.TwoPi; } - arm?.body.SmoothRotate(armAngle - upperArmAngle, 100.0f * armTorque * arm.Mass, wrapAngle: false); + 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; - forearm?.body.SmoothRotate(forearmAngle, 100.0f * handTorque * forearm.Mass, wrapAngle: false); - float handAngle = forearm != null ? forearmAngle : armAngle; - hand?.body.SmoothRotate(handAngle, 10.0f * handTorque * hand.Mass, wrapAngle: false); + 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) @@ -672,7 +694,7 @@ namespace Barotrauma forearmLength += Vector2.Distance( rightHand.PullJointLocalAnchorA, - rightElbow.LimbA.type == LimbType.RightHand ? rightElbow.LocalAnchorA : rightElbow.LocalAnchorB); + rightWrist.LimbA.type == LimbType.RightHand ? rightWrist.LocalAnchorA : rightWrist.LocalAnchorB); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/FishAnimController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/FishAnimController.cs index c22e642f0..70c639da2 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/FishAnimController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/FishAnimController.cs @@ -815,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 0eb6e0eb3..6c964f956 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs @@ -170,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; @@ -987,7 +987,7 @@ namespace Barotrauma 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(); @@ -1002,7 +1002,7 @@ 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); - float speedMultiplier = character.SpeedMultiplier * (1 - Character.GetRightHandPenalty()); + float speedMultiplier = Math.Min(character.SpeedMultiplier * (1 - Character.GetRightHandPenalty()), 1.0f); // Limb hand, Vector2 pos, float force = 1.0f HandIK(rightHand, handPos + rightHandPos, CurrentSwimParams.ArmMoveStrength * speedMultiplier, CurrentSwimParams.HandMoveStrength * speedMultiplier); } @@ -1012,7 +1012,7 @@ 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); - float speedMultiplier = character.SpeedMultiplier * (1 - Character.GetLeftHandPenalty()); + float speedMultiplier = Math.Min(character.SpeedMultiplier * (1 - Character.GetLeftHandPenalty()), 1.0f); HandIK(leftHand, handPos + leftHandPos, CurrentSwimParams.ArmMoveStrength * speedMultiplier, CurrentSwimParams.HandMoveStrength * speedMultiplier); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs index 03f6be102..b04947d5a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs @@ -1212,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) { @@ -1237,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); @@ -1435,7 +1435,7 @@ namespace Barotrauma 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) { @@ -1472,7 +1472,7 @@ namespace Barotrauma Collider.ApplyForce(flowForce, maxVelocity: NetConfig.MaxPhysicsBodyVelocity); foreach (Limb limb in limbs) { - if (!limb.inWater) { continue; } + if (!limb.InWater) { continue; } limb.body.ApplyForce(flowForce, maxVelocity: NetConfig.MaxPhysicsBodyVelocity); } } @@ -1840,9 +1840,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/Character.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs index 05ac0b9d6..8d50256b6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs @@ -313,6 +313,15 @@ 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; public List CurrentOrders => Info?.CurrentOrders; @@ -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; @@ -641,22 +658,13 @@ 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 float Bloodloss { @@ -2405,7 +2413,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) && @@ -3813,6 +3821,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; @@ -3950,7 +3959,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; @@ -4369,7 +4381,6 @@ namespace Barotrauma if (!info.UnlockedTalents.Add(talentPrefab.Identifier)) { return false; } } - DebugConsole.AddWarning("added " + talentPrefab.OriginalName); CharacterTalent characterTalent = new CharacterTalent(talentPrefab, this); characterTalent.ActivateTalent(addingFirstTime); characterTalents.Add(characterTalent); @@ -4377,7 +4388,10 @@ namespace Barotrauma #if SERVER GameMain.NetworkMember.CreateEntityEvent(this, new object[] { NetEntityEvent.Type.UpdateTalents }); #endif - + if (addingFirstTime) + { + OnTalentGiven(talentPrefab.Identifier); + } return true; } @@ -4441,6 +4455,7 @@ namespace Barotrauma } 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. diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs index d6a817716..79d154601 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs @@ -451,9 +451,9 @@ namespace Barotrauma set => Head.FaceAttachmentIndex = value; } - public readonly ImmutableArray HairColors; - public readonly ImmutableArray FacialHairColors; - public readonly ImmutableArray SkinColors; + 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 { @@ -522,15 +522,12 @@ namespace Barotrauma // TODO: support for variants Head = new HeadInfo(); HasGenders = CharacterConfigElement.GetAttributeBool("genders", false); - Head.gender = GetRandomGender(randSync); HasRaces = CharacterConfigElement.GetAttributeBool("races", false); - Head.race = GetRandomRace(randSync); - CalculateHeadSpriteRange(); - HeadSpriteId = GetRandomHeadID(randSync); + SetGenderAndRace(randSync); Job = (jobPrefab == null) ? Job.Random(Rand.RandSync.Unsynced) : new Job(jobPrefab, variant); - HairColors = CharacterConfigElement.GetAttributeColorArray("haircolors", new Color[] { Color.WhiteSmoke }).ToImmutableArray(); - FacialHairColors = CharacterConfigElement.GetAttributeColorArray("facialhaircolors", new Color[] { Color.WhiteSmoke }).ToImmutableArray(); - SkinColors = CharacterConfigElement.GetAttributeColorArray("skincolors", new Color[] { new Color(255, 215, 200, 255) }).ToImmutableArray(); + 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)) @@ -580,26 +577,38 @@ namespace Barotrauma 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 = HairColors.GetRandom(); - FacialHairColor = FacialHairColors.GetRandom(); - SkinColor = SkinColors.GetRandom(); + HairColor = SelectRandomColor(HairColors); + FacialHairColor = SelectRandomColor(FacialHairColors); + SkinColor = SelectRandomColor(SkinColors); } private void CheckColors() { if (HairColor == Color.Black) { - HairColor = HairColors.GetRandom(); + HairColor = SelectRandomColor(HairColors); } if (FacialHairColor == Color.Black) { - FacialHairColor = FacialHairColors.GetRandom(); + FacialHairColor = SelectRandomColor(FacialHairColors); } if (SkinColor == Color.Black) { - SkinColor = SkinColors.GetRandom(); + SkinColor = SelectRandomColor(SkinColors); } } @@ -610,7 +619,6 @@ namespace Barotrauma idCounter++; Name = infoElement.GetAttributeString("name", ""); OriginalName = infoElement.GetAttributeString("originalname", null); - string genderStr = infoElement.GetAttributeString("gender", "male").ToLowerInvariant(); Salary = infoElement.GetAttributeInt("salary", 1000); ExperiencePoints = infoElement.GetAttributeInt("experiencepoints", 0); UnlockedTalents = new HashSet(infoElement.GetAttributeStringArray("unlockedtalents", new string[0], convertToLowerInvariant: true)); @@ -642,10 +650,10 @@ namespace Barotrauma { race = GetRandomRace(Rand.RandSync.Unsynced); } - HairColors = CharacterConfigElement.GetAttributeColorArray("haircolors", new Color[] { Color.WhiteSmoke }).ToImmutableArray(); - FacialHairColors = CharacterConfigElement.GetAttributeColorArray("facialhaircolors", new Color[] { Color.WhiteSmoke }).ToImmutableArray(); - SkinColors = CharacterConfigElement.GetAttributeColorArray("skincolors", new Color[] { new Color(255, 215, 200, 255) }).ToImmutableArray(); - + 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, @@ -655,9 +663,35 @@ namespace Barotrauma infoElement.GetAttributeInt("moustacheindex", -1), infoElement.GetAttributeInt("faceattachmentindex", -1)); - SkinColor = infoElement.GetAttributeColor("skincolor", Color.White); - HairColor = infoElement.GetAttributeColor("haircolor", Color.White); - FacialHairColor = infoElement.GetAttributeColor("facialhaircolor", Color.White); + //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)) @@ -1128,7 +1162,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, Vector2 pos, bool gainedFromApprenticeship = false) { if (Job == null || (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) || Character == null) { return; } @@ -1148,7 +1182,7 @@ namespace Barotrauma { // 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 AbilityValueStringCharacter(increaseSinceLastSkillPoint, skillIdentifier, Character); + var abilitySkillGain = new AbilitySkillGain(increaseSinceLastSkillPoint, skillIdentifier, Character, gainedFromApprenticeship); Character.CheckTalents(AbilityEffectType.OnGainSkillPoint, abilitySkillGain); foreach (Character character in Character.GetFriendlyCrew(Character)) { @@ -1747,4 +1781,19 @@ namespace Barotrauma 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/CharacterHealth.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs index 91fb404b5..6762db6c3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs @@ -749,7 +749,7 @@ namespace Barotrauma StunTimer = Stun > 0 ? StunTimer + deltaTime : 0; - FaceTint = DefaultFaceTint; + if (Character.GodMode) { return; } for (int i = 0; i < limbHealths.Count; i++) { @@ -775,10 +775,6 @@ namespace Barotrauma { UpdateBleedingProjSpecific(bleeding, targetLimb, deltaTime); } - Color faceTint = affliction.GetFaceTint(); - if (faceTint.A > FaceTint.A) { FaceTint = faceTint; } - Color bodyTint = affliction.GetBodyTint(); - if (bodyTint.A > BodyTint.A) { BodyTint = bodyTint; } Character.StackSpeedMultiplier(affliction.GetSpeedMultiplier()); } } @@ -798,10 +794,6 @@ namespace Barotrauma var affliction = afflictions[i]; affliction.Update(this, null, deltaTime); affliction.DamagePerSecondTimer += deltaTime; - Color faceTint = affliction.GetFaceTint(); - if (faceTint.A > FaceTint.A) { FaceTint = faceTint; } - Color bodyTint = affliction.GetBodyTint(); - if (bodyTint.A > BodyTint.A) { BodyTint = bodyTint; } Character.StackSpeedMultiplier(affliction.GetSpeedMultiplier()); } @@ -818,7 +810,7 @@ namespace Barotrauma } UpdateLimbAfflictionOverlays(); - + UpdateSkinTint(); CalculateVitality(); if (Vitality <= MinVitality) @@ -827,6 +819,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; } @@ -905,6 +923,7 @@ namespace Barotrauma if (Unkillable || Character.GodMode) { return; } var (type, affliction) = GetCauseOfDeath(); + UpdateSkinTint(); Character.Kill(type, affliction); #if CLIENT DisplayVitalityDelay = 0.0f; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Limb.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Limb.cs index d0df6e71f..4ece5fc65 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Limb.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Limb.cs @@ -218,7 +218,7 @@ namespace Barotrauma public Vector2 StepOffset => ConvertUnits.ToSimUnits(Params.StepOffset) * ragdoll.RagdollParams.JointScale; - public bool inWater; + public bool InWater { get; set; } private FixedMouseJoint pullJoint; @@ -535,8 +535,11 @@ 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 { @@ -804,7 +807,7 @@ namespace Barotrauma { UpdateProjSpecific(deltaTime); - if (inWater) + if (InWater) { body.ApplyWaterForces(); } @@ -847,20 +850,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; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Animation/AnimationParams.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Animation/AnimationParams.cs index 26fce7320..b469d98d5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Animation/AnimationParams.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Animation/AnimationParams.cs @@ -14,9 +14,9 @@ namespace Barotrauma NotDefined, Walk, Run, - Crouch, SwimSlow, - SwimFast + SwimFast, + Crouch } abstract class GroundedMovementParams : AnimationParams diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/CharacterParams.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/CharacterParams.cs index e6d40d1bc..39016b7d6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/CharacterParams.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/CharacterParams.cs @@ -79,12 +79,18 @@ namespace Barotrauma [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; } @@ -706,6 +712,9 @@ namespace Barotrauma [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 3b17f0739..fff53e186 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Ragdoll/RagdollParams.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Ragdoll/RagdollParams.cs @@ -597,6 +597,9 @@ namespace Barotrauma [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; } @@ -679,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; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityObjects.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityObjects.cs index 7d47baf54..49431bb1e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityObjects.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityObjects.cs @@ -87,19 +87,6 @@ namespace Barotrauma.Abilities public string String { get; set; } } - class AbilityValueStringCharacter : AbilityObject, IAbilityValue, IAbilityString, IAbilityCharacter - { - public AbilityValueStringCharacter(float value, string abilityString, Character character) - { - Value = value; - String = abilityString; - Character = character; - } - public Character Character { get; set; } - public float Value { get; set; } - public string String { get; set; } - } - class AbilityStringCharacter : AbilityObject, IAbilityCharacter, IAbilityString { public AbilityStringCharacter(string abilityString, Character character) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbility.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbility.cs index ffde9bcdb..650bf1863 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbility.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbility.cs @@ -125,7 +125,6 @@ namespace Barotrauma.Abilities return null; } - DebugConsole.AddWarning("Instantiated " + characterAbility + " for talent " + characterAbilityGroup.CharacterTalent.DebugIdentifier); return characterAbility; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyForce.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyForce.cs index 3b35a274a..7dde00097 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyForce.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyForce.cs @@ -1,33 +1,60 @@ 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 impulseStrength; + 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) { - impulseStrength = abilityElement.GetAttributeFloat("impulsestrength", 0f); + 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() { - Affliction affliction = Character.CharacterHealth.GetAffliction(afflictionIdentifier); - - if (affliction == null) { return; } + 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) { - limb.body.ApplyForce(Vector2.Normalize(limb.Mass * Character.AnimController.TargetMovement) * impulseStrength * (affliction.Strength / affliction.Prefab.MaxStrength), maxVelocity); + 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/CustomAbilities/CharacterAbilityAlienHoarder.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityAlienHoarder.cs index cabb8a78c..7c7141a25 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityAlienHoarder.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityAlienHoarder.cs @@ -8,13 +8,13 @@ namespace Barotrauma.Abilities class CharacterAbilityAlienHoarder : CharacterAbility { private readonly float addedDamageMultiplierPerItem; - private readonly int maxAmount; + private readonly float maxAddedDamageMultiplier; private readonly string[] tags; public CharacterAbilityAlienHoarder(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) { addedDamageMultiplierPerItem = abilityElement.GetAttributeFloat("addeddamagemultiplierperitem", 0f); - maxAmount = abilityElement.GetAttributeInt("maxamount", 0); + maxAddedDamageMultiplier = abilityElement.GetAttributeFloat("maxaddedddamagemultiplier", float.MaxValue); tags = abilityElement.GetAttributeStringArray("tags", Array.Empty(), convertToLowerInvariant: true); } @@ -30,7 +30,7 @@ namespace Barotrauma.Abilities totalAddedDamageMultiplier += addedDamageMultiplierPerItem; } } - attackData.DamageMultiplier += addedDamageMultiplierPerItem; + attackData.DamageMultiplier += Math.Min(totalAddedDamageMultiplier, maxAddedDamageMultiplier); } else { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityApprenticeship.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityApprenticeship.cs index b02fbe021..c6f035c5b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityApprenticeship.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityApprenticeship.cs @@ -12,9 +12,9 @@ namespace Barotrauma.Abilities protected override void ApplyEffect(AbilityObject abilityObject) { - if ((abilityObject as IAbilityString)?.String is string skillIdentifier && (abilityObject as IAbilityCharacter)?.Character is Character character) + if (abilityObject is AbilitySkillGain abilitySkillGain && !abilitySkillGain.GainedFromApprenticeship && abilitySkillGain.Character != Character) { - Character.Info?.IncreaseSkillLevel(skillIdentifier, 1.0f, character.Position + Vector2.UnitY * 175.0f); + Character.Info?.IncreaseSkillLevel(abilitySkillGain.String, 1.0f, Character.Position + Vector2.UnitY * 175.0f, gainedFromApprenticeship: true); } } } 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 ab72e7dc2..a73fb5af0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs @@ -845,15 +845,24 @@ namespace Barotrauma var character = args.Length >= 2 ? FindMatchingCharacter(args.Skip(1).ToArray()) : Character.Controlled; if (character != null) { - character.GiveTalent(args[0]); + 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 itemPrefab in TalentPrefab.TalentPrefabs) + foreach (TalentPrefab talent in TalentPrefab.TalentPrefabs) { - talentNames.Add(itemPrefab.Identifier); + talentNames.Add(talent.DisplayName); } return new string[][] @@ -863,24 +872,15 @@ namespace Barotrauma }; }, isCheat: true)); - commands.Add(new Command("unlocktalents", "unlocktalents [all/[jobname]]: give the controlled characters all the talents of the specified class", (string[] args) => + commands.Add(new Command("unlocktalents", "unlocktalents [all/[jobname]] [character]: give the specified character all the talents of the specified class", (string[] args) => { - if (Character.Controlled == null) { return; } - if (args.Length == 0 || args[0].Equals("all", StringComparison.OrdinalIgnoreCase)) - { - foreach (var talentTree in TalentTree.JobTalentTrees) - { - foreach (var subTree in talentTree.Value.TalentSubTrees) - { - foreach (var option in subTree.TalentOptionStages) - { - foreach (var talent in option.Talents) - { - Character.Controlled.GiveTalent(talent); - } - } - } - } + 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 { @@ -893,15 +893,21 @@ namespace Barotrauma if (!TalentTree.JobTalentTrees.TryGetValue(job.Identifier, out TalentTree talentTree)) { ThrowError($"No talents configured for the job \"{args[0]}\"."); - return; + 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.Controlled.GiveTalent(talent); + character.GiveTalent(talent); + NewMessage($"Unlocked talent \"{talent.DisplayName}\"."); } } } @@ -913,7 +919,8 @@ namespace Barotrauma availableArgs.AddRange(JobPrefab.Prefabs.Select(j => j.Name)); return new string[][] { - availableArgs.ToArray() + availableArgs.ToArray(), + Character.CharacterList.Select(c => c.Name).Distinct().ToArray() }; }, isCheat: true)); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/GiveSkillExpAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/GiveSkillExpAction.cs index 17ab504c6..8959f518f 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; 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/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/Mission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs index bfa584f9c..2c8f53636 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs @@ -359,10 +359,19 @@ namespace Barotrauma 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((int)(baseExperienceGain * experienceGainMultiplier.Value), isMissionExperience: true); + 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); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/MonsterEvent.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/MonsterEvent.cs index 9b52a0651..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,18 +288,19 @@ namespace Barotrauma spawnPos = chosenPosition.Position.ToVector2(); if (chosenPosition.Submarine != null || chosenPosition.Ruin != null) { - var spawnPoint = WayPoint.GetRandom(SpawnType.Enemy, sub: chosenPosition.Submarine ?? chosenPosition.Ruin?.Submarine, 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 ?? chosenPosition.Ruin?.Submarine)); - spawnPos = spawnPoint.WorldPosition; + 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) @@ -447,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/BarotraumaShared/SharedSource/GameSession/AutoItemPlacer.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/AutoItemPlacer.cs index dbff41f5b..aa8c2eee6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/AutoItemPlacer.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/AutoItemPlacer.cs @@ -10,10 +10,15 @@ namespace Barotrauma { 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; } @@ -23,6 +28,7 @@ namespace Barotrauma subs.ForEach(s => s.Info.InitialSuppliesSpawned = true); } + float difficultyModifier = GetLevelDifficultyModifier(); foreach (var sub in Submarine.Loaded) { if (sub.Info.Type == SubmarineType.Player || @@ -32,7 +38,7 @@ namespace Barotrauma { continue; } - Place(sub.ToEnumerable()); + Place(sub.ToEnumerable(), difficultyModifier: difficultyModifier); } if (Level.Loaded?.StartOutpost != null && Level.Loaded.Type == LevelData.LevelType.Outpost) @@ -42,12 +48,23 @@ namespace Barotrauma } } - public static void RegenerateLoot(Submarine sub, ItemContainer regeneratedContainer) + 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() { - Place(sub.ToEnumerable(), regeneratedContainer); + return Math.Clamp(Level.Loaded?.Difficulty is float difficulty ? (difficulty / 100f) * (MaxDifficultyModifier * 2) - MaxDifficultyModifier : DefaultDifficultyModifier, -MaxDifficultyModifier, MaxDifficultyModifier); } - private static void Place(IEnumerable subs, ItemContainer regeneratedContainer = null) + 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) { @@ -167,7 +184,7 @@ namespace Barotrauma } foreach (var validContainer in validContainers) { - var newItems = SpawnItem(itemPrefab, containers, validContainer); + var newItems = SpawnItem(itemPrefab, containers, validContainer, difficultyModifier); if (newItems.Any()) { spawnedItems.AddRange(newItems); @@ -201,11 +218,18 @@ namespace Barotrauma return validContainers; } - private static List SpawnItem(ItemPrefab itemPrefab, List containers, KeyValuePair validContainer) + private static readonly float[] qualityCommonnesses = new float[Quality.MaxQuality + 1] + { + 0.85f, + 0.125f, + 0.0225f, + 0.0025f, + }; + + private static List SpawnItem(ItemPrefab itemPrefab, List containers, KeyValuePair validContainer, float difficultyModifier) { List spawnedItems = new List(); - bool success = false; - if (Rand.Value(Rand.RandSync.Server) > validContainer.Value.SpawnProbability) { return spawnedItems; } + 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")) { @@ -220,10 +244,24 @@ namespace Barotrauma break; } if (!validContainer.Key.Inventory.CanBePut(itemPrefab)) { break; } + + int quality = 0; + float qualityCommmonnessSum = qualityCommonnesses.Sum(); + float randomNumber = Rand.Range(0f, qualityCommmonnessSum, Rand.RandSync.Server); + for (int k = qualityCommonnesses.Length - 1; k >= 0; k--) + { + if (randomNumber < qualityCommonnesses[k]) + { + quality = k; + 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) @@ -235,7 +273,6 @@ namespace Barotrauma spawnedItems.Add(item); validContainer.Key.Inventory.TryPutItem(item, null, createNetworkEvent: false); containers.AddRange(item.GetComponents()); - success = true; } return spawnedItems; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs index 5d89b421e..e526eebd8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs @@ -563,13 +563,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) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/EntitySpawnerComponent.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/EntitySpawnerComponent.cs new file mode 100644 index 000000000..f73cebac4 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/EntitySpawnerComponent.cs @@ -0,0 +1,280 @@ +#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")] + 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 = 0), 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; } + + SpawnTimerGoal ??= Rand.Range(Math.Min(SpawnTimerRange.X, SpawnTimerRange.Y), Math.Max(SpawnTimerRange.X, SpawnTimerRange.Y), 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"; + + switch (connection.Name) + { + case "set_state": + CanSpawn = isNonZero; + break; + case "toggle" when isNonZero: + CanSpawn = !CanSpawn; + 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; + } + } + + 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/Holdable/Holdable.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Holdable.cs index d14b487f5..b2c819360 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Holdable.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Holdable.cs @@ -598,9 +598,12 @@ namespace Barotrauma.Items.Components 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 == item.Submarine && i.GetComponent() is Holdable holdable && holdable.Attached && i.Prefab.Identifier == item.prefab.Identifier); + i => i.Submarine == attachTarget?.Submarine && i.GetComponent() is Holdable holdable && holdable.Attached && i.Prefab.Identifier == item.prefab.Identifier); if (currentlyAttachedCount >= maxAttachableCount) { #if CLIENT diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/MeleeWeapon.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/MeleeWeapon.cs index cce65f786..05b87be87 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/MeleeWeapon.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/MeleeWeapon.cs @@ -77,6 +77,7 @@ namespace Barotrauma.Items.Components }; } item.IsShootable = true; + item.RequireAimToUse = element.Parent.GetAttributeBool("requireaimtouse", true); PreferredContainedItems = element.GetAttributeStringArray("preferredcontaineditems", new string[0], convertToLowerInvariant: true); } @@ -210,7 +211,7 @@ namespace Barotrauma.Items.Components 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)); + 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 @@ -222,16 +223,16 @@ namespace Barotrauma.Items.Components else { // TODO: We might want to make this configurable - hitPos = MathUtils.WrapAnglePi(hitPos - deltaTime * 15f); + hitPos -= deltaTime * 15f; if (Swing) { - ac.HoldItem(deltaTime, item, handlePos, SwingPos, Vector2.Zero, aim: false, hitPos, holdAngle + hitPos); + 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.PiOver2) + if (hitPos < -MathHelper.Pi) { RestoreCollision(); hitting = false; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RangedWeapon.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RangedWeapon.cs index ee11e6001..0966ea92f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RangedWeapon.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RangedWeapon.cs @@ -74,8 +74,8 @@ namespace Barotrauma.Items.Components { 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; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RepairTool.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RepairTool.cs index 4aaceab07..bc9c658ae 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RepairTool.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RepairTool.cs @@ -712,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/ItemContainer.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs index f97b2f01f..dc1ea9383 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs @@ -522,7 +522,7 @@ namespace Barotrauma.Items.Components if (Math.Abs(item.Rotation) > 0.01f) { Matrix transform = Matrix.CreateRotationZ(MathHelper.ToRadians(-item.Rotation)); - transformedItemPos = Vector2.Transform(transformedItemPos, transform); + transformedItemPos = Vector2.Transform(transformedItemPos - item.Position, transform) + item.Position; transformedItemInterval = Vector2.Transform(transformedItemInterval, transform); transformedItemIntervalHorizontal = Vector2.Transform(transformedItemIntervalHorizontal, transform); transformedItemIntervalVertical = Vector2.Transform(transformedItemIntervalVertical, transform); @@ -550,6 +550,10 @@ namespace Barotrauma.Items.Components currentRotation *= item.body.Dir; currentRotation += item.body.Rotation; } + else + { + currentRotation += MathHelper.ToRadians(-item.Rotation); + } int i = 0; Vector2 currentItemPos = transformedItemPos; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/MiniMap.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/MiniMap.cs index 38d3c40fa..39049b9bd 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/MiniMap.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/MiniMap.cs @@ -64,13 +64,6 @@ namespace Barotrauma.Items.Components set; } - [Editable, Serialize(true, true, description: "Enable hull condition mode.")] - public bool EnableHullCondition - { - get; - set; - } - [Editable, Serialize(true, true, description: "Enable item finder mode.")] public bool EnableItemFinder { @@ -148,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.ReceivedWaterAmount = Rand.Range(0.0f, 1.0f); + waterAmount = Rand.Range(0.0f, 1.0f); } else { - hullData.ReceivedWaterAmount = 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.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/Quality.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Quality.cs index aaf95429a..5ffe84c32 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Quality.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Quality.cs @@ -9,6 +9,14 @@ namespace Barotrauma.Items.Components { public const int MaxQuality = 3; + public static readonly float[] QualityCommonnesses = new float[] + { + 0.8f, + 0.15f, + 0.045f, + 0.005f, + }; + public enum StatType { Condition, @@ -39,7 +47,18 @@ namespace Barotrauma.Items.Components public int QualityLevel { get { return qualityLevel; } - set { qualityLevel = MathHelper.Clamp(value, 0, MaxQuality); } + 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) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs index b0885d400..041037cdf 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs @@ -376,7 +376,7 @@ namespace Barotrauma.Items.Components tinkeringDuration -= deltaTime; // not great to interject it here, should be less reliant on returning - float conditionDecrease = deltaTime * (CurrentFixer.GetStatValue(StatTypes.TinkeringDamage) / item.MaxCondition) * 100f; + float conditionDecrease = deltaTime * (CurrentFixer.GetStatValue(StatTypes.TinkeringDamage) / item.Prefab.Health) * 100f; item.Condition -= conditionDecrease; if (!CanTinker(CurrentFixer) || tinkeringDuration <= 0f) @@ -424,7 +424,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); @@ -458,7 +459,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; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/LightComponent.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/LightComponent.cs index f6b290bc3..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 } } 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/Inventory.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs index ccb06ad4a..e9f28b882 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; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs index 2d59645bb..2d5079340 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs @@ -106,7 +106,7 @@ namespace Barotrauma //a dictionary containing lists of the status effects in all the components of the item private readonly bool[] hasStatusEffectsOfType; private readonly Dictionary> statusEffectLists; - + public Dictionary SerializableProperties { get; protected set; } private bool? hasInGameEditableProperties; @@ -232,7 +232,7 @@ namespace Barotrauma public bool IsInteractable(Character character) { if (character != null && character.IsOnPlayerTeam) - { + { return IsPlayerTeamInteractable; } else @@ -254,6 +254,12 @@ namespace Barotrauma { if (!Prefab.AllowRotatingInEditor) { return; } rotationRad = MathHelper.ToRadians(value); +#if CLIENT + if (Screen.Selected == GameMain.SubEditorScreen) + { + SetContainedItemPositions(); + } +#endif } } @@ -478,7 +484,7 @@ namespace Barotrauma 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, //we'll just use NaN (which does nothing) and set the default value in the constructor/load [Serialize(float.NaN, false), Editable] @@ -628,9 +634,9 @@ namespace Barotrauma public int Quality { - get - { - return qualityComponent?.QualityLevel ?? 0; + get + { + return qualityComponent?.QualityLevel ?? 0; } set { @@ -1155,7 +1161,7 @@ namespace Barotrauma { return GetComponent()?.GetValue(statType) ?? 0.0f; } - + public void RemoveContained(Item contained) { ownInventory?.RemoveItem(contained); @@ -1867,7 +1873,8 @@ namespace Barotrauma foreach (ItemComponent component in components) { component.FlipX(relativeToSub); - } + } + SetContainedItemPositions(); } public override void FlipY(bool relativeToSub) @@ -1892,6 +1899,7 @@ namespace Barotrauma { component.FlipY(relativeToSub); } + SetContainedItemPositions(); } /// @@ -2112,8 +2120,6 @@ namespace Barotrauma } while (CoroutineManager.DeltaTime <= 0.0f); delayedSignals.Remove((signal, connection)); - - signal.source = this; connection.SendSignal(signal); yield return CoroutineStatus.Success; @@ -2470,6 +2476,8 @@ namespace Barotrauma parentInventory.RemoveItem(this); parentInventory = null; } + + SetContainedItemPositions(); } public void Equip(Character character) @@ -2741,7 +2749,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); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs index ded5ee66a..3378966a4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs @@ -920,7 +920,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 + "\"!"); } @@ -1152,11 +1153,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; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs index 350fb0795..ac3521e32 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs @@ -1829,7 +1829,7 @@ namespace Barotrauma private void GenerateRuin(Point ruinPos, bool mirror) { - var ruinGenerationParams = RuinGenerationParams.GetRandom(); + var ruinGenerationParams = RuinGenerationParams.GetRandom(Rand.RandSync.Server); LocationType locationType = StartLocation?.Type; if (locationType == null) @@ -1839,7 +1839,7 @@ namespace Barotrauma { locationType = LocationType.List.Where(lt => ruinGenerationParams.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); } } @@ -3594,7 +3594,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); } } @@ -3953,7 +3953,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; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Ruins/RuinGenerationParams.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Ruins/RuinGenerationParams.cs index be1f4811e..be359698a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Ruins/RuinGenerationParams.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Ruins/RuinGenerationParams.cs @@ -44,7 +44,7 @@ namespace Barotrauma.RuinGeneration this.filePath = filePath; } - public static RuinGenerationParams GetRandom() + public static RuinGenerationParams GetRandom(Rand.RandSync randSync = Rand.RandSync.Server) { if (paramsList == null) { LoadAll(); } @@ -54,7 +54,7 @@ namespace Barotrauma.RuinGeneration 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() diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Location.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Location.cs index 37c1c3ab8..6c208d35b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Location.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Location.cs @@ -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; /// diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Map.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Map.cs index 76b284bdc..aac3655eb 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Map.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Map.cs @@ -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.1f; + const float BaseDifficulty = -3f; + return (float)(1 - Math.Pow(1 - areaDifficulty, CurveModifier)) * DifficultyMultiplier * 100f + BaseDifficulty; + } } partial void GenerateLocationConnectionVisuals(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostGenerator.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostGenerator.cs index 70e682b0b..224b41e02 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostGenerator.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostGenerator.cs @@ -85,7 +85,15 @@ namespace Barotrauma var subInfo = new SubmarineInfo(outpostModuleFile.Path); if (subInfo.OutpostModuleInfo != null) { - if (subInfo.OutpostModuleInfo.ModuleFlags.Contains("ruin") != generationParams is RuinGeneration.RuinGenerationParams) { continue; } + 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; + } + } outpostModules.Add(subInfo); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs index d142fadbc..e53436daf 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs @@ -11,6 +11,7 @@ using System.Xml.Linq; using Barotrauma.Abilities; #if CLIENT using Microsoft.Xna.Framework.Graphics; +using Barotrauma.Lights; #endif namespace Barotrauma @@ -97,7 +98,7 @@ namespace Barotrauma { get { return Prefab.Body; } } - + public List Bodies { get; private set; } public bool CastShadow @@ -113,7 +114,7 @@ namespace Barotrauma } private float? maxHealth; - + [Serialize(100.0f, true)] public float MaxHealth { @@ -168,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 @@ -176,7 +177,7 @@ namespace Barotrauma get { return spriteColor; } set { spriteColor = value; } } - + [Editable, Serialize(false, true)] public bool UseDropShadow { @@ -216,6 +217,13 @@ namespace Barotrauma UpdateSections(); } } + +#if CLIENT + foreach (LightSource light in Lights) + { + light.SpriteScale = scale * textureScale; + } +#endif } } @@ -231,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 } } @@ -239,7 +254,13 @@ namespace Barotrauma public Vector2 TextureOffset { get { return textureOffset; } - set { textureOffset = value; } + set + { + textureOffset = value; +#if CLIENT + SetLightTextureOffset(); +#endif + } } @@ -282,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; } @@ -364,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 } @@ -375,7 +402,7 @@ namespace Barotrauma defaultRect = rectangle; maxHealth = sp.Health; - + rect = rectangle; TextureScale = sp.TextureScale; @@ -427,13 +454,48 @@ 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) @@ -445,7 +507,7 @@ namespace Barotrauma } InsertToList(); - + DebugConsole.Log("Created " + Name + " (" + ID + ")"); } @@ -477,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)); @@ -505,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); @@ -549,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) @@ -646,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 @@ -693,6 +755,10 @@ namespace Barotrauma #if CLIENT if (convexHulls != null) convexHulls.ForEach(x => x.Remove()); + foreach (LightSource light in Lights) + { + light.Remove(); + } #endif } @@ -725,6 +791,10 @@ namespace Barotrauma #if CLIENT if (convexHulls != null) convexHulls.ForEach(x => x.Remove()); + foreach (LightSource light in Lights) + { + light.Remove(); + } #endif } @@ -782,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; } @@ -800,7 +870,7 @@ namespace Barotrauma Upgrades.Add(upgrade); upgrade.ApplyUpgrade(); } - + UpdateSections(); return true; @@ -915,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; @@ -926,7 +996,7 @@ namespace Barotrauma } return sectionPos; } - } + } public AttackResult AddDamage(Character attacker, Vector2 worldPosition, Attack attack, float deltaTime, bool playSound = false) { @@ -949,7 +1019,7 @@ namespace Barotrauma GameMain.ParticleManager.CreateParticle("dustcloud", SectionPosition(i), 0.0f, 0.0f); #endif } - } + } #if CLIENT if (playSound && damageAmount > 0) { @@ -976,7 +1046,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) { @@ -1022,10 +1092,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 @@ -1034,7 +1104,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)); } } @@ -1053,7 +1123,7 @@ namespace Barotrauma gapRect.Y += 10; gapRect.Width += 20; gapRect.Height += 20; - + bool horizontalGap = !IsHorizontal; if (Prefab.BodyRotation != 0.0f) { @@ -1090,7 +1160,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; @@ -1106,9 +1176,9 @@ namespace Barotrauma { if (damageDiff < 0.0f) { - attacker.Info?.IncreaseSkillLevel("mechanical", + attacker.Info?.IncreaseSkillLevel("mechanical", -damageDiff * SkillSettings.Current.SkillIncreasePerRepairedStructureDamage / Math.Max(attacker.GetSkillLevel("mechanical"), 1.0f), - SectionPosition(sectionIndex)); + SectionPosition(sectionIndex)); } } } @@ -1116,7 +1186,7 @@ namespace Barotrauma bool hasHole = SectionBodyDisabled(sectionIndex); if (hadHole == hasHole) { return; } - + UpdateSections(); } @@ -1198,7 +1268,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; } @@ -1240,7 +1310,7 @@ namespace Barotrauma } partial void CreateConvexHull(Vector2 position, Vector2 size, float rotation); - + public override void FlipX(bool relativeToSub) { base.FlipX(relativeToSub); @@ -1261,7 +1331,7 @@ namespace Barotrauma CreateStairBodies(); } - + if (HasBody) { CreateSections(); @@ -1388,7 +1458,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 @@ -1433,7 +1503,7 @@ namespace Barotrauma } SerializableProperty.SerializeProperties(this, element); - + foreach (var upgrade in Upgrades) { upgrade.Save(element); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/StructurePrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/StructurePrefab.cs index 380df3975..19189ff95 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/StructurePrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/StructurePrefab.cs @@ -291,7 +291,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 3e92a9aea..3273bb4c3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs @@ -914,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) @@ -1429,6 +1431,11 @@ namespace Barotrauma } } } + else if (info.IsRuin) + { + ShowSonarMarker = false; + PhysicsBody.FarseerBody.BodyType = BodyType.Static; + } } if (entityGrid != null) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/WayPoint.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/WayPoint.cs index 3325ea591..e335f0eec 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/WayPoint.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/WayPoint.cs @@ -868,11 +868,12 @@ namespace Barotrauma } } - public static WayPoint GetRandom(SpawnType spawnType = SpawnType.Human, JobPrefab assignedJob = null, Submarine sub = 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.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); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Serialization/SerializableProperty.cs b/Barotrauma/BarotraumaShared/SharedSource/Serialization/SerializableProperty.cs index cb7f26141..1f4ff6489 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Serialization/SerializableProperty.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Serialization/SerializableProperty.cs @@ -92,7 +92,7 @@ namespace Barotrauma } 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; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Serialization/XMLExtensions.cs b/Barotrauma/BarotraumaShared/SharedSource/Serialization/XMLExtensions.cs index 780aaa767..262bbb9c4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Serialization/XMLExtensions.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Serialization/XMLExtensions.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Globalization; using System.IO; using System.Linq; @@ -15,6 +16,22 @@ namespace Barotrauma { public static class XMLExtensions { + 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) => System.IO.Path.GetRelativePath(Environment.CurrentDirectory, element.BaseUri); public static readonly XmlReaderSettings ReaderSettings = new XmlReaderSettings @@ -485,6 +502,25 @@ namespace Barotrauma 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(); @@ -525,12 +561,28 @@ namespace Barotrauma => $"{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:X2}"; + => $"#{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) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs index d0f70222e..2c8cf176c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs @@ -197,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})"; @@ -274,6 +286,8 @@ namespace Barotrauma private readonly bool spawnItemRandomly; private readonly List spawnCharacters; + public readonly List giveTalentInfos; + private readonly List aiTriggers; private readonly List triggeredEvents; @@ -374,6 +388,7 @@ namespace Barotrauma spawnItems = new List(); spawnItemRandomly = element.GetAttributeBool("spawnitemrandomly", false); spawnCharacters = new List(); + giveTalentInfos = new List(); aiTriggers = new List(); Afflictions = new List(); Explosions = new List(); @@ -576,7 +591,7 @@ namespace Barotrauma break; case "requiredaffliction": requiredAfflictions ??= new HashSet<(string, float)>(); - string[] ids = subElement.GetAttributeStringArray("identifier", new string[0]); + string[] ids = subElement.GetAttributeStringArray("identifier", null) ?? subElement.GetAttributeStringArray("type", new string[0]); foreach (string afflictionId in ids) { requiredAfflictions.Add(( @@ -669,6 +684,10 @@ 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; @@ -1305,6 +1324,33 @@ namespace Barotrauma } } } + + if (giveTalentInfos.Any() && (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)) + { + 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) @@ -1367,6 +1413,7 @@ namespace Barotrauma }); } } + if (spawnItemRandomly) { SpawnItem(spawnItems.GetRandom()); 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 88835bfc0..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()) diff --git a/Barotrauma/BarotraumaShared/changelog.txt b/Barotrauma/BarotraumaShared/changelog.txt index 56e627677..94ea2fd37 100644 --- a/Barotrauma/BarotraumaShared/changelog.txt +++ b/Barotrauma/BarotraumaShared/changelog.txt @@ -1,3 +1,54 @@ +--------------------------------------------------------------------------------------------------------- +v0.1500.6.0 +--------------------------------------------------------------------------------------------------------- + +Additions and changes: +- Improvements and additions to the alien ruins and the new fractal guardians. +- More improvements and fixes to the overhauled character sprites and animations. +- 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. +- Talent adjustments and balancing. +- Nerfed thermal goggles: some enemies are invisible to them and targets behind walls are much less clear. +- 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. +- Made welding tools a bit less effective early to compensate for increases to their effectiveness from quality/talents. +- Fuel tanks, rods, grenades and other quality-based items can be stacked again. Items of different qualities still can't be put in the same stack. +- Endocrine Booster now gives a random talent. +- New sprite for the mudraptor beak grown by splicing mudraptor genes. +- Fabricating fuel rods now requirse 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). +- Changed how skillbooks work: the skills are gained when you "finish" reading the book instead of continuously to prevent UI message spam and overlap, the books can be used on others in the health interface or by hitting them with the book. +- Color the selected (but unapplied) talent icons orange instead of changing the icon in the talent UI, made the apply button flash when there's unapplied talents. +- Merged status monitor's status and hull condition tabs. +- Halved concussion's damage threshold to make it possible for more sources of damage to trigger it, + halved the probability to compensate. + +Fixes: +- Fixed high-quality batteries/tanks (or other items with an increased max condition) spawning in non-full condition (unstable only). +- Fixed ropes sometimes crashing the game (unstable only). +- Fixed some items occasionally disappearing from outposts when re-entering them. The most noticeable symptom was wires disappearing from the outpost's airlock, preventing the hatch from opening (unstable only). +- Fixed multiplayer campaign characters resetting if they're dead when the round ends. +- Fixed clients who aren't currently controlling a character not getting XP in the mp campaign (unstable only). +- 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 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. +- Fixed boarding axe (unstable only). +- 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 sub editor where changing ItemComponent color with HSV picker would create an error in the console. +- Fixed inability to hit downed characters with short melee weapons like the diving knife. +- Fixed inability to sell nasonov and faraday artifacts (unstable only). +- Fixed concussion description (unstable only). +- Fixes gender-specific affliction sound effects (e.g. vomiting) not playing (unstable only). +- Fixed humans' "aim source position" being too low, causing aim to be slightly off (unstable only). +- Fixed artifact transport case displaying as empty when there's a nasonov/faraday artifact inside (unstable only). +- Fixed "infiltration" event getting stuck on one of the conversation options. +- Fixed ruin's physics bodies being dynamic in mirrored levels (causing them to sink). +- Backwards compatibility: assign skin colors to characters saved in previous versions. +- Assign random skin/hair colors to characters without one configured instead of defaulting to white. Fixes all tutorial characters having white skin/hair. +Modding: +- 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. + --------------------------------------------------------------------------------------------------------- v0.1500.5.0 --------------------------------------------------------------------------------------------------------- @@ -8,7 +59,6 @@ Additions and changes: - New Alien Ruin mission: kill the guardians inhabiting the ruin and destroy their pods. - Improvements and fixes to the overhauled character sprites and animations. - More talent improvements and additions. -- Balanced loot spawn rates. - Cap the amount argument of the spawnitem command to 100 to prevent freezing/crashing when trying to spawn a ridiculous amount of the item. - Nerfed concussions: they now require a larger amount of damage to the head to trigger and slowly heal by themselves. - Added "unlocktalents [job]" command. @@ -19,6 +69,17 @@ Additions and changes: - Added button to randomize character appearance in the character customization menus. - Permanently reduce character skills 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. +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. +- Modifed characters' base vitalities. +- Adjustments to monster stats. +- Reduced mission experience gains, level difficulty affects mission experience. + Fixes: - Fixed crash when firing a syring gun (unstable only). - Fixed crashing when using meds in multiplayer with friendly fire turned off (unstable only). From de917c5d74733f73ddbaeabb9910654b05958419 Mon Sep 17 00:00:00 2001 From: Markus Isberg <3e849f2e5c@pm.me> Date: Thu, 14 Oct 2021 00:42:06 +0900 Subject: [PATCH 08/12] Unstable 0.1500.7.0 (No edition) --- .../Characters/Animation/Ragdoll.cs | 14 +- .../ClientSource/Characters/Character.cs | 141 ++++++++++++++++-- .../ClientSource/Characters/CharacterInfo.cs | 25 ++-- .../Characters/CharacterNetworking.cs | 7 +- .../Characters/Health/CharacterHealth.cs | 4 +- .../ClientSource/Characters/Limb.cs | 8 +- .../ClientSource/DebugConsole.cs | 7 +- .../BarotraumaClient/ClientSource/GUI/GUI.cs | 7 +- .../ClientSource/GUI/GUIImage.cs | 2 +- .../ClientSource/GUI/GUIListBox.cs | 3 +- .../ClientSource/GUI/GUIStyle.cs | 17 +++ .../Items/Components/ItemComponent.cs | 4 +- .../Items/Components/Machines/MiniMap.cs | 8 +- .../Items/Components/RemoteController.cs | 4 +- .../Components/Signal/ConnectionPanel.cs | 5 - .../Items/Components/Signal/Terminal.cs | 4 +- .../ClientSource/Items/Components/Wearable.cs | 10 +- .../ClientSource/Items/Inventory.cs | 77 ++++++---- .../ClientSource/Items/Item.cs | 4 +- .../ClientSource/Map/MapEntity.cs | 4 +- .../CharacterEditor/CharacterEditorScreen.cs | 11 +- .../ClientSource/Screens/GameScreen.cs | 13 +- .../ClientSource/Screens/NetLobbyScreen.cs | 18 ++- .../ClientSource/Sprite/Sprite.cs | 2 +- .../BarotraumaClient/LinuxClient.csproj | 2 +- Barotrauma/BarotraumaClient/MacClient.csproj | 2 +- .../BarotraumaClient/WindowsClient.csproj | 2 +- .../BarotraumaServer/LinuxServer.csproj | 2 +- Barotrauma/BarotraumaServer/MacServer.csproj | 2 +- .../ServerSource/Characters/CharacterInfo.cs | 6 +- .../Characters/CharacterNetworking.cs | 1 + .../ServerSource/DebugConsole.cs | 10 +- .../ServerSource/Networking/GameServer.cs | 4 + .../BarotraumaServer/WindowsServer.csproj | 2 +- .../Characters/AI/AIController.cs | 5 +- .../Characters/AI/EnemyAIController.cs | 40 ++--- .../Characters/AI/IndoorsSteeringManager.cs | 42 +++--- .../AI/Objectives/AIObjectiveCombat.cs | 6 +- .../AI/Objectives/AIObjectiveFindSafety.cs | 2 +- .../AI/Objectives/AIObjectiveGetItem.cs | 2 +- .../AI/Objectives/AIObjectiveGoTo.cs | 75 ++++++---- .../AI/Objectives/AIObjectiveIdle.cs | 2 +- .../AI/Objectives/AIObjectiveReturn.cs | 36 ++--- .../SharedSource/Characters/AI/PathFinder.cs | 37 ++++- .../Characters/AI/SteeringManager.cs | 5 +- .../Animation/FishAnimController.cs | 2 +- .../Animation/HumanoidAnimController.cs | 25 ++-- .../Characters/Animation/Ragdoll.cs | 33 +++- .../SharedSource/Characters/Character.cs | 39 ++--- .../SharedSource/Characters/CharacterInfo.cs | 20 +-- .../Characters/Health/CharacterHealth.cs | 22 ++- .../SharedSource/Characters/Jobs/JobPrefab.cs | 21 ++- .../SharedSource/Characters/Limb.cs | 4 + .../Params/Animation/AnimationParams.cs | 12 +- .../Params/Ragdoll/RagdollParams.cs | 2 +- .../AbilityConditionIsAiming.cs | 16 +- .../AbilityConditionMission.cs | 0 .../CharacterAbilityGainSimultaneousSkill.cs | 2 +- .../CharacterAbilityGiveMissionCount.cs | 21 --- .../CharacterAbilityIncreaseSkill.cs | 5 +- .../Abilities/CharacterAbilityRevive.cs | 2 +- .../Abilities/CharacterAbilityUnlockTree.cs | 1 + .../CharacterAbilityApprenticeship.cs | 2 +- .../CharacterAbilityMultitasker.cs | 2 +- .../Characters/Talents/CharacterTalent.cs | 2 + .../SharedSource/DebugConsole.cs | 12 +- .../BarotraumaShared/SharedSource/Enums.cs | 1 + .../Events/EventActions/GiveSkillExpAction.cs | 2 +- .../Events/Missions/AlienRuinMission.cs | 4 +- .../Events/Missions/ScanMission.cs | 62 +++++--- .../SharedSource/GameSession/CrewManager.cs | 5 + .../GameSession/Data/Reputation.cs | 2 +- .../GameSession/GameModes/CampaignMode.cs | 19 ++- .../SharedSource/GameSession/GameSession.cs | 4 +- .../Components/EntitySpawnerComponent.cs | 18 ++- .../Items/Components/Holdable/MeleeWeapon.cs | 1 + .../Items/Components/Holdable/Propulsion.cs | 8 +- .../Components/Machines/Deconstructor.cs | 3 +- .../Items/Components/Machines/Fabricator.cs | 3 +- .../Components/Machines/OxygenGenerator.cs | 41 +++-- .../Items/Components/Machines/Steering.cs | 3 +- .../Items/Components/Projectile.cs | 2 +- .../Items/Components/RemoteController.cs | 1 - .../Items/Components/Repairable.cs | 6 +- .../Items/Components/TriggerComponent.cs | 2 + .../SharedSource/Items/Components/Turret.cs | 3 +- .../SharedSource/Items/Components/Wearable.cs | 2 + .../SharedSource/Items/Item.cs | 2 +- .../SharedSource/Items/ItemPrefab.cs | 3 + .../SharedSource/Items/RelatedItem.cs | 12 +- .../BarotraumaShared/SharedSource/Map/Hull.cs | 2 +- .../SharedSource/Map/Levels/Level.cs | 37 ++++- .../Map/Levels/LevelObjects/LevelTrigger.cs | 6 +- .../SharedSource/Map/Map/Map.cs | 2 +- .../Map/Outposts/OutpostGenerator.cs | 12 +- .../SharedSource/Map/Structure.cs | 3 +- .../SharedSource/Map/SubmarineBody.cs | 2 +- .../SharedSource/Map/WayPoint.cs | 2 + .../Networking/Primitives/Message/Message.cs | 20 ++- .../SharedSource/Physics/PhysicsBody.cs | 31 ++-- .../Serialization/XMLExtensions.cs | 5 +- .../StatusEffects/StatusEffect.cs | 7 +- .../SharedSource/Utils/ToolBox.cs | 7 +- .../BarotraumaShared/Submarines/Azimuth.sub | Bin 229143 -> 231424 bytes Barotrauma/BarotraumaShared/changelog.txt | 40 +++++ 105 files changed, 871 insertions(+), 443 deletions(-) rename Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/{AbilityConditionDataless => AbilityConditionData}/AbilityConditionMission.cs (100%) delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveMissionCount.cs diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Animation/Ragdoll.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Animation/Ragdoll.cs index e42aa0547..37f6c91ed 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Animation/Ragdoll.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Animation/Ragdoll.cs @@ -342,7 +342,7 @@ 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; @@ -355,8 +355,16 @@ namespace Barotrauma List depthSortedLimbs = Limbs.OrderBy(l => l.DefaultSpriteDepth).ToList(); foreach (Limb limb in Limbs) { - if (limb.ActiveSprite == null) { continue; } - 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) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs index c5d75f86c..3ee39ef88 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs @@ -128,6 +128,50 @@ 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); @@ -618,6 +662,17 @@ 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 (!IsIncapacitated) @@ -736,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) { @@ -942,6 +1018,55 @@ namespace Barotrauma 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 @@ -1046,24 +1171,14 @@ namespace Barotrauma if (newAmount > prevAmount) { int increase = newAmount - prevAmount; - GUI.AddMessage( - "+" + TextManager.GetWithVariable("currencyformat", "[credits]", increase.ToString()), - GUI.Style.Yellow, - Position + Vector2.UnitY * 150.0f, - Vector2.UnitY * 10.0f, - playSound: true, - subId: Submarine?.ID ?? -1); + AddMessage("+" + TextManager.GetWithVariable("currencyformat", "[credits]", "[value]"), + GUI.Style.Yellow, playSound: this == Controlled, "money", increase); } } partial void OnTalentGiven(string talentIdentifier) { - GUI.AddMessage(TextManager.Get("talentname." + talentIdentifier.ToString()), - GUI.Style.Yellow, - Position + Vector2.UnitY * 150.0f, - Vector2.UnitY * 10.0f, - playSound: true, - subId: Submarine?.ID ?? -1); + AddMessage(TextManager.Get("talentname." + talentIdentifier.ToString()), GUI.Style.Yellow, playSound: this == Controlled); } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterInfo.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterInfo.cs index b85a2b0e2..d8b6cda3a 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterInfo.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterInfo.cs @@ -187,7 +187,7 @@ 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; } @@ -198,17 +198,14 @@ namespace Barotrauma 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)), - specialIncrease ? GUI.Style.Orange : GUI.Style.Green, - textPopupPos, - Vector2.UnitY * 10.0f, - playSound: specialIncrease, - 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, Vector2 textPopupPos) + partial void OnExperienceChanged(int prevAmount, int newAmount) { if (Character.Controlled != null && Character.Controlled.TeamID != TeamID) { return; } @@ -217,13 +214,9 @@ namespace Barotrauma if (newAmount > prevAmount) { int increase = newAmount - prevAmount; - GUI.AddMessage( - string.Format("+{0} {1}", increase, TextManager.Get("experienceshort")), - GUI.Style.Blue, - textPopupPos, - Vector2.UnitY * 10.0f, - playSound: true, - subId: Character?.Submarine?.ID ?? -1); + Character?.AddMessage( + "+[value] " + TextManager.Get("experienceshort"), + GUI.Style.Blue, playSound: Character == Character.Controlled, "exp", increase); } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterNetworking.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterNetworking.cs index 34a5e6378..925021818 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterNetworking.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterNetworking.cs @@ -358,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 @@ -390,7 +390,7 @@ namespace Barotrauma } targetLimb = targetCharacter.AnimController.Limbs[targetLimbIndex]; } - if (attackLimb?.attack != null) + if (attackLimb?.attack != null && Controlled != this) { if (eventType == 4) { @@ -467,8 +467,9 @@ namespace Barotrauma ushort talentCount = msg.ReadUInt16(); for (int i = 0; i < talentCount; i++) { + bool addedThisRound = msg.ReadBoolean(); UInt32 talentIdentifier = msg.ReadUInt32(); - GiveTalent(talentIdentifier); + GiveTalent(talentIdentifier, addedThisRound); } break; case 12: //NetEntityEvent.Type.UpdateMoney: diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs index 945f8acc3..9ee0fa4ba 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs @@ -1969,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 88b7bc21d..42ba80997 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Limb.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Limb.cs @@ -167,6 +167,10 @@ 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; } @@ -397,7 +401,7 @@ namespace Barotrauma return deformations; } } - DefaultSpriteDepth = ActiveSprite.Depth; + DefaultSpriteDepth = GetActiveSprite()?.Depth ?? 0.0f; LightSource?.CheckConditionals(); } @@ -901,7 +905,7 @@ namespace Barotrauma } foreach (WearableSprite wearable in WearingItems) { - if (onlyDrawable != null && onlyDrawable != wearable) continue; + 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; diff --git a/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs b/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs index 90c057cab..68aa6da0c 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs @@ -695,7 +695,12 @@ namespace Barotrauma 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) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs index e7a0a3af5..18c65a9f7 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs @@ -941,7 +941,7 @@ namespace Barotrauma inventoryIndex = updateList.IndexOf(CharacterHUD.HUDFrame); } - if ((!PlayerInput.PrimaryMouseButtonHeld() && !PlayerInput.PrimaryMouseButtonClicked()) || prevMouseOn == null) + if ((!PlayerInput.PrimaryMouseButtonHeld() && !PlayerInput.PrimaryMouseButtonClicked()) || (prevMouseOn == null && !PlayerInput.SecondaryMouseButtonHeld())) { for (var i = updateList.Count - 1; i > inventoryIndex; i--) { @@ -2454,7 +2454,7 @@ namespace Barotrauma { Submarine sub = Submarine.Loaded.FirstOrDefault(s => s.ID == subId); - var newMessage = new GUIMessage(message, color, pos, velocity, lifeTime, Alignment.Center, LargeFont, sub: sub); + 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; @@ -2477,8 +2477,7 @@ namespace Barotrauma moveDir = Rand.Vector(1.0f); } moveDir.Y = -Math.Abs(moveDir.Y); - newMessage.Pos += moveDir * 20; - overlapFound = true; + newMessage.Pos -= Vector2.UnitY * 10; } tries++; if (tries > 20) { break; } 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 f3957f392..2a010e707 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIListBox.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIListBox.cs @@ -1026,7 +1026,6 @@ namespace Barotrauma ContentBackground.DrawManually(spriteBatch, alsoChildren: false); Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle; - RasterizerState prevRasterizerState = spriteBatch.GraphicsDevice.RasterizerState; if (HideChildrenOutsideFrame) { spriteBatch.End(); @@ -1054,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/GUIStyle.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIStyle.cs index 923728eaa..6c6224e8f 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIStyle.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIStyle.cs @@ -511,5 +511,22 @@ namespace Barotrauma 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/Items/Components/ItemComponent.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemComponent.cs index 5e5a9357a..cb663cdcf 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemComponent.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemComponent.cs @@ -444,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) { } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs index ff08aa3e2..d94357008 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs @@ -410,13 +410,13 @@ namespace Barotrauma.Items.Components return true; } - public override void AddToGUIUpdateList() + public override void AddToGUIUpdateList(int order = 0) { - base.AddToGUIUpdateList(); - hullInfoFrame.AddToGUIUpdateList(order: 1); + base.AddToGUIUpdateList(order); + hullInfoFrame.AddToGUIUpdateList(order: order + 1); if (currentMode == MiniMapMode.ItemFinder && searchBar.Selected) { - searchAutoComplete.AddToGUIUpdateList(order: 1); + searchAutoComplete.AddToGUIUpdateList(order: order + 1); } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/RemoteController.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/RemoteController.cs index bf5405474..6f6d3d740 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/RemoteController.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/RemoteController.cs @@ -14,9 +14,9 @@ namespace Barotrauma.Items.Components currentTarget?.UpdateHUD(cam, character,deltaTime); } - public override void AddToGUIUpdateList() + public override void AddToGUIUpdateList(int order = 0) { - currentTarget?.AddToGUIUpdateList(); + currentTarget?.AddToGUIUpdateList(order: -1); } } } 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 e13440e0e..a816f402a 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Terminal.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Terminal.cs @@ -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/Wearable.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Wearable.cs index b489954a0..10d0fc376 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Wearable.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Wearable.cs @@ -5,9 +5,9 @@ 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}"; @@ -15,7 +15,7 @@ namespace Barotrauma.Items.Components 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 045117832..a8d965902 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs @@ -601,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()) @@ -1302,39 +1305,46 @@ 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; - } - 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)) - { - highlightedSubInventorySlots.RemoveWhere(s => s.Item == parentItem); - selectedSlot = null; - } + highlightedSubInventorySlots.RemoveWhere(s => s.Item == parentItem); + return false; } } + return true; } + protected static Rectangle GetSubInventoryHoverArea(SlotReference subSlot) { Rectangle hoverArea; @@ -1548,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) @@ -1591,6 +1601,19 @@ 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 { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs index 8e50e4018..900731c83 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs @@ -1199,7 +1199,7 @@ namespace Barotrauma return texts; } - public override void AddToGUIUpdateList() + public override void AddToGUIUpdateList(int order = 0) { if (Screen.Selected is SubEditorScreen) { @@ -1231,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) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/MapEntity.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/MapEntity.cs index 3664afb1d..642813f4f 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/MapEntity.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/MapEntity.cs @@ -1021,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) { } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/CharacterEditor/CharacterEditorScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/CharacterEditor/CharacterEditorScreen.cs index e46694429..0a553e0a8 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/CharacterEditor/CharacterEditorScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/CharacterEditor/CharacterEditorScreen.cs @@ -497,7 +497,6 @@ namespace Barotrauma.CharacterEditor } 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; @@ -505,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) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/GameScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/GameScreen.cs index da8e8bf52..9a0675524 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/GameScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/GameScreen.cs @@ -92,8 +92,7 @@ namespace Barotrauma } } - if (GameMain.GameSession != null) GameMain.GameSession.AddToGUIUpdateList(); - + GameMain.GameSession?.AddToGUIUpdateList(); Character.AddAllToGUIUpdateList(); } @@ -139,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; @@ -151,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/NetLobbyScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs index 093e093db..40b99af75 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs @@ -1737,9 +1737,10 @@ namespace Barotrauma 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); - 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); @@ -2624,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); @@ -2645,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; } @@ -2664,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; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Sprite/Sprite.cs b/Barotrauma/BarotraumaClient/ClientSource/Sprite/Sprite.cs index 5a9c265f1..ed81e93a2 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Sprite/Sprite.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Sprite/Sprite.cs @@ -169,7 +169,7 @@ namespace Barotrauma } else { - DebugConsole.ThrowError("Sprite \"" + file + "\" not found!"); + DebugConsole.ThrowError($"Sprite \"{file}\" not found! {Environment.StackTrace.CleanupStackTrace()}"); } return null; diff --git a/Barotrauma/BarotraumaClient/LinuxClient.csproj b/Barotrauma/BarotraumaClient/LinuxClient.csproj index 74dd10f01..0b19d09f4 100644 --- a/Barotrauma/BarotraumaClient/LinuxClient.csproj +++ b/Barotrauma/BarotraumaClient/LinuxClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.1500.6.0 + 0.1500.7.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/MacClient.csproj b/Barotrauma/BarotraumaClient/MacClient.csproj index aec74e517..9859d339c 100644 --- a/Barotrauma/BarotraumaClient/MacClient.csproj +++ b/Barotrauma/BarotraumaClient/MacClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.1500.6.0 + 0.1500.7.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/WindowsClient.csproj b/Barotrauma/BarotraumaClient/WindowsClient.csproj index 45c89c098..5a7a69f25 100644 --- a/Barotrauma/BarotraumaClient/WindowsClient.csproj +++ b/Barotrauma/BarotraumaClient/WindowsClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.1500.6.0 + 0.1500.7.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaServer/LinuxServer.csproj b/Barotrauma/BarotraumaServer/LinuxServer.csproj index 71e623332..9b9857398 100644 --- a/Barotrauma/BarotraumaServer/LinuxServer.csproj +++ b/Barotrauma/BarotraumaServer/LinuxServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.1500.6.0 + 0.1500.7.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/MacServer.csproj b/Barotrauma/BarotraumaServer/MacServer.csproj index bc313d665..99db95ee4 100644 --- a/Barotrauma/BarotraumaServer/MacServer.csproj +++ b/Barotrauma/BarotraumaServer/MacServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.1500.6.0 + 0.1500.7.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterInfo.cs b/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterInfo.cs index 04fa5c705..44df073ae 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterInfo.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterInfo.cs @@ -10,7 +10,7 @@ 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)) @@ -24,9 +24,9 @@ namespace Barotrauma } } - partial void OnExperienceChanged(int prevAmount, int newAmount, Vector2 textPopupPos) + partial void OnExperienceChanged(int prevAmount, int newAmount) { - if (Math.Abs(prevAmount - newAmount) > 0) + if (prevAmount != newAmount) { GameMain.NetworkMember.CreateEntityEvent(Character, new object[] { NetEntityEvent.Type.UpdateExperience }); } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs b/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs index f5c86cae2..65861aecc 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs @@ -430,6 +430,7 @@ namespace Barotrauma msg.Write((ushort)characterTalents.Count); foreach (var unlockedTalent in characterTalents) { + msg.Write(unlockedTalent.AddedThisRound); msg.Write(unlockedTalent.Prefab.UIntIdentifier); } break; diff --git a/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs b/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs index 796c72d08..cc969a0dd 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs @@ -1731,7 +1731,9 @@ namespace Barotrauma "givetalent", (Client client, Vector2 cursorWorldPos, string[] args) => { - Character targetCharacter = (args.Length == 0) ? client.Character : FindMatchingCharacter(args, false); + 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 => @@ -1845,7 +1847,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) { @@ -2259,13 +2261,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/Networking/GameServer.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs index 81e8e11ca..2486286e3 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); diff --git a/Barotrauma/BarotraumaServer/WindowsServer.csproj b/Barotrauma/BarotraumaServer/WindowsServer.csproj index 4dc1f8bf0..b5edb232b 100644 --- a/Barotrauma/BarotraumaServer/WindowsServer.csproj +++ b/Barotrauma/BarotraumaServer/WindowsServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.1500.6.0 + 0.1500.7.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AIController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AIController.cs index d6e3520d9..c5e455940 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AIController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AIController.cs @@ -103,6 +103,9 @@ namespace Barotrauma !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; @@ -412,7 +415,7 @@ namespace Barotrauma } else if (EscapeTarget != null && EscapeTarget.FlowTargetHull != Character.CurrentHull) { - if (pathSteering.CurrentPath != null && !pathSteering.IsPathDirty && pathSteering.CurrentPath.Unreachable) + if (IsCurrentPathUnreachable) { unreachableGaps.Add(EscapeTarget); EscapeTarget = null; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs index c1a35d954..332c4d3fe 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs @@ -453,7 +453,7 @@ namespace Barotrauma 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; + 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 @@ -916,9 +916,7 @@ namespace Barotrauma { if (SteeringManager is IndoorsSteeringManager pathSteering) { - if (patrolTarget == null || - pathSteering.CurrentPath == null || - !pathSteering.IsPathDirty && (pathSteering.CurrentPath.Finished || pathSteering.CurrentPath.Unreachable)) + if (patrolTarget == null || IsCurrentPathUnreachable || IsCurrentPathFinished) { newPatrolTargetTimer = Math.Min(newPatrolTargetTimer, newPatrolTargetIntervalMin); } @@ -936,8 +934,7 @@ namespace Barotrauma else if (targetHulls.Any()) { patrolTarget = ToolBox.SelectWeightedRandom(targetHulls, hullWeights, Rand.RandSync.Unsynced); - var path = PathSteering.PathFinder.FindPath(Character.SimPosition, patrolTarget.SimPosition, minGapSize: minGapSize * 1.5f, nodeFilter: n => PatrolNodeFilter(n)); - + var path = PathSteering.PathFinder.FindPath(Character.SimPosition, patrolTarget.SimPosition, Character.Submarine, minGapSize: minGapSize * 1.5f, nodeFilter: n => PatrolNodeFilter(n)); if (path.Unreachable) { //can't go to this room, remove it from the list and try another room @@ -2331,34 +2328,32 @@ namespace Barotrauma 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 PathSteering.SteeringSeek(Character.GetRelativeSimPosition(SelectedAiTarget.Entity), weight: 2, minGapWidth: minGapSize); - if (!PathSteering.IsPathDirty && PathSteering.CurrentPath.Unreachable) - { - // Can't reach - State = AIState.Idle; - IgnoreTarget(SelectedAiTarget); - return; - } } } else { // Outside SteeringManager.SteeringSeek(Character.GetRelativeSimPosition(SelectedAiTarget.Entity), 5); - if (Character.AnimController.InWater) + } + if (steeringManager is IndoorsSteeringManager pathSteering) + { + if (!pathSteering.IsPathDirty && 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 @@ -2510,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") diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/IndoorsSteeringManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/IndoorsSteeringManager.cs index eebf7b0ec..24491ff3d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/IndoorsSteeringManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/IndoorsSteeringManager.cs @@ -115,6 +115,11 @@ namespace Barotrauma IsPathDirty = true; } + public void SteeringSeekSimple(Vector2 targetSimPos, float weight = 1) + { + 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); @@ -164,7 +169,7 @@ namespace Barotrauma private Vector2 CalculateSteeringSeek(Vector2 target, float weight, float minGapSize = 0, Func startNodeFilter = null, Func endNodeFilter = null, Func nodeFilter = null, bool checkVisibility = true) { - bool needsNewPath = currentPath == null || currentPath.Unreachable; + bool needsNewPath = currentPath == null || currentPath.Unreachable || currentPath.Finished; if (!needsNewPath && character.Submarine != null && character.Params.PathFinderPriority > 0.5f) { Vector2 targetDiff = target - currentTarget; @@ -194,16 +199,8 @@ namespace Barotrauma SkipCurrentPathNodes(); currentTarget = target; Vector2 currentPos = host.SimPosition; - if (character != null && character.Submarine == null) - { - var targetHull = Hull.FindHull(ConvertUnits.ToDisplayUnits(target), null, false); - if (targetHull != null && targetHull.Submarine != null) - { - currentPos -= targetHull.Submarine.SimPosition; - } - } - pathFinder.InsideSubmarine = character.Submarine != null; - pathFinder.ApplyPenaltyToOutsideNodes = character.PressureProtection <= 0; + 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 (!useNewPath && currentPath != null && currentPath.CurrentNode != null && newPath.Nodes.Any() && !newPath.Unreachable) @@ -218,7 +215,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 && character.Submarine != null) + if (!useNewPath) { // 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. @@ -322,15 +319,26 @@ namespace Barotrauma doorsChecked = true; } Vector2 pos = host.SimPosition; - if (character != null && CurrentPath.CurrentNode?.Submarine != null) + if (character != null && CurrentPath.CurrentNode != null) { - if (character.Submarine == null) + var nodeSub = CurrentPath.CurrentNode.Submarine; + if (nodeSub != null) { - pos -= CurrentPath.CurrentNode.Submarine.SimPosition; + if (character.Submarine == null) + { + // Going inside + pos -= ConvertUnits.ToSimUnits(nodeSub.Position); + } + else if (character.Submarine != nodeSub) + { + // Different subs + pos -= ConvertUnits.ToSimUnits(nodeSub.Position - character.Submarine.Position); + } } - else if (character.Submarine != currentPath.CurrentNode.Submarine) + else if (character.Submarine != null) { - pos -= ConvertUnits.ToSimUnits(currentPath.CurrentNode.Submarine.Position - character.Submarine.Position); + // Going outside + pos += ConvertUnits.ToSimUnits(character.Submarine.Position); } } bool isDiving = character.AnimController.InWater && character.AnimController.HeadInWater; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs index 1f5d54c45..6bdddd863 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs @@ -724,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)) @@ -783,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, diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindSafety.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindSafety.cs index dc172e587..fe528d9dc 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindSafety.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindSafety.cs @@ -334,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); 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 ded911eea..f4e156d20 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) { @@ -186,7 +188,6 @@ namespace Barotrauma // Wait character.AIController.SteeringManager.Reset(); } - waitUntilPathUnreachable -= deltaTime; if (!character.IsClimbing) { character.SelectedConstruction = null; @@ -222,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(); @@ -325,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; + } } } } @@ -454,19 +461,25 @@ namespace Barotrauma } else if (!isInside && HumanAIController.UseIndoorSteeringOutside) { - if (character.Submarine == null && Target.Submarine != null) - { - targetPos += Target.Submarine.SimPosition; - } - nodeFilter = n => n.Waypoint.Tunnel != null; + nodeFilter = n => n.Waypoint.Submarine == null; } - PathSteering.SteeringSeek(targetPos, weight: 1, - startNodeFilter: n => (n.Waypoint.CurrentHull == null) == (character.CurrentHull == null), - endNodeFilter: endNodeFilter, - nodeFilter: nodeFilter, - checkVisiblity: 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) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveIdle.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveIdle.cs index 3be3dab7e..8d496ac2c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveIdle.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveIdle.cs @@ -251,7 +251,7 @@ 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 hulls on the way to the target diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveReturn.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveReturn.cs index 86756d255..be18f3e28 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveReturn.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveReturn.cs @@ -1,7 +1,6 @@ using Barotrauma.Extensions; using Microsoft.Xna.Framework; using System.Collections.Generic; -using System.Linq; namespace Barotrauma { @@ -69,7 +68,7 @@ namespace Barotrauma HumanAIController.ResetEscape(); } HumanAIController.Escape(deltaTime); - if (HumanAIController.EscapeTarget == null || !HumanAIController.HasValidPath(requireNonDirty: true, requireUnfinished: false)) + if (HumanAIController.EscapeTarget == null || HumanAIController.IsCurrentPathUnreachable) { Abandon = true; } @@ -92,14 +91,16 @@ namespace Barotrauma { RemoveSubObjective(ref moveInCaveObjective); RemoveSubObjective(ref moveOutsideObjective); - // TODO: Check 'repeat' and 'onAbandon' parameters TryAddSubObjective(ref moveInsideObjective, constructor: () => new AIObjectiveGoTo(targetHull, character, objectiveManager), - onCompleted: () => moveInsideObjective = null); + onCompleted: () => RemoveSubObjective(ref moveInsideObjective), + onAbandon: () => Abandon = true); } else { +#if DEBUG DebugConsole.ThrowError("Error with a Return objective: no suitable target for 'moveInsideObjective'"); +#endif } } } @@ -117,8 +118,7 @@ namespace Barotrauma float closestDistance = float.MaxValue; foreach (var w in WayPoint.WayPointList) { - if (w.Tunnel == null) { continue; } - if (w.Tunnel.Type == Level.TunnelType.Cave) { continue; } + 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) @@ -131,17 +131,19 @@ namespace Barotrauma { RemoveSubObjective(ref moveInsideObjective); RemoveSubObjective(ref moveOutsideObjective); - // TODO: Check 'repeat' and 'onAbandon' parameters TryAddSubObjective(ref moveInCaveObjective, constructor: () => new AIObjectiveGoTo(closestOutsideWaypoint, character, objectiveManager) { endNodeFilter = n => n.Waypoint == closestOutsideWaypoint }, - onCompleted: () => moveInCaveObjective = null); + 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 @@ -167,14 +169,16 @@ namespace Barotrauma { RemoveSubObjective(ref moveInsideObjective); RemoveSubObjective(ref moveInCaveObjective); - // TODO: Check 'repeat' and 'onAbandon' parameters TryAddSubObjective(ref moveOutsideObjective, constructor: () => new AIObjectiveGoTo(targetHull, character, objectiveManager), - onCompleted: () => moveOutsideObjective = null); + onCompleted: () => RemoveSubObjective(ref moveOutsideObjective), + onAbandon: () => Abandon = true); } else { +#if DEBUG DebugConsole.ThrowError("Error with a Return objective: no suitable target for 'moveOutsideObjective'"); +#endif } } } @@ -182,19 +186,11 @@ namespace Barotrauma { if (HumanAIController.IsInsideCave) { - if (moveOutsideObjective != null) - { - RemoveSubObjective(ref moveOutsideObjective); - moveOutsideObjective = null; - } + RemoveSubObjective(ref moveOutsideObjective); } else { - if (moveInCaveObjective != null) - { - RemoveSubObjective(ref moveInCaveObjective); - moveInCaveObjective = null; - } + RemoveSubObjective(ref moveInCaveObjective); } } usingEscapeBehavior = shouldUseEscapeBehavior; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PathFinder.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PathFinder.cs index f91b1d36a..57f37a140 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PathFinder.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PathFinder.cs @@ -119,7 +119,8 @@ namespace Barotrauma public PathFinder(List wayPoints, bool isCharacter) { - nodes = PathNode.GenerateNodes(wayPoints.FindAll(w => (w.Submarine != null == isCharacter) || (isCharacter && w.Tunnel != null)), removeOrphans: true); + var filtered = isCharacter ? wayPoints : wayPoints.FindAll(w => w.Submarine == null); + nodes = PathNode.GenerateNodes(filtered, removeOrphans: true); foreach (WayPoint wp in wayPoints) { wp.OnLinksChanged += WaypointLinksChanged; @@ -179,17 +180,37 @@ namespace Barotrauma foreach (PathNode node in nodes) { node.TempPosition = node.Position; - if (hostSub != null) + var wpSub = node.Waypoint.Submarine; + if (hostSub != null && wpSub == null) { - Vector2 diff = node.Waypoint.Submarine != null ? - hostSub.SimPosition - node.Waypoint.Submarine.SimPosition : - hostSub.SimPosition - node.Waypoint.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) + { + //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; } 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/Animation/FishAnimController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/FishAnimController.cs index 70c639da2..c37d0d8b1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/FishAnimController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/FishAnimController.cs @@ -374,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 { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs index 6c964f956..b9aa3d262 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs @@ -746,7 +746,7 @@ 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.5f, 0.5f), arm.Mass * 50.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 @@ -757,7 +757,7 @@ namespace Barotrauma if (elbow != null) { float diff = elbow.JointAngle - (Dir > 0 ? elbow.LowerLimit : elbow.UpperLimit); - forearm.body.ApplyTorque(MathHelper.Clamp(-diff, -MathHelper.PiOver2, MathHelper.PiOver2) * forearm.Mass * 100.0f); + forearm.body.ApplyTorque(MathHelper.Clamp(-diff, -MathHelper.PiOver2, MathHelper.PiOver2) * forearm.Mass * 100.0f * CurrentGroundedParams.ArmMoveStrength); } } } @@ -809,8 +809,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) @@ -830,7 +830,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; } } @@ -921,7 +921,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; @@ -1003,7 +1003,10 @@ namespace Barotrauma rightHandPos.X = (Dir == 1.0f) ? Math.Max(0.3f, rightHandPos.X) : Math.Min(-0.3f, rightHandPos.X); rightHandPos = Vector2.Transform(rightHandPos, rotationMatrix); float speedMultiplier = Math.Min(character.SpeedMultiplier * (1 - Character.GetRightHandPenalty()), 1.0f); - // Limb hand, Vector2 pos, float force = 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); } @@ -1013,6 +1016,10 @@ namespace Barotrauma leftHandPos.X = (Dir == 1.0f) ? Math.Max(0.3f, leftHandPos.X) : Math.Min(-0.3f, leftHandPos.X); leftHandPos = Vector2.Transform(leftHandPos, rotationMatrix); 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); } } @@ -1154,7 +1161,7 @@ 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); + 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); @@ -1383,7 +1390,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 diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs index b04947d5a..2a6ece320 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs @@ -1348,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) { @@ -1469,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); + limb.body.ApplyForce(flowForce); } } } @@ -1506,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; @@ -1587,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 { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs index 8d50256b6..dfc049300 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs @@ -1745,8 +1745,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) @@ -3670,16 +3674,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)); } } @@ -3927,7 +3929,7 @@ namespace Barotrauma } partial void KillProjSpecific(CauseOfDeathType causeOfDeath, Affliction causeOfDeathAffliction, bool log); - public void Revive() + public void Revive(bool removeAllAfflictions = true) { if (Removed) { @@ -3938,7 +3940,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; @@ -4284,12 +4293,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) { @@ -4376,14 +4381,14 @@ namespace Barotrauma public bool GiveTalent(TalentPrefab talentPrefab, bool addingFirstTime = true) { - if (addingFirstTime) - { - if (!info.UnlockedTalents.Add(talentPrefab.Identifier)) { return false; } - } + if (info == null) { return false; } + info.UnlockedTalents.Add(talentPrefab.Identifier); + if (characterTalents.Any(t => t.Prefab == talentPrefab)) { return false; } CharacterTalent characterTalent = new CharacterTalent(talentPrefab, this); characterTalent.ActivateTalent(addingFirstTime); characterTalents.Add(characterTalent); + characterTalent.AddedThisRound = addingFirstTime; #if SERVER GameMain.NetworkMember.CreateEntityEvent(this, new object[] { NetEntityEvent.Type.UpdateTalents }); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs index 79d154601..741f47897 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs @@ -1162,7 +1162,7 @@ namespace Barotrauma return (int)(salary * Job.Prefab.PriceMultiplier); } - public void IncreaseSkillLevel(string skillIdentifier, float increase, Vector2 pos, bool gainedFromApprenticeship = false) + public void IncreaseSkillLevel(string skillIdentifier, float increase, bool gainedFromApprenticeship = false) { if (Job == null || (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) || Character == null) { return; } @@ -1190,10 +1190,10 @@ namespace Barotrauma } } - OnSkillChanged(skillIdentifier, prevLevel, newLevel, pos); + OnSkillChanged(skillIdentifier, prevLevel, newLevel); } - public void SetSkillLevel(string skillIdentifier, float level, Vector2 pos) + public void SetSkillLevel(string skillIdentifier, float level) { if (Job == null) { return; } @@ -1201,19 +1201,19 @@ 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, float popupOffset = 0f, bool isMissionExperience = false) + public void GiveExperience(int amount, bool isMissionExperience = false) { int prevAmount = ExperiencePoints; @@ -1229,7 +1229,7 @@ namespace Barotrauma if (amount < 0) { return; } ExperiencePoints += amount; - OnExperienceChanged(prevAmount, ExperiencePoints, Character.Position + Vector2.UnitY * (150.0f + popupOffset)); + OnExperienceChanged(prevAmount, ExperiencePoints); } public void SetExperience(int newExperience) @@ -1238,7 +1238,7 @@ namespace Barotrauma int prevAmount = ExperiencePoints; ExperiencePoints = newExperience; - OnExperienceChanged(prevAmount, ExperiencePoints, Character.Position + Vector2.UnitY * 150.0f); + OnExperienceChanged(prevAmount, ExperiencePoints); } const int BaseExperienceRequired = 50; @@ -1295,7 +1295,7 @@ namespace Barotrauma return BaseExperienceRequired + AddedExperienceRequiredPerLevel * level; } - partial void OnExperienceChanged(int prevAmount, int newAmount, Vector2 textPopupPos); + partial void OnExperienceChanged(int prevAmount, int newAmount); public void Rename(string newName) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs index 6762db6c3..8a6f55e00 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs @@ -206,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; } @@ -629,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; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/JobPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/JobPrefab.cs index da1952365..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); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Limb.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Limb.cs index 4ece5fc65..9731b08d5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Limb.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Limb.cs @@ -759,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; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Animation/AnimationParams.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Animation/AnimationParams.cs index b469d98d5..ff46272dc 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Animation/AnimationParams.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Animation/AnimationParams.cs @@ -11,12 +11,12 @@ namespace Barotrauma { public enum AnimationType { - NotDefined, - Walk, - Run, - SwimSlow, - SwimFast, - Crouch + NotDefined = 0, + Walk = 1, + Run = 2, + Crouch = 3, + SwimSlow = 4, + SwimFast = 5 } abstract class GroundedMovementParams : AnimationParams diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Ragdoll/RagdollParams.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Ragdoll/RagdollParams.cs index fff53e186..ff4f961f9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Ragdoll/RagdollParams.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Ragdoll/RagdollParams.cs @@ -597,7 +597,7 @@ namespace Barotrauma [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).")] + [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)] diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionIsAiming.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionIsAiming.cs index 01de01b61..26a04a1a7 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionIsAiming.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionIsAiming.cs @@ -12,9 +12,12 @@ namespace Barotrauma.Abilities 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": @@ -28,7 +31,6 @@ namespace Barotrauma.Abilities protected override bool MatchesConditionSpecific() { - bool aimingCorrectItem = false; if (character.AnimController is HumanoidAnimController animController) { foreach (Item item in character.HeldItems) @@ -36,19 +38,23 @@ namespace Barotrauma.Abilities switch (weapontype) { case WeaponType.Melee: - aimingCorrectItem |= item.GetComponent() != null && animController.IsAimingMelee; + var meleeWeapon = item.GetComponent(); + if (meleeWeapon != null) + { + if (animController.IsAimingMelee || (meleeWeapon.Hitting && hittingCountsAsAiming)) { return true; } + } break; case WeaponType.Ranged: - aimingCorrectItem |= item.GetComponent() != null && animController.IsAiming; + if (animController.IsAiming && item.GetComponent() != null) { return true; } break; default: - aimingCorrectItem |= animController.IsAiming || animController.IsAimingMelee; + if (animController.IsAiming || animController.IsAimingMelee) { return true; } break; } } } - return aimingCorrectItem; + return false; } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionMission.cs similarity index 100% rename from Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionMission.cs rename to Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionMission.cs diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGainSimultaneousSkill.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGainSimultaneousSkill.cs index 5de3fb85f..43fef2a11 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGainSimultaneousSkill.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGainSimultaneousSkill.cs @@ -16,7 +16,7 @@ namespace Barotrauma.Abilities { if ((abilityObject as IAbilityValue)?.Value is float skillIncrease) { - Character.Info?.IncreaseSkillLevel(skillIdentifier, skillIncrease, Character.Position + Vector2.UnitY * 175.0f); + Character.Info?.IncreaseSkillLevel(skillIdentifier, skillIncrease); } else { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveMissionCount.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveMissionCount.cs deleted file mode 100644 index 629202f93..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveMissionCount.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Xml.Linq; - -namespace Barotrauma.Abilities -{ - class CharacterAbilityGiveMissionCount : CharacterAbility - { - private readonly int amount; - - public CharacterAbilityGiveMissionCount(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) - { - amount = abilityElement.GetAttributeInt("amount", 0); - } - - public override void InitializeAbility(bool addingFirstTime) - { - if (!addingFirstTime) { return; } - if (!(GameMain.GameSession?.Campaign is CampaignMode campaign)) { return; } - campaign.Settings.AddedMissionCount += amount; - } - } -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityIncreaseSkill.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityIncreaseSkill.cs index 59b532aaf..d93519de0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityIncreaseSkill.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityIncreaseSkill.cs @@ -49,13 +49,12 @@ namespace Barotrauma.Abilities { var skill = character.Info?.Job?.Skills?.GetRandom(); if (skill == null) { return; } - character.Info?.IncreaseSkillLevel(skill.Identifier, skillIncrease, character.Position + Vector2.UnitY * 175.0f); + character.Info?.IncreaseSkillLevel(skill.Identifier, skillIncrease); } else { - character.Info?.IncreaseSkillLevel(skillIdentifier, skillIncrease, character.Position + Vector2.UnitY * 175.0f); + character.Info?.IncreaseSkillLevel(skillIdentifier, skillIncrease); } - } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityRevive.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityRevive.cs index 317d12487..7ed61e90f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityRevive.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityRevive.cs @@ -13,7 +13,7 @@ namespace Barotrauma.Abilities private void ApplyEffectSpecific() { - Character.Revive(); + Character.Revive(removeAllAfflictions: false); } protected override void ApplyEffect() diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityUnlockTree.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityUnlockTree.cs index c9a5f9364..b9f26160c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityUnlockTree.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityUnlockTree.cs @@ -21,6 +21,7 @@ namespace Barotrauma.Abilities { foreach (var talent in talentOption.Talents) { + if (talent == CharacterTalent.Prefab) { continue; } Character.GiveTalent(talent); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityApprenticeship.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityApprenticeship.cs index c6f035c5b..95466f08d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityApprenticeship.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityApprenticeship.cs @@ -14,7 +14,7 @@ namespace Barotrauma.Abilities { if (abilityObject is AbilitySkillGain abilitySkillGain && !abilitySkillGain.GainedFromApprenticeship && abilitySkillGain.Character != Character) { - Character.Info?.IncreaseSkillLevel(abilitySkillGain.String, 1.0f, Character.Position + Vector2.UnitY * 175.0f, gainedFromApprenticeship: true); + Character.Info?.IncreaseSkillLevel(abilitySkillGain.String, 1.0f, gainedFromApprenticeship: true); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityMultitasker.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityMultitasker.cs index b22d35c46..339b5c47f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityMultitasker.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityMultitasker.cs @@ -18,7 +18,7 @@ namespace Barotrauma.Abilities if (skillIdentifier != lastSkillIdentifier) { lastSkillIdentifier = skillIdentifier; - Character.Info?.IncreaseSkillLevel(skillIdentifier, 1.0f, Character.Position + Vector2.UnitY * 175.0f); + Character.Info?.IncreaseSkillLevel(skillIdentifier, 1.0f); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/CharacterTalent.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/CharacterTalent.cs index 703a3c852..3a79e1b2a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/CharacterTalent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/CharacterTalent.cs @@ -13,6 +13,8 @@ namespace Barotrauma public readonly TalentPrefab Prefab; + public bool AddedThisRound = true; + private readonly Dictionary> characterAbilityGroupEffectDictionary = new Dictionary>(); private readonly List characterAbilityGroupIntervals = new List(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs b/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs index a73fb5af0..bc9d3ea89 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs @@ -809,13 +809,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); } } @@ -839,7 +839,7 @@ namespace Barotrauma NewMessage(Hull.EditWater ? "Water editing on" : "Water editing off", Color.White); }, isCheat: true)); - commands.Add(new Command("givetalent", "give [player] testing [talent]", (string[] args) => + 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; @@ -1886,13 +1886,15 @@ namespace Barotrauma } return; } -#if !DEBUG if (!IsCommandPermitted(splitCommand[0].ToLowerInvariant(), GameMain.Client)) { +#if DEBUG + AddWarning("You're not permitted to use the command \"{matchingCommand.Name}\". 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 diff --git a/Barotrauma/BarotraumaShared/SharedSource/Enums.cs b/Barotrauma/BarotraumaShared/SharedSource/Enums.cs index 619a77ea3..b6a3d3d83 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Enums.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Enums.cs @@ -119,6 +119,7 @@ MissionMoneyGainMultiplier, ExperienceGainMultiplier, MissionExperienceGainMultiplier, + ExtraMissionCount, ExtraSpecialSalesCount, ApplyTreatmentsOnSelfFraction, MaxAttachableCount, diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/GiveSkillExpAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/GiveSkillExpAction.cs index 8959f518f..2da290284 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/GiveSkillExpAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/GiveSkillExpAction.cs @@ -40,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/Missions/AlienRuinMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/AlienRuinMission.cs index ad82de75c..993b0cd02 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/AlienRuinMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/AlienRuinMission.cs @@ -165,12 +165,12 @@ namespace Barotrauma public override void End() { - if (AllTargetsEliminated()) + if (State == 2) { GiveReward(); completed = true; } - failed = !completed && state > 0; + failed = !completed && State > 0; } } } \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/ScanMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/ScanMission.cs index 410e7da37..c84e43647 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/ScanMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/ScanMission.cs @@ -18,7 +18,7 @@ namespace Barotrauma private readonly int targetsToScan; private readonly Dictionary scanTargets = new Dictionary(); private readonly HashSet newTargetsScanned = new HashSet(); - private readonly float minTargetDistance, minTargetDistanceSquared; + private readonly float minTargetDistance; private Ruin TargetRuin { get; set; } @@ -58,7 +58,6 @@ namespace Barotrauma itemConfig = prefab.ConfigElement.Element("Items"); targetsToScan = prefab.ConfigElement.GetAttributeInt("targets", 1); minTargetDistance = prefab.ConfigElement.GetAttributeFloat("mintargetdistance", 0.0f); - minTargetDistanceSquared = minTargetDistance * minTargetDistance; } protected override void StartMissionSpecific(Level level) @@ -86,28 +85,57 @@ namespace Barotrauma return; } - var availableWaypoints = TargetRuin.Submarine.GetWaypoints(false); - availableWaypoints.RemoveAll(wp => wp.CurrentHull == null); - if (availableWaypoints.Count < targetsToScan) + 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 ({availableWaypoints.Count} < {targetsToScan})"); + DebugConsole.ThrowError($"Failed to initialize a Scan mission: target ruin has less waypoints than required as scan targets ({ruinWaypoints.Count} < {targetsToScan})"); return; } - for (int i = 0; i < targetsToScan; i++) + var availableWaypoints = new List(); + float minTargetDistanceSquared = minTargetDistance * minTargetDistance; + for (int tries = 0; tries < 15; tries++) { - var selectedWaypoint = availableWaypoints.GetRandom(randSync: Rand.RandSync.Server); - scanTargets.Add(selectedWaypoint, false); - availableWaypoints.Remove(selectedWaypoint); - if (i < (targetsToScan - 1)) + scanTargets.Clear(); + availableWaypoints.Clear(); + availableWaypoints.AddRange(ruinWaypoints); + for (int i = 0; i < targetsToScan; i++) { - availableWaypoints.RemoveAll(wp => wp.CurrentHull == selectedWaypoint.CurrentHull); - availableWaypoints.RemoveAll(wp => Vector2.DistanceSquared(wp.WorldPosition, selectedWaypoint.WorldPosition) < minTargetDistanceSquared); - if (availableWaypoints.None()) + var selectedWaypoint = availableWaypoints.GetRandom(randSync: Rand.RandSync.Server); + scanTargets.Add(selectedWaypoint, false); + availableWaypoints.Remove(selectedWaypoint); + if (i < (targetsToScan - 1)) { - DebugConsole.ThrowError($"Error initializing a Scan mission: not enough targets available to reach the required scan target count (current targets: {scanTargets.Count}, required targets: {targetsToScan})"); - break; + 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})"); } } @@ -218,7 +246,7 @@ namespace Barotrauma public override void End() { - if (AllTargetsScanned && AllScannersReturned()) + if (State == 2 && AllScannersReturned()) { GiveReward(); completed = true; diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/CrewManager.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/CrewManager.cs index 49e0aa705..5aa20f202 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/CrewManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/CrewManager.cs @@ -489,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 49f175753..85a3ef2f4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/Data/Reputation.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/Data/Reputation.cs @@ -51,7 +51,7 @@ namespace Barotrauma if (reputationChange > 0f) { float reputationGainMultiplier = 1f; - foreach (Character character in Character.CharacterList.Where(c => c.TeamID == CharacterTeamType.Team1)) + foreach (Character character in GameSession.GetSessionCrewCharacters()) { reputationGainMultiplier += character.GetStatValue(StatTypes.ReputationGainMultiplier); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs index e526eebd8..a1dfda365 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs @@ -17,9 +17,7 @@ namespace Barotrauma public static CampaignSettings Unsure = Empty; public bool RadiationEnabled { get; set; } - public int AddedMissionCount { get; set; } - - public int TotalMaxMissionCount => MaxMissionCount + AddedMissionCount; + public int TotalMaxMissionCount => MaxMissionCount + GetAddedMissionCount(); private int maxMissionCount; public int MaxMissionCount @@ -36,7 +34,6 @@ namespace Barotrauma { maxMissionCount = DefaultMaxMissionCount; RadiationEnabled = inc.ReadBoolean(); - AddedMissionCount = inc.ReadInt32(); MaxMissionCount = inc.ReadInt32(); } @@ -44,7 +41,6 @@ namespace Barotrauma { maxMissionCount = DefaultMaxMissionCount; RadiationEnabled = element.GetAttributeBool(nameof(RadiationEnabled).ToLowerInvariant(), true); - AddedMissionCount = element.GetAttributeInt(nameof(AddedMissionCount).ToLowerInvariant(), 0); MaxMissionCount = element.GetAttributeInt(nameof(MaxMissionCount).ToLowerInvariant(), DefaultMaxMissionCount); } @@ -52,12 +48,21 @@ namespace Barotrauma { msg.Write(RadiationEnabled); msg.Write(MaxMissionCount); - msg.Write(AddedMissionCount); + } + + 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).ToLowerInvariant(), RadiationEnabled), new XAttribute(nameof(MaxMissionCount).ToLowerInvariant(), MaxMissionCount), new XAttribute(nameof(AddedMissionCount).ToLowerInvariant(), AddedMissionCount)); + return new XElement(nameof(CampaignSettings), new XAttribute(nameof(RadiationEnabled).ToLowerInvariant(), RadiationEnabled), new XAttribute(nameof(MaxMissionCount).ToLowerInvariant(), MaxMissionCount)); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs index afdaf91e6..9523e5299 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs @@ -661,10 +661,10 @@ namespace Barotrauma public static IEnumerable GetSessionCrewCharacters() { #if SERVER - return GameMain.Server.ConnectedClients.Select(c => c.Character).Where(c => c?.Info != null); + 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); + return GameMain.GameSession.CrewManager.GetCharacters().Where(c => c?.Info != null && !c.IsDead); #endif } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/EntitySpawnerComponent.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/EntitySpawnerComponent.cs index f73cebac4..ec5f6b078 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/EntitySpawnerComponent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/EntitySpawnerComponent.cs @@ -49,13 +49,13 @@ namespace Barotrauma.Items.Components [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")] + [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 = 0), Serialize(8, true, "Amount of items or creatures in the spawn area that will prevent further items or creatures from being spawned")] + [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")] @@ -110,7 +110,12 @@ namespace Barotrauma.Items.Components if (GameMain.NetworkMember is { IsClient: true }) { return; } - SpawnTimerGoal ??= Rand.Range(Math.Min(SpawnTimerRange.X, SpawnTimerRange.Y), Math.Max(SpawnTimerRange.X, SpawnTimerRange.Y), Rand.RandSync.Unsynced); + 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; @@ -120,12 +125,12 @@ namespace Barotrauma.Items.Components 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) { @@ -135,6 +140,9 @@ namespace Barotrauma.Items.Components case "toggle" when isNonZero: CanSpawn = !CanSpawn; break; + case "trigger_in" when isNonZero && !isClient: + Spawn(); + break; } } @@ -163,6 +171,8 @@ namespace Barotrauma.Items.Components } } + if (MaximumAmount < 0) { return true; } + int amount; if (!string.IsNullOrWhiteSpace(SpeciesName)) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/MeleeWeapon.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/MeleeWeapon.cs index 05b87be87..299afe98d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/MeleeWeapon.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/MeleeWeapon.cs @@ -59,6 +59,7 @@ namespace Barotrauma.Items.Components [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. 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/Machines/Deconstructor.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Deconstructor.cs index 5efe930de..e3eb12bcd 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Deconstructor.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Deconstructor.cs @@ -200,7 +200,8 @@ namespace Barotrauma.Items.Components void CreateDeconstructProduct(DeconstructItem deconstructProduct, IEnumerable inputItems) { - float percentageHealth = targetItem.Condition / targetItem.Prefab.Health; + float percentageHealth = targetItem.Condition / targetItem.MaxCondition; + if (percentageHealth <= deconstructProduct.MinCondition || percentageHealth > deconstructProduct.MaxCondition) { return; } if (!(MapEntityPrefab.Find(null, deconstructProduct.ItemIdentifier) is ItemPrefab itemPrefab)) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs index 528dee25a..461ae0617 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs @@ -391,8 +391,7 @@ namespace Barotrauma.Items.Components user.Info.IncreaseSkillLevel( skill.Identifier, - addedSkill, - user.Position + Vector2.UnitY * 150.0f); + addedSkill); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/OxygenGenerator.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/OxygenGenerator.cs index 8e836bcf1..60c93f2a9 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 { @@ -64,7 +67,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 +78,8 @@ namespace Barotrauma.Items.Components private void GetVents() { - ventList = new Dictionary(); + ventList ??= new List<(Vent vent, float hullVolume)>(); + ventList.Clear(); foreach (MapEntity entity in item.linkedTo) { if (!(entity is Item linkedItem)) { continue; } @@ -83,30 +87,39 @@ 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)) - { + 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: 5, 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/Steering.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Steering.cs index 0fdba45ca..44e6461b3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Steering.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Steering.cs @@ -392,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/Projectile.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs index 0cfd93de8..f44619825 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs @@ -340,7 +340,7 @@ namespace Barotrauma.Items.Components item.AiTarget.SoundRange = item.AiTarget.MaxSoundRange; } - item.Drop(null); + item.Drop(null, createNetworkEvent: false); launchPos = item.SimPosition; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/RemoteController.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/RemoteController.cs index 1903a74c0..783253c89 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/RemoteController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/RemoteController.cs @@ -35,7 +35,6 @@ namespace Barotrauma.Items.Components public RemoteController(Item item, XElement element) : base(item, element) { - DrawHudWhenEquipped = false; } public override bool Select(Character character) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs index 041037cdf..d36c11e6f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs @@ -440,8 +440,7 @@ 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); @@ -472,8 +471,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; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/TriggerComponent.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/TriggerComponent.cs index 197bdd2c4..10ef02bdb 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/TriggerComponent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/TriggerComponent.cs @@ -159,6 +159,8 @@ namespace Barotrauma.Items.Components ApplyForce(i.body); } } + + item.SendSignal(IsActive ? "1" : "0", "state_out"); } private void ApplyForce(PhysicsBody body) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs index 39d5a30ae..327c23181 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs @@ -438,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); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Wearable.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Wearable.cs index 00a144ba0..2b4190bb4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Wearable.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Wearable.cs @@ -46,6 +46,7 @@ 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; } /// @@ -176,6 +177,7 @@ 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); var scale = SourceElement.GetAttribute("inheritscale"); if (scale != null) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs index 2d5079340..33a9253af 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs @@ -1803,7 +1803,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); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs index 3378966a4..122eebb5b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs @@ -314,6 +314,8 @@ namespace Barotrauma /// public bool IsOverride; + public readonly ItemPrefab VariantOf; + public XElement ConfigElement { get; @@ -783,6 +785,7 @@ namespace Barotrauma } else { + VariantOf = basePrefab; ConfigElement = element = CreateVariantXML(element, basePrefab); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/RelatedItem.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/RelatedItem.cs index c2dd49b6c..ad9b19866 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/RelatedItem.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/RelatedItem.cs @@ -36,6 +36,8 @@ namespace Barotrauma /// public bool ExcludeBroken { get; private set; } + private bool allowVariants = true; + public RelationType Type { get { return type; } @@ -82,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) @@ -168,7 +170,8 @@ namespace Barotrauma new XAttribute("optional", IsOptional), new XAttribute("ignoreineditor", IgnoreInEditor), new XAttribute("excludebroken", ExcludeBroken), - new XAttribute("targetslot", TargetSlot)); + new XAttribute("targetslot", TargetSlot), + new XAttribute("allowvariants", allowVariants)); if (excludedIdentifiers.Length > 0) { @@ -231,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)) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs index ba3774838..47093bcf6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs @@ -934,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); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs index ac3521e32..c0e618e9d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs @@ -1888,7 +1888,10 @@ namespace Barotrauma { 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); + 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) { @@ -1907,7 +1910,10 @@ namespace Barotrauma 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); + wayPoint = new WayPoint(new Vector2(waypointArea.X + waypointArea.Width * i, y), SpawnType.Path, null) + { + Ruin = ruin + }; wayPoints.Add(wayPoint); if (y == waypointArea.Y) { @@ -1940,12 +1946,33 @@ namespace Barotrauma //connect ruin entrances to the outside waypoints foreach (Gap g in Gap.GapList) { - if (g.Submarine != ruin.Submarine || g.IsRoomToRoom) { continue; } + 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; } - var closestWp = FindClosestWayPoint(gapWaypoint.WorldPosition, wayPoints); + + //place another waypoint in front of the entrance + Vector2 entranceDir = Vector2.Zero; + if (g.IsHorizontal) + { + entranceDir = Vector2.UnitX * Math.Sign(g.WorldPosition.X - g.linkedTo[0].WorldPosition.X); + } + else + { + entranceDir = Vector2.UnitY * 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; } - gapWaypoint.ConnectTo(closestWp); + entranceWayPoint.ConnectTo(closestWp); } //create a waypoint path from the ruin to the closest tunnel diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelTrigger.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelTrigger.cs index 3b401aedb..71a30f339 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelTrigger.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelTrigger.cs @@ -666,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; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Map.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Map.cs index aac3655eb..53342d1aa 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Map.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Map.cs @@ -495,7 +495,7 @@ namespace Barotrauma float GetLevelDifficulty(float areaDifficulty) { const float CurveModifier = 1.5f; - const float DifficultyMultiplier = 1.1f; + const float DifficultyMultiplier = 1.14f; const float BaseDifficulty = -3f; return (float)(1 - Math.Pow(1 - areaDifficulty, CurveModifier)) * DifficultyMultiplier * 100f + BaseDifficulty; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostGenerator.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostGenerator.cs index 224b41e02..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,15 +85,19 @@ namespace Barotrauma var subInfo = new SubmarineInfo(outpostModuleFile.Path); if (subInfo.OutpostModuleInfo != null) { - if (generationParams is RuinGeneration.RuinGenerationParams) - { + 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); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs index e53436daf..5ccf84245 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs @@ -1177,8 +1177,7 @@ namespace Barotrauma if (damageDiff < 0.0f) { attacker.Info?.IncreaseSkillLevel("mechanical", - -damageDiff * SkillSettings.Current.SkillIncreasePerRepairedStructureDamage / Math.Max(attacker.GetSkillLevel("mechanical"), 1.0f), - SectionPosition(sectionIndex)); + -damageDiff * SkillSettings.Current.SkillIncreasePerRepairedStructureDamage / Math.Max(attacker.GetSkillLevel("mechanical"), 1.0f)); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineBody.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineBody.cs index c24088ba5..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) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/WayPoint.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/WayPoint.cs index e335f0eec..de504a73e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/WayPoint.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/WayPoint.cs @@ -46,6 +46,7 @@ namespace Barotrauma public Hull CurrentHull { get; private set; } public Level.Tunnel Tunnel; + public RuinGeneration.Ruin Ruin; public SpawnType SpawnType { @@ -1097,6 +1098,7 @@ namespace Barotrauma CurrentHull = null; ConnectedGap = null; Tunnel = null; + Ruin = null; Stairs = null; Ladders = null; OnLinksChanged = null; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/Message/Message.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/Message/Message.cs index 069c59aed..deb765620 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/Message/Message.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/Message/Message.cs @@ -238,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) @@ -672,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; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Physics/PhysicsBody.cs b/Barotrauma/BarotraumaShared/SharedSource/Physics/PhysicsBody.cs index 85868e116..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); } @@ -789,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/Serialization/XMLExtensions.cs b/Barotrauma/BarotraumaShared/SharedSource/Serialization/XMLExtensions.cs index 262bbb9c4..c66595a6c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Serialization/XMLExtensions.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Serialization/XMLExtensions.cs @@ -32,7 +32,10 @@ namespace Barotrauma { typeof(Rectangle), (str, defVal) => ParseRect(str, true) } }.ToImmutableDictionary(); - public static string ParseContentPathFromUri(this XObject element) => System.IO.Path.GetRelativePath(Environment.CurrentDirectory, element.BaseUri); + 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 { diff --git a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs index 2c8cf176c..0100a166f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs @@ -1290,7 +1290,7 @@ namespace Barotrauma Character targetCharacter = CharacterFromTarget(target); if (targetCharacter != null && !targetCharacter.Removed) { - targetCharacter?.Info?.GiveExperience(giveExperience, popupOffset: i * 25f); + targetCharacter?.Info?.GiveExperience(giveExperience); i++; } } @@ -1309,7 +1309,7 @@ namespace Barotrauma // don't let clients simulate random skill gain continue; } - targetCharacter.Info?.IncreaseSkillLevel(GetRandomSkill(), amount, targetCharacter.Position + Vector2.UnitY * (150.0f + i * 25f)); + targetCharacter.Info?.IncreaseSkillLevel(GetRandomSkill(), amount); string GetRandomSkill() { @@ -1318,9 +1318,8 @@ namespace Barotrauma } else { - targetCharacter.Info?.IncreaseSkillLevel(skillIdentifier?.ToLowerInvariant(), amount, targetCharacter.Position + Vector2.UnitY * (150.0f + i * 25f)); + targetCharacter.Info?.IncreaseSkillLevel(skillIdentifier?.ToLowerInvariant(), amount); } - i++; } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Utils/ToolBox.cs b/Barotrauma/BarotraumaShared/SharedSource/Utils/ToolBox.cs index 8d727bee0..de832af9f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Utils/ToolBox.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Utils/ToolBox.cs @@ -633,7 +633,12 @@ namespace Barotrauma { 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("//", "/"); 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 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


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

^C)Z> zGV`scS)m6|OSBmisCOAG=rWAj8hKD})vOuUa#>&K`nd* zv6u}%d2XUz7_O}Hp{93eA}aFe9Iw*&P}NckfQy3V;GI`WodSvC3$1C~3^;gf7W;~H zv698+6%b}mbzD9wQ`(H2kfava;gAoSvI~4ZePR>0aVsQ(c38wkNdstyY(uFaS>H#B zLW>*If*tBlzu7{#f|j=w<@#3CQefVOEPt{C{5>sLhYy=2Vjow;;ZD7>3aI5LiEhhTB_5oe!==~$+1|2 zEEAFCwf_Z(gqrJWmNX0r)f-qZYXwcVWlJN7!1K1Rh?svY_FJu<$;!V^=fSmU^teC` z@Tu7sh4DhPf}4Z5Eb)vctGeS=|Ev`_cn`0?xPb+4y;!ZTtm=R$vkLwYO2wXHh{7I- zRGMkwHov(l*?b>A>9x7hw|{rv9UH{A^fr$+?S>;#BQ$9^j2ZW`CnO|v&uSVxf zy2RUlX9*7j)oj{7!uWgLca%QqizJWj|W^ZD8Xi^_9Xi> zBl>h=Y`@SE9S-Z)!r2;4H{f+6QNh1|d;76H=k?GbvmL1>iRWCs00etpx2GYYli&v*7om;3@C4945# z^xV8QIa30dP_8jc{}9z8)O=w$de#pen&Er=p$@{DUsyCH{EE zdIG-m{$}6K%O4x(vglLrk()Ic_==$?IjTco=SSXdWo2&9L={HNWfZTGmUBFoCJ9ip z0}?p~v*hs~hizuiH0K++jVt^A=DKPx#TP>t5@=-~V{hrCxtHw|2d~JO1?p6;H6~L% znT6fi2?kzRwsdE$(hC3=)zSn4F7+)EPa#uvj}L*L%$W;pziy!b10PMo#E+bRdb3cg zH}eJE%K-jJ0l_eJdKVnJaGXFoM*w+G{@)q01tx930#=|~Xt&I`=qFW38l#(|VG+(R z!ciEOUtYL%_%d_wK}sBVWbrs_lH!IYQPy+$TCuxv_i zJj~?!C(?&!{$SgLdR5pbHb{ozhB`D^G~sS%5u~0>^0Pd7OJ_nMv1U>d6BO_-cM8g} z0ikkyqPJ-QK^1+c;u`29VKY(cMrrq&414YoQw6&b5sQ~xxMX$BL4{hH@eMys1fDsF z!kUj*1^}tJpWU$wU6d+Kr<{4%_>8q^JEEkGGp(3R)P8H;v$-xVkbQ4m7R}qW;1VKM zbIZ~=o{p!uzyekg#4d&cl>j?H#J|eZI9xTAhQ#R}Z+D|92OYsBuC$-~Zo7*4kM#|* zV@k`%7NqzpBO5JCVYY~LJGw_f1WkYSS+BC+4puXM-D*^$m+y^ysKZav=v}Dq z-&e2mWOfM{P10cH+sTFBMzgu|I?^A{d62`pOHdu9iD&OdGtVj4}If|^WC5+h(k2F$cG!2Q> zszRuya{NLZGW)BD6&SnMAX^f^ncyUwvLlOD+n}YBxd>-FP_yCSZU1!bJ#=$lnIG(& z*~!*}oa8@bf4l*=_bxGh9o!pNRBn(x+@}yH0Ugqw#M$$CR?;g<$OTrR%SXY1aUK)O zZ=YZK`(tF)=Rjc#GhP_a^Ig}W`xF-~6A!TTj7EuQiwLEX#)GjCu zqVHm2a?O{-;y%|x62a0$H1sk=L#&DP4k&(6O`){>?6|Oh+-?ba(gWAI{yp(I9O_5% zJ8pU9l+Y1P6G-3g6TxaE@{w+~%49dXRO!_4;wdQ#}Khrytza zxSmoFdMLS^jliUaLPMCG{r=?)r-~8atX9$|%4~sXji?DI16d~W%?);}o_=(VF}P$1 z{!F8Uw)Y5GS3Tj$J?f5+M==e(PMSHUL*?j0d zR!rtO%j-nPet^WFx!3H{$^mk{9s=s}2ka_04^N1P7&eyApJ!X&9zzm6{o-_KJ|k=0 zvl9xPgAE;95d6cbNw@1H4vIFTny4HFD#X|3fVTIFZ|U0zg<;c~|9W={c$0W`ru>corD|dWSv*i^!yz7lmC)=^N zwD#e#wX?4o`jV#p`LdlAyyN`o@)~3VKiiEQA==_owp78d{kSZ>BL1Mnp8U8Ven%~j zBG%BpRoH%OT9vqdy^80f8z5A}FvFz_oTR0vjDHeXck@P_NB>5)2C4OtN+R-#<$J;T zfo5PZmqX&!16h*)9TNOnUjoLFHE1BLnA6c>eX$Y|8T@wWTK@eO`%U@PlbMAp*^VrX zD-ajnk7Vco{-|Yz@o8m7P$W**&r%&QDSw*?Cch<<6E7->$)_gYvQPya{=YO&NzfT0 zm2d_@#6Az2Va%~v^oewoub8&1>z`A3-237&->}_)T`Mui^FE2yBGF&p1e1Kgq$y3D zKm&hHhL}D|nfFBFe-EB-2PUamUueXD5j!eRhM*EU?6Au$UBn!2d3dxx(Z_QrqQn4w zqxl1FfGF1Sp#iv=q+6u9%{rgR4t*Bq=;g))o~W_6}~M@iy3#CFX5gqBp(`GySwhD#N=TqAMO;UM-E>Z-gr0$F4jZDNGJs!J%e z;g!_aY>FUAvJzYb&lMEBm}3J=zW0=C(gS-%D&QM>he%%^-}(B1r$L%j58Uwk zkh+p9g80Fbjsn@(Y4V%}63F(KR4M>zqB}LLx!bTYYWkos&+&5+h0?)XS^nE;48T=l zR*=iZ!=jT_7}jRF0S#^sl4U#c^4JXDJU3&de&V2m@gh{3B*djYXssUWmJRBaejrMrQ8?%80~`#9#;Y?qaN|Rj`1qVmu)(=~Us}_)dA^gU^?B zCYa{;Xo1Uxk)fLRe1qmX+T<^`ho-PK9U>L{1sp4)C$H-0L`(S;B8Iy zr8_rz_i(o&L)&z*T~!h^G@O|^Iek!|WJ%}&OoD97_|rwfDAnN}J6_V52X>7IAQkaP zqe+<3X%guEKd_c-`DZdk1>}v603Cf!=`ioR7r0HL8(gY&l>6`N)Oi?6fe?&i_;kDc z5Y!dK7Z5f3aZZN!UKCjan+BYI9puHpOsB8#XJ_I?-iNE?f^2(o@OCOJbxYkC)CUY? zajibuBbQ*4r@kfsC_)l{Ri|K(k4luWW8Sjp-mLz4P8%D0gZ~Vl$9@eE3crbO%`H~s zsg1k*s)G{xxr9=(6I15@2(U9^;Zbs>11LCRWd4L}>Rp}{BA1Q)@6tj<5OlMMPyBzk zI86rUzXT!+^-luj{m1~!owh-h`eij8gqCo`JC8|AnGOmB$l%;7s16%Wq@r+|8y18I zhHd{so;<*DV-0bmXNku_vI{K)!E1rj+ZN*Sc?_fZJ+Od4I-`9FL_#s4 zqqB}Y2-(9Qsxb|(*8&tXaO)-J0@}qVGxH3%yd6989~kHr7D>F%tn0GjMvY&`mjE5h zz^S<(u`;nOgO3-6n9?dU&k~*lT+n4|5CL>=0y@t(x_{961qMDwFMnB>DP##gk?09T zzChS&yyMoJ7W5P02{!_4Sr&KEHsD3w_Y{we+601OYi*qJUYVd^5A?E)uJTWbOTn1v zsb~~M-b2QW%HCaVvA;RE04IO*cajV(wFZNx0%8^J}$d5IE12Y6z-ADSvetB47?9>Ka z1D*a)(c6_Q51T|iEpQWrM+g(fax0{wjBsFu=4%=nfO+8%i<#tTYrj58Lq%WaxxcnU zko{NNI|C6l=cNN-{JrmTi8EW+$AF7EB~TZdkiqQhD#I>d&4)^xFkA{u`nE3o{@^rJ z?L}E$glWRs3vH)#`@C$F7Ou2zT$--h7UJqR1oPm}!j;F%Z2=cAa#X(b2KczUJDyl@ z%7=!+s0@?;<9Mz^f-(xlfb#W`-$*#eLoqy%V!fyN%yX5_8GT%FWZIMO#3meQGh%Ba zoXt=5?h{ry`ipbP-#i0!r-3XdZ}m@GQ69M%Qy@niGRp@@820*FU*$G!u73|MA$l0? zqJ&brQO-io?6zT--7#}#krMO35D4qFv?yB66Ho}6O>a*ZnU-8c-EYi{b?$oL1AHB@ zfC32aa<5iWy_A!Dmzh#`d>9%_$(>O*HaQ z6#pie|L@PE0rlL?pdTPGCO&0DlG%q&GGxfxLIjK3Te#Y#4MUUnCBMR4%}C7T_3uzu zOvg`Q7&x~LfHTuF5?12pl+uhI(^7xYPy?5MV72A%2G8M^H{AZVZ$(27kv!B2NGMmu zOr%6BrI+gm8S!RORuQUczy0MmEB=Fjzi;uwI3B$S`M5<@EIV!wlh?b@U6}@}RmauH zRNQ;*Aam;I^d#;PnN^UZOuz1-ReIyW?z!2Zau^v-&soeUPqCBu`ys}m$3#=j@qWP{ z&~aY)!bV%cj7oVB){V)Tdz_E2bE?*1ctTR;2o1<{ix`aUIn}}`P^ynS&_#xNL(EaU zfRh>kpF(3Ugn*zk-x1gP<=DRl9LP?}Im{uh7BRzy{*@rR|2z%tFWAcs-o_Wo%d}=B zjlPW*I{{;QQf$_H&91tF`Mn$0y#LSJd<>TW%V|BPiljA1Y*_{Q;Ke)$aIl=@I2e1k z9>1Um(wh^dhVLr!)%s8SMr9)L4%vqlgsHsRZQIVcZo_)I4-g_tFX`nAt;Rca>(^?& zTx{y#pl2X^a31(q;s#ak1kmaPo-%?THn?yLWHG@dZ1Y>>-QHu`ii*A`=wHf+6^jD4 z@dS?`F$%mAv)it#&iD$Tx_Mp{XPe_(NaQccZGAC;v$?#)!>uKU~*(4>k2B0w>I*G&g+X-;KXIBZOl*DHkn#T@-otNH;bGi($ zKnTYAzFCp$SoEh@>NX2WsmUpm(ESbF5=P(0DS`bM)`cF}p3Hf2TFw6l>Ja8WO<{m`i z%LuyHX=vXmyX=xH@ymE)Gkc3OzpWj(Hwkij=RNli>RyXr{cIliuX#%7WZ9#U?j)US zn-sNUad>f-`{W)8VyRY|P~ZTgg`LbGN~s{A+^A?W1Ec2KIApkXIBAYuhAOsK$w$&g zw2cSEO@0t+ZCTxjMUd#kdO`rHumlreXld#l@y3lN2cu1_)Dzz-+6TmY5#jJk=Z22d zX{ngC(THe9t+i#u76CVz<5UFE>d0n28gpkFUcxX0;OlrFtt9^5gTm$@q#)s)roErP zd9R|l(H_{@x`L3g*|Y5X-MmUuYeqFc$4srPNRXE=Mwa_x|Ji2bZ=x#@uVCy9!wV7t? zHYow+id}KES8Tar3vk8F!!_Pn*`_J6Acsd%%YXz*5DFISp7q&YOhuTg17wMBknMyU zv`X{_zi2A<0Q`|Bzc=h_+N(8X-~s+3;~Q;+{``vP5*M^5-jD%?w(Q0T>~YPEhTL@M z9UkM$q{0el{5Iz+8n2H30h$s;?au;jCyNubwXW7Hz0|bjW!{0g6uL3j#?XDcr%Nkz z4}9PCt}_znLc1ziM9!s)qoHJy}q@iV-*+2P0Dn{Y;_3a0 zChJyfUgZ5LD;V9%Jzs2y%8TTvUb)}5o9G?hB}5O?SE_^o#u!8D zeX8`dfRF(^^z#UDA9)@~lyRJL6A-CMX4`~L89G?{o7xpf%p+vOdl$6Vu`zSM`3tC~3LGH49K1imcbA^Ic3B@DrO)^&`-!Toww& zp;0{a`uRax#cMRvAh6e<;I}%mlo8@Q(7i1KZzwXhg=;QhRq4p>34#ztVmOKO-DYw2 zKrF*bNgYl8RoK!^6C@hY#AC1t*9|+dTIGD5ObC4TEyvh8i1pG}{QfoVufFblOK%?( zMEZP$2sE=;KBOZDSY%wcJ;jA0DL-y3B_6(VfPM`vt5YCjo17whN>fE1KrKkQaflZkz!#NYS9$wWZBD_P*vP*U`CJSJ2@7Gx-A+0G9O@P{M=XPax-8_3X_0!Dc7K9GxDlK=TlD7apFRC@Z+#1 zf$5N>QVblVJ3Fy;B2plR@$Pe+YrNjam3m=Ft{WKZv@+qg4><&2cWV}p!eCeqARyja)DY)=xgwqZ(6Fh-$Upt6Ao-O=Kg_N_dri-EVW1bwc++J4cvY!R0guC?3W^uWsZhy>zQ#%U#cWBQ9PtpHlYwQ^0 zvt~M)0CPQ6*D`k=L>iir#!;X+9j7T%tmqq1Zid+kAd_%l@~Hh1rW2Js6EyzqK<$X7 zUT`_+xKGREDwa1w{B@Lg3>7RIQ2`-b4@myBy83F^){zf+{SI{xl#h~}-t&w|+Xqcw z0-P<%U%Dz^%QHh@7K{uSf1We^2X{Ev4u_4+BAA<*rhuGEiet+z`*4-PdihRR4Ltm* z@^Hp86>Wxjqqz)lIcv8jM#z}#t%|+OmG)(n{Rwkm;k0U$Fw27hUI#Alro$_W@1#_( z?ALt=gwpq^?+SJ1fU|BnemgWeQ=R3SF$z34?9(D|0`)%cNXfIFSW`a3w2E^-$6}2JydU4Orit&spML6U)G0u`5|FCC$%@UFt&ysFgb#ioKtO#Cc55CpI z-tK_)Les>3g)VCu$Q&X{irOz3eZ*NnerSH-@bh> zMB@&|^U}OoXrR2{Pz3J<*q^LbrQ#6pC`_K%0fG*e#C%Z`NC{sLCH2lurk%78U_iCvs%WxWmm;c$4RBUXQ#$qW zX6-7Q$$qgxN))V!M%3+s8NlDpy~IO20t89o7TufEp{dxL8B(~E8+XnUih|L`NODXN zCFZf;C)stUu^%eO;WI>?MK8p|v1@4HdtZJTnI#CKe!nE6VLt}4_A`qX^ZjK2q-*}a zN|Ynl@D@PGQ2m(njzk?Q;iLSJR+mJIZ>tw=u%UDSY1OTfPkH5d6nre04RZ^|TwSinma_N= ziA1;*P#OrTc%6Y?9(@6LVRNte{jjg(Ye~do8F(xNQLb`?Vlt(~0%c*)h66Z-h~)D9 zr<{OO@cgv2G)E#4S_b7a6M+USNZS8b?x4yjAKM_~iU&I26z@P7 z>JAtqQ{Ngdcdxg?h03>ixM%c4HjM*O%U%D;SsYD3RDPS`7YCPC?DHK2_40Wo!aIi@ zS6o}3-o8NH{0J(DxWbnnqNJ7N1UoKcIiu4MlRf5hqs3vN?;DHf98?zoTQ(f_JO?5= zzqFe@^si`H%k|K44pExK)hFHYUTL zxRwPRU6@%`aM>ztp0~t)ijT*8IZ4j0X~?wRL}r^uy$T|drlk`1`5G!T&edpdh;cCK zXP?)CM?p~;KN9qmpee!z`3`u}A)`7Fl5YpzOop9t4rfeREvnBJKJM%NraU<-G7umb zYG0s!=>tXZx66~Cu=B-zaj2AOW{Qpv$aq$Js_oW8n<-C&-H4xZ<&7%J04e|rFe#3 zb?E1d^%U;bBOhccoPmlI5Dwe&6eagL;9CynE$fg_@ipmL1ON$$!a-`gJs>6nGgeIg zt`Ji-t)ZE&FHc37FC&i2L1n-i!La+U?Kg@eKSO`JNPP~i?V4cQiT4l~Z(gkB#tlthoMa1k9FC&IA?T=ebQL*CDFB~6s4dw165Rsm=QSlj5i64O2#Rr}CdWEB6{Y7*Q!AjB_I?De<5 z9B&!if5cNH_xB?%8Py3zzojwyK>hpIU*c%32mS`hqnq@3oAJ#G+=mGC?%}3@6i&Wk zG0jC{AcJ3e*Ee_0$?db>-|<_qv>gZka3!1pi{E_#AOP}#tAuXhJ3x1UINVxB9LZKI z4su9M#D4%tnjQB#f0hDs4*0jcJO1Wr?0^TyK4aLZ=@PM&aihJnKoZ^+h^RMTtO;JQ z_c2OYM)*2(lp!)uZzJk^#xFL#KrT?rwsFg@IKY4xZXCYZdHh6HUW6Tke-?xG?$%t z!T(?Wv_37Ma9+carGb++Q zFCv2OxvIbPZle4JgKN{(CH!!~@>FX4Jhv*eY$XN3QIF%rpLVs7fj!*aO>v*${}4DV zz@`LdXlxjgqR_SmHSt{8@E34Zg@I6E^-C*;fsD@()}Q(O(G+vhOZOGg6Q_kZIhPE2 z?uZq3;UhpOFU0$`QyN`BN}yqq%LQLl%fZ#5;trIAEqP$128eJp%{#6jv0(bZ2yZ~f zk0^s%8mJM(^Z5Kv4i8Fa%zXo4SuOEhu$Zk0oiLGFMcUy2^G5<;-r&hku1=Ye-1k{+ zIa@LX2q}cAWG`Er!GK1NHn^Hj^Im>LU8hioc~TRfN(RoxofHD+d#vD{gAb@1-2+Ms z8-z;@EJ)9KlcoD)8_qzCv=is{%{(S0T8gy#51@L);+hcUhArDUVRv>Hu%zABd1%MX z`uClw9cn(1U7Z>qa8-Q1O9P~3E?c!n0f8Y_7@wB<-doTVI(q7aNtEeEb29~q)iCXI z(uggbfGWc5bL?D0JhTHkWX%Na(nb+o|R7EI`722AC1kmUqIZ`Z zhM`o^KLR?^k580`j%4V4Oy#ywB*y&^D0-j-7W|zR;PPR z(G$0WXqWW+KL5g=&2(w_dpmv}K|0iyZ}~hvmXMm_u!a@L3+M_XKA37Ql$&biBu{Ss zqhlzcLJ+Kou#?6tCdLoGey>O8WYA>*lp4lNOCuM#itr7P)4QcW(k792BEER~Rrhy1 z!aQz#=I_n?pC?3Z%AI{efmbFcZ&F4tUYf2_26u8=rF1hl%n0D2J@6YbJM09ISO6rt zcy$}Sl5`bSTT4=WH+fpGKEGV`LC%Qy6@jJOeN-zly8VhIo*zh*yJPD5?q>GI0J`H& zy|fVE&fwK*cz)Ey7`PncL%;6bzknan>s)e&d3> zJTUMdXgh2n+I{xK7y`0-P3dJ03j$j9C@z4{T1{My5DfCUULVtf`Gg*pv!TbD$+yZ= z54+u~0y9L z9L|C_jw=Vaz>E-`P+EdJ0G`;F0Vxs4LW!UXKA!-)#)_TuG~kDa>W=Mtpcf|I1Mq1C zjXwN*f3mL!bwUhnMJumpEN0exvNRCW*k5pX!#h#8rK@Qwex2yAK)zh>lE)`NnLm=w zW7|z6ilQIHfSiWp9E3oG895`7!`HiB_oN42Zu?m(s_r>^1IK6AaLbFIF}u9aAXyW$ z&vo)f0lb#pZrXYXKNjJLE-pfh-A-*hU^S?vZaX<&t^@+rC-jKe%_gw_CvS~BrriMy zHHS{zqREA}q6~O|*k9a07l4K>W0Qv!yViW%q`X^RsT1F{;V5vwe~u<{DZ4Y(6Hvw` zT+|BmR`{t#>*WpI$-oe|lby-*F%Wwm0dF1b$uR>y-_vwqs!yYL2?Wh?aI7}o1;Ta# zzP|NO^u=sTfld=3-EX{yZ$0{};s%Q`D}F*P0W_J#Fs^m4hG7oFT2o4`fR=NA z@;q@a1CDk>$14WTD-8w|R4Gf;#cNUMDYo38OcxvMh%Z)w52kr(l)BVqlfY1M=+$rc z;|>)ODqL^w>t*nfh_VhtQ+!Rf3!~1@W60rWEi3&h55^55KefGSN_@H_92TD)K);-N z5_I$?Bk}>z-+GlvUij=C;0qM|1_Jj@x32IJxG~oRDgKOt;TiVaDT+be)ios9#;qXZ zXc$otW#1#s153>PX5J6Dz*#;%;)x1y?LgAo*A3a4(gj@HZw3Wx?b$S_L zJX(+64~ARljq2ZfKVAXgf`jjLpmN4GNL(ueu2Q74M|Xd`E|VW@#4sSfq#lG0QR2|1 zP)xqOO@0MWCJHcQ$FDE-a>wJK@mu~e*_x2+2QXuBJ*)f+AERcVYx5p|2P?#YaWa2703dpp$xpTuK?H(U$H1R`E~%4q>s zIajcOWeK?^0uzge`V^Fb3{Wh17KPJiJMkuhy8|MK_pdkMzTF+J+gVcBT1E;f4k`WI zKfA4L{7=k>lvi zN1`1WN27N%&KmahSbI*uP9o+OZbsU1Qg9`1rGPx98o{+0ot<+sC#KdEuI=Kxpe# z09@bZ1hUqsw*Tm9GWI`JU>5UbbOA>5Qj;|A*pg&B4BDPc@k+^&-85J+Sla^R_V-t` zv0_TT>sqoCk+44=Uq=^>pp`qHa%a4JrJE(fTaXYewUu#r7S}XiU^9y*U!S-N}R` zF;hlKN?Flt>`a5-)CGO3Pycg3535tWDDhs`lNA7n;_|{aiZ@;k4kaOf*L^hiBb^_g zPjP(g$&8bToQ%5g3dezqzsDM*^S#GB4MuJ1+8^@=hpXcP*e#oxqUM*(( zKVj)4!@0r4>kPQQ7=Emp0wtevjibLcEPP+L#c$*I#K`}cxApE2_UpsTn&_Ys!ho>V zz$!STc*41U{HJBW0V7FMYKbTN3rT#${q2IM4~%d_xh!<V{9S~98*%~C+%OuRR;G7+RTf{YEAo1d7iIdG)Z&FDtT@fZR6)K0ROIPbX>1r{(ES3Hg7^25>r~EWZ22r}WDfZzTPFVt?q#Ua?Q?Tgt{mM9 zFhzsWUu$$a)0Ilx+(OVER@T~eSG(qkr32gCTS=PThAH^S<=nrRB}+>C21&X@vF4*l zpORL`0d;{|8Vn7JHr9#8_qoo8dLDeReD>x61!UGxAJtp|*q|=GBjp!VT+{T6ul>W! zWgNMoX^)-%beQ|10eC>;+*Jx(eN&Bvir?ijQdf-Ai-p$+DWflNruS*Wf5Jt3Y?vG0 z!z7hy-xKKUSLVy4MBv>IRSfi(1;Z7yP!!oQ?Vno~BKuLN)LeA2+DJG7PkTAL7pwwe zcDvW%XTc8R@7*H0`95#_gqAyyOPlhp*Uj7WK09ttD<<9MU3UVv#03)K-QCDE&HlAk zxJaPg!|FCgCx$Tc@;LmSi2$q=oxAP_oC$Dc-EWF*ugd7QLS@smfP@3a@bUmC%}?id zZay!fJE#}8&^W4eDZ9PK#UJj^^s55+Ma3mKM(;r>DK7DyAIGj`oN%6bPhaqjBUI(F z3sH7&VS1NEFOwD0h`;w%*eQb!l<$CYR9lTs?4~Bi(mL6ziSiH)z=6yAb=6|nH*kFI zRuMx8sw-6+g*mdnMNvwWyB{OE8z;F;;j=Fr%?DjS7cp!3%6KJ_Sl`YqlaFv1ZaRLk z6vV*O(_31612*c_7z-3bx4=Zf@rQFfueb$U*^5FpI&NI@Ek0llCggk{Sj}r88ze5^9n{oC zzTc9kHlvD$k1vl{TD))IOj%UILw}Drw1CXI44HG=Wg0E(2;sH7k)X$ts(8&X_|S)f z&jX1QeCrvK@e6*n282D|<2y|lqZ)Z%amG*Xl0`9CZ6k!J2l{3M2+lfmU`2)0;pS(e z`ntg{q`ib@E&AIIG)h{c60~qC+w<2j>I}|{ciqO23+mpoG^S>;%9Vk-E2PWHB()Xz zuBpPa-3kIE*2IM}m?gU5pZ41I&PpAKt15^cv-t11NnT4@Bi+aJAV7x8da60KI{ywo zyGdo|`0c*v7l(?kZ4mM|K`|aeW@aHmuy&|`*g)3S-a;Aj9O8D$k3Uv+MIAtyW2USm zpgMjNXMkrml`gOqmkbqHNEeXAU~}D0`Mw(zYi2G+(s|BaqP*JP!5vG z$@ff?bkz4?=w$BaVw0geU`E)v_9})Bn+1ASGRPWJ5M{;{SA<7tklI^ItJ>{#WPk%O z4B%k8N1zFb%z*eY`F6P-2f}e@k)b}W&+WH9yw0{+nb5j@ZL_mMr^dRWTBPLd1?k<< zzL*VeN|$Tpy=(Xb5j0r%AY#iK<@5;<>&iEnB-CFpfZejokq#R^kRRDC?RHu@;NbT> zu=>U2g%#o}`T3i!*Wo1eEO?=!vs%sm~y&InAF-cu_-LLGG2IvZ;DZ%EGwrl6O-tuV4ypx`wkpLG;}8Y ztIuU5%Nw&fFfuGlxyZ=G5(Z9Q!bkQ# z>4XsM4`PABX0D3ZxYiv9F_KtRv7B&ensm>JnLYm9qsgy#P_dx;-;FlIfg@(;H}H(2 z7*EAX&S{Q?2#C{iU|YWjx%{N%WgngJHgOy>Rggw@$>l+f^7+!jce;QCzD;QcOVIKC z&ALsa0La$Cy6+7Sr1DLeIwUHh=rilA*COk&qhb}uSw0ar_034-YU$5z%!-n^{p8JO z>dI@RW?)yU+2mQ~q(AZJ2YNG>z50rR{4XEBU}PQiG$dTpG}kJ)N08^=KY>j=!;~>h z&=VQ|_Y(+)|$u ztBfN|>umv)z)rl~dDH-+0~TG==|$(dAn(LIFixNag$X>^xf>$FjXV;Gq6IU zfqDERTyioouwDMWFB_Cd)6yp|ddU~6v*NyP&$sGXlz)QB1eZPT4TFWR95H^D<9!BA zldXU?|N2uJKcVbYy@MB5n)Mx~eb#FYl+OkNL-=_uER<8S=D)W56-TvkGw~>G-50s> zy8gQd(}O)G&bxm0_uxo#|gv%hnai762b<3qRd^cGwJ$po{k>vo5HQ6dg z;PM10IKEu}cCcHz^H=EDqw68){d$kNOf4t&=^al)1Vb<{$JNcQXqHe`--rx+)T1l^ zp6THD6r7pSm;{iqTY|`iL|NP|4FI8Ff=S(|Zmkt9V-dac$e;Hj#H2T8H>EK*L^hD; zDx2O*A}E_5y+3tMP~V|oCJf_g;THYgkF&O3N{qFoQHI3U6A*o|qoO{K@`-reF73|i zQN+_$1al&=&b%#@RKBwn-e~9}14_}9B_J{9rJbC+T!5Mhlp%i)4GsTZUg7|{tmA_B zO@w>)m-xG#Z`5Sux&SWa;NVLdvTB9gALC6xP>g7t9ENL16Tlj&WCfHKkEVXSfeR4o zEu`{85O^CJ($%e99GY2X6Y88@lu|!;i2MvtrMs8MgYgrW%>b zxi2b@(rSNss5A2XOLamYqYvm`>l~nbq%;Y|56da$Ia#Z|@jXT_2mHe>E|}X>tP5;% zO1gigs#lA@A8tPKps3ij06xNBV-n`lWkO2RD}17EUk+Yq{sse&VF3C-+}780GjV`+ z$q(x${hH2H1<1o!nez0O~CXrU1!P5Skz9@x}=@>jGpPnM1p}RNoucG%ya*tS7Iy%PIOatIeCr z@7@6|NZ+o@d)~nTlRTMntrie%Y5QYW_p7|G6qyFfp`IcPFK_-ZA$Iuw4}BEoz+9 zp=8phECuzqp7;QyOyd;HvQ+xQOPv0Bt*zLmJKz~za+iiLk}lgcgX_UloR$TN)?3O~ z?4joGx6SOMFu>Jk9xMjdF3+nw;q_5AoVe4K$g8j!`0t5QOHBkr;+qVm2_UuxR&;F9 z^fDP!{d?lgLJy*Fueg4uHMa&>wt@Ny$#*3BHGb7GC$5#Fo9)+(^)W~3YTRe zaTr)qno6s%m7w_fa%6mRIxly1jf&d`=^NGtBy39LLZ! zT%6P>Z$}=MeqI5KJ`6E$_PqA7l7__ra3wsl)4A@G_G({(1oimAfH2d*g~F&{0EJ>F z!YIpI1W`4X47pPUtS8vTTIOVp@E-Atq*xFW;OHWq?s5sAuZCrIp4W|Jld~Ux&kf(5 zYA@MWVLg7#VYIylQ)4j?k_hSfjsctS=%^farh@1Eos0x@!N_hzX))pBX2CVm?)yjB zm*Diq2j--4s&+7gRE`(A?ah&Csf(Ar_Fq2mcCJDQaqCt1A)7MzD9-bhzVdO>+_&!T zIJ7>q;9OO(~3_U^6d?Lohgr=y|=q$xa+>Kbi@iCLqo8KCQFs{$+*c)9TG#RB+Kx-i`7Xb6JRr>PN! z4-^=JN7*&?Y1_#Z#vnAYrD2;26+Ym|fQKuhfH0W0!y2dxou>Ar!MLp6Y(P|{if@rQ z$fVLQ>T`KfYwPt1Rj&KW;lm**{^O?r|I{?_C|HK_+h>R$-H*S^d7>S2ad9bE$svxi zUJRJT7v}E)@SEVXZS856?gi#{Xb6!;+x;Bd-T!!M29J>(A&-`iD{( z{h?JwaLt53kl6CJ3)r?3Kf@<8B9nyLSS^iz+fNJ5W#Hc{RY>!^%l?V!Zn{%BmjlRBZWE$ zKaLa(jUQLFi=wC>0S+WFUPd&tLL72A=?1Fc4-BXY@JuzHT&cjfj{($Lek~CvqkV~4 zyRvOO73@lxYR(pri`Ngkiah0t=#J5U?@DDhxY&9y+$pDEXi#2#?l3-eP*9f2Yd1f? zQNGv|&?(>RKmwR)#%#kA$7R}6^;XC2(6S2Yq=2Ps_2XAFUiPDoC%%C^23nx#Qd(wv zh{k-tb}X#lpK_sC$b&se5B=LUytPA}E*9-IZxFZap-(~dF58+qD{(;-g0GFC!4lR9-=w*kO*v#ZxD8e$$qUY(O3AkCY<#i26v3?(YiNK<9^kW1u4tn;Idg*y{)#C=F}( z04aTr%?#k{NYs#dGrSFki(NFP7FD_YS#{IBv81pG?ECjp0x{Xo#FgGt<@z6-5y~=> z1$WH3Jpqh?-VYO~$nkPlFi@iWW@cUn12GgmiXZ&g=mTqaxXEZ^kAS9W?%GjF8RFz#W(jD(C^gUZ%D zQ+=3_w`1vfmMu+gXXsh-UKCZbh^rZmMqSH9<@sbt%%T(~xlRPS_9=S96I0pQxB(6Y z`W4Xc<#5Roj7vtgw|$nE(yAq$V#EXN&*}#nj4sa2kYefl`xbfe$s%pJto?#KCN4>%1%%E5n>uf#AQ3LW$6yp4_MW9yMm z4h$7xef3;8!^5*I_TbX+MRo@)>YJ$&kl9bwn-Uo>*HPvoM^GyMDtga$e*wZ`RA4E% zNY#PxEIq{Z?LaLt` zG;eY~hy5+f!T9B2fy2fyZk2;-l#g`BvH0%4+B^G$4;AbvlNP{zgWc7_ zTt3XXS;2f5-kR1_{~&0kxo0El`P4Xb>a5_y>aHCs$p|q$y1ykvXDSb)5Mf%}Mm~rK zQC#Wv`&IltMvmIZDfVBHPuuN{Jasw4WKttM?v4*4M=4cl=51eEQfJp6$%NBFCW1L4 zU#tp6kSn)9t9{zW4iK<$8|Tr~Kj?e}lbg|p)05r4#bL=x0$njvh3jah=BTg+;2n|R zRB5}rB|-P7LQK!z4qOF4PfF+UQ>wjBVy*m!({Wp!s(^Kt1X~&L@r_LcV_?%5@`7F1 z>$`kDzV)*?)g+kyBVn6^C)(>ro6R>|dS8fxPn&R^>36kwH%Qw5oeLrKyr)Qk@M7g#|6q z01=&}l7w*znhBm>VvKc5>;_t#JR)p8g1O|{K2Q*G>}wMf-5$dqz&m~ZS{bkJ5r0r; zRKfM@i%>8eKWNG>Uc=I9p@EXcQ%HGLUG=lIx!+#kdTVX2C5olHwh1y(jEF8M_FpjA zfZ{_ON|Ue85XdNC-K^ChY1hs@ko+DP6*NHslKmj6JT)Qsx=`eHJ$=)PU%w2}X87GF zs>#EE5o&Ed!uc(d0gLP1Cid5YRAevS_*BX`WzJ5^l<8f3#`C2(JmBi)HGSeRhHWCS z-Rgmb_;$_`?1lH?j^<~!{tieM=W2bjo2}Si1m>a8R}7`aFcYvP8xk0Hxy`%_U@~l3 z(9AO^`zDuHbq35TB+(QjapY4)=)C_qqMbV%d#?QD74pN4Wo8>XgzV%YcSmOce`Y4+( z5;R>@0!D!fV?LL)Mg9yGAxND5a2=vQv6RFI`FcuV0p~iYXQBZ#qn+x58YNKNrf&Fv)4=3e-be9I*@bCaZWD7Gg69qulBtv+>kaz=I{G3?)isMG z;`mNJzS0JR*kd%|h+PHyjAY*8Pzj;U4Jtxxw&3;sLLMlwThOo~Z%y)Ep2%xx7Sf=9 z#?BA0^$IV2biB|s5#+pIihQUNp<7x=g7r;m=$tbF9{$oDfZL40hcQL%5>o9ORiGJ< z{Km>MtlK1n_i9r`OMBJ)&-OqgeVoj^9y7B=XQ&FZ28dOcb38{oSp!&8aa=VlUJuSB(Cvz#hI@!0LZ9<*PPaUvM()LVAGd zce{JWMSg$goqD+r;8eBh1s@)Iji8zkh!2QX(~=+taLc9qbiZj`&(iO&0WL`!R@*|1 zfk>tB(KQ-EcNjF|f&_MpK)F21nnyGx}W+iM&f_ z*GG>$ZVZMCgi1urGwXuPzoI)jwEcR;ID;SJ*&arcEDZEwTSNzdnB}v$ni#QtY0n+O z0yH?*WOTO0)f#6@R^{k{+0+nU+WY~KiEsMr~ z8Y#YiQ|P|a?Y+*%I)9qJUGxy}h;OlGXp@7^w)?pdw}R4@+_m>FQXMb^6tU0i!l^=O z=IN#$lg9WWwV}sHX5W_hnjiiiza=1u26{bow(brl z^X-UClx|LfZfO9cAJT)aYRJ|)r_Xa zvu6gNb-aexyjH-0v5;lv26NPPo9)%FmqMN7jlVRW?)#!o~v{drR1yjJ+4quk!?hdWGOeO_7tN6aQLcdkjhJhowfn^LOI9SkKz|txF(Oc;fPyVPMRz`Zvf|5&d|i7z!vY4BS8@0h z3tsk4zKVdu9WXFB?S5R_R3{YGcWT|R-zPM%>9a%L zeqTXkSs~yxwchZW7D;up7xxf(CSU-sK2mjE5RhKyJVAJnfICxBfha7o_S=-VaK(Rz z0inhYFSf-(d!(B=u%ht2f?0Plyu&=G`#6Bt=vs&9x++MPH0y|h@m2pga%I@+FJHHM zs~1Aawd?w&iFbF>yHuz=6jytViA>9u)Rf+)WktjOxg$2w_69BT;)J*5Srt1%E5Dke zr+Q2#k!uJlDBh$==#v)=l+N@PtjbfHYqLX|&mY9H%NWN-utNvcrmMbz5%ahM|m z$I?3(*tsIt&~?T2P95Z+*^DOd-&%!xvO~EUF%G%-kYdafRg;8X!Q~g4R2*uO%`-ZZjTfuDALK=SY^=W`K@)^E zT-q5o|;qO`5TQ^BB*bV<_nYxJ+MO%Ey$G z&X@o23`Iia2|x`#c9tB&dTROD=8h?%p1}*J$*7Efz%?vef+N(WfQiT@z`>5E9o5#0 z5|j5+;aRFnIrotI6~?Ft1l^Vr-4^YEB6;B|pvD%3_#qxcxwFVOgi6iW;k0WmCXGmz zn+6f^@rq)m2evO7ECMW~1+*e_*?>rB0en#hf*D%z-$VNa39wzatLgUH;i`?$8~znl z^#b0mJlC^#b&Hjn{gK^KU8psf{5sus?$0j5J`T7*f5QaGxdIg<7U76~a+#cS>y3d9 zZwFDSo_zK?{MJ5Cd__edn)>@nGV|BVu1!8Om*A@_$|}%m*EW6KrJBcyc=TV22Yw_| zp||8j%wC3U@X`l=$^y{MQE|`sJ0JD}2=8eopP(EtzwO2Sg2Dq+ylj124&ZxWJg-$U z;Hv>v|Et85XaZgDw}bUHEm#hzqcbMhM=g2_w1)wq#Xp3&O6!tiDtC*Tpzul0H43)2 z&?*(Ha`kZckyPy_A8Fw$r1-Ipqf48fHUhJSS3F#4jn|t6+eGw(P}7L)m1W9;BNp!G zBtG!rfc`UHU6{TfMef0vQUp@gyagjSG~(vcLGggtt-gMXOqA4Kr>pBS#-jtOd%_R| zmoJLVe$oqjqj92b^ zXt0*TYFf4&&={lNpZ)F!1H|Kw4U56MZrb7N!@t!*^GJ|!yCFImYu)=P?CKLClyr!R z>8pT|I0Qf0lCh^Qk$PnmJs|boyF?i2Rj01WkM;JyRF zNkQ!b($Ct0FVUY4zCmUeOiOzjb42Ind%1=RVZO$KY5!c!r*)UF0&0hE<=@K z48*KdPJ=B}DyOGjZS8}|nftFEEWjBsV^a6+#%D+=H*3?lLDU=(+kUszah+cOnTj}Z zr*=~=iE9-9=NofLHhd*ZEq`-Dp6+eP8DziB-0R8s@jLZeqr_+k`*rdGSOxbr4C1dP0-U_U zZvlKy+R-#HQC>2!7Ur{7HT+4M%zfePR~zx#>-guXy0bJvuLAWQf4+afA<$0o(IBjL z5+1hf_It35IJmivZ_5Z|$Icm5A`m(9>tFd2PSJsTj8YlE@Jo1SW`v=&@>worZu=laxmW=RSC1) zUBKuB3PIOB`ZEJF?-I?&16ZN*l0N)qQwNvskHzat434E#vjvI*<@OSMxa}N$m-c{U z%Zd{LJNQQXx~kUdpI7}MWaQiIY0cg@;!cigf58!BXr}E{-#*AC&NV53q;5nOtD4vN zS_YPp{~+VTc4U4chq>c?1uAqAtdA1+4KO1YI|Be<`{68(+y!IefmcksB>}`=KHA8e zHXw*aU_j~D4*0&JqXAx|o7ptgLWf>B0l)Lvk6z&F?UXfmC`WhIl#P$wGym=)YAYMZ zfecx>rK8?o4&`r@;l>Ng^g&<<8oIAeK_|yeDV_GaCbs-LVqPO%V32TyrcA zQbKb89nu7R5pW(wQwMU<+rVwG_ZO?pZD~yFt$jFSI5a79z%rYZ0US()^gY2ukeutc zMfKAnq4@zMuIa7Ty@Joh3IJLEaJ8LOc78DH=2v0Vvk6FgTXXjiVoo6_51>6k@ZpYr z(MFTc4|N={8`scX8b+$}2yFTR5M;{~x+dCaV+*knOc1E%TLy)R;R|{^!8l2L?T84D z8f5`ZtZ=eGNq*ag3AZjpM~~}VFU@(qdtLtJsV8M?DZ#2j(1ufwrm2gmHpMpv`v9v? zww%qV%Zh#nZ!j3RD#vwxA(2iUqyAzFNVb94`IlSh@RyNxtO6y9vPP7+KODyQHv77p zYUSR)qrcX$f(-5@6nD~IK$!YSSB9GMNGfgk7_hL=IOE|S3iO#xr&WN#uTuoy_yn<* z4C|eTZl~6;=-j()Y1eT@ADw&MLKxu7t?J*Xb+rTP>?G@> ztJZ&<{4ASqm7oq~qh?i@A2e;HAcv|Xdb-a*%x;8RI(+i8fW{3v2(}R*I)Ou=H-&IT`kD2w!R1(&mnw)?I&ndH?hscLh z<~OO(O6zB_y-kC~uGrwOZ!K=Fbx`m{z`m+JUqXbYk-6R}u7Lg%8N4{Z!ViGJo#C1` zw}|8SiDK9xGZoiB(|D-fnR4s-V|MrHCd8x4?-$?|dUa7jmr0hEe3pfQ_IAH@2wvnW z-NK*4ga_|UbOHLwK9?Xq=5=oN+r>*5I@qg|aA1f;!{>cvHpkfhOKV;2Ih**|B3QM6 zr37-;5pgfnY2P1dYlXLl2iq;3Q=WNDfpc$zk2 zbpQO(A4$s`R%>TLW!P^GO;$DIY*+K%zy}a1PjyT%g@qcxP97Y69}S)`y5rhf5X$8i z7dbVeiPZ0R)p1{;MF5Z2WXRV{#8~^Kk@#;zZIo^90%wB0On-aU)jSdQIMZP4 zO$?mXRl(Oj;|!=l50CgNi9KrE(`OpIW=kw<`FUtC}n=9s6C6&U=uoqwcUB9>&m8EHmB?~k!0ka%n zjc(HW<0&ThK8x!LJV6!GT`3uJ#{Z@{u!B^yBx0Tj9}Roo$4F#R6fth=#rqY+e1&DF zJliAg5nHz@o3U{%SeEE&G6iEf2tLmkMgS5d`&EmjWyi^a^FvwCb9t)5m*oL)fTXvJ zl>C4K0o3&dd3olI=kYF_*uI0-LQN5{!vSQ?IID^pPC1qXzcC|!rV>B!@2GX}F^0<> z4_ca**RQIcZWs=U_p=eN<|?=D|6WVNJdV>~y%R?t%2;MXab39O>nL}*Zp~%}-TB65 zPX({4pK*u`CR4}ysX7+G_>w;;;F&OwjWT?DA3i5bQf*}MXJq5)m56Vl2tEh(kPZ=Z z>Vu+5=?#uApfr3hbk{j})r>t`GH6A*04YePuwV8A@S7Tv4HBzR6AATu0f$#@aX&a+ zG*9<3@%Q3ir**{HPbTKK~%A%)ySoqA08%z)8yB9AXY$%Srve$VrSX=)r+z2v?RFrvR|=PIzaH8?Q&{~J^(I5kR+*izW3?~= za``>|JNF>2vY38-)de2mhRpX?ajZ>Pv?pj3QbmKKKEMonhP37vz!ZG!Sn-1-qYQ}>Qz=({An$NDL@|4EV#foZ0~#1qV?MqwFs~t zQRdI=*T8A>js&5m&7k!6;%5bMXERJ!<4+f{YL^f>Y#D z^z%J)w0c`Ec-tA;LoV{1JSq*o1lMRmXMRY7I)E5x=+t`*U6m&=K`7{TYmW%s?UnqJs(=P%|jh zl;(Snh&E*Xbm*^(O<2=u=!w|x6ZSg+rSWvsp>r!rrvtS=X7 z4vdyDQAA+7Noo&iHnOe`D{NUgY>Po;=}5o(Wf{Sb6u*_uoSvsK>tDA1z2Bs#TyIxv z!G7)QFm{0{lAZ|MD|D*Eu4rEaDA~<>x7(pS$z9`=pJS-2*`=@$234`emEZz4tpGsL(o%H0Y%Eh z?*e^R8XK6b?E8GVRsr(j?>2!f*%_FWNpqxtfrjZzTTFo}q@2psTb3u)it_0VMN7py z-W*hCLY3yp{iFbw26M@iOlR$0*e<*EV=W%&%UIBf!4+%LkUvsa{ndm)k3)@O&WJ9O zE`lOsK-D;q8m2ya*fU92fMHpgh9McIAOYz+8uLf3KqG-l>l^U0B_8kf4s(Fq^~V+& z7W*E+FM}&y6d}O**{WV5AfGRHDKHEyVvbX&pYWC+V1HLLyFOg4sC6h~J0UTSarE0k zekmf?lRz#*@Nz>yl4jB?a@5vQ1R2{PNati$ff^>77zP1H%}%@sv> z1mQ3ci{M?)YS`x_Xf%SO&qLXzU?0OG7BKqtjp<+Z@9q{-3-=> zaqv%Xz9%(Yr~V6kZ5dp^R%Cjk6O$Majyu1GIK>Lgj-UT=bRNrXf>9WKAQtqt6dj2U zqV4Fthz?Jm-`OOYu_u-Q-EzKTChC2%E1;QoNNKjB}ZndHg z{mQj=J5VyK^YQ?+5+vN`Ezo9&tEDU-lrjQ#I^e-L#VxVCy5%RUn6h|^9JT7x zEz2D^C#6fo4ovB+=k?kbhLskberDgbXDWN+BLg^KfA#E?c7(O!Uqd6==rmUBTnhr0 z4Tp4Y0U_`ev@Zyg%PihfAZ>E45e}f@TcKoGWOZmIBaSBMdx;bC#pp5G>{(+1_5gE5 z*PcvQ5q&)fk7GkRWTf1oKOqb%QohK=GC1F@QBNUwn#^73XZ`MfI{>f zHe9wh3sVianlFevP9ny0FgxjQ^mgw#ybdIRv(0)BbQlAJrJ{r9%GMV@q#F(SoCt$-%yo2_noOMP`kiEnixBpb0n*m}<&lA$7^i)C;_?!NdoSe0J6vZ<;kgMzw9H~5mIm@(uQHEFB{{_Ojou8y-xf>N<4Rqa2lAU2?pI&9-)HNkvO^8EuuW|w*MEHO z<%xxV&q@S?etP!Sh2&xl7FpC}GItWZuhNwLOlpywS9{~MK+wN9V0+h)V0>#@8JL|SFN_N(F@9VIu<(T&x+w!z=g1VeKC7zUIn*g zu4KFKsca?w0&6^{yD6)mS|=Km4Qo@io7@ZQ-^5S+x=+hOt<@1o3#Cw$UM4!Ygw;`o zXJiYK!Z4=g7l}R~JJwi6jv&T-+&lOdWvUCH2CW&TR3#A-vztoFKY0SCR%#JQd<(dV zzn6`trTXAr=~ews&A;{dS*b{~Kt?0-KRVK1VAw&E_0u`lZZF_8Js*B71r*?C+b0Iy zx2fb053hyAR0wsT6iTtpk{~DU4xDf@ROP zo|}J#@3daepNy2tfD7EO@+YdsZRTF!c)zG9GVx~VB?K-iz5WV@V|`9M=C?t^W2TCP z?@pnop+Gok$T^S5O9wC1gUj#w;*Rf`J}Fvt>Ehj}>n7F7?G*ghsRo zKzQKT-HM8A`n0dNQb}qwQpSDC+49S-rMnTtFXv)8J^(`K<0W@?`qX7z6kT%TRat6a zm_V$O(7VvO%1vdR8uo9&1RU|VFXYjs-%MSZ(k)?Kyp}r33Aezk#Y##wqpf4j%-`r7 z2n`!|7nj>e69J7_2O20@f`Ep!P$SCVm z!IDrCmouOefnbTG28wtAq!=!2VJU}xXf8e7722Sd&}yF_ExN%^cDxY<=-LyQO}XO( zEZC^Zs3h{A4vLr8G??FFUD_eGuM7dwn#h+?;+gFX4KKQ?qPO9`O-*>LvNyTk)tGGi zl)llj)1b1T+QeAb&$} z`++mY#&wpl>oYdR5PU8J_PHtu2CL?=pEi8#^#&KE?^{-r*0569C)~g*7{TLw-+fU7 z2#ui&xOxZ%Za~rv-8hn`{)~gNo`ypBo<~ay070bGenjA-d4~KmF<#vuNE6a{BRzd{ zm`jB4pINBDP`__+kB+qbbBHngVO*eE8=%_j&ly`C1;eKL%i!MwYs&$55JSeOz&D;7 z#d=c1KSMTkUyhz>-;}F;ZN?W&g2Vj$V8U#A;_|)@qU9V(8ui$O#wTgpn?guQ11ff` z_`iWmhBDLoV)Oc?^0SVgu#SC7m=O!r{I2UqAbdWyeJ)9;FW0lF9-= zv3`)t9A!-j05(Pti+0F>sgeAG#No#)Tum|^64OJe_^G$gQl>APwjyBFm6vx06ofVXr?P@CdAWz3xK9&RmdHM0_$Y;=LDk{Mobza2!IStZD@ zoam@~!oLszo6aCV;L{EK6=;nhbE&VqC^_W0iQVankeeqAWt49C3gq%CW8bf)==bF` z=&9W!jK6+OWjII|uUmNc%*ZR}&x-ba@Zv=|r}t^N)7>E?uGeQ1>n8BOXR^P+oWq4& zP}u2jqqw&WM%7mxVDSXtJf(P7EG0^9yNR)-JXH)l$V$b54V`z(4E6ELtZQx(p>fT` z7V;yl@Z)f~>0>)t)msLgb2Eq;Xye|l3jvf2A_wCj<8FAL~K-cyfXxNW$1T=P-UVXHF z=z2o`>?vhzd$^m{L0C)Zr2S|$7ofYj4KYT?8gP$)&;W3t0+(kS_d6CK8A0Dr`h7Z9 zmA9YQd(8!G7WgE6<*P}StQSEFDhiaMZra%mVRuC99id7M1cFbAjQA>~DIl5L5WWTB z(D)!g!&L<>ThOg!@dr1b)&}<0hT-v4tjaZSv8UF&{!%^=_2-ucGHSgHx|$84)joD4 z(sluoi(*DUx@?ZX3b2s%?V@iy0ED-i7hIU$Q1nF;Q-uQqYInTSnb?|xG=+mt9M)y5 z`bq(~LQ(@I>wD<;kOdrZzPlG>w#5)Y6Tk9TaxsA*F$dq_#u16u=hjF5BzLZCGZ+En zPXAQwSMD8Esw8eodhhyi4PD{Y==CMg zX4z>2C1&QpbRLtMEE)c3KgaU7@T=a=FExA!q{_ji8CM{*GjWB0i-s8zoWfuWZYP=rtXsXQ9Ho>wtYox% zkO*YouJXMyMji8ovS2_sfQGq&jYH#3P(gOhY@rHZMZLdI%SD|@PHHs#3bzbcb+V~By(P;>-Weijzi7>!`BKA4i%A>Om zir6><{eJ_yi;VhWd&F;};`n?@$fCkQ(}fiBqnW-RvUJ+?G-ZE?0c%?FU7^zjg!Kf! zK-{ssee)q2=t%e`qR2Blz;JYymU$rgwNqj&T zkO(9V@_WA~M%H9gq_Jb+o?9tneqJmcL~#qVswF&aUl(SK3n@M?iqmid6?$wGE$L@Hc;Px`u%%1qy-l`$!E+ zb@!$W#y2_=SMnRSsIXvp&XlB1$Rlv|H-{q;UBLLTOrkaIfm`1d_7`9pW9jYeatd(MLg@@UK9PNw(joB1iPWI1-QE?yPKrY4) zR0|F(GJSz(eSKchhfanh`0YS~2GG>|(z;iK>0ZShA#T44oXY{U>t+TDp96rP6pRnH z^~af(%B?LpWB1pEx&e`z=#5(osge9wsR*viuOu44E_<((9ElaCuDDDh8ucFD-;{q> zi}$fy`FTmV(NtzGCI{{r+= zx=xSSDm zNsdt0hoxmKM1?O1Tb1Yhs3hKC%8&Zy`Se1g10VMiNJ>s4@r91qi$#KaiTP^H}@a{0U-N<>2XIz5!6vq1QW3);;*Q6 zM%RR?h!02nD#a&ORtYs8h~sdY=C)s3ul9Tbpf!Qj>{B1WPM>%aNetFQNR2MQrul9Y zlU;pS(2rpG`1^IRfVy5gix?AyZ`npQaD?}uQiXTd;-%!cKpSInPM9V+y(}J-kx&@O z!CZ`UeXG;;(W}kYl2YP=80?(g?L9>2cnj?;ep-2Myd;M>WB%vIYfz-=6!YlrTX`h8 zMTP|X!Ny1e;-Wj>`EEL~R|__#xi}hM@j1I43aXuHXh;zRYRY_^063!IK zoF^#FN#NJY%^nLV*(Gu`fsuRP@vq_Cc3jBKf#foL~Ilg)aytFHZg=W3s`kl+{1EzvkPeO1VFpU0L3ezMd@O77hDpfD6_eIj; zvKM7dD9Ll2Si2wmt4ivxwHG-Iy(_s5$65l~0{*S7GQcO&uKT0VtPra<#DmLPC1kkrcu%6t@%3clCxWaf?a zO`V`_T41UC;iV>pjfKCy75AKYVX+x^$z*@B zT*;LJ1m<6z`8-L?Q2}WyqHIx<$~H_sQp-UD;Pns&+{!P1NczJal@mX?ZX3&(QO=X;N?Cf3H;dtE!QOq zv(NrIkO@^@E&?LT>*rLN#F2&IhO4HxF-PNmfqD#xtDhCZKGJ>ZpPVfqhM}ZlVCmB0 zu?<&0$MWlgWJ;XL1@(|eC-Jl#B%gMse?P71KC&krh@S#UrYLTcpIv{LZamKb1!weaGp?MJ!rDa*MQi{Wu<;xxBWe7I! zl1aW{*xCDDATQ7626&}ypL-IHNgb;NvX<9#55Gre|6Q)QLD@tyaB$d+QvHCl^!F4? zVDz#5UcIAl{cz6r5I@*+pm0tV@NYz~*)!fb15302o^#;T9L?uF43a$_43X0GYLFp3 zxlFVEK$%YF9lG;79DfKM5KwFYb2M}juCh}o1OGM`f5)Q@+DEq>@*^*D%>ko^S~S1| z@l0jkX-Y{jU{C)jfar}6pyu7L`6xSHu4qF%qga?K19KWk+hGqGNf`u*-VBnrwr@U6 zIKsOI{`B2a6KIGo8|KM8LB5@dn3N>e5odkeA&X)|>WWxeK))v}Y(VP4g#9cX<@7zi z90L{l4Hc7XK2w!=KVl^XqM7N=O1?@B+ftcU9GBFNc3l&XG~qm&Pxk^60*GLh;RFBj zc0iGdoq4|pLUG#xkN1aw^(Gl&a|-B-vGT7TtE;mv9R#VW#6{|$w{LW|dEml5@+Lky z?`W1xQP}m$gMjie3V-RH}_w=b&iHY;{+b{G%SHk#)ic@sx$3?~Z5#;yG$h-0sTO&5r6EQf&&N!M+f}1AW5KcVAfcFU>7R%?EUV{{WQVcHW+%0?vF>qJ=L%YBV+*kVocSGrzuG%cRL#gZIx%-Z*aXt(N&LA-2jP~{7;F}@SA|(_ zC8%fXM{XdrZ~R++q{!sH$w|nIOx@wt!#IDIF~bBE@tyQhp)Z9tig#SMaCtFhVjq!I!@*T-A29hc zGFp14zKh`McuC&{dlfw2RvfF8Z{(~V_|2g}PhpdypJQ#XO&34Wj|7#_|0ECvzp* zwIHdNbLD|%4VLzI&yEz;c<|CiE-xM2jV)1p-FhUSUE{=_@KaBYIZ5G+UXdmg?)N!) zX1>p>UP+BX4ZrxjpE>{}+{I-NJDqW|jZZ$6s~WouV1(c1G-*C|btAvFX zrd4nfV=YADTR-&tDLxxldzDDl$^P|!?kv!9t^%h^eLU#EskFOpU>Q-)2yixK2soAx zKx;&FAuA4ZD@+06fNgkz^wNIbBiy}L;AA`XNd}*+8_d$(p|TodS&|W>3>!2r%Ze;v z>kN@R^k)~3j2ECr10NI`J{AKKy?yLm@nGqvYd;rbxCBmNywBo8f4u$8=-6a?H<56d z0iikdZu~Z(f_6bH;b~g??{RiCm_U{(!t9=i-pASFPeFr}qM3nse9`*^&&AfAxN{%1;U3Ed=~ldAaL>#@9sGP< zUY<=UI*JHKLP>wVt?v$3kc>3QJYhwF;d77);3tBJ;Ef3cwR|yx0YDBqJ;mfsOaxLL z3xYbxgv-@RehzS2FQe#wz_A1tu5gSRALeg@Lwh8$zcccNOkijrngnVPL90}G)*bxG z!6&@hey7iq<|{T{lr@Rv$d=#rIcBb$B#?MnakqLbY5M)tD`F{Ebj{=pTaAn>2l^TQ zd=cJZS*Bpc1yK>G=bVz_V5XT?xmNmqm{mb^-6y%WUs)+UR2t_@e=6*k$&R?(%&vF^ z6|Z(#Sh!j5Rv4JBTLHL0egE!>l?QrndtDH14?>4yj=%U!-Hl|J9&%>L@N~a8m`Bq9 zGw9#z+x*DYS2QHuWUp14`$m;A0!a{@i5413RGRDZ;>T<9{7hncuX^-yGMCrY`MGia zS(l_&mXYpH_L&7kT}=plol8IO=p zZHl7-xqjd!@81`^6al7%x)E^|#OqmJ-Nwp;Pqmc>rfF**qc4l|l!m1HyvA81$u~zt z^if_bIC#eXidCK#zm{h|C_mQslzu3o&q3&%Ms4WnLHd$6{Ml!`C3+GD^-!lTr=y!O z!%`{w4BRMjUj0QWA#2%_C2TTszr&n^eLIyg(ItSXE4;dVjCgI`#k<-bEQ)(tETt*y z+<+O&3vuCm{AM*{4Tw7T8iaH(@DcNf!yyD_Kj5)H(?Y>Fr1lbhe;l1uYJXf_E z>yB$1_3x8tJl~G@=OTb;yP6JbueOyF_z(EDj}&y43NZ3p!J3!n!L6+f{YsNL!BQr@ zi^sV7N!Fgl+g|s<9<|&`twMc$K5n$hd6?;w#=yX4F7sMuVF=$T9t%P?Xz+`d;%i~k zYpeE2gZEn&yW#+2Pgi*fmjRBrc!)cON*us^^b>|=!I5@lJ{fZ^T~Owu0{qs3+^J0{ z?(bn9>raVHjBMwOO}ZmuVN7u6mR}Hf%Tlg>v%Z@s6dL@TV9K^DGd^84Bzkbs83ixhH zK*}d9?!NKOd=F&MW48uNCp&Py1I;?=m~Ve=L;9N@B30hIw7JP~qt(nm`X78FZnQGRl-Oe)ccP8Up2 zEsgG#OG>Ap2j~y%lGcfY_{M-|?Xd`X`s>ZnEV|76YCfU8ajouJ<0OCcv&x5B&G&1Hd@+LoYB)#Fk4meuk%D-zbf9FHM-3 z8M5v{Q&(vi`D_)~7p{LkjvJN2JBtfW{Z;Q6bO>CDwr7lbpsF2R>db!9GPJuU(D;;tyO!9w+>*UQpv!+0dnpzTD-(lz@)9j z?nnICl1B0^2X=Y-BWW<`_wk!(uOTpi z?n>Vl12yfsD<53mDrb%1BE$_HeMsU*^huogS>S*azZp0-a&|5?T;Q&~vZzssrEK?l zKh6_)2M$<~1_36bSCZa9`{Gk!MFtAi$&GVU2D?-bjm`e$-6V6!b4;=Tb)C!p?BeyY zHWAd@6e4(lOU$WI&a(G*OszyShbHjuhdHx#N-!)d&|f_*a7SM4&X2uxmAm|rf2)6w zQuG)%jvK+k9Yj5R&j9^ygqc_)5Q)s$e2^NN8&?q#N^^|5FpztKab}jzNH9p@>yWk; zQSEl$GO3OhSw(rwcWgzMPP7<0*Uu!g#fx@t2<$4kH`(Zo5#pU?Y=}Y&zJIxE7{f16 zy^Fj}wO*p64sxf}wDyb&@}9GJe5&~phKL+eSxo~ocYVRKc`}eJqC4PKPDxMx0qE$v zvXRSNwrCyY`2&0lXjj3<*!&WAQ4I!j+TnOZD<3Kv`OfwGl&G84eorY4&8BP3Pi^@3 zjL1KxlMKF`Ev^I9=OkCumaQY4Jp?T&=+vI=eVH=66>izNJ^925L4@ZXYMae-`toXv zkI3*ZSSFw$`B2W>oy6bW4X(@U3Fz7qvP7+hmRHnyfXpn)a?pXEC0+w&Rlt?)`<<(i zKzqTe<}S7y#D~-2UX=6#(JAYO8K(N6)U7Bi#EcR{ftKVTa4wb)2?gavys(gHX%62e7?izxn4tOr_F>jftKAK5dPLwlXv3bQ}k&mIUHt+Xu9R_RdpKjKg#a{Bo zRd>^WTR)HiT(6xi_0iKqz973-^fEoCs|BjfF9+dGTop0LRi6%huvqLVR%>%tE6M9^ zM}+T@4J?gzbG)=YtLi7G_R_4|(3zK2&wN>_C1h`^-SFy`oVEPJ1T%!29|@o%=2y9% zw1^}p04fuQlQcit7&!i?z61R88P^>!h90>&_?bGbDN!{hSUj%k!H!oiy3F0G5XMN2w)R-`u9fwj7IlP>4{>Ivj0rQ}nu|$J~pVP&I z#U#8Ff&xoKN?v#OWjPe-&9wFTMj(s!Yj;*9YnGSMx-!Mq0}M!9v92;d|KQPA&WxMZ zSW26H;tK5W)U~+OO*wJA`c2L(q|uy046H!dLiOb~hQ2OAv`r18*x{;&1Lx=7ty`fO zU|hL3S9;=7(U)4sv2sPPw*!tB*0wBY*?oJBsc zl63LX*_^7)CK!4?K-xLI?77kxt?<={&thd6WE*qFWKj7F3~j8{vy|5*koOe%%hsN! zwo+vuaj|%>{Uz=%A-X1%)0`Mr<@cAK5fFku4 zlN2z^5cbPuEKHuFn3u}gUwI8cfVvls8e~%7E0}PkZz2Sd$V?T_>%up%AHd0{n|`;~ z&%~}}9ra{ocmOv*$iK~%rE2K?LB#4Y2I#2TQmPZJ0npWzu&HP01*M_=85Ik2kA_Y6 zlSUe4|Af}w3U6vX%#>&%w5N)Fu0gvi!pSs<6<^mbJr2DsG*Zb*kV#DH3T6tFjEJMQ z%Vd}rU9kdB!FOz%1#F-cZUQ>E5If219EUV8p5?{YvKYrV1=1UqF=LXZhw!^S4tOCT zA+CyGnhxnRt(*GcbI@vT^ZM#slM5@K_0e7oUhu1MTa^ur&VKXt)F=CG3Qe<+uBkEu zb?~nlkK#gCv$#*A-cTwMjI7hzYdD@UXQ zYTFP5G;l}*lnBbnW`Bm^N@6kW(Jc(>5N3OZSukr%e3}7~-b+r0{KlR-whiAHX(G00 z0CaEFQnS0<&X-5nyv%imdc)kW?Gslvep&w6b?>>0;y7nH1gfa4+adh8X;DRdBcf{b zfTRT&ra(`UdqB-ID&ij)#)D>YTI9z^fHIio2v*9!lR#0HnnQ=0RL7Z;K{VJ~p$fbovktzDU zW`?)pJ|^|ITznf8g;{X@{@#)Zf;S|FHq()RKb!96phTcAaTy@C0lc2HttiG*<^$OI zvx;Ii|B_nY$Rjp>EHb_H9%XMe3=d452m_nH-&S91Wx;QYi-TA7n*eFko4d*NYXijt zmrhj?AY}@gb)y4^YeZ0Z#ztpK#X5Y2d-S;F0o1LL|M`~w$KF+3aP(+ z666*i(3X3CKzR1J9`Rq>@E~v0i?RI+4l@3=cy0`o`z58v2Q7BTq1g*}3+DDnYIWs| zem}h;JOr_})|r`MEOrnaN5fV=;LJ9Pk^#;FqEl7V=-wtjYF7Ff3#l-zpdU6N)T;#A zc1+s48wp1EQef21tY4uuV6RMq>BN48&GpvS2Q*03hC5)-Nved`4N}s3UQlxhlV#ku z7i&u_J@{G=X>98pXb^sg&1;_5e1IG@+P!=F+MSg|?b#uu0WiU3EqoGQO!x@1UhKzZ6oJKjlfV7B90l-*V9Ua4gRPyK{{I&P7DeJMV7GH~@_8PeGj zNQ}+kP15Y&t(Xiij3B%xsdwcsbj|9&%ru^E0W6wK2o|%nA^+VB_4**nZ{rH#ZnSL1 z&?jz?^mKD|Y@hoxw+HEXug*^8de(dGJGDcf8e#A(vYlL^wzpT3UUr*>$XT>341x{+ z1^O6sfP;3$>S<|f1-CI|YM6kYai0gf zVT`<-k?i&+Ral(WVGqlhooWF=?9g5^6BSR!FddW|B#n~0$?U!He0P9^0>XSY6Q~$k zIr2NwIxV~-b$=)%iVNA>apS_YerDQ{934EIbf0AjlbofT{mf*X9NF=S1#QzoT%B7< zst@g(Qv$-LIj);;A#q~kMyZ;O=E?aGBI&I+4!DK`Gs5@wPZn6mR(V%1SPx1R93pI@ zHky)tBocRhM#}4lvCIe9(OE(%l-^cMs{%-OwAE?--g#&?o0Z{`PAmgR-t(ooy+O;} z&&&`_4bHp9xu5t^fjoEJn>a$+pL?Kn?73t9I(NXX%}+@z8W%M2(tby@e9|_!?hKZ7 z*WQ3|q3@n0AeQ2T3e($|#~;)u>kN+uFcz}Fhe zhG~3+ak_$%N0Z_c@h6C8L!PUwh{p&LO{O?o8ldpqlDLLitY?oMWdXNL6jcEHon5d3 z((FH<|FOsA6NX_FTf#>M=bfS+ck|QQVB~W6Kv26)vNo5j)B=O2 zLgJM&`|Tv~Os9jRGae(L2i@PsF`Qq(2S2qY)Q64#V9&aBo;SbqzB-XAIN;~!?eS~> z2Ko%N*Ms4U@t~PZ3AFIlVSca!l`+$|tn(4lbUUezycBC>35XNmOKm4VqJiGFb~UTA zC|ApGJu0(7e7TO8$?=#l!vk+1tD<&>p6y=*`Tm6@*kMYE2te>y!deBDV`KLXl(ao? znV=rSZ@_Wh+iu~sfJxE>WRq05U&S|!6PcdB68Ew7ZSyaxQ_aru_*#i#5q{y@0{3x$ z;Nm)XS!hzLzK#pFM+kR8qb1!WZ=?GtE1iwfJZNjggp18GdRBj=@oIiXvw9knFN zlGd8|30GD1qR?H=y9 zu4H`wgUT9tOBf~wMUK(=U(}_e916tLF`iw`&$xGzc9S<2^&D<5fS;t_#D$s!&4{;^nkpZWr*Z>j5&sh=;t@G0O_x)b>#r@z&|132ajtt@xtsa<`z@Y2 zA~axDl1khY?_vX{SYk%zU3f@#1|6GX-~gC_2{U5a1N0;ok~a)tcfHx6{_vexHRQfB z&8@#oI)b>wL}P@qU?^>AaKd3kfvLtj;II+twj26G?Bt@!55~;32x&&mLM@Wk4B%+2 zt&UegDQhi5)MMj0m)MNe2jFx#nF#dcFa9x856U|Tc%)b~+7pnSw0JjOk0%jMGF&}I z#=`eMQM9QjXR1MTJ~kKHmdNC%?ZN_Z%93+#FwS~fBY!}TOks&U6y zI&BZI#*4(K-=Xth2U%lSx9bvDuF<3b7L>AYof zP$QbVg*GOvOYlF6&Lg`~Ac~?3(m-UvkQ_u38D`|1Gpl!b)_vM-Td1o33s;bkj==Pp zB~rx~?Q?swYw=os0aZf!w~CaY1;x4lxO0eVU45MC4^`qCM1Jakn76=9UYyhus%f=F z;>`aYXt0uZypqiRKXU``vyaA~Y{%`%z0bhI^Tqrk`Qd&OlPUp+Nr(!fB_4F1)h}=Y z?*xr@CRvazT92*yCxzCxQCgB-2W}clR8Cb$w?aQU&mEc111*HCREFs&&Z^80A>gj5 zXv636YaEOCVU7c1t5=|Rs1wg}?h&4b!F};^J%vhV*rf>8HXZbUni0`r+ro;~BPqG5 zKGm1*R({hysl&vNOQ{-OIP({9jWQw8wc&J1jv#}w9D+!@5Wk9uf7-M^Fx?go`x=&3 z+V}bBts0-Cns%L<9Ca6zdBOK+iNTta5=3{8E&&+YO4AhX`st#SIT&&J5Mwr+~(AUz7_CSJ9h$HX3AYUBtpEdp?WaBf=|GYjvph(p0zmIN`= z<9v{Th|j6DP01Ccx?0pMU%Om7->U%~Mtn*DtaZ@31exA0Y6JTn9;;t2n11f}`;%t? zM`WE+mzTA_VCJWBG>DIuZPZ>jF;uB$MYoIpU7=ho}X7VU{^*S!v=_Y6-&ShIc~uV>%y-xe+*jxEEh z=oMc$G6(G+W!?(4O*sUAsN<*0Z2DN%Q`S;k6ChCr`36+A#AUd!4mgbuXf%raS1Y3> zYMB()b{;G59E3?9%}w>Nm{)Tp0bh#p0|5Pu!1^ek6(m4fLkd>x5)w=VocjR>6K?2I z+pVOJli}6?&Erp-uW{oui*w?bG4MN@O8_GCTcf}ArJtz{n((#}QFYl3`y-8T0~G5O z(Ni3JK!iYRyJ&w1_seTQ#8DjEd{1Z~=hdUa5~r|EpG?FQ_O$=7YxDuZr32M522hn8 zzcBYV3gaG*6|Caumgoe`E62#+N2Pj1HM_w-KRCC zOFT>gY>TN@y&PcD0a87;N3=8+>akP*E}CZ_4sLCCKP103O6{MfF#^eeo|iK5jlHm_7(1a9r2h-FJE@Z>~V}W zp11(E)~XqA^@)D$(r+Yov;0EuXJpS)B_(|hF3+cWDEQ!wXWl8wgn_GC0@y#1834JG!vSO^Yt33V&c0%F3g1m-YgmeGztxytp{ z(;fj+<=`j;6m|Whegl2ul7NA03QFy&bv`X_h4U@~y-rBn1^tx&XUw6w4ANKK2<`T^ z*!Hp`pQ1l*4EZ+HZ9>L8k{G0jM}F;K{2-UrT%*#vW>^quK?`Q?E8~YLFQ4=pr2gel ziHv9hsgeUzqT*k7kqj}s|7^}e%W*LhRhgWvad)P<;1VZ^P7m0Jy|FC?bQ>te`)rEX z!pB_MR6E96%g1S7tkXsDWsaex#8nUC+E6&e6YqUOWr@v}H?wDC&Jv8=la{VJbF@XB45IhzOmGNy;(1vz|I6 zx5s4wewdzyxhN>th_kL*;nk3n0L$5hxRJf~2qNMt>JMdoA7b1rf#xC)3X1<44Uow? z=Dy+YA?J;eJ{JCIB#HtNXJWh%m5OeN++vO>Znp zS0{<)6#_4YQgeGWosVwKHV1_RP0HLo@7;h+55yhT?8(5jY^39w0r30p@xb#Vs6LrF z?aaMSaa(v6o=uJG>TqWHBwgNt2jFu8k2BEvwgSkyBc#VL#HXZ7HV5=JrbO>!{SXId zLu%0K*0l7!vg@DF_`on>`KOrCC1&GhYrtDv7D61kxkdYBZ?ENq5qSx!{ffp3+eYJP zf<4OmMWG*@kG%KOn2hOLf#76*_qSGB*c5;J&s5TuqTefo++RMYlle=pWbo9F(2&22 zca#pxC>;q@0-?(s7##rsVWRuCIKmfJk?RaViF7+cA6m$?XOX^O4s}>yW&8XVK8M)a zCyHjP)Bsasw*)g+V1l1QrTp6ZYupnZy5pgR&lO)5zX||;L8$X9+~oKmq`&I&!cFwY zi!kbRzEqNfcXJI*qR1XC`Km%3@+}6$(Rwr3NGfMejZ`|IPTkD&y#SgqKIUEz9PEnK|Z0PQV ztFC6}S%0rpV~C2O`_`EA`K1rtund=M>^c*`JwFEO5t29V*0e$c3{9ZojJ(dQ)z{pv ziT2u(Y#IDeXIq5mD7v3<#f8eD=I&pB8#`%CT&IVib*p70_zFsV_%flY4`#mKi1TsGNkY&-{X2HaJ5-85s; zAQi%IfCF7$(J~S6z82^JU}kn$e(MxaO`8HZR^5(v7@hE^{j{0NB>@)Ytq)755#&U6 z!o1=y5Iu3t>@CJa74HD6PMwyH_jMUNXhH!jJ_kjOqh^8ULv@)djYjDPO1RWHO z8`HwGg2~6aE9gu&)nsR_>zmcyx7p7+~)imx2OyuQlKC zG{ARaU!pd$U;1vKmz++hvNNDB9ZS@(dlAk>C?iTp`Kpd2GxnUfdWN&25-O(!pEfQ&e60d>B(j%s={D?UYB`(!ST8F?#)(1p==L6z{ym4qo za6pSnHl+Z}?opnC{_GLL1RF?VxE1*XCM~n~i$2b#|MbKZtsDR8kQ7pOKBQ&k^(17? zXYWLc5pS=Z7n_H`xYD>QJvji{c7FRET)n&u3R{8!{G5$6WVSoP$);*VUgD7X(bvev z2Q!QWnc_Hs3ae{+hHwSTbKSi^C?r#87eFP=aD}v=yh#o^m#nR7i7%&sn_I`@dOOEa zX2-x2ev!!RyFG={qw!VgjiqngaFhHfHx4m~fPTDu2eoX3D4j4T@vh_~9A|uVfj#dg+$Zt_!_yW5szGq~0-= z?Yg&)OM|F0HOmBq|h&aa!$jm*!xxdG>`KmudbC2~91tBxQeLRh+#ZN{R*g%2eM zmopKvN-mwKC#@+`89uMJfcbKG#i4zHE`V|I!6Pz}%}~WWfKeS>rDaMRxPiPIqv}89WgGbqdM$Oxx;0$ zpEmNGX0kPjwK}|B=wMKNadOOFeielY^$u+lJOjc`RlJ+0`ZUu~HI+_%Z#FuV=%PFB z!zkzA+cI$-#7d872;2iZu~I@+{Q)s3mlvEu<@(N?zg&?;^3+$5);g=%^wFy5lOfUI0!azIrwYXbgX? zwmH9>E8XN&pE5uL&Zqt?DFbu`c=HCpcHJrb^PeB4&0i?QP`z=dIZ@MIt{r3OOkbAQ z1#2B(%tt~bJa7S^K&ocJ|IrTl>8Ber;}Fl&-1)xKkuv;5`(JHNx{%zDn!~byPoAjB z#}v+#%|uZ3Os*CYNGQXlacXOt*6Ryvv+la;m+6(0L>q$aI6jDDEsJ``q}>-226{0;fKjU6>0$+T@ROm%|1N7AAPw1*N>pzM z=y1QN%E$Z3S6~>JAq@*CnSLDE$Tm@b^mnr`yQ`jd5)xz3<$zpdX(oGs@M3%X1Crb0 zg}k<2$37$+Xr^ldhWL#b`kSE{kl$%T^C7eo_Caq*Eb7gGPIgaXI=9W&J146gfBIeaW+>cphMLA6ha_wPNRju(kP z$YB1WR_>;9@$QS9-%;a(w$7TNnrsXzGfRF^6>K8_gc+`~yC>E|dz{?VkEheAfx?zX zO!wYkRdlBM>*XQk+Yz{BO1>@4d%`RJh6%@D-zz;$4(zL;i#yqqR1exW-eC#x zP^w+6T;X1(W0I=zMs!IoREamij8YVvND5eV-jVspiQ{(TVqs})VPs*sUE1^ru>2>P z2_v(fH-aW@P9*A_j}E_1Vm|Ax3Wb)=IB%R2jy>w_a_^rr7YIJ=*BE*HqoRuHdKEPA z>g58#k^JFcvPcXQiP|ahwV}6@M`FQMDDq&4{y(o+%RXz@EQ)_WlU9{5eahk28?2bT zwL$-Jbn2$-CAZ(A9AR88uV9)wkS&QSV0ihOk;}edy%j;;TFI{8@A3OhN2b1TF@3Gy zPVU*TUEfm}CAvA)uHNzTqsaii`jAIip;Go}Q=(f)m z7+N+a;pZNZJ0MvQT?}}jZ*F%WrOJ&%{v<{ofVi|Cb zlVO4k>Q!xXig} zv|aF4VFGS?|Kj5?xYRudqZR;LmP^o3y&f%L{Zd(*Ar*ZB&pz2#s~}cqZ&87-stjQm z*UR)}_Ef>d7+S43!Z)D6zrPeDeIi>V!T;V7s3FVw7HG!Ky-{#zOwV@JJvv>^>HRm9 zQ{%0iO4mn;CvT?%N#I4G3m#MwX(S&oP>0~odKv@JDUK;pCWbcyP(VPnL9i*cwow+M zQvYUh;m2bvVt0dnnk94U(C%#tN)t%6b!v8sX&eaY!K}&~2tSx!6QvqF1yt!x#dp`Zr@Kr5$FApak1l1Wp0rP%r|TTT2(lj zQBqjicFUOvxE}*rhx!7-#HZE3AdV-gy;d&!QiZ0r~pm9jDjQfJ!YA2Y<`#r zwi3A?FMS5fx<#@dGl3Z-Jw>~BR;EdafWfyYhA!ZHQ<=@zZ>v@!9R>ctlI$Id<2jn$ z;w-CEpAOFfUTY-G?~iP#|7Si_ASVn%xOSDezk>zZTT5#AbD~)QQMS?m_92#=ltN|9 z(uS{9NJl%UQtE!}7+f7p+VsU6Yi(k*ji>7=VftLuBg)>jN}!==P_=eIf;SgP}%zf;3Hu48g$7?%jMvGlY|Cb zS~~S%P9e!qnGN)VeS4UEm=+p{ZK6r_vk`jr{(PFZdZ-`!{f6GJqt=F*vF|$>vI;A&1Wiz+Kd#GlJ;v9Aml@Z$+mOk z%hS*O4aLbg%dI>(X=VI8c#FohUvYJO)Z4eJ`wJAMrU1;{d8L{>GmKB<_Mp0_A7Y`Kf&r<<5#AuN*xkQ%|k zHO=T0I)t9Yl0nxzYuDJC=%`aTGpRI!>e)}SS$iAj>hP3#`XgE?BInigB)yz`PHb)w z>BquAul^Cs1-@qY`q1?K-t*2&wDIG*y}l(8zH8*SJB$~40KvqT+%)GjR=j1Bn9>cp z!k1`c@32%tn;DZRUbB}kz2qsFCr2Mh5E7vyCjH;Ri~8Cu=E}eGQzJhO!U(7RHP>X7O2gPV2pKKoGmbBQ-h|xnVPHBYz{P`=B3di*+oP2 zE@P~XF>_1`!|i_P zv@4QkQ-K}X)6fK(8p-&`Z31F*3d06RmW(-@NeEgEB3*o?=>}+@AL!0?+e88; zls-erBTu-NpJ-`=)eO#WU*M@4DwoDlQ7HlKdYKE{q|J6$0cQ(Y{gHncKp+6j(n+Xs ze;%Fpnx;!Agrl!!BFI2{G1h(nZQ)EW3Qz#b|2DB+afX;32MHp*H|DUR4*sv zq_8GCV?dX~E|PQyN&v4<5Yu?W!7R*$I`?hG(4B^hnL*gQTFY*gjXyt#=0LA!aUu2T z*Wt+5PlmEwPS)f;Rw~Apjzy;NRoOw|(iH;=&*G^YUgDGkZ}Pw8N9h9qnj7MPl8eVh zPXOl_&eut!1Adzy_isq^UoLV5hjp!7ok|TO*Ij0@w8?ikC5i8i?J%LGEePXtwR`y- z0tLwV{A4$jYhuSPB|m%`HbLEnm~Z4q4LG_>y1<4dfUEHvQqBM6PP9rMk2XtjOPjxylY=yf58ZDCC zsM4;y5bJD3h<3cC#8^w<^_+WTgv*^9u!nc;J@QIWN3-_h8#(Lj+k@HaI65TqPCbuL zDYiQ7UdLTifDYTIUn^RD0fRbBdPU_Ch8IPUySCkf^u4dG&b3$T3_@Avjw0YMV5^ZW>%pudcu z3fZlkJY>%)2&T#c*xsw$XIJS&mTfkwqm}&A_qgd}j1@ptY_tV690nzW^W56G0WvoI zAr?X(8z@PZx0q%J2Yi2Fv>)RV)4^4TF5E@~g;-%vm|9UDUnnW^b=x?iiT#sgAX6E~ z0TDiV$cxi5r}=U#u?18L42)!IIrP)jOcHeU8_EpmO*X0~?=?7;v6ZcfDPzQH7aFy7 z2assCf|oV$N7dpIq&Az^Az>{OUrcqsS^5(S_E$|>@SNMo5(c#Mfb$im@0Vg>{l1*3>oNrYe=s#H;LWL*Y0JzKUv0ep$& zm!G~1Hh&!nrLu{xNb7#8r*D90Lh6KO@=6KR;Sgtk!wq028$vM@rZ^((76AAYU=oga zyw*ag!u>BELYi6RIYHXn^T82CAA^lpVL-%LnPWrs_nUZxf(w1*wBTC0kf(hKmG($o zCu$S%YjX>d%|8O3H%c5#?@a~*6D7#9RS4!k1AjX;qaFI__B_K&pyE|~=}hoSFp^GY z6Z%&6NG^)z8q&NF&JA8~;5;o?Sh@Dmt#ng?bB9q3Ky5EYTw&#S<(CN`JJYM%moNu1 zfo{ghm6Nj+ZRSEp=M1xg0(mYQrRJ6bR&v z94HW`Y<&Yg6-><6P5NlG(~;8q1~^n^gqzJm4?u*RBo3Znt5g@U3ggx)bO2}x$g#as z6NPh<8us0U3@V*cW5^UbUp!%~=0hsUS9^jHM*14>@#;iVrP-68_C^;;9B5t)K1;^{ z{H@a?o?5g-!J5Yh7M4NN_fb0Q;2=NC#`mYjPQE&KJuYL?uyQI%u|KCPg z_}C%Tn?h>fq-*h<{bC~l_~6}Yagk%5#K&hXk(xQmcxd9 zI2m$>@0UPt>4Lq7z&Edm;ros}v^=U;L|o++%| zdk=?7?!@zMw!qC2p~DSzSwMH;pU+|s76Z5&z0B&qay!EnU0)LBtMeF6alz#>pHug&BS!AP?oMP65p-Om69(XXrjB)8kqW7}zKvO+t(}a{1&R z`rX#tGf2`{fnXBo^ccS-V2P?by70qM*A*ZGRr*oZR02SvyZW3aQdw)4aNXicy`tRD z+6W4al%^CDe8-@yo)qztgCR>g%HA}WZ86nW?Iz6K-Y8;OR9Zb8Yp0GiX34kXjLHx% z%%}CTipP{(bx#k&cKjSKyE$wmOy9BR`$+qqk zE06_#aWbr*%F8L=PyHJK)t4%TAAc((VOKrUR_PNa7;07;%$oC$(Z6F~ubqndlGod5 zA>VYJ|9`w6WLZ1+PFB=6)A1}PZ@q6E6jq)N>TVGPRRyKb6`br{mxzW(q@Mtm#WtH2 zBdVYFV}{2I3qYRqy2hB&7cM=;HZr0)3oHhxO@gj|vPFYlZMT$naZcC!!zv*P|39*x zv3zAYRm9BN7j3n>;8{#n_k7L0NX7tOih=owNa<%6io$0;x15W7z^Y34I)0s2my`ta z$8@ul)HgbZKLDTMzN&9B4gZ)Uu)w)g^^^_<+q)<)(*(oe-4ObRFqP9Ox!(yAh<*6K z4cfZ}SVr+|#7U6>WPY73UEYC@e;`fTG8b1O9x-Qx6dR|e#zH{7P z5sW=0u5vOBazy>N=C>%w=T<_X#arOP`;OX1MMD}|M)k-eB?}X@$cmLQCAgG;$qp}2 zc>rIcZthsQ6dj5}Mf(viRLQq+^!H<_tJTk~GWLdr$xmrhdB#$w22_1hXD=-5V;ro1m~wsd2fnpS#_?k;z=3?g9wg z%K->5BlS=4i>+Mxu|Th)1C)uwK^b$gIw$bmf#vmJD-P*OBs}E zAfBX^tmShCelWkc4o|ObBmZ*$_fRkSDBSM{5)1VSvRSHm22cYVsQ+JLm+eAp5NsU4 zH}2il1OX8p9X|p6r-yyL7SWMWUvE}*x1RVtk6&(mucx5(Jo2CC5$r4ddEa=o&hF`t z*@s+A=vjbwin-yC51s4xbAj#RH{*;66~GVMLnZ_z3FIz)b+8_nIl~&q2!5h*M%=Ia zX<*36jmLi8qv2A!0M4kF!{Ov3I%z$YAFGapu5+5K6_h)!=^p=ffIx2VS21OI4T&Bfs|&_s572mS!~nOoR#7ne z0mUWF)GYZ7P9}l9w&P%oB%^NbUci3YsNtz6=4OdIB*iZ}ejJ3|=;?LXetOEeq;#O> z5N{Kt8fu8A=&UWR_1c3M`!p(E^dOXKZ-Wy%W9S2D9iu~l)n}ZJMl^^QqpTyuOO_Mg zMq<6fQ`;r#rOHEJT)1wSVP53)RVr5lj?+4vbDX82x%p*e`FchvQBk=^L4Tb`X%PGW$muO-gd+fIV#_Ao^hso8kOL5YAcM> z%KnCnzMYSB8$06v5Vx0Ic!(_{;h*P4%fb!ZP&ufiEjde;wI*Jz|itVq?4Na{WgCIXH4sq9k%MrR2(foUW zx!yg7RfAicK*b);6X?qCG+D9{B(?~vE>Q7RCmr39j;I}!D zDy%gSNki!jqHka{YWll=;A1L~uj^_pp?vmX;(c?oRPiu8oE_ls*irFhWWI+oG-wdH zYmjDl1|mgsv053Whz-Qc)VUg}p5O5X0ujrbb`sTo>OG6a_X0`BNW2CI#jg+Esn$I^ zw*r=2?HfZ{aO2N9UwKuVW^sq*9K7DWu=-0Xs*#WTItswWJ7Drac==W4jr%52`68hL zi`M`g)1m!&b+>g>2pDnmgOXZDzH$rHtctfp9~Z>7%Kz=O_F=#{Aa5LPo{#S;{(q(! zpj22zKc3gWh=V&1JX^ak7&jvv@Z*<5xC&mAJzwZGs?wl}IF^<(Bo5j91;=HwkV-}o zgqZEf(($!dPoB551Ebtdq(E=S_<(c}&Q$^j?nP1{B6Z)YrdsmXYg^FFu|tPKj|3Ub zZ_8!pvt*$>gEPJZoq5(Dk0I7DwwQIyx?Gkzv-6`O=n?u3O?FG-tJdpRBVBO!%PO(V z@&a&XBKHmyK4COp&pdzZ{H%CnjC;3F%g%tWAR6b_jh=!(_u!t4f40}LKKI_nS_l4a z)js|LsN__yKR){n%v_NAwgF7c5$A00yL1_PBdkzZs{I{vcMo&sJKyvg$ydOyY&a{| zr2?Hhf0gQg9rOY0&FSKPT$yafr9=&V$!WEWz}dccF#*vQi0MNfR}K9F-H3t+x-O92 z&`$4K``V{dnRv?KdY5jWft3nG0X7M+eyb{ZUnGzgy#txex02T8No1e1*{}BbNC{P^ z2f6P~&Tk@5O@1HYGnI6Me16bxk(a(EV&14gJ^6}%fF<=_su)tG*#Dd|7iA5Pvk(c) zp9}>0dWq8~PeYy-a%z`;yurKu@Vs6EA(TJuQ+581g5v7O2`faRno7v|EW9fHRfz*KURHw(o#EqjwIF+s3$Q zcr~QWQ7*q=%uzPB zt`FgNtzi8o7xaXDu_R8fle_TZ1HtC~_Lpxg==30HZP+!gmaQYtSbN5ZM}!u-?tww4 zz=xX`6@Po|SxEIad|JZGns*CV4xz=S02;cy4QXjt~VoHx^?-h6sXE zw@TOK_XgPL3SMX3sXl8NjtN57Zhsp_@UDhjqTjaTMZD#(nB~*48C0DcZfm4Bpiui@ z33#oon|p{EG{t+gkkn!dGyMQOPVYif=2x-LYd>tn6P#gQ`2GsHv`7B>W2HKIn({5< zJ925`<%UAmL0`ElHCbI%HA}sZWM>ndS53z(+TE<8BM6Ll>&(aB24K_BF8;kk9w|Gk zTM^xfO7%HDEI~va8iXVz8^_mUx)^MTwUaG{!CMxX)}hiUHNHc|gp|&!*ph9jAufzT zP=D7%44!N|^vX?^{Vh<~7f$L0;G_fISE>L9SPtmpjsxj+9dgF6uX?9$UGe)>pER>% zFFb$H|3}$XCHkzV!P)(22E-W*V4e!+$~j?g>zax?dX-;m^NN@%4x1X#W5!TG@FeDxSSV3*24-FaNl)bQb{iz@n(;giB;R2P|Y7Uqt_zG_p zs0|eEPZS->5WwCWV-T8N+)H`CL4Fp0L02OIS0nFqCCQ(P02~O&>bk&K2lLHW4Nn-i zy1?wKD)EzMzWhcv`Y*JBK?_lN`4r-(j zV%&xs{bJ#>F9*9Vu2qO($0ed_2K|Fn=Nro!S#7 zAzka`+{ay63_EoZ zcHy4p`wN0oj-#sOcX((1zNq_Znc8$@sO6o!*dUVz9=(u#m*lSLhbLe28WWipIp@2s z0KN&d2UKGgSpE_mZ^#V@IGW%<_2x!yg zRNmsc;o9%m8vqdWZ+XNWFYK^@eFkiq7JbF!okYo9ktXVGuL#MiY67ZWO4RJI;A+`v@$ zsVAzh;NpFx5b$IG3^tu{+L8$aO-MGuB6S zanI^smo?oVvMhwgt)CLz4g>?~ZD+|JgvkJ&JwUt9Xh0|98aA}KY5tJT()UP@3r;<| zu6z)g>4Eb7c@(JKa*1yzHO8~}3^)rxIbu?H&R|Ume}BHh%2a7qS*-$9EcLCmvGft* zrZ-JZIn{wFLc^BJ62Mo;4xM!+>B2Bk*9$XdV_AmY`BAy>F`0!0>V0{-V(foYZ9k4o zMH$2LN@azKT*E#T&|DwAMczfxj~UiU1ykoQhDLYLa;YuvsHQMr zJy+0;W6u92JtawV(*Nm55&adf42x63`QH1Zhs||0-|HZCoKllm1ou7Z)+Dg-S>=KR zWiLUV>p$0%+1C=NtIa(RN7RJ>$I*Fgxd{YO^n+NC(~=-^5*c>nj6jI^ z_3rUv&ar3a*b>lPRrg&G!Q1!n7~{2oP?l6k2@TO`<&E-y85<{V7zV*?SAx9Irn4V{ zr=dU@P4y|1pU$suw_pn(@t|pAJ)XkFd>c)~%84HwQ|~FN(mZVK%8?Z(#YXSoo7cAt z<^*fEL`njP8IIbZK~DIVWLgLIv9LuU$=Q^A8r9%!AFyV9azq03oz;%cU+M90wvvo^kj=hVyS-$ zJ`i*GY$_j9K$6q%Y}Ix29e}i1($hGhc{tEQI8w!Ng<6;tOJ9a*Ks49_s0KQ5**#>d za6O>UJ$1m&;=2kIJdoZ`89ibl{CgHM<0Kt&>3dKKmL+k|0J;-dbs5JoYsV~mvFkJo zG;Uw_R**1k>Vg}2pD!m=10E{iHi)fhIr*ecqkzQybMVp$TDY}hUiA-#q>!N%!2QF6 zKdJ*ubD*_m^9<|@^K249ZnS##pz;J9?=v`JEzG~MKuJO_UwSit3ZY?T1*Gn@>P(7b z(w$?Pnd#=Z1w={!q1muSD4mtmjls3C6J8Hb*1{SD2bs<%{N2lu3z$wS?c%K&AoThP z2&%Uar{x*_MP^`Fdykc(ro!J^ZY96^S)RS%zEtN)#;*VQ`^J~ceq04W8zWmj5+BB& zISP9K3V63K4^kqwzk#RalU|06Wywgol7@{37w~+>PRij-B|r{b}eC)gD7& zlp^k6QGPTHwK{;#?Q6PX1D0p@b{RewKcK(3by{Yni*0-`QkCoPzJ1E5%~@DU`ICPy z2<#NKGm({uz)N+qAD~s|lX4Jv!~(2KaNu|pW0Vi%cOPxKoVjXYqGWcP^7RsuFVFyi z&Dx#SPeUZy2-Xo^q>OYPOPiT3(Ui#tx zR3JI?_bk%TM+KPJe1H2-09-7n#{n-T*_6FtGZjy%R(Oa8bsSrU;44DQaa{JJa(r4u zH9w@7BQ3w*tB^|YE1PGX+2k(V@e!_E`mrxn)m^$#g0!A2{cDhta@inYw>lzTT zP&SXVbFXO!z9C@(Wkpa*BN&0us-9{lAv(m(stDDSx#fh3W2=Xv|{*M zkgy!ra2!Kw?bO#;L$w|=MGVt8+}`NlGYe?q`j#hUaTjet0NODyoVNGNQHg`q5}{F^ z!`@!Gk|JegAZ>9ZFsoKTTm&{5p7+5%`;cNXm6eU+3&ZtF|DuK2J@FTXS`sQTlwyl6 zq{tj+4@BN!jYP9OR_D_2AdWkiFm`$eF((6;T8C&i{S z8t&(NvqVb7v_9p_2boo-FIzC@5?~_X^s7YR=so&8*hMZ1vFG?QUyX9OxM^5m&+=25 zY8OQGNqTIC3Xgg=5T9!kHqY{4cE5Iu%0}u*w(X+v0yCY!cGyB3?*~Ll!(m3mLtrvo z^AD|1>|NJ7^&`mq?>HqZNzO_Jc@j`nzRA}jK-!kR1~#x?*lqPgM(xK?dT0hbfj%tL zGr)sTUO(%m--|z!D`_)m#%F!knR~bV?k0?~u-{UFH!?9vTN1hFu-x!1#sSdnx<&W& zF4XW1_kmSJNdEpJPnJ8GnY7l;l{~peRUZe@)Zgdr#$a+Mh;&Ul;ULt(YNZR5fR(F$ znpO107N5#^_(9ifdg<>%0)!&L)byaxyXN=SDnW+gY~=HqikE2HD?_NASws69AexVC znLTPfc|`fhHsZg1$ng@lJZ@am432;Pk>Bw5MbCgz9<;^z9e{?O1oT@UV_6?akop7y zbzB{Xuic`jK^C9cIp%n60ayvIY*Y_@ZNEV%1(Pu&nTY+YU}M)Dhw`8^0DzNmWPb^zHi*&GQ0F--l|c*Kd)XHFQQgcBL?b= z^2Uqpd3L^2&j7S3#Z}lc4m6<&Sf;F)$!C(|{d^%{?wNdZ55f+hpST+gXRL#xKtm5~ z@RS5vCD9s<1@z`6+m_u&v^0Y27K%&IH}V~{(C?k6^LAVfrElHsx@S-RJZ1v3G?O&O zVTNgZ9;mAhwFz0abcD}$&8~)}Q{yX2W0R@ZUnX^sS7tMiVR+keecK-Z635fxAfx^7 zar`^HHLW%Rruwdn;LMZ}9q`Oc0?_ADO-806(5gsCCcjb6gRPo!$N`TnaQxmOu$J-F zhy#hvKR{COm8~_AG^*FQD4zLt{d7pLXw?Y3K{|^%A?Z?46G}jjf<{&xNtxzw5>Tm) zbe}-@5uxD+o5OT*1JJWxmz!<4qJF~(V>xQWj*CM5Y(J~R{9(%XdqhCWd*h)W{oGiR zmB&AJU<4fSy9dXjnTy#11&Kc1j+4o#6oj9O3NxL~D_=dN1Myr{RNOLop#_STj$ z)@^QoxvdzMh;F~4*sk96SjHiE%{kavkJyFzr3IpLEIi&$Juk)@IdlvV@aQH3O7|)}HRpqymqX1((`4q?{A>fgysHbx} zxZEf#?Z%WxtHK{H^E^uh&7i~rDf{xjx~XKpL;@De;;P!ipiSCQTUbOX*QIhh)LXtYiQClpCjzt^{u#SBG-P&6jAAS1divSve z!f2HG3?{I`P|E)n&wV!#Q_t^mPhQHfz-0)7D`j~qh@0o%k9O;Zc`3&TD*ZGBWG5Su z^(#Mn7NwLtQio4%DapJ$XMOA_(C}Ob83r;1FsrsY(Efxpi~&Qy~O zm+N$%+l}?WT#zV|xh9<2ei}s7&bvB%&wLPHSZ#70?*M*_wg`ru^Xu~z^OLqj(C#e@ zW&ZmacjiZYDEt_Iq~X9ToR@tq8=m(HlWNU|h8h>(ted{GpSLI_l9<$eHIFUugt3T~ zWP8~IDB3=?JiI~>#_N0|584h6RJToKsuwrbu^3PSjv`Fm?##eEP^?~^|O}W{FI2argA>=+|{|$!WoGYoqrrOj=mo-~EK?K5p zwX5!>Oj$l9*OfM@e4V>vho0v~YN`CBL6Op)VLv&zn+!ZMh`fYoS29#D?B z3QXRQ0s=WQ6Zz21dvD+dAY+NYJPkI(dPSkidA6bhWJJ5@%+Z@QH4lX0QXiA6n*|1? z$dY?9DdJ8EINF-|GfuxTJr~PutTdEqMMdnOqk>KlM!v{Dl{X#mgX4_nx7m9TbO{wQ zL(k&Q1M71cwaHgWted2)FJy>04PxCz^M2Lfmp?}FZn7v2i$GcrH+n}a^TdzVZ!fy^ z;FLRO@x4j}e7FJrt;Ulq-8S?yECWVdhbY9)X!{?<2*M*-=!vf{Fw|!89Fpepc8~?# zfxk`!pe1XXMF*D9vp>FrcG$fF|02j6iBPjiTlg|kjiG60%u-FF@{&_mcGz=@tE#t1 z+OG`b&4LDc=t+f@tu4|ge`%G~_fs2J{)feK#^?_5(`Z03BnsGU2=J2>3^~=%rhF91 z@r8rfnBcu9GvtX{rgYLXr<8ekZHjtsF^=~gw|=W zcPG@ITC|rFh$Wz`PtG1_-9O^Dh~Fv7Diq*Kj+Ig>LwBO=aNSuZFy0YhvqK0+2VqSv z4}0oQs+*O~3q{+{RiOQwdUKQSa-J(U)*g3n^Kd>v`2zx#4V;SYaE3#)PkRCwY)0$B zz{5k<%U6Xqp6Bnx$`AThe%=ugw7Q|nbwY76A7;Ltz5z{MAv!fvQ} z;|IkoSu}=n1L9rT(Iv%v&`W!{ujze5bB@CwJh!l0@CU*B!X*8!(P(Ul!U#|d1^wX5 z1p4>}ZzWC47!%xzFzgaQh%F~a(fdO6|`2!PLZ2i0yvepx03>0D-jmF9u zw_b_}{B}?;51C@hI0)j_`bThhS%egub%KQyi++dYpeaRI*GKz!?= z;+w?O$737dWvO2h5|eDKQ{^Gv5bVxN3zQ-JhE-6|H4SS($QCXr#104C8Ahjq%pI!V zM{Pde3;UHlnvx4S0-3iI@rUCV^vA~s%zcz~OTLAi0kEN8mE>6#XJOBUkZX*F z5iq$7zGB`D4;dF15rWj)`X-b#-qi51(%GZEK+GTZbVgMyWMMa}8buPtDxxiuC69 z`Mp2yQXtSL9EbKgZq~dM#3!(hGz6}1ho`75u&3q|@t0Fn1)4dp`+o7cHg%UkVk`w_ znpdmgax~wMRfJZ?FRHNK#_bMF22iX|*v?G?SvJgq16SRkcP?6tdfAnUpPs(116$tQ zf@vv-kzgCpx*lMl;R;(Y*b0OHlD?^X70fGrB6Pa^a9Jqx+0X@X4K)I_sO^@!-Oomb zni7RUq{#xGj##M%R?C50HLYQz&l0Zi$v0`OIyLD?G{z6Zdc&lTQEYZxM5EM}CDJ zvpuPWmr>7Ua3jhSjkqrm8NlYROhc1a^doimtYU`o8|wb+N?uo&yrS8~-n*0ZkDC5| z_g<=<3#C$Ifun2*tPn3F0x;l@uKUzj)0`(_Su1+)MSszrTaR+x*7{jSvEuZ-E{OEbqzS0+%xB9K3~5Qkb3O zBXtGFjp2BoY&yi>7bk4$$zcB&cC7pyF>!9~*YUqE@)vI+io`htBL zvweb^9Qx*jCLp;J8w!2Groxo5kxWH zfQoxf?_N|Yyd%R}GqsP{00YeLPgu9tAIR2gt-|(e$SF%I)Jx~L6|9R2;3scKT&Q{r zs(sPJp3UYpWS``$0mQdg;rls(jM@S(&yy~)R6|RA=M(h30c(uo*?GNxHo?Ft%{aGV z+*8wi5|a>PfmB5Dsn4E0v3qmDV$mQhp?Lf)%|JE3J-Ae#(!sAtoyX$nE}-n?TLm{~ zSrslyiWIC-baklcd4|Q?xvavvNOh*%cSTEziN@7&}@=27VtR=qpPn{Hhm=Sv5`WW@!Yzg^SE4?yv|f=@o0{q6{7~H6 zw056bV&CtsSAX3xhtg^{n0~&Jmzrj$DK5Z5U{s!=#OaL&o$8E-=7jD16mYC91_|4Oc! zU}jHkb6FopsSE2boHN0|;pxP|=<$ok_j6Lq6&0+UrU;4%8!$wr0)xKHb$FNaesdJF z)>50h*2+S_U>AeE{e*rh9p=hqGKd(ZEQXRDX*~MVP2N`w9{w_Z8wu8zR!@xF?5c>tUOlxgd4t&2y61VIIH==) z{v5s;bmA=Vy>up*a>XU;EdnEY$y-c&Tntk5*d^+7{NU1$CM*5%`q>C~!yh2)cF^H2 zFL?|ybsk=V{y8iiD$X^lTxB1S%7@ZUUN4o}{jaDyU)6OoRaTKKw~6BPPGkGL726;LU7hkvUD5uZMs6&p4Jt&!6e?WBl`@4li0oqik%xPuaiCb#JQh%%5 zJH1c6Y?)rApTq7IXa1?X_yIgm*k0TXW|!E*wJq|T@>*EpizY(_MM|g4$Iya&S`UnH zJ0dz+Gs2j7&Y0#4(-e06yeIbkcBeLoMo(=dTQspxm}8LBf#P4A*e!$HU|$gmOxp|o z7GpZbkngRRtpWS^)nIp`g!d z!m23_bes0GLLio0PX_*78#~@RfnkAG>A=+kW{0EpXb^I-5b2T-dq^sl8>fJS*a)`) z_VQmZM$(FD#-R8Szj@sfK~)107uEd_4LuwRbQJt>7`BonNJI_idRztq=ZA%gWIo#l zeQdf0UQdlXcr>=zDu@^zv9t}-i-$5^8XcrvR1`cnFh$SM=P0>p6{U=1`urAHU@}KJ z6OY~vi^}_GYC+h)Y4!=kdaJZ!`<(-E_ye)kAU1aZtw%DgvCbOaj=7LQ{?4ROd@s;0 zE$3T}fp_Fm$JW$)&IdG*Jh_fZlmmc^=M7)?qG%t?pQXy+ywk1|$inGRK&JOVqLz)Q z{2*ws^80}RWW;sqpd_e#qafDVUS-Y@$@^1-p_go?+k(gtYQyUF4AxZIBT7_5)xtg> zf{nm-@9GknZLJ8oHMSf%GIfzIV+EbtGi2gDO8%@}F|f9d9B9?Amy-y32O@mWIxOgR zV5Oo#Rx5rSQu+>C*t|Xw{t22R{^bR-7LqYcX2oDD)hwzRQ~W$A9^ZF1T|8fvdY=+4tCzP1)TH^#~{AvO53Z_WSkXz1WJ>UcUsk z_D%w5*cKH^FDpR%uw$8b&eD?Y)LiL*ZfDh^V0Gh=6$qlLIB7%+95g7J9uc?2*5$X- z2#GfxR`Ht8x7Ed&yaliF2dQvI7SpKw^>DLWDJ4vRT;stnR&>!MBTvoWsO!`FCJ3+5 z3~YjeCC)xSLW*C}EkXAkM$e6p?IHe-CTnSYQ+R>B1P3W|_}kqmn?%y=U(oW4NgjP; z%y|^C)&dfbus=xL;_$vW-Di|9Yw9^sfU1X6+*tnhW-uWj6k3sl^zOXUqrAKL->0aC z5Sb*uqI?L-ps7vicTiOVsSccyd|r>7^1b!>w0NTOdkA6q*E7UU3&SzB`?ek9rTc|H zKd|=WN!`_u0j}I$7&oVd^xiKnd8|a!@5oK>7^0?qfVSYJm2t3MUlaT|ppCV)&$7lc zxtIn(ev1QzR*!*SXjh6_xG8}S<<{1l?g{&jQ-6M!Ryv$KQu4LNj)s z3h#m*Lq9jI|8;b{SnR=zZ^CR~aA?mi+fwP;3U0gk%*do{!yf%gXY_uQ$q=yCP?c+m z#|%eG&(ecNzqTG=o996LOc|8EfmT6Fj%Q<935EdAk>)L#Y)90c zc^71-#vp4$=!4#AiKMW^)kS)c_T>95zLTHs;r>Y~;|n`2vXyk$;h@Bt@smv{lz{1n zT3bqK)e`iA2udB=vvJE!R~-4ZC{j4C6QAD;ck%&=LtUx~P1C{HhTB_n6e1)FID!OA z0S>q3H zw{3hLOQ?PhQGhYK*unm5*MkH>1-S3L(Q1RXGWw7ncSMLxB7E7TDemczsns%@Odt|oX zoTdo1&m$b>3Hiff5@d&My3l-sdxPy8*pG3q!}~i{0UViEol{+~zaj;D;NQh!@L&tu zg4%z;QHST-sS?88<6>k#`zO!?eReR)dG$y{%pLuTL}g8M5}%1wK{1~w(LQ~qAGhB< z^F1yx{j^WIu0RIhZnQ6U#I{AEi4kGnjSSdx8kA0?=4-X)ss80~URvb{WQOWJSaRO} z;qn1|-nB|=^te?8;N9(HX<=j`k)`VjT`sF#fVF(Z46 zQ$COKM;kips}YSx8e{5W-5CKi9Brz1A`HV^Kd-Z4brcXr8 zKm=_D4WrRU=Wp#Kc}{q8B-r0r@*k~yk&a#s4{L|hHLWBV_6mpt5|t^2)E`0kOUOSD zW6zdgNMwTC$4P=2BML$`Yd*3{nzMeh{5&#XKY$$NZM{xmG3t_O6670pUrs4jQB+|E4xB$Tx8C`q7SNb+Ov z$gfQL+Cc(8Vr?}tP=6K%v9HZN51THMa*-CY3@qk{u95GMx+cTLNs%?R<>3uh3=wT{ zsW+wzUCTVBOe;ipA6*b|r#*M<0Lch`)cuA$Ryko6m3gmxpSDWGwg>#X?;0uGoNB-zUZyGbf<>cnz4FZVR&D}5uXPgSd&&2@F(f{INP$P znK*wCR)x>!<<#fTgrOFgaK};_`SUquH60)umVdE+5)9L&WrZE^x8)~TVJizqKOti< zxN`uu%Yb)=D*J|)5M?mhCM-%W6Txp&?^p04)fMP9;}|D47DY6_4)DEU1kr6pqI=Yi z^ajSye`DV0v@QVy^;EK-g zLln%6n#o0)35k8|bbUN01M4zG9};ityeLEgwh%XhRVAzhkoq*pqj2(vlWy86D){fl zZOY=$RT|JEBM1X^B{6K;?iE>U zj0^&22Oq#pxu0I&GLV)Wa=SV=m=$INQUc7Qw^}BgX0&)(L{C|*M_V&}gfh+sUEc4L znk763)P@u_SWFUvX>A^V4S&}IDQj{r@9p_+{H=Rlxmcsg?jr9AG`#2PCu1&IJ(2@B z{?hTyexFYNS-cyO)-xf+3xU;NPHiYtKT3~c(gtt9elncGPKJCw1+t@Ykm`_dQe0mU z;DSE6Jp`71ZndF`onXAHcv^(yeT?pj2Ax&GpjpOW>91EkWr+&($K#iCbR6en&hz}y z-IJ;tsh%2QK1mQTeR7+XFyP*mLU9hic%S(OzFrJmc8_c~`S^|JfHb~yzE>AXv40P> z_ak5qgDc>ILPi5U>nGrZk{vY*7dx*$BnKA)V%Z;)7Zj3z;}_x00lAI)`t@X#cy8v zd3`9w8Y>sw5$U-G3dsSygog;e`bbH^DE^+KTXVDn{EpX1t&G4){sm`zFx;ve_czu{ z6KJs^NSu>{R*LWFNK$u18dzoRvBpra= zRx5?34F%!&jgJw*6UUz!n=B<6-{61F{>P{ zn0e@T>^%Mjh$`2iFwxQ&#RQPD;>H}IQo5b@PsTtA`Bk8W1S%^O5G%@k-g*R;t{vS# zH#(pnc;Q(STJifc(#CA4pu;UT7sBuUZM?iQ03D+?GRR8ybo2r4<0UYnH5kL9uUo8I z`s?#qWbW^7*)Q`Qy!~Eq1z>Vb$Z&<8Sc+%XCE|TM$g)Rfa|;O4!anI|w_``EXBWus z9?-gtd@ARY5OcefkJT1{)!2H$xGj!dsxMJaU%QZvC~=9RH|qD4IZ*GGTLs_6fC^Cj%v(jv%3IH@-O*Ya60w)+*)>sMu?zWXJ&gj$I+LXc-g&P+NEf~q;6d2R zyGuqHBQcQ43{jk?tY8YLt-C94XzA$3H(Yw2+IRCCGyk3=)&K_dYEa32sjUbUqyx3O zu7tGMvAS<*c_{@_`EcrGq`X<@Mzf3l&^R&PzX%R-<{Duz(8vhL?M9G~;A*cjomLm8 zb+J4cg#aQPTU7ER-E}C)X}H-=B$i{$L;%(WO_h=7VZJ~$s=&lfS}4hj%wBKI;M7Aw zEnug?TBoLfM)HwY6-nfZT-*L8-k7^BT$=bZYo>{5`v?K5HhA3DeQovBmNPXK*RC4? z9WP5~ybuXJA#A3;q;@%=a)3th*EHMR!VNekMeSIzTWS`BV$KQHhW`yIwoYX9UKQjF0>oG#wId3eD>o*^q)1!7}EJE|wv zqE3ji#C^FO@Uh%^b^aR^Lxy3j$TvF%+rUOKsOy_FO%f5#@69l8-GY~V14j7CpsO8g z_?<_obSG$%ek7jIUubCtz56Xwv>iP`iw<^DfO;$3wbjv!g9!uqWL)Q-cdn1M9fL%7 z1v`OwuL076sN@w;DjPvHj}_DV@-A}8DS>tAd8)KeXGcLJ+N4ZZ7E)2cdFo&YZKHU? z$|Fn4Xq#V7zz7~cgjZ9_U54Dw{T$HA#5#~{$Laco2PuW7r(GxzoH*`Oe}cpW_B5@Utm} z$3xz)xn9AYqp-#2Be^=D+PwK?%r_ z=j3m!`C*UltpVit(+LTz>3-GGx}OBo^_vjeskf8>jQz|3`k8h%Qwm~|c8A$}^eyWM$iNJ9 z`~9Rf1t7^jXfHH`P*Xmiq)7wwmR-6Wn2%3bNE5c%K05t_IS{i`sD9ZG5NO1*{EQ~* zw!J65B4B*yAL!JBaQ=I$oI^x^pLcHM?3$}*-2fj8G{t$Nbg+?rwnaAO#a0<*8NZPq zd2db#99b zSUGS4OeQuX82PBHDTX)l+gC*m)zrZbu`Z=%dOQvp=L{$dPG8<1g)w3vW6{}C4=K;p z;s>x0I{w*XlzOdd`Ml0O>*+1~dq&ETe7(a!Tptg4EM(*4;(9!BnE8GQ!mC&1nljz>>N7!;*>Rn1uj%vL3#aN8;_OzGSez-=or*DzuA&1d8s=Dc}g62(0E_}4aEGYW9Yw5HMo1mW~+4QZx z_+uySbl3P!O%-I^&WQv2YqYZgNDlU-VN2V*qI7%|7|m#vc^np`e_-_~!i&UVh=jpG zijMI3LIC`hOJ5`M0J$2u9F$m{4EZhQ7_!osX7;+pdyU*cZuRNDRvmqbiB(^%u0PD{ z_xqA>5CB8%u}cjHwVB=}0O_A-7dYYHgCSK5!$?0{MnwS*otEw8h~vPV?t}Q(Zf%j} z9T$kEwI{1ch(FM4bh)$x0(w}Mx}LqAh)D3dfLk+NI}d+;ooOWiDqGzE1HyMg{h_P& z1{fB`BfI$)=Td^EVHEn-dCvv)N>i;0^b-w&hVQ|)I+P>yKGIb+7^58zr0|&Ub$PtC z-7i<1<}i~wZUT8=@Umu7#f-sA<(>yG%#n@6FG#C{a$V##zhk1nE9iiErS z_7Jj|z$oMv*a%qG0#Tp5B8 z`dAXS@S4lTl0WgzcS^&`3tt)fxwcg88qamc!KNJ-0WD0N2wjkRHy@lX#WwGcko_^z zh@FG3iK|^X!*If@9?WHE)_$VT4orz*rh(TuW6GC~ApWW%2?xGMUTu~eJ1P20;Vvh z4+c7>YR0M6fkLsEzZo`%6xE@7uGiQW#c^~!=@4C-_6l9_<;5@Ijd4}NfpZ+yAI2a% z^;3U#uo@HfjTUE)=Epa}{sTVZ`IThj3qDD6Ewf${srQ*x^5^UOAYFsrhk zA=EWfB@D?E0?%M=mJe){InJ`Ob(nHn1rKz@6jM`_S%2y5Fu=iZE1-=#zXgJh`IqAe z38O584-7fW;csF0A4ENJ;2*m|mGQSE=?cYIiGYo7P^weboptW8D2A%O+C+DYJ!KSp62ThQ7vUsCngVTj7lZi&>juz3}|5s9dDirq?ez0ewzCvVx0m zr~%%rRnxafLG$X4F{Wv#b9%rGH}vic1{0#W@;KZ=wDv7h zGFxuS%S~UucoJo|9lkVUr63ZZYf)x0idh!0SyG~{YF}-_HOgLNKeiS*5+{T}xbAQX zvd!G0^ue;5GRKu)8n57bS;y*b-#WYE(M!`?iPbvsyQu?o^L)55}tqk&qJ$f!ok1ZM*UN-wO^#Mr10B%a&4M&&4`cfI)T>qmU| zB7RI?q~Cx%lG{f_X&>og(a>5ohx9%TzpE7wAg7LZ;69GX&3?mc?g6o1>4G&^yew8N zeqx>S;Y=ti$0K1eR}oiBFb!bdCzq!V~YyU}_-~dTCvCs^HUp#r!%?)^rBpPw|jwlkfdkBIq?A*yR9zlCsPXj8d9) zn-L~?Xo-7ykuChLq#Z}&>qpo+$p_CLmA{MBt(ED!WIfW*a}VJJD~YyRtSF z#y|G{t2uG)i5o`mPu2S!T>I2h=K^M<9BQ8bTSd+}bF)hc5GbKU`1Gfj1PBuz`2HZ#+D`hZgTRG0Q=Gp0x;a6S} zb}Ys6<8`GYaW$D)tp}-EMNyYJG20_fV|25(zM!XL9f zUT3no#cpKt4Hj%wy|8G`S3P4i-q1V)-ZI*sB>G#qn@!oPU4Ab2RnQM4gIz%cTg;AE zjJPNbv(`dzeXVI&OK;Nxy_cBG6uDtfalLQ#l*Z6@OO+ho?UrQBTv3_y%+&$Xvm66F zs34DBtp$i}udCuKZ~-*rVQn(Wq}FVyM*}=u1b+v~+7!=Isfr@7dZk}%F1)VOU#vO} zZ0P#!oFnChO|`?UH>6$}4$JfMhI8C9Kn5Vl=dA~L*Oku8~}4M%M7Et8tj^%4uwq>Lc7$gwm} zOi8CWvD1kr%hL-$k;vPY?bGvwZ0c@nST|R0X-Y7pTX0Hs2M78wGFM-2tV*NfmUd;j zoM-9YtY%_UK_Pmi(NQOan)2CoR*)>xEOpd@l=C3gVYst0;9{q4-<=P5nEIv6EGl?* zH{c}P9rbX1?UdG=bO{3Kl-Hfk;91tTF>bdvD_I-uEVc(R^KQp&ur;z*$c`Wu zstl%zpteY#F)ZbrSWiS z)nGU^VS&%1k$25er%GtUT05Pn_l6~IU51d7Tdp(#nnDAHW3t0_hO8;Ln;->SuUl-2 zX-;>ONh($DWd*7z71IRS9RhMiz6R+j@ctRr_z?zGH|O4eq%Vbmd2t+bZ3 zYxHLM&NiL5m|itEGPGrbnJm|Cq7ID7#_QO8FdbDYIicvTO`I^hcE)Oqm9kyH5&}K4 zvUzu_0;;4tDa+=VX-rt)Z~cDR%lLG8lUw_}s=`h>DOEBGtM)bvQ`RD9g*C=@D%{Ll zD6%wj5(|Q0Hw?|??XpS;o$aD462r34F-F9AV0!Z^DVGVglg?y(rZ*vTy*5>84h$yW zo^(67zAO=pgmtpugOF3AzihkN(lXsn+q~Bt%m`x9Dbi|&=%g!ZW`I@7tp#snOpn~I z2i@8_BVseZC6_lU<7N0&elhFrz+vu6EP&~Zn%Z8Dzzs+fb9_1=R^6gIU`3LwcQ;d= zR;Ki>P-`@`1vV|@aztS^Nmu1G-7Ig!iiwGimRG56-du~7@xtu%5;8mKjk3;cQ!Wm&0PAX3FjSqPBrx`?#kJwiBbIWog=8Pf4;kT2X}FsMUzIvG6yh-6^dA zEUFs~xma0IU7i)&qh+e%U}ML{OC_t{T&BBQ0iXvZ2pFu!GLfAy1I84yK zjSagPLKvc5lxv){*bOREthH_8y(wY!nw^H}OoX9aNfYd7#bcdjkH;$zCbnljUQt0| z&hOeAttiil_842U-72nw!rW$xtYy>}{@kTIcwXsB`p&9kxrREm_@S4B7-fR&;#pHS zOEkDiI~fkAx;30#0i>c)oTnJfPZ8p}C2Dv}CR&4CP8=jGftgq9lM5@+=ko3OhEQx~kR4my4xgU3gd(7YIKvd?rIF;6Cwgr-cX&dr6fBk9WTq`& z?_?MJ)NhEb9L=N}x>Fl8+nO>Nkl4tP083R`Xq^mz(}<1U0M|?3oU~FbC{IP2r!!d3 z7X3`i({|#zTdsJ5q)+O!r;ku_wK;H>{ifZ*jA7SRn)3jEK!Cpl&bT72xrAUjx@&Bv zsY)pY5R7?(+E@d(is=OxFBo-sC0S*rWi)lt$W1kN%wn?yZ#lr>M-9w0w>@#yDcjwI zWmhBXz!7QvC+AqNY2`B&z@j8IV=AH63u%6=&DDmY3+r zMyu@gcTF`x&3ZPu?vuMjZ^z_BlHIftCCi6+c6PL8)Km{%%b5y{Byls}U8W?f>ZJbv!ZjsB9&j+w+f zKQe5lwa`1vyi%f-vhMpnUQ1+oKQ-tivK@!%);3$p0r=h4(;*5u)Uud0lClYDU@I+9 zY`NNUJ@Ph^(JyLpnXw&XI<&^BSKDT14aq9-dAgZzbnK$p*_CG5D(CNp6^5P{HzmOQ zY;sv^J9aCT^r%lWiZgDH60`h7^0w3foL3$*E6LTGGa>3+Hc`t?r{=<_;JOKJ4b7kd zwNT%<5cIETHJcJKzuDRpR^YBT3Aw$>VYaI@I(5l!WZ8un)stZn-g~F zQKMG$@S&5@TkY&<5_cJpnnL9g24aFy$Ary^6q8{T9_$}<5~ z53H@yJ!7fwMoucl5}QiD1FQ}+48ZrvMSf0Zwq9B4**sRL`rVR4&N2 zjJ<8+R%wGRaAn>cPa(cAQbk8yROW&Q`+@V=bfKqiF%Ok26a@fmO*T#NCpUV6+E@9R zIS`6=H)Sl>E8HdUY%j}t-fES%3rtU>>t!yBW8AJWoz45cZx$vlrs?C3U}}J-b++lr zVhA=+CpR0{tFF&vNHJBjq$~(@XR{rRx@vX8DWde7wNR zeYT%Tw|zx(wo+%g&AH3gv{LBPbKYB5Mm1|aaB5<{F&W}k+P9a+SeSIvsf^XlZ1}u9 z#2UR0#3d7>ad$NlX+VwErwH)711%X`dxKU<>Jtf<)!2TP)r-3Y;LB2(JO}rWG{s!y*wyDYh*+E_~U$6RV*@+sa;rA(ZK-95@B2r_wWkWxsLlpE1$V%J=?ZwiQEr;MZlfULb0tAp zlWJ)*nXRT%mMf`jcGt~=DbuU=hCM*Ajn#QgY1A`pX=GtT#@PuCX;^8jAsVO^aKIS? zZ{~!-ZnG&4Y<5tq7q*B`XCO0q46moA8*?G= zi2B%|T@SD|nPS52WfFsR(Ibg%cV<_df^Q7xBBL|~!5xez&6*@hcg7 zu+3lzzA)qJR=d|RHD9s1fT7e0qE{|;iX)>wXb5u3W0Xz?)ibx&n_|q9llg z6)QJt%{e{B)8nn$l-owB=xwo_WqJCRN)09=*{S>WPNmYw8(qIUTS&`hPVY2E^A6BY z1ZV4tD>nPavNs=kJ1#re=2vr^U!_L#l4k(sravxp1hNN6Yky>KE!PHy!1fDUdQq6C z+nTPa)p8zr(bg*-a8Sdh+rnlWOrlDa`eLJ&d>VGV+oM@pT?idUS&FjU(Ll;2*0pif zn|G)E{BEEEa)}}U6+M)f#l|Mj+A}jh9W8KF-4{^8qm^4L5}aVty^$j0ZSW`NL{DpL z6w}fNJD$|&iBxo2J8Q=a)+DzChZeC+^GiLAb08KMa;b@J#nsyH_EXi#+OslEKtc56 zPT6wn^)9MI2Vux&eJ4|;np_=lY=u)w8AFzRDeVz#vuPQ#Zj~DpBu1{V1Zy?ih33m` zVOE>0()h9Xp2p!y7*)GF6-nx}(Hx`m1mswNw zc6OrLYNdu7qesiBYD-cQ?e)T4quvtydXyS7eVpE`JxWw&Q#L>Fg^lm|O$o4;JEm1& zIeJjyD0kiwi|m9*v-X{|~dwv<9xF%oOGD5gZFwv?hYxKe#YmcURZd9T8Q(zs*| zW0X&+BLmbe&vyw5WP59@Tizy>F{OHoD&VbYdO@~YokT8AP8T9OC#Mx=k;u)K{b`$A z5OqIA+9je!u19X8IuV-d0lDrf3#&A-^H?k0ryLyc6kwKRie0KU*%k!N%oLdtL3gm# zWU!I|A5u^R|s8VufnE>=cb|EQ_p_Of$E8A4J z?v01^+RP*-a%QD4fP$G12Yop+EGiCx^#G_^6SzTp2`2%?& z9E?V`R9NhGoBXiT+bJ`#l&_G34Zr0&UC!}&&)zgAB7li{TS8ZAgVI`C(>_47Ifk3% z%PSQk>)9$xrrD8LlX`kf^CyIa%dO>{P|a?-IOn9cNbTnAQkjD0rEv(SnAnb}E!WLf z)0W=P)>6&wY(w$+@g`eQ>RYGSAqEiL;_F4RN)f8jN&AMk>bqT|RyS)E3wK&Bxf?ce zQ=J5m3c$2k-tQ3kB_!mg;|0$-*QW$+Fz8h9l`g3r^)lkyfz`6#Q*@3z}^m8A>Qrk>FOZZKMbZ-URW z8+UHz5=|eQOQ}?`3r<$Ql*lC5Oe$q}X>Pr$C}L}r0-F28AVrK5DVH7jR0ps%gI%IU zL3n>Xowhu?SxRj)gQjk3i@J`Ll%bmGv+E4#I%RpemgR+Hd3DU+X0q)*-zJ*+Jl8X8 z4b|@#mjqeXx9M)S+}4#LA*eG@2-`lhBvR&*Z}b`z)&&pF!r<}^v#Tp@(Mt^S4W+<4 zg?S~{lq<`esm}0RKjmoS+C-hSq>Pvull5GAvzX-hIi2mX8$uw;b)66?mNmfVJSw^( zDB`0jvB=M6+hSve&j*{5jq4!cdt`gsS*^>ahBZL3faqv00kH#LS5{fQ*;I4$b*49D zJJ@=h7;kF=xox!puhptz^L*1?H8{RmW{K3SK(oDech%{6UMnR}=DXgyIrp-{Zb9pe zoSW}P(vX&CL__a2#e@O@h*rXBcT~mL4R>vKMMJn!O%15ZXh=v?$HRH1(wfy5-3^Xy zi%fGSwl&{hHg}8kwA)w=8_td_I93~AYCc3;8V=Ds&v5jv9NuAI^kDfxX|;l z$IIK=fYix?)3gd{d|Q``IlvV3NVXzuT6(6hCL9RouoDNc#D#8y?b&9&qx4ce%W2M9 zj65M1j@;YL9D?s#Y=^7P!HGPTGkwa0;Oi2b>AVHT0`8WE0sxl$h1Qxn*Y<3}T z(!E52%_dw^;9Z{f*Ij>N*X8C+TkspKWfnmdS>ZyVra_3gD#AFH!-B;zg?RML0JfE( zQ}Uc=%JWHHsHEgZ6`QfN2i|dVUh9->qtynV+=>Mp2kErBtWW4Uu_HHAt5RKLQxH88 z3rhzOv6I#!Pl7*zv^Hi2!c#r9QypuS4WR-Qwzf%hb~2}Lh+5;)y(ayG{lay2h4IG*5*mLtO~Y@Eul zJHh5|Hp^F82u&HKX^KqoV{)ftyg8@fBLGjj1+qRMoL0@jr=5W-wivcC>Q%^25$l3K zH^bLSyaE2~1e?a>H36Z8MAsmT#&A*t=X6d57zV?VDJ(12+g?Ff;;BK8!@OPIs}Z_Z zT!LXc0w9vF1KOiLn$megbelvcL0ap4txc>|vRRwVbQ;?#%`(hGB@F@J$|D%99zn=_ozfarQXretB9>u8hNBE?zDDTsGtw66GDtG&XcYRZFsx^^mqObhD}rCw!S$6P{D zM!S9+^sw$C4eHmbHuhX*NKQ2cOBbnGwUq)->~NXo92@-V-EG~U>TJ2yT)JZyYl&^g z?W77bCY{0rLv6R24Z&*DWueac9A4K)Qd8n{L`7i%8_O2ex|QF_+vbo~y4`KD*YbK0 z0HOitSQf@D62R7Je3g@YcIDSPg|v~{v{IB^0*nz$>x4(v=(5UilGe$lCb;fb!4J=j z7NvwVvblMoRy2wxm@dk2`|T}~)va4=24T-{6t0@+==R?2M;Ro?4SxwU7MEVkj@>TnQE#ctmBsNH}FE08s)YM;2U>D-d`=3bg$yVEaEw}Y3#r~ zj6ea-yLpy}Hqc&pQAD1S808ce*EvzRl$j(mBr>zE< z;N~c?ngBMdrtlLmaX_4I@jPh7_3mT{T3uUGhSk2l6b6e?p*5&jOlP>FjlrPOo6;OW z+!V3t&O}0+?yCG)gC$g{GK5ec3#FBx9f@tfYx`wJD0WueUcT3B%-*jsZ__ZcB zM8j6YMKQAiuB~k+)HqRQN|VK^zl14u!9xa{AM~{ni)&OrWkK|9y)HM4%ACY^z5Y@{ zstt}&U|Ci)v0M{qm$0i4l~;Dz`m9=Mw00eEREv^6_gSj!7i+jiV1<0ACN@7QaJJE25ZBM3}!Tu%TNWh@cSc9kzEqrp(lVdKtd zq2)cZ(bL*1+aeV?TUM1hR-fg27`WG)fI)&lezP~*;A^6Zmp7F%r6lqy#8}<5!-(8M zmn*$(s+CT_431Q7M$BjFlrhmo&D$)q>Ui!o$aTJ#@A%cZSJQh#3*a6FkhQgzoyaZ# z$|n}h!AJuMn#lkXTh7%iyilH7MO!MCQu%SgT(|fpOIjUxrY4LI4+vN|yJoVR4!8{D z^eDk+Es&fej#_9dT&>j_86%aqhy7t+(E7Vph4YL`bKPvEQ>}GYpSx&08jV-opz+kA z+?)tbVKZK90zE5KU^?7l)6aVRj%X&tWg%Bw@EuJW(6GKZVZ3a$sF|jTdR?RejnwFQ z^pU zH#%tN#EQ*xsnl-QEEV+0N+Jz_@J?qVKb@&{a)Q@)H{fz%GlS7mafeo3u`*bzHLZ>l zlbu-PdLGl)sM)%J$zp%uvD-ZODH*A(ka`)Qz%$-RcUqm?$oCROV9t)*yI}3i3Z+h|&5qaUr3o&jwUnC6^_(X6o2C3*sda>{W9t=g(vX>c zX2H*RjP=J_zSC+Ki|K5IsZLx`9@Mk7o>&?JwgvF0ikBgpc0s6CD?^)WK>)j8lpzjM zY{Qn339O$})4~d?g56acQuUfPC|7zbsg~vZnp1BUCln`>39V7dR@DZVPIrL@qLNVj z!F*a>=kTVr_6qEZQ8vX^Wl-ETlw5&gdb=Xa~RT`CAtF4v_D!KhRPVV3h{BG+W`U_WJo{W)`2 zvb<7CBfY9+cWZ&e(@j#WV$QI;7)mV&H_ch43F|<~rh#rErqNQZ$j>A&<7cKe$l7>G zCT5gcX3FCYTKhw?woR3^WnV4rB$;&AoT$&OOr9KNZK|z`o$1Y;R<+-T=asqQrqL;;CTqv6PP3HXuaa3d)yh$1&E>OWc%Xb^ zoQ8F0D~?;B^HaN71+evyf(RTv<_*{f6}1em`#RQJ)C%B~hdWP-1uhN2ZFq*I)L*AF zW%#Wy$sroW$6^}9>)?~_g%Nn^{f$#H%?ZzU(&)duR_HCeUR%#*|M`Fapa0L()Bo+d z08@26TX`}BUHRWX0G(St4Fyem%K!7E0y@-Hr>bKA@1H{9KXmv%I9$&C?;rf1CtH!+ z|NZj=CpaugVnmXlnIz8q^W<2HqzalV%>MTejA6*YY|u?qQG*+Ruh&t~MYifHlBt<6 zHqNq1^aBzC{rvOfIf`tXmNOS*bA9|ybjX}e9R+TVB{3LE641c#M6R+yud+XY{Yxj| z=Xf~!5rR#~y!IU1E`;mdPX2I8p3 zETG!{>#2~YWmnPnxZxueSg6JB}vhD=i zE_HbNtZ5jqy5OcQ!BDg(%{&|E0XIX-z3$99%38Gn&t)rX2&4NjMf*p9hp?5E2Pfgn zurttKFb~ejl~3;0g8S#;D$7#jf1b>MOoH~mf2QFx0BRGo7MOV$11+%qVPzv}9xMRc zTq;0S;4{Z<7~EJjEYIEV#b6TtQ;ieiq%Np)IF4Q^dsu4016w9MGQlQ)P_SLmfneYH z=m`k|F`DBZK=X<8D_af54OX1sx#m>WvmXxHepgBh0J3D+z)rT=H^yJSm zt&*w9=t-XD@JqOO`G0xz9|z)(3V%g%3|(i;0*b>;!Ubn~#!RP9xwT{v8 zT95hq7vk%(>LZEescxX@+Tn_4&TK4#XONAB6YRHFWg;UhBHXjbVCujC|D5I*ez*CB zu8hsEzs&p!6w-6s3;T?Q7f!y4V+n>Vnec`N916J=d*oIO1)d(01gH1R6chnTGo7;ot~movESbPb%r#O)kF9Mni8`(UFX39fErFT5Q2u9ty2Nzi8}=IU&| zKZ7RZkx>=&M4v(QMH3PWze_n2?&+JR4-!KTggBJz5{AE1kX#75b18;~4}{nY=GCdF zxeWeyAu6psrJu5b^@PE;n>}UDMGy*GYl0+L7@QPCS;QY@5sxJ)21F4KK1B>T-Bb%! z;O4NF-T(}McvJAhFb@O2twqmufxGR|71)ue z6a(lAU;DSyXvSZi8 z5I>_L|LFNZ#9<(0;-i5`q(Vt zk0MsKg|(>6K?8F<(K$W|2x{YS38=3qfEapl!{7Hp9u~ai!Sz$bD^LN|;F{pj(XaZ? zlZf8({E~nsM+FE=60qEG=AcGhM6T`(cs)3bwLf*~X&x#JEb)^* zDT0E(4}0f5dnQ5)#d{{+GhcSkAkp%W{StvE;{6iumoL6w-mj8H@!fd4D6t9iHMa{+ zkswx(UiSVSgY5fw$HYee*Eaeo44E)LPmG!OY?-K>NGxPxA@kLBG8}D~_iUG_WK6tW z;_dRqx64sCyS8J_03-+z5&%f(y8t8zZ*l@4IoXouqf+}(skgvV5K#Q;sPukR^1e~2 z^HG`TBY`3>By;07%>FjSeYc@6Fg4`mjSxuk>W$doMjXoXc@YDIYRIInW012!fCfcs zAPPQ!K$r^QtaD}z!Z#&$5ahG~sk1Mo6d8pqg;ph>1WY>xr2FO=?vO-( zDwDSbcSGqxQyAy)i~;290DC&aT#?r>SIloM9dIMNJTB*UyPm++bkj(fUxmQF5^Ea* z^KRS;e^okafFrmu68Wl;LUyJpzM|bxQ{kn2x_^nA8FkF#@#PlyL{*k3w>Bq`y{s!~Es;?py6!ca8 zE`1d#w}7X~v4|E(2ir+Wrx%ENbus+anG$?W3py$TNEn5Q} zM}w004SH^BknD{?L%RavzIP4EoDa&~7?cYKrSBW`eD)E`J2Qz+!qGR?!2tQKDYApI z$b&~Xs?sFS2Ni$OpFgAyqE+%q^}z@n_~--z?k)KPO(t0^KvRDfwdUd8hZi!-OCHcNAWjqzz*z!S_=Ue1DpdN`uo6Np zbl{pOQBDMfa%Kawk)3c^1oyX1Qx7Mq1HU!kw}11-43<3l2qH!oRhX!N4WWt|N#N=* z1K})$c^*hqlV~lg&RTKNc#(keO;v`4xjl7P-&cACNzH@wx|b$VW*^P~EdrX*@I!Fi zRiY3t6Xo#Rze+$9`xQi?2@E;PKAbAYdx7ZO$Jmr*`ttielUPKbWhxTkJ%ILr;BKd=4Vbjy7mMHye z!HfOp?K=u!1eP22m-DGxROhkhsLo>%)uX%s7I8P0NrDIn>S4UU$jIL!3vSh)deQdK zL1FQ9$5j+f5=7uz``l+F+(W9e;K_%#=h!69#dX8N!okU?46b17K)63x^Y$+=e{E9Vtxg~e!(Q+KNZ_DOz&{VES@xH( zi{Gp$(?^q(;Sq}_K}U}DXr*yv&|&YrRvK})l}7M9@yV>TJGBG)XyYQK z0G1w?q~P^Vw1Ze1c%ZyGTEra2@2MTjtKXz}AqKEt+I*yV;T-;zwSl)Pwlsd7Uj*j` z8jl!8ltK-p1S1#;D-ISh$`$>qs+N9Ag_b+@+_SK1flqvM&;9#V*vJCIIRaP#_{C6H zqi?)mZ`avH8WTb&Nf5T%_k)UzECzXSDH4_=?D=mG$Be@74r*Ze&_27*`@Du|mU8=9 zdZDihe$iLuF(udI#ws5Ng^L@jytP5e1pxKz%yXScE^iZX$^ZV(n=u1SSOylAJPQy5 zEPfj?fFhwcBwTOv;St{g>WjkdlUgjMjuK|2&$$dh5556 z8Q8xRpdOiDgyIhzbk5(1d%Pc%#vJ_)fMLX9R9cKmi&1Ic9F<0X9#k50KtkUoV(>Fi z8gl~(jgG^Z#NA@+bWHAL77J4wC=d&_V&HV@Fvp%b;1$nB^U2&R!di9wRO6b-zzdcViK-#_`QbW`{ zCf1Snsnh2ffXs2ZT#U&FV^a5xd7g`cE`705L*QSN=8Cku?8AOiv;m_aN&r6)ZH*i6 zeOM>F>NlbX#CyA`U)Kx5 z+rA^JLB6Yt`k4elQUD|feV+v3&m{;eo(M`pAT;3{D?%Wo_u=QQv5~-z`ps|w zg!j9<90ZyIm_GKt=_76ovajjJqKW}Qb(H4@IWJWJzKa``)zF1#ROYTx&vh#HMpXrL z9F0l8?MZJqQ`u1a`$3_2(uQbd!Pq8y$5mUJq|51@3;egN$(qFJUH^QpJ&iy?{+)STQok@O~g3!=V*#M!0Xw*F(DPl!-J&bWrl5bj!)Ux|Fmz z5O~Og;44pI1m~=cESy9~_jM5wQa%*0sG}zW9^j~L`^Tk2{TccCLT$wJdE0Xp;=Xx1 zXI4CQd*06Pa^N}l-Q~Pk5`RMgq2mE>xNFQC`XPTT#LDk$Y9LkP<|yoC9aipw0+Dm= z;y!zxgX4GI=bYytB*}SM*&9p%2F)xdb++ae%iE#Gr8TC>r zxxs~k69H?#XVl;s&W63AKn5oQ=6>I>mm>XEV{LRIVDI-0dtR6f*4nLM_q&a{Z`fb@ zb>CQAAo>qHp1xpyvst^CH}lwkuAnyX_aL$grY$K=xOdhz*P`%JbU@l)^-N?SboRe7FmUajsw@e% ze7GfokUi>#|2KtZ?B2-!&8YtEp#JkAefmMwr_loV2IZ&W&_z_`DX4@Lz2G;Bn%xa% zz81U+d{fT^)y@N9XqX@l_6l6|z0EsM8mkd_lKW7|F%2FlcnDOt^QP3gye0-6$Drfa zgO0(}Bmbp;ay~Q1=h{gW}n`;WZ(XvfZ5LEy$Ar<0ZWVk(x`Mn0C z{m>>b*YVZ(r^Q#FE;Q^-0BihD0jwWL+Wm(|(-ub*dWO26iJISApFC6ZZs*w1C=7Kq zfje;5Up>P_z=mIdQ`D_9QHzZ$kg6N_th3u(z2dz6(X}hi5oXt}I3d70fZ;a{$iSO( z!-QmszqNzWXIkygBE(;#Wt||^a3WT-{)yGBSj~#ntXR#8)vWicS-0xs+*ZI?)b{53 z8B}oQ`Wb|_j@7JK&HBu0*0Dk^9{aLlB`fx2#lEcAmlgZ6-tWso6^^cIMI`xpCl`${ zzSrGfQSM6Y%8FfCpHs(*I>m7iF$54^95IX|hH=C&ju^%f!#HC2+gw?<+)^A$^@>%d z*e&&G+)~$2_fel$ilVNe?kSWU_02I?G3q`>-N&f=7i(CKfgzzDmo#>x3dLbo51nP65OynSqm4cRxO3l)GAPV_3<3m&G~!aZ@G@`_ z2cHS}AwiFfvo%BVo>2H>>w)9cCqh+UL9F8(tTPOj5Q2w%rXt|YU>=Kyd3@T>s3ITU zFOY=y;J~rZRSX=R&mIh>KnV%>HOVA@5Ga`w9SO`jAE_Nw0C(RTSm<#9pX=Ic)Cn2& zJD3LO+eg1aI0GF8jUL%}FiH~si}CNTu5NxA&inn9!k>kN(|;=@{1avs-zE5wBjq6Q zPbyciIG+5$P)QnxKd18kqxy8>NG$AsKGp{mQtV*@oPS<`2UTYuSRwnhi-L>_v4esK z;s7WYFN*KJD2}=91WUwxBIXmYB#ueiI7ZO%nuyl~tcj?nDMPdIn)tSB;#Y_kd4$0e zz%AmM4so(!oNO2;8^+0oakAliaquL03kV<7WxWn+kILNs;vb`Lok9IBVpLa*>iRUU zyenGP5eSbnc&uf`T2`!O#adRZWyM<714=q@Z_u)CB_dw*SUx**rhmniv*OBGe@4rC zrY9A|5HNuDQ?D8~lSNJt6a9431Id3*T$hV~0Q!n0xTqdWfF zsgA#m=owS6WgtC}_bGji@FKbZQC~Qa|A-01S%L%Vvq=)LS_skc2I8^+8r=W-wk+AjXmqy;rd zjnl>ACM|K3mbgjFSAbJ~`=Z*9Ytj<+M*q|4VspWj=9jW8>MsEpSb#VJ$~B38P@~sl z_A?DGiddX|us8z;QrvSa?l~6s9E-)-C(3?~8iIdt_A?dL@Q<^f4g3i1CrQxPx6!3LwuU^Dr%kiV1~LwwhH&TeeKwu`xA|`! z91b^HRIU@pC@e6&82Fd^xQ^L>B2@OGU-rD|IZD*s1hp7YNAnlko>RZHJ;!bh9QJ63 z;qDvwqDwoPrR(E{UD|(uulQ{b%-IFV<7UnrW#aQ2@!rP2O+_KHxR zYKpIDH_BFUf4Xc1bGvEWe9K0yuB*o*%vPJT^< zsImmmtb_DGX5_i-2nTuFhD0-VFU!2iE^l+o=XoXlpb|f*Z}c~)@e`{#kMrXH?i!}& zRxC11!HOmBvSK3{7Fx0NQDwUvl?+BD-((x35cl;_sbEy7;F09fX0xTseG}}(ffQV z_-8>zGBGmpm%@{AUzo3MNJ2~ukARUlLo?6dA}=-YCTvdAai6-1?W>aFJY@uFIkN%y zAU|DX+?tj+`_I{5cgVSAj`+y{)rGWg9OcV0k1%^4;z;{N83!sQPi>*-OZau?THgXOBXC1FXv!vJuKQY6c z9JIRF5v8VKfNna@ZhtB_(X&nfAQuBj3=I5cM{ecM4 zSMZKM$T~9S=2sRGtXk3B9QngZV=oq#=nsP9l5AK)6{@@Lc>Nf!FOn)2qW4y4*J@uf+i~NT2TySrvR_lP1)1XGlja= znWl!nM{_Kgwxl%S-dWpRi^5ArptQg083XX`_>UNH5(7?Rz{wwQj*uvF{#%_RZ_*AB zmNjbo$;aBkduSO2pdbcF$FzJ8El;X+&|uQWTI-|iE0+8fIL$FsY+XHqwnrxzb+BA{ z=0j5(zYb|5Hy4d6fd*aype=GR#-XOSIR`CG}l>;-6OElLNHM(739X8;K~21CYMOw)^A0uT@br1qhwKoRKbJQXe~vWM*i zX6W4XK;WqA4+p#1CGF@FT52>z79UQ~cz zsnXM7l?U`@t}N?Wcp-D3`H8Jhf@=yaqF`D7oesiDl{8kq6{J^XEC{D`&_!V?;fElxaHW23xzu(5;qk7Lc29cTA#(A_A$)6PZWx#q2 zC>(mXzqc9rI2OVDLgAQ4JdnI6ZV;Bh&&d0JpVe0><#5v};=`{8N=A)D8F)UPNYXg` ziGcIK8a@a(A56pkCzuAXtl_~;d#i#$eM?1wQ?39=Yx|iC4mIxZpiZuFYL3f-{%e9} zNUw)^L-^;tAp_(Cd7v-gq8K1Y;B5K0p30w%q;IyB z2<&K$f*K##N#HY#?W9NXUiyr>u~>VcSUWL6kro}xwO3e3f+0(Xc;tbek=?;_-c=vGZeF!>vI1<%&4?ZGNqVJs+U_G7?RQFvTV-yOa`z`24vgqv_K4q}z4&NvNv3A*qC3r1l4b67A8 zm6-kY+m9+{OW>F2^thVs{Q~~HEFFQY$GE&-5?$oq(KxI0}L_O;diq>yXlo0t7Rkm--rwyOm5#eoqx_Y0OW z#(_PMyhjvDj6nI#D;fFsj*+@}89z42OdQ=$D7qY_;Kp#=hbW!I(J=vWAVgn8+WV;s z(WJQo7lCGg>Jt5}*k|G$jlYgGzuorh^pcS3k)euI)u8Ja`{tq(U!&>Q16c-jiUz}XhW^n=9wwE+IKuwKM5+209K2f_Q;rm2Su>+svZ zf@-7q1U!^3s_+6qRS6J7sId{^HRuOmF`MlS;aqr}MESj{vsPR*#4%Ljo2u-%!uI4{ z?#mcLSoed{`MjobLIVCAUJTkZ4aNz9s;Io31cD?fj&mm=aE9j4?LLGOHyOmQeMkuM zq-Z-yGuJwiP{;-T&Gn496G_~OK$|V&`~Vl8G$t_E3hNfpxPwtxg#_ZcFNfq zS%E!t0D|5+bMc*%RgAJo?ck>F985)oxBTBfs21o{-N;^eIogCvRRho$GA2$=%z=^Z z&me%-<22il44(mR8BItGxO`|Q5(=%D&(NloAY(jS+@^K?-Jm={k)i{bC-`s^6L%|T zkAF64k3Y%ULv^PPsv;NEFyL@hAPKdve1m_K2frx94n6>8@(QgRe{)rT{69AmSPE5MPZauTP-9b zWMIxqwFMn=P^~u9#^rt@9G65#0n632(dRo!0ehHYKLsgHGW}4RA5bDFm;O3R9GAnw zGlKIdp-`}0J@AsRD@;(e8#S}y zAJ^9XE4Q(EGX@69t9_-Apw42PuKqyUMi3a9J*912&+GBv0qSUb914cU*nwZg9ZGDE zkS~ZkUx+$%fxkbG)PVzaBj5`txqJ{X0v`Rwo>zZKK^FdvK@`n29q=cDv+95wq9Qv5 zR^<<{Q6P{)Qy*1@4w0Tibr0;aWej{V@)88D)tiMP#^%=}i5t0SC@{QY`|G!Yw^5sev!x824#E}RJ>TO#^mYi(2>Xo#Ko_OL zqBK|p(D>%CsxO}gd*=d)zzZmy3XI5LeZ&pkzVG@F1s68`_OL*r&^to1{s=Jsd=mB3 z{^lHHPp9PmUkO4vK&Uxl_V%Bv?ASt0^G>WWcnwJaa)2(pe;r`n0`E%_%m(VUtq;=k?SWJ#{gZ4<$5gFW4Zo)S*z>NHBd2vjB1(*ukEQLM#>xF9sFd41DY;Yt0hY@U|_% zogWfaT@wo$g-4%p6u)C%?yX8a+gz23^0ZNY>>;xlFp8!iRHn)r-(Z$h7^|L z2@W0GAO7w+I7oR`zfb~4vR@|q#C&N{tuLk{( zR=#Emh$Q(0Y9$93p~Z|Mi~I1O?rE-SX~9+sK~1Kr?Vk#HZBD*%ENpbxT!55OT@;u7 z7bjr}V!*qiU&94r%pBOLQ$ul_^9ba{$&)B#bQaby+F;QaFVCFEE)S0ttb*uE!Q7mk zJNa60QWPBJoMGHCx9BbZC>$e3Uc9~-(lO|A`+^Yf^V~Hk;IikI4=96V2tG&@SYW~d zs>>X)VgM8Y&=DOptj7_A?7)Z#6lwbAz%j6bb-|F6Cj?Bwlj?%KJw0@fTzf@58SX7o z4ZoCWM=$)jP}?8=AgPAEO{P7Z#~o_mUog2(Pg?x1C2iML07qoZo(#nU@ve#RU7)F^ z_(~uXNuIt_27V?yVi!bxQ+TGq)@*Fswr$(CHPOW8#I|kQwrwYqKauPge?GThXfNlp zE8mq&S5AfyX)ueA4J$Gq-PJ3TTz+$w{_JqD(G$Mblze^-a)snnmfzl zbdvrGhq*^y(eMjHcGM#3+ZEb+zY;Kl@av;~vM9&WsQ5Hf``CF^L8LYQ18mC8JG4z` z4Tmx43dZ&gQvEKG@K7}{r;BsosrK+N4BKIH%xIj2`!9I+>lkQow%PEYz(=nJ_^4VL zZ_!pMXZ+g08le0+oq_tmgsV0sUPB%PxN)brzVv6Zk(yf&Zh&}l$=^d}o?bFTkuykc zj7zosXB3xm8x-^z=-)v3Y5!cjrY>3o%I3`Y9t5&9Ztq`dA^EoPWut}twzf3;S8tBL ze=UHSC#a^=cO>h}_}ePG{R=7Y@R!>bE5nqL^d}{3?M?O+i=IxO0ml1SuOiRmN-%*M zA#vpx4jP<;qd%67^roPb_;vq+R`Ffb$-h?^veb^aI7{))1<(@6SO@np!oJU z(5BA)hm=px6=o>*PmldW1MH!Rl6j^|lf+HB&)D{eD<#u~0RPV6|5clyhC(N%ek-(^ zZeTDcG)VrS1mv_p29=v$62h7G>ds}y+f+hWLGQs38e;IPKwH%MpDubkjrWJ65@1m5x^3dyHT_t8W0h2u@P zM3OgeW{h#YCy5!%3(TD-K7obTgcHE<`yreUJpciPpyIE&sQ8Tel}hVx5ap2_ zF%>XIPIbd-3wecLyT=#DzM{QDcJIjLR(=7|luAb@Q`=C5x!xH_=AX$u;p*S<_#m5Z=#j=#uOHqP`MLwlDrZPzbNYcv%uLIRHo`KThnY zL-xM4IIP4o$CVC4%WD^xXwqfi5w>^#q8BdI001r>!*bv@t)G~i=~^Dse4T9<1#7x8Z-V=}AI z`5XtkmI{y!)NS-at5AIZuK#&7?d`5=gL~e*Z<8hC8I==fKT7+E&66Co5rlTqb$ILF zIkzevr=XY+glP{tXMbxIKo;Cnfc5 zxII4(TG=txZKie4buMKke9uj&WqBN{if_(-Ye3+8kY2coY5Ajf$Aig9~- zRp11T_qP;B@G`+n*r(qSrF~8G%lNk-!R(%>OPfu!51*tE;fjf1!~7Fum7(7=9qLjQ z|KFt;n07NDoe%blADQ_7+7usrJ(!2nKK3~yiFdSNZ%HR5jHN*ipP20#Ltk(`&SQMi z3P^(YcEh+yKruYl7kiDWKnk}aNcXIZ4u~4*Wyx)VF8EwAd?E8AEQvo-7Zbak60{!P`^kjh8v6Q zeH(&9;p=UTNB;;q@TKPUBOHI%>B#l><$UURQ74F@*u1Vpkj_chd9z58WDZLg)X8s< zC4lv1V#<&RKfetY`YDUJXo+9_{w3C@T*e*95Knc8w^50<3AFn+$Bk&|0B?|yofy{cMaWQ6L)G<` zo??ePFe(a34=fkVp7z%9FmAuNgfX@G!G~o8QlwKV@hk0X;#TlSV>5Js6WB>oemxpp z`ip!$k_ac}-|zW$iK{3W-L70DANp%9{N10Rl*mk=+UAm$W;NfMudWXIBQjvJVldF1<%7pSq(La3N%?nVz1A%O}Fls?l#^GQ5U~ zeN|HiRNMIvAD(NNlD}QG=Iy65dILfa6-{W+x}~E-V;1TigT1FkW)Bj{D4&4Dr4wZM9zZEL*#Ib&|j$%v38XoGXQV^>?$+TpK*RQo8KR2sB zizUEE$GZ8CE)6YP@`j88akk#`DQLz-KxAkj28UO?`}mMTNpJJOWIz5%fz&e%&k-VV z#Qrf>S|H6fD>VEgWQP4!xN)zj}az7cYo~*d+iEtV9X1@xCLut9om4wj}jh z*Dd_{V+$i?qU;eC@=JE#3`J5tZ~r6OkQX$RDBI)mYqo_Kt(6`%`SkM)Y)t^D%r8Gs zRre(gngED7LEHBpG-aFmi1|w%fD?Ya9k_77Mh6vLJ z{kG0@bpP5*M;T$YBQ`#Har$~|S(E77W&HUz7}eBpjQf;4@z_U6!^z^0bGzhTfRqZc;Gm_NIV2ac_WW%(ryvSc$;7Nv`~nL zZE1fBbMvhg!B;#c1+_72xvpl?sDt-sU zBJ8#9oCS2%ZtW|pz|9#6f1hbJ^i2BNf@4KmjZHtzQMIvLXd%lF8W%`JfN>37=Ujyk zfk1Lu{w6!SyPM&MrF8L5H}{U}AY|Lhw%SxIMbF;BYzF#6Z&m8-MnvaBl>sEeoFE7T zt@>k6VRK>luSBBOI_sjhoy-r94u^nz zGi&$ImbI{ZCI0P0>Gh^V>vi>5bCU4*%I~!>2K8Yo*<~cVJFNf_bKIBTd-_O|`hq-4 zpu+QV7PI3yz$7d@joJU@E}fQN2)c<>P{fyD;6 zRPVoTav5?s6y`9i;!9c5`F$koe!B=ybxr^=n>OdANXYg zXLIJGX=aPwWTn}%gK4l^Z|?h^2~;T&ymp$X+kW+d2ApHX;a5~cK|hdX6a-ja(V;pv zXg}1KuD?m;(Ywq-zyUuZfwW89IzM(7rRlTTMQ-$VQr-@~XGJ;xUw+T#f&?Rv(a;^( zK2k-!YB{9tnV9L@u#8bS+pv7sP_Rb$ zVkEsnMTu58tl66Pdkk|p+pv!PpXI)+ErU^f%Dy?{)xzeehs(2SA$rH8?&2P*8ecNY zwCYt?EDKve4CTu#0!jacN2hKO&*KnUZ+K2&mw5D!3M3V`W+_CJ}`1M7XCU((=zf zGHZhSS{KT@V$i|wz8=m?$G<#-A>WZq{htyso)~sYA0SB?WC(!aP|Apa_h0>~>)V|g zQSkJv{9ljuD;w=nX1@zkF{a#f(WLPc!^)n@Dm{X(wUG>>BAJz$DQHH;M$&hJuek-% zuCc*Ii>@~&lX7zAkTx;|)&9;QG>srPlm81*@9Q^R-;-$0qCRw%dM(l``%DpJNLoN* z6N+yqhaA>iv9A= zmc@Z~vf)Ref6CmBG&-_}!&#T&G?ag8+?;c$4JYkV$+1vwEOwKanO0y=pMgQWLB9$y zcK>|h-CZWxf9La<)8RR$#K!0b4r!otz`qJ$IkI{Aab-8+naBY3#yNf$8ncqn(;$0L zOebH*5f!s1^5HPU$rJ8H4Mro9<`}JC&QghpC$L6lEB}iou~^P>kjQ~Y4+$*jfM`ac z?D8(UTCGwSXj7@Mv`Ldfl?2j6dz7IEx^?TFU0FrY7_m;6Gv6>_(mzAjQL`HTZE- zxTZ;N4F~swgrWkE9^z7;fFFM^a?+ z9|cUr4FILZ1j1Klp;m>nAdU=%M;*GuIumq;oQIiyrim2*Tt&#)43hH9rTp>2&De>`!axW5Z8hmeO!jnhwbGL?X}=Oo?_>W^Ny0fFxM+ z8c{jPUBG>O&ZFaJM!T~>7$sr(K0i!wMD66EOr4-6v}dDK-}bM#`th{$u9B{VQsI-G?9}6XoY_9fBT(Vo>odHTDe#J2_^TV@sV?=Go6vOZbkO{k zx(w%xL7QW#m{rgxI8?AY>s~!UY61wOBln;?*ooZSHVrRbvWrTTU8;PlK+`m>1ByTC745<|@_i@E5qWK@r=!akDQFPlzCLGv*Nf|2 ztxi856;N5Wr_6Do7|B_N*TI4l6iGLt#55w{8-Rfu0&N`>M)?mMJUOmBoQaZuAe-n= z%!dptQ91iUT<9JTjj$+No5X3Ip133}m;jXZO7H1g*I#t{F6{Kh2j`*hKJhDBkt3(> z{!xW@vr}J5uv?hU+reAO?M`-@6K{U$RpT?4b+vN$`e5oPI*$S>eU=heu9a$F%yh~O z+S!en;@wz)I-QOs^*w|KwtZFgtq^mN4m1}u$T1l!^|KHYM6z)=0u`0A*HvNK%{0#z zfAd1g=UwUcion8U%5tBV4;|@pRF&yV8lt;u_D!^}+R)U>L#3Xv(#UVHP0hjYagnGu z)E*iG(?G3`(8CH_N!QWQ-G`;8Z%#;9k3CyzlAHRtjUEN(#d;Qt^%4Da$ZyU-^<7*3Qj*u0Cr_Xzuh$&#omDG$lhDobFQo&0bXTdv z0x_bN~Q3xHIZ=9F;_DzK5mz zf<@WT>xy@YbrqkzK{F7?fxG@=#W<)RxXF!gDlG*c=bmpC_)ntWP?@rk_e(~OxmKJz z(!cRWZk)4Mr^jT)Ro3nj`cp2+m6Q+fUM!2g{t`ikJA3!j-wbgL+~;cHc-cblhiw|g?-Y3O1RDhH7ORG1H%017x~4IYrp za;A|CC4jQBmzu=i2JOKBRD^66Co0lo+x>n{tdl)12lP`OQUtuqv8KI#QjL{{p>!7; zkU-T`ieFYc1W0(GH94op1K0sSU#L=th=8#}M*C>o28M!BfPPEP|2EGDucoJ(1l zL7i#!TZ{RtgH%7oMZ@d^i|~Ne>QEM;n58814EjEYM6=1AFooE`%1}8u@Q(U|K-)?e zPY!bOr8~;Q@B6hKLFU`qAB|+oGqcr9D%s~~Yo3^MV`6p#x@o`U;>K1ga zk~k)2)j$TWjG?TtOEs0OLyvBFnO%$P#(B2H+%^e#})PXok@JX zUn72h&MEeNJk5Q-w5L%IsK<`_a4&nLcBXI^Mm9m)Dqgp;eV@>iKJZ$7t&^juCM6oyJW&yD7&f{=tfko6a$_qOWHXG>9XWt?O z9i2Gv4^n<$=bCow6j3Rqy?><7m8B{i3L;>5}HX>g!(qqW(d=gqOoO zj1iu&sPZ@%b?G8`kqco2VeN1kdEFQ;HXD(*+UCL8IIM(`HWlt?-|R=l1-Ee+b;~u{ zPkq+a93rQjeS{;r=pl+GPz7bw5jM`i{GME4tt0)RZ~6CIbuCZ*(N@^2`|laW#^!ba zNFdop)ETWwuMej%;eBonKbM;K+8ip7O$F7e<^w8~={2?Q!f%_@08O;31g6+vN3!UH zTC6dcE<02hNE5sgLJm{BfZvp1rg$Za0J_MKXGVKe=Vn!i@;37p+E(S^*O#Bml6< zP7kt!-Pf}sUuhU5{{JEe-Suq-+DfxR^K2OSUTh@{%x!cWHG;x>G@RsWu)aZxjbMyU zwk+~yq=FE(98{eXq2_rAP<}l6;_PS(G1vy@!VspBf@)J#y_5aFc>obGOp^9_Xf&6@ zgDW}VxH%}y!Ny(iG=y>roV^ez#s?vFbMGB6lekPv_T&HFy^O;;)`bNYA^5;pCTeIg zg-tzCvyJ}D=&A5b`NL*~LCwItG<>q_yd))G2`a*Tq>>sS`G}GUCW68+_cafZhTbZFZlRP$CY6mZCHcuYPTqmBa>75dZx;ZXC^momFl;pMZ!aX`ogTesOng{-Z# zDcNhyHUvqj@!t}5X}+G(Oz3(oB+)FF@$~FHJVjEz0D^5DY;3~mM*R@Nle+l98|zCt*4t7)GTx@a&2?m zdF*FcCwNmpu_i6SKZg#5vXsN*gYdO%4zzo#FSHI35s`W)?l;K3Llr^i8i(^zia6Xi z&U8~}9N~|qR#XMF=WcQzyvu{_*O~W<`;Ma=pJ4Ug4@kb<(t1nBVrI1Id9shxAS)fV zYPtVQw+v7Y4E6PI7zF{iO@@D_a+fM}}>6kn7MR#7W-0GVP*`Q!4tA`xf>tevz=}-}OAZ)&DJk`zuyB6mN zY(v*uOfvlzXJ!~%mLR}a#mS*-g5-mLnvhaKw(iKNs5CSk02V<$EOw2A$`T7BW)rS| z7#m7pOfFxkDfSrYB`3vVf=@Knpo@%*f~+zJHL>vDwvCiI4M} zEJ2FoF)BV#33MVLI#7z-;zpFcn?Bx2j*R2$9_!s3sqkYF+_{|Q7p)_tBogPI`VOA~ z_-C>Hp8A<>m|{2;CW0!}kjg#+@FcX7g1-|;a-Xu$E3NO=GjMWI+ikS}o!8&%Vt*&% zN*<2A3C0>zRb?3(8EJ89#lk#yt>i`qRE0=N%v!2nL=}6`F91f8%FbSh+-41PLnN zQWbIn`qUnY{vKj7{AA=;@gAakvhX9kY71L|BT3?6mZzv#_Em_P{T{LhOX9oqFWu^g zz;~|%Ne1$^(uiSc(|*h2)}j%mud$GZqz+Y~X-!Y{IZn)a`=XVb2AR zyKGB7YHJ;ifuHgxbgX1K+~T|<@R(?sy9Lf0_nt@N3Gz%(@Qp|CSP{lT@E9rI&Xq@Z zrE{q~6<*|6lC4K~LHMDvKcBQe9}LSQJ#4m==89%37HYJr zBjX^daTkX*ru=0gqk)pSw0=q*(N^`G~~i?B=IsKw1WFCR{YOw_@W??=Lsz_ z@?Izvv3Qa|G~{B-xYz~t@BiL}Drn8F(GVkEyCjDHOO_$VR-E9v@_gF;dlP@-&6r}z zdL??VBfrzd6gH6DJXT$BxgV=;_>E5aHn*nK9={y$2DI@jq*Wc7@&uf=-1BG`?_M>I zo%%HD?b2_@$fdnhza_t8kD43l__W!|5C!&KdBPv>ni}`m2&}?Kt}tkF+-?I92phd3 zGf`MKqM122;AoA9@cL1{Gdsc&r`-gCv|D-x@{9uk>N9EQl!vGgQkK%lJDzGlWEUc|$}{BPu&=zkbxaczaQ@w!a%8~^zmF3I5 zGfL#wNA#%3zBVSYQDnxS4(8SS#rTr!7N&Vo`C*h>rn)s^ONcS#)0;D~eq!&ND`>)I z>)*a>15C(~$p=_D)hIh{icD(Hc||E_RZ0J%0vjT5F};v!|JE~Yp8wLn)f*c?R{a!& zidEbj#VPCH5}s=iH8WmmSgJp4iVD+LRxH+a0^@WX#^b^~P^vK&HDgULZcO=7+soie@BHXDX zx00#6HY)f2ermHyJU%ghDL4aC&;9kJ4xow5p)|!j{HXKnOo#gbrrHZa+&{1gPb6l!Q(5X6Ga1#o%FuA8o<$+2P*);;w&b>-t!Ky7J%0A~{6pF| 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 -J*RzX7Y1V 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 ig2vQd?G=AaC;n!GiuVtK>iP%>-(bs diff --git a/Barotrauma/BarotraumaShared/changelog.txt b/Barotrauma/BarotraumaShared/changelog.txt index 94ea2fd37..ea0bd0231 100644 --- a/Barotrauma/BarotraumaShared/changelog.txt +++ b/Barotrauma/BarotraumaShared/changelog.txt @@ -1,3 +1,43 @@ +--------------------------------------------------------------------------------------------------------- +v0.1500.7.0 +--------------------------------------------------------------------------------------------------------- + +Additions and changes: +- More improvements and fixes to the alien ruins and the new fractal guardians. +- Added a colored border to high-quality items' inventory slots. +- 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- +- Field Medic now only triggers on missions. +- Reduced gravity sphere's force to make it possible to escape it with a diving suit on. +- 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. + +Fixes: +- Fixed outpost generator sometimes using ruin hallways in normal outposts (unstable only). +- Fixed ability to put fuel rods in reactors with the Reactor PDA (unstable only). +- Fixed Reactor PDA interface popping up when the item is picked up (unstable only). +- Fixed talents that increase max mission count increasing it every round (unstable only). +- Fixed talent unlock notifications being shown at the beginning of every round (unstable only). +- Fixed husks holding hands in an incorrect orientation when they run (unstable only). +- Fixed "all-seeing eye" talent crashing the game (unstable only). +- Fixed high-quality rods disappearing when deconstructed (unstable only). +- Fixes to the colliders of the new items (unstable only). +- Dementonite tools aren't sold in outposts (unstable only). +- Fixed buccaneer talent's power attack ability not working (unstable only). +- Fixed "strengthened alloys" not unlocking the hardened tool recipes (unstable only). +- Fixes to ruin waypoints (unstable only). +- Fixed limbs without a sprite (e.g. carrier's invisible limb that only serves as a spotlight) causing a crash (unstable only). +- 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). +- Fixed diving suits hiding the weapons held in the bag slot. +- 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 some connection panels in alien ruins being rewireable without a screwdriver, when they shouldn't be rewireable at all (unstable only). +- Fixed monster ranged attacks playing a damage sound when they "hit", when the monster shoots (unstable). +- Fixed moving the cursor on an UI element interrupting the usage of scooters or other items that are used by holding LMB and RMB (unstable only). +- 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 characters getting permanently stunned if they get a forced stun (e.g. by getting hit by a door) while godmode is on. +- 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). +- The "Still Kicking" talent doesn't remove genetic afflictions or buffs (unstable only). + --------------------------------------------------------------------------------------------------------- v0.1500.6.0 --------------------------------------------------------------------------------------------------------- From fc2f7b76da4760eca4ce7213bf43117d4ea27bed Mon Sep 17 00:00:00 2001 From: Markus Isberg <3e849f2e5c@pm.me> Date: Sat, 16 Oct 2021 10:32:38 +0900 Subject: [PATCH 09/12] Unstable 0.1500.8.0 --- .../ClientSource/Characters/CharacterInfo.cs | 13 +-- .../Characters/CharacterNetworking.cs | 14 ++- .../ClientSource/DebugConsole.cs | 3 + .../ClientSource/GUI/TabMenu.cs | 8 +- .../ClientSource/GameSession/HintManager.cs | 10 ++- .../Items/Components/LightComponent.cs | 2 +- .../Items/Components/Machines/Fabricator.cs | 20 +++-- .../ClientSource/Networking/GameClient.cs | 1 + .../ClientSource/Sounds/SoundPlayer.cs | 2 +- .../BarotraumaClient/LinuxClient.csproj | 2 +- Barotrauma/BarotraumaClient/MacClient.csproj | 2 +- .../BarotraumaClient/WindowsClient.csproj | 2 +- .../BarotraumaServer/LinuxServer.csproj | 2 +- Barotrauma/BarotraumaServer/MacServer.csproj | 2 +- .../ServerSource/Characters/CharacterInfo.cs | 10 ++- .../Characters/CharacterNetworking.cs | 26 +++++- .../ServerSource/DebugConsole.cs | 18 +++- .../GameModes/CharacterCampaignData.cs | 58 ++++++++++++- .../GameModes/MultiPlayerCampaign.cs | 7 +- .../ServerSource/Networking/GameServer.cs | 1 + .../ServerSource/Networking/Voting.cs | 2 +- .../BarotraumaServer/WindowsServer.csproj | 2 +- .../Characters/AI/AIController.cs | 40 +++++++-- .../Characters/AI/EnemyAIController.cs | 7 +- .../Characters/AI/HumanAIController.cs | 5 +- .../Characters/AI/IndoorsSteeringManager.cs | 2 +- .../AI/Objectives/AIObjectiveReturn.cs | 23 +++-- .../SharedSource/Characters/AI/PathFinder.cs | 1 + .../Animation/HumanoidAnimController.cs | 50 +++++++---- .../SharedSource/Characters/Character.cs | 32 ++++--- .../SharedSource/Characters/CharacterInfo.cs | 68 +++++++++++---- .../CharacterAbilityApplyStatusEffects.cs | 21 +++-- .../CharacterAbilityGivePermanentStat.cs | 2 +- .../CharacterAbilityResetPermanentStat.cs | 1 + .../CharacterAbilityTandemFire.cs | 2 +- .../Characters/Talents/TalentTree.cs | 5 ++ .../SharedSource/DebugConsole.cs | 14 ++- .../BarotraumaShared/SharedSource/Enums.cs | 1 + .../SharedSource/Events/EventManager.cs | 36 +++++--- .../SharedSource/Events/EventPrefab.cs | 2 +- .../SharedSource/Events/EventSet.cs | 86 ++++++++++++++----- .../SharedSource/Events/Missions/Mission.cs | 2 +- .../GameSession/AutoItemPlacer.cs | 31 +++---- .../GameModes/CharacterCampaignData.cs | 63 +------------- .../SharedSource/Items/CharacterInventory.cs | 4 +- .../Components/EntitySpawnerComponent.cs | 4 +- .../Items/Components/Holdable/Holdable.cs | 4 +- .../Items/Components/ItemContainer.cs | 11 ++- .../Components/Machines/OxygenGenerator.cs | 6 +- .../SharedSource/Items/Inventory.cs | 30 +++++-- .../SharedSource/Items/ItemInventory.cs | 4 +- .../SharedSource/Map/Levels/Level.cs | 12 ++- .../SharedSource/Map/Structure.cs | 7 +- .../SharedSource/Map/StructurePrefab.cs | 3 + .../NetEntityEvent/NetEntityEvent.cs | 1 + .../StatusEffects/StatusEffect.cs | 86 +++++++++---------- Barotrauma/BarotraumaShared/changelog.txt | 37 +++++++- 57 files changed, 608 insertions(+), 302 deletions(-) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterInfo.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterInfo.cs index d8b6cda3a..24ca41abe 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterInfo.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterInfo.cs @@ -688,8 +688,10 @@ namespace Barotrauma 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(); @@ -727,25 +729,24 @@ namespace Barotrauma dropdown.Select(dropdown.ListBox.Content.GetChildIndex(childToSelect)); //The following exists to track mouseover to preview colors before selecting them - bool previewingColor = false; 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 = true; + previewingColor ??= getter(); Color color = (Color)(dropdown.ListBox.Content.FindChild(c => - c == hoveredFrame || c.IsParentOf(hoveredFrame))?.UserData ?? dropdown.SelectedData); + c == hoveredFrame || c.IsParentOf(hoveredFrame))?.UserData ?? dropdown.SelectedData ?? getter()); setter(color); buttonFrame.Color = getter(); buttonFrame.HoverColor = getter(); } - else if (previewingColor) + else if (previewingColor.HasValue) { - setter((Color)dropdown.SelectedData); + setter(previewingColor.Value); buttonFrame.Color = getter(); buttonFrame.HoverColor = getter(); - previewingColor = false; + previewingColor = null; } }, onDraw: null) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterNetworking.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterNetworking.cs index 925021818..5897105f1 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterNetworking.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterNetworking.cs @@ -299,7 +299,7 @@ namespace Barotrauma break; case ServerNetObject.ENTITY_EVENT: - int eventType = msg.ReadRangedInteger(0, 12); + int eventType = msg.ReadRangedInteger(0, 13); switch (eventType) { case 0: //NetEntityEvent.Type.InventoryState @@ -476,6 +476,18 @@ namespace Barotrauma 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(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs b/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs index 68aa6da0c..0bb2d3ce3 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs @@ -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) => diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs index da89b0e4d..f165a8006 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs @@ -139,7 +139,7 @@ namespace Barotrauma GameSession.UpdateTalentNotificationIndicator(talentPointNotification); if (Character.Controlled is { } controlled && talentResetButton != null && talentApplyButton != null) { - int talentCount = selectedTalents.Count - controlled.Info.UnlockedTalents.Count; + int talentCount = selectedTalents.Count - controlled.Info.GetUnlockedTalentsInTree().Count(); talentResetButton.Enabled = talentApplyButton.Enabled = talentCount > 0; if (talentApplyButton.Enabled && talentApplyButton.FlashTimer <= 0.0f) { @@ -1254,7 +1254,7 @@ namespace Barotrauma return; } - selectedTalents = controlledCharacter.Info.UnlockedTalents.ToList(); + 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) { @@ -1541,7 +1541,7 @@ namespace Barotrauma string pointsLeft = controlledCharacter.Info.GetAvailableTalentPoints().ToString(); - int talentCount = selectedTalents.Count - controlledCharacter.Info.UnlockedTalents.Count; + int talentCount = selectedTalents.Count - controlledCharacter.Info.GetUnlockedTalentsInTree().Count(); if (talentCount > 0) { @@ -1612,7 +1612,7 @@ namespace Barotrauma private bool ResetTalentSelection(GUIButton guiButton, object userData) { Character controlledCharacter = Character.Controlled; - selectedTalents = controlledCharacter.Info.UnlockedTalents.ToList(); + selectedTalents = controlledCharacter.Info.GetUnlockedTalentsInTree().ToList(); UpdateTalentButtons(); return true; } 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/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/Fabricator.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs index 69b1337f3..53c770df7 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs @@ -529,21 +529,30 @@ namespace Barotrauma.Items.Components }; }*/ - string name = GetRecipeNameAndAmount(selectedItem); + string itemName = GetRecipeNameAndAmount(selectedItem); + string name = itemName; float quality = GetFabricatedItemQuality(selectedItem, user); if (quality > 0) { - name = TextManager.GetWithVariable("itemname.quality" + (int)quality, "[itemname]", name+'\n', fallBackTag: "itemname.quality3"); + 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), - name, textAlignment: Alignment.CenterLeft, textColor: Color.Aqua, font: GUI.SubHeadingFont, parseRichText: true) + 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)) { @@ -555,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(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs index 8dd2d4939..85267e5a7 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs @@ -1465,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(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundPlayer.cs b/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundPlayer.cs index 818c5040e..172454cc2 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundPlayer.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundPlayer.cs @@ -884,7 +884,7 @@ namespace Barotrauma // 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[mainTrackIndex] = suitableMusic.GetRandom(); + targetMusic[typeAmbienceTrackIndex] = suitableTypeAmbiences.GetRandom(); } //get the appropriate intensity layers for current situation diff --git a/Barotrauma/BarotraumaClient/LinuxClient.csproj b/Barotrauma/BarotraumaClient/LinuxClient.csproj index 0b19d09f4..fb716d58a 100644 --- a/Barotrauma/BarotraumaClient/LinuxClient.csproj +++ b/Barotrauma/BarotraumaClient/LinuxClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.1500.7.0 + 0.1500.8.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/MacClient.csproj b/Barotrauma/BarotraumaClient/MacClient.csproj index 9859d339c..ca78c7537 100644 --- a/Barotrauma/BarotraumaClient/MacClient.csproj +++ b/Barotrauma/BarotraumaClient/MacClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.1500.7.0 + 0.1500.8.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/WindowsClient.csproj b/Barotrauma/BarotraumaClient/WindowsClient.csproj index 5a7a69f25..9ea6a921c 100644 --- a/Barotrauma/BarotraumaClient/WindowsClient.csproj +++ b/Barotrauma/BarotraumaClient/WindowsClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.1500.7.0 + 0.1500.8.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaServer/LinuxServer.csproj b/Barotrauma/BarotraumaServer/LinuxServer.csproj index 9b9857398..6bf02aa12 100644 --- a/Barotrauma/BarotraumaServer/LinuxServer.csproj +++ b/Barotrauma/BarotraumaServer/LinuxServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.1500.7.0 + 0.1500.8.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/MacServer.csproj b/Barotrauma/BarotraumaServer/MacServer.csproj index 99db95ee4..d24dffea7 100644 --- a/Barotrauma/BarotraumaServer/MacServer.csproj +++ b/Barotrauma/BarotraumaServer/MacServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.1500.7.0 + 0.1500.8.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterInfo.cs b/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterInfo.cs index 44df073ae..0dba444c6 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterInfo.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterInfo.cs @@ -32,6 +32,12 @@ namespace Barotrauma } } + 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); @@ -66,8 +72,8 @@ namespace Barotrauma msg.Write((byte)0); } // TODO: animations - msg.Write((byte)savedStatValues.SelectMany(s => s.Value).Count()); - foreach (var savedStatValuePair in savedStatValues) + msg.Write((byte)SavedStatValues.SelectMany(s => s.Value).Count()); + foreach (var savedStatValuePair in SavedStatValues) { foreach (var savedStatValue in savedStatValuePair.Value) { diff --git a/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs b/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs index 65861aecc..bc7428d42 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs @@ -310,7 +310,7 @@ namespace Barotrauma if (extraData != null) { - const int min = 0, max = 12; + const int min = 0, max = 13; switch ((NetEntityEvent.Type)extraData[0]) { case NetEntityEvent.Type.InventoryState: @@ -438,6 +438,30 @@ namespace Barotrauma 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; diff --git a/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs b/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs index cc969a0dd..2b9b3f857 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs @@ -1677,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)); } } ); @@ -2171,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); diff --git a/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/CharacterCampaignData.cs b/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/CharacterCampaignData.cs index 21c9f7c8e..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) diff --git a/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs b/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs index 921de6336..88069d824 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs @@ -209,6 +209,11 @@ namespace Barotrauma //refresh the character data of clients who are still in the server foreach (Client c in GameMain.Server.ConnectedClients) { + 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 @@ -228,7 +233,7 @@ namespace Barotrauma } 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 diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs index 2486286e3..1ed8e43cc 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs @@ -2470,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); 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/WindowsServer.csproj b/Barotrauma/BarotraumaServer/WindowsServer.csproj index b5edb232b..bb6b99dd3 100644 --- a/Barotrauma/BarotraumaServer/WindowsServer.csproj +++ b/Barotrauma/BarotraumaServer/WindowsServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.1500.7.0 + 0.1500.8.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AIController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AIController.cs index c5e455940..6cc7e4886 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AIController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AIController.cs @@ -1,4 +1,5 @@ -using Barotrauma.Items.Components; +using Barotrauma.Extensions; +using Barotrauma.Items.Components; using Barotrauma.Networking; using FarseerPhysics; using Microsoft.Xna.Framework; @@ -346,7 +347,7 @@ namespace Barotrauma } #region Escape - public abstract void Escape(float deltaTime); + public abstract bool Escape(float deltaTime); public Gap EscapeTarget { get; private set; } @@ -425,17 +426,38 @@ namespace Barotrauma } if (EscapeTarget != null) { - SteeringManager.SteeringSeek(EscapeTarget.SimPosition, 10); - float sqrDist = Vector2.DistanceSquared(Character.SimPosition, EscapeTarget.SimPosition); - if (sqrDist < 0.5f || Character.CurrentHull == null || HasValidPath(requireNonDirty: true, requireUnfinished: false) && pathSteering.CurrentPath.Finished) + Vector2 diff = EscapeTarget.WorldPosition - Character.WorldPosition; + float sqrDist = diff.LengthSquared(); + if (Character.CurrentHull == null || sqrDist < MathUtils.Pow2(50) || pathSteering == null || IsCurrentPathUnreachable || IsCurrentPathFinished) { - // Very close to the target, outside, or at the end of the path -> just steer towards it manually without using the path + // Very close to the target, outside, or at the end of the path -> try to steer through the gap SteeringManager.Reset(); - SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(EscapeTarget.WorldPosition - Character.WorldPosition)); - if (sqrDist < 4) + pathSteering?.ResetPath(); + if (sqrDist < MathUtils.Pow2(50)) { - return true; + // Very close -> just keep steering forward + var forward = VectorExtensions.Forward(Character.AnimController.Collider.Rotation + MathHelper.PiOver2); + SteeringManager.SteeringManual(deltaTime, forward); } + else if (Character.CurrentHull == null) + { + // Outside -> steer away from the target + SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(-diff)); + } + else + { + // Still inside -> steer towards the target + SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(diff)); + } + return sqrDist < MathUtils.Pow2(200); + } + else if (pathSteering != null) + { + pathSteering.SteeringSeek(EscapeTarget.SimPosition, weight: 1, minGapSize); + } + else + { + SteeringManager.SteeringSeek(EscapeTarget.SimPosition, 10); } } else diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs index 332c4d3fe..d4ccdeadd 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs @@ -1903,7 +1903,7 @@ namespace Barotrauma Character.AnimController.ReleaseStuckLimbs(); LatchOntoAI?.DeattachFromBody(reset: true, cooldown: 1); if (attacker == null || attacker.AiTarget == null || attacker.Removed || attacker.IsDead) { return; } - if (Character.Params.CanInteract) + if (Character.Params.CanInteract && attackResult.Damage > 10) { ReleaseDragTargets(); } @@ -3607,12 +3607,12 @@ namespace Barotrauma public bool CanPassThroughHole(Structure wall, int sectionIndex) => CanPassThroughHole(wall, sectionIndex, requiredHoleCount); - public override void Escape(float deltaTime) + public override bool Escape(float deltaTime) { if (SelectedAiTarget != null && (SelectedAiTarget.Entity == null || SelectedAiTarget.Entity.Removed)) { State = AIState.Idle; - return; + return false; } else if (SelectedTargetMemory is AITargetMemory targetMemory && SelectedAiTarget?.Entity is Character) { @@ -3653,6 +3653,7 @@ namespace Barotrauma } } } + return isSteeringThroughGap; void SteerAwayFromTheEnemy() { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs index 2378f2e6a..b473ea46c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs @@ -1364,10 +1364,7 @@ namespace Barotrauma ObjectiveManager.WaitTimer = waitDuration; } - public override void Escape(float deltaTime) - { - UpdateEscape(deltaTime, canAttackDoors: false); - } + public override bool Escape(float deltaTime) => UpdateEscape(deltaTime, canAttackDoors: false); private void CheckCrouching(float deltaTime) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/IndoorsSteeringManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/IndoorsSteeringManager.cs index 24491ff3d..603f9da95 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/IndoorsSteeringManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/IndoorsSteeringManager.cs @@ -269,7 +269,7 @@ namespace Barotrauma { 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) != null) + 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; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveReturn.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveReturn.cs index be18f3e28..4ed0d0ee3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveReturn.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveReturn.cs @@ -9,6 +9,7 @@ namespace Barotrauma 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) @@ -57,7 +58,7 @@ namespace Barotrauma return; } bool shouldUseEscapeBehavior = false; - if (character.CurrentHull != null) + if (character.CurrentHull != null || isSteeringThroughGap) { if (character.Submarine == null || !character.Submarine.IsConnectedTo(ReturnTarget)) { @@ -67,8 +68,8 @@ namespace Barotrauma { HumanAIController.ResetEscape(); } - HumanAIController.Escape(deltaTime); - if (HumanAIController.EscapeTarget == null || HumanAIController.IsCurrentPathUnreachable) + isSteeringThroughGap = HumanAIController.Escape(deltaTime); + if (!isSteeringThroughGap && (HumanAIController.EscapeTarget == null || HumanAIController.IsCurrentPathUnreachable)) { Abandon = true; } @@ -92,7 +93,10 @@ namespace Barotrauma RemoveSubObjective(ref moveInCaveObjective); RemoveSubObjective(ref moveOutsideObjective); TryAddSubObjective(ref moveInsideObjective, - constructor: () => new AIObjectiveGoTo(targetHull, character, objectiveManager), + constructor: () => new AIObjectiveGoTo(targetHull, character, objectiveManager) + { + AllowGoingOutside = true + }, onCompleted: () => RemoveSubObjective(ref moveInsideObjective), onAbandon: () => Abandon = true); } @@ -110,7 +114,7 @@ namespace Barotrauma IsCompleted = true; } } - else if (moveInCaveObjective == null && moveOutsideObjective == null) + else if (!isSteeringThroughGap && moveInCaveObjective == null && moveOutsideObjective == null) { if (HumanAIController.IsInsideCave) { @@ -134,7 +138,8 @@ namespace Barotrauma TryAddSubObjective(ref moveInCaveObjective, constructor: () => new AIObjectiveGoTo(closestOutsideWaypoint, character, objectiveManager) { - endNodeFilter = n => n.Waypoint == closestOutsideWaypoint + endNodeFilter = n => n.Waypoint == closestOutsideWaypoint, + AllowGoingOutside = true }, onCompleted: () => RemoveSubObjective(ref moveInCaveObjective), onAbandon: () => Abandon = true); @@ -170,7 +175,10 @@ namespace Barotrauma RemoveSubObjective(ref moveInsideObjective); RemoveSubObjective(ref moveInCaveObjective); TryAddSubObjective(ref moveOutsideObjective, - constructor: () => new AIObjectiveGoTo(targetHull, character, objectiveManager), + constructor: () => new AIObjectiveGoTo(targetHull, character, objectiveManager) + { + AllowGoingOutside = true + }, onCompleted: () => RemoveSubObjective(ref moveOutsideObjective), onAbandon: () => Abandon = true); } @@ -221,6 +229,7 @@ namespace Barotrauma moveInCaveObjective = null; moveOutsideObjective = null; usingEscapeBehavior = false; + isSteeringThroughGap = false; HumanAIController.ResetEscape(); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PathFinder.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PathFinder.cs index 57f37a140..2736e235b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PathFinder.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PathFinder.cs @@ -236,6 +236,7 @@ namespace Barotrauma 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; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs index b9aa3d262..37a5a7029 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs @@ -684,18 +684,15 @@ 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.ArmMoveStrength, 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.ArmMoveStrength, 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 @@ -705,9 +702,7 @@ namespace Barotrauma 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 @@ -728,9 +723,16 @@ namespace Barotrauma { foot.DebugRefPos = colliderPos; foot.DebugTargetPos = footPos; - MoveLimb(foot, footPos, CurrentGroundedParams.FootMoveStrength); - FootIK(foot, footPos, - CurrentGroundedParams.LegBendTorque, CurrentGroundedParams.FootTorque, 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); } } @@ -760,6 +762,12 @@ namespace Barotrauma 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); + } } } } @@ -1008,6 +1016,12 @@ namespace Barotrauma 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) @@ -1021,6 +1035,12 @@ namespace Barotrauma 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); + } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs index dfc049300..4118df6c9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs @@ -665,6 +665,7 @@ namespace Barotrauma 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 { @@ -2751,7 +2752,8 @@ namespace Barotrauma } 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)) @@ -2765,13 +2767,18 @@ namespace Barotrauma { 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.25f; } + if (wasRagdolled != IsRagdolled) { ragdollingLockTimer = 0.5f; } } } - if (!wasRagdolled && IsRagdolled && selfRagdolled) + if (!wasRagdolled && IsRagdolled) { - CheckTalents(AbilityEffectType.OnSelfRagdoll); + 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); @@ -3535,11 +3542,6 @@ namespace Barotrauma if (Removed) { return new AttackResult(); } - if (attacker != null && attacker != this && GameMain.NetworkMember != null && !GameMain.NetworkMember.ServerSettings.AllowFriendlyFire) - { - if (attacker.TeamID == TeamID) { return new AttackResult(); } - } - float closestDistance = 0.0f; foreach (Limb limb in AnimController.Limbs) { @@ -3602,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 @@ -4385,14 +4391,14 @@ namespace Barotrauma 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 SERVER - GameMain.NetworkMember.CreateEntityEvent(this, new object[] { NetEntityEvent.Type.UpdateTalents }); -#endif if (addingFirstTime) { OnTalentGiven(talentPrefab.Identifier); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs index 741f47897..08c342288 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs @@ -216,6 +216,17 @@ namespace Barotrauma 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)); + } + + public int AdditionalTalentPoints { get; set; } private Sprite _headSprite; @@ -1220,7 +1231,7 @@ namespace Barotrauma var experienceGainMultiplier = new AbilityValue(1f); if (isMissionExperience) { - Character.CheckTalents(AbilityEffectType.OnGainMissionExperience, experienceGainMultiplier); + Character?.CheckTalents(AbilityEffectType.OnGainMissionExperience, experienceGainMultiplier); } experienceGainMultiplier.Value += Character.GetStatValue(StatTypes.ExperienceGainMultiplier); @@ -1252,7 +1263,7 @@ namespace Barotrauma public int GetAvailableTalentPoints() { // hashset always has at least 1 - return Math.Max(GetTotalTalentPoints() - UnlockedTalents.Count, 0); + return Math.Max(GetTotalTalentPoints() - GetUnlockedTalentsInTree().Count(), 0); } public float GetProgressTowardsNextLevel() @@ -1297,6 +1308,8 @@ namespace Barotrauma partial void OnExperienceChanged(int prevAmount, int newAmount); + partial void OnPermanentStatChanged(StatTypes statType); + public void Rename(string newName) { if (string.IsNullOrEmpty(newName)) { return; } @@ -1362,7 +1375,7 @@ namespace Barotrauma Job.Save(charElement); XElement savedStatElement = new XElement("savedstatvalues"); - foreach (var statValuePair in savedStatValues) + foreach (var statValuePair in SavedStatValues) { foreach (var savedStat in statValuePair.Value) { @@ -1708,26 +1721,42 @@ namespace Barotrauma } // This could maybe be a LookUp instead? - private readonly Dictionary> savedStatValues = new Dictionary>(); + public readonly Dictionary> SavedStatValues = new Dictionary>(); - public void ResetSavedStatValues() + public void ClearSavedStatValues() { - foreach (var savedStatValue in savedStatValues.SelectMany(s => s.Value)) + foreach (StatTypes statType in SavedStatValues.Keys) { - if (savedStatValue.RemoveOnDeath) - { - savedStatValue.StatValue = 0f; - } + OnPermanentStatChanged(statType); } + SavedStatValues.Clear(); } + + public void ClearSavedStatValues(StatTypes statType) + { + SavedStatValues.Remove(statType); + OnPermanentStatChanged(statType); + } + public void ResetSavedStatValue(string statIdentifier) { - savedStatValues.SelectMany(s => s.Value).Where(s => s.StatIdentifier == statIdentifier).ForEach(v => v.StatValue = 0f); + 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)) + if (SavedStatValues.TryGetValue(statType, out var statValues)) { return statValues.Sum(v => v.StatValue); } @@ -1738,7 +1767,7 @@ namespace Barotrauma } public float GetSavedStatValue(StatTypes statType, string statIdentifier) { - if (savedStatValues.TryGetValue(statType, out var statValues)) + if (SavedStatValues.TryGetValue(statType, out var statValues)) { return statValues.Where(s => s.StatIdentifier.Equals(statIdentifier, StringComparison.OrdinalIgnoreCase)).Sum(v => v.StatValue); } @@ -1750,19 +1779,24 @@ namespace Barotrauma 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)) + if (!SavedStatValues.ContainsKey(statType)) { - savedStatValues.Add(statType, new List()); + SavedStatValues.Add(statType, new List()); } - if (savedStatValues[statType].FirstOrDefault(s => s.StatIdentifier == statIdentifier) is SavedStatValue savedStat) + 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)); + SavedStatValues[statType].Add(new SavedStatValue(statIdentifier, MathHelper.Min(value, maxValue), removeOnDeath, removeAfterRound)); + changed = true; } + if (changed) { OnPermanentStatChanged(statType); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffects.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffects.cs index 9eb8e20e5..a59955d4d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffects.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffects.cs @@ -11,6 +11,7 @@ namespace Barotrauma.Abilities protected readonly List statusEffects; private readonly bool nearbyCharactersAppliesToSelf; + private readonly bool nearbyCharactersAppliesToAllies; private readonly bool applyToSelected; readonly List targets = new List(); @@ -20,6 +21,7 @@ namespace Barotrauma.Abilities 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) @@ -40,6 +42,10 @@ namespace Barotrauma.Abilities { 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); } @@ -56,17 +62,20 @@ namespace Barotrauma.Abilities } } protected override void ApplyEffect() - { - ApplyEffectSpecific(Character); - } - - protected override void ApplyEffect(AbilityObject abilityObject) { if (applyToSelected && Character.SelectedCharacter is Character selectedCharacter) { ApplyEffectSpecific(selectedCharacter); } - else if ((abilityObject as IAbilityCharacter)?.Character is Character targetCharacter) + else + { + ApplyEffectSpecific(Character); + } + } + + protected override void ApplyEffect(AbilityObject abilityObject) + { + if ((abilityObject as IAbilityCharacter)?.Character is Character targetCharacter) { ApplyEffectSpecific(targetCharacter); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGivePermanentStat.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGivePermanentStat.cs index ffa6df774..6fb4887b3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGivePermanentStat.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGivePermanentStat.cs @@ -15,7 +15,7 @@ namespace Barotrauma.Abilities 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) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityResetPermanentStat.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityResetPermanentStat.cs index 0957982df..3716a6fbc 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityResetPermanentStat.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityResetPermanentStat.cs @@ -6,6 +6,7 @@ namespace Barotrauma.Abilities { private readonly string statIdentifier; public override bool AppliesEffectOnIntervalUpdate => true; + public override bool AllowClientSimulation => true; public CharacterAbilityResetPermanentStat(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityTandemFire.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityTandemFire.cs index e3b3ba0c3..75668b0ca 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityTandemFire.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityTandemFire.cs @@ -31,7 +31,7 @@ namespace Barotrauma.Abilities } } - if (closestCharacter.SelectedConstruction == null || !Character.SelectedConstruction.HasTag(tag)) { return; } + if (closestCharacter.SelectedConstruction == null || !closestCharacter.SelectedConstruction.HasTag(tag)) { return; } if (closestDistance < squaredMaxDistance) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/TalentTree.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/TalentTree.cs index 1a96f6f38..02377cba0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/TalentTree.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/TalentTree.cs @@ -66,6 +66,11 @@ namespace Barotrauma } } + 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); diff --git a/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs b/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs index bc9d3ea89..859d6f2fd 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs @@ -602,7 +602,7 @@ namespace Barotrauma } })); - commands.Add(new Command("giveaffliction", "giveaffliction [affliction name] [affliction strength] [character name] [limb type]: 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; } @@ -622,14 +622,12 @@ 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(new string[] { args[2] }); - - + Character targetCharacter = args.Length <= 2 ? Character.Controlled : FindMatchingCharacter(new string[] { args[2] }); if (targetCharacter != null) { Limb targetLimb = targetCharacter.AnimController.MainLimb; @@ -1889,9 +1887,9 @@ namespace Barotrauma if (!IsCommandPermitted(splitCommand[0].ToLowerInvariant(), GameMain.Client)) { #if DEBUG - AddWarning("You're not permitted to use the command \"{matchingCommand.Name}\". Executing the command anyway because this is a debug build."); + 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() + "\"!"); + ThrowError($"You're not permitted to use the command \"{splitCommand[0].ToLowerInvariant()}\"!"); return; #endif } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Enums.cs b/Barotrauma/BarotraumaShared/SharedSource/Enums.cs index b6a3d3d83..3ac2e2dbf 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Enums.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Enums.cs @@ -47,6 +47,7 @@ OnReduceAffliction, OnAddDamageAffliction, OnSelfRagdoll, + OnRagdoll, OnRoundEnd, OnAnyMissionCompleted, OnAllMissionsCompleted, diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs index 8fee80718..0e0e9a77c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs @@ -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)) { @@ -434,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); @@ -465,7 +473,7 @@ namespace Barotrauma selectedEvents.Add(eventSet, new List()); } selectedEvents[eventSet].Add(newEvent); - unusedEvents.Remove((eventPrefab, commonness, probability)); + unusedEvents.Remove(subEventPrefab); } } } @@ -480,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); @@ -866,7 +878,7 @@ 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); 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/Mission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs index 2c8f53636..a0c9c2c07 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs @@ -363,7 +363,7 @@ namespace Barotrauma #if CLIENT foreach (Character character in crewCharacters) { - character.Info.GiveExperience(experienceGain, isMissionExperience: true); + character.Info?.GiveExperience(experienceGain, isMissionExperience: true); } #else foreach (Barotrauma.Networking.Client c in GameMain.Server.ConnectedClients) diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/AutoItemPlacer.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/AutoItemPlacer.cs index aa8c2eee6..8f0c37464 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/AutoItemPlacer.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/AutoItemPlacer.cs @@ -218,12 +218,12 @@ namespace Barotrauma return validContainers; } - private static readonly float[] qualityCommonnesses = new float[Quality.MaxQuality + 1] + private static readonly (int quality, float commonness)[] qualityCommonnesses = new (int quality, float commonness)[Quality.MaxQuality + 1] { - 0.85f, - 0.125f, - 0.0225f, - 0.0025f, + (0, 0.85f), + (1, 0.125f), + (2, 0.0225f), + (3, 0.0025f), }; private static List SpawnItem(ItemPrefab itemPrefab, List containers, KeyValuePair validContainer, float difficultyModifier) @@ -243,20 +243,15 @@ namespace Barotrauma containers.Remove(validContainer.Key); break; } - if (!validContainer.Key.Inventory.CanBePut(itemPrefab)) { break; } - - int quality = 0; - float qualityCommmonnessSum = qualityCommonnesses.Sum(); - float randomNumber = Rand.Range(0f, qualityCommmonnessSum, Rand.RandSync.Server); - for (int k = qualityCommonnesses.Length - 1; k >= 0; k--) - { - if (randomNumber < qualityCommonnesses[k]) - { - quality = k; - 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, 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/Items/CharacterInventory.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/CharacterInventory.cs index 22f679994..0aa9fc4fc 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/CharacterInventory.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/CharacterInventory.cs @@ -141,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); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/EntitySpawnerComponent.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/EntitySpawnerComponent.cs index ec5f6b078..e73ea86f1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/EntitySpawnerComponent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/EntitySpawnerComponent.cs @@ -268,7 +268,7 @@ namespace Barotrauma.Items.Components { string[] allSpecies = SpeciesName.Split(','); string species = allSpecies.GetRandom().Trim(); - Entity.Spawner.AddToSpawnQueue(species, pos); + Entity.Spawner?.AddToSpawnQueue(species, pos); } else if (!string.IsNullOrWhiteSpace(ItemIdentifier)) { @@ -282,7 +282,7 @@ namespace Barotrauma.Items.Components pos -= sub.Position; } - Entity.Spawner.AddToSpawnQueue(prefab, pos, item.Submarine); + Entity.Spawner?.AddToSpawnQueue(prefab, pos, item.Submarine); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Holdable.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Holdable.cs index b2c819360..1449bf5cf 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Holdable.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Holdable.cs @@ -642,10 +642,8 @@ namespace Barotrauma.Items.Components item.Drop(character); item.SetTransform(ConvertUnits.ToSimUnits(GetAttachPosition(character)), 0.0f, findNewHull: false); } + AttachToWall(); } - - AttachToWall(); - return true; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs index dc1ea9383..27b8542ff 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs @@ -291,13 +291,16 @@ namespace Barotrauma.Items.Components int index = Inventory.FindIndex(containedItem); if (index >= 0 && index < slotRestrictions.Length) { - RelatedItem ri = slotRestrictions[index].ContainableItems?.Find(ci => ci.MatchesItem(containedItem)); - if (ri != null) + if (slotRestrictions[index].ContainableItems != null) { activeContainedItems.RemoveAll(i => i.Item == containedItem); - foreach (StatusEffect effect in ri.statusEffects) + foreach (var containableItem in slotRestrictions[index].ContainableItems) { - activeContainedItems.Add(new ActiveContainedItem(containedItem, effect, ri.ExcludeBroken)); + if (!containableItem.MatchesItem(containedItem)) { continue; } + foreach (StatusEffect effect in containableItem.statusEffects) + { + activeContainedItems.Add(new ActiveContainedItem(containedItem, effect, containableItem.ExcludeBroken)); + } } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/OxygenGenerator.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/OxygenGenerator.cs index 60c93f2a9..d76ec970c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/OxygenGenerator.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/OxygenGenerator.cs @@ -34,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; } @@ -78,6 +80,7 @@ namespace Barotrauma.Items.Components private void GetVents() { + totalHullVolume = 0.0f; ventList ??= new List<(Vent vent, float hullVolume)>(); ventList.Clear(); foreach (MapEntity entity in item.linkedTo) @@ -87,13 +90,14 @@ namespace Barotrauma.Items.Components Vent vent = linkedItem.GetComponent(); if (vent?.Item.CurrentHull == null) { continue; } + 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: 5, ignoreClosedGaps: true)) + 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; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs index e9f28b882..c222b6a31 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs @@ -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; @@ -422,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); @@ -503,7 +513,7 @@ namespace Barotrauma { return true; } - return + return TrySwapping(i, item, user, createNetworkEvent, swapWholeStack: true) || TrySwapping(i, item, user, createNetworkEvent, swapWholeStack: false); } @@ -776,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/ItemInventory.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/ItemInventory.cs index 954ab96af..cf1870762 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/ItemInventory.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/ItemInventory.cs @@ -51,11 +51,11 @@ namespace Barotrauma 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, i)) { return false; } - return itemPrefab != null && slots[i].CanBePut(itemPrefab, condition) && slots[i].ItemCount < container.GetMaxStackSize(i); + 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) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs index c0e618e9d..eefafeeab 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs @@ -864,6 +864,16 @@ 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++) @@ -1972,7 +1982,7 @@ namespace Barotrauma ConvertUnits.ToSimUnits(entranceWayPoint.WorldPosition), collisionCategory: Physics.CollisionLevel | Physics.CollisionWall) == null; }); if (closestWp == null) { continue; } - entranceWayPoint.ConnectTo(closestWp); + ConnectWaypoints(entranceWayPoint, closestWp, outSideWaypointInterval); } //create a waypoint path from the ruin to the closest tunnel diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs index 5ccf84245..2907db5ce 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs @@ -1023,7 +1023,12 @@ namespace Barotrauma #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 diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/StructurePrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/StructurePrefab.cs index 19189ff95..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; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/NetEntityEvent/NetEntityEvent.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/NetEntityEvent/NetEntityEvent.cs index fa35ae9aa..8396c48a3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/NetEntityEvent/NetEntityEvent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/NetEntityEvent/NetEntityEvent.cs @@ -26,6 +26,7 @@ namespace Barotrauma.Networking UpdateExperience, UpdateTalents, UpdateMoney, + UpdatePermanentStats, } public readonly Entity Entity; diff --git a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs index 0100a166f..6bb5586c1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs @@ -1284,68 +1284,66 @@ namespace Barotrauma } } - int i = 0; - foreach (int giveExperience in giveExperiences) + if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient) { - Character targetCharacter = CharacterFromTarget(target); - if (targetCharacter != null && !targetCharacter.Removed) - { - targetCharacter?.Info?.GiveExperience(giveExperience); - i++; - } - } + // these effects do not need to be run clientside, as they are replicated from server to clients anyway - if (giveSkills.Any()) - { - foreach ((string skillIdentifier, float amount) in giveSkills) + foreach (int giveExperience in giveExperiences) { Character targetCharacter = CharacterFromTarget(target); if (targetCharacter != null && !targetCharacter.Removed) { - if (skillIdentifier?.ToLowerInvariant() == "randomskill") + targetCharacter?.Info?.GiveExperience(giveExperience); + } + } + + if (giveSkills.Any()) + { + foreach ((string skillIdentifier, float amount) in giveSkills) + { + Character targetCharacter = CharacterFromTarget(target); + if (targetCharacter != null && !targetCharacter.Removed) { - if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) + if (skillIdentifier?.ToLowerInvariant() == "randomskill") { - // don't let clients simulate random skill gain - continue; + targetCharacter.Info?.IncreaseSkillLevel(GetRandomSkill(), amount); + + string GetRandomSkill() + { + return targetCharacter.Info?.Job?.Skills.Select(s => s.Identifier).GetRandom(); + } } - targetCharacter.Info?.IncreaseSkillLevel(GetRandomSkill(), amount); - - string GetRandomSkill() + else { - return targetCharacter.Info?.Job?.Skills.Select(s => s.Identifier).GetRandom(); + targetCharacter.Info?.IncreaseSkillLevel(skillIdentifier?.ToLowerInvariant(), amount); } } - else - { - targetCharacter.Info?.IncreaseSkillLevel(skillIdentifier?.ToLowerInvariant(), amount); - } } } - } - - if (giveTalentInfos.Any() && (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)) - { - 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 (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))); - if (giveTalentInfo.GiveRandom) + foreach (GiveTalentInfo giveTalentInfo in giveTalentInfos) { - targetCharacter.GiveTalent(viableTalents.GetRandom(), true); - } - else - { - foreach (string talent in viableTalents) + IEnumerable viableTalents = giveTalentInfo.TalentIdentifiers.Where(s => !targetCharacter.Info.UnlockedTalents.Contains(s) && !disallowedTalents.Contains(s)); + if (viableTalents.None()) { continue; } + + if (giveTalentInfo.GiveRandom) { - targetCharacter.GiveTalent(talent, true); + targetCharacter.GiveTalent(viableTalents.GetRandom(), true); + } + else + { + foreach (string talent in viableTalents) + { + targetCharacter.GiveTalent(talent, true); + } } } } diff --git a/Barotrauma/BarotraumaShared/changelog.txt b/Barotrauma/BarotraumaShared/changelog.txt index ea0bd0231..9ef824a3c 100644 --- a/Barotrauma/BarotraumaShared/changelog.txt +++ b/Barotrauma/BarotraumaShared/changelog.txt @@ -1,3 +1,38 @@ +--------------------------------------------------------------------------------------------------------- +v0.1500.8.0 +--------------------------------------------------------------------------------------------------------- + +Additions and changes: +- More improvements and fixes to the alien ruins and the new fractal guardians. +- Improvements to character animations and sprites. +- Made ruin scan/clear missions available in outposts. +- Talent adjustments and fixes. +- 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. +- Temporarily disabled magnesium exploding in water to prevent issues with talents related to it. +- Added an additonal ambience track for the ruins. +- New sounds for alien ruins and the guardians. +- Portable pumps turn off automatically when not attached to a wall. + +Fixes: +- Fixed outpost events always unlocking the same escort mission. +- Fixed server occasionally failing to end the round and spamming the clients with XP notifications. Happened when the server was trying to give experience for the completed missions and there were clients in the server whose character had been removed (unstable only). +- Fixed shotgun shells not having a fabrication recipe (unstable only). +- Fixed thermal goggles being sold in outposts (unstable only). +- Fixes to ruin waypoint generation (unstable only). +- Fixes to pathfinding outside ruins (unstable only). +- Fixed oxygen generator output constantly decreasing due to the changes in the previous build (unstable only). +- Fixed long item names being unreadable on the fabricator UI. +- The hints about flooded rooms and ballast flora aren't shown in ruins, wrecks or enemy subs. +- Fixed "setclientcharacter" command crashing the server if the specified character is not found. +- Fixed autofilling subs with supplies sometimes causing high-quality items to appear on the floor near containers (unstable only). +- Fixed guardian spears being fabricatable (unstable only). +- 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. + --------------------------------------------------------------------------------------------------------- v0.1500.7.0 --------------------------------------------------------------------------------------------------------- @@ -7,7 +42,7 @@ Additions and changes: - Added a colored border to high-quality items' inventory slots. - 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- +- Increased Azimuth's battery out relay max power. - Field Medic now only triggers on missions. - Reduced gravity sphere's force to make it possible to escape it with a diving suit on. - 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. From 6b84ff65e33aacce09f6e93c6a8503208a8d9322 Mon Sep 17 00:00:00 2001 From: Markus Isberg <3e849f2e5c@pm.me> Date: Wed, 27 Oct 2021 03:22:53 +0900 Subject: [PATCH 10/12] Unstable 0.15.11.0 + the last 2 unstables I missed --- .../ClientSource/Characters/Limb.cs | 2 +- .../Missions/AbandonedOutpostMission.cs | 1 + .../Events/Missions/AlienRuinMission.cs | 1 + .../Events/Missions/BeaconMission.cs | 12 - .../Events/Missions/CargoMission.cs | 1 + .../Events/Missions/CombatMission.cs | 5 - .../Events/Missions/EscortMission.cs | 2 + .../Events/Missions/MineralMission.cs | 1 + .../ClientSource/Events/Missions/Mission.cs | 5 +- .../Events/Missions/MonsterMission.cs | 1 + .../Events/Missions/NestMission.cs | 1 + .../Events/Missions/PirateMission.cs | 1 + .../Events/Missions/SalvageMission.cs | 1 + .../Events/Missions/ScanMission.cs | 1 + .../ClientSource/GUI/TabMenu.cs | 14 +- .../ClientSource/GameSettings.cs | 14 +- .../Items/Components/Machines/MiniMap.cs | 31 +- .../Items/Components/Repairable.cs | 21 +- .../ClientSource/Items/Components/Rope.cs | 1 + .../ClientSource/Items/Components/Turret.cs | 4 + .../ClientSource/Particles/Particle.cs | 1 + .../BarotraumaClient/LinuxClient.csproj | 4 +- Barotrauma/BarotraumaClient/MacClient.csproj | 6 +- .../BarotraumaClient/WindowsClient.csproj | 4 +- .../BarotraumaServer/LinuxServer.csproj | 4 +- Barotrauma/BarotraumaServer/MacServer.csproj | 4 +- .../ServerSource/Characters/CharacterInfo.cs | 1 + .../Missions/AbandonedOutpostMission.cs | 1 + .../Events/Missions/AlienRuinMission.cs | 1 + .../Events/Missions/BeaconMission.cs | 12 - .../Events/Missions/CargoMission.cs | 1 + .../Events/Missions/CombatMission.cs | 5 - .../Events/Missions/EscortMission.cs | 2 + .../Events/Missions/MineralMission.cs | 1 + .../ServerSource/Events/Missions/Mission.cs | 5 +- .../Events/Missions/MonsterMission.cs | 2 + .../Events/Missions/NestMission.cs | 1 + .../Events/Missions/PirateMission.cs | 2 + .../Events/Missions/SalvageMission.cs | 2 + .../Events/Missions/ScanMission.cs | 1 + .../GameModes/MultiPlayerCampaign.cs | 4 +- .../ServerSource/Networking/KarmaManager.cs | 16 +- .../ServerSource/Networking/RespawnManager.cs | 2 +- .../BarotraumaServer/WindowsServer.csproj | 4 +- .../Characters/AI/AIController.cs | 27 +- .../Characters/AI/HumanAIController.cs | 4 +- .../Characters/AI/IndoorsSteeringManager.cs | 23 +- .../Objectives/AIObjectiveFindDivingGear.cs | 24 +- .../AI/Objectives/AIObjectiveFindSafety.cs | 8 +- .../AI/Objectives/AIObjectiveGoTo.cs | 25 +- .../AI/Objectives/AIObjectiveIdle.cs | 5 +- .../AI/Objectives/AIObjectiveReturn.cs | 25 +- .../SharedSource/Characters/AI/PathFinder.cs | 49 +- .../Characters/Animation/Ragdoll.cs | 2 +- .../SharedSource/Characters/Character.cs | 4 +- .../SharedSource/Characters/CharacterInfo.cs | 16 +- .../Characters/Health/CharacterHealth.cs | 2 +- .../CharacterAbilityGiveResistance.cs | 2 +- .../Events/Missions/GoToMission.cs | 11 - .../SharedSource/Events/Missions/Mission.cs | 2 +- .../SharedSource/GameSettings.cs | 1 + .../Items/Components/GeneticMaterial.cs | 2 +- .../Items/Components/Holdable/Holdable.cs | 2 +- .../Items/Components/Holdable/RangedWeapon.cs | 6 +- .../Items/Components/Machines/Reactor.cs | 7 +- .../Items/Components/Projectile.cs | 9 +- .../Items/Components/Repairable.cs | 25 +- .../SharedSource/Items/Components/Rope.cs | 6 + .../SharedSource/Items/Item.cs | 20 +- .../SharedSource/Map/Levels/Level.cs | 4 +- Barotrauma/BarotraumaShared/changelog.txt | 441 ++++++------------ 71 files changed, 479 insertions(+), 479 deletions(-) delete mode 100644 Barotrauma/BarotraumaClient/ClientSource/Events/Missions/BeaconMission.cs delete mode 100644 Barotrauma/BarotraumaServer/ServerSource/Events/Missions/BeaconMission.cs diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Limb.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Limb.cs index 42ba80997..2e544f692 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Limb.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Limb.cs @@ -1113,7 +1113,7 @@ namespace Barotrauma } if (wearableItemComponent.AllowedSlots.Contains(InvSlotType.Bag)) { - depth -= depthStep * 2; + depth -= depthStep * 4; } wearableColor = wearableItemComponent.Item.GetSpriteColor(); } 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 index 586f9c37a..b943081b2 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/AlienRuinMission.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/AlienRuinMission.cs @@ -6,6 +6,7 @@ namespace Barotrauma { public override void ClientReadInitial(IReadMessage msg) { + base.ClientReadInitial(msg); existingTargets.Clear(); spawnedTargets.Clear(); allTargets.Clear(); 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 66eb75c8b..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++) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/Mission.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/Mission.cs index 5af7dd9cf..e8b3064d4 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/Mission.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/Mission.cs @@ -102,6 +102,9 @@ namespace Barotrauma 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/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 index bbf3c18f6..93dadacc4 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/ScanMission.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/ScanMission.cs @@ -25,6 +25,7 @@ namespace Barotrauma public override void ClientReadInitial(IReadMessage msg) { + base.ClientReadInitial(msg); startingItems.Clear(); ushort itemCount = msg.ReadUInt16(); for (int i = 0; i < itemCount; i++) diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs index f165a8006..6ed653ddc 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs @@ -1287,9 +1287,11 @@ namespace Barotrauma 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(Vector2.One, nameLayout.RectTransform, Anchor.BottomCenter), text: GameMain.NetLobbyScreen.CampaignCharacterDiscarded ? TextManager.Get("settings") : TextManager.Get("createnew")) + 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 }; @@ -1336,6 +1338,16 @@ namespace Barotrauma } } + 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"); diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSettings.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSettings.cs index d3444ed00..49b528e7d 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSettings.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSettings.cs @@ -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"); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs index d94357008..890b2910a 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs @@ -147,14 +147,14 @@ namespace Barotrauma.Items.Components private GUIFrame submarineContainer; private GUIFrame hullInfoFrame; - private GUIScissorComponent scissorComponent; - private GUIComponent miniMapContainer; + 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 GUIComponent? searchAutoComplete; private ItemPrefab? searchedPrefab; @@ -184,7 +184,7 @@ namespace Barotrauma.Items.Components private ImmutableDictionary electricalChildren; private ImmutableDictionary doorChildren; - private ImmutableHashSet itemsFoundOnSub; + private ImmutableHashSet? itemsFoundOnSub; private ImmutableHashSet? MiniMapBlips; private float blipState; @@ -416,7 +416,7 @@ namespace Barotrauma.Items.Components hullInfoFrame.AddToGUIUpdateList(order: order + 1); if (currentMode == MiniMapMode.ItemFinder && searchBar.Selected) { - searchAutoComplete.AddToGUIUpdateList(order: order + 1); + searchAutoComplete?.AddToGUIUpdateList(order: order + 1); } } @@ -686,7 +686,7 @@ namespace Barotrauma.Items.Components private void ControlSearchTooltip(GUITextBox sender, Keys key) { - if (!searchAutoComplete.Visible) { return; } + if (searchAutoComplete is null || !searchAutoComplete.Visible) { return; } GUIListBox listBox = searchAutoComplete.GetChild(); if (listBox is null) { return; } @@ -705,15 +705,17 @@ namespace Barotrauma.Items.Components } } - private bool UpdateSearchTooltip(GUITextBox box, string text) + 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 is null) { return false; } + GUIListBox? listBox = searchAutoComplete.GetChild(); + if (listBox?.Content is null) { return false; } bool first = true; @@ -722,9 +724,9 @@ namespace Barotrauma.Items.Components foreach (GUIComponent component in listBox.Content.Children) { component.Visible = false; - if (component.UserData is ItemPrefab prefab && itemsFoundOnSub.Contains(prefab)) + if (component.UserData is ItemPrefab { Name: { } prefabName} prefab && itemsFoundOnSub.Contains(prefab)) { - component.Visible = prefab.Name.ToLower().Contains(text.ToLower()); + component.Visible = prefabName.ToLower().Contains(text.ToLower()); if (component.Visible && first) { @@ -828,6 +830,7 @@ namespace Barotrauma.Items.Components MiniMapBlips = positions.ToImmutableHashSet(); + if (searchAutoComplete is null) { return; } searchAutoComplete.Visible = false; } @@ -1021,7 +1024,7 @@ namespace Barotrauma.Items.Components if (Voltage < MinVoltage || !miniMapGuiComponent.RectComponent.Visible) { continue; } - int durability = (int)(it.Condition / it.MaxCondition * 100f); + 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) @@ -1188,7 +1191,7 @@ namespace Barotrauma.Items.Components { Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle; spriteBatch.End(); - if (submarinePreview is { } texture) + 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; @@ -1200,7 +1203,7 @@ namespace Barotrauma.Items.Components Vector2 origin = new Vector2(texture.Width / 2f, texture.Height / 2f); float scale = currentMode == MiniMapMode.HullStatus ? 1.0f : Zoom; - spriteBatch.Draw(texture, miniMapContainer.Center, null, blueprintBlue, 0f, origin, scale, SpriteEffects.None, 0f); + spriteBatch.Draw(texture, mapContainer.Center, null, blueprintBlue, 0f, origin, scale, SpriteEffects.None, 0f); spriteBatch.End(); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Repairable.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Repairable.cs index 01db669ab..fbf133ced 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Repairable.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Repairable.cs @@ -52,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))) || IsTinkerable(character); + 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) @@ -344,6 +360,7 @@ namespace Barotrauma.Items.Components 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 b2a025ca9..25d45c773 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Rope.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Rope.cs @@ -84,6 +84,7 @@ namespace Barotrauma.Items.Components public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1) { if (target == null || target.Removed) { return; } + if (target.ParentInventory != null) { return; } Vector2 startPos = GetSourcePos(); startPos.Y = -startPos.Y; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Turret.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Turret.cs index e78bdf3b2..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) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Particles/Particle.cs b/Barotrauma/BarotraumaClient/ClientSource/Particles/Particle.cs index 7747a7b16..6b7cd2e8c 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Particles/Particle.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Particles/Particle.cs @@ -378,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; } diff --git a/Barotrauma/BarotraumaClient/LinuxClient.csproj b/Barotrauma/BarotraumaClient/LinuxClient.csproj index fb716d58a..33aa1207a 100644 --- a/Barotrauma/BarotraumaClient/LinuxClient.csproj +++ b/Barotrauma/BarotraumaClient/LinuxClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.1500.8.0 + 0.15.11.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 diff --git a/Barotrauma/BarotraumaClient/MacClient.csproj b/Barotrauma/BarotraumaClient/MacClient.csproj index ca78c7537..cb3f8eac4 100644 --- a/Barotrauma/BarotraumaClient/MacClient.csproj +++ b/Barotrauma/BarotraumaClient/MacClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.1500.8.0 + 0.15.11.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 diff --git a/Barotrauma/BarotraumaClient/WindowsClient.csproj b/Barotrauma/BarotraumaClient/WindowsClient.csproj index 9ea6a921c..7645101b6 100644 --- a/Barotrauma/BarotraumaClient/WindowsClient.csproj +++ b/Barotrauma/BarotraumaClient/WindowsClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.1500.8.0 + 0.15.11.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 diff --git a/Barotrauma/BarotraumaServer/LinuxServer.csproj b/Barotrauma/BarotraumaServer/LinuxServer.csproj index 6bf02aa12..f0fec53ed 100644 --- a/Barotrauma/BarotraumaServer/LinuxServer.csproj +++ b/Barotrauma/BarotraumaServer/LinuxServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.1500.8.0 + 0.15.11.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 d24dffea7..f129e40bb 100644 --- a/Barotrauma/BarotraumaServer/MacServer.csproj +++ b/Barotrauma/BarotraumaServer/MacServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.1500.8.0 + 0.15.11.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/CharacterInfo.cs b/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterInfo.cs index 0dba444c6..81abdaf26 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterInfo.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterInfo.cs @@ -26,6 +26,7 @@ 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 }); 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 index 4723cb072..dd8138d18 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/AlienRuinMission.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/AlienRuinMission.cs @@ -6,6 +6,7 @@ namespace Barotrauma { public override void ServerWriteInitial(IWriteMessage msg, Client c) { + base.ServerWriteInitial(msg, c); msg.Write((ushort)existingTargets.Count); foreach (var t in existingTargets) { 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 8be6f27df..b3dd48bff 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/MineralMission.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/MineralMission.cs @@ -6,6 +6,7 @@ namespace Barotrauma { public override void ServerWriteInitial(IWriteMessage msg, Client c) { + base.ServerWriteInitial(msg, c); msg.Write((byte)caves.Count); foreach (var cave in caves) { diff --git a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/Mission.cs b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/Mission.cs index 4faee3e40..fc1b5041c 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/Mission.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/Mission.cs @@ -16,7 +16,10 @@ 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) { 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 index 3d3e4f171..dc5dbff31 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/ScanMission.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/ScanMission.cs @@ -6,6 +6,7 @@ namespace Barotrauma { public override void ServerWriteInitial(IWriteMessage msg, Client c) { + base.ServerWriteInitial(msg, c); msg.Write((ushort)startingItems.Count); foreach (var item in startingItems) { diff --git a/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs b/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs index 88069d824..7dafc70d0 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs @@ -214,7 +214,7 @@ namespace Barotrauma c.Character = null; } - if (c.HasSpawned && c.CharacterInfo != null && c.CharacterInfo.CauseOfDeath != null && c.CharacterInfo.CauseOfDeath?.Type != CauseOfDeathType.Disconnected) + 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) @@ -227,7 +227,7 @@ namespace Barotrauma } var characterInfo = c.Character?.Info ?? c.CharacterInfo; if (characterInfo == null) { continue; } - if (characterInfo.CauseOfDeath?.Type != CauseOfDeathType.Disconnected) + if (c.CharacterInfo.CauseOfDeath != null && characterInfo.CauseOfDeath.Type != CauseOfDeathType.Disconnected) { RespawnManager.ReduceCharacterSkills(characterInfo); } 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/RespawnManager.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/RespawnManager.cs index 41c9a34a9..1d2ab8e2e 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/RespawnManager.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/RespawnManager.cs @@ -11,7 +11,7 @@ namespace Barotrauma.Networking /// /// How much skills drop towards the job's default skill levels when respawning midround in the campaign /// - const float SkillReductionOnCampaignMidroundRespawn = 0.5f; + const float SkillReductionOnCampaignMidroundRespawn = 0.75f; private DateTime despawnTime; diff --git a/Barotrauma/BarotraumaServer/WindowsServer.csproj b/Barotrauma/BarotraumaServer/WindowsServer.csproj index bb6b99dd3..3378b23f1 100644 --- a/Barotrauma/BarotraumaServer/WindowsServer.csproj +++ b/Barotrauma/BarotraumaServer/WindowsServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.1500.8.0 + 0.15.11.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/SharedSource/Characters/AI/AIController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AIController.cs index 6cc7e4886..719417830 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AIController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AIController.cs @@ -426,30 +426,35 @@ namespace Barotrauma } if (EscapeTarget != null) { + var door = EscapeTarget.ConnectedDoor; + bool isClosedDoor = door != null && !door.IsOpen; Vector2 diff = EscapeTarget.WorldPosition - Character.WorldPosition; float sqrDist = diff.LengthSquared(); - if (Character.CurrentHull == null || sqrDist < MathUtils.Pow2(50) || pathSteering == null || IsCurrentPathUnreachable || IsCurrentPathFinished) + 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(); - if (sqrDist < MathUtils.Pow2(50)) - { - // Very close -> just keep steering forward - var forward = VectorExtensions.Forward(Character.AnimController.Collider.Rotation + MathHelper.PiOver2); - SteeringManager.SteeringManual(deltaTime, forward); - } - else if (Character.CurrentHull == null) + Vector2 dir = Vector2.Normalize(diff); + if (Character.CurrentHull == null || isClose) { // Outside -> steer away from the target - SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(-diff)); + 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, Vector2.Normalize(diff)); + SteeringManager.SteeringManual(deltaTime, dir); } - return sqrDist < MathUtils.Pow2(200); + return sqrDist < MathUtils.Pow2(250); } else if (pathSteering != null) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs index b473ea46c..974af8a20 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs @@ -250,7 +250,7 @@ namespace Barotrauma { rayEnd += SelectedAiTarget.Entity.Submarine.SimPosition; } - UseIndoorSteeringOutside = Submarine.PickBody(SimPosition, rayEnd, collisionCategory: Physics.CollisionLevel) != null; + UseIndoorSteeringOutside = Submarine.PickBody(SimPosition, rayEnd, collisionCategory: Physics.CollisionLevel | Physics.CollisionWall) != null; } } else @@ -340,7 +340,7 @@ namespace Barotrauma IsInsideCave = Character.CurrentHull == null && Level.Loaded?.Caves.FirstOrDefault(c => c.Area.Contains(Character.WorldPosition)) is Level.Cave; } - if (UseIndoorSteeringOutside || IsInsideCave || Character.Submarine != null || hasValidPath && IsCloseEnoughToTarget(maxSteeringBuffer) || IsCloseEnoughToTarget(steeringBuffer)) + if (UseIndoorSteeringOutside || IsInsideCave || Character.CurrentHull?.Submarine != null || hasValidPath && IsCloseEnoughToTarget(maxSteeringBuffer) || IsCloseEnoughToTarget(steeringBuffer)) { if (steeringManager != insideSteering) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/IndoorsSteeringManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/IndoorsSteeringManager.cs index 603f9da95..cf2635e9d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/IndoorsSteeringManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/IndoorsSteeringManager.cs @@ -1,4 +1,5 @@ -using Barotrauma.Items.Components; +using Barotrauma.Extensions; +using Barotrauma.Items.Components; using Microsoft.Xna.Framework; using System; using System.Linq; @@ -200,10 +201,14 @@ namespace Barotrauma currentTarget = target; Vector2 currentPos = host.SimPosition; pathFinder.InsideSubmarine = character.Submarine != null && !character.Submarine.Info.IsRuin; - pathFinder.ApplyPenaltyToOutsideNodes = character.Submarine != null && character.PressureProtection <= 0; + 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 (!useNewPath && currentPath != null && currentPath.CurrentNode != null && newPath.Nodes.Any() && !newPath.Unreachable) + if (newPath.Unreachable || newPath.Nodes.None()) + { + useNewPath = false; + } + 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()) @@ -215,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. @@ -557,10 +562,14 @@ 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 diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindDivingGear.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindDivingGear.cs index 012c3a858..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, () => @@ -74,10 +78,7 @@ namespace Barotrauma } 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, () => @@ -160,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 fe528d9dc..2c8f6a40b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindSafety.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindSafety.cs @@ -55,8 +55,8 @@ namespace Barotrauma { 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; } @@ -131,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) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGoTo.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGoTo.cs index f4e156d20..a67611486 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGoTo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGoTo.cs @@ -260,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); @@ -374,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; @@ -525,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); } @@ -535,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 8d496ac2c..4be1b47fd 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveIdle.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveIdle.cs @@ -282,7 +282,7 @@ namespace Barotrauma } } newTargetTimer -= deltaTime; - if (!character.IsClimbing && IsSteeringFinished()) + if (!character.IsClimbing && (PathSteering == null || PathSteering.CurrentPath == null || IsSteeringFinished())) { Wander(deltaTime); } @@ -393,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/AIObjectiveReturn.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveReturn.cs index 4ed0d0ee3..78d80ac98 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveReturn.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveReturn.cs @@ -88,6 +88,28 @@ namespace Barotrauma 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); @@ -95,7 +117,8 @@ namespace Barotrauma TryAddSubObjective(ref moveInsideObjective, constructor: () => new AIObjectiveGoTo(targetHull, character, objectiveManager) { - AllowGoingOutside = true + AllowGoingOutside = true, + endNodeFilter = n => n.Waypoint.Submarine == targetHull.Submarine }, onCompleted: () => RemoveSubObjective(ref moveInsideObjective), onAbandon: () => Abandon = true); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PathFinder.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PathFinder.cs index 2736e235b..e46839e24 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PathFinder.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PathFinder.cs @@ -198,7 +198,7 @@ namespace Barotrauma } float xDiff = Math.Abs(start.X - node.TempPosition.X); float yDiff = Math.Abs(start.Y - node.TempPosition.Y); - if (InsideSubmarine) + 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) @@ -215,6 +215,13 @@ namespace Barotrauma //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; @@ -248,19 +255,17 @@ namespace Barotrauma 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; } - // Always check the visibility for the start node - if (!IsWaypointVisible(node, start)) { continue; } - if (node.IsBlocked()) { continue; } - if (node.Waypoint.ConnectedGap != null) - { - if (!CanFitThroughGap(node.Waypoint.ConnectedGap, minGapSize)) { continue; } - } - startNode = node; + if (!CanFitThroughGap(node.Waypoint.ConnectedGap, minGapSize)) { continue; } } + startNode = node; + break; } if (startNode == null) @@ -301,19 +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; } - // 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 (!CanFitThroughGap(node.Waypoint.ConnectedGap, minGapSize)) { continue; } - } - endNode = node; + if (!CanFitThroughGap(node.Waypoint.ConnectedGap, minGapSize)) { continue; } } + endNode = node; + break; } if (endNode == null) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs index 2a6ece320..5913768c7 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs @@ -1103,7 +1103,7 @@ namespace Barotrauma 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) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs index 4118df6c9..8edc9b797 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs @@ -4579,9 +4579,9 @@ namespace Barotrauma private readonly Dictionary abilityResistances = new Dictionary(); - public float GetAbilityResistance(string resistanceId) + public float GetAbilityResistance(AfflictionPrefab affliction) { - return abilityResistances.TryGetValue(resistanceId, out float value) ? value : 1f; + 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) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs index 08c342288..cd9c653a5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs @@ -226,6 +226,15 @@ namespace Barotrauma 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; } @@ -1233,10 +1242,9 @@ namespace Barotrauma { Character?.CheckTalents(AbilityEffectType.OnGainMissionExperience, experienceGainMultiplier); } - experienceGainMultiplier.Value += Character.GetStatValue(StatTypes.ExperienceGainMultiplier); + experienceGainMultiplier.Value += Character?.GetStatValue(StatTypes.ExperienceGainMultiplier) ?? 0; amount = (int)(amount * experienceGainMultiplier.Value); - if (amount < 0) { return; } ExperiencePoints += amount; @@ -1252,8 +1260,8 @@ namespace Barotrauma OnExperienceChanged(prevAmount, ExperiencePoints); } - const int BaseExperienceRequired = 50; - const int AddedExperienceRequiredPerLevel = 450; + const int BaseExperienceRequired = -50; + const int AddedExperienceRequiredPerLevel = 550; public int GetTotalTalentPoints() { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs index 8a6f55e00..9980cb396 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs @@ -458,7 +458,7 @@ namespace Barotrauma { resistance += afflictions[i].GetResistance(affliction); } - return 1 - ((1 - resistance) * Character.GetAbilityResistance(affliction.Identifier)); + return 1 - ((1 - resistance) * Character.GetAbilityResistance(affliction)); } public float GetStatValue(StatTypes statType) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveResistance.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveResistance.cs index af08e66bc..253dd787b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveResistance.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveResistance.cs @@ -10,7 +10,7 @@ namespace Barotrauma.Abilities public CharacterAbilityGiveResistance(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) { resistanceId = abilityElement.GetAttributeString("resistanceid", abilityElement.GetAttributeString("resistance", string.Empty)); - multiplier = abilityElement.GetAttributeFloat("multiplier", 1f); + multiplier = abilityElement.GetAttributeFloat("multiplier", 1f); // rename this to resistance for consistency if (string.IsNullOrEmpty(resistanceId)) { 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/Mission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs index a0c9c2c07..507d7b61f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs @@ -347,7 +347,7 @@ namespace Barotrauma if (!(GameMain.GameSession.GameMode is CampaignMode campaign)) { return; } int reward = GetReward(Submarine.MainSub); - float baseExperienceGain = reward * 0.1f; + float baseExperienceGain = reward * 0.09f; float difficultyMultiplier = 1 + level.Difficulty / 100f; baseExperienceGain *= difficultyMultiplier; diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSettings.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSettings.cs index 56915b462..b8b37bcc3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSettings.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSettings.cs @@ -942,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), diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/GeneticMaterial.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/GeneticMaterial.cs index 3f65446a4..ae34d86fe 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/GeneticMaterial.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/GeneticMaterial.cs @@ -158,7 +158,7 @@ namespace Barotrauma.Items.Components if (!CanBeCombinedWith(otherGeneticMaterial)) { return false; } float conditionIncrease = Rand.Range(ConditionIncreaseOnCombineMin, ConditionIncreaseOnCombineMax); - conditionIncrease *= 1.0f + user.GetStatValue(StatTypes.GeneticMaterialRefineBonus); + conditionIncrease += user.GetStatValue(StatTypes.GeneticMaterialRefineBonus); if (item.Prefab == otherGeneticMaterial.item.Prefab) { item.Condition = Math.Max(item.Condition, otherGeneticMaterial.item.Condition) + conditionIncrease; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Holdable.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Holdable.cs index 1449bf5cf..e88c44e43 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Holdable.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Holdable.cs @@ -781,7 +781,7 @@ namespace Barotrauma.Items.Components if (!aim) { var rope = GetRope(); - if (rope != null && rope.SnapWhenNotAimed) + if (rope != null && rope.SnapWhenNotAimed && rope.Item.ParentInventory == null) { rope.Snap(); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RangedWeapon.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RangedWeapon.cs index 0966ea92f..a07db8e19 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RangedWeapon.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RangedWeapon.cs @@ -198,7 +198,11 @@ 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); - LastProjectile?.Item.GetComponent()?.Snap(); + 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); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Reactor.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Reactor.cs index bac4219d8..8655b2618 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Reactor.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Reactor.cs @@ -494,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); @@ -591,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/Projectile.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs index f44619825..8cc3945de 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs @@ -614,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 diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs index d36c11e6f..3ff601550 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs @@ -406,15 +406,7 @@ namespace Barotrauma.Items.Components fixDuration /= 1 + CurrentFixer.GetStatValue(StatTypes.RepairSpeed) + currentRepairItem?.Prefab.AddedRepairSpeedMultiplier ?? 0f; fixDuration /= 1 + item.GetQualityModifier(Quality.StatType.RepairSpeed); - // kind of rough to keep this in update, but seems most robust - if (requiredSkills.Any(s => s != null && s.Identifier.Equals("mechanical", StringComparison.OrdinalIgnoreCase))) - { - item.MaxRepairConditionMultiplier = 1 + CurrentFixer.GetStatValue(StatTypes.MaxRepairConditionMultiplierMechanical); - } - if (requiredSkills.Any(s => s != null && s.Identifier.Equals("electrical", StringComparison.OrdinalIgnoreCase))) - { - item.MaxRepairConditionMultiplier = 1 + CurrentFixer.GetStatValue(StatTypes.MaxRepairConditionMultiplierElectrical); - } + item.MaxRepairConditionMultiplier = GetMaxRepairConditionMultiplier(CurrentFixer); if (currentFixerAction == FixActions.Repair) { @@ -489,6 +481,21 @@ 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; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Rope.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Rope.cs index de4ae2389..6e9836551 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Rope.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Rope.cs @@ -95,6 +95,10 @@ namespace Barotrauma.Items.Components } } snapped = value; + if (!snapped) + { + snapTimer = 0; + } } } @@ -113,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; } @@ -148,6 +153,7 @@ namespace Barotrauma.Items.Components #endif var projectile = target.GetComponent(); if (projectile == null) { return; } + if (SnapOnCollision) { raycastTimer += deltaTime; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs index 33a9253af..2fd8b35e8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs @@ -1323,7 +1323,7 @@ namespace Barotrauma } Submarine = parentInventory.Owner.Submarine; - if (body != null) body.Submarine = Submarine; + if (body != null) { body.Submarine = Submarine; } return CurrentHull; } @@ -1733,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) { @@ -1814,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); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs index eefafeeab..698b5fbde 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs @@ -1964,11 +1964,11 @@ namespace Barotrauma Vector2 entranceDir = Vector2.Zero; if (g.IsHorizontal) { - entranceDir = Vector2.UnitX * Math.Sign(g.WorldPosition.X - g.linkedTo[0].WorldPosition.X); + entranceDir = Vector2.UnitX * 2 * Math.Sign(g.WorldPosition.X - g.linkedTo[0].WorldPosition.X); } else { - entranceDir = Vector2.UnitY * Math.Sign(g.WorldPosition.Y - g.linkedTo[0].WorldPosition.Y); + 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) { diff --git a/Barotrauma/BarotraumaShared/changelog.txt b/Barotrauma/BarotraumaShared/changelog.txt index 9ef824a3c..de4346bc7 100644 --- a/Barotrauma/BarotraumaShared/changelog.txt +++ b/Barotrauma/BarotraumaShared/changelog.txt @@ -1,148 +1,69 @@ --------------------------------------------------------------------------------------------------------- -v0.1500.8.0 +v0.15.11.0 --------------------------------------------------------------------------------------------------------- -Additions and changes: -- More improvements and fixes to the alien ruins and the new fractal guardians. -- Improvements to character animations and sprites. -- Made ruin scan/clear missions available in outposts. -- Talent adjustments and fixes. -- 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. -- Temporarily disabled magnesium exploding in water to prevent issues with talents related to it. -- Added an additonal ambience track for the ruins. -- New sounds for alien ruins and the guardians. -- Portable pumps turn off automatically when not attached to a wall. - -Fixes: -- Fixed outpost events always unlocking the same escort mission. -- Fixed server occasionally failing to end the round and spamming the clients with XP notifications. Happened when the server was trying to give experience for the completed missions and there were clients in the server whose character had been removed (unstable only). -- Fixed shotgun shells not having a fabrication recipe (unstable only). -- Fixed thermal goggles being sold in outposts (unstable only). -- Fixes to ruin waypoint generation (unstable only). -- Fixes to pathfinding outside ruins (unstable only). -- Fixed oxygen generator output constantly decreasing due to the changes in the previous build (unstable only). -- Fixed long item names being unreadable on the fabricator UI. -- The hints about flooded rooms and ballast flora aren't shown in ruins, wrecks or enemy subs. -- Fixed "setclientcharacter" command crashing the server if the specified character is not found. -- Fixed autofilling subs with supplies sometimes causing high-quality items to appear on the floor near containers (unstable only). -- Fixed guardian spears being fabricatable (unstable only). -- 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. +- 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.1500.7.0 +v0.15.10.0 --------------------------------------------------------------------------------------------------------- -Additions and changes: -- More improvements and fixes to the alien ruins and the new fractal guardians. -- Added a colored border to high-quality items' inventory slots. -- 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. -- Field Medic now only triggers on missions. -- Reduced gravity sphere's force to make it possible to escape it with a diving suit on. -- 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. - -Fixes: -- Fixed outpost generator sometimes using ruin hallways in normal outposts (unstable only). -- Fixed ability to put fuel rods in reactors with the Reactor PDA (unstable only). -- Fixed Reactor PDA interface popping up when the item is picked up (unstable only). -- Fixed talents that increase max mission count increasing it every round (unstable only). -- Fixed talent unlock notifications being shown at the beginning of every round (unstable only). -- Fixed husks holding hands in an incorrect orientation when they run (unstable only). -- Fixed "all-seeing eye" talent crashing the game (unstable only). -- Fixed high-quality rods disappearing when deconstructed (unstable only). -- Fixes to the colliders of the new items (unstable only). -- Dementonite tools aren't sold in outposts (unstable only). -- Fixed buccaneer talent's power attack ability not working (unstable only). -- Fixed "strengthened alloys" not unlocking the hardened tool recipes (unstable only). -- Fixes to ruin waypoints (unstable only). -- Fixed limbs without a sprite (e.g. carrier's invisible limb that only serves as a spotlight) causing a crash (unstable only). -- 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). -- Fixed diving suits hiding the weapons held in the bag slot. -- 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 some connection panels in alien ruins being rewireable without a screwdriver, when they shouldn't be rewireable at all (unstable only). -- Fixed monster ranged attacks playing a damage sound when they "hit", when the monster shoots (unstable). -- Fixed moving the cursor on an UI element interrupting the usage of scooters or other items that are used by holding LMB and RMB (unstable only). -- 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 characters getting permanently stunned if they get a forced stun (e.g. by getting hit by a door) while godmode is on. -- 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). -- The "Still Kicking" talent doesn't remove genetic afflictions or buffs (unstable only). +- 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.1500.6.0 +v0.15.9.0 --------------------------------------------------------------------------------------------------------- -Additions and changes: -- Improvements and additions to the alien ruins and the new fractal guardians. -- More improvements and fixes to the overhauled character sprites and animations. -- 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. -- Talent adjustments and balancing. -- Nerfed thermal goggles: some enemies are invisible to them and targets behind walls are much less clear. -- 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. -- Made welding tools a bit less effective early to compensate for increases to their effectiveness from quality/talents. -- Fuel tanks, rods, grenades and other quality-based items can be stacked again. Items of different qualities still can't be put in the same stack. -- Endocrine Booster now gives a random talent. -- New sprite for the mudraptor beak grown by splicing mudraptor genes. -- Fabricating fuel rods now requirse 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). -- Changed how skillbooks work: the skills are gained when you "finish" reading the book instead of continuously to prevent UI message spam and overlap, the books can be used on others in the health interface or by hitting them with the book. -- Color the selected (but unapplied) talent icons orange instead of changing the icon in the talent UI, made the apply button flash when there's unapplied talents. -- Merged status monitor's status and hull condition tabs. -- Halved concussion's damage threshold to make it possible for more sources of damage to trigger it, + halved the probability to compensate. - -Fixes: -- Fixed high-quality batteries/tanks (or other items with an increased max condition) spawning in non-full condition (unstable only). -- Fixed ropes sometimes crashing the game (unstable only). -- Fixed some items occasionally disappearing from outposts when re-entering them. The most noticeable symptom was wires disappearing from the outpost's airlock, preventing the hatch from opening (unstable only). -- Fixed multiplayer campaign characters resetting if they're dead when the round ends. -- Fixed clients who aren't currently controlling a character not getting XP in the mp campaign (unstable only). -- 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 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. -- Fixed boarding axe (unstable only). -- 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 sub editor where changing ItemComponent color with HSV picker would create an error in the console. -- Fixed inability to hit downed characters with short melee weapons like the diving knife. -- Fixed inability to sell nasonov and faraday artifacts (unstable only). -- Fixed concussion description (unstable only). -- Fixes gender-specific affliction sound effects (e.g. vomiting) not playing (unstable only). -- Fixed humans' "aim source position" being too low, causing aim to be slightly off (unstable only). -- Fixed artifact transport case displaying as empty when there's a nasonov/faraday artifact inside (unstable only). -- Fixed "infiltration" event getting stuck on one of the conversation options. -- Fixed ruin's physics bodies being dynamic in mirrored levels (causing them to sink). -- Backwards compatibility: assign skin colors to characters saved in previous versions. -- Assign random skin/hair colors to characters without one configured instead of defaulting to white. Fixes all tutorial characters having white skin/hair. -Modding: -- 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. - ---------------------------------------------------------------------------------------------------------- -v0.1500.5.0 ---------------------------------------------------------------------------------------------------------- - -Additions and changes: +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. -- Improvements and fixes to the overhauled character sprites and animations. -- More talent improvements and additions. -- Cap the amount argument of the spawnitem command to 100 to prevent freezing/crashing when trying to spawn a ridiculous amount of the item. -- Nerfed concussions: they now require a larger amount of damage to the head to trigger and slowly heal by themselves. -- Added "unlocktalents [job]" command. -- Don't reset the selected limb when reopening the health interface. -- 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). -- The health interface displays a prediction of how much a medical item will reduce/increase the afflictions. -- Certain afflictions make the characters' face change color a bit. -- Added button to randomize character appearance in the character customization menus. -- Permanently reduce character skills 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. +- 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. @@ -151,185 +72,44 @@ Balance changes: - Disabled stacking quality-based items (experimental change, feedback is welcome). - Reduced diving suits damage resistances. - Buffed vigor and haste. -- Modifed characters' base vitalities. +- Modified characters' base vitalities. - Adjustments to monster stats. - Reduced mission experience gains, level difficulty affects mission experience. - -Fixes: -- Fixed crash when firing a syring gun (unstable only). -- Fixed crashing when using meds in multiplayer with friendly fire turned off (unstable only). -- Fixed inability to repair with hardened/dementonite wrenches (unstable only). -- Fixed item quality not persisting between rounds (unstable only). -- Fixed fabricator not outputting high-quality items in full condition if the item's quality modifiers increase the max condition (unstable). -- Display the max condition of the required item on the fabricator (i.e. show that depleted fuel needs to be fabricated from depleted fuel rods). -- Fixed holdable components that block players (e.g. mudraptor shells) causing a "you are removing a body that is not in the simulation" exception when ending a round (unstable only). -- Fixed handheld status monitor and electrical monitor UIs popping up when picking up the item. -- 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 ability to crouch on ladders (unstable only). -- Fixed loadsub command. - ---------------------------------------------------------------------------------------------------------- -v0.1500.4.0 ---------------------------------------------------------------------------------------------------------- - -- Overhauled character sprites, ragdolls and animations (WIP). -- Option to customize the starting crew in the single player campaign. -- Talent improvements and additions. -- Merged the talent and character tabs in the tab menu. -- Disable deconstructor button when there's no deconstructable items in the input slots (also applies to research terminals which are technically deconstructors). - -Fixes: -- Fixed inability to install/update mods that have periods in the name. -- Fixed stack sizes being displayed incorrectly on items with multiple inventories, e.g. deconstructor (unstable only). -- Fixed depleted fuel not being craftable (unstable only). -- Fixed leftmost inventory slot overlapping with the chatbox (unstable only). -- Fixed medic bots trying to treat genetic afflictions (unstable only). -- Fixed nav terminals "current_position_x" output being in pixels when "current_position_y" is in meters. -- Fixed tall subs overlapping with the buttons on the status monitor (unstable only). -- Fixed status monitor's item finder not finding wires (unstable only). -- Cargo scooters can't be put in toolbelts, crates, bandoliers or each other (unstable only). -- Fixed status monitor elements getting misaligned when 1st viewing it while linked to another interface and then individually or vice versa (unstable only). -- 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 tainted genetic materials becoming untainted when saving and loading (unstable only). -- Fixed genetic material effects' strengths changing when saving and reloading (unstable only). -- Fixed tainted genetic materials sometimes giving the user hammerhead matriarch's genetic effects. -- Fixed inability to tinker loaders (unstable only). -- 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 recommended treatments not changing when the strengths of the displayed afflictions change. -- Fixed cargo scooters working with a battery in an incorrect slot (unstable only). -- Fixed cargo scooters and volatile fulgurium fuel rods being craftable by anyone (unstable only). -- Fixed misaligned nav terminal and status monitor in pirate humpback. -- Fixed crash when ordering friendly NPCs (e.g. hostages) to return to the sub. - ---------------------------------------------------------------------------------------------------------- -v0.1500.3.0 ---------------------------------------------------------------------------------------------------------- +- 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: -- More talents and talent-related items (all talent trees now functional and most of the talents implemented). - -Changes: -- Ignore hidden afflictions when determining treatment suggestions to show in the health interface. -- Visualize leaks on the status monitor's hull condition tab (unstable only). -- Added "condition_out" output to outpost O2 generator (unstable only). - -Fixes: -- Fixed crashing when reloading sprites or resetting to prefab in the sub editor. -- Fixed ability to combine unidentified genetic materials with other genetic materials (unstable only). -- Organ damage doesn't cause concussions (unstable only). -- Fixed talent menu being accessible if you leave it open and switch to a game mode where it shouldn't be accessible (unstable only). -- Fixed ability to contain items other than batteries in cargo scooter's battery slot (unstable only). -- Damaging the mudraptor beak given by mudraptor genes damages the head instead of torso, added damage protection to the beak (unstable only). -- Items that are set to be hidden in menus aren't shown in the status monitor's item finder (unstable only). -- Fixed status monitor's item finder not showing wearable items (unstable only). -- Fixed "in use by xxxx" warning being always visible when using a Reactor PDA (unstable only). -- Fixed Reactor PDA rendering over the command interface (unstable only). -- Fixed assault rifle crosshair being drawn when it's in the bag slot (unstable only). -- Fixed equip buttons not being drawn on equipped items that can only be put to other equip slots, but not on the non-limb slots (e.g. assault rifle). - -Modding: -- Option to make property conditionals target contained items using the attribute targetcontaineditem="true". - ---------------------------------------------------------------------------------------------------------- -v0.1500.2.0 ---------------------------------------------------------------------------------------------------------- - -Additions and changes: -- A bunch more talents and talent-related items. +- 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. -- Characters can now only have a single "Movement" category order at a time. -- Added condition_out pin to various items. -- Adjusted the color of the status terminal to be more greener. -- Added door and hatch position indicators to status monitor. -- Made alerts and job icons in status monitor be consistent in size. -- Combined genetic materials show the descriptions of both of the materials in the tooltip. -- The talent menu is disabled when not controlling a human character or playing the campaign. -- Disabled toggling the sonar mode by pressing the Run key. -- Changed default creature attack key to F because R conflicts with the radio keybind. -- 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). - -Fixes: -- 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 insert them into an occupied slot in a container that can't hold the item. -- Fixed genetic materia's effects not always disappearing when unequipping the material (unstable only). -- 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 inability to adjust max mission count in a dedicated server. -- Fixed ID overlaps when loading outpost modules that contain items which spawn with some contained item (e.g. alien battery cells or magazines). -- 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 cargo missions sometimes only rewarding the players for 1 crate even when transporting more. -- Fixed heavy scooter working even if the battery is not in the correct slot, added an icon to the battery slot (unstable only). -- 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 with spineling/raptor genes turning into husks when they die (unstable only). -- Fixed any amount of damage triggering mollusc gene's vigor buff (unstable only), making it easy to max the vigor with tools that do damage every frame (e.g. plasma cutter). -- Fixed genetic materials refining to 100% when combined with stabilozine (unstable only). -- Fixed gene splicer slot's tooltip staying visible when you close the health interface while your cursor is on the slot (unstable only). -- Fixed gene splicer slot sometimes being misaligned when opening the health interface for the 1st time (unstable only). -- Fixed concussion's description being used as its name (unstable only). -- Fixed ability to recursively stack bandoliers, toolbelts and heavy scooters. - -Modding: -- Option to make afflictions draw a full-screen overlay when active. - ---------------------------------------------------------------------------------------------------------- -v0.1500.1.0 ---------------------------------------------------------------------------------------------------------- - -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 outpost) and applied on a character using a Gene Splicer. -- Added WIP talent trees for security officers and assistants. -- Streamlined the health interface. -- Allow administering meds by clicking on the "suitable treatments" suggestions in the health interface. -- Physical injuries to the head can cause concussions. -- Play editor music in multiplayer lobby. +- 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%). - -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 item 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). - -Modding: -- 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). - ---------------------------------------------------------------------------------------------------------- -v0.1500.0.0 ---------------------------------------------------------------------------------------------------------- - -Additions and changes: -- Groundwork for the upcoming talent system: completing missions gives the characters experience points which can be used to unlock special abilities or buffs. Currently only the captain has talents implemented. -- Improved bot chatter when orders are being given, rearranged, or dismissed. -- Damage to arms reduces aiming accuracy. -- Crippled legs slow the player down more. -- Improvements to the blood particle effects when a character is bleeding. +- 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 fire 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. +- 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. @@ -344,14 +124,61 @@ Additions and changes: - Lever state is visualized on its sprite. - Enabled NVidia Optimus on Windows. -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. - 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. @@ -364,6 +191,12 @@ Fixes: 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. @@ -984,7 +817,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. From 1718dbc1c1fea505091cc152e5b8164a240883c4 Mon Sep 17 00:00:00 2001 From: Markus Isberg <3e849f2e5c@pm.me> Date: Wed, 27 Oct 2021 22:07:32 +0900 Subject: [PATCH 11/12] Unstable 0.15.12.0 --- Barotrauma/BarotraumaClient/LinuxClient.csproj | 2 +- Barotrauma/BarotraumaClient/MacClient.csproj | 2 +- Barotrauma/BarotraumaClient/WindowsClient.csproj | 2 +- Barotrauma/BarotraumaServer/LinuxServer.csproj | 2 +- Barotrauma/BarotraumaServer/MacServer.csproj | 2 +- Barotrauma/BarotraumaServer/WindowsServer.csproj | 2 +- .../SharedSource/Characters/AI/EnemyAIController.cs | 2 +- .../SharedSource/Characters/CharacterInfo.cs | 2 +- .../Characters/Health/Afflictions/AfflictionHusk.cs | 10 +++++++--- .../Items/Components/ElectricalDischarger.cs | 1 + Barotrauma/BarotraumaShared/changelog.txt | 10 ++++++++++ 11 files changed, 26 insertions(+), 11 deletions(-) diff --git a/Barotrauma/BarotraumaClient/LinuxClient.csproj b/Barotrauma/BarotraumaClient/LinuxClient.csproj index 33aa1207a..2ab72e41c 100644 --- a/Barotrauma/BarotraumaClient/LinuxClient.csproj +++ b/Barotrauma/BarotraumaClient/LinuxClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.15.11.0 + 0.15.12.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/MacClient.csproj b/Barotrauma/BarotraumaClient/MacClient.csproj index cb3f8eac4..8ed473dd0 100644 --- a/Barotrauma/BarotraumaClient/MacClient.csproj +++ b/Barotrauma/BarotraumaClient/MacClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.15.11.0 + 0.15.12.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/WindowsClient.csproj b/Barotrauma/BarotraumaClient/WindowsClient.csproj index 7645101b6..c8d71c037 100644 --- a/Barotrauma/BarotraumaClient/WindowsClient.csproj +++ b/Barotrauma/BarotraumaClient/WindowsClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.15.11.0 + 0.15.12.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaServer/LinuxServer.csproj b/Barotrauma/BarotraumaServer/LinuxServer.csproj index f0fec53ed..a08658e40 100644 --- a/Barotrauma/BarotraumaServer/LinuxServer.csproj +++ b/Barotrauma/BarotraumaServer/LinuxServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.15.11.0 + 0.15.12.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/MacServer.csproj b/Barotrauma/BarotraumaServer/MacServer.csproj index f129e40bb..168d57971 100644 --- a/Barotrauma/BarotraumaServer/MacServer.csproj +++ b/Barotrauma/BarotraumaServer/MacServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.15.11.0 + 0.15.12.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/WindowsServer.csproj b/Barotrauma/BarotraumaServer/WindowsServer.csproj index 3378b23f1..575a80c8f 100644 --- a/Barotrauma/BarotraumaServer/WindowsServer.csproj +++ b/Barotrauma/BarotraumaServer/WindowsServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.15.11.0 + 0.15.12.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs index d4ccdeadd..440bf76ce 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs @@ -2343,7 +2343,7 @@ namespace Barotrauma } if (steeringManager is IndoorsSteeringManager pathSteering) { - if (!pathSteering.IsPathDirty && pathSteering.CurrentPath.Unreachable) + if (!pathSteering.IsPathDirty && pathSteering.CurrentPath != null && pathSteering.CurrentPath.Unreachable) { // Can't reach State = AIState.Idle; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs index cd9c653a5..c193a3918 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs @@ -1261,7 +1261,7 @@ namespace Barotrauma } const int BaseExperienceRequired = -50; - const int AddedExperienceRequiredPerLevel = 550; + const int AddedExperienceRequiredPerLevel = 500; public int GetTotalTalentPoints() { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionHusk.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionHusk.cs index 0cf50429f..3878de39b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionHusk.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionHusk.cs @@ -151,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); @@ -218,9 +219,12 @@ namespace Barotrauma XElement infoElement = character.Info?.Save(parentElement); CharacterInfo huskCharacterInfo = infoElement == null ? null : new CharacterInfo(infoElement); - var bodyTint = GetBodyTint(); - huskCharacterInfo.SkinColor = - Color.Lerp(huskCharacterInfo.SkinColor, bodyTint.Opaque(), bodyTint.A / 255.0f); + 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) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ElectricalDischarger.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ElectricalDischarger.cs index 2d58be590..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); } } diff --git a/Barotrauma/BarotraumaShared/changelog.txt b/Barotrauma/BarotraumaShared/changelog.txt index de4346bc7..eefe78651 100644 --- a/Barotrauma/BarotraumaShared/changelog.txt +++ b/Barotrauma/BarotraumaShared/changelog.txt @@ -1,3 +1,13 @@ +--------------------------------------------------------------------------------------------------------- +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 --------------------------------------------------------------------------------------------------------- From 4b5b6c5ee649a229ead81315f7d24e440336851c Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Wed, 27 Oct 2021 18:57:04 +0300 Subject: [PATCH 12/12] Updated libraries --- .../Dynamics/World.cs | 3 +- .../Graphics/SpriteBatch.cs | 191 ++++-- .../Graphics/SpriteBatchItem.cs | 3 +- .../Graphics/SpriteBatcher.cs | 32 +- Libraries/XNATypes/RectangleF.cs | 551 ++++++++++++++++++ 5 files changed, 709 insertions(+), 71 deletions(-) create mode 100644 Libraries/XNATypes/RectangleF.cs diff --git a/Libraries/Farseer Physics Engine 3.5/Dynamics/World.cs b/Libraries/Farseer Physics Engine 3.5/Dynamics/World.cs index e950db8d9..e8b8cbea2 100644 --- a/Libraries/Farseer Physics Engine 3.5/Dynamics/World.cs +++ b/Libraries/Farseer Physics Engine 3.5/Dynamics/World.cs @@ -999,7 +999,7 @@ namespace FarseerPhysics.Dynamics if (body == null) throw new ArgumentNullException("body"); if (body.World != this) - throw new ArgumentException("You are removing a body that is not in the simulation.", "body"); + throw new ArgumentException($"You are removing a body that is not in the simulation (userdata: {body.UserData?.ToString() ?? "null"}).", "body"); #if USE_AWAKE_BODY_SET Debug.Assert(!AwakeBodySet.Contains(body)); @@ -1034,6 +1034,7 @@ namespace FarseerPhysics.Dynamics body.DestroyProxies(); for (int i = 0; i < body.FixtureList.Count; i++) { + body.FixtureList[i].UserData = null; if (FixtureRemoved != null) FixtureRemoved(this, body, body.FixtureList[i]); } diff --git a/Libraries/MonoGame.Framework/Src/MonoGame.Framework/Graphics/SpriteBatch.cs b/Libraries/MonoGame.Framework/Src/MonoGame.Framework/Graphics/SpriteBatch.cs index a2cc3d4e3..d6560df72 100644 --- a/Libraries/MonoGame.Framework/Src/MonoGame.Framework/Graphics/SpriteBatch.cs +++ b/Libraries/MonoGame.Framework/Src/MonoGame.Framework/Graphics/SpriteBatch.cs @@ -3,6 +3,9 @@ // file 'LICENSE.txt', which is part of this source code package. using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; using System.Text; namespace Microsoft.Xna.Framework.Graphics @@ -25,15 +28,58 @@ namespace Microsoft.Xna.Framework.Graphics ///
public class SpriteBatch : GraphicsResource, ISpriteBatch { + public struct EffectWithParams + { + private static readonly Dictionary parameterSetters; + private static readonly object[] setterParams = new object[1]; + + static EffectWithParams() + { + parameterSetters = new Dictionary(); + foreach (var method in typeof(EffectParameter).GetMethods()) + { + if (method.Name.Equals("SetValue", StringComparison.InvariantCulture) + && method.GetParameters() is { Length: 1 } parameters) + { + var type = parameters[0].ParameterType; + parameterSetters[type] = method; + foreach (var derived in Assembly.GetAssembly(type).GetTypes().Where(t => t.IsSubclassOf(type))) + { + parameterSetters[derived] = method; + } + } + } + } + + public Effect Effect; + public Dictionary Params; + + public EffectWithParams(Effect effect, Dictionary parameters = null) + { + Effect = effect; + Params = parameters; + } + + internal void Apply() + { + foreach (var (paramName, paramValue) in Params) + { + setterParams[0] = paramValue; + parameterSetters[paramValue.GetType()].Invoke(Effect.Parameters[paramName], setterParams); + } + Effect.CurrentTechnique.Passes[0].Apply(); + } + } + #region Private Fields readonly SpriteBatcher _batcher; SpriteSortMode _sortMode; BlendState _blendState; SamplerState _samplerState; - DepthStencilState _depthStencilState; - RasterizerState _rasterizerState; - Effect _effect; + DepthStencilState _depthStencilState; + RasterizerState _rasterizerState; + EffectWithParams _effect; bool _beginCalled; Effect _spriteEffect; @@ -60,7 +106,7 @@ namespace Microsoft.Xna.Framework.Graphics if (graphicsDevice == null) { throw new ArgumentNullException ("graphicsDevice", FrameworkResources.ResourceCreationWhenDeviceIsNull); - } + } this.GraphicsDevice = graphicsDevice; @@ -107,7 +153,7 @@ namespace Microsoft.Xna.Framework.Graphics _samplerState = samplerState ?? SamplerState.LinearClamp; _depthStencilState = depthStencilState ?? DepthStencilState.None; _rasterizerState = rasterizerState ?? RasterizerState.CullCounterClockwise; - _effect = effect; + _effect = new EffectWithParams(effect); _matrix = transformMatrix; // Setup things now so a user can change them. @@ -119,6 +165,11 @@ namespace Microsoft.Xna.Framework.Graphics _beginCalled = true; } + /// + /// Returns the current effect. + /// + public Effect GetCurrentEffect() => _effect.Effect; + /// /// Flushes all batched text and sprites to the screen. /// @@ -132,18 +183,31 @@ namespace Microsoft.Xna.Framework.Graphics if (_sortMode != SpriteSortMode.Immediate) Setup(); - - _batcher.DrawBatch(_sortMode, _effect); + + _batcher.DrawBatch(_sortMode, _spritePass); } - - void Setup() + + /// + /// Swaps the current effect. + /// + public void SwapEffect(Effect effect = null, Dictionary parameters = null) + { + _effect = new EffectWithParams(effect, parameters); + } + + public void SwapEffect(EffectWithParams effectWithParams) + { + _effect = effectWithParams; + } + + void Setup() { var gd = GraphicsDevice; gd.BlendState = _blendState; gd.DepthStencilState = _depthStencilState; gd.RasterizerState = _rasterizerState; gd.SamplerStates[0] = _samplerState; - + var vp = gd.Viewport; if ((vp.Width != _lastViewport.Width) || (vp.Height != _lastViewport.Height)) { @@ -169,7 +233,7 @@ namespace Microsoft.Xna.Framework.Graphics _spritePass.Apply(); } - + void CheckValid(Texture2D texture) { if (texture == null) @@ -279,6 +343,7 @@ namespace Microsoft.Xna.Framework.Graphics { var item = _batcher.CreateBatchItem(); item.Texture = texture; + item.Effect = _effect; item.SortKey = sortKey; @@ -315,6 +380,7 @@ namespace Microsoft.Xna.Framework.Graphics var item = _batcher.CreateBatchItem(); item.Texture = texture; + item.Effect = _effect; // set SortKey based on SpriteSortMode. switch ( _sortMode ) @@ -332,9 +398,9 @@ namespace Microsoft.Xna.Framework.Graphics item.SortKey = -layerDepth; break; } - + origin = origin * scale; - + float w, h; if (sourceRectangle.HasValue) { @@ -353,7 +419,7 @@ namespace Microsoft.Xna.Framework.Graphics _texCoordTL = Vector2.Zero; _texCoordBR = Vector2.One; } - + if ((effects & SpriteEffects.FlipVertically) != 0) { var temp = _texCoordBR.Y; @@ -366,7 +432,7 @@ namespace Microsoft.Xna.Framework.Graphics _texCoordBR.X = _texCoordTL.X; _texCoordTL.X = temp; } - + if (rotation == 0f) { item.Set(position.X - origin.X, @@ -393,7 +459,7 @@ namespace Microsoft.Xna.Framework.Graphics _texCoordBR, layerDepth); } - + FlushIfNeeded(); } @@ -444,9 +510,10 @@ namespace Microsoft.Xna.Framework.Graphics float layerDepth) { CheckValid(texture); - + var item = _batcher.CreateBatchItem(); item.Texture = texture; + item.Effect = _effect; // set SortKey based on SpriteSortMode. switch ( _sortMode ) @@ -478,7 +545,7 @@ namespace Microsoft.Xna.Framework.Graphics else origin.X = origin.X * (float)destinationRectangle.Width * texture.TexelWidth; if(srcRect.Height != 0) - origin.Y = origin.Y * (float)destinationRectangle.Height / (float)srcRect.Height; + origin.Y = origin.Y * (float)destinationRectangle.Height / (float)srcRect.Height; else origin.Y = origin.Y * (float)destinationRectangle.Height * texture.TexelHeight; } @@ -486,11 +553,11 @@ namespace Microsoft.Xna.Framework.Graphics { _texCoordTL = Vector2.Zero; _texCoordBR = Vector2.One; - + origin.X = origin.X * (float)destinationRectangle.Width * texture.TexelWidth; origin.Y = origin.Y * (float)destinationRectangle.Height * texture.TexelHeight; } - + if ((effects & SpriteEffects.FlipVertically) != 0) { var temp = _texCoordBR.Y; @@ -539,7 +606,7 @@ namespace Microsoft.Xna.Framework.Graphics { if (_sortMode == SpriteSortMode.Immediate) { - _batcher.DrawBatch(_sortMode, _effect); + _batcher.DrawBatch(_sortMode, _spritePass); } } @@ -553,10 +620,11 @@ namespace Microsoft.Xna.Framework.Graphics public void Draw (Texture2D texture, Vector2 position, Rectangle? sourceRectangle, Color color) { CheckValid(texture); - + var item = _batcher.CreateBatchItem(); item.Texture = texture; - + item.Effect = _effect; + // set SortKey based on SpriteSortMode. item.SortKey = _sortMode == SpriteSortMode.Texture ? texture.SortingKey : 0; @@ -600,13 +668,14 @@ namespace Microsoft.Xna.Framework.Graphics public void Draw (Texture2D texture, Rectangle destinationRectangle, Rectangle? sourceRectangle, Color color) { CheckValid(texture); - + var item = _batcher.CreateBatchItem(); item.Texture = texture; - + item.Effect = _effect; + // set SortKey based on SpriteSortMode. item.SortKey = _sortMode == SpriteSortMode.Texture ? texture.SortingKey : 0; - + if (sourceRectangle.HasValue) { var srcRect = sourceRectangle.GetValueOrDefault(); @@ -629,7 +698,7 @@ namespace Microsoft.Xna.Framework.Graphics _texCoordTL, _texCoordBR, 0); - + FlushIfNeeded(); } @@ -642,13 +711,14 @@ namespace Microsoft.Xna.Framework.Graphics public void Draw (Texture2D texture, Vector2 position, Color color) { CheckValid(texture); - + var item = _batcher.CreateBatchItem(); item.Texture = texture; - + item.Effect = _effect; + // set SortKey based on SpriteSortMode. item.SortKey = _sortMode == SpriteSortMode.Texture ? texture.SortingKey : 0; - + item.Set(position.X, position.Y, texture.Width, @@ -670,13 +740,14 @@ namespace Microsoft.Xna.Framework.Graphics public void Draw(Texture2D texture, Rectangle destinationRectangle, Color color) { CheckValid(texture); - + var item = _batcher.CreateBatchItem(); item.Texture = texture; - + item.Effect = _effect; + // set SortKey based on SpriteSortMode. item.SortKey = _sortMode == SpriteSortMode.Texture ? texture.SortingKey : 0; - + item.Set(destinationRectangle.X, destinationRectangle.Y, destinationRectangle.Width, @@ -685,7 +756,7 @@ namespace Microsoft.Xna.Framework.Graphics Vector2.Zero, Vector2.One, 0); - + FlushIfNeeded(); } @@ -699,7 +770,7 @@ namespace Microsoft.Xna.Framework.Graphics public unsafe void DrawString (SpriteFont spriteFont, string text, Vector2 position, Color color) { CheckValid(spriteFont, text); - + float sortKey = (_sortMode == SpriteSortMode.Texture) ? spriteFont.Texture.SortingKey : 0; var offset = Vector2.Zero; @@ -720,7 +791,7 @@ namespace Microsoft.Xna.Framework.Graphics firstGlyphOfLine = true; continue; } - + var currentGlyphIndex = spriteFont.GetGlyphIndexOrDefault(c); var pCurrentGlyph = pGlyphs + currentGlyphIndex; @@ -737,15 +808,16 @@ namespace Microsoft.Xna.Framework.Graphics offset.X += spriteFont.Spacing + pCurrentGlyph->LeftSideBearing; } - var p = offset; + var p = offset; p.X += pCurrentGlyph->Cropping.X; p.Y += pCurrentGlyph->Cropping.Y; p += position; var item = _batcher.CreateBatchItem(); item.Texture = spriteFont.Texture; + item.Effect = _effect; item.SortKey = sortKey; - + _texCoordTL.X = pCurrentGlyph->BoundsInTexture.X * spriteFont.Texture.TexelWidth; _texCoordTL.Y = pCurrentGlyph->BoundsInTexture.Y * spriteFont.Texture.TexelHeight; _texCoordBR.X = (pCurrentGlyph->BoundsInTexture.X + pCurrentGlyph->BoundsInTexture.Width) * spriteFont.Texture.TexelWidth; @@ -759,7 +831,7 @@ namespace Microsoft.Xna.Framework.Graphics _texCoordTL, _texCoordBR, 0); - + offset.X += pCurrentGlyph->Width + pCurrentGlyph->RightSideBearing; } @@ -804,7 +876,7 @@ namespace Microsoft.Xna.Framework.Graphics float rotation, Vector2 origin, Vector2 scale, SpriteEffects effects, float layerDepth) { CheckValid(spriteFont, text); - + float sortKey = 0; // set SortKey based on SpriteSortMode. switch (_sortMode) @@ -831,7 +903,7 @@ namespace Microsoft.Xna.Framework.Graphics if (flippedVert || flippedHorz) { Vector2 size; - + var source = new SpriteFont.CharacterSource(text); spriteFont.MeasureString(ref source, out size); @@ -847,7 +919,7 @@ namespace Microsoft.Xna.Framework.Graphics flipAdjustment.Y = spriteFont.LineSpacing - size.Y; } } - + Matrix transformation = Matrix.Identity; float cos = 0, sin = 0; if (rotation == 0) @@ -866,7 +938,7 @@ namespace Microsoft.Xna.Framework.Graphics transformation.M21 = (flippedVert ? -scale.Y : scale.Y) * (-sin); transformation.M22 = (flippedVert ? -scale.Y : scale.Y) * cos; transformation.M41 = (((flipAdjustment.X - origin.X) * transformation.M11) + (flipAdjustment.Y - origin.Y) * transformation.M21) + position.X; - transformation.M42 = (((flipAdjustment.X - origin.X) * transformation.M12) + (flipAdjustment.Y - origin.Y) * transformation.M22) + position.Y; + transformation.M42 = (((flipAdjustment.X - origin.X) * transformation.M12) + (flipAdjustment.Y - origin.Y) * transformation.M22) + position.Y; } var offset = Vector2.Zero; @@ -916,15 +988,16 @@ namespace Microsoft.Xna.Framework.Graphics Vector2.Transform(ref p, ref transformation, out p); - var item = _batcher.CreateBatchItem(); + var item = _batcher.CreateBatchItem(); item.Texture = spriteFont.Texture; + item.Effect = _effect; item.SortKey = sortKey; - + _texCoordTL.X = pCurrentGlyph->BoundsInTexture.X * spriteFont.Texture.TexelWidth; _texCoordTL.Y = pCurrentGlyph->BoundsInTexture.Y * spriteFont.Texture.TexelHeight; _texCoordBR.X = (pCurrentGlyph->BoundsInTexture.X + pCurrentGlyph->BoundsInTexture.Width) * spriteFont.Texture.TexelWidth; _texCoordBR.Y = (pCurrentGlyph->BoundsInTexture.Y + pCurrentGlyph->BoundsInTexture.Height) * spriteFont.Texture.TexelHeight; - + if ((effects & SpriteEffects.FlipVertically) != 0) { var temp = _texCoordBR.Y; @@ -964,7 +1037,7 @@ namespace Microsoft.Xna.Framework.Graphics _texCoordBR, layerDepth); } - + offset.X += pCurrentGlyph->Width + pCurrentGlyph->RightSideBearing; } @@ -982,7 +1055,7 @@ namespace Microsoft.Xna.Framework.Graphics public unsafe void DrawString (SpriteFont spriteFont, StringBuilder text, Vector2 position, Color color) { CheckValid(spriteFont, text); - + float sortKey = (_sortMode == SpriteSortMode.Texture) ? spriteFont.Texture.SortingKey : 0; var offset = Vector2.Zero; @@ -1020,15 +1093,16 @@ namespace Microsoft.Xna.Framework.Graphics offset.X += spriteFont.Spacing + pCurrentGlyph->LeftSideBearing; } - var p = offset; + var p = offset; p.X += pCurrentGlyph->Cropping.X; p.Y += pCurrentGlyph->Cropping.Y; p += position; - + var item = _batcher.CreateBatchItem(); item.Texture = spriteFont.Texture; + item.Effect = _effect; item.SortKey = sortKey; - + _texCoordTL.X = pCurrentGlyph->BoundsInTexture.X * spriteFont.Texture.TexelWidth; _texCoordTL.Y = pCurrentGlyph->BoundsInTexture.Y * spriteFont.Texture.TexelHeight; _texCoordBR.X = (pCurrentGlyph->BoundsInTexture.X + pCurrentGlyph->BoundsInTexture.Width) * spriteFont.Texture.TexelWidth; @@ -1087,7 +1161,7 @@ namespace Microsoft.Xna.Framework.Graphics float rotation, Vector2 origin, Vector2 scale, SpriteEffects effects, float layerDepth) { CheckValid(spriteFont, text); - + float sortKey = 0; // set SortKey based on SpriteSortMode. switch (_sortMode) @@ -1129,7 +1203,7 @@ namespace Microsoft.Xna.Framework.Graphics flipAdjustment.Y = spriteFont.LineSpacing - size.Y; } } - + Matrix transformation = Matrix.Identity; float cos = 0, sin = 0; if (rotation == 0) @@ -1148,7 +1222,7 @@ namespace Microsoft.Xna.Framework.Graphics transformation.M21 = (flippedVert ? -scale.Y : scale.Y) * (-sin); transformation.M22 = (flippedVert ? -scale.Y : scale.Y) * cos; transformation.M41 = (((flipAdjustment.X - origin.X) * transformation.M11) + (flipAdjustment.Y - origin.Y) * transformation.M21) + position.X; - transformation.M42 = (((flipAdjustment.X - origin.X) * transformation.M12) + (flipAdjustment.Y - origin.Y) * transformation.M22) + position.Y; + transformation.M42 = (((flipAdjustment.X - origin.X) * transformation.M12) + (flipAdjustment.Y - origin.Y) * transformation.M22) + position.Y; } var offset = Vector2.Zero; @@ -1197,16 +1271,17 @@ namespace Microsoft.Xna.Framework.Graphics p.Y += pCurrentGlyph->Cropping.Y; Vector2.Transform(ref p, ref transformation, out p); - - var item = _batcher.CreateBatchItem(); + + var item = _batcher.CreateBatchItem(); item.Texture = spriteFont.Texture; + item.Effect = _effect; item.SortKey = sortKey; - + _texCoordTL.X = pCurrentGlyph->BoundsInTexture.X * (float)spriteFont.Texture.TexelWidth; _texCoordTL.Y = pCurrentGlyph->BoundsInTexture.Y * (float)spriteFont.Texture.TexelHeight; _texCoordBR.X = (pCurrentGlyph->BoundsInTexture.X + pCurrentGlyph->BoundsInTexture.Width) * (float)spriteFont.Texture.TexelWidth; _texCoordBR.Y = (pCurrentGlyph->BoundsInTexture.Y + pCurrentGlyph->BoundsInTexture.Height) * (float)spriteFont.Texture.TexelHeight; - + if ((effects & SpriteEffects.FlipVertically) != 0) { var temp = _texCoordBR.Y; diff --git a/Libraries/MonoGame.Framework/Src/MonoGame.Framework/Graphics/SpriteBatchItem.cs b/Libraries/MonoGame.Framework/Src/MonoGame.Framework/Graphics/SpriteBatchItem.cs index d0ebb45e5..484e521d4 100644 --- a/Libraries/MonoGame.Framework/Src/MonoGame.Framework/Graphics/SpriteBatchItem.cs +++ b/Libraries/MonoGame.Framework/Src/MonoGame.Framework/Graphics/SpriteBatchItem.cs @@ -9,6 +9,7 @@ namespace Microsoft.Xna.Framework.Graphics internal class SpriteBatchItem : IComparable { public Texture2D Texture; + public SpriteBatch.EffectWithParams Effect; public float SortKey; public VertexPositionColorTexture vertexTL; @@ -20,7 +21,7 @@ namespace Microsoft.Xna.Framework.Graphics vertexTL = new VertexPositionColorTexture(); vertexTR = new VertexPositionColorTexture(); vertexBL = new VertexPositionColorTexture(); - vertexBR = new VertexPositionColorTexture(); + vertexBR = new VertexPositionColorTexture(); } public void Set ( float x, float y, float dx, float dy, float w, float h, float sin, float cos, Color color, Vector2 texCoordTL, Vector2 texCoordBR, float depth ) diff --git a/Libraries/MonoGame.Framework/Src/MonoGame.Framework/Graphics/SpriteBatcher.cs b/Libraries/MonoGame.Framework/Src/MonoGame.Framework/Graphics/SpriteBatcher.cs index 036fd783c..948d0078c 100644 --- a/Libraries/MonoGame.Framework/Src/MonoGame.Framework/Graphics/SpriteBatcher.cs +++ b/Libraries/MonoGame.Framework/Src/MonoGame.Framework/Graphics/SpriteBatcher.cs @@ -11,7 +11,7 @@ namespace Microsoft.Xna.Framework.Graphics /// This class handles the queueing of batch items into the GPU by creating the triangle tesselations /// that are used to draw the sprite textures. This class supports int.MaxValue number of sprites to be /// batched and will process them into short.MaxValue groups (strided by 6 for the number of vertices - /// sent to the GPU). + /// sent to the GPU). ///
internal class SpriteBatcher { @@ -68,7 +68,7 @@ namespace Microsoft.Xna.Framework.Graphics } /// - /// Reuse a previously allocated SpriteBatchItem from the item pool. + /// Reuse a previously allocated SpriteBatchItem from the item pool. /// if there is none available grow the pool and initialize new items. /// /// @@ -143,12 +143,8 @@ namespace Microsoft.Xna.Framework.Graphics /// overflow the 16 bit array indices for vertices. ///
/// The type of depth sorting desired for the rendering. - /// The custom effect to apply to the drawn geometry - public unsafe void DrawBatch(SpriteSortMode sortMode, Effect effect) + public unsafe void DrawBatch(SpriteSortMode sortMode, EffectPass defaultSpritePass) { - if (effect != null && effect.IsDisposed) - throw new ObjectDisposedException("effect"); - // nothing to do if (_batchItemCount == 0) return; @@ -180,6 +176,7 @@ namespace Microsoft.Xna.Framework.Graphics var startIndex = 0; var index = 0; Texture2D tex = null; + SpriteBatch.EffectWithParams effect = default; int numBatchesToProcess = batchCount; if (numBatchesToProcess > MaxBatchSize) @@ -196,12 +193,24 @@ namespace Microsoft.Xna.Framework.Graphics { SpriteBatchItem item = _batchItemList[batchIndex]; // if the texture changed, we need to flush and bind the new texture - var shouldFlush = !ReferenceEquals(item.Texture, tex); + var shouldFlush = + !ReferenceEquals(item.Texture, tex) + || !ReferenceEquals(item.Effect.Effect, effect.Effect) + || !ReferenceEquals(item.Effect.Params, effect.Params); if (shouldFlush) { - FlushVertexArray(startIndex, index, effect, tex); + FlushVertexArray(startIndex, index, effect.Effect, tex); tex = item.Texture; + effect = item.Effect; + if (effect.Effect is null || effect.Params is null) + { + defaultSpritePass.Apply(); + } + else + { + effect.Apply(); + } startIndex = index = 0; vertexArrayPtr = vertexArrayFixedPtr; _device.Textures[0] = tex; @@ -215,15 +224,16 @@ namespace Microsoft.Xna.Framework.Graphics // Release the texture. item.Texture = null; + item.Effect = default; } } // flush the remaining vertexArray data - FlushVertexArray(startIndex, index, effect, tex); + FlushVertexArray(startIndex, index, effect.Effect, tex); // Update our batch count to continue the process of culling down // large batches batchCount -= numBatchesToProcess; } - // return items to the pool. + // return items to the pool. _batchItemCount = 0; } diff --git a/Libraries/XNATypes/RectangleF.cs b/Libraries/XNATypes/RectangleF.cs new file mode 100644 index 000000000..1459e1e90 --- /dev/null +++ b/Libraries/XNATypes/RectangleF.cs @@ -0,0 +1,551 @@ +// MIT License - Copyright (C) The Mono.Xna Team +// This file is subject to the terms and conditions defined in +// file 'LICENSE.txt', which is part of this source code package. + +using System; + +namespace Microsoft.Xna.Framework +{ + public struct RectangleF : IEquatable + { + #region Private Fields + + private static RectangleF emptyRectangle = new RectangleF(); + + #endregion + + #region Public Fields + + /// + /// The x coordinate of the top-left corner of this . + /// + + public float X; + + /// + /// The y coordinate of the top-left corner of this . + /// + + public float Y; + + /// + /// The width of this . + /// + + public float Width; + + /// + /// The height of this . + /// + + public float Height; + + #endregion + + #region Public Properties + + /// + /// Returns a with X=0, Y=0, Width=0, Height=0. + /// + public static RectangleF Empty + { + get { return emptyRectangle; } + } + + /// + /// Returns the x coordinate of the left edge of this . + /// + public float Left + { + get { return this.X; } + } + + /// + /// Returns the x coordinate of the right edge of this . + /// + public float Right + { + get { return (this.X + this.Width); } + } + + /// + /// Returns the y coordinate of the top edge of this . + /// + public float Top + { + get { return this.Y; } + } + + /// + /// Returns the y coordinate of the bottom edge of this . + /// + public float Bottom + { + get { return (this.Y + this.Height); } + } + + /// + /// Whether or not this has a and + /// of 0, and a of (0, 0). + /// + public bool IsEmpty + { + get + { + return ((((this.Width == 0) && (this.Height == 0)) && (this.X == 0)) && (this.Y == 0)); + } + } + + /// + /// The top-left coordinates of this . + /// + public Vector2 Location + { + get + { + return new Vector2(this.X, this.Y); + } + set + { + X = value.X; + Y = value.Y; + } + } + + /// + /// The width-height coordinates of this . + /// + public Vector2 Size + { + get + { + return new Vector2(this.Width, this.Height); + } + set + { + Width = value.X; + Height = value.Y; + } + } + + /// + /// A located in the center of this . + /// + /// + /// If or is an odd number, + /// the center point will be rounded down. + /// + public Vector2 Center + { + get + { + return new Vector2(this.X + (this.Width / 2f), this.Y + (this.Height / 2f)); + } + } + + #endregion + + #region Internal Properties + + internal string DebugDisplayString + { + get + { + return string.Concat( + this.X, " ", + this.Y, " ", + this.Width, " ", + this.Height + ); + } + } + + #endregion + + #region Constructors + + /// + /// Creates a new instance of struct, with the specified + /// position, width, and height. + /// + /// The x coordinate of the top-left corner of the created . + /// The y coordinate of the top-left corner of the created . + /// The width of the created . + /// The height of the created . + public RectangleF(float x, float y, float width, float height) + { + this.X = x; + this.Y = y; + this.Width = width; + this.Height = height; + } + + /// + /// Creates a new instance of struct, with the specified + /// location and size. + /// + /// The x and y coordinates of the top-left corner of the created . + /// The width and height of the created . + public RectangleF(Vector2 location, Vector2 size) + { + this.X = location.X; + this.Y = location.Y; + this.Width = size.X; + this.Height = size.Y; + } + + #endregion + + #region Operators + + /// + /// Compares whether two instances are equal. + /// + /// instance on the left of the equal sign. + /// instance on the right of the equal sign. + /// true if the instances are equal; false otherwise. + public static bool operator ==(RectangleF a, RectangleF b) + { + return ((a.X == b.X) && (a.Y == b.Y) && (a.Width == b.Width) && (a.Height == b.Height)); + } + + /// + /// Compares whether two instances are not equal. + /// + /// instance on the left of the not equal sign. + /// instance on the right of the not equal sign. + /// true if the instances are not equal; false otherwise. + public static bool operator !=(RectangleF a, RectangleF b) + { + return !(a == b); + } + + public static implicit operator RectangleF(Rectangle r) => new RectangleF(r.X, r.Y, r.Width, r.Height); + + #endregion + + #region Public Methods + + /// + /// Gets whether or not the provided coordinates lie within the bounds of this . + /// + /// The x coordinate of the point to check for containment. + /// The y coordinate of the point to check for containment. + /// true if the provided coordinates lie inside this ; false otherwise. + public bool Contains(int x, int y) + { + return ((((this.X <= x) && (x < (this.X + this.Width))) && (this.Y <= y)) && (y < (this.Y + this.Height))); + } + + /// + /// Gets whether or not the provided coordinates lie within the bounds of this . + /// + /// The x coordinate of the point to check for containment. + /// The y coordinate of the point to check for containment. + /// true if the provided coordinates lie inside this ; false otherwise. + public bool Contains(float x, float y) + { + return ((((this.X <= x) && (x < (this.X + this.Width))) && (this.Y <= y)) && (y < (this.Y + this.Height))); + } + + /// + /// Gets whether or not the provided lies within the bounds of this . + /// + /// The coordinates to check for inclusion in this . + /// true if the provided lies inside this ; false otherwise. + public bool Contains(Point value) + { + return ((((this.X <= value.X) && (value.X < (this.X + this.Width))) && (this.Y <= value.Y)) && (value.Y < (this.Y + this.Height))); + } + + /// + /// Gets whether or not the provided lies within the bounds of this . + /// + /// The coordinates to check for inclusion in this . + /// true if the provided lies inside this ; false otherwise. As an output parameter. + public void Contains(ref Point value, out bool result) + { + result = ((((this.X <= value.X) && (value.X < (this.X + this.Width))) && (this.Y <= value.Y)) && (value.Y < (this.Y + this.Height))); + } + + /// + /// Gets whether or not the provided lies within the bounds of this . + /// + /// The coordinates to check for inclusion in this . + /// true if the provided lies inside this ; false otherwise. + public bool Contains(Vector2 value) + { + return ((((this.X <= value.X) && (value.X < (this.X + this.Width))) && (this.Y <= value.Y)) && (value.Y < (this.Y + this.Height))); + } + + /// + /// Gets whether or not the provided lies within the bounds of this . + /// + /// The coordinates to check for inclusion in this . + /// true if the provided lies inside this ; false otherwise. As an output parameter. + public void Contains(ref Vector2 value, out bool result) + { + result = ((((this.X <= value.X) && (value.X < (this.X + this.Width))) && (this.Y <= value.Y)) && (value.Y < (this.Y + this.Height))); + } + + /// + /// Gets whether or not the provided lies within the bounds of this . + /// + /// The to check for inclusion in this . + /// true if the provided 's bounds lie entirely inside this ; false otherwise. + public bool Contains(RectangleF value) + { + return ((((this.X <= value.X) && ((value.X + value.Width) <= (this.X + this.Width))) && (this.Y <= value.Y)) && ((value.Y + value.Height) <= (this.Y + this.Height))); + } + + /// + /// Gets whether or not the provided lies within the bounds of this . + /// + /// The to check for inclusion in this . + /// true if the provided 's bounds lie entirely inside this ; false otherwise. As an output parameter. + public void Contains(ref RectangleF value, out bool result) + { + result = ((((this.X <= value.X) && ((value.X + value.Width) <= (this.X + this.Width))) && (this.Y <= value.Y)) && ((value.Y + value.Height) <= (this.Y + this.Height))); + } + + /// + /// Compares whether current instance is equal to specified . + /// + /// The to compare. + /// true if the instances are equal; false otherwise. + public override bool Equals(object obj) + { + return (obj is RectangleF) && this == ((RectangleF)obj); + } + + /// + /// Compares whether current instance is equal to specified . + /// + /// The to compare. + /// true if the instances are equal; false otherwise. + public bool Equals(RectangleF other) + { + return this == other; + } + + /// + /// Gets the hash code of this . + /// + /// Hash code of this . + public override int GetHashCode() + { + unchecked + { + var hash = 17; + hash = hash * 23 + X.GetHashCode(); + hash = hash * 23 + Y.GetHashCode(); + hash = hash * 23 + Width.GetHashCode(); + hash = hash * 23 + Height.GetHashCode(); + return hash; + } + } + + /// + /// Adjusts the edges of this by specified horizontal and vertical amounts. + /// + /// Value to adjust the left and right edges. + /// Value to adjust the top and bottom edges. + public void Inflate(int horizontalAmount, int verticalAmount) + { + X -= horizontalAmount; + Y -= verticalAmount; + Width += horizontalAmount * 2; + Height += verticalAmount * 2; + } + + /// + /// Adjusts the edges of this by specified horizontal and vertical amounts. + /// + /// Value to adjust the left and right edges. + /// Value to adjust the top and bottom edges. + public void Inflate(float horizontalAmount, float verticalAmount) + { + X -= (float)horizontalAmount; + Y -= (float)verticalAmount; + Width += (float)horizontalAmount * 2; + Height += (float)verticalAmount * 2; + } + + /// + /// Adjusts the edges of this by specified horizontal and vertical amounts. + /// + /// Value to adjust the edges. + public void Inflate(Vector2 amount) + { + Inflate(amount.X, amount.Y); + } + + /// + /// Gets whether or not the other intersects with this rectangle. + /// + /// The other rectangle for testing. + /// true if other intersects with this rectangle; false otherwise. + public bool Intersects(RectangleF value) + { + return value.Left < Right && + Left < value.Right && + value.Top < Bottom && + Top < value.Bottom; + } + + + /// + /// Gets whether or not the other intersects with this rectangle. + /// + /// The other rectangle for testing. + /// true if other intersects with this rectangle; false otherwise. As an output parameter. + public void Intersects(ref RectangleF value, out bool result) + { + result = value.Left < Right && + Left < value.Right && + value.Top < Bottom && + Top < value.Bottom; + } + + /// + /// Creates a new that contains overlapping region of two other rectangles. + /// + /// The first . + /// The second . + /// Overlapping region of the two rectangles. + public static RectangleF Intersect(RectangleF value1, RectangleF value2) + { + RectangleF rectangle; + Intersect(ref value1, ref value2, out rectangle); + return rectangle; + } + + /// + /// Creates a new that contains overlapping region of two other rectangles. + /// + /// The first . + /// The second . + /// Overlapping region of the two rectangles as an output parameter. + public static void Intersect(ref RectangleF value1, ref RectangleF value2, out RectangleF result) + { + if (value1.Intersects(value2)) + { + float right_side = MathF.Min(value1.X + value1.Width, value2.X + value2.Width); + float left_side = MathF.Max(value1.X, value2.X); + float top_side = MathF.Max(value1.Y, value2.Y); + float bottom_side = MathF.Min(value1.Y + value1.Height, value2.Y + value2.Height); + result = new RectangleF(left_side, top_side, right_side - left_side, bottom_side - top_side); + } + else + { + result = new RectangleF(0, 0, 0, 0); + } + } + + /// + /// Changes the of this . + /// + /// The x coordinate to add to this . + /// The y coordinate to add to this . + public void Offset(int offsetX, int offsetY) + { + X += offsetX; + Y += offsetY; + } + + /// + /// Changes the of this . + /// + /// The x coordinate to add to this . + /// The y coordinate to add to this . + public void Offset(float offsetX, float offsetY) + { + X += (float)offsetX; + Y += (float)offsetY; + } + + /// + /// Changes the of this . + /// + /// The x and y components to add to this . + public void Offset(Point amount) + { + X += amount.X; + Y += amount.Y; + } + + /// + /// Changes the of this . + /// + /// The x and y components to add to this . + public void Offset(Vector2 amount) + { + X += (float)amount.X; + Y += (float)amount.Y; + } + + /// + /// Returns a representation of this in the format: + /// {X:[] Y:[] Width:[] Height:[]} + /// + /// representation of this . + public override string ToString() + { + return "{X:" + X + " Y:" + Y + " Width:" + Width + " Height:" + Height + "}"; + } + + /// + /// Creates a new that completely contains two other rectangles. + /// + /// The first . + /// The second . + /// The union of the two rectangles. + public static RectangleF Union(RectangleF value1, RectangleF value2) + { + float x = MathF.Min(value1.X, value2.X); + float y = MathF.Min(value1.Y, value2.Y); + return new RectangleF(x, y, + Math.Max(value1.Right, value2.Right) - x, + Math.Max(value1.Bottom, value2.Bottom) - y); + } + + /// + /// Creates a new that completely contains two other rectangles. + /// + /// The first . + /// The second . + /// The union of the two rectangles as an output parameter. + public static void Union(ref RectangleF value1, ref RectangleF value2, out RectangleF result) + { + result.X = Math.Min(value1.X, value2.X); + result.Y = Math.Min(value1.Y, value2.Y); + result.Width = Math.Max(value1.Right, value2.Right) - result.X; + result.Height = Math.Max(value1.Bottom, value2.Bottom) - result.Y; + } + + public void AddPoint(Point point) + { + if (point.X < X) + { + Width += X - point.X; + X = point.X; + } + else if (point.X > Right) + { + Width += point.X - Right; + } + + if (point.Y < Y) + { + Height += Y - point.Y; + Y = point.Y; + } + else if (point.Y > Bottom) + { + Height += point.Y - Bottom; + } + } + + #endregion + } +}
@@ -221,14 +308,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 +368,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/Fabricator.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs index 8d84d9d26..1ea32a0f3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs @@ -316,7 +316,7 @@ namespace Barotrauma.Items.Components availablePrefab.Condition -= availablePrefab.Prefab.Health * requiredItem.MinCondition; continue; } - + availablePrefabs.Remove(availablePrefab); Entity.Spawner.AddToRemoveQueue(availablePrefab); inputContainer.Inventory.RemoveItem(availablePrefab); @@ -324,18 +324,20 @@ namespace Barotrauma.Items.Components } }); - Character tempUser = user; - int amountFittingContainer = outputContainer.Inventory.HowManyCanBePut(fabricatedItem.TargetItem, fabricatedItem.OutCondition * fabricatedItem.TargetItem.Health); - var itemsCreated = new AbilityValue(fabricatedItem.Amount); - foreach (Character character in Character.CharacterList.Where(c => c.TeamID == user.TeamID)) + + var fabricationValueItem = new AbilityValueItem(fabricatedItem.Amount, fabricatedItem.TargetItem); + if (user != null) { - character.CheckTalents(AbilityEffectType.OnAllyItemFabricatedAmount, (fabricatedItem.TargetItem, itemsCreated)); + foreach (Character character in Character.CharacterList.Where(c => c.TeamID == user.TeamID)) + { + character.CheckTalents(AbilityEffectType.OnAllyItemFabricatedAmount, fabricationValueItem); + } + user.CheckTalents(AbilityEffectType.OnItemFabricatedAmount, fabricationValueItem); } - tempUser.CheckTalents(AbilityEffectType.OnItemFabricatedAmount, (fabricatedItem.TargetItem, itemsCreated)); - - for (int i = 0; i < (int)itemsCreated.Value; i++) + var tempUser = user; + for (int i = 0; i < (int)fabricationValueItem.Value; i++) { if (i < amountFittingContainer) { @@ -359,14 +361,13 @@ 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 AbilityValue(0f); + var addedSkillValue = new AbilityValueString(0f, skill.Identifier); user.CheckTalents(AbilityEffectType.OnItemFabricationSkillGain, addedSkillValue); user.Info.IncreaseSkillLevel( diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Steering.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Steering.cs index 543e9bc1b..5c8e7e70e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Steering.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Steering.cs @@ -365,16 +365,13 @@ namespace Barotrauma.Items.Components 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. - // TODO: add current_velocity_x and current_velocity_y pins on the navigation terminals and shuttle terminals - // TODO: increase the size of the connection panels of both navigation terminals - 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.ToString("0.0000", CultureInfo.InvariantCulture), sender: user), "current_position_x"); - item.SendSignal(new Signal(sub.RealWorldDepth.ToString("0.0000", CultureInfo.InvariantCulture), sender: user), "current_depth"); + 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 diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Power/PowerTransfer.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Power/PowerTransfer.cs index a587d47ff..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); 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 2b474213b..37f5833fc 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs @@ -339,7 +339,7 @@ namespace Barotrauma.Items.Components if (currentFixerAction == FixActions.Tinker) { - // this is a bit code rotty to interject it here, should be less reliant on returning + // not great to interject it here, should be less reliant on returning if (!CanTinker(CurrentFixer)) { StopRepairing(CurrentFixer); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/ConnectionPanel.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/ConnectionPanel.cs index e7648c9ac..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); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Terminal.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Terminal.cs index 540284013..335827046 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Terminal.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Terminal.cs @@ -41,7 +41,7 @@ namespace Barotrauma.Items.Components set { if (string.IsNullOrEmpty(value)) { return; } - ShowOnDisplay(value); + ShowOnDisplay(value, addToHistory: true); } } @@ -59,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) { @@ -70,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(); @@ -110,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/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/Turret.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs index 0bed7d299..ecb5d1cf4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs @@ -64,6 +64,8 @@ namespace Barotrauma.Items.Components private Character currentTarget; const float aiFindTargetInterval = 5.0f; + private const float TinkeringPowerCostReduction = 1.25f; + public float Rotation { get { return rotation; } @@ -504,9 +506,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) @@ -617,10 +629,12 @@ namespace Barotrauma.Items.Components 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 if (isTinkering) { - neededPower /= 1.25f; + neededPower /= TinkeringPowerCostReduction; } while (neededPower > 0.0001f && batteries.Count > 0) { @@ -1022,7 +1036,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) { @@ -1046,7 +1060,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) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Wearable.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Wearable.cs index baec2c0b0..be1b859b6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Wearable.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Wearable.cs @@ -55,6 +55,8 @@ namespace Barotrauma public float Scale { get; private set; } + public float Rotation { get; private set; } + public LimbType DepthLimb { get; private set; } private Wearable _wearableComponent; public Wearable WearableComponent @@ -177,6 +179,7 @@ namespace Barotrauma 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) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs index a259d4246..f422df2e8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs @@ -496,7 +496,7 @@ 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; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs index db66ce9f0..14fb5af13 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs @@ -540,6 +540,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; } @@ -767,6 +773,8 @@ namespace Barotrauma condition = MaxCondition; lastSentCondition = condition; + AllowDeconstruct = itemPrefab.AllowDeconstruct; + allPropertyObjects.Add(this); XElement element = itemPrefab.ConfigElement; @@ -1431,7 +1439,7 @@ namespace Barotrauma bool hasTargets = effect.TargetIdentifiers == null; targets.Clear(); - + if (effect.HasTargetType(StatusEffect.TargetType.Contained)) { foreach (Item containedItem in ContainedItems) @@ -1443,6 +1451,11 @@ namespace Barotrauma continue; } + if (effect.TargetSlot > -1) + { + if (OwnInventory.FindIndex(containedItem) != effect.TargetSlot) { continue; } + } + hasTargets = true; targets.Add(containedItem); } @@ -1500,8 +1513,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); } @@ -2298,8 +2311,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) @@ -2312,7 +2325,7 @@ namespace Barotrauma 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; @@ -2331,7 +2344,7 @@ namespace Barotrauma }); } - if (ic.DeleteOnUse) remove = true; + if (ic.DeleteOnUse) { remove = true; } } if (remove) { Spawner?.AddToRemoveQueue(this); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/ItemInventory.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/ItemInventory.cs index d449ad8c9..bcefbec04 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; } @@ -48,14 +48,14 @@ 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; + return item != null && slots[i].CanBePut(item, ignoreCondition) && slots[i].ItemCount < container.GetMaxStackSize(i); } public override bool CanBePutInSlot(ItemPrefab itemPrefab, int i, float? condition) { 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; + return itemPrefab != null && slots[i].CanBePut(itemPrefab, condition) && slots[i].ItemCount < container.GetMaxStackSize(i); } public override int HowManyCanBePut(ItemPrefab itemPrefab, int i, float? condition) @@ -63,7 +63,7 @@ namespace Barotrauma 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); + 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 e2c81690d..67a117dde 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs @@ -18,9 +18,18 @@ 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, string parentDebugName) @@ -28,14 +37,20 @@ namespace Barotrauma 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); + } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Explosion.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Explosion.cs index a1958db8e..4b657de2a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Explosion.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Explosion.cs @@ -33,6 +33,9 @@ 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; @@ -63,8 +66,10 @@ namespace Barotrauma force = element.GetAttributeFloat("force", 0.0f); - bool showEffects = element.GetAttributeBool("showeffects", true); + abilityExplosion = element.GetAttributeBool("abilityexplosion", false); + applyToSelf = element.GetAttributeBool("applytoself", true); + bool showEffects = !abilityExplosion; sparks = element.GetAttributeBool("sparks", showEffects); shockwave = element.GetAttributeBool("shockwave", showEffects); flames = element.GetAttributeBool("flames", showEffects); @@ -191,12 +196,12 @@ namespace Barotrauma } } - if (MathUtils.NearlyEqual(force, 0.0f) && MathUtils.NearlyEqual(Attack.Stun, 0.0f) && MathUtils.NearlyEqual(Attack.GetTotalDamage(false), 0.0f)) + 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) { @@ -250,7 +255,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; } @@ -265,6 +270,8 @@ namespace Barotrauma { continue; } + if (c == attacker && !applyToSelf) { continue; } + if (onlyInside && c.Submarine == null) { continue; } else if (onlyOutside && c.Submarine != null) { continue; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs index afb5f6086..e32aa7ed7 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs @@ -3641,9 +3641,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()); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs index 80ba583ee..f099563c6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs @@ -203,8 +203,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) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs index faa57d7b1..d9ae32ee1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs @@ -223,6 +223,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 +267,9 @@ namespace Barotrauma public readonly List Explosions; private readonly List spawnItems; + private readonly bool spawnItemRandomly; private readonly List spawnCharacters; + private readonly List aiTriggers; private readonly List triggeredEvents; @@ -294,7 +301,10 @@ 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 List Afflictions { @@ -307,7 +317,7 @@ namespace Barotrauma 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; @@ -354,12 +364,13 @@ namespace Barotrauma { requiredItems = new List(); spawnItems = new List(); + spawnItemRandomly = element.GetAttributeBool("spawnitemrandomly", false); spawnCharacters = 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)>(); @@ -369,6 +380,8 @@ namespace Barotrauma 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); @@ -436,11 +449,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": @@ -551,6 +565,13 @@ namespace Barotrauma } requiredItems.Add(newRequiredItem); break; + case "requiredaffliction": + + requiredAfflictions ??= new HashSet<(string, float)>(); + requiredAfflictions.Add(( + subElement.GetAttributeString("identifier", string.Empty), + subElement.GetAttributeFloat("minstrength", 0.0f))); + break; case "conditional": foreach (XAttribute attribute in subElement.Attributes()) { @@ -593,7 +614,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"))); } @@ -604,9 +625,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 { @@ -672,6 +691,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; } @@ -1118,13 +1148,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); character.LastDamageSource = entity; foreach (Limb limb in character.AnimController.Limbs) { @@ -1133,6 +1160,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; } } @@ -1141,14 +1169,15 @@ namespace Barotrauma { if (limb.IsSevered) { continue; } if (limb.character.Removed || limb.Removed) { continue; } + newAffliction = GetMultipliedAffliction(affliction, entity, limb.character, deltaTime); 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) @@ -1162,8 +1191,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) @@ -1305,111 +1337,124 @@ namespace Barotrauma }); } } - foreach (ItemSpawnInfo itemSpawnInfo in spawnItems) + if (spawnItemRandomly) { - for (int i = 0; i < itemSpawnInfo.Count; i++) + SpawnItem(spawnItems.GetRandom()); + } + else + { + foreach (ItemSpawnInfo itemSpawnInfo in spawnItems) { - switch (itemSpawnInfo.SpawnPosition) + for (int i = 0; i < itemSpawnInfo.Count; i++) { - 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; + 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)) + { + Entity.Spawner.AddToSpawnQueue(chosenItemSpawnInfo.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(chosenItemSpawnInfo.ItemPrefab)) + { + Entity.Spawner.AddToSpawnQueue(chosenItemSpawnInfo.ItemPrefab, containedInventory, spawnIfInventoryFull: false); + } + break; + } + } + } + break; + } + } } ApplyProjSpecific(deltaTime, entity, targets, hull, position, playSound: true); - Character CharacterFromTarget(ISerializableEntity target) + static Character CharacterFromTarget(ISerializableEntity target) { Character targetCharacter = target as Character; if (targetCharacter == null) @@ -1494,22 +1539,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); + 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); + 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; @@ -1524,8 +1571,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) @@ -1554,6 +1604,48 @@ 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; + } + + private Affliction GetMultipliedAffliction(Affliction affliction, Entity entity, Character targetCharacter, float deltaTime) + { + float afflictionMultiplier = GetAfflictionMultiplier(entity, targetCharacter, deltaTime); + 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) + { + foreach (Affliction limbAffliction in limb.character.CharacterHealth.GetAllAfflictions()) + { + if (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/TextManager.cs b/Barotrauma/BarotraumaShared/SharedSource/TextManager.cs index c7aa407ea..06710d345 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,42 @@ 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) + { + /* + + + + + + */ + + string extraDescriptionLine = Get(descriptionElement.GetAttributeString("tag", string.Empty)); + 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++) + { + replacementValue += Get(replacementValues[i], returnNull: true) ?? replacementValues[i]; + if (i < replacementValues.Length - 1) + { + replacementValue += ", "; + } + } + 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/Submarines/Humpback.sub b/Barotrauma/BarotraumaShared/Submarines/Humpback.sub index 79b880af6f902f324c992d577e9839c294cf4c78..90a86631d98d15bfee734d435ae17ca89fb03908 100644 GIT binary patch delta 190025 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 z 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%0Apy<^XB~oZw5Eq(ex4wEw43<^cR%c8JPg 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 TPbE1pM*sQ$f$BtnEj0}QYH+H| delta 190236 zcmV(yKw}?p%waKmY`Od@ld*&z|Kee}B@V82*g=RyNyK^lHAm6if9j^H z!4tbNYqtAOvx2XeYq9-F??2sm{^^sU*pFgK(!agC|NVzs|EzWKKfwRDrtAKsV*-8{ zsy_+nRG0r9#($U>9e6^LZR7Gk|4icvE)GSJuYdcR7Jt@#n!2JdhQHkhk4gUg9o#;F zzrcvV_>*Nx?my%5&oHv3IKc?Pqsj)15M1iP1rMOLJO{g z-+$Ya0;5qK$EkjkAyl03`FoE_iQL3Ff4{GBR&~A|a7|ScPXyD^{%upR+VlPQt1|LM#BnV0_`t{3^N zk%aT$2)S6>lLC#=uzvgezb*rxi~oH71~a9^&T!Yfe(pRK_t(xNjO2KK$4gpo zZa$trlEovI^j=X#L@D`49Se&~|IAZWU2l$0KRQ~}Ou5v)^@^48K_I&MXFg&lmQ?Wg z<;{v;II7N632PI~lSc$iOnvkiS+4k0c-6yY?g4I`AHn$~N`C$AL$&jmr70TOy=E!j zatX{=gK#|d(r?lmGY=f6tdrbfki7n`o3jY`H(v{DbA0IuOkGESL6hJF83FH;`UEC_ z``M2)at=X#ttY9pb}BoH2H)H}-OcMl0du?y98FTtu8-(v-}H3^{fy2+wkNImO)sBN zeBREl&+?}BvGOdwwUhgc6R-Lt*oGP=;rnSLkHl!@LpO-C`_b&hUd4LL^UxtITn~Lg zQ5@Bmc*hhct!gxi0{>ou>Bx{)<-SRO7?7e(2HYNW+)Ua+xWVU#UVe`S)|qs)-wP_X zu~T3d53{}wXV+^on_QrKFQ#Z}1lYN51l#un&)g4}^ITrPU7P7%Rp(Q~M{Mmc=uURp zzmTsNi>F)X9ltn|(^Tcz;rzvF3ml;d6%zKhNYYSM$Lcuakt-LHp&-cgHG)@<#;(i&T&D>^q8`ib0hvR z_6!zg;XTFI)xSYM{F)8ZL*8MBD2IQT%GXs|B#P5ZA3_Dlvgtn=l(+kLSOjHgF`4=W z304tu_0uvk-pp6vEdj0I5*OtonkjtJI0#dmWB1c=a_sY%#MmRv#4WRF9a?`G25N9^ zClEEDu|np~5>n<405d6iS;p_5y1loY#Wii6Htkj5d$WrfvZ%j@Y^zo?LZ1|QUy=&&@*fxp5mJK)E zd$jgZZDwJ)buHN)K?y9+qIO5W z(cBi+aQ?Y+x^^pv2p0{L`8qwF-Ga!V+=*z zVl;J}CP7V;mk7Rfe|?wPx^0l)4e;6C+t$s_%3#K{kA>Vyxz&{d&i3hf=G$Nwhfb7$1HUf=8-E8MQ@IRtc+{xe@ zCjqW3^0p+bMtoF=IaGf%`MRrnpkCN{Ir&aHWMj5&7!pvtP7< zHBj|wAAWb^afRAj)KjP5Wjd4(yQp>vL5;Ne-i~nN;k#BX2KQWnQaHkGFX9D*lRpxP z_OTcL)MF%m87!Wjx_)t}m9yX6#ou`_eWQ@Xv#hqqO-9D9tpI-_A>ZsUjKimxkGDZB z)>L)8G@9_1DBZ<2ppXrpQ#kG1D7R9_vdetX)#=F?9$EW$8-e$EB@L!Ag>+JFNF!_+ zX#~rH`j|y-`&BX{$LUMtEmfB;KQi>}s>_}zqV&aS?UyE!Gcr8nsw^4$zK-P@*Y9v$ zcoD5)5S8j>LW+M4i#s?Uwa9v(REX446!ISnVne{|Abugq`t-)9 zd6mCbwjCnpk8gE&h3S7K`EF2F9|E_lmD|ciCt5J7BaDCM6wKRL0Y5BUL;!CTnR`;X z{WYbapNa~~TmVqlQm?%0#Zo#iURT;JX}Av8NOe}POWX7cCqKE9=7HB?fyho50`~DC zWnD`pN;Fwg*#6KEmt~u!u;sEX(;H!g$jbXA$ICKlZmn6&!B4_goNf6S&VZ{K!B|Q|||a(2zysI1~Nqi&n6t+)on4 zakW9x0*Tcl#x~Z>R88R(kVq;kp%cDjwXQvx%8D#KjS!w44k-j)i8DAb@b? z!s!9QkX0_)*FUxI<9v)9T*dJ?T;Uosx?d=gxHx|ht{qXOLI)J@ZYhbg5b>Jo*Lirx z-DtP5>w@IfT^_|2nTd9QOeQ~{pDsnvuM1fUg@XOnlm?X5u{+M9&Q zG9tD--(wHW+m@^_Z>~eKHIR8O?dY}xpKnVWhF@>k&VJ)*pqnUprm_Z@jT41Q zdqnhv^FvuMryy52#js!eIBl3KmS#{qCEhJ7^76Af>~tG zPHABe>b+R?Xz6^OBcmjJRrxk|ga5i{=~4EfWVOQGaS%v~JnsZJ*F;8~S>ZK`qyxX9 zL!%W>{0z_{=YyZZCB_PNC6aY)@yCC2e$sx&bR;TgSXs0W*vdaCNLt!8z3A@2*aGpj$tD z7`6+gQJg3bu!oUB+`q3tOxg^Zq#R?);+QB`<}o$zgU+DFoUf(uP3gCE#}Ome)5+>J zo&sD#r&soYOBPZxJ1pGvS#bDTY&%^MR-1B2{FSgrx%)bGMn(<>9e3GXG8Q zJz7a;pF`(uf_i*g_KQk=@NG*cFmO_semj0CmdNF&b!w2+hVm)3tEAJJN-8x8e-Y61nx z=MX>`T_@q7&TdOgErkiNdiIKAq3UlPOv>9DFV&Kl5J2aue%C%OEXeBsM95)YIrk}i z+~;tSzz1td7T-wZq?Bm9lSr}6(%Fkl3cGDQy)~68Et%p;sQkQ{TICb!SAfcpB-gg| z(p2PuEQRcz?&5!%)bsF6v>clZCyahp3=_TZlHMFY&l1#NIBnKJe)5au)axzU0vxPc z@y=S|YwhW;sJH?^BRnMZWj2d;>?AoT9yMv!0kv~`2V4{28V8NB94A%-5x-`{C!Dn_ zDpsb1`7409B*M#n%yaOb`n_bQUTb6XeWZ`&80Jq+XLEn4^ttG=Y+AprU|v!3`r;ON z^{B?$Nvm7zcv>tUigVhMmUmmpTWUa=fSG-gGW-?8avN9Ds`H0_$2}PCnOtew1&h}- z13o#Effw`@GsULJ)F9u~v^}5Tv41a1SmrLT_SlA-bUMi$);y)KrL)^g7?~L+fc|Pm zG}I%+UuJ&;TOlYD0|Z$cT)nfh>*M zdcE4d?HEWExILXQV<}+r$6403>bC!=X(pK4K zOziuY2K_Ew4Lj+JRW+v6yhCPBM%H`dv3m~0VJ>DWjhQ3Z^R+LtwN?q#l5s@?*Gq=x z1|z>qQF^bB>$S@M?DRK%6h^p+>jNAKgTF5}G4H+H2a07{fik^E6T|#`u8kFmoaZm~`XNpA zyW)t2Ie;y{0|L*Tr}i#Unsxdvyw#|?)Qy%H`-+AJ9ghe3i%**wY0GD?1f~kfFkgS4 z9hXQq_Vey3QCt_mlVn9!d=L&!frYd+71G56Eh+2p)pypZzBTKO^(mWqnPc@WoI}%)RxVeav)Bq%joDL%?n6EzWQmx zLAMtxcdv)p0RIX)ARVj;DBfx(r;6wx0JlCqns(7t>nVUDPCby=_saKn<;P6%Tz9S! z3D2PBx}ewla$ja4;QWg2FZ{j1^yU+gWBs6Zj6Dv_4rM?4eo`I9dd?gHRolOGl& z0f)0h7SI6#h#r#`7+nE$lXMuH0br9587v21Q3!?`;C7Qk8Cn7`rjwr;HwQDNa%`dp zJCofR9RV+s`x!Cvxyp30Rcjj;2U8A;5d^*9P6fk;}qkptyngT=gn}C z()Pp%09s%nfh0$51Ly+M5XMe_dOh5E{@V>fMW|MoT?%i%s_n<&nqjB9?R+}VhSC?U+5c~1KD6d#r2`TZ0BC3W6|FkE)BJLH*CJJD-Nvrx-)KjaGCDxu4lYZ z6_c2cvUUe*e~aa}iI*P@W4ZZIJg=}jsD(ban6+amT(+F&!CJ-(uXuNb|0TBVn zj@!mcQN_BM>s-hHkX@Kus=>H_J)88eH;~&@AT$sf7LDgtb2^hKJk3?qjla#{an^Oe zvG1!c01?>pR}BKLjlX`O5Mw!@e;#L@f`06l0m4p?Dyr+%W{eh8 zeCY)|Owr{r8>ZrX`MfX_gG-QCQZ6W*Nc4Ay`*48}M_yM0|qzV+Y z>3_9ie{g2{llah5WWz}}PEvX$KN4tx#2m2VvX@YO;mX5y-)~6G%QpkBvqIDTzCg?) zVr!{^^9?zyn-DXMMKL@1Dq!tP=kJHzAd1oR&+CR$oA?Cx17WH1l@=|7L;%mc3{dd< z2#a<&dh(kcw}{-eaCBkwywjWVAb6l|D8FFWf7g4>e6kIL!s3p|yxe-yq>qz1qVMfO zcx&jSM1S;RLFs*&&Q0--8~!04w5Ny&{G}l)ds9cs8t1^6j{4GotZ&=)>A*Eu2A5vi z^fNG}0G35Xqz5F94CCAkh$o`|XjiXdkcQudBAc4uN-qOrj>QDPI6lxZ7|IPDonGu0 ze-@bBz;(iwbkjtgoI&Rj(7@w7mE_IDZFv5O-YYg{W*!snZUk>b<#)}q=?rOSm@;i* zeA|%ZF`w393LGr*wB1&we-Fv|5MpkNX(uz6B<0-K0m*ghMWX%pZ<|(9uiu+%M-k9y zZ;-Y5_30uI8EiLfG)+rM90Bm#Cy7p0fAPKyobG7{e48vy9|7>!=@i%%1?Ozz-}iS4 zl5ID;OzC#1U2(NlC%OGb$9YiPU^6S%$e^_Q$hOXPbr!8ksX{acf;H_PekJWnYN84N z>5UTDM#Je;UH+LvnLtl-_O!~x+3uii;RY2*fdF5h1=BP(Du{9bplz}SYh@Gqe|@l` zGo<{jgK^4>Wjea{-F|x)>w>Nzg9lUeed|jO4Dz9*TWG-hf>eLkEPqph+J7DSbH`%Y z{BOh*)VKCW%ktahb)vfyx3oM03lIj!#oDO6i-o1htPv2gUViSx>4xmZ6@vn)enGwf zUL-ZWOU2b|{B<0HuRFTl|yg4;>*;m8nSd6)omkm55Hqvx->fH5!^3de+gl}AgH1D z;TE`TGh?NoQS!50xg*<=nt$>lBT-=O$N6yqmf|v+MDZZ=@W>jamL)%X*3i1kxRNq) z1|p}cTVlB0vy zA37}Yy$%{%yb>IE=+$d}e;xBcq0fXwzBVZ3Nx;p|EAvKsd_FeVcd{>=M!)Y8+mxy{ zWes|}kQ~5YI>q!)HX|XK4>DPjWe5NEXD|}9gRncab^{gJM2AKc8Rj8)4_`O|61U=$ zBD0~sbu9icia+0He^omn%8(qQwEQ}o zv{E7jMVTEd$N~dJjh;7!AR3z{-ori)Klua}#xAJ6hW-`gfs*Tb?q#Quzr|5t<|DtlL25~Gf-Lcu(f_j5 zP#OZv`^``8tW-}0f5H!BXM$!AHBokuk}l?`US5ktrSGf~MyEgNmPBt*^6Vj~SB&43 zULcZ(^0YaW=2$hBjyUWoLY-{ZMCS(5&GWbqH&1bA!9Y<@)7vc{Y0X=-)izthO)5DN zFGk)c`X*y@-)PGz0_&((rm#O$RAQg5MlBqjX_kl|v-j)ye|%v@G=++WeQHwzRC8d! zIpqTltArM%10fnN+CY#>bzipHIO5H#)_6buDn)A8V(sMmE@G9ROrj0d#nhe=^^a_g zNL4TC4rq!%(pHN~vNkvsD|TOE4q%Qsz5K z`w6h!Tp`HIe||X(y?6cx=JdkfsD`VK6o~!iJC%{9-`+|TQrO!Uf7wqB7Yn*T1^{?j zRpDJrHjXVX%HG4bCMt4iKPai5)Arb_V;_fkkNz}}M~gq7xA$wy%&5PNled;__F14k z`JAk)TSK16-tYZ<(XX(z&;(MY??oQ?3hMLpq)Y~Kf2m!vn_K9o&)cA2>+JiOHZ>TpR{$a^PXg_i z@2PX#1@v&SKGPQqCNeBR|Kslf4J61Hp*_nymXk<(gUf8kEm(-f&?9F!$2uPc=g~fX0 z$aNL{LW;ur=)-OlpKCwJtnUYZpXscjM3cpDYxI7v@)=-Qn?#khZ`>@doRZ|fxusKC zWa-l?o8*VziS`BT4H7eVC#~RA0M@x-?|axUe=7|(QyH2?^DxQ?Y5RALv&bsQ7{p{ zf5K+eMPKox(d0RTOlm>Mx|O)!7)sO!t_FhJH0A3V%K9a5-P|J#5Qt-ni{!K0l_@g< zXCPy4&3^D0v%z5e6_$f*balwj&uBmcAvz${ki3pJUfe!WV}xp)x?w>c-o`B;if8Hf za#n>*G?e@}|rmQ_j&4KonsvK-q>db_+rT_hudsA!mj zPTrS{t6q!g_+aoouK zXp@iw^*F)biMhGw=)QtGmr5GID3&b!HYBl`6(JCXnjDB8R7>LfaxIIWjurP z?oEZr+VAWSn?5`$PZD<%X|eR&f0_QuH}X^Qiq2mFj9hcYHe{8Ku?t8ji zx@OI-{gNbp+W|a-^GF&Xs|c5*1KU9Y(XgS~o0EHu0#U1ITE4ReeV4oWS;F@@tH8^r z4R9$z1EFP6bDoggqPg^b=~bl}lT$@?c80d$H3U1QD&sekqH(bq#z zd26Esp`IzEbY3J-9@SI5`8~UtuOsQXv za(~=Fo=tfVh~*O?e=+^pM8#~bcNyTfGDR&O3T@VtkW*TqaJdXwmruVWRyfE|1mV*r zj{|dSC*&cJl|g)zt_q=r5CFy&u$S0g3kFQFOCE@lKclDDMSgfUR9u$SesmgbAC?w8 z`i2q)7IoY1OI-;Jf;&w!hPDKY4~XZe$;_huXEZNzeUGeOe{H)jc&|P%lFg5~+vs{V zwW?8F6G0w*%JA$@g48eaCz65ZDG2HdwJ`GeXFSOXc)TEaM)ds7bLfz;)7m5F_xr3Y zvyl7v)6&QB{Ad%HcT)LZ%5CnSDSt?CP=QjlVlhX4bQyUmWn8Kp^V6c2e8E!U-}yRW z_hazX%&Uvae_9WukE~_GgKHlMPObnE#Y?csvGF5LfM3jS1D4QT>QAV z969DH{RUn{;W-XSWAY)eT7GY*8^<8!)>C5>+fPIps5!Is1pv{be(aQpS#rs^b;LPy zc06=tFNg2Uw{CGyyvhgru13G>!}5a6U^EiR{01Nte`q;@g9ACq^WV@fin(0wMayYq zkK>ik{B~7N`kK@afvva6Yg(v{#2?_isU!fjc;vz61{p2SbD0a z4&rYtR}0b^`n=(12FK0Ho1B1L6@IV?`i_*6lYCTE{9T!bCs6$NE79Cv4Lqe=112jQ{?F1MSotfww`#_&OiXY=Oz` zfA7hr=amTePIj`&S;NC0&bO`zbB;xx$#f8YCAo7^htA?3D0g14Cfj%- zZWIhmOfz;ne3ke&a3gK7KQi~{dwn>6G_hE)W|Mo67)m{`7l9sG;!6>DbCMj(HYwj4 zf10WK@|G?~MbkR6TPhKSHb3a`Vad@0fjhw1&(jy2qjZx`@#vpj`b-pDHI)?aj*}*W zDu7V2gG=ucOl|)Ll(6VDl!GSi2@;vnWwF$lEC{~B`2O{nLr1RWc8|Suqe-rq4I3NR!6gG_S56i=62xMBDU?E$9{$^1M z*-uK+x6`Xdr+`&;5A z)JK`%{Wv>bRL$dzAs=ti1-M8%w&x_nz#)76+=&41T<5F)xS=jWaQYe!P=TL-e->0s zV5-3M!A{XYJc;1eR=UF8iFd;G)1QxvXCC02;4 z#!cO4@WPx;b<0`{gW*u+e!STNB6wALXOGWp5eNsC-rGk95tO_G>@TG8wCfYt$HRU> z0S={KSk?8|a&>}9F{rLa-FRT~f6SaukLgTc$r`jJpErHEWk|Npff1nhteL%Ao*={% zn^JF~g!avnVDqEWQuB_QOISj%N|=@LhT5~7aDP318GnfGgbKVY*l&u78F0|nsPvwu z?dLa4mYkV)KB#mOitl5hW4uz`9NV#eDf)Lc)b9j&J=NqREZydPNGB#NNuVtDe`tdfc%#o%WWKr}Yy=wIn)QYD_gz-oh_@E=mKcN(H)a$S zA$x7N=ZH)wuH2xCuc^|4T|fZJ-##C|vY1tJ9n~+SH%J+RlpN4fGZT0~PgWS17Z*_o z7C8OYaf?0^5Ei!xe|4wF``1aZBEm7l0An37<^}po>K}(U2v2Y6ewv`O0U>y3n#IhUmO7tzM?%ez1dK>!_4Pr+Etl`T3UzD1UI3&ePidug zfLgGmihFuxUJ3FdCJnNc;ZE=(Mr5;?e&bl7zf0WqLmJouzP-lwMYR2B|5cidI5yp10c>qu-ON4330K2mIflosiTCCa2x8*5(LByRx{On zZoM2mLmIJd>_>hAn76zQL<*n4>P+Z|zh!=pk!}#3i>y<`aO#A9n7}vA*A!^#C`>a5 zVY(nQLk#8j#oe;>ZKeecyLmDm?jFatWIe8Af1GgG8Ly%De!xd~**fC~zz+pxe(Yb2 z%Yixr%6*LdcF#2YA8PE5T4=@3@Rn1;aT^3#K-eG%tyB2Ipp@i}i*m_#bh-_sOol=g z2s=~qaPEWuMbdd}If?{P^n+O7-4Z8^u)>NR-W%cJ>nE$H*LAui$jpd%_d-p2)uC@1 ze^_U#u}eo!%)e{Ak)CMFwu}i*mP_}FWtpNVR_KTvGLjo7t)6w^1m^W?yCr9jb zDpOxQ4DiM#CYNKDi)R_Jq+o-wcRyQ#@y=1IR3aob?+&6D(9QoUM>oObGJt)JFM)C_ z!gq4~#n2~?Y>W2*x*iX2rFLIDlwG;Jf6b(z$w0={1&$)F^gu{Z*9D6QAWknY%&=^10*c{9xAk=|K;dufX}Q_R#!zcd+)s(-H>(7Y#^367ei zGoCh7f4RN#8+?4p>dBDec6eWO=icTQa{-ubn>&=^i2YK#`r#6#5g21;%cR>kfAIu~ zEHW~MEPjrz0Qq-%Ek(wk9^`2m;<^pf(_0T1bCeuFi&R=78r@ez!h&uDa>3pQtn~Gk zEVS*^$Tsl-eZG{Xmm4?LwuZ!9@%jEwGQ7Qv3QZE&Qs`4NPPiJDDEN9PUqq~pDL``z&`)zem}aM{sPQ$ef4oS--rX%9 zy|P5W%Yfpdi{Kk_-YPm?hezKQ&8heO?KM`0e*uB8a0COSTWG-Dtm54}g|~jgyaTEP zgi492w9SI!=)`xQk1zX9DAcQ4C_1@C3Hrt7TL#y0Ayx7l3?gWrYvG;tct`Lq;5*px zF?38~0FcejpR)?%oC}&3e^lxzlc3j4Zokng<2^Ck96R-UOx|_HC3El3eXb{%DvXui ze8LC;5f1~Q<3{ud3G}FvnLz(-kLC{tO#wUesQJ-2mHc3T<9AlQ`v*t{{ zWWaR|n5AgsEd&=&oiM(lNNuc-?A)ofZZ9F}oY_sbU}`g{fn7k@LsL8gMPt4fdsjNo z_0$+P8l;3DgLm+L?;V&O&lw04$l0zV^dvQTa;6{CNY5gA`Dw@;U*~f9Fs@@#BAH#y&Ka$!$TiKh21qlb-$5#^G7<6T6Iwg5Ps9v0R)KYrr94Jyz)BJ z-XV6LBjQG37*9=8(zb7qsWH!HRCokV94jDK?PN@$s88v_ol#!0;+73M3(5 zAfuV(kMvCu#< zhIm>^f4qhhR8JtF<=atb27N!v!2vmp z_(}h?Z0Oy}%1#YJ!=a}W7dHW->U1=!rs^_ie|sjk1>~sM4_b=Tu-{(L4@pQJ_npyp z<9aSl$(kv#)&gk!S&ynaSJG2dbm3(oS= ze54G>XzB0OcKUh-IVSekI-1<;I3*Xe;Po%jMkY}{lty$7Q`H!-?N#6>Tgve`J zfAw|h{e+JxED`9)HzX3;bpCyA2!=Qet|^*buHs9xI4g+633PWxF2PY1J*iPSVSae zgAku6agXvG=8;ZUlzA4$XK8jZ0=Tf>4U_U}xk{Yw@7Ap;6pnt!Sl#WBSiy!3e`GTT z;1klnFxEH@c#imJjtfzI!l&hvAK9$O!|d?)s`jfWwgLZ=hpDq0 zp3(s}WDv%{`7vtiE*Erav0v;{maOEi82*G=zDCnf8f_88cJ$zd0EjSflKpVzU}oucIZ2f<;TIvMkbH) zMZH~2<$p=F{+LZUfE>+|Yx&t6%VR&HjHpYW3BZdCj=HDiZ?P>4qAZ$B0xS_CnhM6&l=XK*UEi^7tS0e}Zw~KtbDn z^{Z4Y!FnbG{ObJgy9z3!-HH4yI2ahD%0SMRWFNNdO+uo4=Oo30@nNx)y!O&8FxFRr z{?Z6rAEl0_5KQk@g^xh;o$C5?XI4O(Cx58FZWj>2WbMAsG1OQ2R0kpD$~9KWkhs z;1Y)Dxr=>bA0lAVWgA*OKxo@Y-j1M0eVW%`r zl;E=|ik{mgdxeKh{bGvS`lumhx^J(d7d-PfZ*(4+X+PdS3m#30e;e22UVd5NAV%95 zK|5$2VBT3ognS zu2CQhT*(X$!_h2ce!@71x<%PwKv5s*4OLGfvw`=g4N0HUf54|SUUm+l(J-H0Q{PLe z@}?K_AF8Nv4`Xb;Vyxd_KPBK}5lI_#Qv&X3VHjtuv=;5_qSeT~y6QXe;t<4*$^{+_KDXfd(w#V1^ z8Cne8NmwP(f8dL5WD)k)=QNj0pTC;ywFNHc=-b&-5QX$1GPKo)RWU00?mKkexT5P` zSr5j$NeGseFM6%Yk3`dx#{xXdvZ4JT5C(Aq39d(aK|u2i`jax~;fBirDwhXjlr%eF zu<&CvP5#EO!~XOop*=PZjAAfKy}tStIQL0zF=7n zgZPLs77VAJw6-&Y7V#Rgc3UwIcxI&#mO$rs0NqHW(`}~A4Wf%Yf36?o5x~*gOgLOG z;Cm{C9YaKJhOlfM=@xApbiu*HfItI%E40K@K z?aFgEr79>N>Kkx&im|RT%_XSl=E%?#od4$Ff5z-l0?54ktqb}In2@V@S-bSCOFwA( zdVYx6Z34UB!{p|(@~uiXX>`>AdFPv#j4!bHUb`Dw^ZLFl?Cok(F!Z7LhRW{=a3m)2 z6Ge#f3UeRaR5>LcQDk_&S_w2AwiJ>fUgUZ~i#lYZ(3c0o{($1JcFuLEP%DWHK0#1T ze`Rcu6v@Z0WcmZf@td zX?fxZQ#m%~f$?*BLrA|@#Dz@V<>IkluCdEV`Mz*xgk;TovkTmGP5xty^h*lo5bxiY zt8e~_%#VbEbArDC#hbFK3k4^RO*=;jf1?d;5Q%CSTcTgHTfuc)Lb^ z)D>LuG$7W5YY8-#C`a$tgVP&bwkcSVH8>5Rbpul8bkG=5$V6N|%=rG84}2)7qS;s~~5)``v=HjSVUN^MrpT|wd#cPN?MY&g_{fsl5 zY7T`MiG*m@u~i|TnP_p+uAE$@u{avgM)_mAbPMVE&M6Q0VD4fJEC^>aZE=2X+FqA= zfa$H4=ICWbm8(QL@XW}QBFb~`e;XZHC?}kv<6r2Wi(cCL%og1++H; zW~B1F8YvA9iz(rZrlmN84*Pd+lsdrW9bhm4jvb(N7$Z2Q+Wfr&eDc1s9RDD%cZFg= zCEO55TJ(vhf9STj?RZnuiw&hWxH)WGw$kT;czCh!_gA{EYo61+0`3Pee^2QUCHS%K zCLt+uFeFWu9Vy=n-U%g#AC~}5rILcCafJJuf(4Q$8K6l*?{1vnLI%kV!mw2wZUbs1 z5f+I|kbOp@vDn82T+;3bpXqv`d~T>M!YF;?rnHA-31G^r{D76eCD*tfoynP&U;cWJ z*N}YyGSKzX_U{UuXIf2Pe_W$uLZ2$NIwo$EBz53418@{@-5trFXJ@Kz7mRS(@PLbv(;s(b^oaPE3NW8Ip-lWokJG@ z@V8(T&|{vYgMfKI#o`#|Zh@r07``KI-5u-iJW7CSltU(bim1fIz273<%C=am1Amf8_jRAt3BlCFgdql8xN5 zCa|6rnzIK6>`u|@1AL+6k#0MI=J{$-OA(1ip4Tm5UYz+$VBs8lr=asE2uHmt+zKSC zb}W<@HRRAc_wY*Kr$kF2cMD=S8=paG720tRmiS ze_{_c-<@8Te>Nx60E*)*TGkkQYeRcKDvWnLT`b(+)9N+e19>H@;);U>_5C^1%A?>{ zkI~>t6SqO+8B8)SUUno*=O8pgqjMR@21;WElsQwODIs^U=n}t<+F#0<}luRmd&m6JCXLbU=f;tvcI87-hXb(8pz!4*NMZ|=7%l0s! z(YSK|-dbu0)W{E2^hD~UC43fOb!t=KN#41_+u$VF@ ztWp1-7vz4$-+OKeImPh^Vx9i{v9~CiWbkt7f1Rn2>=8aUo}?(|u&djV?RKTxy6V3s z7<{e*K*$6&pNqXBXI9IsWvISn?*M7PdI%wyn9Y{dLDL^(Vy!^9U#L$(2z;!qdgTH(@q54 zf1|&IUG>2`?mqqZ(xQET_}pr}kpw2m0cJ#cq7kymZ%P;^G<;4?Sh`iMg{$-3*KCc* z3+#WOHm8HF&{iypzkhG(Nc-5tK>8CL#k`y!17rq^ z@}qcNf>xiZ5IZb-I*(Q$7VplKXB1{ezb|t}l|XK#1bfrS`%Vs> zF!oe9h)I?R!o4^Ix$#RKrezs;5Fjz}ITd$ihABEl^1U8kAd@u}aG_ryM-I=6e-zwf z0Rw5C{aET=xd1FtK;N0vpKERpRi8?su(jWq>`9Z%)@w;EUM58m><~!~L`K_kV@xBH zPY1(mgvXh*s)FEIHr|(gG^=|9=D?iGX4C!T1|61z`%VJ@F+k40&jMoFj_Y4wmA-J= zE1Mf)KiZyr48Y-ZrmRC%YvOOH8-3$IIe&_DkpcQWw7#o0hX+O`Y)%RQcz4mi_Y4gE z>;8rzN z9H(EiABdyLvOr00EC|jboCq*Q1l=D>x9pZc0)7?T8>+#SG=ZMR!qR&TZqtLx_ z`_a<$fx`<41jDP#6X^K;VRHLNaBrt z=$AMg>3&|aRDJ*>_xw*JT^vgOLUjU(P*JNdMI68UNf!Y9vdryDummP1<$otsCs}$z zNC`HK5g2~h02Z66i6h)@5jYs;ej+fnsCRMabgu@d|S{NFcrq%?;G@p)DQ13pf5Tii}mSNmL~Nt zndU%04tmRk{LI1N?XjYIaa4Rm2BfRWVg`OFHJX-jA1nSwgYY}KrpRsC^OJL(=&kL) zfQxZ{@9=Sh`aHYBiMdc6<62{=ILM zv8yjS=J*%Ff8L6A&OIgBZ-FARQIJ#RhQy*&NAr$NcN~NFg6OvPU+PUHXWoqJ3Qn@+ zpxX-~-f7fu*psJ4WPcCqO`LA&A>+Qn2%<`C!(L}Dsh#P<134Ws585^FDypea%BS*H zX9!=69@NHlWtyJ%-!CTv0~Z|SaP$?P)Scb-v%$AxhLjv>Kx>1;=)KQy`br4{5FTI= z>nY1i?7IfHxLW0;?v!8q4Lc7gpPNr=%iRBN5PS?Wa0MVW?0;jx2og~n7C+7JUgh6! zL=X_9tbxzKz3vv1e^t(_K*NQXlkU@p(4#mr^8zkw*udFhWi&3Byp=f;v?k+AYTdgz z=SQt>Q2(6=!rn5WrpM_!+2hsRnqfLpUEfrU3u;YBym+hio;8NQV?D9Y_}Vf?#^7-AY54y88$ky5 zdPkQhAk42pAD4CX0M#mL{ibx=$VSTIw*hR`K8t_F(8c->2onaG7zE9$V-1cbN~3fjDlhSjWc!zh zHh7&+cw=1Q&f*7T zajMbrz5_}Bh>7Exe0EKNv1mWR|%{gVP&j(=D~U0ef^179&%awaF2`k+mUe}M#0 z*g*Pl@)Ua@8UG|%otE$cGb~YR$6-4S=XICp|7a*l(%@^dPYl7_k--@che}G8-NRc8Swy@&V&(&z{18fB;Q6}F~b#n2Y$ z_=_{LU6ft-Zwpu_%Sj2zQwD_E!oL`?%$i#Ct_hBu=~j~dlRsq!Pl#R zU>;$LqHMDw1W8~{FZg<}I{|5uHb@b|mOiIt%{@(D%&;pFeb{AirzGE9ufHh=#b;#}J}%xiLB?0obfMH0gdMrh1SbpPJH(@JvP7gcI7Xd|Zkn2oyIHp~hMfumTJ9_NM{tNhJgJKy-C^{)?2% z*m4Fo_K`o&zsIP47y|X0ZO(A)2#yXdDwsilPa(I8T6^47ssr&okiiN9{Xon`$bUgX z@v@Z(Zso{0#naT1$He0nrOA&51Z7S)WYA-7bT244Ew={QQexOw@*Fap+kmOIdyVgU z5a%Ug$ZEbSZr*XMnn zM*-rLhps*I33L=e<$?wBZDutyL;`Iq=p1E2g z8XtEDBs~o%(TK5}_DGKf|CW;|6S>3yjxi|u{yk(Xjx1+Ge#>?>L6*@CPJfW4gCn^r zU_wCbQ~P>~Fwx4#ix<=>Nsmbk8KAnVFJ#h-=k+y}xdS&}h=XzH zdHGaBU`x6g+oUohyyvu-p9q-(vls7yI zj@!V14Ah0=26oJ+vItY;)PD?Q6OEO5yCtG9qOY}~5MNZ{`RbLKm}ye)v3zi0?SZ+c zb>iZ7eBt`p_(D0N;6e9Nw=4@fs6nnJV7^G%LB{#j)wNuBIO|eh>#JKe_QFSbHXt@X zd?kct(wtP~pkB%AONwo`3(drhlOX)|mnWUJz;V*oUVc!B2n!ALUw?b*o9XS;T?jPq z@OE=(YoC#D#%Oe)VoR|%=p$tDmS~(_g4+2tWHRi8)G+54U+i3hJ717;%Q^ULcBscN z5fI_v_c1i$L%#LV(vfD<+*KE*6I%;UApIhrO{>e9g{EH_!w@u0yFlel%%~-hTxyQX zODF0n67XLQ4J+5BqknSndEq!r#;HPQ4J?!+OmtCgE-P@&(Y-n0pLjZ+eVq0;hwLdj zCeWFa_lB}crK@NsO;5){TI{jAhRY!9L^TlF&B&FSpPmNDpo|g`XWm`R!ZJHPBbCxP zt^{)_Xr~AutSXeCJt#0MWD6$Hmo@dz_WDsBn@Kt!ep;Y7gnzdFPMJgHJ0s~nfDm?; z-GBs-k#bg06W0=3gS4{NQ=;tn&n*uNz;NERy3`%}+OaqOUqA7CbjySB{}% zHUvZy8J_a8;{o;EUiiZGRi;)W1i{g9ZdS>lz-32|-6nwalWZ3WaMPtqqKNTGCY^xy zr$AuM#>nQnCx0#1mnC<09`1hWATnt$9r%8ge)gILjR5qWf`Y9iDPPhTL?s_wl%oNg zBegK=R{_{ter?Lf_a12d!qI1WmUaPno5s*6VF4(tK4s3*0q*|<#L{uXDrtW7#X$jf z84f0W6!%63?&mga&zB}wQb%Lk<$nW{sV!hGQ$Wt0Kz}mIpp$LCco2v@1;ZBl!?t?_ zuW(jWG8UlhL8weGo(Q-pblTFz$=~ARUEsx zPIu_<>BChTud$2Q9^bsXNp-#RlzQ3`cjqK`fWrot1i0@k^>gQVPZzw*Vfxr}(9g^k znbt-kReu)=U47od={_J|_0`c|c9Q&2Ou^YtE=(rL-KaGa>&5BIx4_4!HhHR>4yfdV z!XX1Vw@Ys-qa3sGBLi(i`rwdz+K+ybdfb0%_WRa$TV`Hq+dM`{d`ooRJC_%IQ+x1qr{(9A zYCj^KG@;#wK$D;Yc$z0iq`vi_!Nq_fflvh%$MuWG1rbp4#$_Nk99$ZWDI@n38@Ck_ zjZ?UwB*ec#R;J`dR^G{O9XS#yD}UrB4yt|nD=!vq<}Ba7clzex0|Ev?%U<0sB^u#v+>fh~xyXCcO8MQ*7bNnt3N@*@b zP_MZ3=$j!*PstMia%tqr91BZ~pOl#>ikUx(ZeHw?m3B|zil`N3HFt-7MSir*3I@); zq0I;KM-6O@45?1pON10|E@ya5-rUr2RuzU(a1(u~@jIJ=-FDh6di}kYNm;Jt#{mv6Ir(^g%DSuzn z$2Um!4v^~Mv3Fp=`+fBY4@|}%<-cW6T62Kt=lQ&E&&WGFQx|rxhfNz-+#r>K>juMAV5G!5FInH>0m)jJ;E?E@cR}Wt7y5=?4KXV z?29az&@%hR*@a|of}+|fcc7hjz=ZofNT*$)R(Kw0bC2^$a=3}vtT_GR*vaPYu3w2< z)#mAPC&7PsB0yI0u%-ripVt5&Nm%bcRb?np359c29e;YI2I#s3%YUsEy^w$%7=0k~ zBueW}q{oV-_2SlxRlgb4>)S6jhuIb1Nf01bk8(djh|D80s9T!fRRLNm{_DH~MOk>1 zsGe(98d(){I@A3jFfSqrBf>3l*!ct!b(~1G!|E@xRSXB&-XxIUn^d)zYWj7#gQ^~xWC|UxJ(>QQg1)#9!CFg zboKbZ=WRqOIDbR|i45<_%duSAQQe-2uOuc5B{}vYg7knrdv?n^BZclI9Mk zV~FwD<(N3MH|&^}@b_3@4Q4t?TV9GbEqkQZ?<_x}_i?e~v_eJhz?5SM_mnB7ga^Z?YDne9|tZ4NB28)asX?H&29 zMt06y-K0ndeJ5Pijp@fGkk{xt2r|WPdmD@H`K%IuNh6urYtJkB7iM4)i*Hi`eY2^>y$nX4{ziN>AYfqiFAZ->UcQ73g_KVAG^6iylGhxg6 z=Oudb>iMlx{Sf<4ME4#}lkFFAe0RIO8?Rj(${ zxltCxdy_7uvsDTL_+`~iYA;!?FFz05Sa#xXxqs$EJ&mI%2(NV$?H45-3<>1Zlf2!AJ1px0B__)+oSdI7 z4u8@bl>PL>bI6!>U_<&cMi-R;xH+ji?WG+#J`fo-zf!lUdXdo~AT~*kp!vS<=`N$K z6;l#lS8soWHN|<&83{ z2w=|On*z+{L@WG$9qF%~-Q1hJw)9&Ek$>?ujMWHtm?J^iWO9+ekFa+8dOP;|m*Ok~ zLE}qSqSMSikym;rQjHcJG8oWU zsfE4VE-N@Ag;g?RmA@AL@)kldEC_?P<7>U8#%+yv@L4qBXwe~B&Pifdd6xXFihviXa9olyt9hC{twP$e%;u&zJv#eqZ z-&&zqDh>~{R~5oKaE<$c?huXKi}a2S-k@Vkc-7|NrX&nw50<&woeV^Ynl!QAmlmV; zR_D)d266F?obT| z3+BH*S!W|&w9nh~!UkYncrK3zm&FmHP917hYs>1ZvZ#;soheR_5!My6@kB7LrofnL z*74itZ9{5I2#A_`MT2s)pQk8yy+!|MI;y)@a%E@!FD)MrR=%Sl( zNY#7`Y@~@?mEM^%07aa?-K9;Vfqi+es|W!4^flgznG<&kghq*y+N~&K9Z=RAnvo7FGKzcx~SpXXKnu|vR2U_>U8#f2pK7RofVD)+mfK->zJrA-w z5-iKn>L^5j&>|k#5f~nhEI1~%4B>t@s{w1$8_jV?y9Wn^lR+Ol9z{Xeqq@inY|qre z8!R#PE&rl!MO5qP`-XWx6A#!!}KX~$bm;rlG zB_-?>KCP}LD?l@Ue-uz74tv;jXNZG<-O|E_H3Roz1|0ZtF>ZGN|XV`OfA^`FF7 zufDE~CTw?V&6_wzk^R;<$vjkRS%^-C1)|xpBL^4w8?>( z5S`egJdih5jjo^xT@!9F^}FL)vUjyuJpbMT453?kUxDqD9iRkL1;!$x&A>2ur3r*yad&t@xzNnW8Jfs_;PB@ z$8eREJ%0qVt(!P#pqg%3GwZrvtZmy3zTdKeUNlc4*q0np42kAh_hd!ThgF_g1;^5O z8!-3CI-nmuKUb%X(W}>&srA5_dG#_0bYxh(Z;F{ILK(n-8A8M_a7iKUlTzZV9K;(X z=T^MEAn*%hk{|(Sv-)K@v8GEp{?1-NK>(Bydw(@|)@)^2)X?~7!)s${Ai{}r2R`p7 zd3atzr_h*Kc*jr_7dJ64IWOEN0MqOUc(;g<_22BEN0v{FLRjmolK#1e=K8K0h<-Kl zd*pSg8*m|kL+(8}UeX$Y>(99p}nUi9KxTMH#R`-UX%{l4n& z73P+*CwMZh%AUFzIUpL(haj^u}0YE{prbGAataF%8_hjgY+_3`$PuPWPiem z4uaHo#$Fh3WBi=8xFk2s5YAdJ@Qu*{EM{4hNk%q=eu$klLX+>8^5m@N?iTF%0!WKy zp(SkUmW&MKNg$*nCVfD@w!sj4F}nqGKuEG|+lO2bQIb3trLx~eX>3BgoRSRKM);=5 zb#r<8YY5=A+)b))9^V!9x4M5`H-G)A{f#uH^8Si(3<9^&VW?nNBFrfV3q`I;~Q@m_;YUz4_)Wu zAbX$K?g!N5_m?oc+WcJRCJzt#y+tPNT>j2qnXcS`hi8Wj8uR$>z7WisHJ{S-ctN;%|#mh@`ZV+bCR5Z+1Qrt zrVUu&+5Pc^9DkQ8kn6ZXLxI^r?4W}YzAfh!c#Kx}@Pr5zsk7y5bBUf&FB`G!3rWm_ zR%b}nuT)H_RmF1GojV37m4AFx(hFIu^s_BM5_K(fP(PYIU^~9~Wwv&=Af~}&2Uhp8 z^8TjH$^kM(5Ij>D*@2i)4Y$n%7`tjC7hCHm7}!<^#D#*G#g{LzJWR1>~y9hpJxQ;KW;$#lbRgXN-?aWH{yJb#+^ikH$Z+09x%LL_kNm;cHn&@ImdYk`(E zDitEXR?EsF$i_g^9uLV;D0gTn=I47@FNtqn zN%Y!n3`@>-oo}OlJ9wH8v|`>Y4SdZV20?2{9>zB(Gck8yvR!J5?C%>=HZZ=siA4Vh z!C>tG&GJu2%4z#xEaaLY`d%$jTt2YN=CsVt3(eS#*MgAz*?$lh(1-?brZsC3NLEh& zb%pKHlb{NKnnN1o8H&I*+xDuJ(1uBLiUJdS9zBm%o#b9qN1CD zwgNXoE|t{KKl3?G5GYDoDh|uu4U6XY>oo=lO}Khk0vqe-7}mgzj&>1;ndub$g%kzE zOh2mWz-2#;2TZ?ol+QvpTsRRv4uWSne>(YVuja@sK1I;uG^Om5ybhSpusqvU?e*2F3;`>v{mhz){ zdRxT87K-BEGbSX{6npqkIT@cO*InX@wUZpWCe7}L^YTV)^*Oqm{YsUb) zBP%u>9)G>z)6LG3qlUurcR1@YK6KB>w+6L)-JO`#Zk6>8bOG5tJ$zDdu_h4)@+)L#7ia|7-pyq(zuBciM*LO5#htb+0bwTW!IlRZp%#`8 z(_&DLDX-I0)_LfEN$g^v8toD@03imuRObB#PHcg0&FZ#V0OQL_@bk|r6X6E)4$8pV z_$$Jd2Fijf&%WOK1xU~dS?I~P(Wmk+dQ0_|9>R=&sbf2c$A_WiicVg=XfRVc1YUiF z{eQ~Z8B)S+bls{Ry!NH)6=zW5m_JF??9qT^N@pHe>#IXJNFAKN0@`3FbyzsA?d3j zoXC4$)2Ef*wm&!q&WY~|GwN*~RYUPU5Pxc(Ee|qsN&4=X?~@2gM3r5k5X6~V+!R1Qo#r$$)-D^kLw7RpPr z`0q(e3+iHMrA_c40>wxQTxO{9;9WcNDNBE|p|iaHqv$NQTm_;i`aujJNPr>k4u3&r zKyVEXU*FYl{8q<`s(a4fAt7HfWbXIM3J&0q%48N`K{StUDsYe`+})U(_fmjO!zSjb*`t9{XNDoyEwm}0pWyUG2TW{FQng$SkWfGVP+1CeSh*r`y#=( z=4=$!yt~BptBr)o;ng~E8-gkHRWouc(?Dlhh6RWt!!uvRMZq3ZRXXSp|gMkEYQ~kJG9kcdAu@ zCT)4`EbxZ9eM#3EOwCl_o_}LtNZF@XSrE&4vl1zv5xi&D`-^p-q3$pYbLg(QXQqF4 z$RaR9>%kHGhJfovL7Ghh-kWMWh!-Rx%^eSln{yZbJg_glK#eqF4H6^B-iNelQ73Mz!N&}{@Q1&>(EG}WUqsFNGp$#6O1O4fB!a zk|`P`!L8$myBt@&cA;yq+AKMb90K879>CYY@qb>4LVyY;5-t7xH9_`SMns`1?M_(v zN=vYbCDLca31^4h-fnZJe*m++RIgQT&Z)4No`ZMcMiw|yul$XINL07&iE4(e(4W=$ zh^!50k4YxfYJUX+!`1~2J=)iStep?GS>t5UZ;Iq~UuighIBzK70(4O-A>4 zpa?X&s{Qc)_au9_6_@~AW`=%)_MP|Kd#-U3%ps;LdFd`DMn2mSaBg~#t`c;cmFA3 z^2tCYA^ePQ5r3#cI0-QMgYeDu=gPqkq@z%RH1r$RD5)~P<*NRK?PE9BRWn;6JMUXR zMbnpVEq^Y>H_hw93n*imH7S`|%43!!s3^j4z_N%;XW1~|+w(9W7_{XRTD z`EqY6Pr>TUQl~1p3UPi4c%N&a_4*cuMD$K_HA4AgC05}&sH&(<5|Kx$Ey0u?Gc~WW$T{&1t=nKhK zZUttqmEoXS<@@W*QUe6y535E^Tt3J8!JFVDMzPx>o!p2@Q`GTeBupQHrNVfh?-QO8 zsqvF5M9%L9S}8aTY+Oy=k`o@A2r1rkN6*hW!e#iHsm3V1Br1qsWdiQ2FFcb%>ggcg z!+#!eW*;%)_O9+VR(#J|?8k?wA{&-BAZX3aIw;qGFKvuSWk84?k;V^F=fnD=0n)yY zv*kGa^=@$7Zkd$D{U&*{)eUc(7^~C<7Q{zPo8w0m2FeOd!KX!8+HxWL7g(+H`Hpbf zTQe#xOSoEkP=bTnQsFAtsC)cc@~{K-s(%qm!fU36d*jrqlD5#eWZU^-TUm4ul;$sM$XpcN0v!o(lqj31tI>F>I9)BJ$ z@H}WE^LP1p)E1Tn8X_fMv7!Y`tJp}QiTj`)*qGwlSUGdZx}^BH=t?y#k&@U+W4-UM zw0`yZ4o|BKBUxL(KP;Og)V2=+*aTyOyxVj8ab(fpD2Wn|&UU#&)w?I>@b9<(i0{L@ zC#=(c?f04afB%4YSm{EV>F5c<7=NjaaxG*X#aUPku(Ou@w(6~}(&~EnS{mxb`$^Tw`tB*al7z24 zci{Nb6VpN-lm60{>-l_u1I{{~{^DDLA%po91NM%)tKkGYuEq@Eyi3lN3 zFpal;M+{-JkgJV>($IRqC8|`NwuXzoklE7@21_SpQ%JA(6>=GX`W+l!7e4js&Qr}z zbL9tkoc2_xJ6H4xt>e%RHsq3*OON@8y)k?w^*UxtYV#jKqbYt^Q-g?<%ox! z<@uXwDKk61KMqVpZhN6i2Y*{1y4bIpj1Q%F`)oqNTr-{|<bhA4$oI zMO&+QAwiQ^P1uNt9yfItLD*#v4{*Y~}CA9({il z)mUACt4mo>l6b1YH9+u*zjv;Iovhhmj@n-RO06gv{N$wPJU0mj-wM3$wFX7-x+&V? zf>oRBPG3jPu#3dfDR0AQP%F_S#c>ct+bR52kmH6WSg2UAUw;sqz+Vi8v$`&wubJz# z`ZDLNA;(FtFMM$|u~(YZ&kcs(-&PZ_HT#u2#^K&qPBl^MuEj@wFO=k*zgO8ZxYoQ& zJC28N$$9ma?hvrsbk5;mC?25^h~fuiDfEv4K&HKUgYnWS)3~4oBVP_jFXM9xczkKD z3uI30n1?H!b$?)f9@=SU-yQ#SoV~@T5J#ZTu1$lWhEjm0!M{p;cCV@|(ZsvCT343H z#9hA*KcBChN-UmQ_rj_JGi>kaddux~hn7BfIjMBE{@}w^Twr6BEjtWcrxhhlzIHtL z0cH4*$yX>+2n&ozkEp3Q0U667lvzrw!Oy?sKql3_Ie)5QYSpI42z!+YLKHy4UB&1yN{;tEFZ4*AYaO zf}=^!uVC7zKDWeC*U}R1qyOe365sa!oeD05{OO0kuO0pWF-bYeRKKrZsXA+F z%e+_`IkMyEfs_eTJT3g_(4_#koq;F`zo8(}+wzl(w6bsD|LWi^ z0R?K;@E5NrIOqWSR}>p47z}(R<6o1P9heZA3;EWhcs(5LJG#gzDX)eO-c{?9g6e#e zZ)gLgEpw2{*(Vq&i0Pzc^M^{Wbma3)a0LLO>*&a^Q`TPT)=4FupUMF=B^8t9@_@eu zX@7!h9aL1c@>bP7P4C_VV%rKUqYNx`NwlUH*W08EU67iOkY z@fFGzn|KMKU0~F(mjK;Tsb8GvGfQ4y`G2FF7v)N;g}?4W58PAY$ajb8ix_=XIByuQrrcO@(>&)45XUIcV@7iQ$!@lxoh{p?_(7 zWbO9Pa0wcOu6e`#{}RhYeL#V2JR+0do=uE`y@YFPN5{Zfew-*M<0KddHVG7%=c*Aj z&MdeVFF8jmZA##RUJ_*b1oZe17(Lk7y!^^V5D}ahv<`yBcU8Z>taX7m1-ShgyJwA6<`13x?LO*w@AgG`5q6%BT4--+zSO;6yya(Hh{D)1B}nV`l13z;)P6V{!xQ4F4B(6 zt5Nq2*hIEQzi%X$jXQV;coB%GRn3p_;+)|ee8l57T=MLNIdv}m3WUjnHcJpSgHS*8 zJ#OHI8olZ`TTy(}TfTP{v;gM%G3~MWsni}3Xj)B!I3)FZsdK0-8-Jd+HEvPCvwaJz zB>wVF7`&lduf6EvK`o?T{7DEC{k^{Wuj$#-{U8`vN4`8Am)&SWcQ+y!FsY0-Cm9C@ zoboyXC<&BEec2FD-%@HYht!HkX$KTGI3Y}vTmRa!N{A7a;#J@LudS|mMFXuR2Fit9S+S0btZs4CXIjp*MI5Twfa|ghX@`rMC7itJeOrP-t6qs=I-%E~t*1`S>|%{xk~g5HFHOb@`Z#$#BmI z^mZUk5Z3bu;V*STO_W=w|L)X?4?OFT+IoJMur}J1UQZ~(jU7$G4%Q_o5NR~vwrJL? z^XgBM)O*#x?te(>YTZLj%LjS_c&ue+CkyS&?-F2wUY~eMk_QfzHUP;c|9x>AroF-O z8T?L4nt^wf3zbuB1M?Gdlo+xB2X+0Wn_RREI90S@K#P~~wMjZhsx}A%2?aG%UX)X_ zV|a8|4al$J=6-tcvTS!v#ChUtGsce=C~j_{UjtG%kAL#KW1^o!aq!I{?)8^yd&ss5 zBsi6TZA?Lw#^ww8)wR(6pxbTkK%2dd(Q?{1OA1%IU9U}cCbvMU8BAH{8K_;ztIp?T zj0PwofU4>TXNM=6bWS46@TR2`nmZy)Y`)m{cI71R_d}Lh7?ADGpY5&i?c4S$Zd}ke z67MgY1b@~N(b&lEJczj<&qMDYBYXdt9}E*YGRxL>~ zdNoG*J)J3r1^@Gf9q;ya-Z7BwR1+X1aBS6G=0~V5DVR9(%TYA;U3W#YeG8?U_M7C% z7!T#h+vC@VBdMO3vVM7oqU})DeuKDMNfjV#Wq&|5U0st8)fkZ_6Us`~ZmSJ?OS=l` zB|p95Yir-O2Uqx9BeG5t55hA1p{D>5fu%NDW+Mub?|XtH1iR*IA?a_Z%q038u0b=5 zl3Q|a^Zs+0lc4dCbBr;Z8K4mZvX}@en;Y54tl7S0dFc{xzq!T;#io4YAOa-I^0w{w z%74l-b~6tEhdNxwv_Y0@*b>_D|J&FD8ADqMF0yLa6Gcce>i8sdjcAq(v-&%@k`t2F zIK}QT613F%fI||*RKSwhOLZTwQ|SOgASz)z??8_ za!|}Lwp-v-GxTOfwi;hX#?n}Sx9Dmx5r45xfzT0gLO3t3W(C8gK?mRx zk`m2ipkWs0{t2j6%M1C3kSq#$|M$ZGG>f=jtOOhm{OZ9-TrE=ctWj%yv=jO|^6(J1 zlxKdFrsn+XUC~=-zn-)oI26}!EO|K>;F7$`;FUu-9vmToY=VxKZ%0tAE&V zmZE3=%HS&PeqBDnoV_+cIN%KRm&TJ@zMi&rAE75999;2qY1_E{?*Px-0e80y!xzId zbQnXwsXN(1Q{3M-wu+dBkWi}JJZGQRfy%4C$8e}Ygzze2O3&szD@Me?IPXlaIa3AO ze3y!Hdz9{PiKmMb3}kTpXS8+|3x5pIYShYu-z4YNC~zk{_m1l(J!{8%tA3Z%spHa- zZMxW!j3F-2Ue*aa^8jqbp|h%32v${xf~0`;g+ga|NfK{@z^Q$IS~cRWH(OPO5qsj+ znlw`}Ii=1DMtHG3vblSGfvQw8Z#h!nHV%G|fwsP{Xob2gZp^aGq{*foAb(LecqVOu z#-8UvmSYvh1V-nxsEe)XH$c#psu0=-XjL7*5y$&ROwCV*p=vYdv|nFM!Xtz$c`sQh zFawM_&$$ST$PCczn(FSp>i@kS7KPwr0cS`I0D;SP(7x8!R-uub{DO`X5$1Z* z+ae1F>Y<=NZ5hatLUr(*oPWmRQ%ZpkOeSA?`MwVKzBrq)*i31udfb;sOyg+u?)M;E zTIw@^SfB`iB%xjna!(ujTjPBNI;?VDs#uCm98}abeT2G(`wL1Z3HEKv7iVL3r!uF2 zwNFFRuN^Nk&Zto{0t3Ee>M9n?d2I+I1U4O=CSa@SFso;2ldL^*(zI=0p`BtvN0K5+|<0T^qtSCO(meblx z3hTE#eq{5l_D52@On?aUBEx<%ngOCXVyD+cxgH`X@m<%^46YZ&8-$K|-tMBUf!94~ zWrPP+;mEVmQa@ciN90wu0nU0#=#pOBi!?3U(Tu3T!|74FSAWB+N-ALGwvL6e7A+aO zFTS*KP->7bU6BlU+sX9*r=kzV1cE?rk?n*7P$5^XB8`#*Is%+(`~oz&hD6smRq+@} zPt|LB|K8fDL7^BRcazN@wAYSB8KMI4-l7tdck=*`u-&mIP*)<9y)1DSRO(yf{N+dy z0yf>#=*}c=(0@~WojaT9Gr{~J9v0ewcXIl8R*q+UU-8cHy_$YJyt&!IaX4V5ks45Z zY!H@OLC-|Ri?Fo2taUh(b9X>_=G?_;cpkP*l2<k~%@oJ*Oa|&z2!_ z(yfixjXQ$yoFmM&_X$49dKVQ*03-+;IhA%=7Z||Q^n?+wmGVP}G!&pf&lwpNppD9O z;UR~3VSj)8wv!SEe;o{!wB0(}S>6=I};qM#XW<6@05V{_Dq=qsf8wD9i^j94l zOge+)muLkTaC8(5F;W4duQW)9sZWE+d-t9WiI?_@29;H5O76EwrPH+eTur#b8-)`y zF4Pj6K!gHqetpIEqzYt2NpQeBC30*eiY2uUp?_O}lOay=mv}r7;1sxH?OKw8sS@eQ zv6g7F-Cs}8k`74*Kyr?XkcjllVn9s^<~XqNtT|X`Btr`Qhp7`gdKw+;C>L}{7D%X1 z0;2V!-UV#=B3KqZV!y7NO-+4XywR; zvY7ok6Jt}>mr~hid{WtmZ`)~bSfxav@)E|A0Ye{pN049q9_QCFiZ4T%j1M^~TWM#QL?%XWBY_R}s?aeg ziz`4Jcp2Yi*mR;8bo0T1bm~gc4ZmsBq<%d#*?9Z5@Z|ss<(_(aS^MmJ=%Q4_!cC`t zB_$8#D}ikbQGc653z(N9$i%tCWqObqaYIPw1_SV`2GIe&B%KC!GFur z;g-%=eyF#t{krZz1LX%+t-S}qNcx~_%*fgcJO89hohD+&u1@hTaah9rl z?gdEET^^3{QS2D-M!znj4E#(B+nk{ft2EW*6Cp`K2oRn8lxJjWO^c;ZC4}@Vo^L{c zqfmZ1CJWJ`?L1$f(($2f%*p~qe1Gm>?m3y+lf*xE4~j{+n0w*(TuvkN1EyX>O+=%+ za3=A_aC*#qy&Y8C#*y#m0$yHp-qo(vXS& z8+>B7uxLYFXxX==KyW(we7EP`IP*SAhTPGv_@374d zC6eba4*Vw|1%Gtq3K>XZ9DkMZ6;@aPYsgTwJLj(?r`^0=*!h3I<$hKK`Kv0%`qz@4ZJF1GHHff_Q*jUVkV~{MIZGO8cHc zv8<7nUWuxmA#``t7hzOxX|I6Dal|X`Xy^nvYko)CR{|7QdMOUT*`J!E=N^Ylsr0fH z6iYHZ;^fc=@49-KT>XsF(Dv3Z#a^`Ynn?8Gi%*xZK$aw|Pw$;Ew9~IACxOZQwe)8q ztXcZ>ijq0hk=Wn2a(^yoEna2cuxu5B_t7~avXTBfDWEKWU|iUM9HzZtoZEr+Pz@@o zKePSutP!t)>yboj^xnd3O`ivYZEon?PlJ?tH*r6RrmA8EVQ*c)nIzTUvfzbT0+CrX z`Zqk+3}AiwprOBp`8`s`W11TrZ+q;)ST-v`(1OSu3u85m+<*O1(rU2pT6Q>Wo^k6r zPTJR|+4U%@F~Q&kF1BLP6G)W89=yz=0ZvwcI);iOr+as!{1t{<7{pO(;xUPvW^78# z6DiPfbhK1VT!@YmsmeKbd zgPkDEZZX!tnSWsKhD%<*3D=-&MZ^DF_{ZOdu-BBQjIU0y)&!IHPE7w%m=*9Mw6ZV> zV;y_=hoBOjsU85u6VvCye#8-pw8nNV3e8W4Fkwbs_!r<~fo9RodC?yjInXvYCl1g9 zQbHoXuOyR%tclF19k*$lzYSH_GxUm-VTL^E_s$TC-+ye*BO>uYXck@;I&O61EkT?6 z0!WVaQcgv{r`0T(nHe*e&$4i7zUm)3|7zRebJfuhSL){1r}BUrp=|{CTc2;IQX5aa zY75RwVdfd>k${I5N74oAllz(CRsH{2r325Q>z;PeTJvV*Go=IX?!oHQZ;tR*mEb;j zS}>JW!+&Da+(hTCq`kW15HAHdwc>AW@^VssQ3c61j**?&Mc-fY_Gs9Gu6MJ7clUVJ z$X<3iUp=%2Ri*-s;p`&sISC=T!ep)lzTT#)P4N)l3cKNWg!xj1UiO!Tn&`O-%mg#9 zHOM1NVaNeb$2u+1Z-6yG29%WIbis%Lr!-Sf+kZcp9Cn|NF-z@(pD0}830!#YMksp*_67a74tHNwKsY!P7%$crjEDpb z;CEq!H}KjU=lGI9{gDp}RQ`0?jLWv40#zQa1@M1Q^OyhUkHjHE?zs8$}p51kPhjYbFw+SKQE*b;gDfW-dh z?Y9MUK%X&mWnQHZupBAhy-S5I$=4B$0?oYzFj|(gnUiz?+nC4ktn0-^e476Swg+t0 z0Y!fqAkCPejlMvaaEE+4{&?UB^2hywDPg1u{HCYWT0|V7sAn-Hkf0OV<0~0dmIn;+ z3HLs%VeNrCO}Ux=v%mt@-o|M)#+Ta61}9=0z@&8YWS;Su4%p>> zxN;KqY5lJ6&*=nx>8*h=dEdtOKJ{u4pV5C}Tr}AlJDjzH>-Pjck!ex?{r}NivkRo{ z$doEM+hOZ(qQTjzkgs?F6O*#8*U0dfjpt|yDd7ozr&WL_vr`bVAFL{)@ZXHGrZaY-y)uT=LLUN ztM_K2Xl39EhB#EN7ZWQSjVVq2>6rQeLVsbBp>7W1SAC4y>AME)RC>EV-Y5P#8gSVa z(z*&jWk#p=BhS!&`dWd_87O6UhoXLGRqPH@b{PwFfr0bmM zX7B=tl%bKBlW(Q2A_GT=eJTR!;%a}G&FwwUndc3QixtNwpLb*7mK1G5ys|GkP zTz;4h-~H3DF6E~(E0cYlx23{-M0}1$>HTu>Q8^h7ux#T ziS*f)FSGprQrru9-BVhS961;!s$YwI4|n~NGjtl$c}&5WdAP3FFnZ$X2?BHVV%k@n zJpTHLo_<=Vk4fcRn*e>xB#4&?p3EXVGZ={osz2!Z!l;1Z&chSFU^xPfcXS(ef`F#@ zaY46|iJ!fmfAX`3C|!p2oFRY0ojN-xOR%_nyHZ>^_)hR`=$##@D-S}5DsYt^5&U)E^>-*P+>W18F zy1l$1@wea96Ut|fVM^VfG*AF2u5={?pWP}OoCMk--b1*V(-`fzzM_BKsI5zhAIXNC z2<8-S0mtpzmNv9BBCLjgBV*WSre>cqO}G66QVPY!5Bp<*o&k=O#$p1mNTSB38y~C5 z2kZgS$0DtIOnJcd?ReF^k*IB436-7_F+#E$r&rhGC;*NcJIKh)#)Vg}Sa=Ggeb77` z^Ef7|VPq3L!Y1`HOSOO9mM+Zd8h_SF^Ufsnz^9nutP0Po*-!o#gcdAp=EVcVT=-)C zm8aJGHKl0JeK0G#nD-0um*2c1r1_PEoT8KZ3de=;f9m+GDDb$jaqPWFER7E;B+}-W z4ewRdm9u;Yp1pkv3`W3)Aff9nzxP45sZH%`~$yJa@=n)yIf~6 zYGweoM-Y=Y)>9c5RD!t~SK$KATOcH<_Ca9MaFEaM*@jJxI`M`eFc6gmn2xZ|dx!h~ z-!BDaEPS&{Fo43y&;?{4V_SPkKyy-;=)IGm|1TNbW;Jpcz<=|Ut!Lck$%dVKm>GlGfE*UH@a6LJ`5QEu{!%_$?;2t|C zz+z!_Wx|1VxT}D_KnsmVLSW(1A;6ziRSPoP_Z=m{Xa0XS_wH)SmmTtyY!M-a=CQGttbEK)6ERna}B?3?(05_4srx?w%4{!vyNN zL7)`%pcsF>RzpCebLcl4-D1fCA^>CfQJ@TK*;Gy+S_y>sul4ao`7r!4nV$ zNJqGc;|FTZp8?p;+Xb$82n3-!|1|tOVTV-!B#+mIdd*Q=-{nWS5rU18HR291ESfic z(<~1m*An1I?p#NEeqI=+M6H=cT-UG(@{?+Tz~_G#o%GtxkvKn6HED6ycQoAHj$hwU zBzIz`Z6CRwFI zjaNB3{yM*@fudwd*DvnWmlcE>8TM+EsvQ>+K|i?7&x@$EX3`)-!=idedHdg8-a${v8s*$TxK;CX` z2UKG#33y(5Vof7LM0F9c+z$@BBfsz<^Vomhnn$}$OE`SH5l0Gmy1+m|t~7%DHWVnV zAT-(p&OWlY-aR9P{`SPYPI4F#_xE$b8WVwFijv|QH{p<^aic;=x#KAO7++XqIOF4O zf}yqT+6&ijD?A&g={!5j1S)SUxYCE6i13*_t4J-iat=bTn1K{Q9Gq<@0^@R8e_MZ{ z8`MwSN(LiaSf;w`QK@2#QharVr9DVv+_pz$xR`jk=EL1T>HnVbu2OtO_0t=mAB;@T zA1UO}b-VgC$}gY^JX|7Pr?|y^?Zq)!!kP)-KJo^)}#`A|fVYMhlM1 zFZbDLr`B@m#u<4h>@U|HSB9k0KFELS0s4wJ5KWigUa}B62%tZEFbMbGU)b)aX{PL~ zyr5UM0QY!r%Gtwp0}ptNK+DG&$(TkT+fZrbOCR$x5s&&tL!gyHpf5zS)C4&ovTMai z)-!vZ-mwf#xCr65{r_%gC&NCnljd-J2%;{K6?|%o07Cyl{z?-;J{+ciCbfThBj%Xa z)8fSX^S~1Q0Ne`RPfdc0?Zcrh7?j0Set#T~^O!*e=ZyNtp(-XJpz2)63dWpx%}H^i`#4 zKQR8fBv9ZvEMf|CrPY#MpsYeHVp>XKM#jalhyzS4ZKu$vh!k}30~KX+Q!Uxr4r*0zJ^Jn822PU9uRh4C=cE;cp&`np1oz_|uhz3srPeLLT zPnC#7=KItJUb#}p4iJA%$;(!#go+WC_cUd#!{CnLWTk~j2BM3hHN%sUj2-l|DeJan z$4QUN(X4+*7BlmN$wOjD{e~ILPj>_5cFta32uCnvr>3z-}wJN zIgOKANa6$8LN4|HA1xjrz)QT>MhgV1DQe1V6WQ?R19pFHl2^^CC5Iyx{d* z-bozJUogB)y8QLPsz1hn!3qq<=l^b~9*m9rZ`}1E9Ks)Pi%`H(q9o)2&*{bp#$b&C ziWbA6Dr<1~rAndcjN+2-Nt?hgyfYv$)_z*Xh1Yi) zdV8n5D%^hxNC|Xa%Ye`QLT<&Rq!TL^%<(MNgYMDO9f=*Az;w&Ii3|7u0;f%Yg{+v- z;<-7Zc*2zpzY8c}tYb_cNu4sVy4|2UOR5y*LZI)jwz<)my4mPK?zAz;HeizJVD;a$v&Gj%kq7rc!237JI!XXyM9%c8WX zQ_|R1nsoO7w9B7`EbIem%%g(^)P*~!p&Q@dAD|?Qq`=q~zgN5Dae&I3`?&zbl4WWY>$XrV0a)3dgHJ~z$1h&sCpMi8|e0`T! z3{XBm4BWG4Z|f8_7{Ih$eH~o5ZP{f9;26Vk%;mvrBijm@R|5yQEQNX7{8hW9N;rQl z0qu^YV*nL(zg=i4I*~C-i{DG}l*wg`PG(-XUX_8w93>5ov@W#3fX*@i&Az_BMGw=` zG{BAkF0>RVfcmYntKZ$&^e!lGd&CUFfx%pUnr(1_ku^qVda6JiQOyOoZQNAtZ%4Vo z=fP~jEDW~viTlRjd?ZA#FAeWOO$T@(xoy3X!4a8A9G9&N@I0hIh$PhxgWwj3v!et`_MBtC z0gSt7xR=h9BQkR`5s0=i^Uk3$?`Zda>8rIpk4=Q!TB4@vp@ zH-YeIGkoFKS^iqBF++X3w;inUV|@i?e!GT(R%Nm$#e>^S0!5Cajo}+@^`({$U)cxtC*Aq% zGI*p?qeH_EW2@u z5S0KZ<=cgPfJD>~iw){Jn7TZV^RFS&=DL!;#Q)b>y7ZjDm?^qUiI*RLZ(t6+M$gjk zZ1LTU>Y!+;9p8Hj3-naI`M*HJ3NVIB#G8 zzX8$4W!=g)TZ^H#iFkk0b!iXnlZP5N8YxL-e%A{tfW-mN7+vMARC`-$`s>+Spsk+G z0HbLDsA&U6)U-=d%79O41t7Kw2{AHbXfi*kda0RaEaq& z?CHadti?3lqTzNi1I>XXQGLDl2iH6k0&u}<8CpBX8EAqHMi75?@UelF9VhAJkJ*vs z_qTbM0VI(2vu?owfEEcjOOAp)p~RHH{$8N}3$Q@u31DGriW~vJLu#knuM4j`t~CtB zo9SavXUUBYXpE+c8u5_^8#1Sxh4Z8DWNAYx+kq~2YYgK_NEC-NF_y<;ui8D;yp)s= zNnXvtBPNyw&A@-;fGH5LHKu$pqUFLVC3Pm=0awam|1I$iWp51?9#8WMV6gx`@hd6< zKIwH!OH===Kr^xv@&GC2CtW_aOwa*+oSLwq7duiMU~g*3;Tv#;&WGQb1xUK^!LeX= z9brZfz!A_kbf{VQ2r>pwD8^{Ys~QHg-?lTPw=gk==jMO=E+k8;$72nrpTw3M=lqH4 z3xOcLrgeqAu1Byq85<>|J1zw5HBBYi2rl#XUgr$RV`(w#;as{ z^=8RzM>%i6KC)6-D5vn4N1=<-pz1?V{p(B76^V^b6QbdX74Yq0XI)#V9CQL&?tPl z3;<AZ#@UfPJQ{0r3>p}dbn1y&*0g_2 zq5}nm|J1ht7MaE)S>EmQ_{@r?o|h29l~ZQLROd%%rfJ)iwwzSAEHZlxjJDG7X!Fu1 z6tqU>u|pHQ^CgndZhuW%BLJBuCT*Vhk_~ZBG-zhwZ%GOm4uR4EV6mX@Kw;|Ep&LAzn^ zY7$VLG$^rqWh#JVdZa9GE(IDJDCK>AM6B*iGc#NLdgC;+@v-}vbI=k7y?o{6F}|o{ zxJD9H;1v(Qra9a;*2dPa!8j!#YNyfeTxY+ab~X;nc@faKv@|N|;+KVo7wdl$rE>>_ zHsX?E1wLk;ZX<4!MX%Za7cCuh)BTqse5QNgBle6v00 zArB|g39^>4ba)c&g!pP%<0X=lU@rWo5&(6^@{m%!XM%Zz7ri0VtoOkmN9U2?CJ;o? z1F;~dB}pWagMb}5gAke1yT^YUFYNJ11KrhC@4rDSbA36i!)u|6yVneu6AiW&H`eUh zPfw100Ab+Za^0~b1K9~Bvso=pQ&9r6WSEqUf9s#seWKz1%pk0)8npaLh!eDv19b-g z6zD@Nd)93!hRz+GZr`+(&Nwh}>=r*05Q4TPrZAO8Knw8p7kB)6>hFK;9Dk3n_u}LZ z$2v5BoRXx)hW(+j?QMk$eM&NF{5l*_{l-|&2pPa14baZQDFdjNqzoE^o^Pc6d*;O;Rrv8*zD4IZUiuQVbN5f^_u66Ss6zrd zb?`-c+Rme&v{G9bBe4R+s1{Q18@fP<<5#2H9^a@?=^eTpc!q~S9eUk>nN0e-@NFOG zXU#e2%mA97Xv%*MGiAUa41oP7@IV`T*nE@r@4^ zfWfeY!DPH$i>r^O?e|Go)z*WA>hyY$@%j6dctBXxKF~pe*AH^hy}pJxzFcs4y*UR> z!cZ{PT))?(VJ83EyyiEefvo-mysWu*Cu6ZAtJ0d}b>)A-JpY)9#LC)g3!t09o&Wk7 z@3oP}pzpk&7bW7Aq3|lxVppQ-32`%rvN1Mi7!nph&q+iTUJM|UYX@}}?r(Y>94ce^ zqUg--AXx!juK-_hu9%y^dq-~)121j94ZWlo!M9;!yI>>d9PS0{hw$d@;rA-3t_Kn7 zJs%dr8VY}3zgA<+{ah~W1G`nxZsSC8gRhes=Nmu2-RuqE6hEq$d^9MV8YUMvF4rEU zE-?}i`pR&Vz<}Z}A((?gg#*UH#^jlSgRa(Nc)q@f7Vr);Zi7Jx>g=SaKcg;;NirMg)&cuATAz~>-HpGtrh1bO^2ajhyuI;3=s~+2KLZuSmO=2e zzR>Sk6bs*gq{YfBaBlm7jt;<^eNjwK8yvblAk_YVrlQ1HO9o*7_1CSM+jF4jXTka< zEy3!$OHCvC1dA7t(q4d(G8@>mcjV%|#~**2rIYU%kKv$?#Lb}*sRtH~(CIvFzH?L% zG5etActGPv>+iU88%XTb(`ZWIQ@p$BYdXmhmEJ6~2bDW?LmwBr#Jv5sNEt)f8tRsi zJtCa*BH+-xv4g*J+~~cS+iVs%Qs8C}z1^@=2o{uG08fKwm7D#t)HCP!;((?cn2CSt zBrQP5`zC%e>l$GsBuGK}L0YBg1~?2bi=RdrFSlXwr2_17ZH@tB-m^kPDS(0T&cGB1 z4$lOc^!Jog(lh`mo8L#Gse1;@Ge;zyeev$T z4Ekw70^Mpb6eu zJAYx^A?-PklaeOkeAsyN`uj%>vsQACXqBDx0EgFEVOg_${dpg1)~CmG3Qc*}H~kJDVrxVtSu|DX!WgLy;Hejk6AB8uTXYzGInHYii1J6Xb7?MR7n&X$2rVdHY_cz+$g zOVHqWnruKVx1?4A;=*x{NE<%vp%vMdN}^*q{C-CpEO5%ahEH0-&i67!|e`e~-o%p?Vl&^A`q~X+(@&yvgyMvWjMWOJ?-% zgMZAaGn7!5K45C2DofW3_yz;Aka|FfFgqf7lbg~9^K!S2fboAPk_Wpwr6eD{J2BO_ z_Pyk3SBpd^xct}D^jCKY?Kg0&mrF9)6!&iHgQG8cyWp~%62Ph1)fk<2A97`)F2Ceo zk;%j7Ezn?BPN<03L56zn{=NKhv9YD|H>&EhL}u@j`kB9p9rHj{nfvea376tCrb@WH zO-c&{h_YFw^(lYlXd`WGBn4=A0b?3!fAdbr-L6pzCa`VpzX!3+15O~H`Pn--d7#Mx zSKkTeYic23gkKNCO(GFc)M>`uB{TZU|huK8Nub2GPm&8|9{RVie)GfHYOlwpmA1KOq7tV3w1Qk z(s7s(fZu%sZ0-Zh{e)@O(F$Nd_jFqI+VUgl2hD%8FWO58RCWR)%IiiC1_bAJTlGjQ zj#PjD3BojrZ@_yH%-yr-Is&$TcHj@0KwQPdmbLo%?ctzqOb;sbnbU_MAO8T7jyep? zw@%`8+&!Gp8*Ii0sjR9WJgHGZPSQQU4PI0}?_!cOWE27wMZLX7a)%fa>fihCA;x?J zsSbaVv_|%#xD7q{_NVwrf})NWgaOT6Bn#bN@+Lwrw?<=c=i?2ep0cZs9d;#3{%%h6 z(seI1CsiQ6O-Uv0cf&Zn+@jb(zX<-q2Gi5rndCp<%(iB+pB2)n?QW-HL^XTODtd#iBej`RATmrs9M zzlg6*T*Sh{&l>^J$Xs$;jJ~%6bZSEaO7RH3YPVLtcysE0MEhSwY_9P9+U(l~^$YDDz`2pdI+^+o{1G$bV&bFYpex-k)yR7Km zDN+lf6SHPta%_;7vz^z+yrV_)m^hx5(Te zTlzNEI|C7B1SVHo*)6MIpN61|-{Mco%koLsppxmE*x$bIpY>Adt&_ud9~NZ)aWxQs zsv0k-Ra~^X*Us}O?nlb#T#$cala4%9M+A{KUcoR8{qy%m|DD0 zHqH@*y3XplKe(z@(P{wye~HcaG|fBs${y)x^bt_J2uRfB4A@|!!zTm06a-4Bdx`lJ z*kjhd+x*kecCUhF5PkS<0^Xg|TV-MpuCJ3X#;Eb;N!qUnTCAlVx$l3Epo}N~`>c9F z&*^yn!cFPVCeOw2m)N-5nKUpwP+)OQb1`Y365*3yGRbut01O0t1iDc6=tk3s)dPrH zA}Xej)fJ44*ntU*6=FHE+{3MM+$>5yEx+&NgVo5;A=|b>s1Dh+?lHpdozUTM%+CI1 z_Lo+*2UpR=N0*`Q1@C|Rg`(C{{y~4Bx|xi#Qn_G`)Z2=PAvor zp#rhK1jgVEZwec$J=*XDwc?SX(ha9CFwf(IYZKW8L9;gSSdwiN4RFde=-xN(%yqHE z7o~ra*516}msi7Wmm~}FfZ+A_q(b(IoYtAOe%>!&5^F2|Ys|wxlJY2{ z&xht&5>JC)%f4zXH@1-2-ZPt5Al$=0>cc)`@i?r_0?rf7qdT54AwI$9 zjtsyN24f!QfVO`f4DZ5KwOwuEvGrL7pgvK(%!VK?%)=j$0qE?l$}?8&5l*wW58mZo zuI0-#fW2VcYXpNu+N%eE_GR~=n;Zr@2r6Xn(q=*}2t;=Ppz`esoGe|) z3F+_msb0gO7ya~^3jGcme|!i02c4d)oM-~V3NWez7aM<03Qeo1@~j+uN97V>X#dMF zU6Jl!IL9_h{(Ja42)mADXIG7uB2fnU4iz|3a;9xQ2dvf|=@nV`4ni~XJ}hFZ>?3w3 za7?WY&weS4@&Tg2x(>t)^+Qm=*Azf{CY8c*64BB!&f_g)F9MzR{>~Jt87`vug8&Y^ zFnwb|yDoq4E^5b5S)C;d!Qe-*qdA5POw5%En+c3{^f9-lS6WZV+ZC)D-Dt12e^~MJ zaI;lW9gVQ3hyoLpSfJ&6M702C)K_6UbP@cMWgv}CK|OYLBXQA&koC7P-`+?#-M0n~ zr}+>}NbG)Mo~J5rdum9^_dYIqSi@Y&ZOLX|st|uKb1id;e54R zJor!>b5p{Ldph`dCKK`r1@{}Xh5|@sWd3BhXCOcdO&{@#wj?op0AbxCH5{dmdXN>M zmVJLxQIF2-v}b3FOjp1VF3);BM<9IS}9Tpd0^dr%LJBDH4KPyC^Dr2!L< z#1mds;s(g=_Va6jK|^aWD9HPx5BU5Et!9LzRn7YyJstRYs@{uBmVAy_SaBZ+Nj?g9V=~hb zzpk8=GhylH3&kq*ng@ZS737k>{hJEN5&%p=8cQv!j97Z`~@ z{qwNJij%C9z)CRv|MzJM;=jfjq7?D`N-v1&=?@e6ZWbanu<^XL3-`mS- zpKc8dSk6%tq^W))pgJKpI2r-)Y2%>+>Mt}uH6 z&zOqV1Q=XOS^U6hv*j7{JeVLoiceK?3P-6~);0T_l%K~aZ8P`& z0rrwBaw*!jrn>4ax;KB*w2vazL~`ivZ>TBL&T@;{Ra&W1(|s+qb{2r6grU9x1W~f} zh2L<#f3bLL5KE8Q{5fRm`jk%kMaJ}K<|wJ*Qb2FkpMj)OvVoqGX)yhLDsycC9iUhl zD&0%4v_AIlXOr{f1u+eZH4;GU3S9~4OCAudPQMA5<3*ADCaZs-ef#&wujV%l6UCfV zPQy=;gnE&qka$FBsKD0mXR-i4(`SQ6(WfZ+FIprhnB(MaT<}4jz_?8&!@>sDPf@6% z$P#ulNFYDnQ?n0My;gU=-uk`g}K0@V-cekd+-xGAPB z4NMEAyXaRRmNuaU-47DO5G7w(2oS*Uf?QiwK>Z`R2{*qePrB-OCHMFG76_pKfuM)YCk z!!)M=ylCG$_=uUP54sZrUUO<$a1D?f1nyD-v?il$YfYKJhf&qE1~4kx&5%9NE+BG# ziEg{%Dc{nknQWY!SO7hAr>HfSE`DTu7>y(@U0{EXiQZy~^fieS=HT9vuk8i^-A6_uay;&BLNKcfE^hYUT||3=Yhs+d$Cfs*E6f(Peu;+!O6?xpjp{m`CQxopX7~wIJ15kym zA+6PCWKyA<`qMx_o-*k)v;fy6ro@1C8Eieru7-?z`;6F!dPV%x+WqNlQ_?x=c6zmF ze!x%y)`P`8izsos#ut% z3#Bs4c1;n`#S#}4cqJ}%rl+fxGUq?jQ66E>8{J2=md!V}QTd1e{8|#EG~D(xmxn)e zjr_dzTljmvO|M+c9LD?PxI=sXTx%Mo)t4-q?+f5p#L%NgUqQow3^Odu=E|C`)=Pvu z_z%4W{jL6r4_kHjSsWK(XRlp(v(SGzx*}6ufXzIiLU?Q*-g2>ixzCjslBd=75(EzI z^O~)9!B+v8OdD#_+$Kr4-*?}MJeM7_1*q)yb^;+Ew1h(_$%2dvjth*G9Z9HsG$UOF z&dR;1t~MW3RoCBsa0gF`YI-`TzOD%qoUC`<*|Q6MjG9@b(elHF{r_$k>-eYgaUw`|t%*vg*9R zom_!+9fD0UhEK`l(rP7?q~9$-(k*G25Dp@Ui6!K`+h;rOwZ0=npcR0s0U_R0O+mkt z{`vL%GdZE5gh^n9{>+eCwF`d{yO>F+Y{Qg<8AH|U5l>4@u821qbe?Ll$#lOoCeX~} zvNCV#HS1epzD!9m_RpE%$=Z*w>i1vkiVH(dOD0Ax;ks9VI#Df)LJ&a0UBN>v0B8Zb zF@**uU^=5 z)!Wd1qjK`51IWetJmEFd?=30BZ3pi@z3UAETHNr3ye5#zt*t77RBXv5>b_E+m%fbf z8H8i1^BP$!?U{pqxs1e=NtuDjgQ!tip{x&1WEs2N^3K|(pA-;ZRW3=RuKVn)f6o~` z*oMg>ShzqH4rJ36e)i|(uor38^N9bN@YT~t7{eSL0W9B^1_bZW=pM*qAA7WB$2fGnV8 zah)|%KhYuK;s)20SKvgUlj-4@I`%`(ko-Kd|fI`}4={fOw4vImA&6IvZ#_{^&vgrW>o zrwsDwzk*e0gFIB_m!Y8V&{N0W@lykq`g~=_p@1e30oebH6GVq|cP@`qp zE_S~XV7DwTz9~EsqfFJ|=YvZMGpbl6p_d7ECPe9D3)(naQ>0XJQ3GVpEKKIbn;Ub6>nl-0&YUkc2%EDaDDBj3st}} z7<`k@V*vam@?YiinW?*PccTaJ+%5$6_ zr~5Q6I1)zca&;mrjzY{bt&Rz{u_lR7`eRWL)JTN9P2b{T+hO7}p-es_v+sg}h{;FLZ=Tt%rD(WXCjEE``oo(!0K;%=@Gh}h1-%H&GO zEkzQ*fXLwmgAX5^SAdS~jR-E?VgzuA?E9VJl}+f4K0ZYUMgUqpeaqPA%|1-wV&?|{ zat29N$ejCu`TNRh*d72+%sr40HO+r09Y8nB`qOJq>}hfiMx5MgDv)t^Zy!Jm`k0M4 zuH3~;zP$vaSw{u>ii(@l-f zj4-z1Astc)AYEszcUvr9190kA9Ith($+S6^AS^14RybI@UQX~1C{?|-<8gnM>T|yg zu?g?&J7o{7YZRCFhyN-D5RzBTyaq%LsePA!)He^%#ZUF$G>qVyGhqZ-l(2HQ4D)iH z&4`U1fTWIL`pqwqMBe}k-Z7>GxAp)UO%l+vn*q@Bl864yJDVQ7e&t=2B~+L~vOW%Y z%V;&5mQ9x*FfW3pN&-0S-J*XAbOyuFspHkwV{LZTXHFs9XT%%%etyX7id;InJe=*x zJ?)l~YNbs8Z@UyHI>ylTSRubdzmIY89vV@HSYhaKVsRal8h`(Egz7N463=Yu@czvV zuL{8KIQXV+-~&4v*0&9%6$WsAMu>dWRKx~~ioge!pY+q!ZoZWr+){tYvt5$It5aT< z`#Z2-nr>7~N+uvng2?x%CySKauh+A-c*P&mR0xxQu_kI)kl=Ay^JeKCMDiXZ@PI_- z$Hno$i%U-`4N7$|!^sAnpWLLcDd+ido^6ijcbW6T$xxB2!EeWY8-R(`(CuHP4f zMtL!_HNJ*X1Ga$gVg-M(>6>DV2&dXFf^e><0b&Wlw?0a80di<hGxkMs|od54ennxN4;79PfSoK@jmLEz!wN-W%fo*~BX$BG*O+Mf3g_#D zMh(XnI0r1z$46vP-(z(5txvsmUr59%*VoTcuyGE+XMd_?1U;nnj&45&$+se41x zQFLsU_B?@|)IttoiK_53E?+XLD}}LI!<_r;88Cf6o$V{!8g$4CM?&_>t0y1H5wfCtuV9=#Vn+Hio?gyyU1DV0TLy{)|!y70{J7Fpy7^vie+du-|CcnJ( zr7mzzAfp#?d(Xr#pyK=Pex9vvu@g3_8E| z)_ILIUgnHk95i)1?m%CoaO98<9EP9^^bO5zlI{J%bP0?v)J03HjViZ2#zXV9(NTXz zH?1-+ip~aIh+o@y{L-b?HmgHYFj}${k3_z_yirm@pIXZlBz6b))9FhWzkT_1jWZ7J zArWnIeV-}ylwqLSN}!Ao--Tw3HA`w}Cw+4t#9lv%6n1b-gsDE#zNFQ!*YOs1sp&(v z3W(1_)v#MF!6!>yWbO2)Ptw#cL79I}GufT4>wG|`nKk`F`3x%eQfmu05FhTcPQZHv z`SxQ)^a?fo4Ccd6?!FS1zx(`c5U6-UpEs|(RX|;2L*vd)C>=(KUD#fc-&&gT2-48J z0rj}-RWD#0ct#Ylm%Nvq=q;Gz9;|bxyZfqtlqSsz?2Pq%01~WSaX4WylgpHbCYpxhXbNOV{@m&*R%IV`-er7EAm5(uun zM0?8Z_kM6Y9>~Dt+e29tfPa5w0hd!{mYx2nld;JsL6F9fVaXOcL3@L~i9JM!GWJMc zz&wf)%P~sH4{fG;y@~Ipz;a7geGh>?T8sR_ac3ZDSVuUd{W97*6!L;>%LUxsJs@k3 zp;^%Ugj=(QS>~0&`j#CpQ=06(fn{%cz?@(J{_W>q?HWJfBb!(&=v#mGA%l;Od~(|m zfFOlzKTp@inPEtYl16p=#R%ghG1odN;pHU%YylO2YzVYZKZu2K8YwnEh?0;o1vIq0 zyoD#!9|%pU?AXqy*GY0QO5gnXWLcel=k>q|)u2OVXOwf1>*&Lbe5Hf-G2kmEGU|iF%(oKdXkdr^yZE(3oOMKXn4| zwX%dXjq9Fa?8+q(C9>4Ey70@j2l3OFW%Q3sO4SEJe~rK?g4cgBNi{tSP+KnxwuX|& zQ8ysWQO}>ouh5ZG+Eoa91_1uu=dIiX0#n^_zu?)+-vWzy0_OOaL`N%D=|MmKY67IAV}>IS^#J zxJat}g^?h#GcPpVXYm4mc0es)4J2RS9F)CUFX)SeLqv~m{nd;gK@%c(iieq_;yA%C@ND#8hQ$G%W)FEc2~e7~=p>lOwUe*YD;GIk0q!VP zmSv)(F#Pw(7?y6@&tVX(21DGuZ=h~;Hd|xYOfem&XpK&mdCif3KR!iK(+p{v3LwW* z^p``;=kxFc!JNV8hw`PGbSJLIT?(Q$OG-GlG%1r;*-u(ho-H03Y}cpkX*tk)8lX@( zM$+uIV8GwAJw>7*`f-$tAOP8NS!A|va5J4Hx?(?;f{RN1K;fqTCZEQ{ftvT$B0ObK zeW0!EMsw%?PsZ%rTqx8k&xtmDs$;3pJgx^8VJnmb%y|R@O|nLK-1WJJX&M6-XpL@v{PBKe&LCN8IaKd-(6+{D z3rE7*pdf#JY=)H`3`O1I^mJTIN&Wb!f2=DCSS-;ztiCA_!}JNVKTnd-q66A~6$41& zuHlL(i9v=oq@#o~wW?>#UBAe|#Zi?01w&=QWi9=|xF+6HYW0oz#)1b0fm)coVOi;a zV?xdtBFa;Lt$KTajK4s>YhQ|E%=X^$tGD>u=q9fUd=;28SK4VpgCfe7#PDN-;}Z1A?|a3m6;Sla1twt>+TBKSkY(|fDt*(7Pj+MYUWrvLK zK&w;Y!}dyj0ArW~Oz>j>a!&~FRuDR`ib+*}ZRZeQlnJ&;komeA!AEsSP`LwU2Wb?L zz7p_R-fF?jP614uLyk|1S*guByR)2e{D~&7F`$dNc4T)OJffLq#che$u?_~2|2}&?X5I;T0G#V8&Xh{>+l#!x-%YGVin{7F?n7T%vN$$2QG>gsiN?0ZjH`!inJDVuGBLxnqX4+OD*@cL(Xe z2wp$TVLwB}i=oX_HjLY!H%sae1!hnsXJKB1Y|8hs_cJuW)$8)=qRXn3ew=n^p8!p7ITLGT3{)n}z%Ahg+O8*taiUGx4f_zBc%I zy`y&yQHi(LjIS(Zm4kb){DJgam=EC&t`oQ;0*C7q-WSXEAvOulLn=NKlXm zD^dIDBJ=3y$tc{qb8tcZnUpGjytjBm6N_2b@+41SJ+FGlJ3051qmx6Yf>z6qX$*Y- ze3Bn%|2zpYTpG5w)`mUTMjO?<6jfIFH#pOXi?uVX@6<;XRVo;K8KElx%AKRI$M_KI36&}= z!M;bdSf$gy2EOfgcKd?YLGnL8_dWhVLgE9TQVusuD!HbqR6z3h$Uvxng+=CQn-yNZ^o&oG z638*_=k=s3t1VK+xA0zdmD@_av5$nX*%iy0a7{O>L!M*le_|>~WGME;PD969jl*LD zNFRKCnLS{kpGE_06rE$OurE8uoz!I-8=^M9qa;Pf->+ujKyCV}reP%Vi5y6o)-arO zWuissT&T{cdmhSv;OG)|hxNU!D#$#=Q%*fhD~p5SrZ8_BN< z<1z(o9HFf5Rn~z9Xr*M-tqTfs6^xTVEQ0M%UO+|8oA4=rEo&@ai!kg5S9*>8#}m~) z;M^~e**^w&Hl9A^EBjgA#Mm#aBx3%Y?~6 z6tnqBUK}ldD@FE3Mqii)eDDXRhs;x0Vh3ozhvH}AH<1;%SMu6>dD=dzL4Z&#|B;(< zri?3s!|?uaK=tXbBQ7@(ixgilvI-U;4t% z+dIJn6rqw>7ac#RS6W>Wd5JqR3g4R)>U}VQvM>Vc!jxLbl~AySU*~1Rk?;I+vV&H+ zR_2t`EM8xbQd^S8!+8TIn&>ScelxnsFn`#|yUhG*lCSQ?$D7a~sZ-e+rM$fRN>T=# z1q-l$pF%pzg7{515>b;Ty!is0(FL){q-ve>>e5#>8XbKsI8w;U6g!7v;q2 zgSM)-D4^5ZpjrCwb5}j2jKs&!ceON7HP?ZEuJZFt^NriM z9{5n;2EJr(#{KlK*H^q5C5P%4z*p;`JO~>(E@OMTgotqP&Ykk=(1r>BT!_6NLktwA zxM+;Cs5@Wg!1Xd}eGodrZrO%wz$@N=_R;^`(tH#7*Jq{!6+KgwHeC7rqQQX~uO7k7 zcAYcS4}wRu`2_k70s@xuqM60AiAFjdB(DiEnI!Oc-lHEC0{>7Y)RW#uJHk^&-7#yF znP(wC!H-9=HxJx7iXl#}EDyfK-~KOYR3YqM>b*jhi{pix>OSYbkQgVbV!IK4vD$Wg z*0N%UWp+o0^~JxeC~!bBKbD8>jWM;aF=uaad2P4%QgHphAbw!P0yLO^K-7Xy|A9H=VJUfIDPlN?0fz54+ya{x_26kIZ=_%XU1aSf6I`UzKm)`m%IB7<5m zsbWvcW&fojZnQ|b7(fvFRjemxqHO(r(9pTTKsb;iFgC{^*`i^*LF;8?!c*wRzJHM3 zZOGT@ubbhppO*)93w;?J)-*%+nJb{K7)%sOcC+l^WJ!-9*uDYLz(au46=P$aZsQlk1zsV zlj^k=;kCG)i~=W%8CL5p(Lan{Kj1sHk4lD*33S%wGrxtokJLM##C6n`2TDy{cd;=V zKFbo93jN@6|NHzTFa_jCkIFtta$=~J$0x36uv%KS_=U_rj?Q9#-BBzGqYuOYw;}Eh z31o(Akl;Lhom+z%bbsBcguvdvtUy_Rk=0Rk%r9@`!n6)eI&Hgz&C*Nu9eQu>%U+F<Fxd-$Km-QjqaZ=cqge z&}{^+9`KQWwIL}k1KTJm7cLk^4@%moiUFQ6qJ+00BngN+M1OE|r{P^JhF&u|zaW)B@z* zNX9ICOoDpy_kIo}9}=UpF)(Hk7Px0>d)~DTH76r~dd;HZ@#5*p4ziyPfPiL#Ah;Wp z2uX!NnUImkrs?0q$B|)J!_r`Dk9$c}U@(s*ojf_PzbxcP7N)`H9^C^+iHHX@)&V?Z zilYaZ>mP>~eQc%i1caS(L%-Y6nJX7z7%%2Qu>`yGNZT~2oQ6IDrv%NNDx$y4*}Dg# zKy3(ragh*FPd-Lnzf^lom#K(?N$y8a-GS^KMOdj6x4>^W?J;JhZh!2N&k@E^Z2Xdv zZf-KD?lD$cO7^2#t1wF#?YL++xIHh*sYxbVCF6-21$4tDI)i5l5{MJpiQeJOUSl zZ>*F#)#?a|EylU)$w#pnaptIhCoDhd0Fc3;PHR^P=s$Tkord@ITYXGLgAq7B zqvQ(70_;SF2^dD5A*h#}D(I}wqE~}7T1GUn^3|FoXpx7rdWIOdX6RTh|L&8>9WIXX zPsV~vc#*^<=p4|PQEGL#h)?ynjST#yOXr9u=G2|4>(`l3OU>f!&jTsEswi z4iC5Lqc8R?g|tnoTonc^I}Wbt$z=No&mToEWC*K-(#=4ZuTkJLZ=&<)DKE#+g-lFc zGf(NBl$Q$+#6z2VFFO#3y(AYcBLN)=e6Iw~VM-<`+KW%e zSfXBJ;cogQdQ(v!C$&6GUzD1EDBNkzfS9Z256NlHv;@;^B6jxnm-`7~@H)U_>o3?u zQAmA`&psRPL8`bj_loqx+o#GNeius;yF+H!xQG93PK%df9eR@OgiRbLR28QfVLskJ z#u}o{M|q&*L<)*KFD_y%Lh-Qln=hJeUk@R1N?io0hyd3Ahr&?@^d+Vl{2rXwPyJ)RN?3d!_ zL=hfjOL;n4b)>|<0W>@(xW*2dT?4!AE*b|I!#%$=(s0ag#!2N*{mK$CWih%N@238= z5D2E>t0*f@ISx3dj-h3L1yGMOR|qjV#{z8P?#WkA+1QnULLDI}hiJZfD(+&2+X``n zGeRtQ6In@Yo(foU!jP0?4!0H-5VH+ZN~*0uB+vDoiQc%gg&rb^cSJ@X-6!;}SPo_c4@-uglnz}px4|H};wis5G~XJ{ly627!G!V$fH*v|{UNJ>NDCb`&PUZ6 z)VxUs<-_;n46OO1Rr;)iE6eqx%-!J3oDM1^zJoelZiqryR3SubP*QINV_@Fp6=$X%6cMFAF8xuofGBYJv+?Ui0-rC=})9m$S~-F>hSE zEguB9MzE0{;*;SY{0lTidjnMqe_q25is~8qq$iNiz`C1%T0MTX>|At0RMNV$CeTdF z0w*#S$&5sNn_gvuZ3w~@V3yTP^A^kdK)g+eR9X0&UHQCwj-Av=S>}wV=3;S#-I5<= zzvTK+M7(NObvo%c&s8y2r$D6|0r;%w7(Ivw-3UR2S5N2b60~orTJT%>O<=HrMG$=? z{hN2%d$mG;725@Fv1@|am4V`?O2imKeghoi;6OS&oojmYAF|vxNru7n^l7B}xuX#R zPs71gapfh*i22wgLD>(NhsR+3K2DSXD8yKk#%>1P=GGfeD>umvjVnnUsbrSWn#lrl zc5D|yr8;Ib@zwZ+>(CG6YoaWAyXM#w{Sm%F1kiDRz_g0(PL|M5quQx5$)*0rrLJu2 z&9|HkI!KGw@s*TWl`DpRv5pOk7~n%wpVuF|{-AakC+j;>v0+Ou(>3$O z>fEAp@!-v6ee>LEk^-u!ZDS1B!45~T;aj5U=RM}UL<7CL43}AKDyQ^LiEg0PhqJx-S z&-)Gp8R(Nyd?TEErix^W+l5%JJDD*)f?5~J`d)a3$w+T*TLBqY0My|E49c|q3VZ@6 zV&-*=tzpI>zwx%Ms)q%UW+@1~*^~M@8vsCmRSK%Nd!#9Wb(Xc3TL++r?a>4cM(j^K=2#s&a^b2fri#9p#J zU^)8HS|a+4$V_QVvBb3c@)R=!<*XmVjkav-80q(J#o&F@Vq$!DnJIqh$Q_)7EuHj# z&EwLuBH9tfPp3uzb7Ze_mAR+Y;x>8!v)v0Y33y=1hMsBiZ>upb2AElBpk?qujlS_- z?`EO{0C#B%m~&cw={nNbB&sSzlO$9vW~x7SIe=#;8=3WBO%?1nOV-g^x~(3bvo`g2 zA8^p~yaG=G{k_N4ITuAWjd2f_IXrlOi$wHlme4ZaG3aAhMy$vJ24G^Zl||0j%Ig|6 zJaLGx#97bn)l#$R?N8I|*Hk@v4XFZfq`9BE1)tza8=*q2CxB-pM3oag#L%30@8)gn z`d16gB>$}&OP$|_CL1*@lVZPUSHUKluVq9KY8(2zG_e*zM%$w0Z$42u0Rh#22@t`! zw=)CxS++Y%DI{tBVh{x>!UVwFG4CteX)O@oinR<~o)i#|Ugr{Q{6U-703f3cGlJ3@ zlhq{Buz?x>0<}6N?hs1~I*ybKk?QaSfQOdcks-V*lxBC+CeS$fOt=*G>r@;cC3&;3 zq-NOl=0>=H-{Y5Gf<*_RSK1i(bL&FD#u@zED|imjS!teS%;vv_a-1Bv(~MD~A#u zls|xE1g*!}^&#Wfj3nim@-7aOG(>H?p+sCt5P0v08|E6^EUZ@&B_shJ(vh1-J}w}g zDL?}+ODF8f8V8(A6UV61VA~Uqapv;$Rexw_?i7`J3*ab#RbP)fT<6$-XW*77TD#%- z7|eFfn}dp9hNe&fqOr?0>f1DEfx0!Q1%4<^foT#*;3&J749}Pia}idCdu|b6oiw?@ z!;;?Sk?xRP8UrwfpwzEVuY4y(&u)-uZZEh+CFj9;NDw#T`hY>wzxb10 zFNWAagrdJdTHl(EIh~hUfK@k??2hX{`Sy_5V2^r+m2g!y?QIy63LFl|v-Gqwo;(2; z(~yaP!j3xA4&)q?4Z^iKYFQ}K6HOB^xlW1?*FB9pQ8EEV^_bp&l(BtNbIBmY+sxLa zxJdv29LIhw`lzn1Frc*3y&b#ms$1ada1GLnpsIyk6K(LxfLOA~pxe$Mz&2Fr@-r~BI zX?YF^qE5%(Jr03O1Zi?oOa*G-gpwT_D?M@G}99YAc-u6b2|`Pb*9tsZwfb8ayr%Ln1n z`-gw*FCGA_LHwm6M9Lrt^$Uq@07m%*`MV~zTs(scNL1=tyFX3wCNpmrggQ`15r6&N zfce~GrEaKqk&FTW%I7BjYDP7TI8aUXmKG`6POUphR&WOX8j%es-HO(0y6MU_2P2PpuX17CdXqAXbfAcaMLWn^Zn-^TflAn_ZHuK_&#FmL=d zlwVY!-mYb$iX6H0YW`a>Fkia>`{4z{6Ai5D$gI4sN?b|nGKIWo-(UR}JerJXZZG}7 zofj=v3yhi~D-Nbbd-Xe5uDqMlAQQ=GG0l$r%B+)WeFyM2{#StKIyzjJ>GkRU)Gi3c z1S3FyUJY^MH^W%w4>Re_roCW&br9ueh|*#wMaN!=mm*+ANQMZ~qq}Ap(HPg4wNu%Q zz~xbo-T=>;^wRm=%odlKkE5;%9)7;h=io`07NNhf;w1dV!XWu(9g#LsKe1XykC>0^ zw=HjPpXU7@bGcOSoLVPn?UJQ`sphM4C!9F!rr6+e-009Qy4UHoja5Nw;dG!`WtuWt$%>K5PcUYAX3=rFY zTSgv2miDfLtMF!)5Jp)crLgdby>Y$A$}#a*bmqAF#wqteugwLPZ>GmaIcS2o3h*yP zaC-VR=o@-g-zg%hd*fY5%J*X_2|GtL7$}`7j$;s@YaS+^#JAwuz!MzV-jAOE$G_s@ z*!ORu$N?%0-Zh#XjACw#T@p(?JOB(6=Vt0cXvbD(J~p!4;W=&vS7) zNiaOcFqLMO&V605SRZlKt`*2zMbyj~R}^nttWRb?(c?IThulHcGS8$0&ZONvH#m)Y zNGY#})k+rdiv=AvVI(wU6HU&fIV}!hFK@_I`kUN{%O+$;KBZc7Fd)q@9$8s`)9wIT z{Nd3`Jzt^)L+A~O>j9vr5O0VKq)&N6W|RW<@%8u69k!DmtEmhK!D4A_UUrIU&)TW6 zI%|xdF%Xp*mlXYa=bY|ip{}{s9aj?OOdO9l>d&op2?P@h@iG6PPR1tJ#I!uD0{yr6 zQWiEWTpQocBK>S|WqJtbc^UJ6LHbckur}sbnyQ=N%v2^k;*nsn$JZ2p34k0`$9E6w zJ)5d;K6l9FvZQW{y~Nh*U}4?|U0_7+NQ?aU+yjJ_b#h*mhD#?+q7V)mLlXRng+QJB z0kG_KlkmGGe5>*Jyj5da{P%o|ype}O`z2Wy10-I0+mz{s>6=1v>RY0Jh`@x$N!I?7 zaU(~8HfGWuF1q(b4bfb1JDRW-uKzLbhJm-t*OJn%DKo9vVKE2A~vM{q-z_4Mc`ir{Ce|;}D(tdjw@y09}yc@vPU5GbkyP& z#A0`ogR%+G&u`{Fz5$g>yi{M>HM|&K#wVCUZY6AEonRgX%GMcXo?K;kmM_NEiJ)z} zfMuGh;ut}_f8L~jd(p`8;_7LR2Rne?&G|MVz7A?T(${d?t!uU5XxHB@eW1u#l$>yQ zk8-1umvrXuK5NIm->OrGLTyoeaS~$p!T8D7nfV2~2YaYhK-1)`6V^Na8vAJIwg9*n z>lIA&cTl|e767Fn)0;|>^jMmx<(b{F$ zFkv>N2-bOB=(qOtaxS3YE9ACo$Qj^@6(EH%NAp0DN zmnN~|`?Cgr3|83<0ptmfdU8ngAY62JqN`yuENIlK!+p z69S!$d;yRxqQwc3?s^=nI9Z}WvF5>s0ZtGbOiPx1dnIh&^9ldRm17B`*PVOMkQDV2 z2Aqs|-yaf55Go4CANC8%)`mwj8&mndeFa+yP>l9}!A}U8IxgK`qc4a|Kj`f89DCbU zu{qW?7W$DvW&uiJfLw0L(u=T_AW5YfsY=@H9S=}|Ss>(SHez$jQAMT8p3SX3gMR%0 za3}JOksepu54q!zK=%H3lpQvTCSG9`>Owt9&p{SkVgk7A#JPUGKJN;EEhE12Ms2)~ znJ#aC5oHzaCG(!SW6B{|!z+r^kS)kFsHZ3=p*$8~zrfgfu7CHo%181pM} z6tNTAlg34aJ6h}~ZN?Dp*=7k*siv@1`oZW;Z5b6gr+FEkF*(K829sjnaJ%j7VO;hvGS`auT-O zd$M^>BuphzBL_et{WdGq99Di6C`3A7WaPmlh3!cb0*SKTuK}hqEi9&&^sUrUArh(z#Yi7iQE-}Irvok~a(u|k=B-n#D~boCI6*LAzFE+= zOF(S{c=4R%PJeh5m+N6s)fluuH4H+4%5IX1$5fa7MT0t03&D5lpARqHQ{d(is)nuAC8ciiZcQvbs2rrfpKW4GfL@c+4iC&@udBmSc;?@j-^PJ2FU!(usg!2BHmXf<6^7>TaS>9^; z*&PeDbtE(WBtA)<4s}I_UOWMya6k+$pp00s;0F5+=Fs=!^6@}QoLFc|KKh>M86pMDf6GU6%MV1Kbd?kczCVS}61PnMaVyCWW? zxj(A+j<96?S(aZzZWG~u4}Z$yvm~^NH(uE~zvD9jR3;Sf#k@?up}a0Czd91O8B>MN z1e3ADo|pFi2+UF315P5M61D>1*S+1g57t{Jpi}+M@d1{1%X~EfMj@3Cim4*-I<1Dd z1E|m*R03?dC|=qJpdk!6ie-)jo+aGD#7=U!9M~3Cej{?=K;9ch%hPyrgU0J zU(e%K!Z?9{b~^)tZ%-(D7jo60z@_`nwyT@>6$IoR1LebQz3M#|4Ee&RTm4-r9*PjB zuGEVZ0HI3iSK?d+c~L1_`H(72==<~XLqmlo(2gs|oZI*HsYD?DJRHD^-*QV@hw0_^ zC$KH?Yd{qpZsjTPYe*yDZ*P3CPvVYr(z4m=1FgF`2h?(fIh;e7)ra z6g8o$g%+yrgP4HmZfCg^^$p(=v_|hgDRB3`5+Cj4RTnHf?y0+_eJ2!GLNN1?Y}Ofn zLSRz@xY!8I_*;qsnK#oZSN)}+@E}irhLt{h$Aka2NxA@?Q8;lBH-_X&r7+L6tGgX_ zYZOm=o<8B%WC45lUBoI^o5f|4NkM@8nPWciyfAHj4CZ!K?E((SO6&tJbK~xz7wa!o z2#+(}M|db5Bi8qZ;)~OxA=MWF6jO36J@cv0s%sSh6#9;{2sVb;FZ%6#AJ1ujsr1lx zF}MkQuta?nba0IM5pt&81~Cypt`Zm4?e#sTSf!MiepQ8Xl@DC?v_0LK#l;lWI)Bga z!<-{zQ>A-JC?AbW2~E+L!6;WH1+H_5ih~<|I$qE0ie6hrHDhf~5CkxBvpx<>&mWqn8qaZ;;t;QTH5X7M%=eZa@0ClH8XaRvI-05_h zVSbTM@_yTyK$z19aa3@vHhZFQp_qJ@80e$VG%&A(SA%&B0nZbg*QqYiYp9G*ECI&l z+Y(x{GmdqGpz~Ty=*jSZFLefeO8P_zyLG<>{Z7ZDVgHomL4_&wTmRSl=Fn!|SrC_l zyv?*#xOHqW`ztk-t4gYx_><@j9}5(BcFg0z4Jb#Mk#E{;=pt~j$jT#mc=JiMTm5C^ zl5WO603GMNwxiU%`P#lF*77BzcW6C0qStArK5`ZqZ(5jjKR`|Vm^AD3N&W82(ZAJK@2TOGjbgz4vUY`F zh<}cw%F82Vg!(t&qv;d^tx%tzmpO|v;E*Mfk+AOe=yfuK(~MxCD9hhTgx@k3C(QB2 z)8Ti#FfZqzri4v*q?oh z&+1=4nHo@_aqR8XFMikEi`>}>Hr~OD@LTf3{YqDNB@*s`D1;#2nTRma4~x&^2n7(j zPbK*e!J@i!t{%UlifOJpyNmkMd7oLW+@BUhQGBB^t#cQ$1^N*A7}N+vnYI2}GlOTm z#g52-Hs>KF4}rCr8R>v6NGp@Nr$F&sQ;C1`!i6E@PJ5Er_3-zXV^hMW#lsDjl8txD z0c3OoHAlvO6xQKFfI9T-3Wsk=x+`}Axwf81% z03(E!1bwVeTVZbHr?|L-CM3g~m3W$acMoP9whZTg>m-;z^T|w*3%#alT^VJ4fz(cq#BII*4h<#Wgz})MS8xftX)BJT$O)pxrcu=jY+0 z>`qIpM5-kTS_h-5MvPV7zV79T9lg5H6Uj*P!jadQhfMYvi_Aa){)>vl-0w2}Qdrh^$IqI2t5WE`R>(HQ1F2i077rc#RZh*#VmFD~~sp*RQg2zZGPaVnI27 z%{CYeuyY{!0I&V`gQw!>H#wLd>|(a*yOy6EKI^6xPAf;ddi~> zgsDpUXg3yVH#5vm0qV5jL~rit=W*SC#EIkM96@|>mqi%GQbg971adG+5zNSGUX?L( zF9-OyuKj!V(TmTu!(bqWZ(#ES9LX>Hu5qXG(ksM#b~${Qe0MJZOd4}3bjxoJrh#8) z_3LJyU7GkO$(;p4+E>JQLlg@%*BJd{Ip1#sD!-$Kr zhzlDFLVg{`)hyu&`oc)|R`|RmzNdlNJJ0j-B?yV2m(-JO=LE9RXaZxpiJuNK0Sa3Y z+I}2&NoKrW%m4@$03Q?r+`#RBl{26BuL-Wj8zWa7*0T6OPhnp&rH{Yk3t`rGIOA3G z9L*ZR07RxSh=J60hfSvq zB}f<9lYk`s?)@&DlcqP^q7AEFRz?0M8lA@sirO@QXq4rZc%={vmUyavSnfR5`;l^Y zDLsrs+OvVX@g6={6v*q>|G@#^2K?a@NGmYOcO^I-7((8liNvLLBr$SGNRf?6LeCAX zc{Yts2g1AGQ_UV%QX!o*vsmfl=tMPW7c#+HV<8NX4KU5C?zaGr7!thj+0{P##J-WQf(A;wgYKJU`UyE)6waY91`z2x!Ve!e>LTtC*QX@EfBbg5&Gw*e< z{CL<`d8yV7yvH&ZitRDh{QHr*Q1rzbiyptdNzlP0rkQ{SVy7!}m-zx=Y!=`K`9hWz zY{@^D1W-6duoofOV$oG#=$oJ*Y(N~C%9TS#$Ajm9jX~spDAP|3vxovDXqW<5z)`RP zf;v~3ui&fMckLslJa1*jOTQD+C|~<`cE7`@zg6C>j{zLPhieYHn+B zPgRcB7e4`hL|9d!n3I`$@9R`)SV3ze`;+Q`{TT7oOMG5mOmu+@(B=%E*9-Zjz_2-? zXhhxeJ$`wAb+zvADO?cI=Wv=(S#y$8V6~$*uDs+){bD?U$TG>@_x!rqOEAoo(l1=N zr!6D&d`wWb<2I^%hil5-5cN7Jn5;Mi1F?lp(MhPnK<*_YTL7a?=rHFP|w**g}^)L*AwureEe#B z-Ri!o3-81GSGXq_G`n@zy6AnoL`b!JWTo%GYk;9F8m1_RLZ93;Hoz8*wgty>xW;zo zclj15wVBSdmP`iM5&{6$R*Pi*r%i@|@{|k$8$9E)Y*urv&C<;1-h$vCu$#8y?u`X%8Gi+yDqFCrq!z|j&a0)v`LBjS!oekehJLUw(-qu6w4R<(%;=w$Tx+0-Qgm#3?L z(e?p$2IoN+zg;ChQ&Dg~GSC+D>k98>)yXfEMfKt;sRK@aj7!~6BbCTWLT>hYRlG2@ z_U_d!duE(6DNGr=sX><1CAv&dWuq`0V61We@eo^A91$p%#1M~T3Ek&wisrRSMJ$&1 z!%^*X`ZeN!!nGhM2QYfp4I37$%V=eP;J90HPXn+ojyDnJL!yHPt+K@ItmrHl$O57- z{PKe2HB%1;C@{E`ZuxO**ZTG%t^sTEyVpH>0XQI?;!z-FZNx`*EiGND6Nih9O(L_15DND+e);xaJyNg#g zZD9n_JWhZevh`aQ`zOT7bUxH>8#PMg$Ib;Lhk^(Jae~fg^G$^@MZq}gWN|5( zv!FvR>hY9rVPa4zRXe*j^YggOfXq?gPrVo%`g(nhG--Mus2auP5yt#~IQ%bwyD0xL z#&JjV9^IiC$#vH_idgVuav9aJ(Z)+!Zn%UP=A;Dfz`%y@>CzQ=p7IIgPMSnqW!|2Y zWpRe8=<0_I$|G^@sd)p)s$xIngI&B`#d^Sf0Ziw{Ds?y}q3Np+YN6=~Gb+=5cm~R$ z?N_22ZYO$IYbUc(6-!!wncXrnob1%W00E+>%lGFa_Qj>w{EQ#&_hxssUp3)Q*ioeV zh$@E98pn^8_3GlaBMbt+LTj00&iai8tuys=_M&x*948WgJR@`0HR6;pVHR*BN8-E4 z?N_T^%E8YjkSItfTrrCkUoeA!n_!5A}JhCf~2D-d9RbZhX96O{-GGxz+y z8OzQ0y~fL8rs)md5QnZ+FJ{IMb-Rh`X;5ak1tO4W|pCTYP8IQP$gJfc8IlsPE z5Cooc%uTpHUXaeM2+)gpGUV(MML_Pi8Abb1t@)FyxkzB{uP0(l2 z9lj@cBHG4{fDiUKG9w{#+@v@^<4i=MY`_?8oUb0zbqx%Ei_EZ%UY7q+RG?3<2A@Kq zuu>pDEfoI(-sSm>$qU;=4V0Z<{zuaxPa?xjVEQ(@#Z_z6YtT*6xKaM~CX8>>EJ^yo zDY3X2%#-j(hB%K0=vKu_e>Y%ks4x0TSQe0~e(rc42B23fhWFC)`tAb7#DU`n#HWQn z07?;ah0<1klA{7Gi41Qm4Eq{~F zD-dwQzYq0xhCcSCP!L+m9aavB*zx`M*u%8DK~~CopG1sNT9A<+l4|I4idk2grhg{LEw%xXE}aqs5W@$|WHao&25v@q-lOEO1|3 zHumCH3EbSEPjz2sNUA7y9;dh~*>n^weYwnVg|E0_#)N$p8+~ zB7}r9>)sEh&QNN$VKB(j+MftBcw^u#&&Cfqi1K_lyKQ)Zv^yLv9~%Gt+X9o8CdGjd z=_WNGb@U}lC5?8c8$0NIrQd(5ex58{lvEr*n&rl;t2{AO6OzVfU{_isLfWjX(!v{m z=pc9v(&)KFa%*2zXyf^-j^s1Q11L z>Anb(mzhOQ1|~|{GDRQl<0l!MOmI-3mCG_Ly_sTt%X$sBuRhzC)RFh0VNds7(6s~`>p7N3`x*{(Z6aUAxsCn#wL;Y zLhb#i`u*SUMK0a)My(^ZaHh@f6h{g~nJ0aGwS!I$P*;CYTBbmDeIh@Fc+Z!Ur@IQ#;DTCxtvDoQdrkf zY`c6DNnW9tJ0sh6;xytC+IqNzQ@y70yVZoH`Fs%Y2bfxc?m<{N4pcCXf(;TPe=Rj? zPb#4FeMrgJRan5@?)#eN@+}%9_e_Wft#NiTwOn?3KBqC(X{wc zjQ6KTjo#H>1Nvy}SC=>mL!T*Se~NL*?%+VD5jzryvz^`GF{dCX9MmO`yO|m zLhJ_W02f{*3(K6|8N_xjKRF1z0v;m`6%&GlAUZZqe?*>}!}41}_-R$H;sm~2Y^FK{ zmaU10%H0x?Z#@4t1a?Ky^p}cp+uQ2HG{61RKmO$bV}8lZ?j^~Q>8_1Jf9Z>djGbAN z#twBIa!M35EivdH1_=Ipg!7THG+Ov&4_~C}%Rmt*P?bJY&S0U3~@WRnefGM=-q8Y#G*@m;Zx!4-leLXjQz3Jo?cPU;L zuS1*)C0LHdihqdUJXDrh$XNQmPu_SEDE1prk@W!d|I~b-saAW*fAK_4gt4JoT>9pS zuN|L;RH)gxl>}5DCJ2c~I@B3cK2OJon>pHmr^X4(iThq9CWmk0d^^9#A6f8ynn)Q> z?qYlN)s^-4EIlc$_iMp8SPU1s^{}4_U{K164w#Z`l;9srA8r_^xW7B~DI&kEG>0f@8>?G-h8E5vUg29=qO?7gIsLVW`s&;R@groI`37E}vw zaB9s#*Ppl6d(@58n=(PB2neuLXr8)o&N%u?R`TBu865d%e>?ywsM{zwMi&=zVG$oF z*b~C)IHG*4Sl94P0*%4h=?Eix1*nYZrz-cu&VhA(4b6oTJ0oP@xBLNK_Y|rINVTLk zJHk7FGNe*K>%4e^$9nYazr7UqlP)%B*w40oyEThK@r}o`_EI)l%Ekau zY`(V~Adc5se~^Lhao5}$|8aEQ>TW7w6n!8HK;)8)0FiTt$QeZD={?7;ZsG4*Lc`4d z_gX@l{<>Vgza`i78juqKI)0aqrElc!a%+LS!wv}0Pb0U)Qv$G@ z*K4nG)|lql+O;#aGqT3-RYU7>!MWeSg`B17bMbfEwC_-WH-(GI->%}pk6S!1L-6e} zthG<`e_B!(E#6t6MkTK~zSWu_0@2-^VBaGcGEnSnD1&Gzdi|6hG!9ep~Fg6>?vJLEl=1t0O;(LVM)~QL;mJ zEHVECUBK4&?~2JtEzsE_iPazOUSfaeKv_Y{GMmNy{cfoH5(HpMgQ17}`5%Q@z_OcX ze_HC{46=p*WhfacKcFuQ#t1T@0-^*0Wgu^H;j3M+h6w!gfzN;SU(zlTWxAK1I(LJ<*U>L3FIzpL;j(h`b zqo&`E0tbdMEs8plD+uK}ByDGm^E~d{T(lp5sTg%dJZP*DpVh?HN1W8?C_O$CLBFf)4N^SAl&F(CpPE5NV zj3hr64Cv-i7%)YPFCtxM4~)zf0GrCIxoL6IvJZ$4%nLue0tl1Q*8TKUqPR}#e;{LE z8RDQ+&L6aU4Q>Wh%xXsri9X)ZfL*rpc`--Yvq%)-g1~$sr4~!J4TQ(AHU0B~G6xbO z*~U;5*n%hallltzx*c~(I*4h9^6bA5hjE2U!ujKOlW!a_vSW?WO<4ze$&~AkOaSCb zfIy2Mq{8(Dd;ctZGsA@#*5>@we<{O+JWDiTX1C3o9Mjet`^Hv;g-}FLjswqW3yj5T zDx`1F>}GX>KmY+%??w55D`6YRONJ9k*BU!t(70rEiBZg8buU$#(&6E6klG)8m9M?9#ltrm#F&w z+-Gce(8nH53H&R2o>5`m2O_vgc;rw`bZIaRAn-UgzHCg`BJ41NFq)hnMMB~cz>U7whze4Yi#8LJ)C_3pIV9=xEih%%uk>hnQh!6e<~v>=D_zQD#SYo zj~UBL5`ZIBJ!EA9a9?KVfW@_tm!H3ZRFm1cc9kAw`D{2pMj(QX?9!|~j3 zkiW4Q-2l@ws`p*&e|8pcBz**7!jYK(m!T zGW!6$#C=aW;YtgQ>ITluEVO5@9udTIfW${u$vN3ey`}Z`VXm>X5i?Cp$+MG0SNZ3D z>E22SLvBeQATo~u*>iNpf7%U9A=2xHff%&@a+p7zL1pC4f5k`>ycb}we{m?1C=p@@ zSGKnLSu2(4hcgS#H<63;8`r~Vq{# zr;>^TCF5D`@2oggatu%G{ypf};_i&=n^m^T3SKu4#M z9CK8pl`V&xFmSGHy*-Z)7#W5S{v9$G(Xo*{ZyW)uJPyDSYd9$yxS?j-r`lJuI>0uZ ztM45GR!a07#ToXY;W2Bv;rd(-vV&UcyJEi}KP5`*xEo}bvPHY#$%;VR zzwSWle-Yvai~P#Zqk4m#fdPP;ZQhyAMb2@Zkn%mo)73y>?mW5$llr!j|1`2{27bne{?Ymx-s(s(iNh?A5wg^Bhx2c^oA6o zPy{h>vP7XR6keIa%sxM|i9tD5ZiuHs@(X;7c()S)4v|e^ByYVP#0xtFtW(X!EY3CW zSBH(0t$6DMCQwKh#U;c}o3w1KV7(HBeFv@GTjkP!=-i3=P!U7GWXLDsJyWiQ%2&S| ze?wy5>sz;KA&g%56P;uNlOr{O`CS#C~_%U=jkjhj{336yoT z;4B!Y@_P3>HdjdXapgUfGPr#w1JabcJ6}|hB-xj>mCq}^-D`Bzv3(~5DD3A%%U=6+l3r^ff9*)+&?TPREYMVhlJYbWroXa>>|F@QJuBaw zcQLsR*&yY6o^Bbh(i2xk*|I|RsVeN9WQd$>njE7l{F;w%XN=yxWnYeHM1IyZsQqY^ zX%_aROAjoGp=baRpz6b-G@q{4pUko|i6eCfY`i!Pa6ZV7K7@_@E5}o1JL3>(f9bnq zPWdh@FrMetMN>Hp;3}y5V&EgEeNrWSE~<%c6ZYW9cNRQ$_h#S{aU`x<_hh}%xMz=? ziuFZ8qdqrvuep*Fx}6CC9JxBJ^tel%c2}z%KL}GZ$%5XW+H_2E=3n*x%_95Z#csX* zQ|#N0^u@)TK1^NVz+63Fa-{`PfA-o2LBWz=MeUAp(u9;H)z>|O^l5<;{2gF<8<1~i znZ|mK^u~+eA2{KXLIakcVQbu*AD8q7A5d4>9$&E8VFEC=N^0{-uy zMf0?ujMNc4y*#x#*~*uKODvU$VJv{;e~IOe5_2H@XH#utq!F+TUUlgFXv1WUjB8nQ zdxGUVkQIt0EmNT$UPQG{{j{_-+c}k-`;$VLU7oo7vG61Vwx#0Mz=pfI*?w)4;K6HA zv#E$|V+-ZF<{CVW8J~BHe=NTEy>Th);({vQUEf zMQ`>nu9$&`s|=ClqtBpy=+r6Z+D)Je*x%2jd$C@ZBLz=7n0jW)OHFR2Ar$Wo3OJj9 zyAAvN*n39gGCz^v{&}}GU}RB3BJKB@N9v!zgrBpbj}ruX%N3KXf45*zsR(5yLu$y# zG_$_j4hUqz(p=pgHxzUBT2xV)$;NtrF1C$34LJ1O(#^`R7D99lEu&yz{@$^lyfFZq z4o%7tF%|gcD?5F_;++S>%6Qzn+NeIkceL&LyYx z5Dj%;l_}tAhmHQqukyRA9LBPr60vd`t?`+GEre$`j@TJ}f3G?5y zr+L}-8*`K2f3ud(tSjL_7eE+|ns!s@)n7ub-CkOpaPB7Xjcb+VTy;gq7qfj6HSXn8 z2BQu4_gjeXX1cl7#>WRvnMehnnV%ZH?sR`(H_keft_Hih_pDdR8ZvB z!u1(2%??pE5)dp<6qHthIg$aDM_C9;kj{HR9+En&_2f`=O5>0V?7T zob72tf0Z071-$_s~rZ(88tV ze~>Kvw;DRA&*NS0)y(H3rO(Dk&jw;x?d%(WzDfgD5)$Udm6;3&Owh+tT|da?f!xG((iqmbz#lAz%r(Sx@J@ch^#ckX8I}>2 zuK-zexlkXoo5_pefk}dOoBcP1R{qehf31)N2``-r`sy6v-(JyKC3ewI2+ZYmupF>%0GF!G>5W`DlBD1T~JRSd4RyG zG1>d0Up5i&A;rkF%|CxDyGB%2uxKHfApe& zv!)%4(#g|#m((o?&y6;Pi(?X$C!X;_N=&}7J8j#F#pMvjV9L2mmL|W~i+sRzrIJYg za~md{CW=BC89Gjnc!SyW0Q}K@+4U;Xrr$s_i{-PmaVdS&xReMgz$2@Q;930sz+K}6 zCuV8O7kxJi4;F7wz;LX-yk4P9e=f*(j(Cyig#jVPRDuokT|f_{94kMS**>ao3p5Il zgTSFO$a0t9L)C%JwF1-83uLkLGX;RH6?5S{d&DoiBmI9?HfEqq@}~Bxd?}xh%6O?& zulnp2M@*$ycc)hGTHxKTd$klKFc)L5bJgC6A`g;(+(T`^_I)Vx6uYK7ql?V_W~3C;MHHykBfI~NXbnlf2i-sX?KE;Klg7J zSUXUiFQM8t?o-4wD9}+5I!WTgI+>?`h6nYM-g;j8kIVA_3nSsa_+GravWZNb0G}}^ zp|sur=3wz!`<%bP%@$~R{CJfJskY-iKfrjF9V=Kz$g$p4`T~OM4BYy}hq#(8(#5jw zL+#qr!WX#qnu7v^v=7j~$>p}K z_RaR9^1--u_RXjLDW87v%CzDX377diA|*NItlw`ZSKy2Z^IG=TBXw~r11oN>&S3oY zf|GJPi+5?j05T^ZuLxpzEjZp@;sv8=wA$u^Qb?sZ@xpT+7X!XwASo`pLg^fr{xC}l z+K;G_u-Qr-f24?QqkuF`zwBBUZnstK+auE8(?JX_cBSJK2S}i;?@XAx;`|ZN7@NI+ zY8b&d`ny5SQDf%qh~~mk5tG$=_ELawvYv0?XvYHUQ2K}x++Npf_VW<}h_m?@e*==w zOP>6WTJ=qF!TJpcX}_}M^P)aCX?_AFz8`%@XGbcIe;?67lHvH#yO=`d|DzEnE4%m! z=!=C4s25SBKjpZx5!}rJyk8$aLBS?S0nc%|#Bo}lQeP~Uj2BulAJJlBO-OK~GAO)Nz5YOU9cgc@1;dh7= z93))mf2|w?MyLtkBGA;8w+DTyPSFXP^MPC#u#UdHaT%42N*<5jX1S;hq`NS`npf@0{~Xgk(K}?Y2=?#pVgg-|4xJcszJ77YR@hR9;wruP({LCd{(}8 z&Zk}Ve$Cpo5GlK9^fcTtUs)jo{>wc(G{;tvhS~fQ)e~U1X@%TvCyQfQxjAq1eOu;UCcjJI@g1cdH z1>1fA_X`Ppxc`!AWKXA@$nTvHdAkL56d9)kMusZf;msS?HmlU zbdo+K#3pK>+_}t=zu-$aGzJhiZ^2jPbl?%CfT%Kp6m3c};X@&p6h(e@*NNt8?2 z3h7s-r1mIKri@}2Gvzq->qgg&kY*_DvGBt4?Q1iad4TfTk`wor=Vo7%`0Nw;jYne| zOMhW8c5epY0v0v;3!%k9dWi=te&z$Q;?VkvRO+5%RU)NPAL-{c+3wV zy(d}v!)E}bqQ3OyvwelQ!Jd1YqRuMBMvTB7CF97n@uWgt(6{Hf z%7SsRc)#;L!y`edWUExxw!AHQtvJ|IH>h>ygt5|>uaPc~02p095-Z^ESXtU*dUcun z(koDJV7!2FyCOz=4<}OaT6~liD<+gU-{&V0D8app@g&Zlbd*2+e`x85o`CX=>gfo> z2nv)(en}PpXQm!@f7u7x!sf?X$rEHdL8uy!KDTG!-?2=ak0U{(nH6}(eyxwEbms4v z2Yl~V-~|O6H&IzKBInTD(^sy2n@WJBtINVPh(s7INH=(KV+dXDMy!^9J*t#8#rato zu8n$iv%TolBKrvIfARg!`_N{Nh8BCDcG;-tgQ|wubJVbt`$E)yO})6*l(xPU1+-HS zh8m9i$~Kr~@Mtr!)Om)HJLhA>almtDsh0Ws8JvNm+1Z-0;D!L~ymON2;MH`01XN(F zVsFh0fvLh6F`kJ?b?PQ@G4|R6#JCcF1rrL4pzNyb3Tddre;5=0BH(WT$1)2t-~HI` z0gq~@3TK5E+7~ZryDiU^y(W+91QoM~rx9h&i@|c4EEtqr|oDi7d{?A=MD3 z_=>8OZL<14e;dEDo!!|t_YO-;MSo-Spw@9Ac(ZAmHjK0u1XDO>{5T{CQm9XQHM4%` zC;($K*Me3Wb1~H4@V7fH((v7PaI;pk%<>sc5i`dccL#A-X3aT3r_?SsXifb6K3fWg z$`vt_%6nIY!MXu#oT(P>yG=U#D8yzN@kr3M%Ct%=e^S9ICL70SJ?e zJw2tKzH9xXe!vAKkDvHP>ka`UMcs`jWRi4;=Rm!cq2C7+VF=iq4A9pVG&bptxxng8 z<@L7=e*+5v7=AIO@L@E->dGJfd46HBZs3|*P~l}MF{f`eN}e8sj7@8PoiKmHjT3|h z7dH?h#coB5}*3}m(511sl= zGYyqb$K*q1#XJVMJuGxos<1_%b!ErS2%BHmy zZRxLEF=knC+W+TPJ3pewe>F)C@@q3AKr+B-VlTD@$6F;XpQ&+hPo8xB_4!FsTPyuF zr~3~mA0Nr2UosiJYiS(^VKTaRf+}wl%ZV?T42_Ks)uy|F%7CMy4Ca7L5xWV*d+fF_ zf0XI{CWv{`M~!d9fxGg9*j;H+O%9aPsMsi(aF%H_^GABWBkHBd`-uPE)$3Jt?}d|( zB6gAbV~6Ml8WLJrg6Jk`_yd*kt0Y}(mb0OGvPnS7X*4Ivg0BCG-xrsQRb0*UFXi18$L4BDml8cy-!6HK2wBd>wLspqfM84V;Mf%a2%lQXsZee}F@2 z+Nq>f@6g3*tLjC|Lw;?{H2Fj*^pk~30-RIs?ZYLmUjC&;MJFp=QocKT?1 zHM4);VWSajJT@>p4N1;F3=QHmB80$?ol2GPVNL_(CeYeg@$PsM-Jnf?=brv7q-qaB zywYt2nf2Ax2ru5Zo>vxTQZ?vJ-dKL9@n`3|2YY;WnNbeT78${Q7eM6He?IuUhqqUS zDR;$06ZPtqY)G0yPY_O>`dwU4g(^1 z;zJ}`l_y{{RDZ=jPitlQkzS;)@C5*Wv!EO)b}JVU;0(l6kUr4v+Mm#uYpIe--M5!y&+&7qi1Nvnh(xf8wtTk)(LZ_1gunifcX9GXvn) z%%**}BPK)AxV|^E2<{ero()4&lApPvytB1B608D34o&{3$Gi*70`t27(r8_um)iG9 zU3@!f5L!xIKr{uL@$a$E-;2KR1dB!jq)KpdDa=rYd5NL&6{6EifB#UxNG=^%>)j}r zMjE5kQ|YL21UNIzsDNn{{)4s-1Ot5p@)>TxqzRCBS%8Y?Xa6c!4ou0x53~W5a6Q5T zydF_bM@kVG2lYt>&rLPYS&S@UT;kjP*4)HNKowWtZ)8s5bWQ*9zJ%7V0{B?i6y*Ky zz3w;D+2@!@qN>`pf6B`f;ZZzjs&451h1xI2u_ZNU@ItKv^pi@xKRgAOi3O*!{RMyb zz>4oPj%JZl&uH=QB_9byyY(sYqYEwy^pW%TD34CwB`;1!M<|rvuPenAs)JT+S-e$z z)WdP9)x3qrvq4`2<`nI~_U<5YqD3PzNN!V}{BUkE*)U_`fA^x8MH_zoGmwf5O7ueY z%QmMN$^xhxy%aYW7ob)W!1y88qy*0O#3lwj6Y~(P04|pn1iG~n$#R=286wLh0l%7J zh&2y>NHtMZ_O5n4zO7NtXb_52x!gndDjN zRf)RtVPBevRRZNS7=R5vf6EP|*%=7C(2~@E^GjkBw`OL}A%2bKZ#uy!*og=(x7`!c z`w2`GGKnQ)VLkq7yl6WohtRbeDAi=5WVbeF@9KNqf6oPqwlV8e=rb`R9bGnwkuvku zV%X;wCg#V9=2|W2e1=U{MyN($IzzHIhViXk*#{uc3-;04)!+Qgeui3eTzw1BB23-H z+o?}3M_>J>uxaQWB?0#KY=_A{7R8i&i1)PU@LLCW-#46|W$82@xOnEsGn?iYt$O-i z+)6{Te}v8U!?njnfe>IH`nK-eM)Wj z#TT14fuXR}28dg4rU=GmMP2E`yPY0 zkg1#diWRyay$_}NyS0TtQlj!5_Nkj#(m*@JY+EHc!_Uch}q!Wb)= ze=MOrMp+qZGPrb4PUs-KDKoqYS?`>kf5gwvyFM#~bNd*F7J~*I(Cg|_>Go`{t;Xc# z)RV%A-)Kq>gq!`KDfyGYfBM~^l1quw;LSwbfE$G0Q{?A-c(se=y1IBwaU*WY9#!Os z&k!WcpDUyREC_u{pq-}a@)KE0X%I^icKPQKp`Hu38bPzFwIKKY+QeGyG8xkTe|Cs< zSHdr+tx(0Q&FiQ^EHF;9weLA0?{kv)8PtHe)4xSBN+({s=DieNN>&^YUidTunn$qb z)XBANQaVFw=i_1Z@gacbcv{GwjTm5s$D05Ii0LYLyqcwJa1C|=S@c6Mzq^gh zHHbgwQD~D)`>R43t(VvE7hp!=f1)Hdx3+c4Km&GCdd2G#)iYk6{a`!KrxN5TcCFXY z9ykwpC`^bEeet{r;~r1n#*)*nf+txL9mOB{1$w?{w<9|pUv(bEd!sXLuJeuj?mF3c zeW+K?$n{i3HBt8~Oba-NSYbM6^DCgc7`UOpdo?WU9WJkCVSv+uN!UUbe~gf$@vFfb z)d*Wqn$#YY9o=0k#=VB4=YSdZBE03`^flH?kdncfIng9K4CYQV1x0ZDYpxh$LW>?~bCpO$K6Y74?ZFNJcrN)f= z+kK)FP=#Nz!Sp*=(6|O!IVX>3fc%%~75URTe~$<4#H7=oVdBr?fBN_u4IAF;7j>l0 zicLcZeWHS6a?$YTyNZPgG%u*B%W+)&J&p*IKU&-7#BK6L)~}?^3Gj!;j+5>laFetT zI6+=gb{Wfk0~MkFEp9h!swL=hcgZ#lZ{8zwO>!Pe=+lk2eVv0qsQI#OwJ z@MjdfasUM>GSGkz$9J~`%@4Fol!!FYgd}>zSS${JLYI0q8C-WCtVoRs+9= zui26Ko%hu!9{>-WK<+tX73cd7jE8+qJ@LA#zi*j*zDNzFe_iSS=rw9>p8yv+H4)da zSRe(k4=W=+=p{Y3!?dpkJ6#Q|Q~%}jxG|1!Y>W7-?Dx%Z0&NcLDQlP<7}A@K8j#To z0;!{r*mne!YQOmjK0f&ZQ(OHihlB(NyANY;bKQgWmlh&Gh3k=*Fw_-?e1IaaNru&2 zdI0;DFHFh;e+4x$3?MO0x1SJI^A-Vby({ZrTKI#jL28W6*P`}LSm;9CEkV42G4=6Z zKy_ijJaiQ;DEot#k|5)+YX*fi7be+5!f#N8A-nZxDB?Js+*yj*jf98e+2*43S&uF{gJ<5V*%R-CiwOU zJ~t#!2$bR^Bp$dr)wTHgIkm9sdlVX`?0*oWdm8pogrts&J1fqQn%U_zSA)~zUbi+$ zV{qU$WAJLq^ip4Sl>pUpyj*+llV~meC^NQT2uI>ij}7Nr2)8sOteO8)bJ#Z9P*DiA zVoZR0e?h+EZ%4`Og0A0<+oC&`5y^O0oxMmq!DP?Qi_htfQOR7z1mv%lW=N>nF~&aM zpWJs8nt0Qi)-UF@9)b);L9kVE?`MSQ7rih$2t2A#=sox_ED^UUOx%~V%q-R+i9B}O2Rte zKU+@odp81}^kwxB&MX13?R<|tAcvffM#DuwE|#bLFI~Io(e#z{2BSFvNmG4fyD@&a zA%1Nx%y5Yfb>X~KVA|h*3yE72NgK&`f2X(wwP%_@4|uUq{VGwcgmY}S%A$akx`m;j zGIZwttcM7QqKJ!e)th2DaxZaU_5w#3Fx&S|ZH7kApi=pG9r@1I-mYmt;64*HCUo~T zwQG=%@!Ly1Uv>?PZ4v}pWBw~3JXQUk6%!C}x8vJ;?FS9p2rmtt*n(OxR)5dmf31Y| zCZk4e-E)->O%*_yUVA21fo9bbH29k?t6Mm~t{fQGNQ^M*`ed-pLsvJINe*n{z-KVY zi$8S!Rod^JS4ceh{z~_y0s&>POUHVEQu-FC*=8E&HIv$s(Edw{C9uh7eaiTy3*vSW zHoMr-Fa<=Dc*A8d*x|LnEsc#|E>OlpRk)=FDs)_kFfE zeQh1x$>05%fQad8BuNWw9f1J$=C{1nE z>LN(u-%x3Q=u&g)9mMT^lJ9~NCdU0#W>_%yXwk%6VliD5QPkeIn@gk*18@P~&t2IQ zxeRmL?JiasLAzWZh=_<|saZTjDwz94Fjf{8tx)SbCBLQxo328dI0YJ34^~mw8&+2y z)pw%ix$v&V+{NFE9E8$Ge_3pWV?EAB1{ zhDOhnz$@9AmlsgW*E~e-1yNiMskW+VfC|7U$b5}DW&&7;OWig60);&QbaqiJvLjyTgqRr^ zIpc?J*UcP$JWE4eSY$AEW%`t5pnFKo`^ApIe!I)o;J3qntQi5qsU3gr>l%e<_GGtT zU$efs#^;*Ve|dRN@b*f-$?o)|a~lOKMs#Wva0?EWhs^T0ykHWfi^oIgpZ|?A*4SQ0 zGc&7w6@#B~Y^Y0P{q)3c@2r3}UOftf%e6IMHX+n~jTZ)V;aiX52RnMj_zBdvaMBW{ z^^q<)*wQX$`QFa^*kdX?&kiVY9*@i})5EcqahN>@f9n*vTWE2K>4RIr?JML%2>SSX zriCNB7N~vXVx^<)A~F}QOnMRWCGOIXFYk~4$ua!(>0hhz>^chKN6%G{MMDO| z-&nEKf3bqq9avrKTq#?uOxZ0m5;&dY&@M-u)9ryD^l7PZhorV{4fDB=7Y_f>vE;n! z{_sS-u4xi@l?$69!x`u8&Eb=-eUjz6GMIS(>gr+NjEfGl2BP6Lqn+auruNfwqm~dZ zt1XX-@o{(8l6OYwoZ^(^>r}DIkqt0cJ1oep|~y` z(XwUYnSHKMqH6hP`rugMy0*J39^I`=8yIlXAngKd$8S$Q;{IcGl2^SoYd ze~oohw=@R9B^v-N>&^?2+BKY+$3ioA-w$&_$bz{ahkWP3dq3GbnzO>DQBj=!o;;;N zmt=ynsW3H6K{RH60pfr^cQf|VKCNMml~yafEJ`y*^mq{~m8A5@?vcl)BO|4NWw$(zI6}a33VfD5b@Z# zrp_@C{9RGd_UN(jfF|KMU*T8ZOv1+DfWKj&D(7hkJZbiw>2M+|3QmqRm`kS^o)R#> zy4`>*m5;uOZ^G~w=pg6f@OYsv0;m)caG*hAsY@RnAV$ao%IRyYk&Uka!(@zo9>^6{x3--kI12;HtMtdx zfErM_#=GyAe2@Shwcm-|R2JW92A;4E+r<&JFKK=~o{zo#_p1PJo7#K#ol4?9_3WcG zt_AH!sKLqIAe$a3Z5NCo_EA;if0v0TlTO;i#OR2+E|7}W0aww!-D_D?n+Ob`^Q4SdADeHLEeGmV#j)9eu!+j*%N*nzN<9U_DNvF8PSXkeQt<>HT}V zJuC;H{Nf$W)?Kg*!RlnO6A-fvHVrPw@j?IP+OGqMUe4z>PIR4zhwo-Pf9g#(W&q@8 z-R$#~*HhZX&pbv$J|x8t&VJIh@zm=KBpva0Utjs(`(~0YB?)GmKlHc;{qo@ub`eO6gnSp&rm5%5TzavyxNdPXx&vk~%qT%OaVZT3CEO@jG0@6#Uavv%(Mr z4A6bPnw1MCl3kg$0&!c^e_L?)G0s;pQh=XXuVJoPw&maxg2BKpL>DWOw}$sEw*Q(+ zW~09+N*foY$*TRP2jB5~iRn_%cDN}FSoE=9lHkz3Z(Be^oMLbCxHB0E01u~By*=sU zIejwkz>x%+0u-q<*_!3)mC*B)Hnt7s7lT{n6J-UJ3O0>_}sfvN}IxBPrK7Y{fQC{y(BN*i6Q^7Q0d&y({6BymG zt`W^%EBkUC5Jy)VABYG`kg$;7i^Ijo+DQ+4Zl+E@8DN-^dH(+`%X7QHg~`hCae$dn>AYJ%=4eIp6tbuzH4qATQ; zr=qma#9PT7t57_jy3a$~Pf`crmnYY(d~pM5fd1_YNYM9Df4kC-;;h)f162qC1k&~H zkN5Vm&oC(EUDlr){yb|0nxL-TXR$D%UA9}LxJWHYsK6r1hVOP@rw^b20baIASQ(17 z`C3RseYLk|4(O*9pOR4h}%$dli zc89WxWWZ0ff5Ot^v@iV&Zh@(Y_JF$Nxu+cg_BnNF`5x*jT4%z$@1%VY|rGC&#SmZv)^dlV{z3y6*OB&d0)#TjwusZY%ZUHT^h zZ?~Tkr1|3M>b*v}_Cbc}@LS#M<-<2X?u9aCjNLZ4f8OpY75&fOiw}t$WLP@wK{@TD zuU__iATp=zeVj?u2aQ4ewTnfG+FY0BRQc*%FeY>QSC0Hp4=2%l4Gr`ymzesr zoQ_DKuK=D&4k@hy!s7iP^Dv{Dhnnsi0WMjW&$g$Ew$VwaA@zHBU~l&&_2R9M2N(rA zLAmxSf8Bs^lNjH4mVSA_yP%ivQxR2w;qAMR#wjvO4{e(MhMyEPuYKv%a`tzooGMP- zAI#C;FA19N|lmvN?KUwX^^HZf6JSj;X z|4f$}hV4?(s=THM7UpTU10g-m02a8yNsl^wf2CLZ6dpYb9P_U!mGDdiAk)2i^~-tq zH8bLaZ!&H2I4&oqd*!Qt>0dz^5+E6@uNdj6dWO zv_rj&{awC46u59mS)g?DXAr&vE ze^mx20Vat~h}rnPHNC$o0ImFlYS*Kh1Mj;R}O^my)C7U;fInj@@B0NVBC3#4TU z`vimmQ}8cFb^KTvsPEGZ!4ywuwpFPP^G7t;mrghA82N@VyVW~sak4XAM8-r5+2bIa z(CtfIwI@BZhv|#vv7Eu)EDPM-X^0lse@O4jdVjuYZjx&AILk^IV7|;j`d#+T_HX(h zM`y9blaI8@SH`D#xW42Ii79#yWSv&e?ZL` z$eZ!3{YbzKc_Hcp`{T}G`kP3`3cyRMk2HPtAbFbZC@Kn1g048qGG=O|Tl>TmfNIe$A9L$mf5+V!ckZ|O z(&~t{DfE#UG)^Td%&hMfv?wR)`XnUb)tkl{i?kxqxzvwtZg4 z%InaENz^+O5GR}7An*v0^ptf4&5W@VO#EOa$u8_h26+ehv=Z4X^OKj+(qEv9ZxOhh zQ5c#B8X|1~LI*{bSs8XLf32xJn=vl)3T@9&q9WL{Ve-4F z$^h6>4CN_6I`t=S4lLCo0Cxe$2m#EBK3;TkMF5ac!Tg;2_Bh`9GnYf`i^a8E+9XHS z*fvM+0m6!m>j_3y1|V;lL!zwn`QcQ0MYxF=o7$XgxtdEl#uz&Ze}JlFW4PW*82Uu< z;fLn3fc_bV9lc<<*CTX(lQ)aLk2OUiVJr9bI*-UfjuQTRF7{F8AW-%!fm;oQLDNf+2Mt7PmNlz!4f z64#kslj+L(%4FvcHWb6~eGGo530DAPNX8kD{`jZ|be;Q0`xp|i$^6WPL_j+S> z3E1(*09PSD1kI!{n`|zkrp3N!ajfOUMsX5grO_@W0LYd3TV@AKX{2U9u7`co#02!|&s=oHtzogXIB)9oB8o69WIj z+o1onF#S^v{J> zN(JpdJfHb8($0{Ss0Ww<7lvcs8S+CCzcl0d+5Up@rVZ`W6U-q@NZ9M8liPjboj3Nl z@Crbyl0YvSGQ8Q!_B0gP6o<*yA$Vejl79W387JhS3nRf`I|yCw&<;w#pUv^cg_|Xd zcn!QKe_?`yL%PWg(Vn-*k3cE;A*_l(4x^}@ngQpec&-UKv9j-~{M<^i@NO?4^WpHd z4TT<>{Tyx0IT30(gY7`h6xaG0dDX6=1sFLF2=BQblGE~E zHZ*19I`<eR! z)~;;$!oI3mQ3>;vkHzINThj zuic4|86yItfH@n2_`E` zI0)9?4#s)^Xjr;E17v7Cs#@2KheR;oh@C(WkfbQR^>6Tp91+k{qJhwXrSkzh(QZ65 z<=T_CbTZPec)iYWe)iFi>-iu92!FU=SUF%T1srl+e)Ln0dvx)NSJ0Vo+Q);qMFu#K z0l&x;hkond6|fDK@F9-nxkeXpr+;4(hCuE6xe_-WDDje-rw|599g`4 zhO99H-HCx0(51qK#4}*h56Y{4%jS6tXb`}g(>)liO%Je%FJP3<3TUuA%YTPYTWFKI zF2C84I!}m4#<$CjAn45Vrj5x3(N0iI^Q(z5S&?`;J+ zARjiNYW?AJ1H1L^PJP=X7KqE{b*ogTLQdtbPpixq&(PcJd}RC6>uJB- zW#QRg-Xv>(dEjH^=Fj|$ynnv2P9lPku0`L`7riH~ujQhC*6RBip!1EuF9~@2!xTZa z0P>XX5nNsUvWwd)UvZw%-2SZU9ncNm0tmTtikDB`+MpsSo4>H;NkM&p) z`1FCE5zNbS4gX`TDd$Olj}A3za+@FzmW%m1iL1CR{6qBL{`GV`YKP)?JDa(j$CjOmQ7ncO ztWM-j#G|A^`C0Jyye*Rs2`68;io8&F>jaH7f6T{vG38ZPH7EuEu8H#E(Y*N1@p8=# z8O&5U7n*yyQC$JyOJJN(A{Rgh3XiP4*1zF+y&0Q3Brh*vk1Ano(S=NfW8 z7Y+3g2mX-qh+=T;jqOZbYOR? zm>;?E-Oh7;W6{yxLP(7{;(<@t!sT2s+faCiw{fWyb($UPpl~keMM)J9W%C#N#RQ*( zqypcu+JAQYws_I`UPIM3|5E%v_=6O!dDKE)jg4lh`*;I5A4Z{8vjA!UK*3T7)GseUsB|TCHq5fWD&`k_9vMSHyd{v=5L z1;)M@J!l#r#sPT<8cvl#{FOnytjRJc;M(pN zL~uc7cHWmOzy#%23t8{#cAi@Iw|;rBI)9MWyW-MroB_TzWk0KZWfz29*zJ@j*t>Cm zu5|bX@u3W2|n<+rnXnG3?mA+KjDQ#Ty)pt}90 z&w98HeiSg=Jy^@e1*>t=fzyItU4O(5GUk$oG&z7#iNxRa_e=s1?|kzL5(eJr`unvz z`nv`Y-R}fpkN%TC?>Fe%G)h6t-{1#=vHK&oVJql6A#rzbhEFh*O9^3cR zY?P;q8Iu9J80^5c5wFY;V)9#nP)U#i3?x8i@%Fu3Ss`?2xte==LwI%So_{je$j7`t z2M%a|C1(23Gy#Q7WuR#5GaNU-bQ{slvrJoeY{$vMFaaP9swgM@CMWH+!#`8ldDAF_ zzb`}5z3mlO7~;LSeQW(ytt@ys53=;cDdXb<{9VOz-H^1g=n|-KxAZLA5%x`U$W$}C z32lza=1GQ8o|CIL1>wUz3u2Aj3V1aeolWK5;+EaA9bX zUvE4J!V%{WZzSV)gv3yC zJw%m%_fG-E@ok?@XMgM$z70K*z%>W5h1$^@G7(wlfoc*qc+AIW4l%g$_1S5!(i@^Q z19)e1h)2XICxiSopT+LpgILlvTjaQCj2YPm_^i~K6c?y_!5|frSb?L0X1dFd@$V^$Q6utE97e_)*k50PCcx257#N>J0TNMv-RXR z{Q||zn4=YOqkp*>Lq{N=+1L%*ja-kH^#-*-V~6~nvc#Gg+CHG{Vy9CoAvYsecjdFL zs~3~ij=OGE;i1m8F`P=RwE6BZo@TKWuBRv~P#$IZ_rk@7!A8!2|N5no9|{)9b?1cJHL6<@;)_v_p6_?PP7tYi`^xeQWero`hcBPLm^78^15&}O z(I7$C*L2+vD5F}?<*4h#yrm%$|9)ikTT$J1*6C+}H4o`s@B6hO?QOsd-P-ylXTu2T zCxMIL^M8+&Ef7|k22sL|i3mNyeRE@n1iygY0fvJJA92~HH z)eFvG;}QzQ;@|*y87v|GVrb<@GVAM6AF5h|5Nt}{Dcfw92^!Bh1K!@><_9Bj`Uukr z48b{`g9tTSi-#1+1%8Ch-+Yv8t@&Ql?Ap$QKYuIW#OY{K5pMeeEirtsb^==N^{d9f zVu_WUqzjF7SX~mZH%^gq?$@{d^UILMXHBj|=y{c2ijBNqYJ8 zCS75Kf7;Nu@L~~E(s!=6IqJBRuhbVnCUGoLe2cmof2@r2CR7H9rt+yDVGNmuV-0*; zfPcR9JKb3rI7dKX_Q2Hk=*EW89}=NA%bn!mdG=DVHP=w8j(L`46LD&U?_>fKf zguUe-QDEF$lLF`rY2ZeMJH6EKwF|GSVgVtT6?<9{;kyJpPip;ur_{K9L?tiqKr_(tfpb{7(5om#|FDMs4M^PljyK(C})D1o87Y`zBge%DKhOVe8muT3r)1N0onbih5-=cNyli4xKClP9;& z{Y*WHS#{EkVC$ETh5d4OMem@bah@a-E$vnUk*(XIhI4;o`eSzYy{Wp>Hh($;ZNUyo z>^k{jK=)&oR?n>?qsVgI=YzwBydD`}5^5QCyDetAj3b5k?kKf)=6SXH1IbVsS=v z$SxpN`rRWjC9mPegDXXZ_lsf^e#ysZg25!h`tSXXRJ6mspgUnpX>wo)&uIjN{)IGu zfwR`{sblFNz=;>gz}6h3`G?6ks!wMlxraReP%ku=@j>qTPtWRVkbfLlfnZ?Z(9p3^ zj6!|6lidx?BBB3>Q*gb$MXCf@Hx47_w0^ zJ}JM+Z*8_+0dgQIC;+tF_h)302O=cK%MNWg*-+4}h)9^(A5cL;%---lfG8QzOX6QF z|N4}-w?BAFOQJEs5PylN=pCM7-X`8EiLEmB8lN8wk%OKFqBDOR5#8>xBjH=N!f(iP zh5=f}Hu!s>3njgeQ^oMK)ZX3?5eD%U6FLsW$)0tSEk(nu(Ys+ArKX9MLF!1!S>f5u zWbLe@KJ}833{$*vKT4m(5J`N7;58|yy-&F2<8rIC`@WA&Zhy>1phoAbyf5>s%xO1WpixvKz&5 zx!VjnoRo`Gn<-bOsE@zn#8=V;1@pMuFFdkN76UGNZgbn9$rkeq(bwHt zGywNeI%BGbW%L^ViXm*ZYru|6X4jnx=5VXfAXq|TEWrC*Lit_|djhtO#))(nB72?R zFB2?Tczu^Pe!Rqd_2HC^!VVIy{ zA1Bv9|I`(jqF+S$g50OAF{KVwte8@fK%+A(vwz)dKtLTX(rGW!z*@+7R|RPL`yP-) zT@`fl2NG@4rTGDj@f#X6uI1;y2OA!x$oml-t%sdf8i+bL5q7OT+&o;#9e4RO#1w94 zW<({7eAVZ~09seW;p}0} z$%hvswCnWJ1C;>C2_~WoGuOyJ9Q#Hn%|^>cx4y`hc*ZGD(c#VtWsUkH7hXCo{hf)J zV=0AiCO8Y>Av5D5rGq|32}rRsl1m1iIwAgOtIS&Z*Z$~dNWi|tX!bixMA;HE&VOoC zZ18Vs_hzGhf4gs_6WZ-epwGz`J6q(aenb(@ZCUz~DP#xiiK~sAif>{cMFGMrV(c3^5 z^X_WwAOK+s3noK5IFPF#j&bhfYJb@z^A2h0eP#wLn9nG4RD4l0ponOxw@ZS>+F-Q- zvg6@4m2un2w>mk1LJy~Tf7WXQ^dJ19EJM1i1{~hM;W}K>WlcalcAk8ff8Q-V#W9*O z{zpD@bXBMo4{$>$to3+Zk&Lf5n{Trz!OwC~6^<%cQj!k?XrqA?8$0O8+<({c4N2#8MZ*raDJ_!Y0;wHW&E58D6;Rb z&oMuTyty0fo|Ss4{78MkzEw*D=$nHTZARaNc_D!FWummgSLXK%_oz=gs*g(0L{1OR zD$ZVDJ6tx9p+W3N5SRvNWq$#nug0SdpxY!#arU|>nfqlKFmc45@bQx(Iq*8UrXTj| zOYO_O4*!gpmA%L9?xYJZK)=sE>}ldO(OyiPdKD71bwL8?;k2i}E*ofR@Cr6bk~L@= z82^5GZJ>ji+%MFbfFc7#%+OAE%$q$)AIrjgsRbwe1)Tk#nW9L0gMSCQ%uR55u|huJ zg^oiCRgJN~yc?$gDChx>Bhb!u2_=(Tq{Q2?gVrL6i<6KTrp{TGh4y=vk(i!<)ui?3 z;(3sRWc^f`yYw_gx3YdlEF5<;lsOC;{$ofW)Zk|t+l2WgQ`geMAbwi= zC;=OmlhhBmHVzH7bmp}>+DdP+*4*361Sm67Jw7maTfvF9bz*kQ$RU~zLW_9!S(gMv z$`|>?%-6Rw{+`kl`HmZ}6#!&yV4cO62B37c-SoCdb{iEpsekEBQJfTc2s`u{0OFdr z#*iFJ*%dc)fcS7n5c0+Mj#8LpC17MI{*{}_41D8Y0|gd!ioh1Sa<#4|3*V3Rb!{T+vG-$x6q(3~0^VmPK*CFZKz2R#6j=Xcb4|`lc(Q{56rl5fDrypyP+Q=^xms$_Bpjy@@OR-* z31=t1fdZA^VVz|TkyNAqDsuojVd{*UKD4C#j(_Z`ELJDzC{M4w-T)n1(j3HQ2l{&x zpmGf*A2c<>HHEKl4H|P}ZZ7~;2fxmB0RLDm_`Dx8y1U)a-CbYSG@urdP!5IlV>}mZLRDOn*uysF0aN6OpqoTW2};Q zR$X`vI1(w58S>k-n>%g7SMxPjYsuQzQrG}6BeeoG7@K%`A0IuotM%f&*7dUPrqcynf@*|yb^3_oPq!=V$Pg~cn!Tp4=kjyT zy=7E15!7YFnz(V?k-;HQ(2ZDsWPe3%>V8jI`$KESCt)3s**{9t{{nEvRgF=RwbU+P z`cHjEIKuI=2Ndjt(1IO=;szD@ZGiCoxO%*83FYE9wDGu184J#tMOCEbQKqHAz zvcNnn%?XU@9>1Mj{3x7VLEvt$tbC2RaDB^Ac|Zj7A~q-vk)QSu1Iz(zR)6^3z>MU1 zq1|gjoO}(jpf8B+7kq=@h1}D*|9Vola`JnOtxwG^3fjIlJqY978TrFDM-_|>eV_G+ zp87^sK){)jMlEBr9!JkPAz{ady8}$>>ZC&>5Ir{)C^(^4F!>Y^GjhlTUf`>)NxARL z;N((OW`~!N8G?(_+N999gMV`e>`T-}(aFk~Ccg~FmxAW+`_3!^HFW+`c236tM~sr* z%xVoGagyz^eeoSlsM1^xt77*yQNvgH)JCoddhqM-_Z675Qh9ku!wPDr&uv((Ct{v# z4;9K=*}KCMoH6zj%7oF3C;JqBjBn>DG3YkjF<3qWa84qbUmc$G^PTOw9!_472UZ%@$@!Le5V&NwYo30 z3d1WY**t0}g3dtauA@#69yxQG2Rt>9RvZV)zJsgfzP$B&@DIR@BvE0Ulh8v@#F;-N zGrmkYfP@HzvnSnj^?$YM@p)OQZ>+{D#k3N!;w$Y+F&gFjLmHkhvG%FnykdDBp3-{N zxbAH)#d=QSn$@*kS!PH(3;lZEX=5M`A@>%!uK-~Y34zLbzO5-#{VCAfZlAfa#?J26 zJLBLIWh?NGyWgW< zz1*ko8a&xkzx{eMC#Jey9vb4jL8-LIp>ld@Qf-NBp;qA1xp}-fG#w#Yd!78YY1fpf;L01(V$w z8!s-6g&@}hsJ1RP1;T(pRtv`AQdG(*FJdmIqK{HEE+wFWgcs|_7RIHieVX*J8_hcj z-ueuJHe_q;uNF=Fpy=(EYd>(8gX}<_&kF*_IcV_h`mF%U;EoyX^D8BjDwtw8vhI@c zyF^>~k$;4@qMsn+m*SLexL+cwU+FZeOnizu1DtbmC+<&jksD`?`jE8!lTlCdODXde z;@iVci^9j>!DTctJ_00x1+m>KN>CMMC9IQ zpGM;+ZEY{kUt&GJ$VO{u%o6VUN&$q* zZzO#@>3Vfvl5HO`>r$&gyLp>XEGMpdT{;)(X6Q3i@CYPkaVOhu>X+B@u2sen@5V?A z(~1a5K5*9^;}qT2(c@I_m;EL%nhzMqVUb?fDh@a22Nb{?3+{+pHKM03_POuaM=gE9 z7=QIRC=Iipok&t*%X-NAz)`#3;BUkBk_pl{xzD7RmzdEaW`T4RgruACrcCoVM|FS$ z(x0}bq|9sS!5oHZC`YXXQ2^wth{e@`IMGh^eJb4D4{_PP=o@8JIDw!7{i-Ogg*YhL zuo?8y&l^S|tdF0i*2sc5LHz9}FZfd=%YUnlM4#}k@9&Tc$bUx(sZSe#csUfHTE9YO zlf`^gh;i-wT|M^Sx4(Zfw>@Ba^GOYL`;OwJPdrT#)A;Y8c0n&BQ?*uT{_$td79gB8 z=#0tX1h+12jhGPTbci9M*SW4Fbx&1cdV){C`sp zz`m8=BA_5RR9}iq&-8i&At9#Rrs9Rn{h0_<5R;SYE zd+KE749J%~+v+BKRb9IppK15>-dLiBq28A1{nCw`@*4?~9Zs<2Dw__~K7TcoV(bW$ zT~)+yEHy7077P4}Fzhi3?qVFWAfMi$-(@Tz*_Jt;kA4!NMc%|$frdaN$Ks`ueLlAzzO-pG&b~O*^XH{KMuIk*nDX(*!U%&#rcFu{$p+S`)>T8i1W+7 zMDB^!^s`Hv#q~7^uV1P>W`F3HKvp=WAJ_9BuSx@i}}bwnFpr56T8)54&;W%-dBE=wqwA z0B+i7u!yXU;6BqJ?Xk*)d8x+rtrWwRtMpLIWVtc&H!$Ql6o6JS(tnn^l=MMK<^?D?CaD1qP0-r{t-hBG8;S(=*83QDK+bE2;^(7$2QNX3 zJA0K-*y6++rL?^s{gV2r>}S|aE@7LG2hKhbP&dF5`qF3bFEjU}9c@7Tb4(f*Ut%7c z;9RJi--;w=9GIahlz(}Gn@B@vQJLGEk;=`qR!A}-xKd@ydQZf9qcJEa5kadvD`v76 z^^@LZ;+_ZW$tPU(`+itHDAO0PR8xbXmk5AtYbiVYbg}*ZDuBt1Ie0@?sKeb6za=Zd z-ZQsL(fW!;fV0A80iOk2=-fJT#~SwI2LIAs^Wz8F^_7 zkY2?!ub72$&HhdxQ3)hCfxkA3s6oUu=}`U4Ix!R(Sa1?S0XF*73pdyv#vz zz#v0O^Mdv#vq44rr@;}@i0^w@wj=Zbb@yEOP}3ecI#({sTLZ^~`7CWVRBX`2kag<^ zQeE?lV?ztXMu4^(sFG#q1B`_vq(LWFgyhs1HGtjVK3AR{PA@G1x#3Z2`SXbR9((-z zDTCp(sDChFvtL{_K!mhPk7d`Xe5;STDwPaU9szoqM%1eEJfU!P08>}GjiP`G+90ee z&EXq)$-p)hZ$WTJ;d;hT(g|%}$3zF$n0g&n_(W1$!fJt*E@ueBU>e!v@kcg~!PLUm zhX!*M;4y}5CG_(ywfPd-C-m)D-V&maz%Yu`4}Y0%azO6dsgZ25VgPK72@!xL6!c-S zFEC1kHGer{3*F;AF$c^M{MGAQGav&C@*|K@RYoFy(@8%WdPXk+1z?@-o47oU3x|+d{os$|WMjX|p#mqVPmxwov>fDE`x83r zpnq$jI(C-yzvtSUR1Swxe`NlE$&6?neU4wn7HEtcu*OOzNt|L({2^%mERyhG528p^ zopjFN_<)(z1s0=r_xOZ^_{C$m@-bs$kQ)FTlM+6plqCxyD}^B!g&Q56m2v|L+R`6m z$@Zo}z)J^3QXP&UmA{jLtIx6T?Qt+{#ebU2caz{%T@u??^wfNZve>vGv8bXo`JmLO z(J%I`T33k%0FYXZ3G_v40*9zijoc}S`vRA>@tgOX9y2ks30gM!>8!H zSrNS)yWOfsJ}5?jak2x-9_5x=Oi{qxjTO6kfYCRw02+?9V(_tx^{-fqup8>TgnvC7 zzX)|j8dSIsnF%MXM#2W}(!m`4;1Kd;p?cr;7IJS;cyNda_6D#W@)y7biwPUS$poNy zdPu0@3@cM#vmBH#yo)XSUK=y|>JXp|tG8&0$$l@i`p$#scN8S8wKGDUm$_DE6`#)t zC*>mj%&z)5Xb~t^BBvi6x(r%^f`1sipKArhAw~5-^pqf5Vz7c?$F#e&Af|P;=xEks z_w}zk7WEXz-)Ozn^l+kBvQAvseP06H7>$O4N_;i|mW>gC#H>Zbe=HS#C+n>_vBZ+R z^jiyukKpxE?fMhh-^fr5r@!*S{i>_Km10u)g-U6MuBE(xQll$;v8@cGmQS+aFr}Xy%i~73f zd2zXA5J5#1iyg2{9e+tr3cY}$(BrA&7j$0d)d}V`J~Do9aW~R))=9?yx+t5>c|g9w zIqad7Q7(=8lpmChdsW$MpC0Fo*hO2(kh84gULfR2#Da0(jjoKxLKd0inLDet$;SyqW#mIvFu1L>^EE zs|XCJshRJi@iiH)jRzHpX@esXQa8Z84YJ*L>Cph@2^z>cbYEkRjDd@0b`=5A{P4)g zN(LW9rc7A1^E|s7Ga$CYr+Z*4jmKeyhYc6A#=Zjj+BS*3juQ^!+JPuVV);Yb!RS6i zj-47`5%lSx;uysM@WTXZ z2lTQZ=_HaVUEY8c=kAsfr9~Nz$zXvdJ7pCp{jIDwMhCo5VuKtvDC{5zkOz*>Sgt=V zBK|X$ggLaQvB0Mtf-9e)qW#AEl#u*_=4BkF(bzZ_oM_0I!*4F8Vt^yfP&CD1Ohk{~ z;qi)H6Mu)ybHfKhqK%#E;+GDTHCjGPQ)R@BzBUw+a5IsywDg3I%+5r9=+2#sZt}f> zH-`{gfs{`$dh3CEO!*9FUX>f<(0iQP6e8a3&H$v#L-z)~nRU)y_z4Tj0{mf&Q|z3` z6&wKh+K>ydX6n$$;v8etcfhiK7kw@Ip`O^UqkrkRx?}JD#pbR{+#o1-7Xr#KvCzAi zjmzH}t0Z;4HsA5SGsZrJhP>=V|DuW9&yBC=I<{x8F{SF;JLYL@)hRUQq({Y$glzKh zyvRYqcLn-^4jw-2R)AzmUIsEF>dx(lK+io4)?%;c0bkL4+cw7UigmlsHf(vb%yirf z`hQ*YJp9T)Qx{+W(3xg2g(ygQ8WIBAq7cYE;Pv!H4-sgA(DN}h#2e81iX{bp)EU@X z7uWNFUJ+i>2T%wLJfMl5XmdM|#@WSSsb>84Yr&J5&l4<;YQmxl@M?;U>m@;XHc(#@ z^F!i<1?6nX8{#^hri{jrzUV^H$I2kr(|;I;1WIw`f#!f=J{BkJIS9=%|BwCXQp7Yp z4WQO~rlCIo2P?g%xtpi~M&aCk2?`0 z@omoH7122K`mGxhU0b|g_KQJ&7O-ptg+FiZ8$RtCy(ux4jutgSFYRPMv#9BS>K>G| zAfPU#XZF#cb_Gx_PG%_7q+*V^+tccUdklcvengRgZSv{p?wi#ZfXSp zN8Kj;F|W=L-j)SBJRYBFeSrs~zi7&9-{UQI)-ABV$(85akg~EPbou)+BY)Cn|50=n zTaH3e6#XCu5G245cXwt9?hu@>uUEZR54)-(+;`7DJ4F7xv^0w~pU(7O#=Hh;qYd7` zRdzozdPeN(=@*$;ir(nxm>u&u*8rAxH{9D0 zl3QyV?iBQ5ND^uw% z%j>7vicJ7ev5sCa*?+93Z@u4d%KNSu{8_;I1kQbp`nIS=#x0M^TC_%eQYPJv)HIA^ z5ki-7gU;K2{rtbH+}b0I0rW$Z$ocU@?^sBF7l+ekEhxPMj^8@- z3xpzTB*HuX>yR`1ZPynx#bKl+P1gu`-Kei1oJe}OJY*Csz<+2szxt3+A12_Uxm}*I za#_EdAW9uf(K(C_#GPEf!NP(0>KuKzr}Vedc)v@0v420}=e1+^4&;(5Xm;x(Ntle& z(d+#sp|y#@A7#bHV+UKtFVgPUgXWSds&Z5>(h#@OH*kk;$r9a10}wz-Y+B`9L=_ru zU87Zqs+1LNaDO!Xo4UN(MByvaJy5oQrq50W@n{5q>?|sO!s$}BRh~WZt8sb)f9jv` zvmH;vF*OBz%Hs~@eAQTsO~%~0b%z(B(ImhLcRTBNNBFxEo)d)n6>J4QDym|Fb$s_y z&<(`q%@5`{eM-;+Vx()Oyf61ZPUK;)_qZg zFN)29G3gBzNgtFht061UkJ<0&+3-(V#Q0Id*7TpfjC^^%1rwsjSIB?O9b=rS!gbm{ z1VIn#algYaIL230vCi^(tt9`h*Uu#~=H`QDUh>xOp54X+`~u~7VeJl_K7ine5z0BM zZHR!=Dt|mb?kQ|Qo-eamGY|UJ??4trQZ%e~uUWvj|#+Z>R=S`;NE0qNp+~tp@BHQzB_&jDI|izr`w0cs+7j2vD}dbf^@s;s2%u zJ!BIEa9N%kRKOgHen)Wm{D_^5)wRUX!pHdjza8I;-GM<(FnADpf~ufeamffPOMdQ^ zQDzx|)Il~c%#jB9Ekr|&K0N3?#HW6dE<;${U2msO(9px&|F@|wsI+r_=lygjb^tU0 z+kZ+-mEiST0G1^Ho%U(C{1usvu`mp#td6a53@EAxKM(YqMTxcOa{+qdaW%P00u1R% zXZ&teX&#C?9p*h(-%7`JLy+(Jxnnk3qcCrNVdSZPM(pu~E0;j|f~m|Uo)c~Fd5W(kbePMfxb+RZ&J&LPK6}8iC0aE?}e_DnNX^P z-}H`_MYSLlE|r%1ZP!;2Wk)WLz);PLinKl`WqxP?VPG6QxU@R(q$&V@QdX*WcTosx zK28wHO3ZM72sg6k5h{C!PLGHn zOW4^goa-pPV2YZ{vyOL2H`BOoDt|!17!!C+$1D`>sJ>Beas)#4!d}zfQ|$rkq~l(! z>cpYerDod=(Y8j6i@D8*a?^1UB7vk<@2%b4=4!75ILL(Ss1;`O%1VHrvro`+%N2A{KHK!p4eNi$qi|xRAdVus|PIO`GdjQRQKTF`kOv7FdAd!il9Jcb| z`G}~-*G+J)ImP_cG*4H#l#1{_oVy%HETLF0z3~To&EcZ)%zb=cL6BJMK5rC(L1CCq zVdRm(8awhA+>=!Mf-s}%1lL&;J zm@NbZgpcvzs4YRieZ>fwMFxdt^MSWV{8gAWxhKHcMDz&UMIKXY%!5nxPrtQhtO?cnRpxBa+i zLd#v9)h3k}TNPuOsDFSxNbOxplxUZW{naCPu%`*0T2S!D`LU{E}Ov0$?uP(ioS%q%v4il+>bVrDt{__aY7-otX{6YW*u`N zWWXQ4<_nmbDJ>kO?~G(=sEnk(#&w*f@|6dY#W|HSsgJOau=s$O%vqwOA++}Y!FY%* zscxLnZ1Cl#v0SmJ-{qrhS5fv|J92=W=YgnsAlo#B7@4yOoLQ(Ekg*sgy?|ghV0Xu! zzcWxd$Xz~wMSn~#&)sr)(yh=1;HSxhu4N#jRGeD={ml4vrgX?SaLlVtW{YD<5a7SZ zg;liu7=rP3Db#^*G$pbR=To!}2EW&dn;VU#eXLk-HVG$@0rb6v^#$Ip+I`yl8~wQY zAySck06_r%ZhPGv z@O|8<#M^ESyuQ<+i!h4vNk{5b7>H&N%%a3N&7T0sk2c83Fg3bN2gr+vn%8e0Z)28d>)T&~V9Av# ztlj7O5kIwmzV(BF(DcIFOhLFPM7C!P?=b?MDZV6c>jOA;xXW-vZzm2~g0ne)g6JpL z3nsX5af+OiY?^**4%Q{5y(<(n`#OVinFi^2% zkjH-rtGf-LnROqV%q~{Y3s8#@!jBj2-Te7L)j^>mAa1?Plr%MeQFBQfPI;Py-yohR z2!E&Ww!eGm0yzjm%%N>ZfU7&7kZ$YFTGmE7)|jjJtDdgtb)8Xj#;$#;5DZ#l##nVX z9mchCoobfr50T8#)R1E1xYBVqLlXduWJoDpu*!Q@+@=^_7O>-Z6c5pT^my{>3v!No(280%?T_Pn2Am490fc-PQJr)cU z&}PjFL1h6AW+i92nK5BMN5yY4HLF#Q=~FI9R#&?D_(9%J;!jhDBvBSDOZ2`Q2mGQo zn%XpqdQ=CXC4DiVE#^n~0WAnmg-l*RJLV%xB@j8RbwV%e1j<(>b3<8c_o!+IN`K7X zHH-v8#1z_{zrYngreD?mfa@3n-e9T6V8XrSh&b52q_`fRc=;e%+V!K*bLwK4BQW^D z0;ezf@!{+W^pu~EO=#6+WF0c&o_7DtF|p3b+}yPFb@&5J2)j#>2gujx8rsKaI}zMo ze(v{IcV@#dfpYT(#LubNoSXSf*M9n&82q0gt*{M!9_^IP}raAq1U z9coHN(-6`p%|V?89}s+tdKNt#_z;6#4SJsy9sQ z*c}0Ln(0~fGiGZI$W2wM`Mlk$DIZNKAyC|j>{K`+u$8R+(-n@xaFV9phJP9RYC%GN z0$J}L!pTgUdd1VY?elGVlBz*}Vaf)QX19fPVr$S<=pjF@y^(;Xm<@O)zZ@}Q4Ap?( zi48Q&U8A+>Rjhrk{--?2_J2#H_jcd@PK*Dyrlu^WQeTXsImL22?j;RY&)2C2wsVEJ zBLAkVz8;>Wa~Z(toNxjj`G4>3zA+hg#(G-`7tz@1oFtkT(xDqy3oe@A%$irvQLLRr zmH2^}0cC-~b1el7MkEI?8bzdd=kP}m^;kWfzT-Rer6b(h_xaU;`ql>&Q+qO|jc>py z80FBg8GxPTRU+f?$ZDA9ZF`O5bh*Q&mJ|VstezXvwdj397f^n&8h_rY5j5}<-fA9# zu2%0?;ZHnE@plj*W|2JOH@;%&c&dXZ46YLGN&fnYncEQwEEyMwu^y5CT2%z^#DDrR zzlC#X!f2E(Jn!Lr4R{{INI)H)^~d3Ufi09;jnZc&QLAv)1)jw8yDmM}0kr)a2K{QQ z6wT=1tU>o-ofdZ~Ie#EO?}C+Y4nNM*po(gWD}@*Qz34Wp<=lefpfE8JvT9yR11~Nq ztvFF0aMRpZ(rUsyL?5{X^+%5Q&4wkT6QM8Mbx?bhx95e2`zxtx7B>Dr({=VYQA|^L zcp9*sp6{;*lo~H-m(H=%59u#Dp{c$#peX+VuSh!2gkR<_vwzI9op&qJE6+RKiU#^( z$3Bpslo7gG@i<0cD!~i(D*sAPRuIB^lWG^`>OcQ+4K7Y;_(j9yow{aKe1hc3A)F?j zaRa|U_q(wNV!}PixnPb^7gU&5s<*m&=^Ugz3!unHYVsP`6GON>WjNVSdO>myN;M(n zKWFg(n^gaNKz~@h4cB1GNH=!2Jo#_RdR+6{PJ zgz;-`CWte&P4s@Z$`_E6 ztW!-k6!+RL6E$FTy``~SD{_Bg@;Nwb#bm8IKhghEW6jk16(C`%UA5r?e9V?FSenk= zesBzj1b-&nmwA8D6S_s}U9sZV zuGpT3fM$_k^pNL~EZZ#EW@~XtonjgU_E}{E7*B|6*0qZUMhu?y-)M|G>AdAx2os-` zlDHQw+(bG_F$tiHBXnEQz5%(1!UO-On5^GNnoE+`vEhHr2FwevIU#@+efCXVOCsRP z4&n{&Kok&CrPdGnfng{1SYH%_g5L`(1MnMuO}LqoF-XMnw$M#;aFX*xH2xIA2OEpoqWmwOF?ins_kI*Gwxi>7Ya-A+6ly8VBWx{_tqU|RDW=5AJd9ANc$K3k9_ z4&mn%X$95~FaX4>VOOD~en5xa4@|!ONIbg2W*~n;Cf9y({cH}x&kpn5;8>r?Ir9|) z)K3FdN_ey&IVZSe#~G;lUbu~9I%@$KO;aG)mh|Y{jO1%jl$~4rb18z(v`yLAysO1YcYC5VZd9BWVY&2GXsTQ*sH~5H4YXIH;*k!S5dLEZti*ul zz8)&X7e2tWumV~DZ!yzrfQiM>ii;1b0i?mV*i+)ZQES$EVVh9xA$m|Zq%N9wIP`x6 z9q8apkNtXB9_J1DyTn1pG$g8j-`c*v=LhzUpP4)tgOlKjyi(y4gPkHCWJ%xRHX-Fv z&SE^I<8euo`vba>AK53~1yP=mQYZamS1mA~D}#lL7=KI&)YAUDfk(ybpw8}DuLq^y z_|UrB&3Y41c68PWX>gpJrJSX_;PZdegUCXgem3xX3^*^BXPv--R#;+6JC7XdAIfKYh({xmetA~UNCX67u#qYcAlB zmEwcUi$MqG9sQI**8INT`3Qd)RPtv0uHD9>HztGH#k|mE9B=u-cx)4Y4D6yEq1+kc zJaI4c026dI#Ip=bv}^q!_t3rbn?AdUX~ILvJdnw8E%gj?njp2B09`=B6QXCwUkmT& z7WGqvT+rcrWVeTEeQ0E_numAq&LO);xbmZ)kS(}QFjwvs#$tLZl@EWn7*dA4??Ng@ zw{hmYLFk6ODO-BJM}=Oj@hic6VCwx|ES0(3uphI<< zi{h$IiVieX!H^M1C?epDE8+A>?p~@rEko@JBd;n~7@)0-@C;M<%({y0dNds1&=S{$ z4Kc`i7p$Mzs*?LAQ}UZCB8*c8@@HowEOz=B3J{A>K8Q-*z!iUamYtRQUsUTfrm}EA zq6iywpU>pKLo!FE>-u&gI7Sgucex+nbY96gXAqxfLBE8b!7*wJ4H8X_159nC<@<-b zFVtSDXe2S4;j`)5q~GWZUeN_iE{QK{r;-F~4Ikx3!_t^-y;@vClDrBqZ+uRTZE=xH z`*aSnGU2kn5I}!;VnCYuphyLzQ--2HjC*t$X1Z5G^2bSci4TY?m?GqekOB)aZk3nW ziw{t&sj)O5wt|TWseC@V8fO_8w0B(XGbD@Jy*~nzzTlOIz+O$sjt)74`9`!)3{M*X zX*QE4lvLqt0{kVa6`w9066MX46?A~V?sePH6iszmAN+qXn5vpyz&S2v?Cz?W`^ELs z%h;Pz)|{o0SM-23pxZ8dIsMFh3>@PR45e9F#GX2zm52MX1ulsWObwCMQNUyf7qEcB z(R=1ck_2bhMG-FyPtV6k+%N5>nPFw0HJM~!%E>-Qgp zcuIbXsZoDfsjT93PN(AGTcWv8G7hQjFr?we^h7f!>>sem{I-nxJ`=Uc} z9I9I}WPnlLfqfS&QHTL1zzDVd+gR>aa!$C=+}PxgsUX+ZL$|j!^UY5Zg(BG6YcoF{ z_jzPQ7Fee4^TY9%e1F(_#lGO|O}$ z&=!Be5P`cjvH(4zE3q67Aw5~R&vplV&92!71^<7Lmtd37$)h-Rd|;eRNs=mw{&kD_m)=}( z*+|(NrWrNMVe>V}HqC$A-x(C=cQ;U3hPCC}cGS9^v>(LaI{p5r(Dz}G6)XM2Ki4h7 z#PtFYlO%{Ql~pYdN$5bG*sYdEht!HqK}c7%=f(-yG%14aDU`3dYB^Xj&Xc zPC3L}10sw3|AjZdt?k4${zBm~8rXjUOmhBZEE-r-f<}?fs~=;I73A0Z2%ei{b|A6p zYkuV1vWY=$#|AUsye;XS87u`kiU+^)ZmTz;FGy@ZKU|A4$AEVr*MBXSFhmG zesZn9feNSt{qZ>8o=aNNbP7Xw7YI<-W?F!Azvq|NA}K&o&w*I@i8ZzysBnn_$r&LY zz@;R-&eJ}~H4&xM3)K3cBkF*00AUAB@*Ksh=@NeN$S$_Gc6hIYKaPoQdaW0LLhgtJ z2hOJa`&I?c8x{-H%d*&n8L59}uLpC9^7NsKr&vby%-ezIJ^oE~l@Kiz9Y@u!R8DCz z#GR@M_Ll~OACEc+I#MLMi@|#)00j$5c08Xt{`_7=%W)!ofv-+lqesw_xN5{RhdAPF z*WhAnG)`a&6)KJ}MN##tpPorcH-KK0b*oUuugC#ar19vkK-^8mXup3QaC4%+1pwkV zp}jNOa>sd-n0AWAk12ZrE)k&3(LE@#mElT=kmA1Nu`S8IE;vG)_;hF8q-FT%gqlJ1 z>bpw-#E&)swS>o@Mf)^5Peu%ktdj$*qq*%&S_8=|S*y_hO)FyJNp%L4W1;^ACzsRD zw0QQ3Nx~u471}d3Cpmv4p4@ql&dIar^^Gk-tHC?*48?+dJMFqin^e3EAdsvd0W`#| z-|SiQ{h1asPy(ry?2|iGoCIEV1Q5{!EBC^~)5qH49m+%4U$G|H+E3%?(vem5+u+(d zTF#h)oUutljW4G^ncbsA) zLh=8LK4=UB<%{wWqg^go@HjXME?X?L;o2ZajABhN8|(UIAGrG4%Y2_%87}S4_4(JO z9t8Thfxu{_ETwtT+Qu;`m?q$)@J@j(1*E|td8aEVsB6`-9Vk*k-`t^}vi9sBMSV?R zKeXwLS_yyT&-3$BVxTD%ql!DT3g>P8_N)u%BJ6u{Yw+m{<%4HO#DcN(R-!LC+x)#= z(y0XHF)=KZ4Vo+r7I=0i0RJB-g@IH%KC7g^=vVV1`Hh0Nt~uqXo^7G!MjF-2F%%Ch8cgr@5vN_QyBA_$5HacmM)U5(;Fr?Bk>ZofA=D1)dFah62 zO-(C!BOZwQtBwevd#hg@eh9R@{+Hzjtc}3!e3#9V7yHH8cASvp_;e+VT|M9WyUSe# z%maUVb@ZZbKS>$4eI4Z|{I)Ti6k&T3L7zNHBngxD`mkB&d@BF}t9Kgus~z0SF#XuG zEekaraViZxA5;zD98|$v+;R4d>K^SufckV#Ri0-Azu#l)jk8Tyl;KYvOueuh!31Pr zAXP5fBlc(Qrz22oqQDrJg_th|r~O!kIRJlIVoaF|Y<>sfE1wQxngg#0>CCAw&lA26ss*|P)RgvuF*11B-X-w^zwMby|HxhVl?gdZ)~MhwnOnEx z2R4JUH_7^}iY4&d`OEdQVdS5g1q*3%J<`?XY!QXGFq0G zRPVJG{xMK=VL=;!Wz3Gk-~4XDOJvUs?@Rx&5;%R$cw;Oy-1!`sx<#h)? zh{!K1@85}@C;UXR&Y2Pp?bWa+;Uj-ml${ouL1lpR_GLP8kMAQvpjJ9vjfeuo?Ln16#?e-s&59{oUD$y=A7i?4vdT{N!D@#TouYXk&8!Y9BW}o zGhhgyZbb$j@|4B8a9wJosMGhZ(gf7ib5@z_4Avp92_!pw8I%Fc_PP2!KNhY8wfsc{ z{ZwL?&iZ+?v>5Kq!SMkp<$ZtQw!jgxgs*-M13U@PQnr8_yiQK6UdT5DDdzkz5uRS^ zWp~b}>tYrln?|Szuel&3WHO9jE^~MFa2dd3t(7ZU!bY3^KYHjxYtC13M^Ir6PlS~c z4`RC=7bVWyj1M2w&aExHbnugrW+lL-4+&pNrorQpJ8N9J?)bb7 zb>oL|0fei_3p2$lH2;9@5#PVZ6si60ui}HG>Q8^5EQC1dnXOr79U06981D%+dCa7>Lu=p)eH4Y5DYxp?SR2^u z(8GrJwP5dK-(OJ)M!kk=eMZF_`J1FKC!HmXP8sON2U^ZvUQGD?&88E6Bk_50Ey^01 z;t(6+dqhxp?K($`_)H`EJS4>s1Of>!47>!Vr6t;LS%fc!J;;Ce8RXC^eFFM@qpK6W zhfIbyMKPTjsLM5gZoW5oFFFl7kg~EGe{{)TW{O66k+aJi3*_r?QvM+TT0W&$a`EP# z!K3y@+!?{AYyX(xS3mmZuuc>3a_<~6K2;htmO9sT*LMaQZte^K5C-;98l#d67IF68a+{KO;_R_9j1gd`&2fJa=apEi?pRaK+3!`;1 z<^)sC|E9X2&H=&39>?pgfr8Ac=TL{@bn*SEoPJ@vZ70Q>+I^o^fQ30?8;nh5vfIjc zl;cZ`+u!WR&XwmithU#xwk=|r$Z5S$ggC!y%LGjVvT$dE5dOx}ri=~@=QQ8og^74e zBKC`Mg>ZjyLsdZ?zI~_y3@^K)ytI9*51m?PtB_nR4hRf~EpAa`0`j3tOAx`xf-j6* z&Dz6VzQGvCLEeGCu({RNjmH84Q=+sLmaBXi3Rp$s;T(HaTJNy`$;R~3j&%9PnmqOxYsU^ zY{hJ&8*VchZIx9D)~;r>a&iiRO~MmqD5rM}Fnc;CcsWWR6Pgc>-1N=UF#LcJ*yx5v zo#FxHDX1x6>1l*RoyRd`2Qx60bo!0LBw!NJ=>BnuVfe-%yvopA3J(o@ZOpg%m$ZXBd{$$Kc8Vw!}zMZy)H> z-qI3Sg_g&2x$uoZb8*gFJUfH0bQYC?+R$wd|N7N{1*!5d!{DjS9!u3ml&G(?j|S!o zk&jLijK$RWE+9Um+| z24e8lp3SHn4UG+sa~wbfO%^C*y3_uxES@%?XXueA;o1I#I5$4QW*>@)EuAQ`vU=~R z{`_&NP1a!tFL`>c@^S}6HDPCjsQ+*J9bGPn(Vm^cMu(Nj2vFj}rSG@;Zr`5*m=&3T zgC;kuhK&_EB6|(c1OWy0%P4z{h?nQNRqv6a|3^e6ANH@#mCq2 zx_M&`z6xi2>anOsZ4!H7!19O?Ted>ApRcl-eruTXkF9|FDB!6Sga6kLxN?Bdu|N5# zYdb*;rb}4m`+{JN+vN*1$2NRw`Z1~7MP0LY%Us~UJ+((^?Ax|!xjP?cmY$^*f#dPb zxKKQwYO#O-P>e}1Ul)H~jldBw+Av{D9iBc#E)GBKSJdZW*{4~I$5Q}Hfp6pJ)o3P3 z>O7ewuP9lG49^LZ!gxMMH(`>}ZjJy>{aQl3^-X^16@V9b0xCOZv#Xo4CFJ+}sKY)g z{Hnga3(zt(cOCZH}gBV8DW>6dG zuNMJ{N14%Q4eDK0w94#Vyu=Xm*gA4P6{&;ZBwJ)nKgmk&63?DkQPmSYX0N2g?DspI zYHb76XpT1 zG*+$~imF7Br@h1FdU_nodha3nSra!BKFBir!Qwv~TjMni>}ddC{Ard9yplV-Vn4OU z>f*BPA_+GQf|#H%2V8B*e$i!^c-bS7f>W%yeh7%<_|Jdf$M(Zs7lZ=FVARH!rHg85 z3?35aQ{*~KGL)`dd@ zKO(d5pGCqmK-^g{#y5}l{CX*0hPCfk$);B5mHNw@@pilEFn3(Px6yHPMfl0?Xp6L@ ztPn(^-~N9KR80$)OjzBawcJ>|uwDYs?VU4kBL19WClmKMv&k6)h2B&IEw z>Xm@p$u=mkZFj|QWBq7I&R!@O7A6Y4aS3o6%9AtNyi%+pi5l_tb$DOMUJ8t_3q|37 zm{X`CpSZjT_@LjSo=GGo8t1!jYsP66RAXiRj#KsZ97d8|*a zyjvfRog~e9qss+qghX*Fw{L8w0zH$@AdgnNyQYI;STjv8%Fg(!xtZl>CgwoItN-3Z#1KD$a;ZOKz+YzXN~2m zJ=_^DYv>16#3p6oX8WCWA4~QRcLTYZ87VV@_WrU z8*0!8yQQkI&%ml1P~4EdjFrl7jJKOjXoV*Ig^S|@UGrK;S#Q<{kD;oF`k*em zy)9%Q5K6shP%}igZUl}p#U^6mATWIZG*d!p5^#S)bJV`}RJs!*$#!^@pI@S<(5HV> zR5iF$+RD=`Rc68|h zy{#02;3^u^uWQdr2cNYiZLSHo^5=iD-ml$P1;A@u-3w0lcLOfVw?|dSQ#&IhD?O4L zl?;{}ISkgYF8~hjz?F*X^N9%XZYF=(Mafi4d=g3aUpdEt>V}Tx)>L<+Z=7Cjx`_qg zMn=hvq-jvb1Il=KA8|xBGH*u*Dv`|o$zqgii^%P^+_o^oTuqzLWfd&a4x@jaduzA9 z%EvF=yw}!^1(w>jEhyl1`bmA+xf_HfI*?Z1bhXes zc5FZ)l0y*+*8M>WX#y2!f`*&u29iqR*(LHA{fMgoYBsTmi;BU8sDg8H`V&CCzH z>aO5fIB_c4=}+pKjq(_@@#=prHa4!nv!XG9Kmp1oU&5x0GB9JwDib*S(l*avi=WM^ zCSXM2BDe7K1df~sp*WO!iCDMiSH}-O;_^be%}zv`wsV4{LnJHVY3;4Ts?+b_#Sa*& z51c$;UxOV%+fc73>xK3vu#_g`M6mK6|reD!709ncf6pcvZn0&8x6yJ4{b1)vu)it2qcpZNCZ7Jn7UW;|xX5e+5^0aV5c zqot!Zjd`&EcGeoq_-G&|KoHC=MY$`g%oi_aLV&R3qUKv_Cj>0*$0~r~pyNL_mNjvvIqe-p?4(k`006I>o*r6nZbA*ZjY2oydZL zqAsAjjcq_CslK2HTa1diI#mKXYT>PK>zTn^pib&l)d-AX&;^62nic)Swgs3D;Q`;$ zmC}Yw*_fQc}S93hURjt^lkFtAX*axbhy=dhaMGD)#B5p0-^}_jNxaPtn#zX-7!45NNSV zAAo$YP2=F5nLu{(e|Nl|CKXtH<7Eny+h;mq{C=&)PIaY?nLOR*KS5;IdK=@hVpU+W zOBoK+*!@C4#Y}&^Kwfv^(N>xdK<`m?Jo=BsGmRv^{0gM2hfVhj^Z<4Nrjuyt1*`BH zHUBmyUJ0^;f{6>e{9Q6j{5HVOTqN*@ua|1yM!803zY@V`#J>em>^7J23G*bc*#@-zp2JlzPJtoEcE2mh+Mckj&ePkX&4gY^i-CCxgXM?FrK5*;zx^1Pf z&VJ9kjq%JTrd{* z0fgz5=iq-@@&y=B<*YX1W7>@Wd)}7_l`udv1RVZ20Ew{VBS7=n1Omxk==hi0=}mjh zRu*ys07XbBWf>+pa{#<#WUVyToL#nLMff&|F(7};J0<3g(3A+CWgJ}gg2{Xg7hZPJm z)3H_q3VP}s=XWP!XU`oLpZPmFeS;hhrslJ;RJZsSH+wi*W#0qCvl+4ysfk+60&a1D18L zQ8b;E4l;Nv2hY~$G5iP&7+gF#PM~jTY|(#%HNR4gYpa9P45fa)%Cxo6u3&~e+#bJcYRtP>saYKks)likH@mY{7Hl#=5wEZ!dg-w6t zxl2)}Y8-Kd2foDLm;|;1W-b75=q>OQ(OUBmNS@v^YpKwgzCD?_P%dCR&;c}i zYn50ILIa^jsAc$IzYFx*^)3^dlC;Ah07h|q(8yW6>F`qufv3ylg9+99)I@) zpBS8F-xV)CLuF+J^hdAfJM+Ll0vCU`iwJ#nF432bX75ln>Hstm+3v%Bw3~Al$~}&w z9}XL+Ew|=?7Wk(FUb%d-kda$Xx9@jW2ka>mYAk&~IRo^D!ufhxVlhCIs*QJew+o8M zo!JZTyvpI|msy#11HI%$?dM^~aq=R7*u>d+Bg?NZ^R~pYxwm+$4e3vJVKEZO0T@#mIZ$#Hu9#ma6>v{xeHWM|#+KHDf7Ltj zzd_u%zOitC?{H(-B}pt1M`N(qX~36Uc{ny@0JU;36kn8V~CJvakD2mA__ zJ1gQI(!d zq+a9)0==euSVy5YCI34#X zXUz~fXApe7uSajKZoihXE>$ONi&oqQ@rB?)aF+u_QCCs`-46L}^SIhPquEZ-1Y%zT z%|P_^JRS2eL8mTX>}`Ks2{$}4{7z2=;YM~KA*u$L0mQPV@9!7X1sD#a<7Y89dl;!JlXcxLQL-(r$ao?e2Db(hUIZb%Y*{?wQYcLs{i?XRm zIG&_c2KmWD7Zw6T)Fj7tD-+!AL%T4F+@p(`Z0P<)_+vhF(I%mb|Tm>AIO5jOInGYJyKYN=h0L@x3q|5-! zrj8S4Z!iCi`cv?3ldc~KGbNP+D%sZ0q}J>!UPJ6Q>Vr_!2*MNAgW}b5#v!BO@e8Pg zs+njo@>@hIlg58;0&l!==%W&->V0e(pR9N!KxnOR2jCVgbF36Y7R8lF4N0)`9 zzaaU0@eZk;VCHl4&(kxmNWb}kdgc4Z&Pj?2yI%U+!}D8BV7>E}hxmFHt5+HL{t2-g zh>R|b)8iB{`H}6a58Yo8)m~`-+04TB-jL_1?fEWNJ`8`~Boap%T&$aEiIEE@h*tiH zM)UI-WM#?*fQ`VlmIWkyDd2M6Gb3>_m+jHoCx_Nt74&uE40KI!PkQ)kOg5R+v$kjF zI7*#;^JdwUWKSt22YizXpB3o{)^Q>6-tyU()q8&LpX8@^-#m2>41EKCz(QagwxYDH zlxcu1fYpEV&2BK$FZ>7GSSF~3EXPRsHLL1ewlzi>LZ1miwt}22(r~LI}IDZ zocS4XBPger^Znu9@E6;k7g%^zL(=IM8ZrJ-LhTHK%HDIGzEZeL@MuZtczK#vn0u?- z2PEmW9FU=+N9%L_s=_*dk5vXBfXoclutQ@kk?4OmMH0n|a$o!yT!3{WFjYbH0jsc1 z9`6%~lA}AU)_~*nXx5@ z$nY8q_wbw)^$@T|(WeC_ZkYxOfifpLLl{aYBOU?S9U^?%S}frBHC(>S|w@;v6phS@i-WpR5W6eRy$W9U8sN8 zH;5~qcRmkP4v*qS*&umUvcsS@*ncUGMJ6d=mtGulWABO<0X%rZZPUD4lHPX#iZLAN z4xFL|nK0Yq8F0LQ9*#~D*R4l*S<3N#kbQTTK4?1ut}BJ=+ZQkcB#H(AA+#lo0G0!v z;{Luxu>aBkO$r`>Bf;bCAMZT@gdKmN-P_Hv?(+aJ`EXZ$1*NzM@fO>v!)UP<`8c<; z>?H886HVsLFNJ4BZ6#bSO0!W``*jW?N?{lVwBWer#J3)vA;RI~^UX5Ee4#wBai%&r zm^#Qt@!_&h(_I&4u5LrVk9_xc6vvL&kRrm`7U3&fQ}UQQ*Li6l)Oxjv0AznJwvS)n zPoSKSpfVzGS-89hPr-iuTFbYphN}!j8hm>PK7qFC?4F9e@zEp@^9fp@u=+vQA>b)Z zh-#x@E27ANw7gP~ehyCiohiM_j&0mv(t z*mVPaDJH>R_plk1{(mCIM0v?$`K!rS^9q;Ex=5$*N&wJ#MnCW4btT0pm1f;FRvV`5 z%h|+(?=1Aqd_P&yEs^J|0$l_sqh@obkOkMoLoHMXBUWH)$z}-3Y%{~r%z}J_e%5-EB zo>!jN_X^pU%J>ub7Y8x~CR46Kp;79J;tQWzAI{WQ$bEkzL4zVY05$07jjmOKB?|c} z9?Zazz+cz(+c+3N3}ioMXt?-cjc0dY(XK8E0Md-+%scY>fi#-VRZ5k{LAd*L>^m)W zkOuJtn~8!R7T@2m;UYv$KVzdEkisE+S2{GHVTd{UbeEcfWWn5~zV6Biy*nQ12!1Se zfdTLuV>W+XR`->=2c6PHA?QL3e5v`45MBe}=4!0~I5CnQkiZs_+R<*@2>%?i+Y^?} zjCh!>uprb*)AmDhAjADy4Xi09!R-8tG#;}Q#f+mY^+o%&j+kd9)uiv# zqmclX0x*Xv&D46^5nR+IDAF^J0Io?p=_aw%6WMT19_wN5L~Dq1B;`pO+FK zF*BQt>2QkL(@-th1|}NMLNKsi(3>pIYQa-K!62me&E0OiG|ee#@*$g#*0N#x9fU*k zn!D0N4Ou_sbH{fxW}vBid3)!66J-E_J4LJyr#l)a6P@bbZdF4=e165*lV-&awzwUH z2TOn6!T%$0Rsj>$EcVFBXI@ObNzokLZmGR|Q*QPmcFIU4=gEC>r=ocb56~V&U|#Q; zO93{5uYk;UqPIqIeg6iv-$k1`&B@&)1-9@54(Qh;&`VJye?xl%(2$Imro^p$G-Jg; zfy1S?gtiaeywm&tvy;Dby&JCzD010@%Cvt#icn!%^WI}UN~F01nN8W^2nst0t#TK* z0P!{AWebWe_x#biX(tr1?|9WD2;3koaN_}xaEJd z@i-58R&DX=kvHM*z_p5pBOfP&AOhM?3N6km<$V*Mt9IaL@w=M>k@aiHG>+eLe?Z01 z$pIThb?V{MOKBAep;~pa(`wQa?mHeELmb6NOFGP{)lWfm%%6i9^_Lxd38Ki>3=3aj9lS^NzqMIZ6npUhM|z7SIiT=i zU#h%g2XF=KgS7f3U4;v6zrs5029^zq-QUo_4;0@*H%&Ptk66AaFqaot(e(IcM$$0y z+E!?t64dx8(6*CRBK(qL`Fwxr0()m_J1WST9QVYFH+o(Vlb^|sHZfF_^&V2%hXyuv(bQ%aE@Fg$cmwov3o<0TrzeW$ zj}|tjx%3pLK$swmZm-&zNnDwIfMc*VI*Jt4)?AZb5lelVckU9L@r!?|P9?(@f-4zo7^a=%X-?9Go)+hW%D(ALmTI{c9rE@!-AlIWKkSG4NhXqBvM=^i(#QD zW!o9Bx1#0^c-Zl*6ZE;3Eh>xD%H2tNe_%h(ltpADaP6Jy7w#vz+VsGkS48E60y}p| zrc;Hb08aqDM;}Ss%(j1roDXpc0$p}Q{Jxbggb5)&d55)02u(XwwjFmdipl*9$7-w`(B&K_rvvWxQh)%OpaS?r2jb1Fw7w|c zZO0loLxn6>GSLsEPzU+uzvGoK`x#ixQQ9K?gKw}=Ie@c0q%Rb9-kw5aVYq6N<*Rv)K}rR3txr5IinLuzSZcSvuA}C z;&DR$K37Mxp+7u54*8otDwSJsJdzll!#@(KxPAnGk_ms|R2>XQ)klgwgTLFi+IW>^ zhXzdl_OVqb8=81+t|)C+67z|`{~GoEqlgrzR#x30bgFBoR-a?xD z%eAb7KcoXP6RDjTl2H=#%Fn1_p9_N?}pEUHNQxruzI)tf2HfY;7jQV-s< zVHgDNf94OCDf*Cc?Vhf;^Ph5#>Rj_M*s-m>f}#y z441ajqz1mI!P<*+PLg{+RaQVF0iz;_BkY0Mjna08qGJfe3~>(k&c*vlU>f9j#2BdUx7)5s zgKZGxS^E4#$)iWwCIs=AvBWY)^Ra*GOU$cN?xtZk%%rQ%B!{k8(Qdw=M5vP%Vl;dP z-!)oa1wGaw7pgfpJ}9|e<5N|z3_%$E8=SsnG{kpoI!ij{(@+3)Q5^}O%HDs~rLg0A zZhi3v$j`r7v>z2RyNyI}vMoaR>ycHI%mL1)i&X7aqi%qvs8?OHpbM1W-l|%F%V+C2 zF!H?rhSM@Y9N-KAuKtYxy>h^r79q?d1^bJ4UI}#sjV?lHGZAy2e^BTLdPoya8tWr5 z$3=<6oCX0^#nTLV!RJ^;MbCc_(EW8EAj28yB;Q}Z;zJF^@<;%@R@3fh@!2y}Z+fot z)Ixiq(xCu9*eO^)@|oe$6)tk=jnUZR&Rv4I$MPNYx-0gih?t?;AhFs$%7M7OFy6xL zZr^SWi{d&sN5a-&yzP$#n9LyjAvtB$LpnP4XzCpI_ZZYD7|NKY)n_?nrd*BxDdkbbbwa5rB^%ea5g{m~L z=J##aeLuS2Z}`wu#R0zC;2A^#%@mZd)E7BGh@y_;=-r@=y~1Y;_)sS>(f>&2^x>=|90fCqt-5D9}f(PMB2Xel-ExhKp=P;??g6q zw^#cSg@2;cH8#(KGdT)8%t=&@y{;_ECu@Fzo}5fWU7M1q^}2tF0lq}-4rNjbpQp@S z2_g}w5zEOIkJTC}qi3JQO~^*T(__V{u?CHwoxx}V%z5j5mZUGLdM=EDKTj7z98B30f)3H4!k zz^+XmZ4(nCVTFw9E`i(vA!TC*x)F-$!w?X{t?**Ykc6%+wx0KGftD`afm>l?x%}pF zOCNb5j%q;c%JajW2E4~fYOqbQ*_i~bLj|DPv3t(OMbinD+b21C_2`HA8Hn= zay5pv+n&*PZ;aMV0^tjv&S&0>vKn%pFuT4Eo)#vBIeZ=~Ao$!@_X(;O7hT5nCZ%3?KNE zoYx3C|4ZW49XSHL0ojS5b=Di;GPd{7GJ$%dh9AOEla(m7)X@_Jt-prky?%tw5>c>c ziWm4A165L09V~euzGZo4v}MUG1j`BK6<#tgiVJ@cq@%5$X=p5!<|xidgDg_BXYGzy z?NV+sZ@E6kd}Lf^vYo~&4S@OX>{kL~yz1ui(YGPE-xz3qx0d^$_$K17SvN4<`R+Lg z)M_B{#sEj$!Egf2U*@zD_x<-1X~~omn#f-hdF-hKDK{-?lm| z@KS#XsXs1N${ylj+Bp&+Eo7@^v(G2F$ofbEcga!` zwrY2cJugp1&@IixKVON(iQK!>I7m|!!IIOO$AM7~pFeArSI9MMH2|(LI5_Ldgw12? z^MifimMw) zzr^^)HUmmpo}j6-_gob$SQ@y<_drfP%_0D6%e_k;WWqut1|%Eir)x54qtH(aXp}PX zOh+Ok^IBOb((m9|2+IVw?D6X zHT7z}DC29ezs+IkuvVY>q01k{9aQrC{m&Fw=3wD#JhoIV8ERra3jXeHFZ>37$7}tK zCZX>cetx{cPEKmK6Qk}f0ssk~=qN{#`GTZ**2~E>{DzDa`xhYJy3OK!09ZynQGlYH zq0x!_>KK+LqSsA!m8Pe zLT*GtQW!Ck0#5hqx2xku3@(SS9V@2aud*y~WKue>oHQl?)!iea+fkzjGM!@@;lPr1 zdKW8G5)2J3p%KVj@gxZia7%y8&O8RJg9&FQ0D}b=N_;4sWI{Cm<_+3`gzeRoJBX?z z4JAo1`HBL5<-Vr)cW)2x zpBEZloQ#X8V&Ru^e)jO{e)#$rBm2`8?`;y_1@a4L5qB<^6;H_?bAl2O8?+A>mGRvz z9Bsi1*F*s(877xw<_NZV@W#*LWwRGccsP!6SDp{nZWaW4gTi>~i$nk7q`QGa%LzZ7 z{>&idMHh*)0xlUkPj;cG1tgk(I_hi7yj?Kr*uV-S;u)tfP-5+hHkwIjhO)RX6%#P= z^JFivH+vfS7ghWH2Xvl?Gk_?yzdDMYrwgQXl`CcyK0gBxgPp@&Gf9CVed?PWzxmql zK8rQ1owTu$g}C079nYN3;0X1e_*BKfUwET6ta@$O`w5!?D0q;s(Fcxyx6Y_v#k^#I z*+wx*hc?V>@Kncj9>j%rEhS?Q6?NAV7&4hQ{yD`M1vnhTkrWO&pkhw>{H!2Q{3dc)DRfMKzwrFnxdPV?~3@&QOY>4$(*t1J;0F5!8P z?cz&l$v^9FSFEoc2cBJjOq;p8-(CdJ6X+fb{2LUnmMQo-GxxVP0tpg%)p#R$=>f)V z^GKpl@%Ve9+>6tByl?J@BF>knh~4L-br|=PkT;PNX-Wc{jd>aD~1F|pt_bHu@~<$3T%KuWyx>(&8|4|$x0d^DmHIf z6vydC7fr9W)(xkBW3?)f5P!-e>5XbRsR5&#FkP)-DAs<-b6~C8PD-qL;L&l!5d`Z&3AeS*iS7h+7ZmbkN;ci_i&?JE?516K(R9 z@liVY0%ST^*aAiz{}jCVqWa>cKW+`I9tqYZP~6->l4C%B15!0UG62$)KFWWZI-c_L z%Mo9qtvyg&so(X0E|Sb#z|^`KxOj@U=U{UrH`e#^$Ib<9D;3`1KNF z4`sWbxi4la<45r0#DpukkofQo_OWzfF!Ho@qgji8JSUeIC+Z@~7%g7((_xj)=c?(l zay@iLZx}ar&##W@XTQ@)pO&r`f(WGGGJQI~$I`4v-0LN+pm0z%0IZ>29pRiM87z{9 zk}LM}*;ZCig3d`vmxYGFn!DeEjsh&`2t1lthaS*y!AH*H_5VZTs?Ymf1~>rKU3KvU z-vbjp2J_nuw;F~Hz>?uL@N|sA>k9IdZ2qzcm4!fpKWSR*|7i}WHjfY5eZ}lCLZSqD_(G6S1=TI2LQlKA z1ce&d_WTqwA3v!w9w3+47&AOEk}a5WfzK zrMolFwS#ZKj0HrtVa*-!nxL9q#j=`n6lMlN8__g|cK2-9C8@V)j}QEg$ z_t6m;ya}P*M@w!Eo^B_9+nT^-$y*6V9aP`+wk;S&(?CzZpo@QH48}x;Ukv1As!nGb-@Z+jB4pQb@&*t%2>j_evY{jK$G0jJjj(g@xek7u$6ejh(dD zeDpvYK@uu6N!U+Rv#GE1H{>MkJ1d}%Ph)G(|7cR9d75~!5KxeRoM_m?vdm&I?H%oC z-s))lU<6=gU{qPB&4(bs@_HGY=B5RDaHn>6uH`h~KV9Pyx(U6OC>E}{^9#la2x_|9ssPYX9UUL|m zgn2-r|I$Wj3D#%=;*#WD>Ade` z3CQSw281vTK&Jr%-RvIVWI^C~15}y@i(uqfv{Z?H6cbSe-c8E9Hgpan6MxkBE;jEr z`SD#p22a$+g9Dn41WGG?Z`lRxBZ2^N0_-hlS40_yO|OqY#I#zT7;&e71H2912q_r| z5UybryxuUcS7i8`O^D!tJ3|Z-m3jk5^>&MY$2cB3S(>4&QdF1yw%fh!KMY(?V&4H( zR3nJ9j-Qv-+2V zOm>XQVDZd{PmU!heSva<^5VXHN4#=>P;s25Qe@RxVtVRL?;jnUljAaenS+O)#czq* z7!niokf5pj{ATMFh<)0CY7g|g@z{N3L{aL7*!j4*6t>FqRC=8~@#EI&+rFAz0$s0^ zHcS3op00+=g1)NiYrcF2HktPAJX>vlR4osZ^3Hx4sD(w1b8mJ21NCuK2G1_kATb+f zr)Jubj3sF5sIL~iVCM8Y@ZMz}qH&Cq%F=gIIsSaxG5dda1-yFkxrSwI1;T4{RYiF=+1 zQBcQ~d=4AmFTjGtxOoV!rcw33Y;{KS#eV^0y@X4@_1Xz4v|Zp8Wr72R?7+GlX3eRT z5aqVekYC-zw*{s-!9>^70s6FmtbfR_U|+}kH&LhCzN-Z2&JR*I{kASkUQv5AZQMN! zxbHYAs-A)@c^PiP1>(qYqH+eqpWxI2*7x)Ou!k2vg7}9VaWC zg)FvzJVx6+A4XZtV`409c`WTDB35FBmfW^YLl!moA z-R?jHZQ!>kVUvGrJJ8!CJ(a0(%EIsd_2ZZv>-t9662`tJkh+#Lycosi@%fdPg;r;! z|C+wg8IuYKw+z&Ok&bmIU-BWmKK9y!dGW*J)CK&E6OP&CR~WOiU@TXYpR>g^|NPc# zyQK~8zID#u>~(_keUfy=1D!znGO{5@CcBRXeCO9_wPz7yt|L?1J)Vd7nTbr@?ffEm zaARY-G!6VMF^sl{*m2G}at)FI`!}qzH;uWmU?SpLkAO#i+A5ISS#R>ZB~5x_r`Ka| z6b2QC0GG`qS4nB%g1p{H+JfNQf$;Y9bRpEv=fL zTuF6al*wmGIep@C|N5mnD3x7L9B4F5_(anaU)<}=fY!NQ*uH+zDeU65HVYuEV%517 zmBLchKio=x%xAd;UB+P$x-kdm=OJR_nppW#e{O`avh-p9pCbJ=5|T3SD=Iwb`9)v! z0Jn=PPnlhB0(wVhXJ#J;F?>=b8$XZ?Vg1^T$CTbE?@p|G+-O`uq!04`67++3y)z`6 z5RU}XTFIUkS8LO%-(zb@YRo{Rs=J^aw7E^{&>IGUg3o9eaubUJc%3EzxTP2P2=zTnL zxUaf?-+cU{8WE`y|nX zbES1`2ljo7Et7)FUfD{QG|GVCH=K?nN<~_;}z>-^wFBkklf$+Rm5|M(y-|pdb}0?G-6^e{8qkxx3Zmc$~ik=04GWmN~6IPHMlSY6C%vjR^E#w@7`x7NR2#ZpqJK7kl1tP#f!fZ;lXoHdED$*Y@=aVc*(SXO%aE0 z0CZHBXYSov19aBpLe>PzPzD_cv1DJE#5VkxRqILu*%$+c1bOG&2rUS|tCgm(BM*p$ z7_5LQa}7^?8Mz_#{t@SSG28me^5{)*LEG9LLAk|M;QYs8ubZ&ym;cKG1!b_sR`05S zYZg$W)OUv|WXWJdloyE>FI?t-rCUH65A}2+-!9%V+10%@)=F^+e{i!&y+#)W>4D0mu;@8t5K6^XuV!DTK?p*Rb;|VB&yF5A6#z>b* z;vo<%L=s<>cR-kxdJFX1PILhD^rj6G)G_{DB>Qf<HR7DK>89RT3v5!8javV0pop#)ice9R4^I9;1(XCgDpA2p^cLVO! zhaZ{4U%wqKG$i|a=?QXwd~48r+Xd*lD>HkZhk1R&NO)8*BG_NfEacJdKhTJS;7EM0 zq}C*@3$0!t`mAL+v<jDTeXY2cRYJk-WgZr{g>GgFl%qKgj<(tq!S!fa4Be zA27L8QUbW@>+E6&IXw&!Tg(7}ApMzidn7sH_j-4cCsT)3Ng?2Wf;a&X<}G3*d4|@< zR>O)Xak_SCgts<61p|Ea%n&mJy=qlBc6!zN{kFsH&3+ims|xOl;g~|k5)Gy#pw_(9BIZSGA*dzu$Vq?-*Bh}LM;y$% zRpyFc&i`s_EZ#hSm27G=kLZ11)AbX4e^yvPia;zQYX!mNkEcNN_xdr;8UiNjLvn-X zY>{eBz0cyFu9=ib4queq8TY?TGF})+qqZ+T!vu) zkk-WwTJgy5LTelb^7AlXo*yU+)w&`)LREIX*=6@`{I5lS1>ZxK^9Odeota^+eXVkH z)OV&HL}Lzihh{$mpR zO!Ai}iYq>U>cr=h<$WNAh71b8WZFGvDTitBeE-7lI=WLK#w%$#Nzl!x0RhV1djT-b z&q@rEoq>b{!Zut}HE>#*|KV_#LP5aL+UlkMo$Jd3x_p0=md4;vJ%V!6BKUF zI$TYX{&w9a696nqvR0{(F7`W5>d-p`Cj}om;z>4tBfvP>)J*2ZYAliF{NC2}{~;>v zwsoLzQ{QwB+%kb$$7sV;Srj-!4E<-Ez(Q8j4mgWt8D;w*TZcvn!{?bZnOW?cu$wHd zmsHW?6zA~k7w@0qjR5%awSA@kr(YKQl{z21at0qR5nA@KoOy?UBfCRx2$yd4${c>+ zbUEIC$H;;L`2RzMp_dr+WB>yxjg2Q)&>!R-yJGl~BOhe(Ud%%NxBX#Ye_AsCLUa9v zIw44g<$&En0XN!coiNriZgw3A$FgOdwX~0wuiyB7*)r%9HGc>uw{k@7hwOTQ8(jIz zR4RQ%0Ajd~97QJ(u&?ix5>0`Oeeu<0$EVbl4dSH#4`DoVsr?G!5f zxa-pyV#v(o(T^ds;05}9iCd?SxDWMwk<*;%8DD(5LPd^uF>0A+Mi8n~UjHjpr_BMz zXYuDpmlifO#gcu@ucn9?I>+k#?S(jh5C@>*2~*$NF9vrjB+eFmXf5E`Qffbbtj|l@ z9IBoa;jVT@gO&xl2gN^QX~L?vTZ6xuiUn8{85sqshJwn6l}myaS^qc$TM$Y(Hxjcx zfh^8!j=raUD=Ruus;$Ng1finAr;laj6M!5JyR@H4tlCR6to$z0A1wjz3#e^>?sf^B zLFvEydUjf2VRE-+Cr`XWGl*Q$JJK*L!;i=l)#3vXJ?F3b3YyP{;f*N#F?D7gI_uwA zJOhAk^ra+tod?Oy)#4yqWTnWf2CfBSq>ypj zTS-2fyUIP!cayb5O3l)6nL{RjSSS^J9L}2K!C8lozQ&~3+@gm5 z)8;TX9FV{A7?D3UMa(XasDS@A?Ozhws_3^@n2HxXG}$U0dSz?=cx#}498?8a9(M+C zaJ5a9)b9y-$V+1ZR!aCV-b6xv-?eTksQMF&K~9)zQvuaC@jsFs=Wf!$z&iPGr8?ua zPO_qt=pIUM{>!(|sZ-n8Y9*K2;s*1nwZjYCW?LD%3d7LA0HtDSzlq`>)UCaB(U1cX#8VtyZJQewdrrc7_43Yjbt^_% zE{D%Y#MpDN*I^R{;Y~3h;dkKK;%N6T~8C#itG6P|D28_73`0}3)&kP z>rHubcc0-vgTQ1`hCo!puNmOB*d7#hRjen;Wuo*je- z0z2Hh|2R5}ZMUr`3_p+tVj2o8+hV4fm}N0Nea_9GS*uN(UQ0TA|M1xkZbJoG(hf!; zYO4u^C8cpx2Po+}SlM$JdxO8P!7&{6E@BP)9&=Fvjbmdf5rtI0Q-n}v7Cxz?SB(hX zVb$sorU%ubZ6o@nk)`h$R4G=toYFpOrPQ~y900 zC_kWFE*E87hIG0zAHhDUl0p*E48bsnV4-bzoV3^xZ6qCZyB27_o9L)t3ow}aa?~={ zr)WNwe+rvRj`=5RWEu}-?1~tpwb=_)``6`Fuu} zths^!y+;oy$zl6otH1~F`TYt2Bq%uM2ur*Rf8&D7Sk}A{0Uk@BHj7z`PvM|VHRy4w z`GIHi=}6mqFP^n}!h@+RaD)Y0{u7qaS4N;#O_{0PBCF8vQCJnZ{{^p+l?9Gd1uFA@ zWlD$ZtiN)2FQKEh1B1taDDZ?$kLCtYAIDa4CU<%=zrzA(i$DcPPwxe&96o7%tMB6R zf35k7_S&9+9cYlq(?(4{LwS~@p!`^n-&#lo67vG{9-b-WA^>VE*F6M^qJu3WBhfyn<2j`vpubN%Hg$J~YQv?ZaO64Hq$B%wU4*s3CxoMi1nEP#t zR=CLAoXF4(FU1Li{AaRD_&e-@Zq$Ug-NBB)b?8vj%A;!z|(KwxQZ+U$pe z1>;G8gt#QZ1m-1d*P)?rrUng_Y|8BqL&2AV-~H|#^0H*s_8TTVFb-bx$o;;m?iceQ z2gp4LfrEvCgh{QSe4}ddlBQfs&JGxv3zWIlMXdnZ4ZS9nIWN!OeeZNPOKzDUf8T42#Qf5AsLBSG8i4oV!s|0OEnl18wBZkm zYF{K&T5x-Bc9)dt)x!ncz9LlQUZ0EeQvEs;k^Xx6*1%kE3WYltBEt7zTU_3r&h6?h zd}M%kMdB?N+>JYinMEJ+e_cAzvMgPMxGt0g6q*C|gn1;Z=e4Q%{2d)-CMN8HW}Hhrc-IL+kyX#?RXe`8|n-kqpSv-wpD zK+PmscR{hH0sK19S3#VQc?O`&EYvp@qjMe^)z@sk*f@XE5(=E@!8)SLq%&;wzXPLS z>0OI1&))ReW+>kyLz8+yeKPT=? z-M9ifHg18FJahB4tw>GwTsyM0^O@l%`n!XDFcJWAeE5RtP5{ZlR!7j@SWG0mvUI&a z_}S6V4~8#_t(;8X{GEi2wl6SX5-gRi`s2a^OGpYnrYHkse+0A?Bd}bOfvR&fVB{_x z%BhS5hbh#K9U%ZPjJ&#c&DW~BRUr3G@Mgx(xQ0a^S`Ds$p5k#R!)f88bf&VTNf>+A z#MPCMpT5u&e>c2=Hg+{aDwu*QE_)Jogf&IqR3zAdO0nm%j;=(bl2CnSW|Awge8o2?4lN=(p$jv|K znaQ|-yPhP!;!RoL(^Zr-2&FUvdyJ(IfIyJv%zH|us@0gI)jQ_I71b^Nmw$aY8=)QK zoM848HQ(A*|H&T`6ugwlY;)Ah`(c`ctCCsh} z@aWm*OWJMl3K-#srL8pAiYI}jFgx8UbEM$7nK%eU3`{zRT57lfq4lFh^7^k-q57&&_?D#5^9miJr|x_s9at;T?&mKmRC z16N~cndF>a=Sx~^WVdp|&v%MUGuEcO=Lm=8e`~VeR^u-MNW~=NJMby?y!yMLSaRELgngbw4g^nrB|QqBchy!a*FGM*#9`=t zf5O$gctYdOZtm;clE=f^BTaM6OnL!O#mKtW0616E_GdAM;0;bFIn*lDN(Ls9CvMq- z!2Vs57HLQt^VOMSBpJD2&67W=fM`l7(3`?1z4QKJ3v@v$m{*ZZhv86e7zwH_fUcWu zNAzWiFwU^OP&Bg9rjgsZO8L9iNt^ zHl=r;O(mxTc=|RLqF)uDMoJ0A;e2xdY;Ni*-vf5;vT3cVe_J&Q zNH0f|ax1Ms(KxqBbbK2M?RB3w%kk2B-R#wpekk>c0mCQ?jv+huZRbW@#C3_P{}^BqG=p5BcFnu zo!iz;bb{q#<~D*^xl;(E1xA?We|2s&Oka)(W=g=cuyV*j`nq?jZ-_eE>=F%4DZ1F= zRsyUTXc-3Fg~n9R1U4IT$t-}j>qlXAG;jIrk7@6ah-JJ%N&sc&22B%u9P&H5F5rRA zl}QNplPzmU#rTiN?>?^tW`0%MZn^#00dZoW9aiIqElzK51fn{KJlF@hlQ*@y0JJ*@rm~u)8$5=Cq;IY z?15E3@n~J2`;~AN#Dm=ce@1LaoK~Ol;S!93vUs$x1#ZkHP4&>IF6~w>9`x6K{oIzSG)7E_YZtv)3n@HVsqS-qTL0Grq%kZ34Lj$P5B5ttt86RSuwy3qDK!R?#51W6^B5y|yC zO+H)qy--jE!U9P>AMmq(Q#~e@p$0njNNVE3Ob?OD1*ah0|Y`ZjR7) z8Oj&DLr!ObTkm2dcTQvF0A9@7Z5=i6r}s6-f6#c; z|IY*C-F(C1t1|t?%EW}Y>;`>`2}@Ck8){W?FrhaI@jwRXC)4JT9NXLD6)zw z$CA_;=RmSdx-(|$Sg((t^Xu^n-EzwZ;{~tQ>3||U#=S0u*}On*%#yZ>2A++TmvCu> z%?4v)|HWl~2`LUC9t@AgpsCp%_5qVE)oO0q5(C5*f1at-%Zma5EJmlQpo(`%Z^84H z^Px5hF0mG*QkbvpB@m}+{`~eWcsvH7D_pKr{Wt}-uGVTPd)e&1r1e?^2rlBe#^zl` z;W?5;v|Hu?;kJ&_4zvr`?Uthq-R;un1m~PvK4<5e3CR)|u2eK?I$jw_?r^-@D`NXF z=HH2)CllE)AMY{|l(t+z2Q-y!qubVw#auA1e^(>`Fqj_@@wQ*q9Ea{n-I9D$y3Y_f zV7B>o^>2!r+7#O3X4m&T%4z-+r$S#il%4;f-&FJ{W&D_uBNgRzn;Y ziTcbZlFLbvZDfD98=!}Gf$*Br^p%pnf(2!D4KcxvSDqOWSj3H0jlA4WR{Ng{F&-sc ze`xI2)En}tEKS#Zlr?6L1CqycpP~E7{GQVTz^wm%-~$q00;Wggh=TSxG3|o@t*nI6 z*mC0u$In(i5NM+$A=$V*VA|k#|AJ3(B_>;&?$@URT@BK3aI>=a$2wU4XBHDDIjkj6bQfX5Ex>gC)-UuX&4TO4#VELD5xS{`U`pM zY-$uf6Uu>@Ygis`f9{%WmEW4+k~xvNKDc)v|TZL z(D(Vy`QdySIkavyBi{gjE_x64w?c@4s4`Nc9b zh?ym004%q}4X|_pp&2B0tKGeZe|{RFQQTz~izh5gJL?shIEC-8#pd==fy5JRDydj` zK{~}E>WF`?*{e1c2vCx7^@zrV5;>!oZ~~);8+!Z7CbO`?&S^K#cRh882GX<^qOSQW z%;Cl9rZU^u&84EJE91Gc{4e_z<|5@Uq);(!>pE}y48}U3Aqneeh;S~6e_4BwS1g`3 z;H2o4Tu0Kr`~{ACCLYlG34 zS(lBqt~a@2>pBR@RB+JQbcHIVnEO@{RoA-nw9aWTA zIW4Hqy>zp{|16&YcV8Dyf2@3TswKWx5>eg4J?bFfjXyt1vzV&hb#Dz8lYhDQAN10pg?WfE z%I}rLivGDfX}arAUYLJBa5<-d;SF<*%f?3i8lwVx=0`tLnbGfhe@TJbfg|SaC{5^c zZBqX5UmFucR^wr4y1~4w?eukp_2r72JoV`LHiOXc0S_grDJ6aJm$Clo2WA2(L(8yg z?pC8);p-Ry2^ImFe+!gPptwgHmFpR8%wyEQFs%i-+Cj(o8%EEl9xaJFa>D{4DM0h` z4-C;<>I#Xfh|SVZl}!3|r;)D@0L59Pb$`YhSZM&W(b{5leHRcLUP3YQcEmy9m8GvE zAphd|CUN;%6P|5B!q76BD+yCD<10l$YUjYD*xkyk@`6SFEj0KQRTc;YHzAmaQ}N z2IzZ{p{9HTe}e?{cw$#XCBe7O zuXbpek1VYpp>oGKL>j>E*r7y*6cgB|xVQ7<0h@-L8}`8>_@mX!K3`OP)qZw0@%9O) z7oCfkg!56J?-$P6w5u8oSn!S<%1YwW;$#HILMYBMe>whKl;^*F6cD3v)r$STV+LYg z%vu=UiZMBW>BCd4K&tz}=_xxDP{f!55mT`QwpF^gr%Qoo2bU-0_u zaAmNFiN}?akW~qva6&WB!LkGw)pB0pCnVXx2XY*^V#ti<)oB_@9nX1i1mHF@ ziX|?%voRlDWSO1lUGgJDZ*UUl%1iBQCsP7m;@VZjau6iHcE`5^^Q0tnaPRD<(%Q<5 ze+`ymh56PLgiV|*COyj?X{sdFQA1D)^qhJ~Pc^Y(rvTrC{yaylw_dds0Glr>-+AS+ z5eH1C(Cp@B^{DXjWk|swI_gBSvjfT*jE6h69(OKx*X`>LZ>!#qs+9>?j6WcmaLVDu z)lEHtYw-yv_{WOtp%fa?Pnmq@kBa11@FsWpI`F(SV1b~27DSQD`)4?xO#q7Ye`Jhb z0t8uugNN(&1^N|t7&`j}QENx<+vS=;4@q+_;=NqH!c8qP$oZwS|E^N8v`5N*3&;N; zBd5Jha(M%|eJk-cR8^ac9@yC^oY==wV=et0RV1$XXTTjOgix6Hx{(*@!0<)wp%ELs ziRt!two!Gn2N8Wxz_-jAF8sKRf5=I(QhC8V$kvPQgRo_PyCkWh4Y~KU@7+;j7vKvOTM#_(ST(*=-A}MmOIkE2Uf2<8zquygI%?wcv zW=<_&X?T1?dSh(D5V`X?G`U7=p$EbkM#39XYm@K;MFR%p6A+b%!7wLib9YPkzOD@WS07P>E=#f5Sxfozvvvs)+RMViJ7#6_n=|h2`2I$QnGeFr!O# zUW@8eMflIO21apWe`^xPG(%il~CE#7Ey5$*%`hDng>Gz zt3|+O_Ub`KtI2ai4`~JBSUPDa@_iC~k&XrUb|Aw9on}p-C~g}w{Znpp{S$_}0Pvo} z-dC`DL; z$TZ9fGUeQ9eSL?pAc9}K{tRftGsMY^75e%?%f4pVdp_o+;brk*1gzPw58=P8 z27O0Ef2guce*>==0m&hPysYnX$Hxe*LwF^7M%K_wIm}1e!_nwC2}Xht*d|WmWV?MI z3WLwfgXRD=1y@db(iaVck0U1=@t|xdP;gc&;BD|89rBWe@SJ*sV{K5B;r-#){5q28!i%Sb)#n-ctlYD$2Ag{-{9XBqv0> z*6T+<5X5r>OFu5aB*1UNDW^_H^39I>k&lZ9tvx-_zl{D=F831$&3c@SqWe3187J7c zEL#w*f5#6g2dp{hiI{}=VabUCe4_T#ic*1y_|0drng(*tqR}Piwx*eS6)Pj{+oXTR z<96jCA{_$z-j|G`JQSFtD$cR>-wtQvCdU(dXJKMR!uW6EYyzlxY@?iDD}3JNm)&VD ztiUS>ryvl00t+*`kuhg{W#my!pA7@YMYGaPe~41=MXSd>uK?6L zr7AR8Vx&hDQA|GYfSVxz0BS)9(x^H;-%t0j&l?D9OgE5<4m~r&0*gGdHP}AZUkn{W zf9wm@RNEf!OcxZn{qO=`_XC6hWEFp~1B~#$ANzT{146f?!1f5G88U{LiB@N6@&UOS zHv+SL=`vFC1;8{xuW!@u?`6l|6f>QxcStX={fzFaKS$tGdZT&tw>4llGl6*x$~0?J zzIMK?(Buvj3#wWHZxF45ez$;Fq08R=e~`)B{stDHiaA{qTy*GKrUfCLlGEXg;aOpg zrG&qkKq$Wp0!O*LO2y!{WSE#;{bjDr&3sA93|GS$H0#rIc*~kXq9yzV#T#gLw}Y-Q zsEQDgL%||HJ;cp4yK}wqTEkX)oz-&5QGy%8ow}_A$Amfsw4XUlA=v|ld(K;5e+J>r z&xzcU9zAY!O-~HY`4MFGVQu^L0B0&}DFxxPuHHYQH~gg&&Ls!O z517Va@e01g&^lhKZnt5?x>aXkkxXMk427r=_R;f8YuA)W^~6JdJ^WqIUX8&Hy=sL>P|dm%X0avG(Ev z{A{|g{t$*m%2gYO;}j6aixiIEmlisFga*|ufaN3uWt{3Q=hiET`Jg0oj!!hiW-r3C zVl{IMIv!bD(6+5GuKVMD*gYIW5~oznZoR*n_W>#vYU4xc2*< zL?5x+AI03fSE5KSdMN4xTZws955A7LO)e(f5NqbH#WK=SC&|_ zn)Wiu0uk8$5@C*oA%;8pvjAr~L4?nx7FqT-8VJlCw8^cTkSConzaOBm2tIRJY1rBe zOYFteFGPeBIRlDV_+~xhsx0-9xUT}Z?;fiv1)@Is5=ZE{)3|wXgN@&6xhFIaS_PDT zhqV3HKM4kO>UtnSf1O}i`#Z=?B3Yq+&INw|kU>~6Y@0RuRqCWsVH(XK!vNj) zH84H!)j`lAk z>_~FQ?(9PrCo{DvJ^kK2euCnX)Qgc1MLP?7&_BW;f6GzyO31?rWzN>MAgu@Tu!}$G zF|ny7*+}UJqvz!_#r#?M!tb~@zKu-@JDeo5q#tLkwm1R=1os6{ zVL;P8f4PJaMoAP>ZqOJ9wgr(rJ^rwvlxM+oP}0jde3-<*X&hV!FAJ=}B0x$s81gc^nqQrR2H>o5 ze^^$5>PRsQnMMMU8JK4)qZJDu8Fxg0UBlI1Rq@en@dYInT`L|0v-*0B#6l=V`ZuGW zt@$RGP|-}hveYNOpb5><4;c%$3*da8QzCGNqY!yN-jX%u>c1Qif9296zH;bY71Hu!d#q_p9#OV@UKIi#zxVIr zGRu;l0OTTaD0KZOImzI`5qb*m&&SPJMPgvbaC@Q*y0u5H=PODI{`%E%Fe`8i#k`gL z4lP3{u*~d}M+LVA>@zxI1V#skIz+RnEApZMSwN=0?cR+r9wpqw=jU6-@JvehSNZA1 zwSTW;6#(?X;`Dhkz3RaHTP*vhm_hJUHL~_W5w3M=6-#1VTCZf-6?U!h>Z8(IMu3kp z7AL+>M;II)u$*D|dDaH0 zV=NgHz^H8=EDhb-dUi5eOo#ZyV5YG);A22eNI%C5n9H@dv@PEL_PTymdZMgpGSCya z@Ye)y1Bh1MfDDqTzr722k=ExKw&?hvkw4prz8f9k@laC(9#J9rSH66A$&znmi+^(Z zSut7S53Q1d@}+pmH#3>*8-a2^`c-e$?aQVhVVH*P^V^?$3Par@a&Qzy8#;O{ ze&*jp@}i_a;Tqm*cR*6PB*~!$2hZ#o-mGcahz!y~M}v-66M!24tYJx_37+J(on73; zt9^NH3xt4F(=M}^H84Hili8V5i+=%Cxp7A&Xh)mx%x;q#1-k)`1ie-C-Pah6y=(^D zJBC@7je$ti(MCG^^hQJbetQ|T;OmJSbqTR31c~rif;nqG=DicJ@OJv!@v|K-VgsW< zCxxU-r|oj;BtiybhAkCJ^F^07Lr{qCpL5);?{YO)2|he-!eaAe-};)X%VrUvkYdm;!sbIeqSt zA_s6h>-BoL_0p1*LbC+13K3Tp=FR-|BbO7t#dED=B?L*s! zXL>@k$2xnlRMI>Ksn{q;S?eyQ*axzHD7qj|M)s(0(OR4Xl7Hg>u36d+Vd1Ck)WnO0 z=pS>O{ZO#Fq7wD(e_?@!K`9LmNiAW~3k=xeIJTp)Poirhs7z z2A7T|UJIt{EPvUZJ5>Ha*-c^{|A?l9Fpsdl-vcwBt?pxLXrezBB`$D{-{9Dw^LqHa z>XNN+2UvY#W!}Yd(h^6gK-a}@kvh)(N)%3_5&~#qwq-U-k63r|mQj{_e$@%(tfqKg zjtyJz_e&@;BIE(Of28aKjz~(BEg$r=oYc;DU<4gn{(m+Jvx-KNy64bNH4+RBRcd12``{BwXwB60S zz`%TN41d~8xLjM1pY8yR)d6LYRqD7v=E6Iy=9Vw*DXZT3#19bTNawRCl5^dJ;(fcX zpDS$^=XJrdd;@{_dM+qOE(w=Omt-+1|7oIqLbe>40qsyhccba*x_>#U=)PWI0eNQP zDLozn?pHs$H!1!a@rGRgt{oMlHl*G>YO{CPP=E1@F6&nSAN5-zzoKAn`R@bvS^}qU ztw`VxG2c3PF`mxjC&52n2oCn!g(#EH>F4qHi-EZoV&8S8kwB_>S6goUEEa#-{KV{# zphY-|8TE{GL^=sv%ty2JL|j zu79pfbh+#E&_FGR_%i2k zQZ5W&n-f#l2wR5(|1et>DG4QSii7^{wAC5r^CcYJ?=MbqsLJ;J2h(U6f5Y+4BBupv z)%Sv&*YjK785U3E)uAVjh3siRCkfU&0DtD6-$cAT5`4oF707RL>Tl8_KdL!~5fr!z zKMQUP)u>zEMKqv=t0fA%5q`*e_xSnI!}IZeAaxBhWtLm^dHcX4{Z+Ycj|6LewGNmZ zXmeH-SVud7iO@3!5=d)Vwg)`MRk}x8eF1%F<#7l}L}5Es(0is~-HKL2O09-0=zm-0 zhev%gmX(eD;h|S_V5LB@x%6EV1z0EJYb4{UxUGjEpW}?37@Eub&Zp63K zf;v0VG)xf#7qQXs4FW#BBmUl40DskKRy?)C{^6erNHjY#GusA14p4P0Myy^|`pOR> z6<>2h58`r}B{OhW%U2rejpSBZQIKoV{M<8Hq9vKspPw?us29plKIzZh<8h#Jn!dMm zxB|Hez>`V<7eaze%8fGvnP{fI7*J9UlaGeGQh>{L@#li;^=L@Jd%B@h5q~!~zol9z z-1ZXpXSaA|LGE&YPnVQVZ!koSD z;sd=0jNQ`!_;GU}TVvqmNRLq+*7Veku&_UG023p&`2i!Apl+Yhseb~CK^FZ}|$rE0yLOe5EL*m4GvZR=`Bo;f^eF$ntAXp~r8SdXI@) z-z=IW=;h7M8PnXSp?_nu9Z>S^_Z4`UX!4Fh#6H8@CNA6G#*eO->P`lEIMDKwu;02L zL!X&N~F2S8HJ5MgNNFMK62y}fi5EbzVZyRWfns$y4- z;Zvcs^S6?vxg(cH9~~u4))F;7LVyJ2^Dtsd5Q|POP$6mHH_rKYDv23Inh?D(E5Ijg9Kn2U;Pyz z%?HOZh6R0a)XCre`+@ZHF55NYgad86;b6W{MU^nYaP?JyJUs^|1`1gi(;sm17E7iJo4ztoo|V{NQeSDZ?41V0haZG zEvW@MRK?xf$>p^Zqx(Sw;THX2aKYiDI5OW7**H+YLS#M(5O#hBhBj@sGf_>q(x_;f){4FQG^no+=8;SxN&B{h;_$q(0JOZyufC4JDw2z?h`DO3a{CvS!`HeoEv?}^ZJTS1> zu}?@f+<*5j!At_Y{RMr(3S5HiB(?YGm*G<8_I{d%c9vQN<0%(RkYVz;f|zl5@?LG4uh6 z93Z0p1hWx~FX*v91OuJ#nTwV+5@(oSV$zGzvwxh3lq!=E5XliKmjJZ@J6X*WT*1pN zNc=l=dAkH%Gw6spq@s}VW508Ob+{_#^dlD=s{1~afYc;}i*G?rcV0BR=@<;_HoSop zBdwI|vOdeCzS&G^UkNpS|>23Zg~k3#2qg{Gvv3v<9agEu2icU7y!Wf^b#c%omN8 zm4&uHxS0+AEF@;)*RS=dYYjN5#Ta%*=Ae!uk-;l@)3pgE)r|v#T zBU5J;pDWJSGOo3`j2CHdl7Ka+dIkx7Wq-&iBaLvK)oWaCxsV6@MLV!{X+ zQ2CoCGs>PuP!m+Dcnkh&+O5wmxL8-+&SXkcCs--XG^n#pXun>Sg}*E@YNC{yhxJR0 z$LqSdcd}6$YFn7T8AwmkmUZrxV0XPE-^sM)7}*V9GQB8L`%##6@O#N(3P+-MXMahp zs)di~X*;Dk(Dq)j&H1sIHR`|6?T|BkPki_C3@9W0SuS$$#@!YhSM4|N-@f&_{rZg% zWyTqFf_oy}AF=0Z=@}9}QF&a+UO*0I41yGaoKDf9xr@~&PViWR2l9b6k-@+6yGq#p z=0PL;EnkqlofBDhs!bkm)ThktyMN^B=s?AW-*gB{#QlY!?t+BY63}>t6cY4A;|NT^ z5sXzsL3g4Ue}aN;`O!Dpt#t$O(Tr=}enP>_p*J~Ic5qH=h#~Z|!(#mobdK*{*fK{uiDLsy>z^5? zN^k?jT!UC4jCVBU6MvukmQAgC1n-Z-S0NU>?Np|MGJ-C`Mt`y9W5<#2WkM+xJe`lJ z{qqReg-9M>%C`N1-m-&o>O=X8x-!4+=k0+asY;AGA5x1i^j@EHP=Ixksxnjt2MdOa z(9HgJp!%&@pF)xM!ez5%;T}M@#bT62R2p(1l+KzAD*|j+gC|8;0S$!i{e&5RclIOd+`D7jY`juDK z^MSum^EOVN&Pr6I=q%M7Hc5#ea9_!F2GIqr0BqyAMICLx3+T zZqFEs6>s3"bNH*p_Fy4k`(en?=}WhL$hJhS^UyKSW7Z`bOcMr+uwLPkV-!Wm1{ zAVL6|YWnT6-`Ch}!Sq`AKGB5CXBy;gO=>4^ZUWMg`l*JBSq@OJI=tgCrCAE_n}OM% zbyPyxe1AT`s5|8p8&$LnL5KpUI?(BHkG>mD*Z+MmI76C}Dz zlnMy&_$fIeV;;|ZDdt|OHOxFTlaVzf55OyX9H!n^u%;I!AX!L_lcY60M~g+ybf&mn z{P6v|!0vH^9-a5lcT+2B2haqa-|Pnc#2*hvq-Y~GW}bDi-z1RXdOw#BpU0Y^B}xmiOCM@xrKzsDmoQuJ2UOi$NL zcXel2W`qYkz{4Ys_@7_AZa?`3Yn?kHwKt~V-cXkuQ34wRFbtPDQ356cA<36vQ353a z@|TBE0xbb@m$gv>B{Y-2&CZ}9xwQ52e)ccO`7EpET$nEu$uQzHrMHNt`9qjU+X@KN zodYm)S6nDC;o+ezO9+<^QUWP|buyimB|9W0xp-mnlj;(RWUwZ#iC(DBE=qe98&w zOifoL3nw4xvd#}sMdvyzWXhAkETD+4tB1pOjZKzv z6C#yD@;1r~;h|xFg##x&pmjtrBzAAMsVUI9_s%KoZI&7yLqfYRD2v%VAwOP+>tQhJ z=hWnC`feB+CQN(MTZXm^`&`i31Nb*d1J~K&xkOSTKoW6(rKsE_BD>B+nS!c34)Yx` zAI@@-~xXb269Y<))hczLVvPa9(5R$3VAI zLtezv#ggB(>UijDT|#1>FUIvc89qA6XvFP~<6c|bJsO{Xv)70@$`?C-#ki|S@&2)+ zMUFmSIa*qOfcr$!nUpK2vfwY7Flk?ZXCi~jwRfM|;~hlsAk>FDd(+UK&{;tjE|co1 z>I1)P{=wxk*N(Z#z16t?Vh-JnS)dAcF3y3|%faGx+bzpFiq5;lo+*?*SKVkx?k!s! z!Cr>QUT*EVJEoL3E`UrwbHa&876GbVt;p zVYx6(kwCfR!(bl099PeIsXvIdh%(?dHL#*DiO;;lrO3;8s*Ejk`ux6V=@f-6=c>E$ z0_?m^X`?o50<7}_*Sag~LO!XZ?vuCe`gA1^@Q} zVLY73l(0{v{ltU%J$r~h55$t6@iaym#Pd%MR5l^sE1aX`r-go7$80qoQp^0g2b6%*n7r@E;7e-$Z8&@=>56)MUOJG1m+T5+N03YeFVPsxF`Ct7p+X~<}gdbo*M(VXyC~$ zTgtaHc&@$=v*LCinh1ylb9CfOa>Uur0%A>gq!(0)%>>e9$QsV2-ZwemvQcrsj7I6{ zh*WvEJgxNEmO}PfciqFN$jx*lz`ibMSA_IpQ}IbA+BS^_P9pD%Pe(PPQ@KCj5k zvTiy}Ekm6jMkba}GeC_9c6O1ESn zxK7WZTO;Yh4z)#FDH;sO4ZTuQIJ~op4_(}HtnuburdOL>ac#DdNS53(zdYheVaY?5 zNjNt@ZQD)fbLL*Pi{`RxI$dE6V^MRI=EU$g$$jLn2y^Hw1gkG!1c!bef_EZ=d;+nT z4qE~$0c@8!TLLD3{MZhv5EUiun5XLz!h)c_8Cu+x69QbSD(Q|{XCtCld3?Qsg6p*X z?Q@&<=tV!>#KWX{olIRe5WfKO?JkA=dW4CUxKtaf6!*A@uh|;R1q5YM-GdeUia~e0 zCgds}*-y5?+siYt%GW8-xYvl5cE;HYm#*U>xSUCem2;GTMwMqhDs!atq)exw1+^Zk z7OO1|)&XN41-~|Q{R>@vn z+De*{P}RVG%YIy~xwtw&fbU#-AjT*<@Act)eB0tfT(@1wWJV^Vrnc+U&m_vZdo%4c z8%UU%^gP>$u%VG;W)QQmZOo4q59}AfzDx;3Mb-I#e15ZE7;{fJLA+m!RpylJwEMxT z+;7V{Xz@%pOPwws^kA*2(1KFkKZvt&+*~6&1?+*Efj8Q0@YOe7IRkODBTwvwTmjjB zCKYnByW34}_G_<0LV9jHnpR@+2-XU{u%4$Bpgy0S{tlvEu|73xPBDf#%b)|KhrmCT zw|!!NiW4cWp^@D#Z;pu{OpA9<+uPNpO6^q`f|~O z=%GX$@R*INcnbLCrKHG$wNTzGclIsnt9BSDc1e(oN|4w>6U4ixiyEo~8+oIL=@fK- zq*BvBdDMkHpw|#P2*Z6>#!jwNu@G06=}ZZU=*-)!QZ%Dj6r1h*aONebQe8dYqJS*| z>%nBg7#0JpaoM`(XX25P5B%6hGN4D1hCjIIU6w@E|-RW4D*j@q-e=ez#1#K>K6<7B=6y(YsFf_h`7>7)8 zYZom5Tw-tUmNt4E((2tGk)ykEI^;QzV#3n(lA|T%`yzkqI+J=_Zk+PMwRI!$wU{bpsn59`v3 ze~4If*D>7WWvDbE+O9wXu%kEu#nPIpSqzMNE^*bnaH14EbXuBU`mB+yd-a`#y2%Lw)X=&!SCkfhnt;i-(#!eZz4U3Dgu z(2{BnJD-GYQ@>(l7N&dJO#OXQYViy#f6x16oT4kj0PESJHPC2Y#b0B5w20XLta3cb}tubK^?s?_MX)^VPYDkib%AD?2v<1J70$g zxt@~ddXlS%zOGSq88@PfF9v}OSaIK@Meu_J^eNBt7ciiaBOUQ{}GRr00zrZ-<5qhYg|p7U>Gqxi-z~f8;x8di0(; z$&OF@Q?-;v{Ba2YoKa+KeCp7~g@l%5Sb@*3+0h-&|zH;bI6aG^W0noM5D8EXyjl-c*fC;aFVdh>CV}9>vicRTAwfDPWm@>PtKa1D)M3q z(H$E^x|%POB2~d<%(7eWe>Giv1qqdztZQn|KUn#WI8n`ceYjk% zVNK_8b9QvPLlMSD^QxP`X}DT!LZRJ@84AzxAWTm9*%=pmr~`s1dD@Ax%HU=_5g+Jb z%HQ_7CYE-&jAHaNR zvgk>38J78KN7Bs}#HUAz&Y`6a0$pV`=Ku}^ zi+~!@5Cb&pcEB~kHI55p#%C+g`czfK!kVByWC#l#0#9k4Ga@rrQFBx-G02)r*gTpe!+Hd}e@v;jT)|AA@3Dg;fNW=@ zq0F10Q9U=g);#aUL&?|o$LPf;jTs3OVmzfm>|P{y5ybaN^no5DeEWUNtu<&1&KaMQ z1H|fe-rNA6Nst&zEXXgXH5ZZ)dnz^3mB__bMBz5(ubJ({VLk7sFuPEP^fgECN1F1} z8{!2z5KSR@e;Mv+Hnc@2pZ)C^JSK~RjO%(?szjiCRs*r-CeHf70O0YkOuknn=o7uA zzG)|npYS8TM~>hgIhEJb13jOMkpaLaM0OO7gHl*bq`097hNmyy9A)%Ckx3s~LJ_72 zP}bB^N)%a6Y>;Lri1lNs!Rcfiy6x>n+R;mqvBcTde-CDqrwydV`7wsR@AN&82>kOi zR5tLMp-!fx8t?>`hU0-)Igw2ZJ_Cqv4O>nZ^P7123ovS}XemV^`bI1qHJWatsaHi9PY+)$(F8jbn3b~*B< zr+mIHR70u09iKVm{9K*LiEpv|RtJY-y5V%kf0Q_RzthKM#bAUcPLW6{a1v6Qp9Z5I zpIDDZUhj>;(ZPM!7jERMv9HHBgBe|1?Y0-}J?M&ulk>`se3-ONrMRYga5G6l)h*$- zYnQn*tzPZBa9f$%>|o{W(0Bbkx;p;W1@gW=ohsfh!$8bWX+?mA;v+WFIz!&_nVJTs ze|%n~CRn4RuxiiQIvkOwL-V>v1~FTlm?VW#>NB2u_qpX$$cQh0XQm?!$ck5KR}%v4 z)1aM_Izkm!9gBRUQkNttsPpMl|3+5NX z&1Dzx4#G=NgeIxV;1i8z53u%^WM%>?8Stv2N8aDw zy%Xu=ah~PVJ|d9CL1~NHAqAJGW&%I~5SQp?0x5qbZFjg*vk&Cj^~FeYJJ{h#o-V51 zw?p#C+#{;IqZIK+*)3k6+gAtGQ$qDl1d5xA>3|{Zw#cuLFlV=r^=v-Sr9_+S#+XLE zzldyLI6Xqu9V)mNgz)-fbZp0(5&%PVIpDgOK@z=z=?n2orlcj`mRSJ+`EEyGKU zq)UJH{V?&0bbW98g+sZUcA6t>GQ%wP?z=zU9 zx;RP2F#_Pi9LGAr+TNW?EhD0x^G>`u1ku#Sm&#HQnjGc0Iqk>*e+mUn^9NS8AoD z2MvtTF?#A2hwJ$;sfVNYy3Q5ZC(Xg?5btF2_Zr=kt!{%d19k3D({*?n?H40kL;} z4!F8h@UEvmQWsl3Wd%|T>B?N7wzHu3I{|{ey(@fp8~BLkkL0RTfe@P9 zs6*_en!qmHjw<+z(AAK31?0Y$_P+?J~cW|od& zwxEa!q5nJx~p~lqMD&85JeJg8xBS{^){Dq{vpyHe zLRU?UK31Epq~^i;;py`gPFNkK24NwGIch+$OM$Ia#tYo;p3WOT;LE!LjOBGefoaI` z_x`8@Z|KouiMxN~hH1=5l^Dtq=$ixIPsw#7ArE?4dTvN3f_HJ$9|$q@mqe9#Pj-TX z>H<{o@En;-q&n?+-<}5tyF}Q2L_q48vEgCd3Xlph6PwAA_N9%&oeHetRm|?_Et;{L zdsXS{>GPdsm=~eQvRJeD>c{}S5wjS&X%cCLhzMrDFj+@5%QSRXITGY+syJ0LNHEI z*Fum)uu&)yW4jrX@(`I-+UQFhl1+L)HyLrm_L{2cL!)x@jGh=85dcsMd1b1$`!#^> z6j^iCa`k@^zh{maAz8!8HAgEqy)ySrs9FCmEzXFzcV)bG$?)#kk}=>xtf!v#vObZW=o zZ<`*yyXNCm;bWi2G=UuN34-1)yI(yz16#t_ zGqaU93__doN$M6ieL(O;4qkdYQ3c8-=_NqdARcJXvazmo9~Zg7HjI{hCO<%xe~}Vq z!LWb3syj=EafjzBl!a;m0t&%z<26;odZkvCg!JxJZ9L5O{H~u-fh4f6bsYDhr0$XN zt4e$4Ex3K)xctF2_v1O}m*IkYcOQ2w7l=bJBGp*cK0k`YjXT;$$p2Z(jm9^|?P6k z9+&hLYiJz%l&(y|rVs(c1cV`R2|8ICteFC-QNW@&Xva9`ZisJ3`&5X;1Dn|vm;wF) zwcH#Vm|j-&z6uAna<;^;eNQQ;?ua#M0-J0MVxWpQd1mk`tR7?wMvFfOH9a~b-?}ek zL}=`nU2p z=Wqfaf53A`NkT0^s_|w6JbuuEc)X+V(>t#iQ{qnfnwc_enB6ejDft~fZ;=kEF6ceS0 zIhCnZM0j2 zWe31or2G-Diwvm5xn6K)kyNWJ=2wrl7ukdMkE!&^A*=k(@R^y^t;y6loJ~da*Ly}N!dl@qCT}d;ccA`+MTFQR{om6sAO_hMzTXwCFw-6N5y#g9R#Mb&WyEQ*D z*FMDSjjNyQC~3y+eSWe}tu--`f6Aa}IkkZrr=Er+Hk46Z;8>MOb}RhhbLAMB{R=WP zAN6Tzx2AzYj!$wcQIj>SEOU8Xzw=J8cTiT3ypm4c_-%uju<(J%3leAYbLz!+f1(G+XCAcm;9OaYFO+*doEe_KY|ac%-oIVqfDWCmxr_<^1Nr4QBVTa%H8~X6V_&Q#I%Ov94yzmj2ws>fEumV>+@;TxO%4sdl zq@y`8yZiM}NA!E-myo^c_d34hiKVI+CJR(6w29CU=q2wZ#PnrbwH`f8gjx`bIOydu z^Smb~pZI3up|Y3YbOIL+@TtM!S)O%xUYl#k)ex5jbpk4XQ0cihgW6w`qRFtxSL^qK z2-}h~0su~9KpAFnz@LZ3HTC04btEf0DeN~zEM=CS{TDXt-X5lL7ca|lO+5X`3Cry% zXm?EQgm|1E$-eRF_|8|-X7;=Qm9>0YM>9j}FrJ4tK0Sy%)TZ0G-}Z&isCyq&iuhCu z4VCf-EZ4(-_VmvfN=+{gH1wE7TJJ%-Q%3LkBT#Mk3^sHxKR@jS>ggKh)u`IPI3#7D ze2lm;i0e{wulZiQCC)~1^nR4H+Hq|%7fo3-h`Xc z{p_{l!&&$o2*gz}WlLjq zf_TDzl~Ch455$(zR1gnOgQqy-FDztxsaE}U>a;6kiTN3uealqe^A_GM(RQWd-1~bA zszp2NgMJAKrB5WTJ6US}*kqDljP{zbP!H^PjD0jW8AJ62_DG5r+%XC4?G*Bdq}hfV ztnqo%Z81b&z>oqo=%Q_(`u)Ove#i@xU$6LoJiHT3bsH;tc^!l7!IYwJ$@5AP35R+H z&=YK@8Bu7|(yFdwFVEAnqvu)T>($^9k??m6Vt`Xn4R z6}UWGO(;&OqLFC2m9xux)9N}6ym7T5-chQ0fqupfe>8|&-uFajZ#NDqY41Vp-XnvM z5AF>HNPjNJSig+p-DP5iWhbw~aTslXlIF(rJpsdwmKYl+NW}L*{o#@8rRx1c*dBY8 zyQXRIgWT>zmrpJbf8_xgdL=3HHy;jA8o~?5kC8%m;6^4FB@1>tE9bd9`Xn788=tiK zw9JEND(@4>fpVRlNdZ@RMY`Mw@xq4FJ2h?4tTN zwFlXq&Z9QTPD?9q;F2(gI}}(b!1xP6qY8x9`OfGUJH{<_;}|1-G~MV;c?DN_(I{27 z`lQ%4uhE^SpB-z;PmLu+=IarwW_a8@+fn1jePU|a=Ht07pM=VE;T&?eBxafz%8?5_ zE;vrnj@Mt%drRuNW+%F?bV=hyEgB9bkA$I+X!Pm62@~iD#Fgki+k?b3J-!p9W2F&n zF`@9oCD#?J77V+PEf>RKqkAlG0J2C|3d=VEXQA0~oU%etz?W-z0v`f_5tod40v$Gv zI_KxEb@)swnLzXSvYB?A>yi6gTb)sDil;G5b%a&N_=#Dfpc2mUTIKho<=D+a!j&PQ zgqPoW0w903*I@DpnoV-o198jZNdOhB&U>H>Me$5#p4;NZUdm!)#97{Z=WvdoyeD3*w#>jnmFe-$#!KD8HS9h--^q3^ z{M9;W)5Q2uB2mDCM(0FY*ZPIMR48<{wVg2`#h8Df8%k&IMr%atl`qrYG4gq`cnc*k z>cyX_r3a<{v_Wup@2hc`)@Z#5A!IG0x$<76O!Z+!QU~i^>BBe+CE=ZTw2zut6-0A4 zFs*MrZ(_1Ec`gv@qy&L9V!HA#?|3?w#}pLf3vPLOd|_@=(fa(Q0zt$_r_1wPIs`*S zL5L@k#PG=CG5F#w(iD0P6Pb9-)9<-fU2qZ7bQ1M-C=Te=Uq0B9XU}x+Zq0tZ^kcO4v-H*wE-t2RYrN1krkL4C**y!zJbT3?qB4yYWaYZ4e**R?YJi2_7@91oaE zsPhZL$+{+#wE|ZqKLM>Vf+@BMrhkY%2+~kgr*y}G@@zXUx;)Xyo5?K_?G>3sSr@@6 z-#r|M5)E@Qz~%DIqD)wfqmD81`M`P$WzRUh_^iATrf@l2N8xF#ib47i;q7Hf9z}eo zPqqAz73Qr0(IZ9#BuDo<$JnTFnZVLE5bbxJsOpu&A;-t_8mHs5;QrCEMSoxNtSWZW zz6)q|o=LYx9uG54QC9KgWitfgaJdVD{N!7T3gwsl_E$|`@*BZ;HB-zp2Cn+bTvVW$ zaptBDpbu6X@A8uqlO(HNk~c!Z;!P@Hpv{N~Co1myS`AQ@sV+G!GmMMsGLa5whr|8` z0b^YsnIKx%*?XsB+;nz=vp=cIVlrI&DrDiAV*l|dw&e zniy+5jqSPigiK&X<78w95F~b^fOvgLlMo79J?3qTuD|a48QD6ff7Zb06^u1h`9;S$ z-)VZL!pj%MvyM>-O?_St9?ma*PkU90kGAK5bpIqEE?l4R5Yx_#Yj&vca9+&LB6*33 zWU3k;>=dI2vX$cz`F|vl>9b@s_lii|azVbQu;qu@Y} z49#E-_?O*&0vG{Am-c=FC4b-(l;n^&rQp39b8Q*@(#52Bx=|WW%p9K+)9NdagTHcu z_KvMp5Tj=(Kb_5RF%INxU^ULEa8)0#d#@QPddEQlmF&t~^H=nCHbcI+|(SP;=Z=q=jUAk*>pE2vtPs@>1CC#d1XqVud@3a~#kM(7(t zVu8b*r0jJC-e+>=SuwA8m<_@?!#6VX z8to$#9IY{0eUDGh;Vxj>MP}N~)of7}3L3jm8@jN+B0UH8YMWEv$qtC@H2A~$-NHnQgL85Y zL0?UFMH&k2$cNXPB9{ZjHYO=$-PwKLSKRXTYstk10PM>80Mkc5oEUjE)A^nFs?^26 znvN*8Ak5Q+40cP;lAr3OSpmAsQ{f{an!SO+gnmSCL*t>OW_NcRi2n zYLdN^&A2#+lZ&ySRVuBrud6^-_5IrzN6IO`Zpg%jo9`B_LtlYx;{d-t&f6U6kC1;b z{$0qf@xdmv@-i*qq_cl-on^Z>wd#;uOtX!L-b^OB8-dN>#*a;(Pd@KB8?L&@)DY-C zoG6}~i||On{2r(5ybA)(mQagN?R%4^6KX!o$4qJq&r@VC3P3|VGsDQ_g=6KGT-MVG zT+<-H$`Ecx1bnK_kQ{^k4fD&?broTlEORx^4zY7UU0Xm_}mWOFA6})_#FIA-@bM1`y-Rj zV0@ZvMqw5>@4hG4B^&u1H}yRFXpZ*QqZpBH2E-jZRI z@_2B-Lq4u)gRiDQ#7n-*DtVQzZncDbs<7W~^nEUd^Tah^ zA~R#hvEql3VtF^SANR-HGiL;Z6Y)L2J>E2S{dqR_>-pLc?BK`WbnR%-e z<(B{KBC|bTniNit9{a5QcqRu)_V&22nTla>1`dAQv>oMc5&ScBp9UEDxAr*hnTtOC zV=BFDu-<6RbPcn9QQq*@lXwR><4$?LqlBX90hAxq5TIMY&GKckhxfbFx;N+JQ^b8q z7vAX!0ZvT6o_f(M|Q+#j3s*s=hOqywJRCzlY zNyg>#)N6m^zsc5HU*g}He~RW=--=pc=liYERN(C!@S<@jL!9;KUTNrj=q+W zl><1DP#^So_@cXb-`Lr^pG0r?n?9VH2QR(LKY%{XVsDMJf9&*cLzSw_6!zYh3FI`gfP+Itj)+&#u-p%-xJxxYFA1At8_2+?bs$P7#9`OCr2H(Fl zeC>hXOOI(pX##PwYM zI(xFAkbm2iU{)}MxicK_dp&n17NL)FaSSqa;dgi8=>u*7(E0eXcQJ1~lfgCypX49G zHEoT;HGsRS7m>FOc^B&|uz9*9#!s!Zj$rXj?nhWsrO#AqV=T@AUn=kmJlpEu#5G%W zaTjmbKdZQ>ra88f=mFuiNH>t6eKoC{F`{W>LH74pz;g;x5H3CL>4+A<{r7T~yQMQ- zFUaV1zj&heC+I^;=)o}Mv#f8#pK^4`Hj7N%VQVP*lhGT51z3jz9&cE2n==UNpiEQv zXOEk$KC`&+BR{Rg?E{8U-kjd$5 zc=4mOf3Btib`v)JgxGQjTlicD`p$g5jOt<>Dd<5mftLI%Yock3P0$^vL(4TNk<_oI2Nlm@eH#siIF8@Db)`9OH_q zlP=8#;+jucw6f~7t_5%)D5_cy7wbFUx%|%wp`X`@|DNo1men%IJ{v!zh4lr#s_#An zLLcvZzdob#E}LSG`16Bap;HWO`FC&wuCsrAG+jQq>^(kFhXYAhO{qPcX&*=YNT^R! z9O5xseD8k&kr1FJ(%bjxV_6Ka%>T?=3o-nhSUbj006TKqJAGhD zT=V(cXFjv6z#lY&UQA2oe8^uukcvZr4-`>@uIGNcPZ@pihp}v+H}z9=rk@Vl0WT}sa=nH z7_Ru!UorpIZ8H}FuE7`S+mD*(c9a{p4GPTStGqS(T~Quzi(e#OlS+@7B;xJzq`N|gjJ96yDJ?UHf!Bx5^PP`r&v_Gop_@$wm;=<`YP(wPpvB^uh5j`NvR@_D^0&4Aitx_rIGb4OnY3giLVP@W%F!~EuC z`kz#7AL8_oZ@N##AF?XXQwRKE2^{L$AvPb~75_c)2?v6s?_ROspMgTTIp|aV72l1a z_q>&fa}9*w)DYM{Hl%;vcU6kqe%3qzmAqjjD~s29g0zuY2w&;h0av5U(epa2th`E7 zsY)){lqc_T_HmOka>8+7wRCr9*&W2VJFsk+49Nk!a~eKy2Fp=+HS`u$@4?8-rV<>& zy;#VTG50YAqnFv`%8K4632gJ`Ctxz?+5cgrRCN&Jb&u`4YDU)vMdrf0+a0h0n893_KSuQx zApLc5Lq~LT;fXrF4RJMw5p1t5SIF3d_rdXZPtY1U)`sLp{ht=yz>DFs|lA4(4i`Xm1Y+cm#cYhIGR)lys-NM(V9`}S@Lm?UbCm$F#3D1UBjCTW=)x{cewiwhuC?JPP409 zKl(+lUBt3r%UdGg>9qDmr%jaLEx2F$0mESKG@Yas#a`xUlB^j1;>Jq#I627J@BuaR zkj@4lcg3|WvBnXk_Srz&L^|A*Dd(^11gYLUNYpS1&h05*&YuVY9WJ@YT8|u=c6b z6+7zhfx#L*R^u@GP@g9u(I8xGry0K?J)+DUtzlgNcS%gON%8L}S_1_}lm++Sxsrd# z2Z~zzVSB6dr)=gb6M5*YUj<&RoNyY-EEK~lcbb`+Tj5q8H|pIX1d9b#|0S(nMlCj0 zua&6i_*;EZRaGm@l5Ma_H(YZ+WLNfY%xI~9J=31@TcDLIKyqVpf>r_z843$)MHq?A zYgT{hevyU<8q44YPi$C?7i9h{=Q8gs5D|E^#AUPA5VFP157-bZtt7Ndo{j>;0<`PM z#;kL-?PxY&WT2-te;>nJdj)8c-m^G-suFD zpNOq3&A*VfMrv&I#-n@n*vPR*^OKt zeQ_y17Owz?wyNQA) zQDvq6Ged*BG`Vz$^fwG*snxDuyN0V-ui4eux0}ljmuY%nyy_v^blB-Sl}<0S2LYjJ z%ciTW&m9b7Q~kItN<3aY79hG1nUVGgHvDjQ9$I%9Cc|GQ=@Xh+AyJn0r>Z%tEuYu+ zW(Z+{4@2SNSTUG(cJW-o(Ibxk7TIz1(tPuqo&W7(-L+xGx$ABlX3pEF`i6|?Nw4$H zN|Nb^)N*U2 zrUPPFSo=!}WaPZTG&YM|j!CiG^t#|NoB4z=h3_6$JU)7eg`Prp;@DM+?-NXkaIn$L zW;6R~ZVO0NXYN}@6!7uub!fYz#C(1zx{1R(d{48OW-f`~rT#H~4F zB+4GeB0VJMs%41HFJwZ_iVhnpLtjgSoXf1z!l+ZC(qg|=>_0IWUgB_4RmsRf7p$!7 zP284xK|SfAB|%6pkh{ap=sGXOOz~4oR^&uZZ$fSKjz>|zB_OL} zVMZt6;t>VW)7xmQ57Wu@mz#x-MxzmL8C+o&e~>-Hz3M&14Kf`&J#U4FUdRPR7LyfJ7^#+DfKJW3A?teqneKcR;@@cP6Ox!f`jWC!WtGD{-U^R%hXKl zE&BbiB279CsNCu0YIa-K+(aWvIJA=;iHkOGjoe!_Ul!FuYU9d?9N&-amb4XJO%{#rZWk^P({EwUQl$m2?ZOIflTN4SrM514UwPRWvwnVf>2WBM*=X z=AKh!xpXhl3V$%!zRamf;t&Pq6bUwPi4y`wBs8L!!;TMeD~}CPMjLLTrPiE3G4|U^ z>x=euXP<6!Mr8SgB`wZmR0*$YPnAG0mp!rS#@gJXdW9#n&z7o`E$d^$OW4gjk*#?C zspGB|Dy{8H!z-xuBl8TbUyIlJAOX@5>a8&S$f+U%G&=^;qu0fbobF{V;?xp-q7q0J znv26;Nj1u56uk%q7HXIB+4y>UR!hVcDRC|Wj=v#f8iTbD>7WhD#+~t7l$07Z_P<`KQ6#MV3c_U35Jk z2wNM|jM(eIJvUAOdhex2Z*B-%dVnReuD>KEl$(FaMWlP)_Q7A(ovpcKZCM30?)2!- zZzLm^Zbe$z2%Zhedw+p)0ANwSQ%snMYg+r*sebS48+O5=)4pSH7~&ShLgJD6s^`pI zNgCF$OFdxG8Ha%wU^p=)xyp|dOp&Pbe6Cqmpm(lrF&r1heqbr-xMa0dE>QNeo^q=v zaHB)3GrKiG>$jxTDcXXk=H#+2(ZWsY?7w?wKXLS@>~A#rqL$`!GthLnhh%_5S)tkG zTtQ-nzrt=LUq)Q-HMoS#TfwO*3@TxO!b{_A<{>_)(q%)?h>l@l z7^_77OPj`Y2Hp|cWUaaZxzARsh{=Xo8LLASVAjGV5SzU*RC-xfbc^}B(6L%EW*8aN zZr4}wcVdeTOvFiO0U$K=v%+sgH>a^fb8et9=uT51Ar`r1h-l`EJ0q#;t}oDGiW+H7 z_$SUQE98#eGd-T|h8j*|nXy=xb zw+5Omm}%bb!;nnK{&pC&wSJwBs8(fyzN7Tq5& z+{Oy;-+R*RAg|T2y7|he(|xG)@o56(C@ovVlnhi=#7Y^Y0u`y+#dR3rWV$@&iR>G5 zvRO_72**c6I{-|+W!59Brdxfh(+PRvNN#l}gA@1j-cDK!ReG5S4dd}LXBkl3C2vZP z@+CQv84}727k#DX#|q0g2q6~Lzb35 zYMj3@V&zM;#xL6x!igGHt3(TZ_S>H`$Az5VUgp+r?f^1g8ynpG@;&z@%%?Z^K`8ZI zt@+4_PA{N9?c^@ItSQ#cP(fEovz|u~v$-EsBzCFX(~Ih-OW22)R=JGsQ6lu!e&R`F zg-n1cwx4{#7h4+o_Q^pzo1&Wlg{^fS#ktLCv|Itb3F|ooS)p7M>ibU(wN4?leFaUE z@Rc*q1c3AI#}jt;UtGGe2QwBWsH{@;^n;kVkv8y8Wl-jKmeLq0ou9??=ye-O_TmLR z^#tNPYi^Bt=ax++8x%^5X$E$XclmpJ4&m=3^Cz8;wFbE`&IL<%xuTZxP>abMi9OoY-l<4 ze-wlK%<#x*LT9ElvwxBh@jY2!+0Mlo3#E_~v1@)p6)9PY4UsaU4=pLFHM1i&XLXwl zEde$rXv8+Ot%#|J5G{9=h^bbXJq>rt6p?L-!}{2h@ycmhUHw90h3_()JGNJ{)G>e2 zaI#EUyc$^oEuwv4el4Tye40BKRHG$j({AbG7w_dwm97s+bp(QEEib(H)9zixRhw@u{ zBBo(tw{!7K)-a(g50*a4Hd~5|Q5I{Q-zRxXs0*gX*%O zt|%A@$HIxSp=!a2o?MDEm+)m*wLmN6$uM*1Q zO7p%!clFRkuk;`ly*w66M07Iszt(i7TVf}8E7ojaR=F@)j&IA*g($I<&IfoHFjRC< z<+sEg+`r{h*e5ZM%0Fy`En08hFA}06aNU`t^cZ4tZ~TLtiGiCrc+LDJ)sM~3I~gW7 zp|+*-kK!2$Et*j|Ezw)*zdiI-@u7mJJp+hqfQQGR#a@1;`jx!`a!p?D{tg{d#j!$;^fZ%E2)eTGW#TL zf%JH1=kfJI%}HH)`mp;AGlHfBFA~qEr|GJKI>2^(YqG;>!$sHj`mq(vk1x#Cs@VgR zO3(jbt;*BN%n38rOvS}4t;?ML2zou($I2*Vo5u`4P8vv2U7=34{hP?17D*mm$wb(nK9W!-d=17(l)RG5YY$tN) z7`Z>w#i@b5))I*W+TaVJQc|bh%{cRkuIbDxW@rpD5-Tkz@a40(RLxkerwxx zmTDMb7Jd+m9n{mtNf0gZ%+cO@(nUu@bAL%-T#J-`T*MXD_?#J!~khT zRPa}`UZEOPC8xnHv}Qli$9A9Q7wRf?0-WcFwtJ#3Y5zYjR*$WoZaQ*IX>9VGsTq7C zjJX7mW$WGTMau6c-^1(A>{Fw%!i<2!=UjY0-nh!2)-O)kM=N*7e}rj=)!vKR-}Y=I z!n^KOn?CjUv3(u7;6sF`&O8~lVnpUfXz{46kvhIpaS(>o-?zhn_C~+-(fXG*TtCt$ z%KzHDX7ToH^~!zl6dXR0?T6k-(sRe1pqPD@85+N}VAK&7R5v64c`i9z9nS<*N^d=q zRCYP0Z>F!Yq|DVOm8xKzT^>^wrpt5DDRk%2v8GL-!y2I;FUsj<59RY{!~i6oJqHnd zYTf65jQh?gWog4UF&YCDwt$4mtCKZ(1_f33tvo#xq0g8xTYTdx3M>!;{Be64QWe>yZ9+b38Hlkswh={i2N)`>_vf1ilN z3YW^PqE_;RYS4U+tytQNWbE)$(#LsCDrtVKS*>6HjtR^xy4cufrQVq?9GH;?7qeKl%1i5?z?UVH%1{`5h@d97-K+M!C3H$CzpcghKh0e z_O$c|WWF7`2-^?j=~oDWSkYO~nTbc%b!un%iXd+Cc6F^@T6o>A%yadQ4)Zp5h+yHk ze#_R{6Zgj=YM}tPMiKyItZ}S}+NNfydf)}xoLuK>P`)}-YSlX5wqj*>CxPq=|Mb%U=lM>j zk81eLH98#J3lKYOWA(GMBQZKDx#?!z9U<8QXiEXtE^sHWV%qN z>#X)$z4kK4p(z0I!YHAW$4#s|kRM}I05)ZI9Q48g0VUAq*`%7rX2N$fD0PiHsky@8 zNL*5Mm1aYBHnC!sHS^z_ANGh#qQ*IA&)WJ`6u+D3Usq8&-Rn54o3!D82wdF5hFy?d zYuj6UyF1HE!FGF6iJsXDn_JB%x}1O9^l0C8Q!D;{5BCEKJMhTBJ?x^!S#RsTGp{L_ zGB}8c=vvYg9>z}U0*t?fJ*#Qk6ox?P_==tUF0H5(9YL1v+BB9oTBFsy3hUd0$Z1;5 zB4!S}oouyG_}~eSiX-c2pM51zAc*0OR*;P&s69R=fOPqnxX?tA$|6*3`v(jPWqz2f z4tk#3AFd2w-YLtbHE_kKQdTj{flp#QSh+7;vQLH4I;}de$(Mx0G{xAEqZ;6 z$X~0v#Q>t7%c1}niF8p~PF|53t+XVj8;xlVd#3}Z(I1aRwxnx=&Wl$al1(975@9^I zi(OBPdI}}5qC>+TU0_Q$n}CCnDm)rgSX>JdUe|4@{{B1xQQ$J-s0zk83eUo;AYsanx?NCICR%RD4HN1))4GDa@>vWEzOVjS=P;IP?ZR}m-dmHNarzaTT z!2F;x1jB9)-o7{vP}3&5wB16w7u2X_?cY@hK;qQ$U~uhUqeT{LCJ>0cb>Morvy*ds zeubn|9(6ZY4Jir98K^|A2@VoJBHhhV>^(kn zXlX7<&^bQ%6mI;|v9IX#4YMsT<`GD$3-d14h5uB|)FR;D+`Xp7TePRX_8Ya%b3&Bg z49Oe)Mpag*7V^e=5924}F^@uME0sqbQnbo#Z1hAbm0Du^LntfL1h%f?|TqEle_C6F*e zQ35aFeHzB_w-s*o;>{m%`am0Y8L!KZV|I20%e0dLGE^0IqRbmc^yq*A;oL_MZt)rU z`nIfuW_f+yn!I{4dv(Q0CB;Sg2aKPbvbLmD(~LxcDrU9HmZXjfV|K{#(h`U|(*%^) zf9jMwu0U^!r2M6IYYkm8w-A968dzg7UZ#Jr)WuHtrgIUVxI&rxJ_=r;etxRW-p2t6 zw#C}i8oX**rnJ}~_fgp!g^ps0*Q(~GKhm!_6DDKKIcGTg5}Wh-_1%){hK5GmOAtiU zl6ajm%+3B`VAwTI3wKLE^fVvn6&|2Az3n9d>$g{LS|2@Jyk|*(EG%Ff+7&5O-522Z zKr~>uvcLC`+;(Bt)X`M`k%zV%Eo(}gGdSgD+*!j6U(YZx7bG&u6> z#)sNvO#1iP1d({0XQudv0bBaqy=t+;RO;oZ8I#s8c(W;UmUP)+!6A7iYYwmSb<-U> zE$@liqtu{&8Plh#_#|MmY!m;T;Qx7zPX5npq%1f>0W9UZO9}fN4%nbd;r-?2@zG%- zm^w5ELyz1xdsLlVH+4*YNWIruOI{;-jIz4fxq^eX2H&HP^Ev+|*xx(uk*d}bI3Q~#R z{De}&_EMNBOp^tn^$qMH1DPp~QUpu$ZMObyfUbd)Hhjv;ZKP<|o&GM^%-d3I7p#&H zwUU(qq!p28&G-9F!G|vJ4mKc>ml?@*5hX{!3nh{0PfSx$k4Q7)cTL+00J$z8O_7jJ zPniPu8Sc?c=y2CXB@P}>0*T_~z$uNd3H|r4M!!51@iP+z@9N|#K37uh1I!I%_+kjzKrj$LQ{az44O{I)}WF2Uid zgY=e`Dw1`NQiEpClJ+)lV5W%1aU53p`aTPs^DOXE-z;RIo79| z{u;fmoj=@^sy2lQyF?d~*s|OUK`x-IVes?gFs>Ziv zX>Ff;%YO4=uzypQ<5jYj1Y(yiq2{;xz(F$BHi(+e#D_23HzU2#+t`T(4)~Su*5+MK ze;4sITGo$hSw)&9yQa07haw>SHR)qdMWv9utzYrYY?VErf2mB3pI~*6+Ba1Oewr#$ zp8SW!VeW^XbAS2qDo_zHy|p^{Y-0H`UstMaXM!xOU~lVq{VYuA*uG3_LhiDnMiLZ2 zqTiHsCEv*ma6#UboU|raoO~d1iPKaM8^>NH4hp@p8E&q3_*S$xvPa#ah=7^&4h=dq z%)yvIi3JG;?^@EgJV=KiGTjEq-$AxxZsZ<@sEE??5L6Pl_?4dK4_U{aDLLW;J)H4g zZ@Jb7+b0iSw;m0tajdf}g1tr2&JIo5 zwV^QX=!T4F0)i#4it1Md^Rv#0+#(CM>s9VY+wG612Mw=t>&FSpT($=saOmpp-27T@ znPi37g&HBRY=)!HoC(1GM!;60L4I6fr@9iuj?kf>&TtoRW8iz(W&60P%gIJcZ=!nl zR%qSYpeE*?H;+KcovW(w$5OSS7%S)}8XWZKe1vuf#uic4;b zh`I>v8yX+>Fk2S%bZh=W#IoNVaq(iwkHvpcr#Z4$C0rx2SJwdB#Ux$|=)C!c5Ims~ zH?P2X6*-jc>&|zmm*Vdn#gZLDsMTZevlPBmisxAtzx z)-7Osb`IKKQTKX4exrN{LB zpmb8(CuRh5Wz-g6jgRg6u-3JqcfjWS_4xoDc#rbH4<>@_hyAGqkuk7SW8mrby$Zda zGU^ORf5(WAh&b3xXz!Z^NVK)b4P<5hC$~!h<>gH7Pjf~kX!m*jOppMVrnKdJ7{_A* zr2?&mfihL>=U&8r0>{iVplSn5D)*wCbitKj7mM?wD5A{3PE5Bm6O7#YTo|(P`rPRc zU;SiJH5OD*Pz}nqywos9lE2gRytxiqzFHRM#ugXIVEDN=xE5&fK02MMf*$p%Zf+hN zqL4}aQYTv=0(7zS&vw~HNPh4pYjMNi4voXx)ErvNxD0@ny zEO+AjXq}hj(H?5*6SAh3Y{8RQJB*mp&#N0LSs(h8(NhI)@@rPg%9@Pm#L_Zv+F54= zn8S6zu&1cy&MX_lDtWQeN&eobr@J^O4wa;gRX(EI#N)ucX9A_gjhJ12|1i`PlE89Y ziwML!?JzVF$?*@;e&5doc-O&}k+o-tX;LWE<`EorHxFgd`$M!LRV|6>moSOzIEN@s zZe4p0Q4Ei7u#%Jc<)MpP87DCi1c5eH2KxIlF?sAPgF0rZh7!1 zpU}o{;NRdh1){dVdKetl-qn_^LA%U$E>~r`RDIX-)p-!60+HTNLv|j2GzmhC1Jnkg!o zr78&&7QB|KL-(9O!mDN8u(>|3Z8L;;RT$*1)x!Oz&!@Et0TRkqtPl(Txa0hIQtfQ6 z?Scy&!@p(W`^f4VC9UXOt7{!k3 zr8FkxN0Oq_RuX*=_<*2ZxujC@X(%AzBtE=|Zt5Caq7YmoxDdTQ*1?!F@)gEo859*M z->=NB8Y{y1U{YMt(mM;e{k-r4?6WB^KgSW3YdjhWWtT|RVOs;C_sgA+RfWE{z%Jgj z>9hVG7N&vIdPw6EzeZ~|xvxcU36PCZ1nn$yxM9ZpyZV)MRz{H&Nk}{0M}E9A&2L9c zLpYvR&^2tag?D829v=aMpe4IsYHe!*EE8~GA?Z&hw5HlY2{QKJCvAX=( z_q1OGZ)sFxckY`n`HJR!qJ-oIthN1;JRH2eG3Nti8dw$=urz?=64gxLEt`J;GvkE1 zbNn5AW1&mS&65?Sr>Pn4gsgH8nhlA=$NqrK4-A{?+_tS^TUkouDBb@eGH7yCqnnF( zz&o+}eR5{Pf|9+k#6(12FMu^MtIV`4HLYrP;tVKd_517SIkjGU zCDO}vcdM!cutifsA~@C?GxG!-L~IOP{Sl`3#1IG7^bT_gC2%iL?B2~+fMXDO6(K@_ zt^XaRhbo|wD(-%#>ya5q;Hn_7#a{twznFoA9F^~nZ{X)t%OjtCRVa$?u`3$D3(b;> zV}Yas>_RC}C#u5+J;h%lFM03}s~lv2@8ee}0CGiRq>@?vg`;_7 zY?d7f2%8Szcwhc{S!>WI=nL?*e$zT_Pi-*^Aw-M&14s!rjq0b?!v-o?rt~h+P0=rr z29N6J-RneCRV%4dDM^Rdu9bo% zwk2k%HO($&732t-b1I*B{kjYb;B!e{kN-7sUDTK#(4OaT(p2OS%cb_JU1}z+A=`X= z8#<1h(pe%nwbUY43;0R1twY+C|FPraSz=>qcQ6O_x*xn`Y^S&Gw!BNZ7R*CMi-QA zY1XNKM=!^smZY=^`8$y@V}jve?>XOr7PHQUw}ZTA| z1O>&m6l-s%h_Xzr=;s!(^$TZL`+0LU6MP(aT1P=otKXjKbmHjWH_lU3kQ4enj@ppo z!pSW0C8d50%EfRf+H0(teXmT`R+anU7$rlA8$+qZz9R{q*h;9)^R_?`RLVt?55Gd>7yl!T`{wOBV1<)32RmQ5NmdUIdAGA z-yfc}c1_Rn+vq%>>HXPwsWi0%UaOXlY(8frMNKYlvk?kApAN4n55*<>PE-VQ0@TbU z-~Bu);14Wt90Pl^q(@2qs#7PSyE-9Im8_({pjuFR_1j7Dn>x%dtx=x;6{5y{9g5C#o=(Gg&0S$zojnQMGB=wh~2AGVrCt&BDw?KA_Mfc<7qP{z|&v>h|^TWNQ)BcEJNZW8Vkdw^M< z_CM9MYgXl?9(y+Ck|+j>Qa?u_(PIC{&~lif&ypmpUIlt(-XuT$y5BwZ=#9yhUH)O0M2j*!f+5#0->xGo8j#Uw>yndH^fpi_NR7$tw3Qh2_zw2T|?SwY|v*DTl<0~c?`FP6f# z=-y;+%T7X4CjJx2?3Qhti8>^vSv%Ida%%9&1Ma=oeGe2T*73YlIYc}wIC~R3D_f^V z^f$06D|BT^mJ)2=1AATnG}Yn#5>OY+JUO@K8KJ2ds8PpS#-Idf?2ksD`zD?zJpq$?_KtDx*&blRw* zr;oR5Jl34=AP$~GcS^6~m%2JONv&2h%EbhA?+ccbm?VtHU*)_|uUR2!0eMKhx-Xj= zIkn09Yl@R=R=QdlKnn)O{!=ans`pi)n+P74Ad~G;5JePS2uVUy5U_)QL}5%hrZtn1 z<;2#?ds4C$n7WtTbEqaGrA$D%bMt98<>|=+U z>Aml>Au3X=#+aioK0(f1Fo`MbQ+O*&t^cGIE5NmR^{k3*!bR-2Z`CZh!RNd zpsqHuv7lz&8#PoL&;`e{?}^2utHXiG;|yt%;hD-3mrwrD4n~d}XLaFlAwJQ3xEg(T3xCIT zV(t*Yp<8GRCeI zrE83G|Lo7?#$Prrn|ju>jeQl?GsgHyA=6$4yEh81KRBR)YWZKVAINrkc*0$zTFm=x zvMilXn_O8$(WNNA`%g?_sUiB96mcfo?cSGREod(WJ=JZ4T$W~#>K;#J%U|>4FT!Rr z33c?|%nj~TwrxDjkOPZkF+Hk+SYd)(<_U=*$K4^~;bxyU+#<>bormptPo_>=yHau+ z83C=2Vyjgj&lj$@1R^VG&%)sCX$?bJdCwlNf21cng!709GwV*OeG6;Pa1e<4W^bd< zSyh$(N^?{@bil(oZvP?F<5m}KjIP*EJ(lBwu}1oFT#mNu@95H!-tR_$f)&K}$Ku3$1u0Y%SLGF+J^RaLmHd@J*J7tIM!Re+ zJ?e)w_(b`Ncv|uYyh5!@uZ3uQ;Ds#{R>k*(hX8WeN-(*`%GJ9g$NQsg=J)O%4~>ml ze(&~y8!g-=9KdDfkp_OcT#J}C`p~p3!BeSK^mB>e0g?7mE|sD9-?T!1L)ny-vpn4C zX5l4DgTvEG7><^H1m)a__xZ^CGQugFopxL-spX;N#W2}BXRRO*RqjG4?wlq&LaVJY zL+hvJ^F|WEIpesB_MUyphDObmnyRNH)hExy^k4=O2C(PE(&7>EC)Y_uz^*Use^=jm zVvkWV_xi2?f&)v)CJN`@fxh+?w0>rSsmPib`V=&>Qu-DfuHGY3(V!3B+4UG5z|Qka(|w~scO|Jdthg=_$($lXNywvfgetUU zVa!BO0O$p?4vC{fDwghU-YGJ-aL~S^7H3bkf8K5JzfTc#y`41yZ=b*`!@raF*RJ;u z54hm;v4D$ZD~uX*T8(1XbE`y_C0oC_h ztbom@qXz^CGu?My*LRovvURFIGpg=0i=4SsMm1u?xk$BYX!a6(NP%DRa9$?slWpB6 zS+6CVCs|$u&mP~sXS5~9iT7vDbeiv*r+BUTd(D(DaiXnsVH6ODuujmkes|5h0WQ^u zz!kG<;0ms#xNP{OL}o7*txfs@&P#wi7G!AIZ=PhdAuo(9T>>^o3qjn{TrF653#2s$ z4-wZIpG61XLl+-RZF7gDOv+%v>3bEeTUZjlwVVOrHd;-4#C|Gftx~7cQ6kr3(?*!e zs1+&taPC&fHt=odE<)x%9i+!7Bpcmwo7`qER$3MJa0?W__$*2B_=Ean@XZi|Lu3i19{2qS zvpcj(md_^&_M zw;4gohckvn@Sk5+*>La1E^Nx7nl~IK94XN#^h330MX8^9=25S&pU}9sTV*Qr&C_1o z#dW)qWjmL)WcnXXiTsK!B}X;{xO7S-w3e1F(`F8`#8E>FuVaz2;Y^hU1h{IXYb=q8 z>1)u3>j*U*qQ7==a>p#yk(88zv`n$wX^1`QZ*!47@zV+q&2N#A8ME5&s*aMyn13pJ8B9E!H-NvV*PSkt85Chcz zA_jSvr7am$CC~iXF@ckhXwHCE0bS9ESTZ2w%m*&Nwb*Wr7 zgsPSPnOFLOh{ODGBkhR78Q5nrz9zo2Gw%Y6k0hW%1%#`=wr3v;%ru;EyCs!(hYknS zT5#nNdd%8KMA;BAzviN@7Hj!g3%Q_<_>0op7Y7Zc5HC_ge^NYxhQlXoOOGgc5a@)I z-+_7IROtY?uxc6-vEWwx@ujq{Ka3Qyh~v#?MJMZ?Nkdnm&|044>AUE!_#dL)F)pxp z4cE>#r^%XZ+qUf{yC&DlHm4>VlWp5I*|zPsz4v*~`S4%g*N27Q^W4{USAHdalcQ6d zwVnun9PdcABtP3g(-&(#>5-)uTskCm{yw9|yZ!p1O^Y5be@{4ef1d$oogj{E&W?j* zR1S^MN^FY|4T^X9(g)NTc{vQd={ zj{HUw^D3+FNt?VFOr%ZShTYmdgvJ&aDOwNvF8WK7zzW8s(nFI?8HaT^M+gd$+2YD2 zr{@>G;{ym{LRF@gbx}a=jVgugWBvYsFj%>}ae-m>;3(7(!sD^VT0)PEXB8r15))M- zgc>yPjOb-f6!~MDlNfMf{!pM-yFEV!w>9o=$rG)UfVMy`Dbo=A!@EdfK$VL@XrhKw z?#cMK3>$@?%_$&=z$f*iAYT*fVZ_&+JOxU-gOXU`iWjQf>1CG=O0xHTmhh>42GT#I zRB6~kZ4%I>)y8aAZF?YH!F@*1sF-jIDbE7f`KKJzLduzSeQPCG3Oyw$Eg_&o3C5m?BGK!|CZCb^NgTXVS5E2 zXz`6Su8)tsQ0c{FrLW9(ZUvpooa&r?C6viok<^b0zv6DNK}YyVNZhLu7*Jvq5Lvu>dipD-C{BuY@5|l zICz2h8KIcG8nPI^X{?%cKUFowe!@j8h-6hYbG2~LJr?mbrM01z!%DAVfQJZ+oWsK%nkXW-`B`*G++prW(e`^ z5NhnrF-!(oiu~9m7?Rakvc-b-8s9-)X2$39DGKNGT=^$1*+pgl?T(}Vz;n6HKye0X zI4-?X;dq!kvrdfKuJ3RSJ?*xQQGeeVs&3iiKD!L{81YBlsNj2p;yc}|bmGC|4DYL_ z03k4PfNSd(2Ye2|glhQU3{piTpTlI<+pd(H?*KXSAK((xmjt=%2ARfj z*sz;d>t%xU0f~Dk81?nmw&ibY#h|M&VJhap#q6P%Be~P@Y@$j>jDT^o7IOA~kS@Qq zF=*F%%`<(xu;G}F4Q@$hU^6OJKumX%ELHTZPF1L1a*w&gL#@!Udb93%6QOQuO}2+FXWC!{^Y{wy--w7JUkC*U%Lnd`SYluq_^m+UFzA-|ipRX_^!JXFkt$J1cA&!Lc3SHU2VeZP4(e zV)Y}>MoaF6>(Io1`ktUYyWQN0-Mq@%L{br_<~eHLvg)HUy~Xr&jWm3p(B&1ULq!I8Lw`>qYt-0|3K^Y7J)AIboU`E$?2fmko zLVlBhs?ucq&`K*y9U)h;>>?w9TTzC>Ra&c7D6qkd*Aq%WSm$wS5r0~RW9vnVNOQWy zg0=d{=DfW-q=N@^4LEl2V?-kqSgi=b2N7)rllG*veyaZ{bopmcHAYuKBfqT7LP(+_ zarTqd#*#(W;#u)^Y0<^Et0Y1@^Pg8nO8vx_i0noR&y=e>YN-c~6h1YD>)vhBYpIca zsj69WUhvznr~dB^Wp>m8RD0zbR5C47(|hq7Pun1~^Z3`-Z@!W$r>}%QY6k({uFUT~nZ-v4AnD1E#8HQ3Wl%UspT>pOOxST2 zgfW)DM0`crKLNtr*1)B1_aI3xzJCh8c_~o)juedjlF)fB&#F{Uhz-hbukJj0 z-n`eG=o`Q4xGxX+__dv186Fn*7s+1mUfgZ$Sz~|lHe;(3XSOD*6L)3wOd!xCWeU3n z^_C#<7?BH1W4$^5!xudO214|`0F===Ab?%=u-gSmm+tE#d+5OKf^)??ul9U-OjFm_8k#_Ol8c6_MXG z_LjDsbiwh10(IRy*31f9EG=Aurc!|s>uaU>T9DleG8zL%rE>~~fIV+X!>H?!cD^3L zLvQ2{u^$8;YFJM}E*iK8d+nkPBAN};fcTAYt;oPp0*c|dgS`b*O3fMkmb}a05}|Su z!s-W0!p@UGi|RyEF>-x4=wZaN2NBBS;oWO7j$EX-D{5y5hNGl0#TjUtoQR%F3gBB< z^_KPHeLP0#)$lJrrSUD7i~EGY0f5_baA-fBt=Q^9%4A4L8Do{{I^6w}TF)3Qah@7~!i@HBsTnRM9Pz6O zR4OW~IPrBk?5^b;EUQ&8ZWGHNUY#K)L>fDiDHv7hu{x!Ghed9wXDaEzrfiuwS$icR zvp-#$B*KCLwPrWBVM!j-JJ-R2>i&@g>{?bL=$1BdEjNy#M(1HV^4*De6Trm2XnL1Jq7;4;fGeC!XqLZ{~ZhXvvuNDIa z=Y_5GfmLSmJrYV2u`z~HceEOJ>|i^kJZ^;EpbW*{WSEt8!kbo#kW^mmLa?nX0;7LQflN>d>S#(CAm>#P<_os^zTK%<3eZGnX*AZWOMs`_f z;%DK-?&gFK-kuw48%NhvXx}>3w+r@vh6x(X_U=_-`#i`QKSJTt>`7>?q|m%_S(19B z^Olff2;~MjawxRt4sgV)Ab#V&5VA0`<*5D(YAf6lLYE|WNVFBA23+2n&dBjkr7K^f zehj#aO9yymAL?!Tx5XaF?C<%A!;?(7_WvmQzsn=?wcvBuMScF6B$3HbSNgvyyY`P& zFXu~qAF3+I5P^m03iUAi<^jHcX1CxoMMCp;8eOL9Fy1iAr2LoP(5F8{WfdKhAu#v3 zl=8@-k8_OyTBF&7(=2qXpO|e(VqNayy6@CR>w|s^Fw)RhNzE@*N(Ds5tdsj(j>|S0 z*2?Oa&`V4wuryJh36rV5^56u`kT4{9jI-0wKK^k@_(y@fzdmUfa1y-j(y$TMl+g`G z;gP^M>%x1SQLb^w;a2@E6Ey~Ceu`R|e2_TsqWJ?ruWLG)FX$SIn1#-6_Wh%S2JJ;e zgO3I+g9rASyRZ9h8%I`@y{P*xtOtuK!}=d1w6Z>t(Xlm2Po{#n%jm-w4h;Emg>6{; z^Uk!lN{xaZiWN2}yAr~m|2iACGSp`S7`T7fWR5P$B)fNJRgkYac9gq-s&4n{CKyTj zdk?e#y?h{F<&k2KAb2mi0I6lPio!o;aa84wsBNjUSujeU_oLQ&O1scsRp}VqqZKaW zZoJZ|HB#3acJ&^=en18z)XN#!gcr=~FGW~!p|BUxttq05Gtb=MpwT%mo^?p?TD7c# z-nFnQX2VX?>}gfDQ33_KBOtz`uP1P#S3$7AGZ|^!1BM))*qM0QI0Z^Bxa_+n34Q40 zOtZD#ltqzexSScQU5qw72<`Bqor`I~K2MQ5{5QXe+9aYScd4s7{81mLk!5-td0(PT zdGr5bid-&khs(8`)4RrmwwmW0ITD5+jfN7vu18^qm7y`L#1g8j_~J2tNhDT4jer7M z5u6Uh+G|0ZA=;(p4Gc@#d8l&B4kO>6`u6S}%h08+IHz(!1pGRGIXtRVJ2ogc$7s3P z(bFLy8Y3u$4%M;ok<;2~IGj!@%;YYYZyBAm4_Im|XZ6yiZfGf70x-fOKYpXW;7Pbc z)t|@W)3y9ywYRH;tO&vTA+YKwAqs#j=1*aff;fOw2;lh~9Nl7yWs~M++obhKWqs*c^ zR&OLi^vE~Fn6F3IJn`(tL?I^ev5vs5`_sdzEfB~ya1E3sZx}02%)}I9Lji=+@Z&#R zFxz?itM6;+)b$h#GK$3>pKLMX^yln!bdL9@LwRw*F zoMrPPK^yJf2Zr(m_0g#^=OCocW$P5vjPM3yAZRiIPrgbTpJhr;fMwFBz}Voj57I7y zz^Y|a)<0%*HZ_U{3UrQh%eOYy2{E)QosS97Hr%cNv#C#W#y@kr zeSM!ewB~^}?cOAdUIl@Q;OlQj8FUWv17gszx7U39!U0T|d9kvah#w(i2(vx6r*|<~ zyyNC`{*hBN?>*pRZoXVJ0jU_it*N7txR(De^PPrF~$273$ zCpRe>IDERuuM03pYAIIqD($9A6^qRqbef64c{c#-X`_<57WxNCNUhl4pw~%5d4mEk zb{uJtr6v-wM-OprYnQkghz#p4m16K6&?Y0Ti1Ei*UPLv(KkobonPPO)z}lp*&e)7t z7}4b=7RNP1?9K+WRwwP+ed0?(E?EsmVz~{Z=KmNo+2M_d44X#AyNDf-y#~Z>#(J@E zjOhUpmt-&ujS#jhwzvE_=gFA^y>g)hlB*upHP;*)+V0j|{R<>9jSWG3wsb=EA!pf5 zDW@ke(<=?^`g0BJm^J6xxRQ~PvBpz4Bwkfv_i->Q*=m;+E*0mY;;LAFS{w(p<;nkW zhP3DjV2nuUSLYQqmZ-Kb_OFwFD;_2=ipc^BBhoI%J<^Kud1Y@!(DS@Yw5n!G-i9WBG_Kkh zF(d__V?nX6l;O61#L?OiA|TAhdhS&C;$k%}Rg(8U*dZ{NLN$rQ>*N#OPDnm$7jyz6 zRhF1u&31N?)nu;l*s^HMbG(AWBEm(j<-%RFrR%K%3YOcRne-_*+b^>`5wPFPbX@$V zik;A8C~h)2okV48wrPe8sW0YQOhR3qTDX>PN%_uMZkuy=ZvI*$a;ShQv1i0}piQWs ze>N_^1lccy_Dn_71S!z;XQoZE>&XLcS(omeE*N3>=Xgk6;u%thA#{&j*>hiO55jxx zxx!dNd&f7dp$7NJ1vcfN2xuM%%m?{i7rmjkdkR@?c4OcA7Im<*sMv;rhPx&br~@`O>&+?2x5M-*@rN$_wyy~LkfGWM}f|F zX)1d>#u}ZrFHYF7j3*Zb0!70|tLbq-9qfnw61H`NUgvB_I=DS|ULi0JQyZ4rHzi!h zEB2VSpQJ1nVWkn{!&4kvO*1q}tGO%#Aj%OKUB( zu0N}HudTjC3rVav74t+v^tPuE1Xt%lh!5?!+(wo*gWi6Pb)RMC2IV^-4d8V!zN0(7Ik$h!e z6szc2g>=l5RE<#!%jBApMx!uKB1Rlop{!^@zG^3{U(RIGL41=`yS`o1tJ$^x4(pax zPxtNb&bp3)<+e?OOW!QI)VgCYWtAn?<1aKxL5+r-^>`si_+A2PvgtCmS~G|N1`0f< zchM$X2_DruE&7rw^|2!(^%Ba{O^oZafu7<1@s1oj)GCsh@Do}`$H)~27B$F>Qr8B6 zsHP=rizLLs=*IhM7LWCg^et!`iS>3jlt{Q{);IwqM=MQJ&37dm^HjQr{y7z4-hzL@ zvz!T5q(0J?zs9qQRn8ne1!}6%QA3H(^#2=#oWpLqd*evz` z1{6ZBNutsuh%0|Zs_)a`L6`{doZ~-e265~?u5Y2f2e`b4Ms5glBx(G64?I$8Xv&|v z%lQqf`(9Eey7nhmvcPjS4o}$!qUs-N5Xe7tM#7u|QNHl&T)&WMh4w2bND_eydml|f zloLkMo$cidhV!J1*0D=4o3%W+J%^(UY<4a{p3BhyP|SOLQXFfjg!GD*jv24466Hcr}Bi>&{3PB=4- zF{MXqQ0xj$Nx5nQW{SYa-~t{7TLhltAFg?+oxMwr*pm!5 zVXN~Q~UW_*0G{wv%C-? z?!<=wi311UBpo6!-Lhn(U{l36GNr}9|0S;`BZ~F?q@{ZHjz4$THoVH{6k|3Rabvpl zQpT4c$AWZCd5wj*_yxPyD>Ej6n&9-42(1GBo+Qq^DIW|D=R` zXYgS*`8peicp`h^t>$8gG1u1o=vjDz@Phn}O$JuzP?I6()tQhI=^hs(;xQXYZhnP0 zt6+%`kel7_`-=G{yMk5&S|a*4&MhIL!Lj6hL{+N(!B_;w!F;hPU^*dDjqzsNa!*2{ z#H7-Gb*yeFX0QX_ez0@?f;cEe-auu3P^$FB%3X4c zkNg}V4Kj5*mwHboI8{4TiuaTdfTHf!nn#G75T^hCu=+SltF=4d1Qz_ux|j@eZs+IMVtB48+c4+v z9luW6(wK3=e{pp$_+%6$t(%xj2>ByXByz20s{ZeSgdD+~9`Uc0KlhayP<)fc>yaGC z)d~r34uB#)qDm+l;|(x(_$@lWS1?C|oIqR9kK;x^PojKD*3lre=uT`0qQG#9s+?Dn zi7(#O!Q}ogmABFWC{eNkCkPoC27+K^Am~3vct?NuzlhZTa=MbE|RC6%jNq zt+3}DG+=CP{$~(k#dYVI5o_+e-=CIs@*9;^jQa3mrZ3Ce^ zcpx}cAxTK0&K0Ggx9Q3CvuqO)`gZC%D1n=~BtuZ3;SFIS;NwRQ)&SR?6@y&_7Z6nS zwDHt^qSHQpfd2u4)(6W`IzUZ3@aTru3S}onXF! z$5WAN)`#fl9#+(C0;-^wD?^Ni9pEjtF3*j#IZ?N?(;7r5E-x5omo>l}slXp;P18n-Wu>2O|+A6>j=qA)oGUYuTvD;F)t8@dNmO-=0HAd2Bmw12j@AcGe48RJEtDOEbmRCvUw@y%AtX-^8;6M zQD~8)QGHpPuccw728y+m70}3i4;~kw2iqr^mVx$!6^NN(5fiAXkiScGI8~nhf#Rc# zQ54t~!E_guYmUHYa)cxD2_T2$J|e~B8W2_{TKE3)U&eZKYL4;i5M0t{))|?+ZZ&f& zH`KZ+kciLGtQ{KAs&t-oV*4J-J|bp_(1^IqZn_$0E7FLFY>r!b4m9`%puP6b{#D!} z-of{C1K9v|hu*^9@|yvD9CrnYTouBGjZFxZts7v~k*#YW^O9_M$Kjq=R_|9&9#k)7N&FBJ_#+g+`^-UfX-*wX?{AKevzyB2M zvW^QRKyKzSo;#Q&4zPo<(4yD7yC6MAOSw?ZbyK5rII)vkHzXnU8xBDmj>dnXFwB5q z!8PC|M$zo08rY+0bx_<^|H$1ffX(#W`uIqa`%!P$5{SG^nyZH0kfp?#+4{JguhcBv z1~cmHQNPmfYmj*`{h=la<_YUK*df=SlG);f6WOdaeBw>L10*EDhsC($$1Sjhr(_%j zZ|N`rEXJK!HTm)LcD7+{4{k{7dG`>OsNZ?L;D{S^mPd^|b2BAk5A{6rf|>)1)V>up zD8xg~q3_c+&nRq?77}u;($ZJpnW7>7EWn+v&f%z@Fp+HSmKPFL2$a#_gGevF$ zk_L58Nji2nfTiUR%5OP^D5iiU*yi?&#tH*wFv-qJP4@S^Z`GoJ@BdQ+vRR}x3>-c_L`3WVZ}x}j`z47CJ| zT$FP3*>86AE0qug?+|v#%BAjQGB@?Cnxi7;y@7=sKx5#nVNZq~X)jt2*Ic0XNsLRl zt`O=9rvNqbWYC$^MNs$QWg$C>eR7J^I!_YVMdI>O1F~n>`l1~%6SvzySv*^FW7Xip z@+{9gW@-tXmttg%>{Uu_v)rtR`!5gp0pjkP3quSw9nzp2DK;o_96quM74@+MF6u34 z34I=HK;)e-bboO+&v-CcW-L%r!1HUs*{Sd;Ezo&w>Mg?L^eQQ|#;08z(mP0FQVTxA zk^H4tJSl6Cp3bJ&lPfU@c|FxcEyseh9@ZZ8S~RHA8_QF))RJ2bxrjbgb`pTSm+@TF zc$L=sg&KfBe(7k#rSJbhtmBp+&n#3ar^r7KSirAPm{b^?L{TJf>}yM|CV7@7B@tBg zSSB?qo^`0!ZA}&6rAi3dHu5jOw0lq7!S+FAing_hl&JZeVu6S~{Z;>~u92fI(p$Iw zHCVq2n}}9Ce26!+s~;ox{DYeia~zHG+5JX)9`DYwL3)IK!eEdYDS>kW!2Q@bB8UYjH?i>2D_LllmQB~+jc%B(pV7)# zmp6aBX`)jnx$+S%ka=2do3A!V1@!8Z;e{Wna9V%S9bb}B&Tc;Exx#$5N;;qev(x4d zuzy|_EA_w}eZRpn!Xa%NV6&wQI@ioHeScngKTW97cdB-N2e*`2)@*vwKDyFBu|VGt zQ6LKJ+osyx^JT-7t>$7SkAX=?b0&*Q6w(NLI^pU) z!zA-((w3@KuTj_YSGR@o&aEo|R$jH^3^->|5(ZxFp2n}|_&$W*GI zGjKqTt)o0sD#Nni(Abz4Fv6GFuXH7L;!_~Wr6L;Js1N}*vkPnc`*>y`o-GZG)CBBKxT0TVuOhurfK@^ zOY5VuqG%Railu^=I@Jo9X30BkwU{F8-!Q8=W*XY65*@gC4q-YHK%$6-6f#z{UIp|r zS?90#^9<+kiux5eqYC%~=tG=E>1NYqt?c7UzAOj#_;CA~pYdRjlTte2ja6%htQ;Oc zr4oV^$Z?Y3^5>>ZQQ^ioeMJH79KTDKAMJpVplj05XcxV8{qXGbk<@-n6ht30p3Ku_ zJH#hViYnRVOaZ$i5Qlh<1M`4YZSXVxej%W=LhAG-E3p@HUA6GIfH;xMb86%vCh^6$ z;*>LdwPVE@?cw0)7r#BfbCPQkFuh;*Kd!y7!ugGGg)yxVWnuF}VBOd~99G`o;sdjM z{>|RV6*>XWmwA?cWef3vB4s3|+c(ow(6Mm}X3G(NJK?K0R}yC*Z-u8CmO zn1AJx^4AWvUra&zT`hA?PdzOaW-TpVw#rGeD!tDn?pqFw{+)N=3p62+HGQ+l#X4%N z0eXo}c(ItxpL^H&oEI7vAT~)KV@$TzbOXvc!?qo!gfFz*dj@`xNWc9-QHx`}eE+l5 z5nuAwosT74SHE02$aGtdx#7#sVw*=`eQQ3eB>3Fv5Hxeu~SC;yl|z4K73d}$6>3nnjtYh_Aw zNOpAIrV*-NH+>h)LZ+2MJJsXPPNdb?PtRt&F001Sya$g3*$=CH3UcbS7!lW@2Zv*= zlokYv0LnF{r&1{5&!=mwLwn%EluQS%YA{Jh=^Kp@7VMrE|C3`vqe_aq06GUh0AV)) z)2NF1J%|aGeRf$;9Q>lrEqS!STN@-vb-C!=`X)qt9MV_JT}hu#jLQZZ{u+{@89JbL zg)Mh4?wL%9&5P@FB{p2qsSj+_yp(E;z1O!=Sk<+UQXAUdpD!3c*s49KWDj^Yb#fGM z3oT@$Zqu(OC(Y|ddDano# z&&GHQ7c_qxAgwA>;hek>Y|k4K1Zrb6gq42Aolg)r5Fh=XA1p||w+sTT{whjYtD&61 zqEGF6z*MMLexUxvgu;w#>IFalV*Z@+3z6g=IXo`Bf+OTj1BcLAF8SIphX+qa+@^!& zpM&sVhLtYGEViGf#?LY&Ty%DNw)g@uzjh{y2!+vdS3J&8pnDjB5(H5#lijdI&`x5$ ziKHP*J%_F$amb>N<2ygV>Uh~t*M#FQ&>eZ~n`Le6Lz|5n$|t6Yc4|Tu1!b6lXSD2= zh1IUf9}HHB$wU~+e=>vAYD*4jq>h#`)QQ=C?^B*L6fyjFC%PQkP{(@u4!j!z>B8Zb=NrX&D-gKQ6D=A>| zfGHpzKR7cq8pZrS+47Wr>fR~c|7^?3fyJG^* zDI%$Zy>zH@JlNLU#NEtAfnN}5B2N+NTV1OPaZ*gz!Ulj@{?u5uTJ>T!O@ z?X@f!QY0MIBDN1nQc{eSDnSZV)a$UN415cVx@}UVl+3ZI9T7Dwc|77I z;bxbxQo%mQAQRm$1XCs(Nvtp5HpCHxhy-ati3NqXx%D)N+0PLGG6Nl}d<(jUyVSzRMHUjvhz9vy z7e#iSE&0`(w4g7Vj;bhP)=>=Me{Bj#p&*awm%4sE0l z-X+%?ltCQjzRu#yYwy^ErCLsN29%@oId4-1>ryNxgdl?9#zHa7V9``wr6!xCw^wE_ zoHyy=>AzYfiEl|*7U6jE2d_zlToB(oZPLVf((9@C+Ww6H7lbtWwJy%g8(|D69;6kK z6SE)j7_?yO?72xO+#$b#zRXU_GX=68UIUh3PSnfi07C88Kr;n;JN69ze zDP~{~1PSIyW?)C)x}+@W1@q$Kqk%3BLiF8pX80g(+n@$1)1b<|<%mU|u%ArT*)QkK zaCh|KBtk#HnaUlBV>m@qI#U706iL|Uq62N(#rJHbFv24c-DJW(SA&!7q^Vt$h=oK% zIbmShDIv3@lEk^8k1HD;)ZyGTXqOj@)osY{FgAROq}eDJ zy>eRO?XpeT{0y&GDxUyJqXfgTj>6?&L4Mf<&fn@guriXPT}b8tu4pN68e{e zv0vkgkj< zxl*rez60f8A+=tNX9!A^?$NyaHKb(JvI}I9F!7ob%;$f>sBPa3FpvD~1M8F|S(Mn$ zTwrjr&q*)H4@^!}NMD*ZFxPx)yH^T3b%|!dC{1XSepIf(B zbv$ximeju#`O80zUP=uf@I#O0(HE%Zbo~wjJv0BjaMgEuOS+J4l|2&TeIfJZ_Tzj} zB8>z#OuaTJRfBr0it2VuFTpB<>R}J_?@FSta+Z?TV(}JMyGg!TZ$Nr!Y^D*hUKN;j}LJ`qwcdfce-5h z@ok3+`Zlog&dSk06}hJjl!80!rjTz``>V6-t=-M9vmW^nPAJTKJ$*R8@qU=UFc05r z4s_J8M;j(CDeRm7+G};u2FSIGI{B3h@#j9{FHRrnd`q(YGHDU+)f?&M6(qZSSD`7} zNWlx!ZJQpCnFlOvQ|IdJ?rqyH*S8k!mfQLsDgd42;>zXL0UVA)&(}YX&M(3J@;eHM zGTe>(Yo~IiUj|Ci{-i#cx0?mIS5EtNt5bT|>D+a<^vNy_(^0SI?T4EK#B+;3Z_BF+ zJ2$2uJo}`FcOgB_53L#bi#(76*ke);5?ynhj1sh2zGt^P zAHXkBmEM^5B>JP`S!jyA)9EZehW^I>9IkSQ@Akn*gpZJWrJRyXx4V_6H72>NlNwjoh>qyir-| z3-L5KLXX->gYPob=h00Adb;RVf4d%E4<6HpfmFwHuP#DvMV!lvbB7!!djoThBBb4S z%{Swoxt=$b*BOLYtg{lD`?R0U$Yn}he%r7~w#;%vALGA?_J+-=O;1Jw%sK7X?tyh$ zvD9&7g-m{1vRD+mSQz=$TXNYXC{k}j}Y@Ul!?)E!dAJ|Ngmhoj>=jisjqTlV5z%!F^} zVk8Cb8AMCCzkr#-L!4F{=FvNu9{>2kT+h_8F>hJAKErd%+65dfC$AITY85zE>j&cE ztzQVmYVNtxJ=2qHACfPsnw0OGd*p5tXXQ`J>m6a~>4JC}BG`Gd8S*PpU!XgJ*K*8h zbh1b4+ne+x*89@p@oiaKVJvPa4P_WVn1JA=w3>#`c6-pxQOk(Xgfk@trzssilzdTjmqHez(kzWw%5DV|N&qV>%(!posi-H`Oc03ZF z{0f8w!#HJX2UGV-<(KTWCjhELTBBE}C>+QqlG?AkKahPoPzmUP0)+m{Z+c;GLTFJZQGdMk#?4Q7#iiJ)aVzl9^7S=Mj(D5 zlA7V#kPk)IY7kwe3YF|E9vT;Cty~y%LHs^&XG}%dj%AD#-0L?s5CSebk?{KHqhFJL znwn}gD}-Bdq{Q8{DTnU_1h<@9ald{k?$|vOr3QSnBALa#$i7PG?Rzh#*j2=m^5l{R zjo~W(kfBj>j&>h+tw$U(XeZUmC7HPm(c6hip`%sAjGV+5@4H=1DfPAezLD%aD0DV` zz_H9)N{OvU_4Gw|p#g}bEjq})<&47Y+bzBh{t-T<>sf;^-TA&F2OVG9@cVBY#%xLu zK^XO!%QDZL5SbO*rsvnE+iYT!VK!rowU$3Y0ThXnpOoBf<32xtkpQ+WSL15naJbH% zLLv*FI0u`$G4aaJCtuLeDsR$MUx_uMO}s+#F4lz?#~V)oc>~aZlxA!m>3j0{y8VK5 zQBkNaiePlb0tnydk2>~#! zLF4b3$%(niuesDi-mg=h@wrUCvW)?9bdJMFq+{##Au{R;i)bEySHV5b;+NK^S`+Pp z#aw9Uo9~`|O##0safS*7g7~!Ep(H)h;tW{(NO6`W9MeUm})W=xaZzL2lSdf(A;foI5PJVbe^6gu?;s#YADd!1@zDpEt0F|mExCVcD!N3@BK)Q5)NerhxJ z>sbKIee@gK27*m)E=fR(tVi)YZ!0t@Wu~uy}L3XFI0Q$^Z0H zr^XjwvU_{u_R{-ClcAx7^iMbVc)gYxim$SE+vsa)*7dIhqKU{tu>V}S<@kz?6(6bN zXi#D8kK`Z+e*~nfJy+B>^B7S0!d)3P{2_cGv?4t3E73UE^hlp;&sFE8j2Uo3b{UYF zRKoNlkO__+VsazPQday9HJ%H?fa%bMtN2x1H@Lw1#hH)-?5dNL6E%z;$D1kG((j)s zd`Z1U9i&SdmZII`nrJVndGC)ZS7l=O>1au}x3~|@V9nslA>>P;VCjJhj3G4Ga>W7Q z*OuN9vzlblf3!aJFRcTW)k&GN5- zNK!cW8TorV0XAwjZIqbU6Dsyc_o`NRwo$j7jeo<&PsZ8Isd(;N>qn!m`Raj{$?-25 zNt5agRskYx+9%Bx)o32|Jh?cMXMTpjv$SO?Pn8s8p73;f|FNNdVx_elnv;PjQ88p`(;5taY&6$&QZ~HOmdXO%a=3GATnb2*_#idjuL=XnL&m75 zLnC~N)f*H0888Z}8g%k{O~gc$e?h5h;O6<9pIM!Iy`X*3`zTGCNxJ&{5m zT6z2)v|x~%2J>}Hrafw8IPONhtWx?Ha1&-QadV>TmNVk?Ph4Vd3)}wZv}oObu@JA` zT-I2Za%k@!8uePjH^Qwu18^M5{-4x>D0+jy>-2wv4*LHy=qL-p`5JU!0Pqk?4QhJU z*{u)szfQgW9n)L>XP$utmQ$N6ZQ=Iv9}g9N#C7pbQG(EZ|J>6xl|UeWst^0mehzME zr%Y}8)}98pB3coq2I(eJwz~cEfQ6~QkRm{ujsH+TBV0^N?y3n#n75BJRqJ}m1%jLx zyjD(o)73_=7yKn%dsD{i2Fd>O8J$=wgNx1s7IUzN{dPz2Bi4qFyFJ%y2c-3*GDOy+OC;PFv{a~D(+lpl9tRJk5z2 zryY1}d@L^M4bf^6%F=~20hV3g^FBVSI;=l!*Cb~@BWk+0eRJ_#ZxJsE;$)dqA)WpZ zv3P-Aq=MRxHM)|GO_Q?J0q^sR$Uwrn@!Y>T&pX=#+IZC(t+SKLAq}-`eM(KnV$N;u zh-jn^xX48u&tzAp-#WRAFGKCSL$m&EH@jKX$%d+!AP1mA$nC=WpiYOe=QyH}(~eQW z!uisiK6_6n8`YGyi$zxcXAsK64n9CQ*%K(DGgrC*qIpw+kPkr_i8z;M6`+nk0y~H{ z1Zt%H8+qazuLy)&vr6Gvvb2hn{6y@!xBn84YC$}(r+9x z=W5huz!Z;xTZYRmKr+-FX9IIQGqp<^;OmK2^h$<=uix@$Q_uuwcrNIEoahKKMb@Q?ZZHUM0vh*QqvyTtK@{EjR0hYwH z%T1v7T7i?kC5^qWlu3*UCs-ze)R@ASS;!r~qB@EFOweW}6j0zM=M^Ev8mAUGCdxB} zicRD|gt{gBai`Q>OaBz>IO5wQ(0Ov6782m`eKT%H#DP1heV)+0f6<9ht5%iAHUD=a zZHMg)27^pz;+&BoCWH+6$g*sZXfQ@E`z%`JKnWw(IZ3;`)MgciB3SpJV3|0mo)q=J zw0+F`r7YGaBp_JwU1|+kQFY%=vL;qp^x!Aw51T*`VFc#cZeb6B?;gKqJwZI-0r}h% z%aqRP=`if6+x@-5m4aX{>8gkTzL!kaG~uPlcW{a_y%TKBsbA>jTF+5EeaIdfli14G z860AN%kuxlkO{bq|aveov%(}A(8GXz3EUh<4mS{ouHwey2v zWIs0>eG=KX6e>(@T6+`sX6K8ORNLo@6)qe?7K#zs3G@h1p-o_C^Ta=ONSM;U;kmhCXBJU;| pWPZC2;^M0*TIyh@sc{i?@EWCC_!Uzl>) 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

COZ4Zp!?m_DV+ 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 E0A#q8$^ZZW diff --git a/Barotrauma/BarotraumaShared/Submarines/Remora.sub b/Barotrauma/BarotraumaShared/Submarines/Remora.sub index ccce067ae0ca2a1d8aa663aa16c5c7339c0f1e0c..a79c97e861a5e2cbc21748fe768e187d0c4413d7 100644 GIT binary patch literal 279195 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%0Dd9ZcpTuMN;CNy<|V^ok{sIR{Xwq&3tULX9sDeK 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