Build 1.1.4.0

This commit is contained in:
Markus Isberg
2023-03-31 18:40:44 +03:00
parent efba17e0ff
commit 9470edead3
483 changed files with 17487 additions and 8548 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;
@@ -30,6 +33,8 @@ namespace Barotrauma
public bool AllowInterrupt = false; public bool AllowInterrupt = false;
public bool RemoveControlFromCharacter = true; public bool RemoveControlFromCharacter = true;
public bool RunWhilePaused = true;
public CameraTransition(ISpatialEntity targetEntity, Camera cam, Alignment? cameraStartPos, Alignment? cameraEndPos, bool fadeOut = true, bool losFadeIn = false, float waitDuration = 0f, float panDuration = 10.0f, float? startZoom = null, float? endZoom = null) public CameraTransition(ISpatialEntity targetEntity, Camera cam, Alignment? cameraStartPos, Alignment? cameraEndPos, bool fadeOut = true, bool losFadeIn = false, float waitDuration = 0f, float panDuration = 10.0f, float? startZoom = null, float? endZoom = null)
{ {
WaitDuration = waitDuration; WaitDuration = waitDuration;
@@ -45,8 +50,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()
@@ -62,11 +78,13 @@ namespace Barotrauma
#endif #endif
} }
private float DeltaTime => CoroutineManager.Paused && !RunWhilePaused ? 0 : CoroutineManager.DeltaTime;
private IEnumerable<CoroutineStatus> Update(ISpatialEntity targetEntity, Camera cam) private IEnumerable<CoroutineStatus> Update(ISpatialEntity targetEntity, Camera cam)
{ {
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 +98,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 +156,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
@@ -162,14 +181,21 @@ namespace Barotrauma
Lights.LightManager.ViewTarget = prevControlled ?? (targetEntity as Entity); Lights.LightManager.ViewTarget = prevControlled ?? (targetEntity as Entity);
} }
#endif #endif
timer += CoroutineManager.DeltaTime; timer += DeltaTime;
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 += 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;
@@ -19,6 +19,17 @@ namespace Barotrauma
private IEnumerable<CoroutineStatus> FadeOutColors(float time) private IEnumerable<CoroutineStatus> FadeOutColors(float time)
{ {
Dictionary<MapEntity, Color> originalColors = new Dictionary<MapEntity, Color>();
foreach (var item in thalamusItems)
{
originalColors.Add(item, item.SpriteColor);
}
foreach (var structure in thalamusStructures)
{
originalColors.Add(structure, structure.SpriteColor);
}
float timer = 0; float timer = 0;
while (timer < time) while (timer < time)
{ {
@@ -26,15 +37,16 @@ namespace Barotrauma
float m = MathHelper.Lerp(1, Config.DeadEntityColorMultiplier, MathUtils.InverseLerp(0, time, timer)); float m = MathHelper.Lerp(1, Config.DeadEntityColorMultiplier, MathUtils.InverseLerp(0, time, timer));
foreach (var item in thalamusItems) foreach (var item in thalamusItems)
{ {
if (item.Color.A == 0) { continue; }
if (item.Prefab.BrokenSprites.None()) if (item.Prefab.BrokenSprites.None())
{ {
Color c = item.Prefab.SpriteColor; Color c = originalColors[item];
item.SpriteColor = new Color(c.R / 255f * m, c.G / 255f * m, c.B / 255f * m, c.A / 255f); item.SpriteColor = new Color(c.R / 255f * m, c.G / 255f * m, c.B / 255f * m, c.A / 255f);
} }
} }
foreach (var structure in thalamusStructures) foreach (var structure in thalamusStructures)
{ {
Color c = structure.Prefab.SpriteColor; Color c = originalColors[structure];
structure.SpriteColor = new Color(c.R / 255f * m, c.G / 255f * m, c.B / 255f * m, c.A / 255f); structure.SpriteColor = new Color(c.R / 255f * m, c.G / 255f * m, c.B / 255f * m, c.A / 255f);
} }
yield return CoroutineStatus.Running; yield return CoroutineStatus.Running;
@@ -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 && !character.IsRagdolled ? 0.01f : 0.2f; float errorTolerance = character.CanMove && !character.IsRagdolled ? 0.01f : 0.2f;
@@ -109,6 +109,26 @@ 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> /// <summary>
/// Can be used to set camera shake from status effects /// Can be used to set camera shake from status effects
/// </summary> /// </summary>
@@ -278,6 +298,21 @@ namespace Barotrauma
{ {
keys[i].SetState(); keys[i].SetState();
} }
if (CharacterInventory.IsMouseOnInventory && CharacterHUD.ShouldDrawInventory(this))
{
ResetInputIfPrimaryMouse(InputType.Use);
ResetInputIfPrimaryMouse(InputType.Shoot);
ResetInputIfPrimaryMouse(InputType.Select);
void ResetInputIfPrimaryMouse(InputType inputType)
{
if (GameSettings.CurrentConfig.KeyMap.Bindings[inputType].MouseButton == MouseButton.PrimaryMouse)
{
keys[(int)inputType].Reset();
}
}
}
//if we were firing (= pressing the aim and shoot keys at the same time) //if we were firing (= pressing the aim and shoot keys at the same time)
//and the fire key is the same as Select or Use, reset the key to prevent accidentally selecting/using items //and the fire key is the same as Select or Use, reset the key to prevent accidentally selecting/using items
if (wasFiring && !keys[(int)InputType.Shoot].Held) if (wasFiring && !keys[(int)InputType.Shoot].Held)
@@ -296,8 +331,7 @@ namespace Barotrauma
float targetOffsetAmount = 0.0f; float targetOffsetAmount = 0.0f;
if (moveCam) if (moveCam)
{ {
if (NeedsAir && !IsProtectedFromPressure() && if (!IsProtectedFromPressure && (AnimController.CurrentHull == null || AnimController.CurrentHull.LethalPressure > 0.0f))
(AnimController.CurrentHull == null || AnimController.CurrentHull.LethalPressure > 0.0f))
{ {
float pressure = AnimController.CurrentHull == null ? 100.0f : AnimController.CurrentHull.LethalPressure; float pressure = AnimController.CurrentHull == null ? 100.0f : AnimController.CurrentHull.LethalPressure;
if (pressure > 0.0f) if (pressure > 0.0f)
@@ -911,7 +945,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;
} }
@@ -987,7 +1021,7 @@ namespace Barotrauma
} }
} }
if (CharacterHealth.DisplayedVitality < MaxVitality * 0.98f && hudInfoVisible) 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));
@@ -13,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;
@@ -24,9 +23,18 @@ 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 abstract string NumberToDisplay { get; }
public abstract Color Color { 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)
@@ -34,7 +42,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)
@@ -42,22 +50,95 @@ namespace Barotrauma
{ {
Color = GUIStyle.Red Color = GUIStyle.Red
}; };
CreateNumberText(TopHealthBar);
SideContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), bossHealthContainer.RectTransform) SideContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), bossHealthContainer.RectTransform)
{ {
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
}; };
CreateNumberText(SideHealthBar);
TopContainer.Visible = SideContainer.Visible = false; TopContainer.Visible = SideContainer.Visible = false;
TopContainer.CanBeFocused = false; TopContainer.CanBeFocused = false;
TopContainer.Children.ForEach(c => c.CanBeFocused = false); TopContainer.Children.ForEach(c => c.CanBeFocused = false);
SideContainer.CanBeFocused = false; SideContainer.CanBeFocused = false;
SideContainer.Children.ForEach(c => c.CanBeFocused = false); SideContainer.Children.ForEach(c => c.CanBeFocused = false);
void CreateNumberText(GUIComponent parent)
{
new GUITextBlock(new RectTransform(Vector2.One, parent.RectTransform)
{ AbsoluteOffset = new Point(2) },
string.Empty, textAlignment: Alignment.Center, textColor: GUIStyle.TextColorDark)
{
TextGetter = () => NumberToDisplay
};
new GUITextBlock(new RectTransform(Vector2.One, parent.RectTransform),
string.Empty, textAlignment: Alignment.Center, textColor: GUIStyle.TextColorBright)
{
TextGetter = () => NumberToDisplay
};
}
}
public abstract bool IsDuplicate(object targetObject);
}
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 override Color Color =>
Character.CharacterHealth.GetAfflictionStrength(AfflictionPrefab.PoisonType) > 0 || Character.CharacterHealth.GetAfflictionStrength(AfflictionPrefab.ParalysisType) > 0 ?
GUIStyle.HealthBarColorPoisoned : GUIStyle.Red;
public override string NumberToDisplay => string.Empty;
public BossHealthBar(Character character) : base(character.DisplayName)
{
Character = character;
}
public override bool IsDuplicate(object targetObject)
{
return targetObject is Character character && 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 || GameMain.GameSession?.Missions == null || !GameMain.GameSession.Missions.Contains(Mission);
public override Color Color => GUIStyle.Red;
public override string NumberToDisplay => Mission.Prefab.ShowProgressInNumbers ?
$"{Mission.State}/{Mission.Prefab.MaxProgressState}" :
string.Empty;
public MissionProgressBar(Mission mission) : base(mission.Prefab.ProgressBarLabel)
{
Mission = mission;
}
public override bool IsDuplicate(object targetObject)
{
return targetObject is Mission mission && Mission == mission;
} }
} }
@@ -69,9 +150,10 @@ 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> bossProgressBars = 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>();
private static LanguageIdentifier cachedHudTextLanguage = LanguageIdentifier.None;
private static GUILayoutGroup bossHealthContainer; private static GUILayoutGroup bossHealthContainer;
@@ -107,7 +189,7 @@ namespace Barotrauma
GameMain.GameSession?.Campaign != null && GameMain.GameSession?.Campaign != null &&
(GameMain.GameSession.Campaign.ShowCampaignUI || GameMain.GameSession.Campaign.ForceMapUI); (GameMain.GameSession.Campaign.ShowCampaignUI || GameMain.GameSession.Campaign.ForceMapUI);
private static bool ShouldDrawInventory(Character character) public static bool ShouldDrawInventory(Character character)
{ {
var controller = character.SelectedItem?.GetComponent<Controller>(); var controller = character.SelectedItem?.GetComponent<Controller>();
@@ -121,10 +203,15 @@ namespace Barotrauma
public static LocalizedString GetCachedHudText(string textTag, InputType keyBind) public static LocalizedString GetCachedHudText(string textTag, InputType keyBind)
{ {
if (cachedHudTextLanguage != GameSettings.CurrentConfig.Language)
{
cachedHudTexts.Clear();
}
Identifier key = (textTag + keyBind).ToIdentifier(); Identifier key = (textTag + keyBind).ToIdentifier();
if (cachedHudTexts.TryGetValue(key, out LocalizedString text)) { return text; } if (cachedHudTexts.TryGetValue(key, out LocalizedString text)) { return text; }
text = TextManager.GetWithVariable(textTag, "[key]", GameSettings.CurrentConfig.KeyMap.KeyBindText(keyBind)).Value; text = TextManager.GetWithVariable(textTag, "[key]", GameSettings.CurrentConfig.KeyMap.KeyBindText(keyBind)).Value;
cachedHudTexts.Add(key, text); cachedHudTexts.Add(key, text);
cachedHudTextLanguage = GameSettings.CurrentConfig.Language;
return text; return text;
} }
@@ -159,7 +246,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)
{ {
@@ -623,7 +710,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;
@@ -648,7 +737,8 @@ namespace Barotrauma
if (!character.DisableHealthWindow && if (!character.DisableHealthWindow &&
character.IsFriendly(character.FocusedCharacter) && character.IsFriendly(character.FocusedCharacter) &&
character.FocusedCharacter.CharacterHealth.UseHealthWindow && character.FocusedCharacter.CharacterHealth.UseHealthWindow &&
character.CanInteractWith(character.FocusedCharacter, 160f, false)) character.CanInteractWith(character.FocusedCharacter, 160f, false) &&
!character.IsClimbing)
{ {
GUI.DrawString(spriteBatch, textPos, GetCachedHudText("HealHint", InputType.Health), GUI.DrawString(spriteBatch, textPos, GetCachedHudText("HealHint", InputType.Health),
GUIStyle.Green, Color.Black, 2, GUIStyle.SmallFont); GUIStyle.Green, Color.Black, 2, GUIStyle.SmallFont);
@@ -661,27 +751,47 @@ 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; }
if (bossProgressBars.Any(b => b.IsDuplicate(character))) { return; }
AddBossProgressBar(new BossHealthBar(character));
}
public static void ShowMissionProgressBar(Mission mission)
{
if (mission == null || mission.Completed || mission.Failed) { return; }
if (bossProgressBars.Any(b => b.IsDuplicate(mission))) { return; }
AddBossProgressBar(new MissionProgressBar(mission));
}
public static void ClearBossProgressBars()
{
for (int i = bossProgressBars.Count - 1; i>= 0; i--)
{
RemoveBossProgressBar(bossProgressBars[i]);
}
bossProgressBars.Clear();
}
private static void RemoveBossProgressBar(BossProgressBar progressBar)
{
progressBar.SideContainer.Parent?.RemoveChild(progressBar.SideContainer);
progressBar.TopContainer.Parent?.RemoveChild(progressBar.TopContainer);
bossProgressBars.Remove(progressBar);
}
private static void AddBossProgressBar(BossProgressBar progressBar)
{
var healthBarMode = GameMain.NetworkMember?.ServerSettings.ShowEnemyHealthBars ?? GameSettings.CurrentConfig.ShowEnemyHealthBars; var healthBarMode = GameMain.NetworkMember?.ServerSettings.ShowEnemyHealthBars ?? GameSettings.CurrentConfig.ShowEnemyHealthBars;
if (healthBarMode == EnemyHealthBarMode.HideAll) if (healthBarMode == EnemyHealthBarMode.HideAll)
{ {
return; return;
} }
if (bossProgressBars.Count > 5)
var existingBar = bossHealthBars.Find(b => b.Character == character);
if (existingBar != null)
{ {
existingBar.FadeTimer = BossHealthBarDuration; BossProgressBar oldestHealthBar = bossProgressBars.First();
return; foreach (var bar in bossProgressBars)
}
if (bossHealthBars.Count > 5)
{
BossHealthBar oldestHealthBar = bossHealthBars.First();
foreach (var bar in bossHealthBars)
{ {
if (bar.TopHealthBar.BarSize < oldestHealthBar.TopHealthBar.BarSize) if (bar.TopHealthBar.BarSize < oldestHealthBar.TopHealthBar.BarSize)
{ {
@@ -690,62 +800,69 @@ namespace Barotrauma
} }
oldestHealthBar.FadeTimer = Math.Min(oldestHealthBar.FadeTimer, 1.0f); oldestHealthBar.FadeTimer = Math.Min(oldestHealthBar.FadeTimer, 1.0f);
} }
bossProgressBars.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; var healthBarMode = GameMain.NetworkMember?.ServerSettings.ShowEnemyHealthBars ?? GameSettings.CurrentConfig.ShowEnemyHealthBars;
for (int i = 0; i < bossHealthBars.Count; i++) for (int i = 0; i < bossProgressBars.Count; i++)
{ {
var bossHealthBar = bossHealthBars[i]; var bossHealthBar = bossProgressBars[i];
bool showTopBar = i == 0; bool showTopBar = i == bossProgressBars.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(bossHealthBar.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)
Color color = bossHealthBar.Character.CharacterHealth.GetAfflictionStrength("poison") > 0 || bossHealthBar.Character.CharacterHealth.GetAfflictionStrength("paralysis") > 0 ? GUIStyle.HealthBarColorPoisoned : GUIStyle.Red;
bossHealthBar.TopHealthBar.Color = bossHealthBar.SideHealthBar.Color = color;
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 = bossProgressBars.Count - 1; i >= 0 ; i--)
for (int i = bossHealthBars.Count - 1; i >= 0 ; i--)
{ {
var bossHealthBar = bossHealthBars[i]; var bossHealthBar = bossProgressBars[i];
if (bossHealthBar.FadeTimer <= 0 || healthBarMode == EnemyHealthBarMode.HideAll) 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);
bossHealthBars.RemoveAt(i); bossProgressBars.RemoveAt(i);
bossHealthContainer.Recalculate(); bossHealthContainer.Recalculate();
} }
} }
@@ -540,14 +540,25 @@ 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);
if (jobPrefab == null)
{
throw new Exception($"Error while reading {nameof(CharacterInfo)} received from the server: could not find a job prefab with the identifier \"{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();
@@ -555,22 +566,19 @@ namespace Barotrauma
} }
} }
// 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;
ch.Head.HairColor = hairColor; ch.Head.HairColor = hairColor;
ch.Head.FacialHairColor = facialHairColor; ch.Head.FacialHairColor = facialHairColor;
ch.SetPersonalityTrait(); ch.SetPersonalityTrait();
if (ch.Job != null) ch.Job?.OverrideSkills(skillLevels);
{
ch.Job.OverrideSkills(skillLevels);
}
ch.ExperiencePoints = inc.ReadUInt16(); ch.ExperiencePoints = inc.ReadInt32();
ch.AdditionalTalentPoints = inc.ReadRangedInteger(0, MaxAdditionalTalentPoints); ch.AdditionalTalentPoints = inc.ReadRangedInteger(0, MaxAdditionalTalentPoints);
return ch; return ch;
} }
@@ -593,6 +593,7 @@ namespace Barotrauma
{ {
character.MerchantIdentifier = inc.ReadIdentifier(); character.MerchantIdentifier = inc.ReadIdentifier();
} }
character.Faction = inc.ReadIdentifier();
character.HumanPrefabHealthMultiplier = humanPrefabHealthMultiplier; character.HumanPrefabHealthMultiplier = humanPrefabHealthMultiplier;
character.Wallet.Balance = balance; character.Wallet.Balance = balance;
character.Wallet.RewardDistribution = rewardDistribution; character.Wallet.RewardDistribution = rewardDistribution;
@@ -87,6 +87,8 @@ namespace Barotrauma
/// Container for the icons above the health bar /// Container for the icons above the health bar
/// </summary> /// </summary>
private GUIComponent afflictionIconContainer; private GUIComponent afflictionIconContainer;
private float afflictionIconRefreshTimer;
const float AfflictionIconRefreshInterval = 1.0f;
private GUIButton showHiddenAfflictionsButton; private GUIButton showHiddenAfflictionsButton;
@@ -699,10 +701,11 @@ namespace Barotrauma
blurStrength = Math.Max(blurStrength, affliction.GetScreenBlurStrength()); blurStrength = Math.Max(blurStrength, affliction.GetScreenBlurStrength());
radialDistortStrength = Math.Max(radialDistortStrength, affliction.GetRadialDistortStrength()); radialDistortStrength = Math.Max(radialDistortStrength, affliction.GetRadialDistortStrength());
chromaticAberrationStrength = Math.Max(chromaticAberrationStrength, affliction.GetChromaticAberrationStrength()); chromaticAberrationStrength = Math.Max(chromaticAberrationStrength, affliction.GetChromaticAberrationStrength());
float afflictionGrainStrength = affliction.GetScreenGrainStrength(); float afflictionGrainStrength = affliction.GetScreenGrainStrength();
if (afflictionGrainStrength > 0.0f) if (afflictionGrainStrength > 0.0f)
{ {
grainStrength = Math.Max(grainStrength, affliction.GetScreenGrainStrength()); grainStrength = Math.Max(grainStrength, afflictionGrainStrength);
Color afflictionGrainColor = affliction.GetActiveEffect()?.GrainColor ?? Color.White; Color afflictionGrainColor = affliction.GetActiveEffect()?.GrainColor ?? Color.White;
grainColor = Color.Lerp(grainColor, afflictionGrainColor, (float)Math.Pow(1.0f - oxygenLowStrength, 2)); grainColor = Color.Lerp(grainColor, afflictionGrainColor, (float)Math.Pow(1.0f - oxygenLowStrength, 2));
} }
@@ -861,7 +864,7 @@ namespace Barotrauma
{ {
treatmentButton.ToolTip = treatmentButton.ToolTip =
RichString.Rich( RichString.Rich(
$"‖color:gui.green‖[{TextManager.Get(PlayerInput.MouseButtonsSwapped() ? "input.rightmouse" : "input.leftmouse")}] " $"‖color:gui.green‖[{PlayerInput.PrimaryMouseLabel}] "
+ $"{TextManager.Get("quickuseaction.usetreatment")}‖color:end‖" + '\n' + $"{TextManager.Get("quickuseaction.usetreatment")}‖color:end‖" + '\n'
+ treatmentButton.ToolTip.NestedStr); + treatmentButton.ToolTip.NestedStr);
} }
@@ -1018,12 +1021,8 @@ namespace Barotrauma
foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions) foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
{ {
var affliction = kvp.Key; var affliction = kvp.Key;
if (affliction.Prefab.AfflictionOverlay != null) affliction.Prefab.AfflictionOverlay?.Draw(spriteBatch, Vector2.Zero, Color.White * affliction.GetAfflictionOverlayMultiplier(), Vector2.Zero, 0.0f,
{ new Vector2(GameMain.GraphicsWidth / DamageOverlay.size.X, GameMain.GraphicsHeight / DamageOverlay.size.Y));
Sprite ScreenAfflictionOverlay = affliction.Prefab.AfflictionOverlay;
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));
}
} }
float damageOverlayAlpha = DamageOverlayTimer; float damageOverlayAlpha = DamageOverlayTimer;
@@ -1157,15 +1156,20 @@ namespace Barotrauma
} }
} }
afflictionIconContainer.RectTransform.SortChildren((r1, r2) => afflictionIconRefreshTimer -= deltaTime;
if (afflictionIconRefreshTimer <= 0.0f)
{ {
if (r1.GUIComponent.UserData is not AfflictionPrefab prefab1) { return -1; } afflictionIconContainer.RectTransform.SortChildren((r1, r2) =>
if (r2.GUIComponent.UserData is not AfflictionPrefab prefab2) { return 1; } {
var index1 = statusIcons.IndexOf(s => s.Prefab == prefab1); if (r1.GUIComponent.UserData is not AfflictionPrefab prefab1) { return -1; }
var index2 = statusIcons.IndexOf(s => s.Prefab == prefab2); if (r2.GUIComponent.UserData is not AfflictionPrefab prefab2) { return 1; }
return index1.CompareTo(index2); var index1 = statusIcons.IndexOf(s => s.Prefab == prefab1);
}); var index2 = statusIcons.IndexOf(s => s.Prefab == prefab2);
(afflictionIconContainer as GUILayoutGroup).NeedsToRecalculate = true; return index1.CompareTo(index2);
});
(afflictionIconContainer as GUILayoutGroup).NeedsToRecalculate = true;
afflictionIconRefreshTimer = AfflictionIconRefreshInterval;
}
Rectangle hiddenAfflictionHoverArea = showHiddenAfflictionsButton.Rect; Rectangle hiddenAfflictionHoverArea = showHiddenAfflictionsButton.Rect;
foreach (GUIComponent child in hiddenAfflictionIconContainer.Children) foreach (GUIComponent child in hiddenAfflictionIconContainer.Children)
@@ -1983,6 +1987,7 @@ namespace Barotrauma
{ {
newAfflictions.Clear(); newAfflictions.Clear();
newPeriodicEffects.Clear(); newPeriodicEffects.Clear();
bool newAdded = false;
byte afflictionCount = inc.ReadByte(); byte afflictionCount = inc.ReadByte();
for (int i = 0; i < afflictionCount; i++) for (int i = 0; i < afflictionCount; i++)
{ {
@@ -2062,6 +2067,7 @@ namespace Barotrauma
{ {
existingAffliction = afflictionPrefab.Instantiate(strength); existingAffliction = afflictionPrefab.Instantiate(strength);
afflictions.Add(existingAffliction, limb); afflictions.Add(existingAffliction, limb);
newAdded = true;
} }
existingAffliction.SetStrength(strength); existingAffliction.SetStrength(strength);
if (existingAffliction == stunAffliction) if (existingAffliction == stunAffliction)
@@ -2088,6 +2094,11 @@ namespace Barotrauma
CalculateVitality(); CalculateVitality();
DisplayedVitality = Vitality; DisplayedVitality = Vitality;
if (newAdded)
{
MedicalClinic.OnAfflictionCountChanged(Character);
}
} }
partial void UpdateSkinTint() partial void UpdateSkinTint()
@@ -554,7 +554,7 @@ namespace Barotrauma
float damage = 0; float damage = 0;
foreach (var affliction in result.Afflictions) foreach (var affliction in result.Afflictions)
{ {
if (affliction.Prefab.DamageParticles && affliction.Prefab.AfflictionType == "damage") if (affliction.Prefab.DamageParticles && affliction.Prefab.AfflictionType == AfflictionPrefab.DamageType)
{ {
damage += affliction.GetVitalityDecrease(null); damage += affliction.GetVitalityDecrease(null);
} }
@@ -563,11 +563,11 @@ namespace Barotrauma
float bleedingDamageMultiplier = 1; float bleedingDamageMultiplier = 1;
foreach (DamageModifier damageModifier in result.AppliedDamageModifiers) foreach (DamageModifier damageModifier in result.AppliedDamageModifiers)
{ {
if (damageModifier.MatchesAfflictionType("damage")) if (damageModifier.MatchesAfflictionType(AfflictionPrefab.DamageType))
{ {
damageMultiplier *= damageModifier.DamageMultiplier; damageMultiplier *= damageModifier.DamageMultiplier;
} }
else if (damageModifier.MatchesAfflictionType("bleeding")) else if (damageModifier.MatchesAfflictionType(AfflictionPrefab.BleedingType))
{ {
bleedingDamageMultiplier *= damageModifier.DamageMultiplier; bleedingDamageMultiplier *= damageModifier.DamageMultiplier;
} }
@@ -599,7 +599,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;
} }
} }
@@ -646,7 +646,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))),
@@ -683,10 +683,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)
{ {
@@ -727,7 +727,7 @@ namespace Barotrauma
} }
} }
float herpesStrength = character.CharacterHealth.GetAfflictionStrength("spaceherpes"); float herpesStrength = character.CharacterHealth.GetAfflictionStrength(AfflictionPrefab.SpaceHerpesType);
bool hideLimb = Hide || bool hideLimb = Hide ||
OtherWearables.Any(w => w.HideLimb) || OtherWearables.Any(w => w.HideLimb) ||
@@ -1245,6 +1245,12 @@ namespace Barotrauma
}; };
paramsToPass.Params["wearableUvToClipperUv"] = wearableUvToClipperUv; paramsToPass.Params["wearableUvToClipperUv"] = wearableUvToClipperUv;
paramsToPass.Params["stencilUVmin"] = new Vector2(
(float)alphaClipper.Sprite.SourceRect.X / alphaClipper.Sprite.Texture.Width,
(float)alphaClipper.Sprite.SourceRect.Y / alphaClipper.Sprite.Texture.Height);
paramsToPass.Params["stencilUVmax"] = new Vector2(
(float)alphaClipper.Sprite.SourceRect.Right / alphaClipper.Sprite.Texture.Width,
(float)alphaClipper.Sprite.SourceRect.Bottom / alphaClipper.Sprite.Texture.Height);
paramsToPass.Params["clipperTexelSize"] = 2f / alphaClipper.Sprite.Texture.Width; paramsToPass.Params["clipperTexelSize"] = 2f / alphaClipper.Sprite.Texture.Width;
paramsToPass.Params["aCutoff"] = 2f / 255f; paramsToPass.Params["aCutoff"] = 2f / 255f;
paramsToPass.Params["xTexture"] = wearable.Sprite.Texture; paramsToPass.Params["xTexture"] = wearable.Sprite.Texture;
@@ -425,6 +425,10 @@ namespace Barotrauma
{ {
CheatsEnabled = true; CheatsEnabled = true;
SteamAchievementManager.CheatsEnabled = true; SteamAchievementManager.CheatsEnabled = true;
if (GameMain.GameSession?.Campaign is CampaignMode campaign)
{
campaign.CheatsEnabled = true;
}
NewMessage("Enabled cheat commands.", Color.Red); NewMessage("Enabled cheat commands.", Color.Red);
#if USE_STEAM #if USE_STEAM
NewMessage("Steam achievements have been disabled during this play session.", Color.Red); NewMessage("Steam achievements have been disabled during this play session.", Color.Red);
@@ -639,7 +643,7 @@ namespace Barotrauma
{ {
if (Submarine.MainSub == null) { return; } if (Submarine.MainSub == null) { return; }
MapEntity.SelectedList.Clear(); MapEntity.SelectedList.Clear();
MapEntity.mapEntityList.ForEach(me => me.IsHighlighted = false); MapEntity.ClearHighlightedEntities();
WikiImage.Create(Submarine.MainSub); WikiImage.Create(Submarine.MainSub);
})); }));
@@ -752,7 +756,7 @@ namespace Barotrauma
state = !GameMain.LightManager.LosEnabled; state = !GameMain.LightManager.LosEnabled;
} }
GameMain.LightManager.LosEnabled = state; GameMain.LightManager.LosEnabled = state;
NewMessage("Line of sight effect " + (GameMain.LightManager.LosEnabled ? "enabled" : "disabled"), Color.White); NewMessage("Line of sight effect " + (GameMain.LightManager.LosEnabled ? "enabled" : "disabled"), Color.Yellow);
}); });
AssignRelayToServer("los", false); AssignRelayToServer("los", false);
@@ -763,7 +767,7 @@ namespace Barotrauma
state = !GameMain.LightManager.LightingEnabled; state = !GameMain.LightManager.LightingEnabled;
} }
GameMain.LightManager.LightingEnabled = state; GameMain.LightManager.LightingEnabled = state;
NewMessage("Lighting " + (GameMain.LightManager.LightingEnabled ? "enabled" : "disabled"), Color.White); NewMessage("Lighting " + (GameMain.LightManager.LightingEnabled ? "enabled" : "disabled"), Color.Yellow);
}); });
AssignRelayToServer("lighting|lights", false); AssignRelayToServer("lighting|lights", false);
@@ -781,7 +785,7 @@ namespace Barotrauma
hull.OriginalAmbientLight = null; hull.OriginalAmbientLight = null;
} }
} }
NewMessage("Restored all hull ambient lights", Color.White); NewMessage("Restored all hull ambient lights", Color.Yellow);
return; return;
} }
@@ -803,11 +807,11 @@ namespace Barotrauma
if (add) if (add)
{ {
NewMessage($"Set ambient light color to {color}.", Color.White); NewMessage($"Set ambient light color to {color}.", Color.Yellow);
} }
else else
{ {
NewMessage($"Increased ambient light by {color}.", Color.White); NewMessage($"Increased ambient light by {color}.", Color.Yellow);
} }
}); });
AssignRelayToServer("ambientlight", false); AssignRelayToServer("ambientlight", false);
@@ -1124,7 +1128,18 @@ namespace Barotrauma
state = !GameMain.DebugDraw; state = !GameMain.DebugDraw;
} }
GameMain.DebugDraw = state; GameMain.DebugDraw = state;
NewMessage("Debug draw mode " + (GameMain.DebugDraw ? "enabled" : "disabled"), Color.White); NewMessage("Debug draw mode " + (GameMain.DebugDraw ? "enabled" : "disabled"), Color.Yellow);
});
AssignRelayToServer("debugdraw", false);
AssignOnExecute("debugdrawlos", (string[] args) =>
{
if (args.None() || !bool.TryParse(args[0], out bool state))
{
state = !GameMain.LightManager.DebugLos;
}
GameMain.LightManager.DebugLos = state;
NewMessage("Los debug draw mode " + (GameMain.LightManager.DebugLos ? "enabled" : "disabled"), Color.Yellow);
}); });
AssignRelayToServer("debugdraw", false); AssignRelayToServer("debugdraw", false);
@@ -1146,7 +1161,7 @@ namespace Barotrauma
GameMain.LightManager.LosEnabled = true; GameMain.LightManager.LosEnabled = true;
GameMain.LightManager.LosAlpha = 1f; GameMain.LightManager.LosAlpha = 1f;
} }
NewMessage("Dev mode " + (GameMain.DevMode ? "enabled" : "disabled"), Color.White); NewMessage("Dev mode " + (GameMain.DevMode ? "enabled" : "disabled"), Color.Yellow);
}); });
AssignRelayToServer("devmode", false); AssignRelayToServer("devmode", false);
@@ -1157,7 +1172,7 @@ namespace Barotrauma
state = !TextManager.DebugDraw; state = !TextManager.DebugDraw;
} }
TextManager.DebugDraw = state; TextManager.DebugDraw = state;
NewMessage("Localization debug draw mode " + (TextManager.DebugDraw ? "enabled" : "disabled"), Color.White); NewMessage("Localization debug draw mode " + (TextManager.DebugDraw ? "enabled" : "disabled"), Color.Yellow);
}); });
AssignRelayToServer("debugdraw", false); AssignRelayToServer("debugdraw", false);
@@ -1170,19 +1185,19 @@ namespace Barotrauma
var config = GameSettings.CurrentConfig; var config = GameSettings.CurrentConfig;
config.Audio.DisableVoiceChatFilters = state; config.Audio.DisableVoiceChatFilters = state;
GameSettings.SetCurrentConfig(config); GameSettings.SetCurrentConfig(config);
NewMessage("Voice chat filters " + (GameSettings.CurrentConfig.Audio.DisableVoiceChatFilters ? "disabled" : "enabled"), Color.White); NewMessage("Voice chat filters " + (GameSettings.CurrentConfig.Audio.DisableVoiceChatFilters ? "disabled" : "enabled"), Color.Yellow);
}); });
AssignRelayToServer("togglevoicechatfilters", false); AssignRelayToServer("togglevoicechatfilters", false);
commands.Add(new Command("fpscounter", "fpscounter: Toggle the FPS counter.", (string[] args) => commands.Add(new Command("fpscounter", "fpscounter: Toggle the FPS counter.", (string[] args) =>
{ {
GameMain.ShowFPS = !GameMain.ShowFPS; GameMain.ShowFPS = !GameMain.ShowFPS;
NewMessage("FPS counter " + (GameMain.DebugDraw ? "enabled" : "disabled"), Color.White); NewMessage("FPS counter " + (GameMain.DebugDraw ? "enabled" : "disabled"), Color.Yellow);
})); }));
commands.Add(new Command("showperf", "showperf: Toggle performance statistics on/off.", (string[] args) => commands.Add(new Command("showperf", "showperf: Toggle performance statistics on/off.", (string[] args) =>
{ {
GameMain.ShowPerf = !GameMain.ShowPerf; GameMain.ShowPerf = !GameMain.ShowPerf;
NewMessage("Performance statistics " + (GameMain.ShowPerf ? "enabled" : "disabled"), Color.White); NewMessage("Performance statistics " + (GameMain.ShowPerf ? "enabled" : "disabled"), Color.Yellow);
})); }));
AssignOnClientExecute("netstats", (string[] args) => AssignOnClientExecute("netstats", (string[] args) =>
@@ -1194,55 +1209,55 @@ namespace Barotrauma
commands.Add(new Command("hudlayoutdebugdraw|debugdrawhudlayout", "hudlayoutdebugdraw: Toggle the debug drawing mode of HUD layout areas on/off.", (string[] args) => commands.Add(new Command("hudlayoutdebugdraw|debugdrawhudlayout", "hudlayoutdebugdraw: Toggle the debug drawing mode of HUD layout areas on/off.", (string[] args) =>
{ {
HUDLayoutSettings.DebugDraw = !HUDLayoutSettings.DebugDraw; HUDLayoutSettings.DebugDraw = !HUDLayoutSettings.DebugDraw;
NewMessage("HUD layout debug draw mode " + (HUDLayoutSettings.DebugDraw ? "enabled" : "disabled"), Color.White); NewMessage("HUD layout debug draw mode " + (HUDLayoutSettings.DebugDraw ? "enabled" : "disabled"), Color.Yellow);
})); }));
commands.Add(new Command("interactdebugdraw|debugdrawinteract", "interactdebugdraw: Toggle the debug drawing mode of item interaction ranges on/off.", (string[] args) => commands.Add(new Command("interactdebugdraw|debugdrawinteract", "interactdebugdraw: Toggle the debug drawing mode of item interaction ranges on/off.", (string[] args) =>
{ {
Character.DebugDrawInteract = !Character.DebugDrawInteract; Character.DebugDrawInteract = !Character.DebugDrawInteract;
NewMessage("Interact debug draw mode " + (Character.DebugDrawInteract ? "enabled" : "disabled"), Color.White); NewMessage("Interact debug draw mode " + (Character.DebugDrawInteract ? "enabled" : "disabled"), Color.Yellow);
}, isCheat: true)); }, isCheat: true));
AssignOnExecute("togglehud|hud", (string[] args) => AssignOnExecute("togglehud|hud", (string[] args) =>
{ {
GUI.DisableHUD = !GUI.DisableHUD; GUI.DisableHUD = !GUI.DisableHUD;
GameMain.Instance.IsMouseVisible = !GameMain.Instance.IsMouseVisible; GameMain.Instance.IsMouseVisible = !GameMain.Instance.IsMouseVisible;
NewMessage(GUI.DisableHUD ? "Disabled HUD" : "Enabled HUD", Color.White); NewMessage(GUI.DisableHUD ? "Disabled HUD" : "Enabled HUD", Color.Yellow);
}); });
AssignRelayToServer("togglehud|hud", false); AssignRelayToServer("togglehud|hud", false);
AssignOnExecute("toggleupperhud", (string[] args) => AssignOnExecute("toggleupperhud", (string[] args) =>
{ {
GUI.DisableUpperHUD = !GUI.DisableUpperHUD; GUI.DisableUpperHUD = !GUI.DisableUpperHUD;
NewMessage(GUI.DisableUpperHUD ? "Disabled upper HUD" : "Enabled upper HUD", Color.White); NewMessage(GUI.DisableUpperHUD ? "Disabled upper HUD" : "Enabled upper HUD", Color.Yellow);
}); });
AssignRelayToServer("toggleupperhud", false); AssignRelayToServer("toggleupperhud", false);
AssignOnExecute("toggleitemhighlights", (string[] args) => AssignOnExecute("toggleitemhighlights", (string[] args) =>
{ {
GUI.DisableItemHighlights = !GUI.DisableItemHighlights; GUI.DisableItemHighlights = !GUI.DisableItemHighlights;
NewMessage(GUI.DisableItemHighlights ? "Disabled item highlights" : "Enabled item highlights", Color.White); NewMessage(GUI.DisableItemHighlights ? "Disabled item highlights" : "Enabled item highlights", Color.Yellow);
}); });
AssignRelayToServer("toggleitemhighlights", false); AssignRelayToServer("toggleitemhighlights", false);
AssignOnExecute("togglecharacternames", (string[] args) => AssignOnExecute("togglecharacternames", (string[] args) =>
{ {
GUI.DisableCharacterNames = !GUI.DisableCharacterNames; GUI.DisableCharacterNames = !GUI.DisableCharacterNames;
NewMessage(GUI.DisableCharacterNames ? "Disabled character names" : "Enabled character names", Color.White); NewMessage(GUI.DisableCharacterNames ? "Disabled character names" : "Enabled character names", Color.Yellow);
}); });
AssignRelayToServer("togglecharacternames", false); AssignRelayToServer("togglecharacternames", false);
AssignOnExecute("followsub", (string[] args) => AssignOnExecute("followsub", (string[] args) =>
{ {
Camera.FollowSub = !Camera.FollowSub; Camera.FollowSub = !Camera.FollowSub;
NewMessage(Camera.FollowSub ? "Set the camera to follow the closest submarine" : "Disabled submarine following.", Color.White); NewMessage(Camera.FollowSub ? "Set the camera to follow the closest submarine" : "Disabled submarine following.", Color.Yellow);
}); });
AssignRelayToServer("followsub", false); AssignRelayToServer("followsub", false);
AssignOnExecute("toggleaitargets|aitargets", (string[] args) => AssignOnExecute("toggleaitargets|aitargets", (string[] args) =>
{ {
AITarget.ShowAITargets = !AITarget.ShowAITargets; AITarget.ShowAITargets = !AITarget.ShowAITargets;
NewMessage(AITarget.ShowAITargets ? "Enabled AI target drawing" : "Disabled AI target drawing", Color.White); NewMessage(AITarget.ShowAITargets ? "Enabled AI target drawing" : "Disabled AI target drawing", Color.Yellow);
}); });
AssignRelayToServer("toggleaitargets|aitargets", false); AssignRelayToServer("toggleaitargets|aitargets", false);
@@ -1264,10 +1279,36 @@ namespace Barotrauma
GameMain.LightManager.LosEnabled = true; GameMain.LightManager.LosEnabled = true;
GameMain.LightManager.LosAlpha = 1f; GameMain.LightManager.LosAlpha = 1f;
} }
NewMessage(HumanAIController.debugai ? "AI debug info visible" : "AI debug info hidden", Color.White); NewMessage(HumanAIController.debugai ? "AI debug info visible" : "AI debug info hidden", Color.Yellow);
}); });
AssignRelayToServer("debugai", false); AssignRelayToServer("debugai", false);
AssignOnExecute("showmonsters", (string[] args) =>
{
CreatureMetrics.UnlockAll = true;
CreatureMetrics.Save();
NewMessage("All monsters are now visible in the character editor.", Color.Yellow);
if (Screen.Selected == GameMain.CharacterEditorScreen)
{
GameMain.CharacterEditorScreen.Deselect();
GameMain.CharacterEditorScreen.Select();
}
});
AssignRelayToServer("showmonsters", false);
AssignOnExecute("hidemonsters", (string[] args) =>
{
CreatureMetrics.UnlockAll = false;
CreatureMetrics.Save();
NewMessage("All monsters that haven't yet been encountered in the game are now hidden in the character editor.", Color.Yellow);
if (Screen.Selected == GameMain.CharacterEditorScreen)
{
GameMain.CharacterEditorScreen.Deselect();
GameMain.CharacterEditorScreen.Select();
}
});
AssignRelayToServer("hidemonsters", false);
AssignRelayToServer("water|editwater", false); AssignRelayToServer("water|editwater", false);
AssignRelayToServer("fire|editfire", false); AssignRelayToServer("fire|editfire", false);
@@ -2833,7 +2874,7 @@ namespace Barotrauma
NewMessage("Valid ranks are:", Color.White); NewMessage("Valid ranks are:", Color.White);
foreach (PermissionPreset permissionPreset in PermissionPreset.List) foreach (PermissionPreset permissionPreset in PermissionPreset.List)
{ {
NewMessage(" - " + permissionPreset.Name, Color.White); NewMessage(" - " + permissionPreset.DisplayName, Color.White);
} }
ShowQuestionPrompt("Rank to grant to client " + args[0] + "?", (rank) => ShowQuestionPrompt("Rank to grant to client " + args[0] + "?", (rank) =>
{ {
@@ -2991,7 +3032,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);
}, },
() => () =>
{ {
@@ -3319,6 +3360,11 @@ namespace Barotrauma
else else
{ {
NewMessage("Level seed: " + Level.Loaded.Seed); NewMessage("Level seed: " + Level.Loaded.Seed);
NewMessage("Level generation params: " + Level.Loaded.GenerationParams.Identifier);
NewMessage("Adjacent locations: " + (Level.Loaded.StartLocation?.Type.Identifier ?? "none".ToIdentifier()) + ", " + (Level.Loaded.StartLocation?.Type.Identifier ?? "none".ToIdentifier()));
NewMessage("Mirrored: " + Level.Loaded.Mirrored);
NewMessage("Level size: " + Level.Loaded.Size.X + "x" + Level.Loaded.Size.Y);
NewMessage("Minimum main path width: " + (Level.Loaded.LevelData?.MinMainPathWidth?.ToString() ?? "unknown"));
} }
}); });
} }
@@ -83,6 +83,7 @@ namespace Barotrauma
GUIListBox conversationList = lastMessageBox.FindChild("conversationlist", true) as GUIListBox; GUIListBox conversationList = lastMessageBox.FindChild("conversationlist", true) as GUIListBox;
Debug.Assert(conversationList != null); Debug.Assert(conversationList != null);
DisableButtons(conversationList.Content.GetAllChildren<GUIButton>(), selectedButton: null);
// gray out the last text block // gray out the last text block
if (conversationList.Content.Children.LastOrDefault() is GUILayoutGroup lastElement) if (conversationList.Content.Children.LastOrDefault() is GUILayoutGroup lastElement)
{ {
@@ -269,14 +270,7 @@ namespace Barotrauma
if (actionInstance != null) if (actionInstance != null)
{ {
actionInstance.selectedOption = selectedOption; actionInstance.selectedOption = selectedOption;
foreach (GUIButton otherButton in optionButtons) DisableButtons(optionButtons, btn);
{
otherButton.CanBeFocused = false;
if (otherButton != btn)
{
otherButton.TextBlock.OverrideTextColor(Color.DarkGray * 0.8f);
}
}
btn.ExternalHighlight = true; btn.ExternalHighlight = true;
return true; return true;
} }
@@ -286,14 +280,7 @@ namespace Barotrauma
SendResponse(actionId.Value, selectedOption); SendResponse(actionId.Value, selectedOption);
btn.CanBeFocused = false; btn.CanBeFocused = false;
btn.ExternalHighlight = true; btn.ExternalHighlight = true;
foreach (GUIButton otherButton in optionButtons) DisableButtons(optionButtons, btn);
{
otherButton.CanBeFocused = false;
if (otherButton != btn)
{
otherButton.TextBlock.OverrideTextColor(Color.DarkGray * 0.8f);
}
}
return true; return true;
} }
//should not happen //should not happen
@@ -305,6 +292,18 @@ namespace Barotrauma
} }
} }
public static void SelectOption(ushort actionId, int option)
{
if (lastMessageBox.UserData is Pair<string, ushort> userData)
{
if (userData.Second != actionId) { return; }
GUIListBox conversationList = lastMessageBox.FindChild("conversationlist", true) as GUIListBox;
Debug.Assert(conversationList != null);
DisableButtons(conversationList.Content.GetAllChildren<GUIButton>(), (btn) => btn.UserData is int i && i == option);
}
}
private static Tuple<Vector2, Point> GetSizes(DialogTypes dialogTypes) private static Tuple<Vector2, Point> GetSizes(DialogTypes dialogTypes)
{ {
return dialogTypes switch return dialogTypes switch
@@ -383,6 +382,30 @@ namespace Barotrauma
return buttons; return buttons;
} }
private static void DisableButtons(IEnumerable<GUIButton> buttons, GUIButton selectedButton)
{
DisableButtons(buttons, (btn) => btn == selectedButton);
}
private static void DisableButtons(IEnumerable<GUIButton> buttons, Func<GUIButton, bool> isSelectedButton)
{
foreach (GUIButton btn in buttons)
{
if (btn.CanBeFocused)
{
btn.CanBeFocused = false;
if (isSelectedButton(btn))
{
btn.Selected = true;
}
else
{
btn.TextBlock.OverrideTextColor(Color.DarkGray * 0.8f);
}
}
}
}
private static void SendResponse(UInt16 actionId, int selectedOption) private static void SendResponse(UInt16 actionId, int selectedOption)
{ {
IWriteMessage outmsg = new WriteOnlyMessage(); IWriteMessage outmsg = new WriteOnlyMessage();
@@ -608,49 +608,61 @@ namespace Barotrauma
} }
break; break;
case NetworkEventType.CONVERSATION: case NetworkEventType.CONVERSATION:
UInt16 identifier = msg.ReadUInt16();
string eventSprite = msg.ReadString();
byte dialogType = msg.ReadByte();
bool continueConversation = msg.ReadBoolean();
UInt16 speakerId = msg.ReadUInt16();
string text = msg.ReadString();
bool fadeToBlack = msg.ReadBoolean();
byte optionCount = msg.ReadByte();
List<string> options = new List<string>();
for (int i = 0; i < optionCount; i++)
{ {
options.Add(msg.ReadString()); UInt16 identifier = msg.ReadUInt16();
} string eventSprite = msg.ReadString();
byte dialogType = msg.ReadByte();
byte endCount = msg.ReadByte(); bool continueConversation = msg.ReadBoolean();
int[] endings = new int[endCount]; UInt16 speakerId = msg.ReadUInt16();
for (int i = 0; i < endCount; i++) string text = msg.ReadString();
{ bool fadeToBlack = msg.ReadBoolean();
endings[i] = msg.ReadByte(); byte optionCount = msg.ReadByte();
} List<string> options = new List<string>();
for (int i = 0; i < optionCount; i++)
if (string.IsNullOrEmpty(text) && optionCount == 0)
{
GUIMessageBox.MessageBoxes.ForEachMod(mb =>
{ {
if (mb.UserData is Pair<string, UInt16> pair && pair.First == "ConversationAction" && pair.Second == identifier) options.Add(msg.ReadString());
}
byte endCount = msg.ReadByte();
int[] endings = new int[endCount];
for (int i = 0; i < endCount; i++)
{
endings[i] = msg.ReadByte();
}
if (string.IsNullOrEmpty(text) && optionCount == 0)
{
GUIMessageBox.MessageBoxes.ForEachMod(mb =>
{ {
(mb as GUIMessageBox)?.Close(); if (mb.UserData is Pair<string, UInt16> pair && pair.First == "ConversationAction" && pair.Second == identifier)
} {
}); (mb as GUIMessageBox)?.Close();
}
});
}
else
{
ConversationAction.CreateDialog(text, Entity.FindEntityByID(speakerId) as Character, options, endings, eventSprite, identifier, fadeToBlack, (ConversationAction.DialogTypes)dialogType, continueConversation);
}
if (Entity.FindEntityByID(speakerId) is Character speaker)
{
speaker.CampaignInteractionType = CampaignMode.InteractionType.None;
speaker.SetCustomInteract(null, null);
}
break;
} }
else case NetworkEventType.CONVERSATION_SELECTED_OPTION:
{ {
ConversationAction.CreateDialog(text, Entity.FindEntityByID(speakerId) as Character, options, endings, eventSprite, identifier, fadeToBlack, (ConversationAction.DialogTypes)dialogType, continueConversation); UInt16 identifier = msg.ReadUInt16();
int selectedOption = msg.ReadByte() - 1;
ConversationAction.SelectOption(identifier, selectedOption);
break;
} }
if (Entity.FindEntityByID(speakerId) is Character speaker)
{
speaker.CampaignInteractionType = CampaignMode.InteractionType.None;
speaker.SetCustomInteract(null, null);
}
break;
case NetworkEventType.MISSION: case NetworkEventType.MISSION:
Identifier missionIdentifier = msg.ReadIdentifier(); Identifier missionIdentifier = msg.ReadIdentifier();
int locationIndex = msg.ReadInt32();
int destinationIndex = 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 +672,26 @@ namespace Barotrauma
{ {
IconColor = prefab.IconColor IconColor = prefab.IconColor
}; };
if (GameMain.GameSession?.Map is { } map && locationIndex >= 0 && locationIndex < map.Locations.Count)
{
Location location = map.Locations[locationIndex];
map.Discover(location, checkTalents: false);
LocationConnection? connection = null;
if (destinationIndex != locationIndex && destinationIndex >= 0 && destinationIndex < map.Locations.Count)
{
Location destination = map.Locations[destinationIndex];
connection = map.Connections.FirstOrDefault(c => c.Locations.Contains(location) && c.Locations.Contains(destination));
}
if (connection != null)
{
location.UnlockMission(prefab, connection);
}
else
{
location.UnlockMission(prefab);
}
}
} }
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();
@@ -10,8 +10,7 @@ namespace Barotrauma
public override RichString GetMissionRewardText(Submarine sub) public override RichString GetMissionRewardText(Submarine sub)
{ {
LocalizedString rewardText = TextManager.GetWithVariable("currencyformat", "[credits]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", GetReward(sub))); LocalizedString rewardText = GetRewardAmountText(sub);
LocalizedString retVal; LocalizedString retVal;
if (rewardPerCrate.HasValue) if (rewardPerCrate.HasValue)
{ {
@@ -0,0 +1,138 @@
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()
{
SoundPlayer.ForceMusicUpdate();
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)
{
RunWhilePaused = false,
EndWaitDuration = 3.0f
};
}
}, delay: 3.0f);
}
else if (Phase == MissionPhase.AllItemsDestroyed)
{
CoroutineManager.StartCoroutine(wakeUpCoroutine(), name: "EndMission.wakeUpCoroutine");
}
else if (Phase == MissionPhase.BossKilled)
{
if (!string.IsNullOrEmpty(endCinematicSound))
{
SoundPlayer.PlaySound(endCinematicSound);
}
CoroutineManager.Invoke(() =>
{
new CameraTransition(boss, GameMain.GameScreen.Cam, null, Alignment.Center, panDuration: 3, fadeOut: false, endZoom: 0.1f * GUI.yScale)
{
RunWhilePaused = false,
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)
{
RunWhilePaused = false,
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 (boss == null || boss.Removed) { return; }
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;
} }
} }
@@ -29,7 +29,7 @@ namespace Barotrauma
} }
} }
for (int i = 0; i < resourceClusters.Count; i++) for (int i = 0; i < resourceAmounts.Count; i++)
{ {
var amount = msg.ReadByte(); var amount = msg.ReadByte();
var rotation = msg.ReadSingle(); var rotation = msg.ReadSingle();
@@ -54,7 +54,7 @@ namespace Barotrauma
CalculateMissionClusterPositions(); CalculateMissionClusterPositions();
for(int i = 0; i < resourceClusters.Count; i++) for(int i = 0; i < resourceAmounts.Count; i++)
{ {
var identifier = msg.ReadIdentifier(); var identifier = msg.ReadIdentifier();
var count = msg.ReadByte(); var count = msg.ReadByte();
@@ -32,44 +32,73 @@ namespace Barotrauma
return ToolBox.GradientLerp(t, GUIStyle.Green, GUIStyle.Orange, GUIStyle.Red); return ToolBox.GradientLerp(t, GUIStyle.Green, GUIStyle.Orange, GUIStyle.Red);
} }
public virtual RichString GetMissionRewardText(Submarine sub) /// <summary>
/// Returns the amount of marks you get from the reward (e.g. "3,000 mk")
/// </summary>
protected LocalizedString GetRewardAmountText(Submarine sub)
{ {
LocalizedString rewardText = TextManager.GetWithVariable("currencyformat", "[credits]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", GetReward(sub))); int baseReward = GetReward(sub);
return RichString.Rich(TextManager.GetWithVariable("missionreward", "[reward]", "‖color:gui.orange‖"+rewardText+"‖end‖")); int finalReward = GetFinalReward(sub);
string rewardAmountText = string.Format(CultureInfo.InvariantCulture, "{0:N0}", baseReward);
if (finalReward > baseReward)
{
rewardAmountText += $" + {string.Format(CultureInfo.InvariantCulture, "{0:N0}", finalReward - baseReward)}";
}
return TextManager.GetWithVariable("currencyformat", "[credits]", rewardAmountText);
} }
public RichString GetReputationRewardText(Location currLocation) /// <summary>
/// Returns the full reward text of the mission (e.g. "Reward: 2,000 mk" or "Reward: 500 mk x 2 (out of max 5) = 1,000 mk")
/// </summary>
public virtual RichString GetMissionRewardText(Submarine sub)
{
LocalizedString rewardText = GetRewardAmountText(sub);
return RichString.Rich(TextManager.GetWithVariable("missionreward", "[reward]", "‖color:gui.orange‖" + rewardText + "‖end‖"));
}
public RichString GetReputationRewardText()
{ {
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 targetFactionPrefab;
if (reputationReward.Key == "location" )
if (reputationReward.Key == "location")
{ {
name = $"‖color:gui.orange‖{currLocation.Name}‖end‖"; targetFactionPrefab = OriginLocation.Faction?.Prefab;
} }
else else
{ {
var faction = FactionPrefab.Prefabs.Find(f => f.Identifier == reputationReward.Key); FactionPrefab.Prefabs.TryGet(reputationReward.Key, out targetFactionPrefab);
if (faction != null)
{
name = $"‖color:{XMLExtensions.ColorToString(faction.IconColor)}‖{faction.Name}‖end‖";
}
else
{
name = TextManager.Get(reputationReward.Key);
}
} }
float normalizedValue = MathUtils.InverseLerp(-100.0f, 100.0f, reputationReward.Value);
string formattedValue = ((int)reputationReward.Value).ToString("+#;-#;0"); //force plus sign for positive numbers if (targetFactionPrefab == null)
{
return string.Empty;
}
float totalReputationChange = reputationReward.Value;
if (GameMain.GameSession?.Campaign?.Factions.Find(f => f.Prefab == targetFactionPrefab) is Faction faction)
{
totalReputationChange = reputationReward.Value * faction.Reputation.GetReputationChangeMultiplier(reputationReward.Value);
}
LocalizedString name = $"‖color:{XMLExtensions.ToStringHex(targetFactionPrefab.IconColor)}‖{targetFactionPrefab.Name}‖end‖";
float normalizedValue = MathUtils.InverseLerp(-100.0f, 100.0f, totalReputationChange);
string formattedValue = ((int)Math.Round(totalReputationChange)).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)
@@ -107,6 +136,11 @@ namespace Barotrauma
}; };
} }
public Identifier GetOverrideMusicType()
{
return Prefab.GetOverrideMusicType(State);
}
public virtual void ClientRead(IReadMessage msg) public virtual void ClientRead(IReadMessage msg)
{ {
State = msg.ReadInt16(); State = msg.ReadInt16();
@@ -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,
@@ -1,11 +1,16 @@
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System; using System;
using System.Xml.Linq; using System.Collections.Generic;
using System.Collections.Immutable;
namespace Barotrauma namespace Barotrauma
{ {
partial class MissionPrefab : PrefabWithUintIdentifier partial class MissionPrefab : PrefabWithUintIdentifier
{ {
private ImmutableArray<Sprite> portraits = new ImmutableArray<Sprite>();
public bool HasPortraits => portraits.Length > 0;
public Sprite Icon public Sprite Icon
{ {
get; get;
@@ -49,24 +54,57 @@ namespace Barotrauma
private Sprite hudIcon; private Sprite hudIcon;
private Color? hudIconColor; private Color? hudIconColor;
private ImmutableDictionary<int, Identifier> overrideMusicOnState;
partial void InitProjSpecific(ContentXElement element) partial void InitProjSpecific(ContentXElement element)
{ {
DisplayTargetHudIcons = element.GetAttributeBool("displaytargethudicons", false); DisplayTargetHudIcons = element.GetAttributeBool("displaytargethudicons", false);
HudIconMaxDistance = element.GetAttributeFloat("hudiconmaxdistance", 1000.0f); HudIconMaxDistance = element.GetAttributeFloat("hudiconmaxdistance", 1000.0f);
Dictionary<int, Identifier> overrideMusic = new Dictionary<int, Identifier>();
List<Sprite> portraits = new List<Sprite>();
foreach (var subElement in element.Elements()) foreach (var subElement in element.Elements())
{ {
string name = subElement.Name.ToString(); switch (subElement.Name.ToString().ToLowerInvariant())
if (name.Equals("icon", StringComparison.OrdinalIgnoreCase))
{ {
Icon = new Sprite(subElement); case "icon":
IconColor = subElement.GetAttributeColor("color", Color.White); Icon = new Sprite(subElement);
} IconColor = subElement.GetAttributeColor("color", Color.White);
else if (name.Equals("hudicon", StringComparison.OrdinalIgnoreCase)) break;
{ case "hudicon":
hudIcon = new Sprite(subElement); hudIcon = new Sprite(subElement);
hudIconColor = subElement.GetAttributeColor("color"); hudIconColor = subElement.GetAttributeColor("color");
break;
case "overridemusic":
overrideMusic.Add(
subElement.GetAttributeInt("state", 0),
subElement.GetAttributeIdentifier("type", Identifier.Empty));
break;
case "portrait":
var portrait = new Sprite(subElement, lazyLoad: true);
if (portrait != null)
{
portraits.Add(portrait);
}
break;
} }
} }
this.portraits = portraits.ToImmutableArray();
overrideMusicOnState = overrideMusic.ToImmutableDictionary();
}
public Identifier GetOverrideMusicType(int state)
{
if (overrideMusicOnState.TryGetValue(state, out Identifier id))
{
return id;
}
return Identifier.Empty;
}
public Sprite GetPortrait(int randomSeed)
{
if (portraits.Length == 0) { return null; }
return portraits[Math.Abs(randomSeed) % portraits.Length];
} }
partial void DisposeProjectSpecific() partial void DisposeProjectSpecific()
@@ -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;
}
} }
} }
} }
@@ -1,9 +1,9 @@
using Microsoft.Xna.Framework; using Barotrauma.Extensions;
using Microsoft.Xna.Framework;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Xml.Linq; using System.Xml.Linq;
using Barotrauma.Extensions;
namespace Barotrauma namespace Barotrauma
{ {
@@ -199,8 +199,8 @@ namespace Barotrauma
if (GameMain.GraphicsWidth <= maxResolution.X && GameMain.GraphicsHeight <= maxResolution.Y) if (GameMain.GraphicsWidth <= maxResolution.X && GameMain.GraphicsHeight <= maxResolution.Y)
{ {
size = new Point( size = new Point(
subElement.GetAttributeInt("width", 0), ParseSize(subElement, "width"),
subElement.GetAttributeInt("height", 0)); ParseSize(subElement, "height"));
break; break;
} }
} }
@@ -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]", PlayerInput.PrimaryMouseLabel),
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)
{ {
@@ -106,6 +106,11 @@ namespace Barotrauma
public static float VerticalAspectRatio => GameMain.GraphicsHeight / (float)GameMain.GraphicsWidth; public static float VerticalAspectRatio => GameMain.GraphicsHeight / (float)GameMain.GraphicsWidth;
public static float RelativeHorizontalAspectRatio => HorizontalAspectRatio / (ReferenceResolution.X / ReferenceResolution.Y); public static float RelativeHorizontalAspectRatio => HorizontalAspectRatio / (ReferenceResolution.X / ReferenceResolution.Y);
public static float RelativeVerticalAspectRatio => VerticalAspectRatio / (ReferenceResolution.Y / ReferenceResolution.X); public static float RelativeVerticalAspectRatio => VerticalAspectRatio / (ReferenceResolution.Y / ReferenceResolution.X);
/// <summary>
/// A horizontal scaling factor for low aspect ratios (small width relative to height)
/// </summary>
public static float AspectRatioAdjustment => HorizontalAspectRatio < 1.4f ? (1.0f - (1.4f - HorizontalAspectRatio)) : 1.0f;
public static bool IsUltrawide => HorizontalAspectRatio > 2.0f; public static bool IsUltrawide => HorizontalAspectRatio > 2.0f;
public static int UIWidth public static int UIWidth
@@ -140,13 +145,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; }
@@ -195,8 +207,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 } };
} }
} }
@@ -533,18 +546,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
{ {
@@ -684,37 +696,24 @@ namespace Barotrauma
} }
} }
public static void DrawBackgroundSprite(SpriteBatch spriteBatch, Sprite backgroundSprite, float aberrationStrength = 1.0f) public static void DrawBackgroundSprite(SpriteBatch spriteBatch, Sprite backgroundSprite, Color color, Rectangle? drawArea = null, SpriteEffects spriteEffects = SpriteEffects.None)
{ {
double aberrationT = (Timing.TotalTime * 0.5f); Rectangle area = drawArea ?? new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight);
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)area.Width / backgroundSprite.SourceRect.Width,
(float)GameMain.GraphicsHeight / backgroundSprite.SourceRect.Height) * 1.1f; (float)area.Height / backgroundSprite.SourceRect.Height) * 1.1f;
float paddingX = backgroundSprite.SourceRect.Width * scale - GameMain.GraphicsWidth; float paddingX = backgroundSprite.SourceRect.Width * scale - area.Width;
float paddingY = backgroundSprite.SourceRect.Height * scale - GameMain.GraphicsHeight; float paddingY = backgroundSprite.SourceRect.Height * scale - area.Height;
double noiseT = (Timing.TotalTime * 0.02f); double noiseT = Timing.TotalTime * 0.02f;
Vector2 pos = new Vector2((float)PerlinNoise.CalculatePerlin(noiseT, noiseT, 0) - 0.5f, (float)PerlinNoise.CalculatePerlin(noiseT, noiseT, 0.5f) - 0.5f); Vector2 pos = new Vector2((float)PerlinNoise.CalculatePerlin(noiseT, noiseT, 0) - 0.5f, (float)PerlinNoise.CalculatePerlin(noiseT, noiseT, 0.5f) - 0.5f);
pos = new Vector2(pos.X * paddingX, pos.Y * paddingY); pos = new Vector2(pos.X * paddingX, pos.Y * paddingY);
spriteBatch.Draw(backgroundSprite.Texture, spriteBatch.Draw(backgroundSprite.Texture,
new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight) / 2 + pos, area.Center.ToVector2() + pos,
null, Color.White, 0.0f, backgroundSprite.size / 2, null, color, 0.0f, backgroundSprite.size / 2,
scale, SpriteEffects.None, 0.0f); scale, spriteEffects, 0.0f);
spriteBatch.End();
} }
#region Update list #region Update list
@@ -1206,48 +1205,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);
@@ -2599,8 +2587,12 @@ namespace Barotrauma
public static void AddMessage(string message, Color color, float? lifeTime = null, bool playSound = true, GUIFont font = null) public static void AddMessage(string message, Color color, float? lifeTime = null, bool playSound = true, GUIFont font = null)
{ {
if (messages.Any(msg => msg.Text == message)) { return; } var guiMessage = new GUIMessage(message, color, lifeTime ?? MathHelper.Clamp(message.Length / 5.0f, 3.0f, 10.0f), font ?? GUIStyle.LargeFont);
messages.Add(new GUIMessage(message, color, lifeTime ?? MathHelper.Clamp(message.Length / 5.0f, 3.0f, 10.0f), font ?? GUIStyle.LargeFont)); lock (mutex)
{
if (messages.Any(msg => msg.Text == message)) { return; }
messages.Add(guiMessage);
}
if (playSound) { SoundPlayer.PlayUISound(GUISoundType.UIMessage); } if (playSound) { SoundPlayer.PlayUISound(GUISoundType.UIMessage); }
} }
@@ -2610,34 +2602,37 @@ namespace Barotrauma
var newMessage = new GUIMessage(message, color, pos, velocity, lifeTime, Alignment.Center, GUIStyle.Font, sub: sub); var newMessage = new GUIMessage(message, color, pos, velocity, lifeTime, Alignment.Center, GUIStyle.Font, sub: sub);
if (playSound) { SoundPlayer.PlayUISound(soundType); } if (playSound) { SoundPlayer.PlayUISound(soundType); }
bool overlapFound = true;
int tries = 0;
while (overlapFound)
{
overlapFound = false;
foreach (var otherMessage in messages)
{
float xDiff = otherMessage.Pos.X - newMessage.Pos.X;
if (Math.Abs(xDiff) > (newMessage.Size.X + otherMessage.Size.X) / 2) { continue; }
float yDiff = otherMessage.Pos.Y - newMessage.Pos.Y;
if (Math.Abs(yDiff) > (newMessage.Size.Y + otherMessage.Size.Y) / 2) { continue; }
Vector2 moveDir = -(new Vector2(xDiff, yDiff) + Rand.Vector(1.0f));
if (moveDir.LengthSquared() > 0.0001f)
{
moveDir = Vector2.Normalize(moveDir);
}
else
{
moveDir = Rand.Vector(1.0f);
}
moveDir.Y = -Math.Abs(moveDir.Y);
newMessage.Pos -= Vector2.UnitY * 10;
}
tries++;
if (tries > 20) { break; }
}
messages.Add(newMessage); lock (mutex)
{
bool overlapFound = true;
int tries = 0;
while (overlapFound)
{
overlapFound = false;
foreach (var otherMessage in messages)
{
float xDiff = otherMessage.Pos.X - newMessage.Pos.X;
if (Math.Abs(xDiff) > (newMessage.Size.X + otherMessage.Size.X) / 2) { continue; }
float yDiff = otherMessage.Pos.Y - newMessage.Pos.Y;
if (Math.Abs(yDiff) > (newMessage.Size.Y + otherMessage.Size.Y) / 2) { continue; }
Vector2 moveDir = -(new Vector2(xDiff, yDiff) + Rand.Vector(1.0f));
if (moveDir.LengthSquared() > 0.0001f)
{
moveDir = Vector2.Normalize(moveDir);
}
else
{
moveDir = Rand.Vector(1.0f);
}
moveDir.Y = -Math.Abs(moveDir.Y);
newMessage.Pos -= Vector2.UnitY * 10;
}
tries++;
if (tries > 20) { break; }
}
messages.Add(newMessage);
}
} }
public static void ClearMessages() public static void ClearMessages()
@@ -8,9 +8,7 @@ namespace Barotrauma
{ {
public class GUICanvas : RectTransform public class GUICanvas : RectTransform
{ {
private static readonly object mutex = new object(); protected GUICanvas() : base(Size, parent: null) { }
protected GUICanvas() : base(size, parent: null) { }
private static GUICanvas _instance; private static GUICanvas _instance;
public static GUICanvas Instance public static GUICanvas Instance
@@ -33,7 +31,7 @@ namespace Barotrauma
//GUICanvas stores the children as weak references, to allow elements that we no longer need to get garbage collected //GUICanvas stores the children as weak references, to allow elements that we no longer need to get garbage collected
private readonly List<WeakReference<RectTransform>> childrenWeakRef = new List<WeakReference<RectTransform>>(); private readonly List<WeakReference<RectTransform>> childrenWeakRef = new List<WeakReference<RectTransform>>();
private static Vector2 size => new Vector2(GameMain.GraphicsWidth / (float)GUI.UIWidth, 1f); private static Vector2 Size => new Vector2(GameMain.GraphicsWidth / (float)GUI.UIWidth, 1f);
protected override Rectangle NonScaledUIRect => UIRect; protected override Rectangle NonScaledUIRect => UIRect;
@@ -41,25 +39,27 @@ namespace Barotrauma
private static void OnChildrenChanged(RectTransform _) private static void OnChildrenChanged(RectTransform _)
{ {
lock (mutex) CrossThread.RequestExecutionOnMainThread(RefreshChildren);
}
private static void RefreshChildren()
{
//add weak reference if we don't have one yet
foreach (var child in _instance.Children)
{ {
//add weak reference if we don't have one yet if (!_instance.childrenWeakRef.Any(c => c.TryGetTarget(out var existingChild) && existingChild == child))
foreach (var child in _instance.Children)
{ {
if (!_instance.childrenWeakRef.Any(c => c.TryGetTarget(out var existingChild) && existingChild == child)) _instance.childrenWeakRef.Add(new WeakReference<RectTransform>(child));
{
_instance.childrenWeakRef.Add(new WeakReference<RectTransform>(child));
}
} }
//get rid of strong references }
_instance.children.Clear(); //get rid of strong references
//remove dead children _instance.children.Clear();
for (int i = _instance.childrenWeakRef.Count - 2; i >= 0; i--) //remove dead children
for (int i = _instance.childrenWeakRef.Count - 1; i >= 0; i--)
{
if (!_instance.childrenWeakRef[i].TryGetTarget(out var child) || child.Parent != _instance)
{ {
if (!_instance.childrenWeakRef[i].TryGetTarget(out var child) || child.Parent != _instance) _instance.childrenWeakRef.RemoveAt(i);
{
_instance.childrenWeakRef.RemoveAt(i);
}
} }
} }
} }
@@ -67,7 +67,7 @@ namespace Barotrauma
// Turn public, if there is a need to call this manually. // Turn public, if there is a need to call this manually.
private static void RecalculateSize() private static void RecalculateSize()
{ {
Vector2 recalculatedSize = size; Vector2 recalculatedSize = Size;
// Scale children that are supposed to encompass the whole screen so that they are properly scaled on ultrawide as well // Scale children that are supposed to encompass the whole screen so that they are properly scaled on ultrawide as well
for (int i = 0; i < Instance.childrenWeakRef.Count; i++) for (int i = 0; i < Instance.childrenWeakRef.Count; i++)
@@ -109,7 +109,7 @@ namespace Barotrauma
} }
} }
Instance.Resize(size, resizeChildren: true); Instance.Resize(Size, resizeChildren: true);
Instance.GetAllChildren().Select(c => c.GUIComponent as GUITextBlock).ForEach(t => t?.SetTextPos()); Instance.GetAllChildren().Select(c => c.GUIComponent as GUITextBlock).ForEach(t => t?.SetTextPos());
_instance.children.Clear(); _instance.children.Clear();
} }
@@ -244,18 +244,16 @@ namespace Barotrauma
return parentHierarchy.Last(); return parentHierarchy.Last();
} }
public void AddItem(LocalizedString text, object userData = null, LocalizedString toolTip = null) public GUIComponent AddItem(LocalizedString text, object userData = null, LocalizedString toolTip = null, Color? color = null, Color? textColor = null)
{ {
toolTip ??= ""; toolTip ??= "";
if (selectMultiple) if (selectMultiple)
{ {
var frame = new GUIFrame(new RectTransform(new Point(button.Rect.Width, button.Rect.Height), listBox.Content.RectTransform) var frame = new GUIFrame(new RectTransform(new Point(button.Rect.Width, button.Rect.Height), listBox.Content.RectTransform) { IsFixedSize = false }, style: "ListBoxElement", color: color)
{ IsFixedSize = false }, style: "ListBoxElement")
{ {
UserData = userData, UserData = userData,
ToolTip = toolTip ToolTip = toolTip
}; };
new GUITickBox(new RectTransform(new Vector2(1.0f, 0.8f), frame.RectTransform, anchor: Anchor.CenterLeft) { MaxSize = new Point(int.MaxValue, (int)(button.Rect.Height * 0.8f)) }, text) new GUITickBox(new RectTransform(new Vector2(1.0f, 0.8f), frame.RectTransform, anchor: Anchor.CenterLeft) { MaxSize = new Point(int.MaxValue, (int)(button.Rect.Height * 0.8f)) }, text)
{ {
UserData = userData, UserData = userData,
@@ -275,7 +273,7 @@ namespace Barotrauma
foreach (GUIComponent child in ListBox.Content.Children) foreach (GUIComponent child in ListBox.Content.Children)
{ {
var tickBox = child.GetChild<GUITickBox>(); var tickBox = child.GetChild<GUITickBox>();
if (tickBox.Selected) if (tickBox is { Selected: true })
{ {
selectedDataMultiple.Add(child.UserData); selectedDataMultiple.Add(child.UserData);
selectedIndexMultiple.Add(i); selectedIndexMultiple.Add(i);
@@ -289,11 +287,11 @@ namespace Barotrauma
return true; return true;
} }
}; };
return frame;
} }
else else
{ {
new GUITextBlock(new RectTransform(new Point(button.Rect.Width, button.Rect.Height), listBox.Content.RectTransform) return new GUITextBlock(new RectTransform(new Point(button.Rect.Width, button.Rect.Height), listBox.Content.RectTransform) { IsFixedSize = false }, text, style: "ListBoxElement", color: color, textColor: textColor)
{ IsFixedSize = false }, text, style: "ListBoxElement")
{ {
UserData = userData, UserData = userData,
ToolTip = toolTip ToolTip = toolTip
@@ -323,7 +321,7 @@ namespace Barotrauma
} }
else else
{ {
if (!(component is GUITextBlock textBlock)) if (component is not GUITextBlock textBlock)
{ {
textBlock = component.GetChild<GUITextBlock>(); textBlock = component.GetChild<GUITextBlock>();
if (textBlock is null && !AllowNonText) { return false; } if (textBlock is null && !AllowNonText) { return false; }
@@ -1059,6 +1059,7 @@ namespace Barotrauma
GUIComponent child = Content.GetChild(childIndex); GUIComponent child = Content.GetChild(childIndex);
if (child is null) { return; } if (child is null) { return; }
if (!child.Enabled) { return; }
bool wasSelected = true; bool wasSelected = true;
if (OnSelected != null) if (OnSelected != null)
@@ -268,7 +268,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);
@@ -313,7 +313,9 @@ namespace Barotrauma
break; break;
} }
RectTransform.MinSize = TextBox.RectTransform.MinSize; RectTransform.MinSize = new Point(
Math.Max(rectT.MinSize.X, TextBox.RectTransform.MinSize.X),
Math.Max(rectT.MinSize.Y, TextBox.RectTransform.MinSize.Y));
LayoutGroup.Recalculate(); LayoutGroup.Recalculate();
} }
@@ -1,14 +1,12 @@
using Microsoft.Xna.Framework; using Barotrauma.Extensions;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis; using System.Globalization;
using System.Linq; using System.Linq;
using System.Text;
using System.Xml.Linq; using System.Xml.Linq;
using Barotrauma.Extensions;
namespace Barotrauma namespace Barotrauma
{ {
@@ -20,6 +18,26 @@ namespace Barotrauma
{ {
return element.NameAsIdentifier(); return element.NameAsIdentifier();
} }
protected int ParseSize(XElement element, string attributeName)
{
string valueStr = element.GetAttributeString(attributeName, string.Empty);
bool relativeToWidth = valueStr.EndsWith("vw");
bool relativeToHeight = valueStr.EndsWith("vh");
if (relativeToWidth || relativeToHeight)
{
string floatStr = valueStr.Substring(0, valueStr.Length - 2);
if (!float.TryParse(floatStr, NumberStyles.Any, CultureInfo.InvariantCulture, out float relativeHeight))
{
DebugConsole.ThrowError($"Error while parsing a {nameof(GUIComponentStyle)}: {valueStr} is not a valid size.");
}
return (int)(relativeHeight / 100.0f * (relativeToWidth ? GameMain.GraphicsWidth : GameMain.GraphicsHeight));
}
else
{
return element.GetAttributeInt(attributeName, 0);
}
}
} }
public abstract class GUISelector<T> where T : GUIPrefab public abstract class GUISelector<T> where T : GUIPrefab
@@ -166,7 +184,8 @@ namespace Barotrauma
Point maxResolution = subElement.GetAttributePoint("maxresolution", new Point(int.MaxValue, int.MaxValue)); Point maxResolution = subElement.GetAttributePoint("maxresolution", new Point(int.MaxValue, int.MaxValue));
if (GameMain.GraphicsWidth <= maxResolution.X && GameMain.GraphicsHeight <= maxResolution.Y) if (GameMain.GraphicsWidth <= maxResolution.X && GameMain.GraphicsHeight <= maxResolution.Y)
{ {
return (uint)Math.Round(subElement.GetAttributeInt("size", 14) * GameSettings.CurrentConfig.Graphics.TextScale); int rawSize = ParseSize(subElement, "size");
return (uint)Math.Round(rawSize * GameSettings.CurrentConfig.Graphics.TextScale);
} }
} }
return (uint)Math.Round(defaultSize * GameSettings.CurrentConfig.Graphics.TextScale); return (uint)Math.Round(defaultSize * GameSettings.CurrentConfig.Graphics.TextScale);
@@ -438,7 +438,7 @@ namespace Barotrauma
} }
else else
{ {
if ((PlayerInput.LeftButtonClicked() || PlayerInput.RightButtonClicked()) && selected) if ((PlayerInput.PrimaryMouseButtonClicked() || PlayerInput.SecondaryMouseButtonClicked()) && selected)
{ {
if (!mouseHeldInside) { Deselect(); } if (!mouseHeldInside) { Deselect(); }
mouseHeldInside = false; mouseHeldInside = false;
@@ -149,8 +149,7 @@ namespace Barotrauma
int messageAreaWidth = GameMain.GraphicsWidth / 3; int messageAreaWidth = GameMain.GraphicsWidth / 3;
MessageAreaTop = new Rectangle((GameMain.GraphicsWidth - messageAreaWidth) / 2, ButtonAreaTop.Bottom + ButtonAreaTop.Height, messageAreaWidth, ButtonAreaTop.Height); MessageAreaTop = new Rectangle((GameMain.GraphicsWidth - messageAreaWidth) / 2, ButtonAreaTop.Bottom + ButtonAreaTop.Height, messageAreaWidth, ButtonAreaTop.Height);
bool isFourByThree = GUI.IsFourByThree(); int chatBoxWidth = (int)(475 * GUI.Scale * GUI.AspectRatioAdjustment);
int chatBoxWidth = !isFourByThree ? (int)(475 * GUI.Scale) : (int)(375 * GUI.Scale);
int chatBoxHeight = (int)Math.Max(GameMain.GraphicsHeight * 0.25f, 150); int chatBoxHeight = (int)Math.Max(GameMain.GraphicsHeight * 0.25f, 150);
ChatBoxArea = new Rectangle(Padding, GameMain.GraphicsHeight - Padding - chatBoxHeight, chatBoxWidth, chatBoxHeight); ChatBoxArea = new Rectangle(Padding, GameMain.GraphicsHeight - Padding - chatBoxHeight, chatBoxWidth, chatBoxHeight);
@@ -160,8 +159,9 @@ namespace Barotrauma
int crewAreaY = ButtonAreaTop.Bottom + Padding; int crewAreaY = ButtonAreaTop.Bottom + Padding;
int crewAreaHeight = ObjectiveAnchor.Top - Padding - crewAreaY; int crewAreaHeight = ObjectiveAnchor.Top - Padding - crewAreaY;
CrewArea = new Rectangle(Padding, crewAreaY, (int)Math.Max(400 * GUI.Scale, 220), crewAreaHeight);
float crewAreaWidthMultiplier = GUI.IsUltrawide ? GUI.HorizontalAspectRatio : 1.0f;
CrewArea = new Rectangle(Padding, crewAreaY, (int)(Math.Max(400 * GUI.Scale, 220) * crewAreaWidthMultiplier), crewAreaHeight);
InventoryAreaLower = new Rectangle(ChatBoxArea.Right + Padding * 7, inventoryTopY, GameMain.GraphicsWidth - Padding * 9 - ChatBoxArea.Width, GameMain.GraphicsHeight - inventoryTopY); InventoryAreaLower = new Rectangle(ChatBoxArea.Right + Padding * 7, inventoryTopY, GameMain.GraphicsWidth - Padding * 9 - ChatBoxArea.Width, GameMain.GraphicsHeight - inventoryTopY);
int healthWindowWidth = (int)(GameMain.GraphicsWidth * 0.5f); int healthWindowWidth = (int)(GameMain.GraphicsWidth * 0.5f);
@@ -187,19 +187,26 @@ namespace Barotrauma
public static void Draw(SpriteBatch spriteBatch) public static void Draw(SpriteBatch spriteBatch)
{ {
DrawRectangle(ButtonAreaTop, Color.White * 0.5f); DrawRectangle(nameof(ButtonAreaTop), ButtonAreaTop, Color.White * 0.5f);
DrawRectangle(TutorialObjectiveListArea, GUIStyle.Blue * 0.5f); DrawRectangle(nameof(TutorialObjectiveListArea), TutorialObjectiveListArea, GUIStyle.Blue * 0.5f);
DrawRectangle(MessageAreaTop, GUIStyle.Orange * 0.5f); DrawRectangle(nameof(MessageAreaTop), MessageAreaTop, GUIStyle.Orange * 0.5f);
DrawRectangle(CrewArea, Color.Blue * 0.5f); DrawRectangle(nameof(CrewArea), CrewArea, Color.Blue * 0.5f);
DrawRectangle(ChatBoxArea, Color.Cyan * 0.5f); DrawRectangle(nameof(ChatBoxArea), ChatBoxArea, Color.Cyan * 0.5f);
DrawRectangle(HealthBarArea, Color.Red * 0.5f); DrawRectangle(nameof(HealthBarArea), HealthBarArea, Color.Red * 0.5f);
DrawRectangle(HealthBarAfflictionArea, Color.Red * 0.5f); DrawRectangle(nameof(HealthBarAfflictionArea), HealthBarAfflictionArea, Color.Red * 0.5f);
DrawRectangle(InventoryAreaLower, Color.Yellow * 0.5f); DrawRectangle(nameof(InventoryAreaLower), InventoryAreaLower, Color.Yellow * 0.5f);
DrawRectangle(HealthWindowAreaLeft, Color.Red * 0.5f); DrawRectangle(nameof(HealthWindowAreaLeft), HealthWindowAreaLeft, Color.Red * 0.5f);
DrawRectangle(BottomRightInfoArea, Color.Green * 0.5f); DrawRectangle(nameof(BottomRightInfoArea), BottomRightInfoArea, Color.Green * 0.5f);
DrawRectangle(ItemHUDArea, Color.Magenta * 0.3f); DrawRectangle(nameof(ItemHUDArea), ItemHUDArea, Color.Magenta * 0.3f);
void DrawRectangle(Rectangle r, Color c) => GUI.DrawRectangle(spriteBatch, r, c); void DrawRectangle(string label, Rectangle r, Color c)
{
if (!label.IsNullOrEmpty())
{
GUI.DrawString(spriteBatch, r.Location.ToVector2() + Vector2.One * 3, label, c, font: GUIStyle.SmallFont);
}
GUI.DrawRectangle(spriteBatch, r, c);
}
} }
} }
@@ -11,9 +11,9 @@ namespace Barotrauma
{ {
class LoadingScreen class LoadingScreen
{ {
private readonly Texture2D defaultBackgroundTexture, overlay; private readonly Sprite defaultBackgroundTexture, overlay;
private readonly SpriteSheet decorativeGraph, decorativeMap; private readonly SpriteSheet decorativeGraph, decorativeMap;
private Texture2D currentBackgroundTexture; private Sprite currentBackgroundTexture;
private readonly Sprite noiseSprite; private readonly Sprite noiseSprite;
private string randText = ""; private string randText = "";
@@ -24,6 +24,8 @@ namespace Barotrauma
private Video currSplashScreen; private Video currSplashScreen;
private DateTime videoStartTime; private DateTime videoStartTime;
private bool mirrorBackground;
public struct PendingSplashScreen public struct PendingSplashScreen
{ {
public string Filename; public string Filename;
@@ -112,12 +114,12 @@ namespace Barotrauma
public LoadingScreen(GraphicsDevice graphics) public LoadingScreen(GraphicsDevice graphics)
{ {
defaultBackgroundTexture = TextureLoader.FromFile("Content/Map/LocationPortraits/AlienRuins.png"); defaultBackgroundTexture = new Sprite("Content/Map/LocationPortraits/MainMenu1.png", Vector2.Zero);
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 = new Sprite("Content/UI/MainMenuVignette.png", Vector2.Zero);
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"));
@@ -139,34 +141,23 @@ namespace Barotrauma
} }
} }
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;
currentBackgroundTexture ??= defaultBackgroundTexture; currentBackgroundTexture ??= defaultBackgroundTexture;
float overlayScale = Math.Min(GameMain.GraphicsWidth / overlay.size.X, GameMain.GraphicsHeight / overlay.size.Y);
Rectangle drawArea = new Rectangle(
(int)(overlay.size.X * overlayScale / 2), 0,
(int)(GameMain.GraphicsWidth - overlay.size.X * overlayScale / 2), GameMain.GraphicsHeight);
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, samplerState: GUI.SamplerState); spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, samplerState: GUI.SamplerState);
float scale = (GameMain.GraphicsWidth / (float)currentBackgroundTexture.Width) * 1.2f; GUI.DrawBackgroundSprite(spriteBatch, currentBackgroundTexture, Color.White, drawArea,
float paddingX = currentBackgroundTexture.Width * scale - GameMain.GraphicsWidth; spriteEffects: mirrorBackground ? SpriteEffects.FlipHorizontally : SpriteEffects.None);
float paddingY = currentBackgroundTexture.Height * scale - GameMain.GraphicsHeight; overlay.Draw(spriteBatch, Vector2.Zero, scale: overlayScale);
double noiseT = (Timing.TotalTime * 0.02f);
Vector2 pos = new Vector2((float)PerlinNoise.CalculatePerlin(noiseT, noiseT, 0) - 0.5f, (float)PerlinNoise.CalculatePerlin(noiseT, noiseT, 0.5f) - 0.5f);
pos = new Vector2(pos.X * paddingX, pos.Y * paddingY);
spriteBatch.Draw(currentBackgroundTexture,
new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight) / 2 + pos,
null, Color.White, 0.0f, new Vector2(currentBackgroundTexture.Width / 2, currentBackgroundTexture.Height / 2),
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);
double noiseT = Timing.TotalTime * 0.02f;
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;
noiseSprite.DrawTiled(spriteBatch, Vector2.Zero, new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight), noiseSprite.DrawTiled(spriteBatch, Vector2.Zero, new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight),
@@ -174,10 +165,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 +203,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 +224,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 +235,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 +249,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 +280,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 +308,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 +317,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 +427,12 @@ 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));
if (GameMain.GameSession?.GameMode?.Missions is { } missions && missions.Any(m => m.Prefab.HasPortraits))
{
currentBackgroundTexture = missions.Where(m => m.Prefab.HasPortraits).First().Prefab.GetPortrait(Rand.Int(int.MaxValue));
}
mirrorBackground = Rand.Range(0.0f, 1.0f) < 0.5f;
while (!drawn) while (!drawn)
{ {
@@ -143,33 +143,32 @@ namespace Barotrauma
{ {
public readonly MedicalClinic.NetAffliction Target; public readonly MedicalClinic.NetAffliction Target;
public readonly ImmutableArray<GUIComponent> ElementsToDisable; public readonly ImmutableArray<GUIComponent> ElementsToDisable;
public readonly GUIComponent TargetElement;
public PopupAffliction(ImmutableArray<GUIComponent> elementsToDisable, MedicalClinic.NetAffliction target) public PopupAffliction(ImmutableArray<GUIComponent> elementsToDisable, GUIComponent component, MedicalClinic.NetAffliction target)
{ {
Target = target; Target = target;
ElementsToDisable = elementsToDisable; ElementsToDisable = elementsToDisable;
TargetElement = component;
} }
} }
private readonly struct PopupAfflictionList private readonly struct PopupAfflictionList
{ {
public readonly MedicalClinic.NetCrewMember Target; public readonly MedicalClinic.NetCrewMember Target;
public readonly GUIListBox ListElement;
public readonly GUIButton TreatAllButton; public readonly GUIButton TreatAllButton;
public readonly List<PopupAffliction> Afflictions; public readonly HashSet<PopupAffliction> Afflictions;
public PopupAfflictionList(MedicalClinic.NetCrewMember crewMember, GUIButton treatAllButton) public PopupAfflictionList(MedicalClinic.NetCrewMember crewMember, GUIListBox listElement, GUIButton treatAllButton)
{ {
ListElement = listElement;
Target = crewMember; Target = crewMember;
TreatAllButton = treatAllButton; TreatAllButton = treatAllButton;
Afflictions = new List<PopupAffliction>(); Afflictions = new HashSet<PopupAffliction>();
} }
} }
// private enum SortMode
// {
// Severity
// }
private readonly MedicalClinic medicalClinic; private readonly MedicalClinic medicalClinic;
private readonly GUIComponent container; private readonly GUIComponent container;
private Point prevResolution; private Point prevResolution;
@@ -221,23 +220,22 @@ namespace Barotrauma
private void UpdatePopupAfflictions() private void UpdatePopupAfflictions()
{ {
if (selectedCrewAfflictionList is { } afflictionList) if (selectedCrewAfflictionList is not { } afflictionList) { return; }
{
foreach (PopupAffliction popupAffliction in afflictionList.Afflictions)
{
ToggleElements(ElementState.Enabled, popupAffliction.ElementsToDisable);
if (medicalClinic.IsAfflictionPending(afflictionList.Target, popupAffliction.Target))
{
ToggleElements(ElementState.Disabled, popupAffliction.ElementsToDisable);
}
}
afflictionList.TreatAllButton.Enabled = true; foreach (PopupAffliction popupAffliction in afflictionList.Afflictions)
if (afflictionList.Afflictions.All(a => medicalClinic.IsAfflictionPending(afflictionList.Target, a.Target))) {
ToggleElements(ElementState.Enabled, popupAffliction.ElementsToDisable);
if (medicalClinic.IsAfflictionPending(afflictionList.Target, popupAffliction.Target))
{ {
afflictionList.TreatAllButton.Enabled = false; ToggleElements(ElementState.Disabled, popupAffliction.ElementsToDisable);
} }
} }
afflictionList.TreatAllButton.Enabled = true;
if (afflictionList.Afflictions.All(a => medicalClinic.IsAfflictionPending(afflictionList.Target, a.Target)))
{
afflictionList.TreatAllButton.Enabled = false;
}
} }
private void UpdatePending() private void UpdatePending()
@@ -309,7 +307,7 @@ namespace Barotrauma
} }
} }
private void UpdateCrewPanel() public void UpdateCrewPanel()
{ {
if (crewHealList is not { } healList) { return; } if (crewHealList is not { } healList) { return; }
@@ -526,7 +524,7 @@ namespace Barotrauma
new GUITextBlock(new RectTransform(Vector2.One, healthLayout.RectTransform), string.Empty, textAlignment: Alignment.Center, font: GUIStyle.SubHeadingFont) new GUITextBlock(new RectTransform(Vector2.One, healthLayout.RectTransform), string.Empty, textAlignment: Alignment.Center, font: GUIStyle.SubHeadingFont)
{ {
TextGetter = () => TextManager.GetWithVariable("percentageformat", "[value]", $"{(int)(info.Character?.HealthPercentage ?? 100f)}"), TextGetter = () => TextManager.GetWithVariable("percentageformat", "[value]", $"{(int)MathF.Round(info.Character?.HealthPercentage ?? 100f)}"),
TextColor = GUIStyle.Green TextColor = GUIStyle.Green
}; };
@@ -789,7 +787,7 @@ namespace Barotrauma
GUIListBox afflictionList = new GUIListBox(new RectTransform(new Vector2(1f, 0.8f), mainLayout.RectTransform)) { Visible = false }; GUIListBox afflictionList = new GUIListBox(new RectTransform(new Vector2(1f, 0.8f), mainLayout.RectTransform)) { Visible = false };
PopupAfflictionList popupAfflictionList = new PopupAfflictionList(crewMember, treatAllButton); PopupAfflictionList popupAfflictionList = new PopupAfflictionList(crewMember, afflictionList, treatAllButton);
selectedCrewElement = mainFrame; selectedCrewElement = mainFrame;
selectedCrewAfflictionList = popupAfflictionList; selectedCrewAfflictionList = popupAfflictionList;
@@ -810,9 +808,9 @@ namespace Barotrauma
List<GUIComponent> allComponents = new List<GUIComponent>(); List<GUIComponent> allComponents = new List<GUIComponent>();
foreach (MedicalClinic.NetAffliction affliction in request.Afflictions) foreach (MedicalClinic.NetAffliction affliction in request.Afflictions)
{ {
ImmutableArray<GUIComponent> createdComponents = CreatePopupAffliction(afflictionList.Content, crewMember, affliction); CreatedPopupAfflictionElement createdComponents = CreatePopupAffliction(afflictionList.Content, crewMember, affliction);
allComponents.AddRange(createdComponents); allComponents.AddRange(createdComponents.AllCreatedElements);
popupAfflictionList.Afflictions.Add(new PopupAffliction(createdComponents, affliction)); popupAfflictionList.Afflictions.Add(new PopupAffliction(createdComponents.AllCreatedElements, createdComponents.MainElement, affliction));
} }
allComponents.Add(treatAllButton); allComponents.Add(treatAllButton);
@@ -832,9 +830,11 @@ namespace Barotrauma
} }
} }
private ImmutableArray<GUIComponent> CreatePopupAffliction(GUIComponent parent, MedicalClinic.NetCrewMember crewMember, MedicalClinic.NetAffliction affliction) private readonly record struct CreatedPopupAfflictionElement(GUIComponent MainElement, ImmutableArray<GUIComponent> AllCreatedElements);
private CreatedPopupAfflictionElement CreatePopupAffliction(GUIComponent parent, MedicalClinic.NetCrewMember crewMember, MedicalClinic.NetAffliction affliction)
{ {
if (!(affliction.Prefab is { } prefab)) { return ImmutableArray<GUIComponent>.Empty; } ToolBox.ThrowIfNull(affliction.Prefab);
GUIFrame backgroundFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.33f), parent.RectTransform), style: "ListBoxElement"); GUIFrame backgroundFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.33f), parent.RectTransform), style: "ListBoxElement");
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.01f), backgroundFrame.RectTransform, Anchor.BottomCenter), style: "HorizontalLine"); new GUIFrame(new RectTransform(new Vector2(1.0f, 0.01f), backgroundFrame.RectTransform, Anchor.BottomCenter), style: "HorizontalLine");
@@ -846,9 +846,9 @@ namespace Barotrauma
GUILayoutGroup topLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.33f), mainLayout.RectTransform), isHorizontal: true) { Stretch = true }; GUILayoutGroup topLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.33f), mainLayout.RectTransform), isHorizontal: true) { Stretch = true };
Color iconColor = CharacterHealth.GetAfflictionIconColor(prefab, affliction.Strength); Color iconColor = CharacterHealth.GetAfflictionIconColor(affliction.Prefab, affliction.Strength);
GUIImage icon = new GUIImage(new RectTransform(Vector2.One, topLayout.RectTransform, scaleBasis: ScaleBasis.BothHeight), prefab.Icon, scaleToFit: true) GUIImage icon = new GUIImage(new RectTransform(Vector2.One, topLayout.RectTransform, scaleBasis: ScaleBasis.BothHeight), affliction.Prefab.Icon, scaleToFit: true)
{ {
Color = iconColor, Color = iconColor,
DisabledColor = iconColor * 0.5f DisabledColor = iconColor * 0.5f
@@ -856,7 +856,7 @@ namespace Barotrauma
GUILayoutGroup topTextLayout = new GUILayoutGroup(new RectTransform(Vector2.One, topLayout.RectTransform), isHorizontal: true); GUILayoutGroup topTextLayout = new GUILayoutGroup(new RectTransform(Vector2.One, topLayout.RectTransform), isHorizontal: true);
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), affliction.Prefab.Name, font: GUIStyle.SubHeadingFont);
Color textColor = Color.Lerp(GUIStyle.Orange, GUIStyle.Red, affliction.Strength / affliction.Prefab.MaxStrength); Color textColor = Color.Lerp(GUIStyle.Orange, GUIStyle.Red, affliction.Strength / affliction.Prefab.MaxStrength);
@@ -878,7 +878,7 @@ namespace Barotrauma
AutoScaleHorizontal = true AutoScaleHorizontal = true
}; };
EnsureTextDoesntOverflow(prefab.Name.Value, prefabBlock, prefabBlock.Rect, ImmutableArray.Create(mainLayout, topLayout, topTextLayout)); EnsureTextDoesntOverflow(affliction.Prefab.Name.Value, prefabBlock, prefabBlock.Rect, ImmutableArray.Create(mainLayout, topLayout, topTextLayout));
GUILayoutGroup bottomLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.66f), mainLayout.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft); GUILayoutGroup bottomLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.66f), mainLayout.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft);
@@ -923,7 +923,7 @@ namespace Barotrauma
return true; return true;
}; };
return elementsToDisable; return new CreatedPopupAfflictionElement(backgroundFrame, elementsToDisable);
} }
private void AddPending(ImmutableArray<GUIComponent> elementsToDisable, MedicalClinic.NetCrewMember crewMember, ImmutableArray<MedicalClinic.NetAffliction> afflictions) private void AddPending(ImmutableArray<GUIComponent> elementsToDisable, MedicalClinic.NetCrewMember crewMember, ImmutableArray<MedicalClinic.NetAffliction> afflictions)
@@ -1033,11 +1033,53 @@ namespace Barotrauma
} }
} }
public void UpdateAfflictions(MedicalClinic.NetCrewMember crewMember)
{
if (selectedCrewAfflictionList is not { } afflictionList || !afflictionList.Target.CharacterEquals(crewMember)) { return; }
List<GUIComponent> allComponents = new List<GUIComponent>();
foreach (PopupAffliction existingAffliction in afflictionList.Afflictions.ToHashSet())
{
if (crewMember.Afflictions.None(received => received.AfflictionEquals(existingAffliction.Target)))
{
// remove from UI
existingAffliction.TargetElement.RectTransform.Parent = null;
afflictionList.Afflictions.Remove(existingAffliction);
}
else
{
allComponents.AddRange(existingAffliction.ElementsToDisable);
}
}
foreach (MedicalClinic.NetAffliction received in crewMember.Afflictions)
{
// we're not that concerned about updating the strength of the afflictions
if (afflictionList.Afflictions.Any(existing => existing.Target.AfflictionEquals(received))) { continue; }
CreatedPopupAfflictionElement createdComponents = CreatePopupAffliction(afflictionList.ListElement.Content, crewMember, received);
allComponents.AddRange(createdComponents.AllCreatedElements);
afflictionList.Afflictions.Add(new PopupAffliction(createdComponents.AllCreatedElements, createdComponents.MainElement, received));
}
allComponents.Add(afflictionList.TreatAllButton);
afflictionList.TreatAllButton.OnClicked = (_, _) =>
{
var afflictions = crewMember.Afflictions.Where(a => !medicalClinic.IsAfflictionPending(crewMember, a)).ToImmutableArray();
if (!afflictions.Any()) { return true; }
AddPending(allComponents.ToImmutableArray(), crewMember, afflictions);
return true;
};
UpdatePopupAfflictions();
}
public void ClosePopup() public void ClosePopup()
{ {
if (selectedCrewElement is { } popup) if (selectedCrewElement is { } popup)
{ {
popup.Parent?.RemoveChild(selectedCrewElement); popup.RectTransform.Parent = null;
} }
selectedCrewElement = null; selectedCrewElement = null;
@@ -1096,5 +1138,14 @@ namespace Barotrauma
refreshTimer = 0; refreshTimer = 0;
} }
} }
public void OnDeselected()
{
if (GameMain.NetworkMember is not null)
{
MedicalClinic.SendUnsubscribeRequest();
}
ClosePopup();
}
} }
} }
@@ -207,11 +207,12 @@ namespace Barotrauma
cargoManager.OnItemsInSellFromSubCrateChanged.RegisterOverwriteExisting(refreshStoreId, _ => needsSellingFromSubRefresh = true); cargoManager.OnItemsInSellFromSubCrateChanged.RegisterOverwriteExisting(refreshStoreId, _ => needsSellingFromSubRefresh = true);
} }
public void SelectStore(Identifier identifier) public void SelectStore(Character merchant)
{ {
Identifier storeIdentifier = merchant?.MerchantIdentifier ?? Identifier.Empty;
if (CurrentLocation?.Stores != null) if (CurrentLocation?.Stores != null)
{ {
if (!identifier.IsEmpty && CurrentLocation.GetStore(identifier) is { } store) if (!storeIdentifier.IsEmpty && CurrentLocation.GetStore(storeIdentifier) is { } store)
{ {
ActiveStore = store; ActiveStore = store;
if (storeNameBlock != null) if (storeNameBlock != null)
@@ -223,12 +224,13 @@ namespace Barotrauma
} }
storeNameBlock.SetRichText(storeName); storeNameBlock.SetRichText(storeName);
} }
ActiveStore.SetMerchantFaction(merchant.Faction);
} }
else else
{ {
ActiveStore = null; ActiveStore = null;
string errorId, msg; string errorId, msg;
if (identifier.IsEmpty) if (storeIdentifier.IsEmpty)
{ {
errorId = "Store.SelectStore:IdentifierEmpty"; errorId = "Store.SelectStore:IdentifierEmpty";
msg = $"Error selecting store at {CurrentLocation}: identifier is empty."; msg = $"Error selecting store at {CurrentLocation}: identifier is empty.";
@@ -236,7 +238,7 @@ namespace Barotrauma
else else
{ {
errorId = "Store.SelectStore:StoreDoesntExist"; errorId = "Store.SelectStore:StoreDoesntExist";
msg = $"Error selecting store with identifier \"{identifier}\" at {CurrentLocation}: store with the identifier doesn't exist at the location."; msg = $"Error selecting store with identifier \"{storeIdentifier}\" at {CurrentLocation}: store with the identifier doesn't exist at the location.";
} }
DebugConsole.LogError(msg); DebugConsole.LogError(msg);
GameAnalyticsManager.AddErrorEventOnce(errorId, GameAnalyticsManager.ErrorSeverity.Error, msg); GameAnalyticsManager.AddErrorEventOnce(errorId, GameAnalyticsManager.ErrorSeverity.Error, msg);
@@ -249,17 +251,17 @@ namespace Barotrauma
if (campaignUI.Campaign.Map == null) if (campaignUI.Campaign.Map == null)
{ {
errorId = "Store.SelectStore:MapNull"; errorId = "Store.SelectStore:MapNull";
msg = $"Error selecting store with identifier \"{identifier}\": Map is null."; msg = $"Error selecting store with identifier \"{storeIdentifier}\": Map is null.";
} }
else if (CurrentLocation == null) else if (CurrentLocation == null)
{ {
errorId = "Store.SelectStore:CurrentLocationNull"; errorId = "Store.SelectStore:CurrentLocationNull";
msg = $"Error selecting store with identifier \"{identifier}\": CurrentLocation is null."; msg = $"Error selecting store with identifier \"{storeIdentifier}\": CurrentLocation is null.";
} }
else if (CurrentLocation.Stores == null) else if (CurrentLocation.Stores == null)
{ {
errorId = "Store.SelectStore:StoresNull"; errorId = "Store.SelectStore:StoresNull";
msg = $"Error selecting store with identifier \"{identifier}\": CurrentLocation.Stores is null."; msg = $"Error selecting store with identifier \"{storeIdentifier}\": CurrentLocation.Stores is null.";
} }
if (!msg.IsNullOrEmpty()) if (!msg.IsNullOrEmpty())
{ {
@@ -406,11 +408,11 @@ namespace Barotrauma
TextScale = 1.1f, TextScale = 1.1f,
TextGetter = () => TextGetter = () =>
{ {
if (CurrentLocation != null) if (ActiveStore is not null)
{ {
Color textColor = GUIStyle.ColorReputationNeutral; Color textColor = GUIStyle.ColorReputationNeutral;
string sign = ""; string sign = "";
int reputationModifier = (int)MathF.Round((CurrentLocation.GetStoreReputationModifier(activeTab == StoreTab.Buy) - 1) * 100); int reputationModifier = (int)MathF.Round((ActiveStore.GetReputationModifier(activeTab == StoreTab.Buy) - 1) * 100);
if (reputationModifier > 0) if (reputationModifier > 0)
{ {
textColor = IsBuying ? GUIStyle.ColorReputationLow : GUIStyle.ColorReputationHigh; textColor = IsBuying ? GUIStyle.ColorReputationLow : GUIStyle.ColorReputationHigh;
@@ -727,7 +729,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(); });
} }
} }
@@ -855,6 +857,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()
@@ -898,6 +922,7 @@ namespace Barotrauma
{ {
if (itemPrefab.CanBeBoughtFrom(ActiveStore, out PriceInfo priceInfo) && itemPrefab.CanCharacterBuy()) 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) :
@@ -922,7 +947,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);
} }
} }
@@ -1317,6 +1343,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);
@@ -1345,6 +1373,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; }
@@ -1369,6 +1399,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; }
@@ -1391,10 +1423,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
@@ -1424,6 +1458,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))
@@ -1753,7 +1800,7 @@ namespace Barotrauma
{ {
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)
{ {
@@ -1858,7 +1905,7 @@ namespace Barotrauma
LocalizedString toolTip = string.Empty; LocalizedString toolTip = string.Empty;
if (purchasedItem.ItemPrefab != null) if (purchasedItem.ItemPrefab != null)
{ {
toolTip = purchasedItem.ItemPrefab.GetTooltip(); toolTip = purchasedItem.ItemPrefab.GetTooltip(Character.Controlled);
if (itemQuantity != null) if (itemQuantity != null)
{ {
if (itemQuantity.AllNonEmpty) if (itemQuantity.AllNonEmpty)
@@ -1871,6 +1918,23 @@ 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 = RichString.Rich(toolTip); itemComponent.ToolTip = RichString.Rich(toolTip);
} }
@@ -15,8 +15,6 @@ namespace Barotrauma
private int pageCount; private int pageCount;
private readonly bool transferService, purchaseService; private readonly bool transferService, purchaseService;
private bool initialized; private bool initialized;
private int deliveryFee;
private string deliveryLocationName;
public GUIFrame GuiFrame; public GUIFrame GuiFrame;
private GUIFrame pageIndicatorHolder; private GUIFrame pageIndicatorHolder;
@@ -34,14 +32,13 @@ namespace Barotrauma
private readonly List<SubmarineInfo> subsToShow; private readonly List<SubmarineInfo> subsToShow;
private readonly SubmarineDisplayContent[] submarineDisplays = new SubmarineDisplayContent[submarinesPerPage]; private readonly SubmarineDisplayContent[] submarineDisplays = new SubmarineDisplayContent[submarinesPerPage];
private SubmarineInfo selectedSubmarine = null; private SubmarineInfo selectedSubmarine = null;
private LocalizedString purchaseAndSwitchText, purchaseOnlyText, deliveryText, selectedSubText, switchText, missingPreviewText, currencyName; private LocalizedString purchaseAndSwitchText, purchaseOnlyText, selectedSubText, switchText, missingPreviewText, currencyName;
private readonly RectTransform parent; private readonly RectTransform parent;
private readonly Action closeAction; private readonly Action closeAction;
private Sprite pageIndicator; private Sprite pageIndicator;
private readonly LocalizedString[] messageBoxOptions; private readonly LocalizedString[] messageBoxOptions;
public const int DeliveryFeePerDistanceTravelled = 1000;
public static bool ContentRefreshRequired = false; public static bool ContentRefreshRequired = false;
private static readonly Color indicatorColor = new Color(112, 149, 129); private static readonly Color indicatorColor = new Color(112, 149, 129);
@@ -108,14 +105,9 @@ namespace Barotrauma
{ {
initialized = true; initialized = true;
selectedSubText = TextManager.Get("selectedsub"); selectedSubText = TextManager.Get("selectedsub");
deliveryText = TextManager.Get("requestdeliverybutton");
switchText = TextManager.Get("switchtosubmarinebutton"); switchText = TextManager.Get("switchtosubmarinebutton");
purchaseAndSwitchText = TextManager.Get("purchaseandswitch"); purchaseAndSwitchText = TextManager.Get("purchaseandswitch");
purchaseOnlyText = TextManager.Get("purchase"); purchaseOnlyText = TextManager.Get("purchase");
if (transferService)
{
deliveryFee = CalculateDeliveryFee();
}
currencyName = TextManager.Get("credit").Value.ToLowerInvariant(); currencyName = TextManager.Get("credit").Value.ToLowerInvariant();
@@ -124,13 +116,6 @@ namespace Barotrauma
CreateGUI(); CreateGUI();
} }
private int CalculateDeliveryFee()
{
int distanceToOutpost = GameMain.GameSession.Map.DistanceToClosestLocationWithOutpost(GameMain.GameSession.Map.CurrentLocation, out Location endLocation);
deliveryLocationName = endLocation.Name;
return DeliveryFeePerDistanceTravelled * distanceToOutpost;
}
private void CreateGUI() private void CreateGUI()
{ {
createdForResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight); createdForResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
@@ -194,7 +179,7 @@ namespace Barotrauma
confirmButtonAlt = new GUIButton(new RectTransform(new Vector2(0.2f, 1f), bottomContainer.RectTransform), purchaseOnlyText, style: "GUIButtonFreeScale"); confirmButtonAlt = new GUIButton(new RectTransform(new Vector2(0.2f, 1f), bottomContainer.RectTransform), purchaseOnlyText, style: "GUIButtonFreeScale");
transferInfoFrameWidth -= confirmButtonAlt.RectTransform.RelativeSize.X; transferInfoFrameWidth -= confirmButtonAlt.RectTransform.RelativeSize.X;
} }
confirmButton = new GUIButton(new RectTransform(new Vector2(0.2f, 1f), bottomContainer.RectTransform), purchaseService ? purchaseAndSwitchText : deliveryFee > 0 ? deliveryText : switchText, style: "GUIButtonFreeScale"); confirmButton = new GUIButton(new RectTransform(new Vector2(0.2f, 1f), bottomContainer.RectTransform), purchaseService ? purchaseAndSwitchText : switchText, style: "GUIButtonFreeScale");
SetConfirmButtonState(false); SetConfirmButtonState(false);
transferInfoFrameWidth -= confirmButton.RectTransform.RelativeSize.X; transferInfoFrameWidth -= confirmButton.RectTransform.RelativeSize.X;
GUIFrame transferInfoFrame = new GUIFrame(new RectTransform(new Vector2(transferInfoFrameWidth, 1.0f), bottomContainer.RectTransform), style: null) GUIFrame transferInfoFrame = new GUIFrame(new RectTransform(new Vector2(transferInfoFrameWidth, 1.0f), bottomContainer.RectTransform), style: null)
@@ -406,22 +391,14 @@ 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
{ {
if (subToDisplay.Name != CurrentOrPendingSubmarine().Name) if (subToDisplay.Name != CurrentOrPendingSubmarine().Name)
{ {
if (deliveryFee > 0) submarineDisplays[i].submarineFee.Text = string.Empty;
{
LocalizedString amountString = TextManager.FormatCurrency(deliveryFee);
submarineDisplays[i].submarineFee.Text = TextManager.GetWithVariable("deliveryfee", "[amount]", amountString);
}
else
{
submarineDisplays[i].submarineFee.Text = string.Empty;
}
} }
else else
{ {
@@ -581,7 +558,7 @@ namespace Barotrauma
if (owned) if (owned)
{ {
confirmButton.Text = deliveryFee > 0 ? deliveryText : switchText; confirmButton.Text = switchText;
confirmButton.OnClicked = (button, userData) => confirmButton.OnClicked = (button, userData) =>
{ {
ShowTransferPrompt(); ShowTransferPrompt();
@@ -702,37 +679,12 @@ namespace Barotrauma
private void ShowTransferPrompt() private void ShowTransferPrompt()
{ {
if (!GameMain.GameSession.Campaign.CanAfford(deliveryFee) && deliveryFee > 0) var text = TextManager.GetWithVariables("switchsubmarinetext",
{ ("[submarinename1]", CurrentOrPendingSubmarine().DisplayName),
new GUIMessageBox(TextManager.Get("deliveryrequestheader"), TextManager.GetWithVariables("notenoughmoneyfordeliverytext", ("[submarinename2]", selectedSubmarine.DisplayName));
("[currencyname]", currencyName), text += GetItemTransferText();
("[submarinename]", selectedSubmarine.DisplayName), GUIMessageBox msgBox = new GUIMessageBox(TextManager.Get("switchsubmarineheader"), text, messageBoxOptions);
("[location1]", deliveryLocationName),
("[location2]", GameMain.GameSession.Map.CurrentLocation.Name)));
return;
}
GUIMessageBox msgBox;
if (deliveryFee > 0)
{
msgBox = new GUIMessageBox(TextManager.Get("deliveryrequestheader"), TextManager.GetWithVariables("deliveryrequesttext",
("[submarinename1]", selectedSubmarine.DisplayName),
("[location1]", deliveryLocationName),
("[location2]", GameMain.GameSession.Map.CurrentLocation.Name),
("[submarinename2]", CurrentOrPendingSubmarine().DisplayName),
("[amount]", deliveryFee.ToString()),
("[currencyname]", currencyName)), messageBoxOptions);
msgBox.Buttons[0].ClickSound = GUISoundType.ConfirmTransaction;
}
else
{
var text = TextManager.GetWithVariables("switchsubmarinetext",
("[submarinename1]", CurrentOrPendingSubmarine().DisplayName),
("[submarinename2]", selectedSubmarine.DisplayName));
text += GetItemTransferText();
msgBox = new GUIMessageBox(TextManager.Get("switchsubmarineheader"), text, messageBoxOptions);
}
msgBox.Buttons[0].OnClicked = (applyButton, obj) => msgBox.Buttons[0].OnClicked = (applyButton, obj) =>
{ {
@@ -777,7 +729,7 @@ namespace Barotrauma
{ {
if (GameMain.Client == null) if (GameMain.Client == null)
{ {
GameMain.GameSession.SwitchSubmarine(selectedSubmarine, TransferItemsOnSwitch, deliveryFee); GameMain.GameSession.SwitchSubmarine(selectedSubmarine, TransferItemsOnSwitch);
RefreshSubmarineDisplay(true); RefreshSubmarineDisplay(true);
} }
else else
@@ -797,7 +749,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),
@@ -810,7 +764,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();
@@ -854,7 +808,7 @@ namespace Barotrauma
if (GameMain.Client == null) if (GameMain.Client == null)
{ {
GameMain.GameSession.PurchaseSubmarine(selectedSubmarine); GameMain.GameSession.PurchaseSubmarine(selectedSubmarine);
GameMain.GameSession.SwitchSubmarine(selectedSubmarine, TransferItemsOnSwitch, 0); GameMain.GameSession.SwitchSubmarine(selectedSubmarine, TransferItemsOnSwitch);
RefreshSubmarineDisplay(true); RefreshSubmarineDisplay(true);
} }
else else
@@ -868,7 +822,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) =>
@@ -836,7 +836,7 @@ namespace Barotrauma
Identifier eventIdentifier = new Identifier($"{nameof(CreateWalletCrewFrame)}.{character.ID}"); Identifier eventIdentifier = new Identifier($"{nameof(CreateWalletCrewFrame)}.{character.ID}");
campaign.OnMoneyChanged.RegisterOverwriteExisting(eventIdentifier, e => campaign.OnMoneyChanged.RegisterOverwriteExisting(eventIdentifier, e =>
{ {
if (!(e.Owner is Some<Character> { Value: var owner }) || owner != character) { return; } if (!e.Owner.TryUnwrap(out var owner) || owner != character) { return; }
SetWalletText(walletBlock, e.Wallet, icon, largeIcon); SetWalletText(walletBlock, e.Wallet, icon, largeIcon);
}); });
registeredEvents.Add(eventIdentifier); registeredEvents.Add(eventIdentifier);
@@ -1502,27 +1502,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;
@@ -1534,6 +1516,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);
@@ -1545,6 +1551,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)
{ {
@@ -1556,7 +1563,7 @@ namespace Barotrauma
descriptionText += "\n\n" + missionMessage; descriptionText += "\n\n" + missionMessage;
} }
RichString rewardText = mission.GetMissionRewardText(Submarine.MainSub); RichString rewardText = mission.GetMissionRewardText(Submarine.MainSub);
RichString reputationText = mission.GetReputationRewardText(mission.Locations[0]); RichString reputationText = mission.GetReputationRewardText();
Func<string, string> wrapMissionText(GUIFont font) Func<string, string> wrapMissionText(GUIFont font)
{ {
@@ -727,7 +727,7 @@ namespace Barotrauma
talentStages.Add(GetTalentState(character, button.Identifier, selectedTalents)); talentStages.Add(GetTalentState(character, button.Identifier, selectedTalents));
} }
TalentStages collectiveStage = talentStages.Any(static stage => stage is Locked) TalentStages collectiveStage = talentStages.All(static stage => stage is Locked)
? Locked ? Locked
: Available; : Available;
@@ -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.Linq; using System.Linq;
using Barotrauma.Extensions; using Barotrauma.Extensions;
@@ -77,6 +78,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.
@@ -102,6 +105,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)
{ {
@@ -130,6 +134,7 @@ namespace Barotrauma
private void RefreshAll() private void RefreshAll()
{ {
characterList = GameSession.GetSessionCrewCharacters(CharacterType.Both);
switch (selectedUpgradeTab) switch (selectedUpgradeTab)
{ {
case UpgradeTab.Repairs: case UpgradeTab.Repairs:
@@ -273,7 +278,7 @@ namespace Barotrauma
new GUITextBlock(rectT(1, 0, tooltipLayout), string.Empty) { UserData = "moreindicator" }; new GUITextBlock(rectT(1, 0, tooltipLayout), string.Empty) { UserData = "moreindicator" };
ItemInfoFrame.Children.ForEach(c => { c.CanBeFocused = false; c.Children.ForEach(c2 => c2.CanBeFocused = false); }); ItemInfoFrame.Children.ForEach(c => { c.CanBeFocused = false; c.Children.ForEach(c2 => c2.CanBeFocused = false); });
GUIFrame paddedLayout = new GUIFrame(rectT(0.95f, GUI.IsFourByThree() ? 0.98f : 0.95f, parent, Anchor.Center), style: null); GUIFrame paddedLayout = new GUIFrame(rectT(0.95f, 0.95f, parent, Anchor.Center), style: null);
mainStoreLayout = new GUILayoutGroup(rectT(1, 0.9f, paddedLayout, Anchor.BottomLeft), isHorizontal: true) { RelativeSpacing = 0.01f }; mainStoreLayout = new GUILayoutGroup(rectT(1, 0.9f, paddedLayout, Anchor.BottomLeft), isHorizontal: true) { RelativeSpacing = 0.01f };
topHeaderLayout = new GUILayoutGroup(rectT(1, 0.1f, paddedLayout, Anchor.TopLeft), isHorizontal: true); topHeaderLayout = new GUILayoutGroup(rectT(1, 0.1f, paddedLayout, Anchor.TopLeft), isHorizontal: true);
@@ -295,8 +300,8 @@ namespace Barotrauma
new GUITextBlock(rectT(1.0f, 1, locationLayout), TextManager.Get("UpgradeUI.AllSubmarinesInfo"), font: GUIStyle.SmallFont, wrap: true); 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(0.5f, 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(0.5f, 1f, categoryButtonLayout), TextManager.Get("UICategory.Maintenance"), style: "GUITabButton") { UserData = UpgradeTab.Repairs, Selected = selectedUpgradeTab == UpgradeTab.Repairs };
/* RIGHT HEADER LAYOUT /* RIGHT HEADER LAYOUT
* |---------------------------------------------------------------------------------------------------| * |---------------------------------------------------------------------------------------------------|
@@ -347,12 +352,15 @@ namespace Barotrauma
SelectTab(UpgradeTab.Upgrade); SelectTab(UpgradeTab.Upgrade);
var itemSwapPreview = new GUICustomComponent(new RectTransform(new Vector2(0.27f, 0.4f), mainStoreLayout.RectTransform, Anchor.TopLeft) { RelativeOffset = new Vector2(GUI.IsFourByThree() ? 0.5f : 0.47f, 0.0f) }, DrawItemSwapPreview) var itemSwapPreview = new GUICustomComponent(new RectTransform(new Vector2(0.25f, 0.4f), mainStoreLayout.RectTransform, Anchor.TopLeft)
{ RelativeOffset = new Vector2(0.52f * GUI.AspectRatioAdjustment, 0.0f) }, DrawItemSwapPreview)
{ {
IgnoreLayoutGroups = true, IgnoreLayoutGroups = true,
CanBeFocused = true CanBeFocused = true
}; };
GUITextBlock.AutoScaleAndNormalize(upgradeButton.TextBlock, repairButton.TextBlock);
#if DEBUG #if DEBUG
// creates a button that re-creates the UI // creates a button that re-creates the UI
CreateRefreshButton(); CreateRefreshButton();
@@ -725,7 +733,7 @@ namespace Barotrauma
if (storeLayout == null || mainStoreLayout == null) { return; } if (storeLayout == null || mainStoreLayout == null) { return; }
currentStoreLayout = CreateUpgradeCategoryList(rectT(1.0f, 1.5f, storeLayout)); currentStoreLayout = CreateUpgradeCategoryList(rectT(1.0f, 1.5f, storeLayout));
selectedUpgradeCategoryLayout = new GUIFrame(rectT(GUI.IsFourByThree() ? 0.3f : 0.25f, 1, mainStoreLayout), style: null) { CanBeFocused = false }; selectedUpgradeCategoryLayout = new GUIFrame(rectT(0.3f * GUI.AspectRatioAdjustment, 1, mainStoreLayout), style: null) { CanBeFocused = false };
RefreshUpgradeList(); RefreshUpgradeList();
@@ -956,7 +964,7 @@ namespace Barotrauma
bool isUninstallPending = item.Prefab.SwappableItem != null && item.PendingItemSwap?.Identifier == item.Prefab.SwappableItem.ReplacementOnUninstall; bool isUninstallPending = item.Prefab.SwappableItem != null && item.PendingItemSwap?.Identifier == item.Prefab.SwappableItem.ReplacementOnUninstall;
if (isUninstallPending) { canUninstall = false; } if (isUninstallPending) { canUninstall = false; }
frames.Add(CreateUpgradeEntry(rectT(1f, 0.25f, parent.Content), currentOrPending.UpgradePreviewSprite, frames.Add(CreateUpgradeEntry(rectT(1f, 0.35f, parent.Content), currentOrPending.UpgradePreviewSprite,
item.PendingItemSwap != null ? TextManager.GetWithVariable("upgrades.pendingitem", "[itemname]", name) : TextManager.GetWithVariable("upgrades.installeditem", "[itemname]", nameWithQuantity), item.PendingItemSwap != null ? TextManager.GetWithVariable("upgrades.pendingitem", "[itemname]", name) : TextManager.GetWithVariable("upgrades.installeditem", "[itemname]", nameWithQuantity),
currentOrPending.Description, currentOrPending.Description,
0, null, addBuyButton: canUninstall, addProgressBar: false, buttonStyle: "WeaponUninstallButton").Frame); 0, null, addBuyButton: canUninstall, addProgressBar: false, buttonStyle: "WeaponUninstallButton").Frame);
@@ -996,7 +1004,7 @@ namespace Barotrauma
int price = isPurchased || replacement == item.Prefab ? 0 : replacement.SwappableItem.GetPrice(Campaign.Map?.CurrentLocation) * linkedItems.Count(); int price = isPurchased || replacement == item.Prefab ? 0 : replacement.SwappableItem.GetPrice(Campaign.Map?.CurrentLocation) * linkedItems.Count();
frames.Add(CreateUpgradeEntry(rectT(1f, 0.25f, parent.Content), replacement.UpgradePreviewSprite, replacement.Name, replacement.Description, frames.Add(CreateUpgradeEntry(rectT(1f, 0.35f, parent.Content), replacement.UpgradePreviewSprite, replacement.Name, replacement.Description,
price, replacement, price, replacement,
addBuyButton: true, addBuyButton: true,
addProgressBar: false, addProgressBar: false,
@@ -1102,7 +1110,7 @@ namespace Barotrauma
public static UpgradeFrame CreateUpgradeFrame(UpgradePrefab prefab, UpgradeCategory category, CampaignMode campaign, RectTransform rectTransform, bool addBuyButton = true) public static UpgradeFrame 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));
} }
@@ -1129,7 +1137,8 @@ namespace Barotrauma
GUILayoutGroup imageLayout = new GUILayoutGroup(rectT(new Point(prefabLayout.Rect.Height, prefabLayout.Rect.Height), prefabLayout), childAnchor: Anchor.Center); GUILayoutGroup imageLayout = new GUILayoutGroup(rectT(new Point(prefabLayout.Rect.Height, prefabLayout.Rect.Height), prefabLayout), childAnchor: Anchor.Center);
var icon = new GUIImage(rectT(0.9f, 0.9f, imageLayout, scaleBasis: ScaleBasis.BothHeight), sprite, scaleToFit: true) { CanBeFocused = false }; var icon = new GUIImage(rectT(0.9f, 0.9f, imageLayout, scaleBasis: ScaleBasis.BothHeight), sprite, scaleToFit: true) { CanBeFocused = false };
GUILayoutGroup textLayout = new GUILayoutGroup(rectT(1f - imageLayout.RectTransform.RelativeSize.X, 1, prefabLayout)); GUILayoutGroup textLayout = new GUILayoutGroup(rectT(1f - imageLayout.RectTransform.RelativeSize.X, 1, prefabLayout));
var name = new GUITextBlock(rectT(1, 0.25f, textLayout), RichString.Rich(title), font: GUIStyle.SubHeadingFont) { AutoScaleHorizontal = true, AutoScaleVertical = true, Padding = Vector4.Zero }; var name = new GUITextBlock(rectT(1, 0.35f, textLayout), RichString.Rich(title), font: GUIStyle.SubHeadingFont) { AutoScaleHorizontal = true, AutoScaleVertical = true, Padding = Vector4.Zero };
//name.RectTransform.MinSize = new Point(0, (int)name.TextSize.Y);
GUILayoutGroup descriptionLayout = new GUILayoutGroup(rectT(1, 0.75f - progressBarHeight, textLayout)); GUILayoutGroup descriptionLayout = new GUILayoutGroup(rectT(1, 0.75f - progressBarHeight, textLayout));
var description = new GUITextBlock(rectT(1, 1, descriptionLayout), body, font: GUIStyle.SmallFont, wrap: true, textAlignment: Alignment.TopLeft) { Padding = Vector4.Zero }; var description = new GUITextBlock(rectT(1, 1, descriptionLayout), body, font: GUIStyle.SmallFont, wrap: true, textAlignment: Alignment.TopLeft) { Padding = Vector4.Zero };
GUILayoutGroup? progressLayout = null; GUILayoutGroup? progressLayout = null;
@@ -1171,7 +1180,7 @@ namespace Barotrauma
materialCostList.Visible = false; materialCostList.Visible = false;
materialCostList.UserData = UpgradeStoreUserData.MaterialCostList; materialCostList.UserData = UpgradeStoreUserData.MaterialCostList;
var priceText = new GUITextBlock(rectT(0.2f, 1f, buyButtonLayout), formattedPrice, textAlignment: Alignment.Right) var priceText = new GUITextBlock(rectT(0.2f, 1f, buyButtonLayout), formattedPrice, textAlignment: Alignment.CenterRight)
{ {
UserData = UpgradeStoreUserData.PriceLabel, UserData = UpgradeStoreUserData.PriceLabel,
//prices on swappable items are always visible, upgrade prices are enabled in UpdateUpgradeEntry for purchasable upgrades //prices on swappable items are always visible, upgrade prices are enabled in UpdateUpgradeEntry for purchasable upgrades
@@ -1258,7 +1267,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)
@@ -1673,7 +1682,7 @@ namespace Barotrauma
GUITextBlock priceLabel = (GUITextBlock)buttonParent.FindChild(UpgradeStoreUserData.PriceLabel, recursive: true); GUITextBlock priceLabel = (GUITextBlock)buttonParent.FindChild(UpgradeStoreUserData.PriceLabel, recursive: true);
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 (!WaitForServerUpdate) if (!WaitForServerUpdate)
{ {
@@ -1713,7 +1722,7 @@ namespace Barotrauma
static void CreateMaterialCosts(GUIListBox list, UpgradePrefab prefab, int targetLevel) static void CreateMaterialCosts(GUIListBox list, UpgradePrefab prefab, int targetLevel)
{ {
list.Content.ClearChildren(); list.Content.ClearChildren();
List<Item> allItems = Character.Controlled?.Inventory?.FindAllItems(recursive: true) ?? new List<Item>(); var allItems = CargoManager.FindAllItemsOnPlayerAndSub(Character.Controlled);
var resources = prefab.GetApplicableResources(targetLevel); var resources = prefab.GetApplicableResources(targetLevel);
@@ -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,35 +178,21 @@ 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:
int deliveryFee = SubmarineSelection.DeliveryFeePerDistanceTravelled * GameMain.GameSession.Map.DistanceToClosestLocationWithOutpost(GameMain.GameSession.Map.CurrentLocation, out Location endLocation); tag = transferItems ? "submarineswitchwithitemsnofeevote" : "submarineswitchnofeevote";
if (deliveryFee > 0) text = TextManager.GetWithVariables(tag,
{ ("[playername]", characterRichString),
tag = transferItems ? "submarineswitchwithitemsfeevote" : "submarineswitchfeevote"; ("[submarinename]", submarineRichString));
text = TextManager.GetWithVariables(tag,
("[playername]", characterRichString),
("[submarinename]", submarineRichString),
("[locationname]", endLocation.Name),
("[amount]", deliveryFee.ToString()),
("[currencyname]", TextManager.Get("credit").ToLower()));
}
else
{
tag = transferItems ? "submarineswitchwithitemsnofeevote" : "submarineswitchnofeevote";
text = TextManager.GetWithVariables(tag,
("[playername]", characterRichString),
("[submarinename]", submarineRichString));
}
break; break;
} }
votingOnText = RichString.Rich(text); votingOnText = RichString.Rich(text);
@@ -218,6 +205,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 +213,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,31 +221,16 @@ 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()));
break; break;
case VoteType.SwitchSub: case VoteType.SwitchSub:
int deliveryFee = SubmarineSelection.DeliveryFeePerDistanceTravelled * GameMain.GameSession.Map.DistanceToClosestLocationWithOutpost(GameMain.GameSession.Map.CurrentLocation, out Location endLocation); result = TextManager.GetWithVariables(votePassed ? "submarineswitchnofeevotepassed" : "submarineswitchnofeevotefailed",
("[submarinename]", info.DisplayName),
if (deliveryFee > 0) ("[yesvotecount]", yesVoteCount.ToString()),
{ ("[novotecount]", noVoteCount.ToString()));
result = TextManager.GetWithVariables(votePassed ? "submarineswitchfeevotepassed" : "submarineswitchfeevotefailed",
("[submarinename]", info.DisplayName),
("[locationname]", endLocation.Name),
("[amount]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", deliveryFee)),
("[currencyname]", TextManager.Get("credit").ToLower()),
("[yesvotecount]", yesVoteCount.ToString()),
("[novotecount]", noVoteCount.ToString()));
}
else
{
result = TextManager.GetWithVariables(votePassed ? "submarineswitchnofeevotepassed" : "submarineswitchnofeevotefailed",
("[submarinename]", info.DisplayName),
("[yesvotecount]", yesVoteCount.ToString()),
("[novotecount]", noVoteCount.ToString()));
}
break; break;
default: default:
break; break;
@@ -232,8 +232,7 @@ namespace Barotrauma
} }
GameSettings.Init(); GameSettings.Init();
CreatureMetrics.Init();
Md5Hash.Cache.Load();
ConsoleArguments = args; ConsoleArguments = args;
@@ -747,7 +746,7 @@ namespace Barotrauma
} }
else if (HasLoaded) else if (HasLoaded)
{ {
if (ConnectCommand is Some<ConnectCommand> { Value: var connectCommand }) if (ConnectCommand.TryUnwrap(out var connectCommand))
{ {
if (Client != null) if (Client != null)
{ {
@@ -1069,21 +1068,23 @@ namespace Barotrauma
public static void QuitToMainMenu(bool save) public static void QuitToMainMenu(bool save)
{ {
CreatureMetrics.Save();
if (save) if (save)
{ {
GUI.SetSavingIndicatorState(true); GUI.SetSavingIndicatorState(true);
if (GameSession.Submarine != null && !GameSession.Submarine.Removed) if (GameSession.Submarine != null && !GameSession.Submarine.Removed)
{ {
GameSession.SubmarineInfo = new SubmarineInfo(GameSession.Submarine); GameSession.SubmarineInfo = new SubmarineInfo(GameSession.Submarine);
} }
if (GameSession.Campaign is CampaignMode campaign)
// Update store stock when saving and quitting in an outpost (normally updated when CampaignMode.End() is called)
if (GameSession?.Campaign is SinglePlayerCampaign spCampaign && Level.IsLoadedFriendlyOutpost)
{ {
spCampaign.UpdateStoreStock(); if (campaign is SinglePlayerCampaign spCampaign && Level.IsLoadedFriendlyOutpost)
{
spCampaign.UpdateStoreStock();
}
GameSession.EventManager?.RegisterEventHistory(registerFinishedOnly: true);
campaign.End();
} }
SaveUtil.SaveGame(GameSession.SavePath); SaveUtil.SaveGame(GameSession.SavePath);
} }
@@ -1174,6 +1175,7 @@ namespace Barotrauma
protected override void OnExiting(object sender, EventArgs args) protected override void OnExiting(object sender, EventArgs args)
{ {
exiting = true; exiting = true;
CreatureMetrics.Save();
DebugConsole.NewMessage("Exiting..."); DebugConsole.NewMessage("Exiting...");
Client?.Quit(); Client?.Quit();
SteamManager.ShutDown(); SteamManager.ShutDown();
@@ -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
{ {
@@ -194,7 +193,7 @@ namespace Barotrauma
}; };
} }
var reports = OrderPrefab.Prefabs.Where(o => o.IsReport && o.SymbolSprite != null && !o.Hidden).ToArray(); var reports = OrderPrefab.Prefabs.Where(o => o.IsReport && o.SymbolSprite != null && !o.Hidden).OrderBy(o => o.Identifier).ToArray();
if (reports.None()) if (reports.None())
{ {
DebugConsole.ThrowError("No valid orders for report buttons found! Cannot create report buttons. The orders for the report buttons must have 'targetallcharacters' attribute enabled and a valid 'symbolsprite' defined."); DebugConsole.ThrowError("No valid orders for report buttons found! Cannot create report buttons. The orders for the report buttons must have 'targetallcharacters' attribute enabled and a valid 'symbolsprite' defined.");
@@ -377,6 +376,7 @@ namespace Barotrauma
- (0.1f * iconRelativeWidth) - (0.1f * iconRelativeWidth)
// Spacing // Spacing
- (7 * layoutGroup.RelativeSpacing); - (7 * layoutGroup.RelativeSpacing);
nameRelativeWidth = Math.Max(nameRelativeWidth, 0.25f);
var font = layoutGroup.Rect.Width < 150 ? GUIStyle.SmallFont : GUIStyle.Font; var font = layoutGroup.Rect.Width < 150 ? GUIStyle.SmallFont : GUIStyle.Font;
var nameBlock = new GUITextBlock( var nameBlock = new GUITextBlock(
@@ -1403,8 +1403,7 @@ namespace Barotrauma
bool hitDeselect = PlayerInput.KeyHit(InputType.Deselect) && bool hitDeselect = PlayerInput.KeyHit(InputType.Deselect) &&
(!PlayerInput.SecondaryMouseButtonClicked() || (!isMouseOnOptionNode && !isMouseOnShortcutNode)); (!PlayerInput.SecondaryMouseButtonClicked() || (!isMouseOnOptionNode && !isMouseOnShortcutNode));
bool isBoundToPrimaryMouse = GameSettings.CurrentConfig.KeyMap.Bindings[InputType.Command].MouseButton is MouseButton mouseButton && bool isBoundToPrimaryMouse = GameSettings.CurrentConfig.KeyMap.Bindings[InputType.Command].MouseButton == MouseButton.PrimaryMouse;
(mouseButton == MouseButton.PrimaryMouse || mouseButton == (PlayerInput.MouseButtonsSwapped() ? MouseButton.RightMouse : MouseButton.LeftMouse));
bool canToggleInterface = !isBoundToPrimaryMouse || bool canToggleInterface = !isBoundToPrimaryMouse ||
(!isMouseOnOptionNode && !isMouseOnShortcutNode && extraOptionNodes.None(n => GUI.IsMouseOn(n)) && !GUI.IsMouseOn(returnNode)); (!isMouseOnOptionNode && !isMouseOnShortcutNode && extraOptionNodes.None(n => GUI.IsMouseOn(n)) && !GUI.IsMouseOn(returnNode));
@@ -2796,8 +2795,8 @@ namespace Barotrauma
var orderName = GetOrderNameBasedOnContextuality(order); var orderName = GetOrderNameBasedOnContextuality(order);
var icon = CreateNodeIcon(Vector2.One, node.RectTransform, order.SymbolSprite, order.Color, var icon = CreateNodeIcon(Vector2.One, node.RectTransform, order.SymbolSprite, order.Color,
tooltip: !showAssignmentTooltip ? orderName : orderName + tooltip: !showAssignmentTooltip ? orderName : orderName +
"\n" + (!PlayerInput.MouseButtonsSwapped() ? TextManager.Get("input.leftmouse") : TextManager.Get("input.rightmouse")) + ": " + TextManager.Get("commandui.quickassigntooltip") + "\n" + PlayerInput.PrimaryMouseLabel + ": " + TextManager.Get("commandui.quickassigntooltip") +
"\n" + (!PlayerInput.MouseButtonsSwapped() ? TextManager.Get("input.rightmouse") : TextManager.Get("input.leftmouse")) + ": " + TextManager.Get("commandui.manualassigntooltip")); "\n" + PlayerInput.SecondaryMouseLabel + ": " + TextManager.Get("commandui.manualassigntooltip"));
if (disableNode) if (disableNode)
{ {
@@ -2999,8 +2998,8 @@ namespace Barotrauma
var showAssignmentTooltip = characterContext == null && !order.MustManuallyAssign && !order.TargetAllCharacters; var showAssignmentTooltip = characterContext == null && !order.MustManuallyAssign && !order.TargetAllCharacters;
icon = CreateNodeIcon(Vector2.One, node.RectTransform, sprite, order.Color, icon = CreateNodeIcon(Vector2.One, node.RectTransform, sprite, order.Color,
tooltip: characterContext != null ? optionName : optionName + tooltip: characterContext != null ? optionName : optionName +
"\n" + (!PlayerInput.MouseButtonsSwapped() ? TextManager.Get("input.leftmouse") : TextManager.Get("input.rightmouse")) + ": " + TextManager.Get("commandui.quickassigntooltip") + "\n" + PlayerInput.PrimaryMouseLabel + ": " + TextManager.Get("commandui.quickassigntooltip") +
"\n" + (!PlayerInput.MouseButtonsSwapped() ? TextManager.Get("input.rightmouse") : TextManager.Get("input.leftmouse")) + ": " + TextManager.Get("commandui.manualassigntooltip")); "\n" + PlayerInput.SecondaryMouseLabel + ": " + TextManager.Get("commandui.manualassigntooltip"));
} }
if (!CanCharacterBeHeard()) if (!CanCharacterBeHeard())
{ {
@@ -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);
}
} }
} }
} }
@@ -13,7 +13,7 @@ namespace Barotrauma
partial void SettingsChanged(Option<int> balanceChanged, Option<int> rewardChanged) partial void SettingsChanged(Option<int> balanceChanged, Option<int> rewardChanged)
{ {
if (Owner is Some<Character> { Value: var character }) if (Owner.TryUnwrap(out var character))
{ {
if (!character.IsPlayer) { return; } if (!character.IsPlayer) { return; }
} }
@@ -1,5 +1,4 @@
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;
@@ -16,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;
@@ -30,7 +27,13 @@ namespace Barotrauma
protected GUIFrame campaignUIContainer; protected GUIFrame campaignUIContainer;
public CampaignUI CampaignUI; public CampaignUI CampaignUI;
public static CancellationTokenSource StartRoundCancellationToken { get; private set; } public SlideshowPlayer SlideshowPlayer
{
get;
protected set;
}
private CancellationTokenSource startRoundCancellationToken;
public bool ForceMapUI public bool ForceMapUI
{ {
@@ -59,10 +62,19 @@ namespace Barotrauma
{ {
chatBox.ToggleOpen = wasChatBoxOpen; chatBox.ToggleOpen = wasChatBoxOpen;
} }
if (!value && CampaignUI?.SelectedTab == InteractionType.PurchaseSub) if (!value)
{ {
SubmarinePreview.Close(); switch (CampaignUI?.SelectedTab)
{
case InteractionType.PurchaseSub:
SubmarinePreview.Close();
break;
case InteractionType.MedicalClinic:
CampaignUI.MedicalClinic?.OnDeselected();
break;
}
} }
showCampaignUI = value; showCampaignUI = value;
} }
} }
@@ -77,6 +89,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)
@@ -108,6 +121,23 @@ namespace Barotrauma
{ {
return AllowedToManageCampaign(ClientPermissions.ManageMoney); return AllowedToManageCampaign(ClientPermissions.ManageMoney);
} }
protected GUIButton CreateEndRoundButton()
{
int buttonWidth = (int)(450 * GUI.xScale * (GUI.IsUltrawide ? 3.0f : 1.0f));
int buttonHeight = (int)(40 * GUI.yScale);
var rectT = HUDLayoutSettings.ToRectTransform(new Rectangle((GameMain.GraphicsWidth / 2), HUDLayoutSettings.ButtonAreaTop.Center.Y, buttonWidth, buttonHeight), GUI.Canvas);
rectT.Pivot = Pivot.Center;
return new GUIButton(rectT, TextManager.Get("EndRound"), textAlignment: Alignment.Center, style: "EndRoundButton")
{
Pulse = true,
TextBlock =
{
Shadow = true,
AutoScaleHorizontal = true
}
};
}
public override void Draw(SpriteBatch spriteBatch) public override void Draw(SpriteBatch spriteBatch)
{ {
@@ -123,32 +153,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;
@@ -157,7 +165,7 @@ namespace Barotrauma
} }
if (Submarine.MainSub == null || Level.Loaded == null) { return; } if (Submarine.MainSub == null || Level.Loaded == null) { return; }
endRoundButton.Visible = false; bool allowEndingRound = false;
var availableTransition = GetAvailableTransition(out _, out Submarine leavingSub); var availableTransition = GetAvailableTransition(out _, out Submarine leavingSub);
LocalizedString buttonText = ""; LocalizedString buttonText = "";
switch (availableTransition) switch (availableTransition)
@@ -168,12 +176,12 @@ namespace Barotrauma
{ {
string textTag = availableTransition == TransitionType.ProgressToNextLocation ? "EnterLocation" : "EnterEmptyLocation"; string textTag = availableTransition == TransitionType.ProgressToNextLocation ? "EnterLocation" : "EnterEmptyLocation";
buttonText = TextManager.GetWithVariable(textTag, "[locationname]", Level.Loaded.EndLocation?.Name ?? "[ERROR]"); buttonText = TextManager.GetWithVariable(textTag, "[locationname]", Level.Loaded.EndLocation?.Name ?? "[ERROR]");
endRoundButton.Visible = !ForceMapUI && !ShowCampaignUI; allowEndingRound = !ForceMapUI && !ShowCampaignUI;
} }
break; break;
case TransitionType.LeaveLocation: case TransitionType.LeaveLocation:
buttonText = TextManager.GetWithVariable("LeaveLocation", "[locationname]", Level.Loaded.StartLocation?.Name ?? "[ERROR]"); buttonText = TextManager.GetWithVariable("LeaveLocation", "[locationname]", Level.Loaded.StartLocation?.Name ?? "[ERROR]");
endRoundButton.Visible = !ForceMapUI && !ShowCampaignUI; allowEndingRound = !ForceMapUI && !ShowCampaignUI;
break; break;
case TransitionType.ReturnToPreviousLocation: case TransitionType.ReturnToPreviousLocation:
case TransitionType.ReturnToPreviousEmptyLocation: case TransitionType.ReturnToPreviousEmptyLocation:
@@ -181,31 +189,27 @@ namespace Barotrauma
{ {
string textTag = availableTransition == TransitionType.ReturnToPreviousLocation ? "EnterLocation" : "EnterEmptyLocation"; string textTag = availableTransition == TransitionType.ReturnToPreviousLocation ? "EnterLocation" : "EnterEmptyLocation";
buttonText = TextManager.GetWithVariable(textTag, "[locationname]", Level.Loaded.StartLocation?.Name ?? "[ERROR]"); buttonText = TextManager.GetWithVariable(textTag, "[locationname]", Level.Loaded.StartLocation?.Name ?? "[ERROR]");
endRoundButton.Visible = !ForceMapUI && !ShowCampaignUI; allowEndingRound = !ForceMapUI && !ShowCampaignUI;
} }
break; break;
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]");
endRoundButton.Visible = !ForceMapUI && !ShowCampaignUI; allowEndingRound = !ForceMapUI && !ShowCampaignUI;
}
else
{
endRoundButton.Visible = false;
} }
break; break;
} }
if (Level.IsLoadedOutpost && !ObjectiveManager.AllActiveObjectivesCompleted()) if (Level.IsLoadedOutpost && !ObjectiveManager.AllActiveObjectivesCompleted())
{ {
endRoundButton.Visible = false; allowEndingRound = false;
} }
if (ReadyCheckButton != null) { ReadyCheckButton.Visible = allowEndingRound; }
if (ReadyCheckButton != null) { ReadyCheckButton.Visible = endRoundButton.Visible; } endRoundButton.Visible = allowEndingRound && Character.Controlled is { IsIncapacitated: false };
if (endRoundButton.Visible) if (endRoundButton.Visible)
{ {
if (!AllowedToManageCampaign(ClientPermissions.ManageMap)) if (!AllowedToManageCampaign(ClientPermissions.ManageMap))
@@ -259,11 +263,11 @@ namespace Barotrauma
GUI.ClearCursorWait(); GUI.ClearCursorWait();
StartRoundCancellationToken = new CancellationTokenSource(); startRoundCancellationToken = new CancellationTokenSource();
var loadTask = Task.Run(async () => var loadTask = Task.Run(async () =>
{ {
await Task.Yield(); await Task.Yield();
Rand.ThreadId = Thread.CurrentThread.ManagedThreadId; Rand.ThreadId = Environment.CurrentManagedThreadId;
try try
{ {
GameMain.GameSession.StartRound(newLevel, mirrorLevel: mirror, startOutpost: GetPredefinedStartOutpost()); GameMain.GameSession.StartRound(newLevel, mirrorLevel: mirror, startOutpost: GetPredefinedStartOutpost());
@@ -273,7 +277,8 @@ namespace Barotrauma
roundSummaryScreen.LoadException = e; roundSummaryScreen.LoadException = e;
} }
Rand.ThreadId = 0; Rand.ThreadId = 0;
}, StartRoundCancellationToken.Token); startRoundCancellationToken = null;
}, startRoundCancellationToken.Token);
TaskPool.Add("AsyncCampaignStartRound", loadTask, (t) => TaskPool.Add("AsyncCampaignStartRound", loadTask, (t) =>
{ {
overlayColor = Color.Transparent; overlayColor = Color.Transparent;
@@ -283,6 +288,21 @@ namespace Barotrauma
return loadTask; return loadTask;
} }
public void CancelStartRound()
{
startRoundCancellationToken?.Cancel();
}
public void ThrowIfStartRoundCancellationRequested()
{
if (startRoundCancellationToken != null &&
startRoundCancellationToken.Token.IsCancellationRequested)
{
startRoundCancellationToken.Token.ThrowIfCancellationRequested();
startRoundCancellationToken = null;
}
}
protected SubmarineInfo GetPredefinedStartOutpost() protected SubmarineInfo GetPredefinedStartOutpost()
{ {
if (Map?.CurrentLocation?.Type?.GetForcedOutpostGenerationParams() is OutpostGenerationParams parameters && !parameters.OutpostFilePath.IsNullOrEmpty()) if (Map?.CurrentLocation?.Type?.GetForcedOutpostGenerationParams() is OutpostGenerationParams parameters && !parameters.OutpostFilePath.IsNullOrEmpty())
@@ -316,10 +336,15 @@ namespace Barotrauma
goto default; goto default;
default: default:
ShowCampaignUI = true; ShowCampaignUI = true;
CampaignUI.SelectTab(npc.CampaignInteractionType, storeIdentifier: npc.MerchantIdentifier); CampaignUI.SelectTab(npc.CampaignInteractionType, npc);
CampaignUI.UpgradeStore?.RequestRefresh(); CampaignUI.UpgradeStore?.RequestRefresh();
break; break;
} }
if (npc.AIController is HumanAIController humanAi && humanAi.IsInHostileFaction())
{
npc.Speak(TextManager.Get("dialoglowrepcampaigninteraction").Value, identifier: "dialoglowrepcampaigninteraction".ToIdentifier(), minDurationBetweenSimilar: 60.0f);
}
} }
public override void AddToGUIUpdateList() public override void AddToGUIUpdateList()
@@ -125,38 +125,25 @@ namespace Barotrauma
private void CreateButtons() private void CreateButtons()
{ {
int buttonHeight = (int) (GUI.Scale * 40), endRoundButton = CreateEndRoundButton();
buttonWidth = GUI.IntScale(450), endRoundButton.OnClicked = (btn, userdata) =>
buttonCenter = buttonHeight / 2,
screenMiddle = GameMain.GraphicsWidth / 2;
endRoundButton = new GUIButton(HUDLayoutSettings.ToRectTransform(new Rectangle(screenMiddle - buttonWidth / 2, HUDLayoutSettings.ButtonAreaTop.Center.Y - buttonCenter, buttonWidth, buttonHeight), GUI.Canvas),
TextManager.Get("EndRound"), textAlignment: Alignment.Center, style: "EndRoundButton")
{ {
Pulse = true, TryEndRoundWithFuelCheck(
TextBlock = onConfirm: () => GameMain.Client.RequestStartRound(),
{ onReturnToMapScreen: () =>
Shadow = true, {
AutoScaleHorizontal = true ShowCampaignUI = true;
}, if (CampaignUI == null) { InitCampaignUI(); }
OnClicked = (btn, userdata) => CampaignUI.SelectTab(InteractionType.Map);
{ });
TryEndRoundWithFuelCheck( return true;
onConfirm: () => GameMain.Client.RequestStartRound(),
onReturnToMapScreen: () =>
{
ShowCampaignUI = true;
if (CampaignUI == null) { InitCampaignUI(); }
CampaignUI.SelectTab(InteractionType.Map);
});
return true;
}
}; };
int readyButtonHeight = buttonHeight; int readyButtonWidth = (int)(GUI.Scale * 50 * (GUI.IsUltrawide ? 3.0f : 1.0f));
int readyButtonWidth = (int) (GUI.Scale * 50); int readyButtonHeight = (int)(GUI.Scale * 40);
int readyButtonCenter = readyButtonHeight / 2,
ReadyCheckButton = new GUIButton(HUDLayoutSettings.ToRectTransform(new Rectangle(screenMiddle + (buttonWidth / 2) + GUI.IntScale(16), HUDLayoutSettings.ButtonAreaTop.Center.Y - buttonCenter, readyButtonWidth, readyButtonHeight), GUI.Canvas), screenMiddle = GameMain.GraphicsWidth / 2;
ReadyCheckButton = new GUIButton(HUDLayoutSettings.ToRectTransform(new Rectangle(screenMiddle + (endRoundButton.Rect.Width / 2) + GUI.IntScale(16), HUDLayoutSettings.ButtonAreaTop.Center.Y - readyButtonCenter, readyButtonWidth, readyButtonHeight), GUI.Canvas),
style: "RepairBuyButton") style: "RepairBuyButton")
{ {
ToolTip = TextManager.Get("ReadyCheck.Tooltip"), ToolTip = TextManager.Get("ReadyCheck.Tooltip"),
@@ -216,51 +203,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 +243,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)
{ {
@@ -385,7 +346,7 @@ namespace Barotrauma
overlayColor = Color.Transparent; overlayColor = Color.Transparent;
if (DateTime.Now > timeOut) { GameMain.NetLobbyScreen.Select(); } if (DateTime.Now > timeOut) { GameMain.NetLobbyScreen.Select(); }
if (!(Screen.Selected is RoundSummaryScreen)) if (Screen.Selected is not RoundSummaryScreen)
{ {
if (continueButton != null) if (continueButton != null)
{ {
@@ -409,6 +370,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 +405,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,10 +431,16 @@ 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 +471,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 +481,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);
@@ -844,8 +789,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++)
@@ -854,11 +797,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);
@@ -871,6 +809,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))
@@ -995,7 +934,9 @@ namespace Barotrauma
if (firedCharacter != null) { CrewManager.FireCharacter(firedCharacter); } if (firedCharacter != null) { CrewManager.FireCharacter(firedCharacter); }
} }
if (map?.CurrentLocation?.HireManager != null && CampaignUI?.CrewManagement != null) if (map?.CurrentLocation?.HireManager != null && CampaignUI?.CrewManagement != null &&
/*can't apply until we have the latest save file*/
!NetIdUtils.IdMoreRecent(pendingSaveID, LastSaveID))
{ {
CampaignUI.CrewManagement.SetHireables(map.CurrentLocation, availableHires); CampaignUI.CrewManagement.SetHireables(map.CurrentLocation, availableHires);
if (hiredCharacters.Any()) { CampaignUI.CrewManagement.ValidateHires(hiredCharacters); } if (hiredCharacters.Any()) { CampaignUI.CrewManagement.ValidateHires(hiredCharacters); }
@@ -1010,25 +951,20 @@ namespace Barotrauma
foreach (NetWalletTransaction transaction in update.Transactions) foreach (NetWalletTransaction transaction in update.Transactions)
{ {
WalletInfo info = transaction.Info; WalletInfo info = transaction.Info;
switch (transaction.CharacterID) if (transaction.CharacterID.TryUnwrap(out var charID))
{ {
case Some<ushort> { Value: var charID }: Character targetCharacter = Character.CharacterList?.FirstOrDefault(c => c.ID == charID);
{ if (targetCharacter is null) { break; }
Character targetCharacter = Character.CharacterList?.FirstOrDefault(c => c.ID == charID); Wallet wallet = targetCharacter.Wallet;
if (targetCharacter is null) { break; }
Wallet wallet = targetCharacter.Wallet;
wallet.Balance = info.Balance; wallet.Balance = info.Balance;
wallet.RewardDistribution = info.RewardDistribution; wallet.RewardDistribution = info.RewardDistribution;
TryInvokeEvent(wallet, transaction.ChangedData, info); TryInvokeEvent(wallet, transaction.ChangedData, info);
break; }
} else
case None<ushort> _: {
{ Bank.Balance = info.Balance;
Bank.Balance = info.Balance; TryInvokeEvent(Bank, transaction.ChangedData, info);
TryInvokeEvent(Bank, transaction.ChangedData, info);
break;
}
} }
} }
@@ -1043,7 +979,7 @@ namespace Barotrauma
public override bool TryPurchase(Client client, int price) public override bool TryPurchase(Client client, int price)
{ {
if (!AllowedToManageCampaign(ClientPermissions.ManageCampaign)) if (!AllowedToManageCampaign(ClientPermissions.ManageMoney))
{ {
return PersonalWallet.TryDeduct(price); return PersonalWallet.TryDeduct(price);
} }
@@ -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,9 @@ 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);
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 +97,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();
} }
@@ -99,6 +106,17 @@ namespace Barotrauma
private SinglePlayerCampaign(XElement element) : base(GameModePreset.SinglePlayerCampaign, CampaignSettings.Empty) private SinglePlayerCampaign(XElement element) : base(GameModePreset.SinglePlayerCampaign, CampaignSettings.Empty)
{ {
IsFirstRound = false; IsFirstRound = false;
foreach (var subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "metadata":
CampaignMetadata.Load(subElement);
break;
}
}
InitFactions();
foreach (var subElement in element.Elements()) foreach (var subElement in element.Elements())
{ {
@@ -114,9 +132,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;
@@ -133,14 +148,14 @@ namespace Barotrauma
case "stats": case "stats":
LoadStats(subElement); LoadStats(subElement);
break; break;
case "eventmanager":
GameMain.GameSession.EventManager.Load(subElement);
break;
} }
} }
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
@@ -198,28 +213,14 @@ namespace Barotrauma
{ {
StartRound = () => { TryEndRound(); } StartRound = () => { TryEndRound(); }
}; };
}
private void CreateEndRoundButton() endRoundButton = CreateEndRoundButton();
{ endRoundButton.OnClicked = (btn, userdata) =>
int buttonHeight = (int)(GUI.Scale * 40);
int buttonWidth = GUI.IntScale(450);
endRoundButton = new GUIButton(HUDLayoutSettings.ToRectTransform(new Rectangle((GameMain.GraphicsWidth / 2) - (buttonWidth / 2), HUDLayoutSettings.ButtonAreaTop.Center.Y - (buttonHeight / 2), buttonWidth, buttonHeight), GUI.Canvas),
TextManager.Get("EndRound"), textAlignment: Alignment.Center, style: "EndRoundButton")
{ {
Pulse = true, TryEndRoundWithFuelCheck(
TextBlock = onConfirm: () => TryEndRound(),
{ onReturnToMapScreen: () => { ShowCampaignUI = true; CampaignUI.SelectTab(InteractionType.Map); });
Shadow = true, return true;
AutoScaleHorizontal = true
},
OnClicked = (btn, userdata) =>
{
TryEndRoundWithFuelCheck(
onConfirm: () => TryEndRound(),
onReturnToMapScreen: () => { ShowCampaignUI = true; CampaignUI.SelectTab(InteractionType.Map); });
return true;
}
}; };
} }
@@ -265,7 +266,6 @@ namespace Barotrauma
private IEnumerable<CoroutineStatus> DoLoadInitialLevel(LevelData level, bool mirror) private IEnumerable<CoroutineStatus> DoLoadInitialLevel(LevelData level, bool mirror)
{ {
GameMain.GameSession.StartRound(level, mirrorLevel: mirror, startOutpost: GetPredefinedStartOutpost()); GameMain.GameSession.StartRound(level, mirrorLevel: mirror, startOutpost: GetPredefinedStartOutpost());
GameMain.GameScreen.Select(); GameMain.GameScreen.Select();
@@ -296,34 +296,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 +306,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 +324,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;
@@ -440,61 +410,68 @@ namespace Barotrauma
TotalPassedLevels++; TotalPassedLevels++;
break; break;
case TransitionType.ProgressToNextEmptyLocation: case TransitionType.ProgressToNextEmptyLocation:
Map.Visit(Map.CurrentLocation);
TotalPassedLevels++; TotalPassedLevels++;
break; break;
case TransitionType.End:
EndCampaign();
IsFirstRound = true;
break;
} }
Map.ProgressWorld(transitionType, GameMain.GameSession.RoundDuration); Map.ProgressWorld(this, transitionType, GameMain.GameSession.RoundDuration);
var endTransition = new CameraTransition(Submarine.MainSub, GameMain.GameScreen.Cam, null,
transitionType == TransitionType.LeaveLocation ? Alignment.BottomCenter : Alignment.Center,
fadeOut: false,
panDuration: EndTransitionDuration);
GUI.ClearMessages(); GUI.ClearMessages();
Location portraitLocation = Map.SelectedLocation ?? Map.CurrentLocation;
overlaySprite = portraitLocation.Type.GetPortrait(portraitLocation.PortraitId);
float fadeOutDuration = endTransition.PanDuration;
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 (transitionType != TransitionType.End)
if (success)
{ {
GameMain.GameSession.SubmarineInfo = new SubmarineInfo(GameMain.GameSession.Submarine); var endTransition = new CameraTransition(Submarine.MainSub, GameMain.GameScreen.Cam, null,
SaveUtil.SaveGame(GameMain.GameSession.SavePath); transitionType == TransitionType.LeaveLocation ? Alignment.BottomCenter : Alignment.Center,
} fadeOut: false,
else panDuration: EndTransitionDuration);
{
PendingSubmarineSwitch = null;
EnableRoundSummaryGameOverState();
}
CrewManager?.ClearCurrentOrders(); Location portraitLocation = Map.SelectedLocation ?? Map.CurrentLocation;
overlaySprite = portraitLocation.Type.GetPortrait(portraitLocation.PortraitId);
//-------------------------------------- float fadeOutDuration = endTransition.PanDuration;
float t = 0.0f;
SelectSummaryScreen(roundSummary, newLevel, mirror, () => while (t < fadeOutDuration || endTransition.Running)
{
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;
@@ -502,7 +479,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;
@@ -511,39 +491,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() ||
@@ -594,11 +549,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);
@@ -608,11 +571,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();
@@ -725,6 +688,11 @@ namespace Barotrauma
modeElement.Add(Settings.Save()); modeElement.Add(Settings.Save());
modeElement.Add(SaveStats()); modeElement.Add(SaveStats());
if (GameMain.GameSession?.EventManager != null)
{
modeElement.Add(GameMain.GameSession?.EventManager.Save());
}
//save and remove all items that are in someone's inventory so they don't get included in the sub file as well //save and remove all items that are in someone's inventory so they don't get included in the sub file as well
foreach (Character c in Character.CharacterList) foreach (Character c in Character.CharacterList)
{ {
@@ -585,7 +585,7 @@ namespace Barotrauma
if (!gap.IsRoomToRoom) if (!gap.IsRoomToRoom)
{ {
if (!IsWearingDivingSuit()) { continue; } if (!IsWearingDivingSuit()) { continue; }
if (Character.Controlled.IsProtectedFromPressure()) { continue; } if (Character.Controlled.IsProtectedFromPressure) { continue; }
if (DisplayHint("divingsuitwarning".ToIdentifier(), extendTextTag: false)) { return; } if (DisplayHint("divingsuitwarning".ToIdentifier(), extendTextTag: false)) { return; }
continue; continue;
} }
@@ -11,6 +11,8 @@ namespace Barotrauma
{ {
internal sealed partial class MedicalClinic internal sealed partial class MedicalClinic
{ {
private MedicalClinicUI? ui => campaign?.CampaignUI?.MedicalClinic;
public enum RequestResult public enum RequestResult
{ {
Undecided, Undecided,
@@ -303,6 +305,12 @@ namespace Barotrauma
} }
} }
private void AfflictionUpdateReceived(IReadMessage inc)
{
NetCrewMember crewMember = INetSerializableStruct.Read<NetCrewMember>(inc);
ui?.UpdateAfflictions(crewMember);
}
private void PendingRequestReceived(IReadMessage inc) private void PendingRequestReceived(IReadMessage inc)
{ {
var pendingCrew = INetSerializableStruct.Read<NetCollection<NetCrewMember>>(inc); var pendingCrew = INetSerializableStruct.Read<NetCollection<NetCrewMember>>(inc);
@@ -312,6 +320,10 @@ namespace Barotrauma
} }
} }
public static void SendUnsubscribeRequest() => ClientSend(null,
header: NetworkHeader.UNSUBSCRIBE_ME,
deliveryMethod: DeliveryMethod.Reliable);
private static IWriteMessage StartSending() private static IWriteMessage StartSending()
{ {
IWriteMessage writeMessage = new WriteOnlyMessage(); IWriteMessage writeMessage = new WriteOnlyMessage();
@@ -337,6 +349,9 @@ namespace Barotrauma
case NetworkHeader.REQUEST_AFFLICTIONS: case NetworkHeader.REQUEST_AFFLICTIONS:
AfflictionRequestReceived(inc); AfflictionRequestReceived(inc);
break; break;
case NetworkHeader.AFFLICTION_UPDATE:
AfflictionUpdateReceived(inc);
break;
case NetworkHeader.REQUEST_PENDING: case NetworkHeader.REQUEST_PENDING:
PendingRequestReceived(inc); PendingRequestReceived(inc);
break; break;
@@ -40,7 +40,7 @@ namespace Barotrauma
private void CreateMessageBox(string author) private void CreateMessageBox(string author)
{ {
Vector2 relativeSize = new Vector2(GUI.IsFourByThree() ? 0.3f : 0.2f, 0.15f); Vector2 relativeSize = new Vector2(0.2f / GUI.AspectRatioAdjustment, 0.15f);
Point minSize = new Point(300, 200); Point minSize = new Point(300, 200);
msgBox = new GUIMessageBox(readyCheckHeader, readyCheckBody(author), new[] { yesButton, noButton }, relativeSize, minSize, type: GUIMessageBox.Type.Vote) { UserData = PromptData, Draggable = true }; msgBox = new GUIMessageBox(readyCheckHeader, readyCheckBody(author), new[] { yesButton, noButton }, relativeSize, minSize, type: GUIMessageBox.Type.Vote) { UserData = PromptData, Draggable = true };
@@ -21,8 +21,7 @@ namespace Barotrauma
private readonly GameMode gameMode; private readonly GameMode gameMode;
private readonly float initialLocationReputation; private readonly Dictionary<Identifier, float> initialFactionReputations = new Dictionary<Identifier, float>();
private readonly Dictionary<Faction, float> initialFactionReputations = new Dictionary<Faction, float>();
public GUILayoutGroup ButtonArea { get; private set; } public GUILayoutGroup ButtonArea { get; private set; }
@@ -36,12 +35,11 @@ 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)
{ {
initialFactionReputations.Add(faction, faction.Reputation.Value); initialFactionReputations.Add(faction.Prefab.Identifier, faction.Reputation.Value);
} }
} }
} }
@@ -214,11 +212,13 @@ 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 (startLocation != null)
{ {
foreach (Mission mission in startLocation.SelectedMissions) foreach (Mission mission in startLocation.SelectedMissions)
{ {
if (missionsToDisplay.Contains(mission)) { continue; }
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))
{ {
@@ -312,18 +312,27 @@ namespace Barotrauma
} }
var missionDescription = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), var missionDescription = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform),
RichString.Rich(missionMessage), wrap: true); RichString.Rich(missionMessage), wrap: true);
int reward = displayedMission.GetReward(Submarine.MainSub); if (selectedMissions.Contains(displayedMission) && displayedMission.Completed)
if (selectedMissions.Contains(displayedMission) && displayedMission.Completed && reward > 0)
{ {
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), RichString.Rich(displayedMission.GetMissionRewardText(Submarine.MainSub))); RichString reputationText = displayedMission.GetReputationRewardText();
if (GameMain.IsMultiplayer && Character.Controlled is { } controlled) if (!reputationText.IsNullOrEmpty())
{ {
var (share, percentage, _) = Mission.GetRewardShare(controlled.Wallet.RewardDistribution, GameSession.GetSessionCrewCharacters(CharacterType.Player).Where(c => c != controlled), Option<int>.Some(reward)); new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), reputationText, wrap: true);
if (share > 0) }
int totalReward = displayedMission.GetFinalReward(Submarine.MainSub);
if (totalReward > 0)
{
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), RichString.Rich(displayedMission.GetMissionRewardText(Submarine.MainSub)));
if (GameMain.IsMultiplayer && Character.Controlled is { } controlled)
{ {
string shareFormatted = string.Format(CultureInfo.InvariantCulture, "{0:N0}", share); var (share, percentage, _) = Mission.GetRewardShare(controlled.Wallet.RewardDistribution, GameSession.GetSessionCrewCharacters(CharacterType.Player).Where(c => c != controlled), Option<int>.Some(totalReward));
RichString yourShareString = RichString.Rich(TextManager.GetWithVariables("crewwallet.missionreward.get", ("[money]", $"{shareFormatted}"), ("[share]", $"{percentage}"))); if (share > 0)
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), yourShareString); {
string shareFormatted = string.Format(CultureInfo.InvariantCulture, "{0:N0}", share);
RichString yourShareString = RichString.Rich(TextManager.GetWithVariables("crewwallet.missionreward.get", ("[money]", $"{shareFormatted}"), ("[share]", $"{percentage}")));
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), yourShareString);
}
} }
} }
} }
@@ -401,33 +410,17 @@ namespace Barotrauma
}; };
reputationList.ContentBackground.Color = Color.Transparent; reputationList.ContentBackground.Color = Color.Transparent;
if (startLocation.Type.HasOutpost && startLocation.Reputation != null)
{
var iconStyle = GUIStyle.GetComponentStyle("LocationReputationIcon");
var locationFrame = CreateReputationElement(
reputationList.Content,
startLocation.Name,
startLocation.Reputation.Value, startLocation.Reputation.NormalizedValue, initialLocationReputation,
startLocation.Type.Name, "",
iconStyle?.GetDefaultSprite(), startLocation.Type.GetPortrait(0), iconStyle?.Color ?? Color.White);
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))
{ {
float initialReputation = faction.Reputation.Value; float initialReputation = faction.Reputation.Value;
if (initialFactionReputations.ContainsKey(faction)) if (!initialFactionReputations.TryGetValue(faction.Prefab.Identifier, out initialReputation))
{
initialReputation = initialFactionReputations[faction];
}
else
{ {
DebugConsole.AddWarning($"Could not determine reputation change for faction \"{faction.Prefab.Name}\" (faction was not present at the start of the round)."); DebugConsole.AddWarning($"Could not determine reputation change for faction \"{faction.Prefab.Name}\" (faction was not present at the start of the round).");
} }
var factionFrame = CreateReputationElement( var factionFrame = CreateReputationElement(
reputationList.Content, reputationList.Content,
faction.Prefab.Name, faction.Prefab.Name,
faction.Reputation.Value, faction.Reputation.NormalizedValue, initialReputation, faction.Reputation, initialReputation,
faction.Prefab.ShortDescription, faction.Prefab.Description, faction.Prefab.ShortDescription, faction.Prefab.Description,
faction.Prefab.Icon, faction.Prefab.BackgroundPortrait, faction.Prefab.IconColor); faction.Prefab.Icon, faction.Prefab.BackgroundPortrait, faction.Prefab.IconColor);
CreatePathUnlockElement(factionFrame, faction, null); CreatePathUnlockElement(factionFrame, faction, null);
@@ -455,49 +448,57 @@ namespace Barotrauma
void CreatePathUnlockElement(GUIComponent reputationFrame, Faction faction, Location location) void CreatePathUnlockElement(GUIComponent reputationFrame, Faction faction, Location location)
{ {
if (GameMain.GameSession?.Campaign?.Map != null) if (GameMain.GameSession?.Campaign?.Map == null) { return; }
IEnumerable<LocationConnection> connectionsBetweenBiomes =
GameMain.GameSession.Campaign.Map.Connections.Where(c => c.Locations[0].Biome != c.Locations[1].Biome);
foreach (LocationConnection connection in connectionsBetweenBiomes)
{ {
foreach (LocationConnection connection in GameMain.GameSession.Campaign.Map.Connections) if (!connection.Locked || (!connection.Locations[0].Discovered && !connection.Locations[1].Discovered)) { continue; }
//don't show the "reputation required to unlock" text if another connection between the biomes has already been unlocked
if (connectionsBetweenBiomes.Where(c => !c.Locked).Any(c =>
(c.Locations[0].Biome == connection.Locations[0].Biome && c.Locations[1].Biome == connection.Locations[1].Biome) ||
(c.Locations[1].Biome == connection.Locations[0].Biome && c.Locations[0].Biome == connection.Locations[1].Biome)))
{ {
if (!connection.Locked || (!connection.Locations[0].Discovered && !connection.Locations[1].Discovered)) { continue; } 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; }
}
else
{
if (faction == null || faction.Prefab.Identifier != unlockEvent.Faction) { continue; }
}
if (unlockEvent != null)
{
Reputation unlockReputation = gateLocation.Reputation;
Faction unlockFaction = null;
if (!unlockEvent.Faction.IsEmpty)
{ {
if (location == null || gateLocation != location) { continue; } unlockFaction = GameMain.GameSession.Campaign.Factions.Find(f => f.Prefab.Identifier == unlockEvent.Faction);
unlockReputation = unlockFaction?.Reputation;
} }
else float normalizedUnlockReputation = MathUtils.InverseLerp(unlockReputation.MinReputation, unlockReputation.MaxReputation, unlockEvent.UnlockPathReputation);
RichString unlockText = RichString.Rich(TextManager.GetWithVariables(
"lockedpathreputationrequirement",
("[reputation]", Reputation.GetFormattedReputationText(normalizedUnlockReputation, unlockEvent.UnlockPathReputation, addColorTags: true)),
("[biomename]", $"‖color:gui.orange‖{connection.LevelData.Biome.DisplayName}‖end‖")));
var unlockInfoPanel = new GUITextBlock(new RectTransform(new Vector2(0.8f, 0.0f), reputationFrame.RectTransform, Anchor.BottomCenter) { MinSize = new Point(0, GUI.IntScale(30)), AbsoluteOffset = new Point(0, GUI.IntScale(3)) },
unlockText, style: "GUIButtonRound", textAlignment: Alignment.Center, textColor: GUIStyle.TextColorNormal);
unlockInfoPanel.Color = Color.Lerp(unlockInfoPanel.Color, Color.Black, 0.8f);
unlockInfoPanel.UserData = "unlockinfo";
if (unlockInfoPanel.TextSize.X > unlockInfoPanel.Rect.Width * 0.7f)
{ {
if (faction == null || faction.Prefab.Identifier != unlockEvent.UnlockPathFaction) { continue; } unlockInfoPanel.Font = GUIStyle.SmallFont;
}
if (unlockEvent != null)
{
Reputation unlockReputation = gateLocation.Reputation;
Faction unlockFaction = null;
if (!string.IsNullOrEmpty(unlockEvent.UnlockPathFaction))
{
unlockFaction = GameMain.GameSession.Campaign.Factions.Find(f => f.Prefab.Identifier == unlockEvent.UnlockPathFaction);
unlockReputation = unlockFaction?.Reputation;
}
float normalizedUnlockReputation = MathUtils.InverseLerp(unlockReputation.MinReputation, unlockReputation.MaxReputation, unlockEvent.UnlockPathReputation);
RichString unlockText = RichString.Rich(TextManager.GetWithVariables(
"lockedpathreputationrequirement",
("[reputation]", Reputation.GetFormattedReputationText(normalizedUnlockReputation, unlockEvent.UnlockPathReputation, addColorTags: true)),
("[biomename]", $"‖color:gui.orange‖{connection.LevelData.Biome.DisplayName}‖end‖")));
var unlockInfoPanel = new GUITextBlock(new RectTransform(new Vector2(0.8f, 0.0f), reputationFrame.RectTransform, Anchor.BottomCenter) { MinSize = new Point(0, GUI.IntScale(30)), AbsoluteOffset = new Point(0, GUI.IntScale(3)) },
unlockText, style: "GUIButtonRound", textAlignment: Alignment.Center, textColor: GUIStyle.TextColorNormal);
unlockInfoPanel.Color = Color.Lerp(unlockInfoPanel.Color, Color.Black, 0.8f);
unlockInfoPanel.UserData = "unlockinfo";
if (unlockInfoPanel.TextSize.X > unlockInfoPanel.Rect.Width * 0.7f)
{
unlockInfoPanel.Font = GUIStyle.SmallFont;
}
} }
} }
} }
@@ -543,6 +544,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)
@@ -680,7 +686,7 @@ namespace Barotrauma
} }
private GUIFrame CreateReputationElement(GUIComponent parent, private GUIFrame CreateReputationElement(GUIComponent parent,
LocalizedString name, float reputation, float normalizedReputation, float initialReputation, LocalizedString name, Reputation reputation, float initialReputation,
LocalizedString shortDescription, LocalizedString fullDescription, Sprite icon, Sprite backgroundPortrait, Color iconColor) LocalizedString shortDescription, LocalizedString fullDescription, Sprite icon, Sprite backgroundPortrait, Color iconColor)
{ {
var factionFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.1f), parent.RectTransform), style: null); var factionFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.1f), parent.RectTransform), style: null);
@@ -698,21 +704,22 @@ namespace Barotrauma
}; };
} }
var factionInfoHorizontal = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), factionFrame.RectTransform, Anchor.Center), childAnchor: Anchor.CenterLeft, isHorizontal: true) var factionInfoHorizontal = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), factionFrame.RectTransform, Anchor.Center), childAnchor: Anchor.CenterRight, isHorizontal: true)
{ {
AbsoluteSpacing = GUI.IntScale(5), AbsoluteSpacing = GUI.IntScale(5),
Stretch = true Stretch = true
}; };
var factionIcon = new GUIImage(new RectTransform(Vector2.One * 0.7f, factionInfoHorizontal.RectTransform, scaleBasis: ScaleBasis.Smallest), icon, scaleToFit: true)
{
Color = iconColor
};
var factionTextContent = new GUILayoutGroup(new RectTransform(Vector2.One, factionInfoHorizontal.RectTransform)) var factionTextContent = new GUILayoutGroup(new RectTransform(Vector2.One, factionInfoHorizontal.RectTransform))
{ {
AbsoluteSpacing = GUI.IntScale(10), AbsoluteSpacing = GUI.IntScale(10),
Stretch = true Stretch = true
}; };
var factionIcon = new GUIImage(new RectTransform(Vector2.One * 0.7f, factionInfoHorizontal.RectTransform, scaleBasis: ScaleBasis.Smallest), icon, scaleToFit: true)
{
Color = iconColor
};
factionInfoHorizontal.Recalculate(); factionInfoHorizontal.Recalculate();
var header = new GUITextBlock(new RectTransform(new Point(factionTextContent.Rect.Width, GUI.IntScale(40)), factionTextContent.RectTransform), var header = new GUITextBlock(new RectTransform(new Point(factionTextContent.Rect.Width, GUI.IntScale(40)), factionTextContent.RectTransform),
@@ -733,24 +740,30 @@ namespace Barotrauma
factionTextContent.Recalculate(); factionTextContent.Recalculate();
new GUICustomComponent(new RectTransform(new Vector2(0.8f, 1.0f), sliderHolder.RectTransform), new GUICustomComponent(new RectTransform(new Vector2(0.8f, 1.0f), sliderHolder.RectTransform),
onDraw: (sb, customComponent) => DrawReputationBar(sb, customComponent.Rect, normalizedReputation)); onDraw: (sb, customComponent) => DrawReputationBar(sb, customComponent.Rect, reputation.NormalizedValue));
LocalizedString reputationText = Reputation.GetFormattedReputationText(normalizedReputation, reputation, addColorTags: true); var reputationText = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), sliderHolder.RectTransform),
int reputationChange = (int)Math.Round(reputation - initialReputation); string.Empty, textAlignment: Alignment.CenterLeft, font: GUIStyle.SubHeadingFont);
if (Math.Abs(reputationChange) > 0) SetReputationText(reputationText);
reputation?.OnReputationValueChanged.RegisterOverwriteExisting("RefreshRoundSummary".ToIdentifier(), _ =>
{ {
string changeText = $"{(reputationChange > 0 ? "+" : "") + reputationChange}"; SetReputationText(reputationText);
string colorStr = XMLExtensions.ToStringHex(reputationChange > 0 ? GUIStyle.Green : GUIStyle.Red); });
var richText = RichString.Rich($"{reputationText} (‖color:{colorStr}‖{changeText}‖color:end‖)");
new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), sliderHolder.RectTransform), void SetReputationText(GUITextBlock textBlock)
richText,
textAlignment: Alignment.CenterLeft, font: GUIStyle.SubHeadingFont);
}
else
{ {
new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), sliderHolder.RectTransform), LocalizedString reputationText = Reputation.GetFormattedReputationText(reputation.NormalizedValue, reputation.Value, addColorTags: true);
RichString.Rich(reputationText), int reputationChange = (int)Math.Round(reputation.Value - initialReputation);
textAlignment: Alignment.CenterLeft, font: GUIStyle.SubHeadingFont); if (Math.Abs(reputationChange) > 0)
{
string changeText = $"{(reputationChange > 0 ? "+" : "") + reputationChange}";
string colorStr = XMLExtensions.ToStringHex(reputationChange > 0 ? GUIStyle.Green : GUIStyle.Red);
textBlock.Text = RichString.Rich($"{reputationText} (‖color:{colorStr}‖{changeText}‖color:end‖)");
}
else
{
textBlock.Text = RichString.Rich(reputationText);
}
} }
//spacing //spacing
@@ -63,7 +63,6 @@ namespace Barotrauma
public Vector2[] SlotPositions; public Vector2[] SlotPositions;
public static Point SlotSize; public static Point SlotSize;
public static int Spacing;
private Layout layout; private Layout layout;
public Layout CurrentLayout public Layout CurrentLayout
@@ -103,7 +102,7 @@ namespace Barotrauma
{ {
visualSlots ??= new VisualSlot[capacity]; visualSlots ??= new VisualSlot[capacity];
float multiplier = !GUI.IsFourByThree() ? UIScale : UIScale * 0.925f; float multiplier = UIScale * GUI.AspectRatioAdjustment;
for (int i = 0; i < capacity; i++) for (int i = 0; i < capacity; i++)
{ {
@@ -219,18 +218,11 @@ namespace Barotrauma
private void SetSlotPositions(Layout layout) private void SetSlotPositions(Layout layout)
{ {
bool isFourByThree = GUI.IsFourByThree(); int spacing = GUI.IntScale(5);
if (isFourByThree)
{
Spacing = (int)(5 * UIScale);
}
else
{
Spacing = (int)(8 * UIScale);
}
SlotSize = !isFourByThree ? (SlotSpriteSmall.size * UIScale).ToPoint() : (SlotSpriteSmall.size * UIScale * .925f).ToPoint(); SlotSize = (SlotSpriteSmall.size * UIScale * GUI.AspectRatioAdjustment).ToPoint();
int bottomOffset = SlotSize.Y + Spacing * 2 + ContainedIndicatorHeight; int bottomOffset = SlotSize.Y + spacing * 2 + ContainedIndicatorHeight;
int personalSlotY = GameMain.GraphicsHeight - bottomOffset * 2 - spacing * 2 - (int)(UnequippedIndicator.size.Y * UIScale);
if (visualSlots == null) { CreateSlots(); } if (visualSlots == null) { CreateSlots(); }
if (visualSlots.None()) { return; } if (visualSlots.None()) { return; }
@@ -242,11 +234,11 @@ namespace Barotrauma
int personalSlotCount = SlotTypes.Count(s => PersonalSlots.HasFlag(s)); int personalSlotCount = SlotTypes.Count(s => PersonalSlots.HasFlag(s));
int normalSlotCount = SlotTypes.Count(s => !PersonalSlots.HasFlag(s) && s != InvSlotType.HealthInterface); int normalSlotCount = SlotTypes.Count(s => !PersonalSlots.HasFlag(s) && s != InvSlotType.HealthInterface);
int x = GameMain.GraphicsWidth / 2 - normalSlotCount * (SlotSize.X + Spacing) / 2; int x = GameMain.GraphicsWidth / 2 - normalSlotCount * (SlotSize.X + spacing) / 2;
int upperX = HUDLayoutSettings.BottomRightInfoArea.X - SlotSize.X - Spacing; int upperX = HUDLayoutSettings.BottomRightInfoArea.X - SlotSize.X - spacing;
//make sure the rightmost normal slot doesn't overlap with the personal slots //make sure the rightmost normal slot doesn't overlap with the personal slots
x -= Math.Max((x + normalSlotCount * (SlotSize.X + Spacing)) - (upperX - personalSlotCount * (SlotSize.X + Spacing)), 0); x -= Math.Max((x + normalSlotCount * (SlotSize.X + spacing)) - (upperX - personalSlotCount * (SlotSize.X + spacing)), 0);
int hideButtonSlotIndex = -1; int hideButtonSlotIndex = -1;
for (int i = 0; i < SlotPositions.Length; i++) for (int i = 0; i < SlotPositions.Length; i++)
@@ -254,7 +246,7 @@ namespace Barotrauma
if (PersonalSlots.HasFlag(SlotTypes[i])) if (PersonalSlots.HasFlag(SlotTypes[i]))
{ {
SlotPositions[i] = new Vector2(upperX, GameMain.GraphicsHeight - bottomOffset); SlotPositions[i] = new Vector2(upperX, GameMain.GraphicsHeight - bottomOffset);
upperX -= SlotSize.X + Spacing; upperX -= SlotSize.X + spacing;
personalSlotArea = (hideButtonSlotIndex == -1) ? personalSlotArea = (hideButtonSlotIndex == -1) ?
new Rectangle(SlotPositions[i].ToPoint(), SlotSize) : new Rectangle(SlotPositions[i].ToPoint(), SlotSize) :
Rectangle.Union(personalSlotArea, new Rectangle(SlotPositions[i].ToPoint(), SlotSize)); Rectangle.Union(personalSlotArea, new Rectangle(SlotPositions[i].ToPoint(), SlotSize));
@@ -263,7 +255,7 @@ namespace Barotrauma
else else
{ {
SlotPositions[i] = new Vector2(x, GameMain.GraphicsHeight - bottomOffset); SlotPositions[i] = new Vector2(x, GameMain.GraphicsHeight - bottomOffset);
x += SlotSize.X + Spacing; x += SlotSize.X + spacing;
} }
} }
} }
@@ -271,7 +263,7 @@ namespace Barotrauma
case Layout.Right: case Layout.Right:
{ {
int x = HUDLayoutSettings.InventoryAreaLower.Right; int x = HUDLayoutSettings.InventoryAreaLower.Right;
int personalSlotX = HUDLayoutSettings.InventoryAreaLower.Right - SlotSize.X - Spacing; int personalSlotX = HUDLayoutSettings.InventoryAreaLower.Right - SlotSize.X - spacing;
for (int i = 0; i < visualSlots.Length; i++) for (int i = 0; i < visualSlots.Length; i++)
{ {
if (HideSlot(i) || SlotTypes[i] == InvSlotType.HealthInterface) { continue; } if (HideSlot(i) || SlotTypes[i] == InvSlotType.HealthInterface) { continue; }
@@ -282,19 +274,18 @@ namespace Barotrauma
} }
else else
{ {
x -= SlotSize.X + Spacing; x -= SlotSize.X + spacing;
} }
} }
int lowerX = x; int lowerX = x;
int handSlotX = x; int handSlotX = x;
int personalSlotY = GameMain.GraphicsHeight - bottomOffset * 2 - Spacing * 2 - (int)(!GUI.IsFourByThree() ? UnequippedIndicator.size.Y * UIScale * IndicatorScaleAdjustment : UnequippedIndicator.size.Y * UIScale * IndicatorScaleAdjustment * 2f);
for (int i = 0; i < SlotPositions.Length; i++) for (int i = 0; i < SlotPositions.Length; i++)
{ {
if (SlotTypes[i] == InvSlotType.RightHand || SlotTypes[i] == InvSlotType.LeftHand) if (SlotTypes[i] == InvSlotType.RightHand || SlotTypes[i] == InvSlotType.LeftHand)
{ {
SlotPositions[i] = new Vector2(handSlotX, personalSlotY); SlotPositions[i] = new Vector2(handSlotX, personalSlotY);
handSlotX += visualSlots[i].Rect.Width + Spacing; handSlotX += visualSlots[i].Rect.Width + spacing;
continue; continue;
} }
@@ -302,12 +293,12 @@ namespace Barotrauma
if (PersonalSlots.HasFlag(SlotTypes[i])) if (PersonalSlots.HasFlag(SlotTypes[i]))
{ {
SlotPositions[i] = new Vector2(personalSlotX, personalSlotY); SlotPositions[i] = new Vector2(personalSlotX, personalSlotY);
personalSlotX -= visualSlots[i].Rect.Width + Spacing; personalSlotX -= visualSlots[i].Rect.Width + spacing;
} }
else else
{ {
SlotPositions[i] = new Vector2(x, GameMain.GraphicsHeight - bottomOffset); SlotPositions[i] = new Vector2(x, GameMain.GraphicsHeight - bottomOffset);
x += visualSlots[i].Rect.Width + Spacing; x += visualSlots[i].Rect.Width + spacing;
} }
} }
@@ -316,7 +307,7 @@ namespace Barotrauma
{ {
if (!HideSlot(i) || SlotTypes[i] == InvSlotType.HealthInterface) { continue; } if (!HideSlot(i) || SlotTypes[i] == InvSlotType.HealthInterface) { continue; }
if (SlotTypes[i] == InvSlotType.RightHand || SlotTypes[i] == InvSlotType.LeftHand) { continue; } if (SlotTypes[i] == InvSlotType.RightHand || SlotTypes[i] == InvSlotType.LeftHand) { continue; }
x -= visualSlots[i].Rect.Width + Spacing; x -= visualSlots[i].Rect.Width + spacing;
SlotPositions[i] = new Vector2(x, GameMain.GraphicsHeight - bottomOffset); SlotPositions[i] = new Vector2(x, GameMain.GraphicsHeight - bottomOffset);
} }
} }
@@ -325,7 +316,6 @@ namespace Barotrauma
{ {
int x = HUDLayoutSettings.InventoryAreaLower.X; int x = HUDLayoutSettings.InventoryAreaLower.X;
int personalSlotX = x; int personalSlotX = x;
int personalSlotY = GameMain.GraphicsHeight - bottomOffset * 2 - Spacing * 2 - (int)(!GUI.IsFourByThree() ? UnequippedIndicator.size.Y * UIScale * IndicatorScaleAdjustment : UnequippedIndicator.size.Y * UIScale * IndicatorScaleAdjustment * 2f);
for (int i = 0; i < SlotPositions.Length; i++) for (int i = 0; i < SlotPositions.Length; i++)
{ {
@@ -334,33 +324,33 @@ namespace Barotrauma
if (PersonalSlots.HasFlag(SlotTypes[i])) if (PersonalSlots.HasFlag(SlotTypes[i]))
{ {
SlotPositions[i] = new Vector2(personalSlotX, personalSlotY); SlotPositions[i] = new Vector2(personalSlotX, personalSlotY);
personalSlotX += visualSlots[i].Rect.Width + Spacing; personalSlotX += visualSlots[i].Rect.Width + spacing;
} }
else else
{ {
SlotPositions[i] = new Vector2(x, GameMain.GraphicsHeight - bottomOffset); SlotPositions[i] = new Vector2(x, GameMain.GraphicsHeight - bottomOffset);
x += visualSlots[i].Rect.Width + Spacing; x += visualSlots[i].Rect.Width + spacing;
} }
} }
int handSlotX = x - visualSlots[0].Rect.Width - Spacing; int handSlotX = x - visualSlots[0].Rect.Width - spacing;
for (int i = 0; i < SlotPositions.Length; i++) for (int i = 0; i < SlotPositions.Length; i++)
{ {
if (SlotTypes[i] == InvSlotType.RightHand || SlotTypes[i] == InvSlotType.LeftHand) if (SlotTypes[i] == InvSlotType.RightHand || SlotTypes[i] == InvSlotType.LeftHand)
{ {
bool rightSlot = SlotTypes[i] == InvSlotType.RightHand; bool rightSlot = SlotTypes[i] == InvSlotType.RightHand;
SlotPositions[i] = new Vector2(rightSlot ? handSlotX : handSlotX - visualSlots[0].Rect.Width - Spacing, personalSlotY); SlotPositions[i] = new Vector2(rightSlot ? handSlotX : handSlotX - visualSlots[0].Rect.Width - spacing, personalSlotY);
continue; continue;
} }
if (!HideSlot(i) || SlotTypes[i] == InvSlotType.HealthInterface) { continue; } if (!HideSlot(i) || SlotTypes[i] == InvSlotType.HealthInterface) { continue; }
SlotPositions[i] = new Vector2(x, GameMain.GraphicsHeight - bottomOffset); SlotPositions[i] = new Vector2(x, GameMain.GraphicsHeight - bottomOffset);
x += visualSlots[i].Rect.Width + Spacing; x += visualSlots[i].Rect.Width + spacing;
} }
} }
break; break;
case Layout.Center: case Layout.Center:
{ {
int columns = 5; int columns = 5;
int startX = (GameMain.GraphicsWidth / 2) - (SlotSize.X * columns + Spacing * (columns - 1)) / 2; int startX = (GameMain.GraphicsWidth / 2) - (SlotSize.X * columns + spacing * (columns - 1)) / 2;
int startY = GameMain.GraphicsHeight / 2 - (SlotSize.Y * 2); int startY = GameMain.GraphicsHeight / 2 - (SlotSize.Y * 2);
int x = startX, y = startY; int x = startX, y = startY;
for (int i = 0; i < SlotPositions.Length; i++) for (int i = 0; i < SlotPositions.Length; i++)
@@ -369,10 +359,10 @@ namespace Barotrauma
if (SlotTypes[i] == InvSlotType.Card || SlotTypes[i] == InvSlotType.Headset || SlotTypes[i] == InvSlotType.InnerClothes) if (SlotTypes[i] == InvSlotType.Card || SlotTypes[i] == InvSlotType.Headset || SlotTypes[i] == InvSlotType.InnerClothes)
{ {
SlotPositions[i] = new Vector2(x, y); SlotPositions[i] = new Vector2(x, y);
x += visualSlots[i].Rect.Width + Spacing; x += visualSlots[i].Rect.Width + spacing;
} }
} }
y += visualSlots[0].Rect.Height + Spacing + ContainedIndicatorHeight + visualSlots[0].EquipButtonRect.Height; y += visualSlots[0].Rect.Height + spacing + ContainedIndicatorHeight + visualSlots[0].EquipButtonRect.Height;
x = startX; x = startX;
int n = 0; int n = 0;
for (int i = 0; i < SlotPositions.Length; i++) for (int i = 0; i < SlotPositions.Length; i++)
@@ -381,12 +371,12 @@ namespace Barotrauma
if (SlotTypes[i] != InvSlotType.Card && SlotTypes[i] != InvSlotType.Headset && SlotTypes[i] != InvSlotType.InnerClothes) if (SlotTypes[i] != InvSlotType.Card && SlotTypes[i] != InvSlotType.Headset && SlotTypes[i] != InvSlotType.InnerClothes)
{ {
SlotPositions[i] = new Vector2(x, y); SlotPositions[i] = new Vector2(x, y);
x += visualSlots[i].Rect.Width + Spacing; x += visualSlots[i].Rect.Width + spacing;
n++; n++;
if (n >= columns) if (n >= columns)
{ {
x = startX; x = startX;
y += visualSlots[i].Rect.Height + Spacing + ContainedIndicatorHeight + visualSlots[i].EquipButtonRect.Height; y += visualSlots[i].Rect.Height + spacing + ContainedIndicatorHeight + visualSlots[i].EquipButtonRect.Height;
n = 0; n = 0;
} }
} }
@@ -402,7 +392,7 @@ namespace Barotrauma
{ {
if (SlotTypes[i] != InvSlotType.HealthInterface) { continue; } if (SlotTypes[i] != InvSlotType.HealthInterface) { continue; }
SlotPositions[i] = pos; SlotPositions[i] = pos;
pos.Y += visualSlots[i].Rect.Height + Spacing; pos.Y += visualSlots[i].Rect.Height + spacing;
} }
} }
@@ -641,7 +631,7 @@ namespace Barotrauma
{ {
slot.EquipButtonState = slot.EquipButtonRect.Contains(PlayerInput.MousePosition) ? slot.EquipButtonState = slot.EquipButtonRect.Contains(PlayerInput.MousePosition) ?
GUIComponent.ComponentState.Hover : GUIComponent.ComponentState.None; GUIComponent.ComponentState.Hover : GUIComponent.ComponentState.None;
if (PlayerInput.LeftButtonHeld() && PlayerInput.RightButtonHeld()) if (PlayerInput.PrimaryMouseButtonHeld() && PlayerInput.SecondaryMouseButtonHeld())
{ {
slot.EquipButtonState = GUIComponent.ComponentState.None; slot.EquipButtonState = GUIComponent.ComponentState.None;
} }
@@ -1019,6 +1009,46 @@ namespace Barotrauma
} }
} }
public bool CanBeAutoMovedToCorrectSlots(Item item)
{
if (item == null) { return false; }
foreach (var allowedSlot in item.AllowedSlots)
{
InvSlotType slotsFree = InvSlotType.None;
for (int i = 0; i < slots.Length; i++)
{
if (allowedSlot.HasFlag(SlotTypes[i]) && slots[i].Empty()) { slotsFree |= SlotTypes[i]; }
}
if (allowedSlot == slotsFree) { return true; }
}
return false;
}
/// <summary>
/// Flash the slots the item is allowed to go in (not taking into account whether there's already something in those slots)
/// </summary>
public void FlashAllowedSlots(Item item, Color color)
{
if (item == null || visualSlots == null) { return; }
bool flashed = false;
foreach (var allowedSlot in item.AllowedSlots)
{
for (int i = 0; i < slots.Length; i++)
{
if (allowedSlot.HasFlag(SlotTypes[i]))
{
visualSlots[i].ShowBorderHighlight(color, 0.1f, 0.9f);
flashed = true;
}
}
}
if (flashed)
{
SoundPlayer.PlayUISound(GUISoundType.PickItemFail);
}
}
public void DrawOwn(SpriteBatch spriteBatch) public void DrawOwn(SpriteBatch spriteBatch)
{ {
if (!AccessibleWhenAlive && !character.IsDead && !AccessibleByOwner) { return; } if (!AccessibleWhenAlive && !character.IsDead && !AccessibleByOwner) { return; }
@@ -1106,40 +1136,24 @@ namespace Barotrauma
color *= 0.5f; color *= 0.5f;
} }
if (character.HasEquippedItem(slots[i].First())) Vector2 indicatorScale = new Vector2(
visualSlots[i].EquipButtonRect.Size.X / EquippedIndicator.size.X,
visualSlots[i].EquipButtonRect.Size.Y / EquippedIndicator.size.Y);
bool isEquipped = character.HasEquippedItem(slots[i].First());
var sprite = state switch
{ {
switch (state) GUIComponent.ComponentState.None
{ => isEquipped ? EquippedIndicator : UnequippedIndicator,
case GUIComponent.ComponentState.None: GUIComponent.ComponentState.Hover
EquippedIndicator.Draw(spriteBatch, visualSlots[i].EquipButtonRect.Center.ToVector2(), color, EquippedIndicator.Origin, 0, UIScale * IndicatorScaleAdjustment); => isEquipped ? EquippedHoverIndicator : UnequippedHoverIndicator,
break; GUIComponent.ComponentState.Pressed
case GUIComponent.ComponentState.Hover: or GUIComponent.ComponentState.Selected
EquippedHoverIndicator.Draw(spriteBatch, visualSlots[i].EquipButtonRect.Center.ToVector2(), color, EquippedIndicator.Origin, 0, UIScale * IndicatorScaleAdjustment); or GUIComponent.ComponentState.HoverSelected
break; => isEquipped ? EquippedClickedIndicator : UnequippedClickedIndicator,
case GUIComponent.ComponentState.Pressed: _ => throw new NotImplementedException()
case GUIComponent.ComponentState.Selected: };
case GUIComponent.ComponentState.HoverSelected: sprite.Draw(spriteBatch, visualSlots[i].EquipButtonRect.Center.ToVector2(), color, EquippedIndicator.Origin, 0, indicatorScale);
EquippedClickedIndicator.Draw(spriteBatch, visualSlots[i].EquipButtonRect.Center.ToVector2(), color, EquippedIndicator.Origin, 0, UIScale * IndicatorScaleAdjustment);
break;
}
}
else
{
switch (state)
{
case GUIComponent.ComponentState.None:
UnequippedIndicator.Draw(spriteBatch, visualSlots[i].EquipButtonRect.Center.ToVector2(), color, EquippedIndicator.Origin, 0, UIScale * IndicatorScaleAdjustment);
break;
case GUIComponent.ComponentState.Hover:
UnequippedHoverIndicator.Draw(spriteBatch, visualSlots[i].EquipButtonRect.Center.ToVector2(), color, EquippedIndicator.Origin, 0, UIScale * IndicatorScaleAdjustment);
break;
case GUIComponent.ComponentState.Pressed:
case GUIComponent.ComponentState.Selected:
case GUIComponent.ComponentState.HoverSelected:
UnequippedClickedIndicator.Draw(spriteBatch, visualSlots[i].EquipButtonRect.Center.ToVector2(), color, EquippedIndicator.Origin, 0, UIScale * IndicatorScaleAdjustment);
break;
}
}
} }
if (Locked) if (Locked)
@@ -92,9 +92,6 @@ namespace Barotrauma.Items.Components
rect.Height = (int)(rect.Height * (1.0f - openState)); rect.Height = (int)(rect.Height * (1.0f - openState));
} }
//only merge the door's convex hull with overlapping wall segments if it's fully open or fully closed
//it's the heaviest part of changing the convex hull, and doesn't need to be done while the door is still in motion
bool mergeOverlappingSegments = openState <= 0.0f || openState >= 1.0f;
if (Window.Height > 0 && Window.Width > 0) if (Window.Height > 0 && Window.Width > 0)
{ {
if (IsHorizontal) if (IsHorizontal)
@@ -117,7 +114,7 @@ namespace Barotrauma.Items.Components
else else
{ {
convexHull2.Enabled = true; convexHull2.Enabled = true;
convexHull2.SetVertices(GetConvexHullCorners(rect2), mergeOverlappingSegments); SetVertices(convexHull2, rect2);
} }
} }
} }
@@ -141,7 +138,7 @@ namespace Barotrauma.Items.Components
else else
{ {
convexHull2.Enabled = true; convexHull2.Enabled = true;
convexHull2.SetVertices(GetConvexHullCorners(rect2), mergeOverlappingSegments); SetVertices(convexHull2, rect2);
} }
} }
} }
@@ -156,13 +153,28 @@ namespace Barotrauma.Items.Components
else else
{ {
convexHull.Enabled = true; convexHull.Enabled = true;
convexHull.SetVertices(GetConvexHullCorners(rect), mergeOverlappingSegments); SetVertices(convexHull, rect);
} }
convexHull.IsExteriorWall = !linkedGap.IsRoomToRoom;
if (convexHull2 != null) { convexHull2.IsExteriorWall = convexHull.IsExteriorWall; }
} }
private void SetVertices(ConvexHull convexHull, Rectangle rect)
{
var verts = GetConvexHullCorners(rect);
Vector2 center = (verts[0] + verts[2]) / 2;
convexHull.SetVertices(
verts,
IsHorizontal ?
new Vector2[] { new Vector2(verts[0].X, center.Y), new Vector2(verts[2].X, center.Y) } :
new Vector2[] { new Vector2(center.X, verts[0].Y), new Vector2(center.X, verts[2].Y) });
}
partial void UpdateProjSpecific(float deltaTime) partial void UpdateProjSpecific(float deltaTime)
{ {
convexHull.IsExteriorWall = !linkedGap.IsRoomToRoom;
if (convexHull2 != null) { convexHull2.IsExteriorWall = convexHull.IsExteriorWall; }
if (shakeTimer > 0.0f) if (shakeTimer > 0.0f)
{ {
shakeTimer -= deltaTime; shakeTimer -= deltaTime;
@@ -182,7 +194,7 @@ namespace Barotrauma.Items.Components
if (brokenSprite == null) if (brokenSprite == null)
{ {
//broken doors turn black if no broken sprite has been configured //broken doors turn black if no broken sprite has been configured
color *= (item.Condition / item.MaxCondition); color = color.Multiply(item.Condition / item.MaxCondition);
color.A = 255; color.A = 255;
} }
@@ -88,6 +88,11 @@ namespace Barotrauma.Items.Components
public void ClientEventRead(IReadMessage msg, float sendingTime) public void ClientEventRead(IReadMessage msg, float sendingTime)
{ {
UInt16 userID = msg.ReadUInt16();
if (userID != Entity.NullEntityID)
{
user = Entity.FindEntityByID(userID) as Character;
}
CurrPowerConsumption = powerConsumption; CurrPowerConsumption = powerConsumption;
charging = true; charging = true;
timer = Duration; timer = Duration;
@@ -1,13 +1,10 @@
using Barotrauma.Particles; using Barotrauma.Particles;
using Barotrauma.Sounds;
using FarseerPhysics; using FarseerPhysics;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Barotrauma.IO;
using System.Text;
using System.Xml.Linq;
using Barotrauma.Sounds;
using System.Linq; using System.Linq;
namespace Barotrauma.Items.Components namespace Barotrauma.Items.Components
@@ -169,11 +166,11 @@ namespace Barotrauma.Items.Components
partial void LaunchProjSpecific() partial void LaunchProjSpecific()
{ {
Vector2 particlePos = item.WorldPosition + ConvertUnits.ToDisplayUnits(TransformedBarrelPos); Vector2 particlePos = item.WorldPosition + ConvertUnits.ToDisplayUnits(TransformedBarrelPos);
float rotation = -item.body.Rotation; float rotation = item.body.Rotation;
if (item.body.Dir < 0.0f) { rotation += MathHelper.Pi; } if (item.body.Dir < 0.0f) { rotation += MathHelper.Pi; }
foreach (ParticleEmitter emitter in particleEmitters) foreach (ParticleEmitter emitter in particleEmitters)
{ {
emitter.Emit(1.0f, particlePos, hullGuess: item.CurrentHull, angle: rotation, particleRotation: rotation); emitter.Emit(1.0f, particlePos, hullGuess: item.CurrentHull, angle: rotation, particleRotation: -rotation);
} }
} }
@@ -505,13 +505,14 @@ namespace Barotrauma.Items.Components
} }
ActionType type; ActionType type;
string typeStr = subElement.GetAttributeString("type", "");
try try
{ {
type = (ActionType)Enum.Parse(typeof(ActionType), subElement.GetAttributeString("type", ""), true); type = (ActionType)Enum.Parse(typeof(ActionType), typeStr, true);
} }
catch (Exception e) catch (Exception e)
{ {
DebugConsole.ThrowError("Invalid sound type in " + subElement + "!", e); DebugConsole.ThrowError($"Invalid sound type \"{typeStr}\" in item \"{item.Prefab.Identifier}\"!", e);
break; break;
} }
@@ -524,11 +525,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 +587,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 +628,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 +653,12 @@ namespace Barotrauma.Items.Components
return true; return true;
} }
}; };
if (hideDragIcons)
{
dragIcon.Visible = false;
settingsIcon.Visible = false;
}
} }
} }
@@ -277,7 +277,8 @@ namespace Barotrauma.Items.Components
int ignoredItemCount = 0; int ignoredItemCount = 0;
var subContainableItems = AllSubContainableItems; var subContainableItems = AllSubContainableItems;
float capacity = GetMaxStackSize(targetSlot); float targetSlotCapacity = GetMaxStackSize(targetSlot);
float capacity = targetSlotCapacity * MainContainerCapacity;
if (subContainableItems != null) if (subContainableItems != null)
{ {
bool useMainContainerCapacity = true; bool useMainContainerCapacity = true;
@@ -299,15 +300,11 @@ namespace Barotrauma.Items.Components
} }
if (!useMainContainerCapacity) { break; } if (!useMainContainerCapacity) { break; }
} }
if (useMainContainerCapacity) if (!useMainContainerCapacity)
{
capacity *= MainContainerCapacity;
}
else
{ {
// Ignore all items in the main container. // Ignore all items in the main container.
ignoredItemCount = Inventory.AllItems.Count(it => subContainableItems.Any(ri => !ri.MatchesItem(it))); ignoredItemCount = Inventory.AllItems.Count(it => subContainableItems.Any(ri => !ri.MatchesItem(it)));
capacity *= Capacity - MainContainerCapacity; capacity = targetSlotCapacity * (Capacity - MainContainerCapacity);
} }
} }
int itemCount = Inventory.AllItems.Count() - ignoredItemCount; int itemCount = Inventory.AllItems.Count() - ignoredItemCount;
@@ -391,63 +388,60 @@ namespace Barotrauma.Items.Components
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 (DrawableContainedItem contained in drawableContainedItems)
{ {
Vector2 itemPos = currentItemPos; Vector2 itemPos = currentItemPos;
var relatedItem = FindContainableItem(containedItem);
if (relatedItem != null) if (contained.Item?.Sprite == null) { continue; }
if (contained.Hide) { continue; }
if (contained.ItemPos.HasValue)
{ {
if (relatedItem.Hide.HasValue && relatedItem.Hide.Value) { continue; } Vector2 pos = contained.ItemPos.Value;
if (relatedItem.ItemPos.HasValue) if (item.body != null)
{ {
Vector2 pos = relatedItem.ItemPos.Value; Matrix transform = Matrix.CreateRotationZ(item.body.DrawRotation);
if (item.body != null) 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)
{ {
Matrix transform = Matrix.CreateRotationZ(item.body.DrawRotation); itemPos.X = -itemPos.X;
pos.X *= item.body.Dir; itemPos.X += item.Rect.Width;
itemPos = Vector2.Transform(pos, transform) + item.body.DrawPosition;
} }
else if (item.FlippedY)
{ {
itemPos = pos; itemPos.Y = -itemPos.Y;
// This code is aped based on above. Not tested. itemPos.Y -= item.Rect.Height;
if (item.FlippedX) }
{ itemPos += new Vector2(item.Rect.X, item.Rect.Y);
itemPos.X = -itemPos.X; if (item.Submarine != null)
itemPos.X += item.Rect.Width; {
} itemPos += item.Submarine.DrawPosition;
if (item.FlippedY) }
{ if (Math.Abs(item.RotationRad) > 0.01f)
itemPos.Y = -itemPos.Y; {
itemPos.Y -= item.Rect.Height; Matrix transform = Matrix.CreateRotationZ(-item.RotationRad);
} itemPos = Vector2.Transform(itemPos - item.DrawPosition, transform) + item.DrawPosition;
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 (AutoInteractWithContained) if (AutoInteractWithContained)
{ {
containedItem.IsHighlighted = item.IsHighlighted; contained.Item.IsHighlighted = item.IsHighlighted;
item.IsHighlighted = false; item.IsHighlighted = false;
} }
Vector2 origin = containedItem.Sprite.Origin; Vector2 origin = contained.Item.Sprite.Origin;
if (item.FlippedX) { origin.X = containedItem.Sprite.SourceRect.Width - origin.X; } if (item.FlippedX) { origin.X = contained.Item.Sprite.SourceRect.Width - origin.X; }
if (item.FlippedY) { origin.Y = containedItem.Sprite.SourceRect.Height - origin.Y; } if (item.FlippedY) { origin.Y = contained.Item.Sprite.SourceRect.Height - origin.Y; }
float containedSpriteDepth = ContainedSpriteDepth < 0.0f ? containedItem.Sprite.Depth : ContainedSpriteDepth; float containedSpriteDepth = ContainedSpriteDepth < 0.0f ? contained.Item.Sprite.Depth : ContainedSpriteDepth;
if (i < containedSpriteDepths.Length) if (i < containedSpriteDepths.Length)
{ {
containedSpriteDepth = containedSpriteDepths[i]; containedSpriteDepth = containedSpriteDepths[i];
@@ -456,9 +450,9 @@ namespace Barotrauma.Items.Components
SpriteEffects spriteEffects = SpriteEffects.None; SpriteEffects spriteEffects = SpriteEffects.None;
float spriteRotation = ItemRotation; float spriteRotation = ItemRotation;
if (relatedItem != null && relatedItem.Rotation != 0) if (contained.Rotation != 0)
{ {
spriteRotation = relatedItem.Rotation; spriteRotation = contained.Rotation;
} }
if ((item.body != null && item.body.Dir == -1) || item.FlippedX) if ((item.body != null && item.body.Dir == -1) || item.FlippedX)
{ {
@@ -469,17 +463,17 @@ namespace Barotrauma.Items.Components
spriteEffects |= MathUtils.NearlyEqual(spriteRotation % 180, 90.0f) ? SpriteEffects.FlipHorizontally : SpriteEffects.FlipVertically; spriteEffects |= MathUtils.NearlyEqual(spriteRotation % 180, 90.0f) ? SpriteEffects.FlipHorizontally : SpriteEffects.FlipVertically;
} }
containedItem.Sprite.Draw( contained.Item.Sprite.Draw(
spriteBatch, spriteBatch,
new Vector2(itemPos.X, -itemPos.Y), new Vector2(itemPos.X, -itemPos.Y),
isWiringMode ? containedItem.GetSpriteColor(withHighlight: true) * 0.15f : containedItem.GetSpriteColor(withHighlight: true), isWiringMode ? contained.Item.GetSpriteColor(withHighlight: true) * 0.15f : contained.Item.GetSpriteColor(withHighlight: true),
origin, origin,
-(containedItem.body == null ? 0.0f : containedItem.body.DrawRotation), -(contained.Item.body == null ? 0.0f : contained.Item.body.DrawRotation),
containedItem.Scale, contained.Item.Scale,
spriteEffects, spriteEffects,
depth: containedSpriteDepth); depth: containedSpriteDepth);
foreach (ItemContainer ic in containedItem.GetComponents<ItemContainer>()) foreach (ItemContainer ic in contained.Item.GetComponents<ItemContainer>())
{ {
if (ic.hideItems) { continue; } if (ic.hideItems) { continue; }
ic.DrawContainedItems(spriteBatch, containedSpriteDepth); ic.DrawContainedItems(spriteBatch, containedSpriteDepth);
@@ -14,6 +14,16 @@ namespace Barotrauma.Items.Components
private CoroutineHandle resetPredictionCoroutine; private CoroutineHandle resetPredictionCoroutine;
private float resetPredictionTimer; private float resetPredictionTimer;
/// <summary>
/// The current multiplier for the light color (usually equal to <see cref="lightBrightness"/>, but in the case of e.g. blinking lights the multiplier
/// doesn't go to 0 when the light turns off, because otherwise it'd take a while for it turn back on based on the lightBrightness which is interpolated
/// towards the current voltage).
/// </summary>
private float lightColorMultiplier;
[Serialize(1.0f, IsPropertySaveable.Yes, description: "The scale of the light sprite.")]
public float LightSpriteScale { get; set; }
public Vector2 DrawSize public Vector2 DrawSize
{ {
get { return new Vector2(Light.Range * 2, Light.Range * 2); } get { return new Vector2(Light.Range * 2, Light.Range * 2); }
@@ -27,21 +37,14 @@ namespace Barotrauma.Items.Components
Light.Position = ParentBody != null ? ParentBody.Position : item.Position; Light.Position = ParentBody != null ? ParentBody.Position : item.Position;
} }
partial void SetLightSourceState(bool enabled, float? brightness) partial void SetLightSourceState(bool enabled, float brightness)
{ {
if (Light == null) { return; } if (Light == null) { return; }
Light.Enabled = enabled; Light.Enabled = enabled;
if (brightness.HasValue) lightColorMultiplier = brightness;
{
lightBrightness = brightness.Value;
}
else
{
lightBrightness = enabled ? 1.0f : 0.0f;
}
if (enabled) if (enabled)
{ {
Light.Color = LightColor.Multiply(lightBrightness); Light.Color = LightColor.Multiply(lightColorMultiplier);
} }
} }
@@ -92,7 +95,13 @@ namespace Barotrauma.Items.Components
{ {
color = new Color(lightColor, Light.OverrideLightSpriteAlpha.Value); color = new Color(lightColor, Light.OverrideLightSpriteAlpha.Value);
} }
Light.LightSprite.Draw(spriteBatch, new Vector2(drawPos.X, -drawPos.Y), color * lightBrightness, origin, -Light.Rotation, item.Scale, Light.LightSpriteEffect, itemDepth - 0.0001f); Light.LightSprite.Draw(spriteBatch,
new Vector2(drawPos.X, -drawPos.Y),
color * lightBrightness,
origin,
-Light.Rotation,
item.Scale * LightSpriteScale,
Light.LightSpriteEffect, itemDepth - 0.0001f);
} }
} }
@@ -185,6 +185,7 @@ namespace Barotrauma.Items.Components
RefreshActivateButtonText(); RefreshActivateButtonText();
if (GameMain.Client != null) if (GameMain.Client != null)
{ {
pendingFabricatedItem = null;
item.CreateClientEvent(this); item.CreateClientEvent(this);
} }
return true; return true;
@@ -336,8 +337,11 @@ namespace Barotrauma.Items.Components
int calculatePlacement(FabricationRecipe recipe) int calculatePlacement(FabricationRecipe recipe)
{ {
if (recipe.RequiresRecipe && !AnyOneHasRecipeForItem(character, recipe.TargetItem))
{
return -2;
}
int placement = FabricationDegreeOfSuccess(character, recipe.RequiredSkills) >= 0.5f ? 0 : -1; int placement = FabricationDegreeOfSuccess(character, recipe.RequiredSkills) >= 0.5f ? 0 : -1;
placement += recipe.RequiresRecipe && !AnyOneHasRecipeForItem(character, recipe.TargetItem) ? -2 : 0;
return placement; return placement;
} }
@@ -524,7 +528,7 @@ namespace Barotrauma.Items.Components
if (slotRect.Contains(PlayerInput.MousePosition)) if (slotRect.Contains(PlayerInput.MousePosition))
{ {
var suitableIngredients = requiredItem.ItemPrefabs.Select(ip => ip.Name); var suitableIngredients = requiredItem.ItemPrefabs.Select(ip => ip.Name).Distinct();
LocalizedString toolTipText = string.Join(", ", suitableIngredients.Count() > 3 ? suitableIngredients.SkipLast(suitableIngredients.Count() - 3) : suitableIngredients); LocalizedString toolTipText = string.Join(", ", suitableIngredients.Count() > 3 ? suitableIngredients.SkipLast(suitableIngredients.Count() - 3) : suitableIngredients);
if (suitableIngredients.Count() > 3) { toolTipText += "..."; } if (suitableIngredients.Count() > 3) { toolTipText += "..."; }
if (requiredItem.UseCondition && requiredItem.MinCondition < 1.0f) if (requiredItem.UseCondition && requiredItem.MinCondition < 1.0f)
@@ -546,9 +550,11 @@ namespace Barotrauma.Items.Components
{ {
toolTipText = TextManager.GetWithVariable("displayname.emptyitem", "[itemname]", toolTipText); toolTipText = TextManager.GetWithVariable("displayname.emptyitem", "[itemname]", toolTipText);
} }
toolTipText = $"‖color:{Color.White.ToStringHex()}‖{toolTipText}‖color:end‖";
if (!requiredItemPrefab.Description.IsNullOrEmpty()) if (!requiredItemPrefab.Description.IsNullOrEmpty())
{ {
toolTipText += '\n' + requiredItemPrefab.Description; toolTipText = '\n' + requiredItemPrefab.Description;
} }
tooltip = new ToolTip { TargetElement = slotRect, Tooltip = toolTipText }; tooltip = new ToolTip { TargetElement = slotRect, Tooltip = toolTipText };
} }
@@ -590,7 +596,7 @@ namespace Barotrauma.Items.Components
if (tooltip != null) if (tooltip != null)
{ {
GUIComponent.DrawToolTip(spriteBatch, tooltip.Tooltip, tooltip.TargetElement); GUIComponent.DrawToolTip(spriteBatch, RichString.Rich(tooltip.Tooltip), tooltip.TargetElement);
tooltip = null; tooltip = null;
} }
} }
@@ -298,7 +298,7 @@ namespace Barotrauma.Items.Components
} }
} }
OrderPrefab[] reports = OrderPrefab.Prefabs.Where(o => o.IsReport && o.SymbolSprite != null && !o.Hidden).ToArray(); OrderPrefab[] reports = OrderPrefab.Prefabs.Where(o => o.IsReport && o.SymbolSprite != null && !o.Hidden).OrderBy(o => o.Identifier).ToArray();
GUIFrame bottomFrame = new GUIFrame(new RectTransform(new Vector2(0.5f, 0.15f), paddedContainer.RectTransform, Anchor.BottomCenter) { MaxSize = new Point(int.MaxValue, GUI.IntScale(40)) }, style: null) GUIFrame bottomFrame = new GUIFrame(new RectTransform(new Vector2(0.5f, 0.15f), paddedContainer.RectTransform, Anchor.BottomCenter) { MaxSize = new Point(int.MaxValue, GUI.IntScale(40)) }, style: null)
{ {
@@ -452,7 +452,7 @@ namespace Barotrauma.Items.Components
foreach (var (entity, component) in electricalMapComponents) foreach (var (entity, component) in electricalMapComponents)
{ {
GUIComponent parent = component.RectComponent; GUIComponent parent = component.RectComponent;
if (!(entity is Item it )) { continue; } if (entity is not Item it ) { continue; }
Sprite? sprite = it.Prefab.UpgradePreviewSprite; Sprite? sprite = it.Prefab.UpgradePreviewSprite;
if (sprite is null) { continue; } if (sprite is null) { continue; }
@@ -476,7 +476,7 @@ namespace Barotrauma.Items.Components
{ {
if (!hullPointsOfInterest.Contains(entity)) { continue; } if (!hullPointsOfInterest.Contains(entity)) { continue; }
if (!(entity is Item it)) { continue; } if (entity is not Item it) { continue; }
const int borderMaxSize = 2; const int borderMaxSize = 2;
if (it.GetComponent<Door>() is { }) if (it.GetComponent<Door>() is { })
@@ -643,7 +643,7 @@ namespace Barotrauma.Items.Components
elementSize = GuiFrame.Rect.Size; elementSize = GuiFrame.Rect.Size;
} }
float distort = 1.0f - item.Condition / item.MaxCondition; float distort = item.Repairables.Any(r => r.IsBelowRepairThreshold) ? 1.0f - item.Condition / item.MaxCondition : 0.0f;
foreach (HullData hullData in hullDatas.Values) foreach (HullData hullData in hullDatas.Values)
{ {
hullData.DistortionTimer -= deltaTime; hullData.DistortionTimer -= deltaTime;
@@ -702,6 +702,12 @@ namespace Barotrauma.Items.Components
private void DrawHUDFront(SpriteBatch spriteBatch, GUICustomComponent container) private void DrawHUDFront(SpriteBatch spriteBatch, GUICustomComponent container)
{ {
if (miniMapFrame == null)
{
//frame not created yet, could happen if the item hasn't been inside any sub this round?
return;
}
if (Voltage < MinVoltage) if (Voltage < MinVoltage)
{ {
Vector2 textSize = GUIStyle.Font.MeasureString(noPowerTip); Vector2 textSize = GUIStyle.Font.MeasureString(noPowerTip);
@@ -1130,7 +1136,7 @@ namespace Barotrauma.Items.Components
{ {
foreach (var (entity, miniMapGuiComponent) in electricalMapComponents) foreach (var (entity, miniMapGuiComponent) in electricalMapComponents)
{ {
if (!(entity is Item it)) { continue; } if (entity is not Item it) { continue; }
if (!electricalChildren.TryGetValue(miniMapGuiComponent, out GUIComponent? component)) { continue; } if (!electricalChildren.TryGetValue(miniMapGuiComponent, out GUIComponent? component)) { continue; }
if (entity.Removed) if (entity.Removed)
@@ -1220,7 +1226,7 @@ namespace Barotrauma.Items.Components
{ {
foreach (var (entity, component) in hullStatusComponents) foreach (var (entity, component) in hullStatusComponents)
{ {
if (!(entity is Hull hull)) { continue; } if (entity is not Hull hull) { continue; }
if (!hullDatas.TryGetValue(hull, out HullData? hullData) || hullData is null) { continue; } if (!hullDatas.TryGetValue(hull, out HullData? hullData) || hullData is null) { continue; }
if (hullData.Distort) { continue; } if (hullData.Distort) { continue; }
@@ -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 }
@@ -975,7 +980,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,
@@ -985,7 +990,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,
@@ -1010,19 +1015,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++;
} }
@@ -1176,13 +1181,18 @@ namespace Barotrauma.Items.Components
if (dockingPort.Item.Submarine == null) { continue; } if (dockingPort.Item.Submarine == null) { continue; }
if (dockingPort.Item.Submarine.Info.IsWreck) { continue; } if (dockingPort.Item.Submarine.Info.IsWreck) { continue; }
// docking ports should be shown even if defined as not, if the submarine is the same as the sonar's // docking ports should be shown even if defined as not, if the submarine is the same as the sonar's
if (!dockingPort.Item.Submarine.ShowSonarMarker && dockingPort.Item.Submarine != item.Submarine && !dockingPort.Item.Submarine.Info.IsOutpost) { continue; } if (!dockingPort.Item.Submarine.ShowSonarMarker && dockingPort.Item.Submarine != item.Submarine &&
!dockingPort.Item.Submarine.Info.IsOutpost && !dockingPort.Item.Submarine.Info.IsBeacon)
{
continue;
}
//don't show the docking ports of the opposing team on the sonar //don't show the docking ports of the opposing team on the sonar
if (item.Submarine != null && if (item.Submarine != null &&
item.Submarine != GameMain.NetworkMember?.RespawnManager?.RespawnShuttle && item.Submarine != GameMain.NetworkMember?.RespawnManager?.RespawnShuttle &&
dockingPort.Item.Submarine != GameMain.NetworkMember?.RespawnManager?.RespawnShuttle && dockingPort.Item.Submarine != GameMain.NetworkMember?.RespawnManager?.RespawnShuttle &&
dockingPort.Item.Submarine.Info.Type != SubmarineType.Outpost) !dockingPort.Item.Submarine.Info.IsOutpost &&
!dockingPort.Item.Submarine.Info.IsBeacon)
{ {
// specifically checking for friendlyNPC seems more logical here // specifically checking for friendlyNPC seems more logical here
if (dockingPort.Item.Submarine.TeamID != item.Submarine.TeamID && dockingPort.Item.Submarine.TeamID != CharacterTeamType.FriendlyNPC) { continue; } if (dockingPort.Item.Submarine.TeamID != item.Submarine.TeamID && dockingPort.Item.Submarine.TeamID != CharacterTeamType.FriendlyNPC) { continue; }
@@ -1348,6 +1358,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, AITarget needsToBeInSector = null) float pingStrength = 1.0f, AITarget needsToBeInSector = null)
{ {
@@ -1392,6 +1434,16 @@ namespace Barotrauma.Items.Components
if (connectedSubs.Contains(submarine)) { continue; } if (connectedSubs.Contains(submarine)) { continue; }
} }
//display the actual walls if the ping source is inside the sub (but not inside a hull, that's handled above)
//only relevant in the end levels or maybe custom subs with some kind of non-hulled parts
Rectangle worldBorders = submarine.GetDockedBorders();
worldBorders.Location += submarine.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]);
@@ -1608,6 +1660,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;
@@ -178,8 +178,9 @@ namespace Barotrauma.Items.Components
var autoPilotControls = new GUIFrame(new RectTransform(new Vector2(0.75f, 0.62f), paddedControlContainer.RectTransform, Anchor.BottomCenter), "OutlineFrame"); var autoPilotControls = new GUIFrame(new RectTransform(new Vector2(0.75f, 0.62f), paddedControlContainer.RectTransform, Anchor.BottomCenter), "OutlineFrame");
var paddedAutoPilotControls = new GUIFrame(new RectTransform(new Vector2(0.92f, 0.88f), autoPilotControls.RectTransform, Anchor.Center), style: null); var paddedAutoPilotControls = new GUIFrame(new RectTransform(new Vector2(0.92f, 0.88f), autoPilotControls.RectTransform, Anchor.Center), style: null);
int textLimit = (int)(paddedAutoPilotControls.Rect.Width * 0.75f);
maintainPosTickBox = new GUITickBox(new RectTransform(new Vector2(1, 0.333f), paddedAutoPilotControls.RectTransform, Anchor.TopCenter), maintainPosTickBox = new GUITickBox(new RectTransform(new Vector2(1, 0.333f), paddedAutoPilotControls.RectTransform, Anchor.TopCenter),
TextManager.Get("SteeringMaintainPos"), font: GUIStyle.SmallFont, style: "GUIRadioButton") ToolBox.LimitString(TextManager.Get("SteeringMaintainPos"), GUIStyle.SmallFont, textLimit), font: GUIStyle.SmallFont, style: "GUIRadioButton")
{ {
UserData = UIHighlightAction.ElementId.MaintainPosTickBox, UserData = UIHighlightAction.ElementId.MaintainPosTickBox,
Enabled = autoPilot, Enabled = autoPilot,
@@ -214,7 +215,6 @@ namespace Barotrauma.Items.Components
return true; return true;
} }
}; };
int textLimit = (int)(paddedAutoPilotControls.Rect.Width * 0.75f);
levelStartTickBox = new GUITickBox(new RectTransform(new Vector2(1, 0.333f), paddedAutoPilotControls.RectTransform, Anchor.Center), levelStartTickBox = new GUITickBox(new RectTransform(new Vector2(1, 0.333f), paddedAutoPilotControls.RectTransform, Anchor.Center),
GameMain.GameSession?.StartLocation == null ? "" : ToolBox.LimitString(GameMain.GameSession.StartLocation.Name, GUIStyle.SmallFont, textLimit), GameMain.GameSession?.StartLocation == null ? "" : ToolBox.LimitString(GameMain.GameSession.StartLocation.Name, GUIStyle.SmallFont, textLimit),
font: GUIStyle.SmallFont, style: "GUIRadioButton") font: GUIStyle.SmallFont, style: "GUIRadioButton")
@@ -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();
}; };
@@ -20,6 +20,7 @@ namespace Barotrauma.Items.Components
User = Entity.FindEntityByID(userId) as Character; User = Entity.FindEntityByID(userId) as Character;
Vector2 simPosition = new Vector2(msg.ReadSingle(), msg.ReadSingle()); Vector2 simPosition = new Vector2(msg.ReadSingle(), msg.ReadSingle());
float rotation = msg.ReadSingle(); float rotation = msg.ReadSingle();
SpreadCounter = msg.ReadByte();
if (User != null) if (User != null)
{ {
Shoot(User, simPosition, simPosition, rotation, ignoredBodies: User.AnimController.Limbs.Where(l => !l.IsSevered).Select(l => l.body.FarseerBody).ToList(), createNetworkEvent: false); Shoot(User, simPosition, simPosition, rotation, ignoredBodies: User.AnimController.Limbs.Where(l => !l.IsSevered).Select(l => l.body.FarseerBody).ToList(), createNetworkEvent: false);
@@ -262,9 +262,11 @@ namespace Barotrauma.Items.Components
} }
} }
float conditionPercentage = item.Condition / (item.MaxCondition / item.MaxRepairConditionMultiplier) * 100f;
for (int i = 0; i < particleEmitters.Count; i++) for (int i = 0; i < particleEmitters.Count; i++)
{ {
if ((item.ConditionPercentage >= particleEmitterConditionRanges[i].X && item.ConditionPercentage <= particleEmitterConditionRanges[i].Y) || FakeBrokenTimer > 0.0f) if ((conditionPercentage >= particleEmitterConditionRanges[i].X && conditionPercentage <= particleEmitterConditionRanges[i].Y) || FakeBrokenTimer > 0.0f)
{ {
particleEmitters[i].Emit(deltaTime, item.WorldPosition, item.CurrentHull); particleEmitters[i].Emit(deltaTime, item.WorldPosition, item.CurrentHull);
} }
@@ -436,12 +438,16 @@ namespace Barotrauma.Items.Components
ushort currentFixerID = msg.ReadUInt16(); ushort currentFixerID = msg.ReadUInt16();
currentFixerAction = (FixActions)msg.ReadRangedInteger(0, 2); currentFixerAction = (FixActions)msg.ReadRangedInteger(0, 2);
CurrentFixer = currentFixerID != 0 ? Entity.FindEntityByID(currentFixerID) as Character : null; CurrentFixer = currentFixerID != 0 ? Entity.FindEntityByID(currentFixerID) as Character : null;
item.MaxRepairConditionMultiplier = GetMaxRepairConditionMultiplier(CurrentFixer);
if (CurrentFixer == null) if (CurrentFixer is null)
{ {
qteTimer = QteDuration; qteTimer = QteDuration;
qteCooldown = 0.0f; qteCooldown = 0.0f;
} }
else
{
item.MaxRepairConditionMultiplier = GetMaxRepairConditionMultiplier(CurrentFixer);
}
} }
public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null) public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null)
@@ -108,6 +108,7 @@ namespace Barotrauma.Items.Components
{ {
startPos += new Vector2((float)Math.Cos(turret.Rotation), (float)Math.Sin(turret.Rotation)) * turret.BarrelSprite.size.Y * turret.BarrelSprite.RelativeOrigin.Y * item.Scale * 0.9f; startPos += new Vector2((float)Math.Cos(turret.Rotation), (float)Math.Sin(turret.Rotation)) * turret.BarrelSprite.size.Y * turret.BarrelSprite.RelativeOrigin.Y * item.Scale * 0.9f;
} }
startPos -= turret.GetRecoilOffset();
} }
else if (weapon != null) else if (weapon != null)
{ {
@@ -259,7 +259,7 @@ namespace Barotrauma.Items.Components
{ {
bool alreadyConnected = DraggingConnected.IsConnectedTo(panel.Item); bool alreadyConnected = DraggingConnected.IsConnectedTo(panel.Item);
DraggingConnected.RemoveConnection(panel.Item); DraggingConnected.RemoveConnection(panel.Item);
if (DraggingConnected.Connect(this, !alreadyConnected, true)) if (DraggingConnected.TryConnect(this, !alreadyConnected, true))
{ {
var otherConnection = DraggingConnected.OtherConnection(this); var otherConnection = DraggingConnected.OtherConnection(this);
ConnectWire(DraggingConnected); ConnectWire(DraggingConnected);
@@ -110,8 +110,8 @@ namespace Barotrauma.Items.Components
if (HighlightedWire != null) if (HighlightedWire != null)
{ {
HighlightedWire.Item.IsHighlighted = true; HighlightedWire.Item.IsHighlighted = true;
if (HighlightedWire.Connections[0] != null && HighlightedWire.Connections[0].Item != null) HighlightedWire.Connections[0].Item.IsHighlighted = true; if (HighlightedWire.Connections[0] != null && HighlightedWire.Connections[0].Item != null) { HighlightedWire.Connections[0].Item.IsHighlighted = true; }
if (HighlightedWire.Connections[1] != null && HighlightedWire.Connections[1].Item != null) HighlightedWire.Connections[1].Item.IsHighlighted = true; if (HighlightedWire.Connections[1] != null && HighlightedWire.Connections[1].Item != null) { HighlightedWire.Connections[1].Item.IsHighlighted = true; }
} }
} }
@@ -225,7 +225,7 @@ namespace Barotrauma.Items.Components
foreach (var wire in newWires.Where(w => !connection.Wires.Contains(w)).ToArray()) foreach (var wire in newWires.Where(w => !connection.Wires.Contains(w)).ToArray())
{ {
connection.ConnectWire(wire); connection.ConnectWire(wire);
wire.Connect(connection, false); wire.TryConnect(connection, false);
} }
} }
@@ -12,6 +12,8 @@ namespace Barotrauma.Items.Components
private readonly List<GUIComponent> uiElements = new List<GUIComponent>(); private readonly List<GUIComponent> uiElements = new List<GUIComponent>();
private GUILayoutGroup uiElementContainer; private GUILayoutGroup uiElementContainer;
private bool readingNetworkEvent;
private Point ElementMaxSize => new Point(uiElementContainer.Rect.Width, (int)(65 * GUI.yScale)); private Point ElementMaxSize => new Point(uiElementContainer.Rect.Width, (int)(65 * GUI.yScale));
public override bool RecreateGUIOnResolutionChange => true; public override bool RecreateGUIOnResolutionChange => true;
@@ -100,7 +102,7 @@ namespace Barotrauma.Items.Components
{ {
ValueChanged(ni.UserData as CustomInterfaceElement, ni.FloatValue); ValueChanged(ni.UserData as CustomInterfaceElement, ni.FloatValue);
} }
else else if (!readingNetworkEvent)
{ {
item.CreateClientEvent(this); item.CreateClientEvent(this);
} }
@@ -126,7 +128,7 @@ namespace Barotrauma.Items.Components
{ {
ValueChanged(ni.UserData as CustomInterfaceElement, ni.IntValue); ValueChanged(ni.UserData as CustomInterfaceElement, ni.IntValue);
} }
else else if (!readingNetworkEvent)
{ {
item.CreateClientEvent(this); item.CreateClientEvent(this);
} }
@@ -161,7 +163,7 @@ namespace Barotrauma.Items.Components
{ {
TickBoxToggled(tBox.UserData as CustomInterfaceElement, tBox.Selected); TickBoxToggled(tBox.UserData as CustomInterfaceElement, tBox.Selected);
} }
else else if (!readingNetworkEvent)
{ {
item.CreateClientEvent(this); item.CreateClientEvent(this);
} }
@@ -181,12 +183,12 @@ namespace Barotrauma.Items.Components
}; };
btn.OnClicked += (_, userdata) => btn.OnClicked += (_, userdata) =>
{ {
CustomInterfaceElement btnElement = userdata as CustomInterfaceElement;; CustomInterfaceElement btnElement = userdata as CustomInterfaceElement;
if (GameMain.Client == null) if (GameMain.Client == null)
{ {
ButtonClicked(btnElement); ButtonClicked(btnElement);
} }
else else if (!readingNetworkEvent)
{ {
item.CreateClientEvent(this, new EventData(btnElement)); item.CreateClientEvent(this, new EventData(btnElement));
} }
@@ -297,9 +299,10 @@ namespace Barotrauma.Items.Components
LocalizedString CreateLabelText(int elementIndex) LocalizedString CreateLabelText(int elementIndex)
{ {
return string.IsNullOrWhiteSpace(customInterfaceElementList[elementIndex].Label) ? var label = customInterfaceElementList[elementIndex].Label;
return string.IsNullOrWhiteSpace(label) ?
TextManager.GetWithVariable("connection.signaloutx", "[num]", (elementIndex + 1).ToString()) : TextManager.GetWithVariable("connection.signaloutx", "[num]", (elementIndex + 1).ToString()) :
customInterfaceElementList[elementIndex].Label; TextManager.Get(label).Fallback(label);
} }
uiElementContainer.Recalculate(); uiElementContainer.Recalculate();
@@ -386,45 +389,53 @@ namespace Barotrauma.Items.Components
public void ClientEventRead(IReadMessage msg, float sendingTime) public void ClientEventRead(IReadMessage msg, float sendingTime)
{ {
for (int i = 0; i < customInterfaceElementList.Count; i++) readingNetworkEvent = true;
try
{ {
var element = customInterfaceElementList[i]; for (int i = 0; i < customInterfaceElementList.Count; i++)
if (element.HasPropertyName)
{ {
string newValue = msg.ReadString(); var element = customInterfaceElementList[i];
if (!element.IsNumberInput) if (element.HasPropertyName)
{ {
TextChanged(element, newValue); string newValue = msg.ReadString();
if (!element.IsNumberInput)
{
TextChanged(element, newValue);
}
else
{
switch (element.NumberType)
{
case NumberType.Int when int.TryParse(newValue, out int value):
ValueChanged(element, value);
break;
case NumberType.Float when TryParseFloatInvariantCulture(newValue, out float value):
ValueChanged(element, value);
break;
}
}
} }
else else
{ {
switch (element.NumberType) bool elementState = msg.ReadBoolean();
if (element.ContinuousSignal)
{ {
case NumberType.Int when int.TryParse(newValue, out int value): ((GUITickBox)uiElements[i]).Selected = elementState;
ValueChanged(element, value); TickBoxToggled(element, elementState);
break; }
case NumberType.Float when TryParseFloatInvariantCulture(newValue, out float value): else if (elementState)
ValueChanged(element, value); {
break; ButtonClicked(element);
} }
} }
} }
else
{
bool elementState = msg.ReadBoolean();
if (element.ContinuousSignal)
{
((GUITickBox)uiElements[i]).Selected = elementState;
TickBoxToggled(element, elementState);
}
else if (elementState)
{
ButtonClicked(element);
}
}
}
UpdateSignalsProjSpecific(); UpdateSignalsProjSpecific();
}
finally
{
readingNetworkEvent = false;
}
} }
} }
} }
@@ -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;
@@ -212,7 +212,7 @@ namespace Barotrauma.Items.Components
Sprite pingCircle = GUIStyle.UIThermalGlow.Value.Sprite; Sprite pingCircle = GUIStyle.UIThermalGlow.Value.Sprite;
foreach (Limb limb in c.AnimController.Limbs) foreach (Limb limb in c.AnimController.Limbs)
{ {
if (limb.Mass < 1.0f) { continue; } if (limb.Mass < 0.5f && limb != c.AnimController.MainLimb) { continue; }
float noise1 = PerlinNoise.GetPerlin((thermalEffectState + limb.Params.ID + c.ID) * 0.01f, (thermalEffectState + limb.Params.ID + c.ID) * 0.02f); float noise1 = PerlinNoise.GetPerlin((thermalEffectState + limb.Params.ID + c.ID) * 0.01f, (thermalEffectState + limb.Params.ID + c.ID) * 0.02f);
float noise2 = PerlinNoise.GetPerlin((thermalEffectState + limb.Params.ID + c.ID) * 0.01f, (thermalEffectState + limb.Params.ID + c.ID) * 0.008f); float noise2 = PerlinNoise.GetPerlin((thermalEffectState + limb.Params.ID + c.ID) * 0.01f, (thermalEffectState + limb.Params.ID + c.ID) * 0.008f);
Vector2 spriteScale = ConvertUnits.ToDisplayUnits(limb.body.GetSize()) / pingCircle.size * (noise1 * 0.5f + 2f); Vector2 spriteScale = ConvertUnits.ToDisplayUnits(limb.body.GetSize()) / pingCircle.size * (noise1 * 0.5f + 2f);
@@ -302,7 +302,18 @@ namespace Barotrauma.Items.Components
Dictionary<AfflictionPrefab, float> combinedAfflictionStrengths = new Dictionary<AfflictionPrefab, float>(); Dictionary<AfflictionPrefab, float> combinedAfflictionStrengths = new Dictionary<AfflictionPrefab, float>();
foreach (Affliction affliction in allAfflictions) foreach (Affliction affliction in allAfflictions)
{ {
if (affliction.Strength < affliction.Prefab.ShowInHealthScannerThreshold || affliction.Strength <= 0.0f) { continue; } if (affliction.Strength <= 0f) { continue; }
if (affliction.Strength < affliction.Prefab.ShowInHealthScannerThreshold)
{
if (target.IsHuman || target.IsOnPlayerTeam || (affliction.Prefab.AfflictionType != AfflictionPrefab.PoisonType && affliction.Prefab.AfflictionType != AfflictionPrefab.ParalysisType))
{
// Always show the poisons on monsters, because poisoning bigger monsters require multiple doses.
// The solution is hacky, but didn't want to introduce an extra property for this.
// We also want to have a relatively high thershold for showing the poisons on the scanner on humans, so that it's not instantly clear that a target is poisoned and especially not which poison was used.
// Paralysis is treated like a poison but isn't technically a poison, so that we can have multiple afflictions that still are treated the same.
continue;
}
}
if (combinedAfflictionStrengths.ContainsKey(affliction.Prefab)) if (combinedAfflictionStrengths.ContainsKey(affliction.Prefab))
{ {
combinedAfflictionStrengths[affliction.Prefab] += affliction.Strength; combinedAfflictionStrengths[affliction.Prefab] += affliction.Strength;
@@ -334,14 +334,8 @@ namespace Barotrauma.Items.Components
crosshairPointerPos = PlayerInput.MousePosition; crosshairPointerPos = PlayerInput.MousePosition;
} }
public void Draw(SpriteBatch spriteBatch, bool editing = false, float itemDepth = -1) public Vector2 GetRecoilOffset()
{ {
if (!MathUtils.NearlyEqual(item.Rotation, prevBaseRotation) || !MathUtils.NearlyEqual(item.Scale, prevScale))
{
UpdateTransformedBarrelPos();
}
Vector2 drawPos = GetDrawPos();
float recoilOffset = 0.0f; float recoilOffset = 0.0f;
if (Math.Abs(RecoilDistance) > 0.0f && recoilTimer > 0.0f) if (Math.Abs(RecoilDistance) > 0.0f && recoilTimer > 0.0f)
{ {
@@ -362,6 +356,17 @@ namespace Barotrauma.Items.Components
recoilOffset = RecoilDistance; recoilOffset = RecoilDistance;
} }
} }
return new Vector2((float)Math.Cos(rotation), (float)Math.Sin(rotation)) * recoilOffset;
}
public void Draw(SpriteBatch spriteBatch, bool editing = false, float itemDepth = -1)
{
if (!MathUtils.NearlyEqual(item.Rotation, prevBaseRotation) || !MathUtils.NearlyEqual(item.Scale, prevScale))
{
UpdateTransformedBarrelPos();
}
Vector2 drawPos = GetDrawPos();
railSprite?.Draw(spriteBatch, railSprite?.Draw(spriteBatch,
drawPos, drawPos,
@@ -370,7 +375,7 @@ namespace Barotrauma.Items.Components
SpriteEffects.None, item.SpriteDepth + (railSprite.Depth - item.Sprite.Depth)); SpriteEffects.None, item.SpriteDepth + (railSprite.Depth - item.Sprite.Depth));
barrelSprite?.Draw(spriteBatch, barrelSprite?.Draw(spriteBatch,
drawPos - new Vector2((float)Math.Cos(rotation), (float)Math.Sin(rotation)) * recoilOffset * item.Scale, drawPos - GetRecoilOffset() * item.Scale,
item.SpriteColor, item.SpriteColor,
rotation + MathHelper.PiOver2, item.Scale, rotation + MathHelper.PiOver2, item.Scale,
SpriteEffects.None, item.SpriteDepth + (barrelSprite.Depth - item.Sprite.Depth)); SpriteEffects.None, item.SpriteDepth + (barrelSprite.Depth - item.Sprite.Depth));
@@ -53,11 +53,11 @@ namespace Barotrauma
get get
{ {
// Returns a point off-screen, Rectangle.Empty places buttons in the top left of the screen // Returns a point off-screen, Rectangle.Empty places buttons in the top left of the screen
if (IsMoving) return offScreenRect; if (IsMoving) { return offScreenRect; }
int buttonDir = Math.Sign(SubInventoryDir); int buttonDir = Math.Sign(SubInventoryDir);
float sizeY = Inventory.UnequippedIndicator.size.Y * Inventory.UIScale * Inventory.IndicatorScaleAdjustment; float sizeY = Inventory.UnequippedIndicator.size.Y * Inventory.UIScale;
Vector2 equipIndicatorPos = new Vector2(Rect.Left, Rect.Center.Y + (Rect.Height / 2 + 15 * Inventory.UIScale) * buttonDir - sizeY / 2f); Vector2 equipIndicatorPos = new Vector2(Rect.Left, Rect.Center.Y + (Rect.Height / 2 + 15 * Inventory.UIScale) * buttonDir - sizeY / 2f);
equipIndicatorPos += DrawOffset; equipIndicatorPos += DrawOffset;
@@ -176,14 +176,6 @@ namespace Barotrauma
public static Sprite DraggableIndicator; public static Sprite DraggableIndicator;
public static Sprite UnequippedIndicator, UnequippedHoverIndicator, UnequippedClickedIndicator, EquippedIndicator, EquippedHoverIndicator, EquippedClickedIndicator; public static Sprite UnequippedIndicator, UnequippedHoverIndicator, UnequippedClickedIndicator, EquippedIndicator, EquippedHoverIndicator, EquippedClickedIndicator;
public static float IndicatorScaleAdjustment
{
get
{
return !GUI.IsFourByThree() ? 0.8f : 0.7f;
}
}
public static Inventory DraggingInventory; public static Inventory DraggingInventory;
public Inventory ReplacedBy; public Inventory ReplacedBy;
@@ -249,11 +241,11 @@ namespace Barotrauma
{ {
itemsInSlot = ParentInventory.GetItemsAt(SlotIndex); itemsInSlot = ParentInventory.GetItemsAt(SlotIndex);
} }
Tooltip = GetTooltip(Item, itemsInSlot); Tooltip = GetTooltip(Item, itemsInSlot, Character.Controlled);
tooltipDisplayedCondition = (int)Item.ConditionPercentage; tooltipDisplayedCondition = (int)Item.ConditionPercentage;
} }
private RichString GetTooltip(Item item, IEnumerable<Item> itemsInSlot) private static RichString GetTooltip(Item item, IEnumerable<Item> itemsInSlot, Character character)
{ {
if (item == null) { return null; } if (item == null) { return null; }
@@ -348,10 +340,12 @@ namespace Barotrauma
} }
if (itemsInSlot.Count() > 1) if (itemsInSlot.Count() > 1)
{ {
string colorStr = XMLExtensions.ColorToString(GUIStyle.Blue); toolTip += $"\n‖color:gui.blue‖[{GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.TakeOneFromInventorySlot)}] {TextManager.Get("inputtype.takeonefrominventoryslot")}‖color:end‖";
toolTip += $"\n‖color:{colorStr}‖[{GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.TakeOneFromInventorySlot)}] {TextManager.Get("inputtype.takeonefrominventoryslot")}‖color:end‖"; toolTip += $"\n‖color:gui.blue‖[{GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.TakeHalfFromInventorySlot)}] {TextManager.Get("inputtype.takehalffrominventoryslot")}‖color:end‖";
colorStr = XMLExtensions.ColorToString(GUIStyle.Blue); }
toolTip += $"\n‖color:{colorStr}‖[{GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.TakeHalfFromInventorySlot)}] {TextManager.Get("inputtype.takehalffrominventoryslot")}‖color:end‖"; if (item.Prefab.SkillRequirementHints != null && item.Prefab.SkillRequirementHints.Any())
{
toolTip += item.Prefab.GetSkillRequirementHints(character);
} }
return RichString.Rich(toolTip); return RichString.Rich(toolTip);
} }
@@ -576,7 +570,7 @@ namespace Barotrauma
} }
} }
if (PlayerInput.LeftButtonHeld() && PlayerInput.RightButtonHeld()) if (PlayerInput.PrimaryMouseButtonHeld() && PlayerInput.SecondaryMouseButtonHeld())
{ {
mouseOn = false; mouseOn = false;
} }
@@ -727,14 +721,7 @@ namespace Barotrauma
Rectangle subRect = slot.Rect; Rectangle subRect = slot.Rect;
Vector2 spacing; Vector2 spacing;
if (GUI.IsFourByThree()) spacing = new Vector2(10 * UIScale, (10 + UnequippedIndicator.size.Y) * UIScale * GUI.AspectRatioAdjustment);
{
spacing = new Vector2(5 * UIScale, (5 + UnequippedIndicator.size.Y) * UIScale);
}
else
{
spacing = new Vector2(10 * UIScale, (10 + UnequippedIndicator.size.Y) * UIScale);
}
int columns = MathHelper.Clamp((int)Math.Floor(Math.Sqrt(itemCapacity)), 1, container.SlotsPerRow); int columns = MathHelper.Clamp((int)Math.Floor(Math.Sqrt(itemCapacity)), 1, container.SlotsPerRow);
while (itemCapacity / columns * (subRect.Height + spacing.Y) > GameMain.GraphicsHeight * 0.5f) while (itemCapacity / columns * (subRect.Height + spacing.Y) > GameMain.GraphicsHeight * 0.5f)
@@ -1535,16 +1522,6 @@ namespace Barotrauma
{ {
Sprite slotSprite = slot.SlotSprite ?? SlotSpriteSmall; Sprite slotSprite = slot.SlotSprite ?? SlotSpriteSmall;
/*if (inventory != null && (CharacterInventory.PersonalSlots.HasFlag(type) || (inventory.isSubInventory && (inventory.Owner as Item) != null
&& (inventory.Owner as Item).AllowedSlots.Any(a => CharacterInventory.PersonalSlots.HasFlag(a)))))
{
slotColor = slot.IsHighlighted ? GUIStyle.EquipmentSlotColor : GUIStyle.EquipmentSlotColor * 0.8f;
}
else
{
slotColor = slot.IsHighlighted ? GUIStyle.InventorySlotColor : GUIStyle.InventorySlotColor * 0.8f;
}*/
if (inventory != null && inventory.Locked) { slotColor = Color.Gray * 0.5f; } if (inventory != null && inventory.Locked) { slotColor = Color.Gray * 0.5f; }
spriteBatch.Draw(slotSprite.Texture, rect, slotSprite.SourceRect, slotColor); spriteBatch.Draw(slotSprite.Texture, rect, slotSprite.SourceRect, slotColor);
@@ -1731,7 +1708,17 @@ namespace Barotrauma
slot.InventoryKeyIndex < GameSettings.CurrentConfig.InventoryKeyMap.Bindings.Length) slot.InventoryKeyIndex < GameSettings.CurrentConfig.InventoryKeyMap.Bindings.Length)
{ {
spriteBatch.Draw(slotHotkeySprite.Texture, rect.ScaleSize(1.15f), slotHotkeySprite.SourceRect, slotColor); spriteBatch.Draw(slotHotkeySprite.Texture, rect.ScaleSize(1.15f), slotHotkeySprite.SourceRect, slotColor);
GUI.DrawString(spriteBatch, rect.Location.ToVector2() + new Vector2((int)(4.25f * UIScale), (int)Math.Ceiling(-1.5f * UIScale)), GameSettings.CurrentConfig.InventoryKeyMap.Bindings[slot.InventoryKeyIndex].Name, Color.Black, font: GUIStyle.HotkeyFont);
GUIStyle.HotkeyFont.DrawString(
spriteBatch,
GameSettings.CurrentConfig.InventoryKeyMap.Bindings[slot.InventoryKeyIndex].Name,
rect.Location.ToVector2() + new Vector2((int)(4.25f * UIScale), (int)Math.Ceiling(-1.5f * UIScale)),
Color.Black,
rotation: 0.0f,
origin: Vector2.Zero,
scale: Vector2.One * GUI.AspectRatioAdjustment,
SpriteEffects.None,
layerDepth: 0.0f);
} }
} }
@@ -23,7 +23,21 @@ namespace Barotrauma
private readonly List<SerializableEntityEditor> activeEditors = new List<SerializableEntityEditor>(); private readonly List<SerializableEntityEditor> activeEditors = new List<SerializableEntityEditor>();
public GUIComponentStyle IconStyle { get; private set; } = null;
private GUIComponentStyle iconStyle;
public GUIComponentStyle IconStyle
{
get { return iconStyle; }
private set
{
if (IconStyle != value)
{
iconStyle = value;
CheckIsHighlighted();
}
}
}
partial void AssignCampaignInteractionTypeProjSpecific(CampaignMode.InteractionType interactionType) partial void AssignCampaignInteractionTypeProjSpecific(CampaignMode.InteractionType interactionType)
{ {
if (interactionType == CampaignMode.InteractionType.None) if (interactionType == CampaignMode.InteractionType.None)
@@ -143,6 +157,18 @@ namespace Barotrauma
return color; return color;
} }
protected override void CheckIsHighlighted()
{
if (IsHighlighted || ExternalHighlight || IconStyle != null)
{
highlightedEntities.Add(this);
}
else
{
highlightedEntities.Remove(this);
}
}
public Color GetInventoryIconColor() public Color GetInventoryIconColor()
{ {
Color color = InventoryIconColor; Color color = InventoryIconColor;
@@ -281,7 +307,8 @@ namespace Barotrauma
cachedVisibleExtents = extents = new Rectangle(min.ToPoint(), max.ToPoint()); cachedVisibleExtents = extents = new Rectangle(min.ToPoint(), max.ToPoint());
} }
Vector2 worldPosition = WorldPosition; Vector2 worldPosition = WorldPosition + GetCollapseEffectOffset();
if (worldPosition.X + extents.X > worldView.Right || worldPosition.X + extents.Width < worldView.X) { return false; } if (worldPosition.X + extents.X > worldView.Right || worldPosition.X + extents.Width < worldView.X) { return false; }
if (worldPosition.Y + extents.Height < worldView.Y - worldView.Height || worldPosition.Y + extents.Y > worldView.Y) { return false; } if (worldPosition.Y + extents.Height < worldView.Y - worldView.Height || worldPosition.Y + extents.Y > worldView.Y) { return false; }
@@ -310,7 +337,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++)
@@ -426,6 +455,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)
@@ -466,12 +497,12 @@ namespace Barotrauma
Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState, spriteAnimState[decorativeSprite].RandomOffsetMultiplier, -RotationRad) * Scale; Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState, spriteAnimState[decorativeSprite].RandomOffsetMultiplier, -RotationRad) * Scale;
if (flippedX && Prefab.CanSpriteFlipX) { offset.X = -offset.X; } if (flippedX && Prefab.CanSpriteFlipX) { offset.X = -offset.X; }
if (flippedY && Prefab.CanSpriteFlipY) { offset.Y = -offset.Y; } if (flippedY && Prefab.CanSpriteFlipY) { offset.Y = -offset.Y; }
var ca = (float)Math.Cos(-body.Rotation); var ca = MathF.Cos(-body.DrawRotation);
var sa = (float)Math.Sin(-body.Rotation); var sa = MathF.Sin(-body.DrawRotation);
Vector2 transformedOffset = new Vector2(ca * offset.X + sa * offset.Y, -sa * offset.X + ca * offset.Y); Vector2 transformedOffset = new Vector2(ca * offset.X + sa * offset.Y, -sa * offset.X + ca * offset.Y);
decorativeSprite.Sprite.Draw(spriteBatch, new Vector2(DrawPosition.X + transformedOffset.X, -(DrawPosition.Y + transformedOffset.Y)), color, decorativeSprite.Sprite.Draw(spriteBatch, new Vector2(body.DrawPosition.X + transformedOffset.X, -(body.DrawPosition.Y + transformedOffset.Y)), color,
-body.Rotation + rotation, decorativeSprite.GetScale(spriteAnimState[decorativeSprite].RandomScaleFactor) * Scale, activeSprite.effects, -body.DrawRotation + rotation, decorativeSprite.GetScale(spriteAnimState[decorativeSprite].RandomScaleFactor) * Scale, activeSprite.effects,
depth: depth + (decorativeSprite.Sprite.Depth - activeSprite.Depth)); depth: depth + (decorativeSprite.Sprite.Depth - activeSprite.Depth));
} }
} }
@@ -728,7 +759,7 @@ namespace Barotrauma
if (!lClick && !rClick) { return; } if (!lClick && !rClick) { return; }
Vector2 position = cam.ScreenToWorld(PlayerInput.MousePosition); Vector2 position = cam.ScreenToWorld(PlayerInput.MousePosition);
var otherEntity = mapEntityList.FirstOrDefault(e => e != this && e.IsHighlighted && e.IsMouseOn(position)); var otherEntity = highlightedEntities.FirstOrDefault(e => e != this && e.IsMouseOn(position));
if (otherEntity != null) if (otherEntity != null)
{ {
if (linkedTo.Contains(otherEntity)) if (linkedTo.Contains(otherEntity))
@@ -1385,8 +1416,9 @@ namespace Barotrauma
} }
break; break;
case EventType.Status: case EventType.Status:
bool loadingRound = msg.ReadBoolean();
float newCondition = msg.ReadSingle(); float newCondition = msg.ReadSingle();
SetCondition(newCondition, isNetworkEvent: true); SetCondition(newCondition, isNetworkEvent: true, executeEffects: !loadingRound);
break; break;
case EventType.AssignCampaignInteraction: case EventType.AssignCampaignInteraction:
CampaignInteractionType = (CampaignMode.InteractionType)msg.ReadByte(); CampaignInteractionType = (CampaignMode.InteractionType)msg.ReadByte();
@@ -1672,25 +1704,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();
@@ -1794,6 +1825,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;
@@ -254,7 +254,7 @@ namespace Barotrauma
if (!DefaultPrice.RequiresUnlock) { return true; } if (!DefaultPrice.RequiresUnlock) { return true; }
return Character.Controlled is not null && Character.Controlled.HasStoreAccessForItem(this); return Character.Controlled is not null && Character.Controlled.HasStoreAccessForItem(this);
} }
public LocalizedString GetTooltip() public LocalizedString GetTooltip(Character character)
{ {
LocalizedString tooltip = $"‖color:{XMLExtensions.ToStringHex(GUIStyle.TextColorBright)}‖{Name}‖color:end‖"; LocalizedString tooltip = $"‖color:{XMLExtensions.ToStringHex(GUIStyle.TextColorBright)}‖{Name}‖color:end‖";
if (!Description.IsNullOrEmpty()) if (!Description.IsNullOrEmpty())
@@ -265,6 +265,10 @@ namespace Barotrauma
{ {
Wearable.AddTooltipInfo(wearableDamageModifiers, wearableSkillModifiers, ref tooltip); Wearable.AddTooltipInfo(wearableDamageModifiers, wearableSkillModifiers, ref tooltip);
} }
if (SkillRequirementHints != null && SkillRequirementHints.Any())
{
tooltip += GetSkillRequirementHints(character);
}
return tooltip; return tooltip;
} }
@@ -376,5 +380,31 @@ namespace Barotrauma
Sprite.DrawTiled(spriteBatch, new Vector2(placeRect.X, -placeRect.Y), placeRect.Size.ToVector2(), SpriteColor * 0.8f); Sprite.DrawTiled(spriteBatch, new Vector2(placeRect.X, -placeRect.Y), placeRect.Size.ToVector2(), SpriteColor * 0.8f);
} }
} }
public LocalizedString GetSkillRequirementHints(Character character)
{
LocalizedString text = "";
if (SkillRequirementHints != null && SkillRequirementHints.Any() && character != null)
{
Color orange = GUIStyle.Orange;
// Reuse an existing, localized, text because it's what we want here: "Required skills:"
text = "\n\n" + $"‖color:{orange.ToStringHex()}‖{TextManager.Get("requiredrepairskills")}‖color:end‖";
foreach (var hint in SkillRequirementHints)
{
int skillLevel = (int)character.GetSkillLevel(hint.Skill);
Color levelColor = GUIStyle.Yellow;
if (skillLevel >= hint.Level)
{
levelColor = GUIStyle.Green;
}
else if (skillLevel < hint.Level / 2)
{
levelColor = GUIStyle.Red;
}
text += "\n" + hint.GetFormattedText(skillLevel, levelColor.ToStringHex());
}
}
return text;
}
} }
} }
@@ -2,6 +2,7 @@
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using System; using System;
using System.Linq;
namespace Barotrauma namespace Barotrauma
{ {
@@ -320,5 +321,45 @@ namespace Barotrauma
return IsHorizontal ? rect.Height : rect.Width; return IsHorizontal ? rect.Height : rect.Width;
} }
} }
public override void UpdateEditing(Camera cam, float deltaTime)
{
if (editingHUD == null || editingHUD.UserData != this)
{
editingHUD = CreateEditingHUD();
}
}
private GUIComponent CreateEditingHUD(bool inGame = false)
{
editingHUD = new GUIFrame(new RectTransform(new Vector2(0.3f, 0.15f), GUI.Canvas, Anchor.CenterRight) { MinSize = new Point(400, 0) })
{
UserData = this
};
var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.85f), editingHUD.RectTransform, Anchor.Center))
{
Stretch = true,
AbsoluteSpacing = (int)(GUI.Scale * 5)
};
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.2f), paddedFrame.RectTransform), TextManager.Get("entityname.gap"), font: GUIStyle.LargeFont);
var hiddenInGameTickBox = new GUITickBox(new RectTransform(new Vector2(0.5f, 1.0f), paddedFrame.RectTransform), TextManager.Get("sp.hiddeningame.name"))
{
Selected = HiddenInGame
};
hiddenInGameTickBox.OnSelected += (GUITickBox tickbox) =>
{
HiddenInGame = tickbox.Selected;
return true;
};
editingHUD.RectTransform.Resize(new Point(
editingHUD.Rect.Width,
(int)(paddedFrame.Children.Sum(c => c.Rect.Height + paddedFrame.AbsoluteSpacing) / paddedFrame.RectTransform.RelativeSize.Y * 1.25f)));
PositionEditingHUD();
return editingHUD;
}
} }
} }
@@ -129,20 +129,15 @@ namespace Barotrauma
Vector2 position = cam.ScreenToWorld(PlayerInput.MousePosition); Vector2 position = cam.ScreenToWorld(PlayerInput.MousePosition);
foreach (MapEntity entity in mapEntityList) foreach (MapEntity entity in HighlightedEntities)
{ {
if (entity == this || !entity.IsHighlighted) { continue; } if (entity == this) { continue; }
if (!entity.IsMouseOn(position)) { continue; } if (!entity.IsMouseOn(position)) { continue; }
if (entity.linkedTo == null || !entity.Linkable) { continue; } if (entity.linkedTo == null || !entity.Linkable) { continue; }
if (entity.linkedTo.Contains(this) || linkedTo.Contains(entity) || rClick) if (entity.linkedTo.Contains(this) || linkedTo.Contains(entity) || rClick)
{ {
if (entity == this || !entity.IsHighlighted) { continue; } entity.linkedTo.Remove(this);
if (!entity.IsMouseOn(position)) { continue; } linkedTo.Remove(entity);
if (entity.linkedTo.Contains(this))
{
entity.linkedTo.Remove(this);
linkedTo.Remove(entity);
}
} }
else else
{ {
@@ -329,13 +324,13 @@ namespace Barotrauma
} }
/*GUI.DrawLine(spriteBatch, new Vector2(drawRect.X, -WorldSurface), new Vector2(drawRect.Right, -WorldSurface), Color.Cyan * 0.5f); GUI.DrawLine(spriteBatch, new Vector2(drawRect.X, -WorldSurface), new Vector2(drawRect.Right, -WorldSurface), Color.Cyan * 0.5f);
for (int i = 0; i < waveY.Length - 1; i++) for (int i = 0; i < waveY.Length - 1; i++)
{ {
GUI.DrawLine(spriteBatch, GUI.DrawLine(spriteBatch,
new Vector2(drawRect.X + WaveWidth * i, -WorldSurface - waveY[i] - 10), new Vector2(drawRect.X + WaveWidth * i, -WorldSurface - waveY[i] - 10),
new Vector2(drawRect.X + WaveWidth * (i + 1), -WorldSurface - waveY[i + 1] - 10), Color.Blue * 0.5f); new Vector2(drawRect.X + WaveWidth * (i + 1), -WorldSurface - waveY[i + 1] - 10), Color.Blue * 0.5f);
}*/ }
} }
foreach (MapEntity e in linkedTo) foreach (MapEntity e in linkedTo)
@@ -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)
@@ -22,7 +22,7 @@ namespace Barotrauma
public float CurrentRotation; public float CurrentRotation;
private List<SpriteDeformation> spriteDeformations = new List<SpriteDeformation>(); private readonly List<SpriteDeformation> spriteDeformations = new List<SpriteDeformation>();
public Vector2 CurrentScale public Vector2 CurrentScale
{ {
@@ -86,6 +86,8 @@ namespace Barotrauma
private set; private set;
} }
public bool CanBeVisible { get; private set; }
partial void InitProjSpecific() partial void InitProjSpecific()
{ {
Sprite?.EnsureLazyLoaded(); Sprite?.EnsureLazyLoaded();
@@ -156,6 +158,11 @@ namespace Barotrauma
{ {
SonarRadius = Triggers.Select(t => t.ColliderRadius * 1.5f).Max(); SonarRadius = Triggers.Select(t => t.ColliderRadius * 1.5f).Max();
} }
CanBeVisible =
Sprite != null ||
Prefab.DeformableSprite != null ||
Prefab.OverrideProperties.Any(p => p != null && (p.Sprites.Any() || p.DeformableSprite != null));
} }
public void Update(float deltaTime) public void Update(float deltaTime)
@@ -56,63 +56,84 @@ namespace Barotrauma
float minSizeToDraw = MathHelper.Lerp(10.0f, 5.0f, Math.Min(zoom * 20.0f, 1.0f)); float minSizeToDraw = MathHelper.Lerp(10.0f, 5.0f, Math.Min(zoom * 20.0f, 1.0f));
//start from the grid cell at the center of the view
//(if objects needs to be culled, better to cull at the edges of the view)
int midIndexX = (currentIndices.X + currentIndices.Width) / 2;
int midIndexY = (currentIndices.Y + currentIndices.Height) / 2;
CheckIndex(midIndexX, midIndexY);
for (int x = currentIndices.X; x <= currentIndices.Width; x++) for (int x = currentIndices.X; x <= currentIndices.Width; x++)
{ {
for (int y = currentIndices.Y; y <= currentIndices.Height; y++) for (int y = currentIndices.Y; y <= currentIndices.Height; y++)
{ {
if (objectGrid[x, y] == null) { continue; } if (x != midIndexX || y != midIndexY) { CheckIndex(x, y); }
foreach (LevelObject obj in objectGrid[x, y]) }
}
void CheckIndex(int x, int y)
{
if (objectGrid[x, y] == null) { return; }
foreach (LevelObject obj in objectGrid[x, y])
{
if (!obj.CanBeVisible) { continue; }
if (obj.Prefab.HideWhenBroken && obj.Health <= 0.0f) { continue; }
if (zoom < 0.05f)
{ {
if (obj.Prefab.HideWhenBroken && obj.Health <= 0.0f) { continue; } //hide if the sprite is very small when zoomed this far out
if ((obj.Sprite != null && Math.Min(obj.Sprite.size.X * zoom, obj.Sprite.size.Y * zoom) < 5.0f) ||
if (zoom < 0.05f) (obj.ActivePrefab?.DeformableSprite != null && Math.Min(obj.ActivePrefab.DeformableSprite.Sprite.size.X * zoom, obj.ActivePrefab.DeformableSprite.Sprite.size.Y * zoom) < minSizeToDraw))
{ {
//hide if the sprite is very small when zoomed this far out continue;
if ((obj.Sprite != null && Math.Min(obj.Sprite.size.X * zoom, obj.Sprite.size.Y * zoom) < 5.0f) ||
(obj.ActivePrefab?.DeformableSprite != null && Math.Min(obj.ActivePrefab.DeformableSprite.Sprite.size.X * zoom, obj.ActivePrefab.DeformableSprite.Sprite.size.Y * zoom) < minSizeToDraw))
{
continue;
}
float zCutoff = MathHelper.Lerp(5000.0f, 500.0f, (0.05f - zoom) * 20.0f);
if (obj.Position.Z > zCutoff)
{
continue;
}
} }
var objectList = float zCutoff = MathHelper.Lerp(5000.0f, 500.0f, (0.05f - zoom) * 20.0f);
obj.Position.Z >= 0 ? if (obj.Position.Z > zCutoff)
visibleObjectsBack :
(obj.Position.Z < -1 ? visibleObjectsFront : visibleObjectsMid);
int drawOrderIndex = 0;
for (int i = 0; i < objectList.Count; i++)
{ {
if (objectList[i] == obj) continue;
{ }
drawOrderIndex = -1; }
break;
}
if (objectList[i].Position.Z < obj.Position.Z) var objectList =
{ obj.Position.Z >= 0 ?
break; visibleObjectsBack :
} (obj.Position.Z < -1 ? visibleObjectsFront : visibleObjectsMid);
else if (objectList.Count >= MaxVisibleObjects) { continue; }
{
drawOrderIndex = i + 1; int drawOrderIndex = 0;
} for (int i = 0; i < objectList.Count; i++)
{
if (objectList[i] == obj)
{
drawOrderIndex = -1;
break;
} }
if (drawOrderIndex >= 0) if (objectList[i].Position.Z > obj.Position.Z)
{ {
objectList.Insert(drawOrderIndex, obj); break;
if (objectList.Count >= MaxVisibleObjects) { break; }
} }
else
{
drawOrderIndex = i + 1;
if (drawOrderIndex >= MaxVisibleObjects) { break; }
}
}
if (drawOrderIndex >= 0 && drawOrderIndex < MaxVisibleObjects)
{
objectList.Insert(drawOrderIndex, obj);
} }
} }
} }
//object grid is sorted in an ascending order
//(so we prefer the objects in the foreground instead of ones in the background if some need to be culled)
//rendering needs to be done in a descending order though to get the background objects to be drawn first -> reverse the lists
visibleObjectsBack.Reverse();
visibleObjectsMid.Reverse();
visibleObjectsFront.Reverse();
currentGridIndices = currentIndices; currentGridIndices = currentIndices;
} }
@@ -144,14 +165,14 @@ namespace Barotrauma
{ {
Rectangle indices = Rectangle.Empty; Rectangle indices = Rectangle.Empty;
indices.X = (int)Math.Floor(cam.WorldView.X / (float)GridSize); indices.X = (int)Math.Floor(cam.WorldView.X / (float)GridSize);
if (indices.X >= objectGrid.GetLength(0)) return; if (indices.X >= objectGrid.GetLength(0)) { return; }
indices.Y = (int)Math.Floor((cam.WorldView.Y - cam.WorldView.Height - Level.Loaded.BottomPos) / (float)GridSize); indices.Y = (int)Math.Floor((cam.WorldView.Y - cam.WorldView.Height - Level.Loaded.BottomPos) / (float)GridSize);
if (indices.Y >= objectGrid.GetLength(1)) return; if (indices.Y >= objectGrid.GetLength(1)) { return; }
indices.Width = (int)Math.Floor(cam.WorldView.Right / (float)GridSize) + 1; indices.Width = (int)Math.Floor(cam.WorldView.Right / (float)GridSize) + 1;
if (indices.Width < 0) return; if (indices.Width < 0) { return; }
indices.Height = (int)Math.Floor((cam.WorldView.Y - Level.Loaded.BottomPos) / (float)GridSize) + 1; indices.Height = (int)Math.Floor((cam.WorldView.Y - Level.Loaded.BottomPos) / (float)GridSize) + 1;
if (indices.Height < 0) return; if (indices.Height < 0) { return; }
indices.X = Math.Max(indices.X, 0); indices.X = Math.Max(indices.X, 0);
indices.Y = Math.Max(indices.Y, 0); indices.Y = Math.Max(indices.Y, 0);
@@ -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)
@@ -121,11 +140,49 @@ namespace Barotrauma
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;
} }
dustVelocity = Vector2.Lerp(dustVelocity, currentDustVel, deltaTime); waterParticleVelocity = Vector2.Lerp(waterParticleVelocity, currentWaterParticleVel, deltaTime);
WaterRenderer.Instance?.ScrollWater(dustVelocity, deltaTime); WaterRenderer.Instance?.ScrollWater(waterParticleVelocity, 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();
@@ -1,8 +1,6 @@
using Barotrauma.Extensions; using Barotrauma.Items.Components;
using Barotrauma.Items.Components;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using SharpFont;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
@@ -12,28 +10,14 @@ namespace Barotrauma.Lights
{ {
class ConvexHullList class ConvexHullList
{ {
private List<ConvexHull> list;
public HashSet<ConvexHull> IsHidden;
public readonly Submarine Submarine; public readonly Submarine Submarine;
public List<ConvexHull> List public HashSet<ConvexHull> IsHidden = new HashSet<ConvexHull>();
{ public readonly List<ConvexHull> List = new List<ConvexHull>();
get { return list; }
set
{
Debug.Assert(value != null);
Debug.Assert(!list.Contains(null));
list = value;
IsHidden.RemoveWhere(ch => !list.Contains(ch));
}
}
public ConvexHullList(Submarine submarine) public ConvexHullList(Submarine submarine)
{ {
Submarine = submarine; Submarine = submarine;
list = new List<ConvexHull>();
IsHidden = new HashSet<ConvexHull>();
} }
} }
@@ -47,13 +31,13 @@ namespace Barotrauma.Lights
public bool IsHorizontal; public bool IsHorizontal;
public bool IsAxisAligned; public bool IsAxisAligned;
public Vector2 SubmarineDrawPos;
public Segment(SegmentPoint start, SegmentPoint end, ConvexHull convexHull) public Segment(SegmentPoint start, SegmentPoint end, ConvexHull convexHull)
{ {
if (start.Pos.Y > end.Pos.Y) if (start.Pos.Y > end.Pos.Y)
{ {
var temp = start; (end, start) = (start, end);
start = end;
end = temp;
} }
Start = start; Start = start;
@@ -102,14 +86,15 @@ namespace Barotrauma.Lights
private readonly Segment[] segments = new Segment[4]; private readonly Segment[] segments = new Segment[4];
private readonly SegmentPoint[] vertices = new SegmentPoint[4]; private readonly SegmentPoint[] vertices = new SegmentPoint[4];
private readonly SegmentPoint[] losVertices = new SegmentPoint[4]; private readonly SegmentPoint[] losVertices = new SegmentPoint[2];
private readonly VectorPair[] losOffsets = new VectorPair[4]; private readonly Vector2[] losOffsets = new Vector2[2];
private readonly bool[] backFacing;
private readonly bool[] ignoreEdge;
private readonly bool isHorizontal; private readonly bool isHorizontal;
private readonly int thickness;
public bool IsExteriorWall;
public VertexPositionColor[] ShadowVertices { get; private set; } public VertexPositionColor[] ShadowVertices { get; private set; }
public VertexPositionTexture[] PenumbraVertices { get; private set; } public VertexPositionTexture[] PenumbraVertices { get; private set; }
public int ShadowVertexCount { get; private set; } public int ShadowVertexCount { get; private set; }
@@ -145,47 +130,27 @@ namespace Barotrauma.Lights
public Rectangle BoundingBox { get; private set; } public Rectangle BoundingBox { get; private set; }
public ConvexHull(Vector2[] points, Color color, MapEntity parent) public ConvexHull(Rectangle rect, bool? isHorizontal, MapEntity parent)
{ {
if (shadowEffect == null) shadowEffect ??= new BasicEffect(GameMain.Instance.GraphicsDevice)
{
shadowEffect = new BasicEffect(GameMain.Instance.GraphicsDevice)
{ {
VertexColorEnabled = true VertexColorEnabled = true
}; };
} penumbraEffect ??= new BasicEffect(GameMain.Instance.GraphicsDevice)
if (penumbraEffect == null)
{
penumbraEffect = new BasicEffect(GameMain.Instance.GraphicsDevice)
{ {
TextureEnabled = true, TextureEnabled = true,
LightingEnabled = false, LightingEnabled = false,
Texture = TextureLoader.FromFile("Content/Lights/penumbra.png") Texture = TextureLoader.FromFile("Content/Lights/penumbra.png")
}; };
}
ParentEntity = parent; ParentEntity = parent;
ShadowVertices = new VertexPositionColor[6 * 4]; ShadowVertices = new VertexPositionColor[6 * 4];
PenumbraVertices = new VertexPositionTexture[6 * 4]; PenumbraVertices = new VertexPositionTexture[6 * 4];
backFacing = new bool[4]; BoundingBox = rect;
ignoreEdge = new bool[4];
float minX = points[0].X, minY = points[0].Y, maxX = points[0].X, maxY = points[0].Y; this.isHorizontal = isHorizontal ?? BoundingBox.Width > BoundingBox.Height;
for (int i = 1; i < vertices.Length; i++)
{
if (points[i].X < minX) minX = points[i].X;
if (points[i].Y < minY) minY = points[i].Y;
if (points[i].X > maxX) maxX = points[i].X;
if (points[i].Y > minY) maxY = points[i].Y;
}
BoundingBox = new Rectangle((int)minX, (int)minY, (int)(maxX - minX), (int)(maxY - minY));
isHorizontal = BoundingBox.Width > BoundingBox.Height;
if (ParentEntity is Structure structure) if (ParentEntity is Structure structure)
{ {
System.Diagnostics.Debug.Assert(!structure.Removed); System.Diagnostics.Debug.Assert(!structure.Removed);
@@ -198,8 +163,26 @@ namespace Barotrauma.Lights
if (door != null) { isHorizontal = door.IsHorizontal; } if (door != null) { isHorizontal = door.IsHorizontal; }
} }
SetVertices(points); Vector2[] verts = new Vector2[]
{
new Vector2(rect.X, rect.Bottom),
new Vector2(rect.Right, rect.Bottom),
new Vector2(rect.Right, rect.Y),
new Vector2(rect.X, rect.Y),
};
Vector2[] losVerts;
if (this.isHorizontal)
{
thickness = rect.Height;
losVerts = new Vector2[] { new Vector2(rect.X, rect.Center.Y), new Vector2(rect.Right, rect.Center.Y) };
}
else
{
thickness = rect.Width;
losVerts = new Vector2[] { new Vector2(rect.Center.X, rect.Y), new Vector2(rect.Center.X, rect.Bottom) };
}
SetVertices(verts, losVerts);
Enabled = true; Enabled = true;
var chList = HullLists.Find(h => h.Submarine == parent.Submarine); var chList = HullLists.Find(h => h.Submarine == parent.Submarine);
@@ -211,248 +194,63 @@ namespace Barotrauma.Lights
foreach (ConvexHull ch in chList.List) foreach (ConvexHull ch in chList.List)
{ {
MergeOverlappingSegments(ch); MergeLosVertices(ch);
ch.MergeOverlappingSegments(this); ch.MergeLosVertices(this);
} }
chList.List.Add(this); chList.List.Add(this);
} }
private void MergeOverlappingSegments(ConvexHull ch) private void MergeLosVertices(ConvexHull ch, bool refreshOtherOverlappingHulls = true)
{ {
if (ch == this) { return; } if (ch == this) { return; }
if (isHorizontal == ch.isHorizontal) //hide segments that are roughly at the some position as some other segment (e.g. the ends of two adjacent wall pieces)
float mergeDist = MathHelper.Clamp(ch.thickness * 0.55f, 16, 512);
mergeDist = Math.Min(mergeDist, Vector2.Distance(losVertices[0].Pos, losVertices[1].Pos) / 2);
float mergeDistSqr = mergeDist * mergeDist;
bool changed = false;
for (int i = 0; i < losVertices.Length; i++)
{ {
//hide segments that are roughly at the some position as some other segment (e.g. the ends of two adjacent wall pieces) //find the closest point on the other convex hull segment
float mergeDist = 16; Vector2 closest = MathUtils.GetClosestPointOnLineSegment(
float mergeDistSqr = mergeDist * mergeDist; ch.losVertices[0].Pos + ch.losOffsets[0],
ch.losVertices[1].Pos + ch.losOffsets[1],
losVertices[i].Pos);
if (Vector2.DistanceSquared(closest, losVertices[i].Pos) > mergeDistSqr) { continue; }
Rectangle intersection = Rectangle.Intersect(BoundingBox, ch.BoundingBox); //find where the segments would intersect if they had infinite length
int intersectionArea = intersection.Width * intersection.Height; // if it's close to the closest point, let's use that instead to keep
int bboxArea = BoundingBox.Width * BoundingBox.Height; // the direction of the segment unchanged (i.e. vertical segment stays vertical)
int otherBboxArea = ch.BoundingBox.Width * ch.BoundingBox.Height; if (MathUtils.GetLineIntersection(
if (Math.Abs(intersectionArea - bboxArea) < mergeDistSqr) { return; } ch.losVertices[0].Pos + ch.losOffsets[0],
if (Math.Abs(intersectionArea - otherBboxArea) < mergeDistSqr) { return; } ch.losVertices[1].Pos + ch.losOffsets[1],
losVertices[0].Pos,
for (int i = 0; i < segments.Length; i++) losVertices[1].Pos,
out Vector2 intersection))
{ {
for (int j = 0; j < ch.segments.Length; j++) if (Vector2.DistanceSquared(intersection, losVertices[i].Pos) < mergeDistSqr ||
Vector2.DistanceSquared(intersection, closest) < 16.0f * 16.0f)
{ {
if (segments[i].IsHorizontal != ch.segments[j].IsHorizontal) { continue; } closest = intersection;
if (ignoreEdge[i] || ch.ignoreEdge[j]) { continue; }
//the segments must be at different sides of the convex hulls to be merged
//(e.g. the right edge of a wall piece and the left edge of another one)
var segment1Center = (segments[i].Start.Pos + segments[i].End.Pos) / 2.0f;
var segment2Center = (ch.segments[j].Start.Pos + ch.segments[j].End.Pos) / 2.0f;
if (Vector2.Dot(segment1Center - BoundingBox.Center.ToVector2(), segment2Center - ch.BoundingBox.Center.ToVector2()) > 0) { continue; }
if (Vector2.DistanceSquared(segments[i].Start.Pos, ch.segments[j].Start.Pos) < mergeDistSqr &&
Vector2.DistanceSquared(segments[i].End.Pos, ch.segments[j].End.Pos) < mergeDistSqr)
{
ignoreEdge[i] = true;
ch.ignoreEdge[j] = true;
MergeSegments(segments[i], ch.segments[j], true);
}
else if (Vector2.DistanceSquared(segments[i].Start.Pos, ch.segments[j].End.Pos) < mergeDistSqr &&
Vector2.DistanceSquared(segments[i].End.Pos, ch.segments[j].Start.Pos) < mergeDistSqr)
{
ignoreEdge[i] = true;
ch.ignoreEdge[j] = true;
MergeSegments(segments[i], ch.segments[j], false);
}
} }
} }
losOffsets[i] = closest - losVertices[i].Pos;
overlappingHulls.Add(ch);
ch.overlappingHulls.Add(this);
changed = true;
} }
if (changed && refreshOtherOverlappingHulls)
for (int i = 0; i < segments.Length; i++)
{ {
if (ignoreEdge[i]) { continue; } foreach (var overlapping in overlappingHulls)
if (Vector2.DistanceSquared(segments[i].Start.Pos, segments[i].End.Pos) < 1.0f) { continue; }
for (int j = 0; j < ch.segments.Length; j++)
{ {
if (ch.ignoreEdge[j]) { continue; } overlapping.MergeLosVertices(this, refreshOtherOverlappingHulls: false);
if (Vector2.DistanceSquared(ch.segments[j].Start.Pos, ch.segments[j].End.Pos) < 1.0f) { continue; }
if (IsSegmentAInB(segments[i], ch.segments[j]))
{
ignoreEdge[i] = true;
if (Vector2.DistanceSquared(ch.segments[j].Start.Pos, segments[i].Start.Pos) < 4.0f)
{
ch.ShiftSegmentPoint(j, false, segments[i].End.Pos);
}
else if (Vector2.DistanceSquared(ch.segments[j].Start.Pos, segments[i].End.Pos) < 4.0f)
{
ch.ShiftSegmentPoint(j, false, segments[i].Start.Pos);
}
if (Vector2.DistanceSquared(ch.segments[j].End.Pos, segments[i].Start.Pos) < 4.0f)
{
ch.ShiftSegmentPoint(j, true, segments[i].End.Pos);
}
else if (Vector2.DistanceSquared(ch.segments[j].End.Pos, segments[i].End.Pos) < 4.0f)
{
ch.ShiftSegmentPoint(j, true, segments[i].Start.Pos);
}
}
else if (IsSegmentAInB(ch.segments[j], segments[i]))
{
ch.ignoreEdge[j] = true;
if (Vector2.DistanceSquared(segments[i].Start.Pos, ch.segments[j].Start.Pos) < 4.0f)
{
ShiftSegmentPoint(i, false, ch.segments[j].End.Pos);
}
else if (Vector2.DistanceSquared(segments[i].Start.Pos, ch.segments[j].End.Pos) < 4.0f)
{
ShiftSegmentPoint(i, false, ch.segments[j].Start.Pos);
}
if (Vector2.DistanceSquared(segments[i].End.Pos, ch.segments[j].Start.Pos) < 4.0f)
{
ShiftSegmentPoint(i, true, ch.segments[j].End.Pos);
}
else if (Vector2.DistanceSquared(segments[i].End.Pos, ch.segments[j].End.Pos) < 4.0f)
{
ShiftSegmentPoint(i, true, ch.segments[j].Start.Pos);
}
}
} }
} }
//ignore edges that are inside some other convex hull
for (int i = 0; i < vertices.Length; i++)
{
if (ch.IsPointInside(vertices[i].Pos))
{
if (ch.IsPointInside(vertices[(i + 1) % vertices.Length].Pos))
{
ignoreEdge[i] = true;
overlappingHulls.Add(ch);
}
}
}
}
private void ShiftSegmentPoint(int segmentIndex, bool end, Vector2 newPos)
{
var segment = segments[segmentIndex];
losOffsets[segmentIndex] ??= new VectorPair();
bool flipped = false;
if (Vector2.DistanceSquared(vertices[segmentIndex].Pos, segment.Start.Pos) > Vector2.DistanceSquared(vertices[segmentIndex].Pos, segment.End.Pos))
{
flipped = true;
}
if (end == !flipped)
{
losOffsets[segmentIndex].B = newPos;
}
else
{
losOffsets[segmentIndex].A = newPos;
}
}
public bool IsSegmentAInB(Segment a, Segment b)
{
if (Vector2.DistanceSquared(a.Start.Pos, a.End.Pos) > Vector2.DistanceSquared(b.Start.Pos, b.End.Pos))
{
return false;
}
Vector2 min = new Vector2(Math.Min(b.Start.Pos.X, b.End.Pos.X), Math.Min(b.Start.Pos.Y, b.End.Pos.Y));
Vector2 max = new Vector2(Math.Max(b.Start.Pos.X, b.End.Pos.X), Math.Max(b.Start.Pos.Y, b.End.Pos.Y));
min.X -= 1.0f; min.Y -= 1.0f;
max.X += 1.0f; max.Y += 1.0f;
if (a.Start.Pos.X < min.X) { return false; }
if (a.Start.Pos.Y < min.Y) { return false; }
if (a.End.Pos.X < min.X) { return false; }
if (a.End.Pos.Y < min.Y) { return false; }
if (a.Start.Pos.X > max.X) { return false; }
if (a.Start.Pos.Y > max.Y) { return false; }
if (a.End.Pos.X > max.X) { return false; }
if (a.End.Pos.Y > max.Y) { return false; }
float startDist = MathUtils.LineToPointDistanceSquared(b.Start.Pos, b.End.Pos, a.Start.Pos);
if (startDist > 1.0f) { return false; }
float endDist = MathUtils.LineToPointDistanceSquared(b.Start.Pos, b.End.Pos, a.End.Pos);
if (endDist > 1.0f) { return false; }
return true;
}
public bool IsPointInside(Vector2 point)
{
if (!BoundingBox.Contains(point)) { return false; }
Vector2 center = (vertices[0].Pos + vertices[1].Pos + vertices[2].Pos + vertices[3].Pos) * 0.25f;
for (int i = 0; i < 4; i++)
{
Vector2 segmentVector = vertices[(i + 1) % 4].Pos - vertices[i].Pos;
Vector2 centerToVertex = center - vertices[i].Pos;
Vector2 pointToVertex = point - vertices[i].Pos;
float dotCenter = Vector2.Dot(centerToVertex, segmentVector);
float dotPoint = Vector2.Dot(pointToVertex, segmentVector);
if ((dotCenter > 0f && dotPoint < 0f) || (dotCenter < 0f && dotPoint > 0f)) { return false; }
}
return true;
}
private void MergeSegments(Segment segment1, Segment segment2, bool startPointsMatch)
{
int startPointIndex = -1, endPointIndex = -1;
for (int i = 0; i < vertices.Length; i++)
{
if (vertices[i].Pos.NearlyEquals(segment1.Start.Pos))
startPointIndex = i;
else if (vertices[i].Pos.NearlyEquals(segment1.End.Pos))
endPointIndex = i;
}
if (startPointIndex == -1 || endPointIndex == -1) { return; }
int startPoint2Index = -1, endPoint2Index = -1;
for (int i = 0; i < segment2.ConvexHull.vertices.Length; i++)
{
if (segment2.ConvexHull.vertices[i].Pos.NearlyEquals(segment2.Start.Pos))
startPoint2Index = i;
else if (segment2.ConvexHull.vertices[i].Pos.NearlyEquals(segment2.End.Pos))
endPoint2Index = i;
}
if (startPoint2Index == -1 || endPoint2Index == -1) { return; }
if (startPointsMatch)
{
losVertices[startPointIndex].Pos = segment2.ConvexHull.losVertices[startPoint2Index].Pos =
(segment1.Start.Pos + segment2.Start.Pos) / 2.0f;
losVertices[endPointIndex].Pos = segment2.ConvexHull.losVertices[endPoint2Index].Pos =
(segment1.End.Pos + segment2.End.Pos) / 2.0f;
}
else
{
if (Vector2.DistanceSquared(losVertices[startPointIndex].Pos, segment1.Start.Pos) <
Vector2.DistanceSquared(losVertices[startPointIndex].Pos, segment1.End.Pos))
{
losVertices[startPointIndex].Pos = segment2.ConvexHull.losVertices[startPoint2Index].Pos =
(segment1.Start.Pos + segment2.End.Pos) / 2.0f;
losVertices[endPointIndex].Pos = segment2.ConvexHull.losVertices[endPoint2Index].Pos =
(segment1.End.Pos + segment2.Start.Pos) / 2.0f;
}
else
{
losVertices[startPointIndex].Pos = segment2.ConvexHull.losVertices[startPoint2Index].Pos =
(segment1.End.Pos + segment2.Start.Pos) / 2.0f;
losVertices[endPointIndex].Pos = segment2.ConvexHull.losVertices[endPoint2Index].Pos =
(segment1.Start.Pos + segment2.End.Pos) / 2.0f;
}
}
overlappingHulls.Add(segment2.ConvexHull);
segment2.ConvexHull.overlappingHulls.Add(this);
} }
public void Rotate(Vector2 origin, float amount) public void Rotate(Vector2 origin, float amount)
@@ -461,7 +259,7 @@ namespace Barotrauma.Lights
Matrix.CreateTranslation(-origin.X, -origin.Y, 0.0f) * Matrix.CreateTranslation(-origin.X, -origin.Y, 0.0f) *
Matrix.CreateRotationZ(amount) * Matrix.CreateRotationZ(amount) *
Matrix.CreateTranslation(origin.X, origin.Y, 0.0f); Matrix.CreateTranslation(origin.X, origin.Y, 0.0f);
SetVertices(vertices.Select(v => v.Pos).ToArray(), rotationMatrix: rotationMatrix); SetVertices(vertices.Select(v => v.Pos).ToArray(), losVertices.Select(v => v.Pos).ToArray(), rotationMatrix: rotationMatrix);
} }
private void CalculateDimensions() private void CalculateDimensions()
@@ -470,11 +268,10 @@ namespace Barotrauma.Lights
for (int i = 1; i < vertices.Length; i++) for (int i = 1; i < vertices.Length; i++)
{ {
if (vertices[i].Pos.X < minX) minX = vertices[i].Pos.X; minX = Math.Min(minX, vertices[i].Pos.X);
if (vertices[i].Pos.Y < minY) minY = vertices[i].Pos.Y; minY = Math.Min(minY, vertices[i].Pos.Y);
maxX = Math.Max(maxX, vertices[i].Pos.X);
if (vertices[i].Pos.X > maxX) maxX = vertices[i].Pos.X; maxY = Math.Max(maxY, vertices[i].Pos.Y);
if (vertices[i].Pos.Y > minY) maxY = vertices[i].Pos.Y;
} }
BoundingBox = new Rectangle((int)minX, (int)minY, (int)(maxX - minX), (int)(maxY - minY)); BoundingBox = new Rectangle((int)minX, (int)minY, (int)(maxX - minX), (int)(maxY - minY));
@@ -485,21 +282,17 @@ namespace Barotrauma.Lights
for (int i = 0; i < vertices.Length; i++) for (int i = 0; i < vertices.Length; i++)
{ {
vertices[i].Pos += amount; vertices[i].Pos += amount;
losVertices[i].Pos += amount;
losOffsets[i] = null;
segments[i].Start.Pos += amount; segments[i].Start.Pos += amount;
segments[i].End.Pos += amount; segments[i].End.Pos += amount;
} }
for (int i = 0; i < losVertices.Length; i++)
{
losVertices[i].Pos += amount;
}
LastVertexChangeTime = (float)Timing.TotalTime; LastVertexChangeTime = (float)Timing.TotalTime;
overlappingHulls.Clear(); overlappingHulls.Clear();
for (int i = 0; i < 4; i++)
{
ignoreEdge[i] = false;
}
CalculateDimensions(); CalculateDimensions();
@@ -511,8 +304,8 @@ namespace Barotrauma.Lights
overlappingHulls.Clear(); overlappingHulls.Clear();
foreach (ConvexHull ch in chList.List) foreach (ConvexHull ch in chList.List)
{ {
MergeOverlappingSegments(ch); MergeLosVertices(ch);
ch.MergeOverlappingSegments(this); ch.MergeLosVertices(this);
} }
} }
} }
@@ -525,23 +318,23 @@ namespace Barotrauma.Lights
foreach (ConvexHull ch in chList.List) foreach (ConvexHull ch in chList.List)
{ {
ch.overlappingHulls.Clear(); ch.overlappingHulls.Clear();
for (int i = 0; i < 4; i++) for (int i = 0; i < ch.losOffsets.Length; i++)
{ {
ch.ignoreEdge[i] = false; ch.losOffsets[i] = Vector2.Zero;
} }
} }
for (int i = 0; i < chList.List.Count; i++) for (int i = 0; i < chList.List.Count; i++)
{ {
for (int j = i + 1; j < chList.List.Count; j++) for (int j = i + 1; j < chList.List.Count; j++)
{ {
chList.List[i].MergeOverlappingSegments(chList.List[j]); chList.List[i].MergeLosVertices(chList.List[j]);
chList.List[j].MergeOverlappingSegments(chList.List[i]); chList.List[j].MergeLosVertices(chList.List[i]);
} }
} }
} }
} }
public void SetVertices(Vector2[] points, bool mergeOverlappingSegments = true, Matrix? rotationMatrix = null) public void SetVertices(Vector2[] points, Vector2[] losPoints, bool mergeOverlappingSegments = true, Matrix? rotationMatrix = null)
{ {
Debug.Assert(points.Length == 4, "Only rectangular convex hulls are supported"); Debug.Assert(points.Length == 4, "Only rectangular convex hulls are supported");
@@ -549,39 +342,23 @@ namespace Barotrauma.Lights
for (int i = 0; i < 4; i++) for (int i = 0; i < 4; i++)
{ {
vertices[i] = new SegmentPoint(points[i], this); vertices[i] = new SegmentPoint(points[i], this);
losVertices[i] = new SegmentPoint(points[i], this);
losOffsets[i] = null;
} }
for (int i = 0; i < 2; i++)
for (int i = 0; i < 4; i++)
{ {
ignoreEdge[i] = false; losVertices[i] = new SegmentPoint(losPoints[i], this);
} }
overlappingHulls.Clear(); overlappingHulls.Clear();
int margin = 0;
if (Math.Abs(points[0].X - points[2].X) < Math.Abs(points[0].Y - points[2].Y))
{
losVertices[0].Pos = new Vector2(points[0].X + margin, points[0].Y);
losVertices[1].Pos = new Vector2(points[1].X + margin, points[1].Y);
losVertices[2].Pos = new Vector2(points[2].X - margin, points[2].Y);
losVertices[3].Pos = new Vector2(points[3].X - margin, points[3].Y);
}
else
{
losVertices[0].Pos = new Vector2(points[0].X, points[0].Y + margin);
losVertices[1].Pos = new Vector2(points[1].X, points[1].Y - margin);
losVertices[2].Pos = new Vector2(points[2].X, points[2].Y - margin);
losVertices[3].Pos = new Vector2(points[3].X, points[3].Y + margin);
}
if (rotationMatrix.HasValue) if (rotationMatrix.HasValue)
{ {
for (int i = 0; i < vertices.Length; i++) for (int i = 0; i < vertices.Length; i++)
{ {
vertices[i].Pos = Vector2.Transform(vertices[i].Pos, rotationMatrix.Value); vertices[i].Pos = Vector2.Transform(vertices[i].Pos, rotationMatrix.Value);
}
for (int i = 0; i < losVertices.Length; i++)
{
losVertices[i].Pos = Vector2.Transform(losVertices[i].Pos, rotationMatrix.Value); losVertices[i].Pos = Vector2.Transform(losVertices[i].Pos, rotationMatrix.Value);
} }
} }
@@ -602,7 +379,7 @@ namespace Barotrauma.Lights
overlappingHulls.Clear(); overlappingHulls.Clear();
foreach (ConvexHull ch in chList.List) foreach (ConvexHull ch in chList.List)
{ {
MergeOverlappingSegments(ch); MergeLosVertices(ch);
} }
} }
} }
@@ -624,31 +401,17 @@ namespace Barotrauma.Lights
/// <summary> /// <summary>
/// Returns the segments that are facing towards viewPosition /// Returns the segments that are facing towards viewPosition
/// </summary> /// </summary>
public void GetVisibleSegments(Vector2 viewPosition, List<Segment> visibleSegments, bool ignoreEdges) public void GetVisibleSegments(Vector2 viewPosition, List<Segment> visibleSegments)
{ {
for (int i = 0; i < 4; i++) for (int i = 0; i < 4; i++)
{ {
if (ignoreEdge[i] && ignoreEdges) continue; if (IsSegmentFacing(vertices[i].WorldPos, vertices[(i + 1) % 4].WorldPos, viewPosition))
Vector2 pos1 = vertices[i].WorldPos;
Vector2 pos2 = vertices[(i + 1) % 4].WorldPos;
Vector2 middle = (pos1 + pos2) / 2;
Vector2 L = viewPosition - middle;
Vector2 N = new Vector2(
-(pos2.Y - pos1.Y),
pos2.X - pos1.X);
if (Vector2.Dot(N, L) > 0)
{ {
visibleSegments.Add(segments[i]); visibleSegments.Add(segments[i]);
} }
} }
} }
public void RefreshWorldPositions() public void RefreshWorldPositions()
{ {
for (int i = 0; i < 4; i++) for (int i = 0; i < 4; i++)
@@ -676,34 +439,12 @@ namespace Barotrauma.Lights
ShadowVertexCount = 0; ShadowVertexCount = 0;
//compute facing of each edge, using N*L for (int i = 0; i < losVertices.Length; i++)
for (int i = 0; i < 4; i++)
{ {
if (ignoreEdge[i])
{
backFacing[i] = false;
continue;
}
Vector2 firstVertex = losVertices[i].Pos;
Vector2 secondVertex = losVertices[(i+1) % 4].Pos;
Vector2 L = lightSourcePos - ((firstVertex + secondVertex) / 2.0f);
Vector2 N = new Vector2(
-(secondVertex.Y - firstVertex.Y),
secondVertex.X - firstVertex.X);
backFacing[i] = (Vector2.Dot(N, L) < 0);
}
ShadowVertexCount = 0;
for (int i = 0; i < 4; i++)
{
if (!backFacing[i]) { continue; }
int currentIndex = i; int currentIndex = i;
Vector3 vertexPos0 = new Vector3(losOffsets[currentIndex]?.A ?? losVertices[currentIndex].Pos, 0.0f); int nextIndex = (currentIndex + 1) % 2;
Vector3 vertexPos1 = new Vector3(losOffsets[currentIndex]?.B ?? losVertices[(currentIndex + 1) % 4].Pos, 0.0f); Vector3 vertexPos0 = new Vector3(losVertices[currentIndex].Pos + losOffsets[currentIndex], 0.0f);
Vector3 vertexPos1 = new Vector3(losVertices[nextIndex].Pos + losOffsets[nextIndex], 0.0f);
if (Vector3.DistanceSquared(vertexPos0, vertexPos1) < 1.0f) { continue; } if (Vector3.DistanceSquared(vertexPos0, vertexPos1) < 1.0f) { continue; }
@@ -754,9 +495,24 @@ namespace Barotrauma.Lights
ShadowVertexCount += 6; ShadowVertexCount += 6;
} }
if (IsSegmentFacing(losVertices[0].Pos, losVertices[1].Pos, lightSourcePos))
{
Array.Reverse(ShadowVertices);
}
CalculateLosPenumbraVertices(lightSourcePos); CalculateLosPenumbraVertices(lightSourcePos);
} }
private static bool IsSegmentFacing(Vector2 segmentPos1, Vector2 segmentPos2, Vector2 viewPosition)
{
Vector2 segmentMid = (segmentPos1 + segmentPos2) / 2;
Vector2 segmentDiff = segmentPos2 - segmentPos1;
Vector2 segmentNormal = new Vector2(-segmentDiff.Y, segmentDiff.X);
Vector2 viewDirection = viewPosition - segmentMid;
return Vector2.Dot(segmentNormal, viewDirection) > 0;
}
private void CalculateLosPenumbraVertices(Vector2 lightSourcePos) private void CalculateLosPenumbraVertices(Vector2 lightSourcePos)
{ {
Vector3 offset = Vector3.Zero; Vector3 offset = Vector3.Zero;
@@ -766,73 +522,101 @@ namespace Barotrauma.Lights
} }
PenumbraVertexCount = 0; PenumbraVertexCount = 0;
for (int i = 0; i < 4; i++) for (int i = 0; i < losVertices.Length; i++)
{ {
int currentIndex = i; int currentIndex = i;
int prevIndex = (i + 3) % 4; int nextIndex = (i + 1) % 2;
int nextIndex = (i + 1) % 4; Vector2 vertexPos0 = losVertices[currentIndex].Pos + losOffsets[currentIndex];
bool disjointed = losOffsets[i]?.A != null; Vector2 vertexPos1 = losVertices[nextIndex].Pos + losOffsets[nextIndex];
Vector2 vertexPos0 = losOffsets[currentIndex]?.A ?? losVertices[currentIndex].Pos;
Vector2 vertexPos1 = losOffsets[currentIndex]?.B ?? losVertices[nextIndex].Pos;
if (Vector2.DistanceSquared(vertexPos0, vertexPos1) < 1.0f) { continue; } if (Vector2.DistanceSquared(vertexPos0, vertexPos1) < 1.0f) { continue; }
if (backFacing[currentIndex] && (disjointed || (!backFacing[prevIndex]))) Vector3 penumbraStart = new Vector3(vertexPos0, 0.0f);
PenumbraVertices[PenumbraVertexCount] = new VertexPositionTexture
{ {
Vector3 penumbraStart = new Vector3(vertexPos0, 0.0f); Position = penumbraStart + offset,
TextureCoordinate = new Vector2(0.0f, 1.0f)
};
PenumbraVertices[PenumbraVertexCount] = new VertexPositionTexture for (int j = 0; j < 2; j++)
{ {
Position = penumbraStart + offset, PenumbraVertices[PenumbraVertexCount + j + 1] = new VertexPositionTexture();
TextureCoordinate = new Vector2(0.0f, 1.0f) Vector3 vertexDir = penumbraStart - new Vector3(lightSourcePos, 0);
}; vertexDir.Normalize();
for (int j = 0; j < 2; j++) Vector3 normal = (j == 0) ? new Vector3(-vertexDir.Y, vertexDir.X, 0.0f) : new Vector3(vertexDir.Y, -vertexDir.X, 0.0f) * 0.05f;
{
PenumbraVertices[PenumbraVertexCount + j + 1] = new VertexPositionTexture();
Vector3 vertexDir = penumbraStart - new Vector3(lightSourcePos, 0);
vertexDir.Normalize();
Vector3 normal = (j == 0) ? new Vector3(-vertexDir.Y, vertexDir.X, 0.0f) : new Vector3(vertexDir.Y, -vertexDir.X, 0.0f) * 0.05f; vertexDir = penumbraStart - (new Vector3(lightSourcePos, 0) - normal * 20.0f);
vertexDir.Normalize();
PenumbraVertices[PenumbraVertexCount + j + 1].Position = new Vector3(lightSourcePos, 0) + vertexDir * 9000 + offset;
vertexDir = penumbraStart - (new Vector3(lightSourcePos, 0) - normal * 20.0f); PenumbraVertices[PenumbraVertexCount + j + 1].TextureCoordinate = (j == 0) ? new Vector2(0.05f, 0.0f) : new Vector2(1.0f, 0.0f);
vertexDir.Normalize();
PenumbraVertices[PenumbraVertexCount + j + 1].Position = new Vector3(lightSourcePos, 0) + vertexDir * 9000 + offset;
PenumbraVertices[PenumbraVertexCount + j + 1].TextureCoordinate = (j == 0) ? new Vector2(0.05f, 0.0f) : new Vector2(1.0f, 0.0f);
}
PenumbraVertexCount += 3;
} }
disjointed = losOffsets[i]?.B != null; PenumbraVertexCount += 3;
if (backFacing[currentIndex] && (disjointed || (!backFacing[nextIndex])))
penumbraStart = new Vector3(vertexPos1, 0.0f);
PenumbraVertices[PenumbraVertexCount] = new VertexPositionTexture
{ {
Vector3 penumbraStart = new Vector3(vertexPos1, 0.0f); Position = penumbraStart + offset,
TextureCoordinate = new Vector2(0.0f, 1.0f)
};
PenumbraVertices[PenumbraVertexCount] = new VertexPositionTexture for (int j = 0; j < 2; j++)
{ {
Position = penumbraStart + offset, PenumbraVertices[PenumbraVertexCount + (1 - j) + 1] = new VertexPositionTexture();
TextureCoordinate = new Vector2(0.0f, 1.0f) Vector3 vertexDir = penumbraStart - new Vector3(lightSourcePos, 0);
}; vertexDir.Normalize();
for (int j = 0; j < 2; j++) Vector3 normal = (j == 0) ? new Vector3(-vertexDir.Y, vertexDir.X, 0.0f) : new Vector3(vertexDir.Y, -vertexDir.X, 0.0f) * 0.05f;
{
PenumbraVertices[PenumbraVertexCount + (1 - j) + 1] = new VertexPositionTexture();
Vector3 vertexDir = penumbraStart - new Vector3(lightSourcePos, 0);
vertexDir.Normalize();
Vector3 normal = (j == 0) ? new Vector3(-vertexDir.Y, vertexDir.X, 0.0f) : new Vector3(vertexDir.Y, -vertexDir.X, 0.0f) * 0.05f; vertexDir = penumbraStart - (new Vector3(lightSourcePos, 0) + normal * 20.0f);
vertexDir.Normalize();
PenumbraVertices[PenumbraVertexCount + (1 - j) + 1].Position = new Vector3(lightSourcePos, 0) + vertexDir * 9000 + offset;
vertexDir = penumbraStart - (new Vector3(lightSourcePos, 0) + normal * 20.0f); PenumbraVertices[PenumbraVertexCount + (1 - j) + 1].TextureCoordinate = (j == 0) ? new Vector2(0.05f, 0.0f) : new Vector2(1.0f, 0.0f);
vertexDir.Normalize();
PenumbraVertices[PenumbraVertexCount + (1 - j) + 1].Position = new Vector3(lightSourcePos, 0) + vertexDir * 9000 + offset;
PenumbraVertices[PenumbraVertexCount + (1 - j) + 1].TextureCoordinate = (j == 0) ? new Vector2(0.05f, 0.0f) : new Vector2(1.0f, 0.0f);
}
PenumbraVertexCount += 3;
} }
PenumbraVertexCount += 3;
}
}
public void DebugDraw(SpriteBatch spriteBatch)
{
//RecalculateAll(Submarine.MainSub);
//RefreshWorldPositions();
DrawLine(losVertices[0].Pos, losVertices[1].Pos, Color.Gray * 0.5f, width: 3);
DrawLine(losVertices[0].Pos + losOffsets[0], losVertices[1].Pos + losOffsets[1], Color.LightGreen, width: 2);
DrawLine(GameMain.GameScreen.Cam.Position + Vector2.One * 1000, GameMain.GameScreen.Cam.Position - Vector2.One * 1000, Color.Magenta, width: 2);
if (GameMain.LightManager.LightingEnabled)
{
for (int i = 0; i < vertices.Length; i++)
{
Vector2 start = vertices[i].Pos;
Vector2 end = vertices[(i + 1) % 4].Pos;
DrawLine(
start,
end, Color.Yellow * 0.5f,
width: 4);
}
}
void DrawLine(Vector2 vertexPos0, Vector2 vertexPos1, Color color, int width)
{
if (ParentEntity != null && ParentEntity.Submarine != null)
{
vertexPos0 += ParentEntity.Submarine.DrawPosition;
vertexPos1 += ParentEntity.Submarine.DrawPosition;
}
Vector2 viewTargetPos = LightManager.ViewTarget.WorldPosition;
float alpha = IsSegmentFacing(vertexPos0, vertexPos1, viewTargetPos) ? 1.0f : 0.5f;
vertexPos0.Y = -vertexPos0.Y;
vertexPos1.Y = -vertexPos1.Y;
GUI.DrawLine(spriteBatch, vertexPos0, vertexPos1, color * alpha, width: width);
} }
} }
@@ -903,16 +687,13 @@ namespace Barotrauma.Lights
{ {
HullLists.Remove(chList); HullLists.Remove(chList);
} }
foreach (ConvexHull ch2 in overlappingHulls) //create a new list because MergeLosVertices can edit overlappingHulls
foreach (ConvexHull ch2 in overlappingHulls.ToList())
{ {
for (int i = 0; i < 4; i++)
{
ch2.ignoreEdge[i] = false;
}
ch2.overlappingHulls.Remove(this); ch2.overlappingHulls.Remove(this);
foreach (ConvexHull ch in chList.List) foreach (ConvexHull ch in chList.List)
{ {
ch.MergeOverlappingSegments(ch2); ch.MergeLosVertices(ch2);
} }
} }
} }
@@ -5,6 +5,7 @@ using System.Linq;
using System; using System;
using Barotrauma.Items.Components; using Barotrauma.Items.Components;
using Barotrauma.Extensions; using Barotrauma.Extensions;
using System.Threading;
namespace Barotrauma.Lights namespace Barotrauma.Lights
{ {
@@ -22,6 +23,9 @@ namespace Barotrauma.Lights
/// </summary> /// </summary>
const float ObstructLightsBehindCharactersZoomThreshold = 0.5f; const float ObstructLightsBehindCharactersZoomThreshold = 0.5f;
private Thread rayCastThread;
private Queue<RayCastTask> pendingRayCasts = new Queue<RayCastTask>();
public static Entity ViewTarget { get; set; } public static Entity ViewTarget { get; set; }
private float currLightMapScale; private float currLightMapScale;
@@ -58,6 +62,8 @@ namespace Barotrauma.Lights
private readonly List<LightSource> lights; private readonly List<LightSource> lights;
public bool DebugLos;
public bool LosEnabled = true; public bool LosEnabled = true;
public float LosAlpha = 1f; public float LosAlpha = 1f;
public LosMode LosMode = LosMode.Transparent; public LosMode LosMode = LosMode.Transparent;
@@ -68,6 +74,8 @@ namespace Barotrauma.Lights
private readonly Texture2D visionCircle; private readonly Texture2D visionCircle;
private readonly Texture2D gapGlowTexture;
private Vector2 losOffset; private Vector2 losOffset;
private int recalculationCount; private int recalculationCount;
@@ -85,8 +93,16 @@ namespace Barotrauma.Lights
AmbientLight = new Color(20, 20, 20, 255); AmbientLight = new Color(20, 20, 20, 255);
rayCastThread = new Thread(UpdateRayCasts)
{
Name = "LightManager Raycast thread",
IsBackground = true //this should kill the thread if the game crashes
};
rayCastThread.Start();
visionCircle = Sprite.LoadTexture("Content/Lights/visioncircle.png"); visionCircle = Sprite.LoadTexture("Content/Lights/visioncircle.png");
highlightRaster = Sprite.LoadTexture("Content/UI/HighlightRaster.png"); highlightRaster = Sprite.LoadTexture("Content/UI/HighlightRaster.png");
gapGlowTexture = Sprite.LoadTexture("Content/Lights/pointlight_rays.png");
GameMain.Instance.ResolutionChanged += () => GameMain.Instance.ResolutionChanged += () =>
{ {
@@ -100,15 +116,12 @@ namespace Barotrauma.Lights
LosEffect = EffectLoader.Load("Effects/losshader"); LosEffect = EffectLoader.Load("Effects/losshader");
SolidColorEffect = EffectLoader.Load("Effects/solidcolor"); SolidColorEffect = EffectLoader.Load("Effects/solidcolor");
if (lightEffect == null) lightEffect ??= new BasicEffect(GameMain.Instance.GraphicsDevice)
{
lightEffect = new BasicEffect(GameMain.Instance.GraphicsDevice)
{ {
VertexColorEnabled = true, VertexColorEnabled = true,
TextureEnabled = true, TextureEnabled = true,
Texture = LightSource.LightTexture Texture = LightSource.LightTexture
}; };
}
}); });
} }
@@ -155,7 +168,7 @@ namespace Barotrauma.Lights
{ {
foreach (LightSource light in lights) foreach (LightSource light in lights)
{ {
light.NeedsHullCheck = true; light.HullsUpToDate.Clear();
light.NeedsRecalculation = true; light.NeedsRecalculation = true;
} }
} }
@@ -176,6 +189,51 @@ namespace Barotrauma.Lights
} }
} }
private class RayCastTask
{
public LightSource LightSource;
public Vector2 DrawPos;
public float Rotation;
public RayCastTask(LightSource lightSource, Vector2 drawPos, float rotation)
{
LightSource = lightSource;
DrawPos = drawPos;
Rotation = rotation;
}
public void Calculate()
{
LightSource.RayCastTask(DrawPos, Rotation);
}
}
private static readonly object mutex = new object();
public void AddRayCastTask(LightSource lightSource, Vector2 drawPos, float rotation)
{
lock (mutex)
{
if (pendingRayCasts.Any(p => p.LightSource == lightSource)) { return; }
pendingRayCasts.Enqueue(new RayCastTask(lightSource, drawPos, rotation));
}
}
private void UpdateRayCasts()
{
while (true)
{
lock (mutex)
{
while (pendingRayCasts.Count > 0)
{
pendingRayCasts.Dequeue().Calculate();
}
}
Thread.Sleep(10);
}
}
public void RenderLightMap(GraphicsDevice graphics, SpriteBatch spriteBatch, Camera cam, RenderTarget2D backgroundObstructor = null) public void RenderLightMap(GraphicsDevice graphics, SpriteBatch spriteBatch, Camera cam, RenderTarget2D backgroundObstructor = null)
{ {
if (!LightingEnabled) { return; } if (!LightingEnabled) { return; }
@@ -287,8 +345,8 @@ namespace Barotrauma.Lights
foreach (LightSource light in activeLights) foreach (LightSource light in activeLights)
{ {
if (!light.IsBackground || light.CurrentBrightness <= 0.0f) { continue; } if (!light.IsBackground || light.CurrentBrightness <= 0.0f) { continue; }
light.DrawSprite(spriteBatch, cam);
light.DrawLightVolume(spriteBatch, lightEffect, transform, recalculationCount < MaxLightVolumeRecalculationsPerFrame, ref recalculationCount); light.DrawLightVolume(spriteBatch, lightEffect, transform, recalculationCount < MaxLightVolumeRecalculationsPerFrame, ref recalculationCount);
light.DrawSprite(spriteBatch, cam);
} }
GameMain.ParticleManager.Draw(spriteBatch, true, null, Particles.ParticleBlendState.Additive); GameMain.ParticleManager.Draw(spriteBatch, true, null, Particles.ParticleBlendState.Additive);
spriteBatch.End(); spriteBatch.End();
@@ -307,15 +365,46 @@ namespace Barotrauma.Lights
} }
spriteBatch.End(); spriteBatch.End();
SolidColorEffect.CurrentTechnique = SolidColorEffect.Techniques["SolidColor"]; spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive, transformMatrix: spriteBatchTransform);
SolidColorEffect.Parameters["color"].SetValue(AmbientLight.Opaque().ToVector4()); Vector3 glowColorHSV = ToolBox.RGBToHSV(AmbientLight);
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, transformMatrix: spriteBatchTransform, effect: SolidColorEffect); glowColorHSV.Z = Math.Max(glowColorHSV.Z, 0.4f);
Submarine.DrawDamageable(spriteBatch, null); Color glowColor = ToolBox.HSVToRGB(glowColorHSV.X, glowColorHSV.Y, glowColorHSV.Z);
Vector2 glowSpriteSize = new Vector2(gapGlowTexture.Width, gapGlowTexture.Height);
foreach (var gap in Gap.GapList)
{
if (gap.IsRoomToRoom || gap.Open <= 0.0f || gap.ConnectedWall == null) { continue; }
float a = MathHelper.Lerp(0.5f, 1.0f,
PerlinNoise.GetPerlin((float)Timing.TotalTime * 0.05f, gap.GlowEffectT));
float scale = MathHelper.Lerp(0.5f, 2.0f,
PerlinNoise.GetPerlin((float)Timing.TotalTime * 0.01f, gap.GlowEffectT));
float rot = PerlinNoise.GetPerlin((float)Timing.TotalTime * 0.001f, gap.GlowEffectT) * MathHelper.TwoPi;
Vector2 spriteScale = new Vector2(gap.Rect.Width, gap.Rect.Height) / glowSpriteSize;
Vector2 drawPos = new Vector2(gap.DrawPosition.X, -gap.DrawPosition.Y);
spriteBatch.Draw(gapGlowTexture,
drawPos,
null,
glowColor * a,
rot,
glowSpriteSize / 2,
scale: Math.Max(spriteScale.X, spriteScale.Y) * scale,
SpriteEffects.None,
layerDepth: 0);
}
spriteBatch.End();
GameMain.GameScreen.DamageEffect.CurrentTechnique = GameMain.GameScreen.DamageEffect.Techniques["StencilShaderSolidColor"];
GameMain.GameScreen.DamageEffect.Parameters["solidColor"].SetValue(Color.Black.ToVector4());
spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.NonPremultiplied, SamplerState.LinearWrap, transformMatrix: spriteBatchTransform, effect: GameMain.GameScreen.DamageEffect);
Submarine.DrawDamageable(spriteBatch, GameMain.GameScreen.DamageEffect);
spriteBatch.End(); spriteBatch.End();
graphics.BlendState = BlendState.Additive; graphics.BlendState = BlendState.Additive;
//draw the focused item and character to highlight them, //draw the focused item and character to highlight them,
//and light sprites (done before drawing the actual light volumes so we can make characters obstruct the highlights and sprites) //and light sprites (done before drawing the actual light volumes so we can make characters obstruct the highlights and sprites)
//--------------------------------------------------------------------------------------------------- //---------------------------------------------------------------------------------------------------
@@ -449,9 +538,9 @@ namespace Barotrauma.Lights
{ {
highlightedEntities.Add(Character.Controlled.FocusedCharacter); highlightedEntities.Add(Character.Controlled.FocusedCharacter);
} }
foreach (Item item in Item.ItemList) foreach (MapEntity me in MapEntity.HighlightedEntities)
{ {
if ((item.IsHighlighted || item.IconStyle != null) && !highlightedEntities.Contains(item)) if (me is Item item && item != Character.Controlled.FocusedItem)
{ {
highlightedEntities.Add(item); highlightedEntities.Add(item);
} }
@@ -565,7 +654,7 @@ namespace Barotrauma.Lights
public void UpdateObstructVision(GraphicsDevice graphics, SpriteBatch spriteBatch, Camera cam, Vector2 lookAtPosition) public void UpdateObstructVision(GraphicsDevice graphics, SpriteBatch spriteBatch, Camera cam, Vector2 lookAtPosition)
{ {
if ((!LosEnabled || LosMode == LosMode.None) && !ObstructVision) return; if ((!LosEnabled || LosMode == LosMode.None) && !ObstructVision) { return; }
if (ViewTarget == null) return; if (ViewTarget == null) return;
graphics.SetRenderTarget(LosTexture); graphics.SetRenderTarget(LosTexture);
@@ -597,23 +686,30 @@ namespace Barotrauma.Lights
if (LosEnabled && LosMode != LosMode.None && ViewTarget != null) if (LosEnabled && LosMode != LosMode.None && ViewTarget != null)
{ {
Vector2 pos = ViewTarget.DrawPosition; Vector2 pos = ViewTarget.DrawPosition;
if (ViewTarget is Character character &&
character.AnimController?.GetLimb(LimbType.Head) is Limb head &&
!head.IsSevered && !head.Removed)
{
pos = head.body.DrawPosition;
}
Rectangle camView = new Rectangle(cam.WorldView.X, cam.WorldView.Y - cam.WorldView.Height, cam.WorldView.Width, cam.WorldView.Height); Rectangle camView = new Rectangle(cam.WorldView.X, cam.WorldView.Y - cam.WorldView.Height, cam.WorldView.Width, cam.WorldView.Height);
Matrix shadowTransform = cam.ShaderTransform Matrix shadowTransform = cam.ShaderTransform
* Matrix.CreateOrthographic(GameMain.GraphicsWidth, GameMain.GraphicsHeight, -1, 1) * 0.5f; * Matrix.CreateOrthographic(GameMain.GraphicsWidth, GameMain.GraphicsHeight, -1, 1) * 0.5f;
var convexHulls = ConvexHull.GetHullsInRange(ViewTarget.Position, cam.WorldView.Width*0.75f, ViewTarget.Submarine); var convexHulls = ConvexHull.GetHullsInRange(ViewTarget.Position, cam.WorldView.Width * 0.75f, ViewTarget.Submarine);
if (convexHulls != null) if (convexHulls != null)
{ {
List<VertexPositionColor> shadowVerts = new List<VertexPositionColor>(); List<VertexPositionColor> shadowVerts = new List<VertexPositionColor>();
List<VertexPositionTexture> penumbraVerts = new List<VertexPositionTexture>(); List<VertexPositionTexture> penumbraVerts = new List<VertexPositionTexture>();
foreach (ConvexHull convexHull in convexHulls) foreach (ConvexHull convexHull in convexHulls)
{ {
if (!convexHull.Enabled || !convexHull.Intersects(camView)) continue; if (!convexHull.Enabled || !convexHull.Intersects(camView)) { continue; }
if (LosMode == LosMode.BlockOutsideView && !convexHull.IsExteriorWall) { continue; };
Vector2 relativeLightPos = pos; Vector2 relativeLightPos = pos;
if (convexHull.ParentEntity?.Submarine != null) relativeLightPos -= convexHull.ParentEntity.Submarine.Position; if (convexHull.ParentEntity?.Submarine != null) { relativeLightPos -= convexHull.ParentEntity.Submarine.Position; }
convexHull.CalculateLosVertices(relativeLightPos); convexHull.CalculateLosVertices(relativeLightPos);
@@ -646,6 +742,20 @@ namespace Barotrauma.Lights
graphics.SetRenderTarget(null); graphics.SetRenderTarget(null);
} }
public void DebugDrawLos(SpriteBatch spriteBatch, Camera cam)
{
if (ViewTarget == null) { return; }
spriteBatch.Begin(SpriteSortMode.Deferred, transformMatrix: cam.Transform);
var convexHulls = ConvexHull.GetHullsInRange(ViewTarget.Position, cam.WorldView.Width * 0.75f, ViewTarget?.Submarine);
Rectangle camView = new Rectangle(cam.WorldView.X, cam.WorldView.Y - cam.WorldView.Height, cam.WorldView.Width, cam.WorldView.Height);
foreach (ConvexHull convexHull in convexHulls)
{
if (!convexHull.Enabled || !convexHull.Intersects(camView)) { continue; }
convexHull.DebugDraw(spriteBatch);
}
spriteBatch.End();
}
public void ClearLights() public void ClearLights()
{ {
lights.Clear(); lights.Clear();
@@ -15,6 +15,7 @@ namespace Barotrauma.Lights
public bool Persistent; public bool Persistent;
public Dictionary<Identifier, SerializableProperty> SerializableProperties { get; private set; } = new Dictionary<Identifier, SerializableProperty>(); public Dictionary<Identifier, SerializableProperty> SerializableProperties { get; private set; } = new Dictionary<Identifier, SerializableProperty>();
[Serialize("1.0,1.0,1.0,1.0", IsPropertySaveable.Yes, alwaysUseInstanceValues: true), Editable] [Serialize("1.0,1.0,1.0,1.0", IsPropertySaveable.Yes, alwaysUseInstanceValues: true), Editable]
@@ -52,6 +53,10 @@ namespace Barotrauma.Lights
[Serialize(0f, IsPropertySaveable.Yes), Editable(MinValueFloat = -360, MaxValueFloat = 360, ValueStep = 1, DecimalCount = 0)] [Serialize(0f, IsPropertySaveable.Yes), Editable(MinValueFloat = -360, MaxValueFloat = 360, ValueStep = 1, DecimalCount = 0)]
public float Rotation { get; set; } public float Rotation { get; set; }
[Serialize(false, IsPropertySaveable.Yes, "Directional lights only shine in \"one direction\", meaning no shadows are cast behind them."+
" Note that this does not affect how the light texture is drawn: if you want something like a conical spotlight, you should use an appropriate texture for that.")]
public bool Directional { get; set; }
public Vector2 GetOffset() => Vector2.Transform(Offset, Matrix.CreateRotationZ(MathHelper.ToRadians(Rotation))); public Vector2 GetOffset() => Vector2.Transform(Offset, Matrix.CreateRotationZ(MathHelper.ToRadians(Rotation)));
private float flicker; private float flicker;
@@ -203,7 +208,7 @@ namespace Barotrauma.Lights
private VertexPositionColorTexture[] vertices; private VertexPositionColorTexture[] vertices;
private short[] indices; private short[] indices;
private readonly List<ConvexHullList> hullsInRange; private readonly List<ConvexHullList> convexHullsInRange;
public Texture2D texture; public Texture2D texture;
@@ -222,9 +227,10 @@ namespace Barotrauma.Lights
private float prevCalculatedRange; private float prevCalculatedRange;
private Vector2 prevCalculatedPosition; private Vector2 prevCalculatedPosition;
//do we need to recheck which convex hulls are within range //Which submarines' convex hulls are up to date? Resets when the item moves/rotates relative to the submarine.
//(e.g. position or range of the lightsource has changed) //Can contain null (means convex hulls that aren't part of any submarine).
public bool NeedsHullCheck = true; public HashSet<Submarine> HullsUpToDate = new HashSet<Submarine>();
//do we need to recalculate the vertices of the light volume //do we need to recalculate the vertices of the light volume
private bool needsRecalculation; private bool needsRecalculation;
public bool NeedsRecalculation public bool NeedsRecalculation
@@ -234,7 +240,7 @@ namespace Barotrauma.Lights
{ {
if (!needsRecalculation && value) if (!needsRecalculation && value)
{ {
foreach (ConvexHullList chList in hullsInRange) foreach (ConvexHullList chList in convexHullsInRange)
{ {
chList.IsHidden.Clear(); chList.IsHidden.Clear();
} }
@@ -246,6 +252,18 @@ namespace Barotrauma.Lights
//when were the vertices of the light volume last calculated //when were the vertices of the light volume last calculated
public float LastRecalculationTime { get; private set; } public float LastRecalculationTime { get; private set; }
private enum LightVertexState
{
UpToDate,
PendingRayCasts,
PendingVertexRecalculation,
}
private LightVertexState state;
private Vector2 calculatedDrawPos;
private readonly Dictionary<Submarine, Vector2> diffToSub; private readonly Dictionary<Submarine, Vector2> diffToSub;
private DynamicVertexBuffer lightVolumeBuffer; private DynamicVertexBuffer lightVolumeBuffer;
@@ -254,7 +272,6 @@ namespace Barotrauma.Lights
private int indexCount; private int indexCount;
private Vector2 translateVertices; private Vector2 translateVertices;
private float rotateVertices;
private readonly LightSourceParams lightSourceParams; private readonly LightSourceParams lightSourceParams;
@@ -277,7 +294,7 @@ namespace Barotrauma.Lights
return; return;
} }
NeedsHullCheck = true; HullsUpToDate.Clear();
NeedsRecalculation = true; NeedsRecalculation = true;
} }
} }
@@ -292,17 +309,20 @@ namespace Barotrauma.Lights
if (Math.Abs(value - rotation) < 0.001f) { return; } if (Math.Abs(value - rotation) < 0.001f) { return; }
rotation = value; rotation = value;
dir = new Vector2(MathF.Cos(rotation), -MathF.Sin(rotation));
if (Math.Abs(rotation - prevCalculatedRotation) < RotationRecalculationThreshold && vertices != null) if (Math.Abs(rotation - prevCalculatedRotation) < RotationRecalculationThreshold && vertices != null)
{ {
rotateVertices = rotation - prevCalculatedRotation;
return; return;
} }
NeedsHullCheck = true; HullsUpToDate.Clear();
NeedsRecalculation = true; NeedsRecalculation = true;
} }
} }
private Vector2 dir = Vector2.UnitX;
private Vector2 _spriteScale = Vector2.One; private Vector2 _spriteScale = Vector2.One;
public Vector2 SpriteScale public Vector2 SpriteScale
@@ -368,7 +388,7 @@ namespace Barotrauma.Lights
lightSourceParams.Range = value; lightSourceParams.Range = value;
if (Math.Abs(prevCalculatedRange - lightSourceParams.Range) < 10.0f) return; if (Math.Abs(prevCalculatedRange - lightSourceParams.Range) < 10.0f) return;
NeedsHullCheck = true; HullsUpToDate.Clear();
NeedsRecalculation = true; NeedsRecalculation = true;
prevCalculatedRange = lightSourceParams.Range; prevCalculatedRange = lightSourceParams.Range;
} }
@@ -384,8 +404,8 @@ namespace Barotrauma.Lights
set set
{ {
NeedsRecalculation = true; NeedsRecalculation = true;
NeedsHullCheck = true;
lightTextureTargetSize = value; lightTextureTargetSize = value;
HullsUpToDate.Clear();
} }
} }
@@ -424,7 +444,7 @@ namespace Barotrauma.Lights
public bool Enabled = true; public bool Enabled = true;
private readonly ISerializableEntity conditionalTarget; private readonly ISerializableEntity conditionalTarget;
private readonly PropertyConditional.Comparison comparison; private readonly PropertyConditional.LogicalOperatorType logicalOperator;
private readonly List<PropertyConditional> conditionals = new List<PropertyConditional>(); private readonly List<PropertyConditional> conditionals = new List<PropertyConditional>();
public LightSource(ContentXElement element, ISerializableEntity conditionalTarget = null) public LightSource(ContentXElement element, ISerializableEntity conditionalTarget = null)
@@ -432,11 +452,8 @@ namespace Barotrauma.Lights
{ {
lightSourceParams = new LightSourceParams(element); lightSourceParams = new LightSourceParams(element);
CastShadows = element.GetAttributeBool("castshadows", true); CastShadows = element.GetAttributeBool("castshadows", true);
string comparison = element.GetAttributeString("comparison", null); logicalOperator = element.GetAttributeEnum(nameof(logicalOperator),
if (comparison != null) element.GetAttributeEnum("comparison", logicalOperator));
{
Enum.TryParse(comparison, ignoreCase: true, out this.comparison);
}
if (lightSourceParams.DeformableLightSpriteElement != null) if (lightSourceParams.DeformableLightSpriteElement != null)
{ {
@@ -449,13 +466,7 @@ namespace Barotrauma.Lights
switch (subElement.Name.ToString().ToLowerInvariant()) switch (subElement.Name.ToString().ToLowerInvariant())
{ {
case "conditional": case "conditional":
foreach (XAttribute attribute in subElement.Attributes()) conditionals.AddRange(PropertyConditional.FromXElement(subElement));
{
if (PropertyConditional.IsValid(attribute))
{
conditionals.Add(new PropertyConditional(attribute));
}
}
break; break;
} }
} }
@@ -474,7 +485,7 @@ namespace Barotrauma.Lights
public LightSource(Vector2 position, float range, Color color, Submarine submarine, bool addLight=true) public LightSource(Vector2 position, float range, Color color, Submarine submarine, bool addLight=true)
{ {
hullsInRange = new List<ConvexHullList>(); convexHullsInRange = new List<ConvexHullList>();
this.ParentSub = submarine; this.ParentSub = submarine;
this.position = position; this.position = position;
lightSourceParams = new LightSourceParams(range, color); lightSourceParams = new LightSourceParams(range, color);
@@ -515,19 +526,46 @@ namespace Barotrauma.Lights
/// </summary> /// </summary>
private void RefreshConvexHullList(ConvexHullList chList, Vector2 lightPos, Submarine sub) private void RefreshConvexHullList(ConvexHullList chList, Vector2 lightPos, Submarine sub)
{ {
var fullChList = ConvexHull.HullLists.Find(x => x.Submarine == sub); var fullChList = ConvexHull.HullLists.FirstOrDefault(chList => chList.Submarine == sub);
if (fullChList == null) { return; } if (fullChList == null) { return; }
chList.List = fullChList.List.FindAll(ch => ch.Enabled && MathUtils.CircleIntersectsRectangle(lightPos, TextureRange, ch.BoundingBox)); //used to check whether the lightsource hits the target hull if the light is directional
Vector2 ray = new Vector2(dir.X, -dir.Y) * TextureRange;
Vector2 normal = new Vector2(-ray.Y, ray.X);
NeedsHullCheck = true; chList.List.Clear();
foreach (var convexHull in fullChList.List)
{
if (!convexHull.Enabled) { continue; }
if (!MathUtils.CircleIntersectsRectangle(lightPos, TextureRange, convexHull.BoundingBox)) { continue; }
if (lightSourceParams.Directional && false)
{
Rectangle bounds = convexHull.BoundingBox;
//invert because GetLineRectangleIntersection uses the messed up rects that start from top-left
bounds.Y -= bounds.Height;
//the ray can't hit if
// center is in the opposite direction from the ray (cheapest check first)
if (Vector2.Dot(ray, convexHull.BoundingBox.Center.ToVector2() - lightPos) <= 0 &&
/*ray doesn't hit the convex hull*/
!MathUtils.GetLineRectangleIntersection(lightPos, lightPos + ray, bounds, out _) &&
/*normal vectors of the ray don't hit the convex hull */
!MathUtils.GetLineRectangleIntersection(lightPos + normal, lightPos - normal, bounds, out _))
{
continue;
}
}
chList.List.Add(convexHull);
}
chList.IsHidden.RemoveWhere(ch => !chList.List.Contains(ch));
HullsUpToDate.Add(sub);
} }
/// <summary> /// <summary>
/// Recheck which convex hulls are in range (if needed), /// Recheck which convex hulls are in range (if needed),
/// and check if we need to recalculate vertices due to changes in the convex hulls /// and check if we need to recalculate vertices due to changes in the convex hulls
/// </summary> /// </summary>
private void CheckHullsInRange() private void CheckConvexHullsInRange()
{ {
foreach (Submarine sub in Submarine.Loaded) foreach (Submarine sub in Submarine.Loaded)
{ {
@@ -540,21 +578,13 @@ namespace Barotrauma.Lights
private void CheckHullsInRange(Submarine sub) private void CheckHullsInRange(Submarine sub)
{ {
//find the list of convexhulls that belong to the sub //find the list of convexhulls that belong to the sub
ConvexHullList chList = null; ConvexHullList chList = convexHullsInRange.FirstOrDefault(chList => chList.Submarine == sub);
foreach (var ch in hullsInRange)
{
if (ch.Submarine == sub)
{
chList = ch;
break;
}
}
//not found -> create one //not found -> create one
if (chList == null) if (chList == null)
{ {
chList = new ConvexHullList(sub); chList = new ConvexHullList(sub);
hullsInRange.Add(chList); convexHullsInRange.Add(chList);
NeedsRecalculation = true; NeedsRecalculation = true;
} }
@@ -573,7 +603,7 @@ namespace Barotrauma.Lights
//light and the convexhulls are both outside //light and the convexhulls are both outside
if (sub == null) if (sub == null)
{ {
if (NeedsHullCheck) if (!HullsUpToDate.Contains(null))
{ {
RefreshConvexHullList(chList, lightPos, null); RefreshConvexHullList(chList, lightPos, null);
} }
@@ -605,7 +635,7 @@ namespace Barotrauma.Lights
//light and convexhull are both inside the same sub //light and convexhull are both inside the same sub
if (sub == ParentSub) if (sub == ParentSub)
{ {
if (NeedsHullCheck) if (!HullsUpToDate.Contains(sub))
{ {
RefreshConvexHullList(chList, lightPos, sub); RefreshConvexHullList(chList, lightPos, sub);
} }
@@ -613,7 +643,7 @@ namespace Barotrauma.Lights
//light and convexhull are inside different subs //light and convexhull are inside different subs
else else
{ {
if (sub.DockedTo.Contains(ParentSub) && !NeedsHullCheck) { return; } if (sub.DockedTo.Contains(ParentSub) && HullsUpToDate.Contains(sub)) { return; }
lightPos -= (sub.Position - ParentSub.Position); lightPos -= (sub.Position - ParentSub.Position);
@@ -646,19 +676,45 @@ namespace Barotrauma.Lights
} }
} }
private List<Vector2> FindRaycastHits() private static readonly object mutex = new object();
private readonly List<Segment> visibleSegments = new List<Segment>();
private readonly List<SegmentPoint> points = new List<SegmentPoint>();
private readonly List<Vector2> verts = new List<Vector2>();
private readonly SegmentPoint[] boundaryCorners = new SegmentPoint[4];
private void FindRaycastHits()
{ {
if (!CastShadows || Range < 1.0f || Color.A < 1) { return null; } if (!CastShadows || Range < 1.0f || Color.A < 1)
{
state = LightVertexState.PendingVertexRecalculation;
return;
}
Vector2 drawPos = position; Vector2 drawPos = position;
if (ParentSub != null) { drawPos += ParentSub.DrawPosition; } if (ParentSub != null) { drawPos += ParentSub.DrawPosition; }
var hulls = new List<ConvexHull>(); visibleSegments.Clear();
foreach (ConvexHullList chList in hullsInRange) foreach (ConvexHullList chList in convexHullsInRange)
{ {
foreach (ConvexHull hull in chList.List) foreach (ConvexHull hull in chList.List)
{ {
if (!chList.IsHidden.Contains(hull)) { hulls.Add(hull); } if (!chList.IsHidden.Contains(hull))
{
//find convexhull segments that are close enough and facing towards the light source
lock (mutex)
{
hull.RefreshWorldPositions();
hull.GetVisibleSegments(drawPos, visibleSegments);
foreach (var visibleSegment in visibleSegments)
{
if (visibleSegment.ConvexHull?.ParentEntity?.Submarine != null)
{
visibleSegment.SubmarineDrawPos = visibleSegment.ConvexHull.ParentEntity.Submarine.DrawPosition;
}
}
}
}
} }
foreach (ConvexHull hull in chList.List) foreach (ConvexHull hull in chList.List)
{ {
@@ -666,27 +722,19 @@ namespace Barotrauma.Lights
} }
} }
float bounds = TextureRange; state = LightVertexState.PendingRayCasts;
//find convexhull segments that are close enough and facing towards the light source GameMain.LightManager.AddRayCastTask(this, drawPos, rotation);
List<Segment> visibleSegments = new List<Segment>(); }
List<SegmentPoint> points = new List<SegmentPoint>();
foreach (ConvexHull hull in hulls)
{
hull.RefreshWorldPositions();
hull.GetVisibleSegments(drawPos, visibleSegments, ignoreEdges: false);
}
//add a square-shaped boundary to make sure we've got something to construct the triangles from
//even if there aren't enough hull segments around the light source
//(might be more effective to calculate if we actually need these extra points)
public void RayCastTask(Vector2 drawPos, float rotation)
{
Vector2 drawOffset = Vector2.Zero; Vector2 drawOffset = Vector2.Zero;
float boundsExtended = bounds; float boundsExtended = TextureRange;
if (OverrideLightTexture != null) if (OverrideLightTexture != null)
{ {
float cosAngle = (float)Math.Cos(Rotation); float cosAngle = (float)Math.Cos(rotation);
float sinAngle = -(float)Math.Sin(Rotation); float sinAngle = -(float)Math.Sin(rotation);
var overrideTextureDims = new Vector2(OverrideLightTexture.SourceRect.Width, OverrideLightTexture.SourceRect.Height); var overrideTextureDims = new Vector2(OverrideLightTexture.SourceRect.Width, OverrideLightTexture.SourceRect.Height);
@@ -706,12 +754,16 @@ namespace Barotrauma.Lights
drawOffset.Y = origin.X * sinAngle + origin.Y * cosAngle; drawOffset.Y = origin.X * sinAngle + origin.Y * cosAngle;
} }
var boundaryCorners = new SegmentPoint[] { //add a square-shaped boundary to make sure we've got something to construct the triangles from
new SegmentPoint(new Vector2(drawPos.X + drawOffset.X + boundsExtended, drawPos.Y + drawOffset.Y + boundsExtended), null), //even if there aren't enough hull segments around the light source
new SegmentPoint(new Vector2(drawPos.X + drawOffset.X + boundsExtended, drawPos.Y + drawOffset.Y - boundsExtended), null),
new SegmentPoint(new Vector2(drawPos.X + drawOffset.X - boundsExtended, drawPos.Y + drawOffset.Y - boundsExtended), null), //(might be more effective to calculate if we actually need these extra points)
new SegmentPoint(new Vector2(drawPos.X + drawOffset.X - boundsExtended, drawPos.Y + drawOffset.Y + boundsExtended), null) Vector2 boundsMin = drawPos + drawOffset + new Vector2(-boundsExtended, -boundsExtended);
}; Vector2 boundsMax = drawPos + drawOffset + new Vector2(boundsExtended, boundsExtended);
boundaryCorners[0] = new SegmentPoint(boundsMax, null);
boundaryCorners[1] = new SegmentPoint(new Vector2(boundsMax.X, boundsMin.Y), null);
boundaryCorners[2] = new SegmentPoint(boundsMin, null);
boundaryCorners[3] = new SegmentPoint(new Vector2(boundsMin.X, boundsMax.Y), null);
for (int i = 0; i < 4; i++) for (int i = 0; i < 4; i++)
{ {
@@ -719,199 +771,200 @@ namespace Barotrauma.Lights
visibleSegments.Add(s); visibleSegments.Add(s);
} }
//Generate new points at the intersections between segments lock (mutex)
//This is necessary for the light volume to generate properly on some subs
for (int i = 0; i < visibleSegments.Count; i++)
{ {
Vector2 p1a = visibleSegments[i].Start.WorldPos; //Generate new points at the intersections between segments
Vector2 p1b = visibleSegments[i].End.WorldPos; //This is necessary for the light volume to generate properly on some subs
for (int i = 0; i < visibleSegments.Count; i++)
for (int j = i + 1; j < visibleSegments.Count; j++)
{ {
//ignore intersections between parallel axis-aligned segments Vector2 p1a = visibleSegments[i].Start.WorldPos;
if (visibleSegments[i].IsAxisAligned && visibleSegments[j].IsAxisAligned && Vector2 p1b = visibleSegments[i].End.WorldPos;
visibleSegments[i].IsHorizontal == visibleSegments[j].IsHorizontal)
{
continue;
}
Vector2 p2a = visibleSegments[j].Start.WorldPos; for (int j = i + 1; j < visibleSegments.Count; j++)
Vector2 p2b = visibleSegments[j].End.WorldPos;
if (Vector2.DistanceSquared(p1a, p2a) < 5.0f ||
Vector2.DistanceSquared(p1a, p2b) < 5.0f ||
Vector2.DistanceSquared(p1b, p2a) < 5.0f ||
Vector2.DistanceSquared(p1b, p2b) < 5.0f)
{ {
continue; //ignore intersections between parallel axis-aligned segments
} if (visibleSegments[i].IsAxisAligned && visibleSegments[j].IsAxisAligned &&
visibleSegments[i].IsHorizontal == visibleSegments[j].IsHorizontal)
bool intersects;
Vector2 intersection = Vector2.Zero;
if (visibleSegments[i].IsAxisAligned)
{
intersects = MathUtils.GetAxisAlignedLineIntersection(p2a, p2b, p1a, p1b, visibleSegments[i].IsHorizontal, out intersection);
}
else if (visibleSegments[j].IsAxisAligned)
{
intersects = MathUtils.GetAxisAlignedLineIntersection(p1a, p1b, p2a, p2b, visibleSegments[j].IsHorizontal, out intersection);
}
else
{
intersects = MathUtils.GetLineIntersection(p1a, p1b, p2a, p2b, out intersection);
}
if (intersects)
{
SegmentPoint start = visibleSegments[i].Start;
SegmentPoint end = visibleSegments[i].End;
SegmentPoint mid = new SegmentPoint(intersection, null);
if (visibleSegments[i].ConvexHull?.ParentEntity?.Submarine != null)
{
mid.Pos -= visibleSegments[i].ConvexHull.ParentEntity.Submarine.DrawPosition;
}
if (Vector2.DistanceSquared(start.WorldPos, mid.WorldPos) < 5.0f ||
Vector2.DistanceSquared(end.WorldPos, mid.WorldPos) < 5.0f)
{ {
continue; continue;
} }
Segment seg1 = new Segment(start, mid, visibleSegments[i].ConvexHull) Vector2 p2a = visibleSegments[j].Start.WorldPos;
{ Vector2 p2b = visibleSegments[j].End.WorldPos;
IsHorizontal = visibleSegments[i].IsHorizontal,
};
Segment seg2 = new Segment(mid, end, visibleSegments[i].ConvexHull) if (Vector2.DistanceSquared(p1a, p2a) < 5.0f ||
Vector2.DistanceSquared(p1a, p2b) < 5.0f ||
Vector2.DistanceSquared(p1b, p2a) < 5.0f ||
Vector2.DistanceSquared(p1b, p2b) < 5.0f)
{ {
IsHorizontal = visibleSegments[i].IsHorizontal continue;
}; }
visibleSegments[i] = seg1; bool intersects;
visibleSegments.Insert(i + 1, seg2); Vector2 intersection = Vector2.Zero;
if (visibleSegments[i].IsAxisAligned)
{
intersects = MathUtils.GetAxisAlignedLineIntersection(p2a, p2b, p1a, p1b, visibleSegments[i].IsHorizontal, out intersection);
}
else if (visibleSegments[j].IsAxisAligned)
{
intersects = MathUtils.GetAxisAlignedLineIntersection(p1a, p1b, p2a, p2b, visibleSegments[j].IsHorizontal, out intersection);
}
else
{
intersects = MathUtils.GetLineIntersection(p1a, p1b, p2a, p2b, out intersection);
}
if (intersects)
{
SegmentPoint start = visibleSegments[i].Start;
SegmentPoint end = visibleSegments[i].End;
SegmentPoint mid = new SegmentPoint(intersection, null);
mid.Pos -= visibleSegments[i].SubmarineDrawPos;
if (Vector2.DistanceSquared(start.WorldPos, mid.WorldPos) < 5.0f ||
Vector2.DistanceSquared(end.WorldPos, mid.WorldPos) < 5.0f)
{
continue;
}
Segment seg1 = new Segment(start, mid, visibleSegments[i].ConvexHull)
{
IsHorizontal = visibleSegments[i].IsHorizontal,
};
Segment seg2 = new Segment(mid, end, visibleSegments[i].ConvexHull)
{
IsHorizontal = visibleSegments[i].IsHorizontal
};
visibleSegments[i] = seg1;
visibleSegments.Insert(i + 1, seg2);
i--;
break;
}
}
}
points.Clear();
//remove segments that fall out of bounds
for (int i = 0; i < visibleSegments.Count; i++)
{
Segment s = visibleSegments[i];
if (Math.Abs(s.Start.WorldPos.X - drawPos.X - drawOffset.X) > boundsExtended + 1.0f ||
Math.Abs(s.Start.WorldPos.Y - drawPos.Y - drawOffset.Y) > boundsExtended + 1.0f ||
Math.Abs(s.End.WorldPos.X - drawPos.X - drawOffset.X) > boundsExtended + 1.0f ||
Math.Abs(s.End.WorldPos.Y - drawPos.Y - drawOffset.Y) > boundsExtended + 1.0f)
{
visibleSegments.RemoveAt(i);
i--; i--;
break;
} }
} else
}
//remove segments that fall out of bounds
for (int i = 0; i < visibleSegments.Count; i++)
{
Segment s = visibleSegments[i];
if (Math.Abs(s.Start.WorldPos.X - drawPos.X - drawOffset.X) > boundsExtended + 1.0f ||
Math.Abs(s.Start.WorldPos.Y - drawPos.Y - drawOffset.Y) > boundsExtended + 1.0f ||
Math.Abs(s.End.WorldPos.X - drawPos.X - drawOffset.X) > boundsExtended + 1.0f ||
Math.Abs(s.End.WorldPos.Y - drawPos.Y - drawOffset.Y) > boundsExtended + 1.0f)
{
visibleSegments.RemoveAt(i);
i--;
}
else
{
points.Add(s.Start);
points.Add(s.End);
}
}
visibleSegments = visibleSegments.OrderBy(s => MathUtils.LineToPointDistanceSquared(s.Start.WorldPos, s.End.WorldPos, drawPos)).ToList();
var compareCCW = new CompareSegmentPointCW(drawPos);
try
{
points.Sort(compareCCW);
}
catch (Exception e)
{
StringBuilder sb = new StringBuilder("Constructing light volumes failed! Light pos: " + drawPos + ", Hull verts:\n");
foreach (SegmentPoint sp in points)
{
sb.AppendLine(sp.Pos.ToString());
}
DebugConsole.ThrowError(sb.ToString(), e);
}
List<Vector2> output = new List<Vector2>();
//List<Pair<int, Vector2>> preOutput = new List<Pair<int, Vector2>>();
//remove points that are very close to each other
for (int i = 0; i < points.Count; i++)
{
for (int j = Math.Min(i + 4, points.Count-1); j > i; j--)
{
if (Math.Abs(points[i].WorldPos.X - points[j].WorldPos.X) < 6 &&
Math.Abs(points[i].WorldPos.Y - points[j].WorldPos.Y) < 6)
{ {
points.RemoveAt(j); points.Add(s.Start);
points.Add(s.End);
} }
} }
}
foreach (SegmentPoint p in points) //remove points that are very close to each other
{ for (int i = 0; i < points.Count; i++)
Vector2 dir = Vector2.Normalize(p.WorldPos - drawPos);
Vector2 dirNormal = new Vector2(-dir.Y, dir.X) * 3;
//do two slightly offset raycasts to hit the segment itself and whatever's behind it
var intersection1 = RayCast(drawPos, drawPos + dir * boundsExtended * 2 - dirNormal, visibleSegments);
var intersection2 = RayCast(drawPos, drawPos + dir * boundsExtended * 2 + dirNormal, visibleSegments);
if (intersection1.index < 0) return null;
if (intersection2.index < 0) return null;
Segment seg1 = visibleSegments[intersection1.index];
Segment seg2 = visibleSegments[intersection2.index];
bool isPoint1 = MathUtils.LineToPointDistanceSquared(seg1.Start.WorldPos, seg1.End.WorldPos, p.WorldPos) < 25.0f;
bool isPoint2 = MathUtils.LineToPointDistanceSquared(seg2.Start.WorldPos, seg2.End.WorldPos, p.WorldPos) < 25.0f;
if (isPoint1 && isPoint2)
{ {
//hit at the current segmentpoint -> place the segmentpoint into the list for (int j = Math.Min(i + 4, points.Count - 1); j > i; j--)
output.Add(p.WorldPos);
foreach (ConvexHullList hullList in hullsInRange)
{ {
hullList.IsHidden.Remove(p.ConvexHull); if (Math.Abs(points[i].WorldPos.X - points[j].WorldPos.X) < 6 &&
hullList.IsHidden.Remove(seg1.ConvexHull); Math.Abs(points[i].WorldPos.Y - points[j].WorldPos.Y) < 6)
hullList.IsHidden.Remove(seg2.ConvexHull); {
points.RemoveAt(j);
}
} }
} }
else if (intersection1.index != intersection2.index)
var compareCCW = new CompareSegmentPointCW(drawPos);
try
{ {
//the raycasts landed on different segments points.Sort(compareCCW);
//we definitely want to generate new geometry here }
output.Add(isPoint1 ? p.WorldPos : intersection1.pos); catch (Exception e)
output.Add(isPoint2 ? p.WorldPos : intersection2.pos); {
StringBuilder sb = new StringBuilder("Constructing light volumes failed! Light pos: " + drawPos + ", Hull verts:\n");
foreach (ConvexHullList hullList in hullsInRange) foreach (SegmentPoint sp in points)
{ {
hullList.IsHidden.Remove(p.ConvexHull); sb.AppendLine(sp.Pos.ToString());
hullList.IsHidden.Remove(seg1.ConvexHull); }
hullList.IsHidden.Remove(seg2.ConvexHull); DebugConsole.ThrowError(sb.ToString(), e);
} }
visibleSegments.Sort((s1, s2) =>
MathUtils.LineToPointDistanceSquared(s1.Start.WorldPos, s1.End.WorldPos, drawPos)
.CompareTo(MathUtils.LineToPointDistanceSquared(s2.Start.WorldPos, s2.End.WorldPos, drawPos)));
verts.Clear();
foreach (SegmentPoint p in points)
{
Vector2 dir = Vector2.Normalize(p.WorldPos - drawPos);
Vector2 dirNormal = new Vector2(-dir.Y, dir.X) * 3;
//do two slightly offset raycasts to hit the segment itself and whatever's behind it
var intersection1 = RayCast(drawPos, drawPos + dir * boundsExtended * 2 - dirNormal, visibleSegments);
if (intersection1.index < 0) { return; }
var intersection2 = RayCast(drawPos, drawPos + dir * boundsExtended * 2 + dirNormal, visibleSegments);
if (intersection2.index < 0) { return; }
Segment seg1 = visibleSegments[intersection1.index];
Segment seg2 = visibleSegments[intersection2.index];
bool isPoint1 = MathUtils.LineToPointDistanceSquared(seg1.Start.WorldPos, seg1.End.WorldPos, p.WorldPos) < 25.0f;
bool isPoint2 = MathUtils.LineToPointDistanceSquared(seg2.Start.WorldPos, seg2.End.WorldPos, p.WorldPos) < 25.0f;
if (isPoint1 && isPoint2)
{
//hit at the current segmentpoint -> place the segmentpoint into the list
verts.Add(p.WorldPos);
foreach (ConvexHullList hullList in convexHullsInRange)
{
hullList.IsHidden.Remove(p.ConvexHull);
hullList.IsHidden.Remove(seg1.ConvexHull);
hullList.IsHidden.Remove(seg2.ConvexHull);
}
}
else if (intersection1.index != intersection2.index)
{
//the raycasts landed on different segments
//we definitely want to generate new geometry here
verts.Add(isPoint1 ? p.WorldPos : intersection1.pos);
verts.Add(isPoint2 ? p.WorldPos : intersection2.pos);
foreach (ConvexHullList hullList in convexHullsInRange)
{
hullList.IsHidden.Remove(p.ConvexHull);
hullList.IsHidden.Remove(seg1.ConvexHull);
hullList.IsHidden.Remove(seg2.ConvexHull);
}
}
//if neither of the conditions above are met, we just assume
//that the raycasts both resulted on the same segment
//and creating geometry here would be wasteful
} }
//if neither of the conditions above are met, we just assume
//that the raycasts both resulted on the same segment
//and creating geometry here would be wasteful
} }
//remove points that are very close to each other //remove points that are very close to each other
for (int i = 0; i < output.Count - 1; i++) for (int i = 0; i < verts.Count - 1; i++)
{ {
for (int j = Math.Min(i + 4, output.Count - 1); j > i; j--) for (int j = Math.Min(i + 4, verts.Count - 1); j > i; j--)
{ {
if (Math.Abs(output[i].X - output[j].X) < 6 && if (Math.Abs(verts[i].X - verts[j].X) < 6 &&
Math.Abs(output[i].Y - output[j].Y) < 6) Math.Abs(verts[i].Y - verts[j].Y) < 6)
{ {
output.RemoveAt(j); verts.RemoveAt(j);
} }
} }
} }
calculatedDrawPos = drawPos;
return output; state = LightVertexState.PendingVertexRecalculation;
} }
private (int index, Vector2 pos) RayCast(Vector2 rayStart, Vector2 rayEnd, List<Segment> segments) private static (int index, Vector2 pos) RayCast(Vector2 rayStart, Vector2 rayEnd, List<Segment> segments)
{ {
Vector2? closestIntersection = null; Vector2? closestIntersection = null;
int segment = -1; int segment = -1;
@@ -936,13 +989,13 @@ namespace Barotrauma.Lights
//same for the x-axis //same for the x-axis
if (s.Start.WorldPos.X > s.End.WorldPos.X) if (s.Start.WorldPos.X > s.End.WorldPos.X)
{ {
if (s.Start.WorldPos.X < minX) continue; if (s.Start.WorldPos.X < minX) { continue; }
if (s.End.WorldPos.X > maxX) continue; if (s.End.WorldPos.X > maxX) { continue; }
} }
else else
{ {
if (s.End.WorldPos.X < minX) continue; if (s.End.WorldPos.X < minX) { continue; }
if (s.Start.WorldPos.X > maxX) continue; if (s.Start.WorldPos.X > maxX) { continue; }
} }
bool intersects; bool intersects;
@@ -986,14 +1039,11 @@ namespace Barotrauma.Lights
indices = new short[indexCount]; indices = new short[indexCount];
} }
Vector2 drawPos = position; Vector2 drawPos = calculatedDrawPos;
if (ParentSub != null) { drawPos += ParentSub.DrawPosition; }
float cosAngle = (float)Math.Cos(Rotation);
float sinAngle = -(float)Math.Sin(Rotation);
Vector2 uvOffset = Vector2.Zero; Vector2 uvOffset = Vector2.Zero;
Vector2 overrideTextureDims = Vector2.One; Vector2 overrideTextureDims = Vector2.One;
Vector2 dir = this.dir;
if (OverrideLightTexture != null) if (OverrideLightTexture != null)
{ {
overrideTextureDims = new Vector2(OverrideLightTexture.SourceRect.Width, OverrideLightTexture.SourceRect.Height); overrideTextureDims = new Vector2(OverrideLightTexture.SourceRect.Width, OverrideLightTexture.SourceRect.Height);
@@ -1002,8 +1052,7 @@ namespace Barotrauma.Lights
if (LightSpriteEffect == SpriteEffects.FlipHorizontally) if (LightSpriteEffect == SpriteEffects.FlipHorizontally)
{ {
origin.X = OverrideLightTexture.SourceRect.Width - origin.X; origin.X = OverrideLightTexture.SourceRect.Width - origin.X;
cosAngle = -cosAngle; dir = -dir;
sinAngle = -sinAngle;
} }
if (LightSpriteEffect == SpriteEffects.FlipVertically) { origin.Y = OverrideLightTexture.SourceRect.Height - origin.Y; } if (LightSpriteEffect == SpriteEffects.FlipVertically) { origin.Y = OverrideLightTexture.SourceRect.Height - origin.Y; }
uvOffset = (origin / overrideTextureDims) - new Vector2(0.5f, 0.5f); uvOffset = (origin / overrideTextureDims) - new Vector2(0.5f, 0.5f);
@@ -1041,7 +1090,7 @@ namespace Barotrauma.Lights
//calculate normal of first segment //calculate normal of first segment
Vector2 nDiff1 = vertex - nextVertex; Vector2 nDiff1 = vertex - nextVertex;
float tx = nDiff1.X; nDiff1.X = -nDiff1.Y; nDiff1.Y = tx; nDiff1 = new Vector2(-nDiff1.Y, nDiff1.X);
nDiff1 /= Math.Max(Math.Abs(nDiff1.X), Math.Abs(nDiff1.Y)); nDiff1 /= Math.Max(Math.Abs(nDiff1.X), Math.Abs(nDiff1.Y));
//if the normal is pointing towards the light origin //if the normal is pointing towards the light origin
//rather than away from it, invert it //rather than away from it, invert it
@@ -1049,21 +1098,23 @@ namespace Barotrauma.Lights
//calculate normal of second segment //calculate normal of second segment
Vector2 nDiff2 = prevVertex - vertex; Vector2 nDiff2 = prevVertex - vertex;
tx = nDiff2.X; nDiff2.X = -nDiff2.Y; nDiff2.Y = tx; nDiff2 = new Vector2(-nDiff2.Y, nDiff2.X);
nDiff2 /= Math.Max(Math.Abs(nDiff2.X),Math.Abs(nDiff2.Y)); nDiff2 /= Math.Max(Math.Abs(nDiff2.X), Math.Abs(nDiff2.Y));
//if the normal is pointing towards the light origin //if the normal is pointing towards the light origin
//rather than away from it, invert it //rather than away from it, invert it
if (Vector2.DistanceSquared(nDiff2, rawDiff) > Vector2.DistanceSquared(-nDiff2, rawDiff)) nDiff2 = -nDiff2; if (Vector2.DistanceSquared(nDiff2, rawDiff) > Vector2.DistanceSquared(-nDiff2, rawDiff)) nDiff2 = -nDiff2;
//add the normals together and use some magic numbers to create //add the normals together and use some magic numbers to create
//a somewhat useful/good-looking blur //a somewhat useful/good-looking blur
Vector2 nDiff = nDiff1 * 40.0f; float blurDistance = 40.0f;
if (MathUtils.GetLineIntersection(vertex + (nDiff1 * 40.0f), nextVertex + (nDiff1 * 40.0f), vertex + (nDiff2 * 40.0f), prevVertex + (nDiff2 * 40.0f), true, out Vector2 intersection)) Vector2 nDiff = nDiff1 * blurDistance;
if (MathUtils.GetLineIntersection(vertex + (nDiff1 * blurDistance), nextVertex + (nDiff1 * blurDistance), vertex + (nDiff2 * blurDistance), prevVertex + (nDiff2 * blurDistance), true, out Vector2 intersection))
{ {
nDiff = intersection - vertex; nDiff = intersection - vertex;
if (nDiff.LengthSquared() > 10000.0f) if (nDiff.LengthSquared() > 100.0f * 100.0f)
{ {
nDiff /= Math.Max(Math.Abs(nDiff.X), Math.Abs(nDiff.Y)); nDiff *= 100.0f; nDiff /= Math.Max(Math.Abs(nDiff.X), Math.Abs(nDiff.Y));
nDiff *= 100.0f;
} }
} }
@@ -1074,8 +1125,8 @@ namespace Barotrauma.Lights
//calculate texture coordinates based on the light's rotation //calculate texture coordinates based on the light's rotation
Vector2 originDiff = diff; Vector2 originDiff = diff;
diff.X = originDiff.X * cosAngle - originDiff.Y * sinAngle; diff.X = originDiff.X * dir.X - originDiff.Y * dir.Y;
diff.Y = originDiff.X * sinAngle + originDiff.Y * cosAngle; diff.Y = originDiff.X * dir.Y + originDiff.Y * dir.X;
diff *= (overrideTextureDims / OverrideLightTexture.size);// / (1.0f - Math.Max(Math.Abs(uvOffset.X), Math.Abs(uvOffset.Y))); diff *= (overrideTextureDims / OverrideLightTexture.size);// / (1.0f - Math.Max(Math.Abs(uvOffset.X), Math.Abs(uvOffset.Y)));
diff += uvOffset; diff += uvOffset;
} }
@@ -1161,7 +1212,6 @@ namespace Barotrauma.Lights
} }
translateVertices = Vector2.Zero; translateVertices = Vector2.Zero;
rotateVertices = 0.0f;
prevCalculatedPosition = position; prevCalculatedPosition = position;
prevCalculatedRotation = rotation; prevCalculatedRotation = rotation;
} }
@@ -1181,9 +1231,6 @@ namespace Barotrauma.Lights
} }
drawPos.Y = -drawPos.Y; drawPos.Y = -drawPos.Y;
float cosAngle = (float)Math.Cos(Rotation);
float sinAngle = -(float)Math.Sin(Rotation);
float bounds = TextureRange; float bounds = TextureRange;
if (OverrideLightTexture != null) if (OverrideLightTexture != null)
@@ -1195,8 +1242,8 @@ namespace Barotrauma.Lights
origin /= Math.Max(overrideTextureDims.X, overrideTextureDims.Y); origin /= Math.Max(overrideTextureDims.X, overrideTextureDims.Y);
origin *= TextureRange; origin *= TextureRange;
drawPos.X += origin.X * sinAngle + origin.Y * cosAngle; drawPos.X += origin.X * dir.Y + origin.Y * dir.X;
drawPos.Y += origin.X * cosAngle + origin.Y * sinAngle; drawPos.Y += origin.X * dir.X + origin.Y * dir.Y;
} }
//add a square-shaped boundary to make sure we've got something to construct the triangles from //add a square-shaped boundary to make sure we've got something to construct the triangles from
@@ -1302,7 +1349,7 @@ namespace Barotrauma.Lights
{ {
if (conditionals.None()) { return; } if (conditionals.None()) { return; }
if (conditionalTarget == null) { return; } if (conditionalTarget == null) { return; }
if (comparison == PropertyConditional.Comparison.And) if (logicalOperator == PropertyConditional.LogicalOperatorType.And)
{ {
Enabled = conditionals.All(c => c.Matches(conditionalTarget)); Enabled = conditionals.All(c => c.Matches(conditionalTarget));
} }
@@ -1335,35 +1382,43 @@ namespace Barotrauma.Lights
return; return;
} }
CheckHullsInRange(); CheckConvexHullsInRange();
if (NeedsRecalculation && allowRecalculation) if (NeedsRecalculation && allowRecalculation)
{ {
recalculationCount++; if (state == LightVertexState.UpToDate)
var verts = FindRaycastHits();
if (verts == null)
{ {
#if DEBUG recalculationCount++;
DebugConsole.ThrowError($"Failed to generate vertices for a light source. Range: {Range}, color: {Color}, brightness: {CurrentBrightness}, parent: {ParentBody?.UserData ?? "Unknown"}"); FindRaycastHits();
#endif
Enabled = false;
return;
} }
else if (state == LightVertexState.PendingVertexRecalculation)
{
if (verts == null)
{
#if DEBUG
DebugConsole.ThrowError($"Failed to generate vertices for a light source. Range: {Range}, color: {Color}, brightness: {CurrentBrightness}, parent: {ParentBody?.UserData ?? "Unknown"}");
#endif
Enabled = false;
return;
}
CalculateLightVertices(verts); CalculateLightVertices(verts);
LastRecalculationTime = (float)Timing.TotalTime; LastRecalculationTime = (float)Timing.TotalTime;
NeedsRecalculation = false; NeedsRecalculation = false;
state = LightVertexState.UpToDate;
}
} }
if (vertexCount == 0) { return; }
Vector2 offset = ParentSub == null ? Vector2.Zero : ParentSub.DrawPosition; Vector2 offset = ParentSub == null ? Vector2.Zero : ParentSub.DrawPosition;
lightEffect.World = lightEffect.World =
Matrix.CreateTranslation(-new Vector3(position, 0.0f)) * Matrix.CreateTranslation(-new Vector3(position, 0.0f)) *
Matrix.CreateRotationZ(rotateVertices - MathHelper.ToRadians(LightSourceParams.Rotation)) * Matrix.CreateRotationZ(MathHelper.ToRadians(LightSourceParams.Rotation)) *
Matrix.CreateTranslation(new Vector3(position + offset + translateVertices, 0.0f)) * Matrix.CreateTranslation(new Vector3(position + offset + translateVertices, 0.0f)) *
transform; transform;
if (vertexCount == 0) { return; }
lightEffect.DiffuseColor = (new Vector3(Color.R, Color.G, Color.B) * (Color.A / 255.0f * CurrentBrightness)) / 255.0f; lightEffect.DiffuseColor = (new Vector3(Color.R, Color.G, Color.B) * (Color.A / 255.0f * CurrentBrightness)) / 255.0f;
if (OverrideLightTexture != null) if (OverrideLightTexture != null)
@@ -1387,9 +1442,9 @@ namespace Barotrauma.Lights
public void Reset() public void Reset()
{ {
hullsInRange.Clear(); HullsUpToDate.Clear();
convexHullsInRange.Clear();
diffToSub.Clear(); diffToSub.Clear();
NeedsHullCheck = true;
NeedsRecalculation = true; NeedsRecalculation = true;
vertexCount = 0; vertexCount = 0;
@@ -70,9 +70,9 @@ namespace Barotrauma
Vector2 position = cam.ScreenToWorld(PlayerInput.MousePosition); Vector2 position = cam.ScreenToWorld(PlayerInput.MousePosition);
foreach (MapEntity entity in mapEntityList) foreach (MapEntity entity in HighlightedEntities)
{ {
if (entity == this || !entity.IsHighlighted || !(entity is Item) || !entity.IsMouseOn(position)) { continue; } if (entity == this|| entity is not Item || !entity.IsMouseOn(position)) { continue; }
if (((Item)entity).GetComponent<DockingPort>() == null) { continue; } if (((Item)entity).GetComponent<DockingPort>() == null) { continue; }
if (linkedTo.Contains(entity)) if (linkedTo.Contains(entity))
{ {
@@ -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
{ {
@@ -72,6 +73,8 @@ namespace Barotrauma
private RichString beaconStationActiveText, beaconStationInactiveText; private RichString beaconStationActiveText, beaconStationInactiveText;
private GUIComponent locationInfoOverlay;
/*private (Rectangle targetArea, string tip)? connectionTooltip; /*private (Rectangle targetArea, string tip)? connectionTooltip;
private string sanitizedConnectionTooltip; private string sanitizedConnectionTooltip;
private List<RichTextData> connectionTooltipRichTextData; private List<RichTextData> connectionTooltipRichTextData;
@@ -98,7 +101,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 +189,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)
{ {
@@ -210,11 +213,17 @@ namespace Barotrauma
currLocationIndicatorPos = CurrentLocation.MapPosition; currLocationIndicatorPos = CurrentLocation.MapPosition;
} }
RemoveFogOfWar(newLocation); if (newLocation.Visited)
{
RemoveFogOfWar(newLocation);
}
} }
partial void RemoveFogOfWarProjSpecific(Location location) => RemoveFogOfWar(location);
private void RemoveFogOfWar(Location location, bool removeFromAdjacentLocations = true) private void RemoveFogOfWar(Location location, bool removeFromAdjacentLocations = true)
{ {
if (mapTiles == null) { return; }
if (location == null) { return; } if (location == null) { return; }
var mapTile = generationParams.MapTiles.Values.FirstOrDefault().FirstOrDefault(); var mapTile = generationParams.MapTiles.Values.FirstOrDefault().FirstOrDefault();
@@ -252,27 +261,223 @@ 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);
}
}
}
}
private void CreateLocationInfoOverlay(Location location)
{
locationInfoOverlay = new GUIFrame(new RectTransform(new Point(GUI.IntScale(350), GUI.IntScale(350)), GUI.Canvas), style: "GUIToolTip")
{
UserData = location
};
locationInfoOverlay.Color *= 0.8f;
var content = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.85f), locationInfoOverlay.RectTransform, Anchor.Center))
{
Stretch = true,
RelativeSpacing = 0.02f
};
bool showReputation = hudVisibility > 0.0f && location.Type.HasOutpost && location.Reputation != null;
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform), location.Name, font: GUIStyle.LargeFont) { Padding = Vector4.Zero };
if (!location.Type.Name.IsNullOrEmpty())
{
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform), location.Type.Name, font: GUIStyle.SubHeadingFont) { Padding = Vector4.Zero };
}
CreateSpacing(10);
if (!location.Type.Description.IsNullOrEmpty())
{
CreateTextWithIcon(location.Type.Description, location.Type.Sprite);
}
int highestSubTier = location.HighestSubmarineTierAvailable();
List<(SubmarineClass subClass, int tier)> overrideTiers = null;
if (location.CanHaveSubsForSale())
{
overrideTiers = new List<(SubmarineClass subClass, int tier)>();
foreach (SubmarineClass subClass in Enum.GetValues(typeof(SubmarineClass)))
{
if (subClass == SubmarineClass.Undefined) { continue; }
int highestClassTier = location.HighestSubmarineTierAvailable(subClass);
if (highestClassTier > 0 && highestClassTier > highestSubTier)
{
overrideTiers.Add((subClass, highestClassTier));
}
}
}
if (highestSubTier > 0)
{
CreateTextWithIcon(TextManager.GetWithVariable("advancedsub.all", "[tiernumber]", highestSubTier.ToString()), icon: null, style: "LocationOverlaySubmarineIcon");
}
if (overrideTiers != null)
{
foreach (var (subClass, tier) in overrideTiers)
{
CreateTextWithIcon(TextManager.GetWithVariable($"advancedsub.{subClass}", "[tiernumber]", tier.ToString()), icon: null, style: "LocationOverlaySubmarineIcon");
}
}
CreateSpacing(10);
void CreateTextWithIcon(LocalizedString text, Sprite icon, string style = null)
{
var textHolder = new GUILayoutGroup(new RectTransform(new Point(content.Rect.Width, (int)GUIStyle.Font.MeasureString(text).Y), content.RectTransform), isHorizontal: true)
{
Stretch = true,
CanBeFocused = true
};
var guiIcon =
style == null ?
new GUIImage(new RectTransform(Vector2.One * 1.25f, textHolder.RectTransform, scaleBasis: ScaleBasis.BothHeight), icon) :
new GUIImage(new RectTransform(Vector2.One * 1.25f, textHolder.RectTransform, scaleBasis: ScaleBasis.BothHeight), style);
var textBlock = new GUITextBlock(new RectTransform(new Vector2(0.9f, 1.0f), textHolder.RectTransform), text);
textBlock.RectTransform.MinSize = new Point((int)textBlock.TextSize.X, 0);
textHolder.RectTransform.MinSize = new Point((int)textBlock.TextSize.X + guiIcon.Rect.Width, 0);
}
void CreateSpacing(int height)
{
new GUIFrame(new RectTransform(new Point(content.Rect.Width, GUI.IntScale(height)), content.RectTransform), style: null);
}
if (location.Faction != null)
{
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform),
RichString.Rich(TextManager.GetWithVariables("reputationgainnotification",
("[value]", string.Empty),
("[reputationname]", $"‖color:{XMLExtensions.ToStringHex(location.Faction.Prefab.IconColor)}‖{location.Faction.Prefab.Name}‖end‖"))))
{
Padding = Vector4.Zero
};
CreateSpacing(10);
var repBarHolder = new GUILayoutGroup(new RectTransform(new Point(content.Rect.Width, GUI.IntScale(25)), content.RectTransform), isHorizontal: true)
{
Stretch = true,
RelativeSpacing = 0.05f
};
new GUICustomComponent(new RectTransform(new Vector2(0.6f, 1.0f), repBarHolder.RectTransform), onDraw: (sb, component) =>
{
if (location.Reputation == null) { return; }
RoundSummary.DrawReputationBar(sb, component.Rect, location.Reputation.NormalizedValue);
});
new GUITextBlock(new RectTransform(new Vector2(0.4f, 1.0f), repBarHolder.RectTransform),
location.Reputation.GetFormattedReputationText(), textAlignment: Alignment.CenterRight);
new GUIImage(new RectTransform(new Vector2(0.25f, 0.5f), locationInfoOverlay.RectTransform, Anchor.BottomRight) { RelativeOffset = new Vector2(0.05f) },
location.Faction.Prefab.Icon, scaleToFit: true)
{
Color = location.Faction.Prefab.IconColor * 0.5f
};
CreateSpacing(20);
}
locationInfoOverlay.RectTransform.NonScaledSize =
new Point(
Math.Max(locationInfoOverlay.Rect.Width, (int)(content.Children.Max(c => c is GUITextBlock textBlock ? textBlock.TextSize.X : c.RectTransform.MinSize.X) * 1.2f)),
(int)(content.Children.Sum(c => c.Rect.Height) / content.RectTransform.RelativeSize.Y));
} }
partial void ClearAnimQueue() partial void ClearAnimQueue()
@@ -280,12 +485,13 @@ 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)
@@ -345,10 +551,39 @@ namespace Barotrauma
Vector2 rectCenter = new Vector2(rect.Center.X, rect.Center.Y); Vector2 rectCenter = new Vector2(rect.Center.X, rect.Center.Y);
Vector2 viewOffset = DrawOffset + drawOffsetNoise; Vector2 viewOffset = DrawOffset + drawOffsetNoise;
if (HighlightedLocation != null)
{
Vector2 highlightedLocationDrawPos = rectCenter + (HighlightedLocation.MapPosition + viewOffset) * zoom;
if (locationInfoOverlay == null || locationInfoOverlay.UserData != HighlightedLocation)
{
CreateLocationInfoOverlay(HighlightedLocation);
}
Point offsetFromLocationIcon = new Point(GUI.IntScale(25));
var locationInfoRt = locationInfoOverlay.RectTransform;
if (locationInfoRt.Pivot == Pivot.BottomLeft || locationInfoRt.Pivot == Pivot.BottomRight)
{
offsetFromLocationIcon.Y = -offsetFromLocationIcon.Y;
}
if (locationInfoRt.Pivot == Pivot.TopRight || locationInfoRt.Pivot == Pivot.BottomRight)
{
offsetFromLocationIcon.X = -offsetFromLocationIcon.X;
}
locationInfoRt.ScreenSpaceOffset = highlightedLocationDrawPos.ToPoint() + offsetFromLocationIcon;
if (locationInfoOverlay.Rect.Bottom > rect.Bottom)
{
locationInfoRt.Pivot = Pivot.BottomLeft;
}
if (locationInfoOverlay.Rect.Right > rect.Right)
{
locationInfoRt.Pivot = locationInfoRt.Pivot == Pivot.TopLeft ? Pivot.TopRight : Pivot.BottomRight;
}
locationInfoOverlay?.AddToGUIUpdateList(order: 1);
}
float closestDist = 0.0f; float closestDist = 0.0f;
HighlightedLocation = null; HighlightedLocation = null;
if (GUI.MouseOn == null || GUI.MouseOn == mapContainer) if ((GUI.MouseOn == null || GUI.MouseOn == mapContainer))
{ {
for (int i = 0; i < Locations.Count; i++) for (int i = 0; i < Locations.Count; i++)
{ {
@@ -453,12 +688,13 @@ namespace Barotrauma
Level.Loaded.DebugSetEndLocation(null); Level.Loaded.DebugSetEndLocation(null);
Discover(CurrentLocation); Discover(CurrentLocation);
Visit(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
@@ -467,12 +703,6 @@ namespace Barotrauma
} }
} }
if (PlayerInput.KeyDown(Microsoft.Xna.Framework.Input.Keys.LeftShift) && PlayerInput.PrimaryMouseButtonClicked() && HighlightedLocation != null)
{
int distance = DistanceToClosestLocationWithOutpost(HighlightedLocation, out Location foundLocation);
DebugConsole.NewMessage($"Distance to closest outpost from {HighlightedLocation.Name} to {foundLocation?.Name} is {distance}");
}
if (PlayerInput.PrimaryMouseButtonClicked() && HighlightedLocation == null) if (PlayerInput.PrimaryMouseButtonClicked() && HighlightedLocation == null)
{ {
SelectLocation(-1); SelectLocation(-1);
@@ -481,10 +711,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;
@@ -501,13 +731,15 @@ namespace Barotrauma
Vector2 rectCenter = new Vector2(rect.Center.X, rect.Center.Y); Vector2 rectCenter = new Vector2(rect.Center.X, rect.Center.Y);
float missionIconScale = generationParams.MissionIcon != null ? 18.0f / generationParams.MissionIcon.SourceRect.Width : 1.0f;
Rectangle prevScissorRect = GameMain.Instance.GraphicsDevice.ScissorRectangle; Rectangle prevScissorRect = GameMain.Instance.GraphicsDevice.ScissorRectangle;
spriteBatch.End(); spriteBatch.End();
spriteBatch.GraphicsDevice.ScissorRectangle = Rectangle.Intersect(prevScissorRect, rect); spriteBatch.GraphicsDevice.ScissorRectangle = Rectangle.Intersect(prevScissorRect, rect);
spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable); spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
Vector2 topLeft = rectCenter + viewOffset; Vector2 topLeft = rectCenter + viewOffset - rect.Location.ToVector2();
Vector2 bottomRight = rectCenter + (viewOffset + new Vector2(Width, Height)); Vector2 bottomRight = topLeft + new Vector2(Width, Height);
Vector2 mapTileSize = mapTiles[0, 0].size * generationParams.MapTileScale; Vector2 mapTileSize = mapTiles[0, 0].size * generationParams.MapTileScale;
int startX = (int)Math.Floor(-topLeft.X / mapTileSize.X) - 1; int startX = (int)Math.Floor(-topLeft.X / mapTileSize.X) - 1;
@@ -568,7 +800,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;
@@ -577,24 +811,54 @@ namespace Barotrauma
drawRect.X = (int)pos.X - drawRect.Width / 2; drawRect.X = (int)pos.X - drawRect.Width / 2;
drawRect.Y = (int)pos.Y - drawRect.Width / 2; drawRect.Y = (int)pos.Y - drawRect.Width / 2;
if (drawRect.X > rect.Right - GUI.IntScale(100) && generationParams.MissionIcon != null && location.AvailableMissions.Any())
{
Vector2 offScreenMissionIconPos = new Vector2(rect.Right - GUI.IntScale(50), drawRect.Center.Y);
generationParams.MissionIcon.Draw(spriteBatch,
offScreenMissionIconPos,
generationParams.IndicatorColor, scale: missionIconScale * zoom);
GUI.Arrow.Draw(spriteBatch,
offScreenMissionIconPos + Vector2.UnitX * generationParams.MissionIcon.size.X * missionIconScale * zoom,
generationParams.IndicatorColor, MathHelper.PiOver2, scale: 0.5f);
}
if (!rect.Intersects(drawRect)) { continue; } if (!rect.Intersects(drawRect)) { continue; }
Color color = location.Type.SpriteColor; Color color = location.Type.SpriteColor;
if (!location.Discovered) { color = Color.White; } if (!location.Visited) { color = Color.White; }
if (location.Connections.Find(c => c.Locations.Contains(currentDisplayLocation)) == null) if (location.Connections.Find(c => c.Locations.Contains(currentDisplayLocation)) == null)
{ {
color *= 0.5f; color *= 0.5f;
} }
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 +890,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 +902,17 @@ 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;
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 +921,19 @@ 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 -= 80;
GUI.DrawString(spriteBatch, dPos + new Vector2(15, 32), "Faction: " + (location.Faction?.Prefab.Name ?? "none"), Color.White, Color.Black, font: GUIStyle.SubHeadingFont);
GUI.DrawString(spriteBatch, dPos + new Vector2(15, 50), "Secondary Faction: " + (location.SecondaryFaction?.Prefab.Name ?? "none"), Color.White, Color.Black, font: GUIStyle.SubHeadingFont);
dPos.Y += 48; dPos.Y += 48;
string name = $"Reputation: {location.Name}";
Vector2 nameSize = GUIStyle.SmallFont.MeasureString(name);
GUI.DrawString(spriteBatch, dPos, name, Color.White, Color.Black * 0.8f, 4, font: GUIStyle.SmallFont);
dPos.Y += nameSize.Y + 16;
Rectangle bgRect = new Rectangle((int)dPos.X, (int)dPos.Y, 256, 32); if (PlayerInput.KeyDown(Keys.LeftShift))
bgRect.Inflate(8,8); {
Color barColor = ToolBox.GradientLerp(location.Reputation.NormalizedValue, Color.Red, Color.Yellow, Color.LightGreen); GUI.DrawString(spriteBatch, new Vector2(150,150), "Dist: " +
GUI.DrawRectangle(spriteBatch, bgRect, Color.Black * 0.8f, isFilled: true); GetDistanceToClosestLocationOrConnection(CurrentLocation, int.MaxValue, loc => loc == location), Color.White, Color.Black, font: GUIStyle.SubHeadingFont);
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);
@@ -684,97 +950,6 @@ namespace Barotrauma
GUIComponent.DrawToolTip(spriteBatch, tooltip.Value.tip, tooltip.Value.targetArea); GUIComponent.DrawToolTip(spriteBatch, tooltip.Value.tip, tooltip.Value.targetArea);
drawRadiationTooltip = false; drawRadiationTooltip = false;
} }
else if (HighlightedLocation != null)
{
drawRadiationTooltip = false;
Vector2 pos = rectCenter + (HighlightedLocation.MapPosition + viewOffset) * zoom;
pos.X += 50 * zoom;
pos.X = (int)pos.X;
pos.Y = (int)pos.Y;
Vector2 nameSize = GUIStyle.LargeFont.MeasureString(HighlightedLocation.Name);
Vector2 typeSize = HighlightedLocation.Type.Name.IsNullOrEmpty() ? Vector2.Zero : GUIStyle.Font.MeasureString(HighlightedLocation.Type.Name);
Vector2 descSize = HighlightedLocation.Type.Description.IsNullOrEmpty() ? Vector2.Zero : GUIStyle.SmallFont.MeasureString(HighlightedLocation.Type.Description);
Vector2 size = new Vector2(Math.Max(nameSize.X, Math.Max(typeSize.X, descSize.X)), nameSize.Y + typeSize.Y + descSize.Y);
int highestSubTier = HighlightedLocation.HighestSubmarineTierAvailable();
List<(SubmarineClass subClass, int tier)> overrideTiers = null;
if (HighlightedLocation.CanHaveSubsForSale())
{
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));
}
}
}
int subAvailabilityTextCount = (highestSubTier > 0 ? 1 : 0) + (overrideTiers?.Count ?? 0);
size.Y += subAvailabilityTextCount * GUIStyle.SmallFont.MeasureString(TextManager.Get("advancedsub.all")).Y;
bool showReputation = hudVisibility > 0.0f && HighlightedLocation.Discovered && HighlightedLocation.Type.HasOutpost && HighlightedLocation.Reputation != null;
LocalizedString repLabelText = null, repValueText = null;
Vector2 repLabelSize = Vector2.Zero, repBarSize = Vector2.Zero;
if (showReputation)
{
repLabelText = TextManager.Get("reputation");
repLabelSize = GUIStyle.Font.MeasureString(repLabelText);
repBarSize = new Vector2(GUI.IntScale(200), repLabelSize.Y);
size.Y += 2 * repLabelSize.Y + GUI.IntScale(5) + repBarSize.Y;
repValueText = HighlightedLocation.Reputation.GetFormattedReputationText(addColorTags: false);
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(
spriteBatch,
new Rectangle(
(int)(pos.X - 60 * GUI.Scale),
(int)(pos.Y - size.Y),
(int)(size.X + 120 * GUI.Scale),
(int)(size.Y * 2.2f)),
Color.Black * hudVisibility);
var topLeftPos = pos - new Vector2(0.0f, size.Y / 2);
GUI.DrawString(spriteBatch, topLeftPos, HighlightedLocation.Name, GUIStyle.TextColorNormal * hudVisibility * 1.5f, font: GUIStyle.LargeFont);
topLeftPos += new Vector2(0.0f, nameSize.Y);
DrawText(HighlightedLocation.Type.Name);
if (!HighlightedLocation.Type.Description.IsNullOrEmpty())
{
topLeftPos += new Vector2(0.0f, descSize.Y);
DrawText(HighlightedLocation.Type.Description, font: GUIStyle.SmallFont);
}
if (highestSubTier > 0)
{
DrawSubAvailabilityText("advancedsub.all", highestSubTier);
}
if (overrideTiers != null)
{
foreach (var (subClass, tier) in overrideTiers)
{
DrawSubAvailabilityText($"advancedsub.{subClass}", tier);
}
}
void DrawSubAvailabilityText(string tag, int tier)
{
topLeftPos += new Vector2(0.0f, typeSize.Y);
DrawText(TextManager.GetWithVariable(tag, "[tiernumber]", tier.ToString()), font: GUIStyle.SmallFont);
}
if (showReputation)
{
topLeftPos += new Vector2(0.0f, typeSize.Y + repLabelSize.Y);
DrawText(repLabelText.Value);
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));
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));
}
void DrawText(LocalizedString text, GUIFont font = null) => GUI.DrawString(spriteBatch, topLeftPos, text, GUIStyle.TextColorNormal * hudVisibility * 1.5f, font: font);
}
if (drawRadiationTooltip) if (drawRadiationTooltip)
{ {
@@ -892,7 +1067,7 @@ namespace Barotrauma
} }
float a = 1.0f; float a = 1.0f;
if (!connection.Locations[0].Discovered && !connection.Locations[1].Discovered) if (!connection.Locations[0].Visited && !connection.Locations[1].Visited)
{ {
if (IsInFogOfWar(connection.Locations[0])) if (IsInFogOfWar(connection.Locations[0]))
{ {
@@ -961,25 +1136,25 @@ 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;
} }
if (unlockReputation != null)
DrawIcon( {
"LockedLocationConnection", (int)(28 * zoom), DrawIcon(
RichString.Rich(TextManager.GetWithVariables(unlockEvent.UnlockPathTooltip ?? "LockedPathTooltip", "LockedLocationConnection", (int)(28 * zoom),
("[requiredreputation]", Reputation.GetFormattedReputationText(MathUtils.InverseLerp(unlockReputation.MinReputation, unlockReputation.MaxReputation, unlockEvent.UnlockPathReputation), unlockEvent.UnlockPathReputation, addColorTags: true)), RichString.Rich(TextManager.GetWithVariables(unlockEvent.UnlockPathTooltip ?? "LockedPathTooltip",
("[currentreputation]", unlockReputation.GetFormattedReputationText(addColorTags: true))))); ("[requiredreputation]", Reputation.GetFormattedReputationText(MathUtils.InverseLerp(unlockReputation.MinReputation, unlockReputation.MaxReputation, unlockEvent.UnlockPathReputation), unlockEvent.UnlockPathReputation, addColorTags: true)),
("[currentreputation]", unlockReputation.GetFormattedReputationText(addColorTags: true)))));
}
} }
else else
{ {
@@ -1042,13 +1217,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);
} }
@@ -36,7 +36,7 @@ namespace Barotrauma
public static List<MapEntity> CopiedList = new List<MapEntity>(); public static List<MapEntity> CopiedList = new List<MapEntity>();
private static List<MapEntity> highlightedList = new List<MapEntity>(); private static List<MapEntity> highlightedInEditorList = new List<MapEntity>();
private static float highlightTimer; private static float highlightTimer;
@@ -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>
@@ -118,10 +131,7 @@ namespace Barotrauma
return; return;
} }
foreach (MapEntity e in mapEntityList) ClearHighlightedEntities();
{
e.isHighlighted = false;
}
if (DisableSelect) if (DisableSelect)
{ {
@@ -249,11 +259,10 @@ namespace Barotrauma
if (i == 0) highLightedEntity = e; if (i == 0) highLightedEntity = e;
} }
} }
UpdateHighlighting(highlightedEntities); UpdateHighlighting(highlightedEntities);
} }
if (highLightedEntity != null) highLightedEntity.isHighlighted = true; if (highLightedEntity != null) { highLightedEntity.IsHighlighted = true; }
} }
if (GUI.KeyboardDispatcher.Subscriber == null) if (GUI.KeyboardDispatcher.Subscriber == null)
@@ -275,7 +284,6 @@ namespace Barotrauma
if (startMovingPos != Vector2.Zero) if (startMovingPos != Vector2.Zero)
{ {
Item targetContainer = GetPotentialContainer(position, SelectedList); Item targetContainer = GetPotentialContainer(position, SelectedList);
if (targetContainer != null) { targetContainer.IsHighlighted = true; } if (targetContainer != null) { targetContainer.IsHighlighted = true; }
if (PlayerInput.PrimaryMouseButtonReleased()) if (PlayerInput.PrimaryMouseButtonReleased())
@@ -597,10 +605,10 @@ namespace Barotrauma
if (highlightedListBox != null) if (highlightedListBox != null)
{ {
if (GUI.MouseOn == highlightedListBox || highlightedListBox.IsParentOf(GUI.MouseOn)) return; if (GUI.MouseOn == highlightedListBox || highlightedListBox.IsParentOf(GUI.MouseOn)) return;
if (highlightedEntities.SequenceEqual(highlightedList)) return; if (highlightedEntities.SequenceEqual(highlightedInEditorList)) return;
} }
highlightedList = highlightedEntities; highlightedInEditorList = highlightedEntities;
highlightedListBox = new GUIListBox(new RectTransform(new Point(180, highlightedEntities.Count * 18 + 5), GUI.Canvas) highlightedListBox = new GUIListBox(new RectTransform(new Point(180, highlightedEntities.Count * 18 + 5), GUI.Canvas)
{ {
@@ -1083,7 +1091,7 @@ namespace Barotrauma
private void UpdateResizing(Camera cam) private void UpdateResizing(Camera cam)
{ {
isHighlighted = true; IsHighlighted = true;
int startX = ResizeHorizontal ? -1 : 0; int startX = ResizeHorizontal ? -1 : 0;
int StartY = ResizeVertical ? -1 : 0; int StartY = ResizeVertical ? -1 : 0;
@@ -1184,7 +1192,7 @@ namespace Barotrauma
private void DrawResizing(SpriteBatch spriteBatch, Camera cam) private void DrawResizing(SpriteBatch spriteBatch, Camera cam)
{ {
isHighlighted = true; IsHighlighted = true;
int startX = ResizeHorizontal ? -1 : 0; int startX = ResizeHorizontal ? -1 : 0;
int StartY = ResizeVertical ? -1 : 0; int StartY = ResizeVertical ? -1 : 0;
@@ -55,21 +55,14 @@ namespace Barotrauma
{ {
if (!CastShadow) { return; } if (!CastShadow) { return; }
if (convexHulls == null) convexHulls ??= new List<ConvexHull>();
var h = new ConvexHull(
new Rectangle((position - size / 2).ToPoint(), size.ToPoint()),
IsHorizontal,
this)
{ {
convexHulls = new List<ConvexHull>(); IsExteriorWall = IsExteriorWall
}
Vector2 halfSize = size / 2;
Vector2[] verts = new Vector2[]
{
position + new Vector2(-halfSize.X, halfSize.Y),
position + new Vector2(halfSize.X, halfSize.Y),
position + new Vector2(halfSize.X, -halfSize.Y),
position + new Vector2(-halfSize.X, -halfSize.Y),
}; };
var h = new ConvexHull(verts, Color.Black, this);
if (Math.Abs(rotation) > 0.001f) if (Math.Abs(rotation) > 0.001f)
{ {
h.Rotate(position, rotation); h.Rotate(position, rotation);
@@ -226,6 +219,9 @@ namespace Barotrauma
min.Y = Math.Min(worldPos.Y - decorativeSprite.Sprite.size.Y * (1.0f - decorativeSprite.Sprite.RelativeOrigin.Y) * scale, min.Y); min.Y = Math.Min(worldPos.Y - decorativeSprite.Sprite.size.Y * (1.0f - decorativeSprite.Sprite.RelativeOrigin.Y) * scale, min.Y);
max.Y = Math.Max(worldPos.Y + decorativeSprite.Sprite.size.Y * decorativeSprite.Sprite.RelativeOrigin.Y * scale, max.Y); max.Y = Math.Max(worldPos.Y + decorativeSprite.Sprite.size.Y * decorativeSprite.Sprite.RelativeOrigin.Y * scale, max.Y);
} }
Vector2 offset = GetCollapseEffectOffset();
min += offset;
max += offset;
if (min.X > worldView.Right || max.X < worldView.X) { return false; } if (min.X > worldView.Right || max.X < worldView.X) { return false; }
if (min.Y > worldView.Y || max.Y < worldView.Y - worldView.Height) { return false; } if (min.Y > worldView.Y || max.Y < worldView.Y - worldView.Height) { return false; }
@@ -295,6 +291,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();
@@ -504,7 +501,7 @@ namespace Barotrauma
private bool ConditionalMatches(PropertyConditional conditional) private bool ConditionalMatches(PropertyConditional conditional)
{ {
if (!string.IsNullOrEmpty(conditional.TargetItemComponentName)) if (!string.IsNullOrEmpty(conditional.TargetItemComponent))
{ {
return false; return false;
} }
@@ -533,7 +530,7 @@ namespace Barotrauma
float damage = msg.ReadRangedSingle(0.0f, 1.0f, 8) * MaxHealth; float damage = msg.ReadRangedSingle(0.0f, 1.0f, 8) * MaxHealth;
if (!invalidMessage && i < Sections.Length) if (!invalidMessage && i < Sections.Length)
{ {
SetDamage(i, damage); SetDamage(i, damage, isNetworkEvent: true);
} }
} }
} }
@@ -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 &&
@@ -116,7 +116,7 @@ namespace Barotrauma
bool isMouseOnComponent = GUI.MouseOn == component; bool isMouseOnComponent = GUI.MouseOn == component;
camera.MoveCamera(deltaTime, allowZoom: isMouseOnComponent, followSub: false); camera.MoveCamera(deltaTime, allowZoom: isMouseOnComponent, followSub: false);
if (isMouseOnComponent && if (isMouseOnComponent &&
(PlayerInput.MidButtonHeld() || PlayerInput.LeftButtonHeld())) (PlayerInput.MidButtonHeld() || PlayerInput.PrimaryMouseButtonHeld()))
{ {
Vector2 moveSpeed = PlayerInput.MouseSpeed * (float)deltaTime * 60.0f / camera.Zoom; Vector2 moveSpeed = PlayerInput.MouseSpeed * (float)deltaTime * 60.0f / camera.Zoom;
moveSpeed.X = -moveSpeed.X; moveSpeed.X = -moveSpeed.X;
@@ -39,7 +39,7 @@ namespace Barotrauma
{ {
Color clr = CurrentHull == null ? Color.DodgerBlue : GUIStyle.Green; Color clr = CurrentHull == null ? Color.DodgerBlue : GUIStyle.Green;
if (spawnType != SpawnType.Path) { clr = Color.Gray; } if (spawnType != SpawnType.Path) { clr = Color.Gray; }
if (isObstructed) if (!IsTraversable)
{ {
clr = Color.Black; clr = Color.Black;
} }
@@ -84,7 +84,7 @@ namespace Barotrauma
GUI.DrawLine(spriteBatch, GUI.DrawLine(spriteBatch,
drawPos, drawPos,
new Vector2(e.DrawPosition.X, -e.DrawPosition.Y), new Vector2(e.DrawPosition.X, -e.DrawPosition.Y),
(isObstructed ? Color.Gray : GUIStyle.Green) * 0.7f, width: 5, depth: 0.002f); (IsTraversable ? GUIStyle.Green : Color.Gray) * 0.7f, width: 5, depth: 0.002f);
} }
if (ConnectedGap != null) if (ConnectedGap != null)
{ {
@@ -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),
@@ -170,9 +175,9 @@ namespace Barotrauma
if (PlayerInput.KeyDown(Keys.Space)) if (PlayerInput.KeyDown(Keys.Space))
{ {
foreach (MapEntity e in mapEntityList) foreach (MapEntity e in HighlightedEntities)
{ {
if (!(e is WayPoint) || e == this || !e.IsHighlighted) { continue; } if (e is not WayPoint || e == this) { continue; }
if (linkedTo.Contains(e)) if (linkedTo.Contains(e))
{ {
@@ -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(
@@ -75,7 +75,7 @@ namespace Barotrauma.Networking
float gain = 1.0f; float gain = 1.0f;
float noiseGain = 0.0f; float noiseGain = 0.0f;
Vector3? position = null; Vector3? position = null;
if (character != null) if (character != null && !character.IsDead)
{ {
if (GameSettings.CurrentConfig.Audio.UseDirectionalVoiceChat) if (GameSettings.CurrentConfig.Audio.UseDirectionalVoiceChat)
{ {
@@ -332,7 +332,6 @@ namespace Barotrauma.Networking
FileSize = 0 FileSize = 0
}; };
Md5Hash.Cache.Remove(directTransfer.FilePath);
OnFinished(directTransfer); OnFinished(directTransfer);
} }
break; break;
@@ -414,7 +413,6 @@ namespace Barotrauma.Networking
{ {
finishedTransfers.Add((transferId, Timing.TotalTime)); finishedTransfers.Add((transferId, Timing.TotalTime));
StopTransfer(activeTransfer); StopTransfer(activeTransfer);
Md5Hash.Cache.Remove(activeTransfer.FilePath);
OnFinished(activeTransfer); OnFinished(activeTransfer);
} }
else else
@@ -76,6 +76,8 @@ namespace Barotrauma.Networking
Interrupted Interrupted
} }
private UInt16? debugStartGameCampaignSaveID;
private RoundInitStatus roundInitStatus = RoundInitStatus.NotStarted; private RoundInitStatus roundInitStatus = RoundInitStatus.NotStarted;
public bool RoundStarting => roundInitStatus == RoundInitStatus.Starting || roundInitStatus == RoundInitStatus.WaitingForStartGameFinalize; public bool RoundStarting => roundInitStatus == RoundInitStatus.Starting || roundInitStatus == RoundInitStatus.WaitingForStartGameFinalize;
@@ -326,7 +328,7 @@ namespace Barotrauma.Networking
return serverEndpoint switch return serverEndpoint switch
{ {
LidgrenEndpoint lidgrenEndpoint => new LidgrenClientPeer(lidgrenEndpoint, callbacks, ownerKey), LidgrenEndpoint lidgrenEndpoint => new LidgrenClientPeer(lidgrenEndpoint, callbacks, ownerKey),
SteamP2PEndpoint _ when ownerKey is Some<int> { Value: var key } => new SteamP2POwnerPeer(callbacks, key), SteamP2PEndpoint _ when ownerKey.TryUnwrap(out var key) => new SteamP2POwnerPeer(callbacks, key),
SteamP2PEndpoint steamP2PServerEndpoint when ownerKey.IsNone() => new SteamP2PClientPeer(steamP2PServerEndpoint, callbacks), SteamP2PEndpoint steamP2PServerEndpoint when ownerKey.IsNone() => new SteamP2PClientPeer(steamP2PServerEndpoint, callbacks),
_ => throw new ArgumentOutOfRangeException() _ => throw new ArgumentOutOfRangeException()
}; };
@@ -859,17 +861,24 @@ namespace Barotrauma.Networking
ContentFile file = ContentPackageManager.EnabledPackages.All ContentFile file = ContentPackageManager.EnabledPackages.All
.Select(p => .Select(p =>
p.Files.FirstOrDefault(f => f.Path == filePath)) p.Files.FirstOrDefault(f => f.Path == filePath))
.FirstOrDefault(f => !(f is null)); .FirstOrDefault(f => f is not null);
contentToPreload.AddIfNotNull(file); contentToPreload.AddIfNotNull(file);
} }
string campaignErrorInfo = string.Empty;
if (GameMain.GameSession?.Campaign is MultiPlayerCampaign campaign)
{
campaignErrorInfo = $" Round start save ID: {debugStartGameCampaignSaveID}, last save id: {campaign.LastSaveID}, pending save id: {campaign.PendingSaveID}.";
}
GameMain.GameSession.EventManager.PreloadContent(contentToPreload); GameMain.GameSession.EventManager.PreloadContent(contentToPreload);
int subEqualityCheckValue = inc.ReadInt32(); int subEqualityCheckValue = inc.ReadInt32();
if (subEqualityCheckValue != (Submarine.MainSub?.Info?.EqualityCheckVal ?? 0)) if (subEqualityCheckValue != (Submarine.MainSub?.Info?.EqualityCheckVal ?? 0))
{ {
string errorMsg = "Submarine equality check failed. The submarine loaded at your end doesn't match the one loaded by the server." + string errorMsg =
" There may have been an error in receiving the up-to-date submarine file from the server."; "Submarine equality check failed. The submarine loaded at your end doesn't match the one loaded by the server. " +
$"There may have been an error in receiving the up-to-date submarine file from the server. Round init status: {roundInitStatus}." + campaignErrorInfo;
GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:SubsDontMatch" + Level.Loaded.Seed, GameAnalyticsManager.ErrorSeverity.Error, errorMsg); GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:SubsDontMatch" + Level.Loaded.Seed, GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
throw new Exception(errorMsg); throw new Exception(errorMsg);
} }
@@ -886,7 +895,7 @@ namespace Barotrauma.Networking
$"Mission equality check failed. Mission count doesn't match the server. " + $"Mission equality check failed. Mission count doesn't match the server. " +
$"Server: {string.Join(", ", serverMissionIdentifiers)}, " + $"Server: {string.Join(", ", serverMissionIdentifiers)}, " +
$"client: {string.Join(", ", GameMain.GameSession.GameMode.Missions.Select(m => m.Prefab.Identifier))}, " + $"client: {string.Join(", ", GameMain.GameSession.GameMode.Missions.Select(m => m.Prefab.Identifier))}, " +
$"game session: {string.Join(", ", GameMain.GameSession.Missions.Select(m => m.Prefab.Identifier))})"; $"game session: {string.Join(", ", GameMain.GameSession.Missions.Select(m => m.Prefab.Identifier))}). Round init status: {roundInitStatus}." + campaignErrorInfo;
GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:MissionsCountMismatch" + Level.Loaded.Seed, GameAnalyticsManager.ErrorSeverity.Error, errorMsg); GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:MissionsCountMismatch" + Level.Loaded.Seed, GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
throw new Exception(errorMsg); throw new Exception(errorMsg);
} }
@@ -899,7 +908,7 @@ namespace Barotrauma.Networking
$"Mission equality check failed. The mission selected at your end doesn't match the one loaded by the server " + $"Mission equality check failed. The mission selected at your end doesn't match the one loaded by the server " +
$"Server: {string.Join(", ", serverMissionIdentifiers)}, " + $"Server: {string.Join(", ", serverMissionIdentifiers)}, " +
$"client: {string.Join(", ", GameMain.GameSession.GameMode.Missions.Select(m => m.Prefab.Identifier))}, " + $"client: {string.Join(", ", GameMain.GameSession.GameMode.Missions.Select(m => m.Prefab.Identifier))}, " +
$"game session: {string.Join(", ", GameMain.GameSession.Missions.Select(m => m.Prefab.Identifier))})"; $"game session: {string.Join(", ", GameMain.GameSession.Missions.Select(m => m.Prefab.Identifier))}). Round init status: {roundInitStatus}." + campaignErrorInfo;
GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:MissionsDontMatch" + Level.Loaded.Seed, GameAnalyticsManager.ErrorSeverity.Error, errorMsg); GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:MissionsDontMatch" + Level.Loaded.Seed, GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
throw new Exception(errorMsg); throw new Exception(errorMsg);
} }
@@ -922,7 +931,7 @@ namespace Barotrauma.Networking
", level value count: " + levelEqualityCheckValues.Count + ", level value count: " + levelEqualityCheckValues.Count +
", seed: " + Level.Loaded.Seed + ", seed: " + Level.Loaded.Seed +
", sub: " + Submarine.MainSub.Info.Name + " (" + Submarine.MainSub.Info.MD5Hash.ShortRepresentation + ")" + ", sub: " + Submarine.MainSub.Info.Name + " (" + Submarine.MainSub.Info.MD5Hash.ShortRepresentation + ")" +
", mirrored: " + Level.Loaded.Mirrored + ")."; ", mirrored: " + Level.Loaded.Mirrored + "). Round init status: {roundInitStatus}." + campaignErrorInfo;
GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:LevelsDontMatch" + Level.Loaded.Seed, GameAnalyticsManager.ErrorSeverity.Error, errorMsg); GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:LevelsDontMatch" + Level.Loaded.Seed, GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
throw new Exception(errorMsg); throw new Exception(errorMsg);
} }
@@ -988,7 +997,7 @@ namespace Barotrauma.Networking
GameMain.ModDownloadScreen.Reset(); GameMain.ModDownloadScreen.Reset();
ContentPackageManager.EnabledPackages.Restore(); ContentPackageManager.EnabledPackages.Restore();
CampaignMode.StartRoundCancellationToken?.Cancel(); GameMain.GameSession?.Campaign?.CancelStartRound();
if (SteamManager.IsInitialized) if (SteamManager.IsInitialized)
{ {
@@ -1322,6 +1331,8 @@ namespace Barotrauma.Networking
eventErrorWritten = false; eventErrorWritten = false;
GameMain.NetLobbyScreen.StopWaitingForStartRound(); GameMain.NetLobbyScreen.StopWaitingForStartRound();
debugStartGameCampaignSaveID = null;
while (CoroutineManager.IsCoroutineRunning("EndGame")) while (CoroutineManager.IsCoroutineRunning("EndGame"))
{ {
EndCinematic?.Stop(); EndCinematic?.Stop();
@@ -1471,19 +1482,12 @@ namespace Barotrauma.Networking
roundInitStatus = RoundInitStatus.Interrupted; roundInitStatus = RoundInitStatus.Interrupted;
yield return CoroutineStatus.Failure; yield return CoroutineStatus.Failure;
} }
else if (campaign.Map == null)
{
GameStarted = true;
DebugConsole.ThrowError("Failed to start campaign round (campaign map not loaded yet).");
GameMain.NetLobbyScreen.Select();
roundInitStatus = RoundInitStatus.Interrupted;
yield return CoroutineStatus.Failure;
}
if (NetIdUtils.IdMoreRecent(campaignSaveID, campaign.PendingSaveID)) if (NetIdUtils.IdMoreRecent(campaign.PendingSaveID, campaign.LastSaveID) ||
NetIdUtils.IdMoreRecent(campaignSaveID, campaign.PendingSaveID))
{ {
campaign.PendingSaveID = campaignSaveID; campaign.PendingSaveID = campaignSaveID;
DateTime saveFileTimeOut = DateTime.Now + new TimeSpan(0,0,60); DateTime saveFileTimeOut = DateTime.Now + new TimeSpan(0, 0, 60);
while (NetIdUtils.IdMoreRecent(campaignSaveID, campaign.LastSaveID)) while (NetIdUtils.IdMoreRecent(campaignSaveID, campaign.LastSaveID))
{ {
if (DateTime.Now > saveFileTimeOut) if (DateTime.Now > saveFileTimeOut)
@@ -1498,12 +1502,23 @@ namespace Barotrauma.Networking
} }
} }
if (campaign.Map == null)
{
GameStarted = true;
DebugConsole.ThrowError("Failed to start campaign round (campaign map not loaded yet).");
GameMain.NetLobbyScreen.Select();
roundInitStatus = RoundInitStatus.Interrupted;
yield return CoroutineStatus.Failure;
}
campaign.Map.SelectLocation(selectedLocationIndex); campaign.Map.SelectLocation(selectedLocationIndex);
LevelData levelData = nextLocationIndex > -1 ? LevelData levelData = nextLocationIndex > -1 ?
campaign.Map.Locations[nextLocationIndex].LevelData : campaign.Map.Locations[nextLocationIndex].LevelData :
campaign.Map.Connections[nextConnectionIndex].LevelData; campaign.Map.Connections[nextConnectionIndex].LevelData;
debugStartGameCampaignSaveID = campaign.LastSaveID;
if (roundSummary != null) if (roundSummary != null)
{ {
loadTask = campaign.SelectSummaryScreen(roundSummary, levelData, mirrorLevel, null); loadTask = campaign.SelectSummaryScreen(roundSummary, levelData, mirrorLevel, null);
@@ -2587,31 +2602,24 @@ namespace Barotrauma.Networking
public void WriteCharacterInfo(IWriteMessage msg, string newName = null) public void WriteCharacterInfo(IWriteMessage msg, string newName = null)
{ {
msg.WriteBoolean(characterInfo == null); msg.WriteBoolean(characterInfo == null);
msg.WritePadBits();
if (characterInfo == null) { return; } if (characterInfo == null) { return; }
msg.WriteString(newName ?? string.Empty); var head = characterInfo.Head;
msg.WriteByte((byte)characterInfo.Head.Preset.TagSet.Count); var netInfo = new NetCharacterInfo(
foreach (Identifier tag in characterInfo.Head.Preset.TagSet) NewName: newName ?? string.Empty,
{ Tags: head.Preset.TagSet.ToImmutableArray(),
msg.WriteIdentifier(tag); HairIndex: (byte)head.HairIndex,
} BeardIndex: (byte)head.BeardIndex,
msg.WriteByte((byte)characterInfo.Head.HairIndex); MoustacheIndex: (byte)head.MoustacheIndex,
msg.WriteByte((byte)characterInfo.Head.BeardIndex); FaceAttachmentIndex: (byte)head.FaceAttachmentIndex,
msg.WriteByte((byte)characterInfo.Head.MoustacheIndex); SkinColor: head.SkinColor,
msg.WriteByte((byte)characterInfo.Head.FaceAttachmentIndex); HairColor: head.HairColor,
msg.WriteColorR8G8B8(characterInfo.Head.SkinColor); FacialHairColor: head.FacialHairColor,
msg.WriteColorR8G8B8(characterInfo.Head.HairColor); JobVariants: GameMain.NetLobbyScreen.JobPreferences.Select(NetJobVariant.FromJobVariant).ToImmutableArray());
msg.WriteColorR8G8B8(characterInfo.Head.FacialHairColor);
var jobPreferences = GameMain.NetLobbyScreen.JobPreferences; msg.WriteNetSerializableStruct(netInfo);
int count = Math.Min(jobPreferences.Count, 3);
msg.WriteByte((byte)count);
for (int i = 0; i < count; i++)
{
msg.WriteIdentifier(jobPreferences[i].Prefab.Identifier);
msg.WriteByte((byte)jobPreferences[i].Variant);
}
} }
public void Vote(VoteType voteType, object data) public void Vote(VoteType voteType, object data)
@@ -2623,7 +2631,13 @@ namespace Barotrauma.Networking
using (var segmentTable = SegmentTableWriter<ClientNetSegment>.StartWriting(msg)) using (var segmentTable = SegmentTableWriter<ClientNetSegment>.StartWriting(msg))
{ {
segmentTable.StartNewSegment(ClientNetSegment.Vote); segmentTable.StartNewSegment(ClientNetSegment.Vote);
Voting.ClientWrite(msg, voteType, data); bool succeeded = Voting.ClientWrite(msg, voteType, data);
if (!succeeded)
{
throw new Exception(
$"Failed to write vote of type {voteType}: " +
$"data was of invalid type {data?.GetType().Name ?? "NULL"}");
}
} }
ClientPeer.Send(msg, DeliveryMethod.Reliable); ClientPeer.Send(msg, DeliveryMethod.Reliable);
@@ -92,10 +92,10 @@ namespace Barotrauma.Networking
Name = GameMain.Client.Name, Name = GameMain.Client.Name,
OwnerKey = ownerKey, OwnerKey = ownerKey,
SteamId = SteamManager.GetSteamId().Select(id => (AccountId)id), SteamId = SteamManager.GetSteamId().Select(id => (AccountId)id),
SteamAuthTicket = steamAuthTicket switch SteamAuthTicket = steamAuthTicket?.Data switch
{ {
null => Option<byte[]>.None(), null => Option<byte[]>.None(),
var ticket => Option<byte[]>.Some(ticket.Data) var ticketData => Option<byte[]>.Some(ticketData)
}, },
GameVersion = GameMain.Version.ToString(), GameVersion = GameMain.Version.ToString(),
Language = GameSettings.CurrentConfig.Language.Value Language = GameSettings.CurrentConfig.Language.Value
@@ -156,7 +156,7 @@ namespace Barotrauma.Networking
var packet = INetSerializableStruct.Read<ClientSteamTicketAndVersionPacket>(inc); var packet = INetSerializableStruct.Read<ClientSteamTicketAndVersionPacket>(inc);
packet.SteamAuthTicket.TryUnwrap(out byte[] ticket); packet.SteamAuthTicket.TryUnwrap(out var ticket);
Steamworks.BeginAuthResult authSessionStartState = SteamManager.StartAuthSession(ticket, steamId); Steamworks.BeginAuthResult authSessionStartState = SteamManager.StartAuthSession(ticket, steamId);
if (authSessionStartState != Steamworks.BeginAuthResult.OK) if (authSessionStartState != Steamworks.BeginAuthResult.OK)
@@ -1,5 +1,6 @@
#nullable enable #nullable enable
using Barotrauma.Extensions;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@@ -70,6 +71,9 @@ namespace Barotrauma.Networking
[Serialize(PlayStyle.Casual, IsPropertySaveable.Yes)] [Serialize(PlayStyle.Casual, IsPropertySaveable.Yes)]
public PlayStyle PlayStyle { get; set; } public PlayStyle PlayStyle { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
public LanguageIdentifier Language { get; set; }
public Version GameVersion { get; set; } = new Version(0, 0, 0, 0); public Version GameVersion { get; set; } = new Version(0, 0, 0, 0);
public Option<int> Ping = Option<int>.None(); public Option<int> Ping = Option<int>.None();
@@ -281,7 +285,7 @@ namespace Barotrauma.Networking
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
float elementHeight = 0.075f; const float elementHeight = 0.075f;
// Spacing // Spacing
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.025f), content.RectTransform), style: null); new GUIFrame(new RectTransform(new Vector2(1.0f, 0.025f), content.RectTransform), style: null);
@@ -294,6 +298,11 @@ namespace Barotrauma.Networking
serverMsg.Content.RectTransform.SizeChanged += () => { msgText.CalculateHeightFromText(); }; serverMsg.Content.RectTransform.SizeChanged += () => { msgText.CalculateHeightFromText(); };
msgText.RectTransform.SizeChanged += () => { serverMsg.UpdateScrollBarSize(); }; msgText.RectTransform.SizeChanged += () => { serverMsg.UpdateScrollBarSize(); };
var languageLabel = new GUITextBlock(new RectTransform(new Vector2(1.0f, elementHeight), content.RectTransform), TextManager.Get("Language"));
new GUITextBlock(new RectTransform(Vector2.One, languageLabel.RectTransform),
ServerLanguageOptions.Options.FirstOrNull(o => o.Identifier == Language)?.Label ?? TextManager.Get("Unknown"),
textAlignment: Alignment.Right);
var gameMode = new GUITextBlock(new RectTransform(new Vector2(1.0f, elementHeight), content.RectTransform), TextManager.Get("GameMode")); var gameMode = new GUITextBlock(new RectTransform(new Vector2(1.0f, elementHeight), content.RectTransform), TextManager.Get("GameMode"));
new GUITextBlock(new RectTransform(Vector2.One, gameMode.RectTransform), new GUITextBlock(new RectTransform(Vector2.One, gameMode.RectTransform),
TextManager.Get(GameMode.IsEmpty ? "Unknown" : "GameMode." + GameMode).Fallback(GameMode.Value), TextManager.Get(GameMode.IsEmpty ? "Unknown" : "GameMode." + GameMode).Fallback(GameMode.Value),
@@ -363,7 +372,7 @@ namespace Barotrauma.Networking
packageText.Selected = true; packageText.Selected = true;
} }
//workshop download link found //workshop download link found
else if (package.Id is Some<ContentPackageId> { Value: var ugcId } && ugcId is SteamWorkshopId) else if (package.Id.TryUnwrap(out var ugcId) && ugcId is SteamWorkshopId)
{ {
packageText.ToolTip = TextManager.GetWithVariable("ServerListIncompatibleContentPackageWorkshopAvailable", "[contentpackage]", package.Name); packageText.ToolTip = TextManager.GetWithVariable("ServerListIncompatibleContentPackageWorkshopAvailable", "[contentpackage]", package.Name);
} }
@@ -417,8 +426,9 @@ namespace Barotrauma.Networking
GameMode = valueGetter("gamemode")?.ToIdentifier() ?? Identifier.Empty; GameMode = valueGetter("gamemode")?.ToIdentifier() ?? Identifier.Empty;
if (Enum.TryParse(valueGetter("traitors"), out YesNoMaybe traitorsEnabled)) { TraitorsEnabled = traitorsEnabled; } if (Enum.TryParse(valueGetter("traitors"), out YesNoMaybe traitorsEnabled)) { TraitorsEnabled = traitorsEnabled; }
if (Enum.TryParse(valueGetter("playstyle"), out PlayStyle playStyle)) { PlayStyle = playStyle; } if (Enum.TryParse(valueGetter("playstyle"), out PlayStyle playStyle)) { PlayStyle = playStyle; }
Language = valueGetter("language")?.ToLanguageIdentifier() ?? LanguageIdentifier.None;
ContentPackages = ExtractContentPackageInfo(valueGetter).ToImmutableArray(); ContentPackages = ExtractContentPackageInfo(ServerName, valueGetter).ToImmutableArray();
bool getBool(string key) bool getBool(string key)
{ {
@@ -427,8 +437,34 @@ namespace Barotrauma.Networking
} }
} }
private static ContentPackageInfo[] ExtractContentPackageInfo(Func<string, string?> valueGetter) private static ContentPackageInfo[] ExtractContentPackageInfo(string serverName, Func<string, string?> valueGetter)
{ {
//workaround to ServerRules queries truncating the values to 255 bytes
int individualPackageIndex = 0;
string? individualPackage = valueGetter($"contentpackage{individualPackageIndex}");
if (!individualPackage.IsNullOrEmpty())
{
List<ContentPackageInfo> contentPackages = new List<ContentPackageInfo>();
do
{
string[] splitPackageInfo = individualPackage.Split(',');
if (splitPackageInfo.Length != 3)
{
DebugConsole.Log(
$"Error in a server's content package list: malformed content package info ({individualPackage}).");
return Array.Empty<ContentPackageInfo>();
}
string name = splitPackageInfo[0];
string hash = splitPackageInfo[1];
ulong.TryParse(splitPackageInfo[2], out ulong id);
contentPackages.Add(new ContentPackageInfo(name, hash, Option<ContentPackageId>.Some(new SteamWorkshopId(id))));
individualPackageIndex++;
individualPackage = valueGetter($"contentpackage{individualPackageIndex}");
} while (!individualPackage.IsNullOrEmpty());
return contentPackages.ToArray();
}
string? joinedNames = valueGetter("contentpackage"); string? joinedNames = valueGetter("contentpackage");
string? joinedHashes = valueGetter("contentpackagehash"); string? joinedHashes = valueGetter("contentpackagehash");
string? joinedWorkshopIds = valueGetter("contentpackageid"); string? joinedWorkshopIds = valueGetter("contentpackageid");
@@ -438,9 +474,11 @@ namespace Barotrauma.Networking
#warning TODO: genericize #warning TODO: genericize
ulong[] contentPackageIds = joinedWorkshopIds.IsNullOrEmpty() ? new ulong[1] : SteamManager.ParseWorkshopIds(joinedWorkshopIds).ToArray(); ulong[] contentPackageIds = joinedWorkshopIds.IsNullOrEmpty() ? new ulong[1] : SteamManager.ParseWorkshopIds(joinedWorkshopIds).ToArray();
if (contentPackageNames.Length != contentPackageHashes.Length if (contentPackageNames.Length != contentPackageHashes.Length || contentPackageHashes.Length != contentPackageIds.Length)
|| contentPackageHashes.Length != contentPackageIds.Length)
{ {
DebugConsole.Log(
$"The number of names, hashes and Workshop IDs on server \"{serverName}\"" +
$" doesn't match: {contentPackageNames.Length} names ({string.Join(", ", contentPackageNames)}), {contentPackageHashes.Length} hashes, {contentPackageIds.Length} ids)");
return Array.Empty<ContentPackageInfo>(); return Array.Empty<ContentPackageInfo>();
} }
@@ -35,7 +35,7 @@ namespace Barotrauma
} }
private static Option<ServerInfo> InfoFromListEntry(Steamworks.Data.ServerInfo entry) => private static Option<ServerInfo> InfoFromListEntry(Steamworks.Data.ServerInfo entry) =>
entry.Name.IsNullOrEmpty() entry.Name.IsNullOrEmpty() || entry.Address is null
? Option<ServerInfo>.None() ? Option<ServerInfo>.None()
: Option<ServerInfo>.Some(new ServerInfo(new LidgrenEndpoint(entry.Address, entry.ConnectionPort)) : Option<ServerInfo>.Some(new ServerInfo(new LidgrenEndpoint(entry.Address, entry.ConnectionPort))
{ {
@@ -71,10 +71,10 @@ namespace Barotrauma
foreach (var lobby in lobbies) foreach (var lobby in lobbies)
{ {
string lobbyOwnerStr = lobby.GetData("lobbyowner"); string lobbyOwnerStr = lobby.GetData("lobbyowner") ?? "";
lobbyQuery = lobbyQuery.WithoutKeyValue("lobbyowner", lobbyOwnerStr); lobbyQuery = lobbyQuery.WithoutKeyValue("lobbyowner", lobbyOwnerStr);
string serverName = lobby.GetData("name"); string serverName = lobby.GetData("name") ?? "";
if (string.IsNullOrEmpty(serverName)) { continue; } if (string.IsNullOrEmpty(serverName)) { continue; }
var ownerId = SteamId.Parse(lobbyOwnerStr); var ownerId = SteamId.Parse(lobbyOwnerStr);

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