Build 0.20.4.0

This commit is contained in:
Markus Isberg
2022-11-11 17:57:23 +02:00
parent edaf4b09fe
commit 54712b5dc9
201 changed files with 7618 additions and 2020 deletions
@@ -47,8 +47,8 @@ namespace Barotrauma
GUI.DrawRectangle(spriteBatch, wallTargetPos - new Vector2(10.0f, 10.0f), new Vector2(20.0f, 20.0f), Color.Orange, false); GUI.DrawRectangle(spriteBatch, wallTargetPos - new Vector2(10.0f, 10.0f), new Vector2(20.0f, 20.0f), Color.Orange, false);
GUI.DrawLine(spriteBatch, pos, wallTargetPos, Color.Orange * 0.5f, 0, 5); GUI.DrawLine(spriteBatch, pos, wallTargetPos, Color.Orange * 0.5f, 0, 5);
} }
GUI.DrawString(spriteBatch, pos - Vector2.UnitY * 60.0f, $"{SelectedAiTarget.Entity} ({GetTargetMemory(SelectedAiTarget, false)?.Priority.FormatZeroDecimal()})", GUIStyle.Red, Color.Black); GUI.DrawString(spriteBatch, pos - Vector2.UnitY * 60.0f, $"{SelectedAiTarget.Entity}", GUIStyle.Red, Color.Black);
GUI.DrawString(spriteBatch, pos - Vector2.UnitY * 40.0f, $"({targetValue.FormatZeroDecimal()})", GUIStyle.Red, Color.Black); GUI.DrawString(spriteBatch, pos - Vector2.UnitY * 40.0f, $"{targetValue.FormatZeroDecimal()} (M: {SelectedTargetMemory?.Priority.FormatZeroDecimal()}, P: {SelectedTargetingParams?.Priority.FormatZeroDecimal()})", GUIStyle.Red, Color.Black);
} }
/*GUIStyle.Font.DrawString(spriteBatch, targetValue.ToString(), pos - Vector2.UnitY * 80.0f, GUIStyle.Red); /*GUIStyle.Font.DrawString(spriteBatch, targetValue.ToString(), pos - Vector2.UnitY * 80.0f, GUIStyle.Red);
@@ -982,6 +982,23 @@ namespace Barotrauma
if (IsDead) { return; } if (IsDead) { return; }
var healthBarMode = GameMain.NetworkMember?.ServerSettings.ShowEnemyHealthBars ?? GameSettings.CurrentConfig.ShowEnemyHealthBars;
if (healthBarMode != EnemyHealthBarMode.ShowAll)
{
if (Controlled == null)
{
if (!IsOnPlayerTeam) { return; }
}
else
{
if (!HumanAIController.IsFriendly(Controlled, this) ||
(AIController is HumanAIController humanAi && humanAi.ObjectiveManager.CurrentObjective is AIObjectiveCombat combatObjective && HumanAIController.IsFriendly(Controlled, combatObjective.Enemy)))
{
return;
}
}
}
if (CharacterHealth.DisplayedVitality < MaxVitality * 0.98f && hudInfoVisible) if (CharacterHealth.DisplayedVitality < MaxVitality * 0.98f && hudInfoVisible)
{ {
hudInfoAlpha = Math.Max(hudInfoAlpha, Math.Min(CharacterHealth.DamageOverlayTimer, 1.0f)); hudInfoAlpha = Math.Max(hudInfoAlpha, Math.Min(CharacterHealth.DamageOverlayTimer, 1.0f));
@@ -1,6 +1,5 @@
using Barotrauma.Extensions; using Barotrauma.Extensions;
using Barotrauma.Items.Components; using Barotrauma.Items.Components;
using Barotrauma.Tutorials;
using FarseerPhysics; using FarseerPhysics;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
@@ -114,7 +113,7 @@ namespace Barotrauma
return return
character?.Inventory != null && character?.Inventory != null &&
!character.Removed && !character.IsKnockedDown && !character.Removed && !character.IsKnockedDown &&
(controller?.User != character || !controller.HideHUD) && (controller?.User != character || !controller.HideHUD || Screen.Selected.IsEditor) &&
!IsCampaignInterfaceOpen && !IsCampaignInterfaceOpen &&
!ConversationAction.FadeScreenToBlack; !ConversationAction.FadeScreenToBlack;
} }
@@ -548,7 +547,7 @@ namespace Barotrauma
if (CharacterHealth.OpenHealthWindow == character.SelectedCharacter.CharacterHealth) if (CharacterHealth.OpenHealthWindow == character.SelectedCharacter.CharacterHealth)
{ {
character.SelectedCharacter.CharacterHealth.Alignment = Alignment.Left; character.SelectedCharacter.CharacterHealth.Alignment = Alignment.Left;
character.SelectedCharacter.CharacterHealth.DrawStatusHUD(spriteBatch); //character.SelectedCharacter.CharacterHealth.DrawStatusHUD(spriteBatch);
} }
} }
else if (character.Inventory != null) else if (character.Inventory != null)
@@ -644,6 +643,12 @@ namespace Barotrauma
{ {
if (character == null || character.IsDead || character.Removed) { return; } if (character == null || character.IsDead || character.Removed) { return; }
var healthBarMode = GameMain.NetworkMember?.ServerSettings.ShowEnemyHealthBars ?? GameSettings.CurrentConfig.ShowEnemyHealthBars;
if (healthBarMode == EnemyHealthBarMode.HideAll)
{
return;
}
var existingBar = bossHealthBars.Find(b => b.Character == character); var existingBar = bossHealthBars.Find(b => b.Character == character);
if (existingBar != null) if (existingBar != null)
{ {
@@ -669,6 +674,8 @@ namespace Barotrauma
public static void UpdateBossHealthBars(float deltaTime) public static void UpdateBossHealthBars(float deltaTime)
{ {
var healthBarMode = GameMain.NetworkMember?.ServerSettings.ShowEnemyHealthBars ?? GameSettings.CurrentConfig.ShowEnemyHealthBars;
for (int i = 0; i < bossHealthBars.Count; i++) for (int i = 0; i < bossHealthBars.Count; i++)
{ {
var bossHealthBar = bossHealthBars[i]; var bossHealthBar = bossHealthBars[i];
@@ -710,7 +717,7 @@ namespace Barotrauma
for (int i = bossHealthBars.Count - 1; i >= 0 ; i--) for (int i = bossHealthBars.Count - 1; i >= 0 ; i--)
{ {
var bossHealthBar = bossHealthBars[i]; var bossHealthBar = bossHealthBars[i];
if (bossHealthBar.FadeTimer <= 0) if (bossHealthBar.FadeTimer <= 0 || healthBarMode == EnemyHealthBarMode.HideAll)
{ {
bossHealthBar.SideContainer.Parent?.RemoveChild(bossHealthBar.SideContainer); bossHealthBar.SideContainer.Parent?.RemoveChild(bossHealthBar.SideContainer);
bossHealthBar.TopContainer.Parent?.RemoveChild(bossHealthBar.TopContainer); bossHealthBar.TopContainer.Parent?.RemoveChild(bossHealthBar.TopContainer);
@@ -78,7 +78,7 @@ namespace Barotrauma
Color? nameColor = null; Color? nameColor = null;
if (Job != null) { nameColor = Job.Prefab.UIColor; } if (Job != null) { nameColor = Job.Prefab.UIColor; }
GUITextBlock characterNameBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), headerTextArea.RectTransform), ToolBox.LimitString(Name, GUIStyle.Font, headerTextArea.Rect.Width), textColor: nameColor, font: GUIStyle.Font) GUITextBlock characterNameBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.25f), headerTextArea.RectTransform), ToolBox.LimitString(Name, GUIStyle.Font, headerTextArea.Rect.Width), textColor: nameColor, font: GUIStyle.Font)
{ {
ForceUpperCase = ForceUpperCase.Yes, ForceUpperCase = ForceUpperCase.Yes,
Padding = Vector4.Zero Padding = Vector4.Zero
@@ -93,7 +93,7 @@ namespace Barotrauma
if (Job != null) if (Job != null)
{ {
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), headerTextArea.RectTransform), Job.Name, textColor: Job.Prefab.UIColor, font: font) new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.25f), headerTextArea.RectTransform), Job.Name, textColor: Job.Prefab.UIColor, font: font)
{ {
Padding = Vector4.Zero Padding = Vector4.Zero
}; };
@@ -101,7 +101,7 @@ namespace Barotrauma
if (PersonalityTrait != null) if (PersonalityTrait != null)
{ {
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), headerTextArea.RectTransform), new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.25f), headerTextArea.RectTransform),
TextManager.AddPunctuation(':', TextManager.Get("PersonalityTrait"), PersonalityTrait.DisplayName), TextManager.AddPunctuation(':', TextManager.Get("PersonalityTrait"), PersonalityTrait.DisplayName),
font: font) font: font)
{ {
@@ -109,7 +109,23 @@ namespace Barotrauma
}; };
} }
if (Job != null && (Character == null || !Character.IsDead)) GUIButton manageTalentButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.25f), headerTextArea.RectTransform),
text: TextManager.Get("ClientPermission.ManageBotTalents"), style: "GUIButtonSmall")
{
Enabled = false,
UserData = TalentMenu.ManageBotTalentsButtonUserData,
TextBlock =
{
AutoScaleHorizontal = true
}
};
if (TalentMenu.CanManageTalents(this))
{
manageTalentButton.Enabled = true;
}
if (Job != null && Character is not { IsDead: true })
{ {
var skillsArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.63f), paddedFrame.RectTransform, Anchor.BottomCenter, Pivot.BottomCenter)) var skillsArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.63f), paddedFrame.RectTransform, Anchor.BottomCenter, Pivot.BottomCenter))
{ {
@@ -144,7 +160,7 @@ namespace Barotrauma
} }
} }
} }
else if (Character != null && Character.IsDead) else if (Character is { IsDead: true })
{ {
var deadArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.63f), paddedFrame.RectTransform, Anchor.BottomCenter, Pivot.BottomCenter)) var deadArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.63f), paddedFrame.RectTransform, Anchor.BottomCenter, Pivot.BottomCenter))
{ {
@@ -89,11 +89,23 @@ namespace Barotrauma
private int selectedLimbIndex = -1; private int selectedLimbIndex = -1;
private LimbHealth currentDisplayedLimb; private LimbHealth currentDisplayedLimb;
/// <summary>
/// Container for the icons above the health bar
/// </summary>
private GUIComponent afflictionIconContainer;
private GUIButton showHiddenAfflictionsButton;
/// <summary>
/// Container for passive afflictions that have been hidden from afflictionIconContainer
/// </summary>
private GUIComponent hiddenAfflictionIconContainer;
private GUIProgressBar healthWindowHealthBar; private GUIProgressBar healthWindowHealthBar;
private GUIProgressBar healthWindowHealthBarShadow; private GUIProgressBar healthWindowHealthBarShadow;
private GUITextBlock characterName; private GUITextBlock characterName;
private GUIListBox afflictionIconContainer; private GUIListBox afflictionIconList;
private GUILayoutGroup treatmentLayout; private GUILayoutGroup treatmentLayout;
private GUIListBox recommendedTreatmentContainer; private GUIListBox recommendedTreatmentContainer;
@@ -331,7 +343,7 @@ namespace Barotrauma
deadIndicator.AutoScaleHorizontal = true; deadIndicator.AutoScaleHorizontal = true;
} }
afflictionIconContainer = new GUIListBox(new RectTransform(new Vector2(0.25f, 1.0f), characterIndicatorArea.RectTransform), style: null); afflictionIconList = new GUIListBox(new RectTransform(new Vector2(0.25f, 1.0f), characterIndicatorArea.RectTransform), style: null);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.1f), healthWindowVerticalLayout.RectTransform), new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.1f), healthWindowVerticalLayout.RectTransform),
TextManager.Get("SuitableTreatments"), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.BottomCenter); TextManager.Get("SuitableTreatments"), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.BottomCenter);
@@ -379,6 +391,25 @@ namespace Barotrauma
Enabled = true Enabled = true
}; };
afflictionIconContainer = new GUILayoutGroup(
HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.HealthBarAfflictionArea, GUI.Canvas),
isHorizontal: true, childAnchor: Anchor.CenterRight)
{
AbsoluteSpacing = GUI.IntScale(5)
};
showHiddenAfflictionsButton = new GUIButton(new RectTransform(new Point(afflictionIconContainer.Rect.Height), afflictionIconContainer.RectTransform), style: "GUIButtonCircular")
{
CanBeFocused = false
};
hiddenAfflictionIconContainer = new GUILayoutGroup(
HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.HealthBarAfflictionArea, GUI.Canvas),
isHorizontal: true, childAnchor: Anchor.CenterRight)
{
AbsoluteSpacing = GUI.IntScale(5)
};
UpdateAlignment(); UpdateAlignment();
SuicideButton = new GUIButton(new RectTransform(new Vector2(0.1f, 0.02f), GUI.Canvas, Anchor.TopCenter) SuicideButton = new GUIButton(new RectTransform(new Vector2(0.1f, 0.02f), GUI.Canvas, Anchor.TopCenter)
@@ -596,7 +627,7 @@ namespace Barotrauma
public void UpdateHUD(float deltaTime) public void UpdateHUD(float deltaTime)
{ {
if (GUI.DisableHUD) return; if (GUI.DisableHUD) { return; }
if (openHealthWindow != null) if (openHealthWindow != null)
{ {
if (openHealthWindow != Character.Controlled?.CharacterHealth && openHealthWindow != Character.Controlled?.SelectedCharacter?.CharacterHealth) if (openHealthWindow != Character.Controlled?.CharacterHealth && openHealthWindow != Character.Controlled?.SelectedCharacter?.CharacterHealth)
@@ -700,6 +731,8 @@ namespace Barotrauma
distortTimer = 0.0f; distortTimer = 0.0f;
} }
UpdateStatusHUD(deltaTime);
if (PlayerInput.KeyHit(InputType.Health) && GUI.KeyboardDispatcher.Subscriber == null && if (PlayerInput.KeyHit(InputType.Health) && GUI.KeyboardDispatcher.Subscriber == null &&
Character.Controlled.AllowInput && !toggledThisFrame) Character.Controlled.AllowInput && !toggledThisFrame)
{ {
@@ -726,9 +759,9 @@ namespace Barotrauma
OpenHealthWindow = null; OpenHealthWindow = null;
} }
foreach (GUIComponent afflictionIcon in afflictionIconContainer.Content.Children) foreach (GUIComponent afflictionIcon in afflictionIconList.Content.Children)
{ {
if (!(afflictionIcon.UserData is Affliction affliction)) { continue; } if (afflictionIcon.UserData is not Affliction affliction) { continue; }
if (affliction.AppliedAsFailedTreatmentTime > Timing.TotalTime - 1.0 && afflictionIcon.FlashTimer <= 0.0f) if (affliction.AppliedAsFailedTreatmentTime > Timing.TotalTime - 1.0 && afflictionIcon.FlashTimer <= 0.0f)
{ {
afflictionIcon.Flash(GUIStyle.Red); afflictionIcon.Flash(GUIStyle.Red);
@@ -900,7 +933,7 @@ namespace Barotrauma
healthBarHolder.CanBeFocused = healthBar.CanBeFocused = healthBarShadow.CanBeFocused = !Character.ShouldLockHud(); healthBarHolder.CanBeFocused = healthBar.CanBeFocused = healthBarShadow.CanBeFocused = !Character.ShouldLockHud();
if (Character.AllowInput && UseHealthWindow && !Character.DisableHealthWindow && healthBar.Enabled && healthBar.CanBeFocused && if (Character.AllowInput && UseHealthWindow && !Character.DisableHealthWindow && healthBar.Enabled && healthBar.CanBeFocused &&
(GUI.IsMouseOn(healthBar) || highlightedAfflictionIcon != null) && Inventory.SelectedSlot == null) (GUI.IsMouseOn(healthBar) || GUI.MouseOn?.UserData is AfflictionPrefab) && Inventory.SelectedSlot == null)
{ {
healthBar.State = GUIComponent.ComponentState.Hover; healthBar.State = GUIComponent.ComponentState.Hover;
if (PlayerInput.PrimaryMouseButtonClicked()) if (PlayerInput.PrimaryMouseButtonClicked())
@@ -961,6 +994,11 @@ namespace Barotrauma
else if (Character.Controlled == Character && !CharacterHUD.IsCampaignInterfaceOpen) else if (Character.Controlled == Character && !CharacterHUD.IsCampaignInterfaceOpen)
{ {
healthBarHolder.AddToGUIUpdateList(); healthBarHolder.AddToGUIUpdateList();
afflictionIconContainer.AddToGUIUpdateList();
if (hiddenAfflictionIconContainer.Visible)
{
hiddenAfflictionIconContainer.AddToGUIUpdateList();
}
} }
if (SuicideButton.Visible && Character == Character.Controlled) if (SuicideButton.Visible && Character == Character.Controlled)
{ {
@@ -989,7 +1027,7 @@ namespace Barotrauma
if (affliction.Prefab.AfflictionOverlay != null) if (affliction.Prefab.AfflictionOverlay != null)
{ {
Sprite ScreenAfflictionOverlay = affliction.Prefab.AfflictionOverlay; Sprite ScreenAfflictionOverlay = affliction.Prefab.AfflictionOverlay;
ScreenAfflictionOverlay?.Draw(spriteBatch, Vector2.Zero, Color.White * (affliction.GetAfflictionOverlayMultiplier()), Vector2.Zero, 0.0f, ScreenAfflictionOverlay?.Draw(spriteBatch, Vector2.Zero, Color.White * affliction.GetAfflictionOverlayMultiplier(), Vector2.Zero, 0.0f,
new Vector2(GameMain.GraphicsWidth / DamageOverlay.size.X, GameMain.GraphicsHeight / DamageOverlay.size.Y)); new Vector2(GameMain.GraphicsWidth / DamageOverlay.size.X, GameMain.GraphicsHeight / DamageOverlay.size.Y));
} }
} }
@@ -1021,94 +1059,133 @@ namespace Barotrauma
// If manning a turret the portrait doesn't get rendered so we push the health bar to remove the empty gap // If manning a turret the portrait doesn't get rendered so we push the health bar to remove the empty gap
healthBarHolder.RectTransform.ScreenSpaceOffset = Character.ShouldLockHud() ? new Point(0, HUDLayoutSettings.PortraitArea.Height) : Point.Zero; healthBarHolder.RectTransform.ScreenSpaceOffset = Character.ShouldLockHud() ? new Point(0, HUDLayoutSettings.PortraitArea.Height) : Point.Zero;
} }
DrawStatusHUD(spriteBatch);
} }
private (Affliction Affliction, LocalizedString NameToolTip)? highlightedAfflictionIcon = null; //private (Affliction Affliction, LocalizedString NameToolTip)? highlightedAfflictionIcon = null;
public void DrawStatusHUD(SpriteBatch spriteBatch)
private readonly List<Affliction> statusIcons = new List<Affliction>();
private readonly Dictionary<AfflictionPrefab, float> statusIconVisibleTime = new Dictionary<AfflictionPrefab, float>();
private float hideStatusIconDelay = 5.0f;
public void UpdateStatusHUD(float deltaTime)
{ {
highlightedAfflictionIcon = null;
//Rectangle interactArea = healthBar.Rect;
if (Character.Controlled?.SelectedCharacter == null && openHealthWindow == null) if (Character.Controlled?.SelectedCharacter == null && openHealthWindow == null)
{ {
var statusIcons = new List<(Affliction Affliction, LocalizedString Warning)>(); statusIcons.Clear();
if (Character.InPressure) if (Character.InPressure)
{ {
statusIcons.Add((pressureAffliction, TextManager.Get("PressureHUDWarning"))); statusIcons.Add(pressureAffliction);
} }
if (Character.CurrentHull != null && Character.OxygenAvailable < LowOxygenThreshold && oxygenLowAffliction.Strength < oxygenLowAffliction.Prefab.ShowIconThreshold) if (Character.CurrentHull != null && Character.OxygenAvailable < LowOxygenThreshold && oxygenLowAffliction.Strength < oxygenLowAffliction.Prefab.ShowIconThreshold)
{ {
statusIcons.Add((oxygenLowAffliction, TextManager.Get("OxygenHUDWarning"))); statusIcons.Add(oxygenLowAffliction);
} }
foreach (Affliction affliction in currentDisplayedAfflictions) foreach (Affliction affliction in currentDisplayedAfflictions)
{ {
statusIcons.Add((affliction, affliction.Prefab.Name)); statusIcons.Add(affliction);
} }
Vector2 highlightedIconPos = Vector2.Zero; int spacing = GUI.IntScale(10);
Rectangle afflictionArea = HUDLayoutSettings.AfflictionAreaLeft;
// Push the icons down since the portrait doesn't get rendered
if (Character.ShouldLockHud()) if (Character.ShouldLockHud())
{ {
afflictionArea.Y += HUDLayoutSettings.PortraitArea.Height; // Push the icons down since the portrait doesn't get rendered
afflictionIconContainer.RectTransform.ScreenSpaceOffset = new Point(0, HUDLayoutSettings.PortraitArea.Height);
hiddenAfflictionIconContainer.RectTransform.ScreenSpaceOffset = new Point(0, -hiddenAfflictionIconContainer.Rect.Height - spacing + HUDLayoutSettings.PortraitArea.Height);
} }
else
{
afflictionIconContainer.RectTransform.ScreenSpaceOffset = new Point(0, 0);
hiddenAfflictionIconContainer.RectTransform.ScreenSpaceOffset = new Point(0, -hiddenAfflictionIconContainer.Rect.Height - spacing);
}
//remove affliction icons for afflictions that no longer exist
bool horizontal = afflictionArea.Width > afflictionArea.Height; RemoveNonExistentIcons(afflictionIconContainer);
int iconSize = horizontal ? afflictionArea.Height : afflictionArea.Width; RemoveNonExistentIcons(hiddenAfflictionIconContainer);
void RemoveNonExistentIcons(GUIComponent container)
Point pos = new Point(afflictionArea.Right - iconSize, afflictionArea.Top); {
for (int i = container.CountChildren - 1; i >= 0; i--)
{
var child = container.GetChild(i);
if (child.UserData is not AfflictionPrefab afflictionPrefab) { continue; }
if (!statusIcons.Any(s => s.Prefab == afflictionPrefab))
{
container.RemoveChild(child);
statusIconVisibleTime.Remove(afflictionPrefab);
}
}
}
foreach (var statusIcon in statusIcons) foreach (var statusIcon in statusIcons)
{ {
Affliction affliction = statusIcon.Affliction; Affliction affliction = statusIcon;
AfflictionPrefab afflictionPrefab = affliction.Prefab; AfflictionPrefab afflictionPrefab = affliction.Prefab;
Rectangle afflictionIconRect = new Rectangle(pos, new Point(iconSize)); if (!statusIconVisibleTime.ContainsKey(afflictionPrefab)) { statusIconVisibleTime.Add(afflictionPrefab, 0.0f); }
if (afflictionIconRect.Contains(PlayerInput.MousePosition) && !Character.ShouldLockHud() && GUI.MouseOn == null) statusIconVisibleTime[afflictionPrefab] += deltaTime;
var matchingIcon =
afflictionIconContainer.GetChildByUserData(afflictionPrefab) ??
hiddenAfflictionIconContainer.GetChildByUserData(afflictionPrefab);
if (matchingIcon == null)
{ {
highlightedAfflictionIcon = statusIcon; matchingIcon = new GUIButton(new RectTransform(new Point(afflictionIconContainer.Rect.Height), afflictionIconContainer.RectTransform), style: null)
highlightedIconPos = afflictionIconRect.Location.ToVector2(); {
UserData = afflictionPrefab,
ToolTip = affliction.Prefab.Name,
CanBeSelected = false
};
if (affliction == pressureAffliction)
{
matchingIcon.ToolTip = TextManager.Get("PressureHUDWarning");
}
else if (affliction == pressureAffliction)
{
matchingIcon.ToolTip = TextManager.Get("OxygenHUDWarning");
}
new GUIImage(new RectTransform(Vector2.One, matchingIcon.RectTransform, Anchor.BottomCenter), afflictionPrefab.Icon, scaleToFit: true)
{
CanBeFocused = false
};
} }
if (afflictionPrefab.HideIconAfterDelay && statusIconVisibleTime[afflictionPrefab] > hideStatusIconDelay)
if (affliction.DamagePerSecond > 1.0f)
{ {
Rectangle glowRect = afflictionIconRect; matchingIcon.RectTransform.Parent = hiddenAfflictionIconContainer.RectTransform;
glowRect.Inflate((int)(20 * GUI.Scale), (int)(20 * GUI.Scale));
var glow = GUIStyle.GetComponentStyle("OuterGlowCircular");
glow.Sprites[GUIComponent.ComponentState.None][0].Draw(
spriteBatch, glowRect,
GUIStyle.Red * (float)((Math.Sin(affliction.DamagePerSecondTimer * MathHelper.TwoPi - MathHelper.PiOver2) + 1.0f) * 0.5f));
} }
var image = matchingIcon.GetChild<GUIImage>();
image.Color = GetAfflictionIconColor(afflictionPrefab, affliction);
image.HoverColor = Color.Lerp(image.Color, Color.White, 0.5f);
float alphaMultiplier = highlightedAfflictionIcon == statusIcon ? 1f : 0.8f; if (affliction.DamagePerSecond > 1.0f && matchingIcon.FlashTimer <= 0.0f)
{
afflictionPrefab.Icon?.Draw(spriteBatch, matchingIcon.Flash(useCircularFlash: true, flashDuration: 1.5f, flashRectInflate: Vector2.One * 15.0f * GUI.Scale);
pos.ToVector2(), image.Pulsate(Vector2.One, Vector2.One * 1.2f, 1.0f);
/*highlightedIcon == statusIcon ? statusIcon.First.Prefab.IconColor : statusIcon.First.Prefab.IconColor * 0.8f,*/ // OLD IMPLEMENTATION }
GetAfflictionIconColor(afflictionPrefab, affliction) * alphaMultiplier,
rotate: 0,
scale: iconSize / afflictionPrefab.Icon.size.X);
if (horizontal)
pos.X -= iconSize + (int)(5 * GUI.Scale);
else
pos.Y += iconSize + (int)(5 * GUI.Scale);
} }
if (highlightedAfflictionIcon != null) afflictionIconContainer.RectTransform.SortChildren((r1, r2) =>
{ {
LocalizedString nameTooltip = highlightedAfflictionIcon.Value.NameToolTip; if (r1.GUIComponent.UserData is not AfflictionPrefab prefab1) { return -1; }
Vector2 offset = GUIStyle.Font.MeasureString(nameTooltip); if (r2.GUIComponent.UserData is not AfflictionPrefab prefab2) { return 1; }
var index1 = statusIcons.IndexOf(s => s.Prefab == prefab1);
var index2 = statusIcons.IndexOf(s => s.Prefab == prefab2);
return index1.CompareTo(index2);
});
(afflictionIconContainer as GUILayoutGroup).NeedsToRecalculate = true;
GUI.DrawString(spriteBatch, Rectangle hiddenAfflictionHoverArea = showHiddenAfflictionsButton.Rect;
alignment == Alignment.Left ? highlightedIconPos + offset : highlightedIconPos - offset, foreach (GUIComponent child in hiddenAfflictionIconContainer.Children)
nameTooltip, {
Color.White * 0.8f, Color.Black * 0.5f); hiddenAfflictionHoverArea = Rectangle.Union(hiddenAfflictionHoverArea, child.Rect);
} }
hiddenAfflictionIconContainer.Visible =
showHiddenAfflictionsButton.Rect.Contains(PlayerInput.MousePosition) ||
(hiddenAfflictionIconContainer.Visible && hiddenAfflictionHoverArea.Contains(PlayerInput.MousePosition));
showHiddenAfflictionsButton.Visible = hiddenAfflictionIconContainer.CountChildren > 0;
showHiddenAfflictionsButton.IgnoreLayoutGroups = !showHiddenAfflictionsButton.Visible;
showHiddenAfflictionsButton.Text = $"+{hiddenAfflictionIconContainer.CountChildren}";
if (Vitality > 0.0f) if (Vitality > 0.0f)
{ {
float currHealth = healthBar.BarSize; float currHealth = healthBar.BarSize;
@@ -1150,18 +1227,20 @@ namespace Barotrauma
public static Color GetAfflictionIconColor(AfflictionPrefab prefab, float afflictionStrength) public static Color GetAfflictionIconColor(AfflictionPrefab prefab, float afflictionStrength)
{ {
//use sqrt to make the color change rapidly when strength is low
//(low strength is where seeing the severity of the affliction makes more difference - at high strengths the character is already unconscious or dead)
float colorT = MathF.Sqrt(afflictionStrength / prefab.MaxStrength);
// No specific colors, use generic // No specific colors, use generic
if (prefab.IconColors == null) if (prefab.IconColors == null)
{ {
if (prefab.IsBuff) if (prefab.IsBuff)
{ {
return ToolBox.GradientLerp(afflictionStrength / prefab.MaxStrength, GUIStyle.BuffColorLow, GUIStyle.BuffColorMedium, GUIStyle.BuffColorHigh); return ToolBox.GradientLerp(colorT, GUIStyle.BuffColorLow, GUIStyle.BuffColorMedium, GUIStyle.BuffColorHigh);
} }
return ToolBox.GradientLerp(afflictionStrength / prefab.MaxStrength, GUIStyle.DebuffColorLow, GUIStyle.DebuffColorMedium, GUIStyle.DebuffColorHigh); return ToolBox.GradientLerp(colorT, GUIStyle.DebuffColorLow, GUIStyle.DebuffColorMedium, GUIStyle.DebuffColorHigh);
} }
return ToolBox.GradientLerp(colorT, prefab.IconColors);
return ToolBox.GradientLerp(afflictionStrength / prefab.MaxStrength, prefab.IconColors);
} }
public static Color GetAfflictionIconColor(Affliction affliction) => GetAfflictionIconColor(affliction.Prefab, affliction); public static Color GetAfflictionIconColor(Affliction affliction) => GetAfflictionIconColor(affliction.Prefab, affliction);
@@ -1172,7 +1251,7 @@ namespace Barotrauma
{ {
if (selectedLimb == null) if (selectedLimb == null)
{ {
afflictionIconContainer.Content.ClearChildren(); afflictionIconList.Content.ClearChildren();
return; return;
} }
@@ -1207,7 +1286,7 @@ namespace Barotrauma
private void CreateAfflictionInfos(IEnumerable<Affliction> afflictions) private void CreateAfflictionInfos(IEnumerable<Affliction> afflictions)
{ {
afflictionIconContainer.ClearChildren(); afflictionIconList.ClearChildren();
displayedAfflictions.Clear(); displayedAfflictions.Clear();
Affliction mostSevereAffliction = SortAfflictionsBySeverity(afflictions, excludeBuffs: false).FirstOrDefault(); Affliction mostSevereAffliction = SortAfflictionsBySeverity(afflictions, excludeBuffs: false).FirstOrDefault();
@@ -1217,7 +1296,7 @@ namespace Barotrauma
{ {
displayedAfflictions.Add((affliction, affliction.Strength)); displayedAfflictions.Add((affliction, affliction.Strength));
var frame = new GUIButton(new RectTransform(new Vector2(1.0f, 0.25f), afflictionIconContainer.Content.RectTransform), style: "ListBoxElement") var frame = new GUIButton(new RectTransform(new Vector2(1.0f, 0.25f), afflictionIconList.Content.RectTransform), style: "ListBoxElement")
{ {
UserData = affliction, UserData = affliction,
OnClicked = SelectAffliction OnClicked = SelectAffliction
@@ -1275,7 +1354,7 @@ namespace Barotrauma
} }
buttonToSelect?.OnClicked(buttonToSelect, buttonToSelect.UserData); buttonToSelect?.OnClicked(buttonToSelect, buttonToSelect.UserData);
afflictionIconContainer.RecalculateChildren(); afflictionIconList.RecalculateChildren();
} }
private void CreateRecommendedTreatments() private void CreateRecommendedTreatments()
@@ -1386,7 +1465,7 @@ namespace Barotrauma
recommendedTreatmentContainer.RecalculateChildren(); recommendedTreatmentContainer.RecalculateChildren();
afflictionIconContainer.Content.RectTransform.SortChildren((r1, r2) => afflictionIconList.Content.RectTransform.SortChildren((r1, r2) =>
{ {
var first = r1.GUIComponent.UserData as Affliction; var first = r1.GUIComponent.UserData as Affliction;
var second = r2.GUIComponent.UserData as Affliction; var second = r2.GUIComponent.UserData as Affliction;
@@ -1437,7 +1516,10 @@ namespace Barotrauma
}; };
var description = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.3f), parent.RectTransform), var description = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.3f), parent.RectTransform),
affliction.Prefab.Description, textAlignment: Alignment.TopLeft, wrap: true) affliction.Prefab.GetDescription(
affliction.Strength,
Character == Character.Controlled ? AfflictionPrefab.Description.TargetType.Self : AfflictionPrefab.Description.TargetType.OtherCharacter),
textAlignment: Alignment.TopLeft, wrap: true)
{ {
CanBeFocused = false CanBeFocused = false
}; };
@@ -1482,10 +1564,9 @@ namespace Barotrauma
private bool SelectAffliction(GUIButton button, object userData) private bool SelectAffliction(GUIButton button, object userData)
{ {
bool selected = button.Selected; bool selected = button.Selected;
foreach (var child in afflictionIconContainer.Content.Children) foreach (var child in afflictionIconList.Content.Children)
{ {
GUIButton btn = child.GetChild<GUIButton>(); if (child is GUIButton btn)
if (btn != null)
{ {
btn.Selected = btn == button && !selected; btn.Selected = btn == button && !selected;
} }
@@ -1516,7 +1597,7 @@ namespace Barotrauma
afflictionEffectColor = GUIStyle.Green; afflictionEffectColor = GUIStyle.Green;
} }
var child = afflictionIconContainer.Content.FindChild(affliction); var child = afflictionIconList.Content.FindChild(affliction);
var afflictionStrengthPredictionBar = child.GetChild<GUILayoutGroup>().GetChildByUserData("afflictionstrengthprediction") as GUIProgressBar; var afflictionStrengthPredictionBar = child.GetChild<GUILayoutGroup>().GetChildByUserData("afflictionstrengthprediction") as GUIProgressBar;
afflictionStrengthPredictionBar.BarSize = 0.0f; afflictionStrengthPredictionBar.BarSize = 0.0f;
@@ -1836,14 +1917,14 @@ namespace Barotrauma
i++; i++;
} }
if (selectedLimbIndex > -1 && afflictionIconContainer.Content.CountChildren > 0) if (selectedLimbIndex > -1 && afflictionIconList.Content.CountChildren > 0)
{ {
LimbHealth limbHealth = limbHealths[selectedLimbIndex]; LimbHealth limbHealth = limbHealths[selectedLimbIndex];
if (limbHealth?.IndicatorSprite != null) if (limbHealth?.IndicatorSprite != null)
{ {
Rectangle selectedLimbArea = GetLimbHighlightArea(limbHealth, drawArea); Rectangle selectedLimbArea = GetLimbHighlightArea(limbHealth, drawArea);
GUI.DrawLine(spriteBatch, GUI.DrawLine(spriteBatch,
new Vector2(afflictionIconContainer.Rect.X, afflictionIconContainer.Rect.Y), new Vector2(afflictionIconList.Rect.X, afflictionIconList.Rect.Y),
selectedLimbArea.Center.ToVector2(), selectedLimbArea.Center.ToVector2(),
Color.LightGray * 0.5f, width: 4); Color.LightGray * 0.5f, width: 4);
} }
@@ -1823,7 +1823,18 @@ namespace Barotrauma
Identifier afflictionId = affliction.TranslationIdentifier; Identifier afflictionId = affliction.TranslationIdentifier;
addIfMissing($"afflictionname.{afflictionId}".ToIdentifier(), language); addIfMissing($"afflictionname.{afflictionId}".ToIdentifier(), language);
addIfMissing($"afflictiondescription.{afflictionId}".ToIdentifier(), language);
if (affliction.Descriptions.Any())
{
foreach (var description in affliction.Descriptions)
{
addIfMissing(description.TextTag, language);
}
}
else
{
addIfMissing($"afflictiondescription.{afflictionId}".ToIdentifier(), language);
}
} }
foreach (var talentTree in TalentTree.JobTalentTrees) foreach (var talentTree in TalentTree.JobTalentTrees)
@@ -70,6 +70,7 @@ namespace EventInput
public delegate void CharEnteredHandler(object sender, CharacterEventArgs e); public delegate void CharEnteredHandler(object sender, CharacterEventArgs e);
public delegate void KeyEventHandler(object sender, KeyEventArgs e); public delegate void KeyEventHandler(object sender, KeyEventArgs e);
public delegate void EditingTextHandler(object sender, TextEditingEventArgs e);
public static class EventInput public static class EventInput
{ {
@@ -88,6 +89,15 @@ namespace EventInput
/// </summary> /// </summary>
public static event KeyEventHandler KeyUp; public static event KeyEventHandler KeyUp;
#if !WINDOWS
/// <summary>
/// Raised when the user is editing text and IME is in progress.
/// Windows build uses ImeSharp instead because SDL2's IME implementation is broken on Windows (https://github.com/libsdl-org/SDL/issues/2243)
/// </summary>
public static event EditingTextHandler EditingText;
#endif
static bool initialized; static bool initialized;
/// <summary> /// <summary>
@@ -102,6 +112,9 @@ namespace EventInput
} }
window.TextInput += ReceiveInput; window.TextInput += ReceiveInput;
#if !WINDOWS
window.TextEditing += ReceiveTextEditing;
#endif
initialized = true; initialized = true;
} }
@@ -112,6 +125,13 @@ namespace EventInput
KeyDown?.Invoke(sender, new KeyEventArgs(e.Key)); KeyDown?.Invoke(sender, new KeyEventArgs(e.Key));
} }
#if !WINDOWS
private static void ReceiveTextEditing(object sender, TextEditingEventArgs e)
{
EditingText?.Invoke(sender, e);
}
#endif
public static void OnCharEntered(char character) public static void OnCharEntered(char character)
{ {
CharEntered?.Invoke(null, new CharacterEventArgs(character, 0)); CharEntered?.Invoke(null, new CharacterEventArgs(character, 0));
@@ -1,8 +1,4 @@
using System; using Barotrauma;
using System.Threading;
#if WINDOWS
using System.Windows;
#endif
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Input;
@@ -15,6 +11,11 @@ namespace EventInput
void ReceiveCommandInput(char command); void ReceiveCommandInput(char command);
void ReceiveSpecialInput(Keys key); void ReceiveSpecialInput(Keys key);
#if !WINDOWS
/// Windows build uses ImeSharp instead because SDL2's IME implementation is broken on Windows (https://github.com/libsdl-org/SDL/issues/2243)
void ReceiveEditingInput(string text, int start);
#endif
bool Selected { get; set; } //or Focused bool Selected { get; set; } //or Focused
} }
@@ -25,14 +26,26 @@ namespace EventInput
EventInput.Initialize(window); EventInput.Initialize(window);
EventInput.CharEntered += EventInput_CharEntered; EventInput.CharEntered += EventInput_CharEntered;
EventInput.KeyDown += EventInput_KeyDown; EventInput.KeyDown += EventInput_KeyDown;
} #if !WINDOWS
EventInput.EditingText += EventInput_TextEditing;
#endif
/*
* SDL by default starts in a state where it accepts IME inputs
* this is bad because this blocks keybinds since the IME thinks
* it's typing in a text box and not forwarding keybinds to the game.
*/
TextInput.StopTextInput();
}
#if !WINDOWS
public void EventInput_TextEditing(object sender, TextEditingEventArgs e)
{
_subscriber?.ReceiveEditingInput(e.Text, e.Start);
}
#endif
public void EventInput_KeyDown(object sender, KeyEventArgs e) public void EventInput_KeyDown(object sender, KeyEventArgs e)
{ {
if (_subscriber == null) _subscriber?.ReceiveSpecialInput(e.KeyCode);
return;
_subscriber.ReceiveSpecialInput(e.KeyCode);
} }
void EventInput_CharEntered(object sender, CharacterEventArgs e) void EventInput_CharEntered(object sender, CharacterEventArgs e)
@@ -74,12 +87,25 @@ namespace EventInput
get { return _subscriber; } get { return _subscriber; }
set set
{ {
if (_subscriber == value) return; if (_subscriber == value) { return; }
if (_subscriber != null)
if (_subscriber is GUITextBox)
{
TextInput.StopTextInput();
_subscriber.Selected = false; _subscriber.Selected = false;
}
if (value is GUITextBox box)
{
TextInput.SetTextInputRect(box.Rect);
TextInput.StartTextInput();
}
_subscriber = value; _subscriber = value;
if (value != null) if (value != null)
{
value.Selected = true; value.Selected = true;
}
} }
} }
} }
@@ -0,0 +1,37 @@
using Barotrauma.Tutorials;
using Segment = Barotrauma.ObjectiveManager.Segment;
namespace Barotrauma;
partial class CheckObjectiveAction : BinaryOptionAction
{
public enum CheckType
{
Added,
Completed
}
[Serialize(CheckType.Completed, IsPropertySaveable.Yes)]
public CheckType Type { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
public Identifier Identifier { get; set; }
partial void DetermineSuccessProjSpecific(ref bool success)
{
success = false;
if (Identifier.IsEmpty)
{
success = ObjectiveManager.AllActiveObjectivesCompleted();
}
else if (ObjectiveManager.GetObjective(Identifier) is Segment segment)
{
success = Type switch
{
CheckType.Added => true,
CheckType.Completed => segment.IsCompleted,
_ => false
};
}
}
}
@@ -323,7 +323,10 @@ namespace Barotrauma
AlwaysOverrideCursor = true AlwaysOverrideCursor = true
}; };
LocalizedString translatedText = TextManager.ParseInputTypes(TextManager.Get(text)).Fallback(text); LocalizedString translatedText = speaker?.DisplayName is not null ?
TextManager.GetWithVariable(text, "[speakername]", speaker?.DisplayName) :
TextManager.Get(text);
translatedText = TextManager.ParseInputTypes(translatedText).Fallback(text);
if (speaker?.Info != null && drawChathead) if (speaker?.Info != null && drawChathead)
{ {
@@ -11,11 +11,13 @@ partial class MessageBoxAction : EventAction
if (Type == ActionType.Create || Type == ActionType.ConnectObjective) if (Type == ActionType.Create || Type == ActionType.ConnectObjective)
{ {
CreateMessageBox(); CreateMessageBox();
if (!ObjectiveTag.IsEmpty && GameMain.GameSession?.GameMode is TutorialMode tutorialMode) if (!ObjectiveTag.IsEmpty)
{ {
Identifier id = Identifier.IfEmpty(Text); Identifier id = Identifier.IfEmpty(Text);
var segment = Tutorial.Segment.CreateMessageBoxSegment(id, ObjectiveTag, CreateMessageBox); var segment = ObjectiveManager.Segment.CreateMessageBoxSegment(id, ObjectiveTag, CreateMessageBox);
tutorialMode.Tutorial?.TriggerTutorialSegment(segment, connectObjective: Type == ActionType.ConnectObjective); segment.CanBeCompleted = ObjectiveCanBeCompleted;
segment.ParentId = ParentObjectiveId;
ObjectiveManager.TriggerTutorialSegment(segment, connectObjective: Type == ActionType.ConnectObjective);
} }
} }
else if (Type == ActionType.Close) else if (Type == ActionType.Close)
@@ -1,50 +1,43 @@
using Barotrauma.Tutorials;
namespace Barotrauma; namespace Barotrauma;
partial class TutorialSegmentAction : EventAction partial class TutorialSegmentAction : EventAction
{ {
private Tutorial.Segment segment; private ObjectiveManager.Segment segment;
partial void UpdateProjSpecific() partial void UpdateProjSpecific()
{ {
// Only need to create the segment when it's being triggered (otherwise the tutorial already has the segment instance) // Only need to create the segment when it's being triggered (otherwise the tutorial already has the segment instance)
if (Type == SegmentActionType.Trigger) if (Type == SegmentActionType.Trigger)
{ {
segment = Tutorial.Segment.CreateInfoBoxSegment(Identifier, ObjectiveTag, AutoPlayVideo ? Tutorials.AutoPlayVideo.Yes : Tutorials.AutoPlayVideo.No, segment = ObjectiveManager.Segment.CreateInfoBoxSegment(Identifier, ObjectiveTag, AutoPlayVideo ? Tutorials.AutoPlayVideo.Yes : Tutorials.AutoPlayVideo.No,
new Tutorial.Segment.Text(TextTag, Width, Height, Anchor.Center), new ObjectiveManager.Segment.Text(TextTag, Width, Height, Anchor.Center),
new Tutorial.Segment.Video(VideoFile, TextTag, Width, Height)); new ObjectiveManager.Segment.Video(VideoFile, TextTag, Width, Height));
} }
else if (Type == SegmentActionType.Add) else if (Type == SegmentActionType.Add)
{ {
segment = Tutorial.Segment.CreateObjectiveSegment(Identifier, !ObjectiveTag.IsEmpty ? ObjectiveTag : Identifier); segment = ObjectiveManager.Segment.CreateObjectiveSegment(Identifier, !ObjectiveTag.IsEmpty ? ObjectiveTag : Identifier);
} }
if (GameMain.GameSession?.GameMode is TutorialMode tutorialMode) if (segment is not null)
{ {
if (tutorialMode.Tutorial is Tutorial tutorial) segment.CanBeCompleted = CanBeCompleted;
{ segment.ParentId = ParentObjectiveId;
switch (Type)
{
case SegmentActionType.Trigger:
case SegmentActionType.Add:
tutorial.TriggerTutorialSegment(segment);
break;
case SegmentActionType.Complete:
tutorial.CompleteTutorialSegment(Identifier);
break;
case SegmentActionType.Remove:
tutorial.RemoveTutorialSegment(Identifier);
break;
case SegmentActionType.CompleteAndRemove:
tutorial.CompleteTutorialSegment(Identifier);
tutorial.RemoveTutorialSegment(Identifier);
break;
}
}
} }
else switch (Type)
{ {
DebugConsole.ThrowError($"Error in event \"{ParentEvent.Prefab.Identifier}\": attempting to use TutorialSegmentAction during a non-Tutorial game mode!"); case SegmentActionType.Trigger:
case SegmentActionType.Add:
ObjectiveManager.TriggerTutorialSegment(segment);
break;
case SegmentActionType.Complete:
ObjectiveManager.CompleteTutorialSegment(Identifier);
break;
case SegmentActionType.Remove:
ObjectiveManager.RemoveTutorialSegment(Identifier);
break;
case SegmentActionType.CompleteAndRemove:
ObjectiveManager.CompleteTutorialSegment(Identifier);
ObjectiveManager.RemoveTutorialSegment(Identifier);
break;
} }
} }
} }
@@ -255,7 +255,17 @@ namespace Barotrauma
ScreenChanged = false; ScreenChanged = false;
} }
updateList.ForEach(c => c.DrawAuto(spriteBatch)); foreach (GUIComponent c in updateList)
{
c.DrawAuto(spriteBatch);
}
// always draw IME preview on top of everything else
foreach (GUIComponent c in updateList)
{
if (c is not GUITextBox box) { continue; }
box.DrawIMEPreview(spriteBatch);
}
if (ScreenOverlayColor.A > 0.0f) if (ScreenOverlayColor.A > 0.0f)
{ {
@@ -1251,6 +1261,10 @@ namespace Barotrauma
UpdateMessages(deltaTime); UpdateMessages(deltaTime);
UpdateSavingIndicator(deltaTime); UpdateSavingIndicator(deltaTime);
} }
#if WINDOWS
GUITextBox.UpdateIME();
#endif
} }
public static void UpdateGUIMessageBoxesOnly(float deltaTime) public static void UpdateGUIMessageBoxesOnly(float deltaTime)
@@ -1357,7 +1371,7 @@ namespace Barotrauma
} }
} }
#region Element drawing #region Element drawing
private static readonly List<float> usedIndicatorAngles = new List<float>(); private static readonly List<float> usedIndicatorAngles = new List<float>();
@@ -1843,9 +1857,9 @@ namespace Barotrauma
Vector2 pos = new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight) - new Vector2(HUDLayoutSettings.Padding) - 2 * Scale * sheet.FrameSize.ToVector2(); Vector2 pos = new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight) - new Vector2(HUDLayoutSettings.Padding) - 2 * Scale * sheet.FrameSize.ToVector2();
sheet.Draw(spriteBatch, (int)Math.Floor(savingIndicatorSpriteIndex), pos, savingIndicatorColor, origin: Vector2.Zero, rotate: 0.0f, scale: new Vector2(Scale)); sheet.Draw(spriteBatch, (int)Math.Floor(savingIndicatorSpriteIndex), pos, savingIndicatorColor, origin: Vector2.Zero, rotate: 0.0f, scale: new Vector2(Scale));
} }
#endregion #endregion
#region Element creation #region Element creation
public static Texture2D CreateCircle(int radius, bool filled = false) public static Texture2D CreateCircle(int radius, bool filled = false)
{ {
@@ -2217,9 +2231,9 @@ namespace Barotrauma
return msgBox; return msgBox;
} }
#endregion #endregion
#region Element positioning #region Element positioning
private static List<T> CreateElements<T>(int count, RectTransform parent, Func<RectTransform, T> constructor, private static List<T> CreateElements<T>(int count, RectTransform parent, Func<RectTransform, T> constructor,
Vector2? relativeSize = null, Point? absoluteSize = null, Vector2? relativeSize = null, Point? absoluteSize = null,
Anchor anchor = Anchor.TopLeft, Pivot? pivot = null, Point? minSize = null, Point? maxSize = null, Anchor anchor = Anchor.TopLeft, Pivot? pivot = null, Point? minSize = null, Point? maxSize = null,
@@ -2418,9 +2432,9 @@ namespace Barotrauma
} }
} }
#endregion #endregion
#region Misc #region Misc
public static void TogglePauseMenu() public static void TogglePauseMenu()
{ {
if (Screen.Selected == GameMain.MainMenuScreen) { return; } if (Screen.Selected == GameMain.MainMenuScreen) { return; }
@@ -2658,6 +2672,6 @@ namespace Barotrauma
if (!isSavingIndicatorEnabled) { return; } if (!isSavingIndicatorEnabled) { return; }
timeUntilSavingIndicatorDisabled = delay; timeUntilSavingIndicatorDisabled = delay;
} }
#endregion #endregion
} }
} }
@@ -9,6 +9,7 @@ using Barotrauma.IO;
using RestSharp; using RestSharp;
using System.Net; using System.Net;
using System.Collections.Immutable; using System.Collections.Immutable;
using Barotrauma.Tutorials;
namespace Barotrauma namespace Barotrauma
{ {
@@ -736,7 +737,7 @@ namespace Barotrauma
public static void DrawToolTip(SpriteBatch spriteBatch, RichString toolTip, Vector2 pos) public static void DrawToolTip(SpriteBatch spriteBatch, RichString toolTip, Vector2 pos)
{ {
if (GameMain.GameSession?.GameMode is TutorialMode tutorialMode && tutorialMode.Tutorial.ContentRunning) { return; } if (ObjectiveManager.ContentRunning) { return; }
int width = (int)(400 * GUI.Scale); int width = (int)(400 * GUI.Scale);
int height = (int)(18 * GUI.Scale); int height = (int)(18 * GUI.Scale);
@@ -759,7 +760,7 @@ namespace Barotrauma
public static void DrawToolTip(SpriteBatch spriteBatch, RichString toolTip, Rectangle targetElement) public static void DrawToolTip(SpriteBatch spriteBatch, RichString toolTip, Rectangle targetElement)
{ {
if (GameMain.GameSession?.GameMode is TutorialMode tutorialMode && tutorialMode.Tutorial.ContentRunning) { return; } if (ObjectiveManager.ContentRunning) { return; }
int width = (int)(400 * GUI.Scale); int width = (int)(400 * GUI.Scale);
int height = (int)(18 * GUI.Scale); int height = (int)(18 * GUI.Scale);
@@ -112,6 +112,10 @@ namespace Barotrauma
public void ReceiveTextInput(string text) { } public void ReceiveTextInput(string text) { }
public void ReceiveCommandInput(char command) { } public void ReceiveCommandInput(char command) { }
#if !WINDOWS
public void ReceiveEditingInput(string text, int start) { }
#endif
public void ReceiveSpecialInput(Keys key) public void ReceiveSpecialInput(Keys key)
{ {
switch (key) switch (key)
@@ -1349,6 +1349,10 @@ namespace Barotrauma
public void ReceiveTextInput(string text) { } public void ReceiveTextInput(string text) { }
public void ReceiveCommandInput(char command) { } public void ReceiveCommandInput(char command) { }
#if !WINDOWS
public void ReceiveEditingInput(string text, int start) { }
#endif
public void ReceiveSpecialInput(Keys key) public void ReceiveSpecialInput(Keys key)
{ {
switch (key) switch (key)
@@ -24,7 +24,8 @@ namespace Barotrauma
InGame, InGame,
Vote, Vote,
Hint, Hint,
Tutorial Tutorial,
Warning // Keep this last so that it's always drawn in front
} }
private bool IsAnimated => type == Type.InGame || type == Type.Hint || type == Type.Tutorial; private bool IsAnimated => type == Type.InGame || type == Type.Hint || type == Type.Tutorial;
@@ -84,8 +85,8 @@ namespace Barotrauma
public static GUIComponent VisibleBox => MessageBoxes.LastOrDefault(); public static GUIComponent VisibleBox => MessageBoxes.LastOrDefault();
public GUIMessageBox(LocalizedString headerText, LocalizedString text, Vector2? relativeSize = null, Point? minSize = null) public GUIMessageBox(LocalizedString headerText, LocalizedString text, Vector2? relativeSize = null, Point? minSize = null, Type type = Type.Default)
: this(headerText, text, new LocalizedString[] { "OK" }, relativeSize, minSize) : this(headerText, text, new LocalizedString[] { "OK" }, relativeSize, minSize, type: type)
{ {
this.Buttons[0].OnClicked = Close; this.Buttons[0].OnClicked = Close;
} }
@@ -147,7 +148,7 @@ namespace Barotrauma
Tag = tag.ToIdentifier(); Tag = tag.ToIdentifier();
#warning TODO: These should be broken into separate methods at least #warning TODO: These should be broken into separate methods at least
if (type == Type.Default || type == Type.Vote) if (type == Type.Default || type == Type.Vote || type == Type.Warning)
{ {
Content = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.85f), InnerFrame.RectTransform, Anchor.Center)) { AbsoluteSpacing = 5 }; Content = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.85f), InnerFrame.RectTransform, Anchor.Center)) { AbsoluteSpacing = 5 };
@@ -4,6 +4,7 @@ using Microsoft.Xna.Framework.Graphics;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
namespace Barotrauma namespace Barotrauma
@@ -551,6 +552,8 @@ namespace Barotrauma
if (TextGetter != null) { Text = TextGetter(); } if (TextGetter != null) { Text = TextGetter(); }
string textToShow = Censor ? censoredText : (Wrap ? wrappedText.Value : text.SanitizedValue);
Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle; Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle;
if (overflowClipActive) if (overflowClipActive)
{ {
@@ -561,7 +564,7 @@ namespace Barotrauma
spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable); spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
} }
if (!text.IsNullOrEmpty()) if (!string.IsNullOrEmpty(textToShow))
{ {
Vector2 pos = rect.Location.ToVector2() + textPos + TextOffset; Vector2 pos = rect.Location.ToVector2() + textPos + TextOffset;
if (RoundToNearestPixel) if (RoundToNearestPixel)
@@ -570,7 +573,8 @@ namespace Barotrauma
pos.Y = (int)pos.Y; pos.Y = (int)pos.Y;
} }
Color currentTextColor = State == ComponentState.Hover || State == ComponentState.HoverSelected ? HoverTextColor : TextColor; Color currentTextColor = State is ComponentState.Hover or ComponentState.HoverSelected ? HoverTextColor : TextColor;
if (!enabled) if (!enabled)
{ {
currentTextColor = disabledTextColor; currentTextColor = disabledTextColor;
@@ -582,7 +586,6 @@ namespace Barotrauma
if (!HasColorHighlight) if (!HasColorHighlight)
{ {
string textToShow = Censor ? censoredText : (Wrap ? wrappedText.Value : text.SanitizedValue);
Color colorToShow = currentTextColor * (currentTextColor.A / 255.0f); Color colorToShow = currentTextColor * (currentTextColor.A / 255.0f);
if (TextManager.DebugDraw) if (TextManager.DebugDraw)
{ {
@@ -604,10 +607,10 @@ namespace Barotrauma
{ {
if (OverrideRichTextDataAlpha) if (OverrideRichTextDataAlpha)
{ {
RichTextData.Value.ForEach(rt => rt.Alpha = currentTextColor.A / 255.0f); RichTextData?.ForEach(rt => rt.Alpha = currentTextColor.A / 255.0f);
} }
Font.DrawStringWithColors(spriteBatch, Censor ? censoredText : (Wrap ? wrappedText : text.SanitizedString).Value, pos, Font.DrawStringWithColors(spriteBatch, textToShow, pos,
currentTextColor * (currentTextColor.A / 255.0f), 0.0f, origin, TextScale, SpriteEffects.None, textDepth, RichTextData.Value, alignment: textAlignment, forceUpperCase: ForceUpperCase); currentTextColor * (currentTextColor.A / 255.0f), 0.0f, origin, TextScale, SpriteEffects.None, textDepth, RichTextData, alignment: textAlignment, forceUpperCase: ForceUpperCase);
} }
Strikethrough?.Draw(spriteBatch, (int)Math.Ceiling(TextSize.X / 2f), pos.X, Strikethrough?.Draw(spriteBatch, (int)Math.Ceiling(TextSize.X / 2f), pos.X,
@@ -11,7 +11,7 @@ namespace Barotrauma
public delegate void TextBoxEvent(GUITextBox sender, Keys key); public delegate void TextBoxEvent(GUITextBox sender, Keys key);
public class GUITextBox : GUIComponent, IKeyboardSubscriber public partial class GUITextBox : GUIComponent, IKeyboardSubscriber
{ {
public event TextBoxEvent OnSelected; public event TextBoxEvent OnSelected;
public event TextBoxEvent OnDeselected; public event TextBoxEvent OnDeselected;
@@ -67,7 +67,7 @@ namespace Barotrauma
private int selectionEndIndex; private int selectionEndIndex;
private bool IsLeftToRight => selectionStartIndex <= selectionEndIndex; private bool IsLeftToRight => selectionStartIndex <= selectionEndIndex;
private GUICustomComponent caretAndSelectionRenderer; private readonly GUICustomComponent caretAndSelectionRenderer;
private bool mouseHeldInside; private bool mouseHeldInside;
@@ -189,6 +189,7 @@ namespace Barotrauma
base.Font = value; base.Font = value;
if (textBlock == null) { return; } if (textBlock == null) { return; }
textBlock.Font = value; textBlock.Font = value;
imePreviewTextHandler.Font = Font;
} }
} }
@@ -253,6 +254,8 @@ namespace Barotrauma
public override bool PlaySoundOnSelect { get; set; } = true; public override bool PlaySoundOnSelect { get; set; } = true;
private readonly IMEPreviewTextHandler imePreviewTextHandler;
public GUITextBox(RectTransform rectT, string text = "", Color? textColor = null, GUIFont font = null, public GUITextBox(RectTransform rectT, string text = "", Color? textColor = null, GUIFont font = null,
Alignment textAlignment = Alignment.Left, bool wrap = false, string style = "", Color? color = null, bool createClearButton = false, bool createPenIcon = true) Alignment textAlignment = Alignment.Left, bool wrap = false, string style = "", Color? color = null, bool createClearButton = false, bool createPenIcon = true)
: base(style, rectT) : base(style, rectT)
@@ -264,6 +267,7 @@ namespace Barotrauma
frame = new GUIFrame(new RectTransform(Vector2.One, rectT, Anchor.Center), style, color); frame = new GUIFrame(new RectTransform(Vector2.One, rectT, Anchor.Center), style, color);
GUIStyle.Apply(frame, style == "" ? "GUITextBox" : style); GUIStyle.Apply(frame, style == "" ? "GUITextBox" : style);
textBlock = new GUITextBlock(new RectTransform(Vector2.One, frame.RectTransform, Anchor.CenterLeft), text ?? "", textColor, font, textAlignment, wrap); textBlock = new GUITextBlock(new RectTransform(Vector2.One, frame.RectTransform, Anchor.CenterLeft), text ?? "", textColor, font, textAlignment, wrap);
imePreviewTextHandler = new IMEPreviewTextHandler(textBlock.Font);
GUIStyle.Apply(textBlock, "", this); GUIStyle.Apply(textBlock, "", this);
if (font != null) { textBlock.Font = font; } if (font != null) { textBlock.Font = font; }
CaretEnabled = true; CaretEnabled = true;
@@ -295,7 +299,6 @@ namespace Barotrauma
textBlock.RectTransform.MaxSize = new Point(frame.Rect.Width - icon.Rect.Height - clearButtonWidth - icon.RectTransform.AbsoluteOffset.X * 2, int.MaxValue); textBlock.RectTransform.MaxSize = new Point(frame.Rect.Width - icon.Rect.Height - clearButtonWidth - icon.RectTransform.AbsoluteOffset.X * 2, int.MaxValue);
} }
Font = textBlock.Font; Font = textBlock.Font;
Enabled = true; Enabled = true;
rectT.SizeChanged += () => rectT.SizeChanged += () =>
@@ -381,7 +384,9 @@ namespace Barotrauma
{ {
GUI.KeyboardDispatcher.Subscriber = null; GUI.KeyboardDispatcher.Subscriber = null;
} }
OnDeselected?.Invoke(this, Keys.None); OnDeselected?.Invoke(this, Keys.None);
imePreviewTextHandler.Reset();
} }
public override void Flash(Color? color = null, float flashDuration = 1.5f, bool useRectangleFlash = false, bool useCircularFlash = false, Vector2? flashRectOffset = null) public override void Flash(Color? color = null, float flashDuration = 1.5f, bool useRectangleFlash = false, bool useCircularFlash = false, Vector2? flashRectOffset = null)
@@ -673,6 +678,18 @@ namespace Barotrauma
break; break;
} }
} }
#if !WINDOWS
public void ReceiveEditingInput(string text, int start)
{
if (string.IsNullOrEmpty(text))
{
if (start is 0) { imePreviewTextHandler.Reset(); }
return;
}
imePreviewTextHandler.UpdateText(text, start);
}
#endif
public void ReceiveSpecialInput(Keys key) public void ReceiveSpecialInput(Keys key)
{ {
@@ -864,6 +881,24 @@ namespace Barotrauma
} }
} }
public void DrawIMEPreview(SpriteBatch spriteBatch)
{
if (!imePreviewTextHandler.HasText) { return; }
Vector2 imePosition = CaretScreenPos;
int inflate = GUI.IntScale(3);
RectangleF rect = new RectangleF(imePosition, imePreviewTextHandler.TextSize);
rect.Inflate(inflate, inflate);
RectangleF borderRect = rect;
borderRect.Inflate(1, 1);
GUI.DrawFilledRectangle(spriteBatch, borderRect, Color.White, depth: 0.02f);
GUI.DrawFilledRectangle(spriteBatch, rect, Color.Black, depth: 0.01f);
Font.DrawString(spriteBatch, imePreviewTextHandler.PreviewText, imePosition, GUIStyle.Orange, 0.0f, Vector2.Zero, 1f, SpriteEffects.None, 0, alignment: textBlock.TextAlignment, forceUpperCase: textBlock.ForceUpperCase);
}
private void CalculateSelection() private void CalculateSelection()
{ {
string textDrawn = Censor ? textBlock.CensoredText : WrappedText; string textDrawn = Censor ? textBlock.CensoredText : WrappedText;
@@ -0,0 +1,86 @@
using ImeSharp;
using Microsoft.Xna.Framework;
using System;
namespace Barotrauma;
/// <summary>
/// A class for handling Input Method Editor (used for inputting e.g. Chinese and Japanese text)
/// </summary>
public partial class GUITextBox : GUIComponent
{
private static bool initialized;
public static GUIFrame IMEWindow { get; set; }
public static GUITextBlock IMETextBlock { get; set; }
public static void UpdateIME()
{
if (!initialized) { InitializeIME(); }
if (GUI.KeyboardDispatcher.Subscriber is GUITextBox { Selected: true })
{
IMEWindow?.AddToGUIUpdateList(order: 10);
}
}
private static void InitializeIME()
{
InputMethod.Initialize(GameMain.Instance.Window.Hwnd, false);
InputMethod.TextCompositionCallback = OnTextComposition;
InputMethod.CommitTextCompositionCallback = OnCommitTextComposition;
InputMethod.Enabled = true;
IMEWindow = new GUIFrame(new RectTransform(new Point(GUI.IntScale(300), GUI.IntScale(300)), GUI.Canvas), "InnerFrame") { CanBeFocused = false, Visible = false };
IMETextBlock = new GUITextBlock(new RectTransform(Vector2.One, IMEWindow.RectTransform), "") { CanBeFocused = false };
initialized = true;
}
private static void OnTextComposition(IMEString compositionText, int cursorPosition, IMEString[] candidateList, int candidatePageStart, int candidatePageSize, int candidateSelection)
{
if (GUI.KeyboardDispatcher.Subscriber is not GUITextBox { Selected: true } textBox) { return; }
IMEWindow.Visible = true;
string text = compositionText.ToString().Insert(cursorPosition, "|");
if (candidateList != null)
{
text += "\n";
for (int i = 0; i < candidatePageSize; i++)
{
string candidateStr = $"\t{candidatePageStart + i + 1} {candidateList[i]}";
if (candidateSelection == i)
{
candidateStr = $" ‖color:{XMLExtensions.ToStringHex(Color.White)}‖{candidateStr}‖end‖";
}
candidateStr += "\n";
text += candidateStr;
}
}
IMETextBlock.Text = RichString.Rich(text);
IMEWindow.RectTransform.NonScaledSize = new Point(
Math.Max(IMEWindow.Rect.Width, (int)IMETextBlock.TextSize.X + GUI.IntScale(32)),
(int)IMETextBlock.TextSize.Y);
Point windowPos = new Point(textBox.Rect.X, textBox.Rect.Bottom);
if (windowPos.Y + IMEWindow.Rect.Height > GameMain.GraphicsHeight)
{
windowPos.Y = textBox.Rect.Y - IMEWindow.Rect.Height;
}
IMEWindow.RectTransform.ScreenSpaceOffset = windowPos;
}
private static void OnCommitTextComposition(string text)
{
if (IMEWindow.Visible)
{
foreach (char c in text)
{
if (!char.IsControl(c))
{
GUI.KeyboardDispatcher.Subscriber?.ReceiveTextInput(c);
}
}
}
IMEWindow.Visible = false;
}
}
@@ -65,7 +65,7 @@ namespace Barotrauma
get; private set; get; private set;
} }
public static Rectangle AfflictionAreaLeft public static Rectangle HealthBarAfflictionArea
{ {
get; private set; get; private set;
} }
@@ -143,7 +143,7 @@ namespace Barotrauma
} }
int healthBarHeight = (int)(50f * GUI.Scale); int healthBarHeight = (int)(50f * GUI.Scale);
HealthBarArea = new Rectangle(BottomRightInfoArea.Right - healthBarWidth + (int)Math.Floor(1 / GUI.Scale), BottomRightInfoArea.Y - healthBarHeight + GUI.IntScale(10), healthBarWidth, healthBarHeight); HealthBarArea = new Rectangle(BottomRightInfoArea.Right - healthBarWidth + (int)Math.Floor(1 / GUI.Scale), BottomRightInfoArea.Y - healthBarHeight + GUI.IntScale(10), healthBarWidth, healthBarHeight);
AfflictionAreaLeft = new Rectangle(HealthBarArea.X, HealthBarArea.Y - Padding - afflictionAreaHeight, HealthBarArea.Width, afflictionAreaHeight); HealthBarAfflictionArea = new Rectangle(HealthBarArea.X, HealthBarArea.Y - Padding - afflictionAreaHeight, HealthBarArea.Width, afflictionAreaHeight);
int messageAreaWidth = GameMain.GraphicsWidth / 3; int messageAreaWidth = GameMain.GraphicsWidth / 3;
@@ -173,7 +173,7 @@ namespace Barotrauma
int objectiveListAreaX = HealthWindowAreaLeft.Right + Padding; int objectiveListAreaX = HealthWindowAreaLeft.Right + Padding;
int objectiveListAreaY = ButtonAreaTop.Bottom + Padding; int objectiveListAreaY = ButtonAreaTop.Bottom + Padding;
TutorialObjectiveListArea = new Rectangle(objectiveListAreaX, objectiveListAreaY, (GameMain.GraphicsWidth - Padding) - objectiveListAreaX, (AfflictionAreaLeft.Top - Padding) - objectiveListAreaY); TutorialObjectiveListArea = new Rectangle(objectiveListAreaX, objectiveListAreaY, (GameMain.GraphicsWidth - Padding) - objectiveListAreaX, (HealthBarAfflictionArea.Top - Padding) - objectiveListAreaY);
int votingAreaWidth = (int)(400 * GUI.Scale); int votingAreaWidth = (int)(400 * GUI.Scale);
int votingAreaX = GameMain.GraphicsWidth - Padding - votingAreaWidth; int votingAreaX = GameMain.GraphicsWidth - Padding - votingAreaWidth;
@@ -193,7 +193,7 @@ namespace Barotrauma
DrawRectangle(CrewArea, Color.Blue * 0.5f); DrawRectangle(CrewArea, Color.Blue * 0.5f);
DrawRectangle(ChatBoxArea, Color.Cyan * 0.5f); DrawRectangle(ChatBoxArea, Color.Cyan * 0.5f);
DrawRectangle(HealthBarArea, Color.Red * 0.5f); DrawRectangle(HealthBarArea, Color.Red * 0.5f);
DrawRectangle(AfflictionAreaLeft, Color.Red * 0.5f); DrawRectangle(HealthBarAfflictionArea, Color.Red * 0.5f);
DrawRectangle(InventoryAreaLower, Color.Yellow * 0.5f); DrawRectangle(InventoryAreaLower, Color.Yellow * 0.5f);
DrawRectangle(HealthWindowAreaLeft, Color.Red * 0.5f); DrawRectangle(HealthWindowAreaLeft, Color.Red * 0.5f);
DrawRectangle(BottomRightInfoArea, Color.Green * 0.5f); DrawRectangle(BottomRightInfoArea, Color.Green * 0.5f);
@@ -0,0 +1,54 @@
#nullable enable
using Microsoft.Xna.Framework;
namespace Barotrauma
{
public sealed class IMEPreviewTextHandler
{
public string PreviewText { get; private set; } = string.Empty;
public Vector2 TextSize { get; private set; }
public bool HasText => !string.IsNullOrEmpty(PreviewText);
// This has to be settable because for some reason we update the font of GUITextBox in some places
public GUIFont Font { get; set; }
public IMEPreviewTextHandler(GUIFont font)
{
Font = font;
}
public void Reset()
{
TextSize = Vector2.Zero;
PreviewText = string.Empty;
}
public void UpdateText(string text, int start)
{
if (string.IsNullOrEmpty(text) && start is 0)
{
Reset();
return;
}
int totalLength = start + text.Length;
string newText = PreviewText;
if (newText.Length > totalLength)
{
newText = newText[..totalLength];
}
if (totalLength > newText.Length)
{
// this is required for some reason on Windows
// my guess is that the order which TextEditing events come thru is not guaranteed
newText = newText.PadRight(totalLength);
}
newText = newText.Remove(start, text.Length).Insert(start, text);
PreviewText = newText;
TextSize = Font.MeasureString(PreviewText);
}
}
}
@@ -12,7 +12,7 @@ using PlayerBalanceElement = Barotrauma.CampaignUI.PlayerBalanceElement;
namespace Barotrauma namespace Barotrauma
{ {
[SuppressMessage("ReSharper", "UnusedVariable")] [SuppressMessage("ReSharper", "UnusedVariable")]
internal class MedicalClinicUI internal sealed class MedicalClinicUI
{ {
private enum ElementState private enum ElementState
{ {
@@ -127,12 +127,14 @@ namespace Barotrauma
{ {
public readonly GUIComponent Panel; public readonly GUIComponent Panel;
public readonly GUIListBox HealList; public readonly GUIListBox HealList;
public readonly GUIComponent TreatAllButton;
public readonly List<CrewElement> HealElements; public readonly List<CrewElement> HealElements;
public CrewHealList(GUIListBox healList, GUIComponent panel) public CrewHealList(GUIListBox healList, GUIComponent panel, GUIComponent treatAllButton)
{ {
Panel = panel; Panel = panel;
HealList = healList; HealList = healList;
TreatAllButton = treatAllButton;
HealElements = new List<CrewElement>(); HealElements = new List<CrewElement>();
} }
} }
@@ -179,7 +181,7 @@ namespace Barotrauma
private PopupAfflictionList? selectedCrewAfflictionList; private PopupAfflictionList? selectedCrewAfflictionList;
private bool isWaitingForServer; private bool isWaitingForServer;
private const float refreshTimerMax = 3f; private const float refreshTimerMax = 3f;
private float refreshTimer = 0; private float refreshTimer;
private PlayerBalanceElement? playerBalanceElement; private PlayerBalanceElement? playerBalanceElement;
@@ -196,7 +198,7 @@ namespace Barotrauma
{ {
new GUIButton(new RectTransform(new Vector2(0.2f, 0.1f), parent.RectTransform, Anchor.TopCenter), "Recreate UI - NOT PRESENT IN RELEASE!") new GUIButton(new RectTransform(new Vector2(0.2f, 0.1f), parent.RectTransform, Anchor.TopCenter), "Recreate UI - NOT PRESENT IN RELEASE!")
{ {
OnClicked = (_, __) => OnClicked = (_, _) =>
{ {
parent.ClearChildren(); parent.ClearChildren();
CreateUI(); CreateUI();
@@ -254,7 +256,7 @@ namespace Barotrauma
continue; continue;
} }
CreatePendingHealElement(healList.HealList.Content, crewMember, healList, Array.Empty<MedicalClinic.NetAffliction>()); CreatePendingHealElement(healList.HealList.Content, crewMember, healList, ImmutableArray<MedicalClinic.NetAffliction>.Empty);
} }
// check if there are elements that the crew doesn't have // check if there are elements that the crew doesn't have
@@ -309,7 +311,7 @@ namespace Barotrauma
private void UpdateCrewPanel() private void UpdateCrewPanel()
{ {
if (!(crewHealList is { } healList)) { return; } if (crewHealList is not { } healList) { return; }
ImmutableArray<CharacterInfo> crew = MedicalClinic.GetCrewCharacters(); ImmutableArray<CharacterInfo> crew = MedicalClinic.GetCrewCharacters();
@@ -334,12 +336,21 @@ namespace Barotrauma
healList.HealList.Content.RemoveChild(element.UIElement); healList.HealList.Content.RemoveChild(element.UIElement);
} }
IEnumerable<CrewElement> orderedList = healList.HealElements.OrderBy(element => element.Target.Character?.HealthPercentage ?? 100); IEnumerable<CrewElement> orderedList = healList.HealElements.OrderBy(static element => element.Target.Character?.HealthPercentage ?? 100);
foreach (CrewElement element in orderedList) foreach (CrewElement element in orderedList)
{ {
element.UIElement.SetAsLastChild(); element.UIElement.SetAsLastChild();
} }
healList.TreatAllButton.Enabled = false;
foreach (CrewElement element in healList.HealElements)
{
if (element.Afflictions.Count is 0) { continue; }
healList.TreatAllButton.Enabled = true;
break;
}
} }
private static void UpdateAfflictionList(CrewElement healElement) private static void UpdateAfflictionList(CrewElement healElement)
@@ -350,7 +361,7 @@ namespace Barotrauma
// sum up all the afflictions and their strengths // sum up all the afflictions and their strengths
Dictionary<AfflictionPrefab, float> afflictionAndStrength = new Dictionary<AfflictionPrefab, float>(); Dictionary<AfflictionPrefab, float> afflictionAndStrength = new Dictionary<AfflictionPrefab, float>();
foreach (Affliction affliction in health.GetAllAfflictions().Where(a => MedicalClinic.IsHealable(a))) foreach (Affliction affliction in health.GetAllAfflictions().Where(MedicalClinic.IsHealable))
{ {
if (afflictionAndStrength.TryGetValue(affliction.Prefab, out float strength)) if (afflictionAndStrength.TryGetValue(affliction.Prefab, out float strength))
{ {
@@ -446,8 +457,8 @@ namespace Barotrauma
}; };
GUILayoutGroup clinicLabelLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.1f), clinicContent.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft); GUILayoutGroup clinicLabelLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.1f), clinicContent.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft);
GUIImage clinicIcon = new GUIImage(new RectTransform(Vector2.One, clinicLabelLayout.RectTransform, scaleBasis: ScaleBasis.BothHeight), style: "CrewManagementHeaderIcon", scaleToFit: true); new GUIImage(new RectTransform(Vector2.One, clinicLabelLayout.RectTransform, scaleBasis: ScaleBasis.BothHeight), style: "CrewManagementHeaderIcon", scaleToFit: true);
GUITextBlock clinicLabel = new GUITextBlock(new RectTransform(Vector2.One, clinicLabelLayout.RectTransform), TextManager.Get("medicalclinic.medicalclinic"), font: GUIStyle.LargeFont); new GUITextBlock(new RectTransform(Vector2.One, clinicLabelLayout.RectTransform), TextManager.Get("medicalclinic.medicalclinic"), font: GUIStyle.LargeFont);
GUIFrame clinicBackground = new GUIFrame(new RectTransform(Vector2.One, clinicContent.RectTransform)); GUIFrame clinicBackground = new GUIFrame(new RectTransform(Vector2.One, clinicContent.RectTransform));
@@ -480,22 +491,24 @@ namespace Barotrauma
Stretch = true Stretch = true
}; };
// GUILayoutGroup sortLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.05f), clinicContainer.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft);
// new GUITextBlock(new RectTransform(new Vector2(0.2f, 1f), sortLayout.RectTransform), TextManager.Get("campaignstore.sortby"), font: GUI.SubHeadingFont);
// GUIDropDown sortDropdown = new GUIDropDown(new RectTransform(new Vector2(0.3f, 1f), sortLayout.RectTransform));
//
// foreach (SortMode mode in Enum.GetValues(typeof(SortMode)).Cast<SortMode>())
// {
// sortDropdown.AddItem(TextManager.Get($"medicalclinic.sortmode.{mode}"), mode);
// }
//
// sortDropdown.SelectItem(SortMode.Severity);
GUIListBox crewList = new GUIListBox(new RectTransform(Vector2.One, clinicContainer.RectTransform)); GUIListBox crewList = new GUIListBox(new RectTransform(Vector2.One, clinicContainer.RectTransform));
crewHealList = new CrewHealList(crewList, parent); GUIButton treatAllButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.05f), clinicContainer.RectTransform), TextManager.Get("medicalclinic.treateveryone"))
{
OnClicked = (_, _) =>
{
isWaitingForServer = true;
medicalClinic.TreatAllButtonAction(OnReceived);
return true;
}
};
crewHealList = new CrewHealList(crewList, parent, treatAllButton);
void OnReceived(MedicalClinic.CallbackOnlyRequest obj)
{
isWaitingForServer = false;
}
} }
private void CreateCrewEntry(GUIComponent parent, CrewHealList healList, CharacterInfo info, GUIComponent panel) private void CreateCrewEntry(GUIComponent parent, CrewHealList healList, CharacterInfo info, GUIComponent panel)
@@ -525,9 +538,9 @@ namespace Barotrauma
TextColor = GUIStyle.Red TextColor = GUIStyle.Red
}; };
MedicalClinic.NetCrewMember member = new MedicalClinic.NetCrewMember { CharacterInfo = info, Afflictions = Array.Empty<MedicalClinic.NetAffliction>() }; MedicalClinic.NetCrewMember member = new MedicalClinic.NetCrewMember(info);
crewBackground.OnClicked = (_, __) => crewBackground.OnClicked = (_, _) =>
{ {
SelectCharacter(member, new Vector2(panel.Rect.Right, crewBackground.Rect.Top)); SelectCharacter(member, new Vector2(panel.Rect.Right, crewBackground.Rect.Top));
return true; return true;
@@ -618,7 +631,7 @@ namespace Barotrauma
pendingHealList = list; pendingHealList = list;
} }
private void CreatePendingHealElement(GUIComponent parent, MedicalClinic.NetCrewMember crewMember, PendingHealList healList, MedicalClinic.NetAffliction[] afflictions) private void CreatePendingHealElement(GUIComponent parent, MedicalClinic.NetCrewMember crewMember, PendingHealList healList, ImmutableArray<MedicalClinic.NetAffliction> afflictions)
{ {
CharacterInfo? healInfo = crewMember.FindCharacterInfo(MedicalClinic.GetCrewCharacters()); CharacterInfo? healInfo = crewMember.FindCharacterInfo(MedicalClinic.GetCrewCharacters());
if (healInfo is null) { return; } if (healInfo is null) { return; }
@@ -803,7 +816,7 @@ namespace Barotrauma
} }
allComponents.Add(treatAllButton); allComponents.Add(treatAllButton);
treatAllButton.OnClicked = (_, __) => treatAllButton.OnClicked = (_, _) =>
{ {
ImmutableArray<MedicalClinic.NetAffliction> afflictions = request.Afflictions.Where(a => !medicalClinic.IsAfflictionPending(crewMember, a)).ToImmutableArray(); ImmutableArray<MedicalClinic.NetAffliction> afflictions = request.Afflictions.Where(a => !medicalClinic.IsAfflictionPending(crewMember, a)).ToImmutableArray();
if (!afflictions.Any()) { return true; } if (!afflictions.Any()) { return true; }
@@ -873,9 +886,13 @@ namespace Barotrauma
{ {
RelativeSpacing = 0.05f RelativeSpacing = 0.05f
}; };
GUITextBlock descriptionBlock = new GUITextBlock(new RectTransform(new Vector2(1f, 0.6f), bottomTextLayout.RectTransform), prefab.Description, font: GUIStyle.SmallFont, wrap: true) LocalizedString description = affliction.Prefab.GetDescription(affliction.Strength, AfflictionPrefab.Description.TargetType.OtherCharacter);
GUITextBlock descriptionBlock = new GUITextBlock(new RectTransform(new Vector2(1f, 0.6f), bottomTextLayout.RectTransform),
description,
font: GUIStyle.SmallFont,
wrap: true)
{ {
ToolTip = prefab.Description ToolTip = description
}; };
bool truncated = false; bool truncated = false;
while (descriptionBlock.TextSize.Y > descriptionBlock.Rect.Height && descriptionBlock.WrappedText.Contains('\n')) while (descriptionBlock.TextSize.Y > descriptionBlock.Rect.Height && descriptionBlock.WrappedText.Contains('\n'))
@@ -919,10 +936,9 @@ namespace Barotrauma
} }
else else
{ {
MedicalClinic.NetCrewMember newMember = new MedicalClinic.NetCrewMember MedicalClinic.NetCrewMember newMember = crewMember with
{ {
CharacterInfoID = crewMember.CharacterInfoID, Afflictions = ImmutableArray<MedicalClinic.NetAffliction>.Empty
Afflictions = Array.Empty<MedicalClinic.NetAffliction>()
}; };
existingMember = newMember; existingMember = newMember;
@@ -936,7 +952,7 @@ namespace Barotrauma
} }
} }
existingMember.Afflictions = existingMember.Afflictions.Concat(afflictions).ToArray(); existingMember.Afflictions = existingMember.Afflictions.Concat(afflictions).ToImmutableArray();
ToggleElements(ElementState.Disabled, elementsToDisable); ToggleElements(ElementState.Disabled, elementsToDisable);
medicalClinic.AddPendingButtonAction(existingMember, request => medicalClinic.AddPendingButtonAction(existingMember, request =>
{ {
@@ -745,7 +745,7 @@ namespace Barotrauma
} ?? Enumerable.Empty<PurchasedItem>(); } ?? Enumerable.Empty<PurchasedItem>();
foreach (var button in itemCategoryButtons) foreach (var button in itemCategoryButtons)
{ {
if (!(button.UserData is MapEntityCategory category)) if (button.UserData is not MapEntityCategory category)
{ {
continue; continue;
} }
@@ -1110,7 +1110,7 @@ namespace Barotrauma
private void SetPriceGetters(GUIComponent itemFrame, bool buying) private void SetPriceGetters(GUIComponent itemFrame, bool buying)
{ {
if (itemFrame == null || !(itemFrame.UserData is PurchasedItem pi)) { return; } if (itemFrame == null || itemFrame.UserData is not PurchasedItem pi) { return; }
if (itemFrame.FindChild("undiscountedprice", recursive: true) is GUITextBlock undiscountedPriceBlock) if (itemFrame.FindChild("undiscountedprice", recursive: true) is GUITextBlock undiscountedPriceBlock)
{ {
@@ -1667,8 +1667,8 @@ namespace Barotrauma
{ {
foreach (var subItem in subItems) foreach (var subItem in subItems)
{ {
if (!subItem.Components.All(c => !(c is Holdable h) || !h.Attachable || !h.Attached)) { continue; } if (!subItem.Components.All(c => c is not Holdable h || !h.Attachable || !h.Attached)) { continue; }
if (!subItem.Components.All(c => !(c is Wire w) || w.Connections.All(c => c == null))) { continue; } if (!subItem.Components.All(c => c is not Wire w || w.Connections.All(c => c == null))) { continue; }
if (!ItemAndAllContainersInteractable(subItem)) { continue; } if (!ItemAndAllContainersInteractable(subItem)) { continue; }
AddOwnedItem(subItem); AddOwnedItem(subItem);
} }
@@ -1701,7 +1701,7 @@ namespace Barotrauma
void AddOwnedItem(Item item) void AddOwnedItem(Item item)
{ {
if (!(item?.Prefab.GetPriceInfo(ActiveStore) is PriceInfo priceInfo)) { return; } if (item?.Prefab.GetPriceInfo(ActiveStore) is not PriceInfo priceInfo) { return; }
bool isNonEmpty = !priceInfo.DisplayNonEmpty || item.ConditionPercentage > 5.0f; bool isNonEmpty = !priceInfo.DisplayNonEmpty || item.ConditionPercentage > 5.0f;
if (OwnedItems.TryGetValue(item.Prefab, out ItemQuantity itemQuantity)) if (OwnedItems.TryGetValue(item.Prefab, out ItemQuantity itemQuantity))
{ {
@@ -1729,7 +1729,7 @@ namespace Barotrauma
private void SetItemFrameStatus(GUIComponent itemFrame, bool enabled) private void SetItemFrameStatus(GUIComponent itemFrame, bool enabled)
{ {
if (!(itemFrame?.UserData is PurchasedItem pi)) { return; } if (itemFrame?.UserData is not PurchasedItem pi) { return; }
bool refreshFrameStatus = !pi.IsStoreComponentEnabled.HasValue || pi.IsStoreComponentEnabled.Value != enabled; bool refreshFrameStatus = !pi.IsStoreComponentEnabled.HasValue || pi.IsStoreComponentEnabled.Value != enabled;
if (!refreshFrameStatus) { return; } if (!refreshFrameStatus) { return; }
if (itemFrame.FindChild("icon", recursive: true) is GUIImage icon) if (itemFrame.FindChild("icon", recursive: true) is GUIImage icon)
@@ -1841,11 +1841,7 @@ namespace Barotrauma
LocalizedString toolTip = string.Empty; LocalizedString toolTip = string.Empty;
if (purchasedItem.ItemPrefab != null) if (purchasedItem.ItemPrefab != null)
{ {
toolTip = purchasedItem.ItemPrefab.Name; toolTip = purchasedItem.ItemPrefab.GetTooltip();
if (!purchasedItem.ItemPrefab.Description.IsNullOrEmpty())
{
toolTip += $"\n{purchasedItem.ItemPrefab.Description}";
}
if (itemQuantity != null) if (itemQuantity != null)
{ {
if (itemQuantity.AllNonEmpty) if (itemQuantity.AllNonEmpty)
@@ -1859,7 +1855,7 @@ namespace Barotrauma
} }
} }
} }
itemComponent.ToolTip = toolTip; itemComponent.ToolTip = RichString.Rich(toolTip);
} }
if (ownedLabel != null) if (ownedLabel != null)
{ {
@@ -2181,7 +2177,7 @@ namespace Barotrauma
{ {
needsRefresh = itemsToSellFromSub.Count != prevSubItems.Count || needsRefresh = itemsToSellFromSub.Count != prevSubItems.Count ||
itemsToSellFromSub.Sum(i => i.Quantity) != prevSubItems.Sum(i => i.Quantity) || itemsToSellFromSub.Sum(i => i.Quantity) != prevSubItems.Sum(i => i.Quantity) ||
itemsToSellFromSub.Any(i => !(prevSubItems.FirstOrDefault(prev => prev.ItemPrefab == i.ItemPrefab) is PurchasedItem prev) || i.Quantity != prev.Quantity) || itemsToSellFromSub.Any(i => prevSubItems.FirstOrDefault(prev => prev.ItemPrefab == i.ItemPrefab) is not PurchasedItem prev || i.Quantity != prev.Quantity) ||
prevSubItems.Any(prev => itemsToSellFromSub.None(i => i.ItemPrefab == prev.ItemPrefab)); prevSubItems.Any(prev => itemsToSellFromSub.None(i => i.ItemPrefab == prev.ItemPrefab));
} }
} }
@@ -472,7 +472,7 @@ namespace Barotrauma
if (transferService) if (transferService)
{ {
subsToShow.AddRange(GameMain.GameSession.OwnedSubmarines); subsToShow.AddRange(GameMain.GameSession.OwnedSubmarines);
subsToShow.Sort((x, y) => x.SubmarineClass.CompareTo(y.SubmarineClass)); subsToShow.Sort(ComparePrice);
string currentSubName = CurrentOrPendingSubmarine().Name; string currentSubName = CurrentOrPendingSubmarine().Name;
int currentIndex = subsToShow.FindIndex(s => s.Name == currentSubName); int currentIndex = subsToShow.FindIndex(s => s.Name == currentSubName);
if (currentIndex != -1) if (currentIndex != -1)
@@ -484,7 +484,11 @@ namespace Barotrauma
{ {
subsToShow.AddRange((GameMain.Client is null ? SubmarineInfo.SavedSubmarines : MultiPlayerCampaign.GetCampaignSubs()) subsToShow.AddRange((GameMain.Client is null ? SubmarineInfo.SavedSubmarines : MultiPlayerCampaign.GetCampaignSubs())
.Where(s => s.IsCampaignCompatible && !GameMain.GameSession.OwnedSubmarines.Any(os => os.Name == s.Name))); .Where(s => s.IsCampaignCompatible && !GameMain.GameSession.OwnedSubmarines.Any(os => os.Name == s.Name)));
subsToShow.Sort((x, y) => x.SubmarineClass.CompareTo(y.SubmarineClass)); if (GameMain.GameSession.Campaign?.Map?.CurrentLocation is Location currentLocation)
{
subsToShow.RemoveAll(sub => !currentLocation.IsSubmarineAvailable(sub));
}
subsToShow.Sort(ComparePrice);
} }
if (transferService) if (transferService)
@@ -492,10 +496,14 @@ namespace Barotrauma
SetConfirmButtonState(selectedSubmarine != null && selectedSubmarine.Name != CurrentOrPendingSubmarine().Name); SetConfirmButtonState(selectedSubmarine != null && selectedSubmarine.Name != CurrentOrPendingSubmarine().Name);
} }
subsToShow.Sort((x, y) => x.SubmarineClass.CompareTo(y.SubmarineClass));
pageCount = Math.Max(1, (int)Math.Ceiling(subsToShow.Count / (float)submarinesPerPage)); pageCount = Math.Max(1, (int)Math.Ceiling(subsToShow.Count / (float)submarinesPerPage));
UpdatePaging(); UpdatePaging();
ContentRefreshRequired = false; ContentRefreshRequired = false;
static int ComparePrice(SubmarineInfo x, SubmarineInfo y)
{
return x.Price.CompareTo(y.Price) * 100 + x.Name.CompareTo(y.Name);
}
} }
private SubmarineInfo GetSubToDisplay(int index) private SubmarineInfo GetSubToDisplay(int index)
@@ -208,10 +208,7 @@ namespace Barotrauma
} }
GameSession.UpdateTalentNotificationIndicator(talentPointNotification); GameSession.UpdateTalentNotificationIndicator(talentPointNotification);
if (SelectedTab is InfoFrameTab.Talents) talentMenu?.Update();
{
talentMenu?.Update();
}
if (SelectedTab != InfoFrameTab.Crew) { return; } if (SelectedTab != InfoFrameTab.Crew) { return; }
if (linkedGUIList == null) { return; } if (linkedGUIList == null) { return; }
@@ -248,10 +245,6 @@ namespace Barotrauma
{ {
infoFrame?.AddToGUIUpdateList(); infoFrame?.AddToGUIUpdateList();
NetLobbyScreen.JobInfoFrame?.AddToGUIUpdateList(); NetLobbyScreen.JobInfoFrame?.AddToGUIUpdateList();
if (SelectedTab is InfoFrameTab.Talents)
{
talentMenu?.AddToGUIUpdateList();
}
} }
public static void OnRoundEnded() public static void OnRoundEnded()
@@ -404,7 +397,7 @@ namespace Barotrauma
CreateSubmarineInfo(infoFrameHolder, Submarine.MainSub); CreateSubmarineInfo(infoFrameHolder, Submarine.MainSub);
break; break;
case InfoFrameTab.Talents: case InfoFrameTab.Talents:
talentMenu.CreateGUI(infoFrameHolder); talentMenu.CreateGUI(infoFrameHolder, Character.Controlled ?? GameMain.Client?.Character);
break; break;
} }
} }
@@ -958,16 +951,26 @@ namespace Barotrauma
if (character != null) if (character != null)
{ {
if (GameMain.Client == null) if (GameMain.Client is null)
{ {
GUIComponent preview = character.Info.CreateInfoFrame(background, false, null); GUIComponent preview = character.Info.CreateInfoFrame(background, false, null);
} }
else else
{ {
GUIComponent preview = character.Info.CreateInfoFrame(background, false, GetPermissionIcon(GameMain.Client.ConnectedClients.Find(c => c.Character == character))); GUIComponent preview = character.Info.CreateInfoFrame(background, false, GetPermissionIcon(GameMain.Client.ConnectedClients.Find(c => c.Character == character)));
GameMain.Client.SelectCrewCharacter(character, preview); GameMain.Client.SelectCrewCharacter(character, preview);
if (!character.IsBot && GameMain.GameSession?.Campaign is MultiPlayerCampaign mpCampaign) { CreateWalletFrame(background, character, mpCampaign); } if (!character.IsBot && GameMain.GameSession?.Campaign is MultiPlayerCampaign mpCampaign) { CreateWalletFrame(background, character, mpCampaign); }
} }
if (background.FindChild(TalentMenu.ManageBotTalentsButtonUserData, recursive: true) is GUIButton { Enabled: true } talentButton)
{
talentButton.OnClicked = (button, o) =>
{
talentMenu.CreateGUI(infoFrameHolder, character);
return true;
};
}
} }
else if (client != null) else if (client != null)
{ {
@@ -1792,10 +1795,10 @@ namespace Barotrauma
new GUITextBlock(new RectTransform(new Vector2(0.15f, 1.0f), skillContainer.RectTransform), Math.Floor(skill.Level).ToString("F0"), textAlignment: Alignment.TopRight); new GUITextBlock(new RectTransform(new Vector2(0.15f, 1.0f), skillContainer.RectTransform), Math.Floor(skill.Level).ToString("F0"), textAlignment: Alignment.TopRight);
float modifiedSkillLevel = character?.GetSkillLevel(skill.Identifier) ?? skill.Level; float modifiedSkillLevel = MathF.Floor(character?.GetSkillLevel(skill.Identifier) ?? skill.Level);
if (!MathUtils.NearlyEqual(MathF.Floor(modifiedSkillLevel), MathF.Floor(skill.Level))) if (!MathUtils.NearlyEqual(MathF.Floor(modifiedSkillLevel), MathF.Floor(skill.Level)))
{ {
int skillChange = (int)MathF.Floor(modifiedSkillLevel - skill.Level); int skillChange = (int)MathF.Floor(modifiedSkillLevel - MathF.Floor(skill.Level));
string stringColor = skillChange switch string stringColor = skillChange switch
{ {
> 0 => XMLExtensions.ToStringHex(GUIStyle.Green), > 0 => XMLExtensions.ToStringHex(GUIStyle.Green),
@@ -1806,7 +1809,7 @@ namespace Barotrauma
RichString changeText = RichString.Rich($"(‖color:{stringColor}‖{(skillChange > 0 ? "+" : string.Empty) + skillChange}‖color:end‖)"); RichString changeText = RichString.Rich($"(‖color:{stringColor}‖{(skillChange > 0 ? "+" : string.Empty) + skillChange}‖color:end‖)");
new GUITextBlock(new RectTransform(new Vector2(0.15f, 1.0f), skillContainer.RectTransform), changeText) { Padding = Vector4.Zero }; new GUITextBlock(new RectTransform(new Vector2(0.15f, 1.0f), skillContainer.RectTransform), changeText) { Padding = Vector4.Zero };
} }
//skillContainer.Recalculate(); skillContainer.Recalculate();
} }
parent.RecalculateChildren(); parent.RecalculateChildren();
@@ -5,15 +5,18 @@ using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Linq; using System.Linq;
using Barotrauma.Extensions; using Barotrauma.Extensions;
using Barotrauma.Networking;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Input;
using static Barotrauma.TalentTree; using static Barotrauma.TalentTree;
using static Barotrauma.TalentTree.TalentTreeStageState; using static Barotrauma.TalentTree.TalentStages;
namespace Barotrauma namespace Barotrauma
{ {
internal readonly record struct TalentButton(GUIButton Button, internal readonly record struct TalentShowCaseButton(ImmutableHashSet<TalentButton> Buttons,
GUIComponent IconComponent, GUIComponent IconComponent);
internal readonly record struct TalentButton(GUIComponent IconComponent,
TalentPrefab Prefab) TalentPrefab Prefab)
{ {
public Identifier Identifier => Prefab.Identifier; public Identifier Identifier => Prefab.Identifier;
@@ -39,6 +42,11 @@ namespace Barotrauma
internal sealed class TalentMenu internal sealed class TalentMenu
{ {
public const string ManageBotTalentsButtonUserData = "managebottalentsbutton";
private Character? character;
private CharacterInfo? characterInfo;
private static readonly Color unselectedColor = new Color(240, 255, 255, 225), private static readonly Color unselectedColor = new Color(240, 255, 255, 225),
unselectableColor = new Color(100, 100, 100, 225), unselectableColor = new Color(100, 100, 100, 225),
pressedColor = new Color(60, 60, 60, 225), pressedColor = new Color(60, 60, 60, 225),
@@ -46,8 +54,8 @@ namespace Barotrauma
unlockedColor = new Color(24, 37, 31, 255), unlockedColor = new Color(24, 37, 31, 255),
availableColor = new Color(50, 47, 33, 255); availableColor = new Color(50, 47, 33, 255);
private static readonly ImmutableDictionary<TalentTreeStageState, TalentTreeStyle> talentStageStyles = private static readonly ImmutableDictionary<TalentStages, TalentTreeStyle> talentStageStyles =
new Dictionary<TalentTreeStageState, TalentTreeStyle> new Dictionary<TalentStages, TalentTreeStyle>
{ {
[Invalid] = new TalentTreeStyle("TalentTreeLocked", lockedColor), [Invalid] = new TalentTreeStyle("TalentTreeLocked", lockedColor),
[Locked] = new TalentTreeStyle("TalentTreeLocked", lockedColor), [Locked] = new TalentTreeStyle("TalentTreeLocked", lockedColor),
@@ -57,10 +65,13 @@ namespace Barotrauma
}.ToImmutableDictionary(); }.ToImmutableDictionary();
private readonly HashSet<TalentButton> talentButtons = new HashSet<TalentButton>(); private readonly HashSet<TalentButton> talentButtons = new HashSet<TalentButton>();
private readonly HashSet<TalentShowCaseButton> talentShowCaseButtons = new HashSet<TalentShowCaseButton>();
private readonly HashSet<GUIComponent> showCaseTalentFrames = new HashSet<GUIComponent>(); private readonly HashSet<GUIComponent> showCaseTalentFrames = new HashSet<GUIComponent>();
private readonly HashSet<TalentCornerIcon> talentCornerIcons = new HashSet<TalentCornerIcon>(); private readonly HashSet<TalentCornerIcon> talentCornerIcons = new HashSet<TalentCornerIcon>();
private HashSet<Identifier> selectedTalents = new HashSet<Identifier>(); private HashSet<Identifier> selectedTalents = new HashSet<Identifier>();
private readonly Queue<Identifier> showCaseClosureQueue = new();
private GUIListBox? skillListBox; private GUIListBox? skillListBox;
private GUITextBlock? talentPointText; private GUITextBlock? talentPointText;
private GUIProgressBar? experienceBar; private GUIProgressBar? experienceBar;
@@ -70,13 +81,17 @@ namespace Barotrauma
private GUIButton? talentApplyButton, private GUIButton? talentApplyButton,
talentResetButton; talentResetButton;
public void CreateGUI(GUIFrame parent) public void CreateGUI(GUIFrame parent, Character? targetCharacter)
{ {
parent.ClearChildren(); parent.ClearChildren();
talentButtons.Clear(); talentButtons.Clear();
talentShowCaseButtons.Clear();
talentCornerIcons.Clear(); talentCornerIcons.Clear();
showCaseTalentFrames.Clear(); showCaseTalentFrames.Clear();
character = targetCharacter;
characterInfo = targetCharacter?.Info;
GUIFrame background = new GUIFrame(new RectTransform(Vector2.One, parent.RectTransform, Anchor.TopCenter), style: "GUIFrameListBox"); GUIFrame background = new GUIFrame(new RectTransform(Vector2.One, parent.RectTransform, Anchor.TopCenter), style: "GUIFrameListBox");
int padding = GUI.IntScale(15); int padding = GUI.IntScale(15);
GUIFrame frame = new GUIFrame(new RectTransform(new Point(background.Rect.Width - padding, background.Rect.Height - padding), parent.RectTransform, Anchor.Center), style: null); GUIFrame frame = new GUIFrame(new RectTransform(new Point(background.Rect.Width - padding, background.Rect.Height - padding), parent.RectTransform, Anchor.Center), style: null);
@@ -89,23 +104,21 @@ namespace Barotrauma
Stretch = true Stretch = true
}; };
Character? controlledCharacter = Character.Controlled; if (characterInfo is null) { return; }
CharacterInfo? info = controlledCharacter?.Info ?? GameMain.Client?.CharacterInfo;
if (info is null) { return; }
CreateStatPanel(contentLayout, info); CreateStatPanel(contentLayout, characterInfo);
new GUIFrame(new RectTransform(new Vector2(1f, 1f), contentLayout.RectTransform), style: "HorizontalLine"); new GUIFrame(new RectTransform(new Vector2(1f, 1f), contentLayout.RectTransform), style: "HorizontalLine");
if (JobTalentTrees.TryGet(info.Job.Prefab.Identifier, out TalentTree? talentTree)) if (JobTalentTrees.TryGet(characterInfo.Job.Prefab.Identifier, out TalentTree? talentTree))
{ {
CreateTalentMenu(contentLayout, info, talentTree!); CreateTalentMenu(contentLayout, characterInfo, talentTree!);
} }
CreateFooter(contentLayout, info); CreateFooter(contentLayout, characterInfo);
UpdateTalentInfo(); UpdateTalentInfo();
if (GameMain.NetworkMember != null) if (GameMain.NetworkMember != null && IsOwnCharacter(characterInfo))
{ {
CreateMultiplayerCharacterSettings(frame, content); CreateMultiplayerCharacterSettings(frame, content);
} }
@@ -164,7 +177,7 @@ namespace Barotrauma
OnClicked = (button, o) => OnClicked = (button, o) =>
{ {
GameMain.Client?.SendCharacterInfo(GameMain.Client.PendingName); GameMain.Client?.SendCharacterInfo(GameMain.Client.PendingName);
characterSettingsFrame!.Visible = false; characterSettingsFrame.Visible = false;
content.Visible = true; content.Visible = true;
return true; return true;
} }
@@ -179,8 +192,7 @@ namespace Barotrauma
new GUICustomComponent(new RectTransform(new Vector2(0.25f, 1f), topLayout.RectTransform), onDraw: (batch, component) => new GUICustomComponent(new RectTransform(new Vector2(0.25f, 1f), topLayout.RectTransform), onDraw: (batch, component) =>
{ {
float posY = component.Rect.Center.Y - component.Rect.Width / 2; info.DrawPortrait(batch, component.Rect.Location.ToVector2(), Vector2.Zero, component.Rect.Width, false, false);
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.3f, 1f), topLayout.RectTransform)) GUILayoutGroup nameLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.3f, 1f), topLayout.RectTransform))
@@ -209,20 +221,22 @@ namespace Barotrauma
if (talentsOutsideTree.Any()) if (talentsOutsideTree.Any())
{ {
//spacing //spacing
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.05f), nameLayout.RectTransform), style: null); new GUIFrame(new RectTransform(new Vector2(1.0f, 0.01f), nameLayout.RectTransform), style: null);
GUILayoutGroup extraTalentLayout = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.3f), nameLayout.RectTransform), childAnchor: Anchor.TopCenter); GUILayoutGroup extraTalentLayout = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.55f), nameLayout.RectTransform), childAnchor: Anchor.TopCenter);
talentPointText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), extraTalentLayout.RectTransform, anchor: Anchor.Center), TextManager.Get("talentmenu.extratalents"), font: GUIStyle.SubHeadingFont); talentPointText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.3f), extraTalentLayout.RectTransform, anchor: Anchor.Center), TextManager.Get("talentmenu.extratalents"), font: GUIStyle.SubHeadingFont)
{
AutoScaleVertical = true
};
talentPointText.RectTransform.MaxSize = new Point(int.MaxValue, (int)talentPointText.TextSize.Y); talentPointText.RectTransform.MaxSize = new Point(int.MaxValue, (int)talentPointText.TextSize.Y);
var extraTalentList = new GUIListBox(new RectTransform(new Vector2(0.9f, 0.8f), extraTalentLayout.RectTransform, anchor: Anchor.Center), isHorizontal: true) var extraTalentList = new GUIListBox(new RectTransform(new Vector2(0.9f, 0.7f), extraTalentLayout.RectTransform, anchor: Anchor.Center), isHorizontal: true)
{ {
AutoHideScrollBar = false, AutoHideScrollBar = false,
ResizeContentToMakeSpaceForScrollBar = false ResizeContentToMakeSpaceForScrollBar = false
}; };
extraTalentList.ScrollBar.RectTransform.SetPosition(Anchor.BottomCenter, Pivot.TopCenter); extraTalentList.ScrollBar.RectTransform.SetPosition(Anchor.BottomCenter, Pivot.TopCenter);
extraTalentList.RectTransform.MinSize = new Point(0, GUI.IntScale(65));
extraTalentLayout.Recalculate(); extraTalentLayout.Recalculate();
extraTalentList.ForceLayoutRecalculation(); extraTalentList.ForceLayoutRecalculation();
@@ -231,7 +245,7 @@ namespace Barotrauma
if (extraTalent is null) { continue; } if (extraTalent is null) { continue; }
GUIImage talentImg = new GUIImage(new RectTransform(Vector2.One, extraTalentList.Content.RectTransform, scaleBasis: ScaleBasis.BothHeight), sprite: extraTalent.Icon, scaleToFit: true) GUIImage talentImg = new GUIImage(new RectTransform(Vector2.One, extraTalentList.Content.RectTransform, scaleBasis: ScaleBasis.BothHeight), sprite: extraTalent.Icon, scaleToFit: true)
{ {
ToolTip = RichString.Rich($"‖color:{Color.White.ToStringHex()}‖{extraTalent.DisplayName}‖color:end‖" + "\n\n" + extraTalent.Description), ToolTip = RichString.Rich($"‖color:{Color.White.ToStringHex()}‖{extraTalent.DisplayName}‖color:end‖" + "\n\n" + ToolBox.ExtendColorToPercentageSigns(extraTalent.Description.Value)),
Color = GUIStyle.Green Color = GUIStyle.Green
}; };
} }
@@ -302,6 +316,7 @@ namespace Barotrauma
subTreeLayoutGroup.Children.Sum(c => c.Rect.Height + subTreeLayoutGroup.AbsoluteSpacing))); subTreeLayoutGroup.Children.Sum(c => c.Rect.Height + subTreeLayoutGroup.AbsoluteSpacing)));
subTreeLayoutGroup.RectTransform.MinSize = new Point(subTreeLayoutGroup.Rect.Width, subTreeLayoutGroup.Rect.Height); subTreeLayoutGroup.RectTransform.MinSize = new Point(subTreeLayoutGroup.Rect.Width, subTreeLayoutGroup.Rect.Height);
subTreeLayoutGroup.Recalculate(); subTreeLayoutGroup.Recalculate();
if (subTree.Type == TalentTreeType.Specialization) if (subTree.Type == TalentTreeType.Specialization)
{ {
talentList.RectTransform.Resize(new Point(talentList.Rect.Width, Math.Max(subTreeLayoutGroup.Rect.Height, talentList.Rect.Height))); talentList.RectTransform.Resize(new Point(talentList.Rect.Width, Math.Max(subTreeLayoutGroup.Rect.Height, talentList.Rect.Height)));
@@ -310,15 +325,15 @@ namespace Barotrauma
} }
var specializationList = GetSpecializationList(); var specializationList = GetSpecializationList();
GetSpecializationList().RectTransform.Resize(new Point(specializationList.Content.Children.Sum(c => c.Rect.Width), specializationList.Rect.Height)); GetSpecializationList().RectTransform.Resize(new Point(specializationList.Content.Children.Sum(static c => c.Rect.Width), specializationList.Rect.Height));
GUITextBlock.AutoScaleAndNormalize(subTreeNames); GUITextBlock.AutoScaleAndNormalize(subTreeNames);
GUIListBox GetSpecializationList() GUIListBox GetSpecializationList()
{ {
if (mainList.Content.Children.LastOrDefault() is GUIListBox specializationList) if (mainList.Content.Children.LastOrDefault() is GUIListBox specList)
{ {
return specializationList; return specList;
} }
GUIListBox newSpecializationList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.5f), mainList.Content.RectTransform, Anchor.TopCenter), isHorizontal: true, style: null); GUIListBox newSpecializationList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.5f), mainList.Content.RectTransform, Anchor.TopCenter), isHorizontal: true, style: null);
@@ -354,21 +369,24 @@ namespace Barotrauma
GUILayoutGroup talentOptionLayoutGroup = new GUILayoutGroup(new RectTransform(Vector2.One, talentOptionCenterGroup.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft) { Stretch = true }; GUILayoutGroup talentOptionLayoutGroup = new GUILayoutGroup(new RectTransform(Vector2.One, talentOptionCenterGroup.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft) { Stretch = true };
HashSet<Identifier> talentOptionIdentifiers = talentOption.TalentIdentifiers.OrderBy(static t => t).ToHashSet(); HashSet<Identifier> talentOptionIdentifiers = talentOption.TalentIdentifiers.OrderBy(static t => t).ToHashSet();
bool hasShowcase = talentOption.ShowcaseTalent.TryUnwrap(out Identifier showcaseTalentIdentifier); HashSet<TalentButton> buttonsToAdd = new();
GUILayoutGroup showcaseLayout = talentOptionLayoutGroup;
if (hasShowcase) Dictionary<GUILayoutGroup, ImmutableHashSet<Identifier>> showCaseTalentParents = new();
Dictionary<Identifier, GUIComponent> showCaseTalentButtonsToAdd = new();
foreach (var (showCaseTalentIdentifier, talents) in talentOption.ShowCaseTalents)
{ {
talentOptionIdentifiers.Add(showcaseTalentIdentifier); talentOptionIdentifiers.Add(showCaseTalentIdentifier);
Point parentSize = talentBackground.RectTransform.NonScaledSize; Point parentSize = talentBackground.RectTransform.NonScaledSize;
GUIFrame showCaseFrame = new GUIFrame(new RectTransform(new Point((int)(parentSize.X / 3f * (talentOptionIdentifiers.Count - 1)), parentSize.Y)), style: "GUITooltip") GUIFrame showCaseFrame = new GUIFrame(new RectTransform(new Point((int)(parentSize.X / 3f * (talents.Count - 1)), parentSize.Y)), style: "GUITooltip")
{ {
UserData = showcaseTalentIdentifier, UserData = showCaseTalentIdentifier,
IgnoreLayoutGroups = true, IgnoreLayoutGroups = true,
Visible = false Visible = false
}; };
GUILayoutGroup showcaseCenterGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.7f), showCaseFrame.RectTransform, Anchor.Center), childAnchor: Anchor.CenterLeft); GUILayoutGroup showcaseCenterGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.7f), showCaseFrame.RectTransform, Anchor.Center), childAnchor: Anchor.CenterLeft);
showcaseLayout = new GUILayoutGroup(new RectTransform(Vector2.One, showcaseCenterGroup.RectTransform), isHorizontal: true) { Stretch = true }; GUILayoutGroup showcaseLayout = new GUILayoutGroup(new RectTransform(Vector2.One, showcaseCenterGroup.RectTransform), isHorizontal: true) { Stretch = true };
showCaseTalentParents.Add(showcaseLayout, talents);
showCaseTalentFrames.Add(showCaseFrame); showCaseTalentFrames.Add(showCaseFrame);
} }
@@ -376,16 +394,16 @@ namespace Barotrauma
{ {
if (!TalentPrefab.TalentPrefabs.TryGet(talentId, out TalentPrefab? talent)) { continue; } if (!TalentPrefab.TalentPrefabs.TryGet(talentId, out TalentPrefab? talent)) { continue; }
bool isShowCaseTalent = hasShowcase && talentId == showcaseTalentIdentifier; bool isShowCaseTalent = talentOption.ShowCaseTalents.ContainsKey(talentId);
GUIComponent talentParent; GUIComponent talentParent = talentOptionLayoutGroup;
if (hasShowcase && talentId != showcaseTalentIdentifier) foreach (var (key, value) in showCaseTalentParents)
{ {
talentParent = showcaseLayout; if (value.Contains(talentId))
} {
else talentParent = key;
{ break;
talentParent = talentOptionLayoutGroup; }
} }
GUIFrame talentFrame = new GUIFrame(new RectTransform(Vector2.One, talentParent.RectTransform), style: null) GUIFrame talentFrame = new GUIFrame(new RectTransform(Vector2.One, talentParent.RectTransform), style: null)
@@ -396,7 +414,7 @@ namespace Barotrauma
GUIFrame croppedTalentFrame = new GUIFrame(new RectTransform(Vector2.One, talentFrame.RectTransform, anchor: Anchor.Center, scaleBasis: ScaleBasis.BothHeight), style: null); GUIFrame croppedTalentFrame = new GUIFrame(new RectTransform(Vector2.One, talentFrame.RectTransform, anchor: Anchor.Center, scaleBasis: ScaleBasis.BothHeight), style: null);
GUIButton talentButton = new GUIButton(new RectTransform(Vector2.One, croppedTalentFrame.RectTransform, anchor: Anchor.Center), style: null) GUIButton talentButton = new GUIButton(new RectTransform(Vector2.One, croppedTalentFrame.RectTransform, anchor: Anchor.Center), style: null)
{ {
ToolTip = RichString.Rich($"‖color:{Color.White.ToStringHex()}‖{talent.DisplayName}‖color:end‖" + "\n\n" + talent.Description), ToolTip = RichString.Rich($"‖color:{Color.White.ToStringHex()}‖{talent.DisplayName}‖color:end‖" + "\n\n" + ToolBox.ExtendColorToPercentageSigns(talent.Description.Value)),
UserData = talent.Identifier, UserData = talent.Identifier,
PressedColor = pressedColor, PressedColor = pressedColor,
Enabled = info.Character != null, Enabled = info.Character != null,
@@ -420,20 +438,18 @@ namespace Barotrauma
return true; return true;
} }
Character? controlledCharacter = info.Character; if (character is null) { return false; }
if (controlledCharacter is null) { return false; }
if (talentOption.MaxChosenTalents is 1) if (talentOption.MaxChosenTalents is 1)
{ {
// 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 (Identifier identifier in selectedTalents)
{ {
if (guiButton.UserData is Identifier otherTalentIdentifier && guiButton != button) if (character.HasTalent(identifier) || identifier == talentId) { continue; }
if (talentOptionIdentifiers.Contains(identifier))
{ {
if (!controlledCharacter.HasTalent(otherTalentIdentifier)) selectedTalents.Remove(identifier);
{
selectedTalents.Remove(otherTalentIdentifier);
}
} }
} }
} }
@@ -451,7 +467,7 @@ namespace Barotrauma
selectedTalents.Remove(talentIdentifier); selectedTalents.Remove(talentIdentifier);
} }
} }
else if (!controlledCharacter.HasTalent(talentIdentifier)) else if (!character.HasTalent(talentIdentifier))
{ {
selectedTalents.Remove(talentIdentifier); selectedTalents.Remove(talentIdentifier);
} }
@@ -487,9 +503,31 @@ namespace Barotrauma
} }
iconImage.Enabled = talentButton.Enabled; iconImage.Enabled = talentButton.Enabled;
if (isShowCaseTalent) { continue; } if (isShowCaseTalent)
{
showCaseTalentButtonsToAdd.Add(talentId, iconImage);
continue;
}
talentButtons.Add(new TalentButton(talentButton, iconImage, talent)); buttonsToAdd.Add(new TalentButton(iconImage, talent));
}
foreach (TalentButton button in buttonsToAdd)
{
talentButtons.Add(button);
}
foreach (var (key, value) in showCaseTalentButtonsToAdd)
{
HashSet<TalentButton> buttons = new();
foreach (Identifier identifier in talentOption.ShowCaseTalents[key])
{
if (talentButtons.FirstOrNull(talentButton => talentButton.Identifier == identifier) is not { } button) { continue; }
buttons.Add(button);
}
talentShowCaseButtons.Add(new TalentShowCaseButton(buttons.ToImmutableHashSet(), value));
} }
talentCornerIcons.Add(new TalentCornerIcon(subTree.Identifier, index, cornerIcon, talentBackground, talentBackgroundHighlight)); talentCornerIcons.Add(new TalentCornerIcon(subTree.Identifier, index, cornerIcon, talentBackground, talentBackgroundHighlight));
@@ -534,6 +572,8 @@ namespace Barotrauma
private bool ResetTalentSelection(GUIButton guiButton, object userData) private bool ResetTalentSelection(GUIButton guiButton, object userData)
{ {
if (characterInfo is null) { return false; }
selectedTalents = characterInfo.GetUnlockedTalentsInTree().ToHashSet();
UpdateTalentInfo(); UpdateTalentInfo();
return true; return true;
} }
@@ -554,16 +594,15 @@ namespace Barotrauma
private bool ApplyTalentSelection(GUIButton guiButton, object userData) private bool ApplyTalentSelection(GUIButton guiButton, object userData)
{ {
Character controlledCharacter = Character.Controlled; if (character is null) { return false; }
if (controlledCharacter is null) { return false; }
ApplyTalents(controlledCharacter); ApplyTalents(character);
return true; return true;
} }
public void UpdateTalentInfo() public void UpdateTalentInfo()
{ {
if (!(Character.Controlled is { Info: var info } character)) { return; } if (character is null || characterInfo is null) { return; }
bool unlockedAllTalents = character.HasUnlockedAllTalents(); bool unlockedAllTalents = character.HasUnlockedAllTalents();
@@ -576,15 +615,15 @@ namespace Barotrauma
} }
else else
{ {
experienceText.Text = $"{info.ExperiencePoints - info.GetExperienceRequiredForCurrentLevel()} / {info.GetExperienceRequiredToLevelUp() - info.GetExperienceRequiredForCurrentLevel()}"; experienceText.Text = $"{characterInfo.ExperiencePoints - characterInfo.GetExperienceRequiredForCurrentLevel()} / {characterInfo.GetExperienceRequiredToLevelUp() - characterInfo.GetExperienceRequiredForCurrentLevel()}";
experienceBar.BarSize = info.GetProgressTowardsNextLevel(); experienceBar.BarSize = characterInfo.GetProgressTowardsNextLevel();
} }
selectedTalents = CheckTalentSelection(character, selectedTalents).ToHashSet(); selectedTalents = CheckTalentSelection(character, selectedTalents).ToHashSet();
string pointsLeft = info.GetAvailableTalentPoints().ToString(); string pointsLeft = characterInfo.GetAvailableTalentPoints().ToString();
int talentCount = selectedTalents.Count - info.GetUnlockedTalentsInTree().Count(); int talentCount = selectedTalents.Count - characterInfo.GetUnlockedTalentsInTree().Count();
if (unlockedAllTalents) if (unlockedAllTalents)
{ {
@@ -603,7 +642,7 @@ namespace Barotrauma
foreach (TalentCornerIcon cornerIcon in talentCornerIcons) foreach (TalentCornerIcon cornerIcon in talentCornerIcons)
{ {
TalentTreeStageState state = GetTalentOptionStageState(character, cornerIcon.TalentTree, cornerIcon.Index, selectedTalents); TalentStages state = GetTalentOptionStageState(character, cornerIcon.TalentTree, cornerIcon.Index, selectedTalents);
TalentTreeStyle style = talentStageStyles[state]; TalentTreeStyle style = talentStageStyles[state];
GUIComponentStyle newStyle = style.ComponentStyle; GUIComponentStyle newStyle = style.ComponentStyle;
cornerIcon.IconComponent.ApplyStyle(newStyle); cornerIcon.IconComponent.ApplyStyle(newStyle);
@@ -614,60 +653,94 @@ namespace Barotrauma
foreach (TalentButton talentButton in talentButtons) foreach (TalentButton talentButton in talentButtons)
{ {
Identifier talentIdentifier = talentButton.Identifier; TalentStages stage = GetTalentState(character, talentButton.Identifier, selectedTalents);
bool unselectable = !IsViableTalentForCharacter(character, talentIdentifier, selectedTalents) || character.HasTalent(talentIdentifier); ApplyTalentIconColor(stage, talentButton.IconComponent, talentButton.Prefab.ColorOverride);
Color newTalentColor = unselectable ? unselectableColor : unselectedColor; }
Color hoverColor = Color.White;
bool selected = false;
if (character.HasTalent(talentIdentifier)) foreach (TalentShowCaseButton showCaseTalentButton in talentShowCaseButtons)
{ {
selected = true; TalentStages collectiveTalentStage = GetCollectiveTalentState(character, showCaseTalentButton.Buttons, selectedTalents);
newTalentColor = GUIStyle.Green; ApplyTalentIconColor(collectiveTalentStage, showCaseTalentButton.IconComponent, Option<Color>.None());
}
else if (selectedTalents.Contains(talentIdentifier))
{
selected = true;
newTalentColor = GUIStyle.Orange;
hoverColor = Color.Lerp(GUIStyle.Orange, Color.White, 0.7f);
}
bool shouldOverride = !unselectable || selected;
if (shouldOverride && talentButton.Prefab.ColorOverride.TryUnwrap(out Color overrideColor))
{
newTalentColor = overrideColor;
}
talentButton.IconComponent.Color = newTalentColor;
talentButton.IconComponent.HoverColor = hoverColor;
} }
if (skillListBox is null) { return; } if (skillListBox is null) { return; }
TabMenu.CreateSkillList(character, info, skillListBox); TabMenu.CreateSkillList(character, characterInfo, skillListBox);
}
public void AddToGUIUpdateList() static TalentStages GetTalentState(Character character, Identifier talentIdentifier, IReadOnlyCollection<Identifier> selectedTalents)
{
bool mouseInteracted = PlayerInput.PrimaryMouseButtonClicked() || PlayerInput.SecondaryMouseButtonClicked() || PlayerInput.ScrollWheelSpeed != 0;
bool keyboardInteracted = PlayerInput.KeyHit(Keys.Escape) || GameSettings.CurrentConfig.KeyMap.Bindings[InputType.InfoTab].IsHit();
foreach (GUIComponent component in showCaseTalentFrames)
{ {
component.AddToGUIUpdateList(order: 1); bool unselectable = !IsViableTalentForCharacter(character, talentIdentifier, selectedTalents) || character.HasTalent(talentIdentifier);
if (!component.Visible) { continue; } TalentStages stage = unselectable ? Locked : Available;
if (unselectable)
if (keyboardInteracted || (mouseInteracted && !component.Rect.Contains(PlayerInput.MousePosition)))
{ {
component.Visible = false; stage = Locked;
} }
if (character.HasTalent(talentIdentifier))
{
stage = Unlocked;
}
else if (selectedTalents.Contains(talentIdentifier))
{
stage = Highlighted;
}
return stage;
}
static void ApplyTalentIconColor(TalentStages stage, GUIComponent component, Option<Color> colorOverride)
{
Color color = stage switch
{
Invalid => unselectableColor,
Locked => unselectableColor,
Unlocked => GetColorOrOverride(GUIStyle.Green, colorOverride),
Highlighted => GetColorOrOverride(GUIStyle.Orange, colorOverride),
Available => GetColorOrOverride(unselectedColor, colorOverride),
_ => throw new ArgumentOutOfRangeException(nameof(stage), stage, null)
};
component.Color = color;
component.HoverColor = Color.Lerp(color, Color.White, 0.7f);
static Color GetColorOrOverride(Color color, Option<Color> colorOverride) => colorOverride.TryUnwrap(out Color overrideColor) ? overrideColor : color;
}
// this could also be reused for setting colors for talentCornerIcons but that's for another time
static TalentStages GetCollectiveTalentState(Character character, IReadOnlyCollection<TalentButton> buttons, IReadOnlyCollection<Identifier> selectedTalents)
{
HashSet<TalentStages> talentStages = new HashSet<TalentStages>();
foreach (TalentButton button in buttons)
{
talentStages.Add(GetTalentState(character, button.Identifier, selectedTalents));
}
TalentStages collectiveStage = talentStages.Any(static stage => stage is Locked)
? Locked
: Available;
foreach (TalentStages stage in talentStages)
{
if (stage is Highlighted)
{
collectiveStage = Highlighted;
break;
}
if (stage is Unlocked)
{
collectiveStage = Unlocked;
break;
}
}
return collectiveStage;
} }
} }
public void Update() public void Update()
{ {
if (Character.Controlled?.Info is not { } characterInfo || talentResetButton is null || talentApplyButton is null) { return; } if (characterInfo is null || talentResetButton is null || talentApplyButton is null) { return; }
int talentCount = selectedTalents.Count - characterInfo.GetUnlockedTalentsInTree().Count(); int talentCount = selectedTalents.Count - characterInfo.GetUnlockedTalentsInTree().Count();
talentResetButton.Enabled = talentApplyButton.Enabled = talentCount > 0; talentResetButton.Enabled = talentApplyButton.Enabled = talentCount > 0;
@@ -675,6 +748,58 @@ namespace Barotrauma
{ {
talentApplyButton.Flash(GUIStyle.Orange); talentApplyButton.Flash(GUIStyle.Orange);
} }
while (showCaseClosureQueue.TryDequeue(out Identifier identifier))
{
foreach (GUIComponent component in showCaseTalentFrames)
{
if (component.UserData is Identifier showcaseIdentifier && showcaseIdentifier == identifier)
{
component.Visible = false;
}
}
}
bool mouseInteracted = PlayerInput.PrimaryMouseButtonClicked() || PlayerInput.SecondaryMouseButtonClicked() || PlayerInput.ScrollWheelSpeed != 0;
bool keyboardInteracted = PlayerInput.KeyHit(Keys.Escape) || GameSettings.CurrentConfig.KeyMap.Bindings[InputType.InfoTab].IsHit();
foreach (GUIComponent component in showCaseTalentFrames)
{
if (component.UserData is not Identifier identifier) { continue; }
component.AddToGUIUpdateList(order: 1);
if (!component.Visible) { continue; }
if (keyboardInteracted || (mouseInteracted && !component.Rect.Contains(PlayerInput.MousePosition)))
{
showCaseClosureQueue.Enqueue(identifier);
}
}
}
private static bool IsOwnCharacter(CharacterInfo? info)
{
if (info is null) { return false; }
CharacterInfo? ownCharacterInfo = Character.Controlled?.Info ?? GameMain.Client?.CharacterInfo;
if (ownCharacterInfo is null) { return false; }
return info == ownCharacterInfo;
}
public static bool CanManageTalents(CharacterInfo targetInfo)
{
// in singleplayer we can do whatever we want
if (GameMain.IsSingleplayer) { return true; }
// always allow managing talents for own character
if (IsOwnCharacter(targetInfo)) { return true; }
// don't allow controlling non-bot characters
if (targetInfo.Character is not { IsBot: true }) { return false; }
// lastly check if we have the permission to do this
return GameMain.Client is { } client && client.HasPermission(ClientPermissions.ManageBotTalents);
} }
} }
} }
@@ -278,10 +278,13 @@ namespace Barotrauma
* | upgrades | maintenance | <- 1/3rd empty space | * | upgrades | maintenance | <- 1/3rd empty space |
* |---------------------------------------------------------------------------------------------------| * |---------------------------------------------------------------------------------------------------|
*/ */
GUILayoutGroup leftLayout = new GUILayoutGroup(rectT(0.5f, 1, topHeaderLayout)) { RelativeSpacing = 0.05f }; GUILayoutGroup leftLayout = new GUILayoutGroup(rectT(0.4f, 1, topHeaderLayout)) { RelativeSpacing = 0.05f };
GUILayoutGroup locationLayout = new GUILayoutGroup(rectT(1, 0.5f, leftLayout), isHorizontal: true); GUILayoutGroup locationLayout = new GUILayoutGroup(rectT(1, 0.5f, leftLayout), isHorizontal: true);
GUIImage submarineIcon = new GUIImage(rectT(new Point(locationLayout.Rect.Height, locationLayout.Rect.Height), locationLayout), style: "SubmarineIcon", scaleToFit: true); GUIImage submarineIcon = new GUIImage(rectT(new Point(locationLayout.Rect.Height, locationLayout.Rect.Height), locationLayout), style: "SubmarineIcon", scaleToFit: true);
new GUITextBlock(rectT(1.0f - submarineIcon.RectTransform.RelativeSize.X, 1, locationLayout), TextManager.Get("UpgradeUI.Title"), font: GUIStyle.LargeFont); var header = new GUITextBlock(rectT(1.0f - submarineIcon.RectTransform.RelativeSize.X, 1, locationLayout), TextManager.Get("UpgradeUI.Title"), font: GUIStyle.LargeFont);
header.RectTransform.MaxSize = new Point((int)(header.TextSize.X + header.Padding.X + header.Padding.Z), int.MaxValue);
new GUITextBlock(rectT(1.0f, 1, locationLayout), TextManager.Get("UpgradeUI.AllSubmarinesInfo"), font: GUIStyle.SmallFont, wrap: true);
categoryButtonLayout = new GUILayoutGroup(rectT(0.4f, 0.3f, leftLayout), isHorizontal: true) { Stretch = true }; categoryButtonLayout = new GUILayoutGroup(rectT(0.4f, 0.3f, leftLayout), isHorizontal: true) { Stretch = true };
GUIButton upgradeButton = new GUIButton(rectT(1, 1f, categoryButtonLayout), TextManager.Get("UICategory.Upgrades"), style: "GUITabButton") { UserData = UpgradeTab.Upgrade, Selected = selectedUpgradeTab == UpgradeTab.Upgrade }; GUIButton upgradeButton = new GUIButton(rectT(1, 1f, categoryButtonLayout), TextManager.Get("UICategory.Upgrades"), style: "GUITabButton") { UserData = UpgradeTab.Upgrade, Selected = selectedUpgradeTab == UpgradeTab.Upgrade };
GUIButton repairButton = new GUIButton(rectT(1, 1f, categoryButtonLayout), TextManager.Get("UICategory.Maintenance"), style: "GUITabButton") { UserData = UpgradeTab.Repairs, Selected = selectedUpgradeTab == UpgradeTab.Repairs }; GUIButton repairButton = new GUIButton(rectT(1, 1f, categoryButtonLayout), TextManager.Get("UICategory.Maintenance"), style: "GUITabButton") { UserData = UpgradeTab.Repairs, Selected = selectedUpgradeTab == UpgradeTab.Repairs };
@@ -4,6 +4,7 @@ using Barotrauma.Networking;
using Barotrauma.Particles; using Barotrauma.Particles;
using Barotrauma.Steam; using Barotrauma.Steam;
using Barotrauma.Transition; using Barotrauma.Transition;
using Barotrauma.Tutorials;
using FarseerPhysics; using FarseerPhysics;
using FarseerPhysics.Dynamics; using FarseerPhysics.Dynamics;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
@@ -774,9 +775,9 @@ namespace Barotrauma
{ {
GUIMessageBox.MessageBoxes.Remove(GUIMessageBox.VisibleBox); GUIMessageBox.MessageBoxes.Remove(GUIMessageBox.VisibleBox);
} }
else if (GameSession?.GameMode is TutorialMode tutorialMode && tutorialMode.Tutorial.ContentRunning) else if (ObjectiveManager.ContentRunning)
{ {
tutorialMode.Tutorial.CloseActiveContentGUI(); ObjectiveManager.CloseActiveContentGUI();
} }
else if (GameSession.IsTabMenuOpen) else if (GameSession.IsTabMenuOpen)
{ {
@@ -828,7 +829,7 @@ namespace Barotrauma
Paused = Paused =
(DebugConsole.IsOpen || DebugConsole.Paused || (DebugConsole.IsOpen || DebugConsole.Paused ||
GUI.PauseMenuOpen || GUI.SettingsMenuOpen || GUI.PauseMenuOpen || GUI.SettingsMenuOpen ||
(GameSession?.GameMode is TutorialMode tutoMode && tutoMode.Tutorial.ContentRunning)) && (GameSession?.GameMode is TutorialMode && ObjectiveManager.ContentRunning)) &&
(NetworkMember == null || !NetworkMember.GameStarted); (NetworkMember == null || !NetworkMember.GameStarted);
if (GameSession?.GameMode != null && GameSession.GameMode.Paused) if (GameSession?.GameMode != null && GameSession.GameMode.Paused)
{ {
@@ -862,8 +863,9 @@ namespace Barotrauma
{ {
Screen.Selected.Update(Timing.Step); Screen.Selected.Update(Timing.Step);
} }
else if (GameSession?.GameMode is TutorialMode tutorialMode && tutorialMode.Tutorial.ContentRunning) else if (ObjectiveManager.ContentRunning && GameSession?.GameMode is TutorialMode tutorialMode)
{ {
ObjectiveManager.VideoPlayer.Update();
tutorialMode.Update((float)Timing.Step); tutorialMode.Update((float)Timing.Step);
} }
else else
@@ -4,6 +4,7 @@ using Barotrauma.Networking;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using System; using System;
using System.IO;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -203,6 +204,10 @@ namespace Barotrauma
} }
break; break;
} }
if (Level.IsLoadedOutpost && !ObjectiveManager.AllActiveObjectivesCompleted())
{
endRoundButton.Visible = false;
}
if (ReadyCheckButton != null) { ReadyCheckButton.Visible = endRoundButton.Visible; } if (ReadyCheckButton != null) { ReadyCheckButton.Visible = endRoundButton.Visible; }
@@ -266,7 +271,7 @@ namespace Barotrauma
Rand.ThreadId = Thread.CurrentThread.ManagedThreadId; Rand.ThreadId = Thread.CurrentThread.ManagedThreadId;
try try
{ {
GameMain.GameSession.StartRound(newLevel, mirrorLevel: mirror); GameMain.GameSession.StartRound(newLevel, mirrorLevel: mirror, startOutpost: GetPredefinedStartOutpost());
} }
catch (Exception e) catch (Exception e)
{ {
@@ -283,6 +288,18 @@ namespace Barotrauma
return loadTask; return loadTask;
} }
protected SubmarineInfo GetPredefinedStartOutpost()
{
if (Map?.CurrentLocation?.Type?.GetForcedOutpostGenerationParams() is OutpostGenerationParams parameters && !parameters.OutpostFilePath.IsNullOrEmpty())
{
return new SubmarineInfo(parameters.OutpostFilePath.Value)
{
OutpostGenerationParams = parameters
};
}
return null;
}
partial void NPCInteractProjSpecific(Character npc, Character interactor) partial void NPCInteractProjSpecific(Character npc, Character interactor)
{ {
if (npc == null || interactor == null) { return; } if (npc == null || interactor == null) { return; }
@@ -265,8 +265,8 @@ namespace Barotrauma
private IEnumerable<CoroutineStatus> DoLoadInitialLevel(LevelData level, bool mirror) private IEnumerable<CoroutineStatus> DoLoadInitialLevel(LevelData level, bool mirror)
{ {
GameMain.GameSession.StartRound(level,
mirrorLevel: mirror); GameMain.GameSession.StartRound(level, mirrorLevel: mirror, startOutpost: GetPredefinedStartOutpost());
GameMain.GameScreen.Select(); GameMain.GameScreen.Select();
CoroutineManager.StartCoroutine(DoInitialCameraTransition(), "SinglePlayerCampaign.DoInitialCameraTransition"); CoroutineManager.StartCoroutine(DoInitialCameraTransition(), "SinglePlayerCampaign.DoInitialCameraTransition");
@@ -1,7 +1,5 @@
using Barotrauma.Extensions; using Barotrauma.Extensions;
using Barotrauma.IO;
using Barotrauma.Items.Components; using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@@ -25,107 +23,9 @@ namespace Barotrauma.Tutorials
#region Tutorial variables #region Tutorial variables
public readonly Identifier Identifier; public readonly Identifier Identifier;
public LocalizedString DisplayName { get; } public LocalizedString DisplayName { get; }
public LocalizedString Description { get; }
public bool ContentRunning { get; private set; }
private GUIComponent infoBox;
private Action infoBoxClosedCallback;
private VideoPlayer videoPlayer;
private Point screenResolution;
private WindowMode windowMode;
private float prevUIScale;
private GUILayoutGroup objectiveGroup;
private readonly LocalizedString objectiveTextTranslated;
private readonly List<Segment> ActiveObjectives = new List<Segment>();
private const float ObjectiveComponentAnimationTime = 1.5f;
private Segment ActiveContentSegment { get; set; }
public class Segment
{
public readonly record struct Text(
Identifier Tag,
int Width = DefaultWidth,
int Height = DefaultHeight,
Anchor Anchor = Anchor.Center);
public readonly record struct Video(
string FullPath,
Identifier TextTag,
int Width = DefaultWidth,
int Height = DefaultHeight)
{
public string FileName => Path.GetFileName(FullPath.CleanUpPath());
public string ContentPath => Path.GetDirectoryName(FullPath.CleanUpPath());
}
private const int DefaultWidth = 450;
private const int DefaultHeight = 80;
public GUIImage ObjectiveStateIndicator;
public GUIButton ObjectiveButton;
public GUITextBlock LinkedTextBlock;
public LocalizedString ObjectiveText;
public readonly Identifier Id;
public readonly Text TextContent;
public readonly Video VideoContent;
public readonly AutoPlayVideo AutoPlayVideo;
public Action OnClickObjective;
public TutorialSegmentType SegmentType { get; private set; }
public static Segment CreateInfoBoxSegment(Identifier id, Identifier objectiveTextTag, AutoPlayVideo autoPlayVideo, Text textContent = default, Video videoContent = default)
{
return new Segment(id, objectiveTextTag, autoPlayVideo, textContent, videoContent);
}
public static Segment CreateMessageBoxSegment(Identifier id, Identifier objectiveTextTag, Action onClickObjective)
{
return new Segment(id, objectiveTextTag, onClickObjective);
}
public static Segment CreateObjectiveSegment(Identifier id, Identifier objectiveTextTag)
{
return new Segment(id, objectiveTextTag);
}
private Segment(Identifier id, Identifier objectiveTextTag, AutoPlayVideo autoPlayVideo, Text textContent = default, Video videoContent = default)
{
Id = id;
ObjectiveText = TextManager.ParseInputTypes(TextManager.Get(objectiveTextTag));
AutoPlayVideo = autoPlayVideo;
TextContent = textContent;
VideoContent = videoContent;
SegmentType = TutorialSegmentType.InfoBox;
}
private Segment(Identifier id, Identifier objectiveTextTag, Action onClickObjective)
{
Id = id;
ObjectiveText = TextManager.ParseInputTypes(TextManager.Get(objectiveTextTag));
OnClickObjective = onClickObjective;
SegmentType = TutorialSegmentType.MessageBox;
}
private Segment(Identifier id, Identifier objectiveTextTag)
{
Id = id;
ObjectiveText = TextManager.ParseInputTypes(TextManager.Get(objectiveTextTag));
SegmentType = TutorialSegmentType.Objective;
}
public void ConnectMessageBox(Segment messageBoxSegment)
{
SegmentType = TutorialSegmentType.MessageBox;
OnClickObjective = messageBoxSegment.OnClickObjective;
}
}
private bool completed; private bool completed;
public bool Completed public bool Completed
@@ -163,6 +63,8 @@ namespace Barotrauma.Tutorials
public readonly List<(Entity entity, Identifier iconStyle)> Icons = new List<(Entity entity, Identifier iconStyle)>(); public readonly List<(Entity entity, Identifier iconStyle)> Icons = new List<(Entity entity, Identifier iconStyle)>();
public bool Paused { get; private set; }
#endregion #endregion
#region Tutorial Controls #region Tutorial Controls
@@ -171,8 +73,7 @@ namespace Barotrauma.Tutorials
{ {
Identifier = $"tutorial.{prefab.Identifier}".ToIdentifier(); Identifier = $"tutorial.{prefab.Identifier}".ToIdentifier();
DisplayName = TextManager.Get(Identifier); DisplayName = TextManager.Get(Identifier);
objectiveTextTranslated = TextManager.Get("Tutorial.Objective"); Description = TextManager.Get($"tutorial.{prefab.Identifier}.description");
TutorialPrefab = prefab; TutorialPrefab = prefab;
eventPrefab = EventSet.GetEventPrefab(prefab.EventIdentifier); eventPrefab = EventSet.GetEventPrefab(prefab.EventIdentifier);
} }
@@ -260,35 +161,26 @@ namespace Barotrauma.Tutorials
tutorialCoroutine = CoroutineManager.StartCoroutine(UpdateState()); tutorialCoroutine = CoroutineManager.StartCoroutine(UpdateState());
Initialize(); GameMain.GameSession.CrewManager.AllowCharacterSwitch = TutorialPrefab.AllowCharacterSwitch;
GameMain.GameSession.CrewManager.AutoHideCrewList();
if (Character.Controlled?.Inventory is CharacterInventory inventory)
{
foreach (Item item in inventory.AllItemsMod)
{
if (item.HasTag(TutorialPrefab.StartingItemTags)) { continue; }
item.Unequip(Character.Controlled);
Character.Controlled.Inventory.RemoveItem(item);
}
}
yield return CoroutineStatus.Success; yield return CoroutineStatus.Success;
} }
private void Initialize()
{
GameMain.GameSession.CrewManager.AllowCharacterSwitch = TutorialPrefab.AllowCharacterSwitch;
GameMain.GameSession.CrewManager.AutoHideCrewList();
if (Character.Controlled is Character character)
{
foreach (Item item in character.Inventory.AllItemsMod)
{
if (item.HasTag(TutorialPrefab.StartingItemTags)) { continue; }
item.Unequip(character);
character.Inventory.RemoveItem(item);
}
}
}
public void Start() public void Start()
{ {
videoPlayer = new VideoPlayer();
GameMain.Instance.ShowLoading(Loading()); GameMain.Instance.ShowLoading(Loading());
ActiveObjectives.Clear(); ObjectiveManager.ResetObjectives();
ActiveContentSegment = null;
CreateObjectiveFrame();
// Setup doors: Clear all requirements, unless the door is setup as locked. // Setup doors: Clear all requirements, unless the door is setup as locked.
foreach (var item in Item.ItemList) foreach (var item in Item.ItemList)
@@ -304,24 +196,8 @@ namespace Barotrauma.Tutorials
} }
} }
public void AddToGUIUpdateList()
{
if (GameMain.GraphicsWidth != screenResolution.X || GameMain.GraphicsHeight != screenResolution.Y || prevUIScale != GUI.Scale || GameSettings.CurrentConfig.Graphics.DisplayMode != windowMode)
{
CreateObjectiveFrame();
}
if (ActiveObjectives.Count > 0)
{
objectiveGroup?.AddToGUIUpdateList(order: -1);
}
infoBox?.AddToGUIUpdateList(order: 100);
videoPlayer?.AddToGUIUpdateList(order: 100);
}
public void Update() public void Update()
{ {
videoPlayer?.Update();
if (character != null) if (character != null)
{ {
if (character.Oxygen < 1) if (character.Oxygen < 1)
@@ -342,8 +218,7 @@ namespace Barotrauma.Tutorials
{ {
GUI.PreventPauseMenuToggle = false; GUI.PreventPauseMenuToggle = false;
} }
ContentRunning = false; ObjectiveManager.ClearContent();
infoBox = null;
} }
else else
{ {
@@ -374,18 +249,6 @@ namespace Barotrauma.Tutorials
yield return CoroutineStatus.Success; yield return CoroutineStatus.Success;
} }
public void CloseActiveContentGUI()
{
if (videoPlayer.IsPlaying)
{
videoPlayer.Stop();
}
else if (infoBox != null)
{
CloseInfoFrame();
}
}
public IEnumerable<CoroutineStatus> UpdateState() public IEnumerable<CoroutineStatus> UpdateState()
{ {
while (GameMain.Instance.LoadingScreenOpen || Level.Loaded == null || Level.Loaded.Generating) while (GameMain.Instance.LoadingScreenOpen || Level.Loaded == null || Level.Loaded.Generating)
@@ -432,13 +295,56 @@ namespace Barotrauma.Tutorials
yield return new WaitForSeconds(WaitBeforeFade); yield return new WaitForSeconds(WaitBeforeFade);
Action onEnd = () => GameMain.MainMenuScreen.ReturnToMainMenu(null, null);
TutorialPrefab nextTutorialPrefab = null;
bool displayEndMessage =
TutorialPrefab.EndMessage.EndType == TutorialPrefab.EndType.Restart ||
(TutorialPrefab.EndMessage.EndType == TutorialPrefab.EndType.Continue && TutorialPrefab.Prefabs.TryGet(TutorialPrefab.EndMessage.NextTutorialIdentifier, out nextTutorialPrefab));
if (displayEndMessage)
{
Paused = true;
var endingMessageBox = new GUIMessageBox(
headerText: "",
text: TextManager.Get($"{Identifier}.completed"),
buttons: new LocalizedString[]
{
TextManager.Get(nextTutorialPrefab is null ? "restart" : "campaigncontinue"),
TextManager.Get("pausemenuquit")
});
endingMessageBox.Buttons[0].OnClicked += (_, _) =>
{
if (nextTutorialPrefab is null)
{
onEnd = () => Restart(null, null);
}
else
{
onEnd = () =>
{
GameMain.MainMenuScreen.ReturnToMainMenu(null, null);
new Tutorial(nextTutorialPrefab).Start();
};
}
return true;
};
endingMessageBox.Buttons[0].OnClicked += endingMessageBox.Close;
endingMessageBox.Buttons[0].OnClicked += (_, _) => Paused = false;
endingMessageBox.Buttons[1].OnClicked += endingMessageBox.Close;
endingMessageBox.Buttons[1].OnClicked += (_, _) => Paused = false;
}
while (Paused) { yield return CoroutineStatus.Running; }
var endCinematic = new CameraTransition(Submarine.MainSub, GameMain.GameScreen.Cam, null, Alignment.Center, panDuration: FadeOutTime); var endCinematic = new CameraTransition(Submarine.MainSub, GameMain.GameScreen.Cam, null, Alignment.Center, panDuration: FadeOutTime);
Completed = true; Completed = true;
while (endCinematic.Running) { yield return CoroutineStatus.Running; } while (endCinematic.Running) { yield return CoroutineStatus.Running; }
Stop(); Stop();
GameMain.MainMenuScreen.ReturnToMainMenu(null, null); onEnd();
} }
} }
@@ -450,379 +356,15 @@ namespace Barotrauma.Tutorials
return true; return true;
} }
public void TriggerTutorialSegment(Segment segment, bool connectObjective = false)
{
if (segment.SegmentType != TutorialSegmentType.InfoBox)
{
ActiveObjectives.Add(segment);
AddToObjectiveList(segment, connectObjective);
return;
}
Inventory.DraggingItems.Clear();
ContentRunning = true;
ActiveContentSegment = segment;
var title = TextManager.Get(segment.Id);
LocalizedString tutorialText = TextManager.GetFormatted(segment.TextContent.Tag);
tutorialText = TextManager.ParseInputTypes(tutorialText);
switch (segment.AutoPlayVideo)
{
case AutoPlayVideo.Yes:
infoBox = CreateInfoFrame(
title,
tutorialText,
segment.TextContent.Width,
segment.TextContent.Height,
segment.TextContent.Anchor,
hasButton: true,
onInfoBoxClosed: LoadActiveContentVideo);
break;
case AutoPlayVideo.No:
infoBox = CreateInfoFrame(
title,
tutorialText,
segment.TextContent.Width,
segment.TextContent.Height,
segment.TextContent.Anchor,
hasButton: true,
onInfoBoxClosed: StopCurrentContentSegment,
onVideoButtonClicked: LoadActiveContentVideo);
break;
}
}
public void CompleteTutorialSegment(Identifier segmentId)
{
if (GetActiveObjective(segmentId) is not Segment segment)
{
DebugConsole.AddWarning($"Warning: tried to complete the tutorial segment \"{segmentId}\" in tutorial \"{Identifier}\" but it isn't active!");
return;
}
if (GUIStyle.GetComponentStyle("ObjectiveIndicatorCompleted") is GUIComponentStyle style)
{
//return if already completed
if (segment.ObjectiveStateIndicator.Style == style) { return; }
segment.ObjectiveStateIndicator.ApplyStyle(style);
}
segment.ObjectiveStateIndicator.Parent.Flash(color: GUIStyle.Green, flashDuration: 0.35f, useRectangleFlash: true);
segment.ObjectiveButton.OnClicked = null;
segment.ObjectiveButton.CanBeFocused = false;
GameAnalyticsManager.AddDesignEvent($"Tutorial:{Identifier}:{segmentId}:Completed");
}
public void RemoveTutorialSegment(Identifier segmentId)
{
if (GetActiveObjective(segmentId) is not Segment segment)
{
DebugConsole.AddWarning($"Warning: tried to remove the tutorial segment \"{segmentId}\" in tutorial \"{Identifier}\" but it isn't active!");
return;
}
segment.ObjectiveStateIndicator.FadeOut(ObjectiveComponentAnimationTime, false);
segment.LinkedTextBlock.FadeOut(ObjectiveComponentAnimationTime, false);
var parent = segment.LinkedTextBlock.Parent;
parent.FadeOut(ObjectiveComponentAnimationTime, true, onRemove: () =>
{
ActiveObjectives.Remove(segment);
objectiveGroup?.Recalculate();
});
parent.RectTransform.MoveOverTime(GetObjectiveHiddenPosition(parent.RectTransform), ObjectiveComponentAnimationTime);
segment.ObjectiveButton.OnClicked = null;
segment.ObjectiveButton.CanBeFocused = false;
}
private Segment GetActiveObjective(Identifier id) => ActiveObjectives.FirstOrDefault(s => s.Id == id);
public void Stop() public void Stop()
{ {
if (tutorialCoroutine != null) if (tutorialCoroutine != null)
{ {
CoroutineManager.StopCoroutines(tutorialCoroutine); CoroutineManager.StopCoroutines(tutorialCoroutine);
} }
ContentRunning = false; ObjectiveManager.ResetUI();
infoBox = null;
videoPlayer?.Remove();
} }
#endregion #endregion
#region Objectives
/// <summary>
/// Create the objective list that holds the objectives (called on start and on resolution change)
/// </summary>
private void CreateObjectiveFrame()
{
var objectiveListFrame = new GUIFrame(HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.TutorialObjectiveListArea, GUI.Canvas), style: null);
objectiveGroup = new GUILayoutGroup(new RectTransform(Vector2.One, objectiveListFrame.RectTransform))
{
AbsoluteSpacing = (int)GUIStyle.Font.LineHeight
};
for (int i = 0; i < ActiveObjectives.Count; i++)
{
AddToObjectiveList(ActiveObjectives[i]);
}
screenResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
windowMode = GameSettings.CurrentConfig.Graphics.DisplayMode;
prevUIScale = GUI.Scale;
}
/// <summary>
/// Stops content running and adds the active segment to the objective list
/// </summary>
private void StopCurrentContentSegment()
{
if (!ActiveContentSegment.ObjectiveText.IsNullOrEmpty())
{
ActiveObjectives.Add(ActiveContentSegment);
AddToObjectiveList(ActiveContentSegment);
}
ContentRunning = false;
ActiveContentSegment = null;
}
/// <summary>
/// Adds the segment to the objective list
/// </summary>
private void AddToObjectiveList(Segment segment, bool connectExisting = false)
{
if (connectExisting)
{
if (ActiveObjectives.Find(o => o.Id == segment.Id) is { } existingSegment)
{
existingSegment.ConnectMessageBox(segment);
SetButtonBehavior(existingSegment);
}
return;
}
var frameRt = new RectTransform(new Vector2(1.0f, 0.1f), objectiveGroup.RectTransform)
{
AbsoluteOffset = GetObjectiveHiddenPosition(),
MinSize = new Point(0, objectiveGroup.AbsoluteSpacing)
};
var frame = new GUIFrame(frameRt, style: null)
{
CanBeFocused = true
};
objectiveGroup.Recalculate();
segment.LinkedTextBlock = new GUITextBlock(
new RectTransform(new Point(frameRt.Rect.Width - objectiveGroup.AbsoluteSpacing, 0), frame.RectTransform, anchor: Anchor.TopRight),
TextManager.ParseInputTypes(segment.ObjectiveText),
wrap: true);
var size = new Point(segment.LinkedTextBlock.Rect.Width, segment.LinkedTextBlock.Rect.Height);
segment.LinkedTextBlock.RectTransform.NonScaledSize = size;
segment.LinkedTextBlock.RectTransform.MinSize = size;
segment.LinkedTextBlock.RectTransform.MaxSize = size;
segment.LinkedTextBlock.RectTransform.IsFixedSize = true;
frame.RectTransform.Resize(new Point(frame.Rect.Width, segment.LinkedTextBlock.RectTransform.Rect.Height), resizeChildren: false);
frame.RectTransform.IsFixedSize = true;
var indicatorRt = new RectTransform(new Point(objectiveGroup.AbsoluteSpacing), frame.RectTransform, isFixedSize: true);
segment.ObjectiveStateIndicator = new GUIImage(indicatorRt, "ObjectiveIndicatorIncomplete");
SetTransparent(segment.LinkedTextBlock);
segment.ObjectiveButton = new GUIButton(new RectTransform(Vector2.One, segment.LinkedTextBlock.RectTransform, Anchor.TopLeft, Pivot.TopLeft), style: null)
{
ToolTip = objectiveTextTranslated
};
SetButtonBehavior(segment);
SetTransparent(segment.ObjectiveButton);
frameRt.MoveOverTime(new Point(0, frameRt.AbsoluteOffset.Y), ObjectiveComponentAnimationTime, onDoneMoving: () => objectiveGroup?.Recalculate());
static void SetTransparent(GUIComponent component) => component.Color = component.HoverColor = component.PressedColor = component.SelectedColor = Color.Transparent;
void SetButtonBehavior(Segment segment)
{
segment.ObjectiveButton.CanBeFocused = segment.SegmentType != TutorialSegmentType.Objective;
segment.ObjectiveButton.OnClicked = (GUIButton btn, object userdata) =>
{
if (segment.SegmentType == TutorialSegmentType.InfoBox)
{
if (segment.AutoPlayVideo == AutoPlayVideo.Yes)
{
ReplaySegmentVideo(segment);
}
else
{
ShowSegmentText(segment);
}
}
else if (segment.SegmentType == TutorialSegmentType.MessageBox)
{
segment.OnClickObjective?.Invoke();
}
return true;
};
}
}
private void ReplaySegmentVideo(Segment segment)
{
if (ContentRunning) { return; }
Inventory.DraggingItems.Clear();
ContentRunning = true;
LoadVideo(segment);
}
private void ShowSegmentText(Segment segment)
{
if (ContentRunning) { return; }
Inventory.DraggingItems.Clear();
ContentRunning = true;
ActiveContentSegment = segment;
infoBox = CreateInfoFrame(
TextManager.Get(segment.Id),
TextManager.Get(segment.TextContent.Tag),
segment.TextContent.Width,
segment.TextContent.Height,
segment.TextContent.Anchor,
hasButton: true,
onInfoBoxClosed: () => ContentRunning = false,
onVideoButtonClicked: () => LoadVideo(segment));
}
private Point GetObjectiveHiddenPosition(RectTransform rt = null)
{
return new Point(GameMain.GraphicsWidth - objectiveGroup.Rect.X, rt?.AbsoluteOffset.Y ?? 0);
}
#endregion
#region InfoFrame
private void CloseInfoFrame() => CloseInfoFrame(null, null);
private bool CloseInfoFrame(GUIButton button, object userData)
{
infoBox = null;
infoBoxClosedCallback?.Invoke();
return true;
}
/// <summary>
// Creates and displays a tutorial info box
/// </summary>
private GUIComponent CreateInfoFrame(LocalizedString title, LocalizedString text, int width = 300, int height = 80, Anchor anchor = Anchor.TopRight, bool hasButton = false, Action onInfoBoxClosed = null, Action onVideoButtonClicked = null)
{
if (hasButton)
{
height += 60;
}
width = (int)(width * GUI.Scale);
height = (int)(height * GUI.Scale);
LocalizedString wrappedText = ToolBox.WrapText(text, width, GUIStyle.Font);
height += (int)GUIStyle.Font.MeasureString(wrappedText).Y;
if (title.Length > 0)
{
height += (int)GUIStyle.Font.MeasureString(title).Y + (int)(150 * GUI.Scale);
}
var background = new GUIFrame(new RectTransform(new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight), GUI.Canvas, Anchor.Center), style: "GUIBackgroundBlocker");
var infoBlock = new GUIFrame(new RectTransform(new Point(width, height), background.RectTransform, anchor));
infoBlock.Flash(GUIStyle.Green);
var infoContent = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.9f), infoBlock.RectTransform, Anchor.Center))
{
Stretch = true,
AbsoluteSpacing = 5
};
if (title.Length > 0)
{
var titleBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoContent.RectTransform),
title, font: GUIStyle.LargeFont, textAlignment: Alignment.Center, textColor: new Color(253, 174, 0));
titleBlock.RectTransform.IsFixedSize = true;
}
text = RichString.Rich(text);
GUITextBlock textBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoContent.RectTransform), text, wrap: true);
textBlock.RectTransform.IsFixedSize = true;
infoBoxClosedCallback = onInfoBoxClosed;
if (hasButton)
{
var buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.15f), infoContent.RectTransform), isHorizontal: true)
{
RelativeSpacing = 0.1f
};
buttonContainer.RectTransform.IsFixedSize = true;
if (onVideoButtonClicked != null)
{
buttonContainer.Stretch = true;
var videoButton = new GUIButton(new RectTransform(new Vector2(0.4f, 1.0f), buttonContainer.RectTransform),
TextManager.Get("Video"), style: "GUIButtonLarge")
{
OnClicked = (GUIButton button, object obj) =>
{
onVideoButtonClicked();
return true;
}
};
}
else
{
buttonContainer.Stretch = false;
buttonContainer.ChildAnchor = Anchor.Center;
}
var okButton = new GUIButton(new RectTransform(new Vector2(0.4f, 1.0f), buttonContainer.RectTransform),
TextManager.Get("OK"), style: "GUIButtonLarge")
{
OnClicked = CloseInfoFrame
};
}
infoBlock.RectTransform.NonScaledSize = new Point(infoBlock.Rect.Width, (int)(infoContent.Children.Sum(c => c.Rect.Height + infoContent.AbsoluteSpacing) / infoContent.RectTransform.RelativeSize.Y));
SoundPlayer.PlayUISound(GUISoundType.UIMessage);
return background;
}
#endregion
#region Video
private void LoadVideo(Segment segment)
{
videoPlayer ??= new VideoPlayer();
if (segment.AutoPlayVideo == AutoPlayVideo.Yes)
{
videoPlayer.LoadContent(
contentPath: segment.VideoContent.ContentPath,
videoSettings: new VideoPlayer.VideoSettings(segment.VideoContent.FileName),
textSettings: new VideoPlayer.TextSettings(segment.VideoContent.TextTag, segment.VideoContent.Width),
contentId: segment.Id,
startPlayback: true,
objective: segment.ObjectiveText,
onStop: StopCurrentContentSegment);
}
else
{
videoPlayer.LoadContent(
contentPath: segment.VideoContent.ContentPath,
videoSettings: new VideoPlayer.VideoSettings(segment.VideoContent.FileName),
textSettings: null,
contentId: segment.Id,
startPlayback: true,
objective: string.Empty);
}
}
private void LoadActiveContentVideo() => LoadVideo(ActiveContentSegment);
#endregion
} }
} }
@@ -6,6 +6,8 @@ namespace Barotrauma
{ {
public Tutorial Tutorial; public Tutorial Tutorial;
public override bool Paused => Tutorial.Paused;
public TutorialMode(GameModePreset preset) : base(preset) { } public TutorialMode(GameModePreset preset) : base(preset) { }
public override void Start() public override void Start()
@@ -19,12 +21,6 @@ namespace Barotrauma
} }
} }
public override void AddToGUIUpdateList()
{
base.AddToGUIUpdateList();
Tutorial.AddToGUIUpdateList();
}
public override void Update(float deltaTime) public override void Update(float deltaTime)
{ {
base.Update(deltaTime); base.Update(deltaTime);
@@ -1,4 +1,5 @@
using Microsoft.Xna.Framework; using Barotrauma.Tutorials;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
namespace Barotrauma namespace Barotrauma
@@ -128,8 +129,9 @@ namespace Barotrauma
if (GUI.DisableHUD) { return; } if (GUI.DisableHUD) { return; }
GameMode?.AddToGUIUpdateList(); GameMode?.AddToGUIUpdateList();
tabMenu?.AddToGUIUpdateList(); tabMenu?.AddToGUIUpdateList();
ObjectiveManager.AddToGUIUpdateList();
if ((!(GameMode is CampaignMode campaign) || (!campaign.ForceMapUI && !campaign.ShowCampaignUI)) && if ((GameMode is not CampaignMode campaign || (!campaign.ForceMapUI && !campaign.ShowCampaignUI)) &&
!CoroutineManager.IsCoroutineRunning("LevelTransition") && !CoroutineManager.IsCoroutineRunning("SubmarineTransition")) !CoroutineManager.IsCoroutineRunning("LevelTransition") && !CoroutineManager.IsCoroutineRunning("SubmarineTransition"))
{ {
if (topLeftButtonGroup == null) if (topLeftButtonGroup == null)
@@ -223,6 +225,7 @@ namespace Barotrauma
} }
HintManager.Update(); HintManager.Update();
ObjectiveManager.VideoPlayer.Update();
} }
public void SetRespawnInfo(bool visible, string text, Color textColor, bool buttonsVisible, bool waitForNextRoundRespawn) public void SetRespawnInfo(bool visible, string text, Color textColor, bool buttonsVisible, bool waitForNextRoundRespawn)
@@ -1,6 +1,7 @@
using Barotrauma.Extensions; using Barotrauma.Extensions;
using Barotrauma.IO; using Barotrauma.IO;
using Barotrauma.Items.Components; using Barotrauma.Items.Components;
using Barotrauma.Tutorials;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@@ -209,7 +210,7 @@ namespace Barotrauma
{ {
if (item.CurrentHull == null) { continue; } if (item.CurrentHull == null) { continue; }
if (item.GetComponent<Pump>() == null) { continue; } if (item.GetComponent<Pump>() == null) { continue; }
if (!item.HasTag("ballast")) { continue; } if (!item.HasTag("ballast") && !item.CurrentHull.RoomName.Contains("ballast", StringComparison.OrdinalIgnoreCase)) { continue; }
BallastHulls.Add(item.CurrentHull); BallastHulls.Add(item.CurrentHull);
} }
} }
@@ -383,6 +384,34 @@ namespace Barotrauma
IgnoreReminder("tabmenu"); IgnoreReminder("tabmenu");
} }
public static void OnObtainedItem(Character character, Item item)
{
if (!CanDisplayHints()) { return; }
if (character != Character.Controlled || item == null) { return; }
if (DisplayHint($"onobtaineditem.{item.Prefab.Identifier}".ToIdentifier())) { return; }
foreach (Identifier tag in item.GetTags())
{
if (DisplayHint($"onobtaineditem.{tag}".ToIdentifier())) { return; }
}
if ((item.HasTag("geneticmaterial") && character.Inventory.FindItemByTag("geneticdevice".ToIdentifier(), recursive: true) != null) ||
(item.HasTag("geneticdevice") && character.Inventory.FindItemByTag("geneticmaterial".ToIdentifier(), recursive: true) != null))
{
if (DisplayHint($"geneticmaterial.useinstructions".ToIdentifier())) { return; }
}
}
public static void OnStartDeconstructing(Character character, Deconstructor deconstructor)
{
if (!CanDisplayHints()) { return; }
if (character != Character.Controlled || deconstructor == null) { return; }
if (deconstructor.InputContainer.Inventory.AllItems.All(it => it.GetComponent<GeneticMaterial>() is not null))
{
DisplayHint($"geneticmaterial.onrefiningorcombining".ToIdentifier());
}
}
public static void OnStoleItem(Character character, Item item) public static void OnStoleItem(Character character, Item item)
{ {
if (!CanDisplayHints()) { return; } if (!CanDisplayHints()) { return; }
@@ -507,7 +536,7 @@ namespace Barotrauma
if (!CanDisplayHints()) { return; } if (!CanDisplayHints()) { return; }
if (character != Character.Controlled) { return; } if (character != Character.Controlled) { return; }
// Could make this more generic if there will ever be any other status effect related hints // Could make this more generic if there will ever be any other status effect related hints
if (!(component is Repairable) || actionType != ActionType.OnFailure) { return; } if (component is not Repairable || actionType != ActionType.OnFailure) { return; }
DisplayHint("onrepairfailed".ToIdentifier()); DisplayHint("onrepairfailed".ToIdentifier());
} }
@@ -563,7 +592,7 @@ namespace Barotrauma
foreach (var me in gap.linkedTo) foreach (var me in gap.linkedTo)
{ {
if (me == Character.Controlled.CurrentHull) { continue; } if (me == Character.Controlled.CurrentHull) { continue; }
if (!(me is Hull adjacentHull)) { continue; } if (me is not Hull adjacentHull) { continue; }
if (!IsOnFriendlySub()) { continue; } if (!IsOnFriendlySub()) { continue; }
if (IsWearingDivingSuit()) { continue; } if (IsWearingDivingSuit()) { continue; }
if (adjacentHull.LethalPressure > 5.0f && DisplayHint("onadjacenthull.highpressure".ToIdentifier())) { return; } if (adjacentHull.LethalPressure > 5.0f && DisplayHint("onadjacenthull.highpressure".ToIdentifier())) { return; }
@@ -720,6 +749,7 @@ namespace Barotrauma
if (requireControllingCharacter && Character.Controlled == null) { return false; } if (requireControllingCharacter && Character.Controlled == null) { return false; }
var gameMode = GameMain.GameSession?.GameMode; var gameMode = GameMain.GameSession?.GameMode;
if (!(gameMode is CampaignMode || gameMode is MissionMode)) { return false; } if (!(gameMode is CampaignMode || gameMode is MissionMode)) { return false; }
if (ObjectiveManager.AnyObjectives) { return false; }
if (requireGameScreen && Screen.Selected != GameMain.GameScreen) { return false; } if (requireGameScreen && Screen.Selected != GameMain.GameScreen) { return false; }
return true; return true;
} }
@@ -9,7 +9,7 @@ using Barotrauma.Networking;
namespace Barotrauma namespace Barotrauma
{ {
internal partial class MedicalClinic internal sealed partial class MedicalClinic
{ {
public enum RequestResult public enum RequestResult
{ {
@@ -19,63 +19,11 @@ namespace Barotrauma
Timeout Timeout
} }
public readonly struct RequestAction<T> public readonly record struct RequestAction<T>(Action<T> Callback, DateTimeOffset Timeout);
{ public readonly record struct AfflictionRequest(RequestResult Result, ImmutableArray<NetAffliction> Afflictions);
public readonly Action<T> Callback; public readonly record struct PendingRequest(RequestResult Result, NetCollection<NetCrewMember> CrewMembers);
public readonly DateTimeOffset Timeout; public readonly record struct CallbackOnlyRequest(RequestResult Result);
public readonly record struct HealRequest(RequestResult Result, HealRequestResult HealResult);
public RequestAction(Action<T> callback, DateTimeOffset timeout)
{
Callback = callback;
Timeout = timeout;
}
}
public readonly struct AfflictionRequest
{
public readonly RequestResult Result;
public readonly ImmutableArray<NetAffliction> Afflictions;
public AfflictionRequest(RequestResult result, ImmutableArray<NetAffliction> afflictions)
{
Result = result;
Afflictions = afflictions;
}
}
public readonly struct PendingRequest
{
public readonly RequestResult Result;
public readonly ImmutableArray<NetCrewMember> CrewMembers;
public PendingRequest(RequestResult result, ImmutableArray<NetCrewMember> crewMembers)
{
Result = result;
CrewMembers = crewMembers;
}
}
public readonly struct CallbackOnlyRequest
{
public readonly RequestResult Result;
public CallbackOnlyRequest(RequestResult result)
{
Result = result;
}
}
public readonly struct HealRequest
{
public readonly RequestResult Result;
public readonly HealRequestResult HealResult;
public HealRequest(RequestResult result, HealRequestResult healResult)
{
Result = result;
HealResult = healResult;
}
}
private readonly List<RequestAction<AfflictionRequest>> afflictionRequests = new List<RequestAction<AfflictionRequest>>(); private readonly List<RequestAction<AfflictionRequest>> afflictionRequests = new List<RequestAction<AfflictionRequest>>();
private readonly List<RequestAction<PendingRequest>> pendingHealRequests = new List<RequestAction<PendingRequest>>(); private readonly List<RequestAction<PendingRequest>> pendingHealRequests = new List<RequestAction<PendingRequest>>();
@@ -96,7 +44,7 @@ namespace Barotrauma
} }
#endif #endif
if (!(info is { Character: { CharacterHealth: { } health } })) if (info is not { Character.CharacterHealth: { } health })
{ {
onReceived.Invoke(new AfflictionRequest(RequestResult.Error, ImmutableArray<NetAffliction>.Empty)); onReceived.Invoke(new AfflictionRequest(RequestResult.Error, ImmutableArray<NetAffliction>.Empty));
return; return;
@@ -123,14 +71,14 @@ namespace Barotrauma
public void Update(float deltaTime) public void Update(float deltaTime)
{ {
DateTimeOffset now = DateTimeOffset.Now; DateTimeOffset now = DateTimeOffset.Now;
UpdateQueue(afflictionRequests, now, onTimeout: callback => { callback(new AfflictionRequest(RequestResult.Timeout, ImmutableArray<NetAffliction>.Empty)); }); UpdateQueue(afflictionRequests, now, onTimeout: static callback => { callback(new AfflictionRequest(RequestResult.Timeout, ImmutableArray<NetAffliction>.Empty)); });
UpdateQueue(pendingHealRequests, now, onTimeout: callback => { callback(new PendingRequest(RequestResult.Timeout, ImmutableArray<NetCrewMember>.Empty)); }); UpdateQueue(pendingHealRequests, now, onTimeout: static callback => { callback(new PendingRequest(RequestResult.Timeout, NetCollection<NetCrewMember>.Empty)); });
UpdateQueue(healAllRequests, now, onTimeout: callback => { callback(new HealRequest(RequestResult.Timeout, HealRequestResult.Unknown)); }); UpdateQueue(healAllRequests, now, onTimeout: static callback => { callback(new HealRequest(RequestResult.Timeout, HealRequestResult.Unknown)); });
UpdateQueue(clearAllRequests, now, onTimeout: CallbackOnlyTimeout); UpdateQueue(clearAllRequests, now, onTimeout: CallbackOnlyTimeout);
UpdateQueue(addRequests, now, onTimeout: CallbackOnlyTimeout); UpdateQueue(addRequests, now, onTimeout: CallbackOnlyTimeout);
UpdateQueue(removeRequests, now, onTimeout: CallbackOnlyTimeout); UpdateQueue(removeRequests, now, onTimeout: CallbackOnlyTimeout);
void CallbackOnlyTimeout(Action<CallbackOnlyRequest> callback) { callback(new CallbackOnlyRequest(RequestResult.Timeout)); } static void CallbackOnlyTimeout(Action<CallbackOnlyRequest> callback) { callback(new CallbackOnlyRequest(RequestResult.Timeout)); }
} }
public bool IsAfflictionPending(NetCrewMember character, NetAffliction affliction) public bool IsAfflictionPending(NetCrewMember character, NetAffliction affliction)
@@ -148,9 +96,9 @@ namespace Barotrauma
private static bool TryDequeue<T>(List<RequestAction<T>> requestQueue, out Action<T> result) private static bool TryDequeue<T>(List<RequestAction<T>> requestQueue, out Action<T> result)
{ {
RequestAction<T>? first = requestQueue.FirstOrNull(); RequestAction<T>? first = requestQueue.FirstOrNull();
if (!(first is { } action)) if (first is not { } action)
{ {
result = _ => { }; result = static _ => { };
return false; return false;
} }
@@ -191,11 +139,25 @@ namespace Barotrauma
private static int GetPing() private static int GetPing()
{ {
if (GameMain.IsSingleplayer || !(GameMain.Client?.Name is { } ownName) || !(GameMain.NetworkMember?.ConnectedClients is { } clients)) { return 0; } if (GameMain.IsSingleplayer || GameMain.Client?.Name is not { } ownName || GameMain.NetworkMember?.ConnectedClients is not { } clients) { return 0; }
return (from client in clients where client.Name == ownName select client.Ping).FirstOrDefault(); return (from client in clients where client.Name == ownName select client.Ping).FirstOrDefault();
} }
public void TreatAllButtonAction(Action<CallbackOnlyRequest> onReceived)
{
if (GameMain.IsSingleplayer)
{
AddEverythingToPending();
onReceived(new CallbackOnlyRequest(RequestResult.Success));
OnUpdate?.Invoke();
return;
}
addRequests.Add(new RequestAction<CallbackOnlyRequest>(onReceived, GetTimeout()));
ClientSend(null, NetworkHeader.ADD_EVERYTHING_TO_PENDING, DeliveryMethod.Reliable);
}
public void HealAllButtonAction(Action<HealRequest> onReceived) public void HealAllButtonAction(Action<HealRequest> onReceived)
{ {
if (GameMain.IsSingleplayer) if (GameMain.IsSingleplayer)
@@ -296,8 +258,11 @@ namespace Barotrauma
private void NewAdditonReceived(IReadMessage inc, MessageFlag flag) private void NewAdditonReceived(IReadMessage inc, MessageFlag flag)
{ {
NetCrewMember crewMember = INetSerializableStruct.Read<NetCrewMember>(inc); var crewMembers = INetSerializableStruct.Read<NetCollection<NetCrewMember>>(inc);
InsertPendingCrewMember(crewMember); foreach (var crewMember in crewMembers)
{
InsertPendingCrewMember(crewMember);
}
if (flag == MessageFlag.Response && TryDequeue(addRequests, out var callback)) if (flag == MessageFlag.Response && TryDequeue(addRequests, out var callback))
{ {
callback(new CallbackOnlyRequest(RequestResult.Success)); callback(new CallbackOnlyRequest(RequestResult.Success));
@@ -318,11 +283,7 @@ namespace Barotrauma
private static void SendAfflictionRequest(CharacterInfo info) private static void SendAfflictionRequest(CharacterInfo info)
{ {
INetSerializableStruct crewMember = new NetCrewMember INetSerializableStruct crewMember = new NetCrewMember(info);
{
CharacterInfo = info,
Afflictions = Array.Empty<NetAffliction>()
};
ClientSend(crewMember, NetworkHeader.REQUEST_AFFLICTIONS, DeliveryMethod.Unreliable); ClientSend(crewMember, NetworkHeader.REQUEST_AFFLICTIONS, DeliveryMethod.Unreliable);
} }
@@ -337,17 +298,17 @@ namespace Barotrauma
NetCrewMember crewMember = INetSerializableStruct.Read<NetCrewMember>(inc); NetCrewMember crewMember = INetSerializableStruct.Read<NetCrewMember>(inc);
if (TryDequeue(afflictionRequests, out var callback)) if (TryDequeue(afflictionRequests, out var callback))
{ {
RequestResult result = crewMember.CharacterInfoID == 0 ? RequestResult.Error : RequestResult.Success; RequestResult result = crewMember.CharacterInfoID is 0 ? RequestResult.Error : RequestResult.Success;
callback(new AfflictionRequest(result, crewMember.Afflictions.ToImmutableArray())); callback(new AfflictionRequest(result, crewMember.Afflictions.ToImmutableArray()));
} }
} }
private void PendingRequestReceived(IReadMessage inc) private void PendingRequestReceived(IReadMessage inc)
{ {
NetPendingCrew pendingCrew = INetSerializableStruct.Read<NetPendingCrew>(inc); var pendingCrew = INetSerializableStruct.Read<NetCollection<NetCrewMember>>(inc);
if (TryDequeue(pendingHealRequests, out var callback)) if (TryDequeue(pendingHealRequests, out var callback))
{ {
callback(new PendingRequest(RequestResult.Success, pendingCrew.CrewMembers.ToImmutableArray())); callback(new PendingRequest(RequestResult.Success, pendingCrew));
} }
} }
@@ -0,0 +1,599 @@
using Barotrauma.Extensions;
using Barotrauma.IO;
using Barotrauma.Tutorials;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma;
static class ObjectiveManager
{
public class Segment
{
public readonly record struct Text(
Identifier Tag,
int Width = DefaultWidth,
int Height = DefaultHeight,
Anchor Anchor = Anchor.Center);
public readonly record struct Video(
string FullPath,
Identifier TextTag,
int Width = DefaultWidth,
int Height = DefaultHeight)
{
public string FileName => Path.GetFileName(FullPath.CleanUpPath());
public string ContentPath => Path.GetDirectoryName(FullPath.CleanUpPath());
}
private const int DefaultWidth = 450;
private const int DefaultHeight = 80;
public GUIImage ObjectiveStateIndicator;
public GUIButton ObjectiveButton;
public GUITextBlock LinkedTextBlock;
public LocalizedString ObjectiveText;
public readonly Identifier Id;
public readonly Text TextContent;
public readonly Video VideoContent;
public readonly AutoPlayVideo AutoPlayVideo;
public Action OnClickObjective;
public bool IsCompleted { get; set; }
public bool CanBeCompleted { get; set; }
public Identifier ParentId { get; set; }
public TutorialSegmentType SegmentType { get; private set; }
public static Segment CreateInfoBoxSegment(Identifier id, Identifier objectiveTextTag, AutoPlayVideo autoPlayVideo, Text textContent = default, Video videoContent = default)
{
return new Segment(id, objectiveTextTag, autoPlayVideo, textContent, videoContent);
}
public static Segment CreateMessageBoxSegment(Identifier id, Identifier objectiveTextTag, Action onClickObjective)
{
return new Segment(id, objectiveTextTag, onClickObjective);
}
public static Segment CreateObjectiveSegment(Identifier id, Identifier objectiveTextTag)
{
return new Segment(id, objectiveTextTag);
}
private Segment(Identifier id, Identifier objectiveTextTag, AutoPlayVideo autoPlayVideo, Text textContent = default, Video videoContent = default)
{
Id = id;
ObjectiveText = TextManager.ParseInputTypes(TextManager.Get(objectiveTextTag));
AutoPlayVideo = autoPlayVideo;
TextContent = textContent;
VideoContent = videoContent;
SegmentType = TutorialSegmentType.InfoBox;
}
private Segment(Identifier id, Identifier objectiveTextTag, Action onClickObjective)
{
Id = id;
ObjectiveText = TextManager.ParseInputTypes(TextManager.Get(objectiveTextTag));
OnClickObjective = onClickObjective;
SegmentType = TutorialSegmentType.MessageBox;
}
private Segment(Identifier id, Identifier objectiveTextTag)
{
Id = id;
ObjectiveText = TextManager.ParseInputTypes(TextManager.Get(objectiveTextTag));
SegmentType = TutorialSegmentType.Objective;
}
public void ConnectMessageBox(Segment messageBoxSegment)
{
SegmentType = TutorialSegmentType.MessageBox;
OnClickObjective = messageBoxSegment.OnClickObjective;
}
}
private readonly record struct ScreenSettings(
Point ScreenResolution = default,
float UiScale = default,
WindowMode WindowMode = default)
{
public bool HaveChanged() =>
GameMain.GraphicsWidth != ScreenResolution.X ||
GameMain.GraphicsHeight != ScreenResolution.Y ||
GUI.Scale != UiScale ||
GameSettings.CurrentConfig.Graphics.DisplayMode != WindowMode;
};
private const float ObjectiveComponentAnimationTime = 1.5f;
public static bool ContentRunning { get; private set; }
public static VideoPlayer VideoPlayer { get; } = new VideoPlayer();
private static Segment ActiveContentSegment { get; set; }
private readonly static List<Segment> activeObjectives = new List<Segment>();
private static GUIComponent infoBox;
private static Action infoBoxClosedCallback;
private static ScreenSettings screenSettings;
private static GUILayoutGroup objectiveGroup;
private static LocalizedString objectiveTextTranslated;
public static void AddToGUIUpdateList()
{
if (screenSettings.HaveChanged())
{
CreateObjectiveFrame();
}
if (activeObjectives.Count > 0 && GameMain.GameSession?.Campaign is not { ShowCampaignUI: true })
{
objectiveGroup?.AddToGUIUpdateList(order: -1);
}
infoBox?.AddToGUIUpdateList(order: 100);
VideoPlayer.AddToGUIUpdateList(order: 100);
}
public static void TriggerTutorialSegment(Segment segment, bool connectObjective = false)
{
if (segment.SegmentType != TutorialSegmentType.InfoBox)
{
activeObjectives.Add(segment);
AddToObjectiveList(segment, connectObjective);
return;
}
Inventory.DraggingItems.Clear();
ContentRunning = true;
ActiveContentSegment = segment;
var title = TextManager.Get(segment.Id);
LocalizedString tutorialText = TextManager.GetFormatted(segment.TextContent.Tag);
tutorialText = TextManager.ParseInputTypes(tutorialText);
switch (segment.AutoPlayVideo)
{
case AutoPlayVideo.Yes:
infoBox = CreateInfoFrame(
title,
tutorialText,
segment.TextContent.Width,
segment.TextContent.Height,
segment.TextContent.Anchor,
hasButton: true,
onInfoBoxClosed: LoadActiveContentVideo);
break;
case AutoPlayVideo.No:
infoBox = CreateInfoFrame(
title,
tutorialText,
segment.TextContent.Width,
segment.TextContent.Height,
segment.TextContent.Anchor,
hasButton: true,
onInfoBoxClosed: StopCurrentContentSegment,
onVideoButtonClicked: LoadActiveContentVideo);
break;
}
}
public static void CompleteTutorialSegment(Identifier segmentId)
{
if (GetActiveObjective(segmentId) is not Segment segment || !segment.CanBeCompleted || segment.IsCompleted)
{
return;
}
if (!MarkSegmentCompleted(segment))
{
return;
}
if (GameMain.GameSession?.GameMode is TutorialMode tutorialMode)
{
GameAnalyticsManager.AddDesignEvent($"Tutorial:{tutorialMode.Tutorial?.Identifier}:{segmentId}:Completed");
}
else if (GameMain.GameSession?.GameMode is CampaignMode campaign)
{
GameAnalyticsManager.AddDesignEvent($"Tutorial:CampaignMode:{segmentId}:Completed");
campaign?.CampaignMetadata?.SetValue(segmentId, true);
}
}
public static bool MarkSegmentCompleted(Segment segment, bool flash = true)
{
segment.IsCompleted = true;
if (GUIStyle.GetComponentStyle("ObjectiveIndicatorCompleted") is GUIComponentStyle style)
{
if (segment.ObjectiveStateIndicator.Style == style)
{
return false;
}
segment.ObjectiveStateIndicator.ApplyStyle(style);
}
if (flash)
{
segment.ObjectiveStateIndicator.Parent.Flash(color: GUIStyle.Green, flashDuration: 0.35f, useRectangleFlash: true);
}
segment.ObjectiveButton.OnClicked = null;
segment.ObjectiveButton.CanBeFocused = false;
return true;
}
public static void RemoveTutorialSegment(Identifier segmentId)
{
if (GetActiveObjective(segmentId) is not Segment segment)
{
if (GameMain.GameSession?.GameMode is TutorialMode tutorialMode)
{
DebugConsole.AddWarning($"Warning: tried to remove the tutorial segment \"{segmentId}\" in tutorial \"{tutorialMode.Tutorial?.Identifier}\" but it isn't active!");
}
return;
}
segment.ObjectiveStateIndicator.FadeOut(ObjectiveComponentAnimationTime, false);
segment.LinkedTextBlock.FadeOut(ObjectiveComponentAnimationTime, false);
var parent = segment.LinkedTextBlock.Parent;
parent.FadeOut(ObjectiveComponentAnimationTime, true, onRemove: () =>
{
activeObjectives.Remove(segment);
objectiveGroup?.Recalculate();
});
parent.RectTransform.MoveOverTime(GetObjectiveHiddenPosition(parent.RectTransform), ObjectiveComponentAnimationTime);
segment.ObjectiveButton.OnClicked = null;
segment.ObjectiveButton.CanBeFocused = false;
}
public static void CloseActiveContentGUI()
{
if (VideoPlayer.IsPlaying)
{
VideoPlayer.Stop();
}
else if (infoBox != null)
{
CloseInfoFrame();
}
}
public static void ClearContent()
{
ContentRunning = false;
infoBox = null;
}
public static void ResetUI()
{
ContentRunning = false;
infoBox = null;
VideoPlayer.Remove();
}
#region Objectives
private static Segment GetActiveObjective(Identifier id) => activeObjectives.FirstOrDefault(s => s.Id == id);
public static void ResetObjectives()
{
activeObjectives.Clear();
ActiveContentSegment = null;
CreateObjectiveFrame();
}
/// <summary>
/// Create the objective list that holds the objectives (called on start and on resolution change)
/// </summary>
private static void CreateObjectiveFrame()
{
var objectiveListFrame = new GUIFrame(HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.TutorialObjectiveListArea, GUI.Canvas), style: null)
{
CanBeFocused = false
};
objectiveGroup = new GUILayoutGroup(new RectTransform(Vector2.One, objectiveListFrame.RectTransform))
{
AbsoluteSpacing = (int)GUIStyle.Font.LineHeight
};
for (int i = 0; i < activeObjectives.Count; i++)
{
AddToObjectiveList(activeObjectives[i]);
}
screenSettings = new ScreenSettings(new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight), GUI.Scale, GameSettings.CurrentConfig.Graphics.DisplayMode);
}
/// <summary>
/// Stops content running and adds the active segment to the objective list
/// </summary>
private static void StopCurrentContentSegment()
{
if (!ActiveContentSegment.ObjectiveText.IsNullOrEmpty())
{
activeObjectives.Add(ActiveContentSegment);
AddToObjectiveList(ActiveContentSegment);
}
ContentRunning = false;
ActiveContentSegment = null;
}
/// <summary>
/// Adds the segment to the objective list
/// </summary>
private static void AddToObjectiveList(Segment segment, bool connectExisting = false)
{
if (connectExisting)
{
if (activeObjectives.Find(o => o.Id == segment.Id) is { } existingSegment)
{
existingSegment.ConnectMessageBox(segment);
SetButtonBehavior(existingSegment);
}
return;
}
var frameRt = new RectTransform(new Vector2(1.0f, 0.1f), objectiveGroup.RectTransform)
{
MinSize = new Point(0, objectiveGroup.AbsoluteSpacing)
};
Segment parentSegment = activeObjectives.FirstOrDefault(s => s.Id == segment.ParentId);
if (parentSegment is not null)
{
// Add this child as the last child in case there are other existing children already
int totalChildren = activeObjectives.Count(s => s.ParentId == segment.ParentId);
int childIndex = activeObjectives.IndexOf(parentSegment) + totalChildren;
if (objectiveGroup.RectTransform.GetChildIndex(frameRt) != childIndex)
{
frameRt.RepositionChildInHierarchy(childIndex);
activeObjectives.Remove(segment);
activeObjectives.Insert(childIndex, segment);
}
}
frameRt.AbsoluteOffset = GetObjectiveHiddenPosition();
var frame = new GUIFrame(frameRt, style: null)
{
CanBeFocused = true
};
objectiveGroup.Recalculate();
int textWidth = parentSegment is null ? frameRt.Rect.Width - objectiveGroup.AbsoluteSpacing
: frameRt.Rect.Width - 2 * objectiveGroup.AbsoluteSpacing;
segment.LinkedTextBlock = new GUITextBlock(
new RectTransform(new Point(textWidth, 0), frame.RectTransform, anchor: Anchor.TopRight),
TextManager.ParseInputTypes(segment.ObjectiveText),
wrap: true);
var size = new Point(segment.LinkedTextBlock.Rect.Width, segment.LinkedTextBlock.Rect.Height);
segment.LinkedTextBlock.RectTransform.NonScaledSize = size;
segment.LinkedTextBlock.RectTransform.MinSize = size;
segment.LinkedTextBlock.RectTransform.MaxSize = size;
segment.LinkedTextBlock.RectTransform.IsFixedSize = true;
frame.RectTransform.Resize(new Point(frame.Rect.Width, segment.LinkedTextBlock.RectTransform.Rect.Height), resizeChildren: false);
frame.RectTransform.IsFixedSize = true;
var indicatorRt = new RectTransform(new Point(objectiveGroup.AbsoluteSpacing), frame.RectTransform, isFixedSize: true);
if (parentSegment is not null)
{
indicatorRt.AbsoluteOffset = new Point(objectiveGroup.AbsoluteSpacing, 0);
}
segment.ObjectiveStateIndicator = new GUIImage(indicatorRt, "ObjectiveIndicatorIncomplete");
SetTransparent(segment.LinkedTextBlock);
objectiveTextTranslated ??= TextManager.Get("Tutorial.Objective");
segment.ObjectiveButton = new GUIButton(new RectTransform(Vector2.One, segment.LinkedTextBlock.RectTransform, Anchor.TopLeft, Pivot.TopLeft), style: null)
{
ToolTip = objectiveTextTranslated
};
SetButtonBehavior(segment);
SetTransparent(segment.ObjectiveButton);
frameRt.MoveOverTime(new Point(0, frameRt.AbsoluteOffset.Y), ObjectiveComponentAnimationTime, onDoneMoving: () => objectiveGroup?.Recalculate());
// Check if the objective has already been completed in the campaign
if (!segment.IsCompleted && GameMain.GameSession?.Campaign?.CampaignMetadata is CampaignMetadata data && data.GetBoolean(segment.Id))
{
MarkSegmentCompleted(segment, flash: false);
}
static void SetTransparent(GUIComponent component) => component.Color = component.HoverColor = component.PressedColor = component.SelectedColor = Color.Transparent;
void SetButtonBehavior(Segment segment)
{
segment.ObjectiveButton.CanBeFocused = segment.SegmentType != TutorialSegmentType.Objective;
segment.ObjectiveButton.OnClicked = (GUIButton btn, object userdata) =>
{
if (segment.SegmentType == TutorialSegmentType.InfoBox)
{
if (segment.AutoPlayVideo == AutoPlayVideo.Yes)
{
ReplaySegmentVideo(segment);
}
else
{
ShowSegmentText(segment);
}
}
else if (segment.SegmentType == TutorialSegmentType.MessageBox)
{
segment.OnClickObjective?.Invoke();
}
return true;
};
}
}
private static void ReplaySegmentVideo(Segment segment)
{
if (ContentRunning) { return; }
Inventory.DraggingItems.Clear();
ContentRunning = true;
LoadVideo(segment);
}
private static void ShowSegmentText(Segment segment)
{
if (ContentRunning) { return; }
Inventory.DraggingItems.Clear();
ContentRunning = true;
ActiveContentSegment = segment;
infoBox = CreateInfoFrame(
TextManager.Get(segment.Id),
TextManager.Get(segment.TextContent.Tag),
segment.TextContent.Width,
segment.TextContent.Height,
segment.TextContent.Anchor,
hasButton: true,
onInfoBoxClosed: () => ContentRunning = false,
onVideoButtonClicked: () => LoadVideo(segment));
}
private static Point GetObjectiveHiddenPosition(RectTransform rt = null)
{
return new Point(GameMain.GraphicsWidth - objectiveGroup.Rect.X, rt?.AbsoluteOffset.Y ?? 0);
}
public static Segment GetObjective(Identifier identifier)
{
return activeObjectives.FirstOrDefault(o => o.Id == identifier);
}
public static bool AllActiveObjectivesCompleted()
{
return activeObjectives.None() || activeObjectives.All(o => !o.CanBeCompleted || o.IsCompleted);
}
public static bool AnyObjectives => activeObjectives.Any();
#endregion
#region InfoFrame
private static void CloseInfoFrame() => CloseInfoFrame(null, null);
private static bool CloseInfoFrame(GUIButton button, object userData)
{
infoBox = null;
infoBoxClosedCallback?.Invoke();
return true;
}
/// <summary>
// Creates and displays a tutorial info box
/// </summary>
private static GUIComponent CreateInfoFrame(LocalizedString title, LocalizedString text, int width = 300, int height = 80, Anchor anchor = Anchor.TopRight, bool hasButton = false, Action onInfoBoxClosed = null, Action onVideoButtonClicked = null)
{
if (hasButton)
{
height += 60;
}
width = (int)(width * GUI.Scale);
height = (int)(height * GUI.Scale);
LocalizedString wrappedText = ToolBox.WrapText(text, width, GUIStyle.Font);
height += (int)GUIStyle.Font.MeasureString(wrappedText).Y;
if (title.Length > 0)
{
height += (int)GUIStyle.Font.MeasureString(title).Y + (int)(150 * GUI.Scale);
}
var background = new GUIFrame(new RectTransform(new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight), GUI.Canvas, Anchor.Center), style: "GUIBackgroundBlocker");
var infoBlock = new GUIFrame(new RectTransform(new Point(width, height), background.RectTransform, anchor));
infoBlock.Flash(GUIStyle.Green);
var infoContent = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.9f), infoBlock.RectTransform, Anchor.Center))
{
Stretch = true,
AbsoluteSpacing = 5
};
if (title.Length > 0)
{
var titleBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoContent.RectTransform),
title, font: GUIStyle.LargeFont, textAlignment: Alignment.Center, textColor: new Color(253, 174, 0));
titleBlock.RectTransform.IsFixedSize = true;
}
text = RichString.Rich(text);
GUITextBlock textBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoContent.RectTransform), text, wrap: true);
textBlock.RectTransform.IsFixedSize = true;
infoBoxClosedCallback = onInfoBoxClosed;
if (hasButton)
{
var buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.15f), infoContent.RectTransform), isHorizontal: true)
{
RelativeSpacing = 0.1f
};
buttonContainer.RectTransform.IsFixedSize = true;
if (onVideoButtonClicked != null)
{
buttonContainer.Stretch = true;
var videoButton = new GUIButton(new RectTransform(new Vector2(0.4f, 1.0f), buttonContainer.RectTransform),
TextManager.Get("Video"), style: "GUIButtonLarge")
{
OnClicked = (GUIButton button, object obj) =>
{
onVideoButtonClicked();
return true;
}
};
}
else
{
buttonContainer.Stretch = false;
buttonContainer.ChildAnchor = Anchor.Center;
}
var okButton = new GUIButton(new RectTransform(new Vector2(0.4f, 1.0f), buttonContainer.RectTransform),
TextManager.Get("OK"), style: "GUIButtonLarge")
{
OnClicked = CloseInfoFrame
};
}
infoBlock.RectTransform.NonScaledSize = new Point(infoBlock.Rect.Width, (int)(infoContent.Children.Sum(c => c.Rect.Height + infoContent.AbsoluteSpacing) / infoContent.RectTransform.RelativeSize.Y));
SoundPlayer.PlayUISound(GUISoundType.UIMessage);
return background;
}
#endregion
#region Video
private static void LoadVideo(Segment segment)
{
if (segment.AutoPlayVideo == AutoPlayVideo.Yes)
{
VideoPlayer.LoadContent(
contentPath: segment.VideoContent.ContentPath,
videoSettings: new VideoPlayer.VideoSettings(segment.VideoContent.FileName),
textSettings: new VideoPlayer.TextSettings(segment.VideoContent.TextTag, segment.VideoContent.Width),
contentId: segment.Id,
startPlayback: true,
objective: segment.ObjectiveText,
onStop: StopCurrentContentSegment);
}
else
{
VideoPlayer.LoadContent(
contentPath: segment.VideoContent.ContentPath,
videoSettings: new VideoPlayer.VideoSettings(segment.VideoContent.FileName),
textSettings: null,
contentId: segment.Id,
startPlayback: true,
objective: string.Empty);
}
}
private static void LoadActiveContentVideo() => LoadVideo(ActiveContentSegment);
#endregion
}
@@ -801,7 +801,7 @@ namespace Barotrauma
} }
else if (character.HeldItems.Any(i => else if (character.HeldItems.Any(i =>
i.OwnInventory != null && i.OwnInventory != null &&
(i.OwnInventory.CanBePut(item) || (i.OwnInventory.Capacity == 1 && i.OwnInventory.AllowSwappingContainedItems && i.OwnInventory.Container.CanBeContained(item))))) (i.OwnInventory.CanBePut(item) || ((i.OwnInventory.Capacity == 1 || i.OwnInventory.Container.HasSubContainers) && i.OwnInventory.AllowSwappingContainedItems && i.OwnInventory.Container.CanBeContained(item)))))
{ {
return QuickUseAction.PutToEquippedItem; return QuickUseAction.PutToEquippedItem;
} }
@@ -975,7 +975,7 @@ namespace Barotrauma
heldItem.OwnInventory.GetItemAt(0)?.Prefab == item.Prefab && heldItem.OwnInventory.GetItemAt(0)?.Prefab == item.Prefab &&
heldItem.OwnInventory.GetItemsAt(0).Count() > 1; heldItem.OwnInventory.GetItemsAt(0).Count() > 1;
if (heldItem.OwnInventory.TryPutItem(item, Character.Controlled) || if (heldItem.OwnInventory.TryPutItem(item, Character.Controlled) ||
(heldItem.OwnInventory.Capacity == 1 && heldItem.OwnInventory.TryPutItem(item, 0, allowSwapping: !disallowSwapping, allowCombine: false, user: Character.Controlled))) ((heldItem.OwnInventory.Capacity == 1 || heldItem.OwnInventory.Container.HasSubContainers) && heldItem.OwnInventory.TryPutItem(item, 0, allowSwapping: !disallowSwapping, allowCombine: false, user: Character.Controlled)))
{ {
success = true; success = true;
for (int j = 0; j < capacity; j++) for (int j = 0; j < capacity; j++)
@@ -225,67 +225,59 @@ namespace Barotrauma.Items.Components
if (character == Character.Controlled) if (character == Character.Controlled)
{ {
if (targetSections.Count == 0) { return false; } if (targetSections.Count == 0) { return false; }
Spray(deltaTime); Spray(character, deltaTime, applyColors: true);
return true; return true;
} }
else else
{ {
//allow remote players to use the sprayer, but don't actually color the walls (we'll receive the data from the server) //allow remote players to use the sprayer, but don't actually color the walls (we'll receive the data from the server)
return character.IsRemotePlayer; Spray(character, deltaTime, applyColors: false);
return true;
} }
} }
public void Spray(float deltaTime) public void Spray(Character user, float deltaTime, bool applyColors)
{ {
if (targetSections.Count == 0) { return; }
Item liquidItem = liquidContainer?.Inventory.FirstOrDefault(); Item liquidItem = liquidContainer?.Inventory.FirstOrDefault();
if (liquidItem == null) { return; } if (liquidItem == null) { return; }
bool isCleaning = false; bool isCleaning = false;
liquidColors.TryGetValue(liquidItem.Prefab.Identifier, out color); liquidColors.TryGetValue(liquidItem.Prefab.Identifier, out color);
// Ethanol or other cleaning solvent if (applyColors && targetSections.Any())
if (color.A == 0) { isCleaning = true; }
float sizeAdjustedSprayStrength = SprayStrength / targetSections.Count;
if (!isCleaning)
{ {
for (int i = 0; i < targetSections.Count; i++) // Ethanol or other cleaning solvent
if (color.A == 0) { isCleaning = true; }
float sizeAdjustedSprayStrength = SprayStrength / targetSections.Count;
if (!isCleaning)
{ {
targetHull.IncreaseSectionColorOrStrength(targetSections[i], color, sizeAdjustedSprayStrength * deltaTime, true, false); for (int i = 0; i < targetSections.Count; i++)
{
targetHull.IncreaseSectionColorOrStrength(targetSections[i], color, sizeAdjustedSprayStrength * deltaTime, true, false);
}
if (GameMain.GameSession != null)
{
GameMain.GameSession.TimeSpentCleaning += deltaTime;
}
} }
if (GameMain.GameSession != null) else
{ {
GameMain.GameSession.TimeSpentCleaning += deltaTime; for (int i = 0; i < targetSections.Count; i++)
} {
} targetHull.CleanSection(targetSections[i], -sizeAdjustedSprayStrength * deltaTime, true);
else }
{ if (GameMain.GameSession != null)
for (int i = 0; i < targetSections.Count; i++) {
{ GameMain.GameSession.TimeSpentPainting += deltaTime;
targetHull.CleanSection(targetSections[i], -sizeAdjustedSprayStrength * deltaTime, true); }
}
if (GameMain.GameSession != null)
{
GameMain.GameSession.TimeSpentPainting += deltaTime;
} }
} }
Vector2 particleStartPos = item.WorldPosition + ConvertUnits.ToDisplayUnits(TransformedBarrelPos); Vector2 particleStartPos = item.WorldPosition + ConvertUnits.ToDisplayUnits(TransformedBarrelPos);
Vector2 particleEndPos = Vector2.Zero; Vector2 particleEndPos = user.CursorWorldPosition;
for (int i = 0; i < targetSections.Count; i++) //the cursor position is not exact for remote players, we only know the direction they're aiming at but not the distance
{ // -> use 50% range, looks good enough
particleEndPos += new Vector2(targetSections[i].Rect.Center.X, targetSections[i].Rect.Y - targetSections[i].Rect.Height / 2) + targetHull.Rect.Location.ToVector2(); float dist = Math.Min(Vector2.Distance(particleStartPos, particleEndPos), Range * 0.5f);
}
particleEndPos /= targetSections.Count;
if (targetHull?.Submarine != null)
{
particleEndPos += targetHull.Submarine.Position;
}
float dist = Vector2.Distance(particleStartPos, particleEndPos);
foreach (ParticleEmitter particleEmitter in particleEmitters) foreach (ParticleEmitter particleEmitter in particleEmitters)
{ {
float particleAngle = item.body.Rotation + ((item.body.Dir > 0.0f) ? 0.0f : MathHelper.Pi); float particleAngle = item.body.Rotation + ((item.body.Dir > 0.0f) ? 0.0f : MathHelper.Pi);
@@ -309,21 +309,53 @@ namespace Barotrauma.Items.Components
Vector2 currentItemPos = transformedItemPos; Vector2 currentItemPos = transformedItemPos;
SpriteEffects spriteEffects = SpriteEffects.None;
if ((item.body != null && item.body.Dir == -1) || item.FlippedX)
{
spriteEffects |= MathUtils.NearlyEqual(ItemRotation % 180, 90.0f) ? SpriteEffects.FlipVertically : SpriteEffects.FlipHorizontally;
}
if (item.FlippedY)
{
spriteEffects |= MathUtils.NearlyEqual(ItemRotation % 180, 90.0f) ? SpriteEffects.FlipHorizontally : SpriteEffects.FlipVertically;
}
bool isWiringMode = SubEditorScreen.TransparentWiringMode && SubEditorScreen.IsWiringMode(); bool isWiringMode = SubEditorScreen.TransparentWiringMode && SubEditorScreen.IsWiringMode();
int i = 0; int i = 0;
foreach (Item containedItem in Inventory.AllItems) foreach (Item containedItem in Inventory.AllItems)
{ {
Vector2 itemPos = currentItemPos;
var relatedItem = FindContainableItem(containedItem);
if (relatedItem != null)
{
if (relatedItem.Hide.HasValue && relatedItem.Hide.Value) { continue; }
if (relatedItem.ItemPos.HasValue)
{
Vector2 pos = relatedItem.ItemPos.Value;
if (item.body != null)
{
Matrix transform = Matrix.CreateRotationZ(item.body.DrawRotation);
pos.X *= item.body.Dir;
itemPos = Vector2.Transform(pos, transform) + item.body.DrawPosition;
}
else
{
itemPos = pos;
// This code is aped based on above. Not tested.
if (item.FlippedX)
{
itemPos.X = -itemPos.X;
itemPos.X += item.Rect.Width;
}
if (item.FlippedY)
{
itemPos.Y = -itemPos.Y;
itemPos.Y -= item.Rect.Height;
}
itemPos += new Vector2(item.Rect.X, item.Rect.Y);
if (item.Submarine != null)
{
itemPos += item.Submarine.DrawPosition;
}
if (Math.Abs(item.RotationRad) > 0.01f)
{
Matrix transform = Matrix.CreateRotationZ(-item.RotationRad);
itemPos = Vector2.Transform(itemPos - item.DrawPosition, transform) + item.DrawPosition;
}
}
}
}
if (containedItem?.Sprite == null) { continue; } if (containedItem?.Sprite == null) { continue; }
if (AutoInteractWithContained) if (AutoInteractWithContained)
@@ -343,19 +375,34 @@ namespace Barotrauma.Items.Components
} }
containedSpriteDepth = itemDepth + (containedSpriteDepth - (item.Sprite?.Depth ?? item.SpriteDepth)) / 10000.0f; containedSpriteDepth = itemDepth + (containedSpriteDepth - (item.Sprite?.Depth ?? item.SpriteDepth)) / 10000.0f;
SpriteEffects spriteEffects = SpriteEffects.None;
float spriteRotation = ItemRotation;
if (relatedItem != null && relatedItem.Rotation != 0)
{
spriteRotation = relatedItem.Rotation;
}
if ((item.body != null && item.body.Dir == -1) || item.FlippedX)
{
spriteEffects |= MathUtils.NearlyEqual(spriteRotation % 180, 90.0f) ? SpriteEffects.FlipVertically : SpriteEffects.FlipHorizontally;
}
if (item.FlippedY)
{
spriteEffects |= MathUtils.NearlyEqual(spriteRotation % 180, 90.0f) ? SpriteEffects.FlipHorizontally : SpriteEffects.FlipVertically;
}
containedItem.Sprite.Draw( containedItem.Sprite.Draw(
spriteBatch, spriteBatch,
new Vector2(currentItemPos.X, -currentItemPos.Y), new Vector2(itemPos.X, -itemPos.Y),
isWiringMode ? containedItem.GetSpriteColor(withHighlight: true) * 0.15f : containedItem.GetSpriteColor(withHighlight: true), isWiringMode ? containedItem.GetSpriteColor(withHighlight: true) * 0.15f : containedItem.GetSpriteColor(withHighlight: true),
origin, origin,
-(containedItem.body == null ? 0.0f : containedItem.body.DrawRotation ), -(containedItem.body == null ? 0.0f : containedItem.body.DrawRotation),
containedItem.Scale, containedItem.Scale,
spriteEffects, spriteEffects,
depth: containedSpriteDepth); depth: containedSpriteDepth);
foreach (ItemContainer ic in containedItem.GetComponents<ItemContainer>()) foreach (ItemContainer ic in containedItem.GetComponents<ItemContainer>())
{ {
if (ic.hideItems) continue; if (ic.hideItems) { continue; }
ic.DrawContainedItems(spriteBatch, containedSpriteDepth); ic.DrawContainedItems(spriteBatch, containedSpriteDepth);
} }
@@ -86,7 +86,7 @@ namespace Barotrauma.Items.Components
public override void FlipX(bool relativeToSub) public override void FlipX(bool relativeToSub)
{ {
if (Light?.LightSprite != null && item.Prefab.CanSpriteFlipX && item.body == null) if (Light?.LightSprite != null && item.Prefab.CanSpriteFlipX)
{ {
Light.LightSpriteEffect = Light.LightSpriteEffect == SpriteEffects.None ? Light.LightSpriteEffect = Light.LightSpriteEffect == SpriteEffects.None ?
SpriteEffects.FlipHorizontally : SpriteEffects.None; SpriteEffects.FlipHorizontally : SpriteEffects.None;
@@ -811,7 +811,7 @@ namespace Barotrauma.Items.Components
if (distSqr > t.SoundRange * t.SoundRange * 2) { continue; } if (distSqr > t.SoundRange * t.SoundRange * 2) { continue; }
float dist = (float)Math.Sqrt(distSqr); float dist = (float)Math.Sqrt(distSqr);
if (dist > prevPassivePingRadius * Range && dist <= passivePingRadius * Range && Rand.Int(sonarBlips.Count) < 500) if (dist > prevPassivePingRadius * Range && dist <= passivePingRadius * Range && Rand.Int(sonarBlips.Count) < 500 && t.IsWithinSector(transducerCenter))
{ {
Ping(t.WorldPosition, transducerCenter, Ping(t.WorldPosition, transducerCenter,
Math.Min(t.SoundRange, range * 0.5f) * displayScale, 0, displayScale, Math.Min(t.SoundRange, range * 0.5f), Math.Min(t.SoundRange, range * 0.5f) * displayScale, 0, displayScale, Math.Min(t.SoundRange, range * 0.5f),
@@ -1276,7 +1276,7 @@ namespace Barotrauma.Items.Components
float indicatorSector = sector * 0.75f; float indicatorSector = sector * 0.75f;
float indicatorSectorLength = (float)(midLength / Math.Cos(indicatorSector)); float indicatorSectorLength = (float)(midLength / Math.Cos(indicatorSector));
bool withinSector = bool withinSector =
(Math.Abs(diff.X) < steering.ActiveDockingSource.DistanceTolerance.X && Math.Abs(diff.Y) < steering.ActiveDockingSource.DistanceTolerance.Y) || (Math.Abs(diff.X) < steering.ActiveDockingSource.DistanceTolerance.X && Math.Abs(diff.Y) < steering.ActiveDockingSource.DistanceTolerance.Y) ||
Vector2.Dot(normalizedDockingDir, MathUtils.RotatePoint(normalizedDockingDir, indicatorSector)) < Vector2.Dot(normalizedDockingDir, MathUtils.RotatePoint(normalizedDockingDir, indicatorSector)) <
Vector2.Dot(normalizedDockingDir, Vector2.Normalize(dockingDir)); Vector2.Dot(normalizedDockingDir, Vector2.Normalize(dockingDir));
@@ -54,7 +54,7 @@ namespace Barotrauma.Items.Components
private bool? swapDestinationOrder; private bool? swapDestinationOrder;
private GUIMessageBox enterOutpostPrompt; private GUIMessageBox enterOutpostPrompt, exitOutpostPrompt;
private bool levelStartSelected; private bool levelStartSelected;
public bool LevelStartSelected public bool LevelStartSelected
@@ -382,9 +382,20 @@ namespace Barotrauma.Items.Components
DockingSources.Any(d => d.Docked && (d.DockingTarget?.Item.Submarine?.Info?.IsOutpost ?? false))) DockingSources.Any(d => d.Docked && (d.DockingTarget?.Item.Submarine?.Info?.IsOutpost ?? false)))
{ {
// Undocking from an outpost // Undocking from an outpost
campaign.ShowCampaignUI = true; if (!ObjectiveManager.AllActiveObjectivesCompleted())
campaign.CampaignUI.SelectTab(CampaignMode.InteractionType.Map); {
return false; exitOutpostPrompt = new GUIMessageBox("",
TextManager.GetWithVariable("CampaignExitTutorialOutpostPrompt", "[locationname]", campaign.Map.CurrentLocation.Name),
new LocalizedString[] { TextManager.Get("yes"), TextManager.Get("no") });
exitOutpostPrompt.Buttons[0].OnClicked += (_, _) =>
{
exitOutpostPrompt.Close();
return OpenMap(campaign);
};
exitOutpostPrompt.Buttons[1].OnClicked += exitOutpostPrompt.Close;
return false;
}
return OpenMap(campaign);
} }
else if (!Level.IsLoadedOutpost && DockingModeEnabled && ActiveDockingSource != null && else if (!Level.IsLoadedOutpost && DockingModeEnabled && ActiveDockingSource != null &&
!ActiveDockingSource.Docked && DockingTarget?.Item?.Submarine == Level.Loaded.StartOutpost && (DockingTarget?.Item?.Submarine?.Info.IsOutpost ?? false)) !ActiveDockingSource.Docked && DockingTarget?.Item?.Submarine == Level.Loaded.StartOutpost && (DockingTarget?.Item?.Submarine?.Info.IsOutpost ?? false))
@@ -419,6 +430,14 @@ namespace Barotrauma.Items.Components
return true; return true;
} }
}; };
bool OpenMap(CampaignMode campaign)
{
campaign.ShowCampaignUI = true;
campaign.CampaignUI.SelectTab(CampaignMode.InteractionType.Map);
return false;
}
void SendDockingSignal() void SendDockingSignal()
{ {
if (GameMain.Client == null) if (GameMain.Client == null)
@@ -431,6 +450,7 @@ namespace Barotrauma.Items.Components
item.CreateClientEvent(this); item.CreateClientEvent(this);
} }
} }
dockingButton.Font = GUIStyle.SubHeadingFont; dockingButton.Font = GUIStyle.SubHeadingFont;
dockingButton.TextBlock.RectTransform.MaxSize = new Point((int)(dockingButton.Rect.Width * 0.7f), int.MaxValue); dockingButton.TextBlock.RectTransform.MaxSize = new Point((int)(dockingButton.Rect.Width * 0.7f), int.MaxValue);
dockingButton.TextBlock.AutoScaleHorizontal = true; dockingButton.TextBlock.AutoScaleHorizontal = true;
@@ -913,6 +933,7 @@ namespace Barotrauma.Items.Components
maintainPosOriginIndicator?.Remove(); maintainPosOriginIndicator?.Remove();
steeringIndicator?.Remove(); steeringIndicator?.Remove();
enterOutpostPrompt?.Close(); enterOutpostPrompt?.Close();
exitOutpostPrompt?.Close();
pathFinder = null; pathFinder = null;
} }
@@ -293,7 +293,7 @@ namespace Barotrauma.Items.Components
if (target.Bleeding > 0.0f) if (target.Bleeding > 0.0f)
{ {
int bleedingTextIndex = MathHelper.Clamp((int)Math.Floor(target.Bleeding / 100.0f) * BleedingTexts.Length, 0, BleedingTexts.Length - 1); int bleedingTextIndex = MathHelper.Clamp((int)Math.Floor(target.Bleeding / 100.0f * BleedingTexts.Length), 0, BleedingTexts.Length - 1);
texts.Add(BleedingTexts[bleedingTextIndex]); texts.Add(BleedingTexts[bleedingTextIndex]);
textColors.Add(Color.Lerp(GUIStyle.Orange, GUIStyle.Red, target.Bleeding / 100.0f)); textColors.Add(Color.Lerp(GUIStyle.Orange, GUIStyle.Red, target.Bleeding / 100.0f));
} }
@@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using Barotrauma.Networking; using Barotrauma.Networking;
@@ -6,7 +7,7 @@ namespace Barotrauma.Items.Components
{ {
partial class Wearable : Pickable, IServerSerializable partial class Wearable : Pickable, IServerSerializable
{ {
private void GetDamageModifierText(ref LocalizedString description, DamageModifier damageModifier, Identifier afflictionIdentifier) private static void GetDamageModifierText(ref LocalizedString description, DamageModifier damageModifier, Identifier afflictionIdentifier)
{ {
int roundedValue = (int)Math.Round((1 - damageModifier.DamageMultiplier * damageModifier.ProbabilityMultiplier) * 100); int roundedValue = (int)Math.Round((1 - damageModifier.DamageMultiplier * damageModifier.ProbabilityMultiplier) * 100);
if (roundedValue == 0) { return; } if (roundedValue == 0) { return; }
@@ -21,6 +22,11 @@ namespace Barotrauma.Items.Components
} }
public override void AddTooltipInfo(ref LocalizedString name, ref LocalizedString description) public override void AddTooltipInfo(ref LocalizedString name, ref LocalizedString description)
{
AddTooltipInfo(damageModifiers, SkillModifiers, ref description);
}
public static void AddTooltipInfo(IReadOnlyList<DamageModifier> damageModifiers, IReadOnlyDictionary<Identifier, float> skillModifiers, ref LocalizedString description)
{ {
if (damageModifiers.Any()) if (damageModifiers.Any())
{ {
@@ -41,9 +47,9 @@ namespace Barotrauma.Items.Components
} }
} }
} }
if (SkillModifiers.Any()) if (skillModifiers.Any())
{ {
foreach (var skillModifier in SkillModifiers) foreach (var skillModifier in skillModifiers)
{ {
string colorStr = XMLExtensions.ToStringHex(GUIStyle.Green); string colorStr = XMLExtensions.ToStringHex(GUIStyle.Green);
int roundedValue = (int)Math.Round(skillModifier.Value); int roundedValue = (int)Math.Round(skillModifier.Value);
@@ -1610,15 +1610,19 @@ namespace Barotrauma
} }
else if (itemContainer.ShowTotalStackCapacityInContainedStateIndicator) else if (itemContainer.ShowTotalStackCapacityInContainedStateIndicator)
{ {
containedState = itemContainer.Inventory.AllItems.Count() / (float)(itemContainer.GetMaxStackSize(0) * itemContainer.Capacity); int ignoredItems = itemContainer.AllSubContainableItems == null ? 0 : itemContainer.AllSubContainableItems.Count;
int itemCount = itemContainer.Inventory.AllItems.Count() - ignoredItems;
containedState = itemCount / (float)(itemContainer.GetMaxStackSize(0) * itemContainer.MainContainerCapacity);
} }
else else
{ {
var containedItem = itemContainer.Inventory.slots[Math.Max(itemContainer.ContainedStateIndicatorSlot, 0)].FirstOrDefault(); var containedItem = itemContainer.Inventory.slots[Math.Max(itemContainer.ContainedStateIndicatorSlot, 0)].FirstOrDefault();
containedState = itemContainer.Inventory.Capacity == 1 || itemContainer.ContainedStateIndicatorSlot > -1 ? containedState = itemContainer.Inventory.Capacity == 1 || itemContainer.ContainedStateIndicatorSlot > -1 ?
(containedItem == null ? 0.0f : containedItem.Condition / containedItem.MaxCondition) : (containedItem == null ? 0.0f : containedItem.Condition / containedItem.MaxCondition) :
itemContainer.Inventory.slots.Count(i => !i.Empty()) / (float)itemContainer.Inventory.capacity; itemContainer.Inventory.slots.Count(i => !i.Empty()) / (float)itemContainer.Inventory.capacity;
if (containedItem != null && itemContainer.Inventory.Capacity == 1)
if (containedItem != null && (itemContainer.Inventory.Capacity == 1 || itemContainer.HasSubContainers))
{ {
int maxStackSize = Math.Min(containedItem.Prefab.MaxStackSize, itemContainer.GetMaxStackSize(0)); int maxStackSize = Math.Min(containedItem.Prefab.MaxStackSize, itemContainer.GetMaxStackSize(0));
if (maxStackSize > 1 || containedItem.Prefab.HideConditionBar) if (maxStackSize > 1 || containedItem.Prefab.HideConditionBar)
@@ -1,5 +1,5 @@
using Barotrauma.IO; using Barotrauma.Extensions;
using Barotrauma.Extensions; using Barotrauma.Items.Components;
using FarseerPhysics; using FarseerPhysics;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
@@ -7,7 +7,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Linq; using System.Linq;
using System.Xml.Linq;
namespace Barotrauma namespace Barotrauma
{ {
@@ -73,6 +72,9 @@ namespace Barotrauma
public float UpgradePreviewScale = 1.0f; public float UpgradePreviewScale = 1.0f;
private IReadOnlyList<DamageModifier> wearableDamageModifiers;
private IReadOnlyDictionary<Identifier, float> wearableSkillModifiers;
//only used to display correct color in the sub editor, item instances have their own property that can be edited on a per-item basis //only used to display correct color in the sub editor, item instances have their own property that can be edited on a per-item basis
[Serialize("1.0,1.0,1.0,1.0", IsPropertySaveable.No)] [Serialize("1.0,1.0,1.0,1.0", IsPropertySaveable.No)]
public Color InventoryIconColor { get; protected set; } public Color InventoryIconColor { get; protected set; }
@@ -101,6 +103,9 @@ namespace Barotrauma
var containedSprites = new List<ContainedItemSprite>(); var containedSprites = new List<ContainedItemSprite>();
var decorativeSpriteGroups = new Dictionary<int, List<DecorativeSprite>>(); var decorativeSpriteGroups = new Dictionary<int, List<DecorativeSprite>>();
var wearableDamageModifiers = new List<DamageModifier>();
var wearableSkillModifiers = new Dictionary<Identifier, float>();
foreach (var subElement in element.Elements()) foreach (var subElement in element.Elements())
{ {
switch (subElement.Name.LocalName.ToLowerInvariant()) switch (subElement.Name.LocalName.ToLowerInvariant())
@@ -198,8 +203,33 @@ namespace Barotrauma
containedSprites.Add(containedSprite); containedSprites.Add(containedSprite);
} }
break; break;
case "wearable":
foreach (ContentXElement wearableSubElement in subElement.Elements())
{
switch (wearableSubElement.Name.LocalName.ToLowerInvariant())
{
case "damagemodifier":
wearableDamageModifiers.Add(new DamageModifier(wearableSubElement, Name.Value + ", Wearable", checkErrors: false));
break;
case "skillmodifier":
Identifier skillIdentifier = wearableSubElement.GetAttributeIdentifier("skillidentifier", Identifier.Empty);
float skillValue = wearableSubElement.GetAttributeFloat("skillvalue", 0f);
if (wearableSkillModifiers.ContainsKey(skillIdentifier))
{
wearableSkillModifiers[skillIdentifier] += skillValue;
}
else
{
wearableSkillModifiers.TryAdd(skillIdentifier, skillValue);
}
break;
}
}
break;
} }
} }
this.wearableDamageModifiers = wearableDamageModifiers.ToImmutableList();
this.wearableSkillModifiers = wearableSkillModifiers.ToImmutableDictionary();
UpgradeOverrideSprites = upgradeOverrideSprites.Select(kvp => (kvp.Key, kvp.Value.ToImmutableArray())).ToImmutableDictionary(); UpgradeOverrideSprites = upgradeOverrideSprites.Select(kvp => (kvp.Key, kvp.Value.ToImmutableArray())).ToImmutableDictionary();
BrokenSprites = brokenSprites.ToImmutableArray(); BrokenSprites = brokenSprites.ToImmutableArray();
@@ -211,9 +241,21 @@ namespace Barotrauma
public bool CanCharacterBuy() public bool CanCharacterBuy()
{ {
if (!DefaultPrice.RequiresUnlock) { return true; } if (!DefaultPrice.RequiresUnlock) { return true; }
return Character.Controlled is not null && Character.Controlled.HasStoreAccessForItem(this); return Character.Controlled is not null && Character.Controlled.HasStoreAccessForItem(this);
} }
public LocalizedString GetTooltip()
{
LocalizedString tooltip = $"‖color:{XMLExtensions.ToStringHex(GUIStyle.TextColorBright)}‖{Name}‖color:end‖";
if (!Description.IsNullOrEmpty())
{
tooltip += $"\n{Description}";
}
if (wearableDamageModifiers.Any() || wearableSkillModifiers.Any())
{
Wearable.AddTooltipInfo(wearableDamageModifiers, wearableSkillModifiers, ref tooltip);
}
return tooltip;
}
public override void UpdatePlacing(Camera cam) public override void UpdatePlacing(Camera cam)
{ {
@@ -320,15 +362,7 @@ namespace Barotrauma
} }
else else
{ {
Vector2 position = Submarine.MouseToWorldGrid(Screen.Selected.Cam, Submarine.MainSub); Sprite.DrawTiled(spriteBatch, new Vector2(placeRect.X, -placeRect.Y), placeRect.Size.ToVector2(), SpriteColor * 0.8f);
Vector2 placeSize = Size * Scale;
if (placePosition != Vector2.Zero)
{
if (ResizeHorizontal) { placeSize.X = Math.Max(position.X - placePosition.X, placeSize.X); }
if (ResizeVertical) { placeSize.Y = Math.Max(placePosition.Y - position.Y, placeSize.Y); }
position = placePosition;
}
Sprite?.DrawTiled(spriteBatch, new Vector2(position.X, -position.Y), placeSize, color: SpriteColor);
} }
} }
} }
@@ -1,4 +1,5 @@
using Barotrauma.Networking; using Barotrauma.Extensions;
using Barotrauma.Networking;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using System; using System;
@@ -178,7 +179,7 @@ namespace Barotrauma
activeSprite?.Draw( activeSprite?.Draw(
spriteBatch, spriteBatch,
new Vector2(obj.Position.X, -obj.Position.Y) - camDiff * obj.Position.Z / 10000.0f, new Vector2(obj.Position.X, -obj.Position.Y) - camDiff * obj.Position.Z / 10000.0f,
Color.Lerp(Color.White, Level.Loaded.BackgroundTextureColor, obj.Position.Z / 3000.0f), Color.Lerp(obj.Prefab.SpriteColor, obj.Prefab.SpriteColor.Multiply(Level.Loaded.BackgroundTextureColor), obj.Position.Z / 3000.0f),
activeSprite.Origin, activeSprite.Origin,
obj.CurrentRotation, obj.CurrentRotation,
obj.CurrentScale, obj.CurrentScale,
@@ -200,7 +201,7 @@ namespace Barotrauma
obj.ActivePrefab.DeformableSprite.Origin, obj.ActivePrefab.DeformableSprite.Origin,
obj.CurrentRotation, obj.CurrentRotation,
obj.CurrentScale, obj.CurrentScale,
Color.Lerp(Color.White, Level.Loaded.BackgroundTextureColor, obj.Position.Z / 5000.0f)); Color.Lerp(obj.Prefab.SpriteColor, obj.Prefab.SpriteColor.Multiply(Level.Loaded.BackgroundTextureColor), obj.Position.Z / 5000.0f));
} }
@@ -291,7 +291,7 @@ namespace Barotrauma
if (!currentDisplayLocation.Discovered) if (!currentDisplayLocation.Discovered)
{ {
RemoveFogOfWar(currentDisplayLocation); RemoveFogOfWar(currentDisplayLocation);
currentDisplayLocation.Discover(); Discover(currentDisplayLocation);
if (currentDisplayLocation.MapPosition.X > furthestDiscoveredLocation.MapPosition.X) if (currentDisplayLocation.MapPosition.X > furthestDiscoveredLocation.MapPosition.X)
{ {
furthestDiscoveredLocation = currentDisplayLocation; furthestDiscoveredLocation = currentDisplayLocation;
@@ -452,7 +452,7 @@ namespace Barotrauma
Level.Loaded.DebugSetStartLocation(CurrentLocation); Level.Loaded.DebugSetStartLocation(CurrentLocation);
Level.Loaded.DebugSetEndLocation(null); Level.Loaded.DebugSetEndLocation(null);
CurrentLocation.Discover(); Discover(CurrentLocation);
OnLocationChanged?.Invoke(new LocationChangeInfo(prevLocation, CurrentLocation)); OnLocationChanged?.Invoke(new LocationChangeInfo(prevLocation, CurrentLocation));
SelectLocation(-1); SelectLocation(-1);
if (GameMain.Client == null) if (GameMain.Client == null)
@@ -693,7 +693,22 @@ namespace Barotrauma
pos.Y = (int)pos.Y; pos.Y = (int)pos.Y;
Vector2 nameSize = GUIStyle.LargeFont.MeasureString(HighlightedLocation.Name); Vector2 nameSize = GUIStyle.LargeFont.MeasureString(HighlightedLocation.Name);
Vector2 typeSize = HighlightedLocation.Type.Name.IsNullOrEmpty() ? Vector2.Zero : GUIStyle.Font.MeasureString(HighlightedLocation.Type.Name); Vector2 typeSize = HighlightedLocation.Type.Name.IsNullOrEmpty() ? Vector2.Zero : GUIStyle.Font.MeasureString(HighlightedLocation.Type.Name);
Vector2 size = new Vector2(Math.Max(nameSize.X, typeSize.X), nameSize.Y + typeSize.Y); Vector2 descSize = HighlightedLocation.Type.Description.IsNullOrEmpty() ? Vector2.Zero : GUIStyle.SmallFont.MeasureString(HighlightedLocation.Type.Description);
Vector2 size = new Vector2(Math.Max(nameSize.X, Math.Max(typeSize.X, descSize.X)), nameSize.Y + typeSize.Y + descSize.Y);
int highestSubTier = HighlightedLocation.HighestSubmarineTierAvailable();
var overrideTiers = new List<(SubmarineClass subClass, int tier)>();
foreach (SubmarineClass subClass in Enum.GetValues(typeof(SubmarineClass)))
{
if (subClass == SubmarineClass.Undefined) { continue; }
int highestClassTier = HighlightedLocation.HighestSubmarineTierAvailable(subClass);
if (highestClassTier > 0 && highestClassTier > highestSubTier)
{
overrideTiers.Add((subClass, highestClassTier));
}
}
size.Y += ((highestSubTier > 0 ? 1 : 0) + overrideTiers.Count) * GUIStyle.SmallFont.MeasureString(TextManager.Get("advancedsub.all")).Y;
bool showReputation = hudVisibility > 0.0f && HighlightedLocation.Discovered && HighlightedLocation.Type.HasOutpost && HighlightedLocation.Reputation != null; bool showReputation = hudVisibility > 0.0f && HighlightedLocation.Discovered && HighlightedLocation.Type.HasOutpost && HighlightedLocation.Reputation != null;
LocalizedString repLabelText = null, repValueText = null; LocalizedString repLabelText = null, repValueText = null;
Vector2 repLabelSize = Vector2.Zero, repBarSize = Vector2.Zero; Vector2 repLabelSize = Vector2.Zero, repBarSize = Vector2.Zero;
@@ -706,21 +721,51 @@ namespace Barotrauma
repValueText = HighlightedLocation.Reputation.GetFormattedReputationText(addColorTags: false); repValueText = HighlightedLocation.Reputation.GetFormattedReputationText(addColorTags: false);
size.X = Math.Max(size.X, repBarSize.X + GUIStyle.Font.MeasureString(repValueText).X + GUI.IntScale(10)); size.X = Math.Max(size.X, repBarSize.X + GUIStyle.Font.MeasureString(repValueText).X + GUI.IntScale(10));
} }
GUIStyle.GetComponentStyle("OuterGlow").Sprites[GUIComponent.ComponentState.None][0].Draw( GUIStyle.GetComponentStyle("OuterGlow").Sprites[GUIComponent.ComponentState.None][0].Draw(
spriteBatch, new Rectangle((int)(pos.X - 60 * GUI.Scale), (int)(pos.Y - size.Y), (int)(size.X + 120 * GUI.Scale), (int)(size.Y * 2.2f)), Color.Black * hudVisibility); spriteBatch,
new Rectangle(
(int)(pos.X - 60 * GUI.Scale),
(int)(pos.Y - size.Y),
(int)(size.X + 120 * GUI.Scale),
(int)(size.Y * 2.2f)),
Color.Black * hudVisibility);
var topLeftPos = pos - new Vector2(0.0f, size.Y / 2); var topLeftPos = pos - new Vector2(0.0f, size.Y / 2);
GUI.DrawString(spriteBatch, topLeftPos, HighlightedLocation.Name, GUIStyle.TextColorNormal * hudVisibility * 1.5f, font: GUIStyle.LargeFont); GUI.DrawString(spriteBatch, topLeftPos, HighlightedLocation.Name, GUIStyle.TextColorNormal * hudVisibility * 1.5f, font: GUIStyle.LargeFont);
topLeftPos += new Vector2(0.0f, nameSize.Y); topLeftPos += new Vector2(0.0f, nameSize.Y);
GUI.DrawString(spriteBatch, topLeftPos, HighlightedLocation.Type.Name, GUIStyle.TextColorNormal * hudVisibility * 1.5f); DrawText(HighlightedLocation.Type.Name);
if (!HighlightedLocation.Type.Description.IsNullOrEmpty())
{
topLeftPos += new Vector2(0.0f, descSize.Y);
DrawText(HighlightedLocation.Type.Description, font: GUIStyle.SmallFont);
}
if (highestSubTier > 0)
{
DrawSubAvailabilityText("advancedsub.all", highestSubTier);
}
foreach (var (subClass, tier) in overrideTiers)
{
DrawSubAvailabilityText($"advancedsub.{subClass}", tier);
}
void DrawSubAvailabilityText(string tag, int tier)
{
topLeftPos += new Vector2(0.0f, typeSize.Y);
DrawText(TextManager.GetWithVariable(tag, "[tiernumber]", tier.ToString()), font: GUIStyle.SmallFont);
}
if (showReputation) if (showReputation)
{ {
topLeftPos += new Vector2(0.0f, typeSize.Y + repLabelSize.Y); topLeftPos += new Vector2(0.0f, typeSize.Y + repLabelSize.Y);
GUI.DrawString(spriteBatch, topLeftPos, repLabelText.Value, GUIStyle.TextColorNormal * hudVisibility * 1.5f); DrawText(repLabelText.Value);
topLeftPos += new Vector2(0.0f, repLabelSize.Y + GUI.IntScale(10)); topLeftPos += new Vector2(0.0f, repLabelSize.Y + GUI.IntScale(10));
Rectangle repBarRect = new Rectangle(new Point((int)topLeftPos.X, (int)topLeftPos.Y), new Point((int)repBarSize.X, (int)repBarSize.Y)); Rectangle repBarRect = new Rectangle(new Point((int)topLeftPos.X, (int)topLeftPos.Y), new Point((int)repBarSize.X, (int)repBarSize.Y));
RoundSummary.DrawReputationBar(spriteBatch, repBarRect, HighlightedLocation.Reputation.NormalizedValue); RoundSummary.DrawReputationBar(spriteBatch, repBarRect, HighlightedLocation.Reputation.NormalizedValue);
GUI.DrawString(spriteBatch, new Vector2(repBarRect.Right + GUI.IntScale(5), repBarRect.Top), repValueText.Value, Reputation.GetReputationColor(HighlightedLocation.Reputation.NormalizedValue)); GUI.DrawString(spriteBatch, new Vector2(repBarRect.Right + GUI.IntScale(5), repBarRect.Top), repValueText.Value, Reputation.GetReputationColor(HighlightedLocation.Reputation.NormalizedValue));
} }
void DrawText(LocalizedString text, GUIFont font = null) => GUI.DrawString(spriteBatch, topLeftPos, text, GUIStyle.TextColorNormal * hudVisibility * 1.5f, font: font);
} }
if (drawRadiationTooltip) if (drawRadiationTooltip)
@@ -311,6 +311,12 @@ namespace Barotrauma.Networking
CoroutineManager.StartCoroutine(WaitForStartingInfo(), "WaitForStartingInfo"); CoroutineManager.StartCoroutine(WaitForStartingInfo(), "WaitForStartingInfo");
} }
public void SetLobbyPublic(bool isPublic)
{
GameMain.NetLobbyScreen.SetPublic(isPublic);
SteamManager.SetLobbyPublic(isPublic);
}
private ClientPeer CreateNetPeer() private ClientPeer CreateNetPeer()
{ {
Networking.ClientPeer.Callbacks callbacks = new ClientPeer.Callbacks( Networking.ClientPeer.Callbacks callbacks = new ClientPeer.Callbacks(
@@ -1319,6 +1325,7 @@ namespace Barotrauma.Networking
ServerSettings.MaximumMoneyTransferRequest = inc.ReadInt32(); ServerSettings.MaximumMoneyTransferRequest = inc.ReadInt32();
bool usingShuttle = GameMain.NetLobbyScreen.UsingShuttle = inc.ReadBoolean(); bool usingShuttle = GameMain.NetLobbyScreen.UsingShuttle = inc.ReadBoolean();
GameMain.LightManager.LosMode = (LosMode)inc.ReadByte(); GameMain.LightManager.LosMode = (LosMode)inc.ReadByte();
ServerSettings.ShowEnemyHealthBars = (EnemyHealthBarMode)inc.ReadByte();
bool includesFinalize = inc.ReadBoolean(); inc.ReadPadBits(); bool includesFinalize = inc.ReadBoolean(); inc.ReadPadBits();
GameMain.LightManager.LightingEnabled = true; GameMain.LightManager.LightingEnabled = true;
@@ -2521,7 +2528,7 @@ namespace Barotrauma.Networking
public override void CreateEntityEvent(INetSerializable entity, NetEntityEvent.IData extraData = null) public override void CreateEntityEvent(INetSerializable entity, NetEntityEvent.IData extraData = null)
{ {
if (!(entity is IClientSerializable clientSerializable)) if (entity is not IClientSerializable clientSerializable)
{ {
throw new InvalidCastException($"Entity is not {nameof(IClientSerializable)}"); throw new InvalidCastException($"Entity is not {nameof(IClientSerializable)}");
} }
@@ -140,7 +140,7 @@ namespace Barotrauma.Networking
MaxPlayers = incMsg.ReadByte(); MaxPlayers = incMsg.ReadByte();
HasPassword = incMsg.ReadBoolean(); HasPassword = incMsg.ReadBoolean();
IsPublic = incMsg.ReadBoolean(); IsPublic = incMsg.ReadBoolean();
GameMain.NetLobbyScreen.SetPublic(IsPublic); GameMain.Client?.SetLobbyPublic(IsPublic);
AllowFileTransfers = incMsg.ReadBoolean(); AllowFileTransfers = incMsg.ReadBoolean();
incMsg.ReadPadBits(); incMsg.ReadPadBits();
TickRate = incMsg.ReadRangedInteger(1, 60); TickRate = incMsg.ReadRangedInteger(1, 60);
@@ -367,6 +367,17 @@ namespace Barotrauma.Networking
//*********************************************** //***********************************************
//changing server visibility on the fly is not supported in dedicated servers
if (GameMain.Client?.ClientPeer is not LidgrenClientPeer)
{
var isPublic = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), serverTab.RectTransform),
TextManager.Get("publicserver"))
{
ToolTip = TextManager.Get("publicservertooltip")
};
GetPropertyData(nameof(IsPublic)).AssignGUIComponent(isPublic);
}
// Sub Selection // Sub Selection
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), serverTab.RectTransform), TextManager.Get("ServerSettingsSubSelection"), font: GUIStyle.SubHeadingFont); new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), serverTab.RectTransform), TextManager.Get("ServerSettingsSubSelection"), font: GUIStyle.SubHeadingFont);
var selectionFrame = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.02f), serverTab.RectTransform), isHorizontal: true) var selectionFrame = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.02f), serverTab.RectTransform), isHorizontal: true)
@@ -475,9 +486,10 @@ namespace Barotrauma.Networking
// game settings // game settings
//-------------------------------------------------------------------------------- //--------------------------------------------------------------------------------
var roundsTab = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.95f), settingsTabs[(int)SettingsTab.Rounds].RectTransform, Anchor.Center)) { }; var roundsTab = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.95f), settingsTabs[(int)SettingsTab.Rounds].RectTransform, Anchor.Center));
var roundsContent = new GUIListBox(new RectTransform(Vector2.One, roundsTab.RectTransform, Anchor.Center), style: "GUIListBoxNoBorder").Content;
GUILayoutGroup playStyleLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.2f), roundsTab.RectTransform)); GUILayoutGroup playStyleLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.2f), roundsContent.RectTransform));
// Play Style Selection // Play Style Selection
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.2f), playStyleLayout.RectTransform), TextManager.Get("ServerSettingsPlayStyle"), font: GUIStyle.SubHeadingFont); new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.2f), playStyleLayout.RectTransform), TextManager.Get("ServerSettingsPlayStyle"), font: GUIStyle.SubHeadingFont);
var playstyleList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.7f), playStyleLayout.RectTransform)) var playstyleList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.7f), playStyleLayout.RectTransform))
@@ -502,7 +514,7 @@ namespace Barotrauma.Networking
GUITextBlock.AutoScaleAndNormalize(playStyleTickBoxes.Select(t => t.TextBlock)); GUITextBlock.AutoScaleAndNormalize(playStyleTickBoxes.Select(t => t.TextBlock));
playstyleList.RectTransform.MinSize = new Point(0, (int)(playstyleList.Content.Children.First().Rect.Height * 2.0f + playstyleList.Padding.Y + playstyleList.Padding.W)); playstyleList.RectTransform.MinSize = new Point(0, (int)(playstyleList.Content.Children.First().Rect.Height * 2.0f + playstyleList.Padding.Y + playstyleList.Padding.W));
GUILayoutGroup sliderLayout = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.35f), roundsTab.RectTransform)) GUILayoutGroup sliderLayout = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.35f), roundsContent.RectTransform))
{ {
Stretch = true Stretch = true
}; };
@@ -608,7 +620,7 @@ namespace Barotrauma.Networking
}; };
slider.OnMoved(slider, slider.BarScroll); slider.OnMoved(slider, slider.BarScroll);
GUILayoutGroup losModeLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.14f), roundsTab.RectTransform)); GUILayoutGroup losModeLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.14f), roundsContent.RectTransform));
var losModeLabel = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.4f), losModeLayout.RectTransform), var losModeLabel = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.4f), losModeLayout.RectTransform),
TextManager.Get("LosEffect")); TextManager.Get("LosEffect"));
@@ -629,7 +641,30 @@ namespace Barotrauma.Networking
} }
GetPropertyData(nameof(LosMode)).AssignGUIComponent(losModeRadioButtonGroup); GetPropertyData(nameof(LosMode)).AssignGUIComponent(losModeRadioButtonGroup);
GUILayoutGroup numberLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.3f), roundsTab.RectTransform)) GUILayoutGroup healthBarModeLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.14f), roundsContent.RectTransform));
var healthBarModeLabel = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.4f), healthBarModeLayout.RectTransform),
TextManager.Get("ShowEnemyHealthBars"));
var healthBarModeRadioButtonLayout
= new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.6f), healthBarModeLayout.RectTransform),
isHorizontal: true)
{
Stretch = true
};
var healthBarModeRadioButtonGroup = new GUIRadioButtonGroup();
EnemyHealthBarMode[] healthBarModeModes = Enum.GetValues<EnemyHealthBarMode>();
for (int i = 0; i < healthBarModeModes.Length; i++)
{
var losTick = new GUITickBox(new RectTransform(new Vector2(0.3f, 1.0f), healthBarModeRadioButtonLayout.RectTransform),
TextManager.Get($"ShowEnemyHealthBars.{healthBarModeModes[i]}"),
font: GUIStyle.SmallFont, style: "GUIRadioButton");
healthBarModeRadioButtonGroup.AddRadioButton(i, losTick);
}
GetPropertyData(nameof(ShowEnemyHealthBars)).AssignGUIComponent(healthBarModeRadioButtonGroup);
GUILayoutGroup numberLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.3f), roundsContent.RectTransform))
{ {
Stretch = true Stretch = true
}; };
@@ -651,7 +686,7 @@ namespace Barotrauma.Networking
var disableBotConversationsBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), numberLayout.RectTransform), TextManager.Get("ServerSettingsDisableBotConversations")); var disableBotConversationsBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), numberLayout.RectTransform), TextManager.Get("ServerSettingsDisableBotConversations"));
GetPropertyData(nameof(DisableBotConversations)).AssignGUIComponent(disableBotConversationsBox); GetPropertyData(nameof(DisableBotConversations)).AssignGUIComponent(disableBotConversationsBox);
GUILayoutGroup buttonHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.1f), roundsTab.RectTransform), isHorizontal: true) GUILayoutGroup buttonHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.1f), roundsContent.RectTransform), isHorizontal: true)
{ {
Stretch = true, Stretch = true,
RelativeSpacing = 0.05f RelativeSpacing = 0.05f
@@ -112,10 +112,23 @@ namespace Barotrauma
} }
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.AppendLine("Barotrauma Client crash report (generated on " + DateTime.Now + ")"); sb.AppendLine("Barotrauma Client crash report (generated on " + DateTime.Now + ")");
sb.AppendLine("\n"); sb.AppendLine();
sb.AppendLine("Barotrauma seems to have crashed. Sorry for the inconvenience! "); sb.AppendLine("Barotrauma seems to have crashed. Sorry for the inconvenience! ");
sb.AppendLine("\n"); sb.AppendLine();
string dxgiErrorHelpText =
#if WINDOWS
GetDXGIErrorHelpText(game, exception);
#else
string.Empty;
#endif
if (!string.IsNullOrEmpty(dxgiErrorHelpText))
{
sb.AppendLine(dxgiErrorHelpText);
sb.AppendLine();
}
try try
{ {
@@ -135,7 +148,7 @@ namespace Barotrauma
XDocument newDoc = new XDocument(newElement); XDocument newDoc = new XDocument(newElement);
newDoc.Save(GameSettings.PlayerConfigPath); newDoc.Save(GameSettings.PlayerConfigPath);
sb.AppendLine("To prevent further startup errors, installed mods will be disabled the next time you launch the game."); sb.AppendLine("To prevent further startup errors, installed mods will be disabled the next time you launch the game.");
sb.AppendLine("\n"); sb.AppendLine();
} }
} }
} }
@@ -148,7 +161,7 @@ namespace Barotrauma
{ {
sb.AppendLine(exeHash.StringRepresentation); sb.AppendLine(exeHash.StringRepresentation);
} }
sb.AppendLine("\n"); sb.AppendLine();
sb.AppendLine("Game version " + GameMain.Version + sb.AppendLine("Game version " + GameMain.Version +
" (" + AssemblyInfo.BuildString + ", branch " + AssemblyInfo.GitBranch + ", revision " + AssemblyInfo.GitRevision + ")"); " (" + AssemblyInfo.BuildString + ", branch " + AssemblyInfo.GitBranch + ", revision " + AssemblyInfo.GitRevision + ")");
sb.AppendLine($"Graphics mode: {GameSettings.CurrentConfig.Graphics.Width}x{GameSettings.CurrentConfig.Graphics.Height} ({GameSettings.CurrentConfig.Graphics.DisplayMode})"); sb.AppendLine($"Graphics mode: {GameSettings.CurrentConfig.Graphics.Width}x{GameSettings.CurrentConfig.Graphics.Height} ({GameSettings.CurrentConfig.Graphics.DisplayMode})");
@@ -171,7 +184,7 @@ namespace Barotrauma
sb.AppendLine("Client (" + (GameMain.Client.GameStarted ? "Round had started)" : "Round hadn't been started)")); sb.AppendLine("Client (" + (GameMain.Client.GameStarted ? "Round had started)" : "Round hadn't been started)"));
} }
sb.AppendLine("\n"); sb.AppendLine();
sb.AppendLine("System info:"); sb.AppendLine("System info:");
sb.AppendLine(" Operating system: " + System.Environment.OSVersion + (System.Environment.Is64BitOperatingSystem ? " 64 bit" : " x86")); sb.AppendLine(" Operating system: " + System.Environment.OSVersion + (System.Environment.Is64BitOperatingSystem ? " 64 bit" : " x86"));
@@ -201,13 +214,14 @@ namespace Barotrauma
} }
} }
sb.AppendLine("\n"); sb.AppendLine();
sb.AppendLine("Exception: " + exception.Message + " (" + exception.GetType().ToString() + ")"); sb.AppendLine($"Exception: {exception.Message} ({exception.GetType()})");
#if WINDOWS #if WINDOWS
if (exception is SharpDXException sharpDxException && ((uint)sharpDxException.HResult) == 0x887A0005) if (exception is SharpDXException sharpDxException && ((uint)sharpDxException.HResult) == 0x887A0005)
{ {
var dxDevice = (SharpDX.Direct3D11.Device)game.GraphicsDevice.Handle; var dxDevice = (SharpDX.Direct3D11.Device)game.GraphicsDevice.Handle;
sb.AppendLine("Device removed reason: " + dxDevice.DeviceRemovedReason.ToString()); var descriptor = ResultDescriptor.Find(dxDevice.DeviceRemovedReason)?.ApiCode ?? "UNKNOWN";
sb.AppendLine($"Device removed reason: {descriptor} ({dxDevice.DeviceRemovedReason})");
} }
#endif #endif
if (exception.TargetSite != null) if (exception.TargetSite != null)
@@ -219,7 +233,7 @@ namespace Barotrauma
{ {
sb.AppendLine("Stack trace: "); sb.AppendLine("Stack trace: ");
sb.AppendLine(exception.StackTrace.CleanupStackTrace()); sb.AppendLine(exception.StackTrace.CleanupStackTrace());
sb.AppendLine("\n"); sb.AppendLine();
} }
if (exception.InnerException != null) if (exception.InnerException != null)
@@ -261,17 +275,42 @@ namespace Barotrauma
if (GameSettings.CurrentConfig.SaveDebugConsoleLogs if (GameSettings.CurrentConfig.SaveDebugConsoleLogs
|| GameSettings.CurrentConfig.VerboseLogging) { DebugConsole.SaveLogs(); } || GameSettings.CurrentConfig.VerboseLogging) { DebugConsole.SaveLogs(); }
string msg = string.Empty;
if (GameAnalyticsManager.SendUserStatistics) if (GameAnalyticsManager.SendUserStatistics)
{ {
CrashMessageBox("A crash report (\"" + filePath + "\") was saved in the root folder of the game and sent to the developers.", filePath); msg = "A crash report (\"" + filePath + "\") was saved in the root folder of the game and sent to the developers.";
} }
else else
{ {
CrashMessageBox("A crash report (\"" + filePath + "\") was saved in the root folder of the game. The error was not sent to the developers because user statistics have been disabled, but" + msg = "A crash report (\"" + filePath + "\") was saved in the root folder of the game. The error was not sent to the developers because user statistics have been disabled, but" +
" if you'd like to help fix this bug, you may post it on Barotrauma's GitHub issue tracker: https://github.com/Regalis11/Barotrauma/issues/", filePath); " if you'd like to help fix this bug, you may post it on Barotrauma's GitHub issue tracker: https://github.com/Regalis11/Barotrauma/issues/";
} }
if (string.IsNullOrEmpty(dxgiErrorHelpText))
{
msg += "\n\n" + dxgiErrorHelpText;
}
CrashMessageBox(msg, filePath);
} }
#if WINDOWS
private static string GetDXGIErrorHelpText(GameMain game, Exception exception)
{
string text = string.Empty;
if (exception is SharpDXException sharpDxException && ((uint)sharpDxException.HResult) == 0x887A0005)
{
var dxDevice = (SharpDX.Direct3D11.Device)game.GraphicsDevice.Handle;
var descriptor = ResultDescriptor.Find(dxDevice.DeviceRemovedReason)?.ApiCode ?? "UNKNOWN";
text +=
$"The crash was caused by the DirectX error {descriptor} ({dxDevice.DeviceRemovedReason}). " +
"This is a common DirectX error that can be related to various different issues, such as outdated drivers, RAM problems or an overclocked or otherwise overstressed GPU. " +
"There are several potential ways to fix the issue: ensuring your graphics drivers and DirectX installation are up-to-date, disabling overclocking and adjusting various GPU-specific settings. " +
$"You may also be able to find potential solutions to the problem by using the error code {descriptor} ({dxDevice.DeviceRemovedReason}) and your GPU manufacturer as search terms.";
}
return text;
}
#endif
private static IntPtr nvApi64Dll = IntPtr.Zero; private static IntPtr nvApi64Dll = IntPtr.Zero;
private static void EnableNvOptimus() private static void EnableNvOptimus()
{ {
@@ -287,7 +326,7 @@ namespace Barotrauma
private static void FreeNvOptimus() private static void FreeNvOptimus()
{ {
#warning TODO: determine if we can do this safely #warning TODO: determine if we can do this safely
//NativeLibrary.Free(nvApi64Dll); //NativeLibrary.Free(nvApi64Dll);
} }
@@ -104,6 +104,7 @@ namespace Barotrauma
public struct CampaignSettingElements public struct CampaignSettingElements
{ {
public SettingValue<bool> TutorialEnabled;
public SettingValue<bool> RadiationEnabled; public SettingValue<bool> RadiationEnabled;
public SettingValue<int> MaxMissionCount; public SettingValue<int> MaxMissionCount;
public SettingValue<StartingBalanceAmount> StartingFunds; public SettingValue<StartingBalanceAmount> StartingFunds;
@@ -114,6 +115,7 @@ namespace Barotrauma
{ {
return new CampaignSettings(element: null) return new CampaignSettings(element: null)
{ {
TutorialEnabled = TutorialEnabled.GetValue(),
RadiationEnabled = RadiationEnabled.GetValue(), RadiationEnabled = RadiationEnabled.GetValue(),
MaxMissionCount = MaxMissionCount.GetValue(), MaxMissionCount = MaxMissionCount.GetValue(),
StartingBalanceAmount = StartingFunds.GetValue(), StartingBalanceAmount = StartingFunds.GetValue(),
@@ -159,7 +161,7 @@ namespace Barotrauma
} }
} }
protected static CampaignSettingElements CreateCampaignSettingList(GUIComponent parent, CampaignSettings prevSettings) protected static CampaignSettingElements CreateCampaignSettingList(GUIComponent parent, CampaignSettings prevSettings, bool isSinglePlayer)
{ {
const float verticalSize = 0.14f; const float verticalSize = 0.14f;
@@ -180,6 +182,9 @@ namespace Barotrauma
Spacing = GUI.IntScale(5) Spacing = GUI.IntScale(5)
}; };
SettingValue<bool> tutorialEnabled = isSinglePlayer ?
CreateTickbox(settingsList.Content, TextManager.Get("CampaignOption.EnableTutorial"), TextManager.Get("campaignoption.enabletutorial.tooltip"), prevSettings.TutorialEnabled, verticalSize) :
new SettingValue<bool>(() => false, b => { });
SettingValue<bool> radiationEnabled = CreateTickbox(settingsList.Content, TextManager.Get("CampaignOption.EnableRadiation"), TextManager.Get("campaignoption.enableradiation.tooltip"), prevSettings.RadiationEnabled, verticalSize); SettingValue<bool> radiationEnabled = CreateTickbox(settingsList.Content, TextManager.Get("CampaignOption.EnableRadiation"), TextManager.Get("campaignoption.enableradiation.tooltip"), prevSettings.RadiationEnabled, verticalSize);
ImmutableArray<SettingCarouselElement<Identifier>> startingSetOptions = StartItemSet.Sets.OrderBy(s => s.Order).Select(set => new SettingCarouselElement<Identifier>(set.Identifier, $"startitemset.{set.Identifier}")).ToImmutableArray(); ImmutableArray<SettingCarouselElement<Identifier>> startingSetOptions = StartItemSet.Sets.OrderBy(s => s.Order).Select(set => new SettingCarouselElement<Identifier>(set.Identifier, $"startitemset.{set.Identifier}")).ToImmutableArray();
@@ -214,6 +219,7 @@ namespace Barotrauma
{ {
if (o is CampaignSettings settings) if (o is CampaignSettings settings)
{ {
tutorialEnabled.SetValue(isSinglePlayer && settings.TutorialEnabled);
radiationEnabled.SetValue(settings.RadiationEnabled); radiationEnabled.SetValue(settings.RadiationEnabled);
maxMissionCountInput.SetValue(settings.MaxMissionCount); maxMissionCountInput.SetValue(settings.MaxMissionCount);
startingFundsInput.SetValue(settings.StartingBalanceAmount); startingFundsInput.SetValue(settings.StartingBalanceAmount);
@@ -226,6 +232,7 @@ namespace Barotrauma
return new CampaignSettingElements return new CampaignSettingElements
{ {
TutorialEnabled = tutorialEnabled,
RadiationEnabled = radiationEnabled, RadiationEnabled = radiationEnabled,
MaxMissionCount = maxMissionCountInput, MaxMissionCount = maxMissionCountInput,
StartingFunds = startingFundsInput, StartingFunds = startingFundsInput,
@@ -46,7 +46,7 @@ namespace Barotrauma
nameSeedLayout.RectTransform.MinSize = new Point(0, nameSeedLayout.Children.Sum(c => c.RectTransform.MinSize.Y)); nameSeedLayout.RectTransform.MinSize = new Point(0, nameSeedLayout.Children.Sum(c => c.RectTransform.MinSize.Y));
CampaignSettingElements elements = CreateCampaignSettingList(campaignSettingLayout, CampaignSettings.Empty); CampaignSettingElements elements = CreateCampaignSettingList(campaignSettingLayout, CampaignSettings.Empty, false);
var buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.1f), var buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.1f),
verticalLayout.RectTransform) { MaxSize = new Point(int.MaxValue, 60) }, childAnchor: Anchor.BottomRight, isHorizontal: true); verticalLayout.RectTransform) { MaxSize = new Point(int.MaxValue, 60) }, childAnchor: Anchor.BottomRight, isHorizontal: true);
@@ -370,7 +370,7 @@ namespace Barotrauma
GUILayoutGroup campaignSettingContent = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.8f), CampaignCustomizeSettings.Content.RectTransform, Anchor.TopCenter)); GUILayoutGroup campaignSettingContent = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.8f), CampaignCustomizeSettings.Content.RectTransform, Anchor.TopCenter));
CampaignSettingElements elements = CreateCampaignSettingList(campaignSettingContent, prevSettings); CampaignSettingElements elements = CreateCampaignSettingList(campaignSettingContent, prevSettings, true);
CampaignCustomizeSettings.Buttons[0].OnClicked += (button, o) => CampaignCustomizeSettings.Buttons[0].OnClicked += (button, o) =>
{ {
@@ -608,15 +608,16 @@ namespace Barotrauma
{ {
OnClicked = (btn, userdata) => OnClicked = (btn, userdata) =>
{ {
var saveFolder = SaveUtil.GetSaveFolder(SaveUtil.SaveType.Singleplayer);
try try
{ {
ToolBox.OpenFileWithShell(SaveUtil.SaveFolder); ToolBox.OpenFileWithShell(saveFolder);
} }
catch (Exception e) catch (Exception e)
{ {
new GUIMessageBox( new GUIMessageBox(
TextManager.Get("error"), TextManager.Get("error"),
TextManager.GetWithVariables("showinfoldererror", ("[folder]", SaveUtil.SaveFolder), ("[errormessage]", e.Message))); TextManager.GetWithVariables("showinfoldererror", ("[folder]", saveFolder), ("[errormessage]", e.Message)));
} }
return true; return true;
} }
@@ -39,6 +39,7 @@ namespace Barotrauma.CharacterEditor
private bool ShowExtraRagdollControls => editLimbs || editJoints; private bool ShowExtraRagdollControls => editLimbs || editJoints;
public Character SpawnedCharacter => character;
private Character character; private Character character;
private Vector2 spawnPosition; private Vector2 spawnPosition;
@@ -1513,7 +1514,7 @@ namespace Barotrauma.CharacterEditor
} }
} }
private Character SpawnCharacter(Identifier speciesName, RagdollParams ragdoll = null) public Character SpawnCharacter(Identifier speciesName, RagdollParams ragdoll = null)
{ {
DebugConsole.NewMessage(GetCharacterEditorTranslation("TryingToSpawnCharacter").Replace("[config]", speciesName.ToString()), Color.HotPink); DebugConsole.NewMessage(GetCharacterEditorTranslation("TryingToSpawnCharacter").Replace("[config]", speciesName.ToString()), Color.HotPink);
OnPreSpawn(); OnPreSpawn();
@@ -3181,10 +3182,7 @@ namespace Barotrauma.CharacterEditor
OnClicked = (button, data) => OnClicked = (button, data) =>
{ {
ResetView(); ResetView();
CharacterParams.Serialize(); PrepareCharacterCopy();
RagdollParams.Serialize();
AnimParams.ForEach(a => a.Serialize());
Wizard.Instance.CopyExisting(CharacterParams, RagdollParams, AnimParams);
Wizard.Instance.SelectTab(Wizard.Tab.Character); Wizard.Instance.SelectTab(Wizard.Tab.Character);
return true; return true;
} }
@@ -3209,9 +3207,17 @@ namespace Barotrauma.CharacterEditor
fileEditPanel.RectTransform.MinSize = new Point(0, (int)(layoutGroup.RectTransform.Children.Sum(c => c.MinSize.Y + layoutGroup.AbsoluteSpacing) * 1.2f)); fileEditPanel.RectTransform.MinSize = new Point(0, (int)(layoutGroup.RectTransform.Children.Sum(c => c.MinSize.Y + layoutGroup.AbsoluteSpacing) * 1.2f));
} }
#endregion #endregion
#region ToggleButtons public void PrepareCharacterCopy()
{
CharacterParams.Serialize();
RagdollParams.Serialize();
AnimParams.ForEach(a => a.Serialize());
Wizard.Instance.CopyExisting(CharacterParams, RagdollParams, AnimParams);
}
#region ToggleButtons
private enum Direction private enum Direction
{ {
Left, Left,
@@ -101,7 +101,7 @@ namespace Barotrauma.CharacterEditor
{ {
bool isSamePackage = contentPackage.GetFiles<CharacterFile>().Any(f => Path.GetFileNameWithoutExtension(f.Path.Value) == name); bool isSamePackage = contentPackage.GetFiles<CharacterFile>().Any(f => Path.GetFileNameWithoutExtension(f.Path.Value) == name);
LocalizedString verificationText = isSamePackage ? GetCharacterEditorTranslation("existingcharacterfoundreplaceverification") : GetCharacterEditorTranslation("existingcharacterfoundoverrideverification"); LocalizedString verificationText = isSamePackage ? GetCharacterEditorTranslation("existingcharacterfoundreplaceverification") : GetCharacterEditorTranslation("existingcharacterfoundoverrideverification");
var msgBox = new GUIMessageBox("", verificationText, new LocalizedString[] { TextManager.Get("Yes"), TextManager.Get("No") }) var msgBox = new GUIMessageBox("", verificationText, new LocalizedString[] { TextManager.Get("Yes"), TextManager.Get("No") }, type: GUIMessageBox.Type.Warning)
{ {
UserData = "verificationprompt" UserData = "verificationprompt"
}; };
@@ -356,7 +356,7 @@ namespace Barotrauma.CharacterEditor
} }
if (ContentPackageManager.AllPackages.Any(cp => cp.Name.ToLower() == contentPackageNameElement.Text.ToLower())) if (ContentPackageManager.AllPackages.Any(cp => cp.Name.ToLower() == contentPackageNameElement.Text.ToLower()))
{ {
new GUIMessageBox("", TextManager.Get("charactereditor.contentpackagenameinuse", "leveleditorlevelobjnametaken")); new GUIMessageBox("", TextManager.Get("charactereditor.contentpackagenameinuse", "leveleditorlevelobjnametaken"), type: GUIMessageBox.Type.Warning);
return false; return false;
} }
string modName = contentPackageNameElement.Text; string modName = contentPackageNameElement.Text;
@@ -428,17 +428,26 @@ namespace Barotrauma.CharacterEditor
texturePathElement.Flash(useRectangleFlash: true); texturePathElement.Flash(useRectangleFlash: true);
return false; return false;
} }
if (Name == CharacterPrefab.HumanSpeciesName && !IsCopy)
{
// Force a copy when trying to override a human, because handling the crash would be very difficult (we require humans to have certain definitions).
if (!CharacterEditorScreen.Instance.SpawnedCharacter.IsHuman)
{
CharacterEditorScreen.Instance.SpawnCharacter(CharacterPrefab.HumanSpeciesName);
}
CharacterEditorScreen.Instance.PrepareCharacterCopy();
}
if (IsCopy) if (IsCopy)
{ {
SourceRagdoll.Texture = evaluatedTexturePath; SourceRagdoll.Texture = evaluatedTexturePath;
SourceRagdoll.CanEnterSubmarine = CanEnterSubmarine; SourceRagdoll.CanEnterSubmarine = CanEnterSubmarine;
SourceRagdoll.CanWalk = CanWalk; SourceRagdoll.CanWalk = CanWalk;
SourceRagdoll.Serialize(); SourceRagdoll.Serialize();
Wizard.Instance.CreateCharacter(SourceRagdoll.MainElement, SourceCharacter.MainElement, SourceAnimations); Instance.CreateCharacter(SourceRagdoll.MainElement, SourceCharacter.MainElement, SourceAnimations);
} }
else else
{ {
Wizard.Instance.SelectTab(Tab.Ragdoll); Instance.SelectTab(Tab.Ragdoll);
} }
return true; return true;
}; };
@@ -470,9 +479,6 @@ namespace Barotrauma.CharacterEditor
Stretch = true, Stretch = true,
RelativeSpacing = 0.02f RelativeSpacing = 0.02f
}; };
// HTML
GUIMessageBox htmlBox = null;
var loadHtmlButton = new GUIButton(new RectTransform(new Point(content.Rect.Width / 3, elementSize), content.RectTransform), GetCharacterEditorTranslation("LoadFromHTML"));
// Limbs // Limbs
var limbsElement = new GUIFrame(new RectTransform(new Vector2(1, 0.05f), content.RectTransform), style: null) { CanBeFocused = false }; var limbsElement = new GUIFrame(new RectTransform(new Vector2(1, 0.05f), content.RectTransform), style: null) { CanBeFocused = false };
@@ -689,69 +695,6 @@ namespace Barotrauma.CharacterEditor
return true; return true;
} }
}; };
loadHtmlButton.OnClicked = (b, d) =>
{
if (htmlBox == null)
{
htmlBox = new GUIMessageBox(GetCharacterEditorTranslation("LoadHTML"), string.Empty, new LocalizedString[] { TextManager.Get("Close"), TextManager.Get("Load") }, new Vector2(0.65f, 1f));
htmlBox.Header.Font = GUIStyle.LargeFont;
var element = new GUIFrame(new RectTransform(new Vector2(0.9f, 0.05f), htmlBox.Content.RectTransform), style: null, color: Color.Gray * 0.25f);
//new GUITextBlock(new RectTransform(new Vector2(0.3f, 1), element.RectTransform), GetCharacterEditorTranslation("HTMLPath"));
var htmlPathElement = new GUITextBox(new RectTransform(new Vector2(0.7f, 1), element.RectTransform, Anchor.TopRight), GetCharacterEditorTranslation("HTMLPath").Value);
LocalizedString title = GetCharacterEditorTranslation("SelectFile");
new GUIButton(new RectTransform(new Vector2(0.3f, 1), element.RectTransform), title)
{
OnClicked = (button, data) =>
{
FileSelection.OnFileSelected = (file) =>
{
htmlPathElement.Text = file;
};
FileSelection.ClearFileTypeFilters();
FileSelection.AddFileTypeFilter("HTML", "*.html, *.htm");
FileSelection.AddFileTypeFilter("All files", "*.*");
FileSelection.SelectFileTypeFilter("*.html, *.htm");
FileSelection.Open = true;
return true;
}
};
var list = new GUIListBox(new RectTransform(new Vector2(1, 0.8f), htmlBox.Content.RectTransform));
var htmlOutput = new GUITextBlock(new RectTransform(Vector2.One, list.Content.RectTransform), string.Empty) { CanBeFocused = false };
htmlBox.Buttons[0].OnClicked += (_b, _d) =>
{
htmlBox.Close();
return true;
};
htmlBox.Buttons[1].OnClicked += (_b, _d) =>
{
LimbGUIElements.ForEach(l => l.RectTransform.Parent = null);
LimbGUIElements.Clear();
JointGUIElements.ForEach(j => j.RectTransform.Parent = null);
JointGUIElements.Clear();
LimbXElements.Clear();
JointXElements.Clear();
ParseRagdollFromHTML(htmlPathElement.Text, (id, limbName, limbType, rect) =>
{
CreateLimbGUIElement(limbsList.Content.RectTransform, elementSize, id, limbName, limbType, rect);
}, (id1, id2, anchor1, anchor2, jointName) =>
{
CreateJointGUIElement(jointsList.Content.RectTransform, elementSize, id1, id2, anchor1, anchor2, jointName);
});
htmlOutput.Text = new XDocument(new XElement("Ragdoll", new object[]
{
new XAttribute("type", Name), LimbXElements.Values, JointXElements
})).ToString();
htmlOutput.CalculateHeightFromText();
list.UpdateScrollBarSize();
return true;
};
}
else
{
GUIMessageBox.MessageBoxes.Add(htmlBox);
}
return true;
};
// Previous // Previous
box.Buttons[0].OnClicked += (b, d) => box.Buttons[0].OnClicked += (b, d) =>
{ {
@@ -1070,7 +1013,6 @@ namespace Barotrauma.CharacterEditor
// Rectangles // Rectangles
colliderAttributes.Add(new XAttribute("height", (int)(height * 0.85f))); colliderAttributes.Add(new XAttribute("height", (int)(height * 0.85f)));
colliderAttributes.Add(new XAttribute("width", (int)(width * 0.85f))); colliderAttributes.Add(new XAttribute("width", (int)(width * 0.85f)));
idToCodeName.TryGetValue(id, out string notes);
LimbXElements.Add(id.ToString(), new XElement("limb", LimbXElements.Add(id.ToString(), new XElement("limb",
new XAttribute("id", id), new XAttribute("id", id),
new XAttribute("name", limbName), new XAttribute("name", limbName),
@@ -1107,188 +1049,6 @@ namespace Barotrauma.CharacterEditor
} }
} }
Dictionary<int, string> idToCodeName = new Dictionary<int, string>();
protected void ParseRagdollFromHTML(string path, Action<int, string, LimbType, Rectangle> limbCallback = null, Action<int, int, Vector2, Vector2, string> jointCallback = null)
{
// TODO: parse as xml files -> allows to load ragdolls onto the wizard.
//XDocument doc = XMLExtensions.TryLoadXml(path);
//var xElements = doc.Elements().ToArray();
string html = string.Empty;
try
{
html = File.ReadAllText(path);
}
catch (Exception e)
{
DebugConsole.ThrowError(GetCharacterEditorTranslation("FailedToReadHTML").Replace("[path]", path), e);
return;
}
var lines = html.Split(new string[] { "<div", "</div>", Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries)
.Where(s => s.Contains("left") && s.Contains("top") && s.Contains("width") && s.Contains("height"));
int id = 0;
Dictionary<string, int> hierarchyToID = new Dictionary<string, int>();
Dictionary<int, string> idToHierarchy = new Dictionary<int, string>();
Dictionary<int, string> idToPositionCode = new Dictionary<int, string>();
Dictionary<int, string> idToName = new Dictionary<int, string>();
idToCodeName.Clear();
foreach (var line in lines)
{
var codeNames = new string(line.SkipWhile(c => c != '>').Skip(1).ToArray()).Split(',');
for (int i = 0; i < codeNames.Length; i++)
{
string codeName = codeNames[i].Trim();
if (string.IsNullOrWhiteSpace(codeName)) { continue; }
idToCodeName.Add(id, codeName);
string limbName = new string(codeName.SkipWhile(c => c != '_').Skip(1).ToArray());
if (string.IsNullOrWhiteSpace(limbName)) { continue; }
idToName.Add(id, limbName);
var parts = line.Split(' ');
int ParseToInt(string selector)
{
string part = parts.First(p => p.Contains(selector));
string s = new string(part.SkipWhile(c => c != ':').Skip(1).TakeWhile(c => char.IsNumber(c)).ToArray());
int.TryParse(s, out int v);
return v;
};
// example: 111311cr -> 111311
string hierarchy = new string(codeName.TakeWhile(c => char.IsNumber(c)).ToArray());
if (hierarchyToID.ContainsKey(hierarchy))
{
DebugConsole.ThrowError(GetCharacterEditorTranslation("MultipleItemsWithSameHierarchy").Replace("[hierarchy]", hierarchy).Replace("[name]", codeName));
return;
}
hierarchyToID.Add(hierarchy, id);
idToHierarchy.Add(id, hierarchy);
string positionCode = new string(codeName.SkipWhile(c => char.IsNumber(c)).TakeWhile(c => c != '_').ToArray());
idToPositionCode.Add(id, positionCode.ToLowerInvariant());
int x = ParseToInt("left");
int y = ParseToInt("top");
int width = ParseToInt("width");
int height = ParseToInt("height");
// This is overridden when the data is loaded from the gui fields.
LimbXElements.Add(hierarchy, new XElement("limb",
new XAttribute("id", id),
new XAttribute("name", limbName),
new XAttribute("type", ParseLimbType(limbName).ToString()),
new XElement("sprite",
new XAttribute("texture", ""),
new XAttribute("sourcerect", $"{x}, {y}, {width}, {height}"))
));
limbCallback?.Invoke(id, limbName, ParseLimbType(limbName), new Rectangle(x, y, width, height));
id++;
}
}
for (int i = 0; i < id; i++)
{
if (idToHierarchy.TryGetValue(i, out string hierarchy))
{
if (hierarchy != "0")
{
// NEW LOGIC: if hierarchy length == 1, parent to 0
// Else parent to the last bone in the current hierarchy (11 is parented to 1, 212 is parented to 21 etc)
string parent = hierarchy.Length > 1 ? hierarchy.Remove(hierarchy.Length - 1, 1) : "0";
if (hierarchyToID.TryGetValue(parent, out int parentID))
{
Vector2 anchor1 = Vector2.Zero;
Vector2 anchor2 = Vector2.Zero;
idToName.TryGetValue(parentID, out string parentName);
idToName.TryGetValue(i, out string limbName);
string jointName = $"{GetCharacterEditorTranslation("Joint")} {parentName} - {limbName}";
if (idToPositionCode.TryGetValue(i, out string positionCode))
{
float scalar = 0.8f;
if (LimbXElements.TryGetValue(parent, out XElement parentElement))
{
Rectangle parentSourceRect = parentElement.Element("sprite").GetAttributeRect("sourcerect", Rectangle.Empty);
float parentWidth = parentSourceRect.Width / 2 * scalar;
float parentHeight = parentSourceRect.Height / 2 * scalar;
switch (positionCode)
{
case "tl": // -1, 1
anchor1 = new Vector2(-parentWidth, parentHeight);
break;
case "tc": // 0, 1
anchor1 = new Vector2(0, parentHeight);
break;
case "tr": // -1, 1
anchor1 = new Vector2(-parentWidth, parentHeight);
break;
case "cl": // -1, 0
anchor1 = new Vector2(-parentWidth, 0);
break;
case "cr": // 1, 0
anchor1 = new Vector2(parentWidth, 0);
break;
case "bl": // -1, -1
anchor1 = new Vector2(-parentWidth, -parentHeight);
break;
case "bc": // 0, -1
anchor1 = new Vector2(0, -parentHeight);
break;
case "br": // 1, -1
anchor1 = new Vector2(parentWidth, -parentHeight);
break;
}
if (LimbXElements.TryGetValue(hierarchy, out XElement element))
{
Rectangle sourceRect = element.Element("sprite").GetAttributeRect("sourcerect", Rectangle.Empty);
float width = sourceRect.Width / 2 * scalar;
float height = sourceRect.Height / 2 * scalar;
switch (positionCode)
{
// Inverse
case "tl":
// br
anchor2 = new Vector2(-width, -height);
break;
case "tc":
// bc
anchor2 = new Vector2(0, -height);
break;
case "tr":
// bl
anchor2 = new Vector2(-width, -height);
break;
case "cl":
// cr
anchor2 = new Vector2(width, 0);
break;
case "cr":
// cl
anchor2 = new Vector2(-width, 0);
break;
case "bl":
// tr
anchor2 = new Vector2(-width, height);
break;
case "bc":
// tc
anchor2 = new Vector2(0, height);
break;
case "br":
// tl
anchor2 = new Vector2(-width, height);
break;
}
}
}
}
// This is overridden when the data is loaded from the gui fields.
JointXElements.Add(new XElement("joint",
new XAttribute("name", jointName),
new XAttribute("limb1", parentID),
new XAttribute("limb2", i),
new XAttribute("limb1anchor", $"{anchor1.X.Format(2)}, {anchor1.Y.Format(2)}"),
new XAttribute("limb2anchor", $"{anchor2.X.Format(2)}, {anchor2.Y.Format(2)}")
));
jointCallback?.Invoke(parentID, i, anchor1, anchor2, jointName);
}
}
}
}
}
protected LimbType ParseLimbType(string limbName) protected LimbType ParseLimbType(string limbName)
{ {
var limbType = LimbType.None; var limbType = LimbType.None;
@@ -269,7 +269,7 @@ namespace Barotrauma
}; };
#if USE_STEAM #if USE_STEAM
steamWorkshopButton = new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), customizeList.RectTransform), TextManager.Get("SteamWorkshopButton"), textAlignment: Alignment.Left, style: "MainMenuGUIButton") steamWorkshopButton = new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), customizeList.RectTransform), TextManager.Get("settingstab.mods"), textAlignment: Alignment.Left, style: "MainMenuGUIButton")
{ {
ForceUpperCase = ForceUpperCase.Yes, ForceUpperCase = ForceUpperCase.Yes,
Enabled = true, Enabled = true,
@@ -463,13 +463,17 @@ namespace Barotrauma
} }
}; };
var tutorialPreview = new GUILayoutGroup(new RectTransform(new Vector2(0.6f, 1.0f), tutorialContent.RectTransform)) { RelativeSpacing = 0.05f, Stretch = true }; var tutorialPreview = new GUILayoutGroup(new RectTransform(new Vector2(0.6f, 1.0f), tutorialContent.RectTransform)) { RelativeSpacing = 0.05f, Stretch = true };
var imageContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.6f), tutorialPreview.RectTransform), style: "InnerFrame"); var imageContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.5f), tutorialPreview.RectTransform), style: "InnerFrame");
tutorialBanner = new GUIImage(new RectTransform(Vector2.One, imageContainer.RectTransform), style: null, scaleToFit: true); tutorialBanner = new GUIImage(new RectTransform(Vector2.One, imageContainer.RectTransform), style: null, scaleToFit: true);
var infoContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.4f), tutorialPreview.RectTransform), style: "GUIFrameListBox"); var infoContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.5f), tutorialPreview.RectTransform), style: "GUIFrameListBox");
var infoContent = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), infoContainer.RectTransform, Anchor.Center), childAnchor: Anchor.TopCenter); var infoContent = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), infoContainer.RectTransform, Anchor.Center), childAnchor: Anchor.TopLeft)
{
AbsoluteSpacing = GUI.IntScale(10)
};
tutorialHeader = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.75f), infoContent.RectTransform), string.Empty, font: GUIStyle.SubHeadingFont, textAlignment: Alignment.Center); tutorialHeader = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoContent.RectTransform), string.Empty, font: GUIStyle.SubHeadingFont);
tutorialDescription = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoContent.RectTransform), string.Empty, wrap: true);
var startButton = new GUIButton(new RectTransform(new Vector2(0.5f, 0.0f), infoContent.RectTransform, Anchor.BottomRight), text: TextManager.Get("startgamebutton")) var startButton = new GUIButton(new RectTransform(new Vector2(0.5f, 0.0f), infoContent.RectTransform, Anchor.BottomRight), text: TextManager.Get("startgamebutton"))
{ {
@@ -500,6 +504,10 @@ namespace Barotrauma
private void SelectTutorial(Tutorial tutorial) private void SelectTutorial(Tutorial tutorial)
{ {
tutorialHeader.Text = tutorial.DisplayName; tutorialHeader.Text = tutorial.DisplayName;
tutorialHeader.CalculateHeightFromText();
tutorialDescription.Text = tutorial.Description;
tutorialDescription.CalculateHeightFromText();
(tutorialDescription.Parent as GUILayoutGroup)?.Recalculate();
tutorial.TutorialPrefab.Banner?.EnsureLazyLoaded(); tutorial.TutorialPrefab.Banner?.EnsureLazyLoaded();
tutorialBanner.Sprite = tutorial.TutorialPrefab.Banner; tutorialBanner.Sprite = tutorial.TutorialPrefab.Banner;
tutorialBanner.Color = tutorial.TutorialPrefab.Banner == null ? Color.Black : Color.White; tutorialBanner.Color = tutorial.TutorialPrefab.Banner == null ? Color.Black : Color.White;
@@ -2487,7 +2487,7 @@ namespace Barotrauma
{ {
IntValue = MainSub.Info.Tier, IntValue = MainSub.Info.Tier,
MinValueInt = 1, MinValueInt = 1,
MaxValueInt = 3, MaxValueInt = SubmarineInfo.HighestTier,
OnValueChanged = (numberInput) => OnValueChanged = (numberInput) =>
{ {
MainSub.Info.Tier = numberInput.IntValue; MainSub.Info.Tier = numberInput.IntValue;
@@ -2495,7 +2495,7 @@ namespace Barotrauma
}; };
if (MainSub?.Info != null) if (MainSub?.Info != null)
{ {
MainSub.Info.Tier = Math.Clamp(MainSub.Info.Tier, 1, 3); MainSub.Info.Tier = Math.Clamp(MainSub.Info.Tier, 1, SubmarineInfo.HighestTier);
} }
var crewSizeArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.25f), subSettingsContainer.RectTransform), isHorizontal: true) var crewSizeArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.25f), subSettingsContainer.RectTransform), isHorizontal: true)
@@ -3228,7 +3228,7 @@ namespace Barotrauma
} }
string pathWithoutUserName = Path.GetFullPath(sub.FilePath); string pathWithoutUserName = Path.GetFullPath(sub.FilePath);
string saveFolder = Path.GetFullPath(SaveUtil.SaveFolder); string saveFolder = Path.GetFullPath(SaveUtil.DefaultSaveFolder);
if (pathWithoutUserName.StartsWith(saveFolder)) if (pathWithoutUserName.StartsWith(saveFolder))
{ {
pathWithoutUserName = "..." + pathWithoutUserName[saveFolder.Length..]; pathWithoutUserName = "..." + pathWithoutUserName[saveFolder.Length..];
@@ -4217,7 +4217,8 @@ namespace Barotrauma
GUIListBox listBox = new GUIListBox(new RectTransform(new Vector2(0.9f, 0.9f), frame.RectTransform, Anchor.Center)) GUIListBox listBox = new GUIListBox(new RectTransform(new Vector2(0.9f, 0.9f), frame.RectTransform, Anchor.Center))
{ {
PlaySoundOnSelect = true, PlaySoundOnSelect = true,
OnSelected = SelectWire OnSelected = SelectWire,
CanTakeKeyBoardFocus = false
}; };
List<ItemPrefab> wirePrefabs = new List<ItemPrefab>(); List<ItemPrefab> wirePrefabs = new List<ItemPrefab>();
@@ -5866,7 +5867,7 @@ namespace Barotrauma
decimal realWorldDistance = decimal.Round((decimal) (Vector2.Distance(startPos, mouseWorldPos) * Physics.DisplayToRealWorldRatio), 2); decimal realWorldDistance = decimal.Round((decimal) (Vector2.Distance(startPos, mouseWorldPos) * Physics.DisplayToRealWorldRatio), 2);
Vector2 offset = new Vector2(GUI.IntScale(24)); Vector2 offset = new Vector2(GUI.IntScale(24));
GUI.DrawString(spriteBatch, PlayerInput.MousePosition + offset, $"{realWorldDistance}m", GUIStyle.TextColorNormal, font: GUIStyle.SubHeadingFont, backgroundColor: Color.Black, backgroundPadding: 4); GUI.DrawString(spriteBatch, PlayerInput.MousePosition + offset, $"{realWorldDistance} m", GUIStyle.TextColorNormal, font: GUIStyle.Font, backgroundColor: Color.Black, backgroundPadding: 4);
} }
spriteBatch.End(); spriteBatch.End();
@@ -176,7 +176,7 @@ namespace Barotrauma
Action<T> setter) where T : Enum Action<T> setter) where T : Enum
=> Dropdown(parent, textFunc, tooltipFunc, (T[])Enum.GetValues(typeof(T)), currentValue, setter); => Dropdown(parent, textFunc, tooltipFunc, (T[])Enum.GetValues(typeof(T)), currentValue, setter);
private static void Dropdown<T>(GUILayoutGroup parent, Func<T, LocalizedString> textFunc, Func<T, LocalizedString>? tooltipFunc, IReadOnlyList<T> values, T currentValue, Action<T> setter) private static GUIDropDown Dropdown<T>(GUILayoutGroup parent, Func<T, LocalizedString> textFunc, Func<T, LocalizedString>? tooltipFunc, IReadOnlyList<T> values, T currentValue, Action<T> setter)
{ {
var dropdown = new GUIDropDown(NewItemRectT(parent)); var dropdown = new GUIDropDown(NewItemRectT(parent));
values.ForEach(v => dropdown.AddItem(text: textFunc(v), userData: v, toolTip: tooltipFunc?.Invoke(v) ?? null)); values.ForEach(v => dropdown.AddItem(text: textFunc(v), userData: v, toolTip: tooltipFunc?.Invoke(v) ?? null));
@@ -189,9 +189,10 @@ namespace Barotrauma
setter((T)obj); setter((T)obj);
return true; return true;
}; };
return dropdown;
} }
private void Slider(GUILayoutGroup parent, Vector2 range, int steps, Func<float, string> labelFunc, float currentValue, Action<float> setter, LocalizedString? tooltip = null) private static (GUIScrollBar slider, GUITextBlock label) Slider(GUILayoutGroup parent, Vector2 range, int steps, Func<float, string> labelFunc, float currentValue, Action<float> setter, LocalizedString? tooltip = null)
{ {
var layout = new GUILayoutGroup(NewItemRectT(parent), isHorizontal: true); var layout = new GUILayoutGroup(NewItemRectT(parent), isHorizontal: true);
var slider = new GUIScrollBar(new RectTransform((0.72f, 1.0f), layout.RectTransform), style: "GUISlider") var slider = new GUIScrollBar(new RectTransform((0.72f, 1.0f), layout.RectTransform), style: "GUISlider")
@@ -213,11 +214,12 @@ namespace Barotrauma
setter(sb.BarScrollValue); setter(sb.BarScrollValue);
return true; return true;
}; };
return (slider, label);
} }
private void Tickbox(GUILayoutGroup parent, LocalizedString label, LocalizedString tooltip, bool currentValue, Action<bool> setter) private static GUITickBox Tickbox(GUILayoutGroup parent, LocalizedString label, LocalizedString tooltip, bool currentValue, Action<bool> setter)
{ {
var tickbox = new GUITickBox(NewItemRectT(parent), label) return new GUITickBox(NewItemRectT(parent), label)
{ {
Selected = currentValue, Selected = currentValue,
ToolTip = tooltip, ToolTip = tooltip,
@@ -231,7 +233,7 @@ namespace Barotrauma
private string Percentage(float v) => ToolBox.GetFormattedPercentage(v); private string Percentage(float v) => ToolBox.GetFormattedPercentage(v);
private int Round(float v) => (int)MathF.Round(v); private static int Round(float v) => MathUtils.RoundToInt(v);
private void CreateGraphicsTab() private void CreateGraphicsTab()
{ {
@@ -262,30 +264,30 @@ namespace Barotrauma
Spacer(left); Spacer(left);
Label(left, TextManager.Get("DisplayMode"), GUIStyle.SubHeadingFont); Label(left, TextManager.Get("DisplayMode"), GUIStyle.SubHeadingFont);
DropdownEnum(left, (m) => TextManager.Get($"{m}"), null, unsavedConfig.Graphics.DisplayMode, (v) => unsavedConfig.Graphics.DisplayMode = v); DropdownEnum(left, (m) => TextManager.Get($"{m}"), null, unsavedConfig.Graphics.DisplayMode, v => unsavedConfig.Graphics.DisplayMode = v);
Spacer(left); Spacer(left);
Tickbox(left, TextManager.Get("EnableVSync"), TextManager.Get("EnableVSyncTooltip"), unsavedConfig.Graphics.VSync, (v) => unsavedConfig.Graphics.VSync = v); Tickbox(left, TextManager.Get("EnableVSync"), TextManager.Get("EnableVSyncTooltip"), unsavedConfig.Graphics.VSync, v => unsavedConfig.Graphics.VSync = v);
Tickbox(left, TextManager.Get("EnableTextureCompression"), TextManager.Get("EnableTextureCompressionTooltip"), unsavedConfig.Graphics.CompressTextures, (v) => unsavedConfig.Graphics.CompressTextures = v); Tickbox(left, TextManager.Get("EnableTextureCompression"), TextManager.Get("EnableTextureCompressionTooltip"), unsavedConfig.Graphics.CompressTextures, v => unsavedConfig.Graphics.CompressTextures = v);
Label(right, TextManager.Get("LOSEffect"), GUIStyle.SubHeadingFont); Label(right, TextManager.Get("LOSEffect"), GUIStyle.SubHeadingFont);
DropdownEnum(right, (m) => TextManager.Get($"LosMode{m}"), null, unsavedConfig.Graphics.LosMode, (v) => unsavedConfig.Graphics.LosMode = v); DropdownEnum(right, (m) => TextManager.Get($"LosMode{m}"), null, unsavedConfig.Graphics.LosMode, v => unsavedConfig.Graphics.LosMode = v);
Spacer(right); Spacer(right);
Label(right, TextManager.Get("LightMapScale"), GUIStyle.SubHeadingFont); Label(right, TextManager.Get("LightMapScale"), GUIStyle.SubHeadingFont);
Slider(right, (0.5f, 1.0f), 11, (v) => TextManager.GetWithVariable("percentageformat", "[value]", Round(v * 100).ToString()).Value, unsavedConfig.Graphics.LightMapScale, (v) => unsavedConfig.Graphics.LightMapScale = v, TextManager.Get("LightMapScaleTooltip")); Slider(right, (0.5f, 1.0f), 11, v => TextManager.GetWithVariable("percentageformat", "[value]", Round(v * 100).ToString()).Value, unsavedConfig.Graphics.LightMapScale, v => unsavedConfig.Graphics.LightMapScale = v, TextManager.Get("LightMapScaleTooltip"));
Spacer(right); Spacer(right);
Label(right, TextManager.Get("VisibleLightLimit"), GUIStyle.SubHeadingFont); Label(right, TextManager.Get("VisibleLightLimit"), GUIStyle.SubHeadingFont);
Slider(right, (10, 210), 21, (v) => v > 200 ? TextManager.Get("unlimited").Value : Round(v).ToString(), unsavedConfig.Graphics.VisibleLightLimit, Slider(right, (10, 210), 21, v => v > 200 ? TextManager.Get("unlimited").Value : Round(v).ToString(), unsavedConfig.Graphics.VisibleLightLimit,
(v) => unsavedConfig.Graphics.VisibleLightLimit = v > 200 ? int.MaxValue : Round(v), TextManager.Get("VisibleLightLimitTooltip")); v => unsavedConfig.Graphics.VisibleLightLimit = v > 200 ? int.MaxValue : Round(v), TextManager.Get("VisibleLightLimitTooltip"));
Spacer(right); Spacer(right);
Tickbox(right, TextManager.Get("RadialDistortion"), TextManager.Get("RadialDistortionTooltip"), unsavedConfig.Graphics.RadialDistortion, (v) => unsavedConfig.Graphics.RadialDistortion = v); Tickbox(right, TextManager.Get("RadialDistortion"), TextManager.Get("RadialDistortionTooltip"), unsavedConfig.Graphics.RadialDistortion, v => unsavedConfig.Graphics.RadialDistortion = v);
Tickbox(right, TextManager.Get("ChromaticAberration"), TextManager.Get("ChromaticAberrationTooltip"), unsavedConfig.Graphics.ChromaticAberration, (v) => unsavedConfig.Graphics.ChromaticAberration = v); Tickbox(right, TextManager.Get("ChromaticAberration"), TextManager.Get("ChromaticAberrationTooltip"), unsavedConfig.Graphics.ChromaticAberration, v => unsavedConfig.Graphics.ChromaticAberration = v);
Label(right, TextManager.Get("ParticleLimit"), GUIStyle.SubHeadingFont); Label(right, TextManager.Get("ParticleLimit"), GUIStyle.SubHeadingFont);
Slider(right, (100, 1500), 15, (v) => Round(v).ToString(), unsavedConfig.Graphics.ParticleLimit, (v) => unsavedConfig.Graphics.ParticleLimit = Round(v)); Slider(right, (100, 1500), 15, v => Round(v).ToString(), unsavedConfig.Graphics.ParticleLimit, v => unsavedConfig.Graphics.ParticleLimit = Round(v));
Spacer(right); Spacer(right);
} }
@@ -399,23 +401,23 @@ namespace Barotrauma
Spacer(audio); Spacer(audio);
Label(audio, TextManager.Get("SoundVolume"), GUIStyle.SubHeadingFont); Label(audio, TextManager.Get("SoundVolume"), GUIStyle.SubHeadingFont);
Slider(audio, (0, 1), 101, Percentage, unsavedConfig.Audio.SoundVolume, (v) => unsavedConfig.Audio.SoundVolume = v); Slider(audio, (0, 1), 101, Percentage, unsavedConfig.Audio.SoundVolume, v => unsavedConfig.Audio.SoundVolume = v);
Label(audio, TextManager.Get("MusicVolume"), GUIStyle.SubHeadingFont); Label(audio, TextManager.Get("MusicVolume"), GUIStyle.SubHeadingFont);
Slider(audio, (0, 1), 101, Percentage, unsavedConfig.Audio.MusicVolume, (v) => unsavedConfig.Audio.MusicVolume = v); Slider(audio, (0, 1), 101, Percentage, unsavedConfig.Audio.MusicVolume, v => unsavedConfig.Audio.MusicVolume = v);
Label(audio, TextManager.Get("UiSoundVolume"), GUIStyle.SubHeadingFont); Label(audio, TextManager.Get("UiSoundVolume"), GUIStyle.SubHeadingFont);
Slider(audio, (0, 1), 101, Percentage, unsavedConfig.Audio.UiVolume, (v) => unsavedConfig.Audio.UiVolume = v); Slider(audio, (0, 1), 101, Percentage, unsavedConfig.Audio.UiVolume, v => unsavedConfig.Audio.UiVolume = v);
Tickbox(audio, TextManager.Get("MuteOnFocusLost"), TextManager.Get("MuteOnFocusLostTooltip"), unsavedConfig.Audio.MuteOnFocusLost, (v) => unsavedConfig.Audio.MuteOnFocusLost = v); Tickbox(audio, TextManager.Get("MuteOnFocusLost"), TextManager.Get("MuteOnFocusLostTooltip"), unsavedConfig.Audio.MuteOnFocusLost, v => unsavedConfig.Audio.MuteOnFocusLost = v);
Tickbox(audio, TextManager.Get("DynamicRangeCompression"), TextManager.Get("DynamicRangeCompressionTooltip"), unsavedConfig.Audio.DynamicRangeCompressionEnabled, (v) => unsavedConfig.Audio.DynamicRangeCompressionEnabled = v); Tickbox(audio, TextManager.Get("DynamicRangeCompression"), TextManager.Get("DynamicRangeCompressionTooltip"), unsavedConfig.Audio.DynamicRangeCompressionEnabled, v => unsavedConfig.Audio.DynamicRangeCompressionEnabled = v);
Spacer(audio); Spacer(audio);
Label(audio, TextManager.Get("VoiceChatVolume"), GUIStyle.SubHeadingFont); Label(audio, TextManager.Get("VoiceChatVolume"), GUIStyle.SubHeadingFont);
Slider(audio, (0, 2), 201, Percentage, unsavedConfig.Audio.VoiceChatVolume, (v) => unsavedConfig.Audio.VoiceChatVolume = v); Slider(audio, (0, 2), 201, Percentage, unsavedConfig.Audio.VoiceChatVolume, v => unsavedConfig.Audio.VoiceChatVolume = v);
Tickbox(audio, TextManager.Get("DirectionalVoiceChat"), TextManager.Get("DirectionalVoiceChatTooltip"), unsavedConfig.Audio.UseDirectionalVoiceChat, (v) => unsavedConfig.Audio.UseDirectionalVoiceChat = v); Tickbox(audio, TextManager.Get("DirectionalVoiceChat"), TextManager.Get("DirectionalVoiceChatTooltip"), unsavedConfig.Audio.UseDirectionalVoiceChat, v => unsavedConfig.Audio.UseDirectionalVoiceChat = v);
Tickbox(audio, TextManager.Get("VoipAttenuation"), TextManager.Get("VoipAttenuationTooltip"), unsavedConfig.Audio.VoipAttenuationEnabled, (v) => unsavedConfig.Audio.VoipAttenuationEnabled = v); Tickbox(audio, TextManager.Get("VoipAttenuation"), TextManager.Get("VoipAttenuationTooltip"), unsavedConfig.Audio.VoipAttenuationEnabled, v => unsavedConfig.Audio.VoipAttenuationEnabled = v);
Label(voiceChat, TextManager.Get("AudioInputDevice"), GUIStyle.SubHeadingFont); Label(voiceChat, TextManager.Get("AudioInputDevice"), GUIStyle.SubHeadingFont);
@@ -424,7 +426,7 @@ namespace Barotrauma
Spacer(voiceChat); Spacer(voiceChat);
Label(voiceChat, TextManager.Get("VCInputMode"), GUIStyle.SubHeadingFont); Label(voiceChat, TextManager.Get("VCInputMode"), GUIStyle.SubHeadingFont);
DropdownEnum(voiceChat, (v) => TextManager.Get($"VoiceMode.{v}"), (v) => TextManager.Get($"VoiceMode.{v}Tooltip"), unsavedConfig.Audio.VoiceSetting, (v) => unsavedConfig.Audio.VoiceSetting = v); DropdownEnum(voiceChat, v => TextManager.Get($"VoiceMode.{v}"), v => TextManager.Get($"VoiceMode.{v}Tooltip"), unsavedConfig.Audio.VoiceSetting, v => unsavedConfig.Audio.VoiceSetting = v);
Spacer(voiceChat); Spacer(voiceChat);
var noiseGateThresholdLabel = Label(voiceChat, TextManager.Get("NoiseGateThreshold"), GUIStyle.SubHeadingFont); var noiseGateThresholdLabel = Label(voiceChat, TextManager.Get("NoiseGateThreshold"), GUIStyle.SubHeadingFont);
@@ -464,11 +466,11 @@ namespace Barotrauma
Spacer(voiceChat); Spacer(voiceChat);
Label(voiceChat, TextManager.Get("MicrophoneVolume"), GUIStyle.SubHeadingFont); Label(voiceChat, TextManager.Get("MicrophoneVolume"), GUIStyle.SubHeadingFont);
Slider(voiceChat, (0, 10), 101, Percentage, unsavedConfig.Audio.MicrophoneVolume, (v) => unsavedConfig.Audio.MicrophoneVolume = v); Slider(voiceChat, (0, 10), 101, Percentage, unsavedConfig.Audio.MicrophoneVolume, v => unsavedConfig.Audio.MicrophoneVolume = v);
Spacer(voiceChat); Spacer(voiceChat);
Label(voiceChat, TextManager.Get("CutoffPrevention"), GUIStyle.SubHeadingFont); Label(voiceChat, TextManager.Get("CutoffPrevention"), GUIStyle.SubHeadingFont);
Slider(voiceChat, (0, 500), 26, (v) => $"{Round(v)} ms", unsavedConfig.Audio.VoiceChatCutoffPrevention, (v) => unsavedConfig.Audio.VoiceChatCutoffPrevention = Round(v), TextManager.Get("CutoffPreventionTooltip")); Slider(voiceChat, (0, 500), 26, v => $"{Round(v)} ms", unsavedConfig.Audio.VoiceChatCutoffPrevention, v => unsavedConfig.Audio.VoiceChatCutoffPrevention = Round(v), TextManager.Get("CutoffPreventionTooltip"));
} }
private readonly Dictionary<GUIButton, Func<LocalizedString>> inputButtonValueNameGetters = new Dictionary<GUIButton, Func<LocalizedString>>(); private readonly Dictionary<GUIButton, Func<LocalizedString>> inputButtonValueNameGetters = new Dictionary<GUIButton, Func<LocalizedString>>();
@@ -481,8 +483,10 @@ namespace Barotrauma
GUILayoutGroup layout = CreateCenterLayout(content); GUILayoutGroup layout = CreateCenterLayout(content);
Label(layout, TextManager.Get("AimAssist"), GUIStyle.SubHeadingFont); Label(layout, TextManager.Get("AimAssist"), GUIStyle.SubHeadingFont);
Slider(layout, (0, 1), 101, Percentage, unsavedConfig.AimAssistAmount, (v) => unsavedConfig.AimAssistAmount = v, TextManager.Get("AimAssistTooltip"));
Tickbox(layout, TextManager.Get("EnableMouseLook"), TextManager.Get("EnableMouseLookTooltip"), unsavedConfig.EnableMouseLook, (v) => unsavedConfig.EnableMouseLook = v); var aimAssistSlider = Slider(layout, (0, 1), 101, Percentage, unsavedConfig.AimAssistAmount, v => unsavedConfig.AimAssistAmount = v, TextManager.Get("AimAssistTooltip"));
Tickbox(layout, TextManager.Get("EnableMouseLook"), TextManager.Get("EnableMouseLookTooltip"), unsavedConfig.EnableMouseLook, v => unsavedConfig.EnableMouseLook = v);
Spacer(layout); Spacer(layout);
GUIListBox keyMapList = GUIListBox keyMapList =
@@ -523,7 +527,7 @@ namespace Barotrauma
if (willBeSelected) if (willBeSelected)
{ {
inputBoxSelectedThisFrame = true; inputBoxSelectedThisFrame = true;
currentSetter = (v) => currentSetter = v =>
{ {
valueSetter(v); valueSetter(v);
btn.Text = valueNameGetter(); btn.Text = valueNameGetter();
@@ -626,7 +630,7 @@ namespace Barotrauma
currRow, currRow,
TextManager.Get($"InputType.{input}"), TextManager.Get($"InputType.{input}"),
() => unsavedConfig.KeyMap.Bindings[input].Name, () => unsavedConfig.KeyMap.Bindings[input].Name,
(v) => unsavedConfig.KeyMap = unsavedConfig.KeyMap.WithBinding(input, v), v => unsavedConfig.KeyMap = unsavedConfig.KeyMap.WithBinding(input, v),
LegacyInputTypes.Contains(input)); LegacyInputTypes.Contains(input));
} }
} }
@@ -644,7 +648,7 @@ namespace Barotrauma
currRow, currRow,
TextManager.GetWithVariable("inventoryslotkeybind", "[slotnumber]", (currIndex + 1).ToString(CultureInfo.InvariantCulture)), TextManager.GetWithVariable("inventoryslotkeybind", "[slotnumber]", (currIndex + 1).ToString(CultureInfo.InvariantCulture)),
() => unsavedConfig.InventoryKeyMap.Bindings[currIndex].Name, () => unsavedConfig.InventoryKeyMap.Bindings[currIndex].Name,
(v) => unsavedConfig.InventoryKeyMap = unsavedConfig.InventoryKeyMap.WithBinding(currIndex, v)); v => unsavedConfig.InventoryKeyMap = unsavedConfig.InventoryKeyMap.WithBinding(currIndex, v));
} }
} }
@@ -663,6 +667,8 @@ namespace Barotrauma
{ {
unsavedConfig.InventoryKeyMap = GameSettings.Config.InventoryKeyMapping.GetDefault(); unsavedConfig.InventoryKeyMap = GameSettings.Config.InventoryKeyMapping.GetDefault();
unsavedConfig.KeyMap = GameSettings.Config.KeyMapping.GetDefault(); unsavedConfig.KeyMap = GameSettings.Config.KeyMapping.GetDefault();
aimAssistSlider.slider.BarScrollValue = GameSettings.Config.DefaultAimAssist;
aimAssistSlider.label.Text = Percentage(GameSettings.Config.DefaultAimAssist);
foreach (var btn in inputButtonValueNameGetters.Keys) foreach (var btn in inputButtonValueNameGetters.Keys)
{ {
btn.Text = inputButtonValueNameGetters[btn](); btn.Text = inputButtonValueNameGetters[btn]();
@@ -683,13 +689,13 @@ namespace Barotrauma
.OrderBy(l => TextManager.GetTranslatedLanguageName(l).ToIdentifier()) .OrderBy(l => TextManager.GetTranslatedLanguageName(l).ToIdentifier())
.ToArray(); .ToArray();
Label(layout, TextManager.Get("Language"), GUIStyle.SubHeadingFont); Label(layout, TextManager.Get("Language"), GUIStyle.SubHeadingFont);
Dropdown(layout, (v) => TextManager.GetTranslatedLanguageName(v), null, languages, unsavedConfig.Language, (v) => unsavedConfig.Language = v); Dropdown(layout, v => TextManager.GetTranslatedLanguageName(v), null, languages, unsavedConfig.Language, v => unsavedConfig.Language = v);
Spacer(layout); Spacer(layout);
Tickbox(layout, TextManager.Get("PauseOnFocusLost"), TextManager.Get("PauseOnFocusLostTooltip"), unsavedConfig.PauseOnFocusLost, (v) => unsavedConfig.PauseOnFocusLost = v); Tickbox(layout, TextManager.Get("PauseOnFocusLost"), TextManager.Get("PauseOnFocusLostTooltip"), unsavedConfig.PauseOnFocusLost, v => unsavedConfig.PauseOnFocusLost = v);
Spacer(layout); Spacer(layout);
Tickbox(layout, TextManager.Get("DisableInGameHints"), TextManager.Get("DisableInGameHintsTooltip"), unsavedConfig.DisableInGameHints, (v) => unsavedConfig.DisableInGameHints = v); Tickbox(layout, TextManager.Get("DisableInGameHints"), TextManager.Get("DisableInGameHintsTooltip"), unsavedConfig.DisableInGameHints, v => unsavedConfig.DisableInGameHints = v);
var resetInGameHintsButton = var resetInGameHintsButton =
new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), layout.RectTransform), new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), layout.RectTransform),
TextManager.Get("ResetInGameHints"), style: "GUIButtonSmall") TextManager.Get("ResetInGameHints"), style: "GUIButtonSmall")
@@ -711,12 +717,16 @@ namespace Barotrauma
}; };
Spacer(layout); Spacer(layout);
Label(layout, TextManager.Get("ShowEnemyHealthBars"), GUIStyle.SubHeadingFont);
DropdownEnum(layout, v => TextManager.Get($"ShowEnemyHealthBars.{v}"), null, unsavedConfig.ShowEnemyHealthBars, v => unsavedConfig.ShowEnemyHealthBars = v);
Spacer(layout);
Label(layout, TextManager.Get("HUDScale"), GUIStyle.SubHeadingFont); Label(layout, TextManager.Get("HUDScale"), GUIStyle.SubHeadingFont);
Slider(layout, (0.75f, 1.25f), 51, Percentage, unsavedConfig.Graphics.HUDScale, (v) => unsavedConfig.Graphics.HUDScale = v); Slider(layout, (0.75f, 1.25f), 51, Percentage, unsavedConfig.Graphics.HUDScale, v => unsavedConfig.Graphics.HUDScale = v);
Label(layout, TextManager.Get("InventoryScale"), GUIStyle.SubHeadingFont); Label(layout, TextManager.Get("InventoryScale"), GUIStyle.SubHeadingFont);
Slider(layout, (0.75f, 1.25f), 51, Percentage, unsavedConfig.Graphics.InventoryScale, (v) => unsavedConfig.Graphics.InventoryScale = v); Slider(layout, (0.75f, 1.25f), 51, Percentage, unsavedConfig.Graphics.InventoryScale, v => unsavedConfig.Graphics.InventoryScale = v);
Label(layout, TextManager.Get("TextScale"), GUIStyle.SubHeadingFont); Label(layout, TextManager.Get("TextScale"), GUIStyle.SubHeadingFont);
Slider(layout, (0.75f, 1.25f), 51, Percentage, unsavedConfig.Graphics.TextScale, (v) => unsavedConfig.Graphics.TextScale = v); Slider(layout, (0.75f, 1.25f), 51, Percentage, unsavedConfig.Graphics.TextScale, v => unsavedConfig.Graphics.TextScale = v);
#if !OSX #if !OSX
Spacer(layout); Spacer(layout);
@@ -50,20 +50,25 @@ namespace Barotrauma.Steam
lobbyState = LobbyState.Owner; lobbyState = LobbyState.Owner;
lobbyID = (currentLobby?.Id).Value; lobbyID = (currentLobby?.Id).Value;
if (serverSettings.IsPublic) SetLobbyPublic(serverSettings.IsPublic);
{
currentLobby?.SetPublic();
}
else
{
currentLobby?.SetFriendsOnly();
}
currentLobby?.SetJoinable(true); currentLobby?.SetJoinable(true);
UpdateLobby(serverSettings); UpdateLobby(serverSettings);
}); });
} }
public static void SetLobbyPublic(bool isPublic)
{
if (isPublic)
{
currentLobby?.SetPublic();
}
else
{
currentLobby?.SetFriendsOnly();
}
}
public static void UpdateLobby(ServerSettings serverSettings) public static void UpdateLobby(ServerSettings serverSettings)
{ {
if (GameMain.Client == null) if (GameMain.Client == null)
@@ -104,7 +104,7 @@ namespace Barotrauma
} }
} }
List<StatusEffect> successEffects = statusEffects.Where(se => se.type == ActionType.OnUse).ToList(); List<StatusEffect> successEffects = statusEffects.Where(se => se.type == ActionType.OnSuccess).ToList();
List<StatusEffect> failureEffects = statusEffects.Where(se => se.type == ActionType.OnFailure).ToList(); List<StatusEffect> failureEffects = statusEffects.Where(se => se.type == ActionType.OnFailure).ToList();
foreach (StatusEffect statusEffect in successEffects) foreach (StatusEffect statusEffect in successEffects)
@@ -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.20.0.0</Version> <Version>0.20.4.0</Version>
<Copyright>Copyright © FakeFish 2018-2022</Copyright> <Copyright>Copyright © FakeFish 2018-2022</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName> <AssemblyName>Barotrauma</AssemblyName>
@@ -100,6 +100,8 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Compile Remove="ClientSource\GUI\GUITextBoxIME.cs" />
<EmbeddedResource Include="Icon.bmp"> <EmbeddedResource Include="Icon.bmp">
<LogicalName>Icon.bmp</LogicalName> <LogicalName>Icon.bmp</LogicalName>
</EmbeddedResource> </EmbeddedResource>
+2 -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.20.0.0</Version> <Version>0.20.4.0</Version>
<Copyright>Copyright © FakeFish 2018-2022</Copyright> <Copyright>Copyright © FakeFish 2018-2022</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName> <AssemblyName>Barotrauma</AssemblyName>
@@ -99,6 +99,7 @@
<Content Include="Content\**\*"> <Content Include="Content\**\*">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Compile Remove="ClientSource\GUI\GUITextBoxIME.cs" />
<EmbeddedResource Include="Icon.bmp"> <EmbeddedResource Include="Icon.bmp">
<LogicalName>Icon.bmp</LogicalName> <LogicalName>Icon.bmp</LogicalName>
@@ -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.20.0.0</Version> <Version>0.20.4.0</Version>
<Copyright>Copyright © FakeFish 2018-2022</Copyright> <Copyright>Copyright © FakeFish 2018-2022</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName> <AssemblyName>Barotrauma</AssemblyName>
@@ -117,6 +117,7 @@
<ProjectReference Include="..\..\Libraries\Lidgren.Network\Lidgren.NetStandard.csproj" AdditionalProperties="Configuration=Release" /> <ProjectReference Include="..\..\Libraries\Lidgren.Network\Lidgren.NetStandard.csproj" AdditionalProperties="Configuration=Release" />
<ProjectReference Include="..\..\Libraries\MonoGame.Framework\Src\MonoGame.Framework\MonoGame.Framework.Windows.NetStandard.csproj" AdditionalProperties="Configuration=Release" /> <ProjectReference Include="..\..\Libraries\MonoGame.Framework\Src\MonoGame.Framework\MonoGame.Framework.Windows.NetStandard.csproj" AdditionalProperties="Configuration=Release" />
<ProjectReference Include="..\..\Libraries\SharpFont\Source\SharpFont\SharpFont.NetStandard.csproj" AdditionalProperties="Configuration=Release" /> <ProjectReference Include="..\..\Libraries\SharpFont\Source\SharpFont\SharpFont.NetStandard.csproj" AdditionalProperties="Configuration=Release" />
<ProjectReference Include="..\..\Libraries\ImeSharp\ImeSharp.csproj" AdditionalProperties="Configuration=Release" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(Configuration)'=='Debug'"> <ItemGroup Condition="'$(Configuration)'=='Debug'">
<ProjectReference Include="..\..\Libraries\Concentus\CSharp\Concentus\Concentus.NetStandard.csproj" AdditionalProperties="Configuration=Debug" /> <ProjectReference Include="..\..\Libraries\Concentus\CSharp\Concentus\Concentus.NetStandard.csproj" AdditionalProperties="Configuration=Debug" />
@@ -126,6 +127,7 @@
<ProjectReference Include="..\..\Libraries\Lidgren.Network\Lidgren.NetStandard.csproj" AdditionalProperties="Configuration=Debug" /> <ProjectReference Include="..\..\Libraries\Lidgren.Network\Lidgren.NetStandard.csproj" AdditionalProperties="Configuration=Debug" />
<ProjectReference Include="..\..\Libraries\MonoGame.Framework\Src\MonoGame.Framework\MonoGame.Framework.Windows.NetStandard.csproj" AdditionalProperties="Configuration=Debug" /> <ProjectReference Include="..\..\Libraries\MonoGame.Framework\Src\MonoGame.Framework\MonoGame.Framework.Windows.NetStandard.csproj" AdditionalProperties="Configuration=Debug" />
<ProjectReference Include="..\..\Libraries\SharpFont\Source\SharpFont\SharpFont.NetStandard.csproj" AdditionalProperties="Configuration=Debug" /> <ProjectReference Include="..\..\Libraries\SharpFont\Source\SharpFont\SharpFont.NetStandard.csproj" AdditionalProperties="Configuration=Debug" />
<ProjectReference Include="..\..\Libraries\ImeSharp\ImeSharp.csproj" AdditionalProperties="Configuration=Debug" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -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.20.0.0</Version> <Version>0.20.4.0</Version>
<Copyright>Copyright © FakeFish 2018-2022</Copyright> <Copyright>Copyright © FakeFish 2018-2022</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.20.0.0</Version> <Version>0.20.4.0</Version>
<Copyright>Copyright © FakeFish 2018-2022</Copyright> <Copyright>Copyright © FakeFish 2018-2022</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName> <AssemblyName>DedicatedServer</AssemblyName>
@@ -269,10 +269,13 @@ namespace Barotrauma
case EventType.UpdateTalents: case EventType.UpdateTalents:
if (c.Character != this) if (c.Character != this)
{ {
if (!IsBot || !c.HasPermission(ClientPermissions.ManageBotTalents))
{
#if DEBUG #if DEBUG
DebugConsole.Log("Received a character update message from a client who's not controlling the character"); DebugConsole.Log("Received a character update message from a client who's not controlling the character");
#endif #endif
return; return;
}
} }
// get the full list of talents from the player, only give the ones // get the full list of talents from the player, only give the ones
@@ -31,7 +31,7 @@ namespace Barotrauma
if (convAction.SelectedOption > -1) if (convAction.SelectedOption > -1)
{ {
//someone else already chose an option for this conversation: interrupt for this client //someone else already chose an option for this conversation: interrupt for this client
convAction.ServerWrite(convAction.speaker, sender, interrupt: true); convAction.ServerWrite(convAction.Speaker, sender, interrupt: true);
} }
else else
{ {
@@ -2,7 +2,9 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq; using System.Linq;
using Barotrauma.Extensions;
using Barotrauma.Networking; using Barotrauma.Networking;
namespace Barotrauma namespace Barotrauma
@@ -30,6 +32,9 @@ namespace Barotrauma
switch (header) switch (header)
{ {
case NetworkHeader.ADD_EVERYTHING_TO_PENDING:
ProcessAddEverything(sender);
break;
case NetworkHeader.REQUEST_AFFLICTIONS: case NetworkHeader.REQUEST_AFFLICTIONS:
ProcessRequestedAfflictions(inc, sender); ProcessRequestedAfflictions(inc, sender);
break; break;
@@ -57,7 +62,14 @@ namespace Barotrauma
NetCrewMember newCrewMember = INetSerializableStruct.Read<NetCrewMember>(inc); NetCrewMember newCrewMember = INetSerializableStruct.Read<NetCrewMember>(inc);
InsertPendingCrewMember(newCrewMember); InsertPendingCrewMember(newCrewMember);
ServerSend(newCrewMember, NetworkHeader.ADD_PENDING, DeliveryMethod.Reliable, reponseClient: client); ServerSend(new NetCollection<NetCrewMember>(newCrewMember), NetworkHeader.ADD_PENDING, DeliveryMethod.Reliable, reponseClient: client);
}
private void ProcessAddEverything(Client client)
{
if (CheckRateLimit(client) == RateLimitResult.LimitReached) { return; }
AddEverythingToPending();
ServerSend(PendingHeals.ToNetCollection(), NetworkHeader.ADD_PENDING, DeliveryMethod.Reliable, reponseClient: client);
} }
private void ProcessNewRemoval(IReadMessage inc, Client client) private void ProcessNewRemoval(IReadMessage inc, Client client)
@@ -73,12 +85,7 @@ namespace Barotrauma
{ {
if (CheckRateLimit(client) == RateLimitResult.LimitReached) { return; } if (CheckRateLimit(client) == RateLimitResult.LimitReached) { return; }
INetSerializableStruct writeCrewMember = new NetPendingCrew ServerSend(PendingHeals.ToNetCollection(), NetworkHeader.REQUEST_PENDING, DeliveryMethod.Reliable, targetClient: client);
{
CrewMembers = PendingHeals.ToArray()
};
ServerSend(writeCrewMember, NetworkHeader.REQUEST_PENDING, DeliveryMethod.Reliable, targetClient: client);
} }
private void ProcessHealing(Client client) private void ProcessHealing(Client client)
@@ -107,10 +114,10 @@ namespace Barotrauma
CharacterInfo? foundInfo = crewMember.FindCharacterInfo(GetCrewCharacters()); CharacterInfo? foundInfo = crewMember.FindCharacterInfo(GetCrewCharacters());
NetAffliction[] pendingAfflictions = Array.Empty<NetAffliction>(); ImmutableArray<NetAffliction> pendingAfflictions = ImmutableArray<NetAffliction>.Empty;
int infoId = 0; int infoId = 0;
if (foundInfo is { Character: { CharacterHealth: { } health } }) if (foundInfo is { Character.CharacterHealth: { } health })
{ {
pendingAfflictions = GetAllAfflictions(health); pendingAfflictions = GetAllAfflictions(health);
infoId = foundInfo.GetIdentifierUsingOriginalName(); infoId = foundInfo.GetIdentifierUsingOriginalName();
@@ -2516,6 +2516,7 @@ namespace Barotrauma.Networking
msg.WriteInt32(ServerSettings.MaximumMoneyTransferRequest); msg.WriteInt32(ServerSettings.MaximumMoneyTransferRequest);
msg.WriteBoolean(IsUsingRespawnShuttle()); msg.WriteBoolean(IsUsingRespawnShuttle());
msg.WriteByte((byte)ServerSettings.LosMode); msg.WriteByte((byte)ServerSettings.LosMode);
msg.WriteByte((byte)ServerSettings.ShowEnemyHealthBars);
msg.WriteBoolean(includesFinalize); msg.WritePadBits(); msg.WriteBoolean(includesFinalize); msg.WritePadBits();
ServerSettings.WriteMonsterEnabled(msg); ServerSettings.WriteMonsterEnabled(msg);
@@ -272,7 +272,6 @@ namespace Barotrauma.Networking
XDocument doc = new XDocument(new XElement("serversettings")); XDocument doc = new XDocument(new XElement("serversettings"));
doc.Root.SetAttributeValue("name", ServerName); doc.Root.SetAttributeValue("name", ServerName);
doc.Root.SetAttributeValue("public", IsPublic);
doc.Root.SetAttributeValue("port", Port); doc.Root.SetAttributeValue("port", Port);
#if USE_STEAM #if USE_STEAM
doc.Root.SetAttributeValue("queryport", QueryPort); doc.Root.SetAttributeValue("queryport", QueryPort);
@@ -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.20.0.0</Version> <Version>0.20.4.0</Version>
<Copyright>Copyright © FakeFish 2018-2022</Copyright> <Copyright>Copyright © FakeFish 2018-2022</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName> <AssemblyName>DedicatedServer</AssemblyName>
@@ -7,18 +7,21 @@
</CampaignSettingDefinitions> </CampaignSettingDefinitions>
<CampaignSettings <CampaignSettings
presetname="Easy" presetname="Easy"
TutorialEnabled="true"
RadiationEnabled="false" RadiationEnabled="false"
StartingBalanceAmount="High" StartingBalanceAmount="High"
StartItemSet="easy" StartItemSet="easy"
Difficulty="Easy"/> Difficulty="Easy"/>
<CampaignSettings <CampaignSettings
presetname="Normal" presetname="Normal"
TutorialEnabled="true"
RadiationEnabled="false" RadiationEnabled="false"
StartingBalanceAmount="Medium" StartingBalanceAmount="Medium"
StartItemSet="normal" StartItemSet="normal"
Difficulty="Medium"/> Difficulty="Medium"/>
<CampaignSettings <CampaignSettings
presetname="Hard" presetname="Hard"
TutorialEnabled="false"
RadiationEnabled="true" RadiationEnabled="true"
StartingBalanceAmount="Low" StartingBalanceAmount="Low"
StartItemSet="hard" StartItemSet="hard"
@@ -8,7 +8,7 @@
<Preset <Preset
name="Moderator" name="Moderator"
description="Allowed to manage round settings, kick players and access server logs." description="Allowed to manage round settings, kick players and access server logs."
permissions="ManageRound,Kick,SelectSub,SelectMode,ManageCampaign,ConsoleCommands,ServerLog,ManageSettings,ManageMoney"> permissions="ManageRound,Kick,SelectSub,SelectMode,ManageCampaign,ConsoleCommands,ServerLog,ManageSettings,ManageMoney,ManageBotTalents">
<Command name="clientlist"/> <Command name="clientlist"/>
<Command name="readycheck"/> <Command name="readycheck"/>
<Command name="autorestart"/> <Command name="autorestart"/>
@@ -1836,8 +1836,7 @@ namespace Barotrauma
float GetTargetMaxSpeed() => Character.ApplyTemporarySpeedLimits(Character.AnimController.CurrentSwimParams.MovementSpeed * 0.3f); float GetTargetMaxSpeed() => Character.ApplyTemporarySpeedLimits(Character.AnimController.CurrentSwimParams.MovementSpeed * 0.3f);
} }
} }
if (selectedTargetingParams.AttackPattern == AttackPattern.Straight && AttackLimb is Limb attackLimb && attackLimb.attack.Ranged)
if (AttackLimb is Limb attackLimb && attackLimb.attack.Ranged)
{ {
bool advance = !canAttack && Character.InWater || distance > attackLimb.attack.Range * 0.9f; bool advance = !canAttack && Character.InWater || distance > attackLimb.attack.Range * 0.9f;
bool fallBack = canAttack && distance < Math.Min(250, attackLimb.attack.Range * 0.25f); bool fallBack = canAttack && distance < Math.Min(250, attackLimb.attack.Range * 0.25f);
@@ -1881,19 +1880,11 @@ namespace Barotrauma
} }
} }
} }
IDamageable damageTarget = wallTarget != null ? wallTarget.Structure : SelectedAiTarget?.Entity as IDamageable; Entity targetEntity = wallTarget?.Structure ?? SelectedAiTarget?.Entity;
if (AttackLimb?.attack is Attack { Ranged: true} attack) IDamageable damageTarget = targetEntity as IDamageable;
if (AttackLimb?.attack is Attack { Ranged: true} attack && targetEntity != null)
{ {
Limb limb = GetLimbToRotate(attack); AimRangedAttack(attack, targetEntity);
if (limb != null)
{
Vector2 toTarget = damageTarget.WorldPosition - limb.WorldPosition;
float offset = limb.Params.GetSpriteOrientation() - MathHelper.PiOver2;
limb.body.SuppressSmoothRotationCalls = false;
float angle = MathUtils.VectorToAngle(toTarget);
limb.body.SmoothRotate(angle + offset, attack.AimRotationTorque);
limb.body.SuppressSmoothRotationCalls = true;
}
} }
if (canAttack) if (canAttack)
{ {
@@ -1908,6 +1899,22 @@ namespace Barotrauma
} }
} }
public void AimRangedAttack(Attack attack, Entity targetEntity)
{
if (attack == null || attack.Ranged == false || targetEntity == null) { return; }
Character.SetInput(InputType.Aim, false, true);
Limb limb = GetLimbToRotate(attack);
if (limb != null)
{
Vector2 toTarget = targetEntity.WorldPosition - limb.WorldPosition;
float offset = limb.Params.GetSpriteOrientation() - MathHelper.PiOver2;
limb.body.SuppressSmoothRotationCalls = false;
float angle = MathUtils.VectorToAngle(toTarget);
limb.body.SmoothRotate(angle + offset, attack.AimRotationTorque);
limb.body.SuppressSmoothRotationCalls = true;
}
}
private bool IsValidAttack(Limb attackingLimb, IEnumerable<AttackContext> currentContexts, Entity target) private bool IsValidAttack(Limb attackingLimb, IEnumerable<AttackContext> currentContexts, Entity target)
{ {
if (attackingLimb == null) { return false; } if (attackingLimb == null) { return false; }
@@ -2190,11 +2197,11 @@ namespace Barotrauma
if (!ActiveAttack.IsRunning) if (!ActiveAttack.IsRunning)
{ {
#if SERVER #if SERVER
GameMain.NetworkMember.CreateEntityEvent(Character, new Character.SetAttackTargetEventData( GameMain.NetworkMember.CreateEntityEvent(Character, new Character.SetAttackTargetEventData(
AttackLimb, AttackLimb,
damageTarget, damageTarget,
targetLimb, targetLimb,
SimPosition)); SimPosition));
#else #else
Character.PlaySound(CharacterSound.SoundType.Attack, maxInterval: 3); Character.PlaySound(CharacterSound.SoundType.Attack, maxInterval: 3);
#endif #endif
@@ -3599,7 +3606,10 @@ namespace Barotrauma
{ {
// We only want to check the visibility when the target is in ruins/wreck/similiar place where sneaking should be possible. // We only want to check the visibility when the target is in ruins/wreck/similiar place where sneaking should be possible.
// When the monsters attack the player sub, they wall hack so that they can be more aggressive. // When the monsters attack the player sub, they wall hack so that they can be more aggressive.
checkVisibility = target.Entity.Submarine != null && target.Entity.Submarine == Character.Submarine && target.Entity.Submarine.TeamID == CharacterTeamType.None; // Pets should always check the visibility, unless the pet and the target are both outside the submarine -> shouldn't target when they can't perceive (= no wall hack)
checkVisibility =
Character.IsPet && (Character.Submarine == null) != (target.Entity.Submarine == null) ||
target.Entity.Submarine != null && target.Entity.Submarine == Character.Submarine && target.Entity.Submarine.TeamID == CharacterTeamType.None;
} }
if (dist > 0) if (dist > 0)
{ {
@@ -395,6 +395,10 @@ namespace Barotrauma
} }
objectiveManager.UpdateObjectives(deltaTime); objectiveManager.UpdateObjectives(deltaTime);
if (reportProblemsTimer > 0)
{
reportProblemsTimer -= deltaTime;
}
if (reactTimer > 0.0f) if (reactTimer > 0.0f)
{ {
reactTimer -= deltaTime; reactTimer -= deltaTime;
@@ -407,7 +411,6 @@ namespace Barotrauma
else else
{ {
Character.UpdateTeam(); Character.UpdateTeam();
if (Character.CurrentHull != null) if (Character.CurrentHull != null)
{ {
if (Character.IsOnPlayerTeam) if (Character.IsOnPlayerTeam)
@@ -425,19 +428,15 @@ namespace Barotrauma
} }
} }
} }
if (Character.SpeechImpediment < 100.0f) if (reportProblemsTimer <= 0.0f)
{ {
reportProblemsTimer -= deltaTime; if (Character.Submarine != null && (Character.Submarine.TeamID == Character.TeamID || Character.IsEscorted) && !Character.Submarine.Info.IsWreck)
if (reportProblemsTimer <= 0.0f)
{ {
if (Character.Submarine != null && (Character.Submarine.TeamID == Character.TeamID || Character.IsEscorted) && !Character.Submarine.Info.IsWreck) ReportProblems();
{
ReportProblems();
}
reportProblemsTimer = reportProblemsInterval;
} }
UpdateSpeaking(); reportProblemsTimer = reportProblemsInterval;
} }
UpdateSpeaking();
UnequipUnnecessaryItems(); UnequipUnnecessaryItems();
reactTimer = GetReactionTime(); reactTimer = GetReactionTime();
} }
@@ -912,7 +911,7 @@ namespace Barotrauma
{ {
Order newOrder = null; Order newOrder = null;
Hull targetHull = null; Hull targetHull = null;
bool speak = true; bool speak = Character.SpeechImpediment < 100;
if (Character.CurrentHull != null) if (Character.CurrentHull != null)
{ {
bool isFighting = ObjectiveManager.HasActiveObjective<AIObjectiveCombat>(); bool isFighting = ObjectiveManager.HasActiveObjective<AIObjectiveCombat>();
@@ -1063,17 +1062,15 @@ namespace Barotrauma
private void UpdateSpeaking() private void UpdateSpeaking()
{ {
if (!Character.IsOnPlayerTeam) { return; } if (!Character.IsOnPlayerTeam) { return; }
if (Character.SpeechImpediment >= 100) { return; }
if (Character.Oxygen < 20.0f) if (Character.Oxygen < 20.0f)
{ {
Character.Speak(TextManager.Get("DialogLowOxygen").Value, null, Rand.Range(0.5f, 5.0f), "lowoxygen".ToIdentifier(), 30.0f); Character.Speak(TextManager.Get("DialogLowOxygen").Value, null, Rand.Range(0.5f, 5.0f), "lowoxygen".ToIdentifier(), 30.0f);
} }
if (Character.Bleeding > 2.0f) if (Character.Bleeding > 2.0f)
{ {
Character.Speak(TextManager.Get("DialogBleeding").Value, null, Rand.Range(0.5f, 5.0f), "bleeding".ToIdentifier(), 30.0f); Character.Speak(TextManager.Get("DialogBleeding").Value, null, Rand.Range(0.5f, 5.0f), "bleeding".ToIdentifier(), 30.0f);
} }
if (Character.PressureTimer > 50.0f && Character.CurrentHull?.DisplayName != null) if (Character.PressureTimer > 50.0f && Character.CurrentHull?.DisplayName != null)
{ {
Character.Speak(TextManager.GetWithVariable("DialogPressure", "[roomname]", Character.CurrentHull.DisplayName, FormatCapitals.Yes).Value, null, Rand.Range(0.5f, 5.0f), "pressure".ToIdentifier(), 30.0f); Character.Speak(TextManager.GetWithVariable("DialogPressure", "[roomname]", Character.CurrentHull.DisplayName, FormatCapitals.Yes).Value, null, Rand.Range(0.5f, 5.0f), "pressure".ToIdentifier(), 30.0f);
@@ -1860,6 +1857,7 @@ namespace Barotrauma
bool targetAdded = false; bool targetAdded = false;
DoForEachCrewMember(caller, humanAI => DoForEachCrewMember(caller, humanAI =>
{ {
if (caller != humanAI.Character && caller.SpeechImpediment >= 100) { return; }
var objective = humanAI.ObjectiveManager.GetObjective<T1>(); var objective = humanAI.ObjectiveManager.GetObjective<T1>();
if (objective != null) if (objective != null)
{ {
@@ -502,10 +502,12 @@ namespace Barotrauma
public static IEnumerable<Affliction> GetTreatableAfflictions(Character character) public static IEnumerable<Affliction> GetTreatableAfflictions(Character character)
{ {
foreach (Affliction affliction in character.CharacterHealth.GetAllAfflictions()) var allAfflictions = character.CharacterHealth.GetAllAfflictions();
foreach (Affliction affliction in allAfflictions)
{ {
if (affliction.Prefab.IsBuff || affliction.Strength < affliction.Prefab.TreatmentThreshold) { continue; } if (affliction.Prefab.IsBuff || affliction.Strength < affliction.Prefab.TreatmentThreshold) { continue; }
if (!affliction.Prefab.TreatmentSuitability.Any(kvp => kvp.Value > 0)) { continue; } if (!affliction.Prefab.TreatmentSuitability.Any(kvp => kvp.Value > 0)) { continue; }
if (allAfflictions.Any(otherAffliction => affliction.Prefab.IgnoreTreatmentIfAfflictedBy.Contains(otherAffliction.Identifier))) { continue; }
yield return affliction; yield return affliction;
} }
} }
@@ -1836,8 +1836,6 @@ namespace Barotrauma
{ {
heldItem.FlipX(relativeToSub: false); heldItem.FlipX(relativeToSub: false);
} }
// TODO: was this added by a mistake?
//heldItem.FlipX(relativeToSub: false);
} }
foreach (Limb limb in Limbs) foreach (Limb limb in Limbs)
@@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Xml.Linq; using System.Xml.Linq;
using Barotrauma.Items.Components;
namespace Barotrauma namespace Barotrauma
{ {
@@ -530,7 +531,7 @@ namespace Barotrauma
effect.HasTargetType(StatusEffect.TargetType.NearbyCharacters)) effect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
{ {
targets.Clear(); targets.Clear();
targets.AddRange(effect.GetNearbyTargets(worldPosition, targets)); effect.AddNearbyTargets(worldPosition, targets);
effect.Apply(effectType, deltaTime, targetEntity, targets); effect.Apply(effectType, deltaTime, targetEntity, targets);
} }
if (effect.HasTargetType(StatusEffect.TargetType.UseTarget)) if (effect.HasTargetType(StatusEffect.TargetType.UseTarget))
@@ -569,7 +570,15 @@ namespace Barotrauma
DamageParticles(deltaTime, worldPosition); DamageParticles(deltaTime, worldPosition);
var attackResult = targetLimb.character.ApplyAttack(attacker, worldPosition, this, deltaTime, playSound, targetLimb, penetration: Penetration); float penetration = Penetration;
float? penetrationValue = SourceItem?.GetComponent<RangedWeapon>()?.Penetration;
if (penetrationValue.HasValue)
{
penetration += penetrationValue.Value;
}
var attackResult = targetLimb.character.ApplyAttack(attacker, worldPosition, this, deltaTime, playSound, targetLimb, penetration);
var effectType = attackResult.Damage > 0.0f ? ActionType.OnUse : ActionType.OnFailure; var effectType = attackResult.Damage > 0.0f ? ActionType.OnUse : ActionType.OnFailure;
foreach (StatusEffect effect in statusEffects) foreach (StatusEffect effect in statusEffects)
@@ -599,7 +608,7 @@ namespace Barotrauma
effect.HasTargetType(StatusEffect.TargetType.NearbyCharacters)) effect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
{ {
targets.Clear(); targets.Clear();
targets.AddRange(effect.GetNearbyTargets(worldPosition, targets)); effect.AddNearbyTargets(worldPosition, targets);
effect.Apply(effectType, deltaTime, targetLimb.character, targets); effect.Apply(effectType, deltaTime, targetLimb.character, targets);
} }
if (effect.HasTargetType(StatusEffect.TargetType.UseTarget)) if (effect.HasTargetType(StatusEffect.TargetType.UseTarget))
@@ -1601,6 +1601,15 @@ namespace Barotrauma
if (Info?.Job == null) { return 0.0f; } if (Info?.Job == null) { return 0.0f; }
float skillLevel = Info.Job.GetSkillLevel(skillIdentifier); float skillLevel = Info.Job.GetSkillLevel(skillIdentifier);
if (overrideStatTypes.TryGetValue(skillIdentifier, out StatTypes statType))
{
float skillOverride = GetStatValue(statType);
if (skillOverride > skillLevel)
{
skillLevel = skillOverride;
}
}
// apply multipliers first so that multipliers only affect base skill value // apply multipliers first so that multipliers only affect base skill value
foreach (Affliction affliction in CharacterHealth.GetAllAfflictions()) foreach (Affliction affliction in CharacterHealth.GetAllAfflictions())
{ {
@@ -1631,15 +1640,6 @@ namespace Barotrauma
skillLevel += GetStatValue(GetSkillStatType(skillIdentifier)); skillLevel += GetStatValue(GetSkillStatType(skillIdentifier));
if (overrideStatTypes.TryGetValue(skillIdentifier, out StatTypes statType))
{
float skillOverride = GetStatValue(statType);
if (skillOverride > skillLevel)
{
skillLevel = skillOverride;
}
}
return skillLevel; return skillLevel;
} }
@@ -1972,6 +1972,15 @@ namespace Barotrauma
} }
} }
#endif #endif
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient && Controlled != this && IsKeyDown(InputType.Aim))
{
if (currentAttackTarget.AttackLimb?.attack is Attack { Ranged: true } attack && AIController is EnemyAIController enemyAi)
{
enemyAi.AimRangedAttack(attack, currentAttackTarget.DamageTarget as Entity);
}
}
if (attackCoolDown > 0.0f) if (attackCoolDown > 0.0f)
{ {
attackCoolDown -= deltaTime; attackCoolDown -= deltaTime;
@@ -1982,7 +1991,7 @@ namespace Barotrauma
{ {
if ((currentAttackTarget.DamageTarget as Entity)?.Removed ?? false) if ((currentAttackTarget.DamageTarget as Entity)?.Removed ?? false)
{ {
currentAttackTarget = default(AttackTargetData); currentAttackTarget = default;
} }
currentAttackTarget.AttackLimb?.UpdateAttack(deltaTime, currentAttackTarget.AttackPos, currentAttackTarget.DamageTarget, out _); currentAttackTarget.AttackLimb?.UpdateAttack(deltaTime, currentAttackTarget.AttackPos, currentAttackTarget.DamageTarget, out _);
} }
@@ -2077,19 +2086,22 @@ namespace Barotrauma
} }
} }
bool CanUseItemsWhenSelected(Item item) => item == null || !item.Prefab.DisableItemUsageWhenSelected; if (Inventory != null)
if (CanUseItemsWhenSelected(SelectedItem) && CanUseItemsWhenSelected(SelectedSecondaryItem))
{ {
foreach (Item item in HeldItems) bool CanUseItemsWhenSelected(Item item) => item == null || !item.Prefab.DisableItemUsageWhenSelected;
if (CanUseItemsWhenSelected(SelectedItem) && CanUseItemsWhenSelected(SelectedSecondaryItem))
{ {
tryUseItem(item, deltaTime); foreach (Item item in HeldItems)
}
foreach (Item item in Inventory.AllItems)
{
if (item.GetComponent<Wearable>() is { AllowUseWhenWorn: true } && HasEquippedItem(item))
{ {
tryUseItem(item, deltaTime); tryUseItem(item, deltaTime);
} }
foreach (Item item in Inventory.AllItems)
{
if (item.GetComponent<Wearable>() is { AllowUseWhenWorn: true } && HasEquippedItem(item))
{
tryUseItem(item, deltaTime);
}
}
} }
} }
@@ -2170,6 +2182,8 @@ namespace Barotrauma
private AttackTargetData currentAttackTarget; private AttackTargetData currentAttackTarget;
public void SetAttackTarget(Limb attackLimb, IDamageable damageTarget, Vector2 attackPos) public void SetAttackTarget(Limb attackLimb, IDamageable damageTarget, Vector2 attackPos)
{ {
DebugConsole.NewMessage($"SetAttackTarget {this.ToString()}: " + (damageTarget?.ToString() ?? null));
currentAttackTarget = new AttackTargetData() currentAttackTarget = new AttackTargetData()
{ {
AttackLimb = attackLimb, AttackLimb = attackLimb,
@@ -3824,7 +3838,7 @@ namespace Barotrauma
return attackResult; return attackResult;
} }
public void TrySeverLimbJoints(Limb targetLimb, float severLimbsProbability, float damage, bool allowBeheading, Character attacker = null) public void TrySeverLimbJoints(Limb targetLimb, float severLimbsProbability, float damage, bool allowBeheading, bool ignoreSeveranceProbabilityModifier = false, Character attacker = null)
{ {
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; } if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
#if DEBUG #if DEBUG
@@ -3852,7 +3866,7 @@ namespace Barotrauma
var referenceLimb = targetLimb.type == LimbType.Head && targetLimb.Params.ID == 0 ? joint.LimbA : joint.LimbB; var referenceLimb = targetLimb.type == LimbType.Head && targetLimb.Params.ID == 0 ? joint.LimbA : joint.LimbB;
if (referenceLimb != targetLimb) { continue; } if (referenceLimb != targetLimb) { continue; }
float probability = severLimbsProbability; float probability = severLimbsProbability;
if (!IsDead && probability < 1) if (!IsDead && !ignoreSeveranceProbabilityModifier)
{ {
probability *= joint.Params.SeveranceProbabilityModifier; probability *= joint.Params.SeveranceProbabilityModifier;
} }
@@ -4111,7 +4125,7 @@ namespace Barotrauma
statusEffect.HasTargetType(StatusEffect.TargetType.NearbyCharacters)) statusEffect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
{ {
targets.Clear(); targets.Clear();
targets.AddRange(statusEffect.GetNearbyTargets(WorldPosition, targets)); statusEffect.AddNearbyTargets(WorldPosition, targets);
statusEffect.Apply(actionType, deltaTime, this, targets); statusEffect.Apply(actionType, deltaTime, this, targets);
} }
else if (statusEffect.targetLimbs != null) else if (statusEffect.targetLimbs != null)
@@ -4745,6 +4759,8 @@ namespace Barotrauma
public bool HasJob(string identifier) => Info?.Job?.Prefab.Identifier == identifier; public bool HasJob(string identifier) => Info?.Job?.Prefab.Identifier == identifier;
public bool HasJob(Identifier identifier) => Info?.Job?.Prefab.Identifier == identifier;
public bool IsProtectedFromPressure() public bool IsProtectedFromPressure()
{ {
return HasAbilityFlag(AbilityFlags.ImmuneToPressure) || PressureProtection >= (Level.Loaded?.GetRealWorldDepth(WorldPosition.Y) ?? 1.0f); return HasAbilityFlag(AbilityFlags.ImmuneToPressure) || PressureProtection >= (Level.Loaded?.GetRealWorldDepth(WorldPosition.Y) ?? 1.0f);
@@ -4975,7 +4991,7 @@ namespace Barotrauma
/// </summary> /// </summary>
private readonly Dictionary<StatTypes, float> wearableStatValues = new Dictionary<StatTypes, float>(); private readonly Dictionary<StatTypes, float> wearableStatValues = new Dictionary<StatTypes, float>();
public float GetStatValue(StatTypes statType) public float GetStatValue(StatTypes statType, bool includeSaved = true)
{ {
if (!IsHuman) { return 0f; } if (!IsHuman) { return 0f; }
@@ -4988,7 +5004,7 @@ namespace Barotrauma
{ {
statValue += CharacterHealth.GetStatValue(statType); statValue += CharacterHealth.GetStatValue(statType);
} }
if (Info != null) if (Info != null && includeSaved)
{ {
// could be optimized by instead updating the Character.cs statvalues dictionary whenever the CharacterInfo.cs values change // could be optimized by instead updating the Character.cs statvalues dictionary whenever the CharacterInfo.cs values change
statValue += Info.GetSavedStatValue(statType); statValue += Info.GetSavedStatValue(statType);
@@ -1233,10 +1233,6 @@ namespace Barotrauma
int prevAmount = ExperiencePoints; int prevAmount = ExperiencePoints;
var experienceGainMultiplier = new AbilityExperienceGainMultiplier(1f); var experienceGainMultiplier = new AbilityExperienceGainMultiplier(1f);
if (isMissionExperience)
{
Character?.CheckTalents(AbilityEffectType.OnGainMissionExperience, experienceGainMultiplier);
}
experienceGainMultiplier.Value += Character?.GetStatValue(StatTypes.ExperienceGainMultiplier) ?? 0; experienceGainMultiplier.Value += Character?.GetStatValue(StatTypes.ExperienceGainMultiplier) ?? 0;
amount = (int)(amount * experienceGainMultiplier.Value); amount = (int)(amount * experienceGainMultiplier.Value);
@@ -1808,7 +1804,7 @@ namespace Barotrauma
{ {
if (SavedStatValues.TryGetValue(statType, out var statValues)) if (SavedStatValues.TryGetValue(statType, out var statValues))
{ {
return statValues.Where(s => s.StatIdentifier == statIdentifier).Sum(v => v.StatValue); return statValues.Where(value => ToolBox.StatIdentifierMatches(value.StatIdentifier, statIdentifier)).Sum(static v => v.StatValue);
} }
else else
{ {
@@ -429,7 +429,7 @@ namespace Barotrauma
statusEffect.HasTargetType(StatusEffect.TargetType.NearbyCharacters)) statusEffect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
{ {
targets.Clear(); targets.Clear();
targets.AddRange(statusEffect.GetNearbyTargets(characterHealth.Character.WorldPosition, targets)); statusEffect.AddNearbyTargets(characterHealth.Character.WorldPosition, targets);
statusEffect.Apply(type, deltaTime, characterHealth.Character, targets); statusEffect.Apply(type, deltaTime, characterHealth.Character, targets);
} }
} }
@@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.Reflection; using System.Reflection;
using System.Xml.Linq; using System.Xml.Linq;
using Barotrauma.Extensions; using Barotrauma.Extensions;
using System.Collections.Immutable;
namespace Barotrauma namespace Barotrauma
{ {
@@ -214,7 +215,6 @@ namespace Barotrauma
[Serialize("", IsPropertySaveable.No)] [Serialize("", IsPropertySaveable.No)]
public Identifier DialogFlag { get; private set; } public Identifier DialogFlag { get; private set; }
[Serialize("", IsPropertySaveable.No)] [Serialize("", IsPropertySaveable.No)]
public Identifier Tag { get; private set; } public Identifier Tag { get; private set; }
@@ -276,6 +276,47 @@ namespace Barotrauma
} }
} }
public class Description
{
public enum TargetType
{
Any,
Self,
OtherCharacter
}
public readonly LocalizedString Text;
public readonly Identifier TextTag;
public readonly float MinStrength, MaxStrength;
public readonly TargetType Target;
public Description(ContentXElement element, AfflictionPrefab affliction)
{
TextTag = element.GetAttributeIdentifier("textidentifier", Identifier.Empty);
if (!TextTag.IsEmpty)
{
Text = TextManager.Get(TextTag);
}
string text = element.GetAttributeString("text", string.Empty);
if (!text.IsNullOrEmpty())
{
Text = Text?.Fallback(text) ?? text;
}
else if (TextTag.IsEmpty)
{
DebugConsole.ThrowError($"Error in affliction \"{affliction.Identifier}\" - no text defined for one of the descriptions.");
}
MinStrength = element.GetAttributeFloat(nameof(MinStrength), 0.0f);
MaxStrength = element.GetAttributeFloat(nameof(MaxStrength), 100.0f);
if (MinStrength >= MaxStrength)
{
DebugConsole.ThrowError($"Error in affliction \"{affliction.Identifier}\" - max strength is not larger than min.");
}
Target = element.GetAttributeEnum(nameof(Target), TargetType.Any);
}
}
public class PeriodicEffect public class PeriodicEffect
{ {
public readonly List<StatusEffect> StatusEffects = new List<StatusEffect>(); public readonly List<StatusEffect> StatusEffects = new List<StatusEffect>();
@@ -313,7 +354,6 @@ namespace Barotrauma
public static readonly PrefabCollection<AfflictionPrefab> Prefabs = new PrefabCollection<AfflictionPrefab>(); public static readonly PrefabCollection<AfflictionPrefab> Prefabs = new PrefabCollection<AfflictionPrefab>();
private bool disposed = false;
public override void Dispose() { } public override void Dispose() { }
public static IEnumerable<AfflictionPrefab> List => Prefabs; public static IEnumerable<AfflictionPrefab> List => Prefabs;
@@ -330,15 +370,21 @@ namespace Barotrauma
//(e.g. mental health problems on head, lack of oxygen on torso...) //(e.g. mental health problems on head, lack of oxygen on torso...)
public readonly LimbType IndicatorLimb; public readonly LimbType IndicatorLimb;
public readonly LocalizedString Name, Description; public readonly LocalizedString Name;
public readonly Identifier TranslationIdentifier; public readonly Identifier TranslationIdentifier;
public readonly bool IsBuff; public readonly bool IsBuff;
public readonly bool AffectMachines;
public readonly bool HealableInMedicalClinic; public readonly bool HealableInMedicalClinic;
public readonly float HealCostMultiplier; public readonly float HealCostMultiplier;
public readonly int BaseHealCost; public readonly int BaseHealCost;
public readonly LocalizedString CauseOfDeathDescription, SelfCauseOfDeathDescription; public readonly LocalizedString CauseOfDeathDescription, SelfCauseOfDeathDescription;
private readonly LocalizedString defaultDescription;
public readonly ImmutableList<Description> Descriptions;
public readonly bool HideIconAfterDelay;
//how high the strength has to be for the affliction to take affect //how high the strength has to be for the affliction to take affect
public readonly float ActivationThreshold = 0.0f; public readonly float ActivationThreshold = 0.0f;
//how high the strength has to be for the affliction icon to be shown in the UI //how high the strength has to be for the affliction icon to be shown in the UI
@@ -355,6 +401,11 @@ namespace Barotrauma
//how strong the affliction needs to be before bots attempt to treat it //how strong the affliction needs to be before bots attempt to treat it
public readonly float TreatmentThreshold = 5.0f; public readonly float TreatmentThreshold = 5.0f;
/// <summary>
/// Bots will not try to treat the affliction if the character has any of these afflictions
/// </summary>
public ImmutableHashSet<Identifier> IgnoreTreatmentIfAfflictedBy;
/// <summary> /// <summary>
/// The affliction is automatically removed after this time. 0 = unlimited /// The affliction is automatically removed after this time. 0 = unlimited
/// </summary> /// </summary>
@@ -413,13 +464,14 @@ namespace Barotrauma
{ {
Name = Name.Fallback(fallbackName); Name = Name.Fallback(fallbackName);
} }
Description = TextManager.Get($"AfflictionDescription.{TranslationIdentifier}"); defaultDescription = TextManager.Get($"AfflictionDescription.{TranslationIdentifier}");
string fallbackDescription = element.GetAttributeString("description", ""); string fallbackDescription = element.GetAttributeString("description", "");
if (!string.IsNullOrEmpty(fallbackDescription)) if (!string.IsNullOrEmpty(fallbackDescription))
{ {
Description = Description.Fallback(fallbackDescription); defaultDescription = defaultDescription.Fallback(fallbackDescription);
} }
IsBuff = element.GetAttributeBool("isbuff", false); IsBuff = element.GetAttributeBool(nameof(IsBuff), false);
AffectMachines = element.GetAttributeBool(nameof(AffectMachines), true);
HealableInMedicalClinic = element.GetAttributeBool("healableinmedicalclinic", HealableInMedicalClinic = element.GetAttributeBool("healableinmedicalclinic",
!IsBuff && !IsBuff &&
@@ -428,6 +480,8 @@ namespace Barotrauma
HealCostMultiplier = element.GetAttributeFloat(nameof(HealCostMultiplier), 1f); HealCostMultiplier = element.GetAttributeFloat(nameof(HealCostMultiplier), 1f);
BaseHealCost = element.GetAttributeInt(nameof(BaseHealCost), 0); BaseHealCost = element.GetAttributeInt(nameof(BaseHealCost), 0);
IgnoreTreatmentIfAfflictedBy = element.GetAttributeIdentifierArray(nameof(IgnoreTreatmentIfAfflictedBy), Array.Empty<Identifier>()).ToImmutableHashSet();
Duration = element.GetAttributeFloat(nameof(Duration), 0.0f); Duration = element.GetAttributeFloat(nameof(Duration), 0.0f);
if (element.GetAttribute("nameidentifier") != null) if (element.GetAttribute("nameidentifier") != null)
@@ -445,30 +499,33 @@ namespace Barotrauma
} }
} }
ActivationThreshold = element.GetAttributeFloat("activationthreshold", 0.0f); HideIconAfterDelay = element.GetAttributeBool(nameof(HideIconAfterDelay), false);
ShowIconThreshold = element.GetAttributeFloat("showiconthreshold", Math.Max(ActivationThreshold, 0.05f));
ShowIconToOthersThreshold = element.GetAttributeFloat("showicontoothersthreshold", ShowIconThreshold);
MaxStrength = element.GetAttributeFloat("maxstrength", 100.0f);
GrainBurst = element.GetAttributeFloat(nameof(GrainBurst).ToLowerInvariant(), 0.0f);
ShowInHealthScannerThreshold = element.GetAttributeFloat("showinhealthscannerthreshold", ActivationThreshold = element.GetAttributeFloat(nameof(ActivationThreshold), 0.0f);
ShowIconThreshold = element.GetAttributeFloat(nameof(ShowIconThreshold), Math.Max(ActivationThreshold, 0.05f));
ShowIconToOthersThreshold = element.GetAttributeFloat(nameof(ShowIconToOthersThreshold), ShowIconThreshold);
MaxStrength = element.GetAttributeFloat(nameof(MaxStrength), 100.0f);
GrainBurst = element.GetAttributeFloat(nameof(GrainBurst), 0.0f);
ShowInHealthScannerThreshold = element.GetAttributeFloat(nameof(ShowInHealthScannerThreshold),
Math.Max(ActivationThreshold, AfflictionType == "talentbuff" ? float.MaxValue : ShowIconToOthersThreshold)); Math.Max(ActivationThreshold, AfflictionType == "talentbuff" ? float.MaxValue : ShowIconToOthersThreshold));
TreatmentThreshold = element.GetAttributeFloat("treatmentthreshold", Math.Max(ActivationThreshold, 5.0f)); TreatmentThreshold = element.GetAttributeFloat(nameof(TreatmentThreshold), Math.Max(ActivationThreshold, 5.0f));
DamageOverlayAlpha = element.GetAttributeFloat("damageoverlayalpha", 0.0f); DamageOverlayAlpha = element.GetAttributeFloat(nameof(DamageOverlayAlpha), 0.0f);
BurnOverlayAlpha = element.GetAttributeFloat("burnoverlayalpha", 0.0f); BurnOverlayAlpha = element.GetAttributeFloat(nameof(BurnOverlayAlpha), 0.0f);
KarmaChangeOnApplied = element.GetAttributeFloat("karmachangeonapplied", 0.0f); KarmaChangeOnApplied = element.GetAttributeFloat(nameof(KarmaChangeOnApplied), 0.0f);
CauseOfDeathDescription = TextManager.Get($"AfflictionCauseOfDeath.{TranslationIdentifier}").Fallback(element.GetAttributeString("causeofdeathdescription", "")); CauseOfDeathDescription = TextManager.Get($"AfflictionCauseOfDeath.{TranslationIdentifier}").Fallback(element.GetAttributeString("causeofdeathdescription", ""));
SelfCauseOfDeathDescription = TextManager.Get($"AfflictionCauseOfDeathSelf.{TranslationIdentifier}").Fallback(element.GetAttributeString("selfcauseofdeathdescription", "")); SelfCauseOfDeathDescription = TextManager.Get($"AfflictionCauseOfDeathSelf.{TranslationIdentifier}").Fallback(element.GetAttributeString("selfcauseofdeathdescription", ""));
IconColors = element.GetAttributeColorArray("iconcolors", null); IconColors = element.GetAttributeColorArray(nameof(IconColors), null);
AfflictionOverlayAlphaIsLinear = element.GetAttributeBool("afflictionoverlayalphaislinear", false); AfflictionOverlayAlphaIsLinear = element.GetAttributeBool(nameof(AfflictionOverlayAlphaIsLinear), false);
AchievementOnRemoved = element.GetAttributeIdentifier("achievementonremoved", ""); AchievementOnRemoved = element.GetAttributeIdentifier(nameof(AchievementOnRemoved), "");
ResetBetweenRounds = element.GetAttributeBool("resetbetweenrounds", false); ResetBetweenRounds = element.GetAttributeBool("resetbetweenrounds", false);
List<Description> descriptions = new List<Description>();
foreach (var subElement in element.Elements()) foreach (var subElement in element.Elements())
{ {
switch (subElement.Name.ToString().ToLowerInvariant()) switch (subElement.Name.ToString().ToLowerInvariant())
@@ -485,15 +542,38 @@ namespace Barotrauma
case "effect": case "effect":
case "periodiceffect": case "periodiceffect":
break; break;
case "description":
descriptions.Add(new Description(subElement, this));
break;
default: default:
DebugConsole.AddWarning($"Unrecognized element in affliction \"{Identifier}\" ({subElement.Name})"); DebugConsole.AddWarning($"Unrecognized element in affliction \"{Identifier}\" ({subElement.Name})");
break; break;
} }
} }
Descriptions = descriptions.ToImmutableList();
constructor = type.GetConstructor(new[] { typeof(AfflictionPrefab), typeof(float) }); constructor = type.GetConstructor(new[] { typeof(AfflictionPrefab), typeof(float) });
} }
public LocalizedString GetDescription(float strength, Description.TargetType targetType)
{
foreach (var description in Descriptions)
{
if (strength < description.MinStrength || strength > description.MaxStrength) { continue; }
switch (targetType)
{
case Description.TargetType.Self:
if (description.Target == Description.TargetType.OtherCharacter) { continue; }
break;
case Description.TargetType.OtherCharacter:
if (description.Target == Description.TargetType.Self) { continue; }
break;
}
return description.Text;
}
return defaultDescription;
}
public static void LoadAllEffects() public static void LoadAllEffects()
{ {
Prefabs.ForEach(p => p.LoadEffects()); Prefabs.ForEach(p => p.LoadEffects());
@@ -104,7 +104,7 @@ namespace Barotrauma
public bool DoesBleed public bool DoesBleed
{ {
get => Character.Params.Health.DoesBleed; get => Character.Params.Health.DoesBleed && !Character.Params.IsMachine;
private set => Character.Params.Health.DoesBleed = value; private set => Character.Params.Health.DoesBleed = value;
} }
@@ -550,7 +550,7 @@ namespace Barotrauma
amount -= reduceAmount; amount -= reduceAmount;
if (treatmentAction != null) if (treatmentAction != null)
{ {
if (treatmentAction.Value == ActionType.OnUse) if (treatmentAction.Value == ActionType.OnUse || treatmentAction.Value == ActionType.OnSuccess)
{ {
matchingAffliction.AppliedAsSuccessfulTreatmentTime = Timing.TotalTime; matchingAffliction.AppliedAsSuccessfulTreatmentTime = Timing.TotalTime;
} }
@@ -690,6 +690,7 @@ namespace Barotrauma
private void AddLimbAffliction(LimbHealth limbHealth, Affliction newAffliction, bool allowStacking = true) private void AddLimbAffliction(LimbHealth limbHealth, Affliction newAffliction, bool allowStacking = true)
{ {
if (Character.Params.IsMachine && !newAffliction.Prefab.AffectMachines) { return; }
if (!DoesBleed && newAffliction is AfflictionBleeding) { return; } if (!DoesBleed && newAffliction is AfflictionBleeding) { return; }
if (!Character.NeedsOxygen && newAffliction.Prefab == AfflictionPrefab.OxygenLow) { return; } if (!Character.NeedsOxygen && newAffliction.Prefab == AfflictionPrefab.OxygenLow) { return; }
if (Character.Params.Health.StunImmunity && newAffliction.Prefab.AfflictionType == "stun") { return; } if (Character.Params.Health.StunImmunity && newAffliction.Prefab.AfflictionType == "stun") { return; }
@@ -1076,6 +1077,7 @@ namespace Barotrauma
} }
if (strength <= affliction.Prefab.TreatmentThreshold) { continue; } if (strength <= affliction.Prefab.TreatmentThreshold) { continue; }
if (afflictions.Any(otherAffliction => affliction.Prefab.IgnoreTreatmentIfAfflictedBy.Contains(otherAffliction.Key.Identifier))) { continue; }
if (ignoreHiddenAfflictions) if (ignoreHiddenAfflictions)
{ {
@@ -79,27 +79,35 @@ namespace Barotrauma
public ref readonly ImmutableArray<Identifier> ParsedAfflictionTypes => ref parsedAfflictionTypes; public ref readonly ImmutableArray<Identifier> ParsedAfflictionTypes => ref parsedAfflictionTypes;
public DamageModifier(XElement element, string parentDebugName) public DamageModifier(XElement element, string parentDebugName, bool checkErrors = true)
{ {
Deserialize(element); Deserialize(element);
if (element.Attribute("afflictionnames") != null) if (element.Attribute("afflictionnames") != null)
{ {
DebugConsole.ThrowError("Error in DamageModifier config (" + parentDebugName + ") - define afflictions using identifiers or types instead of names."); DebugConsole.ThrowError("Error in DamageModifier config (" + parentDebugName + ") - define afflictions using identifiers or types instead of names.");
} }
foreach (var afflictionType in parsedAfflictionTypes) if (checkErrors)
{ {
if (!AfflictionPrefab.Prefabs.Any(p => p.AfflictionType == afflictionType)) foreach (var afflictionType in parsedAfflictionTypes)
{ {
createWarningOrError($"Potentially invalid damage modifier in \"{parentDebugName}\". Could not find any afflictions of the type \"{afflictionType}\". Did you mean to use an affliction identifier instead?"); if (!AfflictionPrefab.Prefabs.Any(p => p.AfflictionType == afflictionType))
} {
} createWarningOrError($"Potentially invalid damage modifier in \"{parentDebugName}\". Could not find any afflictions of the type \"{afflictionType}\". Did you mean to use an affliction identifier instead?");
foreach (var afflictionIdentifier in parsedAfflictionIdentifiers) }
{ }
if (!AfflictionPrefab.Prefabs.ContainsKey(afflictionIdentifier)) foreach (var afflictionIdentifier in parsedAfflictionIdentifiers)
{ {
createWarningOrError($"Potentially invalid damage modifier in \"{parentDebugName}\". Could not find any afflictions with the identifier \"{afflictionIdentifier}\". Did you mean to use an affliction type instead?"); if (!AfflictionPrefab.Prefabs.ContainsKey(afflictionIdentifier))
{
createWarningOrError($"Potentially invalid damage modifier in \"{parentDebugName}\". Could not find any afflictions with the identifier \"{afflictionIdentifier}\". Did you mean to use an affliction type instead?");
}
}
if (!parsedAfflictionTypes.Any() && !parsedAfflictionIdentifiers.Any())
{
createWarningOrError($"Potentially invalid damage modifier in \"{parentDebugName}\". Neither affliction types of identifiers defined.");
} }
} }
static void createWarningOrError(string msg) static void createWarningOrError(string msg)
{ {
#if DEBUG #if DEBUG
@@ -1196,7 +1196,7 @@ namespace Barotrauma
statusEffect.HasTargetType(StatusEffect.TargetType.NearbyCharacters)) statusEffect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
{ {
targets.Clear(); targets.Clear();
targets.AddRange(statusEffect.GetNearbyTargets(WorldPosition, targets)); statusEffect.AddNearbyTargets(WorldPosition, targets);
statusEffect.Apply(actionType, deltaTime, character, targets); statusEffect.Apply(actionType, deltaTime, character, targets);
} }
else else
@@ -50,6 +50,9 @@ namespace Barotrauma
[Serialize(false, IsPropertySaveable.Yes, description: "Can the creature live without water or does it die on dry land?"), Editable] [Serialize(false, IsPropertySaveable.Yes, description: "Can the creature live without water or does it die on dry land?"), Editable]
public bool NeedsWater { get; set; } public bool NeedsWater { get; set; }
[Serialize(false, IsPropertySaveable.Yes, description: "Is this creature an artificial creature, like robot or machine that shouldn't be affected by afflictions that affect only organic creatures? Overrides DoesBleed."), Editable]
public bool IsMachine { get; set; }
[Serialize(false, IsPropertySaveable.No), Editable] [Serialize(false, IsPropertySaveable.No), Editable]
public bool CanSpeak { get; set; } public bool CanSpeak { get; set; }
@@ -27,7 +27,8 @@ namespace Barotrauma.Abilities
{ {
if (isPositiveReputation) if (isPositiveReputation)
{ {
if (abilityLocation.Location.Reputation.Faction.Reputation.Value <= 0) { return false; } if (abilityLocation.Location?.Reputation is not { } reputation) { return false; }
if (reputation.Value <= 0) { return false; }
} }
if (locationIdentifiers.Any()) if (locationIdentifiers.Any())
@@ -33,13 +33,20 @@ namespace Barotrauma.Abilities
{ {
if (abilityObject is IAbilityMission { Mission: { } mission }) if (abilityObject is IAbilityMission { Mission: { } mission })
{ {
if (isAffiliated && GameMain.GameSession?.Campaign?.Factions.MaxBy(static f => f.Reputation.Value) is { } highestFaction) if (isAffiliated)
{ {
if (highestFaction.Reputation.Value < 0 || !mission.ReputationRewards.ContainsKey(highestFaction.Reputation.Identifier)) if (GameMain.GameSession?.Campaign?.Factions is not { } factions) { return false; }
// FIXME there's probably a better way to check the faction affiliated with the mission later
foreach (Identifier factionIdentifier in mission.ReputationRewards.Keys)
{ {
return false; if (factions.Where(faction => factionIdentifier == faction.Prefab.Identifier).Any(static faction => faction.GetPlayerAffiliationStatus() != FactionAffiliation.Affiliated))
{
return false;
}
} }
} }
return missionType.Contains(mission.Prefab.Type); return missionType.Contains(mission.Prefab.Type);
} }
@@ -1,9 +1,5 @@
using System; using Barotrauma.Extensions;
using Barotrauma.Items.Components; using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using Barotrauma.Extensions;
namespace Barotrauma.Abilities namespace Barotrauma.Abilities
{ {
@@ -24,7 +24,7 @@ internal sealed class AbilityConditionNearbyCharacterCount : AbilityConditionDat
foreach (Character otherCharacter in Character.CharacterList) foreach (Character otherCharacter in Character.CharacterList)
{ {
if (character.Submarine != otherCharacter.Submarine) { continue; } if (character.Submarine != otherCharacter.Submarine) { continue; }
if (!IsViableTarget(targetTypes, otherCharacter)) { return false; } if (!IsViableTarget(targetTypes, otherCharacter)) { continue; }
if (Vector2.DistanceSquared(character.WorldPosition, otherCharacter.WorldPosition) < distance * distance) if (Vector2.DistanceSquared(character.WorldPosition, otherCharacter.WorldPosition) < distance * distance)
{ {
@@ -50,7 +50,7 @@ namespace Barotrauma.Abilities
else if (statusEffect.HasTargetType(StatusEffect.TargetType.NearbyCharacters)) else if (statusEffect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
{ {
targets.Clear(); targets.Clear();
targets.AddRange(statusEffect.GetNearbyTargets(targetCharacter.WorldPosition, targets)); statusEffect.AddNearbyTargets(targetCharacter.WorldPosition, targets);
if (!nearbyCharactersAppliesToSelf) if (!nearbyCharactersAppliesToSelf)
{ {
targets.RemoveAll(c => c == Character); targets.RemoveAll(c => c == Character);
@@ -27,6 +27,8 @@ namespace Barotrauma.Abilities
protected override void ApplyEffect() protected override void ApplyEffect()
{ {
if (Character?.Submarine is null) { return; }
foreach (Item item in Character.Submarine.GetItems(true)) foreach (Item item in Character.Submarine.GetItems(true))
{ {
if (item.HasTag(tags) || tags.Contains(item.Prefab.Identifier)) if (item.HasTag(tags) || tags.Contains(item.Prefab.Identifier))
@@ -0,0 +1,25 @@
#nullable enable
namespace Barotrauma.Abilities
{
internal sealed class CharacterAbilityGiveTalentPointsToAllies : CharacterAbility
{
private readonly int amount;
public CharacterAbilityGiveTalentPointsToAllies(CharacterAbilityGroup characterAbilityGroup, ContentXElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
amount = abilityElement.GetAttributeInt("amount", 0);
}
public override void InitializeAbility(bool addingFirstTime)
{
if (!addingFirstTime) { return; }
foreach (Character character in GameSession.GetSessionCrewCharacters(CharacterType.Both))
{
if (character.Info is null) { return; }
character.Info.AdditionalTalentPoints += amount;
}
}
}
}
@@ -1,5 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Xml.Linq;
namespace Barotrauma.Abilities namespace Barotrauma.Abilities
{ {
@@ -13,6 +13,11 @@ namespace Barotrauma.Abilities
value = abilityElement.GetAttributeInt("value", 0); value = abilityElement.GetAttributeInt("value", 0);
} }
public override void InitializeAbility(bool addingFirstTime)
{
ApplyEffect();
}
protected override void ApplyEffect() protected override void ApplyEffect()
{ {
if (identifier == Identifier.Empty) { return; } if (identifier == Identifier.Empty) { return; }
@@ -12,6 +12,8 @@ namespace Barotrauma.Abilities
public override void InitializeAbility(bool addingFirstTime) public override void InitializeAbility(bool addingFirstTime)
{ {
if (!addingFirstTime) { return; }
JobPrefab? apprentice = CharacterAbilityApplyStatusEffectsToApprenticeship.GetApprenticeJob(Character, JobPrefab.Prefabs.ToImmutableHashSet()); JobPrefab? apprentice = CharacterAbilityApplyStatusEffectsToApprenticeship.GetApprenticeJob(Character, JobPrefab.Prefabs.ToImmutableHashSet());
if (apprentice is null) if (apprentice is null)
{ {
@@ -27,39 +27,40 @@ namespace Barotrauma.Abilities
TimeSinceLastUpdate += deltaTime; TimeSinceLastUpdate += deltaTime;
if (TimeSinceLastUpdate < interval) { return; } if (TimeSinceLastUpdate < interval) { return; }
bool shouldApplyDelayedEffect; bool conditionsMatched;
bool conditionsDidntMatch;
if (AllConditionsMatched()) if (AllConditionsMatched())
{ {
effectDelayTimer += TimeSinceLastUpdate; effectDelayTimer += TimeSinceLastUpdate;
shouldApplyDelayedEffect = effectDelayTimer >= effectDelay; bool shouldApplyDelayedEffect = effectDelayTimer >= effectDelay;
conditionsDidntMatch = false; conditionsMatched = shouldApplyDelayedEffect;
} }
else else
{ {
effectDelayTimer = 0f; effectDelayTimer = 0f;
shouldApplyDelayedEffect = false; conditionsMatched = false;
conditionsDidntMatch = true;
} }
bool hasFallbacks = fallbackAbilities.Count > 0; bool hasFallbacks = fallbackAbilities.Count > 0;
List<CharacterAbility> abilitiesToRun = List<CharacterAbility> abilitiesToRun =
conditionsDidntMatch && hasFallbacks !conditionsMatched && hasFallbacks
? fallbackAbilities ? fallbackAbilities
: characterAbilities; : characterAbilities;
if (hasFallbacks)
{
conditionsMatched = true;
}
foreach (var characterAbility in abilitiesToRun) foreach (var characterAbility in abilitiesToRun)
{ {
if (!characterAbility.IsViable()) { continue; } if (!characterAbility.IsViable()) { continue; }
characterAbility.UpdateCharacterAbility( characterAbility.UpdateCharacterAbility(conditionsMatched, TimeSinceLastUpdate);
shouldApplyDelayedEffect || conditionsDidntMatch,
TimeSinceLastUpdate);
} }
if (shouldApplyDelayedEffect || (conditionsDidntMatch && hasFallbacks)) if (conditionsMatched)
{ {
timesTriggered++; timesTriggered++;
} }
@@ -7,7 +7,7 @@ namespace Barotrauma
{ {
internal sealed class TalentTree : Prefab internal sealed class TalentTree : Prefab
{ {
public enum TalentTreeStageState public enum TalentStages
{ {
Invalid, Invalid,
Locked, Locked,
@@ -72,30 +72,30 @@ namespace Barotrauma
// i hate this function - markus // i hate this function - markus
// me too - joonas // me too - joonas
public static TalentTreeStageState GetTalentOptionStageState(Character character, Identifier subTreeIdentifier, int index, IReadOnlyCollection<Identifier> selectedTalents) public static TalentStages GetTalentOptionStageState(Character character, Identifier subTreeIdentifier, int index, IReadOnlyCollection<Identifier> selectedTalents)
{ {
if (character?.Info?.Job.Prefab is null) { return TalentTreeStageState.Invalid; } if (character?.Info?.Job.Prefab is null) { return TalentStages.Invalid; }
if (!JobTalentTrees.TryGet(character.Info.Job.Prefab.Identifier, out TalentTree talentTree)) { return TalentTreeStageState.Invalid; } if (!JobTalentTrees.TryGet(character.Info.Job.Prefab.Identifier, out TalentTree talentTree)) { return TalentStages.Invalid; }
TalentSubTree subTree = talentTree!.TalentSubTrees.FirstOrDefault(tst => tst.Identifier == subTreeIdentifier); TalentSubTree subTree = talentTree!.TalentSubTrees.FirstOrDefault(tst => tst.Identifier == subTreeIdentifier);
if (subTree is null) { return TalentTreeStageState.Invalid; } if (subTree is null) { return TalentStages.Invalid; }
if (!TalentTreeMeetsRequirements(talentTree, subTree, selectedTalents)) if (!TalentTreeMeetsRequirements(talentTree, subTree, selectedTalents))
{ {
return TalentTreeStageState.Locked; return TalentStages.Locked;
} }
TalentOption targetTalentOption = subTree.TalentOptionStages[index]; TalentOption targetTalentOption = subTree.TalentOptionStages[index];
if (targetTalentOption.HasEnoughTalents(character.Info)) if (targetTalentOption.HasEnoughTalents(character.Info))
{ {
return TalentTreeStageState.Unlocked; return TalentStages.Unlocked;
} }
if (targetTalentOption.HasSelectedTalent(selectedTalents)) if (targetTalentOption.HasSelectedTalent(selectedTalents))
{ {
return TalentTreeStageState.Highlighted; return TalentStages.Highlighted;
} }
bool hasTalentInLastTier = true; bool hasTalentInLastTier = true;
@@ -111,17 +111,17 @@ namespace Barotrauma
if (!hasTalentInLastTier) if (!hasTalentInLastTier)
{ {
return TalentTreeStageState.Locked; return TalentStages.Locked;
} }
bool hasPointsForNewTalent = character.Info.GetTotalTalentPoints() - selectedTalents.Count > 0; bool hasPointsForNewTalent = character.Info.GetTotalTalentPoints() - selectedTalents.Count > 0;
if (hasPointsForNewTalent) if (hasPointsForNewTalent)
{ {
return isLastTalentPurchased ? TalentTreeStageState.Highlighted : TalentTreeStageState.Available; return isLastTalentPurchased ? TalentStages.Highlighted : TalentStages.Available;
} }
return TalentTreeStageState.Locked; return TalentStages.Locked;
} }
@@ -207,7 +207,7 @@ namespace Barotrauma
{ {
nameIdentifier = $"talenttree.{Identifier}"; nameIdentifier = $"talenttree.{Identifier}";
} }
DisplayName = TextManager.Get($"talenttree.{nameIdentifier}").Fallback(Identifier.Value); DisplayName = TextManager.Get(nameIdentifier).Fallback(Identifier.Value);
Type = subTreeElement.GetAttributeEnum("type", TalentTreeType.Specialization); Type = subTreeElement.GetAttributeEnum("type", TalentTreeType.Specialization);
RequiredTrees = subTreeElement.GetAttributeIdentifierImmutableHashSet("requires", ImmutableHashSet<Identifier>.Empty); RequiredTrees = subTreeElement.GetAttributeIdentifierImmutableHashSet("requires", ImmutableHashSet<Identifier>.Empty);
BlockedTrees = subTreeElement.GetAttributeIdentifierImmutableHashSet("blocks", ImmutableHashSet<Identifier>.Empty); BlockedTrees = subTreeElement.GetAttributeIdentifierImmutableHashSet("blocks", ImmutableHashSet<Identifier>.Empty);
@@ -234,7 +234,7 @@ namespace Barotrauma
/// When specified the talent option will show talent with this identifier /// When specified the talent option will show talent with this identifier
/// and clicking on it will expand the talent option to show the talents /// and clicking on it will expand the talent option to show the talents
/// </summary> /// </summary>
public readonly Option<Identifier> ShowcaseTalent; public readonly Dictionary<Identifier, ImmutableHashSet<Identifier>> ShowCaseTalents = new Dictionary<Identifier, ImmutableHashSet<Identifier>>();
public bool HasEnoughTalents(CharacterInfo character) => CountMatchingTalents(character.UnlockedTalents) >= MaxChosenTalents; public bool HasEnoughTalents(CharacterInfo character) => CountMatchingTalents(character.UnlockedTalents) >= MaxChosenTalents;
public bool HasEnoughTalents(IReadOnlyCollection<Identifier> selectedTalents) => CountMatchingTalents(selectedTalents) >= MaxChosenTalents; public bool HasEnoughTalents(IReadOnlyCollection<Identifier> selectedTalents) => CountMatchingTalents(selectedTalents) >= MaxChosenTalents;
@@ -269,19 +269,30 @@ namespace Barotrauma
{ {
MaxChosenTalents = talentOptionsElement.GetAttributeInt("maxchosentalents", 1); MaxChosenTalents = talentOptionsElement.GetAttributeInt("maxchosentalents", 1);
Identifier showcaseTalent = talentOptionsElement.GetAttributeIdentifier("showcasetalent", Identifier.Empty); HashSet<Identifier> identifiers = new HashSet<Identifier>();
ShowcaseTalent = !showcaseTalent.IsEmpty
? Option<Identifier>.Some(showcaseTalent)
: Option<Identifier>.None();
var talentIdentifiers = new HashSet<Identifier>(); foreach (ContentXElement talentOptionElement in talentOptionsElement.Elements())
foreach (var talentOptionElement in talentOptionsElement.GetChildElements("talentoption"))
{ {
Identifier identifier = talentOptionElement.GetAttributeIdentifier("identifier", Identifier.Empty); Identifier elementName = talentOptionElement.Name.ToIdentifier();
talentIdentifiers.Add(identifier); if (elementName == "talentoption")
{
identifiers.Add(talentOptionElement.GetAttributeIdentifier("identifier", Identifier.Empty));
}
else if (elementName == "showcasetalent")
{
Identifier showCaseIdentifier = talentOptionElement.GetAttributeIdentifier("identifier", Identifier.Empty);
HashSet<Identifier> showCaseTalentIdentifiers = new HashSet<Identifier>();
foreach (ContentXElement subElement in talentOptionElement.Elements())
{
Identifier identifier = subElement.GetAttributeIdentifier("identifier", Identifier.Empty);
showCaseTalentIdentifiers.Add(identifier);
identifiers.Add(identifier);
}
ShowCaseTalents.Add(showCaseIdentifier, showCaseTalentIdentifiers.ToImmutableHashSet());
}
} }
this.talentIdentifiers = talentIdentifiers.ToImmutableHashSet(); talentIdentifiers = identifiers.ToImmutableHashSet();
} }
} }
} }

Some files were not shown because too many files have changed in this diff Show More