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;
public float MinZoom
{
get { return minZoom;}
set { minZoom = MathHelper.Clamp(value, 0.001f, 10.0f); }
get { return minZoom; }
set { minZoom = MathHelper.Clamp(value, 0.001f, 10.0f); }
}
private float maxZoom = 2.0f;
@@ -63,7 +63,7 @@ namespace Barotrauma
private float prevZoom;
public float Shake;
private Vector2 shakePosition;
public Vector2 ShakePosition { get; private set; }
private float shakeTimer;
private float globalZoomScale = 1.0f;
@@ -371,7 +371,7 @@ namespace Barotrauma
if (Shake < 0.01f)
{
shakePosition = Vector2.Zero;
ShakePosition = Vector2.Zero;
shakeTimer = 0.0f;
}
else
@@ -379,11 +379,11 @@ namespace Barotrauma
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);
shakePosition = noisePos * Shake * 2.0f;
ShakePosition = noisePos * Shake * 2.0f;
Shake = MathHelper.Lerp(Shake, 0.0f, deltaTime * 2.0f);
}
Translate(moveCam + shakePosition);
Translate(moveCam + ShakePosition);
Freeze = false;
}
@@ -6,6 +6,8 @@ namespace Barotrauma
{
class CameraTransition
{
private static List<CameraTransition> activeTransitions = new List<CameraTransition>();
public bool Running
{
get;
@@ -19,6 +21,7 @@ namespace Barotrauma
private readonly float? endZoom;
public readonly float WaitDuration;
public float EndWaitDuration = 0.1f;
public readonly float PanDuration;
public readonly bool FadeOut;
public readonly bool LosFadeIn;
@@ -29,6 +32,8 @@ namespace Barotrauma
public bool AllowInterrupt = false;
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)
{
@@ -45,8 +50,19 @@ namespace Barotrauma
if (targetEntity == null) { return; }
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");
activeTransitions.Add(this);
}
public void Stop()
@@ -62,11 +78,13 @@ namespace Barotrauma
#endif
}
private float DeltaTime => CoroutineManager.Paused && !RunWhilePaused ? 0 : CoroutineManager.DeltaTime;
private IEnumerable<CoroutineStatus> Update(ISpatialEntity targetEntity, Camera cam)
{
if (targetEntity == null || (targetEntity is Entity e && e.Removed)) { yield return CoroutineStatus.Success; }
prevControlled = Character.Controlled;
prevControlled ??= Character.Controlled;
if (RemoveControlFromCharacter)
{
#if CLIENT
@@ -80,6 +98,7 @@ namespace Barotrauma
float endZoom = this.endZoom ?? 0.5f;
Vector2 initialCameraPos = cam.Position;
Vector2? initialTargetPos = targetEntity?.WorldPosition;
Vector2 endPos = cam.Position;
float timer = -WaitDuration;
@@ -137,13 +156,13 @@ namespace Barotrauma
{
startPos += targetEntity.WorldPosition - initialTargetPos.Value;
}
Vector2 endPos = cameraEndPos.HasValue ?
endPos = cameraEndPos.HasValue ?
new Vector2(
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)) :
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);
#if CLIENT
@@ -162,14 +181,21 @@ namespace Barotrauma
Lights.LightManager.ViewTarget = prevControlled ?? (targetEntity as Entity);
}
#endif
timer += CoroutineManager.DeltaTime;
timer += DeltaTime;
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
GUI.ScreenOverlayColor = Color.TransparentBlack;
@@ -19,6 +19,17 @@ namespace Barotrauma
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;
while (timer < time)
{
@@ -26,15 +37,16 @@ namespace Barotrauma
float m = MathHelper.Lerp(1, Config.DeadEntityColorMultiplier, MathUtils.InverseLerp(0, time, timer));
foreach (var item in thalamusItems)
{
if (item.Color.A == 0) { continue; }
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);
}
}
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);
}
yield return CoroutineStatus.Running;
@@ -99,12 +99,14 @@ namespace Barotrauma
float newAngularVelocity = Collider.AngularVelocity;
Collider.CorrectPosition(character.MemState, out newPosition, out newVelocity, out newRotation, out newAngularVelocity);
newVelocity = newVelocity.ClampLength(100.0f);
if (!MathUtils.IsValid(newVelocity)) { newVelocity = Vector2.Zero; }
overrideTargetMovement = newVelocity.LengthSquared() > 0.01f ? newVelocity : Vector2.Zero;
Collider.LinearVelocity = newVelocity;
Collider.AngularVelocity = newAngularVelocity;
if (Collider.BodyType == BodyType.Dynamic)
{
newVelocity = newVelocity.ClampLength(100.0f);
if (!MathUtils.IsValid(newVelocity)) { newVelocity = Vector2.Zero; }
overrideTargetMovement = newVelocity.LengthSquared() > 0.01f ? newVelocity : Vector2.Zero;
Collider.LinearVelocity = newVelocity;
Collider.AngularVelocity = newAngularVelocity;
}
float distSqrd = Vector2.DistanceSquared(newPosition, Collider.SimPosition);
float errorTolerance = character.CanMove && !character.IsRagdolled ? 0.01f : 0.2f;
@@ -109,6 +109,26 @@ namespace Barotrauma
set => grainStrength = Math.Max(0, value);
}
/// <summary>
/// Can be used by status effects
/// </summary>
public float CollapseEffectStrength
{
get { return Level.Loaded?.Renderer?.CollapseEffectStrength ?? 0.0f; }
set
{
if (Level.Loaded?.Renderer == null) { return; }
if (Controlled == this)
{
float strength = MathHelper.Clamp(value, 0.0f, 1.0f);
Level.Loaded.Renderer.CollapseEffectStrength = strength;
Level.Loaded.Renderer.CollapseEffectOrigin = Submarine?.WorldPosition ?? WorldPosition;
Screen.Selected.Cam.Shake = Math.Max(MathF.Pow(strength, 3) * 100.0f, Screen.Selected.Cam.Shake);
Screen.Selected.Cam.Rotation = strength * (PerlinNoise.GetPerlin((float)Timing.TotalTime * 0.01f, (float)Timing.TotalTime * 0.05f) - 0.5f);
Level.Loaded.Renderer.ChromaticAberrationStrength = value * 50.0f;
}
}
}
/// <summary>
/// Can be used to set camera shake from status effects
/// </summary>
@@ -278,6 +298,21 @@ namespace Barotrauma
{
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)
//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)
@@ -296,8 +331,7 @@ namespace Barotrauma
float targetOffsetAmount = 0.0f;
if (moveCam)
{
if (NeedsAir && !IsProtectedFromPressure() &&
(AnimController.CurrentHull == null || AnimController.CurrentHull.LethalPressure > 0.0f))
if (!IsProtectedFromPressure && (AnimController.CurrentHull == null || AnimController.CurrentHull.LethalPressure > 0.0f))
{
float pressure = AnimController.CurrentHull == null ? 100.0f : AnimController.CurrentHull.LethalPressure;
if (pressure > 0.0f)
@@ -911,7 +945,7 @@ namespace Barotrauma
{
name += " " + TextManager.Get("Disguised");
}
else if (Info.Title != null)
else if (Info.Title != null && TeamID != CharacterTeamType.Team1)
{
name += '\n' + Info.Title;
}
@@ -987,13 +1021,13 @@ 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));
Vector2 healthBarPos = new Vector2(pos.X - 50, -pos.Y);
GUI.DrawProgressBar(spriteBatch, healthBarPos, new Vector2(100.0f, 15.0f),
CharacterHealth.DisplayedVitality / MaxVitality,
CharacterHealth.DisplayedVitality / MaxVitality,
Color.Lerp(GUIStyle.Red, GUIStyle.Green, CharacterHealth.DisplayedVitality / MaxVitality) * 0.8f * hudInfoAlpha,
new Color(0.5f, 0.57f, 0.6f, 1.0f) * hudInfoAlpha);
}
@@ -13,9 +13,8 @@ namespace Barotrauma
{
const float BossHealthBarDuration = 120.0f;
class BossHealthBar
abstract class BossProgressBar
{
public readonly Character Character;
public float FadeTimer;
public readonly GUIComponent TopContainer;
@@ -24,9 +23,18 @@ namespace Barotrauma
public readonly GUIProgressBar TopHealthBar;
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;
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),
RelativeOffset = new Vector2(0.0f, 0.01f)
}, 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)
{
MinSize = new Point(100, HUDLayoutSettings.HealthBarArea.Size.Y)
@@ -42,22 +50,95 @@ namespace Barotrauma
{
Color = GUIStyle.Red
};
CreateNumberText(TopHealthBar);
SideContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), bossHealthContainer.RectTransform)
{
MinSize = new Point(80, 60)
}, 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")
{
Color = GUIStyle.Red
};
CreateNumberText(SideHealthBar);
TopContainer.Visible = SideContainer.Visible = false;
TopContainer.CanBeFocused = false;
TopContainer.Children.ForEach(c => c.CanBeFocused = false);
SideContainer.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 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 LanguageIdentifier cachedHudTextLanguage = LanguageIdentifier.None;
private static GUILayoutGroup bossHealthContainer;
@@ -107,7 +189,7 @@ namespace Barotrauma
GameMain.GameSession?.Campaign != null &&
(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>();
@@ -121,10 +203,15 @@ namespace Barotrauma
public static LocalizedString GetCachedHudText(string textTag, InputType keyBind)
{
if (cachedHudTextLanguage != GameSettings.CurrentConfig.Language)
{
cachedHudTexts.Clear();
}
Identifier key = (textTag + keyBind).ToIdentifier();
if (cachedHudTexts.TryGetValue(key, out LocalizedString text)) { return text; }
text = TextManager.GetWithVariable(textTag, "[key]", GameSettings.CurrentConfig.KeyMap.KeyBindText(keyBind)).Value;
cachedHudTexts.Add(key, text);
cachedHudTextLanguage = GameSettings.CurrentConfig.Language;
return text;
}
@@ -159,7 +246,7 @@ namespace Barotrauma
public static void Update(float deltaTime, Character character, Camera cam)
{
UpdateBossHealthBars(deltaTime);
UpdateBossProgressBars(deltaTime);
if (GUI.DisableHUD)
{
@@ -623,7 +710,9 @@ namespace Barotrauma
GUI.DrawString(spriteBatch, textPos, focusName, nameColor, Color.Black * 0.7f, 2, GUIStyle.SubHeadingFont, ForceUpperCase.No);
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);
textPos.Y += GUIStyle.SubHeadingFont.MeasureString(character.FocusedCharacter.Info.Title.Value).Y;
@@ -648,7 +737,8 @@ namespace Barotrauma
if (!character.DisableHealthWindow &&
character.IsFriendly(character.FocusedCharacter) &&
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),
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 (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;
if (healthBarMode == EnemyHealthBarMode.HideAll)
{
return;
}
var existingBar = bossHealthBars.Find(b => b.Character == character);
if (existingBar != null)
if (bossProgressBars.Count > 5)
{
existingBar.FadeTimer = BossHealthBarDuration;
return;
}
if (bossHealthBars.Count > 5)
{
BossHealthBar oldestHealthBar = bossHealthBars.First();
foreach (var bar in bossHealthBars)
BossProgressBar oldestHealthBar = bossProgressBars.First();
foreach (var bar in bossProgressBars)
{
if (bar.TopHealthBar.BarSize < oldestHealthBar.TopHealthBar.BarSize)
{
@@ -690,62 +800,69 @@ namespace Barotrauma
}
oldestHealthBar.FadeTimer = Math.Min(oldestHealthBar.FadeTimer, 1.0f);
}
bossHealthBars.Add(new BossHealthBar(character));
bossProgressBars.Add(progressBar);
}
public static void UpdateBossHealthBars(float deltaTime)
public static void UpdateBossProgressBars(float deltaTime)
{
var healthBarMode = GameMain.NetworkMember?.ServerSettings.ShowEnemyHealthBars ?? GameSettings.CurrentConfig.ShowEnemyHealthBars;
for (int i = 0; i < bossHealthBars.Count; i++)
for (int i = 0; i < bossProgressBars.Count; i++)
{
var bossHealthBar = bossHealthBars[i];
var bossHealthBar = bossProgressBars[i];
bool showTopBar = i == 0;
if (showTopBar != bossHealthBar.TopContainer.Visible)
bool showTopBar = i == bossProgressBars.Count - 1;
if (showTopBar && !bossHealthBar.TopContainer.Visible)
{
bossHealthContainer.Recalculate();
bossHealthBar.SideContainer.SetAsLastChild();
SetColor(bossHealthBar, bossHealthBar.SideContainer, 0);
}
bossHealthBar.TopContainer.Visible = showTopBar;
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);
foreach (var c in bossHealthBar.SideContainer.GetAllChildren().Concat(bossHealthBar.TopContainer.GetAllChildren()))
if (bossHealthBar.TopContainer.Visible)
{
c.Color = new Color(c.Color, (byte)(alpha * 255));
if (c is GUITextBlock textBlock)
SetColor(bossHealthBar, bossHealthBar.TopContainer, alpha);
}
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;
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)
if (bossHealthBar.Interrupted)
{
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 -= deltaTime;
}
for (int i = bossHealthBars.Count - 1; i >= 0 ; i--)
for (int i = bossProgressBars.Count - 1; i >= 0 ; i--)
{
var bossHealthBar = bossHealthBars[i];
var bossHealthBar = bossProgressBars[i];
if (bossHealthBar.FadeTimer <= 0 || healthBarMode == EnemyHealthBarMode.HideAll)
{
bossHealthBar.SideContainer.Parent?.RemoveChild(bossHealthBar.SideContainer);
bossHealthBar.TopContainer.Parent?.RemoveChild(bossHealthBar.TopContainer);
bossHealthBars.RemoveAt(i);
bossProgressBars.RemoveAt(i);
bossHealthContainer.Recalculate();
}
}
@@ -540,37 +540,45 @@ namespace Barotrauma
string ragdollFile = inc.ReadString();
Identifier npcId = inc.ReadIdentifier();
Identifier factionId = inc.ReadIdentifier();
float minReputationToHire = 0.0f;
if (factionId != default)
{
minReputationToHire = inc.ReadSingle();
}
uint jobIdentifier = inc.ReadUInt32();
int variant = inc.ReadByte();
JobPrefab jobPrefab = null;
Dictionary<Identifier, float> skillLevels = new Dictionary<Identifier, float>();
if (jobIdentifier > 0)
{
{
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))
{
float skillLevel = inc.ReadSingle();
skillLevels.Add(skillPrefab.Identifier, skillLevel);
}
}
}
}
// TODO: animations
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.Head.SkinColor = skinColor;
ch.Head.HairColor = hairColor;
ch.Head.FacialHairColor = facialHairColor;
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);
return ch;
}
@@ -593,6 +593,7 @@ namespace Barotrauma
{
character.MerchantIdentifier = inc.ReadIdentifier();
}
character.Faction = inc.ReadIdentifier();
character.HumanPrefabHealthMultiplier = humanPrefabHealthMultiplier;
character.Wallet.Balance = balance;
character.Wallet.RewardDistribution = rewardDistribution;
@@ -87,6 +87,8 @@ namespace Barotrauma
/// Container for the icons above the health bar
/// </summary>
private GUIComponent afflictionIconContainer;
private float afflictionIconRefreshTimer;
const float AfflictionIconRefreshInterval = 1.0f;
private GUIButton showHiddenAfflictionsButton;
@@ -699,10 +701,11 @@ namespace Barotrauma
blurStrength = Math.Max(blurStrength, affliction.GetScreenBlurStrength());
radialDistortStrength = Math.Max(radialDistortStrength, affliction.GetRadialDistortStrength());
chromaticAberrationStrength = Math.Max(chromaticAberrationStrength, affliction.GetChromaticAberrationStrength());
float afflictionGrainStrength = affliction.GetScreenGrainStrength();
if (afflictionGrainStrength > 0.0f)
{
grainStrength = Math.Max(grainStrength, affliction.GetScreenGrainStrength());
grainStrength = Math.Max(grainStrength, afflictionGrainStrength);
Color afflictionGrainColor = affliction.GetActiveEffect()?.GrainColor ?? Color.White;
grainColor = Color.Lerp(grainColor, afflictionGrainColor, (float)Math.Pow(1.0f - oxygenLowStrength, 2));
}
@@ -861,7 +864,7 @@ namespace Barotrauma
{
treatmentButton.ToolTip =
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'
+ treatmentButton.ToolTip.NestedStr);
}
@@ -1018,12 +1021,8 @@ namespace Barotrauma
foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
{
var affliction = kvp.Key;
if (affliction.Prefab.AfflictionOverlay != null)
{
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));
}
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));
}
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; }
if (r2.GUIComponent.UserData is not AfflictionPrefab prefab2) { return 1; }
var index1 = statusIcons.IndexOf(s => s.Prefab == prefab1);
var index2 = statusIcons.IndexOf(s => s.Prefab == prefab2);
return index1.CompareTo(index2);
});
(afflictionIconContainer as GUILayoutGroup).NeedsToRecalculate = true;
afflictionIconContainer.RectTransform.SortChildren((r1, r2) =>
{
if (r1.GUIComponent.UserData is not AfflictionPrefab prefab1) { return -1; }
if (r2.GUIComponent.UserData is not AfflictionPrefab prefab2) { return 1; }
var index1 = statusIcons.IndexOf(s => s.Prefab == prefab1);
var index2 = statusIcons.IndexOf(s => s.Prefab == prefab2);
return index1.CompareTo(index2);
});
(afflictionIconContainer as GUILayoutGroup).NeedsToRecalculate = true;
afflictionIconRefreshTimer = AfflictionIconRefreshInterval;
}
Rectangle hiddenAfflictionHoverArea = showHiddenAfflictionsButton.Rect;
foreach (GUIComponent child in hiddenAfflictionIconContainer.Children)
@@ -1983,6 +1987,7 @@ namespace Barotrauma
{
newAfflictions.Clear();
newPeriodicEffects.Clear();
bool newAdded = false;
byte afflictionCount = inc.ReadByte();
for (int i = 0; i < afflictionCount; i++)
{
@@ -2062,6 +2067,7 @@ namespace Barotrauma
{
existingAffliction = afflictionPrefab.Instantiate(strength);
afflictions.Add(existingAffliction, limb);
newAdded = true;
}
existingAffliction.SetStrength(strength);
if (existingAffliction == stunAffliction)
@@ -2088,6 +2094,11 @@ namespace Barotrauma
CalculateVitality();
DisplayedVitality = Vitality;
if (newAdded)
{
MedicalClinic.OnAfflictionCountChanged(Character);
}
}
partial void UpdateSkinTint()
@@ -554,7 +554,7 @@ namespace Barotrauma
float damage = 0;
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);
}
@@ -563,11 +563,11 @@ namespace Barotrauma
float bleedingDamageMultiplier = 1;
foreach (DamageModifier damageModifier in result.AppliedDamageModifiers)
{
if (damageModifier.MatchesAfflictionType("damage"))
if (damageModifier.MatchesAfflictionType(AfflictionPrefab.DamageType))
{
damageMultiplier *= damageModifier.DamageMultiplier;
}
else if (damageModifier.MatchesAfflictionType("bleeding"))
else if (damageModifier.MatchesAfflictionType(AfflictionPrefab.BleedingType))
{
bleedingDamageMultiplier *= damageModifier.DamageMultiplier;
}
@@ -599,7 +599,7 @@ namespace Barotrauma
{
if (damageModifier.DamageMultiplier > 0 && !string.IsNullOrWhiteSpace(damageModifier.DamageParticle))
{
overrideParticle = GameMain.ParticleManager?.FindPrefab(damageModifier.DamageParticle);
overrideParticle = ParticleManager.FindPrefab(damageModifier.DamageParticle);
break;
}
}
@@ -646,7 +646,7 @@ namespace Barotrauma
dripParticleTimer += wetTimer * deltaTime * Mass * (wetTimer > 0.9f ? 50.0f : 5.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(
"waterdrop",
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)
{
float brightness = Math.Max(1.0f - burnOverLayStrength, 0.2f);
var spriteParams = Params.GetSprite();
if (spriteParams == null) { return; }
float burn = spriteParams.IgnoreTint ? 0 : burnOverLayStrength;
float brightness = Math.Max(1.0f - burn, 0.2f);
Color clr = spriteParams.Color;
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 ||
OtherWearables.Any(w => w.HideLimb) ||
@@ -1245,6 +1245,12 @@ namespace Barotrauma
};
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["aCutoff"] = 2f / 255f;
paramsToPass.Params["xTexture"] = wearable.Sprite.Texture;
@@ -425,6 +425,10 @@ namespace Barotrauma
{
CheatsEnabled = true;
SteamAchievementManager.CheatsEnabled = true;
if (GameMain.GameSession?.Campaign is CampaignMode campaign)
{
campaign.CheatsEnabled = true;
}
NewMessage("Enabled cheat commands.", Color.Red);
#if USE_STEAM
NewMessage("Steam achievements have been disabled during this play session.", Color.Red);
@@ -639,7 +643,7 @@ namespace Barotrauma
{
if (Submarine.MainSub == null) { return; }
MapEntity.SelectedList.Clear();
MapEntity.mapEntityList.ForEach(me => me.IsHighlighted = false);
MapEntity.ClearHighlightedEntities();
WikiImage.Create(Submarine.MainSub);
}));
@@ -752,7 +756,7 @@ namespace Barotrauma
state = !GameMain.LightManager.LosEnabled;
}
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);
@@ -763,7 +767,7 @@ namespace Barotrauma
state = !GameMain.LightManager.LightingEnabled;
}
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);
@@ -781,7 +785,7 @@ namespace Barotrauma
hull.OriginalAmbientLight = null;
}
}
NewMessage("Restored all hull ambient lights", Color.White);
NewMessage("Restored all hull ambient lights", Color.Yellow);
return;
}
@@ -803,11 +807,11 @@ namespace Barotrauma
if (add)
{
NewMessage($"Set ambient light color to {color}.", Color.White);
NewMessage($"Set ambient light color to {color}.", Color.Yellow);
}
else
{
NewMessage($"Increased ambient light by {color}.", Color.White);
NewMessage($"Increased ambient light by {color}.", Color.Yellow);
}
});
AssignRelayToServer("ambientlight", false);
@@ -1124,7 +1128,18 @@ namespace Barotrauma
state = !GameMain.DebugDraw;
}
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);
@@ -1146,7 +1161,7 @@ namespace Barotrauma
GameMain.LightManager.LosEnabled = true;
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);
@@ -1157,7 +1172,7 @@ namespace Barotrauma
state = !TextManager.DebugDraw;
}
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);
@@ -1170,19 +1185,19 @@ namespace Barotrauma
var config = GameSettings.CurrentConfig;
config.Audio.DisableVoiceChatFilters = state;
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);
commands.Add(new Command("fpscounter", "fpscounter: Toggle the FPS counter.", (string[] args) =>
{
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) =>
{
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) =>
@@ -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) =>
{
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) =>
{
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));
AssignOnExecute("togglehud|hud", (string[] args) =>
{
GUI.DisableHUD = !GUI.DisableHUD;
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);
AssignOnExecute("toggleupperhud", (string[] args) =>
{
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);
AssignOnExecute("toggleitemhighlights", (string[] args) =>
{
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);
AssignOnExecute("togglecharacternames", (string[] args) =>
{
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);
AssignOnExecute("followsub", (string[] args) =>
{
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);
AssignOnExecute("toggleaitargets|aitargets", (string[] args) =>
{
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);
@@ -1264,10 +1279,36 @@ namespace Barotrauma
GameMain.LightManager.LosEnabled = true;
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);
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("fire|editfire", false);
@@ -2833,7 +2874,7 @@ namespace Barotrauma
NewMessage("Valid ranks are:", Color.White);
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) =>
{
@@ -2991,7 +3032,7 @@ namespace Barotrauma
ThrowError($"Could not find the location type \"{args[0]}\".");
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
{
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;
Debug.Assert(conversationList != null);
DisableButtons(conversationList.Content.GetAllChildren<GUIButton>(), selectedButton: null);
// gray out the last text block
if (conversationList.Content.Children.LastOrDefault() is GUILayoutGroup lastElement)
{
@@ -269,14 +270,7 @@ namespace Barotrauma
if (actionInstance != null)
{
actionInstance.selectedOption = selectedOption;
foreach (GUIButton otherButton in optionButtons)
{
otherButton.CanBeFocused = false;
if (otherButton != btn)
{
otherButton.TextBlock.OverrideTextColor(Color.DarkGray * 0.8f);
}
}
DisableButtons(optionButtons, btn);
btn.ExternalHighlight = true;
return true;
}
@@ -286,14 +280,7 @@ namespace Barotrauma
SendResponse(actionId.Value, selectedOption);
btn.CanBeFocused = false;
btn.ExternalHighlight = true;
foreach (GUIButton otherButton in optionButtons)
{
otherButton.CanBeFocused = false;
if (otherButton != btn)
{
otherButton.TextBlock.OverrideTextColor(Color.DarkGray * 0.8f);
}
}
DisableButtons(optionButtons, btn);
return true;
}
//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)
{
return dialogTypes switch
@@ -383,6 +382,30 @@ namespace Barotrauma
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)
{
IWriteMessage outmsg = new WriteOnlyMessage();
@@ -608,58 +608,90 @@ namespace Barotrauma
}
break;
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());
}
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 =>
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++)
{
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:
Identifier missionIdentifier = msg.ReadIdentifier();
int locationIndex = msg.ReadInt32();
int destinationIndex = msg.ReadInt32();
string missionName = msg.ReadString();
MissionPrefab? prefab = MissionPrefab.Prefabs.Find(mp => mp.Identifier == missionIdentifier);
if (prefab != null)
{
new GUIMessageBox(string.Empty, TextManager.GetWithVariable("missionunlocked", "[missionname]", missionName),
new GUIMessageBox(string.Empty, TextManager.GetWithVariable("missionunlocked", "[missionname]", missionName),
Array.Empty<LocalizedString>(), type: GUIMessageBox.Type.InGame, icon: prefab.Icon, relativeSize: new Vector2(0.3f, 0.15f), minSize: new Point(512, 128))
{
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;
case NetworkEventType.UNLOCKPATH:
@@ -8,7 +8,7 @@ namespace Barotrauma
public override int State
{
get { return base.State; }
protected set
set
{
if (state != value)
{
@@ -45,7 +45,10 @@ namespace Barotrauma
{
requireRescue.Add(character);
#if CLIENT
GameMain.GameSession.CrewManager.AddCharacterToCrewList(character);
if (allowOrderingRescuees)
{
GameMain.GameSession.CrewManager.AddCharacterToCrewList(character);
}
#endif
}
ushort itemCount = msg.ReadUInt16();
@@ -10,8 +10,7 @@ namespace Barotrauma
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;
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
{
public override bool DisplayAsCompleted => false;
public override bool DisplayAsCompleted => State >= Prefab.MaxProgressState;
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 rotation = msg.ReadSingle();
@@ -54,7 +54,7 @@ namespace Barotrauma
CalculateMissionClusterPositions();
for(int i = 0; i < resourceClusters.Count; i++)
for(int i = 0; i < resourceAmounts.Count; i++)
{
var identifier = msg.ReadIdentifier();
var count = msg.ReadByte();
@@ -32,44 +32,73 @@ namespace Barotrauma
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)));
return RichString.Rich(TextManager.GetWithVariable("missionreward", "[reward]", "‖color:gui.orange‖"+rewardText+"‖end‖"));
int baseReward = GetReward(sub);
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>();
foreach (var reputationReward in ReputationRewards)
{
LocalizedString name = "";
if (reputationReward.Key == "location")
FactionPrefab targetFactionPrefab;
if (reputationReward.Key == "location" )
{
name = $"‖color:gui.orange‖{currLocation.Name}‖end‖";
targetFactionPrefab = OriginLocation.Faction?.Prefab;
}
else
{
var faction = FactionPrefab.Prefabs.Find(f => f.Identifier == reputationReward.Key);
if (faction != null)
{
name = $"‖color:{XMLExtensions.ColorToString(faction.IconColor)}‖{faction.Name}‖end‖";
}
else
{
name = TextManager.Get(reputationReward.Key);
}
FactionPrefab.Prefabs.TryGet(reputationReward.Key, out targetFactionPrefab);
}
if (targetFactionPrefab == null)
{
return string.Empty;
}
float normalizedValue = MathUtils.InverseLerp(-100.0f, 100.0f, reputationReward.Value);
string formattedValue = ((int)reputationReward.Value).ToString("+#;-#;0"); //force plus sign for positive numbers
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(
"reputationformat",
("[reputationname]", name),
("[reputationvalue]", $"‖color:{XMLExtensions.ColorToString(Reputation.GetReputationColor(normalizedValue))}‖{formattedValue}‖end‖" ));
("[reputationvalue]", $"‖color:{XMLExtensions.ToStringHex(Reputation.GetReputationColor(normalizedValue))}‖{formattedValue}‖end‖" ));
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)
@@ -107,6 +136,11 @@ namespace Barotrauma
};
}
public Identifier GetOverrideMusicType()
{
return Prefab.GetOverrideMusicType(State);
}
public virtual void ClientRead(IReadMessage msg)
{
State = msg.ReadInt16();
@@ -8,6 +8,7 @@ namespace Barotrauma
{
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)
{
IconColor = mission.Prefab.IconColor,
@@ -1,11 +1,16 @@
using Microsoft.Xna.Framework;
using System;
using System.Xml.Linq;
using System.Collections.Generic;
using System.Collections.Immutable;
namespace Barotrauma
{
partial class MissionPrefab : PrefabWithUintIdentifier
{
private ImmutableArray<Sprite> portraits = new ImmutableArray<Sprite>();
public bool HasPortraits => portraits.Length > 0;
public Sprite Icon
{
get;
@@ -49,24 +54,57 @@ namespace Barotrauma
private Sprite hudIcon;
private Color? hudIconColor;
private ImmutableDictionary<int, Identifier> overrideMusicOnState;
partial void InitProjSpecific(ContentXElement element)
{
DisplayTargetHudIcons = element.GetAttributeBool("displaytargethudicons", false);
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())
{
string name = subElement.Name.ToString();
if (name.Equals("icon", StringComparison.OrdinalIgnoreCase))
switch (subElement.Name.ToString().ToLowerInvariant())
{
Icon = new Sprite(subElement);
IconColor = subElement.GetAttributeColor("color", Color.White);
}
else if (name.Equals("hudicon", StringComparison.OrdinalIgnoreCase))
{
hudIcon = new Sprite(subElement);
hudIconColor = subElement.GetAttributeColor("color");
case "icon":
Icon = new Sprite(subElement);
IconColor = subElement.GetAttributeColor("color", Color.White);
break;
case "hudicon":
hudIcon = new Sprite(subElement);
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()
@@ -13,11 +13,12 @@ namespace Barotrauma
byte monsterCount = msg.ReadByte();
for (int i = 0; i < monsterCount; i++)
{
monsters.Add(Character.ReadSpawnData(msg));
}
if (monsters.Contains(null))
{
throw new System.Exception("Error in MonsterMission.ClientReadInitial: monster list contains null (mission: " + Prefab.Identifier + ")");
var monster = Character.ReadSpawnData(msg);
if (monster == null)
{
throw new System.Exception($"Error in MonsterMission.ClientReadInitial: failed to create a monster (mission: {Prefab.Identifier}, index: {i})");
}
monsters.Add(monster);
}
if (monsters.Count != monsterCount)
{
@@ -11,38 +11,59 @@ namespace Barotrauma
public override void ClientReadInitial(IReadMessage msg)
{
base.ClientReadInitial(msg);
bool usedExistingItem = msg.ReadBoolean();
if (usedExistingItem)
foreach (var target in targets)
{
ushort id = msg.ReadUInt16();
item = Entity.FindEntityByID(id) as Item;
if (item == null)
bool targetFound = msg.ReadBoolean();
if (!targetFound) { continue; }
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);
if (item == null)
var state = (Target.RetrievalState)msg.ReadByte();
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.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using Barotrauma.Extensions;
namespace Barotrauma
{
@@ -199,8 +199,8 @@ namespace Barotrauma
if (GameMain.GraphicsWidth <= maxResolution.X && GameMain.GraphicsHeight <= maxResolution.Y)
{
size = new Point(
subElement.GetAttributeInt("width", 0),
subElement.GetAttributeInt("height", 0));
ParseSize(subElement, "width"),
ParseSize(subElement, "height"));
break;
}
}
@@ -3,7 +3,6 @@ using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using PlayerBalanceElement = Barotrauma.CampaignUI.PlayerBalanceElement;
@@ -29,6 +28,8 @@ namespace Barotrauma
private Point resolutionWhenCreated;
private bool needsHireableRefresh;
private enum SortingMethod
{
AlphabeticalAsc,
@@ -50,6 +51,8 @@ namespace Barotrauma
campaignUI.Campaign.Map.OnLocationChanged.RegisterOverwriteExisting(
"CrewManagement.UpdateLocationView".ToIdentifier(),
(locationChangeInfo) => UpdateLocationView(locationChangeInfo.NewLocation, true, locationChangeInfo.PrevLocation));
Reputation.OnAnyReputationValueChanged.RegisterOverwriteExisting(
"CrewManagement.UpdateLocationView".ToIdentifier(), _ => needsHireableRefresh = true);
}
public void RefreshPermissions()
@@ -68,7 +71,13 @@ namespace Barotrauma
{
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)
{
list.Content.RectTransform.SortChildren((x, y) =>
CompareReputationRequirement(x.GUIComponent, y.GUIComponent) ??
((InfoSkill)x.GUIComponent.UserData).CharacterInfo.Name.CompareTo(((InfoSkill)y.GUIComponent.UserData).CharacterInfo.Name));
}
else if (sortingMethod == SortingMethod.JobAsc)
{
SortCharacters(list, SortingMethod.AlphabeticalAsc);
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)
{
SortCharacters(list, SortingMethod.AlphabeticalAsc);
list.Content.RectTransform.SortChildren((x, y) =>
CompareReputationRequirement(x.GUIComponent, y.GUIComponent) ??
((InfoSkill)x.GUIComponent.UserData).CharacterInfo.Salary.CompareTo(((InfoSkill)y.GUIComponent.UserData).CharacterInfo.Salary));
if (sortingMethod == SortingMethod.PriceDesc) { list.Content.RectTransform.ReverseChildren(); }
}
@@ -313,9 +325,26 @@ namespace Barotrauma
{
SortCharacters(list, SortingMethod.AlphabeticalAsc);
list.Content.RectTransform.SortChildren((x, y) =>
CompareReputationRequirement(x.GUIComponent, y.GUIComponent) ??
((InfoSkill)x.GUIComponent.UserData).SkillLevel.CompareTo(((InfoSkill)y.GUIComponent.UserData).SkillLevel));
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
@@ -367,12 +396,25 @@ namespace Barotrauma
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),
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
};
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;
if (characterInfo.Job != null && skill != null)
{
@@ -410,7 +452,7 @@ namespace Barotrauma
{
ClickSound = GUISoundType.Cart,
UserData = characterInfo,
Enabled = HasPermission,
Enabled = CanHire(characterInfo),
OnClicked = (b, o) => AddPendingHire(o as CharacterInfo)
};
hireButton.OnAddedToGUIUpdateList += (GUIComponent btn) =>
@@ -426,10 +468,9 @@ namespace Barotrauma
else if (!btn.Enabled)
{
btn.ToolTip = string.Empty;
btn.Enabled = HasPermission;
btn.Enabled = CanHire(characterInfo);
}
};
}
else if (listBox == pendingList)
{
@@ -437,7 +478,7 @@ namespace Barotrauma
{
ClickSound = GUISoundType.Cart,
UserData = characterInfo,
Enabled = HasPermission,
Enabled = CanHire(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);
new GUIButton(new RectTransform(size, frame.RectTransform) { RelativeOffset = new Vector2(0.025f) }, style: null)
{
Enabled = HasPermission,
ToolTip = TextManager.GetWithVariable("campaigncrew.givenicknametooltip", "[mouseprimary]", TextManager.Get($"input.{(PlayerInput.MouseButtonsSwapped() ? "rightmouse" : "leftmouse")}")),
Enabled = CanHire(characterInfo),
ToolTip = TextManager.GetWithVariable("campaigncrew.givenicknametooltip", "[mouseprimary]", PlayerInput.PrimaryMouseLabel),
UserData = characterInfo,
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)
@@ -488,13 +547,13 @@ namespace Barotrauma
Point absoluteOffset = new Point(
pivot == Pivot.TopLeft ? listBox.Parent.Parent.Rect.Right + 5 : listBox.Parent.Parent.Rect.Left - 5,
characterFrame.Rect.Top);
int frameSize = (int)(GUI.Scale * 300);
if (GameMain.GraphicsHeight - (absoluteOffset.Y + frameSize) < 0)
Point frameSize = new Point(GUI.IntScale(300), GUI.IntScale(350));
if (GameMain.GraphicsHeight - (absoluteOffset.Y + frameSize.Y) < 0)
{
pivot = listBox == hireableList ? Pivot.BottomLeft : Pivot.BottomRight;
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
}, style: "InnerFrame")
@@ -503,7 +562,8 @@ namespace Barotrauma
};
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
@@ -545,9 +605,23 @@ namespace Barotrauma
blockHeight = 1.0f / characterSkills.Count();
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);
}
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)
@@ -636,7 +710,7 @@ namespace Barotrauma
List<CharacterInfo> nonDuplicateHires = new List<CharacterInfo>();
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);
}
@@ -791,6 +865,16 @@ namespace Barotrauma
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);
if (highlightedFrame != null && highlightedInfo != null)
{
@@ -106,6 +106,11 @@ namespace Barotrauma
public static float VerticalAspectRatio => GameMain.GraphicsHeight / (float)GameMain.GraphicsWidth;
public static float RelativeHorizontalAspectRatio => HorizontalAspectRatio / (ReferenceResolution.X / ReferenceResolution.Y);
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 int UIWidth
@@ -140,13 +145,20 @@ namespace Barotrauma
public static Texture2D WhiteTexture => solidWhiteTexture;
private static GUICursor MouseCursorSprites => GUIStyle.CursorSprite;
private static bool debugDrawSounds, debugDrawEvents, debugDrawMetadata;
private static int debugDrawMetadataOffset;
private static readonly string[] ignoredMetadataInfo = { string.Empty, string.Empty, string.Empty, string.Empty };
private static bool debugDrawSounds, debugDrawEvents;
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;
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 SettingsMenuContainer { get; private set; }
@@ -195,8 +207,9 @@ namespace Barotrauma
SettingsMenuOpen ||
DebugConsole.IsOpen ||
GameSession.IsTabMenuOpen ||
(GameMain.GameSession?.GameMode?.Paused ?? false) ||
CharacterHUD.IsCampaignInterfaceOpen;
GameMain.GameSession?.GameMode is { Paused: true } ||
CharacterHUD.IsCampaignInterfaceOpen ||
GameMain.GameSession?.Campaign is { SlideshowPlayer: { Finished: false, Visible: true } };
}
}
@@ -533,18 +546,17 @@ namespace Barotrauma
if (GameMain.GameSession?.GameMode is CampaignMode campaignMode)
{
// TODO: TEST THIS
if (debugDrawMetadata)
if (debugDrawMetaData.Enabled)
{
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+2 to {(string.IsNullOrWhiteSpace(ignoredMetadataInfo[1]) ? "hide" : "show")} faction reputations, \n" +
$"Ctrl+3 to {(string.IsNullOrWhiteSpace(ignoredMetadataInfo[2]) ? "hide" : "show")} upgrade levels, \n" +
$"Ctrl+4 to {(string.IsNullOrWhiteSpace(ignoredMetadataInfo[3]) ? "hide" : "show")} upgrade prices";
$"Ctrl+1 to {(debugDrawMetaData.FactionMetadata ? "hide" : "show")} faction reputations, \n" +
$"Ctrl+2 to {(debugDrawMetaData.UpgradeLevels ? "hide" : "show")} upgrade levels, \n" +
$"Ctrl+3 to {(debugDrawMetaData.UpgradePrices ? "hide" : "show")} upgrade prices";
Vector2 textSize = GUIStyle.SmallFont.MeasureString(text);
Vector2 pos = new Vector2(GameMain.GraphicsWidth - (textSize.X + 10), 300);
DrawString(spriteBatch, pos, text, Color.White, Color.Black * 0.5f, 0, GUIStyle.SmallFont);
pos.Y += textSize.Y + 8;
campaignMode.CampaignMetadata?.DebugDraw(spriteBatch, pos, debugDrawMetadataOffset, ignoredMetadataInfo);
campaignMode.CampaignMetadata?.DebugDraw(spriteBatch, pos, campaignMode, debugDrawMetaData);
}
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);
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);
Rectangle area = drawArea ?? new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight);
float scale = Math.Max(
(float)GameMain.GraphicsWidth / backgroundSprite.SourceRect.Width,
(float)GameMain.GraphicsHeight / backgroundSprite.SourceRect.Height) * 1.1f;
float paddingX = backgroundSprite.SourceRect.Width * scale - GameMain.GraphicsWidth;
float paddingY = backgroundSprite.SourceRect.Height * scale - GameMain.GraphicsHeight;
(float)area.Width / backgroundSprite.SourceRect.Width,
(float)area.Height / backgroundSprite.SourceRect.Height) * 1.1f;
float paddingX = backgroundSprite.SourceRect.Width * scale - area.Width;
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);
pos = new Vector2(pos.X * paddingX, pos.Y * paddingY);
spriteBatch.Draw(backgroundSprite.Texture,
new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight) / 2 + pos,
null, Color.White, 0.0f, backgroundSprite.size / 2,
scale, SpriteEffects.None, 0.0f);
spriteBatch.End();
area.Center.ToVector2() + pos,
null, color, 0.0f, backgroundSprite.size / 2,
scale, spriteEffects, 0.0f);
}
#region Update list
@@ -1206,48 +1205,37 @@ namespace Barotrauma
}
if (PlayerInput.IsCtrlDown() && PlayerInput.KeyHit(Keys.M))
{
debugDrawMetadata = !debugDrawMetadata;
debugDrawMetaData.Enabled = !debugDrawMetaData.Enabled;
}
if (debugDrawMetadata)
if (debugDrawMetaData.Enabled)
{
if (PlayerInput.KeyHit(Keys.Up))
{
debugDrawMetadataOffset--;
debugDrawMetaData.Offset--;
}
if (PlayerInput.KeyHit(Keys.Down))
{
debugDrawMetadataOffset++;
debugDrawMetaData.Offset++;
}
if (PlayerInput.IsCtrlDown())
{
if (PlayerInput.KeyHit(Keys.D1))
{
ignoredMetadataInfo[0] = ignoredMetadataInfo[0] == string.Empty ? "reputation.location" : string.Empty;
debugDrawMetadataOffset = 0;
debugDrawMetaData.FactionMetadata = !debugDrawMetaData.FactionMetadata;
debugDrawMetaData.Offset = 0;
}
if (PlayerInput.KeyHit(Keys.D2))
{
ignoredMetadataInfo[1] = ignoredMetadataInfo[1] == string.Empty ? "reputation.faction" : string.Empty;
debugDrawMetadataOffset = 0;
debugDrawMetaData.UpgradeLevels = !debugDrawMetaData.UpgradeLevels;
debugDrawMetaData.Offset = 0;
}
if (PlayerInput.KeyHit(Keys.D3))
{
ignoredMetadataInfo[2] = ignoredMetadataInfo[2] == string.Empty ? "upgrade." : string.Empty;
debugDrawMetadataOffset = 0;
}
if (PlayerInput.KeyHit(Keys.D4))
{
ignoredMetadataInfo[3] = ignoredMetadataInfo[3] == string.Empty ? "upgradeprice." : string.Empty;
debugDrawMetadataOffset = 0;
debugDrawMetaData.UpgradePrices = !debugDrawMetaData.UpgradePrices;
debugDrawMetaData.Offset = 0;
}
}
}
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)
{
if (messages.Any(msg => msg.Text == message)) { return; }
messages.Add(new GUIMessage(message, color, lifeTime ?? MathHelper.Clamp(message.Length / 5.0f, 3.0f, 10.0f), font ?? GUIStyle.LargeFont));
var guiMessage = 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); }
}
@@ -2610,34 +2602,37 @@ namespace Barotrauma
var newMessage = new GUIMessage(message, color, pos, velocity, lifeTime, Alignment.Center, GUIStyle.Font, sub: sub);
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()
@@ -8,9 +8,7 @@ namespace Barotrauma
{
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;
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
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;
@@ -41,25 +39,27 @@ namespace Barotrauma
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
foreach (var child in _instance.Children)
if (!_instance.childrenWeakRef.Any(c => c.TryGetTarget(out var existingChild) && existingChild == child))
{
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();
//remove dead children
for (int i = _instance.childrenWeakRef.Count - 2; i >= 0; i--)
}
//get rid of strong references
_instance.children.Clear();
//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.
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
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.children.Clear();
}
@@ -244,18 +244,16 @@ namespace Barotrauma
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 ??= "";
if (selectMultiple)
{
var frame = new GUIFrame(new RectTransform(new Point(button.Rect.Width, button.Rect.Height), listBox.Content.RectTransform)
{ IsFixedSize = false }, style: "ListBoxElement")
var frame = new GUIFrame(new RectTransform(new Point(button.Rect.Width, button.Rect.Height), listBox.Content.RectTransform) { IsFixedSize = false }, style: "ListBoxElement", color: color)
{
UserData = userData,
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)
{
UserData = userData,
@@ -275,7 +273,7 @@ namespace Barotrauma
foreach (GUIComponent child in ListBox.Content.Children)
{
var tickBox = child.GetChild<GUITickBox>();
if (tickBox.Selected)
if (tickBox is { Selected: true })
{
selectedDataMultiple.Add(child.UserData);
selectedIndexMultiple.Add(i);
@@ -289,11 +287,11 @@ namespace Barotrauma
return true;
}
};
return frame;
}
else
{
new GUITextBlock(new RectTransform(new Point(button.Rect.Width, button.Rect.Height), listBox.Content.RectTransform)
{ IsFixedSize = false }, text, style: "ListBoxElement")
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)
{
UserData = userData,
ToolTip = toolTip
@@ -323,7 +321,7 @@ namespace Barotrauma
}
else
{
if (!(component is GUITextBlock textBlock))
if (component is not GUITextBlock textBlock)
{
textBlock = component.GetChild<GUITextBlock>();
if (textBlock is null && !AllowNonText) { return false; }
@@ -1059,6 +1059,7 @@ namespace Barotrauma
GUIComponent child = Content.GetChild(childIndex);
if (child is null) { return; }
if (!child.Enabled) { return; }
bool wasSelected = true;
if (OnSelected != null)
@@ -268,7 +268,7 @@ namespace Barotrauma
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);
Header.RectTransform.MinSize = new Point(0, Header.Rect.Height);
@@ -313,7 +313,9 @@ namespace Barotrauma
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();
}
@@ -1,14 +1,12 @@
using Microsoft.Xna.Framework;
using Barotrauma.Extensions;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using Barotrauma.Extensions;
namespace Barotrauma
{
@@ -20,6 +18,26 @@ namespace Barotrauma
{
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
@@ -166,7 +184,8 @@ namespace Barotrauma
Point maxResolution = subElement.GetAttributePoint("maxresolution", new Point(int.MaxValue, int.MaxValue));
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);
@@ -438,7 +438,7 @@ namespace Barotrauma
}
else
{
if ((PlayerInput.LeftButtonClicked() || PlayerInput.RightButtonClicked()) && selected)
if ((PlayerInput.PrimaryMouseButtonClicked() || PlayerInput.SecondaryMouseButtonClicked()) && selected)
{
if (!mouseHeldInside) { Deselect(); }
mouseHeldInside = false;
@@ -143,14 +143,13 @@ namespace Barotrauma
}
int healthBarHeight = (int)(50f * GUI.Scale);
HealthBarArea = new Rectangle(BottomRightInfoArea.Right - healthBarWidth + (int)Math.Floor(1 / GUI.Scale), BottomRightInfoArea.Y - healthBarHeight + GUI.IntScale(10), healthBarWidth, healthBarHeight);
HealthBarAfflictionArea = new Rectangle(HealthBarArea.X, HealthBarArea.Y - Padding - afflictionAreaHeight, HealthBarArea.Width, afflictionAreaHeight);
HealthBarAfflictionArea = new Rectangle(HealthBarArea.X, HealthBarArea.Y - Padding - afflictionAreaHeight, HealthBarArea.Width, afflictionAreaHeight);
int messageAreaWidth = GameMain.GraphicsWidth / 3;
MessageAreaTop = new Rectangle((GameMain.GraphicsWidth - messageAreaWidth) / 2, ButtonAreaTop.Bottom + ButtonAreaTop.Height, messageAreaWidth, ButtonAreaTop.Height);
bool isFourByThree = GUI.IsFourByThree();
int chatBoxWidth = !isFourByThree ? (int)(475 * GUI.Scale) : (int)(375 * GUI.Scale);
int chatBoxWidth = (int)(475 * GUI.Scale * GUI.AspectRatioAdjustment);
int chatBoxHeight = (int)Math.Max(GameMain.GraphicsHeight * 0.25f, 150);
ChatBoxArea = new Rectangle(Padding, GameMain.GraphicsHeight - Padding - chatBoxHeight, chatBoxWidth, chatBoxHeight);
@@ -160,8 +159,9 @@ namespace Barotrauma
int crewAreaY = ButtonAreaTop.Bottom + Padding;
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);
int healthWindowWidth = (int)(GameMain.GraphicsWidth * 0.5f);
@@ -187,19 +187,26 @@ namespace Barotrauma
public static void Draw(SpriteBatch spriteBatch)
{
DrawRectangle(ButtonAreaTop, Color.White * 0.5f);
DrawRectangle(TutorialObjectiveListArea, GUIStyle.Blue * 0.5f);
DrawRectangle(MessageAreaTop, GUIStyle.Orange * 0.5f);
DrawRectangle(CrewArea, Color.Blue * 0.5f);
DrawRectangle(ChatBoxArea, Color.Cyan * 0.5f);
DrawRectangle(HealthBarArea, Color.Red * 0.5f);
DrawRectangle(HealthBarAfflictionArea, Color.Red * 0.5f);
DrawRectangle(InventoryAreaLower, Color.Yellow * 0.5f);
DrawRectangle(HealthWindowAreaLeft, Color.Red * 0.5f);
DrawRectangle(BottomRightInfoArea, Color.Green * 0.5f);
DrawRectangle(ItemHUDArea, Color.Magenta * 0.3f);
DrawRectangle(nameof(ButtonAreaTop), ButtonAreaTop, Color.White * 0.5f);
DrawRectangle(nameof(TutorialObjectiveListArea), TutorialObjectiveListArea, GUIStyle.Blue * 0.5f);
DrawRectangle(nameof(MessageAreaTop), MessageAreaTop, GUIStyle.Orange * 0.5f);
DrawRectangle(nameof(CrewArea), CrewArea, Color.Blue * 0.5f);
DrawRectangle(nameof(ChatBoxArea), ChatBoxArea, Color.Cyan * 0.5f);
DrawRectangle(nameof(HealthBarArea), HealthBarArea, Color.Red * 0.5f);
DrawRectangle(nameof(HealthBarAfflictionArea), HealthBarAfflictionArea, Color.Red * 0.5f);
DrawRectangle(nameof(InventoryAreaLower), InventoryAreaLower, Color.Yellow * 0.5f);
DrawRectangle(nameof(HealthWindowAreaLeft), HealthWindowAreaLeft, Color.Red * 0.5f);
DrawRectangle(nameof(BottomRightInfoArea), BottomRightInfoArea, Color.Green * 0.5f);
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
{
private readonly Texture2D defaultBackgroundTexture, overlay;
private readonly Sprite defaultBackgroundTexture, overlay;
private readonly SpriteSheet decorativeGraph, decorativeMap;
private Texture2D currentBackgroundTexture;
private Sprite currentBackgroundTexture;
private readonly Sprite noiseSprite;
private string randText = "";
@@ -24,6 +24,8 @@ namespace Barotrauma
private Video currSplashScreen;
private DateTime videoStartTime;
private bool mirrorBackground;
public struct PendingSplashScreen
{
public string Filename;
@@ -112,12 +114,12 @@ namespace Barotrauma
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));
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);
DrawLoadingText = true;
SetSelectedTip(TextManager.Get("LoadingScreenTip"));
@@ -138,35 +140,24 @@ namespace Barotrauma
DisableSplashScreen();
}
}
var titleStyle = GUIStyle.GetComponentStyle("TitleText");
Sprite titleSprite = null;
if (!WaitForLanguageSelection && titleStyle != null && titleStyle.Sprites.ContainsKey(GUIComponent.ComponentState.None))
{
titleSprite = titleStyle.Sprites[GUIComponent.ComponentState.None].First()?.Sprite;
}
drawn = true;
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);
float scale = (GameMain.GraphicsWidth / (float)currentBackgroundTexture.Width) * 1.2f;
float paddingX = currentBackgroundTexture.Width * scale - GameMain.GraphicsWidth;
float paddingY = currentBackgroundTexture.Height * scale - GameMain.GraphicsHeight;
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);
GUI.DrawBackgroundSprite(spriteBatch, currentBackgroundTexture, Color.White, drawArea,
spriteEffects: mirrorBackground ? SpriteEffects.FlipHorizontally : SpriteEffects.None);
overlay.Draw(spriteBatch, Vector2.Zero, scale: overlayScale);
double noiseT = Timing.TotalTime * 0.02f;
float noiseStrength = (float)PerlinNoise.CalculatePerlin(noiseT, noiseT, 0);
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),
@@ -174,10 +165,7 @@ namespace Barotrauma
color: Color.White * noiseStrength * 0.1f,
textureScale: Vector2.One * noiseScale);
titleSprite?.Draw(spriteBatch, new Vector2(GameMain.GraphicsWidth * 0.05f, GameMain.GraphicsHeight * 0.125f),
Color.White, origin: new Vector2(0.0f, titleSprite.SourceRect.Height / 2.0f),
scale: GameMain.GraphicsHeight / 2000.0f);
Vector2 textPos = new Vector2((int)(GameMain.GraphicsWidth * 0.05f), (int)(GameMain.GraphicsHeight * 0.75f));
if (WaitForLanguageSelection)
{
DrawLanguageSelectionPrompt(spriteBatch, graphics);
@@ -215,16 +203,18 @@ namespace Barotrauma
#endif
}
}
if (GUIStyle.LargeFont.HasValue)
{
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);
textPos.Y += GUIStyle.LargeFont.MeasureString(loadText.ToUpper()).Y * 1.2f;
}
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');
float lineHeight = GUIStyle.Font.MeasureString(selectedTip).Y;
@@ -234,7 +224,8 @@ namespace Barotrauma
for (int i = 0; i < lines.Length; 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);
rtdOffset += lines[i].Length;
}
@@ -244,7 +235,8 @@ namespace Barotrauma
for (int i = 0; i < lines.Length; 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);
float noiseVal = (float)PerlinNoise.CalculatePerlin(Timing.TotalTime * 0.25f, Timing.TotalTime * 0.5f, 0);
decorativeGraph.Draw(spriteBatch, (int)(decorativeGraph.FrameCount * noiseVal),
new Vector2(GameMain.GraphicsWidth * 0.001f, GameMain.GraphicsHeight * 0.24f),
Color.White, Vector2.Zero, 0.0f, decorativeScale, SpriteEffects.FlipVertically);
if (!WaitForLanguageSelection)
{
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),
new Vector2(GameMain.GraphicsWidth * 0.99f, GameMain.GraphicsHeight * 0.66f),
Color.White, decorativeMap.FrameSize.ToVector2(), 0.0f, decorativeScale);
new Vector2(GameMain.GraphicsWidth * 0.99f, GameMain.GraphicsHeight * 0.01f),
Color.White, new Vector2(decorativeMap.FrameSize.X, 0), 0.0f, decorativeScale, SpriteEffects.FlipHorizontally | SpriteEffects.FlipVertically);
if (noiseVal < 0.2f)
{
@@ -285,8 +280,9 @@ namespace Barotrauma
if (GUIStyle.LargeFont.HasValue)
{
Vector2 textSize = GUIStyle.LargeFont.MeasureString(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));
}
@@ -312,8 +308,8 @@ namespace Barotrauma
languageSelectionCursor = new Sprite("Content/UI/cursor.png", Vector2.Zero);
}
Vector2 textPos = new Vector2(GameMain.GraphicsWidth / 2, GameMain.GraphicsHeight * 0.3f);
Vector2 textSpacing = new Vector2(0.0f, (GameMain.GraphicsHeight * 0.5f) / AvailableLanguages.Length);
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);
foreach (LanguageIdentifier language in AvailableLanguages)
{
string localizedLanguageName = TextManager.GetTranslatedLanguageName(language);
@@ -321,10 +317,10 @@ namespace Barotrauma
Vector2 textSize = font.MeasureString(localizedLanguageName);
bool hover =
Math.Abs(PlayerInput.MousePosition.X - textPos.X) < textSize.X / 2 &&
Math.Abs(PlayerInput.MousePosition.Y - textPos.Y) < textSpacing.Y / 2;
PlayerInput.MousePosition.X > textPos.X && PlayerInput.MousePosition.X < textPos.X + textSize.X &&
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);
if (hover && PlayerInput.PrimaryMouseButtonClicked())
{
@@ -431,7 +427,12 @@ namespace Barotrauma
drawn = false;
LoadState = null;
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)
{
@@ -143,33 +143,32 @@ namespace Barotrauma
{
public readonly MedicalClinic.NetAffliction Target;
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;
ElementsToDisable = elementsToDisable;
TargetElement = component;
}
}
private readonly struct PopupAfflictionList
{
public readonly MedicalClinic.NetCrewMember Target;
public readonly GUIListBox ListElement;
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;
TreatAllButton = treatAllButton;
Afflictions = new List<PopupAffliction>();
Afflictions = new HashSet<PopupAffliction>();
}
}
// private enum SortMode
// {
// Severity
// }
private readonly MedicalClinic medicalClinic;
private readonly GUIComponent container;
private Point prevResolution;
@@ -221,23 +220,22 @@ namespace Barotrauma
private void UpdatePopupAfflictions()
{
if (selectedCrewAfflictionList is { } afflictionList)
{
foreach (PopupAffliction popupAffliction in afflictionList.Afflictions)
{
ToggleElements(ElementState.Enabled, popupAffliction.ElementsToDisable);
if (medicalClinic.IsAfflictionPending(afflictionList.Target, popupAffliction.Target))
{
ToggleElements(ElementState.Disabled, popupAffliction.ElementsToDisable);
}
}
if (selectedCrewAfflictionList is not { } afflictionList) { return; }
afflictionList.TreatAllButton.Enabled = true;
if (afflictionList.Afflictions.All(a => medicalClinic.IsAfflictionPending(afflictionList.Target, a.Target)))
foreach (PopupAffliction popupAffliction in afflictionList.Afflictions)
{
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()
@@ -309,7 +307,7 @@ namespace Barotrauma
}
}
private void UpdateCrewPanel()
public void UpdateCrewPanel()
{
if (crewHealList is not { } healList) { return; }
@@ -502,7 +500,7 @@ namespace Barotrauma
return true;
}
};
crewHealList = new CrewHealList(crewList, parent, treatAllButton);
void OnReceived(MedicalClinic.CallbackOnlyRequest obj)
@@ -526,7 +524,7 @@ namespace Barotrauma
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
};
@@ -789,7 +787,7 @@ namespace Barotrauma
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;
selectedCrewAfflictionList = popupAfflictionList;
@@ -810,9 +808,9 @@ namespace Barotrauma
List<GUIComponent> allComponents = new List<GUIComponent>();
foreach (MedicalClinic.NetAffliction affliction in request.Afflictions)
{
ImmutableArray<GUIComponent> createdComponents = CreatePopupAffliction(afflictionList.Content, crewMember, affliction);
allComponents.AddRange(createdComponents);
popupAfflictionList.Afflictions.Add(new PopupAffliction(createdComponents, affliction));
CreatedPopupAfflictionElement createdComponents = CreatePopupAffliction(afflictionList.Content, crewMember, affliction);
allComponents.AddRange(createdComponents.AllCreatedElements);
popupAfflictionList.Afflictions.Add(new PopupAffliction(createdComponents.AllCreatedElements, createdComponents.MainElement, affliction));
}
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");
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 };
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,
DisabledColor = iconColor * 0.5f
@@ -856,7 +856,7 @@ namespace Barotrauma
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);
@@ -878,7 +878,7 @@ namespace Barotrauma
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);
@@ -923,7 +923,7 @@ namespace Barotrauma
return true;
};
return elementsToDisable;
return new CreatedPopupAfflictionElement(backgroundFrame, elementsToDisable);
}
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()
{
if (selectedCrewElement is { } popup)
{
popup.Parent?.RemoveChild(selectedCrewElement);
popup.RectTransform.Parent = null;
}
selectedCrewElement = null;
@@ -1096,5 +1138,14 @@ namespace Barotrauma
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);
}
public void SelectStore(Identifier identifier)
public void SelectStore(Character merchant)
{
Identifier storeIdentifier = merchant?.MerchantIdentifier ?? Identifier.Empty;
if (CurrentLocation?.Stores != null)
{
if (!identifier.IsEmpty && CurrentLocation.GetStore(identifier) is { } store)
if (!storeIdentifier.IsEmpty && CurrentLocation.GetStore(storeIdentifier) is { } store)
{
ActiveStore = store;
if (storeNameBlock != null)
@@ -223,12 +224,13 @@ namespace Barotrauma
}
storeNameBlock.SetRichText(storeName);
}
ActiveStore.SetMerchantFaction(merchant.Faction);
}
else
{
ActiveStore = null;
string errorId, msg;
if (identifier.IsEmpty)
if (storeIdentifier.IsEmpty)
{
errorId = "Store.SelectStore:IdentifierEmpty";
msg = $"Error selecting store at {CurrentLocation}: identifier is empty.";
@@ -236,7 +238,7 @@ namespace Barotrauma
else
{
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);
GameAnalyticsManager.AddErrorEventOnce(errorId, GameAnalyticsManager.ErrorSeverity.Error, msg);
@@ -249,17 +251,17 @@ namespace Barotrauma
if (campaignUI.Campaign.Map == null)
{
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)
{
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)
{
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())
{
@@ -406,11 +408,11 @@ namespace Barotrauma
TextScale = 1.1f,
TextGetter = () =>
{
if (CurrentLocation != null)
if (ActiveStore is not null)
{
Color textColor = GUIStyle.ColorReputationNeutral;
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)
{
textColor = IsBuying ? GUIStyle.ColorReputationLow : GUIStyle.ColorReputationHigh;
@@ -727,7 +729,7 @@ namespace Barotrauma
ChangeStoreTab(StoreTab.Buy);
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);
}
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;
private void RefreshStoreBuyList()
@@ -898,6 +922,7 @@ namespace Barotrauma
{
if (itemPrefab.CanBeBoughtFrom(ActiveStore, out PriceInfo priceInfo) && itemPrefab.CanCharacterBuy())
{
bool isDailySpecial = ActiveStore.DailySpecials.Contains(itemPrefab);
var itemFrame = isDailySpecial ?
storeDailySpecialsGroup.FindChild(c => c.UserData is PurchasedItem pi && pi.ItemPrefab == itemPrefab) :
@@ -922,7 +947,8 @@ namespace Barotrauma
SetOwnedText(itemFrame);
SetPriceGetters(itemFrame, true);
}
SetItemFrameStatus(itemFrame, hasPermissions && quantity > 0);
SetItemFrameStatus(itemFrame, hasPermissions && quantity > 0 && !GetTooLowReputation(priceInfo).HasValue);
existingItemFrames.Add(itemFrame);
}
}
@@ -1317,6 +1343,8 @@ namespace Barotrauma
{
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 ?
itemX.ItemPrefab.Name.CompareTo(itemY.ItemPrefab.Name) :
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)
{
int reputationCompare = CompareByReputationRestriction(itemX, itemY);
if (reputationCompare != 0) { return reputationCompare; }
int sortResult = ActiveStore.GetAdjustedItemSellPrice(itemX.ItemPrefab).CompareTo(
ActiveStore.GetAdjustedItemSellPrice(itemY.ItemPrefab));
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)
{
int reputationCompare = CompareByReputationRestriction(itemX, itemY);
if (reputationCompare != 0) { return reputationCompare; }
int sortResult = ActiveStore.GetAdjustedItemBuyPrice(itemX.ItemPrefab).CompareTo(
ActiveStore.GetAdjustedItemBuyPrice(itemY.ItemPrefab));
if (sortingMethod == SortingMethod.PriceDesc) { sortResult *= -1; }
@@ -1391,10 +1423,12 @@ namespace Barotrauma
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)
{
int reputationCompare = CompareByReputationRestriction(itemX, itemY);
if (reputationCompare != 0) { return reputationCompare; }
return itemX.ItemPrefab.Category.CompareTo(itemY.ItemPrefab.Category);
}
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)
{
if (ShouldBeOnTop(x) || ShouldBeOnBottom(y))
@@ -1753,7 +1800,7 @@ namespace Barotrauma
{
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)
{
@@ -1858,7 +1905,7 @@ namespace Barotrauma
LocalizedString toolTip = string.Empty;
if (purchasedItem.ItemPrefab != null)
{
toolTip = purchasedItem.ItemPrefab.GetTooltip();
toolTip = purchasedItem.ItemPrefab.GetTooltip(Character.Controlled);
if (itemQuantity != null)
{
if (itemQuantity.AllNonEmpty)
@@ -1871,6 +1918,23 @@ namespace Barotrauma
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);
}
@@ -15,8 +15,6 @@ namespace Barotrauma
private int pageCount;
private readonly bool transferService, purchaseService;
private bool initialized;
private int deliveryFee;
private string deliveryLocationName;
public GUIFrame GuiFrame;
private GUIFrame pageIndicatorHolder;
@@ -34,14 +32,13 @@ namespace Barotrauma
private readonly List<SubmarineInfo> subsToShow;
private readonly SubmarineDisplayContent[] submarineDisplays = new SubmarineDisplayContent[submarinesPerPage];
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 Action closeAction;
private Sprite pageIndicator;
private readonly LocalizedString[] messageBoxOptions;
public const int DeliveryFeePerDistanceTravelled = 1000;
public static bool ContentRefreshRequired = false;
private static readonly Color indicatorColor = new Color(112, 149, 129);
@@ -108,14 +105,9 @@ namespace Barotrauma
{
initialized = true;
selectedSubText = TextManager.Get("selectedsub");
deliveryText = TextManager.Get("requestdeliverybutton");
switchText = TextManager.Get("switchtosubmarinebutton");
purchaseAndSwitchText = TextManager.Get("purchaseandswitch");
purchaseOnlyText = TextManager.Get("purchase");
if (transferService)
{
deliveryFee = CalculateDeliveryFee();
}
currencyName = TextManager.Get("credit").Value.ToLowerInvariant();
@@ -124,13 +116,6 @@ namespace Barotrauma
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()
{
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");
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);
transferInfoFrameWidth -= confirmButton.RectTransform.RelativeSize.X;
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))
{
LocalizedString amountString = TextManager.FormatCurrency(subToDisplay.Price);
LocalizedString amountString = TextManager.FormatCurrency(subToDisplay.GetPrice());
submarineDisplays[i].submarineFee.Text = TextManager.GetWithVariable("price", "[amount]", amountString);
}
else
{
if (subToDisplay.Name != CurrentOrPendingSubmarine().Name)
{
if (deliveryFee > 0)
{
LocalizedString amountString = TextManager.FormatCurrency(deliveryFee);
submarineDisplays[i].submarineFee.Text = TextManager.GetWithVariable("deliveryfee", "[amount]", amountString);
}
else
{
submarineDisplays[i].submarineFee.Text = string.Empty;
}
submarineDisplays[i].submarineFee.Text = string.Empty;
}
else
{
@@ -581,7 +558,7 @@ namespace Barotrauma
if (owned)
{
confirmButton.Text = deliveryFee > 0 ? deliveryText : switchText;
confirmButton.Text = switchText;
confirmButton.OnClicked = (button, userData) =>
{
ShowTransferPrompt();
@@ -702,37 +679,12 @@ namespace Barotrauma
private void ShowTransferPrompt()
{
if (!GameMain.GameSession.Campaign.CanAfford(deliveryFee) && deliveryFee > 0)
{
new GUIMessageBox(TextManager.Get("deliveryrequestheader"), TextManager.GetWithVariables("notenoughmoneyfordeliverytext",
("[currencyname]", currencyName),
("[submarinename]", selectedSubmarine.DisplayName),
("[location1]", deliveryLocationName),
("[location2]", GameMain.GameSession.Map.CurrentLocation.Name)));
return;
}
var text = TextManager.GetWithVariables("switchsubmarinetext",
("[submarinename1]", CurrentOrPendingSubmarine().DisplayName),
("[submarinename2]", selectedSubmarine.DisplayName));
text += GetItemTransferText();
GUIMessageBox msgBox = new GUIMessageBox(TextManager.Get("switchsubmarineheader"), text, messageBoxOptions);
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) =>
{
@@ -777,7 +729,7 @@ namespace Barotrauma
{
if (GameMain.Client == null)
{
GameMain.GameSession.SwitchSubmarine(selectedSubmarine, TransferItemsOnSwitch, deliveryFee);
GameMain.GameSession.SwitchSubmarine(selectedSubmarine, TransferItemsOnSwitch);
RefreshSubmarineDisplay(true);
}
else
@@ -797,7 +749,9 @@ namespace Barotrauma
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",
("[currencyname]", currencyName),
@@ -810,7 +764,7 @@ namespace Barotrauma
{
var text = TextManager.GetWithVariables("purchaseandswitchsubmarinetext",
("[submarinename1]", selectedSubmarine.DisplayName),
("[amount]", selectedSubmarine.Price.ToString()),
("[amount]", price.ToString()),
("[currencyname]", currencyName),
("[submarinename2]", CurrentOrPendingSubmarine().DisplayName));
text += GetItemTransferText();
@@ -854,7 +808,7 @@ namespace Barotrauma
if (GameMain.Client == null)
{
GameMain.GameSession.PurchaseSubmarine(selectedSubmarine);
GameMain.GameSession.SwitchSubmarine(selectedSubmarine, TransferItemsOnSwitch, 0);
GameMain.GameSession.SwitchSubmarine(selectedSubmarine, TransferItemsOnSwitch);
RefreshSubmarineDisplay(true);
}
else
@@ -868,7 +822,7 @@ namespace Barotrauma
{
msgBox = new GUIMessageBox(TextManager.Get("purchasesubmarineheader"), TextManager.GetWithVariables("purchasesubmarinetext",
("[submarinename]", selectedSubmarine.DisplayName),
("[amount]", selectedSubmarine.Price.ToString()),
("[amount]", price.ToString()),
("[currencyname]", currencyName)) + '\n' + TextManager.Get("submarineswitchinstruction"), messageBoxOptions);
msgBox.Buttons[0].OnClicked = (applyButton, obj) =>
@@ -836,7 +836,7 @@ namespace Barotrauma
Identifier eventIdentifier = new Identifier($"{nameof(CreateWalletCrewFrame)}.{character.ID}");
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);
});
registeredEvents.Add(eventIdentifier);
@@ -1502,27 +1502,9 @@ namespace Barotrauma
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);
bool hasPortrait = portrait != null && portrait.SourceRect.Width > 0 && portrait.SourceRect.Height > 0;
int contentWidth = missionFrameContent.Rect.Width;
if (hasPortrait)
{
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);
}
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) });
missionList.ContentBackground.Color = Color.Transparent;
missionList.Spacing = GUI.IntScale(15);
@@ -1545,6 +1551,7 @@ namespace Barotrauma
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);
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;
}
RichString rewardText = mission.GetMissionRewardText(Submarine.MainSub);
RichString reputationText = mission.GetReputationRewardText(mission.Locations[0]);
RichString reputationText = mission.GetReputationRewardText();
Func<string, string> wrapMissionText(GUIFont font)
{
@@ -727,7 +727,7 @@ namespace Barotrauma
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
: Available;
@@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using Barotrauma.Extensions;
@@ -77,6 +78,8 @@ namespace Barotrauma
private PlayerBalanceElement? playerBalanceElement;
private static ImmutableHashSet<Character> characterList = ImmutableHashSet<Character>.Empty;
/// <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.
/// 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)
{
WaitForServerUpdate = false;
characterList = GameSession.GetSessionCrewCharacters(CharacterType.Both);
this.campaignUI = campaignUI;
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()
{
characterList = GameSession.GetSessionCrewCharacters(CharacterType.Both);
switch (selectedUpgradeTab)
{
case UpgradeTab.Repairs:
@@ -273,7 +278,7 @@ namespace Barotrauma
new GUITextBlock(rectT(1, 0, tooltipLayout), string.Empty) { UserData = "moreindicator" };
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 };
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);
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 repairButton = new GUIButton(rectT(1, 1f, categoryButtonLayout), TextManager.Get("UICategory.Maintenance"), style: "GUITabButton") { UserData = UpgradeTab.Repairs, Selected = selectedUpgradeTab == UpgradeTab.Repairs };
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(0.5f, 1f, categoryButtonLayout), TextManager.Get("UICategory.Maintenance"), style: "GUITabButton") { UserData = UpgradeTab.Repairs, Selected = selectedUpgradeTab == UpgradeTab.Repairs };
/* RIGHT HEADER LAYOUT
* |---------------------------------------------------------------------------------------------------|
@@ -347,12 +352,15 @@ namespace Barotrauma
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,
CanBeFocused = true
};
GUITextBlock.AutoScaleAndNormalize(upgradeButton.TextBlock, repairButton.TextBlock);
#if DEBUG
// creates a button that re-creates the UI
CreateRefreshButton();
@@ -725,7 +733,7 @@ namespace Barotrauma
if (storeLayout == null || mainStoreLayout == null) { return; }
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();
@@ -956,7 +964,7 @@ namespace Barotrauma
bool isUninstallPending = item.Prefab.SwappableItem != null && item.PendingItemSwap?.Identifier == item.Prefab.SwappableItem.ReplacementOnUninstall;
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),
currentOrPending.Description,
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();
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,
addBuyButton: true,
addProgressBar: false,
@@ -1102,7 +1110,7 @@ namespace Barotrauma
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));
}
@@ -1129,7 +1137,8 @@ namespace Barotrauma
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 };
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));
var description = new GUITextBlock(rectT(1, 1, descriptionLayout), body, font: GUIStyle.SmallFont, wrap: true, textAlignment: Alignment.TopLeft) { Padding = Vector4.Zero };
GUILayoutGroup? progressLayout = null;
@@ -1171,7 +1180,7 @@ namespace Barotrauma
materialCostList.Visible = false;
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,
//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",
("[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, () =>
{
if (GameMain.NetworkMember != null)
@@ -1673,7 +1682,7 @@ namespace Barotrauma
GUITextBlock priceLabel = (GUITextBlock)buttonParent.FindChild(UpgradeStoreUserData.PriceLabel, recursive: 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)
{
@@ -1713,7 +1722,7 @@ namespace Barotrauma
static void CreateMaterialCosts(GUIListBox list, UpgradePrefab prefab, int targetLevel)
{
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);
@@ -163,6 +163,7 @@ namespace Barotrauma
private void SetSubmarineVotingText(Client starter, SubmarineInfo info, bool transferItems, VoteType type)
{
int price = info.GetPrice();
string name = starter.Name;
JobPrefab prefab = starter?.Character?.Info?.Job?.Prefab;
Color nameColor = prefab != null ? prefab.UIColor : Color.White;
@@ -177,35 +178,21 @@ namespace Barotrauma
text = TextManager.GetWithVariables(tag,
("[playername]", characterRichString),
("[submarinename]", submarineRichString),
("[amount]", info.Price.ToString()),
("[amount]", price.ToString()),
("[currencyname]", TextManager.Get("credit").ToLower()));
break;
case VoteType.PurchaseSub:
text = TextManager.GetWithVariables("submarinepurchasevote",
("[playername]", characterRichString),
("[submarinename]", submarineRichString),
("[amount]", info.Price.ToString()),
("[amount]", price.ToString()),
("[currencyname]", TextManager.Get("credit").ToLower()));
break;
case VoteType.SwitchSub:
int deliveryFee = SubmarineSelection.DeliveryFeePerDistanceTravelled * GameMain.GameSession.Map.DistanceToClosestLocationWithOutpost(GameMain.GameSession.Map.CurrentLocation, out Location endLocation);
if (deliveryFee > 0)
{
tag = transferItems ? "submarineswitchwithitemsfeevote" : "submarineswitchfeevote";
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));
}
tag = transferItems ? "submarineswitchwithitemsnofeevote" : "submarineswitchnofeevote";
text = TextManager.GetWithVariables(tag,
("[playername]", characterRichString),
("[submarinename]", submarineRichString));
break;
}
votingOnText = RichString.Rich(text);
@@ -218,6 +205,7 @@ namespace Barotrauma
private LocalizedString GetSubmarineVoteResultMessage(SubmarineInfo info, VoteType type, int yesVoteCount, int noVoteCount, bool votePassed)
{
int price = info.GetPrice();
LocalizedString result = string.Empty;
switch (type)
@@ -225,7 +213,7 @@ namespace Barotrauma
case VoteType.PurchaseAndSwitchSub:
result = TextManager.GetWithVariables(votePassed ? "submarinepurchaseandswitchvotepassed" : "submarinepurchaseandswitchvotefailed",
("[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()),
("[yesvotecount]", yesVoteCount.ToString()),
("[novotecount]" , noVoteCount.ToString()));
@@ -233,31 +221,16 @@ namespace Barotrauma
case VoteType.PurchaseSub:
result = TextManager.GetWithVariables(votePassed ? "submarinepurchasevotepassed" : "submarinepurchasevotefailed",
("[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()),
("[yesvotecount]", yesVoteCount.ToString()),
("[novotecount]", noVoteCount.ToString()));
break;
case VoteType.SwitchSub:
int deliveryFee = SubmarineSelection.DeliveryFeePerDistanceTravelled * GameMain.GameSession.Map.DistanceToClosestLocationWithOutpost(GameMain.GameSession.Map.CurrentLocation, out Location endLocation);
if (deliveryFee > 0)
{
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()));
}
result = TextManager.GetWithVariables(votePassed ? "submarineswitchnofeevotepassed" : "submarineswitchnofeevotefailed",
("[submarinename]", info.DisplayName),
("[yesvotecount]", yesVoteCount.ToString()),
("[novotecount]", noVoteCount.ToString()));
break;
default:
break;
@@ -232,9 +232,8 @@ namespace Barotrauma
}
GameSettings.Init();
CreatureMetrics.Init();
Md5Hash.Cache.Load();
ConsoleArguments = args;
try
@@ -747,7 +746,7 @@ namespace Barotrauma
}
else if (HasLoaded)
{
if (ConnectCommand is Some<ConnectCommand> { Value: var connectCommand })
if (ConnectCommand.TryUnwrap(out var connectCommand))
{
if (Client != null)
{
@@ -1069,21 +1068,23 @@ namespace Barotrauma
public static void QuitToMainMenu(bool save)
{
CreatureMetrics.Save();
if (save)
{
GUI.SetSavingIndicatorState(true);
if (GameSession.Submarine != null && !GameSession.Submarine.Removed)
{
GameSession.SubmarineInfo = new SubmarineInfo(GameSession.Submarine);
}
// 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)
if (GameSession.Campaign is CampaignMode campaign)
{
spCampaign.UpdateStoreStock();
if (campaign is SinglePlayerCampaign spCampaign && Level.IsLoadedFriendlyOutpost)
{
spCampaign.UpdateStoreStock();
}
GameSession.EventManager?.RegisterEventHistory(registerFinishedOnly: true);
campaign.End();
}
SaveUtil.SaveGame(GameSession.SavePath);
}
@@ -1174,6 +1175,7 @@ namespace Barotrauma
protected override void OnExiting(object sender, EventArgs args)
{
exiting = true;
CreatureMetrics.Save();
DebugConsole.NewMessage("Exiting...");
Client?.Quit();
SteamManager.ShutDown();
@@ -9,7 +9,6 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using Barotrauma.Steam;
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())
{
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)
// Spacing
- (7 * layoutGroup.RelativeSpacing);
nameRelativeWidth = Math.Max(nameRelativeWidth, 0.25f);
var font = layoutGroup.Rect.Width < 150 ? GUIStyle.SmallFont : GUIStyle.Font;
var nameBlock = new GUITextBlock(
@@ -1403,8 +1403,7 @@ namespace Barotrauma
bool hitDeselect = PlayerInput.KeyHit(InputType.Deselect) &&
(!PlayerInput.SecondaryMouseButtonClicked() || (!isMouseOnOptionNode && !isMouseOnShortcutNode));
bool isBoundToPrimaryMouse = GameSettings.CurrentConfig.KeyMap.Bindings[InputType.Command].MouseButton is MouseButton mouseButton &&
(mouseButton == MouseButton.PrimaryMouse || mouseButton == (PlayerInput.MouseButtonsSwapped() ? MouseButton.RightMouse : MouseButton.LeftMouse));
bool isBoundToPrimaryMouse = GameSettings.CurrentConfig.KeyMap.Bindings[InputType.Command].MouseButton == MouseButton.PrimaryMouse;
bool canToggleInterface = !isBoundToPrimaryMouse ||
(!isMouseOnOptionNode && !isMouseOnShortcutNode && extraOptionNodes.None(n => GUI.IsMouseOn(n)) && !GUI.IsMouseOn(returnNode));
@@ -2796,8 +2795,8 @@ namespace Barotrauma
var orderName = GetOrderNameBasedOnContextuality(order);
var icon = CreateNodeIcon(Vector2.One, node.RectTransform, order.SymbolSprite, order.Color,
tooltip: !showAssignmentTooltip ? orderName : orderName +
"\n" + (!PlayerInput.MouseButtonsSwapped() ? TextManager.Get("input.leftmouse") : TextManager.Get("input.rightmouse")) + ": " + TextManager.Get("commandui.quickassigntooltip") +
"\n" + (!PlayerInput.MouseButtonsSwapped() ? TextManager.Get("input.rightmouse") : TextManager.Get("input.leftmouse")) + ": " + TextManager.Get("commandui.manualassigntooltip"));
"\n" + PlayerInput.PrimaryMouseLabel + ": " + TextManager.Get("commandui.quickassigntooltip") +
"\n" + PlayerInput.SecondaryMouseLabel + ": " + TextManager.Get("commandui.manualassigntooltip"));
if (disableNode)
{
@@ -2999,8 +2998,8 @@ namespace Barotrauma
var showAssignmentTooltip = characterContext == null && !order.MustManuallyAssign && !order.TargetAllCharacters;
icon = CreateNodeIcon(Vector2.One, node.RectTransform, sprite, order.Color,
tooltip: characterContext != null ? optionName : optionName +
"\n" + (!PlayerInput.MouseButtonsSwapped() ? TextManager.Get("input.leftmouse") : TextManager.Get("input.rightmouse")) + ": " + TextManager.Get("commandui.quickassigntooltip") +
"\n" + (!PlayerInput.MouseButtonsSwapped() ? TextManager.Get("input.rightmouse") : TextManager.Get("input.leftmouse")) + ": " + TextManager.Get("commandui.manualassigntooltip"));
"\n" + PlayerInput.PrimaryMouseLabel + ": " + TextManager.Get("commandui.quickassigntooltip") +
"\n" + PlayerInput.SecondaryMouseLabel + ": " + TextManager.Get("commandui.manualassigntooltip"));
}
if (!CanCharacterBeHeard())
{
@@ -1,9 +1,7 @@
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace Barotrauma
{
@@ -11,21 +9,22 @@ namespace Barotrauma
{
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;
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(ignored)).ToDictionary(i => i.Key, i => i.Value);
}
campaignData = campaignData.Where(pair => !pair.Key.StartsWith(keyStartsWith)).ToDictionary(i => i.Key, i => i.Value);
}
int offset = 0;;
if (campaignData.Count > 0)
{
offset = debugDrawMetadataOffset % campaignData.Count;
offset = debugDrawMetaData.Offset % campaignData.Count;
if (offset < 0) { offset += campaignData.Count; }
}
@@ -72,7 +71,7 @@ namespace Barotrauma
}
float y = infoRect.Bottom + 16;
if (Campaign.Factions != null)
if (campaign.Factions != null)
{
const string factionHeader = "Reputations";
Vector2 factionHeaderSize = GUIStyle.SubHeadingFont.MeasureString(factionHeader);
@@ -81,7 +80,7 @@ namespace Barotrauma
GUI.DrawString(spriteBatch, factionPos, factionHeader, Color.White, font: GUIStyle.SubHeadingFont);
y += factionHeaderSize.Y + 8;
foreach (Faction faction in Campaign.Factions)
foreach (Faction faction in campaign.Factions)
{
LocalizedString name = faction.Prefab.Name;
Vector2 nameSize = GUIStyle.SmallFont.MeasureString(name);
@@ -94,20 +93,6 @@ namespace Barotrauma
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)
{
if (Owner is Some<Character> { Value: var character })
if (Owner.TryUnwrap(out var character))
{
if (!character.IsPlayer) { return; }
}
@@ -1,5 +1,4 @@
using Barotrauma.Extensions;
using Barotrauma.Items.Components;
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
@@ -16,8 +15,6 @@ namespace Barotrauma
protected bool crewDead;
protected Color overlayColor;
protected LocalizedString overlayText, overlayTextBottom;
protected Color overlayTextColor;
protected Sprite overlaySprite;
private TransitionType prevCampaignUIAutoOpenType;
@@ -30,7 +27,13 @@ namespace Barotrauma
protected GUIFrame campaignUIContainer;
public CampaignUI CampaignUI;
public static CancellationTokenSource StartRoundCancellationToken { get; private set; }
public SlideshowPlayer SlideshowPlayer
{
get;
protected set;
}
private CancellationTokenSource startRoundCancellationToken;
public bool ForceMapUI
{
@@ -59,10 +62,19 @@ namespace Barotrauma
{
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;
}
}
@@ -77,6 +89,7 @@ namespace Barotrauma
{
foreach (Mission mission in Missions.ToList())
{
if (!mission.Prefab.ShowStartMessage) { continue; }
new GUIMessageBox(
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)
@@ -108,6 +121,23 @@ namespace Barotrauma
{
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)
{
@@ -123,32 +153,10 @@ namespace Barotrauma
{
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"))
{
endRoundButton.Visible = false;
@@ -157,7 +165,7 @@ namespace Barotrauma
}
if (Submarine.MainSub == null || Level.Loaded == null) { return; }
endRoundButton.Visible = false;
bool allowEndingRound = false;
var availableTransition = GetAvailableTransition(out _, out Submarine leavingSub);
LocalizedString buttonText = "";
switch (availableTransition)
@@ -168,12 +176,12 @@ namespace Barotrauma
{
string textTag = availableTransition == TransitionType.ProgressToNextLocation ? "EnterLocation" : "EnterEmptyLocation";
buttonText = TextManager.GetWithVariable(textTag, "[locationname]", Level.Loaded.EndLocation?.Name ?? "[ERROR]");
endRoundButton.Visible = !ForceMapUI && !ShowCampaignUI;
allowEndingRound = !ForceMapUI && !ShowCampaignUI;
}
break;
case TransitionType.LeaveLocation:
buttonText = TextManager.GetWithVariable("LeaveLocation", "[locationname]", Level.Loaded.StartLocation?.Name ?? "[ERROR]");
endRoundButton.Visible = !ForceMapUI && !ShowCampaignUI;
allowEndingRound = !ForceMapUI && !ShowCampaignUI;
break;
case TransitionType.ReturnToPreviousLocation:
case TransitionType.ReturnToPreviousEmptyLocation:
@@ -181,31 +189,27 @@ namespace Barotrauma
{
string textTag = availableTransition == TransitionType.ReturnToPreviousLocation ? "EnterLocation" : "EnterEmptyLocation";
buttonText = TextManager.GetWithVariable(textTag, "[locationname]", Level.Loaded.StartLocation?.Name ?? "[ERROR]");
endRoundButton.Visible = !ForceMapUI && !ShowCampaignUI;
allowEndingRound = !ForceMapUI && !ShowCampaignUI;
}
break;
case TransitionType.None:
default:
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)))
{
buttonText = TextManager.GetWithVariable("LeaveLocation", "[locationname]", Level.Loaded.StartLocation?.Name ?? "[ERROR]");
endRoundButton.Visible = !ForceMapUI && !ShowCampaignUI;
}
else
{
endRoundButton.Visible = false;
allowEndingRound = !ForceMapUI && !ShowCampaignUI;
}
break;
}
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 (!AllowedToManageCampaign(ClientPermissions.ManageMap))
@@ -259,11 +263,11 @@ namespace Barotrauma
GUI.ClearCursorWait();
StartRoundCancellationToken = new CancellationTokenSource();
startRoundCancellationToken = new CancellationTokenSource();
var loadTask = Task.Run(async () =>
{
await Task.Yield();
Rand.ThreadId = Thread.CurrentThread.ManagedThreadId;
Rand.ThreadId = Environment.CurrentManagedThreadId;
try
{
GameMain.GameSession.StartRound(newLevel, mirrorLevel: mirror, startOutpost: GetPredefinedStartOutpost());
@@ -273,7 +277,8 @@ namespace Barotrauma
roundSummaryScreen.LoadException = e;
}
Rand.ThreadId = 0;
}, StartRoundCancellationToken.Token);
startRoundCancellationToken = null;
}, startRoundCancellationToken.Token);
TaskPool.Add("AsyncCampaignStartRound", loadTask, (t) =>
{
overlayColor = Color.Transparent;
@@ -283,6 +288,21 @@ namespace Barotrauma
return loadTask;
}
public void CancelStartRound()
{
startRoundCancellationToken?.Cancel();
}
public void ThrowIfStartRoundCancellationRequested()
{
if (startRoundCancellationToken != null &&
startRoundCancellationToken.Token.IsCancellationRequested)
{
startRoundCancellationToken.Token.ThrowIfCancellationRequested();
startRoundCancellationToken = null;
}
}
protected SubmarineInfo GetPredefinedStartOutpost()
{
if (Map?.CurrentLocation?.Type?.GetForcedOutpostGenerationParams() is OutpostGenerationParams parameters && !parameters.OutpostFilePath.IsNullOrEmpty())
@@ -316,10 +336,15 @@ namespace Barotrauma
goto default;
default:
ShowCampaignUI = true;
CampaignUI.SelectTab(npc.CampaignInteractionType, storeIdentifier: npc.MerchantIdentifier);
CampaignUI.SelectTab(npc.CampaignInteractionType, npc);
CampaignUI.UpgradeStore?.RequestRefresh();
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()
@@ -125,38 +125,25 @@ namespace Barotrauma
private void CreateButtons()
{
int buttonHeight = (int) (GUI.Scale * 40),
buttonWidth = GUI.IntScale(450),
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")
endRoundButton = CreateEndRoundButton();
endRoundButton.OnClicked = (btn, userdata) =>
{
Pulse = true,
TextBlock =
{
Shadow = true,
AutoScaleHorizontal = true
},
OnClicked = (btn, userdata) =>
{
TryEndRoundWithFuelCheck(
onConfirm: () => GameMain.Client.RequestStartRound(),
onReturnToMapScreen: () =>
{
ShowCampaignUI = true;
if (CampaignUI == null) { InitCampaignUI(); }
CampaignUI.SelectTab(InteractionType.Map);
});
return true;
}
TryEndRoundWithFuelCheck(
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);
ReadyCheckButton = new GUIButton(HUDLayoutSettings.ToRectTransform(new Rectangle(screenMiddle + (buttonWidth / 2) + GUI.IntScale(16), HUDLayoutSettings.ButtonAreaTop.Center.Y - buttonCenter, readyButtonWidth, readyButtonHeight), GUI.Canvas),
int readyButtonWidth = (int)(GUI.Scale * 50 * (GUI.IsUltrawide ? 3.0f : 1.0f));
int readyButtonHeight = (int)(GUI.Scale * 40);
int readyButtonCenter = readyButtonHeight / 2,
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")
{
ToolTip = TextManager.Get("ReadyCheck.Tooltip"),
@@ -216,51 +203,35 @@ namespace Barotrauma
}
Character prevControlled = Character.Controlled;
if (prevControlled?.AIController != null)
{
prevControlled.AIController.Enabled = false;
}
GUI.DisableHUD = true;
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();
overlayColor = Color.LightGray;
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 borders = outpost.GetDockedBorders();
borders.Location += outpost.WorldPosition.ToPoint();
GameMain.GameScreen.Cam.Position = new Vector2(borders.X + borders.Width / 2, borders.Y - borders.Height / 2);
float startZoom = 0.8f /
((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,
null, null,
fadeOut: false,
@@ -272,16 +243,6 @@ namespace Barotrauma
AllowInterrupt = true,
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)
{
@@ -385,7 +346,7 @@ namespace Barotrauma
overlayColor = Color.Transparent;
if (DateTime.Now > timeOut) { GameMain.NetLobbyScreen.Select(); }
if (!(Screen.Selected is RoundSummaryScreen))
if (Screen.Selected is not RoundSummaryScreen)
{
if (continueButton != null)
{
@@ -409,6 +370,8 @@ namespace Barotrauma
base.Update(deltaTime);
SlideshowPlayer?.UpdateManually(deltaTime);
if (PlayerInput.SecondaryMouseButtonClicked() ||
PlayerInput.KeyHit(Microsoft.Xna.Framework.Input.Keys.Escape))
{
@@ -442,7 +405,8 @@ namespace Barotrauma
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?)
// -> choose a destination when the sub is far enough from the start outpost
@@ -467,11 +431,17 @@ namespace Barotrauma
}
}
public override void UpdateWhilePaused(float deltaTime)
{
SlideshowPlayer?.UpdateManually(deltaTime);
}
public override void End(TransitionType transitionType = TransitionType.None)
{
base.End(transitionType);
ForceMapUI = ShowCampaignUI = false;
SlideshowPlayer?.Finish();
// remove all event dialogue boxes
GUIMessageBox.MessageBoxes.ForEachMod(mb =>
{
@@ -501,7 +471,8 @@ namespace Barotrauma
{
GUIMessageBox.MessageBoxes.Remove(GUIMessageBox.VisibleBox);
}
CoroutineManager.StartCoroutine(DoEndCampaignCameraTransition(), "DoEndCampaignCameraTransition");
GameMain.CampaignEndScreen.Select();
GUI.DisableHUD = false;
GameMain.CampaignEndScreen.OnFinished = () =>
{
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)
{
System.Diagnostics.Debug.Assert(map.Locations.Count < UInt16.MaxValue);
@@ -844,8 +789,6 @@ namespace Barotrauma
{
DebugConsole.Log("Received campaign update (Reputation)");
UInt16 id = msg.ReadUInt16();
float? reputation = null;
if (msg.ReadBoolean()) { reputation = msg.ReadSingle(); }
Dictionary<Identifier, float> factionReps = new Dictionary<Identifier, float>();
byte factionsCount = msg.ReadByte();
for (int i = 0; i < factionsCount; i++)
@@ -854,11 +797,6 @@ namespace Barotrauma
}
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)
{
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}\".");
}
}
campaign?.CampaignUI?.UpgradeStore?.RequestRefresh();
}
}
if (requiredFlags.HasFlag(NetFlags.CharacterInfo))
@@ -995,7 +934,9 @@ namespace Barotrauma
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);
if (hiredCharacters.Any()) { CampaignUI.CrewManagement.ValidateHires(hiredCharacters); }
@@ -1010,25 +951,20 @@ namespace Barotrauma
foreach (NetWalletTransaction transaction in update.Transactions)
{
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; }
Wallet wallet = targetCharacter.Wallet;
Character targetCharacter = Character.CharacterList?.FirstOrDefault(c => c.ID == charID);
if (targetCharacter is null) { break; }
Wallet wallet = targetCharacter.Wallet;
wallet.Balance = info.Balance;
wallet.RewardDistribution = info.RewardDistribution;
TryInvokeEvent(wallet, transaction.ChangedData, info);
break;
}
case None<ushort> _:
{
Bank.Balance = info.Balance;
TryInvokeEvent(Bank, transaction.ChangedData, info);
break;
}
wallet.Balance = info.Balance;
wallet.RewardDistribution = info.RewardDistribution;
TryInvokeEvent(wallet, transaction.ChangedData, info);
}
else
{
Bank.Balance = info.Balance;
TryInvokeEvent(Bank, transaction.ChangedData, info);
}
}
@@ -1043,7 +979,7 @@ namespace Barotrauma
public override bool TryPurchase(Client client, int price)
{
if (!AllowedToManageCampaign(ClientPermissions.ManageCampaign))
if (!AllowedToManageCampaign(ClientPermissions.ManageMoney))
{
return PersonalWallet.TryDeduct(price);
}
@@ -12,7 +12,13 @@ namespace Barotrauma
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)
@@ -31,6 +37,8 @@ namespace Barotrauma
}
}
SlideshowPlayer?.UpdateManually(deltaTime);
CrewManager.ChatBox?.Update(deltaTime);
CrewManager.UpdateReports();
}
@@ -77,9 +85,9 @@ namespace Barotrauma
/// </summary>
private SinglePlayerCampaign(string mapSeed, CampaignSettings settings) : base(GameModePreset.SinglePlayerCampaign, settings)
{
CampaignMetadata = new CampaignMetadata(this);
UpgradeManager = new UpgradeManager(this);
Settings = settings;
InitFactions();
map = new Map(this, mapSeed);
foreach (JobPrefab jobPrefab in JobPrefab.Prefabs)
{
@@ -89,7 +97,6 @@ namespace Barotrauma
CrewManager.AddCharacterInfo(new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: jobPrefab, variant: variant));
}
}
InitCampaignData();
InitUI();
}
@@ -99,6 +106,17 @@ namespace Barotrauma
private SinglePlayerCampaign(XElement element) : base(GameModePreset.SinglePlayerCampaign, CampaignSettings.Empty)
{
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())
{
@@ -114,9 +132,6 @@ namespace Barotrauma
case "map":
map = Map.Load(this, subElement);
break;
case "metadata":
CampaignMetadata = new CampaignMetadata(this, subElement);
break;
case "cargo":
CargoManager.LoadPurchasedItems(subElement);
break;
@@ -133,14 +148,14 @@ namespace Barotrauma
case "stats":
LoadStats(subElement);
break;
case "eventmanager":
GameMain.GameSession.EventManager.Load(subElement);
break;
}
}
CampaignMetadata ??= new CampaignMetadata(this);
UpgradeManager ??= new UpgradeManager(this);
InitCampaignData();
InitUI();
//backwards compatibility for saves made prior to the addition of personal wallets
@@ -198,28 +213,14 @@ namespace Barotrauma
{
StartRound = () => { TryEndRound(); }
};
}
private void CreateEndRoundButton()
{
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")
endRoundButton = CreateEndRoundButton();
endRoundButton.OnClicked = (btn, userdata) =>
{
Pulse = true,
TextBlock =
{
Shadow = true,
AutoScaleHorizontal = true
},
OnClicked = (btn, userdata) =>
{
TryEndRoundWithFuelCheck(
onConfirm: () => TryEndRound(),
onReturnToMapScreen: () => { ShowCampaignUI = true; CampaignUI.SelectTab(InteractionType.Map); });
return true;
}
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)
{
GameMain.GameSession.StartRound(level, mirrorLevel: mirror, startOutpost: GetPredefinedStartOutpost());
GameMain.GameScreen.Select();
@@ -296,34 +296,9 @@ namespace Barotrauma
if (IsFirstRound || showCampaignResetText)
{
overlayColor = Color.LightGray;
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 (SlideshowPrefab.Prefabs.TryGet("campaignstart".ToIdentifier(), out var slideshow))
{
if (timer > fadeInDuration)
{
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;
SlideshowPlayer = new SlideshowPlayer(GUICanvas.Instance, slideshow);
}
var outpost = GameMain.GameSession.Level.StartOutpost;
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);
float startZoom = 0.8f /
((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,
null, null,
fadeOut: false,
@@ -343,17 +324,6 @@ namespace Barotrauma
AllowInterrupt = true,
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)
{
yield return CoroutineStatus.Running;
@@ -440,61 +410,68 @@ namespace Barotrauma
TotalPassedLevels++;
break;
case TransitionType.ProgressToNextEmptyLocation:
Map.Visit(Map.CurrentLocation);
TotalPassedLevels++;
break;
case TransitionType.End:
EndCampaign();
IsFirstRound = true;
break;
}
Map.ProgressWorld(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);
Map.ProgressWorld(this, transitionType, GameMain.GameSession.RoundDuration);
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 (success)
if (transitionType != TransitionType.End)
{
GameMain.GameSession.SubmarineInfo = new SubmarineInfo(GameMain.GameSession.Submarine);
SaveUtil.SaveGame(GameMain.GameSession.SavePath);
}
else
{
PendingSubmarineSwitch = null;
EnableRoundSummaryGameOverState();
}
var endTransition = new CameraTransition(Submarine.MainSub, GameMain.GameScreen.Cam, null,
transitionType == TransitionType.LeaveLocation ? Alignment.BottomCenter : Alignment.Center,
fadeOut: false,
panDuration: EndTransitionDuration);
CrewManager?.ClearCurrentOrders();
//--------------------------------------
SelectSummaryScreen(roundSummary, newLevel, mirror, () =>
{
GameMain.GameScreen.Select();
if (continueButton != null)
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)
{
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;
GUI.ClearCursorWait();
overlayColor = Color.Transparent;
});
CrewManager?.ClearCurrentOrders();
SelectSummaryScreen(roundSummary, newLevel, mirror, () =>
{
GameMain.GameScreen.Select();
if (continueButton != null)
{
continueButton.Visible = true;
}
GUI.DisableHUD = false;
GUI.ClearCursorWait();
overlayColor = Color.Transparent;
});
}
GUI.SetSavingIndicatorState(false);
yield return CoroutineStatus.Success;
@@ -502,7 +479,10 @@ namespace Barotrauma
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 = () =>
{
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)
{
if (CoroutineManager.IsCoroutineRunning("LevelTransition") || CoroutineManager.IsCoroutineRunning("SubmarineTransition") || gameOver) { return; }
base.Update(deltaTime);
SlideshowPlayer?.UpdateManually(deltaTime);
Map?.Radiation?.UpdateRadiation(deltaTime);
if (PlayerInput.SecondaryMouseButtonClicked() ||
@@ -594,11 +549,19 @@ namespace Barotrauma
CampaignUI.SelectTab(InteractionType.Map);
}
}
else if (Level.Loaded.IsEndBiome)
{
var transitionType = GetAvailableTransition(out _, out Submarine leavingSub);
if (transitionType == TransitionType.ProgressToNextLocation)
{
LoadNewLevel();
}
}
else
{
//wasn't initially docked (sub doesn't have a docking port?)
// -> 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;
CampaignUI.SelectTab(InteractionType.Map);
@@ -608,11 +571,11 @@ namespace Barotrauma
else
{
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))
{
LoadNewLevel();
@@ -725,6 +688,11 @@ namespace Barotrauma
modeElement.Add(Settings.Save());
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
foreach (Character c in Character.CharacterList)
{
@@ -585,7 +585,7 @@ namespace Barotrauma
if (!gap.IsRoomToRoom)
{
if (!IsWearingDivingSuit()) { continue; }
if (Character.Controlled.IsProtectedFromPressure()) { continue; }
if (Character.Controlled.IsProtectedFromPressure) { continue; }
if (DisplayHint("divingsuitwarning".ToIdentifier(), extendTextTag: false)) { return; }
continue;
}
@@ -11,6 +11,8 @@ namespace Barotrauma
{
internal sealed partial class MedicalClinic
{
private MedicalClinicUI? ui => campaign?.CampaignUI?.MedicalClinic;
public enum RequestResult
{
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)
{
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()
{
IWriteMessage writeMessage = new WriteOnlyMessage();
@@ -337,6 +349,9 @@ namespace Barotrauma
case NetworkHeader.REQUEST_AFFLICTIONS:
AfflictionRequestReceived(inc);
break;
case NetworkHeader.AFFLICTION_UPDATE:
AfflictionUpdateReceived(inc);
break;
case NetworkHeader.REQUEST_PENDING:
PendingRequestReceived(inc);
break;
@@ -40,7 +40,7 @@ namespace Barotrauma
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);
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 float initialLocationReputation;
private readonly Dictionary<Faction, float> initialFactionReputations = new Dictionary<Faction, float>();
private readonly Dictionary<Identifier, float> initialFactionReputations = new Dictionary<Identifier, float>();
public GUILayoutGroup ButtonArea { get; private set; }
@@ -36,12 +35,11 @@ namespace Barotrauma
this.selectedMissions = selectedMissions.ToList();
this.startLocation = startLocation;
this.endLocation = endLocation;
initialLocationReputation = startLocation?.Reputation?.Value ?? 0.0f;
if (gameMode is CampaignMode campaignMode)
{
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
};
List<Mission> missionsToDisplay = new List<Mission>(selectedMissions);
if (!selectedMissions.Any() && startLocation != null)
List<Mission> missionsToDisplay = new List<Mission>(selectedMissions.Where(m => m.Prefab.ShowInMenus));
if (startLocation != null)
{
foreach (Mission mission in startLocation.SelectedMissions)
{
if (missionsToDisplay.Contains(mission)) { continue; }
if (!mission.Prefab.ShowInMenus) { continue; }
if (mission.Locations[0] == mission.Locations[1] ||
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),
RichString.Rich(missionMessage), wrap: true);
int reward = displayedMission.GetReward(Submarine.MainSub);
if (selectedMissions.Contains(displayedMission) && displayedMission.Completed && reward > 0)
if (selectedMissions.Contains(displayedMission) && displayedMission.Completed)
{
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)
RichString reputationText = displayedMission.GetReputationRewardText();
if (!reputationText.IsNullOrEmpty())
{
var (share, percentage, _) = Mission.GetRewardShare(controlled.Wallet.RewardDistribution, GameSession.GetSessionCrewCharacters(CharacterType.Player).Where(c => c != controlled), Option<int>.Some(reward));
if (share > 0)
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), reputationText, wrap: true);
}
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);
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);
var (share, percentage, _) = Mission.GetRewardShare(controlled.Wallet.RewardDistribution, GameSession.GetSessionCrewCharacters(CharacterType.Player).Where(c => c != controlled), Option<int>.Some(totalReward));
if (share > 0)
{
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;
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))
{
float initialReputation = faction.Reputation.Value;
if (initialFactionReputations.ContainsKey(faction))
{
initialReputation = initialFactionReputations[faction];
}
else
if (!initialFactionReputations.TryGetValue(faction.Prefab.Identifier, out initialReputation))
{
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(
reputationList.Content,
faction.Prefab.Name,
faction.Reputation.Value, faction.Reputation.NormalizedValue, initialReputation,
faction.Reputation, initialReputation,
faction.Prefab.ShortDescription, faction.Prefab.Description,
faction.Prefab.Icon, faction.Prefab.BackgroundPortrait, faction.Prefab.IconColor);
CreatePathUnlockElement(factionFrame, faction, null);
@@ -455,52 +448,60 @@ namespace Barotrauma
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 unlockEvent =
EventPrefab.Prefabs.FirstOrDefault(ep => ep.UnlockPathEvent && ep.BiomeIdentifier == gateLocation.LevelData.Biome.Identifier) ??
EventPrefab.Prefabs.FirstOrDefault(ep => ep.UnlockPathEvent && ep.BiomeIdentifier == Identifier.Empty);
var gateLocation = connection.Locations[0].IsGateBetweenBiomes ? connection.Locations[0] : connection.Locations[1];
var unlockEvent = EventPrefab.GetUnlockPathEvent(gateLocation.LevelData.Biome.Identifier, gateLocation.Faction);
if (unlockEvent == null) { continue; }
if (string.IsNullOrEmpty(unlockEvent.UnlockPathFaction) || unlockEvent.UnlockPathFaction.Equals("location", StringComparison.OrdinalIgnoreCase))
if (unlockEvent == null) { continue; }
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; }
}
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;
}
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 (locationName == null)
@@ -680,7 +686,7 @@ namespace Barotrauma
}
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)
{
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),
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))
{
AbsoluteSpacing = GUI.IntScale(10),
Stretch = true
};
var factionIcon = new GUIImage(new RectTransform(Vector2.One * 0.7f, factionInfoHorizontal.RectTransform, scaleBasis: ScaleBasis.Smallest), icon, scaleToFit: true)
{
Color = iconColor
};
factionInfoHorizontal.Recalculate();
var header = new GUITextBlock(new RectTransform(new Point(factionTextContent.Rect.Width, GUI.IntScale(40)), factionTextContent.RectTransform),
@@ -733,24 +740,30 @@ namespace Barotrauma
factionTextContent.Recalculate();
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));
var reputationText = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), sliderHolder.RectTransform),
string.Empty, textAlignment: Alignment.CenterLeft, font: GUIStyle.SubHeadingFont);
SetReputationText(reputationText);
reputation?.OnReputationValueChanged.RegisterOverwriteExisting("RefreshRoundSummary".ToIdentifier(), _ =>
{
SetReputationText(reputationText);
});
LocalizedString reputationText = Reputation.GetFormattedReputationText(normalizedReputation, reputation, addColorTags: true);
int reputationChange = (int)Math.Round(reputation - initialReputation);
if (Math.Abs(reputationChange) > 0)
void SetReputationText(GUITextBlock textBlock)
{
string changeText = $"{(reputationChange > 0 ? "+" : "") + reputationChange}";
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),
richText,
textAlignment: Alignment.CenterLeft, font: GUIStyle.SubHeadingFont);
}
else
{
new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), sliderHolder.RectTransform),
RichString.Rich(reputationText),
textAlignment: Alignment.CenterLeft, font: GUIStyle.SubHeadingFont);
LocalizedString reputationText = Reputation.GetFormattedReputationText(reputation.NormalizedValue, reputation.Value, addColorTags: true);
int reputationChange = (int)Math.Round(reputation.Value - initialReputation);
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
@@ -63,7 +63,6 @@ namespace Barotrauma
public Vector2[] SlotPositions;
public static Point SlotSize;
public static int Spacing;
private Layout layout;
public Layout CurrentLayout
@@ -103,7 +102,7 @@ namespace Barotrauma
{
visualSlots ??= new VisualSlot[capacity];
float multiplier = !GUI.IsFourByThree() ? UIScale : UIScale * 0.925f;
float multiplier = UIScale * GUI.AspectRatioAdjustment;
for (int i = 0; i < capacity; i++)
{
@@ -219,18 +218,11 @@ namespace Barotrauma
private void SetSlotPositions(Layout layout)
{
bool isFourByThree = GUI.IsFourByThree();
if (isFourByThree)
{
Spacing = (int)(5 * UIScale);
}
else
{
Spacing = (int)(8 * UIScale);
}
int spacing = GUI.IntScale(5);
SlotSize = !isFourByThree ? (SlotSpriteSmall.size * UIScale).ToPoint() : (SlotSpriteSmall.size * UIScale * .925f).ToPoint();
int bottomOffset = SlotSize.Y + Spacing * 2 + ContainedIndicatorHeight;
SlotSize = (SlotSpriteSmall.size * UIScale * GUI.AspectRatioAdjustment).ToPoint();
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.None()) { return; }
@@ -242,11 +234,11 @@ namespace Barotrauma
int personalSlotCount = SlotTypes.Count(s => PersonalSlots.HasFlag(s));
int normalSlotCount = SlotTypes.Count(s => !PersonalSlots.HasFlag(s) && s != InvSlotType.HealthInterface);
int x = GameMain.GraphicsWidth / 2 - normalSlotCount * (SlotSize.X + Spacing) / 2;
int upperX = HUDLayoutSettings.BottomRightInfoArea.X - SlotSize.X - Spacing;
int x = GameMain.GraphicsWidth / 2 - normalSlotCount * (SlotSize.X + spacing) / 2;
int upperX = HUDLayoutSettings.BottomRightInfoArea.X - SlotSize.X - spacing;
//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;
for (int i = 0; i < SlotPositions.Length; i++)
@@ -254,7 +246,7 @@ namespace Barotrauma
if (PersonalSlots.HasFlag(SlotTypes[i]))
{
SlotPositions[i] = new Vector2(upperX, GameMain.GraphicsHeight - bottomOffset);
upperX -= SlotSize.X + Spacing;
upperX -= SlotSize.X + spacing;
personalSlotArea = (hideButtonSlotIndex == -1) ?
new Rectangle(SlotPositions[i].ToPoint(), SlotSize) :
Rectangle.Union(personalSlotArea, new Rectangle(SlotPositions[i].ToPoint(), SlotSize));
@@ -263,7 +255,7 @@ namespace Barotrauma
else
{
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:
{
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++)
{
if (HideSlot(i) || SlotTypes[i] == InvSlotType.HealthInterface) { continue; }
@@ -282,19 +274,18 @@ namespace Barotrauma
}
else
{
x -= SlotSize.X + Spacing;
x -= SlotSize.X + spacing;
}
}
int lowerX = 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++)
{
if (SlotTypes[i] == InvSlotType.RightHand || SlotTypes[i] == InvSlotType.LeftHand)
{
SlotPositions[i] = new Vector2(handSlotX, personalSlotY);
handSlotX += visualSlots[i].Rect.Width + Spacing;
handSlotX += visualSlots[i].Rect.Width + spacing;
continue;
}
@@ -302,12 +293,12 @@ namespace Barotrauma
if (PersonalSlots.HasFlag(SlotTypes[i]))
{
SlotPositions[i] = new Vector2(personalSlotX, personalSlotY);
personalSlotX -= visualSlots[i].Rect.Width + Spacing;
personalSlotX -= visualSlots[i].Rect.Width + spacing;
}
else
{
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 (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);
}
}
@@ -325,7 +316,6 @@ namespace Barotrauma
{
int x = HUDLayoutSettings.InventoryAreaLower.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++)
{
@@ -334,33 +324,33 @@ namespace Barotrauma
if (PersonalSlots.HasFlag(SlotTypes[i]))
{
SlotPositions[i] = new Vector2(personalSlotX, personalSlotY);
personalSlotX += visualSlots[i].Rect.Width + Spacing;
personalSlotX += visualSlots[i].Rect.Width + spacing;
}
else
{
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++)
{
if (SlotTypes[i] == InvSlotType.RightHand || SlotTypes[i] == InvSlotType.LeftHand)
{
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;
}
if (!HideSlot(i) || SlotTypes[i] == InvSlotType.HealthInterface) { continue; }
SlotPositions[i] = new Vector2(x, GameMain.GraphicsHeight - bottomOffset);
x += visualSlots[i].Rect.Width + Spacing;
x += visualSlots[i].Rect.Width + spacing;
}
}
break;
case Layout.Center:
{
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 x = startX, y = startY;
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)
{
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;
int n = 0;
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)
{
SlotPositions[i] = new Vector2(x, y);
x += visualSlots[i].Rect.Width + Spacing;
x += visualSlots[i].Rect.Width + spacing;
n++;
if (n >= columns)
{
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;
}
}
@@ -402,7 +392,7 @@ namespace Barotrauma
{
if (SlotTypes[i] != InvSlotType.HealthInterface) { continue; }
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) ?
GUIComponent.ComponentState.Hover : GUIComponent.ComponentState.None;
if (PlayerInput.LeftButtonHeld() && PlayerInput.RightButtonHeld())
if (PlayerInput.PrimaryMouseButtonHeld() && PlayerInput.SecondaryMouseButtonHeld())
{
slot.EquipButtonState = GUIComponent.ComponentState.None;
}
@@ -1018,7 +1008,47 @@ namespace Barotrauma
SoundPlayer.PlayUISound(success ? GUISoundType.PickItem : GUISoundType.PickItemFail);
}
}
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)
{
if (!AccessibleWhenAlive && !character.IsDead && !AccessibleByOwner) { return; }
@@ -1106,40 +1136,24 @@ namespace Barotrauma
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)
{
case GUIComponent.ComponentState.None:
EquippedIndicator.Draw(spriteBatch, visualSlots[i].EquipButtonRect.Center.ToVector2(), color, EquippedIndicator.Origin, 0, UIScale * IndicatorScaleAdjustment);
break;
case GUIComponent.ComponentState.Hover:
EquippedHoverIndicator.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:
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;
}
}
GUIComponent.ComponentState.None
=> isEquipped ? EquippedIndicator : UnequippedIndicator,
GUIComponent.ComponentState.Hover
=> isEquipped ? EquippedHoverIndicator : UnequippedHoverIndicator,
GUIComponent.ComponentState.Pressed
or GUIComponent.ComponentState.Selected
or GUIComponent.ComponentState.HoverSelected
=> isEquipped ? EquippedClickedIndicator : UnequippedClickedIndicator,
_ => throw new NotImplementedException()
};
sprite.Draw(spriteBatch, visualSlots[i].EquipButtonRect.Center.ToVector2(), color, EquippedIndicator.Origin, 0, indicatorScale);
}
if (Locked)
@@ -92,9 +92,6 @@ namespace Barotrauma.Items.Components
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 (IsHorizontal)
@@ -117,7 +114,7 @@ namespace Barotrauma.Items.Components
else
{
convexHull2.Enabled = true;
convexHull2.SetVertices(GetConvexHullCorners(rect2), mergeOverlappingSegments);
SetVertices(convexHull2, rect2);
}
}
}
@@ -141,7 +138,7 @@ namespace Barotrauma.Items.Components
else
{
convexHull2.Enabled = true;
convexHull2.SetVertices(GetConvexHullCorners(rect2), mergeOverlappingSegments);
SetVertices(convexHull2, rect2);
}
}
}
@@ -156,13 +153,28 @@ namespace Barotrauma.Items.Components
else
{
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)
{
convexHull.IsExteriorWall = !linkedGap.IsRoomToRoom;
if (convexHull2 != null) { convexHull2.IsExteriorWall = convexHull.IsExteriorWall; }
if (shakeTimer > 0.0f)
{
shakeTimer -= deltaTime;
@@ -182,7 +194,7 @@ namespace Barotrauma.Items.Components
if (brokenSprite == null)
{
//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;
}
@@ -88,6 +88,11 @@ namespace Barotrauma.Items.Components
public void ClientEventRead(IReadMessage msg, float sendingTime)
{
UInt16 userID = msg.ReadUInt16();
if (userID != Entity.NullEntityID)
{
user = Entity.FindEntityByID(userID) as Character;
}
CurrPowerConsumption = powerConsumption;
charging = true;
timer = Duration;
@@ -1,13 +1,10 @@
using Barotrauma.Particles;
using Barotrauma.Sounds;
using FarseerPhysics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using Barotrauma.IO;
using System.Text;
using System.Xml.Linq;
using Barotrauma.Sounds;
using System.Linq;
namespace Barotrauma.Items.Components
@@ -169,11 +166,11 @@ namespace Barotrauma.Items.Components
partial void LaunchProjSpecific()
{
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; }
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;
string typeStr = subElement.GetAttributeString("type", "");
try
{
type = (ActionType)Enum.Parse(typeof(ActionType), subElement.GetAttributeString("type", ""), true);
type = (ActionType)Enum.Parse(typeof(ActionType), typeStr, true);
}
catch (Exception e)
{
DebugConsole.ThrowError("Invalid sound type in " + subElement + "!", e);
DebugConsole.ThrowError($"Invalid sound type \"{typeStr}\" in item \"{item.Prefab.Identifier}\"!", e);
break;
}
@@ -524,11 +525,13 @@ namespace Barotrauma.Items.Components
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)
{
Enum.TryParse(subElement.GetAttributeString("selectionmode", "Random"), out SoundSelectionMode selectionMode);
soundSelectionModes[type] = selectionMode;
soundSelectionModes[type] = subElement.GetAttributeEnum("selectionmode", SoundSelectionMode.Random);
}
if (!sounds.TryGetValue(itemSound.Type, out List<ItemSound> soundList))
@@ -584,6 +587,8 @@ namespace Barotrauma.Items.Components
{
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),
GuiFrame.RectTransform, style: null)
{
@@ -623,7 +628,7 @@ namespace Barotrauma.Items.Components
};
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")
{
OnClicked = (btn, userdata) =>
@@ -648,6 +653,12 @@ namespace Barotrauma.Items.Components
return true;
}
};
if (hideDragIcons)
{
dragIcon.Visible = false;
settingsIcon.Visible = false;
}
}
}
@@ -277,7 +277,8 @@ namespace Barotrauma.Items.Components
int ignoredItemCount = 0;
var subContainableItems = AllSubContainableItems;
float capacity = GetMaxStackSize(targetSlot);
float targetSlotCapacity = GetMaxStackSize(targetSlot);
float capacity = targetSlotCapacity * MainContainerCapacity;
if (subContainableItems != null)
{
bool useMainContainerCapacity = true;
@@ -299,15 +300,11 @@ namespace Barotrauma.Items.Components
}
if (!useMainContainerCapacity) { break; }
}
if (useMainContainerCapacity)
{
capacity *= MainContainerCapacity;
}
else
if (!useMainContainerCapacity)
{
// Ignore all items in the main container.
ignoredItemCount = Inventory.AllItems.Count(it => subContainableItems.Any(ri => !ri.MatchesItem(it)));
capacity *= Capacity - MainContainerCapacity;
capacity = targetSlotCapacity * (Capacity - MainContainerCapacity);
}
}
int itemCount = Inventory.AllItems.Count() - ignoredItemCount;
@@ -391,63 +388,60 @@ namespace Barotrauma.Items.Components
bool isWiringMode = SubEditorScreen.TransparentWiringMode && SubEditorScreen.IsWiringMode();
int i = 0;
foreach (Item containedItem in Inventory.AllItems)
foreach (DrawableContainedItem contained in drawableContainedItems)
{
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; }
if (relatedItem.ItemPos.HasValue)
Vector2 pos = contained.ItemPos.Value;
if (item.body != null)
{
Vector2 pos = relatedItem.ItemPos.Value;
if (item.body != null)
Matrix transform = Matrix.CreateRotationZ(item.body.DrawRotation);
pos.X *= item.body.Dir;
itemPos = Vector2.Transform(pos, transform) + item.body.DrawPosition;
}
else
{
itemPos = pos;
// This code is aped based on above. Not tested.
if (item.FlippedX)
{
Matrix transform = Matrix.CreateRotationZ(item.body.DrawRotation);
pos.X *= item.body.Dir;
itemPos = Vector2.Transform(pos, transform) + item.body.DrawPosition;
itemPos.X = -itemPos.X;
itemPos.X += item.Rect.Width;
}
else
if (item.FlippedY)
{
itemPos = pos;
// This code is aped based on above. Not tested.
if (item.FlippedX)
{
itemPos.X = -itemPos.X;
itemPos.X += item.Rect.Width;
}
if (item.FlippedY)
{
itemPos.Y = -itemPos.Y;
itemPos.Y -= item.Rect.Height;
}
itemPos += new Vector2(item.Rect.X, item.Rect.Y);
if (item.Submarine != null)
{
itemPos += item.Submarine.DrawPosition;
}
if (Math.Abs(item.RotationRad) > 0.01f)
{
Matrix transform = Matrix.CreateRotationZ(-item.RotationRad);
itemPos = Vector2.Transform(itemPos - item.DrawPosition, transform) + item.DrawPosition;
}
itemPos.Y = -itemPos.Y;
itemPos.Y -= item.Rect.Height;
}
itemPos += new Vector2(item.Rect.X, item.Rect.Y);
if (item.Submarine != null)
{
itemPos += item.Submarine.DrawPosition;
}
if (Math.Abs(item.RotationRad) > 0.01f)
{
Matrix transform = Matrix.CreateRotationZ(-item.RotationRad);
itemPos = Vector2.Transform(itemPos - item.DrawPosition, transform) + item.DrawPosition;
}
}
}
if (containedItem?.Sprite == null) { continue; }
if (AutoInteractWithContained)
{
containedItem.IsHighlighted = item.IsHighlighted;
contained.Item.IsHighlighted = item.IsHighlighted;
item.IsHighlighted = false;
}
Vector2 origin = containedItem.Sprite.Origin;
if (item.FlippedX) { origin.X = containedItem.Sprite.SourceRect.Width - origin.X; }
if (item.FlippedY) { origin.Y = containedItem.Sprite.SourceRect.Height - origin.Y; }
Vector2 origin = contained.Item.Sprite.Origin;
if (item.FlippedX) { origin.X = contained.Item.Sprite.SourceRect.Width - origin.X; }
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)
{
containedSpriteDepth = containedSpriteDepths[i];
@@ -456,9 +450,9 @@ namespace Barotrauma.Items.Components
SpriteEffects spriteEffects = SpriteEffects.None;
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)
{
@@ -469,17 +463,17 @@ namespace Barotrauma.Items.Components
spriteEffects |= MathUtils.NearlyEqual(spriteRotation % 180, 90.0f) ? SpriteEffects.FlipHorizontally : SpriteEffects.FlipVertically;
}
containedItem.Sprite.Draw(
contained.Item.Sprite.Draw(
spriteBatch,
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,
-(containedItem.body == null ? 0.0f : containedItem.body.DrawRotation),
containedItem.Scale,
-(contained.Item.body == null ? 0.0f : contained.Item.body.DrawRotation),
contained.Item.Scale,
spriteEffects,
depth: containedSpriteDepth);
foreach (ItemContainer ic in containedItem.GetComponents<ItemContainer>())
foreach (ItemContainer ic in contained.Item.GetComponents<ItemContainer>())
{
if (ic.hideItems) { continue; }
ic.DrawContainedItems(spriteBatch, containedSpriteDepth);
@@ -14,6 +14,16 @@ namespace Barotrauma.Items.Components
private CoroutineHandle resetPredictionCoroutine;
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
{
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;
}
partial void SetLightSourceState(bool enabled, float? brightness)
partial void SetLightSourceState(bool enabled, float brightness)
{
if (Light == null) { return; }
Light.Enabled = enabled;
if (brightness.HasValue)
{
lightBrightness = brightness.Value;
}
else
{
lightBrightness = enabled ? 1.0f : 0.0f;
}
lightColorMultiplier = brightness;
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);
}
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();
if (GameMain.Client != null)
{
pendingFabricatedItem = null;
item.CreateClientEvent(this);
}
return true;
@@ -336,8 +337,11 @@ namespace Barotrauma.Items.Components
int calculatePlacement(FabricationRecipe recipe)
{
if (recipe.RequiresRecipe && !AnyOneHasRecipeForItem(character, recipe.TargetItem))
{
return -2;
}
int placement = FabricationDegreeOfSuccess(character, recipe.RequiredSkills) >= 0.5f ? 0 : -1;
placement += recipe.RequiresRecipe && !AnyOneHasRecipeForItem(character, recipe.TargetItem) ? -2 : 0;
return placement;
}
@@ -524,7 +528,7 @@ namespace Barotrauma.Items.Components
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);
if (suitableIngredients.Count() > 3) { toolTipText += "..."; }
if (requiredItem.UseCondition && requiredItem.MinCondition < 1.0f)
@@ -546,9 +550,11 @@ namespace Barotrauma.Items.Components
{
toolTipText = TextManager.GetWithVariable("displayname.emptyitem", "[itemname]", toolTipText);
}
toolTipText = $"‖color:{Color.White.ToStringHex()}‖{toolTipText}‖color:end‖";
if (!requiredItemPrefab.Description.IsNullOrEmpty())
{
toolTipText += '\n' + requiredItemPrefab.Description;
toolTipText = '\n' + requiredItemPrefab.Description;
}
tooltip = new ToolTip { TargetElement = slotRect, Tooltip = toolTipText };
}
@@ -590,7 +596,7 @@ namespace Barotrauma.Items.Components
if (tooltip != null)
{
GUIComponent.DrawToolTip(spriteBatch, tooltip.Tooltip, tooltip.TargetElement);
GUIComponent.DrawToolTip(spriteBatch, RichString.Rich(tooltip.Tooltip), tooltip.TargetElement);
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)
{
@@ -452,7 +452,7 @@ namespace Barotrauma.Items.Components
foreach (var (entity, component) in electricalMapComponents)
{
GUIComponent parent = component.RectComponent;
if (!(entity is Item it )) { continue; }
if (entity is not Item it ) { continue; }
Sprite? sprite = it.Prefab.UpgradePreviewSprite;
if (sprite is null) { continue; }
@@ -476,7 +476,7 @@ namespace Barotrauma.Items.Components
{
if (!hullPointsOfInterest.Contains(entity)) { continue; }
if (!(entity is Item it)) { continue; }
if (entity is not Item it) { continue; }
const int borderMaxSize = 2;
if (it.GetComponent<Door>() is { })
@@ -643,7 +643,7 @@ namespace Barotrauma.Items.Components
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)
{
hullData.DistortionTimer -= deltaTime;
@@ -702,6 +702,12 @@ namespace Barotrauma.Items.Components
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)
{
Vector2 textSize = GUIStyle.Font.MeasureString(noPowerTip);
@@ -1130,7 +1136,7 @@ namespace Barotrauma.Items.Components
{
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 (entity.Removed)
@@ -1220,7 +1226,7 @@ namespace Barotrauma.Items.Components
{
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 (hullData.Distort) { continue; }
@@ -17,6 +17,7 @@ namespace Barotrauma.Items.Components
Default,
Disruption,
Destructible,
Door,
LongRange
}
@@ -110,6 +111,10 @@ namespace Barotrauma.Items.Components
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 }
},
{
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,
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 (Level.Loaded.StartLocation != null)
if (Level.Loaded.StartLocation?.Type is { ShowSonarMarker: true })
{
DrawMarker(spriteBatch,
Level.Loaded.StartLocation.Name,
@@ -985,7 +990,7 @@ namespace Barotrauma.Items.Components
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,
Level.Loaded.EndLocation.Name,
@@ -1010,19 +1015,19 @@ namespace Barotrauma.Items.Components
int missionIndex = 0;
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;
foreach (Vector2 sonarPosition in mission.SonarPositions)
if (!string.IsNullOrEmpty(label.Value))
{
DrawMarker(spriteBatch,
mission.SonarLabel.Value,
label.Value,
mission.SonarIconIdentifier,
"mission" + missionIndex + ":" + i,
sonarPosition, transducerCenter,
position, transducerCenter,
displayScale, center, DisplayRadius * 0.95f);
i++;
}
i++;
}
missionIndex++;
}
@@ -1176,13 +1181,18 @@ namespace Barotrauma.Items.Components
if (dockingPort.Item.Submarine == null) { 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
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
if (item.Submarine != null &&
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
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,
float pingStrength = 1.0f, AITarget needsToBeInSector = null)
{
@@ -1392,6 +1434,16 @@ namespace Barotrauma.Items.Components
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++)
{
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)
{
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 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),
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,
Enabled = autoPilot,
@@ -214,7 +215,6 @@ namespace Barotrauma.Items.Components
return true;
}
};
int textLimit = (int)(paddedAutoPilotControls.Rect.Width * 0.75f);
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),
font: GUIStyle.SmallFont, style: "GUIRadioButton")
@@ -340,6 +340,10 @@ namespace Barotrauma.Items.Components
centerText = $"({TextManager.Get("Meter")})";
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;
return ((int)realWorldDepth).ToString();
};
@@ -20,6 +20,7 @@ namespace Barotrauma.Items.Components
User = Entity.FindEntityByID(userId) as Character;
Vector2 simPosition = new Vector2(msg.ReadSingle(), msg.ReadSingle());
float rotation = msg.ReadSingle();
SpreadCounter = msg.ReadByte();
if (User != null)
{
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++)
{
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);
}
@@ -436,12 +438,16 @@ namespace Barotrauma.Items.Components
ushort currentFixerID = msg.ReadUInt16();
currentFixerAction = (FixActions)msg.ReadRangedInteger(0, 2);
CurrentFixer = currentFixerID != 0 ? Entity.FindEntityByID(currentFixerID) as Character : null;
item.MaxRepairConditionMultiplier = GetMaxRepairConditionMultiplier(CurrentFixer);
if (CurrentFixer == null)
if (CurrentFixer is null)
{
qteTimer = QteDuration;
qteCooldown = 0.0f;
}
else
{
item.MaxRepairConditionMultiplier = GetMaxRepairConditionMultiplier(CurrentFixer);
}
}
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 -= turret.GetRecoilOffset();
}
else if (weapon != null)
{
@@ -259,7 +259,7 @@ namespace Barotrauma.Items.Components
{
bool alreadyConnected = DraggingConnected.IsConnectedTo(panel.Item);
DraggingConnected.RemoveConnection(panel.Item);
if (DraggingConnected.Connect(this, !alreadyConnected, true))
if (DraggingConnected.TryConnect(this, !alreadyConnected, true))
{
var otherConnection = DraggingConnected.OtherConnection(this);
ConnectWire(DraggingConnected);
@@ -110,8 +110,8 @@ namespace Barotrauma.Items.Components
if (HighlightedWire != null)
{
HighlightedWire.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[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; }
}
}
@@ -225,7 +225,7 @@ namespace Barotrauma.Items.Components
foreach (var wire in newWires.Where(w => !connection.Wires.Contains(w)).ToArray())
{
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 GUILayoutGroup uiElementContainer;
private bool readingNetworkEvent;
private Point ElementMaxSize => new Point(uiElementContainer.Rect.Width, (int)(65 * GUI.yScale));
public override bool RecreateGUIOnResolutionChange => true;
@@ -100,7 +102,7 @@ namespace Barotrauma.Items.Components
{
ValueChanged(ni.UserData as CustomInterfaceElement, ni.FloatValue);
}
else
else if (!readingNetworkEvent)
{
item.CreateClientEvent(this);
}
@@ -126,7 +128,7 @@ namespace Barotrauma.Items.Components
{
ValueChanged(ni.UserData as CustomInterfaceElement, ni.IntValue);
}
else
else if (!readingNetworkEvent)
{
item.CreateClientEvent(this);
}
@@ -161,7 +163,7 @@ namespace Barotrauma.Items.Components
{
TickBoxToggled(tBox.UserData as CustomInterfaceElement, tBox.Selected);
}
else
else if (!readingNetworkEvent)
{
item.CreateClientEvent(this);
}
@@ -181,12 +183,12 @@ namespace Barotrauma.Items.Components
};
btn.OnClicked += (_, userdata) =>
{
CustomInterfaceElement btnElement = userdata as CustomInterfaceElement;;
CustomInterfaceElement btnElement = userdata as CustomInterfaceElement;
if (GameMain.Client == null)
{
ButtonClicked(btnElement);
}
else
else if (!readingNetworkEvent)
{
item.CreateClientEvent(this, new EventData(btnElement));
}
@@ -297,9 +299,10 @@ namespace Barotrauma.Items.Components
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()) :
customInterfaceElementList[elementIndex].Label;
TextManager.Get(label).Fallback(label);
}
uiElementContainer.Recalculate();
@@ -386,45 +389,53 @@ namespace Barotrauma.Items.Components
public void ClientEventRead(IReadMessage msg, float sendingTime)
{
for (int i = 0; i < customInterfaceElementList.Count; i++)
readingNetworkEvent = true;
try
{
var element = customInterfaceElementList[i];
if (element.HasPropertyName)
for (int i = 0; i < customInterfaceElementList.Count; i++)
{
string newValue = msg.ReadString();
if (!element.IsNumberInput)
var element = customInterfaceElementList[i];
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
{
switch (element.NumberType)
bool elementState = msg.ReadBoolean();
if (element.ContinuousSignal)
{
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;
((GUITickBox)uiElements[i]).Selected = elementState;
TickBoxToggled(element, elementState);
}
else if (elementState)
{
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 System.Linq;
using System.Xml.Linq;
@@ -24,7 +25,9 @@ namespace Barotrauma.Items.Components
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,
RelativeSpacing = 0.02f,
@@ -33,31 +36,34 @@ namespace Barotrauma.Items.Components
historyBox = new GUIListBox(new RectTransform(new Vector2(1, .9f), layoutGroup.RectTransform), style: null)
{
AutoHideScrollBar = false
AutoHideScrollBar = this.AutoHideScrollbar
};
CreateFillerBlock();
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 (!Readonly)
{
MaxTextLength = MaxMessageLength,
OverflowClip = true,
OnEnterPressed = (GUITextBox textBox, string text) =>
CreateFillerBlock();
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();
}
@@ -101,7 +107,7 @@ namespace Barotrauma.Items.Components
GUITextBlock newBlock = new GUITextBlock(
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)
{
CanBeFocused = false
@@ -123,7 +129,10 @@ namespace Barotrauma.Items.Components
historyBox.RecalculateChildren();
historyBox.UpdateScrollBarSize();
historyBox.ScrollBar.BarScrollValue = 1;
if (AutoScrollToBottom)
{
historyBox.ScrollBar.BarScrollValue = 1;
}
}
public override bool Select(Character character)
@@ -138,7 +147,7 @@ namespace Barotrauma.Items.Components
public override void AddToGUIUpdateList(int order = 0)
{
base.AddToGUIUpdateList(order: order);
if (shouldSelectInputBox)
if (shouldSelectInputBox && !Readonly)
{
inputBox.Select();
shouldSelectInputBox = false;
@@ -212,7 +212,7 @@ namespace Barotrauma.Items.Components
Sprite pingCircle = GUIStyle.UIThermalGlow.Value.Sprite;
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 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);
@@ -302,7 +302,18 @@ namespace Barotrauma.Items.Components
Dictionary<AfflictionPrefab, float> combinedAfflictionStrengths = new Dictionary<AfflictionPrefab, float>();
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))
{
combinedAfflictionStrengths[affliction.Prefab] += affliction.Strength;
@@ -333,15 +333,9 @@ namespace Barotrauma.Items.Components
crosshairPointerPos = PlayerInput.MousePosition;
}
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();
public Vector2 GetRecoilOffset()
{
float recoilOffset = 0.0f;
if (Math.Abs(RecoilDistance) > 0.0f && recoilTimer > 0.0f)
{
@@ -362,6 +356,17 @@ namespace Barotrauma.Items.Components
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,
drawPos,
@@ -370,7 +375,7 @@ namespace Barotrauma.Items.Components
SpriteEffects.None, item.SpriteDepth + (railSprite.Depth - item.Sprite.Depth));
barrelSprite?.Draw(spriteBatch,
drawPos - new Vector2((float)Math.Cos(rotation), (float)Math.Sin(rotation)) * recoilOffset * item.Scale,
drawPos - GetRecoilOffset() * item.Scale,
item.SpriteColor,
rotation + MathHelper.PiOver2, item.Scale,
SpriteEffects.None, item.SpriteDepth + (barrelSprite.Depth - item.Sprite.Depth));
@@ -53,11 +53,11 @@ namespace Barotrauma
get
{
// 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);
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);
equipIndicatorPos += DrawOffset;
@@ -176,14 +176,6 @@ namespace Barotrauma
public static Sprite DraggableIndicator;
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 Inventory ReplacedBy;
@@ -249,11 +241,11 @@ namespace Barotrauma
{
itemsInSlot = ParentInventory.GetItemsAt(SlotIndex);
}
Tooltip = GetTooltip(Item, itemsInSlot);
Tooltip = GetTooltip(Item, itemsInSlot, Character.Controlled);
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; }
@@ -348,10 +340,12 @@ namespace Barotrauma
}
if (itemsInSlot.Count() > 1)
{
string colorStr = XMLExtensions.ColorToString(GUIStyle.Blue);
toolTip += $"\n‖color:{colorStr}‖[{GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.TakeOneFromInventorySlot)}] {TextManager.Get("inputtype.takeonefrominventoryslot")}‖color:end‖";
colorStr = XMLExtensions.ColorToString(GUIStyle.Blue);
toolTip += $"\n‖color:{colorStr}‖[{GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.TakeHalfFromInventorySlot)}] {TextManager.Get("inputtype.takehalffrominventoryslot")}‖color:end‖";
toolTip += $"\n‖color:gui.blue‖[{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‖";
}
if (item.Prefab.SkillRequirementHints != null && item.Prefab.SkillRequirementHints.Any())
{
toolTip += item.Prefab.GetSkillRequirementHints(character);
}
return RichString.Rich(toolTip);
}
@@ -576,7 +570,7 @@ namespace Barotrauma
}
}
if (PlayerInput.LeftButtonHeld() && PlayerInput.RightButtonHeld())
if (PlayerInput.PrimaryMouseButtonHeld() && PlayerInput.SecondaryMouseButtonHeld())
{
mouseOn = false;
}
@@ -727,14 +721,7 @@ namespace Barotrauma
Rectangle subRect = slot.Rect;
Vector2 spacing;
if (GUI.IsFourByThree())
{
spacing = new Vector2(5 * UIScale, (5 + UnequippedIndicator.size.Y) * UIScale);
}
else
{
spacing = new Vector2(10 * UIScale, (10 + UnequippedIndicator.size.Y) * UIScale);
}
spacing = new Vector2(10 * UIScale, (10 + UnequippedIndicator.size.Y) * UIScale * GUI.AspectRatioAdjustment);
int columns = MathHelper.Clamp((int)Math.Floor(Math.Sqrt(itemCapacity)), 1, container.SlotsPerRow);
while (itemCapacity / columns * (subRect.Height + spacing.Y) > GameMain.GraphicsHeight * 0.5f)
@@ -1535,16 +1522,6 @@ namespace Barotrauma
{
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; }
spriteBatch.Draw(slotSprite.Texture, rect, slotSprite.SourceRect, slotColor);
@@ -1731,7 +1708,17 @@ namespace Barotrauma
slot.InventoryKeyIndex < GameSettings.CurrentConfig.InventoryKeyMap.Bindings.Length)
{
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>();
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)
{
if (interactionType == CampaignMode.InteractionType.None)
@@ -143,6 +157,18 @@ namespace Barotrauma
return color;
}
protected override void CheckIsHighlighted()
{
if (IsHighlighted || ExternalHighlight || IconStyle != null)
{
highlightedEntities.Add(this);
}
else
{
highlightedEntities.Remove(this);
}
}
public Color GetInventoryIconColor()
{
Color color = InventoryIconColor;
@@ -281,7 +307,8 @@ namespace Barotrauma
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.Y + extents.Height < worldView.Y - worldView.Height || worldPosition.Y + extents.Y > worldView.Y) { return false; }
@@ -310,7 +337,9 @@ namespace Barotrauma
BrokenItemSprite fadeInBrokenSprite = null;
float fadeInBrokenSpriteAlpha = 0.0f;
float displayCondition = FakeBroken ? 0.0f : ConditionPercentage;
Vector2 drawOffset = Vector2.Zero;
Vector2 drawOffset = GetCollapseEffectOffset();
drawOffset.Y = -drawOffset.Y;
if (displayCondition < MaxCondition)
{
for (int i = 0; i < Prefab.BrokenSprites.Length; i++)
@@ -426,6 +455,8 @@ namespace Barotrauma
var holdable = GetComponent<Holdable>();
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; }
float depthStep = 0.000001f;
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;
if (flippedX && Prefab.CanSpriteFlipX) { offset.X = -offset.X; }
if (flippedY && Prefab.CanSpriteFlipY) { offset.Y = -offset.Y; }
var ca = (float)Math.Cos(-body.Rotation);
var sa = (float)Math.Sin(-body.Rotation);
var ca = MathF.Cos(-body.DrawRotation);
var sa = MathF.Sin(-body.DrawRotation);
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,
-body.Rotation + rotation, decorativeSprite.GetScale(spriteAnimState[decorativeSprite].RandomScaleFactor) * Scale, activeSprite.effects,
decorativeSprite.Sprite.Draw(spriteBatch, new Vector2(body.DrawPosition.X + transformedOffset.X, -(body.DrawPosition.Y + transformedOffset.Y)), color,
-body.DrawRotation + rotation, decorativeSprite.GetScale(spriteAnimState[decorativeSprite].RandomScaleFactor) * Scale, activeSprite.effects,
depth: depth + (decorativeSprite.Sprite.Depth - activeSprite.Depth));
}
}
@@ -728,7 +759,7 @@ namespace Barotrauma
if (!lClick && !rClick) { return; }
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 (linkedTo.Contains(otherEntity))
@@ -1385,8 +1416,9 @@ namespace Barotrauma
}
break;
case EventType.Status:
bool loadingRound = msg.ReadBoolean();
float newCondition = msg.ReadSingle();
SetCondition(newCondition, isNetworkEvent: true);
SetCondition(newCondition, isNetworkEvent: true, executeEffects: !loadingRound);
break;
case EventType.AssignCampaignInteraction:
CampaignInteractionType = (CampaignMode.InteractionType)msg.ReadByte();
@@ -1672,25 +1704,24 @@ namespace Barotrauma
bool hasIdCard = msg.ReadBoolean();
string ownerName = "", ownerTags = "";
int ownerBeardIndex = -1, ownerHairIndex = -1, ownerMoustacheIndex = -1, ownerFaceAttachmentIndex = -1;
Color ownerHairColor = Microsoft.Xna.Framework.Color.White,
ownerFacialHairColor = Microsoft.Xna.Framework.Color.White,
ownerSkinColor = Microsoft.Xna.Framework.Color.White;
Color ownerHairColor = Color.White,
ownerFacialHairColor = Color.White,
ownerSkinColor = Color.White;
Identifier ownerJobId = Identifier.Empty;
Vector2 ownerSheetIndex = Vector2.Zero;
int submarineSpecificId = 0;
if (hasIdCard)
{
submarineSpecificId = msg.ReadInt32();
ownerName = msg.ReadString();
ownerTags = msg.ReadString();
ownerTags = msg.ReadString();
ownerBeardIndex = msg.ReadByte() - 1;
ownerHairIndex = msg.ReadByte() - 1;
ownerMoustacheIndex = msg.ReadByte() - 1;
ownerFaceAttachmentIndex = msg.ReadByte() - 1;
ownerFaceAttachmentIndex = msg.ReadByte() - 1;
ownerHairColor = msg.ReadColorR8G8B8();
ownerFacialHairColor = msg.ReadColorR8G8B8();
ownerSkinColor = msg.ReadColorR8G8B8();
ownerSkinColor = msg.ReadColorR8G8B8();
ownerJobId = msg.ReadIdentifier();
int x = msg.ReadByte();
@@ -1794,6 +1825,7 @@ namespace Barotrauma
}
foreach (IdCard idCard in item.GetComponents<IdCard>())
{
idCard.SubmarineSpecificID = submarineSpecificId;
idCard.TeamID = (CharacterTeamType)teamID;
idCard.OwnerName = ownerName;
idCard.OwnerTags = ownerTags;
@@ -254,7 +254,7 @@ namespace Barotrauma
if (!DefaultPrice.RequiresUnlock) { return true; }
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‖";
if (!Description.IsNullOrEmpty())
@@ -265,6 +265,10 @@ namespace Barotrauma
{
Wearable.AddTooltipInfo(wearableDamageModifiers, wearableSkillModifiers, ref tooltip);
}
if (SkillRequirementHints != null && SkillRequirementHints.Any())
{
tooltip += GetSkillRequirementHints(character);
}
return tooltip;
}
@@ -376,5 +380,31 @@ namespace Barotrauma
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.Graphics;
using System;
using System.Linq;
namespace Barotrauma
{
@@ -320,5 +321,45 @@ namespace Barotrauma
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);
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.linkedTo == null || !entity.Linkable) { continue; }
if (entity.linkedTo.Contains(this) || linkedTo.Contains(entity) || rClick)
{
if (entity == this || !entity.IsHighlighted) { continue; }
if (!entity.IsMouseOn(position)) { continue; }
if (entity.linkedTo.Contains(this))
{
entity.linkedTo.Remove(this);
linkedTo.Remove(entity);
}
entity.linkedTo.Remove(this);
linkedTo.Remove(entity);
}
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++)
{
GUI.DrawLine(spriteBatch,
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);
}*/
}
}
foreach (MapEntity e in linkedTo)
@@ -114,7 +114,11 @@ namespace Barotrauma
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)
@@ -22,7 +22,7 @@ namespace Barotrauma
public float CurrentRotation;
private List<SpriteDeformation> spriteDeformations = new List<SpriteDeformation>();
private readonly List<SpriteDeformation> spriteDeformations = new List<SpriteDeformation>();
public Vector2 CurrentScale
{
@@ -86,6 +86,8 @@ namespace Barotrauma
private set;
}
public bool CanBeVisible { get; private set; }
partial void InitProjSpecific()
{
Sprite?.EnsureLazyLoaded();
@@ -156,6 +158,11 @@ namespace Barotrauma
{
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)
@@ -56,63 +56,84 @@ namespace Barotrauma
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 y = currentIndices.Y; y <= currentIndices.Height; y++)
{
if (objectGrid[x, y] == null) { continue; }
foreach (LevelObject obj in objectGrid[x, y])
if (x != midIndexX || y != midIndexY) { CheckIndex(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; }
if (zoom < 0.05f)
//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) ||
(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
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;
}
continue;
}
var objectList =
obj.Position.Z >= 0 ?
visibleObjectsBack :
(obj.Position.Z < -1 ? visibleObjectsFront : visibleObjectsMid);
int drawOrderIndex = 0;
for (int i = 0; i < objectList.Count; i++)
float zCutoff = MathHelper.Lerp(5000.0f, 500.0f, (0.05f - zoom) * 20.0f);
if (obj.Position.Z > zCutoff)
{
if (objectList[i] == obj)
{
drawOrderIndex = -1;
break;
}
continue;
}
}
if (objectList[i].Position.Z < obj.Position.Z)
{
break;
}
else
{
drawOrderIndex = i + 1;
}
var objectList =
obj.Position.Z >= 0 ?
visibleObjectsBack :
(obj.Position.Z < -1 ? visibleObjectsFront : visibleObjectsMid);
if (objectList.Count >= MaxVisibleObjects) { continue; }
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);
if (objectList.Count >= MaxVisibleObjects) { break; }
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;
}
@@ -144,14 +165,14 @@ namespace Barotrauma
{
Rectangle indices = Rectangle.Empty;
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);
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;
if (indices.Width < 0) return;
if (indices.Width < 0) { return; }
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.Y = Math.Max(indices.Y, 0);
@@ -1,4 +1,4 @@
using FarseerPhysics;
using Barotrauma.Extensions;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
@@ -71,9 +71,12 @@ namespace Barotrauma
{
private static BasicEffect wallEdgeEffect, wallCenterEffect;
private Vector2 dustOffset;
private Vector2 defaultDustVelocity;
private Vector2 dustVelocity;
private Vector2 waterParticleOffset;
private Vector2 waterParticleVelocity;
private float flashCooldown;
private float flashTimer;
public Color FlashColor { get; private set; }
private readonly RasterizerState cullNone;
@@ -81,10 +84,26 @@ namespace Barotrauma
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)
{
defaultDustVelocity = Vector2.UnitY * 10.0f;
cullNone = new RasterizerState() { CullMode = CullMode.None };
if (wallEdgeEffect == null)
@@ -120,12 +139,50 @@ namespace Barotrauma
level.GenerationParams.WallSprite.ReloadTexture();
wallCenterEffect.Texture = level.GenerationParams.WallSprite.Texture;
}
public void Flash()
{
flashTimer = 1.0f;
}
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
//and use it to move the dust texture and water distortion effect
Vector2 currentDustVel = defaultDustVelocity;
//and use it to move the water texture and water distortion effect
Vector2 currentWaterParticleVel = level.GenerationParams.WaterParticleVelocity;
foreach (LevelObject levelObject in level.LevelObjectManager.GetVisibleObjects())
{
if (levelObject.Triggers == null) { continue; }
@@ -139,21 +196,21 @@ namespace Barotrauma
objectMaxFlow = vel;
}
}
currentDustVel += objectMaxFlow;
currentWaterParticleVel += objectMaxFlow;
}
waterParticleVelocity = Vector2.Lerp(waterParticleVelocity, currentWaterParticleVel, deltaTime);
dustVelocity = Vector2.Lerp(dustVelocity, currentDustVel, deltaTime);
WaterRenderer.Instance?.ScrollWater(dustVelocity, deltaTime);
WaterRenderer.Instance?.ScrollWater(waterParticleVelocity, deltaTime);
if (level.GenerationParams.WaterParticles != null)
{
Vector2 waterTextureSize = level.GenerationParams.WaterParticles.size * level.GenerationParams.WaterParticleScale;
dustOffset += new Vector2(dustVelocity.X, -dustVelocity.Y) * level.GenerationParams.WaterParticleScale * deltaTime;
while (dustOffset.X <= -waterTextureSize.X) dustOffset.X += waterTextureSize.X;
while (dustOffset.X >= waterTextureSize.X) dustOffset.X -= waterTextureSize.X;
while (dustOffset.Y <= -waterTextureSize.Y) dustOffset.Y += waterTextureSize.Y;
while (dustOffset.Y >= waterTextureSize.Y) dustOffset.Y -= waterTextureSize.Y;
waterParticleOffset += new Vector2(waterParticleVelocity.X, -waterParticleVelocity.Y) * level.GenerationParams.WaterParticleScale * deltaTime;
while (waterParticleOffset.X <= -waterTextureSize.X) { waterParticleOffset.X += waterTextureSize.X; }
while (waterParticleOffset.X >= waterTextureSize.X){ waterParticleOffset.X -= waterTextureSize.X; }
while (waterParticleOffset.Y <= -waterTextureSize.Y) { waterParticleOffset.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);
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 > 0.0f) offset.X -= srcRect.Width * textureScale;
while (offset.Y <= -srcRect.Height * textureScale) offset.Y += srcRect.Height * textureScale;
@@ -261,7 +318,7 @@ namespace Barotrauma
level.GenerationParams.WaterParticles.DrawTiled(
spriteBatch, origin + offsetS,
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();
@@ -1,8 +1,6 @@
using Barotrauma.Extensions;
using Barotrauma.Items.Components;
using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using SharpFont;
using System;
using System.Collections.Generic;
using System.Diagnostics;
@@ -12,28 +10,14 @@ namespace Barotrauma.Lights
{
class ConvexHullList
{
private List<ConvexHull> list;
public HashSet<ConvexHull> IsHidden;
public readonly Submarine Submarine;
public List<ConvexHull> List
{
get { return list; }
set
{
Debug.Assert(value != null);
Debug.Assert(!list.Contains(null));
list = value;
IsHidden.RemoveWhere(ch => !list.Contains(ch));
}
}
public HashSet<ConvexHull> IsHidden = new HashSet<ConvexHull>();
public readonly List<ConvexHull> List = new List<ConvexHull>();
public ConvexHullList(Submarine submarine)
{
Submarine = submarine;
list = new List<ConvexHull>();
IsHidden = new HashSet<ConvexHull>();
}
}
@@ -47,13 +31,13 @@ namespace Barotrauma.Lights
public bool IsHorizontal;
public bool IsAxisAligned;
public Vector2 SubmarineDrawPos;
public Segment(SegmentPoint start, SegmentPoint end, ConvexHull convexHull)
{
if (start.Pos.Y > end.Pos.Y)
{
var temp = start;
start = end;
end = temp;
(end, start) = (start, end);
}
Start = start;
@@ -102,14 +86,15 @@ namespace Barotrauma.Lights
private readonly Segment[] segments = new Segment[4];
private readonly SegmentPoint[] vertices = new SegmentPoint[4];
private readonly SegmentPoint[] losVertices = new SegmentPoint[4];
private readonly VectorPair[] losOffsets = new VectorPair[4];
private readonly bool[] backFacing;
private readonly bool[] ignoreEdge;
private readonly SegmentPoint[] losVertices = new SegmentPoint[2];
private readonly Vector2[] losOffsets = new Vector2[2];
private readonly bool isHorizontal;
private readonly int thickness;
public bool IsExteriorWall;
public VertexPositionColor[] ShadowVertices { get; private set; }
public VertexPositionTexture[] PenumbraVertices { get; private set; }
public int ShadowVertexCount { get; private set; }
@@ -145,47 +130,27 @@ namespace Barotrauma.Lights
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
};
}
if (penumbraEffect == null)
{
penumbraEffect = new BasicEffect(GameMain.Instance.GraphicsDevice)
penumbraEffect ??= new BasicEffect(GameMain.Instance.GraphicsDevice)
{
TextureEnabled = true,
LightingEnabled = false,
Texture = TextureLoader.FromFile("Content/Lights/penumbra.png")
};
}
ParentEntity = parent;
ShadowVertices = new VertexPositionColor[6 * 4];
PenumbraVertices = new VertexPositionTexture[6 * 4];
backFacing = new bool[4];
ignoreEdge = new bool[4];
BoundingBox = rect;
float minX = points[0].X, minY = points[0].Y, maxX = points[0].X, maxY = points[0].Y;
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;
this.isHorizontal = isHorizontal ?? BoundingBox.Width > BoundingBox.Height;
if (ParentEntity is Structure structure)
{
System.Diagnostics.Debug.Assert(!structure.Removed);
@@ -198,8 +163,26 @@ namespace Barotrauma.Lights
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;
var chList = HullLists.Find(h => h.Submarine == parent.Submarine);
@@ -211,257 +194,72 @@ namespace Barotrauma.Lights
foreach (ConvexHull ch in chList.List)
{
MergeOverlappingSegments(ch);
ch.MergeOverlappingSegments(this);
MergeLosVertices(ch);
ch.MergeLosVertices(this);
}
chList.List.Add(this);
}
private void MergeOverlappingSegments(ConvexHull ch)
private void MergeLosVertices(ConvexHull ch, bool refreshOtherOverlappingHulls = true)
{
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)
float mergeDist = 16;
float mergeDistSqr = mergeDist * mergeDist;
//find the closest point on the other convex hull segment
Vector2 closest = MathUtils.GetClosestPointOnLineSegment(
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);
int intersectionArea = intersection.Width * intersection.Height;
int bboxArea = BoundingBox.Width * BoundingBox.Height;
int otherBboxArea = ch.BoundingBox.Width * ch.BoundingBox.Height;
if (Math.Abs(intersectionArea - bboxArea) < mergeDistSqr) { return; }
if (Math.Abs(intersectionArea - otherBboxArea) < mergeDistSqr) { return; }
for (int i = 0; i < segments.Length; i++)
//find where the segments would intersect if they had infinite length
// if it's close to the closest point, let's use that instead to keep
// the direction of the segment unchanged (i.e. vertical segment stays vertical)
if (MathUtils.GetLineIntersection(
ch.losVertices[0].Pos + ch.losOffsets[0],
ch.losVertices[1].Pos + ch.losOffsets[1],
losVertices[0].Pos,
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; }
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);
}
}
}
}
for (int i = 0; i < segments.Length; i++)
{
if (ignoreEdge[i]) { continue; }
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; }
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);
}
closest = intersection;
}
}
losOffsets[i] = closest - losVertices[i].Pos;
overlappingHulls.Add(ch);
ch.overlappingHulls.Add(this);
changed = true;
}
//ignore edges that are inside some other convex hull
for (int i = 0; i < vertices.Length; i++)
if (changed && refreshOtherOverlappingHulls)
{
if (ch.IsPointInside(vertices[i].Pos))
foreach (var overlapping in overlappingHulls)
{
if (ch.IsPointInside(vertices[(i + 1) % vertices.Length].Pos))
{
ignoreEdge[i] = true;
overlappingHulls.Add(ch);
}
overlapping.MergeLosVertices(this, refreshOtherOverlappingHulls: false);
}
}
}
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)
{
Matrix rotationMatrix =
Matrix.CreateTranslation(-origin.X, -origin.Y, 0.0f) *
Matrix.CreateRotationZ(amount) *
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()
@@ -470,11 +268,10 @@ namespace Barotrauma.Lights
for (int i = 1; i < vertices.Length; i++)
{
if (vertices[i].Pos.X < minX) minX = vertices[i].Pos.X;
if (vertices[i].Pos.Y < minY) minY = vertices[i].Pos.Y;
if (vertices[i].Pos.X > maxX) maxX = vertices[i].Pos.X;
if (vertices[i].Pos.Y > minY) maxY = vertices[i].Pos.Y;
minX = Math.Min(minX, vertices[i].Pos.X);
minY = Math.Min(minY, vertices[i].Pos.Y);
maxX = Math.Max(maxX, vertices[i].Pos.X);
maxY = Math.Max(maxY, vertices[i].Pos.Y);
}
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++)
{
vertices[i].Pos += amount;
losVertices[i].Pos += amount;
losOffsets[i] = null;
segments[i].Start.Pos += amount;
segments[i].End.Pos += amount;
}
for (int i = 0; i < losVertices.Length; i++)
{
losVertices[i].Pos += amount;
}
LastVertexChangeTime = (float)Timing.TotalTime;
overlappingHulls.Clear();
for (int i = 0; i < 4; i++)
{
ignoreEdge[i] = false;
}
CalculateDimensions();
@@ -511,8 +304,8 @@ namespace Barotrauma.Lights
overlappingHulls.Clear();
foreach (ConvexHull ch in chList.List)
{
MergeOverlappingSegments(ch);
ch.MergeOverlappingSegments(this);
MergeLosVertices(ch);
ch.MergeLosVertices(this);
}
}
}
@@ -525,23 +318,23 @@ namespace Barotrauma.Lights
foreach (ConvexHull ch in chList.List)
{
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 j = i + 1; j < chList.List.Count; j++)
{
chList.List[i].MergeOverlappingSegments(chList.List[j]);
chList.List[j].MergeOverlappingSegments(chList.List[i]);
chList.List[i].MergeLosVertices(chList.List[j]);
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");
@@ -549,39 +342,23 @@ namespace Barotrauma.Lights
for (int i = 0; i < 4; i++)
{
vertices[i] = new SegmentPoint(points[i], this);
losVertices[i] = new SegmentPoint(points[i], this);
losOffsets[i] = null;
vertices[i] = new SegmentPoint(points[i], this);
}
for (int i = 0; i < 4; i++)
for (int i = 0; i < 2; i++)
{
ignoreEdge[i] = false;
losVertices[i] = new SegmentPoint(losPoints[i], this);
}
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)
{
for (int i = 0; i < vertices.Length; i++)
{
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);
}
}
@@ -602,7 +379,7 @@ namespace Barotrauma.Lights
overlappingHulls.Clear();
foreach (ConvexHull ch in chList.List)
{
MergeOverlappingSegments(ch);
MergeLosVertices(ch);
}
}
}
@@ -624,31 +401,17 @@ namespace Barotrauma.Lights
/// <summary>
/// Returns the segments that are facing towards viewPosition
/// </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++)
{
if (ignoreEdge[i] && ignoreEdges) continue;
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)
if (IsSegmentFacing(vertices[i].WorldPos, vertices[(i + 1) % 4].WorldPos, viewPosition))
{
visibleSegments.Add(segments[i]);
}
}
}
public void RefreshWorldPositions()
{
for (int i = 0; i < 4; i++)
@@ -676,34 +439,12 @@ namespace Barotrauma.Lights
ShadowVertexCount = 0;
//compute facing of each edge, using N*L
for (int i = 0; i < 4; i++)
for (int i = 0; i < losVertices.Length; 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;
Vector3 vertexPos0 = new Vector3(losOffsets[currentIndex]?.A ?? losVertices[currentIndex].Pos, 0.0f);
Vector3 vertexPos1 = new Vector3(losOffsets[currentIndex]?.B ?? losVertices[(currentIndex + 1) % 4].Pos, 0.0f);
int nextIndex = (currentIndex + 1) % 2;
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; }
@@ -754,9 +495,24 @@ namespace Barotrauma.Lights
ShadowVertexCount += 6;
}
if (IsSegmentFacing(losVertices[0].Pos, losVertices[1].Pos, lightSourcePos))
{
Array.Reverse(ShadowVertices);
}
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)
{
Vector3 offset = Vector3.Zero;
@@ -766,73 +522,101 @@ namespace Barotrauma.Lights
}
PenumbraVertexCount = 0;
for (int i = 0; i < 4; i++)
for (int i = 0; i < losVertices.Length; i++)
{
int currentIndex = i;
int prevIndex = (i + 3) % 4;
int nextIndex = (i + 1) % 4;
bool disjointed = losOffsets[i]?.A != null;
Vector2 vertexPos0 = losOffsets[currentIndex]?.A ?? losVertices[currentIndex].Pos;
Vector2 vertexPos1 = losOffsets[currentIndex]?.B ?? losVertices[nextIndex].Pos;
int nextIndex = (i + 1) % 2;
Vector2 vertexPos0 = losVertices[currentIndex].Pos + losOffsets[currentIndex];
Vector2 vertexPos1 = losVertices[nextIndex].Pos + losOffsets[nextIndex];
if (Vector2.DistanceSquared(vertexPos0, vertexPos1) < 1.0f) { continue; }
Vector3 penumbraStart = new Vector3(vertexPos0, 0.0f);
if (backFacing[currentIndex] && (disjointed || (!backFacing[prevIndex])))
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
{
Position = penumbraStart + offset,
TextureCoordinate = new Vector2(0.0f, 1.0f)
};
for (int j = 0; j < 2; j++)
{
PenumbraVertices[PenumbraVertexCount + j + 1] = new VertexPositionTexture();
Vector3 vertexDir = penumbraStart - new Vector3(lightSourcePos, 0);
vertexDir.Normalize();
for (int j = 0; j < 2; j++)
{
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;
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);
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;
PenumbraVertices[PenumbraVertexCount + j + 1].TextureCoordinate = (j == 0) ? new Vector2(0.05f, 0.0f) : new Vector2(1.0f, 0.0f);
}
disjointed = losOffsets[i]?.B != null;
if (backFacing[currentIndex] && (disjointed || (!backFacing[nextIndex])))
PenumbraVertexCount += 3;
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
{
Position = penumbraStart + offset,
TextureCoordinate = new Vector2(0.0f, 1.0f)
};
for (int j = 0; j < 2; j++)
{
PenumbraVertices[PenumbraVertexCount + (1 - j) + 1] = new VertexPositionTexture();
Vector3 vertexDir = penumbraStart - new Vector3(lightSourcePos, 0);
vertexDir.Normalize();
for (int j = 0; j < 2; j++)
{
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;
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);
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;
PenumbraVertices[PenumbraVertexCount + (1 - j) + 1].TextureCoordinate = (j == 0) ? new Vector2(0.05f, 0.0f) : new Vector2(1.0f, 0.0f);
}
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);
}
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);
foreach (ConvexHull ch in chList.List)
{
ch.MergeOverlappingSegments(ch2);
ch.MergeLosVertices(ch2);
}
}
}
@@ -5,6 +5,7 @@ using System.Linq;
using System;
using Barotrauma.Items.Components;
using Barotrauma.Extensions;
using System.Threading;
namespace Barotrauma.Lights
{
@@ -22,6 +23,9 @@ namespace Barotrauma.Lights
/// </summary>
const float ObstructLightsBehindCharactersZoomThreshold = 0.5f;
private Thread rayCastThread;
private Queue<RayCastTask> pendingRayCasts = new Queue<RayCastTask>();
public static Entity ViewTarget { get; set; }
private float currLightMapScale;
@@ -58,6 +62,8 @@ namespace Barotrauma.Lights
private readonly List<LightSource> lights;
public bool DebugLos;
public bool LosEnabled = true;
public float LosAlpha = 1f;
public LosMode LosMode = LosMode.Transparent;
@@ -68,6 +74,8 @@ namespace Barotrauma.Lights
private readonly Texture2D visionCircle;
private readonly Texture2D gapGlowTexture;
private Vector2 losOffset;
private int recalculationCount;
@@ -85,8 +93,16 @@ namespace Barotrauma.Lights
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");
highlightRaster = Sprite.LoadTexture("Content/UI/HighlightRaster.png");
gapGlowTexture = Sprite.LoadTexture("Content/Lights/pointlight_rays.png");
GameMain.Instance.ResolutionChanged += () =>
{
@@ -100,15 +116,12 @@ namespace Barotrauma.Lights
LosEffect = EffectLoader.Load("Effects/losshader");
SolidColorEffect = EffectLoader.Load("Effects/solidcolor");
if (lightEffect == null)
{
lightEffect = new BasicEffect(GameMain.Instance.GraphicsDevice)
lightEffect ??= new BasicEffect(GameMain.Instance.GraphicsDevice)
{
VertexColorEnabled = true,
TextureEnabled = true,
Texture = LightSource.LightTexture
};
}
});
}
@@ -155,7 +168,7 @@ namespace Barotrauma.Lights
{
foreach (LightSource light in lights)
{
light.NeedsHullCheck = true;
light.HullsUpToDate.Clear();
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)
{
if (!LightingEnabled) { return; }
@@ -287,8 +345,8 @@ namespace Barotrauma.Lights
foreach (LightSource light in activeLights)
{
if (!light.IsBackground || light.CurrentBrightness <= 0.0f) { continue; }
light.DrawSprite(spriteBatch, cam);
light.DrawLightVolume(spriteBatch, lightEffect, transform, recalculationCount < MaxLightVolumeRecalculationsPerFrame, ref recalculationCount);
light.DrawSprite(spriteBatch, cam);
}
GameMain.ParticleManager.Draw(spriteBatch, true, null, Particles.ParticleBlendState.Additive);
spriteBatch.End();
@@ -307,15 +365,46 @@ namespace Barotrauma.Lights
}
spriteBatch.End();
SolidColorEffect.CurrentTechnique = SolidColorEffect.Techniques["SolidColor"];
SolidColorEffect.Parameters["color"].SetValue(AmbientLight.Opaque().ToVector4());
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, transformMatrix: spriteBatchTransform, effect: SolidColorEffect);
Submarine.DrawDamageable(spriteBatch, null);
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive, transformMatrix: spriteBatchTransform);
Vector3 glowColorHSV = ToolBox.RGBToHSV(AmbientLight);
glowColorHSV.Z = Math.Max(glowColorHSV.Z, 0.4f);
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();
graphics.BlendState = BlendState.Additive;
//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)
//---------------------------------------------------------------------------------------------------
@@ -449,9 +538,9 @@ namespace Barotrauma.Lights
{
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);
}
@@ -565,7 +654,7 @@ namespace Barotrauma.Lights
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;
graphics.SetRenderTarget(LosTexture);
@@ -597,23 +686,30 @@ namespace Barotrauma.Lights
if (LosEnabled && LosMode != LosMode.None && ViewTarget != null)
{
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);
Matrix shadowTransform = cam.ShaderTransform
* 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)
{
List<VertexPositionColor> shadowVerts = new List<VertexPositionColor>();
List<VertexPositionTexture> penumbraVerts = new List<VertexPositionTexture>();
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;
if (convexHull.ParentEntity?.Submarine != null) relativeLightPos -= convexHull.ParentEntity.Submarine.Position;
if (convexHull.ParentEntity?.Submarine != null) { relativeLightPos -= convexHull.ParentEntity.Submarine.Position; }
convexHull.CalculateLosVertices(relativeLightPos);
@@ -646,6 +742,20 @@ namespace Barotrauma.Lights
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()
{
lights.Clear();
@@ -15,6 +15,7 @@ namespace Barotrauma.Lights
public bool Persistent;
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]
@@ -52,6 +53,10 @@ namespace Barotrauma.Lights
[Serialize(0f, IsPropertySaveable.Yes), Editable(MinValueFloat = -360, MaxValueFloat = 360, ValueStep = 1, DecimalCount = 0)]
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)));
private float flicker;
@@ -203,7 +208,7 @@ namespace Barotrauma.Lights
private VertexPositionColorTexture[] vertices;
private short[] indices;
private readonly List<ConvexHullList> hullsInRange;
private readonly List<ConvexHullList> convexHullsInRange;
public Texture2D texture;
@@ -222,9 +227,10 @@ namespace Barotrauma.Lights
private float prevCalculatedRange;
private Vector2 prevCalculatedPosition;
//do we need to recheck which convex hulls are within range
//(e.g. position or range of the lightsource has changed)
public bool NeedsHullCheck = true;
//Which submarines' convex hulls are up to date? Resets when the item moves/rotates relative to the submarine.
//Can contain null (means convex hulls that aren't part of any submarine).
public HashSet<Submarine> HullsUpToDate = new HashSet<Submarine>();
//do we need to recalculate the vertices of the light volume
private bool needsRecalculation;
public bool NeedsRecalculation
@@ -234,7 +240,7 @@ namespace Barotrauma.Lights
{
if (!needsRecalculation && value)
{
foreach (ConvexHullList chList in hullsInRange)
foreach (ConvexHullList chList in convexHullsInRange)
{
chList.IsHidden.Clear();
}
@@ -246,6 +252,18 @@ namespace Barotrauma.Lights
//when were the vertices of the light volume last calculated
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 DynamicVertexBuffer lightVolumeBuffer;
@@ -254,7 +272,6 @@ namespace Barotrauma.Lights
private int indexCount;
private Vector2 translateVertices;
private float rotateVertices;
private readonly LightSourceParams lightSourceParams;
@@ -277,7 +294,7 @@ namespace Barotrauma.Lights
return;
}
NeedsHullCheck = true;
HullsUpToDate.Clear();
NeedsRecalculation = true;
}
}
@@ -292,17 +309,20 @@ namespace Barotrauma.Lights
if (Math.Abs(value - rotation) < 0.001f) { return; }
rotation = value;
dir = new Vector2(MathF.Cos(rotation), -MathF.Sin(rotation));
if (Math.Abs(rotation - prevCalculatedRotation) < RotationRecalculationThreshold && vertices != null)
{
rotateVertices = rotation - prevCalculatedRotation;
return;
}
NeedsHullCheck = true;
HullsUpToDate.Clear();
NeedsRecalculation = true;
}
}
private Vector2 dir = Vector2.UnitX;
private Vector2 _spriteScale = Vector2.One;
public Vector2 SpriteScale
@@ -368,7 +388,7 @@ namespace Barotrauma.Lights
lightSourceParams.Range = value;
if (Math.Abs(prevCalculatedRange - lightSourceParams.Range) < 10.0f) return;
NeedsHullCheck = true;
HullsUpToDate.Clear();
NeedsRecalculation = true;
prevCalculatedRange = lightSourceParams.Range;
}
@@ -384,8 +404,8 @@ namespace Barotrauma.Lights
set
{
NeedsRecalculation = true;
NeedsHullCheck = true;
lightTextureTargetSize = value;
HullsUpToDate.Clear();
}
}
@@ -424,7 +444,7 @@ namespace Barotrauma.Lights
public bool Enabled = true;
private readonly ISerializableEntity conditionalTarget;
private readonly PropertyConditional.Comparison comparison;
private readonly PropertyConditional.LogicalOperatorType logicalOperator;
private readonly List<PropertyConditional> conditionals = new List<PropertyConditional>();
public LightSource(ContentXElement element, ISerializableEntity conditionalTarget = null)
@@ -432,11 +452,8 @@ namespace Barotrauma.Lights
{
lightSourceParams = new LightSourceParams(element);
CastShadows = element.GetAttributeBool("castshadows", true);
string comparison = element.GetAttributeString("comparison", null);
if (comparison != null)
{
Enum.TryParse(comparison, ignoreCase: true, out this.comparison);
}
logicalOperator = element.GetAttributeEnum(nameof(logicalOperator),
element.GetAttributeEnum("comparison", logicalOperator));
if (lightSourceParams.DeformableLightSpriteElement != null)
{
@@ -449,13 +466,7 @@ namespace Barotrauma.Lights
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "conditional":
foreach (XAttribute attribute in subElement.Attributes())
{
if (PropertyConditional.IsValid(attribute))
{
conditionals.Add(new PropertyConditional(attribute));
}
}
conditionals.AddRange(PropertyConditional.FromXElement(subElement));
break;
}
}
@@ -474,7 +485,7 @@ namespace Barotrauma.Lights
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.position = position;
lightSourceParams = new LightSourceParams(range, color);
@@ -515,19 +526,46 @@ namespace Barotrauma.Lights
/// </summary>
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; }
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>
/// Recheck which convex hulls are in range (if needed),
/// and check if we need to recalculate vertices due to changes in the convex hulls
/// </summary>
private void CheckHullsInRange()
private void CheckConvexHullsInRange()
{
foreach (Submarine sub in Submarine.Loaded)
{
@@ -540,21 +578,13 @@ namespace Barotrauma.Lights
private void CheckHullsInRange(Submarine sub)
{
//find the list of convexhulls that belong to the sub
ConvexHullList chList = null;
foreach (var ch in hullsInRange)
{
if (ch.Submarine == sub)
{
chList = ch;
break;
}
}
ConvexHullList chList = convexHullsInRange.FirstOrDefault(chList => chList.Submarine == sub);
//not found -> create one
if (chList == null)
{
chList = new ConvexHullList(sub);
hullsInRange.Add(chList);
convexHullsInRange.Add(chList);
NeedsRecalculation = true;
}
@@ -573,7 +603,7 @@ namespace Barotrauma.Lights
//light and the convexhulls are both outside
if (sub == null)
{
if (NeedsHullCheck)
if (!HullsUpToDate.Contains(null))
{
RefreshConvexHullList(chList, lightPos, null);
}
@@ -605,7 +635,7 @@ namespace Barotrauma.Lights
//light and convexhull are both inside the same sub
if (sub == ParentSub)
{
if (NeedsHullCheck)
if (!HullsUpToDate.Contains(sub))
{
RefreshConvexHullList(chList, lightPos, sub);
}
@@ -613,7 +643,7 @@ namespace Barotrauma.Lights
//light and convexhull are inside different subs
else
{
if (sub.DockedTo.Contains(ParentSub) && !NeedsHullCheck) { return; }
if (sub.DockedTo.Contains(ParentSub) && HullsUpToDate.Contains(sub)) { return; }
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;
if (ParentSub != null) { drawPos += ParentSub.DrawPosition; }
var hulls = new List<ConvexHull>();
foreach (ConvexHullList chList in hullsInRange)
visibleSegments.Clear();
foreach (ConvexHullList chList in convexHullsInRange)
{
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)
{
@@ -666,27 +722,19 @@ namespace Barotrauma.Lights
}
}
float bounds = TextureRange;
//find convexhull segments that are close enough and facing towards the light source
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);
}
state = LightVertexState.PendingRayCasts;
GameMain.LightManager.AddRayCastTask(this, drawPos, rotation);
}
//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;
float boundsExtended = bounds;
float boundsExtended = TextureRange;
if (OverrideLightTexture != null)
{
float cosAngle = (float)Math.Cos(Rotation);
float sinAngle = -(float)Math.Sin(Rotation);
float cosAngle = (float)Math.Cos(rotation);
float sinAngle = -(float)Math.Sin(rotation);
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;
}
var boundaryCorners = new SegmentPoint[] {
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),
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)
};
//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)
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++)
{
@@ -719,199 +771,200 @@ namespace Barotrauma.Lights
visibleSegments.Add(s);
}
//Generate new points at the intersections between segments
//This is necessary for the light volume to generate properly on some subs
for (int i = 0; i < visibleSegments.Count; i++)
lock (mutex)
{
Vector2 p1a = visibleSegments[i].Start.WorldPos;
Vector2 p1b = visibleSegments[i].End.WorldPos;
for (int j = i + 1; j < visibleSegments.Count; j++)
//Generate new points at the intersections between segments
//This is necessary for the light volume to generate properly on some subs
for (int i = 0; i < visibleSegments.Count; i++)
{
//ignore intersections between parallel axis-aligned segments
if (visibleSegments[i].IsAxisAligned && visibleSegments[j].IsAxisAligned &&
visibleSegments[i].IsHorizontal == visibleSegments[j].IsHorizontal)
{
continue;
}
Vector2 p1a = visibleSegments[i].Start.WorldPos;
Vector2 p1b = visibleSegments[i].End.WorldPos;
Vector2 p2a = visibleSegments[j].Start.WorldPos;
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)
for (int j = i + 1; j < visibleSegments.Count; j++)
{
continue;
}
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)
//ignore intersections between parallel axis-aligned segments
if (visibleSegments[i].IsAxisAligned && visibleSegments[j].IsAxisAligned &&
visibleSegments[i].IsHorizontal == visibleSegments[j].IsHorizontal)
{
continue;
}
Segment seg1 = new Segment(start, mid, visibleSegments[i].ConvexHull)
{
IsHorizontal = visibleSegments[i].IsHorizontal,
};
Vector2 p2a = visibleSegments[j].Start.WorldPos;
Vector2 p2b = visibleSegments[j].End.WorldPos;
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;
visibleSegments.Insert(i + 1, seg2);
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);
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--;
break;
}
else
{
points.Add(s.Start);
points.Add(s.End);
}
}
}
//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)
//remove points that are very close to each other
for (int i = 0; i < points.Count; i++)
{
visibleSegments.RemoveAt(i);
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);
}
}
}
else
var compareCCW = new CompareSegmentPointCW(drawPos);
try
{
points.Add(s.Start);
points.Add(s.End);
points.Sort(compareCCW);
}
}
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)
catch (Exception e)
{
sb.AppendLine(sp.Pos.ToString());
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);
}
DebugConsole.ThrowError(sb.ToString(), e);
}
List<Vector2> output = new List<Vector2>();
//List<Pair<int, Vector2>> preOutput = new List<Pair<int, Vector2>>();
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
}
}
//remove points that are very close to each other
for (int i = 0; i < points.Count; i++)
for (int i = 0; i < verts.Count - 1; i++)
{
for (int j = Math.Min(i + 4, points.Count-1); j > i; j--)
for (int j = Math.Min(i + 4, verts.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)
if (Math.Abs(verts[i].X - verts[j].X) < 6 &&
Math.Abs(verts[i].Y - verts[j].Y) < 6)
{
points.RemoveAt(j);
verts.RemoveAt(j);
}
}
}
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);
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
output.Add(p.WorldPos);
foreach (ConvexHullList hullList in hullsInRange)
{
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
output.Add(isPoint1 ? p.WorldPos : intersection1.pos);
output.Add(isPoint2 ? p.WorldPos : intersection2.pos);
foreach (ConvexHullList hullList in hullsInRange)
{
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
}
//remove points that are very close to each other
for (int i = 0; i < output.Count - 1; i++)
{
for (int j = Math.Min(i + 4, output.Count - 1); j > i; j--)
{
if (Math.Abs(output[i].X - output[j].X) < 6 &&
Math.Abs(output[i].Y - output[j].Y) < 6)
{
output.RemoveAt(j);
}
}
}
return output;
calculatedDrawPos = drawPos;
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;
int segment = -1;
@@ -936,13 +989,13 @@ namespace Barotrauma.Lights
//same for the x-axis
if (s.Start.WorldPos.X > s.End.WorldPos.X)
{
if (s.Start.WorldPos.X < minX) continue;
if (s.End.WorldPos.X > maxX) continue;
if (s.Start.WorldPos.X < minX) { continue; }
if (s.End.WorldPos.X > maxX) { continue; }
}
else
{
if (s.End.WorldPos.X < minX) continue;
if (s.Start.WorldPos.X > maxX) continue;
if (s.End.WorldPos.X < minX) { continue; }
if (s.Start.WorldPos.X > maxX) { continue; }
}
bool intersects;
@@ -986,14 +1039,11 @@ namespace Barotrauma.Lights
indices = new short[indexCount];
}
Vector2 drawPos = position;
if (ParentSub != null) { drawPos += ParentSub.DrawPosition; }
float cosAngle = (float)Math.Cos(Rotation);
float sinAngle = -(float)Math.Sin(Rotation);
Vector2 drawPos = calculatedDrawPos;
Vector2 uvOffset = Vector2.Zero;
Vector2 overrideTextureDims = Vector2.One;
Vector2 dir = this.dir;
if (OverrideLightTexture != null)
{
overrideTextureDims = new Vector2(OverrideLightTexture.SourceRect.Width, OverrideLightTexture.SourceRect.Height);
@@ -1002,8 +1052,7 @@ namespace Barotrauma.Lights
if (LightSpriteEffect == SpriteEffects.FlipHorizontally)
{
origin.X = OverrideLightTexture.SourceRect.Width - origin.X;
cosAngle = -cosAngle;
sinAngle = -sinAngle;
dir = -dir;
}
if (LightSpriteEffect == SpriteEffects.FlipVertically) { origin.Y = OverrideLightTexture.SourceRect.Height - origin.Y; }
uvOffset = (origin / overrideTextureDims) - new Vector2(0.5f, 0.5f);
@@ -1041,7 +1090,7 @@ namespace Barotrauma.Lights
//calculate normal of first segment
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));
//if the normal is pointing towards the light origin
//rather than away from it, invert it
@@ -1049,21 +1098,23 @@ namespace Barotrauma.Lights
//calculate normal of second segment
Vector2 nDiff2 = prevVertex - vertex;
tx = nDiff2.X; nDiff2.X = -nDiff2.Y; nDiff2.Y = tx;
nDiff2 /= Math.Max(Math.Abs(nDiff2.X),Math.Abs(nDiff2.Y));
nDiff2 = new Vector2(-nDiff2.Y, nDiff2.X);
nDiff2 /= Math.Max(Math.Abs(nDiff2.X), Math.Abs(nDiff2.Y));
//if the normal is pointing towards the light origin
//rather than away from it, invert it
if (Vector2.DistanceSquared(nDiff2, rawDiff) > Vector2.DistanceSquared(-nDiff2, rawDiff)) nDiff2 = -nDiff2;
//add the normals together and use some magic numbers to create
//a somewhat useful/good-looking blur
Vector2 nDiff = nDiff1 * 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))
float blurDistance = 40.0f;
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;
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
Vector2 originDiff = diff;
diff.X = originDiff.X * cosAngle - originDiff.Y * sinAngle;
diff.Y = originDiff.X * sinAngle + originDiff.Y * cosAngle;
diff.X = originDiff.X * dir.X - originDiff.Y * dir.Y;
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 += uvOffset;
}
@@ -1161,7 +1212,6 @@ namespace Barotrauma.Lights
}
translateVertices = Vector2.Zero;
rotateVertices = 0.0f;
prevCalculatedPosition = position;
prevCalculatedRotation = rotation;
}
@@ -1181,9 +1231,6 @@ namespace Barotrauma.Lights
}
drawPos.Y = -drawPos.Y;
float cosAngle = (float)Math.Cos(Rotation);
float sinAngle = -(float)Math.Sin(Rotation);
float bounds = TextureRange;
if (OverrideLightTexture != null)
@@ -1195,8 +1242,8 @@ namespace Barotrauma.Lights
origin /= Math.Max(overrideTextureDims.X, overrideTextureDims.Y);
origin *= TextureRange;
drawPos.X += origin.X * sinAngle + origin.Y * cosAngle;
drawPos.Y += origin.X * cosAngle + origin.Y * sinAngle;
drawPos.X += origin.X * dir.Y + origin.Y * dir.X;
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
@@ -1302,7 +1349,7 @@ namespace Barotrauma.Lights
{
if (conditionals.None()) { return; }
if (conditionalTarget == null) { return; }
if (comparison == PropertyConditional.Comparison.And)
if (logicalOperator == PropertyConditional.LogicalOperatorType.And)
{
Enabled = conditionals.All(c => c.Matches(conditionalTarget));
}
@@ -1335,35 +1382,43 @@ namespace Barotrauma.Lights
return;
}
CheckHullsInRange();
CheckConvexHullsInRange();
if (NeedsRecalculation && allowRecalculation)
{
recalculationCount++;
var verts = FindRaycastHits();
if (verts == null)
if (state == LightVertexState.UpToDate)
{
#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;
recalculationCount++;
FindRaycastHits();
}
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;
NeedsRecalculation = false;
LastRecalculationTime = (float)Timing.TotalTime;
NeedsRecalculation = false;
state = LightVertexState.UpToDate;
}
}
if (vertexCount == 0) { return; }
Vector2 offset = ParentSub == null ? Vector2.Zero : ParentSub.DrawPosition;
lightEffect.World =
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)) *
transform;
if (vertexCount == 0) { return; }
lightEffect.DiffuseColor = (new Vector3(Color.R, Color.G, Color.B) * (Color.A / 255.0f * CurrentBrightness)) / 255.0f;
if (OverrideLightTexture != null)
@@ -1387,9 +1442,9 @@ namespace Barotrauma.Lights
public void Reset()
{
hullsInRange.Clear();
HullsUpToDate.Clear();
convexHullsInRange.Clear();
diffToSub.Clear();
NeedsHullCheck = true;
NeedsRecalculation = true;
vertexCount = 0;
@@ -70,9 +70,9 @@ namespace Barotrauma
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 (linkedTo.Contains(entity))
{
@@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.Xna.Framework.Input;
using Barotrauma.Extensions;
namespace Barotrauma
{
@@ -72,6 +73,8 @@ namespace Barotrauma
private RichString beaconStationActiveText, beaconStationInactiveText;
private GUIComponent locationInfoOverlay;
/*private (Rectangle targetArea, string tip)? connectionTooltip;
private string sanitizedConnectionTooltip;
private List<RichTextData> connectionTooltipRichTextData;
@@ -98,7 +101,7 @@ namespace Barotrauma
OnClicked = (btn, userData) =>
{
Rand.SetSyncedSeed(ToolBox.StringToInt(this.Seed));
Generate(GameMain.GameSession.GameMode is CampaignMode campaign ? campaign.Settings : CampaignSettings.Empty);
Generate(GameMain.GameSession?.Campaign);
InitProjectSpecific();
return true;
}
@@ -186,7 +189,7 @@ namespace Barotrauma
private void LocationChanged(Location prevLocation, Location newLocation)
{
if (prevLocation == newLocation) return;
if (prevLocation == newLocation) { return; }
//focus on starting location
if (prevLocation != null)
{
@@ -210,11 +213,17 @@ namespace Barotrauma
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)
{
if (mapTiles == null) { return; }
if (location == null) { return; }
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)];
}
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)
{
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)]
.Replace("[previousname]", $"‖color:gui.orange‖{prevName}‖end‖")
.Replace("[name]", $"‖color:gui.orange‖{location.Name}‖end‖");
location.LastTypeChangeMessage = msg;
if (GameMain.Client != null)
Vector2 textPos = pos + new Vector2(notification.Offset, -notification.TextSize.Y / 2);
notification.Font.DrawStringWithColors(
spriteBatch,
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(
TextManager.Get("RadioAnnouncerName").Value,
msg,
Networking.ChatMessageType.Default,
sender: null);
mapNotifications.Add(new MapNotification(TextManager.Get(textTag).Value, GUIStyle.SubHeadingFont, mapNotifications, relatedLocation: 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()
@@ -280,12 +485,13 @@ namespace Barotrauma
mapAnimQueue.Clear();
}
public void Update(float deltaTime, GUICustomComponent mapContainer)
public void Update(CampaignMode campaign, float deltaTime, GUICustomComponent mapContainer)
{
Rectangle rect = mapContainer.Rect;
var currentDisplayLocation = GameMain.GameSession?.Campaign?.GetCurrentDisplayLocation();
UpdateNotifications(deltaTime, mapContainer);
var currentDisplayLocation = campaign?.GetCurrentDisplayLocation();
if (currentDisplayLocation != null)
{
if (!currentDisplayLocation.Discovered)
@@ -345,10 +551,39 @@ namespace Barotrauma
Vector2 rectCenter = new Vector2(rect.Center.X, rect.Center.Y);
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;
HighlightedLocation = null;
if (GUI.MouseOn == null || GUI.MouseOn == mapContainer)
if ((GUI.MouseOn == null || GUI.MouseOn == mapContainer))
{
for (int i = 0; i < Locations.Count; i++)
{
@@ -374,7 +609,7 @@ namespace Barotrauma
if (HighlightedLocation == null || dist < closestDist)
{
closestDist = dist;
HighlightedLocation = location;
HighlightedLocation = location;
}
}
}
@@ -453,12 +688,13 @@ namespace Barotrauma
Level.Loaded.DebugSetEndLocation(null);
Discover(CurrentLocation);
Visit(CurrentLocation);
OnLocationChanged?.Invoke(new LocationChangeInfo(prevLocation, CurrentLocation));
SelectLocation(-1);
if (GameMain.Client == null)
{
CurrentLocation.CreateStores();
ProgressWorld();
ProgressWorld(campaign);
Radiation?.OnStep(1);
}
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)
{
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;
var currentDisplayLocation = GameMain.GameSession?.Campaign?.GetCurrentDisplayLocation();
var currentDisplayLocation = campaign?.GetCurrentDisplayLocation();
Rectangle rect = mapContainer.Rect;
@@ -501,13 +731,15 @@ namespace Barotrauma
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;
spriteBatch.End();
spriteBatch.GraphicsDevice.ScissorRectangle = Rectangle.Intersect(prevScissorRect, rect);
spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
Vector2 topLeft = rectCenter + viewOffset;
Vector2 bottomRight = rectCenter + (viewOffset + new Vector2(Width, Height));
Vector2 topLeft = rectCenter + viewOffset - rect.Location.ToVector2();
Vector2 bottomRight = topLeft + new Vector2(Width, Height);
Vector2 mapTileSize = mapTiles[0, 0].size * generationParams.MapTileScale;
int startX = (int)Math.Floor(-topLeft.X / mapTileSize.X) - 1;
@@ -568,7 +800,9 @@ namespace Barotrauma
for (int i = 0; i < Locations.Count; 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;
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.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; }
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)
{
color *= 0.5f;
}
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);
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 (SelectedLocation != null)
@@ -626,7 +890,10 @@ namespace Barotrauma
{
Vector2 typeChangeIconPos = pos + new Vector2(1.35f, -0.35f) * generationParams.LocationIconSize * 0.5f * zoom;
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 &&
(tooltip == null || IsPreferredTooltip(typeChangeIconPos)))
{
@@ -635,14 +902,17 @@ namespace Barotrauma
}
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;
float missionIconScale = 18.0f / generationParams.MissionIcon.SourceRect.Width;
generationParams.MissionIcon.Draw(spriteBatch, missionIconPos, generationParams.IndicatorColor, scale: missionIconScale * zoom);
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)));
}
}
@@ -651,23 +921,19 @@ namespace Barotrauma
if (GameMain.DebugDraw)
{
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;
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);
bgRect.Inflate(8,8);
Color barColor = ToolBox.GradientLerp(location.Reputation.NormalizedValue, Color.Red, Color.Yellow, Color.LightGreen);
GUI.DrawRectangle(spriteBatch, bgRect, Color.Black * 0.8f, isFilled: true);
GUI.DrawRectangle(spriteBatch, new Rectangle((int)dPos.X, (int)dPos.Y, (int)(location.Reputation.NormalizedValue * 255), 32), barColor, isFilled: true);
string reputationValue = ((int)location.Reputation.Value).ToString();
Vector2 repValueSize = GUIStyle.SubHeadingFont.MeasureString(reputationValue);
GUI.DrawString(spriteBatch, dPos + (new Vector2(256, 32) / 2) - (repValueSize / 2), reputationValue, Color.White, Color.Black, font: GUIStyle.SubHeadingFont);
GUI.DrawRectangle(spriteBatch, new Rectangle((int)dPos.X, (int)dPos.Y, 256, 32), Color.White);
if (PlayerInput.KeyDown(Keys.LeftShift))
{
GUI.DrawString(spriteBatch, new Vector2(150,150), "Dist: " +
GetDistanceToClosestLocationOrConnection(CurrentLocation, int.MaxValue, loc => loc == location), Color.White, Color.Black, font: GUIStyle.SubHeadingFont);
}
}
dPos.Y += 48;
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);
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)
{
@@ -892,7 +1067,7 @@ namespace Barotrauma
}
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]))
{
@@ -961,25 +1136,25 @@ namespace Barotrauma
if (connection.Locked)
{
var gateLocation = connection.Locations[0].IsGateBetweenBiomes ? connection.Locations[0] : connection.Locations[1];
var unlockEvent =
EventPrefab.Prefabs.FirstOrDefault(ep => ep.UnlockPathEvent && ep.BiomeIdentifier == gateLocation.LevelData.Biome.Identifier) ??
EventPrefab.Prefabs.FirstOrDefault(ep => ep.UnlockPathEvent && ep.BiomeIdentifier == Identifier.Empty);
var unlockEvent = EventPrefab.GetUnlockPathEvent(gateLocation.LevelData.Biome.Identifier, gateLocation.Faction);
if (unlockEvent != null)
{
Reputation unlockReputation = CurrentLocation.Reputation;
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;
}
DrawIcon(
"LockedLocationConnection", (int)(28 * zoom),
RichString.Rich(TextManager.GetWithVariables(unlockEvent.UnlockPathTooltip ?? "LockedPathTooltip",
("[requiredreputation]", Reputation.GetFormattedReputationText(MathUtils.InverseLerp(unlockReputation.MinReputation, unlockReputation.MaxReputation, unlockEvent.UnlockPathReputation), unlockEvent.UnlockPathReputation, addColorTags: true)),
("[currentreputation]", unlockReputation.GetFormattedReputationText(addColorTags: true)))));
if (unlockReputation != null)
{
DrawIcon(
"LockedLocationConnection", (int)(28 * zoom),
RichString.Rich(TextManager.GetWithVariables(unlockEvent.UnlockPathTooltip ?? "LockedPathTooltip",
("[requiredreputation]", Reputation.GetFormattedReputationText(MathUtils.InverseLerp(unlockReputation.MinReputation, unlockReputation.MaxReputation, unlockEvent.UnlockPathReputation), unlockEvent.UnlockPathReputation, addColorTags: true)),
("[currentreputation]", unlockReputation.GetFormattedReputationText(addColorTags: true)))));
}
}
else
{
@@ -1042,13 +1217,14 @@ namespace Barotrauma
private void DrawDecorativeHUD(SpriteBatch spriteBatch, Rectangle rect)
{
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,
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);
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);
}
@@ -36,7 +36,7 @@ namespace Barotrauma
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;
@@ -99,11 +99,24 @@ namespace Barotrauma
{
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
+ (sprite?.Texture?.SortingKey ?? 0) % 100 * 0.00001f
+ ID % 100 * 0.000001f;
+ (sprite?.Texture?.SortingKey ?? 0) % 100 * 0.000001f
+ ID % 100 * 0.0000001f;
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>
/// Update the selection logic in submarine editor
/// </summary>
@@ -118,10 +131,7 @@ namespace Barotrauma
return;
}
foreach (MapEntity e in mapEntityList)
{
e.isHighlighted = false;
}
ClearHighlightedEntities();
if (DisableSelect)
{
@@ -249,11 +259,10 @@ namespace Barotrauma
if (i == 0) highLightedEntity = e;
}
}
UpdateHighlighting(highlightedEntities);
}
if (highLightedEntity != null) highLightedEntity.isHighlighted = true;
if (highLightedEntity != null) { highLightedEntity.IsHighlighted = true; }
}
if (GUI.KeyboardDispatcher.Subscriber == null)
@@ -275,7 +284,6 @@ namespace Barotrauma
if (startMovingPos != Vector2.Zero)
{
Item targetContainer = GetPotentialContainer(position, SelectedList);
if (targetContainer != null) { targetContainer.IsHighlighted = true; }
if (PlayerInput.PrimaryMouseButtonReleased())
@@ -597,10 +605,10 @@ namespace Barotrauma
if (highlightedListBox != null)
{
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)
{
@@ -1083,7 +1091,7 @@ namespace Barotrauma
private void UpdateResizing(Camera cam)
{
isHighlighted = true;
IsHighlighted = true;
int startX = ResizeHorizontal ? -1 : 0;
int StartY = ResizeVertical ? -1 : 0;
@@ -1184,7 +1192,7 @@ namespace Barotrauma
private void DrawResizing(SpriteBatch spriteBatch, Camera cam)
{
isHighlighted = true;
IsHighlighted = true;
int startX = ResizeHorizontal ? -1 : 0;
int StartY = ResizeVertical ? -1 : 0;
@@ -55,21 +55,14 @@ namespace Barotrauma
{
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>();
}
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),
IsExteriorWall = IsExteriorWall
};
var h = new ConvexHull(verts, Color.Black, this);
if (Math.Abs(rotation) > 0.001f)
{
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);
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.Y > worldView.Y || max.Y < worldView.Y - worldView.Height) { return false; }
@@ -295,6 +291,7 @@ namespace Barotrauma
if (isWiringMode) { color *= 0.15f; }
Vector2 drawOffset = Submarine == null ? Vector2.Zero : Submarine.DrawPosition;
drawOffset += GetCollapseEffectOffset();
float depth = GetDrawDepth();
@@ -504,7 +501,7 @@ namespace Barotrauma
private bool ConditionalMatches(PropertyConditional conditional)
{
if (!string.IsNullOrEmpty(conditional.TargetItemComponentName))
if (!string.IsNullOrEmpty(conditional.TargetItemComponent))
{
return false;
}
@@ -533,7 +530,7 @@ namespace Barotrauma
float damage = msg.ReadRangedSingle(0.0f, 1.0f, 8) * MaxHealth;
if (!invalidMessage && i < Sections.Length)
{
SetDamage(i, damage);
SetDamage(i, damage, isNetworkEvent: true);
}
}
}
@@ -35,6 +35,13 @@ namespace Barotrauma
Rectangle camView = cam.WorldView;
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 &&
Math.Abs(camView.Y - prevCullArea.Y) < CullMoveThreshold &&
Math.Abs(camView.Right - prevCullArea.Right) < CullMoveThreshold &&
@@ -116,7 +116,7 @@ namespace Barotrauma
bool isMouseOnComponent = GUI.MouseOn == component;
camera.MoveCamera(deltaTime, allowZoom: isMouseOnComponent, followSub: false);
if (isMouseOnComponent &&
(PlayerInput.MidButtonHeld() || PlayerInput.LeftButtonHeld()))
(PlayerInput.MidButtonHeld() || PlayerInput.PrimaryMouseButtonHeld()))
{
Vector2 moveSpeed = PlayerInput.MouseSpeed * (float)deltaTime * 60.0f / camera.Zoom;
moveSpeed.X = -moveSpeed.X;
@@ -39,7 +39,7 @@ namespace Barotrauma
{
Color clr = CurrentHull == null ? Color.DodgerBlue : GUIStyle.Green;
if (spawnType != SpawnType.Path) { clr = Color.Gray; }
if (isObstructed)
if (!IsTraversable)
{
clr = Color.Black;
}
@@ -84,7 +84,7 @@ namespace Barotrauma
GUI.DrawLine(spriteBatch,
drawPos,
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)
{
@@ -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,
ID.ToString(),
new Vector2(DrawPosition.X - 10, -DrawPosition.Y - 30),
@@ -170,9 +175,9 @@ namespace Barotrauma
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))
{
@@ -251,6 +256,7 @@ namespace Barotrauma
private bool ChangeSpawnType(GUIButton button, object obj)
{
var prevSpawnType = spawnType;
GUITextBlock spawnTypeText = button.Parent.GetChildByUserData("spawntypetext") as GUITextBlock;
var values = (SpawnType[])Enum.GetValues(typeof(SpawnType));
int currIndex = values.IndexOf(spawnType);
@@ -267,6 +273,7 @@ namespace Barotrauma
}
spawnType = values[currIndex];
spawnTypeText.Text = spawnType.ToString();
if (spawnType == SpawnType.ExitPoint || prevSpawnType == SpawnType.ExitPoint) { CreateEditingHUD(); }
return true;
}
@@ -412,6 +419,28 @@ namespace Barotrauma
textBox.Text = string.Join(",", tags);
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(
@@ -75,7 +75,7 @@ namespace Barotrauma.Networking
float gain = 1.0f;
float noiseGain = 0.0f;
Vector3? position = null;
if (character != null)
if (character != null && !character.IsDead)
{
if (GameSettings.CurrentConfig.Audio.UseDirectionalVoiceChat)
{
@@ -332,7 +332,6 @@ namespace Barotrauma.Networking
FileSize = 0
};
Md5Hash.Cache.Remove(directTransfer.FilePath);
OnFinished(directTransfer);
}
break;
@@ -414,7 +413,6 @@ namespace Barotrauma.Networking
{
finishedTransfers.Add((transferId, Timing.TotalTime));
StopTransfer(activeTransfer);
Md5Hash.Cache.Remove(activeTransfer.FilePath);
OnFinished(activeTransfer);
}
else
@@ -76,6 +76,8 @@ namespace Barotrauma.Networking
Interrupted
}
private UInt16? debugStartGameCampaignSaveID;
private RoundInitStatus roundInitStatus = RoundInitStatus.NotStarted;
public bool RoundStarting => roundInitStatus == RoundInitStatus.Starting || roundInitStatus == RoundInitStatus.WaitingForStartGameFinalize;
@@ -326,7 +328,7 @@ namespace Barotrauma.Networking
return serverEndpoint switch
{
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),
_ => throw new ArgumentOutOfRangeException()
};
@@ -859,17 +861,24 @@ namespace Barotrauma.Networking
ContentFile file = ContentPackageManager.EnabledPackages.All
.Select(p =>
p.Files.FirstOrDefault(f => f.Path == filePath))
.FirstOrDefault(f => !(f is null));
.FirstOrDefault(f => f is not null);
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);
int subEqualityCheckValue = inc.ReadInt32();
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." +
" There may have been an error in receiving the up-to-date submarine file from the server.";
string errorMsg =
"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);
throw new Exception(errorMsg);
}
@@ -886,7 +895,7 @@ namespace Barotrauma.Networking
$"Mission equality check failed. Mission count doesn't match the server. " +
$"Server: {string.Join(", ", serverMissionIdentifiers)}, " +
$"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);
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 " +
$"Server: {string.Join(", ", serverMissionIdentifiers)}, " +
$"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);
throw new Exception(errorMsg);
}
@@ -922,7 +931,7 @@ namespace Barotrauma.Networking
", level value count: " + levelEqualityCheckValues.Count +
", seed: " + Level.Loaded.Seed +
", 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);
throw new Exception(errorMsg);
}
@@ -988,7 +997,7 @@ namespace Barotrauma.Networking
GameMain.ModDownloadScreen.Reset();
ContentPackageManager.EnabledPackages.Restore();
CampaignMode.StartRoundCancellationToken?.Cancel();
GameMain.GameSession?.Campaign?.CancelStartRound();
if (SteamManager.IsInitialized)
{
@@ -1322,6 +1331,8 @@ namespace Barotrauma.Networking
eventErrorWritten = false;
GameMain.NetLobbyScreen.StopWaitingForStartRound();
debugStartGameCampaignSaveID = null;
while (CoroutineManager.IsCoroutineRunning("EndGame"))
{
EndCinematic?.Stop();
@@ -1471,19 +1482,12 @@ namespace Barotrauma.Networking
roundInitStatus = RoundInitStatus.Interrupted;
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;
DateTime saveFileTimeOut = DateTime.Now + new TimeSpan(0,0,60);
DateTime saveFileTimeOut = DateTime.Now + new TimeSpan(0, 0, 60);
while (NetIdUtils.IdMoreRecent(campaignSaveID, campaign.LastSaveID))
{
if (DateTime.Now > saveFileTimeOut)
@@ -1494,16 +1498,27 @@ namespace Barotrauma.Networking
roundInitStatus = RoundInitStatus.Interrupted;
yield return CoroutineStatus.Failure;
}
yield return new WaitForSeconds(0.1f);
yield return new WaitForSeconds(0.1f);
}
}
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);
LevelData levelData = nextLocationIndex > -1 ?
campaign.Map.Locations[nextLocationIndex].LevelData :
campaign.Map.Connections[nextConnectionIndex].LevelData;
debugStartGameCampaignSaveID = campaign.LastSaveID;
if (roundSummary != null)
{
loadTask = campaign.SelectSummaryScreen(roundSummary, levelData, mirrorLevel, null);
@@ -2587,31 +2602,24 @@ namespace Barotrauma.Networking
public void WriteCharacterInfo(IWriteMessage msg, string newName = null)
{
msg.WriteBoolean(characterInfo == null);
msg.WritePadBits();
if (characterInfo == null) { return; }
msg.WriteString(newName ?? string.Empty);
var head = characterInfo.Head;
msg.WriteByte((byte)characterInfo.Head.Preset.TagSet.Count);
foreach (Identifier tag in characterInfo.Head.Preset.TagSet)
{
msg.WriteIdentifier(tag);
}
msg.WriteByte((byte)characterInfo.Head.HairIndex);
msg.WriteByte((byte)characterInfo.Head.BeardIndex);
msg.WriteByte((byte)characterInfo.Head.MoustacheIndex);
msg.WriteByte((byte)characterInfo.Head.FaceAttachmentIndex);
msg.WriteColorR8G8B8(characterInfo.Head.SkinColor);
msg.WriteColorR8G8B8(characterInfo.Head.HairColor);
msg.WriteColorR8G8B8(characterInfo.Head.FacialHairColor);
var netInfo = new NetCharacterInfo(
NewName: newName ?? string.Empty,
Tags: head.Preset.TagSet.ToImmutableArray(),
HairIndex: (byte)head.HairIndex,
BeardIndex: (byte)head.BeardIndex,
MoustacheIndex: (byte)head.MoustacheIndex,
FaceAttachmentIndex: (byte)head.FaceAttachmentIndex,
SkinColor: head.SkinColor,
HairColor: head.HairColor,
FacialHairColor: head.FacialHairColor,
JobVariants: GameMain.NetLobbyScreen.JobPreferences.Select(NetJobVariant.FromJobVariant).ToImmutableArray());
var jobPreferences = GameMain.NetLobbyScreen.JobPreferences;
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);
}
msg.WriteNetSerializableStruct(netInfo);
}
public void Vote(VoteType voteType, object data)
@@ -2623,7 +2631,13 @@ namespace Barotrauma.Networking
using (var segmentTable = SegmentTableWriter<ClientNetSegment>.StartWriting(msg))
{
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);
@@ -92,10 +92,10 @@ namespace Barotrauma.Networking
Name = GameMain.Client.Name,
OwnerKey = ownerKey,
SteamId = SteamManager.GetSteamId().Select(id => (AccountId)id),
SteamAuthTicket = steamAuthTicket switch
SteamAuthTicket = steamAuthTicket?.Data switch
{
null => Option<byte[]>.None(),
var ticket => Option<byte[]>.Some(ticket.Data)
var ticketData => Option<byte[]>.Some(ticketData)
},
GameVersion = GameMain.Version.ToString(),
Language = GameSettings.CurrentConfig.Language.Value
@@ -156,7 +156,7 @@ namespace Barotrauma.Networking
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);
if (authSessionStartState != Steamworks.BeginAuthResult.OK)
@@ -1,5 +1,6 @@
#nullable enable
using Barotrauma.Extensions;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
@@ -69,6 +70,9 @@ namespace Barotrauma.Networking
[Serialize(PlayStyle.Casual, IsPropertySaveable.Yes)]
public PlayStyle PlayStyle { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
public LanguageIdentifier Language { get; set; }
public Version GameVersion { get; set; } = new Version(0, 0, 0, 0);
@@ -281,7 +285,7 @@ namespace Barotrauma.Networking
// -----------------------------------------------------------------------------
float elementHeight = 0.075f;
const float elementHeight = 0.075f;
// Spacing
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(); };
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"));
new GUITextBlock(new RectTransform(Vector2.One, gameMode.RectTransform),
TextManager.Get(GameMode.IsEmpty ? "Unknown" : "GameMode." + GameMode).Fallback(GameMode.Value),
@@ -363,7 +372,7 @@ namespace Barotrauma.Networking
packageText.Selected = true;
}
//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);
}
@@ -417,8 +426,9 @@ namespace Barotrauma.Networking
GameMode = valueGetter("gamemode")?.ToIdentifier() ?? Identifier.Empty;
if (Enum.TryParse(valueGetter("traitors"), out YesNoMaybe traitorsEnabled)) { TraitorsEnabled = traitorsEnabled; }
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)
{
@@ -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? joinedHashes = valueGetter("contentpackagehash");
string? joinedWorkshopIds = valueGetter("contentpackageid");
@@ -438,9 +474,11 @@ namespace Barotrauma.Networking
#warning TODO: genericize
ulong[] contentPackageIds = joinedWorkshopIds.IsNullOrEmpty() ? new ulong[1] : SteamManager.ParseWorkshopIds(joinedWorkshopIds).ToArray();
if (contentPackageNames.Length != contentPackageHashes.Length
|| contentPackageHashes.Length != contentPackageIds.Length)
if (contentPackageNames.Length != contentPackageHashes.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>();
}
@@ -35,7 +35,7 @@ namespace Barotrauma
}
private static Option<ServerInfo> InfoFromListEntry(Steamworks.Data.ServerInfo entry) =>
entry.Name.IsNullOrEmpty()
entry.Name.IsNullOrEmpty() || entry.Address is null
? Option<ServerInfo>.None()
: Option<ServerInfo>.Some(new ServerInfo(new LidgrenEndpoint(entry.Address, entry.ConnectionPort))
{
@@ -71,10 +71,10 @@ namespace Barotrauma
foreach (var lobby in lobbies)
{
string lobbyOwnerStr = lobby.GetData("lobbyowner");
string lobbyOwnerStr = lobby.GetData("lobbyowner") ?? "";
lobbyQuery = lobbyQuery.WithoutKeyValue("lobbyowner", lobbyOwnerStr);
string serverName = lobby.GetData("name");
string serverName = lobby.GetData("name") ?? "";
if (string.IsNullOrEmpty(serverName)) { continue; }
var ownerId = SteamId.Parse(lobbyOwnerStr);

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