From 6b36bf809d4df1fcdb37e53e473199295e1fa145 Mon Sep 17 00:00:00 2001 From: Juan Pablo Arce Date: Tue, 13 Oct 2020 12:59:45 -0300 Subject: [PATCH] Unstable v0.10.6.0 (October 13th 2020) --- .../ClientSource/Characters/Character.cs | 81 ++-- .../GameModes/SinglePlayerCampaign.cs | 11 + .../GameModes/Tutorials/CaptainTutorial.cs | 3 +- .../GameModes/Tutorials/EngineerTutorial.cs | 10 +- .../ClientSource/Items/CharacterInventory.cs | 10 +- .../Items/Components/ItemContainer.cs | 25 ++ .../Items/Components/Signal/Wire.cs | 3 +- .../ClientSource/Items/Components/Turret.cs | 4 +- .../ClientSource/Items/Inventory.cs | 4 +- .../BarotraumaClient/ClientSource/Map/Hull.cs | 64 +++- .../ClientSource/Map/Lights/LightSource.cs | 29 +- .../ClientSource/Networking/GameClient.cs | 9 +- .../ClientSource/Networking/ServerInfo.cs | 12 +- .../CharacterEditor/CharacterEditorScreen.cs | 10 +- .../ClientSource/Screens/MainMenuScreen.cs | 11 +- .../ClientSource/Screens/SubEditorScreen.cs | 11 +- .../ClientSource/Sounds/SoundPlayer.cs | 23 +- .../BarotraumaClient/LinuxClient.csproj | 2 +- Barotrauma/BarotraumaClient/MacClient.csproj | 2 +- .../BarotraumaClient/WindowsClient.csproj | 2 +- .../BarotraumaServer/LinuxServer.csproj | 2 +- Barotrauma/BarotraumaServer/MacServer.csproj | 2 +- .../Characters/CharacterNetworking.cs | 2 +- .../ServerSource/DebugConsole.cs | 12 + .../GameModes/MultiPlayerCampaign.cs | 16 + .../BarotraumaServer/ServerSource/Map/Hull.cs | 55 +-- .../ServerSource/Networking/GameServer.cs | 9 +- .../BarotraumaServer/WindowsServer.csproj | 2 +- .../Data/ContentPackages/Vanilla 0.9.xml | 2 + .../Characters/AI/AIController.cs | 2 +- .../Characters/AI/EnemyAIController.cs | 124 +++++-- .../Characters/AI/HumanAIController.cs | 5 +- .../Characters/AI/IndoorsSteeringManager.cs | 9 +- .../AI/Objectives/AIObjectiveFindSafety.cs | 8 +- .../AI/Objectives/AIObjectiveGetItem.cs | 4 + .../AI/Objectives/AIObjectiveGoTo.cs | 7 +- .../SharedSource/Characters/AI/PathFinder.cs | 10 +- .../SharedSource/Characters/AI/PetBehavior.cs | 154 +++++++- .../SharedSource/Characters/AICharacter.cs | 4 + .../Animation/FishAnimController.cs | 346 +++++++++++------- .../Animation/HumanoidAnimController.cs | 26 +- .../Characters/Animation/Ragdoll.cs | 10 +- .../SharedSource/Characters/Character.cs | 19 +- .../SharedSource/Characters/Limb.cs | 51 +++ .../Params/Animation/AnimationParams.cs | 2 +- .../Characters/Params/CharacterParams.cs | 9 + .../Params/Ragdoll/RagdollParams.cs | 60 ++- .../SharedSource/Events/EventManager.cs | 8 +- .../GameSession/GameModes/CampaignMode.cs | 2 + .../SharedSource/GameSession/GameSession.cs | 5 + .../Items/Components/Holdable/Throwable.cs | 52 ++- .../Items/Components/ItemContainer.cs | 13 + .../SharedSource/Items/Item.cs | 17 +- .../BarotraumaShared/SharedSource/Map/Hull.cs | 3 + .../SharedSource/Map/Map/Map.cs | 2 +- .../SharedSource/Map/MapEntity.cs | 6 +- .../Serialization/SerializableProperty.cs | 2 +- .../StatusEffects/StatusEffect.cs | 5 +- Barotrauma/BarotraumaShared/changelog.txt | 64 ++-- 59 files changed, 1036 insertions(+), 421 deletions(-) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs index 792894fd1..9a94fef2e 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs @@ -593,7 +593,7 @@ namespace Barotrauma } } - if (info != null || Vitality < MaxVitality * 0.98f) + if (info != null || Vitality < MaxVitality * 0.98f || IsPet) { hudInfoTimer -= deltaTime; if (hudInfoTimer <= 0.0f) @@ -772,49 +772,68 @@ namespace Barotrauma MathHelper.Clamp(1.0f - (cursorDist - (hoverRange - fadeOutRange)) / fadeOutRange, 0.2f, 1.0f) : 1.0f; - if (!GUI.DisableCharacterNames && hudInfoVisible && info != null && - (controlled == null || this != controlled.FocusedCharacter) && cam.Zoom > 0.4f) + if (!GUI.DisableCharacterNames && hudInfoVisible && + (controlled == null || this != controlled.FocusedCharacter || IsPet) && cam.Zoom > 0.4f) { - string name = Info.DisplayName; - if (controlled == null && name != Info.Name) { name += " " + TextManager.Get("Disguised"); } - - Vector2 nameSize = GUI.Font.MeasureString(name); - Vector2 namePos = new Vector2(pos.X, pos.Y - 10.0f - (5.0f / cam.Zoom)) - nameSize * 0.5f / cam.Zoom; - - Vector2 screenSize = new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight); - Vector2 viewportSize = new Vector2(cam.WorldView.Width, cam.WorldView.Height); - namePos.X -= cam.WorldView.X; namePos.Y += cam.WorldView.Y; - namePos *= screenSize / viewportSize; - namePos.X = (float)Math.Floor(namePos.X); namePos.Y = (float)Math.Floor(namePos.Y); - namePos *= viewportSize / screenSize; - namePos.X += cam.WorldView.X; namePos.Y -= cam.WorldView.Y; - - Color nameColor = Color.White; - if (Controlled != null && TeamID != Controlled.TeamID) + if (info != null) { - nameColor = TeamID == TeamType.FriendlyNPC ? Color.SkyBlue : GUI.Style.Red; + string name = Info.DisplayName; + if (controlled == null && name != Info.Name) { name += " " + TextManager.Get("Disguised"); } + + Vector2 nameSize = GUI.Font.MeasureString(name); + Vector2 namePos = new Vector2(pos.X, pos.Y - 10.0f - (5.0f / cam.Zoom)) - nameSize * 0.5f / cam.Zoom; + + Vector2 screenSize = new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight); + Vector2 viewportSize = new Vector2(cam.WorldView.Width, cam.WorldView.Height); + namePos.X -= cam.WorldView.X; namePos.Y += cam.WorldView.Y; + namePos *= screenSize / viewportSize; + namePos.X = (float)Math.Floor(namePos.X); namePos.Y = (float)Math.Floor(namePos.Y); + namePos *= viewportSize / screenSize; + namePos.X += cam.WorldView.X; namePos.Y -= cam.WorldView.Y; + + Color nameColor = Color.White; + if (Controlled != null && TeamID != Controlled.TeamID) + { + nameColor = TeamID == TeamType.FriendlyNPC ? Color.SkyBlue : GUI.Style.Red; + } + if (CampaignInteractionType != CampaignMode.InteractionType.None && AllowCustomInteract) + { + var iconStyle = GUI.Style.GetComponentStyle("CampaignInteractionBubble." + CampaignInteractionType); + if (iconStyle != null) + { + Vector2 headPos = AnimController.GetLimb(LimbType.Head)?.WorldPosition ?? WorldPosition + Vector2.UnitY * 100.0f; + Vector2 iconPos = headPos; + iconPos.Y = -iconPos.Y; + nameColor = iconStyle.Color; + var icon = iconStyle.Sprites[GUIComponent.ComponentState.None].First(); + float iconScale = 30.0f / icon.Sprite.size.X / cam.Zoom; + icon.Sprite.Draw(spriteBatch, iconPos + new Vector2(-35.0f, -25.0f), iconStyle.Color * hudInfoAlpha, scale: iconScale); + } + } + + GUI.Font.DrawString(spriteBatch, name, namePos + new Vector2(1.0f / cam.Zoom, 1.0f / cam.Zoom), Color.Black, 0.0f, Vector2.Zero, 1.0f / cam.Zoom, SpriteEffects.None, 0.001f); + GUI.Font.DrawString(spriteBatch, name, namePos, nameColor * hudInfoAlpha, 0.0f, Vector2.Zero, 1.0f / cam.Zoom, SpriteEffects.None, 0.0f); + if (GameMain.DebugDraw) + { + GUI.Font.DrawString(spriteBatch, ID.ToString(), namePos - new Vector2(0.0f, 20.0f), Color.White); + } } - if (CampaignInteractionType != CampaignMode.InteractionType.None && AllowCustomInteract) + + var petBehavior = (AIController as EnemyAIController)?.PetBehavior; + if (petBehavior != null && !IsDead && !IsUnconscious) { - var iconStyle = GUI.Style.GetComponentStyle("CampaignInteractionBubble." + CampaignInteractionType); + var petStatus = petBehavior.GetCurrentStatusIndicatorType(); + var iconStyle = GUI.Style.GetComponentStyle("PetIcon." + petStatus); if (iconStyle != null) { Vector2 headPos = AnimController.GetLimb(LimbType.Head)?.WorldPosition ?? WorldPosition + Vector2.UnitY * 100.0f; Vector2 iconPos = headPos; iconPos.Y = -iconPos.Y; - nameColor = iconStyle.Color; var icon = iconStyle.Sprites[GUIComponent.ComponentState.None].First(); - float iconScale = 30.0f / icon.Sprite.size.X / cam.Zoom; + float iconScale = 30.0f / icon.Sprite.size.X / cam.Zoom; icon.Sprite.Draw(spriteBatch, iconPos + new Vector2(-35.0f, -25.0f), iconStyle.Color * hudInfoAlpha, scale: iconScale); } } - - GUI.Font.DrawString(spriteBatch, name, namePos + new Vector2(1.0f / cam.Zoom, 1.0f / cam.Zoom), Color.Black, 0.0f, Vector2.Zero, 1.0f / cam.Zoom, SpriteEffects.None, 0.001f); - GUI.Font.DrawString(spriteBatch, name, namePos, nameColor * hudInfoAlpha, 0.0f, Vector2.Zero, 1.0f / cam.Zoom, SpriteEffects.None, 0.0f); - if (GameMain.DebugDraw) - { - GUI.Font.DrawString(spriteBatch, ID.ToString(), namePos - new Vector2(0.0f, 20.0f), Color.White); - } } if (IsDead) { return; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/SinglePlayerCampaign.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/SinglePlayerCampaign.cs index f8283e055..01df20fc1 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/SinglePlayerCampaign.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/SinglePlayerCampaign.cs @@ -98,6 +98,9 @@ namespace Barotrauma case "pendingupgrades": UpgradeManager = new UpgradeManager(this, subElement, isSingleplayer: true); break; + case "pets": + petsElement = subElement; + break; } } @@ -213,6 +216,10 @@ namespace Barotrauma crewDead = false; endTimer = 5.0f; CrewManager.InitSinglePlayerRound(); + if (petsElement != null) + { + PetBehavior.LoadPets(petsElement); + } } protected override void LoadInitialLevel() @@ -705,6 +712,10 @@ namespace Barotrauma } } + XElement petsElement = new XElement("pets"); + PetBehavior.SavePets(petsElement); + modeElement.Add(petsElement); + CrewManager.Save(modeElement); CampaignMetadata.Save(modeElement); Map.Save(modeElement); diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/CaptainTutorial.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/CaptainTutorial.cs index 1960acaf9..98cc31663 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/CaptainTutorial.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/CaptainTutorial.cs @@ -186,8 +186,7 @@ namespace Barotrauma.Tutorials // TODO: Rework order highlighting for new command UI // GameMain.GameSession.CrewManager.HighlightOrderButton(captain_mechanic, "repairsystems", highlightColor, new Vector2(5, 5)); //HighlightOrderOption("jobspecific"); - } - while (!HasOrder(captain_mechanic, "repairsystems")); + } while (!HasOrder(captain_mechanic, "repairsystems") && !HasOrder(captain_mechanic, "repairmechanical") && !HasOrder(captain_mechanic, "repairelectrical")); RemoveCompletedObjective(segments[1]); yield return new WaitForSeconds(2f, false); TriggerTutorialSegment(2, GameMain.Config.KeyBindText(InputType.Command)); diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/EngineerTutorial.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/EngineerTutorial.cs index 89aff68ae..09c22b475 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/EngineerTutorial.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/EngineerTutorial.cs @@ -123,7 +123,7 @@ namespace Barotrauma.Tutorials // Room 3 engineer_reactorObjectiveSensor = Item.ItemList.Find(i => i.HasTag("engineer_reactorobjectivesensor")).GetComponent(); - tutorial_oxygenGenerator = Item.ItemList.Find(i => i.HasTag("tutorial_oxygengenerator")).GetComponent(); + tutorial_oxygenGenerator = Item.ItemList.Find(i => i.HasTag("tutorial_oxygengenerator")).GetComponent(); engineer_reactor = Item.ItemList.Find(i => i.HasTag("engineer_reactor")).GetComponent(); engineer_reactor.FireDelay = engineer_reactor.MeltdownDelay = float.PositiveInfinity; engineer_reactor.FuelConsumptionRate = 0.0f; @@ -380,7 +380,7 @@ namespace Barotrauma.Tutorials yield return null; } while (engineer_brokenJunctionBox.Condition < repairableJunctionBoxComponent.RepairThreshold); // Wait until repaired SetHighlight(engineer_brokenJunctionBox, false); - RemoveCompletedObjective(segments[2]); + RemoveCompletedObjective(segments[3]); SetDoorAccess(engineer_thirdDoor, engineer_thirdDoorLight, true); for (int i = 0; i < engineer_disconnectedJunctionBoxes.Length; i++) { @@ -398,7 +398,7 @@ namespace Barotrauma.Tutorials { SetHighlight(engineer_disconnectedJunctionBoxes[i].Item, false); } - RemoveCompletedObjective(segments[3]); + RemoveCompletedObjective(segments[4]); do { yield return null; } while (engineer_workingPump.Item.CurrentHull.WaterPercentage > waterVolumeBeforeOpening); // Wait until drained wiringActive = false; SetDoorAccess(engineer_fourthDoor, engineer_fourthDoorLight, true); @@ -424,7 +424,7 @@ namespace Barotrauma.Tutorials // Remove highlights when each individual machine is repaired do { CheckJunctionBoxHighlights(repairableJunctionBoxComponent1, repairableJunctionBoxComponent2, repairableJunctionBoxComponent3); yield return null; } while (engineer_submarineJunctionBox_1.Condition < repairableJunctionBoxComponent1.RepairThreshold || engineer_submarineJunctionBox_2.Condition < repairableJunctionBoxComponent2.RepairThreshold || engineer_submarineJunctionBox_3.Condition < repairableJunctionBoxComponent3.RepairThreshold); CheckJunctionBoxHighlights(repairableJunctionBoxComponent1, repairableJunctionBoxComponent2, repairableJunctionBoxComponent3); - RemoveCompletedObjective(segments[4]); + RemoveCompletedObjective(segments[5]); yield return new WaitForSeconds(2f, false); TriggerTutorialSegment(6); // Powerup reactor @@ -433,7 +433,7 @@ namespace Barotrauma.Tutorials do { yield return null; } while (!IsReactorPoweredUp(engineer_submarineReactor)); // Wait until ~matches load engineer.RemoveActiveObjectiveEntity(engineer_submarineReactor.Item); SetHighlight(engineer_submarineReactor.Item, false); - RemoveCompletedObjective(segments[5]); + RemoveCompletedObjective(segments[6]); GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Engineer.Radio.Complete"), ChatMessageType.Radio, null); yield return new WaitForSeconds(4f, false); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/CharacterInventory.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/CharacterInventory.cs index 9171c6b6d..850a0a7c1 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/CharacterInventory.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/CharacterInventory.cs @@ -144,17 +144,13 @@ namespace Barotrauma protected override ItemInventory GetActiveEquippedSubInventory(int slotIndex) { var item = Items[slotIndex]; - if (item == null) return null; + if (item == null) { return null; } var container = item.GetComponent(); - if (container == null || - !character.CanAccessInventory(container.Inventory) || - !container.KeepOpenWhenEquipped || - !character.HasEquippedItem(container.Item)) + if (container == null || !container.KeepOpenWhenEquippedBy(character)) { return null; } - return container.Inventory; } @@ -626,7 +622,7 @@ namespace Barotrauma { var itemContainer = item.GetComponent(); if (itemContainer != null && - itemContainer.KeepOpenWhenEquipped && + itemContainer.KeepOpenWhenEquippedBy(character) && character.CanAccessInventory(itemContainer.Inventory) && !highlightedSubInventorySlots.Any(s => s.Inventory == itemContainer.Inventory)) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemContainer.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemContainer.cs index f961eba32..205a72010 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemContainer.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemContainer.cs @@ -70,6 +70,7 @@ namespace Barotrauma.Items.Components [Serialize(false, false, description: "Should the inventory of this item be kept open when the item is equipped by a character.")] public bool KeepOpenWhenEquipped { get; set; } + [Serialize(false, false, description: "Can the inventory of this item be moved around on the screen by the player.")] public bool MovableFrame { get; set; } @@ -162,6 +163,30 @@ namespace Barotrauma.Items.Components } } + public bool KeepOpenWhenEquippedBy(Character character) + { + if (!character.CanAccessInventory(Inventory) || + !KeepOpenWhenEquipped || + !character.HasEquippedItem(Item)) + { + return false; + } + + //if holding 2 different "always open" items in different hands, don't force them to stay open + if (character.SelectedItems[0] != null && + character.SelectedItems[1] != null && + character.SelectedItems[0] != character.SelectedItems[1]) + { + if ((character.SelectedItems[0].GetComponent()?.KeepOpenWhenEquipped ?? false) && + (character.SelectedItems[1].GetComponent()?.KeepOpenWhenEquipped ?? false)) + { + return false; + } + } + + return true; + } + public void Draw(SpriteBatch spriteBatch, bool editing = false, float itemDepth = -1) { if (hideItems || (item.body != null && !item.body.Enabled)) { return; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Wire.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Wire.cs index 4e871f44b..70fa59879 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Wire.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Wire.cs @@ -342,7 +342,8 @@ namespace Barotrauma.Items.Components } else { - if (Vector2.DistanceSquared(nodeWorldPos, draggingWire.nodes[(int)highlightedNodeIndex]) > Submarine.GridSize.X * Submarine.GridSize.X || PlayerInput.IsShiftDown()) + if ((highlightedNodeIndex.HasValue && Vector2.DistanceSquared(nodeWorldPos, draggingWire.nodes[(int)highlightedNodeIndex]) > Submarine.GridSize.X * Submarine.GridSize.X) || + PlayerInput.IsShiftDown()) { selectedNodeIndex = highlightedNodeIndex; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Turret.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Turret.cs index a2a36742f..4ce13c0a2 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Turret.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Turret.cs @@ -286,7 +286,7 @@ namespace Barotrauma.Items.Components rotation + MathHelper.PiOver2, item.Scale, SpriteEffects.None, item.SpriteDepth + (barrelSprite.Depth - item.Sprite.Depth)); - if (!editing || GUI.DisableHUD) { return; } + if (!editing || GUI.DisableHUD || !item.IsSelected) { return; } float widgetRadius = 60.0f; @@ -305,8 +305,6 @@ namespace Barotrauma.Items.Components 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.Selected += () => diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs index 06d89d21f..3ba47d040 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs @@ -404,8 +404,8 @@ namespace Barotrauma container = (this as ItemInventory).Container; } - if (container == null) return false; - return owner.SelectedCharacter != null || !container.KeepOpenWhenEquipped || (!(owner is Character)) || !owner.HasEquippedItem(container.Item); + if (container == null) { return false; } + return owner.SelectedCharacter != null|| (!(owner is Character character)) || !container.KeepOpenWhenEquippedBy(character) || !owner.HasEquippedItem(container.Item); } protected virtual bool HideSlot(int i) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Hull.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Hull.cs index 7c2bc18f3..a99f8e36f 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Hull.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Hull.cs @@ -10,11 +10,30 @@ namespace Barotrauma { partial class Hull : MapEntity, ISerializableEntity, IServerSerializable, IClientSerializable { + private class RemoteDecal + { + public readonly UInt32 DecalId; + public readonly int SpriteIndex; + public Vector2 NormalizedPos; + public readonly float Scale; + + public RemoteDecal(UInt32 decalId, int spriteIndex, Vector2 normalizedPos, float scale) + { + DecalId = decalId; + SpriteIndex = spriteIndex; + NormalizedPos = normalizedPos; + Scale = scale; + } + } + private float serverUpdateDelay; private float remoteWaterVolume, remoteOxygenPercentage; private List remoteFireSources; private readonly List remoteBackgroundSections = new List(); + private readonly List remoteDecals = new List(); + private readonly HashSet pendingDecalUpdates = new HashSet(); + private double lastAmbientLightEditTime; public override bool SelectableInEditor @@ -126,10 +145,14 @@ namespace Barotrauma networkUpdateTimer += deltaTime; if (networkUpdateTimer > 0.2f) { - if (!pendingSectionUpdates.Any()) + if (!pendingSectionUpdates.Any() && !pendingDecalUpdates.Any()) { GameMain.NetworkMember?.CreateEntityEvent(this); } + foreach (Decal decal in pendingDecalUpdates) + { + GameMain.NetworkMember?.CreateEntityEvent(this, new object[] { decal }); + } foreach (int pendingSectionUpdate in pendingSectionUpdates) { GameMain.NetworkMember?.CreateEntityEvent(this, new object[] { pendingSectionUpdate }); @@ -538,9 +561,9 @@ namespace Barotrauma public void ClientWrite(IWriteMessage msg, object[] extraData = null) { - msg.Write(extraData != null); if (extraData == null) { + msg.WriteRangedInteger(0, 0, 2); msg.WriteRangedSingle(MathHelper.Clamp(waterVolume / Volume, 0.0f, 1.5f), 0.0f, 1.5f, 8); msg.Write(FireSources.Count > 0); @@ -560,8 +583,16 @@ namespace Barotrauma } } } + else if (extraData[0] is Decal decal) + { + msg.WriteRangedInteger(1, 0, 2); + int decalIndex = decals.IndexOf(decal); + msg.Write((byte)(decalIndex < 0 ? 255 : decalIndex)); + msg.WriteRangedSingle(decal.BaseAlpha, 0.0f, 1.0f, 8); + } else { + msg.WriteRangedInteger(2, 0, 2); int sectorToUpdate = (int)extraData[0]; int start = sectorToUpdate * BackgroundSectionsPerNetworkEvent; int end = Math.Min((sectorToUpdate + 1) * BackgroundSectionsPerNetworkEvent, BackgroundSections.Count - 1); @@ -622,22 +653,16 @@ namespace Barotrauma else { int decalCount = message.ReadRangedInteger(0, MaxDecalsPerHull); - decals.Clear(); + if (decalCount == 0) { decals.Clear(); } + remoteDecals.Clear(); for (int i = 0; i < decalCount; i++) { UInt32 decalId = message.ReadUInt32(); int spriteIndex = message.ReadByte(); float normalizedXPos = message.ReadRangedSingle(0.0f, 1.0f, 8); float normalizedYPos = message.ReadRangedSingle(0.0f, 1.0f, 8); - float decalPosX = MathHelper.Lerp(rect.X, rect.Right, normalizedXPos); - float decalPosY = MathHelper.Lerp(rect.Y - rect.Height, rect.Y, normalizedYPos); float decalScale = message.ReadRangedSingle(0.0f, 2.0f, 12); - if (Submarine != null) - { - decalPosX += Submarine.Position.X; - decalPosY += Submarine.Position.Y; - } - AddDecal(decalId, new Vector2(decalPosX, decalPosY), decalScale, isNetworkEvent: true, spriteIndex: spriteIndex); + remoteDecals.Add(new RemoteDecal(decalId, spriteIndex, new Vector2(normalizedXPos, normalizedYPos), decalScale)); } } } @@ -658,6 +683,23 @@ namespace Barotrauma } remoteBackgroundSections.Clear(); + if (remoteDecals.Any()) + { + decals.Clear(); + foreach (RemoteDecal remoteDecal in remoteDecals) + { + float decalPosX = MathHelper.Lerp(rect.X, rect.Right, remoteDecal.NormalizedPos.X); + float decalPosY = MathHelper.Lerp(rect.Y - rect.Height, rect.Y, remoteDecal.NormalizedPos.Y); + if (Submarine != null) + { + decalPosX += Submarine.Position.X; + decalPosY += Submarine.Position.Y; + } + AddDecal(remoteDecal.DecalId, new Vector2(decalPosX, decalPosY), remoteDecal.Scale, isNetworkEvent: true, spriteIndex: remoteDecal.SpriteIndex); + } + remoteDecals.Clear(); + } + if (remoteFireSources == null) { return; } WaterVolume = remoteWaterVolume; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightSource.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightSource.cs index 22f24c0c2..438cbb3cb 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightSource.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightSource.cs @@ -44,6 +44,12 @@ namespace Barotrauma.Lights } } + [Serialize(1f, true), Editable(minValue: 0.01f, maxValue: 100f, ValueStep = 0.1f, DecimalCount = 2)] + public float Scale { get; set; } + + [Serialize("0, 0", true), Editable(ValueStep = 1, DecimalCount = 1, MinValueFloat = -1000f, MaxValueFloat = 1000f)] + public Vector2 Offset { get; set; } + public float TextureRange { get; @@ -241,11 +247,13 @@ namespace Barotrauma.Lights } } + private Vector2 _spriteScale = Vector2.One; + public Vector2 SpriteScale { - get; - set; - } = Vector2.One; + get { return _spriteScale * lightSourceParams.Scale; } + set { _spriteScale = value; } + } public float? OverrideLightSpriteAlpha { @@ -280,7 +288,9 @@ namespace Barotrauma.Lights { get { return lightSourceParams.LightSprite; } } - + + private Vector2 OverrideLightTextureOrigin => OverrideLightTexture.Origin + LightSourceParams.Offset; + public Color Color { get { return lightSourceParams.Color; } @@ -564,7 +574,7 @@ namespace Barotrauma.Lights var overrideTextureDims = new Vector2(OverrideLightTexture.SourceRect.Width, OverrideLightTexture.SourceRect.Height); - Vector2 origin = OverrideLightTexture.Origin; + Vector2 origin = OverrideLightTextureOrigin; origin /= Math.Max(overrideTextureDims.X, overrideTextureDims.Y); origin -= Vector2.One * 0.5f; @@ -873,7 +883,7 @@ namespace Barotrauma.Lights { overrideTextureDims = new Vector2(OverrideLightTexture.SourceRect.Width, OverrideLightTexture.SourceRect.Height); - Vector2 origin = OverrideLightTexture.Origin; + Vector2 origin = OverrideLightTextureOrigin; if (LightSpriteEffect == SpriteEffects.FlipHorizontally) { origin.X = OverrideLightTexture.SourceRect.Width - origin.X; } if (LightSpriteEffect == SpriteEffects.FlipVertically) { origin.Y = OverrideLightTexture.SourceRect.Height - origin.Y; } uvOffset = (origin / overrideTextureDims) - new Vector2(0.5f, 0.5f); @@ -1060,7 +1070,7 @@ namespace Barotrauma.Lights { var overrideTextureDims = new Vector2(OverrideLightTexture.SourceRect.Width, OverrideLightTexture.SourceRect.Height); - Vector2 origin = OverrideLightTexture.Origin; + Vector2 origin = OverrideLightTextureOrigin; origin /= Math.Max(overrideTextureDims.X, overrideTextureDims.Y); origin *= TextureRange; @@ -1088,13 +1098,12 @@ namespace Barotrauma.Lights if (DeformableLightSprite != null) { - Vector2 origin = DeformableLightSprite.Origin; + Vector2 origin = DeformableLightSprite.Origin + LightSourceParams.Offset; Vector2 drawPos = position; if (ParentSub != null) { drawPos += ParentSub.DrawPosition; } - if (LightSpriteEffect == SpriteEffects.FlipHorizontally) { origin.X = DeformableLightSprite.Sprite.SourceRect.Width - origin.X; @@ -1113,7 +1122,7 @@ namespace Barotrauma.Lights if (LightSprite != null) { - Vector2 origin = LightSprite.Origin; + Vector2 origin = LightSprite.Origin + LightSourceParams.Offset; if ((LightSpriteEffect & SpriteEffects.FlipHorizontally) == SpriteEffects.FlipHorizontally) { origin.X = LightSprite.SourceRect.Width - origin.X; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs index df183d5e1..df4da0256 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs @@ -1023,7 +1023,10 @@ namespace Barotrauma.Networking if (Enum.TryParse(splitMsg[0], out disconnectReason)) { disconnectReasonIncluded = true; } } - if (disconnectMsg == Lidgren.Network.NetConnection.NoResponseMessage) + if (disconnectMsg == Lidgren.Network.NetConnection.NoResponseMessage || + disconnectReason == DisconnectReason.Banned || + disconnectReason == DisconnectReason.Kicked || + disconnectReason == DisconnectReason.TooManyFailedLogins) { allowReconnect = false; } @@ -2138,6 +2141,7 @@ namespace Barotrauma.Networking return; } + entities.Add(entity); if (entity != null && (entity is Item || entity is Character || entity is Submarine)) { entity.ClientRead(objHeader.Value, inc, sendingTime); @@ -2188,7 +2192,8 @@ namespace Barotrauma.Networking errorLines.Add(ex.StackTrace.CleanupStackTrace()); errorLines.Add(" "); if (prevObjHeader == ServerNetObject.ENTITY_EVENT || prevObjHeader == ServerNetObject.ENTITY_EVENT_INITIAL || - objHeader == ServerNetObject.ENTITY_EVENT || objHeader == ServerNetObject.ENTITY_EVENT_INITIAL) + objHeader == ServerNetObject.ENTITY_EVENT || objHeader == ServerNetObject.ENTITY_EVENT_INITIAL || + objHeader == ServerNetObject.ENTITY_POSITION || prevObjHeader == ServerNetObject.ENTITY_POSITION) { foreach (IServerSerializable ent in entities) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerInfo.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerInfo.cs index e5dfe0417..063201ec6 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerInfo.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerInfo.cs @@ -373,7 +373,17 @@ namespace Barotrauma.Networking info.GameMode = element.GetAttributeString("GameMode", ""); info.GameVersion = element.GetAttributeString("GameVersion", ""); - info.MaxPlayers = element.GetAttributeInt("MaxPlayers", 0); + + int maxPlayersElement = element.GetAttributeInt("MaxPlayers", 0); + + if (maxPlayersElement > NetConfig.MaxPlayers) + { + DebugConsole.IsOpen = true; + DebugConsole.NewMessage($"Setting the maximum amount of players to {maxPlayersElement} failed due to exceeding the limit of {NetConfig.MaxPlayers} players per server. Using the maximum of {NetConfig.MaxPlayers} instead.", Color.Red); + maxPlayersElement = NetConfig.MaxPlayers; + } + + info.MaxPlayers = maxPlayersElement; if (Enum.TryParse(element.GetAttributeString("PlayStyle", ""), out PlayStyle playStyleTemp)) { info.PlayStyle = playStyleTemp; } if (bool.TryParse(element.GetAttributeString("UsingWhiteList", ""), out bool whitelistTemp)) { info.UsingWhiteList = whitelistTemp; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/CharacterEditor/CharacterEditorScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/CharacterEditor/CharacterEditorScreen.cs index 258e0e179..a006f00f9 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/CharacterEditor/CharacterEditorScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/CharacterEditor/CharacterEditorScreen.cs @@ -306,7 +306,7 @@ namespace Barotrauma.CharacterEditor var lastJoint = selectedJoints.LastOrDefault(); if (lastJoint != null) { - lastLimb = PlayerInput.KeyDown(Keys.LeftAlt) ? lastJoint.LimbA : lastJoint.LimbB; + lastLimb = PlayerInput.KeyDown(Keys.LeftAlt) ? lastJoint.LimbB : lastJoint.LimbA; } } if (lastLimb != null) @@ -941,7 +941,7 @@ namespace Barotrauma.CharacterEditor var lastJoint = selectedJoints.LastOrDefault(); if (lastJoint != null) { - lastLimb = PlayerInput.KeyDown(Keys.LeftAlt) ? lastJoint.LimbA : lastJoint.LimbB; + lastLimb = PlayerInput.KeyDown(Keys.LeftAlt) ? lastJoint.LimbB : lastJoint.LimbA; } } if (lastLimb != null) @@ -2389,7 +2389,7 @@ namespace Barotrauma.CharacterEditor IEnumerable limbs = selectedLimbs; if (limbs.None()) { - limbs = selectedJoints.Select(j => PlayerInput.KeyDown(Keys.LeftAlt) ? j.LimbA : j.LimbB); + limbs = selectedJoints.Select(j => PlayerInput.KeyDown(Keys.LeftAlt) ? j.LimbB : j.LimbA); } foreach (var limb in limbs) { @@ -4452,11 +4452,11 @@ namespace Barotrauma.CharacterEditor } if (editJoints) { - if (!altDown && joint.BodyA == limb.body.FarseerBody) + if (altDown && joint.BodyA == limb.body.FarseerBody) { continue; } - if (altDown && joint.BodyB == limb.body.FarseerBody) + if (!altDown && joint.BodyB == limb.body.FarseerBody) { continue; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs index f7d61be76..c57281405 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs @@ -1104,7 +1104,16 @@ namespace Barotrauma { port = settingsDoc.Root.GetAttributeInt("port", port); queryPort = settingsDoc.Root.GetAttributeInt("queryport", queryPort); - maxPlayers = settingsDoc.Root.GetAttributeInt("maxplayers", maxPlayers); + + int maxPlayersElement = settingsDoc.Root.GetAttributeInt("maxplayers", maxPlayers); + if (maxPlayersElement > NetConfig.MaxPlayers) + { + DebugConsole.IsOpen = true; + DebugConsole.NewMessage($"Setting the maximum amount of players to {maxPlayersElement} failed due to exceeding the limit of {NetConfig.MaxPlayers} players per server. Using the maximum of {NetConfig.MaxPlayers} instead.", Color.Red); + maxPlayersElement = NetConfig.MaxPlayers; + } + + maxPlayers = maxPlayersElement; karmaEnabled = settingsDoc.Root.GetAttributeBool("karmaenabled", true); selectedKarmaPreset = settingsDoc.Root.GetAttributeString("karmapreset", "default"); string playStyleStr = settingsDoc.Root.GetAttributeString("playstyle", "Casual"); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs index e8b13c0a7..c5eb94a9d 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs @@ -4375,9 +4375,18 @@ namespace Barotrauma Submarine.DrawFront(spriteBatch, editing: true, e => ShowThalamus || !(e.prefab?.Category.HasFlag(MapEntityCategory.Thalamus) ?? false)); if (!WiringMode && !IsMouseOnEditorGUI()) { - MapEntityPrefab.Selected?.DrawPlacing(spriteBatch, cam); + MapEntityPrefab.Selected?.DrawPlacing(spriteBatch, cam); MapEntity.DrawSelecting(spriteBatch, cam); } + if (dummyCharacter != null && WiringMode) + { + for (int i = 0; i < dummyCharacter.SelectedItems.Length; i++) + { + if (dummyCharacter.SelectedItems[i] == null) { continue; } + if (i > 0 && dummyCharacter.SelectedItems[0] == dummyCharacter.SelectedItems[i]) { continue; } + dummyCharacter.SelectedItems[i].Draw(spriteBatch, editing: false, back: true); + } + } spriteBatch.End(); if (GameMain.LightManager.LightingEnabled && lightingEnabled) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundPlayer.cs b/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundPlayer.cs index ec53aaf92..495e35324 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundPlayer.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundPlayer.cs @@ -74,7 +74,7 @@ namespace Barotrauma //ambience private static Sound waterAmbienceIn, waterAmbienceOut, waterAmbienceMoving; - private static SoundChannel[] waterAmbienceChannels = new SoundChannel[3]; + private static readonly SoundChannel[] waterAmbienceChannels = new SoundChannel[3]; private static float ambientSoundTimer; private static Vector2 ambientSoundInterval = new Vector2(20.0f, 40.0f); //x = min, y = max @@ -85,6 +85,7 @@ namespace Barotrauma private static Vector2 hullSoundInterval = new Vector2(45.0f, 90.0f); //x = min, y = max //misc + private static float[] targetFlowLeft, targetFlowRight; public static List FlowSounds = new List(); public static List SplashSounds = new List(); private static SoundChannel[] flowSoundChannels; @@ -109,6 +110,8 @@ namespace Barotrauma private static Dictionary> guiSounds; + private static bool firstTimeInMainMenu = true; + private static Sound startUpSound; public static bool Initialized; @@ -335,11 +338,14 @@ namespace Barotrauma else { miscSoundList.Add(new KeyValuePair(g.Key, s)); } })); - + flowSoundChannels?.ForEach(ch => ch?.Dispose()); flowSoundChannels = new SoundChannel[FlowSounds.Count]; flowVolumeLeft = new float[FlowSounds.Count]; flowVolumeRight = new float[FlowSounds.Count]; + targetFlowLeft = new float[FlowSounds.Count]; + targetFlowRight = new float[FlowSounds.Count]; + fireSoundChannels?.ForEach(ch => ch?.Dispose()); fireSoundChannels = new SoundChannel[fireSizes]; fireVolumeLeft = new float[fireSizes]; fireVolumeRight = new float[fireSizes]; @@ -494,9 +500,6 @@ namespace Barotrauma { if (FlowSounds.Count == 0) { return; } - float[] targetFlowLeft = new float[FlowSounds.Count]; - float[] targetFlowRight = new float[FlowSounds.Count]; - Vector2 listenerPos = new Vector2(GameMain.SoundManager.ListenerPosition.X, GameMain.SoundManager.ListenerPosition.Y); foreach (Gap gap in Gap.GapList) { @@ -931,7 +934,9 @@ namespace Barotrauma return "editor"; } - if (Screen.Selected != GameMain.GameScreen) { return "menu"; } + if (Screen.Selected != GameMain.GameScreen) { return firstTimeInMainMenu ? "menu" : "default"; } + + firstTimeInMainMenu = false; if (Character.Controlled != null) @@ -973,12 +978,12 @@ namespace Barotrauma float totalArea = 0.0f; foreach (Hull hull in Hull.hullList) { - if (hull.Submarine != targetSubmarine) continue; + if (hull.Submarine != targetSubmarine) { continue; } floodedArea += hull.WaterVolume; totalArea += hull.Volume; } - if (totalArea > 0.0f && floodedArea / totalArea > 0.25f) return "flooded"; + if (totalArea > 0.0f && floodedArea / totalArea > 0.25f) { return "flooded"; } } float enemyDistThreshold = 5000.0f; @@ -991,7 +996,7 @@ namespace Barotrauma foreach (Character character in Character.CharacterList) { if (character.IsDead || !character.Enabled) continue; - if (!(character.AIController is EnemyAIController enemyAI) || (!enemyAI.AttackHumans && !enemyAI.AttackRooms)) continue; + if (!(character.AIController is EnemyAIController enemyAI) || (!enemyAI.AttackHumans && !enemyAI.AttackRooms)) { continue; } if (targetSubmarine != null) { diff --git a/Barotrauma/BarotraumaClient/LinuxClient.csproj b/Barotrauma/BarotraumaClient/LinuxClient.csproj index 4c0350701..f7dca23c3 100644 --- a/Barotrauma/BarotraumaClient/LinuxClient.csproj +++ b/Barotrauma/BarotraumaClient/LinuxClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.10.601.0 + 0.10.6.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/MacClient.csproj b/Barotrauma/BarotraumaClient/MacClient.csproj index 8e5630bf8..6751608a6 100644 --- a/Barotrauma/BarotraumaClient/MacClient.csproj +++ b/Barotrauma/BarotraumaClient/MacClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.10.601.0 + 0.10.6.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/WindowsClient.csproj b/Barotrauma/BarotraumaClient/WindowsClient.csproj index 49df8bb39..ff702b021 100644 --- a/Barotrauma/BarotraumaClient/WindowsClient.csproj +++ b/Barotrauma/BarotraumaClient/WindowsClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.10.601.0 + 0.10.6.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaServer/LinuxServer.csproj b/Barotrauma/BarotraumaServer/LinuxServer.csproj index d67dcdb85..a3a8d8f9f 100644 --- a/Barotrauma/BarotraumaServer/LinuxServer.csproj +++ b/Barotrauma/BarotraumaServer/LinuxServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.10.601.0 + 0.10.6.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/MacServer.csproj b/Barotrauma/BarotraumaServer/MacServer.csproj index 606e373bf..a9341acb2 100644 --- a/Barotrauma/BarotraumaServer/MacServer.csproj +++ b/Barotrauma/BarotraumaServer/MacServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.10.601.0 + 0.10.6.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs b/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs index b3a409cfe..e80aa3d4a 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs @@ -478,7 +478,7 @@ namespace Barotrauma msg.Write(Info == null); msg.Write(entityId); msg.Write(SpeciesName); - msg.Write(seed); + msg.Write(Seed); if (Removed) { diff --git a/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs b/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs index 0b39fe5c0..3db355286 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs @@ -1119,6 +1119,12 @@ namespace Barotrauma } else { + if (maxPlayers > NetConfig.MaxPlayers) + { + NewMessage($"Setting the maximum amount of players to {maxPlayers} failed due to exceeding the limit of {NetConfig.MaxPlayers} players per server. Using the maximum of {NetConfig.MaxPlayers} instead."); + maxPlayers = NetConfig.MaxPlayers; + } + GameMain.Server.ServerSettings.MaxPlayers = maxPlayers; NewMessage("Set the maximum player count to " + maxPlayers + "."); } @@ -1132,6 +1138,12 @@ namespace Barotrauma } else { + if (maxPlayers > NetConfig.MaxPlayers) + { + GameMain.Server.SendConsoleMessage($"Setting the maximum amount of players to {maxPlayers} failed due to exceeding the limit of {NetConfig.MaxPlayers} players per server. Using the maximum of {NetConfig.MaxPlayers} instead.", client); + maxPlayers = NetConfig.MaxPlayers; + } + GameMain.Server.ServerSettings.MaxPlayers = maxPlayers; NewMessage(client.Name + " set the maximum player count to " + maxPlayers + "."); GameMain.Server.SendConsoleMessage("Set the maximum player count to " + maxPlayers + ".", client); diff --git a/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs b/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs index 0c1a0ffbb..364f1d075 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs @@ -147,6 +147,14 @@ namespace Barotrauma c.InGame && (IsOwner(c) || c.HasPermission(ClientPermissions.ManageCampaign))); } + public void LoadPets() + { + if (petsElement != null) + { + PetBehavior.LoadPets(petsElement); + } + } + protected override IEnumerable DoLevelTransition(TransitionType transitionType, LevelData newLevel, Submarine leavingSub, bool mirror, List traitorResults) { lastUpdateID++; @@ -229,6 +237,9 @@ namespace Barotrauma c.Inventory.DeleteAllItems(); } + petsElement = new XElement("pets"); + PetBehavior.SavePets(petsElement); + yield return CoroutineStatus.Running; if (leavingSub != Submarine.MainSub && !leavingSub.DockedTo.Contains(Submarine.MainSub)) @@ -768,6 +779,11 @@ namespace Barotrauma CargoManager?.SavePurchasedItems(modeElement); UpgradeManager?.SavePendingUpgrades(modeElement, UpgradeManager?.PendingUpgrades); + if (petsElement != null) + { + modeElement.Add(petsElement); + } + // save bots CrewManager.SaveMultiplayer(modeElement); diff --git a/Barotrauma/BarotraumaServer/ServerSource/Map/Hull.cs b/Barotrauma/BarotraumaServer/ServerSource/Map/Hull.cs index 391d64fdb..043e85256 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Map/Hull.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Map/Hull.cs @@ -128,28 +128,8 @@ namespace Barotrauma //used when clients use the water/fire console commands or section / decal updates are received public void ServerRead(ClientNetObject type, IReadMessage msg, Client c) { - bool hasExtraData = msg.ReadBoolean(); - if (hasExtraData) - { - int sectorToUpdate = msg.ReadRangedInteger(0, BackgroundSections.Count - 1); - int start = sectorToUpdate * BackgroundSectionsPerNetworkEvent; - int end = Math.Min((sectorToUpdate + 1) * BackgroundSectionsPerNetworkEvent, BackgroundSections.Count - 1); - for (int i = start; i < end; i++) - { - float colorStrength = msg.ReadRangedSingle(0.0f, 1.0f, 8); - Color color = new Color(msg.ReadUInt32()); - - //TODO: verify the client is close enough to this hull to paint it, that the sprayer is functional and that the color matches - if (c.Character != null && c.Character.AllowInput && c.Character.SelectedItems.Any(it => it?.GetComponent() != null)) - { - BackgroundSections[i].SetColorStrength(colorStrength); - BackgroundSections[i].SetColor(color); - } - } - //add to pending updates to notify other clients as well - pendingSectionUpdates.Add(sectorToUpdate); - } - else + int messageType = msg.ReadRangedInteger(0, 2); + if (messageType == 0) { float newWaterVolume = msg.ReadRangedSingle(0.0f, 1.5f, 8) * Volume; @@ -205,6 +185,37 @@ namespace Barotrauma FireSources.RemoveAt(i); } } + } + else if (messageType == 1) + { + byte decalIndex = msg.ReadByte(); + float decalAlpha = msg.ReadRangedSingle(0.0f, 1.0f, 255); + if (decalIndex < 0 || decalIndex >= decals.Count) { return; } + if (c.Character != null && c.Character.AllowInput && c.Character.SelectedItems.Any(it => it?.GetComponent() != null)) + { + decals[decalIndex].BaseAlpha = decalAlpha; + } + decalUpdatePending = true; + } + else + { + int sectorToUpdate = msg.ReadRangedInteger(0, BackgroundSections.Count - 1); + int start = sectorToUpdate * BackgroundSectionsPerNetworkEvent; + int end = Math.Min((sectorToUpdate + 1) * BackgroundSectionsPerNetworkEvent, BackgroundSections.Count - 1); + for (int i = start; i < end; i++) + { + float colorStrength = msg.ReadRangedSingle(0.0f, 1.0f, 8); + Color color = new Color(msg.ReadUInt32()); + + //TODO: verify the client is close enough to this hull to paint it, that the sprayer is functional and that the color matches + if (c.Character != null && c.Character.AllowInput && c.Character.SelectedItems.Any(it => it?.GetComponent() != null)) + { + BackgroundSections[i].SetColorStrength(colorStrength); + BackgroundSections[i].SetColor(color); + } + } + //add to pending updates to notify other clients as well + pendingSectionUpdates.Add(sectorToUpdate); } } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs index 03e37611a..b607fd2b9 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs @@ -1574,8 +1574,9 @@ namespace Barotrauma.Networking { //if docked to a sub with a smaller ID, don't send an update // (= update is only sent for the docked sub that has the smallest ID, doesn't matter if it's the main sub or a shuttle) - if (sub.Info.IsOutpost || sub.DockedTo.Any(s => s.ID < sub.ID)) continue; - if (!c.PendingPositionUpdates.Contains(sub)) c.PendingPositionUpdates.Enqueue(sub); + if (sub.Info.IsOutpost || sub.DockedTo.Any(s => s.ID < sub.ID)) { continue; } + if (sub.PhysicsBody == null || sub.PhysicsBody.BodyType == FarseerPhysics.BodyType.Static) { continue; } + if (!c.PendingPositionUpdates.Contains(sub)) { c.PendingPositionUpdates.Enqueue(sub); } } foreach (Item item in Item.ItemList) @@ -2291,6 +2292,8 @@ namespace Barotrauma.Networking crewManager?.InitRound(); } + campaign?.LoadPets(); + foreach (Submarine sub in Submarine.MainSubs) { if (sub == null) continue; @@ -2359,7 +2362,7 @@ namespace Barotrauma.Networking bool missionAllowRespawn = campaign == null && (missionMode?.Mission == null || missionMode.Mission.AllowRespawn); bool outpostAllowRespawn = campaign != null && campaign.NextLevel?.Type == LevelData.LevelType.Outpost; - msg.Write(missionAllowRespawn || outpostAllowRespawn); + msg.Write(serverSettings.AllowRespawn && (missionAllowRespawn || outpostAllowRespawn)); msg.Write(serverSettings.AllowDisguises); msg.Write(serverSettings.AllowRewiring); msg.Write(serverSettings.AllowRagdollButton); diff --git a/Barotrauma/BarotraumaServer/WindowsServer.csproj b/Barotrauma/BarotraumaServer/WindowsServer.csproj index 7fded1041..18ebf43eb 100644 --- a/Barotrauma/BarotraumaServer/WindowsServer.csproj +++ b/Barotrauma/BarotraumaServer/WindowsServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.10.601.0 + 0.10.6.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.9.xml b/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.9.xml index 7777353ed..7765aec9d 100644 --- a/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.9.xml +++ b/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.9.xml @@ -59,6 +59,7 @@ + @@ -92,6 +93,7 @@ + diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AIController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AIController.cs index 2af7dcc39..6eb1cf197 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AIController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AIController.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; namespace Barotrauma { - public enum AIState { Idle, Attack, Escape, Eat, Flee, Avoid, Aggressive, PassiveAggressive, Protect, Observe, Freeze } + public enum AIState { Idle, Attack, Escape, Eat, Flee, Avoid, Aggressive, PassiveAggressive, Protect, Observe, Freeze, Follow } abstract partial class AIController : ISteerable { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs index 2c9d2d17b..796ff7163 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs @@ -48,13 +48,11 @@ namespace Barotrauma private float attackLimbResetTimer; private bool IsCoolDownRunning => AttackingLimb != null && AttackingLimb.attack.CoolDownTimer > 0; - public float CombatStrength => Character.Params.AI.CombatStrength; - private float Sight => Character.Params.AI.Sight; - private float Hearing => Character.Params.AI.Hearing; - private float FleeHealthThreshold => Character.Params.AI.FleeHealthThreshold; - private float AggressionGreed => Character.Params.AI.AggressionGreed; - private float AggressionHurt => Character.Params.AI.AggressionHurt; - private bool AggressiveBoarding => Character.Params.AI.AggressiveBoarding; + public float CombatStrength => AIParams.CombatStrength; + private float Sight => AIParams.Sight; + private float Hearing => AIParams.Hearing; + private float FleeHealthThreshold => AIParams.FleeHealthThreshold; + private bool AggressiveBoarding => AIParams.AggressiveBoarding; private FishAnimController FishAnimController => Character.AnimController as FishAnimController; @@ -90,7 +88,6 @@ namespace Barotrauma private readonly float priorityFearIncreasement = 2; private readonly float memoryFadeTime = 0.5f; - private readonly float avoidTime = 3; private float avoidTimer; private float observeTimer; @@ -241,6 +238,10 @@ namespace Barotrauma { targetingTag = "dead"; } + else if (PetBehavior != null && aiTarget.Entity == PetBehavior.Owner) + { + targetingTag = "owner"; + } else if (AIParams.TryGetTarget(targetCharacter.SpeciesName, out CharacterParams.TargetParams tP)) { targetingTag = tP.Tag; @@ -384,7 +385,7 @@ namespace Barotrauma { updateTargetsTimer -= deltaTime; } - else + else if (avoidTimer <= 0) { CharacterParams.TargetParams targetingParams = null; UpdateTargets(Character, out targetingParams); @@ -393,11 +394,7 @@ namespace Barotrauma UpdateWallTarget(); } updateTargetsTimer = updateTargetsInterval * Rand.Range(0.75f, 1.25f); - if (avoidTimer > 0) - { - State = AIState.Escape; - } - else if (SelectedAiTarget == null) + if (SelectedAiTarget == null) { State = AIState.Idle; } @@ -502,17 +499,21 @@ namespace Barotrauma } break; case AIState.Protect: + case AIState.Follow: if (SelectedAiTarget == null || SelectedAiTarget.Entity == null || SelectedAiTarget.Entity.Removed) { State = AIState.Idle; return; } - if (SelectedAiTarget.Entity is Character targetCharacter && targetCharacter.LastAttacker is Character attacker && !attacker.Removed && !attacker.IsDead) + if (State == AIState.Protect) { - // Attack the character that attacked the target we are protecting - ChangeTargetState(attacker, AIState.Attack, selectedTargetingParams.Priority * 2); - SelectTarget(attacker.AiTarget); - return; + if (SelectedAiTarget.Entity is Character targetCharacter && targetCharacter.LastAttacker is Character attacker && !attacker.Removed && !attacker.IsDead) + { + // Attack the character that attacked the target we are protecting + ChangeTargetState(attacker, AIState.Attack, selectedTargetingParams.Priority * 2); + SelectTarget(attacker.AiTarget); + return; + } } float sqrDist = Vector2.DistanceSquared(WorldPosition, SelectedAiTarget.WorldPosition); float reactDist = selectedTargetingParams != null && selectedTargetingParams.ReactDistance > 0 ? selectedTargetingParams.ReactDistance : GetPerceivingRange(SelectedAiTarget); @@ -568,6 +569,7 @@ namespace Barotrauma } else { + // TODO: doesn't work right here FaceTarget(SelectedAiTarget.Entity); } observeTimer -= deltaTime; @@ -592,7 +594,6 @@ namespace Barotrauma if (!Character.AnimController.SimplePhysicsEnabled) { LatchOntoAI?.Update(this, deltaTime); - PetBehavior?.Update(deltaTime); } IsSteeringThroughGap = false; if (SwarmBehavior != null) @@ -1454,7 +1455,8 @@ namespace Barotrauma bool isFriendly = IsFriendly(Character, attacker); if (wasLatched) { - avoidTimer = avoidTime * Rand.Range(0.75f, 1.25f); + State = AIState.Escape; + avoidTimer = AIParams.AvoidTime * Rand.Range(0.75f, 1.25f); if (!isFriendly) { SelectTarget(attacker.AiTarget); @@ -1527,7 +1529,7 @@ namespace Barotrauma } AITargetMemory targetMemory = GetTargetMemory(attacker.AiTarget, true); - targetMemory.Priority += GetRelativeDamage(attackResult.Damage, Character.Vitality) * AggressionHurt; + targetMemory.Priority += GetRelativeDamage(attackResult.Damage, Character.Vitality) * AIParams.AggressionHurt; // Only allow to react once. Otherwise would attack the target with only a fraction of a cooldown bool retaliate = !isFriendly && SelectedAiTarget != attacker.AiTarget && attacker.Submarine == Character.Submarine; @@ -1552,12 +1554,14 @@ namespace Barotrauma } else if (avoidGunFire) { - avoidTimer = avoidTime * Rand.Range(0.75f, 1.25f); + State = AIState.Escape; + avoidTimer = AIParams.AvoidTime * Rand.Range(0.75f, 1.25f); SelectTarget(attacker.AiTarget); } if (Character.HealthPercentage <= FleeHealthThreshold) { - avoidTimer = Rand.Range(15, 30); + State = AIState.Flee; + avoidTimer = AIParams.MinFleeTime * Rand.Range(0.75f, 1.25f); SelectTarget(attacker.AiTarget); } } @@ -1587,7 +1591,7 @@ namespace Barotrauma if (damageTarget.Health > 0) { // Managed to hit a living/non-destroyed target. Increase the priority more if the target is low in health -> dies easily/soon - selectedTargetMemory.Priority += GetRelativeDamage(attackResult.Damage, damageTarget.Health) * AggressionGreed; + selectedTargetMemory.Priority += GetRelativeDamage(attackResult.Damage, damageTarget.Health) * AIParams.AggressionGreed; } else { @@ -1654,10 +1658,13 @@ namespace Barotrauma if (!item.Removed && item.body != null) { float itemBodyExtent = item.body.GetMaxExtent() * 2; - if (limbDiff.LengthSquared() < itemBodyExtent * itemBodyExtent) + if (Math.Abs(limbDiff.X) < itemBodyExtent && + Math.Abs(limbDiff.Y) < Character.AnimController.Collider.GetMaxExtent() + Character.AnimController.ColliderHeightFromFloor) { + item.body.LinearVelocity *= 0.9f; + item.body.LinearVelocity -= limbDiff * 0.25f; + item.AddDamage(Character, item.WorldPosition, new Attack(0.0f, 0.0f, 0.0f, 0.0f, 0.1f), deltaTime); - item.body.ApplyForce(-limbDiff * item.body.Mass); if (item.Condition <= 0.0f) { @@ -1697,15 +1704,40 @@ namespace Barotrauma State = AIState.Idle; return; } - Vector2 dir = Vector2.Normalize(SelectedAiTarget.Entity.WorldPosition - Character.WorldPosition); - if (!MathUtils.IsValid(dir)) + if (Character.CurrentHull != null && PathSteering != null) { - return; + // Inside + Character targetCharacter = SelectedAiTarget.Entity as Character; + if ((Character.AnimController.InWater || !Character.AnimController.CanWalk) && + (targetCharacter != null && VisibleHulls.Contains(targetCharacter.CurrentHull) || Character.CanSeeTarget(SelectedAiTarget.Entity))) + { + // Steer towards the target if in the same room and swimming + Vector2 dir = Vector2.Normalize(SelectedAiTarget.Entity.WorldPosition - Character.WorldPosition); + if (MathUtils.IsValid(dir)) + { + SteeringManager.SteeringManual(deltaTime, dir); + } + } + else + { + // Use path finding + SteeringManager.SteeringSeek(Character.GetRelativeSimPosition(SelectedAiTarget.Entity), 2); + if (!PathSteering.IsPathDirty && PathSteering.CurrentPath.Unreachable) + { + // Can't reach + State = AIState.Idle; + return; + } + } } - SteeringManager.SteeringSeek(Character.GetRelativeSimPosition(SelectedAiTarget.Entity), 5); - if (Character.AnimController.InWater) + else { - SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: avoidLookAheadDistance, weight: 15); + // Outside + SteeringManager.SteeringSeek(Character.GetRelativeSimPosition(SelectedAiTarget.Entity), 5); + if (Character.AnimController.InWater) + { + SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: avoidLookAheadDistance, weight: 15); + } } } @@ -1748,6 +1780,10 @@ namespace Barotrauma { targetingTag = "dead"; } + else if (PetBehavior != null && aiTarget.Entity == PetBehavior.Owner) + { + targetingTag = "owner"; + } else if (AIParams.TryGetTarget(targetCharacter.SpeciesName, out CharacterParams.TargetParams tP)) { targetingTag = tP.Tag; @@ -1992,9 +2028,16 @@ namespace Barotrauma if (targetingTag == null) { continue; } var targetParams = GetTargetParams(targetingTag); if (targetParams == null) { continue; } - if (targetCharacter != null && targetCharacter.Submarine != Character.Submarine && targetParams.State == AIState.Observe) + if (targetParams.State == AIState.Observe || targetParams.State == AIState.Eat) + { + if (targetCharacter != null && targetCharacter.Submarine != Character.Submarine) + { + // Don't allow to target characters that are inside a different submarine / outside when we are inside. + continue; + } + } + if (aiTarget.Entity is Item targetItem && targetParams.IgnoreContained && targetItem.ParentInventory != null) { - // Don't allow to observe characters that are inside a different submarine / outside when we are inside. continue; } valueModifier *= targetParams.Priority; @@ -2066,6 +2109,17 @@ namespace Barotrauma } if (targetCharacter != null) { + if (Character.CurrentHull != null && targetCharacter.CurrentHull != Character.CurrentHull) + { + if (targetParams.State == AIState.Follow || targetParams.State == AIState.Protect || targetParams.State == AIState.Observe) + { + // Ignore targets that cannot see + if (!VisibleHulls.Contains(targetCharacter.CurrentHull)) + { + continue; + } + } + } if (targetCharacter.Submarine != Character.Submarine) { if (targetCharacter.Submarine != null) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs index 5affd17de..541f78e66 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs @@ -1111,7 +1111,10 @@ namespace Barotrauma public bool TakeItem(Item item, Inventory targetInventory, bool equip, bool dropOtherIfCannotMove = true, bool allowSwapping = false, bool storeUnequipped = false) { var pickable = item.GetComponent(); - if (pickable == null) { return false; } + if (item.ParentInventory is ItemInventory itemInventory) + { + if (!itemInventory.Container.HasRequiredItems(Character, addMessage: false)) { return false; } + } if (equip) { int targetSlot = -1; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/IndoorsSteeringManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/IndoorsSteeringManager.cs index cde7850d1..26cd8a304 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/IndoorsSteeringManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/IndoorsSteeringManager.cs @@ -190,6 +190,7 @@ namespace Barotrauma } } pathFinder.InsideSubmarine = character.Submarine != null; + pathFinder.ApplyPenaltyToOutsideNodes = character.PressureProtection <= 0; var newPath = pathFinder.FindPath(currentPos, target, character.Submarine, "(Character: " + character.Name + ")", startNodeFilter, endNodeFilter, nodeFilter, checkVisibility: checkVisibility); bool useNewPath = needsNewPath || currentPath == null || currentPath.CurrentNode == null || findPathTimer < -1; if (!useNewPath && currentPath != null && currentPath.CurrentNode != null && newPath.Nodes.Any() && !newPath.Unreachable) @@ -343,7 +344,7 @@ namespace Barotrauma } return diff; } - else if (!canClimb || character.AnimController.InWater) + else if (character.AnimController.InWater) { // If the character is underwater, we don't need the ladders anymore if (character.IsClimbing && isDiving) @@ -370,7 +371,7 @@ namespace Barotrauma } } } - else if (!IsNextLadderSameAsCurrent) + else if (!canClimb || !IsNextLadderSameAsCurrent) { // Walking horizontally Vector2 colliderBottom = character.AnimController.GetColliderBottom(); @@ -378,6 +379,8 @@ namespace Barotrauma Vector2 velocity = collider.LinearVelocity; // If the character is smaller than this, it would fail to use the waypoint nodes because they are always too high. float minHeight = 1; + // If the character is very thin, without a min value, it would often fail to reach the waypoints, because the horizontal distance is too small. + float minWidth = 0.17f; // Cannot use the head position, because not all characters have head or it can be below the total height of the character float characterHeight = Math.Max(colliderSize.Y + character.AnimController.ColliderHeightFromFloor, minHeight); float horizontalDistance = Math.Abs(collider.SimPosition.X - currentPath.CurrentNode.SimPosition.X); @@ -386,7 +389,7 @@ namespace Barotrauma var door = currentPath.CurrentNode.ConnectedDoor; bool blockedByDoor = door != null && !door.IsOpen && !door.IsBroken; float margin = MathHelper.Lerp(1, 10, MathHelper.Clamp(Math.Abs(velocity.X) / 10, 0, 1)); - float targetDistance = collider.radius * margin; + float targetDistance = Math.Max(collider.radius * margin, minWidth); if (horizontalDistance < targetDistance && isAboveFeet && isNotTooHigh && !blockedByDoor) { currentPath.SkipToNextNode(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindSafety.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindSafety.cs index 59fc6ad7d..1d98fcf52 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindSafety.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindSafety.cs @@ -204,11 +204,15 @@ namespace Barotrauma { // Don't ignore any hulls if outside, because apparently it happens that we can't find a path, in which case we just want to try again. // If we ignore the hull, it might be the only airlock in the target sub, which ignores the whole sub. - if (currentHull != null && goToObjective != null) + // If the target hull is inside a submarine that is not our main sub, just ignore it normally when it cannot be reached. This happens with outposts. + if (goToObjective != null) { if (goToObjective.Target is Hull hull) { - HumanAIController.UnreachableHulls.Add(hull); + if (currentHull != null || !Submarine.MainSubs.Contains(hull.Submarine)) + { + HumanAIController.UnreachableHulls.Add(hull); + } } } RemoveSubObjective(ref goToObjective); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGetItem.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGetItem.cs index ecc1ae899..4eda798d4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGetItem.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGetItem.cs @@ -257,6 +257,10 @@ namespace Barotrauma // Don't allow going into another sub, unless it's connected and of the same team and type. if (!character.Submarine.IsEntityFoundOnThisSub(item, includingConnectedSubs: true)) { continue; } if (character.IsItemTakenBySomeoneElse(item)) { continue; } + if (item.ParentInventory is ItemInventory itemInventory) + { + if (!itemInventory.Container.HasRequiredItems(character, addMessage: false)) { continue; } + } float itemPriority = 1; if (GetItemPriority != null) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGoTo.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGoTo.cs index 96a074546..25a2b3634 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGoTo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGoTo.cs @@ -342,7 +342,7 @@ namespace Barotrauma SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(Target.WorldPosition - character.WorldPosition)); if (character.AnimController.InWater) { - SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: 5, weight: 15); + SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: 5, weight: 2); } } } @@ -360,7 +360,10 @@ namespace Barotrauma else { SteeringManager.SteeringSeek(character.GetRelativeSimPosition(Target), 10); - SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: 5, weight: 15); + if (character.AnimController.InWater) + { + SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: 5, weight: 15); + } } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PathFinder.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PathFinder.cs index 920717e41..088906058 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PathFinder.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PathFinder.cs @@ -94,6 +94,7 @@ namespace Barotrauma private readonly List nodes; public bool InsideSubmarine { get; set; } + public bool ApplyPenaltyToOutsideNodes { get; set; } public PathFinder(List wayPoints, bool indoorsSteering = false) { @@ -178,7 +179,7 @@ namespace Barotrauma node.TempDistance = xDiff + (InsideSubmarine ? yDiff * 10.0f : yDiff); //higher cost for vertical movement when inside the sub //much higher cost to waypoints that are outside - if (node.Waypoint.CurrentHull == null && InsideSubmarine) { node.TempDistance *= 10.0f; } + if (node.Waypoint.CurrentHull == null && ApplyPenaltyToOutsideNodes) { node.TempDistance *= 10.0f; } //prefer nodes that are closer to the end position node.TempDistance += (Math.Abs(end.X - node.TempPosition.X) + Math.Abs(end.Y - node.TempPosition.Y)) / 100.0f; @@ -231,8 +232,11 @@ namespace Barotrauma node.TempDistance = Vector2.DistanceSquared(end, node.TempPosition); if (InsideSubmarine) { - //much higher cost to waypoints that are outside - if (node.Waypoint.CurrentHull == null) { node.TempDistance *= 10.0f; } + if (ApplyPenaltyToOutsideNodes) + { + //much higher cost to waypoints that are outside + if (node.Waypoint.CurrentHull == null) { node.TempDistance *= 10.0f; } + } //avoid stopping at a doorway if (node.Waypoint.ConnectedDoor != null) { node.TempDistance *= 10.0f; } //avoid stopping at a ladder diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PetBehavior.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PetBehavior.cs index f0385f2e2..08c483e53 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PetBehavior.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PetBehavior.cs @@ -1,3 +1,4 @@ +using Barotrauma.Extensions; using Barotrauma.Items.Components; using Microsoft.Xna.Framework; using System.Collections.Generic; @@ -9,6 +10,14 @@ namespace Barotrauma { class PetBehavior { + public enum StatusIndicatorType + { + None, + Happy, + Sad, + Hungry + } + public float Hunger { get; set; } = 50.0f; public float Happiness { get; set; } = 50.0f; @@ -21,6 +30,7 @@ namespace Barotrauma public float PlayForce { get; set; } public float PlayTimer { get; set; } + private float? unstunY { get; set; } public EnemyAIController AiController { get; private set; } = null; @@ -126,6 +136,7 @@ namespace Barotrauma public float Hunger; public float Happiness; public float Priority; + public bool IgnoreContained; public CharacterParams.TargetParams TargetParams = null; } @@ -159,7 +170,11 @@ namespace Barotrauma case "eat": Food food = new Food { - Tag = subElement.GetAttributeString("tag", "") + Tag = subElement.GetAttributeString("tag", ""), + Hunger = subElement.GetAttributeFloat("hunger", -1), + Happiness = subElement.GetAttributeFloat("happiness", 1), + Priority = subElement.GetAttributeFloat("priority", 100), + IgnoreContained = subElement.GetAttributeBool("ignorecontained", true) }; string[] requiredHungerStr = subElement.GetAttributeString("requiredhunger", "0-100").Split('-'); food.HungerRange = new Vector2(0, 100); @@ -168,15 +183,20 @@ namespace Barotrauma if (float.TryParse(requiredHungerStr[0], NumberStyles.Any, CultureInfo.InvariantCulture, out float tempF)) { food.HungerRange.X = tempF; } if (float.TryParse(requiredHungerStr[1], NumberStyles.Any, CultureInfo.InvariantCulture, out tempF)) { food.HungerRange.Y = tempF; } } - food.Hunger = subElement.GetAttributeFloat("hunger", -1); - food.Happiness = subElement.GetAttributeFloat("happiness", 1); - food.Priority = subElement.GetAttributeFloat("priority", 100); foods.Add(food); break; } } } + public StatusIndicatorType GetCurrentStatusIndicatorType() + { + if (Hunger > MaxHunger * 0.5f) { return StatusIndicatorType.Hungry; } + if (Happiness > MaxHappiness * 0.75f) { return StatusIndicatorType.Happy; } + if (Happiness < MaxHappiness * 0.25f) { return StatusIndicatorType.Sad; } + return StatusIndicatorType.None; + } + public void OnEat(IEnumerable tags, float amount) { for (int i = 0; i < foods.Count; i++) @@ -203,17 +223,19 @@ namespace Barotrauma } } - public void Play() + public void Play(Character player) { if (PlayTimer > 0.0f) { return; } + if (Owner == null) { Owner = player; } PlayTimer = 5.0f; - AiController.Character.Stun = 1.0f; + AiController.Character.IsRagdolled = true; Happiness += 10.0f; if (Happiness > MaxHappiness) { Happiness = MaxHappiness; } AiController.Character.AnimController.MainLimb.body.LinearVelocity += new Vector2(0, PlayForce); + unstunY = AiController.Character.SimPosition.Y; } - public string GetName() + public string GetTagName() { if (AiController.Character.Inventory != null) { @@ -230,14 +252,16 @@ namespace Barotrauma } } - return AiController.Character.Name; + return string.Empty; } public void Update(float deltaTime) { var character = AiController.Character; if (character?.Removed ?? true || character.IsDead) { return; } - if (GameMain.NetworkMember?.IsClient ?? false) { return; } //TODO: syncing + if (GameMain.NetworkMember?.IsClient ?? false) { return; } + + if (Owner != null && (Owner.Removed || Owner.IsDead)) { Owner = null; } Hunger += HungerIncreaseRate * deltaTime; @@ -245,20 +269,45 @@ namespace Barotrauma PlayTimer -= deltaTime; - for (int i = 0; i < foods.Count; i++) + if (unstunY.HasValue) { - if (Hunger >= foods[i].HungerRange.X && Hunger <= foods[i].HungerRange.Y) + if (PlayTimer > 4.0f) { - if (foods[i].TargetParams == null && - AiController.AIParams.TryAddNewTarget(foods[i].Tag, AIState.Eat, foods[i].Priority, out CharacterParams.TargetParams targetParams)) + float extent = character.AnimController.MainLimb.body.GetMaxExtent(); + if (character.SimPosition.Y < (unstunY.Value + extent * 3.0f) && + character.AnimController.MainLimb.body.LinearVelocity.Y < 0.0f) { - foods[i].TargetParams = targetParams; + character.IsRagdolled = false; + unstunY = null; + } + else + { + character.IsRagdolled = true; } } - else if (foods[i].TargetParams != null) + else { - AiController.AIParams.RemoveTarget(foods[i].TargetParams); - foods[i].TargetParams = null; + character.IsRagdolled = false; + unstunY = null; + } + } + + for (int i = 0; i < foods.Count; i++) + { + Food food = foods[i]; + if (Hunger >= food.HungerRange.X && Hunger <= food.HungerRange.Y) + { + if (food.TargetParams == null && + AiController.AIParams.TryAddNewTarget(food.Tag, AIState.Eat, food.Priority, out CharacterParams.TargetParams targetParams)) + { + targetParams.IgnoreContained = food.IgnoreContained; + food.TargetParams = targetParams; + } + } + else if (food.TargetParams != null) + { + AiController.AIParams.RemoveTarget(food.TargetParams); + food.TargetParams = null; } } @@ -279,7 +328,8 @@ namespace Barotrauma if (character.SelectedBy != null) { - character.Stun = 1.0f; + character.IsRagdolled = true; + unstunY = character.SimPosition.Y; } for (int i = 0; i < itemsToProduce.Count; i++) @@ -287,5 +337,73 @@ namespace Barotrauma itemsToProduce[i].Update(this, deltaTime); } } + + public static void SavePets(XElement petsElement) + { + foreach (Character c in Character.CharacterList) + { + if (!c.IsPet || c.IsDead) { continue; } + if (c.Submarine?.Info.Type != SubmarineType.Player) { continue; } + + var petBehavior = (c.AIController as EnemyAIController)?.PetBehavior; + if (petBehavior == null) { continue; } + + XElement petElement = new XElement("pet", + new XAttribute("speciesname", c.SpeciesName), + new XAttribute("ownerid", petBehavior.Owner?.ID ?? Entity.NullEntityID), + new XAttribute("seed", c.Seed)); + + var petBehaviorElement = new XElement("petbehavior", + new XAttribute("hunger", petBehavior.Hunger.ToString("G", CultureInfo.InvariantCulture)), + new XAttribute("happiness", petBehavior.Happiness.ToString("G", CultureInfo.InvariantCulture))); + petElement.Add(petBehaviorElement); + + var healthElement = new XElement("health"); + c.CharacterHealth.Save(healthElement); + petElement.Add(healthElement); + + if (c.Inventory != null) + { + var inventoryElement = new XElement("inventory"); + c.SaveInventory(c.Inventory, inventoryElement); + petElement.Add(inventoryElement); + } + + petsElement.Add(petElement); + } + } + + public static void LoadPets(XElement petsElement) + { + foreach (XElement subElement in petsElement.Elements()) + { + string speciesName = subElement.GetAttributeString("speciesname", ""); + string seed = subElement.GetAttributeString("seed", "123"); + ushort ownerID = (ushort)subElement.GetAttributeInt("ownerid", 0); + Vector2 spawnPos = Vector2.Zero; + Character owner = Entity.FindEntityByID(ownerID) as Character; + if (owner != null) + { + spawnPos = owner.WorldPosition; + } + else + { + var spawnPoint = WayPoint.WayPointList.Where(wp => wp.SpawnType == SpawnType.Human && wp.Submarine?.Info.Type == SubmarineType.Player).GetRandom(); + spawnPos = spawnPoint?.WorldPosition ?? Submarine.MainSub.WorldPosition; + } + var pet = Character.Create(speciesName, spawnPos, seed); + var petBehavior = (pet.AIController as EnemyAIController)?.PetBehavior; + if (petBehavior != null) + { + petBehavior.Owner = owner; + var petBehaviorElement = subElement.Attribute("petbehavior"); + if (petBehaviorElement != null) + { + petBehavior.Hunger = petBehaviorElement.GetAttributeFloat(50.0f); + petBehavior.Happiness = petBehaviorElement.GetAttributeFloat(50.0f); + } + } + } + } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AICharacter.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AICharacter.cs index e603b5b7c..c583b6dcf 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AICharacter.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AICharacter.cs @@ -47,6 +47,10 @@ namespace Barotrauma base.Update(deltaTime, cam); if (!Enabled) { return; } + if (!IsRemotePlayer && AIController is EnemyAIController enemyAi) + { + enemyAi.PetBehavior?.Update(deltaTime); + } if (IsDead || Vitality <= 0.0f || Stun > 0.0f || IsIncapacitated) { //don't enable simple physics on dead/incapacitated characters diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/FishAnimController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/FishAnimController.cs index ad5759762..ddfa90aea 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/FishAnimController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/FishAnimController.cs @@ -429,113 +429,195 @@ namespace Barotrauma { WalkPos = MathHelper.SmoothStep(WalkPos, MathHelper.PiOver2, deltaTime * 5); mainLimb.PullJointWorldAnchorB = Collider.SimPosition; - return; - } - - Vector2 transformedMovement = reverse ? -movement : movement; - float movementAngle = MathUtils.VectorToAngle(transformedMovement) - MathHelper.PiOver2; - float mainLimbAngle = 0; - if (mainLimb.type == LimbType.Torso && TorsoAngle.HasValue) - { - mainLimbAngle = TorsoAngle.Value; - } - else if (mainLimb.type == LimbType.Head && HeadAngle.HasValue) - { - mainLimbAngle = HeadAngle.Value; - } - mainLimbAngle *= Dir; - while (mainLimb.Rotation - (movementAngle + mainLimbAngle) > MathHelper.Pi) - { - movementAngle += MathHelper.TwoPi; - } - while (mainLimb.Rotation - (movementAngle + mainLimbAngle) < -MathHelper.Pi) - { - movementAngle -= MathHelper.TwoPi; - } - - if (CurrentSwimParams.RotateTowardsMovement) - { - Collider.SmoothRotate(movementAngle, CurrentSwimParams.SteerTorque * character.SpeedMultiplier); - if (TorsoAngle.HasValue) - { - Limb torso = GetLimb(LimbType.Torso); - if (torso != null) - { - SmoothRotateWithoutWrapping(torso, movementAngle + TorsoAngle.Value * Dir, mainLimb, TorsoTorque); - } - } - if (HeadAngle.HasValue) - { - Limb head = GetLimb(LimbType.Head); - if (head != null) - { - SmoothRotateWithoutWrapping(head, movementAngle + HeadAngle.Value * Dir, mainLimb, HeadTorque); - } - } - if (TailAngle.HasValue) - { - Limb tail = GetLimb(LimbType.Tail); - if (tail != null) - { - float? mainLimbTargetAngle = null; - if (mainLimb.type == LimbType.Torso) - { - mainLimbTargetAngle = TorsoAngle; - } - else if (mainLimb.type == LimbType.Head) - { - mainLimbTargetAngle = HeadAngle; - } - float torque = TailTorque; - float maxMultiplier = CurrentSwimParams.TailTorqueMultiplier; - if (mainLimbTargetAngle.HasValue && maxMultiplier > 1) - { - float diff = Math.Abs(mainLimb.Rotation - tail.Rotation); - float offset = Math.Abs(mainLimbTargetAngle.Value - TailAngle.Value); - torque *= MathHelper.Lerp(1, maxMultiplier, MathUtils.InverseLerp(0, MathHelper.PiOver2, diff - offset)); - } - SmoothRotateWithoutWrapping(tail, movementAngle + TailAngle.Value * Dir, mainLimb, torque); - } - } } else { - movementAngle = Dir > 0 ? -MathHelper.PiOver2 : MathHelper.PiOver2; - if (reverse) + Vector2 transformedMovement = reverse ? -movement : movement; + float movementAngle = MathUtils.VectorToAngle(transformedMovement) - MathHelper.PiOver2; + float mainLimbAngle = 0; + if (mainLimb.type == LimbType.Torso && TorsoAngle.HasValue) { - movementAngle = MathUtils.WrapAngleTwoPi(movementAngle - MathHelper.Pi); + mainLimbAngle = TorsoAngle.Value; } - if (mainLimb.type == LimbType.Head && HeadAngle.HasValue) + else if (mainLimb.type == LimbType.Head && HeadAngle.HasValue) { - Collider.SmoothRotate(HeadAngle.Value * Dir, CurrentSwimParams.SteerTorque * character.SpeedMultiplier); + mainLimbAngle = HeadAngle.Value; } - else if (mainLimb.type == LimbType.Torso && TorsoAngle.HasValue) + mainLimbAngle *= Dir; + while (mainLimb.Rotation - (movementAngle + mainLimbAngle) > MathHelper.Pi) { - Collider.SmoothRotate(TorsoAngle.Value * Dir, CurrentSwimParams.SteerTorque * character.SpeedMultiplier); + movementAngle += MathHelper.TwoPi; } - if (TorsoAngle.HasValue) + while (mainLimb.Rotation - (movementAngle + mainLimbAngle) < -MathHelper.Pi) { - Limb torso = GetLimb(LimbType.Torso); - torso?.body.SmoothRotate(TorsoAngle.Value * Dir, TorsoTorque); + movementAngle -= MathHelper.TwoPi; } - if (HeadAngle.HasValue) + if (CurrentSwimParams.RotateTowardsMovement) { - Limb head = GetLimb(LimbType.Head); - head?.body.SmoothRotate(HeadAngle.Value * Dir, HeadTorque); - } - if (TailAngle.HasValue) - { - Limb tail = GetLimb(LimbType.Tail); - tail?.body.SmoothRotate(TailAngle.Value * Dir, TailTorque); - } - } + Collider.SmoothRotate(movementAngle, CurrentSwimParams.SteerTorque * character.SpeedMultiplier); + if (TorsoAngle.HasValue) + { + Limb torso = GetLimb(LimbType.Torso); + if (torso != null) + { + SmoothRotateWithoutWrapping(torso, movementAngle + TorsoAngle.Value * Dir, mainLimb, TorsoTorque); + } + } + if (HeadAngle.HasValue) + { + Limb head = GetLimb(LimbType.Head); + if (head != null) + { + SmoothRotateWithoutWrapping(head, movementAngle + HeadAngle.Value * Dir, mainLimb, HeadTorque); + } + } + if (TailAngle.HasValue) + { + bool isAngleApplied = false; + foreach (var limb in Limbs) + { + if (limb.IsSevered) { continue; } + if (limb.type != LimbType.Tail) { continue; } + if (!limb.Params.ApplyTailAngle) { continue; } + RotateTail(limb); + isAngleApplied = true; + } + if (!isAngleApplied) + { + RotateTail(GetLimb(LimbType.Tail)); + } - var waveLength = Math.Abs(CurrentSwimParams.WaveLength * RagdollParams.JointScale); - var waveAmplitude = Math.Abs(CurrentSwimParams.WaveAmplitude * character.SpeedMultiplier); - if (waveLength > 0 && waveAmplitude > 0) - { - WalkPos -= transformedMovement.Length() / Math.Abs(waveLength); - WalkPos = MathUtils.WrapAngleTwoPi(WalkPos); + void RotateTail(Limb tail) + { + if (tail == null) { return; } + float? mainLimbTargetAngle = null; + if (mainLimb.type == LimbType.Torso) + { + mainLimbTargetAngle = TorsoAngle; + } + else if (mainLimb.type == LimbType.Head) + { + mainLimbTargetAngle = HeadAngle; + } + float torque = TailTorque; + float maxMultiplier = CurrentSwimParams.TailTorqueMultiplier; + if (mainLimbTargetAngle.HasValue && maxMultiplier > 1) + { + float diff = Math.Abs(mainLimb.Rotation - tail.Rotation); + float offset = Math.Abs(mainLimbTargetAngle.Value - TailAngle.Value); + torque *= MathHelper.Lerp(1, maxMultiplier, MathUtils.InverseLerp(0, MathHelper.PiOver2, diff - offset)); + } + SmoothRotateWithoutWrapping(tail, movementAngle + TailAngle.Value * Dir, mainLimb, torque); + } + } + } + else + { + movementAngle = Dir > 0 ? -MathHelper.PiOver2 : MathHelper.PiOver2; + if (reverse) + { + movementAngle = MathUtils.WrapAngleTwoPi(movementAngle - MathHelper.Pi); + } + if (mainLimb.type == LimbType.Head && HeadAngle.HasValue) + { + Collider.SmoothRotate(HeadAngle.Value * Dir, CurrentSwimParams.SteerTorque * character.SpeedMultiplier); + } + else if (mainLimb.type == LimbType.Torso && TorsoAngle.HasValue) + { + Collider.SmoothRotate(TorsoAngle.Value * Dir, CurrentSwimParams.SteerTorque * character.SpeedMultiplier); + } + if (TorsoAngle.HasValue) + { + Limb torso = GetLimb(LimbType.Torso); + torso?.body.SmoothRotate(TorsoAngle.Value * Dir, TorsoTorque); + } + if (HeadAngle.HasValue) + { + Limb head = GetLimb(LimbType.Head); + head?.body.SmoothRotate(HeadAngle.Value * Dir, HeadTorque); + } + if (TailAngle.HasValue) + { + bool isAngleApplied = false; + foreach (var limb in Limbs) + { + if (limb.IsSevered) { continue; } + if (limb.type != LimbType.Tail) { continue; } + if (!limb.Params.ApplyTailAngle) { continue; } + RotateTail(limb); + isAngleApplied = true; + } + if (!isAngleApplied) + { + RotateTail(GetLimb(LimbType.Tail)); + } + + void RotateTail(Limb tail) + { + if (tail != null) + { + tail.body.SmoothRotate(TailAngle.Value * Dir, TailTorque); + } + } + } + } + + var waveLength = Math.Abs(CurrentSwimParams.WaveLength * RagdollParams.JointScale); + var waveAmplitude = Math.Abs(CurrentSwimParams.WaveAmplitude * character.SpeedMultiplier); + if (waveLength > 0 && waveAmplitude > 0) + { + WalkPos -= transformedMovement.Length() / Math.Abs(waveLength); + WalkPos = MathUtils.WrapAngleTwoPi(WalkPos); + } + + foreach (var limb in Limbs) + { + if (limb.IsSevered) { continue; } + switch (limb.type) + { + case LimbType.LeftFoot: + case LimbType.RightFoot: + if (CurrentSwimParams.FootAnglesInRadians.ContainsKey(limb.Params.ID)) + { + SmoothRotateWithoutWrapping(limb, movementAngle + CurrentSwimParams.FootAnglesInRadians[limb.Params.ID] * Dir, mainLimb, FootTorque); + } + break; + case LimbType.Tail: + if (waveLength > 0 && waveAmplitude > 0) + { + float waveRotation = (float)Math.Sin(WalkPos * limb.Params.SineFrequencyMultiplier); + limb.body.ApplyTorque(waveRotation * limb.Mass * waveAmplitude * limb.Params.SineAmplitudeMultiplier); + } + break; + } + } + + for (int i = 0; i < Limbs.Length; i++) + { + var limb = Limbs[i]; + if (limb.IsSevered) { continue; } + if (limb.SteerForce <= 0.0f) { continue; } + if (!Collider.PhysEnabled) { continue; } + Vector2 pullPos = limb.PullJointWorldAnchorA; + limb.body.ApplyForce(movement * limb.SteerForce * limb.Mass * Math.Max(character.SpeedMultiplier, 1), pullPos); + } + + Vector2 mainLimbDiff = mainLimb.PullJointWorldAnchorB - mainLimb.SimPosition; + if (CurrentSwimParams.UseSineMovement) + { + mainLimb.PullJointWorldAnchorB = Vector2.SmoothStep( + mainLimb.PullJointWorldAnchorB, + Collider.SimPosition, + mainLimbDiff.LengthSquared() > 10.0f ? 1.0f : (float)Math.Abs(Math.Sin(WalkPos))); + } + else + { + //mainLimb.PullJointWorldAnchorB = Collider.SimPosition; + mainLimb.PullJointWorldAnchorB = Vector2.Lerp( + mainLimb.PullJointWorldAnchorB, + Collider.SimPosition, + mainLimbDiff.LengthSquared() > 10.0f ? 1.0f : 0.5f); + } } foreach (var limb in Limbs) @@ -543,55 +625,15 @@ namespace Barotrauma if (limb.IsSevered) { continue; } if (Math.Abs(limb.Params.ConstantTorque) > 0) { - limb.body.SmoothRotate(movementAngle + MathHelper.ToRadians(limb.Params.ConstantAngle) * Dir, limb.Params.ConstantTorque, wrapAngle: true); + limb.body.SmoothRotate(MainLimb.Rotation + MathHelper.ToRadians(limb.Params.ConstantAngle) * Dir, limb.Mass * limb.Params.ConstantTorque, wrapAngle: true); } - switch (limb.type) + if (limb.Params.BlinkFrequency > 0) { - case LimbType.LeftFoot: - case LimbType.RightFoot: - if (CurrentSwimParams.FootAnglesInRadians.ContainsKey(limb.Params.ID)) - { - SmoothRotateWithoutWrapping(limb, movementAngle + CurrentSwimParams.FootAnglesInRadians[limb.Params.ID] * Dir, mainLimb, FootTorque); - } - break; - case LimbType.Tail: - if (waveLength > 0 && waveAmplitude > 0) - { - float waveRotation = (float)Math.Sin(WalkPos); - limb.body.ApplyTorque(waveRotation * limb.Mass * waveAmplitude); - } - break; + limb.Blink(deltaTime, MainLimb.Rotation); } } - for (int i = 0; i < Limbs.Length; i++) - { - var limb = Limbs[i]; - if (limb.IsSevered) { continue; } - if (limb.SteerForce <= 0.0f) { continue; } - if (!Collider.PhysEnabled) { continue; } - Vector2 pullPos = limb.PullJointWorldAnchorA; - limb.body.ApplyForce(movement * limb.SteerForce * limb.Mass * Math.Max(character.SpeedMultiplier, 1), pullPos); - } - - Vector2 mainLimbDiff = mainLimb.PullJointWorldAnchorB - mainLimb.SimPosition; - if (CurrentSwimParams.UseSineMovement) - { - mainLimb.PullJointWorldAnchorB = Vector2.SmoothStep( - mainLimb.PullJointWorldAnchorB, - Collider.SimPosition, - mainLimbDiff.LengthSquared() > 10.0f ? 1.0f : (float)Math.Abs(Math.Sin(WalkPos))); - } - else - { - //mainLimb.PullJointWorldAnchorB = Collider.SimPosition; - mainLimb.PullJointWorldAnchorB = Vector2.Lerp( - mainLimb.PullJointWorldAnchorB, - Collider.SimPosition, - mainLimbDiff.LengthSquared() > 10.0f ? 1.0f : 0.5f); - } - - floorY = Limbs[0].SimPosition.Y; + floorY = Limbs[0].SimPosition.Y; } void UpdateWalkAnim(float deltaTime) @@ -686,10 +728,26 @@ namespace Barotrauma if (TailAngle.HasValue) { - var tail = GetLimb(LimbType.Tail); - if (tail != null) + bool isAngleApplied = false; + foreach (var limb in Limbs) { - SmoothRotateWithoutWrapping(tail, movementAngle + TailAngle.Value * Dir, mainLimb, TailTorque); + if (limb.IsSevered) { continue; } + if (limb.type != LimbType.Tail) { continue; } + if (!limb.Params.ApplyTailAngle) { continue; } + RotateTail(limb); + isAngleApplied = true; + } + if (!isAngleApplied) + { + RotateTail(GetLimb(LimbType.Tail)); + } + + void RotateTail(Limb tail) + { + if (tail != null) + { + SmoothRotateWithoutWrapping(tail, movementAngle + TailAngle.Value * Dir, mainLimb, TailTorque); + } } } @@ -709,7 +767,11 @@ namespace Barotrauma if (limb.IsSevered) { continue; } if (Math.Abs(limb.Params.ConstantTorque) > 0) { - limb.body.SmoothRotate(movementAngle + MathHelper.ToRadians(limb.Params.ConstantAngle) * Dir, limb.Params.ConstantTorque, wrapAngle: true); + limb.body.SmoothRotate(MainLimb.Rotation + MathHelper.ToRadians(limb.Params.ConstantAngle) * Dir, limb.Mass * limb.Params.ConstantTorque, wrapAngle: true); + } + if (limb.Params.BlinkFrequency > 0) + { + limb.Blink(deltaTime, MainLimb.Rotation); } switch (limb.type) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs index bba66f791..c77df67a5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs @@ -145,7 +145,7 @@ namespace Barotrauma private set; } - private LimbJoint shoulder; + private LimbJoint rightShoulder, leftShoulder; private float upperLegLength = 0.0f, lowerLegLength = 0.0f; @@ -241,12 +241,13 @@ namespace Barotrauma Limb rightHand = GetLimb(LimbType.RightHand); if (rightHand == null) { return; } - shoulder = GetJointBetweenLimbs(LimbType.Torso, LimbType.RightArm); + rightShoulder = GetJointBetweenLimbs(LimbType.Torso, LimbType.RightArm); + leftShoulder = GetJointBetweenLimbs(LimbType.Torso, LimbType.LeftArm); Vector2 localAnchorShoulder = Vector2.Zero; Vector2 localAnchorElbow = Vector2.Zero; - if (shoulder != null) + if (rightShoulder != null) { - localAnchorShoulder = shoulder.LimbA.type == LimbType.RightArm ? shoulder.LocalAnchorA : shoulder.LocalAnchorB; + localAnchorShoulder = rightShoulder.LimbA.type == LimbType.RightArm ? rightShoulder.LocalAnchorA : rightShoulder.LocalAnchorB; } LimbJoint rightElbow = rightForearm == null ? GetJointBetweenLimbs(LimbType.RightArm, LimbType.RightHand) : @@ -1612,7 +1613,7 @@ namespace Barotrauma pullLimb.PullJointMaxForce = 5000.0f; targetMovement *= MathHelper.Clamp(Mass / target.Mass, 0.5f, 1.0f); - Vector2 shoulderPos = shoulder.WorldAnchorA; + Vector2 shoulderPos = rightShoulder.WorldAnchorA; Vector2 dragDir = inWater ? Vector2.Normalize(targetLimb.SimPosition - shoulderPos) : Vector2.UnitY; targetAnchor = shoulderPos - dragDir * ConvertUnits.ToSimUnits(upperArmLength + forearmLength); @@ -1757,10 +1758,10 @@ namespace Barotrauma } else { - itemAngle = (torso.body.Rotation + holdAngle * Dir); + itemAngle = torso.body.Rotation + holdAngle * Dir; } - Vector2 transformedHoldPos = shoulder.WorldAnchorA; + Vector2 transformedHoldPos = rightShoulder.WorldAnchorA; if (itemPos == Vector2.Zero || isClimbing || usingController) { if (character.SelectedItems[0] == item) @@ -1781,15 +1782,17 @@ namespace Barotrauma if (character.SelectedItems[0] == item) { if (rightHand == null || rightHand.IsSevered) { return; } + transformedHoldPos = rightShoulder.WorldAnchorA; rightHand.Disabled = true; } if (character.SelectedItems[1] == item) { if (leftHand == null || leftHand.IsSevered) { return; } + transformedHoldPos = leftShoulder.WorldAnchorA; leftHand.Disabled = true; } - itemPos.X = itemPos.X * Dir; + itemPos.X *= Dir; transformedHoldPos += Vector2.Transform(itemPos, Matrix.CreateRotationZ(itemAngle)); } @@ -1858,18 +1861,21 @@ namespace Barotrauma private void HandIK(Limb hand, Vector2 pos, float force = 1.0f) { - if (shoulder == null) { return; } - Vector2 shoulderPos = shoulder.WorldAnchorA; + Vector2 shoulderPos; Limb arm, forearm; if (hand.type == LimbType.LeftHand) { + if (leftShoulder == null) { return; } + shoulderPos = leftShoulder.WorldAnchorA; arm = GetLimb(LimbType.LeftArm); forearm = GetLimb(LimbType.LeftForearm); LeftHandIKPos = pos; } else { + if (rightShoulder == null) { return; } + shoulderPos = rightShoulder.WorldAnchorA; arm = GetLimb(LimbType.RightArm); forearm = GetLimb(LimbType.RightForearm); RightHandIKPos = pos; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs index d0db174f1..c4855b08a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs @@ -216,16 +216,18 @@ namespace Barotrauma get { Limb mainLimb = GetLimb(RagdollParams.MainLimb); - if (mainLimb == null) + if (!IsValid(mainLimb)) { Limb torso = GetLimb(LimbType.Torso); Limb head = GetLimb(LimbType.Head); mainLimb = torso ?? head; - if (mainLimb == null) + if (!IsValid(mainLimb)) { - mainLimb = Limbs.FirstOrDefault(l => !l.IsSevered && !l.ignoreCollisions); + mainLimb = Limbs.FirstOrDefault(l => IsValid(l)); } } + + bool IsValid(Limb limb) => limb != null && !limb.IsSevered && !limb.ignoreCollisions; return mainLimb; } } @@ -753,7 +755,6 @@ namespace Barotrauma foreach (Limb limb in Limbs) { if (connectedLimbs.Contains(limb)) { continue; } - if (!character.IsDead && !limb.CanBeSeveredAlive) { continue; } limb.IsSevered = true; if (limb.type == LimbType.RightHand) { @@ -765,7 +766,6 @@ namespace Barotrauma } } - if (!string.IsNullOrEmpty(character.BloodDecalName)) { character.CurrentHull?.AddDecal(character.BloodDecalName, diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs index 15d0909d4..7ecb98a09 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs @@ -131,7 +131,7 @@ namespace Barotrauma protected float oxygenAvailable; //seed used to generate this character - private readonly string seed; + public readonly string Seed; protected Item focusedItem; private Character selectedCharacter, selectedBy; public Character LastAttacker; @@ -273,7 +273,8 @@ namespace Barotrauma { if (IsPet) { - return (AIController as EnemyAIController).PetBehavior.GetName(); + string petName = (AIController as EnemyAIController).PetBehavior.GetTagName(); + if (!string.IsNullOrEmpty(petName)) { return petName; } } if (info != null && !string.IsNullOrWhiteSpace(info.Name)) { return info.Name; } @@ -827,7 +828,7 @@ namespace Barotrauma { prefab = CharacterPrefab.FindBySpeciesName(speciesName); - this.seed = seed; + this.Seed = seed; MTRandom random = new MTRandom(ToolBox.StringToInt(seed)); selectedItems = new Item[2]; @@ -2178,7 +2179,7 @@ namespace Barotrauma } else if (FocusedCharacter != null && !FocusedCharacter.IsIncapacitated && IsKeyHit(InputType.Use) && FocusedCharacter.IsPet && CanInteract) { - (FocusedCharacter.AIController as EnemyAIController).PetBehavior.Play(); + (FocusedCharacter.AIController as EnemyAIController).PetBehavior.Play(this); } else if (FocusedCharacter != null && IsKeyHit(InputType.Health) && FocusedCharacter.CharacterHealth.UseHealthWindow && CanInteract && CanInteractWith(FocusedCharacter, 160f, false)) { @@ -2918,7 +2919,7 @@ namespace Barotrauma } #endif // Don't allow beheading for monster attacks, because it happens too frequently (crawlers/tigerthreshers etc attacking each other -> they will most often target to the head) - TrySeverLimbJoints(limbHit, attack.SeverLimbsProbability, attackResult.Damage, allowBeheading: AIController == null || AIController is HumanAIController); + TrySeverLimbJoints(limbHit, attack.SeverLimbsProbability, attackResult.Damage, allowBeheading: attacker.IsHuman || attacker.IsPlayer); return attackResult; } @@ -2944,7 +2945,8 @@ namespace Barotrauma foreach (LimbJoint joint in AnimController.LimbJoints) { if (!joint.CanBeSevered) { continue; } - if (joint.LimbA != targetLimb && joint.LimbB != targetLimb) { continue; } + // Limb A is where we usually create the joints from. Let's not allow severing when the "parent" limb is hit, or the head can pop off when we hit the torso, for example. + if (joint.LimbB != targetLimb) { continue; } float probability = severLimbsProbability; if (!IsDead) { @@ -3225,6 +3227,11 @@ namespace Barotrauma return; } + if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer) + { + GameMain.NetworkMember.CreateEntityEvent(this, new object[] { NetEntityEvent.Type.Status }); + } + IsDead = true; ApplyStatusEffects(ActionType.OnDeath, 1.0f); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Limb.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Limb.cs index a1f0eb034..71ef86a5b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Limb.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Limb.cs @@ -768,6 +768,18 @@ namespace Barotrauma { attack.UpdateCoolDown(deltaTime); } + + if (Params.BlinkFrequency > 0) + { + if (blinkTimer > -TotalBlinkDurationOut) + { + blinkTimer -= deltaTime; + } + else + { + blinkTimer = Params.BlinkFrequency; + } + } } partial void UpdateProjSpecific(float deltaTime); @@ -1039,6 +1051,45 @@ namespace Barotrauma } } + private float blinkTimer; + private float blinkPhase; + + private float TotalBlinkDurationOut => Params.BlinkDurationOut + Params.BlinkHoldTime; + + public void Blink(float deltaTime, float referenceRotation) + { + if (blinkTimer > -TotalBlinkDurationOut) + { + blinkPhase -= deltaTime; + if (blinkPhase > 0) + { + // in + float t = ToolBox.GetEasing(Params.BlinkTransitionIn, MathUtils.InverseLerp(1, 0, blinkPhase / Params.BlinkDurationIn)); + body.SmoothRotate(referenceRotation + MathHelper.ToRadians(Params.BlinkRotationIn) * Dir, Mass * Params.BlinkForce * t, wrapAngle: true); + } + else + { + if (Math.Abs(blinkPhase) < Params.BlinkHoldTime) + { + // hold + body.SmoothRotate(referenceRotation + MathHelper.ToRadians(Params.BlinkRotationIn) * Dir, Mass * Params.BlinkForce, wrapAngle: true); + } + else + { + // out + float t = ToolBox.GetEasing(Params.BlinkTransitionOut, MathUtils.InverseLerp(0, 1, -blinkPhase / TotalBlinkDurationOut)); + body.SmoothRotate(referenceRotation + MathHelper.ToRadians(Params.BlinkRotationOut) * Dir, Mass * Params.BlinkForce * t, wrapAngle: true); + } + } + } + else + { + // out + blinkPhase = Params.BlinkDurationIn; + body.SmoothRotate(referenceRotation + MathHelper.ToRadians(Params.BlinkRotationOut) * Dir, Mass * Params.BlinkForce, wrapAngle: true); + } + } + public void Remove() { body?.Remove(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Animation/AnimationParams.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Animation/AnimationParams.cs index cab24ff0a..1d18cf6d4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Animation/AnimationParams.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Animation/AnimationParams.cs @@ -305,7 +305,7 @@ namespace Barotrauma instance.Load(fullPath, speciesName); anims.Add(fileName, instance); DebugConsole.NewMessage($"[AnimationParams] New animation file of type {animationType} created.", Color.GhostWhite); - return instance as T; + return instance; } public bool Serialize() => base.Serialize(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/CharacterParams.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/CharacterParams.cs index 6620f3334..4b5530063 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/CharacterParams.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/CharacterParams.cs @@ -474,6 +474,12 @@ namespace Barotrauma [Serialize(true, true, description: "The character will flee for a brief moment when being shot at if not performing an attack."), Editable] public bool AvoidGunfire { get; private set; } + [Serialize(3f, true, description: "How long the creature avoids gunfire. Also used when the creature is unlatched."), Editable(minValue: 0f, maxValue: 100f)] + public float AvoidTime { get; private set; } + + [Serialize(20f, true, description: "How long the creature flees before returning to normal state. When the creature sees the target or is being chased, it will always flee, if it's in the flee state."), Editable(minValue: 0f, maxValue: 100f)] + public float MinFleeTime { get; private set; } + [Serialize(false, true, description: "Does the character try to break inside the sub?"), Editable()] public bool AggressiveBoarding { get; private set; } @@ -580,6 +586,9 @@ namespace Barotrauma [Serialize(0f, true, description: "Generic timer that can be used for different purposes depending on the state. E.g. in Observe state this defines how long the character in general keeps staring the targets (Some random is always applied)."), Editable] public float Timer { get; set; } + [Serialize(false, true, description: "Should the target be ignored if it's inside a container/inventory. Only affects items."), Editable] + public bool IgnoreContained { get; set; } + public TargetParams(XElement element, CharacterParams character) : base(element, character) { } public TargetParams(string tag, AIState state, float priority, CharacterParams character) : base(CreateNewElement(tag, state, priority), character) { } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Ragdoll/RagdollParams.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Ragdoll/RagdollParams.cs index 8998f9321..9a02bf2a8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Ragdoll/RagdollParams.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/Ragdoll/RagdollParams.cs @@ -77,7 +77,7 @@ namespace Barotrauma [Serialize(LimbType.Torso, true), Editable] public LimbType MainLimb { get; set; } - private static Dictionary> allRagdolls = new Dictionary>(); + private readonly static Dictionary> allRagdolls = new Dictionary>(); public List Colliders { get; private set; } = new List(); public List Limbs { get; private set; } = new List(); @@ -546,14 +546,17 @@ namespace Barotrauma [Serialize(LimbType.None, true, description: "The limb type affects many things, like the animations. Torso or Head are considered as the main limbs. Every character should have at least one Torso or Head."), Editable()] public LimbType Type { get; set; } - [Serialize(float.NaN, true, description: "The orientation of the sprite as drawn on the sprite sheet. Overrides the value defined in the Ragdoll settings. Used mainly for animations and widgets."), Editable(-360, 360)] - public float SpriteOrientation { get; set; } - /// /// The orientation of the sprite as drawn on the sprite sheet (in radians). /// public float GetSpriteOrientation() => MathHelper.ToRadians(float.IsNaN(SpriteOrientation) ? Ragdoll.SpritesheetOrientation : SpriteOrientation); + [Serialize("", true), Editable] + public string Notes { get; set; } + + [Serialize(1f, true), Editable] + public float Scale { get; set; } + [Serialize(true, true, description: "Does the limb flip when the character flips?"), Editable()] public bool Flip { get; set; } @@ -566,8 +569,8 @@ namespace Barotrauma [Serialize(false, true, description: "Disable drawing for this limb."), Editable()] public bool Hide { get; set; } - [Serialize(1f, true, description: "Higher values make AI characters prefer attacking this limb."), Editable(MinValueFloat = 0.1f, MaxValueFloat = 10)] - public float AttackPriority { get; set; } + [Serialize(float.NaN, true, description: "The orientation of the sprite as drawn on the sprite sheet. Overrides the value defined in the Ragdoll settings. Used mainly for animations and widgets."), Editable(-360, 360, ValueStep = 90, DecimalCount = 0)] + public float SpriteOrientation { get; set; } [Serialize(0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 500)] public float SteerForce { get; set; } @@ -590,6 +593,9 @@ namespace Barotrauma [Serialize(7f, true, description: "Increasing the damping makes the limb stop rotating more quickly."), Editable] public float AngularDamping { get; set; } + [Serialize(1f, true, description: "Higher values make AI characters prefer attacking this limb."), Editable(MinValueFloat = 0.1f, MaxValueFloat = 10)] + public float AttackPriority { get; set; } + [Serialize("0, 0", true, description: "The position which is used to lead the IK chain to the IK goal. Only applicable if the limb is hand or foot."), Editable()] public Vector2 PullPos { get; set; } @@ -602,18 +608,12 @@ namespace Barotrauma [Serialize("0, 0", true, description: "Relative offset for the mouth position (starting from the center). Only applicable for LimbType.Head. Used for eating."), Editable(DecimalCount = 2, MinValueFloat = -10f, MaxValueFloat = 10f)] public Vector2 MouthPos { get; set; } - [Serialize("", true), Editable] - public string Notes { get; set; } - [Serialize(0f, true), Editable] public float ConstantTorque { get; set; } [Serialize(0f, true), Editable] public float ConstantAngle { get; set; } - [Serialize(1f, true), Editable] - public float Scale { get; set; } - [Serialize(1f, true), Editable(DecimalCount = 2, MinValueFloat = 0, MaxValueFloat = 10)] public float AttackForceMultiplier { get; set; } @@ -624,6 +624,42 @@ namespace Barotrauma [Serialize(10f, true, "How long it takes for the severed limb to fade out"), Editable(MinValueFloat = 0, MaxValueFloat = 100, ValueStep = 1)] public float SeveredFadeOutTime { get; set; } = 10.0f; + [Serialize(false, true, description: "Only applied when the limb is of type Tail. If none of the tails have been defined to use the angle and an angle is defined in the animation parameters, the first tail limb is used."), Editable] + public bool ApplyTailAngle { get; set; } + + [Serialize(1f, true), Editable(ValueStep = 0.1f, DecimalCount = 2)] + public float SineFrequencyMultiplier { get; set; } + + [Serialize(1f, true), Editable(ValueStep = 0.1f, DecimalCount = 2)] + public float SineAmplitudeMultiplier { get; set; } + + [Serialize(0f, true), Editable(0, 100, ValueStep = 1, DecimalCount = 1)] + public float BlinkFrequency { get; set; } + + [Serialize(0.2f, true), Editable(0.01f, 10, ValueStep = 1, DecimalCount = 2)] + public float BlinkDurationIn { get; set; } + + [Serialize(0.5f, true), Editable(0.01f, 10, ValueStep = 1, DecimalCount = 2)] + public float BlinkDurationOut { get; set; } + + [Serialize(0f, true), Editable(0, 10, ValueStep = 1, DecimalCount = 2)] + public float BlinkHoldTime { get; set; } + + [Serialize(0f, true), Editable(-360, 360, ValueStep = 1, DecimalCount = 0)] + public float BlinkRotationIn { get; set; } + + [Serialize(45f, true), Editable(-360, 360, ValueStep = 1, DecimalCount = 0)] + public float BlinkRotationOut { get; set; } + + [Serialize(50f, true), Editable] + public float BlinkForce { get; set; } + + [Serialize(TransitionMode.Linear, true), Editable] + public TransitionMode BlinkTransitionIn { get; private set; } + + [Serialize(TransitionMode.Linear, true), Editable] + public TransitionMode BlinkTransitionOut { get; private set; } + // Non-editable -> // TODO: make read-only [Serialize(0, true)] diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs index 76a4cc6be..9fa16424f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs @@ -592,13 +592,13 @@ namespace Barotrauma (character.CurrentHull.Submarine == Submarine.MainSub || Submarine.MainSub.DockedTo.Contains(character.CurrentHull.Submarine))) { //crawler inside the sub adds 0.1f to enemy danger, mantis 0.25f - enemyDanger += enemyAI.CombatStrength / 1000.0f; + enemyDanger += enemyAI.CombatStrength / 100.0f; } else if (enemyAI.SelectedAiTarget?.Entity?.Submarine != null) { //enemy outside and targeting the sub or something in it //moloch adds 0.24 to enemy danger, a crawler 0.02 - enemyDanger += enemyAI.CombatStrength / 2000.0f; + enemyDanger += enemyAI.CombatStrength / 1000.0f; } } enemyDanger = MathHelper.Clamp(enemyDanger, 0.0f, 1.0f); @@ -654,12 +654,12 @@ namespace Barotrauma if (targetIntensity > currentIntensity) { //25 seconds for intensity to go from 0.0 to 1.0 - currentIntensity = MathHelper.Min(currentIntensity + 0.04f * IntensityUpdateInterval, targetIntensity); + currentIntensity = Math.Min(currentIntensity + 0.04f * IntensityUpdateInterval, targetIntensity); } else { //400 seconds for intensity to go from 1.0 to 0.0 - currentIntensity = MathHelper.Max(0.0025f * IntensityUpdateInterval, targetIntensity); + currentIntensity = Math.Max(currentIntensity - 0.0025f * IntensityUpdateInterval, targetIntensity); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs index 943ce7214..11cb7f46f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs @@ -30,6 +30,8 @@ namespace Barotrauma public CampaignMetadata CampaignMetadata; + protected XElement petsElement; + public enum TransitionType { None, diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs index 3e6c384e4..61b0d7c6f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs @@ -282,6 +282,11 @@ namespace Barotrauma DebugConsole.ThrowError("Couldn't start game session, submarine file corrupted."); return; } + if (SubmarineInfo.SubmarineElement.Elements().Count() == 0) + { + DebugConsole.ThrowError("Couldn't start game session, saved submarine is empty. The submarine file may be corrupted."); + return; + } LevelData = levelData; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Throwable.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Throwable.cs index 62d7c6de6..4680287cd 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Throwable.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Throwable.cs @@ -6,18 +6,20 @@ namespace Barotrauma.Items.Components { class Throwable : Holdable { - private float throwForce, throwPos; + private float throwPos; private bool throwing, throwDone; private bool midAir; - [Serialize(1.0f, false, description: "The impulse applied to the physics body of the item when thrown. Higher values make the item be thrown faster.")] - public float ThrowForce + public Character CurrentThrower { - get { return throwForce; } - set { throwForce = value; } + get; + private set; } + [Serialize(1.0f, false, description: "The impulse applied to the physics body of the item when thrown. Higher values make the item be thrown faster.")] + public float ThrowForce { get; set; } + public Throwable(Item item, XElement element) : base(item, element) { @@ -60,6 +62,21 @@ namespace Barotrauma.Items.Components { if (item.body.LinearVelocity.LengthSquared() < 0.01f) { + CurrentThrower = null; + if (statusEffectLists.ContainsKey(ActionType.OnImpact)) + { + foreach (var statusEffect in statusEffectLists[ActionType.OnImpact]) + { + statusEffect.SetUser(null); + } + } + if (statusEffectLists.ContainsKey(ActionType.OnBroken)) + { + foreach (var statusEffect in statusEffectLists[ActionType.OnBroken]) + { + statusEffect.SetUser(null); + } + } item.body.CollidesWith = Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionPlatform; midAir = false; } @@ -117,9 +134,24 @@ namespace Barotrauma.Items.Components #if SERVER GameServer.Log(GameServer.CharacterLogName(picker) + " threw " + item.Name, ServerLog.MessageType.ItemInteraction); #endif - Character thrower = picker; - item.Drop(thrower, createNetworkEvent: GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer); - item.body.ApplyLinearImpulse(throwVector * throwForce * item.body.Mass * 3.0f, maxVelocity: NetConfig.MaxPhysicsBodyVelocity); + CurrentThrower = picker; + if (statusEffectLists.ContainsKey(ActionType.OnImpact)) + { + foreach (var statusEffect in statusEffectLists[ActionType.OnImpact]) + { + statusEffect.SetUser(CurrentThrower); + } + } + if (statusEffectLists.ContainsKey(ActionType.OnBroken)) + { + foreach (var statusEffect in statusEffectLists[ActionType.OnBroken]) + { + statusEffect.SetUser(CurrentThrower); + } + } + + item.Drop(CurrentThrower, createNetworkEvent: GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer); + item.body.ApplyLinearImpulse(throwVector * ThrowForce * item.body.Mass * 3.0f, maxVelocity: NetConfig.MaxPhysicsBodyVelocity); //disable platform collisions until the item comes back to rest again item.body.CollidesWith = Physics.CollisionWall | Physics.CollisionLevel; @@ -135,12 +167,12 @@ namespace Barotrauma.Items.Components if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer) { - GameMain.NetworkMember.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnSecondaryUse, this, thrower.ID }); + GameMain.NetworkMember.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnSecondaryUse, this, CurrentThrower.ID }); } if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer) { //Stun grenades, flares, etc. all have their throw-related things handled in "onSecondaryUse" - ApplyStatusEffects(ActionType.OnSecondaryUse, deltaTime, thrower, user: thrower); + ApplyStatusEffects(ActionType.OnSecondaryUse, deltaTime, CurrentThrower, user: CurrentThrower); } throwing = false; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs index f74a976ac..3ffb755a1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs @@ -349,6 +349,14 @@ namespace Barotrauma.Items.Components } } + public override void OnItemLoaded() + { + if (item.Submarine == null || !item.Submarine.Loading) + { + SpawnAlwaysContainedItems(); + } + } + public override void OnMapLoaded() { if (itemIds != null) @@ -361,7 +369,11 @@ namespace Barotrauma.Items.Components } itemIds = null; } + SpawnAlwaysContainedItems(); + } + private void SpawnAlwaysContainedItems() + { if (SpawnWithId.Length > 0) { ItemPrefab prefab = ItemPrefab.Prefabs.Find(m => m.Identifier == SpawnWithId); @@ -375,6 +387,7 @@ namespace Barotrauma.Items.Components } } + protected override void ShallowRemoveComponentSpecific() { } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs index d2cfc8dce..9459bc8cb 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs @@ -1658,10 +1658,14 @@ namespace Barotrauma public override void FlipX(bool relativeToSub) { - if (!Prefab.CanFlipX) { return; } - + //call the base method even if the item can't flip, to handle repositioning when flipping the whole sub base.FlipX(relativeToSub); + if (!Prefab.CanFlipX) + { + flippedX = false; + return; + } #if CLIENT if (Prefab.CanSpriteFlipX) { @@ -1677,10 +1681,15 @@ namespace Barotrauma public override void FlipY(bool relativeToSub) { - if (!Prefab.CanFlipY) { return; } - + //call the base method even if the item can't flip, to handle repositioning when flipping the whole sub base.FlipY(relativeToSub); + if (!Prefab.CanFlipY) + { + flippedY = false; + return; + } + #if CLIENT if (Prefab.CanSpriteFlipY) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs index bc3bbf8d1..2bfd020c4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs @@ -1340,6 +1340,9 @@ namespace Barotrauma decalsCleaned = true; #if SERVER decalUpdatePending = true; +#elif CLIENT + pendingDecalUpdates.Add(decal); + networkUpdatePending = true; #endif } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Map.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Map.cs index 469b1c2cb..f5c8ad934 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Map.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Map.cs @@ -833,7 +833,7 @@ namespace Barotrauma if (location.Discovered) { #if CLIENT - RemoveFogOfWar(StartLocation); + RemoveFogOfWar(location); #endif if (furthestDiscoveredLocation == null || location.MapPosition.X > furthestDiscoveredLocation.MapPosition.X) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs index 1966693d2..43ef0f908 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs @@ -50,7 +50,7 @@ namespace Barotrauma //observable collection because some entities may need to be notified when the collection is modified public readonly ObservableCollection linkedTo = new ObservableCollection(); - private bool flippedX, flippedY; + protected bool flippedX, flippedY; public bool FlippedX { get { return flippedX; } } public bool FlippedY { get { return flippedY; } } @@ -534,7 +534,7 @@ namespace Barotrauma public virtual void FlipX(bool relativeToSub) { flippedX = !flippedX; - if (!relativeToSub || Submarine == null) return; + if (!relativeToSub || Submarine == null) { return; } Vector2 relative = WorldPosition - Submarine.WorldPosition; relative.Y = 0.0f; @@ -548,7 +548,7 @@ namespace Barotrauma public virtual void FlipY(bool relativeToSub) { flippedY = !flippedY; - if (!relativeToSub || Submarine == null) return; + if (!relativeToSub || Submarine == null) { return; } Vector2 relative = WorldPosition - Submarine.WorldPosition; relative.X = 0.0f; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Serialization/SerializableProperty.cs b/Barotrauma/BarotraumaShared/SharedSource/Serialization/SerializableProperty.cs index ec442e36d..4f87592ca 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Serialization/SerializableProperty.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Serialization/SerializableProperty.cs @@ -415,7 +415,7 @@ namespace Barotrauma } catch (Exception e) { - DebugConsole.ThrowError("Error in SerializableProperty.TrySetValue", e); + DebugConsole.ThrowError("Error in SerializableProperty.GetValue", e); return false; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs index ca7308180..72e556013 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs @@ -733,9 +733,10 @@ namespace Barotrauma } else { - if (targets.FirstOrDefault(t => t is MapEntity) is MapEntity targetEntity && !targetEntity.Removed) + var targetLimb = targets.FirstOrDefault(t => t is Limb) as Limb; + if (targetLimb != null && !targetLimb.Removed) { - position = targetEntity.WorldPosition; + position = targetLimb.WorldPosition; } } } diff --git a/Barotrauma/BarotraumaShared/changelog.txt b/Barotrauma/BarotraumaShared/changelog.txt index 53eceeba8..4172ff77a 100644 --- a/Barotrauma/BarotraumaShared/changelog.txt +++ b/Barotrauma/BarotraumaShared/changelog.txt @@ -1,40 +1,10 @@ --------------------------------------------------------------------------------------------------------- -v0.10.601.0 (Unstable) ---------------------------------------------------------------------------------------------------------- - -- Added 2 pets: Peanut and Psilotoad. Currently only obtainable via console commands ("spawnitem peanutegg", "spawnitem psilotoadegg", "spawn peanut" or "spawn psilotoad"). -- Improvements to the Watcher. -- Fixed 2 equipped storage containers from one hand to another causing one of them to get stuck mid-air. -- Fixed a crash caused by humanoid enemies (e.g. husks). -- Moved toolbelt slot to the right side of the generic slots, added inventory icon for the slot. -- Fixed EventManager crashing if there are no event sets configured for the current location type (only affected mods that add new location types without adding any events for them). -- Use player name instead of server name for the server owner when hosting a server. -- Fixed diving suit's low oxygen warning beep not following the player wearing the diving suit. -- Made large monsters immune to paralyzant (mudraptor is the largest affected monster). -- Fixed bots not reacting to player reports in multiplayer. -- Added more copper to chalcopyrite and bornite deconstruct recipe. -- More calcium for aragonite, adjusted prices. -- Fixed fires not damaging characters. -- Set terminal's maximum message length to match maximum chat message length (otherwise chat-linked terminals work differently in multiplayer). -- Fixed inability to unlink hulls in the sub editor. -- Fixed bots "cleaning up" components attached to walls. -- Fixed yet another cause for "missing entity" errors. Occasionally happened in monster missions when a monster happened to get assigned the same ID as an item in a player's inventory. -- Fixed items held in the left hand being drawn in front of the characters. -- Allow closing the splash screens with esc. -- Fixed "inventory sizes don't match" error when a human becomes a husk. -- Fixed ability to drag and drop items into outpost reactors. -- Fixed torso getting hidden when wearing a toolbelt. -- Fixed coilgun ammo only using 90% of the fabrication materials. -- Fixed paints reverting to the dirt color client-side after spraying. -- Added missing dialog for the new "Cleanup Items" order. -- Rebalanced upgrade parameters, allowing for more noticable benefits. - ---------------------------------------------------------------------------------------------------------- -v0.10.600.0 (Unstable) +v0.10.6.0 (Unstable) --------------------------------------------------------------------------------------------------------- Changes and additions: - Reworked Watcher. +- Added pets (can be obtained by buying eggs from outposts). The pets produce items that can be used for crafting if they're kept happy and well-fed. - Added a new monster behavior: observe. - Added toolbelt (a wearable container with a capacity of 12 and it's own dedicated slot) as a replacement for the toolbox. - Improvements to the effects caused by psychosis: the affliction icon is not visible to the psychotic character, the fake fires and floods are a bit more convincing, the affliction plays random sounds and can cause other characters to become invisible. @@ -66,12 +36,20 @@ Changes and additions: - A minor change to the status effect condition targeting logic: If "This" and "NearbyCharacters" are both defined as the targets of a status effect, the conditions only apply to "this" entity, even though the effects are applied on all the targets. - Implement spread, speed, and rotation for the spawn item status effects. - Minor damage (less than 1 hp) doesn't spawn particles anymore. +- Rebalanced upgrade parameters, allowing for more noticable benefits. +- Allow closing the splash screens with esc. +- Added more copper to chalcopyrite and bornite deconstruct recipe. +- More calcium for aragonite, adjusted prices. +- Made large monsters immune to paralyzant (mudraptor is the largest affected monster). +- Use player name instead of server name for the server owner when hosting a server. +- Don't draw turret range indicators in the sub editor when the turret isn't selected. Character Editor: - Fixed a number of issues with the joint limit widgets. Also allowed to set a joint to rotate clockwise, which inverses the widget direction. Useful for heads or other limbs that extrude right from the main body. - Inversed the default joint ends, because it's more usual case to edit the second limb of the joint than the first. - The colliders of the hidden limbs are now hidden in the game view. - Changed the hotkey for toggling the parameter editor from "Tab" to "F1" and fix the inability to toggle the editor when a text field is selected. +- Fixed load and save interfaces being broken on lower resolutions. Sounds: - Added 2 new background music tracks @@ -110,6 +88,8 @@ AI improvements and fixes: Misc fixes: - Fixed clients always getting the generic "could not connect" error message when connecting to a server fails, even if there's a specific reason to the connection failing (e.g. disallowed symbols in the player's name, mismatching content packages or game version). +- Fixed yet another cause for "missing entity" errors. Occasionally happened in monster missions when a monster happened to get assigned the same ID as an item in a player's inventory. +- Fixed clients spawning a respawn shuttle in non-campaign missions even if the server has disabled respawning, leading to "missing entity" errors. - Fixed planters dropping removed seeds after a save is reloaded. - Fixed plants not updating the health after being fully grown in multiplayer. - Fixed decal syncing working unreliably in multiplayer. @@ -145,6 +125,26 @@ Misc fixes: - OnDamaged status effects now launch only when there's any damage. Not when the damage is zero. - Fixed health bar pulsating even when no damage is done by an attack/status effect. - Fixed affliction probability not having any effect when used in status effects. +- Fixed EventManager crashing if there are no event sets configured for the current location type (only affected mods that add new location types without adding any events for them). +- Fixed items held in the left hand being drawn in front of the characters. +- Fixed ability to drag and drop items into outpost reactors. +- Set terminal's maximum message length to match maximum chat message length (otherwise chat-linked terminals work differently in multiplayer). +- Fixed diving suit's low oxygen warning beep not following the player wearing the suit. +- Fixed "propaganda" and "clown outbreak" outpost events never triggering. +- Fixed "clown brutality" event getting stuck after the NPCs have been spawned. +- Fixed "impromptu engineering" event giving only one coilgun ammo box despite the text saying 2. +- Fixed "black market" event always giving the player the alien pistol. +- Fixed incendium bars exploding when pressing the Use key while holding one. +- Fixed outpost security not reacting to players throwing items that explode on impact (e.g. flash powder or nitroglycerin). +- Fixed outpost security reacting to raptor bane extract injections with lethal force. +- Fixed mantis' animation. +- Fixed multiple wire nodes occasionally getting placed with one click when rewiring. +- Fixed fire and water flow sounds staying active when returning from a multiplayer session to the main menu. +- Fixed previously discovered map tiles becoming undiscovered when saving and loading a campaign. +- Fixed 1st shot from SMG magazines spawned with console commands doing nothing. +- Fixed calyxanide not damaging huskified humans or crawlers. +- Fixed explosives exploding when combining them results in one being removed. +- Fixed items that don't flip horizontally being positioned incorrectly in mirrored subs. --------------------------------------------------------------------------------------------------------- v0.10.5.1