Faction Test v1.0.1.0

This commit is contained in:
Regalis11
2023-02-16 15:01:28 +02:00
parent caa5a2f762
commit 2c5a7923b0
309 changed files with 7502 additions and 4335 deletions
+1
View File
@@ -2,3 +2,4 @@
*.sln text eol=crlf *.sln text eol=crlf
*.cs text eol=crlf *.cs text eol=crlf
*.xml text eol=crlf *.xml text eol=crlf
Barotrauma\BarotraumaServer\DedicatedServer.exe text eol=lf
@@ -33,6 +33,8 @@ namespace Barotrauma
public bool AllowInterrupt = false; public bool AllowInterrupt = false;
public bool RemoveControlFromCharacter = true; public bool RemoveControlFromCharacter = true;
public bool RunWhilePaused = true;
public CameraTransition(ISpatialEntity targetEntity, Camera cam, Alignment? cameraStartPos, Alignment? cameraEndPos, bool fadeOut = true, bool losFadeIn = false, float waitDuration = 0f, float panDuration = 10.0f, float? startZoom = null, float? endZoom = null) public CameraTransition(ISpatialEntity targetEntity, Camera cam, Alignment? cameraStartPos, Alignment? cameraEndPos, bool fadeOut = true, bool losFadeIn = false, float waitDuration = 0f, float panDuration = 10.0f, float? startZoom = null, float? endZoom = null)
{ {
WaitDuration = waitDuration; WaitDuration = waitDuration;
@@ -76,6 +78,8 @@ namespace Barotrauma
#endif #endif
} }
private float DeltaTime => CoroutineManager.Paused && !RunWhilePaused ? 0 : CoroutineManager.DeltaTime;
private IEnumerable<CoroutineStatus> Update(ISpatialEntity targetEntity, Camera cam) private IEnumerable<CoroutineStatus> Update(ISpatialEntity targetEntity, Camera cam)
{ {
if (targetEntity == null || (targetEntity is Entity e && e.Removed)) { yield return CoroutineStatus.Success; } if (targetEntity == null || (targetEntity is Entity e && e.Removed)) { yield return CoroutineStatus.Success; }
@@ -169,12 +173,15 @@ namespace Barotrauma
} }
if (LosFadeIn && clampedTimer / PanDuration > 0.8f) if (LosFadeIn && clampedTimer / PanDuration > 0.8f)
{ {
GameMain.LightManager.LosAlpha = ((clampedTimer / PanDuration) - 0.8f) * 5.0f; if (!GameMain.DevMode)
{
GameMain.LightManager.LosEnabled = true;
GameMain.LightManager.LosAlpha = ((clampedTimer / PanDuration) - 0.8f) * 5.0f;
}
Lights.LightManager.ViewTarget = prevControlled ?? (targetEntity as Entity); Lights.LightManager.ViewTarget = prevControlled ?? (targetEntity as Entity);
GameMain.LightManager.LosEnabled = true;
} }
#endif #endif
timer += CoroutineManager.DeltaTime; timer += DeltaTime;
yield return CoroutineStatus.Running; yield return CoroutineStatus.Running;
} }
@@ -184,7 +191,7 @@ namespace Barotrauma
{ {
cam.Translate(endPos - cam.Position); cam.Translate(endPos - cam.Position);
cam.Zoom = endZoom; cam.Zoom = endZoom;
endTimer += CoroutineManager.DeltaTime; endTimer += DeltaTime;
yield return CoroutineStatus.Running; yield return CoroutineStatus.Running;
} }
@@ -192,8 +199,11 @@ namespace Barotrauma
#if CLIENT #if CLIENT
GUI.ScreenOverlayColor = Color.TransparentBlack; GUI.ScreenOverlayColor = Color.TransparentBlack;
GameMain.LightManager.LosEnabled = true; if (!GameMain.DevMode)
GameMain.LightManager.LosAlpha = 1f; {
GameMain.LightManager.LosEnabled = true;
GameMain.LightManager.LosAlpha = 1f;
}
#endif #endif
if (prevControlled != null && !prevControlled.Removed) if (prevControlled != null && !prevControlled.Removed)
@@ -109,7 +109,7 @@ namespace Barotrauma
} }
float distSqrd = Vector2.DistanceSquared(newPosition, Collider.SimPosition); float distSqrd = Vector2.DistanceSquared(newPosition, Collider.SimPosition);
float errorTolerance = character.CanMove ? 0.01f : 0.2f; float errorTolerance = character.CanMove && !character.IsRagdolled ? 0.01f : 0.2f;
if (distSqrd > errorTolerance) if (distSqrd > errorTolerance)
{ {
if (distSqrd > 10.0f || !character.CanMove) if (distSqrd > 10.0f || !character.CanMove)
@@ -147,6 +147,7 @@ namespace Barotrauma
{ {
MainLimb.PullJointWorldAnchorB = Collider.SimPosition; MainLimb.PullJointWorldAnchorB = Collider.SimPosition;
MainLimb.PullJointEnabled = true; MainLimb.PullJointEnabled = true;
MainLimb.body.LinearVelocity = newVelocity;
} }
} }
} }
@@ -298,6 +298,21 @@ namespace Barotrauma
{ {
keys[i].SetState(); keys[i].SetState();
} }
if (CharacterInventory.IsMouseOnInventory && CharacterHUD.ShouldDrawInventory(this))
{
ResetInputIfPrimaryMouse(InputType.Use);
ResetInputIfPrimaryMouse(InputType.Shoot);
ResetInputIfPrimaryMouse(InputType.Select);
void ResetInputIfPrimaryMouse(InputType inputType)
{
if (GameSettings.CurrentConfig.KeyMap.Bindings[inputType].MouseButton == MouseButton.PrimaryMouse)
{
keys[(int)inputType].Reset();
}
}
}
//if we were firing (= pressing the aim and shoot keys at the same time) //if we were firing (= pressing the aim and shoot keys at the same time)
//and the fire key is the same as Select or Use, reset the key to prevent accidentally selecting/using items //and the fire key is the same as Select or Use, reset the key to prevent accidentally selecting/using items
if (wasFiring && !keys[(int)InputType.Shoot].Held) if (wasFiring && !keys[(int)InputType.Shoot].Held)
@@ -316,8 +331,7 @@ namespace Barotrauma
float targetOffsetAmount = 0.0f; float targetOffsetAmount = 0.0f;
if (moveCam) if (moveCam)
{ {
if (NeedsAir && !IsProtectedFromPressure() && if (!IsProtectedFromPressure && (AnimController.CurrentHull == null || AnimController.CurrentHull.LethalPressure > 0.0f))
(AnimController.CurrentHull == null || AnimController.CurrentHull.LethalPressure > 0.0f))
{ {
float pressure = AnimController.CurrentHull == null ? 100.0f : AnimController.CurrentHull.LethalPressure; float pressure = AnimController.CurrentHull == null ? 100.0f : AnimController.CurrentHull.LethalPressure;
if (pressure > 0.0f) if (pressure > 0.0f)
@@ -636,7 +650,7 @@ namespace Barotrauma
return closestItem; return closestItem;
} }
private Character FindCharacterAtPosition(Vector2 mouseSimPos, float maxDist = 150.0f) private Character FindCharacterAtPosition(Vector2 mouseSimPos, float maxDist = MaxHighlightDistance)
{ {
Character closestCharacter = null; Character closestCharacter = null;
@@ -646,7 +660,7 @@ namespace Barotrauma
{ {
if (!CanInteractWith(c, checkVisibility: false) || (c.AnimController?.SimplePhysicsEnabled ?? true)) { continue; } if (!CanInteractWith(c, checkVisibility: false) || (c.AnimController?.SimplePhysicsEnabled ?? true)) { continue; }
float dist = Vector2.DistanceSquared(mouseSimPos, c.SimPosition); float dist = c.GetDistanceToClosestLimb(mouseSimPos);
if (dist < closestDist || if (dist < closestDist ||
(c.CampaignInteractionType != CampaignMode.InteractionType.None && closestCharacter?.CampaignInteractionType == CampaignMode.InteractionType.None && dist * 0.9f < closestDist)) (c.CampaignInteractionType != CampaignMode.InteractionType.None && closestCharacter?.CampaignInteractionType == CampaignMode.InteractionType.None && dist * 0.9f < closestDist))
{ {
@@ -29,6 +29,10 @@ namespace Barotrauma
public abstract float State { get; } public abstract float State { get; }
public abstract string NumberToDisplay { get; }
public abstract Color Color { get; }
public BossProgressBar(LocalizedString label) public BossProgressBar(LocalizedString label)
{ {
FadeTimer = BossHealthBarDuration; FadeTimer = BossHealthBarDuration;
@@ -46,6 +50,7 @@ namespace Barotrauma
{ {
Color = GUIStyle.Red Color = GUIStyle.Red
}; };
CreateNumberText(TopHealthBar);
SideContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), bossHealthContainer.RectTransform) SideContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), bossHealthContainer.RectTransform)
{ {
@@ -56,12 +61,28 @@ namespace Barotrauma
{ {
Color = GUIStyle.Red Color = GUIStyle.Red
}; };
CreateNumberText(SideHealthBar);
TopContainer.Visible = SideContainer.Visible = false; TopContainer.Visible = SideContainer.Visible = false;
TopContainer.CanBeFocused = false; TopContainer.CanBeFocused = false;
TopContainer.Children.ForEach(c => c.CanBeFocused = false); TopContainer.Children.ForEach(c => c.CanBeFocused = false);
SideContainer.CanBeFocused = false; SideContainer.CanBeFocused = false;
SideContainer.Children.ForEach(c => c.CanBeFocused = false); SideContainer.Children.ForEach(c => c.CanBeFocused = false);
void CreateNumberText(GUIComponent parent)
{
new GUITextBlock(new RectTransform(Vector2.One, parent.RectTransform)
{ AbsoluteOffset = new Point(2) },
string.Empty, textAlignment: Alignment.Center, textColor: GUIStyle.TextColorDark)
{
TextGetter = () => NumberToDisplay
};
new GUITextBlock(new RectTransform(Vector2.One, parent.RectTransform),
string.Empty, textAlignment: Alignment.Center, textColor: GUIStyle.TextColorBright)
{
TextGetter = () => NumberToDisplay
};
}
} }
public abstract bool IsDuplicate(BossProgressBar progressBar); public abstract bool IsDuplicate(BossProgressBar progressBar);
@@ -77,6 +98,12 @@ namespace Barotrauma
public override bool Interrupted => Character.Removed || !Character.Enabled; 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) public BossHealthBar(Character character) : base(character.DisplayName)
{ {
Character = character; Character = character;
@@ -96,7 +123,13 @@ namespace Barotrauma
public override bool Completed => Mission.State >= Mission.Prefab.MaxProgressState; public override bool Completed => Mission.State >= Mission.Prefab.MaxProgressState;
public override bool Interrupted => Mission.Failed; 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) public MissionProgressBar(Mission mission) : base(mission.Prefab.ProgressBarLabel)
{ {
@@ -155,7 +188,7 @@ namespace Barotrauma
GameMain.GameSession?.Campaign != null && GameMain.GameSession?.Campaign != null &&
(GameMain.GameSession.Campaign.ShowCampaignUI || GameMain.GameSession.Campaign.ForceMapUI); (GameMain.GameSession.Campaign.ShowCampaignUI || GameMain.GameSession.Campaign.ForceMapUI);
private static bool ShouldDrawInventory(Character character) public static bool ShouldDrawInventory(Character character)
{ {
var controller = character.SelectedItem?.GetComponent<Controller>(); var controller = character.SelectedItem?.GetComponent<Controller>();
@@ -656,7 +689,8 @@ namespace Barotrauma
} }
} }
Vector2 startPos = character.DrawPosition + (character.FocusedCharacter.DrawPosition - character.DrawPosition) * 0.7f; float dist = Vector2.Distance(character.FocusedCharacter.DrawPosition, character.DrawPosition);
Vector2 startPos = character.DrawPosition + (character.FocusedCharacter.DrawPosition - character.DrawPosition) / dist * Math.Min(dist, Character.MaxDragDistance);
startPos = cam.WorldToScreen(startPos); startPos = cam.WorldToScreen(startPos);
string focusName = character.FocusedCharacter.Info == null ? character.FocusedCharacter.DisplayName : character.FocusedCharacter.Info.DisplayName; string focusName = character.FocusedCharacter.Info == null ? character.FocusedCharacter.DisplayName : character.FocusedCharacter.Info.DisplayName;
@@ -722,6 +756,11 @@ namespace Barotrauma
AddBossProgressBar(new MissionProgressBar(mission)); AddBossProgressBar(new MissionProgressBar(mission));
} }
public static void ClearBossHealthBars()
{
bossHealthBars.Clear();
}
private static void AddBossProgressBar(BossProgressBar progressBar, float damage = 0.0f) private static void AddBossProgressBar(BossProgressBar progressBar, float damage = 0.0f)
{ {
var healthBarMode = GameMain.NetworkMember?.ServerSettings.ShowEnemyHealthBars ?? GameSettings.CurrentConfig.ShowEnemyHealthBars; var healthBarMode = GameMain.NetworkMember?.ServerSettings.ShowEnemyHealthBars ?? GameSettings.CurrentConfig.ShowEnemyHealthBars;
@@ -791,7 +830,7 @@ namespace Barotrauma
{ {
foreach (var component in container.GetAllChildren()) foreach (var component in container.GetAllChildren())
{ {
component.Color = new Color(component.Color, (byte)(alpha * 255)); component.Color = new Color(bossHealthBar.Color, (byte)(alpha * 255));
if (component is GUITextBlock textBlock) if (component is GUITextBlock textBlock)
{ {
textBlock.TextColor = new Color(bossHealthBar.Completed ? Color.Gray : textBlock.TextColor, (byte)(alpha * 255)); textBlock.TextColor = new Color(bossHealthBar.Completed ? Color.Gray : textBlock.TextColor, (byte)(alpha * 255));
@@ -555,6 +555,10 @@ namespace Barotrauma
if (jobIdentifier > 0) if (jobIdentifier > 0)
{ {
jobPrefab = JobPrefab.Prefabs.Find(jp => jp.UintIdentifier == jobIdentifier); jobPrefab = JobPrefab.Prefabs.Find(jp => jp.UintIdentifier == jobIdentifier);
if (jobPrefab == null)
{
throw new Exception($"Error while reading {nameof(CharacterInfo)} received from the server: could not find a job prefab with the identifier \"{jobIdentifier}\".");
}
foreach (SkillPrefab skillPrefab in jobPrefab.Skills.OrderBy(s => s.Identifier)) foreach (SkillPrefab skillPrefab in jobPrefab.Skills.OrderBy(s => s.Identifier))
{ {
float skillLevel = inc.ReadSingle(); float skillLevel = inc.ReadSingle();
@@ -562,7 +566,6 @@ namespace Barotrauma
} }
} }
// TODO: animations
CharacterInfo ch = new CharacterInfo(speciesName, newName, originalName, jobPrefab, ragdollFile, variant, npcIdentifier: npcId) CharacterInfo ch = new CharacterInfo(speciesName, newName, originalName, jobPrefab, ragdollFile, variant, npcIdentifier: npcId)
{ {
ID = infoID, ID = infoID,
@@ -573,10 +576,7 @@ namespace Barotrauma
ch.Head.HairColor = hairColor; ch.Head.HairColor = hairColor;
ch.Head.FacialHairColor = facialHairColor; ch.Head.FacialHairColor = facialHairColor;
ch.SetPersonalityTrait(); ch.SetPersonalityTrait();
if (ch.Job != null) ch.Job?.OverrideSkills(skillLevels);
{
ch.Job.OverrideSkills(skillLevels);
}
ch.ExperiencePoints = inc.ReadUInt16(); ch.ExperiencePoints = inc.ReadUInt16();
ch.AdditionalTalentPoints = inc.ReadRangedInteger(0, MaxAdditionalTalentPoints); ch.AdditionalTalentPoints = inc.ReadRangedInteger(0, MaxAdditionalTalentPoints);
@@ -593,6 +593,7 @@ namespace Barotrauma
{ {
character.MerchantIdentifier = inc.ReadIdentifier(); character.MerchantIdentifier = inc.ReadIdentifier();
} }
character.Faction = inc.ReadIdentifier();
character.HumanPrefabHealthMultiplier = humanPrefabHealthMultiplier; character.HumanPrefabHealthMultiplier = humanPrefabHealthMultiplier;
character.Wallet.Balance = balance; character.Wallet.Balance = balance;
character.Wallet.RewardDistribution = rewardDistribution; character.Wallet.RewardDistribution = rewardDistribution;
@@ -87,6 +87,8 @@ namespace Barotrauma
/// Container for the icons above the health bar /// Container for the icons above the health bar
/// </summary> /// </summary>
private GUIComponent afflictionIconContainer; private GUIComponent afflictionIconContainer;
private float afflictionIconRefreshTimer;
const float AfflictionIconRefreshInterval = 1.0f;
private GUIButton showHiddenAfflictionsButton; private GUIButton showHiddenAfflictionsButton;
@@ -861,7 +863,7 @@ namespace Barotrauma
{ {
treatmentButton.ToolTip = treatmentButton.ToolTip =
RichString.Rich( RichString.Rich(
$"‖color:gui.green‖[{TextManager.Get(PlayerInput.MouseButtonsSwapped() ? "input.rightmouse" : "input.leftmouse")}] " $"‖color:gui.green‖[{PlayerInput.PrimaryMouseLabel}] "
+ $"{TextManager.Get("quickuseaction.usetreatment")}‖color:end‖" + '\n' + $"{TextManager.Get("quickuseaction.usetreatment")}‖color:end‖" + '\n'
+ treatmentButton.ToolTip.NestedStr); + treatmentButton.ToolTip.NestedStr);
} }
@@ -1157,15 +1159,20 @@ namespace Barotrauma
} }
} }
afflictionIconContainer.RectTransform.SortChildren((r1, r2) => afflictionIconRefreshTimer -= deltaTime;
if (afflictionIconRefreshTimer <= 0.0f)
{ {
if (r1.GUIComponent.UserData is not AfflictionPrefab prefab1) { return -1; } afflictionIconContainer.RectTransform.SortChildren((r1, r2) =>
if (r2.GUIComponent.UserData is not AfflictionPrefab prefab2) { return 1; } {
var index1 = statusIcons.IndexOf(s => s.Prefab == prefab1); if (r1.GUIComponent.UserData is not AfflictionPrefab prefab1) { return -1; }
var index2 = statusIcons.IndexOf(s => s.Prefab == prefab2); if (r2.GUIComponent.UserData is not AfflictionPrefab prefab2) { return 1; }
return index1.CompareTo(index2); var index1 = statusIcons.IndexOf(s => s.Prefab == prefab1);
}); var index2 = statusIcons.IndexOf(s => s.Prefab == prefab2);
(afflictionIconContainer as GUILayoutGroup).NeedsToRecalculate = true; return index1.CompareTo(index2);
});
(afflictionIconContainer as GUILayoutGroup).NeedsToRecalculate = true;
afflictionIconRefreshTimer = AfflictionIconRefreshInterval;
}
Rectangle hiddenAfflictionHoverArea = showHiddenAfflictionsButton.Rect; Rectangle hiddenAfflictionHoverArea = showHiddenAfflictionsButton.Rect;
foreach (GUIComponent child in hiddenAfflictionIconContainer.Children) foreach (GUIComponent child in hiddenAfflictionIconContainer.Children)
@@ -1983,6 +1990,7 @@ namespace Barotrauma
{ {
newAfflictions.Clear(); newAfflictions.Clear();
newPeriodicEffects.Clear(); newPeriodicEffects.Clear();
bool newAdded = false;
byte afflictionCount = inc.ReadByte(); byte afflictionCount = inc.ReadByte();
for (int i = 0; i < afflictionCount; i++) for (int i = 0; i < afflictionCount; i++)
{ {
@@ -2062,6 +2070,7 @@ namespace Barotrauma
{ {
existingAffliction = afflictionPrefab.Instantiate(strength); existingAffliction = afflictionPrefab.Instantiate(strength);
afflictions.Add(existingAffliction, limb); afflictions.Add(existingAffliction, limb);
newAdded = true;
} }
existingAffliction.SetStrength(strength); existingAffliction.SetStrength(strength);
if (existingAffliction == stunAffliction) if (existingAffliction == stunAffliction)
@@ -2071,6 +2080,8 @@ namespace Barotrauma
foreach (var periodicEffect in newPeriodicEffects) foreach (var periodicEffect in newPeriodicEffects)
{ {
if (!existingAffliction.Prefab.PeriodicEffects.Contains(periodicEffect.effect)) { continue; } if (!existingAffliction.Prefab.PeriodicEffects.Contains(periodicEffect.effect)) { continue; }
if (existingAffliction.Strength < periodicEffect.effect.MinStrength) { continue; }
if (periodicEffect.effect.MaxStrength > 0 && existingAffliction.Strength > periodicEffect.effect.MaxStrength) { continue; }
//timer has wrapped around, apply the effect //timer has wrapped around, apply the effect
if (periodicEffect.timer - existingAffliction.PeriodicEffectTimers[periodicEffect.effect] > periodicEffect.effect.MinInterval / 2) if (periodicEffect.timer - existingAffliction.PeriodicEffectTimers[periodicEffect.effect] > periodicEffect.effect.MinInterval / 2)
{ {
@@ -2086,6 +2097,11 @@ namespace Barotrauma
CalculateVitality(); CalculateVitality();
DisplayedVitality = Vitality; DisplayedVitality = Vitality;
if (newAdded)
{
MedicalClinic.OnAfflictionCountChanged(Character);
}
} }
partial void UpdateSkinTint() partial void UpdateSkinTint()
@@ -0,0 +1,22 @@
#nullable enable
using System;
namespace Barotrauma
{
internal static class HealingCooldown
{
public static float NormalizedCooldown => MathF.Min((float) (DateTimeOffset.UtcNow - OnCooldownUntil).TotalSeconds / CooldownDuration, 0f);
public static bool IsOnCooldown => DateTimeOffset.UtcNow < OnCooldownUntil;
private static DateTimeOffset OnCooldownUntil = DateTimeOffset.MinValue;
private const float CooldownDuration = 0.5f;
public static readonly Identifier MedicalItemTag = new Identifier("medical");
public static void PutOnCooldown()
{
OnCooldownUntil = DateTimeOffset.UtcNow.AddSeconds(CooldownDuration);
}
}
}
@@ -8,6 +8,7 @@ using Microsoft.Xna.Framework.Graphics;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Barotrauma.IO; using Barotrauma.IO;
using Barotrauma.Utils;
using System.Linq; using System.Linq;
using System.Xml.Linq; using System.Xml.Linq;
using SpriteParams = Barotrauma.RagdollParams.SpriteParams; using SpriteParams = Barotrauma.RagdollParams.SpriteParams;
@@ -260,9 +261,7 @@ namespace Barotrauma
{ {
if (enableHuskSprite) if (enableHuskSprite)
{ {
List<WearableSprite> otherWearablesWithHusk = new List<WearableSprite>() { HuskSprite }; OtherWearables.Insert(0, HuskSprite);
otherWearablesWithHusk.AddRange(OtherWearables);
OtherWearables = otherWearablesWithHusk;
UpdateWearableTypesToHide(); UpdateWearableTypesToHide();
} }
else else
@@ -546,7 +545,7 @@ namespace Barotrauma
{ {
foreach (var affliction in result.Afflictions) foreach (var affliction in result.Afflictions)
{ {
if (affliction is AfflictionBleeding) if (affliction is AfflictionBleeding bleeding && bleeding.Prefab.DamageParticles)
{ {
bleedingDamage += affliction.GetVitalityDecrease(null); bleedingDamage += affliction.GetVitalityDecrease(null);
} }
@@ -555,7 +554,7 @@ namespace Barotrauma
float damage = 0; float damage = 0;
foreach (var affliction in result.Afflictions) foreach (var affliction in result.Afflictions)
{ {
if (affliction.Prefab.AfflictionType == "damage") if (affliction.Prefab.DamageParticles && affliction.Prefab.AfflictionType == AfflictionPrefab.DamageType)
{ {
damage += affliction.GetVitalityDecrease(null); damage += affliction.GetVitalityDecrease(null);
} }
@@ -564,11 +563,11 @@ namespace Barotrauma
float bleedingDamageMultiplier = 1; float bleedingDamageMultiplier = 1;
foreach (DamageModifier damageModifier in result.AppliedDamageModifiers) foreach (DamageModifier damageModifier in result.AppliedDamageModifiers)
{ {
if (damageModifier.MatchesAfflictionType("damage")) if (damageModifier.MatchesAfflictionType(AfflictionPrefab.DamageType))
{ {
damageMultiplier *= damageModifier.DamageMultiplier; damageMultiplier *= damageModifier.DamageMultiplier;
} }
else if (damageModifier.MatchesAfflictionType("bleeding")) else if (damageModifier.MatchesAfflictionType(AfflictionPrefab.BleedingType))
{ {
bleedingDamageMultiplier *= damageModifier.DamageMultiplier; bleedingDamageMultiplier *= damageModifier.DamageMultiplier;
} }
@@ -728,11 +727,11 @@ namespace Barotrauma
} }
} }
float herpesStrength = character.CharacterHealth.GetAfflictionStrength("spaceherpes"); float herpesStrength = character.CharacterHealth.GetAfflictionStrength(AfflictionPrefab.SpaceHerpesType);
bool hideLimb = Hide || bool hideLimb = Hide ||
OtherWearables.Any(w => w.HideLimb) || OtherWearables.Any(w => w.HideLimb) ||
wearingItems.Any(w => w != null && w.HideLimb); WearingItems.Any(w => w.HideLimb);
bool drawHuskSprite = HuskSprite != null && !wearableTypesToHide.Contains(WearableType.Husk); bool drawHuskSprite = HuskSprite != null && !wearableTypesToHide.Contains(WearableType.Husk);
@@ -828,7 +827,7 @@ namespace Barotrauma
LightSource.LightSpriteEffect = (dir == Direction.Right) ? SpriteEffects.None : SpriteEffects.FlipVertically; LightSource.LightSpriteEffect = (dir == Direction.Right) ? SpriteEffects.None : SpriteEffects.FlipVertically;
} }
float step = depthStep; float step = depthStep;
WearableSprite onlyDrawable = wearingItems.Find(w => w.HideOtherWearables); WearableSprite onlyDrawable = WearingItems.Find(w => w.HideOtherWearables);
if (Params.MirrorHorizontally) if (Params.MirrorHorizontally)
{ {
spriteEffect = spriteEffect == SpriteEffects.None ? SpriteEffects.FlipHorizontally : SpriteEffects.None; spriteEffect = spriteEffect == SpriteEffects.None ? SpriteEffects.FlipHorizontally : SpriteEffects.None;
@@ -965,31 +964,28 @@ namespace Barotrauma
public void UpdateWearableTypesToHide() public void UpdateWearableTypesToHide()
{ {
alphaClipEffectParams?.Clear();
wearableTypeHidingSprites.Clear(); wearableTypeHidingSprites.Clear();
if (WearingItems != null && WearingItems.Count > 0)
void addWearablesFrom(IReadOnlyList<WearableSprite> wearableSprites)
{ {
if (wearableSprites.Count <= 0) { return; }
wearableTypeHidingSprites.AddRange( wearableTypeHidingSprites.AddRange(
WearingItems.FindAll(w => w.HideWearablesOfType != null && w.HideWearablesOfType.Count > 0)); wearableSprites.Where(w => w.HideWearablesOfType.Count > 0));
}
if (OtherWearables != null && OtherWearables.Count > 0)
{
wearableTypeHidingSprites.AddRange(
OtherWearables.FindAll(w => w.HideWearablesOfType != null && w.HideWearablesOfType.Count > 0));
} }
addWearablesFrom(WearingItems);
addWearablesFrom(OtherWearables);
wearableTypesToHide.Clear(); wearableTypesToHide.Clear();
if (wearableTypeHidingSprites.Count > 0)
if (wearableTypeHidingSprites.Count <= 0) { return; }
foreach (WearableSprite sprite in wearableTypeHidingSprites)
{ {
foreach (WearableSprite sprite in wearableTypeHidingSprites) wearableTypesToHide.UnionWith(sprite.HideWearablesOfType);
{
foreach (WearableType type in sprite.HideWearablesOfType)
{
if (!wearableTypesToHide.Contains(type))
{
wearableTypesToHide.Add(type);
}
}
}
} }
} }
@@ -1071,7 +1067,13 @@ namespace Barotrauma
} }
} }
private void DrawWearable(WearableSprite wearable, float depthStep, SpriteBatch spriteBatch, Color color, float alpha, SpriteEffects spriteEffect) private (
Color FinalColor,
Vector2 Origin,
float Rotation,
float Scale,
float Depth)
CalculateDrawParameters(WearableSprite wearable, float depthStep, Color color, float alpha)
{ {
var sprite = ActiveSprite; var sprite = ActiveSprite;
if (wearable.InheritSourceRect) if (wearable.InheritSourceRect)
@@ -1163,27 +1165,118 @@ namespace Barotrauma
float finalAlpha = alpha * wearableColor.A; float finalAlpha = alpha * wearableColor.A;
Color finalColor = color.Multiply(wearableColor); Color finalColor = color.Multiply(wearableColor);
finalColor = new Color(finalColor.R, finalColor.G, finalColor.B, (byte)finalAlpha); finalColor = new Color(finalColor.R, finalColor.G, finalColor.B, (byte)finalAlpha);
wearable.Sprite.Draw(spriteBatch, new Vector2(body.DrawPosition.X, -body.DrawPosition.Y), finalColor, origin, rotation, scale, spriteEffect, depth);
return (finalColor, origin, rotation, scale, depth);
} }
private WearableSprite GetWearableSprite(WearableType type)//, bool random = false) private static Effect alphaClipEffect;
private Dictionary<WearableSprite, Dictionary<string, object>> alphaClipEffectParams;
private void ApplyAlphaClip(SpriteBatch spriteBatch, WearableSprite wearable, WearableSprite alphaClipper, SpriteEffects spriteEffect)
{
SpriteRecorder.Command makeCommand(WearableSprite w)
{
var (_, origin, rotation, scale, _)
= CalculateDrawParameters(w, 0f, Color.White, 0f);
var command = SpriteRecorder.Command.FromTransform(
texture: w.Sprite.Texture,
pos: new Vector2(body.DrawPosition.X, -body.DrawPosition.Y),
srcRect: w.Sprite.SourceRect,
color: Color.White,
rotation: rotation,
origin: origin,
scale: new Vector2(scale, scale),
effects: spriteEffect,
depth: 0f,
index: 0);
return command;
}
void spacesFromCommand(WearableSprite w, SpriteRecorder.Command command, out CoordinateSpace2D textureSpace, out CoordinateSpace2D worldSpace)
{
var (topLeft, bottomLeft, topRight) = spriteEffect switch
{
SpriteEffects.None
=> (command.VertexTL, command.VertexBL, command.VertexTR),
SpriteEffects.FlipHorizontally | SpriteEffects.FlipVertically
=> (command.VertexBR, command.VertexTR, command.VertexBL),
SpriteEffects.FlipHorizontally
=> (command.VertexTR, command.VertexBR, command.VertexTL),
SpriteEffects.FlipVertically
=> (command.VertexBL, command.VertexTL, command.VertexBR)
};
textureSpace = new CoordinateSpace2D
{
Origin = topLeft.TextureCoordinate,
I = topRight.TextureCoordinate - topLeft.TextureCoordinate,
J = bottomLeft.TextureCoordinate - topLeft.TextureCoordinate
};
worldSpace = new CoordinateSpace2D
{
Origin = topLeft.Position.DiscardZ(),
I = topRight.Position.DiscardZ() - topLeft.Position.DiscardZ(),
J = bottomLeft.Position.DiscardZ() - topLeft.Position.DiscardZ()
};
}
var wearableCommand = makeCommand(wearable);
var clipperCommand = makeCommand(alphaClipper);
spacesFromCommand(wearable, wearableCommand, out var wearableTextureSpace, out var wearableWorldSpace);
spacesFromCommand(alphaClipper, clipperCommand, out var clipperTextureSpace, out var clipperWorldSpace);
var wearableUvToClipperUv =
wearableTextureSpace.CanonicalToLocal
* wearableWorldSpace.LocalToCanonical
* clipperWorldSpace.CanonicalToLocal
* clipperTextureSpace.LocalToCanonical;
alphaClipEffect ??= EffectLoader.Load("Effects/wearableclip");
alphaClipEffectParams ??= new Dictionary<WearableSprite, Dictionary<string, object>>();
if (!alphaClipEffectParams.ContainsKey(wearable)) { alphaClipEffectParams.Add(wearable, new Dictionary<string, object>()); }
var paramsToPass = new SpriteBatch.EffectWithParams
{
Effect = alphaClipEffect,
Params = alphaClipEffectParams[wearable]
};
paramsToPass.Params["wearableUvToClipperUv"] = wearableUvToClipperUv;
paramsToPass.Params["clipperTexelSize"] = 2f / alphaClipper.Sprite.Texture.Width;
paramsToPass.Params["aCutoff"] = 2f / 255f;
paramsToPass.Params["xTexture"] = wearable.Sprite.Texture;
paramsToPass.Params["xStencil"] = alphaClipper.Sprite.Texture;
spriteBatch.SwapEffect(paramsToPass);
}
private void DrawWearable(WearableSprite wearable, float depthStep, SpriteBatch spriteBatch, Color color, float alpha, SpriteEffects spriteEffect)
{
var (finalColor, origin, rotation, scale, depth)
= CalculateDrawParameters(wearable, depthStep, color, alpha);
var prevEffect = spriteBatch.GetCurrentEffect();
var alphaClipper = WearingItems.Find(w => w.AlphaClipOtherWearables);
bool shouldApplyAlphaClip = alphaClipper != null && wearable != alphaClipper;
if (shouldApplyAlphaClip)
{
ApplyAlphaClip(spriteBatch, wearable, alphaClipper, spriteEffect);
}
wearable.Sprite.Draw(spriteBatch, new Vector2(body.DrawPosition.X, -body.DrawPosition.Y), finalColor, origin, rotation, scale, spriteEffect, depth);
if (shouldApplyAlphaClip)
{
spriteBatch.SwapEffect(effect: prevEffect);
}
}
private WearableSprite GetWearableSprite(WearableType type)
{ {
var info = character.Info; var info = character.Info;
if (info == null) { return null; } if (info == null) { return null; }
ContentXElement element; ContentXElement element = info.FilterElements(info.Wearables, info.Head.Preset.TagSet, type)?.FirstOrDefault();
/*if (random) return element != null ? new WearableSprite(element.GetChildElement("sprite"), type) : null;
{
element = info.FilterElements(info.Wearables, info.Head.Preset.TagSet)?.GetRandom(Rand.RandSync.ClientOnly);
}
else
{*/
element = info.FilterElements(info.Wearables, info.Head.Preset.TagSet, type)?.FirstOrDefault();
//}
if (element != null)
{
return new WearableSprite(element.GetChildElement("sprite"), type);
}
return null;
} }
partial void RemoveProjSpecific() partial void RemoveProjSpecific()
@@ -1206,8 +1299,8 @@ namespace Barotrauma
LightSource?.Remove(); LightSource?.Remove();
LightSource = null; LightSource = null;
OtherWearables?.ForEach(w => w.Sprite.Remove()); OtherWearables.ForEach(w => w.Sprite.Remove());
OtherWearables = null; OtherWearables.Clear();
HuskSprite?.Sprite.Remove(); HuskSprite?.Sprite.Remove();
HuskSprite = null; HuskSprite = null;
@@ -92,7 +92,7 @@ namespace Barotrauma
public Option<ContentPackageId> UgcId = Option<ContentPackageId>.None(); public Option<ContentPackageId> UgcId = Option<ContentPackageId>.None();
public Option<DateTime> InstallTime = Option<DateTime>.None(); public Option<SerializableDateTime> InstallTime = Option<SerializableDateTime>.None();
public bool HasFile(File file) public bool HasFile(File file)
=> Files.Any(f => => Files.Any(f =>
@@ -120,7 +120,7 @@ namespace Barotrauma
public void DiscardHashAndInstallTime() public void DiscardHashAndInstallTime()
{ {
ExpectedHash = null; ExpectedHash = null;
InstallTime = Option<DateTime>.None(); InstallTime = Option<SerializableDateTime>.None();
} }
public static string IncrementModVersion(string modVersion) public static string IncrementModVersion(string modVersion)
@@ -159,7 +159,7 @@ namespace Barotrauma
addRootAttribute("gameversion", GameMain.Version); addRootAttribute("gameversion", GameMain.Version);
if (AltNames.Any()) { addRootAttribute("altnames", string.Join(",", AltNames)); } if (AltNames.Any()) { addRootAttribute("altnames", string.Join(",", AltNames)); }
if (ExpectedHash != null) { addRootAttribute("expectedhash", ExpectedHash.StringRepresentation); } if (ExpectedHash != null) { addRootAttribute("expectedhash", ExpectedHash.StringRepresentation); }
if (InstallTime.TryUnwrap(out var installTime)) { addRootAttribute("installtime", ToolBox.Epoch.FromDateTime(installTime)); } if (InstallTime.TryUnwrap(out var installTime)) { addRootAttribute("installtime", installTime); }
files.ForEach(f => rootElement.Add(f.ToXElement())); files.ForEach(f => rootElement.Add(f.ToXElement()));
@@ -49,7 +49,7 @@ namespace Barotrauma
&& ugcId is SteamWorkshopId workshopId && ugcId is SteamWorkshopId workshopId
&& item.Id == workshopId.Value && item.Id == workshopId.Value
&& p.InstallTime.TryUnwrap(out var installTime) && p.InstallTime.TryUnwrap(out var installTime)
&& item.LatestUpdateTime <= installTime)) && item.LatestUpdateTime <= installTime.ToUtcValue()))
.ToArray(); .ToArray();
if (!needInstalling.Any()) { return Enumerable.Empty<Steamworks.Ugc.Item>(); } if (!needInstalling.Any()) { return Enumerable.Empty<Steamworks.Ugc.Item>(); }
@@ -639,7 +639,7 @@ namespace Barotrauma
{ {
if (Submarine.MainSub == null) { return; } if (Submarine.MainSub == null) { return; }
MapEntity.SelectedList.Clear(); MapEntity.SelectedList.Clear();
MapEntity.mapEntityList.ForEach(me => me.IsHighlighted = false); MapEntity.ClearHighlightedEntities();
WikiImage.Create(Submarine.MainSub); WikiImage.Create(Submarine.MainSub);
})); }));
@@ -752,7 +752,7 @@ namespace Barotrauma
state = !GameMain.LightManager.LosEnabled; state = !GameMain.LightManager.LosEnabled;
} }
GameMain.LightManager.LosEnabled = state; GameMain.LightManager.LosEnabled = state;
NewMessage("Line of sight effect " + (GameMain.LightManager.LosEnabled ? "enabled" : "disabled"), Color.White); NewMessage("Line of sight effect " + (GameMain.LightManager.LosEnabled ? "enabled" : "disabled"), Color.Yellow);
}); });
AssignRelayToServer("los", false); AssignRelayToServer("los", false);
@@ -763,7 +763,7 @@ namespace Barotrauma
state = !GameMain.LightManager.LightingEnabled; state = !GameMain.LightManager.LightingEnabled;
} }
GameMain.LightManager.LightingEnabled = state; GameMain.LightManager.LightingEnabled = state;
NewMessage("Lighting " + (GameMain.LightManager.LightingEnabled ? "enabled" : "disabled"), Color.White); NewMessage("Lighting " + (GameMain.LightManager.LightingEnabled ? "enabled" : "disabled"), Color.Yellow);
}); });
AssignRelayToServer("lighting|lights", false); AssignRelayToServer("lighting|lights", false);
@@ -781,7 +781,7 @@ namespace Barotrauma
hull.OriginalAmbientLight = null; hull.OriginalAmbientLight = null;
} }
} }
NewMessage("Restored all hull ambient lights", Color.White); NewMessage("Restored all hull ambient lights", Color.Yellow);
return; return;
} }
@@ -803,11 +803,11 @@ namespace Barotrauma
if (add) if (add)
{ {
NewMessage($"Set ambient light color to {color}.", Color.White); NewMessage($"Set ambient light color to {color}.", Color.Yellow);
} }
else else
{ {
NewMessage($"Increased ambient light by {color}.", Color.White); NewMessage($"Increased ambient light by {color}.", Color.Yellow);
} }
}); });
AssignRelayToServer("ambientlight", false); AssignRelayToServer("ambientlight", false);
@@ -1124,10 +1124,32 @@ namespace Barotrauma
state = !GameMain.DebugDraw; state = !GameMain.DebugDraw;
} }
GameMain.DebugDraw = state; GameMain.DebugDraw = state;
NewMessage("Debug draw mode " + (GameMain.DebugDraw ? "enabled" : "disabled"), Color.White); NewMessage("Debug draw mode " + (GameMain.DebugDraw ? "enabled" : "disabled"), Color.Yellow);
}); });
AssignRelayToServer("debugdraw", false); AssignRelayToServer("debugdraw", false);
AssignOnExecute("devmode", (string[] args) =>
{
if (args.None() || !bool.TryParse(args[0], out bool state))
{
state = !GameMain.DevMode;
}
GameMain.DevMode = state;
if (GameMain.DevMode)
{
GameMain.LightManager.LightingEnabled = false;
GameMain.LightManager.LosEnabled = false;
}
else
{
GameMain.LightManager.LightingEnabled = true;
GameMain.LightManager.LosEnabled = true;
GameMain.LightManager.LosAlpha = 1f;
}
NewMessage("Dev mode " + (GameMain.DevMode ? "enabled" : "disabled"), Color.White);
});
AssignRelayToServer("devmode", false);
AssignOnExecute("debugdrawlocalization", (string[] args) => AssignOnExecute("debugdrawlocalization", (string[] args) =>
{ {
if (args.None() || !bool.TryParse(args[0], out bool state)) if (args.None() || !bool.TryParse(args[0], out bool state))
@@ -1135,7 +1157,7 @@ namespace Barotrauma
state = !TextManager.DebugDraw; state = !TextManager.DebugDraw;
} }
TextManager.DebugDraw = state; TextManager.DebugDraw = state;
NewMessage("Localization debug draw mode " + (TextManager.DebugDraw ? "enabled" : "disabled"), Color.White); NewMessage("Localization debug draw mode " + (TextManager.DebugDraw ? "enabled" : "disabled"), Color.Yellow);
}); });
AssignRelayToServer("debugdraw", false); AssignRelayToServer("debugdraw", false);
@@ -1148,19 +1170,19 @@ namespace Barotrauma
var config = GameSettings.CurrentConfig; var config = GameSettings.CurrentConfig;
config.Audio.DisableVoiceChatFilters = state; config.Audio.DisableVoiceChatFilters = state;
GameSettings.SetCurrentConfig(config); GameSettings.SetCurrentConfig(config);
NewMessage("Voice chat filters " + (GameSettings.CurrentConfig.Audio.DisableVoiceChatFilters ? "disabled" : "enabled"), Color.White); NewMessage("Voice chat filters " + (GameSettings.CurrentConfig.Audio.DisableVoiceChatFilters ? "disabled" : "enabled"), Color.Yellow);
}); });
AssignRelayToServer("togglevoicechatfilters", false); AssignRelayToServer("togglevoicechatfilters", false);
commands.Add(new Command("fpscounter", "fpscounter: Toggle the FPS counter.", (string[] args) => commands.Add(new Command("fpscounter", "fpscounter: Toggle the FPS counter.", (string[] args) =>
{ {
GameMain.ShowFPS = !GameMain.ShowFPS; GameMain.ShowFPS = !GameMain.ShowFPS;
NewMessage("FPS counter " + (GameMain.DebugDraw ? "enabled" : "disabled"), Color.White); NewMessage("FPS counter " + (GameMain.DebugDraw ? "enabled" : "disabled"), Color.Yellow);
})); }));
commands.Add(new Command("showperf", "showperf: Toggle performance statistics on/off.", (string[] args) => commands.Add(new Command("showperf", "showperf: Toggle performance statistics on/off.", (string[] args) =>
{ {
GameMain.ShowPerf = !GameMain.ShowPerf; GameMain.ShowPerf = !GameMain.ShowPerf;
NewMessage("Performance statistics " + (GameMain.ShowPerf ? "enabled" : "disabled"), Color.White); NewMessage("Performance statistics " + (GameMain.ShowPerf ? "enabled" : "disabled"), Color.Yellow);
})); }));
AssignOnClientExecute("netstats", (string[] args) => AssignOnClientExecute("netstats", (string[] args) =>
@@ -1172,55 +1194,55 @@ namespace Barotrauma
commands.Add(new Command("hudlayoutdebugdraw|debugdrawhudlayout", "hudlayoutdebugdraw: Toggle the debug drawing mode of HUD layout areas on/off.", (string[] args) => commands.Add(new Command("hudlayoutdebugdraw|debugdrawhudlayout", "hudlayoutdebugdraw: Toggle the debug drawing mode of HUD layout areas on/off.", (string[] args) =>
{ {
HUDLayoutSettings.DebugDraw = !HUDLayoutSettings.DebugDraw; HUDLayoutSettings.DebugDraw = !HUDLayoutSettings.DebugDraw;
NewMessage("HUD layout debug draw mode " + (HUDLayoutSettings.DebugDraw ? "enabled" : "disabled"), Color.White); NewMessage("HUD layout debug draw mode " + (HUDLayoutSettings.DebugDraw ? "enabled" : "disabled"), Color.Yellow);
})); }));
commands.Add(new Command("interactdebugdraw|debugdrawinteract", "interactdebugdraw: Toggle the debug drawing mode of item interaction ranges on/off.", (string[] args) => commands.Add(new Command("interactdebugdraw|debugdrawinteract", "interactdebugdraw: Toggle the debug drawing mode of item interaction ranges on/off.", (string[] args) =>
{ {
Character.DebugDrawInteract = !Character.DebugDrawInteract; Character.DebugDrawInteract = !Character.DebugDrawInteract;
NewMessage("Interact debug draw mode " + (Character.DebugDrawInteract ? "enabled" : "disabled"), Color.White); NewMessage("Interact debug draw mode " + (Character.DebugDrawInteract ? "enabled" : "disabled"), Color.Yellow);
}, isCheat: true)); }, isCheat: true));
AssignOnExecute("togglehud|hud", (string[] args) => AssignOnExecute("togglehud|hud", (string[] args) =>
{ {
GUI.DisableHUD = !GUI.DisableHUD; GUI.DisableHUD = !GUI.DisableHUD;
GameMain.Instance.IsMouseVisible = !GameMain.Instance.IsMouseVisible; GameMain.Instance.IsMouseVisible = !GameMain.Instance.IsMouseVisible;
NewMessage(GUI.DisableHUD ? "Disabled HUD" : "Enabled HUD", Color.White); NewMessage(GUI.DisableHUD ? "Disabled HUD" : "Enabled HUD", Color.Yellow);
}); });
AssignRelayToServer("togglehud|hud", false); AssignRelayToServer("togglehud|hud", false);
AssignOnExecute("toggleupperhud", (string[] args) => AssignOnExecute("toggleupperhud", (string[] args) =>
{ {
GUI.DisableUpperHUD = !GUI.DisableUpperHUD; GUI.DisableUpperHUD = !GUI.DisableUpperHUD;
NewMessage(GUI.DisableUpperHUD ? "Disabled upper HUD" : "Enabled upper HUD", Color.White); NewMessage(GUI.DisableUpperHUD ? "Disabled upper HUD" : "Enabled upper HUD", Color.Yellow);
}); });
AssignRelayToServer("toggleupperhud", false); AssignRelayToServer("toggleupperhud", false);
AssignOnExecute("toggleitemhighlights", (string[] args) => AssignOnExecute("toggleitemhighlights", (string[] args) =>
{ {
GUI.DisableItemHighlights = !GUI.DisableItemHighlights; GUI.DisableItemHighlights = !GUI.DisableItemHighlights;
NewMessage(GUI.DisableItemHighlights ? "Disabled item highlights" : "Enabled item highlights", Color.White); NewMessage(GUI.DisableItemHighlights ? "Disabled item highlights" : "Enabled item highlights", Color.Yellow);
}); });
AssignRelayToServer("toggleitemhighlights", false); AssignRelayToServer("toggleitemhighlights", false);
AssignOnExecute("togglecharacternames", (string[] args) => AssignOnExecute("togglecharacternames", (string[] args) =>
{ {
GUI.DisableCharacterNames = !GUI.DisableCharacterNames; GUI.DisableCharacterNames = !GUI.DisableCharacterNames;
NewMessage(GUI.DisableCharacterNames ? "Disabled character names" : "Enabled character names", Color.White); NewMessage(GUI.DisableCharacterNames ? "Disabled character names" : "Enabled character names", Color.Yellow);
}); });
AssignRelayToServer("togglecharacternames", false); AssignRelayToServer("togglecharacternames", false);
AssignOnExecute("followsub", (string[] args) => AssignOnExecute("followsub", (string[] args) =>
{ {
Camera.FollowSub = !Camera.FollowSub; Camera.FollowSub = !Camera.FollowSub;
NewMessage(Camera.FollowSub ? "Set the camera to follow the closest submarine" : "Disabled submarine following.", Color.White); NewMessage(Camera.FollowSub ? "Set the camera to follow the closest submarine" : "Disabled submarine following.", Color.Yellow);
}); });
AssignRelayToServer("followsub", false); AssignRelayToServer("followsub", false);
AssignOnExecute("toggleaitargets|aitargets", (string[] args) => AssignOnExecute("toggleaitargets|aitargets", (string[] args) =>
{ {
AITarget.ShowAITargets = !AITarget.ShowAITargets; AITarget.ShowAITargets = !AITarget.ShowAITargets;
NewMessage(AITarget.ShowAITargets ? "Enabled AI target drawing" : "Disabled AI target drawing", Color.White); NewMessage(AITarget.ShowAITargets ? "Enabled AI target drawing" : "Disabled AI target drawing", Color.Yellow);
}); });
AssignRelayToServer("toggleaitargets|aitargets", false); AssignRelayToServer("toggleaitargets|aitargets", false);
@@ -1229,21 +1251,49 @@ namespace Barotrauma
HumanAIController.debugai = !HumanAIController.debugai; HumanAIController.debugai = !HumanAIController.debugai;
if (HumanAIController.debugai) if (HumanAIController.debugai)
{ {
GameMain.DevMode = true;
GameMain.DebugDraw = true; GameMain.DebugDraw = true;
GameMain.LightManager.LightingEnabled = false; GameMain.LightManager.LightingEnabled = false;
GameMain.LightManager.LosEnabled = false; GameMain.LightManager.LosEnabled = false;
} }
else else
{ {
GameMain.DevMode = false;
GameMain.DebugDraw = false; GameMain.DebugDraw = false;
GameMain.LightManager.LightingEnabled = true; GameMain.LightManager.LightingEnabled = true;
GameMain.LightManager.LosEnabled = true; GameMain.LightManager.LosEnabled = true;
GameMain.LightManager.LosAlpha = 1f; GameMain.LightManager.LosAlpha = 1f;
} }
NewMessage(HumanAIController.debugai ? "AI debug info visible" : "AI debug info hidden", Color.White); NewMessage(HumanAIController.debugai ? "AI debug info visible" : "AI debug info hidden", Color.Yellow);
}); });
AssignRelayToServer("debugai", false); AssignRelayToServer("debugai", false);
AssignOnExecute("showmonsters", (string[] args) =>
{
CreatureMetrics.UnlockAll = true;
CreatureMetrics.Save();
NewMessage("All monsters are now visible in the character editor.", Color.Yellow);
if (Screen.Selected == GameMain.CharacterEditorScreen)
{
GameMain.CharacterEditorScreen.Deselect();
GameMain.CharacterEditorScreen.Select();
}
});
AssignRelayToServer("showmonsters", false);
AssignOnExecute("hidemonsters", (string[] args) =>
{
CreatureMetrics.UnlockAll = false;
CreatureMetrics.Save();
NewMessage("All monsters that haven't yet been encountered in the game are now hidden in the character editor.", Color.Yellow);
if (Screen.Selected == GameMain.CharacterEditorScreen)
{
GameMain.CharacterEditorScreen.Deselect();
GameMain.CharacterEditorScreen.Select();
}
});
AssignRelayToServer("hidemonsters", false);
AssignRelayToServer("water|editwater", false); AssignRelayToServer("water|editwater", false);
AssignRelayToServer("fire|editfire", false); AssignRelayToServer("fire|editfire", false);
@@ -8,7 +8,8 @@ partial class CheckObjectiveAction : BinaryOptionAction
public enum CheckType public enum CheckType
{ {
Added, Added,
Completed Completed,
Incomplete
} }
[Serialize(CheckType.Completed, IsPropertySaveable.Yes)] [Serialize(CheckType.Completed, IsPropertySaveable.Yes)]
@@ -30,8 +31,13 @@ partial class CheckObjectiveAction : BinaryOptionAction
{ {
CheckType.Added => true, CheckType.Added => true,
CheckType.Completed => segment.IsCompleted, CheckType.Completed => segment.IsCompleted,
CheckType.Incomplete => !segment.IsCompleted,
_ => false _ => false
}; };
} }
else if (Type == CheckType.Incomplete)
{
success = true;
}
} }
} }
@@ -1,5 +1,6 @@
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
namespace Barotrauma; namespace Barotrauma;
@@ -13,11 +14,22 @@ partial class UIHighlightAction : EventAction
bool useCircularFlash = false; bool useCircularFlash = false;
if (Id != ElementId.None) if (Id != ElementId.None)
{ {
FindAndFlashComponents(c => Equals(Id, c.UserData)); var predicate = (GUIComponent c) => c is not null && Equals(Id, c.UserData);
if (!FindAndFlashAddedComponents(predicate))
{
if (predicate(GUIMessageBox.VisibleBox))
{
Flash(GUIMessageBox.VisibleBox);
}
else
{
FindAndFlashMessageBoxComponents(predicate);
}
}
} }
else if (!EntityIdentifier.IsEmpty) else if (!EntityIdentifier.IsEmpty)
{ {
FindAndFlashComponents(c => FindAndFlashAddedComponents(c =>
c.UserData is MapEntityPrefab mep && mep.Identifier == EntityIdentifier || c.UserData is MapEntity me && me.Prefab.Identifier == EntityIdentifier); c.UserData is MapEntityPrefab mep && mep.Identifier == EntityIdentifier || c.UserData is MapEntity me && me.Prefab.Identifier == EntityIdentifier);
} }
else if (!OrderIdentifier.IsEmpty) else if (!OrderIdentifier.IsEmpty)
@@ -26,26 +38,26 @@ partial class UIHighlightAction : EventAction
bool foundMinimapNode = false; bool foundMinimapNode = false;
if (!OrderTargetTag.IsEmpty) if (!OrderTargetTag.IsEmpty)
{ {
foundMinimapNode = FindAndFlashComponents(c => foundMinimapNode = FindAndFlashAddedComponents(c =>
c.UserData is CrewManager.MinimapNodeData nodeData && nodeData.Order is Order order && c.UserData is CrewManager.MinimapNodeData nodeData && nodeData.Order is Order order &&
order.Identifier == OrderIdentifier && order.Option == OrderOption && order.TargetEntity is Item item && item.HasTag(OrderTargetTag)); order.Identifier == OrderIdentifier && order.Option == OrderOption && order.TargetEntity is Item item && item.HasTag(OrderTargetTag));
} }
if (!foundMinimapNode) if (!foundMinimapNode)
{ {
FindAndFlashComponents(c => c.UserData is Order order && order.Identifier == OrderIdentifier && order.Option == OrderOption, FindAndFlashAddedComponents(c => c.UserData is Order order && order.Identifier == OrderIdentifier && order.Option == OrderOption,
c => c.UserData is Order order && order.Identifier == OrderIdentifier, c => c.UserData is Order order && order.Identifier == OrderIdentifier,
c => Equals(OrderCategory, c.UserData)); c => Equals(OrderCategory, c.UserData));
} }
} }
bool FindAndFlashComponents(params Func<GUIComponent, bool>[] predicates) bool FindAndFlashComponents(IEnumerable<GUIComponent> components, params Func<GUIComponent, bool>[] predicates)
{ {
foreach (var predicate in predicates) foreach (var predicate in predicates)
{ {
if (HighlightMultiple) if (HighlightMultiple)
{ {
bool found = false; bool found = false;
foreach (var component in GUI.GetAdditions()) foreach (var component in components)
{ {
if (predicate(component)) if (predicate(component))
{ {
@@ -55,7 +67,7 @@ partial class UIHighlightAction : EventAction
}; };
return found; return found;
} }
else if (GUI.GetAdditions().FirstOrDefault(predicate) is GUIComponent component) else if (components.FirstOrDefault(predicate) is GUIComponent component)
{ {
Flash(component); Flash(component);
return true; return true;
@@ -64,6 +76,10 @@ partial class UIHighlightAction : EventAction
return false; return false;
} }
bool FindAndFlashAddedComponents(params Func<GUIComponent, bool>[] predicates) => FindAndFlashComponents(GUI.GetAdditions(), predicates);
bool FindAndFlashMessageBoxComponents(params Func<GUIComponent, bool>[] predicates) => FindAndFlashComponents(GUIMessageBox.VisibleBox?.GetAllChildren() ?? Enumerable.Empty<GUIComponent>(), predicates);
void Flash(GUIComponent component) void Flash(GUIComponent component)
{ {
if (component.FlashTimer <= 0.0f) if (component.FlashTimer <= 0.0f)
@@ -661,6 +661,8 @@ namespace Barotrauma
case NetworkEventType.MISSION: case NetworkEventType.MISSION:
Identifier missionIdentifier = msg.ReadIdentifier(); Identifier missionIdentifier = msg.ReadIdentifier();
int locationIndex = msg.ReadInt32(); int locationIndex = msg.ReadInt32();
int destinationIndex = msg.ReadInt32();
string missionName = msg.ReadString(); string missionName = msg.ReadString();
MissionPrefab? prefab = MissionPrefab.Prefabs.Find(mp => mp.Identifier == missionIdentifier); MissionPrefab? prefab = MissionPrefab.Prefabs.Find(mp => mp.Identifier == missionIdentifier);
if (prefab != null) if (prefab != null)
@@ -670,9 +672,25 @@ namespace Barotrauma
{ {
IconColor = prefab.IconColor IconColor = prefab.IconColor
}; };
if (GameMain.GameSession?.Map is { } map && locationIndex > 0 && locationIndex < map.Locations.Count) if (GameMain.GameSession?.Map is { } map && locationIndex >= 0 && locationIndex < map.Locations.Count)
{ {
map.Discover(map.Locations[locationIndex], checkTalents: false); Location location = map.Locations[locationIndex];
map.Discover(location, checkTalents: false);
LocationConnection? connection = null;
if (destinationIndex != locationIndex && destinationIndex >= 0 && destinationIndex < map.Locations.Count)
{
Location destination = map.Locations[destinationIndex];
connection = map.Connections.FirstOrDefault(c => c.Locations.Contains(location) && c.Locations.Contains(destination));
}
if (connection != null)
{
location.UnlockMission(prefab, connection);
}
else
{
location.UnlockMission(prefab);
}
} }
} }
break; break;
@@ -10,8 +10,7 @@ namespace Barotrauma
public override RichString GetMissionRewardText(Submarine sub) public override RichString GetMissionRewardText(Submarine sub)
{ {
LocalizedString rewardText = TextManager.GetWithVariable("currencyformat", "[credits]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", GetReward(sub))); LocalizedString rewardText = GetRewardAmountText(sub);
LocalizedString retVal; LocalizedString retVal;
if (rewardPerCrate.HasValue) if (rewardPerCrate.HasValue)
{ {
@@ -21,6 +21,7 @@ namespace Barotrauma
{ {
new CameraTransition(boss, GameMain.GameScreen.Cam, null, Alignment.Center, panDuration: 8, fadeOut: false, startZoom: 1.0f, endZoom: 0.3f * GUI.yScale) 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 EndWaitDuration = 3.0f
}; };
} }
@@ -40,6 +41,7 @@ namespace Barotrauma
{ {
new CameraTransition(boss, GameMain.GameScreen.Cam, null, Alignment.Center, panDuration: 3, fadeOut: false, endZoom: 0.1f * GUI.yScale) new CameraTransition(boss, GameMain.GameScreen.Cam, null, Alignment.Center, panDuration: 3, fadeOut: false, endZoom: 0.1f * GUI.yScale)
{ {
RunWhilePaused = false,
EndWaitDuration = float.PositiveInfinity EndWaitDuration = float.PositiveInfinity
}; };
}, delay: 3.0f); }, delay: 3.0f);
@@ -52,6 +54,7 @@ namespace Barotrauma
{ {
new CameraTransition(boss, GameMain.GameScreen.Cam, null, Alignment.Center, panDuration: 5.0f, fadeOut: false, losFadeIn: false, startZoom: 1.0f, endZoom: 0.4f * GUI.yScale) 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 EndWaitDuration = cameraWaitDuration
}; };
} }
@@ -32,10 +32,28 @@ namespace Barotrauma
return ToolBox.GradientLerp(t, GUIStyle.Green, GUIStyle.Orange, GUIStyle.Red); return ToolBox.GradientLerp(t, GUIStyle.Green, GUIStyle.Orange, GUIStyle.Red);
} }
/// <summary>
/// Returns the amount of marks you get from the reward (e.g. "3,000 mk")
/// </summary>
protected LocalizedString GetRewardAmountText(Submarine sub)
{
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);
}
/// <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) public virtual RichString GetMissionRewardText(Submarine sub)
{ {
LocalizedString rewardText = TextManager.GetWithVariable("currencyformat", "[credits]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", GetReward(sub))); LocalizedString rewardText = GetRewardAmountText(sub);
return RichString.Rich(TextManager.GetWithVariable("missionreward", "[reward]", "‖color:gui.orange‖"+rewardText+"‖end‖")); return RichString.Rich(TextManager.GetWithVariable("missionreward", "[reward]", "‖color:gui.orange‖" + rewardText + "‖end‖"));
} }
public RichString GetReputationRewardText() public RichString GetReputationRewardText()
@@ -43,27 +61,30 @@ namespace Barotrauma
List<LocalizedString> reputationRewardTexts = new List<LocalizedString>(); List<LocalizedString> reputationRewardTexts = new List<LocalizedString>();
foreach (var reputationReward in ReputationRewards) foreach (var reputationReward in ReputationRewards)
{ {
FactionPrefab targetFaction; FactionPrefab targetFactionPrefab;
if (reputationReward.Key == "location" ) if (reputationReward.Key == "location" )
{ {
targetFaction = OriginLocation.Faction?.Prefab; targetFactionPrefab = OriginLocation.Faction?.Prefab;
} }
else else
{ {
FactionPrefab.Prefabs.TryGet(reputationReward.Key, out targetFaction); FactionPrefab.Prefabs.TryGet(reputationReward.Key, out targetFactionPrefab);
} }
LocalizedString name; if (targetFactionPrefab == null)
if (targetFaction != null)
{ {
name = $"‖color:{XMLExtensions.ToStringHex(targetFaction.IconColor)}‖{targetFaction.Name}‖end‖"; return string.Empty;
} }
else
float totalReputationChange = reputationReward.Value;
if (GameMain.GameSession?.Campaign?.Factions.Find(f => f.Prefab == targetFactionPrefab) is Faction faction)
{ {
name = TextManager.Get(reputationReward.Key); totalReputationChange = reputationReward.Value * faction.Reputation.GetReputationChangeMultiplier(reputationReward.Value);
} }
float normalizedValue = MathUtils.InverseLerp(-100.0f, 100.0f, reputationReward.Value);
string formattedValue = ((int)reputationReward.Value).ToString("+#;-#;0"); //force plus sign for positive numbers LocalizedString name = $"‖color:{XMLExtensions.ToStringHex(targetFactionPrefab.IconColor)}‖{targetFactionPrefab.Name}‖end‖";
float normalizedValue = MathUtils.InverseLerp(-100.0f, 100.0f, totalReputationChange);
string formattedValue = ((int)Math.Round(totalReputationChange)).ToString("+#;-#;0"); //force plus sign for positive numbers
LocalizedString rewardText = TextManager.GetWithVariables( LocalizedString rewardText = TextManager.GetWithVariables(
"reputationformat", "reputationformat",
("[reputationname]", name), ("[reputationname]", name),
@@ -516,7 +516,7 @@ namespace Barotrauma
new GUIButton(new RectTransform(size, frame.RectTransform) { RelativeOffset = new Vector2(0.025f) }, style: null) new GUIButton(new RectTransform(size, frame.RectTransform) { RelativeOffset = new Vector2(0.025f) }, style: null)
{ {
Enabled = CanHire(characterInfo), Enabled = CanHire(characterInfo),
ToolTip = TextManager.GetWithVariable("campaigncrew.givenicknametooltip", "[mouseprimary]", TextManager.Get($"input.{(PlayerInput.MouseButtonsSwapped() ? "rightmouse" : "leftmouse")}")), ToolTip = TextManager.GetWithVariable("campaigncrew.givenicknametooltip", "[mouseprimary]", PlayerInput.PrimaryMouseLabel),
UserData = characterInfo, UserData = characterInfo,
OnClicked = CreateRenamingComponent OnClicked = CreateRenamingComponent
}; };
@@ -106,6 +106,11 @@ namespace Barotrauma
public static float VerticalAspectRatio => GameMain.GraphicsHeight / (float)GameMain.GraphicsWidth; public static float VerticalAspectRatio => GameMain.GraphicsHeight / (float)GameMain.GraphicsWidth;
public static float RelativeHorizontalAspectRatio => HorizontalAspectRatio / (ReferenceResolution.X / ReferenceResolution.Y); public static float RelativeHorizontalAspectRatio => HorizontalAspectRatio / (ReferenceResolution.X / ReferenceResolution.Y);
public static float RelativeVerticalAspectRatio => VerticalAspectRatio / (ReferenceResolution.Y / ReferenceResolution.X); public static float RelativeVerticalAspectRatio => VerticalAspectRatio / (ReferenceResolution.Y / ReferenceResolution.X);
/// <summary>
/// A horizontal scaling factor for low aspect ratios (small width relative to height)
/// </summary>
public static float AspectRatioAdjustment => HorizontalAspectRatio < 1.4f ? (1.0f - (1.4f - HorizontalAspectRatio)) : 1.0f;
public static bool IsUltrawide => HorizontalAspectRatio > 2.0f; public static bool IsUltrawide => HorizontalAspectRatio > 2.0f;
public static int UIWidth public static int UIWidth
@@ -2586,8 +2591,11 @@ namespace Barotrauma
public static void AddMessage(string message, Color color, float? lifeTime = null, bool playSound = true, GUIFont font = null) public static void AddMessage(string message, Color color, float? lifeTime = null, bool playSound = true, GUIFont font = null)
{ {
if (messages.Any(msg => msg.Text == message)) { return; } lock (mutex)
messages.Add(new GUIMessage(message, color, lifeTime ?? MathHelper.Clamp(message.Length / 5.0f, 3.0f, 10.0f), font ?? GUIStyle.LargeFont)); {
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));
}
if (playSound) { SoundPlayer.PlayUISound(GUISoundType.UIMessage); } if (playSound) { SoundPlayer.PlayUISound(GUISoundType.UIMessage); }
} }
@@ -2597,34 +2605,37 @@ namespace Barotrauma
var newMessage = new GUIMessage(message, color, pos, velocity, lifeTime, Alignment.Center, GUIStyle.Font, sub: sub); var newMessage = new GUIMessage(message, color, pos, velocity, lifeTime, Alignment.Center, GUIStyle.Font, sub: sub);
if (playSound) { SoundPlayer.PlayUISound(soundType); } if (playSound) { SoundPlayer.PlayUISound(soundType); }
bool overlapFound = true;
int tries = 0;
while (overlapFound)
{
overlapFound = false;
foreach (var otherMessage in messages)
{
float xDiff = otherMessage.Pos.X - newMessage.Pos.X;
if (Math.Abs(xDiff) > (newMessage.Size.X + otherMessage.Size.X) / 2) { continue; }
float yDiff = otherMessage.Pos.Y - newMessage.Pos.Y;
if (Math.Abs(yDiff) > (newMessage.Size.Y + otherMessage.Size.Y) / 2) { continue; }
Vector2 moveDir = -(new Vector2(xDiff, yDiff) + Rand.Vector(1.0f));
if (moveDir.LengthSquared() > 0.0001f)
{
moveDir = Vector2.Normalize(moveDir);
}
else
{
moveDir = Rand.Vector(1.0f);
}
moveDir.Y = -Math.Abs(moveDir.Y);
newMessage.Pos -= Vector2.UnitY * 10;
}
tries++;
if (tries > 20) { break; }
}
messages.Add(newMessage); lock (mutex)
{
bool overlapFound = true;
int tries = 0;
while (overlapFound)
{
overlapFound = false;
foreach (var otherMessage in messages)
{
float xDiff = otherMessage.Pos.X - newMessage.Pos.X;
if (Math.Abs(xDiff) > (newMessage.Size.X + otherMessage.Size.X) / 2) { continue; }
float yDiff = otherMessage.Pos.Y - newMessage.Pos.Y;
if (Math.Abs(yDiff) > (newMessage.Size.Y + otherMessage.Size.Y) / 2) { continue; }
Vector2 moveDir = -(new Vector2(xDiff, yDiff) + Rand.Vector(1.0f));
if (moveDir.LengthSquared() > 0.0001f)
{
moveDir = Vector2.Normalize(moveDir);
}
else
{
moveDir = Rand.Vector(1.0f);
}
moveDir.Y = -Math.Abs(moveDir.Y);
newMessage.Pos -= Vector2.UnitY * 10;
}
tries++;
if (tries > 20) { break; }
}
messages.Add(newMessage);
}
} }
public static void ClearMessages() public static void ClearMessages()
@@ -8,9 +8,7 @@ namespace Barotrauma
{ {
public class GUICanvas : RectTransform public class GUICanvas : RectTransform
{ {
private static readonly object mutex = new object(); protected GUICanvas() : base(Size, parent: null) { }
protected GUICanvas() : base(size, parent: null) { }
private static GUICanvas _instance; private static GUICanvas _instance;
public static GUICanvas Instance public static GUICanvas Instance
@@ -33,7 +31,7 @@ namespace Barotrauma
//GUICanvas stores the children as weak references, to allow elements that we no longer need to get garbage collected //GUICanvas stores the children as weak references, to allow elements that we no longer need to get garbage collected
private readonly List<WeakReference<RectTransform>> childrenWeakRef = new List<WeakReference<RectTransform>>(); private readonly List<WeakReference<RectTransform>> childrenWeakRef = new List<WeakReference<RectTransform>>();
private static Vector2 size => new Vector2(GameMain.GraphicsWidth / (float)GUI.UIWidth, 1f); private static Vector2 Size => new Vector2(GameMain.GraphicsWidth / (float)GUI.UIWidth, 1f);
protected override Rectangle NonScaledUIRect => UIRect; protected override Rectangle NonScaledUIRect => UIRect;
@@ -41,25 +39,27 @@ namespace Barotrauma
private static void OnChildrenChanged(RectTransform _) private static void OnChildrenChanged(RectTransform _)
{ {
lock (mutex) CrossThread.RequestExecutionOnMainThread(RefreshChildren);
}
private static void RefreshChildren()
{
//add weak reference if we don't have one yet
foreach (var child in _instance.Children)
{ {
//add weak reference if we don't have one yet if (!_instance.childrenWeakRef.Any(c => c.TryGetTarget(out var existingChild) && existingChild == child))
foreach (var child in _instance.Children)
{ {
if (!_instance.childrenWeakRef.Any(c => c.TryGetTarget(out var existingChild) && existingChild == child)) _instance.childrenWeakRef.Add(new WeakReference<RectTransform>(child));
{
_instance.childrenWeakRef.Add(new WeakReference<RectTransform>(child));
}
} }
//get rid of strong references }
_instance.children.Clear(); //get rid of strong references
//remove dead children _instance.children.Clear();
for (int i = _instance.childrenWeakRef.Count - 2; i >= 0; i--) //remove dead children
for (int i = _instance.childrenWeakRef.Count - 1; i >= 0; i--)
{
if (!_instance.childrenWeakRef[i].TryGetTarget(out var child) || child.Parent != _instance)
{ {
if (!_instance.childrenWeakRef[i].TryGetTarget(out var child) || child.Parent != _instance) _instance.childrenWeakRef.RemoveAt(i);
{
_instance.childrenWeakRef.RemoveAt(i);
}
} }
} }
} }
@@ -67,7 +67,7 @@ namespace Barotrauma
// Turn public, if there is a need to call this manually. // Turn public, if there is a need to call this manually.
private static void RecalculateSize() private static void RecalculateSize()
{ {
Vector2 recalculatedSize = size; Vector2 recalculatedSize = Size;
// Scale children that are supposed to encompass the whole screen so that they are properly scaled on ultrawide as well // Scale children that are supposed to encompass the whole screen so that they are properly scaled on ultrawide as well
for (int i = 0; i < Instance.childrenWeakRef.Count; i++) for (int i = 0; i < Instance.childrenWeakRef.Count; i++)
@@ -109,7 +109,7 @@ namespace Barotrauma
} }
} }
Instance.Resize(size, resizeChildren: true); Instance.Resize(Size, resizeChildren: true);
Instance.GetAllChildren().Select(c => c.GUIComponent as GUITextBlock).ForEach(t => t?.SetTextPos()); Instance.GetAllChildren().Select(c => c.GUIComponent as GUITextBlock).ForEach(t => t?.SetTextPos());
_instance.children.Clear(); _instance.children.Clear();
} }
@@ -1143,14 +1143,13 @@ namespace Barotrauma
bool wrap = element.GetAttributeBool("wrap", true); bool wrap = element.GetAttributeBool("wrap", true);
Alignment alignment = Alignment alignment =
element.GetAttributeEnum("alignment", text.Contains('\n') ? Alignment.Left : Alignment.Center); element.GetAttributeEnum("alignment", text.Contains('\n') ? Alignment.Left : Alignment.Center);
GUIFont font; if (!GUIStyle.Fonts.TryGetValue(element.GetAttributeIdentifier("font", "Font"), out GUIFont font))
if (!GUIStyle.Fonts.TryGetValue(element.GetAttributeIdentifier("font", "Font"), out font))
{ {
font = GUIStyle.Font; font = GUIStyle.Font;
} }
var textBlock = new GUITextBlock(RectTransform.Load(element, parent), var textBlock = new GUITextBlock(RectTransform.Load(element, parent),
text, color, font, alignment, wrap: wrap, style: style) RichString.Rich(text), color, font, alignment, wrap: wrap, style: style)
{ {
TextScale = scale TextScale = scale
}; };
@@ -244,18 +244,16 @@ namespace Barotrauma
return parentHierarchy.Last(); return parentHierarchy.Last();
} }
public void AddItem(LocalizedString text, object userData = null, LocalizedString toolTip = null) public GUIComponent AddItem(LocalizedString text, object userData = null, LocalizedString toolTip = null, Color? color = null, Color? textColor = null)
{ {
toolTip ??= ""; toolTip ??= "";
if (selectMultiple) if (selectMultiple)
{ {
var frame = new GUIFrame(new RectTransform(new Point(button.Rect.Width, button.Rect.Height), listBox.Content.RectTransform) var frame = new GUIFrame(new RectTransform(new Point(button.Rect.Width, button.Rect.Height), listBox.Content.RectTransform) { IsFixedSize = false }, style: "ListBoxElement", color: color)
{ IsFixedSize = false }, style: "ListBoxElement")
{ {
UserData = userData, UserData = userData,
ToolTip = toolTip ToolTip = toolTip
}; };
new GUITickBox(new RectTransform(new Vector2(1.0f, 0.8f), frame.RectTransform, anchor: Anchor.CenterLeft) { MaxSize = new Point(int.MaxValue, (int)(button.Rect.Height * 0.8f)) }, text) new GUITickBox(new RectTransform(new Vector2(1.0f, 0.8f), frame.RectTransform, anchor: Anchor.CenterLeft) { MaxSize = new Point(int.MaxValue, (int)(button.Rect.Height * 0.8f)) }, text)
{ {
UserData = userData, UserData = userData,
@@ -275,7 +273,7 @@ namespace Barotrauma
foreach (GUIComponent child in ListBox.Content.Children) foreach (GUIComponent child in ListBox.Content.Children)
{ {
var tickBox = child.GetChild<GUITickBox>(); var tickBox = child.GetChild<GUITickBox>();
if (tickBox.Selected) if (tickBox is { Selected: true })
{ {
selectedDataMultiple.Add(child.UserData); selectedDataMultiple.Add(child.UserData);
selectedIndexMultiple.Add(i); selectedIndexMultiple.Add(i);
@@ -289,11 +287,11 @@ namespace Barotrauma
return true; return true;
} }
}; };
return frame;
} }
else else
{ {
new GUITextBlock(new RectTransform(new Point(button.Rect.Width, button.Rect.Height), listBox.Content.RectTransform) return new GUITextBlock(new RectTransform(new Point(button.Rect.Width, button.Rect.Height), listBox.Content.RectTransform) { IsFixedSize = false }, text, style: "ListBoxElement", color: color, textColor: textColor)
{ IsFixedSize = false }, text, style: "ListBoxElement")
{ {
UserData = userData, UserData = userData,
ToolTip = toolTip ToolTip = toolTip
@@ -323,7 +321,7 @@ namespace Barotrauma
} }
else else
{ {
if (!(component is GUITextBlock textBlock)) if (component is not GUITextBlock textBlock)
{ {
textBlock = component.GetChild<GUITextBlock>(); textBlock = component.GetChild<GUITextBlock>();
if (textBlock is null && !AllowNonText) { return false; } if (textBlock is null && !AllowNonText) { return false; }
@@ -1059,6 +1059,7 @@ namespace Barotrauma
GUIComponent child = Content.GetChild(childIndex); GUIComponent child = Content.GetChild(childIndex);
if (child is null) { return; } if (child is null) { return; }
if (!child.Enabled) { return; }
bool wasSelected = true; bool wasSelected = true;
if (OnSelected != null) if (OnSelected != null)
@@ -236,7 +236,8 @@ namespace Barotrauma
new GUIButton(new RectTransform(new Vector2(0.3f, 0.5f), buttonContainer.RectTransform, Anchor.Center), new GUIButton(new RectTransform(new Vector2(0.3f, 0.5f), buttonContainer.RectTransform, Anchor.Center),
style: "UIToggleButton") style: "UIToggleButton")
{ {
OnClicked = Close OnClicked = Close,
UserData = UIHighlightAction.ElementId.MessageBoxCloseButton
} }
}; };
InputType? closeInput = null; InputType? closeInput = null;
@@ -313,7 +313,9 @@ namespace Barotrauma
break; break;
} }
RectTransform.MinSize = TextBox.RectTransform.MinSize; RectTransform.MinSize = new Point(
Math.Max(rectT.MinSize.X, TextBox.RectTransform.MinSize.X),
Math.Max(rectT.MinSize.Y, TextBox.RectTransform.MinSize.Y));
LayoutGroup.Recalculate(); LayoutGroup.Recalculate();
} }
@@ -140,6 +140,7 @@ namespace Barotrauma
public readonly static GUIColor HealthBarColorLow = new GUIColor("HealthBarColorLow"); public readonly static GUIColor HealthBarColorLow = new GUIColor("HealthBarColorLow");
public readonly static GUIColor HealthBarColorMedium = new GUIColor("HealthBarColorMedium"); public readonly static GUIColor HealthBarColorMedium = new GUIColor("HealthBarColorMedium");
public readonly static GUIColor HealthBarColorHigh = new GUIColor("HealthBarColorHigh"); public readonly static GUIColor HealthBarColorHigh = new GUIColor("HealthBarColorHigh");
public readonly static GUIColor HealthBarColorPoisoned = new GUIColor("HealthBarColorPoisoned");
public static Point ItemFrameMargin public static Point ItemFrameMargin
{ {
@@ -438,7 +438,7 @@ namespace Barotrauma
} }
else else
{ {
if ((PlayerInput.LeftButtonClicked() || PlayerInput.RightButtonClicked()) && selected) if ((PlayerInput.PrimaryMouseButtonClicked() || PlayerInput.SecondaryMouseButtonClicked()) && selected)
{ {
if (!mouseHeldInside) { Deselect(); } if (!mouseHeldInside) { Deselect(); }
mouseHeldInside = false; mouseHeldInside = false;
@@ -149,8 +149,7 @@ namespace Barotrauma
int messageAreaWidth = GameMain.GraphicsWidth / 3; int messageAreaWidth = GameMain.GraphicsWidth / 3;
MessageAreaTop = new Rectangle((GameMain.GraphicsWidth - messageAreaWidth) / 2, ButtonAreaTop.Bottom + ButtonAreaTop.Height, messageAreaWidth, ButtonAreaTop.Height); MessageAreaTop = new Rectangle((GameMain.GraphicsWidth - messageAreaWidth) / 2, ButtonAreaTop.Bottom + ButtonAreaTop.Height, messageAreaWidth, ButtonAreaTop.Height);
bool isFourByThree = GUI.IsFourByThree(); int chatBoxWidth = (int)(475 * GUI.Scale * GUI.AspectRatioAdjustment);
int chatBoxWidth = !isFourByThree ? (int)(475 * GUI.Scale) : (int)(375 * GUI.Scale);
int chatBoxHeight = (int)Math.Max(GameMain.GraphicsHeight * 0.25f, 150); int chatBoxHeight = (int)Math.Max(GameMain.GraphicsHeight * 0.25f, 150);
ChatBoxArea = new Rectangle(Padding, GameMain.GraphicsHeight - Padding - chatBoxHeight, chatBoxWidth, chatBoxHeight); ChatBoxArea = new Rectangle(Padding, GameMain.GraphicsHeight - Padding - chatBoxHeight, chatBoxWidth, chatBoxHeight);
@@ -187,19 +186,26 @@ namespace Barotrauma
public static void Draw(SpriteBatch spriteBatch) public static void Draw(SpriteBatch spriteBatch)
{ {
DrawRectangle(ButtonAreaTop, Color.White * 0.5f); DrawRectangle(nameof(ButtonAreaTop), ButtonAreaTop, Color.White * 0.5f);
DrawRectangle(TutorialObjectiveListArea, GUIStyle.Blue * 0.5f); DrawRectangle(nameof(TutorialObjectiveListArea), TutorialObjectiveListArea, GUIStyle.Blue * 0.5f);
DrawRectangle(MessageAreaTop, GUIStyle.Orange * 0.5f); DrawRectangle(nameof(MessageAreaTop), MessageAreaTop, GUIStyle.Orange * 0.5f);
DrawRectangle(CrewArea, Color.Blue * 0.5f); DrawRectangle(nameof(CrewArea), CrewArea, Color.Blue * 0.5f);
DrawRectangle(ChatBoxArea, Color.Cyan * 0.5f); DrawRectangle(nameof(ChatBoxArea), ChatBoxArea, Color.Cyan * 0.5f);
DrawRectangle(HealthBarArea, Color.Red * 0.5f); DrawRectangle(nameof(HealthBarArea), HealthBarArea, Color.Red * 0.5f);
DrawRectangle(HealthBarAfflictionArea, Color.Red * 0.5f); DrawRectangle(nameof(HealthBarAfflictionArea), HealthBarAfflictionArea, Color.Red * 0.5f);
DrawRectangle(InventoryAreaLower, Color.Yellow * 0.5f); DrawRectangle(nameof(InventoryAreaLower), InventoryAreaLower, Color.Yellow * 0.5f);
DrawRectangle(HealthWindowAreaLeft, Color.Red * 0.5f); DrawRectangle(nameof(HealthWindowAreaLeft), HealthWindowAreaLeft, Color.Red * 0.5f);
DrawRectangle(BottomRightInfoArea, Color.Green * 0.5f); DrawRectangle(nameof(BottomRightInfoArea), BottomRightInfoArea, Color.Green * 0.5f);
DrawRectangle(ItemHUDArea, Color.Magenta * 0.3f); DrawRectangle(nameof(ItemHUDArea), ItemHUDArea, Color.Magenta * 0.3f);
void DrawRectangle(Rectangle r, Color c) => GUI.DrawRectangle(spriteBatch, r, c); void DrawRectangle(string label, Rectangle r, Color c)
{
if (!label.IsNullOrEmpty())
{
GUI.DrawString(spriteBatch, r.Location.ToVector2() + Vector2.One * 3, label, c, font: GUIStyle.SmallFont);
}
GUI.DrawRectangle(spriteBatch, r, c);
}
} }
} }
@@ -143,33 +143,32 @@ namespace Barotrauma
{ {
public readonly MedicalClinic.NetAffliction Target; public readonly MedicalClinic.NetAffliction Target;
public readonly ImmutableArray<GUIComponent> ElementsToDisable; public readonly ImmutableArray<GUIComponent> ElementsToDisable;
public readonly GUIComponent TargetElement;
public PopupAffliction(ImmutableArray<GUIComponent> elementsToDisable, MedicalClinic.NetAffliction target) public PopupAffliction(ImmutableArray<GUIComponent> elementsToDisable, GUIComponent component, MedicalClinic.NetAffliction target)
{ {
Target = target; Target = target;
ElementsToDisable = elementsToDisable; ElementsToDisable = elementsToDisable;
TargetElement = component;
} }
} }
private readonly struct PopupAfflictionList private readonly struct PopupAfflictionList
{ {
public readonly MedicalClinic.NetCrewMember Target; public readonly MedicalClinic.NetCrewMember Target;
public readonly GUIListBox ListElement;
public readonly GUIButton TreatAllButton; public readonly GUIButton TreatAllButton;
public readonly List<PopupAffliction> Afflictions; public readonly HashSet<PopupAffliction> Afflictions;
public PopupAfflictionList(MedicalClinic.NetCrewMember crewMember, GUIButton treatAllButton) public PopupAfflictionList(MedicalClinic.NetCrewMember crewMember, GUIListBox listElement, GUIButton treatAllButton)
{ {
ListElement = listElement;
Target = crewMember; Target = crewMember;
TreatAllButton = treatAllButton; TreatAllButton = treatAllButton;
Afflictions = new List<PopupAffliction>(); Afflictions = new HashSet<PopupAffliction>();
} }
} }
// private enum SortMode
// {
// Severity
// }
private readonly MedicalClinic medicalClinic; private readonly MedicalClinic medicalClinic;
private readonly GUIComponent container; private readonly GUIComponent container;
private Point prevResolution; private Point prevResolution;
@@ -221,23 +220,22 @@ namespace Barotrauma
private void UpdatePopupAfflictions() private void UpdatePopupAfflictions()
{ {
if (selectedCrewAfflictionList is { } afflictionList) if (selectedCrewAfflictionList is not { } afflictionList) { return; }
{
foreach (PopupAffliction popupAffliction in afflictionList.Afflictions)
{
ToggleElements(ElementState.Enabled, popupAffliction.ElementsToDisable);
if (medicalClinic.IsAfflictionPending(afflictionList.Target, popupAffliction.Target))
{
ToggleElements(ElementState.Disabled, popupAffliction.ElementsToDisable);
}
}
afflictionList.TreatAllButton.Enabled = true; foreach (PopupAffliction popupAffliction in afflictionList.Afflictions)
if (afflictionList.Afflictions.All(a => medicalClinic.IsAfflictionPending(afflictionList.Target, a.Target))) {
ToggleElements(ElementState.Enabled, popupAffliction.ElementsToDisable);
if (medicalClinic.IsAfflictionPending(afflictionList.Target, popupAffliction.Target))
{ {
afflictionList.TreatAllButton.Enabled = false; ToggleElements(ElementState.Disabled, popupAffliction.ElementsToDisable);
} }
} }
afflictionList.TreatAllButton.Enabled = true;
if (afflictionList.Afflictions.All(a => medicalClinic.IsAfflictionPending(afflictionList.Target, a.Target)))
{
afflictionList.TreatAllButton.Enabled = false;
}
} }
private void UpdatePending() private void UpdatePending()
@@ -309,7 +307,7 @@ namespace Barotrauma
} }
} }
private void UpdateCrewPanel() public void UpdateCrewPanel()
{ {
if (crewHealList is not { } healList) { return; } if (crewHealList is not { } healList) { return; }
@@ -789,7 +787,7 @@ namespace Barotrauma
GUIListBox afflictionList = new GUIListBox(new RectTransform(new Vector2(1f, 0.8f), mainLayout.RectTransform)) { Visible = false }; GUIListBox afflictionList = new GUIListBox(new RectTransform(new Vector2(1f, 0.8f), mainLayout.RectTransform)) { Visible = false };
PopupAfflictionList popupAfflictionList = new PopupAfflictionList(crewMember, treatAllButton); PopupAfflictionList popupAfflictionList = new PopupAfflictionList(crewMember, afflictionList, treatAllButton);
selectedCrewElement = mainFrame; selectedCrewElement = mainFrame;
selectedCrewAfflictionList = popupAfflictionList; selectedCrewAfflictionList = popupAfflictionList;
@@ -810,9 +808,9 @@ namespace Barotrauma
List<GUIComponent> allComponents = new List<GUIComponent>(); List<GUIComponent> allComponents = new List<GUIComponent>();
foreach (MedicalClinic.NetAffliction affliction in request.Afflictions) foreach (MedicalClinic.NetAffliction affliction in request.Afflictions)
{ {
ImmutableArray<GUIComponent> createdComponents = CreatePopupAffliction(afflictionList.Content, crewMember, affliction); CreatedPopupAfflictionElement createdComponents = CreatePopupAffliction(afflictionList.Content, crewMember, affliction);
allComponents.AddRange(createdComponents); allComponents.AddRange(createdComponents.AllCreatedElements);
popupAfflictionList.Afflictions.Add(new PopupAffliction(createdComponents, affliction)); popupAfflictionList.Afflictions.Add(new PopupAffliction(createdComponents.AllCreatedElements, createdComponents.MainElement, affliction));
} }
allComponents.Add(treatAllButton); allComponents.Add(treatAllButton);
@@ -832,9 +830,11 @@ namespace Barotrauma
} }
} }
private ImmutableArray<GUIComponent> CreatePopupAffliction(GUIComponent parent, MedicalClinic.NetCrewMember crewMember, MedicalClinic.NetAffliction affliction) private readonly record struct CreatedPopupAfflictionElement(GUIComponent MainElement, ImmutableArray<GUIComponent> AllCreatedElements);
private CreatedPopupAfflictionElement CreatePopupAffliction(GUIComponent parent, MedicalClinic.NetCrewMember crewMember, MedicalClinic.NetAffliction affliction)
{ {
if (!(affliction.Prefab is { } prefab)) { return ImmutableArray<GUIComponent>.Empty; } ToolBox.ThrowIfNull(affliction.Prefab);
GUIFrame backgroundFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.33f), parent.RectTransform), style: "ListBoxElement"); GUIFrame backgroundFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.33f), parent.RectTransform), style: "ListBoxElement");
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.01f), backgroundFrame.RectTransform, Anchor.BottomCenter), style: "HorizontalLine"); new GUIFrame(new RectTransform(new Vector2(1.0f, 0.01f), backgroundFrame.RectTransform, Anchor.BottomCenter), style: "HorizontalLine");
@@ -846,9 +846,9 @@ namespace Barotrauma
GUILayoutGroup topLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.33f), mainLayout.RectTransform), isHorizontal: true) { Stretch = true }; GUILayoutGroup topLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.33f), mainLayout.RectTransform), isHorizontal: true) { Stretch = true };
Color iconColor = CharacterHealth.GetAfflictionIconColor(prefab, affliction.Strength); Color iconColor = CharacterHealth.GetAfflictionIconColor(affliction.Prefab, affliction.Strength);
GUIImage icon = new GUIImage(new RectTransform(Vector2.One, topLayout.RectTransform, scaleBasis: ScaleBasis.BothHeight), prefab.Icon, scaleToFit: true) GUIImage icon = new GUIImage(new RectTransform(Vector2.One, topLayout.RectTransform, scaleBasis: ScaleBasis.BothHeight), affliction.Prefab.Icon, scaleToFit: true)
{ {
Color = iconColor, Color = iconColor,
DisabledColor = iconColor * 0.5f DisabledColor = iconColor * 0.5f
@@ -856,7 +856,7 @@ namespace Barotrauma
GUILayoutGroup topTextLayout = new GUILayoutGroup(new RectTransform(Vector2.One, topLayout.RectTransform), isHorizontal: true); GUILayoutGroup topTextLayout = new GUILayoutGroup(new RectTransform(Vector2.One, topLayout.RectTransform), isHorizontal: true);
GUITextBlock prefabBlock = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1f), topTextLayout.RectTransform), prefab.Name, font: GUIStyle.SubHeadingFont); GUITextBlock prefabBlock = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1f), topTextLayout.RectTransform), affliction.Prefab.Name, font: GUIStyle.SubHeadingFont);
Color textColor = Color.Lerp(GUIStyle.Orange, GUIStyle.Red, affliction.Strength / affliction.Prefab.MaxStrength); Color textColor = Color.Lerp(GUIStyle.Orange, GUIStyle.Red, affliction.Strength / affliction.Prefab.MaxStrength);
@@ -878,7 +878,7 @@ namespace Barotrauma
AutoScaleHorizontal = true AutoScaleHorizontal = true
}; };
EnsureTextDoesntOverflow(prefab.Name.Value, prefabBlock, prefabBlock.Rect, ImmutableArray.Create(mainLayout, topLayout, topTextLayout)); EnsureTextDoesntOverflow(affliction.Prefab.Name.Value, prefabBlock, prefabBlock.Rect, ImmutableArray.Create(mainLayout, topLayout, topTextLayout));
GUILayoutGroup bottomLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.66f), mainLayout.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft); GUILayoutGroup bottomLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.66f), mainLayout.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft);
@@ -923,7 +923,7 @@ namespace Barotrauma
return true; return true;
}; };
return elementsToDisable; return new CreatedPopupAfflictionElement(backgroundFrame, elementsToDisable);
} }
private void AddPending(ImmutableArray<GUIComponent> elementsToDisable, MedicalClinic.NetCrewMember crewMember, ImmutableArray<MedicalClinic.NetAffliction> afflictions) private void AddPending(ImmutableArray<GUIComponent> elementsToDisable, MedicalClinic.NetCrewMember crewMember, ImmutableArray<MedicalClinic.NetAffliction> afflictions)
@@ -1033,11 +1033,53 @@ namespace Barotrauma
} }
} }
public void UpdateAfflictions(MedicalClinic.NetCrewMember crewMember)
{
if (selectedCrewAfflictionList is not { } afflictionList || !afflictionList.Target.CharacterEquals(crewMember)) { return; }
List<GUIComponent> allComponents = new List<GUIComponent>();
foreach (PopupAffliction existingAffliction in afflictionList.Afflictions.ToHashSet())
{
if (crewMember.Afflictions.None(received => received.AfflictionEquals(existingAffliction.Target)))
{
// remove from UI
existingAffliction.TargetElement.RectTransform.Parent = null;
afflictionList.Afflictions.Remove(existingAffliction);
}
else
{
allComponents.AddRange(existingAffliction.ElementsToDisable);
}
}
foreach (MedicalClinic.NetAffliction received in crewMember.Afflictions)
{
// we're not that concerned about updating the strength of the afflictions
if (afflictionList.Afflictions.Any(existing => existing.Target.AfflictionEquals(received))) { continue; }
CreatedPopupAfflictionElement createdComponents = CreatePopupAffliction(afflictionList.ListElement.Content, crewMember, received);
allComponents.AddRange(createdComponents.AllCreatedElements);
afflictionList.Afflictions.Add(new PopupAffliction(createdComponents.AllCreatedElements, createdComponents.MainElement, received));
}
allComponents.Add(afflictionList.TreatAllButton);
afflictionList.TreatAllButton.OnClicked = (_, _) =>
{
var afflictions = crewMember.Afflictions.Where(a => !medicalClinic.IsAfflictionPending(crewMember, a)).ToImmutableArray();
if (!afflictions.Any()) { return true; }
AddPending(allComponents.ToImmutableArray(), crewMember, afflictions);
return true;
};
UpdatePopupAfflictions();
}
public void ClosePopup() public void ClosePopup()
{ {
if (selectedCrewElement is { } popup) if (selectedCrewElement is { } popup)
{ {
popup.Parent?.RemoveChild(selectedCrewElement); popup.RectTransform.Parent = null;
} }
selectedCrewElement = null; selectedCrewElement = null;
@@ -1096,5 +1138,14 @@ namespace Barotrauma
refreshTimer = 0; refreshTimer = 0;
} }
} }
public void OnDeselected()
{
if (GameMain.NetworkMember is not null)
{
MedicalClinic.SendUnsubscribeRequest();
}
ClosePopup();
}
} }
} }
@@ -207,11 +207,12 @@ namespace Barotrauma
cargoManager.OnItemsInSellFromSubCrateChanged.RegisterOverwriteExisting(refreshStoreId, _ => needsSellingFromSubRefresh = true); cargoManager.OnItemsInSellFromSubCrateChanged.RegisterOverwriteExisting(refreshStoreId, _ => needsSellingFromSubRefresh = true);
} }
public void SelectStore(Identifier identifier) public void SelectStore(Character merchant)
{ {
Identifier storeIdentifier = merchant?.MerchantIdentifier ?? Identifier.Empty;
if (CurrentLocation?.Stores != null) if (CurrentLocation?.Stores != null)
{ {
if (!identifier.IsEmpty && CurrentLocation.GetStore(identifier) is { } store) if (!storeIdentifier.IsEmpty && CurrentLocation.GetStore(storeIdentifier) is { } store)
{ {
ActiveStore = store; ActiveStore = store;
if (storeNameBlock != null) if (storeNameBlock != null)
@@ -223,12 +224,13 @@ namespace Barotrauma
} }
storeNameBlock.SetRichText(storeName); storeNameBlock.SetRichText(storeName);
} }
ActiveStore.SetMerchantFaction(merchant.Faction);
} }
else else
{ {
ActiveStore = null; ActiveStore = null;
string errorId, msg; string errorId, msg;
if (identifier.IsEmpty) if (storeIdentifier.IsEmpty)
{ {
errorId = "Store.SelectStore:IdentifierEmpty"; errorId = "Store.SelectStore:IdentifierEmpty";
msg = $"Error selecting store at {CurrentLocation}: identifier is empty."; msg = $"Error selecting store at {CurrentLocation}: identifier is empty.";
@@ -236,7 +238,7 @@ namespace Barotrauma
else else
{ {
errorId = "Store.SelectStore:StoreDoesntExist"; errorId = "Store.SelectStore:StoreDoesntExist";
msg = $"Error selecting store with identifier \"{identifier}\" at {CurrentLocation}: store with the identifier doesn't exist at the location."; msg = $"Error selecting store with identifier \"{storeIdentifier}\" at {CurrentLocation}: store with the identifier doesn't exist at the location.";
} }
DebugConsole.LogError(msg); DebugConsole.LogError(msg);
GameAnalyticsManager.AddErrorEventOnce(errorId, GameAnalyticsManager.ErrorSeverity.Error, msg); GameAnalyticsManager.AddErrorEventOnce(errorId, GameAnalyticsManager.ErrorSeverity.Error, msg);
@@ -249,17 +251,17 @@ namespace Barotrauma
if (campaignUI.Campaign.Map == null) if (campaignUI.Campaign.Map == null)
{ {
errorId = "Store.SelectStore:MapNull"; errorId = "Store.SelectStore:MapNull";
msg = $"Error selecting store with identifier \"{identifier}\": Map is null."; msg = $"Error selecting store with identifier \"{storeIdentifier}\": Map is null.";
} }
else if (CurrentLocation == null) else if (CurrentLocation == null)
{ {
errorId = "Store.SelectStore:CurrentLocationNull"; errorId = "Store.SelectStore:CurrentLocationNull";
msg = $"Error selecting store with identifier \"{identifier}\": CurrentLocation is null."; msg = $"Error selecting store with identifier \"{storeIdentifier}\": CurrentLocation is null.";
} }
else if (CurrentLocation.Stores == null) else if (CurrentLocation.Stores == null)
{ {
errorId = "Store.SelectStore:StoresNull"; errorId = "Store.SelectStore:StoresNull";
msg = $"Error selecting store with identifier \"{identifier}\": CurrentLocation.Stores is null."; msg = $"Error selecting store with identifier \"{storeIdentifier}\": CurrentLocation.Stores is null.";
} }
if (!msg.IsNullOrEmpty()) if (!msg.IsNullOrEmpty())
{ {
@@ -406,11 +408,11 @@ namespace Barotrauma
TextScale = 1.1f, TextScale = 1.1f,
TextGetter = () => TextGetter = () =>
{ {
if (CurrentLocation != null) if (ActiveStore is not null)
{ {
Color textColor = GUIStyle.ColorReputationNeutral; Color textColor = GUIStyle.ColorReputationNeutral;
string sign = ""; string sign = "";
int reputationModifier = (int)MathF.Round((CurrentLocation.GetStoreReputationModifier(activeTab == StoreTab.Buy) - 1) * 100); int reputationModifier = (int)MathF.Round((ActiveStore.GetReputationModifier(activeTab == StoreTab.Buy) - 1) * 100);
if (reputationModifier > 0) if (reputationModifier > 0)
{ {
textColor = IsBuying ? GUIStyle.ColorReputationLow : GUIStyle.ColorReputationHigh; textColor = IsBuying ? GUIStyle.ColorReputationLow : GUIStyle.ColorReputationHigh;
@@ -1903,7 +1905,7 @@ namespace Barotrauma
LocalizedString toolTip = string.Empty; LocalizedString toolTip = string.Empty;
if (purchasedItem.ItemPrefab != null) if (purchasedItem.ItemPrefab != null)
{ {
toolTip = purchasedItem.ItemPrefab.GetTooltip(); toolTip = purchasedItem.ItemPrefab.GetTooltip(Character.Controlled);
if (itemQuantity != null) if (itemQuantity != null)
{ {
if (itemQuantity.AllNonEmpty) if (itemQuantity.AllNonEmpty)
@@ -15,8 +15,6 @@ namespace Barotrauma
private int pageCount; private int pageCount;
private readonly bool transferService, purchaseService; private readonly bool transferService, purchaseService;
private bool initialized; private bool initialized;
private int deliveryFee;
private string deliveryLocationName;
public GUIFrame GuiFrame; public GUIFrame GuiFrame;
private GUIFrame pageIndicatorHolder; private GUIFrame pageIndicatorHolder;
@@ -34,14 +32,13 @@ namespace Barotrauma
private readonly List<SubmarineInfo> subsToShow; private readonly List<SubmarineInfo> subsToShow;
private readonly SubmarineDisplayContent[] submarineDisplays = new SubmarineDisplayContent[submarinesPerPage]; private readonly SubmarineDisplayContent[] submarineDisplays = new SubmarineDisplayContent[submarinesPerPage];
private SubmarineInfo selectedSubmarine = null; private SubmarineInfo selectedSubmarine = null;
private LocalizedString purchaseAndSwitchText, purchaseOnlyText, deliveryText, selectedSubText, switchText, missingPreviewText, currencyName; private LocalizedString purchaseAndSwitchText, purchaseOnlyText, selectedSubText, switchText, missingPreviewText, currencyName;
private readonly RectTransform parent; private readonly RectTransform parent;
private readonly Action closeAction; private readonly Action closeAction;
private Sprite pageIndicator; private Sprite pageIndicator;
private readonly LocalizedString[] messageBoxOptions; private readonly LocalizedString[] messageBoxOptions;
public const int DeliveryFeePerDistanceTravelled = 1000;
public static bool ContentRefreshRequired = false; public static bool ContentRefreshRequired = false;
private static readonly Color indicatorColor = new Color(112, 149, 129); private static readonly Color indicatorColor = new Color(112, 149, 129);
@@ -108,14 +105,9 @@ namespace Barotrauma
{ {
initialized = true; initialized = true;
selectedSubText = TextManager.Get("selectedsub"); selectedSubText = TextManager.Get("selectedsub");
deliveryText = TextManager.Get("requestdeliverybutton");
switchText = TextManager.Get("switchtosubmarinebutton"); switchText = TextManager.Get("switchtosubmarinebutton");
purchaseAndSwitchText = TextManager.Get("purchaseandswitch"); purchaseAndSwitchText = TextManager.Get("purchaseandswitch");
purchaseOnlyText = TextManager.Get("purchase"); purchaseOnlyText = TextManager.Get("purchase");
if (transferService)
{
deliveryFee = CalculateDeliveryFee();
}
currencyName = TextManager.Get("credit").Value.ToLowerInvariant(); currencyName = TextManager.Get("credit").Value.ToLowerInvariant();
@@ -124,13 +116,6 @@ namespace Barotrauma
CreateGUI(); CreateGUI();
} }
private int CalculateDeliveryFee()
{
int distanceToOutpost = GameMain.GameSession.Map.DistanceToClosestLocationWithOutpost(GameMain.GameSession.Map.CurrentLocation, out Location endLocation);
deliveryLocationName = endLocation.Name;
return DeliveryFeePerDistanceTravelled * distanceToOutpost;
}
private void CreateGUI() private void CreateGUI()
{ {
createdForResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight); createdForResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
@@ -194,7 +179,7 @@ namespace Barotrauma
confirmButtonAlt = new GUIButton(new RectTransform(new Vector2(0.2f, 1f), bottomContainer.RectTransform), purchaseOnlyText, style: "GUIButtonFreeScale"); confirmButtonAlt = new GUIButton(new RectTransform(new Vector2(0.2f, 1f), bottomContainer.RectTransform), purchaseOnlyText, style: "GUIButtonFreeScale");
transferInfoFrameWidth -= confirmButtonAlt.RectTransform.RelativeSize.X; transferInfoFrameWidth -= confirmButtonAlt.RectTransform.RelativeSize.X;
} }
confirmButton = new GUIButton(new RectTransform(new Vector2(0.2f, 1f), bottomContainer.RectTransform), purchaseService ? purchaseAndSwitchText : deliveryFee > 0 ? deliveryText : switchText, style: "GUIButtonFreeScale"); confirmButton = new GUIButton(new RectTransform(new Vector2(0.2f, 1f), bottomContainer.RectTransform), purchaseService ? purchaseAndSwitchText : switchText, style: "GUIButtonFreeScale");
SetConfirmButtonState(false); SetConfirmButtonState(false);
transferInfoFrameWidth -= confirmButton.RectTransform.RelativeSize.X; transferInfoFrameWidth -= confirmButton.RectTransform.RelativeSize.X;
GUIFrame transferInfoFrame = new GUIFrame(new RectTransform(new Vector2(transferInfoFrameWidth, 1.0f), bottomContainer.RectTransform), style: null) GUIFrame transferInfoFrame = new GUIFrame(new RectTransform(new Vector2(transferInfoFrameWidth, 1.0f), bottomContainer.RectTransform), style: null)
@@ -413,15 +398,7 @@ namespace Barotrauma
{ {
if (subToDisplay.Name != CurrentOrPendingSubmarine().Name) if (subToDisplay.Name != CurrentOrPendingSubmarine().Name)
{ {
if (deliveryFee > 0) submarineDisplays[i].submarineFee.Text = string.Empty;
{
LocalizedString amountString = TextManager.FormatCurrency(deliveryFee);
submarineDisplays[i].submarineFee.Text = TextManager.GetWithVariable("deliveryfee", "[amount]", amountString);
}
else
{
submarineDisplays[i].submarineFee.Text = string.Empty;
}
} }
else else
{ {
@@ -581,7 +558,7 @@ namespace Barotrauma
if (owned) if (owned)
{ {
confirmButton.Text = deliveryFee > 0 ? deliveryText : switchText; confirmButton.Text = switchText;
confirmButton.OnClicked = (button, userData) => confirmButton.OnClicked = (button, userData) =>
{ {
ShowTransferPrompt(); ShowTransferPrompt();
@@ -615,7 +592,7 @@ namespace Barotrauma
listBackground.SetCrop(true); listBackground.SetCrop(true);
GUIFont font = GUIStyle.Font; GUIFont font = GUIStyle.Font;
info.CreateSpecsWindow(specsFrame, font); info.CreateSpecsWindow(specsFrame, font, includeCrushDepth: true);
descriptionTextBlock.Text = info.Description; descriptionTextBlock.Text = info.Description;
descriptionTextBlock.CalculateHeightFromText(); descriptionTextBlock.CalculateHeightFromText();
} }
@@ -702,37 +679,12 @@ namespace Barotrauma
private void ShowTransferPrompt() private void ShowTransferPrompt()
{ {
if (!GameMain.GameSession.Campaign.CanAfford(deliveryFee) && deliveryFee > 0) var text = TextManager.GetWithVariables("switchsubmarinetext",
{ ("[submarinename1]", CurrentOrPendingSubmarine().DisplayName),
new GUIMessageBox(TextManager.Get("deliveryrequestheader"), TextManager.GetWithVariables("notenoughmoneyfordeliverytext", ("[submarinename2]", selectedSubmarine.DisplayName));
("[currencyname]", currencyName), text += GetItemTransferText();
("[submarinename]", selectedSubmarine.DisplayName), GUIMessageBox msgBox = new GUIMessageBox(TextManager.Get("switchsubmarineheader"), text, messageBoxOptions);
("[location1]", deliveryLocationName),
("[location2]", GameMain.GameSession.Map.CurrentLocation.Name)));
return;
}
GUIMessageBox msgBox;
if (deliveryFee > 0)
{
msgBox = new GUIMessageBox(TextManager.Get("deliveryrequestheader"), TextManager.GetWithVariables("deliveryrequesttext",
("[submarinename1]", selectedSubmarine.DisplayName),
("[location1]", deliveryLocationName),
("[location2]", GameMain.GameSession.Map.CurrentLocation.Name),
("[submarinename2]", CurrentOrPendingSubmarine().DisplayName),
("[amount]", deliveryFee.ToString()),
("[currencyname]", currencyName)), messageBoxOptions);
msgBox.Buttons[0].ClickSound = GUISoundType.ConfirmTransaction;
}
else
{
var text = TextManager.GetWithVariables("switchsubmarinetext",
("[submarinename1]", CurrentOrPendingSubmarine().DisplayName),
("[submarinename2]", selectedSubmarine.DisplayName));
text += GetItemTransferText();
msgBox = new GUIMessageBox(TextManager.Get("switchsubmarineheader"), text, messageBoxOptions);
}
msgBox.Buttons[0].OnClicked = (applyButton, obj) => msgBox.Buttons[0].OnClicked = (applyButton, obj) =>
{ {
@@ -777,7 +729,7 @@ namespace Barotrauma
{ {
if (GameMain.Client == null) if (GameMain.Client == null)
{ {
GameMain.GameSession.SwitchSubmarine(selectedSubmarine, TransferItemsOnSwitch, deliveryFee); GameMain.GameSession.SwitchSubmarine(selectedSubmarine, TransferItemsOnSwitch);
RefreshSubmarineDisplay(true); RefreshSubmarineDisplay(true);
} }
else else
@@ -856,7 +808,7 @@ namespace Barotrauma
if (GameMain.Client == null) if (GameMain.Client == null)
{ {
GameMain.GameSession.PurchaseSubmarine(selectedSubmarine); GameMain.GameSession.PurchaseSubmarine(selectedSubmarine);
GameMain.GameSession.SwitchSubmarine(selectedSubmarine, TransferItemsOnSwitch, 0); GameMain.GameSession.SwitchSubmarine(selectedSubmarine, TransferItemsOnSwitch);
RefreshSubmarineDisplay(true); RefreshSubmarineDisplay(true);
} }
else else
@@ -836,7 +836,7 @@ namespace Barotrauma
Identifier eventIdentifier = new Identifier($"{nameof(CreateWalletCrewFrame)}.{character.ID}"); Identifier eventIdentifier = new Identifier($"{nameof(CreateWalletCrewFrame)}.{character.ID}");
campaign.OnMoneyChanged.RegisterOverwriteExisting(eventIdentifier, e => campaign.OnMoneyChanged.RegisterOverwriteExisting(eventIdentifier, e =>
{ {
if (!(e.Owner is Some<Character> { Value: var owner }) || owner != character) { return; } if (!e.Owner.TryUnwrap(out var owner) || owner != character) { return; }
SetWalletText(walletBlock, e.Wallet, icon, largeIcon); SetWalletText(walletBlock, e.Wallet, icon, largeIcon);
}); });
registeredEvents.Add(eventIdentifier); registeredEvents.Add(eventIdentifier);
@@ -1786,7 +1786,10 @@ namespace Barotrauma
{ {
CurrentSelectMode = GUIListBox.SelectMode.None CurrentSelectMode = GUIListBox.SelectMode.None
}; };
sub.Info.CreateSpecsWindow(specsListBox, GUIStyle.Font, includeTitle: false, includeClass: false, includeDescription: true); sub.Info.CreateSpecsWindow(specsListBox, GUIStyle.Font,
includeTitle: false,
includeClass: false,
includeDescription: true);
} }
} }
@@ -278,7 +278,7 @@ namespace Barotrauma
new GUITextBlock(rectT(1, 0, tooltipLayout), string.Empty) { UserData = "moreindicator" }; new GUITextBlock(rectT(1, 0, tooltipLayout), string.Empty) { UserData = "moreindicator" };
ItemInfoFrame.Children.ForEach(c => { c.CanBeFocused = false; c.Children.ForEach(c2 => c2.CanBeFocused = false); }); ItemInfoFrame.Children.ForEach(c => { c.CanBeFocused = false; c.Children.ForEach(c2 => c2.CanBeFocused = false); });
GUIFrame paddedLayout = new GUIFrame(rectT(0.95f, GUI.IsFourByThree() ? 0.98f : 0.95f, parent, Anchor.Center), style: null); GUIFrame paddedLayout = new GUIFrame(rectT(0.95f, 0.95f, parent, Anchor.Center), style: null);
mainStoreLayout = new GUILayoutGroup(rectT(1, 0.9f, paddedLayout, Anchor.BottomLeft), isHorizontal: true) { RelativeSpacing = 0.01f }; mainStoreLayout = new GUILayoutGroup(rectT(1, 0.9f, paddedLayout, Anchor.BottomLeft), isHorizontal: true) { RelativeSpacing = 0.01f };
topHeaderLayout = new GUILayoutGroup(rectT(1, 0.1f, paddedLayout, Anchor.TopLeft), isHorizontal: true); topHeaderLayout = new GUILayoutGroup(rectT(1, 0.1f, paddedLayout, Anchor.TopLeft), isHorizontal: true);
@@ -300,8 +300,8 @@ namespace Barotrauma
new GUITextBlock(rectT(1.0f, 1, locationLayout), TextManager.Get("UpgradeUI.AllSubmarinesInfo"), font: GUIStyle.SmallFont, wrap: true); new GUITextBlock(rectT(1.0f, 1, locationLayout), TextManager.Get("UpgradeUI.AllSubmarinesInfo"), font: GUIStyle.SmallFont, wrap: true);
categoryButtonLayout = new GUILayoutGroup(rectT(0.4f, 0.3f, leftLayout), isHorizontal: true) { Stretch = true }; categoryButtonLayout = new GUILayoutGroup(rectT(0.4f, 0.3f, leftLayout), isHorizontal: true) { Stretch = true };
GUIButton upgradeButton = new GUIButton(rectT(1, 1f, categoryButtonLayout), TextManager.Get("UICategory.Upgrades"), style: "GUITabButton") { UserData = UpgradeTab.Upgrade, Selected = selectedUpgradeTab == UpgradeTab.Upgrade }; GUIButton upgradeButton = new GUIButton(rectT(0.5f, 1f, categoryButtonLayout), TextManager.Get("UICategory.Upgrades"), style: "GUITabButton") { UserData = UpgradeTab.Upgrade, Selected = selectedUpgradeTab == UpgradeTab.Upgrade };
GUIButton repairButton = new GUIButton(rectT(1, 1f, categoryButtonLayout), TextManager.Get("UICategory.Maintenance"), style: "GUITabButton") { UserData = UpgradeTab.Repairs, Selected = selectedUpgradeTab == UpgradeTab.Repairs }; GUIButton repairButton = new GUIButton(rectT(0.5f, 1f, categoryButtonLayout), TextManager.Get("UICategory.Maintenance"), style: "GUITabButton") { UserData = UpgradeTab.Repairs, Selected = selectedUpgradeTab == UpgradeTab.Repairs };
/* RIGHT HEADER LAYOUT /* RIGHT HEADER LAYOUT
* |---------------------------------------------------------------------------------------------------| * |---------------------------------------------------------------------------------------------------|
@@ -352,12 +352,15 @@ namespace Barotrauma
SelectTab(UpgradeTab.Upgrade); SelectTab(UpgradeTab.Upgrade);
var itemSwapPreview = new GUICustomComponent(new RectTransform(new Vector2(0.27f, 0.4f), mainStoreLayout.RectTransform, Anchor.TopLeft) { RelativeOffset = new Vector2(GUI.IsFourByThree() ? 0.5f : 0.47f, 0.0f) }, DrawItemSwapPreview) var itemSwapPreview = new GUICustomComponent(new RectTransform(new Vector2(0.25f, 0.4f), mainStoreLayout.RectTransform, Anchor.TopLeft)
{ RelativeOffset = new Vector2(0.52f * GUI.AspectRatioAdjustment, 0.0f) }, DrawItemSwapPreview)
{ {
IgnoreLayoutGroups = true, IgnoreLayoutGroups = true,
CanBeFocused = true CanBeFocused = true
}; };
GUITextBlock.AutoScaleAndNormalize(upgradeButton.TextBlock, repairButton.TextBlock);
#if DEBUG #if DEBUG
// creates a button that re-creates the UI // creates a button that re-creates the UI
CreateRefreshButton(); CreateRefreshButton();
@@ -730,7 +733,7 @@ namespace Barotrauma
if (storeLayout == null || mainStoreLayout == null) { return; } if (storeLayout == null || mainStoreLayout == null) { return; }
currentStoreLayout = CreateUpgradeCategoryList(rectT(1.0f, 1.5f, storeLayout)); currentStoreLayout = CreateUpgradeCategoryList(rectT(1.0f, 1.5f, storeLayout));
selectedUpgradeCategoryLayout = new GUIFrame(rectT(GUI.IsFourByThree() ? 0.3f : 0.25f, 1, mainStoreLayout), style: null) { CanBeFocused = false }; selectedUpgradeCategoryLayout = new GUIFrame(rectT(0.3f * GUI.AspectRatioAdjustment, 1, mainStoreLayout), style: null) { CanBeFocused = false };
RefreshUpgradeList(); RefreshUpgradeList();
@@ -961,7 +964,7 @@ namespace Barotrauma
bool isUninstallPending = item.Prefab.SwappableItem != null && item.PendingItemSwap?.Identifier == item.Prefab.SwappableItem.ReplacementOnUninstall; bool isUninstallPending = item.Prefab.SwappableItem != null && item.PendingItemSwap?.Identifier == item.Prefab.SwappableItem.ReplacementOnUninstall;
if (isUninstallPending) { canUninstall = false; } if (isUninstallPending) { canUninstall = false; }
frames.Add(CreateUpgradeEntry(rectT(1f, 0.25f, parent.Content), currentOrPending.UpgradePreviewSprite, frames.Add(CreateUpgradeEntry(rectT(1f, 0.35f, parent.Content), currentOrPending.UpgradePreviewSprite,
item.PendingItemSwap != null ? TextManager.GetWithVariable("upgrades.pendingitem", "[itemname]", name) : TextManager.GetWithVariable("upgrades.installeditem", "[itemname]", nameWithQuantity), item.PendingItemSwap != null ? TextManager.GetWithVariable("upgrades.pendingitem", "[itemname]", name) : TextManager.GetWithVariable("upgrades.installeditem", "[itemname]", nameWithQuantity),
currentOrPending.Description, currentOrPending.Description,
0, null, addBuyButton: canUninstall, addProgressBar: false, buttonStyle: "WeaponUninstallButton").Frame); 0, null, addBuyButton: canUninstall, addProgressBar: false, buttonStyle: "WeaponUninstallButton").Frame);
@@ -1001,7 +1004,7 @@ namespace Barotrauma
int price = isPurchased || replacement == item.Prefab ? 0 : replacement.SwappableItem.GetPrice(Campaign.Map?.CurrentLocation) * linkedItems.Count(); int price = isPurchased || replacement == item.Prefab ? 0 : replacement.SwappableItem.GetPrice(Campaign.Map?.CurrentLocation) * linkedItems.Count();
frames.Add(CreateUpgradeEntry(rectT(1f, 0.25f, parent.Content), replacement.UpgradePreviewSprite, replacement.Name, replacement.Description, frames.Add(CreateUpgradeEntry(rectT(1f, 0.35f, parent.Content), replacement.UpgradePreviewSprite, replacement.Name, replacement.Description,
price, replacement, price, replacement,
addBuyButton: true, addBuyButton: true,
addProgressBar: false, addProgressBar: false,
@@ -1134,7 +1137,8 @@ namespace Barotrauma
GUILayoutGroup imageLayout = new GUILayoutGroup(rectT(new Point(prefabLayout.Rect.Height, prefabLayout.Rect.Height), prefabLayout), childAnchor: Anchor.Center); GUILayoutGroup imageLayout = new GUILayoutGroup(rectT(new Point(prefabLayout.Rect.Height, prefabLayout.Rect.Height), prefabLayout), childAnchor: Anchor.Center);
var icon = new GUIImage(rectT(0.9f, 0.9f, imageLayout, scaleBasis: ScaleBasis.BothHeight), sprite, scaleToFit: true) { CanBeFocused = false }; var icon = new GUIImage(rectT(0.9f, 0.9f, imageLayout, scaleBasis: ScaleBasis.BothHeight), sprite, scaleToFit: true) { CanBeFocused = false };
GUILayoutGroup textLayout = new GUILayoutGroup(rectT(1f - imageLayout.RectTransform.RelativeSize.X, 1, prefabLayout)); GUILayoutGroup textLayout = new GUILayoutGroup(rectT(1f - imageLayout.RectTransform.RelativeSize.X, 1, prefabLayout));
var name = new GUITextBlock(rectT(1, 0.25f, textLayout), RichString.Rich(title), font: GUIStyle.SubHeadingFont) { AutoScaleHorizontal = true, AutoScaleVertical = true, Padding = Vector4.Zero }; var name = new GUITextBlock(rectT(1, 0.35f, textLayout), RichString.Rich(title), font: GUIStyle.SubHeadingFont) { AutoScaleHorizontal = true, AutoScaleVertical = true, Padding = Vector4.Zero };
//name.RectTransform.MinSize = new Point(0, (int)name.TextSize.Y);
GUILayoutGroup descriptionLayout = new GUILayoutGroup(rectT(1, 0.75f - progressBarHeight, textLayout)); GUILayoutGroup descriptionLayout = new GUILayoutGroup(rectT(1, 0.75f - progressBarHeight, textLayout));
var description = new GUITextBlock(rectT(1, 1, descriptionLayout), body, font: GUIStyle.SmallFont, wrap: true, textAlignment: Alignment.TopLeft) { Padding = Vector4.Zero }; var description = new GUITextBlock(rectT(1, 1, descriptionLayout), body, font: GUIStyle.SmallFont, wrap: true, textAlignment: Alignment.TopLeft) { Padding = Vector4.Zero };
GUILayoutGroup? progressLayout = null; GUILayoutGroup? progressLayout = null;
@@ -1176,7 +1180,7 @@ namespace Barotrauma
materialCostList.Visible = false; materialCostList.Visible = false;
materialCostList.UserData = UpgradeStoreUserData.MaterialCostList; materialCostList.UserData = UpgradeStoreUserData.MaterialCostList;
var priceText = new GUITextBlock(rectT(0.2f, 1f, buyButtonLayout), formattedPrice) var priceText = new GUITextBlock(rectT(0.2f, 1f, buyButtonLayout), formattedPrice, textAlignment: Alignment.CenterRight)
{ {
UserData = UpgradeStoreUserData.PriceLabel, UserData = UpgradeStoreUserData.PriceLabel,
//prices on swappable items are always visible, upgrade prices are enabled in UpdateUpgradeEntry for purchasable upgrades //prices on swappable items are always visible, upgrade prices are enabled in UpdateUpgradeEntry for purchasable upgrades
@@ -189,24 +189,10 @@ namespace Barotrauma
("[currencyname]", TextManager.Get("credit").ToLower())); ("[currencyname]", TextManager.Get("credit").ToLower()));
break; break;
case VoteType.SwitchSub: case VoteType.SwitchSub:
int deliveryFee = SubmarineSelection.DeliveryFeePerDistanceTravelled * GameMain.GameSession.Map.DistanceToClosestLocationWithOutpost(GameMain.GameSession.Map.CurrentLocation, out Location endLocation); tag = transferItems ? "submarineswitchwithitemsnofeevote" : "submarineswitchnofeevote";
if (deliveryFee > 0) text = TextManager.GetWithVariables(tag,
{ ("[playername]", characterRichString),
tag = transferItems ? "submarineswitchwithitemsfeevote" : "submarineswitchfeevote"; ("[submarinename]", submarineRichString));
text = TextManager.GetWithVariables(tag,
("[playername]", characterRichString),
("[submarinename]", submarineRichString),
("[locationname]", endLocation.Name),
("[amount]", deliveryFee.ToString()),
("[currencyname]", TextManager.Get("credit").ToLower()));
}
else
{
tag = transferItems ? "submarineswitchwithitemsnofeevote" : "submarineswitchnofeevote";
text = TextManager.GetWithVariables(tag,
("[playername]", characterRichString),
("[submarinename]", submarineRichString));
}
break; break;
} }
votingOnText = RichString.Rich(text); votingOnText = RichString.Rich(text);
@@ -241,25 +227,10 @@ namespace Barotrauma
("[novotecount]", noVoteCount.ToString())); ("[novotecount]", noVoteCount.ToString()));
break; break;
case VoteType.SwitchSub: case VoteType.SwitchSub:
int deliveryFee = SubmarineSelection.DeliveryFeePerDistanceTravelled * GameMain.GameSession.Map.DistanceToClosestLocationWithOutpost(GameMain.GameSession.Map.CurrentLocation, out Location endLocation); result = TextManager.GetWithVariables(votePassed ? "submarineswitchnofeevotepassed" : "submarineswitchnofeevotefailed",
("[submarinename]", info.DisplayName),
if (deliveryFee > 0) ("[yesvotecount]", yesVoteCount.ToString()),
{ ("[novotecount]", noVoteCount.ToString()));
result = TextManager.GetWithVariables(votePassed ? "submarineswitchfeevotepassed" : "submarineswitchfeevotefailed",
("[submarinename]", info.DisplayName),
("[locationname]", endLocation.Name),
("[amount]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", deliveryFee)),
("[currencyname]", TextManager.Get("credit").ToLower()),
("[yesvotecount]", yesVoteCount.ToString()),
("[novotecount]", noVoteCount.ToString()));
}
else
{
result = TextManager.GetWithVariables(votePassed ? "submarineswitchnofeevotepassed" : "submarineswitchnofeevotefailed",
("[submarinename]", info.DisplayName),
("[yesvotecount]", yesVoteCount.ToString()),
("[novotecount]", noVoteCount.ToString()));
}
break; break;
default: default:
break; break;
@@ -17,14 +17,19 @@ using System.Linq;
using System.Reflection; using System.Reflection;
using System.Threading; using System.Threading;
using Barotrauma.Extensions; using Barotrauma.Extensions;
using System.Collections.Immutable;
namespace Barotrauma namespace Barotrauma
{ {
class GameMain : Game class GameMain : Game
{ {
public static bool ShowFPS = false; public static bool ShowFPS;
public static bool ShowPerf = false; public static bool ShowPerf;
public static bool DebugDraw; public static bool DebugDraw;
/// <summary>
/// Doesn't automatically enable los or bot AI or do anything like that. Probably not fully implemented.
/// </summary>
public static bool DevMode;
public static bool IsSingleplayer => NetworkMember == null; public static bool IsSingleplayer => NetworkMember == null;
public static bool IsMultiplayer => NetworkMember != null; public static bool IsMultiplayer => NetworkMember != null;
@@ -227,8 +232,7 @@ namespace Barotrauma
} }
GameSettings.Init(); GameSettings.Init();
CreatureMetrics.Init();
Md5Hash.Cache.Load();
ConsoleArguments = args; ConsoleArguments = args;
@@ -397,7 +401,7 @@ namespace Barotrauma
TextureLoader.Init(GraphicsDevice); TextureLoader.Init(GraphicsDevice);
//do this here because we need it for the loading screen //do this here because we need it for the loading screen
WaterRenderer.Instance = new WaterRenderer(base.GraphicsDevice, Content); WaterRenderer.Instance = new WaterRenderer(base.GraphicsDevice);
Quad.Init(GraphicsDevice); Quad.Init(GraphicsDevice);
@@ -475,6 +479,19 @@ namespace Barotrauma
yield return CoroutineStatus.Running; yield return CoroutineStatus.Running;
} }
var corePackage = ContentPackageManager.EnabledPackages.Core;
if (corePackage.EnableError.TryUnwrap(out var error))
{
if (error.ErrorsOrException.TryGet(out ImmutableArray<string> errorMessages))
{
throw new Exception($"Error while loading the core content package \"{corePackage.Name}\": {errorMessages.First()}");
}
else if (error.ErrorsOrException.TryGet(out Exception exception))
{
throw new Exception($"Error while loading the core content package \"{corePackage.Name}\": {exception.Message}", exception);
}
}
TextManager.VerifyLanguageAvailable(); TextManager.VerifyLanguageAvailable();
DebugConsole.Init(); DebugConsole.Init();
@@ -498,10 +515,10 @@ namespace Barotrauma
TitleScreen.LoadState = 75.0f; TitleScreen.LoadState = 75.0f;
yield return CoroutineStatus.Running; yield return CoroutineStatus.Running;
GameScreen = new GameScreen(GraphicsDeviceManager.GraphicsDevice, Content); GameScreen = new GameScreen(GraphicsDeviceManager.GraphicsDevice);
ParticleManager = new ParticleManager(GameScreen.Cam); ParticleManager = new ParticleManager(GameScreen.Cam);
LightManager = new Lights.LightManager(base.GraphicsDevice, Content); LightManager = new Lights.LightManager(base.GraphicsDevice);
TitleScreen.LoadState = 80.0f; TitleScreen.LoadState = 80.0f;
yield return CoroutineStatus.Running; yield return CoroutineStatus.Running;
@@ -729,7 +746,7 @@ namespace Barotrauma
} }
else if (HasLoaded) else if (HasLoaded)
{ {
if (ConnectCommand is Some<ConnectCommand> { Value: var connectCommand }) if (ConnectCommand.TryUnwrap(out var connectCommand))
{ {
if (Client != null) if (Client != null)
{ {
@@ -1051,6 +1068,7 @@ namespace Barotrauma
public static void QuitToMainMenu(bool save) public static void QuitToMainMenu(bool save)
{ {
CreatureMetrics.Save();
if (save) if (save)
{ {
GUI.SetSavingIndicatorState(true); GUI.SetSavingIndicatorState(true);
@@ -1156,6 +1174,7 @@ namespace Barotrauma
protected override void OnExiting(object sender, EventArgs args) protected override void OnExiting(object sender, EventArgs args)
{ {
exiting = true; exiting = true;
CreatureMetrics.Save();
DebugConsole.NewMessage("Exiting..."); DebugConsole.NewMessage("Exiting...");
Client?.Quit(); Client?.Quit();
SteamManager.ShutDown(); SteamManager.ShutDown();
@@ -193,7 +193,7 @@ namespace Barotrauma
}; };
} }
var reports = OrderPrefab.Prefabs.Where(o => o.IsReport && o.SymbolSprite != null && !o.Hidden).ToArray(); var reports = OrderPrefab.Prefabs.Where(o => o.IsReport && o.SymbolSprite != null && !o.Hidden).OrderBy(o => o.Identifier).ToArray();
if (reports.None()) if (reports.None())
{ {
DebugConsole.ThrowError("No valid orders for report buttons found! Cannot create report buttons. The orders for the report buttons must have 'targetallcharacters' attribute enabled and a valid 'symbolsprite' defined."); DebugConsole.ThrowError("No valid orders for report buttons found! Cannot create report buttons. The orders for the report buttons must have 'targetallcharacters' attribute enabled and a valid 'symbolsprite' defined.");
@@ -787,7 +787,6 @@ namespace Barotrauma
{ {
return; return;
} }
if (ws != null) if (ws != null)
{ {
hull = Hull.FindHull(ws.WorldPosition); hull = Hull.FindHull(ws.WorldPosition);
@@ -801,7 +800,6 @@ namespace Barotrauma
hull = Hull.FindHull(se.WorldPosition); hull = Hull.FindHull(se.WorldPosition);
} }
} }
if (IsSinglePlayer) if (IsSinglePlayer)
{ {
order.OrderGiver?.Speak(order.GetChatMessage("", hull?.DisplayName?.Value, givingOrderToSelf: character == order.OrderGiver, isNewOrder: isNewOrder), ChatMessageType.Order); order.OrderGiver?.Speak(order.GetChatMessage("", hull?.DisplayName?.Value, givingOrderToSelf: character == order.OrderGiver, isNewOrder: isNewOrder), ChatMessageType.Order);
@@ -816,13 +814,13 @@ namespace Barotrauma
{ {
//can't issue an order if no characters are available //can't issue an order if no characters are available
if (character == null) { return; } if (character == null) { return; }
var orderGiver = order?.OrderGiver; var orderGiver = order?.OrderGiver;
if (IsSinglePlayer) if (IsSinglePlayer)
{ {
character.SetOrder(order, isNewOrder, speak: orderGiver != character); bool isGivingOrderToSelf = orderGiver == character;
string message = order?.GetChatMessage(character.Name, orderGiver?.CurrentHull?.DisplayName?.Value, givingOrderToSelf: character == orderGiver, orderOption: order?.Option ?? Identifier.Empty, isNewOrder: isNewOrder); character.SetOrder(order, isNewOrder, speak: !isGivingOrderToSelf);
orderGiver?.Speak(message); string message = order?.GetChatMessage(character.Name, orderGiver?.CurrentHull?.DisplayName?.Value, isGivingOrderToSelf, orderOption: order?.Option ?? Identifier.Empty, isNewOrder: isNewOrder);
orderGiver?.Speak(message);
} }
else if (orderGiver != null) else if (orderGiver != null)
{ {
@@ -1404,8 +1402,7 @@ namespace Barotrauma
bool hitDeselect = PlayerInput.KeyHit(InputType.Deselect) && bool hitDeselect = PlayerInput.KeyHit(InputType.Deselect) &&
(!PlayerInput.SecondaryMouseButtonClicked() || (!isMouseOnOptionNode && !isMouseOnShortcutNode)); (!PlayerInput.SecondaryMouseButtonClicked() || (!isMouseOnOptionNode && !isMouseOnShortcutNode));
bool isBoundToPrimaryMouse = GameSettings.CurrentConfig.KeyMap.Bindings[InputType.Command].MouseButton is MouseButton mouseButton && bool isBoundToPrimaryMouse = GameSettings.CurrentConfig.KeyMap.Bindings[InputType.Command].MouseButton == MouseButton.PrimaryMouse;
(mouseButton == MouseButton.PrimaryMouse || mouseButton == (PlayerInput.MouseButtonsSwapped() ? MouseButton.RightMouse : MouseButton.LeftMouse));
bool canToggleInterface = !isBoundToPrimaryMouse || bool canToggleInterface = !isBoundToPrimaryMouse ||
(!isMouseOnOptionNode && !isMouseOnShortcutNode && extraOptionNodes.None(n => GUI.IsMouseOn(n)) && !GUI.IsMouseOn(returnNode)); (!isMouseOnOptionNode && !isMouseOnShortcutNode && extraOptionNodes.None(n => GUI.IsMouseOn(n)) && !GUI.IsMouseOn(returnNode));
@@ -2797,8 +2794,8 @@ namespace Barotrauma
var orderName = GetOrderNameBasedOnContextuality(order); var orderName = GetOrderNameBasedOnContextuality(order);
var icon = CreateNodeIcon(Vector2.One, node.RectTransform, order.SymbolSprite, order.Color, var icon = CreateNodeIcon(Vector2.One, node.RectTransform, order.SymbolSprite, order.Color,
tooltip: !showAssignmentTooltip ? orderName : orderName + tooltip: !showAssignmentTooltip ? orderName : orderName +
"\n" + (!PlayerInput.MouseButtonsSwapped() ? TextManager.Get("input.leftmouse") : TextManager.Get("input.rightmouse")) + ": " + TextManager.Get("commandui.quickassigntooltip") + "\n" + PlayerInput.PrimaryMouseLabel + ": " + TextManager.Get("commandui.quickassigntooltip") +
"\n" + (!PlayerInput.MouseButtonsSwapped() ? TextManager.Get("input.rightmouse") : TextManager.Get("input.leftmouse")) + ": " + TextManager.Get("commandui.manualassigntooltip")); "\n" + PlayerInput.SecondaryMouseLabel + ": " + TextManager.Get("commandui.manualassigntooltip"));
if (disableNode) if (disableNode)
{ {
@@ -3000,8 +2997,8 @@ namespace Barotrauma
var showAssignmentTooltip = characterContext == null && !order.MustManuallyAssign && !order.TargetAllCharacters; var showAssignmentTooltip = characterContext == null && !order.MustManuallyAssign && !order.TargetAllCharacters;
icon = CreateNodeIcon(Vector2.One, node.RectTransform, sprite, order.Color, icon = CreateNodeIcon(Vector2.One, node.RectTransform, sprite, order.Color,
tooltip: characterContext != null ? optionName : optionName + tooltip: characterContext != null ? optionName : optionName +
"\n" + (!PlayerInput.MouseButtonsSwapped() ? TextManager.Get("input.leftmouse") : TextManager.Get("input.rightmouse")) + ": " + TextManager.Get("commandui.quickassigntooltip") + "\n" + PlayerInput.PrimaryMouseLabel + ": " + TextManager.Get("commandui.quickassigntooltip") +
"\n" + (!PlayerInput.MouseButtonsSwapped() ? TextManager.Get("input.rightmouse") : TextManager.Get("input.leftmouse")) + ": " + TextManager.Get("commandui.manualassigntooltip")); "\n" + PlayerInput.SecondaryMouseLabel + ": " + TextManager.Get("commandui.manualassigntooltip"));
} }
if (!CanCharacterBeHeard()) if (!CanCharacterBeHeard())
{ {
@@ -13,7 +13,7 @@ namespace Barotrauma
partial void SettingsChanged(Option<int> balanceChanged, Option<int> rewardChanged) partial void SettingsChanged(Option<int> balanceChanged, Option<int> rewardChanged)
{ {
if (Owner is Some<Character> { Value: var character }) if (Owner.TryUnwrap(out var character))
{ {
if (!character.IsPlayer) { return; } if (!character.IsPlayer) { return; }
} }
@@ -33,7 +33,7 @@ namespace Barotrauma
protected set; protected set;
} }
public static CancellationTokenSource StartRoundCancellationToken { get; private set; } private CancellationTokenSource startRoundCancellationToken;
public bool ForceMapUI public bool ForceMapUI
{ {
@@ -62,10 +62,19 @@ namespace Barotrauma
{ {
chatBox.ToggleOpen = wasChatBoxOpen; chatBox.ToggleOpen = wasChatBoxOpen;
} }
if (!value && CampaignUI?.SelectedTab == InteractionType.PurchaseSub) if (!value)
{ {
SubmarinePreview.Close(); switch (CampaignUI?.SelectedTab)
{
case InteractionType.PurchaseSub:
SubmarinePreview.Close();
break;
case InteractionType.MedicalClinic:
CampaignUI.MedicalClinic?.OnDeselected();
break;
}
} }
showCampaignUI = value; showCampaignUI = value;
} }
} }
@@ -110,12 +119,7 @@ namespace Barotrauma
public static bool AllowedToManageWallets() public static bool AllowedToManageWallets()
{ {
if (GameMain.Client == null) { return true; } return AllowedToManageCampaign(ClientPermissions.ManageMoney);
return
GameMain.Client.HasPermission(ClientPermissions.ManageMoney) ||
GameMain.Client.HasPermission(ClientPermissions.ManageCampaign) ||
GameMain.Client.IsServerOwner;
} }
public override void Draw(SpriteBatch spriteBatch) public override void Draw(SpriteBatch spriteBatch)
@@ -247,11 +251,11 @@ namespace Barotrauma
GUI.ClearCursorWait(); GUI.ClearCursorWait();
StartRoundCancellationToken = new CancellationTokenSource(); startRoundCancellationToken = new CancellationTokenSource();
var loadTask = Task.Run(async () => var loadTask = Task.Run(async () =>
{ {
await Task.Yield(); await Task.Yield();
Rand.ThreadId = Thread.CurrentThread.ManagedThreadId; Rand.ThreadId = Environment.CurrentManagedThreadId;
try try
{ {
GameMain.GameSession.StartRound(newLevel, mirrorLevel: mirror, startOutpost: GetPredefinedStartOutpost()); GameMain.GameSession.StartRound(newLevel, mirrorLevel: mirror, startOutpost: GetPredefinedStartOutpost());
@@ -261,7 +265,8 @@ namespace Barotrauma
roundSummaryScreen.LoadException = e; roundSummaryScreen.LoadException = e;
} }
Rand.ThreadId = 0; Rand.ThreadId = 0;
}, StartRoundCancellationToken.Token); startRoundCancellationToken = null;
}, startRoundCancellationToken.Token);
TaskPool.Add("AsyncCampaignStartRound", loadTask, (t) => TaskPool.Add("AsyncCampaignStartRound", loadTask, (t) =>
{ {
overlayColor = Color.Transparent; overlayColor = Color.Transparent;
@@ -271,6 +276,21 @@ namespace Barotrauma
return loadTask; return loadTask;
} }
public void CancelStartRound()
{
startRoundCancellationToken?.Cancel();
}
public void ThrowIfStartRoundCancellationRequested()
{
if (startRoundCancellationToken != null &&
startRoundCancellationToken.Token.IsCancellationRequested)
{
startRoundCancellationToken.Token.ThrowIfCancellationRequested();
startRoundCancellationToken = null;
}
}
protected SubmarineInfo GetPredefinedStartOutpost() protected SubmarineInfo GetPredefinedStartOutpost()
{ {
if (Map?.CurrentLocation?.Type?.GetForcedOutpostGenerationParams() is OutpostGenerationParams parameters && !parameters.OutpostFilePath.IsNullOrEmpty()) if (Map?.CurrentLocation?.Type?.GetForcedOutpostGenerationParams() is OutpostGenerationParams parameters && !parameters.OutpostFilePath.IsNullOrEmpty())
@@ -304,7 +324,7 @@ namespace Barotrauma
goto default; goto default;
default: default:
ShowCampaignUI = true; ShowCampaignUI = true;
CampaignUI.SelectTab(npc.CampaignInteractionType, storeIdentifier: npc.MerchantIdentifier); CampaignUI.SelectTab(npc.CampaignInteractionType, npc);
CampaignUI.UpgradeStore?.RequestRefresh(); CampaignUI.UpgradeStore?.RequestRefresh();
break; break;
} }
@@ -962,25 +962,20 @@ namespace Barotrauma
foreach (NetWalletTransaction transaction in update.Transactions) foreach (NetWalletTransaction transaction in update.Transactions)
{ {
WalletInfo info = transaction.Info; WalletInfo info = transaction.Info;
switch (transaction.CharacterID) if (transaction.CharacterID.TryUnwrap(out var charID))
{ {
case Some<ushort> { Value: var charID }: Character targetCharacter = Character.CharacterList?.FirstOrDefault(c => c.ID == charID);
{ if (targetCharacter is null) { break; }
Character targetCharacter = Character.CharacterList?.FirstOrDefault(c => c.ID == charID); Wallet wallet = targetCharacter.Wallet;
if (targetCharacter is null) { break; }
Wallet wallet = targetCharacter.Wallet;
wallet.Balance = info.Balance; wallet.Balance = info.Balance;
wallet.RewardDistribution = info.RewardDistribution; wallet.RewardDistribution = info.RewardDistribution;
TryInvokeEvent(wallet, transaction.ChangedData, info); TryInvokeEvent(wallet, transaction.ChangedData, info);
break; }
} else
case None<ushort> _: {
{ Bank.Balance = info.Balance;
Bank.Balance = info.Balance; TryInvokeEvent(Bank, transaction.ChangedData, info);
TryInvokeEvent(Bank, transaction.ChangedData, info);
break;
}
} }
} }
@@ -995,7 +990,7 @@ namespace Barotrauma
public override bool TryPurchase(Client client, int price) public override bool TryPurchase(Client client, int price)
{ {
if (!AllowedToManageCampaign(ClientPermissions.ManageCampaign)) if (!AllowedToManageCampaign(ClientPermissions.ManageMoney))
{ {
return PersonalWallet.TryDeduct(price); return PersonalWallet.TryDeduct(price);
} }
@@ -421,6 +421,7 @@ namespace Barotrauma
TotalPassedLevels++; TotalPassedLevels++;
break; break;
case TransitionType.ProgressToNextEmptyLocation: case TransitionType.ProgressToNextEmptyLocation:
Map.Visit(Map.CurrentLocation);
TotalPassedLevels++; TotalPassedLevels++;
break; break;
case TransitionType.End: case TransitionType.End:
@@ -437,9 +438,9 @@ namespace Barotrauma
if (transitionType != TransitionType.End) if (transitionType != TransitionType.End)
{ {
var endTransition = new CameraTransition(Submarine.MainSub, GameMain.GameScreen.Cam, null, var endTransition = new CameraTransition(Submarine.MainSub, GameMain.GameScreen.Cam, null,
transitionType == TransitionType.LeaveLocation ? Alignment.BottomCenter : Alignment.Center, transitionType == TransitionType.LeaveLocation ? Alignment.BottomCenter : Alignment.Center,
fadeOut: false, fadeOut: false,
panDuration: EndTransitionDuration); panDuration: EndTransitionDuration);
Location portraitLocation = Map.SelectedLocation ?? Map.CurrentLocation; Location portraitLocation = Map.SelectedLocation ?? Map.CurrentLocation;
overlaySprite = portraitLocation.Type.GetPortrait(portraitLocation.PortraitId); overlaySprite = portraitLocation.Type.GetPortrait(portraitLocation.PortraitId);
@@ -585,7 +585,7 @@ namespace Barotrauma
if (!gap.IsRoomToRoom) if (!gap.IsRoomToRoom)
{ {
if (!IsWearingDivingSuit()) { continue; } if (!IsWearingDivingSuit()) { continue; }
if (Character.Controlled.IsProtectedFromPressure()) { continue; } if (Character.Controlled.IsProtectedFromPressure) { continue; }
if (DisplayHint("divingsuitwarning".ToIdentifier(), extendTextTag: false)) { return; } if (DisplayHint("divingsuitwarning".ToIdentifier(), extendTextTag: false)) { return; }
continue; continue;
} }
@@ -11,6 +11,8 @@ namespace Barotrauma
{ {
internal sealed partial class MedicalClinic internal sealed partial class MedicalClinic
{ {
private MedicalClinicUI? ui => campaign?.CampaignUI?.MedicalClinic;
public enum RequestResult public enum RequestResult
{ {
Undecided, Undecided,
@@ -303,6 +305,12 @@ namespace Barotrauma
} }
} }
private void AfflictionUpdateReceived(IReadMessage inc)
{
NetCrewMember crewMember = INetSerializableStruct.Read<NetCrewMember>(inc);
ui?.UpdateAfflictions(crewMember);
}
private void PendingRequestReceived(IReadMessage inc) private void PendingRequestReceived(IReadMessage inc)
{ {
var pendingCrew = INetSerializableStruct.Read<NetCollection<NetCrewMember>>(inc); var pendingCrew = INetSerializableStruct.Read<NetCollection<NetCrewMember>>(inc);
@@ -312,6 +320,10 @@ namespace Barotrauma
} }
} }
public static void SendUnsubscribeRequest() => ClientSend(null,
header: NetworkHeader.UNSUBSCRIBE_ME,
deliveryMethod: DeliveryMethod.Reliable);
private static IWriteMessage StartSending() private static IWriteMessage StartSending()
{ {
IWriteMessage writeMessage = new WriteOnlyMessage(); IWriteMessage writeMessage = new WriteOnlyMessage();
@@ -337,6 +349,9 @@ namespace Barotrauma
case NetworkHeader.REQUEST_AFFLICTIONS: case NetworkHeader.REQUEST_AFFLICTIONS:
AfflictionRequestReceived(inc); AfflictionRequestReceived(inc);
break; break;
case NetworkHeader.AFFLICTION_UPDATE:
AfflictionUpdateReceived(inc);
break;
case NetworkHeader.REQUEST_PENDING: case NetworkHeader.REQUEST_PENDING:
PendingRequestReceived(inc); PendingRequestReceived(inc);
break; break;
@@ -40,7 +40,7 @@ namespace Barotrauma
private void CreateMessageBox(string author) private void CreateMessageBox(string author)
{ {
Vector2 relativeSize = new Vector2(GUI.IsFourByThree() ? 0.3f : 0.2f, 0.15f); Vector2 relativeSize = new Vector2(0.3f * GUI.AspectRatioAdjustment, 0.15f);
Point minSize = new Point(300, 200); Point minSize = new Point(300, 200);
msgBox = new GUIMessageBox(readyCheckHeader, readyCheckBody(author), new[] { yesButton, noButton }, relativeSize, minSize, type: GUIMessageBox.Type.Vote) { UserData = PromptData, Draggable = true }; msgBox = new GUIMessageBox(readyCheckHeader, readyCheckBody(author), new[] { yesButton, noButton }, relativeSize, minSize, type: GUIMessageBox.Type.Vote) { UserData = PromptData, Draggable = true };
@@ -311,7 +311,6 @@ namespace Barotrauma
} }
var missionDescription = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), var missionDescription = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform),
RichString.Rich(missionMessage), wrap: true); RichString.Rich(missionMessage), wrap: true);
int reward = displayedMission.GetReward(Submarine.MainSub);
if (selectedMissions.Contains(displayedMission) && displayedMission.Completed) if (selectedMissions.Contains(displayedMission) && displayedMission.Completed)
{ {
RichString reputationText = displayedMission.GetReputationRewardText(); RichString reputationText = displayedMission.GetReputationRewardText();
@@ -320,12 +319,13 @@ namespace Barotrauma
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), reputationText); new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), reputationText);
} }
if (reward > 0) int totalReward = displayedMission.GetFinalReward(Submarine.MainSub);
if (totalReward > 0)
{ {
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), RichString.Rich(displayedMission.GetMissionRewardText(Submarine.MainSub))); 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) if (GameMain.IsMultiplayer && Character.Controlled is { } controlled)
{ {
var (share, percentage, _) = Mission.GetRewardShare(controlled.Wallet.RewardDistribution, GameSession.GetSessionCrewCharacters(CharacterType.Player).Where(c => c != controlled), Option<int>.Some(reward)); var (share, percentage, _) = Mission.GetRewardShare(controlled.Wallet.RewardDistribution, GameSession.GetSessionCrewCharacters(CharacterType.Player).Where(c => c != controlled), Option<int>.Some(totalReward));
if (share > 0) if (share > 0)
{ {
string shareFormatted = string.Format(CultureInfo.InvariantCulture, "{0:N0}", share); string shareFormatted = string.Format(CultureInfo.InvariantCulture, "{0:N0}", share);
@@ -419,7 +419,7 @@ namespace Barotrauma
var factionFrame = CreateReputationElement( var factionFrame = CreateReputationElement(
reputationList.Content, reputationList.Content,
faction.Prefab.Name, faction.Prefab.Name,
faction.Reputation.Value, faction.Reputation.NormalizedValue, initialReputation, faction.Reputation, initialReputation,
faction.Prefab.ShortDescription, faction.Prefab.Description, faction.Prefab.ShortDescription, faction.Prefab.Description,
faction.Prefab.Icon, faction.Prefab.BackgroundPortrait, faction.Prefab.IconColor); faction.Prefab.Icon, faction.Prefab.BackgroundPortrait, faction.Prefab.IconColor);
CreatePathUnlockElement(factionFrame, faction, null); CreatePathUnlockElement(factionFrame, faction, null);
@@ -685,7 +685,7 @@ namespace Barotrauma
} }
private GUIFrame CreateReputationElement(GUIComponent parent, private GUIFrame CreateReputationElement(GUIComponent parent,
LocalizedString name, float reputation, float normalizedReputation, float initialReputation, LocalizedString name, Reputation reputation, float initialReputation,
LocalizedString shortDescription, LocalizedString fullDescription, Sprite icon, Sprite backgroundPortrait, Color iconColor) LocalizedString shortDescription, LocalizedString fullDescription, Sprite icon, Sprite backgroundPortrait, Color iconColor)
{ {
var factionFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.1f), parent.RectTransform), style: null); var factionFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.1f), parent.RectTransform), style: null);
@@ -703,21 +703,22 @@ namespace Barotrauma
}; };
} }
var factionInfoHorizontal = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), factionFrame.RectTransform, Anchor.Center), childAnchor: Anchor.CenterLeft, isHorizontal: true) var factionInfoHorizontal = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), factionFrame.RectTransform, Anchor.Center), childAnchor: Anchor.CenterRight, isHorizontal: true)
{ {
AbsoluteSpacing = GUI.IntScale(5), AbsoluteSpacing = GUI.IntScale(5),
Stretch = true Stretch = true
}; };
var factionIcon = new GUIImage(new RectTransform(Vector2.One * 0.7f, factionInfoHorizontal.RectTransform, scaleBasis: ScaleBasis.Smallest), icon, scaleToFit: true)
{
Color = iconColor
};
var factionTextContent = new GUILayoutGroup(new RectTransform(Vector2.One, factionInfoHorizontal.RectTransform)) var factionTextContent = new GUILayoutGroup(new RectTransform(Vector2.One, factionInfoHorizontal.RectTransform))
{ {
AbsoluteSpacing = GUI.IntScale(10), AbsoluteSpacing = GUI.IntScale(10),
Stretch = true Stretch = true
}; };
var factionIcon = new GUIImage(new RectTransform(Vector2.One * 0.7f, factionInfoHorizontal.RectTransform, scaleBasis: ScaleBasis.Smallest), icon, scaleToFit: true)
{
Color = iconColor
};
factionInfoHorizontal.Recalculate(); factionInfoHorizontal.Recalculate();
var header = new GUITextBlock(new RectTransform(new Point(factionTextContent.Rect.Width, GUI.IntScale(40)), factionTextContent.RectTransform), var header = new GUITextBlock(new RectTransform(new Point(factionTextContent.Rect.Width, GUI.IntScale(40)), factionTextContent.RectTransform),
@@ -738,24 +739,30 @@ namespace Barotrauma
factionTextContent.Recalculate(); factionTextContent.Recalculate();
new GUICustomComponent(new RectTransform(new Vector2(0.8f, 1.0f), sliderHolder.RectTransform), new GUICustomComponent(new RectTransform(new Vector2(0.8f, 1.0f), sliderHolder.RectTransform),
onDraw: (sb, customComponent) => DrawReputationBar(sb, customComponent.Rect, normalizedReputation)); onDraw: (sb, customComponent) => DrawReputationBar(sb, customComponent.Rect, reputation.NormalizedValue));
LocalizedString reputationText = Reputation.GetFormattedReputationText(normalizedReputation, reputation, addColorTags: true); var reputationText = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), sliderHolder.RectTransform),
int reputationChange = (int)Math.Round(reputation - initialReputation); string.Empty, textAlignment: Alignment.CenterLeft, font: GUIStyle.SubHeadingFont);
if (Math.Abs(reputationChange) > 0) SetReputationText(reputationText);
reputation?.OnReputationValueChanged.RegisterOverwriteExisting("RefreshRoundSummary".ToIdentifier(), _ =>
{ {
string changeText = $"{(reputationChange > 0 ? "+" : "") + reputationChange}"; SetReputationText(reputationText);
string colorStr = XMLExtensions.ToStringHex(reputationChange > 0 ? GUIStyle.Green : GUIStyle.Red); });
var richText = RichString.Rich($"{reputationText} (‖color:{colorStr}‖{changeText}‖color:end‖)");
new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), sliderHolder.RectTransform), void SetReputationText(GUITextBlock textBlock)
richText,
textAlignment: Alignment.CenterLeft, font: GUIStyle.SubHeadingFont);
}
else
{ {
new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), sliderHolder.RectTransform), LocalizedString reputationText = Reputation.GetFormattedReputationText(reputation.NormalizedValue, reputation.Value, addColorTags: true);
RichString.Rich(reputationText), int reputationChange = (int)Math.Round(reputation.Value - initialReputation);
textAlignment: Alignment.CenterLeft, font: GUIStyle.SubHeadingFont); if (Math.Abs(reputationChange) > 0)
{
string changeText = $"{(reputationChange > 0 ? "+" : "") + reputationChange}";
string colorStr = XMLExtensions.ToStringHex(reputationChange > 0 ? GUIStyle.Green : GUIStyle.Red);
textBlock.Text = RichString.Rich($"{reputationText} (‖color:{colorStr}‖{changeText}‖color:end‖)");
}
else
{
textBlock.Text = RichString.Rich(reputationText);
}
} }
//spacing //spacing
@@ -63,7 +63,6 @@ namespace Barotrauma
public Vector2[] SlotPositions; public Vector2[] SlotPositions;
public static Point SlotSize; public static Point SlotSize;
public static int Spacing;
private Layout layout; private Layout layout;
public Layout CurrentLayout public Layout CurrentLayout
@@ -103,7 +102,7 @@ namespace Barotrauma
{ {
visualSlots ??= new VisualSlot[capacity]; visualSlots ??= new VisualSlot[capacity];
float multiplier = !GUI.IsFourByThree() ? UIScale : UIScale * 0.925f; float multiplier = UIScale * GUI.AspectRatioAdjustment;
for (int i = 0; i < capacity; i++) for (int i = 0; i < capacity; i++)
{ {
@@ -219,18 +218,11 @@ namespace Barotrauma
private void SetSlotPositions(Layout layout) private void SetSlotPositions(Layout layout)
{ {
bool isFourByThree = GUI.IsFourByThree(); int spacing = GUI.IntScale(5);
if (isFourByThree)
{
Spacing = (int)(5 * UIScale);
}
else
{
Spacing = (int)(8 * UIScale);
}
SlotSize = !isFourByThree ? (SlotSpriteSmall.size * UIScale).ToPoint() : (SlotSpriteSmall.size * UIScale * .925f).ToPoint(); SlotSize = (SlotSpriteSmall.size * UIScale * GUI.AspectRatioAdjustment).ToPoint();
int bottomOffset = SlotSize.Y + Spacing * 2 + ContainedIndicatorHeight; int bottomOffset = SlotSize.Y + spacing * 2 + ContainedIndicatorHeight;
int personalSlotY = GameMain.GraphicsHeight - bottomOffset * 2 - spacing * 2 - (int)(UnequippedIndicator.size.Y * UIScale);
if (visualSlots == null) { CreateSlots(); } if (visualSlots == null) { CreateSlots(); }
if (visualSlots.None()) { return; } if (visualSlots.None()) { return; }
@@ -242,11 +234,11 @@ namespace Barotrauma
int personalSlotCount = SlotTypes.Count(s => PersonalSlots.HasFlag(s)); int personalSlotCount = SlotTypes.Count(s => PersonalSlots.HasFlag(s));
int normalSlotCount = SlotTypes.Count(s => !PersonalSlots.HasFlag(s) && s != InvSlotType.HealthInterface); int normalSlotCount = SlotTypes.Count(s => !PersonalSlots.HasFlag(s) && s != InvSlotType.HealthInterface);
int x = GameMain.GraphicsWidth / 2 - normalSlotCount * (SlotSize.X + Spacing) / 2; int x = GameMain.GraphicsWidth / 2 - normalSlotCount * (SlotSize.X + spacing) / 2;
int upperX = HUDLayoutSettings.BottomRightInfoArea.X - SlotSize.X - Spacing; int upperX = HUDLayoutSettings.BottomRightInfoArea.X - SlotSize.X - spacing;
//make sure the rightmost normal slot doesn't overlap with the personal slots //make sure the rightmost normal slot doesn't overlap with the personal slots
x -= Math.Max((x + normalSlotCount * (SlotSize.X + Spacing)) - (upperX - personalSlotCount * (SlotSize.X + Spacing)), 0); x -= Math.Max((x + normalSlotCount * (SlotSize.X + spacing)) - (upperX - personalSlotCount * (SlotSize.X + spacing)), 0);
int hideButtonSlotIndex = -1; int hideButtonSlotIndex = -1;
for (int i = 0; i < SlotPositions.Length; i++) for (int i = 0; i < SlotPositions.Length; i++)
@@ -254,7 +246,7 @@ namespace Barotrauma
if (PersonalSlots.HasFlag(SlotTypes[i])) if (PersonalSlots.HasFlag(SlotTypes[i]))
{ {
SlotPositions[i] = new Vector2(upperX, GameMain.GraphicsHeight - bottomOffset); SlotPositions[i] = new Vector2(upperX, GameMain.GraphicsHeight - bottomOffset);
upperX -= SlotSize.X + Spacing; upperX -= SlotSize.X + spacing;
personalSlotArea = (hideButtonSlotIndex == -1) ? personalSlotArea = (hideButtonSlotIndex == -1) ?
new Rectangle(SlotPositions[i].ToPoint(), SlotSize) : new Rectangle(SlotPositions[i].ToPoint(), SlotSize) :
Rectangle.Union(personalSlotArea, new Rectangle(SlotPositions[i].ToPoint(), SlotSize)); Rectangle.Union(personalSlotArea, new Rectangle(SlotPositions[i].ToPoint(), SlotSize));
@@ -263,7 +255,7 @@ namespace Barotrauma
else else
{ {
SlotPositions[i] = new Vector2(x, GameMain.GraphicsHeight - bottomOffset); SlotPositions[i] = new Vector2(x, GameMain.GraphicsHeight - bottomOffset);
x += SlotSize.X + Spacing; x += SlotSize.X + spacing;
} }
} }
} }
@@ -271,7 +263,7 @@ namespace Barotrauma
case Layout.Right: case Layout.Right:
{ {
int x = HUDLayoutSettings.InventoryAreaLower.Right; int x = HUDLayoutSettings.InventoryAreaLower.Right;
int personalSlotX = HUDLayoutSettings.InventoryAreaLower.Right - SlotSize.X - Spacing; int personalSlotX = HUDLayoutSettings.InventoryAreaLower.Right - SlotSize.X - spacing;
for (int i = 0; i < visualSlots.Length; i++) for (int i = 0; i < visualSlots.Length; i++)
{ {
if (HideSlot(i) || SlotTypes[i] == InvSlotType.HealthInterface) { continue; } if (HideSlot(i) || SlotTypes[i] == InvSlotType.HealthInterface) { continue; }
@@ -282,19 +274,18 @@ namespace Barotrauma
} }
else else
{ {
x -= SlotSize.X + Spacing; x -= SlotSize.X + spacing;
} }
} }
int lowerX = x; int lowerX = x;
int handSlotX = x; int handSlotX = x;
int personalSlotY = GameMain.GraphicsHeight - bottomOffset * 2 - Spacing * 2 - (int)(!GUI.IsFourByThree() ? UnequippedIndicator.size.Y * UIScale * IndicatorScaleAdjustment : UnequippedIndicator.size.Y * UIScale * IndicatorScaleAdjustment * 2f);
for (int i = 0; i < SlotPositions.Length; i++) for (int i = 0; i < SlotPositions.Length; i++)
{ {
if (SlotTypes[i] == InvSlotType.RightHand || SlotTypes[i] == InvSlotType.LeftHand) if (SlotTypes[i] == InvSlotType.RightHand || SlotTypes[i] == InvSlotType.LeftHand)
{ {
SlotPositions[i] = new Vector2(handSlotX, personalSlotY); SlotPositions[i] = new Vector2(handSlotX, personalSlotY);
handSlotX += visualSlots[i].Rect.Width + Spacing; handSlotX += visualSlots[i].Rect.Width + spacing;
continue; continue;
} }
@@ -302,12 +293,12 @@ namespace Barotrauma
if (PersonalSlots.HasFlag(SlotTypes[i])) if (PersonalSlots.HasFlag(SlotTypes[i]))
{ {
SlotPositions[i] = new Vector2(personalSlotX, personalSlotY); SlotPositions[i] = new Vector2(personalSlotX, personalSlotY);
personalSlotX -= visualSlots[i].Rect.Width + Spacing; personalSlotX -= visualSlots[i].Rect.Width + spacing;
} }
else else
{ {
SlotPositions[i] = new Vector2(x, GameMain.GraphicsHeight - bottomOffset); SlotPositions[i] = new Vector2(x, GameMain.GraphicsHeight - bottomOffset);
x += visualSlots[i].Rect.Width + Spacing; x += visualSlots[i].Rect.Width + spacing;
} }
} }
@@ -316,7 +307,7 @@ namespace Barotrauma
{ {
if (!HideSlot(i) || SlotTypes[i] == InvSlotType.HealthInterface) { continue; } if (!HideSlot(i) || SlotTypes[i] == InvSlotType.HealthInterface) { continue; }
if (SlotTypes[i] == InvSlotType.RightHand || SlotTypes[i] == InvSlotType.LeftHand) { continue; } if (SlotTypes[i] == InvSlotType.RightHand || SlotTypes[i] == InvSlotType.LeftHand) { continue; }
x -= visualSlots[i].Rect.Width + Spacing; x -= visualSlots[i].Rect.Width + spacing;
SlotPositions[i] = new Vector2(x, GameMain.GraphicsHeight - bottomOffset); SlotPositions[i] = new Vector2(x, GameMain.GraphicsHeight - bottomOffset);
} }
} }
@@ -325,7 +316,6 @@ namespace Barotrauma
{ {
int x = HUDLayoutSettings.InventoryAreaLower.X; int x = HUDLayoutSettings.InventoryAreaLower.X;
int personalSlotX = x; int personalSlotX = x;
int personalSlotY = GameMain.GraphicsHeight - bottomOffset * 2 - Spacing * 2 - (int)(!GUI.IsFourByThree() ? UnequippedIndicator.size.Y * UIScale * IndicatorScaleAdjustment : UnequippedIndicator.size.Y * UIScale * IndicatorScaleAdjustment * 2f);
for (int i = 0; i < SlotPositions.Length; i++) for (int i = 0; i < SlotPositions.Length; i++)
{ {
@@ -334,33 +324,33 @@ namespace Barotrauma
if (PersonalSlots.HasFlag(SlotTypes[i])) if (PersonalSlots.HasFlag(SlotTypes[i]))
{ {
SlotPositions[i] = new Vector2(personalSlotX, personalSlotY); SlotPositions[i] = new Vector2(personalSlotX, personalSlotY);
personalSlotX += visualSlots[i].Rect.Width + Spacing; personalSlotX += visualSlots[i].Rect.Width + spacing;
} }
else else
{ {
SlotPositions[i] = new Vector2(x, GameMain.GraphicsHeight - bottomOffset); SlotPositions[i] = new Vector2(x, GameMain.GraphicsHeight - bottomOffset);
x += visualSlots[i].Rect.Width + Spacing; x += visualSlots[i].Rect.Width + spacing;
} }
} }
int handSlotX = x - visualSlots[0].Rect.Width - Spacing; int handSlotX = x - visualSlots[0].Rect.Width - spacing;
for (int i = 0; i < SlotPositions.Length; i++) for (int i = 0; i < SlotPositions.Length; i++)
{ {
if (SlotTypes[i] == InvSlotType.RightHand || SlotTypes[i] == InvSlotType.LeftHand) if (SlotTypes[i] == InvSlotType.RightHand || SlotTypes[i] == InvSlotType.LeftHand)
{ {
bool rightSlot = SlotTypes[i] == InvSlotType.RightHand; bool rightSlot = SlotTypes[i] == InvSlotType.RightHand;
SlotPositions[i] = new Vector2(rightSlot ? handSlotX : handSlotX - visualSlots[0].Rect.Width - Spacing, personalSlotY); SlotPositions[i] = new Vector2(rightSlot ? handSlotX : handSlotX - visualSlots[0].Rect.Width - spacing, personalSlotY);
continue; continue;
} }
if (!HideSlot(i) || SlotTypes[i] == InvSlotType.HealthInterface) { continue; } if (!HideSlot(i) || SlotTypes[i] == InvSlotType.HealthInterface) { continue; }
SlotPositions[i] = new Vector2(x, GameMain.GraphicsHeight - bottomOffset); SlotPositions[i] = new Vector2(x, GameMain.GraphicsHeight - bottomOffset);
x += visualSlots[i].Rect.Width + Spacing; x += visualSlots[i].Rect.Width + spacing;
} }
} }
break; break;
case Layout.Center: case Layout.Center:
{ {
int columns = 5; int columns = 5;
int startX = (GameMain.GraphicsWidth / 2) - (SlotSize.X * columns + Spacing * (columns - 1)) / 2; int startX = (GameMain.GraphicsWidth / 2) - (SlotSize.X * columns + spacing * (columns - 1)) / 2;
int startY = GameMain.GraphicsHeight / 2 - (SlotSize.Y * 2); int startY = GameMain.GraphicsHeight / 2 - (SlotSize.Y * 2);
int x = startX, y = startY; int x = startX, y = startY;
for (int i = 0; i < SlotPositions.Length; i++) for (int i = 0; i < SlotPositions.Length; i++)
@@ -369,10 +359,10 @@ namespace Barotrauma
if (SlotTypes[i] == InvSlotType.Card || SlotTypes[i] == InvSlotType.Headset || SlotTypes[i] == InvSlotType.InnerClothes) if (SlotTypes[i] == InvSlotType.Card || SlotTypes[i] == InvSlotType.Headset || SlotTypes[i] == InvSlotType.InnerClothes)
{ {
SlotPositions[i] = new Vector2(x, y); SlotPositions[i] = new Vector2(x, y);
x += visualSlots[i].Rect.Width + Spacing; x += visualSlots[i].Rect.Width + spacing;
} }
} }
y += visualSlots[0].Rect.Height + Spacing + ContainedIndicatorHeight + visualSlots[0].EquipButtonRect.Height; y += visualSlots[0].Rect.Height + spacing + ContainedIndicatorHeight + visualSlots[0].EquipButtonRect.Height;
x = startX; x = startX;
int n = 0; int n = 0;
for (int i = 0; i < SlotPositions.Length; i++) for (int i = 0; i < SlotPositions.Length; i++)
@@ -381,12 +371,12 @@ namespace Barotrauma
if (SlotTypes[i] != InvSlotType.Card && SlotTypes[i] != InvSlotType.Headset && SlotTypes[i] != InvSlotType.InnerClothes) if (SlotTypes[i] != InvSlotType.Card && SlotTypes[i] != InvSlotType.Headset && SlotTypes[i] != InvSlotType.InnerClothes)
{ {
SlotPositions[i] = new Vector2(x, y); SlotPositions[i] = new Vector2(x, y);
x += visualSlots[i].Rect.Width + Spacing; x += visualSlots[i].Rect.Width + spacing;
n++; n++;
if (n >= columns) if (n >= columns)
{ {
x = startX; x = startX;
y += visualSlots[i].Rect.Height + Spacing + ContainedIndicatorHeight + visualSlots[i].EquipButtonRect.Height; y += visualSlots[i].Rect.Height + spacing + ContainedIndicatorHeight + visualSlots[i].EquipButtonRect.Height;
n = 0; n = 0;
} }
} }
@@ -402,7 +392,7 @@ namespace Barotrauma
{ {
if (SlotTypes[i] != InvSlotType.HealthInterface) { continue; } if (SlotTypes[i] != InvSlotType.HealthInterface) { continue; }
SlotPositions[i] = pos; SlotPositions[i] = pos;
pos.Y += visualSlots[i].Rect.Height + Spacing; pos.Y += visualSlots[i].Rect.Height + spacing;
} }
} }
@@ -641,7 +631,7 @@ namespace Barotrauma
{ {
slot.EquipButtonState = slot.EquipButtonRect.Contains(PlayerInput.MousePosition) ? slot.EquipButtonState = slot.EquipButtonRect.Contains(PlayerInput.MousePosition) ?
GUIComponent.ComponentState.Hover : GUIComponent.ComponentState.None; GUIComponent.ComponentState.Hover : GUIComponent.ComponentState.None;
if (PlayerInput.LeftButtonHeld() && PlayerInput.RightButtonHeld()) if (PlayerInput.PrimaryMouseButtonHeld() && PlayerInput.SecondaryMouseButtonHeld())
{ {
slot.EquipButtonState = GUIComponent.ComponentState.None; slot.EquipButtonState = GUIComponent.ComponentState.None;
} }
@@ -965,7 +955,7 @@ namespace Barotrauma
break; break;
case QuickUseAction.PutToEquippedItem: case QuickUseAction.PutToEquippedItem:
//order by the condition of the contained item to prefer putting into the item with the emptiest ammo/battery/tank //order by the condition of the contained item to prefer putting into the item with the emptiest ammo/battery/tank
foreach (Item heldItem in character.HeldItems.OrderBy(it => it.GetComponent<ItemContainer>()?.GetContainedIndicatorState() ?? 0.0f)) foreach (Item heldItem in character.HeldItems.OrderByDescending(heldItem => GetContainPriority(item, heldItem)))
{ {
if (heldItem.OwnInventory == null) { continue; } if (heldItem.OwnInventory == null) { continue; }
//don't allow swapping if we're moving items into an item with 1 slot holding a stack of items //don't allow swapping if we're moving items into an item with 1 slot holding a stack of items
@@ -986,6 +976,22 @@ namespace Barotrauma
} }
} }
break; break;
static float GetContainPriority(Item item, Item containerItem)
{
var container = containerItem.GetComponent<ItemContainer>();
if (container == null) { return 0.0f; }
for (int i = 0; i < container.Inventory.Capacity; i++)
{
var containedItems = container.Inventory.GetItemsAt(i);
if (containedItems.Any() && container.Inventory.CanBePutInSlot(item, i))
{
//if there's a stack in the contained item that we can add the item to, prefer that
return 10.0f;
}
}
return -container.GetContainedIndicatorState();
}
} }
if (success) if (success)
@@ -1003,6 +1009,46 @@ namespace Barotrauma
} }
} }
public bool CanBeAutoMovedToCorrectSlots(Item item)
{
if (item == null) { return false; }
foreach (var allowedSlot in item.AllowedSlots)
{
InvSlotType slotsFree = InvSlotType.None;
for (int i = 0; i < slots.Length; i++)
{
if (allowedSlot.HasFlag(SlotTypes[i]) && slots[i].Empty()) { slotsFree |= SlotTypes[i]; }
}
if (allowedSlot == slotsFree) { return true; }
}
return false;
}
/// <summary>
/// Flash the slots the item is allowed to go in (not taking into account whether there's already something in those slots)
/// </summary>
public void FlashAllowedSlots(Item item, Color color)
{
if (item == null || visualSlots == null) { return; }
bool flashed = false;
foreach (var allowedSlot in item.AllowedSlots)
{
for (int i = 0; i < slots.Length; i++)
{
if (allowedSlot.HasFlag(SlotTypes[i]))
{
visualSlots[i].ShowBorderHighlight(color, 0.1f, 0.9f);
flashed = true;
}
}
}
if (flashed)
{
SoundPlayer.PlayUISound(GUISoundType.PickItemFail);
}
}
public void DrawOwn(SpriteBatch spriteBatch) public void DrawOwn(SpriteBatch spriteBatch)
{ {
if (!AccessibleWhenAlive && !character.IsDead && !AccessibleByOwner) { return; } if (!AccessibleWhenAlive && !character.IsDead && !AccessibleByOwner) { return; }
@@ -1090,40 +1136,24 @@ namespace Barotrauma
color *= 0.5f; color *= 0.5f;
} }
if (character.HasEquippedItem(slots[i].First())) Vector2 indicatorScale = new Vector2(
visualSlots[i].EquipButtonRect.Size.X / EquippedIndicator.size.X,
visualSlots[i].EquipButtonRect.Size.Y / EquippedIndicator.size.Y);
bool isEquipped = character.HasEquippedItem(slots[i].First());
var sprite = state switch
{ {
switch (state) GUIComponent.ComponentState.None
{ => isEquipped ? EquippedIndicator : UnequippedIndicator,
case GUIComponent.ComponentState.None: GUIComponent.ComponentState.Hover
EquippedIndicator.Draw(spriteBatch, visualSlots[i].EquipButtonRect.Center.ToVector2(), color, EquippedIndicator.Origin, 0, UIScale * IndicatorScaleAdjustment); => isEquipped ? EquippedHoverIndicator : UnequippedHoverIndicator,
break; GUIComponent.ComponentState.Pressed
case GUIComponent.ComponentState.Hover: or GUIComponent.ComponentState.Selected
EquippedHoverIndicator.Draw(spriteBatch, visualSlots[i].EquipButtonRect.Center.ToVector2(), color, EquippedIndicator.Origin, 0, UIScale * IndicatorScaleAdjustment); or GUIComponent.ComponentState.HoverSelected
break; => isEquipped ? EquippedClickedIndicator : UnequippedClickedIndicator,
case GUIComponent.ComponentState.Pressed: _ => throw new NotImplementedException()
case GUIComponent.ComponentState.Selected: };
case GUIComponent.ComponentState.HoverSelected: sprite.Draw(spriteBatch, visualSlots[i].EquipButtonRect.Center.ToVector2(), color, EquippedIndicator.Origin, 0, indicatorScale);
EquippedClickedIndicator.Draw(spriteBatch, visualSlots[i].EquipButtonRect.Center.ToVector2(), color, EquippedIndicator.Origin, 0, UIScale * IndicatorScaleAdjustment);
break;
}
}
else
{
switch (state)
{
case GUIComponent.ComponentState.None:
UnequippedIndicator.Draw(spriteBatch, visualSlots[i].EquipButtonRect.Center.ToVector2(), color, EquippedIndicator.Origin, 0, UIScale * IndicatorScaleAdjustment);
break;
case GUIComponent.ComponentState.Hover:
UnequippedHoverIndicator.Draw(spriteBatch, visualSlots[i].EquipButtonRect.Center.ToVector2(), color, EquippedIndicator.Origin, 0, UIScale * IndicatorScaleAdjustment);
break;
case GUIComponent.ComponentState.Pressed:
case GUIComponent.ComponentState.Selected:
case GUIComponent.ComponentState.HoverSelected:
UnequippedClickedIndicator.Draw(spriteBatch, visualSlots[i].EquipButtonRect.Center.ToVector2(), color, EquippedIndicator.Origin, 0, UIScale * IndicatorScaleAdjustment);
break;
}
}
} }
if (Locked) if (Locked)
@@ -182,7 +182,7 @@ namespace Barotrauma.Items.Components
if (brokenSprite == null) if (brokenSprite == null)
{ {
//broken doors turn black if no broken sprite has been configured //broken doors turn black if no broken sprite has been configured
color *= (item.Condition / item.MaxCondition); color = color.Multiply(item.Condition / item.MaxCondition);
color.A = 255; color.A = 255;
} }
@@ -216,16 +216,19 @@ namespace Barotrauma.Items.Components
if (brokenSprite == null || !IsBroken) if (brokenSprite == null || !IsBroken)
{ {
spriteBatch.Draw(doorSprite.Texture, pos, if (doorSprite?.Texture != null)
getSourceRect(doorSprite, openState, IsHorizontal), {
color, 0.0f, doorSprite.Origin, item.Scale, item.SpriteEffects, doorSprite.Depth); spriteBatch.Draw(doorSprite.Texture, pos,
getSourceRect(doorSprite, openState, IsHorizontal),
color, 0.0f, doorSprite.Origin, item.Scale, item.SpriteEffects, doorSprite.Depth);
}
} }
float maxCondition = item.Repairables.Any() ? float maxCondition = item.Repairables.Any() ?
item.Repairables.Min(r => r.RepairThreshold) / 100.0f * item.MaxCondition : item.Repairables.Min(r => r.RepairThreshold) / 100.0f * item.MaxCondition :
item.MaxCondition; item.MaxCondition;
float healthRatio = item.Health / maxCondition; float healthRatio = item.Health / maxCondition;
if (brokenSprite != null && healthRatio < 1.0f) if (brokenSprite?.Texture != null && healthRatio < 1.0f)
{ {
Vector2 scale = scaleBrokenSprite ? new Vector2(1.0f - healthRatio) : Vector2.One; Vector2 scale = scaleBrokenSprite ? new Vector2(1.0f - healthRatio) : Vector2.One;
if (IsHorizontal) { scale.X = 1; } else { scale.Y = 1; } if (IsHorizontal) { scale.X = 1; } else { scale.Y = 1; }
@@ -285,7 +288,10 @@ namespace Barotrauma.Items.Components
//sent by the server, or reverting it back to its old state if no msg from server was received //sent by the server, or reverting it back to its old state if no msg from server was received
PredictedState = open; PredictedState = open;
resetPredictionTimer = CorrectionDelay; resetPredictionTimer = CorrectionDelay;
if (stateChanged) PlaySound(forcedOpen ? ActionType.OnPicked : ActionType.OnUse); if (stateChanged && !IsBroken)
{
PlayInteractionSound();
}
} }
else else
{ {
@@ -293,26 +299,34 @@ namespace Barotrauma.Items.Components
if (!isNetworkMessage || open != PredictedState) if (!isNetworkMessage || open != PredictedState)
{ {
StopPicking(null); StopPicking(null);
ActionType actionType = ActionType.OnUse; if (!IsBroken)
if (forcedOpen)
{ {
actionType = ActionType.OnPicked; PlayInteractionSound();
} }
else
{
if (open && HasSoundsOfType[(int)ActionType.OnOpen])
{
actionType = ActionType.OnOpen;
}
else if (!open && HasSoundsOfType[(int)ActionType.OnClose])
{
actionType = ActionType.OnClose;
}
}
PlaySound(actionType);
if (isOpen) { stuck = MathHelper.Clamp(stuck - StuckReductionOnOpen, 0.0f, 100.0f); } if (isOpen) { stuck = MathHelper.Clamp(stuck - StuckReductionOnOpen, 0.0f, 100.0f); }
} }
} }
void PlayInteractionSound()
{
ActionType actionType = ActionType.OnUse;
if (forcedOpen)
{
actionType = ActionType.OnPicked;
}
else
{
if (open && HasSoundsOfType[(int)ActionType.OnOpen])
{
actionType = ActionType.OnOpen;
}
else if (!open && HasSoundsOfType[(int)ActionType.OnClose])
{
actionType = ActionType.OnClose;
}
}
PlaySound(actionType);
}
} }
public override void ClientEventRead(IReadMessage msg, float sendingTime) public override void ClientEventRead(IReadMessage msg, float sendingTime)
@@ -1,13 +1,10 @@
using Barotrauma.Particles; using Barotrauma.Particles;
using Barotrauma.Sounds;
using FarseerPhysics; using FarseerPhysics;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Barotrauma.IO;
using System.Text;
using System.Xml.Linq;
using Barotrauma.Sounds;
using System.Linq; using System.Linq;
namespace Barotrauma.Items.Components namespace Barotrauma.Items.Components
@@ -169,11 +166,11 @@ namespace Barotrauma.Items.Components
partial void LaunchProjSpecific() partial void LaunchProjSpecific()
{ {
Vector2 particlePos = item.WorldPosition + ConvertUnits.ToDisplayUnits(TransformedBarrelPos); Vector2 particlePos = item.WorldPosition + ConvertUnits.ToDisplayUnits(TransformedBarrelPos);
float rotation = -item.body.Rotation; float rotation = item.body.Rotation;
if (item.body.Dir < 0.0f) { rotation += MathHelper.Pi; } if (item.body.Dir < 0.0f) { rotation += MathHelper.Pi; }
foreach (ParticleEmitter emitter in particleEmitters) foreach (ParticleEmitter emitter in particleEmitters)
{ {
emitter.Emit(1.0f, particlePos, hullGuess: item.CurrentHull, angle: rotation, particleRotation: rotation); emitter.Emit(1.0f, particlePos, hullGuess: item.CurrentHull, angle: rotation, particleRotation: -rotation);
} }
} }
@@ -505,13 +505,14 @@ namespace Barotrauma.Items.Components
} }
ActionType type; ActionType type;
string typeStr = subElement.GetAttributeString("type", "");
try try
{ {
type = (ActionType)Enum.Parse(typeof(ActionType), subElement.GetAttributeString("type", ""), true); type = (ActionType)Enum.Parse(typeof(ActionType), typeStr, true);
} }
catch (Exception e) catch (Exception e)
{ {
DebugConsole.ThrowError("Invalid sound type in " + subElement + "!", e); DebugConsole.ThrowError($"Invalid sound type \"{typeStr}\" in item \"{item.Prefab.Identifier}\"!", e);
break; break;
} }
@@ -277,7 +277,8 @@ namespace Barotrauma.Items.Components
int ignoredItemCount = 0; int ignoredItemCount = 0;
var subContainableItems = AllSubContainableItems; var subContainableItems = AllSubContainableItems;
float capacity = GetMaxStackSize(targetSlot); float targetSlotCapacity = GetMaxStackSize(targetSlot);
float capacity = targetSlotCapacity * MainContainerCapacity;
if (subContainableItems != null) if (subContainableItems != null)
{ {
bool useMainContainerCapacity = true; bool useMainContainerCapacity = true;
@@ -299,15 +300,11 @@ namespace Barotrauma.Items.Components
} }
if (!useMainContainerCapacity) { break; } if (!useMainContainerCapacity) { break; }
} }
if (useMainContainerCapacity) if (!useMainContainerCapacity)
{
capacity *= MainContainerCapacity;
}
else
{ {
// Ignore all items in the main container. // Ignore all items in the main container.
ignoredItemCount = Inventory.AllItems.Count(it => subContainableItems.Any(ri => !ri.MatchesItem(it))); ignoredItemCount = Inventory.AllItems.Count(it => subContainableItems.Any(ri => !ri.MatchesItem(it)));
capacity *= Capacity - MainContainerCapacity; capacity = targetSlotCapacity * (Capacity - MainContainerCapacity);
} }
} }
int itemCount = Inventory.AllItems.Count() - ignoredItemCount; int itemCount = Inventory.AllItems.Count() - ignoredItemCount;
@@ -391,63 +388,60 @@ namespace Barotrauma.Items.Components
bool isWiringMode = SubEditorScreen.TransparentWiringMode && SubEditorScreen.IsWiringMode(); bool isWiringMode = SubEditorScreen.TransparentWiringMode && SubEditorScreen.IsWiringMode();
int i = 0; int i = 0;
foreach (Item containedItem in Inventory.AllItems) foreach (DrawableContainedItem contained in drawableContainedItems)
{ {
Vector2 itemPos = currentItemPos; Vector2 itemPos = currentItemPos;
var relatedItem = FindContainableItem(containedItem);
if (relatedItem != null) if (contained.Item?.Sprite == null) { continue; }
if (contained.Hide) { continue; }
if (contained.ItemPos.HasValue)
{ {
if (relatedItem.Hide.HasValue && relatedItem.Hide.Value) { continue; } Vector2 pos = contained.ItemPos.Value;
if (relatedItem.ItemPos.HasValue) if (item.body != null)
{ {
Vector2 pos = relatedItem.ItemPos.Value; Matrix transform = Matrix.CreateRotationZ(item.body.DrawRotation);
if (item.body != null) pos.X *= item.body.Dir;
itemPos = Vector2.Transform(pos, transform) + item.body.DrawPosition;
}
else
{
itemPos = pos;
// This code is aped based on above. Not tested.
if (item.FlippedX)
{ {
Matrix transform = Matrix.CreateRotationZ(item.body.DrawRotation); itemPos.X = -itemPos.X;
pos.X *= item.body.Dir; itemPos.X += item.Rect.Width;
itemPos = Vector2.Transform(pos, transform) + item.body.DrawPosition;
} }
else if (item.FlippedY)
{ {
itemPos = pos; itemPos.Y = -itemPos.Y;
// This code is aped based on above. Not tested. itemPos.Y -= item.Rect.Height;
if (item.FlippedX) }
{ itemPos += new Vector2(item.Rect.X, item.Rect.Y);
itemPos.X = -itemPos.X; if (item.Submarine != null)
itemPos.X += item.Rect.Width; {
} itemPos += item.Submarine.DrawPosition;
if (item.FlippedY) }
{ if (Math.Abs(item.RotationRad) > 0.01f)
itemPos.Y = -itemPos.Y; {
itemPos.Y -= item.Rect.Height; Matrix transform = Matrix.CreateRotationZ(-item.RotationRad);
} itemPos = Vector2.Transform(itemPos - item.DrawPosition, transform) + item.DrawPosition;
itemPos += new Vector2(item.Rect.X, item.Rect.Y);
if (item.Submarine != null)
{
itemPos += item.Submarine.DrawPosition;
}
if (Math.Abs(item.RotationRad) > 0.01f)
{
Matrix transform = Matrix.CreateRotationZ(-item.RotationRad);
itemPos = Vector2.Transform(itemPos - item.DrawPosition, transform) + item.DrawPosition;
}
} }
} }
} }
if (containedItem?.Sprite == null) { continue; }
if (AutoInteractWithContained) if (AutoInteractWithContained)
{ {
containedItem.IsHighlighted = item.IsHighlighted; contained.Item.IsHighlighted = item.IsHighlighted;
item.IsHighlighted = false; item.IsHighlighted = false;
} }
Vector2 origin = containedItem.Sprite.Origin; Vector2 origin = contained.Item.Sprite.Origin;
if (item.FlippedX) { origin.X = containedItem.Sprite.SourceRect.Width - origin.X; } if (item.FlippedX) { origin.X = contained.Item.Sprite.SourceRect.Width - origin.X; }
if (item.FlippedY) { origin.Y = containedItem.Sprite.SourceRect.Height - origin.Y; } if (item.FlippedY) { origin.Y = contained.Item.Sprite.SourceRect.Height - origin.Y; }
float containedSpriteDepth = ContainedSpriteDepth < 0.0f ? containedItem.Sprite.Depth : ContainedSpriteDepth; float containedSpriteDepth = ContainedSpriteDepth < 0.0f ? contained.Item.Sprite.Depth : ContainedSpriteDepth;
if (i < containedSpriteDepths.Length) if (i < containedSpriteDepths.Length)
{ {
containedSpriteDepth = containedSpriteDepths[i]; containedSpriteDepth = containedSpriteDepths[i];
@@ -456,9 +450,9 @@ namespace Barotrauma.Items.Components
SpriteEffects spriteEffects = SpriteEffects.None; SpriteEffects spriteEffects = SpriteEffects.None;
float spriteRotation = ItemRotation; float spriteRotation = ItemRotation;
if (relatedItem != null && relatedItem.Rotation != 0) if (contained.Rotation != 0)
{ {
spriteRotation = relatedItem.Rotation; spriteRotation = contained.Rotation;
} }
if ((item.body != null && item.body.Dir == -1) || item.FlippedX) if ((item.body != null && item.body.Dir == -1) || item.FlippedX)
{ {
@@ -469,17 +463,17 @@ namespace Barotrauma.Items.Components
spriteEffects |= MathUtils.NearlyEqual(spriteRotation % 180, 90.0f) ? SpriteEffects.FlipHorizontally : SpriteEffects.FlipVertically; spriteEffects |= MathUtils.NearlyEqual(spriteRotation % 180, 90.0f) ? SpriteEffects.FlipHorizontally : SpriteEffects.FlipVertically;
} }
containedItem.Sprite.Draw( contained.Item.Sprite.Draw(
spriteBatch, spriteBatch,
new Vector2(itemPos.X, -itemPos.Y), new Vector2(itemPos.X, -itemPos.Y),
isWiringMode ? containedItem.GetSpriteColor(withHighlight: true) * 0.15f : containedItem.GetSpriteColor(withHighlight: true), isWiringMode ? contained.Item.GetSpriteColor(withHighlight: true) * 0.15f : contained.Item.GetSpriteColor(withHighlight: true),
origin, origin,
-(containedItem.body == null ? 0.0f : containedItem.body.DrawRotation), -(contained.Item.body == null ? 0.0f : contained.Item.body.DrawRotation),
containedItem.Scale, contained.Item.Scale,
spriteEffects, spriteEffects,
depth: containedSpriteDepth); depth: containedSpriteDepth);
foreach (ItemContainer ic in containedItem.GetComponents<ItemContainer>()) foreach (ItemContainer ic in contained.Item.GetComponents<ItemContainer>())
{ {
if (ic.hideItems) { continue; } if (ic.hideItems) { continue; }
ic.DrawContainedItems(spriteBatch, containedSpriteDepth); ic.DrawContainedItems(spriteBatch, containedSpriteDepth);
@@ -78,14 +78,21 @@ namespace Barotrauma.Items.Components
public void Draw(SpriteBatch spriteBatch, bool editing = false, float itemDepth = -1) public void Draw(SpriteBatch spriteBatch, bool editing = false, float itemDepth = -1)
{ {
if (Light.LightSprite != null && (item.body == null || item.body.Enabled) && lightBrightness > 0.0f && IsOn && Light.Enabled) if (Light?.LightSprite == null) { return; }
if ((item.body == null || item.body.Enabled) && lightBrightness > 0.0f && IsOn && Light.Enabled)
{ {
Vector2 origin = Light.LightSprite.Origin; Vector2 origin = Light.LightSprite.Origin;
if ((Light.LightSpriteEffect & SpriteEffects.FlipHorizontally) == SpriteEffects.FlipHorizontally) { origin.X = Light.LightSprite.SourceRect.Width - origin.X; } if ((Light.LightSpriteEffect & SpriteEffects.FlipHorizontally) == SpriteEffects.FlipHorizontally) { origin.X = Light.LightSprite.SourceRect.Width - origin.X; }
if ((Light.LightSpriteEffect & SpriteEffects.FlipVertically) == SpriteEffects.FlipVertically) { origin.Y = Light.LightSprite.SourceRect.Height - origin.Y; } if ((Light.LightSpriteEffect & SpriteEffects.FlipVertically) == SpriteEffects.FlipVertically) { origin.Y = Light.LightSprite.SourceRect.Height - origin.Y; }
Vector2 drawPos = item.body?.DrawPosition ?? item.DrawPosition; Vector2 drawPos = item.body?.DrawPosition ?? item.DrawPosition;
Light.LightSprite.Draw(spriteBatch, new Vector2(drawPos.X, -drawPos.Y), lightColor * lightBrightness, origin, -Light.Rotation, item.Scale, Light.LightSpriteEffect, itemDepth - 0.0001f);
Color color = lightColor;
if (Light.OverrideLightSpriteAlpha.HasValue)
{
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);
} }
} }
@@ -185,6 +185,7 @@ namespace Barotrauma.Items.Components
RefreshActivateButtonText(); RefreshActivateButtonText();
if (GameMain.Client != null) if (GameMain.Client != null)
{ {
pendingFabricatedItem = null;
item.CreateClientEvent(this); item.CreateClientEvent(this);
} }
return true; return true;
@@ -336,8 +337,11 @@ namespace Barotrauma.Items.Components
int calculatePlacement(FabricationRecipe recipe) int calculatePlacement(FabricationRecipe recipe)
{ {
if (recipe.RequiresRecipe && !AnyOneHasRecipeForItem(character, recipe.TargetItem))
{
return -2;
}
int placement = FabricationDegreeOfSuccess(character, recipe.RequiredSkills) >= 0.5f ? 0 : -1; int placement = FabricationDegreeOfSuccess(character, recipe.RequiredSkills) >= 0.5f ? 0 : -1;
placement += recipe.RequiresRecipe && !AnyOneHasRecipeForItem(character, recipe.TargetItem) ? -2 : 0;
return placement; return placement;
} }
@@ -298,7 +298,7 @@ namespace Barotrauma.Items.Components
} }
} }
OrderPrefab[] reports = OrderPrefab.Prefabs.Where(o => o.IsReport && o.SymbolSprite != null && !o.Hidden).ToArray(); OrderPrefab[] reports = OrderPrefab.Prefabs.Where(o => o.IsReport && o.SymbolSprite != null && !o.Hidden).OrderBy(o => o.Identifier).ToArray();
GUIFrame bottomFrame = new GUIFrame(new RectTransform(new Vector2(0.5f, 0.15f), paddedContainer.RectTransform, Anchor.BottomCenter) { MaxSize = new Point(int.MaxValue, GUI.IntScale(40)) }, style: null) GUIFrame bottomFrame = new GUIFrame(new RectTransform(new Vector2(0.5f, 0.15f), paddedContainer.RectTransform, Anchor.BottomCenter) { MaxSize = new Point(int.MaxValue, GUI.IntScale(40)) }, style: null)
{ {
@@ -413,7 +413,7 @@ namespace Barotrauma.Items.Components
var wire = it.GetComponent<Wire>(); var wire = it.GetComponent<Wire>();
if (wire != null && wire.Connections.Any(c => c != null)) { return false; } if (wire != null && wire.Connections.Any(c => c != null)) { return false; }
if (it.Container?.GetComponent<ItemContainer>() is { DrawInventory: false }) { return false; } if (it.Container?.GetComponent<ItemContainer>() is { DrawInventory: false } or { AllowAccess: false }) { return false; }
if (it.HasTag("traitormissionitem")) { return false; } if (it.HasTag("traitormissionitem")) { return false; }
@@ -452,7 +452,7 @@ namespace Barotrauma.Items.Components
foreach (var (entity, component) in electricalMapComponents) foreach (var (entity, component) in electricalMapComponents)
{ {
GUIComponent parent = component.RectComponent; GUIComponent parent = component.RectComponent;
if (!(entity is Item it )) { continue; } if (entity is not Item it ) { continue; }
Sprite? sprite = it.Prefab.UpgradePreviewSprite; Sprite? sprite = it.Prefab.UpgradePreviewSprite;
if (sprite is null) { continue; } if (sprite is null) { continue; }
@@ -476,7 +476,7 @@ namespace Barotrauma.Items.Components
{ {
if (!hullPointsOfInterest.Contains(entity)) { continue; } if (!hullPointsOfInterest.Contains(entity)) { continue; }
if (!(entity is Item it)) { continue; } if (entity is not Item it) { continue; }
const int borderMaxSize = 2; const int borderMaxSize = 2;
if (it.GetComponent<Door>() is { }) if (it.GetComponent<Door>() is { })
@@ -643,7 +643,7 @@ namespace Barotrauma.Items.Components
elementSize = GuiFrame.Rect.Size; elementSize = GuiFrame.Rect.Size;
} }
float distort = 1.0f - item.Condition / item.MaxCondition; float distort = item.Repairables.Any(r => r.IsBelowRepairThreshold) ? 1.0f - item.Condition / item.MaxCondition : 0.0f;
foreach (HullData hullData in hullDatas.Values) foreach (HullData hullData in hullDatas.Values)
{ {
hullData.DistortionTimer -= deltaTime; hullData.DistortionTimer -= deltaTime;
@@ -1130,7 +1130,7 @@ namespace Barotrauma.Items.Components
{ {
foreach (var (entity, miniMapGuiComponent) in electricalMapComponents) foreach (var (entity, miniMapGuiComponent) in electricalMapComponents)
{ {
if (!(entity is Item it)) { continue; } if (entity is not Item it) { continue; }
if (!electricalChildren.TryGetValue(miniMapGuiComponent, out GUIComponent? component)) { continue; } if (!electricalChildren.TryGetValue(miniMapGuiComponent, out GUIComponent? component)) { continue; }
if (entity.Removed) if (entity.Removed)
@@ -1220,7 +1220,7 @@ namespace Barotrauma.Items.Components
{ {
foreach (var (entity, component) in hullStatusComponents) foreach (var (entity, component) in hullStatusComponents)
{ {
if (!(entity is Hull hull)) { continue; } if (entity is not Hull hull) { continue; }
if (!hullDatas.TryGetValue(hull, out HullData? hullData) || hullData is null) { continue; } if (!hullDatas.TryGetValue(hull, out HullData? hullData) || hullData is null) { continue; }
if (hullData.Distort) { continue; } if (hullData.Distort) { continue; }
@@ -73,6 +73,8 @@ namespace Barotrauma.Items.Components
public bool TriggerInfographic { get; set; } public bool TriggerInfographic { get; set; }
public bool IsInfographicVisible => infographic != null && infographic.Visible;
partial void InitProjSpecific(ContentXElement element) partial void InitProjSpecific(ContentXElement element)
{ {
CreateGUI(); CreateGUI();
@@ -108,6 +110,9 @@ namespace Barotrauma.Items.Components
{ AbsoluteOffset = GUIStyle.ItemFrameOffset }, { AbsoluteOffset = GUIStyle.ItemFrameOffset },
isHorizontal: true) isHorizontal: true)
{ {
CanBeFocused = true,
HoverCursor = CursorState.Default,
AlwaysOverrideCursor = true,
RelativeSpacing = 0.012f, RelativeSpacing = 0.012f,
Stretch = true Stretch = true
}; };
@@ -675,7 +680,7 @@ namespace Barotrauma.Items.Components
} }
} }
if (TriggerInfographic) if (GuiFrame is not null && GuiFrame.Visible && TriggerInfographic)
{ {
CreateInfrographic(); CreateInfrographic();
TriggerInfographic = false; TriggerInfographic = false;
@@ -851,8 +856,9 @@ namespace Barotrauma.Items.Components
{ {
AbsoluteOffset = new Point(0, -50).Multiply(GUI.Scale) AbsoluteOffset = new Point(0, -50).Multiply(GUI.Scale)
}; };
new GUIButton(closeButtonRt, TextManager.Get("close")) new GUIButton(closeButtonRt, TextManager.Get("closeinfographic"))
{ {
UserData = UIHighlightAction.ElementId.CloseButton,
OnClicked = (_, _) => OnClicked = (_, _) =>
{ {
CloseInfographic(Character.Controlled); CloseInfographic(Character.Controlled);
@@ -871,6 +877,7 @@ namespace Barotrauma.Items.Components
string style = arrowStyle == InfographicArrowStyle.Straight ? "InfographicArrow" : "InfographicArrowCurved"; string style = arrowStyle == InfographicArrowStyle.Straight ? "InfographicArrow" : "InfographicArrowCurved";
return new GUIImage(rt, style) return new GUIImage(rt, style)
{ {
CanBeFocused = false,
Rotation = MathHelper.ToRadians(rotationDegrees), Rotation = MathHelper.ToRadians(rotationDegrees),
SpriteEffects = spriteEffects SpriteEffects = spriteEffects
}; };
@@ -20,6 +20,7 @@ namespace Barotrauma.Items.Components
User = Entity.FindEntityByID(userId) as Character; User = Entity.FindEntityByID(userId) as Character;
Vector2 simPosition = new Vector2(msg.ReadSingle(), msg.ReadSingle()); Vector2 simPosition = new Vector2(msg.ReadSingle(), msg.ReadSingle());
float rotation = msg.ReadSingle(); float rotation = msg.ReadSingle();
SpreadCounter = msg.ReadByte();
if (User != null) if (User != null)
{ {
Shoot(User, simPosition, simPosition, rotation, ignoredBodies: User.AnimController.Limbs.Where(l => !l.IsSevered).Select(l => l.body.FarseerBody).ToList(), createNetworkEvent: false); Shoot(User, simPosition, simPosition, rotation, ignoredBodies: User.AnimController.Limbs.Where(l => !l.IsSevered).Select(l => l.body.FarseerBody).ToList(), createNetworkEvent: false);
@@ -262,9 +262,11 @@ namespace Barotrauma.Items.Components
} }
} }
float conditionPercentage = item.Condition / (item.MaxCondition / item.MaxRepairConditionMultiplier) * 100f;
for (int i = 0; i < particleEmitters.Count; i++) for (int i = 0; i < particleEmitters.Count; i++)
{ {
if ((item.ConditionPercentage >= particleEmitterConditionRanges[i].X && item.ConditionPercentage <= particleEmitterConditionRanges[i].Y) || FakeBrokenTimer > 0.0f) if ((conditionPercentage >= particleEmitterConditionRanges[i].X && conditionPercentage <= particleEmitterConditionRanges[i].Y) || FakeBrokenTimer > 0.0f)
{ {
particleEmitters[i].Emit(deltaTime, item.WorldPosition, item.CurrentHull); particleEmitters[i].Emit(deltaTime, item.WorldPosition, item.CurrentHull);
} }
@@ -436,12 +438,16 @@ namespace Barotrauma.Items.Components
ushort currentFixerID = msg.ReadUInt16(); ushort currentFixerID = msg.ReadUInt16();
currentFixerAction = (FixActions)msg.ReadRangedInteger(0, 2); currentFixerAction = (FixActions)msg.ReadRangedInteger(0, 2);
CurrentFixer = currentFixerID != 0 ? Entity.FindEntityByID(currentFixerID) as Character : null; CurrentFixer = currentFixerID != 0 ? Entity.FindEntityByID(currentFixerID) as Character : null;
item.MaxRepairConditionMultiplier = GetMaxRepairConditionMultiplier(CurrentFixer);
if (CurrentFixer == null) if (CurrentFixer is null)
{ {
qteTimer = QteDuration; qteTimer = QteDuration;
qteCooldown = 0.0f; qteCooldown = 0.0f;
} }
else
{
item.MaxRepairConditionMultiplier = GetMaxRepairConditionMultiplier(CurrentFixer);
}
} }
public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null) public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null)
@@ -108,6 +108,7 @@ namespace Barotrauma.Items.Components
{ {
startPos += new Vector2((float)Math.Cos(turret.Rotation), (float)Math.Sin(turret.Rotation)) * turret.BarrelSprite.size.Y * turret.BarrelSprite.RelativeOrigin.Y * item.Scale * 0.9f; startPos += new Vector2((float)Math.Cos(turret.Rotation), (float)Math.Sin(turret.Rotation)) * turret.BarrelSprite.size.Y * turret.BarrelSprite.RelativeOrigin.Y * item.Scale * 0.9f;
} }
startPos -= turret.GetRecoilOffset();
} }
else if (weapon != null) else if (weapon != null)
{ {
@@ -110,8 +110,8 @@ namespace Barotrauma.Items.Components
if (HighlightedWire != null) if (HighlightedWire != null)
{ {
HighlightedWire.Item.IsHighlighted = true; HighlightedWire.Item.IsHighlighted = true;
if (HighlightedWire.Connections[0] != null && HighlightedWire.Connections[0].Item != null) HighlightedWire.Connections[0].Item.IsHighlighted = true; if (HighlightedWire.Connections[0] != null && HighlightedWire.Connections[0].Item != null) { HighlightedWire.Connections[0].Item.IsHighlighted = true; }
if (HighlightedWire.Connections[1] != null && HighlightedWire.Connections[1].Item != null) HighlightedWire.Connections[1].Item.IsHighlighted = true; if (HighlightedWire.Connections[1] != null && HighlightedWire.Connections[1].Item != null) { HighlightedWire.Connections[1].Item.IsHighlighted = true; }
} }
} }
@@ -329,6 +329,7 @@ namespace Barotrauma.Items.Components
partial void UpdateSignalsProjSpecific() partial void UpdateSignalsProjSpecific()
{ {
if (signals == null) { return; }
for (int i = 0; i < signals.Length && i < uiElements.Count; i++) for (int i = 0; i < signals.Length && i < uiElements.Count; i++)
{ {
if (uiElements[i] is GUITextBox tb) if (uiElements[i] is GUITextBox tb)
@@ -25,14 +25,14 @@ namespace Barotrauma.Items.Components
public static Color editorHighlightColor = Color.Yellow; public static Color editorHighlightColor = Color.Yellow;
public static Color editorSelectedColor = Color.Red; public static Color editorSelectedColor = Color.Red;
partial class WireSection public partial class WireSection
{ {
public VertexPositionColorTexture[] vertices; public VertexPositionColorTexture[] vertices;
public VertexPositionColorTexture[] shiftedVertices; public VertexPositionColorTexture[] shiftedVertices;
private float cachedWidth = 0f; private float cachedWidth = 0f;
private void RecalculateVertices(Wire wire, float width) private void RecalculateVertices(Sprite wireSprite, float width)
{ {
if (MathUtils.NearlyEqual(cachedWidth, width)) { return; } if (MathUtils.NearlyEqual(cachedWidth, width)) { return; }
cachedWidth = width; cachedWidth = width;
@@ -45,13 +45,13 @@ namespace Barotrauma.Items.Components
expandDir.X = -expandDir.Y; expandDir.X = -expandDir.Y;
expandDir.Y = -temp; expandDir.Y = -temp;
Rectangle srcRect = wire.wireSprite.SourceRect; Rectangle srcRect = wireSprite.SourceRect;
expandDir *= width * srcRect.Height * 0.5f; expandDir *= width * srcRect.Height * 0.5f;
Vector2 rectLocation = srcRect.Location.ToVector2(); Vector2 rectLocation = srcRect.Location.ToVector2();
Vector2 rectSize = srcRect.Size.ToVector2(); Vector2 rectSize = srcRect.Size.ToVector2();
Vector2 textureSize = new Vector2(wire.wireSprite.Texture.Width, wire.wireSprite.Texture.Height); Vector2 textureSize = new Vector2(wireSprite.Texture.Width, wireSprite.Texture.Height);
Vector2 topLeftUv = rectLocation / textureSize; Vector2 topLeftUv = rectLocation / textureSize;
Vector2 bottomRightUv = (rectLocation + rectSize) / textureSize; Vector2 bottomRightUv = (rectLocation + rectSize) / textureSize;
@@ -67,10 +67,10 @@ namespace Barotrauma.Items.Components
shiftedVertices = (VertexPositionColorTexture[])vertices.Clone(); shiftedVertices = (VertexPositionColorTexture[])vertices.Clone();
} }
public void Draw(SpriteBatch spriteBatch, Wire wire, Color color, Vector2 offset, float depth, float width = 0.3f) public void Draw(ISpriteBatch spriteBatch, Sprite wireSprite, Color color, Vector2 offset, float depth, float width = 0.3f)
{ {
if (width <= 0f) { return; } if (width <= 0f) { return; }
RecalculateVertices(wire, width); RecalculateVertices(wireSprite, width);
for (int i = 0; i < vertices.Length; i++) for (int i = 0; i < vertices.Length; i++)
{ {
@@ -79,21 +79,22 @@ namespace Barotrauma.Items.Components
shiftedVertices[i].Position.X += offset.X; shiftedVertices[i].Position.X += offset.X;
shiftedVertices[i].Position.Y -= offset.Y; shiftedVertices[i].Position.Y -= offset.Y;
} }
spriteBatch.Draw(wire.wireSprite.Texture, spriteBatch.Draw(
wireSprite.Texture,
shiftedVertices, shiftedVertices,
depth); depth);
} }
public static void Draw(SpriteBatch spriteBatch, Wire wire, Vector2 start, Vector2 end, Color color, float depth, float width = 0.3f) public static void Draw(ISpriteBatch spriteBatch, Sprite wireSprite, Vector2 start, Vector2 end, Color color, float depth, float width = 0.3f)
{ {
start.Y = -start.Y; start.Y = -start.Y;
end.Y = -end.Y; end.Y = -end.Y;
spriteBatch.Draw(wire.wireSprite.Texture, spriteBatch.Draw(wireSprite.Texture,
start, wire.wireSprite.SourceRect, color, start, wireSprite.SourceRect, color,
MathUtils.VectorToAngle(end - start), MathUtils.VectorToAngle(end - start),
new Vector2(0.0f, wire.wireSprite.size.Y / 2.0f), new Vector2(0.0f, wireSprite.size.Y / 2.0f),
new Vector2((Vector2.Distance(start, end)) / wire.wireSprite.size.X, width), new Vector2((Vector2.Distance(start, end)) / wireSprite.size.X, width),
SpriteEffects.None, SpriteEffects.None,
depth); depth);
} }
@@ -123,7 +124,7 @@ namespace Barotrauma.Items.Components
get => draggingWire; get => draggingWire;
} }
partial void InitProjSpecific(ContentXElement element) public static Sprite ExtractWireSprite(ContentXElement element)
{ {
if (defaultWireSprite == null) if (defaultWireSprite == null)
{ {
@@ -133,6 +134,7 @@ namespace Barotrauma.Items.Components
}; };
} }
Sprite overrideSprite = null;
foreach (var subElement in element.Elements()) foreach (var subElement in element.Elements())
{ {
if (subElement.Name.ToString().Equals("wiresprite", StringComparison.OrdinalIgnoreCase)) if (subElement.Name.ToString().Equals("wiresprite", StringComparison.OrdinalIgnoreCase))
@@ -142,9 +144,14 @@ namespace Barotrauma.Items.Components
} }
} }
wireSprite = overrideSprite ?? defaultWireSprite; return overrideSprite ?? defaultWireSprite;
} }
partial void InitProjSpecific(ContentXElement element)
{
wireSprite = ExtractWireSprite(element);
if (wireSprite != defaultWireSprite) { overrideSprite = wireSprite; }
}
public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1) public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1)
{ {
@@ -181,20 +188,20 @@ namespace Barotrauma.Items.Components
{ {
foreach (WireSection section in sections) foreach (WireSection section in sections)
{ {
section.Draw(spriteBatch, this, Screen.Selected == GameMain.GameScreen ? higlightColor : editorHighlightColor, drawOffset, depth + 0.00001f, Width * 2.0f); section.Draw(spriteBatch, wireSprite, Screen.Selected == GameMain.GameScreen ? higlightColor : editorHighlightColor, drawOffset, depth + 0.00001f, Width * 2.0f);
} }
} }
else if (item.IsSelected) else if (item.IsSelected)
{ {
foreach (WireSection section in sections) foreach (WireSection section in sections)
{ {
section.Draw(spriteBatch, this, editorSelectedColor, drawOffset, depth + 0.00001f, Width * 2.0f); section.Draw(spriteBatch, wireSprite, editorSelectedColor, drawOffset, depth + 0.00001f, Width * 2.0f);
} }
} }
foreach (WireSection section in sections) foreach (WireSection section in sections)
{ {
section.Draw(spriteBatch, this, item.Color, drawOffset, depth, Width); section.Draw(spriteBatch, wireSprite, item.Color, drawOffset, depth, Width);
} }
if (nodes.Count > 0) if (nodes.Count > 0)
@@ -239,13 +246,13 @@ namespace Barotrauma.Items.Components
} }
WireSection.Draw( WireSection.Draw(
spriteBatch, this, spriteBatch, wireSprite,
new Vector2(nodes[nodes.Count - 1].X, nodes[nodes.Count - 1].Y) + drawOffset, nodes[^1] + drawOffset,
new Vector2(newNodePos.X, newNodePos.Y) + drawOffset, new Vector2(newNodePos.X, newNodePos.Y) + drawOffset,
item.Color, 0.0f, Width); item.Color, 0.0f, Width);
WireSection.Draw( WireSection.Draw(
spriteBatch, this, spriteBatch, wireSprite,
new Vector2(newNodePos.X, newNodePos.Y) + drawOffset, new Vector2(newNodePos.X, newNodePos.Y) + drawOffset,
item.DrawPosition, item.DrawPosition,
item.Color, itemDepth, Width); item.Color, itemDepth, Width);
@@ -255,8 +262,8 @@ namespace Barotrauma.Items.Components
else else
{ {
WireSection.Draw( WireSection.Draw(
spriteBatch, this, spriteBatch, wireSprite,
new Vector2(nodes[nodes.Count - 1].X, nodes[nodes.Count - 1].Y) + drawOffset, nodes[^1] + drawOffset,
item.DrawPosition, item.DrawPosition,
item.Color, 0.0f, Width); item.Color, 0.0f, Width);
} }
@@ -294,12 +301,12 @@ namespace Barotrauma.Items.Components
Vector2 endPos = start + new Vector2((float)Math.Sin(angle), -(float)Math.Cos(angle)) * 50.0f; Vector2 endPos = start + new Vector2((float)Math.Sin(angle), -(float)Math.Cos(angle)) * 50.0f;
WireSection.Draw( WireSection.Draw(
spriteBatch, this, spriteBatch, wireSprite,
start, endPos, start, endPos,
GUIStyle.Orange, depth + 0.00001f, 0.2f); GUIStyle.Orange, depth + 0.00001f, 0.2f);
WireSection.Draw( WireSection.Draw(
spriteBatch, this, spriteBatch, wireSprite,
start, start + (endPos - start) * 0.7f, start, start + (endPos - start) * 0.7f,
item.Color, depth, 0.3f); item.Color, depth, 0.3f);
} }
@@ -302,7 +302,18 @@ namespace Barotrauma.Items.Components
Dictionary<AfflictionPrefab, float> combinedAfflictionStrengths = new Dictionary<AfflictionPrefab, float>(); Dictionary<AfflictionPrefab, float> combinedAfflictionStrengths = new Dictionary<AfflictionPrefab, float>();
foreach (Affliction affliction in allAfflictions) foreach (Affliction affliction in allAfflictions)
{ {
if (affliction.Strength < affliction.Prefab.ShowInHealthScannerThreshold || affliction.Strength <= 0.0f) { continue; } if (affliction.Strength <= 0f) { continue; }
if (affliction.Strength < affliction.Prefab.ShowInHealthScannerThreshold)
{
if (target.IsHuman || target.IsOnPlayerTeam || (affliction.Prefab.AfflictionType != AfflictionPrefab.PoisonType && affliction.Prefab.AfflictionType != AfflictionPrefab.ParalysisType))
{
// Always show the poisons on monsters, because poisoning bigger monsters require multiple doses.
// The solution is hacky, but didn't want to introduce an extra property for this.
// We also want to have a relatively high thershold for showing the poisons on the scanner on humans, so that it's not instantly clear that a target is poisoned and especially not which poison was used.
// Paralysis is treated like a poison but isn't technically a poison, so that we can have multiple afflictions that still are treated the same.
continue;
}
}
if (combinedAfflictionStrengths.ContainsKey(affliction.Prefab)) if (combinedAfflictionStrengths.ContainsKey(affliction.Prefab))
{ {
combinedAfflictionStrengths[affliction.Prefab] += affliction.Strength; combinedAfflictionStrengths[affliction.Prefab] += affliction.Strength;
@@ -334,14 +334,8 @@ namespace Barotrauma.Items.Components
crosshairPointerPos = PlayerInput.MousePosition; crosshairPointerPos = PlayerInput.MousePosition;
} }
public void Draw(SpriteBatch spriteBatch, bool editing = false, float itemDepth = -1) public Vector2 GetRecoilOffset()
{ {
if (!MathUtils.NearlyEqual(item.Rotation, prevBaseRotation) || !MathUtils.NearlyEqual(item.Scale, prevScale))
{
UpdateTransformedBarrelPos();
}
Vector2 drawPos = GetDrawPos();
float recoilOffset = 0.0f; float recoilOffset = 0.0f;
if (Math.Abs(RecoilDistance) > 0.0f && recoilTimer > 0.0f) if (Math.Abs(RecoilDistance) > 0.0f && recoilTimer > 0.0f)
{ {
@@ -362,6 +356,17 @@ namespace Barotrauma.Items.Components
recoilOffset = RecoilDistance; recoilOffset = RecoilDistance;
} }
} }
return new Vector2((float)Math.Cos(rotation), (float)Math.Sin(rotation)) * recoilOffset;
}
public void Draw(SpriteBatch spriteBatch, bool editing = false, float itemDepth = -1)
{
if (!MathUtils.NearlyEqual(item.Rotation, prevBaseRotation) || !MathUtils.NearlyEqual(item.Scale, prevScale))
{
UpdateTransformedBarrelPos();
}
Vector2 drawPos = GetDrawPos();
railSprite?.Draw(spriteBatch, railSprite?.Draw(spriteBatch,
drawPos, drawPos,
@@ -370,7 +375,7 @@ namespace Barotrauma.Items.Components
SpriteEffects.None, item.SpriteDepth + (railSprite.Depth - item.Sprite.Depth)); SpriteEffects.None, item.SpriteDepth + (railSprite.Depth - item.Sprite.Depth));
barrelSprite?.Draw(spriteBatch, barrelSprite?.Draw(spriteBatch,
drawPos - new Vector2((float)Math.Cos(rotation), (float)Math.Sin(rotation)) * recoilOffset * item.Scale, drawPos - GetRecoilOffset() * item.Scale,
item.SpriteColor, item.SpriteColor,
rotation + MathHelper.PiOver2, item.Scale, rotation + MathHelper.PiOver2, item.Scale,
SpriteEffects.None, item.SpriteDepth + (barrelSprite.Depth - item.Sprite.Depth)); SpriteEffects.None, item.SpriteDepth + (barrelSprite.Depth - item.Sprite.Depth));
@@ -9,7 +9,7 @@ namespace Barotrauma.Items.Components
{ {
private static void GetDamageModifierText(ref LocalizedString description, DamageModifier damageModifier, Identifier afflictionIdentifier) private static void GetDamageModifierText(ref LocalizedString description, DamageModifier damageModifier, Identifier afflictionIdentifier)
{ {
int roundedValue = (int)Math.Round((1 - damageModifier.DamageMultiplier * damageModifier.ProbabilityMultiplier) * 100); int roundedValue = (int)Math.Round((1 - Math.Min(damageModifier.DamageMultiplier, damageModifier.ProbabilityMultiplier)) * 100);
if (roundedValue == 0) { return; } if (roundedValue == 0) { return; }
string colorStr = XMLExtensions.ToStringHex(GUIStyle.Green); string colorStr = XMLExtensions.ToStringHex(GUIStyle.Green);
@@ -18,7 +18,7 @@ namespace Barotrauma.Items.Components
TextManager.Get($"afflictiontype.{afflictionIdentifier}").Fallback(afflictionIdentifier.Value); TextManager.Get($"afflictiontype.{afflictionIdentifier}").Fallback(afflictionIdentifier.Value);
if (!description.IsNullOrWhiteSpace()) { description += '\n'; } if (!description.IsNullOrWhiteSpace()) { description += '\n'; }
description += $" ‖color:{colorStr}‖{roundedValue.ToString("-0;+#")}%‖color:end‖ {afflictionName}"; description += $" ‖color:{colorStr}‖{roundedValue:-0;+#}%‖color:end‖ {afflictionName}";
} }
public override void AddTooltipInfo(ref LocalizedString name, ref LocalizedString description) public override void AddTooltipInfo(ref LocalizedString name, ref LocalizedString description)
@@ -36,7 +36,6 @@ namespace Barotrauma.Items.Components
{ {
continue; continue;
} }
foreach (Identifier afflictionIdentifier in damageModifier.ParsedAfflictionIdentifiers) foreach (Identifier afflictionIdentifier in damageModifier.ParsedAfflictionIdentifiers)
{ {
GetDamageModifierText(ref description, damageModifier, afflictionIdentifier); GetDamageModifierText(ref description, damageModifier, afflictionIdentifier);
@@ -53,11 +53,11 @@ namespace Barotrauma
get get
{ {
// Returns a point off-screen, Rectangle.Empty places buttons in the top left of the screen // Returns a point off-screen, Rectangle.Empty places buttons in the top left of the screen
if (IsMoving) return offScreenRect; if (IsMoving) { return offScreenRect; }
int buttonDir = Math.Sign(SubInventoryDir); int buttonDir = Math.Sign(SubInventoryDir);
float sizeY = Inventory.UnequippedIndicator.size.Y * Inventory.UIScale * Inventory.IndicatorScaleAdjustment; float sizeY = Inventory.UnequippedIndicator.size.Y * Inventory.UIScale;
Vector2 equipIndicatorPos = new Vector2(Rect.Left, Rect.Center.Y + (Rect.Height / 2 + 15 * Inventory.UIScale) * buttonDir - sizeY / 2f); Vector2 equipIndicatorPos = new Vector2(Rect.Left, Rect.Center.Y + (Rect.Height / 2 + 15 * Inventory.UIScale) * buttonDir - sizeY / 2f);
equipIndicatorPos += DrawOffset; equipIndicatorPos += DrawOffset;
@@ -176,14 +176,6 @@ namespace Barotrauma
public static Sprite DraggableIndicator; public static Sprite DraggableIndicator;
public static Sprite UnequippedIndicator, UnequippedHoverIndicator, UnequippedClickedIndicator, EquippedIndicator, EquippedHoverIndicator, EquippedClickedIndicator; public static Sprite UnequippedIndicator, UnequippedHoverIndicator, UnequippedClickedIndicator, EquippedIndicator, EquippedHoverIndicator, EquippedClickedIndicator;
public static float IndicatorScaleAdjustment
{
get
{
return !GUI.IsFourByThree() ? 0.8f : 0.7f;
}
}
public static Inventory DraggingInventory; public static Inventory DraggingInventory;
public Inventory ReplacedBy; public Inventory ReplacedBy;
@@ -249,11 +241,11 @@ namespace Barotrauma
{ {
itemsInSlot = ParentInventory.GetItemsAt(SlotIndex); itemsInSlot = ParentInventory.GetItemsAt(SlotIndex);
} }
Tooltip = GetTooltip(Item, itemsInSlot); Tooltip = GetTooltip(Item, itemsInSlot, Character.Controlled);
tooltipDisplayedCondition = (int)Item.ConditionPercentage; tooltipDisplayedCondition = (int)Item.ConditionPercentage;
} }
private RichString GetTooltip(Item item, IEnumerable<Item> itemsInSlot) private static RichString GetTooltip(Item item, IEnumerable<Item> itemsInSlot, Character character)
{ {
if (item == null) { return null; } if (item == null) { return null; }
@@ -348,10 +340,12 @@ namespace Barotrauma
} }
if (itemsInSlot.Count() > 1) if (itemsInSlot.Count() > 1)
{ {
string colorStr = XMLExtensions.ColorToString(GUIStyle.Blue); toolTip += $"\n‖color:gui.blue‖[{GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.TakeOneFromInventorySlot)}] {TextManager.Get("inputtype.takeonefrominventoryslot")}‖color:end‖";
toolTip += $"\n‖color:{colorStr}‖[{GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.TakeOneFromInventorySlot)}] {TextManager.Get("inputtype.takeonefrominventoryslot")}‖color:end‖"; toolTip += $"\n‖color:gui.blue‖[{GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.TakeHalfFromInventorySlot)}] {TextManager.Get("inputtype.takehalffrominventoryslot")}‖color:end‖";
colorStr = XMLExtensions.ColorToString(GUIStyle.Blue); }
toolTip += $"\n‖color:{colorStr}‖[{GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.TakeHalfFromInventorySlot)}] {TextManager.Get("inputtype.takehalffrominventoryslot")}‖color:end‖"; if (item.Prefab.SkillRequirementHints != null && item.Prefab.SkillRequirementHints.Any())
{
toolTip += item.Prefab.GetSkillRequirementHints(character);
} }
return RichString.Rich(toolTip); return RichString.Rich(toolTip);
} }
@@ -576,7 +570,7 @@ namespace Barotrauma
} }
} }
if (PlayerInput.LeftButtonHeld() && PlayerInput.RightButtonHeld()) if (PlayerInput.PrimaryMouseButtonHeld() && PlayerInput.SecondaryMouseButtonHeld())
{ {
mouseOn = false; mouseOn = false;
} }
@@ -727,14 +721,7 @@ namespace Barotrauma
Rectangle subRect = slot.Rect; Rectangle subRect = slot.Rect;
Vector2 spacing; Vector2 spacing;
if (GUI.IsFourByThree()) spacing = new Vector2(10 * UIScale, (10 + UnequippedIndicator.size.Y) * UIScale * GUI.AspectRatioAdjustment);
{
spacing = new Vector2(5 * UIScale, (5 + UnequippedIndicator.size.Y) * UIScale);
}
else
{
spacing = new Vector2(10 * UIScale, (10 + UnequippedIndicator.size.Y) * UIScale);
}
int columns = MathHelper.Clamp((int)Math.Floor(Math.Sqrt(itemCapacity)), 1, container.SlotsPerRow); int columns = MathHelper.Clamp((int)Math.Floor(Math.Sqrt(itemCapacity)), 1, container.SlotsPerRow);
while (itemCapacity / columns * (subRect.Height + spacing.Y) > GameMain.GraphicsHeight * 0.5f) while (itemCapacity / columns * (subRect.Height + spacing.Y) > GameMain.GraphicsHeight * 0.5f)
@@ -1535,16 +1522,6 @@ namespace Barotrauma
{ {
Sprite slotSprite = slot.SlotSprite ?? SlotSpriteSmall; Sprite slotSprite = slot.SlotSprite ?? SlotSpriteSmall;
/*if (inventory != null && (CharacterInventory.PersonalSlots.HasFlag(type) || (inventory.isSubInventory && (inventory.Owner as Item) != null
&& (inventory.Owner as Item).AllowedSlots.Any(a => CharacterInventory.PersonalSlots.HasFlag(a)))))
{
slotColor = slot.IsHighlighted ? GUIStyle.EquipmentSlotColor : GUIStyle.EquipmentSlotColor * 0.8f;
}
else
{
slotColor = slot.IsHighlighted ? GUIStyle.InventorySlotColor : GUIStyle.InventorySlotColor * 0.8f;
}*/
if (inventory != null && inventory.Locked) { slotColor = Color.Gray * 0.5f; } if (inventory != null && inventory.Locked) { slotColor = Color.Gray * 0.5f; }
spriteBatch.Draw(slotSprite.Texture, rect, slotSprite.SourceRect, slotColor); spriteBatch.Draw(slotSprite.Texture, rect, slotSprite.SourceRect, slotColor);
@@ -1713,6 +1690,15 @@ namespace Barotrauma
GUIStyle.SmallFont.DrawString(spriteBatch, stackCountText, stackCountPos, Color.White); GUIStyle.SmallFont.DrawString(spriteBatch, stackCountText, stackCountPos, Color.White);
} }
} }
if (HealingCooldown.IsOnCooldown && item.HasTag(HealingCooldown.MedicalItemTag))
{
RectangleF cdRect = rect;
// shrink the rect from top to bottom depending on HealingCooldown.NormalizedCooldown
cdRect.Height *= HealingCooldown.NormalizedCooldown;
cdRect.Y += rect.Height;
GUI.DrawFilledRectangle(spriteBatch, cdRect, Color.White * 0.5f);
}
} }
if (inventory != null && if (inventory != null &&
@@ -1722,7 +1708,17 @@ namespace Barotrauma
slot.InventoryKeyIndex < GameSettings.CurrentConfig.InventoryKeyMap.Bindings.Length) slot.InventoryKeyIndex < GameSettings.CurrentConfig.InventoryKeyMap.Bindings.Length)
{ {
spriteBatch.Draw(slotHotkeySprite.Texture, rect.ScaleSize(1.15f), slotHotkeySprite.SourceRect, slotColor); spriteBatch.Draw(slotHotkeySprite.Texture, rect.ScaleSize(1.15f), slotHotkeySprite.SourceRect, slotColor);
GUI.DrawString(spriteBatch, rect.Location.ToVector2() + new Vector2((int)(4.25f * UIScale), (int)Math.Ceiling(-1.5f * UIScale)), GameSettings.CurrentConfig.InventoryKeyMap.Bindings[slot.InventoryKeyIndex].Name, Color.Black, font: GUIStyle.HotkeyFont);
GUIStyle.HotkeyFont.DrawString(
spriteBatch,
GameSettings.CurrentConfig.InventoryKeyMap.Bindings[slot.InventoryKeyIndex].Name,
rect.Location.ToVector2() + new Vector2((int)(4.25f * UIScale), (int)Math.Ceiling(-1.5f * UIScale)),
Color.Black,
rotation: 0.0f,
origin: Vector2.Zero,
scale: Vector2.One * GUI.AspectRatioAdjustment,
SpriteEffects.None,
layerDepth: 0.0f);
} }
} }
@@ -23,7 +23,21 @@ namespace Barotrauma
private readonly List<SerializableEntityEditor> activeEditors = new List<SerializableEntityEditor>(); private readonly List<SerializableEntityEditor> activeEditors = new List<SerializableEntityEditor>();
public GUIComponentStyle IconStyle { get; private set; } = null;
private GUIComponentStyle iconStyle;
public GUIComponentStyle IconStyle
{
get { return iconStyle; }
private set
{
if (IconStyle != value)
{
iconStyle = value;
CheckIsHighlighted();
}
}
}
partial void AssignCampaignInteractionTypeProjSpecific(CampaignMode.InteractionType interactionType) partial void AssignCampaignInteractionTypeProjSpecific(CampaignMode.InteractionType interactionType)
{ {
if (interactionType == CampaignMode.InteractionType.None) if (interactionType == CampaignMode.InteractionType.None)
@@ -143,6 +157,18 @@ namespace Barotrauma
return color; return color;
} }
protected override void CheckIsHighlighted()
{
if (IsHighlighted || ExternalHighlight || IconStyle != null)
{
highlightedEntities.Add(this);
}
else
{
highlightedEntities.Remove(this);
}
}
public Color GetInventoryIconColor() public Color GetInventoryIconColor()
{ {
Color color = InventoryIconColor; Color color = InventoryIconColor;
@@ -203,7 +229,7 @@ namespace Barotrauma
} }
} }
partial void InitProjSpecific() public void InitSpriteStates()
{ {
Prefab.Sprite?.EnsureLazyLoaded(); Prefab.Sprite?.EnsureLazyLoaded();
Prefab.InventoryIcon?.EnsureLazyLoaded(); Prefab.InventoryIcon?.EnsureLazyLoaded();
@@ -211,7 +237,6 @@ namespace Barotrauma
{ {
brokenSprite.Sprite.EnsureLazyLoaded(); brokenSprite.Sprite.EnsureLazyLoaded();
} }
foreach (var decorativeSprite in Prefab.DecorativeSprites) foreach (var decorativeSprite in Prefab.DecorativeSprites)
{ {
decorativeSprite.Sprite.EnsureLazyLoaded(); decorativeSprite.Sprite.EnsureLazyLoaded();
@@ -221,6 +246,11 @@ namespace Barotrauma
UpdateSpriteStates(0.0f); UpdateSpriteStates(0.0f);
} }
partial void InitProjSpecific()
{
InitSpriteStates();
}
private Rectangle? cachedVisibleExtents; private Rectangle? cachedVisibleExtents;
public void ResetCachedVisibleSize() public void ResetCachedVisibleSize()
@@ -729,7 +759,7 @@ namespace Barotrauma
if (!lClick && !rClick) { return; } if (!lClick && !rClick) { return; }
Vector2 position = cam.ScreenToWorld(PlayerInput.MousePosition); Vector2 position = cam.ScreenToWorld(PlayerInput.MousePosition);
var otherEntity = mapEntityList.FirstOrDefault(e => e != this && e.IsHighlighted && e.IsMouseOn(position)); var otherEntity = highlightedEntities.FirstOrDefault(e => e != this && e.IsMouseOn(position));
if (otherEntity != null) if (otherEntity != null)
{ {
if (linkedTo.Contains(otherEntity)) if (linkedTo.Contains(otherEntity))
@@ -1414,7 +1444,7 @@ namespace Barotrauma
if (targetComponent == null) if (targetComponent == null)
{ {
ApplyStatusEffects(actionType, 1.0f, targetCharacter, targetLimb, useTarget, true, worldPosition: worldPosition); ApplyStatusEffects(actionType, 1.0f, targetCharacter, targetLimb, useTarget, isNetworkEvent: true, worldPosition: worldPosition);
} }
else else
{ {
@@ -236,6 +236,16 @@ namespace Barotrauma
DecorativeSprites = decorativeSprites.ToImmutableArray(); DecorativeSprites = decorativeSprites.ToImmutableArray();
ContainedSprites = containedSprites.ToImmutableArray(); ContainedSprites = containedSprites.ToImmutableArray();
DecorativeSpriteGroups = decorativeSpriteGroups.Select(kvp => (kvp.Key, kvp.Value.ToImmutableArray())).ToImmutableDictionary(); DecorativeSpriteGroups = decorativeSpriteGroups.Select(kvp => (kvp.Key, kvp.Value.ToImmutableArray())).ToImmutableDictionary();
#if CLIENT
foreach (Item item in Item.ItemList)
{
if (item.Prefab == this)
{
item.InitSpriteStates();
}
}
#endif
} }
public bool CanCharacterBuy() public bool CanCharacterBuy()
@@ -244,7 +254,7 @@ namespace Barotrauma
if (!DefaultPrice.RequiresUnlock) { return true; } if (!DefaultPrice.RequiresUnlock) { return true; }
return Character.Controlled is not null && Character.Controlled.HasStoreAccessForItem(this); return Character.Controlled is not null && Character.Controlled.HasStoreAccessForItem(this);
} }
public LocalizedString GetTooltip() public LocalizedString GetTooltip(Character character)
{ {
LocalizedString tooltip = $"‖color:{XMLExtensions.ToStringHex(GUIStyle.TextColorBright)}‖{Name}‖color:end‖"; LocalizedString tooltip = $"‖color:{XMLExtensions.ToStringHex(GUIStyle.TextColorBright)}‖{Name}‖color:end‖";
if (!Description.IsNullOrEmpty()) if (!Description.IsNullOrEmpty())
@@ -255,6 +265,10 @@ namespace Barotrauma
{ {
Wearable.AddTooltipInfo(wearableDamageModifiers, wearableSkillModifiers, ref tooltip); Wearable.AddTooltipInfo(wearableDamageModifiers, wearableSkillModifiers, ref tooltip);
} }
if (SkillRequirementHints != null && SkillRequirementHints.Any())
{
tooltip += GetSkillRequirementHints(character);
}
return tooltip; return tooltip;
} }
@@ -366,5 +380,31 @@ namespace Barotrauma
Sprite.DrawTiled(spriteBatch, new Vector2(placeRect.X, -placeRect.Y), placeRect.Size.ToVector2(), SpriteColor * 0.8f); Sprite.DrawTiled(spriteBatch, new Vector2(placeRect.X, -placeRect.Y), placeRect.Size.ToVector2(), SpriteColor * 0.8f);
} }
} }
public LocalizedString GetSkillRequirementHints(Character character)
{
LocalizedString text = "";
if (SkillRequirementHints != null && SkillRequirementHints.Any() && character != null)
{
Color orange = GUIStyle.Orange;
// Reuse an existing, localized, text because it's what we want here: "Required skills:"
text = "\n\n" + $"‖color:{orange.ToStringHex()}‖{TextManager.Get("requiredrepairskills")}‖color:end‖";
foreach (var hint in SkillRequirementHints)
{
int skillLevel = (int)character.GetSkillLevel(hint.Skill);
Color levelColor = GUIStyle.Yellow;
if (skillLevel >= hint.Level)
{
levelColor = GUIStyle.Green;
}
else if (skillLevel < hint.Level / 2)
{
levelColor = GUIStyle.Red;
}
text += "\n" + hint.GetFormattedText(skillLevel, levelColor.ToStringHex());
}
}
return text;
}
} }
} }
@@ -129,20 +129,15 @@ namespace Barotrauma
Vector2 position = cam.ScreenToWorld(PlayerInput.MousePosition); Vector2 position = cam.ScreenToWorld(PlayerInput.MousePosition);
foreach (MapEntity entity in mapEntityList) foreach (MapEntity entity in HighlightedEntities)
{ {
if (entity == this || !entity.IsHighlighted) { continue; } if (entity == this) { continue; }
if (!entity.IsMouseOn(position)) { continue; } if (!entity.IsMouseOn(position)) { continue; }
if (entity.linkedTo == null || !entity.Linkable) { continue; } if (entity.linkedTo == null || !entity.Linkable) { continue; }
if (entity.linkedTo.Contains(this) || linkedTo.Contains(entity) || rClick) if (entity.linkedTo.Contains(this) || linkedTo.Contains(entity) || rClick)
{ {
if (entity == this || !entity.IsHighlighted) { continue; } entity.linkedTo.Remove(this);
if (!entity.IsMouseOn(position)) { continue; } linkedTo.Remove(entity);
if (entity.linkedTo.Contains(this))
{
entity.linkedTo.Remove(this);
linkedTo.Remove(entity);
}
} }
else else
{ {
@@ -1,5 +1,4 @@
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@@ -65,15 +64,9 @@ namespace Barotrauma
public Texture2D WaterTexture { get; } public Texture2D WaterTexture { get; }
public WaterRenderer(GraphicsDevice graphicsDevice, ContentManager content) public WaterRenderer(GraphicsDevice graphicsDevice)
{ {
#if WINDOWS WaterEffect = EffectLoader.Load("Effects/watershader");
WaterEffect = content.Load<Effect>("Effects/watershader");
#endif
#if LINUX || OSX
WaterEffect = content.Load<Effect>("Effects/watershader_opengl");
#endif
WaterTexture = TextureLoader.FromFile("Content/Effects/waterbump.png"); WaterTexture = TextureLoader.FromFile("Content/Effects/waterbump.png");
WaterEffect.Parameters["xWaterBumpMap"].SetValue(WaterTexture); WaterEffect.Parameters["xWaterBumpMap"].SetValue(WaterTexture);
@@ -2,7 +2,6 @@
using Barotrauma.Items.Components; using Barotrauma.Items.Components;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using SharpFont;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
@@ -12,28 +11,14 @@ namespace Barotrauma.Lights
{ {
class ConvexHullList class ConvexHullList
{ {
private List<ConvexHull> list;
public HashSet<ConvexHull> IsHidden;
public readonly Submarine Submarine; public readonly Submarine Submarine;
public List<ConvexHull> List public HashSet<ConvexHull> IsHidden = new HashSet<ConvexHull>();
{ public readonly List<ConvexHull> List = new List<ConvexHull>();
get { return list; }
set
{
Debug.Assert(value != null);
Debug.Assert(!list.Contains(null));
list = value;
IsHidden.RemoveWhere(ch => !list.Contains(ch));
}
}
public ConvexHullList(Submarine submarine) public ConvexHullList(Submarine submarine)
{ {
Submarine = submarine; Submarine = submarine;
list = new List<ConvexHull>();
IsHidden = new HashSet<ConvexHull>();
} }
} }
@@ -354,7 +339,7 @@ namespace Barotrauma.Lights
} }
} }
public bool IsSegmentAInB(Segment a, Segment b) public static bool IsSegmentAInB(Segment a, Segment b)
{ {
if (Vector2.DistanceSquared(a.Start.Pos, a.End.Pos) > Vector2.DistanceSquared(b.Start.Pos, b.End.Pos)) if (Vector2.DistanceSquared(a.Start.Pos, a.End.Pos) > Vector2.DistanceSquared(b.Start.Pos, b.End.Pos))
{ {
@@ -362,15 +347,16 @@ namespace Barotrauma.Lights
} }
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 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; 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.X < min.X) { return false; }
if (a.Start.Pos.Y < min.Y) { return false; } if (a.Start.Pos.Y < min.Y) { return false; }
if (a.End.Pos.X < min.X) { return false; } if (a.End.Pos.X < min.X) { return false; }
if (a.End.Pos.Y < min.Y) { return false; } if (a.End.Pos.Y < min.Y) { return false; }
Vector2 max = new Vector2(Math.Max(b.Start.Pos.X, b.End.Pos.X), Math.Max(b.Start.Pos.Y, b.End.Pos.Y));
max.X += 1.0f; max.Y += 1.0f;
if (a.Start.Pos.X > max.X) { return false; } if (a.Start.Pos.X > max.X) { return false; }
if (a.Start.Pos.Y > max.Y) { return false; } if (a.Start.Pos.Y > max.Y) { return false; }
if (a.End.Pos.X > max.X) { return false; } if (a.End.Pos.X > max.X) { return false; }
@@ -628,7 +614,7 @@ namespace Barotrauma.Lights
{ {
for (int i = 0; i < 4; i++) for (int i = 0; i < 4; i++)
{ {
if (ignoreEdge[i] && ignoreEdges) continue; if (ignoreEdge[i] && ignoreEdges) { continue; }
Vector2 pos1 = vertices[i].WorldPos; Vector2 pos1 = vertices[i].WorldPos;
Vector2 pos2 = vertices[(i + 1) % 4].WorldPos; Vector2 pos2 = vertices[(i + 1) % 4].WorldPos;
@@ -1,6 +1,5 @@
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System; using System;
@@ -73,12 +72,14 @@ namespace Barotrauma.Lights
private int recalculationCount; private int recalculationCount;
private float time;
public IEnumerable<LightSource> Lights public IEnumerable<LightSource> Lights
{ {
get { return lights; } get { return lights; }
} }
public LightManager(GraphicsDevice graphics, ContentManager content) public LightManager(GraphicsDevice graphics)
{ {
lights = new List<LightSource>(100); lights = new List<LightSource>(100);
@@ -96,13 +97,8 @@ namespace Barotrauma.Lights
{ {
CreateRenderTargets(graphics); CreateRenderTargets(graphics);
#if WINDOWS LosEffect = EffectLoader.Load("Effects/losshader");
LosEffect = content.Load<Effect>("Effects/losshader"); SolidColorEffect = EffectLoader.Load("Effects/solidcolor");
SolidColorEffect = content.Load<Effect>("Effects/solidcolor");
#else
LosEffect = content.Load<Effect>("Effects/losshader_opengl");
SolidColorEffect = content.Load<Effect>("Effects/solidcolor_opengl");
#endif
if (lightEffect == null) if (lightEffect == null)
{ {
@@ -171,10 +167,12 @@ namespace Barotrauma.Lights
public void Update(float deltaTime) public void Update(float deltaTime)
{ {
//wrap around if the timer gets very large, otherwise we'd start running into floating point accuracy issues
time = (time + deltaTime) % 100000.0f;
foreach (LightSource light in activeLights) foreach (LightSource light in activeLights)
{ {
if (!light.Enabled) { continue; } if (!light.Enabled) { continue; }
light.Update(deltaTime); light.Update(time);
} }
} }
@@ -451,9 +449,9 @@ namespace Barotrauma.Lights
{ {
highlightedEntities.Add(Character.Controlled.FocusedCharacter); highlightedEntities.Add(Character.Controlled.FocusedCharacter);
} }
foreach (Item item in Item.ItemList) foreach (MapEntity me in MapEntity.HighlightedEntities)
{ {
if ((item.IsHighlighted || item.IconStyle != null) && !highlightedEntities.Contains(item)) if (me is Item item && item != Character.Controlled.FocusedItem)
{ {
highlightedEntities.Add(item); highlightedEntities.Add(item);
} }
@@ -200,12 +200,10 @@ namespace Barotrauma.Lights
private static Texture2D lightTexture; private static Texture2D lightTexture;
private float blinkTimer, flickerState, pulseState;
private VertexPositionColorTexture[] vertices; private VertexPositionColorTexture[] vertices;
private short[] indices; private short[] indices;
private readonly List<ConvexHullList> hullsInRange; private readonly List<ConvexHullList> convexHullsInRange;
public Texture2D texture; public Texture2D texture;
@@ -236,7 +234,7 @@ namespace Barotrauma.Lights
{ {
if (!needsRecalculation && value) if (!needsRecalculation && value)
{ {
foreach (ConvexHullList chList in hullsInRange) foreach (ConvexHullList chList in convexHullsInRange)
{ {
chList.IsHidden.Clear(); chList.IsHidden.Clear();
} }
@@ -476,7 +474,7 @@ namespace Barotrauma.Lights
public LightSource(Vector2 position, float range, Color color, Submarine submarine, bool addLight=true) public LightSource(Vector2 position, float range, Color color, Submarine submarine, bool addLight=true)
{ {
hullsInRange = new List<ConvexHullList>(); convexHullsInRange = new List<ConvexHullList>();
this.ParentSub = submarine; this.ParentSub = submarine;
this.position = position; this.position = position;
lightSourceParams = new LightSourceParams(range, color); lightSourceParams = new LightSourceParams(range, color);
@@ -486,12 +484,12 @@ namespace Barotrauma.Lights
if (addLight) { GameMain.LightManager.AddLight(this); } if (addLight) { GameMain.LightManager.AddLight(this); }
} }
public void Update(float deltaTime) public void Update(float time)
{ {
float brightness = 1.0f; float brightness = 1.0f;
if (lightSourceParams.BlinkFrequency > 0.0f) if (lightSourceParams.BlinkFrequency > 0.0f)
{ {
blinkTimer = (blinkTimer + deltaTime * lightSourceParams.BlinkFrequency) % 1.0f; float blinkTimer = (time * lightSourceParams.BlinkFrequency) % 1.0f;
if (blinkTimer > 0.5f) if (blinkTimer > 0.5f)
{ {
CurrentBrightness = 0.0f; CurrentBrightness = 0.0f;
@@ -500,14 +498,13 @@ namespace Barotrauma.Lights
} }
if (lightSourceParams.PulseFrequency > 0.0f && lightSourceParams.PulseAmount > 0.0f) if (lightSourceParams.PulseFrequency > 0.0f && lightSourceParams.PulseAmount > 0.0f)
{ {
pulseState = (pulseState + deltaTime * lightSourceParams.PulseFrequency) % 1.0f; float pulseState = (time * lightSourceParams.PulseFrequency) % 1.0f;
//oscillate between 0-1 //oscillate between 0-1
brightness *= 1.0f - (float)(Math.Sin(pulseState * MathHelper.TwoPi) + 1.0f) / 2.0f * lightSourceParams.PulseAmount; brightness *= 1.0f - (float)(Math.Sin(pulseState * MathHelper.TwoPi) + 1.0f) / 2.0f * lightSourceParams.PulseAmount;
} }
if (lightSourceParams.Flicker > 0.0f) if (lightSourceParams.Flicker > 0.0f && lightSourceParams.FlickerSpeed > 0.0f)
{ {
flickerState += deltaTime * lightSourceParams.FlickerSpeed; float flickerState = (time * lightSourceParams.FlickerSpeed) % 255;
flickerState %= 255;
brightness *= 1.0f - PerlinNoise.GetPerlin(flickerState, flickerState * 0.5f) * lightSourceParams.Flicker; brightness *= 1.0f - PerlinNoise.GetPerlin(flickerState, flickerState * 0.5f) * lightSourceParams.Flicker;
} }
CurrentBrightness = brightness; CurrentBrightness = brightness;
@@ -518,19 +515,25 @@ namespace Barotrauma.Lights
/// </summary> /// </summary>
private void RefreshConvexHullList(ConvexHullList chList, Vector2 lightPos, Submarine sub) private void RefreshConvexHullList(ConvexHullList chList, Vector2 lightPos, Submarine sub)
{ {
var fullChList = ConvexHull.HullLists.Find(x => x.Submarine == sub); var fullChList = ConvexHull.HullLists.FirstOrDefault(chList => chList.Submarine == sub);
if (fullChList == null) { return; } if (fullChList == null) { return; }
chList.List = fullChList.List.FindAll(ch => ch.Enabled && MathUtils.CircleIntersectsRectangle(lightPos, TextureRange, ch.BoundingBox)); chList.List.Clear();
foreach (var convexHull in fullChList.List)
NeedsHullCheck = true; {
if (!convexHull.Enabled) { continue; }
if (!MathUtils.CircleIntersectsRectangle(lightPos, TextureRange, convexHull.BoundingBox)) { continue; }
chList.List.Add(convexHull);
}
chList.IsHidden.RemoveWhere(ch => !chList.List.Contains(ch));
NeedsHullCheck = false;
} }
/// <summary> /// <summary>
/// Recheck which convex hulls are in range (if needed), /// Recheck which convex hulls are in range (if needed),
/// and check if we need to recalculate vertices due to changes in the convex hulls /// and check if we need to recalculate vertices due to changes in the convex hulls
/// </summary> /// </summary>
private void CheckHullsInRange() private void CheckConvexHullsInRange()
{ {
foreach (Submarine sub in Submarine.Loaded) foreach (Submarine sub in Submarine.Loaded)
{ {
@@ -543,21 +546,13 @@ namespace Barotrauma.Lights
private void CheckHullsInRange(Submarine sub) private void CheckHullsInRange(Submarine sub)
{ {
//find the list of convexhulls that belong to the sub //find the list of convexhulls that belong to the sub
ConvexHullList chList = null; ConvexHullList chList = convexHullsInRange.FirstOrDefault(chList => chList.Submarine == sub);
foreach (var ch in hullsInRange)
{
if (ch.Submarine == sub)
{
chList = ch;
break;
}
}
//not found -> create one //not found -> create one
if (chList == null) if (chList == null)
{ {
chList = new ConvexHullList(sub); chList = new ConvexHullList(sub);
hullsInRange.Add(chList); convexHullsInRange.Add(chList);
NeedsRecalculation = true; NeedsRecalculation = true;
} }
@@ -649,6 +644,10 @@ namespace Barotrauma.Lights
} }
} }
private static readonly List<Segment> visibleSegments = new List<Segment>();
private static readonly List<SegmentPoint> points = new List<SegmentPoint>();
private static readonly List<Vector2> output = new List<Vector2>();
private static readonly SegmentPoint[] boundaryCorners = new SegmentPoint[4];
private List<Vector2> FindRaycastHits() private List<Vector2> FindRaycastHits()
{ {
if (!CastShadows || Range < 1.0f || Color.A < 1) { return null; } if (!CastShadows || Range < 1.0f || Color.A < 1) { return null; }
@@ -656,12 +655,17 @@ namespace Barotrauma.Lights
Vector2 drawPos = position; Vector2 drawPos = position;
if (ParentSub != null) { drawPos += ParentSub.DrawPosition; } if (ParentSub != null) { drawPos += ParentSub.DrawPosition; }
var hulls = new List<ConvexHull>(); visibleSegments.Clear();
foreach (ConvexHullList chList in hullsInRange) foreach (ConvexHullList chList in convexHullsInRange)
{ {
foreach (ConvexHull hull in chList.List) foreach (ConvexHull hull in chList.List)
{ {
if (!chList.IsHidden.Contains(hull)) { hulls.Add(hull); } if (!chList.IsHidden.Contains(hull))
{
//find convexhull segments that are close enough and facing towards the light source
hull.RefreshWorldPositions();
hull.GetVisibleSegments(drawPos, visibleSegments, ignoreEdges: false);
}
} }
foreach (ConvexHull hull in chList.List) foreach (ConvexHull hull in chList.List)
{ {
@@ -669,23 +673,13 @@ 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);
}
//add a square-shaped boundary to make sure we've got something to construct the triangles from //add a square-shaped boundary to make sure we've got something to construct the triangles from
//even if there aren't enough hull segments around the light source //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) //(might be more effective to calculate if we actually need these extra points)
Vector2 drawOffset = Vector2.Zero; Vector2 drawOffset = Vector2.Zero;
float boundsExtended = bounds; float boundsExtended = TextureRange;
if (OverrideLightTexture != null) if (OverrideLightTexture != null)
{ {
float cosAngle = (float)Math.Cos(Rotation); float cosAngle = (float)Math.Cos(Rotation);
@@ -709,12 +703,12 @@ namespace Barotrauma.Lights
drawOffset.Y = origin.X * sinAngle + origin.Y * cosAngle; drawOffset.Y = origin.X * sinAngle + origin.Y * cosAngle;
} }
var boundaryCorners = new SegmentPoint[] { Vector2 boundsMin = drawPos + drawOffset + new Vector2(-boundsExtended, -boundsExtended);
new SegmentPoint(new Vector2(drawPos.X + drawOffset.X + boundsExtended, drawPos.Y + drawOffset.Y + boundsExtended), null), Vector2 boundsMax = drawPos + drawOffset + new Vector2(boundsExtended, boundsExtended);
new SegmentPoint(new Vector2(drawPos.X + drawOffset.X + boundsExtended, drawPos.Y + drawOffset.Y - boundsExtended), null), boundaryCorners[0] = new SegmentPoint(boundsMax, null);
new SegmentPoint(new Vector2(drawPos.X + drawOffset.X - boundsExtended, drawPos.Y + drawOffset.Y - boundsExtended), null), boundaryCorners[1] = new SegmentPoint(new Vector2(boundsMax.X, boundsMin.Y), null);
new SegmentPoint(new Vector2(drawPos.X + drawOffset.X - boundsExtended, drawPos.Y + drawOffset.Y + boundsExtended), null) boundaryCorners[2] = new SegmentPoint(boundsMin, null);
}; boundaryCorners[3] = new SegmentPoint(new Vector2(boundsMin.X, boundsMax.Y), null);
for (int i = 0; i < 4; i++) for (int i = 0; i < 4; i++)
{ {
@@ -798,6 +792,7 @@ namespace Barotrauma.Lights
} }
} }
points.Clear();
//remove segments that fall out of bounds //remove segments that fall out of bounds
for (int i = 0; i < visibleSegments.Count; i++) for (int i = 0; i < visibleSegments.Count; i++)
{ {
@@ -817,7 +812,18 @@ namespace Barotrauma.Lights
} }
} }
visibleSegments = visibleSegments.OrderBy(s => MathUtils.LineToPointDistanceSquared(s.Start.WorldPos, s.End.WorldPos, drawPos)).ToList(); //remove points that are very close to each other
for (int i = 0; i < points.Count; i++)
{
for (int j = Math.Min(i + 4, points.Count-1); j > i; j--)
{
if (Math.Abs(points[i].WorldPos.X - points[j].WorldPos.X) < 6 &&
Math.Abs(points[i].WorldPos.Y - points[j].WorldPos.Y) < 6)
{
points.RemoveAt(j);
}
}
}
var compareCCW = new CompareSegmentPointCW(drawPos); var compareCCW = new CompareSegmentPointCW(drawPos);
try try
@@ -834,22 +840,11 @@ namespace Barotrauma.Lights
DebugConsole.ThrowError(sb.ToString(), e); DebugConsole.ThrowError(sb.ToString(), e);
} }
List<Vector2> output = new List<Vector2>(); visibleSegments.Sort((s1, s2) =>
//List<Pair<int, Vector2>> preOutput = new List<Pair<int, Vector2>>(); MathUtils.LineToPointDistanceSquared(s1.Start.WorldPos, s1.End.WorldPos, drawPos)
.CompareTo(MathUtils.LineToPointDistanceSquared(s2.Start.WorldPos, s2.End.WorldPos, drawPos)));
//remove points that are very close to each other
for (int i = 0; i < points.Count; i++)
{
for (int j = Math.Min(i + 4, points.Count-1); j > i; j--)
{
if (Math.Abs(points[i].WorldPos.X - points[j].WorldPos.X) < 6 &&
Math.Abs(points[i].WorldPos.Y - points[j].WorldPos.Y) < 6)
{
points.RemoveAt(j);
}
}
}
output.Clear();
foreach (SegmentPoint p in points) foreach (SegmentPoint p in points)
{ {
Vector2 dir = Vector2.Normalize(p.WorldPos - drawPos); Vector2 dir = Vector2.Normalize(p.WorldPos - drawPos);
@@ -857,10 +852,10 @@ namespace Barotrauma.Lights
//do two slightly offset raycasts to hit the segment itself and whatever's behind it //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 intersection1 = RayCast(drawPos, drawPos + dir * boundsExtended * 2 - dirNormal, visibleSegments);
if (intersection1.index < 0) { return null; }
var intersection2 = RayCast(drawPos, drawPos + dir * boundsExtended * 2 + dirNormal, visibleSegments); var intersection2 = RayCast(drawPos, drawPos + dir * boundsExtended * 2 + dirNormal, visibleSegments);
if (intersection2.index < 0) { return null; }
if (intersection1.index < 0) return null;
if (intersection2.index < 0) return null;
Segment seg1 = visibleSegments[intersection1.index]; Segment seg1 = visibleSegments[intersection1.index];
Segment seg2 = visibleSegments[intersection2.index]; Segment seg2 = visibleSegments[intersection2.index];
@@ -872,7 +867,7 @@ namespace Barotrauma.Lights
//hit at the current segmentpoint -> place the segmentpoint into the list //hit at the current segmentpoint -> place the segmentpoint into the list
output.Add(p.WorldPos); output.Add(p.WorldPos);
foreach (ConvexHullList hullList in hullsInRange) foreach (ConvexHullList hullList in convexHullsInRange)
{ {
hullList.IsHidden.Remove(p.ConvexHull); hullList.IsHidden.Remove(p.ConvexHull);
hullList.IsHidden.Remove(seg1.ConvexHull); hullList.IsHidden.Remove(seg1.ConvexHull);
@@ -886,7 +881,7 @@ namespace Barotrauma.Lights
output.Add(isPoint1 ? p.WorldPos : intersection1.pos); output.Add(isPoint1 ? p.WorldPos : intersection1.pos);
output.Add(isPoint2 ? p.WorldPos : intersection2.pos); output.Add(isPoint2 ? p.WorldPos : intersection2.pos);
foreach (ConvexHullList hullList in hullsInRange) foreach (ConvexHullList hullList in convexHullsInRange)
{ {
hullList.IsHidden.Remove(p.ConvexHull); hullList.IsHidden.Remove(p.ConvexHull);
hullList.IsHidden.Remove(seg1.ConvexHull); hullList.IsHidden.Remove(seg1.ConvexHull);
@@ -914,7 +909,7 @@ namespace Barotrauma.Lights
return output; return output;
} }
private (int index, Vector2 pos) RayCast(Vector2 rayStart, Vector2 rayEnd, List<Segment> segments) private static (int index, Vector2 pos) RayCast(Vector2 rayStart, Vector2 rayEnd, List<Segment> segments)
{ {
Vector2? closestIntersection = null; Vector2? closestIntersection = null;
int segment = -1; int segment = -1;
@@ -939,13 +934,13 @@ namespace Barotrauma.Lights
//same for the x-axis //same for the x-axis
if (s.Start.WorldPos.X > s.End.WorldPos.X) if (s.Start.WorldPos.X > s.End.WorldPos.X)
{ {
if (s.Start.WorldPos.X < minX) continue; if (s.Start.WorldPos.X < minX) { continue; }
if (s.End.WorldPos.X > maxX) continue; if (s.End.WorldPos.X > maxX) { continue; }
} }
else else
{ {
if (s.End.WorldPos.X < minX) continue; if (s.End.WorldPos.X < minX) { continue; }
if (s.Start.WorldPos.X > maxX) continue; if (s.Start.WorldPos.X > maxX) { continue; }
} }
bool intersects; bool intersects;
@@ -1338,7 +1333,7 @@ namespace Barotrauma.Lights
return; return;
} }
CheckHullsInRange(); CheckConvexHullsInRange();
if (NeedsRecalculation && allowRecalculation) if (NeedsRecalculation && allowRecalculation)
{ {
@@ -1390,7 +1385,7 @@ namespace Barotrauma.Lights
public void Reset() public void Reset()
{ {
hullsInRange.Clear(); convexHullsInRange.Clear();
diffToSub.Clear(); diffToSub.Clear();
NeedsHullCheck = true; NeedsHullCheck = true;
NeedsRecalculation = true; NeedsRecalculation = true;
@@ -70,9 +70,9 @@ namespace Barotrauma
Vector2 position = cam.ScreenToWorld(PlayerInput.MousePosition); Vector2 position = cam.ScreenToWorld(PlayerInput.MousePosition);
foreach (MapEntity entity in mapEntityList) foreach (MapEntity entity in HighlightedEntities)
{ {
if (entity == this || !entity.IsHighlighted || !(entity is Item) || !entity.IsMouseOn(position)) { continue; } if (entity == this|| entity is not Item || !entity.IsMouseOn(position)) { continue; }
if (((Item)entity).GetComponent<DockingPort>() == null) { continue; } if (((Item)entity).GetComponent<DockingPort>() == null) { continue; }
if (linkedTo.Contains(entity)) if (linkedTo.Contains(entity))
{ {
@@ -69,7 +69,7 @@ namespace Barotrauma
private (Rectangle targetArea, RichString tip)? tooltip; private (Rectangle targetArea, RichString tip)? tooltip;
private (SubmarineInfo pendingSub, float realWorldCrushDepth) pendingSubInfo; private SubmarineInfo.PendingSubInfo pendingSubInfo;
private RichString beaconStationActiveText, beaconStationInactiveText; private RichString beaconStationActiveText, beaconStationInactiveText;
@@ -213,11 +213,17 @@ namespace Barotrauma
currLocationIndicatorPos = CurrentLocation.MapPosition; currLocationIndicatorPos = CurrentLocation.MapPosition;
} }
RemoveFogOfWar(newLocation); if (newLocation.Visited)
{
RemoveFogOfWar(newLocation);
}
} }
partial void RemoveFogOfWarProjSpecific(Location location) => RemoveFogOfWar(location);
private void RemoveFogOfWar(Location location, bool removeFromAdjacentLocations = true) private void RemoveFogOfWar(Location location, bool removeFromAdjacentLocations = true)
{ {
if (mapTiles == null) { return; }
if (location == null) { return; } if (location == null) { return; }
var mapTile = generationParams.MapTiles.Values.FirstOrDefault().FirstOrDefault(); var mapTile = generationParams.MapTiles.Values.FirstOrDefault().FirstOrDefault();
@@ -453,6 +459,7 @@ namespace Barotrauma
}; };
new GUICustomComponent(new RectTransform(new Vector2(0.6f, 1.0f), repBarHolder.RectTransform), onDraw: (sb, component) => 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); RoundSummary.DrawReputationBar(sb, component.Rect, location.Reputation.NormalizedValue);
}); });
@@ -681,6 +688,7 @@ namespace Barotrauma
Level.Loaded.DebugSetEndLocation(null); Level.Loaded.DebugSetEndLocation(null);
Discover(CurrentLocation); Discover(CurrentLocation);
Visit(CurrentLocation);
OnLocationChanged?.Invoke(new LocationChangeInfo(prevLocation, CurrentLocation)); OnLocationChanged?.Invoke(new LocationChangeInfo(prevLocation, CurrentLocation));
SelectLocation(-1); SelectLocation(-1);
if (GameMain.Client == null) if (GameMain.Client == null)
@@ -695,12 +703,6 @@ namespace Barotrauma
} }
} }
if (PlayerInput.KeyDown(Microsoft.Xna.Framework.Input.Keys.LeftShift) && PlayerInput.PrimaryMouseButtonClicked() && HighlightedLocation != null)
{
int distance = DistanceToClosestLocationWithOutpost(HighlightedLocation, out Location foundLocation);
DebugConsole.NewMessage($"Distance to closest outpost from {HighlightedLocation.Name} to {foundLocation?.Name} is {distance}");
}
if (PlayerInput.PrimaryMouseButtonClicked() && HighlightedLocation == null) if (PlayerInput.PrimaryMouseButtonClicked() && HighlightedLocation == null)
{ {
SelectLocation(-1); SelectLocation(-1);
@@ -729,6 +731,8 @@ namespace Barotrauma
Vector2 rectCenter = new Vector2(rect.Center.X, rect.Center.Y); Vector2 rectCenter = new Vector2(rect.Center.X, rect.Center.Y);
float missionIconScale = generationParams.MissionIcon != null ? 18.0f / generationParams.MissionIcon.SourceRect.Width : 1.0f;
Rectangle prevScissorRect = GameMain.Instance.GraphicsDevice.ScissorRectangle; Rectangle prevScissorRect = GameMain.Instance.GraphicsDevice.ScissorRectangle;
spriteBatch.End(); spriteBatch.End();
spriteBatch.GraphicsDevice.ScissorRectangle = Rectangle.Intersect(prevScissorRect, rect); spriteBatch.GraphicsDevice.ScissorRectangle = Rectangle.Intersect(prevScissorRect, rect);
@@ -807,10 +811,22 @@ namespace Barotrauma
drawRect.X = (int)pos.X - drawRect.Width / 2; drawRect.X = (int)pos.X - drawRect.Width / 2;
drawRect.Y = (int)pos.Y - drawRect.Width / 2; drawRect.Y = (int)pos.Y - drawRect.Width / 2;
if (drawRect.X > rect.Right - GUI.IntScale(100) && generationParams.MissionIcon != null && location.AvailableMissions.Any())
{
Vector2 offScreenMissionIconPos = new Vector2(rect.Right - GUI.IntScale(50), drawRect.Center.Y);
generationParams.MissionIcon.Draw(spriteBatch,
offScreenMissionIconPos,
generationParams.IndicatorColor, scale: missionIconScale * zoom);
GUI.Arrow.Draw(spriteBatch,
offScreenMissionIconPos + Vector2.UnitX * generationParams.MissionIcon.size.X * missionIconScale * zoom,
generationParams.IndicatorColor, MathHelper.PiOver2, scale: 0.5f);
}
if (!rect.Intersects(drawRect)) { continue; } if (!rect.Intersects(drawRect)) { continue; }
Color color = location.Type.SpriteColor; Color color = location.Type.SpriteColor;
if (!location.Discovered) { color = Color.White; } if (!location.Visited) { color = Color.White; }
if (location.Connections.Find(c => c.Locations.Contains(currentDisplayLocation)) == null) if (location.Connections.Find(c => c.Locations.Contains(currentDisplayLocation)) == null)
{ {
color *= 0.5f; color *= 0.5f;
@@ -890,7 +906,6 @@ namespace Barotrauma
location.AvailableMissions.Any(m => m.Locations[0] == m.Locations[1])) location.AvailableMissions.Any(m => m.Locations[0] == m.Locations[1]))
{ {
Vector2 missionIconPos = pos + new Vector2(1.35f, 0.35f) * generationParams.LocationIconSize * 0.5f * zoom; Vector2 missionIconPos = pos + new Vector2(1.35f, 0.35f) * generationParams.LocationIconSize * 0.5f * zoom;
float missionIconScale = 18.0f / generationParams.MissionIcon.SourceRect.Width;
generationParams.MissionIcon.Draw(spriteBatch, missionIconPos, generationParams.IndicatorColor, scale: missionIconScale * zoom); generationParams.MissionIcon.Draw(spriteBatch, missionIconPos, generationParams.IndicatorColor, scale: missionIconScale * zoom);
if (Vector2.Distance(PlayerInput.MousePosition, missionIconPos) < generationParams.MissionIcon.SourceRect.Width * zoom && IsPreferredTooltip(missionIconPos)) if (Vector2.Distance(PlayerInput.MousePosition, missionIconPos) < generationParams.MissionIcon.SourceRect.Width * zoom && IsPreferredTooltip(missionIconPos))
{ {
@@ -908,10 +923,17 @@ namespace Barotrauma
Vector2 dPos = pos; Vector2 dPos = pos;
if (location == HighlightedLocation) if (location == HighlightedLocation)
{ {
dPos.Y += 48; 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, 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); GUI.DrawString(spriteBatch, dPos + new Vector2(15, 50), "Secondary Faction: " + (location.SecondaryFaction?.Prefab.Name ?? "none"), Color.White, Color.Black, font: GUIStyle.SubHeadingFont);
dPos.Y += 48; dPos.Y += 48;
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; dPos.Y += 48;
GUI.DrawString(spriteBatch, dPos, $"Difficulty: {location.LevelData.Difficulty.FormatSingleDecimal()}", Color.White, Color.Black * 0.8f, 4, font: GUIStyle.SmallFont); GUI.DrawString(spriteBatch, dPos, $"Difficulty: {location.LevelData.Difficulty.FormatSingleDecimal()}", Color.White, Color.Black * 0.8f, 4, font: GUIStyle.SmallFont);
@@ -1045,7 +1067,7 @@ namespace Barotrauma
} }
float a = 1.0f; float a = 1.0f;
if (!connection.Locations[0].Discovered && !connection.Locations[1].Discovered) if (!connection.Locations[0].Visited && !connection.Locations[1].Visited)
{ {
if (IsInFogOfWar(connection.Locations[0])) if (IsInFogOfWar(connection.Locations[0]))
{ {
@@ -1089,39 +1111,8 @@ namespace Barotrauma
if (connection.LevelData.HasHuntingGrounds) { iconCount++; } if (connection.LevelData.HasHuntingGrounds) { iconCount++; }
if (connection.Locked) { iconCount++; } if (connection.Locked) { iconCount++; }
string tooltip = null; string tooltip = null;
float subCrushDepth = Level.DefaultRealWorldCrushDepth;
var currentOrPendingSub = SubmarineSelection.CurrentOrPendingSubmarine();
if (Submarine.MainSub != null && Submarine.MainSub.Info == currentOrPendingSub)
{
subCrushDepth = Submarine.MainSub.RealWorldCrushDepth;
}
else if (currentOrPendingSub != null)
{
if (pendingSubInfo.pendingSub != currentOrPendingSub)
{
// Store the real world crush depth for the pending sub so that we don't have to calculate it again every time
pendingSubInfo = (currentOrPendingSub, currentOrPendingSub.GetRealWorldCrushDepth());
}
subCrushDepth = pendingSubInfo.realWorldCrushDepth;
}
if (GameMain.GameSession?.Campaign?.UpgradeManager != null)
{
var hullUpgradePrefab = UpgradePrefab.Find("increasewallhealth".ToIdentifier());
if (hullUpgradePrefab != null)
{
int pendingLevel = GameMain.GameSession.Campaign.UpgradeManager.GetUpgradeLevel(hullUpgradePrefab, hullUpgradePrefab.UpgradeCategories.First());
int currentLevel = GameMain.GameSession.Campaign.UpgradeManager.GetRealUpgradeLevel(hullUpgradePrefab, hullUpgradePrefab.UpgradeCategories.First());
if (pendingLevel > currentLevel)
{
string updateValueStr = hullUpgradePrefab.SourceElement?.GetChildElement("Structure")?.GetAttributeString("crushdepth", null);
if (!string.IsNullOrEmpty(updateValueStr))
{
subCrushDepth = PropertyReference.CalculateUpgrade(subCrushDepth, pendingLevel - currentLevel, updateValueStr);
}
}
}
}
float subCrushDepth = SubmarineInfo.GetSubCrushDepth(SubmarineSelection.CurrentOrPendingSubmarine(), ref pendingSubInfo);
string crushDepthWarningIconStyle = null; string crushDepthWarningIconStyle = null;
if (connection.LevelData.InitialDepth * Physics.DisplayToRealWorldRatio > subCrushDepth) if (connection.LevelData.InitialDepth * Physics.DisplayToRealWorldRatio > subCrushDepth)
{ {
@@ -1277,6 +1268,14 @@ namespace Barotrauma
} }
} }
/// <summary>
/// Resets <see cref="pendingSubInfo"/> and forces crush depth to be calculated again for icon displaying purposes
/// </summary>
public void ResetPendingSub()
{
pendingSubInfo = new SubmarineInfo.PendingSubInfo();
}
partial void RemoveProjSpecific() partial void RemoveProjSpecific()
{ {
noiseOverlay?.Remove(); noiseOverlay?.Remove();
@@ -36,7 +36,7 @@ namespace Barotrauma
public static List<MapEntity> CopiedList = new List<MapEntity>(); public static List<MapEntity> CopiedList = new List<MapEntity>();
private static List<MapEntity> highlightedList = new List<MapEntity>(); private static List<MapEntity> highlightedInEditorList = new List<MapEntity>();
private static float highlightTimer; private static float highlightTimer;
@@ -131,10 +131,7 @@ namespace Barotrauma
return; return;
} }
foreach (MapEntity e in mapEntityList) ClearHighlightedEntities();
{
e.isHighlighted = false;
}
if (DisableSelect) if (DisableSelect)
{ {
@@ -262,11 +259,10 @@ namespace Barotrauma
if (i == 0) highLightedEntity = e; if (i == 0) highLightedEntity = e;
} }
} }
UpdateHighlighting(highlightedEntities); UpdateHighlighting(highlightedEntities);
} }
if (highLightedEntity != null) highLightedEntity.isHighlighted = true; if (highLightedEntity != null) { highLightedEntity.IsHighlighted = true; }
} }
if (GUI.KeyboardDispatcher.Subscriber == null) if (GUI.KeyboardDispatcher.Subscriber == null)
@@ -288,7 +284,6 @@ namespace Barotrauma
if (startMovingPos != Vector2.Zero) if (startMovingPos != Vector2.Zero)
{ {
Item targetContainer = GetPotentialContainer(position, SelectedList); Item targetContainer = GetPotentialContainer(position, SelectedList);
if (targetContainer != null) { targetContainer.IsHighlighted = true; } if (targetContainer != null) { targetContainer.IsHighlighted = true; }
if (PlayerInput.PrimaryMouseButtonReleased()) if (PlayerInput.PrimaryMouseButtonReleased())
@@ -610,10 +605,10 @@ namespace Barotrauma
if (highlightedListBox != null) if (highlightedListBox != null)
{ {
if (GUI.MouseOn == highlightedListBox || highlightedListBox.IsParentOf(GUI.MouseOn)) return; if (GUI.MouseOn == highlightedListBox || highlightedListBox.IsParentOf(GUI.MouseOn)) return;
if (highlightedEntities.SequenceEqual(highlightedList)) return; if (highlightedEntities.SequenceEqual(highlightedInEditorList)) return;
} }
highlightedList = highlightedEntities; highlightedInEditorList = highlightedEntities;
highlightedListBox = new GUIListBox(new RectTransform(new Point(180, highlightedEntities.Count * 18 + 5), GUI.Canvas) highlightedListBox = new GUIListBox(new RectTransform(new Point(180, highlightedEntities.Count * 18 + 5), GUI.Canvas)
{ {
@@ -1096,7 +1091,7 @@ namespace Barotrauma
private void UpdateResizing(Camera cam) private void UpdateResizing(Camera cam)
{ {
isHighlighted = true; IsHighlighted = true;
int startX = ResizeHorizontal ? -1 : 0; int startX = ResizeHorizontal ? -1 : 0;
int StartY = ResizeVertical ? -1 : 0; int StartY = ResizeVertical ? -1 : 0;
@@ -1197,7 +1192,7 @@ namespace Barotrauma
private void DrawResizing(SpriteBatch spriteBatch, Camera cam) private void DrawResizing(SpriteBatch spriteBatch, Camera cam)
{ {
isHighlighted = true; IsHighlighted = true;
int startX = ResizeHorizontal ? -1 : 0; int startX = ResizeHorizontal ? -1 : 0;
int StartY = ResizeVertical ? -1 : 0; int StartY = ResizeVertical ? -1 : 0;
@@ -89,7 +89,11 @@ namespace Barotrauma
CreateSpecsWindow(descriptionBox, font, includeDescription: true); CreateSpecsWindow(descriptionBox, font, includeDescription: true);
} }
public void CreateSpecsWindow(GUIListBox parent, GUIFont font, bool includeTitle = true, bool includeClass = true, bool includeDescription = false) public void CreateSpecsWindow(GUIListBox parent, GUIFont font,
bool includeTitle = true,
bool includeClass = true,
bool includeDescription = false,
bool includeCrushDepth = false)
{ {
float leftPanelWidth = 0.6f; float leftPanelWidth = 0.6f;
float rightPanelWidth = 0.4f / leftPanelWidth; float rightPanelWidth = 0.4f / leftPanelWidth;
@@ -155,6 +159,22 @@ namespace Barotrauma
{ CanBeFocused = false }; { CanBeFocused = false };
cargoCapacityText.RectTransform.MinSize = new Point(0, cargoCapacityText.Children.First().Rect.Height); cargoCapacityText.RectTransform.MinSize = new Point(0, cargoCapacityText.Children.First().Rect.Height);
if (includeCrushDepth)
{
var crushDepthText = new GUITextBlock(new RectTransform(new Vector2(leftPanelWidth, 0), parent.Content.RectTransform),
TextManager.Get("CrushDepth"), textAlignment: Alignment.TopLeft, font: font, wrap: true)
{
CanBeFocused = false
};
new GUITextBlock(new RectTransform(new Vector2(rightPanelWidth, 0.0f), crushDepthText.RectTransform, Anchor.TopRight, Pivot.TopLeft),
TextManager.GetWithVariable("meterformat", "[meters]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", GetSubCrushDepth())),
textAlignment: Alignment.TopLeft, font: font, wrap: true)
{
CanBeFocused = false
};
crushDepthText.RectTransform.MinSize = new Point(0, crushDepthText.Children.First().Rect.Height);
}
if (RecommendedCrewSizeMax > 0) if (RecommendedCrewSizeMax > 0)
{ {
var crewSizeText = new GUITextBlock(new RectTransform(new Vector2(leftPanelWidth, 0), parent.Content.RectTransform), var crewSizeText = new GUITextBlock(new RectTransform(new Vector2(leftPanelWidth, 0), parent.Content.RectTransform),
@@ -227,5 +247,57 @@ namespace Barotrauma
GUITextBlock.AutoScaleAndNormalize(parent.Content.GetAllChildren<GUITextBlock>().Where(c => c != submarineNameText && c != descBlock)); GUITextBlock.AutoScaleAndNormalize(parent.Content.GetAllChildren<GUITextBlock>().Where(c => c != submarineNameText && c != descBlock));
parent.ForceLayoutRecalculation(); parent.ForceLayoutRecalculation();
} }
public readonly record struct PendingSubInfo(SubmarineInfo PendingSub = null, bool StructuresDefineRealWorldCrushDepth = false, float RealWorldCrushDepth = Level.DefaultRealWorldCrushDepth);
private float GetSubCrushDepth()
{
var pendingSubInfo = new PendingSubInfo();
return GetSubCrushDepth(this, ref pendingSubInfo);
}
public static float GetSubCrushDepth(SubmarineInfo subInfo, ref PendingSubInfo pendingSubInfo)
{
float subCrushDepth = Level.DefaultRealWorldCrushDepth;
if (Submarine.MainSub != null && Submarine.MainSub.Info == subInfo)
{
subCrushDepth = Submarine.MainSub.RealWorldCrushDepth;
}
else if (subInfo != null)
{
if (pendingSubInfo.PendingSub != subInfo)
{
// Store the real world crush depth for the pending sub so that we don't have to calculate it again every time
pendingSubInfo = new PendingSubInfo(subInfo, subInfo.IsCrushDepthDefinedInStructures(out float realWorldCrushDepth), realWorldCrushDepth);
}
subCrushDepth = pendingSubInfo.RealWorldCrushDepth;
}
if (GameMain.GameSession?.Campaign?.UpgradeManager != null && UpgradePrefab.Find("increasewallhealth".ToIdentifier()) is UpgradePrefab hullUpgradePrefab)
{
int pendingLevel = GameMain.GameSession.Campaign.UpgradeManager.GetUpgradeLevel(hullUpgradePrefab, hullUpgradePrefab.UpgradeCategories.First(), info: subInfo);
// If there is a sub switch pending, unless its structures have crush depth defined in their elements,
// calculate the value based on the default crush depth and pending upgrade level
int currentLevel = 0;
if (pendingSubInfo.PendingSub is null || pendingSubInfo.StructuresDefineRealWorldCrushDepth)
{
currentLevel = GameMain.GameSession.Campaign.UpgradeManager.GetRealUpgradeLevelForSub(hullUpgradePrefab, hullUpgradePrefab.UpgradeCategories.First(), subInfo);
}
if (pendingLevel > currentLevel)
{
string updateValueStr = hullUpgradePrefab.SourceElement?.GetChildElement("Structure")?.GetAttributeString("crushdepth", null);
if (!string.IsNullOrEmpty(updateValueStr))
{
if (currentLevel > 0)
{
// If the current level is greater than 0, reset the crush depth value back to the base value before calculating the upgrade
int upgradePercentage = UpgradePrefab.ParsePercentage(updateValueStr, Identifier.Empty, suppressWarnings: true);
subCrushDepth /= (1f + (upgradePercentage / 100f * currentLevel));
}
subCrushDepth = PropertyReference.CalculateUpgrade(subCrushDepth, pendingLevel, updateValueStr);
}
}
}
return subCrushDepth;
}
} }
} }
@@ -4,26 +4,29 @@ using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Xml.Linq; using System.Xml.Linq;
using Barotrauma.Items.Components;
namespace Barotrauma namespace Barotrauma
{ {
class SubmarinePreview : IDisposable sealed class SubmarinePreview : IDisposable
{ {
private SpriteRecorder spriteRecorder;
private readonly SubmarineInfo submarineInfo; private readonly SubmarineInfo submarineInfo;
private SpriteRecorder spriteRecorder;
private Camera camera; private Camera camera;
private Task loadTask; private Task loadTask;
private (Vector2 Min, Vector2 Max) bounds;
private volatile bool isDisposed; private volatile bool isDisposed;
private GUIFrame previewFrame; private GUIFrame previewFrame;
private class HullCollection private sealed class HullCollection
{ {
public readonly List<Rectangle> Rects; public readonly List<Rectangle> Rects;
public readonly LocalizedString Name; public readonly LocalizedString Name;
@@ -42,7 +45,7 @@ namespace Barotrauma
} }
} }
private struct Door private readonly struct Door
{ {
public readonly Rectangle Rect; public readonly Rectangle Rect;
@@ -113,7 +116,7 @@ namespace Barotrauma
bool isMouseOnComponent = GUI.MouseOn == component; bool isMouseOnComponent = GUI.MouseOn == component;
camera.MoveCamera(deltaTime, allowZoom: isMouseOnComponent, followSub: false); camera.MoveCamera(deltaTime, allowZoom: isMouseOnComponent, followSub: false);
if (isMouseOnComponent && if (isMouseOnComponent &&
(PlayerInput.MidButtonHeld() || PlayerInput.LeftButtonHeld())) (PlayerInput.MidButtonHeld() || PlayerInput.PrimaryMouseButtonHeld()))
{ {
Vector2 moveSpeed = PlayerInput.MouseSpeed * (float)deltaTime * 60.0f / camera.Zoom; Vector2 moveSpeed = PlayerInput.MouseSpeed * (float)deltaTime * 60.0f / camera.Zoom;
moveSpeed.X = -moveSpeed.X; moveSpeed.X = -moveSpeed.X;
@@ -150,7 +153,9 @@ namespace Barotrauma
ScrollBarVisible = false, ScrollBarVisible = false,
Spacing = GUI.IntScale(5) Spacing = GUI.IntScale(5)
}; };
subInfo.CreateSpecsWindow(specsContainer, GUIStyle.Font, includeTitle: false, includeDescription: true); subInfo.CreateSpecsWindow(specsContainer, GUIStyle.Font,
includeTitle: false,
includeDescription: true);
int width = specsContainer.Rect.Width; int width = specsContainer.Rect.Width;
void recalculateSpecsContainerHeight() void recalculateSpecsContainerHeight()
{ {
@@ -186,7 +191,22 @@ namespace Barotrauma
}); });
recalculateSpecsContainerHeight(); recalculateSpecsContainerHeight();
GeneratePreviewMeshes(); TaskPool.Add(nameof(GeneratePreviewMeshes), GeneratePreviewMeshes(), _ =>
{
if (isDisposed) { return; }
// Reset the camera's position on the main thread,
// because the Camera class is not thread-safe and
// it's possible for its state to not get updated
// properly if done within a task
camera.Position = (bounds.Min + bounds.Max) * (0.5f, -0.5f);
Vector2 span2d = bounds.Max - bounds.Min;
Vector2 scaledSpan2d = span2d / camera.Resolution.ToVector2();
float scaledSpan = Math.Max(scaledSpan2d.X, scaledSpan2d.Y);
camera.MinZoom = Math.Min(0.1f, 0.4f / scaledSpan);
camera.Zoom = 0.7f / scaledSpan;
camera.StopMovement();
camera.UpdateTransform(interpolate: false, updateListener: false);
});
} }
public static void AddToGUIUpdateList() public static void AddToGUIUpdateList()
@@ -207,6 +227,7 @@ namespace Barotrauma
spriteRecorder.Begin(SpriteSortMode.BackToFront); spriteRecorder.Begin(SpriteSortMode.BackToFront);
HashSet<int> toIgnore = new HashSet<int>(); HashSet<int> toIgnore = new HashSet<int>();
HashSet<int> wires = new HashSet<int>();
foreach (var subElement in submarineInfo.SubmarineElement.Elements()) foreach (var subElement in submarineInfo.SubmarineElement.Elements())
{ {
@@ -221,7 +242,7 @@ namespace Barotrauma
ExtractItemContainerIds(component, toIgnore); ExtractItemContainerIds(component, toIgnore);
break; break;
case "connectionpanel": case "connectionpanel":
ExtractConnectionPanelLinks(component, toIgnore); ExtractConnectionPanelLinks(component, wires);
break; break;
} }
} }
@@ -231,20 +252,25 @@ namespace Barotrauma
await Task.Yield(); await Task.Yield();
} }
var wireNodes = new List<XElement>();
foreach (var subElement in submarineInfo.SubmarineElement.Elements()) foreach (var subElement in submarineInfo.SubmarineElement.Elements())
{ {
if (subElement.GetAttributeBool("hiddeningame", false)) { continue; } if (subElement.GetAttributeBool("hiddeningame", false)) { continue; }
switch (subElement.Name.LocalName.ToLowerInvariant()) switch (subElement.Name.LocalName.ToLowerInvariant())
{ {
case "structure":
case "item": case "item":
if (!toIgnore.Contains(subElement.GetAttributeInt("ID", 0))) var id = subElement.GetAttributeInt("ID", 0);
if (wires.Contains(id))
{
wireNodes.Add(subElement);
}
else if (!toIgnore.Contains(id))
{ {
BakeMapEntity(subElement); BakeMapEntity(subElement);
} }
break; break;
case "structure":
BakeMapEntity(subElement);
break;
case "hull": case "hull":
Identifier identifier = subElement.GetAttributeIdentifier("roomname", ""); Identifier identifier = subElement.GetAttributeIdentifier("roomname", "");
if (!identifier.IsEmpty) if (!identifier.IsEmpty)
@@ -261,15 +287,14 @@ namespace Barotrauma
if (isDisposed) { return; } if (isDisposed) { return; }
await Task.Yield(); await Task.Yield();
} }
spriteRecorder.End();
camera.Position = (spriteRecorder.Min + spriteRecorder.Max) * 0.5f; bounds = (spriteRecorder.Min, spriteRecorder.Max);
float scaledSpan = (spriteRecorder.Max - spriteRecorder.Min).X / camera.Resolution.X; wireNodes.ForEach(BakeWireNodes);
camera.Zoom = 0.8f / scaledSpan;
camera.StopMovement(); spriteRecorder.End();
} }
private void ExtractItemContainerIds(XElement component, HashSet<int> ids) private static void ExtractItemContainerIds(XElement component, HashSet<int> ids)
{ {
string containedString = component.GetAttributeString("contained", ""); string containedString = component.GetAttributeString("contained", "");
string[] itemIdStrings = containedString.Split(','); string[] itemIdStrings = containedString.Split(',');
@@ -283,7 +308,7 @@ namespace Barotrauma
} }
} }
private void ExtractConnectionPanelLinks(XElement component, HashSet<int> ids) private static void ExtractConnectionPanelLinks(XElement component, HashSet<int> ids)
{ {
var pins = component.Elements("input").Concat(component.Elements("output")); var pins = component.Elements("input").Concat(component.Elements("output"));
foreach (var pin in pins) foreach (var pin in pins)
@@ -297,6 +322,39 @@ namespace Barotrauma
} }
} }
private void BakeWireNodes(XElement element)
{
var prefabIdentifier = element.GetAttributeIdentifier("identifier", "");
if (prefabIdentifier.IsEmpty) { return; }
if (!ItemPrefab.Prefabs.TryGet(prefabIdentifier, out var prefab)) { return; }
var prefabWireComponentElement = prefab.ConfigElement.GetChildElement("wire");
if (prefabWireComponentElement is null) { return; }
var wireComponent = element.GetChildElement("wire");
if (wireComponent is null) { return; }
var color = element.GetAttributeColor("spritecolor") ?? Color.White;
var nodes = Wire.ExtractNodes(wireComponent).ToImmutableArray();
var wireSprite = Wire.ExtractWireSprite(prefab.ConfigElement);
var useSpriteDepth = element.GetAttributeBool("usespritedepth", false);
var depth =
useSpriteDepth
? element.GetAttributeFloat("spritedepth", 1.0f)
: wireSprite.Depth;
var width = prefabWireComponentElement.GetAttributeFloat("width", 0.3f);
for (int i = 0; i < nodes.Length - 1; i++)
{
var line = (Start: nodes[i], End: nodes[i + 1]);
var wireSegment = new Wire.WireSection(line.Start, line.End);
wireSegment.Draw(spriteRecorder, wireSprite, color, Vector2.Zero, depth, width);
}
}
private void BakeMapEntity(XElement element) private void BakeMapEntity(XElement element)
{ {
Identifier identifier = element.GetAttributeIdentifier("identifier", Identifier.Empty); Identifier identifier = element.GetAttributeIdentifier("identifier", Identifier.Empty);
@@ -313,27 +371,27 @@ namespace Barotrauma
float rotation = element.GetAttributeFloat("rotation", 0f); float rotation = element.GetAttributeFloat("rotation", 0f);
MapEntityPrefab prefab = null; MapEntityPrefab prefab;
if (element.Name.ToString().Equals("item", StringComparison.OrdinalIgnoreCase) && if (element.NameAsIdentifier() == "item"
ItemPrefab.Prefabs.TryGet(identifier, out ItemPrefab ip)) && ItemPrefab.Prefabs.TryGet(identifier, out ItemPrefab ip))
{ {
prefab = ip; prefab = ip;
} }
else else
{ {
prefab = MapEntityPrefab.List.FirstOrDefault(p => p.Identifier == identifier); prefab = MapEntityPrefab.FindByIdentifier(identifier);
} }
if (prefab == null) { return; } if (prefab == null) { return; }
var texture = prefab.Sprite.Texture; flippedX &= prefab.CanSpriteFlipX;
var srcRect = prefab.Sprite.SourceRect; flippedY &= prefab.CanSpriteFlipY;
SpriteEffects spriteEffects = SpriteEffects.None; SpriteEffects spriteEffects = SpriteEffects.None;
if (flippedX && ((prefab as ItemPrefab)?.CanSpriteFlipX ?? true)) if (flippedX)
{ {
spriteEffects |= SpriteEffects.FlipHorizontally; spriteEffects |= SpriteEffects.FlipHorizontally;
} }
if (flippedY && ((prefab as ItemPrefab)?.CanSpriteFlipY ?? true)) if (flippedY)
{ {
spriteEffects |= SpriteEffects.FlipVertically; spriteEffects |= SpriteEffects.FlipVertically;
} }
@@ -419,8 +477,8 @@ namespace Barotrauma
{ {
float offsetState = 0f; float offsetState = 0f;
Vector2 offset = decorativeSprite.GetOffset(ref offsetState, Vector2.Zero) * scale; Vector2 offset = decorativeSprite.GetOffset(ref offsetState, Vector2.Zero) * scale;
if (flippedX && itemPrefab.CanSpriteFlipX) { offset.X = -offset.X; } if (flippedX) { offset.X = -offset.X; }
if (flippedY && itemPrefab.CanSpriteFlipY) { offset.Y = -offset.Y; } if (flippedY) { offset.Y = -offset.Y; }
decorativeSprite.Sprite.DrawTiled(spriteRecorder, decorativeSprite.Sprite.DrawTiled(spriteRecorder,
new Vector2(spritePos.X + offset.X - rect.Width / 2, -(spritePos.Y + offset.Y + rect.Height / 2)), new Vector2(spritePos.X + offset.X - rect.Width / 2, -(spritePos.Y + offset.Y + rect.Height / 2)),
rect.Size.ToVector2(), color: color, rect.Size.ToVector2(), color: color,
@@ -451,8 +509,8 @@ namespace Barotrauma
float rotationState = 0f; float offsetState = 0f; float rotationState = 0f; float offsetState = 0f;
float rot = decorativeSprite.GetRotation(ref rotationState, 0f); float rot = decorativeSprite.GetRotation(ref rotationState, 0f);
Vector2 offset = decorativeSprite.GetOffset(ref offsetState, Vector2.Zero) * scale; Vector2 offset = decorativeSprite.GetOffset(ref offsetState, Vector2.Zero) * scale;
if (flippedX && itemPrefab.CanSpriteFlipX) { offset.X = -offset.X; } if (flippedX) { offset.X = -offset.X; }
if (flippedY && itemPrefab.CanSpriteFlipY) { offset.Y = -offset.Y; } if (flippedY) { offset.Y = -offset.Y; }
decorativeSprite.Sprite.Draw(spriteRecorder, new Vector2(spritePos.X + offset.X, -(spritePos.Y + offset.Y)), color, decorativeSprite.Sprite.Draw(spriteRecorder, new Vector2(spritePos.X + offset.X, -(spritePos.Y + offset.Y)), color,
MathHelper.ToRadians(rotation) + rot, decorativeSprite.GetScale(0f) * scale, prefab.Sprite.effects, MathHelper.ToRadians(rotation) + rot, decorativeSprite.GetScale(0f) * scale, prefab.Sprite.effects,
depth: Math.Min(depth + (decorativeSprite.Sprite.Depth - prefab.Sprite.Depth), 0.999f)); depth: Math.Min(depth + (decorativeSprite.Sprite.Depth - prefab.Sprite.Depth), 0.999f));
@@ -472,6 +530,7 @@ namespace Barotrauma
{ {
overrideSprite = false; overrideSprite = false;
float relativeScale = scale / prefab.Scale;
foreach (var subElement in prefab.ConfigElement.Elements()) foreach (var subElement in prefab.ConfigElement.Elements())
{ {
switch (subElement.Name.LocalName.ToLowerInvariant()) switch (subElement.Name.LocalName.ToLowerInvariant())
@@ -498,7 +557,6 @@ namespace Barotrauma
relativeBarrelPos, relativeBarrelPos,
MathHelper.ToRadians(rotation)); MathHelper.ToRadians(rotation));
float relativeScale = scale / prefab.Scale;
Vector2 drawPos = new Vector2(rect.X + rect.Width * relativeScale / 2 + transformedBarrelPos.X * relativeScale, rect.Y - rect.Height * relativeScale / 2 - transformedBarrelPos.Y * relativeScale); Vector2 drawPos = new Vector2(rect.X + rect.Width * relativeScale / 2 + transformedBarrelPos.X * relativeScale, rect.Y - rect.Height * relativeScale / 2 - transformedBarrelPos.Y * relativeScale);
drawPos.Y = -drawPos.Y; drawPos.Y = -drawPos.Y;
@@ -516,20 +574,22 @@ namespace Barotrauma
break; break;
case "door": case "door":
doors.Add(new Door(rect)); var scaledRect = rect with { Size = (rect.Size.ToVector2() * relativeScale).ToPoint() };
doors.Add(new Door(scaledRect));
var doorSpriteElem = subElement.Elements().FirstOrDefault(e => e.Name.LocalName.Equals("sprite", StringComparison.OrdinalIgnoreCase)); var doorSpriteElem = subElement.Elements().FirstOrDefault(e => e.Name.LocalName.Equals("sprite", StringComparison.OrdinalIgnoreCase));
if (doorSpriteElem != null) if (doorSpriteElem != null)
{ {
string texturePath = doorSpriteElem.GetAttributeString("texture", ""); string texturePath = doorSpriteElem.GetAttributeStringUnrestricted("texture", "");
Vector2 pos = rect.Location.ToVector2() * new Vector2(1f, -1f); Vector2 pos = scaledRect.Location.ToVector2() * new Vector2(1f, -1f);
if (subElement.GetAttributeBool("horizontal", false)) if (subElement.GetAttributeBool("horizontal", false))
{ {
pos.Y += (float)rect.Height * 0.5f; pos.Y += (float)scaledRect.Height * 0.5f;
} }
else else
{ {
pos.X += (float)rect.Width * 0.5f; pos.X += (float)scaledRect.Width * 0.5f;
} }
Sprite doorSprite = new Sprite(doorSpriteElem, texturePath.Contains("/") ? "" : Path.GetDirectoryName(prefab.FilePath)); Sprite doorSprite = new Sprite(doorSpriteElem, texturePath.Contains("/") ? "" : Path.GetDirectoryName(prefab.FilePath));
spriteRecorder.Draw(doorSprite.Texture, pos, spriteRecorder.Draw(doorSprite.Texture, pos,
@@ -555,7 +615,7 @@ namespace Barotrauma
} }
} }
public void ParseUpgrades(XElement prefabConfigElement, ref float scale) private void ParseUpgrades(XElement prefabConfigElement, ref float scale)
{ {
foreach (var upgrade in prefabConfigElement.Elements("Upgrade")) foreach (var upgrade in prefabConfigElement.Elements("Upgrade"))
{ {
@@ -39,7 +39,7 @@ namespace Barotrauma
{ {
Color clr = CurrentHull == null ? Color.DodgerBlue : GUIStyle.Green; Color clr = CurrentHull == null ? Color.DodgerBlue : GUIStyle.Green;
if (spawnType != SpawnType.Path) { clr = Color.Gray; } if (spawnType != SpawnType.Path) { clr = Color.Gray; }
if (isObstructed) if (!IsTraversable)
{ {
clr = Color.Black; clr = Color.Black;
} }
@@ -84,7 +84,7 @@ namespace Barotrauma
GUI.DrawLine(spriteBatch, GUI.DrawLine(spriteBatch,
drawPos, drawPos,
new Vector2(e.DrawPosition.X, -e.DrawPosition.Y), new Vector2(e.DrawPosition.X, -e.DrawPosition.Y),
(isObstructed ? Color.Gray : GUIStyle.Green) * 0.7f, width: 5, depth: 0.002f); (IsTraversable ? GUIStyle.Green : Color.Gray) * 0.7f, width: 5, depth: 0.002f);
} }
if (ConnectedGap != null) if (ConnectedGap != null)
{ {
@@ -175,9 +175,9 @@ namespace Barotrauma
if (PlayerInput.KeyDown(Keys.Space)) if (PlayerInput.KeyDown(Keys.Space))
{ {
foreach (MapEntity e in mapEntityList) foreach (MapEntity e in HighlightedEntities)
{ {
if (!(e is WayPoint) || e == this || !e.IsHighlighted) { continue; } if (e is not WayPoint || e == this) { continue; }
if (linkedTo.Contains(e)) if (linkedTo.Contains(e))
{ {
@@ -12,7 +12,7 @@ namespace Barotrauma.Networking
string name, string name,
Either<Address, AccountId> addressOrAccountId, Either<Address, AccountId> addressOrAccountId,
string reason, string reason,
DateTime? expiration) Option<SerializableDateTime> expiration)
{ {
this.Name = name; this.Name = name;
this.AddressOrAccountId = addressOrAccountId; this.AddressOrAccountId = addressOrAccountId;
@@ -94,8 +94,9 @@ namespace Barotrauma.Networking
topArea.ForceLayoutRecalculation(); topArea.ForceLayoutRecalculation();
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedPlayerFrame.RectTransform), new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedPlayerFrame.RectTransform),
bannedPlayer.ExpirationTime == null ? bannedPlayer.ExpirationTime.TryUnwrap(out var expirationTime)
TextManager.Get("BanPermanent") : TextManager.GetWithVariable("BanExpires", "[time]", bannedPlayer.ExpirationTime.Value.ToString()), ? TextManager.GetWithVariable("BanExpires", "[time]", expirationTime.ToLocalUserString())
: TextManager.Get("BanPermanent"),
font: GUIStyle.SmallFont); font: GUIStyle.SmallFont);
LocalizedString reason = TextManager.GetServerMessage(bannedPlayer.Reason).Fallback(bannedPlayer.Reason); LocalizedString reason = TextManager.GetServerMessage(bannedPlayer.Reason).Fallback(bannedPlayer.Reason);
@@ -149,11 +150,11 @@ namespace Barotrauma.Networking
bool includesExpiration = incMsg.ReadBoolean(); bool includesExpiration = incMsg.ReadBoolean();
incMsg.ReadPadBits(); incMsg.ReadPadBits();
DateTime? expiration = null; Option<SerializableDateTime> expiration = Option<SerializableDateTime>.None();
if (includesExpiration) if (includesExpiration)
{ {
double hoursFromNow = incMsg.ReadDouble(); double hoursFromNow = incMsg.ReadDouble();
expiration = DateTime.Now + TimeSpan.FromHours(hoursFromNow); expiration = Option<SerializableDateTime>.Some(SerializableDateTime.LocalNow + TimeSpan.FromHours(hoursFromNow));
} }
string reason = incMsg.ReadString(); string reason = incMsg.ReadString();
@@ -26,8 +26,8 @@ namespace Barotrauma.Networking
if (type != ChatMessageType.Order) if (type != ChatMessageType.Order)
{ {
changeType = (PlayerConnectionChangeType)msg.ReadByte(); changeType = (PlayerConnectionChangeType)msg.ReadByte();
txt = msg.ReadString();
} }
txt = msg.ReadString();
string senderName = msg.ReadString(); string senderName = msg.ReadString();
Character senderCharacter = null; Character senderCharacter = null;
@@ -87,11 +87,6 @@ namespace Barotrauma.Networking
targetRoom = senderCharacter?.CurrentHull?.DisplayName?.Value; targetRoom = senderCharacter?.CurrentHull?.DisplayName?.Value;
} }
txt = orderPrefab.GetChatMessage(orderMessageInfo.TargetCharacter?.Name, targetRoom,
givingOrderToSelf: orderMessageInfo.TargetCharacter == senderCharacter,
orderOption: orderOption,
isNewOrder: orderMessageInfo.IsNewOrder);
if (GameMain.Client.GameStarted && Screen.Selected == GameMain.GameScreen) if (GameMain.Client.GameStarted && Screen.Selected == GameMain.GameScreen)
{ {
Order order = null; Order order = null;
@@ -332,7 +332,6 @@ namespace Barotrauma.Networking
FileSize = 0 FileSize = 0
}; };
Md5Hash.Cache.Remove(directTransfer.FilePath);
OnFinished(directTransfer); OnFinished(directTransfer);
} }
break; break;
@@ -414,7 +413,6 @@ namespace Barotrauma.Networking
{ {
finishedTransfers.Add((transferId, Timing.TotalTime)); finishedTransfers.Add((transferId, Timing.TotalTime));
StopTransfer(activeTransfer); StopTransfer(activeTransfer);
Md5Hash.Cache.Remove(activeTransfer.FilePath);
OnFinished(activeTransfer); OnFinished(activeTransfer);
} }
else else
@@ -326,7 +326,7 @@ namespace Barotrauma.Networking
return serverEndpoint switch return serverEndpoint switch
{ {
LidgrenEndpoint lidgrenEndpoint => new LidgrenClientPeer(lidgrenEndpoint, callbacks, ownerKey), LidgrenEndpoint lidgrenEndpoint => new LidgrenClientPeer(lidgrenEndpoint, callbacks, ownerKey),
SteamP2PEndpoint _ when ownerKey is Some<int> { Value: var key } => new SteamP2POwnerPeer(callbacks, key), SteamP2PEndpoint _ when ownerKey.TryUnwrap(out var key) => new SteamP2POwnerPeer(callbacks, key),
SteamP2PEndpoint steamP2PServerEndpoint when ownerKey.IsNone() => new SteamP2PClientPeer(steamP2PServerEndpoint, callbacks), SteamP2PEndpoint steamP2PServerEndpoint when ownerKey.IsNone() => new SteamP2PClientPeer(steamP2PServerEndpoint, callbacks),
_ => throw new ArgumentOutOfRangeException() _ => throw new ArgumentOutOfRangeException()
}; };
@@ -522,7 +522,7 @@ namespace Barotrauma.Networking
if (GameStarted && Screen.Selected == GameMain.GameScreen) if (GameStarted && Screen.Selected == GameMain.GameScreen)
{ {
EndVoteTickBox.Visible = ServerSettings.AllowEndVoting && HasSpawned && !(GameMain.GameSession?.GameMode is CampaignMode); EndVoteTickBox.Visible = ServerSettings.AllowEndVoting && HasSpawned;
RespawnManager?.Update(deltaTime); RespawnManager?.Update(deltaTime);
@@ -988,7 +988,7 @@ namespace Barotrauma.Networking
GameMain.ModDownloadScreen.Reset(); GameMain.ModDownloadScreen.Reset();
ContentPackageManager.EnabledPackages.Restore(); ContentPackageManager.EnabledPackages.Restore();
CampaignMode.StartRoundCancellationToken?.Cancel(); GameMain.GameSession?.Campaign?.CancelStartRound();
if (SteamManager.IsInitialized) if (SteamManager.IsInitialized)
{ {
@@ -1761,7 +1761,7 @@ namespace Barotrauma.Networking
{ {
string subName = inc.ReadString(); string subName = inc.ReadString();
string subHash = inc.ReadString(); string subHash = inc.ReadString();
byte subClass = inc.ReadByte(); SubmarineClass subClass = (SubmarineClass)inc.ReadByte();
bool isShuttle = inc.ReadBoolean(); bool isShuttle = inc.ReadBoolean();
bool requiredContentPackagesInstalled = inc.ReadBoolean(); bool requiredContentPackagesInstalled = inc.ReadBoolean();
@@ -1770,7 +1770,7 @@ namespace Barotrauma.Networking
{ {
matchingSub = new SubmarineInfo(Path.Combine(SaveUtil.SubmarineDownloadFolder, subName) + ".sub", subHash, tryLoad: false) matchingSub = new SubmarineInfo(Path.Combine(SaveUtil.SubmarineDownloadFolder, subName) + ".sub", subHash, tryLoad: false)
{ {
SubmarineClass = (SubmarineClass)subClass SubmarineClass = subClass
}; };
if (isShuttle) { matchingSub.AddTag(SubmarineTag.Shuttle); } if (isShuttle) { matchingSub.AddTag(SubmarineTag.Shuttle); }
} }
@@ -2003,10 +2003,10 @@ namespace Barotrauma.Networking
GameMain.NetLobbyScreen.SetTraitorsEnabled(traitorsEnabled); GameMain.NetLobbyScreen.SetTraitorsEnabled(traitorsEnabled);
GameMain.NetLobbyScreen.SetMissionType(missionType); GameMain.NetLobbyScreen.SetMissionType(missionType);
if (!allowModeVoting) GameMain.NetLobbyScreen.SelectMode(modeIndex); GameMain.NetLobbyScreen.SelectMode(modeIndex);
if (isInitialUpdate && GameMain.NetLobbyScreen.SelectedMode == GameModePreset.MultiPlayerCampaign) if (isInitialUpdate && GameMain.NetLobbyScreen.SelectedMode == GameModePreset.MultiPlayerCampaign)
{ {
if (GameMain.Client.IsServerOwner) RequestSelectMode(modeIndex); if (GameMain.Client.IsServerOwner) { RequestSelectMode(modeIndex); }
} }
if (GameMain.NetLobbyScreen.SelectedMode == GameModePreset.MultiPlayerCampaign) if (GameMain.NetLobbyScreen.SelectedMode == GameModePreset.MultiPlayerCampaign)
@@ -2405,7 +2405,9 @@ namespace Barotrauma.Networking
var newSub = new SubmarineInfo(transfer.FilePath); var newSub = new SubmarineInfo(transfer.FilePath);
if (newSub.IsFileCorrupted) { return; } if (newSub.IsFileCorrupted) { return; }
var existingSubs = SubmarineInfo.SavedSubmarines.Where(s => s.Name == newSub.Name && s.MD5Hash.StringRepresentation == newSub.MD5Hash.StringRepresentation).ToList(); var existingSubs = SubmarineInfo.SavedSubmarines
.Where(s => s.Name == newSub.Name && s.MD5Hash == newSub.MD5Hash)
.ToList();
foreach (SubmarineInfo existingSub in existingSubs) foreach (SubmarineInfo existingSub in existingSubs)
{ {
existingSub.Dispose(); existingSub.Dispose();
@@ -2464,12 +2466,13 @@ namespace Barotrauma.Networking
} }
// Replace a submarine dud with the downloaded version // Replace a submarine dud with the downloaded version
SubmarineInfo existingServerSub = ServerSubmarines.Find(s => s.Name == newSub.Name && s.MD5Hash?.StringRepresentation == newSub.MD5Hash?.StringRepresentation); SubmarineInfo existingServerSub = ServerSubmarines.Find(s =>
s.Name == newSub.Name
&& s.MD5Hash == newSub.MD5Hash);
if (existingServerSub != null) if (existingServerSub != null)
{ {
int existingIndex = ServerSubmarines.IndexOf(existingServerSub); int existingIndex = ServerSubmarines.IndexOf(existingServerSub);
ServerSubmarines.RemoveAt(existingIndex); ServerSubmarines[existingIndex] = newSub;
ServerSubmarines.Insert(existingIndex, newSub);
existingServerSub.Dispose(); existingServerSub.Dispose();
} }
@@ -2620,7 +2623,13 @@ namespace Barotrauma.Networking
using (var segmentTable = SegmentTableWriter<ClientNetSegment>.StartWriting(msg)) using (var segmentTable = SegmentTableWriter<ClientNetSegment>.StartWriting(msg))
{ {
segmentTable.StartNewSegment(ClientNetSegment.Vote); segmentTable.StartNewSegment(ClientNetSegment.Vote);
Voting.ClientWrite(msg, voteType, data); bool succeeded = Voting.ClientWrite(msg, voteType, data);
if (!succeeded)
{
throw new Exception(
$"Failed to write vote of type {voteType}: " +
$"data was of invalid type {data?.GetType().Name ?? "NULL"}");
}
} }
ClientPeer.Send(msg, DeliveryMethod.Reliable); ClientPeer.Send(msg, DeliveryMethod.Reliable);
@@ -2790,7 +2799,6 @@ namespace Barotrauma.Networking
/// </summary> /// </summary>
public void RequestSelectMode(int modeIndex) public void RequestSelectMode(int modeIndex)
{ {
if (!HasPermission(ClientPermissions.SelectMode)) return;
if (modeIndex < 0 || modeIndex >= GameMain.NetLobbyScreen.ModeList.Content.CountChildren) if (modeIndex < 0 || modeIndex >= GameMain.NetLobbyScreen.ModeList.Content.CountChildren)
{ {
DebugConsole.ThrowError("Gamemode index out of bounds (" + modeIndex + ")\n" + Environment.StackTrace.CleanupStackTrace()); DebugConsole.ThrowError("Gamemode index out of bounds (" + modeIndex + ")\n" + Environment.StackTrace.CleanupStackTrace());
@@ -2844,13 +2852,14 @@ namespace Barotrauma.Networking
/// <summary> /// <summary>
/// Tell the server to end the round (permission required) /// Tell the server to end the round (permission required)
/// </summary> /// </summary>
public void RequestRoundEnd(bool save) public void RequestRoundEnd(bool save, bool quitCampaign = false)
{ {
IWriteMessage msg = new WriteOnlyMessage(); IWriteMessage msg = new WriteOnlyMessage();
msg.WriteByte((byte)ClientPacketHeader.SERVER_COMMAND); msg.WriteByte((byte)ClientPacketHeader.SERVER_COMMAND);
msg.WriteUInt16((UInt16)ClientPermissions.ManageRound); msg.WriteUInt16((UInt16)ClientPermissions.ManageRound);
msg.WriteBoolean(true); //indicates round end msg.WriteBoolean(true); //indicates round end
msg.WriteBoolean(save); msg.WriteBoolean(save);
msg.WriteBoolean(quitCampaign);
ClientPeer.Send(msg, DeliveryMethod.Reliable); ClientPeer.Send(msg, DeliveryMethod.Reliable);
} }
@@ -82,6 +82,11 @@ namespace Barotrauma.Networking
Initialization = ConnectionInitialization.SteamTicketAndVersion Initialization = ConnectionInitialization.SteamTicketAndVersion
}; };
if (steamAuthTicket is { Canceled: true })
{
throw new InvalidOperationException("ReadConnectionInitializationStep failed: Steam auth ticket has been cancelled.");
}
ClientSteamTicketAndVersionPacket body = new ClientSteamTicketAndVersionPacket ClientSteamTicketAndVersionPacket body = new ClientSteamTicketAndVersionPacket
{ {
Name = GameMain.Client.Name, Name = GameMain.Client.Name,
@@ -156,7 +156,7 @@ namespace Barotrauma.Networking
var packet = INetSerializableStruct.Read<ClientSteamTicketAndVersionPacket>(inc); var packet = INetSerializableStruct.Read<ClientSteamTicketAndVersionPacket>(inc);
packet.SteamAuthTicket.TryUnwrap(out byte[] ticket); packet.SteamAuthTicket.TryUnwrap(out var ticket);
Steamworks.BeginAuthResult authSessionStartState = SteamManager.StartAuthSession(ticket, steamId); Steamworks.BeginAuthResult authSessionStartState = SteamManager.StartAuthSession(ticket, steamId);
if (authSessionStartState != Steamworks.BeginAuthResult.OK) if (authSessionStartState != Steamworks.BeginAuthResult.OK)
@@ -1,5 +1,6 @@
#nullable enable #nullable enable
using Barotrauma.Extensions;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@@ -70,6 +71,9 @@ namespace Barotrauma.Networking
[Serialize(PlayStyle.Casual, IsPropertySaveable.Yes)] [Serialize(PlayStyle.Casual, IsPropertySaveable.Yes)]
public PlayStyle PlayStyle { get; set; } public PlayStyle PlayStyle { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
public LanguageIdentifier Language { get; set; }
public Version GameVersion { get; set; } = new Version(0, 0, 0, 0); public Version GameVersion { get; set; } = new Version(0, 0, 0, 0);
public Option<int> Ping = Option<int>.None(); public Option<int> Ping = Option<int>.None();
@@ -281,7 +285,7 @@ namespace Barotrauma.Networking
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
float elementHeight = 0.075f; const float elementHeight = 0.075f;
// Spacing // Spacing
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.025f), content.RectTransform), style: null); new GUIFrame(new RectTransform(new Vector2(1.0f, 0.025f), content.RectTransform), style: null);
@@ -294,6 +298,11 @@ namespace Barotrauma.Networking
serverMsg.Content.RectTransform.SizeChanged += () => { msgText.CalculateHeightFromText(); }; serverMsg.Content.RectTransform.SizeChanged += () => { msgText.CalculateHeightFromText(); };
msgText.RectTransform.SizeChanged += () => { serverMsg.UpdateScrollBarSize(); }; msgText.RectTransform.SizeChanged += () => { serverMsg.UpdateScrollBarSize(); };
var languageLabel = new GUITextBlock(new RectTransform(new Vector2(1.0f, elementHeight), content.RectTransform), TextManager.Get("Language"));
new GUITextBlock(new RectTransform(Vector2.One, languageLabel.RectTransform),
ServerLanguageOptions.Options.FirstOrNull(o => o.Identifier == Language)?.Label ?? TextManager.Get("Unknown"),
textAlignment: Alignment.Right);
var gameMode = new GUITextBlock(new RectTransform(new Vector2(1.0f, elementHeight), content.RectTransform), TextManager.Get("GameMode")); var gameMode = new GUITextBlock(new RectTransform(new Vector2(1.0f, elementHeight), content.RectTransform), TextManager.Get("GameMode"));
new GUITextBlock(new RectTransform(Vector2.One, gameMode.RectTransform), new GUITextBlock(new RectTransform(Vector2.One, gameMode.RectTransform),
TextManager.Get(GameMode.IsEmpty ? "Unknown" : "GameMode." + GameMode).Fallback(GameMode.Value), TextManager.Get(GameMode.IsEmpty ? "Unknown" : "GameMode." + GameMode).Fallback(GameMode.Value),
@@ -363,7 +372,7 @@ namespace Barotrauma.Networking
packageText.Selected = true; packageText.Selected = true;
} }
//workshop download link found //workshop download link found
else if (package.Id is Some<ContentPackageId> { Value: var ugcId } && ugcId is SteamWorkshopId) else if (package.Id.TryUnwrap(out var ugcId) && ugcId is SteamWorkshopId)
{ {
packageText.ToolTip = TextManager.GetWithVariable("ServerListIncompatibleContentPackageWorkshopAvailable", "[contentpackage]", package.Name); packageText.ToolTip = TextManager.GetWithVariable("ServerListIncompatibleContentPackageWorkshopAvailable", "[contentpackage]", package.Name);
} }
@@ -417,6 +426,7 @@ namespace Barotrauma.Networking
GameMode = valueGetter("gamemode")?.ToIdentifier() ?? Identifier.Empty; GameMode = valueGetter("gamemode")?.ToIdentifier() ?? Identifier.Empty;
if (Enum.TryParse(valueGetter("traitors"), out YesNoMaybe traitorsEnabled)) { TraitorsEnabled = traitorsEnabled; } if (Enum.TryParse(valueGetter("traitors"), out YesNoMaybe traitorsEnabled)) { TraitorsEnabled = traitorsEnabled; }
if (Enum.TryParse(valueGetter("playstyle"), out PlayStyle playStyle)) { PlayStyle = playStyle; } if (Enum.TryParse(valueGetter("playstyle"), out PlayStyle playStyle)) { PlayStyle = playStyle; }
Language = valueGetter("language")?.ToLanguageIdentifier() ?? LanguageIdentifier.None;
ContentPackages = ExtractContentPackageInfo(valueGetter).ToImmutableArray(); ContentPackages = ExtractContentPackageInfo(valueGetter).ToImmutableArray();
@@ -3,6 +3,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Linq; using System.Linq;
using Barotrauma.Steam;
namespace Barotrauma.Networking namespace Barotrauma.Networking
{ {
@@ -367,6 +368,15 @@ namespace Barotrauma.Networking
//*********************************************** //***********************************************
// Language
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), serverTab.RectTransform), TextManager.Get("Language"), font: GUIStyle.SubHeadingFont);
var languageDD = new GUIDropDown(new RectTransform(new Vector2(1.0f, 0.02f), serverTab.RectTransform));
foreach (var language in ServerLanguageOptions.Options)
{
languageDD.AddItem(language.Label, language.Identifier);
}
GetPropertyData(nameof(Language)).AssignGUIComponent(languageDD);
//changing server visibility on the fly is not supported in dedicated servers //changing server visibility on the fly is not supported in dedicated servers
if (GameMain.Client?.ClientPeer is not LidgrenClientPeer) if (GameMain.Client?.ClientPeer is not LidgrenClientPeer)
{ {
@@ -116,10 +116,13 @@ namespace Barotrauma.Networking
bool spectating = Character.Controlled == null; bool spectating = Character.Controlled == null;
float rangeMultiplier = spectating ? 2.0f : 1.0f; float rangeMultiplier = spectating ? 2.0f : 1.0f;
WifiComponent radio = null; WifiComponent radio = null;
var messageType = !client.VoipQueue.ForceLocal && ChatMessage.CanUseRadio(client.Character, out radio) ? ChatMessageType.Radio : ChatMessageType.Default; var messageType =
!client.VoipQueue.ForceLocal && ChatMessage.CanUseRadio(client.Character, out radio) && ChatMessage.CanUseRadio(Character.Controlled) ?
ChatMessageType.Radio : ChatMessageType.Default;
client.Character.ShowSpeechBubble(1.25f, ChatMessage.MessageColor[(int)messageType]); client.Character.ShowSpeechBubble(1.25f, ChatMessage.MessageColor[(int)messageType]);
client.VoipSound.UseRadioFilter = messageType == ChatMessageType.Radio && !GameSettings.CurrentConfig.Audio.DisableVoiceChatFilters; client.VoipSound.UseRadioFilter = messageType == ChatMessageType.Radio && !GameSettings.CurrentConfig.Audio.DisableVoiceChatFilters;
client.RadioNoise = 0.0f;
if (messageType == ChatMessageType.Radio) if (messageType == ChatMessageType.Radio)
{ {
client.VoipSound.SetRange(radio.Range * RangeNear * speechImpedimentMultiplier * rangeMultiplier, radio.Range * speechImpedimentMultiplier * rangeMultiplier); client.VoipSound.SetRange(radio.Range * RangeNear * speechImpedimentMultiplier * rangeMultiplier, radio.Range * speechImpedimentMultiplier * rangeMultiplier);
@@ -131,7 +134,6 @@ namespace Barotrauma.Networking
} }
else else
{ {
client.VoipSound.SetRange(ChatMessage.SpeakRange * RangeNear * speechImpedimentMultiplier * rangeMultiplier, ChatMessage.SpeakRange * speechImpedimentMultiplier * rangeMultiplier); client.VoipSound.SetRange(ChatMessage.SpeakRange * RangeNear * speechImpedimentMultiplier * rangeMultiplier, ChatMessage.SpeakRange * speechImpedimentMultiplier * rangeMultiplier);
} }
client.VoipSound.UseMuffleFilter = client.VoipSound.UseMuffleFilter =
@@ -13,13 +13,11 @@ namespace Barotrauma
{ {
public SubmarineInfo SubmarineInfo { get; set; } public SubmarineInfo SubmarineInfo { get; set; }
public bool TransferItems { get; set; } public bool TransferItems { get; set; }
public int DeliveryFee { get; set; }
public SubmarineVoteInfo(SubmarineInfo submarineInfo, bool transferItems, int deliveryFee) public SubmarineVoteInfo(SubmarineInfo submarineInfo, bool transferItems)
{ {
SubmarineInfo = submarineInfo; SubmarineInfo = submarineInfo;
TransferItems = transferItems; TransferItems = transferItems;
DeliveryFee = deliveryFee;
} }
} }
@@ -98,16 +96,15 @@ namespace Barotrauma
foreach (GUIComponent comp in listBox.Content.Children) foreach (GUIComponent comp in listBox.Content.Children)
{ {
if (comp.UserData != userData) { continue; } if (comp.UserData != userData) { continue; }
if (!(comp.FindChild("votes") is GUITextBlock voteText)) if (comp.FindChild("votes") is not GUITextBlock voteText)
{ {
voteText = new GUITextBlock(new RectTransform(new Point(30, comp.Rect.Height), comp.RectTransform, Anchor.CenterRight), voteText = new GUITextBlock(new RectTransform(new Point(GUI.IntScale(30), comp.Rect.Height), comp.RectTransform, Anchor.CenterRight),
"", textAlignment: Alignment.CenterRight) "", textAlignment: Alignment.Center)
{ {
Padding = Vector4.Zero, Padding = Vector4.Zero,
UserData = "votes" UserData = "votes"
}; };
} }
voteText.Text = votes == 0 ? "" : votes.ToString(); voteText.Text = votes == 0 ? "" : votes.ToString();
} }
} }
@@ -129,64 +126,72 @@ namespace Barotrauma
UpdateVoteTexts(connectedClients, VoteType.Sub); UpdateVoteTexts(connectedClients, VoteType.Sub);
} }
public void ClientWrite(IWriteMessage msg, VoteType voteType, object data) /// <summary>
/// Returns true if the given data is valid for the given vote type,
/// returns false otherwise. If it returns false, the message must
/// be discarded or reset by the caller, as it is now malformed :)
/// </summary>
public bool ClientWrite(IWriteMessage msg, VoteType voteType, object data)
{ {
msg.WriteByte((byte)voteType); msg.WriteByte((byte)voteType);
switch (voteType) switch (voteType)
{ {
case VoteType.Sub: case VoteType.Sub:
if (!(data is SubmarineInfo sub)) { return; } if (data is not SubmarineInfo sub) { return false; }
msg.WriteInt32(sub.EqualityCheckVal); msg.WriteInt32(sub.EqualityCheckVal);
if (sub.EqualityCheckVal == 0) if (sub.EqualityCheckVal <= 0)
{ {
//sub doesn't exist client-side, use hash to let the server know which one we voted for //sub doesn't exist client-side, use hash to let the server know which one we voted for
msg.WriteString(sub.MD5Hash.StringRepresentation); msg.WriteString(sub.MD5Hash.StringRepresentation);
} }
break; break;
case VoteType.Mode: case VoteType.Mode:
if (!(data is GameModePreset gameMode)) { return; } if (data is not GameModePreset gameMode) { return false; }
msg.WriteIdentifier(gameMode.Identifier); msg.WriteIdentifier(gameMode.Identifier);
break; break;
case VoteType.EndRound: case VoteType.EndRound:
if (!(data is bool)) { return; } if (data is not bool endRound) { return false; }
msg.WriteBoolean((bool)data); msg.WriteBoolean(endRound);
break; break;
case VoteType.Kick: case VoteType.Kick:
if (!(data is Client votedClient)) { return; } if (data is not Client votedClient) { return false; }
msg.WriteByte(votedClient.SessionId); msg.WriteByte(votedClient.SessionId);
break; break;
case VoteType.StartRound: case VoteType.StartRound:
if (!(data is bool)) { return; } if (data is not bool startRound) { return false; }
msg.WriteBoolean((bool)data); msg.WriteBoolean(startRound);
break; break;
case VoteType.PurchaseAndSwitchSub: case VoteType.PurchaseAndSwitchSub:
case VoteType.PurchaseSub: case VoteType.PurchaseSub:
case VoteType.SwitchSub: case VoteType.SwitchSub:
if (data is (SubmarineInfo voteSub, bool transferItems)) switch (data)
{ {
//initiate sub vote case (SubmarineInfo voteSub, bool transferItems):
msg.WriteBoolean(true); //initiate sub vote
msg.WriteString(voteSub.Name); msg.WriteBoolean(true);
msg.WriteBoolean(transferItems); msg.WriteString(voteSub.Name);
} msg.WriteBoolean(transferItems);
else break;
{ case int vote:
// vote // vote
if (!(data is int)) { return; } msg.WriteBoolean(false);
msg.WriteBoolean(false); msg.WriteInt32(vote);
msg.WriteInt32((int)data); break;
default:
return false;
} }
break; break;
case VoteType.TransferMoney: case VoteType.TransferMoney:
if (!(data is int)) { return; } if (data is not int money) { return false; }
msg.WriteBoolean(false); //not initiating a vote msg.WriteBoolean(false); //not initiating a vote
msg.WriteInt32((int)data); msg.WriteInt32(money);
break; break;
} }
msg.WritePadBits(); msg.WritePadBits();
return true;
} }
public void ClientRead(IReadMessage inc) public void ClientRead(IReadMessage inc)
@@ -325,13 +330,12 @@ namespace Barotrauma
string subName2 = inc.ReadString(); string subName2 = inc.ReadString();
var submarineInfo = GameMain.GameSession.OwnedSubmarines.FirstOrDefault(s => s.Name == subName2) ?? GameMain.Client.ServerSubmarines.FirstOrDefault(s => s.Name == subName2); var submarineInfo = GameMain.GameSession.OwnedSubmarines.FirstOrDefault(s => s.Name == subName2) ?? GameMain.Client.ServerSubmarines.FirstOrDefault(s => s.Name == subName2);
bool transferItems = inc.ReadBoolean(); bool transferItems = inc.ReadBoolean();
int deliveryFee = inc.ReadInt16();
if (submarineInfo == null) if (submarineInfo == null)
{ {
DebugConsole.ThrowError("Failed to find a matching submarine, vote aborted"); DebugConsole.ThrowError("Failed to find a matching submarine, vote aborted");
return; return;
} }
submarineVoteInfo = new SubmarineVoteInfo(submarineInfo, transferItems, deliveryFee); submarineVoteInfo = new SubmarineVoteInfo(submarineInfo, transferItems);
break; break;
} }
@@ -343,13 +347,13 @@ namespace Barotrauma
{ {
case VoteType.PurchaseAndSwitchSub: case VoteType.PurchaseAndSwitchSub:
GameMain.GameSession.PurchaseSubmarine(subInfo); GameMain.GameSession.PurchaseSubmarine(subInfo);
GameMain.GameSession.SwitchSubmarine(subInfo, submarineVoteInfo.TransferItems, 0); GameMain.GameSession.SwitchSubmarine(subInfo, submarineVoteInfo.TransferItems);
break; break;
case VoteType.PurchaseSub: case VoteType.PurchaseSub:
GameMain.GameSession.PurchaseSubmarine(subInfo); GameMain.GameSession.PurchaseSubmarine(subInfo);
break; break;
case VoteType.SwitchSub: case VoteType.SwitchSub:
GameMain.GameSession.SwitchSubmarine(subInfo, submarineVoteInfo.TransferItems, submarineVoteInfo.DeliveryFee); GameMain.GameSession.SwitchSubmarine(subInfo, submarineVoteInfo.TransferItems);
break; break;
} }
@@ -11,15 +11,13 @@ namespace Barotrauma
public enum MouseButton public enum MouseButton
{ {
None = -1, None = -1,
LeftMouse = 0, PrimaryMouse = 0,
RightMouse = 1, SecondaryMouse = 1,
MiddleMouse = 2, MiddleMouse = 2,
MouseButton4 = 3, MouseButton4 = 3,
MouseButton5 = 4, MouseButton5 = 4,
MouseWheelUp = 5, MouseWheelUp = 5,
MouseWheelDown = 6, MouseWheelDown = 6
PrimaryMouse,
SecondaryMouse
} }
public class KeyOrMouse public class KeyOrMouse
@@ -65,10 +63,6 @@ namespace Barotrauma
return PlayerInput.PrimaryMouseButtonHeld(); return PlayerInput.PrimaryMouseButtonHeld();
case MouseButton.SecondaryMouse: case MouseButton.SecondaryMouse:
return PlayerInput.SecondaryMouseButtonHeld(); return PlayerInput.SecondaryMouseButtonHeld();
case MouseButton.LeftMouse:
return PlayerInput.LeftButtonHeld();
case MouseButton.RightMouse:
return PlayerInput.RightButtonHeld();
case MouseButton.MiddleMouse: case MouseButton.MiddleMouse:
return PlayerInput.MidButtonHeld(); return PlayerInput.MidButtonHeld();
case MouseButton.MouseButton4: case MouseButton.MouseButton4:
@@ -95,10 +89,6 @@ namespace Barotrauma
return PlayerInput.PrimaryMouseButtonClicked(); return PlayerInput.PrimaryMouseButtonClicked();
case MouseButton.SecondaryMouse: case MouseButton.SecondaryMouse:
return PlayerInput.SecondaryMouseButtonClicked(); return PlayerInput.SecondaryMouseButtonClicked();
case MouseButton.LeftMouse:
return PlayerInput.LeftButtonClicked();
case MouseButton.RightMouse:
return PlayerInput.RightButtonClicked();
case MouseButton.MiddleMouse: case MouseButton.MiddleMouse:
return PlayerInput.MidButtonClicked(); return PlayerInput.MidButtonClicked();
case MouseButton.MouseButton4: case MouseButton.MouseButton4:
@@ -218,11 +208,11 @@ namespace Barotrauma
switch (MouseButton) switch (MouseButton)
{ {
case MouseButton.PrimaryMouse: case MouseButton.PrimaryMouse:
return PlayerInput.MouseButtonsSwapped() ? TextManager.Get("input.rightmouse") : TextManager.Get("input.leftmouse"); return PlayerInput.PrimaryMouseLabel;
case MouseButton.SecondaryMouse: case MouseButton.SecondaryMouse:
return PlayerInput.MouseButtonsSwapped() ? TextManager.Get("input.leftmouse") : TextManager.Get("input.rightmouse"); return PlayerInput.SecondaryMouseLabel;
default: default:
return TextManager.Get("input." + MouseButton.ToString().ToLowerInvariant()); return TextManager.Get($"Input.{MouseButton}");
} }
} }
else else
@@ -270,6 +260,9 @@ namespace Barotrauma
} }
#endif #endif
public static readonly LocalizedString PrimaryMouseLabel = TextManager.Get($"Input.{(!MouseButtonsSwapped() ? "Left" : "Right")}Mouse");
public static readonly LocalizedString SecondaryMouseLabel = TextManager.Get($"Input.{(!MouseButtonsSwapped() ? "Right" : "Left")}Mouse");
public static Vector2 MousePosition public static Vector2 MousePosition
{ {
get { return new Vector2(mouseState.Position.X, mouseState.Position.Y); } get { return new Vector2(mouseState.Position.X, mouseState.Position.Y); }
@@ -317,120 +310,48 @@ namespace Barotrauma
} }
public static bool PrimaryMouseButtonHeld() public static bool PrimaryMouseButtonHeld()
{
if (MouseButtonsSwapped())
{
return RightButtonHeld();
}
return LeftButtonHeld();
}
public static bool PrimaryMouseButtonDown()
{
if (MouseButtonsSwapped())
{
return RightButtonDown();
}
return LeftButtonDown();
}
public static bool PrimaryMouseButtonReleased()
{
if (MouseButtonsSwapped())
{
return RightButtonReleased();
}
return LeftButtonReleased();
}
public static bool PrimaryMouseButtonClicked()
{
if (MouseButtonsSwapped())
{
return RightButtonClicked();
}
return LeftButtonClicked();
}
public static bool SecondaryMouseButtonHeld()
{
if (!MouseButtonsSwapped())
{
return RightButtonHeld();
}
return LeftButtonHeld();
}
public static bool SecondaryMouseButtonDown()
{
if (!MouseButtonsSwapped())
{
return RightButtonDown();
}
return LeftButtonDown();
}
public static bool SecondaryMouseButtonReleased()
{
if (!MouseButtonsSwapped())
{
return RightButtonReleased();
}
return LeftButtonReleased();
}
public static bool SecondaryMouseButtonClicked()
{
if (!MouseButtonsSwapped())
{
return RightButtonClicked();
}
return LeftButtonClicked();
}
public static bool LeftButtonHeld()
{ {
return AllowInput && mouseState.LeftButton == ButtonState.Pressed; return AllowInput && mouseState.LeftButton == ButtonState.Pressed;
} }
public static bool LeftButtonDown() public static bool PrimaryMouseButtonDown()
{ {
return AllowInput && return AllowInput &&
oldMouseState.LeftButton == ButtonState.Released && oldMouseState.LeftButton == ButtonState.Released &&
mouseState.LeftButton == ButtonState.Pressed; mouseState.LeftButton == ButtonState.Pressed;
} }
public static bool LeftButtonReleased() public static bool PrimaryMouseButtonReleased()
{ {
return AllowInput && mouseState.LeftButton == ButtonState.Released; return AllowInput && mouseState.LeftButton == ButtonState.Released;
} }
public static bool LeftButtonClicked() public static bool PrimaryMouseButtonClicked()
{ {
return (AllowInput && return (AllowInput &&
oldMouseState.LeftButton == ButtonState.Pressed oldMouseState.LeftButton == ButtonState.Pressed
&& mouseState.LeftButton == ButtonState.Released); && mouseState.LeftButton == ButtonState.Released);
} }
public static bool RightButtonHeld() public static bool SecondaryMouseButtonHeld()
{ {
return AllowInput && mouseState.RightButton == ButtonState.Pressed; return AllowInput && mouseState.RightButton == ButtonState.Pressed;
} }
public static bool RightButtonDown() public static bool SecondaryMouseButtonDown()
{ {
return AllowInput && return AllowInput &&
oldMouseState.RightButton == ButtonState.Released && oldMouseState.RightButton == ButtonState.Released &&
mouseState.RightButton == ButtonState.Pressed; mouseState.RightButton == ButtonState.Pressed;
} }
public static bool RightButtonReleased() public static bool SecondaryMouseButtonReleased()
{ {
return AllowInput && mouseState.RightButton == ButtonState.Released; return AllowInput && mouseState.RightButton == ButtonState.Released;
} }
public static bool RightButtonClicked() public static bool SecondaryMouseButtonClicked()
{ {
return (AllowInput && return (AllowInput &&
oldMouseState.RightButton == ButtonState.Pressed oldMouseState.RightButton == ButtonState.Pressed
@@ -23,15 +23,12 @@ namespace Barotrauma
ScrollBarEnabled = false, ScrollBarEnabled = false,
AllowMouseWheelScroll = false AllowMouseWheelScroll = false
}; };
new GUIButton(new RectTransform(new Vector2(0.1f), creditsPlayer.RectTransform, Anchor.BottomRight, maxSize: new Point(300, 50)) { AbsoluteOffset = new Point(GUI.IntScale(20)) }, creditsPlayer.CloseButton.OnClicked = (btn, userdata) =>
TextManager.Get("close"))
{ {
OnClicked = (btn, userdata) => creditsPlayer.Scroll = 1.0f;
{ return true;
creditsPlayer.Scroll = 1.0f;
return true;
}
}; };
cam = new Camera(); cam = new Camera();
} }
@@ -44,7 +41,16 @@ namespace Barotrauma
} }
creditsPlayer.Restart(); creditsPlayer.Restart();
creditsPlayer.Visible = false; creditsPlayer.Visible = false;
SteamAchievementManager.UnlockAchievement("campaigncompleted".ToIdentifier(), unlockClients: true); UnlockAchievement("campaigncompleted");
UnlockAchievement(
GameMain.GameSession is { Campaign.Settings.RadiationEnabled: true } ?
"campaigncompleted_radiationenabled" :
"campaigncompleted_radiationdisabled");
static void UnlockAchievement(string id)
{
SteamAchievementManager.UnlockAchievement(id.ToIdentifier(), unlockClients: true);
}
} }
public override void Deselect() public override void Deselect()
@@ -4,6 +4,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using Barotrauma.IO; using Barotrauma.IO;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Globalization;
using System.Linq; using System.Linq;
using System.Xml.Linq; using System.Xml.Linq;
@@ -37,7 +38,6 @@ namespace Barotrauma
protected set; protected set;
} }
public CampaignSettings CurrentSettings = new CampaignSettings(element: null);
public GUIButton CampaignCustomizeButton { get; set; } public GUIButton CampaignCustomizeButton { get; set; }
public GUIMessageBox CampaignCustomizeSettings { get; set; } public GUIMessageBox CampaignCustomizeSettings { get; set; }
@@ -58,7 +58,7 @@ namespace Barotrauma
var saveFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.1f), saveList.Content.RectTransform) { MinSize = new Point(0, 45) }, style: "ListBoxElement") var saveFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.1f), saveList.Content.RectTransform) { MinSize = new Point(0, 45) }, style: "ListBoxElement")
{ {
UserData = saveInfo.FilePath UserData = saveInfo
}; };
var nameText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), saveFrame.RectTransform), Path.GetFileNameWithoutExtension(saveInfo.FilePath), var nameText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), saveFrame.RectTransform), Path.GetFileNameWithoutExtension(saveInfo.FilePath),
@@ -87,10 +87,9 @@ namespace Barotrauma
}; };
string saveTimeStr = string.Empty; string saveTimeStr = string.Empty;
if (saveInfo.SaveTime > 0) if (saveInfo.SaveTime.TryUnwrap(out var time))
{ {
DateTime time = ToolBox.Epoch.ToDateTime(saveInfo.SaveTime); saveTimeStr = time.ToLocalUserString();
saveTimeStr = time.ToString();
} }
new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), saveFrame.RectTransform), new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), saveFrame.RectTransform),
text: saveTimeStr, textAlignment: Alignment.Right, font: GUIStyle.SmallFont) text: saveTimeStr, textAlignment: Alignment.Right, font: GUIStyle.SmallFont)
@@ -102,8 +101,29 @@ namespace Barotrauma
return saveFrame; return saveFrame;
} }
protected void SortSaveList()
{
saveList.Content.RectTransform.SortChildren((c1, c2) =>
{
if (c1.GUIComponent.UserData is not CampaignMode.SaveInfo file1
|| c2.GUIComponent.UserData is not CampaignMode.SaveInfo file2)
{
return 0;
}
if (!file1.SaveTime.TryUnwrap(out var file1WriteTime)
|| !file2.SaveTime.TryUnwrap(out var file2WriteTime))
{
return 0;
}
return file2WriteTime.CompareTo(file1WriteTime);
});
}
public struct CampaignSettingElements public struct CampaignSettingElements
{ {
public SettingValue<string> SelectedPreset;
public SettingValue<bool> TutorialEnabled; public SettingValue<bool> TutorialEnabled;
public SettingValue<bool> RadiationEnabled; public SettingValue<bool> RadiationEnabled;
public SettingValue<int> MaxMissionCount; public SettingValue<int> MaxMissionCount;
@@ -115,6 +135,7 @@ namespace Barotrauma
{ {
return new CampaignSettings(element: null) return new CampaignSettings(element: null)
{ {
PresetName = SelectedPreset.GetValue(),
TutorialEnabled = TutorialEnabled.GetValue(), TutorialEnabled = TutorialEnabled.GetValue(),
RadiationEnabled = RadiationEnabled.GetValue(), RadiationEnabled = RadiationEnabled.GetValue(),
MaxMissionCount = MaxMissionCount.GetValue(), MaxMissionCount = MaxMissionCount.GetValue(),
@@ -165,9 +186,13 @@ namespace Barotrauma
{ {
const float verticalSize = 0.14f; const float verticalSize = 0.14f;
bool loadingPreset = false;
GUILayoutGroup presetDropdownLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, verticalSize), parent.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft); GUILayoutGroup presetDropdownLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, verticalSize), parent.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft);
new GUITextBlock(new RectTransform(new Vector2(0.5f, 1f), presetDropdownLayout.RectTransform), TextManager.Get("campaignsettingpreset")); new GUITextBlock(new RectTransform(new Vector2(0.5f, 1f), presetDropdownLayout.RectTransform), TextManager.Get("campaignsettingpreset"));
GUIDropDown presetDropdown = new GUIDropDown(new RectTransform(new Vector2(0.5f, 1f), presetDropdownLayout.RectTransform), elementCount: CampaignModePresets.List.Length); GUIDropDown presetDropdown = new GUIDropDown(new RectTransform(new Vector2(0.5f, 1f), presetDropdownLayout.RectTransform), elementCount: CampaignModePresets.List.Length + 1);
presetDropdown.AddItem(TextManager.Get("karmapreset.custom"), null);
presetDropdown.Select(0);
presetDropdownLayout.RectTransform.MinSize = new Point(0, presetDropdown.Rect.Height); presetDropdownLayout.RectTransform.MinSize = new Point(0, presetDropdown.Rect.Height);
@@ -175,21 +200,30 @@ namespace Barotrauma
{ {
string name = settings.PresetName; string name = settings.PresetName;
presetDropdown.AddItem(TextManager.Get($"preset.{name}").Fallback(name), settings); presetDropdown.AddItem(TextManager.Get($"preset.{name}").Fallback(name), settings);
if (settings.PresetName.Equals(prevSettings.PresetName, StringComparison.OrdinalIgnoreCase))
{
presetDropdown.SelectItem(settings);
}
} }
var presetValue = new SettingValue<string>(
get: () => presetDropdown.SelectedData is CampaignSettings settings ? settings.PresetName : string.Empty,
set: static _ => { }); // we do not need a way to set this value
GUIListBox settingsList = new GUIListBox(new RectTransform(new Vector2(1f, 1f - verticalSize), parent.RectTransform)) GUIListBox settingsList = new GUIListBox(new RectTransform(new Vector2(1f, 1f - verticalSize), parent.RectTransform))
{ {
Spacing = GUI.IntScale(5) Spacing = GUI.IntScale(5)
}; };
SettingValue<bool> tutorialEnabled = isSinglePlayer ? SettingValue<bool> tutorialEnabled = isSinglePlayer ?
CreateTickbox(settingsList.Content, TextManager.Get("CampaignOption.EnableTutorial"), TextManager.Get("campaignoption.enabletutorial.tooltip"), prevSettings.TutorialEnabled, verticalSize) : CreateTickbox(settingsList.Content, TextManager.Get("CampaignOption.EnableTutorial"), TextManager.Get("campaignoption.enabletutorial.tooltip"), prevSettings.TutorialEnabled, verticalSize, OnValuesChanged) :
new SettingValue<bool>(() => false, b => { }); new SettingValue<bool>(static () => false, static _ => { });
SettingValue<bool> radiationEnabled = CreateTickbox(settingsList.Content, TextManager.Get("CampaignOption.EnableRadiation"), TextManager.Get("campaignoption.enableradiation.tooltip"), prevSettings.RadiationEnabled, verticalSize); SettingValue<bool> radiationEnabled = CreateTickbox(settingsList.Content, TextManager.Get("CampaignOption.EnableRadiation"), TextManager.Get("campaignoption.enableradiation.tooltip"), prevSettings.RadiationEnabled, verticalSize, OnValuesChanged);
ImmutableArray<SettingCarouselElement<Identifier>> startingSetOptions = StartItemSet.Sets.OrderBy(s => s.Order).Select(set => new SettingCarouselElement<Identifier>(set.Identifier, $"startitemset.{set.Identifier}")).ToImmutableArray(); ImmutableArray<SettingCarouselElement<Identifier>> startingSetOptions = StartItemSet.Sets.OrderBy(s => s.Order).Select(set => new SettingCarouselElement<Identifier>(set.Identifier, $"startitemset.{set.Identifier}")).ToImmutableArray();
SettingCarouselElement<Identifier> prevStartingSet = startingSetOptions.FirstOrNull(element => element.Value == prevSettings.StartItemSet) ?? startingSetOptions[1]; SettingCarouselElement<Identifier> prevStartingSet = startingSetOptions.FirstOrNull(element => element.Value == prevSettings.StartItemSet) ?? startingSetOptions[1];
SettingValue<Identifier> startingSetInput = CreateSelectionCarousel(settingsList.Content, TextManager.Get("startitemset"), TextManager.Get("startitemsettooltip"), prevStartingSet, verticalSize, startingSetOptions); SettingValue<Identifier> startingSetInput = CreateSelectionCarousel(settingsList.Content, TextManager.Get("startitemset"), TextManager.Get("startitemsettooltip"), prevStartingSet, verticalSize, startingSetOptions, OnValuesChanged);
ImmutableArray<SettingCarouselElement<StartingBalanceAmount>> fundOptions = ImmutableArray.Create( ImmutableArray<SettingCarouselElement<StartingBalanceAmount>> fundOptions = ImmutableArray.Create(
new SettingCarouselElement<StartingBalanceAmount>(StartingBalanceAmount.Low, "startingfunds.low"), new SettingCarouselElement<StartingBalanceAmount>(StartingBalanceAmount.Low, "startingfunds.low"),
@@ -198,7 +232,7 @@ namespace Barotrauma
); );
SettingCarouselElement<StartingBalanceAmount> prevStartingFund = fundOptions.FirstOrNull(element => element.Value == prevSettings.StartingBalanceAmount) ?? fundOptions[1]; SettingCarouselElement<StartingBalanceAmount> prevStartingFund = fundOptions.FirstOrNull(element => element.Value == prevSettings.StartingBalanceAmount) ?? fundOptions[1];
SettingValue<StartingBalanceAmount> startingFundsInput = CreateSelectionCarousel(settingsList.Content, TextManager.Get("startingfundsdescription"), TextManager.Get("startingfundstooltip"), prevStartingFund, verticalSize, fundOptions); SettingValue<StartingBalanceAmount> startingFundsInput = CreateSelectionCarousel(settingsList.Content, TextManager.Get("startingfundsdescription"), TextManager.Get("startingfundstooltip"), prevStartingFund, verticalSize, fundOptions, OnValuesChanged);
ImmutableArray<SettingCarouselElement<GameDifficulty>> difficultyOptions = ImmutableArray.Create( ImmutableArray<SettingCarouselElement<GameDifficulty>> difficultyOptions = ImmutableArray.Create(
new SettingCarouselElement<GameDifficulty>(GameDifficulty.Easy, "difficulty.easy"), new SettingCarouselElement<GameDifficulty>(GameDifficulty.Easy, "difficulty.easy"),
@@ -208,30 +242,38 @@ namespace Barotrauma
); );
SettingCarouselElement<GameDifficulty> prevDifficulty = difficultyOptions.FirstOrNull(element => element.Value == prevSettings.Difficulty) ?? difficultyOptions[1]; SettingCarouselElement<GameDifficulty> prevDifficulty = difficultyOptions.FirstOrNull(element => element.Value == prevSettings.Difficulty) ?? difficultyOptions[1];
SettingValue<GameDifficulty> difficultyInput = CreateSelectionCarousel(settingsList.Content, TextManager.Get("leveldifficulty"), TextManager.Get("leveldifficultyexplanation"), prevDifficulty, verticalSize, difficultyOptions); SettingValue<GameDifficulty> difficultyInput = CreateSelectionCarousel(settingsList.Content, TextManager.Get("leveldifficulty"), TextManager.Get("leveldifficultyexplanation"), prevDifficulty, verticalSize, difficultyOptions, OnValuesChanged);
SettingValue<int> maxMissionCountInput = CreateGUINumberInputCarousel(settingsList.Content, TextManager.Get("maxmissioncount"), TextManager.Get("maxmissioncounttooltip"), SettingValue<int> maxMissionCountInput = CreateGUINumberInputCarousel(settingsList.Content, TextManager.Get("maxmissioncount"), TextManager.Get("maxmissioncounttooltip"),
prevSettings.MaxMissionCount, prevSettings.MaxMissionCount,
valueStep: 1, minValue: CampaignSettings.MinMissionCountLimit, maxValue: CampaignSettings.MaxMissionCountLimit, valueStep: 1, minValue: CampaignSettings.MinMissionCountLimit, maxValue: CampaignSettings.MaxMissionCountLimit,
verticalSize); verticalSize,
OnValuesChanged);
presetDropdown.OnSelected = (selected, o) => presetDropdown.OnSelected = (_, o) =>
{ {
if (o is CampaignSettings settings) if (o is not CampaignSettings settings) { return false; }
{
tutorialEnabled.SetValue(isSinglePlayer && settings.TutorialEnabled); loadingPreset = true;
radiationEnabled.SetValue(settings.RadiationEnabled); tutorialEnabled.SetValue(isSinglePlayer && settings.TutorialEnabled);
maxMissionCountInput.SetValue(settings.MaxMissionCount); radiationEnabled.SetValue(settings.RadiationEnabled);
startingFundsInput.SetValue(settings.StartingBalanceAmount); maxMissionCountInput.SetValue(settings.MaxMissionCount);
difficultyInput.SetValue(settings.Difficulty); startingFundsInput.SetValue(settings.StartingBalanceAmount);
startingSetInput.SetValue(settings.StartItemSet); difficultyInput.SetValue(settings.Difficulty);
return true; startingSetInput.SetValue(settings.StartItemSet);
} loadingPreset = false;
return false; return true;
}; };
void OnValuesChanged()
{
if (loadingPreset) { return; }
presetDropdown.Select(0);
}
return new CampaignSettingElements return new CampaignSettingElements
{ {
SelectedPreset = presetValue,
TutorialEnabled = tutorialEnabled, TutorialEnabled = tutorialEnabled,
RadiationEnabled = radiationEnabled, RadiationEnabled = radiationEnabled,
MaxMissionCount = maxMissionCountInput, MaxMissionCount = maxMissionCountInput,
@@ -241,7 +283,7 @@ namespace Barotrauma
}; };
// Create a number input with plus and minus buttons because for some reason the default GUINumberInput buttons don't work when in a GUIMessageBox // Create a number input with plus and minus buttons because for some reason the default GUINumberInput buttons don't work when in a GUIMessageBox
static SettingValue<int> CreateGUINumberInputCarousel(GUIComponent parent, LocalizedString description, LocalizedString tooltip, int defaultValue, int valueStep, int minValue, int maxValue, float verticalSize) static SettingValue<int> CreateGUINumberInputCarousel(GUIComponent parent, LocalizedString description, LocalizedString tooltip, int defaultValue, int valueStep, int minValue, int maxValue, float verticalSize, Action onChanged)
{ {
GUILayoutGroup inputContainer = CreateSettingBase(parent, description, tooltip, horizontalSize: 0.55f, verticalSize: verticalSize); GUILayoutGroup inputContainer = CreateSettingBase(parent, description, tooltip, horizontalSize: 0.55f, verticalSize: verticalSize);
@@ -266,9 +308,11 @@ namespace Barotrauma
minusButton.OnClicked = plusButton.OnClicked = ChangeValue; minusButton.OnClicked = plusButton.OnClicked = ChangeValue;
numberInput.OnValueChanged += _ => onChanged();
bool ChangeValue(GUIButton btn, object userData) bool ChangeValue(GUIButton btn, object userData)
{ {
if (!(userData is int change)) { return false; } if (userData is not int change) { return false; }
numberInput.IntValue += change; numberInput.IntValue += change;
return true; return true;
@@ -278,7 +322,7 @@ namespace Barotrauma
} }
static SettingValue<T> CreateSelectionCarousel<T>(GUIComponent parent, LocalizedString description, LocalizedString tooltip, SettingCarouselElement<T> defaultValue, float verticalSize, static SettingValue<T> CreateSelectionCarousel<T>(GUIComponent parent, LocalizedString description, LocalizedString tooltip, SettingCarouselElement<T> defaultValue, float verticalSize,
ImmutableArray<SettingCarouselElement<T>> options) ImmutableArray<SettingCarouselElement<T>> options, Action onChanged)
{ {
GUILayoutGroup inputContainer = CreateSettingBase(parent, description, tooltip, horizontalSize: 0.55f, verticalSize: verticalSize); GUILayoutGroup inputContainer = CreateSettingBase(parent, description, tooltip, horizontalSize: 0.55f, verticalSize: verticalSize);
@@ -329,6 +373,8 @@ namespace Barotrauma
return true; return true;
} }
numberInput.OnValueChanged += _ => onChanged();
void SetValue(int value) void SetValue(int value)
{ {
numberInput.IntValue = value; numberInput.IntValue = value;
@@ -338,7 +384,7 @@ namespace Barotrauma
return new SettingValue<T>(() => options[numberInput.IntValue].Value, t => SetValue(options.IndexOf(e => Equals(e.Value, t)))); return new SettingValue<T>(() => options[numberInput.IntValue].Value, t => SetValue(options.IndexOf(e => Equals(e.Value, t))));
} }
static SettingValue<bool> CreateTickbox(GUIComponent parent, LocalizedString description, LocalizedString tooltip, bool defaultValue, float verticalSize) static SettingValue<bool> CreateTickbox(GUIComponent parent, LocalizedString description, LocalizedString tooltip, bool defaultValue, float verticalSize, Action onChanged)
{ {
GUILayoutGroup inputContainer = CreateSettingBase(parent, description, tooltip, 0.7f, verticalSize); GUILayoutGroup inputContainer = CreateSettingBase(parent, description, tooltip, 0.7f, verticalSize);
GUILayoutGroup tickboxContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.3f, 1.0f), inputContainer.RectTransform), childAnchor: Anchor.Center); GUILayoutGroup tickboxContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.3f, 1.0f), inputContainer.RectTransform), childAnchor: Anchor.Center);
@@ -350,6 +396,13 @@ namespace Barotrauma
tickBox.Box.IgnoreLayoutGroups = true; tickBox.Box.IgnoreLayoutGroups = true;
tickBox.Box.RectTransform.SetPosition(Anchor.CenterRight); tickBox.Box.RectTransform.SetPosition(Anchor.CenterRight);
inputContainer.RectTransform.Parent.MinSize = new Point(0, tickBox.RectTransform.MinSize.Y); inputContainer.RectTransform.Parent.MinSize = new Point(0, tickBox.RectTransform.MinSize.Y);
tickBox.OnSelected += _ =>
{
onChanged();
return true;
};
return new SettingValue<bool>(() => tickBox.Selected, b => tickBox.Selected = b); return new SettingValue<bool>(() => tickBox.Selected, b => tickBox.Selected = b);
} }
@@ -367,5 +420,25 @@ namespace Barotrauma
return inputContainer; return inputContainer;
} }
} }
public abstract void UpdateLoadMenu(IEnumerable<CampaignMode.SaveInfo> saveFiles = null);
protected bool DeleteSave(GUIButton button, object obj)
{
if (obj is not CampaignMode.SaveInfo saveInfo) { return false; }
var header = TextManager.Get("deletedialoglabel");
var body = TextManager.GetWithVariable("deletedialogquestion", "[file]", Path.GetFileNameWithoutExtension(saveInfo.FilePath));
EventEditorScreen.AskForConfirmation(header, body, () =>
{
SaveUtil.DeleteSave(saveInfo.FilePath);
prevSaveFiles?.RemoveAll(s => s.FilePath == saveInfo.FilePath);
UpdateLoadMenu(prevSaveFiles.ToList());
return true;
});
return true;
}
} }
} }
@@ -192,7 +192,7 @@ namespace Barotrauma
yield return CoroutineStatus.Success; yield return CoroutineStatus.Success;
} }
public void UpdateLoadMenu(IEnumerable<CampaignMode.SaveInfo> saveFiles = null) public override void UpdateLoadMenu(IEnumerable<CampaignMode.SaveInfo> saveFiles = null)
{ {
prevSaveFiles?.Clear(); prevSaveFiles?.Clear();
prevSaveFiles = null; prevSaveFiles = null;
@@ -220,37 +220,16 @@ namespace Barotrauma
CreateSaveElement(saveInfo); CreateSaveElement(saveInfo);
} }
saveList.Content.RectTransform.SortChildren((c1, c2) => SortSaveList();
{
string file1 = c1.GUIComponent.UserData as string;
string file2 = c2.GUIComponent.UserData as string;
DateTime file1WriteTime = DateTime.MinValue;
DateTime file2WriteTime = DateTime.MinValue;
try
{
file1WriteTime = File.GetLastWriteTime(file1);
}
catch
{
//do nothing - DateTime.MinValue will be used and the element will get sorted at the bottom of the list
};
try
{
file2WriteTime = File.GetLastWriteTime(file2);
}
catch
{
//do nothing - DateTime.MinValue will be used and the element will get sorted at the bottom of the list
};
return file2WriteTime.CompareTo(file1WriteTime);
});
loadGameButton = new GUIButton(new RectTransform(new Vector2(0.45f, 0.12f), loadGameContainer.RectTransform, Anchor.BottomRight), TextManager.Get("LoadButton")) loadGameButton = new GUIButton(new RectTransform(new Vector2(0.45f, 0.12f), loadGameContainer.RectTransform, Anchor.BottomRight), TextManager.Get("LoadButton"))
{ {
OnClicked = (btn, obj) => OnClicked = (btn, obj) =>
{ {
if (string.IsNullOrWhiteSpace(saveList.SelectedData as string)) { return false; } if (saveList.SelectedData is not CampaignMode.SaveInfo saveInfo) { return false; }
LoadGame?.Invoke(saveList.SelectedData as string); if (string.IsNullOrWhiteSpace(saveInfo.FilePath)) { return false; }
LoadGame?.Invoke(saveInfo.FilePath);
CoroutineManager.StartCoroutine(WaitForCampaignSetup(), "WaitForCampaignSetup"); CoroutineManager.StartCoroutine(WaitForCampaignSetup(), "WaitForCampaignSetup");
return true; return true;
}, },
@@ -264,37 +243,20 @@ namespace Barotrauma
}; };
} }
private bool SelectSaveFile(GUIComponent component, object obj) private bool SelectSaveFile(GUIComponent component, object obj)
{ {
string fileName = (string)obj; if (obj is not CampaignMode.SaveInfo saveInfo) { return true; }
string fileName = saveInfo.FilePath;
loadGameButton.Enabled = true; loadGameButton.Enabled = true;
deleteMpSaveButton.Visible = deleteMpSaveButton.Enabled = GameMain.Client.IsServerOwner; deleteMpSaveButton.Visible = deleteMpSaveButton.Enabled = GameMain.Client.IsServerOwner;
deleteMpSaveButton.Enabled = GameMain.GameSession?.SavePath != fileName; deleteMpSaveButton.Enabled = GameMain.GameSession?.SavePath != fileName;
if (deleteMpSaveButton.Visible) if (deleteMpSaveButton.Visible)
{ {
deleteMpSaveButton.UserData = obj as string; deleteMpSaveButton.UserData = saveInfo;
} }
return true; return true;
} }
private bool DeleteSave(GUIButton button, object obj)
{
string saveFile = obj as string;
if (obj == null) { return false; }
var header = TextManager.Get("deletedialoglabel");
var body = TextManager.GetWithVariable("deletedialogquestion", "[file]", Path.GetFileNameWithoutExtension(saveFile));
EventEditorScreen.AskForConfirmation(header, body, () =>
{
SaveUtil.DeleteSave(saveFile);
prevSaveFiles?.RemoveAll(s => s.FilePath == saveFile);
UpdateLoadMenu(prevSaveFiles.ToList());
return true;
});
return true;
}
} }
} }
@@ -194,7 +194,7 @@ namespace Barotrauma
{ {
TextGetter = () => TextGetter = () =>
{ {
int initialMoney = CurrentSettings.InitialMoney; int initialMoney = CampaignSettings.CurrentSettings.InitialMoney;
if (subList.SelectedData is SubmarineInfo subInfo) if (subList.SelectedData is SubmarineInfo subInfo)
{ {
initialMoney -= subInfo.Price; initialMoney -= subInfo.Price;
@@ -208,15 +208,15 @@ namespace Barotrauma
{ {
OnClicked = (tb, userdata) => OnClicked = (tb, userdata) =>
{ {
CreateCustomizeWindow(CurrentSettings, settings => CreateCustomizeWindow(CampaignSettings.CurrentSettings, settings =>
{ {
CampaignSettings prevSettings = CurrentSettings; CampaignSettings prevSettings = CampaignSettings.CurrentSettings;
CurrentSettings = settings; CampaignSettings.CurrentSettings = settings;
if (prevSettings.InitialMoney != settings.InitialMoney) if (prevSettings.InitialMoney != settings.InitialMoney)
{ {
object selectedData = subList.SelectedData; object selectedData = subList.SelectedData;
UpdateSubList(SubmarineInfo.SavedSubmarines); UpdateSubList(SubmarineInfo.SavedSubmarines);
if (selectedData is SubmarineInfo selectedSub && selectedSub.Price <= CurrentSettings.InitialMoney) if (selectedData is SubmarineInfo selectedSub && selectedSub.Price <= CampaignSettings.CurrentSettings.InitialMoney)
{ {
subList.Select(selectedData); subList.Select(selectedData);
} }
@@ -375,6 +375,7 @@ namespace Barotrauma
{ {
onClosed?.Invoke(elements.CreateSettings()); onClosed?.Invoke(elements.CreateSettings());
GameSettings.SaveCurrentConfig();
return CampaignCustomizeSettings.Close(button, o); return CampaignCustomizeSettings.Close(button, o);
}; };
} }
@@ -399,7 +400,7 @@ namespace Barotrauma
SubmarineInfo selectedSub = null; SubmarineInfo selectedSub = null;
if (!(subList.SelectedData is SubmarineInfo)) { return false; } if (subList.SelectedData is not SubmarineInfo) { return false; }
selectedSub = subList.SelectedData as SubmarineInfo; selectedSub = subList.SelectedData as SubmarineInfo;
if (selectedSub.SubmarineClass == SubmarineClass.Undefined) if (selectedSub.SubmarineClass == SubmarineClass.Undefined)
@@ -419,7 +420,7 @@ namespace Barotrauma
string savePath = SaveUtil.CreateSavePath(SaveUtil.SaveType.Singleplayer, saveNameBox.Text); string savePath = SaveUtil.CreateSavePath(SaveUtil.SaveType.Singleplayer, saveNameBox.Text);
bool hasRequiredContentPackages = selectedSub.RequiredContentPackagesInstalled; bool hasRequiredContentPackages = selectedSub.RequiredContentPackagesInstalled;
CampaignSettings settings = CurrentSettings; CampaignSettings settings = CampaignSettings.CurrentSettings;
if (selectedSub.HasTag(SubmarineTag.Shuttle) || !hasRequiredContentPackages) if (selectedSub.HasTag(SubmarineTag.Shuttle) || !hasRequiredContentPackages)
{ {
@@ -476,7 +477,7 @@ namespace Barotrauma
{ {
foreach (GUIComponent child in subList.Content.Children) foreach (GUIComponent child in subList.Content.Children)
{ {
if (!(child.UserData is SubmarineInfo sub)) { return; } if (child.UserData is not SubmarineInfo sub) { return; }
child.Visible = string.IsNullOrEmpty(filter) || sub.DisplayName.Contains(filter.ToLower(), StringComparison.OrdinalIgnoreCase); child.Visible = string.IsNullOrEmpty(filter) || sub.DisplayName.Contains(filter.ToLower(), StringComparison.OrdinalIgnoreCase);
} }
} }
@@ -487,9 +488,9 @@ namespace Barotrauma
(subPreviewContainer.Parent as GUILayoutGroup)?.Recalculate(); (subPreviewContainer.Parent as GUILayoutGroup)?.Recalculate();
subPreviewContainer.ClearChildren(); subPreviewContainer.ClearChildren();
if (!(obj is SubmarineInfo sub)) { return true; } if (obj is not SubmarineInfo sub) { return true; }
#if !DEBUG #if !DEBUG
if (sub.Price > CurrentSettings.InitialMoney && !GameMain.DebugDraw) if (sub.Price > CampaignSettings.CurrentSettings.InitialMoney && !GameMain.DebugDraw)
{ {
SetPage(0); SetPage(0);
nextButton.Enabled = false; nextButton.Enabled = false;
@@ -551,7 +552,7 @@ namespace Barotrauma
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), infoContainer.RectTransform), new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), infoContainer.RectTransform),
TextManager.GetWithVariable("currencyformat", "[credits]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", sub.Price)), textAlignment: Alignment.BottomRight, font: GUIStyle.SmallFont) TextManager.GetWithVariable("currencyformat", "[credits]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", sub.Price)), textAlignment: Alignment.BottomRight, font: GUIStyle.SmallFont)
{ {
TextColor = sub.Price > CurrentSettings.InitialMoney ? GUIStyle.Red : textBlock.TextColor * 0.8f, TextColor = sub.Price > CampaignSettings.CurrentSettings.InitialMoney ? GUIStyle.Red : textBlock.TextColor * 0.8f,
ToolTip = textBlock.ToolTip ToolTip = textBlock.ToolTip
}; };
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), infoContainer.RectTransform), new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), infoContainer.RectTransform),
@@ -563,7 +564,7 @@ namespace Barotrauma
#if !DEBUG #if !DEBUG
if (!GameMain.DebugDraw) if (!GameMain.DebugDraw)
{ {
if (sub.Price > CurrentSettings.InitialMoney || !sub.IsCampaignCompatible) if (sub.Price > CampaignSettings.CurrentSettings.InitialMoney || !sub.IsCampaignCompatible)
{ {
textBlock.CanBeFocused = false; textBlock.CanBeFocused = false;
textBlock.TextColor *= 0.5f; textBlock.TextColor *= 0.5f;
@@ -573,7 +574,7 @@ namespace Barotrauma
} }
if (SubmarineInfo.SavedSubmarines.Any()) if (SubmarineInfo.SavedSubmarines.Any())
{ {
var validSubs = subsToShow.Where(s => s.IsCampaignCompatible && s.Price <= CurrentSettings.InitialMoney).ToList(); var validSubs = subsToShow.Where(s => s.IsCampaignCompatible && s.Price <= CampaignSettings.CurrentSettings.InitialMoney).ToList();
if (validSubs.Count > 0) if (validSubs.Count > 0)
{ {
subList.Select(validSubs[Rand.Int(validSubs.Count)]); subList.Select(validSubs[Rand.Int(validSubs.Count)]);
@@ -581,7 +582,7 @@ namespace Barotrauma
} }
} }
public void UpdateLoadMenu(IEnumerable<CampaignMode.SaveInfo> saveFiles = null) public override void UpdateLoadMenu(IEnumerable<CampaignMode.SaveInfo> saveFiles = null)
{ {
prevSaveFiles?.Clear(); prevSaveFiles?.Clear();
prevSaveFiles = null; prevSaveFiles = null;
@@ -649,37 +650,16 @@ namespace Barotrauma
} }
} }
saveList.Content.RectTransform.SortChildren((c1, c2) => SortSaveList();
{
string file1 = c1.GUIComponent.UserData as string;
string file2 = c2.GUIComponent.UserData as string;
DateTime file1WriteTime = DateTime.MinValue;
DateTime file2WriteTime = DateTime.MinValue;
try
{
file1WriteTime = File.GetLastWriteTime(file1);
}
catch
{
//do nothing - DateTime.MinValue will be used and the element will get sorted at the bottom of the list
};
try
{
file2WriteTime = File.GetLastWriteTime(file2);
}
catch
{
//do nothing - DateTime.MinValue will be used and the element will get sorted at the bottom of the list
};
return file2WriteTime.CompareTo(file1WriteTime);
});
loadGameButton = new GUIButton(new RectTransform(new Vector2(0.45f, 0.12f), loadGameContainer.RectTransform, Anchor.BottomRight), TextManager.Get("LoadButton")) loadGameButton = new GUIButton(new RectTransform(new Vector2(0.45f, 0.12f), loadGameContainer.RectTransform, Anchor.BottomRight), TextManager.Get("LoadButton"))
{ {
OnClicked = (btn, obj) => OnClicked = (btn, obj) =>
{ {
if (string.IsNullOrWhiteSpace(saveList.SelectedData as string)) { return false; } if (saveList.SelectedData is not CampaignMode.SaveInfo saveInfo) { return false; }
LoadGame?.Invoke(saveList.SelectedData as string); if (string.IsNullOrWhiteSpace(saveInfo.FilePath)) { return false; }
LoadGame?.Invoke(saveInfo.FilePath);
return true; return true;
}, },
Enabled = false Enabled = false
@@ -688,7 +668,9 @@ namespace Barotrauma
private bool SelectSaveFile(GUIComponent component, object obj) private bool SelectSaveFile(GUIComponent component, object obj)
{ {
string fileName = (string)obj; if (obj is not CampaignMode.SaveInfo saveInfo) { return true; }
string fileName = saveInfo.FilePath;
XDocument doc = SaveUtil.LoadGameSessionDoc(fileName); XDocument doc = SaveUtil.LoadGameSessionDoc(fileName);
if (doc?.Root == null) if (doc?.Root == null)
@@ -701,72 +683,55 @@ namespace Barotrauma
RemoveSaveFrame(); RemoveSaveFrame();
string subName = doc.Root.GetAttributeString("submarine", ""); string subName = saveInfo.SubmarineName;
string saveTime = doc.Root.GetAttributeString("savetime", "unknown"); LocalizedString saveTime = saveInfo.SaveTime
DateTime? time = null; .Select(t => (LocalizedString)t.ToLocalUserString())
if (long.TryParse(saveTime, out long unixTime)) .Fallback(TextManager.Get("Unknown"));
{
time = ToolBox.Epoch.ToDateTime(unixTime);
saveTime = time.ToString();
}
string mapseed = doc.Root.GetAttributeString("mapseed", "unknown"); string mapseed = doc.Root.GetAttributeString("mapseed", "unknown");
var saveFileFrame = new GUIFrame(new RectTransform(new Vector2(0.45f, 0.6f), loadGameContainer.RectTransform, Anchor.TopRight) var saveFileFrame = new GUIFrame(
{ new RectTransform(new Vector2(0.45f, 0.6f), loadGameContainer.RectTransform, Anchor.TopRight)
RelativeOffset = new Vector2(0.0f, 0.1f) {
}, style: "InnerFrame") RelativeOffset = new Vector2(0.0f, 0.1f)
}, style: "InnerFrame")
{ {
UserData = "savefileframe" UserData = "savefileframe"
}; };
var titleText = new GUITextBlock(new RectTransform(new Vector2(0.9f, 0.2f), saveFileFrame.RectTransform, Anchor.TopCenter) var titleText = new GUITextBlock(
{ new RectTransform(new Vector2(0.9f, 0.2f), saveFileFrame.RectTransform, Anchor.TopCenter)
RelativeOffset = new Vector2(0, 0.05f) {
}, RelativeOffset = new Vector2(0, 0.05f)
},
Path.GetFileNameWithoutExtension(fileName), font: GUIStyle.LargeFont, textAlignment: Alignment.Center); Path.GetFileNameWithoutExtension(fileName), font: GUIStyle.LargeFont, textAlignment: Alignment.Center);
titleText.Text = ToolBox.LimitString(titleText.Text, titleText.Font, titleText.Rect.Width); titleText.Text = ToolBox.LimitString(titleText.Text, titleText.Font, titleText.Rect.Width);
var layoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 0.5f), saveFileFrame.RectTransform, Anchor.Center) var layoutGroup = new GUILayoutGroup(
{ new RectTransform(new Vector2(0.8f, 0.5f), saveFileFrame.RectTransform, Anchor.Center)
RelativeOffset = new Vector2(0, 0.1f) {
}); RelativeOffset = new Vector2(0, 0.1f)
});
new GUITextBlock(new RectTransform(new Vector2(1, 0), layoutGroup.RectTransform), $"{TextManager.Get("Submarine")} : {subName}", font: GUIStyle.SmallFont); new GUITextBlock(new RectTransform(new Vector2(1, 0), layoutGroup.RectTransform),
new GUITextBlock(new RectTransform(new Vector2(1, 0), layoutGroup.RectTransform), $"{TextManager.Get("LastSaved")} : {saveTime}", font: GUIStyle.SmallFont); $"{TextManager.Get("Submarine")} : {subName}", font: GUIStyle.SmallFont);
new GUITextBlock(new RectTransform(new Vector2(1, 0), layoutGroup.RectTransform), $"{TextManager.Get("MapSeed")} : {mapseed}", font: GUIStyle.SmallFont); new GUITextBlock(new RectTransform(new Vector2(1, 0), layoutGroup.RectTransform),
$"{TextManager.Get("LastSaved")} : {saveTime}", font: GUIStyle.SmallFont);
new GUITextBlock(new RectTransform(new Vector2(1, 0), layoutGroup.RectTransform),
$"{TextManager.Get("MapSeed")} : {mapseed}", font: GUIStyle.SmallFont);
new GUIButton(new RectTransform(new Vector2(0.4f, 0.15f), saveFileFrame.RectTransform, Anchor.BottomCenter) new GUIButton(new RectTransform(new Vector2(0.4f, 0.15f), saveFileFrame.RectTransform, Anchor.BottomCenter)
{ {
RelativeOffset = new Vector2(0, 0.1f) RelativeOffset = new Vector2(0, 0.1f)
}, TextManager.Get("Delete"), style: "GUIButtonSmall") }, TextManager.Get("Delete"), style: "GUIButtonSmall")
{ {
UserData = fileName, UserData = saveInfo,
OnClicked = DeleteSave OnClicked = DeleteSave
}; };
return true; return true;
} }
private bool DeleteSave(GUIButton button, object obj)
{
string saveFile = obj as string;
if (obj == null) { return false; }
LocalizedString header = TextManager.Get("deletedialoglabel");
LocalizedString body = TextManager.GetWithVariable("deletedialogquestion", "[file]", Path.GetFileNameWithoutExtension(saveFile));
EventEditorScreen.AskForConfirmation(header, body, () =>
{
SaveUtil.DeleteSave(saveFile);
prevSaveFiles?.RemoveAll(s => s.FilePath == saveFile);
UpdateLoadMenu(prevSaveFiles.ToList());
return true;
});
return true;
}
private void RemoveSaveFrame() private void RemoveSaveFrame()
{ {
GUIComponent prevFrame = null; GUIComponent prevFrame = null;
@@ -352,7 +352,7 @@ namespace Barotrauma
if (!availableMissions.Any()) { availableMissions.Insert(0, null); } if (!availableMissions.Any()) { availableMissions.Insert(0, null); }
availableMissions.AddRange(location.AvailableMissions); availableMissions.AddRange(location.AvailableMissions.Where(m => m.Locations[0] == m.Locations[1]));
missionList.Content.ClearChildren(); missionList.Content.ClearChildren();
@@ -571,7 +571,7 @@ namespace Barotrauma
//locationInfoPanel?.UpdateAuto(1.0f); //locationInfoPanel?.UpdateAuto(1.0f);
} }
public void SelectTab(CampaignMode.InteractionType tab, Identifier storeIdentifier = default) public void SelectTab(CampaignMode.InteractionType tab, Character npc = null)
{ {
if (Campaign.ShowCampaignUI || (Campaign.ForceMapUI && tab == CampaignMode.InteractionType.Map)) if (Campaign.ShowCampaignUI || (Campaign.ForceMapUI && tab == CampaignMode.InteractionType.Map))
{ {
@@ -592,7 +592,7 @@ namespace Barotrauma
switch (selectedTab) switch (selectedTab)
{ {
case CampaignMode.InteractionType.Store: case CampaignMode.InteractionType.Store:
Store.SelectStore(storeIdentifier); Store.SelectStore(npc);
break; break;
case CampaignMode.InteractionType.Crew: case CampaignMode.InteractionType.Crew:
CrewManagement.UpdateCrew(); CrewManagement.UpdateCrew();
@@ -602,6 +602,7 @@ namespace Barotrauma
submarineSelection.RefreshSubmarineDisplay(true, setTransferOptionToTrue: true); submarineSelection.RefreshSubmarineDisplay(true, setTransferOptionToTrue: true);
break; break;
case CampaignMode.InteractionType.Map: case CampaignMode.InteractionType.Map:
GameMain.GameSession?.Map?.ResetPendingSub();
//refresh mission rewards (may have been changed by e.g. a pending submarine switch) //refresh mission rewards (may have been changed by e.g. a pending submarine switch)
foreach (GUITextBlock rewardText in missionRewardTexts) foreach (GUITextBlock rewardText in missionRewardTexts)
{ {
@@ -25,14 +25,11 @@ namespace Barotrauma.CharacterEditor
{ {
get get
{ {
if (cam == null) cam ??= new Camera()
{ {
cam = new Camera() MinZoom = 0.1f,
{ MaxZoom = 5.0f
MinZoom = 0.1f, };
MaxZoom = 5.0f
};
}
return cam; return cam;
} }
} }
@@ -90,15 +87,15 @@ namespace Barotrauma.CharacterEditor
private float spriteSheetZoom = 1; private float spriteSheetZoom = 1;
private float spriteSheetMinZoom = 0.25f; private float spriteSheetMinZoom = 0.25f;
private float spriteSheetMaxZoom = 1; private float spriteSheetMaxZoom = 1;
private int spriteSheetOffsetY = 20; private const int spriteSheetOffsetY = 20;
private int spriteSheetOffsetX = 30; private const int spriteSheetOffsetX = 30;
private bool hideBodySheet; private bool hideBodySheet;
private Color backgroundColor = new Color(0.2f, 0.2f, 0.2f, 1.0f); private Color backgroundColor = new Color(0.2f, 0.2f, 0.2f, 1.0f);
private Vector2 cameraOffset; private Vector2 cameraOffset;
private List<LimbJoint> selectedJoints = new List<LimbJoint>(); private readonly List<LimbJoint> selectedJoints = new List<LimbJoint>();
private List<Limb> selectedLimbs = new List<Limb>(); private readonly List<Limb> selectedLimbs = new List<Limb>();
private HashSet<Character> editedCharacters = new HashSet<Character>(); private readonly HashSet<Character> editedCharacters = new HashSet<Character>();
private bool isEndlessRunner; private bool isEndlessRunner;
@@ -146,7 +143,7 @@ namespace Barotrauma.CharacterEditor
var humanSpeciesName = CharacterPrefab.HumanSpeciesName; var humanSpeciesName = CharacterPrefab.HumanSpeciesName;
if (humanSpeciesName.IsEmpty) if (humanSpeciesName.IsEmpty)
{ {
SpawnCharacter(AllSpecies.First()); SpawnCharacter(VisibleSpecies.First());
} }
else else
{ {
@@ -195,7 +192,7 @@ namespace Barotrauma.CharacterEditor
jointEndLimb = null; jointEndLimb = null;
anchor1Pos = null; anchor1Pos = null;
jointStartLimb = null; jointStartLimb = null;
allSpecies = null; visibleSpecies = null;
onlyShowSourceRectForSelectedLimbs = false; onlyShowSourceRectForSelectedLimbs = false;
unrestrictSpritesheet = false; unrestrictSpritesheet = false;
editedCharacters.Clear(); editedCharacters.Clear();
@@ -217,15 +214,12 @@ namespace Barotrauma.CharacterEditor
private void Reset(IEnumerable<Character> characters = null) private void Reset(IEnumerable<Character> characters = null)
{ {
if (characters == null) characters ??= editedCharacters;
{
characters = editedCharacters;
}
characters.ForEach(c => ResetParams(c)); characters.ForEach(c => ResetParams(c));
ResetVariables(); ResetVariables();
} }
private void ResetParams(Character character) private static void ResetParams(Character character)
{ {
character.Params.Reset(true); character.Params.Reset(true);
foreach (var animation in character.AnimController.AllAnimParams) foreach (var animation in character.AnimController.AllAnimParams)
@@ -262,7 +256,10 @@ namespace Barotrauma.CharacterEditor
#endif #endif
} }
GameMain.Instance.ResolutionChanged -= OnResolutionChanged; GameMain.Instance.ResolutionChanged -= OnResolutionChanged;
GameMain.LightManager.LightingEnabled = true; if (!GameMain.DevMode)
{
GameMain.LightManager.LightingEnabled = true;
}
ClearWidgets(); ClearWidgets();
ClearSelection(); ClearSelection();
} }
@@ -280,6 +277,7 @@ namespace Barotrauma.CharacterEditor
#region Main methods #region Main methods
public override void AddToGUIUpdateList() public override void AddToGUIUpdateList()
{ {
if (rightArea == null || leftArea == null) { return; }
rightArea.AddToGUIUpdateList(); rightArea.AddToGUIUpdateList();
leftArea.AddToGUIUpdateList(); leftArea.AddToGUIUpdateList();
@@ -718,7 +716,7 @@ namespace Barotrauma.CharacterEditor
cameraOffset = Vector2.Clamp(cameraOffset, min, max); cameraOffset = Vector2.Clamp(cameraOffset, min, max);
} }
Cam.Position = targetPos + cameraOffset; Cam.Position = targetPos + cameraOffset;
MapEntity.mapEntityList.ForEach(e => e.IsHighlighted = false); MapEntity.ClearHighlightedEntities();
// Update widgets // Update widgets
jointSelectionWidgets.Values.ForEach(w => w.Update((float)deltaTime)); jointSelectionWidgets.Values.ForEach(w => w.Update((float)deltaTime));
limbEditWidgets.Values.ForEach(w => w.Update((float)deltaTime)); limbEditWidgets.Values.ForEach(w => w.Update((float)deltaTime));
@@ -778,7 +776,7 @@ namespace Barotrauma.CharacterEditor
scaledMouseSpeed = PlayerInput.MouseSpeedPerSecond * (float)deltaTime; scaledMouseSpeed = PlayerInput.MouseSpeedPerSecond * (float)deltaTime;
Cam.UpdateTransform(true); Cam.UpdateTransform(true);
Submarine.CullEntities(Cam); Submarine.CullEntities(Cam);
Submarine.MainSub.UpdateTransform(); Submarine.MainSub?.UpdateTransform();
// Lightmaps // Lightmaps
if (GameMain.LightManager.LightingEnabled) if (GameMain.LightManager.LightingEnabled)
@@ -1439,33 +1437,35 @@ namespace Barotrauma.CharacterEditor
private Identifier currentCharacterIdentifier; private Identifier currentCharacterIdentifier;
private Identifier selectedJob = Identifier.Empty; private Identifier selectedJob = Identifier.Empty;
private List<Identifier> allSpecies; private List<Identifier> visibleSpecies;
private List<Identifier> AllSpecies private List<Identifier> VisibleSpecies
{ {
get get
{ {
if (allSpecies == null) visibleSpecies ??= CharacterPrefab.Prefabs.Where(ShowCreature).OrderBy(p => p.Identifier).Select(p => p.Identifier).ToList();
{ return visibleSpecies;
#if DEBUG
allSpecies = CharacterPrefab.Prefabs.Keys.OrderBy(p => p).ToList();
#else
allSpecies = CharacterPrefab.Prefabs.Keys.Where(p => !p.Contains("variant")).OrderBy(p => p).ToList();
#endif
allSpecies.ForEach(f => DebugConsole.NewMessage(f.Value, Color.White));
}
return allSpecies;
} }
} }
private List<CharacterFile> vanillaCharacters; private bool ShowCreature(CharacterPrefab prefab)
private List<CharacterFile> VanillaCharacters {
Identifier speciesName = prefab.Identifier;
if (speciesName == CharacterPrefab.HumanSpeciesName) { return true; }
if (!VanillaCharacters.Contains(prefab.ContentFile))
{
// Always show all custom characters.
return true;
}
if (CreatureMetrics.UnlockAll) { return true; }
return CreatureMetrics.Unlocked.Contains(speciesName);
}
private IEnumerable<CharacterFile> vanillaCharacters;
private IEnumerable<CharacterFile> VanillaCharacters
{ {
get get
{ {
if (vanillaCharacters == null) vanillaCharacters ??= GameMain.VanillaContent.GetFiles<CharacterFile>();
{
vanillaCharacters = GameMain.VanillaContent.GetFiles<CharacterFile>().ToList();
}
return vanillaCharacters; return vanillaCharacters;
} }
} }
@@ -1474,7 +1474,7 @@ namespace Barotrauma.CharacterEditor
{ {
GetCurrentCharacterIndex(); GetCurrentCharacterIndex();
IncreaseIndex(); IncreaseIndex();
currentCharacterIdentifier = AllSpecies[characterIndex]; currentCharacterIdentifier = VisibleSpecies[characterIndex];
return currentCharacterIdentifier; return currentCharacterIdentifier;
} }
@@ -1482,19 +1482,19 @@ namespace Barotrauma.CharacterEditor
{ {
GetCurrentCharacterIndex(); GetCurrentCharacterIndex();
ReduceIndex(); ReduceIndex();
currentCharacterIdentifier = AllSpecies[characterIndex]; currentCharacterIdentifier = VisibleSpecies[characterIndex];
return currentCharacterIdentifier; return currentCharacterIdentifier;
} }
private void GetCurrentCharacterIndex() private void GetCurrentCharacterIndex()
{ {
characterIndex = AllSpecies.IndexOf(character.SpeciesName); characterIndex = VisibleSpecies.IndexOf(character.SpeciesName);
} }
private void IncreaseIndex() private void IncreaseIndex()
{ {
characterIndex++; characterIndex++;
if (characterIndex > AllSpecies.Count - 1) if (characterIndex > VisibleSpecies.Count - 1)
{ {
characterIndex = 0; characterIndex = 0;
} }
@@ -1505,7 +1505,7 @@ namespace Barotrauma.CharacterEditor
characterIndex--; characterIndex--;
if (characterIndex < 0) if (characterIndex < 0)
{ {
characterIndex = AllSpecies.Count - 1; characterIndex = VisibleSpecies.Count - 1;
} }
} }
@@ -1570,10 +1570,7 @@ namespace Barotrauma.CharacterEditor
{ {
wayPoint = WayPoint.GetRandom(spawnType: SpawnType.Human, sub: Submarine.MainSub); wayPoint = WayPoint.GetRandom(spawnType: SpawnType.Human, sub: Submarine.MainSub);
} }
if (wayPoint == null) wayPoint ??= WayPoint.GetRandom(sub: Submarine.MainSub);
{
wayPoint = WayPoint.GetRandom(sub: Submarine.MainSub);
}
spawnPosition = wayPoint.WorldPosition; spawnPosition = wayPoint.WorldPosition;
} }
@@ -1689,7 +1686,7 @@ namespace Barotrauma.CharacterEditor
XElement overrideElement = null; XElement overrideElement = null;
if (duplicate != null) if (duplicate != null)
{ {
allSpecies = null; visibleSpecies = null;
if (!File.Exists(configFilePath)) if (!File.Exists(configFilePath))
{ {
// If the file exists, we just want to overwrite it. // If the file exists, we just want to overwrite it.
@@ -1825,9 +1822,9 @@ namespace Barotrauma.CharacterEditor
AnimationParams.Create(fullPath, name, animType, type); AnimationParams.Create(fullPath, name, animType, type);
} }
} }
if (!AllSpecies.Contains(name)) if (!VisibleSpecies.Contains(name))
{ {
AllSpecies.Add(name); VisibleSpecies.Add(name);
} }
SpawnCharacter(name, ragdollParams); SpawnCharacter(name, ragdollParams);
limbPairEditing = false; limbPairEditing = false;
@@ -2680,23 +2677,33 @@ namespace Barotrauma.CharacterEditor
{ {
Stretch = true Stretch = true
}; };
// Character selection // Character selection
var characterLabel = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform), GetCharacterEditorTranslation("CharacterPanel"), font: GUIStyle.LargeFont); var characterLabel = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform), GetCharacterEditorTranslation("CharacterPanel"), font: GUIStyle.LargeFont);
var characterDropDown = new GUIDropDown(new RectTransform(new Vector2(1, 0.2f), content.RectTransform) var characterDropDown = new GUIDropDown(new RectTransform(new Vector2(1, 0.2f), content.RectTransform)
{ {
RelativeOffset = new Vector2(0, 0.2f) RelativeOffset = new Vector2(0, 0.2f)
}, elementCount: 8, style: null); }, elementCount: 8, style: null);
characterDropDown.ListBox.Color = new Color(characterDropDown.ListBox.Color.R, characterDropDown.ListBox.Color.G, characterDropDown.ListBox.Color.B, byte.MaxValue); characterDropDown.ListBox.Color = new Color(characterDropDown.ListBox.Color.R, characterDropDown.ListBox.Color.G, characterDropDown.ListBox.Color.B, byte.MaxValue);
foreach (var file in AllSpecies) foreach (CharacterPrefab prefab in CharacterPrefab.Prefabs.OrderByDescending(p => p.Identifier))
{ {
characterDropDown.AddItem(file.Value.CapitaliseFirstInvariant(), file); Identifier speciesName = prefab.Identifier;
if (ShowCreature(prefab))
{
characterDropDown.AddItem(speciesName.Value.CapitaliseFirstInvariant(), speciesName).SetAsFirstChild();
}
else if (!CreatureMetrics.Encountered.Contains(speciesName))
{
// Using a matching placeholder string here ("hidden").
var element = characterDropDown.AddItem(TextManager.Get("hiddensubmarines"), Identifier.Empty, textColor: Color.Gray * 0.75f);
element.SetAsLastChild();
element.Enabled = false;
}
} }
characterDropDown.SelectItem(currentCharacterIdentifier); characterDropDown.SelectItem(currentCharacterIdentifier);
characterDropDown.OnSelected = (component, data) => characterDropDown.OnSelected = (component, data) =>
{ {
Identifier characterIdentifier = (Identifier)data; Identifier characterIdentifier = (Identifier)data;
if (characterIdentifier.IsEmpty) { return true; }
try try
{ {
SpawnCharacter(characterIdentifier); SpawnCharacter(characterIdentifier);
@@ -2797,7 +2804,7 @@ namespace Barotrauma.CharacterEditor
saveAllButton.OnClicked += (button, userData) => saveAllButton.OnClicked += (button, userData) =>
{ {
#if !DEBUG #if !DEBUG
if (VanillaCharacters != null && VanillaCharacters.Contains(CharacterPrefab.Prefabs[currentCharacterIdentifier].ContentFile)) if (VanillaCharacters.Contains(CharacterPrefab.Prefabs[currentCharacterIdentifier].ContentFile))
{ {
GUI.AddMessage(GetCharacterEditorTranslation("CannotEditVanillaCharacters"), GUIStyle.Red, font: GUIStyle.LargeFont); GUI.AddMessage(GetCharacterEditorTranslation("CannotEditVanillaCharacters"), GUIStyle.Red, font: GUIStyle.LargeFont);
return false; return false;
@@ -2837,7 +2844,7 @@ namespace Barotrauma.CharacterEditor
box.Buttons[1].OnClicked += (b, d) => box.Buttons[1].OnClicked += (b, d) =>
{ {
#if !DEBUG #if !DEBUG
if (VanillaCharacters != null && VanillaCharacters.Contains(CharacterPrefab.Prefabs[currentCharacterIdentifier].ContentFile)) if (VanillaCharacters.Contains(CharacterPrefab.Prefabs[currentCharacterIdentifier].ContentFile))
{ {
GUI.AddMessage(GetCharacterEditorTranslation("CannotEditVanillaCharacters"), GUIStyle.Red, font: GUIStyle.LargeFont); GUI.AddMessage(GetCharacterEditorTranslation("CannotEditVanillaCharacters"), GUIStyle.Red, font: GUIStyle.LargeFont);
box.Close(); box.Close();
@@ -2975,7 +2982,7 @@ namespace Barotrauma.CharacterEditor
box.Buttons[1].OnClicked += (b, d) => box.Buttons[1].OnClicked += (b, d) =>
{ {
#if !DEBUG #if !DEBUG
if (VanillaCharacters != null && VanillaCharacters.Contains(CharacterPrefab.Prefabs[currentCharacterIdentifier].ContentFile)) if (VanillaCharacters.Contains(CharacterPrefab.Prefabs[currentCharacterIdentifier].ContentFile))
{ {
GUI.AddMessage(GetCharacterEditorTranslation("CannotEditVanillaCharacters"), GUIStyle.Red, font: GUIStyle.LargeFont); GUI.AddMessage(GetCharacterEditorTranslation("CannotEditVanillaCharacters"), GUIStyle.Red, font: GUIStyle.LargeFont);
box.Close(); box.Close();
@@ -3214,7 +3221,7 @@ namespace Barotrauma.CharacterEditor
Wizard.Instance.CopyExisting(CharacterParams, RagdollParams, AnimParams); Wizard.Instance.CopyExisting(CharacterParams, RagdollParams, AnimParams);
} }
#region ToggleButtons #region ToggleButtons
private enum Direction private enum Direction
{ {
Left, Left,
@@ -3998,7 +4005,7 @@ namespace Barotrauma.CharacterEditor
}; };
}).Draw(spriteBatch, deltaTime); }).Draw(spriteBatch, deltaTime);
} }
else else if (groundedParams != null)
{ {
GetAnimationWidget("HeadPosition", color, Color.Black, initMethod: w => GetAnimationWidget("HeadPosition", color, Color.Black, initMethod: w =>
{ {
@@ -4107,7 +4114,7 @@ namespace Barotrauma.CharacterEditor
}; };
}).Draw(spriteBatch, deltaTime); }).Draw(spriteBatch, deltaTime);
} }
else else if (groundedParams != null)
{ {
GetAnimationWidget("TorsoPosition", color, Color.Black, initMethod: w => GetAnimationWidget("TorsoPosition", color, Color.Black, initMethod: w =>
{ {
@@ -6,7 +6,7 @@ namespace Barotrauma
{ {
private GUIListBox listBox; private GUIListBox listBox;
private ContentXElement configElement; private readonly ContentXElement configElement;
private float scrollSpeed; private float scrollSpeed;
@@ -35,6 +35,8 @@ namespace Barotrauma
set { listBox.BarScroll = value; } set { listBox.BarScroll = value; }
} }
public readonly GUIButton CloseButton;
public CreditsPlayer(RectTransform rectT, string configFile) : base(null, rectT) public CreditsPlayer(RectTransform rectT, string configFile) : base(null, rectT)
{ {
@@ -49,6 +51,10 @@ namespace Barotrauma
configElement = doc.Root.FromPackage(ContentPackageManager.VanillaCorePackage); configElement = doc.Root.FromPackage(ContentPackageManager.VanillaCorePackage);
Load(); Load();
CloseButton = new GUIButton(new RectTransform(new Vector2(0.1f), RectTransform, Anchor.BottomRight, maxSize: new Point(GUI.IntScale(300), GUI.IntScale(50)))
{ AbsoluteOffset = new Point(GUI.IntScale(20), GUI.IntScale(20) + (Rect.Bottom - GameMain.GraphicsHeight)) },
TextManager.Get("close"));
} }
private void Load() private void Load()
@@ -1,7 +1,6 @@
using Barotrauma.Extensions; using Barotrauma.Extensions;
using Barotrauma.Lights; using Barotrauma.Lights;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using System; using System;
using System.Diagnostics; using System.Diagnostics;
@@ -28,7 +27,7 @@ namespace Barotrauma
public Effect ThresholdTintEffect { get; private set; } public Effect ThresholdTintEffect { get; private set; }
public Effect BlueprintEffect { get; set; } public Effect BlueprintEffect { get; set; }
public GameScreen(GraphicsDevice graphics, ContentManager content) public GameScreen(GraphicsDevice graphics)
{ {
cam = new Camera(); cam = new Camera();
cam.Translate(new Vector2(-10.0f, 50.0f)); cam.Translate(new Vector2(-10.0f, 50.0f));
@@ -39,20 +38,13 @@ namespace Barotrauma
CreateRenderTargets(graphics); CreateRenderTargets(graphics);
}; };
Effect LoadEffect(string path)
=> content.Load<Effect>(path
#if LINUX || OSX
+"_opengl"
#endif
);
//var blurEffect = LoadEffect("Effects/blurshader"); //var blurEffect = LoadEffect("Effects/blurshader");
damageEffect = LoadEffect("Effects/damageshader"); damageEffect = EffectLoader.Load("Effects/damageshader");
PostProcessEffect = LoadEffect("Effects/postprocess"); PostProcessEffect = EffectLoader.Load("Effects/postprocess");
GradientEffect = LoadEffect("Effects/gradientshader"); GradientEffect = EffectLoader.Load("Effects/gradientshader");
GrainEffect = LoadEffect("Effects/grainshader"); GrainEffect = EffectLoader.Load("Effects/grainshader");
ThresholdTintEffect = LoadEffect("Effects/thresholdtint"); ThresholdTintEffect = EffectLoader.Load("Effects/thresholdtint");
BlueprintEffect = LoadEffect("Effects/blueprintshader"); BlueprintEffect = EffectLoader.Load("Effects/blueprintshader");
damageStencil = TextureLoader.FromFile("Content/Map/walldamage.png"); damageStencil = TextureLoader.FromFile("Content/Map/walldamage.png");
damageEffect.Parameters["xStencil"].SetValue(damageStencil); damageEffect.Parameters["xStencil"].SetValue(damageStencil);
@@ -46,7 +46,7 @@ namespace Barotrauma
private GUITextBox serverNameBox, passwordBox, maxPlayersBox; private GUITextBox serverNameBox, passwordBox, maxPlayersBox;
private GUITickBox isPublicBox, wrongPasswordBanBox, karmaBox; private GUITickBox isPublicBox, wrongPasswordBanBox, karmaBox;
private GUIDropDown serverExecutableDropdown; private GUIDropDown languageDropdown, serverExecutableDropdown;
private readonly GUIButton joinServerButton, hostServerButton; private readonly GUIButton joinServerButton, hostServerButton;
private readonly GUIFrame modsButtonContainer; private readonly GUIFrame modsButtonContainer;
@@ -466,6 +466,11 @@ namespace Barotrauma
var creditsContainer = new GUIFrame(new RectTransform(new Vector2(0.75f, 1.5f), menuTabs[Tab.Credits].RectTransform, Anchor.CenterRight), style: "OuterGlow", color: Color.Black * 0.8f); var creditsContainer = new GUIFrame(new RectTransform(new Vector2(0.75f, 1.5f), menuTabs[Tab.Credits].RectTransform, Anchor.CenterRight), style: "OuterGlow", color: Color.Black * 0.8f);
creditsPlayer = new CreditsPlayer(new RectTransform(Vector2.One, creditsContainer.RectTransform), "Content/Texts/Credits.xml"); creditsPlayer = new CreditsPlayer(new RectTransform(Vector2.One, creditsContainer.RectTransform), "Content/Texts/Credits.xml");
creditsPlayer.CloseButton.OnClicked = (btn, userdata) =>
{
SelectTab(Tab.Empty);
return true;
};
} }
private void CreateTutorialTab() private void CreateTutorialTab()
@@ -893,12 +898,14 @@ namespace Barotrauma
#endif #endif
} }
string arguments = "-name \"" + ToolBox.EscapeCharacters(name) + "\"" + string arguments =
" -public " + isPublicBox.Selected.ToString() + "-name \"" + ToolBox.EscapeCharacters(name) + "\"" +
" -playstyle " + ((PlayStyle)playstyleBanner.UserData).ToString() + " -public " + isPublicBox.Selected.ToString() +
" -banafterwrongpassword " + wrongPasswordBanBox.Selected.ToString() + " -playstyle " + ((PlayStyle)playstyleBanner.UserData).ToString() +
" -karmaenabled " + (!karmaBox.Selected).ToString() + " -banafterwrongpassword " + wrongPasswordBanBox.Selected.ToString() +
" -maxplayers " + maxPlayersBox.Text; " -karmaenabled " + (!karmaBox.Selected).ToString() +
" -maxplayers " + maxPlayersBox.Text +
$" -language \"{(LanguageIdentifier)languageDropdown.SelectedData}\"";
if (!string.IsNullOrWhiteSpace(passwordBox.Text)) if (!string.IsNullOrWhiteSpace(passwordBox.Text))
{ {
@@ -994,7 +1001,7 @@ namespace Barotrauma
|| item.IsDownloadPending || item.IsDownloadPending
|| (item.InstallTime.TryGetValue(out var workshopInstallTime) || (item.InstallTime.TryGetValue(out var workshopInstallTime)
&& pkg.InstallTime.TryUnwrap(out var localInstallTime) && pkg.InstallTime.TryUnwrap(out var localInstallTime)
&& localInstallTime < workshopInstallTime))); && localInstallTime.ToUtcValue() < workshopInstallTime)));
modUpdateStatus = (DateTime.Now + ModUpdateInterval, count); modUpdateStatus = (DateTime.Now + ModUpdateInterval, count);
} }
@@ -1099,7 +1106,7 @@ namespace Barotrauma
if (i == 0) if (i == 0)
{ {
GUI.DrawLine(spriteBatch, textPos, textPos - Vector2.UnitX * textSize.X, mouseOn ? Color.White : Color.White * 0.7f); GUI.DrawLine(spriteBatch, textPos, textPos - Vector2.UnitX * textSize.X, mouseOn ? Color.White : Color.White * 0.7f);
if (mouseOn && PlayerInput.PrimaryMouseButtonClicked()) if (mouseOn && PlayerInput.PrimaryMouseButtonClicked() && GUI.MouseOn == null)
{ {
GameMain.ShowOpenUrlInWebBrowserPrompt("http://privacypolicy.daedalic.com"); GameMain.ShowOpenUrlInWebBrowserPrompt("http://privacypolicy.daedalic.com");
} }
@@ -1204,45 +1211,28 @@ namespace Barotrauma
{ {
menuTabs[Tab.HostServer].ClearChildren(); menuTabs[Tab.HostServer].ClearChildren();
string name = ""; var serverSettings = XMLExtensions.TryLoadXml(ServerSettings.SettingsFile, out _)?.Root ?? new XElement("serversettings");
string password = "";
int maxPlayers = 8; var name = serverSettings.GetAttributeString("name", "");
bool isPublic = true; var password = serverSettings.GetAttributeString("password", "");
bool banAfterWrongPassword = false; var isPublic = serverSettings.GetAttributeBool("IsPublic", true);
bool karmaEnabled = true; var banAfterWrongPassword = serverSettings.GetAttributeBool("banafterwrongpassword", false);
string selectedKarmaPreset = "";
PlayStyle selectedPlayStyle = PlayStyle.Casual; int maxPlayersElement = serverSettings.GetAttributeInt("maxplayers", 8);
if (File.Exists(ServerSettings.SettingsFile)) if (maxPlayersElement > NetConfig.MaxPlayers)
{ {
XDocument settingsDoc = XMLExtensions.TryLoadXml(ServerSettings.SettingsFile); DebugConsole.AddWarning($"Setting the maximum amount of players to {maxPlayersElement} failed due to exceeding the limit of {NetConfig.MaxPlayers} players per server. Using the maximum of {NetConfig.MaxPlayers} instead.");
if (settingsDoc != null)
{
name = settingsDoc.Root.GetAttributeString("name", name);
password = settingsDoc.Root.GetAttributeString("password", password);
isPublic = settingsDoc.Root.GetAttributeBool("public", isPublic);
banAfterWrongPassword = settingsDoc.Root.GetAttributeBool("banafterwrongpassword", banAfterWrongPassword);
int maxPlayersElement = settingsDoc.Root.GetAttributeInt("maxplayers", maxPlayers);
if (maxPlayersElement > NetConfig.MaxPlayers)
{
DebugConsole.IsOpen = true;
DebugConsole.NewMessage($"Setting the maximum amount of players to {maxPlayersElement} failed due to exceeding the limit of {NetConfig.MaxPlayers} players per server. Using the maximum of {NetConfig.MaxPlayers} instead.", Color.Red);
maxPlayersElement = NetConfig.MaxPlayers;
}
maxPlayers = maxPlayersElement;
karmaEnabled = settingsDoc.Root.GetAttributeBool("karmaenabled", true);
selectedKarmaPreset = settingsDoc.Root.GetAttributeString("karmapreset", "default");
string playStyleStr = settingsDoc.Root.GetAttributeString("playstyle", "Casual");
Enum.TryParse(playStyleStr, out selectedPlayStyle);
}
} }
int maxPlayers = Math.Clamp(maxPlayersElement, min: 1, max: NetConfig.MaxPlayers);
var karmaEnabled = serverSettings.GetAttributeBool("karmaenabled", true);
var selectedPlayStyle = serverSettings.GetAttributeEnum("playstyle", PlayStyle.Casual);
Vector2 textLabelSize = new Vector2(1.0f, 0.05f); Vector2 textLabelSize = new Vector2(1.0f, 0.05f);
Alignment textAlignment = Alignment.CenterLeft; Alignment textAlignment = Alignment.CenterLeft;
Vector2 textFieldSize = new Vector2(0.5f, 1.0f); Vector2 textFieldSize = new Vector2(0.5f, 1.0f);
Vector2 tickBoxSize = new Vector2(0.4f, 0.04f); Vector2 tickBoxSize = new Vector2(0.4f, 0.04f);
var content = new GUILayoutGroup(new RectTransform(new Vector2(0.7f, 0.9f), menuTabs[Tab.HostServer].RectTransform, Anchor.Center), childAnchor: Anchor.TopCenter) var content = new GUILayoutGroup(new RectTransform(new Vector2(0.7f, 0.95f), menuTabs[Tab.HostServer].RectTransform, Anchor.Center), childAnchor: Anchor.TopCenter)
{ {
RelativeSpacing = 0.01f, RelativeSpacing = 0.01f,
Stretch = true Stretch = true
@@ -1320,7 +1310,7 @@ namespace Barotrauma
//other settings ----------------------------------------------------- //other settings -----------------------------------------------------
//spacing //spacing
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.05f), content.RectTransform), style: null); new GUIFrame(new RectTransform(new Vector2(1.0f, 0.025f), content.RectTransform), style: null);
var label = new GUITextBlock(new RectTransform(textLabelSize, parent.RectTransform), TextManager.Get("ServerName"), textAlignment: textAlignment); var label = new GUITextBlock(new RectTransform(textLabelSize, parent.RectTransform), TextManager.Get("ServerName"), textAlignment: textAlignment);
serverNameBox = new GUITextBox(new RectTransform(textFieldSize, label.RectTransform, Anchor.CenterRight), text: name, textAlignment: textAlignment) serverNameBox = new GUITextBox(new RectTransform(textFieldSize, label.RectTransform, Anchor.CenterRight), text: name, textAlignment: textAlignment)
@@ -1372,6 +1362,21 @@ namespace Barotrauma
}; };
label.RectTransform.IsFixedSize = true; label.RectTransform.IsFixedSize = true;
var languageLabel = new GUITextBlock(new RectTransform(textLabelSize, parent.RectTransform),
TextManager.Get("Language"), textAlignment: textAlignment);
languageDropdown = new GUIDropDown(new RectTransform(textFieldSize, languageLabel.RectTransform, Anchor.CenterRight));
foreach (var language in ServerLanguageOptions.Options)
{
languageDropdown.AddItem(language.Label, language.Identifier);
}
var defaultLanguage = ServerLanguageOptions.PickLanguage(GameSettings.CurrentConfig.Language);
var settingsLanguage = serverSettings.GetAttributeIdentifier("language", defaultLanguage.Value).ToLanguageIdentifier();
if (!ServerLanguageOptions.Options.Any(o => o.Identifier == settingsLanguage))
{
settingsLanguage = defaultLanguage;
}
languageDropdown.Select(ServerLanguageOptions.Options.FindIndex(o => o.Identifier == settingsLanguage));
var serverExecutableLabel = new GUITextBlock(new RectTransform(textLabelSize, parent.RectTransform), var serverExecutableLabel = new GUITextBlock(new RectTransform(textLabelSize, parent.RectTransform),
TextManager.Get("ServerExecutable"), textAlignment: textAlignment); TextManager.Get("ServerExecutable"), textAlignment: textAlignment);
const string vanillaServerOption = "Vanilla"; const string vanillaServerOption = "Vanilla";
@@ -189,7 +189,8 @@ namespace Barotrauma
} }
public IReadOnlyList<SubmarineInfo> GetSubList() public IReadOnlyList<SubmarineInfo> GetSubList()
=> SubList.Content.Children.Select(c => c.UserData as SubmarineInfo).ToArray(); => (IReadOnlyList<SubmarineInfo>)GameMain.Client?.ServerSubmarines
?? Array.Empty<SubmarineInfo>();
public readonly GUIListBox PlayerList; public readonly GUIListBox PlayerList;
@@ -300,7 +301,7 @@ namespace Barotrauma
levelSeed = value; levelSeed = value;
int intSeed = ToolBox.StringToInt(levelSeed); int intSeed = ToolBox.StringToInt(levelSeed);
backgroundSprite = LocationType.Random(new MTRandom(intSeed))?.GetPortrait(intSeed); backgroundSprite = LocationType.Random(new MTRandom(intSeed), predicate: lt => lt.UsePortraitInRandomLoadingScreens)?.GetPortrait(intSeed);
SeedBox.Text = levelSeed; SeedBox.Text = levelSeed;
} }
} }
@@ -929,6 +930,8 @@ namespace Barotrauma
var modeTitle = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), modeContent.RectTransform), mode.Name, font: GUIStyle.SubHeadingFont); var modeTitle = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), modeContent.RectTransform), mode.Name, font: GUIStyle.SubHeadingFont);
var modeDescription = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), modeContent.RectTransform), mode.Description, font: GUIStyle.SmallFont, wrap: true); var modeDescription = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), modeContent.RectTransform), mode.Description, font: GUIStyle.SmallFont, wrap: true);
//leave some padding for the vote count text
modeDescription.Padding = new Vector4(modeDescription.Padding.X, modeDescription.Padding.Y, GUI.IntScale(30), modeDescription.Padding.W);
modeTitle.HoverColor = modeDescription.HoverColor = modeTitle.SelectedColor = modeDescription.SelectedColor = Color.Transparent; modeTitle.HoverColor = modeDescription.HoverColor = modeTitle.SelectedColor = modeDescription.SelectedColor = Color.Transparent;
modeTitle.HoverTextColor = modeDescription.HoverTextColor = modeTitle.TextColor; modeTitle.HoverTextColor = modeDescription.HoverTextColor = modeTitle.TextColor;
modeTitle.TextColor = modeDescription.TextColor = modeTitle.TextColor * 0.5f; modeTitle.TextColor = modeDescription.TextColor = modeTitle.TextColor * 0.5f;
@@ -981,7 +984,7 @@ namespace Barotrauma
} }
else else
{ {
GameMain.Client.RequestSelectMode(ModeList.Content.GetChildIndex(ModeList.Content.GetChildByUserData(GameModePreset.Sandbox))); GameMain.Client.RequestRoundEnd(save: false, quitCampaign: true);
} }
return true; return true;
} }
@@ -1931,7 +1934,7 @@ namespace Barotrauma
var selectedSub = component.UserData as SubmarineInfo; var selectedSub = component.UserData as SubmarineInfo;
if (SelectedMode == GameModePreset.MultiPlayerCampaign && CampaignSetupUI != null) if (SelectedMode == GameModePreset.MultiPlayerCampaign && CampaignSetupUI != null)
{ {
if (selectedSub.Price > CampaignSetupUI.CurrentSettings.InitialMoney) if (selectedSub.Price > CampaignSettings.CurrentSettings.InitialMoney)
{ {
new GUIMessageBox(TextManager.Get("warning"), TextManager.Get("campaignsubtooexpensive")); new GUIMessageBox(TextManager.Get("warning"), TextManager.Get("campaignsubtooexpensive"));
} }
@@ -3289,16 +3292,15 @@ namespace Barotrauma
{ {
//campaign running //campaign running
settingsBlocker.Visible = true; settingsBlocker.Visible = true;
CampaignFrame.Visible = GameMain.Client.HasPermission(ClientPermissions.ManageCampaign); CampaignFrame.Visible = QuitCampaignButton.Enabled = CampaignMode.AllowedToManageCampaign(ClientPermissions.ManageRound);
ContinueCampaignButton.Enabled = !GameMain.Client.GameStarted && (GameMain.Client.HasPermission(ClientPermissions.ManageCampaign) || GameMain.Client.HasPermission(ClientPermissions.ManageRound)); ContinueCampaignButton.Enabled = !GameMain.Client.GameStarted && CampaignFrame.Visible;
QuitCampaignButton.Enabled = GameMain.Client.HasPermission(ClientPermissions.ManageCampaign);
CampaignSetupFrame.Visible = false; CampaignSetupFrame.Visible = false;
} }
else else
{ {
CampaignFrame.Visible = false; CampaignFrame.Visible = false;
CampaignSetupFrame.Visible = true; CampaignSetupFrame.Visible = true;
if (!GameMain.Client.HasPermission(ClientPermissions.ManageCampaign)) if (!CampaignMode.AllowedToManageCampaign(ClientPermissions.ManageRound))
{ {
CampaignSetupFrame.ClearChildren(); CampaignSetupFrame.ClearChildren();
new GUITextBlock(new RectTransform(new Vector2(0.8f, 0.5f), CampaignSetupFrame.RectTransform, Anchor.Center), new GUITextBlock(new RectTransform(new Vector2(0.8f, 0.5f), CampaignSetupFrame.RectTransform, Anchor.Center),
@@ -3311,7 +3313,7 @@ namespace Barotrauma
foreach (var subElement in SubList.Content.Children) foreach (var subElement in SubList.Content.Children)
{ {
var sub = subElement.UserData as SubmarineInfo; var sub = subElement.UserData as SubmarineInfo;
bool tooExpensive = sub.Price > CampaignSetupUI.CurrentSettings.InitialMoney; bool tooExpensive = sub.Price > CampaignSettings.CurrentSettings.InitialMoney;
if (tooExpensive || !sub.IsCampaignCompatible) if (tooExpensive || !sub.IsCampaignCompatible)
{ {
foreach (var textBlock in subElement.GetAllChildren<GUITextBlock>()) foreach (var textBlock in subElement.GetAllChildren<GUITextBlock>())
@@ -3362,7 +3364,7 @@ namespace Barotrauma
CampaignFrame.Visible = CampaignSetupFrame.Visible = false; CampaignFrame.Visible = CampaignSetupFrame.Visible = false;
} }
RefreshEnabledElements(); RefreshEnabledElements();
if (enabled) if (enabled && SelectedMode != GameModePreset.MultiPlayerCampaign)
{ {
ModeList.Select(GameModePreset.MultiPlayerCampaign, GUIListBox.Force.Yes); ModeList.Select(GameModePreset.MultiPlayerCampaign, GUIListBox.Force.Yes);
} }
@@ -7,8 +7,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Linq; using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Xml.Linq; using System.Xml.Linq;
namespace Barotrauma namespace Barotrauma
@@ -241,6 +239,7 @@ namespace Barotrauma
private GUITickBox filterPassword; private GUITickBox filterPassword;
private GUITickBox filterFull; private GUITickBox filterFull;
private GUITickBox filterEmpty; private GUITickBox filterEmpty;
private GUIDropDown languageDropdown;
private Dictionary<Identifier, GUIDropDown> ternaryFilters; private Dictionary<Identifier, GUIDropDown> ternaryFilters;
private Dictionary<Identifier, GUITickBox> filterTickBoxes; private Dictionary<Identifier, GUITickBox> filterTickBoxes;
private Dictionary<Identifier, GUITickBox> playStyleTickBoxes; private Dictionary<Identifier, GUITickBox> playStyleTickBoxes;
@@ -255,6 +254,7 @@ namespace Barotrauma
private TernaryOption filterModdedValue = TernaryOption.Any; private TernaryOption filterModdedValue = TernaryOption.Any;
private ColumnLabel sortedBy; private ColumnLabel sortedBy;
private bool sortedAscending = true;
private const float sidebarWidth = 0.2f; private const float sidebarWidth = 0.2f;
public ServerListScreen() public ServerListScreen()
@@ -425,10 +425,13 @@ namespace Barotrauma
ternaryFilters = new Dictionary<Identifier, GUIDropDown>(); ternaryFilters = new Dictionary<Identifier, GUIDropDown>();
filterTickBoxes = new Dictionary<Identifier, GUITickBox>(); filterTickBoxes = new Dictionary<Identifier, GUITickBox>();
RectTransform createFilterRectT()
=> new RectTransform(new Vector2(1.0f, elementHeight), filters.Content.RectTransform);
GUITickBox addTickBox(Identifier key, LocalizedString text = null, bool defaultState = false, bool addTooltip = false) GUITickBox addTickBox(Identifier key, LocalizedString text = null, bool defaultState = false, bool addTooltip = false)
{ {
text ??= TextManager.Get(key); text ??= TextManager.Get(key);
var tickBox = new GUITickBox(new RectTransform(new Vector2(1.0f, elementHeight), filters.Content.RectTransform), text) var tickBox = new GUITickBox(createFilterRectT(), text)
{ {
UserData = text, UserData = text,
Selected = defaultState, Selected = defaultState,
@@ -450,6 +453,109 @@ namespace Barotrauma
filterEmpty = addTickBox("FilterEmptyServers".ToIdentifier()); filterEmpty = addTickBox("FilterEmptyServers".ToIdentifier());
filterOffensive = addTickBox("FilterOffensiveServers".ToIdentifier()); filterOffensive = addTickBox("FilterOffensiveServers".ToIdentifier());
// Language filter
if (ServerLanguageOptions.Options.Any())
{
var languageKey = "Language".ToIdentifier();
var allLanguagesKey = "AllLanguages".ToIdentifier();
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), filters.Content.RectTransform), TextManager.Get(languageKey), font: GUIStyle.SubHeadingFont)
{
CanBeFocused = false
};
languageDropdown = new GUIDropDown(createFilterRectT(), selectMultiple: true);
languageDropdown.AddItem(TextManager.Get(allLanguagesKey), allLanguagesKey);
var allTickbox = languageDropdown.ListBox.Content.FindChild(allLanguagesKey)?.GetChild<GUITickBox>();
// Spacer between "All" and the individual languages
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.0f), languageDropdown.ListBox.Content.RectTransform)
{
MinSize = new Point(0, GUI.IntScaleCeiling(2))
}, style: null)
{
Color = Color.DarkGray,
CanBeFocused = false
};
var selectedLanguages
= ServerListFilters.Instance.GetAttributeLanguageIdentifierArray(
languageKey,
Array.Empty<LanguageIdentifier>());
foreach (var (label, identifier, _) in ServerLanguageOptions.Options)
{
languageDropdown.AddItem(label, identifier);
}
if (!selectedLanguages.Any())
{
selectedLanguages = ServerLanguageOptions.Options.Select(o => o.Identifier).ToArray();
}
foreach (var lang in selectedLanguages)
{
languageDropdown.SelectItem(lang);
}
if (ServerLanguageOptions.Options.All(o => selectedLanguages.Any(l => o.Identifier == l)))
{
languageDropdown.SelectItem(allLanguagesKey);
languageDropdown.Text = TextManager.Get(allLanguagesKey);
}
var langTickboxes = languageDropdown.ListBox.Content.Children
.Where(c => c.UserData is LanguageIdentifier)
.Select(c => c.GetChild<GUITickBox>())
.ToArray();
bool inSelectedCall = false;
languageDropdown.OnSelected = (_, userData) =>
{
if (inSelectedCall) { return true; }
try
{
inSelectedCall = true;
if (Equals(allLanguagesKey, userData))
{
foreach (var tb in langTickboxes)
{
tb.Selected = allTickbox.Selected;
}
}
bool noneSelected = langTickboxes.All(tb => !tb.Selected);
bool allSelected = langTickboxes.All(tb => tb.Selected);
if (allSelected != allTickbox.Selected)
{
allTickbox.Selected = allSelected;
}
if (allSelected)
{
languageDropdown.Text = TextManager.Get(allLanguagesKey);
}
else if (noneSelected)
{
languageDropdown.Text = TextManager.Get("None");
}
var languages = languageDropdown.SelectedDataMultiple.OfType<LanguageIdentifier>();
ServerListFilters.Instance.SetAttribute(languageKey, string.Join(", ", languages));
GameSettings.SaveCurrentConfig();
return true;
}
finally
{
inSelectedCall = false;
FilterServers();
}
};
}
// Filter Tags // Filter Tags
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), filters.Content.RectTransform), TextManager.Get("servertags"), font: GUIStyle.SubHeadingFont) new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), filters.Content.RectTransform), TextManager.Get("servertags"), font: GUIStyle.SubHeadingFont)
{ {
@@ -713,7 +819,7 @@ namespace Barotrauma
private void SortList(ColumnLabel sortBy, bool toggle) private void SortList(ColumnLabel sortBy, bool toggle)
{ {
if (!(labelHolder.GetChildByUserData(sortBy) is GUIButton button)) { return; } if (labelHolder.GetChildByUserData(sortBy) is not GUIButton button) { return; }
sortedBy = sortBy; sortedBy = sortBy;
@@ -730,52 +836,75 @@ namespace Barotrauma
} }
} }
bool ascending = arrowUp.Visible; sortedAscending = arrowUp.Visible;
if (toggle) if (toggle)
{ {
ascending = !ascending; sortedAscending = !sortedAscending;
} }
arrowUp.Visible = ascending; arrowUp.Visible = sortedAscending;
arrowDown.Visible = !ascending; arrowDown.Visible = !sortedAscending;
serverList.Content.RectTransform.SortChildren((c1, c2) => serverList.Content.RectTransform.SortChildren((c1, c2) =>
{ {
if (!(c1.GUIComponent.UserData is ServerInfo s1)) { return 0; } if (c1.GUIComponent.UserData is not ServerInfo s1) { return 0; }
if (!(c2.GUIComponent.UserData is ServerInfo s2)) { return 0; } if (c2.GUIComponent.UserData is not ServerInfo s2) { return 0; }
int comparison = sortedAscending ? 1 : -1;
switch (sortBy) return CompareServer(sortBy, s1, s2) * comparison;
{
case ColumnLabel.ServerListCompatible:
bool s1Compatible = NetworkMember.IsCompatible(GameMain.Version, s1.GameVersion);
bool s2Compatible = NetworkMember.IsCompatible(GameMain.Version, s2.GameVersion);
if (s1Compatible == s2Compatible) { return 0; }
return (s1Compatible ? 1 : -1) * (ascending ? 1 : -1);
case ColumnLabel.ServerListHasPassword:
if (s1.HasPassword == s2.HasPassword) { return 0; }
return (s1.HasPassword ? 1 : -1) * (ascending ? 1 : -1);
case ColumnLabel.ServerListName:
// I think we actually want culture-specific sorting here?
return string.Compare(s1.ServerName, s2.ServerName, StringComparison.CurrentCulture) * (ascending ? 1 : -1);
case ColumnLabel.ServerListRoundStarted:
if (s1.GameStarted == s2.GameStarted) { return 0; }
return (s1.GameStarted ? 1 : -1) * (ascending ? 1 : -1);
case ColumnLabel.ServerListPlayers:
return s2.PlayerCount.CompareTo(s1.PlayerCount) * (ascending ? 1 : -1);
case ColumnLabel.ServerListPing:
return (s1.Ping.TryUnwrap(out var s1Ping), s2.Ping.TryUnwrap(out var s2Ping)) switch
{
(false, false) => 0,
(true, true) => s2Ping.CompareTo(s1Ping) * (ascending ? 1 : -1),
(false, true) => 1,
(true, false) => -1
};
default:
return 0;
}
}); });
} }
private void InsertServer(ServerInfo serverInfo, GUIComponent component)
{
var children = serverList.Content.RectTransform.Children.Reverse().ToList();
int comparison = sortedAscending ? 1 : -1;
foreach (var child in children)
{
if (child.GUIComponent.UserData is not ServerInfo serverInfo2 || serverInfo.Equals(serverInfo2)) { continue; }
if (CompareServer(sortedBy, serverInfo, serverInfo2) * comparison >= 0)
{
var index = serverList.Content.RectTransform.GetChildIndex(child);
component.RectTransform.RepositionChildInHierarchy(index + 1);
return;
}
}
component.RectTransform.SetAsFirstChild();
}
private static int CompareServer(ColumnLabel sortBy, ServerInfo s1, ServerInfo s2)
{
switch (sortBy)
{
case ColumnLabel.ServerListCompatible:
bool s1Compatible = NetworkMember.IsCompatible(GameMain.Version, s1.GameVersion);
bool s2Compatible = NetworkMember.IsCompatible(GameMain.Version, s2.GameVersion);
if (s1Compatible == s2Compatible) { return 0; }
return s1Compatible ? -1 : 1;
case ColumnLabel.ServerListHasPassword:
if (s1.HasPassword == s2.HasPassword) { return 0; }
return s1.HasPassword ? 1 : -1;
case ColumnLabel.ServerListName:
// I think we actually want culture-specific sorting here?
return string.Compare(s1.ServerName, s2.ServerName, StringComparison.CurrentCulture);
case ColumnLabel.ServerListRoundStarted:
if (s1.GameStarted == s2.GameStarted) { return 0; }
return s1.GameStarted ? 1 : -1;
case ColumnLabel.ServerListPlayers:
return s2.PlayerCount.CompareTo(s1.PlayerCount);
case ColumnLabel.ServerListPing:
return (s1.Ping.TryUnwrap(out var s1Ping), s2.Ping.TryUnwrap(out var s2Ping)) switch
{
(false, false) => 0,
(true, true) => s2Ping.CompareTo(s1Ping),
(false, true) => 1,
(true, false) => -1
};
default:
return 0;
}
}
public override void Select() public override void Select()
{ {
base.Select(); base.Select();
@@ -821,6 +950,7 @@ namespace Barotrauma
UpdateFriendsList(); UpdateFriendsList();
panelAnimator?.Update(); panelAnimator?.Update();
scanServersButton.Enabled = (DateTime.Now - lastRefreshTime) >= AllowedRefreshInterval; scanServersButton.Enabled = (DateTime.Now - lastRefreshTime) >= AllowedRefreshInterval;
if (PlayerInput.PrimaryMouseButtonClicked()) if (PlayerInput.PrimaryMouseButtonClicked())
@@ -840,7 +970,7 @@ namespace Barotrauma
RemoveMsgFromServerList(MsgUserData.NoMatchingServers); RemoveMsgFromServerList(MsgUserData.NoMatchingServers);
foreach (GUIComponent child in serverList.Content.Children) foreach (GUIComponent child in serverList.Content.Children)
{ {
if (!(child.UserData is ServerInfo serverInfo)) { continue; } if (child.UserData is not ServerInfo serverInfo) { continue; }
child.Visible = ShouldShowServer(serverInfo); child.Visible = ShouldShowServer(serverInfo);
} }
@@ -851,6 +981,20 @@ namespace Barotrauma
serverList.UpdateScrollBarSize(); serverList.UpdateScrollBarSize();
} }
private bool AllLanguagesVisible
{
get
{
if (languageDropdown is null) { return true; }
// CountChildren-1 because there's a separator element in there that can't be selected
int tickBoxCount = languageDropdown.ListBox.Content.CountChildren - 1;
int selectedCount = languageDropdown.SelectedIndexMultiple.Count();
return selectedCount >= tickBoxCount;
}
}
private bool ShouldShowServer(ServerInfo serverInfo) private bool ShouldShowServer(ServerInfo serverInfo)
{ {
#if !DEBUG #if !DEBUG
@@ -918,6 +1062,14 @@ namespace Barotrauma
} }
} }
if (!AllLanguagesVisible)
{
if (!languageDropdown.SelectedDataMultiple.OfType<LanguageIdentifier>().Contains(serverInfo.Language))
{
return false;
}
}
foreach (GUITickBox tickBox in gameModeTickBoxes.Values) foreach (GUITickBox tickBox in gameModeTickBoxes.Values)
{ {
var gameMode = (Identifier)tickBox.UserData; var gameMode = (Identifier)tickBox.UserData;
@@ -1031,8 +1183,8 @@ namespace Barotrauma
if (!(userdata is FriendInfo { IsInServer: true } info)) { return false; } if (!(userdata is FriendInfo { IsInServer: true } info)) { return false; }
if (info.IsInServer if (info.IsInServer
&& info.ConnectCommand is Some<ConnectCommand> { Value: { EndpointOrLobby: var endpointOrLobby } } && info.ConnectCommand.TryUnwrap(out var command)
&& endpointOrLobby.TryGet(out ConnectCommand.NameAndEndpoint nameAndEndpoint)) && command.EndpointOrLobby.TryGet(out ConnectCommand.NameAndEndpoint nameAndEndpoint))
{ {
const int framePadding = 5; const int framePadding = 5;
@@ -1270,7 +1422,7 @@ namespace Barotrauma
serverPreview.Content.ClearChildren(); serverPreview.Content.ClearChildren();
panelAnimator.RightEnabled = false; panelAnimator.RightEnabled = false;
joinButton.Enabled = false; joinButton.Enabled = false;
selectedServer = null; selectedServer = Option.None;
if (selectedTab == TabEnum.All) if (selectedTab == TabEnum.All)
{ {
@@ -1370,8 +1522,7 @@ namespace Barotrauma
UpdateServerInfoUI(serverInfo); UpdateServerInfoUI(serverInfo);
if (!skipPing) { PingUtils.GetServerPing(serverInfo, UpdateServerInfoUI); } if (!skipPing) { PingUtils.GetServerPing(serverInfo, UpdateServerInfoUI); }
SortList(sortedBy, toggle: false); InsertServer(serverInfo, serverFrame);
FilterServers();
} }
private void UpdateServerInfoUI(ServerInfo serverInfo) private void UpdateServerInfoUI(ServerInfo serverInfo)
@@ -1629,7 +1780,7 @@ namespace Barotrauma
#endif #endif
} }
private Color GetPingTextColor(int ping) private static Color GetPingTextColor(int ping)
{ {
if (ping < 0) { return Color.DarkRed; } if (ping < 0) { return Color.DarkRed; }
return ToolBox.GradientLerp(ping / 200.0f, GUIStyle.Green, GUIStyle.Orange, GUIStyle.Red); return ToolBox.GradientLerp(ping / 200.0f, GUIStyle.Green, GUIStyle.Orange, GUIStyle.Red);
@@ -1664,6 +1815,7 @@ namespace Barotrauma
{ {
ServerListFilters.Instance.SetAttribute(ternaryFilter.Key, ternaryFilter.Value.SelectedData.ToString()); ServerListFilters.Instance.SetAttribute(ternaryFilter.Key, ternaryFilter.Value.SelectedData.ToString());
} }
GameSettings.SaveCurrentConfig();
} }
public void LoadServerFilters() public void LoadServerFilters()

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