0.1500.3.0 (🗿 edition)

This commit is contained in:
Markus Isberg
2021-09-17 22:47:21 +09:00
parent 1231170fce
commit 5a6bbcc79e
75 changed files with 1145 additions and 441 deletions
@@ -802,6 +802,10 @@ namespace Barotrauma
var treatmentButton = component.GetChild<GUIButton>(); var treatmentButton = component.GetChild<GUIButton>();
if (!(treatmentButton?.UserData is ItemPrefab itemPrefab)) { continue; } 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 //key = item identifier
//float = suitability //float = suitability
Dictionary<string, float> treatmentSuitability = new Dictionary<string, float>(); Dictionary<string, float> treatmentSuitability = new Dictionary<string, float>();
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()) foreach (string treatment in treatmentSuitability.Keys.ToList())
{ {
@@ -1260,10 +1267,11 @@ namespace Barotrauma
UserData = item 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, UserData = item,
ToolTip = $"‖color:255,255,255,255‖{item.Name}‖color:end‖" + '\n' + item.Description, ToolTip = $"‖color:255,255,255,255‖{item.Name}‖color:end‖" + '\n' + item.Description,
DisabledColor = Color.White * 0.1f,
OnClicked = (btn, userdata) => OnClicked = (btn, userdata) =>
{ {
if (!(userdata is ItemPrefab itemPrefab)) { return false; } if (!(userdata is ItemPrefab itemPrefab)) { return false; }
@@ -1274,6 +1282,17 @@ namespace Barotrauma
return true; 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; Sprite itemSprite = item.InventoryIcon ?? item.sprite;
Color itemColor = itemSprite == item.sprite ? item.SpriteColor : item.InventoryIconColor; 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), 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, Color = itemColor * 0.9f,
HoverColor = itemColor, HoverColor = itemColor,
SelectedColor = itemColor, SelectedColor = itemColor,
DisabledColor = itemColor * 0.7f DisabledColor = itemColor * 0.8f
}; };
} }
@@ -1697,13 +1697,14 @@ namespace Barotrauma
//check missing mission texts //check missing mission texts
foreach (var missionPrefab in MissionPrefab.List) 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 (!tags[language].Contains(nameIdentifier))
{ {
if (!missingTags.ContainsKey(nameIdentifier)) { missingTags[nameIdentifier] = new HashSet<string>(); } if (!missingTags.ContainsKey(nameIdentifier)) { missingTags[nameIdentifier] = new HashSet<string>(); }
missingTags[nameIdentifier].Add(language); missingTags[nameIdentifier].Add(language);
} }
string descriptionIdentifier = "missiondescription." + missionPrefab.Identifier; string descriptionIdentifier = "missiondescription." + missionId;
if (!tags[language].Contains(descriptionIdentifier)) if (!tags[language].Contains(descriptionIdentifier))
{ {
if (!missingTags.ContainsKey(descriptionIdentifier)) { missingTags[descriptionIdentifier] = new HashSet<string>(); } if (!missingTags.ContainsKey(descriptionIdentifier)) { missingTags[descriptionIdentifier] = new HashSet<string>(); }
@@ -1713,6 +1714,7 @@ namespace Barotrauma
foreach (SubmarineInfo sub in SubmarineInfo.SavedSubmarines) foreach (SubmarineInfo sub in SubmarineInfo.SavedSubmarines)
{ {
if (sub.Type != SubmarineType.Player) { continue; }
string nameIdentifier = "submarine.name." + sub.Name.ToLowerInvariant(); string nameIdentifier = "submarine.name." + sub.Name.ToLowerInvariant();
if (!tags[language].Contains(nameIdentifier)) if (!tags[language].Contains(nameIdentifier))
{ {
@@ -1729,14 +1731,23 @@ namespace Barotrauma
foreach (AfflictionPrefab affliction in AfflictionPrefab.List) 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 (!tags[language].Contains(nameIdentifier))
{ {
if (!missingTags.ContainsKey(nameIdentifier)) { missingTags[nameIdentifier] = new HashSet<string>(); } if (!missingTags.ContainsKey(nameIdentifier)) { missingTags[nameIdentifier] = new HashSet<string>(); }
missingTags[nameIdentifier].Add(language); missingTags[nameIdentifier].Add(language);
} }
string descriptionIdentifier = "afflictiondescription." + affliction.Identifier; string descriptionIdentifier = "afflictiondescription." + afflictionId;
if (!tags[language].Contains(descriptionIdentifier)) if (!tags[language].Contains(descriptionIdentifier))
{ {
if (!missingTags.ContainsKey(descriptionIdentifier)) { missingTags[descriptionIdentifier] = new HashSet<string>(); } if (!missingTags.ContainsKey(descriptionIdentifier)) { missingTags[descriptionIdentifier] = new HashSet<string>(); }
@@ -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<string>(); }
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<string>(); }
missingTags[nameIdentifier].Add(language);
}
}
//check missing entity names //check missing entity names
foreach (MapEntityPrefab me in MapEntityPrefab.List) foreach (MapEntityPrefab me in MapEntityPrefab.List)
{ {
@@ -41,11 +41,14 @@ namespace Barotrauma
public SpriteSheet SavingIndicator { get; private set; } public SpriteSheet SavingIndicator { get; private set; }
public UISprite UIGlow { get; private set; } public UISprite UIGlow { get; private set; }
public UISprite TalentGlow { get; private set; }
public UISprite PingCircle { get; private set; } public UISprite PingCircle { get; private set; }
public UISprite UIGlowCircular { get; private set; } public UISprite UIGlowCircular { get; private set; }
public UISprite UIGlowSolidCircular { get; private set; }
public UISprite ButtonPulse { get; private set; } public UISprite ButtonPulse { get; private set; }
public SpriteSheet FocusIndicator { 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 TextColorDark { get; private set; } = Color.Black * 0.9f;
public Color TextColorDim { get; private set; } = Color.White * 0.6f; 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 ColorReputationVeryLow { get; private set; } = Color.Red;
public Color ColorReputationLow { get; private set; } = Color.Orange; public Color ColorReputationLow { get; private set; } = Color.Orange;
public Color ColorReputationNeutral { get; private set; } = Color.White * 0.8f; public Color ColorReputationNeutral { get; private set; } = Color.White * 0.8f;
@@ -241,6 +250,9 @@ namespace Barotrauma
case "uiglow": case "uiglow":
UIGlow = new UISprite(subElement); UIGlow = new UISprite(subElement);
break; break;
case "talentglow":
TalentGlow = new UISprite(subElement);
break;
case "pingcircle": case "pingcircle":
PingCircle = new UISprite(subElement); PingCircle = new UISprite(subElement);
break; break;
@@ -253,6 +265,9 @@ namespace Barotrauma
case "uiglowcircular": case "uiglowcircular":
UIGlowCircular = new UISprite(subElement); UIGlowCircular = new UISprite(subElement);
break; break;
case "uiglowsolidcircular":
UIGlowSolidCircular = new UISprite(subElement);
break;
case "endroundbuttonpulse": case "endroundbuttonpulse":
ButtonPulse = new UISprite(subElement); ButtonPulse = new UISprite(subElement);
break; break;
@@ -211,6 +211,13 @@ namespace Barotrauma
if (LoadState != null) if (LoadState != null)
{ {
loadText += " " + (int)LoadState + " %"; loadText += " " + (int)LoadState + " %";
#if DEBUG
if (GameMain.FirstLoad && GameMain.CancelQuickStart)
{
loadText += " (Quickstart aborted)";
}
#endif
} }
} }
if (GUI.LargeFont != null) if (GUI.LargeFont != null)
@@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using System.Linq; using System.Linq;
@@ -18,8 +19,8 @@ namespace Barotrauma
private static UISprite spectateIcon, disconnectedIcon; private static UISprite spectateIcon, disconnectedIcon;
private static Sprite ownerIcon, moderatorIcon; private static Sprite ownerIcon, moderatorIcon;
private enum InfoFrameTab { Crew, Mission, Reputation, MyCharacter, Traitor, Submarine, Talents }; public enum InfoFrameTab { Crew, Mission, Reputation, MyCharacter, Traitor, Submarine, Talents };
private static InfoFrameTab selectedTab; public static InfoFrameTab selectedTab;
private GUIFrame infoFrame, contentFrame; private GUIFrame infoFrame, contentFrame;
private readonly List<GUIButton> tabButtons = new List<GUIButton>(); private readonly List<GUIButton> tabButtons = new List<GUIButton>();
@@ -262,7 +263,11 @@ namespace Barotrauma
var talentsButton = createTabButton(InfoFrameTab.Talents, "tabmenu.talents"); var talentsButton = createTabButton(InfoFrameTab.Talents, "tabmenu.talents");
talentsButton.OnAddedToGUIUpdateList += (GUIComponent component) => 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);
}
}; };
} }
@@ -1174,19 +1179,38 @@ namespace Barotrauma
private Color unselectableColor = new Color(100, 100, 100, 225); private Color unselectableColor = new Color(100, 100, 100, 225);
private Color pressedColor = new Color(60, 60, 60, 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<string> selectedTalents = new List<string>(); private List<string> selectedTalents = new List<string>();
private GUITextBlock talentTitleText;
private GUITextBlock talentDescriptionText;
private GUITextBlock talentPointsText;
private GUITextBlock experienceText; private GUITextBlock experienceText;
private GUIProgressBar experienceBar; private GUIProgressBar experienceBar;
private GUITextBlock talentPointText;
private GUIListBox skillListBox;
private readonly ImmutableDictionary<TalentTree.TalentTreeStageState, GUIComponentStyle> talentStageStyles = new Dictionary<TalentTree.TalentTreeStageState, GUIComponentStyle>
{
{ 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<TalentTree.TalentTreeStageState, Color> talentStageBackgroundColors = new Dictionary<TalentTree.TalentTreeStageState, Color>
{
{ 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) private void CreateTalentInfo(GUIFrame infoFrame)
{ {
infoFrame.ClearChildren(); infoFrame.ClearChildren();
talentButtons.Clear(); talentButtons.Clear();
talentCornerIcons.Clear();
Character controlledCharacter = Character.Controlled; Character controlledCharacter = Character.Controlled;
if (controlledCharacter == null) { return; } if (controlledCharacter == null) { return; }
@@ -1195,6 +1219,8 @@ namespace Barotrauma
int padding = GUI.IntScale(15); 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 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) if (controlledCharacter.Info == null)
{ {
DebugConsole.ThrowError("No character info found for talent UI"); DebugConsole.ThrowError("No character info found for talent UI");
@@ -1203,87 +1229,103 @@ namespace Barotrauma
selectedTalents = controlledCharacter.Info.UnlockedTalents.ToList(); 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) 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); new GUICustomComponent(new RectTransform(new Vector2(0.25f, 1f), talentInfoLayoutGroup.RectTransform), onDraw: (batch, component) =>
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)
{ {
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; } 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) 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); 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); 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"); GUIFrame subtreeTitleFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.111f), subTreeLayoutGroup.RectTransform, anchor: Anchor.TopCenter), style: null);
int elementPadding = GUI.IntScale(8);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), subtreeTitleFrame.RectTransform, anchor: Anchor.TopCenter), subTree.Identifier, font: GUI.LargeFont, textAlignment: Alignment.Center); 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++) 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"); GUIFrame talentOptionFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.222f), subTreeLayoutGroup.RectTransform, anchor: Anchor.TopCenter), style: null);
GUIImage talentBackground = new GUIImage(new RectTransform(Vector2.One, talentOptionFrame.RectTransform, anchor: Anchor.Center), style: "TalentBackground")
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, CanBeFocused = false
Color = unselectableColor,
}; };
Point iconSize = cornerIcon.RectTransform.NonScaledSize;
cornerIcon.RectTransform.AbsoluteOffset = new Point(iconSize.X / 2, iconSize.Y / 2);
if (subTree.TalentOptionStages.Count > i) if (subTree.TalentOptionStages.Count > i)
{ {
TalentOption talentOption = subTree.TalentOptionStages[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) GUILayoutGroup talentOptionCenterGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.75f, 0.7f), talentOptionFrame.RectTransform, Anchor.Center), childAnchor: Anchor.CenterLeft);
{
Stretch = true, GUILayoutGroup talentOptionLayoutGroup = new GUILayoutGroup(new RectTransform(Vector2.One, talentOptionCenterGroup.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft) { Stretch = true };
};
foreach (TalentPrefab talent in talentOption.Talents) foreach (TalentPrefab talent in talentOption.Talents)
{ {
int optionPadding = GUI.IntScale(10); GUIFrame talentFrame = new GUIFrame(new RectTransform(Vector2.One, talentOptionLayoutGroup.RectTransform), style: null)
GUIFrame talentFrame = new GUIFrame(new RectTransform(new Point(talentOptionFrame.Rect.Width, talentOptionFrame.Rect.Height - optionPadding), talentOptionLayoutGroup.RectTransform), style: null)
{ {
CanBeFocused = false, 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}", ToolTip = $"{talent.DisplayName}\n\n{talent.Description}",
UserData = talent.Identifier, UserData = talent.Identifier,
PressedColor = pressedColor, PressedColor = pressedColor,
OnClicked = (button, userData) => OnClicked = (button, userData) =>
{ {
talentTitleText.Text = talent.DisplayName;
talentDescriptionText.Text = talent.Description;
// deselect other buttons in tier by removing their selected talents from pool // deselect other buttons in tier by removing their selected talents from pool
foreach (GUIButton guiButton in talentOptionLayoutGroup.GetAllChildren<GUIButton>()) foreach (GUIButton guiButton in talentOptionLayoutGroup.GetAllChildren<GUIButton>())
{ {
@@ -1314,61 +1356,129 @@ namespace Barotrauma
}, },
}; };
talentButton.Color = talentButton.HoverColor = talentButton.PressedColor = talentButton.SelectedColor = Color.Transparent;
int iconPadding = GUI.IntScale(15); GUIComponent iconImage;
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) if (talent.Icon is null)
{ {
PressedColor = unselectableColor, iconImage = new GUITextBlock(new RectTransform(Vector2.One, talentButton.RectTransform, anchor: Anchor.Center, scaleBasis: ScaleBasis.BothHeight), text: "???", font: GUI.LargeFont, textAlignment: Alignment.Center, style: null)
CanBeFocused = false, {
}; 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), 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 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")) new GUIButton(new RectTransform(new Vector2(0.1f, 1f), talentBottomFrame.RectTransform), text: TextManager.Get("reset"), style: "GUICharacterInfoButton")
{
OnClicked = ApplyTalentSelection,
};
new GUIButton(new RectTransform(new Vector2(0.1f, 0.3f), talentBottomFrame.RectTransform, anchor: Anchor.TopLeft), text: TextManager.Get("reset"))
{ {
OnClicked = ResetTalentSelection, OnClicked = ResetTalentSelection,
}; };
new GUIButton(new RectTransform(new Vector2(0.1f, 1f), talentBottomFrame.RectTransform), text: TextManager.Get("applysettingsbutton"), style: "GUICharacterInfoButton")
{
OnClicked = ApplyTalentSelection,
};
UpdateTalentButtons(); 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() private void UpdateTalentButtons()
{ {
Character controlledCharacter = Character.Controlled; 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()}"; experienceText.Text = $"{controlledCharacter.Info.ExperiencePoints - controlledCharacter.Info.GetExperienceRequiredForCurrentLevel()} / {controlledCharacter.Info.GetExperienceRequiredToLevelUp() - controlledCharacter.Info.GetExperienceRequiredForCurrentLevel()}";
experienceBar.BarSize = controlledCharacter.Info.GetProgressTowardsNextLevel(); experienceBar.BarSize = controlledCharacter.Info.GetProgressTowardsNextLevel();
//experienceBar.ToolTip = $"{controlledCharacter.Info.ExperiencePoints - controlledCharacter.Info.GetExperienceRequiredForCurrentLevel()} / {controlledCharacter.Info.GetExperienceRequiredToLevelUp() - controlledCharacter.Info.GetExperienceRequiredForCurrentLevel()}"; //experienceBar.ToolTip = $"{controlledCharacter.Info.ExperiencePoints - controlledCharacter.Info.GetExperienceRequiredForCurrentLevel()} / {controlledCharacter.Info.GetExperienceRequiredToLevelUp() - controlledCharacter.Info.GetExperienceRequiredForCurrentLevel()}";
selectedTalents = TalentTree.CheckTalentSelection(controlledCharacter, selectedTalents); 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) foreach (var talentButton in talentButtons)
@@ -1377,30 +1487,23 @@ namespace Barotrauma
bool unselectable = !TalentTree.IsViableTalentForCharacter(controlledCharacter, talentIdentifier, selectedTalents) || controlledCharacter.HasTalent(talentIdentifier); bool unselectable = !TalentTree.IsViableTalentForCharacter(controlledCharacter, talentIdentifier, selectedTalents) || controlledCharacter.HasTalent(talentIdentifier);
Color newTalentColor = unselectable ? unselectableColor : unselectedColor; Color newTalentColor = unselectable ? unselectableColor : unselectedColor;
talentButton.glow.Visible = false;
if (controlledCharacter.HasTalent(talentIdentifier)) if (controlledCharacter.HasTalent(talentIdentifier))
{ {
newTalentColor = ownedColor; newTalentColor = new Color(140,225,140,255);
} }
else if (selectedTalents.Contains(talentIdentifier)) 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; 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) private void ApplyTalents(Character controlledCharacter)
{ {
@@ -194,6 +194,8 @@ namespace Barotrauma
#if DEBUG #if DEBUG
public static bool FirstLoad = true; public static bool FirstLoad = true;
public static bool CancelQuickStart;
#endif #endif
public GameMain(string[] args) public GameMain(string[] args)
@@ -804,7 +806,9 @@ namespace Barotrauma
} }
#if DEBUG #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; loadingScreenOpen = false;
FirstLoad = false; FirstLoad = false;
@@ -208,7 +208,7 @@ namespace Barotrauma
ReportButtonFrame.RectTransform.AbsoluteOffset = new Point(0, -chatBox.ToggleButton.Rect.Height); ReportButtonFrame.RectTransform.AbsoluteOffset = new Point(0, -chatBox.ToggleButton.Rect.Height);
CreateReports(this, ReportButtonFrame, reports, false); CreateReportButtons(this, ReportButtonFrame, reports, false);
#endregion #endregion
@@ -218,7 +218,7 @@ namespace Barotrauma
dismissedOrderPrefab ??= Order.GetPrefab("dismissed"); dismissedOrderPrefab ??= Order.GetPrefab("dismissed");
} }
public static void CreateReports(CrewManager crewManager, GUIComponent parent, List<Order> reports, bool isHorizontal) public static void CreateReportButtons(CrewManager crewManager, GUIComponent parent, List<Order> reports, bool isHorizontal)
{ {
//report buttons //report buttons
foreach (Order order in reports) foreach (Order order in reports)
@@ -228,22 +228,21 @@ namespace Barotrauma
{ {
OnClicked = (button, userData) => OnClicked = (button, userData) =>
{ {
if (!CanIssueOrders) { return false; } if (!CanIssueOrders || crewManager?.DraggedOrder != null) { return false; }
var sub = Character.Controlled.Submarine; var sub = Character.Controlled.Submarine;
if (sub == null || sub.TeamID != Character.Controlled.TeamID || sub.Info.IsWreck) { return false; } if (sub == null || sub.TeamID != Character.Controlled.TeamID || sub.Info.IsWreck) { return false; }
if (crewManager != null) if (crewManager != null)
{ {
crewManager.SetCharacterOrder(null, order, null, CharacterInfo.HighestManualOrderPriority, Character.Controlled); crewManager.SetCharacterOrder(null, order, null, CharacterInfo.HighestManualOrderPriority, Character.Controlled);
if (crewManager.IsSinglePlayer) { HumanAIController.ReportProblem(Character.Controlled, order); } if (crewManager.IsSinglePlayer) { HumanAIController.ReportProblem(Character.Controlled, order); }
} }
return true; return true;
}, },
UserData = order, UserData = order,
ToolTip = order.Name,
ClampMouseRectToParent = false ClampMouseRectToParent = false
}; };
btn.ToolTip = $"‖color:{XMLExtensions.ColorToString(order.Prefab.Color)}‖{order.Name}‖color:end‖\n{TextManager.Get("draganddropreports")}";
if (crewManager != null) if (crewManager != null)
{ {
@@ -272,8 +271,9 @@ namespace Barotrauma
{ {
Color = order.Color, Color = order.Color,
HoverColor = Color.Lerp(order.Color, Color.White, 0.5f), HoverColor = Color.Lerp(order.Color, Color.White, 0.5f),
ToolTip = order.Name, ToolTip = btn.RawToolTip,
SpriteEffects = SpriteEffects.FlipHorizontally SpriteEffects = SpriteEffects.FlipHorizontally,
UserData = order
}; };
} }
} }
@@ -717,7 +717,7 @@ namespace Barotrauma
hull ??= orderGiver.CurrentHull; hull ??= orderGiver.CurrentHull;
AddOrder(new Order(order.Prefab ?? order, hull, null, orderGiver), order.FadeOutTime); AddOrder(new Order(order.Prefab ?? order, hull, null, orderGiver), order.FadeOutTime);
} }
else if(order.IsIgnoreOrder) else if (order.IsIgnoreOrder)
{ {
WallSection ws = null; WallSection ws = null;
if (order.TargetType == Order.OrderTargetType.Entity && order.TargetEntity is IIgnorable ignorable) if (order.TargetType == Order.OrderTargetType.Entity && order.TargetEntity is IIgnorable ignorable)
@@ -1232,7 +1232,7 @@ namespace Barotrauma
highlightedQuickUseSlot = visualSlots[i]; 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; continue;
} }
@@ -8,6 +8,7 @@ using Barotrauma.IO;
using System.Text; using System.Text;
using System.Xml.Linq; using System.Xml.Linq;
using Barotrauma.Sounds; using Barotrauma.Sounds;
using System.Linq;
namespace Barotrauma.Items.Components namespace Barotrauma.Items.Components
{ {
@@ -145,7 +146,9 @@ namespace Barotrauma.Items.Components
if (character == null || !character.IsKeyDown(InputType.Aim)) { return; } if (character == null || !character.IsKeyDown(InputType.Aim)) { return; }
//camera focused on some other item/device, don't draw the crosshair //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.HideCursor = (crosshairSprite != null || crosshairPointerSprite != null) &&
GUI.MouseOn == null && !Inventory.IsMouseOnInventory && !GameMain.Instance.Paused; GUI.MouseOn == null && !Inventory.IsMouseOnInventory && !GameMain.Instance.Paused;
@@ -214,7 +214,7 @@ namespace Barotrauma.Items.Components
if (UILabel == string.Empty) { return string.Empty; } if (UILabel == string.Empty) { return string.Empty; }
if (UILabel != null) if (UILabel != null)
{ {
return TextManager.Get("UILabel." + UILabel); return TextManager.Get("UILabel." + UILabel, returnNull: true) ?? TextManager.Get(UILabel);
} }
else else
{ {
@@ -37,7 +37,7 @@ namespace Barotrauma.Items.Components
private FabricationRecipe pendingFabricatedItem; private FabricationRecipe pendingFabricatedItem;
private Pair<Rectangle, string> tooltip; private (Rectangle area, string text)? tooltip;
private GUITextBlock requiredTimeBlock; 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), 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, AutoScaleHorizontal = true,
CanBeFocused = false CanBeFocused = false
@@ -278,7 +278,7 @@ namespace Barotrauma.Items.Components
sufficientSkillsText.RectTransform.SetAsFirstChild(); sufficientSkillsText.RectTransform.SetAsFirstChild();
var insufficientSkillsText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.15f), itemList.Content.RectTransform), 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, AutoScaleHorizontal = true,
CanBeFocused = false 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), 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, AutoScaleHorizontal = true,
CanBeFocused = false CanBeFocused = false
@@ -403,7 +403,7 @@ namespace Barotrauma.Items.Components
{ {
toolTipText += '\n' + requiredItem.ItemPrefabs.First().Description; toolTipText += '\n' + requiredItem.ItemPrefabs.First().Description;
} }
tooltip = new Pair<Rectangle, string>(slotRect, toolTipText); tooltip = (slotRect, toolTipText);
} }
slotIndex++; slotIndex++;
@@ -443,7 +443,7 @@ namespace Barotrauma.Items.Components
if (tooltip != null) if (tooltip != null)
{ {
GUIComponent.DrawToolTip(spriteBatch, tooltip.Second, tooltip.First); GUIComponent.DrawToolTip(spriteBatch, tooltip.Value.text, tooltip.Value.area);
tooltip = null; tooltip = null;
} }
} }
@@ -463,6 +463,22 @@ namespace Barotrauma.Items.Components
if (recipe?.DisplayName == null) { continue; } if (recipe?.DisplayName == null) { continue; }
child.Visible = recipe.DisplayName.ToLower().Contains(filter); 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.UpdateScrollBarSize();
itemList.BarScroll = 0.0f; 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), 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 AutoScaleHorizontal = true
}; };
@@ -218,8 +218,8 @@ namespace Barotrauma.Items.Components
DefaultNeutralColor = MiniMapBaseColor * 0.8f, DefaultNeutralColor = MiniMapBaseColor * 0.8f,
HoverColor = Color.White, HoverColor = Color.White,
BlueprintBlue = new Color(23, 38, 33), BlueprintBlue = new Color(23, 38, 33),
HullWaterColor = new Color(17, 173, 179), HullWaterColor = new Color(17, 173, 179) * 0.5f,
HullWaterLineColor = Color.LightBlue, HullWaterLineColor = Color.LightBlue * 0.5f,
NoPowerColor = MiniMapBaseColor * 0.1f, NoPowerColor = MiniMapBaseColor * 0.1f,
ElectricalBaseColor = GUI.Style.Orange, ElectricalBaseColor = GUI.Style.Orange,
NoPowerElectricalColor = ElectricalBaseColor * 0.1f; NoPowerElectricalColor = ElectricalBaseColor * 0.1f;
@@ -307,10 +307,13 @@ namespace Barotrauma.Items.Components
if (reports.Any()) 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) searchBar = new GUITextBox(new RectTransform(new Vector2(1), searchBarFrame.RectTransform), string.Empty, createClearButton: true, createPenIcon: true)
{ {
OnEnterPressed = (box, text) => OnEnterPressed = (box, text) =>
@@ -345,6 +348,7 @@ namespace Barotrauma.Items.Components
foreach (ItemPrefab prefab in ItemPrefab.Prefabs.OrderBy(prefab => prefab.Name)) foreach (ItemPrefab prefab in ItemPrefab.Prefabs.OrderBy(prefab => prefab.Name))
{ {
if (prefab.HideInMenus) { continue; }
CreateItemFrame(prefab, listBox.Content.RectTransform); CreateItemFrame(prefab, listBox.Content.RectTransform);
} }
@@ -355,7 +359,10 @@ namespace Barotrauma.Items.Components
searchBar.OnSelected += (sender, key) => searchBar.OnSelected += (sender, key) =>
{ {
itemsFoundOnSub = Item.ItemList.Where(it => it.Submarine == item.Submarine && !it.NonInteractable && !it.HiddenInGame && it.Components.OfType<Holdable>().Any()).Select(it => it.Prefab).ToImmutableHashSet(); itemsFoundOnSub = Item.ItemList.Where(it =>
it.Submarine == item.Submarine &&
!it.NonInteractable && !it.HiddenInGame &&
(it.GetComponent<Holdable>() != null || it.GetComponent<Wearable>() != null)).Select(it => it.Prefab).ToImmutableHashSet();
}; };
searchBar.OnKeyHit += ControlSearchTooltip; searchBar.OnKeyHit += ControlSearchTooltip;
@@ -487,21 +494,24 @@ namespace Barotrauma.Items.Components
CreateHUD(); 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))) 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 newZoom = Math.Clamp(Zoom + PlayerInput.ScrollWheelSpeed / 1000.0f * Zoom, minZoom, maxZoom);
float distanceScale = newZoom / Zoom; float distanceScale = newZoom / Zoom;
mapOffset *= distanceScale; mapOffset *= distanceScale;
recalculate |= !MathUtils.NearlyEqual(Zoom, newZoom); recalculate |= !MathUtils.NearlyEqual(Zoom, newZoom);
Zoom = newZoom; Zoom = newZoom;
}
} }
if (dragMapStart is { } dragStart) if (dragMapStart is { } dragStart)
@@ -524,15 +534,13 @@ namespace Barotrauma.Items.Components
if (recalculate) if (recalculate)
{ {
miniMapContainer.RectTransform.LocalScale = new Vector2(Zoom); if (miniMapContainer != null)
miniMapContainer.RectTransform.RecalculateChildren(true, true); {
miniMapContainer.RectTransform.AbsoluteOffset = mapOffset.ToPoint(); miniMapContainer.RectTransform.LocalScale = new Vector2(Zoom);
miniMapContainer.RectTransform.RecalculateChildren(true, true);
miniMapContainer.RectTransform.AbsoluteOffset = mapOffset.ToPoint();
}
recalculate = false; 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? // is there a better way to do this?
@@ -606,17 +614,6 @@ namespace Barotrauma.Items.Components
private void DrawHUDFront(SpriteBatch spriteBatch, GUICustomComponent container) 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) if (Voltage < MinVoltage)
{ {
Vector2 textSize = GUI.Font.MeasureString(noPowerTip); Vector2 textSize = GUI.Font.MeasureString(noPowerTip);
@@ -627,18 +624,45 @@ namespace Barotrauma.Items.Components
return; return;
} }
if (currentMode == MiniMapMode.HullStatus) if (currentMode == MiniMapMode.HullStatus || currentMode == MiniMapMode.HullCondition)
{ {
Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle; Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle;
spriteBatch.End(); spriteBatch.End();
spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable); spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
spriteBatch.GraphicsDevice.ScissorRectangle = submarineContainer.Rect; spriteBatch.GraphicsDevice.ScissorRectangle = submarineContainer.Rect;
foreach (var (entity, component) in hullStatusComponents) if (currentMode == MiniMapMode.HullCondition && item.Submarine != null)
{ {
if (!(entity is Hull hull)) { continue; } var sprite = GUI.Style.UIGlowSolidCircular?.Sprite;
if (!hullDatas.TryGetValue(hull, out HullData? hullData) || hullData is null) { continue; } float alpha = (MathF.Sin(blipState / maxBlipState * MathHelper.TwoPi) + 1.5f) * 0.5f;
DrawHullCards(spriteBatch, hull, hullData, component.RectComponent); 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(); spriteBatch.End();
@@ -721,14 +745,21 @@ namespace Barotrauma.Items.Components
UserData = prefab 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) 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); var nameText = new GUITextBlock(new RectTransform(Vector2.One, layout.RectTransform), prefab.Name);
layout.UserData = prefab; nameText.RectTransform.SizeChanged += () =>
{
nameText.Text = ToolBox.LimitString(prefab.Name, nameText.Font, nameText.Rect.Width);
};
} }
private void SearchItems(string text) private void SearchItems(string text)
@@ -792,15 +823,18 @@ namespace Barotrauma.Items.Components
private void UpdateHUDBack() private void UpdateHUDBack()
{ {
if (item.Submarine == null) { return; }
hullInfoFrame.Visible = false; hullInfoFrame.Visible = false;
electricalFrame.Visible = false;
miniMapFrame.Visible = false;
reportFrame.Visible = false; reportFrame.Visible = false;
searchBarFrame.Visible = false; searchBarFrame.Visible = false;
electricalFrame.Visible = false;
miniMapFrame.Visible = false;
switch (currentMode) switch (currentMode)
{ {
case MiniMapMode.HullStatus: case MiniMapMode.HullStatus:
case MiniMapMode.HullCondition:
UpdateHullStatus(); UpdateHullStatus();
miniMapFrame.Visible = true; miniMapFrame.Visible = true;
reportFrame.Visible = true; reportFrame.Visible = true;
@@ -937,16 +971,19 @@ namespace Barotrauma.Items.Components
SetTooltip(borderComponent.Rect.Center, header, line1, line2, line3, line1Color, line2Color, line3Color); 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 // 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) foreach (Hull linkedHull in hullData.LinkedHulls)
{ {
if (!hullStatusComponents.ContainsKey(linkedHull)) { continue; } 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) { break; }
} }
if (isHoveringOver) if (isHoveringOver || (draggingReport && component.MouseRect.Contains(PlayerInput.MousePosition)))
{ {
borderColor = Color.Lerp(borderColor, Color.White, 0.5f); borderColor = Color.Lerp(borderColor, Color.White, 0.5f);
componentColor = HoverColor; componentColor = HoverColor;
@@ -1037,7 +1074,7 @@ namespace Barotrauma.Items.Components
} }
else else
{ {
bool hullsVisible = currentMode == MiniMapMode.HullStatus; bool hullsVisible = currentMode == MiniMapMode.HullStatus || currentMode == MiniMapMode.HullCondition;
foreach (var (entity, component) in hullStatusComponents) 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["width"].SetValue((float)texture.Width);
GameMain.GameScreen.BlueprintEffect.Parameters["height"].SetValue((float)texture.Height); 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); Vector2 origin = new Vector2(texture.Width / 2f, texture.Height / 2f);
float scale = currentMode == MiniMapMode.HullStatus ? 1.0f : Zoom; float scale = currentMode == MiniMapMode.HullStatus ? 1.0f : Zoom;
@@ -19,9 +19,11 @@ namespace Barotrauma.Items.Components
private GUIProgressBar progressBar; private GUIProgressBar progressBar;
private List<ParticleEmitter> particleEmitters = new List<ParticleEmitter>(); private GUITextBlock progressBarOverlayText;
private readonly List<ParticleEmitter> particleEmitters = new List<ParticleEmitter>();
//the corresponding particle emitter is active when the condition is within this range //the corresponding particle emitter is active when the condition is within this range
private List<Vector2> particleEmitterConditionRanges = new List<Vector2>(); private readonly List<Vector2> particleEmitterConditionRanges = new List<Vector2>();
private SoundChannel repairSoundChannel; 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) 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), progressBar = new GUIProgressBar(new RectTransform(new Vector2(0.6f, 1.0f), progressBarHolder.RectTransform),
color: GUI.Style.Green, barSize: 0.0f, style: "DeviceProgressBar"); 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"); repairButtonText = TextManager.Get("RepairButton");
repairingText = TextManager.Get("Repairing"); repairingText = TextManager.Get("Repairing");
RepairButton = new GUIButton(new RectTransform(new Vector2(0.4f, 1.0f), progressBarHolder.RectTransform, Anchor.TopCenter), repairButtonText) 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; progressBarHolder.RectTransform.MinSize = RepairButton.RectTransform.MinSize;
RepairButton.RectTransform.MinSize = new Point((int)(RepairButton.TextBlock.TextSize.X * 1.2f), RepairButton.RectTransform.MinSize.Y); RepairButton.RectTransform.MinSize = new Point((int)(RepairButton.TextBlock.TextSize.X * 1.2f), RepairButton.RectTransform.MinSize.Y);
sabotageButtonText = TextManager.Get("SabotageButton"); sabotageButtonText = TextManager.Get("SabotageButton");
sabotagingText = TextManager.Get("Sabotaging"); sabotagingText = TextManager.Get("Sabotaging");
SabotageButton = new GUIButton(new RectTransform(new Vector2(0.8f, 0.15f), paddedFrame.RectTransform, Anchor.BottomCenter), sabotageButtonText, style: "GUIButtonSmall") 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; 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); 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.Enabled = (currentFixerAction == FixActions.None || (CurrentFixer == character && currentFixerAction != FixActions.Repair)) && !item.IsFullCondition;
RepairButton.Text = (currentFixerAction == FixActions.None || CurrentFixer != character || currentFixerAction != FixActions.Repair) ? RepairButton.Text = (currentFixerAction == FixActions.None || CurrentFixer != character || currentFixerAction != FixActions.Repair) ?
repairButtonText : repairButtonText :
@@ -316,12 +316,17 @@ namespace Barotrauma
string colorStr = XMLExtensions.ColorToString(!item.AllowStealing ? GUI.Style.Red : Color.White); 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‖"; toolTip = $"‖color:{colorStr}‖{name}‖color:end‖";
if (itemsInSlot.All(it => it.NonInteractable || it.NonPlayerTeamInteractable)) if (itemsInSlot.All(it => it.NonInteractable || it.NonPlayerTeamInteractable))
{ {
toolTip += " " + TextManager.Get("connectionlocked"); 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)); 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‖"; toolTip += $"‖color:{conditionColorStr}‖ ({(int)item.ConditionPercentage} %)‖color:end‖";
@@ -1068,7 +1068,7 @@ namespace Barotrauma
foreach (Character otherCharacter in Character.CharacterList) foreach (Character otherCharacter in Character.CharacterList)
{ {
if (otherCharacter != character && if (otherCharacter != character &&
otherCharacter.SelectedConstruction == character.SelectedConstruction) otherCharacter.SelectedConstruction == this)
{ {
ItemInUseWarning.Visible = true; ItemInUseWarning.Visible = true;
if (mergedHUDRect.Width > GameMain.GraphicsWidth / 2) { mergedHUDRect.Inflate(-GameMain.GraphicsWidth / 4, 0); } 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<RemoteController>() == null)
{ {
if (Character.Controlled.SelectedConstruction?.GetComponent<RemoteController>()?.TargetItem != this && if (Character.Controlled.SelectedConstruction?.GetComponent<RemoteController>()?.TargetItem != this &&
!Character.Controlled.HeldItems.Any(it => it.GetComponent<RemoteController>()?.TargetItem == this)) !Character.Controlled.HeldItems.Any(it => it.GetComponent<RemoteController>()?.TargetItem == this))
@@ -1537,6 +1537,7 @@ namespace Barotrauma
byte bodyType = msg.ReadByte(); byte bodyType = msg.ReadByte();
bool spawnedInOutpost = msg.ReadBoolean(); bool spawnedInOutpost = msg.ReadBoolean();
bool allowStealing = msg.ReadBoolean(); bool allowStealing = msg.ReadBoolean();
int quality = msg.ReadRangedInteger(0, Items.Components.Quality.MaxQuality);
byte teamID = msg.ReadByte(); byte teamID = msg.ReadByte();
bool tagsChanged = msg.ReadBoolean(); bool tagsChanged = msg.ReadBoolean();
string tags = ""; string tags = "";
@@ -1612,7 +1613,8 @@ namespace Barotrauma
item = new Item(itemPrefab, pos, sub, id: itemId) item = new Item(itemPrefab, pos, sub, id: itemId)
{ {
SpawnedInOutpost = spawnedInOutpost, SpawnedInOutpost = spawnedInOutpost,
AllowStealing = allowStealing AllowStealing = allowStealing,
Quality = quality
}; };
} }
catch (Exception e) catch (Exception e)
@@ -1694,7 +1694,7 @@ namespace Barotrauma
private void CreateJobVariantTooltip(JobPrefab jobPrefab, int variant, GUIComponent parentSlot) 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") style: "GUIToolTip")
{ {
UserData = new Pair<JobPrefab, int>(jobPrefab, variant) UserData = new Pair<JobPrefab, int>(jobPrefab, variant)
@@ -1707,17 +1707,7 @@ namespace Barotrauma
AbsoluteSpacing = (int)(15 * GUI.Scale) AbsoluteSpacing = (int)(15 * GUI.Scale)
}; };
string name = 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);
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);
var itemIdentifiers = jobPrefab.ItemIdentifiers[variant] var itemIdentifiers = jobPrefab.ItemIdentifiers[variant]
.Distinct() .Distinct()
@@ -1726,7 +1716,7 @@ namespace Barotrauma
int itemsPerRow = 5; int itemsPerRow = 5;
int rows = (int)Math.Max(Math.Ceiling(itemIdentifiers.Count() / (float)itemsPerRow), 1); 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, int>(jobPrefab, variant), itemsPerRow); }); onDraw: (sb, component) => { DrawJobVariantItems(sb, component, new Pair<JobPrefab, int>(jobPrefab, variant), itemsPerRow); });
jobVariantTooltip.RectTransform.MinSize = new Point(0, content.RectTransform.Children.Sum(c => c.Rect.Height + content.AbsoluteSpacing)); jobVariantTooltip.RectTransform.MinSize = new Point(0, content.RectTransform.Children.Sum(c => c.Rect.Height + content.AbsoluteSpacing));
@@ -1,6 +1,7 @@
#nullable enable #nullable enable
using System; using System;
using System.Linq; using System.Linq;
using Barotrauma.Extensions;
using Barotrauma.Items.Components; using Barotrauma.Items.Components;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
@@ -23,6 +24,8 @@ namespace Barotrauma
private Character? dummyCharacter; private Character? dummyCharacter;
public static Effect BlueprintEffect; public static Effect BlueprintEffect;
private TabMenu tabMenu;
public TestScreen() public TestScreen()
{ {
Cam = new Camera(); Cam = new Camera();
@@ -50,18 +53,15 @@ namespace Barotrauma
dummyCharacter?.Remove(); 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>();
miniMap.PowerConsumption = 0;
dummyCharacter = Character.Create(CharacterPrefab.HumanSpeciesName, Vector2.Zero, "", id: Entity.DummyID, hasAi: false); 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.Info.Name = "Galldren";
dummyCharacter.Inventory.CreateSlots(); dummyCharacter.Inventory.CreateSlots();
Character.Controlled = dummyCharacter; Character.Controlled = dummyCharacter;
GameMain.World.ProcessChanges(); GameMain.World.ProcessChanges();
TabMenu.selectedTab = TabMenu.InfoFrameTab.Talents;
tabMenu = new TabMenu();
} }
public override void AddToGUIUpdateList() public override void AddToGUIUpdateList()
@@ -69,34 +69,21 @@ namespace Barotrauma
Frame.AddToGUIUpdateList(); Frame.AddToGUIUpdateList();
CharacterHUD.AddToGUIUpdateList(dummyCharacter); CharacterHUD.AddToGUIUpdateList(dummyCharacter);
dummyCharacter?.SelectedConstruction?.AddToGUIUpdateList(); dummyCharacter?.SelectedConstruction?.AddToGUIUpdateList();
tabMenu.AddToGUIUpdateList();
} }
public override void Update(double deltaTime) public override void Update(double deltaTime)
{ {
base.Update(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.ControlLocalPlayer((float)deltaTime, Cam, false);
dummy.Control((float)deltaTime, Cam); dummy.Control((float)deltaTime, Cam);
dummy.Submarine = submarine;
} }
GUI.Update((float)deltaTime);
} }
public override void Draw(double deltaTime, GraphicsDevice graphics, SpriteBatch spriteBatch) public override void Draw(double deltaTime, GraphicsDevice graphics, SpriteBatch spriteBatch)
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product> <Product>Barotrauma</Product>
<Version>0.1500.2.0</Version> <Version>0.1500.3.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName> <AssemblyName>Barotrauma</AssemblyName>
+1 -1
View File
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product> <Product>Barotrauma</Product>
<Version>0.1500.2.0</Version> <Version>0.1500.3.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName> <AssemblyName>Barotrauma</AssemblyName>
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product> <Product>Barotrauma</Product>
<Version>0.1500.2.0</Version> <Version>0.1500.3.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName> <AssemblyName>Barotrauma</AssemblyName>
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product> <Product>Barotrauma Dedicated Server</Product>
<Version>0.1500.2.0</Version> <Version>0.1500.3.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName> <AssemblyName>DedicatedServer</AssemblyName>
+1 -1
View File
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product> <Product>Barotrauma Dedicated Server</Product>
<Version>0.1500.2.0</Version> <Version>0.1500.3.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName> <AssemblyName>DedicatedServer</AssemblyName>
@@ -282,6 +282,7 @@ namespace Barotrauma
msg.Write(body == null ? (byte)0 : (byte)body.BodyType); msg.Write(body == null ? (byte)0 : (byte)body.BodyType);
msg.Write(SpawnedInOutpost); msg.Write(SpawnedInOutpost);
msg.Write(AllowStealing); msg.Write(AllowStealing);
msg.WriteRangedInteger(Quality, 0, Items.Components.Quality.MaxQuality);
byte teamID = 0; byte teamID = 0;
foreach (WifiComponent wifiComponent in GetComponents<WifiComponent>()) foreach (WifiComponent wifiComponent in GetComponents<WifiComponent>())
@@ -265,7 +265,7 @@ namespace Barotrauma.Networking
"192-255", "192-255",
"384-591", "384-591",
"1024-1279", "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); string[] allowedClientNameCharsStr = doc.Root.GetAttributeStringArray("AllowedClientNameChars", defaultAllowedClientNameChars);
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product> <Product>Barotrauma Dedicated Server</Product>
<Version>0.1500.2.0</Version> <Version>0.1500.3.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName> <AssemblyName>DedicatedServer</AssemblyName>
@@ -161,8 +161,8 @@ namespace Barotrauma
for (int i = 0; i < container.Inventory.Capacity; i++) for (int i = 0; i < container.Inventory.Capacity; i++)
{ {
if (container.Inventory.GetItemAt(i) != null) { continue; } if (container.Inventory.GetItemAt(i) != null) { continue; }
if (MapEntityPrefab.List.GetRandom(e => e is ItemPrefab i && container.CanBeContained(i) && if (MapEntityPrefab.List.GetRandom(e => e is ItemPrefab ip && container.CanBeContained(ip, i) &&
Config.ForbiddenAmmunition.None(id => id.Equals(i.Identifier, StringComparison.OrdinalIgnoreCase)), Rand.RandSync.Server) is ItemPrefab ammoPrefab) 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); Item ammo = new Item(ammoPrefab, container.Item.WorldPosition, Wreck);
if (!container.Inventory.TryPutItem(ammo, i, allowSwapping: false, allowCombine: false, user: null, createNetworkEvent: false)) if (!container.Inventory.TryPutItem(ammo, i, allowSwapping: false, allowCombine: false, user: null, createNetworkEvent: false))
@@ -1430,6 +1430,7 @@ namespace Barotrauma
//throwing conscious/moving characters around takes more force -> double the flow force //throwing conscious/moving characters around takes more force -> double the flow force
if (character.CanMove) { flowForce *= 2.0f; } if (character.CanMove) { flowForce *= 2.0f; }
flowForce *= 1 - Math.Clamp(character.GetStatValue(StatTypes.FlowResistance), 0f, 1f);
float flowForceMagnitude = flowForce.Length(); float flowForceMagnitude = flowForce.Length();
float limbMultipier = limbs.Count(l => l.inWater) / (float)limbs.Length; float limbMultipier = limbs.Count(l => l.inWater) / (float)limbs.Length;
@@ -251,6 +251,8 @@ namespace Barotrauma
private readonly List<Attacker> lastAttackers = new List<Attacker>(); private readonly List<Attacker> lastAttackers = new List<Attacker>();
public IEnumerable<Attacker> LastAttackers => lastAttackers; public IEnumerable<Attacker> LastAttackers => lastAttackers;
public Character LastAttacker => lastAttackers.LastOrDefault()?.Character; public Character LastAttacker => lastAttackers.LastOrDefault()?.Character;
public Character LastOrderedCharacter { get; private set; }
public Character SecondLastOrderedCharacter { get; private set; }
public Entity LastDamageSource; 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! //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 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 wasRagdolled = false;
bool selfRagdolled = false; bool selfRagdolled = false;
@@ -3131,6 +3133,12 @@ namespace Barotrauma
{ {
var abilityOrderedCharacter = new AbilityCharacter(this); var abilityOrderedCharacter = new AbilityCharacter(this);
orderGiver.CheckTalents(AbilityEffectType.OnGiveOrder, abilityOrderedCharacter); orderGiver.CheckTalents(AbilityEffectType.OnGiveOrder, abilityOrderedCharacter);
if (orderGiver.LastOrderedCharacter != this)
{
orderGiver.SecondLastOrderedCharacter = orderGiver.LastOrderedCharacter;
orderGiver.LastOrderedCharacter = this;
}
} }
if (AIController is HumanAIController humanAI) 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) : 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); 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(); } if (limbHit == null) { return new AttackResult(); }
Vector2 forceWorld = attack.TargetImpulseWorld + attack.TargetForceWorld; Vector2 forceWorld = attack.TargetImpulseWorld + attack.TargetForceWorld;
if (attacker != null) if (attacker != null)
@@ -3623,11 +3638,6 @@ namespace Barotrauma
ApplyStatusEffects(ActionType.OnDamaged, 1.0f); ApplyStatusEffects(ActionType.OnDamaged, 1.0f);
hitLimb.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; return attackResult;
} }
@@ -979,6 +979,8 @@ namespace Barotrauma
increase *= SkillSettings.Current.AssistantSkillIncreaseMultiplier; increase *= SkillSettings.Current.AssistantSkillIncreaseMultiplier;
} }
increase *= 1f + Character.GetStatValue(StatTypes.SkillGainSpeed);
float prevLevel = Job.GetSkillLevel(skillIdentifier); float prevLevel = Job.GetSkillLevel(skillIdentifier);
Job.IncreaseSkillLevel(skillIdentifier, increase, Character.HasAbilityFlag(AbilityFlags.GainSkillPastMaximum)); Job.IncreaseSkillLevel(skillIdentifier, increase, Character.HasAbilityFlag(AbilityFlags.GainSkillPastMaximum));
@@ -1527,6 +1529,17 @@ namespace Barotrauma
return 0f; 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) public void ChangeSavedStatValue(StatTypes statType, float value, string statIdentifier, bool removeOnDeath, bool removeAfterRound = false, float maxValue = float.MaxValue)
{ {
@@ -971,7 +971,7 @@ namespace Barotrauma
/// <param name="treatmentSuitability">A dictionary where the key is the identifier of the item and the value the suitability</param> /// <param name="treatmentSuitability">A dictionary where the key is the identifier of the item and the value the suitability</param>
/// <param name="normalize">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.</param> /// <param name="normalize">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.</param>
/// <param name="randomization">Amount of randomization to apply to the values (0 = the values are accurate, 1 = the values are completely random)</param> /// <param name="randomization">Amount of randomization to apply to the values (0 = the values are accurate, 1 = the values are completely random)</param>
public void GetSuitableTreatments(Dictionary<string, float> treatmentSuitability, bool normalize, Limb limb = null, float randomization = 0.0f) public void GetSuitableTreatments(Dictionary<string, float> treatmentSuitability, bool normalize, Limb limb = null, bool ignoreHiddenAfflictions = false, float randomization = 0.0f)
{ {
//key = item identifier //key = item identifier
//float = suitability //float = suitability
@@ -980,6 +980,7 @@ namespace Barotrauma
foreach (Affliction affliction in getAfflictions(limb)) foreach (Affliction affliction in getAfflictions(limb))
{ {
if (affliction.Strength < affliction.Prefab.TreatmentThreshold) { continue; } if (affliction.Strength < affliction.Prefab.TreatmentThreshold) { continue; }
if (ignoreHiddenAfflictions && affliction.Strength < affliction.Prefab.ShowIconThreshold) { continue; }
foreach (KeyValuePair<string, float> treatment in affliction.Prefab.TreatmentSuitability) foreach (KeyValuePair<string, float> treatment in affliction.Prefab.TreatmentSuitability)
{ {
if (!treatmentSuitability.ContainsKey(treatment.Key)) if (!treatmentSuitability.ContainsKey(treatment.Key))
@@ -12,7 +12,7 @@ namespace Barotrauma.Abilities
Ranged = 2 Ranged = 2
}; };
private WeaponType weapontype; private readonly WeaponType weapontype;
public AbilityConditionIsAiming(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) public AbilityConditionIsAiming(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
{ {
switch (conditionElement.GetAttributeString("weapontype", "")) switch (conditionElement.GetAttributeString("weapontype", ""))
@@ -43,7 +43,7 @@ namespace Barotrauma.Abilities
break; break;
default: default:
aimingCorrectItem |= animController.IsAiming || animController.IsAimingMelee; aimingCorrectItem |= animController.IsAiming || animController.IsAimingMelee;
break; break;
} }
} }
} }
@@ -6,12 +6,12 @@ namespace Barotrauma.Abilities
{ {
class AbilityConditionItem : AbilityConditionData class AbilityConditionItem : AbilityConditionData
{ {
private readonly string identifier; private readonly string[] identifiers;
private readonly string[] tags; private readonly string[] tags;
public AbilityConditionItem(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) public AbilityConditionItem(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
{ {
identifier = conditionElement.GetAttributeString("identifier", string.Empty).ToLowerInvariant(); identifiers = conditionElement.GetAttributeStringArray("identifiers", Array.Empty<string>(), convertToLowerInvariant: true);
tags = conditionElement.GetAttributeStringArray("tags", Array.Empty<string>(), convertToLowerInvariant: true); tags = conditionElement.GetAttributeStringArray("tags", Array.Empty<string>(), convertToLowerInvariant: true);
} }
@@ -29,15 +29,15 @@ namespace Barotrauma.Abilities
if (itemPrefab != null) if (itemPrefab != null)
{ {
if (!string.IsNullOrEmpty(identifier)) if (identifiers.Any())
{ {
if (itemPrefab.Identifier != identifier) if (!identifiers.Any(t => itemPrefab.Identifier == t))
{ {
return false; 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 else
{ {
@@ -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;
}
}
}
@@ -13,7 +13,7 @@ namespace Barotrauma.Abilities
protected override bool MatchesConditionSpecific() 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); float currentFloodPercentage = character.Submarine.GetHulls(false).Average(h => h.WaterPercentage);
return currentFloodPercentage / 100 > floodPercentage; return currentFloodPercentage / 100 > floodPercentage;
} }
@@ -10,6 +10,7 @@ namespace Barotrauma.Abilities
protected readonly List<StatusEffect> statusEffects; protected readonly List<StatusEffect> statusEffects;
private readonly bool nearbyCharactersAppliesToSelf;
private readonly bool applyToSelected; private readonly bool applyToSelected;
readonly List<ISerializableEntity> targets = new List<ISerializableEntity>(); readonly List<ISerializableEntity> targets = new List<ISerializableEntity>();
@@ -18,6 +19,7 @@ namespace Barotrauma.Abilities
{ {
statusEffects = CharacterAbilityGroup.ParseStatusEffects(CharacterTalent, abilityElement.GetChildElement("statuseffects")); statusEffects = CharacterAbilityGroup.ParseStatusEffects(CharacterTalent, abilityElement.GetChildElement("statuseffects"));
applyToSelected = abilityElement.GetAttributeBool("applytoselected", false); applyToSelected = abilityElement.GetAttributeBool("applytoselected", false);
nearbyCharactersAppliesToSelf = abilityElement.GetAttributeBool("nearbycharactersappliestoself", true);
} }
protected void ApplyEffectSpecific(Character targetCharacter) protected void ApplyEffectSpecific(Character targetCharacter)
@@ -26,7 +28,7 @@ namespace Barotrauma.Abilities
{ {
if (statusEffect.HasTargetType(StatusEffect.TargetType.UseTarget)) 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.SetUser(targetCharacter);
statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, targetCharacter, targetCharacter); statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, targetCharacter, targetCharacter);
} }
@@ -34,6 +36,10 @@ namespace Barotrauma.Abilities
{ {
targets.Clear(); targets.Clear();
targets.AddRange(statusEffect.GetNearbyTargets(targetCharacter.WorldPosition, targets)); targets.AddRange(statusEffect.GetNearbyTargets(targetCharacter.WorldPosition, targets));
if (!nearbyCharactersAppliesToSelf)
{
targets.RemoveAll(c => c == Character);
}
statusEffect.SetUser(Character); statusEffect.SetUser(Character);
statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, targetCharacter, targets); statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, targetCharacter, targets);
} }
@@ -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;
}
}
}
@@ -27,8 +27,7 @@ namespace Barotrauma.Abilities
targetAllies = abilityElement.GetAttributeBool("targetallies", false); targetAllies = abilityElement.GetAttributeBool("targetallies", false);
removeOnDeath = abilityElement.GetAttributeBool("removeondeath", true); removeOnDeath = abilityElement.GetAttributeBool("removeondeath", true);
removeAfterRound = abilityElement.GetAttributeBool("removeafterround", false); removeAfterRound = abilityElement.GetAttributeBool("removeafterround", false);
giveOnAddingFirstTime = abilityElement.GetAttributeBool("giveonaddingfirsttime", false); giveOnAddingFirstTime = abilityElement.GetAttributeBool("giveonaddingfirsttime", characterAbilityGroup.AbilityEffectType == AbilityEffectType.None);
//maximumValue = abilityElement.GetAttributeFloat("maximumvalue", float.MaxValue);
} }
public override void InitializeAbility(bool addingFirstTime) public override void InitializeAbility(bool addingFirstTime)
@@ -1,4 +1,5 @@
using Microsoft.Xna.Framework; using Barotrauma.Extensions;
using Microsoft.Xna.Framework;
using System.Xml.Linq; using System.Xml.Linq;
namespace Barotrauma.Abilities namespace Barotrauma.Abilities
@@ -7,13 +8,22 @@ namespace Barotrauma.Abilities
{ {
public override bool AppliesEffectOnIntervalUpdate => true; public override bool AppliesEffectOnIntervalUpdate => true;
private string skillIdentifier; private readonly string skillIdentifier;
private float skillIncrease; private readonly float skillIncrease;
public CharacterAbilityIncreaseSkill(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) public CharacterAbilityIncreaseSkill(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{ {
skillIdentifier = abilityElement.GetAttributeString("skillidentifier", "").ToLowerInvariant(); skillIdentifier = abilityElement.GetAttributeString("skillidentifier", "").ToLowerInvariant();
skillIncrease = abilityElement.GetAttributeFloat("skillincrease", 0f); 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() protected override void ApplyEffect()
@@ -35,7 +45,17 @@ namespace Barotrauma.Abilities
private void ApplyEffectSpecific(Character character) 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);
}
} }
} }
} }
@@ -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;
}
}
}
}
@@ -4,8 +4,8 @@ namespace Barotrauma.Abilities
{ {
class CharacterAbilityModifyValue : CharacterAbility class CharacterAbilityModifyValue : CharacterAbility
{ {
private float addedValue; private readonly float addedValue;
private float multiplyValue; private readonly float multiplyValue;
public CharacterAbilityModifyValue(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) public CharacterAbilityModifyValue(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{ {
@@ -6,12 +6,14 @@ namespace Barotrauma.Abilities
{ {
class CharacterAbilityByTheBook : CharacterAbility class CharacterAbilityByTheBook : CharacterAbility
{ {
private int moneyAmount; private readonly int moneyAmount;
private int max; private readonly int experienceAmount;
private readonly int max;
public CharacterAbilityByTheBook(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement) public CharacterAbilityByTheBook(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{ {
moneyAmount = abilityElement.GetAttributeInt("moneyamount", 0); moneyAmount = abilityElement.GetAttributeInt("moneyamount", 0);
experienceAmount = abilityElement.GetAttributeInt("experienceamount", 0);
max = abilityElement.GetAttributeInt("max", 0); max = abilityElement.GetAttributeInt("max", 0);
} }
@@ -28,6 +30,10 @@ namespace Barotrauma.Abilities
if (!enemyCharacter.LockHands) { continue; } if (!enemyCharacter.LockHands) { continue; }
if (timesGiven > max) { continue; } if (timesGiven > max) { continue; }
Character.GiveMoney(moneyAmount); Character.GiveMoney(moneyAmount);
foreach (Character character in Character.GetFriendlyCrew(Character))
{
character.Info?.GiveExperience(experienceAmount);
}
timesGiven++; timesGiven++;
} }
@@ -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<string>(), 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;
}
}
}
}
@@ -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<Fabricator>() is Fabricator fabricator && fabricator.IsActive)
{
fabricator.FabricationSpeedMultiplier += addedFabricationSpeed;
}
}
}
}
}
@@ -1,39 +0,0 @@
using System.Collections.Generic;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class CharacterAbilityTaskmaster : CharacterAbility
{
private readonly List<StatusEffect> statusEffects;
private readonly List<StatusEffect> 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;
}
}
}
}
@@ -14,6 +14,8 @@ namespace Barotrauma.Abilities
// currently only used to turn off simulation if random conditions are in use // currently only used to turn off simulation if random conditions are in use
public bool IsActive { get; private set; } = true; public bool IsActive { get; private set; } = true;
public readonly AbilityEffectType AbilityEffectType;
protected int maxTriggerCount { get; } protected int maxTriggerCount { get; }
protected int timesTriggered = 0; protected int timesTriggered = 0;
@@ -24,8 +26,9 @@ namespace Barotrauma.Abilities
// separate dictionaries for each type of characterability? // separate dictionaries for each type of characterability?
protected readonly List<CharacterAbility> characterAbilities = new List<CharacterAbility>(); protected readonly List<CharacterAbility> characterAbilities = new List<CharacterAbility>();
public CharacterAbilityGroup(CharacterTalent characterTalent, XElement abilityElementGroup) public CharacterAbilityGroup(AbilityEffectType abilityEffectType, CharacterTalent characterTalent, XElement abilityElementGroup)
{ {
AbilityEffectType = abilityEffectType;
CharacterTalent = characterTalent; CharacterTalent = characterTalent;
Character = CharacterTalent.Character; Character = CharacterTalent.Character;
maxTriggerCount = abilityElementGroup.GetAttributeInt("maxtriggercount", int.MaxValue); maxTriggerCount = abilityElementGroup.GetAttributeInt("maxtriggercount", int.MaxValue);
@@ -168,8 +171,7 @@ namespace Barotrauma.Abilities
public static StatTypes ParseStatType(string statTypeString, string debugIdentifier) public static StatTypes ParseStatType(string statTypeString, string debugIdentifier)
{ {
StatTypes statType; if (!Enum.TryParse(statTypeString, true, out StatTypes statType))
if (!Enum.TryParse(statTypeString, true, out statType))
{ {
DebugConsole.ThrowError("Invalid stat type type \"" + statTypeString + "\" in CharacterTalent (" + debugIdentifier + ")"); DebugConsole.ThrowError("Invalid stat type type \"" + statTypeString + "\" in CharacterTalent (" + debugIdentifier + ")");
} }
@@ -8,7 +8,8 @@ namespace Barotrauma.Abilities
{ {
class CharacterAbilityGroupEffect : CharacterAbilityGroup 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) public void CheckAbilityGroup(AbilityObject abilityObject)
{ {
@@ -15,7 +15,8 @@ namespace Barotrauma.Abilities
private float effectDelayTimer; 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 // too many overlapping intervals could cause hitching? maybe randomize a little
interval = abilityElementGroup.GetAttributeFloat("interval", 0f); interval = abilityElementGroup.GetAttributeFloat("interval", 0f);
@@ -85,14 +85,13 @@ namespace Barotrauma
// XML logic // XML logic
private void LoadAbilityGroupInterval(XElement abilityGroup) private void LoadAbilityGroupInterval(XElement abilityGroup)
{ {
string name = abilityGroup.Name.ToString().ToLowerInvariant(); characterAbilityGroupIntervals.Add(new CharacterAbilityGroupInterval(AbilityEffectType.Undefined, this, abilityGroup));
characterAbilityGroupIntervals.Add(new CharacterAbilityGroupInterval(this, abilityGroup));
} }
private void LoadAbilityGroupEffect(XElement abilityGroup) private void LoadAbilityGroupEffect(XElement abilityGroup)
{ {
AbilityEffectType abilityEffectType = ParseAbilityEffectType(this, abilityGroup.GetAttributeString("abilityeffecttype", "none")); 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) public void AddAbilityGroupEffect(CharacterAbilityGroupEffect characterAbilityGroup, AbilityEffectType abilityEffectType = AbilityEffectType.None)
@@ -1,5 +1,4 @@
using Microsoft.Xna.Framework; using System;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Xml.Linq; using System.Xml.Linq;
@@ -8,13 +7,19 @@ namespace Barotrauma
{ {
class TalentTree class TalentTree
{ {
public enum TalentTreeStageState
{
Invalid,
Locked,
Unlocked,
Available,
Highlighted
}
public static readonly Dictionary<string, TalentTree> JobTalentTrees = new Dictionary<string, TalentTree>(); public static readonly Dictionary<string, TalentTree> JobTalentTrees = new Dictionary<string, TalentTree>();
public readonly List<TalentSubTree> TalentSubTrees = new List<TalentSubTree>(); public readonly List<TalentSubTree> TalentSubTrees = new List<TalentSubTree>();
private static HashSet<string> subtreeTalents = new HashSet<string>();
private const string PlaceholderTalent = "placeholder";
public XElement ConfigElement public XElement ConfigElement
{ {
get; get;
@@ -42,7 +47,6 @@ namespace Barotrauma
HashSet<string> duplicateSet = new HashSet<string>(); HashSet<string> duplicateSet = new HashSet<string>();
foreach (string talent in TalentSubTrees.SelectMany(s => s.TalentOptionStages.SelectMany(o => o.Talents.Select(t => t.Identifier)))) 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)); TalentPrefab talentPrefab = TalentPrefab.TalentPrefabs.Find(c => c.Identifier.Equals(talent, StringComparison.OrdinalIgnoreCase));
if (talentPrefab == null) if (talentPrefab == null)
{ {
@@ -97,7 +101,7 @@ namespace Barotrauma
} }
break; break;
default: 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; break;
} }
} }
@@ -117,10 +121,63 @@ namespace Barotrauma
return IsViableTalentForCharacter(character, talentIdentifier, character?.Info?.UnlockedTalents ?? Enumerable.Empty<string>()); return IsViableTalentForCharacter(character, talentIdentifier, character?.Info?.UnlockedTalents ?? Enumerable.Empty<string>());
} }
// i hate this function - markus
public static TalentTreeStageState GetTalentOptionStageState(Character character, string subTreeIdentifier, int index, List<string> 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<string> selectedTalents) public static bool IsViableTalentForCharacter(Character character, string talentIdentifier, IEnumerable<string> selectedTalents)
{ {
if (talentIdentifier == PlaceholderTalent) { return false; }
if (character?.Info?.Job.Prefab == null) { return false; } if (character?.Info?.Job.Prefab == null) { return false; }
if (character.Info.GetTotalTalentPoints() - selectedTalents.Count() <= 0) { return false; } if (character.Info.GetTotalTalentPoints() - selectedTalents.Count() <= 0) { return false; }
@@ -173,12 +230,16 @@ namespace Barotrauma
{ {
public string Identifier { get; } public string Identifier { get; }
public string DisplayName { get; }
public readonly List<TalentOption> TalentOptionStages = new List<TalentOption>(); public readonly List<TalentOption> TalentOptionStages = new List<TalentOption>();
public TalentSubTree(XElement subTreeElement) public TalentSubTree(XElement subTreeElement)
{ {
Identifier = subTreeElement.GetAttributeString("identifier", ""); Identifier = subTreeElement.GetAttributeString("identifier", "");
DisplayName = TextManager.Get("talenttree." + Identifier, returnNull: true) ?? Identifier;
foreach (XElement talentOptionsElement in subTreeElement.GetChildElements("talentoptions")) foreach (XElement talentOptionsElement in subTreeElement.GetChildElements("talentoptions"))
{ {
TalentOptionStages.Add(new TalentOption(talentOptionsElement, Identifier)); TalentOptionStages.Add(new TalentOption(talentOptionsElement, Identifier));
@@ -196,6 +257,7 @@ namespace Barotrauma
foreach (XElement talentOptionElement in talentOptionsElement.GetChildElements("talentoption")) foreach (XElement talentOptionElement in talentOptionsElement.GetChildElements("talentoption"))
{ {
string identifier = talentOptionElement.GetAttributeString("identifier", string.Empty); string identifier = talentOptionElement.GetAttributeString("identifier", string.Empty);
if (!TalentPrefab.TalentPrefabs.ContainsKey(identifier)) if (!TalentPrefab.TalentPrefabs.ContainsKey(identifier))
{ {
DebugConsole.ThrowError($"Error in talent tree \"{debugIdentifier}\" - could not find a talent with the identifier \"{identifier}\"."); DebugConsole.ThrowError($"Error in talent tree \"{debugIdentifier}\" - could not find a talent with the identifier \"{identifier}\".");
@@ -12,16 +12,16 @@
public enum ActionType public enum ActionType
{ {
Always, OnPicked, OnUse, OnSecondaryUse, Always = 0, OnPicked = 1, OnUse = 2, OnSecondaryUse = 3,
OnWearing, OnContaining, OnContained, OnNotContained, OnWearing = 4, OnContaining = 5, OnContained = 6, OnNotContained = 7,
OnActive, OnFailure, OnBroken, OnActive = 8, OnFailure = 9, OnBroken = 10,
OnFire, InWater, NotInWater, OnFire = 11, InWater = 12, NotInWater = 13,
OnImpact, OnImpact = 14,
OnEating, OnEating = 15,
OnDamaged, OnDamaged = 16,
OnSevered, OnSevered = 17,
OnProduceSpawned, OnProduceSpawned = 18,
OnOpen, OnClose, OnOpen = 19, OnClose = 20,
OnDeath = OnBroken, OnDeath = OnBroken,
OnSuccess, OnSuccess,
OnAbility, OnAbility,
@@ -34,6 +34,7 @@
OnAttack, OnAttack,
OnAttackResult, OnAttackResult,
OnAttacked, OnAttacked,
OnAttackedResult,
OnGainSkillPoint, OnGainSkillPoint,
OnAllyGainSkillPoint, OnAllyGainSkillPoint,
OnRepairComplete, OnRepairComplete,
@@ -57,7 +58,9 @@
OnGainMissionMoney, OnGainMissionMoney,
OnItemDeconstructed, OnItemDeconstructed,
OnItemDeconstructedMaterial, OnItemDeconstructedMaterial,
OnItemDeconstructedRetainProbability,
OnStopTinkering, OnStopTinkering,
OnItemPicked,
AfterSubmarineAttacked, AfterSubmarineAttacked,
} }
@@ -78,26 +81,37 @@
BuffDurationMultiplier, BuffDurationMultiplier,
DebuffDurationMultiplier, DebuffDurationMultiplier,
MedicalItemEffectivenessMultiplier, MedicalItemEffectivenessMultiplier,
FlowResistance,
// Combat // Combat
AttackMultiplier, AttackMultiplier,
TeamAttackMultiplier, TeamAttackMultiplier,
RangedAttackSpeed, RangedAttackSpeed,
TurretAttackSpeed, TurretAttackSpeed,
TurretPowerCostReduction, TurretPowerCostReduction,
TurretChargeSpeed,
MeleeAttackSpeed, MeleeAttackSpeed,
MeleeAttackMultiplier, MeleeAttackMultiplier,
RangedAttackMultiplier,
RangedSpreadReduction, RangedSpreadReduction,
// Utility // Utility
RepairSpeed, RepairSpeed,
DeconstructorSpeedMultiplier, DeconstructorSpeedMultiplier,
TinkeringDuration, TinkeringDuration,
RepairToolStructureRepairMultiplier,
RepairToolStructureDamageMultiplier,
RepairToolDeattachTimeMultiplier,
MaxRepairConditionMultiplier,
IncreaseFabricationQuality,
GeneticMaterialRefineBonus,
GeneticMaterialTaintedProbabilityReductionOnCombine,
SkillGainSpeed,
// Misc // Misc
ReputationGainMultiplier, ReputationGainMultiplier,
MissionMoneyGainMultiplier, MissionMoneyGainMultiplier,
ExperienceGainMultiplier, ExperienceGainMultiplier,
MissionExperienceGainMultiplier, MissionExperienceGainMultiplier,
// these should be deprecated and moved to their own implementation, no sense making them share space with stat values // these should be deprecated and moved to their own implementation, no sense making them share space with stat values
Coathor, Coauthor,
WarriorPoetMissionRuns, WarriorPoetMissionRuns,
WarriorPoetEnemiesKilled, WarriorPoetEnemiesKilled,
} }
@@ -113,7 +127,8 @@
CanTinkerFabricatorsAndDeconstructors, CanTinkerFabricatorsAndDeconstructors,
TinkeringPowersDevices, TinkeringPowersDevices,
GainSkillPastMaximum, GainSkillPastMaximum,
RetainExperienceForNewCharacter RetainExperienceForNewCharacter,
AllowSecondOrderedTarget,
} }
} }
@@ -306,7 +306,7 @@ namespace Barotrauma
case 0: case 0:
if (items.All(it => it.Removed || it.Condition <= 0.0f) && 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)) requireRescue.All(c => c.Submarine?.Info.Type == SubmarineType.Player))
{ {
State = 1; State = 1;
@@ -382,11 +382,11 @@ namespace Barotrauma
State = newState; 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() public override void End()
@@ -247,8 +247,8 @@ namespace Barotrauma
public override void End() public override void End()
{ {
var root = item.GetRootContainer() ?? item; var root = item?.GetRootContainer() ?? item;
if (root.CurrentHull?.Submarine == null || (!root.CurrentHull.Submarine.AtEndExit && !root.CurrentHull.Submarine.AtStartExit) || item.Removed) if (root?.CurrentHull?.Submarine == null || (!root.CurrentHull.Submarine.AtEndExit && !root.CurrentHull.Submarine.AtStartExit) || item.Removed)
{ {
return; return;
} }
@@ -92,5 +92,12 @@ namespace Barotrauma.Extensions
{ {
return MathUtils.NearlyEqual(v.X, other.X) && MathUtils.NearlyEqual(v.Y, other.Y); 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;
}
} }
} }
@@ -60,7 +60,6 @@ namespace Barotrauma.Items.Components
} }
} }
public GeneticMaterial(Item item, XElement element) public GeneticMaterial(Item item, XElement element)
: base(item, element) : base(item, element)
{ {
@@ -85,7 +84,7 @@ namespace Barotrauma.Items.Components
public bool CanBeCombinedWith(GeneticMaterial otherGeneticMaterial) 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) public override void Equip(Character character)
@@ -147,9 +146,12 @@ namespace Barotrauma.Items.Components
public bool Combine(GeneticMaterial otherGeneticMaterial, Character user) public bool Combine(GeneticMaterial otherGeneticMaterial, Character user)
{ {
if (!CanBeCombinedWith(otherGeneticMaterial)) { return false; } if (!CanBeCombinedWith(otherGeneticMaterial)) { return false; }
float conditionIncrease = Rand.Range(ConditionIncreaseOnCombineMin, ConditionIncreaseOnCombineMax);
conditionIncrease *= 1.0f + user.GetStatValue(StatTypes.GeneticMaterialRefineBonus);
if (item.Prefab == otherGeneticMaterial.item.Prefab) 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); float taintedProbability = GetTaintedProbabilityOnRefine(user);
if (taintedProbability >= Rand.Range(0.0f, 1.0f)) if (taintedProbability >= Rand.Range(0.0f, 1.0f))
{ {
@@ -160,9 +162,14 @@ namespace Barotrauma.Items.Components
else else
{ {
item.Condition = otherGeneticMaterial.Item.Condition = 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); 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; return false;
} }
} }
@@ -172,7 +179,14 @@ namespace Barotrauma.Items.Components
if (user == null) { return 1.0f; } if (user == null) { return 1.0f; }
float probability = MathHelper.Lerp(0.0f, 0.99f, item.Condition / 100.0f); float probability = MathHelper.Lerp(0.0f, 0.99f, item.Condition / 100.0f);
probability *= MathHelper.Lerp(1.0f, 0.25f, DegreeOfSuccess(user)); 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() private void MakeTainted()
@@ -1,4 +1,5 @@
using Barotrauma.Networking; using Barotrauma.Abilities;
using Barotrauma.Networking;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@@ -73,12 +74,15 @@ namespace Barotrauma.Items.Components
if (PickingTime > 0.0f) 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 ((picker.PickingItem == null || picker.PickingItem == item) && PickingTime <= float.MaxValue)
{ {
#if SERVER #if SERVER
item.CreateServerEvent(this); item.CreateServerEvent(this);
#endif #endif
pickingCoroutine = CoroutineManager.StartCoroutine(WaitForPick(picker, PickingTime)); pickingCoroutine = CoroutineManager.StartCoroutine(WaitForPick(picker, abilityPickingTime.Value));
} }
return false; return false;
} }
@@ -523,7 +523,20 @@ namespace Barotrauma.Items.Components
ApplyStatusEffectsOnTarget(user, deltaTime, ActionType.OnUse, new ISerializableEntity[] { targetStructure }); ApplyStatusEffectsOnTarget(user, deltaTime, ActionType.OnUse, new ISerializableEntity[] { targetStructure });
FixStructureProjSpecific(user, deltaTime, targetStructure, sectionIndex); 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 //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) //(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)) (nextSectionLength > 0 && nextSectionLength < Structure.WallSectionSize * 0.3f))
{ {
//targetStructure.HighLightSection(sectionIndex + i); //targetStructure.HighLightSection(sectionIndex + i);
targetStructure.AddDamage(sectionIndex + i, -StructureFixAmount * degreeOfSuccess); targetStructure.AddDamage(sectionIndex + i, -structureFixAmount * degreeOfSuccess);
} }
} }
return true; return true;
@@ -606,7 +619,8 @@ namespace Barotrauma.Items.Components
levelResource.requiredItems.Any() && levelResource.requiredItems.Any() &&
levelResource.HasRequiredItems(user, addMessage: false)) 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 #if CLIENT
Character.Controlled?.UpdateHUDProgressBar( Character.Controlled?.UpdateHUDProgressBar(
this, this,
@@ -61,7 +61,7 @@ namespace Barotrauma.Items.Components
public int Capacity public int Capacity
{ {
get { return capacity; } get { return capacity; }
set { capacity = Math.Max(value, 1); } set { capacity = Math.Max(value, 0); }
} }
//how many items can be contained //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).")] [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; } 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).")] [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; } public Vector2 ItemInterval { get; set; }
@@ -329,11 +323,24 @@ namespace Barotrauma.Items.Components
{ {
return slotRestrictions.Any(s => s.MatchesItem(item)); 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) public bool CanBeContained(ItemPrefab itemPrefab)
{ {
return slotRestrictions.Any(s => s.MatchesItem(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<ISerializableEntity> targets = new List<ISerializableEntity>(); readonly List<ISerializableEntity> targets = new List<ISerializableEntity>();
public override void Update(float deltaTime, Camera cam) public override void Update(float deltaTime, Camera cam)
@@ -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) if (targetItem.AllowDeconstruct && allowRemove)
{ {
//drop all items that are inside the deconstructed item //drop all items that are inside the deconstructed item
@@ -24,12 +24,6 @@ namespace Barotrauma.Items.Components
private Character user; private Character user;
public float FabricationSpeedMultiplier
{
get;
set;
}
private ItemContainer inputContainer, outputContainer; private ItemContainer inputContainer, outputContainer;
[Serialize(1.0f, true)] [Serialize(1.0f, true)]
@@ -249,7 +243,6 @@ namespace Barotrauma.Items.Components
var availableIngredients = GetAvailableIngredients(); var availableIngredients = GetAvailableIngredients();
if (fabricatedItem == null || !CanBeFabricated(fabricatedItem, availableIngredients, user)) if (fabricatedItem == null || !CanBeFabricated(fabricatedItem, availableIngredients, user))
{ {
FabricationSpeedMultiplier = 1f;
CancelFabricating(); CancelFabricating();
return; return;
} }
@@ -286,8 +279,7 @@ namespace Barotrauma.Items.Components
if (powerConsumption <= 0) { Voltage = 1.0f; } if (powerConsumption <= 0) { Voltage = 1.0f; }
timeUntilReady -= deltaTime * Math.Min(Voltage, 1.0f) * FabricationSpeedMultiplier; timeUntilReady -= deltaTime * Math.Min(Voltage, 1.0f);
FabricationSpeedMultiplier = 1f;
UpdateRequiredTimeProjSpecific(); UpdateRequiredTimeProjSpecific();
@@ -328,13 +320,21 @@ namespace Barotrauma.Items.Components
var fabricationValueItem = new AbilityValueItem(fabricatedItem.Amount, fabricatedItem.TargetItem); 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)) foreach (Character character in Character.CharacterList.Where(c => c.TeamID == user.TeamID))
{ {
character.CheckTalents(AbilityEffectType.OnAllyItemFabricatedAmount, fabricationValueItem); character.CheckTalents(AbilityEffectType.OnAllyItemFabricatedAmount, fabricationValueItem);
} }
user.CheckTalents(AbilityEffectType.OnItemFabricatedAmount, 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; var tempUser = user;
@@ -343,12 +343,20 @@ namespace Barotrauma.Items.Components
if (i < amountFittingContainer) if (i < amountFittingContainer)
{ {
Entity.Spawner.AddToSpawnQueue(fabricatedItem.TargetItem, outputContainer.Inventory, fabricatedItem.TargetItem.Health * fabricatedItem.OutCondition, 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 else
{ {
Entity.Spawner.AddToSpawnQueue(fabricatedItem.TargetItem, item.Position, item.Submarine, fabricatedItem.TargetItem.Health * fabricatedItem.OutCondition, 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;
});
} }
} }
@@ -116,6 +116,8 @@ namespace Barotrauma.Items.Components
item.CurrentHull.WaterVolume += currFlow; item.CurrentHull.WaterVolume += currFlow;
if (item.CurrentHull.WaterVolume > item.CurrentHull.Volume) { item.CurrentHull.Pressure += 0.5f; } if (item.CurrentHull.WaterVolume > item.CurrentHull.Volume) { item.CurrentHull.Pressure += 0.5f; }
Voltage -= deltaTime;
} }
public void InfectBallast(string identifier, bool allowMultiplePerShip = false) public void InfectBallast(string identifier, bool allowMultiplePerShip = false)
@@ -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<StatType, float> statValues = new Dictionary<StatType, float>();
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;
}
}
}
@@ -35,6 +35,7 @@ namespace Barotrauma.Items.Components
public RemoteController(Item item, XElement element) public RemoteController(Item item, XElement element)
: base(item, element) : base(item, element)
{ {
DrawHudWhenEquipped = false;
} }
public override bool Select(Character character) public override bool Select(Character character)
@@ -11,7 +11,7 @@ namespace Barotrauma.Items.Components
{ {
partial class Repairable : ItemComponent, IServerSerializable, IClientSerializable partial class Repairable : ItemComponent, IServerSerializable, IClientSerializable
{ {
private string header; private readonly string header;
private float deteriorationTimer; private float deteriorationTimer;
private float deteriorateAlwaysResetTimer; private float deteriorateAlwaysResetTimer;
@@ -182,6 +182,10 @@ namespace Barotrauma.Items.Components
if (Rand.Range(0.0f, 0.5f) < RepairDegreeOfSuccess(character, requiredSkills)) { return true; } if (Rand.Range(0.0f, 0.5f) < RepairDegreeOfSuccess(character, requiredSkills)) { return true; }
ApplyStatusEffects(ActionType.OnFailure, 1.0f, character); ApplyStatusEffects(ActionType.OnFailure, 1.0f, character);
if (bestRepairItem != null && bestRepairItem.GetComponent<Holdable>() is Holdable h)
{
h.ApplyStatusEffects(ActionType.OnFailure, 1.0f, character);
}
return false; 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); 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 }); GameMain.Server?.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnFailure, this, character.ID });
if (bestRepairItem != null && bestRepairItem.GetComponent<Holdable>() is Holdable h)
{
GameMain.Server?.CreateEntityEvent(bestRepairItem, new object[] { NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnFailure, h, character.ID });
}
return false; return false;
} }
@@ -243,7 +252,7 @@ namespace Barotrauma.Items.Components
} }
return true; return true;
Item GetBestRepairItem(Character character) static Item GetBestRepairItem(Character character)
{ {
return character.HeldItems.OrderByDescending(i => i.Prefab.AddedRepairSpeedMultiplier).FirstOrDefault(); return character.HeldItems.OrderByDescending(i => i.Prefab.AddedRepairSpeedMultiplier).FirstOrDefault();
} }
@@ -386,6 +395,9 @@ namespace Barotrauma.Items.Components
float fixDuration = MathHelper.Lerp(FixDurationLowSkill, FixDurationHighSkill, successFactor); float fixDuration = MathHelper.Lerp(FixDurationLowSkill, FixDurationHighSkill, successFactor);
fixDuration /= 1 + CurrentFixer.GetStatValue(StatTypes.RepairSpeed) + currentRepairItem?.Prefab.AddedRepairSpeedMultiplier ?? 0f; 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) if (currentFixerAction == FixActions.Repair)
{ {
@@ -383,6 +383,10 @@ namespace Barotrauma.Items.Components
else else
{ {
float chargeDeltaTime = tryingToCharge ? deltaTime : -deltaTime; float chargeDeltaTime = tryingToCharge ? deltaTime : -deltaTime;
if (chargeDeltaTime > 0f && user != null)
{
chargeDeltaTime *= 1f + user.GetStatValue(StatTypes.TurretChargeSpeed);
}
currentChargeTime = Math.Clamp(currentChargeTime + chargeDeltaTime, 0f, MaxChargeTime); currentChargeTime = Math.Clamp(currentChargeTime + chargeDeltaTime, 0f, MaxChargeTime);
} }
tryingToCharge = false; tryingToCharge = false;
@@ -285,7 +285,7 @@ namespace Barotrauma.Items.Components
int i = 0; int i = 0;
foreach (XElement subElement in element.Elements()) foreach (XElement subElement in element.Elements())
{ {
switch (subElement.Name.ToString().ToLower()) switch (subElement.Name.ToString().ToLowerInvariant())
{ {
case "sprite": case "sprite":
if (subElement.Attribute("texture") == null) if (subElement.Attribute("texture") == null)
@@ -97,13 +97,15 @@ namespace Barotrauma
private Dictionary<string, Connection> connections; private Dictionary<string, Connection> connections;
private List<Repairable> repairables; private readonly List<Repairable> repairables;
private Queue<float> impactQueue = new Queue<float>(); private Quality qualityComponent;
private readonly Queue<float> impactQueue = new Queue<float>();
//a dictionary containing lists of the status effects in all the components of the item //a dictionary containing lists of the status effects in all the components of the item
private bool[] hasStatusEffectsOfType; private readonly bool[] hasStatusEffectsOfType;
private Dictionary<ActionType, List<StatusEffect>> statusEffectLists; private readonly Dictionary<ActionType, List<StatusEffect>> statusEffectLists;
public Dictionary<string, SerializableProperty> SerializableProperties { get; protected set; } public Dictionary<string, SerializableProperty> SerializableProperties { get; protected set; }
@@ -447,7 +449,7 @@ namespace Barotrauma
} }
public bool IsFullCondition => MathUtils.NearlyEqual(Condition, MaxCondition); 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); public float ConditionPercentage => MathUtils.Percentage(Condition, MaxCondition);
private float offsetOnSelectedMultiplier = 1.0f; private float offsetOnSelectedMultiplier = 1.0f;
@@ -465,10 +467,16 @@ namespace Barotrauma
public float HealthMultiplier public float HealthMultiplier
{ {
get => healthMultiplier; get => healthMultiplier;
set set { healthMultiplier = MathHelper.Clamp(value, 0.0f, float.PositiveInfinity); }
{ }
healthMultiplier = value;
} 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, //the default value should be Prefab.Health, but because we can't use it in the attribute,
@@ -618,6 +626,21 @@ namespace Barotrauma
get { return Prefab.UseInHealthInterface; } get { return Prefab.UseInHealthInterface; }
} }
public int Quality
{
get
{
return qualityComponent?.QualityLevel ?? 0;
}
set
{
if (qualityComponent != null)
{
qualityComponent.QualityLevel = value;
}
}
}
public bool InWater public bool InWater
{ {
get get
@@ -933,6 +956,8 @@ namespace Barotrauma
ownInventory = itemContainer.Inventory; ownInventory = itemContainer.Inventory;
} }
qualityComponent = GetComponent<Quality>();
InitProjSpecific(); InitProjSpecific();
if (callOnItemLoaded) if (callOnItemLoaded)
@@ -1123,6 +1148,11 @@ namespace Barotrauma
return components.Where(c => c is T).Cast<T>(); return components.Where(c => c is T).Cast<T>();
} }
public float GetQualityModifier(Quality.StatType statType)
{
return GetComponent<Quality>()?.GetValue(statType) ?? 0.0f;
}
public void RemoveContained(Item contained) public void RemoveContained(Item contained)
{ {
ownInventory?.RemoveItem(contained); ownInventory?.RemoveItem(contained);
@@ -47,14 +47,14 @@ namespace Barotrauma
{ {
if (ItemOwnsSelf(item)) { return false; } if (ItemOwnsSelf(item)) { return false; }
if (i < 0 || i >= slots.Length) { 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); return item != null && slots[i].CanBePut(item, ignoreCondition) && slots[i].ItemCount < container.GetMaxStackSize(i);
} }
public override bool CanBePutInSlot(ItemPrefab itemPrefab, int i, float? condition) public override bool CanBePutInSlot(ItemPrefab itemPrefab, int i, float? condition)
{ {
if (i < 0 || i >= slots.Length) { return false; } 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); 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 (itemPrefab == null) { return 0; }
if (i < 0 || i >= slots.Length) { 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); return slots[i].HowManyCanBePut(itemPrefab, maxStackSize: Math.Min(itemPrefab.MaxStackSize, container.GetMaxStackSize(i)), condition);
} }
@@ -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.")] [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; } 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 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 //if false, trigger areas define areas that can be used to highlight the item
[Serialize(true, false)] [Serialize(true, false)]
@@ -1164,6 +1167,8 @@ namespace Barotrauma
DefaultPrice ??= new PriceInfo(GetMinPrice() ?? 0, false); DefaultPrice ??= new PriceInfo(GetMinPrice() ?? 0, false);
} }
HideConditionInTooltip = element.GetAttributeBool("hideconditionintooltip", HideConditionBar);
//backwards compatibility //backwards compatibility
if (categoryStr.Equals("Thalamus", StringComparison.OrdinalIgnoreCase)) if (categoryStr.Equals("Thalamus", StringComparison.OrdinalIgnoreCase))
{ {
@@ -128,6 +128,11 @@ namespace Barotrauma
} }
float displayRange = Attack.Range; 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; Vector2 cameraPos = GameMain.GameScreen.Cam.Position;
float cameraDist = Vector2.Distance(cameraPos, worldPosition) / 2.0f; float cameraDist = Vector2.Distance(cameraPos, worldPosition) / 2.0f;
@@ -142,7 +147,7 @@ namespace Barotrauma
if (displayRange < 0.1f) { return; } 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); 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 dist = Vector2.Distance(item.WorldPosition, worldPosition);
float itemRadius = item.body == null ? 0.0f : item.body.GetMaxExtent(); float itemRadius = item.body == null ? 0.0f : item.body.GetMaxExtent();
dist = Math.Max(0.0f, dist - ConvertUnits.ToDisplayUnits(itemRadius)); 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 //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) //(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) 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; float damageAmount = Attack.GetItemDamage(1.0f) * item.Prefab.ExplosionDamageMultiplier;
Vector2 explosionPos = worldPosition; Vector2 explosionPos = worldPosition;
@@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Xml; using System.Xml;
@@ -8,6 +9,7 @@ using System.Xml.Linq;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using File = Barotrauma.IO.File; using File = Barotrauma.IO.File;
using FileStream = Barotrauma.IO.FileStream; using FileStream = Barotrauma.IO.FileStream;
using Path = Barotrauma.IO.Path;
namespace Barotrauma namespace Barotrauma
{ {
@@ -18,11 +20,12 @@ namespace Barotrauma
public static readonly XmlReaderSettings ReaderSettings = new XmlReaderSettings public static readonly XmlReaderSettings ReaderSettings = new XmlReaderSettings
{ {
DtdProcessing = DtdProcessing.Prohibit, DtdProcessing = DtdProcessing.Prohibit,
XmlResolver = null XmlResolver = null,
IgnoreWhitespace = true,
}; };
public static XmlReader CreateReader(System.IO.Stream stream) public static XmlReader CreateReader(System.IO.Stream stream, string baseUri = "")
=> XmlReader.Create(stream, ReaderSettings); => XmlReader.Create(stream, ReaderSettings, baseUri);
public static XDocument TryLoadXml(System.IO.Stream stream) public static XDocument TryLoadXml(System.IO.Stream stream)
{ {
@@ -52,8 +55,8 @@ namespace Barotrauma
{ {
ToolBox.IsProperFilenameCase(filePath); ToolBox.IsProperFilenameCase(filePath);
using FileStream stream = File.Open(filePath, System.IO.FileMode.Open, System.IO.FileAccess.Read); 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); doc = XDocument.Load(reader, LoadOptions.SetBaseUri);
} }
catch (Exception e) catch (Exception e)
{ {
@@ -79,7 +82,7 @@ namespace Barotrauma
try try
{ {
using FileStream stream = File.Open(filePath, System.IO.FileMode.Open, System.IO.FileAccess.Read); 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); doc = XDocument.Load(reader);
} }
catch catch
@@ -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. // 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 TargetGrandParent;
public readonly bool TargetContainedItem;
// Remove this after refactoring // Remove this after refactoring
public static bool IsValid(XAttribute attribute) public static bool IsValid(XAttribute attribute)
{ {
@@ -112,6 +114,7 @@ namespace Barotrauma
TargetContainer = attribute.Parent.GetAttributeBool("targetcontainer", false); TargetContainer = attribute.Parent.GetAttributeBool("targetcontainer", false);
TargetSelf = attribute.Parent.GetAttributeBool("targetself", false); TargetSelf = attribute.Parent.GetAttributeBool("targetself", false);
TargetGrandParent = attribute.Parent.GetAttributeBool("targetgrandparent", false); TargetGrandParent = attribute.Parent.GetAttributeBool("targetgrandparent", false);
TargetContainedItem = attribute.Parent.GetAttributeBool("targetcontaineditem", false);
if (!Enum.TryParse(AttributeName, true, out Type)) if (!Enum.TryParse(AttributeName, true, out Type))
{ {
@@ -171,6 +174,22 @@ namespace Barotrauma
public bool Matches(ISerializableEntity target) 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) switch (Type)
{ {
case ConditionType.PropertyValue: case ConditionType.PropertyValue:
@@ -315,7 +315,11 @@ namespace Barotrauma.IO
return System.IO.File.GetLastWriteTime(path); 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) switch (mode)
{ {
@@ -331,10 +335,12 @@ namespace Barotrauma.IO
} }
break; break;
} }
return new FileStream(path, System.IO.File.Open(path, mode, access =
!Validation.CanWrite(path, false) ? !Validation.CanWrite(path, false) ?
System.IO.FileAccess.Read : 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) public static FileStream OpenRead(string path)
+29
View File
@@ -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 v0.1500.2.0
--------------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------------
@@ -11,7 +11,7 @@
autorestart="false" autorestart="false"
LevelDifficulty="20" LevelDifficulty="20"
AllowedRandomMissionTypes="Random,Salvage,Monster,Cargo,Combat" 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="" ServerMessage=""
tickrate="20" tickrate="20"
randomizeseed="True" randomizeseed="True"