From 7547a9b78a3c823b00461319dab39ddf1c8a31b9 Mon Sep 17 00:00:00 2001 From: Markus Isberg <3e849f2e5c@pm.me> Date: Fri, 13 May 2022 00:55:52 +0900 Subject: [PATCH] Build 0.18.0.0 --- .../ClientSource/Characters/Character.cs | 2 +- .../Characters/Health/CharacterHealth.cs | 19 + .../ClientSource/Characters/Jobs/JobPrefab.cs | 10 +- .../ClientSource/Characters/Limb.cs | 102 ++-- .../Transition/UgcTransition.cs | 2 +- .../ClientSource/DebugConsole.cs | 54 +- .../ClientSource/GUI/GUITextBlock.cs | 5 +- .../ClientSource/GUI/GUITextBox.cs | 2 +- .../ClientSource/GUI/MedicalClinicUI.cs | 32 +- .../ClientSource/GUI/Store.cs | 99 +--- .../ClientSource/GUI/SubmarineSelection.cs | 4 +- .../ClientSource/GUI/TabMenu.cs | 560 ++++++++++-------- .../BarotraumaClient/ClientSource/GameMain.cs | 9 +- .../ClientSource/GameSession/CrewManager.cs | 9 +- .../ClientSource/GameSession/Data/Wallet.cs | 5 + .../GameModes/SinglePlayerCampaign.cs | 51 +- .../GameModes/Tutorials/DoctorTutorial.cs | 2 +- .../GameModes/Tutorials/EngineerTutorial.cs | 2 +- .../GameModes/Tutorials/MechanicTutorial.cs | 4 +- .../GameModes/Tutorials/ScenarioTutorial.cs | 2 +- .../ClientSource/Items/CharacterInventory.cs | 6 +- .../ClientSource/Items/Components/Door.cs | 9 +- .../Components/Machines/Deconstructor.cs | 21 +- .../Items/Components/Machines/Sonar.cs | 9 + .../Items/Components/RepairTool.cs | 19 +- .../Items/Components/Signal/Connection.cs | 31 +- .../Components/Signal/ConnectionPanel.cs | 24 +- .../Components/Signal/CustomInterface.cs | 2 +- .../Items/Components/Signal/Wire.cs | 2 +- .../ClientSource/Items/Components/Turret.cs | 2 +- .../ClientSource/Items/DockingPort.cs | 9 +- .../ClientSource/Items/Inventory.cs | 2 +- .../ClientSource/Items/Item.cs | 15 +- .../ClientSource/Map/ItemAssemblyPrefab.cs | 8 +- .../ClientSource/Map/Lights/ConvexHull.cs | 17 +- .../ClientSource/Map/RoundSound.cs | 9 +- .../ClientSource/Map/Submarine.cs | 2 +- .../ClientSource/Networking/ChatMessage.cs | 8 +- .../ClientSource/Networking/GameClient.cs | 85 +-- .../ClientSource/Networking/ServerSettings.cs | 36 +- .../ClientSource/Particles/Particle.cs | 10 +- .../ClientSource/Particles/ParticleEmitter.cs | 5 +- .../ClientSource/Particles/ParticleManager.cs | 4 +- .../ClientSource/Screens/MainMenuScreen.cs | 15 +- .../ClientSource/Screens/ModDownloadScreen.cs | 9 +- .../ClientSource/Screens/NetLobbyScreen.cs | 7 +- .../ClientSource/Screens/SubEditorScreen.cs | 74 ++- .../ClientSource/Sounds/SoundPlayer.cs | 12 +- .../ClientSource/Sprite/Sprite.cs | 14 +- .../ClientSource/Steam/Workshop.cs | 6 +- .../Steam/WorkshopMenu/Mutable/PublishTab.cs | 5 + .../ClientSource/Steam/WorkshopMenu/UiUtil.cs | 5 + .../Text/LocalizedString/LimitLString.cs | 2 - .../ClientSource/Utils/ToolBox.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 | 2 +- .../ServerSource/GameSession/CrewManager.cs | 10 +- .../GameSession/GameModes/CampaignMode.cs | 28 +- .../GameModes/MultiPlayerCampaign.cs | 83 +-- .../Items/Components/DockingPort.cs | 14 +- .../Items/Components/ItemLabel.cs | 2 +- .../Components/Signal/ConnectionPanel.cs | 11 +- .../Map/Creatures/BallastFloraBehavior.cs | 2 +- .../ServerSource/Networking/Client.cs | 2 +- .../Networking/FileTransfer/FileSender.cs | 2 +- .../ServerSource/Networking/GameServer.cs | 27 +- .../ServerSource/Networking/KarmaManager.cs | 4 +- .../ServerSource/Networking/RespawnManager.cs | 45 +- .../ServerSource/Networking/Voting.cs | 65 +- .../BarotraumaServer/WindowsServer.csproj | 2 +- .../Characters/AI/AIController.cs | 11 + .../Characters/AI/EnemyAIController.cs | 24 +- .../Characters/AI/HumanAIController.cs | 94 ++- .../AI/Objectives/AIObjectiveCleanupItems.cs | 2 +- .../AI/Objectives/AIObjectiveCombat.cs | 37 +- .../AI/Objectives/AIObjectiveFindSafety.cs | 312 ++++++---- .../AI/Objectives/AIObjectiveFixLeak.cs | 1 + .../AI/Objectives/AIObjectiveGetItem.cs | 30 +- .../AI/Objectives/AIObjectiveRescue.cs | 5 +- .../Characters/Animation/AnimController.cs | 18 +- .../Animation/HumanoidAnimController.cs | 4 +- .../SharedSource/Characters/Character.cs | 40 +- .../SharedSource/Characters/CharacterInfo.cs | 7 +- .../Health/Afflictions/AfflictionPrefab.cs | 1 + .../Characters/Health/CharacterHealth.cs | 35 +- .../SharedSource/Characters/HumanPrefab.cs | 4 +- .../SharedSource/Characters/Jobs/Job.cs | 7 +- .../SharedSource/Characters/Jobs/JobPrefab.cs | 2 +- .../SharedSource/Characters/Limb.cs | 20 +- .../ContentFile/BallastFloraFile.cs | 2 +- .../CaveGenerationParametersFile.cs | 2 +- .../ContentFile/CharacterFile.cs | 2 +- .../ContentFile/CorpsesFile.cs | 2 +- .../ContentFile/EventManagerSettingsFile.cs | 2 +- .../ContentFile/FactionsFile.cs | 2 +- .../ContentFile/GenericPrefabFile.cs | 12 +- .../ContentFile/ItemAssemblyFile.cs | 2 +- .../ContentManagement/ContentFile/ItemFile.cs | 2 +- .../ContentFile/LevelObjectPrefabsFile.cs | 2 +- .../ContentFile/LocationTypesFile.cs | 2 +- .../ContentFile/MissionsFile.cs | 2 +- .../ContentFile/NPCSetsFile.cs | 2 +- .../ContentFile/OrdersFile.cs | 2 +- .../ContentFile/OutpostConfigFile.cs | 2 +- .../ContentFile/ParticlesFile.cs | 2 +- .../ContentFile/RandomEventsFile.cs | 2 +- .../ContentFile/RuinConfigFile.cs | 2 +- .../ContentFile/SoundsFile.cs | 2 +- .../ContentFile/StartItemsFile.cs | 12 + .../ContentFile/StructureFile.cs | 2 +- .../ContentFile/TalentTreesFile.cs | 2 +- .../ContentFile/TalentsFile.cs | 2 +- .../ContentFile/TraitorMissionsFile.cs | 2 +- .../ContentFile/UpgradeModulesFile.cs | 2 +- .../ContentFile/WreckAIConfigFile.cs | 2 +- .../ContentPackage/ContentPackage.cs | 3 +- .../ContentManagement/ContentPath.cs | 5 +- .../ContentManagement/ContentXElement.cs | 2 +- .../MissingContentPackageException.cs | 2 +- .../SharedSource/DebugConsole.cs | 13 +- .../SharedSource/Events/ArtifactEvent.cs | 5 +- .../SharedSource/Events/Event.cs | 18 +- .../SharedSource/Events/EventManager.cs | 29 +- .../SharedSource/Events/EventSet.cs | 6 +- .../SharedSource/Events/MalfunctionEvent.cs | 10 +- .../SharedSource/Events/Missions/Mission.cs | 5 +- .../Events/Missions/MissionPrefab.cs | 34 +- .../Events/Missions/PirateMission.cs | 2 +- .../SharedSource/Events/MonsterEvent.cs | 109 ++-- .../SharedSource/Events/ScriptedEvent.cs | 10 +- .../GameSession/AutoItemPlacer.cs | 183 ++++-- .../SharedSource/GameSession/CargoManager.cs | 143 +++-- .../SharedSource/GameSession/CrewManager.cs | 14 +- .../GameSession/GameModes/CampaignMode.cs | 190 +++++- .../GameModes/MultiPlayerCampaign.cs | 2 +- .../SharedSource/GameSession/GameSession.cs | 38 +- .../SharedSource/InputType.cs | 1 - .../Items/Components/DockingPort.cs | 98 ++- .../SharedSource/Items/Components/Door.cs | 23 +- .../Items/Components/Holdable/Holdable.cs | 11 +- .../Components/Holdable/LevelResource.cs | 23 +- .../Items/Components/Holdable/MeleeWeapon.cs | 9 +- .../Items/Components/Holdable/Pickable.cs | 3 +- .../Items/Components/Holdable/Propulsion.cs | 11 +- .../Items/Components/ItemComponent.cs | 2 +- .../Items/Components/ItemContainer.cs | 8 +- .../Items/Components/Machines/Controller.cs | 48 +- .../Items/Components/Power/Powered.cs | 2 +- .../Items/Components/Projectile.cs | 11 + .../BooleanOperatorComponent/AndComponent.cs | 10 + .../BooleanOperatorComponent.cs} | 13 +- .../BooleanOperatorComponent/OrComponent.cs | 10 + .../BooleanOperatorComponent/XorComponent.cs | 10 + .../Items/Components/Signal/Connection.cs | 253 ++++---- .../Components/Signal/ConnectionPanel.cs | 32 +- .../Items/Components/Signal/LightComponent.cs | 2 +- .../Items/Components/Signal/OrComponent.cs | 33 -- .../Items/Components/Signal/WifiComponent.cs | 2 +- .../Items/Components/Signal/Wire.cs | 12 +- .../Items/Components/Signal/XorComponent.cs | 34 -- .../Items/Components/TriggerComponent.cs | 21 +- .../SharedSource/Items/Components/Turret.cs | 9 +- .../SharedSource/Items/Item.cs | 61 +- .../SharedSource/Items/ItemPrefab.cs | 31 +- .../Map/Creatures/BallastFloraBehavior.cs | 16 +- .../BarotraumaShared/SharedSource/Map/Gap.cs | 19 +- .../BarotraumaShared/SharedSource/Map/Hull.cs | 2 +- .../SharedSource/Map/Levels/CaveGenerator.cs | 15 +- .../SharedSource/Map/Levels/Level.cs | 136 +++-- .../SharedSource/Map/Levels/LevelData.cs | 16 +- .../Map/Levels/LevelGenerationParams.cs | 54 +- .../Levels/LevelObjects/LevelObjectManager.cs | 56 +- .../Levels/LevelObjects/LevelObjectPrefab.cs | 1 + .../Map/Levels/LevelObjects/LevelTrigger.cs | 1 - .../SharedSource/Map/Map/Location.cs | 80 +-- .../SharedSource/Map/Map/LocationType.cs | 33 -- .../SharedSource/Map/Map/Map.cs | 27 +- .../SharedSource/Map/MapEntity.cs | 26 +- .../Map/Outposts/OutpostGenerator.cs | 4 +- .../SharedSource/Map/Structure.cs | 27 +- .../SharedSource/Map/Submarine.cs | 365 ++++++------ .../SharedSource/Map/SubmarineBody.cs | 47 +- .../Networking/ChildServerRelay.cs | 15 + .../SharedSource/Networking/RespawnManager.cs | 17 +- .../SharedSource/Networking/ServerSettings.cs | 14 +- .../SharedSource/Physics/PhysicsBody.cs | 57 +- .../SharedSource/Prefabs/Prefab.cs | 4 +- .../SharedSource/Settings/GameSettings.cs | 33 +- .../StatusEffects/PropertyConditional.cs | 3 +- .../StatusEffects/StatusEffect.cs | 21 +- .../SharedSource/Steam/Workshop.cs | 26 +- .../SharedSource/Utils/AssemblyInfo.cs | 4 + .../SharedSource/Utils/ReflectionUtils.cs | 7 +- .../SharedSource/Utils/SaveUtil.cs | 2 +- Barotrauma/BarotraumaShared/changelog.txt | 84 +++ .../BarotraumaShared/serversettings.xml | 4 +- .../INetSerializableStructTests.cs | 205 +++++++ Barotrauma/BarotraumaTest/LinuxTest.csproj | 35 ++ Barotrauma/BarotraumaTest/MacTest.csproj | 35 ++ Barotrauma/BarotraumaTest/TestExample.cs | 122 ++++ Barotrauma/BarotraumaTest/TestProject.cs | 34 ++ Barotrauma/BarotraumaTest/WindowsTest.csproj | 35 ++ .../Common/PathManager.cs | 8 +- .../Common/PhysicsLogic/BreakableBody.cs | 8 +- .../Common/Serialization.cs | 2 +- .../Content/BodyContainer.cs | 2 +- .../Dynamics/Body.Factory.cs | 54 +- .../Dynamics/Fixture.cs | 26 +- .../Dynamics/World.Factory.cs | 121 ++-- .../Dynamics/World.cs | 26 +- .../Graphics/Texture2D.OpenGL.cs | 3 + LinuxSolution.sln | 15 + MacSolution.sln | 15 + WindowsSolution.sln | 9 + 218 files changed, 3881 insertions(+), 2192 deletions(-) create mode 100644 Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/StartItemsFile.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/BooleanOperatorComponent/AndComponent.cs rename Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/{AndComponent.cs => BooleanOperatorComponent/BooleanOperatorComponent.cs} (90%) create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/BooleanOperatorComponent/OrComponent.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/BooleanOperatorComponent/XorComponent.cs delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/OrComponent.cs delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/XorComponent.cs create mode 100644 Barotrauma/BarotraumaTest/INetSerializableStructTests.cs create mode 100644 Barotrauma/BarotraumaTest/LinuxTest.csproj create mode 100644 Barotrauma/BarotraumaTest/MacTest.csproj create mode 100644 Barotrauma/BarotraumaTest/TestExample.cs create mode 100644 Barotrauma/BarotraumaTest/TestProject.cs create mode 100644 Barotrauma/BarotraumaTest/WindowsTest.csproj diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs index 0891e5008..3c015ee76 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs @@ -400,7 +400,7 @@ namespace Barotrauma partial void UpdateControlled(float deltaTime, Camera cam) { - if (controlled != this) return; + if (controlled != this) { return; } ControlLocalPlayer(deltaTime, cam); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs index 436e47141..91e852beb 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs @@ -2008,8 +2008,27 @@ namespace Barotrauma DisplayedVitality = Vitality; } + partial void UpdateSkinTint() + { + if (!Character.IsVisible) { return; } + FaceTint = DefaultFaceTint; + BodyTint = Color.TransparentBlack; + + if (!(Character?.Params?.Health.ApplyAfflictionColors ?? false)) { return; } + + foreach (KeyValuePair kvp in afflictions) + { + var affliction = kvp.Key; + Color faceTint = affliction.GetFaceTint(); + if (faceTint.A > FaceTint.A) { FaceTint = faceTint; } + Color bodyTint = affliction.GetBodyTint(); + if (bodyTint.A > BodyTint.A) { BodyTint = bodyTint; } + } + } + partial void UpdateLimbAfflictionOverlays() { + if (!Character.IsVisible) { return; } foreach (Limb limb in Character.AnimController.Limbs) { if (limb.HealthIndex < 0 || limb.HealthIndex >= limbHealths.Count) { continue; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Jobs/JobPrefab.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Jobs/JobPrefab.cs index 19640dbc7..1ac28a552 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Jobs/JobPrefab.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Jobs/JobPrefab.cs @@ -58,21 +58,19 @@ namespace Barotrauma public class OutfitPreview { - /// - /// Pair.First = sprite, Pair.Second = draw offset - /// - public readonly List> Sprites; + public readonly List<(Sprite sprite, Vector2 drawOffset)> Sprites; + public Vector2 Dimensions; public OutfitPreview() { - Sprites = new List>(); + Sprites = new List<(Sprite sprite, Vector2 drawOffset)>(); Dimensions = Vector2.One; } public void AddSprite(Sprite sprite, Vector2 drawOffset) { - Sprites.Add(new Pair(sprite, drawOffset)); + Sprites.Add((sprite, drawOffset)); } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Limb.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Limb.cs index 41d2d470e..7fbcef8fe 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Limb.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Limb.cs @@ -224,14 +224,14 @@ namespace Barotrauma public float DamageOverlayStrength { get { return damageOverlayStrength; } - set { damageOverlayStrength = MathHelper.Clamp(value, 0.0f, 100.0f); } + set { damageOverlayStrength = MathHelper.Clamp(value, 0.0f, 1.0f); } } private float burnOverLayStrength; public float BurnOverlayStrength { get { return burnOverLayStrength; } - set { burnOverLayStrength = MathHelper.Clamp(value, 0.0f, 100.0f); } + set { burnOverLayStrength = MathHelper.Clamp(value, 0.0f, 1.0f); } } public string HitSoundTag => Params?.Sound?.Tag; @@ -279,7 +279,7 @@ namespace Barotrauma for (int i = 0; i < Params.decorativeSpriteParams.Count; i++) { var param = Params.decorativeSpriteParams[i]; - var decorativeSprite = new DecorativeSprite(param.Element, file: GetSpritePath(param.Element, param)); + var decorativeSprite = new DecorativeSprite(param.Element, file: GetSpritePath(param.Element, param, ref _texturePath)); DecorativeSprites.Add(decorativeSprite); int groupID = decorativeSprite.RandomGroupID; if (!DecorativeSpriteGroups.ContainsKey(groupID)) @@ -295,13 +295,13 @@ namespace Barotrauma switch (subElement.Name.ToString().ToLowerInvariant()) { case "sprite": - Sprite = new Sprite(subElement, file: GetSpritePath(subElement, Params.normalSpriteParams)); + Sprite = new Sprite(subElement, file: GetSpritePath(subElement, Params.normalSpriteParams, ref _texturePath)); break; - case "damagedsprite": - DamagedSprite = new Sprite(subElement, file: GetSpritePath(subElement, Params.damagedSpriteParams)); + case "damagedsprite": + DamagedSprite = new Sprite(subElement, file: GetSpritePath(subElement, Params.damagedSpriteParams, ref _damagedTexturePath)); break; case "conditionalsprite": - var conditionalSprite = new ConditionalSprite(subElement, GetConditionalTarget(), file: GetSpritePath(subElement, null)); + var conditionalSprite = new ConditionalSprite(subElement, GetConditionalTarget(), file: GetSpritePath(subElement, null, ref _texturePath)); ConditionalSprites.Add(conditionalSprite); if (conditionalSprite.DeformableSprite != null) { @@ -311,7 +311,7 @@ namespace Barotrauma } break; case "deformablesprite": - _deformSprite = new DeformableSprite(subElement, filePath: GetSpritePath(subElement, Params.deformSpriteParams)); + _deformSprite = new DeformableSprite(subElement, filePath: GetSpritePath(subElement, Params.deformSpriteParams, ref _texturePath)); var deformations = CreateDeformations(subElement); Deformations.AddRange(deformations); NonConditionalDeformations.AddRange(deformations); @@ -435,33 +435,33 @@ namespace Barotrauma { Sprite.Remove(); var source = Sprite.SourceElement; - Sprite = new Sprite(source, file: GetSpritePath(source, Params.normalSpriteParams)); + Sprite = new Sprite(source, file: GetSpritePath(source, Params.normalSpriteParams, ref _texturePath)); } if (_deformSprite != null) { _deformSprite.Remove(); var source = _deformSprite.Sprite.SourceElement; - _deformSprite = new DeformableSprite(source, filePath: GetSpritePath(source, Params.deformSpriteParams)); + _deformSprite = new DeformableSprite(source, filePath: GetSpritePath(source, Params.deformSpriteParams, ref _texturePath)); } if (DamagedSprite != null) { DamagedSprite.Remove(); var source = DamagedSprite.SourceElement; - DamagedSprite = new Sprite(source, file: GetSpritePath(source, Params.damagedSpriteParams)); + DamagedSprite = new Sprite(source, file: GetSpritePath(source, Params.damagedSpriteParams, ref _damagedTexturePath)); } for (int i = 0; i < ConditionalSprites.Count; i++) { var conditionalSprite = ConditionalSprites[i]; var source = conditionalSprite.ActiveSprite.SourceElement; conditionalSprite.Remove(); - ConditionalSprites[i] = new ConditionalSprite(source, character, file: GetSpritePath(source, null)); + ConditionalSprites[i] = new ConditionalSprite(source, character, file: GetSpritePath(source, null, ref _texturePath)); } for (int i = 0; i < DecorativeSprites.Count; i++) { var decorativeSprite = DecorativeSprites[i]; decorativeSprite.Remove(); var source = decorativeSprite.Sprite.SourceElement; - DecorativeSprites[i] = new DecorativeSprite(source, file: GetSpritePath(source, Params.decorativeSpriteParams[i])); + DecorativeSprites[i] = new DecorativeSprite(source, file: GetSpritePath(source, Params.decorativeSpriteParams[i], ref _texturePath)); } } @@ -472,16 +472,17 @@ namespace Barotrauma } private string _texturePath; - private string GetSpritePath(ContentXElement element, SpriteParams spriteParams) + private string _damagedTexturePath; + private string GetSpritePath(ContentXElement element, SpriteParams spriteParams, ref string path) { - if (_texturePath == null) + if (path == null) { if (spriteParams != null) { ContentPath texturePath = character.Params.VariantFile?.Root?.GetAttributeContentPath("texture", character.Prefab.ContentPackage) ?? ContentPath.FromRaw(character.Prefab.ContentPackage, spriteParams.GetTexturePath()); - _texturePath = GetSpritePath(texturePath); + path = GetSpritePath(texturePath); } else { @@ -489,10 +490,10 @@ namespace Barotrauma texturePath = texturePath.IsNullOrWhiteSpace() ? ContentPath.FromRaw(character.Prefab.ContentPackage, ragdoll.RagdollParams.Texture) : texturePath; - _texturePath = GetSpritePath(texturePath); + path = GetSpritePath(texturePath); } } - return _texturePath; + return path; } /// @@ -625,12 +626,7 @@ namespace Barotrauma { if (!body.Enabled) { return; } - if (!IsDead) - { - DamageOverlayStrength -= deltaTime; - BurnOverlayStrength -= deltaTime; - } - else + if (IsDead) { var spriteParams = Params.GetSprite(); if (spriteParams != null && spriteParams.DeadColorTime > 0 && deadTimer < spriteParams.DeadColorTime) @@ -688,7 +684,7 @@ namespace Barotrauma public void Draw(SpriteBatch spriteBatch, Camera cam, Color? overrideColor = null, bool disableDeformations = false) { - float brightness = 1.0f - (burnOverLayStrength / 100.0f) * 0.5f; + float brightness = Math.Max(1.0f - burnOverLayStrength, 0.2f); var spriteParams = Params.GetSprite(); if (spriteParams == null) { return; } @@ -831,32 +827,6 @@ namespace Barotrauma { LightSource.LightSpriteEffect = (dir == Direction.Right) ? SpriteEffects.None : SpriteEffects.FlipVertically; } - if (damageOverlayStrength > 0.0f && DamagedSprite != null && !hideLimb) - { - DamagedSprite.Draw(spriteBatch, - new Vector2(body.DrawPosition.X, -body.DrawPosition.Y), - color * Math.Min(damageOverlayStrength, 1.0f), activeSprite.Origin, - -body.DrawRotation, - Scale, spriteEffect, activeSprite.Depth - (depthStep * 90)); - } - foreach (var decorativeSprite in DecorativeSprites) - { - if (!spriteAnimState[decorativeSprite].IsActive) { continue; } - Color c = new Color(decorativeSprite.Color.R / 255f * brightness, decorativeSprite.Color.G / 255f * brightness, decorativeSprite.Color.B / 255f * brightness, decorativeSprite.Color.A / 255f); - if (deadTimer > 0) - { - c = Color.Lerp(c, spriteParams.DeadColor, MathUtils.InverseLerp(0, Params.GetSprite().DeadColorTime, deadTimer)); - } - c = overrideColor ?? c; - float rotation = decorativeSprite.GetRotation(ref spriteAnimState[decorativeSprite].RotationState, spriteAnimState[decorativeSprite].RandomRotationFactor); - Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState, spriteAnimState[decorativeSprite].RandomOffsetMultiplier) * Scale; - var ca = (float)Math.Cos(-body.Rotation); - var sa = (float)Math.Sin(-body.Rotation); - Vector2 transformedOffset = new Vector2(ca * offset.X + sa * offset.Y, -sa * offset.X + ca * offset.Y); - decorativeSprite.Sprite.Draw(spriteBatch, new Vector2(body.DrawPosition.X + transformedOffset.X, -(body.DrawPosition.Y + transformedOffset.Y)), c, - -body.Rotation + rotation, decorativeSprite.GetScale(spriteAnimState[decorativeSprite].RandomScaleFactor) * Scale, spriteEffect, - depth: activeSprite.Depth - (depthStep * 100)); - } float step = depthStep; WearableSprite onlyDrawable = wearingItems.Find(w => w.HideOtherWearables); if (Params.MirrorHorizontally) @@ -925,6 +895,36 @@ namespace Barotrauma //if there are multiple sprites on this limb, make the successive ones be drawn in front depthStep += step; } + if (!Hide && onlyDrawable == null) + { + foreach (var decorativeSprite in DecorativeSprites) + { + if (!spriteAnimState[decorativeSprite].IsActive) { continue; } + Color c = new Color(decorativeSprite.Color.R / 255f * brightness, decorativeSprite.Color.G / 255f * brightness, decorativeSprite.Color.B / 255f * brightness, decorativeSprite.Color.A / 255f); + if (deadTimer > 0) + { + c = Color.Lerp(c, spriteParams.DeadColor, MathUtils.InverseLerp(0, Params.GetSprite().DeadColorTime, deadTimer)); + } + c = overrideColor ?? c; + float rotation = decorativeSprite.GetRotation(ref spriteAnimState[decorativeSprite].RotationState, spriteAnimState[decorativeSprite].RandomRotationFactor); + Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState, spriteAnimState[decorativeSprite].RandomOffsetMultiplier) * Scale; + var ca = (float)Math.Cos(-body.Rotation); + var sa = (float)Math.Sin(-body.Rotation); + Vector2 transformedOffset = new Vector2(ca * offset.X + sa * offset.Y, -sa * offset.X + ca * offset.Y); + decorativeSprite.Sprite.Draw(spriteBatch, new Vector2(body.DrawPosition.X + transformedOffset.X, -(body.DrawPosition.Y + transformedOffset.Y)), c, + -body.Rotation + rotation, decorativeSprite.GetScale(spriteAnimState[decorativeSprite].RandomScaleFactor) * Scale, spriteEffect, + depth: activeSprite.Depth - depthStep); + depthStep += step; + } + if (damageOverlayStrength > 0.0f && DamagedSprite != null) + { + DamagedSprite.Draw(spriteBatch, + new Vector2(body.DrawPosition.X, -body.DrawPosition.Y), + color * damageOverlayStrength, activeSprite.Origin, + -body.DrawRotation, + Scale, spriteEffect, activeSprite.Depth - depthStep * Math.Max(1, WearingItems.Count * 2)); // Multiply by 2 to get rid of z-fighting with some clothing combos + } + } if (GameMain.DebugDraw) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/ContentManagement/Transition/UgcTransition.cs b/Barotrauma/BarotraumaClient/ClientSource/ContentManagement/Transition/UgcTransition.cs index eb85f6b64..795b4ae24 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/ContentManagement/Transition/UgcTransition.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/ContentManagement/Transition/UgcTransition.cs @@ -359,7 +359,7 @@ namespace Barotrauma.Transition else { //copying a mod: we have a neat method for that! - await SteamManager.Workshop.CopyDirectory(path, Path.GetFileName(path), path, destPath); + await SteamManager.Workshop.CopyDirectory(path, Path.GetFileName(path), path, destPath, SteamManager.Workshop.ShouldCorrectPaths.Yes); return null; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs b/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs index a13d82f7f..c56a20796 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs @@ -125,7 +125,7 @@ namespace Barotrauma { if (isOpen) { - frame.AddToGUIUpdateList(); + frame.AddToGUIUpdateList(order: 1); } } @@ -1714,9 +1714,47 @@ namespace Barotrauma //check missing mission texts foreach (var missionPrefab in MissionPrefab.Prefabs) { - Identifier missionId = (missionPrefab.ConfigElement.GetAttribute("textidentifier") == null ? missionPrefab.Identifier : missionPrefab.ConfigElement.GetAttributeIdentifier("textidentifier", Identifier.Empty)); - addIfMissing($"missionname.{missionId}".ToIdentifier(), language); - addIfMissing($"missiondescription.{missionId}".ToIdentifier(), language); + Identifier missionId = missionPrefab.ConfigElement.GetAttribute("textidentifier") == null ? + missionPrefab.Identifier : + missionPrefab.ConfigElement.GetAttributeIdentifier("textidentifier", Identifier.Empty); + + if (!tags[language].Contains(missionPrefab.ConfigElement.GetAttributeIdentifier("name", Identifier.Empty))) + { + addIfMissing($"missionname.{missionId}".ToIdentifier(), language); + } + + if (missionPrefab.Type == MissionType.Combat) + { + addIfMissing($"MissionDescriptionNeutral.{missionId}".ToIdentifier(), language); + addIfMissing($"MissionDescription1.{missionId}".ToIdentifier(), language); + addIfMissing($"MissionDescription2.{missionId}".ToIdentifier(), language); + addIfMissing($"MissionTeam1.{missionId}".ToIdentifier(), language); + addIfMissing($"MissionTeam2.{missionId}".ToIdentifier(), language); + } + else + { + if (!tags[language].Contains(missionPrefab.ConfigElement.GetAttributeIdentifier("description", Identifier.Empty))) + { + addIfMissing($"missiondescription.{missionId}".ToIdentifier(), language); + } + if (!tags[language].Contains(missionPrefab.ConfigElement.GetAttributeIdentifier("successmessage", Identifier.Empty))) + { + addIfMissing($"missionsuccess.{missionId}".ToIdentifier(), language); + } + //only check failure message if there's something defined in the xml (otherwise we just use the generic "missionfailed" text) + if (missionPrefab.ConfigElement.GetAttribute("failuremessage") != null && + !tags[language].Contains(missionPrefab.ConfigElement.GetAttributeIdentifier("failuremessage", Identifier.Empty))) + { + addIfMissing($"missionfailure.{missionId}".ToIdentifier(), language); + } + } + for (int i = 0; i type.IsSubclassOf(typeof(ItemComponent)))) @@ -2503,7 +2541,7 @@ namespace Barotrauma var entity = MapEntity.mapEntityList[i] as ISerializableEntity; if (entity != null) { - List> allProperties = new List>(); + List<(object obj, SerializableProperty property)> allProperties = new List<(object obj, SerializableProperty property)>(); if (entity is Item item) { @@ -2518,14 +2556,14 @@ namespace Barotrauma for (int k = 0; k < properties.Count; k++) { - allProperties.Add(new Pair(entity, properties[k])); + allProperties.Add((entity, properties[k])); } } for (int j = 0; j < allProperties.Count; j++) { - var property = allProperties[j].Second; - string propertyName = (allProperties[j].First.GetType().Name + "." + property.PropertyInfo.Name).ToLowerInvariant(); + var property = allProperties[j].property; + string propertyName = (allProperties[j].obj.GetType().Name + "." + property.PropertyInfo.Name).ToLowerInvariant(); LocalizedString displayName = TextManager.Get($"sp.{propertyName}.name"); if (displayName.IsNullOrEmpty()) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUITextBlock.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUITextBlock.cs index e4c047bff..404c09927 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUITextBlock.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUITextBlock.cs @@ -35,7 +35,6 @@ namespace Barotrauma public TextGetterHandler TextGetter; public bool Wrap; - private bool playerInput; public bool RoundToNearestPixel = true; @@ -287,8 +286,7 @@ namespace Barotrauma /// If the rectT height is set 0, the height is calculated from the text. /// public GUITextBlock(RectTransform rectT, RichString text, Color? textColor = null, GUIFont font = null, - Alignment textAlignment = Alignment.Left, bool wrap = false, string style = "", Color? color = null, - bool playerInput = false) + Alignment textAlignment = Alignment.Left, bool wrap = false, string style = "", Color? color = null) : base(style, rectT) { if (color.HasValue) @@ -307,7 +305,6 @@ namespace Barotrauma this.textAlignment = textAlignment; this.Wrap = wrap; this.Text = text ?? ""; - this.playerInput = playerInput; if (rectT.Rect.Height == 0 && !text.IsNullOrEmpty()) { CalculateHeightFromText(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUITextBox.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUITextBox.cs index 2bcb6bbb7..d48669710 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUITextBox.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUITextBox.cs @@ -261,7 +261,7 @@ namespace Barotrauma this.color = color ?? Color.White; frame = new GUIFrame(new RectTransform(Vector2.One, rectT, Anchor.Center), style, color); GUIStyle.Apply(frame, style == "" ? "GUITextBox" : style); - textBlock = new GUITextBlock(new RectTransform(Vector2.One, frame.RectTransform, Anchor.CenterLeft), text ?? "", textColor, font, textAlignment, wrap, playerInput: true); + textBlock = new GUITextBlock(new RectTransform(Vector2.One, frame.RectTransform, Anchor.CenterLeft), text ?? "", textColor, font, textAlignment, wrap); GUIStyle.Apply(textBlock, "", this); if (font != null) { textBlock.Font = font; } CaretEnabled = true; diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/MedicalClinicUI.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/MedicalClinicUI.cs index 211f69381..a86a2ca4c 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/MedicalClinicUI.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/MedicalClinicUI.cs @@ -657,6 +657,7 @@ namespace Barotrauma { CanBeFocused = false }; + GUILayoutGroup parentLayout = new GUILayoutGroup(new RectTransform(Vector2.One, backgroundFrame.RectTransform), isHorizontal: true) { Stretch = true }; if (!(affliction.Prefab is { } prefab)) { return; } @@ -676,7 +677,7 @@ namespace Barotrauma GUIFrame textContainer = new GUIFrame(new RectTransform(new Vector2(0.6f, 1f), textLayout.RectTransform), style: null); GUITextBlock afflictionName = new GUITextBlock(new RectTransform(Vector2.One, textContainer.RectTransform), name, font: GUIStyle.SubHeadingFont); - GUITextBlock healCost = new GUITextBlock(new RectTransform(new Vector2(0.2f, 1f), textLayout.RectTransform), TextManager.FormatCurrency(affliction.Price), textAlignment: Alignment.Center, font: GUIStyle.LargeFont) + GUITextBlock healCost = new GUITextBlock(new RectTransform(new Vector2(0.2f, 1f), textLayout.RectTransform), TextManager.FormatCurrency(affliction.Price), textAlignment: Alignment.Center, font: GUIStyle.SubHeadingFont) { Padding = Vector4.Zero }; @@ -746,12 +747,12 @@ namespace Barotrauma ClosePopup(); - GUIFrame mainFrame = new GUIFrame(new RectTransform(new Vector2(0.28f, 0.45f), container.RectTransform) + GUIFrame mainFrame = new GUIFrame(new RectTransform(new Vector2(0.28f, 0.5f), container.RectTransform) { ScreenSpaceOffset = location.ToPoint() }); - GUILayoutGroup mainLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.95f), mainFrame.RectTransform, Anchor.Center)) { RelativeSpacing = 0.01f, Stretch = true }; + GUILayoutGroup mainLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), mainFrame.RectTransform, Anchor.Center)) { RelativeSpacing = 0.01f, Stretch = true }; if (mainFrame.Rect.Bottom > GameMain.GraphicsHeight) { @@ -819,7 +820,9 @@ namespace Barotrauma if (!(affliction.Prefab is { } prefab)) { return ImmutableArray.Empty; } GUIFrame backgroundFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.33f), parent.RectTransform), style: "ListBoxElement"); - GUILayoutGroup mainLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.95f), backgroundFrame.RectTransform, Anchor.Center)) + new GUIFrame(new RectTransform(new Vector2(1.0f, 0.01f), backgroundFrame.RectTransform, Anchor.BottomCenter), style: "HorizontalLine"); + + GUILayoutGroup mainLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), backgroundFrame.RectTransform, Anchor.Center)) { RelativeSpacing = 0.05f }; @@ -862,13 +865,27 @@ namespace Barotrauma GUILayoutGroup bottomLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.66f), mainLayout.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft); - GUILayoutGroup bottomTextLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 1f), bottomLayout.RectTransform)); - GUITextBlock descriptionBlock = new GUITextBlock(new RectTransform(new Vector2(1f, 0.5f), bottomTextLayout.RectTransform), ToolBox.LimitString(prefab.Description, GUIStyle.Font, GUI.IntScale(64)), wrap: true) + GUILayoutGroup bottomTextLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 1f), bottomLayout.RectTransform)) + { + RelativeSpacing = 0.05f + }; + GUITextBlock descriptionBlock = new GUITextBlock(new RectTransform(new Vector2(1f, 0.6f), bottomTextLayout.RectTransform), prefab.Description, font: GUIStyle.SmallFont, wrap: true) { ToolTip = prefab.Description }; + bool truncated = false; + while (descriptionBlock.TextSize.Y > descriptionBlock.Rect.Height && descriptionBlock.WrappedText.Contains('\n')) + { + var split = descriptionBlock.WrappedText.Value.Split('\n'); + descriptionBlock.Text = string.Join('\n', split.Take(split.Length - 1)); + truncated = true; + } + if (truncated) + { + descriptionBlock.Text += "..."; + } - GUITextBlock priceBlock = new GUITextBlock(new RectTransform(new Vector2(1f, 0.5f), bottomTextLayout.RectTransform), TextManager.FormatCurrency(affliction.Price), font: GUIStyle.LargeFont); + GUITextBlock priceBlock = new GUITextBlock(new RectTransform(new Vector2(1f, 0.25f), bottomTextLayout.RectTransform), TextManager.FormatCurrency(affliction.Price), font: GUIStyle.SubHeadingFont); GUIButton buyButton = new GUIButton(new RectTransform(new Vector2(0.2f, 0.75f), bottomLayout.RectTransform), style: "CrewManagementAddButton"); @@ -923,6 +940,7 @@ namespace Barotrauma }); } + #warning TODO: this doesn't seem like the right place for this, and it's not clear from the method signature how this differs from ToolBox.LimitString public static void EnsureTextDoesntOverflow(string? text, GUITextBlock textBlock, Rectangle bounds, ImmutableArray? layoutGroups = null) { if (string.IsNullOrWhiteSpace(text)) { return; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/Store.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/Store.cs index e51c586c9..303a207fd 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/Store.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/Store.cs @@ -46,9 +46,7 @@ namespace Barotrauma private int buyTotal, sellTotal, sellFromSubTotal; private GUITextBlock storeNameBlock; - private GUITextBlock merchantBalanceBlock; - private GUITextBlock currentSellValueBlock, newSellValueBlock; - private GUIImage sellValueChangeArrow; + private GUITextBlock reputationEffectBlock; private GUIDropDown sortingDropDown; private GUITextBox searchBox; private GUILayoutGroup categoryButtonContainer; @@ -376,41 +374,29 @@ namespace Barotrauma AutoScaleVertical = true, ForceUpperCase = ForceUpperCase.Yes }; - merchantBalanceBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), merchantBalanceContainer.RectTransform), - "", font: GUIStyle.SubHeadingFont) + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), merchantBalanceContainer.RectTransform), "", + color: Color.White, font: GUIStyle.SubHeadingFont) { AutoScaleVertical = true, TextScale = 1.1f, - TextGetter = () => - { - merchantBalanceBlock.TextColor = ActiveStore?.BalanceColor ?? Color.Red; - return GetMerchantBalanceText(); - } + TextGetter = () => GetMerchantBalanceText() }; // Item sell value ------------------------------------------------ - var sellValueContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 1.0f), balanceAndValueGroup.RectTransform)) + var reputationEffectContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 1.0f), balanceAndValueGroup.RectTransform)) { CanBeFocused = true, - RelativeSpacing = 0.005f + RelativeSpacing = 0.005f, + ToolTip = TextManager.Get("campaignstore.reputationtooltip") }; - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), sellValueContainer.RectTransform), - TextManager.Get("campaignstore.sellvalue"), font: GUIStyle.Font, textAlignment: Alignment.BottomLeft) + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), reputationEffectContainer.RectTransform), + TextManager.Get("reputation"), font: GUIStyle.Font, textAlignment: Alignment.BottomLeft) { AutoScaleVertical = true, CanBeFocused = false, - ForceUpperCase = ForceUpperCase.Yes + ForceUpperCase = ForceUpperCase.Yes, }; - - var valueChangeGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.5f), sellValueContainer.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft) - { - CanBeFocused = false, - RelativeSpacing = 0.02f - }; - float blockWidth = GUI.IsFourByThree() ? 0.32f : 0.28f; - Point blockMaxSize = new Point((int)(GameSettings.CurrentConfig.Graphics.TextScale * 60), valueChangeGroup.Rect.Height); - currentSellValueBlock = new GUITextBlock(new RectTransform(new Vector2(blockWidth, 1.0f), valueChangeGroup.RectTransform) { MaxSize = blockMaxSize }, - "", font: GUIStyle.SubHeadingFont) + reputationEffectBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), reputationEffectContainer.RectTransform), "", font: GUIStyle.SubHeadingFont) { AutoScaleVertical = true, CanBeFocused = false, @@ -419,64 +405,27 @@ namespace Barotrauma { if (CurrentLocation != null) { - int balanceAfterTransaction = activeTab switch + Color textColor = GUIStyle.ColorReputationNeutral; + string sign = ""; + int reputationModifier = (int)MathF.Round((CurrentLocation.GetStoreReputationModifier(activeTab == StoreTab.Buy) - 1) * 100); + if (reputationModifier > 0) { - StoreTab.Buy => ActiveStore.Balance + buyTotal, - StoreTab.Sell => ActiveStore.Balance - sellTotal, - StoreTab.SellSub => ActiveStore.Balance - sellFromSubTotal, - _ => throw new NotImplementedException(), - }; - if (balanceAfterTransaction != ActiveStore.Balance) - { - var newStatus = CurrentLocation.GetStoreBalanceStatus(balanceAfterTransaction); - if (ActiveStore.ActiveBalanceStatus.SellPriceModifier != newStatus.SellPriceModifier) - { - string tooltipTag = newStatus.SellPriceModifier > ActiveStore.ActiveBalanceStatus.SellPriceModifier ? - "campaingstore.valueincreasetooltip" : "campaingstore.valuedecreasetooltip"; - sellValueContainer.ToolTip = TextManager.Get(tooltipTag); - currentSellValueBlock.TextColor = newStatus.Color; - sellValueChangeArrow.Color = newStatus.Color; - sellValueChangeArrow.Visible = true; - newSellValueBlock.TextColor = newStatus.Color; - newSellValueBlock.Text = $"{(newStatus.SellPriceModifier * 100).FormatZeroDecimal()} %"; - return $"{(ActiveStore.ActiveBalanceStatus.SellPriceModifier * 100).FormatZeroDecimal()} %"; - } + textColor = IsBuying ? GUIStyle.ColorReputationLow : GUIStyle.ColorReputationHigh; + sign = "+"; } - sellValueContainer.ToolTip = TextManager.Get("campaignstore.sellvaluetooltip"); - currentSellValueBlock.TextColor = ActiveStore.BalanceColor; - sellValueChangeArrow.Visible = false; - newSellValueBlock.Text = null; - return $"{(ActiveStore.ActiveBalanceStatus.SellPriceModifier * 100).FormatZeroDecimal()} %"; + else if (reputationModifier < 0) + { + textColor = IsBuying ? GUIStyle.ColorReputationHigh : GUIStyle.ColorReputationLow; + } + reputationEffectBlock.TextColor = textColor; + return $"{sign}{reputationModifier}%"; } else { - sellValueContainer.ToolTip = null; - sellValueChangeArrow.Visible = false; - newSellValueBlock.Text = null; - return null; + return ""; } } }; - Vector4 newPadding = currentSellValueBlock.Padding; - newPadding.Z = 0; - currentSellValueBlock.Padding = newPadding; - float relativeHeight = 0.45f; - float relativeWidth = (relativeHeight * valueChangeGroup.Rect.Height) / valueChangeGroup.Rect.Width; - sellValueChangeArrow = new GUIImage(new RectTransform(new Vector2(relativeWidth, relativeHeight), valueChangeGroup.RectTransform), "StoreArrow", scaleToFit: true) - { - CanBeFocused = false, - Visible = false - }; - newSellValueBlock = new GUITextBlock(new RectTransform(new Vector2(blockWidth, 1.0f), valueChangeGroup.RectTransform) { MaxSize = blockMaxSize }, - "", font: GUIStyle.SubHeadingFont) - { - AutoScaleVertical = true, - CanBeFocused = false, - TextScale = 1.1f - }; - newPadding = newSellValueBlock.Padding; - newPadding.X = 0; - newSellValueBlock.Padding = newPadding; // Store mode buttons ------------------------------------------------ var modeButtonFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.4f / 14.0f), storeContent.RectTransform), style: null); diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/SubmarineSelection.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/SubmarineSelection.cs index 35ec743ca..707e3580a 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/SubmarineSelection.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/SubmarineSelection.cs @@ -626,7 +626,7 @@ namespace Barotrauma { if (GameMain.Client == null) { - SubmarineInfo newSub = GameMain.GameSession.SwitchSubmarine(selectedSubmarine, deliveryFee); + GameMain.GameSession.SwitchSubmarine(selectedSubmarine, deliveryFee); RefreshSubmarineDisplay(true); } else @@ -664,7 +664,7 @@ namespace Barotrauma if (GameMain.Client == null) { GameMain.GameSession.PurchaseSubmarine(selectedSubmarine); - SubmarineInfo newSub = GameMain.GameSession.SwitchSubmarine(selectedSubmarine, 0); + GameMain.GameSession.SwitchSubmarine(selectedSubmarine, 0); RefreshSubmarineDisplay(true); } else diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs index 8530291a5..87b4b2e92 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs @@ -54,47 +54,65 @@ namespace Barotrauma private ushort currentPing; private readonly Character character; - private readonly bool hasCharacter; + private readonly bool wasCharacterAlive; private readonly GUITextBlock textBlock; private readonly GUIFrame frame; private readonly GUIImage permissionIcon; - public LinkedGUI(Client client, GUIFrame frame, bool hasCharacter, GUITextBlock textBlock, GUIImage permissionIcon) + public LinkedGUI(Client client, GUIFrame frame, GUITextBlock textBlock, GUIImage permissionIcon) { this.Client = client; this.textBlock = textBlock; this.frame = frame; - this.hasCharacter = hasCharacter; this.permissionIcon = permissionIcon; + character = client?.Character; + wasCharacterAlive = client.Character != null && !client.Character.IsDead; } - public LinkedGUI(Character character, GUIFrame frame, bool hasCharacter, GUITextBlock textBlock) + public LinkedGUI(Character character, GUIFrame frame, GUITextBlock textBlock) { this.character = character; this.textBlock = textBlock; this.frame = frame; - this.hasCharacter = hasCharacter; + wasCharacterAlive = character != null && !character.IsDead; } public bool HasMultiplayerCharacterChanged() { if (Client == null) { return false; } - bool characterState = Client.Character != null; - if (characterState && Client.Character.IsDead) characterState = false; - return hasCharacter != characterState; + + if (GameSettings.CurrentConfig.VerboseLogging) + { + if (Client.Character != character) + { + DebugConsole.Log($"Refreshing tab menu crew list (client \"{Client.Name}\"'s character changed from \"{character?.Name ?? "null"}\" to \"{Client.Character?.Name ?? "null"}\")"); + } + } + return Client.Character != character; } - public bool HasMultiplayerCharacterDied() - { - if (Client == null || !hasCharacter || Client.Character == null) { return false; } - return Client.Character.IsDead; - } - - public bool HasAICharacterDied() + public bool HasCharacterDied() { if (character == null) { return false; } - return character.IsDead; + bool isAlive = !(character?.IsDead ?? true); + if (GameSettings.CurrentConfig.VerboseLogging) + { + if (wasCharacterAlive && !isAlive) + { + DebugConsole.Log(Client == null ? + $"Refreshing tab menu crew list (character \"{character?.Name ?? "null"}\" died)" : + $"Refreshing tab menu crew list (client \"{Client.Name}\"'s character \"{character?.Name ?? "null"}\" died)"); + } + else if (!wasCharacterAlive && isAlive) + { + DebugConsole.Log(Client == null ? + + $"Refreshing tab menu crew list (character \"{character?.Name ?? "null"}\" came back to life)" : + $"Refreshing tab menu crew list (client \"{Client.Name}\"'s character \"{character?.Name ?? "null"}\" came back to life)"); + } + } + return isAlive != wasCharacterAlive; } public void TryPingRefresh() @@ -207,7 +225,7 @@ namespace Barotrauma { linkedGUIList[i].TryPingRefresh(); linkedGUIList[i].TryPermissionIconRefresh(GetPermissionIcon(linkedGUIList[i].Client)); - if (linkedGUIList[i].HasMultiplayerCharacterChanged() || linkedGUIList[i].HasMultiplayerCharacterDied() || linkedGUIList[i].HasAICharacterDied()) + if (linkedGUIList[i].HasMultiplayerCharacterChanged() || linkedGUIList[i].HasCharacterDied()) { RemoveCurrentElements(); CreateMultiPlayerList(true); @@ -219,10 +237,11 @@ namespace Barotrauma { for (int i = 0; i < linkedGUIList.Count; i++) { - if (linkedGUIList[i].HasAICharacterDied()) + if (linkedGUIList[i].HasCharacterDied()) { RemoveCurrentElements(); CreateSinglePlayerList(true); + return; } } } @@ -297,6 +316,10 @@ namespace Barotrauma var balanceFrame = new GUIFrame(new RectTransform(new Point(innerLayoutGroup.Rect.Width, innerLayoutGroup.Rect.Height - infoFrameHolderHeight), parent: innerLayoutGroup.RectTransform), style: "InnerFrame"); GUITextBlock balanceText = new GUITextBlock(new RectTransform(Vector2.One, balanceFrame.RectTransform), string.Empty, textAlignment: Alignment.Right); + if (GameMain.IsMultiplayer) + { + balanceText.ToolTip = TextManager.Get("bankdescription"); + } 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)) @@ -337,7 +360,7 @@ namespace Barotrauma var talentsButton = createTabButton(InfoFrameTab.Talents, "tabmenu.character"); talentsButton.OnAddedToGUIUpdateList += (component) => { - talentsButton.Enabled = Character.Controlled?.Info != null; + talentsButton.Enabled = Character.Controlled?.Info != null || (GameMain.Client?.CharacterInfo != null && GameMain.GameSession?.GameMode is MultiPlayerCampaign); if (!talentsButton.Enabled && selectedTab == InfoFrameTab.Talents) { SelectInfoFrameTab(InfoFrameTab.Crew); @@ -560,7 +583,7 @@ namespace Barotrauma GUITextBlock characterNameBlock = new GUITextBlock(new RectTransform(new Point(characterColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform), ToolBox.LimitString(character.Info.Name, GUIStyle.Font, characterColumnWidth), textAlignment: Alignment.Center, textColor: character.Info.Job.Prefab.UIColor); - linkedGUIList.Add(new LinkedGUI(character, frame, !character.IsDead, textBlock: null)); + linkedGUIList.Add(new LinkedGUI(character, frame, textBlock: null)); } private void CreateMultiPlayerListContentHolder(GUILayoutGroup headerFrame) @@ -657,7 +680,7 @@ namespace Barotrauma if (client != null) { CreateNameWithPermissionIcon(client, paddedFrame, out GUIImage permissionIcon); - linkedGUIList.Add(new LinkedGUI(client, frame, true, + linkedGUIList.Add(new LinkedGUI(client, frame, new GUITextBlock(new RectTransform(new Point(pingColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform), client.Ping.ToString(), textAlignment: Alignment.Center), permissionIcon)); } @@ -668,12 +691,12 @@ namespace Barotrauma if (character is AICharacter) { - linkedGUIList.Add(new LinkedGUI(character, frame, !character.IsDead, + linkedGUIList.Add(new LinkedGUI(character, frame, new GUITextBlock(new RectTransform(new Point(pingColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform), TextManager.Get("tabmenu.bot"), textAlignment: Alignment.Center) { ForceUpperCase = ForceUpperCase.Yes })); } else { - linkedGUIList.Add(new LinkedGUI(client: null, frame, true, textBlock: null, permissionIcon: null)); + linkedGUIList.Add(new LinkedGUI(client: null, frame, textBlock: null, permissionIcon: null)); new GUICustomComponent(new RectTransform(new Point(pingColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform, Anchor.Center), onDraw: (sb, component) => DrawDisconnectedIcon(sb, component.Rect)) { @@ -718,7 +741,7 @@ namespace Barotrauma }; CreateNameWithPermissionIcon(client, paddedFrame, out GUIImage permissionIcon); - linkedGUIList.Add(new LinkedGUI(client, frame, false, + linkedGUIList.Add(new LinkedGUI(client, frame, new GUITextBlock(new RectTransform(new Point(pingColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform), client.Ping.ToString(), textAlignment: Alignment.Center), permissionIcon)); @@ -775,19 +798,27 @@ namespace Barotrauma Stretch = true }; + new GUIFrame(new RectTransform(Vector2.One, paddedLayoutGroup.RectTransform), style: null) + { + IgnoreLayoutGroups = true, + ToolTip = TextManager.Get("walletdescription") + }; + if (character.IsBot) { return; } Sprite walletSprite = GUIStyle.CrewWalletIconSmall.Value.Sprite; - GUIImage icon = new GUIImage(new RectTransform(Vector2.One, paddedLayoutGroup.RectTransform, scaleBasis: ScaleBasis.BothHeight), walletSprite, scaleToFit: true); + GUIImage icon = new GUIImage(new RectTransform(Vector2.One, paddedLayoutGroup.RectTransform, scaleBasis: ScaleBasis.BothHeight), walletSprite, scaleToFit: true) { CanBeFocused = false }; GUITextBlock walletBlock = new GUITextBlock(new RectTransform(Vector2.One, paddedLayoutGroup.RectTransform), string.Empty, textAlignment: Alignment.Right, font: GUIStyle.Font) { AutoScaleHorizontal = true, - Padding = Vector4.Zero + Padding = Vector4.Zero, + CanBeFocused = false }; GUIImage largeIcon = new GUIImage(new RectTransform(Vector2.One, paddedLayoutGroup.RectTransform), walletSprite, scaleToFit: true) { + CanBeFocused = false, IgnoreLayoutGroups = true, Visible = false }; @@ -971,16 +1002,25 @@ namespace Barotrauma float relativeX = icon.RectTransform.NonScaledSize.X / (float)icon.Parent.RectTransform.NonScaledSize.X; GUILayoutGroup headerTextLayout = new GUILayoutGroup(new RectTransform(new Vector2(1.0f - relativeX, 1f), headerLayout.RectTransform), isHorizontal: true) { Stretch = true }; new GUITextBlock(new RectTransform(new Vector2(0.5f, 1f), headerTextLayout.RectTransform), TextManager.Get("crewwallet.wallet"), font: GUIStyle.LargeFont); + GUIFrame walletTooltipFrame = new GUIFrame(new RectTransform(Vector2.One, headerLayout.RectTransform), style: null) + { + IgnoreLayoutGroups = true, + ToolTip = TextManager.Get("walletdescription") + }; GUITextBlock moneyBlock = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1f), headerTextLayout.RectTransform), TextManager.FormatCurrency(targetWallet.Balance), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.Right); GUILayoutGroup middleLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.66f), walletLayout.RectTransform)); GUILayoutGroup salaryTextLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.5f), middleLayout.RectTransform), isHorizontal: true); GUITextBlock salaryTitle = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1f), salaryTextLayout.RectTransform), TextManager.Get("crewwallet.salary"), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.BottomLeft); GUITextBlock rewardBlock = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1f), salaryTextLayout.RectTransform), string.Empty, textAlignment: Alignment.BottomRight); + GUIFrame salaryTooltipFrame = new GUIFrame(new RectTransform(Vector2.One, middleLayout.RectTransform), style: null) + { + IgnoreLayoutGroups = true, + ToolTip = TextManager.Get("crewwallet.salary.tooltip") + }; GUILayoutGroup sliderLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.5f), middleLayout.RectTransform), isHorizontal: true, childAnchor: Anchor.Center); GUIScrollBar salarySlider = new GUIScrollBar(new RectTransform(new Vector2(0.9f, 1f), sliderLayout.RectTransform), style: "GUISlider", barSize: 0.03f) { - ToolTip = TextManager.Get("crewwallet.salary.tooltip"), Range = new Vector2(0, 1), BarScrollValue = targetWallet.RewardDistribution / 100f, Step = 0.01f, @@ -1050,149 +1090,195 @@ namespace Barotrauma return true; } }; + + Identifier eventIdentifier = nameof(CreateWalletFrame).ToIdentifier(); + ToggleTransferMenuIcon(transferMenuButton, open: isTransferMenuOpen); ToggleCenterButton(centerButton, isSending); - - if (!(Character.Controlled is { } myCharacter)) - { - salarySlider.Enabled = false; - transferAmountInput.Enabled = false; - centerButton.Enabled = false; - confirmButton.Enabled = false; - return; - } - - bool hasMoneyPermissions = CampaignMode.AllowedToManageWallets(); - salarySlider.Enabled = hasMoneyPermissions; Wallet otherWallet; + GameMain.Client?.OnPermissionChanged.RegisterOverwriteExisting(eventIdentifier, e => UpdateWalletInterface(registerEvents: false)); + UpdateWalletInterface(registerEvents: true); - switch (hasMoneyPermissions) + void UpdateWalletInterface(bool registerEvents) { - case true: - rightName.Text = TextManager.Get("crewwallet.bank"); - otherWallet = campaign.Bank; - break; - case false when character == myCharacter: - rightName.Text = TextManager.Get("crewwallet.bank"); - otherWallet = campaign.Bank; - isSending = true; - ToggleCenterButton(centerButton, isSending); - break; - default: - rightName.Text = myCharacter.Name; - otherWallet = campaign.PersonalWallet; - break; - } - - MedicalClinicUI.EnsureTextDoesntOverflow(rightName.Text.ToString(), rightName, rightLayout.Rect, layoutGroups); - updateButtonText(); - if (!hasMoneyPermissions) - { - if (character != Character.Controlled) + if (!(Character.Controlled is { } myCharacter)) { - centerButton.Enabled = centerButton.CanBeFocused = false; + salarySlider.Enabled = false; + transferAmountInput.Enabled = false; + centerButton.Enabled = false; + confirmButton.Enabled = false; + return; } - salarySlider.Enabled = salarySlider.CanBeFocused = false; - } - leftBalance.Text = TextManager.FormatCurrency(otherWallet.Balance); + bool hasMoneyPermissions = CampaignMode.AllowedToManageWallets(); + salarySlider.Enabled = hasMoneyPermissions; - UpdateAllInputs(); + switch (hasMoneyPermissions) + { + case true: + rightName.Text = TextManager.Get("crewwallet.bank"); + otherWallet = campaign.Bank; + break; + case false when character == myCharacter: + rightName.Text = TextManager.Get("crewwallet.bank"); + otherWallet = campaign.Bank; + isSending = true; + ToggleCenterButton(centerButton, isSending); + break; + default: + rightName.Text = myCharacter.Name; + otherWallet = campaign.PersonalWallet; + break; + } + + MedicalClinicUI.EnsureTextDoesntOverflow(rightName.Text.ToString(), rightName, rightLayout.Rect, layoutGroups); + + UpdatedConfirmButtonText(); + + if (!hasMoneyPermissions) + { + if (character != Character.Controlled) + { + centerButton.Enabled = centerButton.CanBeFocused = false; + } + + salarySlider.Enabled = salarySlider.CanBeFocused = false; + } + + leftBalance.Text = TextManager.FormatCurrency(otherWallet.Balance); - centerButton.OnClicked = (btn, o) => - { - isSending = !isSending; - updateButtonText(); - ToggleCenterButton(btn, isSending); UpdateAllInputs(); - return true; - }; - void updateButtonText() - { - confirmButton.Text = TextManager.Get(hasMoneyPermissions || isSending ? "confirm" : "crewwallet.requestmoney"); - } + if (!registerEvents) { return; } - transferAmountInput.OnValueChanged = input => - { - UpdateInputs(); - }; - - transferAmountInput.OnValueEntered = input => - { - UpdateAllInputs(); - }; - - Identifier eventIdentifier = nameof(CreateWalletFrame).ToIdentifier(); - campaign.OnMoneyChanged.RegisterOverwriteExisting(eventIdentifier, e => - { - if (e.Wallet == targetWallet) + centerButton.OnClicked = (btn, o) => { - moneyBlock.Text = TextManager.FormatCurrency(e.Info.Balance); - salarySlider.BarScrollValue = e.Info.RewardDistribution / 100f; + isSending = !isSending; + UpdatedConfirmButtonText(); + ToggleCenterButton(btn, isSending); + UpdateAllInputs(); + return true; + }; + + transferAmountInput.OnValueChanged = input => + { + UpdateInputs(); + }; + + transferAmountInput.OnValueEntered = input => + { + UpdateAllInputs(); + }; + + resetButton.OnClicked = (button, o) => + { + transferAmountInput.IntValue = 0; + UpdateAllInputs(); + return true; + }; + + confirmButton.OnClicked = (button, o) => + { + int amount = transferAmountInput.IntValue; + if (amount == 0) { return false; } + + Option target1 = Option.Some(character), + target2 = otherWallet == campaign.Bank ? Option.None() : Option.Some(myCharacter); + if (isSending) { (target1, target2) = (target2, target1); } + + SendTransaction(target1, target2, amount); + isTransferMenuOpen = false; + ToggleTransferMenuIcon(transferMenuButton, isTransferMenuOpen); + return true; + }; + + campaign.OnMoneyChanged.RegisterOverwriteExisting(eventIdentifier, e => + { + if (e.Wallet == targetWallet) + { + moneyBlock.Text = TextManager.FormatCurrency(e.Info.Balance); + salarySlider.BarScrollValue = e.Info.RewardDistribution / 100f; + } + + UpdateAllInputs(); + }); + + registeredEvents.Add(eventIdentifier); + + void UpdatedConfirmButtonText() + { + confirmButton.Text = TextManager.Get(hasMoneyPermissions || isSending ? "confirm" : "crewwallet.requestmoney"); } - UpdateAllInputs(); - }); - registeredEvents.Add(eventIdentifier); - resetButton.OnClicked = (button, o) => - { - transferAmountInput.IntValue = 0; - UpdateAllInputs(); - return true; - }; - - confirmButton.OnClicked = (button, o) => - { - int amount = transferAmountInput.IntValue; - if (amount == 0) { return false; } - - Option target1 = Option.Some(character), - target2 = otherWallet == campaign.Bank ? Option.None() : Option.Some(myCharacter); - if (isSending) { (target1, target2) = (target2, target1); } - - SendTransaction(target1, target2, amount); - isTransferMenuOpen = false; - ToggleTransferMenuIcon(transferMenuButton, isTransferMenuOpen); - return true; - }; - - void UpdateAllInputs() - { - UpdateInputs(); - UpdateMaxInput(); - } - - void UpdateInputs() - { - confirmButton.Enabled = resetButton.Enabled = transferAmountInput.IntValue > 0; - if (transferAmountInput.IntValue == 0) + void UpdateAllInputs() { - rightBalance.Text = TextManager.FormatCurrency(otherWallet.Balance); - rightBalance.TextColor = GUIStyle.TextColorNormal; - leftBalance.Text = TextManager.FormatCurrency(targetWallet.Balance); - leftBalance.TextColor = GUIStyle.TextColorNormal; + UpdateInputs(); + UpdateMaxInput(); } - else if (isSending) + + void UpdateInputs() { - rightBalance.Text = TextManager.FormatCurrency(otherWallet.Balance + transferAmountInput.IntValue); - rightBalance.TextColor = GUIStyle.Blue; - leftBalance.Text = TextManager.FormatCurrency(targetWallet.Balance - transferAmountInput.IntValue); - leftBalance.TextColor = GUIStyle.Red; + confirmButton.Enabled = resetButton.Enabled = transferAmountInput.IntValue > 0; + if (transferAmountInput.IntValue == 0) + { + rightBalance.Text = TextManager.FormatCurrency(otherWallet.Balance); + rightBalance.TextColor = GUIStyle.TextColorNormal; + leftBalance.Text = TextManager.FormatCurrency(targetWallet.Balance); + leftBalance.TextColor = GUIStyle.TextColorNormal; + } + else if (isSending) + { + rightBalance.Text = TextManager.FormatCurrency(otherWallet.Balance + transferAmountInput.IntValue); + rightBalance.TextColor = GUIStyle.Blue; + leftBalance.Text = TextManager.FormatCurrency(targetWallet.Balance - transferAmountInput.IntValue); + leftBalance.TextColor = GUIStyle.Red; + } + else + { + rightBalance.Text = TextManager.FormatCurrency(otherWallet.Balance - transferAmountInput.IntValue); + rightBalance.TextColor = GUIStyle.Red; + leftBalance.Text = TextManager.FormatCurrency(targetWallet.Balance + transferAmountInput.IntValue); + leftBalance.TextColor = GUIStyle.Blue; + } } - else + + void UpdateMaxInput() { - rightBalance.Text = TextManager.FormatCurrency(otherWallet.Balance - transferAmountInput.IntValue); - rightBalance.TextColor = GUIStyle.Red; - leftBalance.Text = TextManager.FormatCurrency(targetWallet.Balance + transferAmountInput.IntValue); - leftBalance.TextColor = GUIStyle.Blue; + int maxValue = isSending ? targetWallet.Balance : otherWallet.Balance; + transferAmountInput.MaxValueInt = maxValue; + + transferAmountInput.Enabled = true; + transferAmountInput.ToolTip = string.Empty; + + if (!hasMoneyPermissions && GameMain.Client?.ServerSettings is { } serverSettings) + { + transferAmountInput.MaxValueInt = Math.Min(maxValue, serverSettings.MaximumTransferRequest); + if (serverSettings.MaximumTransferRequest <= 0) + { + transferAmountInput.Enabled = false; + transferAmountInput.ToolTip = TextManager.Get("wallettransferrequestdisabled"); + } + } } } - void UpdateMaxInput() + void SetRewardText(int value, GUITextBlock block) { - transferAmountInput.MaxValueInt = isSending ? targetWallet.Balance : otherWallet.Balance; + var (_, percentage, sum) = Mission.GetRewardShare(value, salaryCrew, Option.None()); + LocalizedString tooltip = string.Empty; + block.TextColor = GUIStyle.TextColorNormal; + + if (sum > 100) + { + tooltip = TextManager.GetWithVariables("crewwallet.salary.over100toolitp", ("[sum]", $"{(int)sum}"), ("[newvalue]", $"{percentage}")); + block.TextColor = GUIStyle.Orange; + } + + LocalizedString text = TextManager.GetWithVariable("percentageformat", "[value]", $"{value}"); + + block.Text = text; + block.ToolTip = RichString.Rich(tooltip); } static void ToggleTransferMenuIcon(GUIButton btn, bool open) @@ -1235,24 +1321,6 @@ namespace Barotrauma transfer.Write(msg); GameMain.Client?.ClientPeer?.Send(msg, DeliveryMethod.Reliable); } - - void SetRewardText(int value, GUITextBlock block) - { - var (_, percentage, sum) = Mission.GetRewardShare(value, salaryCrew, Option.None()); - LocalizedString tooltip = string.Empty; - block.TextColor = GUIStyle.TextColorNormal; - - if (sum > 100) - { - tooltip = TextManager.GetWithVariables("crewwallet.salary.over100toolitp", ("[sum]", $"{(int)sum}"), ("[newvalue]", $"{percentage}")); - block.TextColor = GUIStyle.Orange; - } - - LocalizedString text = TextManager.GetWithVariable("percentageformat", "[value]", $"{value}"); - - block.Text = text; - block.ToolTip = RichString.Rich(tooltip); - } } private GUIComponent CreateClientInfoFrame(GUIFrame frame, Client client, Sprite permissionIcon = null) @@ -1740,9 +1808,6 @@ namespace Barotrauma talentButtons.Clear(); talentCornerIcons.Clear(); - Character controlledCharacter = Character.Controlled; - if (controlledCharacter == null) { return; } - GUIFrame talentFrameBackground = new GUIFrame(new RectTransform(Vector2.One, infoFrame.RectTransform, Anchor.TopCenter), style: "GUIFrameListBox"); int padding = GUI.IntScale(15); GUIFrame talentFrameContent = new GUIFrame(new RectTransform(new Point(talentFrameBackground.Rect.Width - padding, talentFrameBackground.Rect.Height - padding), infoFrame.RectTransform, Anchor.Center), style: null); @@ -1762,13 +1827,20 @@ namespace Barotrauma GameMain.NetLobbyScreen.CreatePlayerFrame(playerFrame, alwaysAllowEditing: true, createPendingText: false); } + /*Character controlledCharacter = Character.Controlled; + if (controlledCharacter == null) { return; } + if (controlledCharacter.Info is null) { DebugConsole.ThrowError("No character info found for talent UI"); return; - } + }*/ - selectedTalents = controlledCharacter.Info.GetUnlockedTalentsInTree().ToList(); + Character controlledCharacter = Character.Controlled; + CharacterInfo info = controlledCharacter?.Info ?? GameMain.Client?.CharacterInfo; + if (info == null) { return; } + + Job job = info.Job; GUILayoutGroup talentFrameLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f), talentFrameMain.RectTransform, anchor: Anchor.Center), childAnchor: Anchor.TopCenter) { @@ -1776,9 +1848,7 @@ namespace Barotrauma }; GUILayoutGroup talentInfoLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.25f), talentFrameLayoutGroup.RectTransform, Anchor.Center), isHorizontal: true); - - CharacterInfo info = controlledCharacter.Info; - Job job = info.Job; + new GUICustomComponent(new RectTransform(new Vector2(0.25f, 1f), talentInfoLayoutGroup.RectTransform), onDraw: (batch, component) => { @@ -1801,11 +1871,11 @@ namespace Barotrauma GUITextBlock traitBlock = new GUITextBlock(new RectTransform(Vector2.One, nameLayout.RectTransform), traitString, font: GUIStyle.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); + GUIFrame talentsOutsideTreeFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.35f), nameLayout.RectTransform, Anchor.BottomCenter), style: null); if (!(GameMain.NetworkMember is null)) { - GUIButton newCharacterBox = new GUIButton(new RectTransform(new Vector2(0.675f, 1f), endocrineFrame.RectTransform, Anchor.TopLeft), text: GameMain.NetLobbyScreen.CampaignCharacterDiscarded ? TextManager.Get("settings") : TextManager.Get("createnew")) + GUIButton newCharacterBox = new GUIButton(new RectTransform(new Vector2(0.675f, 1f), talentsOutsideTreeFrame.RectTransform, Anchor.TopLeft), text: GameMain.NetLobbyScreen.CampaignCharacterDiscarded ? TextManager.Get("settings") : TextManager.Get("createnew")) { IgnoreLayoutGroups = true }; @@ -1852,13 +1922,14 @@ namespace Barotrauma } } - IEnumerable endocrineTalents = info.GetEndocrineTalents().Select(e => TalentPrefab.TalentPrefabs.Find(c => c.Identifier == e)); + IEnumerable talentsOutsideTree = info.GetUnlockedTalentsOutsideTree().Select(e => TalentPrefab.TalentPrefabs.Find(c => c.Identifier == e)); - if (endocrineTalents.Count() > 0) + if (talentsOutsideTree.Count() > 0) { - GUIImage endocrineIcon = new GUIImage(new RectTransform(new Vector2(0.275f, 1f), endocrineFrame.RectTransform, anchor: Anchor.TopRight, scaleBasis: ScaleBasis.Normal), style: "EndocrineReminderIcon") + //TODO: replace with something more generic + GUIImage endocrineIcon = new GUIImage(new RectTransform(new Vector2(0.275f, 1f), talentsOutsideTreeFrame.RectTransform, anchor: Anchor.TopRight, scaleBasis: ScaleBasis.Normal), style: "EndocrineReminderIcon") { - ToolTip = $"{TextManager.Get("afflictionname.endocrineboost")}\n\n{string.Join(", ", endocrineTalents.Select(e => e.DisplayName))}" + ToolTip = $"{TextManager.Get("afflictionname.endocrineboost")}\n\n{string.Join(", ", talentsOutsideTree.Select(e => e.DisplayName))}" }; } @@ -1870,49 +1941,55 @@ namespace Barotrauma 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); + CreateTalentSkillList(controlledCharacter, info, skillListBox); - if (!TalentTree.JobTalentTrees.TryGet(controlledCharacter.Info.Job.Prefab.Identifier, out TalentTree talentTree)) { return; } - - new GUIFrame(new RectTransform(new Vector2(1f, 1f), talentFrameLayoutGroup.RectTransform), style: "HorizontalLine"); - - GUIListBox talentTreeListBox = new GUIListBox(new RectTransform(new Vector2(1f, 0.7f), talentFrameLayoutGroup.RectTransform, Anchor.TopCenter), isHorizontal: true, style: null); - - List subTreeNames = new List(); - foreach (var subTree in talentTree.TalentSubTrees) + if (controlledCharacter != null) { - 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); + if (!TalentTree.JobTalentTrees.TryGet(info.Job.Prefab.Identifier, out TalentTree talentTree)) { return; } - GUIFrame subtreeTitleFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.111f), subTreeLayoutGroup.RectTransform, anchor: Anchor.TopCenter), style: null); - int elementPadding = GUI.IntScale(8); - Point headerSize = subtreeTitleFrame.RectTransform.NonScaledSize; - GUIFrame subTreeTitleBackground = new GUIFrame(new RectTransform(new Point(headerSize.X - elementPadding, headerSize.Y), subtreeTitleFrame.RectTransform, anchor: Anchor.Center), style: "SubtreeHeader"); - subTreeNames.Add(new GUITextBlock(new RectTransform(Vector2.One, subTreeTitleBackground.RectTransform, anchor: Anchor.TopCenter), subTree.DisplayName, font: GUIStyle.SubHeadingFont, textAlignment: Alignment.Center)); + new GUIFrame(new RectTransform(new Vector2(1f, 1f), talentFrameLayoutGroup.RectTransform), style: "HorizontalLine"); - for (int i = 0; i < 4; i++) + GUIListBox talentTreeListBox = new GUIListBox(new RectTransform(new Vector2(1f, 0.7f), talentFrameLayoutGroup.RectTransform, Anchor.TopCenter), isHorizontal: true, style: null); + + selectedTalents = info.GetUnlockedTalentsInTree().ToList(); + + List subTreeNames = new List(); + foreach (var subTree in talentTree.TalentSubTrees) { - GUIFrame talentOptionFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.222f), subTreeLayoutGroup.RectTransform, anchor: Anchor.TopCenter), style: null); + 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); - Point talentFrameSize = talentOptionFrame.RectTransform.NonScaledSize; + GUIFrame subtreeTitleFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.111f), subTreeLayoutGroup.RectTransform, anchor: Anchor.TopCenter), style: null); + int elementPadding = GUI.IntScale(8); + Point headerSize = subtreeTitleFrame.RectTransform.NonScaledSize; + GUIFrame subTreeTitleBackground = new GUIFrame(new RectTransform(new Point(headerSize.X - elementPadding, headerSize.Y), subtreeTitleFrame.RectTransform, anchor: Anchor.Center), style: "SubtreeHeader"); + subTreeNames.Add(new GUITextBlock(new RectTransform(Vector2.One, subTreeTitleBackground.RectTransform, anchor: Anchor.TopCenter), subTree.DisplayName, font: GUIStyle.SubHeadingFont, textAlignment: Alignment.Center)); - GUIFrame talentBackground = new GUIFrame(new RectTransform(new Point(talentFrameSize.X - elementPadding, talentFrameSize.Y - elementPadding), talentOptionFrame.RectTransform, anchor: Anchor.Center), style: "TalentBackground"); - GUIFrame talentBackgroundHighlight = new GUIFrame(new RectTransform(Vector2.One, talentBackground.RectTransform, anchor: Anchor.Center), style: "TalentBackgroundGlow") { Visible = false }; - - GUIImage cornerIcon = new GUIImage(new RectTransform(new Vector2(0.2f), talentOptionFrame.RectTransform, anchor: Anchor.BottomRight, scaleBasis: ScaleBasis.BothHeight) { MaxSize = new Point(16) }, style: null) + for (int i = 0; i < 4; i++) { - CanBeFocused = false - }; + GUIFrame talentOptionFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.222f), subTreeLayoutGroup.RectTransform, anchor: Anchor.TopCenter), style: null); - Point iconSize = cornerIcon.RectTransform.NonScaledSize; - cornerIcon.RectTransform.AbsoluteOffset = new Point(iconSize.X / 2, iconSize.Y / 2); + 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") + { + Color = talentStageBackgroundColors[TalentTree.TalentTreeStageState.Locked] + }; + GUIFrame talentBackgroundHighlight = new GUIFrame(new RectTransform(Vector2.One, talentBackground.RectTransform, anchor: Anchor.Center), style: "TalentBackgroundGlow") { Visible = false }; + + GUIImage cornerIcon = new GUIImage(new RectTransform(new Vector2(0.2f), talentOptionFrame.RectTransform, anchor: Anchor.BottomRight, scaleBasis: ScaleBasis.BothHeight) { MaxSize = new Point(16) }, style: null) + { + CanBeFocused = false, + Color = talentStageBackgroundColors[TalentTree.TalentTreeStageState.Locked] + }; + + Point iconSize = cornerIcon.RectTransform.NonScaledSize; + cornerIcon.RectTransform.AbsoluteOffset = new Point(iconSize.X / 2, iconSize.Y / 2); + + if (subTree.TalentOptionStages.Count <= i) { continue; } - if (subTree.TalentOptionStages.Count > i) - { TalentOption talentOption = subTree.TalentOptionStages[i]; - GUILayoutGroup talentOptionCenterGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.75f, 0.7f), talentOptionFrame.RectTransform, Anchor.Center), childAnchor: Anchor.CenterLeft); - GUILayoutGroup talentOptionLayoutGroup = new GUILayoutGroup(new RectTransform(Vector2.One, talentOptionCenterGroup.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft) { Stretch = true }; foreach (TalentPrefab talent in talentOption.Talents.OrderBy(t => t.Identifier)) @@ -1929,6 +2006,7 @@ namespace Barotrauma ToolTip = RichString.Rich(talent.DisplayName + "\n\n" + talent.Description), UserData = talent.Identifier, PressedColor = pressedColor, + Enabled = controlledCharacter != null, OnClicked = (button, userData) => { // deselect other buttons in tier by removing their selected talents from pool @@ -1961,7 +2039,7 @@ namespace Barotrauma }, }; - talentButton.Color = talentButton.HoverColor = talentButton.PressedColor = talentButton.SelectedColor = Color.Transparent; + talentButton.Color = talentButton.HoverColor = talentButton.PressedColor = talentButton.SelectedColor = talentButton.DisabledColor = Color.Transparent; GUIComponent iconImage; if (talent.Icon is null) @@ -1971,6 +2049,7 @@ namespace Barotrauma OutlineColor = GUIStyle.Red, TextColor = GUIStyle.Red, PressedColor = unselectableColor, + DisabledColor = unselectableColor, CanBeFocused = false, }; } @@ -1979,63 +2058,63 @@ namespace Barotrauma iconImage = new GUIImage(new RectTransform(Vector2.One, talentButton.RectTransform, anchor: Anchor.Center), sprite: talent.Icon, scaleToFit: true) { PressedColor = unselectableColor, + DisabledColor = unselectableColor * 0.5f, CanBeFocused = false, }; } - + iconImage.Enabled = talentButton.Enabled; talentButtons.Add((talentButton, iconImage)); } - - talentCornerIcons.Add((subTree.Identifier, i, cornerIcon, talentBackground, talentBackgroundHighlight)); + talentCornerIcons.Add((subTree.Identifier, i, cornerIcon, talentBackground, talentBackgroundHighlight)); } } - } - GUITextBlock.AutoScaleAndNormalize(subTreeNames); + GUITextBlock.AutoScaleAndNormalize(subTreeNames); - GUILayoutGroup talentBottomFrame = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.07f), talentFrameLayoutGroup.RectTransform, Anchor.TopCenter), isHorizontal: true) { RelativeSpacing = 0.01f }; + GUILayoutGroup talentBottomFrame = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.07f), talentFrameLayoutGroup.RectTransform, Anchor.TopCenter), isHorizontal: true) { RelativeSpacing = 0.01f }; - GUILayoutGroup experienceLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.59f, 1f), talentBottomFrame.RectTransform)); - GUIFrame experienceBarFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.5f), experienceLayout.RectTransform), style: null); + GUILayoutGroup experienceLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.59f, 1f), talentBottomFrame.RectTransform)); + GUIFrame experienceBarFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.5f), experienceLayout.RectTransform), style: null); - experienceBar = new GUIProgressBar(new RectTransform(new Vector2(1f, 1f), experienceBarFrame.RectTransform, Anchor.CenterLeft), - barSize: controlledCharacter.Info.GetProgressTowardsNextLevel(), color: GUIStyle.Green) - { - IsHorizontal = true, - }; + experienceBar = new GUIProgressBar(new RectTransform(new Vector2(1f, 1f), experienceBarFrame.RectTransform, Anchor.CenterLeft), + barSize: info.GetProgressTowardsNextLevel(), color: GUIStyle.Green) + { + IsHorizontal = true, + }; - experienceText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), experienceBarFrame.RectTransform, anchor: Anchor.Center), "", font: GUIStyle.Font, textAlignment: Alignment.CenterRight) - { - Shadow = true, - ToolTip = TextManager.Get("experiencetooltip") - }; + experienceText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), experienceBarFrame.RectTransform, anchor: Anchor.Center), "", font: GUIStyle.Font, textAlignment: Alignment.CenterRight) + { + Shadow = true, + ToolTip = TextManager.Get("experiencetooltip") + }; - talentPointText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), experienceLayout.RectTransform, anchor: Anchor.Center), "", font: GUIStyle.SubHeadingFont, textAlignment: Alignment.CenterRight) { AutoScaleVertical = true }; + talentPointText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), experienceLayout.RectTransform, anchor: Anchor.Center), "", font: GUIStyle.SubHeadingFont, 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); + 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); + } UpdateTalentInfo(); } - private void CreateTalentSkillList(Character character, GUIListBox parent) + private void CreateTalentSkillList(Character character, CharacterInfo info, GUIListBox parent) { parent.Content.ClearChildren(); List skillNames = new List(); - foreach (Skill skill in character.Info.Job.GetSkills()) + foreach (Skill skill in info.Job.GetSkills()) { GUILayoutGroup skillContainer = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.2f), parent.Content.RectTransform), isHorizontal: true) { CanBeFocused = false }; skillNames.Add(new GUITextBlock(new RectTransform(new Vector2(0.7f, 1f), skillContainer.RectTransform), TextManager.Get($"skillname.{skill.Identifier}").Fallback(skill.Identifier.Value))); 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); + float modifiedSkillLevel = character?.GetSkillLevel(skill.Identifier) ?? skill.Level; if (!MathUtils.NearlyEqual(MathF.Floor(modifiedSkillLevel), MathF.Floor(skill.Level))) { int skillChange = (int)MathF.Floor(modifiedSkillLevel - skill.Level); @@ -2129,7 +2208,7 @@ namespace Barotrauma talentButton.icon.HoverColor = hoverColor; } - CreateTalentSkillList(controlledCharacter, skillListBox); + CreateTalentSkillList(controlledCharacter, controlledCharacter.Info, skillListBox); } private void ApplyTalents(Character controlledCharacter) @@ -2157,6 +2236,7 @@ namespace Barotrauma private bool ResetTalentSelection(GUIButton guiButton, object userData) { Character controlledCharacter = Character.Controlled; + if (controlledCharacter?.Info == null) { return false; } selectedTalents = controlledCharacter.Info.GetUnlockedTalentsInTree().ToList(); UpdateTalentInfo(); return true; diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs b/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs index 9d9cb8ac5..db6a05ff0 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs @@ -546,6 +546,10 @@ namespace Barotrauma yield return CoroutineStatus.Running; +#if DEBUG + LevelGenerationParams.CheckValidity(); +#endif + MainMenuScreen.Select(); foreach (Identifier steamError in SteamManager.InitializationErrors) @@ -1033,11 +1037,6 @@ namespace Barotrauma { GUI.SetSavingIndicatorState(true); - if (GameSession.Submarine != null && !GameSession.Submarine.Removed) - { - GameSession.SubmarineInfo = new SubmarineInfo(GameSession.Submarine); - } - // Update store stock when saving and quitting in an outpost (normally updated when CampaignMode.End() is called) if (GameSession?.Campaign is SinglePlayerCampaign spCampaign && Level.IsLoadedOutpost && spCampaign.Map?.CurrentLocation != null && spCampaign.CargoManager != null) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs index 439e43f96..7dbeffd05 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs @@ -81,7 +81,6 @@ namespace Barotrauma : this(isSinglePlayer) { AddCharacterElements(element); - ActiveOrdersElement = element.GetChildElement("activeorders"); } partial void InitProjectSpecific() @@ -3661,9 +3660,9 @@ namespace Barotrauma crewList.ClearChildren(); } - public void Save(XElement parentElement) + public XElement Save(XElement parentElement) { - XElement element = new XElement("crew"); + var element = new XElement("crew"); for (int i = 0; i < characterInfos.Count; i++) { var ci = characterInfos[i]; @@ -3674,8 +3673,8 @@ namespace Barotrauma infoElement.Add(new XAttribute("crewlistindex", ci.CrewListIndex)); if (ci.LastControlled) { infoElement.Add(new XAttribute("lastcontrolled", true)); } } - SaveActiveOrders(element); - parentElement.Add(element); + parentElement?.Add(element); + return element; } public static void ClientReadActiveOrders(IReadMessage inc) diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/Data/Wallet.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/Data/Wallet.cs index 8c8832a05..4a91c9026 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/Data/Wallet.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/Data/Wallet.cs @@ -13,6 +13,11 @@ namespace Barotrauma partial void SettingsChanged(Option balanceChanged, Option rewardChanged) { + if (Owner is Some { Value: var character }) + { + if (!character.IsPlayer) { return; } + } + CampaignMode campaign = GameMain.GameSession?.Campaign; WalletChangedData data = new WalletChangedData { diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/SinglePlayerCampaign.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/SinglePlayerCampaign.cs index 53ba428fb..7e7506931 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/SinglePlayerCampaign.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/SinglePlayerCampaign.cs @@ -92,6 +92,7 @@ namespace Barotrauma break; case "crew": GameMain.GameSession.CrewManager = new CrewManager(subElement, true); + ActiveOrdersElement = element.GetChildElement("activeorders"); break; case "map": map = Map.Load(this, subElement, Settings); @@ -242,11 +243,10 @@ namespace Barotrauma crewDead = false; endTimer = 5.0f; CrewManager.InitSinglePlayerRound(); - if (petsElement != null) - { - PetBehavior.LoadPets(petsElement); - } - CrewManager.LoadActiveOrders(); + LoadPets(); + LoadActiveOrders(); + + CargoManager.InitPurchasedIDCards(); GUI.DisableSavingIndicatorDelayed(); } @@ -461,41 +461,7 @@ namespace Barotrauma if (success) { - if (leavingSub != Submarine.MainSub && !leavingSub.DockedTo.Contains(Submarine.MainSub)) - { - Submarine.MainSub = leavingSub; - GameMain.GameSession.Submarine = leavingSub; - GameMain.GameSession.SubmarineInfo = leavingSub.Info; - leavingSub.Info.FilePath = System.IO.Path.Combine(SaveUtil.TempPath, leavingSub.Info.Name + ".sub"); - var subsToLeaveBehind = GetSubsToLeaveBehind(leavingSub); - GameMain.GameSession.OwnedSubmarines.Add(leavingSub.Info); - foreach (Submarine sub in subsToLeaveBehind) - { - GameMain.GameSession.OwnedSubmarines.RemoveAll(s => s != leavingSub.Info && s.Name == sub.Info.Name); - MapEntity.mapEntityList.RemoveAll(e => e.Submarine == sub && e is LinkedSubmarine); - LinkedSubmarine.CreateDummy(leavingSub, sub); - } - } - - GameMain.GameSession.SubmarineInfo = new SubmarineInfo(GameMain.GameSession.Submarine); - - if (PendingSubmarineSwitch != null) - { - SubmarineInfo previousSub = GameMain.GameSession.SubmarineInfo; - GameMain.GameSession.SubmarineInfo = PendingSubmarineSwitch; - - for (int i = 0; i < GameMain.GameSession.OwnedSubmarines.Count; i++) - { - if (GameMain.GameSession.OwnedSubmarines[i].Name == previousSub.Name) - { - GameMain.GameSession.OwnedSubmarines[i] = previousSub; - break; - } - } - } - SaveUtil.SaveGame(GameMain.GameSession.SavePath); - PendingSubmarineSwitch = null; } else { @@ -766,11 +732,10 @@ namespace Barotrauma c.Info.SaveOrderData(); } - petsElement = new XElement("pets"); - PetBehavior.SavePets(petsElement); - modeElement.Add(petsElement); + SavePets(modeElement); + var crewManagerElement = CrewManager.Save(modeElement); + SaveActiveOrders(crewManagerElement); - CrewManager.Save(modeElement); CampaignMetadata.Save(modeElement); Map.Save(modeElement); CargoManager?.SavePurchasedItems(modeElement); diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/DoctorTutorial.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/DoctorTutorial.cs index 4f6f7d940..5edeeb26c 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/DoctorTutorial.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/DoctorTutorial.cs @@ -257,7 +257,7 @@ namespace Barotrauma.Tutorials yield return new WaitForSeconds(2.0f); }*/ - TriggerTutorialSegment(0, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Select), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Deselect), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.ToggleInventory)); // Medical supplies objective + TriggerTutorialSegment(0, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Select), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Deselect)); // Medical supplies objective do { diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/EngineerTutorial.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/EngineerTutorial.cs index 80f7ed649..ca52c58ba 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/EngineerTutorial.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/EngineerTutorial.cs @@ -275,7 +275,7 @@ namespace Barotrauma.Tutorials do { yield return null; } while (!engineer_equipmentObjectiveSensor.MotionDetected); GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Engineer.Radio.Equipment"), ChatMessageType.Radio, null); yield return new WaitForSeconds(0.5f, false); - TriggerTutorialSegment(0, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Select), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Deselect), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.ToggleInventory)); // Retrieve equipment + TriggerTutorialSegment(0, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Select), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Deselect)); // Retrieve equipment bool firstSlotRemoved = false; bool secondSlotRemoved = false; bool thirdSlotRemoved = false; diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/MechanicTutorial.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/MechanicTutorial.cs index a9ea9047a..de6a066f2 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/MechanicTutorial.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/MechanicTutorial.cs @@ -330,7 +330,7 @@ namespace Barotrauma.Tutorials yield return new WaitForSeconds(0.0f, false); GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Mechanic.Radio.Equipment"), ChatMessageType.Radio, null); do { yield return null; } while (!mechanic_equipmentObjectiveSensor.MotionDetected); - TriggerTutorialSegment(1, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Select), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Deselect), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.ToggleInventory)); // Equipment & inventory objective + TriggerTutorialSegment(1, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Select), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Deselect)); // Equipment & inventory objective SetHighlight(mechanic_equipmentCabinet.Item, true); bool firstSlotRemoved = false; bool secondSlotRemoved = false; @@ -372,7 +372,7 @@ namespace Barotrauma.Tutorials // Room 3 do { yield return null; } while (!mechanic_weldingObjectiveSensor.MotionDetected); - TriggerTutorialSegment(2, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Aim), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Shoot), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.ToggleInventory)); // Welding objective + TriggerTutorialSegment(2, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Aim), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Shoot)); // Welding objective do { if (!mechanic.HasEquippedItem("divingmask".ToIdentifier())) diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/ScenarioTutorial.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/ScenarioTutorial.cs index 60369b132..5c9061d8e 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/ScenarioTutorial.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/ScenarioTutorial.cs @@ -106,7 +106,7 @@ namespace Barotrauma.Tutorials Character.Controlled = character; character.GiveJobItems(null); - var idCard = character.Inventory.FindItemByIdentifier("idcard".ToIdentifier()); + var idCard = character.Inventory.FindItemByTag("identitycard".ToIdentifier()); if (idCard == null) { DebugConsole.ThrowError("Item prefab \"ID Card\" not found!"); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/CharacterInventory.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/CharacterInventory.cs index 4746f0e9f..2245ad99d 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/CharacterInventory.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/CharacterInventory.cs @@ -525,6 +525,7 @@ namespace Barotrauma if (!AccessibleWhenAlive && !character.IsDead && !AccessibleByOwner) { syncItemsDelay = Math.Max(syncItemsDelay - deltaTime, 0.0f); + doubleClickedItems.Clear(); return; } @@ -931,7 +932,7 @@ namespace Barotrauma // Move the item from the subinventory to the selected container return QuickUseAction.PutToContainer; } - else + else if (character.Inventory.AccessibleWhenAlive || character.Inventory.AccessibleByOwner) { // Take from the subinventory and place it in the character's main inventory if no target container is selected return QuickUseAction.TakeFromContainer; @@ -959,7 +960,8 @@ namespace Barotrauma } else if (character.SelectedBy?.Inventory != null && Character.Controlled == character.SelectedBy && - !character.SelectedBy.Inventory.Locked && + !character.SelectedBy.Inventory.Locked && + (character.SelectedBy.Inventory.AccessibleWhenAlive || character.SelectedBy.Inventory.AccessibleByOwner) && allowInventorySwap) { return QuickUseAction.TakeFromCharacter; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Door.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Door.cs index 2b372fd27..9ea53f8da 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Door.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Door.cs @@ -66,6 +66,9 @@ namespace Barotrauma.Items.Components rect.Height = (int)(rect.Height * (1.0f - openState)); } + //only merge the door's convex hull with overlapping wall segments if it's fully open or fully closed + //it's the heaviest part of changing the convex hull, and doesn't need to be done while the door is still in motion + bool mergeOverlappingSegments = openState <= 0.0f || openState >= 1.0f; if (Window.Height > 0 && Window.Width > 0) { if (IsHorizontal) @@ -88,7 +91,7 @@ namespace Barotrauma.Items.Components else { convexHull2.Enabled = true; - convexHull2.SetVertices(GetConvexHullCorners(rect2)); + convexHull2.SetVertices(GetConvexHullCorners(rect2), mergeOverlappingSegments); } } } @@ -112,7 +115,7 @@ namespace Barotrauma.Items.Components else { convexHull2.Enabled = true; - convexHull2.SetVertices(GetConvexHullCorners(rect2)); + convexHull2.SetVertices(GetConvexHullCorners(rect2), mergeOverlappingSegments); } } } @@ -127,7 +130,7 @@ namespace Barotrauma.Items.Components else { convexHull.Enabled = true; - convexHull.SetVertices(GetConvexHullCorners(rect)); + convexHull.SetVertices(GetConvexHullCorners(rect), mergeOverlappingSegments); } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Deconstructor.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Deconstructor.cs index 85791f95b..2a6d4328e 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Deconstructor.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Deconstructor.cs @@ -1,4 +1,5 @@ -using Barotrauma.Networking; +using Barotrauma.Extensions; +using Barotrauma.Networking; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using System.Linq; @@ -78,7 +79,7 @@ namespace Barotrauma.Items.Components activateButton = new GUIButton(new RectTransform(new Vector2(0.95f, 0.8f), buttonContainer.RectTransform), TextManager.Get("DeconstructorDeconstruct"), style: "DeviceButton") { TextBlock = { AutoScaleHorizontal = true }, - OnClicked = ToggleActive + OnClicked = OnActivateButtonClicked }; inSufficientPowerWarning = new GUITextBlock(new RectTransform(Vector2.One, activateButton.RectTransform), TextManager.Get("DeconstructorNoPower"), textColor: GUIStyle.Orange, textAlignment: Alignment.Center, color: Color.Black, style: "OuterGlow", wrap: true) @@ -164,7 +165,7 @@ namespace Barotrauma.Items.Components } } } - activateButton.Enabled = outputsFound; + activateButton.Enabled = outputsFound || !InputContainer.Inventory.IsEmpty(); activateButton.Text = TextManager.Get(ActivateButtonText); }; } @@ -236,8 +237,19 @@ namespace Barotrauma.Items.Components inSufficientPowerWarning.Visible = IsActive && !hasPower; } - private bool ToggleActive(GUIButton button, object obj) + private bool OnActivateButtonClicked(GUIButton button, object obj) { + var disallowedItem = inputContainer.Inventory.FindItem(i => !i.AllowDeconstruct, recursive: false); + if (disallowedItem != null) + { + int index = inputContainer.Inventory.FindIndex(disallowedItem); + if (index >= 0 && index < inputContainer.Inventory.visualSlots.Length) + { + var slot = inputContainer.Inventory.visualSlots[index]; + slot?.ShowBorderHighlight(GUIStyle.Red, 0.1f, 0.9f); + } + return true; + } if (GameMain.Client != null) { pendingState = !IsActive; @@ -247,7 +259,6 @@ namespace Barotrauma.Items.Components { SetActive(!IsActive, Character.Controlled); } - return true; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs index 90f0e04b3..0b3583f3b 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs @@ -1367,6 +1367,15 @@ namespace Barotrauma.Items.Components pingRadius, prevPingRadius, 250.0f, 150.0f, range, pingStrength, passive); } + if (pingSource.Y - Level.Loaded.BottomPos < range) + { + CreateBlipsForLine( + new Vector2(pingSource.X - range, Level.Loaded.BottomPos), + new Vector2(pingSource.X + range, Level.Loaded.BottomPos), + pingSource, transducerPos, + pingRadius, prevPingRadius, + 250.0f, 150.0f, range, pingStrength, passive); + } List cells = Level.Loaded.GetCells(pingSource, 7); foreach (Voronoi2.VoronoiCell cell in cells) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/RepairTool.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/RepairTool.cs index 759f67ad6..a449dc00f 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/RepairTool.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/RepairTool.cs @@ -23,10 +23,10 @@ namespace Barotrauma.Items.Components } #endif - private List particleEmitters = new List(); - private List particleEmitterHitStructure = new List(); - private List particleEmitterHitCharacter = new List(); - private List> particleEmitterHitItem = new List>(); + private readonly List particleEmitters = new List(); + private readonly List particleEmitterHitStructure = new List(); + private readonly List particleEmitterHitCharacter = new List(); + private readonly List<(RelatedItem relatedItem, ParticleEmitter emitter)> particleEmitterHitItem = new List<(RelatedItem relatedItem, ParticleEmitter emitter)>(); private float prevProgressBarState; private Item prevProgressBarTarget = null; @@ -46,10 +46,7 @@ namespace Barotrauma.Items.Components Identifier[] excludedIdentifiers = subElement.GetAttributeIdentifierArray("excludedidentifiers", Array.Empty()); if (excludedIdentifiers.Length == 0) { excludedIdentifiers = subElement.GetAttributeIdentifierArray("excludedidentifier", Array.Empty()); } - particleEmitterHitItem.Add( - new Pair( - new RelatedItem(identifiers, excludedIdentifiers), - new ParticleEmitter(subElement))); + particleEmitterHitItem.Add((new RelatedItem(identifiers, excludedIdentifiers), new ParticleEmitter(subElement))); break; case "particleemitterhitstructure": particleEmitterHitStructure.Add(new ParticleEmitter(subElement)); @@ -139,11 +136,11 @@ namespace Barotrauma.Items.Components Vector2 particlePos = ConvertUnits.ToDisplayUnits(pickedPosition); if (targetItem.Submarine != null) particlePos += targetItem.Submarine.DrawPosition; - foreach (var emitter in particleEmitterHitItem) + foreach ((RelatedItem relatedItem, ParticleEmitter emitter) in particleEmitterHitItem) { - if (!emitter.First.MatchesItem(targetItem)) { continue; } + if (!relatedItem.MatchesItem(targetItem)) { continue; } float particleAngle = item.body.Rotation + MathHelper.ToRadians(BarrelRotation) + ((item.body.Dir > 0.0f) ? 0.0f : MathHelper.Pi); - emitter.Second.Emit(deltaTime, particlePos, item.CurrentHull, particleAngle + MathHelper.Pi, -particleAngle + MathHelper.Pi); + emitter.Emit(deltaTime, particlePos, item.CurrentHull, particleAngle + MathHelper.Pi, -particleAngle + MathHelper.Pi); } } #if DEBUG diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Connection.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Connection.cs index 93c653f0b..8d5d16766 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Connection.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Connection.cs @@ -38,7 +38,7 @@ namespace Barotrauma.Items.Components int totalWireCount = 0; foreach (Connection c in panel.Connections) { - totalWireCount += c.Wires.Count(w => w != null); + totalWireCount += c.Wires.Count; } Wire equippedWire = null; @@ -87,8 +87,8 @@ namespace Barotrauma.Items.Components (DraggingConnected.Connections[0] == null && DraggingConnected.Connections[1] == null) || (DraggingConnected.Connections.Contains(c) && DraggingConnected.Connections.Contains(null))) { - int linkIndex = c.FindWireIndex(DraggingConnected.Item); - if (linkIndex > -1 || panel.DisconnectedWires.Contains(DraggingConnected)) + var linkedWire = c.FindWireByItem(DraggingConnected.Item); + if (linkedWire != null || panel.DisconnectedWires.Contains(DraggingConnected)) { Inventory.DraggingItems.Clear(); Inventory.DraggingItems.Add(DraggingConnected.Item); @@ -108,7 +108,7 @@ namespace Barotrauma.Items.Components c.DrawWires(spriteBatch, panel, rightPos, rightWirePos, mouseInRect, equippedWire, wireInterval); } rightPos.Y += connectorIntervalLeft; - rightWirePos.Y += c.Wires.Count(w => w != null) * wireInterval; + rightWirePos.Y += c.Wires.Count * wireInterval; } else { @@ -121,7 +121,7 @@ namespace Barotrauma.Items.Components c.DrawWires(spriteBatch, panel, leftPos, leftWirePos, mouseInRect, equippedWire, wireInterval); } leftPos.Y += connectorIntervalRight; - leftWirePos.Y += c.Wires.Count(w => w != null) * wireInterval; + leftWirePos.Y += c.Wires.Count * wireInterval; } } } @@ -228,15 +228,15 @@ namespace Barotrauma.Items.Components { float connectorSpriteScale = (35.0f / connectionSprite.SourceRect.Width) * panel.Scale; - for (int i = 0; i < MaxWires; i++) + foreach (var wire in wires) { - if (wires[i] == null || wires[i].Hidden || (DraggingConnected == wires[i] && (mouseIn || Screen.Selected == GameMain.SubEditorScreen))) { continue; } - if (wires[i].HiddenInGame && Screen.Selected == GameMain.GameScreen) { continue; } + if (wire.Hidden || (DraggingConnected == wire && (mouseIn || Screen.Selected == GameMain.SubEditorScreen))) { continue; } + if (wire.HiddenInGame && Screen.Selected == GameMain.GameScreen) { continue; } - Connection recipient = wires[i].OtherConnection(this); + Connection recipient = wire.OtherConnection(this); LocalizedString label = recipient == null ? "" : recipient.item.Name + $" ({recipient.DisplayName})"; - if (wires[i].Locked) { label += "\n" + TextManager.Get("ConnectionLocked"); } - DrawWire(spriteBatch, wires[i], position, wirePosition, equippedWire, panel, label); + if (wire.Locked) { label += "\n" + TextManager.Get("ConnectionLocked"); } + DrawWire(spriteBatch, wire, position, wirePosition, equippedWire, panel, label); wirePosition.Y += wireInterval; } @@ -248,18 +248,17 @@ namespace Barotrauma.Items.Components if (!PlayerInput.PrimaryMouseButtonHeld()) { if ((GameMain.NetworkMember != null || panel.CheckCharacterSuccess(Character.Controlled)) && - Wires.Count(w => w != null) < MaxPlayerConnectableWires) + Wires.Count < MaxPlayerConnectableWires) { //find an empty cell for the new connection - int index = FindEmptyIndex(); - if (index > -1 && !Wires.Contains(DraggingConnected)) + if (WireSlotsAvailable() && !Wires.Contains(DraggingConnected)) { bool alreadyConnected = DraggingConnected.IsConnectedTo(panel.Item); DraggingConnected.RemoveConnection(panel.Item); if (DraggingConnected.Connect(this, !alreadyConnected, true)) { var otherConnection = DraggingConnected.OtherConnection(this); - SetWire(index, DraggingConnected); + ConnectWire(DraggingConnected); } } } @@ -284,7 +283,7 @@ namespace Barotrauma.Items.Components flashColor * (float)Math.Sin(FlashTimer % flashCycleDuration / flashCycleDuration * MathHelper.Pi * 0.8f), scale: connectorSpriteScale); } - if (Wires.Any(w => w != null && w != DraggingConnected && !w.Hidden && (!w.HiddenInGame || Screen.Selected != GameMain.GameScreen))) + if (Wires.Any(w => w != DraggingConnected && !w.Hidden && (!w.HiddenInGame || Screen.Selected != GameMain.GameScreen))) { int screwIndex = (int)Math.Floor(position.Y / 30.0f) % screwSprites.Count; screwSprites[screwIndex].Draw(spriteBatch, position, scale: connectorSpriteScale); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/ConnectionPanel.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/ConnectionPanel.cs index 0d026f53b..21b3b4204 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/ConnectionPanel.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/ConnectionPanel.cs @@ -77,7 +77,7 @@ namespace Barotrauma.Items.Components } } - public override void Move(Vector2 amount) + public override void Move(Vector2 amount, bool ignoreContacts = false) { if (item.Submarine == null || item.Submarine.Loading || Screen.Selected != GameMain.SubEditorScreen) { return; } MoveConnectedWires(amount); @@ -173,9 +173,8 @@ namespace Barotrauma.Items.Components private void ApplyRemoteState(IReadMessage msg) { - List prevWires = Connections.SelectMany(c => c.Wires.Where(w => w != null)).ToList(); - List newWires = new List(); - + List prevWires = Connections.SelectMany(c => c.Wires).ToList(); + ushort userID = msg.ReadUInt16(); if (userID == 0) @@ -195,7 +194,9 @@ namespace Barotrauma.Items.Components foreach (Connection connection in Connections) { - for (int i = 0; i < connection.MaxWires; i++) + HashSet newWires = new HashSet(); + uint wireCount = msg.ReadVariableUInt32(); + for (int i = 0; i < wireCount; i++) { ushort wireId = msg.ReadUInt16(); @@ -204,9 +205,18 @@ namespace Barotrauma.Items.Components if (wireComponent == null) { continue; } newWires.Add(wireComponent); + } - connection.SetWire(i, wireComponent); - wireComponent.Connect(connection, false); + Wire[] oldWires = connection.Wires.Where(w => !newWires.Contains(w)).ToArray(); + foreach (var wire in oldWires) + { + connection.DisconnectWire(wire); + } + + foreach (var wire in newWires.Where(w => !connection.Wires.Contains(w)).ToArray()) + { + connection.ConnectWire(wire); + wire.Connect(connection, false); } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/CustomInterface.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/CustomInterface.cs index 50a7f11d3..1853d569c 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/CustomInterface.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/CustomInterface.cs @@ -205,7 +205,7 @@ namespace Barotrauma.Items.Components foreach (var uiElement in uiElements) { if (!(uiElement.UserData is CustomInterfaceElement element)) { continue; } - bool visible = Screen.Selected == GameMain.SubEditorScreen || element.StatusEffects.Any() || element.HasPropertyName || (element.Connection != null && element.Connection.Wires.Any(w => w != null)); + bool visible = Screen.Selected == GameMain.SubEditorScreen || element.StatusEffects.Any() || element.HasPropertyName || (element.Connection != null && element.Connection.Wires.Count > 0); if (visible) { visibleElementCount++; } if (uiElement.Visible != visible) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Wire.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Wire.cs index fcede0408..e0b3a75f0 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Wire.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Wire.cs @@ -526,7 +526,7 @@ namespace Barotrauma.Items.Components } } - public override void Move(Vector2 amount) + public override void Move(Vector2 amount, bool ignoreContacts = false) { //only used in the sub editor, hence only in the client project if (!item.IsSelected) { return; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Turret.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Turret.cs index 42fc88545..919eae413 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Turret.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Turret.cs @@ -175,7 +175,7 @@ namespace Barotrauma.Items.Components }; } - public override void Move(Vector2 amount) + public override void Move(Vector2 amount, bool ignoreContacts = false) { widgets.Clear(); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/DockingPort.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/DockingPort.cs index c8eb6b435..9466e4377 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/DockingPort.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/DockingPort.cs @@ -6,8 +6,10 @@ using Microsoft.Xna.Framework.Graphics; namespace Barotrauma.Items.Components { - partial class DockingPort : ItemComponent, IDrawableComponent, IServerSerializable + partial class DockingPort : ItemComponent, IDrawableComponent, IServerSerializable, IClientSerializable { + private GUIMessageBox autodockingVerification; + public Vector2 DrawSize { //use the extents of the item as the draw size @@ -180,5 +182,10 @@ namespace Barotrauma.Items.Components Undock(); } } + + public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null) + { + msg.Write((byte)allowOutpostAutoDocking); + } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs index 96c563ea2..b23835fec 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs @@ -265,7 +265,7 @@ namespace Barotrauma else { LocalizedString description = item.Description; - if (item.Prefab.Identifier == "idcard" || item.Tags.Contains("despawncontainer")) + if (item.HasTag("identitycard") || item.HasTag("despawncontainer")) { string[] readTags = item.Tags.Split(','); string idName = null; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs index 7a16fca75..e7cc950ab 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs @@ -1,4 +1,6 @@ -using Barotrauma.Items.Components; +using Barotrauma.Extensions; +using Barotrauma.Items.Components; +using Barotrauma.MapCreatures.Behavior; using Barotrauma.Networking; using FarseerPhysics; using Microsoft.Xna.Framework; @@ -6,13 +8,8 @@ using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using System; using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using Barotrauma.Extensions; -using Barotrauma.MapCreatures.Behavior; -using FarseerPhysics.Dynamics; -using FarseerPhysics.Dynamics.Contacts; using System.Collections.Immutable; +using System.Linq; namespace Barotrauma { @@ -720,7 +717,7 @@ namespace Barotrauma //remove identifiers from the available container tags //(otherwise the list will include many irrelevant options, //e.g. "weldingtool" because a welding fuel tank can be placed inside the container, etc) - .Where(t => !ItemPrefab.Prefabs.Any(ip => ip.Identifier == t)) + .Where(t => !ItemPrefab.Prefabs.ContainsKey(t)) .ToImmutableHashSet(); new GUIButton(new RectTransform(new Vector2(0.1f, 1), tagsField.RectTransform, Anchor.TopRight), "...") { @@ -1174,7 +1171,7 @@ namespace Barotrauma texts.Clear(); string nameText = Name; - if (Prefab.Identifier == "idcard" || Tags.Contains("despawncontainer")) + if (Prefab.Tags.Contains("identitycard") || Tags.Contains("despawncontainer")) { string[] readTags = Tags.Split(','); string idName = null; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/ItemAssemblyPrefab.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/ItemAssemblyPrefab.cs index ed2f608d8..797801633 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/ItemAssemblyPrefab.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/ItemAssemblyPrefab.cs @@ -80,15 +80,17 @@ namespace Barotrauma } Vector2 center = new Vector2((minX + maxX) / 2.0f, (minY + maxY) / 2.0f); if (Submarine.MainSub != null) { center -= Submarine.MainSub.HiddenSubPosition; } - center.X -= MathUtils.RoundTowardsClosest(center.X, Submarine.GridSize.X); - center.Y -= MathUtils.RoundTowardsClosest(center.Y, Submarine.GridSize.Y); + + Vector2 offsetFromGrid = new Vector2( + MathUtils.RoundTowardsClosest(center.X, Submarine.GridSize.X) - center.X, + MathUtils.RoundTowardsClosest(center.Y, Submarine.GridSize.Y) - center.Y - Submarine.GridSize.Y / 2); MapEntity.SelectedList.Clear(); assemblyEntities.ForEach(e => MapEntity.AddSelection(e)); foreach (MapEntity mapEntity in assemblyEntities) { - mapEntity.Move(-center); + mapEntity.Move(-center - offsetFromGrid); mapEntity.Submarine = Submarine.MainSub; var entityElement = mapEntity.Save(element); if (disabledEntities.Contains(mapEntity)) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/ConvexHull.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/ConvexHull.cs index 0483137ee..27b1aaf28 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/ConvexHull.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/ConvexHull.cs @@ -461,7 +461,7 @@ namespace Barotrauma.Lights Matrix.CreateTranslation(-origin.X, -origin.Y, 0.0f) * Matrix.CreateRotationZ(amount) * Matrix.CreateTranslation(origin.X, origin.Y, 0.0f); - SetVertices(vertices.Select(v => v.Pos).ToArray(), rotationMatrix); + SetVertices(vertices.Select(v => v.Pos).ToArray(), rotationMatrix: rotationMatrix); } private void CalculateDimensions() @@ -541,7 +541,7 @@ namespace Barotrauma.Lights } } - public void SetVertices(Vector2[] points, Matrix? rotationMatrix = null) + public void SetVertices(Vector2[] points, bool mergeOverlappingSegments = true, Matrix? rotationMatrix = null) { Debug.Assert(points.Length == 4, "Only rectangular convex hulls are supported"); @@ -594,13 +594,16 @@ namespace Barotrauma.Lights if (ParentEntity == null) { return; } - var chList = HullLists.Find(h => h.Submarine == ParentEntity.Submarine); - if (chList != null) + if (mergeOverlappingSegments) { - overlappingHulls.Clear(); - foreach (ConvexHull ch in chList.List) + var chList = HullLists.Find(h => h.Submarine == ParentEntity.Submarine); + if (chList != null) { - MergeOverlappingSegments(ch); + overlappingHulls.Clear(); + foreach (ConvexHull ch in chList.List) + { + MergeOverlappingSegments(ch); + } } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/RoundSound.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/RoundSound.cs index 5f4645cac..365db83ac 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/RoundSound.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/RoundSound.cs @@ -81,11 +81,18 @@ namespace Barotrauma } catch (System.IO.FileNotFoundException e) { - string errorMsg = "Failed to load sound file \"" + filename + "\"."; + string errorMsg = "Failed to load sound file \"" + filename + "\" (file not found)."; DebugConsole.ThrowError(errorMsg, e); GameAnalyticsManager.AddErrorEventOnce("RoundSound.LoadRoundSound:FileNotFound" + filename, GameAnalyticsManager.ErrorSeverity.Error, errorMsg + "\n" + Environment.StackTrace.CleanupStackTrace()); return null; } + catch (System.IO.InvalidDataException e) + { + string errorMsg = "Failed to load sound file \"" + filename + "\" (invalid data)."; + DebugConsole.ThrowError(errorMsg, e); + GameAnalyticsManager.AddErrorEventOnce("RoundSound.LoadRoundSound:InvalidData" + filename, GameAnalyticsManager.ErrorSeverity.Error, errorMsg + "\n" + Environment.StackTrace.CleanupStackTrace()); + return null; + } } RoundSound newSound = new RoundSound(element, existingSound); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Submarine.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Submarine.cs index eff30e678..45218c779 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Submarine.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Submarine.cs @@ -525,7 +525,7 @@ namespace Barotrauma Item.ItemList.Count(it2 => it2.linkedTo.Contains(item) && !item.linkedTo.Contains(it2)); for (int i = 0; i < item.Connections.Count; i++) { - int wireCount = item.Connections[i].Wires.Count(w => w != null); + int wireCount = item.Connections[i].Wires.Count; if (doorLinks + wireCount > item.Connections[i].MaxWires) { errorMsgs.Add(TextManager.GetWithVariables("InsufficientFreeConnectionsWarning", diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/ChatMessage.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/ChatMessage.cs index 9e0472592..5e22aaa33 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/ChatMessage.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/ChatMessage.cs @@ -1,4 +1,5 @@ -using Microsoft.Xna.Framework; +using Barotrauma.Items.Components; +using Microsoft.Xna.Framework; using System; namespace Barotrauma.Networking @@ -183,6 +184,11 @@ namespace Barotrauma.Networking break; default: GameMain.Client.AddChatMessage(txt, type, senderName, senderClient, senderCharacter, changeType, textColor: textColor); + if (type == ChatMessageType.Radio && CanUseRadio(senderCharacter, out WifiComponent radio)) + { + Signal s = new Signal(txt, sender: senderCharacter, source: radio.Item); + radio.TransmitSignal(s, sentFromChat: true); + } break; } LastID = id; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs index ce27b16ae..858971e33 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs @@ -3,6 +3,7 @@ using Barotrauma.Steam; using Microsoft.Xna.Framework; using System; using System.Collections.Generic; +using System.Collections.Immutable; using Barotrauma.IO; using System.IO.Compression; using System.Linq; @@ -11,6 +12,7 @@ using System.Threading; using System.Threading.Tasks; using System.Xml.Linq; using Barotrauma.Extensions; +using Microsoft.Xna.Framework.Input; namespace Barotrauma.Networking { @@ -182,6 +184,20 @@ namespace Barotrauma.Networking get { return ownerKey > 0 || steamP2POwner; } } + internal readonly struct PermissionChangedEvent + { + public readonly ClientPermissions NewPermissions; + public readonly ImmutableArray NewPermittedConsoleCommands; + + public PermissionChangedEvent(ClientPermissions newPermissions, IReadOnlyList newPermittedConsoleCommands) + { + NewPermissions = newPermissions; + NewPermittedConsoleCommands = newPermittedConsoleCommands.ToImmutableArray(); + } + } + + public readonly NamedEvent OnPermissionChanged = new NamedEvent(); + public GameClient(string newName, string ip, UInt64 steamId, string serverName = null, int ownerKey = 0, bool steamP2POwner = false) { //TODO: gui stuff should probably not be here? @@ -570,7 +586,12 @@ namespace Barotrauma.Networking public override void Update(float deltaTime) { #if DEBUG - if (PlayerInput.GetKeyboardState.IsKeyDown(Microsoft.Xna.Framework.Input.Keys.P)) return; + if (PlayerInput.GetKeyboardState.IsKeyDown(Keys.P)) return; + + if (PlayerInput.KeyHit(Keys.Home)) + { + OnPermissionChanged.Invoke(new PermissionChangedEvent(permissions, permittedConsoleCommands)); + } #endif foreach (Client c in ConnectedClients) @@ -1019,40 +1040,25 @@ namespace Barotrauma.Networking GameMain.GameSession.EnforceMissionOrder(serverMissionIdentifiers); } - byte equalityCheckValueCount = inc.ReadByte(); - List levelEqualityCheckValues = new List(); - for (int i = 0; i < equalityCheckValueCount; i++) + var levelEqualityCheckValues = new Dictionary(); + foreach (Level.LevelGenStage stage in Enum.GetValues(typeof(Level.LevelGenStage)).OfType().OrderBy(s => s)) { - levelEqualityCheckValues.Add(inc.ReadInt32()); + levelEqualityCheckValues.Add(stage, inc.ReadInt32()); } - if (Level.Loaded.EqualityCheckValues.Count != levelEqualityCheckValues.Count) + foreach (var stage in levelEqualityCheckValues.Keys) { - string errorMsg = "Level equality check failed. The level generated at your end doesn't match the level generated by the server" + - " (client value count: " + Level.Loaded.EqualityCheckValues.Count + - ", level value count: " + levelEqualityCheckValues.Count + - ", seed: " + Level.Loaded.Seed + - ", sub: " + Submarine.MainSub.Info.Name + " (" + Submarine.MainSub.Info.MD5Hash.ShortRepresentation + ")" + - ", mirrored: " + Level.Loaded.Mirrored + ")."; - GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:LevelsDontMatch" + Level.Loaded.Seed, GameAnalyticsManager.ErrorSeverity.Error, errorMsg); - throw new Exception(errorMsg); - } - else - { - for (int i = 0; i < equalityCheckValueCount; i++) + if (Level.Loaded.EqualityCheckValues[stage] != levelEqualityCheckValues[stage]) { - if (Level.Loaded.EqualityCheckValues[i] != levelEqualityCheckValues[i]) - { - string errorMsg = "Level equality check failed. The level generated at your end doesn't match the level generated by the server" + - " (client value #" + i + ": " + Level.Loaded.EqualityCheckValues[i] + - ", server value #" + i + ": " + levelEqualityCheckValues[i].ToString("X") + - ", level value count: " + levelEqualityCheckValues.Count + - ", seed: " + Level.Loaded.Seed + - ", sub: " + Submarine.MainSub.Info.Name + " (" + Submarine.MainSub.Info.MD5Hash.ShortRepresentation + ")" + - ", mirrored: " + Level.Loaded.Mirrored + ")."; - GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:LevelsDontMatch" + Level.Loaded.Seed, GameAnalyticsManager.ErrorSeverity.Error, errorMsg); - throw new Exception(errorMsg); - } + string errorMsg = "Level equality check failed. The level generated at your end doesn't match the level generated by the server" + + " (client value " + stage + ": " + Level.Loaded.EqualityCheckValues[stage].ToString("X") + + ", server value " + stage + ": " + levelEqualityCheckValues[stage].ToString("X") + + ", level value count: " + levelEqualityCheckValues.Count + + ", seed: " + Level.Loaded.Seed + + ", sub: " + Submarine.MainSub.Info.Name + " (" + Submarine.MainSub.Info.MD5Hash.ShortRepresentation + ")" + + ", mirrored: " + Level.Loaded.Mirrored + ")."; + GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:LevelsDontMatch" + Level.Loaded.Seed, GameAnalyticsManager.ErrorSeverity.Error, errorMsg); + throw new Exception(errorMsg); } } @@ -1076,6 +1082,7 @@ namespace Barotrauma.Networking reconnectBox?.Close(); reconnectBox = null; + GameMain.ModDownloadScreen.Reset(); ContentPackageManager.EnabledPackages.Restore(); GUI.ClearCursorWait(); @@ -1380,18 +1387,13 @@ namespace Barotrauma.Networking private void SetMyPermissions(ClientPermissions newPermissions, IEnumerable permittedConsoleCommands) { if (!(this.permittedConsoleCommands.Any(c => !permittedConsoleCommands.Contains(c)) || - permittedConsoleCommands.Any(c => !this.permittedConsoleCommands.Contains(c)))) + permittedConsoleCommands.Any(c => !this.permittedConsoleCommands.Contains(c)))) { if (newPermissions == permissions) return; } - bool refreshCampaignUI = false; - - if (permissions.HasFlag(ClientPermissions.ManageCampaign) != newPermissions.HasFlag(ClientPermissions.ManageCampaign) || - permissions.HasFlag(ClientPermissions.ManageRound) != newPermissions.HasFlag(ClientPermissions.ManageRound)) - { - refreshCampaignUI = true; - } + bool refreshCampaignUI = permissions.HasFlag(ClientPermissions.ManageCampaign) != newPermissions.HasFlag(ClientPermissions.ManageCampaign) || + permissions.HasFlag(ClientPermissions.ManageRound) != newPermissions.HasFlag(ClientPermissions.ManageRound); permissions = newPermissions; this.permittedConsoleCommands = new List(permittedConsoleCommands); @@ -1430,7 +1432,7 @@ namespace Barotrauma.Networking if (newPermissions.HasFlag(ClientPermissions.ConsoleCommands)) { var commandsLabel = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), rightColumn.RectTransform), - TextManager.Get("PermittedConsoleCommands"), wrap: true, font: GUIStyle.SubHeadingFont); + TextManager.Get("PermittedConsoleCommands"), wrap: true, font: GUIStyle.SubHeadingFont); var commandList = new GUIListBox(new RectTransform(new Vector2(1.0f, 1.0f), rightColumn.RectTransform)); foreach (string permittedCommand in permittedConsoleCommands) { @@ -1469,6 +1471,7 @@ namespace Barotrauma.Networking } GameMain.NetLobbyScreen.RefreshEnabledElements(); + OnPermissionChanged.Invoke(new PermissionChangedEvent(permissions, this.permittedConsoleCommands)); } private IEnumerable StartGame(IReadMessage inc) @@ -3680,7 +3683,9 @@ namespace Barotrauma.Networking } if (Level.Loaded != null) { - errorLines.Add("Level: " + Level.Loaded.Seed + ", " + string.Join(", ", Level.Loaded.EqualityCheckValues.Select(cv => cv.ToString("X")))); + errorLines.Add("Level: " + Level.Loaded.Seed + ", " + + string.Join("; ", Level.Loaded.EqualityCheckValues.Select(cv + => cv.Key + "=" + cv.Value.ToString("X")))); errorLines.Add("Entity count before generating level: " + Level.Loaded.EntityCountBeforeGenerate); errorLines.Add("Entities:"); foreach (Entity e in Level.Loaded.EntitiesBeforeGenerate.OrderBy(e => e.CreationIndex)) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerSettings.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerSettings.cs index 430df186b..13f093216 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerSettings.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerSettings.cs @@ -621,7 +621,7 @@ namespace Barotrauma.Networking { Stretch = true }; - + var losModeRadioButtonGroup = new GUIRadioButtonGroup(); LosMode[] losModes = (LosMode[])Enum.GetValues(typeof(LosMode)); for (int i = 0; i < losModes.Length; i++) @@ -634,6 +634,14 @@ namespace Barotrauma.Networking var traitorsMinPlayerCount = CreateLabeledNumberInput(roundsTab, "ServerSettingsTraitorsMinPlayerCount", 1, 16, "ServerSettingsTraitorsMinPlayerCountToolTip"); GetPropertyData(nameof(TraitorsMinPlayerCount)).AssignGUIComponent(traitorsMinPlayerCount); + var maximumTransferAmount = CreateLabeledNumberInput(roundsTab, "serversettingsmaximumtransferrequest", 0, CampaignMode.MaxMoney, "serversettingsmaximumtransferrequesttooltip"); + GetPropertyData(nameof(MaximumTransferRequest)).AssignGUIComponent(maximumTransferAmount); + + var lootedMoneyDestination = CreateLabeledDropdown(roundsTab, "serversettingslootedmoneydestination", numElements: 2, "serversettingslootedmoneydestinationtooltip"); + lootedMoneyDestination.AddItem(TextManager.Get("lootedmoneydestination.bank"), LootedMoneyDestination.Bank); + lootedMoneyDestination.AddItem(TextManager.Get("lootedmoneydestination.wallet"), LootedMoneyDestination.Wallet); + GetPropertyData(nameof(LootedMoneyDestination)).AssignGUIComponent(lootedMoneyDestination); + var ragdollButtonBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), roundsTab.RectTransform), TextManager.Get("ServerSettingsAllowRagdollButton")); GetPropertyData(nameof(AllowRagdollButton)).AssignGUIComponent(ragdollButtonBox); @@ -991,6 +999,32 @@ namespace Barotrauma.Networking return input; } + private GUIDropDown CreateLabeledDropdown(GUIComponent parent, string labelTag, int numElements, string toolTipTag = null) + { + var container = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.1f), parent.RectTransform), isHorizontal: true) + { + Stretch = true, + RelativeSpacing = 0.05f, + ToolTip = TextManager.Get(labelTag) + }; + + var label = new GUITextBlock(new RectTransform(new Vector2(0.7f, 1.0f), container.RectTransform), + TextManager.Get(labelTag), textAlignment: Alignment.CenterLeft, font: GUIStyle.SmallFont) + { + AutoScaleHorizontal = true + }; + if (!string.IsNullOrEmpty(toolTipTag)) + { + label.ToolTip = TextManager.Get(toolTipTag); + } + var input = new GUIDropDown(new RectTransform(new Vector2(0.3f, 1.0f), container.RectTransform), elementCount: numElements); + + container.RectTransform.MinSize = new Point(0, input.RectTransform.MinSize.Y); + container.RectTransform.MaxSize = new Point(int.MaxValue, input.RectTransform.MaxSize.Y); + + return input; + } + private bool SelectSettingsTab(GUIButton button, object obj) { settingsTabIndex = (int)obj; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Particles/Particle.cs b/Barotrauma/BarotraumaClient/ClientSource/Particles/Particle.cs index fa77bcd3e..8af7d4e14 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Particles/Particle.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Particles/Particle.cs @@ -103,7 +103,7 @@ namespace Barotrauma.Particles { return debugName; } - public void Init(ParticlePrefab prefab, Vector2 position, Vector2 speed, float rotation, Hull hullGuess = null, bool drawOnTop = false, float collisionIgnoreTimer = 0f, Tuple tracerPoints = null) + public void Init(ParticlePrefab prefab, Vector2 position, Vector2 speed, float rotation, Hull hullGuess = null, bool drawOnTop = false, float collisionIgnoreTimer = 0f, float lifeTimeMultiplier = 1f, Tuple tracerPoints = null) { this.prefab = prefab; #if DEBUG @@ -149,13 +149,13 @@ namespace Barotrauma.Particles if (prefab.LifeTimeMin <= 0.0f) { - totalLifeTime = prefab.LifeTime; - lifeTime = prefab.LifeTime; + totalLifeTime = prefab.LifeTime * lifeTimeMultiplier; + lifeTime = prefab.LifeTime * lifeTimeMultiplier; } else { - totalLifeTime = Rand.Range(prefab.LifeTimeMin, prefab.LifeTime); - lifeTime = totalLifeTime; + totalLifeTime = Rand.Range(prefab.LifeTimeMin, prefab.LifeTime) * lifeTimeMultiplier; + lifeTime = totalLifeTime * lifeTimeMultiplier; } startDelay = Rand.Range(prefab.StartDelayMin, prefab.StartDelayMax); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Particles/ParticleEmitter.cs b/Barotrauma/BarotraumaClient/ClientSource/Particles/ParticleEmitter.cs index f0457e030..cae3906e3 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Particles/ParticleEmitter.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Particles/ParticleEmitter.cs @@ -85,6 +85,9 @@ namespace Barotrauma.Particles [Editable, Serialize("1,1,1,1", IsPropertySaveable.Yes)] public Color ColorMultiplier { get; set; } + [Editable, Serialize(1f, IsPropertySaveable.Yes)] + public float LifeTimeMultiplier { get; set; } + [Editable, Serialize(false, IsPropertySaveable.Yes)] public bool DrawOnTop { get; set; } @@ -197,7 +200,7 @@ namespace Barotrauma.Particles position += dir * Rand.Range(Prefab.Properties.DistanceMin, Prefab.Properties.DistanceMax); } - var particle = GameMain.ParticleManager.CreateParticle(particlePrefab, position, velocity, particleRotation, hullGuess, Prefab.DrawOnTop, tracerPoints: tracerPoints); + var particle = GameMain.ParticleManager.CreateParticle(particlePrefab, position, velocity, particleRotation, hullGuess, Prefab.DrawOnTop, lifeTimeMultiplier: Prefab.Properties.LifeTimeMultiplier, tracerPoints: tracerPoints); if (particle != null) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Particles/ParticleManager.cs b/Barotrauma/BarotraumaClient/ClientSource/Particles/ParticleManager.cs index 558faee69..982106567 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Particles/ParticleManager.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Particles/ParticleManager.cs @@ -76,7 +76,7 @@ namespace Barotrauma.Particles return CreateParticle(prefab, position, velocity, rotation, hullGuess, collisionIgnoreTimer: collisionIgnoreTimer, tracerPoints:tracerPoints); } - 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) + public Particle CreateParticle(ParticlePrefab prefab, Vector2 position, Vector2 velocity, float rotation = 0.0f, Hull hullGuess = null, bool drawOnTop = false, float collisionIgnoreTimer = 0f, float lifeTimeMultiplier = 1f, Tuple tracerPoints = null) { if (prefab == null || prefab.Sprites.Count == 0) { return null; } @@ -115,7 +115,7 @@ namespace Barotrauma.Particles if (particles[particleCount] == null) { particles[particleCount] = new Particle(); } - particles[particleCount].Init(prefab, position, velocity, rotation, hullGuess, drawOnTop, collisionIgnoreTimer, tracerPoints: tracerPoints); + particles[particleCount].Init(prefab, position, velocity, rotation, hullGuess, drawOnTop, collisionIgnoreTimer, lifeTimeMultiplier, tracerPoints: tracerPoints); particleCount++; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs index 9df67c87e..f8c3d29c9 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs @@ -77,9 +77,20 @@ namespace Barotrauma if (remoteContentDoc?.Root != null) { remoteContentContainer.ClearChildren(); - foreach (var subElement in remoteContentDoc.Root.Elements()) + try { - GUIComponent.FromXML(subElement.FromPackage(null), remoteContentContainer.RectTransform); + foreach (var subElement in remoteContentDoc.Root.Elements()) + { + GUIComponent.FromXML(subElement.FromPackage(null), remoteContentContainer.RectTransform); + } + } + catch (Exception e) + { +#if DEBUG + DebugConsole.ThrowError("Reading received remote main menu content failed.", e); +#endif + GameAnalyticsManager.AddErrorEventOnce("MainMenuScreen.RemoteContentParse:Exception", GameAnalyticsManager.ErrorSeverity.Error, + "Reading received remote main menu content failed. " + e.Message); } } }; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/ModDownloadScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/ModDownloadScreen.cs index 0656aacea..537124f37 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/ModDownloadScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/ModDownloadScreen.cs @@ -20,10 +20,11 @@ namespace Barotrauma private ServerContentPackage? currentDownload; private readonly List downloadedPackages = new List(); + public IEnumerable DownloadedPackages => downloadedPackages; private bool confirmDownload; - private void Reset() + public void Reset() { pendingDownloads.Clear(); downloadedPackages.Clear(); @@ -255,12 +256,6 @@ namespace Barotrauma } } - public override void Deselect() - { - Reset(); - base.Deselect(); - } - public override void Update(double deltaTime) { base.Update(deltaTime); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs index 2e9797dba..ff6f3240e 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs @@ -1400,7 +1400,7 @@ namespace Barotrauma public void CreatePlayerFrame(GUIComponent parent, bool createPendingText = true, bool alwaysAllowEditing = false) { UpdatePlayerFrame( - Character.Controlled?.Info ?? playerInfoContainer.Children?.First().UserData as CharacterInfo, + Character.Controlled?.Info ?? playerInfoContainer.Children?.First().UserData as CharacterInfo ?? GameMain.Client.CharacterInfo, allowEditing: alwaysAllowEditing || campaignCharacterInfo == null, parent: parent, createPendingText: createPendingText); @@ -3131,10 +3131,11 @@ namespace Barotrauma retVal[i] = new GUIImage[outfitPreview.Sprites.Count]; for (int j = 0; j < outfitPreview.Sprites.Count; j++) { - Pair sprite = outfitPreview.Sprites[j]; + Sprite sprite = outfitPreview.Sprites[j].sprite; + Vector2 drawOffset = outfitPreview.Sprites[j].drawOffset; 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) - { RelativeOffset = sprite.Second / outfitPreview.Dimensions }, sprite.First, scaleToFit: true) + { RelativeOffset = drawOffset / outfitPreview.Dimensions }, sprite, scaleToFit: true) { PressedColor = Color.White, CanBeFocused = false diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs index 73e46e383..66ea4de08 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs @@ -1190,18 +1190,23 @@ namespace Barotrauma frame.RectTransform.MaxSize = new Point(int.MaxValue, frame.Rect.Width); LocalizedString name = legacy ? TextManager.GetWithVariable("legacyitemformat", "[name]", ep.Name) : ep.Name; - frame.ToolTip = ep.Description.IsNullOrEmpty() ? name : name + '\n' + ep.Description; + frame.ToolTip = $"{frame.ToolTip}\n‖color:{XMLExtensions.ToStringHex(GUIStyle.TextColorBright)}‖{name}‖color:end‖"; + if (!ep.Description.IsNullOrEmpty()) + { + frame.ToolTip += '\n' + ep.Description; + } if (ep.ContentPackage != GameMain.VanillaContent && ep.ContentPackage != null) { frame.Color = Color.Magenta; - frame.ToolTip = RichString.Rich($"{frame.ToolTip}\n‖color:{XMLExtensions.ToStringHex(Color.MediumPurple)}‖{ep.ContentPackage?.Name}‖color:end‖"); + frame.ToolTip = $"{frame.ToolTip}\n‖color:{XMLExtensions.ToStringHex(Color.MediumPurple)}‖{ep.ContentPackage?.Name}‖color:end‖"; } if (ep.HideInMenus) { frame.Color = Color.Red; name = "[HIDDEN] " + name; } + frame.ToolTip = RichString.Rich(frame.ToolTip); GUILayoutGroup paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 0.8f), frame.RectTransform, Anchor.Center), childAnchor: Anchor.TopCenter) { @@ -1976,7 +1981,7 @@ namespace Barotrauma return true; }; - nameBox.Text = subNameLabel?.Text?.SanitizedValue ?? ""; + nameBox.Text = MainSub?.Info.Name ?? ""; submarineNameCharacterCount.Text = nameBox.Text.Length + " / " + submarineNameLimit; @@ -2750,6 +2755,8 @@ namespace Barotrauma } } + nameBox.Text = nameBox.Text.Trim(); + bool hideInMenus = nameBox.Parent.GetChildByUserData("hideinmenus") is GUITickBox hideInMenusTickBox && hideInMenusTickBox.Selected; string saveFolder = Path.Combine(ContentPackage.LocalModsDir, nameBox.Text); string filePath = Path.Combine(saveFolder, $"{nameBox.Text}.xml").CleanUpPathCrossPlatform(); @@ -2884,8 +2891,30 @@ namespace Barotrauma { if (deleteButtonHolder.FindChild("delete") is GUIButton deleteBtn) { - deleteBtn.Enabled = userData is SubmarineInfo subInfo - && GetContentPackageIntrinsicallyTiedToSub(subInfo) != null; + deleteBtn.ToolTip = string.Empty; + if (!(userData is SubmarineInfo subInfo)) + { + deleteBtn.Enabled = false; + return true; + } + + var package = GetContentPackageIntrinsicallyTiedToSub(subInfo); + if (package != null) + { + deleteBtn.Enabled = true; + } + else + { + deleteBtn.Enabled = false; + if (ContentPackageManager.VanillaCorePackage?.Files.Any(f => f.Path == subInfo.FilePath) ?? false) + { + deleteBtn.ToolTip = TextManager.Get("cantdeletevanillasub"); + } + else if (ContentPackageManager.AllPackages.FirstOrDefault(p => p.Files.Any(f => f.Path == subInfo.FilePath)) is ContentPackage subPackage) + { + deleteBtn.ToolTip = TextManager.GetWithVariable("cantdeletemodsub", "[modname]", subPackage.Name); + } + } } return true; } @@ -2925,6 +2954,21 @@ namespace Barotrauma ToolTip = sub.FilePath }; + if (!(ContentPackageManager.VanillaCorePackage?.Files.Any(f => f.Path == sub.FilePath) ?? false)) + { + if (GetContentPackageIntrinsicallyTiedToSub(sub) == null && + ContentPackageManager.AllPackages.FirstOrDefault(p => p.Files.Any(f => f.Path == sub.FilePath)) is ContentPackage subPackage) + { + //workshop mod + textBlock.OverrideTextColor(Color.MediumPurple); + } + else + { + //local mod + textBlock.OverrideTextColor(GUIStyle.TextColorBright); + } + } + if (sub.HasTag(SubmarineTag.Shuttle)) { var shuttleText = new GUITextBlock(new RectTransform(new Vector2(0.2f, 1.0f), textBlock.RectTransform, Anchor.CenterRight), @@ -3037,6 +3081,24 @@ namespace Barotrauma if (!(child.UserData is SubmarineInfo sub)) { continue; } child.Visible = string.IsNullOrEmpty(filter) || sub.Name.ToLower().Contains(filter.ToLower()); } + + //go through the elements backwards, and disable the labels for sub categories if there's no subs visible in them + bool subVisibleInCategory = false; + foreach (GUIComponent child in subList.Content.Children.Reverse()) + { + if (!(child.UserData is SubmarineInfo sub)) + { + if (child.Enabled) + { + child.Visible = subVisibleInCategory; + } + subVisibleInCategory = false; + } + else + { + subVisibleInCategory |= child.Visible; + } + } } /// @@ -4828,7 +4890,7 @@ namespace Barotrauma } } - if (GameSettings.CurrentConfig.KeyMap.Bindings[InputType.ToggleInventory].IsHit() && mode == Mode.Default) + if (PlayerInput.KeyHit(Keys.Q) && mode == Mode.Default) { toggleEntityMenuButton.OnClicked?.Invoke(toggleEntityMenuButton, toggleEntityMenuButton.UserData); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundPlayer.cs b/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundPlayer.cs index c7d65c9ca..821725532 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundPlayer.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundPlayer.cs @@ -233,16 +233,18 @@ namespace Barotrauma float dist = diff.Length(); float distFallOff = dist / FlowSoundRange; - if (distFallOff >= 0.99f) continue; + if (distFallOff >= 0.99f) { continue; } + + float gain = MathHelper.Clamp(gapFlow / 100.0f, 0.0f, 1.0f); //flow at the left side if (diff.X < 0) { - targetFlowLeft[flowSoundIndex] += 1.0f - distFallOff; + targetFlowLeft[flowSoundIndex] += gain - distFallOff; } else { - targetFlowRight[flowSoundIndex] += 1.0f - distFallOff; + targetFlowRight[flowSoundIndex] += gain - distFallOff; } } } @@ -287,7 +289,7 @@ namespace Barotrauma flowSoundChannels[i] = FlowSounds[i].Sound.Play(1.0f, FlowSoundRange, soundPos); flowSoundChannels[i].Looping = true; } - flowSoundChannels[i].Gain = Math.Min(Math.Max(flowVolumeRight[i], flowVolumeLeft[i]), 1.0f); + flowSoundChannels[i].Gain = Math.Max(flowVolumeRight[i], flowVolumeLeft[i]); flowSoundChannels[i].Position = new Vector3(soundPos, 0.0f); } } @@ -853,7 +855,7 @@ namespace Barotrauma if (SplashSounds.Count == 0) { return; } int splashIndex = MathHelper.Clamp((int)(strength + Rand.Range(-2.0f, 2.0f)), 0, SplashSounds.Count - 1); float range = 800.0f; - var channel = SplashSounds[splashIndex].Sound.Play(1.0f, range, worldPosition, muffle: ShouldMuffleSound(Character.Controlled, worldPosition, range, null)); + SplashSounds[splashIndex].Sound?.Play(1.0f, range, worldPosition, muffle: ShouldMuffleSound(Character.Controlled, worldPosition, range, null)); } public static void PlayDamageSound(string damageType, float damage, PhysicsBody body) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Sprite/Sprite.cs b/Barotrauma/BarotraumaClient/ClientSource/Sprite/Sprite.cs index 6db48e03d..d4e1648a3 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Sprite/Sprite.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Sprite/Sprite.cs @@ -39,9 +39,8 @@ namespace Barotrauma get { return texture != null && !cannotBeLoaded; } } - public Sprite(Sprite other) : this(other.texture, other.sourceRect, other.offset, other.rotation) + public Sprite(Sprite other) : this(other.texture, other.sourceRect, other.offset, other.rotation, other.FilePath.Value) { - FilePath = other.FilePath; Compress = other.Compress; size = other.size; effects = other.effects; @@ -58,6 +57,17 @@ namespace Barotrauma rotation = newRotation; FilePath = ContentPath.FromRaw(path); AddToList(this); + if (!string.IsNullOrEmpty(path)) + { + Identifier fullPath = Path.GetFullPath(path).CleanUpPathCrossPlatform(correctFilenameCase: false).ToIdentifier(); + lock (list) + { + if (!textureRefCounts.TryAdd(fullPath, new TextureRefCounter { RefCount = 1, Texture = texture })) + { + textureRefCounts[fullPath].RefCount++; + } + } + } } partial void LoadTexture(ref Vector4 sourceVector, ref bool shouldReturn) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Steam/Workshop.cs b/Barotrauma/BarotraumaClient/ClientSource/Steam/Workshop.cs index 37a04c2a0..fd9431cc1 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Steam/Workshop.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Steam/Workshop.cs @@ -16,6 +16,8 @@ namespace Barotrauma.Steam { public static partial class Workshop { + public const int MaxThumbnailSize = 1024 * 1024; + public static readonly ImmutableArray Tags = new [] { "submarine", @@ -177,7 +179,7 @@ namespace Barotrauma.Steam DeletePublishStagingCopy(); Directory.CreateDirectory(PublishStagingDir); - await CopyDirectory(contentPackage.Dir, contentPackage.Name, Path.GetDirectoryName(contentPackage.Path)!, PublishStagingDir); + await CopyDirectory(contentPackage.Dir, contentPackage.Name, Path.GetDirectoryName(contentPackage.Path)!, PublishStagingDir, ShouldCorrectPaths.No); //Load filelist.xml and write the hash into it so anyone downloading this mod knows what it should be ModProject modProject = new ModProject(contentPackage) @@ -218,7 +220,7 @@ namespace Barotrauma.Steam throw new Exception($"{newPath} already exists"); } - await CopyDirectory(contentPackage.Dir, contentPackage.Name, Path.GetDirectoryName(contentPackage.Path)!, newPath); + await CopyDirectory(contentPackage.Dir, contentPackage.Name, Path.GetDirectoryName(contentPackage.Path)!, newPath, ShouldCorrectPaths.Yes); ModProject modProject = new ModProject(contentPackage); modProject.DiscardHashAndInstallTime(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Steam/WorkshopMenu/Mutable/PublishTab.cs b/Barotrauma/BarotraumaClient/ClientSource/Steam/WorkshopMenu/Mutable/PublishTab.cs index ec55b9e72..fdc29524d 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Steam/WorkshopMenu/Mutable/PublishTab.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Steam/WorkshopMenu/Mutable/PublishTab.cs @@ -197,6 +197,11 @@ namespace Barotrauma.Steam FileSelection.OnFileSelected = (fn) => { + if (new FileInfo(fn).Length > SteamManager.Workshop.MaxThumbnailSize) + { + new GUIMessageBox(TextManager.Get("Error"), TextManager.Get("WorkshopItemPreviewImageTooLarge")); + return; + } thumbnailPath = fn; CreateLocalThumbnail(thumbnailPath, thumbnailContainer); }; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Steam/WorkshopMenu/UiUtil.cs b/Barotrauma/BarotraumaClient/ClientSource/Steam/WorkshopMenu/UiUtil.cs index 692bc61b9..e803a86e2 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Steam/WorkshopMenu/UiUtil.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Steam/WorkshopMenu/UiUtil.cs @@ -119,6 +119,11 @@ namespace Barotrauma.Steam { CanBeFocused = false }; + new GUICustomComponent(new RectTransform(Vector2.Zero, searchHolder.RectTransform), onUpdate: + (f, component) => + { + searchTitle.RectTransform.NonScaledSize = searchBox.Frame.RectTransform.NonScaledSize; + }); searchBox.OnSelected += (sender, userdata) => { searchTitle.Visible = false; }; searchBox.OnDeselected += (sender, userdata) => { searchTitle.Visible = searchBox.Text.IsNullOrWhiteSpace(); }; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Text/LocalizedString/LimitLString.cs b/Barotrauma/BarotraumaClient/ClientSource/Text/LocalizedString/LimitLString.cs index 8e652c5cd..804c688ca 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Text/LocalizedString/LimitLString.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Text/LocalizedString/LimitLString.cs @@ -8,7 +8,6 @@ namespace Barotrauma private readonly int maxWidth; private ScalableFont? cachedFont = null; - private uint cachedFontSize = 0; public LimitLString(LocalizedString text, GUIFont font, int maxWidth) { @@ -27,7 +26,6 @@ namespace Barotrauma { cachedValue = ToolBox.LimitString(nestedStr.Value, font.Value, maxWidth); cachedFont = font.Value; - cachedFontSize = font.Size; UpdateLanguage(); } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Utils/ToolBox.cs b/Barotrauma/BarotraumaClient/ClientSource/Utils/ToolBox.cs index 397c71876..c11566a30 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Utils/ToolBox.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Utils/ToolBox.cs @@ -404,7 +404,7 @@ namespace Barotrauma public static string LimitString(string str, ScalableFont font, int maxWidth) { - if (maxWidth <= 0 || string.IsNullOrWhiteSpace(str)) return ""; + if (maxWidth <= 0 || string.IsNullOrWhiteSpace(str)) { return ""; } float currWidth = font.MeasureString("...").X; for (int i = 0; i < str.Length; i++) diff --git a/Barotrauma/BarotraumaClient/LinuxClient.csproj b/Barotrauma/BarotraumaClient/LinuxClient.csproj index feb824001..a2ee9a9e0 100644 --- a/Barotrauma/BarotraumaClient/LinuxClient.csproj +++ b/Barotrauma/BarotraumaClient/LinuxClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.17.15.0 + 0.18.0.0 Copyright © FakeFish 2018-2022 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/MacClient.csproj b/Barotrauma/BarotraumaClient/MacClient.csproj index c0b1d5d63..9360c73bf 100644 --- a/Barotrauma/BarotraumaClient/MacClient.csproj +++ b/Barotrauma/BarotraumaClient/MacClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.17.15.0 + 0.18.0.0 Copyright © FakeFish 2018-2022 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/WindowsClient.csproj b/Barotrauma/BarotraumaClient/WindowsClient.csproj index 870cafa2d..b9fecd89c 100644 --- a/Barotrauma/BarotraumaClient/WindowsClient.csproj +++ b/Barotrauma/BarotraumaClient/WindowsClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.17.15.0 + 0.18.0.0 Copyright © FakeFish 2018-2022 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaServer/LinuxServer.csproj b/Barotrauma/BarotraumaServer/LinuxServer.csproj index f494058e0..0f61a36a8 100644 --- a/Barotrauma/BarotraumaServer/LinuxServer.csproj +++ b/Barotrauma/BarotraumaServer/LinuxServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.17.15.0 + 0.18.0.0 Copyright © FakeFish 2018-2022 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/MacServer.csproj b/Barotrauma/BarotraumaServer/MacServer.csproj index e3385b582..75305843b 100644 --- a/Barotrauma/BarotraumaServer/MacServer.csproj +++ b/Barotrauma/BarotraumaServer/MacServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.17.15.0 + 0.18.0.0 Copyright © FakeFish 2018-2022 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterInfo.cs b/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterInfo.cs index a63d4e033..f2fde6748 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterInfo.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterInfo.cs @@ -37,7 +37,7 @@ namespace Barotrauma partial void OnPermanentStatChanged(StatTypes statType) { if (Character == null || Character.Removed) { return; } - GameMain.NetworkMember.CreateEntityEvent(Character, new Character.UpdatePermanentStatsEventData()); + GameMain.NetworkMember.CreateEntityEvent(Character, new Character.UpdatePermanentStatsEventData(statType)); } public void ServerWrite(IWriteMessage msg) diff --git a/Barotrauma/BarotraumaServer/ServerSource/GameSession/CrewManager.cs b/Barotrauma/BarotraumaServer/ServerSource/GameSession/CrewManager.cs index 541435d72..2c7358b04 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/GameSession/CrewManager.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/GameSession/CrewManager.cs @@ -25,9 +25,9 @@ namespace Barotrauma /// /// Saves bots in multiplayer /// - public void SaveMultiplayer(XElement root) + public XElement SaveMultiplayer(XElement parentElement) { - XElement saveElement = new XElement("bots", new XAttribute("hasbots", HasBots)); + var element = new XElement("bots", new XAttribute("hasbots", HasBots)); foreach (CharacterInfo info in characterInfos) { if (Level.Loaded != null) @@ -35,13 +35,13 @@ namespace Barotrauma if (!info.IsNewHire && (info.Character == null || info.Character.IsDead)) { continue; } } - XElement characterElement = info.Save(saveElement); + XElement characterElement = info.Save(element); if (info.InventoryData != null) { characterElement.Add(info.InventoryData); } if (info.HealthData != null) { characterElement.Add(info.HealthData); } if (info.OrderData != null) { characterElement.Add(info.OrderData); } } - SaveActiveOrders(saveElement); - root.Add(saveElement); + parentElement?.Add(element); + return element; } public void ServerWriteActiveOrders(IWriteMessage msg) diff --git a/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/CampaignMode.cs b/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/CampaignMode.cs index a2bfc7450..9360c71d2 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/CampaignMode.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/CampaignMode.cs @@ -1,4 +1,5 @@ -using Barotrauma.Networking; +using Barotrauma.Extensions; +using Barotrauma.Networking; namespace Barotrauma { @@ -10,6 +11,31 @@ namespace Barotrauma protected set; } + private static bool IsOwner(Client client) => client != null && client.Connection == GameMain.Server.OwnerConnection; + + /// + /// There is a client-side implementation of the method in + /// + public bool AllowedToManageCampaign(Client client, ClientPermissions permissions) + { + //allow managing the campaign if the client has permissions, is the owner, or the only client in the server, + //or if no-one has management permissions + return + client.HasPermission(permissions) || + client.HasPermission(ClientPermissions.ManageCampaign) || + GameMain.Server.ConnectedClients.Count == 1 || + IsOwner(client) || + GameMain.Server.ConnectedClients.None(c => c.InGame && (IsOwner(c) || c.HasPermission(permissions))); + } + + public bool AllowedToManageWallets(Client client) + { + return + client.HasPermission(ClientPermissions.ManageCampaign) || + client.HasPermission(ClientPermissions.ManageMoney) || + IsOwner(client); + } + public override void ShowStartMessage() { foreach (Mission mission in Missions) diff --git a/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs b/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs index 965530ee9..858cb5b29 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs @@ -163,29 +163,6 @@ namespace Barotrauma private static bool IsOwner(Client client) => client != null && client.Connection == GameMain.Server.OwnerConnection; - /// - /// There is a client-side implementation of the method in - /// - public bool AllowedToManageCampaign(Client client, ClientPermissions permissions) - { - //allow managing the campaign if the client has permissions, is the owner, or the only client in the server, - //or if no-one has management permissions - return - client.HasPermission(permissions) || - client.HasPermission(ClientPermissions.ManageCampaign) || - GameMain.Server.ConnectedClients.Count == 1 || - IsOwner(client) || - GameMain.Server.ConnectedClients.None(c => c.InGame && (IsOwner(c) || c.HasPermission(permissions))); - } - - public bool AllowedToManageWallets(Client client) - { - return - client.HasPermission(ClientPermissions.ManageCampaign) || - client.HasPermission(ClientPermissions.ManageMoney) || - IsOwner(client); - } - public void SaveExperiencePoints(Client client) { ClearSavedExperiencePoints(client); @@ -200,14 +177,6 @@ namespace Barotrauma savedExperiencePoints.RemoveAll(s => s.SteamID != 0 && client.SteamID == s.SteamID || client.EndpointMatches(s.EndPoint)); } - public void LoadPets() - { - if (petsElement != null) - { - PetBehavior.LoadPets(petsElement); - } - } - public void SavePlayers() { //refresh the character data of clients who are still in the server @@ -261,8 +230,7 @@ namespace Barotrauma characterData.ForEach(cd => cd.HasSpawned = false); - petsElement = new XElement("pets"); - PetBehavior.SavePets(petsElement); + SavePets(); //remove all items that are in someone's inventory foreach (Character c in Character.CharacterList) @@ -285,6 +253,8 @@ namespace Barotrauma c.Inventory.DeleteAllItems(); } + + SaveActiveOrders(); } public void MoveDiscardedCharacterBalancesToBank() @@ -348,44 +318,10 @@ namespace Barotrauma if (success) { SavePlayers(); - yield return CoroutineStatus.Running; - - if (leavingSub != Submarine.MainSub && !leavingSub.DockedTo.Contains(Submarine.MainSub)) - { - Submarine.MainSub = leavingSub; - GameMain.GameSession.Submarine = leavingSub; - GameMain.GameSession.SubmarineInfo = leavingSub.Info; - leavingSub.Info.FilePath = System.IO.Path.Combine(SaveUtil.TempPath, leavingSub.Info.Name + ".sub"); - var subsToLeaveBehind = GetSubsToLeaveBehind(leavingSub); - GameMain.GameSession.OwnedSubmarines.Add(leavingSub.Info); - foreach (Submarine sub in subsToLeaveBehind) - { - GameMain.GameSession.OwnedSubmarines.RemoveAll(s => s != leavingSub.Info && s.Name == sub.Info.Name); - MapEntity.mapEntityList.RemoveAll(e => e.Submarine == sub && e is LinkedSubmarine); - LinkedSubmarine.CreateDummy(leavingSub, sub); - } - } + LeaveUnconnectedSubs(leavingSub); NextLevel = newLevel; - GameMain.GameSession.SubmarineInfo = new SubmarineInfo(GameMain.GameSession.Submarine); - - if (PendingSubmarineSwitch != null) - { - SubmarineInfo previousSub = GameMain.GameSession.SubmarineInfo; - GameMain.GameSession.SubmarineInfo = PendingSubmarineSwitch; - - for (int i = 0; i < GameMain.GameSession.OwnedSubmarines.Count; i++) - { - if (GameMain.GameSession.OwnedSubmarines[i].Name == previousSub.Name) - { - GameMain.GameSession.OwnedSubmarines[i] = previousSub; - break; - } - } - } - SaveUtil.SaveGame(GameMain.GameSession.SavePath); - PendingSubmarineSwitch = null; } else { @@ -977,6 +913,8 @@ namespace Barotrauma { NetWalletTransfer transfer = INetSerializableStruct.Read(msg); + if (GameMain.Server is null) { return; } + switch (transfer.Sender) { case Some { Value: var id }: @@ -992,7 +930,8 @@ namespace Barotrauma { if (transfer.Receiver is Some { Value: var receiverId } && receiverId == sender.CharacterID) { - GameMain.Server?.Voting.StartTransferVote(sender, null, transfer.Amount, sender); + if (transfer.Amount > GameMain.Server.ServerSettings.MaximumTransferRequest) { return; } + GameMain.Server.Voting.StartTransferVote(sender, null, transfer.Amount, sender); GameServer.Log($"{sender.Name} started a vote to transfer {transfer.Amount} mk from the bank.", ServerLog.MessageType.Money); } return; @@ -1301,7 +1240,11 @@ namespace Barotrauma } // save bots - CrewManager.SaveMultiplayer(modeElement); + var crewManagerElement = CrewManager.SaveMultiplayer(modeElement); + if (ActiveOrdersElement != null) + { + crewManagerElement.Add(ActiveOrdersElement); + } XElement savedExperiencePointsElement = new XElement("SavedExperiencePoints"); foreach (var savedExperiencePoint in savedExperiencePoints) diff --git a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/DockingPort.cs b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/DockingPort.cs index aa7abb66a..607149e26 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/DockingPort.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/DockingPort.cs @@ -1,19 +1,27 @@ using Barotrauma.Networking; -using System; namespace Barotrauma.Items.Components { - partial class DockingPort : ItemComponent, IDrawableComponent, IServerSerializable + partial class DockingPort : ItemComponent, IDrawableComponent, IServerSerializable, IClientSerializable { public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null) { msg.Write(docked); - if (docked) { msg.Write(DockingTarget.item.ID); msg.Write(IsLocked); } } + public void ServerEventRead(IReadMessage msg, Client c) + { + var allowOutpostAutoDocking = (AllowOutpostAutoDocking)msg.ReadByte(); + if (outpostAutoDockingPromptShown && + (GameMain.GameSession?.Campaign?.AllowedToManageCampaign(c, ClientPermissions.ManageMap) ?? false)) + { + this.allowOutpostAutoDocking = allowOutpostAutoDocking; + } + } + } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/ItemLabel.cs b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/ItemLabel.cs index 063f23d7a..ad7dd525f 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/ItemLabel.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/ItemLabel.cs @@ -39,7 +39,7 @@ namespace Barotrauma.Items.Components set; } - public override void Move(Vector2 amount) + public override void Move(Vector2 amount, bool ignoreContacts = false) { //do nothing } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Signal/ConnectionPanel.cs b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Signal/ConnectionPanel.cs index 2e27864a2..a9cffe9ef 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Signal/ConnectionPanel.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Signal/ConnectionPanel.cs @@ -15,7 +15,8 @@ namespace Barotrauma.Items.Components for (int i = 0; i < Connections.Count; i++) { wires[i] = new List(); - for (int j = 0; j < Connections[i].MaxWires; j++) + uint wireCount = msg.ReadVariableUInt32(); + for (int j = 0; j < wireCount; j++) { ushort wireId = msg.ReadUInt16(); @@ -91,12 +92,8 @@ namespace Barotrauma.Items.Components //go through existing wire links for (int i = 0; i < Connections.Count; i++) { - int j = -1; - foreach (Wire existingWire in Connections[i].Wires) + foreach (Wire existingWire in Connections[i].Wires.ToArray()) { - j++; - if (existingWire == null) { continue; } - //existing wire not in the list of new wires -> disconnect it if (!wires[i].Contains(existingWire)) { @@ -163,7 +160,7 @@ namespace Barotrauma.Items.Components }*/ } - Connections[i].SetWire(j, null); + Connections[i].DisconnectWire(existingWire); } } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Map/Creatures/BallastFloraBehavior.cs b/Barotrauma/BarotraumaServer/ServerSource/Map/Creatures/BallastFloraBehavior.cs index 9645311ce..877d0b4b1 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Map/Creatures/BallastFloraBehavior.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Map/Creatures/BallastFloraBehavior.cs @@ -42,7 +42,7 @@ namespace Barotrauma.MapCreatures.Behavior foreach (BallastFloraBranch branch in Branches) { //don't notify about minuscule amounts of damage (<= 1.0f) - if (branch.AccumulatedDamage > 1.0f) + if (Math.Abs(branch.AccumulatedDamage) > 1.0f) { CreateNetworkMessage(new BranchDamageEventData(branch)); branch.AccumulatedDamage = 0.0f; diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/Client.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/Client.cs index 5a31dc4cf..ea54be031 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/Client.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/Client.cs @@ -24,7 +24,7 @@ namespace Barotrauma.Networking public UInt16 LastRecvCampaignUpdate = 0; public UInt16 LastRecvCampaignSave = 0; - public Pair LastCampaignSaveSendTime; + public (UInt16 saveId, float time) LastCampaignSaveSendTime; public readonly List ChatMsgQueue = new List(); public UInt16 LastChatMsgQueueID; diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/FileTransfer/FileSender.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/FileTransfer/FileSender.cs index 7d5385f7f..b79b94e64 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/FileTransfer/FileSender.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/FileTransfer/FileSender.cs @@ -391,7 +391,7 @@ namespace Barotrauma.Networking StartTransfer(inc.Sender, FileTransferType.CampaignSave, GameMain.GameSession.SavePath); if (GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign) { - client.LastCampaignSaveSendTime = new Pair(campaign.LastSaveID, (float)Lidgren.Network.NetTime.Now); + client.LastCampaignSaveSendTime = (campaign.LastSaveID, (float)Lidgren.Network.NetTime.Now); } } break; diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs index 28d5adb13..effeb21a5 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs @@ -955,7 +955,9 @@ namespace Barotrauma.Networking } if (Level.Loaded != null) { - errorLines.Add("Level: " + Level.Loaded.Seed + ", " + string.Join(", ", Level.Loaded.EqualityCheckValues.Select(cv => cv.ToString("X")))); + errorLines.Add("Level: " + Level.Loaded.Seed + ", " + + string.Join("; ", Level.Loaded.EqualityCheckValues.Select(cv + => cv.Key + "=" + cv.Value.ToString("X")))); errorLines.Add("Entity count before generating level: " + Level.Loaded.EntityCountBeforeGenerate); errorLines.Add("Entities:"); foreach (Entity e in Level.Loaded.EntitiesBeforeGenerate.OrderBy(e => e.CreationIndex)) @@ -1548,11 +1550,11 @@ namespace Barotrauma.Networking NetIdUtils.IdMoreRecent(campaign.LastSaveID, c.LastRecvCampaignSave)) { //already sent an up-to-date campaign save - if (c.LastCampaignSaveSendTime != null && campaign.LastSaveID == c.LastCampaignSaveSendTime.First) + if (c.LastCampaignSaveSendTime != default && campaign.LastSaveID == c.LastCampaignSaveSendTime.saveId) { //the save was sent less than 5 second ago, don't attempt to resend yet //(the client may have received it but hasn't acked us yet) - if (c.LastCampaignSaveSendTime.Second > NetTime.Now - 5.0f) + if (c.LastCampaignSaveSendTime.time > NetTime.Now - 5.0f) { return; } @@ -1561,7 +1563,7 @@ namespace Barotrauma.Networking if (!FileSender.ActiveTransfers.Any(t => t.Connection == c.Connection && t.FileType == FileTransferType.CampaignSave)) { FileSender.StartTransfer(c.Connection, FileTransferType.CampaignSave, GameMain.GameSession.SavePath); - c.LastCampaignSaveSendTime = new Pair(campaign.LastSaveID, (float)NetTime.Now); + c.LastCampaignSaveSendTime = (campaign.LastSaveID, (float)NetTime.Now); } } } @@ -2193,7 +2195,7 @@ namespace Barotrauma.Networking Level.Loaded?.SpawnNPCs(); Level.Loaded?.SpawnCorpses(); Level.Loaded?.PrepareBeaconStation(); - AutoItemPlacer.PlaceIfNeeded(); + AutoItemPlacer.SpawnItems(); CrewManager crewManager = campaign?.CrewManager; @@ -2388,7 +2390,9 @@ namespace Barotrauma.Networking } campaign?.LoadPets(); - crewManager?.LoadActiveOrders(); + campaign?.LoadActiveOrders(); + + campaign?.CargoManager.InitPurchasedIDCards(); foreach (Submarine sub in Submarine.MainSubs) { @@ -2400,7 +2404,7 @@ namespace Barotrauma.Networking spawnList.Add(new PurchasedItem(kvp.Key, kvp.Value, buyer: null)); } - CargoManager.CreateItems(spawnList, sub); + CargoManager.CreateItems(spawnList, sub, cargoManager: null); } TraitorManager = null; @@ -2531,10 +2535,9 @@ namespace Barotrauma.Networking { msg.Write(mission.Prefab.Identifier); } - msg.Write((byte)GameMain.GameSession.Level.EqualityCheckValues.Count); - foreach (int equalityCheckValue in GameMain.GameSession.Level.EqualityCheckValues) + foreach (Level.LevelGenStage stage in Enum.GetValues(typeof(Level.LevelGenStage)).OfType().OrderBy(s => s)) { - msg.Write(equalityCheckValue); + msg.Write(GameMain.GameSession.Level.EqualityCheckValues[stage]); } foreach (Mission mission in GameMain.GameSession.Missions) { @@ -3178,9 +3181,9 @@ namespace Barotrauma.Networking Client recipient = connectedClients.Find(c => c.Connection == transfer.Connection); if (transfer.FileType == FileTransferType.CampaignSave && (transfer.Status == FileTransferStatus.Sending || transfer.Status == FileTransferStatus.Finished) && - recipient.LastCampaignSaveSendTime != null) + recipient.LastCampaignSaveSendTime != default) { - recipient.LastCampaignSaveSendTime.Second = (float)Lidgren.Network.NetTime.Now; + recipient.LastCampaignSaveSendTime.time = (float)NetTime.Now; } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/KarmaManager.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/KarmaManager.cs index 8fe44eb71..e86a858f5 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/KarmaManager.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/KarmaManager.cs @@ -278,12 +278,12 @@ namespace Barotrauma static bool isValid(Item item) { - return item.Prefab.Identifier == "idcard" || item.GetComponent() != null || item.GetComponent() != null; + return item.GetComponent() != null || item.GetComponent() != null || item.GetComponent() != null; } if (foundItem == null) { return; } - bool isIdCard = ((MapEntity)foundItem).Prefab.Identifier == "idcard"; + bool isIdCard = foundItem.GetComponent() != null; bool isWeapon = foundItem.GetComponent() != null || foundItem.GetComponent() != null; if (isIdCard) diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/RespawnManager.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/RespawnManager.cs index b11700a19..28d1b91c0 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/RespawnManager.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/RespawnManager.cs @@ -441,32 +441,43 @@ namespace Barotrauma.Networking GameServer.Log(string.Format("Respawning {0} ({1}) as {2}", GameServer.ClientLogName(clients[i]), clients[i].Connection?.EndPointString, characterInfos[i].Job.Name), ServerLog.MessageType.Spawning); } - if (divingSuitPrefab != null && oxyPrefab != null && RespawnShuttle != null) + if (RespawnShuttle != null) { Vector2 pos = cargoSp == null ? character.Position : cargoSp.Position; - if (divingSuitPrefab != null && oxyPrefab != null) + if (divingSuitPrefab != null) { var divingSuit = new Item(divingSuitPrefab, pos, respawnSub); Spawner.CreateNetworkEvent(new EntitySpawner.SpawnEntity(divingSuit)); respawnItems.Add(divingSuit); - var oxyTank = new Item(oxyPrefab, pos, respawnSub); - Spawner.CreateNetworkEvent(new EntitySpawner.SpawnEntity(oxyTank)); - divingSuit.Combine(oxyTank, user: null); - respawnItems.Add(oxyTank); + if (oxyPrefab != null && divingSuit.GetComponent() != null) + { + var oxyTank = new Item(oxyPrefab, pos, respawnSub); + Spawner.CreateNetworkEvent(new EntitySpawner.SpawnEntity(oxyTank)); + divingSuit.Combine(oxyTank, user: null); + respawnItems.Add(oxyTank); + } } - if (scooterPrefab != null && batteryPrefab != null) + if (!(GameMain.GameSession.GameMode is CampaignMode)) { - var scooter = new Item(scooterPrefab, pos, respawnSub); - Spawner.CreateNetworkEvent(new EntitySpawner.SpawnEntity(scooter)); - - var battery = new Item(batteryPrefab, pos, respawnSub); - Spawner.CreateNetworkEvent(new EntitySpawner.SpawnEntity(battery)); - - scooter.Combine(battery, user: null); - respawnItems.Add(scooter); - respawnItems.Add(battery); + if (scooterPrefab != null) + { + var scooter = new Item(scooterPrefab, pos, respawnSub); + Spawner.CreateNetworkEvent(new EntitySpawner.SpawnEntity(scooter)); + respawnItems.Add(scooter); + if (batteryPrefab != null) + { + var battery = new Item(batteryPrefab, pos, respawnSub); + Spawner.CreateNetworkEvent(new EntitySpawner.SpawnEntity(battery)); + scooter.Combine(battery, user: null); + respawnItems.Add(battery); + } + } + } + if (respawnContainer != null) + { + AutoItemPlacer.RegenerateLoot(RespawnShuttle, respawnContainer); } } @@ -504,7 +515,7 @@ namespace Barotrauma.Networking //add the ID card tags they should've gotten when spawning in the shuttle foreach (Item item in character.Inventory.AllItems.Distinct()) { - if (item.Prefab.Identifier != "idcard") { continue; } + if (item.GetComponent() == null) { continue; } foreach (string s in shuttleSpawnPoints[i].IdCardTags) { item.AddTag(s); diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/Voting.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/Voting.cs index f8a129f51..12fa0d853 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/Voting.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/Voting.cs @@ -44,12 +44,14 @@ namespace Barotrauma { GameMain.Server?.SwitchSubmarine(); } + else + { + voting.RegisterRejectedVote(this); + } voting.StopSubmarineVote(passed); } } - public static IVote ActiveVote; - public class TransferVote : IVote { public Client VoteStarter { get; } @@ -83,12 +85,22 @@ namespace Barotrauma toWallet.Give(TransferAmount); } } + else + { + voting.RegisterRejectedVote(this); + } voting.StopMoneyTransferVote(passed); } } + public static IVote ActiveVote; + private static readonly Queue pendingVotes = new Queue(); + private readonly TimeSpan rejectedVoteCooldown = new TimeSpan(0, 1, 0); + + private readonly Dictionary rejectedVoteTimes = new Dictionary(); + private void StartSubmarineVote(SubmarineInfo subInfo, VoteType voteType, Client sender) { if (ActiveVote == null) @@ -136,6 +148,10 @@ namespace Barotrauma public void StartTransferVote(Client starter, Client from, int transferAmount, Client to) { + if (ShouldRejectVote(starter, VoteType.TransferMoney)) + { + return; + } if (ActiveVote == null) { starter.SetVote(VoteType.TransferMoney, 2); @@ -156,6 +172,31 @@ namespace Barotrauma } } + private bool ShouldRejectVote(Client sender, VoteType voteType) + { + if (rejectedVoteTimes.ContainsKey(sender)) + { + TimeSpan remainingCooldown = (rejectedVoteTimes[sender].time + rejectedVoteCooldown) - DateTime.Now; + if (rejectedVoteTimes[sender].voteType == voteType && + remainingCooldown.TotalSeconds > 0) + { + GameMain.Server.SendDirectChatMessage( + TextManager.FormatServerMessage("voterejectedpleasewait", ("[time]", ((int)remainingCooldown.TotalSeconds).ToString())), + sender, ChatMessageType.ServerMessageBox); + return true; + } + } + return false; + } + + protected void RegisterRejectedVote(IVote vote) + { + if (vote.VoteStarter != null) + { + rejectedVoteTimes[vote.VoteStarter] = (vote.VoteType, DateTime.Now); + } + } + public void Update(float deltaTime) { if (ActiveVote == null) { return; } @@ -227,7 +268,6 @@ namespace Barotrauma GameServer.Log(GameServer.ClientLogName(sender) + (ready ? " is ready to start the game." : " is not ready to start the game."), ServerLog.MessageType.ServerMessage); } break; - case VoteType.PurchaseAndSwitchSub: case VoteType.PurchaseSub: case VoteType.SwitchSub: @@ -240,18 +280,25 @@ namespace Barotrauma int amount = inc.ReadInt32(); int fromClientId = inc.ReadByte(); int toClientId = inc.ReadByte(); - pendingVotes.Enqueue(new TransferVote(sender, - GameMain.Server.ConnectedClients.Find(c => c.ID == fromClientId), - amount, - GameMain.Server.ConnectedClients.Find(c => c.ID == toClientId))); + if (!ShouldRejectVote(sender, voteType)) + { + pendingVotes.Enqueue(new TransferVote(sender, + GameMain.Server.ConnectedClients.Find(c => c.ID == fromClientId), + amount, + GameMain.Server.ConnectedClients.Find(c => c.ID == toClientId))); + } } else { string subName = inc.ReadString(); SubmarineInfo subInfo = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == subName); - if (GameMain.GameSession?.Campaign is MultiPlayerCampaign campaign && (campaign.CanPurchaseSub(subInfo, sender) || GameMain.GameSession.IsSubmarineOwned(subInfo))) + if (!ShouldRejectVote(sender, voteType)) { - StartSubmarineVote(subInfo, voteType, sender); + if (GameMain.GameSession?.Campaign is MultiPlayerCampaign campaign && + (campaign.CanPurchaseSub(subInfo, sender) || GameMain.GameSession.IsSubmarineOwned(subInfo))) + { + StartSubmarineVote(subInfo, voteType, sender); + } } } } diff --git a/Barotrauma/BarotraumaServer/WindowsServer.csproj b/Barotrauma/BarotraumaServer/WindowsServer.csproj index 54c93dda6..c5f7034cc 100644 --- a/Barotrauma/BarotraumaServer/WindowsServer.csproj +++ b/Barotrauma/BarotraumaServer/WindowsServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.17.15.0 + 0.18.0.0 Copyright © FakeFish 2018-2022 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AIController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AIController.cs index 9c942ee08..cc9a9100e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AIController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AIController.cs @@ -31,6 +31,17 @@ namespace Barotrauma if (_previousAiTarget != null) { _lastAiTarget = _previousAiTarget; + if (_selectedAiTarget != null) + { + if (_selectedAiTarget.Entity is Item i && _previousAiTarget.Entity is Character c) + { + if (i.IsOwnedBy(c)) { return; } + } + else if (_previousAiTarget.Entity is Item it && _selectedAiTarget.Entity is Character ch) + { + if (it.IsOwnedBy(ch)) { return; } + } + } } OnTargetChanged(_previousAiTarget, _selectedAiTarget); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs index 0edb7fa1d..8bc57ffa0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs @@ -1055,6 +1055,9 @@ namespace Barotrauma private Vector2 attackWorldPos; private Vector2 attackSimPos; + private float reachTimer; + // How long the monster tries to reach out for the target when it's close to it before ignoring it. + private const float reachTimeOut = 10; private void UpdateAttack(float deltaTime) { @@ -1427,6 +1430,22 @@ namespace Barotrauma // Check that we can reach the target distance = toTarget.Length(); canAttack = distance < AttackLimb.attack.Range; + if (canAttack) + { + reachTimer = 0; + } + else if (selectedTargetingParams.AttackPattern == AttackPattern.Straight && distance < AttackLimb.attack.Range * 5) + { + reachTimer += deltaTime; + if (reachTimer > reachTimeOut) + { + reachTimer = 0; + IgnoreTarget(SelectedAiTarget); + State = AIState.Idle; + ResetAITarget(); + return; + } + } // Crouch if the target is down (only humanoids), so that we can reach it. if (Character.AnimController is HumanoidAnimController humanoidAnimController && distance < AttackLimb.attack.Range * 2) @@ -1958,9 +1977,8 @@ namespace Barotrauma } if (!isFriendly && attackResult.Damage > 0.0f) { - ignoredTargets.Remove(attacker.AiTarget); bool canAttack = attacker.Submarine == Character.Submarine && canAttackCharacters || attacker.Submarine != null && canAttackWalls; - if (AIParams.AttackWhenProvoked && canAttack) + if (AIParams.AttackWhenProvoked && canAttack && !ignoredTargets.Contains(attacker.AiTarget)) { if (attacker.IsHusk) { @@ -3476,6 +3494,7 @@ namespace Barotrauma { observeTimer = targetParams.Timer * Rand.Range(0.75f, 1.25f); } + reachTimer = 0; } protected override void OnStateChanged(AIState from, AIState to) @@ -3496,6 +3515,7 @@ namespace Barotrauma SetStateResetTimer(); } blockCheckTimer = 0; + reachTimer = 0; } private void SetStateResetTimer() => stateResetTimer = stateResetCooldown * Rand.Range(0.75f, 1.25f); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs index 651c3bbf9..a63a5e0ad 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs @@ -59,7 +59,11 @@ namespace Barotrauma private readonly float enemyCheckInterval = 0.2f; private readonly float enemySpotDistanceOutside = 800; private readonly float enemySpotDistanceInside = 1000; - private float enemycheckTimer; + private float enemyCheckTimer; + + private readonly float reportProblemsInterval = 1.0f; + private float reportProblemsTimer; + /// /// How far other characters can hear reports done by this character (e.g. reports for fires, intruders). Defaults to infinity. @@ -166,6 +170,7 @@ namespace Barotrauma objectiveManager = new AIObjectiveManager(c); reactTimer = GetReactionTime(); SortTimer = Rand.Range(0f, sortObjectiveInterval); + reportProblemsTimer = Rand.Range(0f, reportProblemsInterval); } public override void Update(float deltaTime) @@ -309,10 +314,10 @@ namespace Barotrauma { // Spot enemies while staying outside or inside an enemy ship. // does not apply for escorted characters, such as prisoners or terrorists who have their own behavior - enemycheckTimer -= deltaTime; - if (enemycheckTimer < 0) + enemyCheckTimer -= deltaTime; + if (enemyCheckTimer < 0) { - enemycheckTimer = enemyCheckInterval * Rand.Range(0.75f, 1.25f); + enemyCheckTimer = enemyCheckInterval * Rand.Range(0.75f, 1.25f); if (!objectiveManager.IsCurrentObjective()) { float closestDistance = 0; @@ -407,19 +412,29 @@ namespace Barotrauma { if (Character.IsOnPlayerTeam) { - VisibleHulls.ForEach(h => PropagateHullSafety(Character, h)); + foreach (Hull h in VisibleHulls) + { + PropagateHullSafety(Character, h); + } } else { - // Outpost npcs don't inform each other about threats, like crew members do. - VisibleHulls.ForEach(h => RefreshHullSafety(h)); + foreach (Hull h in VisibleHulls) + { + RefreshHullSafety(h); + } } } if (Character.SpeechImpediment < 100.0f) { - if (Character.Submarine != null && (Character.Submarine.TeamID == Character.TeamID || Character.IsEscorted) && !Character.Submarine.Info.IsWreck) + reportProblemsTimer -= deltaTime; + if (reportProblemsTimer <= 0.0f) { - ReportProblems(); + if (Character.Submarine != null && (Character.Submarine.TeamID == Character.TeamID || Character.IsEscorted) && !Character.Submarine.Info.IsWreck) + { + ReportProblems(); + } + reportProblemsTimer = reportProblemsInterval; } UpdateSpeaking(); } @@ -785,9 +800,10 @@ namespace Barotrauma if (item == null || item.Removed) { return; } if (!itemsToRelocate.Contains(item)) { return; } var mainSub = Submarine.MainSub; - if (item.ParentInventory != null) + Entity owner = item.GetRootInventoryOwner(); + if (owner != null) { - if (item.ParentInventory.Owner is Character c) + if (owner is Character c) { if (c.TeamID == CharacterTeamType.Team1 || c.TeamID == CharacterTeamType.Team2) { @@ -795,24 +811,37 @@ namespace Barotrauma return; } } - else if (item.ParentInventory.Owner.Submarine == mainSub) + else if (owner.Submarine == mainSub) { // Placed inside an inventory that's already in the main sub. return; } } - // Laying on ground inside the main sub. + // Laying on the ground inside the main sub. if (item.Submarine == mainSub) { return; } - WayPoint wp = WayPoint.GetRandom(SpawnType.Cargo, null, mainSub); - if (wp != null) + if (owner != null && owner != item) { - item.Submarine = mainSub; - item.SetTransform(wp.SimPosition, 0.0f); + item.Drop(null); + } + item.Submarine = mainSub; + Item newContainer = mainSub.FindContainerFor(item, onlyPrimary: false); + if (newContainer == null || !newContainer.OwnInventory.TryPutItem(item, user: null)) + { + WayPoint wp = WayPoint.GetRandom(SpawnType.Cargo, null, mainSub) ?? WayPoint.GetRandom(SpawnType.Path, null, mainSub); + if (wp != null) + { + item.SetTransform(wp.SimPosition, 0.0f, findNewHull: false, setPrevTransform: false); + } + else + { + DebugConsole.AddWarning($"Failed to relocate item {item.Prefab.Identifier} ({item.ID}), because no cargo spawn point could be found!"); + } } itemsToRelocate.Remove(item); + DebugConsole.Log($"Relocated item {item.Prefab.Identifier} ({item.ID}) back to the main sub."); } } @@ -1149,7 +1178,7 @@ namespace Barotrauma bool isAttackerFightingEnemy = false; float minorDamageThreshold = 1; float majorDamageThreshold = 20; - if (attacker.TeamID == Character.TeamID) + if (attacker.TeamID == Character.TeamID && !attacker.IsInstigator) { minorDamageThreshold = 10; majorDamageThreshold = 40; @@ -1356,6 +1385,10 @@ namespace Barotrauma Character FindInstigator() { + if (Character.IsInstigator) + { + return Character; + } if (attacker.IsInstigator) { return attacker; @@ -1545,7 +1578,7 @@ namespace Barotrauma (!requireEquipped || character.HasEquippedItem(i)) && (predicate == null || predicate(i)), recursive, matchingItems); items = matchingItems; - return matchingItems.Any(i => i != null && (containedTag.IsEmpty || i.ContainedItems.Any(it => it.HasTag(containedTag) && it.ConditionPercentage > conditionPercentage))); + return matchingItems.Any(i => i != null && (containedTag.IsEmpty || i.OwnInventory == null || i.ContainedItems.Any(it => it.HasTag(containedTag) && it.ConditionPercentage > conditionPercentage))); } public static void StructureDamaged(Structure structure, float damageAmount, Character character) @@ -1889,7 +1922,7 @@ namespace Barotrauma float fireFactor = 1; if (!ignoreFire) { - float calculateFire(Hull h) => h.FireSources.Count * 0.5f + h.FireSources.Sum(fs => fs.DamageRange) / h.Size.X; + static float calculateFire(Hull h) => h.FireSources.Count * 0.5f + h.FireSources.Sum(fs => fs.DamageRange) / h.Size.X; // Even the smallest fire reduces the safety by 50% float fire = visibleHulls == null ? calculateFire(hull) : visibleHulls.Sum(h => calculateFire(h)); fireFactor = MathHelper.Lerp(1, 0, MathHelper.Clamp(fire, 0, 1)); @@ -1897,10 +1930,22 @@ namespace Barotrauma float enemyFactor = 1; if (!ignoreEnemies) { - bool isValidTarget(Character e) => IsActive(e) && !IsFriendly(character, e) && !e.IsArrested; - int enemyCount = visibleHulls == null ? - Character.CharacterList.Count(e => isValidTarget(e) && e.CurrentHull == hull) : - Character.CharacterList.Count(e => isValidTarget(e) && visibleHulls.Contains(e.CurrentHull)); + int enemyCount = 0; + foreach (Character c in Character.CharacterList) + { + if (visibleHulls == null) + { + if (c.CurrentHull != hull) { continue; } + } + else + { + if (!visibleHulls.Contains(c.CurrentHull)) { continue; } + } + if (IsActive(c) && !IsFriendly(character, c) && !c.IsArrested) + { + enemyCount++; + } + } // The hull safety decreases 90% per enemy up to 100% (TODO: test smaller percentages) enemyFactor = MathHelper.Lerp(1, 0, MathHelper.Clamp(enemyCount * 0.9f, 0, 1)); } @@ -1911,6 +1956,7 @@ namespace Barotrauma if (item.Prefab != null && item.Prefab.IsDangerous) { dangerousItemsFactor = 0; + break; } } float safety = oxygenFactor * waterFactor * fireFactor * enemyFactor * dangerousItemsFactor; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCleanupItems.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCleanupItems.cs index c647b82d0..9222801ef 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCleanupItems.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCleanupItems.cs @@ -113,7 +113,7 @@ namespace Barotrauma else { var connectionPanel = item.GetComponent(); - if (connectionPanel != null && connectionPanel.Connections.Any(c => c.Wires.Any(w => w != null))) + if (connectionPanel != null && connectionPanel.Connections.Any(c => c.Wires.Count > 0)) { return false; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs index bdc673490..c3a21854d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using FarseerPhysics.Dynamics; +using static Barotrauma.AIObjectiveFindSafety; namespace Barotrauma { @@ -775,7 +776,13 @@ namespace Barotrauma } else { - retreatTarget = findSafety.FindBestHull(HumanAIController.VisibleHulls, allowChangingTheSubmarine: character.TeamID != CharacterTeamType.FriendlyNPC); + HullSearchStatus hullSearchStatus = findSafety.FindBestHull(out Hull potentialSafeHull, HumanAIController.VisibleHulls, allowChangingSubmarine: character.TeamID != CharacterTeamType.FriendlyNPC); + if (hullSearchStatus != HullSearchStatus.Finished) + { + findSafety.UpdateSimpleEscape(deltaTime); + return; + } + retreatTarget = potentialSafeHull; findHullTimer = findHullInterval * Rand.Range(0.9f, 1.1f); } } @@ -785,21 +792,21 @@ namespace Barotrauma { UsePathingOutside = false }, - onAbandon: () => + onAbandon: () => + { + if (Enemy != null && HumanAIController.VisibleHulls.Contains(Enemy.CurrentHull)) { - if (Enemy != null && HumanAIController.VisibleHulls.Contains(Enemy.CurrentHull)) - { - // If in the same room with an enemy -> don't try to escape because we'd want to fight it - SteeringManager.Reset(); - RemoveSubObjective(ref retreatObjective); - } - else - { - // else abandon and fall back to find safety mode - Abandon = true; - } - }, - onCompleted: () => RemoveSubObjective(ref retreatObjective)); + // If in the same room with an enemy -> don't try to escape because we'd want to fight it + SteeringManager.Reset(); + RemoveSubObjective(ref retreatObjective); + } + else + { + // else abandon and fall back to find safety mode + Abandon = true; + } + }, + onCompleted: () => RemoveSubObjective(ref retreatObjective)); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindSafety.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindSafety.cs index f3380c63a..2eb5b453e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindSafety.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindSafety.cs @@ -1,4 +1,5 @@ -using FarseerPhysics; +using Barotrauma.Extensions; +using FarseerPhysics; using Microsoft.Xna.Framework; using System; using System.Collections.Generic; @@ -192,9 +193,17 @@ namespace Barotrauma } else { + HullSearchStatus hullSearchStatus = FindBestHull(out Hull potentialSafeHull, allowChangingSubmarine: character.TeamID != CharacterTeamType.FriendlyNPC); + if (hullSearchStatus != HullSearchStatus.Finished) + { + UpdateSimpleEscape(deltaTime); + return; + } + searchHullTimer = SearchHullInterval * Rand.Range(0.9f, 1.1f); previousSafeHull = currentSafeHull; - currentSafeHull = FindBestHull(allowChangingTheSubmarine: character.TeamID != CharacterTeamType.FriendlyNPC); + currentSafeHull = potentialSafeHull; + cannotFindSafeHull = currentSafeHull == null || HumanAIController.NeedsDivingGear(currentSafeHull, out _); if (currentSafeHull == null) { @@ -250,58 +259,122 @@ namespace Barotrauma } } if (subObjectives.Any(so => so.CanBeCompleted)) { return; } - if (currentHull != null) + UpdateSimpleEscape(deltaTime); + } + } + + public void UpdateSimpleEscape(float deltaTime) + { + Vector2 escapeVel = Vector2.Zero; + if (character.CurrentHull != null) + { + foreach (Hull hull in HumanAIController.VisibleHulls) { - //goto objective doesn't exist (a safe hull not found, or a path to a safe hull not found) - // -> attempt to manually steer away from hazards - Vector2 escapeVel = Vector2.Zero; - foreach (Hull hull in HumanAIController.VisibleHulls) + foreach (FireSource fireSource in hull.FireSources) { - foreach (FireSource fireSource in hull.FireSources) - { - Vector2 dir = character.Position - fireSource.Position; - float distMultiplier = MathHelper.Clamp(100.0f / Vector2.Distance(fireSource.Position, character.Position), 0.1f, 10.0f); - escapeVel += new Vector2(Math.Sign(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier); - } - } - foreach (Character enemy in Character.CharacterList) - { - if (!HumanAIController.IsActive(enemy) || HumanAIController.IsFriendly(enemy) || enemy.IsArrested) { continue; } - if (HumanAIController.VisibleHulls.Contains(enemy.CurrentHull)) - { - Vector2 dir = character.Position - enemy.Position; - float distMultiplier = MathHelper.Clamp(100.0f / Vector2.Distance(enemy.Position, character.Position), 0.1f, 10.0f); - escapeVel += new Vector2(Math.Sign(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier); - } - } - if (escapeVel != Vector2.Zero) - { - float left = currentHull.Rect.X + 50; - float right = currentHull.Rect.Right - 50; - //only move if we haven't reached the edge of the room - if (escapeVel.X < 0 && character.Position.X > left || escapeVel.X > 0 && character.Position.X < right) - { - character.AIController.SteeringManager.SteeringManual(deltaTime, escapeVel); - } - else - { - character.AnimController.TargetDir = escapeVel.X < 0.0f ? Direction.Right : Direction.Left; - character.AIController.SteeringManager.Reset(); - } - return; + Vector2 dir = character.Position - fireSource.Position; + float distMultiplier = MathHelper.Clamp(100.0f / Vector2.Distance(fireSource.Position, character.Position), 0.1f, 10.0f); + escapeVel += new Vector2(Math.Sign(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier); } } + foreach (Character enemy in Character.CharacterList) + { + if (!HumanAIController.IsActive(enemy) || HumanAIController.IsFriendly(enemy) || enemy.IsArrested) { continue; } + if (HumanAIController.VisibleHulls.Contains(enemy.CurrentHull)) + { + Vector2 dir = character.Position - enemy.Position; + float distMultiplier = MathHelper.Clamp(100.0f / Vector2.Distance(enemy.Position, character.Position), 0.1f, 10.0f); + escapeVel += new Vector2(Math.Sign(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier); + } + } + } + if (escapeVel != Vector2.Zero) + { + float left = character.CurrentHull.Rect.X + 50; + float right = character.CurrentHull.Rect.Right - 50; + //only move if we haven't reached the edge of the room + if (escapeVel.X < 0 && character.Position.X > left || escapeVel.X > 0 && character.Position.X < right) + { + character.AIController.SteeringManager.SteeringManual(deltaTime, escapeVel); + } + else + { + character.AnimController.TargetDir = escapeVel.X < 0.0f ? Direction.Right : Direction.Left; + character.AIController.SteeringManager.Reset(); + } + } + else + { objectiveManager.GetObjective().Wander(deltaTime); } } - public Hull FindBestHull(IEnumerable ignoredHulls = null, bool allowChangingTheSubmarine = true) + public enum HullSearchStatus { - //sort the hulls based on distance and which sub they're in - //tends to make the method much faster, because we find a potential hull earlier and can discard further-away hulls more easily - //(for instance, an NPC in an outpost might otherwise go through all the hulls in the main sub first and do tons of expensive - //path calculations, only to discard all of them when going through the hulls in the outpost) - float EstimateHullSuitability(Hull hull) + Running, + Finished + } + + private readonly List hulls = new List(); + private int hullSearchIndex = -1; + float bestHullValue = 0; + bool bestHullIsAirlock = false; + Hull potentialBestHull; + + /// + /// Tries to find the best (safe, nearby) hull the character can find a path to. + /// Checks one hull at a time, and returns HullSearchStatus.Finished when all potential hulls have been checked. + /// + public HullSearchStatus FindBestHull(out Hull bestHull, IEnumerable ignoredHulls = null, bool allowChangingSubmarine = true) + { + if (hullSearchIndex == -1) + { + bestHullValue = 0; + potentialBestHull = null; + bestHullIsAirlock = false; + hulls.Clear(); + var connectedSubs = character.Submarine?.GetConnectedSubs(); + foreach (Hull hull in Hull.HullList) + { + 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 (!allowChangingSubmarine && hull.Submarine != character.Submarine) { continue; } + if (hull.Rect.Height < ConvertUnits.ToDisplayUnits(character.AnimController.ColliderHeightFromFloor) * 2) { continue; } + if (ignoredHulls != null && ignoredHulls.Contains(hull)) { continue; } + if (HumanAIController.UnreachableHulls.Contains(hull)) { continue; } + if (connectedSubs != null && !connectedSubs.Contains(hull.Submarine)) { continue; } + + //sort the hulls based on distance and which sub they're in + //tends to make the method much faster, because we find a potential hull earlier and can discard further-away hulls more easily + //(for instance, an NPC in an outpost might otherwise go through all the hulls in the main sub first and do tons of expensive + //path calculations, only to discard all of them when going through the hulls in the outpost) + float hullSuitability = EstimateHullSuitability(character, hull); + if (!hulls.Any()) + { + hulls.Add(hull); + } + else + { + for (int i = 0; i < hulls.Count; i++) + { + if (hullSuitability > EstimateHullSuitability(character, hulls[i])) + { + hulls.Insert(i, hull); + break; + } + } + } + } + if (hulls.None()) + { + bestHull = null; + return HullSearchStatus.Finished; + } + hullSearchIndex = 0; + } + + static float EstimateHullSuitability(Character character, Hull hull) { float dist = Math.Abs(hull.WorldPosition.X - character.WorldPosition.X) + @@ -314,86 +387,91 @@ namespace Barotrauma return suitability; } - Hull bestHull = null; - float bestValue = 0; - bool bestIsAirlock = false; - foreach (Hull hull in Hull.HullList.OrderByDescending(h => EstimateHullSuitability(h))) + Hull potentialHull = hulls[hullSearchIndex]; + + float hullSafety = 0; + bool hullIsAirlock = false; + bool isCharacterInside = character.CurrentHull != null && character.Submarine != null; + if (isCharacterInside) { - if (hull.Submarine == null) { continue; } - // Ruins are mazes filled with water. There's no safe hulls and we don't want to use the resources on it. - if (hull.Submarine.Info.IsRuin) { continue; } - if (!allowChangingTheSubmarine && hull.Submarine != character.Submarine) { continue; } - if (hull.Rect.Height < ConvertUnits.ToDisplayUnits(character.AnimController.ColliderHeightFromFloor) * 2) { continue; } - if (ignoredHulls != null && ignoredHulls.Contains(hull)) { continue; } - if (HumanAIController.UnreachableHulls.Contains(hull)) { continue; } - float hullSafety = 0; - bool hullIsAirlock = false; - bool isCharacterInside = character.CurrentHull != null && character.Submarine != null; - if (isCharacterInside) - { - if (!character.Submarine.IsConnectedTo(hull.Submarine)) { continue; } - hullSafety = HumanAIController.GetHullSafety(hull, hull.GetConnectedHulls(true, 1), character); - float yDist = Math.Abs(character.WorldPosition.Y - hull.WorldPosition.Y); - yDist = yDist > 100 ? yDist * 3 : 0; - float dist = Math.Abs(character.WorldPosition.X - hull.WorldPosition.X) + yDist; - float distanceFactor = MathHelper.Lerp(1, 0.9f, MathUtils.InverseLerp(0, 10000, dist)); - hullSafety *= distanceFactor; - //skip the hull if the safety is already less than the best hull - //(no need to do the expensive pathfinding if we already know we're not going to choose this hull) - if (hullSafety < bestValue) { continue; } + hullSafety = HumanAIController.GetHullSafety(potentialHull, potentialHull.GetConnectedHulls(true, 1), character); + float yDist = Math.Abs(character.WorldPosition.Y - potentialHull.WorldPosition.Y); + yDist = yDist > 100 ? yDist * 3 : 0; + float dist = Math.Abs(character.WorldPosition.X - potentialHull.WorldPosition.X) + yDist; + float distanceFactor = MathHelper.Lerp(1, 0.9f, MathUtils.InverseLerp(0, 10000, dist)); + hullSafety *= distanceFactor; + //skip the hull if the safety is already less than the best hull + //(no need to do the expensive pathfinding if we already know we're not going to choose this hull) + if (hullSafety > bestHullValue) + { //avoid airlock modules if not allowed to change the sub - if (!allowChangingTheSubmarine && hull.OutpostModuleTags.Any(t => t == "airlock")) + if (allowChangingSubmarine || !potentialHull.OutpostModuleTags.Any(t => t == "airlock")) { - continue; + // Don't allow to go outside if not already outside. + var path = PathSteering.PathFinder.FindPath(character.SimPosition, potentialHull.SimPosition, character.Submarine, nodeFilter: node => node.Waypoint.CurrentHull != null); + if (path.Unreachable) + { + hullSafety = 0; + HumanAIController.UnreachableHulls.Add(potentialHull); + } + else + { + // Each unsafe node reduces the hull safety value. + // Ignore the current hull, because otherwise we couldn't find a path out. + int unsafeNodes = path.Nodes.Count(n => n.CurrentHull != character.CurrentHull && HumanAIController.UnsafeHulls.Contains(n.CurrentHull)); + hullSafety /= 1 + unsafeNodes; + // If the target is not inside a friendly submarine, considerably reduce the hull safety. + if (!character.Submarine.IsEntityFoundOnThisSub(potentialHull, true)) + { + hullSafety /= 10; + } + } } - // Don't allow to go outside if not already outside. - var path = PathSteering.PathFinder.FindPath(character.SimPosition, hull.SimPosition, character.Submarine, nodeFilter: node => node.Waypoint.CurrentHull != null); - if (path.Unreachable) + else { - HumanAIController.UnreachableHulls.Add(hull); - continue; + hullSafety = 0; } - // Each unsafe node reduces the hull safety value. - // Ignore the current hull, because otherwise we couldn't find a path out. - int unsafeNodes = path.Nodes.Count(n => n.CurrentHull != character.CurrentHull && HumanAIController.UnsafeHulls.Contains(n.CurrentHull)); - hullSafety /= 1 + unsafeNodes; - // If the target is not inside a friendly submarine, considerably reduce the hull safety. - if (!character.Submarine.IsEntityFoundOnThisSub(hull, true)) - { - hullSafety /= 10; - } - } - else - { - // TODO: could also target gaps that get us inside? - if (hull.IsTaggedAirlock()) - { - hullSafety = 100; - hullIsAirlock = true; - } - else if(!bestIsAirlock && hull.LeadsOutside(character)) - { - hullSafety = 100; - } - // Huge preference for closer targets - float distance = Vector2.DistanceSquared(character.WorldPosition, hull.WorldPosition); - float distanceFactor = MathHelper.Lerp(1, 0.2f, MathUtils.InverseLerp(0, MathUtils.Pow(100000, 2), distance)); - hullSafety *= distanceFactor; - // If the target is not inside a friendly submarine, considerably reduce the hull safety. - // Intentionally exclude wrecks from this check - if (hull.Submarine.TeamID != character.TeamID && hull.Submarine.TeamID != CharacterTeamType.FriendlyNPC) - { - hullSafety /= 10; - } - } - if (hullSafety > bestValue || (!isCharacterInside && hullIsAirlock && !bestIsAirlock)) - { - bestHull = hull; - bestValue = hullSafety; - bestIsAirlock = hullIsAirlock; } } - return bestHull; + else + { + // TODO: could also target gaps that get us inside? + if (potentialHull.IsTaggedAirlock()) + { + hullSafety = 100; + hullIsAirlock = true; + } + else if(!bestHullIsAirlock && potentialHull.LeadsOutside(character)) + { + hullSafety = 100; + } + // Huge preference for closer targets + float distance = Vector2.DistanceSquared(character.WorldPosition, potentialHull.WorldPosition); + float distanceFactor = MathHelper.Lerp(1, 0.2f, MathUtils.InverseLerp(0, MathUtils.Pow(100000, 2), distance)); + hullSafety *= distanceFactor; + // If the target is not inside a friendly submarine, considerably reduce the hull safety. + // Intentionally exclude wrecks from this check + if (potentialHull.Submarine.TeamID != character.TeamID && potentialHull.Submarine.TeamID != CharacterTeamType.FriendlyNPC) + { + hullSafety /= 10; + } + } + if (hullSafety > bestHullValue || (!isCharacterInside && hullIsAirlock && !bestHullIsAirlock)) + { + potentialBestHull = potentialHull; + bestHullValue = hullSafety; + bestHullIsAirlock = hullIsAirlock; + } + + bestHull = potentialBestHull; + hullSearchIndex++; + + if (hullSearchIndex >= hulls.Count) + { + hullSearchIndex = -1; + return HullSearchStatus.Finished; + } + return HullSearchStatus.Running; } public override void Reset() diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFixLeak.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFixLeak.cs index 99adb5e1d..8807abfd1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFixLeak.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFixLeak.cs @@ -165,6 +165,7 @@ namespace Barotrauma requiredCondition = () => Leak.Submarine == character.Submarine && Leak.linkedTo.Any(e => e is Hull h && character.CurrentHull == h), + endNodeFilter = n => n.Waypoint.CurrentHull != null && Leak.linkedTo.Any(e => e is Hull h && h == n.Waypoint.CurrentHull), // The Go To objective can be abandoned if the leak is fixed (in which case we don't want to use the dialogue) SpeakCannotReachCondition = () => !CheckObjectiveSpecific() }, diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGetItem.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGetItem.cs index 4b5aba5dc..853abb82e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGetItem.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGetItem.cs @@ -471,7 +471,8 @@ namespace Barotrauma { if (spawnItemIfNotFound) { - if (!(MapEntityPrefab.List.FirstOrDefault(me => me is ItemPrefab ip && IdentifiersOrTags.Any(id => id == ip.Identifier || ip.Tags.Contains(id))) is ItemPrefab prefab)) + ItemPrefab prefab = FindItemToSpawn(); + if (prefab == null) { #if DEBUG DebugConsole.NewMessage($"{character.Name}: Cannot find an item with the following identifier(s) or tag(s): {string.Join(", ", IdentifiersOrTags)}, tried to spawn the item but no matching item prefabs were found.", Color.Yellow); @@ -501,6 +502,33 @@ namespace Barotrauma } } + /// + /// Returns the "best" item to spawn when using and there's multiple suitable items. + /// Best in this context is the one that's sold at the lowest price in stores (usually the most "basic" item) + /// + /// + private ItemPrefab FindItemToSpawn() + { + ItemPrefab bestItem = null; + float lowestCost = float.MaxValue; + foreach (MapEntityPrefab prefab in MapEntityPrefab.List) + { + if (!(prefab is ItemPrefab itemPrefab)) { continue; } + if (IdentifiersOrTags.Any(id => id == prefab.Identifier || prefab.Tags.Contains(id))) + { + float cost = itemPrefab.DefaultPrice != null && itemPrefab.CanBeBought ? + itemPrefab.DefaultPrice.Price : + float.MaxValue; + if (cost < lowestCost || bestItem == null) + { + bestItem = itemPrefab; + lowestCost = cost; + } + } + } + return bestItem; + } + protected override bool CheckObjectiveSpecific() { if (IsCompleted) { return true; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRescue.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRescue.cs index 4be832fd0..c700ff0ad 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRescue.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRescue.cs @@ -4,6 +4,7 @@ using Microsoft.Xna.Framework; using System; using System.Collections.Generic; using System.Linq; +using static Barotrauma.AIObjectiveFindSafety; namespace Barotrauma { @@ -186,7 +187,9 @@ namespace Barotrauma } else { - safeHull = objectiveManager.GetObjective().FindBestHull(HumanAIController.VisibleHulls); + HullSearchStatus hullSearchStatus = objectiveManager.GetObjective().FindBestHull(out Hull potentialSafeHull, HumanAIController.VisibleHulls); + if (hullSearchStatus != HullSearchStatus.Finished) { return; } + safeHull = potentialSafeHull; findHullTimer = findHullInterval * Rand.Range(0.9f, 1.1f); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/AnimController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/AnimController.cs index ba7a674fb..258eb4e33 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/AnimController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/AnimController.cs @@ -24,7 +24,7 @@ namespace Barotrauma public bool IsAiming => wasAiming; public bool IsAimingMelee => wasAimingMelee; - protected bool Aiming => aiming || aimingMelee; + protected bool Aiming => aiming || aimingMelee || LockFlippingUntil > Timing.TotalTime && character.IsKeyDown(InputType.Aim); public float ArmLength => upperArmLength + forearmLength; @@ -275,6 +275,8 @@ namespace Barotrauma // We need some margin, because if a hatch has closed, it's possible that the height from floor is slightly negative. public bool IsAboveFloor => GetHeightFromFloor() > -0.1f; + public float LockFlippingUntil; + public void UpdateUseItem(bool allowMovement, Vector2 handWorldPos) { useItemTimer = 0.5f; @@ -380,18 +382,10 @@ namespace Barotrauma { //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) + if (!anotherItemControlsPose && TargetMovement == Vector2.Zero && inWater) { - 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); - } + torso.body.AngularVelocity -= torso.body.AngularVelocity * 0.1f; + torso.body.ApplyForce(torso.body.LinearVelocity * -0.5f); } aiming = true; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs index 7953deff1..74f7ad622 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs @@ -164,8 +164,6 @@ namespace Barotrauma public float LegBendTorque => CurrentGroundedParams.LegBendTorque * RagdollParams.JointScale; public Vector2 HandMoveOffset => CurrentGroundedParams.HandMoveOffset * RagdollParams.JointScale; - public float LockFlippingUntil; - public override Vector2 AimSourceSimPos { get @@ -841,7 +839,7 @@ namespace Barotrauma rotation += 360; } float targetSpeed = TargetMovement.Length(); - if (targetSpeed > 0.1f && !character.IsRemotelyControlled && !character.IsKeyDown(InputType.Aim)) + if (targetSpeed > 0.1f && !character.IsRemotelyControlled && !Aiming) { if (Anim != Animation.UsingConstruction && !(character.SelectedConstruction?.GetComponent()?.ControlCharacterPose ?? false)) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs index 736e50d2c..d31d943a0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs @@ -153,12 +153,12 @@ namespace Barotrauma protected ActiveTeamChange currentTeamChange; const string OriginalTeamIdentifier = "original"; - public static void ThrowIfAccessingWalletsInSingleplayer() + private void ThrowIfAccessingWalletsInSingleplayer() { #if CLIENT && DEBUG if (Screen.Selected is TestScreen) { return; } #endif - if (GameMain.NetworkMember is null || GameMain.IsSingleplayer) + if ((GameMain.NetworkMember is null || GameMain.IsSingleplayer) && IsPlayer) { throw new InvalidOperationException($"Tried to access crew wallets in singleplayer. Use {nameof(CampaignMode)}.{nameof(CampaignMode.Bank)} or {nameof(CampaignMode)}.{nameof(CampaignMode.GetWallet)} instead."); } @@ -560,18 +560,35 @@ namespace Barotrauma #if CLIENT CharacterHealth.SetHealthBarVisibility(value == null); -#elif SERVER - if (value is { IsDead: true, Wallet: { Balance: var balance } grabbedWallet } && balance > 0) +#endif + bool isServerOrSingleplayer = GameMain.IsSingleplayer || GameMain.NetworkMember is { IsServer: true }; + if (IsPlayer && isServerOrSingleplayer && value is { IsDead: true, Wallet: { Balance: var balance } grabbedWallet } && balance > 0) { - if (GameMain.GameSession.Campaign is MultiPlayerCampaign mpCampaign) +#if SERVER + if (GameMain.GameSession.Campaign is MultiPlayerCampaign mpCampaign && GameMain.Server is { ServerSettings: { } settings }) { - mpCampaign.Bank.Give(balance); + switch (settings.LootedMoneyDestination) + { + case LootedMoneyDestination.Wallet when IsPlayer: + Wallet.Give(balance); + break; + default: + mpCampaign.Bank.Give(balance); + break; + + } } - grabbedWallet.Deduct(balance); GameServer.Log($"{GameServer.CharacterLogName(this)} grabbed {value.Name}'s body and received {grabbedWallet.Balance} mk.", ServerLog.MessageType.Money); - } +#elif CLIENT + if (GameMain.GameSession.Campaign is SinglePlayerCampaign spCampaign) + { + spCampaign.Bank.Give(balance); + } #endif + + grabbedWallet.Deduct(balance); + } } } @@ -1443,7 +1460,7 @@ namespace Barotrauma foreach (Item item in Inventory.AllItems) { - if (item?.Prefab.Identifier != "idcard") { continue; } + if (item?.GetComponent() == null) { continue; } foreach (string s in spawnPoint.IdCardTags) { item.AddTag(s); @@ -3954,7 +3971,10 @@ namespace Barotrauma if (actionType != ActionType.OnDamaged && actionType != ActionType.OnSevered) { // OnDamaged is called only for the limb that is hit. - AnimController.Limbs.ForEach(l => l.ApplyStatusEffects(actionType, deltaTime)); + foreach (Limb limb in AnimController.Limbs) + { + limb.ApplyStatusEffects(actionType, deltaTime); + } } //OnActive effects are handled by the afflictions themselves if (actionType != ActionType.OnActive) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs index 61790a3b2..7700016df 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs @@ -252,12 +252,11 @@ namespace Barotrauma } /// - /// Endocrine boosters can unlock talents outside the user's talent tree. This method is used to specifically get them + /// Returns unlocked talents that aren't part of the character's talent tree (which can be unlocked e.g. with an endocrine booster) /// - public IEnumerable GetEndocrineTalents() + public IEnumerable GetUnlockedTalentsOutsideTree() { if (!TalentTree.JobTalentTrees.TryGet(Job.Prefab.Identifier, out TalentTree talentTree)) { return Enumerable.Empty(); } - return UnlockedTalents.Where(t => !talentTree.TalentIsInTree(t)); } @@ -1182,7 +1181,7 @@ namespace Barotrauma // Replace the name tag of any existing id cards or duffel bags foreach (var item in Item.ItemList) { - if (item.Prefab.Identifier != "idcard" && !item.Tags.Contains("despawncontainer")) { continue; } + if (!item.HasTag("identitycard") && !item.HasTag("despawncontainer")) { continue; } foreach (var tag in item.Tags.Split(',')) { var splitTag = tag.Split(":"); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionPrefab.cs index 4bc5d873b..4e277a25a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionPrefab.cs @@ -300,6 +300,7 @@ namespace Barotrauma } public static AfflictionPrefab InternalDamage => Prefabs["internaldamage"]; + public static AfflictionPrefab BiteWounds => Prefabs["bitewounds"]; public static AfflictionPrefab ImpactDamage => Prefabs["blunttrauma"]; public static AfflictionPrefab Bleeding => Prefabs["bleeding"]; public static AfflictionPrefab Burn => Prefabs["burn"]; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs index 7687f0b65..5e4435b7d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs @@ -124,7 +124,18 @@ namespace Barotrauma public float PressureKillDelay { get; private set; } = 5.0f; - public float Vitality { get; private set; } + private float vitality; + public float Vitality + { + get + { + return Character.IsDead ? minVitality : vitality; + } + private set + { + vitality = value; + } + } public float HealthPercentage => MathUtils.Percentage(Vitality, MaxVitality); @@ -725,6 +736,8 @@ namespace Barotrauma AddLimbAffliction(limbHealth: null, newAffliction, allowStacking); } + partial void UpdateSkinTint(); + partial void UpdateLimbAfflictionOverlays(); public void Update(float deltaTime) @@ -788,7 +801,7 @@ namespace Barotrauma if (!Character.GodMode) { UpdateLimbAfflictionOverlays(); - UpdateSkinTint(); + UpdateSkinTint(); CalculateVitality(); if (Vitality <= MinVitality) @@ -798,23 +811,6 @@ namespace Barotrauma } } - private void UpdateSkinTint() - { - FaceTint = DefaultFaceTint; - BodyTint = Color.TransparentBlack; - - if (!(Character?.Params?.Health.ApplyAfflictionColors ?? false)) { return; } - - foreach (KeyValuePair kvp in afflictions) - { - var affliction = kvp.Key; - Color faceTint = affliction.GetFaceTint(); - if (faceTint.A > FaceTint.A) { FaceTint = faceTint; } - Color bodyTint = affliction.GetBodyTint(); - if (bodyTint.A > BodyTint.A) { BodyTint = bodyTint; } - } - } - private void UpdateDamageReductions(float deltaTime) { float healthRegen = Character.Params.Health.ConstantHealthRegeneration; @@ -905,6 +901,7 @@ namespace Barotrauma if (Unkillable || Character.GodMode) { return; } var (type, affliction) = GetCauseOfDeath(); + UpdateLimbAfflictionOverlays(); UpdateSkinTint(); Character.Kill(type, affliction); #if CLIENT diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/HumanPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/HumanPrefab.cs index 46a7da94f..c2d64b348 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/HumanPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/HumanPrefab.cs @@ -105,9 +105,9 @@ namespace Barotrauma return spawnPointTags; } - public JobPrefab GetJobPrefab(Rand.RandSync randSync = Rand.RandSync.Unsynced) + public JobPrefab GetJobPrefab(Rand.RandSync randSync = Rand.RandSync.Unsynced, Func predicate = null) { - return Job != null && Job != "any" ? JobPrefab.Get(Job) : JobPrefab.Random(randSync); + return Job != null && Job != "any" ? JobPrefab.Get(Job) : JobPrefab.Random(randSync, predicate); } public void InitializeCharacter(Character npc, ISpatialEntity positionToStayIn = null) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/Job.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/Job.cs index f9f45d936..615a33350 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/Job.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/Job.cs @@ -209,11 +209,8 @@ namespace Barotrauma } } - if (item.Prefab.Identifier == "idcard") - { - IdCard idCardComponent = item.GetComponent(); - idCardComponent?.Initialize(spawnPoint, character); - } + IdCard idCardComponent = item.GetComponent(); + idCardComponent?.Initialize(spawnPoint, character); foreach (WifiComponent wifiComponent in item.GetComponents()) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/JobPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/JobPrefab.cs index 1d1c112f1..f8ea728fd 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/JobPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/JobPrefab.cs @@ -293,6 +293,6 @@ namespace Barotrauma //ClothingElement = element.GetChildElement("PortraitClothing"); } - public static JobPrefab Random(Rand.RandSync sync) => Prefabs.GetRandom(p => !p.HiddenJob, sync); + public static JobPrefab Random(Rand.RandSync sync, Func predicate = null) => Prefabs.GetRandom(p => !p.HiddenJob && (predicate == null || predicate(p)), sync); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Limb.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Limb.cs index f9c0f5562..98761b381 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Limb.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Limb.cs @@ -331,7 +331,7 @@ namespace Barotrauma #if CLIENT if (isSevered) { - damageOverlayStrength = 100.0f; + damageOverlayStrength = 1.0f; } #endif } @@ -597,18 +597,7 @@ namespace Barotrauma dir = Direction.Right; body = new PhysicsBody(limbParams); type = limbParams.Type; - if (limbParams.IgnoreCollisions) - { - body.CollisionCategories = Category.None; - body.CollidesWith = Category.None; - IgnoreCollisions = true; - } - else - { - //limbs don't collide with each other - body.CollisionCategories = Physics.CollisionCharacter; - body.CollidesWith = Physics.CollisionAll & ~Physics.CollisionCharacter & ~Physics.CollisionItem & ~Physics.CollisionItemBlocking; - } + IgnoreCollisions = limbParams.IgnoreCollisions; body.UserData = this; pullJoint = new FixedMouseJoint(body.FarseerBody, ConvertUnits.ToSimUnits(limbParams.PullPos * Scale)) { @@ -646,10 +635,9 @@ namespace Barotrauma } attack.DamageRange = ConvertUnits.ToDisplayUnits(attack.DamageRange); } - if (!character.VariantOf.IsEmpty) + if (character is { VariantOf: { IsEmpty: false } }) { - var attackElement = CharacterPrefab.Prefabs.TryGet(character.VariantOf, out var basePrefab) - ? basePrefab.ConfigElement.GetChildElement("attack") : null; + var attackElement = character.Params.VariantFile.Root.GetChildElement("attack"); if (attackElement != null) { attack.DamageMultiplier = attackElement.GetAttributeFloat("damagemultiplier", 1f); diff --git a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/BallastFloraFile.cs b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/BallastFloraFile.cs index 1b45bc400..eb564c001 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/BallastFloraFile.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/BallastFloraFile.cs @@ -9,7 +9,7 @@ namespace Barotrauma protected override bool MatchesSingular(Identifier identifier) => identifier == "ballastflorabehavior"; protected override bool MatchesPlural(Identifier identifier) => identifier == "ballastflorabehaviors"; - protected override PrefabCollection prefabs => BallastFloraPrefab.Prefabs; + protected override PrefabCollection Prefabs => BallastFloraPrefab.Prefabs; protected override BallastFloraPrefab CreatePrefab(ContentXElement element) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/CaveGenerationParametersFile.cs b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/CaveGenerationParametersFile.cs index 717057ff6..afb3296aa 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/CaveGenerationParametersFile.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/CaveGenerationParametersFile.cs @@ -9,7 +9,7 @@ namespace Barotrauma protected override bool MatchesSingular(Identifier identifier) => identifier == "cave"; protected override bool MatchesPlural(Identifier identifier) => identifier == "cavegenerationparameters"; - protected override PrefabCollection prefabs => CaveGenerationParams.CaveParams; + protected override PrefabCollection Prefabs => CaveGenerationParams.CaveParams; protected override CaveGenerationParams CreatePrefab(ContentXElement element) { return new CaveGenerationParams(element, this); diff --git a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/CharacterFile.cs b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/CharacterFile.cs index 1a0b569d5..e3412c1be 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/CharacterFile.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/CharacterFile.cs @@ -77,7 +77,7 @@ namespace Barotrauma { HashSet texturePaths = new HashSet { - ragdollParams.Texture + ContentPath.FromRaw(CharacterPrefab.Prefabs[speciesName].ContentPackage, ragdollParams.Texture).Value }; foreach (RagdollParams.LimbParams limb in ragdollParams.Limbs) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/CorpsesFile.cs b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/CorpsesFile.cs index b9eb4ddce..79e071b9e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/CorpsesFile.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/CorpsesFile.cs @@ -9,7 +9,7 @@ namespace Barotrauma protected override bool MatchesSingular(Identifier identifier) => identifier == "corpse"; protected override bool MatchesPlural(Identifier identifier) => identifier == "corpses"; - protected override PrefabCollection prefabs => CorpsePrefab.Prefabs; + protected override PrefabCollection Prefabs => CorpsePrefab.Prefabs; protected override CorpsePrefab CreatePrefab(ContentXElement element) { return new CorpsePrefab(element, this); diff --git a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/EventManagerSettingsFile.cs b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/EventManagerSettingsFile.cs index 298f618c5..0e6c4c309 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/EventManagerSettingsFile.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/EventManagerSettingsFile.cs @@ -8,7 +8,7 @@ namespace Barotrauma protected override bool MatchesSingular(Identifier identifier) => !MatchesPlural(identifier); protected override bool MatchesPlural(Identifier identifier) => identifier == "EventManagerSettings"; - protected override PrefabCollection prefabs => EventManagerSettings.Prefabs; + protected override PrefabCollection Prefabs => EventManagerSettings.Prefabs; protected override EventManagerSettings CreatePrefab(ContentXElement element) { return new EventManagerSettings(element, this); diff --git a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/FactionsFile.cs b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/FactionsFile.cs index bb200e5c6..fac0ae4cb 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/FactionsFile.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/FactionsFile.cs @@ -9,7 +9,7 @@ namespace Barotrauma protected override bool MatchesSingular(Identifier identifier) => identifier == "faction"; protected override bool MatchesPlural(Identifier identifier) => identifier == "factions"; - protected override PrefabCollection prefabs => FactionPrefab.Prefabs; + protected override PrefabCollection Prefabs => FactionPrefab.Prefabs; protected override FactionPrefab CreatePrefab(ContentXElement element) { return new FactionPrefab(element, this); diff --git a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/GenericPrefabFile.cs b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/GenericPrefabFile.cs index e59272553..cd0906887 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/GenericPrefabFile.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/GenericPrefabFile.cs @@ -9,7 +9,7 @@ namespace Barotrauma protected abstract bool MatchesSingular(Identifier identifier); protected abstract bool MatchesPlural(Identifier identifier); - protected abstract PrefabCollection prefabs { get; } + protected abstract PrefabCollection Prefabs { get; } protected abstract T CreatePrefab(ContentXElement element); private void LoadFromXElement(ContentXElement parentElement, bool overriding) @@ -29,14 +29,14 @@ namespace Barotrauma } else if (elemName == "clear") { - prefabs.AddOverrideFile(this); + Prefabs.AddOverrideFile(this); } else if (MatchesSingular(elemName)) { T prefab = CreatePrefab(parentElement); try { - prefabs.Add(prefab, overriding); + Prefabs.Add(prefab, overriding); } catch { @@ -53,7 +53,7 @@ namespace Barotrauma } else { - DebugConsole.ThrowError($"Invalid {GetType().Name} element: {parentElement.Name} in {Path}"); + DebugConsole.ThrowError($"GenericPrefabFile: Invalid {GetType().Name} element: {parentElement.Name} in {Path}"); } } @@ -68,12 +68,12 @@ namespace Barotrauma public override sealed void UnloadFile() { - prefabs.RemoveByFile(this); + Prefabs.RemoveByFile(this); } public sealed override void Sort() { - prefabs.SortAll(); + Prefabs.SortAll(); } } } \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/ItemAssemblyFile.cs b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/ItemAssemblyFile.cs index bff61b1bb..4186f99d6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/ItemAssemblyFile.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/ItemAssemblyFile.cs @@ -9,7 +9,7 @@ namespace Barotrauma protected override bool MatchesSingular(Identifier identifier) => identifier == "itemassembly"; protected override bool MatchesPlural(Identifier identifier) => identifier == "itemassemblies"; - protected override PrefabCollection prefabs => ItemAssemblyPrefab.Prefabs; + protected override PrefabCollection Prefabs => ItemAssemblyPrefab.Prefabs; protected override ItemAssemblyPrefab CreatePrefab(ContentXElement element) { return new ItemAssemblyPrefab(element, this); diff --git a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/ItemFile.cs b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/ItemFile.cs index 5065470c2..afb002440 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/ItemFile.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/ItemFile.cs @@ -9,7 +9,7 @@ namespace Barotrauma protected override bool MatchesSingular(Identifier identifier) => !MatchesPlural(identifier); protected override bool MatchesPlural(Identifier identifier) => identifier == "items"; - protected override PrefabCollection prefabs => ItemPrefab.Prefabs; + protected override PrefabCollection Prefabs => ItemPrefab.Prefabs; protected override ItemPrefab CreatePrefab(ContentXElement element) { return new ItemPrefab(element, this); diff --git a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/LevelObjectPrefabsFile.cs b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/LevelObjectPrefabsFile.cs index 228dca7cf..2f359a366 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/LevelObjectPrefabsFile.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/LevelObjectPrefabsFile.cs @@ -9,7 +9,7 @@ namespace Barotrauma protected override bool MatchesSingular(Identifier identifier) => !MatchesPlural(identifier); protected override bool MatchesPlural(Identifier identifier) => identifier == "levelobjects"; - protected override PrefabCollection prefabs => LevelObjectPrefab.Prefabs; + protected override PrefabCollection Prefabs => LevelObjectPrefab.Prefabs; protected override LevelObjectPrefab CreatePrefab(ContentXElement element) { return new LevelObjectPrefab(element, this); diff --git a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/LocationTypesFile.cs b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/LocationTypesFile.cs index cd3cc4c91..f9376f752 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/LocationTypesFile.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/LocationTypesFile.cs @@ -9,7 +9,7 @@ namespace Barotrauma protected override bool MatchesSingular(Identifier identifier) => !MatchesPlural(identifier); protected override bool MatchesPlural(Identifier identifier) => identifier == "locationtypes"; - protected override PrefabCollection prefabs => LocationType.Prefabs; + protected override PrefabCollection Prefabs => LocationType.Prefabs; protected override LocationType CreatePrefab(ContentXElement element) { return new LocationType(element, this); diff --git a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/MissionsFile.cs b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/MissionsFile.cs index 11efb2d0c..8907524de 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/MissionsFile.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/MissionsFile.cs @@ -23,7 +23,7 @@ namespace Barotrauma /*missionTypes.Any(t => identifier == t.Name) || identifier == "OutpostDestroyMission" || identifier == "OutpostRescueMission";*/ protected override bool MatchesPlural(Identifier identifier) => identifier == "missions"; - protected override PrefabCollection prefabs => MissionPrefab.Prefabs; + protected override PrefabCollection Prefabs => MissionPrefab.Prefabs; protected override MissionPrefab CreatePrefab(ContentXElement element) { return new MissionPrefab(element, this); diff --git a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/NPCSetsFile.cs b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/NPCSetsFile.cs index 85b2548d9..4433b6158 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/NPCSetsFile.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/NPCSetsFile.cs @@ -9,7 +9,7 @@ namespace Barotrauma protected override bool MatchesSingular(Identifier identifier) => identifier == "npcset"; protected override bool MatchesPlural(Identifier identifier) => identifier == "npcsets"; - protected override PrefabCollection prefabs => NPCSet.Sets; + protected override PrefabCollection Prefabs => NPCSet.Sets; protected override NPCSet CreatePrefab(ContentXElement element) { return new NPCSet(element, this); diff --git a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/OrdersFile.cs b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/OrdersFile.cs index 5699a0410..57273ced6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/OrdersFile.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/OrdersFile.cs @@ -42,7 +42,7 @@ namespace Barotrauma } else { - DebugConsole.ThrowError($"Invalid {GetType().Name} element: {parentElement.Name} in {Path}"); + DebugConsole.ThrowError($"OrdersFile: Invalid {GetType().Name} element: {parentElement.Name} in {Path}"); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/OutpostConfigFile.cs b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/OutpostConfigFile.cs index 1972243dc..1e2546859 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/OutpostConfigFile.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/OutpostConfigFile.cs @@ -9,7 +9,7 @@ namespace Barotrauma protected override bool MatchesSingular(Identifier identifier) => identifier == "OutpostConfig"; protected override bool MatchesPlural(Identifier identifier) => identifier == "OutpostGenerationParameters"; - protected override PrefabCollection prefabs => OutpostGenerationParams.OutpostParams; + protected override PrefabCollection Prefabs => OutpostGenerationParams.OutpostParams; protected override OutpostGenerationParams CreatePrefab(ContentXElement element) { return new OutpostGenerationParams(element, this); diff --git a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/ParticlesFile.cs b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/ParticlesFile.cs index de128d17c..3730156ee 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/ParticlesFile.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/ParticlesFile.cs @@ -14,7 +14,7 @@ namespace Barotrauma protected override bool MatchesSingular(Identifier identifier) => !MatchesPlural(identifier); protected override bool MatchesPlural(Identifier identifier) => identifier == "prefabs" || identifier == "particles"; - protected override PrefabCollection prefabs => ParticlePrefab.Prefabs; + protected override PrefabCollection Prefabs => ParticlePrefab.Prefabs; protected override ParticlePrefab CreatePrefab(ContentXElement element) { return new ParticlePrefab(element, this); diff --git a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/RandomEventsFile.cs b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/RandomEventsFile.cs index d4c5b1c43..6f636c4e8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/RandomEventsFile.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/RandomEventsFile.cs @@ -57,7 +57,7 @@ namespace Barotrauma } else { - DebugConsole.ThrowError($"Invalid {GetType().Name} element: {parentElement.Name} in {Path}"); + DebugConsole.ThrowError($"RandomEventsFile: Invalid {GetType().Name} element: {parentElement.Name} in {Path}"); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/RuinConfigFile.cs b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/RuinConfigFile.cs index 9a8de8fd1..08ba4d436 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/RuinConfigFile.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/RuinConfigFile.cs @@ -10,7 +10,7 @@ namespace Barotrauma protected override bool MatchesSingular(Identifier identifier) => identifier == "RuinConfig"; protected override bool MatchesPlural(Identifier identifier) => identifier == "RuinGenerationParameters"; - protected override PrefabCollection prefabs => RuinGenerationParams.RuinParams; + protected override PrefabCollection Prefabs => RuinGenerationParams.RuinParams; protected override RuinGenerationParams CreatePrefab(ContentXElement element) { return new RuinGenerationParams(element, this); diff --git a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/SoundsFile.cs b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/SoundsFile.cs index 57034f4d1..b14263066 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/SoundsFile.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/SoundsFile.cs @@ -11,7 +11,7 @@ namespace Barotrauma { public SoundsFile(ContentPackage contentPackage, ContentPath path) : base(contentPackage, path) { } - protected override PrefabCollection prefabs => SoundPrefab.Prefabs; + protected override PrefabCollection Prefabs => SoundPrefab.Prefabs; protected override SoundPrefab CreatePrefab(ContentXElement element) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/StartItemsFile.cs b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/StartItemsFile.cs new file mode 100644 index 000000000..072acfe63 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/StartItemsFile.cs @@ -0,0 +1,12 @@ +namespace Barotrauma +{ + sealed class StartItemsFile : GenericPrefabFile + { + public StartItemsFile(ContentPackage contentPackage, ContentPath path) : base(contentPackage, path) { } + + protected override bool MatchesSingular(Identifier identifier) => identifier == "itemset"; + protected override bool MatchesPlural(Identifier identifier) => identifier == "startitems"; + protected override PrefabCollection Prefabs => StartItemSet.Sets; + protected override StartItemSet CreatePrefab(ContentXElement element) => new StartItemSet(element, this); + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/StructureFile.cs b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/StructureFile.cs index b961311ab..bec8357b2 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/StructureFile.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/StructureFile.cs @@ -9,7 +9,7 @@ namespace Barotrauma protected override bool MatchesSingular(Identifier identifier) => !MatchesPlural(identifier); protected override bool MatchesPlural(Identifier identifier) => identifier == "prefabs" || identifier == "structures"; - protected override PrefabCollection prefabs => StructurePrefab.Prefabs; + protected override PrefabCollection Prefabs => StructurePrefab.Prefabs; protected override StructurePrefab CreatePrefab(ContentXElement element) { return new StructurePrefab(element, this); diff --git a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/TalentTreesFile.cs b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/TalentTreesFile.cs index 6ca1f9c68..cc25d8fc5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/TalentTreesFile.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/TalentTreesFile.cs @@ -9,7 +9,7 @@ namespace Barotrauma protected override bool MatchesSingular(Identifier identifier) => identifier == "talenttree"; protected override bool MatchesPlural(Identifier identifier) => identifier == "talenttrees"; - protected override PrefabCollection prefabs => TalentTree.JobTalentTrees; + protected override PrefabCollection Prefabs => TalentTree.JobTalentTrees; protected override TalentTree CreatePrefab(ContentXElement element) { return new TalentTree(element, this); diff --git a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/TalentsFile.cs b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/TalentsFile.cs index 1b5b05f4f..c234bd117 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/TalentsFile.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/TalentsFile.cs @@ -9,7 +9,7 @@ namespace Barotrauma protected override bool MatchesSingular(Identifier identifier) => identifier == "talent"; protected override bool MatchesPlural(Identifier identifier) => identifier == "talents"; - protected override PrefabCollection prefabs => TalentPrefab.TalentPrefabs; + protected override PrefabCollection Prefabs => TalentPrefab.TalentPrefabs; protected override TalentPrefab CreatePrefab(ContentXElement element) { return new TalentPrefab(element, this); diff --git a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/TraitorMissionsFile.cs b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/TraitorMissionsFile.cs index 3a58364e0..a43c10379 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/TraitorMissionsFile.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/TraitorMissionsFile.cs @@ -17,7 +17,7 @@ namespace Barotrauma protected override bool MatchesSingular(Identifier identifier) => identifier == "TraitorMission"; protected override bool MatchesPlural(Identifier identifier) => identifier == "TraitorMissions"; - protected override PrefabCollection prefabs => PrefabType.Prefabs; + protected override PrefabCollection Prefabs => PrefabType.Prefabs; protected override PrefabType CreatePrefab(ContentXElement element) { return new PrefabType(element, this); diff --git a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/UpgradeModulesFile.cs b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/UpgradeModulesFile.cs index 61de9e96b..91e1a8d47 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/UpgradeModulesFile.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/UpgradeModulesFile.cs @@ -14,7 +14,7 @@ namespace Barotrauma protected override bool MatchesPlural(Identifier identifier) => identifier == "upgrademodules"; - protected override PrefabCollection prefabs => UpgradeContentPrefab.PrefabsAndCategories; + protected override PrefabCollection Prefabs => UpgradeContentPrefab.PrefabsAndCategories; protected override UpgradeContentPrefab CreatePrefab(ContentXElement element) { Identifier elemName = element.NameAsIdentifier(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/WreckAIConfigFile.cs b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/WreckAIConfigFile.cs index 54a445ff0..be1c2ef5c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/WreckAIConfigFile.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/WreckAIConfigFile.cs @@ -9,7 +9,7 @@ namespace Barotrauma protected override bool MatchesSingular(Identifier identifier) => identifier == "wreckaiconfig"; protected override bool MatchesPlural(Identifier identifier) => identifier == "wreckaiconfigs"; - protected override PrefabCollection prefabs => WreckAIConfig.Prefabs; + protected override PrefabCollection Prefabs => WreckAIConfig.Prefabs; protected override WreckAIConfig CreatePrefab(ContentXElement element) { return new WreckAIConfig(element, this); diff --git a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentPackage/ContentPackage.cs b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentPackage/ContentPackage.cs index eba749c54..da7f6be5a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentPackage/ContentPackage.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentPackage/ContentPackage.cs @@ -14,8 +14,7 @@ namespace Barotrauma { public abstract class ContentPackage { - #warning TODO: make this independent of the current version - public static readonly Version MinimumHashCompatibleVersion = GameMain.Version; + public static readonly Version MinimumHashCompatibleVersion = new Version(0, 17, 16, 0); public const string LocalModsDir = "LocalMods"; public static readonly string WorkshopModsDir = Barotrauma.IO.Path.Combine( diff --git a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentPath.cs b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentPath.cs index d105e09cc..b7388bb2b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentPath.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentPath.cs @@ -49,7 +49,10 @@ namespace Barotrauma .Replace(string.Format(OtherModDirFmt, ContentPackage.SteamWorkshopId.ToString(CultureInfo.InvariantCulture)), modPath, StringComparison.OrdinalIgnoreCase); } } - var allPackages = ContentPackageManager.EnabledPackages.All; + var allPackages = ContentPackageManager.AllPackages; +#if CLIENT + if (GameMain.ModDownloadScreen?.DownloadedPackages != null) { allPackages = allPackages.Concat(GameMain.ModDownloadScreen.DownloadedPackages); } +#endif foreach (Identifier otherModName in otherMods) { if (!UInt64.TryParse(otherModName.Value, out UInt64 workshopId)) { workshopId = 0; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentXElement.cs b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentXElement.cs index 5711e7769..f5dba0b3d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentXElement.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentXElement.cs @@ -51,7 +51,7 @@ namespace Barotrauma => Element.Descendants().Select(e => new ContentXElement(ContentPackage, e)); public IEnumerable GetChildElements(string name) - => Elements().Where(e => string.Equals(name, e.Name.LocalName, StringComparison.CurrentCultureIgnoreCase)); + => Elements().Where(e => string.Equals(name, e.Name.LocalName, StringComparison.InvariantCultureIgnoreCase)); public XAttribute? GetAttribute(string name) => Element.GetAttribute(name); diff --git a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/MissingContentPackageException.cs b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/MissingContentPackageException.cs index f6fb91198..2c6d0df5e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/MissingContentPackageException.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/MissingContentPackageException.cs @@ -11,7 +11,7 @@ namespace Barotrauma { Message = $"\"{whoAsked?.Name ?? "[NULL]"}\" depends on a package " + $"with name or ID \"{missingPackage ?? "[NULL]"}\" " + - $"that is not currently enabled."; + $"that is not currently installed."; } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs b/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs index afa6c9758..3369bb456 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs @@ -780,7 +780,7 @@ namespace Barotrauma return; } GameMain.GameSession.EventManager.ActiveEvents.Add(newEvent); - newEvent.Init(true); + newEvent.Init(); NewMessage($"Initialized event {eventPrefab.Identifier}", Color.Aqua); return; } @@ -1829,6 +1829,17 @@ namespace Barotrauma })); #endif + commands.Add(new Command("startitems|startitemset", "start item set identifier", (string[] args) => + { + if (args.Length == 0) + { + ThrowError($"No start item set identifier defined!"); + return; + } + AutoItemPlacer.StartItemSet = args[0].ToIdentifier(); + NewMessage($"Start item set changed to \"{AutoItemPlacer.StartItemSet}\""); + }, isCheat: false)); + //"dummy commands" that only exist so that the server can give clients permissions to use them //TODO: alphabetical order? commands.Add(new Command("control", "control [character name]: Start controlling the specified character (client-only).", null, () => diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/ArtifactEvent.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/ArtifactEvent.cs index 3c8e23fd4..2275bcd06 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/ArtifactEvent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/ArtifactEvent.cs @@ -53,8 +53,9 @@ namespace Barotrauma } } - public override void Init(bool affectSubImmediately) + public override void Init(EventSet parentSet) { + base.Init(parentSet); spawnPos = Level.Loaded.GetRandomItemPos( (Rand.Value(Rand.RandSync.ServerAndClient) < 0.5f) ? Level.PositionType.MainPath | Level.PositionType.SidePath : @@ -111,7 +112,7 @@ namespace Barotrauma case 1: if (!Submarine.MainSub.AtEndExit && !Submarine.MainSub.AtStartExit) return; - Finished(); + Finish(); state = 2; break; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Event.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Event.cs index be1857c50..192664d17 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Event.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Event.cs @@ -5,13 +5,16 @@ using System.Collections.Generic; namespace Barotrauma { class Event - { + { + public event Action Finished; protected bool isFinished; protected readonly EventPrefab prefab; public EventPrefab Prefab => prefab; + public EventSet ParentSet { get; private set; } + public Func SpawnPosFilter; public bool IsFinished @@ -42,23 +45,20 @@ namespace Barotrauma yield break; } - public virtual void Init(bool affectSubImmediately) + public virtual void Init(EventSet parentSet = null) { + ParentSet = parentSet; } public virtual void Update(float deltaTime) { } - public virtual void Finished() + public virtual void Finish() { isFinished = true; - } - - public virtual bool CanAffectSubImmediately(Level level) - { - return true; - } + Finished?.Invoke(); + } public virtual bool LevelMeetsRequirements() { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs index 8bbb511bf..c295eaa49 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs @@ -117,6 +117,8 @@ namespace Barotrauma public bool Enabled = true; + private MTRandom rand; + public void StartRound(Level level) { this.level = level; @@ -147,7 +149,7 @@ namespace Barotrauma seed ^= ToolBox.IdentifierToInt(previousEvent.Identifier); } } - MTRandom rand = new MTRandom(seed); + rand = new MTRandom(seed); EventSet initialEventSet = SelectRandomEvents(EventSet.Prefabs.ToList(), requireCampaignSet: GameMain.GameSession?.GameMode is CampaignMode, rand); EventSet additiveSet = null; @@ -159,12 +161,12 @@ namespace Barotrauma if (initialEventSet != null) { pendingEventSets.Add(initialEventSet); - CreateEvents(initialEventSet, rand); + CreateEvents(initialEventSet); } if (additiveSet != null) { pendingEventSets.Add(additiveSet); - CreateEvents(additiveSet, rand); + CreateEvents(additiveSet); } if (level?.LevelData?.Type == LevelData.LevelType.Outpost) @@ -183,7 +185,7 @@ namespace Barotrauma if (unlockPathEventPrefab != null) { var newEvent = unlockPathEventPrefab.CreateInstance(); - newEvent.Init(true); + newEvent.Init(); ActiveEvents.Add(newEvent); } else @@ -362,8 +364,9 @@ namespace Barotrauma return retVal; } - private void CreateEvents(EventSet eventSet, Random rand) + private void CreateEvents(EventSet eventSet) { + selectedEvents.Remove(eventSet); if (level == null) { return; } if (level.LevelData.HasHuntingGrounds && eventSet.DisableInHuntingGrounds) { return; } DebugConsole.NewMessage($"Loading event set {eventSet.Identifier}", Color.LightBlue, debugOnly: true); @@ -421,7 +424,7 @@ namespace Barotrauma var newEvent = eventPrefab.CreateInstance(); if (newEvent == null) { continue; } - newEvent.Init(true); + newEvent.Init(eventSet); if (i < spawnPosFilter.Count) { newEvent.SpawnPosFilter = spawnPosFilter[i]; } DebugConsole.NewMessage($"Initialized event {newEvent}", debugOnly: true); if (!selectedEvents.ContainsKey(eventSet)) @@ -438,7 +441,7 @@ namespace Barotrauma var newEventSet = SelectRandomEvents(eventSet.ChildSets, random: rand); if (newEventSet != null) { - CreateEvents(newEventSet, rand); + CreateEvents(newEventSet); } } } @@ -451,7 +454,7 @@ namespace Barotrauma var eventPrefab = ToolBox.SelectWeightedRandom(eventPrefabs.Where(isPrefabSuitable), e => e.Commonness, rand); var newEvent = eventPrefab.CreateInstance(); if (newEvent == null) { continue; } - newEvent.Init(true); + newEvent.Init(eventSet); DebugConsole.NewMessage($"Initialized event {newEvent}", debugOnly: true); if (!selectedEvents.ContainsKey(eventSet)) { @@ -465,7 +468,7 @@ namespace Barotrauma { if (!IsValidForLevel(childEventSet, level)) { continue; } if (location != null && !IsValidForLocation(childEventSet, location)) { continue; } - CreateEvents(childEventSet, rand); + CreateEvents(childEventSet); } } } @@ -666,6 +669,14 @@ namespace Barotrauma { eventCoolDown = settings.EventCooldown; } + if (eventSet.ResetTime > 0) + { + ev.Finished += () => + { + pendingEventSets.Add(eventSet); + CreateEvents(eventSet); + }; + } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventSet.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventSet.cs index 150aee8b6..ebd615008 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventSet.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventSet.cs @@ -58,7 +58,7 @@ namespace Barotrauma } #endif - public static List GetAllEventPrefabs() + public static List GetAllEventPrefabs() { List eventPrefabs = EventPrefab.Prefabs.ToList(); foreach (var eventSet in Prefabs) @@ -118,6 +118,8 @@ namespace Barotrauma public readonly float DefaultCommonness; public readonly ImmutableDictionary OverrideCommonness; + public readonly float ResetTime; + public readonly struct SubEventPrefab { public SubEventPrefab(Either prefabOrIdentifiers, float? commonness, float? probability) @@ -244,6 +246,7 @@ namespace Barotrauma OncePerOutpost = element.GetAttributeBool("onceperoutpost", false); TriggerEventCooldown = element.GetAttributeBool("triggereventcooldown", true); IsCampaignSet = element.GetAttributeBool("campaign", LevelType == LevelData.LevelType.Outpost || (parentSet?.IsCampaignSet ?? false)); + ResetTime = element.GetAttributeFloat("resettime", 0); DefaultCommonness = 1.0f; foreach (var subElement in element.Elements()) @@ -454,7 +457,6 @@ namespace Barotrauma { childSet.Dispose(); } - } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/MalfunctionEvent.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/MalfunctionEvent.cs index f716173e0..47c73a3eb 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/MalfunctionEvent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/MalfunctionEvent.cs @@ -39,13 +39,9 @@ namespace Barotrauma targetItemIdentifiers = prefab.ConfigElement.GetAttributeIdentifierArray("itemidentifiers", Array.Empty()); } - public override bool CanAffectSubImmediately(Level level) - { - return Item.ItemList.Count(i => i.Condition > 0.0f && targetItemIdentifiers.Contains(i.Prefab.Identifier)) >= maxItemAmount; - } - - public override void Init(bool affectSubImmediately) + public override void Init(EventSet parentSet) { + base.Init(parentSet); var matchingItems = Item.ItemList.FindAll(i => i.Condition > 0.0f && targetItemIdentifiers.Contains(i.Prefab.Identifier)); int itemAmount = Rand.Range(minItemAmount, maxItemAmount, Rand.RandSync.ServerAndClient); for (int i = 0; i < itemAmount; i++) @@ -60,7 +56,7 @@ namespace Barotrauma if (isFinished) return; if (targetItems.Count == 0 || timer >= duration) { - Finished(); + Finish(); return; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs index dc062b81e..4975202c3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs @@ -323,7 +323,7 @@ namespace Barotrauma { var newEvent = eventPrefab.CreateInstance(); GameMain.GameSession.EventManager.ActiveEvents.Add(newEvent); - newEvent.Init(true); + newEvent.Init(); } } @@ -382,7 +382,8 @@ namespace Barotrauma #if SERVER totalReward = DistributeRewardsToCrew(GameSession.GetSessionCrewCharacters(CharacterType.Player), totalReward); #endif - if (totalReward > 0) + bool isSingleplayerOrServer = GameMain.IsSingleplayer || GameMain.NetworkMember is { IsServer: true }; + if (isSingleplayerOrServer && totalReward > 0) { campaign.Bank.Give(totalReward); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MissionPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MissionPrefab.cs index 796ae18b0..1be2ee4d3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MissionPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MissionPrefab.cs @@ -146,8 +146,15 @@ namespace Barotrauma tags = element.GetAttributeStringArray("tags", Array.Empty(), convertToLowerInvariant: true); - Name = TextManager.Get($"MissionName.{TextIdentifier}").Fallback(element.GetAttributeString("name", "")); - Description = TextManager.Get($"MissionDescription.{TextIdentifier}").Fallback(element.GetAttributeString("description", "")); + Name = + TextManager.Get($"MissionName.{TextIdentifier}") + .Fallback(TextManager.Get(element.GetAttributeString("name", ""))) + .Fallback(element.GetAttributeString("name", "")); + Description = + TextManager.Get($"MissionDescription.{TextIdentifier}") + .Fallback(TextManager.Get(element.GetAttributeString("description", ""))) + .Fallback(element.GetAttributeString("description", "")); + Reward = element.GetAttributeInt("reward", 1); AllowRetry = element.GetAttributeBool("allowretry", false); IsSideObjective = element.GetAttributeBool("sideobjective", false); @@ -160,10 +167,15 @@ namespace Barotrauma Difficulty = Math.Clamp(difficulty, MinDifficulty, MaxDifficulty); } - SuccessMessage = TextManager.Get($"MissionSuccess.{TextIdentifier}").Fallback(element.GetAttributeString("successmessage", "Mission completed successfully")); - FailureMessage = TextManager.Get($"MissionFailure.{TextIdentifier}").Fallback( - TextManager.Get("missionfailed")).Fallback( - GameSettings.CurrentConfig.Language == TextManager.DefaultLanguage ? element.GetAttributeString("failuremessage", "") : ""); + SuccessMessage = + TextManager.Get($"MissionSuccess.{TextIdentifier}") + .Fallback(TextManager.Get(element.GetAttributeString("successmessage", ""))) + .Fallback(element.GetAttributeString("successmessage", "Mission completed successfully")); + FailureMessage = + TextManager.Get($"MissionFailure.{TextIdentifier}") + .Fallback(TextManager.Get(element.GetAttributeString("missionfailed", ""))) + .Fallback(TextManager.Get("missionfailed")) + .Fallback(GameSettings.CurrentConfig.Language == TextManager.DefaultLanguage ? element.GetAttributeString("failuremessage", "") : ""); string sonarLabelTag = element.GetAttributeString("sonarlabel", ""); @@ -208,8 +220,14 @@ namespace Barotrauma headers.Add(string.Empty); messages.Add(string.Empty); } - headers[messageIndex] = TextManager.Get($"MissionHeader{messageIndex}.{TextIdentifier}").Fallback(subElement.GetAttributeString("header", "")); - messages[messageIndex] = TextManager.Get($"MissionMessage{messageIndex}.{TextIdentifier}").Fallback(subElement.GetAttributeString("text", "")); + headers[messageIndex] = + TextManager.Get($"MissionHeader{messageIndex}.{TextIdentifier}") + .Fallback(TextManager.Get(subElement.GetAttributeString("header", ""))) + .Fallback(subElement.GetAttributeString("header", "")); + messages[messageIndex] = + TextManager.Get($"MissionMessage{messageIndex}.{TextIdentifier}") + .Fallback(TextManager.Get(subElement.GetAttributeString("text", ""))) + .Fallback(subElement.GetAttributeString("text", "")); messageIndex++; break; case "locationtype": diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/PirateMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/PirateMission.cs index 6e0b0abc9..20d26fbf5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/PirateMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/PirateMission.cs @@ -270,7 +270,7 @@ namespace Barotrauma foreach (Item item in spawnedCharacter.Inventory.AllItems) { - if (item?.Prefab.Identifier == "idcard") + if (item?.GetComponent() != null) { item.AddTag("id_pirate"); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/MonsterEvent.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/MonsterEvent.cs index 4093d6923..66bd8857f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/MonsterEvent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/MonsterEvent.cs @@ -16,6 +16,8 @@ namespace Barotrauma private readonly float scatter; private readonly float offset; private readonly float delayBetweenSpawns; + private float resetTime; + private float resetTimer; private Vector2? spawnPos; @@ -24,7 +26,7 @@ namespace Barotrauma public readonly Level.PositionType SpawnPosType; private readonly string spawnPointTag; - private bool spawnPending; + private bool spawnPending, spawnReady; public readonly int MaxAmountPerLevel = int.MaxValue; @@ -96,6 +98,7 @@ namespace Barotrauma offset = prefab.ConfigElement.GetAttributeFloat("offset", 0); scatter = Math.Clamp(prefab.ConfigElement.GetAttributeFloat("scatter", 500), 0, 3000); delayBetweenSpawns = prefab.ConfigElement.GetAttributeFloat("delaybetweenspawns", 0.1f); + resetTime = prefab.ConfigElement.GetAttributeFloat("resettime", 0); if (GameMain.NetworkMember != null) { @@ -131,14 +134,14 @@ namespace Barotrauma } } - public override bool CanAffectSubImmediately(Level level) - { - float maxRange = Sonar.DefaultSonarRange * 0.8f; - return GetAvailableSpawnPositions().Any(p => Vector2.DistanceSquared(p.Position.ToVector2(), GetReferenceSub().WorldPosition) < maxRange * maxRange); - } - - public override void Init(bool affectSubImmediately) + public override void Init(EventSet parentSet) { + base.Init(parentSet); + if (parentSet != null && resetTime == 0) + { + // Use the parent reset time only if there's no reset time defined for the event. + resetTime = parentSet.ResetTime; + } if (GameSettings.CurrentConfig.VerboseLogging) { DebugConsole.NewMessage("Initialized MonsterEvent (" + SpeciesName + ")", Color.White); @@ -199,7 +202,7 @@ namespace Barotrauma { //no suitable position found, disable the event spawnPos = null; - Finished(); + Finish(); return; } Submarine refSub = GetReferenceSub(); @@ -267,22 +270,17 @@ namespace Barotrauma if (!isRuinOrWreck) { float minDistance = 20000; - var refSub = GetReferenceSub(); - availablePositions.RemoveAll(p => Vector2.DistanceSquared(refSub.WorldPosition, p.Position.ToVector2()) < minDistance * minDistance); - if (Submarine.MainSubs.Length > 1) + for (int i = 0; i < Submarine.MainSubs.Length; i++) { - for (int i = 1; i < Submarine.MainSubs.Length; i++) - { - if (Submarine.MainSubs[i] == null) { continue; } - availablePositions.RemoveAll(p => Vector2.DistanceSquared(Submarine.MainSubs[i].WorldPosition, p.Position.ToVector2()) < minDistance * minDistance); - } + if (Submarine.MainSubs[i] == null) { continue; } + availablePositions.RemoveAll(p => Vector2.DistanceSquared(Submarine.MainSubs[i].WorldPosition, p.Position.ToVector2()) < minDistance * minDistance); } } if (availablePositions.None()) { //no suitable position found, disable the event spawnPos = null; - Finished(); + Finish(); return; } chosenPosition = availablePositions.GetRandomUnsynced(); @@ -306,7 +304,7 @@ namespace Barotrauma { //no suitable position found, disable the event spawnPos = null; - Finished(); + Finish(); return; } } @@ -344,7 +342,7 @@ namespace Barotrauma { //no suitable position found, disable the event spawnPos = null; - Finished(); + Finish(); return; } } @@ -352,20 +350,42 @@ namespace Barotrauma } } - private float GetMinDistanceToSub(Submarine submarine) + private float GetMinDistanceToSub(Submarine submarine) { - return Math.Max(Math.Max(submarine.Borders.Width, submarine.Borders.Height), Sonar.DefaultSonarRange * 0.9f); + float minDist = Math.Max(Math.Max(submarine.Borders.Width, submarine.Borders.Height), Sonar.DefaultSonarRange * 0.9f); + if (SpawnPosType.HasFlag(Level.PositionType.Abyss)) + { + minDist *= 2; + } + return minDist; } public override void Update(float deltaTime) { if (disallowed) { - Finished(); + Finish(); return; } - if (isFinished) { return; } + if (resetTimer > 0) + { + resetTimer -= deltaTime; + if (resetTimer <= 0) + { + if (ParentSet?.ResetTime > 0) + { + // If parent has reset time defined, the set is recreated. Otherwise we'll just reset this event. + Finish(); + } + else + { + spawnReady = false; + spawnPos = null; + } + } + return; + } if (spawnPos == null) { @@ -373,7 +393,11 @@ namespace Barotrauma { if (Character.CharacterList.Count(c => c.SpeciesName == SpeciesName) >= MaxAmountPerLevel) { - disallowed = true; + // If the event is set to reset, let's just wait until the old corpse is removed (after being disabled). + if (resetTime == 0) + { + disallowed = true; + } return; } } @@ -384,9 +408,14 @@ namespace Barotrauma spawnPending = true; } - bool spawnReady = false; if (spawnPending) { + System.Diagnostics.Debug.Assert(spawnPos.HasValue); + if (spawnPos == null) + { + Finish(); + return; + } //wait until there are no submarines at the spawnpos if (SpawnPosType.HasFlag(Level.PositionType.MainPath) || SpawnPosType.HasFlag(Level.PositionType.SidePath) || SpawnPosType.HasFlag(Level.PositionType.Abyss)) { @@ -554,28 +583,24 @@ namespace Barotrauma } } - if (!spawnReady) { return; } - - Entity targetEntity = Submarine.FindClosest(GameMain.GameScreen.Cam.WorldViewCenter); -#if CLIENT - if (Character.Controlled != null) { targetEntity = Character.Controlled; } -#endif - - bool monstersDead = true; - foreach (Character monster in monsters) + if (spawnReady) { - if (!monster.IsDead) + if (monsters.None()) { - monstersDead = false; - - if (targetEntity != null && Vector2.DistanceSquared(monster.WorldPosition, targetEntity.WorldPosition) < 5000.0f * 5000.0f) + Finish(); + } + else if (monsters.All(m => m.IsDead)) + { + if (resetTime > 0) { - break; + resetTimer = resetTime; + } + else + { + Finish(); } } } - - if (monstersDead) { Finished(); } } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/ScriptedEvent.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/ScriptedEvent.cs index c41676ff9..49631f810 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/ScriptedEvent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/ScriptedEvent.cs @@ -173,14 +173,14 @@ namespace Barotrauma if (!Actions.Any()) { - Finished(); + Finish(); return; } var currentAction = Actions[CurrentActionIndex]; if (!currentAction.CanBeFinished()) { - Finished(); + Finish(); return; } @@ -207,7 +207,7 @@ namespace Barotrauma if (CurrentActionIndex >= Actions.Count || CurrentActionIndex < 0) { - Finished(); + Finish(); } } else @@ -232,9 +232,9 @@ namespace Barotrauma return false; } - public override void Finished() + public override void Finish() { - base.Finished(); + base.Finish(); GameAnalyticsManager.AddDesignEvent($"ScriptedEvent:{prefab.Identifier}:Finished:{CurrentActionIndex}"); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/AutoItemPlacer.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/AutoItemPlacer.cs index 7311d6c05..733238123 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/AutoItemPlacer.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/AutoItemPlacer.cs @@ -3,36 +3,32 @@ using System; using System.Collections.Generic; using System.Linq; using Barotrauma.Extensions; +using Microsoft.Xna.Framework; namespace Barotrauma { - #warning TODO: This class needs some changes: - // - We shouldn't be iterating over MapEntityPrefab.List. It has no guarantee of any sort of order and becomes entirely unpredictable once you start adding mods. - // - Note: iterating over ItemPrefab.Prefabs would also be incorrect. Sorting by UintIdentifier is necessary for determinism. - // - SpawnItems and SpawnItem are named incorrectly. static class AutoItemPlacer { 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() + public static void SpawnItems() { if (GameMain.NetworkMember != null && !GameMain.NetworkMember.IsServer) { return; } - for (int i = 0; i < Submarine.MainSubs.Length; i++) + bool skipMainSubs = GameMain.GameSession.GameMode is CampaignMode { IsFirstRound: false }; + if (!skipMainSubs) { - if (Submarine.MainSubs[i] == null || Submarine.MainSubs[i].Info.InitialSuppliesSpawned) { continue; } - List subs = new List() { Submarine.MainSubs[i] }; - subs.AddRange(Submarine.MainSubs[i].DockedTo.Where(d => !d.Info.IsOutpost)); - Place(subs); - subs.ForEach(s => s.Info.InitialSuppliesSpawned = true); + for (int i = 0; i < Submarine.MainSubs.Length; i++) + { + var sub = Submarine.MainSubs[i]; + if (sub == null || sub.Info.InitialSuppliesSpawned) { continue; } + SpawnStartItems(sub); + var subs = sub.GetConnectedSubs().Where(s => s.TeamID == sub.TeamID); + CreateAndPlace(subs); + subs.ForEach(s => s.Info.InitialSuppliesSpawned = true); + } } - float difficultyModifier = GetLevelDifficultyModifier(); foreach (var sub in Submarine.Loaded) { if (sub.Info.Type == SubmarineType.Player || @@ -42,33 +38,93 @@ namespace Barotrauma { continue; } - Place(sub.ToEnumerable(), difficultyModifier: difficultyModifier); + if (sub.Info.InitialSuppliesSpawned) { continue; } + CreateAndPlace(sub.ToEnumerable()); + sub.Info.InitialSuppliesSpawned = true; } if (Level.Loaded?.StartOutpost != null && Level.Loaded.Type == LevelData.LevelType.Outpost) { - Rand.SetSyncedSeed(ToolBox.StringToInt(Level.Loaded.StartOutpost.Info.Name)); - Place(Level.Loaded.StartOutpost.ToEnumerable()); + var sub = Level.Loaded.StartOutpost; + if (!sub.Info.InitialSuppliesSpawned) + { + Rand.SetSyncedSeed(ToolBox.StringToInt(sub.Info.Name)); + CreateAndPlace(sub.ToEnumerable()); + sub.Info.InitialSuppliesSpawned = true; + } } } - private const float MaxDifficultyModifier = 0.2f; - - /// - /// Spawn probability of loot is modified by difficulty, -20% less loot at 0% difficulty and +20% loot at 100% difficulty. - /// - private static float GetLevelDifficultyModifier() - { - return Math.Clamp(Level.Loaded?.Difficulty is float difficulty ? (difficulty / 100f) * (MaxDifficultyModifier * 2) - MaxDifficultyModifier : DefaultDifficultyModifier, -MaxDifficultyModifier, MaxDifficultyModifier); - } - public static void RegenerateLoot(Submarine sub, ItemContainer regeneratedContainer) { - // Level difficulty currently doesn't affect regenerated loot for the sake of simplicity - Place(sub.ToEnumerable(), regeneratedContainer: regeneratedContainer); + CreateAndPlace(sub.ToEnumerable(), regeneratedContainer: regeneratedContainer); } - private static void Place(IEnumerable subs, ItemContainer regeneratedContainer = null, float difficultyModifier = DefaultDifficultyModifier) + public static Identifier StartItemSet = new Identifier("normal"); + private static void SpawnStartItems(Submarine sub) + { + if (!Barotrauma.StartItemSet.Sets.TryGet(StartItemSet, out StartItemSet itemSet)) + { + DebugConsole.AddWarning($"Couldn't find a start item set matching the identifier \"{StartItemSet}\"!"); + return; + } + WayPoint wp = WayPoint.GetRandom(SpawnType.Cargo, null, sub); + ISpatialEntity initialSpawnPos; + if (wp?.CurrentHull == null) + { + var spawnHull = Hull.HullList.Where(h => h.Submarine == sub && !h.IsWetRoom).GetRandomUnsynced(); + if (spawnHull == null) + { + DebugConsole.AddWarning($"Failed to spawn start items in the sub. No cargo waypoint or dry hulls found to spawn the items in."); + return; + } + initialSpawnPos = spawnHull; + + } + else + { + initialSpawnPos = wp; + } + var newItems = new List(); + foreach (var startItem in itemSet.Items) + { + if (!ItemPrefab.Prefabs.TryGet(startItem.Item, out ItemPrefab itemPrefab)) + { + DebugConsole.AddWarning($"Cannot find a start item with with the identifier \"{startItem.Item}\""); + continue; + } + for (int i = 0; i < startItem.Amount; i++) + { + var item = new Item(itemPrefab, initialSpawnPos.Position, sub, callOnItemLoaded: false); + // Is this necessary? + foreach (WifiComponent wifiComponent in item.GetComponents()) + { + wifiComponent.TeamID = sub.TeamID; + } + newItems.Add(item); + } + } + var cargoContainers = new List(); + foreach (var item in newItems) + { +#if SERVER + Entity.Spawner.CreateNetworkEvent(new EntitySpawner.SpawnEntity(item)); +#endif + foreach (ItemComponent ic in item.Components) + { + ic.OnItemLoaded(); + } + var container = sub.FindContainerFor(item, onlyPrimary: true); + if (container == null) + { + var cargoContainer = CargoManager.GetOrCreateCargoContainerFor(item.Prefab, initialSpawnPos, ref cargoContainers); + container = cargoContainer?.Item; + } + container?.OwnInventory.TryPutItem(item, user: null); + } + } + + private static void CreateAndPlace(IEnumerable subs, ItemContainer regeneratedContainer = null) { if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { @@ -76,7 +132,7 @@ namespace Barotrauma return; } - List spawnedItems = new List(100); + List itemsToSpawn = new List(100); int itemCountApprox = MapEntityPrefab.List.Count() / 3; var containers = new List(70 + 30 * subs.Count()); @@ -100,11 +156,11 @@ namespace Barotrauma containers.Shuffle(Rand.RandSync.ServerAndClient); } - foreach (ItemPrefab ip in ItemPrefab.Prefabs) + var itemPrefabs = ItemPrefab.Prefabs.OrderBy(p => p.UintIdentifier); + foreach (ItemPrefab ip in itemPrefabs) { if (!ip.PreferredContainers.Any()) { continue; } - if (ip.ConfigElement.Elements().Any(e => string.Equals(e.Name.ToString(), typeof(ItemContainer).Name.ToString(), StringComparison.OrdinalIgnoreCase)) && - ItemPrefab.Prefabs.Any(ip2 => CanSpawnIn(ip2, ip))) + if (ip.ConfigElement.Elements().Any(e => string.Equals(e.Name.ToString(), typeof(ItemContainer).Name.ToString(), StringComparison.OrdinalIgnoreCase)) && itemPrefabs.Any(ip2 => CanSpawnIn(ip2, ip))) { prefabsItemsCanSpawnIn.Add(ip); } @@ -141,9 +197,9 @@ namespace Barotrauma { var subNames = subs.Select(s => s.Info.Name).ToList(); DebugConsole.NewMessage($"Automatically placed items in { string.Join(", ", subNames) }:"); - foreach (string itemName in spawnedItems.Select(it => it.Name).Distinct()) + foreach (string itemName in itemsToSpawn.Select(it => it.Name).Distinct()) { - DebugConsole.NewMessage(" - " + itemName + " x" + spawnedItems.Count(it => it.Name == itemName)); + DebugConsole.NewMessage(" - " + itemName + " x" + itemsToSpawn.Count(it => it.Name == itemName)); } } @@ -153,24 +209,28 @@ namespace Barotrauma { foreach (Location.TakenItem takenItem in GameMain.GameSession.StartLocation.TakenItems) { - var matchingItem = spawnedItems.Find(it => takenItem.Matches(it)); + var matchingItem = itemsToSpawn.Find(it => takenItem.Matches(it)); if (matchingItem == null) { continue; } - var containedItems = spawnedItems.FindAll(it => it.ParentInventory?.Owner == matchingItem); + if (OutputDebugInfo) + { + DebugConsole.NewMessage($"Removing the stolen item: {matchingItem.Prefab.Identifier} ({matchingItem.ID})"); + } + var containedItems = itemsToSpawn.FindAll(it => it.ParentInventory?.Owner == matchingItem); matchingItem.Remove(); - spawnedItems.Remove(matchingItem); + itemsToSpawn.Remove(matchingItem); foreach (Item containedItem in containedItems) { containedItem.Remove(); - spawnedItems.Remove(containedItem); + itemsToSpawn.Remove(containedItem); } } } - foreach (Item spawnedItem in spawnedItems) + foreach (Item item in itemsToSpawn) { #if SERVER - Entity.Spawner.CreateNetworkEvent(new EntitySpawner.SpawnEntity(spawnedItem)); + Entity.Spawner.CreateNetworkEvent(new EntitySpawner.SpawnEntity(item)); #endif - foreach (ItemComponent ic in spawnedItem.Components) + foreach (ItemComponent ic in item.Components) { ic.OnItemLoaded(); } @@ -186,9 +246,12 @@ namespace Barotrauma return false; } bool success = false; + bool isCampaign = GameMain.GameSession?.GameMode is CampaignMode; foreach (PreferredContainer preferredContainer in itemPrefab.PreferredContainers) { - if (preferredContainer.SpawnProbability <= 0.0f || preferredContainer.MaxAmount <= 0) { continue; } + if (preferredContainer.CampaignOnly && !isCampaign) { continue; } + if (preferredContainer.NotCampaign && isCampaign) { continue; } + if (preferredContainer.SpawnProbability <= 0.0f || preferredContainer.MaxAmount <= 0 && preferredContainer.Amount <= 0) { continue; } validContainers = GetValidContainers(preferredContainer, containers, validContainers, primary: true); if (validContainers.None()) { @@ -196,10 +259,10 @@ namespace Barotrauma } foreach (var validContainer in validContainers) { - var newItems = SpawnItem(itemPrefab, containers, validContainer, difficultyModifier); + var newItems = CreateItems(itemPrefab, containers, validContainer); if (newItems.Any()) { - spawnedItems.AddRange(newItems); + itemsToSpawn.AddRange(newItems); success = true; } } @@ -238,16 +301,20 @@ namespace Barotrauma (3, 0.0f), }; - private static List SpawnItem(ItemPrefab itemPrefab, List containers, KeyValuePair validContainer, float difficultyModifier) + private static List CreateItems(ItemPrefab itemPrefab, List containers, KeyValuePair validContainer) { - List spawnedItems = new List(); - if (Rand.Value(Rand.RandSync.ServerAndClient) > validContainer.Value.SpawnProbability * (1f + difficultyModifier)) { return spawnedItems; } + List newItems = new List(); + if (Rand.Value(Rand.RandSync.ServerAndClient) > validContainer.Value.SpawnProbability) { return newItems; } // Don't add dangerously reactive materials in thalamus wrecks if (validContainer.Key.Item.Submarine.WreckAI != null && itemPrefab.Tags.Contains("explodesinwater")) { - return spawnedItems; + return newItems; + } + int amount = validContainer.Value.Amount; + if (amount == 0) + { + amount = Rand.Range(validContainer.Value.MinAmount, validContainer.Value.MaxAmount + 1, Rand.RandSync.ServerAndClient); } - int amount = Rand.Range(validContainer.Value.MinAmount, validContainer.Value.MaxAmount + 1, Rand.RandSync.ServerAndClient); for (int i = 0; i < amount; i++) { if (validContainer.Key.Inventory.IsFull(takeStacksIntoAccount: true)) @@ -255,14 +322,12 @@ namespace Barotrauma containers.Remove(validContainer.Key); 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.ServerAndClient); + qualityCommonnesses.Select(q => q.commonness).ToList(), Rand.RandSync.ServerAndClient); if (!validContainer.Key.Inventory.CanBePut(itemPrefab, quality: quality)) { break; } var item = new Item(itemPrefab, validContainer.Key.Item.Position, validContainer.Key.Item.Submarine, callOnItemLoaded: false) { @@ -277,11 +342,11 @@ namespace Barotrauma { wifiComponent.TeamID = validContainer.Key.Item.Submarine.TeamID; } - spawnedItems.Add(item); + newItems.Add(item); validContainer.Key.Inventory.TryPutItem(item, null, createNetworkEvent: false); containers.AddRange(item.GetComponents()); } - return spawnedItems; + return newItems; } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/CargoManager.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/CargoManager.cs index 2492beea5..b14a19dde 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/CargoManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/CargoManager.cs @@ -22,14 +22,14 @@ namespace Barotrauma public int Quantity { get; set; } public bool? IsStoreComponentEnabled { get; set; } - public readonly int BuyerCharacterInfoId; + public readonly int BuyerCharacterInfoIdentifier; public PurchasedItem(ItemPrefab itemPrefab, int quantity, int buyerCharacterInfoId) { ItemPrefabIdentifier = itemPrefab.Identifier; Quantity = quantity; IsStoreComponentEnabled = null; - BuyerCharacterInfoId = buyerCharacterInfoId; + BuyerCharacterInfoIdentifier = buyerCharacterInfoId; } #if CLIENT @@ -44,7 +44,7 @@ namespace Barotrauma ItemPrefabIdentifier = itemPrefabId; Quantity = quantity; IsStoreComponentEnabled = null; - BuyerCharacterInfoId = buyer?.Character?.Info?.ID ?? Character.Controlled?.Info?.ID ?? 0; + BuyerCharacterInfoIdentifier = buyer?.Character?.Info?.GetIdentifier() ?? Character.Controlled?.Info?.GetIdentifier() ?? 0; } public override string ToString() @@ -284,11 +284,10 @@ namespace Barotrauma foreach (PurchasedItem item in newItems) { int itemValue = item.Quantity * buyValues[item.ItemPrefab]; + GameAnalyticsManager.AddMoneySpentEvent(itemValue, GameAnalyticsManager.MoneySink.Store, item.ItemPrefab.Identifier.Value); sb.Append($"\n - {item.ItemPrefab.Name} x{item.Quantity}"); price += itemValue; - } - GameServer.Log($"{NetworkMember.ClientLogName(client, client?.Name ?? "Unknown")} purchased {newItems.Count} item(s) for {TextManager.FormatCurrency(price)}{sb.ToString()}", ServerLog.MessageType.Money); } #endif @@ -317,7 +316,10 @@ namespace Barotrauma // Exchange money int itemValue = item.Quantity * buyValues[item.ItemPrefab]; campaign.TryPurchase(client, itemValue); - GameAnalyticsManager.AddMoneySpentEvent(itemValue, GameAnalyticsManager.MoneySink.Store, item.ItemPrefab.Identifier.Value); + if (GameMain.IsSingleplayer) + { + GameAnalyticsManager.AddMoneySpentEvent(itemValue, GameAnalyticsManager.MoneySink.Store, item.ItemPrefab.Identifier.Value); + } store.Balance += itemValue; if (removeFromCrate) { @@ -368,12 +370,13 @@ namespace Barotrauma public void CreatePurchasedItems() { + purchasedIDCards.Clear(); var items = new List(); foreach (var storeSpecificItems in PurchasedItems) { items.AddRange(storeSpecificItems.Value); } - CreateItems(items, Submarine.MainSub); + CreateItems(items, Submarine.MainSub, this); PurchasedItems.Clear(); OnPurchasedItemsChanged?.Invoke(); } @@ -407,7 +410,7 @@ namespace Barotrauma 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 (item.GetRootContainer() is Item rootContainer && rootContainer.HasTag("donttakeitems")) { return false; } + if (item.GetRootContainer() is Item rootContainer && rootContainer.HasTag("dontsellitems")) { return false; } return true; }).Distinct(); @@ -428,7 +431,7 @@ namespace Barotrauma if (!item.Prefab.CanBeSold) { return false; } if (item.SpawnedInCurrentOutpost) { return false; } if (!item.Prefab.AllowSellingWhenBroken && item.ConditionPercentage < 90.0f) { return false; } - if (confirmedItems.Any(ci => ci.Item == item)) { return false; } + if (confirmedItems != null && confirmedItems.Any(ci => ci.Item == item)) { return false; } if (UndeterminedSoldEntities.TryGetValue(item.Prefab, out int count)) { int newCount = count - 1; @@ -448,13 +451,58 @@ namespace Barotrauma 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 => confirmedItems.Any(ci => ci.Item == it))) { return false; } + if (confirmedItems != null) + { + // Otherwise there must be no contained items or the contained items must be confirmed as sold + if (!containedItems.All(it => confirmedItems.Any(ci => ci.Item == it))) { return false; } + } } return true; } - public static void CreateItems(List itemsToSpawn, Submarine sub) + public static ItemContainer GetOrCreateCargoContainerFor(ItemPrefab item, ISpatialEntity cargoRoomOrSpawnPoint, ref List availableContainers) + { + ItemContainer itemContainer = null; + if (!string.IsNullOrEmpty(item.CargoContainerIdentifier)) + { + itemContainer = availableContainers.Find(ac => + ac.Inventory.CanBePut(item) && + (ac.Item.Prefab.Identifier == item.CargoContainerIdentifier || + ac.Item.Prefab.Tags.Contains(item.CargoContainerIdentifier))); + + if (itemContainer == null) + { + ItemPrefab containerPrefab = ItemPrefab.Prefabs.Find(ep => + ep.Identifier == item.CargoContainerIdentifier || + (ep.Tags != null && ep.Tags.Contains(item.CargoContainerIdentifier))); + + if (containerPrefab == null) + { + DebugConsole.AddWarning($"CargoManager: could not find the item prefab for container {item.CargoContainerIdentifier}!"); + return null; + } + + Vector2 containerPosition = cargoRoomOrSpawnPoint is Hull cargoRoom ? GetCargoPos(cargoRoom, containerPrefab) : cargoRoomOrSpawnPoint.Position; + Item containerItem = new Item(containerPrefab, containerPosition, cargoRoomOrSpawnPoint.Submarine); + itemContainer = containerItem.GetComponent(); + if (itemContainer == null) + { + DebugConsole.AddWarning($"CargoManager: No ItemContainer component found in {containerItem.Prefab.Identifier}!"); + return null; + } + availableContainers.Add(itemContainer); +#if SERVER + if (GameMain.Server != null) + { + Entity.Spawner.CreateNetworkEvent(new EntitySpawner.SpawnEntity(itemContainer.Item)); + } +#endif + } + } + return itemContainer; + } + + public static void CreateItems(List itemsToSpawn, Submarine sub, CargoManager cargoManager) { if (itemsToSpawn.Count == 0) { return; } @@ -496,60 +544,26 @@ namespace Barotrauma } List availableContainers = new List(); - ItemPrefab containerPrefab = null; foreach (PurchasedItem pi in itemsToSpawn) { Vector2 position = GetCargoPos(cargoRoom, pi.ItemPrefab); for (int i = 0; i < pi.Quantity; i++) { - ItemContainer itemContainer = null; - if (!string.IsNullOrEmpty(pi.ItemPrefab.CargoContainerIdentifier)) - { - itemContainer = availableContainers.Find(ac => - ac.Inventory.CanBePut(pi.ItemPrefab) && - (ac.Item.Prefab.Identifier == pi.ItemPrefab.CargoContainerIdentifier || - ac.Item.Prefab.Tags.Contains(pi.ItemPrefab.CargoContainerIdentifier.ToLowerInvariant()))); - - if (itemContainer == null) - { - containerPrefab = ItemPrefab.Prefabs.Find(ep => - ep.Identifier == pi.ItemPrefab.CargoContainerIdentifier || - (ep.Tags != null && ep.Tags.Contains(pi.ItemPrefab.CargoContainerIdentifier.ToLowerInvariant()))); - - if (containerPrefab == null) - { - DebugConsole.ThrowError("Cargo spawning failed - could not find the item prefab for container \"" + pi.ItemPrefab.CargoContainerIdentifier + "\"!"); - continue; - } - - Vector2 containerPosition = GetCargoPos(cargoRoom, containerPrefab); - Item containerItem = new Item(containerPrefab, containerPosition, wp.Submarine); - itemContainer = containerItem.GetComponent(); - if (itemContainer == null) - { - DebugConsole.ThrowError("Cargo spawning failed - container \"" + containerItem.Name + "\" does not have an ItemContainer component!"); - continue; - } - availableContainers.Add(itemContainer); -#if SERVER - if (GameMain.Server != null) - { - Entity.Spawner.CreateNetworkEvent(new EntitySpawner.SpawnEntity(itemContainer.Item)); - } -#endif - } - } - var item = new Item(pi.ItemPrefab, position, wp.Submarine); - itemContainer?.Inventory.TryPutItem(item, null); - - itemSpawned(item); + var itemContainer = GetOrCreateCargoContainerFor(pi.ItemPrefab, cargoRoom, ref availableContainers); + itemContainer?.Inventory.TryPutItem(item, null); + var idCard = item.GetComponent(); + if (cargoManager != null && idCard != null && pi.BuyerCharacterInfoIdentifier != 0) + { + cargoManager.purchasedIDCards.Add((pi, idCard)); + } + itemSpawned(pi, item); #if SERVER Entity.Spawner?.CreateNetworkEvent(new EntitySpawner.SpawnEntity(item)); #endif (itemContainer?.Item ?? item).CampaignInteractionType = CampaignMode.InteractionType.Cargo; - static void itemSpawned(Item item) + static void itemSpawned(PurchasedItem purchased, Item item) { Submarine sub = item.Submarine ?? item.GetRootContainer()?.Submarine; if (sub != null) @@ -565,6 +579,23 @@ namespace Barotrauma itemsToSpawn.Clear(); } + private readonly List<(PurchasedItem purchaseInfo, IdCard idCard)> purchasedIDCards = new List<(PurchasedItem purchaseInfo, IdCard idCard)>(); + public void InitPurchasedIDCards() + { + foreach ((PurchasedItem purchased, IdCard idCard) in purchasedIDCards) + { + if (idCard != null && purchased.BuyerCharacterInfoIdentifier != 0) + { + var owner = Character.CharacterList.Find(c => c.Info?.GetIdentifier() == purchased.BuyerCharacterInfoIdentifier); + if (owner?.Info != null) + { + var mainSubSpawnPoints = WayPoint.SelectCrewSpawnPoints(new List() { owner.Info }, Submarine.MainSub); + idCard.Initialize(mainSubSpawnPoints.FirstOrDefault(), owner); + } + } + } + } + public static Vector2 GetCargoPos(Hull hull, ItemPrefab itemPrefab) { float floorPos = hull.Rect.Y - hull.Rect.Height; @@ -603,7 +634,7 @@ namespace Barotrauma new XAttribute("id", item.ItemPrefab.Identifier), new XAttribute("qty", item.Quantity), new XAttribute("storeid", storeSpecificItems.Key), - new XAttribute("buyer", item.BuyerCharacterInfoId))); + new XAttribute("buyer", item.BuyerCharacterInfoIdentifier))); } } parentElement.Add(itemsElement); diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/CrewManager.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/CrewManager.cs index 6bf837b8b..421af50e0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/CrewManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/CrewManager.cs @@ -51,8 +51,6 @@ namespace Barotrauma public ReadyCheck ActiveReadyCheck; - public XElement ActiveOrdersElement { get; set; } - public CrewManager(bool isSinglePlayer) { IsSinglePlayer = isSinglePlayer; @@ -493,9 +491,8 @@ namespace Barotrauma partial void UpdateProjectSpecific(float deltaTime); - private void SaveActiveOrders(XElement parentElement) + public void SaveActiveOrders(XElement element) { - ActiveOrdersElement = new XElement("activeorders"); // Only save orders with no fade out time (e.g. ignore orders) var ordersToSave = new List(); foreach (var activeOrder in ActiveOrders) @@ -504,14 +501,13 @@ namespace Barotrauma if (order == null || activeOrder.FadeOutTime.HasValue) { continue; } ordersToSave.Add(order.WithManualPriority(CharacterInfo.HighestManualOrderPriority)); } - CharacterInfo.SaveOrders(ActiveOrdersElement, ordersToSave.ToArray()); - parentElement?.Add(ActiveOrdersElement); + CharacterInfo.SaveOrders(element, ordersToSave.ToArray()); } - public void LoadActiveOrders() + public void LoadActiveOrders(XElement element) { - if (ActiveOrdersElement == null) { return; } - foreach (var orderInfo in CharacterInfo.LoadOrders(ActiveOrdersElement)) + if (element == null) { return; } + foreach (var orderInfo in CharacterInfo.LoadOrders(element)) { IIgnorable ignoreTarget = null; if (orderInfo.IsIgnoreOrder) diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs index 019a75046..04ca98e30 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.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; @@ -107,6 +108,8 @@ namespace Barotrauma protected XElement petsElement; + protected XElement ActiveOrdersElement { get; set; } + public CampaignSettings Settings; private readonly List extraMissions = new List(); @@ -739,8 +742,10 @@ namespace Barotrauma foreach (LocationConnection connection in Map.Connections) { connection.Difficulty = MathHelper.Lerp(connection.Difficulty, 100.0f, 0.25f); - connection.LevelData.Difficulty = connection.Difficulty; - connection.LevelData.IsBeaconActive = false; + connection.LevelData = new LevelData(connection) + { + IsBeaconActive = false + }; connection.LevelData.HasHuntingGrounds = connection.LevelData.OriginallyHadHuntingGrounds; } foreach (Location location in Map.Locations) @@ -1032,5 +1037,184 @@ namespace Barotrauma } } + protected void LeaveUnconnectedSubs(Submarine leavingSub) + { + if (leavingSub != Submarine.MainSub && !leavingSub.DockedTo.Contains(Submarine.MainSub)) + { + Submarine.MainSub = leavingSub; + GameMain.GameSession.Submarine = leavingSub; + GameMain.GameSession.SubmarineInfo = leavingSub.Info; + leavingSub.Info.FilePath = System.IO.Path.Combine(SaveUtil.TempPath, leavingSub.Info.Name + ".sub"); + var subsToLeaveBehind = GetSubsToLeaveBehind(leavingSub); + GameMain.GameSession.OwnedSubmarines.Add(leavingSub.Info); + foreach (Submarine sub in subsToLeaveBehind) + { + GameMain.GameSession.OwnedSubmarines.RemoveAll(s => s != leavingSub.Info && s.Name == sub.Info.Name); + MapEntity.mapEntityList.RemoveAll(e => e.Submarine == sub && e is LinkedSubmarine); + LinkedSubmarine.CreateDummy(leavingSub, sub); + } + } + } + + public SubmarineInfo SwitchSubs() + { + TransferItemsBetweenSubs(); + RefreshOwnedSubmarines(); + PendingSubmarineSwitch = null; + return GameMain.GameSession.SubmarineInfo; + } + + /// + /// Also serializes the current sub. + /// + protected void TransferItemsBetweenSubs() + { + Submarine currentSub = GameMain.GameSession.Submarine; + if (currentSub == null || currentSub.Removed) + { + DebugConsole.ThrowError("Cannot transfer items between subs, because the current sub is null or removed!"); + return; + } + var itemsToTransfer = new List<(Item item, Item container)>(); + if (PendingSubmarineSwitch != null) + { + // Remove items from the old sub + foreach (Item item in Item.ItemList) + { + if (item.Removed) { continue; } + if (item.NonInteractable) { continue; } + if (item.HiddenInGame) { continue; } + if (item.Submarine != currentSub) { continue; } + if (item.Prefab.DontTransferBetweenSubs) { continue; } + if (item.GetRootInventoryOwner() is Character) { continue; } + if (item.GetComponent() == null && item.GetComponent() == null && item.GetComponent() == null) { continue; } + if (item.Components.Any(c => c is Holdable h && h.Attached)) { continue; } + if (item.Components.Any(c => c is Wire w && w.Connections.Any(c => c != null))) { continue; } + itemsToTransfer.Add((item, item.Container)); + item.Submarine = null; + } + foreach (var (item, container) in itemsToTransfer) + { + if (container?.Submarine != null) + { + // Drop the item if it's not inside another item set to be transferred. + item.Drop(null, createNetworkEvent: false, setTransform: false); + } + } + } + // Serialize the current sub + GameMain.GameSession.SubmarineInfo = new SubmarineInfo(currentSub); + if (PendingSubmarineSwitch != null && itemsToTransfer.Any()) + { + // Load the new sub + var newSub = new Submarine(PendingSubmarineSwitch); + // Move the transferred items + List availableContainers = Item.ItemList + .Where(it => it.Submarine == newSub && it.HasTag("crate") && !it.NonInteractable && !it.HiddenInGame && !it.Removed) + .Select(it => it.GetComponent()) + .Where(c => c != null) + .ToList(); + foreach (var (item, oldContainer) in itemsToTransfer) + { + Item newContainer = null; + item.Submarine = newSub; + if (item.Container == null) + { + newContainer = newSub.FindContainerFor(item, onlyPrimary: true, checkTransferConditions: true); + } + if (item.Container == null && (newContainer == null || !newContainer.OwnInventory.TryPutItem(item, user: null, createNetworkEvent: false))) + { + WayPoint wp = WayPoint.GetRandom(SpawnType.Cargo, null, newSub); + Hull spawnHull = wp?.CurrentHull ?? Hull.HullList.Where(h => h.Submarine == newSub && !h.IsWetRoom).GetRandomUnsynced(); + if (spawnHull == null) + { + DebugConsole.AddWarning($"Failed to transfer items between subs. No cargo waypoint or dry hulls found in the new sub."); + return; + } + if (spawnHull != null) + { + var cargoContainer = CargoManager.GetOrCreateCargoContainerFor(item.Prefab, spawnHull, ref availableContainers); + if (cargoContainer == null || !cargoContainer.Inventory.TryPutItem(item, user: null, createNetworkEvent: false)) + { + item.SetTransform(wp.SimPosition, 0.0f, findNewHull: false, setPrevTransform: false); + } + } + else + { + DebugConsole.AddWarning($"Failed to transfer item {item.Prefab.Identifier} ({item.ID}), because no cargo spawn point could be found!"); + } + } + string newContainerName = newContainer == null ? "(null)" : $"{newContainer.Prefab.Identifier} ({newContainer.Tags})"; + string msg = "Item transfer log error."; + if (oldContainer != null) + { + if (newContainer == null && oldContainer == item.Container) + { + msg = $"Transferred {item.Prefab.Identifier} ({item.ID}) contained inside {oldContainer.Prefab.Identifier} ({oldContainer.ID})"; + } + else + { + msg = $"Transferred {item.Prefab.Identifier} ({item.ID}) from {oldContainer.Prefab.Identifier} ({oldContainer.Tags}) to {newContainerName}"; + } + } + else + { + msg = $"Transferred {item.Prefab.Identifier} ({item.ID}) to {newContainerName}"; + } +#if DEBUG + DebugConsole.NewMessage(msg); +#else + DebugConsole.Log(msg); +#endif + } + // Serialize the new sub + PendingSubmarineSwitch = new SubmarineInfo(newSub); + } + } + + protected void RefreshOwnedSubmarines() + { + if (PendingSubmarineSwitch != null) + { + SubmarineInfo previousSub = GameMain.GameSession.SubmarineInfo; + GameMain.GameSession.SubmarineInfo = PendingSubmarineSwitch; + + for (int i = 0; i < GameMain.GameSession.OwnedSubmarines.Count; i++) + { + if (GameMain.GameSession.OwnedSubmarines[i].Name == previousSub.Name) + { + GameMain.GameSession.OwnedSubmarines[i] = previousSub; + break; + } + } + } + } + + public void SavePets(XElement parentElement = null) + { + petsElement = new XElement("pets"); + PetBehavior.SavePets(petsElement); + parentElement?.Add(petsElement); + } + + public void LoadPets() + { + if (petsElement != null) + { + PetBehavior.LoadPets(petsElement); + } + } + + public void SaveActiveOrders(XElement parentElement = null) + { + ActiveOrdersElement = new XElement("activeorders"); + CrewManager?.SaveActiveOrders(ActiveOrdersElement); + parentElement?.Add(ActiveOrdersElement); + } + + public void LoadActiveOrders() + { + CrewManager?.LoadActiveOrders(ActiveOrdersElement); + } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/MultiPlayerCampaign.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/MultiPlayerCampaign.cs index a8c7034ef..181c73232 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/MultiPlayerCampaign.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/MultiPlayerCampaign.cs @@ -155,7 +155,7 @@ namespace Barotrauma case "bots" when GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer: CrewManager.HasBots = subElement.GetAttributeBool("hasbots", false); CrewManager.AddCharacterElements(subElement); - CrewManager.ActiveOrdersElement = subElement.GetChildElement("activeorders"); + ActiveOrdersElement = subElement.GetChildElement("activeorders"); break; case "cargo": CargoManager?.LoadPurchasedItems(subElement); diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs index a7b79214f..9abdd2721 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs @@ -275,7 +275,7 @@ namespace Barotrauma /// /// Switch to another submarine. The sub is loaded when the next round starts. /// - public SubmarineInfo SwitchSubmarine(SubmarineInfo newSubmarine, int cost, Client? client = null) + public void SwitchSubmarine(SubmarineInfo newSubmarine, int cost, Client? client = null) { if (!OwnedSubmarines.Any(s => s.Name == newSubmarine.Name)) { @@ -293,15 +293,12 @@ namespace Barotrauma } } } - if ((GameMain.NetworkMember is null || GameMain.NetworkMember is { IsServer: true }) && cost > 0) { Campaign!.TryPurchase(client, cost); } GameAnalyticsManager.AddMoneySpentEvent(cost, GameAnalyticsManager.MoneySink.SubmarineSwitch, newSubmarine.Name); Campaign!.PendingSubmarineSwitch = newSubmarine; - - return newSubmarine; } public void PurchaseSubmarine(SubmarineInfo newSubmarine, Client? client = null) @@ -600,10 +597,13 @@ namespace Barotrauma { //only place items and corpses here in single player //the server does this after loading the respawn shuttle - Level?.SpawnNPCs(); - Level?.SpawnCorpses(); - Level?.PrepareBeaconStation(); - AutoItemPlacer.PlaceIfNeeded(); + if (Level != null) + { + Level.SpawnNPCs(); + Level.SpawnCorpses(); + Level.PrepareBeaconStation(); + } + AutoItemPlacer.SpawnItems(); } if (GameMode is MultiPlayerCampaign mpCampaign) { @@ -836,6 +836,11 @@ namespace Barotrauma { GUI.TogglePauseMenu(); } + if (IsTabMenuOpen) + { + ToggleTabMenu(); + } + GUI.PreventPauseMenuToggle = true; if (!(GameMode is TestGameMode) && Screen.Selected == GameMain.GameScreen && RoundSummary != null) @@ -1072,8 +1077,21 @@ namespace Barotrauma rootElement.Add(new XAttribute("savetime", ToolBox.Epoch.NowLocal)); rootElement.Add(new XAttribute("version", GameMain.Version)); - var submarineInfo = Campaign?.PendingSubmarineSwitch ?? SubmarineInfo; - rootElement.Add(new XAttribute("submarine", submarineInfo == null ? "" : submarineInfo.Name)); + if (Submarine?.Info != null && !Submarine.Removed && Campaign != null) + { + bool hasNewPendingSub = Campaign.PendingSubmarineSwitch != null && + Campaign.PendingSubmarineSwitch.MD5Hash.StringRepresentation != Submarine.Info.MD5Hash.StringRepresentation; + + if (hasNewPendingSub) + { + Campaign.SwitchSubs(); + } + else + { + SubmarineInfo = new SubmarineInfo(Submarine); + } + } + rootElement.Add(new XAttribute("submarine", SubmarineInfo == null ? "" : SubmarineInfo.Name)); if (OwnedSubmarines != null) { List ownedSubmarineNames = new List(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/InputType.cs b/Barotrauma/BarotraumaShared/SharedSource/InputType.cs index fcac2eba3..493ef435d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/InputType.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/InputType.cs @@ -17,7 +17,6 @@ namespace Barotrauma Deselect, Shoot, Command, - ToggleInventory, TakeOneFromInventorySlot, TakeHalfFromInventorySlot, NextFireMode, diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/DockingPort.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/DockingPort.cs index f8249369e..d1371c9fb 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/DockingPort.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/DockingPort.cs @@ -99,8 +99,8 @@ namespace Barotrauma.Items.Components { if (!docked && value) { - if (DockingTarget == null) AttemptDock(); - if (DockingTarget == null) return; + if (DockingTarget == null) { AttemptDock(); } + if (DockingTarget == null) { return; } docked = true; } @@ -126,6 +126,14 @@ namespace Barotrauma.Items.Components /// public event Action OnUnDocked; + private bool outpostAutoDockingPromptShown; + + enum AllowOutpostAutoDocking + { + Ask, Yes, No + } + private AllowOutpostAutoDocking allowOutpostAutoDocking = AllowOutpostAutoDocking.Ask; + public DockingPort(Item item, ContentXElement element) : base(item, element) { @@ -622,7 +630,8 @@ namespace Barotrauma.Items.Components { bodies[i + j * 2] = GameMain.World.CreateEdge( ConvertUnits.ToSimUnits(new Vector2(hullRects[i].X, hullRects[i].Y - hullRects[i].Height * j)), - ConvertUnits.ToSimUnits(new Vector2(hullRects[i].Right, hullRects[i].Y - hullRects[i].Height * j))); + ConvertUnits.ToSimUnits(new Vector2(hullRects[i].Right, hullRects[i].Y - hullRects[i].Height * j)), + BodyType.Static); } } @@ -632,7 +641,9 @@ namespace Barotrauma.Items.Components ConvertUnits.ToSimUnits(hullRects[0].Width + hullRects[1].Width), ConvertUnits.ToSimUnits(hullRects[0].Height), density: 0.0f, - offset: ConvertUnits.ToSimUnits(new Vector2(hullRects[0].Right, hullRects[0].Y - hullRects[0].Height / 2) - hulls[0].Submarine.HiddenSubPosition)); + offset: ConvertUnits.ToSimUnits(new Vector2(hullRects[0].Right, hullRects[0].Y - hullRects[0].Height / 2) - hulls[0].Submarine.HiddenSubPosition), + Physics.CollisionWall, + Physics.CollisionCharacter | Physics.CollisionItem | Physics.CollisionCharacter | Physics.CollisionItemBlocking | Physics.CollisionProjectile); outsideBlocker.UserData = this; } @@ -742,7 +753,8 @@ namespace Barotrauma.Items.Components { bodies[i + j * 2] = GameMain.World.CreateEdge( ConvertUnits.ToSimUnits(new Vector2(hullRects[i].X + hullRects[i].Width * j, hullRects[i].Y)), - ConvertUnits.ToSimUnits(new Vector2(hullRects[i].X + hullRects[i].Width * j, hullRects[i].Y - hullRects[i].Height))); + ConvertUnits.ToSimUnits(new Vector2(hullRects[i].X + hullRects[i].Width * j, hullRects[i].Y - hullRects[i].Height)), + BodyType.Static); } } @@ -752,7 +764,9 @@ namespace Barotrauma.Items.Components ConvertUnits.ToSimUnits(hullRects[0].Width), ConvertUnits.ToSimUnits(hullRects[0].Height + hullRects[1].Height), density: 0.0f, - offset: ConvertUnits.ToSimUnits(new Vector2(hullRects[0].Center.X, hullRects[0].Y) - hulls[0].Submarine.HiddenSubPosition)); + offset: ConvertUnits.ToSimUnits(new Vector2(hullRects[0].Center.X, hullRects[0].Y) - hulls[0].Submarine.HiddenSubPosition), + Physics.CollisionWall, + Physics.CollisionCharacter | Physics.CollisionItem | Physics.CollisionCharacter | Physics.CollisionItemBlocking | Physics.CollisionProjectile); outsideBlocker.UserData = this; } @@ -778,8 +792,6 @@ namespace Barotrauma.Items.Components if (body == null) { continue; } body.BodyType = BodyType.Static; body.Friction = 0.5f; - - body.CollisionCategories = Physics.CollisionWall; } } @@ -947,7 +959,7 @@ namespace Barotrauma.Items.Components { foreach (Body body in bodies) { - if (body == null) continue; + if (body == null) { continue; } GameMain.World.Remove(body); } bodies = null; @@ -961,6 +973,9 @@ namespace Barotrauma.Items.Components { item.CreateServerEvent(this); } +#elif CLIENT + autodockingVerification?.Close(); + autodockingVerification = null; #endif OnUnDocked?.Invoke(); OnUnDocked = null; @@ -1140,27 +1155,86 @@ namespace Barotrauma.Items.Components public override void ReceiveSignal(Signal signal, Connection connection) { - if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; } +#if CLIENT + if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient && + !(GameMain.GameSession?.Campaign?.AllowedToManageCampaign(ClientPermissions.ManageMap) ?? false)) + { + return; + } +#endif if (dockingCooldown > 0.0f) { return; } bool wasDocked = docked; DockingPort prevDockingTarget = DockingTarget; + bool newDockedState = wasDocked; switch (connection.Name) { case "toggle": if (signal.value != "0") { - Docked = !docked; + newDockedState = !docked; } break; case "set_active": case "set_state": - Docked = signal.value != "0"; + newDockedState = signal.value != "0"; break; } + if (newDockedState != wasDocked) + { + bool tryingToToggleOutpostDocking = docked ? + DockingTarget?.Item?.Submarine?.Info?.IsOutpost ?? false : + FindAdjacentPort()?.Item?.Submarine?.Info?.IsOutpost ?? false; + //trying to dock/undock from an outpost and the signal was sent by some automated system instead of a character + // -> ask if the player really wants to dock/undock to prevent a softlock if someone's wired the docking port + // in a way that makes always makes it dock/undock immediately at the start of the roun + if (tryingToToggleOutpostDocking && signal.sender == null) + { + if (allowOutpostAutoDocking == AllowOutpostAutoDocking.Ask) + { +#if CLIENT + if (!outpostAutoDockingPromptShown) + { + autodockingVerification = new GUIMessageBox(string.Empty, + TextManager.Get(newDockedState ? "autodockverification" : "autoundockverification"), + new LocalizedString[] { TextManager.Get("Yes"), TextManager.Get("No") }); + autodockingVerification.Buttons[0].OnClicked += (btn, userdata) => + { + autodockingVerification?.Close(); + autodockingVerification = null; + if (item.Removed || GameMain.Client == null) { return false; } + allowOutpostAutoDocking = AllowOutpostAutoDocking.Yes; + item.CreateClientEvent(this); + return true; + }; + autodockingVerification.Buttons[1].OnClicked += (btn, userdata) => + { + autodockingVerification?.Close(); + autodockingVerification = null; + if (item.Removed || GameMain.Client == null) { return false; } + allowOutpostAutoDocking = AllowOutpostAutoDocking.No; + item.CreateClientEvent(this); + return true; + }; + } +#endif + outpostAutoDockingPromptShown = true; + return; + } + else if (allowOutpostAutoDocking == AllowOutpostAutoDocking.No) + { + return; + } + } + + if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; } + + Docked = newDockedState; + } + #if SERVER if (signal.sender != null && docked != wasDocked) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Door.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Door.cs index 98dc5114d..bb9c5bb64 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Door.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Door.cs @@ -241,12 +241,14 @@ namespace Barotrauma.Items.Components Body = new PhysicsBody( ConvertUnits.ToSimUnits(Math.Max(doorRect.Width, 1)), ConvertUnits.ToSimUnits(Math.Max(doorRect.Height, 1)), - 0.0f, - 1.5f) + radius: 0.0f, + density: 1.5f, + BodyType.Static, + Physics.CollisionWall, + Physics.CollisionCharacter | Physics.CollisionItem | Physics.CollisionCharacter | Physics.CollisionItemBlocking | Physics.CollisionProjectile, + findNewContacts: false) { UserData = item, - CollisionCategories = Physics.CollisionWall, - BodyType = BodyType.Static, Friction = 0.5f }; Body.SetTransformIgnoreContacts( @@ -258,11 +260,16 @@ namespace Barotrauma.Items.Components } } - public override void Move(Vector2 amount) + public override void Move(Vector2 amount, bool ignoreContacts = false) { - base.Move(amount); - - Body?.SetTransform(Body.SimPosition + ConvertUnits.ToSimUnits(amount), 0.0f); + if (ignoreContacts) + { + Body?.SetTransformIgnoreContacts(Body.SimPosition + ConvertUnits.ToSimUnits(amount), 0.0f); + } + else + { + Body?.SetTransform(Body.SimPosition + ConvertUnits.ToSimUnits(amount), 0.0f); + } #if CLIENT UpdateConvexHulls(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Holdable.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Holdable.cs index 384b88db6..9538b5805 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Holdable.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Holdable.cs @@ -164,6 +164,8 @@ namespace Barotrauma.Items.Components public bool SwingWhenAiming { get; set; } [Editable, Serialize(false, IsPropertySaveable.No, description: "Should the item swing around when it's being used (for example, when firing a weapon or a welding tool).")] public bool SwingWhenUsing { get; set; } + [Editable, Serialize(false, IsPropertySaveable.No)] + public bool DisableHeadRotation { get; set; } [ConditionallyEditable(ConditionallyEditable.ConditionType.Attachable, MinValueFloat = 0.0f, MaxValueFloat = 0.999f, DecimalCount = 3), Serialize(0.55f, IsPropertySaveable.No, description: "Sprite depth that's used when the item is NOT attached to a wall.")] public float SpriteDepthWhenDropped @@ -180,11 +182,12 @@ namespace Barotrauma.Items.Components Pusher = null; if (element.GetAttributeBool("blocksplayers", false)) { - Pusher = new PhysicsBody(item.body.width, item.body.height, item.body.radius, item.body.Density) + Pusher = new PhysicsBody(item.body.width, item.body.height, item.body.radius, + item.body.Density, + BodyType.Dynamic, + Physics.CollisionItemBlocking, + Physics.CollisionCharacter | Physics.CollisionProjectile) { - BodyType = BodyType.Dynamic, - CollidesWith = Physics.CollisionCharacter | Physics.CollisionProjectile, - CollisionCategories = Physics.CollisionItemBlocking, Enabled = false, UserData = this }; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/LevelResource.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/LevelResource.cs index 0dee7a4a2..0467429ea 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/LevelResource.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/LevelResource.cs @@ -79,11 +79,18 @@ namespace Barotrauma.Items.Components IsActive = true; } - public override void Move(Vector2 amount) + public override void Move(Vector2 amount, bool ignoreContacts = false) { if (trigger != null && amount.LengthSquared() > 0.00001f) { - trigger.SetTransform(item.SimPosition, 0.0f); + if (ignoreContacts) + { + trigger.SetTransformIgnoreContacts(item.SimPosition, 0.0f); + } + else + { + trigger.SetTransform(item.SimPosition, 0.0f); + } } } @@ -119,17 +126,19 @@ namespace Barotrauma.Items.Components } var body = item.body ?? holdable.Body; - + if (body != null) { - trigger = new PhysicsBody(body.width, body.height, body.radius, body.Density) + trigger = new PhysicsBody(body.width, body.height, body.radius, + body.Density, + BodyType.Static, + Physics.CollisionWall, + Physics.CollisionNone, + findNewContacts: false) { UserData = item }; trigger.FarseerBody.SetIsSensor(true); - trigger.FarseerBody.BodyType = BodyType.Static; - trigger.FarseerBody.CollisionCategories = Physics.CollisionWall; - trigger.FarseerBody.CollidesWith = Physics.CollisionNone; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/MeleeWeapon.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/MeleeWeapon.cs index 353a0b0f6..53a55e033 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/MeleeWeapon.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/MeleeWeapon.cs @@ -111,8 +111,9 @@ namespace Barotrauma.Items.Components ActivateNearbySleepingCharacters(); reloadTimer = reload; - reloadTimer /= (1f + character.GetStatValue(StatTypes.MeleeAttackSpeed)); - reloadTimer /= (1f + item.GetQualityModifier(Quality.StatType.StrikingSpeedMultiplier)); + reloadTimer /= 1f + character.GetStatValue(StatTypes.MeleeAttackSpeed); + reloadTimer /= 1f + item.GetQualityModifier(Quality.StatType.StrikingSpeedMultiplier); + character.AnimController.LockFlippingUntil = (float)Timing.TotalTime + reloadTimer; item.body.FarseerBody.CollisionCategories = Physics.CollisionProjectile; item.body.FarseerBody.CollidesWith = Physics.CollisionCharacter | Physics.CollisionWall | Physics.CollisionItemBlocking; @@ -216,6 +217,10 @@ namespace Barotrauma.Items.Components { 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); + if (ac.InWater) + { + ac.LockFlippingUntil = (float)Timing.TotalTime + Reload; + } } else { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Pickable.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Pickable.cs index ddd615402..f1651b408 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Pickable.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Pickable.cs @@ -71,6 +71,7 @@ namespace Barotrauma.Items.Components //return if someone is already trying to pick the item if (pickTimer > 0.0f) { return false; } if (picker == null || picker.Inventory == null) { return false; } + if (!picker.Inventory.AccessibleWhenAlive && !picker.Inventory.AccessibleByOwner) { return false; } if (PickingTime > 0.0f) { @@ -226,7 +227,7 @@ namespace Barotrauma.Items.Components { foreach (Connection c in connectionPanel.Connections) { - foreach (Wire w in c.Wires) + foreach (Wire w in c.Wires.ToArray()) { if (w == null) continue; w.Item.Drop(character); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Propulsion.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Propulsion.cs index 971068a95..cadea84be 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Propulsion.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Propulsion.cs @@ -40,7 +40,7 @@ namespace Barotrauma.Items.Components public override bool Use(float deltaTime, Character character = null) { - if (character == null || character.Removed) return false; + if (character == null || character.Removed) { return false; } if (!character.IsKeyDown(InputType.Aim) || character.Stun > 0.0f) { return false; } IsActive = true; @@ -55,12 +55,11 @@ namespace Barotrauma.Items.Components if (UsableIn == UseEnvironment.Water) { return true; } } - Vector2 dir = Vector2.Normalize(character.CursorPosition - character.Position); - //move upwards if the cursor is at the position of the character - if (!MathUtils.IsValid(dir)) dir = Vector2.UnitY; - + Vector2 dir = character.CursorPosition - character.Position; + if (!MathUtils.IsValid(dir)) { return true; } + float length = 200; + dir = dir.ClampLength(length) / length; Vector2 propulsion = dir * Force * character.PropulsionSpeedMultiplier; - if (character.AnimController.InWater && Force > 0.0f) { character.AnimController.TargetMovement = dir; } foreach (Limb limb in character.AnimController.Limbs) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemComponent.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemComponent.cs index 845ae5efe..dd88b19f1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemComponent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemComponent.cs @@ -416,7 +416,7 @@ namespace Barotrauma.Items.Components } } - public virtual void Move(Vector2 amount) { } + public virtual void Move(Vector2 amount, bool ignoreContacts = false) { } /// a Character has picked the item public virtual bool Pick(Character picker) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs index 891fc3fce..3d5f79b0f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs @@ -315,7 +315,7 @@ namespace Barotrauma.Items.Components IsActive = activeContainedItems.Count > 0 || Inventory.AllItems.Any(it => it.body != null); } - public override void Move(Vector2 amount) + public override void Move(Vector2 amount, bool ignoreContacts = false) { SetContainedItemPositions(); } @@ -751,7 +751,11 @@ namespace Barotrauma.Items.Components return; } #endif - Inventory.AllItemsMod.ForEach(it => it.Drop(null)); + //if we're unloading the whole sub, no need to drop anything (everything's going to be removed anyway) + if (!Submarine.Unloading) + { + Inventory.AllItemsMod.ForEach(it => it.Drop(null)); + } } public override void Load(ContentXElement componentElement, bool usePrefabValues, IdRemap idRemap) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Controller.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Controller.cs index 296277c9d..4bf9d88fe 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Controller.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Controller.cs @@ -462,19 +462,8 @@ namespace Barotrauma.Items.Components { dir = dir == Direction.Left ? Direction.Right : Direction.Left; } - - userPos.X = -UserPos.X; - - for (int i = 0; i < limbPositions.Count; i++) - { - float diff = (item.Rect.X + limbPositions[i].Position.X * item.Scale) - item.Rect.Center.X; - - Vector2 flippedPos = - new Vector2( - (item.Rect.Center.X - diff - item.Rect.X) / item.Scale, - limbPositions[i].Position.Y); - limbPositions[i] = new LimbPos(limbPositions[i].LimbType, flippedPos, limbPositions[i].AllowUsingLimb); - } + userPos.X = -UserPos.X; + FlipLimbPositions(); } public override void FlipY(bool relativeToSub) @@ -519,6 +508,11 @@ namespace Barotrauma.Items.Components { if (Screen.Selected == GameMain.SubEditorScreen) { + if (item.FlippedX) + { + FlipLimbPositions(); + } + // Don't save flipped positions. foreach (var limbPos in limbPositions) { element.Add(new XElement("limbposition", @@ -526,6 +520,10 @@ namespace Barotrauma.Items.Components new XAttribute("position", XMLExtensions.Vector2ToString(limbPos.Position)), new XAttribute("allowusinglimb", limbPos.AllowUsingLimb))); } + if (item.FlippedX) + { + FlipLimbPositions(); + } } return element; } @@ -558,5 +556,29 @@ namespace Barotrauma.Items.Components } } } + + private void FlipLimbPositions() + { + for (int i = 0; i < limbPositions.Count; i++) + { + float diff = (item.Rect.X + limbPositions[i].Position.X * item.Scale) - item.Rect.Center.X; + + Vector2 flippedPos = + new Vector2( + (item.Rect.Center.X - diff - item.Rect.X) / item.Scale, + limbPositions[i].Position.Y); + limbPositions[i] = new LimbPos(limbPositions[i].LimbType, flippedPos, limbPositions[i].AllowUsingLimb); + } + } + + public override void Reset() + { + base.Reset(); + LoadLimbPositions(originalElement); + if (item.FlippedX) + { + FlipLimbPositions(); + } + } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Power/Powered.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Power/Powered.cs index d22c343b5..977dda461 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Power/Powered.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Power/Powered.cs @@ -150,7 +150,7 @@ namespace Barotrauma.Items.Components { if (powerOut?.Grid != null) { return powerOut.Grid.Voltage; } } - return voltage; + return currPowerConsumption <= 0.0f ? 1.0f : voltage; } set { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs index fe52d7480..ee6516ef8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs @@ -231,6 +231,13 @@ namespace Barotrauma.Items.Components set; } + [Serialize(false, IsPropertySaveable.No, description:"Enable only if you want to make the projectile ignore collisions with other projectiles when it's shot. Doesn't have any effect, if the item is not set to be damaged by projectiles.")] + public bool IgnoreProjectilesWhileActive + { + get; + set; + } + public Body StickTarget { get; @@ -405,6 +412,10 @@ namespace Barotrauma.Items.Components item.body.CollisionCategories = Physics.CollisionProjectile; item.body.CollidesWith = Physics.CollisionCharacter | Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionItemBlocking; + if (item.Prefab.DamagedByProjectiles && !IgnoreProjectilesWhileActive) + { + item.body.CollidesWith |= Physics.CollisionProjectile; + } IsActive = true; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/BooleanOperatorComponent/AndComponent.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/BooleanOperatorComponent/AndComponent.cs new file mode 100644 index 000000000..e98614a3a --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/BooleanOperatorComponent/AndComponent.cs @@ -0,0 +1,10 @@ +namespace Barotrauma.Items.Components +{ + sealed class AndComponent : BooleanOperatorComponent + { + public AndComponent(Item item, ContentXElement element) + : base(item, element) { } + + protected override bool GetOutput(int numTrueInputs) => numTrueInputs >= 2; + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/AndComponent.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/BooleanOperatorComponent/BooleanOperatorComponent.cs similarity index 90% rename from Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/AndComponent.cs rename to Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/BooleanOperatorComponent/BooleanOperatorComponent.cs index 22ceb46ec..bd3140234 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/AndComponent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/BooleanOperatorComponent/BooleanOperatorComponent.cs @@ -3,7 +3,7 @@ using System.Xml.Linq; namespace Barotrauma.Items.Components { - class AndComponent : ItemComponent + abstract class BooleanOperatorComponent : ItemComponent { protected string output, falseOutput; @@ -70,22 +70,25 @@ namespace Barotrauma.Items.Components } } - public AndComponent(Item item, ContentXElement element) + public BooleanOperatorComponent(Item item, ContentXElement element) : base(item, element) { timeSinceReceived = new float[] { Math.Max(timeFrame * 2.0f, 0.1f), Math.Max(timeFrame * 2.0f, 0.1f) }; IsActive = true; } - public override void Update(float deltaTime, Camera cam) + protected abstract bool GetOutput(int numTrueInputs); + + public sealed override void Update(float deltaTime, Camera cam) { - bool state = true; + int receivedInputs = 0; for (int i = 0; i < timeSinceReceived.Length; i++) { - if (timeSinceReceived[i] > timeFrame) { state = false; } + if (timeSinceReceived[i] <= timeFrame) { receivedInputs += 1; } timeSinceReceived[i] += deltaTime; } + bool state = GetOutput(receivedInputs); string signalOut = state ? output : falseOutput; if (string.IsNullOrEmpty(signalOut)) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/BooleanOperatorComponent/OrComponent.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/BooleanOperatorComponent/OrComponent.cs new file mode 100644 index 000000000..f7208e8e3 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/BooleanOperatorComponent/OrComponent.cs @@ -0,0 +1,10 @@ +namespace Barotrauma.Items.Components +{ + sealed class OrComponent : BooleanOperatorComponent + { + public OrComponent(Item item, ContentXElement element) + : base(item, element) { } + + protected override bool GetOutput(int numTrueInputs) => numTrueInputs > 0; + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/BooleanOperatorComponent/XorComponent.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/BooleanOperatorComponent/XorComponent.cs new file mode 100644 index 000000000..ee299d837 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/BooleanOperatorComponent/XorComponent.cs @@ -0,0 +1,10 @@ +namespace Barotrauma.Items.Components +{ + sealed class XorComponent : BooleanOperatorComponent + { + public XorComponent(Item item, ContentXElement element) + : base(item, element) { } + + protected override bool GetOutput(int numTrueInputs) => numTrueInputs == 1; + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Connection.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Connection.cs index 51fcd38d9..34256fe0c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Connection.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Connection.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using System.Xml.Linq; @@ -19,11 +20,8 @@ namespace Barotrauma.Items.Components public readonly string Name; public readonly LocalizedString DisplayName; - private readonly Wire[] wires; - public IEnumerable Wires - { - get { return wires; } - } + private readonly HashSet wires; + public IReadOnlyCollection Wires => wires; private readonly Item item; @@ -31,7 +29,7 @@ namespace Barotrauma.Items.Components public readonly List Effects; - public readonly ushort[] wireId; + public readonly List LoadedWireIds; //The grid the connection is a part of public GridInfo Grid; @@ -92,7 +90,7 @@ namespace Barotrauma.Items.Components MaxWires = Math.Max(element.Elements().Count(e => e.Name.ToString().Equals("link", StringComparison.OrdinalIgnoreCase)), MaxWires); MaxPlayerConnectableWires = element.GetAttributeInt("maxplayerconnectablewires", MaxWires); - wires = new Wire[MaxWires]; + wires = new HashSet(); IsOutput = element.Name.ToString() == "output"; Name = element.GetAttributeString("name", IsOutput ? "output" : "input"); @@ -150,23 +148,15 @@ namespace Barotrauma.Items.Components IsPower = Name == "power_in" || Name == "power" || Name == "power_out"; - wireId = new ushort[MaxWires]; - + LoadedWireIds = new List(); foreach (var subElement in element.Elements()) { switch (subElement.Name.ToString().ToLowerInvariant()) { case "link": - int index = -1; - for (int i = 0; i < MaxWires; i++) - { - if (wireId[i] < 1) { index = i; } - } - if (index == -1) { break; } - int id = subElement.GetAttributeInt("w", 0); if (id < 0) { id = 0; } - wireId[index] = idRemap.GetOffsetId(id); + if (LoadedWireIds.Count < MaxWires) { LoadedWireIds.Add(idRemap.GetOffsetId(id)); } break; case "statuseffect": @@ -185,138 +175,111 @@ namespace Barotrauma.Items.Components private void RefreshRecipients() { recipients.Clear(); - for (int i = 0; i < MaxWires; i++) + foreach (var wire in wires) { - if (wires[i] == null) continue; - Connection recipient = wires[i].OtherConnection(this); - if (recipient != null) recipients.Add(recipient); + Connection recipient = wire.OtherConnection(this); + if (recipient != null) { recipients.Add(recipient); } } recipientsDirty = false; } - public int FindEmptyIndex() - { - for (int i = 0; i < MaxWires; i++) - { - if (wires[i] == null) return i; - } - return -1; - } - - public int FindWireIndex(Wire wire) - { - for (int i = 0; i < MaxWires; i++) - { - if (wires[i] == wire) return i; - } - return -1; - } - - public int FindWireIndex(Item wireItem) - { - for (int i = 0; i < MaxWires; i++) - { - if (wires[i] == null && wireItem == null) return i; - if (wires[i] != null && wires[i].Item == wireItem) return i; - } - return -1; - } + public Wire FindWireByItem(Item it) + => Wires.FirstOrDefault(w => w.Item == it); + public bool WireSlotsAvailable() + => wires.Count < MaxWires; + public bool TryAddLink(Wire wire) { - for (int i = 0; i < MaxWires; i++) + if (wire is null + || wires.Contains(wire) + || !WireSlotsAvailable()) { - if (wires[i] == null) - { - SetWire(i, wire); - return true; - } + return false; } - return false; + wires.Add(wire); + return true; } - public void SetWire(int index, Wire wire) + public void DisconnectWire(Wire wire) { - Wire previousWire = wires[index]; - if (wire != previousWire && previousWire != null) - { - var otherConnection = previousWire.OtherConnection(this); - if (otherConnection != null) - { - //Change the connection grids or flag them for updating - if (IsPower && otherConnection.IsPower && Grid != null) - { - //Check if both connections belong to a larger grid - if (otherConnection.recipients.Count > 1 && recipients.Count > 1) - { - Powered.ChangedConnections.Add(otherConnection); - Powered.ChangedConnections.Add(this); - } - else if (recipients.Count > 1) - { - //This wire was the only one at the other grid - otherConnection.Grid?.RemoveConnection(otherConnection); - otherConnection.Grid = null; - } - else if (otherConnection.recipients.Count > 1) - { - Grid?.RemoveConnection(this); - Grid = null; - } - else if (Grid.Connections.Count == 2) - { - //Delete the grid as these were the only 2 devices - Powered.Grids.Remove(Grid.ID); - Grid = null; - otherConnection.Grid = null; - } - } - otherConnection.recipientsDirty = true; - } - } + if (wire == null || !wires.Contains(wire)) { return; } - wires[index] = wire; + var prevOtherConnection = wire.OtherConnection(this); + if (prevOtherConnection != null) + { + //Change the connection grids or flag them for updating + if (IsPower && prevOtherConnection.IsPower && Grid != null) + { + //Check if both connections belong to a larger grid + if (prevOtherConnection.recipients.Count > 1 && recipients.Count > 1) + { + Powered.ChangedConnections.Add(prevOtherConnection); + Powered.ChangedConnections.Add(this); + } + else if (recipients.Count > 1) + { + //This wire was the only one at the other grid + prevOtherConnection.Grid?.RemoveConnection(prevOtherConnection); + prevOtherConnection.Grid = null; + } + else if (prevOtherConnection.recipients.Count > 1) + { + Grid?.RemoveConnection(this); + Grid = null; + } + else if (Grid.Connections.Count == 2) + { + //Delete the grid as these were the only 2 devices + Powered.Grids.Remove(Grid.ID); + Grid = null; + prevOtherConnection.Grid = null; + } + } + prevOtherConnection.recipientsDirty = true; + } + wires.Remove(wire); recipientsDirty = true; - if (wire != null) + } + + public void ConnectWire(Wire wire) + { + if (wire == null || !TryAddLink(wire)) { return; } + ConnectionPanel.DisconnectedWires.Remove(wire); + var otherConnection = wire.OtherConnection(this); + if (otherConnection != null) { - - ConnectionPanel.DisconnectedWires.Remove(wire); - var otherConnection = wire.OtherConnection(this); - if (otherConnection != null) + //Set the other connection grid if a grid exists already + if (Powered.ValidPowerConnection(this, otherConnection)) { - //Set the other connection grid if a grid exists already - if (Powered.ValidPowerConnection(this, otherConnection)) + if (Grid == null && otherConnection.Grid != null) { - if (Grid == null && otherConnection.Grid != null) - { - otherConnection.Grid.AddConnection(this); - Grid = otherConnection.Grid; - } - else if (Grid != null && otherConnection.Grid == null) - { - Grid.AddConnection(otherConnection); - otherConnection.Grid = Grid; - } - else - { - //Flag change so that proper grids can be formed - Powered.ChangedConnections.Add(this); - Powered.ChangedConnections.Add(otherConnection); - } + otherConnection.Grid.AddConnection(this); + Grid = otherConnection.Grid; + } + else if (Grid != null && otherConnection.Grid == null) + { + Grid.AddConnection(otherConnection); + otherConnection.Grid = Grid; + } + else + { + //Flag change so that proper grids can be formed + Powered.ChangedConnections.Add(this); + Powered.ChangedConnections.Add(otherConnection); } - - otherConnection.recipientsDirty = true; } + + otherConnection.recipientsDirty = true; } + recipientsDirty = true; } public void SendSignal(Signal signal) { - for (int i = 0; i < MaxWires; i++) + foreach (var wire in wires) { - if (wires[i] == null) { continue; } - - Connection recipient = wires[i].OtherConnection(this); + Connection recipient = wire.OtherConnection(this); if (recipient == null) { continue; } if (recipient.item == this.item || signal.source?.LastSentSignalRecipients.LastOrDefault() == recipient) { continue; } @@ -350,35 +313,32 @@ namespace Barotrauma.Items.Components } } - for (int i = 0; i < MaxWires; i++) + foreach (var wire in wires) { - if (wires[i] == null) continue; - - wires[i].RemoveConnection(this); - wires[i] = null; + wire.RemoveConnection(this); recipientsDirty = true; } + wires.Clear(); } - public void ConnectLinked() + public void InitializeFromLoaded() { - if (wireId == null) return; + if (LoadedWireIds.Count == 0) { return; } - for (int i = 0; i < MaxWires; i++) + for (int i = 0; i < LoadedWireIds.Count; i++) { - if (wireId[i] == 0) { continue; } + if (!(Entity.FindEntityByID(LoadedWireIds[i]) is Item wireItem)) { continue; } - if (!(Entity.FindEntityByID(wireId[i]) is Item wireItem)) { continue; } - wires[i] = wireItem.GetComponent(); - recipientsDirty = true; - - if (wires[i] != null) + var wire = wireItem.GetComponent(); + if (wire != null && TryAddLink(wire)) { - if (wires[i].Item.body != null) wires[i].Item.body.Enabled = false; - wires[i].Connect(this, false, false); - wires[i].FixNodeEnds(); + if (wire.Item.body != null) wire.Item.body.Enabled = false; + wire.Connect(this, false, false); + wire.FixNodeEnds(); + recipientsDirty = true; } } + LoadedWireIds.Clear(); } @@ -386,19 +346,10 @@ namespace Barotrauma.Items.Components { XElement newElement = new XElement(IsOutput ? "output" : "input", new XAttribute("name", Name)); - Array.Sort(wires, delegate (Wire wire1, Wire wire2) + foreach (var wire in wires.OrderBy(w => w.Item.ID)) { - if (wire1 == null) return 1; - if (wire2 == null) return -1; - return wire1.Item.ID.CompareTo(wire2.Item.ID); - }); - - for (int i = 0; i < MaxWires; i++) - { - if (wires[i] == null) continue; - newElement.Add(new XElement("link", - new XAttribute("w", wires[i].Item.ID.ToString()))); + new XAttribute("w", wire.Item.ID.ToString()))); } parentElement.Add(newElement); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/ConnectionPanel.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/ConnectionPanel.cs index f7f7d38eb..41a02b481 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/ConnectionPanel.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/ConnectionPanel.cs @@ -49,7 +49,7 @@ namespace Barotrauma.Items.Components public bool TemporarilyLocked { - get { return Level.IsLoadedOutpost && item.GetComponent() != null; } + get { return Level.IsLoadedOutpost && (item.GetComponent()?.Docked ?? false); } } //connection panels can't be deactivated externally (by signals or status effects) @@ -99,7 +99,7 @@ namespace Barotrauma.Items.Components { foreach (Connection c in Connections) { - c.ConnectLinked(); + c.InitializeFromLoaded(); } if (disconnectedWireIds != null) @@ -286,25 +286,8 @@ namespace Barotrauma.Items.Components for (int i = 0; i < loadedConnections.Count && i < Connections.Count; i++) { - if (loadedConnections[i].wireId.Length == Connections[i].wireId.Length) - { - loadedConnections[i].wireId.CopyTo(Connections[i].wireId, 0); - } - else - { - //backwards compatibility when maximum number of wires has changed - foreach (ushort id in loadedConnections[i].wireId) - { - for (int j = 0; j < Connections[i].wireId.Length; j++) - { - if (Connections[i].wireId[j] == 0) - { - Connections[i].wireId[j] = id; - break; - } - } - } - } + Connections[i].LoadedWireIds.Clear(); + Connections[i].LoadedWireIds.AddRange(loadedConnections[i].LoadedWireIds); } disconnectedWireIds = element.GetAttributeUshortArray("disconnectedwires", Array.Empty()).ToList(); @@ -361,10 +344,8 @@ namespace Barotrauma.Items.Components DisconnectedWires.Clear(); foreach (Connection c in Connections) { - foreach (Wire wire in c.Wires) + foreach (Wire wire in c.Wires.ToArray()) { - if (wire == null) { continue; } - if (wire.OtherConnection(c) == null) //wire not connected to anything else { #if CLIENT @@ -408,13 +389,14 @@ namespace Barotrauma.Items.Components foreach (Connection connection in Connections) { + msg.WriteVariableUInt32((uint)connection.Wires.Count); foreach (Wire wire in connection.Wires) { msg.Write(wire?.Item == null ? (ushort)0 : wire.Item.ID); } } - msg.Write((ushort)DisconnectedWires.Count()); + msg.Write((ushort)DisconnectedWires.Count); foreach (Wire disconnectedWire in DisconnectedWires) { msg.Write(disconnectedWire.Item.ID); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/LightComponent.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/LightComponent.cs index df7bc1b0e..fd5ee7f13 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/LightComponent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/LightComponent.cs @@ -187,7 +187,7 @@ namespace Barotrauma.Items.Components set; } - public override void Move(Vector2 amount) + public override void Move(Vector2 amount, bool ignoreContacts = false) { #if CLIENT Light.Position += amount; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/OrComponent.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/OrComponent.cs deleted file mode 100644 index 3d3c7ab9d..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/OrComponent.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Xml.Linq; - -namespace Barotrauma.Items.Components -{ - class OrComponent : AndComponent - { - public OrComponent(Item item, ContentXElement element) - : base(item, element) - { - IsActive = true; - } - - public override void Update(float deltaTime, Camera cam) - { - bool state = false; - for (int i = 0; i < timeSinceReceived.Length; i++) - { - if (timeSinceReceived[i] <= timeFrame) { state = true; } - timeSinceReceived[i] += deltaTime; - } - - string signalOut = state ? output : falseOutput; - if (string.IsNullOrEmpty(signalOut)) - { - //deactivate the component if state is false and there's no false output (will be woken up by non-zero signals in ReceiveSignal) - if (!state) { IsActive = false; } - return; - } - - item.SendSignal(new Signal(signalOut, sender: signalSender[0] ?? signalSender[1]), "signal_out"); - } - } -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/WifiComponent.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/WifiComponent.cs index c95c55497..1240fb9cb 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/WifiComponent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/WifiComponent.cs @@ -135,7 +135,7 @@ namespace Barotrauma.Items.Components // = no point in receiving if (!LinkToChat) { - if (signalOutConnection == null || !signalOutConnection.Wires.Any(w => w != null)) + if (signalOutConnection == null || signalOutConnection.Wires.Count <= 0) { return false; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Wire.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Wire.cs index 19588f114..ec17bd33b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Wire.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Wire.cs @@ -143,12 +143,11 @@ namespace Barotrauma.Items.Components { if (connections[i] == null || connections[i].Item != item) { continue; } - foreach (Wire wire in connections[i].Wires) + if (connections[i].Wires.Contains(this)) { - if (wire != this) continue; SetConnectedDirty(); - connections[i].SetWire(connections[i].FindWireIndex(wire), null); + connections[i].DisconnectWire(this); } connections[i] = null; @@ -597,15 +596,16 @@ namespace Barotrauma.Items.Components for (int i = 0; i < 2; i++) { if (connections[i] == null) { continue; } - int wireIndex = connections[i].FindWireIndex(item); - if (wireIndex == -1) { continue; } + + var wire = connections[i].FindWireByItem(item); + if (wire is null) { continue; } #if SERVER if (!connections[i].Item.Removed && (!connections[i].Item.Submarine?.Loading ?? true) && (!Level.Loaded?.Generating ?? true)) { connections[i].Item.CreateServerEvent(connections[i].Item.GetComponent()); } #endif - connections[i].SetWire(wireIndex, null); + connections[i].DisconnectWire(wire); connections[i] = null; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/XorComponent.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/XorComponent.cs deleted file mode 100644 index 71981bb8b..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/XorComponent.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Xml.Linq; - -namespace Barotrauma.Items.Components -{ - class XorComponent : AndComponent - { - public XorComponent(Item item, ContentXElement element) - : base(item, element) - { - IsActive = true; - } - - public override void Update(float deltaTime, Camera cam) - { - int receivedInputs = 0; - for (int i = 0; i < timeSinceReceived.Length; i++) - { - if (timeSinceReceived[i] <= timeFrame) { receivedInputs += 1; } - timeSinceReceived[i] += deltaTime; - } - - bool state = receivedInputs == 1; - string signalOut = state ? output : falseOutput; - if (string.IsNullOrEmpty(signalOut)) - { - //deactivate the component if state is false and there's no false output (will be woken up by non-zero signals in ReceiveSignal) - if (!state) { IsActive = false; } - return; - } - - item.SendSignal(new Signal(signalOut, sender: signalSender[0] ?? signalSender[1]), "signal_out"); - } - } -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/TriggerComponent.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/TriggerComponent.cs index 582357d8f..5854fccb9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/TriggerComponent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/TriggerComponent.cs @@ -3,9 +3,8 @@ using FarseerPhysics.Dynamics; using FarseerPhysics.Dynamics.Contacts; using Microsoft.Xna.Framework; using System; -using System.Linq; using System.Collections.Generic; -using System.Xml.Linq; +using System.Linq; namespace Barotrauma.Items.Components { @@ -93,13 +92,11 @@ namespace Barotrauma.Items.Components 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) + PhysicsBody = new PhysicsBody(0.0f, 0.0f, Radius, 1.5f, BodyType.Static, Physics.CollisionWall, LevelTrigger.GetCollisionCategories(triggeredBy)) { - BodyType = BodyType.Static, - CollidesWith = LevelTrigger.GetCollisionCategories(triggeredBy), - CollisionCategories = Physics.CollisionWall, UserData = item }; + PhysicsBody.SetTransformIgnoreContacts(item.SimPosition, 0.0f); PhysicsBody.FarseerBody.SetIsSensor(true); PhysicsBody.FarseerBody.OnCollision += OnCollision; PhysicsBody.FarseerBody.OnSeparation += OnSeparation; @@ -215,12 +212,18 @@ namespace Barotrauma.Items.Components body.ApplyForce(force); } - public override void Move(Vector2 amount) + public override void Move(Vector2 amount, bool ignoreContacts = false) { - base.Move(amount); if (PhysicsBody != null) { - PhysicsBody.SetTransform(PhysicsBody.SimPosition + ConvertUnits.ToSimUnits(amount), 0.0f); + if (ignoreContacts) + { + PhysicsBody.SetTransformIgnoreContacts(PhysicsBody.SimPosition + ConvertUnits.ToSimUnits(amount), 0.0f); + } + else + { + PhysicsBody.SetTransform(PhysicsBody.SimPosition + ConvertUnits.ToSimUnits(amount), 0.0f); + } PhysicsBody.Submarine = item.Submarine; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs index 824c85659..a6720fe49 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs @@ -661,6 +661,7 @@ namespace Barotrauma.Items.Components while (neededPower > 0.0001f && batteries.Count > 0) { batteries.RemoveAll(b => b.Charge <= 0.0001f || b.MaxOutPut <= 0.0001f); + if (!batteries.Any()) { break; } float takePower = neededPower / batteries.Count; takePower = Math.Min(takePower, batteries.Min(b => Math.Min(b.Charge * 3600.0f, b.MaxOutPut))); foreach (PowerContainer battery in batteries) @@ -1151,8 +1152,12 @@ namespace Barotrauma.Items.Components foreach (Character enemy in Character.CharacterList) { // Ignore dead, friendly, and those that are inside the same sub - if (enemy.IsDead || !enemy.Enabled || enemy.Submarine == character.Submarine) { continue; } - if (enemy.Submarine != null && enemy.Submarine.TeamID == character.Submarine.TeamID) { continue; } + if (enemy.IsDead || !enemy.Enabled) { continue; } + if (character.Submarine != null) + { + if (enemy.Submarine == character.Submarine) { continue; } + 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; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs index 03f8e5d48..8df11b4de 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs @@ -115,7 +115,7 @@ namespace Barotrauma private readonly Quality qualityComponent; - private readonly ConcurrentQueue impactQueue = new ConcurrentQueue(); + private ConcurrentQueue impactQueue; //a dictionary containing lists of the status effects in all the components of the item private readonly bool[] hasStatusEffectsOfType; @@ -835,33 +835,35 @@ namespace Barotrauma var rand = new Random(ID); density = MathHelper.Lerp(minDensity, maxDensity, (float)rand.NextDouble()); } - body = new PhysicsBody(subElement, ConvertUnits.ToSimUnits(Position), Scale, density); - string collisionCategory = subElement.GetAttributeString("collisioncategory", null); + string collisionCategoryStr = subElement.GetAttributeString("collisioncategory", null); + + Category collisionCategory = Physics.CollisionItem; + Category collidesWith = Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionPlatform; if ((Prefab.DamagedByProjectiles || Prefab.DamagedByMeleeWeapons) && Condition > 0) { //force collision category to Character to allow projectiles and weapons to hit //(we could also do this by making the projectiles and weapons hit CollisionItem //and check if the collision should be ignored in the OnCollision callback, but //that'd make the hit detection more expensive because every item would be included) - body.CollisionCategories = Physics.CollisionCharacter; - body.CollidesWith = Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionPlatform | Physics.CollisionProjectile; + collisionCategory = Physics.CollisionCharacter; } - if (collisionCategory != null) + if (collisionCategoryStr != null) { - if (!Physics.TryParseCollisionCategory(collisionCategory, out Category cat)) + if (!Physics.TryParseCollisionCategory(collisionCategoryStr, out Category cat)) { - DebugConsole.ThrowError("Invalid collision category in item \"" + Name+"\" (" + collisionCategory + ")"); + DebugConsole.ThrowError("Invalid collision category in item \"" + Name+"\" (" + collisionCategoryStr + ")"); } else { - body.CollisionCategories = cat; + collisionCategory = cat; if (cat.HasFlag(Physics.CollisionCharacter)) { - body.CollidesWith = Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionPlatform | Physics.CollisionProjectile; + collisionCategory |= Physics.CollisionProjectile; } } } + body = new PhysicsBody(subElement, ConvertUnits.ToSimUnits(Position), Scale, density, collisionCategory, collidesWith, findNewContacts: false); body.FarseerBody.AngularDamping = subElement.GetAttributeFloat("angulardamping", 0.2f); body.FarseerBody.LinearDamping = subElement.GetAttributeFloat("lineardamping", 0.1f); body.UserData = this; @@ -1261,12 +1263,7 @@ namespace Barotrauma partial void SetActiveSpriteProjSpecific(); - public override void Move(Vector2 amount) - { - Move(amount, ignoreContacts: false); - } - - public void Move(Vector2 amount, bool ignoreContacts) + public override void Move(Vector2 amount, bool ignoreContacts = false) { if (!MathUtils.IsValid(amount)) { @@ -1289,7 +1286,7 @@ namespace Barotrauma } foreach (ItemComponent ic in components) { - ic.Move(amount); + ic.Move(amount, ignoreContacts); } if (body != null && (Submarine == null || !Submarine.Loading)) { FindHull(); } @@ -1703,9 +1700,12 @@ namespace Barotrauma public override void Update(float deltaTime, Camera cam) { - while (impactQueue.TryDequeue(out float impact)) + if (impactQueue != null) { - HandleCollision(impact); + while (impactQueue.TryDequeue(out float impact)) + { + HandleCollision(impact); + } } if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer && (!Submarine?.Loading ?? true)) @@ -1959,6 +1959,7 @@ namespace Barotrauma if (contact.FixtureA.Body == f1.Body) { normal = -normal; } float impact = Vector2.Dot(f1.Body.LinearVelocity, -normal); + impactQueue ??= new ConcurrentQueue(); impactQueue.Enqueue(impact); return true; @@ -2680,21 +2681,21 @@ namespace Barotrauma foreach (ItemComponent ic in components) { ic.Unequip(character); } } - public List> GetProperties() + public List<(object obj, SerializableProperty property)> GetProperties() { - List> allProperties = new List>(); + List<(object obj, SerializableProperty property)> allProperties = new List<(object obj, SerializableProperty property)>(); List itemProperties = SerializableProperty.GetProperties(this); foreach (var itemProperty in itemProperties) { - allProperties.Add(new Pair(this, itemProperty)); + allProperties.Add((this, itemProperty)); } foreach (ItemComponent ic in components) { List componentProperties = SerializableProperty.GetProperties(ic); foreach (var componentProperty in componentProperties) { - allProperties.Add(new Pair(ic, componentProperty)); + allProperties.Add((ic, componentProperty)); } } return allProperties; @@ -2708,13 +2709,13 @@ namespace Barotrauma SerializableProperty property = extraData.SerializableProperty; if (property != null) { - var propertyOwner = allProperties.Find(p => p.Second == property); + var propertyOwner = allProperties.Find(p => p.property == property); if (allProperties.Count > 1) { - msg.Write((byte)allProperties.FindIndex(p => p.Second == property)); + msg.Write((byte)allProperties.FindIndex(p => p.property == property)); } - object value = property.GetValue(propertyOwner.First); + object value = property.GetValue(propertyOwner.obj); if (value is string stringVal) { msg.Write(stringVal); @@ -2795,7 +2796,7 @@ namespace Barotrauma } } - private List> GetInGameEditableProperties(bool ignoreConditions = false) + private List<(object obj, SerializableProperty property)> GetInGameEditableProperties(bool ignoreConditions = false) { if (ignoreConditions) { @@ -2804,7 +2805,7 @@ namespace Barotrauma else { return GetProperties() - .Where(ce => ce.Second.GetAttribute().IsEditable(this)) + .Where(ce => ce.property.GetAttribute().IsEditable(this)) .Union(GetProperties()).ToList(); } } @@ -2823,8 +2824,8 @@ namespace Barotrauma } bool allowEditing = true; - object parentObject = allProperties[propertyIndex].First; - SerializableProperty property = allProperties[propertyIndex].Second; + object parentObject = allProperties[propertyIndex].obj; + SerializableProperty property = allProperties[propertyIndex].property; if (inGameEditableOnly && parentObject is ItemComponent ic) { if (!ic.AllowInGameEditing) { allowEditing = false; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs index 74532ffa6..e97bb6481 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs @@ -253,6 +253,12 @@ namespace Barotrauma public readonly float MinCondition; public readonly int MinAmount; public readonly int MaxAmount; + // Overrides min and max, if defined. + public readonly int Amount; + public readonly bool CampaignOnly; + public readonly bool NotCampaign; + public readonly bool TransferOnlyOnePerContainer; + public readonly bool AllowTransfersHere = true; public PreferredContainer(XElement element) { @@ -261,21 +267,26 @@ namespace Barotrauma SpawnProbability = element.GetAttributeFloat("spawnprobability", 0.0f); MinAmount = element.GetAttributeInt("minamount", 0); MaxAmount = Math.Max(MinAmount, element.GetAttributeInt("maxamount", 0)); + Amount = element.GetAttributeInt("amount", 0); MaxCondition = element.GetAttributeFloat("maxcondition", 100f); MinCondition = element.GetAttributeFloat("mincondition", 0f); + CampaignOnly = element.GetAttributeBool("campaignonly", CampaignOnly); + NotCampaign = element.GetAttributeBool("notcampaign", NotCampaign); + TransferOnlyOnePerContainer = element.GetAttributeBool("TransferOnlyOnePerContainer", TransferOnlyOnePerContainer); + AllowTransfersHere = element.GetAttributeBool("AllowTransfersHere", AllowTransfersHere); - if (element.Attribute("spawnprobability") == null) + if (element.GetAttribute("spawnprobability") == null) { //if spawn probability is not defined but amount is, assume the probability is 1 - if (MaxAmount > 0) + if (MaxAmount > 0 || Amount > 0) { SpawnProbability = 1.0f; } } - else if (element.Attribute("minamount") == null && element.Attribute("maxamount") == null) + else if (element.GetAttribute("minamount") == null && element.GetAttribute("maxamount") == null && element.GetAttribute("amount") == null) { //spawn probability defined but amount isn't, assume amount is 1 - MinAmount = MaxAmount = 1; + MinAmount = MaxAmount = Amount = 1; SpawnProbability = element.GetAttributeFloat("spawnprobability", 0.0f); } } @@ -600,6 +611,9 @@ namespace Barotrauma public ImmutableHashSet AllowDroppingOnSwapWith { get; private set; } + [Serialize(false, IsPropertySaveable.No)] + public bool DontTransferBetweenSubs { get; private set; } + protected override Identifier DetermineIdentifier(XElement element) { Identifier identifier = base.DetermineIdentifier(element); @@ -1084,7 +1098,7 @@ namespace Barotrauma //legacy support identifier = GenerateLegacyIdentifier(name); } - prefab = Find(p => p is ItemPrefab && p.Identifier == identifier) as ItemPrefab; + Prefabs.TryGet(identifier, out prefab); //not found, see if we can find a prefab with a matching alias if (prefab == null && !string.IsNullOrEmpty(name)) @@ -1104,12 +1118,13 @@ namespace Barotrauma return prefab; } - public bool IsContainerPreferred(Item item, ItemContainer targetContainer, out bool isPreferencesDefined, out bool isSecondary, bool requireConditionRequirement = false) + public bool IsContainerPreferred(Item item, ItemContainer targetContainer, out bool isPreferencesDefined, out bool isSecondary, bool requireConditionRequirement = false, bool checkTransferConditions = false) { isPreferencesDefined = PreferredContainers.Any(); isSecondary = false; if (!isPreferencesDefined) { return true; } - if (PreferredContainers.Any(pc => (!requireConditionRequirement || HasConditionRequirement(pc)) && IsItemConditionAcceptable(item, pc) && IsContainerPreferred(pc.Primary, targetContainer))) + if (PreferredContainers.Any(pc => (!requireConditionRequirement || HasConditionRequirement(pc)) && IsItemConditionAcceptable(item, pc) && + IsContainerPreferred(pc.Primary, targetContainer) && (!checkTransferConditions || CanBeTransferred(item.Prefab.Identifier, pc, targetContainer)))) { return true; } @@ -1132,6 +1147,8 @@ namespace Barotrauma } private bool IsItemConditionAcceptable(Item item, PreferredContainer pc) => item.ConditionPercentage >= pc.MinCondition && item.ConditionPercentage <= pc.MaxCondition; + private bool CanBeTransferred(Identifier item, PreferredContainer pc, ItemContainer targetContainer) => + pc.AllowTransfersHere && (!pc.TransferOnlyOnePerContainer || targetContainer.Inventory.AllItems.None(i => i.Prefab.Identifier == item)); 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)); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Creatures/BallastFloraBehavior.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Creatures/BallastFloraBehavior.cs index d6a3357f0..b5fd3d37c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Creatures/BallastFloraBehavior.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Creatures/BallastFloraBehavior.cs @@ -214,7 +214,7 @@ namespace Barotrauma.MapCreatures.Behavior [Serialize(400, IsPropertySaveable.Yes, "How much health the root has.")] public int RootHealth { get; set; } - [Serialize(0.0005f, IsPropertySaveable.Yes, "How fast the root's health regenerates per each grown branch.")] + [Serialize(0.00025f, IsPropertySaveable.Yes, "How fast the root's health regenerates per each grown branch.")] public float HealthRegenPerBranch { get; set; } [Serialize(30, IsPropertySaveable.Yes, "How far away from the root branches can regenerate health (in number of branches). The amount of regen decreases lineary further from the root.")] @@ -1148,7 +1148,7 @@ namespace Barotrauma.MapCreatures.Behavior return; } #if SERVER - if (!wasRemoved) + if (!wasRemoved && Parent != null && !Parent.Removed) { CreateNetworkMessage(new BranchRemoveEventData(branch)); } @@ -1199,7 +1199,10 @@ namespace Barotrauma.MapCreatures.Behavior StateMachine?.State?.Exit(); #if SERVER - CreateNetworkMessage(new KillEventData()); + if (Parent != null && !Parent.Removed) + { + CreateNetworkMessage(new KillEventData()); + } #endif } @@ -1220,8 +1223,11 @@ namespace Barotrauma.MapCreatures.Behavior } _entityList.Remove(this); -#if SERVER - CreateNetworkMessage(new RemoveEventData()); +#if SERVER + if (Parent != null && !Parent.Removed) + { + CreateNetworkMessage(new RemoveEventData()); + } #endif } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Gap.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Gap.cs index d0c9e6d0a..53c2a59eb 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Gap.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Gap.cs @@ -148,11 +148,12 @@ namespace Barotrauma InsertToList(); float blockerSize = ConvertUnits.ToSimUnits(Math.Max(rect.Width, rect.Height)) / 2; - outsideCollisionBlocker = GameMain.World.CreateEdge(-Vector2.UnitX * blockerSize, Vector2.UnitX * blockerSize); + outsideCollisionBlocker = GameMain.World.CreateEdge(-Vector2.UnitX * blockerSize, Vector2.UnitX * blockerSize, + BodyType.Static, + Physics.CollisionWall, + Physics.CollisionCharacter, + findNewContacts: false); outsideCollisionBlocker.UserData = $"CollisionBlocker (Gap {ID})"; - outsideCollisionBlocker.BodyType = BodyType.Static; - outsideCollisionBlocker.CollisionCategories = Physics.CollisionWall; - outsideCollisionBlocker.CollidesWith = Physics.CollisionCharacter; outsideCollisionBlocker.Enabled = false; #if CLIENT Resized += newRect => IsHorizontal = newRect.Width < newRect.Height; @@ -165,7 +166,7 @@ namespace Barotrauma return new Gap(rect, IsHorizontal, Submarine); } - public override void Move(Vector2 amount) + public override void Move(Vector2 amount, bool ignoreContacts = false) { if (!MathUtils.IsValid(amount)) { @@ -326,14 +327,6 @@ namespace Barotrauma { lerpedFlowForce = Vector2.Lerp(lerpedFlowForce, flowForce, deltaTime * 5.0f); } - if (FlowTargetHull != null && IsRoomToRoom) - { - var otherRoom = linkedTo[1] == FlowTargetHull ? linkedTo[0] : linkedTo[1]; - if ((otherRoom as Hull).Volume < FlowTargetHull.Volume) - { - lerpedFlowForce = Vector2.Zero; - } - } openedTimer -= deltaTime; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs index f4172ff8f..88cfa4d2c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs @@ -590,7 +590,7 @@ namespace Barotrauma return index; } - public override void Move(Vector2 amount) + public override void Move(Vector2 amount, bool ignoreContacts = false) { if (!MathUtils.IsValid(amount)) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/CaveGenerator.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/CaveGenerator.cs index 735066d2d..133da33e1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/CaveGenerator.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/CaveGenerator.cs @@ -157,9 +157,6 @@ namespace Barotrauma public static List GeneratePath(List targetCells, List cells) { - Stopwatch sw2 = new Stopwatch(); - sw2.Start(); - List pathCells = new List(); if (targetCells.Count == 0) { return pathCells; } @@ -213,10 +210,6 @@ namespace Barotrauma } while (currentCell != targetCells[targetCells.Count - 1] && iterationsLeft > 0); - - Debug.WriteLine("gettooclose: " + sw2.ElapsedMilliseconds + " ms"); - sw2.Restart(); - return pathCells; } @@ -351,7 +344,7 @@ namespace Barotrauma BodyType = BodyType.Static, CollisionCategories = Physics.CollisionLevel }; - GameMain.World.Add(cellBody); + GameMain.World.Add(cellBody, findNewContacts: false); for (int n = cells.Count - 1; n >= 0; n-- ) { @@ -429,7 +422,9 @@ namespace Barotrauma Vertices bodyVertices = new Vertices(triangles[i]); PolygonShape polygon = new PolygonShape(bodyVertices, 5.0f); - Fixture fixture = new Fixture(polygon) + Fixture fixture = new Fixture(polygon, + Physics.CollisionLevel, + Physics.CollisionAll) { UserData = cell }; @@ -446,8 +441,6 @@ namespace Barotrauma } cell.Body = cellBody; } - - cellBody.CollisionCategories = Physics.CollisionLevel; cellBody.ResetMassData(); return cellBody; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs index 763fb0324..426a8d9ef 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs @@ -299,11 +299,41 @@ namespace Barotrauma /// Random integers generated during the level generation. If these values differ between clients/server, /// it means the levels aren't identical for some reason and there will most likely be major ID mismatches. /// - public List EqualityCheckValues + public enum LevelGenStage { - get; - private set; - } = new List(); + GenStart, + TunnelGen, + VoronoiGen, + VoronoiGen2, + VoronoiGen3, + Ruins, + FloatingIce, + LevelBodies, + IceSpires, + TopAndBottom, + PlaceLevelObjects, + GenerateItems, + Finish + } + + private readonly Dictionary equalityCheckValues = Enum.GetValues(typeof(LevelGenStage)) + .Cast() + .Select(k => (k, 0)) + .ToDictionary(); + public IReadOnlyDictionary EqualityCheckValues => equalityCheckValues; + + private void GenerateEqualityCheckValue(LevelGenStage stage) + { + equalityCheckValues[stage] = Rand.Int(int.MaxValue, Rand.RandSync.ServerAndClient); + } + + private void ClearEqualityCheckValues() + { + foreach (LevelGenStage stage in Enum.GetValues(typeof(LevelGenStage))) + { + equalityCheckValues[stage] = 0; + } + } public List EntitiesBeforeGenerate { get; private set; } = new List(); public int EntityCountBeforeGenerate { get; private set; } @@ -404,7 +434,7 @@ namespace Barotrauma Loaded = this; Generating = true; - EqualityCheckValues.Clear(); + ClearEqualityCheckValues(); EntitiesBeforeGenerate = GetEntities().ToList(); EntityCountBeforeGenerate = EntitiesBeforeGenerate.Count(); @@ -414,7 +444,7 @@ namespace Barotrauma EndLocation = GameMain.GameSession?.EndLocation; } - EqualityCheckValues.Add(Rand.Int(int.MaxValue, Rand.RandSync.ServerAndClient)); + GenerateEqualityCheckValue(LevelGenStage.GenStart); LevelObjectManager = new LevelObjectManager(); @@ -477,7 +507,7 @@ namespace Barotrauma (int)MathHelper.Lerp(borders.Bottom - Math.Max(minMainPathWidth, ExitDistance * 1.5f), borders.Y + minMainPathWidth, GenerationParams.EndPosition.Y)); endExitPosition = new Point(endPosition.X, borders.Bottom); - EqualityCheckValues.Add(Rand.Int(int.MaxValue, Rand.RandSync.ServerAndClient)); + GenerateEqualityCheckValue(LevelGenStage.TunnelGen); //---------------------------------------------------------------------------------- //generate the initial nodes for the main path and smaller tunnels @@ -573,7 +603,7 @@ namespace Barotrauma GenerateAbyssArea(); GenerateCaves(mainPath); - EqualityCheckValues.Add(Rand.Int(int.MaxValue, Rand.RandSync.ServerAndClient)); + GenerateEqualityCheckValue(LevelGenStage.VoronoiGen); //---------------------------------------------------------------------------------- //generate voronoi sites @@ -678,7 +708,7 @@ namespace Barotrauma } } - EqualityCheckValues.Add(Rand.Int(int.MaxValue, Rand.RandSync.ServerAndClient)); + GenerateEqualityCheckValue(LevelGenStage.VoronoiGen2); //---------------------------------------------------------------------------------- // construct the voronoi graph and cells @@ -796,7 +826,7 @@ namespace Barotrauma startPosition.X = (int)pathCells[0].Site.Coord.X; startExitPosition.X = startPosition.X; - EqualityCheckValues.Add(Rand.Int(int.MaxValue, Rand.RandSync.ServerAndClient)); + GenerateEqualityCheckValue(LevelGenStage.VoronoiGen3); //---------------------------------------------------------------------------------- // remove unnecessary cells and create some holes at the bottom of the level @@ -1025,7 +1055,7 @@ namespace Barotrauma } } - EqualityCheckValues.Add(Rand.Int(int.MaxValue, Rand.RandSync.ServerAndClient)); + GenerateEqualityCheckValue(LevelGenStage.Ruins); //---------------------------------------------------------------------------------- // create some ruins @@ -1038,7 +1068,7 @@ namespace Barotrauma GenerateRuin(ruinPositions[i], mirror); } - EqualityCheckValues.Add(Rand.Int(int.MaxValue, Rand.RandSync.ServerAndClient)); + GenerateEqualityCheckValue(LevelGenStage.FloatingIce); //---------------------------------------------------------------------------------- // create floating ice chunks @@ -1070,7 +1100,7 @@ namespace Barotrauma } } - EqualityCheckValues.Add(Rand.Int(int.MaxValue, Rand.RandSync.ServerAndClient)); + GenerateEqualityCheckValue(LevelGenStage.LevelBodies); //---------------------------------------------------------------------------------- // generate the bodies and rendered triangles of the cells @@ -1175,7 +1205,7 @@ namespace Barotrauma } #endif - EqualityCheckValues.Add(Rand.Int(int.MaxValue, Rand.RandSync.ServerAndClient)); + GenerateEqualityCheckValue(LevelGenStage.IceSpires); //---------------------------------------------------------------------------------- // create ice spires @@ -1210,7 +1240,7 @@ namespace Barotrauma CreateOutposts(); - EqualityCheckValues.Add(Rand.Int(int.MaxValue, Rand.RandSync.ServerAndClient)); + GenerateEqualityCheckValue(LevelGenStage.TopAndBottom); //---------------------------------------------------------------------------------- // top barrier & sea floor @@ -1252,15 +1282,15 @@ namespace Barotrauma CreateWrecks(); CreateBeaconStation(); - EqualityCheckValues.Add(Rand.Int(int.MaxValue, Rand.RandSync.ServerAndClient)); + GenerateEqualityCheckValue(LevelGenStage.PlaceLevelObjects); LevelObjectManager.PlaceObjects(this, GenerationParams.LevelObjectAmount); - EqualityCheckValues.Add(Rand.Int(int.MaxValue, Rand.RandSync.ServerAndClient)); + GenerateEqualityCheckValue(LevelGenStage.GenerateItems); GenerateItems(); - EqualityCheckValues.Add(Rand.Int(int.MaxValue, Rand.RandSync.ServerAndClient)); + GenerateEqualityCheckValue(LevelGenStage.Finish); #if CLIENT backgroundCreatureManager.SpawnCreatures(this, GenerationParams.BackgroundCreatureAmount); @@ -2606,7 +2636,7 @@ namespace Barotrauma #if DEBUG DebugConsole.NewMessage("Level resources spawned: " + itemCount + "\n" + " Spawn points containing resources: " + PathPoints.Where(p => p.ClusterLocations.Any()).Count() + "/" + PathPoints.Count + "\n" + - " Total value: "+ PathPoints.Sum(p => p.ClusterLocations.Sum(c => c.Resources.Sum(r => r.Prefab.DefaultPrice?.Price ?? 0)))+" mk"); + " Total value: " + PathPoints.Sum(p => p.ClusterLocations.Sum(c => c.Resources.Sum(r => r.Prefab.DefaultPrice?.Price ?? 0))) + " mk"); if (AbyssResources.Count > 0) { @@ -3688,6 +3718,7 @@ namespace Barotrauma if (wreckFiles.None()) { DebugConsole.ThrowError("No wreck files found in the selected content packages!"); + Wrecks = new List(); return; } wreckFiles.Shuffle(Rand.RandSync.ServerAndClient); @@ -4112,12 +4143,12 @@ namespace Barotrauma int corpseCount = Rand.Range(Loaded.GenerationParams.MinCorpseCount, Loaded.GenerationParams.MaxCorpseCount + 1); var allSpawnPoints = WayPoint.WayPointList.FindAll(wp => wp.Submarine == wreck && wp.CurrentHull != null); var pathPoints = allSpawnPoints.FindAll(wp => wp.SpawnType == SpawnType.Path); - pathPoints.Shuffle(Rand.RandSync.Unsynced); var corpsePoints = allSpawnPoints.FindAll(wp => wp.SpawnType == SpawnType.Corpse); - corpsePoints.Shuffle(Rand.RandSync.Unsynced); - if (!corpsePoints.Any() && !pathPoints.Any()) { continue; } - + pathPoints.Shuffle(Rand.RandSync.Unsynced); + // Sort by job so that we first spawn those with a predefined job (might have special id cards) + corpsePoints = corpsePoints.OrderBy(p => p.AssignedJob == null).ThenBy(p => Rand.Value()).ToList(); + var usedJobs = new HashSet(); int spawnCounter = 0; for (int j = 0; j < corpseCount; j++) { @@ -4126,18 +4157,18 @@ namespace Barotrauma CorpsePrefab selectedPrefab; if (job == null) { - selectedPrefab = GetCorpsePrefab(p => p.SpawnPosition == PositionType.Wreck); + selectedPrefab = GetCorpsePrefab(usedJobs); } else { - selectedPrefab = GetCorpsePrefab(p => p.SpawnPosition == PositionType.Wreck && (p.Job == "any" || p.Job == job.Identifier)); + selectedPrefab = GetCorpsePrefab(usedJobs, p => p.Job == "any" || p.Job == job.Identifier); if (selectedPrefab == null) { corpsePoints.Remove(sp); pathPoints.Remove(sp); sp = corpsePoints.FirstOrDefault(sp => sp.AssignedJob == null) ?? pathPoints.FirstOrDefault(sp => sp.AssignedJob == null); // Deduce the job from the selected prefab - selectedPrefab = GetCorpsePrefab(p => p.SpawnPosition == PositionType.Wreck); + selectedPrefab = GetCorpsePrefab(usedJobs); } } if (selectedPrefab == null) { continue; } @@ -4156,28 +4187,65 @@ namespace Barotrauma pathPoints.Remove(sp); } - job ??= selectedPrefab.GetJobPrefab(); + job ??= selectedPrefab.GetJobPrefab(predicate: p => !usedJobs.Contains(p)); if (job == null) { continue; } - + if (job.Identifier == "captain" || job.Identifier == "engineer" || job.Identifier == "medicaldoctor" || job.Identifier == "securityofficer") + { + // Only spawn one of these jobs per wreck + usedJobs.Add(job); + } var characterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: job, randSync: Rand.RandSync.ServerAndClient); var corpse = Character.Create(CharacterPrefab.HumanSpeciesName, worldPos, ToolBox.RandomSeed(8), characterInfo, hasAi: true, createNetworkEvent: true); corpse.AnimController.FindHull(worldPos, setSubmarine: true); corpse.TeamID = CharacterTeamType.None; corpse.EnableDespawn = false; selectedPrefab.GiveItems(corpse, wreck); + corpse.CharacterHealth.ApplyAffliction(corpse.AnimController.MainLimb, AfflictionPrefab.OxygenLow.Instantiate(200)); + bool applyBurns = Rand.Value() < 0.1f; + bool applyDamage = Rand.Value() < 0.3f; + foreach (var limb in corpse.AnimController.Limbs) + { + if (applyDamage && (limb.type == LimbType.Head || Rand.Value() < 0.5f)) + { + var prefab = AfflictionPrefab.BiteWounds; + float max = prefab.MaxStrength / prefab.DamageOverlayAlpha; + corpse.CharacterHealth.ApplyAffliction(limb, prefab.Instantiate(GetStrength(limb, max))); + } + if (applyBurns) + { + var prefab = AfflictionPrefab.Burn; + float max = prefab.MaxStrength / prefab.BurnOverlayAlpha; + corpse.CharacterHealth.ApplyAffliction(limb, prefab.Instantiate(GetStrength(limb, max))); + } + + static float GetStrength(Limb limb, float max) + { + float strength = Rand.Range(0, max); + if (limb.type != LimbType.Head) + { + strength = Math.Min(strength, Rand.Range(0, max)); + } + return strength; + } + } corpse.Kill(CauseOfDeathType.Unknown, causeOfDeathAffliction: null, log: false); corpse.GiveIdCardTags(sp); -#if SERVER - if (selectedPrefab.MinMoney >= 0 && selectedPrefab.MaxMoney > 0) + + bool isServerOrSingleplayer = GameMain.IsSingleplayer || GameMain.NetworkMember is { IsServer: true }; + if (isServerOrSingleplayer && selectedPrefab.MinMoney >= 0 && selectedPrefab.MaxMoney > 0) { corpse.Wallet.Give(Rand.Range(selectedPrefab.MinMoney, selectedPrefab.MaxMoney, Rand.RandSync.Unsynced)); } -#endif + spawnCounter++; - static CorpsePrefab GetCorpsePrefab(Func predicate) + static CorpsePrefab GetCorpsePrefab(HashSet usedJobs, Func predicate = null) { - IEnumerable filteredPrefabs = CorpsePrefab.Prefabs.Where(predicate); + IEnumerable filteredPrefabs = CorpsePrefab.Prefabs.Where(p => + usedJobs.None(j => j.Identifier == p.Job.ToIdentifier()) && + p.SpawnPosition == PositionType.Wreck && + (predicate == null || predicate(p))); + return ToolBox.SelectWeightedRandom(filteredPrefabs.ToList(), filteredPrefabs.Select(p => p.Commonness).ToList(), Rand.RandSync.Unsynced); } } @@ -4270,7 +4338,7 @@ namespace Barotrauma blockedRects?.Clear(); EntitiesBeforeGenerate?.Clear(); - EqualityCheckValues?.Clear(); + ClearEqualityCheckValues(); if (Ruins != null) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelData.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelData.cs index 8356a9461..7313759d5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelData.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelData.cs @@ -20,7 +20,7 @@ namespace Barotrauma public readonly string Seed; - public float Difficulty; + public readonly float Difficulty; public readonly Biome Biome; @@ -141,8 +141,8 @@ namespace Barotrauma Seed = locationConnection.Locations[0].BaseName + locationConnection.Locations[1].BaseName; Biome = locationConnection.Biome; Type = LevelType.LocationConnection; - GenerationParams = LevelGenerationParams.GetRandom(Seed, LevelType.LocationConnection, Biome.Identifier); Difficulty = locationConnection.Difficulty; + GenerationParams = LevelGenerationParams.GetRandom(Seed, LevelType.LocationConnection, Difficulty, Biome.Identifier); float sizeFactor = MathUtils.InverseLerp( MapGenerationParams.Instance.SmallLevelConnectionLength, @@ -171,13 +171,13 @@ namespace Barotrauma /// /// Instantiates level data using the properties of the location /// - public LevelData(Location location) + public LevelData(Location location, float difficulty) { Seed = location.BaseName; Biome = location.Biome; Type = LevelType.Outpost; - GenerationParams = LevelGenerationParams.GetRandom(Seed, LevelType.Outpost, Biome.Identifier); - Difficulty = 0.0f; + Difficulty = difficulty; + GenerationParams = LevelGenerationParams.GetRandom(Seed, LevelType.Outpost, Difficulty, Biome.Identifier); var rand = new MTRandom(ToolBox.StringToInt(Seed)); int width = (int)MathHelper.Lerp(GenerationParams.MinWidth, GenerationParams.MaxWidth, (float)rand.NextDouble()); @@ -200,14 +200,16 @@ namespace Barotrauma (requireOutpost ? LevelType.Outpost : LevelType.LocationConnection) : generationParams.Type; - if (generationParams == null) { generationParams = LevelGenerationParams.GetRandom(seed, type); } + float selectedDifficulty = difficulty ?? Rand.Range(30.0f, 80.0f, Rand.RandSync.ServerAndClient); + + if (generationParams == null) { generationParams = LevelGenerationParams.GetRandom(seed, type, selectedDifficulty); } var biome = Biome.Prefabs.FirstOrDefault(b => generationParams?.AllowedBiomeIdentifiers.Contains(b.Identifier) ?? false) ?? Biome.Prefabs.GetRandom(Rand.RandSync.ServerAndClient); var levelData = new LevelData( seed, - difficulty ?? Rand.Range(30.0f, 80.0f, Rand.RandSync.ServerAndClient), + selectedDifficulty, Rand.Range(0.0f, 1.0f, Rand.RandSync.ServerAndClient), generationParams, biome); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelGenerationParams.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelGenerationParams.cs index cb6640f49..d4e56b2e1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelGenerationParams.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelGenerationParams.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; -using System.Xml.Linq; namespace Barotrauma { @@ -68,6 +67,27 @@ namespace Barotrauma set; } + [Serialize(1.0f, IsPropertySaveable.Yes, "If there are multiple level generation parameters available for a level in a given biome, their commonness determines how likely it is for one to get selected."), Editable(MinValueFloat = 0, MaxValueFloat = 100)] + public float Commonness + { + get; + set; + } + + [Serialize(0.0f, IsPropertySaveable.Yes, "The difficulty of the level has to be above or equal to this for these parameters to get chosen for the level."), Editable(MinValueFloat = 0, MaxValueFloat = 100)] + public float MinLevelDifficulty + { + get; + set; + } + + [Serialize(100.0f, IsPropertySaveable.Yes, "The difficulty of the level has to be below or equal to this for these parameters to get chosen for the level."), Editable(MinValueFloat = 0, MaxValueFloat = 100)] + public float MaxLevelDifficulty + { + get; + set; + } + [Serialize("27,30,36", IsPropertySaveable.Yes), Editable] public Color AmbientLightColor { @@ -536,7 +556,26 @@ namespace Barotrauma public Sprite WallSpriteDestroyed { get; private set; } public Sprite WaterParticles { get; private set; } - public static LevelGenerationParams GetRandom(string seed, LevelData.LevelType type, Identifier biome = default) + #warning TODO: this should be in the unit test project (#3164) + public static void CheckValidity() + { + foreach (Biome biome in Biome.Prefabs) + { + for (float i = 0.0f; i <= 100.0f; i += 0.5f) + { + if (GetRandom("test", LevelData.LevelType.LocationConnection, i, biome.Identifier) == null) + { + DebugConsole.ThrowError($"No suitable level generation parameters found for a specific type of level (level type: LocationConnection, difficulty: {i}, biome: {biome.Identifier})"); + } + if (GetRandom("test", LevelData.LevelType.Outpost, i, biome.Identifier) == null) + { + DebugConsole.ThrowError($"No suitable level generation parameters found for a specific type of level (level type: Outpost, difficulty: {i}, biome: {biome.Identifier})"); + } + } + } + } + + public static LevelGenerationParams GetRandom(string seed, LevelData.LevelType type, float difficulty, Identifier biome = default) { Rand.SetSyncedSeed(ToolBox.StringToInt(seed)); @@ -568,7 +607,16 @@ namespace Barotrauma } } - return matchingLevelParams.GetRandom(Rand.RandSync.ServerAndClient); + if (!matchingLevelParams.Any(lp => difficulty >= lp.MinLevelDifficulty && difficulty <= lp.MaxLevelDifficulty)) + { + DebugConsole.ThrowError($"Suitable level generation presets not found (biome \"{biome.IfEmpty("null".ToIdentifier())}\", type: \"{type}\", difficulty: {difficulty})"); + } + else + { + matchingLevelParams = matchingLevelParams.Where(lp => difficulty >= lp.MinLevelDifficulty && difficulty <= lp.MaxLevelDifficulty); + } + + return ToolBox.SelectWeightedRandom(matchingLevelParams, p => p.Commonness, Rand.RandSync.ServerAndClient); } public LevelGenerationParams(ContentXElement element, LevelGenerationParametersFile file) : base(file, element.GetAttributeIdentifier("identifier", element.Name.LocalName)) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelObjectManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelObjectManager.cs index 5fd728e3e..b07e2dd78 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelObjectManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelObjectManager.cs @@ -102,32 +102,44 @@ namespace Barotrauma foreach (Structure structure in Structure.WallList) { if (!structure.HasBody || structure.HiddenInGame) { continue; } + + LevelObjectPrefab.SpawnPosType spawnPosType = LevelObjectPrefab.SpawnPosType.None; if (level.Ruins.Any(r => r.Submarine == structure.Submarine)) { - 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; } + spawnPosType = LevelObjectPrefab.SpawnPosType.RuinWall; + } + else if (structure.Submarine?.Info?.Type == SubmarineType.Outpost) + { + spawnPosType = LevelObjectPrefab.SpawnPosType.OutpostWall; + } + else + { + continue; + } - availableSpawnPositions.Add(new SpawnPosition( - 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, - 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; } + 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(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)); - } + availableSpawnPositions.Add(new SpawnPosition( + new GraphEdge(new Vector2(structure.WorldRect.X, structure.WorldPosition.Y), new Vector2(structure.WorldRect.Right, structure.WorldPosition.Y)), + bottomHull ? Vector2.UnitY : -Vector2.UnitY, + spawnPosType, + 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, + spawnPosType, + leftHull ? Alignment.Left : Alignment.Right)); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelObjectPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelObjectPrefab.cs index 9917943c1..326b443ca 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelObjectPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelObjectPrefab.cs @@ -44,6 +44,7 @@ namespace Barotrauma MainPath = 64, LevelStart = 128, LevelEnd = 256, + OutpostWall = 512, Wall = MainPathWall | SidePathWall | CaveWall, } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelTrigger.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelTrigger.cs index 02140f9a5..480163ffa 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelTrigger.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelTrigger.cs @@ -6,7 +6,6 @@ using Microsoft.Xna.Framework; using System; using System.Collections.Generic; using System.Linq; -using System.Xml.Linq; namespace Barotrauma { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Location.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Location.cs index f39a30ee4..e17f274bf 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Location.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Location.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Xml.Linq; -using StoreBalanceStatus = Barotrauma.LocationType.StoreBalanceStatus; namespace Barotrauma { @@ -92,21 +91,8 @@ namespace Barotrauma public class StoreInfo { - private int balance; - public Identifier Identifier { get; } - public int Balance - { - get - { - return balance; - } - set - { - balance = value; - ActiveBalanceStatus = Location.GetStoreBalanceStatus(value); - } - } + public int Balance { get; set; } public List Stock { get; } = new List(); public List DailySpecials { get; } = new List(); public List RequestedGoods { get; } = new List(); @@ -114,8 +100,6 @@ namespace Barotrauma /// In percentages. Larger values make buying more expensive and selling less profitable, and vice versa. /// public int PriceModifier { get; set; } - public StoreBalanceStatus ActiveBalanceStatus { get; private set; } - public Color BalanceColor => ActiveBalanceStatus.Color; public Location Location { get; } private StoreInfo(Location location) @@ -298,14 +282,7 @@ namespace Barotrauma price = Location.DailySpecialPriceModifier * price; } // Adjust by current location reputation - if (Location.Reputation.Value > 0.0f) - { - price = MathHelper.Lerp(1.0f, 1.0f - Location.StoreMaxReputationModifier, Location.Reputation.Value / Location.Reputation.MaxReputation) * price; - } - else - { - price = MathHelper.Lerp(1.0f, 1.0f + Location.StoreMaxReputationModifier, Location.Reputation.Value / Location.Reputation.MinReputation) * price; - } + price *= Location.GetStoreReputationModifier(true); // Price should never go below 1 mk return Math.Max((int)price, 1); } @@ -319,22 +296,13 @@ namespace Barotrauma float price = Location.StoreSellPriceModifier * priceInfo.Price; // Adjust by random price modifier price = (100 - PriceModifier) / 100.0f * price; - // Adjust by current store balance - price = ActiveBalanceStatus.SellPriceModifier * price; // Adjust by requested good status if (considerRequestedGoods && RequestedGoods.Contains(item)) { price = Location.RequestGoodPriceModifier * price; } // Adjust by current location reputation - if (Location.Reputation.Value > 0.0f) - { - price = MathHelper.Lerp(1.0f, 1.0f + Location.StoreMaxReputationModifier, Location.Reputation.Value / Location.Reputation.MaxReputation) * price; - } - else - { - price = MathHelper.Lerp(1.0f, 1.0f - Location.StoreMaxReputationModifier, Location.Reputation.Value / Location.Reputation.MinReputation) * price; - } + price *= Location.GetStoreReputationModifier(false); // Price should never go below 1 mk return Math.Max((int)price, 1); } @@ -353,7 +321,6 @@ namespace Barotrauma private float RequestGoodPriceModifier => Type.RequestGoodPriceModifier; public int StoreInitialBalance => Type.StoreInitialBalance; private int StorePriceModifierRange => Type.StorePriceModifierRange; - private List StoreBalanceStatuses => Type.StoreBalanceStatuses; /// /// How many map progress steps it takes before the discounts should be updated. @@ -1224,6 +1191,32 @@ namespace Barotrauma } } + public float GetStoreReputationModifier(bool buying) + { + if (buying) + { + if (Reputation.Value > 0.0f) + { + return MathHelper.Lerp(1.0f, 1.0f - StoreMaxReputationModifier, Reputation.Value / Reputation.MaxReputation); + } + else + { + return MathHelper.Lerp(1.0f, 1.0f + StoreMaxReputationModifier, Reputation.Value / Reputation.MinReputation); + } + } + else + { + if (Reputation.Value > 0.0f) + { + return MathHelper.Lerp(1.0f, 1.0f + StoreMaxReputationModifier, Reputation.Value / Reputation.MaxReputation); + } + else + { + return MathHelper.Lerp(1.0f, 1.0f - StoreMaxReputationModifier, Reputation.Value / Reputation.MinReputation); + } + } + } + public int GetExtraSpecialSalesCount() { var characters = GameSession.GetSessionCrewCharacters(CharacterType.Both); @@ -1231,21 +1224,6 @@ namespace Barotrauma return characters.Max(c => (int)c.GetStatValue(StatTypes.ExtraSpecialSalesCount)); } - public StoreBalanceStatus GetStoreBalanceStatus(int balance) - { - StoreBalanceStatus nextStatus = StoreBalanceStatuses[0]; - for (int i = 1; i < StoreBalanceStatuses.Count; i++) - { - var status = StoreBalanceStatuses[i]; - if (status.PercentageOfInitialBalance < nextStatus.PercentageOfInitialBalance && - ((float)balance / StoreInitialBalance) < status.PercentageOfInitialBalance) - { - nextStatus = status; - } - } - return nextStatus; - } - public void Discover(bool checkTalents = true) { if (Discovered) { return; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/LocationType.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/LocationType.cs index e81e2795d..efa64c5d4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/LocationType.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/LocationType.cs @@ -88,27 +88,6 @@ namespace Barotrauma public int DailySpecialsCount { get; } = 1; public int RequestedGoodsCount { get; } = 1; - public List StoreBalanceStatuses { get; } = new List() - { - new StoreBalanceStatus(1.0f, 1.0f, Color.White), - new StoreBalanceStatus(0.5f, 0.75f, Color.Orange), - new StoreBalanceStatus(0.25f, 0.2f, Color.Red) - }; - - public struct StoreBalanceStatus - { - public float PercentageOfInitialBalance { get; } - public float SellPriceModifier { get; } - public Color Color { get; } - - public StoreBalanceStatus(float percentage, float sellPriceModifier, Color color) - { - PercentageOfInitialBalance = percentage; - SellPriceModifier = sellPriceModifier; - Color = color; - } - } - public override string ToString() { return $"LocationType (" + Identifier + ")"; @@ -208,18 +187,6 @@ namespace Barotrauma RequestGoodPriceModifier = subElement.GetAttributeFloat("requestgoodpricemodifier", RequestGoodPriceModifier); StoreInitialBalance = subElement.GetAttributeInt("initialbalance", StoreInitialBalance); StorePriceModifierRange = subElement.GetAttributeInt("pricemodifierrange", StorePriceModifierRange); - var balanceStatusElements = subElement.GetChildElements("balancestatus"); - if (balanceStatusElements.Any()) - { - StoreBalanceStatuses.Clear(); - foreach (var balanceStatusElement in balanceStatusElements) - { - float percentage = balanceStatusElement.GetAttributeFloat("percentage", 1.0f); - float modifier = balanceStatusElement.GetAttributeFloat("sellpricemodifier", 1.0f); - Color color = balanceStatusElement.GetAttributeColor("color", Color.White); - StoreBalanceStatuses.Add(new StoreBalanceStatus(percentage, modifier, color)); - } - } DailySpecialsCount = subElement.GetAttributeInt("dailyspecialscount", DailySpecialsCount); RequestedGoodsCount = subElement.GetAttributeInt("requestedgoodscount", RequestedGoodsCount); break; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Map.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Map.cs index 478350b10..192065b94 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Map.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Map.cs @@ -238,6 +238,16 @@ namespace Barotrauma } System.Diagnostics.Debug.Assert(StartLocation != null, "Start location not assigned after level generation."); + //ensure all paths from the starting location have 0 difficulty to make the 1st campaign round very easy + foreach (var locationConnection in StartLocation.Connections) + { + if (locationConnection.Difficulty > 0.0f) + { + locationConnection.Difficulty = 0.0f; + locationConnection.LevelData = new LevelData(locationConnection); + } + } + CurrentLocation.Discover(true); CurrentLocation.CreateStores(); @@ -509,25 +519,13 @@ namespace Barotrauma 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.LevelData = new LevelData(location, MathHelper.Clamp(location.MapPosition.X / Width * 100, 0.0f, 100.0f)); location.UnlockInitialMissions(); } foreach (LocationConnection connection in Connections) { connection.LevelData = new LevelData(connection); } - - float GetLevelDifficulty(float areaDifficulty) - { - const float CurveModifier = 1.5f; - const float DifficultyMultiplier = 1.14f; - const float BaseDifficulty = -3f; - return (float)(1 - Math.Pow(1 - areaDifficulty, CurveModifier)) * DifficultyMultiplier * 100f + BaseDifficulty; - } } partial void GenerateLocationConnectionVisuals(); @@ -1015,8 +1013,7 @@ namespace Barotrauma { string prevName = location.Name; - var newType = LocationType.Prefabs[change.ChangeToType]; - if (newType == null) + if (!LocationType.Prefabs.TryGet(change.ChangeToType, out var newType)) { DebugConsole.ThrowError($"Failed to change the type of the location \"{location.Name}\". Location type \"{change.ChangeToType}\" not found."); return false; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs index fb989ec73..e3b3d3d81 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs @@ -291,7 +291,7 @@ namespace Barotrauma } } - public virtual void Move(Vector2 amount) + public virtual void Move(Vector2 amount, bool ignoreContacts = false) { rect.X += (int)amount.X; rect.Y += (int)amount.Y; @@ -491,25 +491,33 @@ namespace Barotrauma protected void InsertToList() { - int i = 0; - if (Sprite == null) { mapEntityList.Add(this); return; } + int i = 0; while (i < mapEntityList.Count) { i++; - - Sprite existingSprite = mapEntityList[i - 1].Sprite; - if (existingSprite == null) continue; -#if CLIENT - if (existingSprite.Texture == this.Sprite.Texture) break; -#endif + if (mapEntityList[i - 1]?.Prefab == Prefab) + { + mapEntityList.Insert(i, this); + return; + } } +#if CLIENT + i = 0; + while (i < mapEntityList.Count) + { + i++; + Sprite existingSprite = mapEntityList[i - 1].Sprite; + if (existingSprite == null) { continue; } + if (existingSprite.Texture == this.Sprite.Texture) { break; } + } +#endif mapEntityList.Insert(i, this); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostGenerator.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostGenerator.cs index 295b92751..8e598475e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostGenerator.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostGenerator.cs @@ -1077,7 +1077,7 @@ namespace Barotrauma { foreach (Connection c in gapToRemove.ConnectedDoor.Item.Connections) { - c.Wires.ForEach(w => w?.Item.Remove()); + c.Wires.ToArray().ForEach(w => w?.Item.Remove()); } } @@ -1428,7 +1428,7 @@ namespace Barotrauma { foreach (Connection connection in linkedItem.Connections) { - foreach (Wire w in connection.Wires) + foreach (Wire w in connection.Wires.ToArray()) { w?.Item.Remove(); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs index d3901b682..b973831de 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs @@ -354,7 +354,7 @@ namespace Barotrauma private set; } - public override void Move(Vector2 amount) + public override void Move(Vector2 amount, bool ignoreContacts = false) { if (!MathUtils.IsValid(amount)) { @@ -377,7 +377,15 @@ namespace Barotrauma Vector2 simAmount = ConvertUnits.ToSimUnits(amount); foreach (Body b in Bodies) { - b.SetTransform(b.Position + simAmount, b.Rotation); + Vector2 pos = b.Position + simAmount; + if (ignoreContacts) + { + b.SetTransformIgnoreContacts(ref pos, b.Rotation); + } + else + { + b.SetTransform(pos, b.Rotation); + } } } @@ -1208,7 +1216,7 @@ namespace Barotrauma private void UpdateSections() { - if (Bodies == null) return; + if (Bodies == null) { return; } foreach (Body b in Bodies) { GameMain.World.Remove(b); @@ -1281,9 +1289,9 @@ namespace Barotrauma Body newBody = GameMain.World.CreateRectangle( ConvertUnits.ToSimUnits(rect.Width), ConvertUnits.ToSimUnits(rect.Height), - 1.5f); - newBody.BodyType = BodyType.Static; - //newBody.Position = ConvertUnits.ToSimUnits(new Vector2(rect.X + rect.Width / 2.0f, rect.Y - rect.Height / 2.0f)); + 1.5f, + bodyType: BodyType.Static, + findNewContacts: false); newBody.Friction = 0.5f; newBody.OnCollision += OnWallCollision; newBody.CollisionCategories = (Prefab.Platform) ? Physics.CollisionPlatform : Physics.CollisionWall; @@ -1292,15 +1300,16 @@ namespace Barotrauma Vector2 structureCenter = ConvertUnits.ToSimUnits(Position); if (BodyRotation != 0.0f) { - newBody.Position = structureCenter + bodyOffset + new Vector2( + Vector2 pos = structureCenter + bodyOffset + new Vector2( (float)Math.Cos(IsHorizontal ? -BodyRotation : MathHelper.PiOver2 - BodyRotation), (float)Math.Sin(IsHorizontal ? -BodyRotation : MathHelper.PiOver2 - BodyRotation)) * ConvertUnits.ToSimUnits(diffFromCenter); - newBody.Rotation = -BodyRotation; + newBody.SetTransformIgnoreContacts(ref pos, -BodyRotation); } else { - newBody.Position = structureCenter + (IsHorizontal ? Vector2.UnitX : Vector2.UnitY) * ConvertUnits.ToSimUnits(diffFromCenter) + bodyOffset; + Vector2 pos = structureCenter + (IsHorizontal ? Vector2.UnitX : Vector2.UnitY) * ConvertUnits.ToSimUnits(diffFromCenter) + bodyOffset; + newBody.SetTransformIgnoreContacts(ref pos, newBody.Rotation); } if (createConvexHull) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs index a046b1098..b5f7d87ba 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs @@ -1302,189 +1302,198 @@ namespace Barotrauma public Submarine(SubmarineInfo info, bool showWarningMessages = true, Func> loadEntities = null, IdRemap linkedRemap = null) : base(null, Entity.NullEntityID) { Loading = true; - - loaded.Add(this); - - Info = new SubmarineInfo(info); - - ConnectedDockingPorts = new Dictionary(); - - //place the sub above the top of the level - HiddenSubPosition = HiddenSubStartPosition; - if (GameMain.GameSession != null && GameMain.GameSession.LevelData != null) + GameMain.World.Enabled = false; + try { - HiddenSubPosition += Vector2.UnitY * GameMain.GameSession.LevelData.Size.Y; - } + loaded.Add(this); - foreach (Submarine sub in loaded) - { - HiddenSubPosition += Vector2.UnitY * (sub.Borders.Height + 5000.0f); - } + Info = new SubmarineInfo(info); - IdOffset = IdRemap.DetermineNewOffset(); + ConnectedDockingPorts = new Dictionary(); - List newEntities = new List(); - if (loadEntities == null) - { - if (Info.SubmarineElement != null) + //place the sub above the top of the level + HiddenSubPosition = HiddenSubStartPosition; + if (GameMain.GameSession != null && GameMain.GameSession.LevelData != null) { - newEntities = MapEntity.LoadAll(this, Info.SubmarineElement, Info.FilePath, IdOffset); - } - } - else - { - newEntities = loadEntities(this); - newEntities.ForEach(me => me.Submarine = this); - } - - if (newEntities != null) - { - foreach (var e in newEntities) - { - if (linkedRemap != null) { e.ResolveLinks(linkedRemap); } - e.unresolvedLinkedToID = null; - } - } - - Vector2 center = Vector2.Zero; - var matchingHulls = Hull.HullList.FindAll(h => h.Submarine == this); - - if (matchingHulls.Any()) - { - Vector2 topLeft = new Vector2(matchingHulls[0].Rect.X, matchingHulls[0].Rect.Y); - Vector2 bottomRight = new Vector2(matchingHulls[0].Rect.X, matchingHulls[0].Rect.Y); - foreach (Hull hull in matchingHulls) - { - if (hull.Rect.X < topLeft.X) topLeft.X = hull.Rect.X; - if (hull.Rect.Y > topLeft.Y) topLeft.Y = hull.Rect.Y; - - if (hull.Rect.Right > bottomRight.X) bottomRight.X = hull.Rect.Right; - if (hull.Rect.Y - hull.Rect.Height < bottomRight.Y) bottomRight.Y = hull.Rect.Y - hull.Rect.Height; + HiddenSubPosition += Vector2.UnitY * GameMain.GameSession.LevelData.Size.Y; } - center = (topLeft + bottomRight) / 2.0f; - center.X -= center.X % GridSize.X; - center.Y -= center.Y % GridSize.Y; - - RepositionEntities(-center, MapEntity.mapEntityList.Where(me => me.Submarine == this)); - - subBody = new SubmarineBody(this, showWarningMessages); - subBody.SetPosition(HiddenSubPosition); - - if (info.IsOutpost) + foreach (Submarine sub in loaded) { - ShowSonarMarker = false; - PhysicsBody.FarseerBody.BodyType = BodyType.Static; - TeamID = CharacterTeamType.FriendlyNPC; + HiddenSubPosition += Vector2.UnitY * (sub.Borders.Height + 5000.0f); + } - bool indestructible = - GameMain.NetworkMember != null && - !GameMain.NetworkMember.ServerSettings.DestructibleOutposts && - !(info.OutpostGenerationParams?.AlwaysDestructible ?? false); + IdOffset = IdRemap.DetermineNewOffset(); - foreach (MapEntity me in MapEntity.mapEntityList) + List newEntities = new List(); + if (loadEntities == null) + { + if (Info.SubmarineElement != null) { - if (me.Submarine != this) { continue; } - if (me is Item item) - { - item.SpawnedInCurrentOutpost = info.OutpostGenerationParams != null; - item.AllowStealing = info.OutpostGenerationParams?.AllowStealing ?? true; - if (item.GetComponent() != null && indestructible) - { - item.Indestructible = true; - } - foreach (ItemComponent ic in item.Components) - { - if (ic is ConnectionPanel connectionPanel) - { - //prevent rewiring - if (info.OutpostGenerationParams != null && !info.OutpostGenerationParams.AlwaysRewireable) - { - connectionPanel.Locked = true; - } - } - else if (ic is Holdable holdable && holdable.Attached && item.GetComponent() == null) - { - //prevent deattaching items from walls -#if CLIENT - if (GameMain.GameSession?.GameMode is TutorialMode) { continue; } -#endif - holdable.CanBePicked = false; - holdable.CanBeSelected = false; - } - } - } - else if (me is Structure structure && structure.Prefab.IndestructibleInOutposts && indestructible) - { - structure.Indestructible = true; - } + newEntities = MapEntity.LoadAll(this, Info.SubmarineElement, Info.FilePath, IdOffset); } } - else if (info.IsRuin) + else { - ShowSonarMarker = false; - PhysicsBody.FarseerBody.BodyType = BodyType.Static; + newEntities = loadEntities(this); + newEntities.ForEach(me => me.Submarine = this); } - } - if (entityGrid != null) - { - Hull.EntityGrids.Remove(entityGrid); - entityGrid = null; - } - entityGrid = Hull.GenerateEntityGrid(this); - - for (int i = 0; i < MapEntity.mapEntityList.Count; i++) - { - if (MapEntity.mapEntityList[i].Submarine != this) { continue; } - MapEntity.mapEntityList[i].Move(HiddenSubPosition); - } - - Loading = false; - - MapEntity.MapLoaded(newEntities, true); - foreach (MapEntity me in MapEntity.mapEntityList) - { - if (me is LinkedSubmarine linkedSub && linkedSub.Submarine == this) + if (newEntities != null) { - linkedSub.LinkDummyToMainSubmarine(); + foreach (var e in newEntities) + { + if (linkedRemap != null) { e.ResolveLinks(linkedRemap); } + e.unresolvedLinkedToID = null; + } } - } - foreach (Hull hull in matchingHulls) - { - if (string.IsNullOrEmpty(hull.RoomName))// || !hull.RoomName.Contains("roomname.", StringComparison.OrdinalIgnoreCase)) + Vector2 center = Vector2.Zero; + var matchingHulls = Hull.HullList.FindAll(h => h.Submarine == this); + + if (matchingHulls.Any()) { - hull.RoomName = hull.CreateRoomName(); - } - } + Vector2 topLeft = new Vector2(matchingHulls[0].Rect.X, matchingHulls[0].Rect.Y); + Vector2 bottomRight = new Vector2(matchingHulls[0].Rect.X, matchingHulls[0].Rect.Y); + foreach (Hull hull in matchingHulls) + { + if (hull.Rect.X < topLeft.X) topLeft.X = hull.Rect.X; + if (hull.Rect.Y > topLeft.Y) topLeft.Y = hull.Rect.Y; - if (GameMain.GameSession?.Campaign?.UpgradeManager != null) - { - GameMain.GameSession.Campaign.UpgradeManager.OnUpgradesChanged += ResetCrushDepth; - } + if (hull.Rect.Right > bottomRight.X) bottomRight.X = hull.Rect.Right; + if (hull.Rect.Y - hull.Rect.Height < bottomRight.Y) bottomRight.Y = hull.Rect.Y - hull.Rect.Height; + } + + center = (topLeft + bottomRight) / 2.0f; + center.X -= center.X % GridSize.X; + center.Y -= center.Y % GridSize.Y; + + RepositionEntities(-center, MapEntity.mapEntityList.Where(me => me.Submarine == this)); + + subBody = new SubmarineBody(this, showWarningMessages); + Vector2 pos = ConvertUnits.ToSimUnits(HiddenSubPosition); + subBody.Body.FarseerBody.SetTransformIgnoreContacts(ref pos, 0.0f); + + if (info.IsOutpost) + { + ShowSonarMarker = false; + PhysicsBody.FarseerBody.BodyType = BodyType.Static; + TeamID = CharacterTeamType.FriendlyNPC; + + bool indestructible = + GameMain.NetworkMember != null && + !GameMain.NetworkMember.ServerSettings.DestructibleOutposts && + !(info.OutpostGenerationParams?.AlwaysDestructible ?? false); + + foreach (MapEntity me in MapEntity.mapEntityList) + { + if (me.Submarine != this) { continue; } + if (me is Item item) + { + item.SpawnedInCurrentOutpost = info.OutpostGenerationParams != null; + item.AllowStealing = info.OutpostGenerationParams?.AllowStealing ?? true; + if (item.GetComponent() != null && indestructible) + { + item.Indestructible = true; + } + foreach (ItemComponent ic in item.Components) + { + if (ic is ConnectionPanel connectionPanel) + { + //prevent rewiring + if (info.OutpostGenerationParams != null && !info.OutpostGenerationParams.AlwaysRewireable) + { + connectionPanel.Locked = true; + } + } + else if (ic is Holdable holdable && holdable.Attached && item.GetComponent() == null) + { + //prevent deattaching items from walls +#if CLIENT + if (GameMain.GameSession?.GameMode is TutorialMode) { continue; } +#endif + holdable.CanBePicked = false; + holdable.CanBeSelected = false; + } + } + } + else if (me is Structure structure && structure.Prefab.IndestructibleInOutposts && indestructible) + { + structure.Indestructible = true; + } + } + } + else if (info.IsRuin) + { + ShowSonarMarker = false; + PhysicsBody.FarseerBody.BodyType = BodyType.Static; + } + } + + if (entityGrid != null) + { + Hull.EntityGrids.Remove(entityGrid); + entityGrid = null; + } + entityGrid = Hull.GenerateEntityGrid(this); + + for (int i = 0; i < MapEntity.mapEntityList.Count; i++) + { + if (MapEntity.mapEntityList[i].Submarine != this) { continue; } + MapEntity.mapEntityList[i].Move(HiddenSubPosition, ignoreContacts: true); + } + + Loading = false; + + MapEntity.MapLoaded(newEntities, true); + foreach (MapEntity me in MapEntity.mapEntityList) + { + if (me is LinkedSubmarine linkedSub && linkedSub.Submarine == this) + { + linkedSub.LinkDummyToMainSubmarine(); + } + } + + foreach (Hull hull in matchingHulls) + { + if (string.IsNullOrEmpty(hull.RoomName))// || !hull.RoomName.Contains("roomname.", StringComparison.OrdinalIgnoreCase)) + { + hull.RoomName = hull.CreateRoomName(); + } + } + + if (GameMain.GameSession?.Campaign?.UpgradeManager != null) + { + GameMain.GameSession.Campaign.UpgradeManager.OnUpgradesChanged += ResetCrushDepth; + } #if CLIENT - GameMain.LightManager.OnMapLoaded(); + GameMain.LightManager.OnMapLoaded(); #endif - //if the sub was made using an older version, - //halve the brightness of the lights to make them look (almost) right on the new lighting formula - if (showWarningMessages && - !string.IsNullOrEmpty(Info.FilePath) && - Screen.Selected != GameMain.SubEditorScreen && - (Info.GameVersion == null || Info.GameVersion < new Version("0.8.9.0"))) - { - DebugConsole.ThrowError("The submarine \"" + Info.Name + "\" was made using an older version of the Barotrauma that used a different formula to calculate the lighting. " - + "The game automatically adjusts the lights make them look better with the new formula, but it's recommended to open the submarine in the submarine editor and make sure everything looks right after the automatic conversion."); - foreach (Item item in Item.ItemList) + //if the sub was made using an older version, + //halve the brightness of the lights to make them look (almost) right on the new lighting formula + if (showWarningMessages && + !string.IsNullOrEmpty(Info.FilePath) && + Screen.Selected != GameMain.SubEditorScreen && + (Info.GameVersion == null || Info.GameVersion < new Version("0.8.9.0"))) { - if (item.Submarine != this) continue; - if (item.ParentInventory != null || item.body != null) continue; - var lightComponent = item.GetComponent(); - if (lightComponent != null) lightComponent.LightColor = new Color(lightComponent.LightColor, lightComponent.LightColor.A / 255.0f * 0.5f); + DebugConsole.ThrowError("The submarine \"" + Info.Name + "\" was made using an older version of the Barotrauma that used a different formula to calculate the lighting. " + + "The game automatically adjusts the lights make them look better with the new formula, but it's recommended to open the submarine in the submarine editor and make sure everything looks right after the automatic conversion."); + foreach (Item item in Item.ItemList) + { + if (item.Submarine != this) continue; + if (item.ParentInventory != null || item.body != null) continue; + var lightComponent = item.GetComponent(); + if (lightComponent != null) lightComponent.LightColor = new Color(lightComponent.LightColor, lightComponent.LightColor.A / 255.0f * 0.5f); + } } + GenerateOutdoorNodes(); + } + finally + { + Loading = false; + GameMain.World.Enabled = true; } - GenerateOutdoorNodes(); } protected override ushort DetermineID(ushort id, Submarine submarine) @@ -1495,10 +1504,7 @@ namespace Barotrauma public static Submarine Load(SubmarineInfo info, bool unloadPrevious, IdRemap linkedRemap = null) { if (unloadPrevious) { Unload(); } - - Submarine sub = new Submarine(info, false, linkedRemap: linkedRemap); - - return sub; + return new Submarine(info, false, linkedRemap: linkedRemap); } private void ResetCrushDepth() @@ -1599,18 +1605,14 @@ namespace Barotrauma { if (item.FindParentInventory(inv => inv is CharacterInventory) != null) { continue; } #if CLIENT - if (Screen.Selected != GameMain.SubEditorScreen) - { - if (e.Submarine != this && item.GetRootContainer()?.Submarine != this) { continue; } - } - else + if (Screen.Selected == GameMain.SubEditorScreen) { e.Submarine = this; } -#else - if (e.Submarine != this && item.GetRootContainer()?.Submarine != this) { continue; } #endif - + if (e.Submarine != this) { continue; } + var rootContainer = item.GetRootContainer(); + if (rootContainer != null && rootContainer.Submarine != this) { continue; } } else { @@ -1851,5 +1853,32 @@ namespace Barotrauma } public void RefreshOutdoorNodes() => OutdoorNodes.ForEach(n => n?.Waypoint?.FindHull()); + + public Item FindContainerFor(Item item, bool onlyPrimary, bool checkTransferConditions = false) + { + var potentialContainers = new List(); + foreach (Item potentialContainer in Item.ItemList) + { + if (potentialContainer.Removed) { continue; } + if (potentialContainer.NonInteractable) { continue; } + if (potentialContainer.HiddenInGame) { continue; } + if (potentialContainer.Submarine != this) { continue; } + if (potentialContainer == item) { continue; } + if (potentialContainer.Condition <= 0) { continue; } + if (potentialContainer.OwnInventory == null) { continue; } + if (potentialContainer.GetRootInventoryOwner() != potentialContainer) { continue; } + var container = potentialContainer.GetComponent(); + if (container == null) { continue; } + if (!potentialContainer.OwnInventory.CanBePut(item)) { continue; } + if (!container.ShouldBeContained(item, out _)) { continue; } + if (!item.Prefab.IsContainerPreferred(item, container, out bool isPreferencesDefined, out bool isSecondary, checkTransferConditions: checkTransferConditions) || !isPreferencesDefined || onlyPrimary && isSecondary) { continue; } + potentialContainers.Add(potentialContainer); + if (!isSecondary) + { + break; + } + } + return potentialContainers.LastOrDefault(); + } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineBody.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineBody.cs index 3216589a1..c2340805b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineBody.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineBody.cs @@ -136,7 +136,17 @@ namespace Barotrauma HullVertices = convexHull; - farseerBody = GameMain.World.CreateBody(); + farseerBody = GameMain.World.CreateBody(findNewContacts: false, bodyType: BodyType.Dynamic); + var collisionCategory = Physics.CollisionWall; + var collidesWith = + Physics.CollisionItem | + Physics.CollisionLevel | + Physics.CollisionCharacter | + Physics.CollisionProjectile | + Physics.CollisionWall; + farseerBody.CollisionCategories = collisionCategory; + farseerBody.CollidesWith = collidesWith; + farseerBody.Enabled = false; farseerBody.UserData = this; foreach (var mapEntity in MapEntity.mapEntityList) { @@ -152,7 +162,9 @@ namespace Barotrauma ConvertUnits.ToSimUnits(wall.BodyHeight), 50.0f, -wall.BodyRotation, - ConvertUnits.ToSimUnits(new Vector2(rect.X + rect.Width / 2, rect.Y - rect.Height / 2) + wall.BodyOffset)).UserData = wall; + ConvertUnits.ToSimUnits(new Vector2(rect.X + rect.Width / 2, rect.Y - rect.Height / 2) + wall.BodyOffset), + collisionCategory, + collidesWith).UserData = wall; } } @@ -167,7 +179,9 @@ namespace Barotrauma ConvertUnits.ToSimUnits(rect.Width), ConvertUnits.ToSimUnits(rect.Height), 100.0f, - ConvertUnits.ToSimUnits(new Vector2(rect.X + rect.Width / 2, rect.Y - rect.Height / 2))).UserData = hull; + ConvertUnits.ToSimUnits(new Vector2(rect.X + rect.Width / 2, rect.Y - rect.Height / 2)), + collisionCategory, + collidesWith).UserData = hull; } foreach (Item item in Item.ItemList) @@ -191,47 +205,40 @@ namespace Barotrauma if (width > 0.0f && height > 0.0f) { - item.StaticFixtures.Add(farseerBody.CreateRectangle(simWidth, simHeight, 5.0f, simPos)); + item.StaticFixtures.Add(farseerBody.CreateRectangle(simWidth, simHeight, 5.0f, simPos, collisionCategory, collidesWith)); SetExtents(item.Position - new Vector2(width, height) / 2, item.Position + new Vector2(width, height) / 2, hasCollider: true); } else if (radius > 0.0f && width > 0.0f) { - item.StaticFixtures.Add(farseerBody.CreateRectangle(simWidth, simRadius * 2, 5.0f, simPos)); - item.StaticFixtures.Add(farseerBody.CreateCircle(simRadius, 5.0f, simPos - Vector2.UnitX * simWidth / 2)); - item.StaticFixtures.Add(farseerBody.CreateCircle(simRadius, 5.0f, simPos + Vector2.UnitX * simWidth / 2)); + item.StaticFixtures.Add(farseerBody.CreateRectangle(simWidth, simRadius * 2, 5.0f, simPos, collisionCategory, collidesWith)); + item.StaticFixtures.Add(farseerBody.CreateCircle(simRadius, 5.0f, simPos - Vector2.UnitX * simWidth / 2, collisionCategory, collidesWith)); + item.StaticFixtures.Add(farseerBody.CreateCircle(simRadius, 5.0f, simPos + Vector2.UnitX * simWidth / 2, collisionCategory, collidesWith)); SetExtents(item.Position - new Vector2(width / 2 + radius, height / 2), item.Position + new Vector2(width / 2 + radius, height / 2), hasCollider: true); } else if (radius > 0.0f && height > 0.0f) { - item.StaticFixtures.Add(farseerBody.CreateRectangle(simRadius * 2, height, 5.0f, simPos)); - item.StaticFixtures.Add(farseerBody.CreateCircle(simRadius, 5.0f, simPos - Vector2.UnitY * simHeight / 2)); - item.StaticFixtures.Add(farseerBody.CreateCircle(simRadius, 5.0f, simPos + Vector2.UnitX * simHeight / 2)); + item.StaticFixtures.Add(farseerBody.CreateRectangle(simRadius * 2, height, 5.0f, simPos, collisionCategory, collidesWith)); + item.StaticFixtures.Add(farseerBody.CreateCircle(simRadius, 5.0f, simPos - Vector2.UnitY * simHeight / 2, collisionCategory, collidesWith)); + item.StaticFixtures.Add(farseerBody.CreateCircle(simRadius, 5.0f, simPos + Vector2.UnitY * simHeight / 2, collisionCategory, collidesWith)); SetExtents(item.Position - new Vector2(width / 2, height / 2 + radius), item.Position + new Vector2(width / 2, height / 2 + radius), hasCollider: true); } else if (radius > 0.0f) { - item.StaticFixtures.Add(farseerBody.CreateCircle(simRadius, 5.0f, simPos)); + item.StaticFixtures.Add(farseerBody.CreateCircle(simRadius, 5.0f, simPos, collisionCategory, collidesWith)); visibleMinExtents.X = Math.Min(item.Position.X - radius, visibleMinExtents.X); visibleMinExtents.Y = Math.Min(item.Position.Y - radius, visibleMinExtents.Y); visibleMaxExtents.X = Math.Max(item.Position.X + radius, visibleMaxExtents.X); visibleMaxExtents.Y = Math.Max(item.Position.Y + radius, visibleMaxExtents.Y); SetExtents(item.Position - new Vector2(radius, radius), item.Position + new Vector2(radius, radius), hasCollider: true); } + item.StaticFixtures.ForEach(f => f.UserData = item); } Borders = new Rectangle((int)minExtents.X, (int)maxExtents.Y, (int)(maxExtents.X - minExtents.X), (int)(maxExtents.Y - minExtents.Y)); VisibleBorders = new Rectangle((int)visibleMinExtents.X, (int)visibleMaxExtents.Y, (int)(visibleMaxExtents.X - visibleMinExtents.X), (int)(visibleMaxExtents.Y - visibleMinExtents.Y)); } - farseerBody.BodyType = BodyType.Dynamic; - farseerBody.CollisionCategories = Physics.CollisionWall; - farseerBody.CollidesWith = - Physics.CollisionItem | - Physics.CollisionLevel | - Physics.CollisionCharacter | - Physics.CollisionProjectile | - Physics.CollisionWall; - + farseerBody.Enabled = true; farseerBody.Restitution = Restitution; farseerBody.Friction = Friction; farseerBody.FixedRotation = true; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/ChildServerRelay.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/ChildServerRelay.cs index f48d81a46..b269b2c76 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/ChildServerRelay.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/ChildServerRelay.cs @@ -107,10 +107,25 @@ namespace Barotrauma.Networking return -1; } + //FIXME workaround for crash when closing the server under .NET 6.0, not sure if this is the proper way to fix it but it prevents it from crashing the client. - Markus +#if NET6_0 + try + { + if (readTask.IsCompleted || readTask.Wait(100, readCancellationToken.Token)) + { + break; + } + } + catch (OperationCanceledException) + { + return -1; + } +#else if (readTask.IsCompleted || readTask.Wait(timeOut)) { break; } +#endif } if (readTask.Status != TaskStatus.RanToCompletion) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/RespawnManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/RespawnManager.cs index 50a94dbe5..b80fd2455 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/RespawnManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/RespawnManager.cs @@ -21,6 +21,8 @@ namespace Barotrauma.Networking private readonly NetworkMember networkMember; private readonly Steering shuttleSteering; private readonly List shuttleDoors; + private const string RespawnContainerTag = "respawncontainer"; + private readonly ItemContainer respawnContainer; //items created during respawn //any respawn items left in the shuttle are removed when the shuttle despawns @@ -100,13 +102,18 @@ namespace Barotrauma.Networking shuttleDoors = new List(); foreach (Item item in Item.ItemList) { - if (item.Submarine != RespawnShuttle) continue; + if (item.Submarine != RespawnShuttle) { continue; } + + if (item.HasTag(RespawnContainerTag)) + { + respawnContainer = item.GetComponent(); + } var steering = item.GetComponent(); - if (steering != null) shuttleSteering = steering; + if (steering != null) { shuttleSteering = steering; } var door = item.GetComponent(); - if (door != null) shuttleDoors.Add(door); + if (door != null) { shuttleDoors.Add(door); } //lock all wires to prevent the players from messing up the electronics var connectionPanel = item.GetComponent(); @@ -227,14 +234,14 @@ namespace Barotrauma.Networking despawnTime = ReturnTime + new TimeSpan(0, 0, seconds: 30); #endif - if (RespawnShuttle == null) return; + if (RespawnShuttle == null) { return; } foreach (Item item in Item.ItemList) { if (item.Submarine != RespawnShuttle) { continue; } //remove respawn items that have been left in the shuttle - if (respawnItems.Contains(item)) + if (respawnItems.Contains(item) || respawnContainer?.Item != null && item.IsOwnedBy(respawnContainer.Item)) { Spawner.AddItemToRemoveQueue(item); continue; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/ServerSettings.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/ServerSettings.cs index 736099f64..10e45816a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/ServerSettings.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/ServerSettings.cs @@ -39,6 +39,12 @@ namespace Barotrauma.Networking SomethingDifferent = 4 } + internal enum LootedMoneyDestination + { + Bank, + Wallet + } + partial class ServerSettings : ISerializableEntity { public const string SettingsFile = "serversettings.xml"; @@ -901,10 +907,16 @@ namespace Barotrauma.Networking set; } + [Serialize(LootedMoneyDestination.Bank, IsPropertySaveable.Yes)] + public LootedMoneyDestination LootedMoneyDestination { get; set; } + + [Serialize(999999, IsPropertySaveable.Yes)] + public int MaximumTransferRequest { get; set; } + private int maxMissionCount = CampaignSettings.DefaultMaxMissionCount; [Serialize(CampaignSettings.DefaultMaxMissionCount, IsPropertySaveable.Yes)] - public int MaxMissionCount + public int MaxMissionCount { get { return maxMissionCount; } set { maxMissionCount = MathHelper.Clamp(value, CampaignSettings.MinMissionCountLimit, CampaignSettings.MaxMissionCountLimit); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Physics/PhysicsBody.cs b/Barotrauma/BarotraumaShared/SharedSource/Physics/PhysicsBody.cs index ae1f8adf7..74da0ce7d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Physics/PhysicsBody.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Physics/PhysicsBody.cs @@ -344,14 +344,14 @@ namespace Barotrauma } } - public PhysicsBody(XElement element, float scale = 1.0f) : this(element, Vector2.Zero, scale) { } - public PhysicsBody(ColliderParams cParams) : this(cParams, Vector2.Zero) { } - public PhysicsBody(LimbParams lParams) : this(lParams, Vector2.Zero) { } + public PhysicsBody(XElement element, float scale = 1.0f, bool findNewContacts = true) : this(element, Vector2.Zero, scale, findNewContacts: findNewContacts) { } + public PhysicsBody(ColliderParams cParams, bool findNewContacts = true) : this(cParams, Vector2.Zero, findNewContacts) { } + public PhysicsBody(LimbParams lParams, bool findNewContacts = true) : this(lParams, Vector2.Zero, findNewContacts) { } - public PhysicsBody(float width, float height, float radius, float density) + public PhysicsBody(float width, float height, float radius, float density, BodyType bodyType, Category collisionCategory, Category collidesWith, bool findNewContacts = true) { density = Math.Max(density, MinDensity); - CreateBody(width, height, radius, density); + CreateBody(width, height, radius, density, bodyType, collisionCategory, collidesWith, findNewContacts); LastSentPosition = FarseerBody.Position; list.Add(this); } @@ -359,21 +359,21 @@ namespace Barotrauma public PhysicsBody(Body farseerBody) { FarseerBody = farseerBody; - if (FarseerBody.UserData == null) FarseerBody.UserData = this; + if (FarseerBody.UserData == null) { FarseerBody.UserData = this; } LastSentPosition = FarseerBody.Position; list.Add(this); } - public PhysicsBody(ColliderParams colliderParams, Vector2 position) + public PhysicsBody(ColliderParams colliderParams, Vector2 position, bool findNewContacts = true) { float radius = ConvertUnits.ToSimUnits(colliderParams.Radius) * colliderParams.Ragdoll.LimbScale; float height = ConvertUnits.ToSimUnits(colliderParams.Height) * colliderParams.Ragdoll.LimbScale; float width = ConvertUnits.ToSimUnits(colliderParams.Width) * colliderParams.Ragdoll.LimbScale; density = 10; - CreateBody(width, height, radius, density); - FarseerBody.BodyType = BodyType.Dynamic; - FarseerBody.CollidesWith = Physics.CollisionWall | Physics.CollisionLevel; - FarseerBody.CollisionCategories = Physics.CollisionCharacter; + CreateBody(width, height, radius, density, BodyType.Dynamic, + Physics.CollisionCharacter, + Physics.CollisionWall | Physics.CollisionLevel, + findNewContacts); FarseerBody.AngularDamping = DefaultAngularDamping; FarseerBody.FixedRotation = true; FarseerBody.Friction = 0.05f; @@ -383,16 +383,24 @@ namespace Barotrauma list.Add(this); } - public PhysicsBody(LimbParams limbParams, Vector2 position) + public PhysicsBody(LimbParams limbParams, Vector2 position, bool findNewContacts = true) { float radius = ConvertUnits.ToSimUnits(limbParams.Radius) * limbParams.Scale * limbParams.Ragdoll.LimbScale; float height = ConvertUnits.ToSimUnits(limbParams.Height) * limbParams.Scale * limbParams.Ragdoll.LimbScale; float width = ConvertUnits.ToSimUnits(limbParams.Width) * limbParams.Scale * limbParams.Ragdoll.LimbScale; density = Math.Max(limbParams.Density, MinDensity); - CreateBody(width, height, radius, density); - FarseerBody.BodyType = BodyType.Dynamic; - FarseerBody.CollidesWith = Physics.CollisionWall | Physics.CollisionLevel; - FarseerBody.CollisionCategories = Physics.CollisionItem; + + Category collisionCategory = Physics.CollisionCharacter; + Category collidesWith = Physics.CollisionAll & ~Physics.CollisionCharacter & ~Physics.CollisionItem & ~Physics.CollisionItemBlocking; + if (limbParams.IgnoreCollisions) + { + collisionCategory = Category.None; + collidesWith = Category.None; + } + CreateBody(width, height, radius, density, BodyType.Dynamic, + collisionCategory: collisionCategory, + collidesWith: collidesWith, + findNewContacts: findNewContacts); FarseerBody.Friction = limbParams.Friction; FarseerBody.Restitution = limbParams.Restitution; FarseerBody.AngularDamping = limbParams.AngularDamping; @@ -402,17 +410,14 @@ namespace Barotrauma list.Add(this); } - public PhysicsBody(XElement element, Vector2 position, float scale = 1.0f, float? forceDensity = null) + public PhysicsBody(XElement element, Vector2 position, float scale = 1.0f, float? forceDensity = null, Category collisionCategory = Physics.CollisionItem, Category collidesWith = Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionPlatform, bool findNewContacts = true) { float radius = ConvertUnits.ToSimUnits(element.GetAttributeFloat("radius", 0.0f)) * scale; float height = ConvertUnits.ToSimUnits(element.GetAttributeFloat("height", 0.0f)) * scale; float width = ConvertUnits.ToSimUnits(element.GetAttributeFloat("width", 0.0f)) * scale; density = Math.Max(forceDensity ?? element.GetAttributeFloat("density", 10.0f), MinDensity); - CreateBody(width, height, radius, density); Enum.TryParse(element.GetAttributeString("bodytype", "Dynamic"), out BodyType bodyType); - FarseerBody.BodyType = bodyType; - FarseerBody.CollisionCategories = Physics.CollisionItem; - FarseerBody.CollidesWith = Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionPlatform; + CreateBody(width, height, radius, density, bodyType, collisionCategory, collidesWith, findNewContacts); FarseerBody.Friction = element.GetAttributeFloat("friction", 0.5f); FarseerBody.Restitution = element.GetAttributeFloat("restitution", 0.05f); FarseerBody.UserData = this; @@ -421,7 +426,7 @@ namespace Barotrauma list.Add(this); } - private void CreateBody(float width, float height, float radius, float density) + private void CreateBody(float width, float height, float radius, float density, BodyType bodyType, Category collisionCategory, Category collidesWith, bool findNewContacts = true) { if (IsValidShape(radius, height, width)) { @@ -429,16 +434,16 @@ namespace Barotrauma switch (bodyShape) { case Shape.Capsule: - FarseerBody = GameMain.World.CreateCapsule(height, radius, density); + FarseerBody = GameMain.World.CreateCapsule(height, radius, density, bodyType: bodyType, collisionCategory: collisionCategory, collidesWith: collidesWith, findNewContacts: findNewContacts); ; break; case Shape.HorizontalCapsule: - FarseerBody = GameMain.World.CreateCapsuleHorizontal(width, radius, density); + FarseerBody = GameMain.World.CreateCapsuleHorizontal(width, radius, density, bodyType: bodyType, collisionCategory: collisionCategory, collidesWith: collidesWith, findNewContacts: findNewContacts); break; case Shape.Circle: - FarseerBody = GameMain.World.CreateCircle(radius, density); + FarseerBody = GameMain.World.CreateCircle(radius, density, bodyType: bodyType, collisionCategory: collisionCategory, collidesWith: collidesWith, findNewContacts: findNewContacts); break; case Shape.Rectangle: - FarseerBody = GameMain.World.CreateRectangle(width, height, density); + FarseerBody = GameMain.World.CreateRectangle(width, height, density, bodyType: bodyType, collisionCategory: collisionCategory, collidesWith: collidesWith, findNewContacts: findNewContacts); break; default: throw new NotImplementedException(bodyShape.ToString()); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Prefabs/Prefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Prefabs/Prefab.cs index 1b40954db..c86e6050c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Prefabs/Prefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Prefabs/Prefab.cs @@ -20,9 +20,9 @@ namespace Barotrauma { if (!potentialCallFromConstructor) { return; } StackTrace st = new StackTrace(skipFrames: 2, fNeedFileInfo: false); - for (int i = st.FrameCount-1; i >= 0; i--) + for (int i = st.FrameCount - 1; i >= 0; i--) { - if (st.GetFrame(i)?.GetMethod() is {IsConstructor: true, DeclaringType: { } declaringType} + if (st.GetFrame(i)?.GetMethod() is { IsConstructor: true, DeclaringType: { } declaringType } && Types.Contains(declaringType)) { throw new Exception("Called disallowed method from within a prefab's constructor!"); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Settings/GameSettings.cs b/Barotrauma/BarotraumaShared/SharedSource/Settings/GameSettings.cs index d1aac1552..5c0e19cfd 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Settings/GameSettings.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Settings/GameSettings.cs @@ -301,7 +301,6 @@ namespace Barotrauma { InputType.Down, Keys.S }, { InputType.Left, Keys.A }, { InputType.Right, Keys.D }, - { InputType.ToggleInventory, Keys.Q }, { InputType.SelectNextCharacter, Keys.Z }, { InputType.SelectPreviousCharacter, Keys.X }, @@ -452,19 +451,19 @@ namespace Barotrauma public static void SetCurrentConfig(in Config newConfig) { - bool setGraphicsMode = - currentConfig.Graphics.Width != newConfig.Graphics.Width - || currentConfig.Graphics.Height != newConfig.Graphics.Height - || currentConfig.Graphics.VSync != newConfig.Graphics.VSync - || currentConfig.Graphics.DisplayMode != newConfig.Graphics.DisplayMode; - + bool resolutionChanged = + currentConfig.Graphics.Width != newConfig.Graphics.Width || + currentConfig.Graphics.Height != newConfig.Graphics.Height; bool languageChanged = currentConfig.Language != newConfig.Language; - bool audioOutputChanged = currentConfig.Audio.AudioOutputDevice != newConfig.Audio.AudioOutputDevice; bool voiceCaptureChanged = currentConfig.Audio.VoiceCaptureDevice != newConfig.Audio.VoiceCaptureDevice; - bool textScaleChanged = Math.Abs(currentConfig.Graphics.TextScale - newConfig.Graphics.TextScale) > MathF.Pow(2.0f, -7); + bool setGraphicsMode = + resolutionChanged || + currentConfig.Graphics.VSync != newConfig.Graphics.VSync || + currentConfig.Graphics.DisplayMode != newConfig.Graphics.DisplayMode; + currentConfig = newConfig; #if CLIENT @@ -483,7 +482,7 @@ namespace Barotrauma VoipCapture.ChangeCaptureDevice(currentConfig.Audio.VoiceCaptureDevice); } - if (textScaleChanged) + if (textScaleChanged || resolutionChanged) { foreach (var font in GUIStyle.Fonts.Values) { @@ -504,23 +503,11 @@ namespace Barotrauma XElement graphicsElement = new XElement("graphicssettings"); root.Add(graphicsElement); currentConfig.Graphics.SerializeElement(graphicsElement); - -#region Backwards compatibility crap -#warning TODO: remove once modding refactor ships in a stable release - XElement backwardsCompatibilityGraphicsMode = new XElement(graphicsElement); root.Add(backwardsCompatibilityGraphicsMode); - backwardsCompatibilityGraphicsMode.Name = "graphicsmode"; -#endregion - + XElement audioElement = new XElement("audio"); root.Add(audioElement); currentConfig.Audio.SerializeElement(audioElement); XElement contentPackagesElement = new XElement("contentpackages"); root.Add(contentPackagesElement); -#region More backwards compatibility crap - XComment backwardsCompatibleComment = new XComment("Backwards compatibility"); contentPackagesElement.Add(backwardsCompatibleComment); -#warning TODO: remove once modding refactor ships in a stable release - XElement backwardsCompatibleCoreElement = new XElement("core"); contentPackagesElement.Add(backwardsCompatibleCoreElement); - backwardsCompatibleCoreElement.SetAttributeValue("name", "Vanilla 0.9"); -#endregion XComment corePackageComment = new XComment(ContentPackageManager.EnabledPackages.Core?.Name ?? "Vanilla"); contentPackagesElement.Add(corePackageComment); XElement corePackageElement = new XElement(ContentPackageManager.CorePackageElementName); contentPackagesElement.Add(corePackageElement); corePackageElement.SetAttributeValue("path", ContentPackageManager.EnabledPackages.Core?.Path ?? ContentPackageManager.VanillaFileList); diff --git a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/PropertyConditional.cs b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/PropertyConditional.cs index 45aa60f26..f86226e55 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/PropertyConditional.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/PropertyConditional.cs @@ -1,8 +1,7 @@ using System; -using System.Collections.Generic; using System.Globalization; -using System.Xml.Linq; using System.Linq; +using System.Xml.Linq; namespace Barotrauma { diff --git a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs index 017a10301..fd364e4f8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs @@ -320,7 +320,7 @@ namespace Barotrauma private readonly int useItemCount; - private readonly bool removeItem, removeCharacter, breakLimb, hideLimb; + private readonly bool removeItem, dropContainedItems, removeCharacter, breakLimb, hideLimb; private readonly float hideLimbTimer; public readonly ActionType type = ActionType.OnActive; @@ -608,6 +608,9 @@ namespace Barotrauma case "removeitem": removeItem = true; break; + case "dropcontaineditems": + dropContainedItems = true; + break; case "removecharacter": removeCharacter = true; break; @@ -1224,6 +1227,22 @@ namespace Barotrauma } } + if (dropContainedItems) + { + for (int i = 0; i < targets.Count; i++) + { + if (targets[i] is Item item) + { + foreach (var itemContainer in item.GetComponents()) + { + foreach (var containedItem in itemContainer.Inventory.AllItemsMod) + { + containedItem.Drop(dropper: null); + } + } + } + } + } if (removeItem) { for (int i = 0; i < targets.Count; i++) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Steam/Workshop.cs b/Barotrauma/BarotraumaShared/SharedSource/Steam/Workshop.cs index 09e9c36ce..46a58055b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Steam/Workshop.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Steam/Workshop.cs @@ -324,7 +324,7 @@ namespace Barotrauma.Steam using (var copyIndicator = new CopyIndicator(copyIndicatorPath)) { - await CopyDirectory(itemDirectory, modPathDirName ?? modName, itemDirectory, installDir); + await CopyDirectory(itemDirectory, modPathDirName ?? modName, itemDirectory, installDir, ShouldCorrectPaths.Yes); string fileListDestPath = Path.Combine(installDir, ContentPackage.FileListFileName); XDocument fileListDest = XMLExtensions.TryLoadXml(fileListDestPath); @@ -358,12 +358,15 @@ namespace Barotrauma.Steam string val = attribute.Value.CleanUpPathCrossPlatform(correctFilenameCase: false); + bool isPath = false; + //Handle mods that have been mangled by pre-modding-refactor //copying of post-modding-refactor mods (what a clusterfuck) int modDirStrIndex = val.IndexOf(ContentPath.ModDirStr, StringComparison.OrdinalIgnoreCase); if (modDirStrIndex >= 0) { val = val[modDirStrIndex..]; + isPath = true; } //Handle really old mods (0.9.0.4-era) that might be structured as @@ -372,6 +375,7 @@ namespace Barotrauma.Steam if (File.Exists(fullSrcPath)) { val = $"{ContentPath.ModDirStr}/{val}"; + isPath = true; } //Handle old mods that installed to the fixed Mods directory @@ -380,6 +384,7 @@ namespace Barotrauma.Steam if (val.StartsWith(oldModDir, StringComparison.OrdinalIgnoreCase)) { val = $"{ContentPath.ModDirStr}{val.Remove(0, oldModDir.Length)}"; + isPath = true; } //Handle old mods that depend on other mods else if (val.StartsWith("Mods/", StringComparison.OrdinalIgnoreCase)) @@ -387,13 +392,15 @@ namespace Barotrauma.Steam string otherModName = val.Substring(val.IndexOf('/')+1); otherModName = otherModName.Substring(0, otherModName.IndexOf('/')); val = $"{string.Format(ContentPath.OtherModDirFmt, otherModName)}{val.Remove(0, $"Mods/{otherModName}".Length)}"; + isPath = true; } //Handle really old mods that installed Submarines in the wrong place else if (val.StartsWith("Submarines/", StringComparison.OrdinalIgnoreCase)) { val = $"{ContentPath.ModDirStr}/{val}"; + isPath = true; } - attribute.Value = val; + if (isPath) { attribute.Value = val; } } await Task.WhenAll( element.Elements() @@ -403,11 +410,11 @@ namespace Barotrauma.Steam element: subElement))); } - private static async Task CopyFile(string fileListDir, string modName, string from, string to) + private static async Task CopyFile(string fileListDir, string modName, string from, string to, ShouldCorrectPaths shouldCorrectPaths) { await Task.Yield(); Identifier extension = Path.GetExtension(from).ToIdentifier(); - if (extension == ".xml") + if (extension == ".xml" && shouldCorrectPaths == ShouldCorrectPaths.Yes) { try { @@ -436,7 +443,12 @@ namespace Barotrauma.Steam File.Copy(from, to, overwrite: true); } - public static async Task CopyDirectory(string fileListDir, string modName, string from, string to) + public enum ShouldCorrectPaths + { + Yes, No + } + + public static async Task CopyDirectory(string fileListDir, string modName, string from, string to, ShouldCorrectPaths shouldCorrectPaths) { from = Path.GetFullPath(from); to = Path.GetFullPath(to); Directory.CreateDirectory(to); @@ -448,10 +460,10 @@ namespace Barotrauma.Steam string[] subDirs = Directory.GetDirectories(from); foreach (var file in files) { - await CopyFile(fileListDir, modName, file, convertFromTo(file)); + await CopyFile(fileListDir, modName, file, convertFromTo(file), shouldCorrectPaths); } - foreach (var dir in subDirs) { await CopyDirectory(fileListDir, modName, dir, convertFromTo(dir)); } + foreach (var dir in subDirs) { await CopyDirectory(fileListDir, modName, dir, convertFromTo(dir), shouldCorrectPaths); } } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Utils/AssemblyInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/Utils/AssemblyInfo.cs index 404e8b571..7a904fdac 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Utils/AssemblyInfo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Utils/AssemblyInfo.cs @@ -1,7 +1,11 @@ using System; using System.Linq; using System.Reflection; +using System.Runtime.CompilerServices; +[assembly:InternalsVisibleTo("WindowsTest"), + InternalsVisibleTo("MacTest"), + InternalsVisibleTo("LinuxTest")] public static class AssemblyInfo { public static readonly string GitRevision; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Utils/ReflectionUtils.cs b/Barotrauma/BarotraumaShared/SharedSource/Utils/ReflectionUtils.cs index 2a1585e5b..eb17b94a8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Utils/ReflectionUtils.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Utils/ReflectionUtils.cs @@ -7,9 +7,14 @@ namespace Barotrauma { public static class ReflectionUtils { + private static Type[] cachedNonAbstractTypes; public static IEnumerable GetDerivedNonAbstract() { - return Assembly.GetEntryAssembly().GetTypes().Where(t => t.IsSubclassOf(typeof(T)) && !t.IsAbstract); + if (cachedNonAbstractTypes == null) + { + cachedNonAbstractTypes = Assembly.GetEntryAssembly().GetTypes().Where(t => !t.IsAbstract).ToArray(); + } + return cachedNonAbstractTypes.Where(t => t.IsSubclassOf(typeof(T))); } } } \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Utils/SaveUtil.cs b/Barotrauma/BarotraumaShared/SharedSource/Utils/SaveUtil.cs index b55e03a9f..365924358 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Utils/SaveUtil.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Utils/SaveUtil.cs @@ -480,7 +480,7 @@ namespace Barotrauma int read = 0; // FIXME workaround for .NET6 causing save decompression to fail -#if NET6_0 && LINUX +#if NET6_0 for (int i = 0; i < amount; i++) { int result = zipStream.ReadByte(); diff --git a/Barotrauma/BarotraumaShared/changelog.txt b/Barotrauma/BarotraumaShared/changelog.txt index 37b6f8943..2942b8e72 100644 --- a/Barotrauma/BarotraumaShared/changelog.txt +++ b/Barotrauma/BarotraumaShared/changelog.txt @@ -1,3 +1,87 @@ +--------------------------------------------------------------------------------------------------------- +v0.18.0.0 +--------------------------------------------------------------------------------------------------------- + +Balancing: +- Cargo mission reward of construction materials has been reduced to be less balance-breaking. +- Many fabrication and deconstruction recipes changed to prevent infinite construction loops. +- Reduced selling price to ~25% of base price to avoid getting too rich from looting too early/easily +- Increased effect of "Requested Goods" to be 2x to compensate for the decreased selling price. +- Alien artifacts and trinkets can still be sold for a high price at research stations. (2x modifier, to compensate for the reduced selling price) +- Removed batteries from Headset, to reduce the value of selling/deconstructing these. +- Duffelbag deteriorates over time when in use, and now is carried with both hands. +- All items now deconstruct into less materials than it takes to construct them. Avoiding infinite construction/deconstruction loops for easy skill leveling. +- Revisited all item spawns (WIP). Drastically reduced and adjusted the spawns everywhere. Disabled some spawns in campaign. All the subs should now start with a bare minimum in the campaign. +- (Temporarily?) Removed most hand-placed items from the vanilla subs to make balancing and debugging the auto item placement easier. +- Revisited crew corpse spawns. The id cards are no longer manually placed. The cards found from the crew now actually work. +- Minor adjustments to bandit loadouts. +- Changes to chaingun. Now fires 500 shots instead of 200 per ammo box, at the cost of DPS. +- Added shredder rounds for chaingun, as an option against armoured targets. +- Adjusted the armor penetration of all turrets. +- Made location evolution take a little longer, colonies cannot be formed closer than three steps to another colony. +- Made wreck missions a little more common. + +Changes and additions: +- Added damage overlays to characters (characters who've taken damage look damaged). +- Show a verification prompt if an automated circuit tries to make the submarine undock from or dock with an outpost. Prevents campaign getting softlocked if someone rewires the docking port in a way that makes it dock/undock immediately at the start of around. +- Color subs in the sub editor's list to indicate whether they're vanilla, workshop or local subs, added a tooltip that explains why some of them cannot be deleted through the editor. +- Optimized AI pathfinding when they're trying to find a safe hull. Particularly noticeable in colonies when the NPCs are fleeing from something. +- Optimized character status effects (e.g. health regen and other constant damage reductions). +- Optimized watcher's acid clouds. +- Optimized loading submarines. Reduces loading times especially when there's lots of items in the sub. +- ID cards can now be purchased from outposts. The card gets assigned the appropriate tags for the character doing the purchase. +- Clients need to wait 1 minute if their vote gets rejected before they can start another vote of the same type. +- Increased the priority of explosion particles to make it less likely for them to not appear when the particle limit has been reached. +- Made matriarch genes slowly heal bleeding (not just afflictions of the type "damage") to get it to be more in line with the description. +- Adjusted small water flow sounds: lower max volume, lerp volume according to the water flow (-> small leaks are much more quiet). +- Added energy drinks and protein bars to vending machines. +- Reduced Winterhalter engine power drain (from 6000 total to 4250). +- Decorative level objects (plants and whatnot) can spawn on outpost walls. +- Adjustments on the particle effects of chaingun and coilgun. +- Added non-lethal rubber bullets for riot shotgun. +- Added a server setting to change if the looted money goes to the player or to the bank. +- Improved tooltips in the wallet menu to make their function more clear. +- Corpses can now be grabbed in singleplayer to loot money. +- Made the crew wallet menu update when the players permissions change. +- Prevented selling items from submarine containers tagged with "donttakeitems", instead of "donttakeitems". +- Removed merchant balance effect on item prices. +- Replaced "item sell value" with the location reputation effect on the store interface. + +Fixes: +- Fixed an issue where the client was adding mission rewards into the bank on their screen causing desync. +- Fixed item sets failing to load when the system language is set to Turkish, causing NPCs to spawn without any items. +- Fixed ballast flora sometimes becoming unkillable in multiplayer. +- Attempt to fix tab menu crew list sometimes getting stuck to a broken state at the beginning of a round. +- Fixed inability to access the character tab in the tab menu when dead (preventing you from creating a new character). +- Fixed occasional "hash calculation for content package xxxx didn't match expected hash" errors when updating/enabling certain mods. +- Fixed preview sometimes breaking in the character customization menu when switching the hair or accessories on Linux or Mac. +- Fixed fonts not getting rescaled when changing resolution. +- Fixed misplaced hull in the beacon stations. +- Fixed ability to pick up items and take items from other characters when controlling a character whose inventory is inaccessible while alive. +- Fixed message box about a too large preview image not being shown when trying to publish one in the Workshop (instead throwing the generic "publishing failed" error). +- Fixed Venture airlock (missing button, inner door wiring). +- Fixed level floor not being visible on the sonar. +- Fixed bots being unable to shoot with a turret whose line of sight is blocked by another turret (even though the projectiles can go through the turret). +- Fixed switching a sub making its preview image disappear from the submarine switch menu. +- Fixed item assemblies still getting misaligned when saving. +- Fixed crashing when there's no audio device available (no speakers/headset connected) and a character enters water. +- Fixed crashing when trying to save an item assembly with a space at the end of the name. +- Fixed crashing when a character tries to operate a turret from outside the sub. +- Fixed submarine name being set to a truncated value in the submarine save dialog if the submarine name text at the top of the screen gets truncated, leading to a crash if you try to save the sub with that name. +- Fixed devices whose power consumption is set to 0 not working when not connected to a grid. +- Fixed outpost NPCs choosing the item to spawn for the device they're operating randomly, occasionally causing them to for example load reactors with volatile rods. +- Clients replicate sending chat messages to wifi components in mp. Fixes radio-linked wifi components not receiving the signals client-side. +- Fixed tab menu staying open during loading screens. +- Signal components' and terminals' sprites don't mirror horizontally in mirrored subs (what's a DNA, RO, ROX or XEGER component??). +- Fixed inability to rewire any docking ports in outpost levels, even if the port is not docked with anything (should only apply to the port docked with the outpost). +- Fixed "Ignore This" orders being wiped when loading an existing multiplayer campaign save. + +Modding: +- Fixed permanent stats given by talents not getting synced to clients in multiplayer (doesn't affect any vanilla talents). +- Fixed nullref exception when trying to trigger a location type change to a type that doesn't exist (doesn't happen in the vanilla game). +- Added an extra tag to the "canned heat" talent to make it easier to add custom upgradeable tanks that aren't compatible with vanilla tools. +- Option to make status effects drop the items contained inside the target item (usage example in the duffel bag). + --------------------------------------------------------------------------------------------------------- v0.17.15.0 --------------------------------------------------------------------------------------------------------- diff --git a/Barotrauma/BarotraumaShared/serversettings.xml b/Barotrauma/BarotraumaShared/serversettings.xml index 34f4c94b2..50ed25dac 100644 --- a/Barotrauma/BarotraumaShared/serversettings.xml +++ b/Barotrauma/BarotraumaShared/serversettings.xml @@ -52,4 +52,6 @@ missiontype="Random" autobantime="3600" maxautobantime="86400" - /> \ No newline at end of file + lootedmoneydestination="Bank" + maximumtransferrequest="999999" +/> \ No newline at end of file diff --git a/Barotrauma/BarotraumaTest/INetSerializableStructTests.cs b/Barotrauma/BarotraumaTest/INetSerializableStructTests.cs new file mode 100644 index 000000000..83cedcc37 --- /dev/null +++ b/Barotrauma/BarotraumaTest/INetSerializableStructTests.cs @@ -0,0 +1,205 @@ +#nullable enable + +using System; +using Barotrauma; +using Barotrauma.Networking; +using FluentAssertions; +using FsCheck; +using Microsoft.Xna.Framework; +using Xunit; + +namespace TestProject +{ + // ReSharper disable UnusedMember.Local NotAccessedField.Local UnusedMember.Global + public class INetSerializableStructTests + { + private class CustomGenerators + { + // no null strings!!! + public static Arbitrary StringGeneratorOverride() => Arb.Default.String().Generator.Where(s => s != null).ToArbitrary(); + } + + public INetSerializableStructTests() + { + Arb.Register(); + Arb.Register(); + } + + [Fact] + public void TestOptional() + { + Prop.ForAll>(SerializeDeserialize).QuickCheckThrowOnFailure(); + Prop.ForAll>(SerializeDeserialize).QuickCheckThrowOnFailure(); + Prop.ForAll>(SerializeDeserialize).QuickCheckThrowOnFailure(); + } + + [Fact] + public void TestNested() + { + Prop.ForAll((arg1, arg2, arg3) => SerializeDeserializeNullableTuple(arg1, new TupleNullableStruct { One = arg2, Two = arg3 })).QuickCheckThrowOnFailure(); + Prop.ForAll((arg1, arg2) => SerializeDeserialize(new TupleNullableStruct { One = arg1, Two = arg2 })).QuickCheckThrowOnFailure(); + Prop.ForAll((arg1, arg2) => SerializeDeserialize(new TupleNullableStruct { One = arg1, Two = arg2 })).QuickCheckThrowOnFailure(); + } + + [Fact] + public void TestVector2() + { + Prop.ForAll(SerializeDeserialize).QuickCheckThrowOnFailure(); + } + + [Fact] + public void TestColor() + { + Prop.ForAll(SerializeDeserialize).QuickCheckThrowOnFailure(); + } + + [Fact] + public void TestEnum() + { + Prop.ForAll(SerializeDeserialize).QuickCheckThrowOnFailure(); + } + + [Fact] + public void TestArray() + { + Prop.ForAll(SerializeDeserialize).QuickCheckThrowOnFailure(); + Prop.ForAll(SerializeDeserialize).QuickCheckThrowOnFailure(); + Prop.ForAll(SerializeDeserialize).QuickCheckThrowOnFailure(); + } + + [Fact] + public void TestNullable() + { + Prop.ForAll(SerializeDeserializeNullableTuple).QuickCheckThrowOnFailure(); + } + + [Fact] + public void TestBoolean() + { + Prop.ForAll(SerializeDeserialize).QuickCheckThrowOnFailure(); + } + + [Fact] + public void TestByte() + { + Prop.ForAll(SerializeDeserialize).QuickCheckThrowOnFailure(); + } + + [Fact] + public void TestUInt16() + { + Prop.ForAll(SerializeDeserialize).QuickCheckThrowOnFailure(); + } + + [Fact] + public void TestInt16() + { + Prop.ForAll(SerializeDeserialize).QuickCheckThrowOnFailure(); + } + + [Fact] + public void TestUInt32() + { + Prop.ForAll(SerializeDeserialize).QuickCheckThrowOnFailure(); + } + + [Fact] + public void TestInt32() + { + Prop.ForAll(SerializeDeserialize).QuickCheckThrowOnFailure(); + } + + [Fact] + public void TestUInt64() + { + Prop.ForAll(SerializeDeserialize).QuickCheckThrowOnFailure(); + } + + [Fact] + public void TestInt64() + { + Prop.ForAll(SerializeDeserialize).QuickCheckThrowOnFailure(); + } + + [Fact] + public void TestSingle() + { + Prop.ForAll(SerializeDeserialize).QuickCheckThrowOnFailure(); + } + + [Fact] + public void TestDouble() + { + Prop.ForAll(SerializeDeserialize).QuickCheckThrowOnFailure(); + } + + [Fact] + public void TestString() + { + Prop.ForAll(SerializeDeserialize).QuickCheckThrowOnFailure(); + } + + private enum EnumTest + { + One = 1, + Two = 2, + Three = 3, + Thousand = 1000 + } + + private struct TestStruct : INetSerializableStruct + { + [NetworkSerialize] + public T Value; + + public T NotSerializedValue; + + public T NotSerializedFunction() => throw new NotImplementedException(); + } + + private struct TupleNullableStruct : INetSerializableStruct + { + [NetworkSerialize] + public T? One; + + [NetworkSerialize] + public U? Two; + + public (T, U) NotSerializedValue; + public (T, U) NotSerializedFunction() => throw new NotImplementedException(); + } + + private static void SerializeDeserialize(T arg) where T : notnull + { + ReadWriteMessage msg = new ReadWriteMessage(); + TestStruct writeStruct = new TestStruct + { + Value = arg + }; + + ((INetSerializableStruct)writeStruct).Write(msg); + msg.BitPosition = 0; + + TestStruct readStruct = INetSerializableStruct.Read>(msg); + + readStruct.Should().BeEquivalentTo(writeStruct, options => options.ComparingByMembers>()); + } + + private static void SerializeDeserializeNullableTuple(T arg1, U arg2) + { + ReadWriteMessage msg = new ReadWriteMessage(); + TupleNullableStruct writeStruct = new TupleNullableStruct + { + One = arg1, + Two = arg2 + }; + + ((INetSerializableStruct)writeStruct).Write(msg); + msg.BitPosition = 0; + + TupleNullableStruct readStruct = INetSerializableStruct.Read>(msg); + + readStruct.Should().BeEquivalentTo(writeStruct, options => options.ComparingByMembers>()); + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaTest/LinuxTest.csproj b/Barotrauma/BarotraumaTest/LinuxTest.csproj new file mode 100644 index 000000000..821101d06 --- /dev/null +++ b/Barotrauma/BarotraumaTest/LinuxTest.csproj @@ -0,0 +1,35 @@ + + + + net6.0 + enable + + false + + LinuxTest + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + + diff --git a/Barotrauma/BarotraumaTest/MacTest.csproj b/Barotrauma/BarotraumaTest/MacTest.csproj new file mode 100644 index 000000000..20b02e7a3 --- /dev/null +++ b/Barotrauma/BarotraumaTest/MacTest.csproj @@ -0,0 +1,35 @@ + + + + net6.0 + enable + + false + + MacTest + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + + diff --git a/Barotrauma/BarotraumaTest/TestExample.cs b/Barotrauma/BarotraumaTest/TestExample.cs new file mode 100644 index 000000000..6c02360dd --- /dev/null +++ b/Barotrauma/BarotraumaTest/TestExample.cs @@ -0,0 +1,122 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Text; +using System.Xml.Linq; +using Barotrauma; +using FluentAssertions; +using FsCheck; +using FsCheck.Xunit; +using Microsoft.Xna.Framework; +using Xunit; +using Xunit.Abstractions; + +namespace TestProject +{ + public class TestExample + { + // By default FsCheck has generators for basic types like floats ints and strings + // Anything custom like Rectangle or Vector2 requires writing a custom generator for it + private class CustomExampleGenerators + { + // We override the float generator to exclude NaNs and infinites + public static Arbitrary FloatGeneratorOverride() => Arb.Default.Float32().Generator.Where(MathUtils.IsValid).ToArbitrary(); + + // We override the String generator to exclude null and empty strings + public static Arbitrary StringGeneratorOverride() => Arb.Default.String().Generator.Where(s => !string.IsNullOrWhiteSpace(s)).ToArbitrary(); + + // Generator for the Rectangle type + public static Arbitrary RectangleGenerator() + { + return Arb.From(from int x in Arb.Generate() + from int y in Arb.Generate() + from int w in Arb.Generate().Where(i => i > 0) + from int h in Arb.Generate().Where(i => i > 0) + select new Rectangle(x, y, w, h)); + } + } + + // Used to output text into the test output + private readonly ITestOutputHelper testOutputHelper; + + public TestExample(ITestOutputHelper testOutputHelper) + { + this.testOutputHelper = testOutputHelper; + Arb.Register(); // Register our custom generators + } + + [Fact] // Create a public function and add the [Fact] attribute on it to make a test function + public void TestXORAlgorithm() + { + Prop.ForAll((text, key) => // generates a pair of random strings + { + string encrypted = XOREncryptDecrypt(text, key); + string decrypted = XOREncryptDecrypt(encrypted, key); + + decrypted.Should().BeEquivalentTo(text); // FluentAssertions provides clear and verbose assertions with the Should() method + }).VerboseCheckThrowOnFailure(testOutputHelper); + // VerboseCheck performs 100 tests and outputs the generated values into the test output + // ThrowOnFailure will additionally throw an exception if any of the functions fail which will make the test fail + + // We will see that this fails the test with the following exception: + + /* + * System.Exception + * Falsifiable, after 1 test (0 shrinks) (StdGen (2118948508,297004609)): + * Original: + * ("Jl", "m") + * with exception: + * Xunit.Sdk.XunitException: Expected decrypted to be equivalent to "Jl" with a length of 2, but "948492" has a length of 6, differs near "948" (index 0). + */ + + // We can see that the reason it is failing is because the original text was "Jl" but when encrypted and decrypted the string becomes "948492" + // This is of course because we are not casting the XOR'd value to a char and instead appending the integer to the string builder + } + + // Erroneous XOR encryption algorithm + private static string XOREncryptDecrypt(string text, string key) + { + var result = new StringBuilder(); + + for (int i = 0; i < text.Length; i++) + { + result.Append(text[i] ^ (uint)key[i % key.Length]); + } + + return result.ToString(); + } + + private class ExampleEntity : ISerializableEntity + { + [Serialize(0.0f, IsPropertySaveable.Yes)] + public float ExampleValue { get; set; } + + public string Name => nameof(ExampleEntity); + public Dictionary SerializableProperties { get; } + + public ExampleEntity() + { + SerializableProperties = SerializableProperty.GetProperties(this); + } + } + + [Fact] + public void TestPropertyConditionalEqualsOperator() + { + // Test if the PropertyConditional equals operator is working correctly + Prop.ForAll(value => + { + XAttribute xmlAttribute = new XAttribute("examplevalue", $"equals {value}"); + + PropertyConditional conditional = new PropertyConditional(xmlAttribute); + + ExampleEntity entity = new ExampleEntity + { + ExampleValue = value + }; + + conditional.Matches(entity).Should().BeTrue(); + }).VerboseCheckThrowOnFailure(testOutputHelper); // Remember to pass testOutputHelper so we actually get output + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaTest/TestProject.cs b/Barotrauma/BarotraumaTest/TestProject.cs new file mode 100644 index 000000000..614b0b23b --- /dev/null +++ b/Barotrauma/BarotraumaTest/TestProject.cs @@ -0,0 +1,34 @@ +using Barotrauma; +using FsCheck; +using Microsoft.Xna.Framework; + +namespace TestProject +{ + public static class TestProject + { + public class CustomGenerators + { + public static Arbitrary Vector2Generator() + { + return Arb.From(from int x in Arb.Generate() + from int y in Arb.Generate() + select new Vector2(x, y)); + } + + public static Arbitrary ColorGenerator() + { + return Arb.From(from int r in Gen.Choose(0, 255) + from int g in Gen.Choose(0, 255) + from int b in Gen.Choose(0, 255) + select new Color(r, g, b)); + } + + public static Arbitrary> OptionalGenerator() + { + return Arb.From(from T x in Arb.Generate() + from bool isNone in Arb.Generate() + select x is null || isNone ? Option.None() : Option.Some(x)); + } + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaTest/WindowsTest.csproj b/Barotrauma/BarotraumaTest/WindowsTest.csproj new file mode 100644 index 000000000..f7c2fc3be --- /dev/null +++ b/Barotrauma/BarotraumaTest/WindowsTest.csproj @@ -0,0 +1,35 @@ + + + + net6.0 + enable + + false + + WindowsTest + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + + diff --git a/Libraries/Farseer Physics Engine 3.5/Common/PathManager.cs b/Libraries/Farseer Physics Engine 3.5/Common/PathManager.cs index ac937096b..21e218c7b 100644 --- a/Libraries/Farseer Physics Engine 3.5/Common/PathManager.cs +++ b/Libraries/Farseer Physics Engine 3.5/Common/PathManager.cs @@ -44,13 +44,13 @@ namespace FarseerPhysics.Common if (path.Closed) { ChainShape chain = new ChainShape(verts, true); - body.CreateFixture(chain); + body.CreateFixture(chain, Category.Cat1, Category.All); } else { for (int i = 1; i < verts.Count; i++) { - body.CreateFixture(new EdgeShape(verts[i], verts[i - 1])); + body.CreateFixture(new EdgeShape(verts[i], verts[i - 1]), Category.Cat1, Category.All); } } } @@ -74,7 +74,7 @@ namespace FarseerPhysics.Common foreach (Vertices item in decomposedVerts) { - body.CreateFixture(new PolygonShape(item, density)); + body.CreateFixture(new PolygonShape(item, density), Category.Cat1, Category.All); } } @@ -105,7 +105,7 @@ namespace FarseerPhysics.Common foreach (Shape shape in shapes) { - b.CreateFixture(shape); + b.CreateFixture(shape, Category.Cat1, Category.All); } bodyList.Add(b); diff --git a/Libraries/Farseer Physics Engine 3.5/Common/PhysicsLogic/BreakableBody.cs b/Libraries/Farseer Physics Engine 3.5/Common/PhysicsLogic/BreakableBody.cs index 9f367a27a..4d988c318 100644 --- a/Libraries/Farseer Physics Engine 3.5/Common/PhysicsLogic/BreakableBody.cs +++ b/Libraries/Farseer Physics Engine 3.5/Common/PhysicsLogic/BreakableBody.cs @@ -56,7 +56,7 @@ namespace FarseerPhysics.Common.PhysicsLogic foreach (Vertices part in vertices) { PolygonShape polygonShape = new PolygonShape(part, density); - Fixture fixture = MainBody.CreateFixture(polygonShape); + Fixture fixture = MainBody.CreateFixture(polygonShape, Category.Cat1, Category.All); Parts.Add(fixture); } } @@ -67,7 +67,7 @@ namespace FarseerPhysics.Common.PhysicsLogic foreach (Shape part in shapes) { - Fixture fixture = MainBody.CreateFixture(part); + Fixture fixture = MainBody.CreateFixture(part, Category.Cat1, Category.All); Parts.Add(fixture); } } @@ -82,7 +82,7 @@ namespace FarseerPhysics.Common.PhysicsLogic foreach (Vertices part in triangles) { PolygonShape polygonShape = new PolygonShape(part, density); - Fixture fixture = MainBody.CreateFixture(polygonShape); + Fixture fixture = MainBody.CreateFixture(polygonShape, Category.Cat1, Category.All); Parts.Add(fixture); } } @@ -161,7 +161,7 @@ namespace FarseerPhysics.Common.PhysicsLogic Body body = World.CreateBody(MainBody.Position, MainBody.Rotation, BodyType.Dynamic); body.UserData = MainBody.UserData; - Fixture newFixture = body.CreateFixture(shape); + Fixture newFixture = body.CreateFixture(shape, Category.Cat1, Category.All); newFixture.UserData = fixtureTag; Parts[i] = newFixture; diff --git a/Libraries/Farseer Physics Engine 3.5/Common/Serialization.cs b/Libraries/Farseer Physics Engine 3.5/Common/Serialization.cs index 35f6535b9..5df05e24c 100644 --- a/Libraries/Farseer Physics Engine 3.5/Common/Serialization.cs +++ b/Libraries/Farseer Physics Engine 3.5/Common/Serialization.cs @@ -614,7 +614,7 @@ namespace FarseerPhysics.Common { foreach (XMLFragmentElement element in fixtureElement.Elements) { - Fixture fixture = new Fixture(); + Fixture fixture = new Fixture(Category.Cat1, Category.All); if (element.Name.ToLower() != "fixture") throw new Exception(); diff --git a/Libraries/Farseer Physics Engine 3.5/Content/BodyContainer.cs b/Libraries/Farseer Physics Engine 3.5/Content/BodyContainer.cs index 02ed6f58f..67dd7621c 100644 --- a/Libraries/Farseer Physics Engine 3.5/Content/BodyContainer.cs +++ b/Libraries/Farseer Physics Engine 3.5/Content/BodyContainer.cs @@ -35,7 +35,7 @@ namespace FarseerPhysics.Content foreach (FixtureTemplate fixtureTemplate in Fixtures) { - Fixture fixture = body.CreateFixture(fixtureTemplate.Shape); + Fixture fixture = body.CreateFixture(fixtureTemplate.Shape, Category.Cat1, Category.All); fixture.UserData = fixtureTemplate.Name; fixture.Restitution = fixtureTemplate.Restitution; fixture.Friction = fixtureTemplate.Friction; diff --git a/Libraries/Farseer Physics Engine 3.5/Dynamics/Body.Factory.cs b/Libraries/Farseer Physics Engine 3.5/Dynamics/Body.Factory.cs index 69769aabe..389c100c1 100644 --- a/Libraries/Farseer Physics Engine 3.5/Dynamics/Body.Factory.cs +++ b/Libraries/Farseer Physics Engine 3.5/Dynamics/Body.Factory.cs @@ -27,76 +27,76 @@ namespace FarseerPhysics.Dynamics /// The shape. /// Application specific data /// - public virtual Fixture CreateFixture(Shape shape) + public virtual Fixture CreateFixture(Shape shape, Category collisionCategory, Category collidesWith) { - Fixture fixture = new Fixture(shape); + Fixture fixture = new Fixture(shape, collisionCategory, collidesWith); Add(fixture); return fixture; } - public Fixture CreateEdge(Vector2 start, Vector2 end) + public Fixture CreateEdge(Vector2 start, Vector2 end, Category collisionCategory, Category collidesWith) { EdgeShape edgeShape = new EdgeShape(start, end); - return CreateFixture(edgeShape); + return CreateFixture(edgeShape, collisionCategory, collidesWith); } - public Fixture CreateChainShape(Vertices vertices) + public Fixture CreateChainShape(Vertices vertices, Category collisionCategory, Category collidesWith) { ChainShape shape = new ChainShape(vertices); - return CreateFixture(shape); + return CreateFixture(shape, collisionCategory, collidesWith); } - public Fixture CreateLoopShape(Vertices vertices) + public Fixture CreateLoopShape(Vertices vertices, Category collisionCategory, Category collidesWith) { ChainShape shape = new ChainShape(vertices, true); - return CreateFixture(shape); + return CreateFixture(shape, collisionCategory, collidesWith); } - public Fixture CreateRectangle(float width, float height, float density, Vector2 offset) + public Fixture CreateRectangle(float width, float height, float density, Vector2 offset, Category collisionCategory, Category collidesWith) { Vertices rectangleVertices = PolygonTools.CreateRectangle(width / 2, height / 2); rectangleVertices.Translate(ref offset); PolygonShape rectangleShape = new PolygonShape(rectangleVertices, density); - return CreateFixture(rectangleShape); + return CreateFixture(rectangleShape, collisionCategory, collidesWith); } - public Fixture CreateRectangle(float width, float height, float density, float rotation, Vector2 offset) + public Fixture CreateRectangle(float width, float height, float density, float rotation, Vector2 offset, Category collisionCategory, Category collidesWith) { Vertices rectangleVertices = PolygonTools.CreateRectangle(width / 2, height / 2, Vector2.Zero, rotation); rectangleVertices.Translate(ref offset); PolygonShape rectangleShape = new PolygonShape(rectangleVertices, density); - return CreateFixture(rectangleShape); + return CreateFixture(rectangleShape, collisionCategory, collidesWith); } - public Fixture CreateCircle(float radius, float density) + public Fixture CreateCircle(float radius, float density, Category collisionCategory, Category collidesWith) { if (radius <= 0) throw new ArgumentOutOfRangeException("radius", "Radius must be more than 0 meters"); CircleShape circleShape = new CircleShape(radius, density); - return CreateFixture(circleShape); + return CreateFixture(circleShape, collisionCategory, collidesWith); } - public Fixture CreateCircle(float radius, float density, Vector2 offset) + public Fixture CreateCircle(float radius, float density, Vector2 offset, Category collisionCategory, Category collidesWith) { if (radius <= 0) throw new ArgumentOutOfRangeException("radius", "Radius must be more than 0 meters"); CircleShape circleShape = new CircleShape(radius, density); circleShape.Position = offset; - return CreateFixture(circleShape); + return CreateFixture(circleShape, collisionCategory, collidesWith); } - public Fixture CreatePolygon(Vertices vertices, float density) + public Fixture CreatePolygon(Vertices vertices, float density, Category collisionCategory, Category collidesWith) { if (vertices.Count <= 1) throw new ArgumentOutOfRangeException("vertices", "Too few points to be a polygon"); PolygonShape polygon = new PolygonShape(vertices, density); - return CreateFixture(polygon); + return CreateFixture(polygon, collisionCategory, collidesWith); } - public Fixture CreateEllipse(float xRadius, float yRadius, int edges, float density) + public Fixture CreateEllipse(float xRadius, float yRadius, int edges, float density, Category collisionCategory, Category collidesWith) { if (xRadius <= 0) throw new ArgumentOutOfRangeException("xRadius", "X-radius must be more than 0"); @@ -106,10 +106,10 @@ namespace FarseerPhysics.Dynamics Vertices ellipseVertices = PolygonTools.CreateEllipse(xRadius, yRadius, edges); PolygonShape polygonShape = new PolygonShape(ellipseVertices, density); - return CreateFixture(polygonShape); + return CreateFixture(polygonShape, collisionCategory, collidesWith); } - public List CreateCompoundPolygon(List list, float density) + public List CreateCompoundPolygon(List list, float density, Category collisionCategory, Category collidesWith) { List res = new List(list.Count); @@ -119,26 +119,26 @@ namespace FarseerPhysics.Dynamics if (vertices.Count == 2) { EdgeShape shape = new EdgeShape(vertices[0], vertices[1]); - res.Add(CreateFixture(shape)); + res.Add(CreateFixture(shape, collisionCategory, collidesWith)); } else { PolygonShape shape = new PolygonShape(vertices, density); - res.Add(CreateFixture(shape)); + res.Add(CreateFixture(shape, collisionCategory, collidesWith)); } } return res; } - public Fixture CreateLineArc(float radians, int sides, float radius, bool closed) + public Fixture CreateLineArc(float radians, int sides, float radius, bool closed, Category collisionCategory, Category collidesWith) { Vertices arc = PolygonTools.CreateArc(radians, sides, radius); arc.Rotate((MathHelper.Pi - radians) / 2); - return closed ? CreateLoopShape(arc) : CreateChainShape(arc); + return closed ? CreateLoopShape(arc, collisionCategory, collidesWith) : CreateChainShape(arc, collisionCategory, collidesWith); } - public List CreateSolidArc(float density, float radians, int sides, float radius) + public List CreateSolidArc(float density, float radians, int sides, float radius, Category collisionCategory, Category collidesWith) { Vertices arc = PolygonTools.CreateArc(radians, sides, radius); arc.Rotate((MathHelper.Pi - radians) / 2); @@ -148,7 +148,7 @@ namespace FarseerPhysics.Dynamics List triangles = Triangulate.ConvexPartition(arc, TriangulationAlgorithm.Earclip); - return CreateCompoundPolygon(triangles, density); + return CreateCompoundPolygon(triangles, density, collisionCategory, collidesWith); } } } \ No newline at end of file diff --git a/Libraries/Farseer Physics Engine 3.5/Dynamics/Fixture.cs b/Libraries/Farseer Physics Engine 3.5/Dynamics/Fixture.cs index 40381f0d4..d65d9a1b0 100644 --- a/Libraries/Farseer Physics Engine 3.5/Dynamics/Fixture.cs +++ b/Libraries/Farseer Physics Engine 3.5/Dynamics/Fixture.cs @@ -82,10 +82,10 @@ namespace FarseerPhysics.Dynamics /// public OnSeparationEventHandler OnSeparation; - internal Fixture() // Note: This is internal because it's used by Deserialization. + internal Fixture(Category collisionCategory, Category collidesWith) // Note: This is internal because it's used by Deserialization. { - _collisionCategories = Category.Cat1; - _collidesWith = Category.All; + _collisionCategories = collisionCategory; + _collidesWith = collidesWith; _collisionGroup = 0; //Fixture defaults @@ -93,7 +93,7 @@ namespace FarseerPhysics.Dynamics Restitution = 0f; } - public Fixture(Shape shape) : this() + public Fixture(Shape shape, Category collisionCategory, Category collidesWith) : this(collisionCategory, collidesWith) { Shape = shape.Clone(); @@ -375,15 +375,15 @@ namespace FarseerPhysics.Dynamics /// The cloned fixture. internal Fixture CloneOnto(Body body, Shape shape) { - Fixture fixture = new Fixture(shape.Clone()); - fixture.UserData = UserData; - fixture.Restitution = Restitution; - fixture.Friction = Friction; - fixture.IsSensor = IsSensor; - fixture._collisionGroup = _collisionGroup; - fixture._collisionCategories = _collisionCategories; - fixture._collidesWith = _collidesWith; - + Fixture fixture = new Fixture(shape.Clone(), _collisionCategories, _collidesWith) + { + UserData = UserData, + Restitution = Restitution, + Friction = Friction, + IsSensor = IsSensor, + _collisionGroup = _collisionGroup + }; + body.Add(fixture); return fixture; } diff --git a/Libraries/Farseer Physics Engine 3.5/Dynamics/World.Factory.cs b/Libraries/Farseer Physics Engine 3.5/Dynamics/World.Factory.cs index f3a778d24..b99ca20e6 100644 --- a/Libraries/Farseer Physics Engine 3.5/Dynamics/World.Factory.cs +++ b/Libraries/Farseer Physics Engine 3.5/Dynamics/World.Factory.cs @@ -17,43 +17,42 @@ namespace FarseerPhysics.Dynamics { public partial class World { - public virtual Body CreateBody(Vector2 position = new Vector2(), float rotation = 0, BodyType bodyType = BodyType.Static) + public virtual Body CreateBody(Vector2 position = new Vector2(), float rotation = 0, BodyType bodyType = BodyType.Static, bool findNewContacts = true) { - Body body = new Body(); - body.Position = position; - body.Rotation = rotation; - body.BodyType = bodyType; - - AddAsync(body); + Body body = new Body + { + Position = position, + Rotation = rotation, + BodyType = bodyType + }; + + AddAsync(body, findNewContacts); return body; } - public Body CreateEdge(Vector2 start, Vector2 end) + public Body CreateEdge(Vector2 start, Vector2 end, BodyType bodyType = BodyType.Static, Category collisionCategory = Category.Cat1, Category collidesWith = Category.All, bool findNewContacts = true) { - Body body = CreateBody(); - - body.CreateEdge(start, end); + Body body = CreateBody(bodyType: bodyType, findNewContacts: findNewContacts); + body.CreateEdge(start, end, collisionCategory, collidesWith); return body; } - public Body CreateChainShape(Vertices vertices, Vector2 position = new Vector2()) + public Body CreateChainShape(Vertices vertices, Vector2 position = new Vector2(), Category collisionCategory = Category.Cat1, Category collidesWith = Category.All, bool findNewContacts = true) { - Body body = CreateBody(position); - - body.CreateChainShape(vertices); + Body body = CreateBody(position, findNewContacts: findNewContacts); + body.CreateChainShape(vertices, collisionCategory, collidesWith); return body; } - public Body CreateLoopShape(Vertices vertices, Vector2 position = new Vector2()) + public Body CreateLoopShape(Vertices vertices, Vector2 position = new Vector2(), Category collisionCategory = Category.Cat1, Category collidesWith = Category.All, bool findNewContacts = true) { - Body body = CreateBody(position); - - body.CreateLoopShape(vertices); + Body body = CreateBody(position, findNewContacts: findNewContacts); + body.CreateLoopShape(vertices, collisionCategory, collidesWith); return body; } - public Body CreateRectangle(float width, float height, float density, Vector2 position = new Vector2(), float rotation = 0, BodyType bodyType = BodyType.Static) + public Body CreateRectangle(float width, float height, float density, Vector2 position = new Vector2(), float rotation = 0, BodyType bodyType = BodyType.Static, Category collisionCategory = Category.Cat1, Category collidesWith = Category.All, bool findNewContacts = true) { if (width <= 0) throw new ArgumentOutOfRangeException("width", "Width must be more than 0 meters"); @@ -61,44 +60,44 @@ namespace FarseerPhysics.Dynamics if (height <= 0) throw new ArgumentOutOfRangeException("height", "Height must be more than 0 meters"); - Body body = CreateBody(position, rotation, bodyType); + Body body = CreateBody(position, rotation, bodyType, findNewContacts); Vertices rectangleVertices = PolygonTools.CreateRectangle(width / 2, height / 2); - body.CreatePolygon(rectangleVertices, density); + body.CreatePolygon(rectangleVertices, density, collisionCategory, collidesWith); return body; } - public Body CreateCircle(float radius, float density, Vector2 position = new Vector2(), BodyType bodyType = BodyType.Static) + public Body CreateCircle(float radius, float density, Vector2 position = new Vector2(), BodyType bodyType = BodyType.Static, Category collisionCategory = Category.Cat1, Category collidesWith = Category.All, bool findNewContacts = true) { - Body body = CreateBody(position, 0, bodyType); - body.CreateCircle(radius, density); + Body body = CreateBody(position, 0, bodyType, findNewContacts); + body.CreateCircle(radius, density, collisionCategory, collidesWith); return body; } - public Body CreateEllipse(float xRadius, float yRadius, int edges, float density, Vector2 position = new Vector2(), float rotation = 0, BodyType bodyType = BodyType.Static) + public Body CreateEllipse(float xRadius, float yRadius, int edges, float density, Vector2 position = new Vector2(), float rotation = 0, BodyType bodyType = BodyType.Static, Category collisionCategory = Category.Cat1, Category collidesWith = Category.All, bool findNewContacts = true) { - Body body = CreateBody(position, rotation, bodyType); - body.CreateEllipse(xRadius, yRadius, edges, density); + Body body = CreateBody(position, rotation, bodyType, findNewContacts); + body.CreateEllipse(xRadius, yRadius, edges, density, collisionCategory, collidesWith); return body; } - public Body CreatePolygon(Vertices vertices, float density, Vector2 position = new Vector2(), float rotation = 0, BodyType bodyType = BodyType.Static) + public Body CreatePolygon(Vertices vertices, float density, Vector2 position = new Vector2(), float rotation = 0, BodyType bodyType = BodyType.Static, Category collisionCategory = Category.Cat1, Category collidesWith = Category.All, bool findNewContacts = true) { - Body body = CreateBody(position, rotation, bodyType); - body.CreatePolygon(vertices, density); + Body body = CreateBody(position, rotation, bodyType, findNewContacts); + body.CreatePolygon(vertices, density, collisionCategory, collidesWith); return body; } - public Body CreateCompoundPolygon(List list, float density, Vector2 position = new Vector2(), float rotation = 0, BodyType bodyType = BodyType.Static) + public Body CreateCompoundPolygon(List list, float density, Vector2 position = new Vector2(), float rotation = 0, BodyType bodyType = BodyType.Static, Category collisionCategory = Category.Cat1, Category collidesWith = Category.All, bool findNewContacts = true) { //We create a single body - Body body = CreateBody(position, rotation, bodyType); - body.CreateCompoundPolygon(list, density); + Body body = CreateBody(position, rotation, bodyType, findNewContacts); + body.CreateCompoundPolygon(list, density, collisionCategory, collidesWith); return body; } - public Body CreateGear(float radius, int numberOfTeeth, float tipPercentage, float toothHeight, float density, Vector2 position = new Vector2(), float rotation = 0, BodyType bodyType = BodyType.Static) + public Body CreateGear(float radius, int numberOfTeeth, float tipPercentage, float toothHeight, float density, Vector2 position = new Vector2(), float rotation = 0, BodyType bodyType = BodyType.Static, Category collisionCategory = Category.Cat1, Category collidesWith = Category.All) { Vertices gearPolygon = PolygonTools.CreateGear(radius, numberOfTeeth, tipPercentage, toothHeight); @@ -108,13 +107,13 @@ namespace FarseerPhysics.Dynamics //Decompose the gear: List list = Triangulate.ConvexPartition(gearPolygon, TriangulationAlgorithm.Earclip); - return CreateCompoundPolygon(list, density, position, rotation, bodyType); + return CreateCompoundPolygon(list, density, position, rotation, bodyType, collisionCategory, collidesWith); } - return CreatePolygon(gearPolygon, density, position, rotation, bodyType); + return CreatePolygon(gearPolygon, density, position, rotation, bodyType, collisionCategory, collidesWith); } - public Body CreateCapsule(float height, float topRadius, int topEdges, float bottomRadius, int bottomEdges, float density, Vector2 position = new Vector2(), float rotation = 0, BodyType bodyType = BodyType.Static) + public Body CreateCapsule(float height, float topRadius, int topEdges, float bottomRadius, int bottomEdges, float density, Vector2 position = new Vector2(), float rotation = 0, BodyType bodyType = BodyType.Static, Category collisionCategory = Category.Cat1, Category collidesWith = Category.All, bool findNewContacts = true) { Vertices verts = PolygonTools.CreateCapsule(height, topRadius, topEdges, bottomRadius, bottomEdges); @@ -122,23 +121,25 @@ namespace FarseerPhysics.Dynamics if (verts.Count >= Settings.MaxPolygonVertices) { List vertList = Triangulate.ConvexPartition(verts, TriangulationAlgorithm.Earclip); - return CreateCompoundPolygon(vertList, density, position, rotation, bodyType); + return CreateCompoundPolygon(vertList, density, position, rotation, bodyType, collisionCategory, collidesWith, findNewContacts); } - return CreatePolygon(verts, density, position, rotation, bodyType); + return CreatePolygon(verts, density, position, rotation, bodyType, collisionCategory, collidesWith, findNewContacts); } - public Body CreateCapsuleHorizontal(float width, float endRadius, float density, Vector2 position = new Vector2(), float rotation = 0, BodyType bodyType = BodyType.Static) + public Body CreateCapsuleHorizontal(float width, float endRadius, float density, Vector2 position = new Vector2(), float rotation = 0, BodyType bodyType = BodyType.Static, Category collisionCategory = Category.Cat1, Category collidesWith = Category.All, bool findNewContacts = true) { //Create the middle rectangle Vertices rectangle = PolygonTools.CreateRectangle(width / 2, endRadius); - List list = new List(); - list.Add(rectangle); + List list = new List + { + rectangle + }; - Body body = CreateCompoundPolygon(list, density, position, rotation, bodyType); - body.CreateCircle(endRadius, density, new Vector2(width / 2, 0)); - body.CreateCircle(endRadius, density, new Vector2(-width / 2, 0)); + Body body = CreateCompoundPolygon(list, density, position, rotation, bodyType, collisionCategory, collidesWith, findNewContacts); + body.CreateCircle(endRadius, density, new Vector2(width / 2, 0), collisionCategory, collidesWith); + body.CreateCircle(endRadius, density, new Vector2(-width / 2, 0), collisionCategory, collidesWith); //Create the two circles //CircleShape topCircle = new CircleShape(endRadius, density); @@ -150,17 +151,19 @@ namespace FarseerPhysics.Dynamics //body.CreateFixture(bottomCircle); return body; } - public Body CreateCapsule(float height, float endRadius, float density, Vector2 position = new Vector2(), float rotation = 0, BodyType bodyType = BodyType.Static) + public Body CreateCapsule(float height, float endRadius, float density, Vector2 position = new Vector2(), float rotation = 0, BodyType bodyType = BodyType.Static, Category collisionCategory = Category.Cat1, Category collidesWith = Category.All, bool findNewContacts = true) { //Create the middle rectangle Vertices rectangle = PolygonTools.CreateRectangle(endRadius, height / 2); - List list = new List(); - list.Add(rectangle); + List list = new List() + { + rectangle + }; - Body body = CreateCompoundPolygon(list, density, position, rotation, bodyType); - body.CreateCircle(endRadius, density, new Vector2(0, height / 2)); - body.CreateCircle(endRadius, density, new Vector2(0, -(height / 2))); + Body body = CreateCompoundPolygon(list, density, position, rotation, bodyType, collisionCategory, collidesWith, findNewContacts); + body.CreateCircle(endRadius, density, new Vector2(0, height / 2), collisionCategory, collidesWith); + body.CreateCircle(endRadius, density, new Vector2(0, -(height / 2)), collisionCategory, collidesWith); //Create the two circles //CircleShape topCircle = new CircleShape(endRadius, density); @@ -173,7 +176,7 @@ namespace FarseerPhysics.Dynamics return body; } - public Body CreateRoundedRectangle(float width, float height, float xRadius, float yRadius, int segments, float density, Vector2 position = new Vector2(), float rotation = 0, BodyType bodyType = BodyType.Static) + public Body CreateRoundedRectangle(float width, float height, float xRadius, float yRadius, int segments, float density, Vector2 position = new Vector2(), float rotation = 0, BodyType bodyType = BodyType.Static, Category collisionCategory = Category.Cat1, Category collidesWith = Category.All) { Vertices verts = PolygonTools.CreateRoundedRectangle(width, height, xRadius, yRadius, segments); @@ -181,23 +184,23 @@ namespace FarseerPhysics.Dynamics if (verts.Count >= Settings.MaxPolygonVertices) { List vertList = Triangulate.ConvexPartition(verts, TriangulationAlgorithm.Earclip); - return CreateCompoundPolygon(vertList, density, position, rotation, bodyType); + return CreateCompoundPolygon(vertList, density, position, rotation, bodyType, collisionCategory, collidesWith); } - return CreatePolygon(verts, density, position, rotation, bodyType); + return CreatePolygon(verts, density, position, rotation, bodyType, collisionCategory, collidesWith); } - public Body CreateLineArc(float radians, int sides, float radius, bool closed = false, Vector2 position = new Vector2(), float rotation = 0, BodyType bodyType = BodyType.Static) + public Body CreateLineArc(float radians, int sides, float radius, bool closed = false, Vector2 position = new Vector2(), float rotation = 0, BodyType bodyType = BodyType.Static, Category collisionCategory = Category.Cat1, Category collidesWith = Category.All) { Body body = CreateBody(position, rotation, bodyType); - body.CreateLineArc(radians, sides, radius, closed); + body.CreateLineArc(radians, sides, radius, closed, collisionCategory, collidesWith); return body; } - public Body CreateSolidArc(float density, float radians, int sides, float radius, Vector2 position = new Vector2(), float rotation = 0, BodyType bodyType = BodyType.Static) + public Body CreateSolidArc(float density, float radians, int sides, float radius, Vector2 position = new Vector2(), float rotation = 0, BodyType bodyType = BodyType.Static, Category collisionCategory = Category.Cat1, Category collidesWith = Category.All) { Body body = CreateBody(position, rotation, bodyType); - body.CreateSolidArc(density, radians, sides, radius); + body.CreateSolidArc(density, radians, sides, radius, collisionCategory, collidesWith); return body; } diff --git a/Libraries/Farseer Physics Engine 3.5/Dynamics/World.cs b/Libraries/Farseer Physics Engine 3.5/Dynamics/World.cs index e8b8cbea2..0aa2ef49a 100644 --- a/Libraries/Farseer Physics Engine 3.5/Dynamics/World.cs +++ b/Libraries/Farseer Physics Engine 3.5/Dynamics/World.cs @@ -936,7 +936,7 @@ namespace FarseerPhysics.Dynamics /// Warning: This method is locked during callbacks. /// /// Thrown when the world is Locked/Stepping. - public virtual void Add(Body body) + public virtual void Add(Body body, bool findNewContacts) { if (IsLocked) throw new WorldLockedException("Cannot add bodies when the World is locked."); @@ -972,8 +972,10 @@ namespace FarseerPhysics.Dynamics if (Enabled) body.CreateProxies(); - ContactManager.FindNewContacts(); - + if (findNewContacts) + { + ContactManager.FindNewContacts(); + } // Fire World events: @@ -1227,7 +1229,7 @@ namespace FarseerPhysics.Dynamics /// Add a rigid body. /// /// - public void AddAsync(Body body) + public void AddAsync(Body body, bool findNewContacts) { if (body == null) throw new ArgumentNullException("body"); @@ -1243,7 +1245,7 @@ namespace FarseerPhysics.Dynamics Debug.WriteLine("You are adding the same body more than once."); } else - Add(body); + Add(body, findNewContacts); } /// @@ -1322,7 +1324,7 @@ namespace FarseerPhysics.Dynamics if (_bodyAddList.Count > 0) { foreach (Body body in _bodyAddList) - Add(body); + Add(body, findNewContacts: true); _bodyAddList.Clear(); } @@ -1414,7 +1416,7 @@ namespace FarseerPhysics.Dynamics ProcessChanges(); if (Settings.EnableDiagnostics) - AddRemoveTime = TimeSpan.FromTicks(_watch.ElapsedTicks); + AddRemoveTime = TimeSpan.FromTicks(_watch.Elapsed.Ticks); // If new fixtures were added, we need to find the new contacts. if (_worldHasNewFixture) @@ -1423,7 +1425,7 @@ namespace FarseerPhysics.Dynamics _worldHasNewFixture = false; } if (Settings.EnableDiagnostics) - NewContactsTime = TimeSpan.FromTicks(_watch.ElapsedTicks) - AddRemoveTime; + NewContactsTime = TimeSpan.FromTicks(_watch.Elapsed.Ticks) - AddRemoveTime; //FPE only: moved position and velocity iterations into Settings.cs TimeStep step; @@ -1443,12 +1445,12 @@ namespace FarseerPhysics.Dynamics ControllerList[i].Update(dt); } if (Settings.EnableDiagnostics) - ControllersUpdateTime = TimeSpan.FromTicks(_watch.ElapsedTicks) - (AddRemoveTime + NewContactsTime); + ControllersUpdateTime = TimeSpan.FromTicks(_watch.Elapsed.Ticks) - (AddRemoveTime + NewContactsTime); // Update contacts. This is where some contacts are destroyed. ContactManager.Collide(); if (Settings.EnableDiagnostics) - ContactsUpdateTime = TimeSpan.FromTicks(_watch.ElapsedTicks) - (AddRemoveTime + NewContactsTime + ControllersUpdateTime); + ContactsUpdateTime = TimeSpan.FromTicks(_watch.Elapsed.Ticks) - (AddRemoveTime + NewContactsTime + ControllersUpdateTime); // Integrate velocities, solve velocity constraints, and integrate positions. if (_stepComplete && step.dt > 0.0f) @@ -1456,7 +1458,7 @@ namespace FarseerPhysics.Dynamics Solve(ref step); } if (Settings.EnableDiagnostics) - SolveUpdateTime = TimeSpan.FromTicks(_watch.ElapsedTicks) - (AddRemoveTime + NewContactsTime + ControllersUpdateTime + ContactsUpdateTime); + SolveUpdateTime = TimeSpan.FromTicks(_watch.Elapsed.Ticks) - (AddRemoveTime + NewContactsTime + ControllersUpdateTime + ContactsUpdateTime); // Handle TOI events. if (Settings.ContinuousPhysics && step.dt > 0.0f) @@ -1464,7 +1466,7 @@ namespace FarseerPhysics.Dynamics SolveTOI(ref step, ref iterations); } if (Settings.EnableDiagnostics) - ContinuousPhysicsTime = TimeSpan.FromTicks(_watch.ElapsedTicks) - (AddRemoveTime + NewContactsTime + ControllersUpdateTime + ContactsUpdateTime + SolveUpdateTime); + ContinuousPhysicsTime = TimeSpan.FromTicks(_watch.Elapsed.Ticks) - (AddRemoveTime + NewContactsTime + ControllersUpdateTime + ContactsUpdateTime + SolveUpdateTime); if (step.dt > 0.0f) Fluid.Update(dt); diff --git a/Libraries/MonoGame.Framework/Src/MonoGame.Framework/Graphics/Texture2D.OpenGL.cs b/Libraries/MonoGame.Framework/Src/MonoGame.Framework/Graphics/Texture2D.OpenGL.cs index 6e6d04d58..5ec3a2b63 100644 --- a/Libraries/MonoGame.Framework/Src/MonoGame.Framework/Graphics/Texture2D.OpenGL.cs +++ b/Libraries/MonoGame.Framework/Src/MonoGame.Framework/Graphics/Texture2D.OpenGL.cs @@ -35,6 +35,7 @@ namespace Microsoft.Xna.Framework.Graphics format.GetGLFormat(GraphicsDevice, out glInternalFormat, out glFormat, out glType); Threading.BlockOnUIThread(() => { + var prev = GraphicsExtensions.GetBoundTexture2D(); GenerateGLTextureIfRequired(); int w = width; int h = height; @@ -80,6 +81,8 @@ namespace Microsoft.Xna.Framework.Graphics h = h / 2; ++level; } + + GL.BindTexture(TextureTarget.Texture2D, prev); }); } diff --git a/LinuxSolution.sln b/LinuxSolution.sln index 073003f04..095528d41 100644 --- a/LinuxSolution.sln +++ b/LinuxSolution.sln @@ -39,6 +39,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LinuxServer", "Barotrauma\B EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MonoGame.Framework.Linux.NetStandard", "Libraries\MonoGame.Framework\Src\MonoGame.Framework\MonoGame.Framework.Linux.NetStandard.csproj", "{33E95A21-E071-4432-819F-AA64CF3EF3F1}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LinuxTest", "Barotrauma\BarotraumaTest\LinuxTest.csproj", "{F1B80D94-8BD6-48CE-8D17-BB2A5C98BCA3}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -181,6 +183,18 @@ Global {33E95A21-E071-4432-819F-AA64CF3EF3F1}.Unstable|Any CPU.Build.0 = Release|Any CPU {33E95A21-E071-4432-819F-AA64CF3EF3F1}.Unstable|x64.ActiveCfg = Unstable|x64 {33E95A21-E071-4432-819F-AA64CF3EF3F1}.Unstable|x64.Build.0 = Unstable|x64 + {F1B80D94-8BD6-48CE-8D17-BB2A5C98BCA3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F1B80D94-8BD6-48CE-8D17-BB2A5C98BCA3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F1B80D94-8BD6-48CE-8D17-BB2A5C98BCA3}.Debug|x64.ActiveCfg = Debug|Any CPU + {F1B80D94-8BD6-48CE-8D17-BB2A5C98BCA3}.Debug|x64.Build.0 = Debug|Any CPU + {F1B80D94-8BD6-48CE-8D17-BB2A5C98BCA3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F1B80D94-8BD6-48CE-8D17-BB2A5C98BCA3}.Release|Any CPU.Build.0 = Release|Any CPU + {F1B80D94-8BD6-48CE-8D17-BB2A5C98BCA3}.Release|x64.ActiveCfg = Release|Any CPU + {F1B80D94-8BD6-48CE-8D17-BB2A5C98BCA3}.Release|x64.Build.0 = Release|Any CPU + {F1B80D94-8BD6-48CE-8D17-BB2A5C98BCA3}.Unstable|Any CPU.ActiveCfg = Debug|Any CPU + {F1B80D94-8BD6-48CE-8D17-BB2A5C98BCA3}.Unstable|Any CPU.Build.0 = Debug|Any CPU + {F1B80D94-8BD6-48CE-8D17-BB2A5C98BCA3}.Unstable|x64.ActiveCfg = Debug|Any CPU + {F1B80D94-8BD6-48CE-8D17-BB2A5C98BCA3}.Unstable|x64.Build.0 = Debug|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -198,6 +212,7 @@ Global {D47E4AAA-C3E5-4F0D-B7FF-D3B54966DE51} = {68B18BE6-9EE0-49DA-AE3A-4C7326F768F9} {2B0881F6-9C67-4446-A1F2-FC042763A462} = {68B18BE6-9EE0-49DA-AE3A-4C7326F768F9} {33E95A21-E071-4432-819F-AA64CF3EF3F1} = {DE36F45F-F09E-4719-B953-00D148F7722A} + {F1B80D94-8BD6-48CE-8D17-BB2A5C98BCA3} = {68B18BE6-9EE0-49DA-AE3A-4C7326F768F9} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {17032EAB-554B-4B44-A4F6-EFB177ACAB7A} diff --git a/MacSolution.sln b/MacSolution.sln index 162365f9c..20e699038 100644 --- a/MacSolution.sln +++ b/MacSolution.sln @@ -36,6 +36,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MonoGame.Framework.MacOS.Ne EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Facepunch.Steamworks.Posix", "Libraries\Facepunch.Steamworks\Facepunch.Steamworks.Posix.csproj", "{F10CE3BB-26B8-446E-84D2-86D25E850F61}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MacTest", "Barotrauma\BarotraumaTest\MacTest.csproj", "{20BC9336-B439-4BF1-8B65-D587DBF421D1}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -178,6 +180,18 @@ Global {F10CE3BB-26B8-446E-84D2-86D25E850F61}.Unstable|Any CPU.Build.0 = Release|Any CPU {F10CE3BB-26B8-446E-84D2-86D25E850F61}.Unstable|x64.ActiveCfg = Release|Any CPU {F10CE3BB-26B8-446E-84D2-86D25E850F61}.Unstable|x64.Build.0 = Release|Any CPU + {20BC9336-B439-4BF1-8B65-D587DBF421D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {20BC9336-B439-4BF1-8B65-D587DBF421D1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {20BC9336-B439-4BF1-8B65-D587DBF421D1}.Debug|x64.ActiveCfg = Debug|Any CPU + {20BC9336-B439-4BF1-8B65-D587DBF421D1}.Debug|x64.Build.0 = Debug|Any CPU + {20BC9336-B439-4BF1-8B65-D587DBF421D1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {20BC9336-B439-4BF1-8B65-D587DBF421D1}.Release|Any CPU.Build.0 = Release|Any CPU + {20BC9336-B439-4BF1-8B65-D587DBF421D1}.Release|x64.ActiveCfg = Release|Any CPU + {20BC9336-B439-4BF1-8B65-D587DBF421D1}.Release|x64.Build.0 = Release|Any CPU + {20BC9336-B439-4BF1-8B65-D587DBF421D1}.Unstable|Any CPU.ActiveCfg = Debug|Any CPU + {20BC9336-B439-4BF1-8B65-D587DBF421D1}.Unstable|Any CPU.Build.0 = Debug|Any CPU + {20BC9336-B439-4BF1-8B65-D587DBF421D1}.Unstable|x64.ActiveCfg = Debug|Any CPU + {20BC9336-B439-4BF1-8B65-D587DBF421D1}.Unstable|x64.Build.0 = Debug|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -195,6 +209,7 @@ Global {F17FB469-E9E6-4B1C-B887-4FE709D4D771} = {DFD82BBD-8D05-403D-BEBC-F4C1CF783E18} {35DDDA7D-328D-4A5D-BCBB-2E60C830A899} = {DE36F45F-F09E-4719-B953-00D148F7722A} {F10CE3BB-26B8-446E-84D2-86D25E850F61} = {DE36F45F-F09E-4719-B953-00D148F7722A} + {20BC9336-B439-4BF1-8B65-D587DBF421D1} = {DFD82BBD-8D05-403D-BEBC-F4C1CF783E18} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {17032EAB-554B-4B44-A4F6-EFB177ACAB7A} diff --git a/WindowsSolution.sln b/WindowsSolution.sln index 9c1bdeae7..515450184 100644 --- a/WindowsSolution.sln +++ b/WindowsSolution.sln @@ -39,6 +39,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "XNATypes", "Libraries\XNATy EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharpFont.NetStandard", "Libraries\SharpFont\Source\SharpFont\SharpFont.NetStandard.csproj", "{6911872D-40EF-400C-B0A1-9985A19ED488}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WindowsTest", "Barotrauma\BarotraumaTest\WindowsTest.csproj", "{C7212AE2-A925-4225-A639-AE0653EF65B0}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution Libraries\GameAnalytics\GA-SDK-MONO-SHARED\GA-SDK-MONO-SHARED.projitems*{95c4d59d-9be4-4278-b4f8-46c0ba1a3916}*SharedItemsImports = 5 @@ -115,6 +117,12 @@ Global {6911872D-40EF-400C-B0A1-9985A19ED488}.Release|x64.Build.0 = Release|x64 {6911872D-40EF-400C-B0A1-9985A19ED488}.Unstable|x64.ActiveCfg = Release|x64 {6911872D-40EF-400C-B0A1-9985A19ED488}.Unstable|x64.Build.0 = Release|x64 + {C7212AE2-A925-4225-A639-AE0653EF65B0}.Debug|x64.ActiveCfg = Debug|Any CPU + {C7212AE2-A925-4225-A639-AE0653EF65B0}.Debug|x64.Build.0 = Debug|Any CPU + {C7212AE2-A925-4225-A639-AE0653EF65B0}.Release|x64.ActiveCfg = Release|Any CPU + {C7212AE2-A925-4225-A639-AE0653EF65B0}.Release|x64.Build.0 = Release|Any CPU + {C7212AE2-A925-4225-A639-AE0653EF65B0}.Unstable|x64.ActiveCfg = Debug|Any CPU + {C7212AE2-A925-4225-A639-AE0653EF65B0}.Unstable|x64.Build.0 = Debug|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -132,6 +140,7 @@ Global {47848C6E-C7A8-4EC3-96C2-3BC8A4234AFA} = {78A9F0AA-5519-407A-9B72-2A09F5DF7068} {1F318AC4-F808-4130-867F-B98DF9AA8F95} = {DE36F45F-F09E-4719-B953-00D148F7722A} {6911872D-40EF-400C-B0A1-9985A19ED488} = {DE36F45F-F09E-4719-B953-00D148F7722A} + {C7212AE2-A925-4225-A639-AE0653EF65B0} = {78A9F0AA-5519-407A-9B72-2A09F5DF7068} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {17032EAB-554B-4B44-A4F6-EFB177ACAB7A}