diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterInfo.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterInfo.cs index d8b6cda3a..24ca41abe 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterInfo.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterInfo.cs @@ -688,8 +688,10 @@ namespace Barotrauma new GUIFrame( new RectTransform(Vector2.One * 0.7f, dropdownButton.RectTransform, Anchor.CenterLeft) { RelativeOffset = new Vector2(0.05f, 0.0f) }, style: null); + Color? previewingColor = null; dropdown.OnSelected = (component, color) => { + previewingColor = null; setter((Color)color); buttonFrame.Color = getter(); buttonFrame.HoverColor = getter(); @@ -727,25 +729,24 @@ namespace Barotrauma dropdown.Select(dropdown.ListBox.Content.GetChildIndex(childToSelect)); //The following exists to track mouseover to preview colors before selecting them - bool previewingColor = false; new GUICustomComponent(new RectTransform(Vector2.One, buttonFrame.RectTransform), onUpdate: (deltaTime, component) => { if (GUI.MouseOn is GUIFrame { Parent: { } p } hoveredFrame && dropdown.ListBox.Content.IsParentOf(hoveredFrame)) { - previewingColor = true; + previewingColor ??= getter(); Color color = (Color)(dropdown.ListBox.Content.FindChild(c => - c == hoveredFrame || c.IsParentOf(hoveredFrame))?.UserData ?? dropdown.SelectedData); + c == hoveredFrame || c.IsParentOf(hoveredFrame))?.UserData ?? dropdown.SelectedData ?? getter()); setter(color); buttonFrame.Color = getter(); buttonFrame.HoverColor = getter(); } - else if (previewingColor) + else if (previewingColor.HasValue) { - setter((Color)dropdown.SelectedData); + setter(previewingColor.Value); buttonFrame.Color = getter(); buttonFrame.HoverColor = getter(); - previewingColor = false; + previewingColor = null; } }, onDraw: null) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterNetworking.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterNetworking.cs index 925021818..5897105f1 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterNetworking.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterNetworking.cs @@ -299,7 +299,7 @@ namespace Barotrauma break; case ServerNetObject.ENTITY_EVENT: - int eventType = msg.ReadRangedInteger(0, 12); + int eventType = msg.ReadRangedInteger(0, 13); switch (eventType) { case 0: //NetEntityEvent.Type.InventoryState @@ -476,6 +476,18 @@ namespace Barotrauma int moneyAmount = msg.ReadInt32(); SetMoney(moneyAmount); break; + case 13: //NetEntityEvent.Type.UpdatePermanentStats: + byte savedStatValueCount = msg.ReadByte(); + StatTypes statType = (StatTypes)msg.ReadByte(); + info?.ClearSavedStatValues(statType); + for (int i = 0; i < savedStatValueCount; i++) + { + string statIdentifier = msg.ReadString(); + float statValue = msg.ReadSingle(); + bool removeOnDeath = msg.ReadBoolean(); + info?.ChangeSavedStatValue(statType, statValue, statIdentifier, removeOnDeath, setValue: true); + } + break; } msg.ReadPadBits(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs b/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs index 68aa6da0c..0bb2d3ce3 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs @@ -476,6 +476,7 @@ namespace Barotrauma if (Screen.Selected == GameMain.SubEditorScreen) { NewMessage("WARNING: Switching directly from the submarine editor to the game view may cause bugs and crashes. Use with caution.", Color.Orange); + Entity.Spawner ??= new EntitySpawner(); } GameMain.GameScreen.Select(); })); @@ -488,6 +489,8 @@ namespace Barotrauma Submarine.MainSub = Submarine.Load(subInfo, true); } GameMain.SubEditorScreen.Select(enableAutoSave: Screen.Selected != GameMain.GameScreen); + Entity.Spawner?.Remove(); + Entity.Spawner = null; }, isCheat: true)); commands.Add(new Command("editparticles|particleeditor", "editparticles/particleeditor: Switch to the Particle Editor to edit particle effects.", (string[] args) => diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs index da89b0e4d..f165a8006 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs @@ -139,7 +139,7 @@ namespace Barotrauma GameSession.UpdateTalentNotificationIndicator(talentPointNotification); if (Character.Controlled is { } controlled && talentResetButton != null && talentApplyButton != null) { - int talentCount = selectedTalents.Count - controlled.Info.UnlockedTalents.Count; + int talentCount = selectedTalents.Count - controlled.Info.GetUnlockedTalentsInTree().Count(); talentResetButton.Enabled = talentApplyButton.Enabled = talentCount > 0; if (talentApplyButton.Enabled && talentApplyButton.FlashTimer <= 0.0f) { @@ -1254,7 +1254,7 @@ namespace Barotrauma return; } - selectedTalents = controlledCharacter.Info.UnlockedTalents.ToList(); + selectedTalents = controlledCharacter.Info.GetUnlockedTalentsInTree().ToList(); GUILayoutGroup talentFrameLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f), talentFrameMain.RectTransform, anchor: Anchor.Center), childAnchor: Anchor.TopCenter) { @@ -1541,7 +1541,7 @@ namespace Barotrauma string pointsLeft = controlledCharacter.Info.GetAvailableTalentPoints().ToString(); - int talentCount = selectedTalents.Count - controlledCharacter.Info.UnlockedTalents.Count; + int talentCount = selectedTalents.Count - controlledCharacter.Info.GetUnlockedTalentsInTree().Count(); if (talentCount > 0) { @@ -1612,7 +1612,7 @@ namespace Barotrauma private bool ResetTalentSelection(GUIButton guiButton, object userData) { Character controlledCharacter = Character.Controlled; - selectedTalents = controlledCharacter.Info.UnlockedTalents.ToList(); + selectedTalents = controlledCharacter.Info.GetUnlockedTalentsInTree().ToList(); UpdateTalentButtons(); return true; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/HintManager.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/HintManager.cs index be0df2d3e..b58477409 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/HintManager.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/HintManager.cs @@ -544,7 +544,7 @@ namespace Barotrauma if (Character.Controlled.CurrentHull == null) { return; } if (HumanAIController.IsBallastFloraNoticeable(Character.Controlled, Character.Controlled.CurrentHull)) { - if (DisplayHint("onballastflorainfected")) { return; } + if (IsOnFriendlySub() && DisplayHint("onballastflorainfected")) { return; } } foreach (var gap in Character.Controlled.CurrentHull.ConnectedGaps) { @@ -552,7 +552,7 @@ namespace Barotrauma if (Vector2.DistanceSquared(Character.Controlled.WorldPosition, gap.ConnectedDoor.Item.WorldPosition) > 400 * 400) { continue; } if (!gap.IsRoomToRoom) { - if (!(Character.Controlled.GetEquippedItem("deepdiving", InvSlotType.OuterClothes) is Item)) { continue; } + if (!IsWearingDivingSuit()) { continue; } if (Character.Controlled.IsProtectedFromPressure()) { continue; } if (DisplayHint("divingsuitwarning", extendTextTag: false)) { return; } continue; @@ -561,10 +561,16 @@ namespace Barotrauma { if (me == Character.Controlled.CurrentHull) { continue; } if (!(me is Hull adjacentHull)) { continue; } + if (!IsOnFriendlySub()) { continue; } + if (IsWearingDivingSuit()) { continue; } if (adjacentHull.LethalPressure > 5.0f && DisplayHint("onadjacenthull.highpressure")) { return; } if (adjacentHull.WaterPercentage > 75 && !BallastHulls.Contains(adjacentHull) && DisplayHint("onadjacenthull.highwaterpercentage")) { return; } } + + static bool IsWearingDivingSuit() => Character.Controlled.GetEquippedItem("deepdiving", InvSlotType.OuterClothes) is Item; } + + static bool IsOnFriendlySub() => Character.Controlled.Submarine is Submarine sub && (sub.TeamID == Character.Controlled.TeamID || sub.TeamID == CharacterTeamType.FriendlyNPC); } private static void CheckReminders() diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/LightComponent.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/LightComponent.cs index f58aee17b..bff562d65 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/LightComponent.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/LightComponent.cs @@ -39,7 +39,7 @@ namespace Barotrauma.Items.Components public void Draw(SpriteBatch spriteBatch, bool editing = false, float itemDepth = -1) { - if (Light.LightSprite != null && (item.body == null || item.body.Enabled) && lightBrightness > 0.0f && IsOn) + if (Light.LightSprite != null && (item.body == null || item.body.Enabled) && lightBrightness > 0.0f && IsOn && Light.Enabled) { Vector2 origin = Light.LightSprite.Origin; if ((Light.LightSpriteEffect & SpriteEffects.FlipHorizontally) == SpriteEffects.FlipHorizontally) { origin.X = Light.LightSprite.SourceRect.Width - origin.X; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs index 69b1337f3..53c770df7 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs @@ -529,21 +529,30 @@ namespace Barotrauma.Items.Components }; }*/ - string name = GetRecipeNameAndAmount(selectedItem); + string itemName = GetRecipeNameAndAmount(selectedItem); + string name = itemName; float quality = GetFabricatedItemQuality(selectedItem, user); if (quality > 0) { - name = TextManager.GetWithVariable("itemname.quality" + (int)quality, "[itemname]", name+'\n', fallBackTag: "itemname.quality3"); + name = TextManager.GetWithVariable("itemname.quality" + (int)quality, "[itemname]", itemName + '\n', fallBackTag: "itemname.quality3"); } var nameBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedFrame.RectTransform), - name, textAlignment: Alignment.CenterLeft, textColor: Color.Aqua, font: GUI.SubHeadingFont, parseRichText: true) + name, textAlignment: Alignment.TopLeft, textColor: Color.Aqua, font: GUI.SubHeadingFont, parseRichText: true) { AutoScaleHorizontal = true }; - - nameBlock.Padding = new Vector4(0, nameBlock.Padding.Y, nameBlock.Padding.Z, nameBlock.Padding.W); + nameBlock.Padding = new Vector4(0, nameBlock.Padding.Y, GUI.IntScale(5), nameBlock.Padding.W); + if (nameBlock.TextScale < 0.7f) + { + nameBlock.SetRichText(TextManager.GetWithVariable("itemname.quality" + (int)quality, "[itemname]", itemName, fallBackTag: "itemname.quality3")); + nameBlock.AutoScaleHorizontal = false; + nameBlock.TextScale = 0.7f; + nameBlock.Wrap = true; + nameBlock.SetTextPos(); + nameBlock.RectTransform.MinSize = new Point(0, (int)(nameBlock.TextSize.Y * nameBlock.TextScale)); + } if (!string.IsNullOrWhiteSpace(selectedItem.TargetItem.Description)) { @@ -555,6 +564,7 @@ namespace Barotrauma.Items.Components while (description.Rect.Height + nameBlock.Rect.Height > paddedFrame.Rect.Height) { var lines = description.WrappedText.Split('\n'); + if (lines.Length <= 1) { break; } var newString = string.Join('\n', lines.Take(lines.Length - 1)); description.Text = newString.Substring(0, newString.Length - 4) + "..."; description.CalculateHeightFromText(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs index 8dd2d4939..85267e5a7 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs @@ -1465,6 +1465,7 @@ namespace Barotrauma.Networking bool respawnAllowed = inc.ReadBoolean(); serverSettings.AllowDisguises = inc.ReadBoolean(); serverSettings.AllowRewiring = inc.ReadBoolean(); + serverSettings.AllowFriendlyFire = inc.ReadBoolean(); serverSettings.LockAllDefaultWires = inc.ReadBoolean(); serverSettings.AllowRagdollButton = inc.ReadBoolean(); GameMain.NetLobbyScreen.UsingShuttle = inc.ReadBoolean(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundPlayer.cs b/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundPlayer.cs index 818c5040e..172454cc2 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundPlayer.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundPlayer.cs @@ -884,7 +884,7 @@ namespace Barotrauma // Switch the type ambience if nothing playing atm or the currently playing clip is not suitable anymore else if (targetMusic[typeAmbienceTrackIndex] == null || currentMusic[typeAmbienceTrackIndex] == null || !currentMusic[typeAmbienceTrackIndex].IsPlaying() || suitableTypeAmbiences.None(m => m.File == currentMusic[typeAmbienceTrackIndex].Filename)) { - targetMusic[mainTrackIndex] = suitableMusic.GetRandom(); + targetMusic[typeAmbienceTrackIndex] = suitableTypeAmbiences.GetRandom(); } //get the appropriate intensity layers for current situation diff --git a/Barotrauma/BarotraumaClient/LinuxClient.csproj b/Barotrauma/BarotraumaClient/LinuxClient.csproj index 0b19d09f4..fb716d58a 100644 --- a/Barotrauma/BarotraumaClient/LinuxClient.csproj +++ b/Barotrauma/BarotraumaClient/LinuxClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.1500.7.0 + 0.1500.8.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/MacClient.csproj b/Barotrauma/BarotraumaClient/MacClient.csproj index 9859d339c..ca78c7537 100644 --- a/Barotrauma/BarotraumaClient/MacClient.csproj +++ b/Barotrauma/BarotraumaClient/MacClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.1500.7.0 + 0.1500.8.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/WindowsClient.csproj b/Barotrauma/BarotraumaClient/WindowsClient.csproj index 5a7a69f25..9ea6a921c 100644 --- a/Barotrauma/BarotraumaClient/WindowsClient.csproj +++ b/Barotrauma/BarotraumaClient/WindowsClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.1500.7.0 + 0.1500.8.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaServer/LinuxServer.csproj b/Barotrauma/BarotraumaServer/LinuxServer.csproj index 9b9857398..6bf02aa12 100644 --- a/Barotrauma/BarotraumaServer/LinuxServer.csproj +++ b/Barotrauma/BarotraumaServer/LinuxServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.1500.7.0 + 0.1500.8.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/MacServer.csproj b/Barotrauma/BarotraumaServer/MacServer.csproj index 99db95ee4..d24dffea7 100644 --- a/Barotrauma/BarotraumaServer/MacServer.csproj +++ b/Barotrauma/BarotraumaServer/MacServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.1500.7.0 + 0.1500.8.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterInfo.cs b/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterInfo.cs index 44df073ae..0dba444c6 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterInfo.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterInfo.cs @@ -32,6 +32,12 @@ namespace Barotrauma } } + partial void OnPermanentStatChanged(StatTypes statType) + { + if (Character == null || Character.Removed) { return; } + GameMain.NetworkMember.CreateEntityEvent(Character, new object[] { NetEntityEvent.Type.UpdatePermanentStats, statType }); + } + public void ServerWrite(IWriteMessage msg) { msg.Write(ID); @@ -66,8 +72,8 @@ namespace Barotrauma msg.Write((byte)0); } // TODO: animations - msg.Write((byte)savedStatValues.SelectMany(s => s.Value).Count()); - foreach (var savedStatValuePair in savedStatValues) + msg.Write((byte)SavedStatValues.SelectMany(s => s.Value).Count()); + foreach (var savedStatValuePair in SavedStatValues) { foreach (var savedStatValue in savedStatValuePair.Value) { diff --git a/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs b/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs index 65861aecc..bc7428d42 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs @@ -310,7 +310,7 @@ namespace Barotrauma if (extraData != null) { - const int min = 0, max = 12; + const int min = 0, max = 13; switch ((NetEntityEvent.Type)extraData[0]) { case NetEntityEvent.Type.InventoryState: @@ -438,6 +438,30 @@ namespace Barotrauma msg.WriteRangedInteger(12, min, max); msg.Write(GameMain.GameSession.Campaign.Money); break; + case NetEntityEvent.Type.UpdatePermanentStats: + msg.WriteRangedInteger(13, min, max); + if (Info == null || extraData.Length < 2 || !(extraData[1] is StatTypes statType)) + { + msg.Write((byte)0); + msg.Write((byte)0); + } + else if (!Info.SavedStatValues.ContainsKey(statType)) + { + msg.Write((byte)0); + msg.Write((byte)statType); + } + else + { + msg.Write((byte)Info.SavedStatValues[statType].Count); + msg.Write((byte)statType); + foreach (var savedStatValue in Info.SavedStatValues[statType]) + { + msg.Write(savedStatValue.StatIdentifier); + msg.Write(savedStatValue.StatValue); + msg.Write(savedStatValue.RemoveOnDeath); + } + } + break; default: DebugConsole.ThrowError("Invalid NetworkEvent type for entity " + ToString() + " (" + (NetEntityEvent.Type)extraData[0] + ")"); break; diff --git a/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs b/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs index cc969a0dd..2b9b3f857 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs @@ -1677,10 +1677,25 @@ namespace Barotrauma return; } + bool relativeStrength = false; + if (args.Length > 4) + { + bool.TryParse(args[4], out relativeStrength); + } + Character targetCharacter = (args.Length <= 2) ? client.Character : FindMatchingCharacter(args.Skip(2).ToArray()); if (targetCharacter != null) { - targetCharacter.CharacterHealth.ApplyAffliction(targetCharacter.AnimController.MainLimb, afflictionPrefab.Instantiate(afflictionStrength)); + Limb targetLimb = targetCharacter.AnimController.MainLimb; + if (args.Length > 3) + { + targetLimb = targetCharacter.AnimController.Limbs.FirstOrDefault(l => l.type.ToString().Equals(args[3], StringComparison.OrdinalIgnoreCase)); + } + if (relativeStrength) + { + afflictionStrength *= targetCharacter.MaxVitality / afflictionPrefab.MaxStrength; + } + targetCharacter.CharacterHealth.ApplyAffliction(targetLimb ?? targetCharacter.AnimController.MainLimb, afflictionPrefab.Instantiate(afflictionStrength)); } } ); @@ -2171,6 +2186,7 @@ namespace Barotrauma if (client == null) { GameMain.Server.SendConsoleMessage("Client \"" + args[0] + "\" not found.", senderClient); + return; } var character = FindMatchingCharacter(args.Skip(1).ToArray(), false); diff --git a/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/CharacterCampaignData.cs b/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/CharacterCampaignData.cs index 21c9f7c8e..9f3f034b7 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/CharacterCampaignData.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/CharacterCampaignData.cs @@ -1,4 +1,6 @@ using Barotrauma.Networking; +using System.Globalization; +using System.Xml.Linq; namespace Barotrauma { @@ -11,11 +13,65 @@ namespace Barotrauma get { return itemData != null; } } - partial void InitProjSpecific(Client client) + public CharacterCampaignData(Client client, bool giveRespawnPenaltyAffliction = false) { + Name = client.Name; ClientEndPoint = client.Connection.EndPointString; SteamID = client.SteamID; CharacterInfo = client.CharacterInfo; + + healthData = new XElement("health"); + client.Character?.CharacterHealth?.Save(healthData); + if (giveRespawnPenaltyAffliction) + { + var respawnPenaltyAffliction = RespawnManager.GetRespawnPenaltyAffliction(); + healthData.Add(new XElement("Affliction", + new XAttribute("identifier", respawnPenaltyAffliction.Identifier), + new XAttribute("strength", respawnPenaltyAffliction.Strength.ToString("G", CultureInfo.InvariantCulture)))); + } + if (client.Character?.Inventory != null) + { + itemData = new XElement("inventory"); + Character.SaveInventory(client.Character.Inventory, itemData); + } + OrderData = new XElement("orders"); + if (client.CharacterInfo != null) + { + CharacterInfo.SaveOrderData(client.CharacterInfo, OrderData); + } + } + + + public CharacterCampaignData(XElement element) + { + Name = element.GetAttributeString("name", "Unnamed"); + ClientEndPoint = element.GetAttributeString("endpoint", null) ?? element.GetAttributeString("ip", ""); + string steamID = element.GetAttributeString("steamid", ""); + if (!string.IsNullOrEmpty(steamID)) + { + ulong.TryParse(steamID, out ulong parsedID); + SteamID = parsedID; + } + + foreach (XElement subElement in element.Elements()) + { + switch (subElement.Name.ToString().ToLowerInvariant()) + { + case "character": + case "characterinfo": + CharacterInfo = new CharacterInfo(subElement); + break; + case "inventory": + itemData = subElement; + break; + case "health": + healthData = subElement; + break; + case "orders": + OrderData = subElement; + break; + } + } } public bool MatchesClient(Client client) diff --git a/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs b/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs index 921de6336..88069d824 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs @@ -209,6 +209,11 @@ namespace Barotrauma //refresh the character data of clients who are still in the server foreach (Client c in GameMain.Server.ConnectedClients) { + if (c.Character != null && c.Character.Info == null) + { + c.Character = null; + } + if (c.HasSpawned && c.CharacterInfo != null && c.CharacterInfo.CauseOfDeath != null && c.CharacterInfo.CauseOfDeath?.Type != CauseOfDeathType.Disconnected) { //the client has opted to spawn this round with Reaper's Tax @@ -228,7 +233,7 @@ namespace Barotrauma } c.CharacterInfo = characterInfo; characterData.RemoveAll(cd => cd.MatchesClient(c)); - characterData.Add(new CharacterCampaignData(c)); + characterData.Add(new CharacterCampaignData(c)); } //refresh the character data of clients who aren't in the server anymore diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs index 2486286e3..1ed8e43cc 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs @@ -2470,6 +2470,7 @@ namespace Barotrauma.Networking msg.Write(serverSettings.AllowRespawn && missionAllowRespawn); msg.Write(serverSettings.AllowDisguises); msg.Write(serverSettings.AllowRewiring); + msg.Write(serverSettings.AllowFriendlyFire); msg.Write(serverSettings.LockAllDefaultWires); msg.Write(serverSettings.AllowRagdollButton); msg.Write(serverSettings.UseRespawnShuttle); diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/Voting.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/Voting.cs index acb320756..889f82de4 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/Voting.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/Voting.cs @@ -92,7 +92,7 @@ namespace Barotrauma case VoteType.Mode: string modeIdentifier = inc.ReadString(); GameModePreset mode = GameModePreset.List.Find(gm => gm.Identifier == modeIdentifier); - if (!mode.Votable) { break; } + if (mode == null || !mode.Votable) { break; } sender.SetVote(voteType, mode); break; case VoteType.EndRound: diff --git a/Barotrauma/BarotraumaServer/WindowsServer.csproj b/Barotrauma/BarotraumaServer/WindowsServer.csproj index b5edb232b..bb6b99dd3 100644 --- a/Barotrauma/BarotraumaServer/WindowsServer.csproj +++ b/Barotrauma/BarotraumaServer/WindowsServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.1500.7.0 + 0.1500.8.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AIController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AIController.cs index c5e455940..6cc7e4886 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AIController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AIController.cs @@ -1,4 +1,5 @@ -using Barotrauma.Items.Components; +using Barotrauma.Extensions; +using Barotrauma.Items.Components; using Barotrauma.Networking; using FarseerPhysics; using Microsoft.Xna.Framework; @@ -346,7 +347,7 @@ namespace Barotrauma } #region Escape - public abstract void Escape(float deltaTime); + public abstract bool Escape(float deltaTime); public Gap EscapeTarget { get; private set; } @@ -425,17 +426,38 @@ namespace Barotrauma } if (EscapeTarget != null) { - SteeringManager.SteeringSeek(EscapeTarget.SimPosition, 10); - float sqrDist = Vector2.DistanceSquared(Character.SimPosition, EscapeTarget.SimPosition); - if (sqrDist < 0.5f || Character.CurrentHull == null || HasValidPath(requireNonDirty: true, requireUnfinished: false) && pathSteering.CurrentPath.Finished) + Vector2 diff = EscapeTarget.WorldPosition - Character.WorldPosition; + float sqrDist = diff.LengthSquared(); + if (Character.CurrentHull == null || sqrDist < MathUtils.Pow2(50) || pathSteering == null || IsCurrentPathUnreachable || IsCurrentPathFinished) { - // Very close to the target, outside, or at the end of the path -> just steer towards it manually without using the path + // Very close to the target, outside, or at the end of the path -> try to steer through the gap SteeringManager.Reset(); - SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(EscapeTarget.WorldPosition - Character.WorldPosition)); - if (sqrDist < 4) + pathSteering?.ResetPath(); + if (sqrDist < MathUtils.Pow2(50)) { - return true; + // Very close -> just keep steering forward + var forward = VectorExtensions.Forward(Character.AnimController.Collider.Rotation + MathHelper.PiOver2); + SteeringManager.SteeringManual(deltaTime, forward); } + else if (Character.CurrentHull == null) + { + // Outside -> steer away from the target + SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(-diff)); + } + else + { + // Still inside -> steer towards the target + SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(diff)); + } + return sqrDist < MathUtils.Pow2(200); + } + else if (pathSteering != null) + { + pathSteering.SteeringSeek(EscapeTarget.SimPosition, weight: 1, minGapSize); + } + else + { + SteeringManager.SteeringSeek(EscapeTarget.SimPosition, 10); } } else diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs index 332c4d3fe..d4ccdeadd 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs @@ -1903,7 +1903,7 @@ namespace Barotrauma Character.AnimController.ReleaseStuckLimbs(); LatchOntoAI?.DeattachFromBody(reset: true, cooldown: 1); if (attacker == null || attacker.AiTarget == null || attacker.Removed || attacker.IsDead) { return; } - if (Character.Params.CanInteract) + if (Character.Params.CanInteract && attackResult.Damage > 10) { ReleaseDragTargets(); } @@ -3607,12 +3607,12 @@ namespace Barotrauma public bool CanPassThroughHole(Structure wall, int sectionIndex) => CanPassThroughHole(wall, sectionIndex, requiredHoleCount); - public override void Escape(float deltaTime) + public override bool Escape(float deltaTime) { if (SelectedAiTarget != null && (SelectedAiTarget.Entity == null || SelectedAiTarget.Entity.Removed)) { State = AIState.Idle; - return; + return false; } else if (SelectedTargetMemory is AITargetMemory targetMemory && SelectedAiTarget?.Entity is Character) { @@ -3653,6 +3653,7 @@ namespace Barotrauma } } } + return isSteeringThroughGap; void SteerAwayFromTheEnemy() { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs index 2378f2e6a..b473ea46c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs @@ -1364,10 +1364,7 @@ namespace Barotrauma ObjectiveManager.WaitTimer = waitDuration; } - public override void Escape(float deltaTime) - { - UpdateEscape(deltaTime, canAttackDoors: false); - } + public override bool Escape(float deltaTime) => UpdateEscape(deltaTime, canAttackDoors: false); private void CheckCrouching(float deltaTime) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/IndoorsSteeringManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/IndoorsSteeringManager.cs index 24491ff3d..603f9da95 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/IndoorsSteeringManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/IndoorsSteeringManager.cs @@ -269,7 +269,7 @@ namespace Barotrauma { var waypoint = CurrentPath.Nodes[i]; float directDistance = Vector2.DistanceSquared(character.WorldPosition, waypoint.WorldPosition); - if (directDistance > (pathDistance * pathDistance) || Submarine.PickBody(host.SimPosition, waypoint.SimPosition, collisionCategory: Physics.CollisionLevel) != null) + if (directDistance > (pathDistance * pathDistance) || Submarine.PickBody(host.SimPosition, waypoint.SimPosition, collisionCategory: Physics.CollisionLevel | Physics.CollisionWall) != null) { pathDistance -= CurrentPath.GetLength(startIndex: i - 1, endIndex: i); continue; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveReturn.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveReturn.cs index be18f3e28..4ed0d0ee3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveReturn.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveReturn.cs @@ -9,6 +9,7 @@ namespace Barotrauma public override string Identifier { get; set; } = "return"; private AIObjectiveGoTo moveInsideObjective, moveInCaveObjective, moveOutsideObjective; private bool usingEscapeBehavior; + private bool isSteeringThroughGap; public Submarine ReturnTarget { get; } public AIObjectiveReturn(Character character, Character orderGiver, AIObjectiveManager objectiveManager, float priorityModifier = 1.0f) : base(character, objectiveManager, priorityModifier) @@ -57,7 +58,7 @@ namespace Barotrauma return; } bool shouldUseEscapeBehavior = false; - if (character.CurrentHull != null) + if (character.CurrentHull != null || isSteeringThroughGap) { if (character.Submarine == null || !character.Submarine.IsConnectedTo(ReturnTarget)) { @@ -67,8 +68,8 @@ namespace Barotrauma { HumanAIController.ResetEscape(); } - HumanAIController.Escape(deltaTime); - if (HumanAIController.EscapeTarget == null || HumanAIController.IsCurrentPathUnreachable) + isSteeringThroughGap = HumanAIController.Escape(deltaTime); + if (!isSteeringThroughGap && (HumanAIController.EscapeTarget == null || HumanAIController.IsCurrentPathUnreachable)) { Abandon = true; } @@ -92,7 +93,10 @@ namespace Barotrauma RemoveSubObjective(ref moveInCaveObjective); RemoveSubObjective(ref moveOutsideObjective); TryAddSubObjective(ref moveInsideObjective, - constructor: () => new AIObjectiveGoTo(targetHull, character, objectiveManager), + constructor: () => new AIObjectiveGoTo(targetHull, character, objectiveManager) + { + AllowGoingOutside = true + }, onCompleted: () => RemoveSubObjective(ref moveInsideObjective), onAbandon: () => Abandon = true); } @@ -110,7 +114,7 @@ namespace Barotrauma IsCompleted = true; } } - else if (moveInCaveObjective == null && moveOutsideObjective == null) + else if (!isSteeringThroughGap && moveInCaveObjective == null && moveOutsideObjective == null) { if (HumanAIController.IsInsideCave) { @@ -134,7 +138,8 @@ namespace Barotrauma TryAddSubObjective(ref moveInCaveObjective, constructor: () => new AIObjectiveGoTo(closestOutsideWaypoint, character, objectiveManager) { - endNodeFilter = n => n.Waypoint == closestOutsideWaypoint + endNodeFilter = n => n.Waypoint == closestOutsideWaypoint, + AllowGoingOutside = true }, onCompleted: () => RemoveSubObjective(ref moveInCaveObjective), onAbandon: () => Abandon = true); @@ -170,7 +175,10 @@ namespace Barotrauma RemoveSubObjective(ref moveInsideObjective); RemoveSubObjective(ref moveInCaveObjective); TryAddSubObjective(ref moveOutsideObjective, - constructor: () => new AIObjectiveGoTo(targetHull, character, objectiveManager), + constructor: () => new AIObjectiveGoTo(targetHull, character, objectiveManager) + { + AllowGoingOutside = true + }, onCompleted: () => RemoveSubObjective(ref moveOutsideObjective), onAbandon: () => Abandon = true); } @@ -221,6 +229,7 @@ namespace Barotrauma moveInCaveObjective = null; moveOutsideObjective = null; usingEscapeBehavior = false; + isSteeringThroughGap = false; HumanAIController.ResetEscape(); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PathFinder.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PathFinder.cs index 57f37a140..2736e235b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PathFinder.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PathFinder.cs @@ -236,6 +236,7 @@ namespace Barotrauma collisionCategory: Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionStairs); if (body != null) { + if (body.UserData is Submarine) { return false; } if (body.UserData is Structure s && !s.IsPlatform) { return false; } if (body.UserData is Item && body.FixtureList[0].CollisionCategories.HasFlag(Physics.CollisionWall)) { return false; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs index b9aa3d262..37a5a7029 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs @@ -684,18 +684,15 @@ namespace Barotrauma if (rightHand != null && !rightHand.Disabled) { - HandIK(rightHand, torso.SimPosition + posAddition + - new Vector2( - -handPos.X, - (Math.Sign(walkPosX) == Math.Sign(Dir)) ? handPos.Y : lowerY), CurrentGroundedParams.ArmMoveStrength, CurrentGroundedParams.HandMoveStrength); + HandIK(rightHand, + torso.SimPosition + posAddition + new Vector2(-handPos.X, (Math.Sign(walkPosX) == Math.Sign(Dir)) ? handPos.Y : lowerY), + CurrentGroundedParams.ArmMoveStrength, CurrentGroundedParams.HandMoveStrength); } - if (leftHand != null && !leftHand.Disabled) { - HandIK(leftHand, torso.SimPosition + posAddition + - new Vector2( - handPos.X, - (Math.Sign(walkPosX) == Math.Sign(-Dir)) ? handPos.Y : lowerY), CurrentGroundedParams.ArmMoveStrength, CurrentGroundedParams.HandMoveStrength); + HandIK(leftHand, + torso.SimPosition + posAddition + new Vector2(handPos.X, (Math.Sign(walkPosX) == Math.Sign(-Dir)) ? handPos.Y : lowerY), + CurrentGroundedParams.ArmMoveStrength, CurrentGroundedParams.HandMoveStrength); } } else @@ -705,9 +702,7 @@ namespace Barotrauma Vector2 footPos = colliderPos; if (Crouching) { - footPos = new Vector2( - Math.Sign(stepSize.X * i) * Dir * 0.4f, - colliderPos.Y); + footPos = new Vector2(Math.Sign(stepSize.X * i) * Dir * 0.35f, colliderPos.Y); if (Math.Sign(footPos.X) != Math.Sign(Dir)) { //lift the foot at the back up a bit @@ -728,9 +723,16 @@ namespace Barotrauma { foot.DebugRefPos = colliderPos; foot.DebugTargetPos = footPos; - MoveLimb(foot, footPos, CurrentGroundedParams.FootMoveStrength); - FootIK(foot, footPos, - CurrentGroundedParams.LegBendTorque, CurrentGroundedParams.FootTorque, CurrentGroundedParams.FootAngleInRadians); + float footMoveForce = CurrentGroundedParams.FootMoveStrength; + float legBendTorque = CurrentGroundedParams.LegBendTorque; + if (Crouching) + { + // Keeps the pose + legBendTorque = 100; + footMoveForce *= 2; + } + MoveLimb(foot, footPos, footMoveForce); + FootIK(foot, footPos, legBendTorque, CurrentGroundedParams.FootTorque, CurrentGroundedParams.FootAngleInRadians); } } @@ -760,6 +762,12 @@ namespace Barotrauma forearm.body.ApplyTorque(MathHelper.Clamp(-diff, -MathHelper.PiOver2, MathHelper.PiOver2) * forearm.Mass * 100.0f * CurrentGroundedParams.ArmMoveStrength); } } + // Try to keep the wrist straight + LimbJoint wrist = GetJointBetweenLimbs(foreArmType, hand.type); + if (wrist != null) + { + hand.body.ApplyTorque(MathHelper.Clamp(-wrist.JointAngle, -MathHelper.PiOver2, MathHelper.PiOver2) * hand.Mass * 100f * CurrentGroundedParams.HandMoveStrength); + } } } } @@ -1008,6 +1016,12 @@ namespace Barotrauma speedMultiplier = Math.Min(speedMultiplier, 0.1f); } HandIK(rightHand, handPos + rightHandPos, CurrentSwimParams.ArmMoveStrength * speedMultiplier, CurrentSwimParams.HandMoveStrength * speedMultiplier); + // Try to keep the wrist straight + LimbJoint wrist = GetJointBetweenLimbs(LimbType.RightForearm, LimbType.RightHand); + if (wrist != null) + { + rightHand.body.ApplyTorque(MathHelper.Clamp(-wrist.JointAngle, -MathHelper.PiOver2, MathHelper.PiOver2) * rightHand.Mass * 100f * CurrentSwimParams.HandMoveStrength); + } } if (leftHand != null && !leftHand.Disabled) @@ -1021,6 +1035,12 @@ namespace Barotrauma speedMultiplier = Math.Min(speedMultiplier, 0.1f); } HandIK(leftHand, handPos + leftHandPos, CurrentSwimParams.ArmMoveStrength * speedMultiplier, CurrentSwimParams.HandMoveStrength * speedMultiplier); + // Try to keep the wrist straight + LimbJoint wrist = GetJointBetweenLimbs(LimbType.LeftForearm, LimbType.LeftHand); + if (wrist != null) + { + leftHand.body.ApplyTorque(MathHelper.Clamp(-wrist.JointAngle, -MathHelper.PiOver2, MathHelper.PiOver2) * leftHand.Mass * 100f * CurrentSwimParams.HandMoveStrength); + } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs index dfc049300..4118df6c9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs @@ -665,6 +665,7 @@ namespace Barotrauma public float MaxVitality => CharacterHealth.MaxVitality; public float MaxHealth => MaxVitality; public AIState AIState => AIController is EnemyAIController enemyAI ? enemyAI.State : AIState.Idle; + public bool IsLatched => AIController is EnemyAIController enemyAI && enemyAI.LatchOntoAI != null && enemyAI.LatchOntoAI.IsAttached; public float Bloodloss { @@ -2751,7 +2752,8 @@ namespace Barotrauma } else if (this != Controlled) { - IsRagdolled = IsKeyDown(InputType.Ragdoll); + wasRagdolled = IsRagdolled; + IsRagdolled = selfRagdolled = IsKeyDown(InputType.Ragdoll); } //Keep us ragdolled if we were forced or we're too speedy to unragdoll else if (allowRagdoll && (!IsRagdolled || !tooFastToUnragdoll)) @@ -2765,13 +2767,18 @@ namespace Barotrauma { wasRagdolled = IsRagdolled; IsRagdolled = selfRagdolled = IsKeyDown(InputType.Ragdoll); //Handle this here instead of Control because we can stop being ragdolled ourselves - if (wasRagdolled != IsRagdolled) { ragdollingLockTimer = 0.25f; } + if (wasRagdolled != IsRagdolled) { ragdollingLockTimer = 0.5f; } } } - if (!wasRagdolled && IsRagdolled && selfRagdolled) + if (!wasRagdolled && IsRagdolled) { - CheckTalents(AbilityEffectType.OnSelfRagdoll); + if (selfRagdolled) + { + CheckTalents(AbilityEffectType.OnSelfRagdoll); + } + // currently does not work when you are stunned, like it should + CheckTalents(AbilityEffectType.OnRagdoll); } lowPassMultiplier = MathHelper.Lerp(lowPassMultiplier, 1.0f, 0.1f); @@ -3535,11 +3542,6 @@ namespace Barotrauma if (Removed) { return new AttackResult(); } - if (attacker != null && attacker != this && GameMain.NetworkMember != null && !GameMain.NetworkMember.ServerSettings.AllowFriendlyFire) - { - if (attacker.TeamID == TeamID) { return new AttackResult(); } - } - float closestDistance = 0.0f; foreach (Limb limb in AnimController.Limbs) { @@ -3602,7 +3604,11 @@ namespace Barotrauma if (attacker != null && attacker != this && GameMain.NetworkMember != null && !GameMain.NetworkMember.ServerSettings.AllowFriendlyFire) { - if (attacker.TeamID == TeamID) { return new AttackResult(); } + if (attacker.TeamID == TeamID) + { + afflictions = afflictions.Where(a => !a.Prefab.IsBuff); + if (!afflictions.Any()) { return new AttackResult(); } + } } #if CLIENT @@ -4385,14 +4391,14 @@ namespace Barotrauma info.UnlockedTalents.Add(talentPrefab.Identifier); if (characterTalents.Any(t => t.Prefab == talentPrefab)) { return false; } +#if SERVER + GameMain.NetworkMember.CreateEntityEvent(this, new object[] { NetEntityEvent.Type.UpdateTalents }); +#endif CharacterTalent characterTalent = new CharacterTalent(talentPrefab, this); characterTalent.ActivateTalent(addingFirstTime); characterTalents.Add(characterTalent); characterTalent.AddedThisRound = addingFirstTime; -#if SERVER - GameMain.NetworkMember.CreateEntityEvent(this, new object[] { NetEntityEvent.Type.UpdateTalents }); -#endif if (addingFirstTime) { OnTalentGiven(talentPrefab.Identifier); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs index 741f47897..08c342288 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs @@ -216,6 +216,17 @@ namespace Barotrauma public HashSet UnlockedTalents { get; private set; } = new HashSet(); + /// + /// Endocrine boosters can unlock talents outside the user's talent tree. This method is used to cull them from the selection + /// + public IEnumerable GetUnlockedTalentsInTree() + { + if (!TalentTree.JobTalentTrees.TryGetValue(Job.Prefab.Identifier, out TalentTree talentTree)) { return Enumerable.Empty(); } + + return UnlockedTalents.Where(t => talentTree.TalentIsInTree(t)); + } + + public int AdditionalTalentPoints { get; set; } private Sprite _headSprite; @@ -1220,7 +1231,7 @@ namespace Barotrauma var experienceGainMultiplier = new AbilityValue(1f); if (isMissionExperience) { - Character.CheckTalents(AbilityEffectType.OnGainMissionExperience, experienceGainMultiplier); + Character?.CheckTalents(AbilityEffectType.OnGainMissionExperience, experienceGainMultiplier); } experienceGainMultiplier.Value += Character.GetStatValue(StatTypes.ExperienceGainMultiplier); @@ -1252,7 +1263,7 @@ namespace Barotrauma public int GetAvailableTalentPoints() { // hashset always has at least 1 - return Math.Max(GetTotalTalentPoints() - UnlockedTalents.Count, 0); + return Math.Max(GetTotalTalentPoints() - GetUnlockedTalentsInTree().Count(), 0); } public float GetProgressTowardsNextLevel() @@ -1297,6 +1308,8 @@ namespace Barotrauma partial void OnExperienceChanged(int prevAmount, int newAmount); + partial void OnPermanentStatChanged(StatTypes statType); + public void Rename(string newName) { if (string.IsNullOrEmpty(newName)) { return; } @@ -1362,7 +1375,7 @@ namespace Barotrauma Job.Save(charElement); XElement savedStatElement = new XElement("savedstatvalues"); - foreach (var statValuePair in savedStatValues) + foreach (var statValuePair in SavedStatValues) { foreach (var savedStat in statValuePair.Value) { @@ -1708,26 +1721,42 @@ namespace Barotrauma } // This could maybe be a LookUp instead? - private readonly Dictionary> savedStatValues = new Dictionary>(); + public readonly Dictionary> SavedStatValues = new Dictionary>(); - public void ResetSavedStatValues() + public void ClearSavedStatValues() { - foreach (var savedStatValue in savedStatValues.SelectMany(s => s.Value)) + foreach (StatTypes statType in SavedStatValues.Keys) { - if (savedStatValue.RemoveOnDeath) - { - savedStatValue.StatValue = 0f; - } + OnPermanentStatChanged(statType); } + SavedStatValues.Clear(); } + + public void ClearSavedStatValues(StatTypes statType) + { + SavedStatValues.Remove(statType); + OnPermanentStatChanged(statType); + } + public void ResetSavedStatValue(string statIdentifier) { - savedStatValues.SelectMany(s => s.Value).Where(s => s.StatIdentifier == statIdentifier).ForEach(v => v.StatValue = 0f); + foreach (StatTypes statType in SavedStatValues.Keys) + { + bool changed = false; + foreach (SavedStatValue savedStatValue in SavedStatValues[statType]) + { + if (savedStatValue.StatIdentifier != statIdentifier) { continue; } + if (MathUtils.NearlyEqual(savedStatValue.StatValue, 0.0f)) { continue; } + savedStatValue.StatValue = 0.0f; + changed = true; + } + if (changed) { OnPermanentStatChanged(statType); } + } } public float GetSavedStatValue(StatTypes statType) { - if (savedStatValues.TryGetValue(statType, out var statValues)) + if (SavedStatValues.TryGetValue(statType, out var statValues)) { return statValues.Sum(v => v.StatValue); } @@ -1738,7 +1767,7 @@ namespace Barotrauma } public float GetSavedStatValue(StatTypes statType, string statIdentifier) { - if (savedStatValues.TryGetValue(statType, out var statValues)) + if (SavedStatValues.TryGetValue(statType, out var statValues)) { return statValues.Where(s => s.StatIdentifier.Equals(statIdentifier, StringComparison.OrdinalIgnoreCase)).Sum(v => v.StatValue); } @@ -1750,19 +1779,24 @@ namespace Barotrauma public void ChangeSavedStatValue(StatTypes statType, float value, string statIdentifier, bool removeOnDeath, bool removeAfterRound = false, float maxValue = float.MaxValue, bool setValue = false) { - if (!savedStatValues.ContainsKey(statType)) + if (!SavedStatValues.ContainsKey(statType)) { - savedStatValues.Add(statType, new List()); + SavedStatValues.Add(statType, new List()); } - if (savedStatValues[statType].FirstOrDefault(s => s.StatIdentifier == statIdentifier) is SavedStatValue savedStat) + bool changed = false; + if (SavedStatValues[statType].FirstOrDefault(s => s.StatIdentifier == statIdentifier) is SavedStatValue savedStat) { + float prevValue = savedStat.StatValue; savedStat.StatValue = setValue ? value : MathHelper.Min(savedStat.StatValue + value, maxValue); + changed = !MathUtils.NearlyEqual(savedStat.StatValue, prevValue); } else { - savedStatValues[statType].Add(new SavedStatValue(statIdentifier, MathHelper.Min(value, maxValue), removeOnDeath, removeAfterRound)); + SavedStatValues[statType].Add(new SavedStatValue(statIdentifier, MathHelper.Min(value, maxValue), removeOnDeath, removeAfterRound)); + changed = true; } + if (changed) { OnPermanentStatChanged(statType); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffects.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffects.cs index 9eb8e20e5..a59955d4d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffects.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffects.cs @@ -11,6 +11,7 @@ namespace Barotrauma.Abilities protected readonly List statusEffects; private readonly bool nearbyCharactersAppliesToSelf; + private readonly bool nearbyCharactersAppliesToAllies; private readonly bool applyToSelected; readonly List targets = new List(); @@ -20,6 +21,7 @@ namespace Barotrauma.Abilities statusEffects = CharacterAbilityGroup.ParseStatusEffects(CharacterTalent, abilityElement.GetChildElement("statuseffects")); applyToSelected = abilityElement.GetAttributeBool("applytoselected", false); nearbyCharactersAppliesToSelf = abilityElement.GetAttributeBool("nearbycharactersappliestoself", true); + nearbyCharactersAppliesToAllies = abilityElement.GetAttributeBool("nearbycharactersappliestoallies", true); } protected void ApplyEffectSpecific(Character targetCharacter) @@ -40,6 +42,10 @@ namespace Barotrauma.Abilities { targets.RemoveAll(c => c == Character); } + if (!nearbyCharactersAppliesToAllies) + { + targets.RemoveAll(c => c is Character otherCharacter && HumanAIController.IsFriendly(otherCharacter, Character)); + } statusEffect.SetUser(Character); statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, targetCharacter, targets); } @@ -56,17 +62,20 @@ namespace Barotrauma.Abilities } } protected override void ApplyEffect() - { - ApplyEffectSpecific(Character); - } - - protected override void ApplyEffect(AbilityObject abilityObject) { if (applyToSelected && Character.SelectedCharacter is Character selectedCharacter) { ApplyEffectSpecific(selectedCharacter); } - else if ((abilityObject as IAbilityCharacter)?.Character is Character targetCharacter) + else + { + ApplyEffectSpecific(Character); + } + } + + protected override void ApplyEffect(AbilityObject abilityObject) + { + if ((abilityObject as IAbilityCharacter)?.Character is Character targetCharacter) { ApplyEffectSpecific(targetCharacter); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGivePermanentStat.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGivePermanentStat.cs index ffa6df774..6fb4887b3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGivePermanentStat.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGivePermanentStat.cs @@ -15,7 +15,7 @@ namespace Barotrauma.Abilities private readonly bool setValue; //private readonly float maximumValue; - + public override bool AllowClientSimulation => true; public override bool AppliesEffectOnIntervalUpdate => true; public CharacterAbilityGivePermanentStat(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityResetPermanentStat.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityResetPermanentStat.cs index 0957982df..3716a6fbc 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityResetPermanentStat.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityResetPermanentStat.cs @@ -6,6 +6,7 @@ namespace Barotrauma.Abilities { private readonly string statIdentifier; public override bool AppliesEffectOnIntervalUpdate => true; + public override bool AllowClientSimulation => true; public CharacterAbilityResetPermanentStat(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityTandemFire.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityTandemFire.cs index e3b3ba0c3..75668b0ca 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityTandemFire.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityTandemFire.cs @@ -31,7 +31,7 @@ namespace Barotrauma.Abilities } } - if (closestCharacter.SelectedConstruction == null || !Character.SelectedConstruction.HasTag(tag)) { return; } + if (closestCharacter.SelectedConstruction == null || !closestCharacter.SelectedConstruction.HasTag(tag)) { return; } if (closestDistance < squaredMaxDistance) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/TalentTree.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/TalentTree.cs index 1a96f6f38..02377cba0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/TalentTree.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/TalentTree.cs @@ -66,6 +66,11 @@ namespace Barotrauma } } + public bool TalentIsInTree(string talentIdentifier) + { + return TalentSubTrees.SelectMany(s => s.TalentOptionStages.SelectMany(o => o.Talents.Select(t => t.Identifier))).Any(c => c == talentIdentifier); + } + public static void LoadFromFile(ContentFile file) { DebugConsole.Log("Loading talent tree: " + file.Path); diff --git a/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs b/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs index bc9d3ea89..859d6f2fd 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs @@ -602,7 +602,7 @@ namespace Barotrauma } })); - commands.Add(new Command("giveaffliction", "giveaffliction [affliction name] [affliction strength] [character name] [limb type]: Add an affliction to a character. If the name parameter is omitted, the affliction is added to the controlled character.", (string[] args) => + commands.Add(new Command("giveaffliction", "giveaffliction [affliction name] [affliction strength] [character name] [limb type] [use relative strength]: Add an affliction to a character. If the name parameter is omitted, the affliction is added to the controlled character.", (string[] args) => { if (args.Length < 2) { return; } @@ -622,14 +622,12 @@ namespace Barotrauma } bool relativeStrength = false; - if (args.Length > 2) + if (args.Length > 4) { - bool.TryParse(args[2], out relativeStrength); + bool.TryParse(args[4], out relativeStrength); } - Character targetCharacter = (relativeStrength || args.Length <= 2) ? Character.Controlled : FindMatchingCharacter(new string[] { args[2] }); - - + Character targetCharacter = args.Length <= 2 ? Character.Controlled : FindMatchingCharacter(new string[] { args[2] }); if (targetCharacter != null) { Limb targetLimb = targetCharacter.AnimController.MainLimb; @@ -1889,9 +1887,9 @@ namespace Barotrauma if (!IsCommandPermitted(splitCommand[0].ToLowerInvariant(), GameMain.Client)) { #if DEBUG - AddWarning("You're not permitted to use the command \"{matchingCommand.Name}\". Executing the command anyway because this is a debug build."); + AddWarning($"You're not permitted to use the command \"{splitCommand[0].ToLowerInvariant()}\". Executing the command anyway because this is a debug build."); #else - ThrowError("You're not permitted to use the command \"" + splitCommand[0].ToLowerInvariant() + "\"!"); + ThrowError($"You're not permitted to use the command \"{splitCommand[0].ToLowerInvariant()}\"!"); return; #endif } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Enums.cs b/Barotrauma/BarotraumaShared/SharedSource/Enums.cs index b6a3d3d83..3ac2e2dbf 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Enums.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Enums.cs @@ -47,6 +47,7 @@ OnReduceAffliction, OnAddDamageAffliction, OnSelfRagdoll, + OnRagdoll, OnRoundEnd, OnAnyMissionCompleted, OnAllMissionsCompleted, diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs index 8fee80718..0e0e9a77c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs @@ -179,7 +179,7 @@ namespace Barotrauma if (eventSet == null) { return; } if (eventSet.OncePerOutpost) { - foreach (EventPrefab ep in eventSet.EventPrefabs.Select(e => e.prefab)) + foreach (EventPrefab ep in eventSet.EventPrefabs.SelectMany(e => e.Prefabs)) { if (!level.LevelData.NonRepeatableEvents.Contains(ep)) { @@ -434,23 +434,31 @@ namespace Barotrauma } } - var suitablePrefabs = eventSet.EventPrefabs.FindAll(e => - string.IsNullOrEmpty(e.prefab.BiomeIdentifier) || - e.prefab.BiomeIdentifier.Equals(level.LevelData?.Biome?.Identifier, StringComparison.OrdinalIgnoreCase)); + bool isPrefabSuitable(EventPrefab p) + => string.IsNullOrEmpty(p.BiomeIdentifier) || + p.BiomeIdentifier.Equals(level.LevelData?.Biome?.Identifier, StringComparison.OrdinalIgnoreCase); + + var suitablePrefabSubsets = eventSet.EventPrefabs + .FindAll(p => p.Prefabs.Any(isPrefabSuitable)); for (int i = 0; i < applyCount; i++) { if (eventSet.ChooseRandom) { - if (suitablePrefabs.Count > 0) + if (suitablePrefabSubsets.Count > 0) { - var unusedEvents = new List<(EventPrefab prefab, float commonness, float probability)>(suitablePrefabs); + var unusedEvents = suitablePrefabSubsets.ToList(); for (int j = 0; j < eventSet.EventCount; j++) { - if (unusedEvents.All(e => CalculateCommonness(e.prefab, e.commonness) <= 0.0f)) { break; } - (EventPrefab eventPrefab, float commonness, float probability) = ToolBox.SelectWeightedRandom(unusedEvents, unusedEvents.Select(e => CalculateCommonness(e.prefab, e.commonness)).ToList(), rand); - if (eventPrefab != null && rand.NextDouble() <= probability) + if (unusedEvents.All(e => e.Prefabs.All(p => CalculateCommonness(p, e.Commonness) <= 0.0f))) { break; } + EventSet.SubEventPrefab subEventPrefab = ToolBox.SelectWeightedRandom(unusedEvents, unusedEvents.Select(e => e.Prefabs.Max(p => CalculateCommonness(p, e.Commonness))).ToList(), rand); + (IEnumerable eventPrefabs, float commonness, float probability) = subEventPrefab; + if (eventPrefabs != null && rand.NextDouble() <= probability) { + var finalPrefabs = eventPrefabs.Where(isPrefabSuitable).ToArray(); + var finalPrefabCommonnesses = finalPrefabs.Select(p => p.Commonness).ToArray(); + var eventPrefab = ToolBox.SelectWeightedRandom(finalPrefabs, finalPrefabCommonnesses, rand); + var newEvent = eventPrefab.CreateInstance(); if (newEvent == null) { continue; } newEvent.Init(true); @@ -465,7 +473,7 @@ namespace Barotrauma selectedEvents.Add(eventSet, new List()); } selectedEvents[eventSet].Add(newEvent); - unusedEvents.Remove((eventPrefab, commonness, probability)); + unusedEvents.Remove(subEventPrefab); } } } @@ -480,9 +488,13 @@ namespace Barotrauma } else { - foreach ((EventPrefab eventPrefab, float commonness, float probability) in suitablePrefabs) + foreach ((IEnumerable eventPrefabs, float commonness, float probability) in suitablePrefabSubsets) { if (rand.NextDouble() > probability) { continue; } + + var finalPrefabs = eventPrefabs.Where(isPrefabSuitable).ToArray(); + var finalPrefabCommonnesses = finalPrefabs.Select(p => p.Commonness).ToArray(); + var eventPrefab = ToolBox.SelectWeightedRandom(finalPrefabs, finalPrefabCommonnesses, rand); var newEvent = eventPrefab.CreateInstance(); if (newEvent == null) { continue; } newEvent.Init(true); @@ -866,7 +878,7 @@ namespace Barotrauma private float CalculateDistanceTraveled() { - if (level == null) { return 0.0f; } + if (level == null || pathFinder == null) { return 0.0f; } var refEntity = GetRefEntity(); if (refEntity == null) { return 0.0f; } Vector2 target = ConvertUnits.ToSimUnits(level.EndPosition); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventPrefab.cs index b7632a54e..3938f6db0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventPrefab.cs @@ -40,7 +40,7 @@ namespace Barotrauma BiomeIdentifier = ConfigElement.GetAttributeString("biome", string.Empty); Commonness = element.GetAttributeFloat("commonness", 1.0f); Probability = Math.Clamp(element.GetAttributeFloat(1.0f, "probability", "spawnprobability"), 0, 1); - TriggerEventCooldown = element.GetAttributeBool("triggereventcooldown", true); + TriggerEventCooldown = element.GetAttributeBool("triggereventcooldown", EventType != typeof(ScriptedEvent)); UnlockPathEvent = element.GetAttributeBool("unlockpathevent", false); UnlockPathTooltip = element.GetAttributeString("unlockpathtooltip", "lockedpathtooltip"); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventSet.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventSet.cs index 54d748b8e..7431b31b3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventSet.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventSet.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using System.Xml.Linq; using Barotrauma.Extensions; @@ -48,10 +49,10 @@ namespace Barotrauma List eventPrefabs = new List(PrefabList); foreach (var eventSet in List) { - eventPrefabs.AddRange(eventSet.EventPrefabs.Select(ep => ep.prefab)); + eventPrefabs.AddRange(eventSet.EventPrefabs.SelectMany(ep => ep.Prefabs)); foreach (var childSet in eventSet.ChildSets) { - eventPrefabs.AddRange(childSet.EventPrefabs.Select(ep => ep.prefab)); + eventPrefabs.AddRange(childSet.EventPrefabs.SelectMany(ep => ep.Prefabs)); } } return eventPrefabs; @@ -98,7 +99,48 @@ namespace Barotrauma public readonly Dictionary Commonness; - public readonly List<(EventPrefab prefab, float commonness, float probability)> EventPrefabs; + public struct SubEventPrefab + { + public SubEventPrefab(string debugIdentifier, string[] prefabIdentifiers, float? commonness, float? probability) + { + EventPrefab tryFindPrefab(string id) + { + var prefab = PrefabList.Find(p => p.Identifier.Equals(id, StringComparison.OrdinalIgnoreCase)); + if (prefab is null) + { + DebugConsole.ThrowError($"Error in event set \"{debugIdentifier}\" - could not find the event prefab \"{id}\"."); + } + return prefab; + } + + this.Prefabs = prefabIdentifiers + .Select(tryFindPrefab) + .Where(p => p != null) + .ToImmutableArray(); + this.Commonness = commonness ?? this.Prefabs.Select(p => p.Commonness).Max(); + this.Probability = probability ?? this.Prefabs.Select(p => p.Probability).Max(); + } + + public SubEventPrefab(EventPrefab prefab, float commonness, float probability) + { + Prefabs = prefab.ToEnumerable().ToImmutableArray(); + Commonness = commonness; + Probability = probability; + } + + public readonly ImmutableArray Prefabs; + public readonly float Commonness; + public readonly float Probability; + + public void Deconstruct(out IEnumerable prefabs, out float commonness, out float probability) + { + prefabs = Prefabs; + commonness = Commonness; + probability = Probability; + } + } + + public readonly List EventPrefabs; public readonly List ChildSets; @@ -112,7 +154,7 @@ namespace Barotrauma { DebugIdentifier = element.GetAttributeString("identifier", null) ?? debugIdentifier; Commonness = new Dictionary(); - EventPrefabs = new List<(EventPrefab prefab, float commonness, float probability)>(); + EventPrefabs = new List(); ChildSets = new List(); BiomeIdentifier = element.GetAttributeString("biome", string.Empty); @@ -178,23 +220,20 @@ namespace Barotrauma //an element with just an identifier = reference to an event prefab if (!subElement.HasElements && subElement.Attributes().First().Name.ToString().Equals("identifier", StringComparison.OrdinalIgnoreCase)) { - string identifier = subElement.GetAttributeString("identifier", ""); - var prefab = PrefabList.Find(p => p.Identifier.Equals(identifier, StringComparison.OrdinalIgnoreCase)); - if (prefab == null) - { - DebugConsole.ThrowError($"Error in event set \"{debugIdentifier}\" - could not find the event prefab \"{identifier}\"."); - } - else - { - float commonness = subElement.GetAttributeFloat("commonness", prefab.Commonness); - float probability = subElement.GetAttributeFloat("probability", prefab.Probability); - EventPrefabs.Add((prefab, commonness, probability)); - } + string[] identifiers = subElement.GetAttributeStringArray("identifier", Array.Empty()); + + float commonness = subElement.GetAttributeFloat("commonness", -1f); + float probability = subElement.GetAttributeFloat("probability", -1f); + EventPrefabs.Add(new SubEventPrefab( + debugIdentifier, + identifiers, + commonness>=0f ? commonness : (float?)null, + probability>=0f ? probability : (float?)null)); } else { var prefab = new EventPrefab(subElement); - EventPrefabs.Add((prefab, prefab.Commonness, prefab.Probability)); + EventPrefabs.Add(new SubEventPrefab(prefab, prefab.Commonness, prefab.Probability)); } break; } @@ -346,13 +385,13 @@ namespace Barotrauma { if (thisSet.ChooseRandom) { - var unusedEvents = new List<(EventPrefab prefab, float commonness, float probability)>(thisSet.EventPrefabs); + var unusedEvents = thisSet.EventPrefabs.ToList(); for (int i = 0; i < thisSet.EventCount; i++) { - var eventPrefab = ToolBox.SelectWeightedRandom(unusedEvents, unusedEvents.Select(e => e.commonness).ToList(), Rand.RandSync.Unsynced); - if (eventPrefab.prefab != null) + var eventPrefab = ToolBox.SelectWeightedRandom(unusedEvents, unusedEvents.Select(e => e.Commonness).ToList(), Rand.RandSync.Unsynced); + if (eventPrefab.Prefabs.Any(p => p != null)) { - AddEvent(stats, eventPrefab.prefab); + AddEvents(stats, eventPrefab.Prefabs); unusedEvents.Remove(eventPrefab); } } @@ -361,7 +400,7 @@ namespace Barotrauma { foreach (var eventPrefab in thisSet.EventPrefabs) { - AddEvent(stats, eventPrefab.prefab); + AddEvents(stats, eventPrefab.Prefabs); } } foreach (var childSet in thisSet.ChildSets) @@ -370,6 +409,9 @@ namespace Barotrauma } } + static void AddEvents(EventDebugStats stats, IEnumerable eventPrefabs) + => eventPrefabs.ForEach(p => AddEvent(stats, p)); + static void AddEvent(EventDebugStats stats, EventPrefab eventPrefab) { if (eventPrefab.EventType == typeof(MonsterEvent)) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs index 2c8f53636..a0c9c2c07 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs @@ -363,7 +363,7 @@ namespace Barotrauma #if CLIENT foreach (Character character in crewCharacters) { - character.Info.GiveExperience(experienceGain, isMissionExperience: true); + character.Info?.GiveExperience(experienceGain, isMissionExperience: true); } #else foreach (Barotrauma.Networking.Client c in GameMain.Server.ConnectedClients) diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/AutoItemPlacer.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/AutoItemPlacer.cs index aa8c2eee6..8f0c37464 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/AutoItemPlacer.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/AutoItemPlacer.cs @@ -218,12 +218,12 @@ namespace Barotrauma return validContainers; } - private static readonly float[] qualityCommonnesses = new float[Quality.MaxQuality + 1] + private static readonly (int quality, float commonness)[] qualityCommonnesses = new (int quality, float commonness)[Quality.MaxQuality + 1] { - 0.85f, - 0.125f, - 0.0225f, - 0.0025f, + (0, 0.85f), + (1, 0.125f), + (2, 0.0225f), + (3, 0.0025f), }; private static List SpawnItem(ItemPrefab itemPrefab, List containers, KeyValuePair validContainer, float difficultyModifier) @@ -243,20 +243,15 @@ namespace Barotrauma containers.Remove(validContainer.Key); break; } - if (!validContainer.Key.Inventory.CanBePut(itemPrefab)) { break; } - - int quality = 0; - float qualityCommmonnessSum = qualityCommonnesses.Sum(); - float randomNumber = Rand.Range(0f, qualityCommmonnessSum, Rand.RandSync.Server); - for (int k = qualityCommonnesses.Length - 1; k >= 0; k--) - { - if (randomNumber < qualityCommonnesses[k]) - { - quality = k; - break; - } - } + var existingItem = validContainer.Key.Inventory.AllItems.FirstOrDefault(it => it.prefab == itemPrefab); + int quality = + existingItem?.Quality ?? + ToolBox.SelectWeightedRandom( + qualityCommonnesses.Select(q => q.quality).ToList(), + qualityCommonnesses.Select(q => q.commonness).ToList(), + Rand.RandSync.Server); + if (!validContainer.Key.Inventory.CanBePut(itemPrefab, quality: quality)) { break; } var item = new Item(itemPrefab, validContainer.Key.Item.Position, validContainer.Key.Item.Submarine) { SpawnedInOutpost = validContainer.Key.Item.SpawnedInOutpost, diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CharacterCampaignData.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CharacterCampaignData.cs index 163255fdd..0df3b6f0c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CharacterCampaignData.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CharacterCampaignData.cs @@ -1,6 +1,4 @@ -using Barotrauma.Networking; -using System.Globalization; -using System.Xml.Linq; +using System.Xml.Linq; namespace Barotrauma { @@ -29,65 +27,6 @@ namespace Barotrauma private XElement healthData; public XElement OrderData { get; private set; } - partial void InitProjSpecific(Client client); - public CharacterCampaignData(Client client, bool giveRespawnPenaltyAffliction = false) - { - Name = client.Name; - InitProjSpecific(client); - - healthData = new XElement("health"); - client.Character?.CharacterHealth?.Save(healthData); - if (giveRespawnPenaltyAffliction) - { - var respawnPenaltyAffliction = RespawnManager.GetRespawnPenaltyAffliction(); - healthData.Add(new XElement("Affliction", - new XAttribute("identifier", respawnPenaltyAffliction.Identifier), - new XAttribute("strength", respawnPenaltyAffliction.Strength.ToString("G", CultureInfo.InvariantCulture)))); - } - if (client.Character?.Inventory != null) - { - itemData = new XElement("inventory"); - Character.SaveInventory(client.Character.Inventory, itemData); - } - OrderData = new XElement("orders"); - if (client.Character != null) - { - CharacterInfo.SaveOrderData(client.Character.Info, OrderData); - } - } - - public CharacterCampaignData(XElement element) - { - Name = element.GetAttributeString("name", "Unnamed"); - ClientEndPoint = element.GetAttributeString("endpoint", null) ?? element.GetAttributeString("ip", ""); - string steamID = element.GetAttributeString("steamid", ""); - if (!string.IsNullOrEmpty(steamID)) - { - ulong.TryParse(steamID, out ulong parsedID); - SteamID = parsedID; - } - - foreach (XElement subElement in element.Elements()) - { - switch (subElement.Name.ToString().ToLowerInvariant()) - { - case "character": - case "characterinfo": - CharacterInfo = new CharacterInfo(subElement); - break; - case "inventory": - itemData = subElement; - break; - case "health": - healthData = subElement; - break; - case "orders": - OrderData = subElement; - break; - } - } - } - public void Refresh(Character character) { healthData = new XElement("health"); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/CharacterInventory.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/CharacterInventory.cs index 22f679994..0aa9fc4fc 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/CharacterInventory.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/CharacterInventory.cs @@ -141,10 +141,10 @@ namespace Barotrauma (SlotTypes[i] == InvSlotType.Any || slots[i].ItemCount < 1); } - public override bool CanBePutInSlot(ItemPrefab itemPrefab, int i, float? condition) + public override bool CanBePutInSlot(ItemPrefab itemPrefab, int i, float? condition, int? quality = null) { return - base.CanBePutInSlot(itemPrefab, i, condition) && + base.CanBePutInSlot(itemPrefab, i, condition, quality) && (SlotTypes[i] == InvSlotType.Any || slots[i].ItemCount < 1); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/EntitySpawnerComponent.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/EntitySpawnerComponent.cs index ec5f6b078..e73ea86f1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/EntitySpawnerComponent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/EntitySpawnerComponent.cs @@ -268,7 +268,7 @@ namespace Barotrauma.Items.Components { string[] allSpecies = SpeciesName.Split(','); string species = allSpecies.GetRandom().Trim(); - Entity.Spawner.AddToSpawnQueue(species, pos); + Entity.Spawner?.AddToSpawnQueue(species, pos); } else if (!string.IsNullOrWhiteSpace(ItemIdentifier)) { @@ -282,7 +282,7 @@ namespace Barotrauma.Items.Components pos -= sub.Position; } - Entity.Spawner.AddToSpawnQueue(prefab, pos, item.Submarine); + Entity.Spawner?.AddToSpawnQueue(prefab, pos, item.Submarine); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Holdable.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Holdable.cs index b2c819360..1449bf5cf 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Holdable.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Holdable.cs @@ -642,10 +642,8 @@ namespace Barotrauma.Items.Components item.Drop(character); item.SetTransform(ConvertUnits.ToSimUnits(GetAttachPosition(character)), 0.0f, findNewHull: false); } + AttachToWall(); } - - AttachToWall(); - return true; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs index dc1ea9383..27b8542ff 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs @@ -291,13 +291,16 @@ namespace Barotrauma.Items.Components int index = Inventory.FindIndex(containedItem); if (index >= 0 && index < slotRestrictions.Length) { - RelatedItem ri = slotRestrictions[index].ContainableItems?.Find(ci => ci.MatchesItem(containedItem)); - if (ri != null) + if (slotRestrictions[index].ContainableItems != null) { activeContainedItems.RemoveAll(i => i.Item == containedItem); - foreach (StatusEffect effect in ri.statusEffects) + foreach (var containableItem in slotRestrictions[index].ContainableItems) { - activeContainedItems.Add(new ActiveContainedItem(containedItem, effect, ri.ExcludeBroken)); + if (!containableItem.MatchesItem(containedItem)) { continue; } + foreach (StatusEffect effect in containableItem.statusEffects) + { + activeContainedItems.Add(new ActiveContainedItem(containedItem, effect, containableItem.ExcludeBroken)); + } } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/OxygenGenerator.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/OxygenGenerator.cs index 60c93f2a9..d76ec970c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/OxygenGenerator.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/OxygenGenerator.cs @@ -34,6 +34,8 @@ namespace Barotrauma.Items.Components public OxygenGenerator(Item item, XElement element) : base(item, element) { + //randomize update timer so all oxygen generators don't update at the same time + ventUpdateTimer = Rand.Range(0.0f, VentUpdateInterval); IsActive = true; } @@ -78,6 +80,7 @@ namespace Barotrauma.Items.Components private void GetVents() { + totalHullVolume = 0.0f; ventList ??= new List<(Vent vent, float hullVolume)>(); ventList.Clear(); foreach (MapEntity entity in item.linkedTo) @@ -87,13 +90,14 @@ namespace Barotrauma.Items.Components Vent vent = linkedItem.GetComponent(); if (vent?.Item.CurrentHull == null) { continue; } + totalHullVolume += vent.Item.CurrentHull.Volume; ventList.Add((vent, vent.Item.CurrentHull.Volume)); } for (int i = 0; i < ventList.Count; i++) { Vent vent = ventList[i].vent; - foreach (Hull connectedHull in vent.Item.CurrentHull.GetConnectedHulls(includingThis: false, searchDepth: 5, ignoreClosedGaps: true)) + foreach (Hull connectedHull in vent.Item.CurrentHull.GetConnectedHulls(includingThis: false, searchDepth: 3, ignoreClosedGaps: true)) { //another vent in the connected hull -> don't add it to this vent's total hull volume if (ventList.Any(v => v.vent != vent && v.vent.Item.CurrentHull == connectedHull)) { continue; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs index e9f28b882..c222b6a31 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs @@ -57,7 +57,7 @@ namespace Barotrauma return true; } - public bool CanBePut(ItemPrefab itemPrefab, float? condition = null) + public bool CanBePut(ItemPrefab itemPrefab, float? condition = null, int? quality = null) { if (itemPrefab == null) { return false; } if (items.Count > 0) @@ -82,6 +82,11 @@ namespace Barotrauma if (items.Any(it => !it.IsFullCondition)) { return false; } } + if (quality.HasValue) + { + if (items[0].Quality != quality.Value) { return false; } + } + if (items[0].Prefab.Identifier != itemPrefab.Identifier || items.Count + 1 > itemPrefab.MaxStackSize) { @@ -172,6 +177,11 @@ namespace Barotrauma items.Clear(); } + public void RemoveWhere(Func predicate) + { + items.RemoveAll(it => predicate(it)); + } + public bool Any() { return items.Count > 0; @@ -422,16 +432,16 @@ namespace Barotrauma return slots[i].CanBePut(item, ignoreCondition); } - public bool CanBePut(ItemPrefab itemPrefab, float? condition = null) + public bool CanBePut(ItemPrefab itemPrefab, float? condition = null, int? quality = null) { for (int i = 0; i < capacity; i++) { - if (CanBePutInSlot(itemPrefab, i, condition)) { return true; } + if (CanBePutInSlot(itemPrefab, i, condition, quality)) { return true; } } return false; } - public virtual bool CanBePutInSlot(ItemPrefab itemPrefab, int i, float? condition = null) + public virtual bool CanBePutInSlot(ItemPrefab itemPrefab, int i, float? condition = null, int? quality = null) { if (i < 0 || i >= slots.Length) { return false; } return slots[i].CanBePut(itemPrefab, condition); @@ -503,7 +513,7 @@ namespace Barotrauma { return true; } - return + return TrySwapping(i, item, user, createNetworkEvent, swapWholeStack: true) || TrySwapping(i, item, user, createNetworkEvent, swapWholeStack: false); } @@ -776,11 +786,17 @@ namespace Barotrauma { for (int j = 0; j < capacity; j++) { - if (slots[j].Contains(item)) { slots[j].RemoveAllItems(); }; + if (slots[j].Contains(item)) + { + slots[j].RemoveWhere(it => existingItems.Contains(it) || stackedItems.Contains(it)); + } } for (int j = 0; j < otherInventory.capacity; j++) { - if (otherInventory.slots[j].Contains(existingItems.FirstOrDefault())) { otherInventory.slots[j].RemoveAllItems(); } + if (otherInventory.slots[j].Contains(existingItems.FirstOrDefault())) + { + otherInventory.slots[j].RemoveWhere(it => existingItems.Contains(it) || stackedItems.Contains(it)); + } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/ItemInventory.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/ItemInventory.cs index 954ab96af..cf1870762 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/ItemInventory.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/ItemInventory.cs @@ -51,11 +51,11 @@ namespace Barotrauma return item != null && slots[i].CanBePut(item, ignoreCondition) && slots[i].ItemCount < container.GetMaxStackSize(i); } - public override bool CanBePutInSlot(ItemPrefab itemPrefab, int i, float? condition) + public override bool CanBePutInSlot(ItemPrefab itemPrefab, int i, float? condition, int? quality = null) { if (i < 0 || i >= slots.Length) { return false; } if (!container.CanBeContained(itemPrefab, i)) { return false; } - return itemPrefab != null && slots[i].CanBePut(itemPrefab, condition) && slots[i].ItemCount < container.GetMaxStackSize(i); + return itemPrefab != null && slots[i].CanBePut(itemPrefab, condition, quality) && slots[i].ItemCount < container.GetMaxStackSize(i); } public override int HowManyCanBePut(ItemPrefab itemPrefab, int i, float? condition) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs index c0e618e9d..eefafeeab 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs @@ -864,6 +864,16 @@ namespace Barotrauma foreach (AbyssIsland island in AbyssIslands) { island.Area = new Rectangle(borders.Width - island.Area.Right, island.Area.Y, island.Area.Width, island.Area.Height); + foreach (var cell in island.Cells) + { + if (!mirroredSites.Contains(cell.Site)) + { + if (cell.Site.Coord.X % GridCellSize < 1.0f && + cell.Site.Coord.X % GridCellSize >= 0.0f) { cell.Site.Coord.X += 1.0f; } + cell.Site.Coord.X = borders.Width - cell.Site.Coord.X; + mirroredSites.Add(cell.Site); + } + } } for (int i = 0; i < ruinPositions.Count; i++) @@ -1972,7 +1982,7 @@ namespace Barotrauma ConvertUnits.ToSimUnits(entranceWayPoint.WorldPosition), collisionCategory: Physics.CollisionLevel | Physics.CollisionWall) == null; }); if (closestWp == null) { continue; } - entranceWayPoint.ConnectTo(closestWp); + ConnectWaypoints(entranceWayPoint, closestWp, outSideWaypointInterval); } //create a waypoint path from the ruin to the closest tunnel diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs index 5ccf84245..2907db5ce 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs @@ -1023,7 +1023,12 @@ namespace Barotrauma #if CLIENT if (playSound && damageAmount > 0) { - SoundPlayer.PlayDamageSound(attack.StructureSoundType, damageAmount, worldPosition, tags: Tags); + string damageSound = Prefab.DamageSound; + if (string.IsNullOrWhiteSpace(damageSound)) + { + damageSound = attack.StructureSoundType; + } + SoundPlayer.PlayDamageSound(damageSound, damageAmount, worldPosition, tags: Tags); } #endif diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/StructurePrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/StructurePrefab.cs index 19189ff95..805bc42b8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/StructurePrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/StructurePrefab.cs @@ -167,6 +167,9 @@ namespace Barotrauma private set { size = value; } } + [Serialize("", true)] + public string DamageSound { get; private set; } + public Vector2 ScaledSize => size * Scale; protected Vector2 textureScale = Vector2.One; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/NetEntityEvent/NetEntityEvent.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/NetEntityEvent/NetEntityEvent.cs index fa35ae9aa..8396c48a3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/NetEntityEvent/NetEntityEvent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/NetEntityEvent/NetEntityEvent.cs @@ -26,6 +26,7 @@ namespace Barotrauma.Networking UpdateExperience, UpdateTalents, UpdateMoney, + UpdatePermanentStats, } public readonly Entity Entity; diff --git a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs index 0100a166f..6bb5586c1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs @@ -1284,68 +1284,66 @@ namespace Barotrauma } } - int i = 0; - foreach (int giveExperience in giveExperiences) + if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient) { - Character targetCharacter = CharacterFromTarget(target); - if (targetCharacter != null && !targetCharacter.Removed) - { - targetCharacter?.Info?.GiveExperience(giveExperience); - i++; - } - } + // these effects do not need to be run clientside, as they are replicated from server to clients anyway - if (giveSkills.Any()) - { - foreach ((string skillIdentifier, float amount) in giveSkills) + foreach (int giveExperience in giveExperiences) { Character targetCharacter = CharacterFromTarget(target); if (targetCharacter != null && !targetCharacter.Removed) { - if (skillIdentifier?.ToLowerInvariant() == "randomskill") + targetCharacter?.Info?.GiveExperience(giveExperience); + } + } + + if (giveSkills.Any()) + { + foreach ((string skillIdentifier, float amount) in giveSkills) + { + Character targetCharacter = CharacterFromTarget(target); + if (targetCharacter != null && !targetCharacter.Removed) { - if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) + if (skillIdentifier?.ToLowerInvariant() == "randomskill") { - // don't let clients simulate random skill gain - continue; + targetCharacter.Info?.IncreaseSkillLevel(GetRandomSkill(), amount); + + string GetRandomSkill() + { + return targetCharacter.Info?.Job?.Skills.Select(s => s.Identifier).GetRandom(); + } } - targetCharacter.Info?.IncreaseSkillLevel(GetRandomSkill(), amount); - - string GetRandomSkill() + else { - return targetCharacter.Info?.Job?.Skills.Select(s => s.Identifier).GetRandom(); + targetCharacter.Info?.IncreaseSkillLevel(skillIdentifier?.ToLowerInvariant(), amount); } } - else - { - targetCharacter.Info?.IncreaseSkillLevel(skillIdentifier?.ToLowerInvariant(), amount); - } } } - } - - if (giveTalentInfos.Any() && (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)) - { - Character targetCharacter = CharacterFromTarget(target); - if (targetCharacter?.Info == null) { continue; } - if (!TalentTree.JobTalentTrees.TryGetValue(targetCharacter.Info.Job.Prefab.Identifier, out TalentTree talentTree)) { continue; } - // for the sake of technical simplicity, for now do not allow talents to be given if the character could unlock them in their talent tree as well - IEnumerable disallowedTalents = talentTree.TalentSubTrees.SelectMany(s => s.TalentOptionStages.SelectMany(o => o.Talents.Select(t => t.Identifier))); - foreach (GiveTalentInfo giveTalentInfo in giveTalentInfos) - { - IEnumerable viableTalents = giveTalentInfo.TalentIdentifiers.Where(s => !targetCharacter.Info.UnlockedTalents.Contains(s) && !disallowedTalents.Contains(s)); - if (viableTalents.None()) { continue; } + if (giveTalentInfos.Any()) + { + Character targetCharacter = CharacterFromTarget(target); + if (targetCharacter?.Info == null) { continue; } + if (!TalentTree.JobTalentTrees.TryGetValue(targetCharacter.Info.Job.Prefab.Identifier, out TalentTree talentTree)) { continue; } + // for the sake of technical simplicity, for now do not allow talents to be given if the character could unlock them in their talent tree as well + IEnumerable disallowedTalents = talentTree.TalentSubTrees.SelectMany(s => s.TalentOptionStages.SelectMany(o => o.Talents.Select(t => t.Identifier))); - if (giveTalentInfo.GiveRandom) + foreach (GiveTalentInfo giveTalentInfo in giveTalentInfos) { - targetCharacter.GiveTalent(viableTalents.GetRandom(), true); - } - else - { - foreach (string talent in viableTalents) + IEnumerable viableTalents = giveTalentInfo.TalentIdentifiers.Where(s => !targetCharacter.Info.UnlockedTalents.Contains(s) && !disallowedTalents.Contains(s)); + if (viableTalents.None()) { continue; } + + if (giveTalentInfo.GiveRandom) { - targetCharacter.GiveTalent(talent, true); + targetCharacter.GiveTalent(viableTalents.GetRandom(), true); + } + else + { + foreach (string talent in viableTalents) + { + targetCharacter.GiveTalent(talent, true); + } } } } diff --git a/Barotrauma/BarotraumaShared/changelog.txt b/Barotrauma/BarotraumaShared/changelog.txt index ea0bd0231..9ef824a3c 100644 --- a/Barotrauma/BarotraumaShared/changelog.txt +++ b/Barotrauma/BarotraumaShared/changelog.txt @@ -1,3 +1,38 @@ +--------------------------------------------------------------------------------------------------------- +v0.1500.8.0 +--------------------------------------------------------------------------------------------------------- + +Additions and changes: +- More improvements and fixes to the alien ruins and the new fractal guardians. +- Improvements to character animations and sprites. +- Made ruin scan/clear missions available in outposts. +- Talent adjustments and fixes. +- Adjustments to outpost distribution: natural formations greatly reduced in the 1st zone, cities slightly reduced in the 1st zone, outposts (including specialized ones) increased in the 3rd and 4th zone. +- Made magnesium a little more common in stores and wrecks. +- Temporarily disabled magnesium exploding in water to prevent issues with talents related to it. +- Added an additonal ambience track for the ruins. +- New sounds for alien ruins and the guardians. +- Portable pumps turn off automatically when not attached to a wall. + +Fixes: +- Fixed outpost events always unlocking the same escort mission. +- Fixed server occasionally failing to end the round and spamming the clients with XP notifications. Happened when the server was trying to give experience for the completed missions and there were clients in the server whose character had been removed (unstable only). +- Fixed shotgun shells not having a fabrication recipe (unstable only). +- Fixed thermal goggles being sold in outposts (unstable only). +- Fixes to ruin waypoint generation (unstable only). +- Fixes to pathfinding outside ruins (unstable only). +- Fixed oxygen generator output constantly decreasing due to the changes in the previous build (unstable only). +- Fixed long item names being unreadable on the fabricator UI. +- The hints about flooded rooms and ballast flora aren't shown in ruins, wrecks or enemy subs. +- Fixed "setclientcharacter" command crashing the server if the specified character is not found. +- Fixed autofilling subs with supplies sometimes causing high-quality items to appear on the floor near containers (unstable only). +- Fixed guardian spears being fabricatable (unstable only). +- Fixed "stowaway" event triggering an event cooldown, preventing monsters from spawning at the beginning of the round. +- Fixed clients (excluding the host) always considering friendly fire to be disabled, leading to minor cosmetic desyncs when a player applies afflictions on another one (i.e. there was a brief delay before the afflictions update client-side). +- Fixed inability to apply buffs on the crew when friendly fire is disabled. +- Fixed ItemContainers only applying the StatusEffects from the first matching Containable, even if there's multiple. Prevented the artifact-specific effects of artifact holder from executing. +- Fixed "giveaffliction" command's limbtype argument not working in multiplayer. + --------------------------------------------------------------------------------------------------------- v0.1500.7.0 --------------------------------------------------------------------------------------------------------- @@ -7,7 +42,7 @@ Additions and changes: - Added a colored border to high-quality items' inventory slots. - Changed the look of the skill/xp notifications to accommodate the larger numbers of notifications you can get from talents and skillbooks. - Added a fabricator and deconstructor to Azimuth and slightly lowered its maximum speed. -- Increased Azimuth's battery out relay max power- +- Increased Azimuth's battery out relay max power. - Field Medic now only triggers on missions. - Reduced gravity sphere's force to make it possible to escape it with a diving suit on. - Diving suit and human ragdoll damagemodifier changes: the suits now offer less protection, but humans have a bit more natural protection towards physical damage types.