diff --git a/.gitignore b/.gitignore index 49930d4bc..181e352c6 100644 --- a/.gitignore +++ b/.gitignore @@ -12,11 +12,11 @@ bld/ [Dd]ebug*/ [Rr]elease*/ -# Barotrauma content folder -BarotraumaShared/Content/ - +# Misc vs crap *.v12.suo *.suo +*.csproj.user +*.shproj.user #performance reports & sessions *.vsp @@ -25,3 +25,6 @@ BarotraumaShared/Content/ # Mac *.DS_Store + +#Merge script +temp.txt diff --git a/Barotrauma/BarotraumaClient/ClientCode.projitems b/Barotrauma/BarotraumaClient/ClientCode.projitems index dddac413d..8b57735b9 100644 --- a/Barotrauma/BarotraumaClient/ClientCode.projitems +++ b/Barotrauma/BarotraumaClient/ClientCode.projitems @@ -160,6 +160,7 @@ + diff --git a/Barotrauma/BarotraumaClient/Properties/AssemblyInfo.cs b/Barotrauma/BarotraumaClient/Properties/AssemblyInfo.cs index 82dea8e96..6509147bc 100644 --- a/Barotrauma/BarotraumaClient/Properties/AssemblyInfo.cs +++ b/Barotrauma/BarotraumaClient/Properties/AssemblyInfo.cs @@ -31,5 +31,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("0.9.1.0")] +[assembly: AssemblyVersion("0.9.200.0")] [assembly: AssemblyFileVersion("0.9.1.0")] diff --git a/Barotrauma/BarotraumaClient/Source/Camera.cs b/Barotrauma/BarotraumaClient/Source/Camera.cs index e16142fe4..fe79671b4 100644 --- a/Barotrauma/BarotraumaClient/Source/Camera.cs +++ b/Barotrauma/BarotraumaClient/Source/Camera.cs @@ -1,4 +1,6 @@ -using Microsoft.Xna.Framework; +using Barotrauma.Networking; +using Lidgren.Network; +using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Input; using System; @@ -171,6 +173,11 @@ namespace Barotrauma get { return targetPos; } set { targetPos = value; } } + + public Vector2 GetPosition() + { + return position; + } // Auxiliary function to move the camera public void Translate(Vector2 amount) @@ -178,6 +185,15 @@ namespace Barotrauma position += amount; } + public void ClientWrite(NetOutgoingMessage msg) + { + if (Character.Controlled != null && !Character.Controlled.IsDead) return; + + msg.Write((byte)ClientNetObject.SPECTATING_POS); + msg.Write(position.X); + msg.Write(position.Y); + } + private void CreateMatrices() { resolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight); diff --git a/Barotrauma/BarotraumaClient/Source/Characters/CharacterNetworking.cs b/Barotrauma/BarotraumaClient/Source/Characters/CharacterNetworking.cs index 406452aab..5ad1a4063 100644 --- a/Barotrauma/BarotraumaClient/Source/Characters/CharacterNetworking.cs +++ b/Barotrauma/BarotraumaClient/Source/Characters/CharacterNetworking.cs @@ -14,6 +14,13 @@ namespace Barotrauma { if (this != Controlled) { + if (GameMain.Client.EndCinematic != null) // Freezes the characters during the ending cinematic + { + AnimController.Frozen = true; + memState.Clear(); + return; + } + //freeze AI characters if more than 1 seconds have passed since last update from the server if (lastRecvPositionUpdateTime < NetTime.Now - 1.0f) { diff --git a/Barotrauma/BarotraumaClient/Source/Characters/Limb.cs b/Barotrauma/BarotraumaClient/Source/Characters/Limb.cs index 846e82d39..af335ceb0 100644 --- a/Barotrauma/BarotraumaClient/Source/Characters/Limb.cs +++ b/Barotrauma/BarotraumaClient/Source/Characters/Limb.cs @@ -128,6 +128,7 @@ namespace Barotrauma } public WearableSprite HuskSprite { get; private set; } + public WearableSprite HerpesSprite { get; private set; } public void LoadHuskSprite() { @@ -139,6 +140,16 @@ namespace Barotrauma HuskSprite = new WearableSprite(element.Element("sprite"), WearableType.Husk); } } + public void LoadHerpesSprite() + { + var info = character.Info; + if (info == null) { return; } + var element = info.FilterByTypeAndHeadID(character.Info.FilterElementsByGenderAndRace(character.Info.Wearables), WearableType.Herpes).FirstOrDefault(); + if (element != null) + { + HerpesSprite = new WearableSprite(element.Element("sprite"), WearableType.Herpes); + } + } public float TextureScale => limbParams.Ragdoll.TextureScale; @@ -387,7 +398,7 @@ namespace Barotrauma color *= SeveredFadeOutTime - severedFadeOutTimer; } } - + body.Dir = Dir; bool hideLimb = wearingItems.Any(w => w != null && w.HideLimb); @@ -425,10 +436,20 @@ namespace Barotrauma SpriteEffects spriteEffect = (dir == Direction.Right) ? SpriteEffects.None : SpriteEffects.FlipHorizontally; if (onlyDrawable == null) { + if (HerpesSprite != null) + { + float herpesStrength = character.CharacterHealth.GetAfflictionStrength("spaceherpes"); + if (herpesStrength > 0.0f) + { + DrawWearable(HerpesSprite, depthStep, spriteBatch, color * (herpesStrength / 100.0f), spriteEffect); + depthStep += 0.000001f; + } + } if (HuskSprite != null && (character.SpeciesName == "Humanhusk" || (character.SpeciesName == "Human" && character.CharacterHealth.GetAffliction("huskinfection")?.State == AfflictionHusk.InfectionState.Active))) { DrawWearable(HuskSprite, depthStep, spriteBatch, color, spriteEffect); + depthStep += 0.000001f; } foreach (WearableSprite wearable in OtherWearables) { @@ -585,6 +606,9 @@ namespace Barotrauma HuskSprite?.Sprite.Remove(); HuskSprite = null; + + HerpesSprite?.Sprite.Remove(); + HerpesSprite = null; } } } diff --git a/Barotrauma/BarotraumaClient/Source/DebugConsole.cs b/Barotrauma/BarotraumaClient/Source/DebugConsole.cs index 17fc3b0f6..429bb51f2 100644 --- a/Barotrauma/BarotraumaClient/Source/DebugConsole.cs +++ b/Barotrauma/BarotraumaClient/Source/DebugConsole.cs @@ -133,17 +133,7 @@ namespace Barotrauma if (PlayerInput.KeyHit(Keys.F3)) { - isOpen = !isOpen; - if (isOpen) - { - textBox.Select(); - AddToGUIUpdateList(); - } - else - { - GUI.ForceMouseOn(null); - textBox.Deselect(); - } + Toggle(); } else if (isOpen && PlayerInput.KeyHit(Keys.Escape)) { @@ -179,6 +169,21 @@ namespace Barotrauma } } + public static void Toggle() + { + isOpen = !isOpen; + if (isOpen) + { + textBox.Select(); + AddToGUIUpdateList(); + } + else + { + GUI.ForceMouseOn(null); + textBox.Deselect(); + } + } + public static void Draw(SpriteBatch spriteBatch) { if (!isOpen) return; @@ -298,9 +303,16 @@ namespace Barotrauma private static void AssignOnClientExecute(string names, Action onClientExecute) { - Command command = commands.First(c => c.names.Intersect(names.Split('|')).Count() > 0); - command.OnClientExecute = onClientExecute; - command.RelayToServer = false; + Command command = commands.Find(c => c.names.Intersect(names.Split('|')).Count() > 0); + if (command == null) + { + throw new Exception("AssignOnClientExecute failed. Command matching the name(s) \"" + names + "\" not found."); + } + else + { + command.OnClientExecute = onClientExecute; + command.RelayToServer = false; + } } private static void AssignRelayToServer(string names, bool relay) @@ -1797,6 +1809,11 @@ namespace Barotrauma limb.HuskSprite.Sprite.ReloadXML(); limb.HuskSprite.Sprite.ReloadTexture(); } + if (limb.HerpesSprite != null) + { + limb.HerpesSprite.Sprite.ReloadXML(); + limb.HerpesSprite.Sprite.ReloadTexture(); + } } } diff --git a/Barotrauma/BarotraumaClient/Source/GUI/GUI.cs b/Barotrauma/BarotraumaClient/Source/GUI/GUI.cs index 3418d020e..4585a78d4 100644 --- a/Barotrauma/BarotraumaClient/Source/GUI/GUI.cs +++ b/Barotrauma/BarotraumaClient/Source/GUI/GUI.cs @@ -278,7 +278,7 @@ namespace Barotrauma Color.White, Color.Black * 0.5f, 0, SmallFont); DrawString(spriteBatch, new Vector2(10, 40), - "Bodies: " + GameMain.World.BodyList.Count + " (" + GameMain.World.BodyList.FindAll(b => b.Awake && b.Enabled).Count + " awake)", + $"Bodies: {GameMain.World.BodyList.Count} ({GameMain.World.BodyList.FindAll(b => b.Awake && b.Enabled).Count} awake, {GameMain.World.BodyList.FindAll(b => b.Awake && b.BodyType == FarseerPhysics.Dynamics.BodyType.Dynamic && b.Enabled).Count} dynamic)", Color.White, Color.Black * 0.5f, 0, SmallFont); if (Screen.Selected.Cam != null) @@ -1040,6 +1040,8 @@ namespace Barotrauma public static Texture2D CreateRectangle(int width, int height) { + width = Math.Max(width, 1); + height = Math.Max(height, 1); Color[] data = new Color[width * height]; for (int i = 0; i < data.Length; i++) @@ -1057,7 +1059,6 @@ namespace Barotrauma TrySetArray(data, (height - 1) * width + x, Color.White); } - Texture2D texture = null; CrossThread.RequestExecutionOnMainThread(() => { diff --git a/Barotrauma/BarotraumaClient/Source/GUI/LoadingScreen.cs b/Barotrauma/BarotraumaClient/Source/GUI/LoadingScreen.cs index d84e9a2dd..52b571a7c 100644 --- a/Barotrauma/BarotraumaClient/Source/GUI/LoadingScreen.cs +++ b/Barotrauma/BarotraumaClient/Source/GUI/LoadingScreen.cs @@ -178,25 +178,27 @@ namespace Barotrauma } else if (DrawLoadingText) { - string loadText = ""; - if (LoadState == 100.0f) + if (TextManager.Initialized) { - loadText = TextManager.Get("PressAnyKey"); - } - else - { - loadText = TextManager.Get("Loading"); - if (LoadState != null) + string loadText; + if (LoadState == 100.0f) { - loadText += " " + (int)LoadState + " %"; + loadText = TextManager.Get("PressAnyKey"); + } + else + { + loadText = TextManager.Get("Loading"); + if (LoadState != null) + { + loadText += " " + (int)LoadState + " %"; + } + } + if (GUI.LargeFont != null) + { + GUI.LargeFont.DrawString(spriteBatch, loadText.ToUpper(), + new Vector2(GameMain.GraphicsWidth / 2.0f - GUI.LargeFont.MeasureString(loadText).X / 2.0f, GameMain.GraphicsHeight * 0.7f), + Color.White); } - } - - if (GUI.LargeFont != null) - { - GUI.LargeFont.DrawString(spriteBatch, loadText.ToUpper(), - new Vector2(GameMain.GraphicsWidth / 2.0f - GUI.LargeFont.MeasureString(loadText).X / 2.0f, GameMain.GraphicsHeight * 0.7f), - Color.White); } if (GUI.Font != null && selectedTip != null) diff --git a/Barotrauma/BarotraumaClient/Source/GUI/Widget.cs b/Barotrauma/BarotraumaClient/Source/GUI/Widget.cs index bd4206e00..9fe9ca365 100644 --- a/Barotrauma/BarotraumaClient/Source/GUI/Widget.cs +++ b/Barotrauma/BarotraumaClient/Source/GUI/Widget.cs @@ -58,6 +58,8 @@ namespace Barotrauma public event Action PreDraw; public event Action PostDraw; + public bool RequireMouseOn = true; + public Action refresh; public object data; @@ -109,7 +111,7 @@ namespace Barotrauma { PreUpdate?.Invoke(deltaTime); if (!enabled) { return; } - if (IsMouseOver) + if (IsMouseOver || (!RequireMouseOn && selectedWidgets.Contains(this) && PlayerInput.LeftButtonHeld())) { Hovered?.Invoke(); if ((multiselect && !selectedWidgets.Contains(this)) || selectedWidgets.None()) diff --git a/Barotrauma/BarotraumaClient/Source/GameMain.cs b/Barotrauma/BarotraumaClient/Source/GameMain.cs index 92e524f18..13fdede06 100644 --- a/Barotrauma/BarotraumaClient/Source/GameMain.cs +++ b/Barotrauma/BarotraumaClient/Source/GameMain.cs @@ -404,54 +404,13 @@ namespace Barotrauma DebugConsole.Log("Selected content packages: " + string.Join(", ", SelectedPackages.Select(cp => cp.Name))); } -/*#if DEBUG +#if DEBUG GameSettings.ShowUserStatisticsPrompt = false; GameSettings.SendUserStatistics = false; -#endif*/ +#endif InitUserStats(); - yield return CoroutineStatus.Running; - - LightManager = new Lights.LightManager(base.GraphicsDevice, Content); - - TitleScreen.LoadState = 1.0f; - yield return CoroutineStatus.Running; - - GUI.LoadContent(); - TitleScreen.LoadState = 2.0f; - - yield return CoroutineStatus.Running; - - MissionPrefab.Init(); - MapEntityPrefab.Init(); - Tutorials.Tutorial.Init(); - MapGenerationParams.Init(); - LevelGenerationParams.LoadPresets(); - ScriptedEventSet.LoadPrefabs(); - AfflictionPrefab.LoadAll(GetFilesOfType(ContentType.Afflictions)); - TitleScreen.LoadState = 10.0f; - yield return CoroutineStatus.Running; - - StructurePrefab.LoadAll(GetFilesOfType(ContentType.Structure)); - TitleScreen.LoadState = 15.0f; - yield return CoroutineStatus.Running; - - ItemPrefab.LoadAll(GetFilesOfType(ContentType.Item)); - TitleScreen.LoadState = 25.0f; - yield return CoroutineStatus.Running; - - JobPrefab.LoadAll(GetFilesOfType(ContentType.Jobs)); - // Add any missing jobs from the prefab into Config.JobNamePreferences. - foreach (JobPrefab job in JobPrefab.List) - { - if (!Config.JobPreferences.Contains(job.Identifier)) { Config.JobPreferences.Add(job.Identifier); } - } - - NPCConversation.LoadAll(GetFilesOfType(ContentType.NPCConversations)); - - ItemAssemblyPrefab.LoadAll(); - TitleScreen.LoadState = 30.0f; yield return CoroutineStatus.Running; Debug.WriteLine("sounds"); @@ -464,55 +423,105 @@ namespace Barotrauma i++; TitleScreen.LoadState = SoundPlayer.SoundCount == 0 ? - 30.0f : - Math.Min(30.0f + 40.0f * i / Math.Max(SoundPlayer.SoundCount, 1), 70.0f); + 1.0f : + Math.Min(40.0f * i / Math.Max(SoundPlayer.SoundCount, 1), 40.0f); yield return CoroutineStatus.Running; } - TitleScreen.LoadState = 70.0f; + TitleScreen.LoadState = 40.0f; yield return CoroutineStatus.Running; + LightManager = new Lights.LightManager(base.GraphicsDevice, Content); + + TitleScreen.LoadState = 41.0f; + yield return CoroutineStatus.Running; + + GUI.LoadContent(); + TitleScreen.LoadState = 42.0f; + + yield return CoroutineStatus.Running; + + MissionPrefab.Init(); + MapEntityPrefab.Init(); + Tutorials.Tutorial.Init(); + MapGenerationParams.Init(); + LevelGenerationParams.LoadPresets(); + ScriptedEventSet.LoadPrefabs(); + AfflictionPrefab.LoadAll(GetFilesOfType(ContentType.Afflictions)); + TitleScreen.LoadState = 50.0f; + yield return CoroutineStatus.Running; + + StructurePrefab.LoadAll(GetFilesOfType(ContentType.Structure)); + TitleScreen.LoadState = 53.0f; + yield return CoroutineStatus.Running; + + ItemPrefab.LoadAll(GetFilesOfType(ContentType.Item)); + TitleScreen.LoadState = 55.0f; + yield return CoroutineStatus.Running; + + JobPrefab.LoadAll(GetFilesOfType(ContentType.Jobs)); + // Add any missing jobs from the prefab into Config.JobNamePreferences. + foreach (JobPrefab job in JobPrefab.List) + { + if (!Config.JobPreferences.Contains(job.Identifier)) { Config.JobPreferences.Add(job.Identifier); } + } + + NPCConversation.LoadAll(GetFilesOfType(ContentType.NPCConversations)); + + ItemAssemblyPrefab.LoadAll(); + TitleScreen.LoadState = 60.0f; + yield return CoroutineStatus.Running; + GameModePreset.Init(); Submarine.RefreshSavedSubs(); - TitleScreen.LoadState = 80.0f; - + TitleScreen.LoadState = 65.0f; yield return CoroutineStatus.Running; GameScreen = new GameScreen(GraphicsDeviceManager.GraphicsDevice, Content); - TitleScreen.LoadState = 90.0f; - + TitleScreen.LoadState = 68.0f; yield return CoroutineStatus.Running; MainMenuScreen = new MainMenuScreen(this); LobbyScreen = new LobbyScreen(); ServerListScreen = new ServerListScreen(); + TitleScreen.LoadState = 70.0f; + yield return CoroutineStatus.Running; + if (SteamManager.USE_STEAM) { SteamWorkshopScreen = new SteamWorkshopScreen(); } - SubEditorScreen = new SubEditorScreen(); + + TitleScreen.LoadState = 75.0f; + yield return CoroutineStatus.Running; + ParticleEditorScreen = new ParticleEditorScreen(); + + TitleScreen.LoadState = 80.0f; + yield return CoroutineStatus.Running; + LevelEditorScreen = new LevelEditorScreen(); SpriteEditorScreen = new SpriteEditorScreen(); CharacterEditorScreen = new CharacterEditorScreen(); yield return CoroutineStatus.Running; - TitleScreen.LoadState = 95.0f; + TitleScreen.LoadState = 85.0f; ParticleManager = new ParticleManager(GameScreen.Cam); ParticleManager.LoadPrefabs(); - TitleScreen.LoadState = 97.0f; + TitleScreen.LoadState = 88.0f; LevelObjectPrefab.LoadAll(); - DecalManager = new DecalManager(); - TitleScreen.LoadState = 99.0f; + + TitleScreen.LoadState = 90.0f; yield return CoroutineStatus.Running; + DecalManager = new DecalManager(); LocationType.Init(); MainMenuScreen.Select(); @@ -634,6 +643,7 @@ namespace Barotrauma PlayerInput.Update(Timing.Step); + if (loadingScreenOpen) { //reset accumulator if loading @@ -641,6 +651,11 @@ namespace Barotrauma // -> no pause caused by leftover time in the accumulator when starting a new shift GameMain.ResetFrameTime(); + if (!TitleScreen.PlayingSplashScreen) + { + SoundPlayer.Update((float)Timing.Step); + } + if (TitleScreen.LoadState >= 100.0f && !TitleScreen.PlayingSplashScreen && (!waitForKeyHit || ((PlayerInput.GetKeyboardState.GetPressedKeys().Length > 0 || PlayerInput.LeftButtonClicked()) && WindowActive))) { diff --git a/Barotrauma/BarotraumaClient/Source/GameSession/CrewManager.cs b/Barotrauma/BarotraumaClient/Source/GameSession/CrewManager.cs index 647871bfa..ab40ee75f 100644 --- a/Barotrauma/BarotraumaClient/Source/GameSession/CrewManager.cs +++ b/Barotrauma/BarotraumaClient/Source/GameSession/CrewManager.cs @@ -455,10 +455,14 @@ namespace Barotrauma isHorizontal: true, childAnchor: Anchor.CenterLeft) { AbsoluteSpacing = (int)(10 * GUI.Scale), - UserData = "orderbuttons", - CanBeFocused = false + UserData = "orderbuttons" }; + var spacer = new GUIFrame(new RectTransform(new Point(spacing, orderButtonFrame.Rect.Height), frame.RectTransform) + { + AbsoluteOffset = new Point(characterInfoWidth, 0) + }); + //listbox for holding the orders inappropriate for this character //(so we can easily toggle their visibility) var wrongOrderList = new GUIListBox(new RectTransform(new Point(50, orderButtonFrame.Rect.Height), orderButtonFrame.RectTransform), isHorizontal: true, style: null) diff --git a/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/MechanicTutorial.cs b/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/MechanicTutorial.cs index 7eebb036c..4b04bd01e 100644 --- a/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/MechanicTutorial.cs +++ b/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/MechanicTutorial.cs @@ -32,7 +32,6 @@ namespace Barotrauma.Tutorials private LightComponent mechanic_thirdDoorLight; private Structure mechanic_brokenWall_1; private Hull mechanic_brokenhull_1; - private MotionSensor mechanic_ladderSensor; // Room 4 private MotionSensor mechanic_craftingObjectiveSensor; diff --git a/Barotrauma/BarotraumaClient/Source/Items/CharacterInventory.cs b/Barotrauma/BarotraumaClient/Source/Items/CharacterInventory.cs index b79bd9c75..0acf7811a 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/CharacterInventory.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/CharacterInventory.cs @@ -752,11 +752,11 @@ namespace Barotrauma character.SelectedBy.Inventory != null) { //item is in the inventory of another character -> attempt to get the item from there - success = character.SelectedBy.Inventory.TryPutItem(item, Character.Controlled, item.AllowedSlots, true); + success = character.SelectedBy.Inventory.TryPutItemWithAutoEquipCheck(item, Character.Controlled, item.AllowedSlots, true); } break; case QuickUseAction.TakeFromContainer: - success = TryPutItem(item, Character.Controlled, item.AllowedSlots, true); + success = TryPutItemWithAutoEquipCheck(item, Character.Controlled, item.AllowedSlots, true); break; } diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Door.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Door.cs index 24571071e..e624d8ab5 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/Door.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/Door.cs @@ -182,7 +182,7 @@ namespace Barotrauma.Items.Components } - partial void SetState(bool open, bool isNetworkMessage, bool sendNetworkMessage) + partial void SetState(bool open, bool isNetworkMessage, bool sendNetworkMessage, bool forcedOpen) { if (isStuck || (PredictedState == null && isOpen == open) || @@ -200,12 +200,16 @@ namespace Barotrauma.Items.Components //sent by the server, or reverting it back to its old state if no msg from server was received PredictedState = open; resetPredictionTimer = CorrectionDelay; - if (stateChanged) PlaySound(ActionType.OnUse, item.WorldPosition); + if (stateChanged) PlaySound(forcedOpen ? ActionType.OnPicked : ActionType.OnUse, item.WorldPosition); } else { isOpen = open; - if (!isNetworkMessage || open != PredictedState) PlaySound(ActionType.OnUse, item.WorldPosition); + if (!isNetworkMessage || open != PredictedState) + { + StopPicking(null); + PlaySound(forcedOpen ? ActionType.OnPicked : ActionType.OnUse, item.WorldPosition); + } } //opening a partially stuck door makes it less stuck @@ -217,7 +221,9 @@ namespace Barotrauma.Items.Components { base.ClientRead(type, msg, sendingTime); - SetState(msg.ReadBoolean(), isNetworkMessage: true, sendNetworkMessage: false); + bool open = msg.ReadBoolean(); + bool forcedOpen = msg.ReadBoolean(); + SetState(open, isNetworkMessage: true, sendNetworkMessage: false, forcedOpen: forcedOpen); Stuck = msg.ReadRangedSingle(0.0f, 100.0f, 8); PredictedState = null; diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/ItemComponent.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/ItemComponent.cs index c5ecaf754..3ff365527 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/ItemComponent.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/ItemComponent.cs @@ -291,16 +291,16 @@ namespace Barotrauma.Items.Components else { float volume = GetSoundVolume(itemSound); - if (volume <= 0.0f) return; + if (volume <= 0.0f) { return; } SoundPlayer.PlaySound(itemSound.RoundSound.Sound, position, volume, itemSound.Range, item.CurrentHull); } } public void StopSounds(ActionType type) { - if (loopingSound == null) return; + if (loopingSound == null) { return; } - if (loopingSound.Type != type) return; + if (loopingSound.Type != type) { return; } if (loopingSoundChannel != null) { diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/MiniMap.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/MiniMap.cs index d1f6f9ad4..a6d4c56d4 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/MiniMap.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/MiniMap.cs @@ -25,12 +25,12 @@ namespace Barotrauma.Items.Components { noPowerTip = TextManager.Get("SteeringNoPowerTip"); - GuiFrame.RectTransform.RelativeOffset = new Vector2(0.4f, 0.0f); - new GUICustomComponent(new RectTransform(new Vector2(0.9f, 0.85f), GuiFrame.RectTransform, Anchor.Center), + GuiFrame.RectTransform.RelativeOffset = new Vector2(0.05f, 0.0f); + new GUICustomComponent(new RectTransform(new Vector2(0.95f, 0.9f), GuiFrame.RectTransform, Anchor.Center), DrawHUDBack, null); - submarineContainer = new GUIFrame(new RectTransform(new Vector2(0.9f, 0.85f), GuiFrame.RectTransform, Anchor.Center), style: null); + submarineContainer = new GUIFrame(new RectTransform(new Vector2(0.95f, 0.9f), GuiFrame.RectTransform, Anchor.Center), style: null); - new GUICustomComponent(new RectTransform(new Vector2(0.9f, 0.85f), GuiFrame.RectTransform, Anchor.Center), + new GUICustomComponent(new RectTransform(new Vector2(0.95f, 0.9f), GuiFrame.RectTransform, Anchor.Center), DrawHUDFront, null) { CanBeFocused = false @@ -179,11 +179,21 @@ namespace Barotrauma.Items.Components GetLinkedHulls(hull, hullData.LinkedHulls); hullDatas.Add(hull, hullData); } + + Color neutralColor = Color.DarkCyan; + if (hull.RoomName != null) + { + if (hull.RoomName.Contains("ballast") || hull.RoomName.Contains("Ballast") || + hull.RoomName.Contains("airlock") || hull.RoomName.Contains("Airlock")) + { + neutralColor = new Color(9, 80, 159); + } + } if (hullData.Distort) { hullFrame.Children.First().Color = Color.Lerp(Color.Black, Color.DarkGray * 0.5f, Rand.Range(0.0f, 1.0f)); - hullFrame.Color = Color.DarkGray * 0.5f; + hullFrame.Color = neutralColor * 0.5f; continue; } @@ -192,20 +202,23 @@ namespace Barotrauma.Items.Components hullFrame.Parent.Rect.Width / (float)hull.Submarine.Borders.Width, hullFrame.Parent.Rect.Height / (float)hull.Submarine.Borders.Height); - Color borderColor = Color.DarkCyan; + Color borderColor = neutralColor; float? gapOpenSum = 0.0f; if (ShowHullIntegrity) { gapOpenSum = hull.ConnectedGaps.Where(g => !g.IsRoomToRoom).Sum(g => g.Open); - borderColor = Color.Lerp(Color.DarkCyan, Color.Red, Math.Min((float)gapOpenSum, 1.0f)); + borderColor = Color.Lerp(neutralColor, Color.Red, Math.Min((float)gapOpenSum, 1.0f)); } float? oxygenAmount = null; if (!RequireOxygenDetectors || hullData?.Oxygen != null) { oxygenAmount = RequireOxygenDetectors ? hullData.Oxygen : hull.OxygenPercentage; - GUI.DrawRectangle(spriteBatch, hullFrame.Rect, Color.Lerp(Color.Red * 0.5f, Color.Green * 0.3f, (float)oxygenAmount / 100.0f), true); + GUI.DrawRectangle( + spriteBatch, hullFrame.Rect, + Color.Lerp(Color.Red * 0.5f, Color.Green * 0.3f, (float)oxygenAmount / 100.0f), + true); } float? waterAmount = null; @@ -234,7 +247,7 @@ namespace Barotrauma.Items.Components } else { - hullFrame.Children.First().Color = Color.DarkCyan * 0.8f; + hullFrame.Children.First().Color = neutralColor * 0.8f; } if (mouseOnHull == hull) @@ -291,8 +304,6 @@ namespace Barotrauma.Items.Components GUI.DrawLine(spriteBatch, center + start, center + end, Color.DarkCyan * Rand.Range(0.3f, 0.35f), width: (int)(10 * GUI.Scale)); } } - - } private void GetLinkedHulls(Hull hull, List linkedHulls) diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Reactor.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Reactor.cs index b361c951f..dcaff5bb2 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Reactor.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Reactor.cs @@ -58,6 +58,10 @@ namespace Barotrauma.Items.Components private GUIFrame inventoryContainer; private GUIComponent leftHUDColumn; + private GUIComponent midHUDColumn; + private GUIComponent rightHUDColumn; + + private GUILayoutGroup sliderControlsContainer; private Dictionary warningButtons = new Dictionary(); @@ -107,11 +111,13 @@ namespace Barotrauma.Items.Components RelativeSpacing = 0.012f, Stretch = true }; - + GUIFrame columnLeft = new GUIFrame(new RectTransform(new Vector2(0.25f, 1.0f), paddedFrame.RectTransform), style: null); - leftHUDColumn = columnLeft; GUIFrame columnMid = new GUIFrame(new RectTransform(new Vector2(0.45f, 1.0f), paddedFrame.RectTransform), style: null); GUIFrame columnRight = new GUIFrame(new RectTransform(new Vector2(0.3f, 1.0f), paddedFrame.RectTransform), style: null); + leftHUDColumn = columnLeft; + midHUDColumn = columnMid; + rightHUDColumn = columnRight; //---------------------------------------------------------- //left column @@ -180,9 +186,16 @@ namespace Barotrauma.Items.Components ToolTip = TextManager.Get("ReactorTipTurbineOutput") }; - new GUITextBlock(new RectTransform(new Point(0, (int)(20 * GUI.Scale)), columnMid.RectTransform, Anchor.BottomLeft) { AbsoluteOffset = new Point(0, (int)(90 * GUI.Scale)) }, + GUILayoutGroup sliderControls = new GUILayoutGroup(new RectTransform(new Point(columnMid.Rect.Width, (int)(114 * GUI.Scale)), columnMid.RectTransform, Anchor.BottomCenter)) + { + Stretch = true, + AbsoluteSpacing = (int)(5 * GUI.Scale) + }; + sliderControlsContainer = sliderControls; + + new GUITextBlock(new RectTransform(new Point(0, (int)(20 * GUI.Scale)), sliderControls.RectTransform, Anchor.TopLeft), TextManager.Get("ReactorFissionRate")); - fissionRateScrollBar = new GUIScrollBar(new RectTransform(new Point(columnMid.Rect.Width, (int)(30 * GUI.Scale)), columnMid.RectTransform, Anchor.BottomCenter) { AbsoluteOffset = new Point(0, (int)(60 * GUI.Scale)) }, + fissionRateScrollBar = new GUIScrollBar(new RectTransform(new Point(sliderControls.Rect.Width, (int)(30 * GUI.Scale)), sliderControls.RectTransform, Anchor.TopCenter), style: "GUISlider", barSize: 0.1f) { OnMoved = (GUIScrollBar bar, float scrollAmount) => @@ -194,10 +207,10 @@ namespace Barotrauma.Items.Components return false; } }; - - new GUITextBlock(new RectTransform(new Point(0, (int)(20 * GUI.Scale)), columnMid.RectTransform, Anchor.BottomLeft) { AbsoluteOffset = new Point(0, (int)(30 * GUI.Scale)) }, + + new GUITextBlock(new RectTransform(new Point(0, (int)(20 * GUI.Scale)), sliderControls.RectTransform, Anchor.BottomLeft), TextManager.Get("ReactorTurbineOutput")); - turbineOutputScrollBar = new GUIScrollBar(new RectTransform(new Point(columnMid.Rect.Width, (int)(30 * GUI.Scale)), columnMid.RectTransform, Anchor.BottomCenter), + turbineOutputScrollBar = new GUIScrollBar(new RectTransform(new Point(sliderControls.Rect.Width, (int)(30 * GUI.Scale)), sliderControls.RectTransform, Anchor.BottomCenter), style: "GUISlider", barSize: 0.1f, isHorizontal: true) { OnMoved = (GUIScrollBar bar, float scrollAmount) => @@ -263,11 +276,11 @@ namespace Barotrauma.Items.Components "Load", textColor: Color.LightBlue, textAlignment: Alignment.CenterLeft) { ToolTip = TextManager.Get("ReactorTipLoad") - }; + }; string loadStr = TextManager.Get("ReactorLoad"); loadText.TextGetter += () => { return loadStr.Replace("[kw]", ((int)load).ToString()); }; - var outputText = new GUITextBlock(new RectTransform(textSize, graphArea.RectTransform, Anchor.BottomLeft, Pivot.TopLeft), + var outputText = new GUITextBlock(new RectTransform(textSize, graphArea.RectTransform, Anchor.BottomLeft, Pivot.TopLeft), "Output", textColor: Color.LightGreen, textAlignment: Alignment.CenterLeft) { ToolTip = TextManager.Get("ReactorTipPower") @@ -321,9 +334,9 @@ namespace Barotrauma.Items.Components if (temperature > optimalTemperature.Y) { - GUI.DrawRectangle(spriteBatch, - new Vector2(graphArea.X - 30, graphArea.Y), - new Vector2(tempMeterFrame.SourceRect.Width, (graphArea.Bottom - graphArea.Height * optimalTemperature.Y / 100.0f) - graphArea.Y), + GUI.DrawRectangle(spriteBatch, + new Vector2(graphArea.X - 30, graphArea.Y), + new Vector2(tempMeterFrame.SourceRect.Width, (graphArea.Bottom - graphArea.Height * optimalTemperature.Y / 100.0f) - graphArea.Y), Color.Red * (float)Math.Sin(Timing.TotalTime * 5.0f) * 0.7f, isFilled: true); } if (temperature < optimalTemperature.X) @@ -415,15 +428,52 @@ namespace Barotrauma.Items.Components warningButtons["ReactorWarningLowFuel"].Selected = prevAvailableFuel < fissionRate && lightOn; warningButtons["ReactorWarningMeltdown"].Selected = meltDownTimer > MeltdownDelay * 0.5f || item.Condition == 0.0f && lightOn; warningButtons["ReactorWarningSCRAM"].Selected = temperature > 0.1f && onOffSwitch.BarScroll > 0.5f; - + AutoTemp = autoTempSlider.BarScroll < 0.5f; shutDown = onOffSwitch.BarScroll > 0.5f; + if ((sliderControlsContainer.Rect.Contains(PlayerInput.MousePosition) || sliderControlsContainer.Children.Contains(GUIScrollBar.draggingBar)) && + !PlayerInput.KeyDown(InputType.Deselect) && !PlayerInput.KeyHit(InputType.Deselect)) + { + Character.DisableControls = true; + } + if (shutDown) { fissionRateScrollBar.BarScroll = FissionRate / 100.0f; turbineOutputScrollBar.BarScroll = TurbineOutput / 100.0f; - } + } + else if (!autoTemp && Character.DisableControls && GUI.KeyboardDispatcher.Subscriber == null) + { + Vector2 input = Vector2.Zero; + float rate = 50.0f; //percentage per second + + if (PlayerInput.KeyDown(InputType.Left)) input.X += -1.0f; + if (PlayerInput.KeyDown(InputType.Right)) input.X += 1.0f; + if (PlayerInput.KeyDown(InputType.Up)) input.Y += 1.0f; + if (PlayerInput.KeyDown(InputType.Down)) input.Y += -1.0f; + if (PlayerInput.KeyDown(InputType.Run)) rate = 200.0f; + + rate *= deltaTime; + input.X *= rate; + input.Y *= rate; + + if (input.LengthSquared() > 0) + { + LastUser = Character.Controlled; + unsentChanges = true; + if (input.X != 0.0f && GUIScrollBar.draggingBar != fissionRateScrollBar) + { + targetFissionRate = MathHelper.Clamp(targetFissionRate + input.X, 0.0f, 100.0f); + fissionRateScrollBar.BarScroll += input.X / 100.0f; + } + if (input.Y != 0.0f && GUIScrollBar.draggingBar != turbineOutputScrollBar) + { + targetTurbineOutput = MathHelper.Clamp(targetTurbineOutput + input.Y, 0.0f, 100.0f); + turbineOutputScrollBar.BarScroll += input.Y / 100.0f; + } + } + } } private bool ToggleAutoTemp(GUITickBox tickBox) diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Sonar.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Sonar.cs index 4b9fe65a0..105536133 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Sonar.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Sonar.cs @@ -11,12 +11,6 @@ namespace Barotrauma.Items.Components { partial class Sonar : Powered, IServerSerializable, IClientSerializable { - enum Mode - { - Active, - Passive - }; - private bool dynamicDockingIndicator = true; private bool unsentChanges; @@ -43,8 +37,6 @@ namespace Barotrauma.Items.Components private List sonarBlips; - private float prevPingRadius; - private float prevPassivePingRadius; private Vector2 center; @@ -102,7 +94,7 @@ namespace Barotrauma.Items.Components ToolTip = TextManager.Get("SonarTipActive"), OnSelected = (GUITickBox box) => { - IsActive = box.Selected; + CurrentMode = box.Selected ? Mode.Active : Mode.Passive; if (GameMain.Client != null) { unsentChanges = true; @@ -262,7 +254,7 @@ namespace Barotrauma.Items.Components { if (component is GUITextBlock textBlock) { - textBlock.TextColor = IsActive ? textBlock.Style.textColor : textBlock.Style.textColor * 0.5f; + textBlock.TextColor = currentMode == Mode.Active ? textBlock.Style.textColor : textBlock.Style.textColor * 0.5f; } } @@ -281,14 +273,21 @@ namespace Barotrauma.Items.Components if (Level.Loaded != null) { Dictionary levelTriggerFlows = new Dictionary(); - foreach (LevelObject levelObject in Level.Loaded.LevelObjectManager.GetAllObjects(transducerCenter, range * pingState / zoom)) + for (var pingIndex = 0; pingIndex < activePingsCount; ++pingIndex) { - //gather all nearby triggers that are causing the water to flow into the dictionary - foreach (LevelTrigger trigger in levelObject.Triggers) + var activePing = activePings[pingIndex]; + foreach (LevelObject levelObject in Level.Loaded.LevelObjectManager.GetAllObjects(transducerCenter, range * activePing.State / zoom)) { - Vector2 flow = trigger.GetWaterFlowVelocity(); - //ignore ones that are barely doing anything (flow^2 < 1) - if (flow.LengthSquared() > 1.0f) levelTriggerFlows.Add(trigger, flow); + //gather all nearby triggers that are causing the water to flow into the dictionary + foreach (LevelTrigger trigger in levelObject.Triggers) + { + Vector2 flow = trigger.GetWaterFlowVelocity(); + //ignore ones that are barely doing anything (flow^2 < 1) + if (flow.LengthSquared() > 1.0f && !levelTriggerFlows.ContainsKey(trigger)) + { + levelTriggerFlows.Add(trigger, flow); + } + } } } @@ -350,13 +349,18 @@ namespace Barotrauma.Items.Components prevDockingDist = float.MaxValue; } - if (IsActive) + for (var pingIndex = 0; pingIndex < activePingsCount; ++pingIndex) + { + var activePing = activePings[pingIndex]; + float pingRadius = DisplayRadius * activePing.State / zoom; + UpdateDisruptions(transducerCenter, pingRadius / displayScale, activePing.PrevPingRadius / displayScale); + Ping(transducerCenter, transducerCenter, + pingRadius, activePing.PrevPingRadius, displayScale, range / zoom, passive: false, pingStrength: 2.0f); + activePing.PrevPingRadius = pingRadius; + + } + if (currentMode == Mode.Active && currentPingIndex != -1) { - float pingRadius = DisplayRadius * pingState / zoom; - UpdateDisruptions(transducerCenter, pingRadius / displayScale, prevPingRadius / displayScale); - Ping(transducerCenter, transducerCenter, - pingRadius, prevPingRadius, displayScale, range / zoom, passive: false, pingStrength: 2.0f); - prevPingRadius = pingRadius; return; } @@ -396,17 +400,18 @@ namespace Barotrauma.Items.Components screenBackground.Draw(spriteBatch, center, 0.0f, rect.Width / screenBackground.size.X); } - if (IsActive) + if (currentMode == Mode.Active && currentPingIndex != -1) { - if (isLastPingDirectional && directionalPingCircle != null) + var activePing = activePings[currentPingIndex]; + if (activePing.IsDirectional && directionalPingCircle != null) { - directionalPingCircle.Draw(spriteBatch, center, Color.White * (1.0f - pingState), - rotate: MathUtils.VectorToAngle(lastPingDirection), - scale: (DisplayRadius / directionalPingCircle.size.X) * pingState); + directionalPingCircle.Draw(spriteBatch, center, Color.White * (1.0f - activePing.State), + rotate: MathUtils.VectorToAngle(activePing.Direction), + scale: (DisplayRadius / directionalPingCircle.size.X) * activePing.State); } else { - pingCircle.Draw(spriteBatch, center, Color.White * (1.0f - pingState), 0.0f, (DisplayRadius * 2 / pingCircle.size.X) * pingState); + pingCircle.Draw(spriteBatch, center, Color.White * (1.0f - activePing.State), 0.0f, (DisplayRadius * 2 / pingCircle.size.X) * activePing.State); } } @@ -448,7 +453,7 @@ namespace Barotrauma.Items.Components spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend); } - float directionalPingVisibility = useDirectionalPing && IsActive ? 1.0f : showDirectionalIndicatorTimer; + float directionalPingVisibility = useDirectionalPing && currentMode == Mode.Active ? 1.0f : showDirectionalIndicatorTimer; if (directionalPingVisibility > 0.0f) { Vector2 sector1 = MathUtils.RotatePointAroundTarget(pingDirection * DisplayRadius, Vector2.Zero, DirectionalPingSector * 0.5f); @@ -753,24 +758,47 @@ namespace Barotrauma.Items.Components disruptedDirections.Clear(); if (Level.Loaded == null) { return; } - foreach (LevelObject levelObject in Level.Loaded.LevelObjectManager.GetAllObjects(pingSource, range * pingState)) + for (var pingIndex = 0; pingIndex < activePingsCount; ++pingIndex) { - if (levelObject.ActivePrefab?.SonarDisruption <= 0.0f) { continue; } - - float disruptionStrength = levelObject.ActivePrefab.SonarDisruption; - Vector2 disruptionPos = new Vector2(levelObject.Position.X, levelObject.Position.Y); - - float disruptionDist = Vector2.Distance(pingSource, disruptionPos); - disruptedDirections.Add(new Pair((disruptionPos - pingSource) / disruptionDist, disruptionStrength)); - - if (disruptionDist > worldPrevPingRadius && disruptionDist <= worldPingRadius) + var activePing = activePings[pingIndex]; + foreach (LevelObject levelObject in Level.Loaded.LevelObjectManager.GetAllObjects(pingSource, range * activePing.State)) { - for (int i = 0; i < disruptionStrength * Level.GridCellSize * 0.02f; i++) + if (levelObject.ActivePrefab?.SonarDisruption <= 0.0f) { continue; } + + float disruptionStrength = levelObject.ActivePrefab.SonarDisruption; + Vector2 disruptionPos = new Vector2(levelObject.Position.X, levelObject.Position.Y); + + float disruptionDist = Vector2.Distance(pingSource, disruptionPos); + disruptedDirections.Add(new Pair((disruptionPos - pingSource) / disruptionDist, disruptionStrength)); + + if (disruptionDist > worldPrevPingRadius && disruptionDist <= worldPingRadius) { - var blip = new SonarBlip(disruptionPos + Rand.Vector(Rand.Range(0.0f, Level.GridCellSize * 4 * disruptionStrength)), MathHelper.Lerp(1.0f, 1.5f, disruptionStrength), Rand.Range(1.0f, 2.0f + disruptionStrength)); - sonarBlips.Add(blip); + CreateBlipsForDisruption(disruptionPos, disruptionStrength); } } + foreach (AITarget aiTarget in AITarget.List) + { + if (aiTarget.SonarDisruption <= 0.0f || !aiTarget.Enabled) { continue; } + float distSqr = Vector2.DistanceSquared(aiTarget.WorldPosition, pingSource); + if (distSqr > worldPingRadiusSqr) { continue; } + + float disruptionDist = (float)Math.Sqrt(distSqr); + disruptedDirections.Add(new Pair((aiTarget.WorldPosition - pingSource) / disruptionDist, aiTarget.SonarDisruption)); + + if (disruptionDist > worldPrevPingRadius && disruptionDist <= worldPingRadius) + { + CreateBlipsForDisruption(aiTarget.WorldPosition, aiTarget.SonarDisruption); + } + } + } + + void CreateBlipsForDisruption(Vector2 disruptionPos, float disruptionStrength) + { + for (int i = 0; i < disruptionStrength * Level.GridCellSize * 0.02f; i++) + { + var blip = new SonarBlip(disruptionPos + Rand.Vector(Rand.Range(0.0f, Level.GridCellSize * 4 * disruptionStrength)), MathHelper.Lerp(1.0f, 1.5f, disruptionStrength), Rand.Range(1.0f, 2.0f + disruptionStrength)); + sonarBlips.Add(blip); + } } } @@ -1045,9 +1073,9 @@ namespace Barotrauma.Items.Components } Vector2 dir = pos / (float)Math.Sqrt(posDistSqr); - if (isLastPingDirectional) + if (currentPingIndex != -1 && activePings[currentPingIndex].IsDirectional) { - if (Vector2.Dot(lastPingDirection, dir) < DirectionalPingDotProduct) + if (Vector2.Dot(activePings[currentPingIndex].Direction, dir) < DirectionalPingDotProduct) { blip.FadeTimer = 0.0f; return false; @@ -1155,8 +1183,8 @@ namespace Barotrauma.Items.Components public void ClientWrite(Lidgren.Network.NetBuffer msg, object[] extraData = null) { - msg.Write(IsActive); - if (IsActive) + msg.Write(currentMode == Mode.Active); + if (currentMode == Mode.Active) { msg.WriteRangedSingle(zoom, MinZoom, MaxZoom, 8); msg.Write(useDirectionalPing); @@ -1192,8 +1220,8 @@ namespace Barotrauma.Items.Components StartDelayedCorrection(type, msg.ExtractBits(msgLength), sendingTime); return; } - - IsActive = isActive; + + CurrentMode = isActive ? Mode.Active : Mode.Passive; if (isActive) { activeTickBox.Selected = true; diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Steering.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Steering.cs index 0f57fa4da..caec0c63e 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Steering.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Steering.cs @@ -735,9 +735,7 @@ namespace Barotrauma.Items.Components if (sourcePort.Docked || sourcePort.Item.Submarine == null) { continue; } if (sourcePort.Item.Submarine != controlledSub) { continue; } - int sourceDir = sourcePort.IsHorizontal ? - Math.Sign(sourcePort.Item.WorldPosition.X - sourcePort.Item.Submarine.WorldPosition.X) : - Math.Sign(sourcePort.Item.WorldPosition.Y - sourcePort.Item.Submarine.WorldPosition.Y); + int sourceDir = sourcePort.GetDir(); foreach (DockingPort targetPort in DockingPort.List) { @@ -745,9 +743,7 @@ namespace Barotrauma.Items.Components if (targetPort.Item.Submarine == controlledSub || targetPort.IsHorizontal != sourcePort.IsHorizontal) { continue; } if (Level.Loaded != null && targetPort.Item.Submarine.WorldPosition.Y > Level.Loaded.Size.Y) { continue; } - int targetDir = targetPort.IsHorizontal ? - Math.Sign(targetPort.Item.WorldPosition.X - targetPort.Item.Submarine.WorldPosition.X) : - Math.Sign(targetPort.Item.WorldPosition.Y - targetPort.Item.Submarine.WorldPosition.Y); + int targetDir = targetPort.GetDir(); if (sourceDir == targetDir) { continue; } diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Power/Powered.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Power/Powered.cs index c92d0aec9..acf4b27c2 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/Power/Powered.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/Power/Powered.cs @@ -1,19 +1,16 @@ -using System.Collections.Generic; +using Barotrauma.Sounds; +using System.Collections.Generic; using System.Xml.Linq; -using Barotrauma.Sounds; namespace Barotrauma.Items.Components { partial class Powered : ItemComponent { - protected List sparkSounds; - private RoundSound powerOnSound; private bool powerOnSoundPlayed; partial void InitProjectSpecific(XElement element) { - sparkSounds = new List(); foreach (XElement subElement in element.Elements()) { switch (subElement.Name.ToString().ToLowerInvariant()) @@ -21,10 +18,6 @@ namespace Barotrauma.Items.Components case "poweronsound": powerOnSound = Submarine.LoadRoundSound(subElement, false); break; - case "sparksound": - var sparkSound = Submarine.LoadRoundSound(subElement, false); - if (sparkSound != null) { sparkSounds.Add(sparkSound); } - break; } } } diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/RepairTool.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/RepairTool.cs index f8f82df50..8c65373b2 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/RepairTool.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/RepairTool.cs @@ -32,6 +32,8 @@ namespace Barotrauma.Items.Components private List ParticleEmitterHitCharacter = new List(); private List> ParticleEmitterHitItem = new List>(); + private float prevProgressBarState; + partial void InitProjSpecific(XElement element) { foreach (XElement subElement in element.Elements()) @@ -70,7 +72,7 @@ namespace Barotrauma.Items.Components float particleAngle = item.body.Rotation + ((item.body.Dir > 0.0f) ? 0.0f : MathHelper.Pi); ParticleEmitter.Emit( deltaTime, item.WorldPosition + TransformedBarrelPos, - item.CurrentHull, particleAngle, -particleAngle); + item.CurrentHull, particleAngle, ParticleEmitter.Prefab.CopyEntityAngle ? -particleAngle : 0); } } @@ -110,19 +112,22 @@ namespace Barotrauma.Items.Components } } - partial void FixItemProjSpecific(Character user, float deltaTime, Item targetItem, float prevCondition) + partial void FixItemProjSpecific(Character user, float deltaTime, Item targetItem) { - if (prevCondition != targetItem.Condition) + float progressBarState = targetItem.ConditionPercentage / 100.0f; + if (!MathUtils.NearlyEqual(progressBarState, prevProgressBarState)) { Vector2 progressBarPos = targetItem.DrawPosition; var progressBar = user.UpdateHUDProgressBar( targetItem, progressBarPos, - targetItem.Condition / item.MaxCondition, + progressBarState, Color.Red, Color.Green); if (progressBar != null) { progressBar.Size = new Vector2(60.0f, 20.0f); } } + prevProgressBarState = progressBarState; + Vector2 particlePos = ConvertUnits.ToDisplayUnits(pickedPosition); if (targetItem.Submarine != null) particlePos += targetItem.Submarine.DrawPosition; foreach (var emitter in ParticleEmitterHitItem) diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/Connection.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/Connection.cs index ba5580dc7..ba26adbd4 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/Connection.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/Connection.cs @@ -74,10 +74,16 @@ namespace Barotrauma.Items.Components //dropped or dragged from the panel to the players inventory if (draggingConnected != null) { - int linkIndex = c.FindWireIndex(draggingConnected.Item); - if (linkIndex > -1) + //the wire can only be dragged out if it's not connected to anything at the other end + if (Screen.Selected == GameMain.SubEditorScreen || + (draggingConnected.Connections[0] == null && draggingConnected.Connections[1] == null) || + (draggingConnected.Connections.Contains(c) && draggingConnected.Connections.Contains(null))) { - Inventory.draggingItem = c.wires[linkIndex].Item; + int linkIndex = c.FindWireIndex(draggingConnected.Item); + if (linkIndex > -1 || panel.DisconnectedWires.Contains(draggingConnected)) + { + Inventory.draggingItem = draggingConnected.Item; + } } } @@ -109,59 +115,79 @@ namespace Barotrauma.Items.Components if (draggingConnected != null) { - DrawWire(spriteBatch, draggingConnected, draggingConnected.Item, PlayerInput.MousePosition, new Vector2(x + width / 2, y + height - 10), mouseInRect, null, panel, ""); + if (mouseInRect) + { + DrawWire(spriteBatch, draggingConnected, PlayerInput.MousePosition, new Vector2(x + width / 2, y + height - 10), null, panel, ""); + } if (!PlayerInput.LeftButtonHeld()) { - if (GameMain.Client != null) + if (draggingConnected.Connections[0]?.ConnectionPanel == panel || + draggingConnected.Connections[1]?.ConnectionPanel == panel) { - panel.Item.CreateClientEvent(panel); + draggingConnected.RemoveConnection(panel.Item); + panel.DisconnectedWires.Add(draggingConnected); } + if (GameMain.Client != null) { panel.Item.CreateClientEvent(panel); } draggingConnected = null; } } //if the Character using the panel has a wire item equipped //and the wire hasn't been connected yet, draw it on the panel - if (equippedWire != null) + if (equippedWire != null && (draggingConnected != equippedWire || !mouseInRect)) { if (panel.Connections.Find(c => c.Wires.Contains(equippedWire)) == null) { - DrawWire(spriteBatch, equippedWire, equippedWire.Item, - new Vector2(x + width / 2, y + height - 100), - new Vector2(x + width / 2, y + height), mouseInRect, null, panel, ""); + DrawWire(spriteBatch, equippedWire, new Vector2(x + width / 2, y + height - 150 * GUI.Scale), + new Vector2(x + width / 2, y + height), + null, panel, ""); - if (draggingConnected == equippedWire) Inventory.draggingItem = equippedWire.Item; + if (draggingConnected == equippedWire) { Inventory.draggingItem = equippedWire.Item; } } } - + + + float step = (width * 0.75f) / panel.DisconnectedWires.Count(); + x = (int)(x + width / 2 - step * (panel.DisconnectedWires.Count() - 1) / 2); + foreach (Wire wire in panel.DisconnectedWires) + { + if (wire == draggingConnected && mouseInRect) { continue; } + + Connection recipient = wire.OtherConnection(null); + string label = recipient == null ? "" : recipient.item.Name + $" ({recipient.DisplayName})"; + if (wire.Locked) { label += "\n" + TextManager.Get("ConnectionLocked"); } + DrawWire(spriteBatch, wire, new Vector2(x, y + height - 100 * GUI.Scale), + new Vector2(x, y + height), + null, panel, label); + x += (int)step; + } + //stop dragging a wire item if the cursor is within any connection panel //(so we don't drop the item when dropping the wire on a connection) - if (mouseInRect || GUI.MouseOn?.UserData is ConnectionPanel) Inventory.draggingItem = null; + if (mouseInRect || GUI.MouseOn?.UserData is ConnectionPanel) { Inventory.draggingItem = null; } } private void Draw(SpriteBatch spriteBatch, ConnectionPanel panel, Vector2 position, Vector2 labelPos, Vector2 wirePosition, bool mouseIn, Wire equippedWire, float wireInterval) { - //spriteBatch.DrawString(GUI.SmallFont, Name, new Vector2(labelPos.X, labelPos.Y-10), Color.White); GUI.DrawString(spriteBatch, labelPos, DisplayName, IsPower ? Color.Red : Color.White, Color.Black, 0, GUI.SmallFont); - + connectionSprite.Draw(spriteBatch, position); for (int i = 0; i < MaxLinked; i++) { - if (wires[i] == null || wires[i].Hidden || draggingConnected == wires[i]) continue; + if (wires[i] == null || wires[i].Hidden || (draggingConnected == wires[i] && (mouseIn || Screen.Selected == GameMain.SubEditorScreen))) { continue; } Connection recipient = wires[i].OtherConnection(this); - - string label = recipient == null ? "" : - wires[i].Locked ? recipient.item.Name + "\n" + TextManager.Get("ConnectionLocked") : recipient.item.Name; - DrawWire(spriteBatch, wires[i], (recipient == null) ? wires[i].Item : recipient.item, position, wirePosition, mouseIn, equippedWire, panel, label); + string 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); wirePosition.Y += wireInterval; } - if (draggingConnected != null && Vector2.Distance(position, PlayerInput.MousePosition) < 13.0f) + if (draggingConnected != null && Vector2.Distance(position, PlayerInput.MousePosition) < (20.0f * GUI.Scale)) { connectionSpriteHighlight.Draw(spriteBatch, position); @@ -172,15 +198,15 @@ namespace Barotrauma.Items.Components if (index > -1 && !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); } } + if (GameMain.Client != null) { panel.Item.CreateClientEvent(panel); } + draggingConnected = null; } } @@ -215,15 +241,8 @@ namespace Barotrauma.Items.Components flashTimer -= deltaTime; } - private static void DrawWire(SpriteBatch spriteBatch, Wire wire, Item item, Vector2 end, Vector2 start, bool mouseIn, Wire equippedWire, ConnectionPanel panel, string label) + private static void DrawWire(SpriteBatch spriteBatch, Wire wire, Vector2 end, Vector2 start, Wire equippedWire, ConnectionPanel panel, string label) { - if (draggingConnected == wire) - { - if (!mouseIn) return; - end = PlayerInput.MousePosition; - start.X = (start.X + end.X) / 2.0f; - } - int textX = (int)start.X; if (start.X < end.X) textX -= 10; @@ -244,11 +263,19 @@ namespace Barotrauma.Items.Components if (!string.IsNullOrEmpty(label)) { - GUI.DrawString(spriteBatch, - new Vector2(start.X < end.X ? textX - GUI.SmallFont.MeasureString(label).X : textX, start.Y - 5.0f), - label, - (mouseOn ? Color.Gold : Color.White) * (wire.Locked ? 0.6f : 1.0f), Color.Black * 0.8f, - 3, GUI.SmallFont); + if (start.Y > panel.GuiFrame.Rect.Bottom - 1.0f) + { + //wire at the bottom of the panel -> draw the text below the panel, tilted 45 degrees + GUI.SmallFont.DrawString(spriteBatch, label, start + Vector2.UnitY * 20 * GUI.Scale, Color.White, 45.0f, Vector2.Zero, 1.0f, SpriteEffects.None, 0.0f); + } + else + { + GUI.DrawString(spriteBatch, + new Vector2(start.X < end.X ? textX - GUI.SmallFont.MeasureString(label).X : textX, start.Y - 5.0f), + label, + (mouseOn ? Color.Gold : Color.White) * (wire.Locked ? 0.6f : 1.0f), Color.Black * 0.8f, + 3, GUI.SmallFont); + } } var wireEnd = end + Vector2.Normalize(start - end) * 30.0f; @@ -259,14 +286,14 @@ namespace Barotrauma.Items.Components { spriteBatch.Draw(wireVertical.Texture, new Rectangle(wireEnd.ToPoint(), new Point(18, (int)dist)), wireVertical.SourceRect, Color.Gold, - MathUtils.VectorToAngle(end - start) + MathHelper.PiOver2, //angle of line (calulated above) + MathUtils.VectorToAngle(end - start) + MathHelper.PiOver2, new Vector2(6, 0), // point in line about which to rotate SpriteEffects.None, 0.0f); } spriteBatch.Draw(wireVertical.Texture, new Rectangle(wireEnd.ToPoint(), new Point(12, (int)dist)), wireVertical.SourceRect, wire.Item.Color * alpha, - MathUtils.VectorToAngle(end - start) + MathHelper.PiOver2, //angle of line (calulated above) + MathUtils.VectorToAngle(end - start) + MathHelper.PiOver2, new Vector2(6, 0), // point in line about which to rotate SpriteEffects.None, 0.0f); @@ -283,7 +310,7 @@ namespace Barotrauma.Items.Components if (allowRewiring && !wire.Locked && (!panel.Locked || Screen.Selected == GameMain.SubEditorScreen)) { //start dragging the wire - if (PlayerInput.LeftButtonHeld()) draggingConnected = wire; + if (PlayerInput.LeftButtonHeld()) { draggingConnected = wire; } } } } diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/ConnectionPanel.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/ConnectionPanel.cs index 3a0748e73..cefe12ae5 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/ConnectionPanel.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/ConnectionPanel.cs @@ -52,7 +52,7 @@ namespace Barotrauma.Items.Components private void DrawConnections(SpriteBatch spriteBatch, GUICustomComponent container) { - if (user != Character.Controlled || user == null) return; + if (user != Character.Controlled || user == null) { return; } HighlightedWire = null; Connection.DrawConnections(spriteBatch, this, user); @@ -70,8 +70,22 @@ namespace Barotrauma.Items.Components { //delay reading the state until midround syncing is done //because some of the wires connected to the panel may not exist yet - int bitsToRead = Connections.Count * Connection.MaxLinked * 16; - StartDelayedCorrection(type, msg.ExtractBits(bitsToRead), sendingTime, waitForMidRoundSync: true); + long msgStartPos = msg.Position; + foreach (Connection connection in Connections) + { + for (int i = 0; i < Connection.MaxLinked; i++) + { + msg.ReadUInt16(); + } + } + ushort disconnectedWireCount = msg.ReadUInt16(); + for (int i = 0; i < disconnectedWireCount; i++) + { + msg.ReadUInt16(); + } + int msgLength = (int)(msg.Position - msgStartPos); + msg.Position = msgStartPos; + StartDelayedCorrection(type, msg.ExtractBits(msgLength), sendingTime, waitForMidRoundSync: true); } else { @@ -95,11 +109,9 @@ namespace Barotrauma.Items.Components { ushort wireId = msg.ReadUInt16(); - Item wireItem = Entity.FindEntityByID(wireId) as Item; - if (wireItem == null) continue; - + if (!(Entity.FindEntityByID(wireId) is Item wireItem)) { continue; } Wire wireComponent = wireItem.GetComponent(); - if (wireComponent == null) continue; + if (wireComponent == null) { continue; } newWires.Add(wireComponent); @@ -108,18 +120,46 @@ namespace Barotrauma.Items.Components } } + List previousDisconnectedWires = new List(DisconnectedWires); + DisconnectedWires.Clear(); + ushort disconnectedWireCount = msg.ReadUInt16(); + for (int i = 0; i < disconnectedWireCount; i++) + { + ushort wireId = msg.ReadUInt16(); + if (!(Entity.FindEntityByID(wireId) is Item wireItem)) { continue; } + Wire wireComponent = wireItem.GetComponent(); + if (wireComponent == null) { continue; } + DisconnectedWires.Add(wireComponent); + } + foreach (Wire wire in prevWires) { - if (wire.Connections[0] == null && wire.Connections[1] == null) + bool connected = wire.Connections[0] != null || wire.Connections[1] != null; + if (!connected) + { + foreach (Item item in Item.ItemList) + { + var connectionPanel = item.GetComponent(); + if (connectionPanel != null && connectionPanel.DisconnectedWires.Contains(wire)) + { + connected = true; + break; + } + } + } + if (wire.Item.ParentInventory == null && !connected) { wire.Item.Drop(null); } - //wires that are not in anyone's inventory (i.e. not currently being rewired) can never be connected to only one connection - // -> someone must have dropped the wire from the connection panel - else if (wire.Item.ParentInventory == null && - (wire.Connections[0] != null ^ wire.Connections[1] != null)) + } + + foreach (Wire disconnectedWire in previousDisconnectedWires) + { + if (disconnectedWire.Connections[0] == null && + disconnectedWire.Connections[1] == null && + !DisconnectedWires.Contains(disconnectedWire)) { - wire.Item.Drop(null); + disconnectedWire.Item.Drop(dropper: null); } } } diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/Wire.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/Wire.cs index 61d5bcc62..2d1ca769f 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/Wire.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/Wire.cs @@ -2,6 +2,7 @@ using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; +using System; using System.Collections.Generic; using System.Linq; @@ -90,18 +91,26 @@ namespace Barotrauma.Items.Components section.Draw(spriteBatch, item.Color, drawOffset, depth, 0.3f); } - if (IsActive && nodes.Count > 0 && Vector2.Distance(newNodePos, nodes[nodes.Count - 1]) > nodeDistance) + if (nodes.Count > 0) { - WireSection.Draw( - spriteBatch, - new Vector2(nodes[nodes.Count - 1].X, nodes[nodes.Count - 1].Y) + drawOffset, - new Vector2(newNodePos.X, newNodePos.Y) + drawOffset, - item.Color * 0.5f, - depth, - 0.3f); + if (!IsActive) + { + if (connections[0] == null) { DrawHangingWire(spriteBatch, nodes[0] + drawOffset, depth); } + if (connections[1] == null) { DrawHangingWire(spriteBatch, nodes.Last() + drawOffset, depth); } + } + if (IsActive && Vector2.Distance(newNodePos, nodes[nodes.Count - 1]) > nodeDistance) + { + WireSection.Draw( + spriteBatch, + new Vector2(nodes[nodes.Count - 1].X, nodes[nodes.Count - 1].Y) + drawOffset, + new Vector2(newNodePos.X, newNodePos.Y) + drawOffset, + item.Color * 0.5f, + depth, + 0.3f); + } } - if (!editing || !GameMain.SubEditorScreen.WiringMode) return; + if (!editing || !GameMain.SubEditorScreen.WiringMode) { return; } for (int i = 0; i < nodes.Count; i++) { @@ -126,6 +135,23 @@ namespace Barotrauma.Items.Components } } + private void DrawHangingWire(SpriteBatch spriteBatch, Vector2 start, float depth) + { + float angle = (float)Math.Sin(GameMain.GameScreen.GameTime * 2.0f + item.ID) * 0.2f; + Vector2 endPos = start + new Vector2((float)Math.Sin(angle), -(float)Math.Cos(angle)) * 50.0f; + + WireSection.Draw( + spriteBatch, + start, endPos, + Color.Orange, depth + 0.00001f, 0.2f); + + WireSection.Draw( + spriteBatch, + start, start + (endPos - start) * 0.7f, + item.Color, depth, 0.3f); + } + + public static void UpdateEditing(List wires) { //dragging a node of some wire diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Turret.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Turret.cs index 03adc3207..8795b1dad 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/Turret.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/Turret.cs @@ -25,6 +25,9 @@ namespace Barotrauma.Items.Components private SoundChannel moveSoundChannel; private Vector2 crosshairPos, crosshairPointerPos; + + private Dictionary widgets = new Dictionary(); + private float prevAngle; private bool flashLowPower; private bool flashNoAmmo; @@ -117,6 +120,25 @@ namespace Barotrauma.Items.Components barSize: 0.0f); } + private void InitializeRotationLimitWidget(Widget widget) + { + widget.Hovered += () => + { + widget.secondaryColor = Color.Green; + }; + widget.Selected += () => + { + widget.color = Color.Green; + }; + widget.MouseHeld += (deltaTime) => + { + widget.DrawPos = PlayerInput.MousePosition; + }; + widget.Deselected += () => + { + widget.color = Color.Red; + }; + } partial void LaunchProjSpecific() { @@ -246,24 +268,153 @@ namespace Barotrauma.Items.Components item.SpriteColor, rotation + MathHelper.PiOver2, item.Scale, SpriteEffects.None, item.SpriteDepth + (barrelSprite.Depth - item.Sprite.Depth)); - - - if (!editing) return; + + if (!editing) { return; } + + float widgetRadius = 60.0f; GUI.DrawLine(spriteBatch, drawPos, - drawPos + new Vector2((float)Math.Cos(minRotation), (float)Math.Sin(minRotation)) * 60.0f, + drawPos + new Vector2((float)Math.Cos(minRotation), (float)Math.Sin(minRotation)) * widgetRadius, Color.Green); GUI.DrawLine(spriteBatch, drawPos, - drawPos + new Vector2((float)Math.Cos(maxRotation), (float)Math.Sin(maxRotation)) * 60.0f, + drawPos + new Vector2((float)Math.Cos(maxRotation), (float)Math.Sin(maxRotation)) * widgetRadius, Color.Green); GUI.DrawLine(spriteBatch, drawPos, - drawPos + new Vector2((float)Math.Cos((maxRotation + minRotation) / 2), (float)Math.Sin((maxRotation + minRotation) / 2)) * 60.0f, + drawPos + new Vector2((float)Math.Cos((maxRotation + minRotation) / 2), (float)Math.Sin((maxRotation + minRotation) / 2)) * widgetRadius, Color.LightGreen); + + if (!item.IsSelected) { return; } + + Widget minRotationWidget = GetWidget("minrotation", spriteBatch, size: 10, initMethod: (widget) => + { + widget.MouseDown += () => + { + widget.color = Color.Green; + prevAngle = minRotation; + }; + widget.Deselected += () => + { + widget.color = Color.Yellow; + item.CreateEditingHUD(); + }; + widget.MouseHeld += (deltaTime) => + { + minRotation = GetRotationAngle(drawPos); + if (minRotation > maxRotation) + { + float temp = minRotation; + minRotation = maxRotation; + maxRotation = temp; + } + MapEntity.DisableSelect = true; + }; + widget.PreUpdate += (deltaTime) => + { + widget.DrawPos = new Vector2(widget.DrawPos.X, -widget.DrawPos.Y); + widget.DrawPos = Screen.Selected.Cam.WorldToScreen(widget.DrawPos); + }; + widget.PostUpdate += (deltaTime) => + { + widget.DrawPos = Screen.Selected.Cam.ScreenToWorld(widget.DrawPos); + widget.DrawPos = new Vector2(widget.DrawPos.X, -widget.DrawPos.Y); + }; + widget.PreDraw += (sprtBtch, deltaTime) => + { + widget.tooltip = "Min: " + (int)MathHelper.ToDegrees(minRotation); + widget.DrawPos = drawPos + new Vector2((float)Math.Cos(minRotation), (float)Math.Sin(minRotation)) * widgetRadius; + widget.Update(deltaTime); + }; + }); + + Widget maxRotationWidget = GetWidget("maxrotation", spriteBatch, size: 10, initMethod: (widget) => + { + widget.MouseDown += () => + { + widget.color = Color.Green; + prevAngle = minRotation; + }; + widget.Deselected += () => + { + widget.color = Color.Yellow; + item.CreateEditingHUD(); + }; + widget.MouseHeld += (deltaTime) => + { + maxRotation = GetRotationAngle(drawPos); + if (minRotation > maxRotation) + { + float temp = minRotation; + minRotation = maxRotation; + maxRotation = temp; + } + MapEntity.DisableSelect = true; + }; + widget.PreUpdate += (deltaTime) => + { + widget.DrawPos = new Vector2(widget.DrawPos.X, -widget.DrawPos.Y); + widget.DrawPos = Screen.Selected.Cam.WorldToScreen(widget.DrawPos); + }; + widget.PostUpdate += (deltaTime) => + { + widget.DrawPos = Screen.Selected.Cam.ScreenToWorld(widget.DrawPos); + widget.DrawPos = new Vector2(widget.DrawPos.X, -widget.DrawPos.Y); + }; + widget.PreDraw += (sprtBtch, deltaTime) => + { + widget.tooltip = "Max: " + (int)MathHelper.ToDegrees(maxRotation); + widget.DrawPos = drawPos + new Vector2((float)Math.Cos(maxRotation), (float)Math.Sin(maxRotation)) * widgetRadius; + widget.Update(deltaTime); + }; + }); + minRotationWidget.Draw(spriteBatch, (float)Timing.Step); + maxRotationWidget.Draw(spriteBatch, (float)Timing.Step); + } + + private Widget GetWidget(string id, SpriteBatch spriteBatch, int size = 5, Action initMethod = null) + { + if (!widgets.TryGetValue(id, out Widget widget)) + { + widget = new Widget(id, size, Widget.Shape.Rectangle) + { + color = Color.Yellow, + tooltipOffset = new Vector2(size / 2 + 5, -10), + inputAreaMargin = 20, + RequireMouseOn = false + }; + widgets.Add(id, widget); + initMethod?.Invoke(widget); + } + return widget; + } + + /// + /// Returns correct angle between -2PI and +2PI + /// + /// + /// + private float GetRotationAngle(Vector2 drawPosition) + { + Vector2 mouseVector = Screen.Selected.Cam.ScreenToWorld(PlayerInput.MousePosition); + mouseVector.Y = -mouseVector.Y; + Vector2 rotationVector = mouseVector - drawPosition; + rotationVector.Normalize(); + double angle = Math.Atan2(MathHelper.ToRadians(rotationVector.Y), MathHelper.ToRadians(rotationVector.X)); + if (angle < 0) + {// calculates which coterminal angle is closer to previous angle + angle = Math.Abs(angle - prevAngle) < Math.Abs((angle + Math.PI * 2) - prevAngle) ? angle : angle + Math.PI * 2; + } + else if (angle > 0) + { + angle = Math.Abs(angle - prevAngle) < Math.Abs((angle - Math.PI * 2) - prevAngle) ? angle : angle - Math.PI * 2; + } + angle = MathHelper.Clamp((float)angle, -((float)Math.PI * 2), (float)Math.PI * 2); + prevAngle = (float)angle; + return (float)angle; } public override void DrawHUD(SpriteBatch spriteBatch, Character character) diff --git a/Barotrauma/BarotraumaClient/Source/Items/Item.cs b/Barotrauma/BarotraumaClient/Source/Items/Item.cs index ba6bc9a40..9e7d9461f 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Item.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Item.cs @@ -329,6 +329,10 @@ namespace Barotrauma item.AiTarget?.Draw(spriteBatch); } } + if (body != null) + { + body.DebugDraw(spriteBatch, Color.White); + } } if (!editing || (body != null && !body.Enabled)) @@ -880,7 +884,6 @@ namespace Barotrauma } } break; - case NetEntityEvent.Type.InventoryState: { int containerIndex = msg.ReadRangedInteger(0, components.Count - 1); @@ -969,6 +972,10 @@ namespace Barotrauma case NetEntityEvent.Type.ChangeProperty: WritePropertyChange(msg, extraData, true); break; + case NetEntityEvent.Type.Combine: + UInt16 combineTargetID = (UInt16)extraData[1]; + msg.Write(combineTargetID); + break; } msg.WritePadBits(); } diff --git a/Barotrauma/BarotraumaClient/Source/Map/Explosion.cs b/Barotrauma/BarotraumaClient/Source/Map/Explosion.cs index cf6fc9973..6e2791151 100644 --- a/Barotrauma/BarotraumaClient/Source/Map/Explosion.cs +++ b/Barotrauma/BarotraumaClient/Source/Map/Explosion.cs @@ -90,6 +90,15 @@ namespace Barotrauma } } + private Vector2 ClampParticlePos(Vector2 particlePos, Hull hull) + { + if (hull == null) return particlePos; + + return new Vector2( + MathHelper.Clamp(particlePos.X, hull.WorldRect.X, hull.WorldRect.Right), + MathHelper.Clamp(particlePos.Y, hull.WorldRect.Y - hull.WorldRect.Height, hull.WorldRect.Y)); + } + private IEnumerable DimLight(LightSource light) { float currBrightness = 1.0f; diff --git a/Barotrauma/BarotraumaClient/Source/Map/Lights/LightManager.cs b/Barotrauma/BarotraumaClient/Source/Map/Lights/LightManager.cs index f8a60c153..da2bc00a6 100644 --- a/Barotrauma/BarotraumaClient/Source/Map/Lights/LightManager.cs +++ b/Barotrauma/BarotraumaClient/Source/Map/Lights/LightManager.cs @@ -260,20 +260,19 @@ namespace Barotrauma.Lights (int)(GameMain.GraphicsWidth * currLightMapScale), (int)(GameMain.GraphicsHeight * currLightMapScale)), Color.Black); spriteBatch.End(); } - else + + visibleHulls = GetVisibleHulls(cam); + spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Opaque, transformMatrix: spriteBatchTransform); + foreach (Rectangle drawRect in visibleHulls.Values) { - visibleHulls = GetVisibleHulls(cam); - spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Opaque, transformMatrix: spriteBatchTransform); - foreach (Rectangle drawRect in visibleHulls.Values) - { - //TODO: draw some sort of smoothed rectangle - GUI.DrawRectangle(spriteBatch, - new Vector2(drawRect.X, -drawRect.Y), - new Vector2(drawRect.Width, drawRect.Height), - Color.Black, true); - } - spriteBatch.End(); - } + //TODO: draw some sort of smoothed rectangle + GUI.DrawRectangle(spriteBatch, + new Vector2(drawRect.X, -drawRect.Y), + new Vector2(drawRect.Width, drawRect.Height), + Color.Black, true); + } + spriteBatch.End(); + graphics.BlendState = BlendState.Additive; } diff --git a/Barotrauma/BarotraumaClient/Source/Map/MapEntity.cs b/Barotrauma/BarotraumaClient/Source/Map/MapEntity.cs index f5da6ece8..2d5e84ddb 100644 --- a/Barotrauma/BarotraumaClient/Source/Map/MapEntity.cs +++ b/Barotrauma/BarotraumaClient/Source/Map/MapEntity.cs @@ -382,8 +382,28 @@ namespace Barotrauma } else { - selectedList.Clear(); - newSelection.ForEach(e => AddSelection(e)); + selectedList = newSelection; + //selectedList.Clear(); + //newSelection.ForEach(e => AddSelection(e)); + foreach (var entity in newSelection) + { + HandleDoorGapLinks(entity, + onGapFound: (door, gap) => + { + door.RefreshLinkedGap(); + if (!selectedList.Contains(gap)) + { + selectedList.Add(gap); + } + }, + onDoorFound: (door, gap) => + { + if (!selectedList.Contains(door.Item)) + { + selectedList.Add(door.Item); + } + }); + } } //select wire if both items it's connected to are selected diff --git a/Barotrauma/BarotraumaClient/Source/Map/Structure.cs b/Barotrauma/BarotraumaClient/Source/Map/Structure.cs index 726ddd2dc..a8bca105a 100644 --- a/Barotrauma/BarotraumaClient/Source/Map/Structure.cs +++ b/Barotrauma/BarotraumaClient/Source/Map/Structure.cs @@ -12,11 +12,6 @@ using System.Collections.Generic; namespace Barotrauma { - partial class WallSection - { - public ConvexHull hull; - } - partial class Structure : MapEntity, IDamageable, IServerSerializable { public static bool ShowWalls = true, ShowStructures = true; diff --git a/Barotrauma/BarotraumaClient/Source/Networking/FileTransfer/FileReceiver.cs b/Barotrauma/BarotraumaClient/Source/Networking/FileTransfer/FileReceiver.cs index 5fa809bd9..d1f3919ab 100644 --- a/Barotrauma/BarotraumaClient/Source/Networking/FileTransfer/FileReceiver.cs +++ b/Barotrauma/BarotraumaClient/Source/Networking/FileTransfer/FileReceiver.cs @@ -204,8 +204,7 @@ namespace Barotrauma.Networking ulong fileSize = inc.ReadUInt64(); string fileName = inc.ReadString(); - string errorMsg; - if (!ValidateInitialData(fileType, fileName, fileSize, out errorMsg)) + if (!ValidateInitialData(fileType, fileName, fileSize, out string errorMsg)) { GameMain.Client.CancelFileTransfer(inc.SequenceChannel); DebugConsole.ThrowError("File transfer failed (" + errorMsg + ")"); diff --git a/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs b/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs index 91366725a..d5e989882 100644 --- a/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs +++ b/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs @@ -32,6 +32,8 @@ namespace Barotrauma.Networking protected GUITickBox cameraFollowsSub; + public RoundEndCinematic EndCinematic; + private ClientPermissions permissions = ClientPermissions.None; private List permittedConsoleCommands = new List(); @@ -209,7 +211,7 @@ namespace Barotrauma.Networking otherClients = new List(); - serverSettings = new ServerSettings("Server", 0, 0, 0, false, false); + serverSettings = new ServerSettings(this, "Server", 0, 0, 0, false, false); ConnectToServer(ip, serverName); @@ -800,7 +802,7 @@ namespace Barotrauma.Networking GameMain.GameSession.WinningTeam = winningTeam; GameMain.GameSession.Mission.Completed = true; } - CoroutineManager.StartCoroutine(EndGame(endMessage)); + CoroutineManager.StartCoroutine(EndGame(endMessage), "EndGame"); break; case ServerPacketHeader.CAMPAIGN_SETUP_INFO: UInt16 saveCount = inc.ReadUInt16(); @@ -1059,6 +1061,12 @@ namespace Barotrauma.Networking if (Character != null) Character.Remove(); HasSpawned = false; + while (CoroutineManager.IsCoroutineRunning("EndGame")) + { + if (EndCinematic != null) { EndCinematic.Stop(); } + yield return CoroutineStatus.Running; + } + GameMain.LightManager.LightingEnabled = true; //enable spectate button in case we fail to start the round now @@ -1192,6 +1200,15 @@ namespace Barotrauma.Networking if (GameMain.GameSession != null) { GameMain.GameSession.GameMode.End(endMessage); } + // Enable characters near the main sub for the endCinematic + foreach (Character c in Character.CharacterList) + { + if (Vector2.DistanceSquared(Submarine.MainSub.WorldPosition, c.WorldPosition) < NetConfig.EnableCharacterDistSqr) + { + c.Enabled = true; + } + } + ServerSettings.ServerDetailsChanged = true; gameStarted = false; @@ -1199,17 +1216,15 @@ namespace Barotrauma.Networking GameMain.GameScreen.Cam.TargetPos = Vector2.Zero; GameMain.LightManager.LosEnabled = false; respawnManager = null; - - float endPreviewLength = 10.0f; + if (Screen.Selected == GameMain.GameScreen) { - new RoundEndCinematic(Submarine.MainSub, GameMain.GameScreen.Cam, endPreviewLength); - float secondsLeft = endPreviewLength; - do + EndCinematic = new RoundEndCinematic(Submarine.MainSub, GameMain.GameScreen.Cam); + while (EndCinematic.Running && Screen.Selected == GameMain.GameScreen) { - secondsLeft -= CoroutineManager.UnscaledDeltaTime; yield return CoroutineStatus.Running; - } while (secondsLeft > 0.0f && Screen.Selected == GameMain.GameScreen); + } + EndCinematic = null; } Submarine.Unload(); @@ -1628,6 +1643,7 @@ namespace Barotrauma.Networking outmsg.Write(LastClientListUpdateID); Character.Controlled?.ClientWrite(outmsg); + GameMain.GameScreen.Cam?.ClientWrite(outmsg); entityEventManager.Write(outmsg, client.ServerConnection); @@ -1829,9 +1845,10 @@ namespace Barotrauma.Networking steamAuthTicket?.Cancel(); steamAuthTicket = null; - foreach (var fileTransfer in FileReceiver.ActiveTransfers) + List activeTransfers = new List(FileReceiver.ActiveTransfers); + foreach (var fileTransfer in activeTransfers) { - fileTransfer.Dispose(); + FileReceiver.StopTransfer(fileTransfer, deleteFile: true); } if (HasPermission(ClientPermissions.ServerLog)) @@ -1842,7 +1859,8 @@ namespace Barotrauma.Networking if (GameMain.ServerChildProcess != null) { int checks = 0; - while (!GameMain.ServerChildProcess.HasExited) { + while (!GameMain.ServerChildProcess.HasExited) + { if (checks > 10) { GameMain.ServerChildProcess.Kill(); @@ -2329,24 +2347,34 @@ namespace Barotrauma.Networking if (respawnManager != null) { - string respawnInfo = ""; + string respawnText = ""; + float textScale = 1.0f; + Color textColor = Color.White; if (respawnManager.CurrentState == RespawnManager.State.Waiting && - respawnManager.CountdownStarted) + respawnManager.RespawnCountdownStarted) { - respawnInfo = TextManager.GetWithVariable(respawnManager.UsingShuttle ? "RespawnShuttleDispatching" : "RespawningIn", "[time]", ToolBox.SecondsToReadableTime(respawnManager.RespawnTimer)); + float timeLeft = (float)(respawnManager.RespawnTime - DateTime.Now).TotalSeconds; + respawnText = TextManager.GetWithVariable(respawnManager.UsingShuttle ? "RespawnShuttleDispatching" : "RespawningIn", "[time]", ToolBox.SecondsToReadableTime(timeLeft)); } - else if (respawnManager.CurrentState == RespawnManager.State.Transporting) + else if (respawnManager.CurrentState == RespawnManager.State.Transporting && + respawnManager.ReturnCountdownStarted) { - respawnInfo = respawnManager.TransportTimer <= 0.0f ? + float timeLeft = (float)(respawnManager.ReturnTime - DateTime.Now).TotalSeconds; + respawnText = timeLeft <= 0.0f ? "" : - TextManager.GetWithVariable("RespawnShuttleLeavingIn", "[time]", ToolBox.SecondsToReadableTime(respawnManager.TransportTimer)); + TextManager.GetWithVariable("RespawnShuttleLeavingIn", "[time]", ToolBox.SecondsToReadableTime(timeLeft)); + if (timeLeft < 20.0f) + { + //oscillate between 0-1 + float phase = (float)(Math.Sin(timeLeft * MathHelper.Pi) + 1.0f) * 0.5f; + textScale = 1.0f + phase * 0.5f; + textColor = Color.Lerp(Color.Red, Color.White, 1.0f - phase); + } } - - if (respawnManager != null) + + if (!string.IsNullOrEmpty(respawnText)) { - GUI.DrawString(spriteBatch, - new Vector2(120.0f, 10), - respawnInfo, Color.White, null, 0, GUI.SmallFont); + GUI.SmallFont.DrawString(spriteBatch, respawnText, new Vector2(120.0f, 10), textColor, 0.0f, Vector2.Zero, textScale, Microsoft.Xna.Framework.Graphics.SpriteEffects.None, 0.0f); } } diff --git a/Barotrauma/BarotraumaClient/Source/Networking/KarmaManager.cs b/Barotrauma/BarotraumaClient/Source/Networking/KarmaManager.cs new file mode 100644 index 000000000..8b3bb1dad --- /dev/null +++ b/Barotrauma/BarotraumaClient/Source/Networking/KarmaManager.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.Xna.Framework; + +namespace Barotrauma +{ + partial class KarmaManager : ISerializableEntity + { + public void CreateSettingsFrame(GUIComponent parent) + { + CreateLabeledSlider(parent, 0.0f, 40.0f, 1.0f, "KickBanThreshold"); + CreateLabeledSlider(parent, 0.0f, 50.0f, 1.0f, "HerpesThreshold"); + + CreateLabeledSlider(parent, 0.0f, 0.5f, 0.01f, "KarmaDecay"); + CreateLabeledSlider(parent, 50.0f, 100.0f, 1.0f, "KarmaDecayThreshold"); + CreateLabeledSlider(parent, 0.0f, 0.5f, 0.01f, "KarmaIncrease"); + CreateLabeledSlider(parent, 0.0f, 50.0f, 1.0f, "KarmaIncreaseThreshold"); + + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.12f), parent.RectTransform), TextManager.Get("Karma.PositiveActions"), textAlignment: Alignment.Center) + { + CanBeFocused = false + }; + + CreateLabeledSlider(parent, 0.0f, 1.0f, 0.01f, "StructureRepairKarmaIncrease"); + CreateLabeledSlider(parent, 0.0f, 1.0f, 0.01f, "HealFriendlyKarmaIncrease"); + CreateLabeledSlider(parent, 0.0f, 1.0f, 0.01f, "DamageEnemyKarmaIncrease"); + CreateLabeledSlider(parent, 0.0f, 1.0f, 0.01f, "ItemRepairKarmaIncrease"); + CreateLabeledSlider(parent, 0.0f, 10.0f, 0.05f, "ExtinguishFireKarmaIncrease"); + + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.12f), parent.RectTransform), TextManager.Get("Karma.NegativeActions"), textAlignment: Alignment.Center) + { + CanBeFocused = false + }; + + CreateLabeledSlider(parent, 0.0f, 1.0f, 0.01f, "StructureDamageKarmaDecrease"); + CreateLabeledSlider(parent, 0.0f, 1.0f, 0.01f, "DamageFriendlyKarmaDecrease"); + CreateLabeledSlider(parent, 0.0f, 100.0f, 1.0f, "ReactorMeltdownKarmaDecrease"); + CreateLabeledSlider(parent, 0.0f, 10.0f, 0.05f, "ReactorOverheatKarmaDecrease"); + CreateLabeledSlider(parent, 0.0f, 20.0f, 1f, "AllowedWireDisconnectionsPerMinute"); + CreateLabeledSlider(parent, 0.0f, 20.0f, 0.5f, "WireDisconnectionKarmaDecrease"); + CreateLabeledSlider(parent, 0.0f, 30.0f, 1.0f, "SpamFilterKarmaDecrease"); + } + + private void CreateLabeledSlider(GUIComponent parent, float min, float max, float step, string propertyName) + { + var container = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.1f), parent.RectTransform), isHorizontal: true) + { + Stretch = true, + RelativeSpacing = 0.05f, + ToolTip = TextManager.Get("Karma." + propertyName + "ToolTip") + }; + + string labelText = TextManager.Get("Karma." + propertyName); + var label = new GUITextBlock(new RectTransform(new Vector2(0.7f, 0.8f), container.RectTransform), + labelText, font: GUI.SmallFont) + { + ToolTip = TextManager.Get("Karma." + propertyName + "ToolTip") + }; + + var slider = new GUIScrollBar(new RectTransform(new Vector2(0.3f, 0.8f), container.RectTransform), barSize: 0.1f) + { + Step = step <= 0.0f ? 0.0f : step / (max - min), + Range = new Vector2(min, max), + OnMoved = (GUIScrollBar scrollBar, float barScroll) => + { + string formattedValueStr = step >= 1.0f ? + ((int)scrollBar.BarScrollValue).ToString() : + scrollBar.BarScrollValue.Format(decimalCount: step <= 0.1f ? 2 : 1); + label.Text = TextManager.AddPunctuation(':', labelText, formattedValueStr); + return true; + } + }; + GameMain.NetworkMember.ServerSettings.AssignGUIComponent(propertyName, slider); + slider.OnMoved(slider, slider.BarScroll); + } + } +} diff --git a/Barotrauma/BarotraumaClient/Source/Networking/RespawnManager.cs b/Barotrauma/BarotraumaClient/Source/Networking/RespawnManager.cs index dab9f7392..06d4fd199 100644 --- a/Barotrauma/BarotraumaClient/Source/Networking/RespawnManager.cs +++ b/Barotrauma/BarotraumaClient/Source/Networking/RespawnManager.cs @@ -1,25 +1,59 @@ -using System; +using Lidgren.Network; +using Microsoft.Xna.Framework; +using System; namespace Barotrauma.Networking { partial class RespawnManager { - partial void UpdateWaiting(float deltaTime) - { - if (CountdownStarted) - { - respawnTimer = Math.Max(0.0f, respawnTimer - deltaTime); - } - } + private DateTime lastShuttleLeavingWarningTime; partial void UpdateTransportingProjSpecific(float deltaTime) { - if (shuttleTransportTimer + deltaTime > 15.0f && shuttleTransportTimer <= 15.0f && - GameMain.Client?.Character != null && - GameMain.Client.Character.Submarine == respawnShuttle) + if (GameMain.Client?.Character == null || GameMain.Client.Character.Submarine != RespawnShuttle) { return; } + if (!ReturnCountdownStarted) { return; } + + //show a warning when there's 20 seconds until the shuttle leaves + if ((ReturnTime - DateTime.Now).TotalSeconds < 20.0f && + (DateTime.Now - lastShuttleLeavingWarningTime).TotalSeconds > 30.0f) { + lastShuttleLeavingWarningTime = DateTime.Now; GameMain.Client.AddChatMessage("ServerMessage.ShuttleLeaving", ChatMessageType.Server); } } + + public void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime) + { + var newState = (State)msg.ReadRangedInteger(0, Enum.GetNames(typeof(State)).Length); + + switch (newState) + { + case State.Transporting: + ReturnCountdownStarted = msg.ReadBoolean(); + maxTransportTime = msg.ReadSingle(); + float transportTimeLeft = msg.ReadSingle(); + + ReturnTime = DateTime.Now + new TimeSpan(0, 0, 0, 0, milliseconds: (int)(transportTimeLeft * 1000.0f)); + RespawnCountdownStarted = false; + if (CurrentState != newState) + { + CoroutineManager.StopCoroutines("forcepos"); + //CoroutineManager.StartCoroutine(ForceShuttleToPos(Level.Loaded.StartPosition - Vector2.UnitY * Level.ShaftHeight, 100.0f), "forcepos"); + } + break; + case State.Waiting: + RespawnCountdownStarted = msg.ReadBoolean(); + ResetShuttle(); + float newRespawnTime = msg.ReadSingle(); + RespawnTime = DateTime.Now + new TimeSpan(0, 0, 0, 0, milliseconds: (int)(newRespawnTime * 1000.0f)); + break; + case State.Returning: + RespawnCountdownStarted = false; + break; + } + CurrentState = newState; + + msg.ReadPadBits(); + } } } diff --git a/Barotrauma/BarotraumaClient/Source/Networking/ServerInfo.cs b/Barotrauma/BarotraumaClient/Source/Networking/ServerInfo.cs index ec1f695e5..fa1b4510d 100644 --- a/Barotrauma/BarotraumaClient/Source/Networking/ServerInfo.cs +++ b/Barotrauma/BarotraumaClient/Source/Networking/ServerInfo.cs @@ -51,7 +51,22 @@ namespace Barotrauma.Networking public bool ContentPackagesMatch(IEnumerable myContentPackages) { - return ContentPackagesMatch(myContentPackages.Select(cp => cp.MD5hash.Hash)); + //make sure we have all the packages the server requires + foreach (string hash in ContentPackageHashes) + { + if (!myContentPackages.Any(myPackage => myPackage.MD5hash.Hash == hash)) { return false; } + } + + //make sure the server isn't missing any of our packages that cause multiplayer incompatibility + foreach (ContentPackage myPackage in myContentPackages) + { + if (myPackage.HasMultiplayerIncompatibleContent) + { + if (!ContentPackageHashes.Any(hash => hash == myPackage.MD5hash.Hash)) { return false; } + } + } + + return true; } public bool ContentPackagesMatch(IEnumerable myContentPackageHashes) diff --git a/Barotrauma/BarotraumaClient/Source/Networking/ServerSettings.cs b/Barotrauma/BarotraumaClient/Source/Networking/ServerSettings.cs index 2af9e3428..2040864b4 100644 --- a/Barotrauma/BarotraumaClient/Source/Networking/ServerSettings.cs +++ b/Barotrauma/BarotraumaClient/Source/Networking/ServerSettings.cs @@ -17,7 +17,7 @@ namespace Barotrauma.Networking public void AssignGUIComponent(GUIComponent component) { GUIComponent = component; - GUIComponentValue = property.GetValue(serverSettings); + GUIComponentValue = property.GetValue(parentObject); TempValue = GUIComponentValue; } @@ -30,6 +30,7 @@ namespace Barotrauma.Networking else if (GUIComponent is GUITextBox textBox) return textBox.Text; else if (GUIComponent is GUIScrollBar scrollBar) return scrollBar.BarScrollValue; else if (GUIComponent is GUIRadioButtonGroup radioButtonGroup) return radioButtonGroup.Selected; + else if (GUIComponent is GUIDropDown dropdown) return dropdown.SelectedData; return null; } set @@ -39,6 +40,7 @@ namespace Barotrauma.Networking else if (GUIComponent is GUITextBox textBox) textBox.Text = (string)value; else if (GUIComponent is GUIScrollBar scrollBar) scrollBar.BarScrollValue = (float)value; else if (GUIComponent is GUIRadioButtonGroup radioButtonGroup) radioButtonGroup.Selected = (Enum)value; + else if (GUIComponent is GUIDropDown dropdown) dropdown.SelectItem(value); } } @@ -50,31 +52,6 @@ namespace Barotrauma.Networking return !PropEquals(TempValue, GUIComponentValue); } } - - public bool PropEquals(object a,object b) - { - switch (typeString) - { - case "float": - if (!(a is float?)) return false; - if (!(b is float?)) return false; - return (float)a == (float)b; - case "int": - if (!(a is int?)) return false; - if (!(b is int?)) return false; - return (int)a == (int)b; - case "bool": - if (!(a is bool?)) return false; - if (!(b is bool?)) return false; - return (bool)a == (bool)b; - case "Enum": - if (!(a is Enum)) return false; - if (!(b is Enum)) return false; - return ((Enum)a).Equals((Enum)b); - default: - return a.ToString().Equals(b.ToString(),StringComparison.InvariantCulture); - } - } } private Dictionary tempMonsterEnabled; @@ -222,11 +199,15 @@ namespace Barotrauma.Networking private GUIFrame[] settingsTabs; private GUIButton[] tabButtons; private int settingsTabIndex; + + private GUIDropDown karmaPresetDD; + private GUIComponent karmaSettingsBlocker; enum SettingsTab { + General, Rounds, - Server, + Antigriefing, Banlist, Whitelist } @@ -236,6 +217,11 @@ namespace Barotrauma.Networking return netProperties.First(p => p.Value.Name == name).Value; } + public void AssignGUIComponent(string propertyName, GUIComponent component) + { + GetPropertyData(propertyName).AssignGUIComponent(component); + } + public void AddToGUIUpdateList() { if (GUI.DisableHUD) return; @@ -264,7 +250,7 @@ namespace Barotrauma.Networking }; //center frames - GUIFrame innerFrame = new GUIFrame(new RectTransform(new Vector2(0.3f, 0.7f), settingsFrame.RectTransform, Anchor.Center) { MinSize = new Point(400, 430) }); + GUIFrame innerFrame = new GUIFrame(new RectTransform(new Vector2(0.4f, 0.75f), settingsFrame.RectTransform, Anchor.Center) { MinSize = new Point(400, 430) }); GUIFrame paddedFrame = new GUIFrame(new RectTransform(new Vector2(0.9f, 0.9f), innerFrame.RectTransform, Anchor.Center), style: null); new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), paddedFrame.RectTransform), TextManager.Get("Settings"), font: GUI.LargeFont); @@ -304,18 +290,21 @@ namespace Barotrauma.Networking OnClicked = ToggleSettingsFrame }; + //-------------------------------------------------------------------------------- - // game settings + // server settings //-------------------------------------------------------------------------------- - var roundsTab = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.95f), settingsTabs[(int)SettingsTab.Rounds].RectTransform, Anchor.Center)) + var serverTab = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.95f), settingsTabs[(int)SettingsTab.General].RectTransform, Anchor.Center)) { Stretch = true, RelativeSpacing = 0.02f }; + + //*********************************************** - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), roundsTab.RectTransform), TextManager.Get("ServerSettingsSubSelection")); - var selectionFrame = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), roundsTab.RectTransform), isHorizontal: true) + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), serverTab.RectTransform), TextManager.Get("ServerSettingsSubSelection")); + var selectionFrame = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), serverTab.RectTransform), isHorizontal: true) { Stretch = true, RelativeSpacing = 0.05f @@ -327,11 +316,11 @@ namespace Barotrauma.Networking var selectionTick = new GUITickBox(new RectTransform(new Vector2(0.3f, 1.0f), selectionFrame.RectTransform), TextManager.Get(((SelectionMode)i).ToString()), font: GUI.SmallFont); selectionMode.AddRadioButton((SelectionMode)i, selectionTick); } - DebugConsole.NewMessage(SubSelectionMode.ToString(),Color.White); + DebugConsole.NewMessage(SubSelectionMode.ToString(), Color.White); GetPropertyData("SubSelectionMode").AssignGUIComponent(selectionMode); - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), roundsTab.RectTransform), TextManager.Get("ServerSettingsModeSelection")); - selectionFrame = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), roundsTab.RectTransform), isHorizontal: true) + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), serverTab.RectTransform), TextManager.Get("ServerSettingsModeSelection")); + selectionFrame = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), serverTab.RectTransform), isHorizontal: true) { Stretch = true, RelativeSpacing = 0.05f @@ -345,6 +334,84 @@ namespace Barotrauma.Networking } GetPropertyData("ModeSelectionMode").AssignGUIComponent(selectionMode); + + //*********************************************** + + var voiceChatEnabled = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), serverTab.RectTransform), + TextManager.Get("ServerSettingsVoiceChatEnabled")); + GetPropertyData("VoiceChatEnabled").AssignGUIComponent(voiceChatEnabled); + + //*********************************************** + + string autoRestartDelayLabel = TextManager.Get("ServerSettingsAutoRestartDelay") + " "; + var startIntervalText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), serverTab.RectTransform), autoRestartDelayLabel); + var startIntervalSlider = new GUIScrollBar(new RectTransform(new Vector2(1.0f, 0.05f), serverTab.RectTransform), barSize: 0.1f) + { + UserData = startIntervalText, + Step = 0.05f, + OnMoved = (GUIScrollBar scrollBar, float barScroll) => + { + GUITextBlock text = scrollBar.UserData as GUITextBlock; + text.Text = autoRestartDelayLabel + ToolBox.SecondsToReadableTime(scrollBar.BarScrollValue); + return true; + } + }; + startIntervalSlider.Range = new Vector2(10.0f, 300.0f); + GetPropertyData("AutoRestartInterval").AssignGUIComponent(startIntervalSlider); + startIntervalSlider.OnMoved(startIntervalSlider, startIntervalSlider.BarScroll); + + //*********************************************** + + var startWhenClientsReady = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), serverTab.RectTransform), + TextManager.Get("ServerSettingsStartWhenClientsReady")); + GetPropertyData("StartWhenClientsReady").AssignGUIComponent(startWhenClientsReady); + + CreateLabeledSlider(serverTab, "ServerSettingsStartWhenClientsReadyRatio", out GUIScrollBar slider, out GUITextBlock sliderLabel); + string clientsReadyRequiredLabel = sliderLabel.Text; + slider.Step = 0.2f; + slider.Range = new Vector2(0.5f, 1.0f); + slider.OnMoved = (GUIScrollBar scrollBar, float barScroll) => + { + ((GUITextBlock)scrollBar.UserData).Text = clientsReadyRequiredLabel.Replace("[percentage]", ((int)MathUtils.Round(scrollBar.BarScrollValue * 100.0f, 10.0f)).ToString()); + return true; + }; + GetPropertyData("StartWhenClientsReadyRatio").AssignGUIComponent(slider); + slider.OnMoved(slider, slider.BarScroll); + + //*********************************************** + + var allowSpecBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), serverTab.RectTransform), TextManager.Get("ServerSettingsAllowSpectating")); + GetPropertyData("AllowSpectating").AssignGUIComponent(allowSpecBox); + + + var shareSubsBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), serverTab.RectTransform), TextManager.Get("ServerSettingsShareSubFiles")); + GetPropertyData("AllowFileTransfers").AssignGUIComponent(shareSubsBox); + + var randomizeLevelBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), serverTab.RectTransform), TextManager.Get("ServerSettingsRandomizeSeed")); + GetPropertyData("RandomizeSeed").AssignGUIComponent(randomizeLevelBox); + + var saveLogsBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), serverTab.RectTransform), TextManager.Get("ServerSettingsSaveLogs")) + { + OnSelected = (GUITickBox) => + { + //TODO: fix? + //showLogButton.Visible = SaveServerLogs; + return true; + } + }; + GetPropertyData("SaveServerLogs").AssignGUIComponent(saveLogsBox); + + //-------------------------------------------------------------------------------- + // game settings + //-------------------------------------------------------------------------------- + + var roundsTab = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.95f), settingsTabs[(int)SettingsTab.Rounds].RectTransform, Anchor.Center)) + { + Stretch = true, + RelativeSpacing = 0.02f + }; + + var endBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), roundsTab.RectTransform), TextManager.Get("ServerSettingsEndRoundWhenDestReached")); GetPropertyData("EndRoundAtLevelEnd").AssignGUIComponent(endBox); @@ -353,7 +420,7 @@ namespace Barotrauma.Networking TextManager.Get("ServerSettingsEndRoundVoting")); GetPropertyData("AllowEndVoting").AssignGUIComponent(endVoteBox); - CreateLabeledSlider(roundsTab, "ServerSettingsEndRoundVotesRequired", out GUIScrollBar slider, out GUITextBlock sliderLabel); + CreateLabeledSlider(roundsTab, "ServerSettingsEndRoundVotesRequired", out slider, out sliderLabel); string endRoundLabel = sliderLabel.Text; slider.Step = 0.2f; @@ -361,7 +428,7 @@ namespace Barotrauma.Networking GetPropertyData("EndVoteRequiredRatio").AssignGUIComponent(slider); slider.OnMoved = (GUIScrollBar scrollBar, float barScroll) => { - ((GUITextBlock)scrollBar.UserData).Text = endRoundLabel + (int)MathUtils.Round(scrollBar.BarScrollValue * 100.0f, 10.0f) + " %"; + ((GUITextBlock)scrollBar.UserData).Text = endRoundLabel + " " + (int)MathUtils.Round(scrollBar.BarScrollValue * 100.0f, 10.0f) + " %"; return true; }; slider.OnMoved(slider, slider.BarScroll); @@ -378,7 +445,7 @@ namespace Barotrauma.Networking slider.OnMoved = (GUIScrollBar scrollBar, float barScroll) => { GUITextBlock text = scrollBar.UserData as GUITextBlock; - text.Text = intervalLabel + ToolBox.SecondsToReadableTime(scrollBar.BarScrollValue); + text.Text = intervalLabel + " " + ToolBox.SecondsToReadableTime(scrollBar.BarScrollValue); return true; }; slider.OnMoved(slider, slider.BarScroll); @@ -437,6 +504,57 @@ namespace Barotrauma.Networking }; slider.OnMoved(slider, slider.BarScroll); + + var ragdollButtonBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), roundsTab.RectTransform), TextManager.Get("ServerSettingsAllowRagdollButton")); + GetPropertyData("AllowRagdollButton").AssignGUIComponent(ragdollButtonBox); + + var traitorRatioBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), roundsTab.RectTransform), TextManager.Get("ServerSettingsUseTraitorRatio")); + + CreateLabeledSlider(roundsTab, "", out slider, out sliderLabel); + var traitorRatioSlider = slider; + traitorRatioBox.OnSelected = (GUITickBox) => + { + traitorRatioSlider.OnMoved(traitorRatioSlider, traitorRatioSlider.BarScroll); + return true; + }; + + if (TraitorUseRatio) + { + traitorRatioSlider.Range = new Vector2(0.1f, 1.0f); + } + else + { + traitorRatioSlider.Range = new Vector2(1.0f, maxPlayers); + } + + string traitorRatioLabel = TextManager.Get("ServerSettingsTraitorRatio") + " "; + string traitorCountLabel = TextManager.Get("ServerSettingsTraitorCount") + " "; + + traitorRatioSlider.Range = new Vector2(0.1f, 1.0f); + traitorRatioSlider.OnMoved = (GUIScrollBar scrollBar, float barScroll) => + { + GUITextBlock traitorText = scrollBar.UserData as GUITextBlock; + if (traitorRatioBox.Selected) + { + scrollBar.Step = 0.01f; + scrollBar.Range = new Vector2(0.1f, 1.0f); + traitorText.Text = traitorRatioLabel + (int)MathUtils.Round(scrollBar.BarScrollValue * 100.0f, 1.0f) + " %"; + } + else + { + scrollBar.Step = 1f / (maxPlayers - 1); + scrollBar.Range = new Vector2(1.0f, maxPlayers); + traitorText.Text = traitorCountLabel + scrollBar.BarScrollValue; + } + return true; + }; + + GetPropertyData("TraitorUseRatio").AssignGUIComponent(traitorRatioBox); + GetPropertyData("TraitorRatio").AssignGUIComponent(traitorRatioSlider); + + traitorRatioSlider.OnMoved(traitorRatioSlider, traitorRatioSlider.BarScroll); + traitorRatioBox.OnSelected(traitorRatioBox); + var buttonHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.07f), roundsTab.RectTransform), isHorizontal: true) { Stretch = true, @@ -558,68 +676,21 @@ namespace Barotrauma.Networking }; } + //-------------------------------------------------------------------------------- - // server settings + // antigriefing //-------------------------------------------------------------------------------- - var serverTab = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.95f), settingsTabs[(int)SettingsTab.Server].RectTransform, Anchor.Center)) + var antigriefingTab = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.95f), settingsTabs[(int)SettingsTab.Antigriefing].RectTransform, Anchor.Center)) { Stretch = true, RelativeSpacing = 0.02f }; - //*********************************************** - - var voiceChatEnabled = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), serverTab.RectTransform), - TextManager.Get("ServerSettingsVoiceChatEnabled")); - GetPropertyData("VoiceChatEnabled").AssignGUIComponent(voiceChatEnabled); - - //*********************************************** - - string autoRestartDelayLabel = TextManager.Get("ServerSettingsAutoRestartDelay") + " "; - var startIntervalText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), serverTab.RectTransform), autoRestartDelayLabel); - var startIntervalSlider = new GUIScrollBar(new RectTransform(new Vector2(1.0f, 0.05f), serverTab.RectTransform), barSize: 0.1f) - { - UserData = startIntervalText, - Step = 0.05f, - OnMoved = (GUIScrollBar scrollBar, float barScroll) => - { - GUITextBlock text = scrollBar.UserData as GUITextBlock; - text.Text = autoRestartDelayLabel + ToolBox.SecondsToReadableTime(scrollBar.BarScrollValue); - return true; - } - }; - startIntervalSlider.Range = new Vector2(10.0f, 300.0f); - GetPropertyData("AutoRestartInterval").AssignGUIComponent(startIntervalSlider); - startIntervalSlider.OnMoved(startIntervalSlider, startIntervalSlider.BarScroll); - - //*********************************************** - - var startWhenClientsReady = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), serverTab.RectTransform), - TextManager.Get("ServerSettingsStartWhenClientsReady")); - GetPropertyData("StartWhenClientsReady").AssignGUIComponent(startWhenClientsReady); - - CreateLabeledSlider(serverTab, "ServerSettingsStartWhenClientsReadyRatio", out slider, out sliderLabel); - string clientsReadyRequiredLabel = sliderLabel.Text; - slider.Step = 0.2f; - slider.Range = new Vector2(0.5f, 1.0f); - slider.OnMoved = (GUIScrollBar scrollBar, float barScroll) => - { - ((GUITextBlock)scrollBar.UserData).Text = clientsReadyRequiredLabel.Replace("[percentage]", ((int)MathUtils.Round(scrollBar.BarScrollValue * 100.0f, 10.0f)).ToString()); - return true; - }; - GetPropertyData("StartWhenClientsReadyRatio").AssignGUIComponent(slider); - slider.OnMoved(slider, slider.BarScroll); - - //*********************************************** - - var allowSpecBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), serverTab.RectTransform), TextManager.Get("ServerSettingsAllowSpectating")); - GetPropertyData("AllowSpectating").AssignGUIComponent(allowSpecBox); - - var voteKickBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), serverTab.RectTransform), TextManager.Get("ServerSettingsAllowVoteKick")); + var voteKickBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), antigriefingTab.RectTransform), TextManager.Get("ServerSettingsAllowVoteKick")); GetPropertyData("AllowVoteKick").AssignGUIComponent(voteKickBox); - CreateLabeledSlider(serverTab, "ServerSettingsKickVotesRequired", out slider, out sliderLabel); + CreateLabeledSlider(antigriefingTab, "ServerSettingsKickVotesRequired", out slider, out sliderLabel); string votesRequiredLabel = sliderLabel.Text + " "; slider.Step = 0.2f; slider.Range = new Vector2(0.5f, 1.0f); @@ -631,9 +702,9 @@ namespace Barotrauma.Networking GetPropertyData("KickVoteRequiredRatio").AssignGUIComponent(slider); slider.OnMoved(slider, slider.BarScroll); - CreateLabeledSlider(serverTab, "ServerSettingsAutobanTime", out slider, out sliderLabel); + CreateLabeledSlider(antigriefingTab, "ServerSettingsAutobanTime", out slider, out sliderLabel); string autobanLabel = sliderLabel.Text + " "; - slider.Step = 0.05f; + slider.Step = 0.01f; slider.Range = new Vector2(0.0f, MaxAutoBanTime); slider.OnMoved = (GUIScrollBar scrollBar, float barScroll) => { @@ -643,91 +714,66 @@ namespace Barotrauma.Networking GetPropertyData("AutoBanTime").AssignGUIComponent(slider); slider.OnMoved(slider, slider.BarScroll); - var shareSubsBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), serverTab.RectTransform), TextManager.Get("ServerSettingsShareSubFiles")); - GetPropertyData("AllowFileTransfers").AssignGUIComponent(shareSubsBox); + // karma -------------------------------------------------------------------------- - var randomizeLevelBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), serverTab.RectTransform), TextManager.Get("ServerSettingsRandomizeSeed")); - GetPropertyData("RandomizeSeed").AssignGUIComponent(randomizeLevelBox); - - var saveLogsBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), serverTab.RectTransform), TextManager.Get("ServerSettingsSaveLogs")) - { - OnSelected = (GUITickBox) => - { - //TODO: fix? - //showLogButton.Visible = SaveServerLogs; - return true; - } - }; - GetPropertyData("SaveServerLogs").AssignGUIComponent(saveLogsBox); - - var ragdollButtonBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), serverTab.RectTransform), TextManager.Get("ServerSettingsAllowRagdollButton")); - GetPropertyData("AllowRagdollButton").AssignGUIComponent(ragdollButtonBox); - - var traitorRatioBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), serverTab.RectTransform), TextManager.Get("ServerSettingsUseTraitorRatio")); - - CreateLabeledSlider(serverTab, "", out slider, out sliderLabel); - /*var traitorRatioText = new GUITextBlock(new Rectangle(20, y + 20, 20, 20), "Traitor ratio: 20 %", "", settingsTabs[1], GUI.SmallFont); - var traitorRatioSlider = new GUIScrollBar(new Rectangle(150, y + 22, 100, 15), "", 0.1f, settingsTabs[1]);*/ - var traitorRatioSlider = slider; - traitorRatioBox.OnSelected = (GUITickBox) => - { - traitorRatioSlider.OnMoved(traitorRatioSlider, traitorRatioSlider.BarScroll); - return true; - }; - - if (TraitorUseRatio) - { - traitorRatioSlider.Range = new Vector2(0.1f, 1.0f); - } - else - { - traitorRatioSlider.Range = new Vector2(1.0f, maxPlayers); - } - - string traitorRatioLabel = TextManager.Get("ServerSettingsTraitorRatio") + " "; - string traitorCountLabel = TextManager.Get("ServerSettingsTraitorCount") + " "; - - traitorRatioSlider.Range = new Vector2(0.1f, 1.0f); - traitorRatioSlider.OnMoved = (GUIScrollBar scrollBar, float barScroll) => - { - GUITextBlock traitorText = scrollBar.UserData as GUITextBlock; - if (traitorRatioBox.Selected) - { - scrollBar.Step = 0.01f; - scrollBar.Range = new Vector2(0.1f, 1.0f); - traitorText.Text = traitorRatioLabel + (int)MathUtils.Round(scrollBar.BarScrollValue * 100.0f, 1.0f) + " %"; - } - else - { - scrollBar.Step = 1f / (maxPlayers - 1); - scrollBar.Range = new Vector2(1.0f, maxPlayers); - traitorText.Text = traitorCountLabel + scrollBar.BarScrollValue; - } - return true; - }; - - GetPropertyData("TraitorUseRatio").AssignGUIComponent(traitorRatioBox); - GetPropertyData("TraitorRatio").AssignGUIComponent(traitorRatioSlider); - - traitorRatioSlider.OnMoved(traitorRatioSlider, traitorRatioSlider.BarScroll); - traitorRatioBox.OnSelected(traitorRatioBox); - - - var karmaBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), serverTab.RectTransform), TextManager.Get("ServerSettingsUseKarma")); + var karmaBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), antigriefingTab.RectTransform), TextManager.Get("ServerSettingsUseKarma")); GetPropertyData("KarmaEnabled").AssignGUIComponent(karmaBox); + karmaPresetDD = new GUIDropDown(new RectTransform(new Vector2(1.0f, 0.05f), antigriefingTab.RectTransform)); + foreach (string karmaPreset in GameMain.NetworkMember.KarmaManager.Presets.Keys) + { + karmaPresetDD.AddItem(TextManager.Get("KarmaPreset." + karmaPreset), karmaPreset); + } + + var karmaSettingsContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.5f), antigriefingTab.RectTransform), style: null); + var karmaSettingsList = new GUIListBox(new RectTransform(Vector2.One, karmaSettingsContainer.RectTransform)); + + karmaSettingsBlocker = new GUIFrame(new RectTransform(Vector2.One, karmaSettingsContainer.RectTransform, Anchor.CenterLeft) { MaxSize = new Point(karmaSettingsList.Content.Rect.Width, int.MaxValue) }, + style: "InnerFrame"); + karmaPresetDD.OnSelected = (selected, obj) => + { + List properties = netProperties.Values.ToList(); + List prevValues = new List(); + foreach (NetPropertyData prop in netProperties.Values) + { + prevValues.Add(prop.TempValue); + if (prop.GUIComponent != null) { prop.Value = prop.GUIComponentValue; } + } + if (KarmaPreset == "custom") + { + GameMain.NetworkMember?.KarmaManager?.SaveCustomPreset(); + GameMain.NetworkMember?.KarmaManager?.Save(); + } + KarmaPreset = obj as string; + GameMain.NetworkMember.KarmaManager.SelectPreset(KarmaPreset); + karmaSettingsList.Content.ClearChildren(); + karmaSettingsBlocker.Visible = !karmaBox.Selected || KarmaPreset != "custom"; + GameMain.NetworkMember.KarmaManager.CreateSettingsFrame(karmaSettingsList.Content); + for (int i = 0; i < netProperties.Count; i++) + { + properties[i].TempValue = prevValues[i]; + } + return true; + }; + karmaPresetDD.SelectItem(KarmaPreset); + AssignGUIComponent("KarmaPreset", karmaPresetDD); + karmaBox.OnSelected = (tb) => + { + karmaSettingsBlocker.Visible = !tb.Selected || KarmaPreset != "custom"; + return true; + }; + //-------------------------------------------------------------------------------- // banlist //-------------------------------------------------------------------------------- - BanList.CreateBanFrame(settingsTabs[2]); + BanList.CreateBanFrame(settingsTabs[(int)SettingsTab.Banlist]); //-------------------------------------------------------------------------------- // whitelist //-------------------------------------------------------------------------------- - Whitelist.CreateWhiteListFrame(settingsTabs[3]); - + Whitelist.CreateWhiteListFrame(settingsTabs[(int)SettingsTab.Whitelist]); } private void CreateLabeledSlider(GUIComponent parent, string labelTag, out GUIScrollBar slider, out GUITextBlock label) @@ -763,6 +809,11 @@ namespace Barotrauma.Networking { if (settingsFrame == null) { + if (KarmaPreset == "custom") + { + GameMain.NetworkMember?.KarmaManager?.SaveCustomPreset(); + GameMain.NetworkMember?.KarmaManager?.Save(); + } CreateSettingsFrame(); } else @@ -777,44 +828,5 @@ namespace Barotrauma.Networking return false; } - - public void ManagePlayersFrame(GUIFrame infoFrame) - { - GUIListBox cList = new GUIListBox(new RectTransform(Vector2.One, infoFrame.RectTransform)); - /*foreach (Client c in ConnectedClients) - { - var frame = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.15f), cList.Content.RectTransform), - c.Name + " (" + c.Connection.RemoteEndPoint.Address.ToString() + ")", style: "ListBoxElement") - { - Color = (c.InGame && c.Character != null && !c.Character.IsDead) ? Color.Gold * 0.2f : Color.Transparent, - HoverColor = Color.LightGray * 0.5f, - SelectedColor = Color.Gold * 0.5f - }; - - var buttonArea = new GUILayoutGroup(new RectTransform(new Vector2(0.45f, 0.85f), frame.RectTransform, Anchor.CenterRight) { RelativeOffset = new Vector2(0.05f, 0.0f) }, - isHorizontal: true); - - var kickButton = new GUIButton(new RectTransform(new Vector2(0.25f, 1.0f), buttonArea.RectTransform), - TextManager.Get("Kick")) - { - UserData = c.Name, - OnClicked = GameMain.NetLobbyScreen.KickPlayer - }; - - var banButton = new GUIButton(new RectTransform(new Vector2(0.25f, 1.0f), buttonArea.RectTransform), - TextManager.Get("Ban")) - { - UserData = c.Name, - OnClicked = GameMain.NetLobbyScreen.BanPlayer - }; - - var rangebanButton = new GUIButton(new RectTransform(new Vector2(0.25f, 1.0f), buttonArea.RectTransform), - TextManager.Get("BanRange")) - { - UserData = c.Name, - OnClicked = GameMain.NetLobbyScreen.BanPlayerRange - }; - }*/ //TODO: reimplement - } } } \ No newline at end of file diff --git a/Barotrauma/BarotraumaClient/Source/Networking/SteamManager.cs b/Barotrauma/BarotraumaClient/Source/Networking/SteamManager.cs index e505a5d24..e0443bb38 100644 --- a/Barotrauma/BarotraumaClient/Source/Networking/SteamManager.cs +++ b/Barotrauma/BarotraumaClient/Source/Networking/SteamManager.cs @@ -597,6 +597,8 @@ namespace Barotrauma.Steam if (string.IsNullOrEmpty(item.Error)) { DebugConsole.NewMessage("Published workshop item " + item.Title + " successfully.", Microsoft.Xna.Framework.Color.LightGreen); + var newItem = instance.client.Workshop.GetItem(item.Id); + newItem?.Subscribe(); } else { diff --git a/Barotrauma/BarotraumaClient/Source/Particles/Particle.cs b/Barotrauma/BarotraumaClient/Source/Particles/Particle.cs index 7a79d8925..3507bb6fb 100644 --- a/Barotrauma/BarotraumaClient/Source/Particles/Particle.cs +++ b/Barotrauma/BarotraumaClient/Source/Particles/Particle.cs @@ -56,6 +56,8 @@ namespace Barotrauma.Particles private int animFrame; private float collisionUpdateTimer; + + public bool HighQualityCollisionDetection; public ParticlePrefab.DrawTargetType DrawTarget { @@ -133,6 +135,8 @@ namespace Barotrauma.Particles velocityChange = prefab.VelocityChangeDisplay; velocityChangeWater = prefab.VelocityChangeWaterDisplay; + HighQualityCollisionDetection = false; + OnChangeHull = null; subEmitters.Clear(); @@ -224,7 +228,7 @@ namespace Barotrauma.Particles } lifeTime -= deltaTime; - if (lifeTime <= 0.0f || color.A <= 0 || size.X <= 0.0f || size.Y <= 0.0f) return false; + if (lifeTime <= 0.0f || color.A <= 0 || size.X <= 0.0f || size.Y <= 0.0f) { return false; } if (hasSubEmitters) { @@ -234,16 +238,23 @@ namespace Barotrauma.Particles } } - if (!prefab.UseCollision) return true; + if (!prefab.UseCollision) { return true; } - collisionUpdateTimer -= deltaTime; - if (collisionUpdateTimer <= 0.0f) + if (HighQualityCollisionDetection) { - //more frequent collision updates if the particle is moving fast - collisionUpdateTimer = 0.5f - Math.Min((Math.Abs(velocity.X) + Math.Abs(velocity.Y)) * 0.001f, 0.4f); return CollisionUpdate(); } - + else + { + collisionUpdateTimer -= deltaTime; + if (collisionUpdateTimer <= 0.0f) + { + //more frequent collision updates if the particle is moving fast + collisionUpdateTimer = 0.5f - Math.Min((Math.Abs(velocity.X) + Math.Abs(velocity.Y)) * 0.01f, 0.45f); + return CollisionUpdate(); + } + } + return true; } @@ -288,7 +299,7 @@ namespace Barotrauma.Particles bool gapFound = false; foreach (Gap gap in hullGaps) { - if (gap.Open <= 0.0f || gap.IsHorizontal != (collisionNormal.X != 0.0f)) continue; + if (gap.Open <= 0.9f || gap.IsHorizontal != (collisionNormal.X != 0.0f)) continue; if (gap.IsHorizontal) { diff --git a/Barotrauma/BarotraumaClient/Source/Particles/ParticleEmitter.cs b/Barotrauma/BarotraumaClient/Source/Particles/ParticleEmitter.cs index f70074ecc..34dfcf6b8 100644 --- a/Barotrauma/BarotraumaClient/Source/Particles/ParticleEmitter.cs +++ b/Barotrauma/BarotraumaClient/Source/Particles/ParticleEmitter.cs @@ -56,6 +56,7 @@ namespace Barotrauma.Particles if (particle != null) { particle.Size *= Rand.Range(Prefab.ScaleMin, Prefab.ScaleMax); + particle.HighQualityCollisionDetection = Prefab.HighQualityCollisionDetection; } } @@ -98,6 +99,8 @@ namespace Barotrauma.Particles public readonly float ParticlesPerSecond; + public readonly bool HighQualityCollisionDetection; + public readonly bool CopyEntityAngle; public ParticleEmitterPrefab(XElement element) @@ -145,7 +148,7 @@ namespace Barotrauma.Particles EmitInterval = element.GetAttributeFloat("emitinterval", 0.0f); ParticlesPerSecond = element.GetAttributeInt("particlespersecond", 0); ParticleAmount = element.GetAttributeInt("particleamount", 0); - + HighQualityCollisionDetection = element.GetAttributeBool("highqualitycollisiondetection", false); CopyEntityAngle = element.GetAttributeBool("copyentityangle", false); } } diff --git a/Barotrauma/BarotraumaClient/Source/Physics/PhysicsBody.cs b/Barotrauma/BarotraumaClient/Source/Physics/PhysicsBody.cs index 88e1011e8..d79fc2499 100644 --- a/Barotrauma/BarotraumaClient/Source/Physics/PhysicsBody.cs +++ b/Barotrauma/BarotraumaClient/Source/Physics/PhysicsBody.cs @@ -45,7 +45,7 @@ namespace Barotrauma { if (!body.Enabled) { - color = Color.Gray; + color = Color.Black; } else if (!body.Awake) { diff --git a/Barotrauma/BarotraumaClient/Source/Program.cs b/Barotrauma/BarotraumaClient/Source/Program.cs index b64ef62d9..506c0a219 100644 --- a/Barotrauma/BarotraumaClient/Source/Program.cs +++ b/Barotrauma/BarotraumaClient/Source/Program.cs @@ -267,6 +267,17 @@ namespace Barotrauma sb.AppendLine(exception.StackTrace); sb.AppendLine("\n"); + if (exception.InnerException != null) + { + sb.AppendLine("InnerException: " + exception.InnerException.Message); + if (exception.InnerException.TargetSite != null) + { + sb.AppendLine("Target site: " + exception.InnerException.TargetSite.ToString()); + } + sb.AppendLine("Stack trace: "); + sb.AppendLine(exception.InnerException.StackTrace); + } + sb.AppendLine("Last debug messages:"); for (int i = DebugConsole.Messages.Count - 1; i >= 0; i--) { diff --git a/Barotrauma/BarotraumaClient/Source/Screens/CharacterEditorScreen.cs b/Barotrauma/BarotraumaClient/Source/Screens/CharacterEditorScreen.cs index e396b450d..9ae6411bd 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/CharacterEditorScreen.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/CharacterEditorScreen.cs @@ -2788,6 +2788,7 @@ namespace Barotrauma if (editRagdoll || !editLimbs && !editJoints) { RagdollParams.AddToEditor(ParamsEditor.Instance, alsoChildren: false); + RagdollParams.ColliderParams.ForEach(c => c.AddToEditor(ParamsEditor.Instance)); } if (editJoints) { @@ -3041,14 +3042,16 @@ namespace Barotrauma private void CalculateSpritesheetZoom() { - float width = textures.OrderByDescending(t => t.Width).First().Width; + var texture = textures.OrderByDescending(t => t.Width).FirstOrDefault(); + if (texture == null) + { + spriteSheetZoom = 1; + return; + } + float width = texture.Width; float height = textures.Sum(t => t.Height); float margin = 20; - if (textures == null || textures.None()) - { - spriteSheetMaxZoom = 1; - } - else if (height > width) + if (height > width) { spriteSheetMaxZoom = (centerArea.Rect.Bottom - spriteSheetOffsetY - margin) / height; } diff --git a/Barotrauma/BarotraumaClient/Source/Screens/ServerListScreen.cs b/Barotrauma/BarotraumaClient/Source/Screens/ServerListScreen.cs index b0688089c..342a73ee0 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/ServerListScreen.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/ServerListScreen.cs @@ -415,9 +415,22 @@ namespace Barotrauma ServerInfo s1 = c1.GUIComponent.UserData as ServerInfo; ServerInfo s2 = c2.GUIComponent.UserData as ServerInfo; + if (s1 == null && s2 == null) + { + return 0; + } + else if (s1 == null) + { + return ascending ? 1 : -1; + } + else if (s2 == null) + { + return ascending ? -1 : 1; + } + switch (sortBy) { - case "ServerListCompatible": + case "ServerListCompatible": bool? s1Compatible = NetworkMember.IsCompatible(GameMain.Version.ToString(), s1.GameVersion); if (!s1.ContentPackageHashes.Any()) { s1Compatible = null; } if (s1Compatible.HasValue) { s1Compatible = s1Compatible.Value && s1.ContentPackagesMatch(GameMain.SelectedPackages); }; @@ -441,7 +454,7 @@ namespace Barotrauma if (s1.HasPassword == s2.HasPassword) { return 0; } return (s1.HasPassword ? 1 : -1) * (ascending ? 1 : -1); case "ServerListName": - return s1.ServerName.CompareTo(s2.ServerName) * (ascending ? 1 : -1); + return string.Compare(s1.ServerName, s2.ServerName) * (ascending ? 1 : -1); case "ServerListRoundStarted": if (s1.GameStarted == s2.GameStarted) { return 0; } return (s1.GameStarted ? 1 : -1) * (ascending ? 1 : -1); diff --git a/Barotrauma/BarotraumaClient/Source/Screens/SpriteEditorScreen.cs b/Barotrauma/BarotraumaClient/Source/Screens/SpriteEditorScreen.cs index 72ad18ada..fd5456641 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/SpriteEditorScreen.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/SpriteEditorScreen.cs @@ -20,6 +20,8 @@ namespace Barotrauma private GUIFrame bottomPanel; private GUIFrame backgroundColorPanel; + private bool drawGrid, snapToGrid; + private GUIFrame topPanelContents; private GUITextBlock texturePathText; private GUITextBlock xmlPathText; @@ -137,7 +139,7 @@ namespace Barotrauma return true; } }; - new GUIButton(new RectTransform(new Vector2(0.05f, 0.35f), topPanelContents.RectTransform, Anchor.TopCenter, Pivot.CenterLeft) { RelativeOffset = new Vector2(0.055f, 0.3f) }, "Reset Zoom") + var resetBtn = new GUIButton(new RectTransform(new Vector2(0.05f, 0.35f), topPanelContents.RectTransform, Anchor.TopCenter, Pivot.CenterLeft) { RelativeOffset = new Vector2(0.055f, 0.3f) }, "Reset Zoom") { OnClicked = (box, data) => { @@ -145,6 +147,26 @@ namespace Barotrauma return true; } }; + resetBtn.TextBlock.AutoScale = true; + + new GUITickBox(new RectTransform(new Vector2(0.2f, 0.2f), topPanelContents.RectTransform, Anchor.BottomCenter, Pivot.CenterRight) { RelativeOffset = new Vector2(0, 0.3f) }, "Show grid") + { + Selected = drawGrid, + OnSelected = (tickBox) => + { + drawGrid = tickBox.Selected; + return true; + } + }; + new GUITickBox(new RectTransform(new Vector2(0.2f, 0.2f), topPanelContents.RectTransform, Anchor.BottomCenter, Pivot.CenterRight) { RelativeOffset = new Vector2(0.17f, 0.3f) }, "Snap to grid") + { + Selected = snapToGrid, + OnSelected = (tickBox) => + { + snapToGrid = tickBox.Selected; + return true; + } + }; texturePathText = new GUITextBlock(new RectTransform(new Vector2(0.5f, 0.4f), topPanelContents.RectTransform, Anchor.Center, Pivot.BottomCenter) { RelativeOffset = new Vector2(0.4f, 0) }, "", Color.LightGray); xmlPathText = new GUITextBlock(new RectTransform(new Vector2(0.5f, 0.4f), topPanelContents.RectTransform, Anchor.Center, Pivot.TopCenter) { RelativeOffset = new Vector2(0.4f, 0) }, "", Color.LightGray); @@ -230,8 +252,7 @@ namespace Barotrauma }, style: null, color: Color.Black * 0.6f); var colorLabel = new GUITextBlock(new RectTransform(new Vector2(0.3f, 1), element.RectTransform, Anchor.CenterLeft), colorComponentLabels[i], font: GUI.SmallFont, textAlignment: Alignment.CenterLeft); - GUINumberInput numberInput = new GUINumberInput(new RectTransform(new Vector2(0.7f, 1), element.RectTransform, Anchor.CenterRight), - GUINumberInput.NumberType.Int) + var numberInput = new GUINumberInput(new RectTransform(new Vector2(0.7f, 1), element.RectTransform, Anchor.CenterRight), GUINumberInput.NumberType.Int) { Font = GUI.SmallFont }; @@ -259,26 +280,32 @@ namespace Barotrauma } } - private HashSet loadedSprites = new HashSet(); + private readonly HashSet loadedSprites = new HashSet(); private void LoadSprites() { loadedSprites.ForEach(s => s.Remove()); loadedSprites.Clear(); - //foreach (string filePath in ContentPackage.GetAllContentFiles(GameMain.SelectedPackages)) - //{ - // XDocument doc = XMLExtensions.TryLoadXml(filePath); - // if (doc != null && doc.Root != null) - // { - // LoadSprites(doc.Root); - // } - //} + var contentPackages = GameMain.Config.SelectedContentPackages.ToList(); - foreach (string filePath in Directory.GetFiles("Content/", "*.xml", SearchOption.AllDirectories)) +#if !DEBUG + var vanilla = GameMain.VanillaContent; + if (vanilla != null) { - XDocument doc = XMLExtensions.TryLoadXml(filePath); - if (doc != null && doc.Root != null) + contentPackages.Remove(vanilla); + } +#endif + foreach (var contentPackage in contentPackages) + { + foreach (var file in contentPackage.Files) { - LoadSprites(doc.Root); + if (file.Path.EndsWith(".xml")) + { + XDocument doc = XMLExtensions.TryLoadXml(file.Path); + if (doc != null && doc.Root != null) + { + LoadSprites(doc.Root); + } + } } } @@ -304,11 +331,12 @@ namespace Barotrauma { string spriteFolder = ""; string textureElement = element.GetAttributeString("texture", ""); - // TODO: parse and create + // TODO: parse and create? if (textureElement.Contains("[GENDER]") || textureElement.Contains("[HEADID]") || textureElement.Contains("[RACE]")) { return; } if (!textureElement.Contains("/")) { - spriteFolder = Path.GetDirectoryName(element.ParseContentPathFromUri()); + var parsedPath = element.ParseContentPathFromUri(); + spriteFolder = Path.GetDirectoryName(parsedPath); } // Uncomment if we do multiple passes -> there can be duplicates //string identifier = Sprite.GetID(element); @@ -344,7 +372,7 @@ namespace Barotrauma xmlPathText.TextColor = Color.LightGreen; return true; } - #endregion +#endregion #region Public methods public override void AddToGUIUpdateList() @@ -466,6 +494,11 @@ namespace Barotrauma //GUI.DrawRectangle(spriteBatch, viewArea, Color.Green, isFilled: false); GUI.DrawRectangle(spriteBatch, textureRect, Color.Gray, isFilled: false); + if (drawGrid) + { + DrawGrid(spriteBatch, textureRect, zoom, Submarine.GridSize); + } + foreach (GUIComponent element in spriteList.Content.Children) { Sprite sprite = element.UserData as Sprite; @@ -507,8 +540,11 @@ namespace Barotrauma w.tooltip = $"Position: {sprite.SourceRect.Location}"; w.MouseHeld += dTime => { - w.DrawPos = PlayerInput.MousePosition; - sprite.SourceRect = new Rectangle(((w.DrawPos + new Vector2(w.size / 2) - textureRect.Location.ToVector2()) / zoom).ToPoint(), sprite.SourceRect.Size); + w.DrawPos = (drawGrid && snapToGrid) ? + SnapToGrid(PlayerInput.MousePosition, textureRect, zoom, Submarine.GridSize, Submarine.GridSize.X / 4.0f * zoom) : + PlayerInput.MousePosition; + w.DrawPos = new Vector2((float)Math.Ceiling(w.DrawPos.X), (float)Math.Ceiling(w.DrawPos.Y)); + sprite.SourceRect = new Rectangle(((w.DrawPos - textureRect.Location.ToVector2()) / zoom).ToPoint(), sprite.SourceRect.Size); if (spriteList.SelectedComponent is GUITextBlock textBox) { // TODO: cache the sprite name? @@ -516,15 +552,18 @@ namespace Barotrauma } w.tooltip = $"Position: {sprite.SourceRect.Location}"; }; - w.refresh = () => w.DrawPos = textureRect.Location.ToVector2() + sprite.SourceRect.Location.ToVector2() * zoom - new Vector2(w.size / 2); + w.refresh = () => w.DrawPos = textureRect.Location.ToVector2() + sprite.SourceRect.Location.ToVector2() * zoom; }); var sizeWidget = GetWidget($"{id}_size", sprite, widgetSize, Widget.Shape.Rectangle, initMethod: w => { w.tooltip = $"Size: {sprite.SourceRect.Size}"; w.MouseHeld += dTime => { - w.DrawPos = PlayerInput.MousePosition; - sprite.SourceRect = new Rectangle(sprite.SourceRect.Location, ((w.DrawPos - new Vector2(w.size) - positionWidget.DrawPos) / zoom).ToPoint()); + w.DrawPos = (drawGrid && snapToGrid) ? + SnapToGrid(PlayerInput.MousePosition, textureRect, zoom, Submarine.GridSize, Submarine.GridSize.X / 4.0f * zoom) : + PlayerInput.MousePosition; + w.DrawPos = new Vector2((float)Math.Ceiling(w.DrawPos.X), (float)Math.Ceiling(w.DrawPos.Y)); + sprite.SourceRect = new Rectangle(sprite.SourceRect.Location, ((w.DrawPos - positionWidget.DrawPos) / zoom).ToPoint()); // TODO: allow to lock the origin sprite.RelativeOrigin = sprite.RelativeOrigin; if (spriteList.SelectedComponent is GUITextBlock textBox) @@ -534,7 +573,7 @@ namespace Barotrauma } w.tooltip = $"Size: {sprite.SourceRect.Size}"; }; - w.refresh = () => w.DrawPos = textureRect.Location.ToVector2() + new Vector2(sprite.SourceRect.Right, sprite.SourceRect.Bottom) * zoom + new Vector2(w.size / 2); + w.refresh = () => w.DrawPos = textureRect.Location.ToVector2() + new Vector2(sprite.SourceRect.Right, sprite.SourceRect.Bottom) * zoom; }); if (isSelected) { @@ -553,6 +592,58 @@ namespace Barotrauma spriteBatch.End(); } + private void DrawGrid(SpriteBatch spriteBatch, Rectangle gridArea, float zoom, Vector2 gridSize) + { + gridSize *= zoom; + if (gridSize.X < 1.0f) { return; } + if (gridSize.Y < 1.0f) { return; } + int xLines = (int)(gridArea.Width / gridSize.X); + int yLines = (int)(gridArea.Height / gridSize.Y); + + for (int x = 0; x <= xLines; x++) + { + GUI.DrawLine(spriteBatch, + new Vector2(gridArea.X + x * gridSize.X, gridArea.Y), + new Vector2(gridArea.X + x * gridSize.X, gridArea.Bottom), + Color.White * 0.25f); + } + for (int y = 0; y <= yLines; y++) + { + GUI.DrawLine(spriteBatch, + new Vector2(gridArea.X, gridArea.Y + y * gridSize.Y), + new Vector2(gridArea.Right, gridArea.Y + y * gridSize.Y), + Color.White * 0.25f); + } + } + + private Vector2 SnapToGrid(Vector2 position, Rectangle gridArea, float zoom, Vector2 gridSize, float tolerance) + { + gridSize *= zoom; + if (gridSize.X < 1.0f) { return position; } + if (gridSize.Y < 1.0f) { return position; } + + Vector2 snappedPos = position; + snappedPos.X -= gridArea.X; + snappedPos.Y -= gridArea.Y; + + Vector2 gridPos = new Vector2( + MathUtils.RoundTowardsClosest(snappedPos.X, gridSize.X), + MathUtils.RoundTowardsClosest(snappedPos.Y, gridSize.Y)); + + if (Math.Abs(gridPos.X - snappedPos.X) < tolerance) + { + snappedPos.X = gridPos.X; + } + if (Math.Abs(gridPos.Y - snappedPos.Y) < tolerance) + { + snappedPos.Y = gridPos.Y; + } + + snappedPos.X += gridArea.X; + snappedPos.Y += gridArea.Y; + return snappedPos; + } + public override void Select() { base.Select(); @@ -706,7 +797,7 @@ namespace Barotrauma zoomBar.BarScroll = GetBarScrollValue(); viewAreaOffset = Point.Zero; } - #endregion +#endregion #region Helpers private Point viewAreaOffset; @@ -750,7 +841,7 @@ namespace Barotrauma // Keeps the relative origin unchanged. The absolute origin will be recalculated. sprite.RelativeOrigin = sprite.RelativeOrigin; } - #endregion +#endregion #region Widgets private Dictionary widgets = new Dictionary(); @@ -792,6 +883,6 @@ namespace Barotrauma widgets.Clear(); Widget.selectedWidgets.Clear(); } - #endregion +#endregion } } diff --git a/Barotrauma/BarotraumaClient/Source/Screens/SteamWorkshopScreen.cs b/Barotrauma/BarotraumaClient/Source/Screens/SteamWorkshopScreen.cs index 905f0dea1..22e6c1206 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/SteamWorkshopScreen.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/SteamWorkshopScreen.cs @@ -51,7 +51,7 @@ namespace Barotrauma tabs = new GUIFrame[Enum.GetValues(typeof(Tab)).Length]; - menu = new GUIFrame(new RectTransform(new Vector2(0.85f, 0.8f), GUI.Canvas, Anchor.Center) { MinSize = new Point(GameMain.GraphicsHeight, 0) }); + menu = new GUIFrame(new RectTransform(new Vector2(0.85f, 0.85f), GUI.Canvas, Anchor.Center) { MinSize = new Point(GameMain.GraphicsHeight, 0) }); var container = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.85f), menu.RectTransform, Anchor.Center) { RelativeOffset = new Vector2(0.0f, 0.05f) }) { Stretch = true }; @@ -111,6 +111,7 @@ namespace Barotrauma var listContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 1.0f), tabs[(int)Tab.Browse].RectTransform)) { + RelativeSpacing = 0.01f, Stretch = true }; @@ -125,7 +126,7 @@ namespace Barotrauma }; var findModsButtonContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.02f), listContainer.RectTransform), style: null); - new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), findModsButtonContainer.RectTransform, Anchor.Center), TextManager.Get("FindModsButton"), style: null) + new GUIButton(new RectTransform(new Vector2(1.0f, 0.9f), findModsButtonContainer.RectTransform, Anchor.Center), TextManager.Get("FindModsButton"), style: null) { Color = new Color(38, 86, 38, 75), HoverColor = new Color(85, 203, 99, 50), @@ -260,7 +261,12 @@ namespace Barotrauma private void RefreshItemLists() { - SteamManager.GetSubscribedWorkshopItems((items) => { OnItemsReceived(items, subscribedItemList); }); + SteamManager.GetSubscribedWorkshopItems((items) => + { + //filter out the items published by the player (they're shown in the publish tab) + var mySteamID = SteamManager.GetSteamID(); + OnItemsReceived(items.Where(it => it.OwnerId != mySteamID).ToList(), subscribedItemList); + }); SteamManager.GetPopularWorkshopItems((items) => { OnItemsReceived(items, topItemList); }, 20); SteamManager.GetPublishedWorkshopItems((items) => { OnItemsReceived(items, publishedItemList); }); @@ -704,19 +710,17 @@ namespace Barotrauma } }; - var headerAreaBackground = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.5f), content.RectTransform, maxSize: new Point(int.MaxValue, 235))) { Color = Color.Black }; - - var headerArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f), headerAreaBackground.RectTransform), childAnchor: Anchor.Center); + var headerArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.5f), content.RectTransform)) { Color = Color.Black }; if (itemPreviewSprites.ContainsKey(item.PreviewImageUrl)) { - new GUIImage(new RectTransform(new Point(headerArea.Rect.Width, headerArea.Rect.Height), headerArea.RectTransform), itemPreviewSprites[item.PreviewImageUrl], scaleToFit: true); + new GUIImage(new RectTransform(Vector2.One, headerArea.RectTransform), itemPreviewSprites[item.PreviewImageUrl], scaleToFit: true); } else { - new GUIImage(new RectTransform(new Point(headerArea.Rect.Width, headerArea.Rect.Height), headerArea.RectTransform), SteamManager.Instance.DefaultPreviewImage, scaleToFit: true); + new GUIImage(new RectTransform(Vector2.One, headerArea.RectTransform), SteamManager.Instance.DefaultPreviewImage, scaleToFit: true); } - + var descriptionContainer = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.2f), content.RectTransform)) { ScrollBarVisible = true }; //spacing @@ -944,21 +948,25 @@ namespace Barotrauma tagBtn.TextBlock.AutoScale = true; tagBtn.Color *= 0.5f; tagBtn.SelectedColor = Color.LightGreen; + tagBtn.HoverColor = Color.Lerp(tagBtn.HoverColor, Color.LightGreen, 0.5f); tagBtn.Selected = itemEditor.Tags.Any(t => t.ToLowerInvariant() == tag); + Color defaultTextColor = tagBtn.TextColor; + tagBtn.TextColor = tagBtn.Selected ? Color.LightGreen : defaultTextColor; + tagBtn.OnClicked = (btn, userdata) => { if (!tagBtn.Selected) { if (!itemEditor.Tags.Any(t => t.ToLowerInvariant() == tag)) { itemEditor.Tags.Add(tagBtn.Text); } tagBtn.Selected = true; - tagBtn.TextBlock.TextColor = Color.LightGreen; + tagBtn.TextColor = Color.LightGreen; } else { itemEditor.Tags.RemoveAll(t => t.ToLowerInvariant() == tagBtn.Text.ToLowerInvariant()); tagBtn.Selected = false; - tagBtn.TextBlock.TextColor = tagBtn.TextColor; + tagBtn.TextColor = defaultTextColor; } return true; }; diff --git a/Barotrauma/BarotraumaClient/Source/Screens/SubEditorScreen.cs b/Barotrauma/BarotraumaClient/Source/Screens/SubEditorScreen.cs index d89cd95ad..31a310ecb 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/SubEditorScreen.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/SubEditorScreen.cs @@ -71,6 +71,12 @@ namespace Barotrauma private Color primaryColor = new Color(12, 14, 15, 190); private Color secondaryColor = new Color(12, 14, 15, 215); + private const int submarineNameLimit = 30; + private GUITextBlock submarineNameCharacterCount; + + private const int submarineDescriptionLimit = 500; + private GUITextBlock submarineDescriptionCharacterCount; + public override Camera Cam { get { return cam; } @@ -346,7 +352,6 @@ namespace Barotrauma UseGridLayout = true, CheckSelected = MapEntityPrefab.GetSelected }; - UpdateEntityList(); //empty guiframe as a separator new GUIFrame(new RectTransform(new Vector2(1.0f, 0.02f), paddedLeftPanel.RectTransform), style: null); @@ -603,14 +608,16 @@ namespace Barotrauma } - entityList.Content.RectTransform.SortChildren((i1, i2) => + entityList.Content.RectTransform.SortChildren((i1, i2) => (i1.GUIComponent.UserData as MapEntityPrefab).Name.CompareTo((i2.GUIComponent.UserData as MapEntityPrefab).Name)); } - + public override void Select() { base.Select(); + UpdateEntityList(); + foreach (MapEntityPrefab prefab in MapEntityPrefab.List) { prefab.sprite?.EnsureLazyLoaded(); @@ -950,32 +957,60 @@ namespace Barotrauma OnClicked = (btn, userdata) => { if (GUI.MouseOn == btn || GUI.MouseOn == btn.TextBlock) saveFrame = null; return true; } }; - var innerFrame = new GUIFrame(new RectTransform(new Vector2(0.4f, 0.45f), saveFrame.RectTransform, Anchor.Center) { MinSize = new Point(750, 400) }); + var innerFrame = new GUIFrame(new RectTransform(new Vector2(0.4f, 0.5f), saveFrame.RectTransform, Anchor.Center) { MinSize = new Point(750, 400) }); var paddedSaveFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), innerFrame.RectTransform, Anchor.Center)) { Stretch = true, RelativeSpacing = 0.02f }; //var header = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedSaveFrame.RectTransform), TextManager.Get("SaveSubDialogHeader"), font: GUI.LargeFont); var columnArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.9f), paddedSaveFrame.RectTransform), isHorizontal: true) { Stretch = true }; - var leftColumn = new GUILayoutGroup(new RectTransform(new Vector2(0.55f, 1.0f), columnArea.RectTransform)) { RelativeSpacing = 0.02f, Stretch = true }; + var leftColumn = new GUILayoutGroup(new RectTransform(new Vector2(0.55f, 1.0f), columnArea.RectTransform)) { RelativeSpacing = 0.01f, Stretch = true }; var rightColumn = new GUILayoutGroup(new RectTransform(new Vector2(0.42f, 1.0f), columnArea.RectTransform)) { RelativeSpacing = 0.02f, Stretch = true }; // left column ----------------------------------------------------------------------- - var saveSubLabel = new GUITextBlock(new RectTransform(new Vector2(0.4f, 0.03f), leftColumn.RectTransform), + var nameHeaderGroup = new GUILayoutGroup(new RectTransform(new Vector2(.975f, 0.03f), leftColumn.RectTransform), true); + var saveSubLabel = new GUITextBlock(new RectTransform(new Vector2(.5f, 1f), nameHeaderGroup.RectTransform), TextManager.Get("SaveSubDialogName")); - nameBox = new GUITextBox(new RectTransform(new Vector2(0.65f, 0.05f), leftColumn.RectTransform)) + submarineNameCharacterCount = new GUITextBlock(new RectTransform(new Vector2(.5f, 1f), nameHeaderGroup.RectTransform), string.Empty, textAlignment: Alignment.TopRight); + + nameBox = new GUITextBox(new RectTransform(new Vector2(.95f, 0.05f), leftColumn.RectTransform)) { OnEnterPressed = ChangeSubName, Text = GetSubName() }; + nameBox.OnTextChanged += (textBox, text) => + { + if (text.Length > submarineNameLimit) + { + nameBox.Text = text.Substring(0, submarineNameLimit); + nameBox.Flash(Color.Red); + return true; + } - new GUITextBlock(new RectTransform(new Vector2(0.5f, 0.03f), leftColumn.RectTransform), TextManager.Get("SaveSubDialogDescription")); + submarineNameCharacterCount.Text = text.Length + " / " + submarineNameLimit; + return true; + }; + + submarineNameCharacterCount.Text = nameBox.Text.Length + " / " + submarineNameLimit; + + var descriptionHeaderGroup = new GUILayoutGroup(new RectTransform(new Vector2(.975f, 0.03f), leftColumn.RectTransform), true); + + new GUITextBlock(new RectTransform(new Vector2(0.5f, 1f), descriptionHeaderGroup.RectTransform), TextManager.Get("SaveSubDialogDescription")); + submarineDescriptionCharacterCount = new GUITextBlock(new RectTransform(new Vector2(.5f, 1f), descriptionHeaderGroup.RectTransform), string.Empty, textAlignment: Alignment.TopRight); var descriptionContainer = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.25f), leftColumn.RectTransform)); - descriptionBox = new GUITextBox(new RectTransform(Vector2.One, descriptionContainer.Content.RectTransform), font: GUI.SmallFont, wrap: true); + descriptionBox = new GUITextBox(new RectTransform(Vector2.One, descriptionContainer.Content.RectTransform, Anchor.Center), font: GUI.SmallFont, wrap: true); + descriptionBox.OnTextChanged += (textBox, text) => { + if (text.Length > submarineDescriptionLimit) + { + descriptionBox.Text = text.Substring(0, submarineDescriptionLimit); + descriptionBox.Flash(Color.Red); + return true; + } + Vector2 textSize = textBox.Font.MeasureString(descriptionBox.WrappedText); textBox.RectTransform.NonScaledSize = new Point(textBox.RectTransform.NonScaledSize.X, Math.Max(descriptionContainer.Rect.Height, (int)textSize.Y + 10)); descriptionContainer.UpdateScrollBarSize(); @@ -984,6 +1019,7 @@ namespace Barotrauma return true; }; descriptionBox.Text = Submarine.MainSub == null ? "" : Submarine.MainSub.Description; + submarineDescriptionCharacterCount.Text = descriptionBox.Text.Length + " / " + submarineDescriptionLimit; var crewSizeArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.03f), leftColumn.RectTransform), isHorizontal: true) { AbsoluteSpacing = 5 }; @@ -1493,8 +1529,21 @@ namespace Barotrauma private void TryDeleteSub(Submarine sub) { - if (sub == null) return; - + if (sub == null) { return; } + + //if the sub is included in a content package that only defines that one sub, + //delete the content package as well + ContentPackage subPackage = null; + foreach (ContentPackage cp in ContentPackage.List) + { + if (cp.Files.Count == 1 && Path.GetFullPath(cp.Files[0].Path) == Path.GetFullPath(sub.FilePath)) + { + subPackage = cp; + break; + } + } + subPackage?.Delete(); + var msgBox = new GUIMessageBox( TextManager.Get("DeleteDialogLabel"), TextManager.GetWithVariable("DeleteDialogQuestion", "[file]", sub.Name), @@ -1524,7 +1573,6 @@ namespace Barotrauma if (CharacterMode) SetCharacterMode(false); if (WiringMode) SetWiringMode(false); - saveFrame = null; loadFrame = null; @@ -1754,7 +1802,9 @@ namespace Barotrauma { textBox.UserData = text; } - + + submarineDescriptionCharacterCount.Text = text.Length + " / " + submarineDescriptionLimit; + return true; } @@ -2131,6 +2181,10 @@ namespace Barotrauma { dummyCharacter.SelectedConstruction.AddToGUIUpdateList(); } + else if (WiringMode && MapEntity.SelectedList.Count == 1 && MapEntity.SelectedList[0] is Item item && item.GetComponent() != null) + { + MapEntity.SelectedList[0].AddToGUIUpdateList(); + } } else { @@ -2290,9 +2344,9 @@ namespace Barotrauma dummyCharacter.SelectedConstruction = null; }*/ } - else if (MapEntity.FilteredSelectedList.Count == 1) + else if (MapEntity.SelectedList.Count == 1) { - (MapEntity.FilteredSelectedList[0] as Item)?.UpdateHUD(cam, dummyCharacter, (float)deltaTime); + (MapEntity.SelectedList[0] as Item)?.UpdateHUD(cam, dummyCharacter, (float)deltaTime); } CharacterHUD.Update((float)deltaTime, dummyCharacter, cam); diff --git a/Barotrauma/BarotraumaClient/Source/Sounds/SoundPlayer.cs b/Barotrauma/BarotraumaClient/Source/Sounds/SoundPlayer.cs index 515bc987a..623535393 100644 --- a/Barotrauma/BarotraumaClient/Source/Sounds/SoundPlayer.cs +++ b/Barotrauma/BarotraumaClient/Source/Sounds/SoundPlayer.cs @@ -203,6 +203,8 @@ namespace Barotrauma public static void Update(float deltaTime) { + if (!Initialized) { return; } + UpdateMusic(deltaTime); if (startUpSound != null && !GameMain.SoundManager.IsPlaying(startUpSound)) @@ -629,7 +631,7 @@ namespace Barotrauma { if (OverrideMusicType != null) return OverrideMusicType; - if (Screen.Selected != GameMain.GameScreen) + if (Screen.Selected == null || Screen.Selected != GameMain.GameScreen) { return "menu"; } @@ -644,7 +646,7 @@ namespace Barotrauma Submarine targetSubmarine = Character.Controlled?.Submarine; if ((targetSubmarine != null && targetSubmarine.AtDamageDepth) || - (Screen.Selected == GameMain.GameScreen && GameMain.GameScreen.Cam.Position.Y < SubmarineBody.DamageDepth)) + (GameMain.GameScreen != null && Screen.Selected == GameMain.GameScreen && GameMain.GameScreen.Cam.Position.Y < SubmarineBody.DamageDepth)) { return "deep"; } diff --git a/Barotrauma/BarotraumaClient/Source/StatusEffects/StatusEffect.cs b/Barotrauma/BarotraumaClient/Source/StatusEffects/StatusEffect.cs index 4198d21b7..9b9bd5e70 100644 --- a/Barotrauma/BarotraumaClient/Source/StatusEffects/StatusEffect.cs +++ b/Barotrauma/BarotraumaClient/Source/StatusEffects/StatusEffect.cs @@ -54,7 +54,9 @@ namespace Barotrauma partial void ApplyProjSpecific(float deltaTime, Entity entity, List targets, Hull hull) { - if (entity != null && sounds.Count > 0) + if (entity == null) { return; } + + if (sounds.Count > 0) { if (soundChannel == null || !soundChannel.IsPlaying) { @@ -95,22 +97,20 @@ namespace Barotrauma } } - if (entity != null) + foreach (ParticleEmitter emitter in particleEmitters) { - foreach (ParticleEmitter emitter in particleEmitters) + float angle = 0.0f; + if (emitter.Prefab.CopyEntityAngle) { - float angle = 0.0f; - if (emitter.Prefab.CopyEntityAngle) + if (entity is Item item && item.body != null) { - if (entity is Item it) - { - angle = it.body == null ? 0.0f : it.body.Rotation; - } + angle = item.body.Rotation + ((item.body.Dir > 0.0f) ? 0.0f : MathHelper.Pi); } - - emitter.Emit(deltaTime, entity.WorldPosition, hull, angle); } + + emitter.Emit(deltaTime, entity.WorldPosition, hull, angle); } + } static partial void UpdateAllProjSpecific(float deltaTime) diff --git a/Barotrauma/BarotraumaClient/soft_oal_x64.dll b/Barotrauma/BarotraumaClient/soft_oal_x64.dll index 1368f8380..2013cc5e8 100644 Binary files a/Barotrauma/BarotraumaClient/soft_oal_x64.dll and b/Barotrauma/BarotraumaClient/soft_oal_x64.dll differ diff --git a/Barotrauma/BarotraumaServer/Properties/AssemblyInfo.cs b/Barotrauma/BarotraumaServer/Properties/AssemblyInfo.cs index a366a224d..4e3b8e5fc 100644 --- a/Barotrauma/BarotraumaServer/Properties/AssemblyInfo.cs +++ b/Barotrauma/BarotraumaServer/Properties/AssemblyInfo.cs @@ -31,5 +31,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("0.9.1.0")] +[assembly: AssemblyVersion("0.9.200.0")] [assembly: AssemblyFileVersion("0.9.1.0")] diff --git a/Barotrauma/BarotraumaServer/Server.csproj b/Barotrauma/BarotraumaServer/Server.csproj index 9d9e7370d..85abb98f3 100644 --- a/Barotrauma/BarotraumaServer/Server.csproj +++ b/Barotrauma/BarotraumaServer/Server.csproj @@ -212,6 +212,7 @@ + diff --git a/Barotrauma/BarotraumaServer/Source/Camera.cs b/Barotrauma/BarotraumaServer/Source/Camera.cs index 1e21b40e6..9091bdaca 100644 --- a/Barotrauma/BarotraumaServer/Source/Camera.cs +++ b/Barotrauma/BarotraumaServer/Source/Camera.cs @@ -120,12 +120,16 @@ namespace Barotrauma get { return targetPos; } set { targetPos = value; } } + + public Vector2 GetPosition() + { + return position; + } // Auxiliary function to move the camera public void Translate(Vector2 amount) { position += amount; - } public void UpdateTransform(bool interpolate = true, bool clampPos = false) diff --git a/Barotrauma/BarotraumaServer/Source/Characters/Character.cs b/Barotrauma/BarotraumaServer/Source/Characters/Character.cs index 936802e51..8ad7eac64 100644 --- a/Barotrauma/BarotraumaServer/Source/Characters/Character.cs +++ b/Barotrauma/BarotraumaServer/Source/Characters/Character.cs @@ -11,22 +11,9 @@ namespace Barotrauma { } - partial void AdjustKarma(Character attacker, AttackResult attackResult) + partial void OnAttackedProjSpecific(Character attacker, AttackResult attackResult) { - if (attacker == null) return; - - Client attackerClient = GameMain.Server.ConnectedClients.Find(c => c.Character == attacker); - if (attackerClient == null) return; - - Client targetClient = GameMain.Server.ConnectedClients.Find(c => c.Character == this); - if (targetClient != null) - { - if (attacker.TeamID == TeamID) - { - attackerClient.Karma -= attackResult.Damage * 0.01f; - if (CharacterHealth.MaxVitality <= CharacterHealth.MinVitality) attackerClient.Karma = 0.0f; - } - } + GameMain.Server.KarmaManager.OnCharacterHealthChanged(this, attacker, attackResult.Damage, attackResult.Afflictions); } partial void KillProjSpecific(CauseOfDeathType causeOfDeath, Affliction causeOfDeathAffliction) diff --git a/Barotrauma/BarotraumaServer/Source/Characters/CharacterNetworking.cs b/Barotrauma/BarotraumaServer/Source/Characters/CharacterNetworking.cs index 9933c2e45..9cd263d20 100644 --- a/Barotrauma/BarotraumaServer/Source/Characters/CharacterNetworking.cs +++ b/Barotrauma/BarotraumaServer/Source/Characters/CharacterNetworking.cs @@ -21,29 +21,25 @@ namespace Barotrauma { if (!Enabled) { return 1000.0f; } - if (recipient.Character == null || recipient.Character.IsDead) - { - return 0.2f; - } - else - { - float distance = Vector2.Distance(recipient.Character.WorldPosition, WorldPosition); - float priority = 1.0f - MathUtils.InverseLerp( - NetConfig.HighPrioCharacterPositionUpdateDistance, - NetConfig.LowPrioCharacterPositionUpdateDistance, - distance); + Vector2 comparePosition = recipient.SpectatePos == null ? recipient.Character.WorldPosition : recipient.SpectatePos.Value; - float interval = MathHelper.Lerp( - NetConfig.LowPrioCharacterPositionUpdateInterval, - NetConfig.HighPrioCharacterPositionUpdateInterval, - priority); + float distance = Vector2.Distance(comparePosition, WorldPosition); + float priority = 1.0f - MathUtils.InverseLerp( + NetConfig.HighPrioCharacterPositionUpdateDistance, + NetConfig.LowPrioCharacterPositionUpdateDistance, + distance); - if (IsDead) - { - interval = Math.Max(interval * 2, 0.1f); - } - return interval; + float interval = MathHelper.Lerp( + NetConfig.LowPrioCharacterPositionUpdateInterval, + NetConfig.HighPrioCharacterPositionUpdateInterval, + priority); + + if (IsDead) + { + interval = Math.Max(interval * 2, 0.1f); } + + return interval; } partial void UpdateNetInput() diff --git a/Barotrauma/BarotraumaServer/Source/DebugConsole.cs b/Barotrauma/BarotraumaServer/Source/DebugConsole.cs index bfb5fa573..b4f62239e 100644 --- a/Barotrauma/BarotraumaServer/Source/DebugConsole.cs +++ b/Barotrauma/BarotraumaServer/Source/DebugConsole.cs @@ -86,7 +86,8 @@ namespace Barotrauma int inputLines = Math.Max((int)Math.Ceiling(input.Length / (float)Console.WindowWidth), 1); Console.CursorLeft = 0; Console.Write(new string(' ', consoleWidth)); - Console.CursorTop -= inputLines; Console.CursorLeft = 0; + Console.CursorTop = Math.Max(Console.CursorTop - inputLines, 0); + Console.CursorLeft = 0; while (queuedMessages.Count > 0) { ColoredText msg = queuedMessages.Dequeue(); @@ -121,9 +122,9 @@ namespace Barotrauma switch (key.Key) { case ConsoleKey.Enter: - lock (DebugConsole.QueuedCommands) + lock (QueuedCommands) { - DebugConsole.QueuedCommands.Add(input); + QueuedCommands.Add(input); } input = ""; memoryIndex = -1; @@ -219,7 +220,15 @@ namespace Barotrauma private static void AssignOnClientRequestExecute(string names, Action onClientRequestExecute) { - commands.First(c => c.names.Intersect(names.Split('|')).Count() > 0).OnClientRequestExecute = onClientRequestExecute; + var matchingCommand = commands.Find(c => c.names.Intersect(names.Split('|')).Count() > 0); + if (matchingCommand == null) + { + throw new Exception("AssignOnClientRequestExecute failed. Command matching the name(s) \"" + names + "\" not found."); + } + else + { + matchingCommand.OnClientRequestExecute = onClientRequestExecute; + } } private static void InitProjectSpecific() @@ -568,10 +577,8 @@ namespace Barotrauma NewMessage(client.Name + " has the following permissions:", Color.White); foreach (ClientPermissions permission in Enum.GetValues(typeof(ClientPermissions))) { - if (permission == ClientPermissions.None || !client.HasPermission(permission)) continue; - System.Reflection.FieldInfo fi = typeof(ClientPermissions).GetField(permission.ToString()); - DescriptionAttribute[] attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false); - NewMessage(" - " + attributes[0].Description, Color.White); + if (permission == ClientPermissions.None || !client.HasPermission(permission)) { continue; } + NewMessage(" - " + TextManager.Get("ClientPermission." + permission), Color.White); } if (client.HasPermission(ClientPermissions.ConsoleCommands)) { @@ -590,12 +597,100 @@ namespace Barotrauma } }); - /*AssignOnExecute("togglekarma", (string[] args) => + AssignOnExecute("togglekarma", (string[] args) => { - return; if (GameMain.Server == null) return; GameMain.Server.ServerSettings.KarmaEnabled = !GameMain.Server.ServerSettings.KarmaEnabled; - });*/ + NewMessage(GameMain.Server.ServerSettings.KarmaEnabled ? "Karma system enabled." : "Karma system disabled.", Color.LightGreen); + }); + + AssignOnExecute("resetkarma", (string[] args) => + { + if (GameMain.Server == null || args.Length == 0) return; + var client = GameMain.Server.ConnectedClients.Find(c => c.Name == args[0]); + if (client == null) + { + ThrowError("Client \"" + args[0] + "\" not found."); + return; + } + client.Karma = 100.0f; + NewMessage("Set the karma of the client \"" + args[0] + "\" to 100.", Color.LightGreen); + }); + AssignOnClientRequestExecute("resetkarma", (Client client, Vector2 cursorWorldPos, string[] args) => + { + if (GameMain.Server == null || args.Length == 0) return; + var targetClient = GameMain.Server.ConnectedClients.Find(c => c.Name == args[0]); + if (targetClient == null) + { + ThrowError("Client \"" + args[0] + "\" not found."); + return; + } + targetClient.Karma = 100.0f; + GameMain.Server.SendDirectChatMessage("Set the karma of the client \"" + args[0] + "\" to 100.", client); + NewMessage("Client \"" + client.Name + "\" set the karma of \"" + args[0] + "\" to 100.", Color.LightGreen); + }); + + AssignOnExecute("setkarma", (string[] args) => + { + if (GameMain.Server == null || args.Length < 2) return; + var client = GameMain.Server.ConnectedClients.Find(c => c.Name == args[0]); + if (client == null) + { + ThrowError("Client \"" + args[0] + "\" not found."); + return; + } + if (!float.TryParse(args[1], out float karmaValue) || karmaValue < 0.0f || karmaValue > 100.0f) + { + ThrowError("\"" + args[1] + "\" is not a valid karma value. You need to enter a number between 0-100."); + return; + } + client.Karma = karmaValue; + NewMessage("Set the karma of the client \"" + args[0] + "\" to " + karmaValue + ".", Color.LightGreen); + }); + AssignOnClientRequestExecute("setkarma", (Client client, Vector2 cursorWorldPos, string[] args) => + { + if (GameMain.Server == null || args.Length < 2) return; + var targetClient = GameMain.Server.ConnectedClients.Find(c => c.Name == args[0]); + if (targetClient == null) + { + GameMain.Server.SendDirectChatMessage("Client \"" + args[0] + "\" not found.", client); + return; + } + if (!float.TryParse(args[1], out float karmaValue) || karmaValue < 0.0f || karmaValue > 100.0f) + { + GameMain.Server.SendDirectChatMessage("\"" + args[1] + "\" is not a valid karma value. You need to enter a number between 0-100.", client); + return; + } + targetClient.Karma = karmaValue; + GameMain.Server.SendDirectChatMessage("Set the karma of the client \"" + args[0] + "\" to " + karmaValue + ".", client); + NewMessage("Client \"" + client.Name + "\" set the karma of \"" + args[0] + "\" to " + karmaValue + ".", Color.LightGreen); + }); + + AssignOnExecute("showkarma", (string[] args) => + { + if (GameMain.Server == null) return; + NewMessage("***************", Color.Cyan); + foreach (Client c in GameMain.Server.ConnectedClients) + { + NewMessage("- " + c.ID.ToString() + ": " + c.Name + (c.Character != null ? " playing " + c.Character.LogName : "") + ", " + c.Karma, Color.Cyan); + } + NewMessage("***************", Color.Cyan); + }); + AssignOnClientRequestExecute("showkarma", (Client client, Vector2 cursorWorldPos, string[] args) => + { + GameMain.Server.SendConsoleMessage("***************", client); + foreach (Client c in GameMain.Server.ConnectedClients) + { + GameMain.Server.SendConsoleMessage("- " + c.ID.ToString() + ": " + c.Name + (c.Character != null ? " playing " + c.Character.LogName : "") + ", " + c.Karma, client); + } + GameMain.Server.SendConsoleMessage("***************", client); + }); + AssignOnExecute("togglekarmatestmode|karmatestmode", (string[] args) => + { + if (GameMain.Server?.KarmaManager == null) return; + GameMain.Server.KarmaManager.TestMode = !GameMain.Server.KarmaManager.TestMode; + NewMessage(GameMain.Server.KarmaManager.TestMode ? "Karma test mode enabled." : "Karma test mode disabled.", Color.LightGreen); + }); AssignOnExecute("banip", (string[] args) => { @@ -989,6 +1084,16 @@ namespace Barotrauma }; })); + + AssignOnExecute("respawnnow", (string[] args) => + { + if (GameMain.Server?.RespawnManager == null) { return; } + if (GameMain.Server.RespawnManager.CurrentState != RespawnManager.State.Transporting) + { + GameMain.Server.RespawnManager.ForceRespawn(); + } + }); + commands.Add(new Command("startgame|startround|start", "start/startgame/startround: Start a new round.", (string[] args) => { if (Screen.Selected == GameMain.GameScreen) return; @@ -1523,7 +1628,11 @@ namespace Barotrauma "showperm", (Client senderClient, Vector2 cursorWorldPos, string[] args) => { - if (args.Length < 2) return; + if (args.Length < 1) + { + GameMain.Server.SendConsoleMessage("showperm [id]: Shows the current administrative permissions of the client with the specified client ID.", senderClient); + return; + } int.TryParse(args[0], out int id); var client = GameMain.Server.ConnectedClients.Find(c => c.ID == id); @@ -1542,10 +1651,8 @@ namespace Barotrauma GameMain.Server.SendConsoleMessage(client.Name + " has the following permissions:", senderClient); foreach (ClientPermissions permission in Enum.GetValues(typeof(ClientPermissions))) { - if (permission == ClientPermissions.None || !client.HasPermission(permission)) continue; - System.Reflection.FieldInfo fi = typeof(ClientPermissions).GetField(permission.ToString()); - DescriptionAttribute[] attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false); - GameMain.Server.SendConsoleMessage(" - " + attributes[0].Description, senderClient); + if (permission == ClientPermissions.None || !client.HasPermission(permission)) { continue; } + GameMain.Server.SendConsoleMessage(" - " + TextManager.Get("ClientPermission." + permission), senderClient); } if (client.HasPermission(ClientPermissions.ConsoleCommands)) { diff --git a/Barotrauma/BarotraumaServer/Source/Events/Missions/CombatMission.cs b/Barotrauma/BarotraumaServer/Source/Events/Missions/CombatMission.cs index d7646ce37..e0fa06f4c 100644 --- a/Barotrauma/BarotraumaServer/Source/Events/Missions/CombatMission.cs +++ b/Barotrauma/BarotraumaServer/Source/Events/Missions/CombatMission.cs @@ -8,6 +8,9 @@ namespace Barotrauma { private bool[] teamDead = new bool[2]; + private bool initialized = false; + private int state = 0; + public override string Description { get diff --git a/Barotrauma/BarotraumaServer/Source/Items/Components/Door.cs b/Barotrauma/BarotraumaServer/Source/Items/Components/Door.cs index cb4ea37e4..0dd7a2a65 100644 --- a/Barotrauma/BarotraumaServer/Source/Items/Components/Door.cs +++ b/Barotrauma/BarotraumaServer/Source/Items/Components/Door.cs @@ -5,7 +5,7 @@ namespace Barotrauma.Items.Components { partial class Door { - partial void SetState(bool open, bool isNetworkMessage, bool sendNetworkMessage) + partial void SetState(bool open, bool isNetworkMessage, bool sendNetworkMessage, bool forcedOpen) { if (isStuck || isOpen == open) { @@ -18,7 +18,7 @@ namespace Barotrauma.Items.Components if (sendNetworkMessage) { - item.CreateServerEvent(this); + GameMain.Server.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ComponentState, item.GetComponentIndex(this), forcedOpen }); } } @@ -27,6 +27,7 @@ namespace Barotrauma.Items.Components base.ServerWrite(msg, c, extraData); msg.Write(isOpen); + msg.Write(extraData.Length == 3 ? (bool)extraData[2] : false); //forced open msg.WriteRangedSingle(stuck, 0.0f, 100.0f, 8); } } diff --git a/Barotrauma/BarotraumaServer/Source/Items/Components/Machines/Steering.cs b/Barotrauma/BarotraumaServer/Source/Items/Components/Machines/Steering.cs index 62a2b71d7..2a777d279 100644 --- a/Barotrauma/BarotraumaServer/Source/Items/Components/Machines/Steering.cs +++ b/Barotrauma/BarotraumaServer/Source/Items/Components/Machines/Steering.cs @@ -7,5 +7,11 @@ namespace Barotrauma.Items.Components public bool MaintainPos; public bool LevelStartSelected; public bool LevelEndSelected; + + public bool UnsentChanges + { + get { return unsentChanges; } + set { unsentChanges = value; } + } } } diff --git a/Barotrauma/BarotraumaServer/Source/Items/Components/Repairable.cs b/Barotrauma/BarotraumaServer/Source/Items/Components/Repairable.cs index 7bf36f0f8..e0421a39f 100644 --- a/Barotrauma/BarotraumaServer/Source/Items/Components/Repairable.cs +++ b/Barotrauma/BarotraumaServer/Source/Items/Components/Repairable.cs @@ -5,12 +5,6 @@ namespace Barotrauma.Items.Components { partial class Repairable : ItemComponent, IServerSerializable, IClientSerializable { - void InitProjSpecific() - { - //let the clients know the initial deterioration delay - item.CreateServerEvent(this); - } - public void ServerRead(ClientNetObject type, NetBuffer msg, Client c) { if (c.Character == null) return; diff --git a/Barotrauma/BarotraumaServer/Source/Items/Components/Signal/ConnectionPanel.cs b/Barotrauma/BarotraumaServer/Source/Items/Components/Signal/ConnectionPanel.cs index 789bf4eaf..cb6f58536 100644 --- a/Barotrauma/BarotraumaServer/Source/Items/Components/Signal/ConnectionPanel.cs +++ b/Barotrauma/BarotraumaServer/Source/Items/Components/Signal/ConnectionPanel.cs @@ -33,6 +33,18 @@ namespace Barotrauma.Items.Components } } + + List clientSideDisconnectedWires = new List(); + ushort disconnectedWireCount = msg.ReadUInt16(); + for (int i = 0; i < disconnectedWireCount; i++) + { + ushort wireId = msg.ReadUInt16(); + if (!(Entity.FindEntityByID(wireId) is Item wireItem)) { continue; } + Wire wireComponent = wireItem.GetComponent(); + if (wireComponent == null) { continue; } + clientSideDisconnectedWires.Add(wireComponent); + } + //don't allow rewiring locked panels if (Locked || !GameMain.NetworkMember.ServerSettings.AllowRewiring) { return; } @@ -47,7 +59,7 @@ namespace Barotrauma.Items.Components { //wire not found in any of the connections yet (client is trying to connect a new wire) // -> we need to check if the client has access to it - if (!Connections.Any(connection => connection.Wires.Contains(wire))) + if (!Connections.Any(connection => connection.Wires.Contains(wire)) && !DisconnectedWires.Contains(wire)) { if (!wire.Item.CanClientAccess(c)) { return; } } @@ -75,11 +87,19 @@ namespace Barotrauma.Items.Components } existingWire.RemoveConnection(item); + item.GetComponent()?.DisconnectedWires.Add(existingWire); + + GameMain.Server.KarmaManager.OnWireDisconnected(c.Character, existingWire); if (existingWire.Connections[0] == null && existingWire.Connections[1] == null) { GameServer.Log(c.Character.LogName + " disconnected a wire from " + Connections[i].Item.Name + " (" + Connections[i].Name + ")", ServerLog.MessageType.ItemInteraction); + + if (!clientSideDisconnectedWires.Contains(existingWire)) + { + existingWire.Item.Drop(c.Character); + } } else if (existingWire.Connections[0] != null) { @@ -89,24 +109,24 @@ namespace Barotrauma.Items.Components //wires that are not in anyone's inventory (i.e. not currently being rewired) //can never be connected to only one connection // -> the client must have dropped the wire from the connection panel - if (existingWire.Item.ParentInventory == null && !wires.Any(w => w.Contains(existingWire))) + /*if (existingWire.Item.ParentInventory == null && !wires.Any(w => w.Contains(existingWire))) { //let other clients know the item was also disconnected from the other connection existingWire.Connections[0].Item.CreateServerEvent(existingWire.Connections[0].Item.GetComponent()); existingWire.Item.Drop(c.Character); - } + }*/ } else if (existingWire.Connections[1] != null) { GameServer.Log(c.Character.LogName + " disconnected a wire from " + Connections[i].Item.Name + " (" + Connections[i].Name + ") to " + existingWire.Connections[1].Item.Name + " (" + existingWire.Connections[1].Name + ")", ServerLog.MessageType.ItemInteraction); - if (existingWire.Item.ParentInventory == null && !wires.Any(w => w.Contains(existingWire))) + /*if (existingWire.Item.ParentInventory == null && !wires.Any(w => w.Contains(existingWire))) { //let other clients know the item was also disconnected from the other connection existingWire.Connections[1].Item.CreateServerEvent(existingWire.Connections[1].Item.GetComponent()); existingWire.Item.Drop(c.Character); - } + }*/ } Connections[i].SetWire(j, null); @@ -114,6 +134,17 @@ namespace Barotrauma.Items.Components } } + foreach (Wire disconnectedWire in DisconnectedWires.ToList()) + { + if (disconnectedWire.Connections[0] == null && + disconnectedWire.Connections[1] == null && + !clientSideDisconnectedWires.Contains(disconnectedWire)) + { + disconnectedWire.Item.Drop(c.Character); + GameServer.Log(c.Character.LogName + " dropped " + disconnectedWire.Name, ServerLog.MessageType.Inventory); + } + } + //go through new wires for (int i = 0; i < Connections.Count; i++) { diff --git a/Barotrauma/BarotraumaServer/Source/Items/Item.cs b/Barotrauma/BarotraumaServer/Source/Items/Item.cs index 8e29b0a21..2092cba17 100644 --- a/Barotrauma/BarotraumaServer/Source/Items/Item.cs +++ b/Barotrauma/BarotraumaServer/Source/Items/Item.cs @@ -186,6 +186,15 @@ namespace Barotrauma case NetEntityEvent.Type.ChangeProperty: ReadPropertyChange(msg, true, c); break; + case NetEntityEvent.Type.Combine: + UInt16 combineTargetID = msg.ReadUInt16(); + Item combineTarget = FindEntityByID(combineTargetID) as Item; + if (combineTarget == null || !c.Character.CanInteractWith(this) || !c.Character.CanInteractWith(combineTarget)) + { + return; + } + Combine(combineTarget); + break; } } diff --git a/Barotrauma/BarotraumaServer/Source/Map/Structure.cs b/Barotrauma/BarotraumaServer/Source/Map/Structure.cs index 96d04a1aa..bdf6d9748 100644 --- a/Barotrauma/BarotraumaServer/Source/Map/Structure.cs +++ b/Barotrauma/BarotraumaServer/Source/Map/Structure.cs @@ -5,24 +5,9 @@ namespace Barotrauma { partial class Structure : MapEntity, IDamageable, IServerSerializable, ISerializableEntity { - partial void AdjustKarma(IDamageable attacker, float amount) + partial void OnHealthChangedProjSpecific(Character attacker, float damageAmount) { - if (GameMain.Server != null) - { - if (Submarine == null) return; - if (attacker == null) return; - if (attacker is Character attackerCharacter) - { - Client attackerClient = GameMain.Server.ConnectedClients.Find(c => c.Character == attackerCharacter); - if (attackerClient != null) - { - if (attackerCharacter.TeamID == Submarine.TeamID) - { - attackerClient.Karma -= amount * 0.001f; - } - } - } - } + GameMain.Server.KarmaManager.OnStructureHealthChanged(this, attacker, damageAmount); } public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null) diff --git a/Barotrauma/BarotraumaServer/Source/Networking/ChatMessage.cs b/Barotrauma/BarotraumaServer/Source/Networking/ChatMessage.cs index 4b4b86e42..a063fb756 100644 --- a/Barotrauma/BarotraumaServer/Source/Networking/ChatMessage.cs +++ b/Barotrauma/BarotraumaServer/Source/Networking/ChatMessage.cs @@ -83,8 +83,9 @@ namespace Barotrauma.Networking if (similarity + c.ChatSpamSpeed > 5.0f && !isOwner) { - c.ChatSpamCount++; + GameMain.Server.KarmaManager.OnSpamFilterTriggered(c); + c.ChatSpamCount++; if (c.ChatSpamCount > 3) { //kick for spamming too much diff --git a/Barotrauma/BarotraumaServer/Source/Networking/Client.cs b/Barotrauma/BarotraumaServer/Source/Networking/Client.cs index 611793855..8a91ceaef 100644 --- a/Barotrauma/BarotraumaServer/Source/Networking/Client.cs +++ b/Barotrauma/BarotraumaServer/Source/Networking/Client.cs @@ -65,20 +65,19 @@ namespace Barotrauma.Networking public bool SpectateOnly; - private float karma = 1.0f; + private float karma = 100.0f; public float Karma { get { - if (GameMain.Server == null) return 1.0f; - if (!GameMain.Server.ServerSettings.KarmaEnabled) return 1.0f; + if (GameMain.Server == null || !GameMain.Server.ServerSettings.KarmaEnabled) { return 100.0f; } + if (HasPermission(ClientPermissions.KarmaImmunity)) { return 100.0f; } return karma; } set { - if (GameMain.Server == null) return; - if (!GameMain.Server.ServerSettings.KarmaEnabled) return; - karma = Math.Min(Math.Max(value, 0.0f), 1.0f); + if (GameMain.Server == null || !GameMain.Server.ServerSettings.KarmaEnabled) { return; } + karma = Math.Min(Math.Max(value, 0.0f), 100.0f); } } diff --git a/Barotrauma/BarotraumaServer/Source/Networking/GameServer.cs b/Barotrauma/BarotraumaServer/Source/Networking/GameServer.cs index b3e1b9209..9bf787280 100644 --- a/Barotrauma/BarotraumaServer/Source/Networking/GameServer.cs +++ b/Barotrauma/BarotraumaServer/Source/Networking/GameServer.cs @@ -78,7 +78,7 @@ namespace Barotrauma.Networking { get { return entityEventManager; } } - + public TimeSpan UpdateInterval { get { return updateInterval; } @@ -109,7 +109,6 @@ namespace Barotrauma.Networking LastClientListUpdateID = 0; NetPeerConfiguration = new NetPeerConfiguration("barotrauma"); - NetPeerConfiguration.Port = port; Port = port; QueryPort = queryPort; @@ -119,12 +118,12 @@ namespace Barotrauma.Networking NetPeerConfiguration.EnableUPnP = true; } - serverSettings = new ServerSettings(name, port, queryPort, maxPlayers, isPublic, attemptUPnP); + serverSettings = new ServerSettings(this, name, port, queryPort, maxPlayers, isPublic, attemptUPnP); if (!string.IsNullOrEmpty(password)) { serverSettings.SetPassword(password); } - + NetPeerConfiguration.MaximumConnections = maxPlayers * 2; //double the lidgren connections for unauthenticated players NetPeerConfiguration.DisableMessageType(NetIncomingMessageType.DebugMessage | @@ -353,6 +352,7 @@ namespace Barotrauma.Networking unauthenticatedClients.RemoveAll(uc => uc.AuthTimer <= 0.0f); fileSender.Update(deltaTime); + KarmaManager.UpdateClients(ConnectedClients, deltaTime); if (serverSettings.VoiceChatEnabled) { @@ -361,7 +361,7 @@ namespace Barotrauma.Networking if (gameStarted) { - if (respawnManager != null) respawnManager.Update(deltaTime); + if (respawnManager != null) { respawnManager.Update(deltaTime); } entityEventManager.Update(connectedClients); @@ -415,25 +415,32 @@ namespace Barotrauma.Networking } } - if (isCrewDead && respawnManager == null) + float endRoundDelay = 1.0f; + if (serverSettings.AutoRestart && isCrewDead) + { + endRoundDelay = 5.0f; + endRoundTimer += deltaTime; + } + else if (serverSettings.EndRoundAtLevelEnd && subAtLevelEnd) + { + endRoundDelay = 5.0f; + endRoundTimer += deltaTime; + } + else if (isCrewDead && respawnManager == null) { if (endRoundTimer <= 0.0f) { SendChatMessage(TextManager.GetWithVariable("CrewDeadNoRespawns", "[time]", "60"), ChatMessageType.Server); } + endRoundDelay = 60.0f; endRoundTimer += deltaTime; } else { endRoundTimer = 0.0f; } - - //restart if all characters are dead or submarine is at the end of the level - if ((serverSettings.AutoRestart && isCrewDead) - || - (serverSettings.EndRoundAtLevelEnd && subAtLevelEnd) - || - (isCrewDead && respawnManager == null && endRoundTimer >= 60.0f)) + + if (endRoundTimer >= endRoundDelay) { if (serverSettings.AutoRestart && isCrewDead) { @@ -1029,6 +1036,9 @@ namespace Barotrauma.Networking case ClientNetObject.VOTE: serverSettings.Voting.ServerRead(inc, c); break; + case ClientNetObject.SPECTATING_POS: + c.SpectatePos = new Vector2(inc.ReadFloat(), inc.ReadFloat()); + break; default: return; } @@ -1346,11 +1356,20 @@ namespace Barotrauma.Networking foreach (Character character in Character.CharacterList) { if (!character.Enabled) continue; - if (c.Character != null && - Vector2.DistanceSquared(character.WorldPosition, c.Character.WorldPosition) >= - NetConfig.DisableCharacterDistSqr) + + if (c.SpectatePos == null) { - continue; + if (c.Character != null && Vector2.DistanceSquared(character.WorldPosition, c.Character.WorldPosition) >= NetConfig.DisableCharacterDistSqr) + { + continue; + } + } + else + { + if (Vector2.DistanceSquared(character.WorldPosition, c.SpectatePos.Value) >= NetConfig.DisableCharacterDistSqr) + { + continue; + } } float updateInterval = character.GetPositionUpdateInterval(c); @@ -2005,8 +2024,16 @@ namespace Barotrauma.Networking public void EndGame() { - if (!gameStarted) return; - Log("Ending the round...", ServerLog.MessageType.ServerMessage); + if (!gameStarted) { return; } + if (GameSettings.VerboseLogging) + { + Log("Ending the round...\n" + Environment.StackTrace, ServerLog.MessageType.ServerMessage); + + } + else + { + Log("Ending the round...", ServerLog.MessageType.ServerMessage); + } string endMessage = "The round has ended." + '\n'; @@ -2068,31 +2095,14 @@ namespace Barotrauma.Networking } } - CoroutineManager.StartCoroutine(EndCinematic(), "EndCinematic"); - - GameMain.NetLobbyScreen.RandomizeSettings(); - } - - public IEnumerable EndCinematic() - { - float endPreviewLength = 10.0f; - - var cinematic = new RoundEndCinematic(Submarine.MainSub, GameMain.GameScreen.Cam, endPreviewLength); - - do - { - yield return CoroutineStatus.Running; - } while (cinematic.Running); - Submarine.Unload(); entityEventManager.Clear(); - GameMain.NetLobbyScreen.Select(); Log("Round ended.", ServerLog.MessageType.ServerMessage); - yield return CoroutineStatus.Success; + GameMain.NetLobbyScreen.RandomizeSettings(); } - + public override void AddChatMessage(ChatMessage message) { if (string.IsNullOrEmpty(message.Text)) { return; } @@ -2257,6 +2267,7 @@ namespace Barotrauma.Networking previousPlayers.Add(previousPlayer); } previousPlayer.Name = client.Name; + previousPlayer.Karma = client.Karma; previousPlayer.KickVoters.Clear(); foreach (Client c in connectedClients) { @@ -2267,6 +2278,8 @@ namespace Barotrauma.Networking client.Dispose(); connectedClients.Remove(client); + KarmaManager.OnClientDisconnected(client); + UpdateVoteStatus(); SendChatMessage(msg, ChatMessageType.Server); @@ -3077,6 +3090,7 @@ namespace Barotrauma.Networking public string Name; public string IP; public UInt64 SteamID; + public float Karma; public readonly List KickVoters = new List(); public PreviousPlayer(Client c) diff --git a/Barotrauma/BarotraumaServer/Source/Networking/GameServerLogin.cs b/Barotrauma/BarotraumaServer/Source/Networking/GameServerLogin.cs index 78c9ec2b8..f45e1fc36 100644 --- a/Barotrauma/BarotraumaServer/Source/Networking/GameServerLogin.cs +++ b/Barotrauma/BarotraumaServer/Source/Networking/GameServerLogin.cs @@ -472,9 +472,10 @@ namespace Barotrauma.Networking unauthClient = null; ConnectedClients.Add(newClient); - var previousPlayer = previousPlayers.Find(p => p.MatchesClient(newClient)); + var previousPlayer = previousPlayers.Find(p => p.MatchesClient(newClient)); if (previousPlayer != null) { + newClient.Karma = previousPlayer.Karma; foreach (Client c in previousPlayer.KickVoters) { if (!connectedClients.Contains(c)) { continue; } diff --git a/Barotrauma/BarotraumaServer/Source/Networking/KarmaManager.cs b/Barotrauma/BarotraumaServer/Source/Networking/KarmaManager.cs new file mode 100644 index 000000000..fc0ea45a7 --- /dev/null +++ b/Barotrauma/BarotraumaServer/Source/Networking/KarmaManager.cs @@ -0,0 +1,337 @@ +using Barotrauma.Items.Components; +using Barotrauma.Networking; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Barotrauma +{ + partial class KarmaManager : ISerializableEntity + { + private class ClientMemory + { + public List> WireDisconnectTime = new List>(); + + //the client's karma value when they were last sent a notification about it (e.g. "your karma is very low") + public float PreviousNotifiedKarma; + + public float StructureDamageAccumulator; + + private float structureDamagePerSecond; + public float StructureDamagePerSecond + { + get { return Math.Max(StructureDamageAccumulator, structureDamagePerSecond); } + set { structureDamagePerSecond = value; } + } + } + + public bool TestMode = false; + + private readonly Dictionary clientMemories = new Dictionary(); + private readonly List bannedClients = new List(); + + private DateTime perSecondUpdate; + + private double KarmaNotificationTime; + + public void UpdateClients(IEnumerable clients, float deltaTime) + { + if (!GameMain.Server.GameStarted) { return; } + + bannedClients.Clear(); + foreach (Client client in clients) + { + var clientMemory = GetClientMemory(client); + UpdateClient(client, deltaTime); + + if (perSecondUpdate < DateTime.Now) + { + clientMemory.StructureDamagePerSecond = clientMemory.StructureDamageAccumulator; + clientMemory.StructureDamageAccumulator = 0.0f; + } + } + if (perSecondUpdate < DateTime.Now) + { + perSecondUpdate = DateTime.Now + new TimeSpan(0, 0, 1); + } + + if (TestMode || Timing.TotalTime > KarmaNotificationTime) + { + foreach (Client client in clients) + { + SendKarmaNotifications(client); + } + KarmaNotificationTime = Timing.TotalTime + KarmaNotificationInterval; + } + + foreach (Client bannedClient in bannedClients) + { + GameMain.Server.BanClient(bannedClient, $"KarmaBanned~[banthreshold]={(int)KickBanThreshold}", duration: TimeSpan.FromSeconds(GameMain.Server.ServerSettings.AutoBanTime)); + } + } + + private void SendKarmaNotifications(Client client, string debugKarmaChangeReason = "") + { + var clientMemory = GetClientMemory(client); + float karmaChange = client.Karma - clientMemory.PreviousNotifiedKarma; + if (Math.Abs(karmaChange) > KarmaNotificationInterval || (TestMode && Math.Abs(karmaChange) > 2.0f)) + { + if (TestMode) + { + string msg = + karmaChange < 0 ? $"You karma has decreased to {client.Karma}" : $"You karma has increased to {client.Karma}"; + if (!string.IsNullOrEmpty(debugKarmaChangeReason)) + { + msg += $". Reason: {debugKarmaChangeReason}"; + } + GameMain.Server.SendDirectChatMessage(msg, client); + } + else if (Math.Abs(KickBanThreshold - client.Karma) < KarmaNotificationInterval) + { + GameMain.Server.SendDirectChatMessage(TextManager.Get("KarmaBanWarning"), client); + } + else + { + GameMain.Server.SendDirectChatMessage(TextManager.Get(karmaChange < 0 ? "KarmaDecreasedUnknownAmount" : "KarmaIncreasedUnknownAmount"), client); + } + clientMemory.PreviousNotifiedKarma = client.Karma; + } + } + + private void UpdateClient(Client client, float deltaTime) + { + if (client.Karma > KarmaDecayThreshold) + { + client.Karma -= KarmaDecay * deltaTime; + } + else if (client.Karma < KarmaIncreaseThreshold) + { + client.Karma += KarmaIncrease * deltaTime; + } + + if (client.Character != null && !client.Character.Removed) + { + //increase the strength of the herpes affliction in steps instead of linearly + //otherwise clients could determine their exact karma value from the strength + float herpesStrength = 0.0f; + if (client.Karma < 20) + herpesStrength = 100.0f; + else if (client.Karma < 30) + herpesStrength = 60.0f; + else if (client.Karma < 40.0f) + herpesStrength = 30.0f; + + var existingAffliction = client.Character.CharacterHealth.GetAffliction("spaceherpes"); + if (existingAffliction == null && herpesStrength > 0.0f) + { + client.Character.CharacterHealth.ApplyAffliction(null, new Affliction(herpesAffliction, herpesStrength)); + } + else if (existingAffliction != null) + { + existingAffliction.Strength = herpesStrength; + } + + //check if the client has disconnected an excessive number of wires + var clientMemory = GetClientMemory(client); + if (clientMemory.WireDisconnectTime.Count > (int)AllowedWireDisconnectionsPerMinute) + { + clientMemory.WireDisconnectTime.RemoveRange(0, clientMemory.WireDisconnectTime.Count - (int)AllowedWireDisconnectionsPerMinute); + if (clientMemory.WireDisconnectTime.All(w => Timing.TotalTime - w.Second < 60.0f)) + { + float karmaDecrease = -WireDisconnectionKarmaDecrease; + //engineers don't lose as much karma for removing lots of wires + if (client.Character.Info?.Job.Prefab.Identifier == "engineer") { karmaDecrease *= 0.5f; } + AdjustKarma(client.Character, karmaDecrease, "Disconnected excessive number of wires"); + } + } + + if (client.Character?.Info?.Job.Prefab.Identifier == "captain" && client.Character.SelectedConstruction != null) + { + if (client.Character.SelectedConstruction.GetComponent() != null) + { + AdjustKarma(client.Character, SteerSubKarmaIncrease * deltaTime, "Steering the sub"); + } + } + } + + if (client.Karma < KickBanThreshold && client.Connection != GameMain.Server.OwnerConnection) + { + if (TestMode) + { + client.Karma = 50.0f; + GameMain.Server.SendDirectChatMessage("BANNED! (not really because karma test mode is enabled)", client); + } + else + { + bannedClients.Add(client); + } + } + } + + public void OnClientDisconnected(Client client) + { + clientMemories.Remove(client); + } + + public void OnCharacterHealthChanged(Character target, Character attacker, float damage, IEnumerable appliedAfflictions = null) + { + if (target == null || attacker == null) { return; } + if (target == attacker) { return; } + + //damaging dead characters doesn't affect karma + if (target.IsDead || target.Removed) { return; } + + bool isEnemy = target.AIController is EnemyAIController || target.TeamID != attacker.TeamID; + if (GameMain.Server.TraitorManager != null) + { + if (GameMain.Server.TraitorManager.TraitorList.Any(t => t.Character == target)) + { + //traitors always count as enemies + isEnemy = true; + } + if (GameMain.Server.TraitorManager.TraitorList.Any(t => t.Character == attacker && t.TargetCharacter == target)) + { + //target counts as an enemy to the traitor + isEnemy = true; + } + } + + if (appliedAfflictions != null) + { + foreach (Affliction affliction in appliedAfflictions) + { + if (MathUtils.NearlyEqual(affliction.Prefab.KarmaChangeOnApplied, 0.0f)) { continue; } + damage -= affliction.Prefab.KarmaChangeOnApplied * affliction.Strength; + } + } + + if (target.AIController is EnemyAIController || target.TeamID != attacker.TeamID) + { + if (damage > 0) + { + float karmaIncrease = damage * DamageEnemyKarmaIncrease; + if (attacker?.Info?.Job.Prefab.Identifier == "securityofficer") { karmaIncrease *= 2.0f; } + AdjustKarma(attacker, karmaIncrease, "Damaged enemy"); + } + } + else + { + if (damage > 0) + { + AdjustKarma(attacker, -damage * DamageFriendlyKarmaDecrease, "Damaged friendly"); + } + else + { + float karmaIncrease = -damage * HealFriendlyKarmaIncrease; + if (attacker?.Info?.Job.Prefab.Identifier == "medicaldoctor") { karmaIncrease *= 2.0f; } + AdjustKarma(attacker, karmaIncrease, "Healed friendly"); + } + } + } + + + public void OnStructureHealthChanged(Structure structure, Character attacker, float damageAmount) + { + if (attacker == null) { return; } + //damaging/repairing ruin structures or enemy subs doesn't affect karma + if (structure.Submarine == null || structure.Submarine.TeamID != attacker.TeamID) + { + return; + } + + if (damageAmount > 0) + { + if (StructureDamageKarmaDecrease <= 0.0f) { return; } + Client client = GameMain.Server.ConnectedClients.Find(c => c.Character == attacker); + if (client != null) + { + //cap the damage so the karma can't decrease by more than MaxStructureDamageKarmaDecreasePerSecond per second + var clientMemory = GetClientMemory(client); + clientMemory.StructureDamageAccumulator += damageAmount; + if (clientMemory.StructureDamagePerSecond + damageAmount >= MaxStructureDamageKarmaDecreasePerSecond / StructureDamageKarmaDecrease) + { + damageAmount -= (MaxStructureDamageKarmaDecreasePerSecond / StructureDamageKarmaDecrease) - clientMemory.StructureDamagePerSecond; + if (damageAmount <= 0.0f) { return; } + } + } + AdjustKarma(attacker, -damageAmount * StructureDamageKarmaDecrease, "Damaged structures"); + } + else + { + float karmaIncrease = -damageAmount * StructureRepairKarmaIncrease; + //mechanics get twice as much karma for repairing walls + if (attacker.Info?.Job.Prefab.Identifier == "mechanic") { karmaIncrease *= 2.0f; } + AdjustKarma(attacker, karmaIncrease, "Repaired structures"); + } + } + + public void OnItemRepaired(Character character, Repairable repairable, float repairAmount) + { + float karmaIncrease = repairAmount * ItemRepairKarmaIncrease; + if (repairable.HasRequiredSkills(character)) { karmaIncrease *= 2.0f; } + AdjustKarma(character, karmaIncrease, "Repaired item"); + } + + public void OnReactorOverHeating(Character character, float deltaTime) + { + AdjustKarma(character, -ReactorOverheatKarmaDecrease * deltaTime, "Caused reactor to overheat"); + } + + public void OnReactorMeltdown(Character character) + { + AdjustKarma(character, -ReactorMeltdownKarmaDecrease, "Caused a reactor meltdown"); + } + + public void OnExtinguishingFire(Character character, float deltaTime) + { + AdjustKarma(character, ExtinguishFireKarmaIncrease * deltaTime, "Extinguished a fire"); + } + + public void OnWireDisconnected(Character character, Wire wire) + { + if (character == null || wire == null) { return; } + Client client = GameMain.Server.ConnectedClients.Find(c => c.Character == character); + if (client == null) { return; } + + if (!clientMemories.ContainsKey(client)) { clientMemories[client] = new ClientMemory(); } + + clientMemories[client].WireDisconnectTime.RemoveAll(w => w.First == wire); + clientMemories[client].WireDisconnectTime.Add(new Pair(wire, (float)Timing.TotalTime)); + } + + private ClientMemory GetClientMemory(Client client) + { + if (!clientMemories.ContainsKey(client)) + { + clientMemories[client] = new ClientMemory() + { + PreviousNotifiedKarma = client.Karma + }; + } + return clientMemories[client]; + } + + public void OnSpamFilterTriggered(Client client) + { + if (client != null) + { + client.Karma -= SpamFilterKarmaDecrease; + SendKarmaNotifications(client, "Triggered the spam filter"); + } + } + + private void AdjustKarma(Character target, float amount, string debugKarmaChangeReason = "") + { + if (target == null) { return; } + + Client client = GameMain.Server.ConnectedClients.Find(c => c.Character == target); + if (client == null) { return; } + + client.Karma += amount; + if (TestMode) + { + SendKarmaNotifications(client, debugKarmaChangeReason); + } + } + } +} diff --git a/Barotrauma/BarotraumaServer/Source/Networking/RespawnManager.cs b/Barotrauma/BarotraumaServer/Source/Networking/RespawnManager.cs index dcd655cbd..019db3603 100644 --- a/Barotrauma/BarotraumaServer/Source/Networking/RespawnManager.cs +++ b/Barotrauma/BarotraumaServer/Source/Networking/RespawnManager.cs @@ -1,4 +1,5 @@ using Barotrauma.Items.Components; +using Lidgren.Network; using Microsoft.Xna.Framework; using System; using System.Collections.Generic; @@ -53,36 +54,34 @@ namespace Barotrauma.Networking return botsToRespawn; } - partial void UpdateWaiting(float deltaTime) + private bool RespawnPending() { int characterToRespawnCount = GetClientsToRespawn().Count; int totalCharacterCount = GameMain.Server.ConnectedClients.Count; - /*if (server.Character != null) - { - totalCharacterCount++; - if (server.Character.IsDead) characterToRespawnCount++; - }*/ - bool startCountdown = (float)characterToRespawnCount >= Math.Max((float)totalCharacterCount * GameMain.Server.ServerSettings.MinRespawnRatio, 1.0f); + return (float)characterToRespawnCount >= Math.Max((float)totalCharacterCount * GameMain.Server.ServerSettings.MinRespawnRatio, 1.0f); + } - if (startCountdown != CountdownStarted) + partial void UpdateWaiting(float deltaTime) + { + bool respawnPending = RespawnPending(); + if (respawnPending != RespawnCountdownStarted) { - CountdownStarted = startCountdown; + RespawnCountdownStarted = respawnPending; + RespawnTime = DateTime.Now + new TimeSpan(0,0,0,0, (int)(GameMain.Server.ServerSettings.RespawnInterval * 1000.0f)); GameMain.Server.CreateEntityEvent(this); } - if (!CountdownStarted) return; + if (!RespawnCountdownStarted) { return; } - respawnTimer -= deltaTime; - if (respawnTimer <= 0.0f) + if (DateTime.Now > RespawnTime) { - respawnTimer = GameMain.Server.ServerSettings.RespawnInterval; - DispatchShuttle(); + RespawnCountdownStarted = false; } - if (respawnShuttle == null) return; + if (RespawnShuttle == null) { return; } - respawnShuttle.Velocity = Vector2.Zero; + RespawnShuttle.Velocity = Vector2.Zero; if (shuttleSteering != null) { @@ -93,9 +92,9 @@ namespace Barotrauma.Networking partial void DispatchShuttle() { - if (respawnShuttle != null) + if (RespawnShuttle != null) { - state = State.Transporting; + CurrentState = State.Transporting; GameMain.Server.CreateEntityEvent(this); ResetShuttle(); @@ -110,11 +109,27 @@ namespace Barotrauma.Networking RespawnCharacters(); CoroutineManager.StopCoroutines("forcepos"); - CoroutineManager.StartCoroutine(ForceShuttleToPos(Level.Loaded.StartPosition - Vector2.UnitY * Level.ShaftHeight, 100.0f), "forcepos"); + Vector2 spawnPos = FindSpawnPos(); + if (spawnPos.Y > Level.Loaded.Size.Y) + { + CoroutineManager.StartCoroutine(ForceShuttleToPos(Level.Loaded.StartPosition - Vector2.UnitY * Level.ShaftHeight, 100.0f), "forcepos"); + } + else + { + RespawnShuttle.SetPosition(spawnPos); + RespawnShuttle.Velocity = Vector2.Zero; + if (shuttleSteering != null) + { + shuttleSteering.AutoPilot = true; + shuttleSteering.MaintainPos = true; + shuttleSteering.PosToMaintain = RespawnShuttle.WorldPosition; + shuttleSteering.UnsentChanges = true; + } + } } else { - state = State.Waiting; + CurrentState = State.Waiting; GameServer.Log("Respawning everyone in main sub.", ServerLog.MessageType.Spawning); GameMain.Server.CreateEntityEvent(this); @@ -129,18 +144,18 @@ namespace Barotrauma.Networking if (door.IsOpen) door.TrySetState(false, false, true); } - var shuttleGaps = Gap.GapList.FindAll(g => g.Submarine == respawnShuttle && g.ConnectedWall != null); + var shuttleGaps = Gap.GapList.FindAll(g => g.Submarine == RespawnShuttle && g.ConnectedWall != null); shuttleGaps.ForEach(g => Spawner.AddToRemoveQueue(g)); - var dockingPorts = Item.ItemList.FindAll(i => i.Submarine == respawnShuttle && i.GetComponent() != null); + var dockingPorts = Item.ItemList.FindAll(i => i.Submarine == RespawnShuttle && i.GetComponent() != null); dockingPorts.ForEach(d => d.GetComponent().Undock()); //shuttle has returned if the path has been traversed or the shuttle is close enough to the exit if (!CoroutineManager.IsCoroutineRunning("forcepos")) { if ((shuttleSteering?.SteeringPath != null && shuttleSteering.SteeringPath.Finished) - || (respawnShuttle.WorldPosition.Y + respawnShuttle.Borders.Y > Level.Loaded.StartPosition.Y - Level.ShaftHeight && - Math.Abs(Level.Loaded.StartPosition.X - respawnShuttle.WorldPosition.X) < 1000.0f)) + || (RespawnShuttle.WorldPosition.Y + RespawnShuttle.Borders.Y > Level.Loaded.StartPosition.Y - Level.ShaftHeight && + Math.Abs(Level.Loaded.StartPosition.X - RespawnShuttle.WorldPosition.X) < 1000.0f)) { CoroutineManager.StopCoroutines("forcepos"); CoroutineManager.StartCoroutine( @@ -149,46 +164,60 @@ namespace Barotrauma.Networking } } - if (respawnShuttle.WorldPosition.Y > Level.Loaded.Size.Y || shuttleReturnTimer <= 0.0f) + if (RespawnShuttle.WorldPosition.Y > Level.Loaded.Size.Y || DateTime.Now > despawnTime) { CoroutineManager.StopCoroutines("forcepos"); ResetShuttle(); - state = State.Waiting; + CurrentState = State.Waiting; GameServer.Log("The respawn shuttle has left.", ServerLog.MessageType.Spawning); GameMain.Server.CreateEntityEvent(this); - respawnTimer = GameMain.Server.ServerSettings.RespawnInterval; - CountdownStarted = false; + RespawnCountdownStarted = false; } } partial void UpdateTransportingProjSpecific(float deltaTime) { - //if there are no living chracters inside, transporting can be stopped immediately - if (!Character.CharacterList.Any(c => c.Submarine == respawnShuttle && !c.IsDead)) + + if (!ReturnCountdownStarted) { - shuttleTransportTimer = 0.0f; + //if there are no living chracters inside, transporting can be stopped immediately + if (!Character.CharacterList.Any(c => c.Submarine == RespawnShuttle && !c.IsDead)) + { + ReturnTime = DateTime.Now; + ReturnCountdownStarted = true; + } + else if (!RespawnPending()) + { + //don't start counting down until someone else needs to respawn + ReturnTime = DateTime.Now + new TimeSpan(0, 0, 0, 0, milliseconds: (int)(maxTransportTime * 1000)); + despawnTime = ReturnTime + new TimeSpan(0, 0, seconds: 30); + return; + } + else + { + ReturnCountdownStarted = true; + GameMain.Server.CreateEntityEvent(this); + } } - if (shuttleTransportTimer <= 0.0f) + if (DateTime.Now > ReturnTime) { GameServer.Log("The respawn shuttle is leaving.", ServerLog.MessageType.ServerMessage); - state = State.Returning; + CurrentState = State.Returning; GameMain.Server.CreateEntityEvent(this); - CountdownStarted = false; + RespawnCountdownStarted = false; maxTransportTime = GameMain.Server.ServerSettings.MaxTransportTime; - shuttleReturnTimer = maxTransportTime; - shuttleTransportTimer = maxTransportTime; } } partial void RespawnCharactersProjSpecific() { - var respawnSub = respawnShuttle ?? Submarine.MainSub; + var respawnSub = RespawnShuttle ?? Submarine.MainSub; var clients = GetClientsToRespawn(); foreach (Client c in clients) @@ -250,7 +279,7 @@ namespace Barotrauma.Networking GameServer.Log(string.Format("Respawning {0} ({1}) as {2}", clients[i].Name, clients[i].Connection?.RemoteEndPoint?.Address, characterInfos[i].Job.Name), ServerLog.MessageType.Spawning); } - if (divingSuitPrefab != null && oxyPrefab != null && respawnShuttle != null) + if (divingSuitPrefab != null && oxyPrefab != null && RespawnShuttle != null) { Vector2 pos = cargoSp == null ? character.Position : cargoSp.Position; if (divingSuitPrefab != null && oxyPrefab != null) @@ -295,5 +324,27 @@ namespace Barotrauma.Networking } } } + + public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null) + { + msg.WriteRangedInteger(0, Enum.GetNames(typeof(State)).Length, (int)CurrentState); + + switch (CurrentState) + { + case State.Transporting: + msg.Write(ReturnCountdownStarted); + msg.Write(GameMain.Server.ServerSettings.MaxTransportTime); + msg.Write((float)(ReturnTime - DateTime.Now).TotalSeconds); + break; + case State.Waiting: + msg.Write(RespawnCountdownStarted); + msg.Write((float)(RespawnTime - DateTime.Now).TotalSeconds); + break; + case State.Returning: + break; + } + + msg.WritePadBits(); + } } } diff --git a/Barotrauma/BarotraumaServer/Source/Networking/ServerSettings.cs b/Barotrauma/BarotraumaServer/Source/Networking/ServerSettings.cs index fa0472297..da1b96533 100644 --- a/Barotrauma/BarotraumaServer/Source/Networking/ServerSettings.cs +++ b/Barotrauma/BarotraumaServer/Source/Networking/ServerSettings.cs @@ -101,8 +101,12 @@ namespace Barotrauma.Networking if (netProperties.ContainsKey(key)) { + object prevValue = netProperties[key].Value; netProperties[key].Read(incMsg); - GameServer.Log(c.Name + " changed " + netProperties[key].Name + " to " + netProperties[key].Value.ToString(), ServerLog.MessageType.ServerMessage); + if (!netProperties[key].PropEquals(prevValue, netProperties[key])) + { + GameServer.Log(c.Name + " changed " + netProperties[key].Name + " to " + netProperties[key].Value.ToString(), ServerLog.MessageType.ServerMessage); + } changed = true; } else @@ -205,6 +209,12 @@ namespace Barotrauma.Networking { doc.Save(writer); } + + if (KarmaPreset == "custom") + { + GameMain.Server?.KarmaManager?.SaveCustomPreset(); + } + GameMain.Server?.KarmaManager?.Save(); } private void LoadSettings() @@ -300,9 +310,6 @@ namespace Barotrauma.Networking { if (!MonsterEnabled.ContainsKey(s)) MonsterEnabled.Add(s, true); } - - AutoBanTime = doc.Root.GetAttributeFloat("autobantime", 60); - MaxAutoBanTime = doc.Root.GetAttributeFloat("maxautobantime", 360); } public void LoadClientPermissions() diff --git a/Barotrauma/BarotraumaServer/Source/Program.cs b/Barotrauma/BarotraumaServer/Source/Program.cs index 01070636e..a30172199 100644 --- a/Barotrauma/BarotraumaServer/Source/Program.cs +++ b/Barotrauma/BarotraumaServer/Source/Program.cs @@ -89,6 +89,17 @@ namespace Barotrauma sb.AppendLine(exception.StackTrace); sb.AppendLine("\n"); + if (exception.InnerException != null) + { + sb.AppendLine("InnerException: " + exception.InnerException.Message); + if (exception.InnerException.TargetSite != null) + { + sb.AppendLine("Target site: " + exception.InnerException.TargetSite.ToString()); + } + sb.AppendLine("Stack trace: "); + sb.AppendLine(exception.InnerException.StackTrace); + } + sb.AppendLine("Last debug messages:"); for (int i = DebugConsole.Messages.Count - 1; i > 0 && i > DebugConsole.Messages.Count - 15; i-- ) { diff --git a/Barotrauma/BarotraumaShared/Data/karmasettings.xml b/Barotrauma/BarotraumaShared/Data/karmasettings.xml new file mode 100644 index 000000000..50ba0e140 --- /dev/null +++ b/Barotrauma/BarotraumaShared/Data/karmasettings.xml @@ -0,0 +1,66 @@ + + + + + + \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/Data/permissionpresets.xml b/Barotrauma/BarotraumaShared/Data/permissionpresets.xml index 3187fe94a..648674540 100644 --- a/Barotrauma/BarotraumaShared/Data/permissionpresets.xml +++ b/Barotrauma/BarotraumaShared/Data/permissionpresets.xml @@ -25,6 +25,7 @@ + + \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedCode.projitems b/Barotrauma/BarotraumaShared/SharedCode.projitems index 3ce8598e9..78b2b83f0 100644 --- a/Barotrauma/BarotraumaShared/SharedCode.projitems +++ b/Barotrauma/BarotraumaShared/SharedCode.projitems @@ -66,6 +66,7 @@ + @@ -226,6 +227,7 @@ + diff --git a/Barotrauma/BarotraumaShared/SharedContent.projitems b/Barotrauma/BarotraumaShared/SharedContent.projitems index b54752cf9..ee46100fe 100644 --- a/Barotrauma/BarotraumaShared/SharedContent.projitems +++ b/Barotrauma/BarotraumaShared/SharedContent.projitems @@ -481,6 +481,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest @@ -683,6 +686,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest @@ -1640,54 +1646,6 @@ PreserveNewest - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - PreserveNewest @@ -2183,9 +2141,105 @@ PreserveNewest + + PreserveNewest + PreserveNewest + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + PreserveNewest diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/AITarget.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/AITarget.cs index 07e5b86a8..d432de18e 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/AITarget.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/AITarget.cs @@ -64,6 +64,12 @@ namespace Barotrauma } } + public float SonarDisruption + { + get; + set; + } + public string SonarLabel; public bool Enabled = true; @@ -127,6 +133,7 @@ namespace Barotrauma MaxSightRange = element.GetAttributeFloat("maxsightrange", SightRange); MaxSoundRange = element.GetAttributeFloat("maxsoundrange", SoundRange); FadeOutTime = element.GetAttributeFloat("fadeouttime", FadeOutTime); + SonarDisruption = element.GetAttributeFloat("sonardisruption", 0.0f); SonarLabel = element.GetAttributeString("sonarlabel", ""); string typeString = element.GetAttributeString("type", "Any"); if (Enum.TryParse(typeString, out TargetType t)) diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/EnemyAIController.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/EnemyAIController.cs index 059634be8..43428f673 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/EnemyAIController.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/EnemyAIController.cs @@ -841,7 +841,7 @@ namespace Barotrauma private Limb GetAttackLimb(Vector2 attackWorldPos, Limb ignoredLimb = null) { AttackContext currentContext = Character.GetAttackContext(); - var target = wallTarget != null ? wallTarget.Structure : SelectedAiTarget.Entity; + var target = wallTarget != null ? wallTarget.Structure : SelectedAiTarget?.Entity; Limb selectedLimb = null; float currentPriority = 0; foreach (Limb limb in Character.AnimController.Limbs) diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveCombat.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveCombat.cs index 85ebb08a8..94ac4404c 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveCombat.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveCombat.cs @@ -163,7 +163,7 @@ namespace Barotrauma { Weapon = null; } - else if (!WeaponComponent.HasRequiredContainedItems(false)) + else if (!WeaponComponent.HasRequiredContainedItems(character, addMessage: false)) { // Seek ammunition only if cannot find a new weapon if (!Reload(!HoldPosition, () => GetWeapon(out _) == null)) @@ -234,14 +234,14 @@ namespace Barotrauma { if (component is RangedWeapon rw) { - if (ignoreRequiredItems || rw.HasRequiredContainedItems(false)) + if (ignoreRequiredItems || rw.HasRequiredContainedItems(character, addMessage: false)) { weapons.Add(rw); } } else if (component is MeleeWeapon mw) { - if (ignoreRequiredItems || mw.HasRequiredContainedItems(false)) + if (ignoreRequiredItems || mw.HasRequiredContainedItems(character, addMessage: false)) { weapons.Add(mw); } @@ -257,7 +257,7 @@ namespace Barotrauma { if (statusEffect.Afflictions.Any()) { - if (ignoreRequiredItems || component.HasRequiredContainedItems(false)) + if (ignoreRequiredItems || component.HasRequiredContainedItems(character, addMessage: false)) { weapons.Add(component); } @@ -284,7 +284,7 @@ namespace Barotrauma private bool Equip() { if (character.LockHands) { return false; } - if (!WeaponComponent.HasRequiredContainedItems(false)) + if (!WeaponComponent.HasRequiredContainedItems(character, addMessage: false)) { Mode = CombatMode.Retreat; return false; @@ -428,7 +428,7 @@ namespace Barotrauma } } } - if (WeaponComponent.HasRequiredContainedItems(false)) + if (WeaponComponent.HasRequiredContainedItems(character, addMessage: false)) { return true; } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveExtinguishFire.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveExtinguishFire.cs index b2bccbe4d..3d29eb047 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveExtinguishFire.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveExtinguishFire.cs @@ -108,6 +108,7 @@ namespace Barotrauma else { move = false; + character.SetInput(extinguisher.Item.IsShootable ? InputType.Shoot : InputType.Use, false, true); extinguisher.Use(deltaTime, character); if (!targetHull.FireSources.Contains(fs)) { diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRepairItem.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRepairItem.cs index 38bfff732..c8d162c3b 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRepairItem.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRepairItem.cs @@ -4,7 +4,6 @@ using System; using System.Linq; using Barotrauma.Extensions; using FarseerPhysics; -using Barotrauma.Items.Components; namespace Barotrauma { diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRescue.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRescue.cs index 3eff46f3a..dbddff518 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRescue.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRescue.cs @@ -207,7 +207,7 @@ namespace Barotrauma bool remove = false; foreach (ItemComponent ic in item.Components) { - if (!ic.HasRequiredContainedItems(addMessage: false)) { continue; } + if (!ic.HasRequiredContainedItems(user: character, addMessage: false)) { continue; } #if CLIENT ic.PlaySound(ActionType.OnUse, character.WorldPosition, character); #endif diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AICharacter.cs b/Barotrauma/BarotraumaShared/Source/Characters/AICharacter.cs index 5779f8cec..be8525a94 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AICharacter.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AICharacter.cs @@ -50,23 +50,13 @@ namespace Barotrauma if (!IsRemotePlayer) { - float characterDist = Vector2.DistanceSquared(cam.WorldViewCenter, WorldPosition); -#if SERVER + float characterDist = float.MaxValue; +#if CLIENT + characterDist = Vector2.DistanceSquared(cam.GetPosition(), WorldPosition); +#elif SERVER if (GameMain.Server != null) { - //get the distance from the closest player to this character - foreach (Character c in CharacterList) - { - if (c != this && c.IsRemotePlayer) - { - float dist = Vector2.DistanceSquared(c.WorldPosition, WorldPosition); - if (dist < characterDist) - { - characterDist = dist; - if (characterDist < DisableSimplePhysicsDistSqr) break; - } - } - } + characterDist = GetClosestDistance(); } #endif @@ -90,5 +80,50 @@ namespace Barotrauma aiController.Update(deltaTime); } } + +#if SERVER + // Gets the closest distance, either an active player character or spectator + private float GetClosestDistance() + { + float minDist = float.MaxValue; + + for (int i = 0; i < GameMain.Server.ConnectedClients.Count; i++) + { + var spectatePos = GameMain.Server.ConnectedClients[i].SpectatePos; + if (spectatePos != null) + { + float dist = Vector2.DistanceSquared(spectatePos.Value, WorldPosition); + + if (dist < minDist) + { + minDist = dist; + } + if (dist < DisableSimplePhysicsDistSqr) + { + return dist; + } + } + } + + foreach (Character c in CharacterList) + { + if (c != this && c.IsRemotePlayer) + { + float dist = Vector2.DistanceSquared(c.WorldPosition, WorldPosition); + + if (dist < minDist) + { + minDist = dist; + } + if (dist < DisableSimplePhysicsDistSqr) + { + return dist; + } + } + } + + return minDist; + } +#endif } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Animation/FishAnimController.cs b/Barotrauma/BarotraumaShared/Source/Characters/Animation/FishAnimController.cs index 1d7ac3ecb..952243dfb 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Animation/FishAnimController.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Animation/FishAnimController.cs @@ -139,8 +139,9 @@ namespace Barotrauma Collider.FarseerBody.FixedRotation = false; if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient) { - Collider.LinearVelocity = MainLimb.LinearVelocity; + Collider.Enabled = false; Collider.FarseerBody.FixedRotation = false; + Collider.LinearVelocity = MainLimb.LinearVelocity; Collider.SetTransformIgnoreContacts(MainLimb.SimPosition, MainLimb.Rotation); } if (character.IsDead && deathAnimTimer < deathAnimDuration) diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Animation/HumanoidAnimController.cs b/Barotrauma/BarotraumaShared/Source/Characters/Animation/HumanoidAnimController.cs index 06adfe917..d0cabdce8 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Animation/HumanoidAnimController.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Animation/HumanoidAnimController.cs @@ -286,7 +286,7 @@ namespace Barotrauma var waistJoint = GetJointBetweenLimbs(LimbType.Waist, upperLegType); Vector2 localAnchorWaist = Vector2.Zero; Vector2 localAnchorKnee = Vector2.Zero; - if (shoulder != null) + if (waistJoint != null) { localAnchorWaist = waistJoint.LimbA.type == upperLegType ? waistJoint.LocalAnchorA : waistJoint.LocalAnchorB; } @@ -298,6 +298,7 @@ namespace Barotrauma upperLegLength = Vector2.Distance(localAnchorWaist, localAnchorKnee); LimbJoint ankleJoint = GetJointBetweenLimbs(lowerLegType, footType); + if (ankleJoint == null || kneeJoint == null) { return; } lowerLegLength = Vector2.Distance( kneeJoint.LimbA.type == lowerLegType ? kneeJoint.LocalAnchorA : kneeJoint.LocalAnchorB, ankleJoint.LimbA.type == lowerLegType ? ankleJoint.LocalAnchorA : ankleJoint.LocalAnchorB); @@ -537,14 +538,13 @@ namespace Barotrauma float limpAmount = character.CharacterHealth.GetAfflictionStrength("damage", leftFoot, true) + - character.CharacterHealth.GetAfflictionStrength("damage", rightFoot, true); + character.CharacterHealth.GetAfflictionStrength("damage", rightFoot, true) + + character.CharacterHealth.GetAfflictionStrength("spaceherpes"); limpAmount = MathHelper.Clamp(limpAmount / 100.0f, 0.0f, 1.0f); float walkCycleMultiplier = 1.0f; if (Stairs != null) { - //TODO: allow editing these values in character editor? - bool running = Math.Abs(targetMovement.X) > 2.0f; TargetMovement = new Vector2(MathHelper.Clamp(TargetMovement.X, -1.7f, 1.7f), TargetMovement.Y); walkCycleMultiplier *= 1.5f; } @@ -579,7 +579,7 @@ namespace Barotrauma if (limpAmount > 0.0f) { //make the footpos oscillate when limping - footMid += (Math.Max(Math.Abs(walkPosX) * limpAmount, 0.0f) * Math.Min(Math.Abs(TargetMovement.X), 0.3f)); + footMid += (Math.Max(Math.Abs(walkPosX) * limpAmount, 0.0f) * Math.Min(Math.Abs(TargetMovement.X), 0.3f)) * Dir; } movement = overrideTargetMovement == Vector2.Zero ? @@ -663,7 +663,13 @@ namespace Barotrauma } } - if (TorsoAngle.HasValue) torso.body.SmoothRotate(TorsoAngle.Value * Dir, 50.0f); + if (TorsoAngle.HasValue) + { + float torsoAngle = TorsoAngle.Value; + float herpesStrength = character.CharacterHealth.GetAfflictionStrength("spaceherpes"); + torsoAngle -= herpesStrength / 150.0f; + torso.body.SmoothRotate(torsoAngle * Dir, 50.0f); + } if (HeadAngle.HasValue) head.body.SmoothRotate(HeadAngle.Value * Dir, 50.0f); if (!onGround) @@ -689,7 +695,6 @@ namespace Barotrauma for (int i = -1; i < 2; i += 2) { Limb foot = i == -1 ? leftFoot : rightFoot; - Limb leg = i == -1 ? leftLeg : rightLeg; Vector2 footPos = stepSize * -i; footPos += new Vector2(Math.Sign(movement.X) * FootMoveOffset.X, FootMoveOffset.Y); @@ -706,6 +711,15 @@ namespace Barotrauma } footPos.Y = Math.Min(waistPos.Y - colliderPos.Y - 0.4f, footPos.Y); +#if CLIENT + if ((i == 1 && Math.Sign(Math.Sin(WalkPos)) > 0 && Math.Sign(walkPosY) < 0) || + (i == -1 && Math.Sign(Math.Sin(WalkPos)) < 0 && Math.Sign(walkPosY) > 0)) + { + PlayImpactSound(foot); + } + +#endif + if (!foot.Disabled) { foot.DebugRefPos = colliderPos; @@ -766,7 +780,6 @@ namespace Barotrauma } var foot = i == -1 ? rightFoot : leftFoot; - Limb leg = i == -1 ? rightLeg : leftLeg; if (!foot.Disabled) { diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Animation/Params/Animation/AnimationParams.cs b/Barotrauma/BarotraumaShared/Source/Characters/Animation/Params/Animation/AnimationParams.cs index f4e118652..53edbdc7d 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Animation/Params/Animation/AnimationParams.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Animation/Params/Animation/AnimationParams.cs @@ -96,16 +96,16 @@ namespace Barotrauma public virtual AnimationType AnimationType { get; protected set; } public static string GetDefaultFileName(string speciesName, AnimationType animType) => $"{speciesName.CapitaliseFirstInvariant()}{animType.ToString()}"; - public static string GetDefaultFolder(string speciesName) => $"Content/Characters/{speciesName.CapitaliseFirstInvariant()}/Animations/"; public static string GetDefaultFile(string speciesName, AnimationType animType, ContentPackage contentPackage = null) => $"{GetFolder(speciesName, contentPackage)}{GetDefaultFileName(speciesName, animType)}.xml"; public static string GetFolder(string speciesName, ContentPackage contentPackage = null) { - var folder = XMLExtensions.TryLoadXml(Character.GetConfigFile(speciesName, contentPackage))?.Root?.Element("animations")?.GetAttributeString("folder", string.Empty); + string configFilePath = Character.GetConfigFile(speciesName, contentPackage); + var folder = XMLExtensions.TryLoadXml(configFilePath)?.Root?.Element("animations")?.GetAttributeString("folder", string.Empty); if (string.IsNullOrEmpty(folder) || folder.ToLowerInvariant() == "default") { - folder = GetDefaultFolder(speciesName); + folder = Path.Combine(Path.GetDirectoryName(configFilePath), "Animations"); } return folder; } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Animation/Params/Ragdoll/RagdollParams.cs b/Barotrauma/BarotraumaShared/Source/Characters/Animation/Params/Ragdoll/RagdollParams.cs index 4477deb76..c7eb7cf4a 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Animation/Params/Ragdoll/RagdollParams.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Animation/Params/Ragdoll/RagdollParams.cs @@ -66,7 +66,6 @@ namespace Barotrauma .Concat(Joints.Select(j => j as RagdollSubParams))); public static string GetDefaultFileName(string speciesName) => $"{speciesName.CapitaliseFirstInvariant()}DefaultRagdoll"; - public static string GetDefaultFolder(string speciesName) => $"Content/Characters/{speciesName.CapitaliseFirstInvariant()}/Ragdolls/"; public static string GetDefaultFile(string speciesName, ContentPackage contentPackage = null) => $"{GetFolder(speciesName, contentPackage)}{GetDefaultFileName(speciesName)}.xml"; private static readonly object[] dummyParams = new object[] @@ -84,11 +83,11 @@ namespace Barotrauma public static string GetFolder(string speciesName, ContentPackage contentPackage = null) { - var folder = XMLExtensions.TryLoadXml(Character.GetConfigFile(speciesName, contentPackage))?.Root?.Element("ragdolls")?.GetAttributeString("folder", string.Empty); + string configFilePath = Character.GetConfigFile(speciesName, contentPackage); + var folder = XMLExtensions.TryLoadXml(configFilePath)?.Root?.Element("ragdolls")?.GetAttributeString("folder", string.Empty); if (string.IsNullOrEmpty(folder) || folder.ToLowerInvariant() == "default") { - //DebugConsole.NewMessage("[RagollParams] Using the default folder."); - folder = GetDefaultFolder(speciesName); + folder = Path.Combine(Path.GetDirectoryName(configFilePath), "Ragdolls") + Path.DirectorySeparatorChar; } return folder; } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs b/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs index 43d64ee53..a87f4c7b9 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs @@ -1047,11 +1047,16 @@ namespace Barotrauma protected bool levitatingCollider = true; + /// + /// How long has the ragdoll stayed motionless + /// + private float bodyInRestTimer; + public bool forceStanding; public void Update(float deltaTime, Camera cam) { - if (!character.Enabled || Frozen || Invalid) return; + if (!character.Enabled || Frozen || Invalid) { return; } CheckValidity(); @@ -1063,6 +1068,8 @@ namespace Barotrauma FindHull(); PreventOutsideCollision(); + + CheckBodyInRest(deltaTime); splashSoundTimer -= deltaTime; @@ -1315,6 +1322,29 @@ namespace Barotrauma UpdateProjSpecific(deltaTime); } + private void CheckBodyInRest(float deltaTime) + { + if (Collider.LinearVelocity.LengthSquared() > 0.01f || character.SelectedBy != null || !character.IsDead) + { + bodyInRestTimer = 0.0f; + foreach (Limb limb in Limbs) + { + limb.body.PhysEnabled = true; + } + } + else if (Limbs.All(l => l != null && !l.body.Enabled || l.LinearVelocity.LengthSquared() < 0.001f)) + { + bodyInRestTimer += deltaTime; + if (bodyInRestTimer > 1.0f) + { + foreach (Limb limb in Limbs) + { + limb.body.PhysEnabled = false; + } + } + } + } + public bool Invalid { get; private set; } private int validityResets; private bool CheckValidity() diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Attack.cs b/Barotrauma/BarotraumaShared/Source/Characters/Attack.cs index f89ae0d22..287600bcc 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Attack.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Attack.cs @@ -298,7 +298,10 @@ namespace Barotrauma } float afflictionStrength = subElement.GetAttributeFloat(1.0f, "amount", "strength"); - Afflictions.Add(afflictionPrefab.Instantiate(afflictionStrength)); + var affliction = afflictionPrefab.Instantiate(afflictionStrength); + affliction.ApplyProbability = subElement.GetAttributeFloat("probability", 1.0f); + Afflictions.Add(affliction); + break; case "conditional": foreach (XAttribute attribute in subElement.Attributes()) diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Character.cs b/Barotrauma/BarotraumaShared/Source/Characters/Character.cs index dba7e62e3..1ee5c62e8 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Character.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Character.cs @@ -743,31 +743,33 @@ namespace Barotrauma PressureProtection = 100.0f; } + List inventoryElements = new List(); + List inventoryCommonness = new List(); + List healthElements = new List(); + List healthCommonness = new List(); foreach (XElement subElement in doc.Root.Elements()) { switch (subElement.Name.ToString().ToLowerInvariant()) { case "inventory": - Inventory = new CharacterInventory(subElement, this); + inventoryElements.Add(subElement); + inventoryCommonness.Add(subElement.GetAttributeFloat("commonness", 1.0f)); break; case "health": - CharacterHealth = new CharacterHealth(subElement, this); + healthElements.Add(subElement); + healthCommonness.Add(subElement.GetAttributeFloat("commonness", 1.0f)); break; case "statuseffect": statusEffects.Add(StatusEffect.Load(subElement, Name)); break; } } - - List healthElements = new List(); - List healthCommonness = new List(); - foreach (XElement element in doc.Root.Elements()) + if (inventoryElements.Count > 0) { - if (element.Name.ToString().ToLowerInvariant() != "health") continue; - healthElements.Add(element); - healthCommonness.Add(element.GetAttributeFloat("commonness", 1.0f)); + Inventory = new CharacterInventory( + inventoryElements.Count == 1 ? inventoryElements[0] : ToolBox.SelectWeightedRandom(inventoryElements, inventoryCommonness, random), + this); } - if (healthElements.Count == 0) { CharacterHealth = new CharacterHealth(this); @@ -834,6 +836,7 @@ namespace Barotrauma #if CLIENT head.LoadHuskSprite(); + head.LoadHerpesSprite(); #endif } @@ -977,7 +980,30 @@ namespace Barotrauma return false; } #endif - + if (inputType == InputType.Up || inputType == InputType.Down || + inputType == InputType.Left || inputType == InputType.Right) + { + var invertControls = CharacterHealth.GetAffliction("invertcontrols"); + if (invertControls != null) + { + switch (inputType) + { + case InputType.Left: + inputType = InputType.Right; + break; + case InputType.Right: + inputType = InputType.Left; + break; + case InputType.Up: + inputType = InputType.Down; + break; + case InputType.Down: + inputType = InputType.Up; + break; + } + } + } + return keys[(int)inputType].Held; } @@ -1579,9 +1605,16 @@ namespace Barotrauma //locked wires are never interactable if (wire.Locked) return false; - //wires are interactable if the character has selected either of the items the wire is connected to - if (wire.Connections[0]?.Item != null && SelectedConstruction == wire.Connections[0].Item) return true; - if (wire.Connections[1]?.Item != null && SelectedConstruction == wire.Connections[1].Item) return true; + //wires are interactable if the character has selected an item the wire is connected to, + //and it's disconnected from the other end + if (wire.Connections[0]?.Item != null && SelectedConstruction == wire.Connections[0].Item) + { + return wire.Connections[1] == null; + } + if (wire.Connections[1]?.Item != null && SelectedConstruction == wire.Connections[1].Item) + { + return wire.Connections[0] == null; + } } if (checkLinked && item.DisplaySideBySideWhenLinked) @@ -1891,15 +1924,30 @@ namespace Barotrauma distSqr = Math.Min(distSqr, Vector2.DistanceSquared(otherCharacter.WorldPosition, c.WorldPosition)); } +#if SERVER + for (int i = 0; i < GameMain.Server.ConnectedClients.Count; i++) + { + var spectatePos = GameMain.Server.ConnectedClients[i].SpectatePos; + if (spectatePos != null) + { + distSqr = Math.Min(distSqr, Vector2.DistanceSquared(spectatePos.Value, c.WorldPosition)); + } + } +#endif + if (distSqr > NetConfig.DisableCharacterDistSqr) { c.Enabled = false; + if (c.IsDead && c.AIController is EnemyAIController) + { + Entity.Spawner?.AddToRemoveQueue(c); + } } else if (distSqr < NetConfig.EnableCharacterDistSqr) { c.Enabled = true; } - } + } } else if (Submarine.MainSub != null) { @@ -1907,19 +1955,22 @@ namespace Barotrauma float distSqr = Vector2.DistanceSquared(Submarine.MainSub.WorldPosition, c.WorldPosition); if (Controlled != null) { - distSqr = Math.Min(distSqr, Vector2.DistanceSquared(Controlled.WorldPosition, c.WorldPosition)); + distSqr = Math.Min(distSqr, Vector2.DistanceSquared(GameMain.GameScreen.Cam.GetPosition(), c.WorldPosition)); } - + if (distSqr > NetConfig.DisableCharacterDistSqr) { c.Enabled = false; + if (c.IsDead && c.AIController is EnemyAIController) + { + Entity.Spawner?.AddToRemoveQueue(c); + } } - else if ( distSqr < NetConfig.EnableCharacterDistSqr) + else if (distSqr < NetConfig.EnableCharacterDistSqr) { c.Enabled = true; } } - } } @@ -2261,8 +2312,6 @@ namespace Barotrauma speechBubbleColor = color; } - partial void AdjustKarma(Character attacker, AttackResult attackResult); - partial void DamageHUD(float amount); public void SetAllDamage(float damageAmount, float bleedingDamageAmount, float burnDamageAmount) @@ -2358,7 +2407,12 @@ namespace Barotrauma { hitLimb = null; - if (Removed) return new AttackResult(); + if (Removed) { return new AttackResult(); } + + if (attacker != null && GameMain.NetworkMember != null && !GameMain.NetworkMember.ServerSettings.AllowFriendlyFire) + { + if (attacker.TeamID == TeamID) { return new AttackResult(); } + } float closestDistance = 0.0f; foreach (Limb limb in AnimController.Limbs) @@ -2376,7 +2430,12 @@ namespace Barotrauma public AttackResult DamageLimb(Vector2 worldPosition, Limb hitLimb, List afflictions, float stun, bool playSound, float attackImpulse, Character attacker = null) { - if (Removed) return new AttackResult(); + if (Removed) { return new AttackResult(); } + + if (attacker != null && attacker != this && GameMain.NetworkMember != null && !GameMain.NetworkMember.ServerSettings.AllowFriendlyFire) + { + if (attacker.TeamID == TeamID) { return new AttackResult(); } + } SetStun(stun); Vector2 dir = hitLimb.WorldPosition - worldPosition; @@ -2395,7 +2454,6 @@ namespace Barotrauma OnAttacked?.Invoke(attacker, attackResult); OnAttackedProjSpecific(attacker, attackResult); }; - AdjustKarma(attacker, attackResult); if (attacker != null && attackResult.Damage > 0.0f) { diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Health/Afflictions/Affliction.cs b/Barotrauma/BarotraumaShared/Source/Characters/Health/Afflictions/Affliction.cs index 384803e03..d9cc9f827 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Health/Afflictions/Affliction.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Health/Afflictions/Affliction.cs @@ -18,6 +18,11 @@ namespace Barotrauma public float StrengthDiminishMultiplier = 1.0f; public Affliction MultiplierSource; + /// + /// Probability for the affliction to be applied. Used by attacks. + /// + public float ApplyProbability; + /// /// Which character gave this affliction /// @@ -162,6 +167,7 @@ namespace Barotrauma foreach (StatusEffect statusEffect in currentEffect.StatusEffects) { + statusEffect.SetUser(Source); if (statusEffect.HasTargetType(StatusEffect.TargetType.Character)) { statusEffect.Apply(ActionType.OnActive, deltaTime, characterHealth.Character, characterHealth.Character); diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Health/Afflictions/AfflictionPrefab.cs b/Barotrauma/BarotraumaShared/Source/Characters/Health/Afflictions/AfflictionPrefab.cs index 708e62bee..c6d60ea31 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Health/Afflictions/AfflictionPrefab.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Health/Afflictions/AfflictionPrefab.cs @@ -157,6 +157,9 @@ namespace Barotrauma //how high the strength has to be for the affliction icon to be shown with a health scanner public readonly float ShowInHealthScannerThreshold = 0.05f; + //how much karma changes when a player applies this affliction to someone (per strength of the affliction) + public float KarmaChangeOnApplied; + public float BurnOverlayAlpha; public float DamageOverlayAlpha; @@ -265,9 +268,12 @@ namespace Barotrauma DamageOverlayAlpha = element.GetAttributeFloat("damageoverlayalpha", 0.0f); BurnOverlayAlpha = element.GetAttributeFloat("burnoverlayalpha", 0.0f); + KarmaChangeOnApplied = element.GetAttributeFloat("karmachangeonapplied", 0.0f); + CauseOfDeathDescription = TextManager.Get("AfflictionCauseOfDeath." + Identifier, true) ?? element.GetAttributeString("causeofdeathdescription", ""); SelfCauseOfDeathDescription = TextManager.Get("AfflictionCauseOfDeathSelf." + Identifier, true) ?? element.GetAttributeString("selfcauseofdeathdescription", ""); + AchievementOnRemoved = element.GetAttributeString("achievementonremoved", ""); foreach (XElement subElement in element.Elements()) diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Health/Afflictions/AfflictionSpaceHerpes.cs b/Barotrauma/BarotraumaShared/Source/Characters/Health/Afflictions/AfflictionSpaceHerpes.cs new file mode 100644 index 000000000..8e1f3dd45 --- /dev/null +++ b/Barotrauma/BarotraumaShared/Source/Characters/Health/Afflictions/AfflictionSpaceHerpes.cs @@ -0,0 +1,47 @@ +using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Barotrauma +{ + class AfflictionSpaceHerpes : Affliction + { + private float invertControlsCooldown = 60.0f; + private float stunCoolDown = 60.0f; + public AfflictionSpaceHerpes(AfflictionPrefab prefab, float strength) : base(prefab, strength) + { + } + + public override void Update(CharacterHealth characterHealth, Limb targetLimb, float deltaTime) + { + base.Update(characterHealth, targetLimb, deltaTime); + + if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; } + + invertControlsCooldown -= deltaTime; + if (invertControlsCooldown <= 0.0f) + { + //invert controls every 126-234 seconds when strength is close to 0 + //every 56-104 seconds when strength is close to 100 + invertControlsCooldown = (180.0f - Strength) * Rand.Range(0.7f, 1.3f); + var invertControlsAffliction = AfflictionPrefab.List.Find(ap => ap.Identifier == "invertcontrols"); + float invertControlsDuration = MathHelper.Lerp(10.0f, 60.0f, Strength / 100.0f) * Rand.Range(0.7f, 1.3f); + characterHealth.ApplyAffliction(null, new Affliction(invertControlsAffliction, invertControlsDuration)); + } + + if (Strength > 50.0f) + { + stunCoolDown -= deltaTime; + if (stunCoolDown <= 0.0f) + { + //stun every 126-234 seconds when strength is close to 0 + //stun 56-104 seconds when strength is close to 100 + stunCoolDown = (180.0f - Strength) * Rand.Range(0.7f, 1.3f); + float stunDuration = MathHelper.Lerp(3.0f, 10.0f, Strength / 100.0f) * Rand.Range(0.7f, 1.3f); + characterHealth.Character.SetStun(stunDuration); + } + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Health/CharacterHealth.cs b/Barotrauma/BarotraumaShared/Source/Characters/Health/CharacterHealth.cs index 6425a776e..97ce677aa 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Health/CharacterHealth.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Health/CharacterHealth.cs @@ -411,7 +411,7 @@ namespace Barotrauma amount -= reduceAmount; } } - + CalculateVitality(); } public void ApplyDamage(Limb hitLimb, AttackResult attackResult) diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Limb.cs b/Barotrauma/BarotraumaShared/Source/Characters/Limb.cs index 08446471c..e2f9b50bc 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Limb.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Limb.cs @@ -407,7 +407,7 @@ namespace Barotrauma { List appliedDamageModifiers = new List(); //create a copy of the original affliction list to prevent modifying the afflictions of an Attack/StatusEffect etc - afflictions = new List(afflictions); + afflictions = new List(afflictions.Where(a => Rand.Range(0.0f, 1.0f) <= a.ApplyProbability)); for (int i = 0; i < afflictions.Count; i++) { foreach (DamageModifier damageModifier in damageModifiers) diff --git a/Barotrauma/BarotraumaShared/Source/ContentPackage.cs b/Barotrauma/BarotraumaShared/Source/ContentPackage.cs index 8b1ececdc..ebeb9dbda 100644 --- a/Barotrauma/BarotraumaShared/Source/ContentPackage.cs +++ b/Barotrauma/BarotraumaShared/Source/ContentPackage.cs @@ -485,6 +485,22 @@ namespace Barotrauma List.Add(package); } } + + public void Delete() + { + try + { + File.Delete(Path); + GameMain.Config.SelectedContentPackages.Remove(this); + GameMain.Config.SaveNewPlayerConfig(); + } + catch (IOException e) + { + DebugConsole.ThrowError("Failed to delete content package \"" + Name + "\".", e); + return; + } + List.Remove(this); + } } public class ContentFile diff --git a/Barotrauma/BarotraumaShared/Source/CoroutineManager.cs b/Barotrauma/BarotraumaShared/Source/CoroutineManager.cs index 701d6259b..f0297a6a4 100644 --- a/Barotrauma/BarotraumaShared/Source/CoroutineManager.cs +++ b/Barotrauma/BarotraumaShared/Source/CoroutineManager.cs @@ -136,7 +136,7 @@ namespace Barotrauma if (!handle.Coroutine.MoveNext()) return; } } - catch (ThreadAbortException tae) + catch (ThreadAbortException) { //not an error, don't worry about it } diff --git a/Barotrauma/BarotraumaShared/Source/DebugConsole.cs b/Barotrauma/BarotraumaShared/Source/DebugConsole.cs index 85f68ba26..71be5c15c 100644 --- a/Barotrauma/BarotraumaShared/Source/DebugConsole.cs +++ b/Barotrauma/BarotraumaShared/Source/DebugConsole.cs @@ -110,7 +110,15 @@ namespace Barotrauma private static void AssignOnExecute(string names, Action onExecute) { - commands.First(c => c.names.Intersect(names.Split('|')).Count() > 0).OnExecute = onExecute; + var matchingCommand = commands.Find(c => c.names.Intersect(names.Split('|')).Count() > 0); + if (matchingCommand == null) + { + throw new Exception("AssignOnExecute failed. Command matching the name(s) \""+names+"\" not found."); + } + else + { + matchingCommand.OnExecute = onExecute; + } } static DebugConsole() @@ -251,16 +259,11 @@ namespace Barotrauma spawnPosParams.ToArray() }; }, isCheat: true)); - - + commands.Add(new Command("disablecrewai", "disablecrewai: Disable the AI of the NPCs in the crew.", (string[] args) => { HumanAIController.DisableCrewAI = true; NewMessage("Crew AI disabled", Color.Red); - // This is probably not where it should be? - //ThrowError("Karma has not been fully implemented yet, and is disabled in this version of Barotrauma."); - /*if (GameMain.Server == null) return; - GameMain.Server.KarmaEnabled = !GameMain.Server.KarmaEnabled;*/ })); commands.Add(new Command("enablecrewai", "enablecrewai: Enable the AI of the NPCs in the crew.", (string[] args) => @@ -304,8 +307,31 @@ namespace Barotrauma commands.Add(new Command("revokecommandperm", "revokecommandperm [id]: Revokes permission to use the specified console commands from the player with the specified client ID.", null)); commands.Add(new Command("showperm", "showperm [id]: Shows the current administrative permissions of the client with the specified client ID.", null)); + + commands.Add(new Command("respawnnow", "respawnnow: Trigger a respawn immediately if there are any clients waiting to respawn.", null)); - //commands.Add(new Command("togglekarma", "togglekarma: Toggles the karma system.", null)); + commands.Add(new Command("showkarma", "showkarma: Show the current karma values of the players.", null)); + commands.Add(new Command("togglekarma", "togglekarma: Toggle the karma system on/off.", null)); + commands.Add(new Command("resetkarma", "resetkarma [client]: Resets the karma value of the specified client to 100.", null, + () => + { + if (GameMain.NetworkMember?.ConnectedClients == null) { return null; } + return new string[][] + { + GameMain.NetworkMember.ConnectedClients.Select(c => c.Name).ToArray() + }; + })); + commands.Add(new Command("setkarma", "setkarma [client] [0-100]: Sets the karma of the specified client to the specified value.", null, + () => + { + if (GameMain.NetworkMember?.ConnectedClients == null) { return null; } + return new string[][] + { + GameMain.NetworkMember.ConnectedClients.Select(c => c.Name).ToArray(), + new string[] { "50" } + }; + })); + commands.Add(new Command("togglekarmatestmode", "togglekarmatestmode: Toggle the karma test mode on/off. When test mode is enabled, clients get notified when their karma value changes (including the reason for the increase/decrease) and the server doesn't ban clients whose karma decreases below the ban threshold.", null)); commands.Add(new Command("kick", "kick [name]: Kick a player out of the server.", (string[] args) => { @@ -654,11 +680,11 @@ namespace Barotrauma if (args.Length > 0 && args[0].ToLowerInvariant() == "start") { - Submarine.MainSub.SetPosition(Level.Loaded.StartPosition); + Submarine.MainSub.SetPosition(Level.Loaded.StartPosition - Vector2.UnitY * Submarine.MainSub.Borders.Height); } else { - Submarine.MainSub.SetPosition(Level.Loaded.EndPosition); + Submarine.MainSub.SetPosition(Level.Loaded.EndPosition - Vector2.UnitY * Submarine.MainSub.Borders.Height); } }, isCheat: true)); diff --git a/Barotrauma/BarotraumaShared/Source/Events/EventManager.cs b/Barotrauma/BarotraumaShared/Source/Events/EventManager.cs index b75f7012e..23317eb03 100644 --- a/Barotrauma/BarotraumaShared/Source/Events/EventManager.cs +++ b/Barotrauma/BarotraumaShared/Source/Events/EventManager.cs @@ -280,16 +280,23 @@ namespace Barotrauma float holeCount = 0.0f; floodingAmount = 0.0f; + int hullCount = 0; foreach (Hull hull in Hull.hullList) { if (hull.Submarine == null || hull.Submarine.IsOutpost) { continue; } + hullCount++; foreach (Gap gap in hull.ConnectedGaps) { if (!gap.IsRoomToRoom) holeCount += gap.Open; } - floodingAmount += hull.WaterVolume / hull.Volume / Hull.hullList.Count; + floodingAmount += hull.WaterVolume / hull.Volume; fireAmount += hull.FireSources.Sum(fs => fs.Size.X); } + if (hullCount > 0) + { + floodingAmount = floodingAmount / hullCount; + } + //hull integrity at 0.0 if there are 10 or more wide-open holes avgHullIntegrity = MathHelper.Clamp(1.0f - holeCount / 10.0f, 0.0f, 1.0f); diff --git a/Barotrauma/BarotraumaShared/Source/Events/Missions/CombatMission.cs b/Barotrauma/BarotraumaShared/Source/Events/Missions/CombatMission.cs index 09abef077..b0d878033 100644 --- a/Barotrauma/BarotraumaShared/Source/Events/Missions/CombatMission.cs +++ b/Barotrauma/BarotraumaShared/Source/Events/Missions/CombatMission.cs @@ -1,9 +1,5 @@ using Barotrauma.Items.Components; -using Barotrauma.Networking; -using Microsoft.Xna.Framework; using System.Collections.Generic; -using System.Linq; -using System.Xml.Linq; namespace Barotrauma { @@ -12,9 +8,6 @@ namespace Barotrauma private Submarine[] subs; private List[] crews; - private bool initialized = false; - private int state = 0; - private string[] descriptions; private static string[] teamNames = { "Team A", "Team B" }; diff --git a/Barotrauma/BarotraumaShared/Source/GameSession/GameModes/MultiPlayerCampaign.cs b/Barotrauma/BarotraumaShared/Source/GameSession/GameModes/MultiPlayerCampaign.cs index f172f2760..72ce67d80 100644 --- a/Barotrauma/BarotraumaShared/Source/GameSession/GameModes/MultiPlayerCampaign.cs +++ b/Barotrauma/BarotraumaShared/Source/GameSession/GameModes/MultiPlayerCampaign.cs @@ -81,10 +81,8 @@ namespace Barotrauma bool success = GameMain.Server.ConnectedClients.Any(c => c.InGame && c.Character != null && !c.Character.IsDead); - -#if CLIENT + success = success || (GameMain.Server.Character != null && !GameMain.Server.Character.IsDead); -#endif /*if (success) { @@ -119,11 +117,7 @@ namespace Barotrauma { c.Inventory?.DeleteAllItems(); } - -#if CLIENT - GameMain.GameSession.CrewManager.EndRound(); -#endif - + if (success) { bool atEndPosition = Submarine.MainSub.AtEndPosition; diff --git a/Barotrauma/BarotraumaShared/Source/Items/CharacterInventory.cs b/Barotrauma/BarotraumaShared/Source/Items/CharacterInventory.cs index 4b8d00255..dce74ab12 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/CharacterInventory.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/CharacterInventory.cs @@ -110,7 +110,25 @@ namespace Barotrauma public override bool CanBePut(Item item, int i) { return base.CanBePut(item, i) && item.AllowedSlots.Contains(SlotTypes[i]); - } + } + + /// + /// If there is no room in the generic inventory (InvSlotType.Any), check if the item can be auto-equipped into its respective limbslot + /// + public bool TryPutItemWithAutoEquipCheck(Item item, Character user, List allowedSlots = null, bool createNetworkEvent = true) + { + // Does not auto-equip the item if specified and no suitable any slot found (for example handcuffs are not auto-equipped) + if (item.AllowedSlots.Contains(InvSlotType.Any)) + { + var wearable = item.GetComponent(); + if (wearable != null && !wearable.AutoEquipWhenFull && CheckIfAnySlotAvailable(item, false) == -1) + { + return false; + } + } + + return TryPutItem(item, user, allowedSlots, createNetworkEvent); + } /// /// If there is room, puts the item in the inventory and returns true, otherwise returns false @@ -139,29 +157,10 @@ namespace Barotrauma //try to place the item in a LimbSlot.Any slot if that's allowed if (allowedSlots.Contains(InvSlotType.Any)) { - for (int i = 0; i < capacity; i++) + int freeIndex = CheckIfAnySlotAvailable(item, inWrongSlot); + if (freeIndex > -1) { - if (SlotTypes[i] != InvSlotType.Any) continue; - if (Items[i] == item) - { - PutItem(item, i, user, true, createNetworkEvent); - item.Unequip(character); - return true; - } - } - for (int i = 0; i < capacity; i++) - { - if (SlotTypes[i] != InvSlotType.Any) continue; - if (inWrongSlot) - { - if (Items[i] != item && Items[i] != null) continue; - } - else - { - if (Items[i] != null) continue; - } - - PutItem(item, i, user, true, createNetworkEvent); + PutItem(item, freeIndex, user, true, createNetworkEvent); item.Unequip(character); return true; } @@ -242,10 +241,37 @@ namespace Barotrauma } } - return placedInSlot > -1; } + public int CheckIfAnySlotAvailable(Item item, bool inWrongSlot) + { + for (int i = 0; i < capacity; i++) + { + if (SlotTypes[i] != InvSlotType.Any) continue; + if (Items[i] == item) + { + return i; + } + } + for (int i = 0; i < capacity; i++) + { + if (SlotTypes[i] != InvSlotType.Any) continue; + if (inWrongSlot) + { + if (Items[i] != item && Items[i] != null) continue; + } + else + { + if (Items[i] != null) continue; + } + + return i; + } + + return -1; + } + public override bool TryPutItem(Item item, int index, bool allowSwapping, bool allowCombine, Character user, bool createNetworkEvent = true) { if (index < 0 || index >= Items.Length) diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/DockingPort.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/DockingPort.cs index 5ff19891e..fcfdc692e 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/DockingPort.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/DockingPort.cs @@ -312,6 +312,30 @@ namespace Barotrauma.Items.Components joint.CollideConnected = true; } + public int GetDir() + { + if (DockingDir != 0) { return DockingDir; } + + if (door != null) + { + if (door.LinkedGap.linkedTo.Count == 1) + { + return IsHorizontal ? + Math.Sign(door.Item.WorldPosition.X - door.LinkedGap.linkedTo[0].WorldPosition.X) : + Math.Sign(door.Item.WorldPosition.Y - door.LinkedGap.linkedTo[0].WorldPosition.Y); + } + } + + if (item.Submarine != null) + { + return IsHorizontal ? + Math.Sign(item.WorldPosition.X - item.Submarine.WorldPosition.X) : + Math.Sign(item.WorldPosition.Y - item.Submarine.WorldPosition.Y); + } + + return 0; + } + private void ConnectWireBetweenPorts() { Wire wire = item.GetComponent(); diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Door.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Door.cs index cf5b12bd3..7a34357e8 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Door.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Door.cs @@ -250,17 +250,14 @@ namespace Barotrauma.Items.Components if (item.Condition <= RepairThreshold) { return true; } if (requiredItems.Any() && !hasValidIdCard) { - ForceOpen(ActionType.OnPicked); + ToggleState(ActionType.OnPicked); } return false; } - private void ForceOpen(ActionType actionType) + private void ToggleState(ActionType actionType) { - SetState(PredictedState == null ? !isOpen : !PredictedState.Value, false, true); //crowbar function -#if CLIENT - PlaySound(actionType, item.WorldPosition, picker); -#endif + SetState(PredictedState == null ? !isOpen : !PredictedState.Value, false, true, forcedOpen: actionType == ActionType.OnPicked); } public override bool Select(Character character) @@ -272,7 +269,7 @@ namespace Barotrauma.Items.Components { float originalPickingTime = PickingTime; PickingTime = 0; - ForceOpen(ActionType.OnUse); + ToggleState(ActionType.OnUse); PickingTime = originalPickingTime; } else if (hasRequiredItems) @@ -538,11 +535,11 @@ namespace Barotrauma.Items.Components if (connection.Name == "toggle") { - SetState(!wasOpen, false, true); + SetState(!wasOpen, false, true, forcedOpen: false); } else if (connection.Name == "set_state") { - SetState(signal != "0", false, true); + SetState(signal != "0", false, true, forcedOpen: false); } #if SERVER @@ -555,9 +552,9 @@ namespace Barotrauma.Items.Components public void TrySetState(bool open, bool isNetworkMessage, bool sendNetworkMessage = false) { - SetState(open, isNetworkMessage, sendNetworkMessage); + SetState(open, isNetworkMessage, sendNetworkMessage, forcedOpen: false); } - partial void SetState(bool open, bool isNetworkMessage, bool sendNetworkMessage); + partial void SetState(bool open, bool isNetworkMessage, bool sendNetworkMessage, bool forcedOpen); } } diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Holdable.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Holdable.cs index ffb107200..c896f1e39 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Holdable.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Holdable.cs @@ -558,6 +558,30 @@ namespace Barotrauma.Items.Components } } + public override XElement Save(XElement parentElement) + { + if (!attachable) + { + return base.Save(parentElement); + } + + var tempMsg = DisplayMsg; + var tempPickKey = PickKey; + var tempRequiredItems = requiredItems; + + DisplayMsg = prevMsg; + PickKey = prevPickKey; + requiredItems = prevRequiredItems; + + XElement saveElement = base.Save(parentElement); + + DisplayMsg = tempMsg; + PickKey = tempPickKey; + requiredItems = tempRequiredItems; + + return saveElement; + } + public override void ServerWrite(NetBuffer msg, Client c, object[] extraData = null) { base.ServerWrite(msg, c, extraData); diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Pickable.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Pickable.cs index 69aa69502..15844682f 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Pickable.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Pickable.cs @@ -17,6 +17,8 @@ namespace Barotrauma.Items.Components private Character activePicker; + private CoroutineHandle pickingCoroutine; + public List AllowedSlots { get { return allowedSlots; } @@ -69,7 +71,7 @@ namespace Barotrauma.Items.Components #if SERVER item.CreateServerEvent(this); #endif - CoroutineManager.StartCoroutine(WaitForPick(picker, PickingTime)); + pickingCoroutine = CoroutineManager.StartCoroutine(WaitForPick(picker, PickingTime)); } return false; } @@ -81,7 +83,7 @@ namespace Barotrauma.Items.Components public virtual bool OnPicked(Character picker) { - if (picker.Inventory.TryPutItem(item, picker, allowedSlots)) + if (picker.Inventory.TryPutItemWithAutoEquipCheck(item, picker, allowedSlots)) { if (!picker.HasSelectedItem(item) && item.body != null) item.body.Enabled = false; this.picker = picker; @@ -136,7 +138,7 @@ namespace Barotrauma.Items.Components } #if CLIENT - picker.UpdateHUDProgressBar( + Character.Controlled?.UpdateHUDProgressBar( this, item.WorldPosition, pickTimer / requiredTime, @@ -160,13 +162,18 @@ namespace Barotrauma.Items.Components yield return CoroutineStatus.Success; } - private void StopPicking(Character picker) + protected void StopPicking(Character picker) { if (picker != null) { picker.AnimController.Anim = AnimController.Animation.None; picker.PickingItem = null; } + if (pickingCoroutine != null) + { + CoroutineManager.StopCoroutines(pickingCoroutine); + pickingCoroutine = null; + } activePicker = null; pickTimer = 0.0f; } diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/RangedWeapon.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/RangedWeapon.cs index 1a58bbbe4..1a60462f0 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/RangedWeapon.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/RangedWeapon.cs @@ -149,7 +149,7 @@ namespace Barotrauma.Items.Components Vector2 sourcePos = character?.AnimController == null ? item.SimPosition : character.AnimController.AimSourceSimPos; Vector2 barrelPos = TransformedBarrelPos; //make sure there's no obstacles between the base of the weapon (or the shoulder of the character) and the end of the barrel - if (Submarine.PickBody(sourcePos, barrelPos, projectile.IgnoredBodies) == null) + if (Submarine.PickBody(sourcePos, barrelPos, projectile.IgnoredBodies, Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionItemBlocking) == null) { //no obstacles -> we can spawn the projectile at the barrel projectilePos = barrelPos; diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/RepairTool.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/RepairTool.cs index e6839bd1e..54f8c1372 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/RepairTool.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/RepairTool.cs @@ -14,12 +14,23 @@ namespace Barotrauma.Items.Components { partial class RepairTool : ItemComponent { + public enum UseEnvironment + { + Air, Water, Both, None + }; + private readonly List fixableEntities; private Vector2 pickedPosition; private float activeTimer; private Vector2 debugRayStartPos, debugRayEndPos; - + + [Serialize("Both", false)] + public UseEnvironment UsableIn + { + get; set; + } + [Serialize(0.0f, false)] public float Range { get; set; } @@ -43,6 +54,9 @@ namespace Barotrauma.Items.Components [Serialize(false, false)] public bool RepairMultiple { get; set; } + [Serialize(0.0f, false)] + public float FireProbability { get; set; } + public Vector2 TransformedBarrelPos { get @@ -109,6 +123,29 @@ namespace Barotrauma.Items.Components return false; } + if (UsableIn == UseEnvironment.None) + { + ApplyStatusEffects(ActionType.OnFailure, deltaTime, character); + return false; + } + + if (character.AnimController.InWater) + { + if (UsableIn == UseEnvironment.Air) + { + ApplyStatusEffects(ActionType.OnFailure, deltaTime, character); + return false; + } + } + else + { + if (UsableIn == UseEnvironment.Water) + { + ApplyStatusEffects(ActionType.OnFailure, deltaTime, character); + return false; + } + } + Vector2 targetPosition = item.WorldPosition; targetPosition += new Vector2( (float)Math.Cos(item.body.Rotation), @@ -149,7 +186,7 @@ namespace Barotrauma.Items.Components { Repair(rayStart - character.Submarine.SimPosition, rayEnd - character.Submarine.SimPosition, deltaTime, character, degreeOfSuccess, ignoredBodies); } - + UseProjSpecific(deltaTime); return true; @@ -162,9 +199,12 @@ namespace Barotrauma.Items.Components private void Repair(Vector2 rayStart, Vector2 rayEnd, float deltaTime, Character user, float degreeOfSuccess, List ignoredBodies) { var collisionCategories = Physics.CollisionWall | Physics.CollisionCharacter | Physics.CollisionItem | Physics.CollisionLevel | Physics.CollisionRepair; + + float lastPickedFraction = 0.0f; if (RepairMultiple) { var bodies = Submarine.PickBodies(rayStart, rayEnd, ignoredBodies, collisionCategories, ignoreSensors: false, allowInsideFixture: true); + lastPickedFraction = Submarine.LastPickedFraction; Type lastHitType = null; hitCharacters.Clear(); foreach (Body body in bodies) @@ -194,6 +234,7 @@ namespace Barotrauma.Items.Components if (FixBody(user, deltaTime, degreeOfSuccess, body)) { + lastPickedFraction = Submarine.LastPickedBodyDist(body); if (bodyType != null) { lastHitType = bodyType; } } } @@ -205,13 +246,14 @@ namespace Barotrauma.Items.Components ignoredBodies, collisionCategories, ignoreSensors: false, customPredicate: (Fixture f) => { return f?.Body?.UserData != null; }, allowInsideFixture: true)); + lastPickedFraction = Submarine.LastPickedFraction; } if (ExtinguishAmount > 0.0f && item.CurrentHull != null) { fireSourcesInRange.Clear(); //step along the ray in 10% intervals, collecting all fire sources in the range - for (float x = 0.0f; x <= Submarine.LastPickedFraction; x += 0.1f) + for (float x = 0.0f; x <= lastPickedFraction; x += 0.1f) { Vector2 displayPos = ConvertUnits.ToDisplayUnits(rayStart + (rayEnd - rayStart) * x); if (item.CurrentHull.Submarine != null) { displayPos += item.CurrentHull.Submarine.Position; } @@ -230,6 +272,20 @@ namespace Barotrauma.Items.Components foreach (FireSource fs in fireSourcesInRange) { fs.Extinguish(deltaTime, ExtinguishAmount); +#if SERVER + GameMain.Server.KarmaManager.OnExtinguishingFire(user, deltaTime); +#endif + } + } + + + if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer) + { + if (Rand.Range(0.0f, 1.0f) < FireProbability * deltaTime) + { + Vector2 displayPos = ConvertUnits.ToDisplayUnits(rayStart + (rayEnd - rayStart) * lastPickedFraction * 0.9f); + if (item.CurrentHull.Submarine != null) { displayPos += item.CurrentHull.Submarine.Position; } + new FireSource(displayPos); } } } @@ -242,12 +298,12 @@ namespace Barotrauma.Items.Components if (targetBody.UserData is Structure targetStructure) { - if (!fixableEntities.Contains("structure") && !fixableEntities.Contains(targetStructure.Prefab.Identifier)) { return false; } if (targetStructure.IsPlatform) { return false; } - int sectionIndex = targetStructure.FindSectionIndex(ConvertUnits.ToDisplayUnits(pickedPosition)); if (sectionIndex < 0) { return false; } + if (!fixableEntities.Contains("structure") && !fixableEntities.Contains(targetStructure.Prefab.Identifier)) { return true; } + FixStructureProjSpecific(user, deltaTime, targetStructure, sectionIndex); targetStructure.AddDamage(sectionIndex, -StructureFixAmount * degreeOfSuccess, user); @@ -283,9 +339,7 @@ namespace Barotrauma.Items.Components else if (targetBody.UserData is Item targetItem) { targetItem.IsHighlighted = true; - - float prevCondition = targetItem.Condition; - + ApplyStatusEffectsOnTarget(user, deltaTime, ActionType.OnUse, targetItem.AllPropertyObjects); var levelResource = targetItem.GetComponent(); @@ -300,9 +354,9 @@ namespace Barotrauma.Items.Components targetItem.WorldPosition, levelResource.DeattachTimer / levelResource.DeattachDuration, Color.Red, Color.Green); -#endif +#endif } - FixItemProjSpecific(user, deltaTime, targetItem, prevCondition); + FixItemProjSpecific(user, deltaTime, targetItem); return true; } return false; @@ -310,13 +364,12 @@ namespace Barotrauma.Items.Components partial void FixStructureProjSpecific(Character user, float deltaTime, Structure targetStructure, int sectionIndex); partial void FixCharacterProjSpecific(Character user, float deltaTime, Character targetCharacter); - partial void FixItemProjSpecific(Character user, float deltaTime, Item targetItem, float prevCondition); + partial void FixItemProjSpecific(Character user, float deltaTime, Item targetItem); private float sinTime; public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective) { - Gap leak = objective.OperateTarget as Gap; - if (leak == null) return true; + if (!(objective.OperateTarget is Gap leak)) return true; Vector2 fromItemToLeak = leak.WorldPosition - item.WorldPosition; float dist = fromItemToLeak.Length(); @@ -461,7 +514,7 @@ namespace Barotrauma.Items.Components } } } -#endif +#endif } } } diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Throwable.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Throwable.cs index 7e29caa55..45f4c2585 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Throwable.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Throwable.cs @@ -64,14 +64,19 @@ namespace Barotrauma.Items.Components return; } - if (picker.IsKeyDown(InputType.Aim) && picker.IsKeyHit(InputType.Shoot)) - throwing = true; + if (picker.IsKeyDown(InputType.Aim) && picker.IsKeyHit(InputType.Shoot)) { throwing = true; } + if (!picker.IsKeyDown(InputType.Aim) && !throwing) { throwPos = 0.0f; } + bool aim = picker.IsKeyDown(InputType.Aim) && (picker.SelectedConstruction == null || picker.SelectedConstruction.GetComponent() != null); - if (!picker.IsKeyDown(InputType.Aim) && !throwing) throwPos = 0.0f; + if (picker.IsUnconscious || picker.IsDead || !picker.AllowInput) + { + throwing = false; + aim = false; + } ApplyStatusEffects(ActionType.OnActive, deltaTime, picker); - if (item.body.Dir != picker.AnimController.Dir) Flip(); + if (item.body.Dir != picker.AnimController.Dir) { Flip(); } AnimController ac = picker.AnimController; @@ -79,7 +84,6 @@ namespace Barotrauma.Items.Components if (!throwing) { - bool aim = picker.IsKeyDown(InputType.Aim) && (picker.SelectedConstruction == null || picker.SelectedConstruction.GetComponent() != null); if (aim) { throwPos = MathUtils.WrapAnglePi(System.Math.Min(throwPos + deltaTime * 5.0f, MathHelper.PiOver2)); @@ -123,7 +127,8 @@ namespace Barotrauma.Items.Components } if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer) { - ApplyStatusEffects(ActionType.OnSecondaryUse, deltaTime, thrower); //Stun grenades, flares, etc. all have their throw-related things handled in "onSecondaryUse" + //Stun grenades, flares, etc. all have their throw-related things handled in "onSecondaryUse" + ApplyStatusEffects(ActionType.OnSecondaryUse, deltaTime, thrower, user: thrower); } throwing = false; } diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/ItemComponent.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/ItemComponent.cs index f24471693..93b1b199d 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/ItemComponent.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/ItemComponent.cs @@ -65,20 +65,25 @@ namespace Barotrauma.Items.Components } public Dictionary SerializableProperties { get; protected set; } - + + public float IsActiveTimer; public virtual bool IsActive { get { return isActive; } set { #if CLIENT - if (!value && isActive) + if (!value) { - StopSounds(ActionType.OnActive); + IsActiveTimer = 0.0f; + if (isActive) + { + StopSounds(ActionType.OnActive); + } } #endif if (AITarget != null) AITarget.Enabled = value; - isActive = value; + isActive = value; } } @@ -384,7 +389,10 @@ namespace Barotrauma.Items.Components item.Use(1.0f); break; case "toggle": - IsActive = !isActive; + if (signal != "0") + { + IsActive = !isActive; + } break; case "set_active": case "set_state": @@ -410,8 +418,10 @@ namespace Barotrauma.Items.Components { if (item.ParentInventory != null) { - Character owner = (Character)item.ParentInventory.Owner; - if (owner != null && owner.HasSelectedItem(item)) item.Unequip(owner); + if (item.ParentInventory.Owner is Character owner && owner.HasSelectedItem(item)) + { + item.Unequip(owner); + } item.ParentInventory.RemoveItem(item); } Entity.Spawner.AddToRemoveQueue(item); @@ -424,8 +434,10 @@ namespace Barotrauma.Items.Components { if (this.Item.ParentInventory != null) { - Character owner = (Character)this.Item.ParentInventory.Owner; - if (owner != null && owner.HasSelectedItem(this.Item)) this.Item.Unequip(owner); + if (this.Item.ParentInventory.Owner is Character owner && owner.HasSelectedItem(this.Item)) + { + this.Item.Unequip(owner); + } this.Item.ParentInventory.RemoveItem(this.Item); } Entity.Spawner.AddToRemoveQueue(this.Item); @@ -561,14 +573,14 @@ namespace Barotrauma.Items.Components public virtual void FlipY(bool relativeToSub) { } - public bool HasRequiredContainedItems(bool addMessage, string msg = null) + public bool HasRequiredContainedItems(Character user, bool addMessage, string msg = null) { if (!requiredItems.ContainsKey(RelatedItem.RelationType.Contained)) return true; if (item.OwnInventory == null) return false; foreach (RelatedItem ri in requiredItems[RelatedItem.RelationType.Contained]) { - if (!item.OwnInventory.Items.Any(it => it != null && it.Condition > 0.0f && ri.MatchesItem(it))) + if (!ri.CheckRequirements(user, item)) { #if CLIENT msg = msg ?? ri.Msg; diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Reactor.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Reactor.cs index e38964d4a..f0244b06b 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Reactor.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Reactor.cs @@ -74,14 +74,14 @@ namespace Barotrauma.Items.Components } } - [Editable(0.0f, float.MaxValue, ToolTip = "How long the temperature has to stay critical until a meltdown occurs."), Serialize(30.0f, true)] + [Editable(0.0f, float.MaxValue, ToolTip = "How long the temperature has to stay critical until a meltdown occurs."), Serialize(120.0f, true)] public float MeltdownDelay { get { return meltDownDelay; } set { meltDownDelay = Math.Max(value, 0.0f); } } - [Editable(0.0f, float.MaxValue, ToolTip = "How long the temperature has to stay critical until the reactor catches fire."), Serialize(10.0f, true)] + [Editable(0.0f, float.MaxValue, ToolTip = "How long the temperature has to stay critical until the reactor catches fire."), Serialize(30.0f, true)] public float FireDelay { get { return fireDelay; } @@ -132,6 +132,13 @@ namespace Barotrauma.Items.Components } } + [Serialize(false, true)] + public bool TemperatureCritical + { + get { return temperature > allowedTemperature.Y; } + set { /*do nothing*/ } + } + private float correctTurbineOutput; private float targetFissionRate; @@ -384,6 +391,14 @@ namespace Barotrauma.Items.Components float prevFireTimer = fireTimer; fireTimer += MathHelper.Lerp(deltaTime * 2.0f, deltaTime, item.Condition / item.MaxCondition); + +#if SERVER + if (fireTimer > Math.Min(5.0f, FireDelay / 2) && blameOnBroken?.Character?.SelectedConstruction == item) + { + GameMain.Server.KarmaManager.OnReactorOverHeating(blameOnBroken.Character, deltaTime); + } +#endif + if (fireTimer >= FireDelay && prevFireTimer < fireDelay) { new FireSource(item.WorldPosition); @@ -437,14 +452,8 @@ namespace Barotrauma.Items.Components private void MeltDown() { - if (item.Condition <= 0.0f) return; -#if CLIENT - if (GameMain.Client != null) return; -#endif - -#if SERVER - GameServer.Log("Reactor meltdown!", ServerLog.MessageType.ItemInteraction); -#endif + if (item.Condition <= 0.0f) { return; } + if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; } item.Condition = 0.0f; fireTimer = 0.0f; @@ -461,9 +470,10 @@ namespace Barotrauma.Items.Components } #if SERVER - if (GameMain.Server != null && GameMain.Server.ConnectedClients.Contains(blameOnBroken)) + GameServer.Log("Reactor meltdown!", ServerLog.MessageType.ItemInteraction); + if (GameMain.Server != null) { - blameOnBroken.Karma = 0.0f; + GameMain.Server.KarmaManager.OnReactorMeltdown(blameOnBroken?.Character); } #endif } diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Sonar.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Sonar.cs index 539ba0aac..726272639 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Sonar.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Sonar.cs @@ -9,6 +9,12 @@ namespace Barotrauma.Items.Components { partial class Sonar : Powered, IServerSerializable, IClientSerializable { + public enum Mode + { + Active, + Passive + }; + public const float DefaultSonarRange = 10000.0f; class ConnectedTransducer @@ -35,18 +41,30 @@ namespace Barotrauma.Items.Components private float range; - private float pingState; + private const float PingFrequency = 0.5f; + + private Mode currentMode = Mode.Passive; + + private class ActivePing + { + public float State; + public bool IsDirectional; + public Vector2 Direction; + public float PrevPingRadius; + } + // rotating list of currently active pings + private ActivePing[] activePings = new ActivePing[8]; + // total number of currently active pings, range [0, activePings.Length[ + private int activePingsCount; + // currently active ping index on the above list + private int currentPingIndex = -1; private const float MinZoom = 1.0f, MaxZoom = 4.0f; private float zoom = 1.0f; private bool useDirectionalPing = false; - private Vector2 lastPingDirection = new Vector2(1.0f, 0.0f); private Vector2 pingDirection = new Vector2(1.0f, 0.0f); - //was the last ping sent with directional pinging - private bool isLastPingDirectional; - private Sprite pingCircle, directionalPingCircle, screenOverlay, screenBackground; private Sprite sonarBlip; private Sprite lineSprite; @@ -86,24 +104,24 @@ namespace Barotrauma.Items.Components { get { return zoom; } } - - public override bool IsActive - { - get - { - return base.IsActive; - } + public Mode CurrentMode + { + get => currentMode; set { - base.IsActive = value; - if (!value && item.CurrentHull != null) + currentMode = value; + if (value == Mode.Passive) { - item.CurrentHull.AiTarget.SectorDegrees = 360.0f; + currentPingIndex = -1; + if (item.CurrentHull != null) + { + item.CurrentHull.AiTarget.SectorDegrees = 360.0f; + } } #if CLIENT - if (activeTickBox != null) activeTickBox.Selected = value; - if (passiveTickBox != null) passiveTickBox.Selected = !value; + if (activeTickBox != null) activeTickBox.Selected = value == Mode.Active; + if (passiveTickBox != null) passiveTickBox.Selected = value == Mode.Passive; #endif } } @@ -112,8 +130,9 @@ namespace Barotrauma.Items.Components : base(item, element) { connectedTransducers = new List(); - - IsActive = false; + + CurrentMode = Mode.Passive; + IsActive = true; InitProjSpecific(element); } @@ -133,40 +152,80 @@ namespace Barotrauma.Items.Components } connectedTransducers.RemoveAll(t => t.DisconnectTimer <= 0.0f); } - - if ((voltage >= minVoltage || powerConsumption <= 0.0f) && - (!UseTransducers || connectedTransducers.Count > 0)) + + for (var pingIndex = 0; pingIndex < activePingsCount; ++pingIndex) { - pingState = pingState + deltaTime * 0.5f; - if (pingState > 1.0f) + activePings[pingIndex].State += deltaTime * PingFrequency; + } + + if (currentMode == Mode.Active) + { + if ((voltage >= minVoltage || powerConsumption <= 0.0f) && + (!UseTransducers || connectedTransducers.Count > 0)) + { + if (currentPingIndex != -1) + { + var activePing = activePings[currentPingIndex]; + if (activePing.State > 1.0f) + { + if (item.CurrentHull != null) + { + item.CurrentHull.AiTarget.SoundRange = Math.Max(Range * activePing.State / zoom, item.CurrentHull.AiTarget.SoundRange); + item.CurrentHull.AiTarget.SectorDegrees = activePing.IsDirectional ? DirectionalPingSector : 360.0f; + item.CurrentHull.AiTarget.SectorDir = new Vector2(pingDirection.X, -pingDirection.Y); + } + if (item.AiTarget != null) + { + item.AiTarget.SoundRange = Math.Max(Range * activePing.State / zoom, item.AiTarget.SoundRange); + item.AiTarget.SectorDegrees = activePing.IsDirectional ? DirectionalPingSector : 360.0f; + item.AiTarget.SectorDir = new Vector2(pingDirection.X, -pingDirection.Y); + } + aiPingCheckPending = true; + currentPingIndex = -1; + } + } + if (currentPingIndex == -1 && activePingsCount < activePings.Length) + { + currentPingIndex = activePingsCount++; + if (activePings[currentPingIndex] == null) + { + activePings[currentPingIndex] = new ActivePing(); + } + activePings[currentPingIndex].IsDirectional = useDirectionalPing; + activePings[currentPingIndex].Direction = pingDirection; + activePings[currentPingIndex].State = 0.0f; + activePings[currentPingIndex].PrevPingRadius = 0.0f; + item.Use(deltaTime); + } + } + else { if (item.CurrentHull != null) { - item.CurrentHull.AiTarget.SoundRange = Math.Max(Range * pingState / zoom, item.CurrentHull.AiTarget.SoundRange); - item.CurrentHull.AiTarget.SectorDegrees = isLastPingDirectional ? DirectionalPingSector : 360.0f; - item.CurrentHull.AiTarget.SectorDir = new Vector2(pingDirection.X, -pingDirection.Y); + item.CurrentHull.AiTarget.SectorDegrees = 360.0f; } - if (item.AiTarget != null) - { - item.AiTarget.SoundRange = Math.Max(Range * pingState / zoom, item.AiTarget.SoundRange); - item.AiTarget.SectorDegrees = isLastPingDirectional ? DirectionalPingSector : 360.0f; - item.AiTarget.SectorDir = new Vector2(pingDirection.X, -pingDirection.Y); - } - aiPingCheckPending = true; - isLastPingDirectional = useDirectionalPing; - lastPingDirection = pingDirection; - item.Use(deltaTime); - pingState = 0.0f; + currentPingIndex = -1; + aiPingCheckPending = false; } } - else + + for (var pingIndex = 0; pingIndex < activePingsCount;) { - if (item.CurrentHull != null) + if (activePings[pingIndex].State > 1.0f) { - item.CurrentHull.AiTarget.SectorDegrees = 360.0f; + var lastIndex = --activePingsCount; + var oldActivePing = activePings[pingIndex]; + activePings[pingIndex] = activePings[lastIndex]; + activePings[lastIndex] = oldActivePing; + if (currentPingIndex == lastIndex) + { + currentPingIndex = pingIndex; + } + } + else + { + ++pingIndex; } - aiPingCheckPending = false; - pingState = 0.0f; } Voltage -= deltaTime; @@ -174,7 +233,7 @@ namespace Barotrauma.Items.Components public override bool Use(float deltaTime, Character character = null) { - return pingState > 1.0f; + return currentPingIndex != -1; } protected override void RemoveComponentSpecific() @@ -189,7 +248,7 @@ namespace Barotrauma.Items.Components public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective) { - if (!IsActive || !aiPingCheckPending) return false; + if (currentMode == Mode.Passive || !aiPingCheckPending) return false; Dictionary> targetGroups = new Dictionary>(); @@ -301,13 +360,13 @@ namespace Barotrauma.Items.Components } } - if (!item.CanClientAccess(c)) return; + if (!item.CanClientAccess(c)) return; - IsActive = isActive; + CurrentMode = isActive ? Mode.Active : Mode.Passive; //TODO: cleanup #if CLIENT - activeTickBox.Selected = IsActive; + activeTickBox.Selected = currentMode == Mode.Active; #endif if (isActive) { @@ -331,8 +390,8 @@ namespace Barotrauma.Items.Components public void ServerWrite(Lidgren.Network.NetBuffer msg, Client c, object[] extraData = null) { - msg.Write(IsActive); - if (IsActive) + msg.Write(currentMode == Mode.Active); + if (currentMode == Mode.Active) { msg.WriteRangedSingle(zoom, MinZoom, MaxZoom, 8); msg.Write(useDirectionalPing); diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerTransfer.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerTransfer.cs index 097e26985..b1b2350cb 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerTransfer.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerTransfer.cs @@ -198,11 +198,7 @@ namespace Barotrauma.Items.Components if (pt.item.Condition <= 0.0f && prevCondition > 0.0f) { #if CLIENT - if (sparkSounds.Count > 0) - { - var sparkSound = sparkSounds[Rand.Int(sparkSounds.Count)]; - SoundPlayer.PlaySound(sparkSound.Sound, pt.item.WorldPosition, sparkSound.Volume, sparkSound.Range, pt.item.CurrentHull); - } + SoundPlayer.PlaySound("zap", item.WorldPosition, hullGuess: item.CurrentHull); Vector2 baseVel = Rand.Vector(300.0f); for (int i = 0; i < 10; i++) @@ -333,22 +329,22 @@ namespace Barotrauma.Items.Components var recipients = c.Recipients; foreach (Connection recipient in recipients) { - if (recipient?.Item == null) continue; + if (recipient?.Item == null || !recipient.IsPower) { continue; } Item it = recipient.Item; - if (it.Condition <= 0.0f) continue; + if (it.Condition <= 0.0f) { continue; } foreach (ItemComponent ic in it.Components) { - if (!(ic is Powered powered) || !powered.IsActive) continue; - if (connectedList.Contains(powered)) continue; + if (!(ic is Powered powered) || !powered.IsActive) { continue; } + if (connectedList.Contains(powered)) { continue; } if (powered is PowerTransfer powerTransfer) { RelayComponent otherRelayComponent = powerTransfer as RelayComponent; if ((thisRelayComponent == null) == (otherRelayComponent == null)) { - if (!powerTransfer.CanTransfer) continue; + if (!powerTransfer.CanTransfer) { continue; } powerTransfer.CheckJunctions(deltaTime, increaseUpdateCount, clampPower, clampLoad); } else @@ -358,7 +354,7 @@ namespace Barotrauma.Items.Components float maxPowerOut = (thisRelayComponent != null && !c.IsOutput) ? 0.0f : clampLoad; if (maxPowerIn > 0.0f || maxPowerOut > 0.0f) { - powerTransfer.CheckJunctions(deltaTime, false, maxPowerIn, maxPowerOut); + powerTransfer.CheckJunctions(deltaTime, false, maxPowerIn, maxPowerOut); } } @@ -455,7 +451,7 @@ namespace Barotrauma.Items.Components } bool broken = recipient.Item.Condition <= 0.0f; - foreach (StatusEffect effect in recipient.effects) + foreach (StatusEffect effect in recipient.Effects) { if (broken && effect.type != ActionType.OnBroken) continue; recipient.Item.ApplyStatusEffect(effect, ActionType.OnUse, 1.0f, null, null, false, false); diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Power/Powered.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Power/Powered.cs index 7f3ee64e6..60b0e2748 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Power/Powered.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Power/Powered.cs @@ -45,7 +45,10 @@ namespace Barotrauma.Items.Components set { base.IsActive = value; - if (!value) currPowerConsumption = 0.0f; + if (!value) + { + currPowerConsumption = 0.0f; + } } } diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Projectile.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Projectile.cs index d155ff108..22e00938a 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Projectile.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Projectile.cs @@ -113,6 +113,13 @@ namespace Barotrauma.Items.Components set; } + [Serialize(1, false)] + public int HitScanCount + { + get; + set; + } + [Serialize(false, false)] public bool RemoveOnHit { @@ -120,6 +127,13 @@ namespace Barotrauma.Items.Components set; } + [Serialize(0.0f, false)] + public float Spread + { + get; + set; + } + public Projectile(Item item, XElement element) : base (item, element) { @@ -154,17 +168,25 @@ namespace Barotrauma.Items.Components public override bool Use(float deltaTime, Character character = null) { - if (character != null && !characterUsable) return false; + if (character != null && !characterUsable) { return false; } - Vector2 launchDir = new Vector2((float)Math.Cos(item.body.Rotation), (float)Math.Sin(item.body.Rotation)); - - if (Hitscan) + for (int i = 0; i < HitScanCount; i++) { - DoHitscan(launchDir); - } - else - { - Launch(launchDir * launchImpulse * item.body.Mass); + float launchAngle = item.body.Rotation + MathHelper.ToRadians(Rand.Range(-Spread, Spread)); + Vector2 launchDir = new Vector2((float)Math.Cos(launchAngle), (float)Math.Sin(launchAngle)); + if (Hitscan) + { + Vector2 prevSimpos = item.SimPosition; + DoHitscan(launchDir); + if (i < HitScanCount - 1) + { + item.SetTransform(prevSimpos, item.body.Rotation); + } + } + else + { + Launch(launchDir * launchImpulse * item.body.Mass); + } } User = character; @@ -306,6 +328,9 @@ namespace Barotrauma.Items.Components !fixture.CollisionCategories.HasFlag(Physics.CollisionWall) && !fixture.CollisionCategories.HasFlag(Physics.CollisionLevel)) return true; + fixture.Body.GetTransform(out FarseerPhysics.Common.Transform transform); + if (!fixture.Shape.TestPoint(ref transform, ref rayStart)) { return true; } + hits.Add(new HitscanResult(fixture, rayStart, -dir, 0.0f)); return true; }, ref aabb); diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Repairable.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Repairable.cs index 07af6777c..183fed5da 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Repairable.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Repairable.cs @@ -189,7 +189,11 @@ namespace Barotrauma.Items.Components } else { - item.Condition += deltaTime / (fixDuration / item.MaxCondition); + float conditionIncrease = deltaTime / (fixDuration / item.MaxCondition); + item.Condition += conditionIncrease; +#if SERVER + GameMain.Server.KarmaManager.OnItemRepaired(CurrentFixer, this, conditionIncrease); +#endif } if (wasBroken && item.IsFullCondition) diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/Connection.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/Connection.cs index d12dfc5a9..541036a31 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/Connection.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/Connection.cs @@ -24,7 +24,7 @@ namespace Barotrauma.Items.Components public readonly bool IsOutput; - public readonly List effects; + public readonly List Effects; public readonly ushort[] wireId; @@ -135,7 +135,7 @@ namespace Barotrauma.Items.Components IsPower = Name == "power_in" || Name == "power" || Name == "power_out"; - effects = new List(); + Effects = new List(); wireId = new ushort[MaxLinked]; @@ -158,7 +158,7 @@ namespace Barotrauma.Items.Components break; case "statuseffect": - effects.Add(StatusEffect.Load(subElement, item.Name + ", connection " + Name)); + Effects.Add(StatusEffect.Load(subElement, item.Name + ", connection " + Name)); break; } } @@ -222,6 +222,7 @@ namespace Barotrauma.Items.Components recipientsDirty = true; if (wire != null) { + ConnectionPanel.DisconnectedWires.Remove(wire); var otherConnection = wire.OtherConnection(this); if (otherConnection != null) { @@ -251,10 +252,10 @@ namespace Barotrauma.Items.Components } bool broken = recipient.Item.Condition <= 0.0f; - foreach (StatusEffect effect in recipient.effects) + foreach (StatusEffect effect in recipient.Effects) { if (broken && effect.type != ActionType.OnBroken) continue; - recipient.Item.ApplyStatusEffect(effect, ActionType.OnUse, 1.0f, null, null, false, false); + recipient.Item.ApplyStatusEffect(effect, ActionType.OnUse, (float)Timing.Step, null, null, false, false); } } } @@ -277,11 +278,9 @@ namespace Barotrauma.Items.Components for (int i = 0; i < MaxLinked; i++) { - if (wireId[i] == 0) continue; + if (wireId[i] == 0) { continue; } - Item wireItem = Entity.FindEntityByID(wireId[i]) as Item; - - if (wireItem == null) continue; + if (!(Entity.FindEntityByID(wireId[i]) is Item wireItem)) { continue; } wires[i] = wireItem.GetComponent(); recipientsDirty = true; diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/ConnectionPanel.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/ConnectionPanel.cs index f9fa63b30..781568b39 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/ConnectionPanel.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/ConnectionPanel.cs @@ -15,6 +15,11 @@ namespace Barotrauma.Items.Components private Character user; + /// + /// Wires that have been disconnected from the panel, but not removed completely (visible at the bottom of the connection panel). + /// + public readonly HashSet DisconnectedWires = new HashSet(); + [Serialize(false, true), Editable(ToolTip = "Locked connection panels cannot be rewired in-game.")] public bool Locked { @@ -103,6 +108,23 @@ namespace Barotrauma.Items.Components public override void Update(float deltaTime, Camera cam) { +#if CLIENT + foreach (Wire wire in DisconnectedWires) + { + if (Rand.Range(0.0f, 500.0f) < 1.0f) + { + SoundPlayer.PlaySound("zap", item.WorldPosition, hullGuess: item.CurrentHull); + Vector2 baseVel = new Vector2(0.0f, -100.0f); + for (int i = 0; i < 5; i++) + { + var particle = GameMain.ParticleManager.CreateParticle("spark", item.WorldPosition, + baseVel + Rand.Vector(100.0f), 0.0f, item.CurrentHull); + if (particle != null) { particle.Size *= Rand.Range(0.5f, 1.0f); } + } + } + } +#endif + if (user == null || user.SelectedConstruction != item) { user = null; @@ -192,11 +214,12 @@ namespace Barotrauma.Items.Components protected override void RemoveComponentSpecific() { + DisconnectedWires.Clear(); foreach (Connection c in Connections) { foreach (Wire wire in c.Wires) { - if (wire == null) continue; + if (wire == null) { continue; } if (wire.OtherConnection(c) == null) //wire not connected to anything else { @@ -219,6 +242,12 @@ namespace Barotrauma.Items.Components msg.Write(wire?.Item == null ? (ushort)0 : wire.Item.ID); } } + + msg.Write((ushort)DisconnectedWires.Count()); + foreach (Wire disconnectedWire in DisconnectedWires) + { + msg.Write(disconnectedWire.Item.ID); + } } } } diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/LightComponent.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/LightComponent.cs index 9015e1159..6ce8d7502 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/LightComponent.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/LightComponent.cs @@ -165,6 +165,10 @@ namespace Barotrauma.Items.Components { base.OnItemLoaded(); itemLoaded = true; +#if CLIENT + light.Color = IsActive ? lightColor : Color.Transparent; + if (!IsActive) lightBrightness = 0.0f; +#endif } public override void Update(float deltaTime, Camera cam) @@ -217,10 +221,9 @@ namespace Barotrauma.Items.Components if (Rand.Range(0.0f, 1.0f) < 0.05f && voltage < Rand.Range(0.0f, minVoltage)) { #if CLIENT - if (voltage > 0.1f && sparkSounds.Count > 0) + if (voltage > 0.1f) { - var sparkSound = sparkSounds[Rand.Int(sparkSounds.Count)]; - SoundPlayer.PlaySound(sparkSound.Sound, item.WorldPosition, sparkSound.Volume, sparkSound.Range, item.CurrentHull); + SoundPlayer.PlaySound("zap", item.WorldPosition, hullGuess: item.CurrentHull); } #endif lightBrightness = 0.0f; diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/RelayComponent.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/RelayComponent.cs index c31ea5628..3ea9643ad 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/RelayComponent.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/RelayComponent.cs @@ -1,6 +1,7 @@ using Barotrauma.Networking; using Lidgren.Network; using System; +using System.Collections.Generic; using System.Xml.Linq; namespace Barotrauma.Items.Components @@ -11,6 +12,17 @@ namespace Barotrauma.Items.Components private bool isOn; + private static readonly Dictionary connectionPairs = new Dictionary + { + { "power_in", "power_out"}, + { "signal_in", "signal_out" }, + { "signal_in1", "signal_out1" }, + { "signal_in2", "signal_out2" }, + { "signal_in3", "signal_out3" }, + { "signal_in4", "signal_out4" }, + { "signal_in5", "signal_out5" } + }; + [Editable, Serialize(1000.0f, true)] public float MaxPower { @@ -59,17 +71,11 @@ namespace Barotrauma.Items.Components public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f, float signalStrength = 1.0f) { - if (connection.IsPower || item.Condition <= 0.0f) return; + if (connection.IsPower || item.Condition <= 0.0f) { return; } - if (connection.Name.Contains("_in")) + if (connectionPairs.TryGetValue(connection.Name, out string outConnection)) { - if (!IsOn) return; - - string outConnection = connection.Name.Contains("power_in") ? "power_out" : "signal_out"; - - int connectionNumber = -1; - int.TryParse(connection.Name.Substring(connection.Name.Length - 1, 1), out connectionNumber); - if (connectionNumber > 0) outConnection += connectionNumber; + if (!IsOn) { return; } item.SendSignal(stepsTaken, signal, outConnection, sender, power, source, signalStrength); } else if (connection.Name == "toggle") diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/WifiComponent.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/WifiComponent.cs index 9f9923583..addb59590 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/WifiComponent.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/WifiComponent.cs @@ -73,7 +73,7 @@ namespace Barotrauma.Items.Components public bool CanTransmit() { - return HasRequiredContainedItems(true); + return HasRequiredContainedItems(user: null, addMessage: false); } public IEnumerable GetReceiversInRange() @@ -89,7 +89,7 @@ namespace Barotrauma.Items.Components if (Vector2.DistanceSquared(item.WorldPosition, sender.item.WorldPosition) > sender.range * sender.range) { return false; } - return HasRequiredContainedItems(false); + return HasRequiredContainedItems(user: null, addMessage: false); } public override void Update(float deltaTime, Camera cam) diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/Wire.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/Wire.cs index e67851762..6b4c8736e 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/Wire.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/Wire.cs @@ -16,8 +16,8 @@ namespace Barotrauma.Items.Components private Vector2 start; private Vector2 end; - private float angle; - private float length; + private readonly float angle; + private readonly float length; public Vector2 Start { @@ -45,7 +45,7 @@ namespace Barotrauma.Items.Components const int MaxNodesPerNetworkEvent = 30; private List nodes; - private List sections; + private readonly List sections; private Connection[] connections; @@ -85,24 +85,23 @@ namespace Barotrauma.Items.Components #if CLIENT if (wireSprite == null) { - wireSprite = new Sprite("Content/Items/wireHorizontal.png", new Vector2(0.5f, 0.5f)); - wireSprite.Depth = 0.85f; + wireSprite = new Sprite("Content/Items/wireHorizontal.png", new Vector2(0.5f, 0.5f)) + { + Depth = 0.85f + }; } #endif nodes = new List(); sections = new List(); - - connections = new Connection[2]; - + connections = new Connection[2]; IsActive = false; } - + public Connection OtherConnection(Connection connection) { - if (connection == null) return null; - if (connection == connections[0]) return connections[1]; - if (connection == connections[1]) return connections[0]; + if (connection == connections[0]) { return connections[1]; } + if (connection == connections[1]) { return connections[0]; } return null; } @@ -133,8 +132,8 @@ namespace Barotrauma.Items.Components public void RemoveConnection(Connection connection) { - if (connection == connections[0]) connections[0] = null; - if (connection == connections[1]) connections[1] = null; + if (connection == connections[0]) { connections[0] = null; } + if (connection == connections[1]) { connections[1] = null; } SetConnectedDirty(); } @@ -143,10 +142,10 @@ namespace Barotrauma.Items.Components { for (int i = 0; i < 2; i++) { - if (connections[i] == newConnection) return false; + if (connections[i] == newConnection) { return false; } } - if (!connections.Any(c => c == null)) return false; + if (!connections.Any(c => c == null)) { return false; } for (int i = 0; i < 2; i++) { @@ -156,37 +155,39 @@ namespace Barotrauma.Items.Components break; } } + if (item.body != null) { item.Submarine = newConnection.Item.Submarine; } - if (item.body != null) item.Submarine = newConnection.Item.Submarine; + newConnection.ConnectionPanel.DisconnectedWires.Remove(this); for (int i = 0; i < 2; i++) { - if (connections[i] != null) continue; + if (connections[i] != null) { continue; } connections[i] = newConnection; + FixNodeEnds(); - if (!addNode) break; + if (!addNode) { break; } Submarine refSub = newConnection.Item.Submarine; if (refSub == null) { Structure attachTarget = Structure.GetAttachTarget(newConnection.Item.WorldPosition); - if (attachTarget == null) continue; + if (attachTarget == null) { continue; } refSub = attachTarget.Submarine; } Vector2 nodePos = refSub == null ? newConnection.Item.Position : newConnection.Item.Position - refSub.HiddenSubPosition; - - if (nodes.Count > 0 && nodes[0] == nodePos) break; - if (nodes.Count > 1 && nodes[nodes.Count - 1] == nodePos) break; + + if (nodes.Count > 0 && nodes[0] == nodePos) { break; } + if (nodes.Count > 1 && nodes[nodes.Count - 1] == nodePos) { break; } //make sure we place the node at the correct end of the wire (the end that's closest to the new node pos) int newNodeIndex = 0; if (nodes.Count > 1) { - if (Vector2.DistanceSquared(nodes[nodes.Count-1], nodePos) < Vector2.DistanceSquared(nodes[0], nodePos)) + if (Vector2.DistanceSquared(nodes[nodes.Count - 1], nodePos) < Vector2.DistanceSquared(nodes[0], nodePos)) { newNodeIndex = nodes.Count; } @@ -244,21 +245,18 @@ namespace Barotrauma.Items.Components public override void Equip(Character character) { ClearConnections(character); - IsActive = true; } public override void Unequip(Character character) { ClearConnections(character); - IsActive = false; } public override void Drop(Character dropper) { - ClearConnections(dropper); - + ClearConnections(dropper); IsActive = false; } @@ -399,7 +397,6 @@ namespace Barotrauma.Items.Components public override bool Pick(Character picker) { ClearConnections(picker); - return true; } @@ -467,9 +464,26 @@ namespace Barotrauma.Items.Components nodes.Clear(); sections.Clear(); + foreach (Item item in Item.ItemList) + { + var connectionPanel = item.GetComponent(); + if (connectionPanel != null && connectionPanel.DisconnectedWires.Contains(this)) + { +#if SERVER + item.CreateServerEvent(connectionPanel); +#endif + connectionPanel.DisconnectedWires.Remove(this); + } + } + #if SERVER if (user != null) { + if (connections[0] != null || connections[1] != null) + { + GameMain.Server.KarmaManager.OnWireDisconnected(user, this); + } + if (connections[0] != null && connections[1] != null) { GameServer.Log(user.LogName + " disconnected a wire from " + @@ -488,17 +502,21 @@ namespace Barotrauma.Items.Components } } #endif - + SetConnectedDirty(); for (int i = 0; i < 2; i++) { - if (connections[i] == null) continue; + if (connections[i] == null) { continue; } int wireIndex = connections[i].FindWireIndex(item); - - if (wireIndex == -1) continue; + if (wireIndex == -1) { continue; } +#if SERVER + if (!connections[i].Item.Removed) + { + connections[i].Item.CreateServerEvent(connections[i].Item.GetComponent()); + } +#endif connections[i].SetWire(wireIndex, null); - connections[i] = null; } @@ -565,7 +583,27 @@ namespace Barotrauma.Items.Components } } while (removed); + } + private void FixNodeEnds() + { + if (connections[0] == null || connections[1] == null || nodes.Count == 0) { return; } + + Vector2 nodePos = nodes[0]; + + Submarine refSub = connections[0].Item.Submarine ?? connections[1].Item.Submarine; + if (refSub != null) { nodePos += refSub.HiddenSubPosition; } + + float dist1 = Vector2.DistanceSquared(connections[0].Item.Position, nodePos); + float dist2 = Vector2.DistanceSquared(connections[1].Item.Position, nodePos); + + //first node is closer to the second item + //= the nodes are "backwards", need to reverse them + if (dist1 > dist2) + { + nodes.Reverse(); + UpdateSections(); + } } private int GetClosestNodeIndex(Vector2 pos, float maxDist, out float closestDist) @@ -640,12 +678,8 @@ namespace Barotrauma.Items.Components string[] nodeCoords = nodeString.Split(';'); for (int i = 0; i < nodeCoords.Length / 2; i++) { - float x = 0.0f, y = 0.0f; - - float.TryParse(nodeCoords[i * 2], NumberStyles.Float, CultureInfo.InvariantCulture, out x); - - float.TryParse(nodeCoords[i * 2 + 1], NumberStyles.Float, CultureInfo.InvariantCulture, out y); - + float.TryParse(nodeCoords[i * 2], NumberStyles.Float, CultureInfo.InvariantCulture, out float x); + float.TryParse(nodeCoords[i * 2 + 1], NumberStyles.Float, CultureInfo.InvariantCulture, out float y); nodes.Add(new Vector2(x, y)); } @@ -687,7 +721,6 @@ namespace Barotrauma.Items.Components protected override void RemoveComponentSpecific() { ClearConnections(); - base.RemoveComponentSpecific(); } diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Wearable.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Wearable.cs index 974b41790..de3eacc57 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Wearable.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Wearable.cs @@ -17,7 +17,8 @@ namespace Barotrauma Moustache, FaceAttachment, JobIndicator, - Husk + Husk, + Herpes } class WearableSprite @@ -101,6 +102,7 @@ namespace Barotrauma case WearableType.FaceAttachment: case WearableType.JobIndicator: case WearableType.Husk: + case WearableType.Herpes: Limb = LimbType.Head; HideLimb = false; HideOtherWearables = false; @@ -207,6 +209,12 @@ namespace Barotrauma.Items.Components { get { return damageModifiers; } } + + private bool autoEquipWhenFull; + public bool AutoEquipWhenFull + { + get { return autoEquipWhenFull; } + } public Wearable(Item item, XElement element) : base(item, element) { @@ -220,6 +228,7 @@ namespace Barotrauma.Items.Components wearableSprites = new WearableSprite[spriteCount]; limbType = new LimbType[spriteCount]; limb = new Limb[spriteCount]; + autoEquipWhenFull = element.GetAttributeBool("autoequipwhenfull", true); int i = 0; foreach (XElement subElement in element.Elements()) { diff --git a/Barotrauma/BarotraumaShared/Source/Items/Item.cs b/Barotrauma/BarotraumaShared/Source/Items/Item.cs index b990d98df..3ac7e5fe3 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Item.cs @@ -206,7 +206,7 @@ namespace Barotrauma set { if (scale == value) { return; } - scale = MathHelper.Clamp(value, 0.1f, 10.0f); + scale = MathHelper.Clamp(value, 0.01f, 10.0f); float relativeScale = scale / prefab.Scale; @@ -288,7 +288,7 @@ namespace Barotrauma /// /// Can be used by status effects or conditionals to modify the sound range /// - public float SoundRange + public new float SoundRange { get { return aiTarget == null ? 0.0f : aiTarget.SoundRange; } set { if (aiTarget != null) { aiTarget.SoundRange = Math.Max(0.0f, value); } } @@ -298,7 +298,7 @@ namespace Barotrauma /// /// Can be used by status effects or conditionals to modify the sound range /// - public float SightRange + public new float SightRange { get { return aiTarget == null ? 0.0f : aiTarget.SightRange; } set { if (aiTarget != null) { aiTarget.SightRange = Math.Max(0.0f, value); } } @@ -1154,7 +1154,14 @@ namespace Barotrauma { ic.Update(deltaTime, cam); #if CLIENT - if (ic.IsActive) ic.PlaySound(ActionType.OnActive, WorldPosition); + if (ic.IsActive) + { + if (ic.IsActiveTimer > 0.02f) + { + ic.PlaySound(ActionType.OnActive, WorldPosition); + } + ic.IsActiveTimer += deltaTime; + } #endif } } @@ -1381,7 +1388,7 @@ namespace Barotrauma return connectedComponents; } - private static readonly Pair[] connectionPairs = new Pair[] + public static readonly Pair[] connectionPairs = new Pair[] { new Pair("power_in", "power_out"), new Pair("signal_in1", "signal_out1"), @@ -1453,11 +1460,11 @@ namespace Barotrauma public void SendSignal(int stepsTaken, string signal, string connectionName, Character sender, float power = 0.0f, Item source = null, float signalStrength = 1.0f) { LastSentSignalRecipients.Clear(); - if (connections == null) return; + if (connections == null) { return; } stepsTaken++; - if (!connections.TryGetValue(connectionName, out Connection c)) return; + if (!connections.TryGetValue(connectionName, out Connection c)) { return; } if (stepsTaken > 10) { @@ -1467,6 +1474,11 @@ namespace Barotrauma } else { + foreach (StatusEffect effect in c.Effects) + { + if (condition <= 0.0f && effect.type != ActionType.OnBroken) { continue; } + if (signal != "0" && !string.IsNullOrEmpty(signal)) { ApplyStatusEffect(effect, ActionType.OnUse, (float)Timing.Step, null, null, false, false); } + } c.SendSignal(stepsTaken, signal, source ?? this, sender, power, signalStrength); } } @@ -1519,47 +1531,50 @@ namespace Barotrauma foreach (ItemComponent ic in components) { bool pickHit = false, selectHit = false; - if (Screen.Selected == GameMain.SubEditorScreen) + + if (picker.IsKeyDown(InputType.Aim)) { - pickHit = picker.IsKeyHit(InputType.Select); - selectHit = picker.IsKeyHit(InputType.Select); + pickHit = false; + selectHit = false; } else { - if (picker.IsKeyDown(InputType.Aim)) + if (forceSelectKey) { - pickHit = false; - selectHit = false; + if (ic.PickKey == InputType.Select) pickHit = true; + if (ic.SelectKey == InputType.Select) selectHit = true; + } + else if (forceActionKey) + { + if (ic.PickKey == InputType.Use) pickHit = true; + if (ic.SelectKey == InputType.Use) selectHit = true; } else { - if (forceSelectKey) - { - if (ic.PickKey == InputType.Select) pickHit = true; - if (ic.SelectKey == InputType.Select) selectHit = true; - } - else if (forceActionKey) - { - if (ic.PickKey == InputType.Use) pickHit = true; - if (ic.SelectKey == InputType.Use) selectHit = true; - } - else - { - pickHit = picker.IsKeyHit(ic.PickKey); - selectHit = picker.IsKeyHit(ic.SelectKey); + pickHit = picker.IsKeyHit(ic.PickKey); + selectHit = picker.IsKeyHit(ic.SelectKey); #if CLIENT - //if the cursor is on a UI component, disable interaction with the left mouse button - //to prevent accidentally selecting items when clicking UI elements - if (picker == Character.Controlled && GUI.MouseOn != null) - { - if (GameMain.Config.KeyBind(ic.PickKey).MouseButton == 0) pickHit = false; - if (GameMain.Config.KeyBind(ic.SelectKey).MouseButton == 0) selectHit = false; - } -#endif + //if the cursor is on a UI component, disable interaction with the left mouse button + //to prevent accidentally selecting items when clicking UI elements + if (picker == Character.Controlled && GUI.MouseOn != null) + { + if (GameMain.Config.KeyBind(ic.PickKey).MouseButton == 0) pickHit = false; + if (GameMain.Config.KeyBind(ic.SelectKey).MouseButton == 0) selectHit = false; } +#endif } } +#if CLIENT + //use the non-mouse interaction key (E on both default and legacy keybinds) in wiring mode + //LMB is used to manipulate wires, so using E to select connection panels is much easier + if (Screen.Selected == GameMain.SubEditorScreen && GameMain.SubEditorScreen.WiringMode) + { + pickHit = selectHit = GameMain.Config.KeyBind(InputType.Use).MouseButton == null ? + picker.IsKeyHit(InputType.Use) : + picker.IsKeyHit(InputType.Select); + } +#endif if (!pickHit && !selectHit) continue; @@ -1621,8 +1636,8 @@ namespace Barotrauma return; } - if (condition == 0.0f) return; - + if (condition == 0.0f) { return; } + bool remove = false; foreach (ItemComponent ic in components) @@ -1631,7 +1646,7 @@ namespace Barotrauma #if CLIENT isControlled = character == Character.Controlled; #endif - if (!ic.HasRequiredContainedItems(isControlled)) continue; + if (!ic.HasRequiredContainedItems(character, isControlled)) { continue; } if (ic.Use(deltaTime, character)) { ic.WasUsed = true; @@ -1642,7 +1657,7 @@ namespace Barotrauma ic.ApplyStatusEffects(ActionType.OnUse, deltaTime, character, targetLimb); - if (ic.DeleteOnUse) remove = true; + if (ic.DeleteOnUse) { remove = true; } } } @@ -1654,7 +1669,7 @@ namespace Barotrauma public void SecondaryUse(float deltaTime, Character character = null) { - if (condition == 0.0f) return; + if (condition == 0.0f) { return; } bool remove = false; @@ -1664,7 +1679,7 @@ namespace Barotrauma #if CLIENT isControlled = character == Character.Controlled; #endif - if (!ic.HasRequiredContainedItems(isControlled)) continue; + if (!ic.HasRequiredContainedItems(character, isControlled)) { continue; } if (ic.SecondaryUse(deltaTime, character)) { ic.WasUsed = true; @@ -1675,7 +1690,7 @@ namespace Barotrauma ic.ApplyStatusEffects(ActionType.OnSecondaryUse, deltaTime, character); - if (ic.DeleteOnUse) remove = true; + if (ic.DeleteOnUse) { remove = true; } } } @@ -1702,7 +1717,7 @@ namespace Barotrauma bool remove = false; foreach (ItemComponent ic in components) { - if (!ic.HasRequiredContainedItems(user == Character.Controlled)) continue; + if (!ic.HasRequiredContainedItems(user, addMessage: user == Character.Controlled)) continue; bool success = Rand.Range(0.0f, 0.5f) < ic.DegreeOfSuccess(user); ActionType actionType = success ? ActionType.OnUse : ActionType.OnFailure; @@ -1713,7 +1728,7 @@ namespace Barotrauma ic.WasUsed = true; ic.ApplyStatusEffects(actionType, 1.0f, character, targetLimb, user: user); - if (GameMain.NetworkMember!=null && GameMain.NetworkMember.IsServer) + if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer) { GameMain.NetworkMember.CreateEntityEvent(this, new object[] { @@ -1729,11 +1744,15 @@ namespace Barotrauma public bool Combine(Item item) { + if (item == this) { return false; } bool isCombined = false; foreach (ItemComponent ic in components) { - if (ic.Combine(item)) isCombined = true; + if (ic.Combine(item)) { isCombined = true; } } +#if CLIENT + if (isCombined) { GameMain.Client?.CreateEntityEvent(this, new object[] { NetEntityEvent.Type.Combine, item.ID }); } +#endif return isCombined; } diff --git a/Barotrauma/BarotraumaShared/Source/Items/ItemPrefab.cs b/Barotrauma/BarotraumaShared/Source/Items/ItemPrefab.cs index 39c924710..2ff9aaae1 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/ItemPrefab.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/ItemPrefab.cs @@ -412,8 +412,12 @@ namespace Barotrauma } XDocument doc = XMLExtensions.TryLoadXml(filePath); - if (doc?.Root == null) { return; } - + if (doc?.Root == null) + { + DebugConsole.ThrowError("File \"" + filePath + "\" could not be loaded."); + continue; + } + if (doc.Root.Name.ToString().ToLowerInvariant() == "items") { foreach (XElement element in doc.Root.Elements()) diff --git a/Barotrauma/BarotraumaShared/Source/Items/RelatedItem.cs b/Barotrauma/BarotraumaShared/Source/Items/RelatedItem.cs index 7c907cd87..cf23f7d59 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/RelatedItem.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/RelatedItem.cs @@ -95,41 +95,25 @@ namespace Barotrauma switch (type) { case RelationType.Contained: - if (parentItem == null) return false; - - var containedItems = parentItem.ContainedItems; - if (containedItems == null) return false; - - if (MatchOnEmpty && !containedItems.Any(ci => ci != null)) - { - return true; - } - - foreach (Item contained in containedItems) - { - if (contained.Condition > 0.0f && MatchesItem(contained)) return true; - } - break; + if (parentItem == null) { return false; } + return CheckContained(parentItem); case RelationType.Container: - if (parentItem == null || parentItem.Container == null) return false; - + if (parentItem == null || parentItem.Container == null) { return false; } return parentItem.Container.Condition > 0.0f && MatchesItem(parentItem.Container); case RelationType.Equipped: - if (character == null) return false; + if (character == null) { return false; } foreach (Item equippedItem in character.SelectedItems) { - if (equippedItem == null) continue; - - if (equippedItem.Condition > 0.0f && MatchesItem(equippedItem)) return true; + if (equippedItem == null) { continue; } + if (equippedItem.Condition > 0.0f && MatchesItem(equippedItem)) { return true; } } break; case RelationType.Picked: - if (character == null || character.Inventory == null) return false; + if (character == null || character.Inventory == null) { return false; } foreach (Item pickedItem in character.Inventory.Items) { - if (pickedItem == null) continue; - - if (MatchesItem(pickedItem)) return true; + if (pickedItem == null) { continue; } + if (MatchesItem(pickedItem)) { return true; } } break; default: @@ -139,6 +123,25 @@ namespace Barotrauma return false; } + private bool CheckContained(Item parentItem) + { + var containedItems = parentItem.ContainedItems; + if (containedItems == null) { return false; } + + if (MatchOnEmpty && !containedItems.Any(ci => ci != null)) + { + return true; + } + + foreach (Item contained in containedItems) + { + if (contained == null) { continue; } + if (contained.Condition > 0.0f && MatchesItem(contained)) { return true; } + if (CheckContained(contained)) { return true; } + } + return false; + } + public void Save(XElement element) { element.Add( diff --git a/Barotrauma/BarotraumaShared/Source/Map/Explosion.cs b/Barotrauma/BarotraumaShared/Source/Map/Explosion.cs index a594ad0d1..ce914981f 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Explosion.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Explosion.cs @@ -77,7 +77,7 @@ namespace Barotrauma return prevExplosions.FindAll(e => e.Third >= Timing.TotalTime - maxSecondsAgo); } - public void Explode(Vector2 worldPosition, Entity damageSource) + public void Explode(Vector2 worldPosition, Entity damageSource, Character attacker = null) { prevExplosions.Add(new Triplet(this, worldPosition, (float)Timing.TotalTime)); if (prevExplosions.Count > 100) @@ -98,7 +98,7 @@ namespace Barotrauma if (attack.GetStructureDamage(1.0f) > 0.0f) { - RangedStructureDamage(worldPosition, displayRange, attack.GetStructureDamage(1.0f)); + RangedStructureDamage(worldPosition, displayRange, attack.GetStructureDamage(1.0f), attacker); } if (empStrength > 0.0f) @@ -130,7 +130,7 @@ namespace Barotrauma if (force == 0.0f && attack.Stun == 0.0f && attack.GetTotalDamage(false) == 0.0f) return; - DamageCharacters(worldPosition, attack, force, damageSource); + DamageCharacters(worldPosition, attack, force, damageSource, attacker); if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient) { @@ -163,17 +163,9 @@ namespace Barotrauma } partial void ExplodeProjSpecific(Vector2 worldPosition, Hull hull); + - private Vector2 ClampParticlePos(Vector2 particlePos, Hull hull) - { - if (hull == null) return particlePos; - - return new Vector2( - MathHelper.Clamp(particlePos.X, hull.WorldRect.X, hull.WorldRect.Right), - MathHelper.Clamp(particlePos.Y, hull.WorldRect.Y - hull.WorldRect.Height, hull.WorldRect.Y)); - } - - public static void DamageCharacters(Vector2 worldPosition, Attack attack, float force, Entity damageSource) + public static void DamageCharacters(Vector2 worldPosition, Attack attack, float force, Entity damageSource, Character attacker) { if (attack.Range <= 0.0f) return; @@ -222,11 +214,13 @@ namespace Barotrauma modifiedAfflictions.Add(affliction.CreateMultiplied(distFactor / c.AnimController.Limbs.Length)); } c.LastDamageSource = damageSource; - Character attacker = null; - if (damageSource is Item item) + if (attacker == null) { - attacker = item.GetComponent()?.User; - if (attacker == null) attacker = item.GetComponent()?.User; + if (damageSource is Item item) + { + attacker = item.GetComponent()?.User; + if (attacker == null) attacker = item.GetComponent()?.User; + } } //use a position slightly from the limb's position towards the explosion @@ -280,7 +274,7 @@ namespace Barotrauma /// /// Returns a dictionary where the keys are the structures that took damage and the values are the amount of damage taken /// - public static Dictionary RangedStructureDamage(Vector2 worldPosition, float worldRange, float damage) + public static Dictionary RangedStructureDamage(Vector2 worldPosition, float worldRange, float damage, Character attacker = null) { List structureList = new List(); float dist = 600.0f; @@ -304,7 +298,7 @@ namespace Barotrauma float distFactor = 1.0f - (Vector2.Distance(structure.SectionPosition(i, true), worldPosition) / worldRange); if (distFactor <= 0.0f) continue; - structure.AddDamage(i, damage * distFactor); + structure.AddDamage(i, damage * distFactor, attacker); if (damagedStructures.ContainsKey(structure)) { diff --git a/Barotrauma/BarotraumaShared/Source/Map/Levels/Level.cs b/Barotrauma/BarotraumaShared/Source/Map/Levels/Level.cs index 3f6d04810..5c93cc301 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Levels/Level.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Levels/Level.cs @@ -853,7 +853,7 @@ namespace Barotrauma return tooCloseCells; } - private List GetTooCloseCells(Vector2 position, float minDistance) + public List GetTooCloseCells(Vector2 position, float minDistance) { List tooCloseCells = new List(); diff --git a/Barotrauma/BarotraumaShared/Source/Map/Map/LocationType.cs b/Barotrauma/BarotraumaShared/Source/Map/Map/LocationType.cs index 7e22a1f71..b24d55c4b 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Map/LocationType.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Map/LocationType.cs @@ -57,7 +57,12 @@ namespace Barotrauma get; private set; } - + + public override string ToString() + { + return $"LocationType (" + Identifier + ")"; + } + private LocationType(XElement element) { Identifier = element.GetAttributeString("identifier", element.Name.ToString()); diff --git a/Barotrauma/BarotraumaShared/Source/Map/RoundEndCinematic.cs b/Barotrauma/BarotraumaShared/Source/Map/RoundEndCinematic.cs index 2daa10306..6ea887a58 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/RoundEndCinematic.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/RoundEndCinematic.cs @@ -12,9 +12,13 @@ namespace Barotrauma private set; } + public Camera AssignedCamera; + private float duration; + + private CoroutineHandle updateCoroutine; - public RoundEndCinematic(Submarine submarine, Camera cam, float duration) + public RoundEndCinematic(Submarine submarine, Camera cam, float duration = 10.0f) : this(new List() { submarine }, cam, duration) { @@ -25,9 +29,19 @@ namespace Barotrauma if (!submarines.Any(s => s != null)) return; this.duration = duration; + AssignedCamera = cam; Running = true; - CoroutineManager.StartCoroutine(Update(submarines, cam)); + updateCoroutine = CoroutineManager.StartCoroutine(Update(submarines, cam)); + } + + public void Stop() + { + CoroutineManager.StopCoroutines(updateCoroutine); + Running = false; +#if CLIENT + GUI.ScreenOverlayColor = Color.TransparentBlack; +#endif } private IEnumerable Update(List subs, Camera cam) @@ -72,6 +86,11 @@ namespace Barotrauma (minPos.Y + maxPos.Y) / 2.0f); cam.Translate(cameraPos - cam.Position); + foreach (Submarine sub in subs) + { + sub.PhysicsBody?.ResetDynamics(); + } + #if CLIENT cam.Zoom = MathHelper.SmoothStep(initialZoom, 0.5f, timer / duration); if (timer / duration > 0.9f) diff --git a/Barotrauma/BarotraumaShared/Source/Map/Structure.cs b/Barotrauma/BarotraumaShared/Source/Map/Structure.cs index 217e60041..0211c3d84 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Structure.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Structure.cs @@ -816,8 +816,7 @@ namespace Barotrauma } - - partial void AdjustKarma(IDamageable attacker, float amount); + public AttackResult AddDamage(Character attacker, Vector2 worldPosition, Attack attack, float deltaTime, bool playSound = false) { @@ -972,23 +971,18 @@ namespace Barotrauma bool hadHole = SectionBodyDisabled(sectionIndex); Sections[sectionIndex].damage = MathHelper.Clamp(damage, 0.0f, Prefab.Health); - //otherwise it's possible to infinitely gain karma by welding fixed things if (attacker != null && damageDiff != 0.0f) { - AdjustKarma(attacker, damageDiff); -#if CLIENT - if (GameMain.Client == null) + OnHealthChangedProjSpecific(attacker, damageDiff); + if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient) { -#endif if (damageDiff < 0.0f) { attacker.Info.IncreaseSkillLevel("mechanical", -damageDiff * SkillIncreaseMultiplier / Math.Max(attacker.GetSkillLevel("mechanical"), 1.0f), SectionPosition(sectionIndex, true)); } -#if CLIENT } -#endif } bool hasHole = SectionBodyDisabled(sectionIndex); @@ -998,6 +992,8 @@ namespace Barotrauma UpdateSections(); } + partial void OnHealthChangedProjSpecific(Character attacker, float damageAmount); + public void SetCollisionCategory(Category collisionCategory) { if (Bodies == null) return; diff --git a/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs b/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs index e653d0603..6d25e9c13 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs @@ -745,6 +745,12 @@ namespace Barotrauma private static readonly Dictionary bodyDist = new Dictionary(); private static readonly List bodies = new List(); + public static float LastPickedBodyDist(Body body) + { + if (!bodyDist.ContainsKey(body)) { return 0.0f; } + return bodyDist[body]; + } + /// /// Returns a list of physics bodies the ray intersects with, sorted according to distance (the closest body is at the beginning of the list). /// @@ -1067,16 +1073,14 @@ namespace Barotrauma //Level.Loaded.Move(-amount); } - public static Submarine FindClosest(Vector2 worldPosition, bool ignoreOutposts = false) + public static Submarine FindClosest(Vector2 worldPosition, bool ignoreOutposts = false, bool ignoreOutsideLevel = true) { Submarine closest = null; float closestDist = 0.0f; foreach (Submarine sub in loaded) { - if (ignoreOutposts && sub.IsOutpost) - { - continue; - } + if (ignoreOutposts && sub.IsOutpost) { continue; } + if (ignoreOutsideLevel && Level.Loaded != null && sub.WorldPosition.Y > Level.Loaded.Size.Y) { continue; } float dist = Vector2.DistanceSquared(worldPosition, sub.WorldPosition); if (closest == null || dist < closestDist) { @@ -1209,7 +1213,34 @@ namespace Barotrauma foreach (string path in filePaths) { var sub = new Submarine(path); - if (!sub.IsFileCorrupted) + if (sub.IsFileCorrupted) + { +#if CLIENT + if (DebugConsole.IsOpen) { DebugConsole.Toggle(); } + var deleteSubPrompt = new GUIMessageBox( + TextManager.Get("Error"), + TextManager.GetWithVariable("SubLoadError", "[subname]", sub.name) +"\n"+ + TextManager.GetWithVariable("DeleteFileVerification", "[filename]", sub.name), + new string[] { TextManager.Get("Yes"), TextManager.Get("No") }); + + string filePath = path; + deleteSubPrompt.Buttons[0].OnClicked += (btn, userdata) => + { + try + { + File.Delete(filePath); + } + catch (Exception e) + { + DebugConsole.ThrowError($"Failed to delete file \"{filePath}\".", e); + } + deleteSubPrompt.Close(); + return true; + }; + deleteSubPrompt.Buttons[1].OnClicked += deleteSubPrompt.Close; +#endif + } + else { savedSubmarines.Add(sub); } @@ -1621,7 +1652,6 @@ namespace Barotrauma if (wp.isObstructed) { continue; } foreach (var connection in node.connections) { - bool isObstructed = false; var connectedWp = connection.Waypoint; if (connectedWp.isObstructed) { continue; } Vector2 start = ConvertUnits.ToSimUnits(wp.WorldPosition); @@ -1652,7 +1682,6 @@ namespace Barotrauma if (wp.isObstructed) { continue; } foreach (var connection in node.connections) { - bool isObstructed = false; var connectedWp = connection.Waypoint; if (connectedWp.isObstructed) { continue; } Vector2 start = ConvertUnits.ToSimUnits(wp.WorldPosition) - otherSub.SimPosition; diff --git a/Barotrauma/BarotraumaShared/Source/Networking/Client.cs b/Barotrauma/BarotraumaShared/Source/Networking/Client.cs index 501119553..192b4eb19 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/Client.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/Client.cs @@ -1,4 +1,5 @@ using Lidgren.Network; +using Microsoft.Xna.Framework; using System; using System.Collections.Generic; using System.Linq; @@ -42,6 +43,27 @@ namespace Barotrauma.Networking } } + private Vector2 spectate_position; + public Vector2? SpectatePos + { + get + { + if (character == null || character.IsDead) + { + return spectate_position; + } + else + { + return null; + } + } + + set + { + spectate_position = value.Value; + } + } + private bool muted; public bool Muted { diff --git a/Barotrauma/BarotraumaShared/Source/Networking/ClientPermissions.cs b/Barotrauma/BarotraumaShared/Source/Networking/ClientPermissions.cs index d83ef4bb9..95adfe26a 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/ClientPermissions.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/ClientPermissions.cs @@ -21,7 +21,8 @@ namespace Barotrauma.Networking ServerLog = 0x100, ManageSettings = 0x200, ManagePermissions = 0x400, - All = 0x7ff + KarmaImmunity = 0x800, + All = 0xFFF } class PermissionPreset diff --git a/Barotrauma/BarotraumaShared/Source/Networking/KarmaManager.cs b/Barotrauma/BarotraumaShared/Source/Networking/KarmaManager.cs new file mode 100644 index 000000000..dacacbe4d --- /dev/null +++ b/Barotrauma/BarotraumaShared/Source/Networking/KarmaManager.cs @@ -0,0 +1,143 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Xml; +using System.Xml.Linq; + +namespace Barotrauma +{ + partial class KarmaManager : ISerializableEntity + { + public static readonly string ConfigFile = "Data" + Path.DirectorySeparatorChar + "karmasettings.xml"; + + public string Name => "KarmaManager"; + + public Dictionary SerializableProperties { get; private set; } + + [Serialize(0.1f, true)] + public float KarmaDecay { get; set; } + + [Serialize(50.0f, true)] + public float KarmaDecayThreshold { get; set; } + + [Serialize(0.15f, true)] + public float KarmaIncrease { get; set; } + + [Serialize(50.0f, true)] + public float KarmaIncreaseThreshold { get; set; } + + [Serialize(0.05f, true)] + public float StructureRepairKarmaIncrease { get; set; } + [Serialize(0.1f, true)] + public float StructureDamageKarmaDecrease { get; set; } + [Serialize(30.0f, true)] + public float MaxStructureDamageKarmaDecreasePerSecond { get; set; } + + [Serialize(0.03f, true)] + public float ItemRepairKarmaIncrease { get; set; } + + [Serialize(0.5f, true)] + public float ReactorOverheatKarmaDecrease { get; set; } + [Serialize(30.0f, true)] + public float ReactorMeltdownKarmaDecrease { get; set; } + + [Serialize(0.1f, true)] + public float DamageEnemyKarmaIncrease { get; set; } + [Serialize(0.2f, true)] + public float HealFriendlyKarmaIncrease { get; set; } + [Serialize(0.25f, true)] + public float DamageFriendlyKarmaDecrease { get; set; } + + [Serialize(1.0f, true)] + public float ExtinguishFireKarmaIncrease { get; set; } + + + private float allowedWireDisconnectionsPerMinute; + [Serialize(5.0f, true)] + public float AllowedWireDisconnectionsPerMinute + { + get { return allowedWireDisconnectionsPerMinute; } + set { allowedWireDisconnectionsPerMinute = Math.Max(0.0f, value); } + } + + [Serialize(6.0f, true)] + public float WireDisconnectionKarmaDecrease { get; set; } + + [Serialize(0.15f, true)] + public float SteerSubKarmaIncrease { get; set; } + + [Serialize(15.0f, true)] + public float SpamFilterKarmaDecrease { get; set; } + + [Serialize(40.0f, true)] + public float HerpesThreshold { get; set; } + + [Serialize(1.0f, true)] + public float KickBanThreshold { get; set; } + + [Serialize(10.0f, true)] + public float KarmaNotificationInterval { get; set; } + + private readonly AfflictionPrefab herpesAffliction; + + public Dictionary Presets = new Dictionary(); + + public KarmaManager() + { + XDocument doc = XMLExtensions.TryLoadXml(ConfigFile); + SerializableProperties = SerializableProperty.DeserializeProperties(this, doc?.Root); + if (doc?.Root != null) + { + Presets["custom"] = doc.Root; + foreach (XElement subElement in doc.Root.Elements()) + { + string presetName = subElement.GetAttributeString("name", ""); + Presets[presetName.ToLowerInvariant()] = subElement; + } + SelectPreset("default"); + } + herpesAffliction = AfflictionPrefab.List.Find(ap => ap.Identifier == "spaceherpes"); + } + + public void SelectPreset(string presetName) + { + if (string.IsNullOrEmpty(presetName)) { return; } + presetName = presetName.ToLowerInvariant(); + + if (Presets.ContainsKey(presetName)) + { + SerializableProperty.DeserializeProperties(this, Presets[presetName]); + } + } + + public void SaveCustomPreset() + { + if (Presets.ContainsKey("custom")) + { + SerializableProperty.SerializeProperties(this, Presets["custom"]); + } + } + + public void Save() + { + XDocument doc = new XDocument(new XElement(Name)); + + foreach (KeyValuePair preset in Presets) + { + doc.Root.Add(preset.Value); + } + + XmlWriterSettings settings = new XmlWriterSettings + { + Indent = true, + NewLineOnAttributes = true + }; + + using (var writer = XmlWriter.Create(ConfigFile, settings)) + { + doc.Save(writer); + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/Source/Networking/NetEntityEvent/NetEntityEvent.cs b/Barotrauma/BarotraumaShared/Source/Networking/NetEntityEvent/NetEntityEvent.cs index d5cd0904e..d93b9258e 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/NetEntityEvent/NetEntityEvent.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/NetEntityEvent/NetEntityEvent.cs @@ -15,7 +15,8 @@ namespace Barotrauma.Networking ApplyStatusEffect, ChangeProperty, Control, - UpdateSkills + UpdateSkills, + Combine } public readonly Entity Entity; diff --git a/Barotrauma/BarotraumaShared/Source/Networking/NetworkMember.cs b/Barotrauma/BarotraumaShared/Source/Networking/NetworkMember.cs index 923077535..b4d1b39a3 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/NetworkMember.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/NetworkMember.cs @@ -35,7 +35,8 @@ namespace Barotrauma.Networking CHAT_MESSAGE, //also self-explanatory VOTE, //you get the idea CHARACTER_INPUT, - ENTITY_STATE + ENTITY_STATE, + SPECTATING_POS } enum ClientNetError @@ -168,7 +169,13 @@ namespace Barotrauma.Networking updateInterval = new TimeSpan(0, 0, 0, 0, MathHelper.Clamp(1000 / serverSettings.TickRate, 1, 500)); } } - + + public KarmaManager KarmaManager + { + get; + private set; + } = new KarmaManager(); + public string Name { get { return name; } @@ -214,7 +221,7 @@ namespace Barotrauma.Networking var radioComponent = radio.GetComponent(); if (radioComponent == null) return false; - return radioComponent.HasRequiredContainedItems(false); + return radioComponent.HasRequiredContainedItems(sender, addMessage: false); } public void AddChatMessage(string message, ChatMessageType type, string senderName = "", Character senderCharacter = null) diff --git a/Barotrauma/BarotraumaShared/Source/Networking/RespawnManager.cs b/Barotrauma/BarotraumaShared/Source/Networking/RespawnManager.cs index 98f6af3a2..6bcd22d24 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/RespawnManager.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/RespawnManager.cs @@ -1,6 +1,5 @@ using Barotrauma.Items.Components; using FarseerPhysics; -using Lidgren.Network; using Microsoft.Xna.Framework; using System; using System.Collections.Generic; @@ -18,10 +17,6 @@ namespace Barotrauma.Networking } private NetworkMember networkMember; - - private State state; - - private Submarine respawnShuttle; private Steering shuttleSteering; private List shuttleDoors; @@ -31,46 +26,40 @@ namespace Barotrauma.Networking public bool UsingShuttle { - get { return respawnShuttle != null; } + get { return RespawnShuttle != null; } } /// - /// How long until the shuttle is dispatched with respawned characters + /// When will the shuttle be dispatched with respawned characters /// - public float RespawnTimer - { - get { return respawnTimer; } - } + public DateTime RespawnTime { get; private set; } /// - /// how long until the shuttle starts heading back out of the level + /// When will the sub start heading back out of the level /// - public float TransportTimer - { - get { return shuttleTransportTimer; } - } + public DateTime ReturnTime { get; private set; } - public bool CountdownStarted + public bool RespawnCountdownStarted { get; private set; } - public State CurrentState + public bool ReturnCountdownStarted { - get { return state; } + get; + private set; } - private float respawnTimer, shuttleReturnTimer, shuttleTransportTimer; + public State CurrentState { get; private set; } + + private DateTime despawnTime; private float maxTransportTime; private float updateReturnTimer; - public Submarine RespawnShuttle - { - get { return respawnShuttle; } - } + public Submarine RespawnShuttle { get; private set; } public RespawnManager(NetworkMember networkMember, Submarine shuttle) : base(shuttle) @@ -79,8 +68,8 @@ namespace Barotrauma.Networking if (shuttle != null) { - respawnShuttle = new Submarine(shuttle.FilePath, shuttle.MD5Hash.Hash, true); - respawnShuttle.Load(false); + RespawnShuttle = new Submarine(shuttle.FilePath, shuttle.MD5Hash.Hash, true); + RespawnShuttle.Load(false); ResetShuttle(); @@ -89,7 +78,7 @@ namespace Barotrauma.Networking shuttleDoors = new List(); foreach (Item item in Item.ItemList) { - if (item.Submarine != respawnShuttle) continue; + if (item.Submarine != RespawnShuttle) continue; var steering = item.GetComponent(); if (steering != null) shuttleSteering = steering; @@ -113,13 +102,12 @@ namespace Barotrauma.Networking } else { - respawnShuttle = null; + RespawnShuttle = null; } #if SERVER if (networkMember is GameServer server) { - respawnTimer = server.ServerSettings.RespawnInterval; maxTransportTime = server.ServerSettings.MaxTransportTime; } #endif @@ -127,15 +115,15 @@ namespace Barotrauma.Networking public void Update(float deltaTime) { - if (respawnShuttle == null) + if (RespawnShuttle == null) { - if (state != State.Waiting) + if (CurrentState != State.Waiting) { - state = State.Waiting; + CurrentState = State.Waiting; } } - switch (state) + switch (CurrentState) { case State.Waiting: UpdateWaiting(deltaTime); @@ -155,32 +143,25 @@ namespace Barotrauma.Networking { //infinite transport time -> shuttle wont return if (maxTransportTime <= 0.0f) return; - - shuttleTransportTimer -= deltaTime; - UpdateTransportingProjSpecific(deltaTime); } partial void UpdateTransportingProjSpecific(float deltaTime); + public void ForceRespawn() + { + ResetShuttle(); + RespawnTime = DateTime.Now; + CurrentState = State.Waiting; + } + private void UpdateReturning(float deltaTime) { - //if (shuttleReturnTimer == maxTransportTime && - // networkMember.Character != null && - // networkMember.Character.Submarine == respawnShuttle) - //{ - // networkMember.AddChatMessage("The shuttle will automatically return back to the outpost. Please leave the shuttle immediately.", ChatMessageType.Server); - //} - - shuttleReturnTimer -= deltaTime; - updateReturnTimer += deltaTime; - if (updateReturnTimer > 1.0f) { updateReturnTimer = 0.0f; - - respawnShuttle.PhysicsBody.FarseerBody.IgnoreCollisionWith(Level.Loaded.TopBarrier); + RespawnShuttle.PhysicsBody.FarseerBody.IgnoreCollisionWith(Level.Loaded.TopBarrier); if (shuttleSteering != null) { @@ -196,41 +177,41 @@ namespace Barotrauma.Networking private IEnumerable ForceShuttleToPos(Vector2 position, float speed) { - if (respawnShuttle == null) + if (RespawnShuttle == null) { yield return CoroutineStatus.Success; } - respawnShuttle.PhysicsBody.FarseerBody.IgnoreCollisionWith(Level.Loaded.TopBarrier); + RespawnShuttle.PhysicsBody.FarseerBody.IgnoreCollisionWith(Level.Loaded.TopBarrier); - while (Math.Abs(position.Y - respawnShuttle.WorldPosition.Y) > 100.0f) + while (Math.Abs(position.Y - RespawnShuttle.WorldPosition.Y) > 100.0f) { - Vector2 diff = position - respawnShuttle.WorldPosition; + Vector2 diff = position - RespawnShuttle.WorldPosition; if (diff.LengthSquared() > 0.01f) { Vector2 displayVel = Vector2.Normalize(diff) * speed; - respawnShuttle.SubBody.Body.LinearVelocity = ConvertUnits.ToSimUnits(displayVel); + RespawnShuttle.SubBody.Body.LinearVelocity = ConvertUnits.ToSimUnits(displayVel); } yield return CoroutineStatus.Running; - if (respawnShuttle.SubBody == null) yield return CoroutineStatus.Success; + if (RespawnShuttle.SubBody == null) yield return CoroutineStatus.Success; } - respawnShuttle.PhysicsBody.FarseerBody.RestoreCollisionWith(Level.Loaded.TopBarrier); + RespawnShuttle.PhysicsBody.FarseerBody.RestoreCollisionWith(Level.Loaded.TopBarrier); yield return CoroutineStatus.Success; } private void ResetShuttle() { - shuttleTransportTimer = maxTransportTime; - shuttleReturnTimer = maxTransportTime; + ReturnTime = DateTime.Now + new TimeSpan(0, 0, 0, 0, milliseconds: (int)(maxTransportTime * 1000)); + despawnTime = ReturnTime + new TimeSpan(0, 0, seconds: 30); - if (respawnShuttle == null) return; + if (RespawnShuttle == null) return; foreach (Item item in Item.ItemList) { - if (item.Submarine != respawnShuttle) continue; + if (item.Submarine != RespawnShuttle) continue; //remove respawn items that have been left in the shuttle if (respawnItems.Contains(item)) @@ -251,7 +232,7 @@ namespace Barotrauma.Networking foreach (Structure wall in Structure.WallList) { - if (wall.Submarine != respawnShuttle) continue; + if (wall.Submarine != RespawnShuttle) continue; for (int i = 0; i < wall.SectionCount; i++) { @@ -259,12 +240,12 @@ namespace Barotrauma.Networking } } - var shuttleGaps = Gap.GapList.FindAll(g => g.Submarine == respawnShuttle && g.ConnectedWall != null); + var shuttleGaps = Gap.GapList.FindAll(g => g.Submarine == RespawnShuttle && g.ConnectedWall != null); shuttleGaps.ForEach(g => Spawner.AddToRemoveQueue(g)); foreach (Hull hull in Hull.hullList) { - if (hull.Submarine != respawnShuttle) continue; + if (hull.Submarine != RespawnShuttle) continue; hull.OxygenPercentage = 100.0f; hull.WaterVolume = 0.0f; @@ -272,7 +253,7 @@ namespace Barotrauma.Networking foreach (Character c in Character.CharacterList) { - if (c.Submarine != respawnShuttle) continue; + if (c.Submarine != RespawnShuttle) continue; #if CLIENT if (Character.Controlled == c) Character.Controlled = null; @@ -288,15 +269,12 @@ namespace Barotrauma.Networking if (item == null) continue; Spawner.AddToRemoveQueue(item); } - } + } } - respawnShuttle.SetPosition(new Vector2(Level.Loaded.StartPosition.X, Level.Loaded.Size.Y + respawnShuttle.Borders.Height)); - - respawnShuttle.Velocity = Vector2.Zero; - - respawnShuttle.PhysicsBody.FarseerBody.RestoreCollisionWith(Level.Loaded.TopBarrier); - + RespawnShuttle.SetPosition(new Vector2(Level.Loaded.StartPosition.X, Level.Loaded.Size.Y + RespawnShuttle.Borders.Height)); + RespawnShuttle.Velocity = Vector2.Zero; + RespawnShuttle.PhysicsBody.FarseerBody.RestoreCollisionWith(Level.Loaded.TopBarrier); } partial void RespawnCharactersProjSpecific(); @@ -305,55 +283,92 @@ namespace Barotrauma.Networking RespawnCharactersProjSpecific(); } - public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null) + public Vector2 FindSpawnPos() { - msg.WriteRangedInteger(0, Enum.GetNames(typeof(State)).Length, (int)state); + if (Level.Loaded == null || Submarine.MainSub == null) { return Vector2.Zero; } - switch (state) + Rectangle dockedBorders = RespawnShuttle.GetDockedBorders(); + Vector2 diffFromDockedBorders = + new Vector2(dockedBorders.Center.X, dockedBorders.Y - dockedBorders.Height / 2) + - new Vector2(RespawnShuttle.Borders.Center.X, RespawnShuttle.Borders.Y - RespawnShuttle.Borders.Height / 2); + + int minWidth = Math.Max(dockedBorders.Width, 1000); + int minHeight = Math.Max(dockedBorders.Height, 1000); + + List potentialSpawnPositions = new List(); + foreach (Level.InterestingPosition potentialSpawnPos in Level.Loaded.PositionsOfInterest.Where(p => p.PositionType == Level.PositionType.MainPath)) { - case State.Transporting: - msg.Write(TransportTimer); - break; - case State.Waiting: - msg.Write(CountdownStarted); - msg.Write(respawnTimer); - break; - case State.Returning: - break; - } + bool invalid = false; + //make sure the shuttle won't overlap with any ruins + foreach (var ruin in Level.Loaded.Ruins) + { + if (Math.Abs(ruin.Area.Center.X - potentialSpawnPos.Position.X) < (minWidth + ruin.Area.Width) / 2) { invalid = true; break; } + if (Math.Abs(ruin.Area.Center.Y - potentialSpawnPos.Position.Y) < (minHeight + ruin.Area.Height) / 2) { invalid = true; break; } + } + if (invalid) { continue; } - msg.WritePadBits(); - } + //make sure there aren't any walls too close + var tooCloseCells = Level.Loaded.GetTooCloseCells(potentialSpawnPos.Position.ToVector2(), Math.Max(minWidth, minHeight)); + if (tooCloseCells.Any()) { continue; } - public void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime) - { - var newState = (State)msg.ReadRangedInteger(0, Enum.GetNames(typeof(State)).Length); + //make sure the spawnpoint is far enough from other subs + foreach (Submarine sub in Submarine.Loaded) + { + if (sub == RespawnShuttle || RespawnShuttle.DockedTo.Contains(sub)) { continue; } - switch (newState) - { - case State.Transporting: - maxTransportTime = msg.ReadSingle(); - shuttleTransportTimer = maxTransportTime; - CountdownStarted = false; - - if (state != newState) + float minDist = Math.Max(Math.Max(minWidth, minHeight) + Math.Max(sub.Borders.Width, sub.Borders.Height), 10000.0f); + if (Vector2.DistanceSquared(sub.WorldPosition, potentialSpawnPos.Position.ToVector2()) < minDist * minDist) { - CoroutineManager.StopCoroutines("forcepos"); - CoroutineManager.StartCoroutine(ForceShuttleToPos(Level.Loaded.StartPosition - Vector2.UnitY * Level.ShaftHeight, 100.0f), "forcepos"); + invalid = true; + break; } - break; - case State.Waiting: - CountdownStarted = msg.ReadBoolean(); - ResetShuttle(); - respawnTimer = msg.ReadSingle(); - break; - case State.Returning: - CountdownStarted = false; - break; - } - state = newState; + } + if (invalid) { continue; } - msg.ReadPadBits(); + foreach (Character character in Character.CharacterList) + { + if (character.IsDead) + { + //cannot spawn directly over dead bodies + if (Math.Abs(character.WorldPosition.X - potentialSpawnPos.Position.X) < minWidth) { invalid = true; break; } + if (Math.Abs(character.WorldPosition.Y - potentialSpawnPos.Position.Y) < minHeight) { invalid = true; break; } + } + else + { + //cannot spawn near alive characters (to prevent other players from seeing the shuttle + //appear out of nowhere, or monsters from immediatelly wrecking the shuttle) + if (Vector2.DistanceSquared(character.WorldPosition, potentialSpawnPos.Position.ToVector2()) < 5000.0f * 5000.0f) + { + invalid = true; + break; + } + } + } + if (invalid) { continue; } + + potentialSpawnPositions.Add(potentialSpawnPos); + } + Vector2 bestSpawnPos = new Vector2(Level.Loaded.StartPosition.X, Level.Loaded.Size.Y + RespawnShuttle.Borders.Height); + float bestSpawnPosValue = 0.0f; + foreach (var potentialSpawnPos in potentialSpawnPositions) + { + //the closer the spawnpos is to the main sub, the better + float spawnPosValue = 100000.0f / Math.Max(Vector2.Distance(potentialSpawnPos.Position.ToVector2(), Submarine.MainSub.WorldPosition), 1.0f); + + //prefer spawnpoints that are at the left side of the sub (so the shuttle doesn't have to go backwards) + if (potentialSpawnPos.Position.X > Submarine.MainSub.WorldPosition.X) + { + spawnPosValue *= 0.1f; + } + + if (spawnPosValue > bestSpawnPosValue) + { + bestSpawnPos = potentialSpawnPos.Position.ToVector2(); + bestSpawnPosValue = spawnPosValue; + } + } + + return bestSpawnPos; } } } diff --git a/Barotrauma/BarotraumaShared/Source/Networking/ServerSettings.cs b/Barotrauma/BarotraumaShared/Source/Networking/ServerSettings.cs index abbed98a0..fec6d1e0f 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/ServerSettings.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/ServerSettings.cs @@ -94,7 +94,7 @@ namespace Barotrauma.Networking private SerializableProperty property; private string typeString; - private ServerSettings serverSettings; + private object parentObject; public string Name { @@ -103,16 +103,42 @@ namespace Barotrauma.Networking public object Value { - get { return property.GetValue(serverSettings); } + get { return property.GetValue(parentObject); } + set { property.SetValue(parentObject, value); } } - - public NetPropertyData(ServerSettings serverSettings, SerializableProperty property, string typeString) + + public NetPropertyData(object parentObject, SerializableProperty property, string typeString) { this.property = property; this.typeString = typeString; - this.serverSettings = serverSettings; + this.parentObject = parentObject; } - + + public bool PropEquals(object a, object b) + { + switch (typeString) + { + case "float": + if (!(a is float?)) return false; + if (!(b is float?)) return false; + return MathUtils.NearlyEqual((float)a, (float)b); + case "int": + if (!(a is int?)) return false; + if (!(b is int?)) return false; + return (int)a == (int)b; + case "bool": + if (!(a is bool?)) return false; + if (!(b is bool?)) return false; + return (bool)a == (bool)b; + case "Enum": + if (!(a is Enum)) return false; + if (!(b is Enum)) return false; + return ((Enum)a).Equals((Enum)b); + default: + return a.ToString().Equals(b.ToString(), StringComparison.InvariantCulture); + } + } + public void Read(NetBuffer msg) { long oldPos = msg.Position; @@ -126,20 +152,24 @@ namespace Barotrauma.Networking { case "float": if (size != 4) break; - property.SetValue(serverSettings, msg.ReadFloat()); + property.SetValue(parentObject, msg.ReadFloat()); + return; + case "int": + if (size != 4) break; + property.SetValue(parentObject, msg.ReadInt32()); return; case "vector2": if (size != 8) break; x = msg.ReadFloat(); y = msg.ReadFloat(); - property.SetValue(serverSettings, new Vector2(x, y)); + property.SetValue(parentObject, new Vector2(x, y)); return; case "vector3": if (size != 12) break; x = msg.ReadFloat(); y = msg.ReadFloat(); z = msg.ReadFloat(); - property.SetValue(serverSettings, new Vector3(x, y, z)); + property.SetValue(parentObject, new Vector3(x, y, z)); return; case "vector4": if (size != 16) break; @@ -147,7 +177,7 @@ namespace Barotrauma.Networking y = msg.ReadFloat(); z = msg.ReadFloat(); w = msg.ReadFloat(); - property.SetValue(serverSettings, new Vector4(x, y, z, w)); + property.SetValue(parentObject, new Vector4(x, y, z, w)); return; case "color": if (size != 4) break; @@ -155,7 +185,7 @@ namespace Barotrauma.Networking g = msg.ReadByte(); b = msg.ReadByte(); a = msg.ReadByte(); - property.SetValue(serverSettings, new Color(r, g, b, a)); + property.SetValue(parentObject, new Color(r, g, b, a)); return; case "rectangle": if (size != 16) break; @@ -163,12 +193,12 @@ namespace Barotrauma.Networking iy = msg.ReadInt32(); width = msg.ReadInt32(); height = msg.ReadInt32(); - property.SetValue(serverSettings, new Rectangle(ix, iy, width, height)); + property.SetValue(parentObject, new Rectangle(ix, iy, width, height)); return; default: msg.Position = oldPos; //reset position to properly read the string string incVal = msg.ReadString(); - property.TrySetValue(serverSettings, incVal); + property.TrySetValue(parentObject, incVal); return; } @@ -178,13 +208,17 @@ namespace Barotrauma.Networking public void Write(NetBuffer msg, object overrideValue = null) { - if (overrideValue == null) overrideValue = property.GetValue(serverSettings); + if (overrideValue == null) overrideValue = property.GetValue(parentObject); switch (typeString) { case "float": msg.WriteVariableUInt32(4); msg.Write((float)overrideValue); break; + case "int": + msg.WriteVariableUInt32(4); + msg.Write((int)overrideValue); + break; case "vector2": msg.WriteVariableUInt32(8); msg.Write(((Vector2)overrideValue).X); @@ -232,14 +266,14 @@ namespace Barotrauma.Networking private set; } - Dictionary netProperties; + Dictionary netProperties; partial void InitProjSpecific(); - public ServerSettings(string serverName, int port, int queryPort, int maxPlayers, bool isPublic, bool enableUPnP) - { + public ServerSettings(NetworkMember networkMember, string serverName, int port, int queryPort, int maxPlayers, bool isPublic, bool enableUPnP) + { ServerLog = new ServerLog(serverName); - + Voting = new Voting(); Whitelist = new WhiteList(); @@ -265,17 +299,30 @@ namespace Barotrauma.Networking foreach (var property in saveProperties) { object value = property.GetValue(this); - if (value == null) continue; + if (value == null) { continue; } string typeName = SerializableProperty.GetSupportedTypeName(value.GetType()); if (typeName != null || property.PropertyType.IsEnum) { NetPropertyData netPropertyData = new NetPropertyData(this, property, typeName); - UInt32 key = ToolBox.StringToUInt32Hash(property.Name, md5); + if (netProperties.ContainsKey(key)){ throw new Exception("Hashing collision in ServerSettings.netProperties: " + netProperties[key] + " has same key as " + property.Name + " (" + key.ToString() + ")"); } + netProperties.Add(key, netPropertyData); + } + } - if (netProperties.ContainsKey(key)) throw new Exception("Hashing collision in ServerSettings.netProperties: " + netProperties[key] + " has same key as " + property.Name + " (" + key.ToString() + ")"); + var karmaProperties = SerializableProperty.GetProperties(networkMember.KarmaManager); + foreach (var property in karmaProperties) + { + object value = property.GetValue(networkMember.KarmaManager); + if (value == null) { continue; } + string typeName = SerializableProperty.GetSupportedTypeName(value.GetType()); + if (typeName != null || property.PropertyType.IsEnum) + { + NetPropertyData netPropertyData = new NetPropertyData(networkMember.KarmaManager, property, typeName); + UInt32 key = ToolBox.StringToUInt32Hash(property.Name, md5); + if (netProperties.ContainsKey(key)) { throw new Exception("Hashing collision in ServerSettings.netProperties: " + netProperties[key] + " has same key as " + property.Name + " (" + key.ToString() + ")"); } netProperties.Add(key, netPropertyData); } } @@ -548,7 +595,14 @@ namespace Barotrauma.Networking get; set; } - + + [Serialize(true, true)] + public bool AllowFriendlyFire + { + get; + set; + } + private YesNoMaybe traitorsEnabled; public YesNoMaybe TraitorsEnabled { @@ -631,12 +685,26 @@ namespace Barotrauma.Networking private set; } + private bool karmaEnabled; [Serialize(false, true)] public bool KarmaEnabled + { + get { return karmaEnabled; } + set + { + karmaEnabled = value; +#if CLIENT + if (karmaSettingsBlocker != null) { karmaSettingsBlocker.Visible = !karmaEnabled || karmaPresetDD.SelectedData as string != "custom"; } +#endif + } + } + + [Serialize("default", true)] + public string KarmaPreset { get; set; - } + } = "default"; [Serialize("sandbox", true)] public string GameModeIdentifier @@ -664,14 +732,14 @@ namespace Barotrauma.Networking set; } - [Serialize(60f, true)] + [Serialize(60f * 60.0f, true)] public float AutoBanTime { get; private set; } - [Serialize(360f, true)] + [Serialize(60.0f * 60.0f * 24.0f, true)] public float MaxAutoBanTime { get; diff --git a/Barotrauma/BarotraumaShared/Source/Physics/PhysicsBody.cs b/Barotrauma/BarotraumaShared/Source/Physics/PhysicsBody.cs index 5484e3ffe..0e293408b 100644 --- a/Barotrauma/BarotraumaShared/Source/Physics/PhysicsBody.cs +++ b/Barotrauma/BarotraumaShared/Source/Physics/PhysicsBody.cs @@ -512,11 +512,9 @@ namespace Barotrauma #endif } - public bool IsValidValue(float value, string valueName, float? minValue = null, float? maxValue = null) + public bool IsValidValue(float value, string valueName, float minValue = float.MinValue, float maxValue = float.MaxValue) { - if (!MathUtils.IsValid(value) || - (minValue.HasValue && value < minValue.Value) || - (maxValue.HasValue && value > maxValue.Value)) + if (!MathUtils.IsValid(value) || value < minValue || value > maxValue) { string userData = UserData == null ? "null" : UserData.ToString(); string errorMsg = @@ -539,11 +537,11 @@ namespace Barotrauma return true; } - private bool IsValidValue(Vector2 value, string valueName, float? minValue = null, float? maxValue = null) + private bool IsValidValue(Vector2 value, string valueName, float minValue = float.MinValue, float maxValue = float.MaxValue) { if (!MathUtils.IsValid(value) || - (minValue.HasValue && (value.X < minValue.Value || value.Y < minValue.Value)) || - (maxValue.HasValue && (value.X > maxValue.Value || value.Y > maxValue))) + (value.X < minValue || value.Y < minValue) || + (value.X > maxValue || value.Y > maxValue)) { string userData = UserData == null ? "null" : UserData.ToString(); string errorMsg = diff --git a/Barotrauma/BarotraumaShared/Source/Screens/GameScreen.cs b/Barotrauma/BarotraumaShared/Source/Screens/GameScreen.cs index e7a5815ad..176b6dede 100644 --- a/Barotrauma/BarotraumaShared/Source/Screens/GameScreen.cs +++ b/Barotrauma/BarotraumaShared/Source/Screens/GameScreen.cs @@ -7,12 +7,18 @@ namespace Barotrauma { partial class GameScreen : Screen { - private Camera cam; + private readonly Camera cam; public override Camera Cam { get { return cam; } } + + public double GameTime + { + get; + private set; + } public GameScreen() { @@ -74,6 +80,9 @@ namespace Barotrauma closestSub.ApplyForce(targetMovement * closestSub.SubBody.Body.Mass * 100.0f); } #endif + + GameTime += deltaTime; + foreach (PhysicsBody body in PhysicsBody.List) { body.Update((float)deltaTime); diff --git a/Barotrauma/BarotraumaShared/Source/Serialization/XMLExtensions.cs b/Barotrauma/BarotraumaShared/Source/Serialization/XMLExtensions.cs index d1409336d..5f50ee246 100644 --- a/Barotrauma/BarotraumaShared/Source/Serialization/XMLExtensions.cs +++ b/Barotrauma/BarotraumaShared/Source/Serialization/XMLExtensions.cs @@ -15,7 +15,9 @@ namespace Barotrauma public static string ParseContentPathFromUri(this XObject element) { string[] splitted = element.BaseUri.Split(new char[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }); - IEnumerable filtered = splitted.SkipWhile(part => part != "Content"); + string currentFolder = Environment.CurrentDirectory.Split(new char[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }).Last(); + // Filter out the current folder -> result is "Content/blaahblaah" or "Mods/blaahblaah" etc. + IEnumerable filtered = splitted.SkipWhile(part => part != currentFolder).Skip(1); return string.Join("/", filtered); } diff --git a/Barotrauma/BarotraumaShared/Source/StatusEffects/StatusEffect.cs b/Barotrauma/BarotraumaShared/Source/StatusEffects/StatusEffect.cs index 550d7c11f..7b063568f 100644 --- a/Barotrauma/BarotraumaShared/Source/StatusEffects/StatusEffect.cs +++ b/Barotrauma/BarotraumaShared/Source/StatusEffects/StatusEffect.cs @@ -121,6 +121,8 @@ namespace Barotrauma private List spawnItems; + private Character user; + public readonly float FireSize; public HashSet TargetIdentifiers @@ -494,6 +496,7 @@ namespace Barotrauma public void SetUser(Character user) { + this.user = user; foreach (Affliction affliction in Afflictions) { affliction.Source = user; @@ -627,7 +630,7 @@ namespace Barotrauma } } - if (explosion != null && entity != null) explosion.Explode(entity.WorldPosition, entity); + if (explosion != null && entity != null) { explosion.Explode(entity.WorldPosition, damageSource: entity, attacker: user); } foreach (ISerializableEntity target in targets) { @@ -655,14 +658,26 @@ namespace Barotrauma foreach (Pair reduceAffliction in ReduceAffliction) { float reduceAmount = disableDeltaTime ? reduceAffliction.Second : reduceAffliction.Second * deltaTime; + Limb targetLimb = null; + Character targetCharacter = null; if (target is Character character) { - character.CharacterHealth.ReduceAffliction(null, reduceAffliction.First, reduceAmount); + targetCharacter = character; } else if (target is Limb limb) { - limb.character.CharacterHealth.ReduceAffliction(limb, reduceAffliction.First, reduceAmount); + targetLimb = limb; + targetCharacter = limb.character; } + if (targetCharacter != null) + { + float prevVitality = targetCharacter.Vitality; + targetCharacter.CharacterHealth.ReduceAffliction(targetLimb, reduceAffliction.First, reduceAmount); +#if SERVER + GameMain.Server.KarmaManager.OnCharacterHealthChanged(targetCharacter, user, prevVitality - targetCharacter.Vitality); +#endif + } + } } @@ -822,13 +837,24 @@ namespace Barotrauma foreach (Pair reduceAffliction in element.Parent.ReduceAffliction) { - if (target is Character) + Limb targetLimb = null; + Character targetCharacter = null; + if (target is Character character) { - ((Character)target).CharacterHealth.ReduceAffliction(null, reduceAffliction.First, reduceAffliction.Second * deltaTime); + targetCharacter = character; } else if (target is Limb limb) { - limb.character.CharacterHealth.ReduceAffliction(limb, reduceAffliction.First, reduceAffliction.Second * deltaTime); + targetLimb = limb; + targetCharacter = limb.character; + } + if (targetCharacter != null) + { + float prevVitality = targetCharacter.Vitality; + targetCharacter.CharacterHealth.ReduceAffliction(targetLimb, reduceAffliction.First, reduceAffliction.Second * deltaTime); +#if SERVER + GameMain.Server.KarmaManager.OnCharacterHealthChanged(targetCharacter, element.Parent.user, prevVitality - targetCharacter.Vitality); +#endif } } } diff --git a/Barotrauma/BarotraumaShared/Source/TextManager.cs b/Barotrauma/BarotraumaShared/Source/TextManager.cs index a14401921..beacafa37 100644 --- a/Barotrauma/BarotraumaShared/Source/TextManager.cs +++ b/Barotrauma/BarotraumaShared/Source/TextManager.cs @@ -15,11 +15,17 @@ namespace Barotrauma //key = language private static Dictionary> textPacks = new Dictionary>(); - private static string[] serverMessageCharacters = new string[] { "~", "[", "]", "=" }; + private static readonly string[] serverMessageCharacters = new string[] { "~", "[", "]", "=" }; public static string Language; - private static HashSet availableLanguages = new HashSet(); + public static bool Initialized + { + get; + private set; + } + + private static readonly HashSet availableLanguages = new HashSet(); public static IEnumerable AvailableLanguages { get { return availableLanguages; } @@ -99,6 +105,7 @@ namespace Barotrauma availableLanguages.Add(textPack.Language); textPacks.Add(textPack.Language, new List() { textPack }); } + Initialized = true; } public static bool ContainsTag(string textTag) diff --git a/Barotrauma/BarotraumaShared/Source/Utils/ToolBox.cs b/Barotrauma/BarotraumaShared/Source/Utils/ToolBox.cs index 2e9e4114b..0844ee21b 100644 --- a/Barotrauma/BarotraumaShared/Source/Utils/ToolBox.cs +++ b/Barotrauma/BarotraumaShared/Source/Utils/ToolBox.cs @@ -260,19 +260,20 @@ namespace Barotrauma public static string SecondsToReadableTime(float seconds) { + int s = (int)(seconds % 60.0f); if (seconds < 60.0f) { - return (int)seconds + " s"; + return s + " s"; } - else - { - int m = (int)(seconds / 60.0f); - int s = (int)(seconds % 60.0f); - return s == 0 ? - m + " m" : - m + " m " + s + " s"; - } + int h = (int)(seconds / (60.0f * 60.0f)); + int m = (int)((seconds / 60.0f) % 60); + + string text = ""; + if (h != 0) { text = h + " h"; } + if (m != 0) { text = string.IsNullOrEmpty(text) ? m + " m" : string.Join(" ", text, m, "m"); } + if (s != 0) { text = string.IsNullOrEmpty(text) ? s + " s" : string.Join(" ", text, s, "s"); } + return text; } private static Dictionary> cachedLines = new Dictionary>(); diff --git a/Barotrauma/BarotraumaShared/Submarines/Humpback.sub b/Barotrauma/BarotraumaShared/Submarines/Humpback.sub index 38b34627a..30f9ea929 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Humpback.sub and b/Barotrauma/BarotraumaShared/Submarines/Humpback.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Remora.sub b/Barotrauma/BarotraumaShared/Submarines/Remora.sub index 892a2fdbf..36417c181 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Remora.sub and b/Barotrauma/BarotraumaShared/Submarines/Remora.sub differ diff --git a/Barotrauma/BarotraumaShared/changelog.txt b/Barotrauma/BarotraumaShared/changelog.txt index 529f60b6e..cf04fe7ac 100644 --- a/Barotrauma/BarotraumaShared/changelog.txt +++ b/Barotrauma/BarotraumaShared/changelog.txt @@ -1,3 +1,112 @@ +--------------------------------------------------------------------------------------------------------- +v0.9.2.0 +--------------------------------------------------------------------------------------------------------- + +Easier server hosting: +- Switched to Steam's networking API, which allows clients to connect to servers without the need for the +host to forward their ports. + +Anti-griefing: +- Karma: a system that detects malicious actions and automatically creates a more challenging experience +for griefers, or potentially triggers a kickban. The feature is completely optional and the server hosts +can decide how aggressively it will react to malicious actions. In a nutshell, you can lose karma for doing +dumb things, karma is regained gradually over time and may be increased more rapidly by doing good things, +thus negating false positives and also giving you the chance to redeem yourself. Lose too much karma and +you'll start to experience increasing levels of inconvenience. +- Wire griefing is more difficult: wires have to be disconnected from the connection panels at both ends +before they can be removed. Disconnected wires can be seen visibly hanging from the device, with noticeable +particle effects and sound cues that make it easier to detect and fix wiring problems. +- Added a built-in overheat warning light and siren to reactors and increased the time it takes for the +reactor to catch fire and explode. +- Option to disable friendly fire altogether. + +Expanded traitor mode: +- More varied objectives: destroy cargo, jam the sonar, sabotage devices, infect crew members, kill a specific +target, cause a reactor meltdown, flood the sub... +- The traitors must complete a set of tasks, generally starting with less destructive objectives that +give the other players a better chance to detect (and detain/kill) the traitor before they proceed to the +more lethal ones. + +New items: +- Additional weapons: stun gun, SMG, shotgun, grenade launcher, flamer. +- New monster loot: husk stinger, mudraptor shell, crawler mask, moloch shell shield. +- Fun stuff: new musical instruments, captain's pipe, clown hammer. + +Bugfixes: +- Fixed shuttles not being able to redock into some submarines with unconventionally positioned docking +ports. Specifically, if a port needed to be docked to from below but was positioned above the center of +the submarine (or vice versa), the docking interface would not activate on the navigation terminal. +- Fixed crashing when combining items inside an ItemContainer (e.g. cabinet, deconstructor). +- Fixed item editing panel not appearing in wiring mode when selecting a wire in the sub editor. +- Fixed ability to throw throwable items while unconscious. +- Fixed bots not using fire extinguishers client-side in multiplayer. +- Fixed clients not hearing the sound when someone crowbars a door open. +- Fixed progress bars not showing client-side when cutting/welding doors. +- Fixed inability to combine items in multiplayer. +- Fixed inability to scroll the crew list with the mouse wheel when the cursor is over certain parts +of the list. +- Fixed sonar pings stopping mid-way when active sonar is turned off, which could be exploited to stop +the pings before they reach a monster further away from the sub. +- Fixed clients not seeing other characters when entering spectator mode after their character has been +eaten by a creature. +- Fixed clients not seeing other characters in spectator mode after the distance between the submarine +and the client's corpse gets great enough. +- Fixed clients getting stuck to a non-functional game screen if they start a new round before the ending +cinematic has finished server-side. +- Fixed projectiles hitting a character you're standing next to when firing. +- Fixed characters automatically equipping handcuffs (i.e. handcuffing themselves) if they were picked up +when the only free inventory space was the hand slots. +- Fixed a rare race condition that occasionally caused the game to crash during the loading screen with +a "no text packs available in English!" error message. +- Fixed husk infection being healable with broad-spectrum antibiotics even when the infection has reached +the final stage. +- Fixed attachable items (detonators, electrical components) becoming deattachable without any tools +if the sub is saved after deattaching them. +- Fixed projectile raycasts not taking wall rotation into account, causing the projectiles to hit sloped +walls when standing next to them. +- Fixed crashing in the character editor when no textures were found for the selected character. +- Fixed crashing if a humanoid character has no knees or ankles. +- Fixed EventManager calculating flooding amount incorrectly, causing floods to only have a very small +effect on the intensity value. +- Fixed batteries and supercapacitors being able to provide power through their signal connections +(e.g. "set_charge_rate", "charge_rate_out"). +- Lighting fix: obstruct background lights behind hulls. Previously the background lights would only get +obstructed by background structures, causing light to "bleed through" parts with no structures (e.g. +humpback's docking port) and windows in the background walls. +- Fixed item loading being interrupted if any item XML cannot be loaded, causing some items not to be +loaded if any of the selected content packages are missing files or contain corrupted item XMLs. + +Misc additions and changes: +- Respawn shuttles now spawn inside the level somewhere near the submarine, instead of always spawning +at the beginning of the level. +- Respawn shuttles don't leave and despawn until a new respawn is pending. +- Added "respawnnow" console command which immediately dispatches the respawn shuttle. +- Search for default animation/ragdoll folders from the folder where the character file is located, not +from "Content/Characters". Fixes mods not being able to load animation/ragdoll files if the file paths +are not defined explicitly in the character configuration file. +- Improved performance by deleting dead monsters that are far away from the sub and by disabling physics +on dead bodies when they stay still long enough. +- In wiring mode, items are selected by pressing E instead of clicking. Selecting items with the left +mouse button made it very difficult to manipulate the wires because it was easy to accidentally select +some device instead of a wire node. +- Added limits to submarine name and description length. +- Delete incomplete file downloads when disconnecting from a server while a file transfer is active. +Prevents console errors about corrupted submarine/save files caused by the partially downloaded files. +- Increased Humpback's battery capacity. +- Added widgets for manipulating coilgun/railgun rotation limits in the sub editor. +- Show ballast tanks and airlocks in a different color on the status monitor to make it easier to +distinguish which rooms are actually flooding and which are supposed to have water in them. +- Option to define multiple inventory variants for a character (e.g. to add variation to monster loot). +- Start playing the main menu music during the loading screen. +- More reliable human walk sounds (played at specific points of the walk cycle instead of relying on +impacts between the feet and the floor). +- Option to show a 16x16 grid and snap the cursor to it in the sprite editor. +- Reactor can be controlled with movement keys. +- Option to add a probability for afflictions applied by an attack. +- Husk attacks have a 50% chance of causing a husk infection. +- Decreased minimum item scale from 0.1 to 0.01. +- The amount of materials received from deconstructing fuel rods depends on the condition of the fuel rod. + --------------------------------------------------------------------------------------------------------- v0.9.1.0 --------------------------------------------------------------------------------------------------------- diff --git a/Barotrauma/BarotraumaShared/libopenal.so.1 b/Barotrauma/BarotraumaShared/libopenal.so.1 deleted file mode 100755 index af45fd0c2..000000000 Binary files a/Barotrauma/BarotraumaShared/libopenal.so.1 and /dev/null differ diff --git a/Barotrauma/BarotraumaShared/libsteam_api.dylib b/Barotrauma/BarotraumaShared/libsteam_api.dylib deleted file mode 100644 index 6e2309a81..000000000 Binary files a/Barotrauma/BarotraumaShared/libsteam_api.dylib and /dev/null differ diff --git a/Barotrauma/BarotraumaShared/libsteam_api64.so b/Barotrauma/BarotraumaShared/libsteam_api64.so deleted file mode 100644 index edcda830f..000000000 Binary files a/Barotrauma/BarotraumaShared/libsteam_api64.so and /dev/null differ diff --git a/Barotrauma/BarotraumaShared/serversettings.xml b/Barotrauma/BarotraumaShared/serversettings.xml index 0aa7f4229..7b3925886 100644 --- a/Barotrauma/BarotraumaShared/serversettings.xml +++ b/Barotrauma/BarotraumaShared/serversettings.xml @@ -33,8 +33,8 @@ karmaenabled="False" gamemodeidentifier="sandbox" missiontype="Random" - autobantime="60" - maxautobantime="360" + autobantime="600" + maxautobantime="86400" name="Server" public="false" port="27015" diff --git a/Barotrauma/BarotraumaShared/steam_api64.dll b/Barotrauma/BarotraumaShared/steam_api64.dll deleted file mode 100644 index 00132885c..000000000 Binary files a/Barotrauma/BarotraumaShared/steam_api64.dll and /dev/null differ diff --git a/Barotrauma_Solution.sln b/Barotrauma_Solution.sln index 88ed780fe..bea6b8805 100644 --- a/Barotrauma_Solution.sln +++ b/Barotrauma_Solution.sln @@ -338,6 +338,7 @@ Global {85232B20-074D-4723-B0C6-91495391E448}.ReleaseWindows|x86.ActiveCfg = ReleaseWindows|x86 {85232B20-074D-4723-B0C6-91495391E448}.ReleaseWindows|x86.Build.0 = ReleaseWindows|x86 {85232B20-074D-4723-B0C6-91495391E448}.ReleaseMac|Any CPU.Build.0 = ReleaseMac|x64 + {85232B20-074D-4723-B0C6-91495391E448}.DebugMac|Any CPU.Build.0 = DebugMac|x64 {A4610E4C-DD34-428B-BABB-779CA0B5993A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A4610E4C-DD34-428B-BABB-779CA0B5993A}.Debug|Any CPU.Build.0 = Debug|Any CPU {A4610E4C-DD34-428B-BABB-779CA0B5993A}.Debug|x64.ActiveCfg = Debug|Any CPU diff --git a/Libraries/MonoGame.Framework/Windows/MonoGame.Framework.pdb b/Libraries/MonoGame.Framework/Windows/MonoGame.Framework.pdb new file mode 100644 index 000000000..8634c71d6 Binary files /dev/null and b/Libraries/MonoGame.Framework/Windows/MonoGame.Framework.pdb differ