From 5a6bbcc79e2391cfc6a91a7f9aafd4a244c9ad45 Mon Sep 17 00:00:00 2001 From: Markus Isberg <3e849f2e5c@pm.me> Date: Fri, 17 Sep 2021 22:47:21 +0900 Subject: [PATCH] =?UTF-8?q?0.1500.3.0=20(=F0=9F=97=BF=20edition)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Characters/Health/CharacterHealth.cs | 27 +- .../ClientSource/DebugConsole.cs | 42 ++- .../ClientSource/GUI/GUIStyle.cs | 15 + .../ClientSource/GUI/LoadingScreen.cs | 41 +-- .../ClientSource/GUI/TabMenu.cs | 285 ++++++++++++------ .../BarotraumaClient/ClientSource/GameMain.cs | 24 +- .../ClientSource/GameSession/CrewManager.cs | 16 +- .../ClientSource/Items/CharacterInventory.cs | 2 +- .../Items/Components/Holdable/RangedWeapon.cs | 5 +- .../Items/Components/ItemContainer.cs | 2 +- .../Items/Components/Machines/Fabricator.cs | 41 ++- .../Items/Components/Machines/MiniMap.cs | 137 ++++++--- .../Items/Components/Repairable.cs | 30 +- .../ClientSource/Items/Inventory.cs | 7 +- .../ClientSource/Items/Item.cs | 8 +- .../ClientSource/Screens/NetLobbyScreen.cs | 16 +- .../ClientSource/Screens/TestScreen.cs | 35 +-- .../BarotraumaClient/LinuxClient.csproj | 2 +- Barotrauma/BarotraumaClient/MacClient.csproj | 2 +- .../BarotraumaClient/WindowsClient.csproj | 2 +- .../BarotraumaServer/LinuxServer.csproj | 2 +- Barotrauma/BarotraumaServer/MacServer.csproj | 2 +- .../ServerSource/Items/Item.cs | 1 + .../ServerSource/Networking/ServerSettings.cs | 2 +- .../BarotraumaServer/WindowsServer.csproj | 2 +- .../Characters/AI/Wreck/WreckAI.cs | 4 +- .../Characters/Animation/Ragdoll.cs | 1 + .../SharedSource/Characters/Character.cs | 22 +- .../SharedSource/Characters/CharacterInfo.cs | 13 + .../Characters/Health/CharacterHealth.cs | 3 +- .../AbilityConditionIsAiming.cs | 8 +- .../AbilityConditionItem.cs | 10 +- .../AbilityConditionHasVelocity.cs | 20 ++ .../AbilityConditionShipFlooded.cs | 2 +- .../CharacterAbilityApplyStatusEffects.cs | 8 +- ...pplyStatusEffectsToLastOrderedCharacter.cs | 31 ++ .../CharacterAbilityGivePermanentStat.cs | 3 +- .../CharacterAbilityIncreaseSkill.cs | 28 +- .../CharacterAbilityModifyStatToFlooding.cs | 34 +++ .../Abilities/CharacterAbilityModifyValue.cs | 4 +- .../CharacterAbilityByTheBook.cs | 10 +- .../CharacterAbilityEnigmaMachine.cs | 43 +++ .../CharacterAbilityIndustrialRevolution.cs | 30 -- .../CharacterAbilityTaskmaster.cs | 39 --- .../AbilityGroups/CharacterAbilityGroup.cs | 8 +- .../CharacterAbilityGroupEffect.cs | 3 +- .../CharacterAbilityGroupInterval.cs | 3 +- .../Characters/Talents/CharacterTalent.cs | 5 +- .../Characters/Talents/TalentTree.cs | 80 ++++- .../BarotraumaShared/SharedSource/Enums.cs | 39 ++- .../Missions/AbandonedOutpostMission.cs | 2 +- .../Events/Missions/PirateMission.cs | 6 +- .../Events/Missions/SalvageMission.cs | 4 +- .../Extensions/VectorExtensions.cs | 7 + .../Items/Components/GeneticMaterial.cs | 26 +- .../Items/Components/Holdable/Pickable.cs | 8 +- .../Items/Components/Holdable/RepairTool.cs | 20 +- .../Items/Components/ItemContainer.cs | 21 +- .../Components/Machines/Deconstructor.cs | 11 + .../Items/Components/Machines/Fabricator.cs | 32 +- .../Items/Components/Machines/Pump.cs | 2 + .../SharedSource/Items/Components/Quality.cs | 72 +++++ .../Items/Components/RemoteController.cs | 1 + .../Items/Components/Repairable.cs | 16 +- .../SharedSource/Items/Components/Turret.cs | 4 + .../SharedSource/Items/Components/Wearable.cs | 2 +- .../SharedSource/Items/Item.cs | 50 ++- .../SharedSource/Items/ItemInventory.cs | 6 +- .../SharedSource/Items/ItemPrefab.cs | 5 + .../SharedSource/Map/Explosion.cs | 13 +- .../Serialization/XMLExtensions.cs | 17 +- .../StatusEffects/PropertyConditional.cs | 19 ++ .../SharedSource/Utils/SafeIO.cs | 12 +- Barotrauma/BarotraumaShared/changelog.txt | 29 ++ .../BarotraumaShared/serversettings.xml | 2 +- 75 files changed, 1145 insertions(+), 441 deletions(-) create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionHasVelocity.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffectsToLastOrderedCharacter.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyStatToFlooding.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityEnigmaMachine.cs delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityIndustrialRevolution.cs delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityTaskmaster.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Items/Components/Quality.cs diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs index d6443aee0..2e648839a 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs @@ -801,7 +801,11 @@ namespace Barotrauma { var treatmentButton = component.GetChild(); if (!(treatmentButton?.UserData is ItemPrefab itemPrefab)) { continue; } - treatmentButton.Enabled = Character.Controlled.Inventory.AllItems.Any(it => it.prefab == itemPrefab); + treatmentButton.Enabled = Character.Controlled.Inventory.AllItems.Any(it => it.prefab == itemPrefab); + foreach (GUIComponent child in treatmentButton.Children) + { + child.Enabled = treatmentButton.Enabled; + } } } @@ -1155,7 +1159,10 @@ namespace Barotrauma //key = item identifier //float = suitability Dictionary treatmentSuitability = new Dictionary(); - GetSuitableTreatments(treatmentSuitability, normalize: true, limb: selectedLimbIndex == -1 ? null : Character.AnimController.Limbs.Find(l => l.HealthIndex == selectedLimbIndex)); + GetSuitableTreatments(treatmentSuitability, + normalize: true, + ignoreHiddenAfflictions: true, + limb: selectedLimbIndex == -1 ? null : Character.AnimController.Limbs.Find(l => l.HealthIndex == selectedLimbIndex)); foreach (string treatment in treatmentSuitability.Keys.ToList()) { @@ -1260,10 +1267,11 @@ namespace Barotrauma UserData = item }; - var innerFrame = new GUIButton(new RectTransform(Vector2.One, itemSlot.RectTransform, Anchor.Center, Pivot.Center, scaleBasis: ScaleBasis.Smallest), style: "GUIButtonRound") + var innerFrame = new GUIButton(new RectTransform(Vector2.One, itemSlot.RectTransform, Anchor.Center, Pivot.Center, scaleBasis: ScaleBasis.Smallest), style: "SubtreeHeader") { UserData = item, ToolTip = $"‖color:255,255,255,255‖{item.Name}‖color:end‖" + '\n' + item.Description, + DisabledColor = Color.White * 0.1f, OnClicked = (btn, userdata) => { if (!(userdata is ItemPrefab itemPrefab)) { return false; } @@ -1274,6 +1282,17 @@ namespace Barotrauma return true; } }; + + new GUIImage(new RectTransform(Vector2.One, innerFrame.RectTransform, Anchor.Center), style: "TalentBackgroundGlow") + { + CanBeFocused = false, + Color = Color.White * 0.7f, + HoverColor = Color.White, + PressedColor = Color.DarkGray, + SelectedColor = Color.Transparent, + DisabledColor = Color.Transparent + }; + Sprite itemSprite = item.InventoryIcon ?? item.sprite; Color itemColor = itemSprite == item.sprite ? item.SpriteColor : item.InventoryIconColor; var itemIcon = new GUIImage(new RectTransform(new Vector2(0.8f, 0.8f), innerFrame.RectTransform, Anchor.Center), @@ -1283,7 +1302,7 @@ namespace Barotrauma Color = itemColor * 0.9f, HoverColor = itemColor, SelectedColor = itemColor, - DisabledColor = itemColor * 0.7f + DisabledColor = itemColor * 0.8f }; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs b/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs index d8cb533ed..988125af4 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs @@ -1697,13 +1697,14 @@ namespace Barotrauma //check missing mission texts foreach (var missionPrefab in MissionPrefab.List) { - string nameIdentifier = "missionname." + missionPrefab.Identifier; + string missionId = (missionPrefab.ConfigElement.Attribute("textidentifier") == null ? missionPrefab.Identifier : missionPrefab.ConfigElement.GetAttributeString("textidentifier", string.Empty)); + string nameIdentifier = "missionname." + missionId; if (!tags[language].Contains(nameIdentifier)) { if (!missingTags.ContainsKey(nameIdentifier)) { missingTags[nameIdentifier] = new HashSet(); } missingTags[nameIdentifier].Add(language); } - string descriptionIdentifier = "missiondescription." + missionPrefab.Identifier; + string descriptionIdentifier = "missiondescription." + missionId; if (!tags[language].Contains(descriptionIdentifier)) { if (!missingTags.ContainsKey(descriptionIdentifier)) { missingTags[descriptionIdentifier] = new HashSet(); } @@ -1713,6 +1714,7 @@ namespace Barotrauma foreach (SubmarineInfo sub in SubmarineInfo.SavedSubmarines) { + if (sub.Type != SubmarineType.Player) { continue; } string nameIdentifier = "submarine.name." + sub.Name.ToLowerInvariant(); if (!tags[language].Contains(nameIdentifier)) { @@ -1729,14 +1731,23 @@ namespace Barotrauma foreach (AfflictionPrefab affliction in AfflictionPrefab.List) { - string nameIdentifier = "afflictionname." + affliction.Identifier; + if (affliction.ShowIconThreshold > affliction.MaxStrength && + affliction.ShowIconToOthersThreshold > affliction.MaxStrength && + affliction.ShowInHealthScannerThreshold > affliction.MaxStrength) + { + //hidden affliction, no need for localization + continue; + } + + string afflictionId = affliction.TranslationOverride ?? affliction.Identifier; + string nameIdentifier = "afflictionname." + afflictionId; if (!tags[language].Contains(nameIdentifier)) { if (!missingTags.ContainsKey(nameIdentifier)) { missingTags[nameIdentifier] = new HashSet(); } missingTags[nameIdentifier].Add(language); } - string descriptionIdentifier = "afflictiondescription." + affliction.Identifier; + string descriptionIdentifier = "afflictiondescription." + afflictionId; if (!tags[language].Contains(descriptionIdentifier)) { if (!missingTags.ContainsKey(descriptionIdentifier)) { missingTags[descriptionIdentifier] = new HashSet(); } @@ -1744,6 +1755,29 @@ namespace Barotrauma } } + foreach (var talentTree in TalentTree.JobTalentTrees) + { + foreach (var talentSubTree in talentTree.Value.TalentSubTrees) + { + string nameIdentifier = "talenttree." + talentSubTree.Identifier; + if (!tags[language].Contains(nameIdentifier)) + { + if (!missingTags.ContainsKey(nameIdentifier)) { missingTags[nameIdentifier] = new HashSet(); } + missingTags[nameIdentifier].Add(language); + } + } + } + + foreach (var talent in TalentPrefab.TalentPrefabs) + { + string nameIdentifier = "talentname." + talent.Identifier; + if (!tags[language].Contains(nameIdentifier)) + { + if (!missingTags.ContainsKey(nameIdentifier)) { missingTags[nameIdentifier] = new HashSet(); } + missingTags[nameIdentifier].Add(language); + } + } + //check missing entity names foreach (MapEntityPrefab me in MapEntityPrefab.List) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIStyle.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIStyle.cs index bb9e36562..3a6537aa0 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIStyle.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIStyle.cs @@ -41,11 +41,14 @@ namespace Barotrauma public SpriteSheet SavingIndicator { get; private set; } public UISprite UIGlow { get; private set; } + public UISprite TalentGlow { get; private set; } public UISprite PingCircle { get; private set; } public UISprite UIGlowCircular { get; private set; } + public UISprite UIGlowSolidCircular { get; private set; } + public UISprite ButtonPulse { get; private set; } public SpriteSheet FocusIndicator { get; private set; } @@ -88,6 +91,12 @@ namespace Barotrauma public Color TextColorDark { get; private set; } = Color.Black * 0.9f; public Color TextColorDim { get; private set; } = Color.White * 0.6f; + public Color ItemQualityColorPoor { get; private set; } = Color.Gray; + public Color ItemQualityColorNormal { get; private set; } = Color.White; + public Color ItemQualityColorGood { get; private set; } = Color.LightGreen; + public Color ItemQualityColorExcellent { get; private set; } = Color.LightBlue; + public Color ItemQualityColorMasterwork { get; private set; } = Color.MediumPurple; + public Color ColorReputationVeryLow { get; private set; } = Color.Red; public Color ColorReputationLow { get; private set; } = Color.Orange; public Color ColorReputationNeutral { get; private set; } = Color.White * 0.8f; @@ -241,6 +250,9 @@ namespace Barotrauma case "uiglow": UIGlow = new UISprite(subElement); break; + case "talentglow": + TalentGlow = new UISprite(subElement); + break; case "pingcircle": PingCircle = new UISprite(subElement); break; @@ -253,6 +265,9 @@ namespace Barotrauma case "uiglowcircular": UIGlowCircular = new UISprite(subElement); break; + case "uiglowsolidcircular": + UIGlowSolidCircular = new UISprite(subElement); + break; case "endroundbuttonpulse": ButtonPulse = new UISprite(subElement); break; diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/LoadingScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/LoadingScreen.cs index 5a84a234f..3e7e06550 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/LoadingScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/LoadingScreen.cs @@ -81,7 +81,7 @@ namespace Barotrauma private readonly object loadMutex = new object(); private float? loadState; - + public float? LoadState { get @@ -90,8 +90,8 @@ namespace Barotrauma { return loadState; } - } - set + } + set { lock (loadMutex) { @@ -141,7 +141,7 @@ namespace Barotrauma GameMain.Config.EnableSplashScreen = false; } } - + var titleStyle = GUI.Style?.GetComponentStyle("TitleText"); Sprite titleSprite = null; if (!WaitForLanguageSelection && titleStyle != null && titleStyle.Sprites.ContainsKey(GUIComponent.ComponentState.None)) @@ -177,8 +177,8 @@ namespace Barotrauma color: Color.White * noiseStrength * 0.1f, textureScale: Vector2.One * noiseScale); - titleSprite?.Draw(spriteBatch, new Vector2(GameMain.GraphicsWidth * 0.05f, GameMain.GraphicsHeight * 0.125f), - Color.White, origin: new Vector2(0.0f, titleSprite.SourceRect.Height / 2.0f), + titleSprite?.Draw(spriteBatch, new Vector2(GameMain.GraphicsWidth * 0.05f, GameMain.GraphicsHeight * 0.125f), + Color.White, origin: new Vector2(0.0f, titleSprite.SourceRect.Height / 2.0f), scale: GameMain.GraphicsHeight / 2000.0f); if (WaitForLanguageSelection) @@ -211,6 +211,13 @@ namespace Barotrauma if (LoadState != null) { loadText += " " + (int)LoadState + " %"; + +#if DEBUG + if (GameMain.FirstLoad && GameMain.CancelQuickStart) + { + loadText += " (Quickstart aborted)"; + } +#endif } } if (GUI.LargeFont != null) @@ -265,7 +272,7 @@ namespace Barotrauma decorativeGraph.Draw(spriteBatch, (int)(decorativeGraph.FrameCount * noiseVal), new Vector2(GameMain.GraphicsWidth * 0.001f, GameMain.GraphicsHeight * 0.24f), Color.White, Vector2.Zero, 0.0f, decorativeScale, SpriteEffects.FlipVertically); - + decorativeMap.Draw(spriteBatch, (int)(decorativeMap.FrameCount * noiseVal), new Vector2(GameMain.GraphicsWidth * 0.99f, GameMain.GraphicsHeight * 0.66f), Color.White, decorativeMap.FrameSize.ToVector2(), 0.0f, decorativeScale); @@ -281,9 +288,9 @@ namespace Barotrauma } else if (noiseVal < 0.5f) { - randText = - Rand.Int(100).ToString().PadLeft(2, '0') + " " + - Rand.Int(100).ToString().PadLeft(2, '0') + " " + + randText = + Rand.Int(100).ToString().PadLeft(2, '0') + " " + + Rand.Int(100).ToString().PadLeft(2, '0') + " " + Rand.Int(100).ToString().PadLeft(2, '0') + " " + Rand.Int(100).ToString().PadLeft(2, '0'); } @@ -299,12 +306,12 @@ namespace Barotrauma { if (languageSelectionFont == null) { - languageSelectionFont = new ScalableFont("Content/Fonts/NotoSans/NotoSans-Bold.ttf", + languageSelectionFont = new ScalableFont("Content/Fonts/NotoSans/NotoSans-Bold.ttf", (uint)(30 * (GameMain.GraphicsHeight / 1080.0f)), graphicsDevice); } if (languageSelectionFontCJK == null) { - languageSelectionFontCJK = new ScalableFont("Content/Fonts/NotoSans/NotoSansCJKsc-Bold.otf", + languageSelectionFontCJK = new ScalableFont("Content/Fonts/NotoSans/NotoSansCJKsc-Bold.otf", (uint)(30 * (GameMain.GraphicsHeight / 1080.0f)), graphicsDevice, dynamicLoading: true); } if (languageSelectionCursor == null) @@ -320,11 +327,11 @@ namespace Barotrauma var font = TextManager.IsCJK(localizedLanguageName) ? languageSelectionFontCJK : languageSelectionFont; Vector2 textSize = font.MeasureString(localizedLanguageName); - bool hover = - Math.Abs(PlayerInput.MousePosition.X - textPos.X) < textSize.X / 2 && + bool hover = + Math.Abs(PlayerInput.MousePosition.X - textPos.X) < textSize.X / 2 && Math.Abs(PlayerInput.MousePosition.Y - textPos.Y) < textSpacing.Y / 2; - font.DrawString(spriteBatch, localizedLanguageName, textPos - textSize / 2, + font.DrawString(spriteBatch, localizedLanguageName, textPos - textSize / 2, hover ? Color.White : Color.White * 0.6f); if (hover && PlayerInput.PrimaryMouseButtonClicked()) { @@ -394,14 +401,14 @@ namespace Barotrauma LoadState = null; SetSelectedTip(TextManager.Get("LoadingScreenTip", true)); currentBackgroundTexture = LocationType.List.GetRandom()?.GetPortrait(Rand.Int(int.MaxValue))?.Texture; - + while (!drawn) { yield return CoroutineStatus.Running; } CoroutineManager.StartCoroutine(loader); - + yield return CoroutineStatus.Running; while (CoroutineManager.IsCoroutineRunning(loader.ToString())) diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs index efee168f0..803f2537c 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using System.Linq; @@ -18,8 +19,8 @@ namespace Barotrauma private static UISprite spectateIcon, disconnectedIcon; private static Sprite ownerIcon, moderatorIcon; - private enum InfoFrameTab { Crew, Mission, Reputation, MyCharacter, Traitor, Submarine, Talents }; - private static InfoFrameTab selectedTab; + public enum InfoFrameTab { Crew, Mission, Reputation, MyCharacter, Traitor, Submarine, Talents }; + public static InfoFrameTab selectedTab; private GUIFrame infoFrame, contentFrame; private readonly List tabButtons = new List(); @@ -48,7 +49,7 @@ namespace Barotrauma private readonly GUIFrame frame; public LinkedGUI(Client client, GUIFrame frame, bool hasCharacter, GUITextBlock textBlock) - { + { this.client = client; this.textBlock = textBlock; this.frame = frame; @@ -262,7 +263,11 @@ namespace Barotrauma var talentsButton = createTabButton(InfoFrameTab.Talents, "tabmenu.talents"); talentsButton.OnAddedToGUIUpdateList += (GUIComponent component) => { - talentsButton.Enabled = Character.Controlled?.Info != null && GameMain.GameSession?.Campaign != null; + talentsButton.Enabled = Character.Controlled?.Info != null && (GameMain.GameSession?.Campaign != null || Screen.Selected == GameMain.TestScreen || GameMain.GameSession.GameMode is TestGameMode); + if (!talentsButton.Enabled && selectedTab == InfoFrameTab.Talents) + { + SelectInfoFrameTab(null, InfoFrameTab.Crew); + } }; } @@ -417,7 +422,7 @@ namespace Barotrauma if (GameMain.IsMultiplayer) { CreateMultiPlayerList(false); - CreateMultiPlayerLogContent(crewFrame); + CreateMultiPlayerLogContent(crewFrame); } else { @@ -608,7 +613,7 @@ namespace Barotrauma AbsoluteSpacing = 2 }; - new GUICustomComponent(new RectTransform(new Point(jobColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform, Anchor.Center), + new GUICustomComponent(new RectTransform(new Point(jobColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform, Anchor.Center), onDraw: (sb, component) => DrawNotInGameIcon(sb, component.Rect, client)) { CanBeFocused = false, @@ -837,7 +842,7 @@ namespace Barotrauma } private static readonly List> storedMessages = new List>(); - + public static void StorePlayerConnectionChangeMessage(ChatMessage message) { if (!GameMain.GameSession?.IsRunning ?? true) { return; } @@ -850,7 +855,7 @@ namespace Barotrauma TabMenu instance = GameSession.TabMenuInstance; instance.AddLineToLog(msg, message.ChangeType); instance.RemoveCurrentElements(); - instance.CreateMultiPlayerList(true); + instance.CreateMultiPlayerList(true); } } @@ -969,7 +974,7 @@ namespace Barotrauma GUIFrame missionDescriptionHolder = new GUIFrame(new RectTransform(Vector2.One, missionList.Content.RectTransform), style: null); GUILayoutGroup missionTextGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.744f, 0f), missionDescriptionHolder.RectTransform, Anchor.CenterLeft) { AbsoluteOffset = new Point(iconSize + spacing, 0) }, false, childAnchor: Anchor.TopLeft) { - AbsoluteSpacing = spacing + AbsoluteSpacing = spacing }; string descriptionText = mission.Description; foreach (string missionMessage in mission.ShownMessages) @@ -997,7 +1002,7 @@ namespace Barotrauma float ySize = missionNameSize.Y + missionDescriptionSize.Y + missionRewardSize.Y + missionReputationSize.Y + missionTextGroup.AbsoluteSpacing * 4; bool displayDifficulty = mission.Difficulty.HasValue; if (displayDifficulty) { ySize += missionRewardSize.Y; } - + missionDescriptionHolder.RectTransform.NonScaledSize = new Point(missionDescriptionHolder.RectTransform.NonScaledSize.X, (int)ySize); missionTextGroup.RectTransform.NonScaledSize = new Point(missionTextGroup.RectTransform.NonScaledSize.X, missionDescriptionHolder.RectTransform.NonScaledSize.Y); @@ -1008,8 +1013,8 @@ namespace Barotrauma int iconHeight = Math.Max(missionTextGroup.RectTransform.NonScaledSize.Y, (int)(iconWidth * iconAspectRatio)); Point iconSize = new Point(iconWidth, iconHeight);*/ - new GUIImage(new RectTransform(new Point(iconSize), missionDescriptionHolder.RectTransform), mission.Prefab.Icon, null, true) - { + new GUIImage(new RectTransform(new Point(iconSize), missionDescriptionHolder.RectTransform), mission.Prefab.Icon, null, true) + { Color = mission.Prefab.IconColor, HoverColor = mission.Prefab.IconColor, SelectedColor = mission.Prefab.IconColor, @@ -1174,19 +1179,38 @@ namespace Barotrauma private Color unselectableColor = new Color(100, 100, 100, 225); private Color pressedColor = new Color(60, 60, 60, 225); - private readonly List<(GUIButton button, GUIImage background, GUIImage icon)> talentButtons = new List<(GUIButton button, GUIImage background, GUIImage icon)>(); + private readonly List<(GUIButton button, GUIComponent icon, GUIImage glow)> talentButtons = new List<(GUIButton button, GUIComponent icon, GUIImage glow)>(); + private readonly List<(string talentTree, int index, GUIImage icon, GUIFrame background, GUIFrame backgroundGlow)> talentCornerIcons = new List<(string talentTree, int index, GUIImage icon, GUIFrame background, GUIFrame backgroundGlow)>(); private List selectedTalents = new List(); - private GUITextBlock talentTitleText; - private GUITextBlock talentDescriptionText; - private GUITextBlock talentPointsText; private GUITextBlock experienceText; private GUIProgressBar experienceBar; + private GUITextBlock talentPointText; + private GUIListBox skillListBox; + + private readonly ImmutableDictionary talentStageStyles = new Dictionary + { + { TalentTree.TalentTreeStageState.Invalid, GUI.Style.GetComponentStyle("TalentTreeLocked") }, + { TalentTree.TalentTreeStageState.Locked, GUI.Style.GetComponentStyle("TalentTreeLocked") }, + { TalentTree.TalentTreeStageState.Unlocked, GUI.Style.GetComponentStyle("TalentTreePurchased") }, + { TalentTree.TalentTreeStageState.Available, GUI.Style.GetComponentStyle("TalentTreeUnlocked") }, + { TalentTree.TalentTreeStageState.Highlighted, GUI.Style.GetComponentStyle("TalentTreeAvailable") }, + }.ToImmutableDictionary(); + + private readonly ImmutableDictionary talentStageBackgroundColors = new Dictionary + { + { TalentTree.TalentTreeStageState.Invalid, new Color(48,48,48,255) }, + { TalentTree.TalentTreeStageState.Locked, new Color(48,48,48,255) }, + { TalentTree.TalentTreeStageState.Unlocked, new Color(24,37,31,255) }, + { TalentTree.TalentTreeStageState.Available, new Color(50,47,33,255) }, + { TalentTree.TalentTreeStageState.Highlighted, new Color(50,47,33,255) }, + }.ToImmutableDictionary(); private void CreateTalentInfo(GUIFrame infoFrame) { infoFrame.ClearChildren(); talentButtons.Clear(); + talentCornerIcons.Clear(); Character controlledCharacter = Character.Controlled; if (controlledCharacter == null) { return; } @@ -1195,6 +1219,8 @@ namespace Barotrauma int padding = GUI.IntScale(15); GUIFrame talentFrameContent = new GUIFrame(new RectTransform(new Point(talentFrameBackground.Rect.Width - padding, talentFrameBackground.Rect.Height - padding), infoFrame.RectTransform, Anchor.Center), style: null); + GUIFrame paddedTalentFrame = new GUIFrame(new RectTransform(new Vector2(0.9f), talentFrameContent.RectTransform, Anchor.Center), style: null); + if (controlledCharacter.Info == null) { DebugConsole.ThrowError("No character info found for talent UI"); @@ -1203,87 +1229,103 @@ namespace Barotrauma selectedTalents = controlledCharacter.Info.UnlockedTalents.ToList(); - GUILayoutGroup talentFrameLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f), talentFrameContent.RectTransform, anchor: Anchor.Center), childAnchor: Anchor.TopCenter) + GUILayoutGroup talentFrameLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f), paddedTalentFrame.RectTransform, anchor: Anchor.Center), childAnchor: Anchor.TopCenter) { AbsoluteSpacing = GUI.IntScale(5) }; - GUILayoutGroup talentInfoLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.325f), talentFrameLayoutGroup.RectTransform, Anchor.Center), childAnchor: Anchor.TopCenter); + GUILayoutGroup talentInfoLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.25f), talentFrameLayoutGroup.RectTransform, Anchor.Center), isHorizontal: true); - GUIFrame talentTitleFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.25f), talentInfoLayoutGroup.RectTransform, Anchor.TopCenter), style: null); + CharacterInfo info = controlledCharacter.Info; + Job job = info.Job; - talentTitleText = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), talentTitleFrame.RectTransform, Anchor.TopLeft), "", font: GUI.LargeFont); - talentPointsText = new GUITextBlock(new RectTransform(new Vector2(0.25f, 1.0f), talentTitleFrame.RectTransform, Anchor.TopRight), "", font: GUI.Font, textAlignment: Alignment.Center); - - GUIFrame talentDescriptionFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.4f), talentInfoLayoutGroup.RectTransform, Anchor.TopCenter), style: null); - - talentDescriptionText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), talentDescriptionFrame.RectTransform, Anchor.TopLeft), "", font: GUI.Font, textAlignment: Alignment.TopLeft, wrap: true); - - GUIFrame characterInfoFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.3f), talentInfoLayoutGroup.RectTransform, Anchor.TopLeft), style: null); - GUILayoutGroup characterInfoColumn = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f), characterInfoFrame.RectTransform, anchor: Anchor.TopLeft), childAnchor: Anchor.TopLeft, isHorizontal: true); - - // move to a different tab menu - if (GameSettings.VerboseLogging) + new GUICustomComponent(new RectTransform(new Vector2(0.25f, 1f), talentInfoLayoutGroup.RectTransform), onDraw: (batch, component) => { - CreateCharacterSheet(characterInfoColumn); - } + float posY = component.Rect.Bottom - component.Rect.Width; + info.DrawPortrait(batch, new Vector2(component.Rect.X, posY), Vector2.Zero, component.Rect.Width, false, false); + }); + + GUILayoutGroup nameLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.375f, 1f), talentInfoLayoutGroup.RectTransform)); + + Vector2 nameSize = GUI.SubHeadingFont.MeasureString(info.Name); + GUITextBlock nameBlock = new GUITextBlock(new RectTransform(Vector2.One, nameLayout.RectTransform), info.Name, font: GUI.SubHeadingFont) { TextColor = job.Prefab.UIColor }; + nameBlock.RectTransform.NonScaledSize = nameSize.Pad(nameBlock.Padding).ToPoint(); + + Vector2 jobSize = GUI.SmallFont.MeasureString(job.Name); + GUITextBlock jobBlock = new GUITextBlock(new RectTransform(Vector2.One, nameLayout.RectTransform), job.Name, font: GUI.SmallFont) { TextColor = job.Prefab.UIColor }; + jobBlock.RectTransform.NonScaledSize = jobSize.Pad(jobBlock.Padding).ToPoint(); + + string traitString = TextManager.AddPunctuation(':', TextManager.Get("PersonalityTrait"), TextManager.Get("personalitytrait." + info.PersonalityTrait.Name.Replace(" ", ""))); + Vector2 traitSize = GUI.SmallFont.MeasureString(traitString); + GUITextBlock traitBlock = new GUITextBlock(new RectTransform(Vector2.One, nameLayout.RectTransform), traitString, font: GUI.SmallFont); + traitBlock.RectTransform.NonScaledSize = traitSize.Pad(traitBlock.Padding).ToPoint(); + + GUILayoutGroup skillLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.375f, 1f), talentInfoLayoutGroup.RectTransform)) { Stretch = true }; + + string skillString = TextManager.Get("skills"); + Vector2 skillSize = GUI.SubHeadingFont.MeasureString(skillString); + GUITextBlock skillBlock = new GUITextBlock(new RectTransform(Vector2.One, skillLayout.RectTransform), skillString, font: GUI.SubHeadingFont); + skillBlock.RectTransform.NonScaledSize = skillSize.Pad(skillBlock.Padding).ToPoint(); + + skillListBox = new GUIListBox(new RectTransform(new Vector2(1f, 1f - skillBlock.RectTransform.RelativeSize.Y), skillLayout.RectTransform), style: null); + CreateTalentSkillList(controlledCharacter, skillListBox); if (!TalentTree.JobTalentTrees.TryGetValue(controlledCharacter.Info.Job.Prefab.Identifier, out TalentTree talentTree)) { return; } - GUIListBox talentTreeListBox = new GUIListBox(new RectTransform(new Vector2(1f, 0.6f), talentFrameLayoutGroup.RectTransform, Anchor.TopCenter), isHorizontal: true, style: null); + new GUIFrame(new RectTransform(new Vector2(1f, 1f), talentFrameLayoutGroup.RectTransform), style: "HorizontalLine"); - int spacing = GUI.IntScale(5); + GUIListBox talentTreeListBox = new GUIListBox(new RectTransform(new Vector2(1f, 0.7f), talentFrameLayoutGroup.RectTransform, Anchor.TopCenter), isHorizontal: true, style: null); foreach (var subTree in talentTree.TalentSubTrees) { GUIFrame subTreeFrame = new GUIFrame(new RectTransform(new Vector2(0.333f, 1f), talentTreeListBox.Content.RectTransform, anchor: Anchor.TopLeft), style: null); GUILayoutGroup subTreeLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1f, 1f), subTreeFrame.RectTransform, Anchor.Center), false, childAnchor: Anchor.TopCenter); - GUIFrame subtreeTitleFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.111f), subTreeLayoutGroup.RectTransform, anchor: Anchor.TopCenter), style: "SubtreeHeader"); - - new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), subtreeTitleFrame.RectTransform, anchor: Anchor.TopCenter), subTree.Identifier, font: GUI.LargeFont, textAlignment: Alignment.Center); + GUIFrame subtreeTitleFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.111f), subTreeLayoutGroup.RectTransform, anchor: Anchor.TopCenter), style: null); + int elementPadding = GUI.IntScale(8); + Point headerSize = subtreeTitleFrame.RectTransform.NonScaledSize; + GUIFrame subTreeTitleBackground = new GUIFrame(new RectTransform(new Point(headerSize.X - elementPadding, headerSize.Y), subtreeTitleFrame.RectTransform, anchor: Anchor.Center), style: "SubtreeHeader"); + new GUITextBlock(new RectTransform(Vector2.One, subTreeTitleBackground.RectTransform, anchor: Anchor.TopCenter), subTree.DisplayName, font: GUI.LargeFont, textAlignment: Alignment.Center); for (int i = 0; i < 4; i++) { - GUIFrame talentOptionFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.222f), subTreeLayoutGroup.RectTransform, anchor: Anchor.TopCenter), style: "TalentOptionFrame"); - GUIImage talentBackground = new GUIImage(new RectTransform(Vector2.One, talentOptionFrame.RectTransform, anchor: Anchor.Center), style: "TalentBackground") + GUIFrame talentOptionFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.222f), subTreeLayoutGroup.RectTransform, anchor: Anchor.TopCenter), style: null); + + Point talentFrameSize = talentOptionFrame.RectTransform.NonScaledSize; + + GUIFrame talentBackground = new GUIFrame(new RectTransform(new Point(talentFrameSize.X - elementPadding, talentFrameSize.Y - elementPadding), talentOptionFrame.RectTransform, anchor: Anchor.Center), style: "TalentBackground"); + GUIFrame talentBackgroundHighlight = new GUIFrame(new RectTransform(Vector2.One, talentBackground.RectTransform, anchor: Anchor.Center), style: "TalentBackgroundGlow") { Visible = false }; + + GUIImage cornerIcon = new GUIImage(new RectTransform(new Vector2(0.25f), talentOptionFrame.RectTransform, anchor: Anchor.BottomRight, scaleBasis: ScaleBasis.BothHeight), style: null) { - CanBeFocused = false, - Color = unselectableColor, + CanBeFocused = false }; + Point iconSize = cornerIcon.RectTransform.NonScaledSize; + cornerIcon.RectTransform.AbsoluteOffset = new Point(iconSize.X / 2, iconSize.Y / 2); + if (subTree.TalentOptionStages.Count > i) { TalentOption talentOption = subTree.TalentOptionStages[i]; - GUILayoutGroup talentOptionLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1f, 1f), talentOptionFrame.RectTransform, Anchor.CenterLeft), isHorizontal: true, childAnchor: Anchor.CenterLeft) - { - Stretch = true, - }; + GUILayoutGroup talentOptionCenterGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.75f, 0.7f), talentOptionFrame.RectTransform, Anchor.Center), childAnchor: Anchor.CenterLeft); + + GUILayoutGroup talentOptionLayoutGroup = new GUILayoutGroup(new RectTransform(Vector2.One, talentOptionCenterGroup.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft) { Stretch = true }; foreach (TalentPrefab talent in talentOption.Talents) { - int optionPadding = GUI.IntScale(10); - GUIFrame talentFrame = new GUIFrame(new RectTransform(new Point(talentOptionFrame.Rect.Width, talentOptionFrame.Rect.Height - optionPadding), talentOptionLayoutGroup.RectTransform), style: null) + GUIFrame talentFrame = new GUIFrame(new RectTransform(Vector2.One, talentOptionLayoutGroup.RectTransform), style: null) { CanBeFocused = false, }; - new GUIImage(new RectTransform(Vector2.One, talentFrame.RectTransform, anchor: Anchor.Center), style: "TalentFrameBackground") - { - CanBeFocused = false, - }; - GUIImage iconImage = null; - GUIButton talentButton = new GUIButton(new RectTransform(Vector2.One, talentFrame.RectTransform, anchor: Anchor.Center), style: "TalentFrame") + GUIButton talentButton = new GUIButton(new RectTransform(Vector2.One, talentFrame.RectTransform, anchor: Anchor.Center), style: null) { ToolTip = $"{talent.DisplayName}\n\n{talent.Description}", UserData = talent.Identifier, PressedColor = pressedColor, OnClicked = (button, userData) => { - talentTitleText.Text = talent.DisplayName; - talentDescriptionText.Text = talent.Description; - // deselect other buttons in tier by removing their selected talents from pool foreach (GUIButton guiButton in talentOptionLayoutGroup.GetAllChildren()) { @@ -1314,61 +1356,129 @@ namespace Barotrauma }, }; + talentButton.Color = talentButton.HoverColor = talentButton.PressedColor = talentButton.SelectedColor = Color.Transparent; - int iconPadding = GUI.IntScale(15); - iconImage = new GUIImage(new RectTransform(new Point(talentFrame.Rect.Width - iconPadding, talentFrame.Rect.Height - iconPadding), talentFrame.RectTransform, anchor: Anchor.Center), sprite: talent.Icon, scaleToFit: true) + GUIComponent iconImage; + if (talent.Icon is null) { - PressedColor = unselectableColor, - CanBeFocused = false, - }; + iconImage = new GUITextBlock(new RectTransform(Vector2.One, talentButton.RectTransform, anchor: Anchor.Center, scaleBasis: ScaleBasis.BothHeight), text: "???", font: GUI.LargeFont, textAlignment: Alignment.Center, style: null) + { + OutlineColor = GUI.Style.Red, + TextColor = GUI.Style.Red, + PressedColor = unselectableColor, + CanBeFocused = false, + }; + } + else + { + iconImage = new GUIImage(new RectTransform(Vector2.One, talentButton.RectTransform, anchor: Anchor.Center), sprite: talent.Icon, scaleToFit: true) + { + PressedColor = unselectableColor, + CanBeFocused = false, + }; + } - talentButtons.Add((talentButton, talentBackground, iconImage)); + GUIImage iconGlow = new GUIImage(new RectTransform(Vector2.One, iconImage.RectTransform, anchor: Anchor.Center), sprite: GUI.Style.TalentGlow.Sprite, scaleToFit: true) { Visible = false }; + + talentButtons.Add((talentButton, iconImage, iconGlow)); } + + talentCornerIcons.Add((subTree.Identifier, i, cornerIcon, talentBackground, talentBackgroundHighlight)); } } } - GUIFrame talentBottomFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.07f), talentFrameLayoutGroup.RectTransform, Anchor.TopCenter), style: null); + GUILayoutGroup talentBottomFrame = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.07f), talentFrameLayoutGroup.RectTransform, Anchor.TopCenter), isHorizontal: true) { RelativeSpacing = 0.01f }; - GUIFrame experienceBarFrame = new GUIFrame(new RectTransform(new Vector2(0.775f, 0.75f), talentBottomFrame.RectTransform, Anchor.TopCenter), style: null); + GUILayoutGroup experienceLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.775f, 1f), talentBottomFrame.RectTransform)); + GUIFrame experienceBarFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.5f), experienceLayout.RectTransform), style: null); experienceBar = new GUIProgressBar(new RectTransform(new Vector2(1f, 1f), experienceBarFrame.RectTransform, Anchor.CenterLeft), - barSize: controlledCharacter.Info.GetProgressTowardsNextLevel(), color: Color.White, style: "ExperienceBar") + barSize: controlledCharacter.Info.GetProgressTowardsNextLevel(), color: GUI.Style.Green) { IsHorizontal = true }; - GUIImage experienceTextBackground = new GUIImage(new RectTransform(new Vector2(0.2f, 0.475f), experienceBarFrame.RectTransform, anchor: Anchor.Center), style: "ExperienceTextBackground"); + experienceText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), experienceBarFrame.RectTransform, anchor: Anchor.Center), "", font: GUI.Font, textAlignment: Alignment.CenterRight); - experienceText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), experienceTextBackground.RectTransform, anchor: Anchor.Center), "", font: GUI.Font, textColor: Color.White, textAlignment: Alignment.Center); + talentPointText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), experienceLayout.RectTransform, anchor: Anchor.Center), "", font: GUI.SubHeadingFont, parseRichText: true, textAlignment: Alignment.CenterRight); - new GUIButton(new RectTransform(new Vector2(0.1f, 0.3f), talentBottomFrame.RectTransform, anchor: Anchor.TopRight), text: TextManager.Get("applysettingsbutton")) - { - OnClicked = ApplyTalentSelection, - }; - - new GUIButton(new RectTransform(new Vector2(0.1f, 0.3f), talentBottomFrame.RectTransform, anchor: Anchor.TopLeft), text: TextManager.Get("reset")) + new GUIButton(new RectTransform(new Vector2(0.1f, 1f), talentBottomFrame.RectTransform), text: TextManager.Get("reset"), style: "GUICharacterInfoButton") { OnClicked = ResetTalentSelection, }; + new GUIButton(new RectTransform(new Vector2(0.1f, 1f), talentBottomFrame.RectTransform), text: TextManager.Get("applysettingsbutton"), style: "GUICharacterInfoButton") + { + OnClicked = ApplyTalentSelection, + }; + UpdateTalentButtons(); } + private void CreateTalentSkillList(Character character, GUIListBox parent) + { + parent.Content.ClearChildren(); + foreach (Skill skill in character.Info.Job.Skills) + { + GUILayoutGroup skillContainer = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.2f), parent.Content.RectTransform), isHorizontal: true) { CanBeFocused = false }; + + new GUITextBlock(new RectTransform(new Vector2(0.7f, 1f), skillContainer.RectTransform), TextManager.Get($"skillname.{skill.Identifier}", returnNull: true) ?? skill.Identifier); + new GUITextBlock(new RectTransform(new Vector2(0.15f, 1.0f), skillContainer.RectTransform), Math.Floor(skill.Level).ToString("F0"), textAlignment: Alignment.CenterRight) { Padding = new Vector4(0, 0, 4, 0) }; + + float modifiedSkillLevel = character.GetSkillLevel(skill.Identifier); + if (!MathUtils.NearlyEqual(MathF.Floor(modifiedSkillLevel), MathF.Floor(skill.Level))) + { + int skillChange = (int)MathF.Floor(modifiedSkillLevel - skill.Level); + string stringColor = true switch + { + true when skillChange > 0 => XMLExtensions.ColorToString(GUI.Style.Green), + true when skillChange < 0 => XMLExtensions.ColorToString(GUI.Style.Red), + _ => XMLExtensions.ColorToString(GUI.Style.TextColor) + }; + + string changeText = $"(‖color:{stringColor}‖{(skillChange > 0 ? "+" : string.Empty) + skillChange}‖color:end‖)"; + new GUITextBlock(new RectTransform(new Vector2(0.15f, 1.0f), skillContainer.RectTransform), changeText, parseRichText: true) { Padding = Vector4.Zero }; + } + skillContainer.Recalculate(); + } + + parent.RecalculateChildren(); + } + private void UpdateTalentButtons() { Character controlledCharacter = Character.Controlled; - talentPointsText.Text = $"{TextManager.Get("talentpointsleft")}{controlledCharacter.Info.GetAvailableTalentPoints()}"; experienceText.Text = $"{controlledCharacter.Info.ExperiencePoints - controlledCharacter.Info.GetExperienceRequiredForCurrentLevel()} / {controlledCharacter.Info.GetExperienceRequiredToLevelUp() - controlledCharacter.Info.GetExperienceRequiredForCurrentLevel()}"; experienceBar.BarSize = controlledCharacter.Info.GetProgressTowardsNextLevel(); //experienceBar.ToolTip = $"{controlledCharacter.Info.ExperiencePoints - controlledCharacter.Info.GetExperienceRequiredForCurrentLevel()} / {controlledCharacter.Info.GetExperienceRequiredToLevelUp() - controlledCharacter.Info.GetExperienceRequiredForCurrentLevel()}"; selectedTalents = TalentTree.CheckTalentSelection(controlledCharacter, selectedTalents); - foreach (var talentButton in talentButtons) + string pointsLeft = controlledCharacter.Info.GetAvailableTalentPoints().ToString(); + + int talentCount = selectedTalents.Count - controlledCharacter.Info.UnlockedTalents.Count; + + if (talentCount > 0) { - talentButton.background.Color = unselectableColor; + string pointsUsed = $"‖color:{XMLExtensions.ColorToString(GUI.Style.Red)}‖{-talentCount}‖color:end‖"; + string localizedString = TextManager.GetWithVariables("talentmenu.points.spending", new []{ "[amount]", "[used]" }, new []{ pointsLeft, pointsUsed}); + talentPointText.SetRichText(localizedString); + } + else + { + talentPointText.SetRichText(TextManager.GetWithVariable("talentmenu.points", "[amount]", pointsLeft)); + } + + foreach (var (talentTree, index, icon, frame, glow) in talentCornerIcons) + { + TalentTree.TalentTreeStageState state = TalentTree.GetTalentOptionStageState(controlledCharacter, talentTree, index, selectedTalents); + GUIComponentStyle newStyle = talentStageStyles[state]; + icon.ApplyStyle(newStyle); + icon.Color = newStyle.Color; + frame.Color = talentStageBackgroundColors[state]; + glow.Visible = state == TalentTree.TalentTreeStageState.Highlighted; } foreach (var talentButton in talentButtons) @@ -1377,30 +1487,23 @@ namespace Barotrauma bool unselectable = !TalentTree.IsViableTalentForCharacter(controlledCharacter, talentIdentifier, selectedTalents) || controlledCharacter.HasTalent(talentIdentifier); Color newTalentColor = unselectable ? unselectableColor : unselectedColor; + talentButton.glow.Visible = false; + if (controlledCharacter.HasTalent(talentIdentifier)) { - newTalentColor = ownedColor; + newTalentColor = new Color(140,225,140,255); } else if (selectedTalents.Contains(talentIdentifier)) { - newTalentColor = selectedColor; + newTalentColor = new Color(174,164,124,255); + talentButton.glow.Visible = true; } - talentButton.button.Color = newTalentColor; - talentButton.button.SelectedColor = newTalentColor; - talentButton.button.HoverColor = newTalentColor; - talentButton.button.DisabledColor = newTalentColor; - talentButton.icon.Color = newTalentColor; - - // update background color as well, if not defined yet - if (talentButton.background.Color == unselectableColor) - { - talentButton.background.Color = newTalentColor; - } } - } + CreateTalentSkillList(controlledCharacter, skillListBox); + } private void ApplyTalents(Character controlledCharacter) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs b/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs index e22ee76cb..e979ecbeb 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs @@ -194,6 +194,8 @@ namespace Barotrauma #if DEBUG public static bool FirstLoad = true; + + public static bool CancelQuickStart; #endif public GameMain(string[] args) @@ -309,7 +311,7 @@ namespace Barotrauma GraphicsDeviceManager.PreferredBackBufferWidth = GraphicsWidth; GraphicsDeviceManager.PreferredBackBufferHeight = GraphicsHeight; - + GraphicsDeviceManager.ApplyChanges(); if (windowMode == WindowMode.BorderlessWindowed) @@ -588,7 +590,7 @@ namespace Barotrauma StructurePrefab.LoadAll(GetFilesOfType(ContentType.Structure)); TitleScreen.LoadState = 55.0f; yield return CoroutineStatus.Running; - + UpgradePrefab.LoadAll(GetFilesOfType(ContentType.UpgradeModules)); TitleScreen.LoadState = 56.0f; yield return CoroutineStatus.Running; @@ -601,7 +603,7 @@ namespace Barotrauma ItemAssemblyPrefab.LoadAll(); TitleScreen.LoadState = 60.0f; yield return CoroutineStatus.Running; - + GameModePreset.Init(); SaveUtil.DeleteDownloadedSubs(); @@ -654,7 +656,7 @@ namespace Barotrauma ParticleManager.LoadPrefabs(); TitleScreen.LoadState = 88.0f; LevelObjectPrefab.LoadAll(); - + TitleScreen.LoadState = 90.0f; yield return CoroutineStatus.Running; @@ -804,7 +806,9 @@ namespace Barotrauma } #if DEBUG - if (TitleScreen.LoadState >= 100.0f && !TitleScreen.PlayingSplashScreen && (Config.AutomaticQuickStartEnabled || Config.AutomaticCampaignLoadEnabled || Config.TestScreenEnabled) && FirstLoad && !PlayerInput.KeyDown(Keys.LeftShift)) + CancelQuickStart |= PlayerInput.KeyDown(Keys.LeftShift); + + if (TitleScreen.LoadState >= 100.0f && !TitleScreen.PlayingSplashScreen && (Config.AutomaticQuickStartEnabled || Config.AutomaticCampaignLoadEnabled || Config.TestScreenEnabled) && FirstLoad && !CancelQuickStart) { loadingScreenOpen = false; FirstLoad = false; @@ -812,7 +816,7 @@ namespace Barotrauma if (Config.TestScreenEnabled) { TestScreen.Select(); - } + } else if (Config.AutomaticQuickStartEnabled) { MainMenuScreen.QuickStart(); @@ -930,8 +934,8 @@ namespace Barotrauma static bool itemHudActive() { if (Character.Controlled?.SelectedConstruction == null) { return false; } - return - Character.Controlled.SelectedConstruction.ActiveHUDs.Any(ic => ic.GuiFrame != null) || + return + Character.Controlled.SelectedConstruction.ActiveHUDs.Any(ic => ic.GuiFrame != null) || ((Character.Controlled.ViewTarget as Item)?.Prefab?.FocusOnSelected ?? false); } } @@ -1095,7 +1099,7 @@ namespace Barotrauma if (save) { GUI.SetSavingIndicatorState(true); - + if (GameSession.Submarine != null && !GameSession.Submarine.Removed) { GameSession.SubmarineInfo = new SubmarineInfo(GameSession.Submarine); @@ -1267,7 +1271,7 @@ namespace Barotrauma string text = TextManager.GetWithVariable("openlinkinbrowserprompt", "[link]", url); string extensionText = TextManager.Get(promptExtensionTag, returnNull: true, useEnglishAsFallBack: false); if (!string.IsNullOrEmpty(extensionText)) - { + { text += $"\n\n{extensionText}"; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs index 520e873f5..09fe317e9 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs @@ -208,7 +208,7 @@ namespace Barotrauma ReportButtonFrame.RectTransform.AbsoluteOffset = new Point(0, -chatBox.ToggleButton.Rect.Height); - CreateReports(this, ReportButtonFrame, reports, false); + CreateReportButtons(this, ReportButtonFrame, reports, false); #endregion @@ -218,7 +218,7 @@ namespace Barotrauma dismissedOrderPrefab ??= Order.GetPrefab("dismissed"); } - public static void CreateReports(CrewManager crewManager, GUIComponent parent, List reports, bool isHorizontal) + public static void CreateReportButtons(CrewManager crewManager, GUIComponent parent, List reports, bool isHorizontal) { //report buttons foreach (Order order in reports) @@ -228,22 +228,21 @@ namespace Barotrauma { OnClicked = (button, userData) => { - if (!CanIssueOrders) { return false; } + if (!CanIssueOrders || crewManager?.DraggedOrder != null) { return false; } var sub = Character.Controlled.Submarine; if (sub == null || sub.TeamID != Character.Controlled.TeamID || sub.Info.IsWreck) { return false; } if (crewManager != null) { crewManager.SetCharacterOrder(null, order, null, CharacterInfo.HighestManualOrderPriority, Character.Controlled); - if (crewManager.IsSinglePlayer) { HumanAIController.ReportProblem(Character.Controlled, order); } } return true; }, UserData = order, - ToolTip = order.Name, ClampMouseRectToParent = false }; + btn.ToolTip = $"‖color:{XMLExtensions.ColorToString(order.Prefab.Color)}‖{order.Name}‖color:end‖\n{TextManager.Get("draganddropreports")}"; if (crewManager != null) { @@ -272,8 +271,9 @@ namespace Barotrauma { Color = order.Color, HoverColor = Color.Lerp(order.Color, Color.White, 0.5f), - ToolTip = order.Name, - SpriteEffects = SpriteEffects.FlipHorizontally + ToolTip = btn.RawToolTip, + SpriteEffects = SpriteEffects.FlipHorizontally, + UserData = order }; } } @@ -717,7 +717,7 @@ namespace Barotrauma hull ??= orderGiver.CurrentHull; AddOrder(new Order(order.Prefab ?? order, hull, null, orderGiver), order.FadeOutTime); } - else if(order.IsIgnoreOrder) + else if (order.IsIgnoreOrder) { WallSection ws = null; if (order.TargetType == Order.OrderTargetType.Entity && order.TargetEntity is IIgnorable ignorable) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/CharacterInventory.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/CharacterInventory.cs index 1e365da6b..72434637b 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/CharacterInventory.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/CharacterInventory.cs @@ -1232,7 +1232,7 @@ namespace Barotrauma highlightedQuickUseSlot = visualSlots[i]; } - if (!slots[i].First().AllowedSlots.Any(a => a == InvSlotType.Any) || SlotTypes[i] == InvSlotType.HealthInterface) + if (slots[i].First().AllowedSlots.Count() == 1 || SlotTypes[i] == InvSlotType.HealthInterface) { continue; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Holdable/RangedWeapon.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Holdable/RangedWeapon.cs index 08ffe00cf..b6eca6599 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Holdable/RangedWeapon.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Holdable/RangedWeapon.cs @@ -8,6 +8,7 @@ using Barotrauma.IO; using System.Text; using System.Xml.Linq; using Barotrauma.Sounds; +using System.Linq; namespace Barotrauma.Items.Components { @@ -145,7 +146,9 @@ namespace Barotrauma.Items.Components if (character == null || !character.IsKeyDown(InputType.Aim)) { return; } //camera focused on some other item/device, don't draw the crosshair - if (character.ViewTarget != null && (character.ViewTarget is Item item) && item.Prefab.FocusOnSelected) { return; } + if (character.ViewTarget != null && (character.ViewTarget is Item viewTargetItem) && viewTargetItem.Prefab.FocusOnSelected) { return; } + //don't draw the crosshair if the item is in some other type of equip slot than hands (e.g. assault rifle in the bag slot) + if (!character.HeldItems.Contains(item)) { return; } GUI.HideCursor = (crosshairSprite != null || crosshairPointerSprite != null) && GUI.MouseOn == null && !Inventory.IsMouseOnInventory && !GameMain.Instance.Paused; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemContainer.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemContainer.cs index 5b054daff..d2521a444 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemContainer.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemContainer.cs @@ -214,7 +214,7 @@ namespace Barotrauma.Items.Components if (UILabel == string.Empty) { return string.Empty; } if (UILabel != null) { - return TextManager.Get("UILabel." + UILabel); + return TextManager.Get("UILabel." + UILabel, returnNull: true) ?? TextManager.Get(UILabel); } else { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs index f85f13a2a..fb9f701ea 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs @@ -37,7 +37,7 @@ namespace Barotrauma.Items.Components private FabricationRecipe pendingFabricatedItem; - private Pair tooltip; + private (Rectangle area, string text)? tooltip; private GUITextBlock requiredTimeBlock; @@ -270,7 +270,7 @@ namespace Barotrauma.Items.Components }); var sufficientSkillsText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.15f), itemList.Content.RectTransform), - TextManager.Get("fabricatorsufficientskills", returnNull: true) ?? "Sufficient skills to fabricate", textColor: GUI.Style.Green, font: GUI.SubHeadingFont) + TextManager.Get("fabricatorsufficientskills"), textColor: GUI.Style.Green, font: GUI.SubHeadingFont) { AutoScaleHorizontal = true, CanBeFocused = false @@ -278,7 +278,7 @@ namespace Barotrauma.Items.Components sufficientSkillsText.RectTransform.SetAsFirstChild(); var insufficientSkillsText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.15f), itemList.Content.RectTransform), - TextManager.Get("fabricatorinsufficientskills", returnNull: true) ?? "Insufficient skills to fabricate", textColor: Color.Orange, font: GUI.SubHeadingFont) + TextManager.Get("fabricatorinsufficientskills"), textColor: Color.Orange, font: GUI.SubHeadingFont) { AutoScaleHorizontal = true, CanBeFocused = false @@ -290,7 +290,7 @@ namespace Barotrauma.Items.Components } var requiresRecipeText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.15f), itemList.Content.RectTransform), - TextManager.Get("fabricatorrequiresrecipe", returnNull: true) ?? "Requires recipe to fabricate", textColor: Color.Red, font: GUI.SubHeadingFont) + TextManager.Get("fabricatorrequiresrecipe"), textColor: Color.Red, font: GUI.SubHeadingFont) { AutoScaleHorizontal = true, CanBeFocused = false @@ -403,7 +403,7 @@ namespace Barotrauma.Items.Components { toolTipText += '\n' + requiredItem.ItemPrefabs.First().Description; } - tooltip = new Pair(slotRect, toolTipText); + tooltip = (slotRect, toolTipText); } slotIndex++; @@ -443,7 +443,7 @@ namespace Barotrauma.Items.Components if (tooltip != null) { - GUIComponent.DrawToolTip(spriteBatch, tooltip.Second, tooltip.First); + GUIComponent.DrawToolTip(spriteBatch, tooltip.Value.text, tooltip.Value.area); tooltip = null; } } @@ -463,6 +463,22 @@ namespace Barotrauma.Items.Components if (recipe?.DisplayName == null) { continue; } child.Visible = recipe.DisplayName.ToLower().Contains(filter); } + + //go through the elements backwards, and disable the labels ("insufficient skills to fabricate", "recipe required...") if there's no items below them + bool recipeVisible = false; + foreach (GUIComponent child in itemList.Content.Children.Reverse()) + { + if (!(child.UserData is FabricationRecipe recipe)) + { + child.Visible = recipeVisible; + recipeVisible = false; + } + else + { + recipeVisible = child.Visible; + } + } + itemList.UpdateScrollBarSize(); itemList.BarScroll = 0.0f; @@ -498,9 +514,20 @@ namespace Barotrauma.Items.Components }; }*/ + string name = GetRecipeNameAndAmount(selectedItem); + + float quality = 0; + foreach (string tag in selectedItem.TargetItem.Tags) + { + quality += user?.Info?.GetSavedStatValue(StatTypes.IncreaseFabricationQuality, tag) ?? 0; + } + if (quality > 0) + { + name = TextManager.GetWithVariable("itemname.quality" + (int)quality, "[itemname]", name+'\n', fallBackTag: "itemname.quality3"); + } var nameBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedFrame.RectTransform), - GetRecipeNameAndAmount(selectedItem), textAlignment: Alignment.CenterLeft, textColor: Color.Aqua, font: GUI.SubHeadingFont) + name, textAlignment: Alignment.CenterLeft, textColor: Color.Aqua, font: GUI.SubHeadingFont, parseRichText: true) { AutoScaleHorizontal = true }; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs index 7c64d0346..0c213a8cb 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs @@ -218,8 +218,8 @@ namespace Barotrauma.Items.Components DefaultNeutralColor = MiniMapBaseColor * 0.8f, HoverColor = Color.White, BlueprintBlue = new Color(23, 38, 33), - HullWaterColor = new Color(17, 173, 179), - HullWaterLineColor = Color.LightBlue, + HullWaterColor = new Color(17, 173, 179) * 0.5f, + HullWaterLineColor = Color.LightBlue * 0.5f, NoPowerColor = MiniMapBaseColor * 0.1f, ElectricalBaseColor = GUI.Style.Orange, NoPowerElectricalColor = ElectricalBaseColor * 0.1f; @@ -307,10 +307,13 @@ namespace Barotrauma.Items.Components if (reports.Any()) { - CrewManager.CreateReports(GameMain.GameSession?.CrewManager, reportFrame, reports, true); + CrewManager.CreateReportButtons(GameMain.GameSession?.CrewManager, reportFrame, reports, true); } - searchBarFrame = new GUILayoutGroup(new RectTransform(new Vector2(1), bottomFrame.RectTransform), isHorizontal: true, childAnchor: Anchor.Center); + searchBarFrame = new GUILayoutGroup(new RectTransform(new Vector2(1.5f, 1.0f), bottomFrame.RectTransform, Anchor.Center), isHorizontal: true, childAnchor: Anchor.Center) + { + Visible = false + }; searchBar = new GUITextBox(new RectTransform(new Vector2(1), searchBarFrame.RectTransform), string.Empty, createClearButton: true, createPenIcon: true) { OnEnterPressed = (box, text) => @@ -345,6 +348,7 @@ namespace Barotrauma.Items.Components foreach (ItemPrefab prefab in ItemPrefab.Prefabs.OrderBy(prefab => prefab.Name)) { + if (prefab.HideInMenus) { continue; } CreateItemFrame(prefab, listBox.Content.RectTransform); } @@ -355,7 +359,10 @@ namespace Barotrauma.Items.Components searchBar.OnSelected += (sender, key) => { - itemsFoundOnSub = Item.ItemList.Where(it => it.Submarine == item.Submarine && !it.NonInteractable && !it.HiddenInGame && it.Components.OfType().Any()).Select(it => it.Prefab).ToImmutableHashSet(); + itemsFoundOnSub = Item.ItemList.Where(it => + it.Submarine == item.Submarine && + !it.NonInteractable && !it.HiddenInGame && + (it.GetComponent() != null || it.GetComponent() != null)).Select(it => it.Prefab).ToImmutableHashSet(); }; searchBar.OnKeyHit += ControlSearchTooltip; @@ -487,21 +494,24 @@ namespace Barotrauma.Items.Components CreateHUD(); } - if (PlayerInput.PrimaryMouseButtonDown() && currentMode != MiniMapMode.HullStatus) + if (scissorComponent != null) { - if (GUI.MouseOn == scissorComponent || scissorComponent.IsParentOf(GUI.MouseOn)) + if (PlayerInput.PrimaryMouseButtonDown() && currentMode != MiniMapMode.HullStatus) { - dragMapStart = PlayerInput.MousePosition; + if (GUI.MouseOn == scissorComponent || scissorComponent.IsParentOf(GUI.MouseOn)) + { + dragMapStart = PlayerInput.MousePosition; + } } - } - if (currentMode != MiniMapMode.HullStatus && Math.Abs(PlayerInput.ScrollWheelSpeed) > 0 && (GUI.MouseOn == scissorComponent || scissorComponent.IsParentOf(GUI.MouseOn))) - { - float newZoom = Math.Clamp(Zoom + PlayerInput.ScrollWheelSpeed / 1000.0f * Zoom, minZoom, maxZoom); - float distanceScale = newZoom / Zoom; - mapOffset *= distanceScale; - recalculate |= !MathUtils.NearlyEqual(Zoom, newZoom); - Zoom = newZoom; + if (currentMode != MiniMapMode.HullStatus && Math.Abs(PlayerInput.ScrollWheelSpeed) > 0 && (GUI.MouseOn == scissorComponent || scissorComponent.IsParentOf(GUI.MouseOn))) + { + float newZoom = Math.Clamp(Zoom + PlayerInput.ScrollWheelSpeed / 1000.0f * Zoom, minZoom, maxZoom); + float distanceScale = newZoom / Zoom; + mapOffset *= distanceScale; + recalculate |= !MathUtils.NearlyEqual(Zoom, newZoom); + Zoom = newZoom; + } } if (dragMapStart is { } dragStart) @@ -524,15 +534,13 @@ namespace Barotrauma.Items.Components if (recalculate) { - miniMapContainer.RectTransform.LocalScale = new Vector2(Zoom); - miniMapContainer.RectTransform.RecalculateChildren(true, true); - miniMapContainer.RectTransform.AbsoluteOffset = mapOffset.ToPoint(); + if (miniMapContainer != null) + { + miniMapContainer.RectTransform.LocalScale = new Vector2(Zoom); + miniMapContainer.RectTransform.RecalculateChildren(true, true); + miniMapContainer.RectTransform.AbsoluteOffset = mapOffset.ToPoint(); + } recalculate = false; - - // var (maxWidth, maxHeight) = miniMapContainer.Rect.Size.ToVector2() / 2f; - // - // mapOffset.X = Math.Clamp(mapOffset.X, -maxWidth, maxWidth); - // mapOffset.Y = Math.Clamp(mapOffset.Y, -maxHeight, maxHeight); } // is there a better way to do this? @@ -606,17 +614,6 @@ namespace Barotrauma.Items.Components private void DrawHUDFront(SpriteBatch spriteBatch, GUICustomComponent container) { - // TODO remove - #warning remove - if (currentMode == MiniMapMode.HullCondition) - { - const string wipText = "work in progress"; - Vector2 textSize = GUI.LargeFont.MeasureString(wipText); - Vector2 textPos = GuiFrame.Rect.Center.ToVector2(); - - GUI.DrawString(spriteBatch, textPos - textSize / 2, wipText.ToUpper(), GUI.Style.Orange, Color.Black * 0.8f, backgroundPadding: 8, font: GUI.LargeFont); - } - if (Voltage < MinVoltage) { Vector2 textSize = GUI.Font.MeasureString(noPowerTip); @@ -627,18 +624,45 @@ namespace Barotrauma.Items.Components return; } - if (currentMode == MiniMapMode.HullStatus) + if (currentMode == MiniMapMode.HullStatus || currentMode == MiniMapMode.HullCondition) { Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle; spriteBatch.End(); spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable); spriteBatch.GraphicsDevice.ScissorRectangle = submarineContainer.Rect; - foreach (var (entity, component) in hullStatusComponents) + if (currentMode == MiniMapMode.HullCondition && item.Submarine != null) { - if (!(entity is Hull hull)) { continue; } - if (!hullDatas.TryGetValue(hull, out HullData? hullData) || hullData is null) { continue; } - DrawHullCards(spriteBatch, hull, hullData, component.RectComponent); + var sprite = GUI.Style.UIGlowSolidCircular?.Sprite; + float alpha = (MathF.Sin(blipState / maxBlipState * MathHelper.TwoPi) + 1.5f) * 0.5f; + if (sprite != null) + { + Vector2 spriteSize = sprite.size; + Rectangle worldBorders = item.Submarine.GetDockedBorders(); + worldBorders.Location += item.Submarine.WorldPosition.ToPoint(); + foreach (Gap gap in Gap.GapList) + { + if (gap.IsRoomToRoom || gap.Submarine != item.Submarine || gap.ConnectedDoor != null) { continue; } + RectangleF entityRect = ScaleRectToUI(gap, miniMapFrame.Rect, worldBorders); + + Vector2 scale = new Vector2(entityRect.Size.X / spriteSize.X, entityRect.Size.Y / spriteSize.Y) * 2.0f; + + Color color = ToolBox.GradientLerp(gap.Open, GUI.Style.HealthBarColorMedium, GUI.Style.HealthBarColorLow) * alpha; + sprite.Draw(spriteBatch, + miniMapFrame.Rect.Location.ToVector2() + entityRect.Center, + color, origin: sprite.Origin, rotate: 0.0f, scale: scale); + } + } + } + + if (currentMode == MiniMapMode.HullStatus) + { + foreach (var (entity, component) in hullStatusComponents) + { + if (!(entity is Hull hull)) { continue; } + if (!hullDatas.TryGetValue(hull, out HullData? hullData) || hullData is null) { continue; } + DrawHullCards(spriteBatch, hull, hullData, component.RectComponent); + } } spriteBatch.End(); @@ -721,14 +745,21 @@ namespace Barotrauma.Items.Components UserData = prefab }; - GUILayoutGroup layout = new GUILayoutGroup(new RectTransform(Vector2.One, frame.RectTransform), isHorizontal: true); + GUILayoutGroup layout = new GUILayoutGroup(new RectTransform(Vector2.One, frame.RectTransform), isHorizontal: true) + { + Stretch = true + }; new GUIImage(new RectTransform(Vector2.One, layout.RectTransform, scaleBasis: ScaleBasis.BothHeight), sprite) { - Color = prefab.InventoryIconColor + Color = prefab.InventoryIconColor, + UserData = prefab }; - new GUITextBlock(new RectTransform(Vector2.One, layout.RectTransform), prefab.Name, font: GUI.SubHeadingFont); - layout.UserData = prefab; + var nameText = new GUITextBlock(new RectTransform(Vector2.One, layout.RectTransform), prefab.Name); + nameText.RectTransform.SizeChanged += () => + { + nameText.Text = ToolBox.LimitString(prefab.Name, nameText.Font, nameText.Rect.Width); + }; } private void SearchItems(string text) @@ -792,15 +823,18 @@ namespace Barotrauma.Items.Components private void UpdateHUDBack() { + if (item.Submarine == null) { return; } + hullInfoFrame.Visible = false; - electricalFrame.Visible = false; - miniMapFrame.Visible = false; reportFrame.Visible = false; searchBarFrame.Visible = false; + electricalFrame.Visible = false; + miniMapFrame.Visible = false; switch (currentMode) { case MiniMapMode.HullStatus: + case MiniMapMode.HullCondition: UpdateHullStatus(); miniMapFrame.Visible = true; reportFrame.Visible = true; @@ -937,16 +971,19 @@ namespace Barotrauma.Items.Components SetTooltip(borderComponent.Rect.Center, header, line1, line2, line3, line1Color, line2Color, line3Color); } + bool draggingReport = GameMain.GameSession?.CrewManager?.DraggedOrder != null; // When setting the colors we want to know the linked hulls too or else the linked hull will not realize its being hovered over and reset the border color foreach (Hull linkedHull in hullData.LinkedHulls) { if (!hullStatusComponents.ContainsKey(linkedHull)) { continue; } - isHoveringOver |= canHoverOverHull && hullStatusComponents[linkedHull].RectComponent == GUI.MouseOn; + isHoveringOver |= + canHoverOverHull && + (hullStatusComponents[linkedHull].RectComponent == GUI.MouseOn || (draggingReport && hullStatusComponents[linkedHull].RectComponent.MouseRect.Contains(PlayerInput.MousePosition))); if (isHoveringOver) { break; } } - if (isHoveringOver) + if (isHoveringOver || (draggingReport && component.MouseRect.Contains(PlayerInput.MousePosition))) { borderColor = Color.Lerp(borderColor, Color.White, 0.5f); componentColor = HoverColor; @@ -1037,7 +1074,7 @@ namespace Barotrauma.Items.Components } else { - bool hullsVisible = currentMode == MiniMapMode.HullStatus; + bool hullsVisible = currentMode == MiniMapMode.HullStatus || currentMode == MiniMapMode.HullCondition; foreach (var (entity, component) in hullStatusComponents) { @@ -1150,7 +1187,7 @@ namespace Barotrauma.Items.Components GameMain.GameScreen.BlueprintEffect.Parameters["width"].SetValue((float)texture.Width); GameMain.GameScreen.BlueprintEffect.Parameters["height"].SetValue((float)texture.Height); - Color blueprintBlue = BlueprintBlue * currentMode switch { MiniMapMode.HullStatus => 0.1f, MiniMapMode.ElectricalView => 0.1f, _ => 0.5f }; + Color blueprintBlue = BlueprintBlue * currentMode switch { MiniMapMode.HullStatus => 0.1f, MiniMapMode.HullCondition => 0.1f, MiniMapMode.ElectricalView => 0.1f, _ => 0.5f }; Vector2 origin = new Vector2(texture.Width / 2f, texture.Height / 2f); float scale = currentMode == MiniMapMode.HullStatus ? 1.0f : Zoom; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Repairable.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Repairable.cs index a960ec531..f68bdc796 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Repairable.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Repairable.cs @@ -19,9 +19,11 @@ namespace Barotrauma.Items.Components private GUIProgressBar progressBar; - private List particleEmitters = new List(); + private GUITextBlock progressBarOverlayText; + + private readonly List particleEmitters = new List(); //the corresponding particle emitter is active when the condition is within this range - private List particleEmitterConditionRanges = new List(); + private readonly List particleEmitterConditionRanges = new List(); private SoundChannel repairSoundChannel; @@ -88,7 +90,7 @@ namespace Barotrauma.Items.Components } } - private void CreateGUI() + protected override void CreateGUI() { var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 0.75f), GuiFrame.RectTransform, Anchor.Center), childAnchor: Anchor.TopCenter) { @@ -123,6 +125,11 @@ namespace Barotrauma.Items.Components progressBar = new GUIProgressBar(new RectTransform(new Vector2(0.6f, 1.0f), progressBarHolder.RectTransform), color: GUI.Style.Green, barSize: 0.0f, style: "DeviceProgressBar"); + progressBarOverlayText = new GUITextBlock(new RectTransform(Vector2.One, progressBar.RectTransform), string.Empty, font: GUI.SubHeadingFont, textAlignment: Alignment.Center) + { + IgnoreLayoutGroups = true + }; + repairButtonText = TextManager.Get("RepairButton"); repairingText = TextManager.Get("Repairing"); RepairButton = new GUIButton(new RectTransform(new Vector2(0.4f, 1.0f), progressBarHolder.RectTransform, Anchor.TopCenter), repairButtonText) @@ -138,6 +145,7 @@ namespace Barotrauma.Items.Components progressBarHolder.RectTransform.MinSize = RepairButton.RectTransform.MinSize; RepairButton.RectTransform.MinSize = new Point((int)(RepairButton.TextBlock.TextSize.X * 1.2f), RepairButton.RectTransform.MinSize.Y); + sabotageButtonText = TextManager.Get("SabotageButton"); sabotagingText = TextManager.Get("Sabotaging"); SabotageButton = new GUIButton(new RectTransform(new Vector2(0.8f, 0.15f), paddedFrame.RectTransform, Anchor.BottomCenter), sabotageButtonText, style: "GUIButtonSmall") @@ -229,9 +237,23 @@ namespace Barotrauma.Items.Components { IsActive = true; - progressBar.BarSize = item.Condition / item.MaxCondition; + float defaultMaxCondition = (item.MaxCondition / item.MaxRepairConditionMultiplier); + + progressBar.BarSize = item.Condition / defaultMaxCondition; progressBar.Color = ToolBox.GradientLerp(progressBar.BarSize, GUI.Style.Red, GUI.Style.Orange, GUI.Style.Green); + if (item.Condition > defaultMaxCondition) + { + float extraCondition = item.MaxCondition * (item.MaxRepairConditionMultiplier - 1.0f); + progressBar.Color = ToolBox.GradientLerp((item.Condition - defaultMaxCondition) / extraCondition, GUI.Style.ColorReputationHigh, GUI.Style.ColorReputationVeryHigh); + progressBarOverlayText.Visible = true; + progressBarOverlayText.Text = $"{(int)Math.Round((item.Condition / defaultMaxCondition) * 100)}%"; + } + else + { + progressBarOverlayText.Visible = false; + } + RepairButton.Enabled = (currentFixerAction == FixActions.None || (CurrentFixer == character && currentFixerAction != FixActions.Repair)) && !item.IsFullCondition; RepairButton.Text = (currentFixerAction == FixActions.None || CurrentFixer != character || currentFixerAction != FixActions.Repair) ? repairButtonText : diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs index a91cd1cf9..daa10c434 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs @@ -316,12 +316,17 @@ namespace Barotrauma string colorStr = XMLExtensions.ColorToString(!item.AllowStealing ? GUI.Style.Red : Color.White); + if (item.Quality > 0) + { + name = TextManager.GetWithVariable("itemname.quality" + item.Quality, "[itemname]", name, fallBackTag: "itemname.quality3"); + } toolTip = $"‖color:{colorStr}‖{name}‖color:end‖"; + if (itemsInSlot.All(it => it.NonInteractable || it.NonPlayerTeamInteractable)) { toolTip += " " + TextManager.Get("connectionlocked"); } - if (!item.IsFullCondition && !item.Prefab.HideConditionBar) + if (!item.IsFullCondition && !item.Prefab.HideConditionInTooltip) { string conditionColorStr = XMLExtensions.ColorToString(ToolBox.GradientLerp(item.Condition / item.MaxCondition, GUI.Style.ColorInventoryEmpty, GUI.Style.ColorInventoryHalf, GUI.Style.ColorInventoryFull)); toolTip += $"‖color:{conditionColorStr}‖ ({(int)item.ConditionPercentage} %)‖color:end‖"; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs index 1ed06add7..ea190c1d8 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs @@ -1068,7 +1068,7 @@ namespace Barotrauma foreach (Character otherCharacter in Character.CharacterList) { if (otherCharacter != character && - otherCharacter.SelectedConstruction == character.SelectedConstruction) + otherCharacter.SelectedConstruction == this) { ItemInUseWarning.Visible = true; if (mergedHUDRect.Width > GameMain.GraphicsWidth / 2) { mergedHUDRect.Inflate(-GameMain.GraphicsWidth / 4, 0); } @@ -1194,7 +1194,7 @@ namespace Barotrauma } } - if (Character.Controlled != null && Character.Controlled.SelectedConstruction != this) + if (Character.Controlled != null && Character.Controlled.SelectedConstruction != this && GetComponent() == null) { if (Character.Controlled.SelectedConstruction?.GetComponent()?.TargetItem != this && !Character.Controlled.HeldItems.Any(it => it.GetComponent()?.TargetItem == this)) @@ -1537,6 +1537,7 @@ namespace Barotrauma byte bodyType = msg.ReadByte(); bool spawnedInOutpost = msg.ReadBoolean(); bool allowStealing = msg.ReadBoolean(); + int quality = msg.ReadRangedInteger(0, Items.Components.Quality.MaxQuality); byte teamID = msg.ReadByte(); bool tagsChanged = msg.ReadBoolean(); string tags = ""; @@ -1612,7 +1613,8 @@ namespace Barotrauma item = new Item(itemPrefab, pos, sub, id: itemId) { SpawnedInOutpost = spawnedInOutpost, - AllowStealing = allowStealing + AllowStealing = allowStealing, + Quality = quality }; } catch (Exception e) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs index 29699b746..7944e07d2 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs @@ -1694,7 +1694,7 @@ namespace Barotrauma private void CreateJobVariantTooltip(JobPrefab jobPrefab, int variant, GUIComponent parentSlot) { - jobVariantTooltip = new GUIFrame(new RectTransform(new Point((int)(500 * GUI.Scale), (int)(200 * GUI.Scale)), GUI.Canvas, pivot: Pivot.BottomRight), + jobVariantTooltip = new GUIFrame(new RectTransform(new Point((int)(400 * GUI.Scale), (int)(180 * GUI.Scale)), GUI.Canvas, pivot: Pivot.BottomRight), style: "GUIToolTip") { UserData = new Pair(jobPrefab, variant) @@ -1707,17 +1707,7 @@ namespace Barotrauma AbsoluteSpacing = (int)(15 * GUI.Scale) }; - string name = - TextManager.Get("jobname." + jobPrefab.Identifier + (variant + 1), returnNull: true, fallBackTag: "jobname." + jobPrefab.Identifier) ?? - ""; - - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform), name, font: GUI.SubHeadingFont); - - string description = - TextManager.Get("jobdescription." + jobPrefab.Identifier + (variant + 1), returnNull: true, fallBackTag: "jobdescription." + jobPrefab.Identifier) ?? - ""; - - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform), description, wrap: true, font: GUI.SmallFont); + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform), TextManager.GetWithVariable("startingequipmentname", "[number]", (variant + 1).ToString()), font: GUI.SubHeadingFont, textAlignment: Alignment.Center); var itemIdentifiers = jobPrefab.ItemIdentifiers[variant] .Distinct() @@ -1726,7 +1716,7 @@ namespace Barotrauma int itemsPerRow = 5; int rows = (int)Math.Max(Math.Ceiling(itemIdentifiers.Count() / (float)itemsPerRow), 1); - new GUICustomComponent(new RectTransform(new Vector2(1.0f, 0.4f * rows), content.RectTransform, Anchor.BottomLeft), + new GUICustomComponent(new RectTransform(new Vector2(1.0f, 0.4f * rows), content.RectTransform, Anchor.BottomCenter), onDraw: (sb, component) => { DrawJobVariantItems(sb, component, new Pair(jobPrefab, variant), itemsPerRow); }); jobVariantTooltip.RectTransform.MinSize = new Point(0, content.RectTransform.Children.Sum(c => c.Rect.Height + content.AbsoluteSpacing)); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/TestScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/TestScreen.cs index a806460f5..38b34c0a0 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/TestScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/TestScreen.cs @@ -1,6 +1,7 @@ #nullable enable using System; using System.Linq; +using Barotrauma.Extensions; using Barotrauma.Items.Components; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; @@ -23,6 +24,8 @@ namespace Barotrauma private Character? dummyCharacter; public static Effect BlueprintEffect; + private TabMenu tabMenu; + public TestScreen() { Cam = new Camera(); @@ -50,18 +53,15 @@ namespace Barotrauma dummyCharacter?.Remove(); } - // ???????? - submarine = new Submarine(SubmarineInfo.SavedSubmarines.FirstOrDefault(info => info.Name.Equals("Crescent", StringComparison.OrdinalIgnoreCase))); - miniMapItem = new Item(ItemPrefab.Find(null, "statusmonitor"), Vector2.Zero, submarine); - MiniMap miniMap = miniMapItem.GetComponent(); - miniMap.PowerConsumption = 0; - dummyCharacter = Character.Create(CharacterPrefab.HumanSpeciesName, Vector2.Zero, "", id: Entity.DummyID, hasAi: false); + dummyCharacter.Info.Job = new Job(JobPrefab.Prefabs.Where(jp => TalentTree.JobTalentTrees.ContainsKey(jp.Identifier)).GetRandom()); dummyCharacter.Info.Name = "Galldren"; dummyCharacter.Inventory.CreateSlots(); Character.Controlled = dummyCharacter; GameMain.World.ProcessChanges(); + TabMenu.selectedTab = TabMenu.InfoFrameTab.Talents; + tabMenu = new TabMenu(); } public override void AddToGUIUpdateList() @@ -69,34 +69,21 @@ namespace Barotrauma Frame.AddToGUIUpdateList(); CharacterHUD.AddToGUIUpdateList(dummyCharacter); dummyCharacter?.SelectedConstruction?.AddToGUIUpdateList(); + tabMenu.AddToGUIUpdateList(); } public override void Update(double deltaTime) { base.Update(deltaTime); + tabMenu.Update(); - if (dummyCharacter is { } dummy && miniMapItem is { } item) + if (dummyCharacter is { } dummy) { - if (dummy.SelectedConstruction != item) - { - dummy.SelectedConstruction = item; - } - dummy.SelectedConstruction?.UpdateHUD(Cam, dummy, (float)deltaTime); - Vector2 pos = FarseerPhysics.ConvertUnits.ToSimUnits(item.Position); - foreach (Limb limb in dummy.AnimController.Limbs) - { - limb.body.SetTransform(pos, 0.0f); - } - - if (dummy.AnimController?.Collider is { } collider) - { - collider.SetTransform(pos, 0); - } - dummy.ControlLocalPlayer((float)deltaTime, Cam, false); dummy.Control((float)deltaTime, Cam); - dummy.Submarine = submarine; } + + GUI.Update((float)deltaTime); } public override void Draw(double deltaTime, GraphicsDevice graphics, SpriteBatch spriteBatch) diff --git a/Barotrauma/BarotraumaClient/LinuxClient.csproj b/Barotrauma/BarotraumaClient/LinuxClient.csproj index 002617924..8bdd4831b 100644 --- a/Barotrauma/BarotraumaClient/LinuxClient.csproj +++ b/Barotrauma/BarotraumaClient/LinuxClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.1500.2.0 + 0.1500.3.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/MacClient.csproj b/Barotrauma/BarotraumaClient/MacClient.csproj index 8ed8a8db7..4dd212c17 100644 --- a/Barotrauma/BarotraumaClient/MacClient.csproj +++ b/Barotrauma/BarotraumaClient/MacClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.1500.2.0 + 0.1500.3.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/WindowsClient.csproj b/Barotrauma/BarotraumaClient/WindowsClient.csproj index f2ef1049a..5df6359c5 100644 --- a/Barotrauma/BarotraumaClient/WindowsClient.csproj +++ b/Barotrauma/BarotraumaClient/WindowsClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.1500.2.0 + 0.1500.3.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaServer/LinuxServer.csproj b/Barotrauma/BarotraumaServer/LinuxServer.csproj index 32989bc6e..5d6dfdf6e 100644 --- a/Barotrauma/BarotraumaServer/LinuxServer.csproj +++ b/Barotrauma/BarotraumaServer/LinuxServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.1500.2.0 + 0.1500.3.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/MacServer.csproj b/Barotrauma/BarotraumaServer/MacServer.csproj index a2648dcd0..26fce29e3 100644 --- a/Barotrauma/BarotraumaServer/MacServer.csproj +++ b/Barotrauma/BarotraumaServer/MacServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.1500.2.0 + 0.1500.3.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/ServerSource/Items/Item.cs b/Barotrauma/BarotraumaServer/ServerSource/Items/Item.cs index c393668e2..c7b6eb206 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Items/Item.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Items/Item.cs @@ -282,6 +282,7 @@ namespace Barotrauma msg.Write(body == null ? (byte)0 : (byte)body.BodyType); msg.Write(SpawnedInOutpost); msg.Write(AllowStealing); + msg.WriteRangedInteger(Quality, 0, Items.Components.Quality.MaxQuality); byte teamID = 0; foreach (WifiComponent wifiComponent in GetComponents()) diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/ServerSettings.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/ServerSettings.cs index 90271e6e3..5da3ddd5f 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/ServerSettings.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/ServerSettings.cs @@ -265,7 +265,7 @@ namespace Barotrauma.Networking "192-255", "384-591", "1024-1279", - "19968-40959","13312-19903","131072-15043983","15043985-173791","173824-178207","178208-183983","63744-64255","194560-195103" //CJK + "19968-21327","21329-40959","13312-19903","131072-173791","173824-178207","178208-183983","63744-64255","194560-195103" //CJK }; string[] allowedClientNameCharsStr = doc.Root.GetAttributeStringArray("AllowedClientNameChars", defaultAllowedClientNameChars); diff --git a/Barotrauma/BarotraumaServer/WindowsServer.csproj b/Barotrauma/BarotraumaServer/WindowsServer.csproj index 4d2b9f8d8..6b4075d6e 100644 --- a/Barotrauma/BarotraumaServer/WindowsServer.csproj +++ b/Barotrauma/BarotraumaServer/WindowsServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.1500.2.0 + 0.1500.3.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Wreck/WreckAI.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Wreck/WreckAI.cs index 3d5ac5a49..dc6e181ae 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Wreck/WreckAI.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Wreck/WreckAI.cs @@ -161,8 +161,8 @@ namespace Barotrauma for (int i = 0; i < container.Inventory.Capacity; i++) { if (container.Inventory.GetItemAt(i) != null) { continue; } - if (MapEntityPrefab.List.GetRandom(e => e is ItemPrefab i && container.CanBeContained(i) && - Config.ForbiddenAmmunition.None(id => id.Equals(i.Identifier, StringComparison.OrdinalIgnoreCase)), Rand.RandSync.Server) is ItemPrefab ammoPrefab) + if (MapEntityPrefab.List.GetRandom(e => e is ItemPrefab ip && container.CanBeContained(ip, i) && + Config.ForbiddenAmmunition.None(id => id.Equals(ip.Identifier, StringComparison.OrdinalIgnoreCase)), Rand.RandSync.Server) is ItemPrefab ammoPrefab) { Item ammo = new Item(ammoPrefab, container.Item.WorldPosition, Wreck); if (!container.Inventory.TryPutItem(ammo, i, allowSwapping: false, allowCombine: false, user: null, createNetworkEvent: false)) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs index 3bbb32214..a42e5d81e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs @@ -1430,6 +1430,7 @@ namespace Barotrauma //throwing conscious/moving characters around takes more force -> double the flow force if (character.CanMove) { flowForce *= 2.0f; } + flowForce *= 1 - Math.Clamp(character.GetStatValue(StatTypes.FlowResistance), 0f, 1f); float flowForceMagnitude = flowForce.Length(); float limbMultipier = limbs.Count(l => l.inWater) / (float)limbs.Length; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs index abd381c65..da6c1677b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs @@ -251,6 +251,8 @@ namespace Barotrauma private readonly List lastAttackers = new List(); public IEnumerable LastAttackers => lastAttackers; public Character LastAttacker => lastAttackers.LastOrDefault()?.Character; + public Character LastOrderedCharacter { get; private set; } + public Character SecondLastOrderedCharacter { get; private set; } public Entity LastDamageSource; @@ -2720,7 +2722,7 @@ namespace Barotrauma //Do ragdoll shenanigans before Stun because it's still technically a stun, innit? Less network updates for us! bool allowRagdoll = GameMain.NetworkMember?.ServerSettings?.AllowRagdollButton ?? true; - bool tooFastToUnragdoll = AnimController.Collider.LinearVelocity.LengthSquared() > 5.0f * 5.0f; + bool tooFastToUnragdoll = AnimController.Collider.LinearVelocity.LengthSquared() > 2.5f * 2.5f; bool wasRagdolled = false; bool selfRagdolled = false; @@ -3131,6 +3133,12 @@ namespace Barotrauma { var abilityOrderedCharacter = new AbilityCharacter(this); orderGiver.CheckTalents(AbilityEffectType.OnGiveOrder, abilityOrderedCharacter); + + if (orderGiver.LastOrderedCharacter != this) + { + orderGiver.SecondLastOrderedCharacter = orderGiver.LastOrderedCharacter; + orderGiver.LastOrderedCharacter = this; + } } if (AIController is HumanAIController humanAI) @@ -3406,6 +3414,13 @@ namespace Barotrauma AddDamage(worldPosition, attackAfflictions, attack.Stun, playSound, attackImpulse, out limbHit, attacker, attack.DamageMultiplier * attackData.DamageMultiplier) : DamageLimb(worldPosition, targetLimb, attackAfflictions, attack.Stun, playSound, attackImpulse, attacker, attack.DamageMultiplier * attackData.DamageMultiplier, penetration: penetration + attackData.AddedPenetration); + if (attacker != null) + { + var abilityAttackResult = new AbilityAttackResult(attackResult); + attacker.CheckTalents(AbilityEffectType.OnAttackResult, abilityAttackResult); + CheckTalents(AbilityEffectType.OnAttackedResult, abilityAttackResult); + } + if (limbHit == null) { return new AttackResult(); } Vector2 forceWorld = attack.TargetImpulseWorld + attack.TargetForceWorld; if (attacker != null) @@ -3623,11 +3638,6 @@ namespace Barotrauma ApplyStatusEffects(ActionType.OnDamaged, 1.0f); hitLimb.ApplyStatusEffects(ActionType.OnDamaged, 1.0f); } - if (attacker != null) - { - var abilityAttackResult = new AbilityAttackResult(attackResult); - attacker.CheckTalents(AbilityEffectType.OnAttackResult, abilityAttackResult); - } return attackResult; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs index e69408bbd..e4190f0ea 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs @@ -979,6 +979,8 @@ namespace Barotrauma increase *= SkillSettings.Current.AssistantSkillIncreaseMultiplier; } + increase *= 1f + Character.GetStatValue(StatTypes.SkillGainSpeed); + float prevLevel = Job.GetSkillLevel(skillIdentifier); Job.IncreaseSkillLevel(skillIdentifier, increase, Character.HasAbilityFlag(AbilityFlags.GainSkillPastMaximum)); @@ -1527,6 +1529,17 @@ namespace Barotrauma return 0f; } } + public float GetSavedStatValue(StatTypes statType, string statIdentifier) + { + if (savedStatValues.TryGetValue(statType, out var statValues)) + { + return statValues.Where(s => s.StatIdentifier.Equals(statIdentifier, StringComparison.OrdinalIgnoreCase)).Sum(v => v.StatValue); + } + else + { + return 0f; + } + } public void ChangeSavedStatValue(StatTypes statType, float value, string statIdentifier, bool removeOnDeath, bool removeAfterRound = false, float maxValue = float.MaxValue) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs index 8f882e2f7..be9258701 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs @@ -971,7 +971,7 @@ namespace Barotrauma /// A dictionary where the key is the identifier of the item and the value the suitability /// If true, the suitability values are normalized between 0 and 1. If not, they're arbitrary values defined in the medical item XML, where negative values are unsuitable, and positive ones suitable. /// Amount of randomization to apply to the values (0 = the values are accurate, 1 = the values are completely random) - public void GetSuitableTreatments(Dictionary treatmentSuitability, bool normalize, Limb limb = null, float randomization = 0.0f) + public void GetSuitableTreatments(Dictionary treatmentSuitability, bool normalize, Limb limb = null, bool ignoreHiddenAfflictions = false, float randomization = 0.0f) { //key = item identifier //float = suitability @@ -980,6 +980,7 @@ namespace Barotrauma foreach (Affliction affliction in getAfflictions(limb)) { if (affliction.Strength < affliction.Prefab.TreatmentThreshold) { continue; } + if (ignoreHiddenAfflictions && affliction.Strength < affliction.Prefab.ShowIconThreshold) { continue; } foreach (KeyValuePair treatment in affliction.Prefab.TreatmentSuitability) { if (!treatmentSuitability.ContainsKey(treatment.Key)) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionIsAiming.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionIsAiming.cs index 324676a98..01de01b61 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionIsAiming.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionIsAiming.cs @@ -7,12 +7,12 @@ namespace Barotrauma.Abilities { private enum WeaponType { - Any = 0, - Melee = 1, + Any = 0, + Melee = 1, Ranged = 2 }; - private WeaponType weapontype; + private readonly WeaponType weapontype; public AbilityConditionIsAiming(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) { switch (conditionElement.GetAttributeString("weapontype", "")) @@ -43,7 +43,7 @@ namespace Barotrauma.Abilities break; default: aimingCorrectItem |= animController.IsAiming || animController.IsAimingMelee; - break; + break; } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionItem.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionItem.cs index ff6cba362..d4eb985d2 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionItem.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionItem.cs @@ -6,12 +6,12 @@ namespace Barotrauma.Abilities { class AbilityConditionItem : AbilityConditionData { - private readonly string identifier; + private readonly string[] identifiers; private readonly string[] tags; public AbilityConditionItem(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) { - identifier = conditionElement.GetAttributeString("identifier", string.Empty).ToLowerInvariant(); + identifiers = conditionElement.GetAttributeStringArray("identifiers", Array.Empty(), convertToLowerInvariant: true); tags = conditionElement.GetAttributeStringArray("tags", Array.Empty(), convertToLowerInvariant: true); } @@ -29,15 +29,15 @@ namespace Barotrauma.Abilities if (itemPrefab != null) { - if (!string.IsNullOrEmpty(identifier)) + if (identifiers.Any()) { - if (itemPrefab.Identifier != identifier) + if (!identifiers.Any(t => itemPrefab.Identifier == t)) { return false; } } - return tags.Any(t => itemPrefab.Tags.Any(p => t == p)); + return !tags.Any() || tags.Any(t => itemPrefab.Tags.Any(p => t == p)); } else { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionHasVelocity.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionHasVelocity.cs new file mode 100644 index 000000000..d3aa75bde --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionHasVelocity.cs @@ -0,0 +1,20 @@ +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class AbilityConditionHasVelocity : AbilityConditionDataless + { + private readonly float velocity; + + public AbilityConditionHasVelocity(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) + { + velocity = conditionElement.GetAttributeFloat("velocity", 0f); + } + + protected override bool MatchesConditionSpecific() + { + return character.AnimController.Collider.LinearVelocity.LengthSquared() > velocity * velocity; + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionShipFlooded.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionShipFlooded.cs index ba5f10ccc..9a99f4ce8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionShipFlooded.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionShipFlooded.cs @@ -13,7 +13,7 @@ namespace Barotrauma.Abilities protected override bool MatchesConditionSpecific() { - if (character.Submarine == null || character.Submarine.TeamID != character.TeamID) { return false; } + if (!character.IsInFriendlySub) { return false; } float currentFloodPercentage = character.Submarine.GetHulls(false).Average(h => h.WaterPercentage); return currentFloodPercentage / 100 > floodPercentage; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffects.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffects.cs index 6dbc6450e..9eb8e20e5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffects.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffects.cs @@ -10,6 +10,7 @@ namespace Barotrauma.Abilities protected readonly List statusEffects; + private readonly bool nearbyCharactersAppliesToSelf; private readonly bool applyToSelected; readonly List targets = new List(); @@ -18,6 +19,7 @@ namespace Barotrauma.Abilities { statusEffects = CharacterAbilityGroup.ParseStatusEffects(CharacterTalent, abilityElement.GetChildElement("statuseffects")); applyToSelected = abilityElement.GetAttributeBool("applytoselected", false); + nearbyCharactersAppliesToSelf = abilityElement.GetAttributeBool("nearbycharactersappliestoself", true); } protected void ApplyEffectSpecific(Character targetCharacter) @@ -26,7 +28,7 @@ namespace Barotrauma.Abilities { if (statusEffect.HasTargetType(StatusEffect.TargetType.UseTarget)) { - // currently used this to spawn items on the targeted character + // currently used to spawn items on the targeted character statusEffect.SetUser(targetCharacter); statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, targetCharacter, targetCharacter); } @@ -34,6 +36,10 @@ namespace Barotrauma.Abilities { targets.Clear(); targets.AddRange(statusEffect.GetNearbyTargets(targetCharacter.WorldPosition, targets)); + if (!nearbyCharactersAppliesToSelf) + { + targets.RemoveAll(c => c == Character); + } statusEffect.SetUser(Character); statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, targetCharacter, targets); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffectsToLastOrderedCharacter.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffectsToLastOrderedCharacter.cs new file mode 100644 index 000000000..fc4291453 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffectsToLastOrderedCharacter.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class CharacterAbilityApplyStatusEffectsToLastOrderedCharacter : CharacterAbilityApplyStatusEffects + { + public CharacterAbilityApplyStatusEffectsToLastOrderedCharacter(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) + { + } + + protected override void ApplyEffect() + { + if (IsViableTarget(Character.LastOrderedCharacter)) + { + ApplyEffectSpecific(Character.LastOrderedCharacter); + } + if (Character.HasAbilityFlag(AbilityFlags.AllowSecondOrderedTarget) && IsViableTarget(Character.SecondLastOrderedCharacter)) + { + ApplyEffectSpecific(Character.SecondLastOrderedCharacter); + } + } + + private bool IsViableTarget(Character targetCharacter) + { + if (targetCharacter == null || targetCharacter.Removed) { return false; } + if (targetCharacter == Character) { return false; } + return true; + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGivePermanentStat.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGivePermanentStat.cs index 55da7e1cc..ea1a181a2 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGivePermanentStat.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGivePermanentStat.cs @@ -27,8 +27,7 @@ namespace Barotrauma.Abilities targetAllies = abilityElement.GetAttributeBool("targetallies", false); removeOnDeath = abilityElement.GetAttributeBool("removeondeath", true); removeAfterRound = abilityElement.GetAttributeBool("removeafterround", false); - giveOnAddingFirstTime = abilityElement.GetAttributeBool("giveonaddingfirsttime", false); - //maximumValue = abilityElement.GetAttributeFloat("maximumvalue", float.MaxValue); + giveOnAddingFirstTime = abilityElement.GetAttributeBool("giveonaddingfirsttime", characterAbilityGroup.AbilityEffectType == AbilityEffectType.None); } public override void InitializeAbility(bool addingFirstTime) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityIncreaseSkill.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityIncreaseSkill.cs index 830d4c9ce..59b532aaf 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityIncreaseSkill.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityIncreaseSkill.cs @@ -1,4 +1,5 @@ -using Microsoft.Xna.Framework; +using Barotrauma.Extensions; +using Microsoft.Xna.Framework; using System.Xml.Linq; namespace Barotrauma.Abilities @@ -7,13 +8,22 @@ namespace Barotrauma.Abilities { public override bool AppliesEffectOnIntervalUpdate => true; - private string skillIdentifier; - private float skillIncrease; + private readonly string skillIdentifier; + private readonly float skillIncrease; public CharacterAbilityIncreaseSkill(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) { skillIdentifier = abilityElement.GetAttributeString("skillidentifier", "").ToLowerInvariant(); skillIncrease = abilityElement.GetAttributeFloat("skillincrease", 0f); + + if (string.IsNullOrEmpty(skillIdentifier)) + { + DebugConsole.ThrowError($"Error in talent \"{characterAbilityGroup.CharacterTalent.DebugIdentifier}\" - skill identifier not defined in CharacterAbilityIncreaseSkill."); + } + if (MathUtils.NearlyEqual(skillIncrease, 0)) + { + DebugConsole.AddWarning($"Possible error in talent \"{characterAbilityGroup.CharacterTalent.DebugIdentifier}\" - skill increase set to 0."); + } } protected override void ApplyEffect() @@ -35,7 +45,17 @@ namespace Barotrauma.Abilities private void ApplyEffectSpecific(Character character) { - character.Info?.IncreaseSkillLevel(skillIdentifier, skillIncrease, character.Position + Vector2.UnitY * 175.0f); + if (skillIdentifier.Equals("random")) + { + var skill = character.Info?.Job?.Skills?.GetRandom(); + if (skill == null) { return; } + character.Info?.IncreaseSkillLevel(skill.Identifier, skillIncrease, character.Position + Vector2.UnitY * 175.0f); + } + else + { + character.Info?.IncreaseSkillLevel(skillIdentifier, skillIncrease, character.Position + Vector2.UnitY * 175.0f); + } + } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyStatToFlooding.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyStatToFlooding.cs new file mode 100644 index 000000000..20dbf654d --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyStatToFlooding.cs @@ -0,0 +1,34 @@ +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class CharacterAbilityModifyStatToFlooding : CharacterAbility + { + private readonly StatTypes statType; + private readonly float maxValue; + private float lastValue = 0f; + + public CharacterAbilityModifyStatToFlooding(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) + { + statType = CharacterAbilityGroup.ParseStatType(abilityElement.GetAttributeString("stattype", ""), CharacterTalent.DebugIdentifier); + maxValue = abilityElement.GetAttributeFloat("maxvalue", 0f); + } + + protected override void VerifyState(bool conditionsMatched, float timeSinceLastUpdate) + { + Character.ChangeStat(statType, -lastValue); + + if (conditionsMatched && Character.IsInFriendlySub) + { + float currentFloodPercentage = Character.Submarine.GetHulls(false).Average(h => h.WaterPercentage); + lastValue = currentFloodPercentage / 100f * maxValue; + Character.ChangeStat(statType, lastValue); + } + else + { + lastValue = 0f; + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyValue.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyValue.cs index 8c12ea122..59a203a7f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyValue.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityModifyValue.cs @@ -4,8 +4,8 @@ namespace Barotrauma.Abilities { class CharacterAbilityModifyValue : CharacterAbility { - private float addedValue; - private float multiplyValue; + private readonly float addedValue; + private readonly float multiplyValue; public CharacterAbilityModifyValue(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityByTheBook.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityByTheBook.cs index de83bf9a9..8bfe07b9d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityByTheBook.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityByTheBook.cs @@ -6,12 +6,14 @@ namespace Barotrauma.Abilities { class CharacterAbilityByTheBook : CharacterAbility { - private int moneyAmount; - private int max; + private readonly int moneyAmount; + private readonly int experienceAmount; + private readonly int max; public CharacterAbilityByTheBook(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) { moneyAmount = abilityElement.GetAttributeInt("moneyamount", 0); + experienceAmount = abilityElement.GetAttributeInt("experienceamount", 0); max = abilityElement.GetAttributeInt("max", 0); } @@ -28,6 +30,10 @@ namespace Barotrauma.Abilities if (!enemyCharacter.LockHands) { continue; } if (timesGiven > max) { continue; } Character.GiveMoney(moneyAmount); + foreach (Character character in Character.GetFriendlyCrew(Character)) + { + character.Info?.GiveExperience(experienceAmount); + } timesGiven++; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityEnigmaMachine.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityEnigmaMachine.cs new file mode 100644 index 000000000..6b034d24b --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityEnigmaMachine.cs @@ -0,0 +1,43 @@ +using System; +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma.Abilities +{ + class CharacterAbilityEnigmaMachine : CharacterAbility + { + private readonly float addedValue; + private readonly float multiplyValue; + private readonly string[] tags; + private readonly int maxMultiplyCount; + + public CharacterAbilityEnigmaMachine(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) + { + addedValue = abilityElement.GetAttributeFloat("addedvalue", 0f); + multiplyValue = abilityElement.GetAttributeFloat("multiplyvalue", 1f); + tags = abilityElement.GetAttributeStringArray("tags", Array.Empty(), convertToLowerInvariant: true); + maxMultiplyCount = abilityElement.GetAttributeInt("maxmultiplycount", int.MaxValue); + } + + protected override void ApplyEffect(AbilityObject abilityObject) + { + if (abilityObject is IAbilityValue abilityValue) + { + int multiplyCount = 0; + + foreach (Item item in Item.ItemList) + { + if (item.Prefab.Tags.Any(t => tags.Contains(t))) + { + multiplyCount++; + if (multiplyCount == maxMultiplyCount) + { + break; + } + } + } + abilityValue.Value += addedValue * multiplyCount; + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityIndustrialRevolution.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityIndustrialRevolution.cs deleted file mode 100644 index 324bf919d..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityIndustrialRevolution.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Barotrauma.Items.Components; -using Microsoft.Xna.Framework; -using System.Collections.Generic; -using System.Xml.Linq; - -namespace Barotrauma.Abilities -{ - class CharacterAbilityIndustrialRevolution : CharacterAbility - { - float addedFabricationSpeed; - - public CharacterAbilityIndustrialRevolution(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) - { - addedFabricationSpeed = abilityElement.GetAttributeFloat("addedfabricationspeed", 0f); - } - - public override void UpdateCharacterAbility(bool conditionsMatched, float timeSinceLastUpdate) - { - if (conditionsMatched) - { - // not necessarily the cleanest or performant way, but at least this shouldn't break anything. - // must be done every frame in order to work. - if (Character.SelectedConstruction?.GetComponent() is Fabricator fabricator && fabricator.IsActive) - { - fabricator.FabricationSpeedMultiplier += addedFabricationSpeed; - } - } - } - } -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityTaskmaster.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityTaskmaster.cs deleted file mode 100644 index 2ae4b6256..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityTaskmaster.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System.Collections.Generic; -using System.Xml.Linq; - -namespace Barotrauma.Abilities -{ - class CharacterAbilityTaskmaster : CharacterAbility - { - private readonly List statusEffects; - private readonly List statusEffectsRemove; - - private Character lastCharacter; - - public CharacterAbilityTaskmaster(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) - { - statusEffects = CharacterAbilityGroup.ParseStatusEffects(CharacterTalent, abilityElement.GetChildElement("statuseffects")); - statusEffectsRemove = CharacterAbilityGroup.ParseStatusEffects(CharacterTalent, abilityElement.GetChildElement("statuseffectsremove")); - } - - protected override void ApplyEffect(AbilityObject abilityObject) - { - if ((abilityObject as IAbilityCharacter)?.Character is Character targetCharacter) - { - if (targetCharacter == Character) { return; } - - foreach (var statusEffect in statusEffectsRemove) - { - statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, Character, lastCharacter); - } - - foreach (var statusEffect in statusEffects) - { - statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, Character, targetCharacter); - } - - lastCharacter = targetCharacter; - } - } - } -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroup.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroup.cs index 1c5471f24..5ada7de4a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroup.cs @@ -14,6 +14,8 @@ namespace Barotrauma.Abilities // currently only used to turn off simulation if random conditions are in use public bool IsActive { get; private set; } = true; + public readonly AbilityEffectType AbilityEffectType; + protected int maxTriggerCount { get; } protected int timesTriggered = 0; @@ -24,8 +26,9 @@ namespace Barotrauma.Abilities // separate dictionaries for each type of characterability? protected readonly List characterAbilities = new List(); - public CharacterAbilityGroup(CharacterTalent characterTalent, XElement abilityElementGroup) + public CharacterAbilityGroup(AbilityEffectType abilityEffectType, CharacterTalent characterTalent, XElement abilityElementGroup) { + AbilityEffectType = abilityEffectType; CharacterTalent = characterTalent; Character = CharacterTalent.Character; maxTriggerCount = abilityElementGroup.GetAttributeInt("maxtriggercount", int.MaxValue); @@ -168,8 +171,7 @@ namespace Barotrauma.Abilities public static StatTypes ParseStatType(string statTypeString, string debugIdentifier) { - StatTypes statType; - if (!Enum.TryParse(statTypeString, true, out statType)) + if (!Enum.TryParse(statTypeString, true, out StatTypes statType)) { DebugConsole.ThrowError("Invalid stat type type \"" + statTypeString + "\" in CharacterTalent (" + debugIdentifier + ")"); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroupEffect.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroupEffect.cs index 55629172f..7249b69bf 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroupEffect.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroupEffect.cs @@ -8,7 +8,8 @@ namespace Barotrauma.Abilities { class CharacterAbilityGroupEffect : CharacterAbilityGroup { - public CharacterAbilityGroupEffect(CharacterTalent characterTalent, XElement abilityElementGroup) : base(characterTalent, abilityElementGroup) { } + public CharacterAbilityGroupEffect(AbilityEffectType abilityEffectType, CharacterTalent characterTalent, XElement abilityElementGroup) : + base(abilityEffectType, characterTalent, abilityElementGroup) { } public void CheckAbilityGroup(AbilityObject abilityObject) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroupInterval.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroupInterval.cs index 4e3d0896f..379b09f46 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroupInterval.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/AbilityGroups/CharacterAbilityGroupInterval.cs @@ -15,7 +15,8 @@ namespace Barotrauma.Abilities private float effectDelayTimer; - public CharacterAbilityGroupInterval(CharacterTalent characterTalent, XElement abilityElementGroup) : base(characterTalent, abilityElementGroup) + public CharacterAbilityGroupInterval(AbilityEffectType abilityEffectType, CharacterTalent characterTalent, XElement abilityElementGroup) : + base(abilityEffectType, characterTalent, abilityElementGroup) { // too many overlapping intervals could cause hitching? maybe randomize a little interval = abilityElementGroup.GetAttributeFloat("interval", 0f); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/CharacterTalent.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/CharacterTalent.cs index 5d8a1587b..703a3c852 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/CharacterTalent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/CharacterTalent.cs @@ -85,14 +85,13 @@ namespace Barotrauma // XML logic private void LoadAbilityGroupInterval(XElement abilityGroup) { - string name = abilityGroup.Name.ToString().ToLowerInvariant(); - characterAbilityGroupIntervals.Add(new CharacterAbilityGroupInterval(this, abilityGroup)); + characterAbilityGroupIntervals.Add(new CharacterAbilityGroupInterval(AbilityEffectType.Undefined, this, abilityGroup)); } private void LoadAbilityGroupEffect(XElement abilityGroup) { AbilityEffectType abilityEffectType = ParseAbilityEffectType(this, abilityGroup.GetAttributeString("abilityeffecttype", "none")); - AddAbilityGroupEffect(new CharacterAbilityGroupEffect(this, abilityGroup), abilityEffectType); + AddAbilityGroupEffect(new CharacterAbilityGroupEffect(abilityEffectType, this, abilityGroup), abilityEffectType); } public void AddAbilityGroupEffect(CharacterAbilityGroupEffect characterAbilityGroup, AbilityEffectType abilityEffectType = AbilityEffectType.None) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/TalentTree.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/TalentTree.cs index 6483edaa1..744ffb132 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/TalentTree.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/TalentTree.cs @@ -1,5 +1,4 @@ -using Microsoft.Xna.Framework; -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Xml.Linq; @@ -8,13 +7,19 @@ namespace Barotrauma { class TalentTree { + public enum TalentTreeStageState + { + Invalid, + Locked, + Unlocked, + Available, + Highlighted + } + public static readonly Dictionary JobTalentTrees = new Dictionary(); public readonly List TalentSubTrees = new List(); - private static HashSet subtreeTalents = new HashSet(); - - private const string PlaceholderTalent = "placeholder"; public XElement ConfigElement { get; @@ -35,14 +40,13 @@ namespace Barotrauma foreach (XElement subTreeElement in element.GetChildElements("subtree")) { - TalentSubTrees.Add(new TalentSubTree(subTreeElement)); + TalentSubTrees.Add(new TalentSubTree(subTreeElement)); } // talents found and unlocked using the identifier wihin the talent tree, so no duplicates may occur HashSet duplicateSet = new HashSet(); foreach (string talent in TalentSubTrees.SelectMany(s => s.TalentOptionStages.SelectMany(o => o.Talents.Select(t => t.Identifier)))) { - if (talent == PlaceholderTalent) { continue; } TalentPrefab talentPrefab = TalentPrefab.TalentPrefabs.Find(c => c.Identifier.Equals(talent, StringComparison.OrdinalIgnoreCase)); if (talentPrefab == null) { @@ -97,7 +101,7 @@ namespace Barotrauma } break; default: - DebugConsole.ThrowError($"Invalid XML root element: '{rootElement.Name.ToString()}' in {file.Path}"); + DebugConsole.ThrowError($"Invalid XML root element: '{rootElement.Name}' in {file.Path}"); break; } } @@ -117,10 +121,63 @@ namespace Barotrauma return IsViableTalentForCharacter(character, talentIdentifier, character?.Info?.UnlockedTalents ?? Enumerable.Empty()); } + // i hate this function - markus + public static TalentTreeStageState GetTalentOptionStageState(Character character, string subTreeIdentifier, int index, List selectedTalents) + { + if (character?.Info?.Job.Prefab is null) { return TalentTreeStageState.Invalid; } + + if (!JobTalentTrees.TryGetValue(character.Info.Job.Prefab.Identifier, out TalentTree talentTree)) { return TalentTreeStageState.Invalid; } + + TalentSubTree subTree = talentTree.TalentSubTrees.FirstOrDefault(tst => tst.Identifier == subTreeIdentifier); + + if (subTree == null) { return TalentTreeStageState.Invalid; } + + TalentOption targetTalentOption = subTree.TalentOptionStages[index]; + + if (targetTalentOption.Talents.Any(t => character.HasTalent(t.Identifier))) + { + return TalentTreeStageState.Unlocked; + } + + if (targetTalentOption.Talents.Any(t => selectedTalents.Contains(t.Identifier))) + { + return TalentTreeStageState.Highlighted; + } + + bool hasTalentInLastTier = true; + bool isLastTalentPurchased = true; + + int lastindex = index - 1; + if (lastindex >= 0) + { + TalentOption lastLatentOption = subTree.TalentOptionStages[lastindex]; + hasTalentInLastTier = lastLatentOption.Talents.Any(HasTalent); + isLastTalentPurchased = lastLatentOption.Talents.Any(t => character.HasTalent(t.Identifier)); + } + + if (!hasTalentInLastTier) + { + return TalentTreeStageState.Locked; + } + + bool hasPointsForNewTalent = character.Info.GetTotalTalentPoints() - selectedTalents.Count > 0; + + if (hasPointsForNewTalent) + { + return isLastTalentPurchased ? TalentTreeStageState.Highlighted : TalentTreeStageState.Available; + } + + return TalentTreeStageState.Locked; + + bool HasTalent(TalentPrefab t) + { + return selectedTalents.Contains(t.Identifier); + } + } + public static bool IsViableTalentForCharacter(Character character, string talentIdentifier, IEnumerable selectedTalents) { - if (talentIdentifier == PlaceholderTalent) { return false; } if (character?.Info?.Job.Prefab == null) { return false; } if (character.Info.GetTotalTalentPoints() - selectedTalents.Count() <= 0) { return false; } @@ -173,12 +230,16 @@ namespace Barotrauma { public string Identifier { get; } + public string DisplayName { get; } + public readonly List TalentOptionStages = new List(); public TalentSubTree(XElement subTreeElement) { Identifier = subTreeElement.GetAttributeString("identifier", ""); + DisplayName = TextManager.Get("talenttree." + Identifier, returnNull: true) ?? Identifier; + foreach (XElement talentOptionsElement in subTreeElement.GetChildElements("talentoptions")) { TalentOptionStages.Add(new TalentOption(talentOptionsElement, Identifier)); @@ -196,6 +257,7 @@ namespace Barotrauma foreach (XElement talentOptionElement in talentOptionsElement.GetChildElements("talentoption")) { string identifier = talentOptionElement.GetAttributeString("identifier", string.Empty); + if (!TalentPrefab.TalentPrefabs.ContainsKey(identifier)) { DebugConsole.ThrowError($"Error in talent tree \"{debugIdentifier}\" - could not find a talent with the identifier \"{identifier}\"."); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Enums.cs b/Barotrauma/BarotraumaShared/SharedSource/Enums.cs index be70e66b9..20acddd27 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Enums.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Enums.cs @@ -12,16 +12,16 @@ public enum ActionType { - Always, OnPicked, OnUse, OnSecondaryUse, - OnWearing, OnContaining, OnContained, OnNotContained, - OnActive, OnFailure, OnBroken, - OnFire, InWater, NotInWater, - OnImpact, - OnEating, - OnDamaged, - OnSevered, - OnProduceSpawned, - OnOpen, OnClose, + Always = 0, OnPicked = 1, OnUse = 2, OnSecondaryUse = 3, + OnWearing = 4, OnContaining = 5, OnContained = 6, OnNotContained = 7, + OnActive = 8, OnFailure = 9, OnBroken = 10, + OnFire = 11, InWater = 12, NotInWater = 13, + OnImpact = 14, + OnEating = 15, + OnDamaged = 16, + OnSevered = 17, + OnProduceSpawned = 18, + OnOpen = 19, OnClose = 20, OnDeath = OnBroken, OnSuccess, OnAbility, @@ -34,6 +34,7 @@ OnAttack, OnAttackResult, OnAttacked, + OnAttackedResult, OnGainSkillPoint, OnAllyGainSkillPoint, OnRepairComplete, @@ -57,7 +58,9 @@ OnGainMissionMoney, OnItemDeconstructed, OnItemDeconstructedMaterial, + OnItemDeconstructedRetainProbability, OnStopTinkering, + OnItemPicked, AfterSubmarineAttacked, } @@ -78,26 +81,37 @@ BuffDurationMultiplier, DebuffDurationMultiplier, MedicalItemEffectivenessMultiplier, + FlowResistance, // Combat AttackMultiplier, TeamAttackMultiplier, RangedAttackSpeed, TurretAttackSpeed, TurretPowerCostReduction, + TurretChargeSpeed, MeleeAttackSpeed, MeleeAttackMultiplier, + RangedAttackMultiplier, RangedSpreadReduction, // Utility RepairSpeed, DeconstructorSpeedMultiplier, TinkeringDuration, + RepairToolStructureRepairMultiplier, + RepairToolStructureDamageMultiplier, + RepairToolDeattachTimeMultiplier, + MaxRepairConditionMultiplier, + IncreaseFabricationQuality, + GeneticMaterialRefineBonus, + GeneticMaterialTaintedProbabilityReductionOnCombine, + SkillGainSpeed, // Misc ReputationGainMultiplier, MissionMoneyGainMultiplier, ExperienceGainMultiplier, MissionExperienceGainMultiplier, // these should be deprecated and moved to their own implementation, no sense making them share space with stat values - Coathor, + Coauthor, WarriorPoetMissionRuns, WarriorPoetEnemiesKilled, } @@ -113,7 +127,8 @@ CanTinkerFabricatorsAndDeconstructors, TinkeringPowersDevices, GainSkillPastMaximum, - RetainExperienceForNewCharacter + RetainExperienceForNewCharacter, + AllowSecondOrderedTarget, } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/AbandonedOutpostMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/AbandonedOutpostMission.cs index 55e7366cf..7869cd1b0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/AbandonedOutpostMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/AbandonedOutpostMission.cs @@ -306,7 +306,7 @@ namespace Barotrauma case 0: if (items.All(it => it.Removed || it.Condition <= 0.0f) && - requireKill.All(c => c.Removed || c.IsDead) && + requireKill.All(c => c.Removed || c.IsDead || (c.LockHands && c.Submarine == Submarine.MainSub)) && requireRescue.All(c => c.Submarine?.Info.Type == SubmarineType.Player)) { State = 1; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/PirateMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/PirateMission.cs index 6d7319de6..2374eefea 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/PirateMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/PirateMission.cs @@ -382,11 +382,11 @@ namespace Barotrauma State = newState; } - private bool CheckWinState() => !IsClient && (characters.All(m => !Survived(m))); + private bool CheckWinState() => !IsClient && characters.All(m => DeadOrCaptured(m)); - private bool Survived(Character character) + private bool DeadOrCaptured(Character character) { - return character != null && !character.Removed && !character.IsDead; + return character != null && !character.Removed && (character.IsDead || (character.LockHands && character.Submarine == Submarine.MainSub)); } public override void End() diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/SalvageMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/SalvageMission.cs index 36f611211..e2b586e73 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/SalvageMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/SalvageMission.cs @@ -247,8 +247,8 @@ namespace Barotrauma public override void End() { - var root = item.GetRootContainer() ?? item; - if (root.CurrentHull?.Submarine == null || (!root.CurrentHull.Submarine.AtEndExit && !root.CurrentHull.Submarine.AtStartExit) || item.Removed) + var root = item?.GetRootContainer() ?? item; + if (root?.CurrentHull?.Submarine == null || (!root.CurrentHull.Submarine.AtEndExit && !root.CurrentHull.Submarine.AtStartExit) || item.Removed) { return; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Extensions/VectorExtensions.cs b/Barotrauma/BarotraumaShared/SharedSource/Extensions/VectorExtensions.cs index 479b031d3..25c6dba97 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Extensions/VectorExtensions.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Extensions/VectorExtensions.cs @@ -92,5 +92,12 @@ namespace Barotrauma.Extensions { return MathUtils.NearlyEqual(v.X, other.X) && MathUtils.NearlyEqual(v.Y, other.Y); } + + public static Vector2 Pad(this Vector2 v, Vector4 padding) + { + v.X += padding.X + padding.Z; + v.Y += padding.Y + padding.W; + return v; + } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/GeneticMaterial.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/GeneticMaterial.cs index 60b304478..3a1381b97 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/GeneticMaterial.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/GeneticMaterial.cs @@ -60,7 +60,6 @@ namespace Barotrauma.Items.Components } } - public GeneticMaterial(Item item, XElement element) : base(item, element) { @@ -85,7 +84,7 @@ namespace Barotrauma.Items.Components public bool CanBeCombinedWith(GeneticMaterial otherGeneticMaterial) { - return !tainted && otherGeneticMaterial != null && !otherGeneticMaterial.tainted; + return !tainted && otherGeneticMaterial != null && !otherGeneticMaterial.tainted && item.AllowDeconstruct && otherGeneticMaterial.item.AllowDeconstruct; } public override void Equip(Character character) @@ -147,9 +146,12 @@ namespace Barotrauma.Items.Components public bool Combine(GeneticMaterial otherGeneticMaterial, Character user) { if (!CanBeCombinedWith(otherGeneticMaterial)) { return false; } + + float conditionIncrease = Rand.Range(ConditionIncreaseOnCombineMin, ConditionIncreaseOnCombineMax); + conditionIncrease *= 1.0f + user.GetStatValue(StatTypes.GeneticMaterialRefineBonus); if (item.Prefab == otherGeneticMaterial.item.Prefab) { - item.Condition = Math.Max(item.Condition, otherGeneticMaterial.item.Condition) + Rand.Range(ConditionIncreaseOnCombineMin, ConditionIncreaseOnCombineMax); + item.Condition = Math.Max(item.Condition, otherGeneticMaterial.item.Condition) + conditionIncrease; float taintedProbability = GetTaintedProbabilityOnRefine(user); if (taintedProbability >= Rand.Range(0.0f, 1.0f)) { @@ -160,9 +162,14 @@ namespace Barotrauma.Items.Components else { item.Condition = otherGeneticMaterial.Item.Condition = - (item.Condition + otherGeneticMaterial.Item.Condition) / 2.0f + Rand.Range(ConditionIncreaseOnCombineMin, ConditionIncreaseOnCombineMax); + (item.Condition + otherGeneticMaterial.Item.Condition) / 2.0f + conditionIncrease; item.OwnInventory?.TryPutItem(otherGeneticMaterial.Item, user: null); - MakeTainted(); + item.AllowDeconstruct = false; + otherGeneticMaterial.Item.AllowDeconstruct = false; + if (GetTaintedProbabilityOnCombine(user) >= Rand.Range(0.0f, 1.0f)) + { + MakeTainted(); + } return false; } } @@ -172,7 +179,14 @@ namespace Barotrauma.Items.Components if (user == null) { return 1.0f; } float probability = MathHelper.Lerp(0.0f, 0.99f, item.Condition / 100.0f); probability *= MathHelper.Lerp(1.0f, 0.25f, DegreeOfSuccess(user)); - return probability; + return MathHelper.Clamp(probability, 0.0f, 1.0f); + } + + private float GetTaintedProbabilityOnCombine(Character user) + { + if (user == null) { return 1.0f; } + float probability = 1.0f - user.GetStatValue(StatTypes.GeneticMaterialTaintedProbabilityReductionOnCombine); + return MathHelper.Clamp(probability, 0.0f, 1.0f); } private void MakeTainted() diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Pickable.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Pickable.cs index dbac3d4fb..49abae0d4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Pickable.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Pickable.cs @@ -1,4 +1,5 @@ -using Barotrauma.Networking; +using Barotrauma.Abilities; +using Barotrauma.Networking; using Microsoft.Xna.Framework; using System; using System.Collections.Generic; @@ -73,12 +74,15 @@ namespace Barotrauma.Items.Components if (PickingTime > 0.0f) { + var abilityPickingTime = new AbilityValueItem(PickingTime, item.Prefab); + picker.CheckTalents(AbilityEffectType.OnItemPicked, abilityPickingTime); + if ((picker.PickingItem == null || picker.PickingItem == item) && PickingTime <= float.MaxValue) { #if SERVER item.CreateServerEvent(this); #endif - pickingCoroutine = CoroutineManager.StartCoroutine(WaitForPick(picker, PickingTime)); + pickingCoroutine = CoroutineManager.StartCoroutine(WaitForPick(picker, abilityPickingTime.Value)); } return false; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RepairTool.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RepairTool.cs index ddf06b33f..1ed8b6edc 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RepairTool.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RepairTool.cs @@ -523,7 +523,20 @@ namespace Barotrauma.Items.Components ApplyStatusEffectsOnTarget(user, deltaTime, ActionType.OnUse, new ISerializableEntity[] { targetStructure }); FixStructureProjSpecific(user, deltaTime, targetStructure, sectionIndex); - targetStructure.AddDamage(sectionIndex, -StructureFixAmount * degreeOfSuccess, user); + + float structureFixAmount = StructureFixAmount; + if (structureFixAmount >= 0f) + { + structureFixAmount *= 1 + user.GetStatValue(StatTypes.RepairToolStructureRepairMultiplier); + structureFixAmount *= 1 + item.GetQualityModifier(Quality.StatType.RepairToolStructureRepairMultiplier); + } + else + { + structureFixAmount *= 1 + user.GetStatValue(StatTypes.RepairToolStructureDamageMultiplier); + structureFixAmount *= 1 + item.GetQualityModifier(Quality.StatType.RepairToolStructureDamageMultiplier); + } + + targetStructure.AddDamage(sectionIndex, -structureFixAmount * degreeOfSuccess, user); //if the next section is small enough, apply the effect to it as well //(to make it easier to fix a small "left-over" section) @@ -535,7 +548,7 @@ namespace Barotrauma.Items.Components (nextSectionLength > 0 && nextSectionLength < Structure.WallSectionSize * 0.3f)) { //targetStructure.HighLightSection(sectionIndex + i); - targetStructure.AddDamage(sectionIndex + i, -StructureFixAmount * degreeOfSuccess); + targetStructure.AddDamage(sectionIndex + i, -structureFixAmount * degreeOfSuccess); } } return true; @@ -606,7 +619,8 @@ namespace Barotrauma.Items.Components levelResource.requiredItems.Any() && levelResource.HasRequiredItems(user, addMessage: false)) { - levelResource.DeattachTimer += deltaTime; + float addedDetachTime = deltaTime * (1f + user.GetStatValue(StatTypes.RepairToolDeattachTimeMultiplier)) * item.GetQualityModifier(Quality.StatType.RepairToolDeattachTimeMultiplier); + levelResource.DeattachTimer += addedDetachTime; #if CLIENT Character.Controlled?.UpdateHUDProgressBar( this, diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs index ba479089d..f97b2f01f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs @@ -61,7 +61,7 @@ namespace Barotrauma.Items.Components public int Capacity { get { return capacity; } - set { capacity = Math.Max(value, 1); } + set { capacity = Math.Max(value, 0); } } //how many items can be contained @@ -86,15 +86,9 @@ namespace Barotrauma.Items.Components } } -#if DEBUG - [Editable] -#endif [Serialize("0.0,0.0", false, description: "The position where the contained items get drawn at (offset from the upper left corner of the sprite in pixels).")] public Vector2 ItemPos { get; set; } -#if DEBUG - [Editable] -#endif [Serialize("0.0,0.0", false, description: "The interval at which the contained items are spaced apart from each other (in pixels).")] public Vector2 ItemInterval { get; set; } @@ -329,11 +323,24 @@ namespace Barotrauma.Items.Components { return slotRestrictions.Any(s => s.MatchesItem(item)); } + + public bool CanBeContained(Item item, int index) + { + if (index < 0 || index >= capacity) { return false; } + return slotRestrictions[index].MatchesItem(item); + } + public bool CanBeContained(ItemPrefab itemPrefab) { return slotRestrictions.Any(s => s.MatchesItem(itemPrefab)); } + public bool CanBeContained(ItemPrefab itemPrefab, int index) + { + if (index < 0 || index >= capacity) { return false; } + return slotRestrictions[index].MatchesItem(itemPrefab); + } + readonly List targets = new List(); public override void Update(float deltaTime, Camera cam) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Deconstructor.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Deconstructor.cs index 18996185f..158af353e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Deconstructor.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Deconstructor.cs @@ -256,6 +256,17 @@ namespace Barotrauma.Items.Components } } + if (user != null && !user.Removed) + { + var deconstructItemRetainProbability = new AbilityValueItem(0f, targetItem.Prefab); + user.CheckTalents(AbilityEffectType.OnItemDeconstructedRetainProbability, deconstructItemRetainProbability); + + if (deconstructItemRetainProbability.Value > Rand.Range(0f, 1f, Rand.RandSync.Unsynced)) + { + allowRemove = false; + } + } + if (targetItem.AllowDeconstruct && allowRemove) { //drop all items that are inside the deconstructed item diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs index d47c21d23..f5cb14ba8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs @@ -24,12 +24,6 @@ namespace Barotrauma.Items.Components private Character user; - public float FabricationSpeedMultiplier - { - get; - set; - } - private ItemContainer inputContainer, outputContainer; [Serialize(1.0f, true)] @@ -249,7 +243,6 @@ namespace Barotrauma.Items.Components var availableIngredients = GetAvailableIngredients(); if (fabricatedItem == null || !CanBeFabricated(fabricatedItem, availableIngredients, user)) { - FabricationSpeedMultiplier = 1f; CancelFabricating(); return; } @@ -286,8 +279,7 @@ namespace Barotrauma.Items.Components if (powerConsumption <= 0) { Voltage = 1.0f; } - timeUntilReady -= deltaTime * Math.Min(Voltage, 1.0f) * FabricationSpeedMultiplier; - FabricationSpeedMultiplier = 1f; + timeUntilReady -= deltaTime * Math.Min(Voltage, 1.0f); UpdateRequiredTimeProjSpecific(); @@ -328,13 +320,21 @@ namespace Barotrauma.Items.Components var fabricationValueItem = new AbilityValueItem(fabricatedItem.Amount, fabricatedItem.TargetItem); - if (user != null) + int quality = 0; + if (user?.Info != null) { foreach (Character character in Character.CharacterList.Where(c => c.TeamID == user.TeamID)) { character.CheckTalents(AbilityEffectType.OnAllyItemFabricatedAmount, fabricationValueItem); } user.CheckTalents(AbilityEffectType.OnItemFabricatedAmount, fabricationValueItem); + + float floatQuality = 0.0f; + foreach (string tag in fabricatedItem.TargetItem.Tags) + { + floatQuality += user.Info.GetSavedStatValue(StatTypes.IncreaseFabricationQuality, tag); + } + quality = (int)floatQuality; } var tempUser = user; @@ -343,12 +343,20 @@ namespace Barotrauma.Items.Components if (i < amountFittingContainer) { Entity.Spawner.AddToSpawnQueue(fabricatedItem.TargetItem, outputContainer.Inventory, fabricatedItem.TargetItem.Health * fabricatedItem.OutCondition, - onSpawned: (Item spawnedItem) => { onItemSpawned(spawnedItem, tempUser); }); + onSpawned: (Item spawnedItem) => + { + onItemSpawned(spawnedItem, tempUser); + spawnedItem.Quality = quality; + }); } else { Entity.Spawner.AddToSpawnQueue(fabricatedItem.TargetItem, item.Position, item.Submarine, fabricatedItem.TargetItem.Health * fabricatedItem.OutCondition, - onSpawned: (Item spawnedItem) => { onItemSpawned(spawnedItem, tempUser); }); + onSpawned: (Item spawnedItem) => + { + onItemSpawned(spawnedItem, tempUser); + spawnedItem.Quality = quality; + }); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Pump.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Pump.cs index 7b00dd94e..bacb23c6e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Pump.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Pump.cs @@ -116,6 +116,8 @@ namespace Barotrauma.Items.Components item.CurrentHull.WaterVolume += currFlow; if (item.CurrentHull.WaterVolume > item.CurrentHull.Volume) { item.CurrentHull.Pressure += 0.5f; } + + Voltage -= deltaTime; } public void InfectBallast(string identifier, bool allowMultiplePerShip = false) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Quality.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Quality.cs new file mode 100644 index 000000000..3bfab45f0 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Quality.cs @@ -0,0 +1,72 @@ +using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; +using System.Xml.Linq; + +namespace Barotrauma.Items.Components +{ + partial class Quality : ItemComponent + { + public const int MaxQuality = 3; + + public enum StatType + { + Condition, + ExplosionRadius, + ExplosionDamage, + RepairSpeed, + RepairToolStructureRepairMultiplier, + RepairToolStructureDamageMultiplier, + RepairToolDeattachTimeMultiplier, + // unused as of now + AttackMultiplier, + AttackSpeedMultiplier, + ForceDoorsOpenSpeedMultiplier, + RangedSpreadReduction, + ChargeSpeedMultiplier, + MovementSpeedMultiplier, + // generic stats to be used for various needs, declared just in case (localization) + EffectivenessMultiplier, + PowerOutputMultiplier, + ConsumptionReductionMultiplier, + } + + private readonly Dictionary statValues = new Dictionary(); + + private int qualityLevel; + + [Serialize(0, false)] + public int QualityLevel + { + get { return qualityLevel; } + set { qualityLevel = MathHelper.Clamp(value, 0, MaxQuality); } + } + + public Quality(Item item, XElement element) : base(item, element) + { + foreach (XElement subElement in element.Elements()) + { + switch (subElement.Name.ToString().ToLower()) + { + case "stattype": + case "statvalue": + case "qualitystat": + string statTypeString = subElement.GetAttributeString("stattype", ""); + if (!Enum.TryParse(statTypeString, true, out StatType statType)) + { + DebugConsole.ThrowError("Invalid stat type type \"" + statTypeString + "\" in item (" + item.prefab.Identifier + ")"); + } + float statValue = subElement.GetAttributeFloat("value", 0f); + statValues.TryAdd(statType, statValue); + break; + } + } + } + + public float GetValue(StatType statType) + { + if (!statValues.ContainsKey(statType)) { return 0.0f; } + return statValues[statType] * qualityLevel; + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/RemoteController.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/RemoteController.cs index 783253c89..1903a74c0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/RemoteController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/RemoteController.cs @@ -35,6 +35,7 @@ namespace Barotrauma.Items.Components public RemoteController(Item item, XElement element) : base(item, element) { + DrawHudWhenEquipped = false; } public override bool Select(Character character) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs index 65a531190..5fbb40733 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs @@ -11,7 +11,7 @@ namespace Barotrauma.Items.Components { partial class Repairable : ItemComponent, IServerSerializable, IClientSerializable { - private string header; + private readonly string header; private float deteriorationTimer; private float deteriorateAlwaysResetTimer; @@ -182,6 +182,10 @@ namespace Barotrauma.Items.Components if (Rand.Range(0.0f, 0.5f) < RepairDegreeOfSuccess(character, requiredSkills)) { return true; } ApplyStatusEffects(ActionType.OnFailure, 1.0f, character); + if (bestRepairItem != null && bestRepairItem.GetComponent() is Holdable h) + { + h.ApplyStatusEffects(ActionType.OnFailure, 1.0f, character); + } return false; } @@ -217,6 +221,11 @@ namespace Barotrauma.Items.Components { GameServer.Log($"{GameServer.CharacterLogName(character)} failed to {(action == FixActions.Sabotage ? "sabotage" : "repair")} {item.Name}", ServerLog.MessageType.ItemInteraction); GameMain.Server?.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnFailure, this, character.ID }); + if (bestRepairItem != null && bestRepairItem.GetComponent() is Holdable h) + { + GameMain.Server?.CreateEntityEvent(bestRepairItem, new object[] { NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnFailure, h, character.ID }); + } + return false; } @@ -243,7 +252,7 @@ namespace Barotrauma.Items.Components } return true; - Item GetBestRepairItem(Character character) + static Item GetBestRepairItem(Character character) { return character.HeldItems.OrderByDescending(i => i.Prefab.AddedRepairSpeedMultiplier).FirstOrDefault(); } @@ -386,6 +395,9 @@ namespace Barotrauma.Items.Components float fixDuration = MathHelper.Lerp(FixDurationLowSkill, FixDurationHighSkill, successFactor); fixDuration /= 1 + CurrentFixer.GetStatValue(StatTypes.RepairSpeed) + currentRepairItem?.Prefab.AddedRepairSpeedMultiplier ?? 0f; + fixDuration /= 1 + item.GetQualityModifier(Quality.StatType.RepairSpeed); + + item.MaxRepairConditionMultiplier = 1 + CurrentFixer.GetStatValue(StatTypes.MaxRepairConditionMultiplier); if (currentFixerAction == FixActions.Repair) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs index fbf1e9961..759893105 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs @@ -383,6 +383,10 @@ namespace Barotrauma.Items.Components else { float chargeDeltaTime = tryingToCharge ? deltaTime : -deltaTime; + if (chargeDeltaTime > 0f && user != null) + { + chargeDeltaTime *= 1f + user.GetStatValue(StatTypes.TurretChargeSpeed); + } currentChargeTime = Math.Clamp(currentChargeTime + chargeDeltaTime, 0f, MaxChargeTime); } tryingToCharge = false; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Wearable.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Wearable.cs index be1b859b6..5c3ccc0b8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Wearable.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Wearable.cs @@ -285,7 +285,7 @@ namespace Barotrauma.Items.Components int i = 0; foreach (XElement subElement in element.Elements()) { - switch (subElement.Name.ToString().ToLower()) + switch (subElement.Name.ToString().ToLowerInvariant()) { case "sprite": if (subElement.Attribute("texture") == null) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs index 6e6cfb9a0..119d7ddd5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs @@ -97,13 +97,15 @@ namespace Barotrauma private Dictionary connections; - private List repairables; + private readonly List repairables; - private Queue impactQueue = new Queue(); + private Quality qualityComponent; + + private readonly Queue impactQueue = new Queue(); //a dictionary containing lists of the status effects in all the components of the item - private bool[] hasStatusEffectsOfType; - private Dictionary> statusEffectLists; + private readonly bool[] hasStatusEffectsOfType; + private readonly Dictionary> statusEffectLists; public Dictionary SerializableProperties { get; protected set; } @@ -447,7 +449,7 @@ namespace Barotrauma } public bool IsFullCondition => MathUtils.NearlyEqual(Condition, MaxCondition); - public float MaxCondition => Prefab.Health * healthMultiplier; + public float MaxCondition => Prefab.Health * healthMultiplier * maxRepairConditionMultiplier * (1.0f + GetQualityModifier(Items.Components.Quality.StatType.Condition)); public float ConditionPercentage => MathUtils.Percentage(Condition, MaxCondition); private float offsetOnSelectedMultiplier = 1.0f; @@ -465,12 +467,18 @@ namespace Barotrauma public float HealthMultiplier { get => healthMultiplier; - set - { - healthMultiplier = value; - } + set { healthMultiplier = MathHelper.Clamp(value, 0.0f, float.PositiveInfinity); } } - + + private float maxRepairConditionMultiplier = 1.0f; + + [Serialize(1.0f, true)] + public float MaxRepairConditionMultiplier + { + get => maxRepairConditionMultiplier; + set { maxRepairConditionMultiplier = MathHelper.Clamp(value, 0.0f, float.PositiveInfinity); } + } + //the default value should be Prefab.Health, but because we can't use it in the attribute, //we'll just use NaN (which does nothing) and set the default value in the constructor/load [Serialize(float.NaN, false), Editable] @@ -618,6 +626,21 @@ namespace Barotrauma get { return Prefab.UseInHealthInterface; } } + public int Quality + { + get + { + return qualityComponent?.QualityLevel ?? 0; + } + set + { + if (qualityComponent != null) + { + qualityComponent.QualityLevel = value; + } + } + } + public bool InWater { get @@ -933,6 +956,8 @@ namespace Barotrauma ownInventory = itemContainer.Inventory; } + qualityComponent = GetComponent(); + InitProjSpecific(); if (callOnItemLoaded) @@ -1122,6 +1147,11 @@ namespace Barotrauma if (!componentsByType.ContainsKey(typeof(T))) { return Enumerable.Empty(); } return components.Where(c => c is T).Cast(); } + + public float GetQualityModifier(Quality.StatType statType) + { + return GetComponent()?.GetValue(statType) ?? 0.0f; + } public void RemoveContained(Item contained) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/ItemInventory.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/ItemInventory.cs index bcefbec04..954ab96af 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/ItemInventory.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/ItemInventory.cs @@ -47,14 +47,14 @@ namespace Barotrauma { if (ItemOwnsSelf(item)) { return false; } if (i < 0 || i >= slots.Length) { return false; } - if (!container.CanBeContained(item)) { return false; } + if (!container.CanBeContained(item, i)) { return false; } return item != null && slots[i].CanBePut(item, ignoreCondition) && slots[i].ItemCount < container.GetMaxStackSize(i); } public override bool CanBePutInSlot(ItemPrefab itemPrefab, int i, float? condition) { if (i < 0 || i >= slots.Length) { return false; } - if (!container.CanBeContained(itemPrefab)) { return false; } + if (!container.CanBeContained(itemPrefab, i)) { return false; } return itemPrefab != null && slots[i].CanBePut(itemPrefab, condition) && slots[i].ItemCount < container.GetMaxStackSize(i); } @@ -62,7 +62,7 @@ namespace Barotrauma { if (itemPrefab == null) { return 0; } if (i < 0 || i >= slots.Length) { return 0; } - if (!container.CanBeContained(itemPrefab)) { return 0; } + if (!container.CanBeContained(itemPrefab, i)) { return 0; } return slots[i].HowManyCanBePut(itemPrefab, maxStackSize: Math.Min(itemPrefab.MaxStackSize, container.GetMaxStackSize(i)), condition); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs index 67a117dde..54f1daca4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs @@ -370,6 +370,9 @@ namespace Barotrauma [Serialize(false, false, description: "Hides the condition bar displayed at the bottom of the inventory slot the item is in.")] public bool HideConditionBar { get; set; } + [Serialize(false, false, description: "Hides the condition displayed in the item's tooltip.")] + public bool HideConditionInTooltip { get; set; } + //if true and the item has trigger areas defined, characters need to be within the trigger to interact with the item //if false, trigger areas define areas that can be used to highlight the item [Serialize(true, false)] @@ -1164,6 +1167,8 @@ namespace Barotrauma DefaultPrice ??= new PriceInfo(GetMinPrice() ?? 0, false); } + HideConditionInTooltip = element.GetAttributeBool("hideconditionintooltip", HideConditionBar); + //backwards compatibility if (categoryStr.Equals("Thalamus", StringComparison.OrdinalIgnoreCase)) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Explosion.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Explosion.cs index 4b657de2a..3d0f8ee77 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Explosion.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Explosion.cs @@ -128,6 +128,11 @@ namespace Barotrauma } float displayRange = Attack.Range; + if (damageSource is Item sourceItem) + { + displayRange *= 1.0f + sourceItem.GetQualityModifier(Quality.StatType.ExplosionRadius); + Attack.DamageMultiplier *= 1.0f + sourceItem.GetQualityModifier(Quality.StatType.ExplosionDamage); + } Vector2 cameraPos = GameMain.GameScreen.Cam.Position; float cameraDist = Vector2.Distance(cameraPos, worldPosition) / 2.0f; @@ -142,7 +147,7 @@ namespace Barotrauma if (displayRange < 0.1f) { return; } - if (Attack.GetStructureDamage(1.0f) > 0.0f || Attack.GetLevelWallDamage(1.0f) > 0.0f) + if (!MathUtils.NearlyEqual(Attack.GetStructureDamage(1.0f), 0.0f) || !MathUtils.NearlyEqual(Attack.GetLevelWallDamage(1.0f), 0.0f)) { RangedStructureDamage(worldPosition, displayRange, Attack.GetStructureDamage(1.0f), Attack.GetLevelWallDamage(1.0f), attacker); } @@ -211,9 +216,9 @@ namespace Barotrauma float dist = Vector2.Distance(item.WorldPosition, worldPosition); float itemRadius = item.body == null ? 0.0f : item.body.GetMaxExtent(); dist = Math.Max(0.0f, dist - ConvertUnits.ToDisplayUnits(itemRadius)); - if (dist > Attack.Range) { continue; } + if (dist > displayRange) { continue; } - if (dist < Attack.Range * 0.5f && applyFireEffects && !item.FireProof && ignoreFireEffectsForTags.None(t => item.HasTag(t))) + if (dist < displayRange * 0.5f && applyFireEffects && !item.FireProof && ignoreFireEffectsForTags.None(t => item.HasTag(t))) { //don't apply OnFire effects if the item is inside a fireproof container //(or if it's inside a container that's inside a fireproof container, etc) @@ -240,7 +245,7 @@ namespace Barotrauma if (item.Prefab.DamagedByExplosions && !item.Indestructible) { - float distFactor = 1.0f - dist / Attack.Range; + float distFactor = 1.0f - dist / displayRange; float damageAmount = Attack.GetItemDamage(1.0f) * item.Prefab.ExplosionDamageMultiplier; Vector2 explosionPos = worldPosition; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Serialization/XMLExtensions.cs b/Barotrauma/BarotraumaShared/SharedSource/Serialization/XMLExtensions.cs index 0e594bbe7..21fa03573 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Serialization/XMLExtensions.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Serialization/XMLExtensions.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.IO; using System.Linq; using System.Text; using System.Xml; @@ -8,6 +9,7 @@ using System.Xml.Linq; using Microsoft.Xna.Framework; using File = Barotrauma.IO.File; using FileStream = Barotrauma.IO.FileStream; +using Path = Barotrauma.IO.Path; namespace Barotrauma { @@ -18,11 +20,12 @@ namespace Barotrauma public static readonly XmlReaderSettings ReaderSettings = new XmlReaderSettings { DtdProcessing = DtdProcessing.Prohibit, - XmlResolver = null + XmlResolver = null, + IgnoreWhitespace = true, }; - - public static XmlReader CreateReader(System.IO.Stream stream) - => XmlReader.Create(stream, ReaderSettings); + + public static XmlReader CreateReader(System.IO.Stream stream, string baseUri = "") + => XmlReader.Create(stream, ReaderSettings, baseUri); public static XDocument TryLoadXml(System.IO.Stream stream) { @@ -52,8 +55,8 @@ namespace Barotrauma { ToolBox.IsProperFilenameCase(filePath); using FileStream stream = File.Open(filePath, System.IO.FileMode.Open, System.IO.FileAccess.Read); - using XmlReader reader = CreateReader(stream); - doc = XDocument.Load(reader); + using XmlReader reader = CreateReader(stream, Path.GetFullPath(filePath)); + doc = XDocument.Load(reader, LoadOptions.SetBaseUri); } catch (Exception e) { @@ -79,7 +82,7 @@ namespace Barotrauma try { using FileStream stream = File.Open(filePath, System.IO.FileMode.Open, System.IO.FileAccess.Read); - using XmlReader reader = CreateReader(stream); + using XmlReader reader = CreateReader(stream, Path.GetFullPath(filePath)); doc = XDocument.Load(reader); } catch diff --git a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/PropertyConditional.cs b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/PropertyConditional.cs index 6925b0dac..258bd5466 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/PropertyConditional.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/PropertyConditional.cs @@ -60,6 +60,8 @@ namespace Barotrauma // Only used by conditionals targeting an item. By default, containers check the parent item. This allows you to check the grandparent instead. public readonly bool TargetGrandParent; + public readonly bool TargetContainedItem; + // Remove this after refactoring public static bool IsValid(XAttribute attribute) { @@ -112,6 +114,7 @@ namespace Barotrauma TargetContainer = attribute.Parent.GetAttributeBool("targetcontainer", false); TargetSelf = attribute.Parent.GetAttributeBool("targetself", false); TargetGrandParent = attribute.Parent.GetAttributeBool("targetgrandparent", false); + TargetContainedItem = attribute.Parent.GetAttributeBool("targetcontaineditem", false); if (!Enum.TryParse(AttributeName, true, out Type)) { @@ -171,6 +174,22 @@ namespace Barotrauma public bool Matches(ISerializableEntity target) { + if (TargetContainedItem) + { + if (target is Item item) + { + return item.ContainedItems.Any(it => Matches(it)); + } + else if (target is Items.Components.ItemComponent ic) + { + return ic.Item.ContainedItems.Any(it => Matches(it)); + } + else if (target is Character character) + { + return character.Inventory != null && character.Inventory.AllItems.Any(it => Matches(it)); + } + } + switch (Type) { case ConditionType.PropertyValue: diff --git a/Barotrauma/BarotraumaShared/SharedSource/Utils/SafeIO.cs b/Barotrauma/BarotraumaShared/SharedSource/Utils/SafeIO.cs index a2b4ba5cd..aef87c736 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Utils/SafeIO.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Utils/SafeIO.cs @@ -315,7 +315,11 @@ namespace Barotrauma.IO return System.IO.File.GetLastWriteTime(path); } - public static FileStream Open(string path, System.IO.FileMode mode, System.IO.FileAccess access = System.IO.FileAccess.ReadWrite) + public static FileStream Open( + string path, + System.IO.FileMode mode, + System.IO.FileAccess access = System.IO.FileAccess.ReadWrite, + System.IO.FileShare? share = null) { switch (mode) { @@ -331,10 +335,12 @@ namespace Barotrauma.IO } break; } - return new FileStream(path, System.IO.File.Open(path, mode, + access = !Validation.CanWrite(path, false) ? System.IO.FileAccess.Read : - access)); + access; + var shareVal = share ?? (access == System.IO.FileAccess.Read ? System.IO.FileShare.Read : System.IO.FileShare.None); + return new FileStream(path, System.IO.File.Open(path, mode, access, shareVal)); } public static FileStream OpenRead(string path) diff --git a/Barotrauma/BarotraumaShared/changelog.txt b/Barotrauma/BarotraumaShared/changelog.txt index babb03713..9a8ee1ea6 100644 --- a/Barotrauma/BarotraumaShared/changelog.txt +++ b/Barotrauma/BarotraumaShared/changelog.txt @@ -1,3 +1,32 @@ +--------------------------------------------------------------------------------------------------------- +v0.1500.3.0 +--------------------------------------------------------------------------------------------------------- + +Additions and changes: +- More talents and talent-related items (all talent trees now functional and most of the talents implemented). + +Changes: +- Ignore hidden afflictions when determining treatment suggestions to show in the health interface. +- Visualize leaks on the status monitor's hull condition tab (unstable only). +- Added "condition_out" output to outpost O2 generator (unstable only). + +Fixes: +- Fixed crashing when reloading sprites or resetting to prefab in the sub editor. +- Fixed ability to combine unidentified genetic materials with other genetic materials (unstable only). +- Organ damage doesn't cause concussions (unstable only). +- Fixed talent menu being accessible if you leave it open and switch to a game mode where it shouldn't be accessible (unstable only). +- Fixed ability to contain items other than batteries in cargo scooter's battery slot (unstable only). +- Damaging the mudraptor beak given by mudraptor genes damages the head instead of torso, added damage protection to the beak (unstable only). +- Items that are set to be hidden in menus aren't shown in the status monitor's item finder (unstable only). +- Fixed status monitor's item finder not showing wearable items (unstable only). +- Fixed "in use by xxxx" warning being always visible when using a Reactor PDA (unstable only). +- Fixed Reactor PDA rendering over the command interface (unstable only). +- Fixed assault rifle crosshair being drawn when it's in the bag slot (unstable only). +- Fixed equip buttons not being drawn on equipped items that can only be put to other equip slots, but not on the non-limb slots (e.g. assault rifle). + +Modding: +- Option to make property conditionals target contained items using the attribute targetcontaineditem="true". + --------------------------------------------------------------------------------------------------------- v0.1500.2.0 --------------------------------------------------------------------------------------------------------- diff --git a/Barotrauma/BarotraumaShared/serversettings.xml b/Barotrauma/BarotraumaShared/serversettings.xml index 38cae3c14..34f4c94b2 100644 --- a/Barotrauma/BarotraumaShared/serversettings.xml +++ b/Barotrauma/BarotraumaShared/serversettings.xml @@ -11,7 +11,7 @@ autorestart="false" LevelDifficulty="20" AllowedRandomMissionTypes="Random,Salvage,Monster,Cargo,Combat" - AllowedClientNameChars="32-33,38-46,48-57,65-90,91-91,93-93,95-122,192-255,384-591,1024-1279,19968-40959,13312-19903,131072-15043983,15043985-173791,173824-178207,178208-183983,63744-64255,194560-195103" + AllowedClientNameChars="32-33,38-46,48-57,65-90,91-91,93-93,95-122,192-255,384-591,1024-1279,19968-21327,21329-40959,13312-19903,131072-173791,173824-178207,178208-183983,63744-64255,194560-195103" ServerMessage="" tickrate="20" randomizeseed="True"