Faction Test 100.4.0.0

This commit is contained in:
Markus Isberg
2022-11-14 18:28:28 +02:00
parent 87426b68b2
commit c772b61fc1
412 changed files with 16984 additions and 5530 deletions
@@ -36,8 +36,8 @@ namespace Barotrauma
private float minZoom = 0.1f; private float minZoom = 0.1f;
public float MinZoom public float MinZoom
{ {
get { return minZoom;} get { return minZoom; }
set { minZoom = MathHelper.Clamp(value, 0.001f, 10.0f); } set { minZoom = MathHelper.Clamp(value, 0.001f, 10.0f); }
} }
private float maxZoom = 2.0f; private float maxZoom = 2.0f;
@@ -63,7 +63,7 @@ namespace Barotrauma
private float prevZoom; private float prevZoom;
public float Shake; public float Shake;
private Vector2 shakePosition; public Vector2 ShakePosition { get; private set; }
private float shakeTimer; private float shakeTimer;
private float globalZoomScale = 1.0f; private float globalZoomScale = 1.0f;
@@ -371,7 +371,7 @@ namespace Barotrauma
if (Shake < 0.01f) if (Shake < 0.01f)
{ {
shakePosition = Vector2.Zero; ShakePosition = Vector2.Zero;
shakeTimer = 0.0f; shakeTimer = 0.0f;
} }
else else
@@ -379,11 +379,11 @@ namespace Barotrauma
shakeTimer += deltaTime * 5.0f; shakeTimer += deltaTime * 5.0f;
Vector2 noisePos = new Vector2((float)PerlinNoise.CalculatePerlin(shakeTimer, shakeTimer, 0) - 0.5f, (float)PerlinNoise.CalculatePerlin(shakeTimer, shakeTimer, 0.5f) - 0.5f); Vector2 noisePos = new Vector2((float)PerlinNoise.CalculatePerlin(shakeTimer, shakeTimer, 0) - 0.5f, (float)PerlinNoise.CalculatePerlin(shakeTimer, shakeTimer, 0.5f) - 0.5f);
shakePosition = noisePos * Shake * 2.0f; ShakePosition = noisePos * Shake * 2.0f;
Shake = MathHelper.Lerp(Shake, 0.0f, deltaTime * 2.0f); Shake = MathHelper.Lerp(Shake, 0.0f, deltaTime * 2.0f);
} }
Translate(moveCam + shakePosition); Translate(moveCam + ShakePosition);
Freeze = false; Freeze = false;
} }
@@ -6,6 +6,8 @@ namespace Barotrauma
{ {
class CameraTransition class CameraTransition
{ {
private static List<CameraTransition> activeTransitions = new List<CameraTransition>();
public bool Running public bool Running
{ {
get; get;
@@ -19,6 +21,7 @@ namespace Barotrauma
private readonly float? endZoom; private readonly float? endZoom;
public readonly float WaitDuration; public readonly float WaitDuration;
public float EndWaitDuration = 0.1f;
public readonly float PanDuration; public readonly float PanDuration;
public readonly bool FadeOut; public readonly bool FadeOut;
public readonly bool LosFadeIn; public readonly bool LosFadeIn;
@@ -45,8 +48,19 @@ namespace Barotrauma
if (targetEntity == null) { return; } if (targetEntity == null) { return; }
Running = true; Running = true;
CoroutineManager.StopCoroutines("CameraTransition");
prevControlled = Character.Controlled;
activeTransitions.RemoveAll(a => !CoroutineManager.IsCoroutineRunning(a.updateCoroutine));
foreach (var activeTransition in activeTransitions)
{
if (activeTransition.prevControlled != null)
{
prevControlled ??= activeTransition.prevControlled;
}
activeTransition.Stop();
}
updateCoroutine = CoroutineManager.StartCoroutine(Update(targetEntity, cam), "CameraTransition"); updateCoroutine = CoroutineManager.StartCoroutine(Update(targetEntity, cam), "CameraTransition");
activeTransitions.Add(this);
} }
public void Stop() public void Stop()
@@ -66,7 +80,7 @@ namespace Barotrauma
{ {
if (targetEntity == null || (targetEntity is Entity e && e.Removed)) { yield return CoroutineStatus.Success; } if (targetEntity == null || (targetEntity is Entity e && e.Removed)) { yield return CoroutineStatus.Success; }
prevControlled = Character.Controlled; prevControlled ??= Character.Controlled;
if (RemoveControlFromCharacter) if (RemoveControlFromCharacter)
{ {
#if CLIENT #if CLIENT
@@ -80,6 +94,7 @@ namespace Barotrauma
float endZoom = this.endZoom ?? 0.5f; float endZoom = this.endZoom ?? 0.5f;
Vector2 initialCameraPos = cam.Position; Vector2 initialCameraPos = cam.Position;
Vector2? initialTargetPos = targetEntity?.WorldPosition; Vector2? initialTargetPos = targetEntity?.WorldPosition;
Vector2 endPos = cam.Position;
float timer = -WaitDuration; float timer = -WaitDuration;
@@ -137,13 +152,13 @@ namespace Barotrauma
{ {
startPos += targetEntity.WorldPosition - initialTargetPos.Value; startPos += targetEntity.WorldPosition - initialTargetPos.Value;
} }
Vector2 endPos = cameraEndPos.HasValue ? endPos = cameraEndPos.HasValue ?
new Vector2( new Vector2(
MathHelper.Lerp(minPos.X, maxPos.X, (cameraEndPos.Value.ToVector2().X + 1.0f) / 2.0f), MathHelper.Lerp(minPos.X, maxPos.X, (cameraEndPos.Value.ToVector2().X + 1.0f) / 2.0f),
MathHelper.Lerp(maxPos.Y, minPos.Y, (cameraEndPos.Value.ToVector2().Y + 1.0f) / 2.0f)) : MathHelper.Lerp(maxPos.Y, minPos.Y, (cameraEndPos.Value.ToVector2().Y + 1.0f) / 2.0f)) :
prevControlled?.WorldPosition ?? targetEntity.WorldPosition; prevControlled?.WorldPosition ?? targetEntity.WorldPosition;
Vector2 cameraPos = Vector2.SmoothStep(startPos, endPos, clampedTimer / PanDuration); Vector2 cameraPos = Vector2.SmoothStep(startPos, endPos, clampedTimer / PanDuration) + cam.ShakePosition;
cam.Translate(cameraPos - cam.Position); cam.Translate(cameraPos - cam.Position);
#if CLIENT #if CLIENT
@@ -164,9 +179,16 @@ namespace Barotrauma
yield return CoroutineStatus.Running; yield return CoroutineStatus.Running;
} }
Running = false; float endTimer = 0.0f;
while (endTimer <= EndWaitDuration)
{
cam.Translate(endPos - cam.Position);
cam.Zoom = endZoom;
endTimer += CoroutineManager.DeltaTime;
yield return CoroutineStatus.Running;
}
yield return new WaitForSeconds(0.1f); Running = false;
#if CLIENT #if CLIENT
GUI.ScreenOverlayColor = Color.TransparentBlack; GUI.ScreenOverlayColor = Color.TransparentBlack;
@@ -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);
@@ -99,12 +99,14 @@ namespace Barotrauma
float newAngularVelocity = Collider.AngularVelocity; float newAngularVelocity = Collider.AngularVelocity;
Collider.CorrectPosition(character.MemState, out newPosition, out newVelocity, out newRotation, out newAngularVelocity); Collider.CorrectPosition(character.MemState, out newPosition, out newVelocity, out newRotation, out newAngularVelocity);
newVelocity = newVelocity.ClampLength(100.0f); if (Collider.BodyType == BodyType.Dynamic)
if (!MathUtils.IsValid(newVelocity)) { newVelocity = Vector2.Zero; } {
overrideTargetMovement = newVelocity.LengthSquared() > 0.01f ? newVelocity : Vector2.Zero; newVelocity = newVelocity.ClampLength(100.0f);
if (!MathUtils.IsValid(newVelocity)) { newVelocity = Vector2.Zero; }
Collider.LinearVelocity = newVelocity; overrideTargetMovement = newVelocity.LengthSquared() > 0.01f ? newVelocity : Vector2.Zero;
Collider.AngularVelocity = newAngularVelocity; Collider.LinearVelocity = newVelocity;
Collider.AngularVelocity = newAngularVelocity;
}
float distSqrd = Vector2.DistanceSquared(newPosition, Collider.SimPosition); float distSqrd = Vector2.DistanceSquared(newPosition, Collider.SimPosition);
float errorTolerance = character.CanMove ? 0.01f : 0.2f; float errorTolerance = character.CanMove ? 0.01f : 0.2f;
@@ -442,8 +444,7 @@ namespace Barotrauma
{ {
foreach (Limb limb in Limbs) foreach (Limb limb in Limbs)
{ {
if (limb == null || limb.IsSevered || limb.ActiveSprite == null) { continue; } if (limb == null || limb.IsSevered || limb.ActiveSprite == null || !limb.DoesFlip) { continue; }
Vector2 spriteOrigin = limb.ActiveSprite.Origin; Vector2 spriteOrigin = limb.ActiveSprite.Origin;
spriteOrigin.X = limb.ActiveSprite.SourceRect.Width - spriteOrigin.X; spriteOrigin.X = limb.ActiveSprite.SourceRect.Width - spriteOrigin.X;
limb.ActiveSprite.Origin = spriteOrigin; limb.ActiveSprite.Origin = spriteOrigin;
@@ -468,7 +469,10 @@ namespace Barotrauma
{ {
var damageSound = character.GetSound(s => s.Type == CharacterSound.SoundType.Damage); var damageSound = character.GetSound(s => s.Type == CharacterSound.SoundType.Damage);
float range = damageSound != null ? damageSound.Range * 2 : ConvertUnits.ToDisplayUnits(character.AnimController.Collider.GetSize().Length() * 10); float range = damageSound != null ? damageSound.Range * 2 : ConvertUnits.ToDisplayUnits(character.AnimController.Collider.GetSize().Length() * 10);
SoundPlayer.PlayDamageSound(limbJoint.Params.BreakSound, 1.0f, limbJoint.LimbA.body.DrawPosition, range: range); if (!limbJoint.Params.BreakSound.IsNullOrEmpty() && !limbJoint.Params.BreakSound.Equals("none", StringComparison.OrdinalIgnoreCase))
{
SoundPlayer.PlayDamageSound(limbJoint.Params.BreakSound, 1.0f, limbJoint.LimbA.body.DrawPosition, range: range);
}
} }
} }
@@ -42,7 +42,7 @@ namespace Barotrauma
if (sound != null) if (sound != null)
{ {
SoundPlayer.PlaySound(sound.Sound, worldPosition, sound.Volume, sound.Range, ignoreMuffling: sound.IgnoreMuffling); SoundPlayer.PlaySound(sound.Sound, worldPosition, sound.Volume, sound.Range, ignoreMuffling: sound.IgnoreMuffling, freqMult: sound.GetRandomFrequencyMultiplier());
} }
} }
} }
@@ -109,6 +109,42 @@ namespace Barotrauma
set => grainStrength = Math.Max(0, value); set => grainStrength = Math.Max(0, value);
} }
/// <summary>
/// Can be used by status effects
/// </summary>
public float CollapseEffectStrength
{
get { return Level.Loaded?.Renderer?.CollapseEffectStrength ?? 0.0f; }
set
{
if (Level.Loaded?.Renderer == null) { return; }
if (Controlled == this)
{
float strength = MathHelper.Clamp(value, 0.0f, 1.0f);
Level.Loaded.Renderer.CollapseEffectStrength = strength;
Level.Loaded.Renderer.CollapseEffectOrigin = Submarine?.WorldPosition ?? WorldPosition;
Screen.Selected.Cam.Shake = Math.Max(MathF.Pow(strength, 3) * 100.0f, Screen.Selected.Cam.Shake);
Screen.Selected.Cam.Rotation = strength * (PerlinNoise.GetPerlin((float)Timing.TotalTime * 0.01f, (float)Timing.TotalTime * 0.05f) - 0.5f);
Level.Loaded.Renderer.ChromaticAberrationStrength = value * 50.0f;
}
}
}
/// <summary>
/// Can be used to set camera shake from status effects
/// </summary>
public float CameraShake
{
get { return Screen.Selected?.Cam?.Shake ?? 0.0f; }
set
{
if (!MathUtils.IsValid(value)) { return; }
if (Screen.Selected?.Cam != null)
{
Screen.Selected.Cam.Shake = value;
}
}
}
private readonly List<ParticleEmitter> bloodEmitters = new List<ParticleEmitter>(); private readonly List<ParticleEmitter> bloodEmitters = new List<ParticleEmitter>();
public IEnumerable<ParticleEmitter> BloodEmitters public IEnumerable<ParticleEmitter> BloodEmitters
{ {
@@ -907,7 +943,7 @@ namespace Barotrauma
{ {
name += " " + TextManager.Get("Disguised"); name += " " + TextManager.Get("Disguised");
} }
else if (Info.Title != null) else if (Info.Title != null && TeamID != CharacterTeamType.Team1)
{ {
name += '\n' + Info.Title; name += '\n' + Info.Title;
} }
@@ -965,14 +1001,31 @@ namespace Barotrauma
} }
if (IsDead) { return; } if (IsDead) { return; }
if (CharacterHealth.DisplayedVitality < MaxVitality * 0.98f && hudInfoVisible) 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 (Params.ShowHealthBar && 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));
Vector2 healthBarPos = new Vector2(pos.X - 50, -pos.Y); Vector2 healthBarPos = new Vector2(pos.X - 50, -pos.Y);
GUI.DrawProgressBar(spriteBatch, healthBarPos, new Vector2(100.0f, 15.0f), GUI.DrawProgressBar(spriteBatch, healthBarPos, new Vector2(100.0f, 15.0f),
CharacterHealth.DisplayedVitality / MaxVitality, CharacterHealth.DisplayedVitality / MaxVitality,
Color.Lerp(GUIStyle.Red, GUIStyle.Green, CharacterHealth.DisplayedVitality / MaxVitality) * 0.8f * hudInfoAlpha, Color.Lerp(GUIStyle.Red, GUIStyle.Green, CharacterHealth.DisplayedVitality / MaxVitality) * 0.8f * hudInfoAlpha,
new Color(0.5f, 0.57f, 0.6f, 1.0f) * hudInfoAlpha); new Color(0.5f, 0.57f, 0.6f, 1.0f) * hudInfoAlpha);
} }
@@ -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;
@@ -14,9 +13,8 @@ namespace Barotrauma
{ {
const float BossHealthBarDuration = 120.0f; const float BossHealthBarDuration = 120.0f;
class BossHealthBar abstract class BossProgressBar
{ {
public readonly Character Character;
public float FadeTimer; public float FadeTimer;
public readonly GUIComponent TopContainer; public readonly GUIComponent TopContainer;
@@ -25,9 +23,14 @@ namespace Barotrauma
public readonly GUIProgressBar TopHealthBar; public readonly GUIProgressBar TopHealthBar;
public readonly GUIProgressBar SideHealthBar; public readonly GUIProgressBar SideHealthBar;
public BossHealthBar(Character character) public abstract bool Completed { get; }
public abstract bool Interrupted { get; }
public abstract float State { get; }
public BossProgressBar(LocalizedString label)
{ {
Character = character;
FadeTimer = BossHealthBarDuration; FadeTimer = BossHealthBarDuration;
TopContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.18f, 0.03f), HUDFrame.RectTransform, Anchor.TopCenter) TopContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.18f, 0.03f), HUDFrame.RectTransform, Anchor.TopCenter)
@@ -35,7 +38,7 @@ namespace Barotrauma
MinSize = new Point(100, 50), MinSize = new Point(100, 50),
RelativeOffset = new Vector2(0.0f, 0.01f) RelativeOffset = new Vector2(0.0f, 0.01f)
}, isHorizontal: false, childAnchor: Anchor.TopCenter); }, isHorizontal: false, childAnchor: Anchor.TopCenter);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.4f), TopContainer.RectTransform), character.DisplayName, textAlignment: Alignment.Center, textColor: GUIStyle.Red); new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.4f), TopContainer.RectTransform), label, textAlignment: Alignment.Center, textColor: GUIStyle.Red);
TopHealthBar = new GUIProgressBar(new RectTransform(new Vector2(1.0f, 0.6f), TopContainer.RectTransform) TopHealthBar = new GUIProgressBar(new RectTransform(new Vector2(1.0f, 0.6f), TopContainer.RectTransform)
{ {
MinSize = new Point(100, HUDLayoutSettings.HealthBarArea.Size.Y) MinSize = new Point(100, HUDLayoutSettings.HealthBarArea.Size.Y)
@@ -48,7 +51,7 @@ namespace Barotrauma
{ {
MinSize = new Point(80, 60) MinSize = new Point(80, 60)
}, isHorizontal: false, childAnchor: Anchor.TopRight); }, isHorizontal: false, childAnchor: Anchor.TopRight);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.3f), SideContainer.RectTransform), character.DisplayName, textAlignment: Alignment.CenterRight, textColor: GUIStyle.Red); new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.3f), SideContainer.RectTransform), label, textAlignment: Alignment.CenterRight, textColor: GUIStyle.Red);
SideHealthBar = new GUIProgressBar(new RectTransform(new Vector2(1.0f, 0.7f), SideContainer.RectTransform), barSize: 0.0f, style: "CharacterHealthBar") SideHealthBar = new GUIProgressBar(new RectTransform(new Vector2(1.0f, 0.7f), SideContainer.RectTransform), barSize: 0.0f, style: "CharacterHealthBar")
{ {
Color = GUIStyle.Red Color = GUIStyle.Red
@@ -60,6 +63,50 @@ namespace Barotrauma
SideContainer.CanBeFocused = false; SideContainer.CanBeFocused = false;
SideContainer.Children.ForEach(c => c.CanBeFocused = false); SideContainer.Children.ForEach(c => c.CanBeFocused = false);
} }
public abstract bool IsDuplicate(BossProgressBar progressBar);
}
class BossHealthBar : BossProgressBar
{
public readonly Character Character;
public override float State => Character.Vitality / Character.MaxVitality;
public override bool Completed => Character.IsDead;
public override bool Interrupted => Character.Removed || !Character.Enabled;
public BossHealthBar(Character character) : base(character.DisplayName)
{
Character = character;
}
public override bool IsDuplicate(BossProgressBar progressBar)
{
return progressBar is BossHealthBar bossHealthBar && bossHealthBar.Character == Character;
}
}
class MissionProgressBar : BossProgressBar
{
public readonly Mission Mission;
public override float State => Mission.State / (float)Mission.Prefab.MaxProgressState;
public override bool Completed => Mission.State >= Mission.Prefab.MaxProgressState;
public override bool Interrupted => Mission.Failed;
public MissionProgressBar(Mission mission) : base(mission.Prefab.ProgressBarLabel)
{
Mission = mission;
}
public override bool IsDuplicate(BossProgressBar progressBar)
{
return progressBar is MissionProgressBar missionProgressBar && missionProgressBar.Mission == Mission;
}
} }
private static readonly Dictionary<ISpatialEntity, int> orderIndicatorCount = new Dictionary<ISpatialEntity, int>(); private static readonly Dictionary<ISpatialEntity, int> orderIndicatorCount = new Dictionary<ISpatialEntity, int>();
@@ -70,7 +117,7 @@ namespace Barotrauma
private static readonly List<Item> brokenItems = new List<Item>(); private static readonly List<Item> brokenItems = new List<Item>();
private static float brokenItemsCheckTimer; private static float brokenItemsCheckTimer;
private static readonly List<BossHealthBar> bossHealthBars = new List<BossHealthBar>(); private static readonly List<BossProgressBar> bossHealthBars = new List<BossProgressBar>();
private static readonly Dictionary<Identifier, LocalizedString> cachedHudTexts = new Dictionary<Identifier, LocalizedString>(); private static readonly Dictionary<Identifier, LocalizedString> cachedHudTexts = new Dictionary<Identifier, LocalizedString>();
@@ -114,7 +161,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;
} }
@@ -159,7 +206,7 @@ namespace Barotrauma
public static void Update(float deltaTime, Character character, Camera cam) public static void Update(float deltaTime, Character character, Camera cam)
{ {
UpdateBossHealthBars(deltaTime); UpdateBossProgressBars(deltaTime);
if (GUI.DisableHUD) if (GUI.DisableHUD)
{ {
@@ -307,7 +354,7 @@ namespace Barotrauma
{ {
if (!brokenItem.IsInteractable(character)) { continue; } if (!brokenItem.IsInteractable(character)) { continue; }
float alpha = GetDistanceBasedIconAlpha(brokenItem); float alpha = GetDistanceBasedIconAlpha(brokenItem);
if (alpha <= 0.0f) continue; if (alpha <= 0.0f) { continue; }
GUI.DrawIndicator(spriteBatch, brokenItem.DrawPosition, cam, 100.0f, GUIStyle.BrokenIcon.Value.Sprite, GUI.DrawIndicator(spriteBatch, brokenItem.DrawPosition, cam, 100.0f, GUIStyle.BrokenIcon.Value.Sprite,
Color.Lerp(GUIStyle.Red, GUIStyle.Orange * 0.5f, brokenItem.Condition / brokenItem.MaxCondition) * alpha); Color.Lerp(GUIStyle.Red, GUIStyle.Orange * 0.5f, brokenItem.Condition / brokenItem.MaxCondition) * alpha);
} }
@@ -548,7 +595,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)
@@ -602,7 +649,9 @@ namespace Barotrauma
GUI.DrawString(spriteBatch, textPos, focusName, nameColor, Color.Black * 0.7f, 2, GUIStyle.SubHeadingFont, ForceUpperCase.No); GUI.DrawString(spriteBatch, textPos, focusName, nameColor, Color.Black * 0.7f, 2, GUIStyle.SubHeadingFont, ForceUpperCase.No);
textPos.Y += GUIStyle.SubHeadingFont.MeasureString(focusName).Y; textPos.Y += GUIStyle.SubHeadingFont.MeasureString(focusName).Y;
if (character.FocusedCharacter.Info?.Title != null && !character.FocusedCharacter.Info.Title.IsNullOrEmpty()) if (character.FocusedCharacter.Info?.Title != null &&
!character.FocusedCharacter.Info.Title.IsNullOrEmpty() &&
character.FocusedCharacter.TeamID != CharacterTeamType.Team1)
{ {
GUI.DrawString(spriteBatch, textPos, character.FocusedCharacter.Info.Title, nameColor, Color.Black * 0.7f, 2, GUIStyle.SubHeadingFont, ForceUpperCase.No); GUI.DrawString(spriteBatch, textPos, character.FocusedCharacter.Info.Title, nameColor, Color.Black * 0.7f, 2, GUIStyle.SubHeadingFont, ForceUpperCase.No);
textPos.Y += GUIStyle.SubHeadingFont.MeasureString(character.FocusedCharacter.Info.Title.Value).Y; textPos.Y += GUIStyle.SubHeadingFont.MeasureString(character.FocusedCharacter.Info.Title.Value).Y;
@@ -640,20 +689,41 @@ namespace Barotrauma
} }
} }
public static void ShowBossHealthBar(Character character) public static void ShowBossHealthBar(Character character, float damage)
{ {
if (character == null || character.IsDead || character.Removed) { return; } if (character == null || character.IsDead || character.Removed) { return; }
AddBossProgressBar(new BossHealthBar(character), damage);
}
var existingBar = bossHealthBars.Find(b => b.Character == character); public static void ShowMissionProgressBar(Mission mission)
if (existingBar != null) {
if (mission == null || mission.Completed || mission.Failed) { return; }
AddBossProgressBar(new MissionProgressBar(mission));
}
private static void AddBossProgressBar(BossProgressBar progressBar, float damage = 0.0f)
{
var healthBarMode = GameMain.NetworkMember?.ServerSettings.ShowEnemyHealthBars ?? GameSettings.CurrentConfig.ShowEnemyHealthBars;
if (healthBarMode == EnemyHealthBarMode.HideAll)
{ {
existingBar.FadeTimer = BossHealthBarDuration;
return; return;
} }
var existingBar = bossHealthBars.Find(b => b.IsDuplicate(progressBar));
if (existingBar != null)
{
existingBar.FadeTimer = BossHealthBarDuration;
if (damage > 0)
{
// Show the most recent target at the top of the screen
bossHealthBars.Remove(existingBar);
bossHealthBars.Add(existingBar);
}
return;
}
if (bossHealthBars.Count > 5) if (bossHealthBars.Count > 5)
{ {
BossHealthBar oldestHealthBar = bossHealthBars.First(); BossProgressBar oldestHealthBar = bossHealthBars.First();
foreach (var bar in bossHealthBars) foreach (var bar in bossHealthBars)
{ {
if (bar.TopHealthBar.BarSize < oldestHealthBar.TopHealthBar.BarSize) if (bar.TopHealthBar.BarSize < oldestHealthBar.TopHealthBar.BarSize)
@@ -663,54 +733,65 @@ namespace Barotrauma
} }
oldestHealthBar.FadeTimer = Math.Min(oldestHealthBar.FadeTimer, 1.0f); oldestHealthBar.FadeTimer = Math.Min(oldestHealthBar.FadeTimer, 1.0f);
} }
bossHealthBars.Add(progressBar);
bossHealthBars.Add(new BossHealthBar(character));
} }
public static void UpdateBossHealthBars(float deltaTime) public static void UpdateBossProgressBars(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];
bool showTopBar = i == 0; bool showTopBar = i == bossHealthBars.Count - 1;
if (showTopBar != bossHealthBar.TopContainer.Visible) if (showTopBar && !bossHealthBar.TopContainer.Visible)
{ {
bossHealthContainer.Recalculate(); bossHealthBar.SideContainer.SetAsLastChild();
SetColor(bossHealthBar, bossHealthBar.SideContainer, 0);
} }
bossHealthBar.TopContainer.Visible = showTopBar; bossHealthBar.TopContainer.Visible = showTopBar;
bossHealthBar.SideContainer.Visible = !bossHealthBar.TopContainer.Visible; bossHealthBar.SideContainer.Visible = !bossHealthBar.TopContainer.Visible;
float health = bossHealthBar.Character.Vitality / bossHealthBar.Character.MaxVitality; bossHealthBar.TopHealthBar.BarSize = bossHealthBar.SideHealthBar.BarSize = bossHealthBar.State;
float alpha = Math.Min(bossHealthBar.FadeTimer, 1.0f); float alpha = Math.Min(bossHealthBar.FadeTimer, 1.0f);
foreach (var c in bossHealthBar.SideContainer.GetAllChildren().Concat(bossHealthBar.TopContainer.GetAllChildren()))
if (bossHealthBar.TopContainer.Visible)
{ {
c.Color = new Color(c.Color, (byte)(alpha * 255)); SetColor(bossHealthBar, bossHealthBar.TopContainer, alpha);
if (c is GUITextBlock textBlock) }
if (bossHealthBar.SideContainer.Visible)
{
SetColor(bossHealthBar, bossHealthBar.SideContainer, alpha);
}
static void SetColor(BossProgressBar bossHealthBar, GUIComponent container, float alpha)
{
foreach (var component in container.GetAllChildren())
{ {
textBlock.TextColor = new Color(bossHealthBar.Character.IsDead ? Color.Gray : textBlock.TextColor, (byte)(alpha * 255)); component.Color = new Color(component.Color, (byte)(alpha * 255));
if (component is GUITextBlock textBlock)
{
textBlock.TextColor = new Color(bossHealthBar.Completed ? Color.Gray : textBlock.TextColor, (byte)(alpha * 255));
}
} }
} }
bossHealthBar.TopHealthBar.BarSize = bossHealthBar.SideHealthBar.BarSize = health; if (bossHealthBar.Interrupted)
if (bossHealthBar.Character.Removed || !bossHealthBar.Character.Enabled)
{ {
bossHealthBar.FadeTimer = Math.Min(bossHealthBar.FadeTimer, 1.0f); bossHealthBar.FadeTimer = Math.Min(bossHealthBar.FadeTimer, 1.0f);
} }
else if (bossHealthBar.Character.IsDead) else if (bossHealthBar.Completed)
{ {
bossHealthBar.FadeTimer = Math.Min(bossHealthBar.FadeTimer, 5.0f); bossHealthBar.FadeTimer = Math.Min(bossHealthBar.FadeTimer, 5.0f);
} }
bossHealthBar.FadeTimer -= deltaTime; bossHealthBar.FadeTimer -= deltaTime;
} }
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
@@ -92,8 +92,8 @@ 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))
{ {
@@ -120,7 +136,7 @@ namespace Barotrauma
skills.Sort((s1, s2) => -s1.Level.CompareTo(s2.Level)); skills.Sort((s1, s2) => -s1.Level.CompareTo(s2.Level));
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), skillsArea.RectTransform), TextManager.AddPunctuation(':', TextManager.Get("skills"), string.Empty), font: font) { Padding = Vector4.Zero }; new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), skillsArea.RectTransform), TextManager.AddPunctuation(':', TextManager.Get("skills"), string.Empty), font: font) { Padding = Vector4.Zero };
foreach (Skill skill in skills) foreach (Skill skill in skills)
{ {
Color textColor = Color.White * (0.5f + skill.Level / 200.0f); Color textColor = Color.White * (0.5f + skill.Level / 200.0f);
@@ -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))
{ {
@@ -524,25 +540,33 @@ namespace Barotrauma
string ragdollFile = inc.ReadString(); string ragdollFile = inc.ReadString();
Identifier npcId = inc.ReadIdentifier(); Identifier npcId = inc.ReadIdentifier();
Identifier factionId = inc.ReadIdentifier();
float minReputationToHire = 0.0f;
if (factionId != default)
{
minReputationToHire = inc.ReadSingle();
}
uint jobIdentifier = inc.ReadUInt32(); uint jobIdentifier = inc.ReadUInt32();
int variant = inc.ReadByte(); int variant = inc.ReadByte();
JobPrefab jobPrefab = null; JobPrefab jobPrefab = null;
Dictionary<Identifier, float> skillLevels = new Dictionary<Identifier, float>(); Dictionary<Identifier, float> skillLevels = new Dictionary<Identifier, float>();
if (jobIdentifier > 0) if (jobIdentifier > 0)
{ {
jobPrefab = JobPrefab.Prefabs.Find(jp => jp.UintIdentifier == jobIdentifier); jobPrefab = JobPrefab.Prefabs.Find(jp => jp.UintIdentifier == jobIdentifier);
foreach (SkillPrefab skillPrefab in jobPrefab.Skills.OrderBy(s => s.Identifier)) foreach (SkillPrefab skillPrefab in jobPrefab.Skills.OrderBy(s => s.Identifier))
{ {
float skillLevel = inc.ReadSingle(); float skillLevel = inc.ReadSingle();
skillLevels.Add(skillPrefab.Identifier, skillLevel); skillLevels.Add(skillPrefab.Identifier, skillLevel);
} }
} }
// TODO: animations // TODO: animations
CharacterInfo ch = new CharacterInfo(speciesName, newName, originalName, jobPrefab, ragdollFile, variant, npcIdentifier: npcId) CharacterInfo ch = new CharacterInfo(speciesName, newName, originalName, jobPrefab, ragdollFile, variant, npcIdentifier: npcId)
{ {
ID = infoID ID = infoID,
MinReputationToHire = (factionId, minReputationToHire)
}; };
ch.RecreateHead(tagSet.ToImmutableHashSet(), hairIndex, beardIndex, moustacheIndex, faceAttachmentIndex); ch.RecreateHead(tagSet.ToImmutableHashSet(), hairIndex, beardIndex, moustacheIndex, faceAttachmentIndex);
ch.Head.SkinColor = skinColor; ch.Head.SkinColor = skinColor;
@@ -501,7 +501,7 @@ namespace Barotrauma
info?.ClearSavedStatValues(statType); info?.ClearSavedStatValues(statType);
for (int i = 0; i < savedStatValueCount; i++) for (int i = 0; i < savedStatValueCount; i++)
{ {
string statIdentifier = msg.ReadString(); Identifier statIdentifier = msg.ReadIdentifier();
float statValue = msg.ReadSingle(); float statValue = msg.ReadSingle();
bool removeOnDeath = msg.ReadBoolean(); bool removeOnDeath = msg.ReadBoolean();
info?.ChangeSavedStatValue(statType, statValue, statIdentifier, removeOnDeath, setValue: true); info?.ChangeSavedStatValue(statType, statValue, statIdentifier, removeOnDeath, setValue: true);
@@ -32,12 +32,6 @@ namespace Barotrauma
public static Sprite DamageOverlay => DamageOverlayPrefab.Prefabs.ActivePrefab.DamageOverlay; public static Sprite DamageOverlay => DamageOverlayPrefab.Prefabs.ActivePrefab.DamageOverlay;
private readonly static LocalizedString[] strengthTexts = new LocalizedString[]
{
TextManager.Get("AfflictionStrengthLow"),
TextManager.Get("AfflictionStrengthMedium"),
TextManager.Get("AfflictionStrengthHigh")
};
private Point screenResolution; private Point screenResolution;
@@ -89,11 +83,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 +337,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 +385,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 +621,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 +725,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 +753,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 +927,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())
@@ -960,7 +987,12 @@ 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 +1021,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 +1053,134 @@ 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 const 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);
} }
afflictionIconContainer.Visible = true;
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;
@@ -1126,6 +1198,7 @@ namespace Barotrauma
} }
else else
{ {
afflictionIconContainer.Visible = hiddenAfflictionIconContainer.Visible = false;
if (Vitality > 0.0f) if (Vitality > 0.0f)
{ {
float currHealth = healthWindowHealthBar.BarSize; float currHealth = healthWindowHealthBar.BarSize;
@@ -1150,18 +1223,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 +1247,7 @@ namespace Barotrauma
{ {
if (selectedLimb == null) if (selectedLimb == null)
{ {
afflictionIconContainer.Content.ClearChildren(); afflictionIconList.Content.ClearChildren();
return; return;
} }
@@ -1207,7 +1282,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 +1292,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 +1350,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 +1461,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 +1512,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
}; };
@@ -1449,8 +1527,7 @@ namespace Barotrauma
Point nameDims = new Point(afflictionName.Rect.Width, (int)(GUIStyle.LargeFont.Size * 1.5f)); Point nameDims = new Point(afflictionName.Rect.Width, (int)(GUIStyle.LargeFont.Size * 1.5f));
afflictionStrength.Text = strengthTexts[ afflictionStrength.Text = affliction.GetStrengthText();
MathHelper.Clamp((int)Math.Floor((affliction.Strength / affliction.Prefab.MaxStrength) * strengthTexts.Length), 0, strengthTexts.Length - 1)];
Vector2 strengthDims = GUIStyle.SubHeadingFont.MeasureString(afflictionStrength.Text); Vector2 strengthDims = GUIStyle.SubHeadingFont.MeasureString(afflictionStrength.Text);
@@ -1482,10 +1559,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 +1592,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;
@@ -1581,8 +1657,7 @@ namespace Barotrauma
var strengthText = labelContainer.GetChildByUserData("strength") as GUITextBlock; var strengthText = labelContainer.GetChildByUserData("strength") as GUITextBlock;
strengthText.Text = strengthTexts[ strengthText.Text = affliction.GetStrengthText();
MathHelper.Clamp((int)Math.Floor((affliction.Strength / affliction.Prefab.MaxStrength) * strengthTexts.Length), 0, strengthTexts.Length - 1)];
strengthText.TextColor = Color.Lerp(GUIStyle.Orange, GUIStyle.Red, strengthText.TextColor = Color.Lerp(GUIStyle.Orange, GUIStyle.Red,
affliction.Strength / affliction.Prefab.MaxStrength); affliction.Strength / affliction.Prefab.MaxStrength);
@@ -1836,14 +1911,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);
} }
@@ -600,7 +600,7 @@ namespace Barotrauma
{ {
if (damageModifier.DamageMultiplier > 0 && !string.IsNullOrWhiteSpace(damageModifier.DamageParticle)) if (damageModifier.DamageMultiplier > 0 && !string.IsNullOrWhiteSpace(damageModifier.DamageParticle))
{ {
overrideParticle = GameMain.ParticleManager?.FindPrefab(damageModifier.DamageParticle); overrideParticle = ParticleManager.FindPrefab(damageModifier.DamageParticle);
break; break;
} }
} }
@@ -647,7 +647,7 @@ namespace Barotrauma
dripParticleTimer += wetTimer * deltaTime * Mass * (wetTimer > 0.9f ? 50.0f : 5.0f); dripParticleTimer += wetTimer * deltaTime * Mass * (wetTimer > 0.9f ? 50.0f : 5.0f);
if (dripParticleTimer > 1.0f) if (dripParticleTimer > 1.0f)
{ {
float dropRadius = body.BodyShape == PhysicsBody.Shape.Rectangle ? Math.Min(body.width, body.height) : body.radius; float dropRadius = body.BodyShape == PhysicsBody.Shape.Rectangle ? Math.Min(body.Width, body.Height) : body.Radius;
GameMain.ParticleManager.CreateParticle( GameMain.ParticleManager.CreateParticle(
"waterdrop", "waterdrop",
WorldPosition + Rand.Vector(Rand.Range(0.0f, ConvertUnits.ToDisplayUnits(dropRadius))), WorldPosition + Rand.Vector(Rand.Range(0.0f, ConvertUnits.ToDisplayUnits(dropRadius))),
@@ -684,10 +684,10 @@ namespace Barotrauma
public void Draw(SpriteBatch spriteBatch, Camera cam, Color? overrideColor = null, bool disableDeformations = false) public void Draw(SpriteBatch spriteBatch, Camera cam, Color? overrideColor = null, bool disableDeformations = false)
{ {
float brightness = Math.Max(1.0f - burnOverLayStrength, 0.2f);
var spriteParams = Params.GetSprite(); var spriteParams = Params.GetSprite();
if (spriteParams == null) { return; } if (spriteParams == null) { return; }
float burn = spriteParams.IgnoreTint ? 0 : burnOverLayStrength;
float brightness = Math.Max(1.0f - burn, 0.2f);
Color clr = spriteParams.Color; Color clr = spriteParams.Color;
if (!spriteParams.IgnoreTint) if (!spriteParams.IgnoreTint)
{ {
@@ -1136,6 +1136,17 @@ namespace Barotrauma
}); });
AssignRelayToServer("debugdraw", false); AssignRelayToServer("debugdraw", false);
AssignOnExecute("debugdrawlocalization", (string[] args) =>
{
if (args.None() || !bool.TryParse(args[0], out bool state))
{
state = !TextManager.DebugDraw;
}
TextManager.DebugDraw = state;
NewMessage("Localization debug draw mode " + (TextManager.DebugDraw ? "enabled" : "disabled"), Color.White);
});
AssignRelayToServer("debugdraw", false);
AssignOnExecute("togglevoicechatfilters", (string[] args) => AssignOnExecute("togglevoicechatfilters", (string[] args) =>
{ {
if (args.None() || !bool.TryParse(args[0], out bool state)) if (args.None() || !bool.TryParse(args[0], out bool state))
@@ -1695,6 +1706,8 @@ namespace Barotrauma
config.Language = language; config.Language = language;
GameSettings.SetCurrentConfig(config); GameSettings.SetCurrentConfig(config);
} }
HashSet<string> missingTexts = new HashSet<string>();
//key = text tag, value = list of languages the tag is missing from //key = text tag, value = list of languages the tag is missing from
Dictionary<Identifier, HashSet<LanguageIdentifier>> missingTags = new Dictionary<Identifier, HashSet<LanguageIdentifier>>(); Dictionary<Identifier, HashSet<LanguageIdentifier>> missingTags = new Dictionary<Identifier, HashSet<LanguageIdentifier>>();
@@ -1755,20 +1768,38 @@ namespace Barotrauma
foreach (Type itemComponentType in typeof(ItemComponent).Assembly.GetTypes().Where(type => type.IsSubclassOf(typeof(ItemComponent)))) foreach (Type itemComponentType in typeof(ItemComponent).Assembly.GetTypes().Where(type => type.IsSubclassOf(typeof(ItemComponent))))
{ {
foreach (var property in itemComponentType.GetProperties()) checkSerializableEntityType(itemComponentType);
}
checkSerializableEntityType(typeof(Item));
checkSerializableEntityType(typeof(Hull));
checkSerializableEntityType(typeof(Structure));
void checkSerializableEntityType(Type t)
{
foreach (var property in t.GetProperties())
{ {
if (!property.IsDefined(typeof(InGameEditable), false)) { continue; } if (!property.IsDefined(typeof(Editable), false)) { continue; }
string propertyTag = $"{property.DeclaringType.Name}.{property.Name}"; string propertyTag = $"{property.DeclaringType.Name}.{property.Name}";
addIfMissingAll(language, if (addIfMissingAll(language,
propertyTag.ToIdentifier(), propertyTag.ToIdentifier(),
property.Name.ToIdentifier(), property.Name.ToIdentifier(),
$"sp.{propertyTag}.name".ToIdentifier()); $"sp.{property.Name}.name".ToIdentifier(),
$"sp.{propertyTag}.name".ToIdentifier()) && language == "English".ToLanguageIdentifier())
{
missingTexts.Add($"<sp.{propertyTag.ToLower()}.name>{property.Name.FormatCamelCaseWithSpaces()}</sp.{propertyTag.ToLower()}.name>");
}
addIfMissingAll(language, var description = (property.GetCustomAttributes(true).First(a => a is Serialize) as Serialize).Description;
if (addIfMissingAll(language,
$"sp.{propertyTag}.description".ToIdentifier(), $"sp.{propertyTag}.description".ToIdentifier(),
$"{property.Name.ToIdentifier()}.description".ToIdentifier()); $"sp.{property.Name}.description".ToIdentifier(),
$"{property.Name.ToIdentifier()}.description".ToIdentifier()) && language == "English".ToLanguageIdentifier())
{
missingTexts.Add($"<sp.{propertyTag.ToLower()}.description>{description}</sp.{propertyTag.ToLower()}.description>");
}
} }
} }
@@ -1792,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)
@@ -1889,6 +1931,23 @@ namespace Barotrauma
ToolBox.OpenFileWithShell(Path.GetFullPath(filePath)); ToolBox.OpenFileWithShell(Path.GetFullPath(filePath));
SwapLanguage(TextManager.DefaultLanguage); SwapLanguage(TextManager.DefaultLanguage);
if (missingTexts.Any())
{
ShowQuestionPrompt("Dump the property names and descriptions missing from English to a new xml file? Y/N",
(option) =>
{
if (option.ToLowerInvariant() == "y")
{
string path = "newtexts.txt";
Barotrauma.IO.Validation.SkipValidationInDebugBuilds = true;
File.WriteAllLines(path, missingTexts);
Barotrauma.IO.Validation.SkipValidationInDebugBuilds = false;
ToolBox.OpenFileWithShell(Path.GetFullPath(path));
SwapLanguage(TextManager.DefaultLanguage);
}
});
}
void addIfMissing(Identifier tag, LanguageIdentifier language) void addIfMissing(Identifier tag, LanguageIdentifier language)
{ {
if (!tags[language].Contains(tag)) if (!tags[language].Contains(tag))
@@ -1897,15 +1956,90 @@ namespace Barotrauma
missingTags[tag].Add(language); missingTags[tag].Add(language);
} }
} }
void addIfMissingAll(LanguageIdentifier language, params Identifier[] potentialTags) bool addIfMissingAll(LanguageIdentifier language, params Identifier[] potentialTags)
{ {
if (!potentialTags.Any(t => tags[language].Contains(t))) if (!potentialTags.Any(t => tags[language].Contains(t)))
{ {
var tag = potentialTags.First(); var tag = potentialTags.First();
if (!missingTags.ContainsKey(tag)) { missingTags[tag] = new HashSet<LanguageIdentifier>(); } if (!missingTags.ContainsKey(tag)) { missingTags[tag] = new HashSet<LanguageIdentifier>(); }
missingTags[tag].Add(language); missingTags[tag].Add(language);
return true;
}
return false;
}
}));
commands.Add(new Command("checkduplicateloca", "", (string[] args) =>
{
if (args.Length < 1)
{
ThrowError("Please specify a file path.");
return;
}
XDocument doc1 = XMLExtensions.TryLoadXml(args[0]);
if (doc1?.Root == null)
{
ThrowError($"Could not load the file \"{args[0]}\"");
return;
}
List<(string tag, string text)> texts = new List<(string tag, string text)>();
bool duplicatesFound = false;
foreach (XElement element in doc1.Root.Elements())
{
string tag = element.Name.ToString();
string text = element.ElementInnerText();
if (texts.Any(t => t.tag == tag))
{
ThrowError($"Duplicate tag \"{tag}\".");
duplicatesFound = true;
} }
} }
if (duplicatesFound)
{
ThrowError($"Aborting, please fix duplicate tags in the file and try again.");
return;
}
foreach (XElement element in doc1.Root.Elements())
{
string tag = element.Name.ToString();
string text = element.ElementInnerText();
if (texts.Any(t => t.text == text))
{
if (tag.StartsWith("sp."))
{
string[] split = tag.Split('.');
if (split.Length > 3)
{
texts.RemoveAll(t => t.text == text);
string newTag = $"sp.{split[2]}.{split[3]}";
texts.Add((newTag, text));
NewMessage($"Duplicate text \"{tag}\", merging to \"{newTag}\".");
}
else
{
NewMessage($"Duplicate text \"{tag}\", using existing one \"{texts.Find(t => t.text == text).tag}\".");
}
}
else
{
texts.Add((tag, text));
ThrowError($"Duplicate text \"{tag}\". Could not determine if the text can be merged with an existing one, please check it manually.");
}
}
else
{
texts.Add((tag, text));
}
}
string filePath = "uniquetexts.xml";
Barotrauma.IO.Validation.SkipValidationInDebugBuilds = true;
File.WriteAllLines(filePath, texts.Select(t => $"<{t.tag}>{t.text}</{t.tag}>"));
Barotrauma.IO.Validation.SkipValidationInDebugBuilds = false;
ToolBox.OpenFileWithShell(Path.GetFullPath(filePath));
})); }));
commands.Add(new Command("comparelocafiles", "comparelocafiles [file1] [file2]", (string[] args) => commands.Add(new Command("comparelocafiles", "comparelocafiles [file1] [file2]", (string[] args) =>
@@ -2585,99 +2719,6 @@ namespace Barotrauma
})); }));
#endif #endif
commands.Add(new Command("cleanbuild", "", (string[] args) =>
{
/*GameSettings.CurrentConfig.MusicVolume = 0.5f;
GameSettings.CurrentConfig.SoundVolume = 0.5f;
GameSettings.CurrentConfig.DynamicRangeCompressionEnabled = true;
GameSettings.CurrentConfig.VoipAttenuationEnabled = true;
NewMessage("Music and sound volume set to 0.5", Color.Green);
GameSettings.CurrentConfig.GraphicsWidth = 0;
GameSettings.CurrentConfig.GraphicsHeight = 0;
GameSettings.CurrentConfig.WindowMode = WindowMode.BorderlessWindowed;
NewMessage("Resolution set to 0 x 0 (screen resolution will be used)", Color.Green);
NewMessage("Fullscreen enabled", Color.Green);
GameSettings.CurrentConfig.VerboseLogging = false;
if (GameSettings.CurrentConfig.MasterServerUrl != "http://www.undertowgames.com/baromaster")
{
ThrowError("MasterServerUrl \"" + GameSettings.CurrentConfig.MasterServerUrl + "\"!");
}
GameSettings.SaveCurrentConfig();*/
throw new NotImplementedException();
#warning TODO: reimplement
var saveFiles = Barotrauma.IO.Directory.GetFiles(SaveUtil.SaveFolder);
foreach (string saveFile in saveFiles)
{
Barotrauma.IO.File.Delete(saveFile);
NewMessage("Deleted " + saveFile, Color.Green);
}
if (Barotrauma.IO.Directory.Exists(Barotrauma.IO.Path.Combine(SaveUtil.SaveFolder, "temp")))
{
Barotrauma.IO.Directory.Delete(Barotrauma.IO.Path.Combine(SaveUtil.SaveFolder, "temp"), true);
NewMessage("Deleted temp save folder", Color.Green);
}
if (Barotrauma.IO.Directory.Exists(ServerLog.SavePath))
{
var logFiles = Barotrauma.IO.Directory.GetFiles(ServerLog.SavePath);
foreach (string logFile in logFiles)
{
Barotrauma.IO.File.Delete(logFile);
NewMessage("Deleted " + logFile, Color.Green);
}
}
if (Barotrauma.IO.File.Exists("filelist.xml"))
{
Barotrauma.IO.File.Delete("filelist.xml");
NewMessage("Deleted filelist", Color.Green);
}
if (Barotrauma.IO.File.Exists("Data/bannedplayers.txt"))
{
Barotrauma.IO.File.Delete("Data/bannedplayers.txt");
NewMessage("Deleted bannedplayers.txt", Color.Green);
}
if (Barotrauma.IO.File.Exists("Submarines/TutorialSub.sub"))
{
Barotrauma.IO.File.Delete("Submarines/TutorialSub.sub");
NewMessage("Deleted TutorialSub from the submarine folder", Color.Green);
}
/*if (Barotrauma.IO.File.Exists(GameServer.SettingsFile))
{
Barotrauma.IO.File.Delete(GameServer.SettingsFile);
NewMessage("Deleted server settings", Color.Green);
}
if (Barotrauma.IO.File.Exists(GameServer.ClientPermissionsFile))
{
Barotrauma.IO.File.Delete(GameServer.ClientPermissionsFile);
NewMessage("Deleted client permission file", Color.Green);
}*/
if (Barotrauma.IO.File.Exists("crashreport.log"))
{
Barotrauma.IO.File.Delete("crashreport.log");
NewMessage("Deleted crashreport.log", Color.Green);
}
if (!Barotrauma.IO.File.Exists("Content/Map/TutorialSub.sub"))
{
ThrowError("TutorialSub.sub not found!");
}
}));
commands.Add(new Command("reloadcorepackage", "", (string[] args) => commands.Add(new Command("reloadcorepackage", "", (string[] args) =>
{ {
if (args.Length < 1) if (args.Length < 1)
@@ -2925,7 +2966,7 @@ namespace Barotrauma
ThrowError($"Could not find the location type \"{args[0]}\"."); ThrowError($"Could not find the location type \"{args[0]}\".");
return; return;
} }
GameMain.GameSession.Campaign.Map.CurrentLocation.ChangeType(locationType); GameMain.GameSession.Campaign.Map.CurrentLocation.ChangeType(GameMain.GameSession.Campaign, locationType);
}, },
() => () =>
{ {
@@ -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;
} }
} }
} }
@@ -651,6 +651,7 @@ namespace Barotrauma
break; break;
case NetworkEventType.MISSION: case NetworkEventType.MISSION:
Identifier missionIdentifier = msg.ReadIdentifier(); Identifier missionIdentifier = msg.ReadIdentifier();
int locationIndex = msg.ReadInt32();
string missionName = msg.ReadString(); string missionName = msg.ReadString();
MissionPrefab? prefab = MissionPrefab.Prefabs.Find(mp => mp.Identifier == missionIdentifier); MissionPrefab? prefab = MissionPrefab.Prefabs.Find(mp => mp.Identifier == missionIdentifier);
if (prefab != null) if (prefab != null)
@@ -660,6 +661,10 @@ namespace Barotrauma
{ {
IconColor = prefab.IconColor IconColor = prefab.IconColor
}; };
if (GameMain.GameSession?.Map is { } map && locationIndex > 0 && locationIndex < map.Locations.Count)
{
map.Discover(map.Locations[locationIndex], checkTalents: false);
}
} }
break; break;
case NetworkEventType.UNLOCKPATH: case NetworkEventType.UNLOCKPATH:
@@ -8,7 +8,7 @@ namespace Barotrauma
public override int State public override int State
{ {
get { return base.State; } get { return base.State; }
protected set set
{ {
if (state != value) if (state != value)
{ {
@@ -45,7 +45,10 @@ namespace Barotrauma
{ {
requireRescue.Add(character); requireRescue.Add(character);
#if CLIENT #if CLIENT
GameMain.GameSession.CrewManager.AddCharacterToCrewList(character); if (allowOrderingRescuees)
{
GameMain.GameSession.CrewManager.AddCharacterToCrewList(character);
}
#endif #endif
} }
ushort itemCount = msg.ReadUInt16(); ushort itemCount = msg.ReadUInt16();
@@ -0,0 +1,129 @@
using Barotrauma.Networking;
using System.Collections.Generic;
using System.Collections.Immutable;
namespace Barotrauma
{
partial class EndMission : Mission
{
public override bool DisplayAsCompleted => false;
public override bool DisplayAsFailed => false;
partial void OnStateChangedProjSpecific()
{
if (Phase == MissionPhase.NoItemsDestroyed)
{
CoroutineManager.Invoke(() =>
{
if (boss != null && !boss.Removed)
{
new CameraTransition(boss, GameMain.GameScreen.Cam, null, Alignment.Center, panDuration: 8, fadeOut: false, startZoom: 1.0f, endZoom: 0.3f * GUI.yScale)
{
EndWaitDuration = 3.0f
};
}
}, delay: 3.0f);
}
else if (Phase == MissionPhase.AllItemsDestroyed)
{
CoroutineManager.StartCoroutine(wakeUpCoroutine(), name: "EndMission.wakeUpCoroutine");
}
else if (Phase == MissionPhase.BossKilled)
{
CoroutineManager.Invoke(() =>
{
new CameraTransition(boss, GameMain.GameScreen.Cam, null, Alignment.Center, panDuration: 3, fadeOut: false, endZoom: 0.1f * GUI.yScale)
{
EndWaitDuration = float.PositiveInfinity
};
}, delay: 3.0f);
}
IEnumerable<CoroutineStatus> wakeUpCoroutine()
{
yield return new WaitForSeconds(wakeUpCinematicDelay);
if (boss != null && !boss.Removed)
{
new CameraTransition(boss, GameMain.GameScreen.Cam, null, Alignment.Center, panDuration: 5.0f, fadeOut: false, losFadeIn: false, startZoom: 1.0f, endZoom: 0.4f * GUI.yScale)
{
EndWaitDuration = cameraWaitDuration
};
}
yield return new WaitForSeconds(bossWakeUpDelay);
if (boss != null && !boss.Removed)
{
foreach (var limb in boss.AnimController.Limbs)
{
if (!limb.FreezeBlinkState) { continue; }
limb.FreezeBlinkState = false;
if (limb.LightSource is Lights.LightSource light)
{
light.Enabled = true;
}
}
}
}
}
partial void UpdateProjSpecific()
{
if (Phase is MissionPhase.Initial or MissionPhase.NoItemsDestroyed or MissionPhase.SomeItemsDestroyed)
{
// Put asleep.
// Have to set the light every frame (or at least periodically), because light.Enabled is changed when Character.IsVisible changes (off/on screen). See GameScreen.Draw().
foreach (var limb in boss.AnimController.Limbs)
{
if (limb.Params.BlinkFrequency > 0)
{
limb.FreezeBlinkState = true;
limb.BlinkPhase = -limb.Params.BlinkHoldTime;
if (limb.LightSource is Lights.LightSource light)
{
light.Enabled = false;
}
}
}
}
#if DEBUG
if (PlayerInput.KeyHit(Microsoft.Xna.Framework.Input.Keys.O))
{
State = 0;
}
if (PlayerInput.KeyHit(Microsoft.Xna.Framework.Input.Keys.Y))
{
destructibleItems.ForEach(it => it.Condition = 0.0f);
}
if (PlayerInput.KeyHit(Microsoft.Xna.Framework.Input.Keys.U))
{
boss?.SetAllDamage(20000.0f, 0.0f, 0.0f);
}
#endif
}
public override void ClientReadInitial(IReadMessage msg)
{
base.ClientReadInitial(msg);
boss = Character.ReadSpawnData(msg);
byte minionCount = msg.ReadByte();
List<Character> minionList = new List<Character>();
for (int i = 0; i < minionCount; i++)
{
var minion = Character.ReadSpawnData(msg);
if (minion == null)
{
throw new System.Exception($"Error in EndMission.ClientReadInitial: failed to create a minion (mission: {Prefab.Identifier}, index: {i})");
}
minionList.Add(minion);
}
minions = minionList.ToImmutableArray();
if (minions.Length != minionCount)
{
throw new System.Exception("Error in EndMission.ClientReadInitial: minion count does not match the server count (" + minionCount + " != " + minions.Length + "mission: " + Prefab.Identifier + ")");
}
}
}
}
@@ -2,7 +2,7 @@
{ {
partial class GoToMission : Mission partial class GoToMission : Mission
{ {
public override bool DisplayAsCompleted => false; public override bool DisplayAsCompleted => State >= Prefab.MaxProgressState;
public override bool DisplayAsFailed => false; public override bool DisplayAsFailed => false;
} }
} }
@@ -43,33 +43,41 @@ namespace Barotrauma
List<LocalizedString> reputationRewardTexts = new List<LocalizedString>(); List<LocalizedString> reputationRewardTexts = new List<LocalizedString>();
foreach (var reputationReward in ReputationRewards) foreach (var reputationReward in ReputationRewards)
{ {
LocalizedString name = ""; FactionPrefab targetFaction;
if (reputationReward.Key == "location" )
if (reputationReward.Key == "location")
{ {
name = $"‖color:gui.orange‖{currLocation.Name}‖end‖"; targetFaction = currLocation.Faction?.Prefab;
} }
else else
{ {
var faction = FactionPrefab.Prefabs.Find(f => f.Identifier == reputationReward.Key); FactionPrefab.Prefabs.TryGet(reputationReward.Key, out targetFaction);
if (faction != null) }
{
name = $"‖color:{XMLExtensions.ColorToString(faction.IconColor)}‖{faction.Name}‖end‖"; LocalizedString name;
} if (targetFaction != null)
else {
{ name = $"‖color:{XMLExtensions.ToStringHex(targetFaction.IconColor)}‖{targetFaction.Name}‖end‖";
name = TextManager.Get(reputationReward.Key); }
} else
{
name = TextManager.Get(reputationReward.Key);
} }
float normalizedValue = MathUtils.InverseLerp(-100.0f, 100.0f, reputationReward.Value); float normalizedValue = MathUtils.InverseLerp(-100.0f, 100.0f, reputationReward.Value);
string formattedValue = ((int)reputationReward.Value).ToString("+#;-#;0"); //force plus sign for positive numbers string formattedValue = ((int)reputationReward.Value).ToString("+#;-#;0"); //force plus sign for positive numbers
LocalizedString rewardText = TextManager.GetWithVariables( LocalizedString rewardText = TextManager.GetWithVariables(
"reputationformat", "reputationformat",
("[reputationname]", name), ("[reputationname]", name),
("[reputationvalue]", $"‖color:{XMLExtensions.ColorToString(Reputation.GetReputationColor(normalizedValue))}‖{formattedValue}‖end‖" )); ("[reputationvalue]", $"‖color:{XMLExtensions.ToStringHex(Reputation.GetReputationColor(normalizedValue))}‖{formattedValue}‖end‖" ));
reputationRewardTexts.Add(rewardText.Value); reputationRewardTexts.Add(rewardText.Value);
} }
return RichString.Rich(TextManager.AddPunctuation(':', TextManager.Get("reputation"), LocalizedString.Join(", ", reputationRewardTexts))); if (reputationRewardTexts.Any())
{
return RichString.Rich(TextManager.AddPunctuation(':', TextManager.Get("reputation"), LocalizedString.Join(", ", reputationRewardTexts)));
}
else
{
return string.Empty;
}
} }
partial void ShowMessageProjSpecific(int missionState) partial void ShowMessageProjSpecific(int missionState)
@@ -8,6 +8,7 @@ namespace Barotrauma
{ {
foreach (Mission mission in missions) foreach (Mission mission in missions)
{ {
if (!mission.Prefab.ShowStartMessage) { continue; }
new GUIMessageBox(RichString.Rich(mission.Name), RichString.Rich(mission.Description), Array.Empty<LocalizedString>(), type: GUIMessageBox.Type.InGame, icon: mission.Prefab.Icon) new GUIMessageBox(RichString.Rich(mission.Name), RichString.Rich(mission.Description), Array.Empty<LocalizedString>(), type: GUIMessageBox.Type.InGame, icon: mission.Prefab.Icon)
{ {
IconColor = mission.Prefab.IconColor, IconColor = mission.Prefab.IconColor,
@@ -13,11 +13,12 @@ namespace Barotrauma
byte monsterCount = msg.ReadByte(); byte monsterCount = msg.ReadByte();
for (int i = 0; i < monsterCount; i++) for (int i = 0; i < monsterCount; i++)
{ {
monsters.Add(Character.ReadSpawnData(msg)); var monster = Character.ReadSpawnData(msg);
} if (monster == null)
if (monsters.Contains(null)) {
{ throw new System.Exception($"Error in MonsterMission.ClientReadInitial: failed to create a monster (mission: {Prefab.Identifier}, index: {i})");
throw new System.Exception("Error in MonsterMission.ClientReadInitial: monster list contains null (mission: " + Prefab.Identifier + ")"); }
monsters.Add(monster);
} }
if (monsters.Count != monsterCount) if (monsters.Count != monsterCount)
{ {
@@ -11,38 +11,59 @@ namespace Barotrauma
public override void ClientReadInitial(IReadMessage msg) public override void ClientReadInitial(IReadMessage msg)
{ {
base.ClientReadInitial(msg); base.ClientReadInitial(msg);
bool usedExistingItem = msg.ReadBoolean();
if (usedExistingItem) foreach (var target in targets)
{ {
ushort id = msg.ReadUInt16(); bool targetFound = msg.ReadBoolean();
item = Entity.FindEntityByID(id) as Item; if (!targetFound) { continue; }
if (item == null)
bool usedExistingItem = msg.ReadBoolean();
if (usedExistingItem)
{ {
throw new System.Exception("Error in SalvageMission.ClientReadInitial: failed to find item " + id + " (mission: " + Prefab.Identifier + ")"); ushort id = msg.ReadUInt16();
target.Item = Entity.FindEntityByID(id) as Item;
if (target.Item == null)
{
throw new System.Exception("Error in SalvageMission.ClientReadInitial: failed to find item " + id + " (mission: " + Prefab.Identifier + ")");
}
}
else
{
target.Item = Item.ReadSpawnData(msg);
if (target.Item == null)
{
throw new System.Exception("Error in SalvageMission.ClientReadInitial: spawned item was null (mission: " + Prefab.Identifier + ")");
}
}
int executedEffectCount = msg.ReadByte();
for (int i = 0; i < executedEffectCount; i++)
{
int listIndex = msg.ReadByte();
int effectIndex = msg.ReadByte();
var selectedEffect = target.StatusEffects[listIndex][effectIndex];
target.Item.ApplyStatusEffect(selectedEffect, selectedEffect.type, deltaTime: 1.0f, worldPosition: target.Item.Position);
}
if (target.Item.body != null)
{
target.Item.body.FarseerBody.BodyType = BodyType.Kinematic;
} }
} }
else }
public override void ClientRead(IReadMessage msg)
{
base.ClientRead(msg);
int targetCount = msg.ReadByte();
for (int i = 0; i < targetCount; i++)
{ {
item = Item.ReadSpawnData(msg); var state = (Target.RetrievalState)msg.ReadByte();
if (item == null) if (i < targets.Count)
{ {
throw new System.Exception("Error in SalvageMission.ClientReadInitial: spawned item was null (mission: " + Prefab.Identifier + ")"); targets[i].State = state;
} }
} }
int executedEffectCount = msg.ReadByte();
for (int i = 0; i < executedEffectCount; i++)
{
int index1 = msg.ReadByte();
int index2 = msg.ReadByte();
var selectedEffect = statusEffects[index1][index2];
item.ApplyStatusEffect(selectedEffect, selectedEffect.type, deltaTime: 1.0f, worldPosition: item.Position);
}
if (item.body != null)
{
item.body.FarseerBody.BodyType = BodyType.Kinematic;
}
} }
} }
} }
@@ -769,7 +769,7 @@ namespace Barotrauma
if (Character.Controlled != null && ChatMessage.CanUseRadio(Character.Controlled, out WifiComponent radio)) if (Character.Controlled != null && ChatMessage.CanUseRadio(Character.Controlled, out WifiComponent radio))
{ {
radio.Channel = channel; radio.Channel = channel;
GameMain.Client?.CreateEntityEvent(radio.Item, new Item.ChangePropertyEventData(radio.SerializableProperties["channel".ToIdentifier()])); GameMain.Client?.CreateEntityEvent(radio.Item, new Item.ChangePropertyEventData(radio.SerializableProperties["channel".ToIdentifier()], radio));
if (setText) if (setText)
{ {
@@ -3,7 +3,6 @@ using Barotrauma.Networking;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.Linq; using System.Linq;
using PlayerBalanceElement = Barotrauma.CampaignUI.PlayerBalanceElement; using PlayerBalanceElement = Barotrauma.CampaignUI.PlayerBalanceElement;
@@ -29,6 +28,8 @@ namespace Barotrauma
private Point resolutionWhenCreated; private Point resolutionWhenCreated;
private bool needsHireableRefresh;
private enum SortingMethod private enum SortingMethod
{ {
AlphabeticalAsc, AlphabeticalAsc,
@@ -50,6 +51,8 @@ namespace Barotrauma
campaignUI.Campaign.Map.OnLocationChanged.RegisterOverwriteExisting( campaignUI.Campaign.Map.OnLocationChanged.RegisterOverwriteExisting(
"CrewManagement.UpdateLocationView".ToIdentifier(), "CrewManagement.UpdateLocationView".ToIdentifier(),
(locationChangeInfo) => UpdateLocationView(locationChangeInfo.NewLocation, true, locationChangeInfo.PrevLocation)); (locationChangeInfo) => UpdateLocationView(locationChangeInfo.NewLocation, true, locationChangeInfo.PrevLocation));
Reputation.OnAnyReputationValueChanged.RegisterOverwriteExisting(
"CrewManagement.UpdateLocationView".ToIdentifier(), _ => needsHireableRefresh = true);
} }
public void RefreshPermissions() public void RefreshPermissions()
@@ -68,7 +71,13 @@ namespace Barotrauma
{ {
if (child.FindChild(c => c is GUIButton && c.UserData is CharacterInfo, true) is GUIButton buyButton) if (child.FindChild(c => c is GUIButton && c.UserData is CharacterInfo, true) is GUIButton buyButton)
{ {
buyButton.Enabled = HasPermission; CharacterInfo characterInfo = buyButton.UserData as CharacterInfo;
bool enoughReputationToHire = EnoughReputationToHire(characterInfo);
buyButton.Enabled = HasPermission && enoughReputationToHire;
foreach (GUITextBlock text in child.GetAllChildren<GUITextBlock>())
{
text.TextColor = new Color(text.TextColor, buyButton.Enabled ? 1.0f : 0.6f);
}
} }
} }
} }
@@ -294,18 +303,21 @@ namespace Barotrauma
if (sortingMethod == SortingMethod.AlphabeticalAsc) if (sortingMethod == SortingMethod.AlphabeticalAsc)
{ {
list.Content.RectTransform.SortChildren((x, y) => list.Content.RectTransform.SortChildren((x, y) =>
CompareReputationRequirement(x.GUIComponent, y.GUIComponent) ??
((InfoSkill)x.GUIComponent.UserData).CharacterInfo.Name.CompareTo(((InfoSkill)y.GUIComponent.UserData).CharacterInfo.Name)); ((InfoSkill)x.GUIComponent.UserData).CharacterInfo.Name.CompareTo(((InfoSkill)y.GUIComponent.UserData).CharacterInfo.Name));
} }
else if (sortingMethod == SortingMethod.JobAsc) else if (sortingMethod == SortingMethod.JobAsc)
{ {
SortCharacters(list, SortingMethod.AlphabeticalAsc); SortCharacters(list, SortingMethod.AlphabeticalAsc);
list.Content.RectTransform.SortChildren((x, y) => list.Content.RectTransform.SortChildren((x, y) =>
String.Compare(((InfoSkill)x.GUIComponent.UserData).CharacterInfo.Job.Name.Value, ((InfoSkill)y.GUIComponent.UserData).CharacterInfo.Job.Name.Value, StringComparison.Ordinal)); CompareReputationRequirement(x.GUIComponent, y.GUIComponent) ??
string.Compare(((InfoSkill)x.GUIComponent.UserData).CharacterInfo.Job.Name.Value, ((InfoSkill)y.GUIComponent.UserData).CharacterInfo.Job.Name.Value, StringComparison.Ordinal));
} }
else if (sortingMethod == SortingMethod.PriceAsc || sortingMethod == SortingMethod.PriceDesc) else if (sortingMethod == SortingMethod.PriceAsc || sortingMethod == SortingMethod.PriceDesc)
{ {
SortCharacters(list, SortingMethod.AlphabeticalAsc); SortCharacters(list, SortingMethod.AlphabeticalAsc);
list.Content.RectTransform.SortChildren((x, y) => list.Content.RectTransform.SortChildren((x, y) =>
CompareReputationRequirement(x.GUIComponent, y.GUIComponent) ??
((InfoSkill)x.GUIComponent.UserData).CharacterInfo.Salary.CompareTo(((InfoSkill)y.GUIComponent.UserData).CharacterInfo.Salary)); ((InfoSkill)x.GUIComponent.UserData).CharacterInfo.Salary.CompareTo(((InfoSkill)y.GUIComponent.UserData).CharacterInfo.Salary));
if (sortingMethod == SortingMethod.PriceDesc) { list.Content.RectTransform.ReverseChildren(); } if (sortingMethod == SortingMethod.PriceDesc) { list.Content.RectTransform.ReverseChildren(); }
} }
@@ -313,9 +325,26 @@ namespace Barotrauma
{ {
SortCharacters(list, SortingMethod.AlphabeticalAsc); SortCharacters(list, SortingMethod.AlphabeticalAsc);
list.Content.RectTransform.SortChildren((x, y) => list.Content.RectTransform.SortChildren((x, y) =>
CompareReputationRequirement(x.GUIComponent, y.GUIComponent) ??
((InfoSkill)x.GUIComponent.UserData).SkillLevel.CompareTo(((InfoSkill)y.GUIComponent.UserData).SkillLevel)); ((InfoSkill)x.GUIComponent.UserData).SkillLevel.CompareTo(((InfoSkill)y.GUIComponent.UserData).SkillLevel));
if (sortingMethod == SortingMethod.SkillDesc) { list.Content.RectTransform.ReverseChildren(); } if (sortingMethod == SortingMethod.SkillDesc) { list.Content.RectTransform.ReverseChildren(); }
} }
int? CompareReputationRequirement(GUIComponent c1, GUIComponent c2)
{
CharacterInfo info1 = ((InfoSkill)c1.UserData).CharacterInfo;
CharacterInfo info2 = ((InfoSkill)c2.UserData).CharacterInfo;
float requirement1 = EnoughReputationToHire(info1) ? 0 : info1.MinReputationToHire.reputation;
float requirement2 = EnoughReputationToHire(info2) ? 0 : info2.MinReputationToHire.reputation;
if (MathUtils.NearlyEqual(requirement1, 0.0f) && MathUtils.NearlyEqual(requirement2, 0.0f))
{
return null;
}
else
{
return requirement1.CompareTo(requirement2);
}
}
} }
private readonly struct InfoSkill private readonly struct InfoSkill
@@ -367,12 +396,25 @@ namespace Barotrauma
nameBlock.Text = ToolBox.LimitString(nameBlock.Text, nameBlock.Font, nameBlock.Rect.Width); nameBlock.Text = ToolBox.LimitString(nameBlock.Text, nameBlock.Font, nameBlock.Rect.Width);
GUITextBlock jobBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), nameAndJobGroup.RectTransform), GUITextBlock jobBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), nameAndJobGroup.RectTransform),
characterInfo.Job.Name, textColor: Color.White, font: GUIStyle.SmallFont, textAlignment: Alignment.TopLeft) characterInfo.Title ?? characterInfo.Job.Name, textColor: Color.White, font: GUIStyle.SmallFont, textAlignment: Alignment.TopLeft)
{ {
CanBeFocused = false CanBeFocused = false
}; };
jobBlock.Text = ToolBox.LimitString(jobBlock.Text, jobBlock.Font, jobBlock.Rect.Width); if (!characterInfo.MinReputationToHire.factionId.IsEmpty)
{
var faction = campaign.Factions.Find(f => f.Prefab.Identifier == characterInfo.MinReputationToHire.factionId);
if (faction != null)
{
jobBlock.TextColor = faction.Prefab.IconColor;
}
}
var fullJobText = jobBlock.Text;
jobBlock.Text = ToolBox.LimitString(fullJobText, jobBlock.Font, jobBlock.Rect.Width);
if (jobBlock.Text != fullJobText)
{
jobBlock.ToolTip = fullJobText;
jobBlock.CanBeFocused = true;
}
float width = 0.6f / 3; float width = 0.6f / 3;
if (characterInfo.Job != null && skill != null) if (characterInfo.Job != null && skill != null)
{ {
@@ -410,7 +452,7 @@ namespace Barotrauma
{ {
ClickSound = GUISoundType.Cart, ClickSound = GUISoundType.Cart,
UserData = characterInfo, UserData = characterInfo,
Enabled = HasPermission, Enabled = CanHire(characterInfo),
OnClicked = (b, o) => AddPendingHire(o as CharacterInfo) OnClicked = (b, o) => AddPendingHire(o as CharacterInfo)
}; };
hireButton.OnAddedToGUIUpdateList += (GUIComponent btn) => hireButton.OnAddedToGUIUpdateList += (GUIComponent btn) =>
@@ -426,10 +468,9 @@ namespace Barotrauma
else if (!btn.Enabled) else if (!btn.Enabled)
{ {
btn.ToolTip = string.Empty; btn.ToolTip = string.Empty;
btn.Enabled = HasPermission; btn.Enabled = CanHire(characterInfo);
} }
}; };
} }
else if (listBox == pendingList) else if (listBox == pendingList)
{ {
@@ -437,7 +478,7 @@ namespace Barotrauma
{ {
ClickSound = GUISoundType.Cart, ClickSound = GUISoundType.Cart,
UserData = characterInfo, UserData = characterInfo,
Enabled = HasPermission, Enabled = CanHire(characterInfo),
OnClicked = (b, o) => RemovePendingHire(o as CharacterInfo) OnClicked = (b, o) => RemovePendingHire(o as CharacterInfo)
}; };
} }
@@ -474,12 +515,30 @@ namespace Barotrauma
size = new Point(3 * mainGroup.AbsoluteSpacing + icon.Rect.Width + nameAndJobGroup.Rect.Width, mainGroup.Rect.Height); size = new Point(3 * mainGroup.AbsoluteSpacing + icon.Rect.Width + nameAndJobGroup.Rect.Width, mainGroup.Rect.Height);
new GUIButton(new RectTransform(size, frame.RectTransform) { RelativeOffset = new Vector2(0.025f) }, style: null) new GUIButton(new RectTransform(size, frame.RectTransform) { RelativeOffset = new Vector2(0.025f) }, style: null)
{ {
Enabled = HasPermission, Enabled = CanHire(characterInfo),
ToolTip = TextManager.GetWithVariable("campaigncrew.givenicknametooltip", "[mouseprimary]", TextManager.Get($"input.{(PlayerInput.MouseButtonsSwapped() ? "rightmouse" : "leftmouse")}")), ToolTip = TextManager.GetWithVariable("campaigncrew.givenicknametooltip", "[mouseprimary]", TextManager.Get($"input.{(PlayerInput.MouseButtonsSwapped() ? "rightmouse" : "leftmouse")}")),
UserData = characterInfo, UserData = characterInfo,
OnClicked = CreateRenamingComponent OnClicked = CreateRenamingComponent
}; };
} }
bool CanHire(CharacterInfo characterInfo)
{
if (!HasPermission) { return false; }
return EnoughReputationToHire(characterInfo);
}
}
private bool EnoughReputationToHire(CharacterInfo characterInfo)
{
if (characterInfo.MinReputationToHire.factionId != Identifier.Empty)
{
if (campaign.GetReputation(characterInfo.MinReputationToHire.factionId) < characterInfo.MinReputationToHire.reputation)
{
return false;
}
}
return true;
} }
private void CreateCharacterPreviewFrame(GUIListBox listBox, GUIFrame characterFrame, CharacterInfo characterInfo) private void CreateCharacterPreviewFrame(GUIListBox listBox, GUIFrame characterFrame, CharacterInfo characterInfo)
@@ -488,13 +547,13 @@ namespace Barotrauma
Point absoluteOffset = new Point( Point absoluteOffset = new Point(
pivot == Pivot.TopLeft ? listBox.Parent.Parent.Rect.Right + 5 : listBox.Parent.Parent.Rect.Left - 5, pivot == Pivot.TopLeft ? listBox.Parent.Parent.Rect.Right + 5 : listBox.Parent.Parent.Rect.Left - 5,
characterFrame.Rect.Top); characterFrame.Rect.Top);
int frameSize = (int)(GUI.Scale * 300); Point frameSize = new Point(GUI.IntScale(300), GUI.IntScale(350));
if (GameMain.GraphicsHeight - (absoluteOffset.Y + frameSize) < 0) if (GameMain.GraphicsHeight - (absoluteOffset.Y + frameSize.Y) < 0)
{ {
pivot = listBox == hireableList ? Pivot.BottomLeft : Pivot.BottomRight; pivot = listBox == hireableList ? Pivot.BottomLeft : Pivot.BottomRight;
absoluteOffset.Y = characterFrame.Rect.Bottom; absoluteOffset.Y = characterFrame.Rect.Bottom;
} }
characterPreviewFrame = new GUIFrame(new RectTransform(new Point(frameSize), parent: campaignUI.GetTabContainer(CampaignMode.InteractionType.Crew).Parent.RectTransform, pivot: pivot) characterPreviewFrame = new GUIFrame(new RectTransform(frameSize, parent: campaignUI.GetTabContainer(CampaignMode.InteractionType.Crew).Parent.RectTransform, pivot: pivot)
{ {
AbsoluteOffset = absoluteOffset AbsoluteOffset = absoluteOffset
}, style: "InnerFrame") }, style: "InnerFrame")
@@ -503,7 +562,8 @@ namespace Barotrauma
}; };
GUILayoutGroup mainGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.95f), characterPreviewFrame.RectTransform, anchor: Anchor.Center)) GUILayoutGroup mainGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.95f), characterPreviewFrame.RectTransform, anchor: Anchor.Center))
{ {
RelativeSpacing = 0.01f RelativeSpacing = 0.01f,
Stretch = true
}; };
// Character info // Character info
@@ -545,9 +605,23 @@ namespace Barotrauma
blockHeight = 1.0f / characterSkills.Count(); blockHeight = 1.0f / characterSkills.Count();
foreach (Skill skill in characterSkills) foreach (Skill skill in characterSkills)
{ {
new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), skillNameGroup.RectTransform), TextManager.Get("SkillName." + skill.Identifier)); new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), skillNameGroup.RectTransform), TextManager.Get("SkillName." + skill.Identifier), font: GUIStyle.SmallFont);
new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), skillLevelGroup.RectTransform), ((int)skill.Level).ToString(), textAlignment: Alignment.Right); new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), skillLevelGroup.RectTransform), ((int)skill.Level).ToString(), textAlignment: Alignment.Right);
} }
if (characterInfo.MinReputationToHire.reputation > 0.0f)
{
var repStr = TextManager.GetWithVariables(
"campaignstore.reputationrequired",
("[amount]", ((int)characterInfo.MinReputationToHire.reputation).ToString()),
("[faction]", TextManager.Get("faction." + characterInfo.MinReputationToHire.factionId).Value));
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), mainGroup.RectTransform),
repStr, textColor: !EnoughReputationToHire(characterInfo) ? GUIStyle.Orange : GUIStyle.Green,
font: GUIStyle.SmallFont, wrap: true, textAlignment: Alignment.Center);
}
mainGroup.Recalculate();
characterPreviewFrame.RectTransform.MinSize =
new Point(0, (int)(mainGroup.Children.Sum(c => c.Rect.Height + mainGroup.Rect.Height * mainGroup.RelativeSpacing) / mainGroup.RectTransform.RelativeSize.Y));
} }
private bool SelectCharacter(GUIListBox listBox, GUIFrame characterFrame, CharacterInfo characterInfo) private bool SelectCharacter(GUIListBox listBox, GUIFrame characterFrame, CharacterInfo characterInfo)
@@ -636,7 +710,7 @@ namespace Barotrauma
List<CharacterInfo> nonDuplicateHires = new List<CharacterInfo>(); List<CharacterInfo> nonDuplicateHires = new List<CharacterInfo>();
hires.ForEach(hireInfo => hires.ForEach(hireInfo =>
{ {
if(campaign.CrewManager.GetCharacterInfos().None(crewInfo => crewInfo.IsNewHire && crewInfo.GetIdentifierUsingOriginalName() == hireInfo.GetIdentifierUsingOriginalName())) if (campaign.CrewManager.GetCharacterInfos().None(crewInfo => crewInfo.IsNewHire && crewInfo.GetIdentifierUsingOriginalName() == hireInfo.GetIdentifierUsingOriginalName()))
{ {
nonDuplicateHires.Add(hireInfo); nonDuplicateHires.Add(hireInfo);
} }
@@ -791,6 +865,16 @@ namespace Barotrauma
playerBalanceElement = CampaignUI.UpdateBalanceElement(playerBalanceElement); playerBalanceElement = CampaignUI.UpdateBalanceElement(playerBalanceElement);
} }
if (needsHireableRefresh)
{
RefreshCrewFrames(hireableList);
if (sortingDropDown?.SelectedItemData != null)
{
SortCharacters(hireableList, (SortingMethod)sortingDropDown.SelectedItemData);
}
needsHireableRefresh = false;
}
(GUIComponent highlightedFrame, CharacterInfo highlightedInfo) = FindHighlightedCharacter(GUI.MouseOn); (GUIComponent highlightedFrame, CharacterInfo highlightedInfo) = FindHighlightedCharacter(GUI.MouseOn);
if (highlightedFrame != null && highlightedInfo != null) if (highlightedFrame != null && highlightedInfo != null)
{ {
@@ -357,6 +357,17 @@ namespace Barotrauma
string txt = directory; string txt = directory;
if (txt.StartsWith(currentDirectory)) { txt = txt.Substring(currentDirectory.Length); } if (txt.StartsWith(currentDirectory)) { txt = txt.Substring(currentDirectory.Length); }
if (!txt.EndsWith("/")) { txt += "/"; } if (!txt.EndsWith("/")) { txt += "/"; }
//get directory info
DirectoryInfo dirInfo = new DirectoryInfo(directory);
try
{
//this will throw an exception if the directory can't be opened
Directory.GetDirectories(directory);
}
catch (UnauthorizedAccessException)
{
continue;
}
var itemFrame = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), fileList.Content.RectTransform), txt) var itemFrame = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), fileList.Content.RectTransform), txt)
{ {
UserData = ItemIsDirectory.Yes UserData = ItemIsDirectory.Yes
@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using Barotrauma.IO; using Barotrauma.IO;
using System.Linq; using System.Linq;
using System.Xml.Linq;
using Barotrauma.CharacterEditor; using Barotrauma.CharacterEditor;
using Barotrauma.Extensions; using Barotrauma.Extensions;
using Barotrauma.Items.Components; using Barotrauma.Items.Components;
@@ -50,6 +49,14 @@ namespace Barotrauma
static class GUI static class GUI
{ {
// Controls where a line is drawn for given coords.
public enum OutlinePosition
{
Default = 0, // Thickness is inside of top left and outside of bottom right coord
Inside = 1, // Thickness is subtracted from the inside
Centered = 2, // Thickness is centered on given coords
Outside = 3, // Tickness is added to the outside
}
public static GUICanvas Canvas => GUICanvas.Instance; public static GUICanvas Canvas => GUICanvas.Instance;
public static CursorState MouseCursor = CursorState.Default; public static CursorState MouseCursor = CursorState.Default;
@@ -133,13 +140,20 @@ namespace Barotrauma
public static Texture2D WhiteTexture => solidWhiteTexture; public static Texture2D WhiteTexture => solidWhiteTexture;
private static GUICursor MouseCursorSprites => GUIStyle.CursorSprite; private static GUICursor MouseCursorSprites => GUIStyle.CursorSprite;
private static bool debugDrawSounds, debugDrawEvents, debugDrawMetadata; private static bool debugDrawSounds, debugDrawEvents;
private static int debugDrawMetadataOffset;
private static readonly string[] ignoredMetadataInfo = { string.Empty, string.Empty, string.Empty, string.Empty }; private static DebugDrawMetaData debugDrawMetaData;
public struct DebugDrawMetaData
{
public bool Enabled;
public bool FactionMetadata, UpgradeLevels, UpgradePrices;
public int Offset;
}
public static GraphicsDevice GraphicsDevice => GameMain.Instance.GraphicsDevice; public static GraphicsDevice GraphicsDevice => GameMain.Instance.GraphicsDevice;
private static List<GUIMessage> messages = new List<GUIMessage>(); private static readonly List<GUIMessage> messages = new List<GUIMessage>();
public static GUIFrame PauseMenu { get; private set; } public static GUIFrame PauseMenu { get; private set; }
public static GUIFrame SettingsMenuContainer { get; private set; } public static GUIFrame SettingsMenuContainer { get; private set; }
@@ -188,8 +202,9 @@ namespace Barotrauma
SettingsMenuOpen || SettingsMenuOpen ||
DebugConsole.IsOpen || DebugConsole.IsOpen ||
GameSession.IsTabMenuOpen || GameSession.IsTabMenuOpen ||
(GameMain.GameSession?.GameMode?.Paused ?? false) || GameMain.GameSession?.GameMode is { Paused: true } ||
CharacterHUD.IsCampaignInterfaceOpen; CharacterHUD.IsCampaignInterfaceOpen ||
GameMain.GameSession?.Campaign is { SlideshowPlayer: { Finished: false, Visible: true } };
} }
} }
@@ -248,7 +263,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)
{ {
@@ -516,18 +541,17 @@ namespace Barotrauma
if (GameMain.GameSession?.GameMode is CampaignMode campaignMode) if (GameMain.GameSession?.GameMode is CampaignMode campaignMode)
{ {
// TODO: TEST THIS // TODO: TEST THIS
if (debugDrawMetadata) if (debugDrawMetaData.Enabled)
{ {
string text = "Ctrl+M to hide campaign metadata debug info\n\n" + string text = "Ctrl+M to hide campaign metadata debug info\n\n" +
$"Ctrl+1 to {(string.IsNullOrWhiteSpace(ignoredMetadataInfo[0]) ? "hide" : "show")} outpost reputations, \n" + $"Ctrl+1 to {(debugDrawMetaData.FactionMetadata ? "hide" : "show")} faction reputations, \n" +
$"Ctrl+2 to {(string.IsNullOrWhiteSpace(ignoredMetadataInfo[1]) ? "hide" : "show")} faction reputations, \n" + $"Ctrl+2 to {(debugDrawMetaData.UpgradeLevels ? "hide" : "show")} upgrade levels, \n" +
$"Ctrl+3 to {(string.IsNullOrWhiteSpace(ignoredMetadataInfo[2]) ? "hide" : "show")} upgrade levels, \n" + $"Ctrl+3 to {(debugDrawMetaData.UpgradePrices ? "hide" : "show")} upgrade prices";
$"Ctrl+4 to {(string.IsNullOrWhiteSpace(ignoredMetadataInfo[3]) ? "hide" : "show")} upgrade prices";
Vector2 textSize = GUIStyle.SmallFont.MeasureString(text); Vector2 textSize = GUIStyle.SmallFont.MeasureString(text);
Vector2 pos = new Vector2(GameMain.GraphicsWidth - (textSize.X + 10), 300); Vector2 pos = new Vector2(GameMain.GraphicsWidth - (textSize.X + 10), 300);
DrawString(spriteBatch, pos, text, Color.White, Color.Black * 0.5f, 0, GUIStyle.SmallFont); DrawString(spriteBatch, pos, text, Color.White, Color.Black * 0.5f, 0, GUIStyle.SmallFont);
pos.Y += textSize.Y + 8; pos.Y += textSize.Y + 8;
campaignMode.CampaignMetadata?.DebugDraw(spriteBatch, pos, debugDrawMetadataOffset, ignoredMetadataInfo); campaignMode.CampaignMetadata?.DebugDraw(spriteBatch, pos, campaignMode, debugDrawMetaData);
} }
else else
{ {
@@ -582,6 +606,10 @@ namespace Barotrauma
GameMain.Client?.Draw(spriteBatch); GameMain.Client?.Draw(spriteBatch);
string factionWaterMark = "FACTION/ENDGAME TEST VERSION - please do not publicly share any material you see here!".ToUpper();
DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth / 2, 100 * yScale) - GUIStyle.SubHeadingFont.MeasureString(factionWaterMark) / 2, factionWaterMark,
GUIStyle.Red * 0.5f, font: GUIStyle.SubHeadingFont, backgroundColor: Color.Black * 0.5f, backgroundPadding: 10);
if (Character.Controlled?.Inventory != null) if (Character.Controlled?.Inventory != null)
{ {
if (Character.Controlled.Stun < 0.1f && !Character.Controlled.IsDead) if (Character.Controlled.Stun < 0.1f && !Character.Controlled.IsDead)
@@ -667,21 +695,8 @@ namespace Barotrauma
} }
} }
public static void DrawBackgroundSprite(SpriteBatch spriteBatch, Sprite backgroundSprite, float aberrationStrength = 1.0f) public static void DrawBackgroundSprite(SpriteBatch spriteBatch, Sprite backgroundSprite, Color color)
{ {
double aberrationT = (Timing.TotalTime * 0.5f);
GameMain.GameScreen.PostProcessEffect.Parameters["blurDistance"].SetValue(0.001f * aberrationStrength);
GameMain.GameScreen.PostProcessEffect.Parameters["chromaticAberrationStrength"].SetValue(new Vector3(-0.025f, -0.01f, -0.05f) *
(float)(PerlinNoise.CalculatePerlin(aberrationT, aberrationT, 0) + 0.5f) * aberrationStrength);
Matrix.CreateOrthographicOffCenter(0, GameMain.GraphicsWidth, GameMain.GraphicsHeight, 0, 0, -1, out Matrix projection);
GameMain.GameScreen.PostProcessEffect.Parameters["MatrixTransform"].SetValue(projection);
GameMain.GameScreen.PostProcessEffect.CurrentTechnique = GameMain.GameScreen.PostProcessEffect.Techniques["BlurChromaticAberration"];
GameMain.GameScreen.PostProcessEffect.CurrentTechnique.Passes[0].Apply();
spriteBatch.Begin(SpriteSortMode.Immediate, effect: GameMain.GameScreen.PostProcessEffect);
float scale = Math.Max( float scale = Math.Max(
(float)GameMain.GraphicsWidth / backgroundSprite.SourceRect.Width, (float)GameMain.GraphicsWidth / backgroundSprite.SourceRect.Width,
(float)GameMain.GraphicsHeight / backgroundSprite.SourceRect.Height) * 1.1f; (float)GameMain.GraphicsHeight / backgroundSprite.SourceRect.Height) * 1.1f;
@@ -694,10 +709,8 @@ namespace Barotrauma
spriteBatch.Draw(backgroundSprite.Texture, spriteBatch.Draw(backgroundSprite.Texture,
new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight) / 2 + pos, new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight) / 2 + pos,
null, Color.White, 0.0f, backgroundSprite.size / 2, null, color, 0.0f, backgroundSprite.size / 2,
scale, SpriteEffects.None, 0.0f); scale, SpriteEffects.None, 0.0f);
spriteBatch.End();
} }
#region Update list #region Update list
@@ -1189,48 +1202,37 @@ namespace Barotrauma
} }
if (PlayerInput.IsCtrlDown() && PlayerInput.KeyHit(Keys.M)) if (PlayerInput.IsCtrlDown() && PlayerInput.KeyHit(Keys.M))
{ {
debugDrawMetadata = !debugDrawMetadata; debugDrawMetaData.Enabled = !debugDrawMetaData.Enabled;
} }
if (debugDrawMetadata) if (debugDrawMetaData.Enabled)
{ {
if (PlayerInput.KeyHit(Keys.Up)) if (PlayerInput.KeyHit(Keys.Up))
{ {
debugDrawMetadataOffset--; debugDrawMetaData.Offset--;
} }
if (PlayerInput.KeyHit(Keys.Down)) if (PlayerInput.KeyHit(Keys.Down))
{ {
debugDrawMetadataOffset++; debugDrawMetaData.Offset++;
} }
if (PlayerInput.IsCtrlDown()) if (PlayerInput.IsCtrlDown())
{ {
if (PlayerInput.KeyHit(Keys.D1)) if (PlayerInput.KeyHit(Keys.D1))
{ {
ignoredMetadataInfo[0] = ignoredMetadataInfo[0] == string.Empty ? "reputation.location" : string.Empty; debugDrawMetaData.FactionMetadata = !debugDrawMetaData.FactionMetadata;
debugDrawMetadataOffset = 0; debugDrawMetaData.Offset = 0;
} }
if (PlayerInput.KeyHit(Keys.D2)) if (PlayerInput.KeyHit(Keys.D2))
{ {
ignoredMetadataInfo[1] = ignoredMetadataInfo[1] == string.Empty ? "reputation.faction" : string.Empty; debugDrawMetaData.UpgradeLevels = !debugDrawMetaData.UpgradeLevels;
debugDrawMetadataOffset = 0; debugDrawMetaData.Offset = 0;
} }
if (PlayerInput.KeyHit(Keys.D3)) if (PlayerInput.KeyHit(Keys.D3))
{ {
ignoredMetadataInfo[2] = ignoredMetadataInfo[2] == string.Empty ? "upgrade." : string.Empty; debugDrawMetaData.UpgradePrices = !debugDrawMetaData.UpgradePrices;
debugDrawMetadataOffset = 0; debugDrawMetaData.Offset = 0;
}
if (PlayerInput.KeyHit(Keys.D4))
{
ignoredMetadataInfo[3] = ignoredMetadataInfo[3] == string.Empty ? "upgradeprice." : string.Empty;
debugDrawMetadataOffset = 0;
} }
} }
} }
HandlePersistingElements(deltaTime); HandlePersistingElements(deltaTime);
@@ -1244,6 +1246,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)
@@ -1350,7 +1356,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>();
@@ -1605,6 +1611,54 @@ namespace Barotrauma
} }
} }
public static void DrawRectangle(SpriteBatch sb, Vector2 position, Vector2 size, Vector2 origin, float rotation, Color clr, float depth = 0.0f, float thickness = 1, OutlinePosition outlinePos = OutlinePosition.Centered)
{
Vector2 topLeft = new Vector2(-origin.X, -origin.Y);
Vector2 topRight = new Vector2(-origin.X + size.X, -origin.Y);
Vector2 bottomLeft = new Vector2(-origin.X, -origin.Y + size.Y);
Vector2 actualSize = size;
switch(outlinePos)
{
case OutlinePosition.Default:
actualSize += new Vector2(thickness);
break;
case OutlinePosition.Centered:
topLeft -= new Vector2(thickness * 0.5f);
topRight -= new Vector2(thickness * 0.5f);
bottomLeft -= new Vector2(thickness * 0.5f);
actualSize += new Vector2(thickness);
break;
case OutlinePosition.Inside:
topRight -= new Vector2(thickness, 0.0f);
bottomLeft -= new Vector2(0.0f, thickness);
break;
case OutlinePosition.Outside:
topLeft -= new Vector2(thickness);
topRight -= new Vector2(0.0f, thickness);
bottomLeft -= new Vector2(thickness, 0.0f);
actualSize += new Vector2(thickness * 2.0f);
break;
}
Matrix rotate = Matrix.CreateRotationZ(rotation);
topLeft = Vector2.Transform(topLeft, rotate) + position;
topRight = Vector2.Transform(topRight, rotate) + position;
bottomLeft = Vector2.Transform(bottomLeft, rotate) + position;
Rectangle srcRect = new Rectangle(0, 0, 1, 1);
sb.Draw(solidWhiteTexture, topLeft, srcRect, clr, rotation, Vector2.Zero, new Vector2(thickness, actualSize.Y), SpriteEffects.None, depth);
sb.Draw(solidWhiteTexture, topLeft, srcRect, clr, rotation, Vector2.Zero, new Vector2(actualSize.X, thickness), SpriteEffects.None, depth);
sb.Draw(solidWhiteTexture, topRight, srcRect, clr, rotation, Vector2.Zero, new Vector2(thickness, actualSize.Y), SpriteEffects.None, depth);
sb.Draw(solidWhiteTexture, bottomLeft, srcRect, clr, rotation, Vector2.Zero, new Vector2(actualSize.X, thickness), SpriteEffects.None, depth);
}
public static void DrawFilledRectangle(SpriteBatch sb, Vector2 position, Vector2 size, Vector2 pivot, float rotation, Color clr, float depth = 0.0f)
{
Rectangle srcRect = new Rectangle(0, 0, 1, 1);
sb.Draw(solidWhiteTexture, position, srcRect, clr, rotation, (pivot/size), size, SpriteEffects.None, depth);
}
public static void DrawFilledRectangle(SpriteBatch sb, RectangleF rect, Color clr, float depth = 0.0f) public static void DrawFilledRectangle(SpriteBatch sb, RectangleF rect, Color clr, float depth = 0.0f)
{ {
DrawFilledRectangle(sb, rect.Location, rect.Size, clr, depth); DrawFilledRectangle(sb, rect.Location, rect.Size, clr, depth);
@@ -1788,9 +1842,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)
{ {
@@ -2162,9 +2216,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,
@@ -2363,9 +2417,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; }
@@ -2603,6 +2657,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 };
@@ -266,7 +267,7 @@ namespace Barotrauma
Buttons.Clear(); Buttons.Clear();
} }
Header = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), Content.RectTransform), headerText, wrap: true); Header = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), Content.RectTransform), headerText, wrap: true, textColor: GUIStyle.TextColorBright);
GUIStyle.Apply(Header, "", this); GUIStyle.Apply(Header, "", this);
Header.RectTransform.MinSize = new Point(0, Header.Rect.Height); Header.RectTransform.MinSize = new Point(0, Header.Rect.Height);
@@ -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
@@ -285,8 +286,8 @@ namespace Barotrauma
/// This is the new constructor. /// This is the new constructor.
/// If the rectT height is set 0, the height is calculated from the text. /// If the rectT height is set 0, the height is calculated from the text.
/// </summary> /// </summary>
public GUITextBlock(RectTransform rectT, RichString text, Color? textColor = null, GUIFont font = null, public GUITextBlock(RectTransform rectT, RichString text, Color? textColor = null, GUIFont font = null,
Alignment textAlignment = Alignment.Left, bool wrap = false, string style = "", Color? color = null) Alignment textAlignment = Alignment.Left, bool wrap = false, string style = "", Color? color = null)
: base(style, rectT) : base(style, rectT)
{ {
if (color.HasValue) if (color.HasValue)
@@ -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,8 +586,14 @@ 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 (!text.NestedStr.Loaded || text.NestedStr.Language == LanguageIdentifier.None)
{
colorToShow = Color.Magenta;
}
}
if (Shadow) if (Shadow)
{ {
@@ -597,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,12 +67,12 @@ 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;
private readonly Memento<string> memento = new Memento<string>(); private readonly Memento<string> memento = new Memento<string>();
// Skip one update cycle, fixes Enter key instantly deselecting the chatbox // Skip one update cycle, fixes Enter key instantly deselecting the chatbox
private bool skipUpdate; private bool skipUpdate;
@@ -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,18 +299,17 @@ 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 += () =>
{ {
if (icon != null) { textBlock.RectTransform.MaxSize = new Point(frame.Rect.Width - icon.Rect.Height - icon.RectTransform.AbsoluteOffset.X * 2, int.MaxValue); } if (icon != null) { textBlock.RectTransform.MaxSize = new Point(frame.Rect.Width - icon.Rect.Height - icon.RectTransform.AbsoluteOffset.X * 2, int.MaxValue); }
caretPosDirty = true; caretPosDirty = true;
}; };
rectT.ScaleChanged += () => rectT.ScaleChanged += () =>
{ {
if (icon != null) { textBlock.RectTransform.MaxSize = new Point(frame.Rect.Width - icon.Rect.Height - icon.RectTransform.AbsoluteOffset.X * 2, int.MaxValue); } if (icon != null) { textBlock.RectTransform.MaxSize = new Point(frame.Rect.Width - icon.Rect.Height - icon.RectTransform.AbsoluteOffset.X * 2, int.MaxValue); }
caretPosDirty = true; caretPosDirty = true;
}; };
} }
@@ -381,14 +384,16 @@ 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)
{ {
frame.Flash(color, flashDuration, useRectangleFlash, useCircularFlash, flashRectOffset); frame.Flash(color, flashDuration, useRectangleFlash, useCircularFlash, flashRectOffset);
} }
protected override void Update(float deltaTime) protected override void Update(float deltaTime)
{ {
if (!Visible) return; if (!Visible) return;
@@ -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);
}
}
}
@@ -117,7 +117,7 @@ namespace Barotrauma
decorativeMap = new SpriteSheet("Content/Map/MapHUD.png", 6, 5, Vector2.Zero, sourceRect: new Rectangle(0, 0, 2048, 640)); decorativeMap = new SpriteSheet("Content/Map/MapHUD.png", 6, 5, Vector2.Zero, sourceRect: new Rectangle(0, 0, 2048, 640));
decorativeGraph = new SpriteSheet("Content/Map/MapHUD.png", 4, 10, Vector2.Zero, sourceRect: new Rectangle(1025, 1259, 1024, 732)); decorativeGraph = new SpriteSheet("Content/Map/MapHUD.png", 4, 10, Vector2.Zero, sourceRect: new Rectangle(1025, 1259, 1024, 732));
overlay = TextureLoader.FromFile("Content/UI/LoadingScreenOverlay.png"); overlay = TextureLoader.FromFile("Content/UI/MainMenuVignette.png");
noiseSprite = new Sprite("Content/UI/noise.png", Vector2.Zero); noiseSprite = new Sprite("Content/UI/noise.png", Vector2.Zero);
DrawLoadingText = true; DrawLoadingText = true;
SetSelectedTip(TextManager.Get("LoadingScreenTip")); SetSelectedTip(TextManager.Get("LoadingScreenTip"));
@@ -138,13 +138,6 @@ namespace Barotrauma
DisableSplashScreen(); DisableSplashScreen();
} }
} }
var titleStyle = GUIStyle.GetComponentStyle("TitleText");
Sprite titleSprite = null;
if (!WaitForLanguageSelection && titleStyle != null && titleStyle.Sprites.ContainsKey(GUIComponent.ComponentState.None))
{
titleSprite = titleStyle.Sprites[GUIComponent.ComponentState.None].First()?.Sprite;
}
drawn = true; drawn = true;
@@ -165,7 +158,7 @@ namespace Barotrauma
null, Color.White, 0.0f, new Vector2(currentBackgroundTexture.Width / 2, currentBackgroundTexture.Height / 2), null, Color.White, 0.0f, new Vector2(currentBackgroundTexture.Width / 2, currentBackgroundTexture.Height / 2),
scale, SpriteEffects.None, 0.0f); scale, SpriteEffects.None, 0.0f);
spriteBatch.Draw(overlay, new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight), null, Color.White, 0.0f, Vector2.Zero, SpriteEffects.None, 0.0f); spriteBatch.Draw(overlay, Vector2.Zero, null, Color.White, 0.0f, Vector2.Zero, Math.Min(GameMain.GraphicsWidth / (float)overlay.Width, GameMain.GraphicsHeight / (float)overlay.Height), SpriteEffects.None, 0.0f);
float noiseStrength = (float)PerlinNoise.CalculatePerlin(noiseT, noiseT, 0); float noiseStrength = (float)PerlinNoise.CalculatePerlin(noiseT, noiseT, 0);
float noiseScale = (float)PerlinNoise.CalculatePerlin(noiseT * 5.0f, noiseT * 2.0f, 0) * 4.0f; float noiseScale = (float)PerlinNoise.CalculatePerlin(noiseT * 5.0f, noiseT * 2.0f, 0) * 4.0f;
@@ -174,10 +167,7 @@ namespace Barotrauma
color: Color.White * noiseStrength * 0.1f, color: Color.White * noiseStrength * 0.1f,
textureScale: Vector2.One * noiseScale); textureScale: Vector2.One * noiseScale);
titleSprite?.Draw(spriteBatch, new Vector2(GameMain.GraphicsWidth * 0.05f, GameMain.GraphicsHeight * 0.125f), Vector2 textPos = new Vector2((int)(GameMain.GraphicsWidth * 0.05f), (int)(GameMain.GraphicsHeight * 0.75f));
Color.White, origin: new Vector2(0.0f, titleSprite.SourceRect.Height / 2.0f),
scale: GameMain.GraphicsHeight / 2000.0f);
if (WaitForLanguageSelection) if (WaitForLanguageSelection)
{ {
DrawLanguageSelectionPrompt(spriteBatch, graphics); DrawLanguageSelectionPrompt(spriteBatch, graphics);
@@ -215,16 +205,18 @@ namespace Barotrauma
#endif #endif
} }
} }
if (GUIStyle.LargeFont.HasValue) if (GUIStyle.LargeFont.HasValue)
{ {
GUIStyle.LargeFont.DrawString(spriteBatch, loadText.ToUpper(), GUIStyle.LargeFont.DrawString(spriteBatch, loadText.ToUpper(),
new Vector2(GameMain.GraphicsWidth / 2.0f - GUIStyle.LargeFont.MeasureString(loadText.ToUpper()).X / 2.0f, GameMain.GraphicsHeight * 0.75f), textPos,
Color.White); Color.White);
textPos.Y += GUIStyle.LargeFont.MeasureString(loadText.ToUpper()).Y * 1.2f;
} }
if (GUIStyle.Font.HasValue && selectedTip != null) if (GUIStyle.Font.HasValue && selectedTip != null)
{ {
string wrappedTip = ToolBox.WrapText(selectedTip.SanitizedValue, GameMain.GraphicsWidth * 0.5f, GUIStyle.Font.Value); string wrappedTip = ToolBox.WrapText(selectedTip.SanitizedValue, GameMain.GraphicsWidth * 0.3f, GUIStyle.Font.Value);
string[] lines = wrappedTip.Split('\n'); string[] lines = wrappedTip.Split('\n');
float lineHeight = GUIStyle.Font.MeasureString(selectedTip).Y; float lineHeight = GUIStyle.Font.MeasureString(selectedTip).Y;
@@ -234,7 +226,8 @@ namespace Barotrauma
for (int i = 0; i < lines.Length; i++) for (int i = 0; i < lines.Length; i++)
{ {
GUIStyle.Font.DrawStringWithColors(spriteBatch, lines[i], GUIStyle.Font.DrawStringWithColors(spriteBatch, lines[i],
new Vector2((int)(GameMain.GraphicsWidth / 2.0f - GUIStyle.Font.MeasureString(lines[i]).X / 2.0f), (int)(GameMain.GraphicsHeight * 0.8f + i * lineHeight)), Color.White, new Vector2(textPos.X, (int)(textPos.Y + i * lineHeight)),
Color.White,
0f, Vector2.Zero, 1f, SpriteEffects.None, 0f, selectedTip.RichTextData.Value, rtdOffset); 0f, Vector2.Zero, 1f, SpriteEffects.None, 0f, selectedTip.RichTextData.Value, rtdOffset);
rtdOffset += lines[i].Length; rtdOffset += lines[i].Length;
} }
@@ -244,7 +237,8 @@ namespace Barotrauma
for (int i = 0; i < lines.Length; i++) for (int i = 0; i < lines.Length; i++)
{ {
GUIStyle.Font.DrawString(spriteBatch, lines[i], GUIStyle.Font.DrawString(spriteBatch, lines[i],
new Vector2((int)(GameMain.GraphicsWidth / 2.0f - GUIStyle.Font.MeasureString(lines[i]).X / 2.0f), (int)(GameMain.GraphicsHeight * 0.8f + i * lineHeight)), Color.White); new Vector2(textPos.X, (int)(textPos.Y + i * lineHeight)),
new Color(228, 217, 167, 255));
} }
} }
} }
@@ -257,13 +251,16 @@ namespace Barotrauma
Vector2 decorativeScale = new Vector2(GameMain.GraphicsHeight / 1080.0f); Vector2 decorativeScale = new Vector2(GameMain.GraphicsHeight / 1080.0f);
float noiseVal = (float)PerlinNoise.CalculatePerlin(Timing.TotalTime * 0.25f, Timing.TotalTime * 0.5f, 0); float noiseVal = (float)PerlinNoise.CalculatePerlin(Timing.TotalTime * 0.25f, Timing.TotalTime * 0.5f, 0);
decorativeGraph.Draw(spriteBatch, (int)(decorativeGraph.FrameCount * noiseVal), if (!WaitForLanguageSelection)
new Vector2(GameMain.GraphicsWidth * 0.001f, GameMain.GraphicsHeight * 0.24f), {
Color.White, Vector2.Zero, 0.0f, decorativeScale, SpriteEffects.FlipVertically); decorativeGraph.Draw(spriteBatch, (int)(decorativeGraph.FrameCount * noiseVal),
new Vector2(GameMain.GraphicsWidth * 0.001f, textPos.Y),
Color.White, new Vector2(0, decorativeMap.FrameSize.Y), 0.0f, decorativeScale, SpriteEffects.FlipVertically);
}
decorativeMap.Draw(spriteBatch, (int)(decorativeMap.FrameCount * noiseVal), decorativeMap.Draw(spriteBatch, (int)(decorativeMap.FrameCount * noiseVal),
new Vector2(GameMain.GraphicsWidth * 0.99f, GameMain.GraphicsHeight * 0.66f), new Vector2(GameMain.GraphicsWidth * 0.99f, GameMain.GraphicsHeight * 0.01f),
Color.White, decorativeMap.FrameSize.ToVector2(), 0.0f, decorativeScale); Color.White, new Vector2(decorativeMap.FrameSize.X, 0), 0.0f, decorativeScale, SpriteEffects.FlipHorizontally | SpriteEffects.FlipVertically);
if (noiseVal < 0.2f) if (noiseVal < 0.2f)
{ {
@@ -285,8 +282,9 @@ namespace Barotrauma
if (GUIStyle.LargeFont.HasValue) if (GUIStyle.LargeFont.HasValue)
{ {
Vector2 textSize = GUIStyle.LargeFont.MeasureString(randText);
GUIStyle.LargeFont.DrawString(spriteBatch, randText, GUIStyle.LargeFont.DrawString(spriteBatch, randText,
new Vector2(GameMain.GraphicsWidth - decorativeMap.FrameSize.X * decorativeScale.X * 0.8f, GameMain.GraphicsHeight * 0.57f), new Vector2(GameMain.GraphicsWidth * 0.95f - textSize.X, GameMain.GraphicsHeight * 0.06f),
Color.White * (1.0f - noiseVal)); Color.White * (1.0f - noiseVal));
} }
@@ -312,8 +310,8 @@ namespace Barotrauma
languageSelectionCursor = new Sprite("Content/UI/cursor.png", Vector2.Zero); languageSelectionCursor = new Sprite("Content/UI/cursor.png", Vector2.Zero);
} }
Vector2 textPos = new Vector2(GameMain.GraphicsWidth / 2, GameMain.GraphicsHeight * 0.3f); Vector2 textPos = new Vector2((int)(GameMain.GraphicsWidth * 0.05f), (int)(GameMain.GraphicsHeight * 0.3f));
Vector2 textSpacing = new Vector2(0.0f, (GameMain.GraphicsHeight * 0.5f) / AvailableLanguages.Length); Vector2 textSpacing = new Vector2(0.0f, GameMain.GraphicsHeight * 0.5f / AvailableLanguages.Length);
foreach (LanguageIdentifier language in AvailableLanguages) foreach (LanguageIdentifier language in AvailableLanguages)
{ {
string localizedLanguageName = TextManager.GetTranslatedLanguageName(language); string localizedLanguageName = TextManager.GetTranslatedLanguageName(language);
@@ -321,10 +319,10 @@ namespace Barotrauma
Vector2 textSize = font.MeasureString(localizedLanguageName); Vector2 textSize = font.MeasureString(localizedLanguageName);
bool hover = bool hover =
Math.Abs(PlayerInput.MousePosition.X - textPos.X) < textSize.X / 2 && PlayerInput.MousePosition.X > textPos.X && PlayerInput.MousePosition.X < textPos.X + textSize.X &&
Math.Abs(PlayerInput.MousePosition.Y - textPos.Y) < textSpacing.Y / 2; PlayerInput.MousePosition.Y > textPos.Y && PlayerInput.MousePosition.Y < textPos.Y + textSize.Y;
font.DrawString(spriteBatch, localizedLanguageName, textPos - textSize / 2, font.DrawString(spriteBatch, localizedLanguageName, textPos,
hover ? Color.White : Color.White * 0.6f); hover ? Color.White : Color.White * 0.6f);
if (hover && PlayerInput.PrimaryMouseButtonClicked()) if (hover && PlayerInput.PrimaryMouseButtonClicked())
{ {
@@ -431,7 +429,7 @@ namespace Barotrauma
drawn = false; drawn = false;
LoadState = null; LoadState = null;
SetSelectedTip(TextManager.Get("LoadingScreenTip")); SetSelectedTip(TextManager.Get("LoadingScreenTip"));
currentBackgroundTexture = LocationType.Prefabs.GetRandomUnsynced()?.GetPortrait(Rand.Int(int.MaxValue))?.Texture; currentBackgroundTexture = LocationType.Prefabs.Where(p => p.UsePortraitInRandomLoadingScreens).GetRandomUnsynced()?.GetPortrait(Rand.Int(int.MaxValue))?.Texture;
while (!drawn) while (!drawn)
{ {
@@ -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; }
@@ -845,9 +858,9 @@ namespace Barotrauma
GUITextBlock prefabBlock = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1f), topTextLayout.RectTransform), prefab.Name, font: GUIStyle.SubHeadingFont); GUITextBlock prefabBlock = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1f), topTextLayout.RectTransform), prefab.Name, font: GUIStyle.SubHeadingFont);
Color textColor = Color.Lerp(GUIStyle.Orange, GUIStyle.Red, (int)affliction.AfflictionSeverity / 2f); Color textColor = Color.Lerp(GUIStyle.Orange, GUIStyle.Red, affliction.Strength / affliction.Prefab.MaxStrength);
LocalizedString vitalityText = TextManager.GetWithVariable("medicalclinic.vitalitydifference", "[amount]", (-affliction.Strength).ToString()); LocalizedString vitalityText = affliction.VitalityDecrease == 0 ? string.Empty : TextManager.GetWithVariable("medicalclinic.vitalitydifference", "[amount]", (-affliction.VitalityDecrease).ToString());
GUITextBlock vitalityBlock = new GUITextBlock(new RectTransform(new Vector2(0.25f, 1f), topTextLayout.RectTransform), vitalityText, textAlignment: Alignment.Center) GUITextBlock vitalityBlock = new GUITextBlock(new RectTransform(new Vector2(0.25f, 1f), topTextLayout.RectTransform), vitalityText, textAlignment: Alignment.Center)
{ {
TextColor = textColor, TextColor = textColor,
@@ -856,7 +869,7 @@ namespace Barotrauma
AutoScaleHorizontal = true AutoScaleHorizontal = true
}; };
LocalizedString severityText = TextManager.Get($"AfflictionStrength{affliction.AfflictionSeverity}"); LocalizedString severityText = Affliction.GetStrengthText(affliction.Strength, affliction.Prefab.MaxStrength);
GUITextBlock severityBlock = new GUITextBlock(new RectTransform(new Vector2(0.25f, 1f), topTextLayout.RectTransform), severityText, textAlignment: Alignment.Center, font: GUIStyle.SubHeadingFont) GUITextBlock severityBlock = new GUITextBlock(new RectTransform(new Vector2(0.25f, 1f), topTextLayout.RectTransform), severityText, textAlignment: Alignment.Center, font: GUIStyle.SubHeadingFont)
{ {
TextColor = textColor, TextColor = textColor,
@@ -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 =>
{ {
@@ -724,7 +724,7 @@ namespace Barotrauma
ChangeStoreTab(StoreTab.Buy); ChangeStoreTab(StoreTab.Buy);
if (newLocation?.Reputation != null) if (newLocation?.Reputation != null)
{ {
CurrentLocation.Reputation.OnReputationValueChanged.RegisterOverwriteExisting("RefreshStore".ToIdentifier(), _ => { SetNeedsRefresh(); }); newLocation.Reputation.OnReputationValueChanged.RegisterOverwriteExisting("RefreshStore".ToIdentifier(), _ => { SetNeedsRefresh(); });
} }
} }
@@ -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;
} }
@@ -852,6 +852,28 @@ namespace Barotrauma
FilterStoreItems(category, searchBox.Text); FilterStoreItems(category, searchBox.Text);
} }
private static KeyValuePair<Identifier, float>? GetReputationRequirement(PriceInfo priceInfo)
{
return GameMain.GameSession?.Campaign is not null
? priceInfo.MinReputation.FirstOrNull()
: null;
}
private static KeyValuePair<Identifier, float>? GetTooLowReputation(PriceInfo priceInfo)
{
if (GameMain.GameSession?.Campaign is CampaignMode campaign)
{
foreach (var minRep in priceInfo.MinReputation)
{
if (campaign.GetReputation(minRep.Key) < minRep.Value)
{
return minRep;
}
}
}
return null;
}
int prevDailySpecialCount, prevRequestedGoodsCount, prevSubRequestedGoodsCount; int prevDailySpecialCount, prevRequestedGoodsCount, prevSubRequestedGoodsCount;
private void RefreshStoreBuyList() private void RefreshStoreBuyList()
@@ -892,8 +914,9 @@ namespace Barotrauma
void CreateOrUpdateItemFrame(ItemPrefab itemPrefab, int quantity) void CreateOrUpdateItemFrame(ItemPrefab itemPrefab, int quantity)
{ {
if (itemPrefab.CanBeBoughtFrom(ActiveStore, out PriceInfo priceInfo)) if (itemPrefab.CanBeBoughtFrom(ActiveStore, out PriceInfo priceInfo) && itemPrefab.CanCharacterBuy())
{ {
bool isDailySpecial = ActiveStore.DailySpecials.Contains(itemPrefab); bool isDailySpecial = ActiveStore.DailySpecials.Contains(itemPrefab);
var itemFrame = isDailySpecial ? var itemFrame = isDailySpecial ?
storeDailySpecialsGroup.FindChild(c => c.UserData is PurchasedItem pi && pi.ItemPrefab == itemPrefab) : storeDailySpecialsGroup.FindChild(c => c.UserData is PurchasedItem pi && pi.ItemPrefab == itemPrefab) :
@@ -918,7 +941,8 @@ namespace Barotrauma
SetOwnedText(itemFrame); SetOwnedText(itemFrame);
SetPriceGetters(itemFrame, true); SetPriceGetters(itemFrame, true);
} }
SetItemFrameStatus(itemFrame, hasPermissions && quantity > 0);
SetItemFrameStatus(itemFrame, hasPermissions && quantity > 0 && !GetTooLowReputation(priceInfo).HasValue);
existingItemFrames.Add(itemFrame); existingItemFrames.Add(itemFrame);
} }
} }
@@ -1110,7 +1134,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)
{ {
@@ -1302,6 +1326,8 @@ namespace Barotrauma
{ {
if (x.GUIComponent.UserData is PurchasedItem itemX && y.GUIComponent.UserData is PurchasedItem itemY) if (x.GUIComponent.UserData is PurchasedItem itemX && y.GUIComponent.UserData is PurchasedItem itemY)
{ {
int reputationCompare = CompareByReputationRestriction(itemX, itemY);
if (reputationCompare != 0) { return reputationCompare; }
int sortResult = itemX.ItemPrefab.Name != itemY.ItemPrefab.Name ? int sortResult = itemX.ItemPrefab.Name != itemY.ItemPrefab.Name ?
itemX.ItemPrefab.Name.CompareTo(itemY.ItemPrefab.Name) : itemX.ItemPrefab.Name.CompareTo(itemY.ItemPrefab.Name) :
itemX.ItemPrefab.Identifier.CompareTo(itemY.ItemPrefab.Identifier); itemX.ItemPrefab.Identifier.CompareTo(itemY.ItemPrefab.Identifier);
@@ -1330,6 +1356,8 @@ namespace Barotrauma
{ {
if (x.GUIComponent.UserData is PurchasedItem itemX && y.GUIComponent.UserData is PurchasedItem itemY) if (x.GUIComponent.UserData is PurchasedItem itemX && y.GUIComponent.UserData is PurchasedItem itemY)
{ {
int reputationCompare = CompareByReputationRestriction(itemX, itemY);
if (reputationCompare != 0) { return reputationCompare; }
int sortResult = ActiveStore.GetAdjustedItemSellPrice(itemX.ItemPrefab).CompareTo( int sortResult = ActiveStore.GetAdjustedItemSellPrice(itemX.ItemPrefab).CompareTo(
ActiveStore.GetAdjustedItemSellPrice(itemY.ItemPrefab)); ActiveStore.GetAdjustedItemSellPrice(itemY.ItemPrefab));
if (sortingMethod == SortingMethod.PriceDesc) { sortResult *= -1; } if (sortingMethod == SortingMethod.PriceDesc) { sortResult *= -1; }
@@ -1354,6 +1382,8 @@ namespace Barotrauma
{ {
if (x.GUIComponent.UserData is PurchasedItem itemX && y.GUIComponent.UserData is PurchasedItem itemY) if (x.GUIComponent.UserData is PurchasedItem itemX && y.GUIComponent.UserData is PurchasedItem itemY)
{ {
int reputationCompare = CompareByReputationRestriction(itemX, itemY);
if (reputationCompare != 0) { return reputationCompare; }
int sortResult = ActiveStore.GetAdjustedItemBuyPrice(itemX.ItemPrefab).CompareTo( int sortResult = ActiveStore.GetAdjustedItemBuyPrice(itemX.ItemPrefab).CompareTo(
ActiveStore.GetAdjustedItemBuyPrice(itemY.ItemPrefab)); ActiveStore.GetAdjustedItemBuyPrice(itemY.ItemPrefab));
if (sortingMethod == SortingMethod.PriceDesc) { sortResult *= -1; } if (sortingMethod == SortingMethod.PriceDesc) { sortResult *= -1; }
@@ -1376,10 +1406,12 @@ namespace Barotrauma
specialsGroup.Recalculate(); specialsGroup.Recalculate();
} }
static int CompareByCategory(RectTransform x, RectTransform y) int CompareByCategory(RectTransform x, RectTransform y)
{ {
if (x.GUIComponent.UserData is PurchasedItem itemX && y.GUIComponent.UserData is PurchasedItem itemY) if (x.GUIComponent.UserData is PurchasedItem itemX && y.GUIComponent.UserData is PurchasedItem itemY)
{ {
int reputationCompare = CompareByReputationRestriction(itemX, itemY);
if (reputationCompare != 0) { return reputationCompare; }
return itemX.ItemPrefab.Category.CompareTo(itemY.ItemPrefab.Category); return itemX.ItemPrefab.Category.CompareTo(itemY.ItemPrefab.Category);
} }
else else
@@ -1409,6 +1441,19 @@ namespace Barotrauma
} }
} }
int CompareByReputationRestriction(PurchasedItem item1, PurchasedItem item2)
{
PriceInfo priceInfo1 = item1.ItemPrefab.GetPriceInfo(ActiveStore);
PriceInfo priceInfo2 = item2.ItemPrefab.GetPriceInfo(ActiveStore);
if (priceInfo1 != null && priceInfo2 != null)
{
var requiredReputation1 = GetTooLowReputation(priceInfo1)?.Value ?? 0.0f;
var requiredReputation2 = GetTooLowReputation(priceInfo2)?.Value ?? 0.0f;
return requiredReputation1.CompareTo(requiredReputation2);
}
return 0;
}
static int CompareByElement(RectTransform x, RectTransform y) static int CompareByElement(RectTransform x, RectTransform y)
{ {
if (ShouldBeOnTop(x) || ShouldBeOnBottom(y)) if (ShouldBeOnTop(x) || ShouldBeOnBottom(y))
@@ -1667,8 +1712,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 +1746,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,14 +1774,14 @@ 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)
{ {
if (pi.ItemPrefab?.InventoryIcon != null) if (pi.ItemPrefab?.InventoryIcon != null)
{ {
icon.Color = pi.ItemPrefab.InventoryIconColor * (enabled ? 1.0f: 0.5f); icon.Color = pi.ItemPrefab.InventoryIconColor * (enabled ? 1.0f : 0.5f);
} }
else if (pi.ItemPrefab?.Sprite != null) else if (pi.ItemPrefab?.Sprite != null)
{ {
@@ -1841,11 +1886,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)
@@ -1858,8 +1899,25 @@ namespace Barotrauma
toolTip += $"\n{TextManager.GetWithVariable("campaignstore.ownedtotal", "[amount]", itemQuantity.Total.ToString())}"; toolTip += $"\n{TextManager.GetWithVariable("campaignstore.ownedtotal", "[amount]", itemQuantity.Total.ToString())}";
} }
} }
PriceInfo priceInfo = purchasedItem.ItemPrefab.GetPriceInfo(ActiveStore);
var campaign = GameMain.GameSession?.Campaign;
if (priceInfo != null && campaign != null)
{
var requiredReputation = GetReputationRequirement(priceInfo);
if (requiredReputation != null)
{
var repStr = TextManager.GetWithVariables(
"campaignstore.reputationrequired",
("[amount]", ((int)requiredReputation.Value.Value).ToString()),
("[faction]", TextManager.Get("faction." + requiredReputation.Value.Key).Value));
Color color = campaign.GetReputation(requiredReputation.Value.Key) < requiredReputation.Value.Value ?
GUIStyle.Orange : GUIStyle.Green;
toolTip += $"\n‖color:{color.ToStringHex()}‖{repStr}‖color:end‖";
}
}
} }
itemComponent.ToolTip = toolTip; itemComponent.ToolTip = RichString.Rich(toolTip);
} }
if (ownedLabel != null) if (ownedLabel != null)
{ {
@@ -1995,11 +2053,23 @@ namespace Barotrauma
int totalPrice = 0; int totalPrice = 0;
foreach (var item in itemsToPurchase) foreach (var item in itemsToPurchase)
{ {
if (item?.ItemPrefab == null || !item.ItemPrefab.CanBeBoughtFrom(ActiveStore, out var priceInfo)) if (item is null) { continue; }
if (item.ItemPrefab == null || !item.ItemPrefab.CanBeBoughtFrom(ActiveStore, out var priceInfo))
{ {
itemsToRemove.Add(item); itemsToRemove.Add(item);
continue; continue;
} }
if (item.ItemPrefab.DefaultPrice.RequiresUnlock)
{
if (!CargoManager.HasUnlockedStoreItem(item.ItemPrefab))
{
itemsToRemove.Add(item);
continue;
}
}
totalPrice += item.Quantity * ActiveStore.GetAdjustedItemBuyPrice(item.ItemPrefab, priceInfo: priceInfo); totalPrice += item.Quantity * ActiveStore.GetAdjustedItemBuyPrice(item.ItemPrefab, priceInfo: priceInfo);
} }
itemsToRemove.ForEach(i => itemsToPurchase.Remove(i)); itemsToRemove.ForEach(i => itemsToPurchase.Remove(i));
@@ -2169,7 +2239,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));
} }
} }
@@ -406,7 +406,7 @@ namespace Barotrauma
if (!GameMain.GameSession.IsSubmarineOwned(subToDisplay)) if (!GameMain.GameSession.IsSubmarineOwned(subToDisplay))
{ {
LocalizedString amountString = TextManager.FormatCurrency(subToDisplay.Price); LocalizedString amountString = TextManager.FormatCurrency(subToDisplay.GetPrice());
submarineDisplays[i].submarineFee.Text = TextManager.GetWithVariable("price", "[amount]", amountString); submarineDisplays[i].submarineFee.Text = TextManager.GetWithVariable("price", "[amount]", amountString);
} }
else else
@@ -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)
@@ -673,7 +681,7 @@ namespace Barotrauma
{ {
if (GameMain.GameSession?.Campaign?.PendingSubmarineSwitch == null) if (GameMain.GameSession?.Campaign?.PendingSubmarineSwitch == null)
{ {
return Submarine.MainSub.Info; return Submarine.MainSub?.Info;
} }
else else
{ {
@@ -789,7 +797,9 @@ namespace Barotrauma
private void ShowBuyPrompt(bool purchaseOnly) private void ShowBuyPrompt(bool purchaseOnly)
{ {
if (!GameMain.GameSession.Campaign.CanAfford(selectedSubmarine.Price)) int price = selectedSubmarine.GetPrice();
if (!GameMain.GameSession.Campaign.CanAfford(price))
{ {
new GUIMessageBox(TextManager.Get("purchasesubmarineheader"), TextManager.GetWithVariables("notenoughmoneyforpurchasetext", new GUIMessageBox(TextManager.Get("purchasesubmarineheader"), TextManager.GetWithVariables("notenoughmoneyforpurchasetext",
("[currencyname]", currencyName), ("[currencyname]", currencyName),
@@ -802,7 +812,7 @@ namespace Barotrauma
{ {
var text = TextManager.GetWithVariables("purchaseandswitchsubmarinetext", var text = TextManager.GetWithVariables("purchaseandswitchsubmarinetext",
("[submarinename1]", selectedSubmarine.DisplayName), ("[submarinename1]", selectedSubmarine.DisplayName),
("[amount]", selectedSubmarine.Price.ToString()), ("[amount]", price.ToString()),
("[currencyname]", currencyName), ("[currencyname]", currencyName),
("[submarinename2]", CurrentOrPendingSubmarine().DisplayName)); ("[submarinename2]", CurrentOrPendingSubmarine().DisplayName));
text += GetItemTransferText(); text += GetItemTransferText();
@@ -860,7 +870,7 @@ namespace Barotrauma
{ {
msgBox = new GUIMessageBox(TextManager.Get("purchasesubmarineheader"), TextManager.GetWithVariables("purchasesubmarinetext", msgBox = new GUIMessageBox(TextManager.Get("purchasesubmarineheader"), TextManager.GetWithVariables("purchasesubmarinetext",
("[submarinename]", selectedSubmarine.DisplayName), ("[submarinename]", selectedSubmarine.DisplayName),
("[amount]", selectedSubmarine.Price.ToString()), ("[amount]", price.ToString()),
("[currencyname]", currencyName)) + '\n' + TextManager.Get("submarineswitchinstruction"), messageBoxOptions); ("[currencyname]", currencyName)) + '\n' + TextManager.Get("submarineswitchinstruction"), messageBoxOptions);
msgBox.Buttons[0].OnClicked = (applyButton, obj) => msgBox.Buttons[0].OnClicked = (applyButton, obj) =>
@@ -34,7 +34,7 @@ namespace Barotrauma
private List<CharacterTeamType> teamIDs; private List<CharacterTeamType> teamIDs;
private const string inLobbyString = "\u2022 \u2022 \u2022"; private const string inLobbyString = "\u2022 \u2022 \u2022";
private GUIFrame pendingChangesFrame = null; public static GUIFrame PendingChangesFrame = null;
public static Color OwnCharacterBGColor = Color.Gold * 0.7f; public static Color OwnCharacterBGColor = Color.Gold * 0.7f;
private bool isTransferMenuOpen; private bool isTransferMenuOpen;
@@ -44,6 +44,7 @@ namespace Barotrauma
private float transferMenuOpenState; private float transferMenuOpenState;
private bool transferMenuStateCompleted; private bool transferMenuStateCompleted;
private readonly HashSet<Identifier> registeredEvents = new HashSet<Identifier>(); private readonly HashSet<Identifier> registeredEvents = new HashSet<Identifier>();
private readonly TalentMenu talentMenu = new TalentMenu();
private class LinkedGUI private class LinkedGUI
{ {
@@ -206,15 +207,8 @@ namespace Barotrauma
transferMenuButton.RectTransform.AbsoluteOffset = new Point(0, -pos - transferMenu.Rect.Height); transferMenuButton.RectTransform.AbsoluteOffset = new Point(0, -pos - transferMenu.Rect.Height);
} }
GameSession.UpdateTalentNotificationIndicator(talentPointNotification); GameSession.UpdateTalentNotificationIndicator(talentPointNotification);
if (Character.Controlled?.Info is { } characterInfo && talentResetButton != null && talentApplyButton != null)
{ talentMenu?.Update();
int talentCount = selectedTalents.Count - characterInfo.GetUnlockedTalentsInTree().Count();
talentResetButton.Enabled = talentApplyButton.Enabled = talentCount > 0;
if (talentApplyButton.Enabled && talentApplyButton.FlashTimer <= 0.0f)
{
talentApplyButton.Flash(GUIStyle.Orange);
}
}
if (SelectedTab != InfoFrameTab.Crew) { return; } if (SelectedTab != InfoFrameTab.Crew) { return; }
if (linkedGUIList == null) { return; } if (linkedGUIList == null) { return; }
@@ -325,11 +319,11 @@ namespace Barotrauma
AbsoluteOffset = new Point(contentFrame.Rect.X, contentFrame.Rect.Bottom + GUI.IntScale(8)) AbsoluteOffset = new Point(contentFrame.Rect.X, contentFrame.Rect.Bottom + GUI.IntScale(8))
}, style: null); }, style: null);
pendingChangesFrame = new GUIFrame(new RectTransform(Vector2.One, bottomDisclaimerFrame.RectTransform, Anchor.Center), style: null); PendingChangesFrame = new GUIFrame(new RectTransform(Vector2.One, bottomDisclaimerFrame.RectTransform, Anchor.Center), style: null);
if (GameMain.NetLobbyScreen?.CampaignCharacterDiscarded ?? false) if (GameMain.NetLobbyScreen?.CampaignCharacterDiscarded ?? false)
{ {
NetLobbyScreen.CreateChangesPendingFrame(pendingChangesFrame); NetLobbyScreen.CreateChangesPendingFrame(PendingChangesFrame);
} }
SetBalanceText(balanceText, campaignMode.Bank.Balance); SetBalanceText(balanceText, campaignMode.Bank.Balance);
@@ -403,7 +397,7 @@ namespace Barotrauma
CreateSubmarineInfo(infoFrameHolder, Submarine.MainSub); CreateSubmarineInfo(infoFrameHolder, Submarine.MainSub);
break; break;
case InfoFrameTab.Talents: case InfoFrameTab.Talents:
CreateCharacterInfo(infoFrameHolder); talentMenu.CreateGUI(infoFrameHolder, Character.Controlled ?? GameMain.Client?.Character);
break; break;
} }
} }
@@ -957,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)
{ {
@@ -1494,27 +1498,9 @@ namespace Barotrauma
AbsoluteSpacing = GUI.IntScale(10) AbsoluteSpacing = GUI.IntScale(10)
}; };
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), locationInfoContainer.RectTransform), location.Name, font: GUIStyle.LargeFont);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), locationInfoContainer.RectTransform), location.Type.Name, font: GUIStyle.SubHeadingFont);
var biomeLabel = new GUITextBlock(new RectTransform(new Vector2(0.5f, 0.0f), locationInfoContainer.RectTransform),
TextManager.Get("Biome", "location"), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.CenterLeft);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), biomeLabel.RectTransform), Level.Loaded.LevelData.Biome.DisplayName, textAlignment: Alignment.CenterRight);
var difficultyLabel = new GUITextBlock(new RectTransform(new Vector2(0.5f, 0.0f), locationInfoContainer.RectTransform),
TextManager.Get("LevelDifficulty"), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.CenterLeft);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), difficultyLabel.RectTransform), ((int)Level.Loaded.LevelData.Difficulty) + " %", textAlignment: Alignment.CenterRight);
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.01f), missionFrameContent.RectTransform) { AbsoluteOffset = new Point(0, locationInfoContainer.Rect.Height + padding) }, style: "HorizontalLine")
{
CanBeFocused = false
};
int locationInfoYOffset = locationInfoContainer.Rect.Height + padding * 2;
Sprite portrait = location.Type.GetPortrait(location.PortraitId); Sprite portrait = location.Type.GetPortrait(location.PortraitId);
bool hasPortrait = portrait != null && portrait.SourceRect.Width > 0 && portrait.SourceRect.Height > 0; bool hasPortrait = portrait != null && portrait.SourceRect.Width > 0 && portrait.SourceRect.Height > 0;
int contentWidth = missionFrameContent.Rect.Width; int contentWidth = missionFrameContent.Rect.Width;
if (hasPortrait) if (hasPortrait)
{ {
float portraitAspectRatio = portrait.SourceRect.Width / portrait.SourceRect.Height; float portraitAspectRatio = portrait.SourceRect.Width / portrait.SourceRect.Height;
@@ -1526,6 +1512,30 @@ namespace Barotrauma
portraitImage.RectTransform.NonScaledSize = new Point(Math.Min((int)(portraitImage.Rect.Size.Y * portraitAspectRatio), portraitImage.Rect.Width), portraitImage.Rect.Size.Y); portraitImage.RectTransform.NonScaledSize = new Point(Math.Min((int)(portraitImage.Rect.Size.Y * portraitAspectRatio), portraitImage.Rect.Width), portraitImage.Rect.Size.Y);
} }
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), locationInfoContainer.RectTransform), location.Name, font: GUIStyle.LargeFont);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), locationInfoContainer.RectTransform), location.Type.Name, font: GUIStyle.SubHeadingFont);
if (location.Faction?.Prefab != null)
{
var factionLabel = new GUITextBlock(new RectTransform(new Vector2(0.5f, 0.0f), locationInfoContainer.RectTransform),
TextManager.Get("Faction"), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.CenterLeft);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), factionLabel.RectTransform), location.Faction.Prefab.Name, textAlignment: Alignment.CenterRight);
}
var biomeLabel = new GUITextBlock(new RectTransform(new Vector2(0.5f, 0.0f), locationInfoContainer.RectTransform),
TextManager.Get("Biome", "location"), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.CenterLeft);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), biomeLabel.RectTransform), Level.Loaded.LevelData.Biome.DisplayName, textAlignment: Alignment.CenterRight);
var difficultyLabel = new GUITextBlock(new RectTransform(new Vector2(0.5f, 0.0f), locationInfoContainer.RectTransform),
TextManager.Get("LevelDifficulty"), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.CenterLeft);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), difficultyLabel.RectTransform), TextManager.GetWithVariable("percentageformat", "[value]", ((int)Level.Loaded.LevelData.Difficulty).ToString()), textAlignment: Alignment.CenterRight);
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.01f), missionFrameContent.RectTransform) { AbsoluteOffset = new Point(0, locationInfoContainer.Rect.Height + padding) }, style: "HorizontalLine")
{
CanBeFocused = false
};
int locationInfoYOffset = locationInfoContainer.Rect.Height + padding * 2;
GUIListBox missionList = new GUIListBox(new RectTransform(new Point(contentWidth, missionFrameContent.Rect.Height - locationInfoYOffset), missionFrameContent.RectTransform, Anchor.TopCenter) { AbsoluteOffset = new Point(0, locationInfoYOffset) }); GUIListBox missionList = new GUIListBox(new RectTransform(new Point(contentWidth, missionFrameContent.Rect.Height - locationInfoYOffset), missionFrameContent.RectTransform, Anchor.TopCenter) { AbsoluteOffset = new Point(0, locationInfoYOffset) });
missionList.ContentBackground.Color = Color.Transparent; missionList.ContentBackground.Color = Color.Transparent;
missionList.Spacing = GUI.IntScale(15); missionList.Spacing = GUI.IntScale(15);
@@ -1537,6 +1547,7 @@ namespace Barotrauma
foreach (Mission mission in GameMain.GameSession.Missions) foreach (Mission mission in GameMain.GameSession.Missions)
{ {
if (!mission.Prefab.ShowInMenus) { continue; }
GUIFrame missionDescriptionHolder = new GUIFrame(new RectTransform(Vector2.One, missionList.Content.RectTransform), style: null); GUIFrame missionDescriptionHolder = new GUIFrame(new RectTransform(Vector2.One, missionList.Content.RectTransform), style: null);
GUILayoutGroup missionTextGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.744f, 0f), missionDescriptionHolder.RectTransform, Anchor.CenterLeft) { AbsoluteOffset = new Point(iconSize + spacing, 0) }, false, childAnchor: Anchor.TopLeft) GUILayoutGroup missionTextGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.744f, 0f), missionDescriptionHolder.RectTransform, Anchor.CenterLeft) { AbsoluteOffset = new Point(iconSize + spacing, 0) }, false, childAnchor: Anchor.TopLeft)
{ {
@@ -1774,373 +1785,10 @@ namespace Barotrauma
sub.Info.CreateSpecsWindow(specsListBox, GUIStyle.Font, includeTitle: false, includeClass: false, includeDescription: true); sub.Info.CreateSpecsWindow(specsListBox, GUIStyle.Font, includeTitle: false, includeClass: false, includeDescription: true);
} }
} }
private Color unselectedColor = new Color(240, 255, 255, 225);
private Color unselectableColor = new Color(100, 100, 100, 225);
private Color pressedColor = new Color(60, 60, 60, 225);
private readonly List<(GUIButton button, GUIComponent icon)> talentButtons = new List<(GUIButton button, GUIComponent icon)>();
private readonly List<(Identifier talentTree, int index, GUIImage icon, GUIFrame background, GUIFrame backgroundGlow)> talentCornerIcons = new List<(Identifier talentTree, int index, GUIImage icon, GUIFrame background, GUIFrame backgroundGlow)>();
private List<Identifier> selectedTalents = new List<Identifier>();
private GUITextBlock experienceText;
private GUIProgressBar experienceBar;
private GUITextBlock talentPointText;
private GUIListBox skillListBox;
private GUIButton talentApplyButton,
talentResetButton;
private GUIImage talentPointNotification; private GUIImage talentPointNotification;
private readonly ImmutableDictionary<TalentTree.TalentTreeStageState, GUIComponentStyle> talentStageStyles = new Dictionary<TalentTree.TalentTreeStageState, GUIComponentStyle> public static void CreateSkillList(Character character, CharacterInfo info, GUIListBox parent)
{
{ TalentTree.TalentTreeStageState.Invalid, GUIStyle.GetComponentStyle("TalentTreeLocked") },
{ TalentTree.TalentTreeStageState.Locked, GUIStyle.GetComponentStyle("TalentTreeLocked") },
{ TalentTree.TalentTreeStageState.Unlocked, GUIStyle.GetComponentStyle("TalentTreePurchased") },
{ TalentTree.TalentTreeStageState.Available, GUIStyle.GetComponentStyle("TalentTreeUnlocked") },
{ TalentTree.TalentTreeStageState.Highlighted, GUIStyle.GetComponentStyle("TalentTreeAvailable") },
}.ToImmutableDictionary();
private readonly ImmutableDictionary<TalentTree.TalentTreeStageState, Color> talentStageBackgroundColors = new Dictionary<TalentTree.TalentTreeStageState, Color>
{
{ TalentTree.TalentTreeStageState.Invalid, new Color(48,48,48,255) },
{ TalentTree.TalentTreeStageState.Locked, new Color(48,48,48,255) },
{ TalentTree.TalentTreeStageState.Unlocked, new Color(24,37,31,255) },
{ TalentTree.TalentTreeStageState.Available, new Color(50,47,33,255) },
{ TalentTree.TalentTreeStageState.Highlighted, new Color(50,47,33,255) },
}.ToImmutableDictionary();
private void CreateCharacterInfo(GUIFrame infoFrame)
{
infoFrame.ClearChildren();
talentButtons.Clear();
talentCornerIcons.Clear();
GUIFrame background = new GUIFrame(new RectTransform(Vector2.One, infoFrame.RectTransform, Anchor.TopCenter), style: "GUIFrameListBox");
int padding = GUI.IntScale(15);
GUIFrame frame = new GUIFrame(new RectTransform(new Point(background.Rect.Width - padding, background.Rect.Height - padding), infoFrame.RectTransform, Anchor.Center), style: null);
GUIFrame content = new GUIFrame(new RectTransform(new Vector2(0.98f), frame.RectTransform, Anchor.Center), style: null);
GUIFrame characterSettingsFrame = null;
GUILayoutGroup characterLayout = null;
if (!(GameMain.NetworkMember is null))
{
characterSettingsFrame = new GUIFrame(new RectTransform(Vector2.One, frame.RectTransform), style: null) { Visible = false };
characterLayout = new GUILayoutGroup(new RectTransform(Vector2.One, characterSettingsFrame.RectTransform));
GUIFrame containerFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.9f), characterLayout.RectTransform), style: null);
GUIFrame playerFrame = new GUIFrame(new RectTransform(new Vector2(0.9f, 0.7f), containerFrame.RectTransform, Anchor.Center), style: null);
GameMain.NetLobbyScreen.CreatePlayerFrame(playerFrame, alwaysAllowEditing: true, createPendingText: false);
}
Character controlledCharacter = Character.Controlled;
CharacterInfo info = controlledCharacter?.Info ?? GameMain.Client?.CharacterInfo;
if (info == null) { return; }
Job job = info.Job;
GUILayoutGroup contentLayout = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f), content.RectTransform, anchor: Anchor.Center), childAnchor: Anchor.TopCenter)
{
AbsoluteSpacing = GUI.IntScale(10),
Stretch = true
};
GUILayoutGroup topLayout = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.3f), contentLayout.RectTransform, Anchor.Center), isHorizontal: true);
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, 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))
{
AbsoluteSpacing = GUI.IntScale(5),
CanBeFocused = true
};
GUITextBlock nameBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), nameLayout.RectTransform), info.Name, font: GUIStyle.SubHeadingFont);
if (!info.OmitJobInMenus)
{
nameBlock.TextColor = job.Prefab.UIColor;
GUITextBlock jobBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), nameLayout.RectTransform), job.Name, font: GUIStyle.SmallFont) { TextColor = job.Prefab.UIColor };
}
if (info.PersonalityTrait != null)
{
LocalizedString traitString = TextManager.AddPunctuation(':', TextManager.Get("PersonalityTrait"), info.PersonalityTrait.DisplayName);
Vector2 traitSize = GUIStyle.SmallFont.MeasureString(traitString);
GUITextBlock traitBlock = new GUITextBlock(new RectTransform(Vector2.One, nameLayout.RectTransform), traitString, font: GUIStyle.SmallFont);
traitBlock.RectTransform.NonScaledSize = traitSize.Pad(traitBlock.Padding).ToPoint();
}
IEnumerable<TalentPrefab> talentsOutsideTree = info.GetUnlockedTalentsOutsideTree().Select(e => TalentPrefab.TalentPrefabs.Find(c => c.Identifier == e));
if (talentsOutsideTree.Count() > 0)
{
//spacing
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.05f), nameLayout.RectTransform), style: null);
GUILayoutGroup extraTalentLayout = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.3f), 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.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)
{
AutoHideScrollBar = false,
ResizeContentToMakeSpaceForScrollBar = false
};
extraTalentList.ScrollBar.RectTransform.SetPosition(Anchor.BottomCenter, Pivot.TopCenter);
extraTalentList.RectTransform.MinSize = new Point(0, GUI.IntScale(65));
extraTalentLayout.Recalculate();
extraTalentList.ForceLayoutRecalculation();
foreach (var extraTalent in talentsOutsideTree)
{
var img = new GUIImage(new RectTransform(new Point(extraTalentList.Content.Rect.Height), extraTalentList.Content.RectTransform), sprite: extraTalent.Icon, scaleToFit: true)
{
ToolTip = RichString.Rich($"‖color:{Color.White.ToStringHex()}‖{extraTalent.DisplayName}‖color:end‖" + "\n\n" + extraTalent.Description),
Color = GUIStyle.Green
};
img.RectTransform.SizeChanged += () =>
{
img.RectTransform.MaxSize = new Point(img.Rect.Height);
};
}
}
GUILayoutGroup skillLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.45f, 1f), topLayout.RectTransform), childAnchor: Anchor.TopRight)
{
AbsoluteSpacing = GUI.IntScale(5),
Stretch = true
};
GUITextBlock skillBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), skillLayout.RectTransform), TextManager.Get("skills"), font: GUIStyle.SubHeadingFont);
skillListBox = new GUIListBox(new RectTransform(new Vector2(1f, 1f - skillBlock.RectTransform.RelativeSize.Y), skillLayout.RectTransform), style: null);
CreateSkillList(controlledCharacter, info, skillListBox);
new GUIFrame(new RectTransform(new Vector2(1f, 1f), contentLayout.RectTransform), style: "HorizontalLine");
GUIListBox talentTreeListBox = new GUIListBox(new RectTransform(new Vector2(1f, 0.6f), contentLayout.RectTransform, Anchor.TopCenter), isHorizontal: true, style: null);
if (controlledCharacter == null)
{
talentTreeListBox.Enabled = false;
}
else
{
if (!TalentTree.JobTalentTrees.TryGet(info.Job.Prefab.Identifier, out TalentTree talentTree)) { return; }
selectedTalents = info.GetUnlockedTalentsInTree().ToList();
List<GUITextBlock> subTreeNames = new List<GUITextBlock>();
foreach (var subTree in talentTree.TalentSubTrees)
{
GUIFrame subTreeFrame = new GUIFrame(new RectTransform(new Vector2(0.333f, 1f), talentTreeListBox.Content.RectTransform, anchor: Anchor.TopLeft), style: null);
GUILayoutGroup subTreeLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1f, 1f), subTreeFrame.RectTransform, Anchor.Center), false, childAnchor: Anchor.TopCenter);
GUIFrame subtreeTitleFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.111f), subTreeLayoutGroup.RectTransform, anchor: Anchor.TopCenter), style: null);
int elementPadding = GUI.IntScale(8);
Point headerSize = subtreeTitleFrame.RectTransform.NonScaledSize;
GUIFrame subTreeTitleBackground = new GUIFrame(new RectTransform(new Point(headerSize.X - elementPadding, headerSize.Y), subtreeTitleFrame.RectTransform, anchor: Anchor.Center), style: "SubtreeHeader");
subTreeNames.Add(new GUITextBlock(new RectTransform(Vector2.One, subTreeTitleBackground.RectTransform, anchor: Anchor.TopCenter), subTree.DisplayName, font: GUIStyle.SubHeadingFont, textAlignment: Alignment.Center));
for (int i = 0; i < 4; i++)
{
GUIFrame talentOptionFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.222f), subTreeLayoutGroup.RectTransform, anchor: Anchor.TopCenter), style: null);
Point talentFrameSize = talentOptionFrame.RectTransform.NonScaledSize;
GUIFrame talentBackground = new GUIFrame(new RectTransform(new Point(talentFrameSize.X - elementPadding, talentFrameSize.Y - elementPadding), talentOptionFrame.RectTransform, anchor: Anchor.Center), style: "TalentBackground")
{
Color = talentStageBackgroundColors[TalentTree.TalentTreeStageState.Locked]
};
GUIFrame talentBackgroundHighlight = new GUIFrame(new RectTransform(Vector2.One, talentBackground.RectTransform, anchor: Anchor.Center), style: "TalentBackgroundGlow") { Visible = false };
GUIImage cornerIcon = new GUIImage(new RectTransform(new Vector2(0.2f), talentOptionFrame.RectTransform, anchor: Anchor.BottomRight, scaleBasis: ScaleBasis.BothHeight) { MaxSize = new Point(16) }, style: null)
{
CanBeFocused = false,
Color = talentStageBackgroundColors[TalentTree.TalentTreeStageState.Locked]
};
Point iconSize = cornerIcon.RectTransform.NonScaledSize;
cornerIcon.RectTransform.AbsoluteOffset = new Point(iconSize.X / 2, iconSize.Y / 2);
if (subTree.TalentOptionStages.Length <= i) { continue; }
TalentOption talentOption = subTree.TalentOptionStages[i];
GUILayoutGroup talentOptionCenterGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.75f, 0.7f), talentOptionFrame.RectTransform, Anchor.Center), childAnchor: Anchor.CenterLeft);
GUILayoutGroup talentOptionLayoutGroup = new GUILayoutGroup(new RectTransform(Vector2.One, talentOptionCenterGroup.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft) { Stretch = true };
foreach (Identifier talentId in talentOption.TalentIdentifiers.OrderBy(t => t))
{
if (!TalentPrefab.TalentPrefabs.TryGet(talentId, out TalentPrefab talent)) { continue; }
GUIFrame talentFrame = new GUIFrame(new RectTransform(Vector2.One, talentOptionLayoutGroup.RectTransform), style: null)
{
CanBeFocused = false
};
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)
{
ToolTip = RichString.Rich($"‖color:{Color.White.ToStringHex()}‖{talent.DisplayName}‖color:end‖" + "\n\n" + talent.Description),
UserData = talent.Identifier,
PressedColor = pressedColor,
Enabled = controlledCharacter != null,
OnClicked = (button, userData) =>
{
// deselect other buttons in tier by removing their selected talents from pool
foreach (GUIButton guiButton in talentOptionLayoutGroup.GetAllChildren<GUIButton>())
{
if (guiButton.UserData is Identifier otherTalentIdentifier && guiButton != button)
{
if (!controlledCharacter.HasTalent(otherTalentIdentifier))
{
selectedTalents.Remove(otherTalentIdentifier);
}
}
}
Identifier talentIdentifier = (Identifier)userData;
if (TalentTree.IsViableTalentForCharacter(controlledCharacter, talentIdentifier, selectedTalents))
{
if (!selectedTalents.Contains(talentIdentifier))
{
selectedTalents.Add(talentIdentifier);
}
}
else if (!controlledCharacter.HasTalent(talentIdentifier))
{
selectedTalents.Remove(talentIdentifier);
}
UpdateTalentInfo();
return true;
},
};
talentButton.Color = talentButton.HoverColor = talentButton.PressedColor = talentButton.SelectedColor = talentButton.DisabledColor = Color.Transparent;
GUIComponent iconImage;
if (talent.Icon is null)
{
iconImage = new GUITextBlock(new RectTransform(Vector2.One, talentButton.RectTransform, anchor: Anchor.Center), text: "???", font: GUIStyle.LargeFont, textAlignment: Alignment.Center, style: null)
{
OutlineColor = GUIStyle.Red,
TextColor = GUIStyle.Red,
PressedColor = unselectableColor,
DisabledColor = unselectableColor,
CanBeFocused = false,
};
}
else
{
iconImage = new GUIImage(new RectTransform(Vector2.One, talentButton.RectTransform, anchor: Anchor.Center), sprite: talent.Icon, scaleToFit: true)
{
PressedColor = unselectableColor,
DisabledColor = unselectableColor * 0.5f,
CanBeFocused = false,
};
}
iconImage.Enabled = talentButton.Enabled;
talentButtons.Add((talentButton, iconImage));
}
talentCornerIcons.Add((subTree.Identifier, i, cornerIcon, talentBackground, talentBackgroundHighlight));
}
}
GUITextBlock.AutoScaleAndNormalize(subTreeNames);
GUILayoutGroup bottomLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.07f), contentLayout.RectTransform, Anchor.TopCenter), isHorizontal: true)
{
RelativeSpacing = 0.01f,
Stretch = true
};
GUILayoutGroup experienceLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.59f, 1f), bottomLayout.RectTransform));
GUIFrame experienceBarFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.5f), experienceLayout.RectTransform), style: null);
experienceBar = new GUIProgressBar(new RectTransform(new Vector2(1f, 1f), experienceBarFrame.RectTransform, Anchor.CenterLeft),
barSize: info.GetProgressTowardsNextLevel(), color: GUIStyle.Green)
{
IsHorizontal = true,
};
experienceText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), experienceBarFrame.RectTransform, anchor: Anchor.Center), "", font: GUIStyle.Font, textAlignment: Alignment.CenterRight)
{
Shadow = true,
ToolTip = TextManager.Get("experiencetooltip")
};
talentPointText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), experienceLayout.RectTransform, anchor: Anchor.Center), "", font: GUIStyle.SubHeadingFont, textAlignment: Alignment.CenterRight) { AutoScaleVertical = true };
talentResetButton = new GUIButton(new RectTransform(new Vector2(0.19f, 1f), bottomLayout.RectTransform), text: TextManager.Get("reset"), style: "GUIButtonFreeScale")
{
OnClicked = ResetTalentSelection
};
talentApplyButton = new GUIButton(new RectTransform(new Vector2(0.19f, 1f), bottomLayout.RectTransform), text: TextManager.Get("applysettingsbutton"), style: "GUIButtonFreeScale")
{
OnClicked = ApplyTalentSelection,
};
GUITextBlock.AutoScaleAndNormalize(talentResetButton.TextBlock, talentApplyButton.TextBlock);
}
if (!(GameMain.NetworkMember is null))
{
GUIButton newCharacterBox = new GUIButton(new RectTransform(new Vector2(0.5f, 0.2f), skillLayout.RectTransform, Anchor.BottomRight),
text: GameMain.NetLobbyScreen.CampaignCharacterDiscarded ? TextManager.Get("settings") : TextManager.Get("createnew"), style: "GUIButtonSmall")
{
IgnoreLayoutGroups = false
};
newCharacterBox.TextBlock.AutoScaleHorizontal = true;
newCharacterBox.OnClicked = (button, o) =>
{
if (!GameMain.NetLobbyScreen.CampaignCharacterDiscarded)
{
GameMain.NetLobbyScreen.TryDiscardCampaignCharacter(() =>
{
newCharacterBox.Text = TextManager.Get("settings");
if (pendingChangesFrame != null)
{
NetLobbyScreen.CreateChangesPendingFrame(pendingChangesFrame);
}
OpenMenu();
});
return true;
}
OpenMenu();
return true;
void OpenMenu()
{
characterSettingsFrame!.Visible = true;
content.Visible = false;
}
};
if (!(characterLayout is null))
{
GUILayoutGroup characterCloseButtonLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.1f), characterLayout.RectTransform), childAnchor: Anchor.BottomCenter);
new GUIButton(new RectTransform(new Vector2(0.4f, 1f), characterCloseButtonLayout.RectTransform), TextManager.Get("ApplySettingsButton")) //TODO: Is this text appropriate for this circumstance for all languages?
{
OnClicked = (button, o) =>
{
GameMain.Client?.SendCharacterInfo(GameMain.Client.PendingName);
characterSettingsFrame!.Visible = false;
content.Visible = true;
return true;
}
};
}
}
UpdateTalentInfo();
}
private void CreateSkillList(Character character, CharacterInfo info, GUIListBox parent)
{ {
parent.Content.ClearChildren(); parent.Content.ClearChildren();
List<GUITextBlock> skillNames = new List<GUITextBlock>(); List<GUITextBlock> skillNames = new List<GUITextBlock>();
@@ -2154,10 +1802,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),
@@ -2168,123 +1816,17 @@ 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();
GUITextBlock.AutoScaleAndNormalize(skillNames); GUITextBlock.AutoScaleAndNormalize(skillNames);
} }
private void UpdateTalentInfo()
{
Character controlledCharacter = Character.Controlled;
if (controlledCharacter?.Info == null) { return; }
if (SelectedTab != InfoFrameTab.Talents) { return; }
bool unlockedAllTalents = controlledCharacter.HasUnlockedAllTalents();
if (unlockedAllTalents)
{
experienceText.Text = string.Empty;
experienceBar.BarSize = 1f;
}
else
{
experienceText.Text = $"{controlledCharacter.Info.ExperiencePoints - controlledCharacter.Info.GetExperienceRequiredForCurrentLevel()} / {controlledCharacter.Info.GetExperienceRequiredToLevelUp() - controlledCharacter.Info.GetExperienceRequiredForCurrentLevel()}";
experienceBar.BarSize = controlledCharacter.Info.GetProgressTowardsNextLevel();
}
selectedTalents = TalentTree.CheckTalentSelection(controlledCharacter, selectedTalents);
string pointsLeft = controlledCharacter.Info.GetAvailableTalentPoints().ToString();
int talentCount = selectedTalents.Count - controlledCharacter.Info.GetUnlockedTalentsInTree().Count();
if (unlockedAllTalents)
{
talentPointText.SetRichText($"‖color:{XMLExtensions.ToStringHex(Color.Gray)}‖{TextManager.Get("talentmenu.alltalentsunlocked")}‖color:end‖");
}
else if (talentCount > 0)
{
string pointsUsed = $"‖color:{XMLExtensions.ColorToString(GUIStyle.Red)}‖{-talentCount}‖color:end‖";
LocalizedString localizedString = TextManager.GetWithVariables("talentmenu.points.spending", ("[amount]", pointsLeft), ("[used]", pointsUsed));
talentPointText.SetRichText(localizedString);
}
else
{
talentPointText.SetRichText(TextManager.GetWithVariable("talentmenu.points", "[amount]", pointsLeft));
}
foreach (var (talentTree, index, icon, frame, glow) in talentCornerIcons)
{
TalentTree.TalentTreeStageState state = TalentTree.GetTalentOptionStageState(controlledCharacter, talentTree, index, selectedTalents);
GUIComponentStyle newStyle = talentStageStyles[state];
icon.ApplyStyle(newStyle);
icon.Color = newStyle.Color;
frame.Color = talentStageBackgroundColors[state];
glow.Visible = state == TalentTree.TalentTreeStageState.Highlighted;
}
foreach (var talentButton in talentButtons)
{
Identifier talentIdentifier = (Identifier)talentButton.button.UserData;
bool unselectable = !TalentTree.IsViableTalentForCharacter(controlledCharacter, talentIdentifier, selectedTalents) || controlledCharacter.HasTalent(talentIdentifier);
Color newTalentColor = unselectable ? unselectableColor : unselectedColor;
Color hoverColor = Color.White;
if (controlledCharacter.HasTalent(talentIdentifier))
{
newTalentColor = GUIStyle.Green;
}
else if (selectedTalents.Contains(talentIdentifier))
{
newTalentColor = GUIStyle.Orange;
hoverColor = Color.Lerp(GUIStyle.Orange, Color.White, 0.7f);
}
talentButton.icon.Color = newTalentColor;
talentButton.icon.HoverColor = hoverColor;
}
CreateSkillList(controlledCharacter, controlledCharacter.Info, skillListBox);
}
private void ApplyTalents(Character controlledCharacter)
{
selectedTalents = TalentTree.CheckTalentSelection(controlledCharacter, selectedTalents);
foreach (Identifier talent in selectedTalents)
{
controlledCharacter.GiveTalent(talent);
if (GameMain.Client != null)
{
GameMain.Client.CreateEntityEvent(controlledCharacter, new Character.UpdateTalentsEventData());
}
}
selectedTalents = controlledCharacter.Info.GetUnlockedTalentsInTree().ToList();
UpdateTalentInfo();
}
private bool ApplyTalentSelection(GUIButton guiButton, object userData)
{
Character controlledCharacter = Character.Controlled;
ApplyTalents(controlledCharacter);
return true;
}
private bool ResetTalentSelection(GUIButton guiButton, object userData)
{
Character controlledCharacter = Character.Controlled;
if (controlledCharacter?.Info == null) { return false; }
selectedTalents = controlledCharacter.Info.GetUnlockedTalentsInTree().ToList();
UpdateTalentInfo();
return true;
}
public void OnExperienceChanged(Character character) public void OnExperienceChanged(Character character)
{ {
if (character != Character.Controlled) { return; } if (character != Character.Controlled) { return; }
UpdateTalentInfo(); talentMenu.UpdateTalentInfo();
} }
public void OnClose() public void OnClose()
@@ -0,0 +1,805 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Barotrauma.Extensions;
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using static Barotrauma.TalentTree;
using static Barotrauma.TalentTree.TalentStages;
namespace Barotrauma
{
internal readonly record struct TalentShowCaseButton(ImmutableHashSet<TalentButton> Buttons,
GUIComponent IconComponent);
internal readonly record struct TalentButton(GUIComponent IconComponent,
TalentPrefab Prefab)
{
public Identifier Identifier => Prefab.Identifier;
}
internal readonly record struct TalentCornerIcon(Identifier TalentTree,
int Index,
GUIImage IconComponent,
GUIFrame BackgroundComponent,
GUIFrame GlowComponent);
internal readonly struct TalentTreeStyle
{
public readonly GUIComponentStyle ComponentStyle;
public readonly Color Color;
public TalentTreeStyle(string componentStyle, Color color)
{
ComponentStyle = GUIStyle.GetComponentStyle(componentStyle);
Color = color;
}
}
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),
unselectableColor = new Color(100, 100, 100, 225),
pressedColor = new Color(60, 60, 60, 225),
lockedColor = new Color(48, 48, 48, 255),
unlockedColor = new Color(24, 37, 31, 255),
availableColor = new Color(50, 47, 33, 255);
private static readonly ImmutableDictionary<TalentStages, TalentTreeStyle> talentStageStyles =
new Dictionary<TalentStages, TalentTreeStyle>
{
[Invalid] = new TalentTreeStyle("TalentTreeLocked", lockedColor),
[Locked] = new TalentTreeStyle("TalentTreeLocked", lockedColor),
[Unlocked] = new TalentTreeStyle("TalentTreePurchased", unlockedColor),
[Available] = new TalentTreeStyle("TalentTreeUnlocked", availableColor),
[Highlighted] = new TalentTreeStyle("TalentTreeAvailable", availableColor)
}.ToImmutableDictionary();
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<TalentCornerIcon> talentCornerIcons = new HashSet<TalentCornerIcon>();
private HashSet<Identifier> selectedTalents = new HashSet<Identifier>();
private readonly Queue<Identifier> showCaseClosureQueue = new();
private GUIListBox? skillListBox;
private GUITextBlock? talentPointText;
private GUIProgressBar? experienceBar;
private GUITextBlock? experienceText;
private GUILayoutGroup? skillLayout;
private GUIButton? talentApplyButton,
talentResetButton;
public void CreateGUI(GUIFrame parent, Character? targetCharacter)
{
parent.ClearChildren();
talentButtons.Clear();
talentShowCaseButtons.Clear();
talentCornerIcons.Clear();
showCaseTalentFrames.Clear();
character = targetCharacter;
characterInfo = targetCharacter?.Info;
GUIFrame background = new GUIFrame(new RectTransform(Vector2.One, parent.RectTransform, Anchor.TopCenter), style: "GUIFrameListBox");
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 content = new GUIFrame(new RectTransform(new Vector2(0.98f), frame.RectTransform, Anchor.Center), style: null);
GUILayoutGroup contentLayout = new GUILayoutGroup(new RectTransform(Vector2.One, content.RectTransform, anchor: Anchor.Center), childAnchor: Anchor.TopCenter)
{
AbsoluteSpacing = GUI.IntScale(10),
Stretch = true
};
if (characterInfo is null) { return; }
CreateStatPanel(contentLayout, characterInfo);
new GUIFrame(new RectTransform(new Vector2(1f, 1f), contentLayout.RectTransform), style: "HorizontalLine");
if (JobTalentTrees.TryGet(characterInfo.Job.Prefab.Identifier, out TalentTree? talentTree))
{
CreateTalentMenu(contentLayout, characterInfo, talentTree!);
}
CreateFooter(contentLayout, characterInfo);
UpdateTalentInfo();
if (GameMain.NetworkMember != null && IsOwnCharacter(characterInfo))
{
CreateMultiplayerCharacterSettings(frame, content);
}
}
private void CreateMultiplayerCharacterSettings(GUIComponent parent, GUIComponent content)
{
if (skillLayout is null) { return; }
GUIFrame characterSettingsFrame = new GUIFrame(new RectTransform(Vector2.One, parent.RectTransform), style: null) { Visible = false };
GUILayoutGroup characterLayout = new GUILayoutGroup(new RectTransform(Vector2.One, characterSettingsFrame.RectTransform));
GUIFrame containerFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.9f), characterLayout.RectTransform), style: null);
GUIFrame playerFrame = new GUIFrame(new RectTransform(new Vector2(0.9f, 0.7f), containerFrame.RectTransform, Anchor.Center), style: null);
GameMain.NetLobbyScreen.CreatePlayerFrame(playerFrame, alwaysAllowEditing: true, createPendingText: false);
GUIButton newCharacterBox = new GUIButton(new RectTransform(new Vector2(0.5f, 0.2f), skillLayout.RectTransform, Anchor.BottomRight),
text: GameMain.NetLobbyScreen.CampaignCharacterDiscarded ? TextManager.Get("settings") : TextManager.Get("createnew"), style: "GUIButtonSmall")
{
IgnoreLayoutGroups = false,
TextBlock =
{
AutoScaleHorizontal = true
}
};
newCharacterBox.OnClicked = (button, o) =>
{
if (!GameMain.NetLobbyScreen.CampaignCharacterDiscarded)
{
GameMain.NetLobbyScreen.TryDiscardCampaignCharacter(() =>
{
newCharacterBox.Text = TextManager.Get("settings");
if (TabMenu.PendingChangesFrame != null)
{
NetLobbyScreen.CreateChangesPendingFrame(TabMenu.PendingChangesFrame);
}
OpenMenu();
});
return true;
}
OpenMenu();
return true;
void OpenMenu()
{
characterSettingsFrame!.Visible = true;
content.Visible = false;
}
};
GUILayoutGroup characterCloseButtonLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.1f), characterLayout.RectTransform), childAnchor: Anchor.BottomCenter);
new GUIButton(new RectTransform(new Vector2(0.4f, 1f), characterCloseButtonLayout.RectTransform), TextManager.Get("ApplySettingsButton")) //TODO: Is this text appropriate for this circumstance for all languages?
{
OnClicked = (button, o) =>
{
GameMain.Client?.SendCharacterInfo(GameMain.Client.PendingName);
characterSettingsFrame.Visible = false;
content.Visible = true;
return true;
}
};
}
private void CreateStatPanel(GUIComponent parent, CharacterInfo info)
{
Job job = info.Job;
GUILayoutGroup topLayout = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.3f), parent.RectTransform, Anchor.Center), isHorizontal: true);
new GUICustomComponent(new RectTransform(new Vector2(0.25f, 1f), topLayout.RectTransform), onDraw: (batch, component) =>
{
info.DrawPortrait(batch, component.Rect.Location.ToVector2(), Vector2.Zero, component.Rect.Width, false, false);
});
GUILayoutGroup nameLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.3f, 1f), topLayout.RectTransform))
{
AbsoluteSpacing = GUI.IntScale(5),
CanBeFocused = true
};
GUITextBlock nameBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), nameLayout.RectTransform), info.Name, font: GUIStyle.SubHeadingFont);
if (!info.OmitJobInMenus)
{
nameBlock.TextColor = job.Prefab.UIColor;
GUITextBlock jobBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), nameLayout.RectTransform), job.Name, font: GUIStyle.SmallFont) { TextColor = job.Prefab.UIColor };
}
if (info.PersonalityTrait != null)
{
LocalizedString traitString = TextManager.AddPunctuation(':', TextManager.Get("PersonalityTrait"), info.PersonalityTrait.DisplayName);
Vector2 traitSize = GUIStyle.SmallFont.MeasureString(traitString);
GUITextBlock traitBlock = new GUITextBlock(new RectTransform(Vector2.One, nameLayout.RectTransform), traitString, font: GUIStyle.SmallFont);
traitBlock.RectTransform.NonScaledSize = traitSize.Pad(traitBlock.Padding).ToPoint();
}
ImmutableHashSet<TalentPrefab?> talentsOutsideTree = info.GetUnlockedTalentsOutsideTree().Select(static e => TalentPrefab.TalentPrefabs.Find(c => c.Identifier == e)).ToImmutableHashSet();
if (talentsOutsideTree.Any())
{
//spacing
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.55f), nameLayout.RectTransform), childAnchor: Anchor.TopCenter);
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);
var extraTalentList = new GUIListBox(new RectTransform(new Vector2(0.9f, 0.7f), extraTalentLayout.RectTransform, anchor: Anchor.Center), isHorizontal: true)
{
AutoHideScrollBar = false,
ResizeContentToMakeSpaceForScrollBar = false
};
extraTalentList.ScrollBar.RectTransform.SetPosition(Anchor.BottomCenter, Pivot.TopCenter);
extraTalentLayout.Recalculate();
extraTalentList.ForceLayoutRecalculation();
foreach (var extraTalent in talentsOutsideTree)
{
if (extraTalent is null) { continue; }
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" + ToolBox.ExtendColorToPercentageSigns(extraTalent.Description.Value)),
Color = GUIStyle.Green
};
}
}
skillLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.45f, 1f), topLayout.RectTransform), childAnchor: Anchor.TopRight)
{
AbsoluteSpacing = GUI.IntScale(5),
Stretch = true
};
GUITextBlock skillBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), skillLayout.RectTransform), TextManager.Get("skills"), font: GUIStyle.SubHeadingFont);
skillListBox = new GUIListBox(new RectTransform(new Vector2(1f, 1f - skillBlock.RectTransform.RelativeSize.Y), skillLayout.RectTransform), style: null);
TabMenu.CreateSkillList(info.Character, info, skillListBox);
}
private void CreateTalentMenu(GUIComponent parent, CharacterInfo info, TalentTree tree)
{
GUIListBox mainList = new GUIListBox(new RectTransform(new Vector2(1f, 0.9f), parent.RectTransform, anchor: Anchor.TopCenter));
selectedTalents = info.GetUnlockedTalentsInTree().ToHashSet();
List<GUITextBlock> subTreeNames = new List<GUITextBlock>();
foreach (var subTree in tree.TalentSubTrees)
{
GUIListBox talentList;
GUIComponent talentParent;
Vector2 treeSize;
switch (subTree.Type)
{
case TalentTreeType.Primary:
talentList = mainList;
treeSize = new Vector2(1f, 0.5f);
break;
case TalentTreeType.Specialization:
talentList = GetSpecializationList();
treeSize = new Vector2(0.333f, 1f);
break;
default:
throw new ArgumentOutOfRangeException($"Invalid TalentTreeType \"{subTree.Type}\"");
}
talentParent = talentList.Content;
GUILayoutGroup subTreeLayoutGroup = new GUILayoutGroup(new RectTransform(treeSize, talentParent.RectTransform), isHorizontal: false, childAnchor: Anchor.TopCenter)
{
Stretch = true
};
if (subTree.Type != TalentTreeType.Primary)
{
GUIFrame subtreeTitleFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.05f), subTreeLayoutGroup.RectTransform, anchor: Anchor.TopCenter)
{ MinSize = new Point(0, GUI.IntScale(30)) }, style: null);
subtreeTitleFrame.RectTransform.IsFixedSize = true;
int elementPadding = GUI.IntScale(8);
Point headerSize = subtreeTitleFrame.RectTransform.NonScaledSize;
GUIFrame subTreeTitleBackground = new GUIFrame(new RectTransform(new Point(headerSize.X - elementPadding, headerSize.Y), subtreeTitleFrame.RectTransform, anchor: Anchor.Center), style: "SubtreeHeader");
subTreeNames.Add(new GUITextBlock(new RectTransform(Vector2.One, subTreeTitleBackground.RectTransform, anchor: Anchor.TopCenter), subTree.DisplayName, font: GUIStyle.SubHeadingFont, textAlignment: Alignment.Center));
}
int optionAmount = subTree.TalentOptionStages.Length;
for (int i = 0; i < optionAmount; i++)
{
TalentOption option = subTree.TalentOptionStages[i];
CreateTalentOption(subTreeLayoutGroup, subTree, i, option, info);
}
subTreeLayoutGroup.RectTransform.Resize(new Point(subTreeLayoutGroup.Rect.Width,
subTreeLayoutGroup.Children.Sum(c => c.Rect.Height + subTreeLayoutGroup.AbsoluteSpacing)));
subTreeLayoutGroup.RectTransform.MinSize = new Point(subTreeLayoutGroup.Rect.Width, subTreeLayoutGroup.Rect.Height);
subTreeLayoutGroup.Recalculate();
if (subTree.Type == TalentTreeType.Specialization)
{
talentList.RectTransform.Resize(new Point(talentList.Rect.Width, Math.Max(subTreeLayoutGroup.Rect.Height, talentList.Rect.Height)));
talentList.RectTransform.MinSize = new Point(0, talentList.Rect.Height);
}
}
var specializationList = GetSpecializationList();
GetSpecializationList().RectTransform.Resize(new Point(specializationList.Content.Children.Sum(static c => c.Rect.Width), specializationList.Rect.Height));
GUITextBlock.AutoScaleAndNormalize(subTreeNames);
GUIListBox GetSpecializationList()
{
if (mainList.Content.Children.LastOrDefault() is GUIListBox specList)
{
return specList;
}
GUIListBox newSpecializationList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.5f), mainList.Content.RectTransform, Anchor.TopCenter), isHorizontal: true, style: null);
return newSpecializationList;
}
}
private void CreateTalentOption(GUIComponent parent, TalentSubTree subTree, int index, TalentOption talentOption, CharacterInfo info)
{
int elementPadding = GUI.IntScale(8);
GUIFrame talentOptionFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.01f), parent.RectTransform, anchor: Anchor.TopCenter)
{ MinSize = new Point(0, GUI.IntScale(65)) }, style: null);
Point talentFrameSize = talentOptionFrame.RectTransform.NonScaledSize;
GUIFrame talentBackground = new GUIFrame(new RectTransform(new Point(talentFrameSize.X - elementPadding, talentFrameSize.Y - elementPadding), talentOptionFrame.RectTransform, anchor: Anchor.Center),
style: "TalentBackground")
{
Color = talentStageStyles[Locked].Color
};
GUIFrame talentBackgroundHighlight = new GUIFrame(new RectTransform(Vector2.One, talentBackground.RectTransform, anchor: Anchor.Center), style: "TalentBackgroundGlow") { Visible = false };
GUIImage cornerIcon = new GUIImage(new RectTransform(new Vector2(0.2f), talentOptionFrame.RectTransform, anchor: Anchor.BottomRight, scaleBasis: ScaleBasis.BothHeight) { MaxSize = new Point(16) }, style: null)
{
CanBeFocused = false,
Color = talentStageStyles[Locked].Color
};
Point iconSize = cornerIcon.RectTransform.NonScaledSize;
cornerIcon.RectTransform.AbsoluteOffset = new Point(iconSize.X / 2, iconSize.Y / 2);
GUILayoutGroup talentOptionCenterGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.6f, 0.9f), talentOptionFrame.RectTransform, Anchor.Center), childAnchor: Anchor.CenterLeft);
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<TalentButton> buttonsToAdd = new();
Dictionary<GUILayoutGroup, ImmutableHashSet<Identifier>> showCaseTalentParents = new();
Dictionary<Identifier, GUIComponent> showCaseTalentButtonsToAdd = new();
foreach (var (showCaseTalentIdentifier, talents) in talentOption.ShowCaseTalents)
{
talentOptionIdentifiers.Add(showCaseTalentIdentifier);
Point parentSize = talentBackground.RectTransform.NonScaledSize;
GUIFrame showCaseFrame = new GUIFrame(new RectTransform(new Point((int)(parentSize.X / 3f * (talents.Count - 1)), parentSize.Y)), style: "GUITooltip")
{
UserData = showCaseTalentIdentifier,
IgnoreLayoutGroups = true,
Visible = false
};
GUILayoutGroup showcaseCenterGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.7f), showCaseFrame.RectTransform, Anchor.Center), childAnchor: Anchor.CenterLeft);
GUILayoutGroup showcaseLayout = new GUILayoutGroup(new RectTransform(Vector2.One, showcaseCenterGroup.RectTransform), isHorizontal: true) { Stretch = true };
showCaseTalentParents.Add(showcaseLayout, talents);
showCaseTalentFrames.Add(showCaseFrame);
}
foreach (Identifier talentId in talentOptionIdentifiers)
{
if (!TalentPrefab.TalentPrefabs.TryGet(talentId, out TalentPrefab? talent)) { continue; }
bool isShowCaseTalent = talentOption.ShowCaseTalents.ContainsKey(talentId);
GUIComponent talentParent = talentOptionLayoutGroup;
foreach (var (key, value) in showCaseTalentParents)
{
if (value.Contains(talentId))
{
talentParent = key;
break;
}
}
GUIFrame talentFrame = new GUIFrame(new RectTransform(Vector2.One, talentParent.RectTransform), style: null)
{
CanBeFocused = false
};
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)
{
ToolTip = RichString.Rich($"‖color:{Color.White.ToStringHex()}‖{talent.DisplayName}‖color:end‖" + "\n\n" + ToolBox.ExtendColorToPercentageSigns(talent.Description.Value)),
UserData = talent.Identifier,
PressedColor = pressedColor,
Enabled = info.Character != null,
OnClicked = (button, userData) =>
{
if (isShowCaseTalent)
{
foreach (GUIComponent component in showCaseTalentFrames)
{
if (component.UserData is Identifier showcaseIdentifier && showcaseIdentifier == talentId)
{
component.RectTransform.ScreenSpaceOffset = new Point((int)(button.Rect.Location.X - component.Rect.Width / 2f + button.Rect.Width / 2f), button.Rect.Location.Y - component.Rect.Height);
component.Visible = true;
}
else
{
component.Visible = false;
}
}
return true;
}
if (character is null) { return false; }
if (talentOption.MaxChosenTalents is 1)
{
// deselect other buttons in tier by removing their selected talents from pool
foreach (Identifier identifier in selectedTalents)
{
if (character.HasTalent(identifier) || identifier == talentId) { continue; }
if (talentOptionIdentifiers.Contains(identifier))
{
selectedTalents.Remove(identifier);
}
}
}
Identifier talentIdentifier = (Identifier)userData;
if (IsViableTalentForCharacter(info.Character, talentIdentifier, selectedTalents))
{
if (!selectedTalents.Contains(talentIdentifier))
{
selectedTalents.Add(talentIdentifier);
}
else
{
selectedTalents.Remove(talentIdentifier);
}
}
else if (!character.HasTalent(talentIdentifier))
{
selectedTalents.Remove(talentIdentifier);
}
UpdateTalentInfo();
return true;
},
};
talentButton.Color = talentButton.HoverColor = talentButton.PressedColor = talentButton.SelectedColor = talentButton.DisabledColor = Color.Transparent;
GUIComponent iconImage;
if (talent.Icon is null)
{
iconImage = new GUITextBlock(new RectTransform(Vector2.One, talentButton.RectTransform, anchor: Anchor.Center), text: "???", font: GUIStyle.LargeFont, textAlignment: Alignment.Center, style: null)
{
OutlineColor = GUIStyle.Red,
TextColor = GUIStyle.Red,
PressedColor = unselectableColor,
DisabledColor = unselectableColor,
CanBeFocused = false,
};
}
else
{
iconImage = new GUIImage(new RectTransform(Vector2.One, talentButton.RectTransform, anchor: Anchor.Center), sprite: talent.Icon, scaleToFit: true)
{
Color = talent.ColorOverride.TryUnwrap(out Color color) ? color : Color.White,
PressedColor = unselectableColor,
DisabledColor = unselectableColor * 0.5f,
CanBeFocused = false,
};
}
iconImage.Enabled = talentButton.Enabled;
if (isShowCaseTalent)
{
showCaseTalentButtonsToAdd.Add(talentId, iconImage);
continue;
}
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));
}
private void CreateFooter(GUIComponent parent, CharacterInfo info)
{
GUILayoutGroup bottomLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.07f), parent.RectTransform, Anchor.TopCenter), isHorizontal: true)
{
RelativeSpacing = 0.01f,
Stretch = true
};
GUILayoutGroup experienceLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.59f, 1f), bottomLayout.RectTransform));
GUIFrame experienceBarFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.5f), experienceLayout.RectTransform), style: null);
experienceBar = new GUIProgressBar(new RectTransform(new Vector2(1f, 1f), experienceBarFrame.RectTransform, Anchor.CenterLeft),
barSize: info.GetProgressTowardsNextLevel(), color: GUIStyle.Green)
{
IsHorizontal = true,
};
experienceText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), experienceBarFrame.RectTransform, anchor: Anchor.Center), "", font: GUIStyle.Font, textAlignment: Alignment.CenterRight)
{
Shadow = true,
ToolTip = TextManager.Get("experiencetooltip")
};
talentPointText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), experienceLayout.RectTransform, anchor: Anchor.Center), "", font: GUIStyle.SubHeadingFont, textAlignment: Alignment.CenterRight)
{ AutoScaleVertical = true };
talentResetButton = new GUIButton(new RectTransform(new Vector2(0.19f, 1f), bottomLayout.RectTransform), text: TextManager.Get("reset"), style: "GUIButtonFreeScale")
{
OnClicked = ResetTalentSelection
};
talentApplyButton = new GUIButton(new RectTransform(new Vector2(0.19f, 1f), bottomLayout.RectTransform), text: TextManager.Get("applysettingsbutton"), style: "GUIButtonFreeScale")
{
OnClicked = ApplyTalentSelection,
};
GUITextBlock.AutoScaleAndNormalize(talentResetButton.TextBlock, talentApplyButton.TextBlock);
}
private bool ResetTalentSelection(GUIButton guiButton, object userData)
{
if (characterInfo is null) { return false; }
selectedTalents = characterInfo.GetUnlockedTalentsInTree().ToHashSet();
UpdateTalentInfo();
return true;
}
private void ApplyTalents(Character controlledCharacter)
{
foreach (Identifier talent in CheckTalentSelection(controlledCharacter, selectedTalents))
{
controlledCharacter.GiveTalent(talent);
if (GameMain.Client != null)
{
GameMain.Client.CreateEntityEvent(controlledCharacter, new Character.UpdateTalentsEventData());
}
}
UpdateTalentInfo();
}
private bool ApplyTalentSelection(GUIButton guiButton, object userData)
{
if (character is null) { return false; }
ApplyTalents(character);
return true;
}
public void UpdateTalentInfo()
{
if (character is null || characterInfo is null) { return; }
bool unlockedAllTalents = character.HasUnlockedAllTalents();
if (experienceBar is null || experienceText is null) { return; }
if (unlockedAllTalents)
{
experienceText.Text = string.Empty;
experienceBar.BarSize = 1f;
}
else
{
experienceText.Text = $"{characterInfo.ExperiencePoints - characterInfo.GetExperienceRequiredForCurrentLevel()} / {characterInfo.GetExperienceRequiredToLevelUp() - characterInfo.GetExperienceRequiredForCurrentLevel()}";
experienceBar.BarSize = characterInfo.GetProgressTowardsNextLevel();
}
selectedTalents = CheckTalentSelection(character, selectedTalents).ToHashSet();
string pointsLeft = characterInfo.GetAvailableTalentPoints().ToString();
int talentCount = selectedTalents.Count - characterInfo.GetUnlockedTalentsInTree().Count();
if (unlockedAllTalents)
{
talentPointText?.SetRichText($"‖color:{Color.Gray.ToStringHex()}‖{TextManager.Get("talentmenu.alltalentsunlocked")}‖color:end‖");
}
else if (talentCount > 0)
{
string pointsUsed = $"‖color:{XMLExtensions.ToStringHex(GUIStyle.Red)}‖{-talentCount}‖color:end‖";
LocalizedString localizedString = TextManager.GetWithVariables("talentmenu.points.spending", ("[amount]", pointsLeft), ("[used]", pointsUsed));
talentPointText?.SetRichText(localizedString);
}
else
{
talentPointText?.SetRichText(TextManager.GetWithVariable("talentmenu.points", "[amount]", pointsLeft));
}
foreach (TalentCornerIcon cornerIcon in talentCornerIcons)
{
TalentStages state = GetTalentOptionStageState(character, cornerIcon.TalentTree, cornerIcon.Index, selectedTalents);
TalentTreeStyle style = talentStageStyles[state];
GUIComponentStyle newStyle = style.ComponentStyle;
cornerIcon.IconComponent.ApplyStyle(newStyle);
cornerIcon.IconComponent.Color = newStyle.Color;
cornerIcon.BackgroundComponent.Color = style.Color;
cornerIcon.GlowComponent.Visible = state == Highlighted;
}
foreach (TalentButton talentButton in talentButtons)
{
TalentStages stage = GetTalentState(character, talentButton.Identifier, selectedTalents);
ApplyTalentIconColor(stage, talentButton.IconComponent, talentButton.Prefab.ColorOverride);
}
foreach (TalentShowCaseButton showCaseTalentButton in talentShowCaseButtons)
{
TalentStages collectiveTalentStage = GetCollectiveTalentState(character, showCaseTalentButton.Buttons, selectedTalents);
ApplyTalentIconColor(collectiveTalentStage, showCaseTalentButton.IconComponent, Option<Color>.None());
}
if (skillListBox is null) { return; }
TabMenu.CreateSkillList(character, characterInfo, skillListBox);
static TalentStages GetTalentState(Character character, Identifier talentIdentifier, IReadOnlyCollection<Identifier> selectedTalents)
{
bool unselectable = !IsViableTalentForCharacter(character, talentIdentifier, selectedTalents) || character.HasTalent(talentIdentifier);
TalentStages stage = unselectable ? Locked : Available;
if (unselectable)
{
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.All(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()
{
if (characterInfo is null || talentResetButton is null || talentApplyButton is null) { return; }
int talentCount = selectedTalents.Count - characterInfo.GetUnlockedTalentsInTree().Count();
talentResetButton.Enabled = talentApplyButton.Enabled = talentCount > 0;
if (talentApplyButton.Enabled && talentApplyButton.FlashTimer <= 0.0f)
{
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);
}
}
}
@@ -2,6 +2,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics; using System.Diagnostics;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
@@ -78,6 +79,8 @@ namespace Barotrauma
private PlayerBalanceElement? playerBalanceElement; private PlayerBalanceElement? playerBalanceElement;
private static ImmutableHashSet<Character> characterList = ImmutableHashSet<Character>.Empty;
/// <summary> /// <summary>
/// While set to true any call to <see cref="RefreshUpgradeList"/> will cause the buy button to be disabled and to not update the prices. /// While set to true any call to <see cref="RefreshUpgradeList"/> will cause the buy button to be disabled and to not update the prices.
/// This is to prevent us from buying another upgrade before the server has given us the new prices and causing potential syncing issues. /// This is to prevent us from buying another upgrade before the server has given us the new prices and causing potential syncing issues.
@@ -93,6 +96,7 @@ namespace Barotrauma
public UpgradeStore(CampaignUI campaignUI, GUIComponent parent) public UpgradeStore(CampaignUI campaignUI, GUIComponent parent)
{ {
WaitForServerUpdate = false; WaitForServerUpdate = false;
characterList = GameSession.GetSessionCrewCharacters(CharacterType.Both);
this.campaignUI = campaignUI; this.campaignUI = campaignUI;
GUIFrame upgradeFrame = new GUIFrame(rectT(1, 1, parent, Anchor.Center), style: "OuterGlow", color: Color.Black * 0.7f) GUIFrame upgradeFrame = new GUIFrame(rectT(1, 1, parent, Anchor.Center), style: "OuterGlow", color: Color.Black * 0.7f)
{ {
@@ -121,6 +125,7 @@ namespace Barotrauma
private void RefreshAll() private void RefreshAll()
{ {
characterList = GameSession.GetSessionCrewCharacters(CharacterType.Both);
switch (selectedUpgradeTab) switch (selectedUpgradeTab)
{ {
case UpgradeTab.Repairs: case UpgradeTab.Repairs:
@@ -278,10 +283,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 };
@@ -433,8 +441,8 @@ namespace Barotrauma
Location location = Campaign.Map.CurrentLocation; Location location = Campaign.Map.CurrentLocation;
int hullRepairCost = Campaign.GetHullRepairCost(); int hullRepairCost = CampaignMode.GetHullRepairCost();
int itemRepairCost = Campaign.GetItemRepairCost(); int itemRepairCost = CampaignMode.GetItemRepairCost();
int shuttleRetrieveCost = CampaignMode.ShuttleReplaceCost; int shuttleRetrieveCost = CampaignMode.ShuttleReplaceCost;
if (location != null) if (location != null)
{ {
@@ -1080,7 +1088,7 @@ namespace Barotrauma
public static GUIFrame CreateUpgradeFrame(UpgradePrefab prefab, UpgradeCategory category, CampaignMode campaign, RectTransform rectTransform, bool addBuyButton = true) public static GUIFrame CreateUpgradeFrame(UpgradePrefab prefab, UpgradeCategory category, CampaignMode campaign, RectTransform rectTransform, bool addBuyButton = true)
{ {
int price = prefab.Price.GetBuyPrice(campaign.UpgradeManager.GetUpgradeLevel(prefab, category), campaign.Map?.CurrentLocation); int price = prefab.Price.GetBuyPrice(campaign.UpgradeManager.GetUpgradeLevel(prefab, category), campaign.Map?.CurrentLocation, characterList);
return CreateUpgradeEntry(rectTransform, prefab.Sprite, prefab.Name, prefab.Description, price, new CategoryData(category, prefab), addBuyButton, upgradePrefab: prefab, currentLevel: campaign.UpgradeManager.GetUpgradeLevel(prefab, category)); return CreateUpgradeEntry(rectTransform, prefab.Sprite, prefab.Name, prefab.Description, price, new CategoryData(category, prefab), addBuyButton, upgradePrefab: prefab, currentLevel: campaign.UpgradeManager.GetUpgradeLevel(prefab, category));
} }
@@ -1222,7 +1230,7 @@ namespace Barotrauma
{ {
LocalizedString promptBody = TextManager.GetWithVariables("Upgrades.PurchasePromptBody", LocalizedString promptBody = TextManager.GetWithVariables("Upgrades.PurchasePromptBody",
("[upgradename]", prefab.Name), ("[upgradename]", prefab.Name),
("[amount]", prefab.Price.GetBuyPrice(Campaign.UpgradeManager.GetUpgradeLevel(prefab, category), Campaign.Map?.CurrentLocation).ToString())); ("[amount]", prefab.Price.GetBuyPrice(Campaign.UpgradeManager.GetUpgradeLevel(prefab, category), Campaign.Map?.CurrentLocation, characterList).ToString()));
currectConfirmation = EventEditorScreen.AskForConfirmation(TextManager.Get("Upgrades.PurchasePromptTitle"), promptBody, () => currectConfirmation = EventEditorScreen.AskForConfirmation(TextManager.Get("Upgrades.PurchasePromptTitle"), promptBody, () =>
{ {
if (GameMain.NetworkMember != null) if (GameMain.NetworkMember != null)
@@ -1639,7 +1647,7 @@ namespace Barotrauma
GUITextBlock priceLabel = textBlocks[0]; GUITextBlock priceLabel = textBlocks[0];
priceLabel.Visible = true; priceLabel.Visible = true;
int price = prefab.Price.GetBuyPrice(campaign.UpgradeManager.GetUpgradeLevel(prefab, category), campaign.Map?.CurrentLocation); int price = prefab.Price.GetBuyPrice(campaign.UpgradeManager.GetUpgradeLevel(prefab, category), campaign.Map?.CurrentLocation, characterList);
if (priceLabel != null && !WaitForServerUpdate) if (priceLabel != null && !WaitForServerUpdate)
{ {
@@ -163,6 +163,7 @@ namespace Barotrauma
private void SetSubmarineVotingText(Client starter, SubmarineInfo info, bool transferItems, VoteType type) private void SetSubmarineVotingText(Client starter, SubmarineInfo info, bool transferItems, VoteType type)
{ {
int price = info.GetPrice();
string name = starter.Name; string name = starter.Name;
JobPrefab prefab = starter?.Character?.Info?.Job?.Prefab; JobPrefab prefab = starter?.Character?.Info?.Job?.Prefab;
Color nameColor = prefab != null ? prefab.UIColor : Color.White; Color nameColor = prefab != null ? prefab.UIColor : Color.White;
@@ -177,14 +178,14 @@ namespace Barotrauma
text = TextManager.GetWithVariables(tag, text = TextManager.GetWithVariables(tag,
("[playername]", characterRichString), ("[playername]", characterRichString),
("[submarinename]", submarineRichString), ("[submarinename]", submarineRichString),
("[amount]", info.Price.ToString()), ("[amount]", price.ToString()),
("[currencyname]", TextManager.Get("credit").ToLower())); ("[currencyname]", TextManager.Get("credit").ToLower()));
break; break;
case VoteType.PurchaseSub: case VoteType.PurchaseSub:
text = TextManager.GetWithVariables("submarinepurchasevote", text = TextManager.GetWithVariables("submarinepurchasevote",
("[playername]", characterRichString), ("[playername]", characterRichString),
("[submarinename]", submarineRichString), ("[submarinename]", submarineRichString),
("[amount]", info.Price.ToString()), ("[amount]", price.ToString()),
("[currencyname]", TextManager.Get("credit").ToLower())); ("[currencyname]", TextManager.Get("credit").ToLower()));
break; break;
case VoteType.SwitchSub: case VoteType.SwitchSub:
@@ -218,6 +219,7 @@ namespace Barotrauma
private LocalizedString GetSubmarineVoteResultMessage(SubmarineInfo info, VoteType type, int yesVoteCount, int noVoteCount, bool votePassed) private LocalizedString GetSubmarineVoteResultMessage(SubmarineInfo info, VoteType type, int yesVoteCount, int noVoteCount, bool votePassed)
{ {
int price = info.GetPrice();
LocalizedString result = string.Empty; LocalizedString result = string.Empty;
switch (type) switch (type)
@@ -225,7 +227,7 @@ namespace Barotrauma
case VoteType.PurchaseAndSwitchSub: case VoteType.PurchaseAndSwitchSub:
result = TextManager.GetWithVariables(votePassed ? "submarinepurchaseandswitchvotepassed" : "submarinepurchaseandswitchvotefailed", result = TextManager.GetWithVariables(votePassed ? "submarinepurchaseandswitchvotepassed" : "submarinepurchaseandswitchvotefailed",
("[submarinename]", info.DisplayName), ("[submarinename]", info.DisplayName),
("[amount]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", info.Price)), ("[amount]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", price)),
("[currencyname]", TextManager.Get("credit").ToLower()), ("[currencyname]", TextManager.Get("credit").ToLower()),
("[yesvotecount]", yesVoteCount.ToString()), ("[yesvotecount]", yesVoteCount.ToString()),
("[novotecount]" , noVoteCount.ToString())); ("[novotecount]" , noVoteCount.ToString()));
@@ -233,7 +235,7 @@ namespace Barotrauma
case VoteType.PurchaseSub: case VoteType.PurchaseSub:
result = TextManager.GetWithVariables(votePassed ? "submarinepurchasevotepassed" : "submarinepurchasevotefailed", result = TextManager.GetWithVariables(votePassed ? "submarinepurchasevotepassed" : "submarinepurchasevotefailed",
("[submarinename]", info.DisplayName), ("[submarinename]", info.DisplayName),
("[amount]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", info.Price)), ("[amount]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", price)),
("[currencyname]", TextManager.Get("credit").ToLower()), ("[currencyname]", TextManager.Get("credit").ToLower()),
("[yesvotecount]", yesVoteCount.ToString()), ("[yesvotecount]", yesVoteCount.ToString()),
("[novotecount]", noVoteCount.ToString())); ("[novotecount]", noVoteCount.ToString()));
@@ -30,7 +30,7 @@ namespace Barotrauma
Data = data, Data = data,
OnClick = (GUITextBlock component, GUITextBlock.ClickableArea area) => OnClick = (GUITextBlock component, GUITextBlock.ClickableArea area) =>
{ {
GameMain.Instance.ShowOpenUrlInWebBrowserPrompt("https://gameanalytics.com/privacy/"); GameMain.ShowOpenUrlInWebBrowserPrompt("https://gameanalytics.com/privacy/");
} }
}); });
} }
@@ -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)
{ {
@@ -791,6 +792,10 @@ namespace Barotrauma
{ {
GUI.TogglePauseMenu(); GUI.TogglePauseMenu();
} }
else if (GameSession?.Campaign is { ShowCampaignUI: true, ForceMapUI: false })
{
GameSession.Campaign.ShowCampaignUI = false;
}
//open the pause menu if not controlling a character OR if the character has no UIs active that can be closed with ESC //open the pause menu if not controlling a character OR if the character has no UIs active that can be closed with ESC
else if ((Character.Controlled == null || !itemHudActive()) else if ((Character.Controlled == null || !itemHudActive())
&& CharacterHealth.OpenHealthWindow == null && CharacterHealth.OpenHealthWindow == null
@@ -824,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)
{ {
@@ -858,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
@@ -1200,7 +1206,7 @@ namespace Barotrauma
base.OnExiting(sender, args); base.OnExiting(sender, args);
} }
public void ShowOpenUrlInWebBrowserPrompt(string url, string promptExtensionTag = null) public static void ShowOpenUrlInWebBrowserPrompt(string url, string promptExtensionTag = null)
{ {
if (string.IsNullOrEmpty(url)) { return; } if (string.IsNullOrEmpty(url)) { return; }
if (GUIMessageBox.VisibleBox?.UserData as string == "verificationprompt") { return; } if (GUIMessageBox.VisibleBox?.UserData as string == "verificationprompt") { return; }
@@ -1218,7 +1224,14 @@ namespace Barotrauma
}; };
msgBox.Buttons[0].OnClicked = (btn, userdata) => msgBox.Buttons[0].OnClicked = (btn, userdata) =>
{ {
ToolBox.OpenFileWithShell(url); try
{
ToolBox.OpenFileWithShell(url);
}
catch (Exception e)
{
DebugConsole.ThrowError($"Failed to open the url {url}", e);
}
msgBox.Close(); msgBox.Close();
return true; return true;
}; };
@@ -9,7 +9,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Xml.Linq; using System.Xml.Linq;
using Barotrauma.Steam;
namespace Barotrauma namespace Barotrauma
{ {
@@ -1,9 +1,7 @@
using System; using Microsoft.Xna.Framework;
using System.Collections.Generic; using Microsoft.Xna.Framework.Graphics;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Linq; using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace Barotrauma namespace Barotrauma
{ {
@@ -11,21 +9,22 @@ namespace Barotrauma
{ {
private const int MaxDrawnElements = 12; private const int MaxDrawnElements = 12;
public void DebugDraw(SpriteBatch spriteBatch, Vector2 pos, int debugDrawMetadataOffset, string[] ignoredMetadataInfo) public void DebugDraw(SpriteBatch spriteBatch, Vector2 pos, CampaignMode campaign, GUI.DebugDrawMetaData debugDrawMetaData)
{ {
var campaignData = data; var campaignData = data;
foreach (string ignored in ignoredMetadataInfo) if (!debugDrawMetaData.FactionMetadata) { removeData("reputation.faction"); }
if (!debugDrawMetaData.UpgradeLevels) { removeData("upgrade."); }
if (!debugDrawMetaData.UpgradePrices) { removeData("upgradeprice."); }
void removeData(string keyStartsWith)
{ {
if (!string.IsNullOrWhiteSpace(ignored)) campaignData = campaignData.Where(pair => !pair.Key.StartsWith(keyStartsWith)).ToDictionary(i => i.Key, i => i.Value);
{
campaignData = campaignData.Where(pair => !pair.Key.StartsWith(ignored)).ToDictionary(i => i.Key, i => i.Value);
}
} }
int offset = 0;; int offset = 0;;
if (campaignData.Count > 0) if (campaignData.Count > 0)
{ {
offset = debugDrawMetadataOffset % campaignData.Count; offset = debugDrawMetaData.Offset % campaignData.Count;
if (offset < 0) { offset += campaignData.Count; } if (offset < 0) { offset += campaignData.Count; }
} }
@@ -72,7 +71,7 @@ namespace Barotrauma
} }
float y = infoRect.Bottom + 16; float y = infoRect.Bottom + 16;
if (Campaign.Factions != null) if (campaign.Factions != null)
{ {
const string factionHeader = "Reputations"; const string factionHeader = "Reputations";
Vector2 factionHeaderSize = GUIStyle.SubHeadingFont.MeasureString(factionHeader); Vector2 factionHeaderSize = GUIStyle.SubHeadingFont.MeasureString(factionHeader);
@@ -81,7 +80,7 @@ namespace Barotrauma
GUI.DrawString(spriteBatch, factionPos, factionHeader, Color.White, font: GUIStyle.SubHeadingFont); GUI.DrawString(spriteBatch, factionPos, factionHeader, Color.White, font: GUIStyle.SubHeadingFont);
y += factionHeaderSize.Y + 8; y += factionHeaderSize.Y + 8;
foreach (Faction faction in Campaign.Factions) foreach (Faction faction in campaign.Factions)
{ {
LocalizedString name = faction.Prefab.Name; LocalizedString name = faction.Prefab.Name;
Vector2 nameSize = GUIStyle.SmallFont.MeasureString(name); Vector2 nameSize = GUIStyle.SmallFont.MeasureString(name);
@@ -94,20 +93,6 @@ namespace Barotrauma
y += 15; y += 15;
} }
} }
Location location = Campaign.Map?.CurrentLocation;
if (location?.Reputation != null)
{
string name = Campaign.Map?.CurrentLocation.Name;
Vector2 nameSize = GUIStyle.SmallFont.MeasureString(name);
GUI.DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth - 264, y), name, Color.White, font: GUIStyle.SmallFont);
y += nameSize.Y + 5;
float normalizedReputation = MathUtils.InverseLerp(location.Reputation.MinReputation, location.Reputation.MaxReputation, location.Reputation.Value);
Color color = ToolBox.GradientLerp(normalizedReputation, Color.Red, Color.Yellow, Color.LightGreen);
GUI.DrawRectangle(spriteBatch, new Rectangle(GameMain.GraphicsWidth - 264, (int) y, (int)(normalizedReputation * 255), 10), color, isFilled: true);
GUI.DrawRectangle(spriteBatch, new Rectangle(GameMain.GraphicsWidth - 264, (int) y, 256, 10), Color.White);
}
} }
} }
} }
@@ -1,9 +1,9 @@
using Barotrauma.Extensions; using Barotrauma.Extensions;
using Barotrauma.Items.Components;
using Barotrauma.Networking; 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;
@@ -15,8 +15,6 @@ namespace Barotrauma
protected bool crewDead; protected bool crewDead;
protected Color overlayColor; protected Color overlayColor;
protected LocalizedString overlayText, overlayTextBottom;
protected Color overlayTextColor;
protected Sprite overlaySprite; protected Sprite overlaySprite;
private TransitionType prevCampaignUIAutoOpenType; private TransitionType prevCampaignUIAutoOpenType;
@@ -29,6 +27,12 @@ namespace Barotrauma
protected GUIFrame campaignUIContainer; protected GUIFrame campaignUIContainer;
public CampaignUI CampaignUI; public CampaignUI CampaignUI;
public SlideshowPlayer SlideshowPlayer
{
get;
protected set;
}
public static CancellationTokenSource StartRoundCancellationToken { get; private set; } public static CancellationTokenSource StartRoundCancellationToken { get; private set; }
public bool ForceMapUI public bool ForceMapUI
@@ -76,6 +80,7 @@ namespace Barotrauma
{ {
foreach (Mission mission in Missions.ToList()) foreach (Mission mission in Missions.ToList())
{ {
if (!mission.Prefab.ShowStartMessage) { continue; }
new GUIMessageBox( new GUIMessageBox(
RichString.Rich(mission.Prefab.IsSideObjective ? TextManager.AddPunctuation(':', TextManager.Get("sideobjective"), mission.Name) : mission.Name), RichString.Rich(mission.Prefab.IsSideObjective ? TextManager.AddPunctuation(':', TextManager.Get("sideobjective"), mission.Name) : mission.Name),
RichString.Rich(mission.Description), Array.Empty<LocalizedString>(), type: GUIMessageBox.Type.InGame, icon: mission.Prefab.Icon) RichString.Rich(mission.Description), Array.Empty<LocalizedString>(), type: GUIMessageBox.Type.InGame, icon: mission.Prefab.Icon)
@@ -86,6 +91,8 @@ namespace Barotrauma
} }
} }
private static bool IsOwner(Client client) => client != null && client.IsOwner;
/// <summary> /// <summary>
/// There is a server-side implementation of the method in <see cref="MultiPlayerCampaign"/> /// There is a server-side implementation of the method in <see cref="MultiPlayerCampaign"/>
/// </summary> /// </summary>
@@ -97,10 +104,8 @@ namespace Barotrauma
return return
GameMain.Client.HasPermission(permissions) || GameMain.Client.HasPermission(permissions) ||
GameMain.Client.HasPermission(ClientPermissions.ManageCampaign) || GameMain.Client.HasPermission(ClientPermissions.ManageCampaign) ||
GameMain.Client.ConnectedClients.Count == 1 ||
GameMain.Client.IsServerOwner || GameMain.Client.IsServerOwner ||
//allow managing if no-one with permissions is alive AnyOneAllowedToManageCampaign(permissions);
GameMain.Client.ConnectedClients.None(c => c.InGame && c.Character is { IsIncapacitated: false, IsDead: false } && (c.IsOwner || c.HasPermission(permissions)));
} }
public static bool AllowedToManageWallets() public static bool AllowedToManageWallets()
@@ -127,32 +132,10 @@ namespace Barotrauma
{ {
GUI.DrawRectangle(spriteBatch, new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight), overlayColor, isFilled: true); GUI.DrawRectangle(spriteBatch, new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight), overlayColor, isFilled: true);
} }
if (!overlayText.IsNullOrEmpty() && overlayTextColor.A > 0)
{
var backgroundSprite = GUIStyle.GetComponentStyle("CommandBackground").GetDefaultSprite();
Vector2 centerPos = new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight) / 2;
LocalizedString wrappedText = ToolBox.WrapText(overlayText, GameMain.GraphicsWidth / 3, GUIStyle.Font);
Vector2 textSize = GUIStyle.Font.MeasureString(wrappedText);
Vector2 textPos = centerPos - textSize / 2;
backgroundSprite.Draw(spriteBatch,
centerPos,
Color.White * (overlayTextColor.A / 255.0f),
origin: backgroundSprite.size / 2,
rotate: 0.0f,
scale: new Vector2(GameMain.GraphicsWidth / 2 / backgroundSprite.size.X, textSize.Y / backgroundSprite.size.Y * 1.5f));
GUI.DrawString(spriteBatch, textPos + Vector2.One, wrappedText, Color.Black * (overlayTextColor.A / 255.0f));
GUI.DrawString(spriteBatch, textPos, wrappedText, overlayTextColor);
if (!overlayTextBottom.IsNullOrEmpty())
{
Vector2 bottomTextPos = centerPos + new Vector2(0.0f, textSize.Y / 2 + 40 * GUI.Scale) - GUIStyle.Font.MeasureString(overlayTextBottom) / 2;
GUI.DrawString(spriteBatch, bottomTextPos + Vector2.One, overlayTextBottom.Value, Color.Black * (overlayTextColor.A / 255.0f));
GUI.DrawString(spriteBatch, bottomTextPos, overlayTextBottom.Value, overlayTextColor);
}
}
} }
SlideshowPlayer?.DrawManually(spriteBatch);
if (GUI.DisableHUD || GUI.DisableUpperHUD || ForceMapUI || CoroutineManager.IsCoroutineRunning("LevelTransition")) if (GUI.DisableHUD || GUI.DisableUpperHUD || ForceMapUI || CoroutineManager.IsCoroutineRunning("LevelTransition"))
{ {
endRoundButton.Visible = false; endRoundButton.Visible = false;
@@ -192,6 +175,7 @@ namespace Barotrauma
case TransitionType.None: case TransitionType.None:
default: default:
if (Level.Loaded.Type == LevelData.LevelType.Outpost && if (Level.Loaded.Type == LevelData.LevelType.Outpost &&
!Level.Loaded.IsEndBiome &&
(Character.Controlled?.Submarine?.Info.Type == SubmarineType.Player || (Character.Controlled?.CurrentHull?.OutpostModuleTags.Contains("airlock".ToIdentifier()) ?? false))) (Character.Controlled?.Submarine?.Info.Type == SubmarineType.Player || (Character.Controlled?.CurrentHull?.OutpostModuleTags.Contains("airlock".ToIdentifier()) ?? false)))
{ {
buttonText = TextManager.GetWithVariable("LeaveLocation", "[locationname]", Level.Loaded.StartLocation?.Name ?? "[ERROR]"); buttonText = TextManager.GetWithVariable("LeaveLocation", "[locationname]", Level.Loaded.StartLocation?.Name ?? "[ERROR]");
@@ -203,6 +187,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 +254,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 +271,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; }
@@ -216,51 +216,35 @@ namespace Barotrauma
} }
Character prevControlled = Character.Controlled; Character prevControlled = Character.Controlled;
if (prevControlled?.AIController != null)
{
prevControlled.AIController.Enabled = false;
}
GUI.DisableHUD = true; GUI.DisableHUD = true;
if (IsFirstRound) if (IsFirstRound)
{ {
Character.Controlled = null; if (SlideshowPrefab.Prefabs.TryGet("campaignstart".ToIdentifier(), out var slideshow))
{
SlideshowPlayer = new SlideshowPlayer(GUICanvas.Instance, slideshow);
}
Character.Controlled = null;
prevControlled?.ClearInputs(); prevControlled?.ClearInputs();
overlayColor = Color.LightGray; overlayColor = Color.LightGray;
overlaySprite = Map.CurrentLocation.Type.GetPortrait(Map.CurrentLocation.PortraitId); overlaySprite = Map.CurrentLocation.Type.GetPortrait(Map.CurrentLocation.PortraitId);
overlayTextColor = Color.Transparent;
overlayText = TextManager.GetWithVariables("campaignstart",
("xxxx", Map.CurrentLocation.Name), ("yyyy", TextManager.Get($"submarineclass.{Submarine.MainSub.Info.SubmarineClass}")));
float fadeInDuration = 1.0f;
float textDuration = 10.0f;
float timer = 0.0f;
while (timer < textDuration)
{
if (GameMain.GameSession == null || Screen.Selected != GameMain.GameScreen)
{
GUI.DisableHUD = false;
yield return CoroutineStatus.Success;
}
// Try to grab the controlled here to prevent inputs, assigned late on multiplayer
if (Character.Controlled != null)
{
prevControlled = Character.Controlled;
Character.Controlled = null;
prevControlled.ClearInputs();
}
GameMain.GameScreen.Cam.Freeze = true;
overlayTextColor = Color.Lerp(Color.Transparent, Color.White, (timer - 1.0f) / fadeInDuration);
timer = Math.Min(timer + CoroutineManager.DeltaTime, textDuration);
yield return CoroutineStatus.Running;
}
var outpost = GameMain.GameSession.Level.StartOutpost; var outpost = GameMain.GameSession.Level.StartOutpost;
var borders = outpost.GetDockedBorders(); var borders = outpost.GetDockedBorders();
borders.Location += outpost.WorldPosition.ToPoint(); borders.Location += outpost.WorldPosition.ToPoint();
GameMain.GameScreen.Cam.Position = new Vector2(borders.X + borders.Width / 2, borders.Y - borders.Height / 2); GameMain.GameScreen.Cam.Position = new Vector2(borders.X + borders.Width / 2, borders.Y - borders.Height / 2);
float startZoom = 0.8f / float startZoom = 0.8f /
((float)Math.Max(borders.Width, borders.Height) / (float)GameMain.GameScreen.Cam.Resolution.X); ((float)Math.Max(borders.Width, borders.Height) / (float)GameMain.GameScreen.Cam.Resolution.X);
GameMain.GameScreen.Cam.MinZoom = Math.Min(startZoom, GameMain.GameScreen.Cam.MinZoom); GameMain.GameScreen.Cam.Zoom = GameMain.GameScreen.Cam.MinZoom = Math.Min(startZoom, GameMain.GameScreen.Cam.MinZoom);
while (SlideshowPlayer != null && !SlideshowPlayer.LastTextShown)
{
GUI.PreventPauseMenuToggle = true;
yield return CoroutineStatus.Running;
}
GUI.PreventPauseMenuToggle = false;
prevControlled ??= Character.Controlled;
GameMain.LightManager.LosAlpha = 0.0f;
var transition = new CameraTransition(prevControlled, GameMain.GameScreen.Cam, var transition = new CameraTransition(prevControlled, GameMain.GameScreen.Cam,
null, null, null, null,
fadeOut: false, fadeOut: false,
@@ -272,16 +256,6 @@ namespace Barotrauma
AllowInterrupt = true, AllowInterrupt = true,
RemoveControlFromCharacter = false RemoveControlFromCharacter = false
}; };
fadeInDuration = 1.0f;
timer = 0.0f;
overlayTextColor = Color.Transparent;
overlayText = "";
while (timer < fadeInDuration)
{
overlayColor = Color.Lerp(Color.LightGray, Color.Transparent, timer / fadeInDuration);
timer += CoroutineManager.DeltaTime;
yield return CoroutineStatus.Running;
}
overlayColor = Color.Transparent; overlayColor = Color.Transparent;
while (transition.Running) while (transition.Running)
{ {
@@ -409,6 +383,8 @@ namespace Barotrauma
base.Update(deltaTime); base.Update(deltaTime);
SlideshowPlayer?.UpdateManually(deltaTime);
if (PlayerInput.SecondaryMouseButtonClicked() || if (PlayerInput.SecondaryMouseButtonClicked() ||
PlayerInput.KeyHit(Microsoft.Xna.Framework.Input.Keys.Escape)) PlayerInput.KeyHit(Microsoft.Xna.Framework.Input.Keys.Escape))
{ {
@@ -442,7 +418,8 @@ namespace Barotrauma
CampaignUI.SelectTab(InteractionType.Map); CampaignUI.SelectTab(InteractionType.Map);
} }
} }
else //end biome is handled by the server (automatic transition without a map screen when the end of the level is reached)
else if (!Level.Loaded.IsEndBiome)
{ {
//wasn't initially docked (sub doesn't have a docking port?) //wasn't initially docked (sub doesn't have a docking port?)
// -> choose a destination when the sub is far enough from the start outpost // -> choose a destination when the sub is far enough from the start outpost
@@ -467,11 +444,17 @@ namespace Barotrauma
} }
} }
public override void UpdateWhilePaused(float deltaTime)
{
SlideshowPlayer?.UpdateManually(deltaTime);
}
public override void End(TransitionType transitionType = TransitionType.None) public override void End(TransitionType transitionType = TransitionType.None)
{ {
base.End(transitionType); base.End(transitionType);
ForceMapUI = ShowCampaignUI = false; ForceMapUI = ShowCampaignUI = false;
SlideshowPlayer?.Finish();
// remove all event dialogue boxes // remove all event dialogue boxes
GUIMessageBox.MessageBoxes.ForEachMod(mb => GUIMessageBox.MessageBoxes.ForEachMod(mb =>
{ {
@@ -501,7 +484,8 @@ namespace Barotrauma
{ {
GUIMessageBox.MessageBoxes.Remove(GUIMessageBox.VisibleBox); GUIMessageBox.MessageBoxes.Remove(GUIMessageBox.VisibleBox);
} }
CoroutineManager.StartCoroutine(DoEndCampaignCameraTransition(), "DoEndCampaignCameraTransition"); GameMain.CampaignEndScreen.Select();
GUI.DisableHUD = false;
GameMain.CampaignEndScreen.OnFinished = () => GameMain.CampaignEndScreen.OnFinished = () =>
{ {
GameMain.NetLobbyScreen.Select(); GameMain.NetLobbyScreen.Select();
@@ -510,32 +494,6 @@ namespace Barotrauma
}; };
} }
private IEnumerable<CoroutineStatus> DoEndCampaignCameraTransition()
{
Character controlled = Character.Controlled;
if (controlled != null)
{
controlled.AIController.Enabled = false;
}
GUI.DisableHUD = true;
ISpatialEntity endObject = Level.Loaded.LevelObjectManager.GetAllObjects().FirstOrDefault(obj => obj.Prefab.SpawnPos == LevelObjectPrefab.SpawnPosType.LevelEnd);
var transition = new CameraTransition(endObject ?? Submarine.MainSub, GameMain.GameScreen.Cam,
null, Alignment.Center,
fadeOut: true,
panDuration: 10,
startZoom: null, endZoom: 0.2f);
while (transition.Running)
{
yield return CoroutineStatus.Running;
}
GameMain.CampaignEndScreen.Select();
GUI.DisableHUD = false;
yield return CoroutineStatus.Success;
}
public void ClientWrite(IWriteMessage msg) public void ClientWrite(IWriteMessage msg)
{ {
System.Diagnostics.Debug.Assert(map.Locations.Count < UInt16.MaxValue); System.Diagnostics.Debug.Assert(map.Locations.Count < UInt16.MaxValue);
@@ -834,8 +792,6 @@ namespace Barotrauma
{ {
DebugConsole.Log("Received campaign update (Reputation)"); DebugConsole.Log("Received campaign update (Reputation)");
UInt16 id = msg.ReadUInt16(); UInt16 id = msg.ReadUInt16();
float? reputation = null;
if (msg.ReadBoolean()) { reputation = msg.ReadSingle(); }
Dictionary<Identifier, float> factionReps = new Dictionary<Identifier, float>(); Dictionary<Identifier, float> factionReps = new Dictionary<Identifier, float>();
byte factionsCount = msg.ReadByte(); byte factionsCount = msg.ReadByte();
for (int i = 0; i < factionsCount; i++) for (int i = 0; i < factionsCount; i++)
@@ -844,11 +800,6 @@ namespace Barotrauma
} }
if (ShouldApply(NetFlags.Reputation, id, requireUpToDateSave: true)) if (ShouldApply(NetFlags.Reputation, id, requireUpToDateSave: true))
{ {
if (reputation.HasValue)
{
campaign.Map.CurrentLocation.Reputation.SetReputation(reputation.Value);
campaign?.CampaignUI?.UpgradeStore?.RequestRefresh();
}
foreach (var (identifier, rep) in factionReps) foreach (var (identifier, rep) in factionReps)
{ {
Faction faction = campaign.Factions.FirstOrDefault(f => f.Prefab.Identifier == identifier); Faction faction = campaign.Factions.FirstOrDefault(f => f.Prefab.Identifier == identifier);
@@ -861,6 +812,7 @@ namespace Barotrauma
DebugConsole.ThrowError($"Received an update for a faction that doesn't exist \"{identifier}\"."); DebugConsole.ThrowError($"Received an update for a faction that doesn't exist \"{identifier}\".");
} }
} }
campaign?.CampaignUI?.UpgradeStore?.RequestRefresh();
} }
} }
if (requiredFlags.HasFlag(NetFlags.CharacterInfo)) if (requiredFlags.HasFlag(NetFlags.CharacterInfo))
@@ -12,7 +12,13 @@ namespace Barotrauma
public override bool Paused public override bool Paused
{ {
get { return ForceMapUI || CoroutineManager.IsCoroutineRunning("LevelTransition") || ShowCampaignUI && CampaignUI.SelectedTab == InteractionType.Map; } get
{
return
ForceMapUI || CoroutineManager.IsCoroutineRunning("LevelTransition") ||
ShowCampaignUI && CampaignUI.SelectedTab == InteractionType.Map ||
(SlideshowPlayer != null && !SlideshowPlayer.LastTextShown);
}
} }
public override void UpdateWhilePaused(float deltaTime) public override void UpdateWhilePaused(float deltaTime)
@@ -31,6 +37,8 @@ namespace Barotrauma
} }
} }
SlideshowPlayer?.UpdateManually(deltaTime);
CrewManager.ChatBox?.Update(deltaTime); CrewManager.ChatBox?.Update(deltaTime);
CrewManager.UpdateReports(); CrewManager.UpdateReports();
} }
@@ -77,9 +85,10 @@ namespace Barotrauma
/// </summary> /// </summary>
private SinglePlayerCampaign(string mapSeed, CampaignSettings settings) : base(GameModePreset.SinglePlayerCampaign, settings) private SinglePlayerCampaign(string mapSeed, CampaignSettings settings) : base(GameModePreset.SinglePlayerCampaign, settings)
{ {
CampaignMetadata = new CampaignMetadata(this); CampaignMetadata = new CampaignMetadata();
UpgradeManager = new UpgradeManager(this); UpgradeManager = new UpgradeManager(this);
Settings = settings; Settings = settings;
InitFactions();
map = new Map(this, mapSeed); map = new Map(this, mapSeed);
foreach (JobPrefab jobPrefab in JobPrefab.Prefabs) foreach (JobPrefab jobPrefab in JobPrefab.Prefabs)
{ {
@@ -89,7 +98,6 @@ namespace Barotrauma
CrewManager.AddCharacterInfo(new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: jobPrefab, variant: variant)); CrewManager.AddCharacterInfo(new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: jobPrefab, variant: variant));
} }
} }
InitCampaignData();
InitUI(); InitUI();
} }
@@ -100,6 +108,19 @@ namespace Barotrauma
{ {
IsFirstRound = false; IsFirstRound = false;
foreach (var subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "metadata":
CampaignMetadata = new CampaignMetadata(subElement);
break;
}
}
CampaignMetadata ??= new CampaignMetadata();
InitFactions();
foreach (var subElement in element.Elements()) foreach (var subElement in element.Elements())
{ {
switch (subElement.Name.ToString().ToLowerInvariant()) switch (subElement.Name.ToString().ToLowerInvariant())
@@ -114,9 +135,6 @@ namespace Barotrauma
case "map": case "map":
map = Map.Load(this, subElement); map = Map.Load(this, subElement);
break; break;
case "metadata":
CampaignMetadata = new CampaignMetadata(this, subElement);
break;
case "cargo": case "cargo":
CargoManager.LoadPurchasedItems(subElement); CargoManager.LoadPurchasedItems(subElement);
break; break;
@@ -136,11 +154,8 @@ namespace Barotrauma
} }
} }
CampaignMetadata ??= new CampaignMetadata(this);
UpgradeManager ??= new UpgradeManager(this); UpgradeManager ??= new UpgradeManager(this);
InitCampaignData();
InitUI(); InitUI();
//backwards compatibility for saves made prior to the addition of personal wallets //backwards compatibility for saves made prior to the addition of personal wallets
@@ -265,8 +280,7 @@ namespace Barotrauma
private IEnumerable<CoroutineStatus> DoLoadInitialLevel(LevelData level, bool mirror) private IEnumerable<CoroutineStatus> DoLoadInitialLevel(LevelData level, bool mirror)
{ {
GameMain.GameSession.StartRound(level, GameMain.GameSession.StartRound(level, mirrorLevel: mirror, startOutpost: GetPredefinedStartOutpost());
mirrorLevel: mirror);
GameMain.GameScreen.Select(); GameMain.GameScreen.Select();
CoroutineManager.StartCoroutine(DoInitialCameraTransition(), "SinglePlayerCampaign.DoInitialCameraTransition"); CoroutineManager.StartCoroutine(DoInitialCameraTransition(), "SinglePlayerCampaign.DoInitialCameraTransition");
@@ -296,34 +310,9 @@ namespace Barotrauma
if (IsFirstRound || showCampaignResetText) if (IsFirstRound || showCampaignResetText)
{ {
overlayColor = Color.LightGray; if (SlideshowPrefab.Prefabs.TryGet("campaignstart".ToIdentifier(), out var slideshow))
overlaySprite = Map.CurrentLocation.Type.GetPortrait(Map.CurrentLocation.PortraitId);
overlayTextColor = Color.Transparent;
overlayText = TextManager.GetWithVariables(showCampaignResetText ? "campaignend4" : "campaignstart",
("xxxx", Map.CurrentLocation.Name),
("yyyy", TextManager.Get("submarineclass." + Submarine.MainSub.Info.SubmarineClass)));
LocalizedString pressAnyKeyText = TextManager.Get("pressanykey");
float fadeInDuration = 2.0f;
float textDuration = 10.0f;
float timer = 0.0f;
while (true)
{ {
if (timer > fadeInDuration) SlideshowPlayer = new SlideshowPlayer(GUICanvas.Instance, slideshow);
{
overlayTextBottom = pressAnyKeyText;
if (PlayerInput.GetKeyboardState.GetPressedKeys().Length > 0 || PlayerInput.PrimaryMouseButtonClicked())
{
break;
}
}
if (GameMain.GameSession == null)
{
GUI.DisableHUD = false;
yield return CoroutineStatus.Success;
}
overlayTextColor = Color.Lerp(Color.Transparent, Color.White, (timer - 1.0f) / fadeInDuration);
timer = Math.Min(timer + CoroutineManager.DeltaTime, textDuration);
yield return CoroutineStatus.Running;
} }
var outpost = GameMain.GameSession.Level.StartOutpost; var outpost = GameMain.GameSession.Level.StartOutpost;
var borders = outpost.GetDockedBorders(); var borders = outpost.GetDockedBorders();
@@ -331,7 +320,13 @@ namespace Barotrauma
GameMain.GameScreen.Cam.Position = new Vector2(borders.X + borders.Width / 2, borders.Y - borders.Height / 2); GameMain.GameScreen.Cam.Position = new Vector2(borders.X + borders.Width / 2, borders.Y - borders.Height / 2);
float startZoom = 0.8f / float startZoom = 0.8f /
((float)Math.Max(borders.Width, borders.Height) / (float)GameMain.GameScreen.Cam.Resolution.X); ((float)Math.Max(borders.Width, borders.Height) / (float)GameMain.GameScreen.Cam.Resolution.X);
GameMain.GameScreen.Cam.MinZoom = Math.Min(startZoom, GameMain.GameScreen.Cam.MinZoom); GameMain.GameScreen.Cam.Zoom = GameMain.GameScreen.Cam.MinZoom = Math.Min(startZoom, GameMain.GameScreen.Cam.MinZoom);
while (SlideshowPlayer != null && !SlideshowPlayer.LastTextShown)
{
GUI.PreventPauseMenuToggle = true;
yield return CoroutineStatus.Running;
}
GUI.PreventPauseMenuToggle = false;
var transition = new CameraTransition(prevControlled, GameMain.GameScreen.Cam, var transition = new CameraTransition(prevControlled, GameMain.GameScreen.Cam,
null, null, null, null,
fadeOut: false, fadeOut: false,
@@ -343,17 +338,6 @@ namespace Barotrauma
AllowInterrupt = true, AllowInterrupt = true,
RemoveControlFromCharacter = false RemoveControlFromCharacter = false
}; };
fadeInDuration = 1.0f;
timer = 0.0f;
overlayTextColor = Color.Transparent;
overlayText = "";
while (timer < fadeInDuration)
{
overlayColor = Color.Lerp(Color.LightGray, Color.Transparent, timer / fadeInDuration);
timer += CoroutineManager.DeltaTime;
yield return CoroutineStatus.Running;
}
overlayColor = Color.Transparent;
while (transition.Running) while (transition.Running)
{ {
yield return CoroutineStatus.Running; yield return CoroutineStatus.Running;
@@ -407,6 +391,11 @@ namespace Barotrauma
GUI.SetSavingIndicatorState(success); GUI.SetSavingIndicatorState(success);
crewDead = false; crewDead = false;
if (success)
{
// Event history must be registered before ending the round or it will be cleared
GameMain.GameSession.EventManager.RegisterEventHistory();
}
GameMain.GameSession.EndRound("", traitorResults, transitionType); GameMain.GameSession.EndRound("", traitorResults, transitionType);
var continueButton = GameMain.GameSession.RoundSummary?.ContinueButton; var continueButton = GameMain.GameSession.RoundSummary?.ContinueButton;
RoundSummary roundSummary = null; RoundSummary roundSummary = null;
@@ -437,60 +426,65 @@ namespace Barotrauma
case TransitionType.ProgressToNextEmptyLocation: case TransitionType.ProgressToNextEmptyLocation:
TotalPassedLevels++; TotalPassedLevels++;
break; break;
case TransitionType.End:
EndCampaign();
IsFirstRound = true;
break;
} }
Map.ProgressWorld(transitionType, (float)(Timing.TotalTime - GameMain.GameSession.RoundStartTime)); Map.ProgressWorld(this, transitionType, (float)(Timing.TotalTime - GameMain.GameSession.RoundStartTime));
var endTransition = new CameraTransition(Submarine.MainSub, GameMain.GameScreen.Cam, null, GUI.ClearMessages();
//--------------------------------------
if (transitionType != TransitionType.End)
{
var endTransition = new CameraTransition(Submarine.MainSub, GameMain.GameScreen.Cam, null,
transitionType == TransitionType.LeaveLocation ? Alignment.BottomCenter : Alignment.Center, transitionType == TransitionType.LeaveLocation ? Alignment.BottomCenter : Alignment.Center,
fadeOut: false, fadeOut: false,
panDuration: EndTransitionDuration); panDuration: EndTransitionDuration);
GUI.ClearMessages(); Location portraitLocation = Map.SelectedLocation ?? Map.CurrentLocation;
overlaySprite = portraitLocation.Type.GetPortrait(portraitLocation.PortraitId);
Location portraitLocation = Map.SelectedLocation ?? Map.CurrentLocation; float fadeOutDuration = endTransition.PanDuration;
overlaySprite = portraitLocation.Type.GetPortrait(portraitLocation.PortraitId); float t = 0.0f;
float fadeOutDuration = endTransition.PanDuration; while (t < fadeOutDuration || endTransition.Running)
float t = 0.0f;
while (t < fadeOutDuration || endTransition.Running)
{
t += CoroutineManager.DeltaTime;
overlayColor = Color.Lerp(Color.Transparent, Color.White, t / fadeOutDuration);
yield return CoroutineStatus.Running;
}
overlayColor = Color.White;
yield return CoroutineStatus.Running;
//--------------------------------------
if (success)
{
GameMain.GameSession.SubmarineInfo = new SubmarineInfo(GameMain.GameSession.Submarine);
GameMain.GameSession.EventManager.RegisterEventHistory();
SaveUtil.SaveGame(GameMain.GameSession.SavePath);
}
else
{
PendingSubmarineSwitch = null;
EnableRoundSummaryGameOverState();
}
CrewManager?.ClearCurrentOrders();
//--------------------------------------
SelectSummaryScreen(roundSummary, newLevel, mirror, () =>
{
GameMain.GameScreen.Select();
if (continueButton != null)
{ {
continueButton.Visible = true; t += CoroutineManager.DeltaTime;
overlayColor = Color.Lerp(Color.Transparent, Color.White, t / fadeOutDuration);
yield return CoroutineStatus.Running;
}
overlayColor = Color.White;
yield return CoroutineStatus.Running;
//--------------------------------------
if (success)
{
GameMain.GameSession.SubmarineInfo = new SubmarineInfo(GameMain.GameSession.Submarine);
SaveUtil.SaveGame(GameMain.GameSession.SavePath);
}
else
{
PendingSubmarineSwitch = null;
EnableRoundSummaryGameOverState();
} }
GUI.DisableHUD = false; CrewManager?.ClearCurrentOrders();
GUI.ClearCursorWait();
overlayColor = Color.Transparent; SelectSummaryScreen(roundSummary, newLevel, mirror, () =>
}); {
GameMain.GameScreen.Select();
if (continueButton != null)
{
continueButton.Visible = true;
}
GUI.DisableHUD = false;
GUI.ClearCursorWait();
overlayColor = Color.Transparent;
});
}
GUI.SetSavingIndicatorState(false); GUI.SetSavingIndicatorState(false);
yield return CoroutineStatus.Success; yield return CoroutineStatus.Success;
@@ -498,7 +492,10 @@ namespace Barotrauma
protected override void EndCampaignProjSpecific() protected override void EndCampaignProjSpecific()
{ {
CoroutineManager.StartCoroutine(DoEndCampaignCameraTransition(), "DoEndCampaignCameraTransition"); GameMain.GameSession.SubmarineInfo = new SubmarineInfo(GameMain.GameSession.Submarine);
SaveUtil.SaveGame(GameMain.GameSession.SavePath);
GameMain.CampaignEndScreen.Select();
GUI.DisableHUD = false;
GameMain.CampaignEndScreen.OnFinished = () => GameMain.CampaignEndScreen.OnFinished = () =>
{ {
showCampaignResetText = true; showCampaignResetText = true;
@@ -507,39 +504,14 @@ namespace Barotrauma
}; };
} }
private IEnumerable<CoroutineStatus> DoEndCampaignCameraTransition()
{
if (Character.Controlled != null)
{
Character.Controlled.AIController.Enabled = false;
Character.Controlled = null;
}
GUI.DisableHUD = true;
ISpatialEntity endObject = Level.Loaded.LevelObjectManager.GetAllObjects().FirstOrDefault(obj => obj.Prefab.SpawnPos == LevelObjectPrefab.SpawnPosType.LevelEnd);
var transition = new CameraTransition(endObject ?? Submarine.MainSub, GameMain.GameScreen.Cam,
null, Alignment.Center,
fadeOut: true,
panDuration: 10,
startZoom: null, endZoom: 0.2f);
while (transition.Running)
{
yield return CoroutineStatus.Running;
}
GameMain.GameSession.SubmarineInfo = new SubmarineInfo(GameMain.GameSession.Submarine);
SaveUtil.SaveGame(GameMain.GameSession.SavePath);
GameMain.CampaignEndScreen.Select();
GUI.DisableHUD = false;
yield return CoroutineStatus.Success;
}
public override void Update(float deltaTime) public override void Update(float deltaTime)
{ {
if (CoroutineManager.IsCoroutineRunning("LevelTransition") || CoroutineManager.IsCoroutineRunning("SubmarineTransition") || gameOver) { return; } if (CoroutineManager.IsCoroutineRunning("LevelTransition") || CoroutineManager.IsCoroutineRunning("SubmarineTransition") || gameOver) { return; }
base.Update(deltaTime); base.Update(deltaTime);
SlideshowPlayer?.UpdateManually(deltaTime);
Map?.Radiation?.UpdateRadiation(deltaTime); Map?.Radiation?.UpdateRadiation(deltaTime);
if (PlayerInput.SecondaryMouseButtonClicked() || if (PlayerInput.SecondaryMouseButtonClicked() ||
@@ -590,11 +562,19 @@ namespace Barotrauma
CampaignUI.SelectTab(InteractionType.Map); CampaignUI.SelectTab(InteractionType.Map);
} }
} }
else if (Level.Loaded.IsEndBiome)
{
var transitionType = GetAvailableTransition(out _, out Submarine leavingSub);
if (transitionType == TransitionType.ProgressToNextLocation)
{
LoadNewLevel();
}
}
else else
{ {
//wasn't initially docked (sub doesn't have a docking port?) //wasn't initially docked (sub doesn't have a docking port?)
// -> choose a destination when the sub is far enough from the start outpost // -> choose a destination when the sub is far enough from the start outpost
if (!Submarine.MainSub.AtStartExit) if (!Submarine.MainSub.AtStartExit && !Level.Loaded.StartOutpost.ExitPoints.Any())
{ {
ForceMapUI = true; ForceMapUI = true;
CampaignUI.SelectTab(InteractionType.Map); CampaignUI.SelectTab(InteractionType.Map);
@@ -604,11 +584,11 @@ namespace Barotrauma
else else
{ {
var transitionType = GetAvailableTransition(out _, out Submarine leavingSub); var transitionType = GetAvailableTransition(out _, out Submarine leavingSub);
if (transitionType == TransitionType.End) if (Level.Loaded.IsEndBiome && transitionType == TransitionType.ProgressToNextLocation)
{ {
EndCampaign(); LoadNewLevel();
} }
if (transitionType == TransitionType.ProgressToNextLocation && else if (transitionType == TransitionType.ProgressToNextLocation &&
Level.Loaded.EndOutpost != null && Level.Loaded.EndOutpost.DockedTo.Contains(leavingSub)) Level.Loaded.EndOutpost != null && Level.Loaded.EndOutpost.DockedTo.Contains(leavingSub))
{ {
LoadNewLevel(); LoadNewLevel();
@@ -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
}
@@ -21,7 +21,6 @@ namespace Barotrauma
private readonly GameMode gameMode; private readonly GameMode gameMode;
private readonly float initialLocationReputation;
private readonly Dictionary<Faction, float> initialFactionReputations = new Dictionary<Faction, float>(); private readonly Dictionary<Faction, float> initialFactionReputations = new Dictionary<Faction, float>();
public GUILayoutGroup ButtonArea { get; private set; } public GUILayoutGroup ButtonArea { get; private set; }
@@ -36,7 +35,6 @@ namespace Barotrauma
this.selectedMissions = selectedMissions.ToList(); this.selectedMissions = selectedMissions.ToList();
this.startLocation = startLocation; this.startLocation = startLocation;
this.endLocation = endLocation; this.endLocation = endLocation;
initialLocationReputation = startLocation?.Reputation?.Value ?? 0.0f;
if (gameMode is CampaignMode campaignMode) if (gameMode is CampaignMode campaignMode)
{ {
foreach (Faction faction in campaignMode.Factions) foreach (Faction faction in campaignMode.Factions)
@@ -214,11 +212,12 @@ namespace Barotrauma
Stretch = true Stretch = true
}; };
List<Mission> missionsToDisplay = new List<Mission>(selectedMissions); List<Mission> missionsToDisplay = new List<Mission>(selectedMissions.Where(m => m.Prefab.ShowInMenus));
if (!selectedMissions.Any() && startLocation != null) if (!selectedMissions.Any() && startLocation != null)
{ {
foreach (Mission mission in startLocation.SelectedMissions) foreach (Mission mission in startLocation.SelectedMissions)
{ {
if (!mission.Prefab.ShowInMenus) { continue; }
if (mission.Locations[0] == mission.Locations[1] || if (mission.Locations[0] == mission.Locations[1] ||
mission.Locations.Contains(campaignMode?.Map.SelectedLocation)) mission.Locations.Contains(campaignMode?.Map.SelectedLocation))
{ {
@@ -401,7 +400,7 @@ namespace Barotrauma
}; };
reputationList.ContentBackground.Color = Color.Transparent; reputationList.ContentBackground.Color = Color.Transparent;
if (startLocation.Type.HasOutpost && startLocation.Reputation != null) /*if (startLocation.Type.HasOutpost && startLocation.Reputation != null)
{ {
var iconStyle = GUIStyle.GetComponentStyle("LocationReputationIcon"); var iconStyle = GUIStyle.GetComponentStyle("LocationReputationIcon");
var locationFrame = CreateReputationElement( var locationFrame = CreateReputationElement(
@@ -411,7 +410,7 @@ namespace Barotrauma
startLocation.Type.Name, "", startLocation.Type.Name, "",
iconStyle?.GetDefaultSprite(), startLocation.Type.GetPortrait(0), iconStyle?.Color ?? Color.White); iconStyle?.GetDefaultSprite(), startLocation.Type.GetPortrait(0), iconStyle?.Color ?? Color.White);
CreatePathUnlockElement(locationFrame, null, startLocation); CreatePathUnlockElement(locationFrame, null, startLocation);
} }*/
foreach (Faction faction in campaignMode.Factions.OrderBy(f => f.Prefab.MenuOrder).ThenBy(f => f.Prefab.Name)) foreach (Faction faction in campaignMode.Factions.OrderBy(f => f.Prefab.MenuOrder).ThenBy(f => f.Prefab.Name))
{ {
@@ -462,27 +461,25 @@ namespace Barotrauma
if (!connection.Locked || (!connection.Locations[0].Discovered && !connection.Locations[1].Discovered)) { continue; } if (!connection.Locked || (!connection.Locations[0].Discovered && !connection.Locations[1].Discovered)) { continue; }
var gateLocation = connection.Locations[0].IsGateBetweenBiomes ? connection.Locations[0] : connection.Locations[1]; var gateLocation = connection.Locations[0].IsGateBetweenBiomes ? connection.Locations[0] : connection.Locations[1];
var unlockEvent = var unlockEvent = EventPrefab.GetUnlockPathEvent(gateLocation.LevelData.Biome.Identifier, gateLocation.Faction);
EventPrefab.Prefabs.FirstOrDefault(ep => ep.UnlockPathEvent && ep.BiomeIdentifier == gateLocation.LevelData.Biome.Identifier) ??
EventPrefab.Prefabs.FirstOrDefault(ep => ep.UnlockPathEvent && ep.BiomeIdentifier == Identifier.Empty);
if (unlockEvent == null) { continue; } if (unlockEvent == null) { continue; }
if (string.IsNullOrEmpty(unlockEvent.UnlockPathFaction) || unlockEvent.UnlockPathFaction.Equals("location", StringComparison.OrdinalIgnoreCase)) if (unlockEvent.Faction.IsEmpty)
{ {
if (location == null || gateLocation != location) { continue; } if (location == null || gateLocation != location) { continue; }
} }
else else
{ {
if (faction == null || faction.Prefab.Identifier != unlockEvent.UnlockPathFaction) { continue; } if (faction == null || faction.Prefab.Identifier != unlockEvent.Faction) { continue; }
} }
if (unlockEvent != null) if (unlockEvent != null)
{ {
Reputation unlockReputation = gateLocation.Reputation; Reputation unlockReputation = gateLocation.Reputation;
Faction unlockFaction = null; Faction unlockFaction = null;
if (!string.IsNullOrEmpty(unlockEvent.UnlockPathFaction)) if (!unlockEvent.Faction.IsEmpty)
{ {
unlockFaction = GameMain.GameSession.Campaign.Factions.Find(f => f.Prefab.Identifier == unlockEvent.UnlockPathFaction); unlockFaction = GameMain.GameSession.Campaign.Factions.Find(f => f.Prefab.Identifier == unlockEvent.Faction);
unlockReputation = unlockFaction?.Reputation; unlockReputation = unlockFaction?.Reputation;
} }
float normalizedUnlockReputation = MathUtils.InverseLerp(unlockReputation.MinReputation, unlockReputation.MaxReputation, unlockEvent.UnlockPathReputation); float normalizedUnlockReputation = MathUtils.InverseLerp(unlockReputation.MinReputation, unlockReputation.MaxReputation, unlockEvent.UnlockPathReputation);
@@ -506,7 +503,7 @@ namespace Barotrauma
private LocalizedString GetHeaderText(bool gameOver, CampaignMode.TransitionType transitionType) private LocalizedString GetHeaderText(bool gameOver, CampaignMode.TransitionType transitionType)
{ {
string locationName = Submarine.MainSub.AtEndExit ? endLocation?.Name : startLocation?.Name; string locationName = Submarine.MainSub is { AtEndExit: true } ? endLocation?.Name : startLocation?.Name;
string textTag; string textTag;
if (gameOver) if (gameOver)
@@ -543,6 +540,11 @@ namespace Barotrauma
} }
} }
if (startLocation?.Biome != null && startLocation.Biome.IsEndBiome)
{
locationName ??= startLocation.Name;
}
if (textTag == null) { return ""; } if (textTag == null) { return ""; }
if (locationName == null) if (locationName == null)
@@ -774,7 +774,6 @@ namespace Barotrauma
} }
else else
{ {
bool isEquippable = item.AllowedSlots.Any(s => s != InvSlotType.Any);
var selectedContainer = character.SelectedItem?.GetComponent<ItemContainer>(); var selectedContainer = character.SelectedItem?.GetComponent<ItemContainer>();
if (selectedContainer != null && if (selectedContainer != null &&
@@ -802,8 +801,7 @@ namespace Barotrauma
} }
else if (character.HeldItems.Any(i => else if (character.HeldItems.Any(i =>
i.OwnInventory != null && i.OwnInventory != null &&
/*disallow putting into equipped item if the item is equippable (equip as the quick action instead)*/ (i.OwnInventory.CanBePut(item) || ((i.OwnInventory.Capacity == 1 || i.OwnInventory.Container.HasSubContainers) && i.OwnInventory.AllowSwappingContainedItems && i.OwnInventory.Container.CanBeContained(item)))))
((i.OwnInventory.CanBePut(item) && (allowInventorySwap || !isEquippable)) || (i.OwnInventory.Capacity == 1 && i.OwnInventory.AllowSwappingContainedItems && i.OwnInventory.Container.CanBeContained(item)))))
{ {
return QuickUseAction.PutToEquippedItem; return QuickUseAction.PutToEquippedItem;
} }
@@ -977,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++)
@@ -25,14 +25,31 @@ namespace Barotrauma.Items.Components
foreach (Node node in nodes) foreach (Node node in nodes)
{ {
GameMain.ParticleManager.CreateParticle("swirlysmoke", node.WorldPosition, Vector2.Zero); GameMain.ParticleManager.CreateParticle("swirlysmoke", node.WorldPosition, Vector2.Zero);
if (node.ParentIndex > -1)
{
Vector2 diff = nodes[node.ParentIndex].WorldPosition - node.WorldPosition;
float dist = diff.Length();
Vector2 normalizedDiff = diff / dist;
for (float x = 0.0f; x < dist; x += 50.0f)
{
var spark = GameMain.ParticleManager.CreateParticle("ElectricShock", node.WorldPosition + normalizedDiff * x, Vector2.Zero);
if (spark != null)
{
spark.Size *= 0.3f;
}
}
}
} }
} }
public void DrawElectricity(SpriteBatch spriteBatch) public void DrawElectricity(SpriteBatch spriteBatch)
{ {
if (timer <= 0.0f) { return; }
for (int i = 0; i < nodes.Count; i++) for (int i = 0; i < nodes.Count; i++)
{ {
if (nodes[i].Length <= 1.0f) continue; if (nodes[i].Length <= 1.0f) { continue; }
var node = nodes[i]; var node = nodes[i];
electricitySprite.Draw(spriteBatch, electricitySprite.Draw(spriteBatch,
(i + frameOffset) % electricitySprite.FrameCount, (i + frameOffset) % electricitySprite.FrameCount,
@@ -46,10 +63,16 @@ namespace Barotrauma.Items.Components
if (GameMain.DebugDraw) if (GameMain.DebugDraw)
{ {
for (int i = 0; i < nodes.Count; i++) for (int i = 1; i < nodes.Count; i++)
{ {
if (nodes[i].Length <= 1.0f) continue; GUI.DrawLine(spriteBatch,
GUI.DrawRectangle(spriteBatch, new Vector2(nodes[i].WorldPosition.X, -nodes[i].WorldPosition.Y), Vector2.One * 5, Color.LightCyan, isFilled: true); new Vector2(nodes[i].WorldPosition.X, -nodes[i].WorldPosition.Y),
new Vector2(nodes[nodes[i].ParentIndex].WorldPosition.X, -nodes[nodes[i].ParentIndex].WorldPosition.Y),
Color.LightCyan,
width: 3);
if (nodes[i].Length <= 1.0f) { continue; }
GUI.DrawRectangle(spriteBatch, new Vector2(nodes[i].WorldPosition.X, -nodes[i].WorldPosition.Y), Vector2.One * 10, Color.LightCyan, isFilled: true);
} }
} }
} }
@@ -114,8 +114,8 @@ namespace Barotrauma.Items.Components
{ {
if (chargeSound != null) if (chargeSound != null)
{ {
chargeSoundChannel = SoundPlayer.PlaySound(chargeSound.Sound, item.WorldPosition, chargeSound.Volume, chargeSound.Range, ignoreMuffling: chargeSound.IgnoreMuffling); chargeSoundChannel = SoundPlayer.PlaySound(chargeSound.Sound, item.WorldPosition, chargeSound.Volume, chargeSound.Range, ignoreMuffling: chargeSound.IgnoreMuffling, freqMult: chargeSound.GetRandomFrequencyMultiplier());
if (chargeSoundChannel != null) chargeSoundChannel.Looping = true; if (chargeSoundChannel != null) { chargeSoundChannel.Looping = true; }
} }
} }
else if (chargeSoundChannel != null) else if (chargeSoundChannel != null)
@@ -224,68 +224,60 @@ namespace Barotrauma.Items.Components
if (character == null) { return false; } if (character == null) { return false; }
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);
@@ -524,11 +524,13 @@ namespace Barotrauma.Items.Components
VolumeProperty = subElement.GetAttributeIdentifier("volumeproperty", "") VolumeProperty = subElement.GetAttributeIdentifier("volumeproperty", "")
}; };
if (soundSelectionModes == null) soundSelectionModes = new Dictionary<ActionType, SoundSelectionMode>(); if (soundSelectionModes == null)
{
soundSelectionModes = new Dictionary<ActionType, SoundSelectionMode>();
}
if (!soundSelectionModes.ContainsKey(type) || soundSelectionModes[type] == SoundSelectionMode.Random) if (!soundSelectionModes.ContainsKey(type) || soundSelectionModes[type] == SoundSelectionMode.Random)
{ {
Enum.TryParse(subElement.GetAttributeString("selectionmode", "Random"), out SoundSelectionMode selectionMode); soundSelectionModes[type] = subElement.GetAttributeEnum("selectionmode", SoundSelectionMode.Random);
soundSelectionModes[type] = selectionMode;
} }
if (!sounds.TryGetValue(itemSound.Type, out List<ItemSound> soundList)) if (!sounds.TryGetValue(itemSound.Type, out List<ItemSound> soundList))
@@ -584,6 +586,8 @@ namespace Barotrauma.Items.Components
{ {
if (GuiFrame != null && GuiFrameSource.GetAttributeBool("draggable", true)) if (GuiFrame != null && GuiFrameSource.GetAttributeBool("draggable", true))
{ {
bool hideDragIcons = GuiFrameSource.GetAttributeBool("hidedragicons", false);
var handle = new GUIDragHandle(new RectTransform(Vector2.One, GuiFrame.RectTransform, Anchor.Center), var handle = new GUIDragHandle(new RectTransform(Vector2.One, GuiFrame.RectTransform, Anchor.Center),
GuiFrame.RectTransform, style: null) GuiFrame.RectTransform, style: null)
{ {
@@ -623,7 +627,7 @@ namespace Barotrauma.Items.Components
}; };
int buttonHeight = (int)(GUIStyle.ItemFrameMargin.Y * 0.4f); int buttonHeight = (int)(GUIStyle.ItemFrameMargin.Y * 0.4f);
new GUIButton(new RectTransform(new Point(buttonHeight), handle.RectTransform, Anchor.TopLeft) { AbsoluteOffset = new Point(buttonHeight / 4), MinSize = new Point(buttonHeight) }, var settingsIcon = new GUIButton(new RectTransform(new Point(buttonHeight), handle.RectTransform, Anchor.TopLeft) { AbsoluteOffset = new Point(buttonHeight / 4), MinSize = new Point(buttonHeight) },
style: "GUIButtonSettings") style: "GUIButtonSettings")
{ {
OnClicked = (btn, userdata) => OnClicked = (btn, userdata) =>
@@ -648,6 +652,12 @@ namespace Barotrauma.Items.Components
return true; return true;
} }
}; };
if (hideDragIcons)
{
dragIcon.Visible = false;
settingsIcon.Visible = false;
}
} }
} }
@@ -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;
@@ -1119,7 +1119,7 @@ namespace Barotrauma.Items.Components
if (it.GetComponent<PowerContainer>() is { } battery) if (it.GetComponent<PowerContainer>() is { } battery)
{ {
int batteryCapacity = (int)(battery.Charge / battery.Capacity * 100f); int batteryCapacity = (int)(battery.Charge / battery.GetCapacity() * 100f);
line2 = TextManager.GetWithVariable("statusmonitor.battery.tooltip", "[amount]", batteryCapacity.ToString()); line2 = TextManager.GetWithVariable("statusmonitor.battery.tooltip", "[amount]", batteryCapacity.ToString());
} }
else if (it.GetComponent<PowerTransfer>() is { } powerTransfer) else if (it.GetComponent<PowerTransfer>() is { } powerTransfer)
@@ -17,6 +17,7 @@ namespace Barotrauma.Items.Components
Default, Default,
Disruption, Disruption,
Destructible, Destructible,
Door,
LongRange LongRange
} }
@@ -110,6 +111,10 @@ namespace Barotrauma.Items.Components
BlipType.Destructible, BlipType.Destructible,
new Color[] { Color.TransparentBlack, new Color(74, 113, 75) * 0.8f, new Color(151, 236, 172) * 0.8f, new Color(153, 217, 234) * 0.8f } new Color[] { Color.TransparentBlack, new Color(74, 113, 75) * 0.8f, new Color(151, 236, 172) * 0.8f, new Color(153, 217, 234) * 0.8f }
}, },
{
BlipType.Door,
new Color[] { Color.TransparentBlack, new Color(73, 78, 86), new Color(66, 94, 100), new Color(47, 115, 58), new Color(255, 255, 255) }
},
{ {
BlipType.LongRange, BlipType.LongRange,
new Color[] { Color.TransparentBlack, Color.TransparentBlack, new Color(254, 68, 19) * 0.8f, Color.TransparentBlack } new Color[] { Color.TransparentBlack, Color.TransparentBlack, new Color(254, 68, 19) * 0.8f, Color.TransparentBlack }
@@ -811,7 +816,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),
@@ -971,7 +976,7 @@ namespace Barotrauma.Items.Components
if (GameMain.GameSession == null || Level.Loaded == null) { return; } if (GameMain.GameSession == null || Level.Loaded == null) { return; }
if (Level.Loaded.StartLocation != null) if (Level.Loaded.StartLocation?.Type is { ShowSonarMarker: true })
{ {
DrawMarker(spriteBatch, DrawMarker(spriteBatch,
Level.Loaded.StartLocation.Name, Level.Loaded.StartLocation.Name,
@@ -981,7 +986,7 @@ namespace Barotrauma.Items.Components
displayScale, center, DisplayRadius); displayScale, center, DisplayRadius);
} }
if (Level.Loaded.EndLocation != null && Level.Loaded.Type == LevelData.LevelType.LocationConnection) if (Level.Loaded is { EndLocation.Type.ShowSonarMarker: true, Type: LevelData.LevelType.LocationConnection })
{ {
DrawMarker(spriteBatch, DrawMarker(spriteBatch,
Level.Loaded.EndLocation.Name, Level.Loaded.EndLocation.Name,
@@ -1006,19 +1011,19 @@ namespace Barotrauma.Items.Components
int missionIndex = 0; int missionIndex = 0;
foreach (Mission mission in GameMain.GameSession.Missions) foreach (Mission mission in GameMain.GameSession.Missions)
{ {
if (!mission.SonarLabel.IsNullOrWhiteSpace()) int i = 0;
foreach ((LocalizedString label, Vector2 position) in mission.SonarLabels)
{ {
int i = 0; if (!string.IsNullOrEmpty(label.Value))
foreach (Vector2 sonarPosition in mission.SonarPositions)
{ {
DrawMarker(spriteBatch, DrawMarker(spriteBatch,
mission.SonarLabel.Value, label.Value,
mission.SonarIconIdentifier, mission.SonarIconIdentifier,
"mission" + missionIndex + ":" + i, "mission" + missionIndex + ":" + i,
sonarPosition, transducerCenter, position, transducerCenter,
displayScale, center, DisplayRadius * 0.95f); displayScale, center, DisplayRadius * 0.95f);
i++;
} }
i++;
} }
missionIndex++; missionIndex++;
} }
@@ -1276,7 +1281,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));
@@ -1344,6 +1349,38 @@ namespace Barotrauma.Items.Components
} }
} }
public void RegisterExplosion(Explosion explosion, Vector2 worldPosition)
{
if (Character.Controlled?.SelectedItem != item) { return; }
if (explosion.Attack.StructureDamage <= 0 && explosion.Attack.ItemDamage <= 0 && explosion.EmpStrength <= 0) { return; }
Vector2 transducerCenter = GetTransducerPos();
if (Vector2.DistanceSquared(worldPosition, transducerCenter) > range * range) { return; }
int blipCount = MathHelper.Clamp((int)(explosion.Attack.Range / 100.0f), 0, 50);
for (int i = 0; i < blipCount; i++)
{
sonarBlips.Add(new SonarBlip(
worldPosition + Rand.Vector(Rand.Range(0.0f, explosion.Attack.Range)),
1.0f,
Rand.Range(0.5f, 1.0f),
BlipType.Disruption));
}
if (explosion.EmpStrength > 0.0f)
{
int empBlipCount = MathHelper.Clamp((int)(blipCount * explosion.EmpStrength), 10, 50);
for (int i = 0; i < empBlipCount; i++)
{
Vector2 dir = Rand.Vector(1.0f);
var longRangeBlip = new SonarBlip(worldPosition, Rand.Range(1.9f, 2.1f), Rand.Range(1.0f, 1.5f), BlipType.LongRange)
{
Velocity = dir * MathUtils.Round(Rand.Range(4000.0f, 6000.0f), 1000.0f),
Rotation = (float)Math.Atan2(-dir.Y, dir.X)
};
longRangeBlip.Size.Y *= 4.0f;
sonarBlips.Add(longRangeBlip);
}
}
}
private void Ping(Vector2 pingSource, Vector2 transducerPos, float pingRadius, float prevPingRadius, float displayScale, float range, bool passive, private void Ping(Vector2 pingSource, Vector2 transducerPos, float pingRadius, float prevPingRadius, float displayScale, float range, bool passive,
float pingStrength = 1.0f) float pingStrength = 1.0f)
{ {
@@ -1388,6 +1425,14 @@ namespace Barotrauma.Items.Components
if (connectedSubs.Contains(submarine)) { continue; } if (connectedSubs.Contains(submarine)) { continue; }
} }
Rectangle worldBorders = Submarine.MainSub.GetDockedBorders();
worldBorders.Location += Submarine.MainSub.WorldPosition.ToPoint();
if (Submarine.RectContains(worldBorders, pingSource))
{
CreateBlipsForSubmarineWalls(submarine, pingSource, transducerPos, pingRadius, prevPingRadius, range, passive);
continue;
}
for (int i = 0; i < submarine.HullVertices.Count; i++) for (int i = 0; i < submarine.HullVertices.Count; i++)
{ {
Vector2 start = ConvertUnits.ToDisplayUnits(submarine.HullVertices[i]); Vector2 start = ConvertUnits.ToDisplayUnits(submarine.HullVertices[i]);
@@ -1586,6 +1631,40 @@ namespace Barotrauma.Items.Components
} }
} }
private void CreateBlipsForSubmarineWalls(Submarine sub, Vector2 pingSource, Vector2 transducerPos, float pingRadius, float prevPingRadius, float range, bool passive)
{
foreach (Structure structure in Structure.WallList)
{
if (structure.Submarine != sub) { continue; }
CreateBlips(structure.IsHorizontal, structure.WorldPosition, structure.WorldRect);
}
foreach (var door in Door.DoorList)
{
if (door.Item.Submarine != sub || door.IsOpen) { continue; }
CreateBlips(door.IsHorizontal, door.Item.WorldPosition, door.Item.WorldRect, BlipType.Door);
}
void CreateBlips(bool isHorizontal, Vector2 worldPos, Rectangle worldRect, BlipType blipType = BlipType.Default)
{
Vector2 point1, point2;
if (isHorizontal)
{
point1 = new Vector2(worldRect.X, worldPos.Y);
point2 = new Vector2(worldRect.Right, worldPos.Y);
}
else
{
point1 = new Vector2(worldPos.X, worldRect.Y);
point2 = new Vector2(worldPos.X, worldRect.Y - worldRect.Height);
}
CreateBlipsForLine(
point1,
point2,
pingSource, transducerPos,
pingRadius, prevPingRadius, 50.0f, 5.0f, range, 2.0f, passive, blipType);
}
}
private bool CheckBlipVisibility(SonarBlip blip, Vector2 transducerPos) private bool CheckBlipVisibility(SonarBlip blip, Vector2 transducerPos)
{ {
Vector2 pos = (blip.Position - transducerPos) * displayScale * zoom; Vector2 pos = (blip.Position - transducerPos) * displayScale * zoom;
@@ -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
@@ -340,6 +340,10 @@ namespace Barotrauma.Items.Components
centerText = $"({TextManager.Get("Meter")})"; centerText = $"({TextManager.Get("Meter")})";
rightTextGetter = () => rightTextGetter = () =>
{ {
if (Level.Loaded is { IsEndBiome: true })
{
return Timing.TotalTime % 5.0f < 0.5f ? Rand.Range(-9000, 9000).ToString() : "ERROR";
}
float realWorldDepth = controlledSub == null ? -1000.0f : controlledSub.RealWorldDepth; float realWorldDepth = controlledSub == null ? -1000.0f : controlledSub.RealWorldDepth;
return ((int)realWorldDepth).ToString(); return ((int)realWorldDepth).ToString();
}; };
@@ -382,15 +386,26 @@ 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))
{ {
// Docking to an outpost // Docking to an outpost
var subsToLeaveBehind = campaign.GetSubsToLeaveBehind(Item.Submarine); var subsToLeaveBehind = CampaignMode.GetSubsToLeaveBehind(Item.Submarine);
if (subsToLeaveBehind.Any()) if (subsToLeaveBehind.Any())
{ {
enterOutpostPrompt = new GUIMessageBox( enterOutpostPrompt = new GUIMessageBox(
@@ -419,6 +434,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 +454,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 +937,7 @@ namespace Barotrauma.Items.Components
maintainPosOriginIndicator?.Remove(); maintainPosOriginIndicator?.Remove();
steeringIndicator?.Remove(); steeringIndicator?.Remove();
enterOutpostPrompt?.Close(); enterOutpostPrompt?.Close();
exitOutpostPrompt?.Close();
pathFinder = null; pathFinder = null;
} }
@@ -133,35 +133,43 @@ namespace Barotrauma.Items.Components
public void Draw(SpriteBatch spriteBatch, bool editing = false, float itemDepth = -1) public void Draw(SpriteBatch spriteBatch, bool editing = false, float itemDepth = -1)
{ {
if (indicatorSize.X <= 1.0f || indicatorSize.Y <= 1.0f) { return; } Vector2 scaledIndicatorSize = indicatorSize * item.Scale;
if (scaledIndicatorSize.X <= 2.0f || scaledIndicatorSize.Y <= 2.0f) { return; }
const float outlineThickness = 1.0f;
Vector2 itemSize = new Vector2(item.Sprite.SourceRect.Width, item.Sprite.SourceRect.Height) * item.Scale; Vector2 itemSize = new Vector2(item.Sprite.SourceRect.Width, item.Sprite.SourceRect.Height) * item.Scale;
Vector2 indicatorPos = -itemSize / 2 + indicatorPosition * item.Scale; Vector2 indicatorPos = -itemSize / 2.0f + indicatorPosition * item.Scale;
if (item.FlippedX && item.Prefab.CanSpriteFlipX) { indicatorPos.X = -indicatorPos.X - indicatorSize.X * item.Scale; } Vector2 itemPosition = new Vector2(item.DrawPosition.X, -item.DrawPosition.Y);
if (item.FlippedY && item.Prefab.CanSpriteFlipY) { indicatorPos.Y = -indicatorPos.Y - indicatorSize.Y * item.Scale; } Vector2 flip = new Vector2(item.FlippedX && item.Prefab.CanSpriteFlipX ? -1.0f : 1.0f, item.FlippedY && item.Prefab.CanSpriteFlipY ? -1.0f : 1.0f);
Matrix rotate = Matrix.CreateRotationZ(item.RotationRad);
Vector2 center = Vector2.Transform((indicatorPos + (scaledIndicatorSize * 0.5f)) * flip, rotate) + itemPosition;
if (charge > 0 && capacity > 0) if (charge > 0 && capacity > 0)
{ {
float chargeRatio = MathHelper.Clamp(charge / capacity, 0.0f, 1.0f); float chargeRatio = MathHelper.Clamp(charge / capacity, 0.0f, 1.0f);
Color indicatorColor = ToolBox.GradientLerp(chargeRatio, Color.Red, Color.Orange, Color.Green); Color indicatorColor = ToolBox.GradientLerp(chargeRatio, Color.Red, Color.Orange, Color.Green);
if (!isHorizontal) Vector2 indicatorCenter = (indicatorPos + (scaledIndicatorSize * 0.5f)) * flip;
Vector2 indicatorSize;
if (isHorizontal)
{ {
GUI.DrawRectangle(spriteBatch, float indicatorLength = (scaledIndicatorSize.X - outlineThickness * 2.0f) * chargeRatio;
new Vector2(item.DrawPosition.X, -item.DrawPosition.Y + ((indicatorSize.Y * item.Scale) * (1.0f - chargeRatio))) + indicatorPos, indicatorCenter.X += -scaledIndicatorSize.X * 0.5f + (flipIndicator ? scaledIndicatorSize.X - outlineThickness - indicatorLength * 0.5f : outlineThickness + indicatorLength * 0.5f);
new Vector2(indicatorSize.X * item.Scale, (indicatorSize.Y * item.Scale) * chargeRatio), indicatorColor, true, indicatorSize = new Vector2(indicatorLength, scaledIndicatorSize.Y);
depth: item.SpriteDepth - 0.00001f);
} }
else else
{ {
GUI.DrawRectangle(spriteBatch, float indicatorLength = (scaledIndicatorSize.Y - outlineThickness * 2.0f) * chargeRatio;
new Vector2(item.DrawPosition.X, -item.DrawPosition.Y) + indicatorPos, indicatorCenter.Y += -scaledIndicatorSize.Y * 0.5f + (flipIndicator ? outlineThickness + indicatorLength * 0.5f : scaledIndicatorSize.Y - outlineThickness - indicatorLength * 0.5f);
new Vector2((indicatorSize.X * item.Scale) * chargeRatio, indicatorSize.Y * item.Scale), indicatorColor, true, indicatorSize = new Vector2(scaledIndicatorSize.X, indicatorLength);
depth: item.SpriteDepth - 0.00001f);
} }
indicatorCenter = Vector2.Transform(indicatorCenter, rotate) + itemPosition;
GUI.DrawFilledRectangle(spriteBatch, indicatorCenter, indicatorSize, indicatorSize * 0.5f, item.RotationRad, indicatorColor, item.SpriteDepth - 0.00001f);
} }
GUI.DrawRectangle(spriteBatch,
new Vector2(item.DrawPosition.X, -item.DrawPosition.Y) + indicatorPos, GUI.DrawRectangle(spriteBatch, center, scaledIndicatorSize, scaledIndicatorSize * 0.5f, item.RotationRad, Color.Black, item.SpriteDepth - 0.000015f, outlineThickness, GUI.OutlinePosition.Inside);
indicatorSize * item.Scale, Color.Black, depth: item.SpriteDepth - 0.000015f);
} }
public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData) public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData)
@@ -1,4 +1,5 @@
using Barotrauma.Networking; using Barotrauma.Extensions;
using Barotrauma.Networking;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System.Linq; using System.Linq;
using System.Xml.Linq; using System.Xml.Linq;
@@ -24,7 +25,9 @@ namespace Barotrauma.Items.Components
partial void InitProjSpecific(XElement element) partial void InitProjSpecific(XElement element)
{ {
var layoutGroup = new GUILayoutGroup(new RectTransform(GuiFrame.Rect.Size - GUIStyle.ItemFrameMargin, GuiFrame.RectTransform, Anchor.Center) { AbsoluteOffset = GUIStyle.ItemFrameOffset }) float marginMultiplier = element.GetAttributeFloat("marginmultiplier", 1.0f);
var layoutGroup = new GUILayoutGroup(new RectTransform(GuiFrame.Rect.Size - GUIStyle.ItemFrameMargin.Multiply(marginMultiplier), GuiFrame.RectTransform, Anchor.Center) { AbsoluteOffset = GUIStyle.ItemFrameOffset.Multiply(marginMultiplier) })
{ {
ChildAnchor = Anchor.TopCenter, ChildAnchor = Anchor.TopCenter,
RelativeSpacing = 0.02f, RelativeSpacing = 0.02f,
@@ -33,31 +36,34 @@ namespace Barotrauma.Items.Components
historyBox = new GUIListBox(new RectTransform(new Vector2(1, .9f), layoutGroup.RectTransform), style: null) historyBox = new GUIListBox(new RectTransform(new Vector2(1, .9f), layoutGroup.RectTransform), style: null)
{ {
AutoHideScrollBar = false AutoHideScrollBar = this.AutoHideScrollbar
}; };
CreateFillerBlock(); if (!Readonly)
new GUIFrame(new RectTransform(new Vector2(0.9f, 0.01f), layoutGroup.RectTransform), style: "HorizontalLine");
inputBox = new GUITextBox(new RectTransform(new Vector2(1, .1f), layoutGroup.RectTransform), textColor: TextColor)
{ {
MaxTextLength = MaxMessageLength, CreateFillerBlock();
OverflowClip = true,
OnEnterPressed = (GUITextBox textBox, string text) => new GUIFrame(new RectTransform(new Vector2(0.9f, 0.01f), layoutGroup.RectTransform), style: "HorizontalLine");
inputBox = new GUITextBox(new RectTransform(new Vector2(1, .1f), layoutGroup.RectTransform), textColor: TextColor)
{ {
if (GameMain.NetworkMember == null) MaxTextLength = MaxMessageLength,
OverflowClip = true,
OnEnterPressed = (GUITextBox textBox, string text) =>
{ {
SendOutput(text); if (GameMain.NetworkMember == null)
{
SendOutput(text);
}
else
{
item.CreateClientEvent(this, new ClientEventData(text));
}
textBox.Text = string.Empty;
return true;
} }
else };
{ }
item.CreateClientEvent(this, new ClientEventData(text));
}
textBox.Text = string.Empty;
return true;
}
};
layoutGroup.Recalculate(); layoutGroup.Recalculate();
} }
@@ -101,7 +107,7 @@ namespace Barotrauma.Items.Components
GUITextBlock newBlock = new GUITextBlock( GUITextBlock newBlock = new GUITextBlock(
new RectTransform(new Vector2(1, 0), historyBox.Content.RectTransform, anchor: Anchor.TopCenter), new RectTransform(new Vector2(1, 0), historyBox.Content.RectTransform, anchor: Anchor.TopCenter),
"> " + input, LineStartSymbol + TextManager.Get(input).Fallback(input),
textColor: color, wrap: true, font: UseMonospaceFont ? GUIStyle.MonospacedFont : GUIStyle.Font) textColor: color, wrap: true, font: UseMonospaceFont ? GUIStyle.MonospacedFont : GUIStyle.Font)
{ {
CanBeFocused = false CanBeFocused = false
@@ -123,7 +129,10 @@ namespace Barotrauma.Items.Components
historyBox.RecalculateChildren(); historyBox.RecalculateChildren();
historyBox.UpdateScrollBarSize(); historyBox.UpdateScrollBarSize();
historyBox.ScrollBar.BarScrollValue = 1; if (AutoScrollToBottom)
{
historyBox.ScrollBar.BarScrollValue = 1;
}
} }
public override bool Select(Character character) public override bool Select(Character character)
@@ -138,7 +147,7 @@ namespace Barotrauma.Items.Components
public override void AddToGUIUpdateList(int order = 0) public override void AddToGUIUpdateList(int order = 0)
{ {
base.AddToGUIUpdateList(order: order); base.AddToGUIUpdateList(order: order);
if (shouldSelectInputBox) if (shouldSelectInputBox && !Readonly)
{ {
inputBox.Select(); inputBox.Select();
shouldSelectInputBox = false; shouldSelectInputBox = false;
@@ -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));
} }
@@ -212,14 +212,14 @@ namespace Barotrauma.Items.Components
{ {
if (moveSoundChannel == null && startMoveSound != null) if (moveSoundChannel == null && startMoveSound != null)
{ {
moveSoundChannel = SoundPlayer.PlaySound(startMoveSound.Sound, item.WorldPosition, startMoveSound.Volume, startMoveSound.Range, ignoreMuffling: startMoveSound.IgnoreMuffling); moveSoundChannel = SoundPlayer.PlaySound(startMoveSound.Sound, item.WorldPosition, startMoveSound.Volume, startMoveSound.Range, ignoreMuffling: startMoveSound.IgnoreMuffling, freqMult: startMoveSound.GetRandomFrequencyMultiplier());
} }
else if (moveSoundChannel == null || !moveSoundChannel.IsPlaying) else if (moveSoundChannel == null || !moveSoundChannel.IsPlaying)
{ {
if (moveSound != null) if (moveSound != null)
{ {
moveSoundChannel.FadeOutAndDispose(); moveSoundChannel.FadeOutAndDispose();
moveSoundChannel = SoundPlayer.PlaySound(moveSound.Sound, item.WorldPosition, moveSound.Volume, moveSound.Range, ignoreMuffling: moveSound.IgnoreMuffling); moveSoundChannel = SoundPlayer.PlaySound(moveSound.Sound, item.WorldPosition, moveSound.Volume, moveSound.Range, ignoreMuffling: moveSound.IgnoreMuffling, freqMult: moveSound.GetRandomFrequencyMultiplier());
if (moveSoundChannel != null) moveSoundChannel.Looping = true; if (moveSoundChannel != null) moveSoundChannel.Looping = true;
} }
} }
@@ -231,7 +231,7 @@ namespace Barotrauma.Items.Components
if (endMoveSound != null && moveSoundChannel.Sound != endMoveSound.Sound) if (endMoveSound != null && moveSoundChannel.Sound != endMoveSound.Sound)
{ {
moveSoundChannel.FadeOutAndDispose(); moveSoundChannel.FadeOutAndDispose();
moveSoundChannel = SoundPlayer.PlaySound(endMoveSound.Sound, item.WorldPosition, endMoveSound.Volume, endMoveSound.Range, ignoreMuffling: endMoveSound.IgnoreMuffling); moveSoundChannel = SoundPlayer.PlaySound(endMoveSound.Sound, item.WorldPosition, endMoveSound.Volume, endMoveSound.Range, ignoreMuffling: endMoveSound.IgnoreMuffling, freqMult: endMoveSound.GetRandomFrequencyMultiplier());
if (moveSoundChannel != null) moveSoundChannel.Looping = false; if (moveSoundChannel != null) moveSoundChannel.Looping = false;
} }
else if (!moveSoundChannel.IsPlaying) else if (!moveSoundChannel.IsPlaying)
@@ -260,7 +260,7 @@ namespace Barotrauma.Items.Components
{ {
if (chargeSound != null) if (chargeSound != null)
{ {
chargeSoundChannel = SoundPlayer.PlaySound(chargeSound.Sound, item.WorldPosition, chargeSound.Volume, chargeSound.Range, ignoreMuffling: chargeSound.IgnoreMuffling); chargeSoundChannel = SoundPlayer.PlaySound(chargeSound.Sound, item.WorldPosition, chargeSound.Volume, chargeSound.Range, ignoreMuffling: chargeSound.IgnoreMuffling, freqMult: chargeSound.GetRandomFrequencyMultiplier());
if (chargeSoundChannel != null) chargeSoundChannel.Looping = true; if (chargeSoundChannel != null) chargeSoundChannel.Looping = true;
} }
} }
@@ -581,7 +581,7 @@ namespace Barotrauma.Items.Components
var battery = recipient.Item?.GetComponent<PowerContainer>(); var battery = recipient.Item?.GetComponent<PowerContainer>();
if (battery == null || battery.Item.Condition <= 0.0f) { continue; } if (battery == null || battery.Item.Condition <= 0.0f) { continue; }
availableCharge += battery.Charge; availableCharge += battery.Charge;
availableCapacity += battery.Capacity; availableCapacity += battery.GetCapacity();
} }
} }
@@ -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; }
@@ -19,8 +20,13 @@ namespace Barotrauma.Items.Components
if (!description.IsNullOrWhiteSpace()) { description += '\n'; } if (!description.IsNullOrWhiteSpace()) { description += '\n'; }
description += $" ‖color:{colorStr}‖{roundedValue.ToString("-0;+#")}%‖color:end‖ {afflictionName}"; description += $" ‖color:{colorStr}‖{roundedValue.ToString("-0;+#")}%‖color:end‖ {afflictionName}";
} }
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,20 +1610,26 @@ 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();
int targetSlot = Math.Max(itemContainer.ContainedStateIndicatorSlot, 0);
var containedItem = itemContainer.Inventory.slots[targetSlot].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(targetSlot));
if (maxStackSize > 1 || containedItem.Prefab.HideConditionBar) if (maxStackSize > 1 || containedItem.Prefab.HideConditionBar)
{ {
containedState = itemContainer.Inventory.slots[0].Items.Count / (float)maxStackSize; containedState = itemContainer.Inventory.slots[targetSlot].Items.Count / (float)maxStackSize;
} }
} }
} }
@@ -301,7 +301,9 @@ namespace Barotrauma
BrokenItemSprite fadeInBrokenSprite = null; BrokenItemSprite fadeInBrokenSprite = null;
float fadeInBrokenSpriteAlpha = 0.0f; float fadeInBrokenSpriteAlpha = 0.0f;
float displayCondition = FakeBroken ? 0.0f : ConditionPercentage; float displayCondition = FakeBroken ? 0.0f : ConditionPercentage;
Vector2 drawOffset = Vector2.Zero; Vector2 drawOffset = GetCollapseEffectOffset();
drawOffset.Y = -drawOffset.Y;
if (displayCondition < MaxCondition) if (displayCondition < MaxCondition)
{ {
for (int i = 0; i < Prefab.BrokenSprites.Length; i++) for (int i = 0; i < Prefab.BrokenSprites.Length; i++)
@@ -417,6 +419,8 @@ namespace Barotrauma
var holdable = GetComponent<Holdable>(); var holdable = GetComponent<Holdable>();
if (holdable != null && holdable.Picker?.AnimController != null) if (holdable != null && holdable.Picker?.AnimController != null)
{ {
//don't draw the item on hands if it's also being worn
if (GetComponent<Wearable>() is { IsActive: true }) { return; }
if (!back) { return; } if (!back) { return; }
float depthStep = 0.000001f; float depthStep = 0.000001f;
if (holdable.Picker.Inventory?.GetItemInLimbSlot(InvSlotType.RightHand) == this) if (holdable.Picker.Inventory?.GetItemInLimbSlot(InvSlotType.RightHand) == this)
@@ -1415,6 +1419,15 @@ namespace Barotrauma
case EventType.ChangeProperty: case EventType.ChangeProperty:
ReadPropertyChange(msg, false); ReadPropertyChange(msg, false);
break; break;
case EventType.ItemStat:
byte length = msg.ReadByte();
for (int i = 0; i < length; i++)
{
var statIdentifier = INetSerializableStruct.Read<ItemStatManager.TalentStatIdentifier>(msg);
var statValue = msg.ReadSingle();
StatManager.ApplyStat(statIdentifier, statValue);
}
break;
case EventType.Upgrade: case EventType.Upgrade:
Identifier identifier = msg.ReadIdentifier(); Identifier identifier = msg.ReadIdentifier();
byte level = msg.ReadByte(); byte level = msg.ReadByte();
@@ -1654,25 +1667,24 @@ namespace Barotrauma
bool hasIdCard = msg.ReadBoolean(); bool hasIdCard = msg.ReadBoolean();
string ownerName = "", ownerTags = ""; string ownerName = "", ownerTags = "";
int ownerBeardIndex = -1, ownerHairIndex = -1, ownerMoustacheIndex = -1, ownerFaceAttachmentIndex = -1; int ownerBeardIndex = -1, ownerHairIndex = -1, ownerMoustacheIndex = -1, ownerFaceAttachmentIndex = -1;
Color ownerHairColor = Microsoft.Xna.Framework.Color.White, Color ownerHairColor = Color.White,
ownerFacialHairColor = Microsoft.Xna.Framework.Color.White, ownerFacialHairColor = Color.White,
ownerSkinColor = Microsoft.Xna.Framework.Color.White; ownerSkinColor = Color.White;
Identifier ownerJobId = Identifier.Empty; Identifier ownerJobId = Identifier.Empty;
Vector2 ownerSheetIndex = Vector2.Zero; Vector2 ownerSheetIndex = Vector2.Zero;
int submarineSpecificId = 0;
if (hasIdCard) if (hasIdCard)
{ {
submarineSpecificId = msg.ReadInt32();
ownerName = msg.ReadString(); ownerName = msg.ReadString();
ownerTags = msg.ReadString(); ownerTags = msg.ReadString();
ownerBeardIndex = msg.ReadByte() - 1; ownerBeardIndex = msg.ReadByte() - 1;
ownerHairIndex = msg.ReadByte() - 1; ownerHairIndex = msg.ReadByte() - 1;
ownerMoustacheIndex = msg.ReadByte() - 1; ownerMoustacheIndex = msg.ReadByte() - 1;
ownerFaceAttachmentIndex = msg.ReadByte() - 1; ownerFaceAttachmentIndex = msg.ReadByte() - 1;
ownerHairColor = msg.ReadColorR8G8B8(); ownerHairColor = msg.ReadColorR8G8B8();
ownerFacialHairColor = msg.ReadColorR8G8B8(); ownerFacialHairColor = msg.ReadColorR8G8B8();
ownerSkinColor = msg.ReadColorR8G8B8(); ownerSkinColor = msg.ReadColorR8G8B8();
ownerJobId = msg.ReadIdentifier(); ownerJobId = msg.ReadIdentifier();
int x = msg.ReadByte(); int x = msg.ReadByte();
@@ -1776,6 +1788,7 @@ namespace Barotrauma
} }
foreach (IdCard idCard in item.GetComponents<IdCard>()) foreach (IdCard idCard in item.GetComponents<IdCard>())
{ {
idCard.SubmarineSpecificID = submarineSpecificId;
idCard.TeamID = (CharacterTeamType)teamID; idCard.TeamID = (CharacterTeamType)teamID;
idCard.OwnerName = ownerName; idCard.OwnerName = ownerName;
idCard.OwnerTags = ownerTags; idCard.OwnerTags = ownerTags;
@@ -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();
@@ -208,6 +238,25 @@ namespace Barotrauma
DecorativeSpriteGroups = decorativeSpriteGroups.Select(kvp => (kvp.Key, kvp.Value.ToImmutableArray())).ToImmutableDictionary(); DecorativeSpriteGroups = decorativeSpriteGroups.Select(kvp => (kvp.Key, kvp.Value.ToImmutableArray())).ToImmutableDictionary();
} }
public bool CanCharacterBuy()
{
if (!DefaultPrice.RequiresUnlock) { return true; }
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)
{ {
Vector2 position = Submarine.MouseToWorldGrid(cam, Submarine.MainSub); Vector2 position = Submarine.MouseToWorldGrid(cam, Submarine.MainSub);
@@ -313,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);
} }
} }
} }
@@ -114,7 +114,11 @@ namespace Barotrauma
graphics.Clear(BackgroundColor); graphics.Clear(BackgroundColor);
renderer?.DrawBackground(spriteBatch, cam, LevelObjectManager, backgroundCreatureManager); if (renderer != null)
{
GameMain.LightManager.AmbientLight = GameMain.LightManager.AmbientLight.Add(renderer.FlashColor);
renderer?.DrawBackground(spriteBatch, cam, LevelObjectManager, backgroundCreatureManager);
}
} }
public void DrawFront(SpriteBatch spriteBatch, Camera cam) public void DrawFront(SpriteBatch spriteBatch, Camera cam)
@@ -226,7 +226,7 @@ namespace Barotrauma
{ {
if (SoundChannels[i] == null || !SoundChannels[i].IsPlaying) if (SoundChannels[i] == null || !SoundChannels[i].IsPlaying)
{ {
SoundChannels[i] = roundSound.Sound.Play(roundSound.Volume, roundSound.Range, soundPos); SoundChannels[i] = roundSound.Sound.Play(roundSound.Volume, roundSound.Range, roundSound.GetRandomFrequencyMultiplier(), soundPos);
} }
SoundChannels[i].Position = new Vector3(soundPos.X, soundPos.Y, 0.0f); SoundChannels[i].Position = new Vector3(soundPos.X, soundPos.Y, 0.0f);
} }
@@ -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));
} }
@@ -1,4 +1,4 @@
using FarseerPhysics; using Barotrauma.Extensions;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using System; using System;
@@ -71,9 +71,12 @@ namespace Barotrauma
{ {
private static BasicEffect wallEdgeEffect, wallCenterEffect; private static BasicEffect wallEdgeEffect, wallCenterEffect;
private Vector2 dustOffset; private Vector2 waterParticleOffset;
private Vector2 defaultDustVelocity; private Vector2 waterParticleVelocity;
private Vector2 dustVelocity;
private float flashCooldown;
private float flashTimer;
public Color FlashColor { get; private set; }
private readonly RasterizerState cullNone; private readonly RasterizerState cullNone;
@@ -81,10 +84,26 @@ namespace Barotrauma
private readonly List<LevelWallVertexBuffer> vertexBuffers = new List<LevelWallVertexBuffer>(); private readonly List<LevelWallVertexBuffer> vertexBuffers = new List<LevelWallVertexBuffer>();
private float chromaticAberrationStrength;
public float ChromaticAberrationStrength
{
get { return chromaticAberrationStrength; }
set { chromaticAberrationStrength = MathHelper.Clamp(value, 0.0f, 100.0f); }
}
public float CollapseEffectStrength
{
get;
set;
}
public Vector2 CollapseEffectOrigin
{
get;
set;
}
public LevelRenderer(Level level) public LevelRenderer(Level level)
{ {
defaultDustVelocity = Vector2.UnitY * 10.0f;
cullNone = new RasterizerState() { CullMode = CullMode.None }; cullNone = new RasterizerState() { CullMode = CullMode.None };
if (wallEdgeEffect == null) if (wallEdgeEffect == null)
@@ -120,12 +139,50 @@ namespace Barotrauma
level.GenerationParams.WallSprite.ReloadTexture(); level.GenerationParams.WallSprite.ReloadTexture();
wallCenterEffect.Texture = level.GenerationParams.WallSprite.Texture; wallCenterEffect.Texture = level.GenerationParams.WallSprite.Texture;
} }
public void Flash()
{
flashTimer = 1.0f;
}
public void Update(float deltaTime, Camera cam) public void Update(float deltaTime, Camera cam)
{ {
if (CollapseEffectStrength > 0.0f)
{
CollapseEffectStrength = Math.Max(0.0f, CollapseEffectStrength - deltaTime);
}
if (ChromaticAberrationStrength > 0.0f)
{
ChromaticAberrationStrength = Math.Max(0.0f, ChromaticAberrationStrength - deltaTime * 10.0f);
}
if (level.GenerationParams.FlashInterval.Y > 0)
{
flashCooldown -= deltaTime;
if (flashCooldown <= 0.0f)
{
flashTimer = 1.0f;
if (level.GenerationParams.FlashSound != null)
{
level.GenerationParams.FlashSound.Play(1.0f, "default");
}
flashCooldown = Rand.Range(level.GenerationParams.FlashInterval.X, level.GenerationParams.FlashInterval.Y, Rand.RandSync.Unsynced);
}
if (flashTimer > 0.0f)
{
float brightness = flashTimer * 1.1f - PerlinNoise.GetPerlin((float)Timing.TotalTime, (float)Timing.TotalTime * 0.66f) * 0.1f;
FlashColor = level.GenerationParams.FlashColor.Multiply(MathHelper.Clamp(brightness, 0.0f, 1.0f));
flashTimer -= deltaTime * 0.5f;
}
else
{
FlashColor = Color.TransparentBlack;
}
}
//calculate the sum of the forces of nearby level triggers //calculate the sum of the forces of nearby level triggers
//and use it to move the dust texture and water distortion effect //and use it to move the water texture and water distortion effect
Vector2 currentDustVel = defaultDustVelocity; Vector2 currentWaterParticleVel = level.GenerationParams.WaterParticleVelocity;
foreach (LevelObject levelObject in level.LevelObjectManager.GetVisibleObjects()) foreach (LevelObject levelObject in level.LevelObjectManager.GetVisibleObjects())
{ {
if (levelObject.Triggers == null) { continue; } if (levelObject.Triggers == null) { continue; }
@@ -139,21 +196,21 @@ namespace Barotrauma
objectMaxFlow = vel; objectMaxFlow = vel;
} }
} }
currentDustVel += objectMaxFlow; currentWaterParticleVel += objectMaxFlow;
} }
waterParticleVelocity = Vector2.Lerp(waterParticleVelocity, currentWaterParticleVel, deltaTime);
dustVelocity = Vector2.Lerp(dustVelocity, currentDustVel, deltaTime); WaterRenderer.Instance?.ScrollWater(waterParticleVelocity, deltaTime);
WaterRenderer.Instance?.ScrollWater(dustVelocity, deltaTime);
if (level.GenerationParams.WaterParticles != null) if (level.GenerationParams.WaterParticles != null)
{ {
Vector2 waterTextureSize = level.GenerationParams.WaterParticles.size * level.GenerationParams.WaterParticleScale; Vector2 waterTextureSize = level.GenerationParams.WaterParticles.size * level.GenerationParams.WaterParticleScale;
dustOffset += new Vector2(dustVelocity.X, -dustVelocity.Y) * level.GenerationParams.WaterParticleScale * deltaTime; waterParticleOffset += new Vector2(waterParticleVelocity.X, -waterParticleVelocity.Y) * level.GenerationParams.WaterParticleScale * deltaTime;
while (dustOffset.X <= -waterTextureSize.X) dustOffset.X += waterTextureSize.X; while (waterParticleOffset.X <= -waterTextureSize.X) { waterParticleOffset.X += waterTextureSize.X; }
while (dustOffset.X >= waterTextureSize.X) dustOffset.X -= waterTextureSize.X; while (waterParticleOffset.X >= waterTextureSize.X){ waterParticleOffset.X -= waterTextureSize.X; }
while (dustOffset.Y <= -waterTextureSize.Y) dustOffset.Y += waterTextureSize.Y; while (waterParticleOffset.Y <= -waterTextureSize.Y) { waterParticleOffset.Y += waterTextureSize.Y; }
while (dustOffset.Y >= waterTextureSize.Y) dustOffset.Y -= waterTextureSize.Y; while (waterParticleOffset.Y >= waterTextureSize.Y) { waterParticleOffset.Y -= waterTextureSize.Y; }
} }
} }
@@ -234,7 +291,7 @@ namespace Barotrauma
Rectangle srcRect = new Rectangle(0, 0, 2048, 2048); Rectangle srcRect = new Rectangle(0, 0, 2048, 2048);
Vector2 origin = new Vector2(cam.WorldView.X, -cam.WorldView.Y); Vector2 origin = new Vector2(cam.WorldView.X, -cam.WorldView.Y);
Vector2 offset = -origin + dustOffset; Vector2 offset = -origin + waterParticleOffset;
while (offset.X <= -srcRect.Width * textureScale) offset.X += srcRect.Width * textureScale; while (offset.X <= -srcRect.Width * textureScale) offset.X += srcRect.Width * textureScale;
while (offset.X > 0.0f) offset.X -= srcRect.Width * textureScale; while (offset.X > 0.0f) offset.X -= srcRect.Width * textureScale;
while (offset.Y <= -srcRect.Height * textureScale) offset.Y += srcRect.Height * textureScale; while (offset.Y <= -srcRect.Height * textureScale) offset.Y += srcRect.Height * textureScale;
@@ -261,7 +318,7 @@ namespace Barotrauma
level.GenerationParams.WaterParticles.DrawTiled( level.GenerationParams.WaterParticles.DrawTiled(
spriteBatch, origin + offsetS, spriteBatch, origin + offsetS,
new Vector2(cam.WorldView.Width - offsetS.X, cam.WorldView.Height - offsetS.Y), new Vector2(cam.WorldView.Width - offsetS.X, cam.WorldView.Height - offsetS.Y),
color: Color.White * alpha, textureScale: new Vector2(texScale)); color: level.GenerationParams.WaterParticleColor * alpha, textureScale: new Vector2(texScale));
} }
} }
spriteBatch.End(); spriteBatch.End();
@@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Linq; using System.Linq;
using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Input;
using Barotrauma.Extensions;
namespace Barotrauma namespace Barotrauma
{ {
@@ -98,7 +99,7 @@ namespace Barotrauma
OnClicked = (btn, userData) => OnClicked = (btn, userData) =>
{ {
Rand.SetSyncedSeed(ToolBox.StringToInt(this.Seed)); Rand.SetSyncedSeed(ToolBox.StringToInt(this.Seed));
Generate(GameMain.GameSession.GameMode is CampaignMode campaign ? campaign.Settings : CampaignSettings.Empty); Generate(GameMain.GameSession?.Campaign);
InitProjectSpecific(); InitProjectSpecific();
return true; return true;
} }
@@ -186,7 +187,7 @@ namespace Barotrauma
private void LocationChanged(Location prevLocation, Location newLocation) private void LocationChanged(Location prevLocation, Location newLocation)
{ {
if (prevLocation == newLocation) return; if (prevLocation == newLocation) { return; }
//focus on starting location //focus on starting location
if (prevLocation != null) if (prevLocation != null)
{ {
@@ -252,27 +253,104 @@ namespace Barotrauma
return !tileDiscovered[MathHelper.Clamp(x, 0, tileDiscovered.Length), MathHelper.Clamp(y, 0, tileDiscovered.Length)]; return !tileDiscovered[MathHelper.Clamp(x, 0, tileDiscovered.Length), MathHelper.Clamp(y, 0, tileDiscovered.Length)];
} }
private class MapNotification
{
public readonly RichString Text;
public readonly GUIFont Font;
public readonly Vector2 TextSize;
public int TimesShown;
public float Offset;
public readonly Location RelatedLocation;
public bool IsCurrentlyVisible;
public MapNotification(string text, GUIFont font, List<MapNotification> existingNotifications, Location relatedLocation)
{
Text = RichString.Rich(text);
Font = font;
TextSize = Font.MeasureString(Font.ForceUpperCase ? Text.SanitizedValue.ToUpper() : Text.SanitizedValue);
if (existingNotifications.Any())
{
Offset = existingNotifications.Max(n => n.Offset + n.TextSize.X + GUI.IntScale(60));
}
RelatedLocation = relatedLocation;
}
}
private readonly List<MapNotification> mapNotifications = new List<MapNotification>();
partial void ChangeLocationTypeProjSpecific(Location location, string prevName, LocationTypeChange change) partial void ChangeLocationTypeProjSpecific(Location location, string prevName, LocationTypeChange change)
{ {
if (change.Messages.Any()) var messages = change.GetMessages(location.Faction);
if (!messages.Any()) { return; }
string msg = messages.GetRandom(Rand.RandSync.Unsynced)
.Replace("[previousname]", $"‖color:gui.yellow‖{prevName}‖end‖")
.Replace("[name]", $"‖color:gui.yellow‖{location.Name}‖end‖");
location.LastTypeChangeMessage = msg;
mapNotifications.Add(new MapNotification(msg, GUIStyle.SubHeadingFont, mapNotifications, location));
}
public void DrawNotifications(SpriteBatch spriteBatch, GUICustomComponent container)
{
Vector2 pos = new Vector2(container.Rect.Right, container.Rect.Center.Y);
foreach (var notification in mapNotifications)
{ {
string msg = change.Messages[Rand.Range(0, change.Messages.Count)] Vector2 textPos = pos + new Vector2(notification.Offset, -notification.TextSize.Y / 2);
.Replace("[previousname]", $"‖color:gui.orange‖{prevName}‖end‖")
.Replace("[name]", $"‖color:gui.orange‖{location.Name}‖end‖"); notification.Font.DrawStringWithColors(
location.LastTypeChangeMessage = msg; spriteBatch,
if (GameMain.Client != null) notification.Text.SanitizedValue,
textPos,
Color.White, 0.0f, Vector2.Zero, 1.0f, SpriteEffects.None, 0,
notification.Text.RichTextData);
int margin = container.Rect.Width / 5;
notification.IsCurrentlyVisible =
textPos.X < container.Rect.Right - margin &&
textPos.X + notification.TextSize.X > container.Rect.X + margin;
}
}
private void UpdateNotifications(float deltaTime, GUICustomComponent mapContainer)
{
if (mapNotifications.Count < 5)
{
int maxIndex = 1;
while (TextManager.ContainsTag("randomnews" + maxIndex))
{ {
GameMain.Client.AddChatMessage(msg, Networking.ChatMessageType.Default, TextManager.Get("RadioAnnouncerName").Value); maxIndex++;
} }
else string textTag = "randomnews" + Rand.Range(0, maxIndex);
if (TextManager.ContainsTag(textTag))
{ {
GameMain.GameSession?.GameMode.CrewManager.AddSinglePlayerChatMessage( mapNotifications.Add(new MapNotification(TextManager.Get(textTag).Value, GUIStyle.SubHeadingFont, mapNotifications, relatedLocation: null));
TextManager.Get("RadioAnnouncerName").Value,
msg,
Networking.ChatMessageType.Default,
sender: null);
} }
} }
for (int i = mapNotifications.Count - 1; i >= 0; i--)
{
var notification = mapNotifications[i];
notification.Offset -= deltaTime * 75.0f;
if (notification.Offset < -notification.TextSize.X - mapContainer.Rect.Width)
{
notification.Offset = Math.Max(mapNotifications.Max(n => n.Offset + n.TextSize.X) + GUI.IntScale(60), 0);
notification.TimesShown++;
if (mapNotifications.Count > 5)
{
mapNotifications.RemoveAt(i);
}
else if (mapNotifications.Count > 3 && notification.TimesShown > 2)
{
mapNotifications.RemoveAt(i);
}
}
}
} }
partial void ClearAnimQueue() partial void ClearAnimQueue()
@@ -280,18 +358,19 @@ namespace Barotrauma
mapAnimQueue.Clear(); mapAnimQueue.Clear();
} }
public void Update(float deltaTime, GUICustomComponent mapContainer) public void Update(CampaignMode campaign, float deltaTime, GUICustomComponent mapContainer)
{ {
Rectangle rect = mapContainer.Rect; Rectangle rect = mapContainer.Rect;
var currentDisplayLocation = GameMain.GameSession?.Campaign?.GetCurrentDisplayLocation(); UpdateNotifications(deltaTime, mapContainer);
var currentDisplayLocation = campaign?.GetCurrentDisplayLocation();
if (currentDisplayLocation != null) if (currentDisplayLocation != null)
{ {
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,13 +531,13 @@ 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)
{ {
CurrentLocation.CreateStores(); CurrentLocation.CreateStores();
ProgressWorld(); ProgressWorld(campaign);
Radiation?.OnStep(1); Radiation?.OnStep(1);
} }
else else
@@ -481,10 +560,10 @@ namespace Barotrauma
} }
} }
public void Draw(SpriteBatch spriteBatch, GUICustomComponent mapContainer) public void Draw(CampaignMode campaign, SpriteBatch spriteBatch, GUICustomComponent mapContainer)
{ {
tooltip = null; tooltip = null;
var currentDisplayLocation = GameMain.GameSession?.Campaign?.GetCurrentDisplayLocation(); var currentDisplayLocation = campaign?.GetCurrentDisplayLocation();
Rectangle rect = mapContainer.Rect; Rectangle rect = mapContainer.Rect;
@@ -568,7 +647,9 @@ namespace Barotrauma
for (int i = 0; i < Locations.Count; i++) for (int i = 0; i < Locations.Count; i++)
{ {
Location location = Locations[i]; Location location = Locations[i];
if (IsInFogOfWar(location)) { continue; } if (!location.Discovered && IsInFogOfWar(location)) { continue; }
bool isEndLocation = endLocations.Contains(location);
if (!GameMain.DebugDraw && isEndLocation && location != endLocations.First()) { continue; }
Vector2 pos = rectCenter + (location.MapPosition + viewOffset) * zoom; Vector2 pos = rectCenter + (location.MapPosition + viewOffset) * zoom;
Sprite locationSprite = location.IsCriticallyRadiated() ? location.Type.RadiationSprite ?? location.Type.Sprite : location.Type.Sprite; Sprite locationSprite = location.IsCriticallyRadiated() ? location.Type.RadiationSprite ?? location.Type.Sprite : location.Type.Sprite;
@@ -587,14 +668,32 @@ namespace Barotrauma
} }
float iconScale = location == currentDisplayLocation ? 1.2f : 1.0f; float iconScale = location == currentDisplayLocation ? 1.2f : 1.0f;
if (location == HighlightedLocation) if (location == HighlightedLocation) { iconScale *= 1.2f; }
if (isEndLocation) { iconScale *= 2.0f; }
float notificationPulseAmount = 1.0f;
float notificationColorLerp = 0.0f;
if (mapNotifications.Any(n => n.RelatedLocation == location && n.IsCurrentlyVisible))
{ {
iconScale *= 1.2f; float sin = MathF.Sin((float)Timing.TotalTime * 2.0f);
notificationPulseAmount = Math.Max(sin + 0.5f, 1.0f);
notificationColorLerp = (notificationPulseAmount - 1.0f) * 4.0f;
color = Color.Lerp(color, GUIStyle.Yellow, notificationColorLerp);
iconScale *= notificationPulseAmount;
} }
locationSprite.Draw(spriteBatch, pos, color, locationSprite.Draw(spriteBatch, pos, color,
scale: generationParams.LocationIconSize / locationSprite.size.X * iconScale * zoom); scale: generationParams.LocationIconSize / locationSprite.size.X * iconScale * zoom);
if (location.Faction != null)
{
float factionIconScale = iconScale * 0.7f;
Sprite factionIcon = location.Faction.Prefab.IconSmall ?? location.Faction.Prefab.Icon;
Color factionIconColor = Color.Lerp(color, location.Faction.Prefab.IconColor, notificationColorLerp);
factionIcon.Draw(spriteBatch, pos + new Vector2(-15, 15) * zoom, factionIconColor,
scale: generationParams.LocationIconSize / factionIcon.size.X * factionIconScale * zoom);
}
if (location == currentDisplayLocation) if (location == currentDisplayLocation)
{ {
if (SelectedLocation != null) if (SelectedLocation != null)
@@ -626,7 +725,10 @@ namespace Barotrauma
{ {
Vector2 typeChangeIconPos = pos + new Vector2(1.35f, -0.35f) * generationParams.LocationIconSize * 0.5f * zoom; Vector2 typeChangeIconPos = pos + new Vector2(1.35f, -0.35f) * generationParams.LocationIconSize * 0.5f * zoom;
float typeChangeIconScale = 18.0f / generationParams.TypeChangeIcon.SourceRect.Width; float typeChangeIconScale = 18.0f / generationParams.TypeChangeIcon.SourceRect.Width;
generationParams.TypeChangeIcon.Draw(spriteBatch, typeChangeIconPos, GUIStyle.Red, scale: typeChangeIconScale * zoom); Color iconColor = GUIStyle.Red;
color = Color.Lerp(color, GUIStyle.Yellow, notificationColorLerp);
iconScale *= notificationPulseAmount;
generationParams.TypeChangeIcon.Draw(spriteBatch, typeChangeIconPos, iconColor, scale: typeChangeIconScale * zoom);
if (Vector2.Distance(PlayerInput.MousePosition, typeChangeIconPos) < generationParams.TypeChangeIcon.SourceRect.Width * zoom && if (Vector2.Distance(PlayerInput.MousePosition, typeChangeIconPos) < generationParams.TypeChangeIcon.SourceRect.Width * zoom &&
(tooltip == null || IsPreferredTooltip(typeChangeIconPos))) (tooltip == null || IsPreferredTooltip(typeChangeIconPos)))
{ {
@@ -635,14 +737,18 @@ namespace Barotrauma
} }
if (location != CurrentLocation && generationParams.MissionIcon != null) if (location != CurrentLocation && generationParams.MissionIcon != null)
{ {
if ((CurrentLocation == currentDisplayLocation && CurrentLocation.AvailableMissions.Any(m => m.Locations.Contains(location))) || location.AvailableMissions.Any(m => m.Prefab.Type == MissionType.GoTo)) if ((CurrentLocation == currentDisplayLocation && CurrentLocation.AvailableMissions.Any(m => m.Locations.Contains(location))) ||
location.AvailableMissions.Any(m => m.Locations[0] == m.Locations[1]))
{ {
Vector2 missionIconPos = pos + new Vector2(1.35f, 0.35f) * generationParams.LocationIconSize * 0.5f * zoom; Vector2 missionIconPos = pos + new Vector2(1.35f, 0.35f) * generationParams.LocationIconSize * 0.5f * zoom;
float missionIconScale = 18.0f / generationParams.MissionIcon.SourceRect.Width; float missionIconScale = 18.0f / generationParams.MissionIcon.SourceRect.Width;
generationParams.MissionIcon.Draw(spriteBatch, missionIconPos, generationParams.IndicatorColor, scale: missionIconScale * zoom); generationParams.MissionIcon.Draw(spriteBatch, missionIconPos, generationParams.IndicatorColor, scale: missionIconScale * zoom);
if (Vector2.Distance(PlayerInput.MousePosition, missionIconPos) < generationParams.MissionIcon.SourceRect.Width * zoom && IsPreferredTooltip(missionIconPos)) if (Vector2.Distance(PlayerInput.MousePosition, missionIconPos) < generationParams.MissionIcon.SourceRect.Width * zoom && IsPreferredTooltip(missionIconPos))
{ {
var availableMissions = CurrentLocation.AvailableMissions.Where(m => m.Locations.Contains(location)).Concat(location.AvailableMissions.Where(m => m.Prefab.Type == MissionType.GoTo)).Distinct(); var availableMissions = CurrentLocation.AvailableMissions
.Where(m => m.Locations.Contains(location))
.Concat(location.AvailableMissions.Where(m => m.Locations[0] == m.Locations[1]))
.Distinct();
tooltip = (new Rectangle(missionIconPos.ToPoint(), new Point(30)), TextManager.Get("mission") + '\n'+ string.Join('\n', availableMissions.Select(m => "- " + m.Name))); tooltip = (new Rectangle(missionIconPos.ToPoint(), new Point(30)), TextManager.Get("mission") + '\n'+ string.Join('\n', availableMissions.Select(m => "- " + m.Name)));
} }
} }
@@ -651,23 +757,12 @@ namespace Barotrauma
if (GameMain.DebugDraw) if (GameMain.DebugDraw)
{ {
Vector2 dPos = pos; Vector2 dPos = pos;
if (location == HighlightedLocation && (!location.Discovered || !location.HasOutpost()) && location.Reputation != null) if (location == HighlightedLocation)
{ {
dPos.Y += 48; dPos.Y += 48;
string name = $"Reputation: {location.Name}"; GUI.DrawString(spriteBatch, dPos + new Vector2(15, 32), "Faction: "+(location.Faction?.Prefab.Name ?? "none"), Color.White, Color.Black, font: GUIStyle.SubHeadingFont);
Vector2 nameSize = GUIStyle.SmallFont.MeasureString(name); GUI.DrawString(spriteBatch, dPos + new Vector2(15, 50), "Secondary Faction: " + (location.SecondaryFaction?.Prefab.Name ?? "none"), Color.White, Color.Black, font: GUIStyle.SubHeadingFont);
GUI.DrawString(spriteBatch, dPos, name, Color.White, Color.Black * 0.8f, 4, font: GUIStyle.SmallFont); dPos.Y += 48;
dPos.Y += nameSize.Y + 16;
Rectangle bgRect = new Rectangle((int)dPos.X, (int)dPos.Y, 256, 32);
bgRect.Inflate(8,8);
Color barColor = ToolBox.GradientLerp(location.Reputation.NormalizedValue, Color.Red, Color.Yellow, Color.LightGreen);
GUI.DrawRectangle(spriteBatch, bgRect, Color.Black * 0.8f, isFilled: true);
GUI.DrawRectangle(spriteBatch, new Rectangle((int)dPos.X, (int)dPos.Y, (int)(location.Reputation.NormalizedValue * 255), 32), barColor, isFilled: true);
string reputationValue = ((int)location.Reputation.Value).ToString();
Vector2 repValueSize = GUIStyle.SubHeadingFont.MeasureString(reputationValue);
GUI.DrawString(spriteBatch, dPos + (new Vector2(256, 32) / 2) - (repValueSize / 2), reputationValue, Color.White, Color.Black, font: GUIStyle.SubHeadingFont);
GUI.DrawRectangle(spriteBatch, new Rectangle((int)dPos.X, (int)dPos.Y, 256, 32), Color.White);
} }
dPos.Y += 48; dPos.Y += 48;
GUI.DrawString(spriteBatch, dPos, $"Difficulty: {location.LevelData.Difficulty.FormatSingleDecimal()}", Color.White, Color.Black * 0.8f, 4, font: GUIStyle.SmallFont); GUI.DrawString(spriteBatch, dPos, $"Difficulty: {location.LevelData.Difficulty.FormatSingleDecimal()}", Color.White, Color.Black * 0.8f, 4, font: GUIStyle.SmallFont);
@@ -693,11 +788,27 @@ 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 factionSize = HighlightedLocation.Faction == null ? Vector2.Zero : GUIStyle.Font.MeasureString(HighlightedLocation.Faction.Prefab.Name);
bool showReputation = hudVisibility > 0.0f && HighlightedLocation.Discovered && HighlightedLocation.Type.HasOutpost && HighlightedLocation.Reputation != null; bool showReputation = hudVisibility > 0.0f && HighlightedLocation.Type.HasOutpost && HighlightedLocation.Reputation != null;
Vector2 descSize = HighlightedLocation.Type.Description.IsNullOrEmpty() ? Vector2.Zero : GUIStyle.SmallFont.MeasureString(HighlightedLocation.Type.Description);
Vector2 size = new Vector2(Math.Max(factionSize.X, Math.Max(nameSize.X, Math.Max(typeSize.X, descSize.X))), nameSize.Y + factionSize.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;
LocalizedString repLabelText = null, repValueText = null; LocalizedString repLabelText = null, repValueText = null;
Vector2 repLabelSize = Vector2.Zero, repBarSize = Vector2.Zero; Vector2 repLabelSize = Vector2.Zero, repBarSize = Vector2.Zero;
if (showReputation) if (showReputation && HighlightedLocation.Reputation != null)
{ {
repLabelText = TextManager.Get("reputation"); repLabelText = TextManager.Get("reputation");
repLabelSize = GUIStyle.Font.MeasureString(repLabelText); repLabelSize = GUIStyle.Font.MeasureString(repLabelText);
@@ -706,21 +817,59 @@ 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.0f)),
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);
if (!HighlightedLocation.Type.Name.IsNullOrEmpty())
{
DrawText(HighlightedLocation.Type.Name);
topLeftPos += new Vector2(0.0f, typeSize.Y);
}
if (HighlightedLocation.Faction != null)
{
GUI.DrawString(spriteBatch, topLeftPos, HighlightedLocation.Faction.Prefab.Name, GUIStyle.TextColorNormal * hudVisibility * 1.5f);
topLeftPos += new Vector2(0.0f, factionSize.Y);
}
if (!HighlightedLocation.Type.Description.IsNullOrEmpty())
{
DrawText(HighlightedLocation.Type.Description, font: GUIStyle.SmallFont);
topLeftPos += new Vector2(0.0f, descSize.Y);
}
if (highestSubTier > 0)
{
DrawSubAvailabilityText("advancedsub.all", highestSubTier);
}
foreach (var (subClass, tier) in overrideTiers)
{
DrawSubAvailabilityText($"advancedsub.{subClass}", tier);
}
void DrawSubAvailabilityText(string tag, int tier)
{
DrawText(TextManager.GetWithVariable(tag, "[tiernumber]", tier.ToString()), font: GUIStyle.SmallFont);
topLeftPos += new Vector2(0.0f, typeSize.Y);
}
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)
@@ -939,17 +1088,15 @@ namespace Barotrauma
if (connection.Locked) if (connection.Locked)
{ {
var gateLocation = connection.Locations[0].IsGateBetweenBiomes ? connection.Locations[0] : connection.Locations[1]; var gateLocation = connection.Locations[0].IsGateBetweenBiomes ? connection.Locations[0] : connection.Locations[1];
var unlockEvent = var unlockEvent = EventPrefab.GetUnlockPathEvent(gateLocation.LevelData.Biome.Identifier, gateLocation.Faction);
EventPrefab.Prefabs.FirstOrDefault(ep => ep.UnlockPathEvent && ep.BiomeIdentifier == gateLocation.LevelData.Biome.Identifier) ??
EventPrefab.Prefabs.FirstOrDefault(ep => ep.UnlockPathEvent && ep.BiomeIdentifier == Identifier.Empty);
if (unlockEvent != null) if (unlockEvent != null)
{ {
Reputation unlockReputation = CurrentLocation.Reputation; Reputation unlockReputation = CurrentLocation.Reputation;
Faction unlockFaction = null; Faction unlockFaction = null;
if (!string.IsNullOrEmpty(unlockEvent.UnlockPathFaction)) if (!unlockEvent.Faction.IsEmpty)
{ {
unlockFaction = GameMain.GameSession.Campaign.Factions.Find(f => f.Prefab.Identifier == unlockEvent.UnlockPathFaction); unlockFaction = GameMain.GameSession.Campaign.Factions.Find(f => f.Prefab.Identifier == unlockEvent.Faction);
unlockReputation = unlockFaction?.Reputation; unlockReputation = unlockFaction?.Reputation;
} }
@@ -1020,13 +1167,14 @@ namespace Barotrauma
private void DrawDecorativeHUD(SpriteBatch spriteBatch, Rectangle rect) private void DrawDecorativeHUD(SpriteBatch spriteBatch, Rectangle rect)
{ {
generationParams.DecorativeGraphSprite.Draw(spriteBatch, (int)((Timing.TotalTime * 5.0f) % generationParams.DecorativeGraphSprite.FrameCount), generationParams.DecorativeGraphSprite.Draw(spriteBatch, (int)((Timing.TotalTime * 5.0f) % generationParams.DecorativeGraphSprite.FrameCount),
new Vector2(rect.Left, rect.Top), Color.White, Vector2.Zero, 0, Vector2.One * GUI.Scale); new Vector2(rect.X, rect.Bottom - (generationParams.DecorativeGraphSprite.FrameSize.Y + 30) * GUI.Scale),
Color.White, Vector2.Zero, 0, Vector2.One * GUI.Scale, SpriteEffects.FlipVertically);
GUI.DrawString(spriteBatch, GUI.DrawString(spriteBatch,
new Vector2(rect.Right - GUI.IntScale(170), rect.Y + GUI.IntScale(5)), new Vector2(rect.Right - GUI.IntScale(170), rect.Y + GUI.IntScale(5)),
"JOVIAN FLUX " + ((cameraNoiseStrength + Rand.Range(-0.02f, 0.02f)) * 500), generationParams.IndicatorColor * hudVisibility, font: GUIStyle.SmallFont); "JOVIAN FLUX " + ((cameraNoiseStrength + Rand.Range(-0.02f, 0.02f)) * 500), generationParams.IndicatorColor * hudVisibility, font: GUIStyle.SmallFont);
GUI.DrawString(spriteBatch, GUI.DrawString(spriteBatch,
new Vector2(rect.X + GUI.IntScale(15), rect.Bottom - GUI.IntScale(25)), new Vector2(rect.X + GUI.IntScale(5), rect.Y + GUI.IntScale(5)),
"LAT " + (-DrawOffset.Y / 100.0f) + " LON " + (-DrawOffset.X / 100.0f), generationParams.IndicatorColor * hudVisibility, font: GUIStyle.SmallFont); "LAT " + (-DrawOffset.Y / 100.0f) + " LON " + (-DrawOffset.X / 100.0f), generationParams.IndicatorColor * hudVisibility, font: GUIStyle.SmallFont);
} }
@@ -99,11 +99,24 @@ namespace Barotrauma
{ {
float depth = baseDepth float depth = baseDepth
//take texture into account to get entities with (roughly) the same base depth and texture to render consecutively to minimize texture swaps //take texture into account to get entities with (roughly) the same base depth and texture to render consecutively to minimize texture swaps
+ (sprite?.Texture?.SortingKey ?? 0) % 100 * 0.00001f + (sprite?.Texture?.SortingKey ?? 0) % 100 * 0.000001f
+ ID % 100 * 0.000001f; + ID % 100 * 0.0000001f;
return Math.Min(depth, 1.0f); return Math.Min(depth, 1.0f);
} }
protected Vector2 GetCollapseEffectOffset()
{
if (Level.Loaded?.Renderer?.CollapseEffectStrength is float collapseEffectStrength and > 0.0f && Submarine is not { Info.Type: SubmarineType.Player })
{
Vector2 noisePos = new Vector2(
(float)PerlinNoise.GetPerlin((float)(Timing.TotalTime + ID) * 0.1f, (float)(Timing.TotalTime + ID) * 0.5f) - 0.5f,
(float)PerlinNoise.GetPerlin((float)(Timing.TotalTime + ID) * 0.1f, (float)(Timing.TotalTime + ID) * 0.1f) - 0.5f);
Vector2 offsetFromOrigin = Level.Loaded.Renderer.CollapseEffectOrigin - DrawPosition;
return offsetFromOrigin * MathF.Pow(collapseEffectStrength, MathHelper.Lerp(1, 4, ID % 1000 / 1000.0f)) + (noisePos * 100.0f * collapseEffectStrength);
}
return Vector2.Zero;
}
/// <summary> /// <summary>
/// Update the selection logic in submarine editor /// Update the selection logic in submarine editor
/// </summary> /// </summary>
@@ -295,6 +295,7 @@ namespace Barotrauma
if (isWiringMode) { color *= 0.15f; } if (isWiringMode) { color *= 0.15f; }
Vector2 drawOffset = Submarine == null ? Vector2.Zero : Submarine.DrawPosition; Vector2 drawOffset = Submarine == null ? Vector2.Zero : Submarine.DrawPosition;
drawOffset += GetCollapseEffectOffset();
float depth = GetDrawDepth(); float depth = GetDrawDepth();
@@ -35,6 +35,13 @@ namespace Barotrauma
Rectangle camView = cam.WorldView; Rectangle camView = cam.WorldView;
camView = new Rectangle(camView.X - CullMargin, camView.Y + CullMargin, camView.Width + CullMargin * 2, camView.Height + CullMargin * 2); camView = new Rectangle(camView.X - CullMargin, camView.Y + CullMargin, camView.Width + CullMargin * 2, camView.Height + CullMargin * 2);
if (Level.Loaded?.Renderer?.CollapseEffectStrength is > 0.0f)
{
//force everything to be visible when the collapse effect (which moves everything to a single point) is active
camView = Rectangle.Union(AbsRect(camView.Location.ToVector2(), camView.Size.ToVector2()), new Rectangle(Point.Zero, Level.Loaded.Size));
camView.Y += camView.Height;
}
if (Math.Abs(camView.X - prevCullArea.X) < CullMoveThreshold && if (Math.Abs(camView.X - prevCullArea.X) < CullMoveThreshold &&
Math.Abs(camView.Y - prevCullArea.Y) < CullMoveThreshold && Math.Abs(camView.Y - prevCullArea.Y) < CullMoveThreshold &&
Math.Abs(camView.Right - prevCullArea.Right) < CullMoveThreshold && Math.Abs(camView.Right - prevCullArea.Right) < CullMoveThreshold &&
@@ -123,6 +123,11 @@ namespace Barotrauma
} }
} }
} }
else if (spawnType == SpawnType.ExitPoint && ExitPointSize != Point.Zero)
{
GUI.DrawRectangle(spriteBatch, drawPos - ExitPointSize.ToVector2() / 2, ExitPointSize.ToVector2(), Color.Cyan, thickness: 5);
}
GUIStyle.SmallFont.DrawString(spriteBatch, GUIStyle.SmallFont.DrawString(spriteBatch,
ID.ToString(), ID.ToString(),
new Vector2(DrawPosition.X - 10, -DrawPosition.Y - 30), new Vector2(DrawPosition.X - 10, -DrawPosition.Y - 30),
@@ -251,6 +256,7 @@ namespace Barotrauma
private bool ChangeSpawnType(GUIButton button, object obj) private bool ChangeSpawnType(GUIButton button, object obj)
{ {
var prevSpawnType = spawnType;
GUITextBlock spawnTypeText = button.Parent.GetChildByUserData("spawntypetext") as GUITextBlock; GUITextBlock spawnTypeText = button.Parent.GetChildByUserData("spawntypetext") as GUITextBlock;
var values = (SpawnType[])Enum.GetValues(typeof(SpawnType)); var values = (SpawnType[])Enum.GetValues(typeof(SpawnType));
int currIndex = values.IndexOf(spawnType); int currIndex = values.IndexOf(spawnType);
@@ -267,6 +273,7 @@ namespace Barotrauma
} }
spawnType = values[currIndex]; spawnType = values[currIndex];
spawnTypeText.Text = spawnType.ToString(); spawnTypeText.Text = spawnType.ToString();
if (spawnType == SpawnType.ExitPoint || prevSpawnType == SpawnType.ExitPoint) { CreateEditingHUD(); }
return true; return true;
} }
@@ -412,6 +419,28 @@ namespace Barotrauma
textBox.Text = string.Join(",", tags); textBox.Text = string.Join(",", tags);
textBox.Flash(GUIStyle.Green); textBox.Flash(GUIStyle.Green);
}; };
if (SpawnType == SpawnType.ExitPoint)
{
var sizeField = GUI.CreatePointField(ExitPointSize, GUI.IntScale(20), TextManager.Get("dimensions"), paddedFrame.RectTransform);
GUINumberInput xField = null, yField = null;
foreach (GUIComponent child in sizeField.GetAllChildren())
{
if (yField == null)
{
yField = child as GUINumberInput;
}
else
{
xField = child as GUINumberInput;
if (xField != null) { break; }
}
}
xField.MinValueInt = 0;
xField.OnValueChanged = (numberInput) => { ExitPointSize = new Point(numberInput.IntValue, ExitPointSize.Y); };
yField.MinValueInt = 0;
yField.OnValueChanged = (numberInput) => { ExitPointSize = new Point(ExitPointSize.X, numberInput.IntValue); };
}
} }
editingHUD.RectTransform.Resize(new Point( editingHUD.RectTransform.Resize(new Point(
@@ -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)}");
} }
@@ -277,16 +277,12 @@ namespace Barotrauma.Networking
public void Clear() public void Clear()
{ {
ID = 0;
lastReceivedID = 0; lastReceivedID = 0;
firstNewID = null; firstNewID = null;
events.Clear();
eventLastSent.Clear(); eventLastSent.Clear();
MidRoundSyncingDone = false; MidRoundSyncingDone = false;
ClearSelf();
} }
/// <summary> /// <summary>
@@ -297,6 +293,10 @@ namespace Barotrauma.Networking
{ {
ID = 0; ID = 0;
events.Clear(); events.Clear();
if (thisClient != null)
{
thisClient.LastSentEntityEventID = 0;
}
} }
} }
} }
@@ -14,7 +14,7 @@ namespace Barotrauma.Networking
{ {
static class PingUtils static class PingUtils
{ {
private static readonly Dictionary<IPAddress, int> activePings = new Dictionary<IPAddress, int>(); private static readonly Dictionary<IPEndPoint, int> activePings = new Dictionary<IPEndPoint, int>();
private static bool steamPingInfoReady; private static bool steamPingInfoReady;
@@ -36,9 +36,9 @@ namespace Barotrauma.Networking
switch (serverInfo.Endpoint) switch (serverInfo.Endpoint)
{ {
case LidgrenEndpoint { NetEndpoint: { Address: var address } }: case LidgrenEndpoint { NetEndpoint: var endPoint }:
GetIPAddressPing(serverInfo, address, onPingDiscovered); GetIPAddressPing(serverInfo, endPoint, onPingDiscovered);
break; break;
case SteamP2PEndpoint steamP2PEndpoint: case SteamP2PEndpoint steamP2PEndpoint:
TaskPool.Add($"EstimateSteamLobbyPing ({steamP2PEndpoint.StringRepresentation})", TaskPool.Add($"EstimateSteamLobbyPing ({steamP2PEndpoint.StringRepresentation})",
@@ -131,9 +131,9 @@ namespace Barotrauma.Networking
} }
} }
private static void GetIPAddressPing(ServerInfo serverInfo, IPAddress address, Action<ServerInfo> onPingDiscovered) private static void GetIPAddressPing(ServerInfo serverInfo, IPEndPoint endPoint, Action<ServerInfo> onPingDiscovered)
{ {
if (IPAddress.IsLoopback(address)) if (IPAddress.IsLoopback(endPoint.Address))
{ {
serverInfo.Ping = Option<int>.Some(0); serverInfo.Ping = Option<int>.Some(0);
onPingDiscovered(serverInfo); onPingDiscovered(serverInfo);
@@ -142,24 +142,24 @@ namespace Barotrauma.Networking
{ {
lock (activePings) lock (activePings)
{ {
if (activePings.ContainsKey(address)) { return; } if (activePings.ContainsKey(endPoint)) { return; }
activePings.Add(address, activePings.Any() ? activePings.Values.Max() + 1 : 0); activePings.Add(endPoint, activePings.Any() ? activePings.Values.Max() + 1 : 0);
} }
serverInfo.Ping = Option<int>.None(); serverInfo.Ping = Option<int>.None();
TaskPool.Add($"PingServerAsync ({address})", PingServerAsync(address, 1000), TaskPool.Add($"PingServerAsync ({endPoint})", PingServerAsync(endPoint, 1000),
rtt => rtt =>
{ {
if (!rtt.TryGetResult(out serverInfo.Ping)) { serverInfo.Ping = Option<int>.None(); } if (!rtt.TryGetResult(out serverInfo.Ping)) { serverInfo.Ping = Option<int>.None(); }
onPingDiscovered(serverInfo); onPingDiscovered(serverInfo);
lock (activePings) lock (activePings)
{ {
activePings.Remove(address); activePings.Remove(endPoint);
} }
}); });
} }
} }
private static async Task<Option<int>> PingServerAsync(IPAddress ipAddress, int timeOut) private static async Task<Option<int>> PingServerAsync(IPEndPoint endPoint, int timeOut)
{ {
await Task.Yield(); await Task.Yield();
bool shouldGo = false; bool shouldGo = false;
@@ -167,21 +167,21 @@ namespace Barotrauma.Networking
{ {
lock (activePings) lock (activePings)
{ {
shouldGo = activePings.Count(kvp => kvp.Value < activePings[ipAddress]) < 25; shouldGo = activePings.Count(kvp => kvp.Value < activePings[endPoint]) < 25;
} }
await Task.Delay(25); await Task.Delay(25);
} }
if (ipAddress == null) { return Option<int>.None(); } if (endPoint?.Address == null) { return Option<int>.None(); }
//don't attempt to ping if the address is IPv6 and it's not supported //don't attempt to ping if the address is IPv6 and it's not supported
if (ipAddress.AddressFamily == AddressFamily.InterNetworkV6 && !Socket.OSSupportsIPv6) { return Option<int>.None(); } if (endPoint.Address.AddressFamily == AddressFamily.InterNetworkV6 && !Socket.OSSupportsIPv6) { return Option<int>.None(); }
Ping ping = new Ping(); Ping ping = new Ping();
byte[] buffer = new byte[32]; byte[] buffer = new byte[32];
try try
{ {
PingReply pingReply = await ping.SendPingAsync(ipAddress, timeOut, buffer, new PingOptions(128, true)); PingReply pingReply = await ping.SendPingAsync(endPoint.Address, timeOut, buffer, new PingOptions(128, true));
return pingReply.Status switch return pingReply.Status switch
{ {
@@ -191,9 +191,9 @@ namespace Barotrauma.Networking
} }
catch (Exception ex) catch (Exception ex)
{ {
GameAnalyticsManager.AddErrorEventOnce("ServerListScreen.PingServer:PingException" + ipAddress, GameAnalyticsManager.ErrorSeverity.Warning, "Failed to ping a server - " + (ex?.InnerException?.Message ?? ex.Message)); GameAnalyticsManager.AddErrorEventOnce("ServerListScreen.PingServer:PingException" + endPoint.Address, GameAnalyticsManager.ErrorSeverity.Warning, "Failed to ping a server - " + (ex?.InnerException?.Message ?? ex.Message));
#if DEBUG #if DEBUG
DebugConsole.NewMessage("Failed to ping a server (" + ipAddress + ") - " + (ex?.InnerException?.Message ?? ex.Message), Color.Red); DebugConsole.NewMessage("Failed to ping a server (" + endPoint.Address + ") - " + (ex?.InnerException?.Message ?? ex.Message), Color.Red);
#endif #endif
return Option<int>.None(); return Option<int>.None();
@@ -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
@@ -82,6 +82,12 @@ namespace Barotrauma.Particles
[Editable, Serialize(false, IsPropertySaveable.Yes)] [Editable, Serialize(false, IsPropertySaveable.Yes)]
public bool CopyEntityAngle { get; set; } public bool CopyEntityAngle { get; set; }
[Editable, Serialize(true, IsPropertySaveable.Yes, description: "Should the entity heading direction be applied to the particle rotation? Only affects after flipping the texture and when CopyEntityAngle is true.")]
public bool CopyEntityDir { get; set; }
[Editable, Serialize(false, IsPropertySaveable.Yes, description: "Only relevant for status effects. Makes the emitter copy the angle from the target of the effect instead of the entity applying the effect.")]
public bool CopyTargetAngle { get; set; }
[Editable, Serialize("1,1,1,1", IsPropertySaveable.Yes)] [Editable, Serialize("1,1,1,1", IsPropertySaveable.Yes)]
public Color ColorMultiplier { get; set; } public Color ColorMultiplier { get; set; }
@@ -200,7 +206,7 @@ namespace Barotrauma.Particles
position += dir * Rand.Range(Prefab.Properties.DistanceMin, Prefab.Properties.DistanceMax); position += dir * Rand.Range(Prefab.Properties.DistanceMin, Prefab.Properties.DistanceMax);
} }
var particle = GameMain.ParticleManager.CreateParticle(particlePrefab, position, velocity, particleRotation, hullGuess, Prefab.DrawOnTop, lifeTimeMultiplier: Prefab.Properties.LifeTimeMultiplier, tracerPoints: tracerPoints); var particle = GameMain.ParticleManager.CreateParticle(particlePrefab, position, velocity, particleRotation, hullGuess, lifeTimeMultiplier: Prefab.Properties.LifeTimeMultiplier, tracerPoints: tracerPoints);
if (particle != null) if (particle != null)
{ {
@@ -75,21 +75,21 @@ namespace Barotrauma.Particles
return CreateParticle(prefab, position, velocity, rotation, hullGuess, collisionIgnoreTimer: collisionIgnoreTimer, tracerPoints:tracerPoints); return CreateParticle(prefab, position, velocity, rotation, hullGuess, collisionIgnoreTimer: collisionIgnoreTimer, tracerPoints:tracerPoints);
} }
public Particle CreateParticle(ParticlePrefab prefab, Vector2 position, Vector2 velocity, float rotation = 0.0f, Hull hullGuess = null, bool drawOnTop = false, float collisionIgnoreTimer = 0f, float lifeTimeMultiplier = 1f, Tuple<Vector2, Vector2> tracerPoints = null) public Particle CreateParticle(ParticlePrefab prefab, Vector2 position, Vector2 velocity, float rotation = 0.0f, Hull hullGuess = null, float collisionIgnoreTimer = 0f, float lifeTimeMultiplier = 1f, Tuple<Vector2, Vector2> tracerPoints = null)
{ {
if (prefab == null || prefab.Sprites.Count == 0) { return null; } if (prefab == null || prefab.Sprites.Count == 0) { return null; }
if (particleCount >= MaxParticles) if (particleCount >= MaxParticles)
{ {
for (int i = 0; i < particleCount; i++) for (int i = 0; i < particleCount; i++)
{ {
if (particles[i].Prefab.Priority < prefab.Priority) if (particles[i].Prefab.Priority < prefab.Priority ||
(!particles[i].Prefab.DrawAlways && prefab.DrawAlways))
{ {
RemoveParticle(i); RemoveParticle(i);
break; break;
} }
} }
if (particleCount >= MaxParticles) { return null; } if (particleCount >= MaxParticles) { return null; }
} }
Vector2 particleEndPos = prefab.CalculateEndPosition(position, velocity); Vector2 particleEndPos = prefab.CalculateEndPosition(position, velocity);
@@ -109,26 +109,30 @@ namespace Barotrauma.Particles
Rectangle expandedViewRect = MathUtils.ExpandRect(cam.WorldView, MaxOutOfViewDist); Rectangle expandedViewRect = MathUtils.ExpandRect(cam.WorldView, MaxOutOfViewDist);
if (minPos.X > expandedViewRect.Right || maxPos.X < expandedViewRect.X) { return null; } if (!prefab.DrawAlways)
if (minPos.Y > expandedViewRect.Y || maxPos.Y < expandedViewRect.Y - expandedViewRect.Height) { return null; } {
if (minPos.X > expandedViewRect.Right || maxPos.X < expandedViewRect.X) { return null; }
if (minPos.Y > expandedViewRect.Y || maxPos.Y < expandedViewRect.Y - expandedViewRect.Height) { return null; }
}
if (particles[particleCount] == null) { particles[particleCount] = new Particle(); } if (particles[particleCount] == null) { particles[particleCount] = new Particle(); }
particles[particleCount].Init(prefab, position, velocity, rotation, hullGuess, drawOnTop, collisionIgnoreTimer, lifeTimeMultiplier, tracerPoints: tracerPoints); particles[particleCount].Init(prefab, position, velocity, rotation, hullGuess, prefab.DrawOnTop, collisionIgnoreTimer, lifeTimeMultiplier, tracerPoints: tracerPoints);
particleCount++; particleCount++;
return particles[particleCount - 1]; return particles[particleCount - 1];
} }
public List<ParticlePrefab> GetPrefabList() public static List<ParticlePrefab> GetPrefabList()
{ {
return ParticlePrefab.Prefabs.ToList(); return ParticlePrefab.Prefabs.ToList();
} }
public ParticlePrefab FindPrefab(string prefabName) public static ParticlePrefab FindPrefab(string prefabName)
{ {
return ParticlePrefab.Prefabs.Find(p => p.Identifier == prefabName); ParticlePrefab.Prefabs.TryGet(prefabName, out ParticlePrefab prefab);
return prefab;
} }
private void RemoveParticle(int index) private void RemoveParticle(int index)
@@ -170,7 +174,7 @@ namespace Barotrauma.Particles
remove = true; remove = true;
} }
if (remove) RemoveParticle(i); if (remove) { RemoveParticle(i); }
} }
} }
@@ -185,6 +185,9 @@ namespace Barotrauma.Particles
[Editable, Serialize(false, IsPropertySaveable.No, description: "Should the particle be always rendered on top of entities?")] [Editable, Serialize(false, IsPropertySaveable.No, description: "Should the particle be always rendered on top of entities?")]
public bool DrawOnTop { get; private set; } public bool DrawOnTop { get; private set; }
[Editable, Serialize(false, IsPropertySaveable.No, description: "Draw the particle even when it's calculated to be outside of view (the formula doesn't take scales into account). ")]
public bool DrawAlways { get; private set; }
[Editable, Serialize(ParticleBlendState.AlphaBlend, IsPropertySaveable.No, description: "The type of blending to use when rendering the particle.")] [Editable, Serialize(ParticleBlendState.AlphaBlend, IsPropertySaveable.No, description: "The type of blending to use when rendering the particle.")]
public ParticleBlendState BlendState { get; private set; } public ParticleBlendState BlendState { get; private set; }
@@ -79,13 +79,13 @@ namespace Barotrauma
new Vector2(DrawPosition.X, -DrawPosition.Y), new Vector2(DrawPosition.X, -DrawPosition.Y),
Color.Cyan, 0, 5); Color.Cyan, 0, 5);
} }
if (bodyShapeTexture == null && IsValidShape(radius, height, width)) if (bodyShapeTexture == null && IsValidShape(Radius, Height, Width))
{ {
switch (BodyShape) switch (BodyShape)
{ {
case Shape.Rectangle: case Shape.Rectangle:
{ {
float maxSize = Math.Max(ConvertUnits.ToDisplayUnits(width), ConvertUnits.ToDisplayUnits(height)); float maxSize = Math.Max(ConvertUnits.ToDisplayUnits(Width), ConvertUnits.ToDisplayUnits(Height));
if (maxSize > 128.0f) if (maxSize > 128.0f)
{ {
bodyShapeTextureScale = 128.0f / maxSize; bodyShapeTextureScale = 128.0f / maxSize;
@@ -96,14 +96,14 @@ namespace Barotrauma
} }
bodyShapeTexture = GUI.CreateRectangle( bodyShapeTexture = GUI.CreateRectangle(
(int)ConvertUnits.ToDisplayUnits(width * bodyShapeTextureScale), (int)ConvertUnits.ToDisplayUnits(Width * bodyShapeTextureScale),
(int)ConvertUnits.ToDisplayUnits(height * bodyShapeTextureScale)); (int)ConvertUnits.ToDisplayUnits(Height * bodyShapeTextureScale));
break; break;
} }
case Shape.Capsule: case Shape.Capsule:
case Shape.HorizontalCapsule: case Shape.HorizontalCapsule:
{ {
float maxSize = Math.Max(ConvertUnits.ToDisplayUnits(radius), ConvertUnits.ToDisplayUnits(Math.Max(height, width))); float maxSize = Math.Max(ConvertUnits.ToDisplayUnits(Radius), ConvertUnits.ToDisplayUnits(Math.Max(Height, Width)));
if (maxSize > 128.0f) if (maxSize > 128.0f)
{ {
bodyShapeTextureScale = 128.0f / maxSize; bodyShapeTextureScale = 128.0f / maxSize;
@@ -114,20 +114,20 @@ namespace Barotrauma
} }
bodyShapeTexture = GUI.CreateCapsule( bodyShapeTexture = GUI.CreateCapsule(
(int)ConvertUnits.ToDisplayUnits(radius * bodyShapeTextureScale), (int)ConvertUnits.ToDisplayUnits(Radius * bodyShapeTextureScale),
(int)ConvertUnits.ToDisplayUnits(Math.Max(height, width) * bodyShapeTextureScale)); (int)ConvertUnits.ToDisplayUnits(Math.Max(Height, Width) * bodyShapeTextureScale));
break; break;
} }
case Shape.Circle: case Shape.Circle:
if (ConvertUnits.ToDisplayUnits(radius) > 128.0f) if (ConvertUnits.ToDisplayUnits(Radius) > 128.0f)
{ {
bodyShapeTextureScale = 128.0f / ConvertUnits.ToDisplayUnits(radius); bodyShapeTextureScale = 128.0f / ConvertUnits.ToDisplayUnits(Radius);
} }
else else
{ {
bodyShapeTextureScale = 1.0f; bodyShapeTextureScale = 1.0f;
} }
bodyShapeTexture = GUI.CreateCircle((int)ConvertUnits.ToDisplayUnits(radius * bodyShapeTextureScale)); bodyShapeTexture = GUI.CreateCircle((int)ConvertUnits.ToDisplayUnits(Radius * bodyShapeTextureScale));
break; break;
default: default:
throw new NotImplementedException(); throw new NotImplementedException();
@@ -59,6 +59,7 @@ namespace Barotrauma
switch (MouseButton) switch (MouseButton)
{ {
case MouseButton.None: case MouseButton.None:
if (Key == Keys.None) { return false; }
return PlayerInput.KeyDown(Key); return PlayerInput.KeyDown(Key);
case MouseButton.PrimaryMouse: case MouseButton.PrimaryMouse:
return PlayerInput.PrimaryMouseButtonHeld(); return PlayerInput.PrimaryMouseButtonHeld();
@@ -88,6 +89,7 @@ namespace Barotrauma
switch (MouseButton) switch (MouseButton)
{ {
case MouseButton.None: case MouseButton.None:
if (Key == Keys.None) { return false; }
return PlayerInput.KeyHit(Key); return PlayerInput.KeyHit(Key);
case MouseButton.PrimaryMouse: case MouseButton.PrimaryMouse:
return PlayerInput.PrimaryMouseButtonClicked(); return PlayerInput.PrimaryMouseButtonClicked();
@@ -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)
@@ -260,18 +274,43 @@ 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,11 +326,11 @@ 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);
} }
} }
#endif #endif
} }
@@ -7,17 +7,13 @@ namespace Barotrauma
{ {
class CampaignEndScreen : Screen class CampaignEndScreen : Screen
{ {
private Video video;
private readonly CreditsPlayer creditsPlayer; private readonly CreditsPlayer creditsPlayer;
private readonly Camera cam; private readonly Camera cam;
public Action OnFinished; public Action OnFinished;
private LocalizedString textOverlay; protected SlideshowPlayer slideshowPlayer;
private float textOverlayTimer;
private Vector2 textOverlaySize;
public CampaignEndScreen() public CampaignEndScreen()
{ {
@@ -42,13 +38,10 @@ namespace Barotrauma
public override void Select() public override void Select()
{ {
base.Select(); base.Select();
if (SlideshowPrefab.Prefabs.TryGet("campaignending".ToIdentifier(), out var slideshow))
textOverlay = ToolBox.WrapText(TextManager.Get("campaignend1"), GameMain.GraphicsWidth / 3, GUIStyle.Font); {
textOverlaySize = GUIStyle.Font.MeasureString(textOverlay); slideshowPlayer = new SlideshowPlayer(GUICanvas.Instance, slideshow);
textOverlayTimer = 0.0f; }
video = Video.Load(GameMain.GraphicsDeviceManager.GraphicsDevice, GameMain.SoundManager, "Content/SplashScreens/Ending.webm");
video.Play();
creditsPlayer.Restart(); creditsPlayer.Restart();
creditsPlayer.Visible = false; creditsPlayer.Visible = false;
SteamAchievementManager.UnlockAchievement("campaigncompleted".ToIdentifier(), unlockClients: true); SteamAchievementManager.UnlockAchievement("campaigncompleted".ToIdentifier(), unlockClients: true);
@@ -56,14 +49,13 @@ namespace Barotrauma
public override void Deselect() public override void Deselect()
{ {
video?.Dispose();
video = null;
GUI.HideCursor = false; GUI.HideCursor = false;
SoundPlayer.OverrideMusicType = Identifier.Empty; SoundPlayer.OverrideMusicType = Identifier.Empty;
} }
public override void Update(double deltaTime) public override void Update(double deltaTime)
{ {
slideshowPlayer?.UpdateManually((float)deltaTime);
if (creditsPlayer.Finished) if (creditsPlayer.Finished)
{ {
OnFinished?.Invoke(); OnFinished?.Invoke();
@@ -73,46 +65,18 @@ namespace Barotrauma
public override void Draw(double deltaTime, GraphicsDevice graphics, SpriteBatch spriteBatch) public override void Draw(double deltaTime, GraphicsDevice graphics, SpriteBatch spriteBatch)
{ {
spriteBatch.Begin(); spriteBatch.Begin(SpriteSortMode.Deferred, null, GUI.SamplerState, null, GameMain.ScissorTestEnable);
graphics.Clear(Color.Black); graphics.Clear(Color.Black);
if (video.IsPlaying) SoundPlayer.OverrideMusicType = "ending".ToIdentifier();
if (slideshowPlayer != null && !slideshowPlayer.Finished)
{ {
GUI.HideCursor = !GUI.PauseMenuOpen; slideshowPlayer.DrawManually(spriteBatch);
spriteBatch.Draw(video.GetTexture(), new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight), Color.White);
} }
else else
{ {
SoundPlayer.OverrideMusicType = "ending".ToIdentifier(); GUI.HideCursor = false;
float duration = 20.0f; creditsPlayer.Visible = true;
float creditsDelay = 3.0f;
if (textOverlayTimer < duration + creditsDelay)
{
float textAlpha;
float fadeInTime = 5.0f, fadeOutTime = 3.0f;
textOverlayTimer += (float)deltaTime;
if (textOverlayTimer < fadeInTime)
{
textAlpha = textOverlayTimer / fadeInTime;
}
else if (textOverlayTimer > duration - fadeOutTime)
{
textAlpha = Math.Min((duration - textOverlayTimer) / fadeOutTime, 1.0f);
}
else
{
textAlpha = 1.0f;
}
GUIStyle.Font.DrawString(spriteBatch, textOverlay, new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight) / 2 - textOverlaySize / 2, Color.White * textAlpha);
}
else
{
GUI.HideCursor = false;
creditsPlayer.Visible = true;
}
} }
spriteBatch.End();
spriteBatch.Begin(SpriteSortMode.Deferred, null, GUI.SamplerState, null, GameMain.ScissorTestEnable);
GUI.Draw(cam, spriteBatch); GUI.Draw(cam, spriteBatch);
spriteBatch.End(); spriteBatch.End();
} }
@@ -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,

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