Merge branch 'master' of https://github.com/Regalis11/Barotrauma into develop

This commit is contained in:
EvilFactory
2023-12-14 11:56:39 -03:00
376 changed files with 7775 additions and 2879 deletions
+1 -1
View File
@@ -73,7 +73,7 @@ body:
label: Version label: Version
description: Which version of the game did the bug happen in? You can see the current version number in the bottom left corner of your screen in the main menu. description: Which version of the game did the bug happen in? You can see the current version number in the bottom left corner of your screen in the main menu.
options: options:
- v1.1.19.3 (Treacherous Tides Hotfix 2) - v1.2.6.0 (Winter Update)
- Other - Other
validations: validations:
required: true required: true
@@ -6,7 +6,7 @@ using System;
namespace Barotrauma namespace Barotrauma
{ {
class Camera : IDisposable class Camera
{ {
public static bool FollowSub = true; public static bool FollowSub = true;
@@ -147,21 +147,10 @@ namespace Barotrauma
position = Vector2.Zero; position = Vector2.Zero;
CreateMatrices(); CreateMatrices();
// TODO: this has the potential to cause a resource leak
// by sneakily creating a reference to cameras that we might
// fail to release.
GameMain.Instance.ResolutionChanged += CreateMatrices;
UpdateTransform(false); UpdateTransform(false);
} }
private bool disposed = false;
public void Dispose()
{
if (!disposed) { GameMain.Instance.ResolutionChanged -= CreateMatrices; }
disposed = true;
}
public Vector2 TargetPos { get; set; } public Vector2 TargetPos { get; set; }
public Vector2 GetPosition() public Vector2 GetPosition()
@@ -207,6 +196,12 @@ namespace Barotrauma
public void UpdateTransform(bool interpolate = true, bool updateListener = true) public void UpdateTransform(bool interpolate = true, bool updateListener = true)
{ {
if (GameMain.GraphicsWidth != Resolution.X ||
GameMain.GraphicsHeight != Resolution.Y)
{
CreateMatrices();
}
Vector2 interpolatedPosition = interpolate ? Timing.Interpolate(prevPosition, position) : position; Vector2 interpolatedPosition = interpolate ? Timing.Interpolate(prevPosition, position) : position;
float interpolatedZoom = interpolate ? Timing.Interpolate(prevZoom, zoom) : zoom; float interpolatedZoom = interpolate ? Timing.Interpolate(prevZoom, zoom) : zoom;
@@ -136,6 +136,7 @@ namespace Barotrauma
set set
{ {
if (!MathUtils.IsValid(value)) { return; } if (!MathUtils.IsValid(value)) { return; }
if (this != Controlled) { return; }
if (Screen.Selected?.Cam != null) if (Screen.Selected?.Cam != null)
{ {
Screen.Selected.Cam.Shake = value; Screen.Selected.Cam.Shake = value;
@@ -229,6 +230,8 @@ namespace Barotrauma
} }
} }
private float pressureEffectTimer;
private readonly List<ObjectiveEntity> activeObjectiveEntities = new List<ObjectiveEntity>(); private readonly List<ObjectiveEntity> activeObjectiveEntities = new List<ObjectiveEntity>();
public IEnumerable<ObjectiveEntity> ActiveObjectiveEntities public IEnumerable<ObjectiveEntity> ActiveObjectiveEntities
{ {
@@ -333,18 +336,22 @@ namespace Barotrauma
{ {
if (!IsProtectedFromPressure && (AnimController.CurrentHull == null || AnimController.CurrentHull.LethalPressure > 0.0f)) if (!IsProtectedFromPressure && (AnimController.CurrentHull == null || AnimController.CurrentHull.LethalPressure > 0.0f))
{ {
float pressure = AnimController.CurrentHull == null ? 100.0f : AnimController.CurrentHull.LethalPressure; //wait until the character has been in pressure for one second so the zoom doesn't
if (pressure > 0.0f) //"flicker" in and out if the pressure fluctuates around the minimum threshold
pressureEffectTimer += deltaTime;
if (pressureEffectTimer > 1.0f)
{ {
//lerp in during the 1st second of the pressure timer so the zoom doesn't float pressure = AnimController.CurrentHull == null ? 100.0f : AnimController.CurrentHull.LethalPressure;
//"flicker" in and out if the pressure fluctuates around the minimum threshold float zoomInEffectStrength = MathHelper.Clamp(pressure / 100.0f, 0.0f, 1.0f);
float timerMultiplier = (PressureTimer / 100.0f);
float zoomInEffectStrength = MathHelper.Clamp(pressure / 100.0f * timerMultiplier, 0.0f, 1.0f);
cam.Zoom = MathHelper.Lerp(cam.Zoom, cam.Zoom = MathHelper.Lerp(cam.Zoom,
cam.DefaultZoom + (Math.Max(pressure, 10) / 150.0f) * Rand.Range(0.9f, 1.1f), cam.DefaultZoom + (Math.Max(pressure, 10) / 150.0f) * Rand.Range(0.9f, 1.1f),
zoomInEffectStrength); zoomInEffectStrength);
} }
} }
else
{
pressureEffectTimer = 0.0f;
}
if (IsHumanoid) if (IsHumanoid)
{ {
@@ -521,22 +528,25 @@ namespace Barotrauma
if (controlled == this) if (controlled == this)
{ {
controlled = null; controlled = null;
if (!(Screen.Selected?.Cam is null)) if (Screen.Selected?.Cam is not null)
{ {
Screen.Selected.Cam.TargetPos = Vector2.Zero; Screen.Selected.Cam.TargetPos = Vector2.Zero;
Lights.LightManager.ViewTarget = null; Lights.LightManager.ViewTarget = null;
} }
} }
sounds.ForEach(s => s.Sound?.Dispose());
sounds.Clear();
if (GameMain.GameSession?.CrewManager != null && if (GameMain.GameSession?.CrewManager != null &&
GameMain.GameSession.CrewManager.GetCharacters().Contains(this)) GameMain.GameSession.CrewManager.GetCharacters().Contains(this))
{ {
GameMain.GameSession.CrewManager.RemoveCharacter(this); GameMain.GameSession.CrewManager.RemoveCharacter(this);
} }
if (GameMain.Client?.Character == this) GameMain.Client.Character = null; if (GameMain.Client?.Character == this) { GameMain.Client.Character = null; }
if (Lights.LightManager.ViewTarget == this) Lights.LightManager.ViewTarget = null; if (Lights.LightManager.ViewTarget == this) { Lights.LightManager.ViewTarget = null; }
} }
@@ -894,7 +894,7 @@ namespace Barotrauma
if (!orderIndicatorCount.ContainsKey(target)) { orderIndicatorCount.Add(target, 0); } if (!orderIndicatorCount.ContainsKey(target)) { orderIndicatorCount.Add(target, 0); }
Vector2 drawPos = target is Entity ? (target as Entity).DrawPosition : Vector2 drawPos = target is Entity entity ? entity.DrawPosition :
target.Submarine == null ? target.Position : target.Position + target.Submarine.DrawPosition; target.Submarine == null ? target.Position : target.Position + target.Submarine.DrawPosition;
drawPos += Vector2.UnitX * order.SymbolSprite.size.X * 1.5f * orderIndicatorCount[target]; drawPos += Vector2.UnitX * order.SymbolSprite.size.X * 1.5f * orderIndicatorCount[target];
GUI.DrawIndicator(spriteBatch, drawPos, cam, 100.0f, order.SymbolSprite, order.Color * iconAlpha, GUI.DrawIndicator(spriteBatch, drawPos, cam, 100.0f, order.SymbolSprite, order.Color * iconAlpha,
@@ -217,6 +217,7 @@ namespace Barotrauma
if ((int)newLevel > (int)prevLevel) if ((int)newLevel > (int)prevLevel)
{ {
Character.Controlled?.SelectedItem?.OnPlayerSkillsChanged();
int increase = Math.Max((int)newLevel - (int)prevLevel, 1); int increase = Math.Max((int)newLevel - (int)prevLevel, 1);
Character?.AddMessage( Character?.AddMessage(
@@ -518,7 +519,7 @@ namespace Barotrauma
attachment.Sprite.Draw(spriteBatch, drawPos, color ?? Color.White, origin, rotate: 0, scale: scale, depth: depth, spriteEffect: spriteEffects); attachment.Sprite.Draw(spriteBatch, drawPos, color ?? Color.White, origin, rotate: 0, scale: scale, depth: depth, spriteEffect: spriteEffects);
} }
public static CharacterInfo ClientRead(Identifier speciesName, IReadMessage inc) public static CharacterInfo ClientRead(Identifier speciesName, IReadMessage inc, bool requireJobPrefabFound = true)
{ {
ushort infoID = inc.ReadUInt16(); ushort infoID = inc.ReadUInt16();
string newName = inc.ReadString(); string newName = inc.ReadString();
@@ -554,14 +555,19 @@ 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) if (jobPrefab == null && requireJobPrefabFound)
{ {
throw new Exception($"Error while reading {nameof(CharacterInfo)} received from the server: could not find a job prefab with the identifier \"{jobIdentifier}\"."); 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)) byte skillCount = inc.ReadByte();
List<SkillPrefab> jobSkills = jobPrefab?.Skills.OrderBy(s => s.Identifier).ToList();
for (int i = 0; i < skillCount; i++)
{ {
float skillLevel = inc.ReadSingle(); float skillLevel = inc.ReadSingle();
skillLevels.Add(skillPrefab.Identifier, skillLevel); if (jobSkills != null && i < jobSkills.Count)
{
skillLevels.Add(jobSkills[i].Identifier, skillLevel);
}
} }
} }
@@ -2170,6 +2170,8 @@ namespace Barotrauma
medUIExtra?.Remove(); medUIExtra?.Remove();
medUIExtra = null; medUIExtra = null;
Character.OnAttacked -= OnAttacked;
limbIndicatorOverlay?.Remove(); limbIndicatorOverlay?.Remove();
limbIndicatorOverlay = null; limbIndicatorOverlay = null;
@@ -1216,7 +1216,7 @@ namespace Barotrauma
pos: new Vector2(body.DrawPosition.X, -body.DrawPosition.Y), pos: new Vector2(body.DrawPosition.X, -body.DrawPosition.Y),
srcRect: w.Sprite.SourceRect, srcRect: w.Sprite.SourceRect,
color: Color.White, color: Color.White,
rotation: rotation, rotationRad: rotation,
origin: origin, origin: origin,
scale: new Vector2(scale, scale), scale: new Vector2(scale, scale),
effects: spriteEffect, effects: spriteEffect,
@@ -34,7 +34,7 @@ namespace Barotrauma
bool allowCheats = GameMain.NetworkMember == null && (GameMain.GameSession?.GameMode is TestGameMode || Screen.Selected is { IsEditor: true }); bool allowCheats = GameMain.NetworkMember == null && (GameMain.GameSession?.GameMode is TestGameMode || Screen.Selected is { IsEditor: true });
if (!allowCheats && !CheatsEnabled && IsCheat) if (!allowCheats && !CheatsEnabled && IsCheat)
{ {
NewMessage("You need to enable cheats using the command \"enablecheats\" before you can use the command \"" + names[0] + "\".", Color.Red); NewMessage("You need to enable cheats using the command \"enablecheats\" before you can use the command \"" + Names[0] + "\".", Color.Red);
#if USE_STEAM #if USE_STEAM
NewMessage("Enabling cheats will disable Steam achievements during this play session.", Color.Red); NewMessage("Enabling cheats will disable Steam achievements during this play session.", Color.Red);
#endif #endif
@@ -215,11 +215,11 @@ namespace Barotrauma
SoundPlayer.PlayUISound(GUISoundType.Select); SoundPlayer.PlayUISound(GUISoundType.Select);
} }
private static bool IsCommandPermitted(string command, GameClient client) private static bool IsCommandPermitted(Identifier command, GameClient client)
{ {
if (GameMain.LuaCs.Game.IsCustomCommandPermitted(command)) { return true; } if (GameMain.LuaCs.Game.IsCustomCommandPermitted(command)) { return true; }
switch (command) switch (command.Value.ToLowerInvariant())
{ {
case "kick": case "kick":
return client.HasPermission(ClientPermissions.Kick); return client.HasPermission(ClientPermissions.Kick);
@@ -306,7 +306,7 @@ namespace Barotrauma
} }
}; };
var textBlock = new GUITextBlock(new RectTransform(new Point(listBox.Content.Rect.Width - 5, 0), textContainer.RectTransform, Anchor.TopLeft) { AbsoluteOffset = new Point(2, 2) }, var textBlock = new GUITextBlock(new RectTransform(new Point(listBox.Content.Rect.Width - 5, 0), textContainer.RectTransform, Anchor.TopLeft) { AbsoluteOffset = new Point(2, 2) },
msg.Text, textAlignment: Alignment.TopLeft, font: GUIStyle.SmallFont, wrap: true) RichString.Rich(msg.Text), textAlignment: Alignment.TopLeft, font: GUIStyle.SmallFont, wrap: true)
{ {
CanBeFocused = false, CanBeFocused = false,
TextColor = msg.Color TextColor = msg.Color
@@ -348,7 +348,7 @@ namespace Barotrauma
CanBeFocused = false CanBeFocused = false
}; };
var textBlock = new GUITextBlock(new RectTransform(new Point(listBox.Content.Rect.Width - 170, 0), textContainer.RectTransform, Anchor.TopRight) { AbsoluteOffset = new Point(20, 0) }, var textBlock = new GUITextBlock(new RectTransform(new Point(listBox.Content.Rect.Width - 170, 0), textContainer.RectTransform, Anchor.TopRight) { AbsoluteOffset = new Point(20, 0) },
command.help, textAlignment: Alignment.TopLeft, font: GUIStyle.SmallFont, wrap: true) command.Help, textAlignment: Alignment.TopLeft, font: GUIStyle.SmallFont, wrap: true)
{ {
CanBeFocused = false, CanBeFocused = false,
TextColor = Color.White TextColor = Color.White
@@ -356,7 +356,7 @@ namespace Barotrauma
textContainer.RectTransform.NonScaledSize = new Point(textContainer.RectTransform.NonScaledSize.X, textBlock.RectTransform.NonScaledSize.Y + 5); textContainer.RectTransform.NonScaledSize = new Point(textContainer.RectTransform.NonScaledSize.X, textBlock.RectTransform.NonScaledSize.Y + 5);
textBlock.SetTextPos(); textBlock.SetTextPos();
new GUITextBlock(new RectTransform(new Point(150, textContainer.Rect.Height), textContainer.RectTransform), new GUITextBlock(new RectTransform(new Point(150, textContainer.Rect.Height), textContainer.RectTransform),
command.names[0], textAlignment: Alignment.TopLeft); command.Names[0].Value, textAlignment: Alignment.TopLeft);
listBox.UpdateScrollBarSize(); listBox.UpdateScrollBarSize();
listBox.BarScroll = 1.0f; listBox.BarScroll = 1.0f;
@@ -366,7 +366,7 @@ namespace Barotrauma
private static void AssignOnClientExecute(string names, Action<string[]> onClientExecute) private static void AssignOnClientExecute(string names, Action<string[]> onClientExecute)
{ {
Command command = commands.Find(c => c.names.Intersect(names.Split('|')).Count() > 0); Command command = commands.Find(c => c.Names.Intersect(names.Split('|').ToIdentifiers()).Any());
if (command == null) if (command == null)
{ {
throw new Exception("AssignOnClientExecute failed. Command matching the name(s) \"" + names + "\" not found."); throw new Exception("AssignOnClientExecute failed. Command matching the name(s) \"" + names + "\" not found.");
@@ -380,7 +380,7 @@ namespace Barotrauma
private static void AssignRelayToServer(string names, bool relay) private static void AssignRelayToServer(string names, bool relay)
{ {
Command command = commands.Find(c => c.names.Intersect(names.Split('|')).Count() > 0); Command command = commands.Find(c => c.Names.Intersect(names.Split('|').ToIdentifiers()).Any());
if (command == null) if (command == null)
{ {
DebugConsole.Log("Could not assign to relay to server: " + names); DebugConsole.Log("Could not assign to relay to server: " + names);
@@ -691,6 +691,7 @@ namespace Barotrauma
AssignRelayToServer("savebinds", false); AssignRelayToServer("savebinds", false);
AssignRelayToServer("spreadsheetexport", false); AssignRelayToServer("spreadsheetexport", false);
#if DEBUG #if DEBUG
AssignRelayToServer("listspamfilters", false);
AssignRelayToServer("crash", false); AssignRelayToServer("crash", false);
AssignRelayToServer("showballastflorasprite", false); AssignRelayToServer("showballastflorasprite", false);
AssignRelayToServer("simulatedlatency", false); AssignRelayToServer("simulatedlatency", false);
@@ -716,6 +717,8 @@ namespace Barotrauma
AssignRelayToServer("showmoney", true); AssignRelayToServer("showmoney", true);
AssignRelayToServer("setskill", true); AssignRelayToServer("setskill", true);
AssignRelayToServer("readycheck", true); AssignRelayToServer("readycheck", true);
commands.Add(new Command("debugjobassignment", "", (string[] args) => { }));
AssignRelayToServer("debugjobassignment", true);
AssignRelayToServer("givetalent", true); AssignRelayToServer("givetalent", true);
AssignRelayToServer("unlocktalents", true); AssignRelayToServer("unlocktalents", true);
@@ -2242,6 +2245,30 @@ namespace Barotrauma
})); }));
#if DEBUG #if DEBUG
commands.Add(new Command("listspamfilters", "Lists filters that are in the global spam filter.", (string[] args) =>
{
if (!SpamServerFilters.GlobalSpamFilter.TryUnwrap(out var filter))
{
ThrowError("Global spam list is not initialized.");
return;
}
if (!filter.Filters.Any())
{
NewMessage("Global spam list is empty.", GUIStyle.Green);
return;
}
StringBuilder sb = new();
foreach (var f in filter.Filters)
{
sb.AppendLine(f.ToString());
}
NewMessage(sb.ToString(), GUIStyle.Green);
}));
commands.Add(new Command("setplanthealth", "setplanthealth [value]: Sets the health of the selected plant in sub editor.", (string[] args) => commands.Add(new Command("setplanthealth", "setplanthealth [value]: Sets the health of the selected plant in sub editor.", (string[] args) =>
{ {
if (1 > args.Length || Screen.Selected != GameMain.SubEditorScreen) { return; } if (1 > args.Length || Screen.Selected != GameMain.SubEditorScreen) { return; }
@@ -3102,7 +3129,7 @@ namespace Barotrauma
int i = 0; int i = 0;
foreach (LocationConnection connection in campaign.Map.CurrentLocation.Connections) foreach (LocationConnection connection in campaign.Map.CurrentLocation.Connections)
{ {
NewMessage(" " + i + ". " + connection.OtherLocation(campaign.Map.CurrentLocation).Name, Color.White); NewMessage(" " + i + ". " + connection.OtherLocation(campaign.Map.CurrentLocation).DisplayName, Color.White);
i++; i++;
} }
ShowQuestionPrompt("Select a destination (0 - " + (campaign.Map.CurrentLocation.Connections.Count - 1) + "):", (string selectedDestination) => ShowQuestionPrompt("Select a destination (0 - " + (campaign.Map.CurrentLocation.Connections.Count - 1) + "):", (string selectedDestination) =>
@@ -374,13 +374,12 @@ namespace Barotrauma
btn.RectTransform.MinSize = new Point(0, (int)(btn.TextBlock.Rect.Height * 1.2f)); btn.RectTransform.MinSize = new Point(0, (int)(btn.TextBlock.Rect.Height * 1.2f));
} }
textContent.RectTransform.MinSize = new Point(0, textContent.Children.Sum(c => c.Rect.Height) + GUI.IntScale(16)); textContent.RectTransform.MinSize = new Point(0, textContent.Children.Sum(c => c.Rect.Height + textContent.AbsoluteSpacing) + GUI.IntScale(16));
content.RectTransform.MinSize = new Point(0, content.Children.Sum(c => c.Rect.Height)); content.RectTransform.MinSize = new Point(0, content.Children.Sum(c => c.Rect.Height));
// Recalculate the text size as it is scaled up and no longer matching the text height due to the textContent's minSize increasing // Recalculate the text size as it is scaled up and no longer matching the text height due to the textContent's minSize increasing
textBlock.CalculateHeightFromText(); textBlock.CalculateHeightFromText();
textBlock.TextAlignment = Alignment.TopLeft; textBlock.TextAlignment = Alignment.TopLeft;
//content.RectTransform.MinSize = new Point(0, textContent.Rect.Height);
return buttons; return buttons;
} }
@@ -16,6 +16,11 @@ partial class EventObjectiveAction : EventAction
int width = 450, int width = 450,
int height = 80) int height = 80)
{ {
if (Type == SegmentActionType.AddIfNotFound)
{
if (ObjectiveManager.IsSegmentActive(Identifier)) { return; }
}
ObjectiveManager.Segment? segment = null; ObjectiveManager.Segment? segment = null;
// Only need to create the segment when it's being triggered (otherwise the tutorial already has the segment instance) // Only need to create the segment when it's being triggered (otherwise the tutorial already has the segment instance)
if (Type == SegmentActionType.Trigger) if (Type == SegmentActionType.Trigger)
@@ -24,7 +29,8 @@ partial class EventObjectiveAction : EventAction
new ObjectiveManager.Segment.Text(TextTag, width, height, Anchor.Center), new ObjectiveManager.Segment.Text(TextTag, width, height, Anchor.Center),
new ObjectiveManager.Segment.Video(videoFile, TextTag, width, height)); new ObjectiveManager.Segment.Video(videoFile, TextTag, width, height));
} }
else if (Type == SegmentActionType.Add) else if (Type == SegmentActionType.Add ||
Type == SegmentActionType.AddIfNotFound)
{ {
segment = ObjectiveManager.Segment.CreateObjectiveSegment(Identifier, !ObjectiveTag.IsEmpty ? ObjectiveTag : Identifier); segment = ObjectiveManager.Segment.CreateObjectiveSegment(Identifier, !ObjectiveTag.IsEmpty ? ObjectiveTag : Identifier);
} }
@@ -33,10 +39,12 @@ partial class EventObjectiveAction : EventAction
segment.CanBeCompleted = CanBeCompleted; segment.CanBeCompleted = CanBeCompleted;
segment.ParentId = ParentObjectiveId; segment.ParentId = ParentObjectiveId;
} }
switch (Type) switch (Type)
{ {
case SegmentActionType.Trigger: case SegmentActionType.Trigger:
case SegmentActionType.Add: case SegmentActionType.Add:
case SegmentActionType.AddIfNotFound:
ObjectiveManager.TriggerSegment(segment); ObjectiveManager.TriggerSegment(segment);
break; break;
case SegmentActionType.Complete: case SegmentActionType.Complete:
@@ -1,22 +1,19 @@
#nullable enable
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma; namespace Barotrauma;
partial class TutorialHighlightAction : EventAction partial class HighlightAction : EventAction
{ {
private static readonly Color highlightColor = Color.Orange; partial void SetHighlightProjSpecific(Entity entity, IEnumerable<Character>? targetCharacters)
partial void UpdateProjSpecific()
{ {
if (GameMain.GameSession?.GameMode is not TutorialMode) { return; } if (targetCharacters != null && !targetCharacters.Contains(Character.Controlled))
foreach (var target in ParentEvent.GetTargets(TargetTag))
{ {
SetHighlight(target); return;
} }
}
private void SetHighlight(Entity entity)
{
if (entity is Item i) if (entity is Item i)
{ {
SetItemHighlight(i); SetItemHighlight(i);
@@ -47,7 +47,7 @@ namespace Barotrauma
} }
float theoreticalMaxMonsterStrength = 10000; float theoreticalMaxMonsterStrength = 10000;
float relativeMaxMonsterStrength = theoreticalMaxMonsterStrength * (GameMain.GameSession?.LevelData?.Difficulty ?? 0f) / 100; float relativeMaxMonsterStrength = theoreticalMaxMonsterStrength * (GameMain.GameSession?.Level?.Difficulty ?? 0f) / 100;
float absoluteMonsterStrength = monsterStrength / theoreticalMaxMonsterStrength; float absoluteMonsterStrength = monsterStrength / theoreticalMaxMonsterStrength;
float relativeMonsterStrength = monsterStrength / relativeMaxMonsterStrength; float relativeMonsterStrength = monsterStrength / relativeMaxMonsterStrength;
@@ -581,7 +581,14 @@ namespace Barotrauma
StatusEffect effect = StatusEffect.Load(subElement, $"EventManager.ClientRead ({eventIdentifier})"); StatusEffect effect = StatusEffect.Load(subElement, $"EventManager.ClientRead ({eventIdentifier})");
foreach (Entity target in targets) foreach (Entity target in targets)
{ {
effect.Apply(effect.type, 1.0f, target, target as ISerializableEntity); if (target is Item item)
{
effect.Apply(effect.type, 1.0f, item, item.AllPropertyObjects);
}
else
{
effect.Apply(effect.type, 1.0f, target, target as ISerializableEntity);
}
} }
} }
break; break;
@@ -51,7 +51,8 @@ namespace Barotrauma
if (requiredDeliveryAmount == 0) { requiredDeliveryAmount = items.Count; } if (requiredDeliveryAmount == 0) { requiredDeliveryAmount = items.Count; }
if (requiredDeliveryAmount > items.Count) if (requiredDeliveryAmount > items.Count)
{ {
DebugConsole.AddWarning($"Error in mission \"{Prefab.Identifier}\". Required delivery amount is {requiredDeliveryAmount} but there's only {items.Count} items to deliver."); DebugConsole.AddWarning($"Error in mission \"{Prefab.Identifier}\". Required delivery amount is {requiredDeliveryAmount} but there's only {items.Count} items to deliver.",
contentPackage: Prefab.ContentPackage);
requiredDeliveryAmount = items.Count; requiredDeliveryAmount = items.Count;
} }
} }
@@ -126,7 +126,13 @@ namespace Barotrauma
void GiveMissionExperience(CharacterInfo info) void GiveMissionExperience(CharacterInfo info)
{ {
if (info == null) { return; } if (info == null) { return; }
var experienceGainMultiplierIndividual = new AbilityMissionExperienceGainMultiplier(this, 1f); var experienceGainMultiplierIndividual = new AbilityMissionExperienceGainMultiplier(this, 1f, info.Character);
//check if anyone else in the crew has talents that could give a bonus to this one
foreach (var c in crew)
{
if (c == info.Character) { continue; }
c.CheckTalents(AbilityEffectType.OnAllyGainMissionExperience, experienceGainMultiplierIndividual);
}
info.Character?.CheckTalents(AbilityEffectType.OnGainMissionExperience, experienceGainMultiplierIndividual); info.Character?.CheckTalents(AbilityEffectType.OnGainMissionExperience, experienceGainMultiplierIndividual);
info.GiveExperience((int)(experienceGain * experienceGainMultiplierIndividual.Value)); info.GiveExperience((int)(experienceGain * experienceGainMultiplierIndividual.Value));
} }
@@ -247,30 +247,33 @@ namespace Barotrauma
UpdateCrew(); UpdateCrew();
} }
public void UpdateHireables()
{
UpdateHireables(campaign?.CurrentLocation);
}
private void UpdateHireables(Location location) private void UpdateHireables(Location location)
{ {
if (hireableList != null) if (hireableList == null) { return; }
hireableList.Content.Children.ToList().ForEach(c => hireableList.RemoveChild(c));
var hireableCharacters = location.GetHireableCharacters();
if (hireableCharacters.None())
{ {
hireableList.Content.Children.ToList().ForEach(c => hireableList.RemoveChild(c)); new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.2f), hireableList.Content.RectTransform), TextManager.Get("HireUnavailable"), textAlignment: Alignment.Center)
var hireableCharacters = location.GetHireableCharacters();
if (hireableCharacters.None())
{ {
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.2f), hireableList.Content.RectTransform), TextManager.Get("HireUnavailable"), textAlignment: Alignment.Center) CanBeFocused = false
{ };
CanBeFocused = false
};
}
else
{
foreach (CharacterInfo c in hireableCharacters)
{
if (c == null) { continue; }
CreateCharacterFrame(c, hireableList);
}
}
sortingDropDown.SelectItem(SortingMethod.JobAsc);
hireableList.UpdateScrollBarSize();
} }
else
{
foreach (CharacterInfo c in hireableCharacters)
{
if (c == null) { continue; }
CreateCharacterFrame(c, hireableList);
}
}
sortingDropDown.SelectItem(SortingMethod.JobAsc);
hireableList.UpdateScrollBarSize();
} }
public void SetHireables(Location location, List<CharacterInfo> availableHires) public void SetHireables(Location location, List<CharacterInfo> availableHires)
@@ -434,7 +437,7 @@ namespace Barotrauma
if (listBox != crewList) if (listBox != crewList)
{ {
new GUITextBlock(new RectTransform(new Vector2(width, 1.0f), mainGroup.RectTransform), new GUITextBlock(new RectTransform(new Vector2(width, 1.0f), mainGroup.RectTransform),
TextManager.FormatCurrency(characterInfo.Salary), TextManager.FormatCurrency(HireManager.GetSalaryFor(characterInfo)),
textAlignment: Alignment.Center) textAlignment: Alignment.Center)
{ {
CanBeFocused = false CanBeFocused = false
@@ -692,11 +695,8 @@ namespace Barotrauma
private void SetTotalHireCost() private void SetTotalHireCost()
{ {
if (pendingList == null || totalBlock == null || validateHiresButton == null) { return; } if (pendingList == null || totalBlock == null || validateHiresButton == null) { return; }
int total = 0; var infos = pendingList.Content.Children.Select(static c => ((InfoSkill)c.UserData).CharacterInfo).ToArray();
pendingList.Content.Children.ForEach(c => int total = HireManager.GetSalaryFor(infos);
{
total += ((InfoSkill)c.UserData).CharacterInfo.Salary;
});
totalBlock.Text = TextManager.FormatCurrency(total); totalBlock.Text = TextManager.FormatCurrency(total);
bool enoughMoney = campaign == null || campaign.CanAfford(total); bool enoughMoney = campaign == null || campaign.CanAfford(total);
totalBlock.TextColor = enoughMoney ? Color.White : Color.Red; totalBlock.TextColor = enoughMoney ? Color.White : Color.Red;
@@ -718,14 +718,14 @@ namespace Barotrauma
if (nonDuplicateHires.None()) { return false; } if (nonDuplicateHires.None()) { return false; }
int total = nonDuplicateHires.Aggregate(0, (total, info) => total + info.Salary); int total = HireManager.GetSalaryFor(nonDuplicateHires);
if (!campaign.CanAfford(total)) { return false; } if (!campaign.CanAfford(total)) { return false; }
bool atLeastOneHired = false; bool atLeastOneHired = false;
foreach (CharacterInfo ci in nonDuplicateHires) foreach (CharacterInfo ci in nonDuplicateHires)
{ {
if (campaign.TryHireCharacter(campaign.Map.CurrentLocation, ci)) if (campaign.TryHireCharacter(campaign.Map.CurrentLocation, ci, Character.Controlled))
{ {
atLeastOneHired = true; atLeastOneHired = true;
} }
@@ -741,7 +741,7 @@ namespace Barotrauma
SelectCharacter(null, null, null); SelectCharacter(null, null, null);
var dialog = new GUIMessageBox( var dialog = new GUIMessageBox(
TextManager.Get("newcrewmembers"), TextManager.Get("newcrewmembers"),
TextManager.GetWithVariable("crewhiredmessage", "[location]", campaignUI?.Campaign?.Map?.CurrentLocation?.Name), TextManager.GetWithVariable("crewhiredmessage", "[location]", campaignUI?.Campaign?.Map?.CurrentLocation?.DisplayName),
new LocalizedString[] { TextManager.Get("Ok") }); new LocalizedString[] { TextManager.Get("Ok") });
dialog.Buttons[0].OnClicked += dialog.Close; dialog.Buttons[0].OnClicked += dialog.Close;
} }
@@ -512,10 +512,18 @@ namespace Barotrauma
soundStr += " (stopped)"; soundStr += " (stopped)";
clr *= 0.5f; clr *= 0.5f;
} }
else if (playingSoundChannel.Muffled) else
{ {
soundStr += " (muffled)"; if (playingSoundChannel.Muffled)
clr = Color.Lerp(clr, Color.LightGray, 0.5f); {
soundStr += " (muffled)";
clr = Color.Lerp(clr, Color.LightGray, 0.5f);
}
if (playingSoundChannel.FadingOutAndDisposing)
{
soundStr += ". Fading out...";
clr = Color.Lerp(clr, Color.Black, 0.15f);
}
} }
} }
@@ -2163,10 +2171,10 @@ namespace Barotrauma
}; };
} }
public static GUIMessageBox AskForConfirmation(LocalizedString header, LocalizedString body, Action onConfirm, Action onDeny = null) public static GUIMessageBox AskForConfirmation(LocalizedString header, LocalizedString body, Action onConfirm, Action onDeny = null, Vector2? relativeSize = null, Point? minSize = null)
{ {
LocalizedString[] buttons = { TextManager.Get("Ok"), TextManager.Get("Cancel") }; LocalizedString[] buttons = { TextManager.Get("Ok"), TextManager.Get("Cancel") };
GUIMessageBox msgBox = new GUIMessageBox(header, body, buttons, new Vector2(0.2f, 0.175f), minSize: new Point(300, 175)); GUIMessageBox msgBox = new GUIMessageBox(header, body, buttons, relativeSize: relativeSize ?? new Vector2(0.2f, 0.175f), minSize: minSize ?? new Point(300, 175));
// Cancel button // Cancel button
msgBox.Buttons[1].OnClicked = delegate msgBox.Buttons[1].OnClicked = delegate
@@ -775,23 +775,30 @@ namespace Barotrauma
toolTipBlock.UserData = toolTip; toolTipBlock.UserData = toolTip;
} }
toolTipBlock.RectTransform.AbsoluteOffset = CalculateOffset();
RectTransform.CalculateAnchorPoint(anchor, targetElement) +
RectTransform.CalculatePivotOffset(pivot, toolTipBlock.RectTransform.NonScaledSize);
if (toolTipBlock.Rect.Right > GameMain.GraphicsWidth - 10) if (toolTipBlock.Rect.Right > GameMain.GraphicsWidth - 10)
{ {
toolTipBlock.RectTransform.AbsoluteOffset -= new Point(toolTipBlock.Rect.Width + targetElement.Width, 0); anchor = RectTransform.MoveAnchorLeft(anchor);
pivot = (Pivot)RectTransform.MoveAnchorRight((Anchor)pivot);
CalculateOffset();
} }
if (toolTipBlock.Rect.Bottom > GameMain.GraphicsHeight - 10) if (toolTipBlock.Rect.Bottom > GameMain.GraphicsHeight - 10)
{ {
toolTipBlock.RectTransform.AbsoluteOffset -= new Point( anchor = RectTransform.MoveAnchorTop(anchor);
0, pivot = (Pivot)RectTransform.MoveAnchorBottom((Anchor)pivot);
toolTipBlock.Rect.Bottom - (GameMain.GraphicsHeight - 10)); CalculateOffset();
} }
toolTipBlock.SetTextPos(); toolTipBlock.SetTextPos();
toolTipBlock.DrawManually(spriteBatch); toolTipBlock.DrawManually(spriteBatch);
void CalculateOffset()
{
toolTipBlock.RectTransform.AbsoluteOffset =
RectTransform.CalculateAnchorPoint(anchor, targetElement) +
RectTransform.CalculatePivotOffset(pivot, toolTipBlock.RectTransform.NonScaledSize);
}
} }
#endregion #endregion
@@ -3,6 +3,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Barotrauma.Extensions; using Barotrauma.Extensions;
using Microsoft.Xna.Framework.Input;
namespace Barotrauma namespace Barotrauma
{ {
@@ -81,6 +82,8 @@ namespace Barotrauma
public bool FlashOnAutoCloseCondition { get; set; } public bool FlashOnAutoCloseCondition { get; set; }
public Action OnEnterPressed { get; set; }
public Type MessageBoxType => type; public Type MessageBoxType => type;
public static GUIComponent VisibleBox => MessageBoxes.LastOrDefault(); public static GUIComponent VisibleBox => MessageBoxes.LastOrDefault();
@@ -89,6 +92,10 @@ namespace Barotrauma
: this(headerText, text, new LocalizedString[] { "OK" }, relativeSize, minSize, type: type) : this(headerText, text, new LocalizedString[] { "OK" }, relativeSize, minSize, type: type)
{ {
this.Buttons[0].OnClicked = Close; this.Buttons[0].OnClicked = Close;
OnEnterPressed = () =>
{
Buttons[0].OnClicked(Buttons[0], Buttons[0].UserData);
};
} }
public GUIMessageBox(RichString headerText, RichString text, LocalizedString[] buttons, public GUIMessageBox(RichString headerText, RichString text, LocalizedString[] buttons,
@@ -516,6 +523,11 @@ namespace Barotrauma
protected override void Update(float deltaTime) protected override void Update(float deltaTime)
{ {
if (PlayerInput.KeyHit(Keys.Enter))
{
OnEnterPressed?.Invoke();
}
if (Draggable) if (Draggable)
{ {
GUIComponent parent = GUI.MouseOn?.Parent?.Parent; GUIComponent parent = GUI.MouseOn?.Parent?.Parent;
@@ -18,6 +18,20 @@ namespace Barotrauma
public GUIButton PlusButton { get; private set; } public GUIButton PlusButton { get; private set; }
public GUIButton MinusButton { get; private set; } public GUIButton MinusButton { get; private set; }
private void UpdatePlusMinusButtonVisibility()
{
if (ForceShowPlusMinusButtons
|| inputType == NumberType.Int
|| (inputType == NumberType.Float && MinValueFloat > float.MinValue && MaxValueFloat < float.MaxValue))
{
ShowPlusMinusButtons();
}
else
{
HidePlusMinusButtons();
}
}
private NumberType inputType; private NumberType inputType;
public NumberType InputType public NumberType InputType
{ {
@@ -26,15 +40,7 @@ namespace Barotrauma
{ {
if (inputType == value) { return; } if (inputType == value) { return; }
inputType = value; inputType = value;
if (inputType == NumberType.Int || UpdatePlusMinusButtonVisibility();
(inputType == NumberType.Float && MinValueFloat > float.MinValue && MaxValueFloat < float.MaxValue))
{
ShowPlusMinusButtons();
}
else
{
HidePlusMinusButtons();
}
} }
} }
@@ -46,15 +52,7 @@ namespace Barotrauma
{ {
minValueFloat = value; minValueFloat = value;
ClampFloatValue(); ClampFloatValue();
if (inputType == NumberType.Int || UpdatePlusMinusButtonVisibility();
(inputType == NumberType.Float && MinValueFloat > float.MinValue && MaxValueFloat < float.MaxValue))
{
ShowPlusMinusButtons();
}
else
{
HidePlusMinusButtons();
}
} }
} }
public float? MaxValueFloat public float? MaxValueFloat
@@ -64,15 +62,7 @@ namespace Barotrauma
{ {
maxValueFloat = value; maxValueFloat = value;
ClampFloatValue(); ClampFloatValue();
if (inputType == NumberType.Int || UpdatePlusMinusButtonVisibility();
(inputType == NumberType.Float && MinValueFloat > float.MinValue && MaxValueFloat < float.MaxValue))
{
ShowPlusMinusButtons();
}
else
{
HidePlusMinusButtons();
}
} }
} }
@@ -96,6 +86,19 @@ namespace Barotrauma
} }
} }
private bool forceShowPlusMinusButtons;
public bool ForceShowPlusMinusButtons
{
get { return forceShowPlusMinusButtons; }
set
{
if (forceShowPlusMinusButtons == value) { return; }
forceShowPlusMinusButtons = value;
UpdatePlusMinusButtonVisibility();
}
}
private int decimalsToDisplay = 1; private int decimalsToDisplay = 1;
public int DecimalsToDisplay public int DecimalsToDisplay
{ {
@@ -184,7 +187,7 @@ namespace Barotrauma
/// </summary> /// </summary>
public bool WrapAround; public bool WrapAround;
public float valueStep; public float ValueStep;
private float pressedTimer; private float pressedTimer;
private readonly float pressedDelay = 0.5f; private readonly float pressedDelay = 0.5f;
@@ -339,12 +342,12 @@ namespace Barotrauma
{ {
if (inputType == NumberType.Int) if (inputType == NumberType.Int)
{ {
IntValue -= valueStep > 0 ? (int)valueStep : 1; IntValue -= ValueStep > 0 ? (int)ValueStep : 1;
ClampIntValue(); ClampIntValue();
} }
else if (maxValueFloat.HasValue && minValueFloat.HasValue) else if (maxValueFloat.HasValue && minValueFloat.HasValue)
{ {
FloatValue -= valueStep > 0 ? valueStep : Round(); FloatValue -= ValueStep > 0 ? ValueStep : Round();
ClampFloatValue(); ClampFloatValue();
} }
} }
@@ -353,12 +356,12 @@ namespace Barotrauma
{ {
if (inputType == NumberType.Int) if (inputType == NumberType.Int)
{ {
IntValue += valueStep > 0 ? (int)valueStep : 1; IntValue += ValueStep > 0 ? (int)ValueStep : 1;
ClampIntValue(); ClampIntValue();
} }
else if (inputType == NumberType.Float) else if (inputType == NumberType.Float)
{ {
FloatValue += valueStep > 0 ? valueStep : Round(); FloatValue += ValueStep > 0 ? ValueStep : Round();
ClampFloatValue(); ClampFloatValue();
} }
} }
@@ -313,13 +313,18 @@ namespace Barotrauma
public class GUIColor : GUISelector<GUIColorPrefab> public class GUIColor : GUISelector<GUIColorPrefab>
{ {
public GUIColor(string identifier) : base(identifier) { } private readonly Color fallbackColor;
public GUIColor(string identifier, Color fallbackColor) : base(identifier)
{
this.fallbackColor = fallbackColor;
}
public Color Value public Color Value
{ {
get get
{ {
return Prefabs.ActivePrefab.Color; return Prefabs?.ActivePrefab?.Color ?? fallbackColor;
} }
} }
@@ -75,72 +75,72 @@ namespace Barotrauma
/// <summary> /// <summary>
/// General green color used for elements whose colors are set from code /// General green color used for elements whose colors are set from code
/// </summary> /// </summary>
public readonly static GUIColor Green = new GUIColor("Green"); public readonly static GUIColor Green = new GUIColor("Green", new Color(154, 213, 163, 255));
/// <summary> /// <summary>
/// General red color used for elements whose colors are set from code /// General red color used for elements whose colors are set from code
/// </summary> /// </summary>
public readonly static GUIColor Orange = new GUIColor("Orange"); public readonly static GUIColor Orange = new GUIColor("Orange", new Color(243, 162, 50, 255));
/// <summary> /// <summary>
/// General red color used for elements whose colors are set from code /// General red color used for elements whose colors are set from code
/// </summary> /// </summary>
public readonly static GUIColor Red = new GUIColor("Red"); public readonly static GUIColor Red = new GUIColor("Red", new Color(245, 105, 105, 255));
/// <summary> /// <summary>
/// General blue color used for elements whose colors are set from code /// General blue color used for elements whose colors are set from code
/// </summary> /// </summary>
public readonly static GUIColor Blue = new GUIColor("Blue"); public readonly static GUIColor Blue = new GUIColor("Blue", new Color(126, 211, 224, 255));
/// <summary> /// <summary>
/// General yellow color used for elements whose colors are set from code /// General yellow color used for elements whose colors are set from code
/// </summary> /// </summary>
public readonly static GUIColor Yellow = new GUIColor("Yellow"); public readonly static GUIColor Yellow = new GUIColor("Yellow", new Color(255, 255, 0, 255));
/// <summary> /// <summary>
/// Color to display the name of modded servers in the server list. /// Color to display the name of modded servers in the server list.
/// </summary> /// </summary>
public readonly static GUIColor ModdedServerColor = new GUIColor("ModdedServerColor"); public readonly static GUIColor ModdedServerColor = new GUIColor("ModdedServerColor", new Color(154, 185, 160, 255));
public readonly static GUIColor ColorInventoryEmpty = new GUIColor("ColorInventoryEmpty"); public readonly static GUIColor ColorInventoryEmpty = new GUIColor("ColorInventoryEmpty", new Color(245, 105, 105, 255));
public readonly static GUIColor ColorInventoryHalf = new GUIColor("ColorInventoryHalf"); public readonly static GUIColor ColorInventoryHalf = new GUIColor("ColorInventoryHalf", new Color(243, 162, 50, 255));
public readonly static GUIColor ColorInventoryFull = new GUIColor("ColorInventoryFull"); public readonly static GUIColor ColorInventoryFull = new GUIColor("ColorInventoryFull", new Color(96, 222, 146, 255));
public readonly static GUIColor ColorInventoryBackground = new GUIColor("ColorInventoryBackground"); public readonly static GUIColor ColorInventoryBackground = new GUIColor("ColorInventoryBackground", new Color(56, 56, 56, 255));
public readonly static GUIColor ColorInventoryEmptyOverlay = new GUIColor("ColorInventoryEmptyOverlay"); public readonly static GUIColor ColorInventoryEmptyOverlay = new GUIColor("ColorInventoryEmptyOverlay", new Color(125, 125, 125, 255));
public readonly static GUIColor TextColorNormal = new GUIColor("TextColorNormal"); public readonly static GUIColor TextColorNormal = new GUIColor("TextColorNormal", new Color(228, 217, 167, 255));
public readonly static GUIColor TextColorBright = new GUIColor("TextColorBright"); public readonly static GUIColor TextColorBright = new GUIColor("TextColorBright", new Color(255, 255, 255, 255));
public readonly static GUIColor TextColorDark = new GUIColor("TextColorDark"); public readonly static GUIColor TextColorDark = new GUIColor("TextColorDark", new Color(0, 0, 0, 230));
public readonly static GUIColor TextColorDim = new GUIColor("TextColorDim"); public readonly static GUIColor TextColorDim = new GUIColor("TextColorDim", new Color(153, 153, 153, 153));
public readonly static GUIColor ItemQualityColorPoor = new GUIColor("ItemQualityColorPoor"); public readonly static GUIColor ItemQualityColorPoor = new GUIColor("ItemQualityColorPoor", new Color(128, 128, 128, 255));
public readonly static GUIColor ItemQualityColorNormal = new GUIColor("ItemQualityColorNormal"); public readonly static GUIColor ItemQualityColorNormal = new GUIColor("ItemQualityColorNormal", new Color(255, 255, 255, 255));
public readonly static GUIColor ItemQualityColorGood = new GUIColor("ItemQualityColorGood"); public readonly static GUIColor ItemQualityColorGood = new GUIColor("ItemQualityColorGood", new Color(144, 238, 144, 255));
public readonly static GUIColor ItemQualityColorExcellent = new GUIColor("ItemQualityColorExcellent"); public readonly static GUIColor ItemQualityColorExcellent = new GUIColor("ItemQualityColorExcellent", new Color(173, 216, 230, 255));
public readonly static GUIColor ItemQualityColorMasterwork = new GUIColor("ItemQualityColorMasterwork"); public readonly static GUIColor ItemQualityColorMasterwork = new GUIColor("ItemQualityColorMasterwork", new Color(147, 112, 219, 255));
public readonly static GUIColor ColorReputationVeryLow = new GUIColor("ColorReputationVeryLow"); public readonly static GUIColor ColorReputationVeryLow = new GUIColor("ColorReputationVeryLow", new Color(192, 60, 60, 255));
public readonly static GUIColor ColorReputationLow = new GUIColor("ColorReputationLow"); public readonly static GUIColor ColorReputationLow = new GUIColor("ColorReputationLow", new Color(203, 145, 23, 255));
public readonly static GUIColor ColorReputationNeutral = new GUIColor("ColorReputationNeutral"); public readonly static GUIColor ColorReputationNeutral = new GUIColor("ColorReputationNeutral", new Color(228, 217, 167, 255));
public readonly static GUIColor ColorReputationHigh = new GUIColor("ColorReputationHigh"); public readonly static GUIColor ColorReputationHigh = new GUIColor("ColorReputationHigh", new Color(51, 152, 64, 255));
public readonly static GUIColor ColorReputationVeryHigh = new GUIColor("ColorReputationVeryHigh"); public readonly static GUIColor ColorReputationVeryHigh = new GUIColor("ColorReputationVeryHigh", new Color(71, 160, 164, 255));
// Inventory // Inventory
public readonly static GUIColor EquipmentSlotIconColor = new GUIColor("EquipmentSlotIconColor"); public readonly static GUIColor EquipmentSlotIconColor = new GUIColor("EquipmentSlotIconColor", new Color(99, 70, 64, 255));
// Health HUD // Health HUD
public readonly static GUIColor BuffColorLow = new GUIColor("BuffColorLow"); public readonly static GUIColor BuffColorLow = new GUIColor("BuffColorLow", new Color(66, 170, 73, 255));
public readonly static GUIColor BuffColorMedium = new GUIColor("BuffColorMedium"); public readonly static GUIColor BuffColorMedium = new GUIColor("BuffColorMedium", new Color(110, 168, 118, 255));
public readonly static GUIColor BuffColorHigh = new GUIColor("BuffColorHigh"); public readonly static GUIColor BuffColorHigh = new GUIColor("BuffColorHigh", new Color(154, 213, 163, 255));
public readonly static GUIColor DebuffColorLow = new GUIColor("DebuffColorLow"); public readonly static GUIColor DebuffColorLow = new GUIColor("DebuffColorLow", new Color(243, 162, 50, 255));
public readonly static GUIColor DebuffColorMedium = new GUIColor("DebuffColorMedium"); public readonly static GUIColor DebuffColorMedium = new GUIColor("DebuffColorMedium", new Color(155, 55, 55, 255));
public readonly static GUIColor DebuffColorHigh = new GUIColor("DebuffColorHigh"); public readonly static GUIColor DebuffColorHigh = new GUIColor("DebuffColorHigh", new Color(228, 27, 27, 255));
public readonly static GUIColor HealthBarColorLow = new GUIColor("HealthBarColorLow"); public readonly static GUIColor HealthBarColorLow = new GUIColor("HealthBarColorLow", new Color(255, 0, 0, 255));
public readonly static GUIColor HealthBarColorMedium = new GUIColor("HealthBarColorMedium"); public readonly static GUIColor HealthBarColorMedium = new GUIColor("HealthBarColorMedium", new Color(255, 165, 0, 255));
public readonly static GUIColor HealthBarColorHigh = new GUIColor("HealthBarColorHigh"); public readonly static GUIColor HealthBarColorHigh = new GUIColor("HealthBarColorHigh", new Color(78, 114, 88));
public readonly static GUIColor HealthBarColorPoisoned = new GUIColor("HealthBarColorPoisoned"); public readonly static GUIColor HealthBarColorPoisoned = new GUIColor("HealthBarColorPoisoned", new Color(100, 150, 0, 255));
private readonly static Point defaultItemFrameMargin = new Point(50, 56); private readonly static Point defaultItemFrameMargin = new Point(50, 56);
@@ -461,14 +461,16 @@ namespace Barotrauma
} }
private ImmutableArray<Vector2> cachedCaretPositions = ImmutableArray<Vector2>.Empty; private ImmutableArray<Vector2> cachedCaretPositions = ImmutableArray<Vector2>.Empty;
//which text were the cached caret positions calculated for?
private string cachedCaretPositionsText;
public ImmutableArray<Vector2> GetAllCaretPositions() public ImmutableArray<Vector2> GetAllCaretPositions()
{ {
if (cachedCaretPositions.Any()) string textDrawn = Censor ? CensoredText : Text.SanitizedValue;
if (cachedCaretPositions.Any() &&
textDrawn == cachedCaretPositionsText)
{ {
return cachedCaretPositions; return cachedCaretPositions;
} }
string textDrawn = Censor ? CensoredText : Text.SanitizedValue;
float w = Wrap float w = Wrap
? (Rect.Width - Padding.X - Padding.Z) / TextScale ? (Rect.Width - Padding.X - Padding.Z) / TextScale
: float.PositiveInfinity; : float.PositiveInfinity;
@@ -482,6 +484,7 @@ namespace Barotrauma
.Select(p => p - new Vector2(alignmentXDiff, 0)) .Select(p => p - new Vector2(alignmentXDiff, 0))
.Select(p => p * TextScale + TextPos - Origin * TextScale) .Select(p => p * TextScale + TextPos - Origin * TextScale)
.ToImmutableArray(); .ToImmutableArray();
cachedCaretPositionsText = textDrawn;
return cachedCaretPositions; return cachedCaretPositions;
} }
@@ -363,6 +363,10 @@ namespace Barotrauma
{ {
CaretIndex = Math.Clamp(CaretIndex, 0, textBlock.Text.Length); CaretIndex = Math.Clamp(CaretIndex, 0, textBlock.Text.Length);
var caretPositions = textBlock.GetAllCaretPositions(); var caretPositions = textBlock.GetAllCaretPositions();
if (CaretIndex >= caretPositions.Length)
{
throw new Exception($"Caret index was outside the bounds of the calculated caret positions. Index: {CaretIndex}, caret positions: {caretPositions.Length}, text: {textBlock.Text}");
}
caretPos = caretPositions[CaretIndex]; caretPos = caretPositions[CaretIndex];
caretPosDirty = false; caretPosDirty = false;
} }
@@ -784,11 +784,95 @@ namespace Barotrauma
#region Static methods #region Static methods
public static Pivot MatchPivotToAnchor(Anchor anchor) public static Pivot MatchPivotToAnchor(Anchor anchor)
{ {
if (!Enum.TryParse(anchor.ToString(), out Pivot pivot)) return (Pivot)anchor;
}
public static Anchor MatchAnchorToPivot(Pivot pivot)
{
return (Anchor)pivot;
}
/// <summary>
/// Moves the anchor to the left, keeping the vertical position unchanged (e.g. CenterRight -> CenterLeft)
/// </summary>
public static Anchor MoveAnchorLeft(Anchor anchor)
{
switch (anchor)
{ {
throw new Exception($"[RectTransform] Cannot match pivot to anchor {anchor}"); case Anchor.TopCenter:
case Anchor.TopRight:
return Anchor.TopLeft;
case Anchor.Center:
case Anchor.CenterRight:
return Anchor.CenterLeft;
case Anchor.BottomCenter:
case Anchor.BottomRight:
return Anchor.BottomLeft;
default:
return anchor;
}
}
/// <summary>
/// Moves the anchor to the right, keeping the vertical position unchanged (e.g. CenterLeft -> CenterRight)
/// </summary>
public static Anchor MoveAnchorRight(Anchor anchor)
{
switch (anchor)
{
case Anchor.TopCenter:
case Anchor.TopLeft:
return Anchor.TopRight;
case Anchor.Center:
case Anchor.CenterLeft:
return Anchor.CenterRight;
case Anchor.BottomCenter:
case Anchor.BottomLeft:
return Anchor.BottomRight;
default:
return anchor;
}
}
/// <summary>
/// Moves the anchor to the top, keeping the horizontal position unchanged (e.g. BottomCenter -> TopCenter)
/// </summary>
public static Anchor MoveAnchorTop(Anchor anchor)
{
switch (anchor)
{
case Anchor.CenterLeft:
case Anchor.BottomLeft:
return Anchor.TopLeft;
case Anchor.Center:
case Anchor.BottomCenter:
return Anchor.TopCenter;
case Anchor.CenterRight:
case Anchor.BottomRight:
return Anchor.TopRight;
default:
return anchor;
}
}
/// <summary>
/// Moves the anchor to the bottom, keeping the horizontal position unchanged (e.g. TopCenter -> BottomCenter)
/// </summary>
public static Anchor MoveAnchorBottom(Anchor anchor)
{
switch (anchor)
{
case Anchor.CenterLeft:
case Anchor.TopLeft:
return Anchor.BottomLeft;
case Anchor.Center:
case Anchor.TopCenter:
return Anchor.BottomCenter;
case Anchor.CenterRight:
case Anchor.TopRight:
return Anchor.BottomRight;
default:
return anchor;
} }
return pivot;
} }
/// <summary> /// <summary>
@@ -811,11 +895,11 @@ namespace Barotrauma
} }
} }
public static Point CalculatePivotOffset(Pivot pivot, Point size) public static Point CalculatePivotOffset(Pivot anchor, Point size)
{ {
int width = size.X; int width = size.X;
int height = size.Y; int height = size.Y;
switch (pivot) switch (anchor)
{ {
case Pivot.TopLeft: case Pivot.TopLeft:
return Point.Zero; return Point.Zero;
@@ -836,7 +920,7 @@ namespace Barotrauma
case Pivot.BottomRight: case Pivot.BottomRight:
return new Point(-width, -height); return new Point(-width, -height);
default: default:
throw new NotImplementedException(pivot.ToString()); throw new NotImplementedException(anchor.ToString());
} }
} }
@@ -40,6 +40,8 @@ namespace Barotrauma
private readonly List<PurchasedItem> itemsToSell = new List<PurchasedItem>(); private readonly List<PurchasedItem> itemsToSell = new List<PurchasedItem>();
private readonly List<PurchasedItem> itemsToSellFromSub = new List<PurchasedItem>(); private readonly List<PurchasedItem> itemsToSellFromSub = new List<PurchasedItem>();
private GUIMessageBox deliveryPrompt;
private StoreTab activeTab = StoreTab.Buy; private StoreTab activeTab = StoreTab.Buy;
private MapEntityCategory? selectedItemCategory; private MapEntityCategory? selectedItemCategory;
private bool suppressBuySell; private bool suppressBuySell;
@@ -341,9 +343,9 @@ namespace Barotrauma
}; };
var panelMaxWidth = (int)(GUI.xScale * (GUI.HorizontalAspectRatio < 1.4f ? 650 : 560)); var panelMaxWidth = (int)(GUI.xScale * (GUI.HorizontalAspectRatio < 1.4f ? 650 : 560));
var storeContent = new GUILayoutGroup(new RectTransform(new Vector2(0.45f, 1.0f), campaignUI.GetTabContainer(CampaignMode.InteractionType.Store).RectTransform) var storeContent = new GUILayoutGroup(new RectTransform(new Vector2(0.45f, 1.0f), campaignUI.GetTabContainer(CampaignMode.InteractionType.Store).RectTransform, Anchor.BottomLeft)
{ {
MaxSize = new Point(panelMaxWidth, campaignUI.GetTabContainer(CampaignMode.InteractionType.Store).Rect.Height) MaxSize = new Point(panelMaxWidth, campaignUI.GetTabContainer(CampaignMode.InteractionType.Store).Rect.Height - HUDLayoutSettings.ButtonAreaTop.Bottom)
}) })
{ {
Stretch = true, Stretch = true,
@@ -583,9 +585,9 @@ namespace Barotrauma
// Shopping Crate ------------------------------------------------------------------------------------------------------------------------------------------ // Shopping Crate ------------------------------------------------------------------------------------------------------------------------------------------
var shoppingCrateContent = new GUILayoutGroup(new RectTransform(new Vector2(0.45f, 1.0f), campaignUI.GetTabContainer(CampaignMode.InteractionType.Store).RectTransform, anchor: Anchor.TopRight) var shoppingCrateContent = new GUILayoutGroup(new RectTransform(new Vector2(0.45f, 1.0f), campaignUI.GetTabContainer(CampaignMode.InteractionType.Store).RectTransform, anchor: Anchor.BottomRight)
{ {
MaxSize = new Point(panelMaxWidth, campaignUI.GetTabContainer(CampaignMode.InteractionType.Store).Rect.Height) MaxSize = new Point(panelMaxWidth, campaignUI.GetTabContainer(CampaignMode.InteractionType.Store).Rect.Height - HUDLayoutSettings.ButtonAreaTop.Bottom)
}) })
{ {
Stretch = true, Stretch = true,
@@ -922,15 +924,12 @@ namespace Barotrauma
{ {
if (itemPrefab.CanBeBoughtFrom(ActiveStore, out PriceInfo priceInfo) && itemPrefab.CanCharacterBuy()) if (itemPrefab.CanBeBoughtFrom(ActiveStore, out PriceInfo priceInfo) && itemPrefab.CanCharacterBuy())
{ {
bool isDailySpecial = ActiveStore.DailySpecials.Contains(itemPrefab); bool isDailySpecial = ActiveStore.DailySpecials.Contains(itemPrefab);
var itemFrame = isDailySpecial ? var itemFrame = isDailySpecial ?
storeDailySpecialsGroup.FindChild(c => c.UserData is PurchasedItem pi && pi.ItemPrefab == itemPrefab) : storeDailySpecialsGroup.FindChild(c => c.UserData is PurchasedItem pi && pi.ItemPrefab == itemPrefab) :
storeBuyList.Content.FindChild(c => c.UserData is PurchasedItem pi && pi.ItemPrefab == itemPrefab); storeBuyList.Content.FindChild(c => c.UserData is PurchasedItem pi && pi.ItemPrefab == itemPrefab);
if (CargoManager.GetPurchasedItem(ActiveStore, itemPrefab) is { } purchasedItem)
{ quantity = Math.Max(quantity - CargoManager.GetPurchasedItemCount(ActiveStore, itemPrefab), 0);
quantity = Math.Max(quantity - purchasedItem.Quantity, 0);
}
if (CargoManager.GetBuyCrateItem(ActiveStore, itemPrefab) is { } buyCrateItem) if (CargoManager.GetBuyCrateItem(ActiveStore, itemPrefab) is { } buyCrateItem)
{ {
quantity = Math.Max(quantity - buyCrateItem.Quantity, 0); quantity = Math.Max(quantity - buyCrateItem.Quantity, 0);
@@ -1245,9 +1244,9 @@ namespace Barotrauma
int totalPrice = 0; int totalPrice = 0;
if (ActiveStore != null) if (ActiveStore != null)
{ {
foreach (PurchasedItem item in items) foreach (PurchasedItem item in items.ToList())
{ {
if (!(item.ItemPrefab.GetPriceInfo(ActiveStore) is { } priceInfo)) { continue; } if (item.ItemPrefab.GetPriceInfo(ActiveStore) is not { } priceInfo) { continue; }
GUINumberInput numInput = null; GUINumberInput numInput = null;
if (!(listBox.Content.FindChild(c => c.UserData is PurchasedItem pi && pi.ItemPrefab.Identifier == item.ItemPrefab.Identifier) is { } itemFrame)) if (!(listBox.Content.FindChild(c => c.UserData is PurchasedItem pi && pi.ItemPrefab.Identifier == item.ItemPrefab.Identifier) is { } itemFrame))
{ {
@@ -1749,7 +1748,7 @@ namespace Barotrauma
} }
// Add items already purchased // Add items already purchased
CargoManager?.GetPurchasedItems(ActiveStore).ForEach(pi => AddNonEmptyOwnedItems(pi)); CargoManager?.GetPurchasedItems(ActiveStore).Where(pi => !pi.DeliverImmediately).ForEach(pi => AddNonEmptyOwnedItems(pi));
ownedItemsUpdateTimer = 0.0f; ownedItemsUpdateTimer = 0.0f;
@@ -1959,14 +1958,13 @@ namespace Barotrauma
} }
catch (NotImplementedException e) catch (NotImplementedException e)
{ {
DebugConsole.LogError($"Error getting item availability: Uknown store tab type. {e.StackTrace.CleanupStackTrace()}"); DebugConsole.LogError($"Error getting item availability: Unknown store tab type. {e.StackTrace.CleanupStackTrace()}");
} }
if (list != null && list.Find(i => i.ItemPrefab == itemPrefab) is PurchasedItem item) if (list != null && list.Find(i => i.ItemPrefab == itemPrefab) is PurchasedItem item)
{ {
if (mode == StoreTab.Buy) if (mode == StoreTab.Buy)
{ {
var purchasedItem = CargoManager.GetPurchasedItem(ActiveStore, item.ItemPrefab); return Math.Max(item.Quantity - CargoManager.GetPurchasedItemCount(ActiveStore, item.ItemPrefab), 0);
if (purchasedItem != null) { return Math.Max(item.Quantity - purchasedItem.Quantity, 0); }
} }
return item.Quantity; return item.Quantity;
} }
@@ -2093,16 +2091,57 @@ namespace Barotrauma
} }
itemsToRemove.ForEach(i => itemsToPurchase.Remove(i)); itemsToRemove.ForEach(i => itemsToPurchase.Remove(i));
if (itemsToPurchase.None() || Balance < totalPrice) { return false; } if (itemsToPurchase.None() || Balance < totalPrice) { return false; }
CargoManager.PurchaseItems(ActiveStore.Identifier, itemsToPurchase, true);
GameMain.Client?.SendCampaignState(); if (CampaignMode.AllowImmediateItemDelivery())
var dialog = new GUIMessageBox( {
TextManager.Get("newsupplies"), deliveryPrompt = new GUIMessageBox(
TextManager.GetWithVariable("suppliespurchasedmessage", "[location]", campaignUI?.Campaign?.Map?.CurrentLocation?.Name), TextManager.Get("newsupplies"),
new LocalizedString[] { TextManager.Get("Ok") }); TextManager.Get("suppliespurchased.deliverymethod"),
dialog.Buttons[0].OnClicked += dialog.Close; new LocalizedString[]
{
TextManager.Get("suppliespurchased.deliverymethod.deliverimmediately"),
TextManager.Get("suppliespurchased.deliverymethod.delivertosub")
});
deliveryPrompt.Buttons[0].OnClicked = (btn, userdata) =>
{
ConfirmPurchase(deliverImmediately: true);
deliveryPrompt.Close();
return true;
};
deliveryPrompt.Buttons[1].OnClicked = (btn, userdata) =>
{
ConfirmPurchase(deliverImmediately: false);
deliveryPrompt.Close();
return true;
};
}
else
{
ConfirmPurchase(deliverImmediately: false);
}
void ConfirmPurchase(bool deliverImmediately)
{
itemsToPurchase.ForEach(it => it.DeliverImmediately = deliverImmediately);
CargoManager.PurchaseItems(ActiveStore.Identifier, itemsToPurchase, removeFromCrate: true);
GameMain.Client?.SendCampaignState();
if (!deliverImmediately)
{
var dialog = new GUIMessageBox(
TextManager.Get("newsupplies"),
TextManager.GetWithVariable("suppliespurchasedmessage", "[location]", campaignUI?.Campaign?.Map?.CurrentLocation?.DisplayName));
dialog.Buttons[0].OnClicked += dialog.Close;
}
}
return false; return false;
} }
public void OnDeselected()
{
deliveryPrompt?.Close();
deliveryPrompt = null;
}
private bool SellItems() private bool SellItems()
{ {
if (!HasActiveTabPermissions()) { return false; } if (!HasActiveTabPermissions()) { return false; }
@@ -2118,7 +2157,7 @@ namespace Barotrauma
} }
catch (NotImplementedException e) catch (NotImplementedException e)
{ {
DebugConsole.LogError($"Error confirming the store transaction: Uknown store tab type. {e.StackTrace.CleanupStackTrace()}"); DebugConsole.LogError($"Error confirming the store transaction: Unknown store tab type. {e.StackTrace.CleanupStackTrace()}");
return false; return false;
} }
var itemsToRemove = new List<PurchasedItem>(); var itemsToRemove = new List<PurchasedItem>();
@@ -130,7 +130,7 @@ namespace Barotrauma
}; };
content = new GUILayoutGroup(new RectTransform(new Point(background.Rect.Width - HUDLayoutSettings.Padding * 4, background.Rect.Height - HUDLayoutSettings.Padding * 4), background.RectTransform, Anchor.Center)) { AbsoluteSpacing = (int)(HUDLayoutSettings.Padding * 1.5f) }; content = new GUILayoutGroup(new RectTransform(new Point(background.Rect.Width - HUDLayoutSettings.Padding * 4, background.Rect.Height - HUDLayoutSettings.Padding * 4), background.RectTransform, Anchor.Center)) { AbsoluteSpacing = (int)(HUDLayoutSettings.Padding * 1.5f) };
GUITextBlock header = new GUITextBlock(new RectTransform(new Vector2(1f, 0.0f), content.RectTransform), transferService ? TextManager.Get("switchsubmarineheader") : TextManager.GetWithVariable("outpostshipyard", "[location]", GameMain.GameSession.Map.CurrentLocation.Name), font: GUIStyle.LargeFont); GUITextBlock header = new GUITextBlock(new RectTransform(new Vector2(1f, 0.0f), content.RectTransform), transferService ? TextManager.Get("switchsubmarineheader") : TextManager.GetWithVariable("outpostshipyard", "[location]", GameMain.GameSession.Map.CurrentLocation.DisplayName), font: GUIStyle.LargeFont);
header.CalculateHeightFromText(0, true); header.CalculateHeightFromText(0, true);
playerBalanceElement = CampaignUI.AddBalanceElement(header, new Vector2(1.0f, 1.5f)); playerBalanceElement = CampaignUI.AddBalanceElement(header, new Vector2(1.0f, 1.5f));
@@ -165,6 +165,11 @@ namespace Barotrauma
public TabMenu() public TabMenu()
{ {
if (!initialized) { Initialize(); } if (!initialized) { Initialize(); }
if (Level.Loaded == null)
{
//make sure we're not trying to view e.g. mission or reputation info if the tab menu is opened in the test mode
SelectedTab = InfoFrameTab.Crew;
}
CreateInfoFrame(SelectedTab); CreateInfoFrame(SelectedTab);
SelectInfoFrameTab(SelectedTab); SelectInfoFrameTab(SelectedTab);
} }
@@ -303,7 +308,7 @@ namespace Barotrauma
{ {
var missionBtn = createTabButton(InfoFrameTab.Mission, "mission"); var missionBtn = createTabButton(InfoFrameTab.Mission, "mission");
eventLogNotification = GameSession.CreateNotificationIcon(missionBtn); eventLogNotification = GameSession.CreateNotificationIcon(missionBtn);
eventLogNotification.Visible = GameMain.GameSession.EventManager?.EventLog?.UnreadEntries ?? false; eventLogNotification.Visible = GameMain.GameSession?.EventManager?.EventLog?.UnreadEntries ?? false;
if (eventLogNotification.Visible) if (eventLogNotification.Visible)
{ {
eventLogNotification.Pulsate(Vector2.One, Vector2.One * 2, 1.0f); eventLogNotification.Pulsate(Vector2.One, Vector2.One * 2, 1.0f);
@@ -1508,7 +1513,7 @@ namespace Barotrauma
portraitImage.RectTransform.NonScaledSize = new Point(Math.Min((int)(portraitImage.Rect.Size.Y * portraitAspectRatio), portraitImage.Rect.Width), portraitImage.Rect.Size.Y); portraitImage.RectTransform.NonScaledSize = new Point(Math.Min((int)(portraitImage.Rect.Size.Y * portraitAspectRatio), portraitImage.Rect.Width), portraitImage.Rect.Size.Y);
} }
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), locationInfoContainer.RectTransform), location.Name, font: GUIStyle.LargeFont); new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), locationInfoContainer.RectTransform), location.DisplayName, font: GUIStyle.LargeFont);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), locationInfoContainer.RectTransform), location.Type.Name, font: GUIStyle.SubHeadingFont); new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), locationInfoContainer.RectTransform), location.Type.Name, font: GUIStyle.SubHeadingFont);
if (location.Faction?.Prefab != null) if (location.Faction?.Prefab != null)
@@ -163,7 +163,7 @@ namespace Barotrauma
else if (Tile) else if (Tile)
{ {
Vector2 startPos = new Vector2(rect.X, rect.Y); Vector2 startPos = new Vector2(rect.X, rect.Y);
Sprite.DrawTiled(spriteBatch, startPos, new Vector2(rect.Width, rect.Height), color, startOffset: uvOffset); Sprite.DrawTiled(spriteBatch, startPos, new Vector2(rect.Width, rect.Height), color: color, startOffset: uvOffset);
} }
else else
{ {
@@ -1110,7 +1110,7 @@ namespace Barotrauma
public static UpgradeFrame CreateUpgradeFrame(UpgradePrefab prefab, UpgradeCategory category, CampaignMode campaign, RectTransform rectTransform, bool addBuyButton = true) public static UpgradeFrame CreateUpgradeFrame(UpgradePrefab prefab, UpgradeCategory category, CampaignMode campaign, RectTransform rectTransform, bool addBuyButton = true)
{ {
int price = prefab.Price.GetBuyPrice(campaign.UpgradeManager.GetUpgradeLevel(prefab, category), campaign.Map?.CurrentLocation, characterList); int price = prefab.Price.GetBuyPrice(prefab, campaign.UpgradeManager.GetUpgradeLevel(prefab, category), campaign.Map?.CurrentLocation, characterList);
return CreateUpgradeEntry(rectTransform, prefab.Sprite, prefab.Name, prefab.Description, price, new CategoryData(category, prefab), addBuyButton, upgradePrefab: prefab, currentLevel: campaign.UpgradeManager.GetUpgradeLevel(prefab, category)); return CreateUpgradeEntry(rectTransform, prefab.Sprite, prefab.Name, prefab.Description, price, new CategoryData(category, prefab), addBuyButton, upgradePrefab: prefab, currentLevel: campaign.UpgradeManager.GetUpgradeLevel(prefab, category));
} }
@@ -1267,7 +1267,7 @@ namespace Barotrauma
{ {
LocalizedString promptBody = TextManager.GetWithVariables("Upgrades.PurchasePromptBody", LocalizedString promptBody = TextManager.GetWithVariables("Upgrades.PurchasePromptBody",
("[upgradename]", prefab.Name), ("[upgradename]", prefab.Name),
("[amount]", prefab.Price.GetBuyPrice(Campaign.UpgradeManager.GetUpgradeLevel(prefab, category), Campaign.Map?.CurrentLocation, characterList).ToString())); ("[amount]", prefab.Price.GetBuyPrice(prefab, Campaign.UpgradeManager.GetUpgradeLevel(prefab, category), Campaign.Map?.CurrentLocation, characterList).ToString()));
currectConfirmation = EventEditorScreen.AskForConfirmation(TextManager.Get("Upgrades.PurchasePromptTitle"), promptBody, () => currectConfirmation = EventEditorScreen.AskForConfirmation(TextManager.Get("Upgrades.PurchasePromptTitle"), promptBody, () =>
{ {
if (GameMain.NetworkMember != null) if (GameMain.NetworkMember != null)
@@ -1682,7 +1682,7 @@ namespace Barotrauma
GUITextBlock priceLabel = (GUITextBlock)buttonParent.FindChild(UpgradeStoreUserData.PriceLabel, recursive: true); GUITextBlock priceLabel = (GUITextBlock)buttonParent.FindChild(UpgradeStoreUserData.PriceLabel, recursive: true);
priceLabel.Visible = true; priceLabel.Visible = true;
int price = prefab.Price.GetBuyPrice(campaign.UpgradeManager.GetUpgradeLevel(prefab, category), campaign.Map?.CurrentLocation, characterList); int price = prefab.Price.GetBuyPrice(prefab, campaign.UpgradeManager.GetUpgradeLevel(prefab, category), campaign.Map?.CurrentLocation, characterList);
if (!WaitForServerUpdate) if (!WaitForServerUpdate)
{ {
@@ -124,6 +124,10 @@ namespace Barotrauma
private Viewport defaultViewport; private Viewport defaultViewport;
/// <summary>
/// NOTE: Use very carefully. You need to ensure that you ALWAYS unsubscribe from this when you no longer need the subscriber!
/// If you're subscribing to this from something else than a singleton or something that there's only ever one instance of, you're probably in dangerous territory.
/// </summary>
public event Action ResolutionChanged; public event Action ResolutionChanged;
private bool exiting; private bool exiting;
@@ -407,7 +411,7 @@ namespace Barotrauma
//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); WaterRenderer.Instance = new WaterRenderer(base.GraphicsDevice);
Quad.Init(GraphicsDevice); GraphicsQuad.Init(GraphicsDevice);
loadingScreenOpen = true; loadingScreenOpen = true;
TitleScreen = new LoadingScreen(GraphicsDevice) TitleScreen = new LoadingScreen(GraphicsDevice)
@@ -147,7 +147,7 @@ namespace Barotrauma
} }
catch (NotImplementedException e) catch (NotImplementedException e)
{ {
DebugConsole.LogError($"Error selling items: uknown store tab type \"{sellingMode}\".\n{e.StackTrace.CleanupStackTrace()}"); DebugConsole.LogError($"Error selling items: unknown store tab type \"{sellingMode}\".\n{e.StackTrace.CleanupStackTrace()}");
return; return;
} }
bool canAddToRemoveQueue = campaign.IsSinglePlayer && Entity.Spawner != null; bool canAddToRemoveQueue = campaign.IsSinglePlayer && Entity.Spawner != null;
@@ -72,6 +72,9 @@ namespace Barotrauma
case InteractionType.MedicalClinic: case InteractionType.MedicalClinic:
CampaignUI.MedicalClinic?.OnDeselected(); CampaignUI.MedicalClinic?.OnDeselected();
break; break;
case InteractionType.Store:
CampaignUI.Store?.OnDeselected();
break;
} }
} }
@@ -121,6 +124,16 @@ namespace Barotrauma
{ {
return AllowedToManageCampaign(ClientPermissions.ManageMoney); return AllowedToManageCampaign(ClientPermissions.ManageMoney);
} }
public static bool AllowImmediateItemDelivery()
{
if (GameMain.Client == null) { return true; }
return
GameMain.Client.ServerSettings.AllowImmediateItemDelivery ||
GameMain.Client.HasPermission(ClientPermissions.ManageCampaign) ||
GameMain.Client.IsServerOwner;
}
protected GUIButton CreateEndRoundButton() protected GUIButton CreateEndRoundButton()
{ {
int buttonWidth = (int)(450 * GUI.xScale * (GUI.IsUltrawide ? 3.0f : 1.0f)); int buttonWidth = (int)(450 * GUI.xScale * (GUI.IsUltrawide ? 3.0f : 1.0f));
@@ -182,12 +195,12 @@ namespace Barotrauma
if (Level.Loaded.EndOutpost == null || !Level.Loaded.EndOutpost.DockedTo.Contains(leavingSub)) if (Level.Loaded.EndOutpost == null || !Level.Loaded.EndOutpost.DockedTo.Contains(leavingSub))
{ {
string textTag = availableTransition == TransitionType.ProgressToNextLocation ? "EnterLocation" : "EnterEmptyLocation"; string textTag = availableTransition == TransitionType.ProgressToNextLocation ? "EnterLocation" : "EnterEmptyLocation";
buttonText = TextManager.GetWithVariable(textTag, "[locationname]", Level.Loaded.EndLocation?.Name ?? "[ERROR]"); buttonText = TextManager.GetWithVariable(textTag, "[locationname]", Level.Loaded.EndLocation?.DisplayName ?? "[ERROR]");
allowEndingRound = !ForceMapUI && !ShowCampaignUI; allowEndingRound = !ForceMapUI && !ShowCampaignUI;
} }
break; break;
case TransitionType.LeaveLocation: case TransitionType.LeaveLocation:
buttonText = TextManager.GetWithVariable("LeaveLocation", "[locationname]", Level.Loaded.StartLocation?.Name ?? "[ERROR]"); buttonText = TextManager.GetWithVariable("LeaveLocation", "[locationname]", Level.Loaded.StartLocation?.DisplayName ?? "[ERROR]");
allowEndingRound = !ForceMapUI && !ShowCampaignUI; allowEndingRound = !ForceMapUI && !ShowCampaignUI;
break; break;
case TransitionType.ReturnToPreviousLocation: case TransitionType.ReturnToPreviousLocation:
@@ -195,7 +208,7 @@ namespace Barotrauma
if (Level.Loaded.StartOutpost == null || !Level.Loaded.StartOutpost.DockedTo.Contains(leavingSub)) if (Level.Loaded.StartOutpost == null || !Level.Loaded.StartOutpost.DockedTo.Contains(leavingSub))
{ {
string textTag = availableTransition == TransitionType.ReturnToPreviousLocation ? "EnterLocation" : "EnterEmptyLocation"; string textTag = availableTransition == TransitionType.ReturnToPreviousLocation ? "EnterLocation" : "EnterEmptyLocation";
buttonText = TextManager.GetWithVariable(textTag, "[locationname]", Level.Loaded.StartLocation?.Name ?? "[ERROR]"); buttonText = TextManager.GetWithVariable(textTag, "[locationname]", Level.Loaded.StartLocation?.DisplayName ?? "[ERROR]");
allowEndingRound = !ForceMapUI && !ShowCampaignUI; allowEndingRound = !ForceMapUI && !ShowCampaignUI;
} }
break; break;
@@ -211,7 +224,7 @@ namespace Barotrauma
endRoundButton.Color = GUIStyle.Red * 0.7f; endRoundButton.Color = GUIStyle.Red * 0.7f;
endRoundButton.HoverColor = GUIStyle.Red; endRoundButton.HoverColor = GUIStyle.Red;
} }
buttonText = TextManager.GetWithVariable("LeaveLocation", "[locationname]", Level.Loaded.StartLocation?.Name ?? "[ERROR]"); buttonText = TextManager.GetWithVariable("LeaveLocation", "[locationname]", Level.Loaded.StartLocation?.DisplayName ?? "[ERROR]");
allowEndingRound = !ForceMapUI && !ShowCampaignUI; allowEndingRound = !ForceMapUI && !ShowCampaignUI;
} }
else else
@@ -535,7 +535,7 @@ namespace Barotrauma
bool refreshCampaignUI = false; bool refreshCampaignUI = false;
if (!(GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign) || campaignID != campaign.CampaignID) if (GameMain.GameSession?.GameMode is not MultiPlayerCampaign campaign || campaignID != campaign.CampaignID)
{ {
string savePath = SaveUtil.CreateSavePath(SaveUtil.SaveType.Multiplayer); string savePath = SaveUtil.CreateSavePath(SaveUtil.SaveType.Multiplayer);
@@ -614,7 +614,7 @@ namespace Barotrauma
{ {
if (availableMission.ConnectionIndex < 0 || availableMission.ConnectionIndex >= campaign.Map.CurrentLocation.Connections.Count) if (availableMission.ConnectionIndex < 0 || availableMission.ConnectionIndex >= campaign.Map.CurrentLocation.Connections.Count)
{ {
DebugConsole.ThrowError($"Error when receiving campaign data from the server: connection index for mission \"{availableMission.Identifier}\" out of range (index: {availableMission.ConnectionIndex}, current location: {campaign.Map.CurrentLocation.Name}, connections: {campaign.Map.CurrentLocation.Connections.Count})."); DebugConsole.ThrowError($"Error when receiving campaign data from the server: connection index for mission \"{availableMission.Identifier}\" out of range (index: {availableMission.ConnectionIndex}, current location: {campaign.Map.CurrentLocation.DisplayName}, connections: {campaign.Map.CurrentLocation.Connections.Count}).");
continue; continue;
} }
LocationConnection connection = campaign.Map.CurrentLocation.Connections[availableMission.ConnectionIndex]; LocationConnection connection = campaign.Map.CurrentLocation.Connections[availableMission.ConnectionIndex];
@@ -647,7 +647,15 @@ namespace Barotrauma
{ {
if (ownedSubIndex >= GameMain.Client.ServerSubmarines.Count) if (ownedSubIndex >= GameMain.Client.ServerSubmarines.Count)
{ {
string errorMsg = $"Error in {nameof(MultiPlayerCampaign.ClientRead)}. Owned submarine index was out of bounds. Index: {ownedSubIndex}, submarines: {string.Join(", ", GameMain.Client.ServerSubmarines.Select(s => s.Name))}"; string errorMsg;
if (GameMain.Client.ServerSubmarines.None())
{
errorMsg = $"Error in {nameof(MultiPlayerCampaign.ClientRead)}. Owned submarine index was out of bounds (list of server submarines is empty).";
}
else
{
errorMsg = $"Error in {nameof(MultiPlayerCampaign.ClientRead)}. Owned submarine index was out of bounds. Index: {ownedSubIndex}, submarines: {string.Join(", ", GameMain.Client.ServerSubmarines.Select(s => s.Name))}";
}
DebugConsole.ThrowError(errorMsg); DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce( GameAnalyticsManager.AddErrorEventOnce(
"MultiPlayerCampaign.ClientRead.OwnerSubIndexOutOfBounds" + ownedSubIndex, "MultiPlayerCampaign.ClientRead.OwnerSubIndexOutOfBounds" + ownedSubIndex,
@@ -822,11 +830,12 @@ namespace Barotrauma
UInt16 id = msg.ReadUInt16(); UInt16 id = msg.ReadUInt16();
bool hasCharacterData = msg.ReadBoolean(); bool hasCharacterData = msg.ReadBoolean();
CharacterInfo myCharacterInfo = null; CharacterInfo myCharacterInfo = null;
bool waitForModsDownloaded = Screen.Selected is ModDownloadScreen;
if (hasCharacterData) if (hasCharacterData)
{ {
myCharacterInfo = CharacterInfo.ClientRead(CharacterPrefab.HumanSpeciesName, msg); myCharacterInfo = CharacterInfo.ClientRead(CharacterPrefab.HumanSpeciesName, msg, requireJobPrefabFound: !waitForModsDownloaded);
} }
if (ShouldApply(NetFlags.CharacterInfo, id, requireUpToDateSave: true)) if (!waitForModsDownloaded && ShouldApply(NetFlags.CharacterInfo, id, requireUpToDateSave: true))
{ {
if (myCharacterInfo != null) if (myCharacterInfo != null)
{ {
@@ -548,9 +548,12 @@ namespace Barotrauma
} }
else else
{ {
//wasn't initially docked (sub doesn't have a docking port?) //force the map to open if the sub is somehow not at the start of the outpost level
// -> choose a destination when the sub is far enough from the start outpost //UNLESS the level has specific exit points, in that case the sub needs to get to those
if (!Submarine.MainSub.AtStartExit && !Level.Loaded.StartOutpost.ExitPoints.Any()) if (!Submarine.MainSub.AtStartExit &&
/*there should normally always be a start outpost in outpost levels,
* but that might not always be the case e.g. mods or outdated saves (see #13042)*/
Level.Loaded.StartOutpost is not { ExitPoints.Count: > 0 })
{ {
ForceMapUI = true; ForceMapUI = true;
CampaignUI.SelectTab(InteractionType.Map); CampaignUI.SelectTab(InteractionType.Map);
@@ -48,6 +48,8 @@ namespace Barotrauma
private GUIImage eventLogNotification; private GUIImage eventLogNotification;
private Point prevTopLeftButtonsResolution;
private void CreateTopLeftButtons() private void CreateTopLeftButtons()
{ {
if (topLeftButtonGroup != null) if (topLeftButtonGroup != null)
@@ -61,10 +63,6 @@ namespace Barotrauma
AbsoluteSpacing = HUDLayoutSettings.Padding, AbsoluteSpacing = HUDLayoutSettings.Padding,
CanBeFocused = false CanBeFocused = false
}; };
topLeftButtonGroup.RectTransform.ParentChanged += (_) =>
{
GameMain.Instance.ResolutionChanged -= CreateTopLeftButtons;
};
int buttonHeight = GUI.IntScale(40); int buttonHeight = GUI.IntScale(40);
Vector2 buttonSpriteSize = GUIStyle.GetComponentStyle("CrewListToggleButton").GetDefaultSprite().size; Vector2 buttonSpriteSize = GUIStyle.GetComponentStyle("CrewListToggleButton").GetDefaultSprite().size;
int buttonWidth = (int)((buttonHeight / buttonSpriteSize.Y) * buttonSpriteSize.X); int buttonWidth = (int)((buttonHeight / buttonSpriteSize.Y) * buttonSpriteSize.X);
@@ -98,8 +96,6 @@ namespace Barotrauma
talentPointNotification = CreateNotificationIcon(tabMenuButton); talentPointNotification = CreateNotificationIcon(tabMenuButton);
eventLogNotification = CreateNotificationIcon(tabMenuButton); eventLogNotification = CreateNotificationIcon(tabMenuButton);
GameMain.Instance.ResolutionChanged += CreateTopLeftButtons;
respawnInfoFrame = new GUIFrame(new RectTransform(new Vector2(0.5f, 1.0f), parent: topLeftButtonGroup.RectTransform) respawnInfoFrame = new GUIFrame(new RectTransform(new Vector2(0.5f, 1.0f), parent: topLeftButtonGroup.RectTransform)
{ MaxSize = new Point(HUDLayoutSettings.ButtonAreaTop.Width / 3, int.MaxValue) }, style: null) { MaxSize = new Point(HUDLayoutSettings.ButtonAreaTop.Width / 3, int.MaxValue) }, style: null)
{ {
@@ -121,6 +117,7 @@ namespace Barotrauma
return true; return true;
} }
}; };
prevTopLeftButtonsResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
} }
public void AddToGUIUpdateList() public void AddToGUIUpdateList()
@@ -133,7 +130,8 @@ namespace Barotrauma
if ((GameMode is not CampaignMode campaign || (!campaign.ForceMapUI && !campaign.ShowCampaignUI)) && if ((GameMode is not CampaignMode campaign || (!campaign.ForceMapUI && !campaign.ShowCampaignUI)) &&
!CoroutineManager.IsCoroutineRunning("LevelTransition") && !CoroutineManager.IsCoroutineRunning("SubmarineTransition")) !CoroutineManager.IsCoroutineRunning("LevelTransition") && !CoroutineManager.IsCoroutineRunning("SubmarineTransition"))
{ {
if (topLeftButtonGroup == null) if (topLeftButtonGroup == null ||
prevTopLeftButtonsResolution.X != GameMain.GraphicsWidth || prevTopLeftButtonsResolution.Y != GameMain.GraphicsHeight)
{ {
CreateTopLeftButtons(); CreateTopLeftButtons();
} }
@@ -139,6 +139,11 @@ static class ObjectiveManager
VideoPlayer.AddToGUIUpdateList(order: 100); VideoPlayer.AddToGUIUpdateList(order: 100);
} }
public static bool IsSegmentActive(Identifier segmentId)
{
return activeObjectives.Any(o => o.Id == segmentId);
}
public static void TriggerSegment(Segment segment, bool connectObjective = false) public static void TriggerSegment(Segment segment, bool connectObjective = false)
{ {
if (segment.SegmentType != SegmentType.InfoBox) if (segment.SegmentType != SegmentType.InfoBox)
@@ -361,9 +366,18 @@ static class ObjectiveManager
activeObjectives.IndexOf(parentSegment) + activeObjectives.Count(s => s.ParentId == segment.ParentId); activeObjectives.IndexOf(parentSegment) + activeObjectives.Count(s => s.ParentId == segment.ParentId);
if (objectiveGroup.RectTransform.GetChildIndex(frameRt) != childIndex) if (objectiveGroup.RectTransform.GetChildIndex(frameRt) != childIndex)
{ {
frameRt.RepositionChildInHierarchy(childIndex); if (childIndex < 0 || childIndex >= frameRt.Parent.CountChildren)
activeObjectives.Remove(segment); {
activeObjectives.Insert(childIndex, segment); DebugConsole.ThrowError(
$"Error in {nameof(ObjectiveManager.AddToObjectiveList)}. " +
$"Failed to reposition an objective in the list. Text \"{segment.ObjectiveText}\", parentId: {segment.ParentId}, childIndex: {childIndex}");
}
else
{
frameRt.RepositionChildInHierarchy(childIndex);
activeObjectives.Remove(segment);
activeObjectives.Insert(childIndex, segment);
}
} }
} }
frameRt.AbsoluteOffset = GetObjectiveHiddenPosition(); frameRt.AbsoluteOffset = GetObjectiveHiddenPosition();
@@ -564,7 +564,7 @@ namespace Barotrauma
private LocalizedString GetHeaderText(bool gameOver, CampaignMode.TransitionType transitionType) private LocalizedString GetHeaderText(bool gameOver, CampaignMode.TransitionType transitionType)
{ {
string locationName = Submarine.MainSub is { AtEndExit: true } ? endLocation?.Name : startLocation?.Name; LocalizedString locationName = Submarine.MainSub is { AtEndExit: true } ? endLocation?.DisplayName : startLocation?.DisplayName;
string textTag; string textTag;
if (gameOver) if (gameOver)
@@ -576,23 +576,23 @@ namespace Barotrauma
switch (transitionType) switch (transitionType)
{ {
case CampaignMode.TransitionType.LeaveLocation: case CampaignMode.TransitionType.LeaveLocation:
locationName = startLocation?.Name; locationName = startLocation?.DisplayName;
textTag = "RoundSummaryLeaving"; textTag = "RoundSummaryLeaving";
break; break;
case CampaignMode.TransitionType.ProgressToNextLocation: case CampaignMode.TransitionType.ProgressToNextLocation:
locationName = endLocation?.Name; locationName = endLocation?.DisplayName;
textTag = "RoundSummaryProgress"; textTag = "RoundSummaryProgress";
break; break;
case CampaignMode.TransitionType.ProgressToNextEmptyLocation: case CampaignMode.TransitionType.ProgressToNextEmptyLocation:
locationName = endLocation?.Name; locationName = endLocation?.DisplayName;
textTag = "RoundSummaryProgressToEmptyLocation"; textTag = "RoundSummaryProgressToEmptyLocation";
break; break;
case CampaignMode.TransitionType.ReturnToPreviousLocation: case CampaignMode.TransitionType.ReturnToPreviousLocation:
locationName = startLocation?.Name; locationName = startLocation?.DisplayName;
textTag = "RoundSummaryReturn"; textTag = "RoundSummaryReturn";
break; break;
case CampaignMode.TransitionType.ReturnToPreviousEmptyLocation: case CampaignMode.TransitionType.ReturnToPreviousEmptyLocation:
locationName = startLocation?.Name; locationName = startLocation?.DisplayName;
textTag = "RoundSummaryReturnToEmptyLocation"; textTag = "RoundSummaryReturnToEmptyLocation";
break; break;
default: default:
@@ -603,14 +603,14 @@ namespace Barotrauma
if (startLocation?.Biome != null && startLocation.Biome.IsEndBiome) if (startLocation?.Biome != null && startLocation.Biome.IsEndBiome)
{ {
locationName ??= startLocation.Name; locationName ??= startLocation.DisplayName;
} }
if (textTag == null) { return ""; } if (textTag == null) { return ""; }
if (locationName == null) if (locationName == null)
{ {
DebugConsole.ThrowError($"Error while creating round summary: could not determine destination location. Start location: {startLocation?.Name ?? "null"}, end location: {endLocation?.Name ?? "null"}"); DebugConsole.ThrowError($"Error while creating round summary: could not determine destination location. Start location: {startLocation?.DisplayName ?? "null"}, end location: {endLocation?.DisplayName ?? "null"}");
locationName = "[UNKNOWN]"; locationName = "[UNKNOWN]";
} }
@@ -568,7 +568,7 @@ namespace Barotrauma
itemContainer.KeepOpenWhenEquippedBy(character) && itemContainer.KeepOpenWhenEquippedBy(character) &&
!DraggingItems.Contains(item) && !DraggingItems.Contains(item) &&
character.CanAccessInventory(itemContainer.Inventory) && character.CanAccessInventory(itemContainer.Inventory) &&
!highlightedSubInventorySlots.Any(s => s.Inventory == itemContainer.Inventory)) !highlightedSubInventorySlots.Any(s => s.Inventory == itemContainer.Inventory && s.SlotIndex == i))
{ {
ShowSubInventory(new SlotReference(this, visualSlots[i], i, false, itemContainer.Inventory), deltaTime, cam, hideSubInventories, true); ShowSubInventory(new SlotReference(this, visualSlots[i], i, false, itemContainer.Inventory), deltaTime, cam, hideSubInventories, true);
} }
@@ -709,11 +709,11 @@ namespace Barotrauma
private void ShowSubInventory(SlotReference slotRef, float deltaTime, Camera cam, List<SlotReference> hideSubInventories, bool isEquippedSubInventory) private void ShowSubInventory(SlotReference slotRef, float deltaTime, Camera cam, List<SlotReference> hideSubInventories, bool isEquippedSubInventory)
{ {
Rectangle hoverArea = GetSubInventoryHoverArea(slotRef); Rectangle hoverArea = GetSubInventoryHoverArea(slotRef);
if (isEquippedSubInventory) if (isEquippedSubInventory && slotRef.Inventory is not ItemInventory { Container.MovableFrame: true, Container.KeepOpenWhenEquipped: true })
{ {
foreach (SlotReference highlightedSubInventorySlot in highlightedSubInventorySlots) foreach (SlotReference highlightedSubInventorySlot in highlightedSubInventorySlots)
{ {
if (highlightedSubInventorySlot == slotRef) continue; if (highlightedSubInventorySlot == slotRef) { continue; }
if (hoverArea.Intersects(GetSubInventoryHoverArea(highlightedSubInventorySlot))) if (hoverArea.Intersects(GetSubInventoryHoverArea(highlightedSubInventorySlot)))
{ {
return; // If an equipped one intersects with a currently active hover one, do not open return; // If an equipped one intersects with a currently active hover one, do not open
@@ -819,6 +819,7 @@ namespace Barotrauma
if (selectedContainer != null && if (selectedContainer != null &&
selectedContainer.Inventory != null && selectedContainer.Inventory != null &&
!selectedContainer.Inventory.Locked && !selectedContainer.Inventory.Locked &&
selectedContainer.DrawInventory &&
allowInventorySwap) allowInventorySwap)
{ {
//player has selected the inventory of another item -> attempt to move the item there //player has selected the inventory of another item -> attempt to move the item there
@@ -841,6 +842,7 @@ namespace Barotrauma
} }
else if (character.HeldItems.FirstOrDefault(i => else if (character.HeldItems.FirstOrDefault(i =>
i.OwnInventory != null && i.OwnInventory != null &&
i.OwnInventory.Container.DrawInventory &&
(i.OwnInventory.CanBePut(item) || ((i.OwnInventory.Capacity == 1 || i.OwnInventory.Container.HasSubContainers) && i.OwnInventory.AllowSwappingContainedItems && i.OwnInventory.Container.CanBeContained(item)))) is { } equippedContainer) (i.OwnInventory.CanBePut(item) || ((i.OwnInventory.Capacity == 1 || i.OwnInventory.Container.HasSubContainers) && i.OwnInventory.AllowSwappingContainedItems && i.OwnInventory.Container.CanBeContained(item)))) is { } equippedContainer)
{ {
if (allowEquip) if (allowEquip)
@@ -1027,7 +1029,7 @@ namespace Barotrauma
//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.OrderByDescending(heldItem => GetContainPriority(item, heldItem))) foreach (Item heldItem in character.HeldItems.OrderByDescending(heldItem => GetContainPriority(item, heldItem)))
{ {
if (heldItem.OwnInventory == null) { continue; } if (heldItem.OwnInventory == null || !heldItem.OwnInventory.Container.DrawInventory) { 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
//(in that case, the quick action should just fill up the stack) //(in that case, the quick action should just fill up the stack)
bool disallowSwapping = bool disallowSwapping =
@@ -54,7 +54,9 @@ namespace Barotrauma.Items.Components
{ {
if (deconstructor.InputContainer.Inventory.AllItems.Count() == 2) if (deconstructor.InputContainer.Inventory.AllItems.Count() == 2)
{ {
if (!deconstructor.InputContainer.Inventory.AllItems.All(it => it.Prefab == item.Prefab)) var otherGeneticMaterial =
deconstructor.InputContainer.Inventory.AllItems.FirstOrDefault(it => it != item && it.Prefab == item.Prefab)?.GetComponent<GeneticMaterial>();
if (otherGeneticMaterial == null)
{ {
buttonText = TextManager.Get("researchstation.combine"); buttonText = TextManager.Get("researchstation.combine");
infoText = TextManager.Get("researchstation.combine.infotext"); infoText = TextManager.Get("researchstation.combine.infotext");
@@ -62,7 +64,7 @@ namespace Barotrauma.Items.Components
else else
{ {
buttonText = TextManager.Get("researchstation.refine"); buttonText = TextManager.Get("researchstation.refine");
int taintedProbability = (int)(GetTaintedProbabilityOnRefine(Character.Controlled) * 100); int taintedProbability = (int)(GetTaintedProbabilityOnRefine(otherGeneticMaterial, Character.Controlled) * 100);
infoText = TextManager.GetWithVariable("researchstation.refine.infotext", "[taintedprobability]", taintedProbability.ToString()); infoText = TextManager.GetWithVariable("researchstation.refine.infotext", "[taintedprobability]", taintedProbability.ToString());
} }
} }
@@ -260,6 +260,12 @@ namespace Barotrauma.Items.Components
if (!hasSoundsOfType[(int)type]) { return; } if (!hasSoundsOfType[(int)type]) { return; }
if (GameMain.Client?.MidRoundSyncing ?? false) { return; } if (GameMain.Client?.MidRoundSyncing ?? false) { return; }
//above the top boundary of the level (in an inactive respawn shuttle?)
if (item.Submarine != null && Level.Loaded != null && item.Submarine.WorldPosition.Y > Level.Loaded.Size.Y)
{
return;
}
if (loopingSound != null) if (loopingSound != null)
{ {
if (Vector3.DistanceSquared(GameMain.SoundManager.ListenerPosition, new Vector3(item.WorldPosition, 0.0f)) > loopingSound.Range * loopingSound.Range || if (Vector3.DistanceSquared(GameMain.SoundManager.ListenerPosition, new Vector3(item.WorldPosition, 0.0f)) > loopingSound.Range * loopingSound.Range ||
@@ -388,12 +394,9 @@ namespace Barotrauma.Items.Components
} }
} }
public void StopSounds(ActionType type) public void StopLoopingSound()
{ {
if (loopingSound == null) { return; } if (loopingSound == null) { return; }
if (loopingSound.Type != type) { return; }
if (loopingSoundChannel != null) if (loopingSoundChannel != null)
{ {
loopingSoundChannel.FadeOutAndDispose(); loopingSoundChannel.FadeOutAndDispose();
@@ -402,6 +405,12 @@ namespace Barotrauma.Items.Components
} }
} }
public void StopSounds(ActionType type)
{
if (loopingSound == null || loopingSound.Type != type) { return; }
StopLoopingSound();
}
private float GetSoundVolume(ItemSound sound) private float GetSoundVolume(ItemSound sound)
{ {
if (sound == null) { return 0.0f; } if (sound == null) { return 0.0f; }
@@ -497,7 +506,8 @@ namespace Barotrauma.Items.Components
case "guiframe": case "guiframe":
if (subElement.GetAttribute("rect") != null) if (subElement.GetAttribute("rect") != null)
{ {
DebugConsole.ThrowError($"Error in item config \"{item.ConfigFilePath}\" - GUIFrame defined as rect, use RectTransform instead."); DebugConsole.ThrowError($"Error in item config \"{item.ConfigFilePath}\" - GUIFrame defined as rect, use RectTransform instead.",
contentPackage: subElement.ContentPackage);
break; break;
} }
GuiFrameSource = subElement; GuiFrameSource = subElement;
@@ -516,7 +526,8 @@ namespace Barotrauma.Items.Components
if (filePath.IsNullOrEmpty()) if (filePath.IsNullOrEmpty())
{ {
DebugConsole.ThrowError( DebugConsole.ThrowError(
$"Error when instantiating item \"{item.Name}\" - sound with no file path set"); $"Error when instantiating item \"{item.Name}\" - sound with no file path set",
contentPackage: subElement.ContentPackage);
break; break;
} }
@@ -528,7 +539,8 @@ namespace Barotrauma.Items.Components
} }
catch (Exception e) catch (Exception e)
{ {
DebugConsole.ThrowError($"Invalid sound type \"{typeStr}\" in item \"{item.Prefab.Identifier}\"!", e); DebugConsole.ThrowError($"Invalid sound type \"{typeStr}\" in item \"{item.Prefab.Identifier}\"!", e,
contentPackage: subElement.ContentPackage);
break; break;
} }
@@ -758,6 +770,8 @@ namespace Barotrauma.Items.Components
} }
} }
public virtual void OnPlayerSkillsChanged() { }
public virtual void AddTooltipInfo(ref LocalizedString name, ref LocalizedString description) { } public virtual void AddTooltipInfo(ref LocalizedString name, ref LocalizedString description) { }
} }
} }
@@ -158,7 +158,8 @@ namespace Barotrauma.Items.Components
IndicatorStyle = GUIStyle.GetComponentStyle("ContainedStateIndicator." + ContainedStateIndicatorStyle); IndicatorStyle = GUIStyle.GetComponentStyle("ContainedStateIndicator." + ContainedStateIndicatorStyle);
if (ContainedStateIndicator != null || ContainedStateIndicatorEmpty != null) if (ContainedStateIndicator != null || ContainedStateIndicatorEmpty != null)
{ {
DebugConsole.AddWarning($"Item \"{item.Name}\" defines both a contained state indicator style and a custom indicator sprite. Will use the custom sprite..."); DebugConsole.AddWarning($"Item \"{item.Name}\" defines both a contained state indicator style and a custom indicator sprite. Will use the custom sprite...",
contentPackage: item.Prefab.ContentPackage);
} }
} }
if (GuiFrame == null) if (GuiFrame == null)
@@ -345,9 +346,9 @@ namespace Barotrauma.Items.Components
public bool KeepOpenWhenEquippedBy(Character character) public bool KeepOpenWhenEquippedBy(Character character)
{ {
if (!character.CanAccessInventory(Inventory) || if (!KeepOpenWhenEquipped ||
!KeepOpenWhenEquipped || !character.HasEquippedItem(Item) ||
!character.HasEquippedItem(Item)) !character.CanAccessInventory(Inventory))
{ {
return false; return false;
} }
@@ -570,11 +571,13 @@ namespace Barotrauma.Items.Components
{ {
spriteRotation = contained.Rotation; spriteRotation = contained.Rotation;
} }
if ((item.body != null && item.body.Dir == -1) || item.FlippedX) bool flipX = (item.body != null && item.body.Dir == -1) || item.FlippedX;
if (flipX)
{ {
spriteEffects |= MathUtils.NearlyEqual(spriteRotation % 180, 90.0f) ? SpriteEffects.FlipVertically : SpriteEffects.FlipHorizontally; spriteEffects |= MathUtils.NearlyEqual(spriteRotation % 180, 90.0f) ? SpriteEffects.FlipVertically : SpriteEffects.FlipHorizontally;
} }
if (item.FlippedY) bool flipY = item.FlippedY;
if (flipY)
{ {
spriteEffects |= MathUtils.NearlyEqual(spriteRotation % 180, 90.0f) ? SpriteEffects.FlipHorizontally : SpriteEffects.FlipVertically; spriteEffects |= MathUtils.NearlyEqual(spriteRotation % 180, 90.0f) ? SpriteEffects.FlipHorizontally : SpriteEffects.FlipVertically;
} }
@@ -588,6 +591,7 @@ namespace Barotrauma.Items.Components
contained.Item.Scale, contained.Item.Scale,
spriteEffects, spriteEffects,
depth: containedSpriteDepth); depth: containedSpriteDepth);
contained.Item.DrawDecorativeSprites(spriteBatch, itemPos, flipX,flipY, (contained.Item.body == null ? 0.0f : contained.Item.body.DrawRotation), containedSpriteDepth);
foreach (ItemContainer ic in contained.Item.GetComponents<ItemContainer>()) foreach (ItemContainer ic in contained.Item.GetComponents<ItemContainer>())
{ {
@@ -227,7 +227,7 @@ namespace Barotrauma.Items.Components
switch (text) switch (text)
{ {
case "[CurrentLocationName]": case "[CurrentLocationName]":
SetDisplayText(Level.Loaded?.StartLocation?.Name ?? string.Empty); SetDisplayText(Level.Loaded?.StartLocation?.DisplayName.Value ?? string.Empty);
break; break;
case "[CurrentBiomeName]": case "[CurrentBiomeName]":
SetDisplayText(Level.Loaded?.LevelData?.Biome?.DisplayName.Value ?? string.Empty); SetDisplayText(Level.Loaded?.LevelData?.Biome?.DisplayName.Value ?? string.Empty);
@@ -67,7 +67,7 @@ namespace Barotrauma.Items.Components
Light.Position = item.Position; Light.Position = item.Position;
} }
PhysicsBody body = Light.ParentBody; PhysicsBody body = Light.ParentBody;
if (body != null) if (body != null && body.Enabled)
{ {
Light.Rotation = body.Dir > 0.0f ? body.DrawRotation : body.DrawRotation - MathHelper.Pi; Light.Rotation = body.Dir > 0.0f ? body.DrawRotation : body.DrawRotation - MathHelper.Pi;
Light.LightSpriteEffect = (body.Dir > 0.0f) ? SpriteEffects.None : SpriteEffects.FlipVertically; Light.LightSpriteEffect = (body.Dir > 0.0f) ? SpriteEffects.None : SpriteEffects.FlipVertically;
@@ -66,7 +66,11 @@ namespace Barotrauma.Items.Components
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.3f), sliderArea.RectTransform, Anchor.TopCenter), "", textColor: GUIStyle.TextColorNormal, font: GUIStyle.SubHeadingFont, textAlignment: Alignment.Center) new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.3f), sliderArea.RectTransform, Anchor.TopCenter), "", textColor: GUIStyle.TextColorNormal, font: GUIStyle.SubHeadingFont, textAlignment: Alignment.Center)
{ {
AutoScaleHorizontal = true, AutoScaleHorizontal = true,
TextGetter = () => { return TextManager.AddPunctuation(':', powerLabel, (int)(targetForce) + " %"); } TextGetter = () =>
{
return TextManager.AddPunctuation(':', powerLabel,
TextManager.GetWithVariable("percentageformat", "[value]", ((int)MathF.Round(targetForce)).ToString()));
}
}; };
forceSlider = new GUIScrollBar(new RectTransform(new Vector2(0.95f, 0.45f), sliderArea.RectTransform, Anchor.Center), barSize: 0.1f, style: "DeviceSlider") forceSlider = new GUIScrollBar(new RectTransform(new Vector2(0.95f, 0.45f), sliderArea.RectTransform, Anchor.Center), barSize: 0.1f, style: "DeviceSlider")
{ {
@@ -4,6 +4,7 @@ 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.Globalization;
using System.Linq; using System.Linq;
namespace Barotrauma.Items.Components namespace Barotrauma.Items.Components
@@ -393,6 +394,8 @@ namespace Barotrauma.Items.Components
partial void SelectProjSpecific(Character character) partial void SelectProjSpecific(Character character)
{ {
if (character != Character.Controlled) { return; }
var nonItems = itemList.Content.Children.Where(c => c.UserData is not FabricationRecipe).ToList(); var nonItems = itemList.Content.Children.Where(c => c.UserData is not FabricationRecipe).ToList();
nonItems.ForEach(i => itemList.Content.RemoveChild(i)); nonItems.ForEach(i => itemList.Content.RemoveChild(i));
@@ -784,6 +787,7 @@ namespace Barotrauma.Items.Components
private void HideEmptyItemListCategories() private void HideEmptyItemListCategories()
{ {
bool visibleElementsChanged = false;
//go through the elements backwards, and disable the labels ("insufficient skills to fabricate", "recipe required...") if there's no items below them //go through the elements backwards, and disable the labels ("insufficient skills to fabricate", "recipe required...") if there's no items below them
bool recipeVisible = false; bool recipeVisible = false;
foreach (GUIComponent child in itemList.Content.Children.Reverse()) foreach (GUIComponent child in itemList.Content.Children.Reverse())
@@ -792,7 +796,11 @@ namespace Barotrauma.Items.Components
{ {
if (child.Enabled) if (child.Enabled)
{ {
child.Visible = recipeVisible; if (child.Visible != recipeVisible)
{
child.Visible = recipeVisible;
visibleElementsChanged = true;
}
} }
recipeVisible = false; recipeVisible = false;
} }
@@ -802,8 +810,11 @@ namespace Barotrauma.Items.Components
} }
} }
itemList.UpdateScrollBarSize(); if (visibleElementsChanged)
itemList.BarScroll = 0.0f; {
itemList.UpdateScrollBarSize();
itemList.BarScroll = 0.0f;
}
} }
public bool ClearFilter() public bool ClearFilter()
@@ -815,11 +826,22 @@ namespace Barotrauma.Items.Components
return true; return true;
} }
private readonly record struct SelectedRecipe(Character User, FabricationRecipe SelectedItem, Option<float> OverrideRequiredTime);
private Option<SelectedRecipe> LastSelectedRecipe = Option.None;
private bool SelectItem(Character user, FabricationRecipe selectedItem, float? overrideRequiredTime = null) private bool SelectItem(Character user, FabricationRecipe selectedItem, float? overrideRequiredTime = null)
{ {
this.selectedItem = selectedItem; this.selectedItem = selectedItem;
displayingForCharacter = user; displayingForCharacter = user;
var selectedRecipe = new SelectedRecipe(user, selectedItem, overrideRequiredTime is null ? Option.None : Option.Some(overrideRequiredTime.Value));
LastSelectedRecipe = Option.Some(selectedRecipe);
CreateSelectedItemUI(selectedRecipe);
return true;
}
private void CreateSelectedItemUI(SelectedRecipe recipe)
{
var (user, selectedItem, overrideRequiredTime) = recipe;
int max = Math.Max(selectedItem.TargetItem.GetMaxStackSize(outputContainer.Inventory) / selectedItem.Amount, 1); int max = Math.Max(selectedItem.TargetItem.GetMaxStackSize(outputContainer.Inventory) / selectedItem.Amount, 1);
if (amountInput != null) if (amountInput != null)
@@ -843,8 +865,10 @@ namespace Barotrauma.Items.Components
LocalizedString itemName = GetRecipeNameAndAmount(selectedItem); LocalizedString itemName = GetRecipeNameAndAmount(selectedItem);
LocalizedString name = itemName; LocalizedString name = itemName;
float quality = selectedItem.Quality ?? GetFabricatedItemQuality(selectedItem, user); QualityResult result = GetFabricatedItemQuality(selectedItem, user);
if (quality > 0)
float quality = selectedItem.Quality ?? result.Quality;
if (quality > 0 || result.HasRandomQualityRollChance)
{ {
name = TextManager.GetWithVariable("itemname.quality" + (int)quality, "[itemname]", itemName + '\n') name = TextManager.GetWithVariable("itemname.quality" + (int)quality, "[itemname]", itemName + '\n')
.Fallback(TextManager.GetWithVariable("itemname.quality3", "[itemname]", itemName + '\n')); .Fallback(TextManager.GetWithVariable("itemname.quality3", "[itemname]", itemName + '\n'));
@@ -855,6 +879,49 @@ namespace Barotrauma.Items.Components
{ {
AutoScaleHorizontal = true AutoScaleHorizontal = true
}; };
if (result.HasRandomQualityRollChance)
{
var iconLayout = new GUIFrame(new RectTransform(new Vector2(0.4f, 1f), selectedItemFrame.RectTransform, anchor: Anchor.TopRight), style: null);
var icon = GameSession.CreateNotificationIcon(iconLayout, offset: true);
float percentage1 = result.TotalPlusOnePercentage;
float percentage2 = result.TotalPlusTwoPercentage;
string chance1text = percentage1.ToString("F1", CultureInfo.InvariantCulture);
string chance2text = percentage2.ToString("F1", CultureInfo.InvariantCulture);
int quality1 = Math.Clamp(result.Quality + 1, min: 0, max: 3);
int quality2 = Math.Clamp(result.Quality + 2, min: 0, max: 3);
LocalizedString quality1Text = TextManager.Get($"quality{quality1}");
LocalizedString quality2Text = TextManager.Get($"quality{quality2}");
string localizationTag = percentage2 > 0f && percentage1 > 0 && quality1 != quality2 ? "meetsbonusrequirementtwice" : "meetsbonusrequirement";
var variables = new (string Key, LocalizedString Value)[]
{
("[chance]", chance1text), ("[quality]", quality1Text),
("[chance2]", chance2text), ("[quality2]", quality2Text)
};
if (MathUtils.NearlyEqual(percentage1, 0))
{
variables = new[] { ("[chance]", chance2text), ("[quality]", quality2Text) };
}
if (quality1 == quality2)
{
LocalizedString rawPercentage = result.PlusOnePercentage.ToString("F1", CultureInfo.InvariantCulture);
variables = new[] { ("[chance]", rawPercentage), ("[quality]", quality1Text) };
}
LocalizedString qualityTooltip = TextManager.GetWithVariables(localizationTag, variables);
icon.ToolTip = RichString.Rich(qualityTooltip);
icon.Visible = icon.CanBeFocused = true;
}
nameBlock.Padding = new Vector4(0, nameBlock.Padding.Y, GUI.IntScale(5), nameBlock.Padding.W); nameBlock.Padding = new Vector4(0, nameBlock.Padding.Y, GUI.IntScale(5), nameBlock.Padding.W);
if (nameBlock.TextScale < 0.7f) if (nameBlock.TextScale < 0.7f)
{ {
@@ -911,8 +978,9 @@ namespace Barotrauma.Items.Components
float degreeOfSuccess = user == null ? 0.0f : FabricationDegreeOfSuccess(user, selectedItem.RequiredSkills); float degreeOfSuccess = user == null ? 0.0f : FabricationDegreeOfSuccess(user, selectedItem.RequiredSkills);
if (degreeOfSuccess > 0.5f) { degreeOfSuccess = 1.0f; } if (degreeOfSuccess > 0.5f) { degreeOfSuccess = 1.0f; }
float requiredTime = overrideRequiredTime ?? float requiredTime = overrideRequiredTime.TryUnwrap(out var time)
(user == null ? selectedItem.RequiredTime : GetRequiredTime(selectedItem, user)); ? time
: (user == null ? selectedItem.RequiredTime : GetRequiredTime(selectedItem, user));
if ((int)requiredTime > 0) if ((int)requiredTime > 0)
{ {
@@ -936,7 +1004,6 @@ namespace Barotrauma.Items.Components
font: GUIStyle.SmallFont); font: GUIStyle.SmallFont);
} }
return true;
} }
public void HighlightRecipe(string identifier, Color color) public void HighlightRecipe(string identifier, Color color)
@@ -1046,6 +1113,15 @@ namespace Barotrauma.Items.Components
} }
} }
public override void OnPlayerSkillsChanged()
=> RefreshSelectedItem();
public void RefreshSelectedItem()
{
if (!LastSelectedRecipe.TryUnwrap(out var lastSelected)) { return; }
CreateSelectedItemUI(lastSelected);
}
partial void UpdateRequiredTimeProjSpecific() partial void UpdateRequiredTimeProjSpecific()
{ {
if (requiredTimeBlock == null) { return; } if (requiredTimeBlock == null) { return; }
@@ -66,7 +66,15 @@ namespace Barotrauma.Items.Components
private float prevPassivePingRadius; private float prevPassivePingRadius;
private Vector2 center; private Vector2 center;
private float displayScale;
/// <summary>
/// Current scale of the display, taking zoom into account. In other words, the scaling factor of world coordinates to coordinates on the display.
/// </summary>
public float DisplayScale
{
get;
private set;
} = 1.0f;
private const float DisruptionUpdateInterval = 0.2f; private const float DisruptionUpdateInterval = 0.2f;
private float disruptionUpdateTimer; private float disruptionUpdateTimer;
@@ -751,9 +759,9 @@ namespace Barotrauma.Items.Components
{ {
var activePing = activePings[pingIndex]; var activePing = activePings[pingIndex];
float pingRadius = DisplayRadius * activePing.State / zoom; float pingRadius = DisplayRadius * activePing.State / zoom;
if (disruptionUpdateTimer <= 0.0f) { UpdateDisruptions(transducerCenter, pingRadius / displayScale); } if (disruptionUpdateTimer <= 0.0f) { UpdateDisruptions(transducerCenter, pingRadius / DisplayScale); }
Ping(transducerCenter, transducerCenter, Ping(transducerCenter, transducerCenter,
pingRadius, activePing.PrevPingRadius, displayScale, range / zoom, passive: false, pingStrength: 2.0f); pingRadius, activePing.PrevPingRadius, DisplayScale, range / zoom, passive: false, pingStrength: 2.0f);
activePing.PrevPingRadius = pingRadius; activePing.PrevPingRadius = pingRadius;
} }
if (disruptionUpdateTimer <= 0.0f) if (disruptionUpdateTimer <= 0.0f)
@@ -770,7 +778,7 @@ namespace Barotrauma.Items.Components
if (c.Params.HideInSonar) { continue; } if (c.Params.HideInSonar) { continue; }
if (!c.IsUnconscious && c.Params.DistantSonarRange > 0.0f && if (!c.IsUnconscious && c.Params.DistantSonarRange > 0.0f &&
((c.WorldPosition - transducerCenter) * displayScale).LengthSquared() > DisplayRadius * DisplayRadius) ((c.WorldPosition - transducerCenter) * DisplayScale).LengthSquared() > DisplayRadius * DisplayRadius)
{ {
Vector2 targetVector = c.WorldPosition - transducerCenter; Vector2 targetVector = c.WorldPosition - transducerCenter;
if (targetVector.LengthSquared() > MathUtils.Pow2(c.Params.DistantSonarRange)) { continue; } if (targetVector.LengthSquared() > MathUtils.Pow2(c.Params.DistantSonarRange)) { continue; }
@@ -818,7 +826,7 @@ namespace Barotrauma.Items.Components
if (dist > prevPassivePingRadius * Range && dist <= passivePingRadius * Range && Rand.Int(sonarBlips.Count) < 500) if (dist > prevPassivePingRadius * Range && dist <= passivePingRadius * Range && Rand.Int(sonarBlips.Count) < 500)
{ {
Ping(t.WorldPosition, transducerCenter, Ping(t.WorldPosition, transducerCenter,
t.SoundRange * displayScale, 0, displayScale, range, t.SoundRange * DisplayScale, 0, DisplayScale, range,
passive: true, pingStrength: 0.5f, needsToBeInSector: t); passive: true, pingStrength: 0.5f, needsToBeInSector: t);
if (t.IsWithinSector(transducerCenter)) if (t.IsWithinSector(transducerCenter))
{ {
@@ -857,7 +865,7 @@ namespace Barotrauma.Items.Components
displayBorderSize = 0.2f; displayBorderSize = 0.2f;
center = rect.Center.ToVector2(); center = rect.Center.ToVector2();
DisplayRadius = (rect.Width / 2.0f) * (1.0f - displayBorderSize); DisplayRadius = (rect.Width / 2.0f) * (1.0f - displayBorderSize);
displayScale = DisplayRadius / range * zoom; DisplayScale = DisplayRadius / range * zoom;
screenBackground?.Draw(spriteBatch, center, 0.0f, rect.Width / screenBackground.size.X); screenBackground?.Draw(spriteBatch, center, 0.0f, rect.Width / screenBackground.size.X);
@@ -972,7 +980,7 @@ namespace Barotrauma.Items.Components
aiTarget.SonarIconIdentifier, aiTarget.SonarIconIdentifier,
aiTarget, aiTarget,
aiTarget.WorldPosition, transducerCenter, aiTarget.WorldPosition, transducerCenter,
displayScale, center, DisplayRadius * 0.975f); DisplayScale, center, DisplayRadius * 0.975f);
} }
} }
@@ -983,21 +991,21 @@ namespace Barotrauma.Items.Components
if (Level.Loaded.StartLocation?.Type is { ShowSonarMarker: true }) if (Level.Loaded.StartLocation?.Type is { ShowSonarMarker: true })
{ {
DrawMarker(spriteBatch, DrawMarker(spriteBatch,
Level.Loaded.StartLocation.Name, Level.Loaded.StartLocation.DisplayName.Value,
(Level.Loaded.StartOutpost != null ? "outpost" : "location").ToIdentifier(), (Level.Loaded.StartOutpost != null ? "outpost" : "location").ToIdentifier(),
"startlocation", "startlocation",
Level.Loaded.StartExitPosition, transducerCenter, Level.Loaded.StartExitPosition, transducerCenter,
displayScale, center, DisplayRadius); DisplayScale, center, DisplayRadius);
} }
if (Level.Loaded is { EndLocation.Type.ShowSonarMarker: true, Type: LevelData.LevelType.LocationConnection }) if (Level.Loaded is { EndLocation.Type.ShowSonarMarker: true, Type: LevelData.LevelType.LocationConnection })
{ {
DrawMarker(spriteBatch, DrawMarker(spriteBatch,
Level.Loaded.EndLocation.Name, Level.Loaded.EndLocation.DisplayName.Value,
(Level.Loaded.EndOutpost != null ? "outpost" : "location").ToIdentifier(), (Level.Loaded.EndOutpost != null ? "outpost" : "location").ToIdentifier(),
"endlocation", "endlocation",
Level.Loaded.EndExitPosition, transducerCenter, Level.Loaded.EndExitPosition, transducerCenter,
displayScale, center, DisplayRadius); DisplayScale, center, DisplayRadius);
} }
for (int i = 0; i < Level.Loaded.Caves.Count; i++) for (int i = 0; i < Level.Loaded.Caves.Count; i++)
@@ -1009,7 +1017,7 @@ namespace Barotrauma.Items.Components
"cave".ToIdentifier(), "cave".ToIdentifier(),
"cave" + i, "cave" + i,
cave.StartPos.ToVector2(), transducerCenter, cave.StartPos.ToVector2(), transducerCenter,
displayScale, center, DisplayRadius); DisplayScale, center, DisplayRadius);
} }
} }
@@ -1026,7 +1034,7 @@ namespace Barotrauma.Items.Components
mission.SonarIconIdentifier, mission.SonarIconIdentifier,
"mission" + missionIndex + ":" + i, "mission" + missionIndex + ":" + i,
position, transducerCenter, position, transducerCenter,
displayScale, center, DisplayRadius * 0.95f); DisplayScale, center, DisplayRadius * 0.95f);
} }
i++; i++;
} }
@@ -1059,7 +1067,7 @@ namespace Barotrauma.Items.Components
DrawMarker(spriteBatch, DrawMarker(spriteBatch,
i.Name, "mineral".ToIdentifier(), "mineralcluster" + i, i.Name, "mineral".ToIdentifier(), "mineralcluster" + i,
c.center, transducerCenter, c.center, transducerCenter,
displayScale, center, DisplayRadius * 0.95f, DisplayScale, center, DisplayRadius * 0.95f,
onlyShowTextOnMouseOver: true); onlyShowTextOnMouseOver: true);
} }
} }
@@ -1088,19 +1096,19 @@ namespace Barotrauma.Items.Components
(sub.Info.HasTag(SubmarineTag.Shuttle) ? "shuttle" : "submarine").ToIdentifier(), (sub.Info.HasTag(SubmarineTag.Shuttle) ? "shuttle" : "submarine").ToIdentifier(),
sub, sub,
sub.WorldPosition, transducerCenter, sub.WorldPosition, transducerCenter,
displayScale, center, DisplayRadius * 0.95f); DisplayScale, center, DisplayRadius * 0.95f);
} }
if (GameMain.DebugDraw) if (GameMain.DebugDraw)
{ {
var steering = item.GetComponent<Steering>(); var steering = item.GetComponent<Steering>();
steering?.DebugDrawHUD(spriteBatch, transducerCenter, displayScale, DisplayRadius, center); steering?.DebugDrawHUD(spriteBatch, transducerCenter, DisplayScale, DisplayRadius, center);
} }
} }
private void DrawOwnSubmarineBorders(SpriteBatch spriteBatch, Vector2 transducerCenter, float signalStrength) private void DrawOwnSubmarineBorders(SpriteBatch spriteBatch, Vector2 transducerCenter, float signalStrength)
{ {
float simScale = displayScale * Physics.DisplayToSimRation * zoom; float simScale = DisplayScale * Physics.DisplayToSimRation;
foreach (Submarine submarine in Submarine.Loaded) foreach (Submarine submarine in Submarine.Loaded)
{ {
@@ -1167,7 +1175,7 @@ namespace Barotrauma.Items.Components
private void DrawDockingPorts(SpriteBatch spriteBatch, Vector2 transducerCenter, float signalStrength) private void DrawDockingPorts(SpriteBatch spriteBatch, Vector2 transducerCenter, float signalStrength)
{ {
float scale = displayScale * zoom; float scale = DisplayScale;
Steering steering = item.GetComponent<Steering>(); Steering steering = item.GetComponent<Steering>();
if (steering != null && steering.DockingModeEnabled && steering.ActiveDockingSource != null) if (steering != null && steering.DockingModeEnabled && steering.ActiveDockingSource != null)
@@ -1219,7 +1227,7 @@ namespace Barotrauma.Items.Components
private void DrawDockingIndicator(SpriteBatch spriteBatch, Steering steering, ref Vector2 transducerCenter) private void DrawDockingIndicator(SpriteBatch spriteBatch, Steering steering, ref Vector2 transducerCenter)
{ {
float scale = displayScale * zoom; float scale = DisplayScale;
Vector2 worldFocusPos = (steering.ActiveDockingSource.Item.WorldPosition + steering.DockingTarget.Item.WorldPosition) / 2.0f; Vector2 worldFocusPos = (steering.ActiveDockingSource.Item.WorldPosition + steering.DockingTarget.Item.WorldPosition) / 2.0f;
worldFocusPos.X = steering.DockingTarget.Item.WorldPosition.X; worldFocusPos.X = steering.DockingTarget.Item.WorldPosition.X;
@@ -1591,7 +1599,7 @@ namespace Barotrauma.Items.Components
{ {
lineStep /= zoom; lineStep /= zoom;
zStep /= zoom; zStep /= zoom;
range *= displayScale; range *= DisplayScale;
float length = (point1 - point2).Length(); float length = (point1 - point2).Length();
Vector2 lineDir = (point2 - point1) / length; Vector2 lineDir = (point2 - point1) / length;
for (float x = 0; x < length; x += lineStep * Rand.Range(0.8f, 1.2f)) for (float x = 0; x < length; x += lineStep * Rand.Range(0.8f, 1.2f))
@@ -1602,12 +1610,12 @@ namespace Barotrauma.Items.Components
//ignore if outside the display //ignore if outside the display
Vector2 transducerDiff = point - transducerPos; Vector2 transducerDiff = point - transducerPos;
Vector2 transducerDisplayDiff = transducerDiff * displayScale; Vector2 transducerDisplayDiff = transducerDiff * DisplayScale / zoom;
if (transducerDisplayDiff.LengthSquared() > DisplayRadius * DisplayRadius) { continue; } if (transducerDisplayDiff.LengthSquared() > DisplayRadius * DisplayRadius) { continue; }
//ignore if the point is not within the ping //ignore if the point is not within the ping
Vector2 pointDiff = point - pingSource; Vector2 pointDiff = point - pingSource;
Vector2 displayPointDiff = pointDiff * displayScale; Vector2 displayPointDiff = pointDiff * DisplayScale / zoom;
float displayPointDistSqr = displayPointDiff.LengthSquared(); float displayPointDistSqr = displayPointDiff.LengthSquared();
if (displayPointDistSqr < prevPingRadius * prevPingRadius || displayPointDistSqr > pingRadius * pingRadius) { continue; } if (displayPointDistSqr < prevPingRadius * prevPingRadius || displayPointDistSqr > pingRadius * pingRadius) { continue; }
@@ -1628,9 +1636,9 @@ namespace Barotrauma.Items.Components
float displayPointDist = (float)Math.Sqrt(displayPointDistSqr); float displayPointDist = (float)Math.Sqrt(displayPointDistSqr);
float alpha = pingStrength * Rand.Range(1.5f, 2.0f); float alpha = pingStrength * Rand.Range(1.5f, 2.0f);
for (float z = 0; z < DisplayRadius - transducerDist * displayScale; z += zStep) for (float z = 0; z < DisplayRadius - transducerDist * DisplayScale; z += zStep)
{ {
Vector2 pos = point + Rand.Vector(150.0f / zoom) + pingDirection * z / displayScale; Vector2 pos = point + Rand.Vector(150.0f / zoom) + pingDirection * z / DisplayScale;
float fadeTimer = alpha * (1.0f - displayPointDist / range); float fadeTimer = alpha * (1.0f - displayPointDist / range);
if (needsToBeInSector != null) if (needsToBeInSector != null)
@@ -1697,7 +1705,7 @@ namespace Barotrauma.Items.Components
private bool CheckBlipVisibility(SonarBlip blip, Vector2 transducerPos) private bool CheckBlipVisibility(SonarBlip blip, Vector2 transducerPos)
{ {
Vector2 pos = (blip.Position - transducerPos) * displayScale * zoom; Vector2 pos = (blip.Position - transducerPos) * DisplayScale;
pos.Y = -pos.Y; pos.Y = -pos.Y;
float posDistSqr = pos.LengthSquared(); float posDistSqr = pos.LengthSquared();
@@ -1731,7 +1739,7 @@ namespace Barotrauma.Items.Components
} }
if (currentPingIndex != -1 && activePings[currentPingIndex].IsDirectional) if (currentPingIndex != -1 && activePings[currentPingIndex].IsDirectional)
{ {
var pos = (resourcePos - transducerPos) * displayScale * zoom; var pos = (resourcePos - transducerPos) * DisplayScale;
pos.Y = -pos.Y; pos.Y = -pos.Y;
var length = pos.Length(); var length = pos.Length();
var dir = pos / length; var dir = pos / length;
@@ -1749,7 +1757,7 @@ namespace Barotrauma.Items.Components
float distort = 1.0f - item.Condition / item.MaxCondition; float distort = 1.0f - item.Condition / item.MaxCondition;
Vector2 pos = (blip.Position - transducerPos) * displayScale * zoom; Vector2 pos = (blip.Position - transducerPos) * DisplayScale;
pos.Y = -pos.Y; pos.Y = -pos.Y;
if (Rand.Range(0.5f, 2.0f) < distort) { pos.X = -pos.X; } if (Rand.Range(0.5f, 2.0f) < distort) { pos.X = -pos.X; }
@@ -1825,14 +1833,13 @@ namespace Barotrauma.Items.Components
Vector2 position = worldPosition - transducerPosition; Vector2 position = worldPosition - transducerPosition;
position *= zoom;
position *= scale; position *= scale;
position.Y = -position.Y; position.Y = -position.Y;
float textAlpha = MathHelper.Clamp(1.5f - dist / 50000.0f, 0.5f, 1.0f); float textAlpha = MathHelper.Clamp(1.5f - dist / 50000.0f, 0.5f, 1.0f);
Vector2 dir = Vector2.Normalize(position); Vector2 dir = Vector2.Normalize(position);
Vector2 markerPos = (linearDist * zoom * scale > radius) ? dir * radius : position; Vector2 markerPos = (linearDist * scale > radius) ? dir * radius : position;
markerPos += center; markerPos += center;
markerPos.X = (int)markerPos.X; markerPos.X = (int)markerPos.X;
@@ -216,7 +216,7 @@ namespace Barotrauma.Items.Components
} }
}; };
levelStartTickBox = new GUITickBox(new RectTransform(new Vector2(1, 0.333f), paddedAutoPilotControls.RectTransform, Anchor.Center), levelStartTickBox = new GUITickBox(new RectTransform(new Vector2(1, 0.333f), paddedAutoPilotControls.RectTransform, Anchor.Center),
GameMain.GameSession?.StartLocation == null ? "" : ToolBox.LimitString(GameMain.GameSession.StartLocation.Name, GUIStyle.SmallFont, textLimit), GameMain.GameSession?.StartLocation == null ? "" : ToolBox.LimitString(GameMain.GameSession.StartLocation.DisplayName, GUIStyle.SmallFont, textLimit),
font: GUIStyle.SmallFont, style: "GUIRadioButton") font: GUIStyle.SmallFont, style: "GUIRadioButton")
{ {
Enabled = autoPilot, Enabled = autoPilot,
@@ -243,7 +243,7 @@ namespace Barotrauma.Items.Components
}; };
levelEndTickBox = new GUITickBox(new RectTransform(new Vector2(1, 0.333f), paddedAutoPilotControls.RectTransform, Anchor.BottomCenter), levelEndTickBox = new GUITickBox(new RectTransform(new Vector2(1, 0.333f), paddedAutoPilotControls.RectTransform, Anchor.BottomCenter),
(GameMain.GameSession?.EndLocation == null || Level.IsLoadedOutpost) ? "" : ToolBox.LimitString(GameMain.GameSession.EndLocation.Name, GUIStyle.SmallFont, textLimit), (GameMain.GameSession?.EndLocation == null || Level.IsLoadedOutpost) ? "" : ToolBox.LimitString(GameMain.GameSession.EndLocation.DisplayName, GUIStyle.SmallFont, textLimit),
font: GUIStyle.SmallFont, style: "GUIRadioButton") font: GUIStyle.SmallFont, style: "GUIRadioButton")
{ {
Enabled = autoPilot, Enabled = autoPilot,
@@ -389,7 +389,7 @@ namespace Barotrauma.Items.Components
if (!ObjectiveManager.AllActiveObjectivesCompleted()) if (!ObjectiveManager.AllActiveObjectivesCompleted())
{ {
exitOutpostPrompt = new GUIMessageBox("", exitOutpostPrompt = new GUIMessageBox("",
TextManager.GetWithVariable("CampaignExitTutorialOutpostPrompt", "[locationname]", campaign.Map.CurrentLocation.Name), TextManager.GetWithVariable("CampaignExitTutorialOutpostPrompt", "[locationname]", campaign.Map.CurrentLocation.DisplayName),
new LocalizedString[] { TextManager.Get("yes"), TextManager.Get("no") }); new LocalizedString[] { TextManager.Get("yes"), TextManager.Get("no") });
exitOutpostPrompt.Buttons[0].OnClicked += (_, _) => exitOutpostPrompt.Buttons[0].OnClicked += (_, _) =>
{ {
@@ -509,9 +509,9 @@ namespace Barotrauma.Items.Components
noPowerTip = TextManager.Get("SteeringNoPowerTip"); noPowerTip = TextManager.Get("SteeringNoPowerTip");
autoPilotMaintainPosTip = TextManager.Get("SteeringAutoPilotMaintainPosTip"); autoPilotMaintainPosTip = TextManager.Get("SteeringAutoPilotMaintainPosTip");
autoPilotLevelStartTip = TextManager.GetWithVariable("SteeringAutoPilotLocationTip", "[locationname]", autoPilotLevelStartTip = TextManager.GetWithVariable("SteeringAutoPilotLocationTip", "[locationname]",
GameMain.GameSession?.StartLocation == null ? "Start" : GameMain.GameSession.StartLocation.Name); GameMain.GameSession?.StartLocation == null ? "Start" : GameMain.GameSession.StartLocation.DisplayName);
autoPilotLevelEndTip = TextManager.GetWithVariable("SteeringAutoPilotLocationTip", "[locationname]", autoPilotLevelEndTip = TextManager.GetWithVariable("SteeringAutoPilotLocationTip", "[locationname]",
GameMain.GameSession?.EndLocation == null ? "End" : GameMain.GameSession.EndLocation.Name); GameMain.GameSession?.EndLocation == null ? "End" : GameMain.GameSession.EndLocation.DisplayName);
} }
protected override void OnResolutionChanged() protected override void OnResolutionChanged()
@@ -589,7 +589,8 @@ namespace Barotrauma.Items.Components
Sonar sonar = item.GetComponent<Sonar>(); Sonar sonar = item.GetComponent<Sonar>();
if (sonar != null && controlledSub != null) if (sonar != null && controlledSub != null)
{ {
Vector2 displayPosToMaintain = ((posToMaintain.Value - controlledSub.WorldPosition)) / sonar.Range * sonar.DisplayRadius * sonar.Zoom; Vector2 displayPosToMaintain = ((posToMaintain.Value - controlledSub.WorldPosition)) * sonar.DisplayScale;
displayPosToMaintain.Y = -displayPosToMaintain.Y; displayPosToMaintain.Y = -displayPosToMaintain.Y;
displayPosToMaintain = displayPosToMaintain.ClampLength(velRect.Width / 2); displayPosToMaintain = displayPosToMaintain.ClampLength(velRect.Width / 2);
displayPosToMaintain = steerArea.Rect.Center.ToVector2() + displayPosToMaintain; displayPosToMaintain = steerArea.Rect.Center.ToVector2() + displayPosToMaintain;
@@ -677,7 +678,7 @@ namespace Barotrauma.Items.Components
if (obstacle.Intersection.HasValue) if (obstacle.Intersection.HasValue)
{ {
Vector2 intersectionPos = (obstacle.Intersection.Value - transducerCenter) *displayScale; Vector2 intersectionPos = (obstacle.Intersection.Value - transducerCenter) * displayScale;
intersectionPos.Y = -intersectionPos.Y; intersectionPos.Y = -intersectionPos.Y;
intersectionPos += center; intersectionPos += center;
GUI.DrawRectangle(spriteBatch, intersectionPos - Vector2.One * 2, Vector2.One * 4, GUIStyle.Red); GUI.DrawRectangle(spriteBatch, intersectionPos - Vector2.One * 2, Vector2.One * 4, GUIStyle.Red);
@@ -151,7 +151,7 @@ namespace Barotrauma.Items.Components
GUI.DrawLine(spriteBatch, GUI.DrawLine(spriteBatch,
new Vector2(debugRayStartPos.X, -debugRayStartPos.Y), new Vector2(debugRayStartPos.X, -debugRayStartPos.Y),
new Vector2(debugRayEndPos.X, -debugRayEndPos.Y), new Vector2(debugRayEndPos.X, -debugRayEndPos.Y),
Color.Yellow); Color.Yellow, width: 3f);
} }
} }
#endif #endif
@@ -95,7 +95,7 @@ namespace Barotrauma.Items.Components
MaxValueFloat = numberInputMax, MaxValueFloat = numberInputMax,
FloatValue = Math.Clamp(floatSignal, numberInputMin, numberInputMax), FloatValue = Math.Clamp(floatSignal, numberInputMin, numberInputMax),
DecimalsToDisplay = ciElement.NumberInputDecimalPlaces, DecimalsToDisplay = ciElement.NumberInputDecimalPlaces,
valueStep = numberInputStep, ValueStep = numberInputStep,
OnValueChanged = (ni) => OnValueChanged = (ni) =>
{ {
if (GameMain.Client == null) if (GameMain.Client == null)
@@ -121,7 +121,7 @@ namespace Barotrauma.Items.Components
MinValueInt = numberInputMin, MinValueInt = numberInputMin,
MaxValueInt = numberInputMax, MaxValueInt = numberInputMax,
IntValue = Math.Clamp(intSignal, numberInputMin, numberInputMax), IntValue = Math.Clamp(intSignal, numberInputMin, numberInputMax),
valueStep = numberInputStep, ValueStep = numberInputStep,
OnValueChanged = (ni) => OnValueChanged = (ni) =>
{ {
if (GameMain.Client == null) if (GameMain.Client == null)
@@ -137,7 +137,8 @@ namespace Barotrauma.Items.Components
} }
else else
{ {
DebugConsole.LogError($"Error creating a CustomInterface component: unexpected NumberType \"{(ciElement.NumberType.HasValue ? ciElement.NumberType.Value.ToString() : "none")}\""); DebugConsole.LogError($"Error creating a CustomInterface component: unexpected NumberType \"{(ciElement.NumberType.HasValue ? ciElement.NumberType.Value.ToString() : "none")}\"",
contentPackage: item.Prefab.ContentPackage);
} }
if (numberInput != null) if (numberInput != null)
{ {
@@ -349,8 +349,8 @@ namespace Barotrauma.Items.Components
} }
GUI.DrawString(spriteBatch, hudPos, texts[0].Value, textColors[0] * alpha, Color.Black * 0.7f * alpha, 2, GUIStyle.SubHeadingFont, ForceUpperCase.No); GUI.DrawString(spriteBatch, hudPos, texts[0].Value, textColors[0] * alpha, Color.Black * 0.7f * alpha, 2, GUIStyle.SubHeadingFont, ForceUpperCase.No);
hudPos.X += 5.0f; hudPos.X += 5.0f * GUI.Scale;
hudPos.Y += 24.0f * GameSettings.CurrentConfig.Graphics.TextScale; hudPos.Y += GUIStyle.SubHeadingFont.MeasureString(texts[0].Value).Y;
hudPos.X = (int)hudPos.X; hudPos.X = (int)hudPos.X;
hudPos.Y = (int)hudPos.Y; hudPos.Y = (int)hudPos.Y;
@@ -358,7 +358,7 @@ namespace Barotrauma.Items.Components
for (int i = 1; i < texts.Count; i++) for (int i = 1; i < texts.Count; i++)
{ {
GUI.DrawString(spriteBatch, hudPos, texts[i], textColors[i] * alpha, Color.Black * 0.7f * alpha, 2, GUIStyle.SmallFont); GUI.DrawString(spriteBatch, hudPos, texts[i], textColors[i] * alpha, Color.Black * 0.7f * alpha, 2, GUIStyle.SmallFont);
hudPos.Y += (int)(18.0f * GameSettings.CurrentConfig.Graphics.TextScale); hudPos.Y += (int)(GUIStyle.SubHeadingFont.MeasureString(texts[i].Value).Y);
} }
} }
} }
@@ -16,6 +16,8 @@ namespace Barotrauma.Items.Components
private GUIProgressBar powerIndicator; private GUIProgressBar powerIndicator;
private Vector2? debugDrawTargetPos;
public int UIElementHeight public int UIElementHeight
{ {
get get
@@ -422,9 +424,23 @@ namespace Barotrauma.Items.Components
if (GameMain.DebugDraw) if (GameMain.DebugDraw)
{ {
Vector2 firingPos = GetRelativeFiringPosition(); Vector2 firingPos = GetRelativeFiringPosition();
Vector2 endPos = firingPos + 3500 * GetBarrelDir();
firingPos.Y = -firingPos.Y; firingPos.Y = -firingPos.Y;
endPos.Y = -endPos.Y;
GUI.DrawLine(spriteBatch, firingPos - Vector2.UnitX * 5, firingPos + Vector2.UnitX * 5, Color.Red); GUI.DrawLine(spriteBatch, firingPos - Vector2.UnitX * 5, firingPos + Vector2.UnitX * 5, Color.Red);
GUI.DrawLine(spriteBatch, firingPos - Vector2.UnitY * 5, firingPos + Vector2.UnitY * 5, Color.Red); GUI.DrawLine(spriteBatch, firingPos - Vector2.UnitY * 5, firingPos + Vector2.UnitY * 5, Color.Red);
if (debugDrawTargetPos.HasValue)
{
Vector2 targetPos = debugDrawTargetPos.Value;
targetPos.Y = -targetPos.Y;
GUI.DrawLine(spriteBatch, targetPos - Vector2.UnitX * 5, targetPos + Vector2.UnitX * 5, Color.Magenta, width: 5);
GUI.DrawLine(spriteBatch, targetPos - Vector2.UnitY * 5, targetPos + Vector2.UnitY * 5, Color.Magenta, width: 5);
GUI.DrawLine(spriteBatch, firingPos, targetPos, Color.Magenta, width: 2);
}
GUI.DrawLine(spriteBatch, firingPos, endPos, Color.LightGray, width: 2);
} }
if (!editing || GUI.DisableHUD || !item.IsSelected) { return; } if (!editing || GUI.DisableHUD || !item.IsSelected) { return; }
@@ -258,7 +258,7 @@ namespace Barotrauma
else else
{ {
LocalizedString description = item.Description; LocalizedString description = item.Description;
if (item.HasTag(Tags.IdCard) || item.HasTag(Tags.DespawnContainer)) if (item.HasTag(Tags.IdCardTag) || item.HasTag(Tags.DespawnContainer))
{ {
string[] readTags = item.Tags.Split(','); string[] readTags = item.Tags.Split(',');
string idName = null; string idName = null;
@@ -730,7 +730,7 @@ namespace Barotrauma
DraggingInventory = null; DraggingInventory = null;
subInventory.savedPosition = PlayerInput.MousePosition.ToPoint(); subInventory.savedPosition = PlayerInput.MousePosition.ToPoint();
} }
else else if (DraggingInventory == subInventory)
{ {
subInventory.savedPosition = PlayerInput.MousePosition.ToPoint(); subInventory.savedPosition = PlayerInput.MousePosition.ToPoint();
} }
@@ -901,7 +901,7 @@ namespace Barotrauma
if (IsOnInventorySlot(Character.Controlled.SelectedCharacter.Inventory)) { return true; } if (IsOnInventorySlot(Character.Controlled.SelectedCharacter.Inventory)) { return true; }
} }
bool IsOnInventorySlot(Inventory inventory) static bool IsOnInventorySlot(Inventory inventory)
{ {
for (var i = 0; i < inventory.visualSlots.Length; i++) for (var i = 0; i < inventory.visualSlots.Length; i++)
{ {
@@ -1107,7 +1107,7 @@ namespace Barotrauma
if (container.MovableFrame && !IsInventoryHoverAvailable(Owner as Character, container)) if (container.MovableFrame && !IsInventoryHoverAvailable(Owner as Character, container))
{ {
if (positionUpdateQueued) // Wait a frame before updating the positioning of the container after a resolution change to have everything working if (container.Inventory.positionUpdateQueued) // Wait a frame before updating the positioning of the container after a resolution change to have everything working
{ {
int height = (int)(movableFrameRectHeight * UIScale); int height = (int)(movableFrameRectHeight * UIScale);
CreateSlots(); CreateSlots();
@@ -1116,7 +1116,7 @@ namespace Barotrauma
draggableIndicatorOffset = DraggableIndicator.size * draggableIndicatorScale / 2f; draggableIndicatorOffset = DraggableIndicator.size * draggableIndicatorScale / 2f;
draggableIndicatorOffset += new Vector2(height / 2f - draggableIndicatorOffset.Y); draggableIndicatorOffset += new Vector2(height / 2f - draggableIndicatorOffset.Y);
container.Inventory.originalPos = container.Inventory.savedPosition = container.Inventory.movableFrameRect.Center; container.Inventory.originalPos = container.Inventory.savedPosition = container.Inventory.movableFrameRect.Center;
positionUpdateQueued = false; container.Inventory.positionUpdateQueued = false;
} }
if (container.Inventory.movableFrameRect.Size == Point.Zero || GUI.HasSizeChanged(prevScreenResolution, prevUIScale, prevHUDScale)) if (container.Inventory.movableFrameRect.Size == Point.Zero || GUI.HasSizeChanged(prevScreenResolution, prevUIScale, prevHUDScale))
@@ -1127,11 +1127,20 @@ namespace Barotrauma
prevScreenResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight); prevScreenResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
prevUIScale = UIScale; prevUIScale = UIScale;
prevHUDScale = GUI.Scale; prevHUDScale = GUI.Scale;
positionUpdateQueued = true; container.Inventory.positionUpdateQueued = true;
} }
else else
{ {
GUI.DrawRectangle(spriteBatch, container.Inventory.movableFrameRect, movableFrameRectColor, true); Color color = movableFrameRectColor;
if (DraggingInventory != null && DraggingInventory != container.Inventory)
{
color *= 0.7f;
}
else if (container.Inventory.movableFrameRect.Contains(PlayerInput.MousePosition))
{
color = Color.Lerp(color, PlayerInput.PrimaryMouseButtonHeld() ? Color.Black : Color.White, 0.25f);
}
GUI.DrawRectangle(spriteBatch, container.Inventory.movableFrameRect, color, true);
DraggableIndicator.Draw(spriteBatch, container.Inventory.movableFrameRect.Location.ToVector2() + draggableIndicatorOffset, 0, draggableIndicatorScale); DraggableIndicator.Draw(spriteBatch, container.Inventory.movableFrameRect.Location.ToVector2() + draggableIndicatorOffset, 0, draggableIndicatorScale);
} }
} }
@@ -1274,7 +1283,15 @@ namespace Barotrauma
int itemCount = 0; int itemCount = 0;
foreach (Item item in DraggingItems) foreach (Item item in DraggingItems)
{ {
bool success = selectedInventory.TryPutItem(item, slotIndex, allowSwapping: !anySuccess, allowCombine, Character.Controlled); if (selectedInventory.GetItemAt(slotIndex)?.OwnInventory?.Container is { } container &&
container.Inventory.CanBePut(item))
{
if (!container.AllowDragAndDrop || !container.DrawInventory)
{
allowCombine = false;
}
}
bool success = selectedInventory.TryPutItem(item, slotIndex, allowSwapping: !anySuccess, allowCombine, Character.Controlled);
if (success) if (success)
{ {
anySuccess = true; anySuccess = true;
@@ -1380,18 +1397,17 @@ namespace Barotrauma
protected static Rectangle GetSubInventoryHoverArea(SlotReference subSlot) protected static Rectangle GetSubInventoryHoverArea(SlotReference subSlot)
{ {
Rectangle hoverArea; if (Character.Controlled == null)
if ((Screen.Selected != GameMain.SubEditorScreen || GameMain.SubEditorScreen.DrawCharacterInventory) &&
(!subSlot.Inventory.Movable() ||
(Character.Controlled?.Inventory == subSlot.ParentInventory && !Character.Controlled.HasEquippedItem(subSlot.Item)) ||
(subSlot.ParentInventory is CharacterInventory characterInventory && characterInventory.CurrentLayout != CharacterInventory.Layout.Default)))
{ {
//slot not visible as a separate, movable panel -> just use the area of the slot directly return Rectangle.Empty;
hoverArea = subSlot.Slot.Rect;
hoverArea.Location += subSlot.Slot.DrawOffset.ToPoint();
hoverArea = Rectangle.Union(hoverArea, subSlot.Slot.EquipButtonRect);
} }
else
Rectangle hoverArea;
bool isMovable = subSlot.Inventory.Movable() && !subSlot.ParentInventory.IsInventoryHoverAvailable(Character.Controlled, subSlot.Item?.GetComponent<ItemContainer>());
bool unEquipped = Character.Controlled.Inventory == subSlot.ParentInventory && !Character.Controlled.HasEquippedItem(subSlot.Item);
bool isDefaultLayout = subSlot.ParentInventory is not CharacterInventory characterInventory || characterInventory.CurrentLayout == CharacterInventory.Layout.Default;
bool subEditorCharacterInventoryHidden = Screen.Selected == GameMain.SubEditorScreen && !GameMain.SubEditorScreen.DrawCharacterInventory;
if (subEditorCharacterInventoryHidden || (isMovable && !unEquipped && isDefaultLayout))
{ {
hoverArea = subSlot.Inventory.BackgroundFrame; hoverArea = subSlot.Inventory.BackgroundFrame;
hoverArea.Location += subSlot.Slot.DrawOffset.ToPoint(); hoverArea.Location += subSlot.Slot.DrawOffset.ToPoint();
@@ -1400,6 +1416,13 @@ namespace Barotrauma
hoverArea = Rectangle.Union(hoverArea, subSlot.Inventory.movableFrameRect); hoverArea = Rectangle.Union(hoverArea, subSlot.Inventory.movableFrameRect);
} }
} }
else
{
//slot not visible as a separate, movable panel -> just use the area of the slot directly
hoverArea = subSlot.Slot.Rect;
hoverArea.Location += subSlot.Slot.DrawOffset.ToPoint();
hoverArea = Rectangle.Union(hoverArea, subSlot.Slot.EquipButtonRect);
}
if (subSlot.Inventory?.visualSlots != null) if (subSlot.Inventory?.visualSlots != null)
{ {
@@ -1584,11 +1607,16 @@ namespace Barotrauma
if (DraggingItems.Any() && inventory != null && slotIndex > -1 && slotIndex < inventory.visualSlots.Length) if (DraggingItems.Any() && inventory != null && slotIndex > -1 && slotIndex < inventory.visualSlots.Length)
{ {
var itemInSlot = inventory.slots[slotIndex].FirstOrDefault();
if (inventory.CanBePutInSlot(DraggingItems.First(), slotIndex)) if (inventory.CanBePutInSlot(DraggingItems.First(), slotIndex))
{ {
canBePut = true; canBePut = true;
} }
else if (inventory.slots[slotIndex].FirstOrDefault()?.OwnInventory?.CanBePut(DraggingItems.First()) ?? false) else if
(itemInSlot?.OwnInventory != null &&
itemInSlot.OwnInventory.CanBePut(DraggingItems.First()) &&
itemInSlot.OwnInventory.Container.AllowDragAndDrop &&
itemInSlot.OwnInventory.Container.DrawInventory)
{ {
canBePut = true; canBePut = true;
} }
@@ -1922,9 +1950,25 @@ namespace Barotrauma
foreach (UInt16 id in receivedItemIDs[i]) foreach (UInt16 id in receivedItemIDs[i])
{ {
if (Entity.FindEntityByID(id) is not Item item || slots[i].Contains(item)) { continue; } if (Entity.FindEntityByID(id) is not Item item || slots[i].Contains(item)) { continue; }
if (Owner is Item thisItem && thisItem.Container == item)
{
//if this item is inside the item we're trying to contain inside it, we need to drop it (both items can't be inside each other!)
//can happen when a player swaps the items to be "the other way around", and we receive a message about the contained item
//before the message about the "parent item" being placed in some other inventory (like the player's inventory)
thisItem.Drop(null);
}
if (!TryPutItem(item, i, false, false, null, false)) if (!TryPutItem(item, i, false, false, null, false))
{ {
ForceToSlot(item, i); try
{
ForceToSlot(item, i);
}
catch (InvalidOperationException e)
{
DebugConsole.AddSafeError(e.Message + "\n" + e.StackTrace.CleanupStackTrace());
}
} }
for (int j = 0; j < capacity; j++) for (int j = 0; j < capacity; j++)
{ {
@@ -132,9 +132,9 @@ namespace Barotrauma
return GetDrawDepth(SpriteDepth + DrawDepthOffset, Sprite); return GetDrawDepth(SpriteDepth + DrawDepthOffset, Sprite);
} }
public Color GetSpriteColor(bool withHighlight = false) public Color GetSpriteColor(Color? defaultColor = null, bool withHighlight = false)
{ {
Color color = spriteColor; Color color = defaultColor ?? spriteColor;
if (Prefab.UseContainedSpriteColor && ownInventory != null) if (Prefab.UseContainedSpriteColor && ownInventory != null)
{ {
foreach (Item item in ContainedItems) foreach (Item item in ContainedItems)
@@ -286,7 +286,8 @@ namespace Barotrauma
{ {
int padding = 100; int padding = 100;
Vector2 min = new Vector2(-rect.Width / 2 - padding, -rect.Height / 2 - padding); RectangleF boundingBox = GetTransformedQuad().BoundingAxisAlignedRectangle;
Vector2 min = new Vector2(-boundingBox.Width / 2 - padding, -boundingBox.Height / 2 - padding);
Vector2 max = -min; Vector2 max = -min;
foreach (IDrawableComponent drawable in drawableComponents) foreach (IDrawableComponent drawable in drawableComponents)
@@ -333,9 +334,7 @@ namespace Barotrauma
else if (!ShowItems) { return; } else if (!ShowItems) { return; }
} }
Color color = Color color = GetSpriteColor(spriteColor);
overrideColor ??
(IsIncludedInSelection && editing ? GUIStyle.Blue : GetSpriteColor(withHighlight: true));
bool isWiringMode = editing && SubEditorScreen.TransparentWiringMode && SubEditorScreen.IsWiringMode() && !isWire && parentInventory == null; bool isWiringMode = editing && SubEditorScreen.TransparentWiringMode && SubEditorScreen.IsWiringMode() && !isWire && parentInventory == null;
bool renderTransparent = isWiringMode && GetComponent<ConnectionPanel>() == null; bool renderTransparent = isWiringMode && GetComponent<ConnectionPanel>() == null;
@@ -388,9 +387,9 @@ namespace Barotrauma
{ {
if (Prefab.ResizeHorizontal || Prefab.ResizeVertical) if (Prefab.ResizeHorizontal || Prefab.ResizeVertical)
{ {
Vector2 size = new Vector2(rect.Width, rect.Height);
if (color.A > 0) if (color.A > 0)
{ {
Vector2 size = new Vector2(rect.Width, rect.Height);
activeSprite.DrawTiled(spriteBatch, new Vector2(DrawPosition.X - rect.Width / 2, -(DrawPosition.Y + rect.Height / 2)) + drawOffset, activeSprite.DrawTiled(spriteBatch, new Vector2(DrawPosition.X - rect.Width / 2, -(DrawPosition.Y + rect.Height / 2)) + drawOffset,
size, color: color, size, color: color,
textureScale: Vector2.One * Scale, textureScale: Vector2.One * Scale,
@@ -403,18 +402,7 @@ namespace Barotrauma
textureScale: Vector2.One * Scale, textureScale: Vector2.One * Scale,
depth: d); depth: d);
} }
foreach (var decorativeSprite in Prefab.DecorativeSprites) DrawDecorativeSprites(spriteBatch, DrawPosition, flippedX && Prefab.CanSpriteFlipX, flippedY && Prefab.CanSpriteFlipY, rotation: 0, depth);
{
if (!spriteAnimState[decorativeSprite].IsActive) { continue; }
Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState, spriteAnimState[decorativeSprite].RandomOffsetMultiplier, flippedX && Prefab.CanSpriteFlipX ? RotationRad : -RotationRad) * Scale;
if (flippedX && Prefab.CanSpriteFlipX) { offset.X = -offset.X; }
if (flippedY && Prefab.CanSpriteFlipY) { offset.Y = -offset.Y; }
decorativeSprite.Sprite.DrawTiled(spriteBatch,
new Vector2(DrawPosition.X + offset.X - rect.Width / 2, -(DrawPosition.Y + offset.Y + rect.Height / 2)),
size, color: color,
textureScale: Vector2.One * Scale,
depth: Math.Min(depth + (decorativeSprite.Sprite.Depth - activeSprite.Depth), 0.999f));
}
} }
} }
else else
@@ -434,19 +422,8 @@ namespace Barotrauma
Prefab.InfectedSprite?.Draw(spriteBatch, new Vector2(DrawPosition.X, -DrawPosition.Y) + drawOffset, color, Prefab.InfectedSprite.Origin, RotationRad, Scale, activeSprite.effects, depth - 0.001f); Prefab.InfectedSprite?.Draw(spriteBatch, new Vector2(DrawPosition.X, -DrawPosition.Y) + drawOffset, color, Prefab.InfectedSprite.Origin, RotationRad, Scale, activeSprite.effects, depth - 0.001f);
Prefab.DamagedInfectedSprite?.Draw(spriteBatch, new Vector2(DrawPosition.X, -DrawPosition.Y) + drawOffset, Infector.HealthColor, Prefab.DamagedInfectedSprite.Origin, RotationRad, Scale, activeSprite.effects, depth - 0.002f); Prefab.DamagedInfectedSprite?.Draw(spriteBatch, new Vector2(DrawPosition.X, -DrawPosition.Y) + drawOffset, Infector.HealthColor, Prefab.DamagedInfectedSprite.Origin, RotationRad, Scale, activeSprite.effects, depth - 0.002f);
} }
foreach (var decorativeSprite in Prefab.DecorativeSprites)
{ DrawDecorativeSprites(spriteBatch, DrawPosition, flippedX && Prefab.CanSpriteFlipX, flippedY && Prefab.CanSpriteFlipY, -RotationRad, depth);
if (!spriteAnimState[decorativeSprite].IsActive) { continue; }
float rot = decorativeSprite.GetRotation(ref spriteAnimState[decorativeSprite].RotationState, spriteAnimState[decorativeSprite].RandomRotationFactor);
bool flipX = flippedX && Prefab.CanSpriteFlipX;
bool flipY = flippedY && Prefab.CanSpriteFlipY;
Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState, spriteAnimState[decorativeSprite].RandomOffsetMultiplier, flipX ^ flipY ? RotationRad : -RotationRad) * Scale;
if (flipX) { offset.X = -offset.X; }
if (flipY) { offset.Y = -offset.Y; }
decorativeSprite.Sprite.Draw(spriteBatch, new Vector2(DrawPosition.X + offset.X, -(DrawPosition.Y + offset.Y)), color,
RotationRad + rot, decorativeSprite.GetScale(spriteAnimState[decorativeSprite].RandomScaleFactor) * Scale, activeSprite.effects,
depth: Math.Min(depth + (decorativeSprite.Sprite.Depth - activeSprite.Depth), 0.999f));
}
} }
} }
else if (body.Enabled) else if (body.Enabled)
@@ -490,21 +467,7 @@ namespace Barotrauma
float d = Math.Min(depth + (fadeInBrokenSprite.Sprite.Depth - activeSprite.Depth - 0.000001f), 0.999f); float d = Math.Min(depth + (fadeInBrokenSprite.Sprite.Depth - activeSprite.Depth - 0.000001f), 0.999f);
body.Draw(spriteBatch, fadeInBrokenSprite.Sprite, color * fadeInBrokenSpriteAlpha, d, Scale); body.Draw(spriteBatch, fadeInBrokenSprite.Sprite, color * fadeInBrokenSpriteAlpha, d, Scale);
} }
foreach (var decorativeSprite in Prefab.DecorativeSprites) DrawDecorativeSprites(spriteBatch, body.DrawPosition, flipX: body.Dir < 0, flipY: false, rotation: body.Rotation, depth: depth);
{
if (!spriteAnimState[decorativeSprite].IsActive) { continue; }
float rotation = decorativeSprite.GetRotation(ref spriteAnimState[decorativeSprite].RotationState, spriteAnimState[decorativeSprite].RandomRotationFactor);
Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState, spriteAnimState[decorativeSprite].RandomOffsetMultiplier, -RotationRad) * Scale;
if (flippedX && Prefab.CanSpriteFlipX) { offset.X = -offset.X; }
if (flippedY && Prefab.CanSpriteFlipY) { offset.Y = -offset.Y; }
var ca = MathF.Cos(-body.DrawRotation);
var sa = MathF.Sin(-body.DrawRotation);
Vector2 transformedOffset = new Vector2(ca * offset.X + sa * offset.Y, -sa * offset.X + ca * offset.Y);
decorativeSprite.Sprite.Draw(spriteBatch, new Vector2(body.DrawPosition.X + transformedOffset.X, -(body.DrawPosition.Y + transformedOffset.Y)), color,
-body.DrawRotation + rotation, decorativeSprite.GetScale(spriteAnimState[decorativeSprite].RandomScaleFactor) * Scale, activeSprite.effects,
depth: depth + (decorativeSprite.Sprite.Depth - activeSprite.Depth));
}
} }
foreach (var upgrade in Upgrades) foreach (var upgrade in Upgrades)
@@ -522,7 +485,6 @@ namespace Barotrauma
rotation, decorativeSprite.GetScale(spriteAnimState[decorativeSprite].RandomScaleFactor) * Scale, activeSprite.effects, rotation, decorativeSprite.GetScale(spriteAnimState[decorativeSprite].RandomScaleFactor) * Scale, activeSprite.effects,
depth: depth + (decorativeSprite.Sprite.Depth - activeSprite.Depth)); depth: depth + (decorativeSprite.Sprite.Depth - activeSprite.Depth));
} }
} }
activeSprite.effects = oldEffects; activeSprite.effects = oldEffects;
@@ -567,8 +529,14 @@ namespace Barotrauma
Vector2 drawPos = new Vector2(DrawPosition.X - rect.Width / 2, -(DrawPosition.Y + rect.Height / 2)); Vector2 drawPos = new Vector2(DrawPosition.X - rect.Width / 2, -(DrawPosition.Y + rect.Height / 2));
Vector2 drawSize = new Vector2(MathF.Ceiling(rect.Width + Math.Abs(drawPos.X - (int)drawPos.X)), MathF.Ceiling(rect.Height + Math.Abs(drawPos.Y - (int)drawPos.Y))); Vector2 drawSize = new Vector2(MathF.Ceiling(rect.Width + Math.Abs(drawPos.X - (int)drawPos.X)), MathF.Ceiling(rect.Height + Math.Abs(drawPos.Y - (int)drawPos.Y)));
drawPos = new Vector2(MathF.Floor(drawPos.X), MathF.Floor(drawPos.Y)); drawPos = new Vector2(MathF.Floor(drawPos.X), MathF.Floor(drawPos.Y));
GUI.DrawRectangle(spriteBatch, drawPos, drawSize, GUI.DrawRectangle(sb: spriteBatch,
Color.White, false, 0, thickness: Math.Max(1, (int)(2 / Screen.Selected.Cam.Zoom))); center: drawPos + drawSize * 0.5f,
width: drawSize.X,
height: drawSize.Y,
rotation: RotationRad,
clr: Color.White,
depth: 0,
thickness: 2f / Screen.Selected.Cam.Zoom);
foreach (Rectangle t in Prefab.Triggers) foreach (Rectangle t in Prefab.Triggers)
{ {
@@ -618,6 +586,62 @@ namespace Barotrauma
} }
return origin; return origin;
} }
Color GetSpriteColor(Color defaultColor)
{
return
overrideColor ??
(IsIncludedInSelection && editing ? GUIStyle.Blue : this.GetSpriteColor(defaultColor: defaultColor, withHighlight: true));
}
}
public void DrawDecorativeSprites(SpriteBatch spriteBatch, Vector2 drawPos, bool flipX, bool flipY, float rotation, float depth)
{
foreach (var decorativeSprite in Prefab.DecorativeSprites)
{
Color decorativeSpriteColor = GetSpriteColor(decorativeSprite.Color).Multiply(GetSpriteColor(spriteColor));
if (!spriteAnimState[decorativeSprite].IsActive) { continue; }
Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState, spriteAnimState[decorativeSprite].RandomOffsetMultiplier,
flipX ^ flipY ? -rotation : rotation) * Scale;
if (ResizeHorizontal || ResizeVertical)
{
decorativeSprite.Sprite.DrawTiled(spriteBatch,
new Vector2(DrawPosition.X + offset.X - rect.Width / 2, -(DrawPosition.Y + offset.Y + rect.Height / 2)),
new Vector2(rect.Width, rect.Height), color: decorativeSpriteColor,
textureScale: Vector2.One * Scale,
depth: Math.Min(depth + (decorativeSprite.Sprite.Depth - activeSprite.Depth), 0.999f));
}
else
{
float spriteRotation = decorativeSprite.GetRotation(ref spriteAnimState[decorativeSprite].RotationState, spriteAnimState[decorativeSprite].RandomRotationFactor);
Vector2 origin = decorativeSprite.Sprite.Origin;
SpriteEffects spriteEffects = SpriteEffects.None;
if (flipX && Prefab.CanSpriteFlipX)
{
offset.X = -offset.X;
origin.X = -origin.X + decorativeSprite.Sprite.size.X;
spriteEffects = SpriteEffects.FlipHorizontally;
}
if (flipY && Prefab.CanSpriteFlipY)
{
offset.Y = -offset.Y;
origin.Y = -origin.Y + decorativeSprite.Sprite.size.Y;
spriteEffects |= SpriteEffects.FlipVertically;
}
if (body != null)
{
var ca = MathF.Cos(-body.DrawRotation);
var sa = MathF.Sin(-body.DrawRotation);
offset = new Vector2(ca * offset.X + sa * offset.Y, -sa * offset.X + ca * offset.Y);
}
decorativeSprite.Sprite.Draw(spriteBatch, new Vector2(drawPos.X + offset.X, -(drawPos.Y + offset.Y)), decorativeSpriteColor, origin,
-rotation + spriteRotation, decorativeSprite.GetScale(spriteAnimState[decorativeSprite].RandomScaleFactor) * Scale, spriteEffects,
depth: depth + (decorativeSprite.Sprite.Depth - activeSprite.Depth));
}
}
} }
partial void OnCollisionProjSpecific(float impact) partial void OnCollisionProjSpecific(float impact)
@@ -795,6 +819,19 @@ namespace Barotrauma
} }
} }
public override bool IsMouseOn(Vector2 position)
{
Vector2 rectSize = rect.Size.ToVector2();
Vector2 bodyPos = WorldPosition;
Vector2 transformedMousePos = MathUtils.RotatePointAroundTarget(position, bodyPos, RotationRad);
return
Math.Abs(transformedMousePos.X - bodyPos.X) < rectSize.X / 2.0f &&
Math.Abs(transformedMousePos.Y - bodyPos.Y) < rectSize.Y / 2.0f;
}
public GUIComponent CreateEditingHUD(bool inGame = false) public GUIComponent CreateEditingHUD(bool inGame = false)
{ {
activeEditors.Clear(); activeEditors.Clear();
@@ -852,7 +889,12 @@ namespace Barotrauma
CanBeFocused = true CanBeFocused = true
}; };
new GUIButton(new RectTransform(new Vector2(0.23f, 1.0f), buttonContainer.RectTransform), TextManager.Get("MirrorEntityX"), style: "GUIButtonSmall") GUINumberInput rotationField =
itemEditor.Fields.TryGetValue("Rotation".ToIdentifier(), out var rotationFieldComponents)
? rotationFieldComponents.OfType<GUINumberInput>().FirstOrDefault()
: null;
var mirrorX = new GUIButton(new RectTransform(new Vector2(0.23f, 1.0f), buttonContainer.RectTransform), TextManager.Get("MirrorEntityX"), style: "GUIButtonSmall")
{ {
ToolTip = TextManager.Get("MirrorEntityXToolTip"), ToolTip = TextManager.Get("MirrorEntityXToolTip"),
Enabled = Prefab.CanFlipX, Enabled = Prefab.CanFlipX,
@@ -863,10 +905,13 @@ namespace Barotrauma
me.FlipX(relativeToSub: false); me.FlipX(relativeToSub: false);
} }
if (!SelectedList.Contains(this)) { FlipX(relativeToSub: false); } if (!SelectedList.Contains(this)) { FlipX(relativeToSub: false); }
ColorFlipButton(button, FlippedX);
if (rotationField != null) { rotationField.FloatValue = Rotation; }
return true; return true;
} }
}; };
new GUIButton(new RectTransform(new Vector2(0.23f, 1.0f), buttonContainer.RectTransform), TextManager.Get("MirrorEntityY"), style: "GUIButtonSmall") ColorFlipButton(mirrorX, FlippedX);
var mirrorY = new GUIButton(new RectTransform(new Vector2(0.23f, 1.0f), buttonContainer.RectTransform), TextManager.Get("MirrorEntityY"), style: "GUIButtonSmall")
{ {
ToolTip = TextManager.Get("MirrorEntityYToolTip"), ToolTip = TextManager.Get("MirrorEntityYToolTip"),
Enabled = Prefab.CanFlipY, Enabled = Prefab.CanFlipY,
@@ -877,9 +922,12 @@ namespace Barotrauma
me.FlipY(relativeToSub: false); me.FlipY(relativeToSub: false);
} }
if (!SelectedList.Contains(this)) { FlipY(relativeToSub: false); } if (!SelectedList.Contains(this)) { FlipY(relativeToSub: false); }
ColorFlipButton(button, FlippedY);
if (rotationField != null) { rotationField.FloatValue = Rotation; }
return true; return true;
} }
}; };
ColorFlipButton(mirrorY, FlippedY);
if (Sprite != null) if (Sprite != null)
{ {
var reloadTextureButton = new GUIButton(new RectTransform(new Vector2(0.23f, 1.0f), buttonContainer.RectTransform), TextManager.Get("ReloadSprite"), style: "GUIButtonSmall"); var reloadTextureButton = new GUIButton(new RectTransform(new Vector2(0.23f, 1.0f), buttonContainer.RectTransform), TextManager.Get("ReloadSprite"), style: "GUIButtonSmall");
@@ -1540,6 +1588,23 @@ namespace Barotrauma
RemoveFromDroppedStack(allowClientExecute: true); RemoveFromDroppedStack(allowClientExecute: true);
} }
break; break;
case EventType.SetHighlight:
bool isTargetedForThisClient = msg.ReadBoolean();
if (isTargetedForThisClient)
{
bool highlight = msg.ReadBoolean();
ExternalHighlight = highlight;
if (highlight)
{
Color highlightColor = msg.ReadColorR8G8B8A8();
HighlightColor = highlightColor;
}
else
{
HighlightColor = null;
}
}
break;
default: default:
throw new Exception($"Malformed incoming item event: unsupported event type {eventType}"); throw new Exception($"Malformed incoming item event: unsupported event type {eventType}");
} }
@@ -1940,5 +2005,13 @@ namespace Barotrauma
Inventory.DraggingSlot = null; Inventory.DraggingSlot = null;
} }
} }
public void OnPlayerSkillsChanged()
{
foreach (ItemComponent ic in components)
{
ic.OnPlayerSkillsChanged();
}
}
} }
} }
@@ -321,10 +321,8 @@ namespace Barotrauma
} }
else else
{ {
if (ResizeHorizontal) if (ResizeHorizontal) { placeSize.X = Math.Max(position.X - placePosition.X, Size.X); }
placeSize.X = Math.Max(position.X - placePosition.X, Size.X); if (ResizeVertical) { placeSize.Y = Math.Max(placePosition.Y - position.Y, Size.Y); }
if (ResizeVertical)
placeSize.Y = Math.Max(placePosition.Y - position.Y, Size.Y);
if (PlayerInput.PrimaryMouseButtonReleased()) if (PlayerInput.PrimaryMouseButtonReleased())
{ {
@@ -369,15 +367,31 @@ namespace Barotrauma
} }
} }
public override void DrawPlacing(SpriteBatch spriteBatch, Rectangle placeRect, float scale = 1.0f, SpriteEffects spriteEffects = SpriteEffects.None) public override void DrawPlacing(SpriteBatch spriteBatch, Rectangle placeRect, float scale = 1.0f, float rotation = 0.0f, SpriteEffects spriteEffects = SpriteEffects.None)
{ {
if (!ResizeHorizontal && !ResizeVertical) if (!ResizeHorizontal && !ResizeVertical)
{ {
Sprite.Draw(spriteBatch, new Vector2(placeRect.Center.X, -(placeRect.Y - placeRect.Height / 2)), SpriteColor * 0.8f, scale: scale); sprite.Draw(
spriteBatch: spriteBatch,
pos: new Vector2(placeRect.Center.X,
-(placeRect.Y - placeRect.Height / 2)),
color: SpriteColor * 0.8f,
scale: scale,
rotate: rotation,
spriteEffect: spriteEffects ^ sprite.effects);
} }
else else
{ {
Sprite.DrawTiled(spriteBatch, new Vector2(placeRect.X, -placeRect.Y), placeRect.Size.ToVector2(), SpriteColor * 0.8f); Vector2 position = placeRect.Location.ToVector2();
Vector2 placeSize = placeRect.Size.ToVector2();
sprite?.DrawTiled(
spriteBatch: spriteBatch,
position: new Vector2(position.X, -position.Y),
targetSize: placeSize,
rotation: rotation,
textureScale: Vector2.One * scale,
color: SpriteColor * 0.8f,
spriteEffects: spriteEffects ^ sprite.effects);
} }
} }
@@ -277,12 +277,21 @@ namespace Barotrauma
Rectangle drawRect = Rectangle drawRect =
Submarine == null ? rect : new Rectangle((int)(Submarine.DrawPosition.X + rect.X), (int)(Submarine.DrawPosition.Y + rect.Y), rect.Width, rect.Height); Submarine == null ? rect : new Rectangle((int)(Submarine.DrawPosition.X + rect.X), (int)(Submarine.DrawPosition.Y + rect.Y), rect.Width, rect.Height);
if ((IsSelected || IsHighlighted) && editing) if (editing)
{ {
if (IsSelected || IsHighlighted)
{
GUI.DrawRectangle(spriteBatch,
new Vector2(drawRect.X, -drawRect.Y),
new Vector2(rect.Width, rect.Height),
(IsHighlighted ? Color.LightBlue * 0.8f : GUIStyle.Red * 0.5f) * alpha, false, 0, (int)Math.Max(5.0f / Screen.Selected.Cam.Zoom, 1.0f));
}
float waterHeight = WaterVolume / rect.Width;
GUI.DrawRectangle(spriteBatch, GUI.DrawRectangle(spriteBatch,
new Vector2(drawRect.X, -drawRect.Y), new Vector2(drawRect.X, -drawRect.Y + drawRect.Height - waterHeight),
new Vector2(rect.Width, rect.Height), new Vector2(drawRect.Width, waterHeight),
(IsHighlighted ? Color.LightBlue * 0.8f : GUIStyle.Red * 0.5f) * alpha, false, 0, (int)Math.Max(5.0f / Screen.Selected.Cam.Zoom, 1.0f)); Color.Blue * 0.25f, isFilled: true);
} }
GUI.DrawRectangle(spriteBatch, GUI.DrawRectangle(spriteBatch,
@@ -746,7 +755,7 @@ namespace Barotrauma
var newFire = i < FireSources.Count ? var newFire = i < FireSources.Count ?
FireSources[i] : FireSources[i] :
new FireSource(Submarine == null ? pos : pos + Submarine.Position, null, true); new FireSource(Submarine == null ? pos : pos + Submarine.Position, sourceCharacter: null, isNetworkMessage: true);
newFire.Position = pos; newFire.Position = pos;
newFire.Size = new Vector2(size, newFire.Size.Y); newFire.Size = new Vector2(size, newFire.Size.Y);
@@ -86,7 +86,7 @@ namespace Barotrauma
MathUtils.RoundTowardsClosest(center.Y, Submarine.GridSize.Y) - center.Y - Submarine.GridSize.Y / 2); MathUtils.RoundTowardsClosest(center.Y, Submarine.GridSize.Y) - center.Y - Submarine.GridSize.Y / 2);
MapEntity.SelectedList.Clear(); MapEntity.SelectedList.Clear();
assemblyEntities.ForEach(e => MapEntity.AddSelection(e)); entities.ForEach(e => MapEntity.AddSelection(e));
foreach (MapEntity mapEntity in assemblyEntities) foreach (MapEntity mapEntity in assemblyEntities)
{ {
@@ -43,7 +43,7 @@ namespace Barotrauma
{ {
mainElement = mainElement.FirstElement(); mainElement = mainElement.FirstElement();
prefabs.Clear(); prefabs.Clear();
DebugConsole.NewMessage($"Overriding all background creatures with '{configPath}'", Color.Yellow); DebugConsole.NewMessage($"Overriding all background creatures with '{configPath}'", Color.MediumPurple);
} }
else if (prefabs.Any()) else if (prefabs.Any())
{ {
@@ -1,10 +1,9 @@
using Barotrauma.Networking; using Barotrauma.Networking;
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.Linq;
using System.Collections.Generic; using System.Collections.Generic;
using FarseerPhysics;
namespace Barotrauma namespace Barotrauma
{ {
@@ -53,7 +52,7 @@ namespace Barotrauma
foreach (InterestingPosition pos in PositionsOfInterest) foreach (InterestingPosition pos in PositionsOfInterest)
{ {
Color color = Color.Yellow; Color color = Color.Yellow;
if (pos.PositionType == PositionType.Cave) if (pos.PositionType == PositionType.Cave || pos.PositionType == PositionType.AbyssCave)
{ {
color = Color.DarkOrange; color = Color.DarkOrange;
} }
@@ -61,6 +60,10 @@ namespace Barotrauma
{ {
color = Color.LightGray; color = Color.LightGray;
} }
if (!pos.IsValid)
{
color = Color.Red;
}
GUI.DrawRectangle(spriteBatch, new Vector2(pos.Position.X - 15.0f, -pos.Position.Y - 15.0f), new Vector2(30.0f, 30.0f), color, true); GUI.DrawRectangle(spriteBatch, new Vector2(pos.Position.X - 15.0f, -pos.Position.Y - 15.0f), new Vector2(30.0f, 30.0f), color, true);
} }
@@ -162,6 +162,8 @@ namespace Barotrauma
CanBeVisible = CanBeVisible =
Sprite != null || Sprite != null ||
Prefab.DeformableSprite != null || Prefab.DeformableSprite != null ||
ParticleEmitters is { Length: > 0 } ||
(GameMain.DebugDraw && Triggers is { Count: > 0 }) ||
Prefab.OverrideProperties.Any(p => p != null && (p.Sprites.Any() || p.DeformableSprite != null)); Prefab.OverrideProperties.Any(p => p != null && (p.Sprites.Any() || p.DeformableSprite != null));
} }
@@ -24,6 +24,13 @@ namespace Barotrauma
public bool ForceRefreshVisibleObjects; public bool ForceRefreshVisibleObjects;
partial void RemoveProjSpecific()
{
visibleObjectsBack.Clear();
visibleObjectsMid.Clear();
visibleObjectsFront.Clear();
}
partial void UpdateProjSpecific(float deltaTime) partial void UpdateProjSpecific(float deltaTime)
{ {
foreach (LevelObject obj in visibleObjectsBack) foreach (LevelObject obj in visibleObjectsBack)
@@ -133,6 +133,8 @@ namespace Barotrauma.Lights
public Rectangle BoundingBox { get; private set; } public Rectangle BoundingBox { get; private set; }
public bool IsInvalid { get; private set; }
public ConvexHull(Rectangle rect, bool isHorizontal, MapEntity parent) public ConvexHull(Rectangle rect, bool isHorizontal, MapEntity parent)
{ {
shadowEffect ??= new BasicEffect(GameMain.Instance.GraphicsDevice) shadowEffect ??= new BasicEffect(GameMain.Instance.GraphicsDevice)
@@ -481,15 +483,34 @@ namespace Barotrauma.Lights
for (int i = 0; i < 4; i++) for (int i = 0; i < 4; i++)
{ {
vertices[i].WorldPos = vertices[i].Pos; vertices[i].WorldPos = vertices[i].Pos;
ValidateVertex(vertices[i].WorldPos, "vertices[i].Pos");
segments[i].Start.WorldPos = segments[i].Start.Pos; segments[i].Start.WorldPos = segments[i].Start.Pos;
ValidateVertex(segments[i].Start.WorldPos, "segments[i].Start.Pos");
segments[i].End.WorldPos = segments[i].End.Pos; segments[i].End.WorldPos = segments[i].End.Pos;
ValidateVertex(segments[i].End.WorldPos, "segments[i].End.Pos");
} }
if (ParentEntity == null || ParentEntity.Submarine == null) { return; } if (ParentEntity == null || ParentEntity.Submarine == null) { return; }
for (int i = 0; i < 4; i++) for (int i = 0; i < 4; i++)
{ {
vertices[i].WorldPos += ParentEntity.Submarine.DrawPosition; vertices[i].WorldPos += ParentEntity.Submarine.DrawPosition;
ValidateVertex(vertices[i].WorldPos, "vertices[i].WorldPos");
segments[i].Start.WorldPos += ParentEntity.Submarine.DrawPosition; segments[i].Start.WorldPos += ParentEntity.Submarine.DrawPosition;
ValidateVertex(segments[i].Start.WorldPos, "segments[i].Start.WorldPos");
segments[i].End.WorldPos += ParentEntity.Submarine.DrawPosition; segments[i].End.WorldPos += ParentEntity.Submarine.DrawPosition;
ValidateVertex(segments[i].End.WorldPos, "segments[i].End.WorldPos");
}
void ValidateVertex(Vector2 vertex, string debugName)
{
if (!MathUtils.IsValid(vertex))
{
IsInvalid = true;
string errorMsg = $"Invalid vertex on convex hull ({debugName}: {vertex}, parent entity: {ParentEntity?.ToString() ?? "null"}).";
#if DEBUG
DebugConsole.ThrowError(errorMsg);
#endif
GameAnalyticsManager.AddErrorEventOnce("ConvexHull.RefreshWorldPositions:InvalidVertex", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
}
} }
} }
@@ -234,6 +234,15 @@ namespace Barotrauma.Lights
} }
} }
public void DebugDrawVertices(SpriteBatch spriteBatch)
{
foreach (LightSource light in lights)
{
if (!light.Enabled) { continue; }
light.DebugDrawVertices(spriteBatch);
}
}
public void RenderLightMap(GraphicsDevice graphics, SpriteBatch spriteBatch, Camera cam, RenderTarget2D backgroundObstructor = null) public void RenderLightMap(GraphicsDevice graphics, SpriteBatch spriteBatch, Camera cam, RenderTarget2D backgroundObstructor = null)
{ {
if (!LightingEnabled) { return; } if (!LightingEnabled) { return; }
@@ -269,6 +278,9 @@ namespace Barotrauma.Lights
light.Position = pos; light.Position = pos;
} }
//above the top boundary of the level (in an inactive respawn shuttle?)
if (Level.Loaded != null && light.WorldPosition.Y > Level.Loaded.Size.Y) { continue; }
float range = light.LightSourceParams.TextureRange; float range = light.LightSourceParams.TextureRange;
if (light.LightSprite != null) if (light.LightSprite != null)
{ {
@@ -801,6 +813,8 @@ namespace Barotrauma.Lights
public void ClearLights() public void ClearLights()
{ {
activeLights.Clear();
activeLightsWithLightVolume.Clear();
lights.Clear(); lights.Clear();
} }
} }
@@ -37,9 +37,9 @@ namespace Barotrauma.Lights
TextureRange = range; TextureRange = range;
if (OverrideLightTexture != null) if (OverrideLightTexture != null)
{ {
TextureRange += Math.Max( TextureRange *= 1.0f + Math.Max(
Math.Abs(OverrideLightTexture.RelativeOrigin.X - 0.5f) * OverrideLightTexture.size.X, Math.Abs(OverrideLightTexture.RelativeOrigin.X - 0.5f),
Math.Abs(OverrideLightTexture.RelativeOrigin.Y - 0.5f) * OverrideLightTexture.size.Y); Math.Abs(OverrideLightTexture.RelativeOrigin.Y - 0.5f));
} }
} }
} }
@@ -238,7 +238,11 @@ namespace Barotrauma.Lights
private bool needsRecalculationWhenUpToDate; private bool needsRecalculationWhenUpToDate;
public bool NeedsRecalculation public bool NeedsRecalculation
{ {
get { return needsRecalculation; } get
{
if (ParentBody?.UserData is Item it && it.Prefab.Identifier == "flashlight") { return true; }
return needsRecalculation;
}
set set
{ {
if (!needsRecalculation && value) if (!needsRecalculation && value)
@@ -708,6 +712,7 @@ namespace Barotrauma.Lights
{ {
foreach (ConvexHull hull in chList.List) foreach (ConvexHull hull in chList.List)
{ {
if (hull.IsInvalid) { continue; }
if (!chList.IsHidden.Contains(hull)) if (!chList.IsHidden.Contains(hull))
{ {
//find convexhull segments that are close enough and facing towards the light source //find convexhull segments that are close enough and facing towards the light source
@@ -735,6 +740,7 @@ namespace Barotrauma.Lights
GameMain.LightManager.AddRayCastTask(this, drawPos, rotation); GameMain.LightManager.AddRayCastTask(this, drawPos, rotation);
} }
const float MinPointDistance = 6;
public void RayCastTask(Vector2 drawPos, float rotation) public void RayCastTask(Vector2 drawPos, float rotation)
{ {
@@ -877,12 +883,11 @@ namespace Barotrauma.Lights
} }
} }
const float MinPointDistance = 6;
//remove points that are very close to each other //remove points that are very close to each other
for (int i = 0; i < points.Count; i++) //+= 2 because the points are added in pairs above, i.e. 0 and 1 belong to the same segment
for (int i = 0; i < points.Count; i += 2)
{ {
for (int j = Math.Min(i + 4, points.Count - 1); j > i; j--) for (int j = Math.Min(i + 2, points.Count - 1); j > i; j--)
{ {
if (Math.Abs(points[i].WorldPos.X - points[j].WorldPos.X) < MinPointDistance && if (Math.Abs(points[i].WorldPos.X - points[j].WorldPos.X) < MinPointDistance &&
Math.Abs(points[i].WorldPos.Y - points[j].WorldPos.Y) < MinPointDistance) Math.Abs(points[i].WorldPos.Y - points[j].WorldPos.Y) < MinPointDistance)
@@ -892,14 +897,14 @@ namespace Barotrauma.Lights
} }
} }
var compareCCW = new CompareSegmentPointCW(drawPos);
try try
{ {
points.Sort(compareCCW); var compareCW = new CompareSegmentPointCW(drawPos);
points.Sort(compareCW);
} }
catch (Exception e) catch (Exception e)
{ {
StringBuilder sb = new StringBuilder("Constructing light volumes failed! Light pos: " + drawPos + ", Hull verts:\n"); StringBuilder sb = new StringBuilder($"Constructing light volumes failed ({nameof(CompareSegmentPointCW)})! Light pos: {drawPos}, Hull verts:\n");
foreach (SegmentPoint sp in points) foreach (SegmentPoint sp in points)
{ {
sb.AppendLine(sp.Pos.ToString()); sb.AppendLine(sp.Pos.ToString());
@@ -914,7 +919,11 @@ namespace Barotrauma.Lights
verts.Clear(); verts.Clear();
foreach (SegmentPoint p in points) foreach (SegmentPoint p in points)
{ {
Vector2 dir = Vector2.Normalize(p.WorldPos - drawPos); Vector2 diff = p.WorldPos - drawPos;
float dist = diff.Length();
//light source exactly at the segment point, don't cast a shadow (normalizing the vector would lead to NaN)
if (dist <= 0.0001f) { continue; }
Vector2 dir = diff / dist;
Vector2 dirNormal = new Vector2(-dir.Y, dir.X) * MinPointDistance; Vector2 dirNormal = new Vector2(-dir.Y, dir.X) * MinPointDistance;
//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
@@ -940,9 +949,36 @@ namespace Barotrauma.Lights
{ {
//the raycasts landed on different segments //the raycasts landed on different segments
//we definitely want to generate new geometry here //we definitely want to generate new geometry here
verts.Add(isPoint1 ? p.WorldPos : intersection1.pos); if (isPoint1)
verts.Add(isPoint2 ? p.WorldPos : intersection2.pos); {
markAsVisible = true; TryAddPoints(intersection2.pos, p.WorldPos, drawPos, verts);
markAsVisible = true;
}
else if (isPoint2)
{
TryAddPoints(intersection1.pos, p.WorldPos, drawPos, verts);
markAsVisible = true;
}
else
{
//didn't hit either point, completely obstructed
verts.Add(intersection1.pos);
verts.Add(intersection2.pos);
}
static void TryAddPoints(Vector2 intersection, Vector2 point, Vector2 refPos, List<Vector2> verts)
{
//* 0.8f because we don't care about obstacles that are very close (intersecting walls),
//only about obstacles that are clearly between the point and the refPos
bool intersectionCloserThanPoint = Vector2.DistanceSquared(intersection, refPos) < Vector2.DistanceSquared(point, refPos) * 0.8f;
//if the raycast hit a segment that's closer than the point we're aiming towards,
//it means we didn't hit a segment behind the point, but something that's obstructing it
//= we don't want to add vertex at that obstructed point, it could make the light go through obstacles
if (!intersectionCloserThanPoint)
{
verts.Add(point);
}
verts.Add(intersection);
}
} }
if (markAsVisible) if (markAsVisible)
{ {
@@ -959,15 +995,32 @@ namespace Barotrauma.Lights
//remove points that are very close to each other //remove points that are very close to each other
for (int i = 0; i < verts.Count - 1; i++) for (int i = 0; i < verts.Count - 1; i++)
{ {
for (int j = Math.Min(i + 4, verts.Count - 1); j > i; j--) for (int j = verts.Count - 1; j > i; j--)
{ {
if (Math.Abs(verts[i].X - verts[j].X) < 6 && if (Math.Abs(verts[i].X - verts[j].X) < MinPointDistance &&
Math.Abs(verts[i].Y - verts[j].Y) < 6) Math.Abs(verts[i].Y - verts[j].Y) < MinPointDistance)
{ {
verts.RemoveAt(j); verts.RemoveAt(j);
} }
} }
} }
try
{
var compareCW = new CompareCW(drawPos);
verts.Sort(compareCW);
}
catch (Exception e)
{
StringBuilder sb = new StringBuilder($"Constructing light volumes failed ({nameof(CompareSegmentPointCW)})! Light pos: {drawPos}, verts:\n");
foreach (Vector2 v in verts)
{
sb.AppendLine(v.ToString());
}
DebugConsole.ThrowError(sb.ToString(), e);
}
calculatedDrawPos = drawPos; calculatedDrawPos = drawPos;
state = LightVertexState.PendingVertexRecalculation; state = LightVertexState.PendingVertexRecalculation;
} }
@@ -1114,7 +1167,7 @@ namespace Barotrauma.Lights
//add the normals together and use some magic numbers to create //add the normals together and use some magic numbers to create
//a somewhat useful/good-looking blur //a somewhat useful/good-looking blur
float blurDistance = 40.0f; float blurDistance = 25.0f;
Vector2 nDiff = nDiff1 * blurDistance; Vector2 nDiff = nDiff1 * blurDistance;
if (MathUtils.GetLineIntersection(vertex + (nDiff1 * blurDistance), nextVertex + (nDiff1 * blurDistance), vertex + (nDiff2 * blurDistance), prevVertex + (nDiff2 * blurDistance), true, out Vector2 intersection)) if (MathUtils.GetLineIntersection(vertex + (nDiff1 * blurDistance), nextVertex + (nDiff1 * blurDistance), vertex + (nDiff2 * blurDistance), prevVertex + (nDiff2 * blurDistance), true, out Vector2 intersection))
{ {
@@ -1230,7 +1283,8 @@ namespace Barotrauma.Lights
/// <param name="spriteBatch"></param> /// <param name="spriteBatch"></param>
public void DrawSprite(SpriteBatch spriteBatch, Camera cam) public void DrawSprite(SpriteBatch spriteBatch, Camera cam)
{ {
if (GameMain.DebugDraw) //uncomment if you want to visualize the bounds of the light volume
/*if (GameMain.DebugDraw)
{ {
Vector2 drawPos = position; Vector2 drawPos = position;
if (ParentSub != null) if (ParentSub != null)
@@ -1269,7 +1323,7 @@ namespace Barotrauma.Lights
{ {
GUI.DrawLine(spriteBatch, boundaryCorners[i].Pos, boundaryCorners[(i + 1) % 4].Pos, Color.White, 0, 3); GUI.DrawLine(spriteBatch, boundaryCorners[i].Pos, boundaryCorners[(i + 1) % 4].Pos, Color.White, 0, 3);
} }
} }*/
if (DeformableLightSprite != null) if (DeformableLightSprite != null)
{ {
@@ -1318,7 +1372,7 @@ namespace Barotrauma.Lights
if (LightTextureTargetSize != Vector2.Zero) if (LightTextureTargetSize != Vector2.Zero)
{ {
LightSprite.DrawTiled(spriteBatch, drawPos, LightTextureTargetSize, color, startOffset: LightTextureOffset, textureScale: LightTextureScale); LightSprite.DrawTiled(spriteBatch, drawPos, LightTextureTargetSize, color: color, startOffset: LightTextureOffset, textureScale: LightTextureScale);
} }
else else
{ {
@@ -1367,6 +1421,38 @@ namespace Barotrauma.Lights
} }
} }
public void DebugDrawVertices(SpriteBatch spriteBatch)
{
if (Range < 1.0f || Color.A < 1 || CurrentBrightness <= 0.0f) { return; }
//commented out because this is mostly just useful in very specific situations, otherwise it just makes debugdraw very messy
//(you may also need to add a condition here that only draws this for the specific light you're interested in)
if (GameMain.DebugDraw && vertices != null)
{
if (ParentBody?.UserData is Item it && it.Prefab.Identifier == "flashlight")
for (int i = 1; i < vertices.Length - 1; i += 2)
{
Vector2 vert1 = new Vector2(vertices[i].Position.X, vertices[i].Position.Y);
int nextIndex = (i + 2) % vertices.Length;
//the first vertex is the one at the position of the light source, skip that one
//(we just want to draw lines between the vertices at the circumference of the light volume)
if (nextIndex == 0) { nextIndex++; }
Vector2 vert2 = new Vector2(vertices[nextIndex].Position.X, vertices[nextIndex].Position.Y);
if (ParentSub != null)
{
vert1 += ParentSub.DrawPosition;
vert2 += ParentSub.DrawPosition;
}
vert1.Y = -vert1.Y;
vert2.Y = -vert2.Y;
var randomColor = ToolBox.GradientLerp(i / (float)vertices.Length, Color.Magenta, Color.Blue, Color.Yellow, Color.Green, Color.Cyan, Color.Red, Color.Purple, Color.Yellow);
GUI.DrawLine(spriteBatch, vert1, vert2, randomColor * 0.8f, width: 2);
}
}
}
public void DrawLightVolume(SpriteBatch spriteBatch, BasicEffect lightEffect, Matrix transform, bool allowRecalculation, ref int recalculationCount) public void DrawLightVolume(SpriteBatch spriteBatch, BasicEffect lightEffect, Matrix transform, bool allowRecalculation, ref int recalculationCount)
{ {
if (Range < 1.0f || Color.A < 1 || CurrentBrightness <= 0.0f) { return; } if (Range < 1.0f || Color.A < 1 || CurrentBrightness <= 0.0f) { return; }
@@ -291,14 +291,14 @@ namespace Barotrauma
private readonly List<MapNotification> mapNotifications = new List<MapNotification>(); private readonly List<MapNotification> mapNotifications = new List<MapNotification>();
partial void ChangeLocationTypeProjSpecific(Location location, string prevName, LocationTypeChange change) partial void ChangeLocationTypeProjSpecific(Location location, LocalizedString prevName, LocationTypeChange change)
{ {
var messages = change.GetMessages(location.Faction); var messages = change.GetMessages(location.Faction);
if (!messages.Any()) { return; } if (!messages.Any()) { return; }
string msg = messages.GetRandom(Rand.RandSync.Unsynced) string msg = messages.GetRandom(Rand.RandSync.Unsynced)
.Replace("[previousname]", $"‖color:gui.yellow‖{prevName}‖end‖") .Replace("[previousname]", $"‖color:gui.yellow‖{prevName}‖end‖")
.Replace("[name]", $"‖color:gui.yellow‖{location.Name}‖end‖"); .Replace("[name]", $"‖color:gui.yellow‖{location.DisplayName}‖end‖");
location.LastTypeChangeMessage = msg; location.LastTypeChangeMessage = msg;
mapNotifications.Add(new MapNotification(msg, GUIStyle.SubHeadingFont, mapNotifications, location)); mapNotifications.Add(new MapNotification(msg, GUIStyle.SubHeadingFont, mapNotifications, location));
@@ -377,7 +377,7 @@ namespace Barotrauma
bool showReputation = hudVisibility > 0.0f && location.Type.HasOutpost && location.Reputation != null; bool showReputation = hudVisibility > 0.0f && location.Type.HasOutpost && location.Reputation != null;
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform), location.Name, font: GUIStyle.LargeFont) { Padding = Vector4.Zero }; new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform), location.DisplayName, font: GUIStyle.LargeFont) { Padding = Vector4.Zero };
if (!location.Type.Name.IsNullOrEmpty()) if (!location.Type.Name.IsNullOrEmpty())
{ {
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform), location.Type.Name, font: GUIStyle.SubHeadingFont) { Padding = Vector4.Zero }; new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform), location.Type.Name, font: GUIStyle.SubHeadingFont) { Padding = Vector4.Zero };
@@ -1080,6 +1080,7 @@ namespace Barotrauma
} }
float dist = Vector2.Distance(start, end); float dist = Vector2.Distance(start, end);
var connectionSprite = connection.Passed ? generationParams.PassedConnectionSprite : generationParams.ConnectionSprite; var connectionSprite = connection.Passed ? generationParams.PassedConnectionSprite : generationParams.ConnectionSprite;
if (connectionSprite?.Texture == null) { continue; }
Color segmentColor = connectionColor; Color segmentColor = connectionColor;
int segmentWidth = width; int segmentWidth = width;
@@ -1092,9 +1093,6 @@ namespace Barotrauma
segmentWidth /= 2; segmentWidth /= 2;
segmentColor = connection.Passed ? generationParams.ConnectionColor : generationParams.UnvisitedConnectionColor; segmentColor = connection.Passed ? generationParams.ConnectionColor : generationParams.UnvisitedConnectionColor;
} }
else
{
}
} }
spriteBatch.Draw(connectionSprite.Texture, spriteBatch.Draw(connectionSprite.Texture,
@@ -29,7 +29,7 @@ namespace Barotrauma
Vector2 spriteScale = new Vector2(zoom); Vector2 spriteScale = new Vector2(zoom);
uiSprite.Sprite.DrawTiled(spriteBatch, topLeft, size, Params.RadiationAreaColor, Vector2.Zero, textureScale: spriteScale); uiSprite.Sprite.DrawTiled(spriteBatch, topLeft, size, color: Params.RadiationAreaColor, startOffset: Vector2.Zero, textureScale: spriteScale);
Vector2 topRight = topLeft + Vector2.UnitX * size.X; Vector2 topRight = topLeft + Vector2.UnitX * size.X;
@@ -8,6 +8,7 @@ using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Linq; using System.Linq;
using Barotrauma.Lights; using Barotrauma.Lights;
using Microsoft.Xna.Framework;
namespace Barotrauma namespace Barotrauma
{ {
@@ -65,9 +66,7 @@ namespace Barotrauma
disableSelect = value; disableSelect = value;
if (disableSelect) if (disableSelect)
{ {
startMovingPos = Vector2.Zero; StopSelection();
selectionSize = Vector2.Zero;
selectionPos = Vector2.Zero;
} }
} }
} }
@@ -163,21 +162,9 @@ namespace Barotrauma
{ {
if (SelectedAny) if (SelectedAny)
{ {
if (SelectedList.Any(static t => t is Item it && it.GetComponent<CircuitBox>() is not null)) SubEditorScreen.StoreCommand(new AddOrDeleteCommand(new List<MapEntity>(SelectedList), true));
{ SelectedList.ForEachMod(static e => { if (!e.Removed) { e.Remove(); } });
GUI.AskForConfirmation(SubEditorScreen.CircuitBoxDeletionWarningHeader, SubEditorScreen.CircuitBoxDeletionWarningBody, onConfirm: Delete); SelectedList.Clear();
}
else
{
Delete();
}
void Delete()
{
SubEditorScreen.StoreCommand(new AddOrDeleteCommand(new List<MapEntity>(SelectedList), true));
SelectedList.ForEach(static e => { if (!e.Removed) { e.Remove(); } });
SelectedList.Clear();
}
} }
} }
@@ -494,6 +481,13 @@ namespace Barotrauma
} }
} }
public static void StopSelection()
{
startMovingPos = Vector2.Zero;
selectionSize = Vector2.Zero;
selectionPos = Vector2.Zero;
}
public static Vector2 GetNudgeAmount(bool doHold = true) public static Vector2 GetNudgeAmount(bool doHold = true)
{ {
Vector2 nudgeAmount = Vector2.Zero; Vector2 nudgeAmount = Vector2.Zero;
@@ -792,12 +786,16 @@ namespace Barotrauma
foreach (MapEntity e in SelectedList) foreach (MapEntity e in SelectedList)
{ {
SpriteEffects spriteEffects = SpriteEffects.None; SpriteEffects spriteEffects = SpriteEffects.None;
float spriteRotation = 0.0f;
float rectangleRotation = 0.0f;
switch (e) switch (e)
{ {
case Item item: case Item item:
{ {
if (item.FlippedX && item.Prefab.CanSpriteFlipX) { spriteEffects ^= SpriteEffects.FlipHorizontally; } if (item.FlippedX && item.Prefab.CanSpriteFlipX) { spriteEffects ^= SpriteEffects.FlipHorizontally; }
if (item.flippedY && item.Prefab.CanSpriteFlipY) { spriteEffects ^= SpriteEffects.FlipVertically; } if (item.FlippedY && item.Prefab.CanSpriteFlipY) { spriteEffects ^= SpriteEffects.FlipVertically; }
spriteRotation = MathHelper.ToRadians(item.Rotation);
rectangleRotation = spriteRotation;
var wire = item.GetComponent<Wire>(); var wire = item.GetComponent<Wire>();
if (wire != null && wire.Item.body != null && !wire.Item.body.Enabled) if (wire != null && wire.Item.body != null && !wire.Item.body.Enabled)
{ {
@@ -809,7 +807,10 @@ namespace Barotrauma
case Structure structure: case Structure structure:
{ {
if (structure.FlippedX && structure.Prefab.CanSpriteFlipX) { spriteEffects ^= SpriteEffects.FlipHorizontally; } if (structure.FlippedX && structure.Prefab.CanSpriteFlipX) { spriteEffects ^= SpriteEffects.FlipHorizontally; }
if (structure.flippedY && structure.Prefab.CanSpriteFlipY) { spriteEffects ^= SpriteEffects.FlipVertically; } if (structure.FlippedY && structure.Prefab.CanSpriteFlipY) { spriteEffects ^= SpriteEffects.FlipVertically; }
spriteRotation = MathHelper.ToRadians(structure.Rotation);
rectangleRotation = spriteRotation;
if (structure.FlippedX != structure.FlippedY) { rectangleRotation = -rectangleRotation; }
break; break;
} }
case WayPoint wayPoint: case WayPoint wayPoint:
@@ -831,11 +832,12 @@ namespace Barotrauma
} }
} }
e.Prefab?.DrawPlacing(spriteBatch, e.Prefab?.DrawPlacing(spriteBatch,
new Rectangle(e.WorldRect.Location + new Point((int)moveAmount.X, (int)-moveAmount.Y), e.WorldRect.Size), e.Scale, spriteEffects); new Rectangle(e.WorldRect.Location + new Point((int)moveAmount.X, (int)-moveAmount.Y), e.WorldRect.Size), e.Scale, spriteRotation, spriteEffects: spriteEffects);
GUI.DrawRectangle(spriteBatch, GUI.DrawRectangle(spriteBatch,
new Vector2(e.WorldRect.X, -e.WorldRect.Y) + moveAmount, center: e.WorldRect.Center.ToVector2().FlipY() + moveAmount + new Vector2(0f, e.WorldRect.Height),
new Vector2(e.rect.Width, e.rect.Height), width: e.WorldRect.Width, height: e.WorldRect.Height,
Color.White, false, 0, (int)Math.Max(3.0f / GameScreen.Selected.Cam.Zoom, 2.0f)); rotation: rectangleRotation, clr: Color.White,
depth: 0f, thickness: Math.Max(3.0f / GameScreen.Selected.Cam.Zoom, 2.0f));
} }
//stop dragging the "selection rectangle" //stop dragging the "selection rectangle"
@@ -877,6 +879,23 @@ namespace Barotrauma
spriteBatch.DrawLine(corners[3] + offset, corners[0] - offset, color, thickness); spriteBatch.DrawLine(corners[3] + offset, corners[0] - offset, color, thickness);
} }
protected static void ColorFlipButton(GUIButton btn, bool flip)
{
var color = flip ? GUIStyle.Green : Color.White;
var hsv = ToolBox.RGBToHSV(color);
// Boost saturation and reduce value a bit because our default colors are too muted for this button's style
var hsvBase = hsv;
hsvBase.Y *= 4f;
hsvBase.Z *= 0.8f;
btn.Color = ToolBox.HSVToRGB(hsvBase.X, hsvBase.Y, hsvBase.Z);
btn.SelectedColor = ToolBox.HSVToRGB(hsvBase.X, hsvBase.Y, hsvBase.Z);
var hsvHover = hsv;
hsvHover.Z *= 1.2f;
btn.HoverColor = ToolBox.HSVToRGB(hsvHover.X, hsvHover.Y, hsvHover.Z);
}
public static List<MapEntity> FilteredSelectedList { get; private set; } = new List<MapEntity>(); public static List<MapEntity> FilteredSelectedList { get; private set; } = new List<MapEntity>();
public static void UpdateEditor(Camera cam, float deltaTime) public static void UpdateEditor(Camera cam, float deltaTime)
@@ -1105,6 +1124,25 @@ namespace Barotrauma
public virtual void DrawEditing(SpriteBatch spriteBatch, Camera cam) { } public virtual void DrawEditing(SpriteBatch spriteBatch, Camera cam) { }
private float RotationRad
=> MathHelper.ToRadians(
this switch
{
Structure s => s.Rotation,
Item it => it.Rotation,
_ => 0.0f
});
private Vector2 GetEditingHandlePos(int x, int y, Camera cam)
{
Vector2 handleDiff = new Vector2(x * (rect.Width * 0.5f), y * (rect.Height * 0.5f));
var rotation = -RotationRad;
handleDiff = MathUtils.RotatePoint(handleDiff, rotation);
if (FlippedX) { handleDiff = handleDiff.FlipX(); }
if (FlippedY) { handleDiff = handleDiff.FlipY(); }
return cam.WorldToScreen(Position + handleDiff);
}
float ResizeHandleSize => 10 * GUI.Scale; float ResizeHandleSize => 10 * GUI.Scale;
float ResizeHandleHighlightDistance => 8 * GUI.Scale; float ResizeHandleHighlightDistance => 8 * GUI.Scale;
@@ -1119,9 +1157,10 @@ namespace Barotrauma
{ {
for (int y = startY; y < 2; y += 2) for (int y = startY; y < 2; y += 2)
{ {
Vector2 handlePos = cam.WorldToScreen(Position + new Vector2(x * (rect.Width * 0.5f), y * (rect.Height * 0.5f))); Vector2 handlePos = GetEditingHandlePos(x, y, cam);
bool highlighted = Vector2.DistanceSquared(PlayerInput.MousePosition, handlePos) < ResizeHandleHighlightDistance * ResizeHandleHighlightDistance; bool highlighted = Vector2.DistanceSquared(PlayerInput.MousePosition, handlePos) < ResizeHandleHighlightDistance * ResizeHandleHighlightDistance;
if (highlighted && PlayerInput.PrimaryMouseButtonDown()) if (highlighted && PlayerInput.PrimaryMouseButtonDown())
{ {
selectionPos = Vector2.Zero; selectionPos = Vector2.Zero;
@@ -1138,44 +1177,83 @@ namespace Barotrauma
{ {
if (prevRect == null) if (prevRect == null)
{ {
prevRect = new Rectangle(Rect.Location, Rect.Size); prevRect = Rect;
} }
Vector2 placePosition = new Vector2(rect.X, rect.Y); Vector2 placePosition = prevRect.Value.Location.ToVector2();
Vector2 placeSize = new Vector2(rect.Width, rect.Height); Vector2 placeSize = prevRect.Value.Size.ToVector2();
Vector2 mousePos = Submarine.MouseToWorldGrid(cam, Submarine.MainSub, round: true); static Vector2 flipThenRotate(Vector2 point, Vector2 center, float angle, bool flipX, bool flipY)
if (PlayerInput.IsShiftDown())
{ {
mousePos = cam.ScreenToWorld(PlayerInput.MousePosition); if (flipX) { point = (point - center).FlipX() + center; }
if (flipY) { point = (point - center).FlipY() + center; }
point = MathUtils.RotatePointAroundTarget(point, center, angle);
return point;
}
static Vector2 rotateThenFlip(Vector2 point, Vector2 center, float angle, bool flipX, bool flipY)
{
point = MathUtils.RotatePointAroundTarget(point, center, angle);
if (flipX) { point = (point - center).FlipX() + center; }
if (flipY) { point = (point - center).FlipY() + center; }
return point;
}
Vector2 mousePos = cam.ScreenToWorld(PlayerInput.MousePosition);
Vector2 prevPos = placePosition;
Vector2 prevOppositeCorner = prevPos + placeSize.FlipY();
Vector2 prevCenter = placePosition + placeSize.FlipY() * 0.5f;
mousePos = flipThenRotate(mousePos, prevCenter, RotationRad, FlippedX, FlippedY);
if (!PlayerInput.IsShiftDown())
{
mousePos = Submarine.VectorToWorldGrid(mousePos, Submarine.MainSub, round: true);
} }
if (resizeDirX > 0) if (resizeDirX > 0)
{ {
mousePos.X = Math.Max(mousePos.X, rect.X + Submarine.GridSize.X); mousePos.X = Math.Max(mousePos.X, prevRect.Value.X + Submarine.GridSize.X);
placeSize.X = mousePos.X - placePosition.X; placeSize.X = mousePos.X - placePosition.X;
} }
else if (resizeDirX < 0) else if (resizeDirX < 0)
{ {
mousePos.X = Math.Min(mousePos.X, rect.Right - Submarine.GridSize.X); mousePos.X = Math.Min(mousePos.X, prevRect.Value.Right - Submarine.GridSize.X);
placeSize.X = MathF.Round((placePosition.X + placeSize.X) - mousePos.X); placeSize.X = MathF.Round((placePosition.X + placeSize.X) - mousePos.X);
placePosition.X = MathF.Round(mousePos.X); placePosition.X = MathF.Round(mousePos.X);
} }
if (resizeDirY < 0) if (resizeDirY < 0)
{ {
mousePos.Y = Math.Min(mousePos.Y, rect.Y - Submarine.GridSize.Y); mousePos.Y = Math.Min(mousePos.Y, prevRect.Value.Y - Submarine.GridSize.Y);
placeSize.Y = placePosition.Y - mousePos.Y; placeSize.Y = placePosition.Y - mousePos.Y;
} }
else if (resizeDirY > 0) else if (resizeDirY > 0)
{ {
mousePos.Y = Math.Max(mousePos.Y, rect.Y - rect.Height + Submarine.GridSize.X); mousePos.Y = Math.Max(mousePos.Y, prevRect.Value.Y - prevRect.Value.Height + Submarine.GridSize.Y);
placeSize.Y = mousePos.Y - (rect.Y - rect.Height); placeSize.Y = mousePos.Y - (prevRect.Value.Y - prevRect.Value.Height);
placePosition.Y = mousePos.Y; placePosition.Y = mousePos.Y;
} }
Vector2 newPos = placePosition;
Vector2 newOppositeCorner = placePosition + placeSize.FlipY();
Vector2 transformedCornerDiff = rotateThenFlip(newPos-prevPos, Vector2.Zero, -RotationRad, FlippedX, FlippedY);
Vector2 transformedOppositeCornerDiff = rotateThenFlip(newOppositeCorner-prevOppositeCorner, Vector2.Zero, -RotationRad, FlippedX, FlippedY);
Vector2 newPosTransformed = rotateThenFlip(prevPos, prevCenter, -RotationRad, FlippedX, FlippedY)
+ transformedCornerDiff;
Vector2 newOppositeTransformed = rotateThenFlip(prevOppositeCorner, prevCenter, -RotationRad, FlippedX, FlippedY)
+ transformedOppositeCornerDiff;
Vector2 newTransformedCenter = (newPosTransformed + newOppositeTransformed) * 0.5f;
var newDiff = (newOppositeCorner - newPos) * 0.5f;
placePosition = newTransformedCenter - newDiff;
if ((int)placePosition.X != rect.X || (int)placePosition.Y != rect.Y || (int)placeSize.X != rect.Width || (int)placeSize.Y != rect.Height) if ((int)placePosition.X != rect.X || (int)placePosition.Y != rect.Y || (int)placeSize.X != rect.Width || (int)placeSize.Y != rect.Height)
{ {
Rect = new Rectangle((int)placePosition.X, (int)placePosition.Y, (int)placeSize.X, (int)placeSize.Y); Rect = new Rectangle((int)placePosition.X, (int)placePosition.Y, (int)placeSize.X, (int)placeSize.Y);
@@ -1210,15 +1288,16 @@ namespace Barotrauma
IsHighlighted = true; IsHighlighted = true;
int startX = ResizeHorizontal ? -1 : 0; int startX = ResizeHorizontal ? -1 : 0;
int StartY = ResizeVertical ? -1 : 0; int startY = ResizeVertical ? -1 : 0;
for (int x = startX; x < 2; x += 2) for (int x = startX; x < 2; x += 2)
{ {
for (int y = StartY; y < 2; y += 2) for (int y = startY; y < 2; y += 2)
{ {
Vector2 handlePos = cam.WorldToScreen(Position + new Vector2(x * (rect.Width * 0.5f), y * (rect.Height * 0.5f))); Vector2 handlePos = GetEditingHandlePos(x, y, cam);
bool highlighted = Vector2.DistanceSquared(PlayerInput.MousePosition, handlePos) < ResizeHandleHighlightDistance * ResizeHandleHighlightDistance; bool highlighted = Vector2.DistanceSquared(PlayerInput.MousePosition, handlePos) < ResizeHandleHighlightDistance * ResizeHandleHighlightDistance;
var color = Color.White * (highlighted ? 1.0f : 0.6f);
if (highlighted && !PlayerInput.PrimaryMouseButtonHeld()) if (highlighted && !PlayerInput.PrimaryMouseButtonHeld())
{ {
GUI.MouseCursor = CursorState.Hand; GUI.MouseCursor = CursorState.Hand;
@@ -1226,7 +1305,7 @@ namespace Barotrauma
GUI.DrawRectangle(spriteBatch, GUI.DrawRectangle(spriteBatch,
handlePos - new Vector2(ResizeHandleSize / 2), handlePos - new Vector2(ResizeHandleSize / 2),
new Vector2(ResizeHandleSize), new Vector2(ResizeHandleSize),
Color.White * (highlighted ? 1.0f : 0.6f), color,
true, 0, true, 0,
(int)Math.Max(1.5f / GameScreen.Selected.Cam.Zoom, 1.0f)); (int)Math.Max(1.5f / GameScreen.Selected.Cam.Zoom, 1.0f));
} }
@@ -1241,12 +1320,15 @@ namespace Barotrauma
HashSet<MapEntity> foundEntities = new HashSet<MapEntity>(); HashSet<MapEntity> foundEntities = new HashSet<MapEntity>();
Rectangle selectionRect = Submarine.AbsRect(pos, size); Rectangle selectionRect = Submarine.AbsRect(pos, size);
Quad2D selectionQuad = Quad2D.FromSubmarineRectangle(selectionRect);
foreach (MapEntity entity in MapEntityList) foreach (MapEntity entity in MapEntityList)
{ {
if (!entity.SelectableInEditor) { continue; } if (!entity.SelectableInEditor) { continue; }
if (Submarine.RectsOverlap(selectionRect, entity.rect)) Quad2D entityQuad = entity.GetTransformedQuad();
if (selectionQuad.Intersects(entityQuad))
{ {
foundEntities.Add(entity); foundEntities.Add(entity);
entity.IsIncludedInSelection = true; entity.IsIncludedInSelection = true;
@@ -84,7 +84,7 @@ namespace Barotrauma
} }
} }
public virtual void DrawPlacing(SpriteBatch spriteBatch, Rectangle drawRect, float scale = 1.0f, SpriteEffects spriteEffects = SpriteEffects.None) public virtual void DrawPlacing(SpriteBatch spriteBatch, Rectangle drawRect, float scale = 1.0f, float rotation = 0.0f, SpriteEffects spriteEffects = SpriteEffects.None)
{ {
if (Submarine.MainSub != null) if (Submarine.MainSub != null)
{ {
@@ -1,10 +1,9 @@
#nullable enable #nullable enable
using Barotrauma.Sounds;
using Microsoft.Xna.Framework;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Xml.Linq;
using Barotrauma.Sounds;
using Microsoft.Xna.Framework;
namespace Barotrauma namespace Barotrauma
{ {
@@ -45,7 +44,8 @@ namespace Barotrauma
} }
if (FrequencyMultiplierRange.Y > 4.0f) if (FrequencyMultiplierRange.Y > 4.0f)
{ {
DebugConsole.ThrowError($"Loaded frequency range exceeds max value: {FrequencyMultiplierRange} (original string was \"{freqMultAttr}\")"); DebugConsole.ThrowError($"Loaded frequency range exceeds max value: {FrequencyMultiplierRange} (original string was \"{freqMultAttr}\")",
contentPackage: element.ContentPackage);
} }
IgnoreMuffling = element.GetAttributeBool("dontmuffle", false); IgnoreMuffling = element.GetAttributeBool("dontmuffle", false);
} }
@@ -65,7 +65,8 @@ namespace Barotrauma
if (filename is null) if (filename is null)
{ {
string errorMsg = "Error when loading round sound (" + element + ") - file path not set"; string errorMsg = "Error when loading round sound (" + element + ") - file path not set";
DebugConsole.ThrowError(errorMsg); DebugConsole.ThrowError(errorMsg,
contentPackage: element.ContentPackage);
GameAnalyticsManager.AddErrorEventOnce("RoundSound.LoadRoundSound:FilePathEmpty" + element.ToString(), GameAnalyticsManager.ErrorSeverity.Error, errorMsg + "\n" + Environment.StackTrace.CleanupStackTrace()); GameAnalyticsManager.AddErrorEventOnce("RoundSound.LoadRoundSound:FilePathEmpty" + element.ToString(), GameAnalyticsManager.ErrorSeverity.Error, errorMsg + "\n" + Environment.StackTrace.CleanupStackTrace());
return null; return null;
} }
@@ -86,7 +87,8 @@ namespace Barotrauma
catch (System.IO.FileNotFoundException e) catch (System.IO.FileNotFoundException e)
{ {
string errorMsg = "Failed to load sound file \"" + filename + "\" (file not found)."; string errorMsg = "Failed to load sound file \"" + filename + "\" (file not found).";
DebugConsole.ThrowError(errorMsg, e); DebugConsole.ThrowError(errorMsg, e,
contentPackage: element.ContentPackage);
if (!ContentPackageManager.ModsEnabled) if (!ContentPackageManager.ModsEnabled)
{ {
GameAnalyticsManager.AddErrorEventOnce("RoundSound.LoadRoundSound:FileNotFound" + filename, GameAnalyticsManager.ErrorSeverity.Error, errorMsg + "\n" + Environment.StackTrace.CleanupStackTrace()); GameAnalyticsManager.AddErrorEventOnce("RoundSound.LoadRoundSound:FileNotFound" + filename, GameAnalyticsManager.ErrorSeverity.Error, errorMsg + "\n" + Environment.StackTrace.CleanupStackTrace());
@@ -96,7 +98,8 @@ namespace Barotrauma
catch (System.IO.InvalidDataException e) catch (System.IO.InvalidDataException e)
{ {
string errorMsg = "Failed to load sound file \"" + filename + "\" (invalid data)."; string errorMsg = "Failed to load sound file \"" + filename + "\" (invalid data).";
DebugConsole.ThrowError(errorMsg, e); DebugConsole.ThrowError(errorMsg, e,
contentPackage: element.ContentPackage);
GameAnalyticsManager.AddErrorEventOnce("RoundSound.LoadRoundSound:InvalidData" + filename, GameAnalyticsManager.ErrorSeverity.Error, errorMsg + "\n" + Environment.StackTrace.CleanupStackTrace()); GameAnalyticsManager.AddErrorEventOnce("RoundSound.LoadRoundSound:InvalidData" + filename, GameAnalyticsManager.ErrorSeverity.Error, errorMsg + "\n" + Environment.StackTrace.CleanupStackTrace());
return null; return null;
} }
@@ -123,7 +126,8 @@ namespace Barotrauma
catch (System.IO.FileNotFoundException e) catch (System.IO.FileNotFoundException e)
{ {
string errorMsg = "Failed to load sound file \"" + roundSound.Filename + "\"."; string errorMsg = "Failed to load sound file \"" + roundSound.Filename + "\".";
DebugConsole.ThrowError(errorMsg, e); DebugConsole.ThrowError(errorMsg, e,
contentPackage: roundSound.Sound?.XElement?.ContentPackage);
GameAnalyticsManager.AddErrorEventOnce("RoundSound.LoadRoundSound:FileNotFound" + roundSound.Filename, GameAnalyticsManager.ErrorSeverity.Error, errorMsg + "\n" + Environment.StackTrace.CleanupStackTrace()); GameAnalyticsManager.AddErrorEventOnce("RoundSound.LoadRoundSound:FileNotFound" + roundSound.Filename, GameAnalyticsManager.ErrorSeverity.Error, errorMsg + "\n" + Environment.StackTrace.CleanupStackTrace());
return; return;
} }
@@ -150,7 +150,8 @@ namespace Barotrauma
Stretch = true, Stretch = true,
RelativeSpacing = 0.01f RelativeSpacing = 0.01f
}; };
new GUIButton(new RectTransform(new Vector2(0.23f, 1.0f), buttonContainer.RectTransform), TextManager.Get("MirrorEntityX"), style: "GUIButtonSmall")
var mirrorX = new GUIButton(new RectTransform(new Vector2(0.23f, 1.0f), buttonContainer.RectTransform), TextManager.Get("MirrorEntityX"), style: "GUIButtonSmall")
{ {
ToolTip = TextManager.Get("MirrorEntityXToolTip"), ToolTip = TextManager.Get("MirrorEntityXToolTip"),
OnClicked = (button, data) => OnClicked = (button, data) =>
@@ -160,10 +161,12 @@ namespace Barotrauma
me.FlipX(relativeToSub: false); me.FlipX(relativeToSub: false);
} }
if (!SelectedList.Contains(this)) { FlipX(relativeToSub: false); } if (!SelectedList.Contains(this)) { FlipX(relativeToSub: false); }
ColorFlipButton(button, FlippedX);
return true; return true;
} }
}; };
new GUIButton(new RectTransform(new Vector2(0.23f, 1.0f), buttonContainer.RectTransform), TextManager.Get("MirrorEntityY"), style: "GUIButtonSmall") ColorFlipButton(mirrorX, FlippedX);
var mirrorY = new GUIButton(new RectTransform(new Vector2(0.23f, 1.0f), buttonContainer.RectTransform), TextManager.Get("MirrorEntityY"), style: "GUIButtonSmall")
{ {
ToolTip = TextManager.Get("MirrorEntityYToolTip"), ToolTip = TextManager.Get("MirrorEntityYToolTip"),
OnClicked = (button, data) => OnClicked = (button, data) =>
@@ -173,9 +176,11 @@ namespace Barotrauma
me.FlipY(relativeToSub: false); me.FlipY(relativeToSub: false);
} }
if (!SelectedList.Contains(this)) { FlipY(relativeToSub: false); } if (!SelectedList.Contains(this)) { FlipY(relativeToSub: false); }
ColorFlipButton(button, FlippedY);
return true; return true;
} }
}; };
ColorFlipButton(mirrorY, FlippedY);
new GUIButton(new RectTransform(new Vector2(0.23f, 1.0f), buttonContainer.RectTransform), TextManager.Get("ReloadSprite"), style: "GUIButtonSmall") new GUIButton(new RectTransform(new Vector2(0.23f, 1.0f), buttonContainer.RectTransform), TextManager.Get("ReloadSprite"), style: "GUIButtonSmall")
{ {
OnClicked = (button, data) => OnClicked = (button, data) =>
@@ -231,11 +236,14 @@ namespace Barotrauma
public override bool IsVisible(Rectangle worldView) public override bool IsVisible(Rectangle worldView)
{ {
Rectangle worldRect = WorldRect; RectangleF worldRect = Quad2D.FromSubmarineRectangle(WorldRect).Rotated(
FlippedX != FlippedY
? rotationRad
: -rotationRad).BoundingAxisAlignedRectangle;
Vector2 worldPos = WorldPosition; Vector2 worldPos = WorldPosition;
Vector2 min = new Vector2(worldRect.X, worldRect.Y - worldRect.Height); Vector2 min = new Vector2(worldRect.X, worldRect.Y);
Vector2 max = new Vector2(worldRect.Right, worldRect.Y); Vector2 max = new Vector2(worldRect.Right, worldRect.Y + worldRect.Height);
foreach (DecorativeSprite decorativeSprite in Prefab.DecorativeSprites) foreach (DecorativeSprite decorativeSprite in Prefab.DecorativeSprites)
{ {
float scale = decorativeSprite.GetScale(spriteAnimState[decorativeSprite].RandomScaleFactor) * Scale; float scale = decorativeSprite.GetScale(spriteAnimState[decorativeSprite].RandomScaleFactor) * Scale;
@@ -307,7 +315,12 @@ namespace Barotrauma
Vector2 bodyPos = WorldPosition + BodyOffset * Scale; Vector2 bodyPos = WorldPosition + BodyOffset * Scale;
GUI.DrawRectangle(spriteBatch, new Vector2(bodyPos.X, -bodyPos.Y), rectSize.X, rectSize.Y, BodyRotation, Color.White, GUI.DrawRectangle(sb: spriteBatch,
center: new Vector2(bodyPos.X, -bodyPos.Y),
width: rectSize.X,
height: rectSize.Y,
rotation: BodyRotation,
clr: Color.White,
thickness: Math.Max(1, (int)(2 / Screen.Selected.Cam.Zoom))); thickness: Math.Max(1, (int)(2 / Screen.Selected.Cam.Zoom)));
} }
@@ -357,8 +370,10 @@ namespace Barotrauma
Prefab.BackgroundSprite.DrawTiled( Prefab.BackgroundSprite.DrawTiled(
spriteBatch, spriteBatch,
new Vector2(rect.X + drawOffset.X, -(rect.Y + drawOffset.Y)), new Vector2(rect.X + rect.Width / 2 + drawOffset.X, -(rect.Y - rect.Height / 2 + drawOffset.Y)),
new Vector2(rect.Width, rect.Height), new Vector2(rect.Width, rect.Height),
rotation: rotationRad,
origin: rect.Size.ToVector2() * new Vector2(0.5f, 0.5f),
color: Prefab.BackgroundSpriteColor, color: Prefab.BackgroundSpriteColor,
textureScale: TextureScale * Scale, textureScale: TextureScale * Scale,
startOffset: backGroundOffset, startOffset: backGroundOffset,
@@ -368,8 +383,10 @@ namespace Barotrauma
{ {
Prefab.BackgroundSprite.DrawTiled( Prefab.BackgroundSprite.DrawTiled(
spriteBatch, spriteBatch,
new Vector2(rect.X + drawOffset.X, -(rect.Y + drawOffset.Y)) + dropShadowOffset, new Vector2(rect.X + rect.Width / 2 + drawOffset.X, -(rect.Y - rect.Height / 2 + drawOffset.Y)) + dropShadowOffset,
new Vector2(rect.Width, rect.Height), new Vector2(rect.Width, rect.Height),
rotation: rotationRad,
origin: rect.Size.ToVector2() * new Vector2(0.5f, 0.5f),
color: Color.Black * 0.5f, color: Color.Black * 0.5f,
textureScale: TextureScale * Scale, textureScale: TextureScale * Scale,
startOffset: backGroundOffset, startOffset: backGroundOffset,
@@ -385,6 +402,13 @@ namespace Barotrauma
SpriteEffects oldEffects = Prefab.Sprite.effects; SpriteEffects oldEffects = Prefab.Sprite.effects;
Prefab.Sprite.effects ^= SpriteEffects; Prefab.Sprite.effects ^= SpriteEffects;
Vector2 advanceX = MathUtils.RotatedUnitXRadians(this.rotationRad).FlipY();
Vector2 advanceY = advanceX.YX().FlipX();
if (FlippedX != FlippedY)
{
advanceX = advanceX.FlipY();
advanceY = advanceY.FlipX();
}
for (int i = 0; i < Sections.Length; i++) for (int i = 0; i < Sections.Length; i++)
{ {
Rectangle drawSection = Sections[i].rect; Rectangle drawSection = Sections[i].rect;
@@ -409,7 +433,7 @@ namespace Barotrauma
drawSection = new Rectangle( drawSection = new Rectangle(
drawSection.X, drawSection.X,
drawSection.Y, drawSection.Y,
Sections[Sections.Length -1 ].rect.Right - drawSection.X, Sections[Sections.Length - 1].rect.Right - drawSection.X,
drawSection.Y - (Sections[Sections.Length - 1].rect.Y - Sections[Sections.Length - 1].rect.Height)); drawSection.Y - (Sections[Sections.Length - 1].rect.Y - Sections[Sections.Length - 1].rect.Height));
i = Sections.Length; i = Sections.Length;
} }
@@ -424,10 +448,18 @@ namespace Barotrauma
sectionOffset.X += MathUtils.PositiveModulo((int)-textureOffset.X, Prefab.Sprite.SourceRect.Width); sectionOffset.X += MathUtils.PositiveModulo((int)-textureOffset.X, Prefab.Sprite.SourceRect.Width);
sectionOffset.Y += MathUtils.PositiveModulo((int)-textureOffset.Y, Prefab.Sprite.SourceRect.Height); sectionOffset.Y += MathUtils.PositiveModulo((int)-textureOffset.Y, Prefab.Sprite.SourceRect.Height);
Vector2 pos = new Vector2(drawSection.X, drawSection.Y);
pos -= rect.Location.ToVector2();
pos = advanceX * pos.X + advanceY * pos.Y;
pos += rect.Location.ToVector2();
pos = new Vector2(pos.X + rect.Width / 2 + drawOffset.X, -(pos.Y - rect.Height / 2 + drawOffset.Y));
Prefab.Sprite.DrawTiled( Prefab.Sprite.DrawTiled(
spriteBatch, spriteBatch,
new Vector2(drawSection.X + drawOffset.X, -(drawSection.Y + drawOffset.Y)), pos,
new Vector2(drawSection.Width, drawSection.Height), new Vector2(drawSection.Width, drawSection.Height),
rotation: rotationRad,
origin: rect.Size.ToVector2() * new Vector2(0.5f, 0.5f),
color: color, color: color,
startOffset: sectionOffset, startOffset: sectionOffset,
depth: depth, depth: depth,
@@ -437,7 +469,7 @@ namespace Barotrauma
foreach (var decorativeSprite in Prefab.DecorativeSprites) foreach (var decorativeSprite in Prefab.DecorativeSprites)
{ {
if (!spriteAnimState[decorativeSprite].IsActive) { continue; } if (!spriteAnimState[decorativeSprite].IsActive) { continue; }
float rotation = decorativeSprite.GetRotation(ref spriteAnimState[decorativeSprite].RotationState, spriteAnimState[decorativeSprite].RandomRotationFactor); float rotation = decorativeSprite.GetRotation(ref spriteAnimState[decorativeSprite].RotationState, spriteAnimState[decorativeSprite].RandomRotationFactor) + this.rotationRad;
Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState, spriteAnimState[decorativeSprite].RandomOffsetMultiplier) * Scale; Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState, spriteAnimState[decorativeSprite].RandomOffsetMultiplier) * Scale;
decorativeSprite.Sprite.Draw(spriteBatch, new Vector2(DrawPosition.X + offset.X, -(DrawPosition.Y + offset.Y)), color, decorativeSprite.Sprite.Draw(spriteBatch, new Vector2(DrawPosition.X + offset.X, -(DrawPosition.Y + offset.Y)), color,
rotation, decorativeSprite.GetScale(spriteAnimState[decorativeSprite].RandomScaleFactor) * Scale, Prefab.Sprite.effects, rotation, decorativeSprite.GetScale(spriteAnimState[decorativeSprite].RandomScaleFactor) * Scale, Prefab.Sprite.effects,
@@ -94,19 +94,20 @@ namespace Barotrauma
GUI.DrawRectangle(spriteBatch, new Rectangle(newRect.X, -newRect.Y - GameMain.GraphicsHeight, newRect.Width, newRect.Height + GameMain.GraphicsHeight * 2), Color.White); GUI.DrawRectangle(spriteBatch, new Rectangle(newRect.X, -newRect.Y - GameMain.GraphicsHeight, newRect.Width, newRect.Height + GameMain.GraphicsHeight * 2), Color.White);
} }
public override void DrawPlacing(SpriteBatch spriteBatch, Rectangle placeRect, float scale = 1.0f, SpriteEffects spriteEffects = SpriteEffects.None) public override void DrawPlacing(SpriteBatch spriteBatch, Rectangle placeRect, float scale = 1.0f, float rotation = 0.0f, SpriteEffects spriteEffects = SpriteEffects.None)
{ {
SpriteEffects oldEffects = Sprite.effects; var position = placeRect.Location.ToVector2().FlipY();
Sprite.effects ^= spriteEffects; position += placeRect.Size.ToVector2() * 0.5f;
Sprite.DrawTiled( Sprite.DrawTiled(
spriteBatch, spriteBatch,
new Vector2(placeRect.X, -placeRect.Y), position,
new Vector2(placeRect.Width, placeRect.Height), placeRect.Size.ToVector2(),
color: Color.White * 0.8f, color: Color.White * 0.8f,
textureScale: TextureScale * scale); origin: placeRect.Size.ToVector2() * 0.5f,
rotation: rotation,
Sprite.effects = oldEffects; textureScale: TextureScale * scale,
spriteEffects: spriteEffects ^ Sprite.effects);
} }
} }
} }
@@ -25,7 +25,7 @@ namespace Barotrauma
/// <summary> /// <summary>
/// Margin applied around the view area when culling entities (i.e. entities that are this far outside the view are still considered visible) /// Margin applied around the view area when culling entities (i.e. entities that are this far outside the view are still considered visible)
/// </summary> /// </summary>
private const int CullMargin = 500; private const int CullMargin = 50;
/// <summary> /// <summary>
/// Update entity culling when any corner of the view has moved more than this /// Update entity culling when any corner of the view has moved more than this
/// </summary> /// </summary>
@@ -713,18 +713,12 @@ namespace Barotrauma
return GameMain.LightManager.Lights.Count(l => l.CastShadows && !l.IsBackground) - disabledItemLightCount; return GameMain.LightManager.Lights.Count(l => l.CastShadows && !l.IsBackground) - disabledItemLightCount;
} }
public static Vector2 MouseToWorldGrid(Camera cam, Submarine sub, bool round = false) public static Vector2 MouseToWorldGrid(Camera cam, Submarine sub, Vector2? mousePos = null, bool round = false)
{ {
Vector2 position = PlayerInput.MousePosition; Vector2 position = mousePos ?? PlayerInput.MousePosition;
position = cam.ScreenToWorld(position); position = cam.ScreenToWorld(position);
Vector2 worldGridPos = VectorToWorldGrid(position, round); Vector2 worldGridPos = VectorToWorldGrid(position, sub, round);
if (sub != null)
{
worldGridPos.X += sub.Position.X % GridSize.X;
worldGridPos.Y += sub.Position.Y % GridSize.Y;
}
return worldGridPos; return worldGridPos;
} }
@@ -421,7 +421,7 @@ namespace Barotrauma
float scale = element.GetAttributeFloat("scale", 1f); float scale = element.GetAttributeFloat("scale", 1f);
Color color = element.GetAttributeColor("spritecolor", Color.White); Color color = element.GetAttributeColor("spritecolor", Color.White);
float rotation = element.GetAttributeFloat("rotation", 0f); float rotationRad = MathHelper.ToRadians(element.GetAttributeFloat("rotation", 0f));
MapEntityPrefab prefab; MapEntityPrefab prefab;
if (element.NameAsIdentifier() == "item" if (element.NameAsIdentifier() == "item"
@@ -455,7 +455,7 @@ namespace Barotrauma
ItemPrefab itemPrefab = prefab as ItemPrefab; ItemPrefab itemPrefab = prefab as ItemPrefab;
if (itemPrefab != null) if (itemPrefab != null)
{ {
BakeItemComponents(itemPrefab, rect, color, scale, rotation, depth, out overrideSprite); BakeItemComponents(itemPrefab, rect, color, scale, rotationRad, depth, out overrideSprite);
} }
if (!overrideSprite) if (!overrideSprite)
@@ -485,13 +485,15 @@ namespace Barotrauma
MathUtils.PositiveModulo((int)-textureOffset.Y, prefab.Sprite.SourceRect.Height)); MathUtils.PositiveModulo((int)-textureOffset.Y, prefab.Sprite.SourceRect.Height));
prefab.Sprite.DrawTiled( prefab.Sprite.DrawTiled(
spriteRecorder, spriteBatch: spriteRecorder,
rect.Location.ToVector2() * new Vector2(1f, -1f), position: new Vector2(rect.X + rect.Width / 2, -(rect.Y - rect.Height / 2)),
rect.Size.ToVector2(), targetSize: rect.Size.ToVector2(),
origin: rect.Size.ToVector2() * new Vector2(0.5f, 0.5f),
color: color, color: color,
startOffset: backGroundOffset, startOffset: backGroundOffset,
textureScale: textureScale * scale, textureScale: textureScale * scale,
depth: depth); depth: depth,
rotation: rotationRad);
} }
else if (itemPrefab != null) else if (itemPrefab != null)
{ {
@@ -552,7 +554,7 @@ namespace Barotrauma
spritePos * new Vector2(1f, -1f), spritePos * new Vector2(1f, -1f),
color, color,
prefab.Sprite.Origin, prefab.Sprite.Origin,
rotation, rotationRad,
scale, scale,
prefab.Sprite.effects, depth); prefab.Sprite.effects, depth);
@@ -564,7 +566,7 @@ namespace Barotrauma
if (flippedX) { offset.X = -offset.X; } if (flippedX) { offset.X = -offset.X; }
if (flippedY) { 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, rotationRad + 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));
} }
} }
@@ -577,7 +579,7 @@ namespace Barotrauma
private void BakeItemComponents( private void BakeItemComponents(
ItemPrefab prefab, ItemPrefab prefab,
Rectangle rect, Color color, Rectangle rect, Color color,
float scale, float rotation, float depth, float scale, float rotationRad, float depth,
out bool overrideSprite) out bool overrideSprite)
{ {
overrideSprite = false; overrideSprite = false;
@@ -607,7 +609,7 @@ namespace Barotrauma
Vector2 relativeBarrelPos = barrelPos * prefab.Scale - new Vector2(rect.Width / 2, rect.Height / 2); Vector2 relativeBarrelPos = barrelPos * prefab.Scale - new Vector2(rect.Width / 2, rect.Height / 2);
var transformedBarrelPos = MathUtils.RotatePoint( var transformedBarrelPos = MathUtils.RotatePoint(
relativeBarrelPos, relativeBarrelPos,
MathHelper.ToRadians(rotation)); rotationRad);
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;
@@ -615,13 +617,13 @@ namespace Barotrauma
railSprite?.Draw(spriteRecorder, railSprite?.Draw(spriteRecorder,
drawPos, drawPos,
color, color,
rotation + MathHelper.PiOver2, scale, rotationRad, scale,
SpriteEffects.None, depth + (railSprite.Depth - prefab.Sprite.Depth)); SpriteEffects.None, depth + (railSprite.Depth - prefab.Sprite.Depth));
barrelSprite?.Draw(spriteRecorder, barrelSprite?.Draw(spriteRecorder,
drawPos, drawPos,
color, color,
rotation + MathHelper.PiOver2, scale, rotationRad, scale,
SpriteEffects.None, depth + (barrelSprite.Depth - prefab.Sprite.Depth)); SpriteEffects.None, depth + (barrelSprite.Depth - prefab.Sprite.Depth));
break; break;
@@ -781,7 +783,7 @@ namespace Barotrauma
previewFrame = null; previewFrame = null;
} }
spriteRecorder?.Dispose(); spriteRecorder = null; spriteRecorder?.Dispose(); spriteRecorder = null;
camera?.Dispose(); camera = null; camera = null;
isDisposed = true; isDisposed = true;
} }
} }
@@ -122,12 +122,12 @@ namespace Barotrauma.Networking
VoipSound = null; VoipSound = null;
} }
public void SetPermissions(ClientPermissions permissions, IEnumerable<string> permittedConsoleCommands) public void SetPermissions(ClientPermissions permissions, IEnumerable<Identifier> permittedConsoleCommands)
{ {
List<DebugConsole.Command> permittedCommands = new List<DebugConsole.Command>(); List<DebugConsole.Command> permittedCommands = new List<DebugConsole.Command>();
foreach (string commandName in permittedConsoleCommands) foreach (Identifier commandName in permittedConsoleCommands)
{ {
var consoleCommand = DebugConsole.Commands.Find(c => c.names.Contains(commandName)); var consoleCommand = DebugConsole.Commands.Find(c => c.Names.Contains(commandName));
if (consoleCommand != null) if (consoleCommand != null)
{ {
permittedCommands.Add(consoleCommand); permittedCommands.Add(consoleCommand);
@@ -65,7 +65,7 @@ namespace Barotrauma.Networking
public bool LateCampaignJoin = false; public bool LateCampaignJoin = false;
private ClientPermissions permissions = ClientPermissions.None; private ClientPermissions permissions = ClientPermissions.None;
private List<string> permittedConsoleCommands = new List<string>(); private List<Identifier> permittedConsoleCommands = new List<Identifier>();
private bool connected; private bool connected;
@@ -170,9 +170,9 @@ namespace Barotrauma.Networking
internal readonly struct PermissionChangedEvent internal readonly struct PermissionChangedEvent
{ {
public readonly ClientPermissions NewPermissions; public readonly ClientPermissions NewPermissions;
public readonly ImmutableArray<string> NewPermittedConsoleCommands; public readonly ImmutableArray<Identifier> NewPermittedConsoleCommands;
public PermissionChangedEvent(ClientPermissions newPermissions, IReadOnlyList<string> newPermittedConsoleCommands) public PermissionChangedEvent(ClientPermissions newPermissions, IReadOnlyList<Identifier> newPermittedConsoleCommands)
{ {
NewPermissions = newPermissions; NewPermissions = newPermissions;
NewPermittedConsoleCommands = newPermittedConsoleCommands.ToImmutableArray(); NewPermittedConsoleCommands = newPermittedConsoleCommands.ToImmutableArray();
@@ -1213,11 +1213,11 @@ namespace Barotrauma.Networking
targetClient?.SetPermissions(permissions, permittedCommands); targetClient?.SetPermissions(permissions, permittedCommands);
if (clientId == SessionId) if (clientId == SessionId)
{ {
SetMyPermissions(permissions, permittedCommands.Select(command => command.names[0])); SetMyPermissions(permissions, permittedCommands.Select(command => command.Names[0]));
} }
} }
private void SetMyPermissions(ClientPermissions newPermissions, IEnumerable<string> permittedConsoleCommands) private void SetMyPermissions(ClientPermissions newPermissions, IEnumerable<Identifier> permittedConsoleCommands)
{ {
if (!(this.permittedConsoleCommands.Any(c => !permittedConsoleCommands.Contains(c)) || if (!(this.permittedConsoleCommands.Any(c => !permittedConsoleCommands.Contains(c)) ||
permittedConsoleCommands.Any(c => !this.permittedConsoleCommands.Contains(c)))) permittedConsoleCommands.Any(c => !this.permittedConsoleCommands.Contains(c))))
@@ -1229,7 +1229,7 @@ namespace Barotrauma.Networking
permissions.HasFlag(ClientPermissions.ManageRound) != newPermissions.HasFlag(ClientPermissions.ManageRound); permissions.HasFlag(ClientPermissions.ManageRound) != newPermissions.HasFlag(ClientPermissions.ManageRound);
permissions = newPermissions; permissions = newPermissions;
this.permittedConsoleCommands = new List<string>(permittedConsoleCommands); this.permittedConsoleCommands = permittedConsoleCommands.ToList();
//don't show the "permissions changed" popup if the client owns the server //don't show the "permissions changed" popup if the client owns the server
if (!IsServerOwner) if (!IsServerOwner)
{ {
@@ -1267,10 +1267,10 @@ namespace Barotrauma.Networking
var commandsLabel = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), rightColumn.RectTransform), var commandsLabel = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), rightColumn.RectTransform),
TextManager.Get("PermittedConsoleCommands"), wrap: true, font: GUIStyle.SubHeadingFont); TextManager.Get("PermittedConsoleCommands"), wrap: true, font: GUIStyle.SubHeadingFont);
var commandList = new GUIListBox(new RectTransform(new Vector2(1.0f, 1.0f), rightColumn.RectTransform)); var commandList = new GUIListBox(new RectTransform(new Vector2(1.0f, 1.0f), rightColumn.RectTransform));
foreach (string permittedCommand in permittedConsoleCommands) foreach (Identifier permittedCommand in permittedConsoleCommands)
{ {
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), commandList.Content.RectTransform, minSize: new Point(0, 15)), new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), commandList.Content.RectTransform, minSize: new Point(0, 15)),
permittedCommand, font: GUIStyle.SmallFont) permittedCommand.Value, font: GUIStyle.SmallFont)
{ {
CanBeFocused = false CanBeFocused = false
}; };
@@ -1350,6 +1350,7 @@ namespace Barotrauma.Networking
bool respawnAllowed = inc.ReadBoolean(); bool respawnAllowed = inc.ReadBoolean();
ServerSettings.AllowDisguises = inc.ReadBoolean(); ServerSettings.AllowDisguises = inc.ReadBoolean();
ServerSettings.AllowRewiring = inc.ReadBoolean(); ServerSettings.AllowRewiring = inc.ReadBoolean();
ServerSettings.AllowImmediateItemDelivery = inc.ReadBoolean();
ServerSettings.AllowFriendlyFire = inc.ReadBoolean(); ServerSettings.AllowFriendlyFire = inc.ReadBoolean();
ServerSettings.LockAllDefaultWires = inc.ReadBoolean(); ServerSettings.LockAllDefaultWires = inc.ReadBoolean();
ServerSettings.AllowLinkingWifiToChat = inc.ReadBoolean(); ServerSettings.AllowLinkingWifiToChat = inc.ReadBoolean();
@@ -2223,7 +2224,7 @@ namespace Barotrauma.Networking
} }
outmsg.WriteByte((byte)MultiplayerPreferences.Instance.TeamPreference); outmsg.WriteByte((byte)MultiplayerPreferences.Instance.TeamPreference);
if (!(GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign) || campaign.LastSaveID == 0) if (GameMain.GameSession?.GameMode is not MultiPlayerCampaign campaign || campaign.LastSaveID == 0)
{ {
outmsg.WriteUInt16((UInt16)0); outmsg.WriteUInt16((UInt16)0);
} }
@@ -2553,18 +2554,18 @@ namespace Barotrauma.Networking
return permissions.HasFlag(permission); return permissions.HasFlag(permission);
} }
public bool HasConsoleCommandPermission(string commandName) public bool HasConsoleCommandPermission(Identifier commandName)
{ {
if (!permissions.HasFlag(ClientPermissions.ConsoleCommands)) { return false; } if (!permissions.HasFlag(ClientPermissions.ConsoleCommands)) { return false; }
if (permittedConsoleCommands.Any(c => c.Equals(commandName, StringComparison.OrdinalIgnoreCase))) { return true; } if (permittedConsoleCommands.Contains(commandName)) { return true; }
//check aliases //check aliases
foreach (DebugConsole.Command command in DebugConsole.Commands) foreach (DebugConsole.Command command in DebugConsole.Commands)
{ {
if (command.names.Contains(commandName)) if (command.Names.Contains(commandName))
{ {
if (command.names.Intersect(permittedConsoleCommands).Any()) { return true; } if (command.Names.Intersect(permittedConsoleCommands).Any()) { return true; }
break; break;
} }
} }
@@ -75,6 +75,9 @@ namespace Barotrauma.Networking
[Serialize("", IsPropertySaveable.Yes)] [Serialize("", IsPropertySaveable.Yes)]
public LanguageIdentifier Language { get; set; } public LanguageIdentifier Language { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
public string SelectedSub { get; set; } = string.Empty;
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();
@@ -104,6 +107,8 @@ namespace Barotrauma.Networking
public ImmutableArray<ContentPackageInfo> ContentPackages; public ImmutableArray<ContentPackageInfo> ContentPackages;
public int ContentPackageCount;
public bool IsModded => ContentPackages.Any(p => !GameMain.VanillaContent.NameMatches(p.Name)); public bool IsModded => ContentPackages.Any(p => !GameMain.VanillaContent.NameMatches(p.Name));
public ServerInfo(Endpoint endpoint) public ServerInfo(Endpoint endpoint)
@@ -176,33 +181,6 @@ namespace Barotrauma.Networking
}; };
title.Text = ToolBox.LimitString(title.Text, title.Font, (int)(title.Rect.Width * 0.85f)); title.Text = ToolBox.LimitString(title.Text, title.Font, (int)(title.Rect.Width * 0.85f));
bool isFavorite = serverListScreen.IsFavorite(this);
static LocalizedString favoriteTickBoxToolTip(bool isFavorite)
=> TextManager.Get(isFavorite ? "RemoveFromFavorites" : "AddToFavorites");
GUITickBox favoriteTickBox = new GUITickBox(new RectTransform(new Vector2(0.15f, 0.8f), title.RectTransform, Anchor.CenterRight),
"", null, "GUIServerListFavoriteTickBox")
{
UserData = this,
Selected = isFavorite,
ToolTip = favoriteTickBoxToolTip(isFavorite),
OnSelected = tickbox =>
{
ServerInfo info = (ServerInfo)tickbox.UserData;
if (tickbox.Selected)
{
GameMain.ServerListScreen.AddToFavoriteServers(info);
}
else
{
GameMain.ServerListScreen.RemoveFromFavoriteServers(info);
}
tickbox.ToolTip = favoriteTickBoxToolTip(tickbox.Selected);
return true;
}
};
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), frame.RectTransform), new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), frame.RectTransform),
TextManager.AddPunctuation(':', TextManager.Get("ServerListVersion"), TextManager.AddPunctuation(':', TextManager.Get("ServerListVersion"),
GameVersion == new Version(0, 0, 0, 0) ? TextManager.Get("Unknown") : GameVersion.ToString())) GameVersion == new Version(0, 0, 0, 0) ? TextManager.Get("Unknown") : GameVersion.ToString()))
@@ -258,6 +236,59 @@ namespace Barotrauma.Networking
{ {
Stretch = true Stretch = true
}; };
var buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 0.25f), playStyleBanner.RectTransform, Anchor.BottomRight),
isHorizontal: true, childAnchor: Anchor.BottomRight);
//shadow behind the buttons
new GUIFrame(new RectTransform(new Vector2(3.15f, 1.05f), buttonContainer.RectTransform, Anchor.BottomRight, scaleBasis: ScaleBasis.Smallest), style: null)
{
Color = Color.Black * 0.7f,
IgnoreLayoutGroups = true
};
bool isFavorite = serverListScreen.IsFavorite(this);
static LocalizedString favoriteTickBoxToolTip(bool isFavorite)
=> TextManager.Get(isFavorite ? "RemoveFromFavorites" : "AddToFavorites");
GUITickBox favoriteTickBox = new GUITickBox(new RectTransform(Vector2.One, buttonContainer.RectTransform, scaleBasis: ScaleBasis.Smallest),
"", null, "GUIServerListFavoriteTickBox")
{
UserData = this,
Selected = isFavorite,
ToolTip = favoriteTickBoxToolTip(isFavorite),
OnSelected = tickbox =>
{
ServerInfo info = (ServerInfo)tickbox.UserData;
if (tickbox.Selected)
{
GameMain.ServerListScreen.AddToFavoriteServers(info);
}
else
{
GameMain.ServerListScreen.RemoveFromFavoriteServers(info);
}
tickbox.ToolTip = favoriteTickBoxToolTip(tickbox.Selected);
return true;
}
};
new GUIButton(new RectTransform(Vector2.One, buttonContainer.RectTransform, scaleBasis: ScaleBasis.Smallest), style: "GUIServerListReportServer")
{
ToolTip = TextManager.Get("reportserver"),
OnClicked = (_, _) => {ServerListScreen.CreateReportPrompt(this); return true; }
};
new GUIButton(new RectTransform(Vector2.One, buttonContainer.RectTransform, scaleBasis: ScaleBasis.Smallest), style: "GUIServerListHideServer")
{
ToolTip = TextManager.Get("filterserver"),
OnClicked = (_, _) =>
{
ServerListScreen.CreateFilterServerPrompt(this);
return true;
}
};
// playstyle tags ----------------------------------------------------------------------------- // playstyle tags -----------------------------------------------------------------------------
var playStyleContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.15f), content.RectTransform), isHorizontal: true) var playStyleContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.15f), content.RectTransform), isHorizontal: true)
@@ -309,6 +340,14 @@ namespace Barotrauma.Networking
TextManager.Get(GameMode.IsEmpty ? "Unknown" : "GameMode." + GameMode).Fallback(GameMode.Value), TextManager.Get(GameMode.IsEmpty ? "Unknown" : "GameMode." + GameMode).Fallback(GameMode.Value),
textAlignment: Alignment.Right); textAlignment: Alignment.Right);
if (!string.IsNullOrEmpty(SelectedSub))
{
var submarineText = new GUITextBlock(new RectTransform(new Vector2(1.0f, elementHeight), content.RectTransform), TextManager.Get("Submarine"));
new GUITextBlock(new RectTransform(Vector2.One, submarineText.RectTransform),
SelectedSub,
textAlignment: Alignment.Right);
}
GUITextBlock playStyleText = new GUITextBlock(new RectTransform(new Vector2(1.0f, elementHeight), content.RectTransform), TextManager.Get("serverplaystyle")); GUITextBlock playStyleText = new GUITextBlock(new RectTransform(new Vector2(1.0f, elementHeight), content.RectTransform), TextManager.Get("serverplaystyle"));
new GUITextBlock(new RectTransform(Vector2.One, playStyleText.RectTransform), TextManager.Get("servertag." + playStyle), textAlignment: Alignment.Right); new GUITextBlock(new RectTransform(Vector2.One, playStyleText.RectTransform), TextManager.Get("servertag." + playStyle), textAlignment: Alignment.Right);
@@ -385,6 +424,15 @@ namespace Barotrauma.Networking
} }
} }
} }
if (ContentPackageCount > ContentPackages.Length)
{
new GUITextBlock(
new RectTransform(new Vector2(1.0f, 0.15f), contentPackageList.Content.RectTransform) { MinSize = new Point(0, 15) },
TextManager.GetWithVariable("workshopitemdownloadprompttruncated", "[number]", (ContentPackageCount - ContentPackages.Length).ToString()))
{
CanBeFocused = false
};
}
} }
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@@ -423,13 +471,15 @@ namespace Barotrauma.Networking
AllowSpectating = getBool("allowspectating"); AllowSpectating = getBool("allowspectating");
AllowRespawn = getBool("allowrespawn"); AllowRespawn = getBool("allowrespawn");
VoipEnabled = getBool("voicechatenabled"); VoipEnabled = getBool("voicechatenabled");
GameMode = valueGetter("gamemode")?.ToIdentifier() ?? Identifier.Empty; GameMode = valueGetter("gamemode")?.ToIdentifier() ?? Identifier.Empty;
if (float.TryParse(valueGetter("traitors"), NumberStyles.Any, CultureInfo.InvariantCulture, out float traitorProbability)) { TraitorProbability = traitorProbability; } if (float.TryParse(valueGetter("traitors"), NumberStyles.Any, CultureInfo.InvariantCulture, out float traitorProbability)) { TraitorProbability = traitorProbability; }
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; Language = valueGetter("language")?.ToLanguageIdentifier() ?? LanguageIdentifier.None;
SelectedSub = valueGetter("submarine") ?? string.Empty;
ContentPackages = ExtractContentPackageInfo(ServerName, valueGetter).ToImmutableArray(); ContentPackages = ExtractContentPackageInfo(ServerName, valueGetter).ToImmutableArray();
ContentPackageCount = ContentPackages.Length;
if (int.TryParse(valueGetter("packagecount"), out int packageCount)) { ContentPackageCount = packageCount; }
bool getBool(string key) bool getBool(string key)
{ {
@@ -936,6 +936,10 @@ namespace Barotrauma.Networking
TextManager.Get("ServerSettingsAllowVoteKick")); TextManager.Get("ServerSettingsAllowVoteKick"));
GetPropertyData(nameof(AllowVoteKick)).AssignGUIComponent(voteKickBox); GetPropertyData(nameof(AllowVoteKick)).AssignGUIComponent(voteKickBox);
var allowImmediateItemDeliveryBox = new GUITickBox(new RectTransform(new Vector2(0.48f, 0.05f), tickBoxContainer.Content.RectTransform),
TextManager.Get("ServerSettingsImmediateItemDelivery"));
GetPropertyData(nameof(AllowImmediateItemDelivery)).AssignGUIComponent(allowImmediateItemDeliveryBox);
GUITextBlock.AutoScaleAndNormalize(tickBoxContainer.Content.Children.Select(c => ((GUITickBox)c).TextBlock)); GUITextBlock.AutoScaleAndNormalize(tickBoxContainer.Content.Children.Select(c => ((GUITickBox)c).TextBlock));
tickBoxContainer.RectTransform.MinSize = new Point(0, (int)(tickBoxContainer.Content.Children.First().Rect.Height * 2.0f + tickBoxContainer.Padding.Y + tickBoxContainer.Padding.W)); tickBoxContainer.RectTransform.MinSize = new Point(0, (int)(tickBoxContainer.Content.Children.First().Rect.Height * 2.0f + tickBoxContainer.Padding.Y + tickBoxContainer.Padding.W));
@@ -1,4 +1,5 @@
using Microsoft.Xna.Framework; #nullable enable
using Microsoft.Xna.Framework;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Xml.Linq; using System.Xml.Linq;
@@ -148,7 +149,7 @@ namespace Barotrauma.Particles
Prefab = prefab; Prefab = prefab;
} }
public void Emit(float deltaTime, Vector2 position, Hull hullGuess = null, float angle = 0.0f, float particleRotation = 0.0f, float velocityMultiplier = 1.0f, float sizeMultiplier = 1.0f, float amountMultiplier = 1.0f, Color? colorMultiplier = null, ParticlePrefab overrideParticle = null, bool mirrorAngle = false, Tuple<Vector2, Vector2> tracerPoints = null) public void Emit(float deltaTime, Vector2 position, Hull? hullGuess = null, float angle = 0.0f, float particleRotation = 0.0f, float velocityMultiplier = 1.0f, float sizeMultiplier = 1.0f, float amountMultiplier = 1.0f, Color? colorMultiplier = null, ParticlePrefab? overrideParticle = null, bool mirrorAngle = false, Tuple<Vector2, Vector2>? tracerPoints = null)
{ {
if (GameMain.Client?.MidRoundSyncing ?? false) { return; } if (GameMain.Client?.MidRoundSyncing ?? false) { return; }
@@ -191,16 +192,17 @@ namespace Barotrauma.Particles
burstEmitTimer = Prefab.Properties.EmitInterval; burstEmitTimer = Prefab.Properties.EmitInterval;
for (int i = 0; i < Prefab.Properties.ParticleAmount * amountMultiplier; i++) for (int i = 0; i < Prefab.Properties.ParticleAmount * amountMultiplier; i++)
{ {
Emit(position, hullGuess, angle, particleRotation, velocityMultiplier, sizeMultiplier, colorMultiplier, overrideParticle, tracerPoints: tracerPoints); Emit(position, hullGuess, angle, particleRotation, velocityMultiplier, sizeMultiplier, colorMultiplier, overrideParticle, mirrorAngle, tracerPoints: tracerPoints);
} }
} }
private void Emit(Vector2 position, Hull hullGuess, float angle, float particleRotation, float velocityMultiplier, float sizeMultiplier, Color? colorMultiplier = null, ParticlePrefab overrideParticle = null, bool mirrorAngle = false, Tuple<Vector2, Vector2> tracerPoints = null) private void Emit(Vector2 position, Hull? hullGuess, float angle, float particleRotation, float velocityMultiplier, float sizeMultiplier, Color? colorMultiplier = null, ParticlePrefab? overrideParticle = null, bool mirrorAngle = false, Tuple<Vector2, Vector2>? tracerPoints = null)
{ {
var particlePrefab = overrideParticle ?? Prefab.ParticlePrefab; var particlePrefab = overrideParticle ?? Prefab.ParticlePrefab;
if (particlePrefab == null) if (particlePrefab == null)
{ {
DebugConsole.AddWarning($"Could not find the particle prefab \"{Prefab.ParticlePrefabName}\"."); DebugConsole.AddWarning($"Could not find the particle prefab \"{Prefab.ParticlePrefabName}\".",
contentPackage: Prefab.ContentPackage);
return; return;
} }
@@ -271,7 +273,7 @@ namespace Barotrauma.Particles
{ {
public readonly Identifier ParticlePrefabName; public readonly Identifier ParticlePrefabName;
public ParticlePrefab ParticlePrefab public ParticlePrefab? ParticlePrefab
{ {
get get
{ {
@@ -282,12 +284,16 @@ namespace Barotrauma.Particles
public readonly ParticleEmitterProperties Properties; public readonly ParticleEmitterProperties Properties;
public bool DrawOnTop => Properties.DrawOnTop || ParticlePrefab.DrawOnTop; public readonly ContentPackage? ContentPackage;
public bool DrawOnTop => Properties.DrawOnTop || ParticlePrefab is { DrawOnTop: true };
public ParticleEmitterPrefab(ContentXElement element) public ParticleEmitterPrefab(ContentXElement element)
{ {
Properties = new ParticleEmitterProperties(element); if (element == null) { throw new ArgumentNullException(nameof(element)); }
Properties = new ParticleEmitterProperties(element!);
ParticlePrefabName = element.GetAttributeIdentifier("particle", ""); ParticlePrefabName = element.GetAttributeIdentifier("particle", "");
ContentPackage = element.ContentPackage;
} }
public ParticleEmitterPrefab(ParticlePrefab prefab, ParticleEmitterProperties properties) public ParticleEmitterPrefab(ParticlePrefab prefab, ParticleEmitterProperties properties)
@@ -244,7 +244,8 @@ namespace Barotrauma.Particles
if (Sprites.Count == 0) if (Sprites.Count == 0)
{ {
DebugConsole.ThrowError($"Particle prefab \"{Name}\" in the file \"{file}\" has no sprites defined!"); DebugConsole.ThrowError($"Particle prefab \"{Name}\" in the file \"{file}\" has no sprites defined!",
contentPackage: element.ContentPackage);
} }
//if velocity change in water is not given, it defaults to the normal velocity change //if velocity change in water is not given, it defaults to the normal velocity change
@@ -66,15 +66,22 @@ namespace Barotrauma
private static void CrashHandler(object sender, UnhandledExceptionEventArgs args) private static void CrashHandler(object sender, UnhandledExceptionEventArgs args)
{ {
Exception unhandledException = args.ExceptionObject as Exception;
try try
{ {
Game?.Exit(); Game?.Exit();
CrashDump(Game, "crashreport.log", (Exception)args.ExceptionObject); CrashDump(Game, "crashreport.log", unhandledException);
Game?.Dispose(); Game?.Dispose();
} }
catch (Exception e) catch (Exception exceptionHandlerError)
{ {
Debug.WriteLine(e.Message); Debug.WriteLine(exceptionHandlerError.Message);
string slimCrashReport = "Exception handler failed: " + exceptionHandlerError.Message + "\n" + exceptionHandlerError.StackTrace;
if (unhandledException != null)
{
slimCrashReport += "\n\nInitial exception: " + unhandledException.Message + "\n" + unhandledException.StackTrace;
}
File.WriteAllText("crashreportslim.log", slimCrashReport);
//exception handler is broken, we have a serious problem here!! //exception handler is broken, we have a serious problem here!!
return; return;
} }
@@ -40,7 +40,8 @@ namespace Barotrauma
public CampaignMode Campaign { get; } public CampaignMode Campaign { get; }
public CrewManagement CrewManagement { get; set; } public CrewManagement CrewManagement { get; set; }
private Store Store { get; set; }
public Store Store { get; private set; }
public UpgradeStore UpgradeStore { get; set; } public UpgradeStore UpgradeStore { get; set; }
@@ -254,7 +255,7 @@ namespace Barotrauma
RelativeSpacing = 0.02f, RelativeSpacing = 0.02f,
}; };
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform), location.Name, font: GUIStyle.LargeFont) new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform), location.DisplayName, font: GUIStyle.LargeFont)
{ {
AutoScaleHorizontal = true AutoScaleHorizontal = true
}; };
@@ -598,9 +599,10 @@ namespace Barotrauma
break; break;
case CampaignMode.InteractionType.Crew: case CampaignMode.InteractionType.Crew:
CrewManagement.UpdateCrew(); CrewManagement.UpdateCrew();
CrewManagement.UpdateHireables();
break; break;
case CampaignMode.InteractionType.PurchaseSub: case CampaignMode.InteractionType.PurchaseSub:
if (submarineSelection == null) submarineSelection = new SubmarineSelection(false, () => Campaign.ShowCampaignUI = false, tabs[(int)CampaignMode.InteractionType.PurchaseSub].RectTransform); submarineSelection ??= new SubmarineSelection(false, () => Campaign.ShowCampaignUI = false, tabs[(int)CampaignMode.InteractionType.PurchaseSub].RectTransform);
submarineSelection.RefreshSubmarineDisplay(true, setTransferOptionToTrue: true); submarineSelection.RefreshSubmarineDisplay(true, setTransferOptionToTrue: true);
break; break;
case CampaignMode.InteractionType.Map: case CampaignMode.InteractionType.Map:
@@ -259,8 +259,8 @@ namespace Barotrauma
graphics.BlendState = BlendState.NonPremultiplied; graphics.BlendState = BlendState.NonPremultiplied;
graphics.SamplerStates[0] = SamplerState.LinearWrap; graphics.SamplerStates[0] = SamplerState.LinearWrap;
Quad.UseBasicEffect(renderTargetBackground); GraphicsQuad.UseBasicEffect(renderTargetBackground);
Quad.Render(); GraphicsQuad.Render();
//Draw the rest of the structures, characters and front structures //Draw the rest of the structures, characters and front structures
spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.NonPremultiplied, null, DepthStencilState.None, null, null, cam.Transform); spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.NonPremultiplied, null, DepthStencilState.None, null, null, cam.Transform);
@@ -313,8 +313,8 @@ namespace Barotrauma
graphics.BlendState = BlendState.Opaque; graphics.BlendState = BlendState.Opaque;
graphics.SamplerStates[0] = SamplerState.LinearWrap; graphics.SamplerStates[0] = SamplerState.LinearWrap;
Quad.UseBasicEffect(renderTarget); GraphicsQuad.UseBasicEffect(renderTarget);
Quad.Render(); GraphicsQuad.Render();
//draw alpha blended particles that are inside a sub //draw alpha blended particles that are inside a sub
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, null, DepthStencilState.DepthRead, null, null, cam.Transform); spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, null, DepthStencilState.DepthRead, null, null, cam.Transform);
@@ -380,8 +380,8 @@ namespace Barotrauma
graphics.DepthStencilState = DepthStencilState.None; graphics.DepthStencilState = DepthStencilState.None;
graphics.SamplerStates[0] = SamplerState.LinearWrap; graphics.SamplerStates[0] = SamplerState.LinearWrap;
graphics.BlendState = CustomBlendStates.Multiplicative; graphics.BlendState = CustomBlendStates.Multiplicative;
Quad.UseBasicEffect(GameMain.LightManager.LightMap); GraphicsQuad.UseBasicEffect(GameMain.LightManager.LightMap);
Quad.Render(); GraphicsQuad.Render();
} }
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, SamplerState.LinearWrap, DepthStencilState.None, null, null, cam.Transform); spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, SamplerState.LinearWrap, DepthStencilState.None, null, null, cam.Transform);
@@ -390,6 +390,8 @@ namespace Barotrauma
c.DrawFront(spriteBatch, cam); c.DrawFront(spriteBatch, cam);
} }
GameMain.LightManager.DebugDrawVertices(spriteBatch);
Level.Loaded?.DrawDebugOverlay(spriteBatch, cam); Level.Loaded?.DrawDebugOverlay(spriteBatch, cam);
if (GameMain.DebugDraw) if (GameMain.DebugDraw)
{ {
@@ -438,7 +440,7 @@ namespace Barotrauma
graphics.SamplerStates[0] = SamplerState.PointClamp; graphics.SamplerStates[0] = SamplerState.PointClamp;
graphics.SamplerStates[1] = SamplerState.PointClamp; graphics.SamplerStates[1] = SamplerState.PointClamp;
GameMain.LightManager.LosEffect.CurrentTechnique.Passes[0].Apply(); GameMain.LightManager.LosEffect.CurrentTechnique.Passes[0].Apply();
Quad.Render(); GraphicsQuad.Render();
graphics.SamplerStates[0] = SamplerState.LinearWrap; graphics.SamplerStates[0] = SamplerState.LinearWrap;
graphics.SamplerStates[1] = SamplerState.LinearWrap; graphics.SamplerStates[1] = SamplerState.LinearWrap;
} }
@@ -506,7 +508,7 @@ namespace Barotrauma
graphics.DepthStencilState = DepthStencilState.None; graphics.DepthStencilState = DepthStencilState.None;
if (string.IsNullOrEmpty(postProcessTechnique)) if (string.IsNullOrEmpty(postProcessTechnique))
{ {
Quad.UseBasicEffect(renderTargetFinal); GraphicsQuad.UseBasicEffect(renderTargetFinal);
} }
else else
{ {
@@ -515,7 +517,7 @@ namespace Barotrauma
PostProcessEffect.CurrentTechnique = PostProcessEffect.Techniques[postProcessTechnique]; PostProcessEffect.CurrentTechnique = PostProcessEffect.Techniques[postProcessTechnique];
PostProcessEffect.CurrentTechnique.Passes[0].Apply(); PostProcessEffect.CurrentTechnique.Passes[0].Apply();
} }
Quad.Render(); GraphicsQuad.Render();
if (fadeToBlackState > 0.0f) if (fadeToBlackState > 0.0f)
{ {
@@ -221,7 +221,17 @@ namespace Barotrauma
currentLevelData.AllowInvalidOutpost = allowInvalidOutpost.Selected; currentLevelData.AllowInvalidOutpost = allowInvalidOutpost.Selected;
var dummyLocations = GameSession.CreateDummyLocations(currentLevelData); var dummyLocations = GameSession.CreateDummyLocations(currentLevelData);
Level.Generate(currentLevelData, mirror: mirrorLevel.Selected, startLocation: dummyLocations[0], endLocation: dummyLocations[1]); Level.Generate(currentLevelData, mirror: mirrorLevel.Selected, startLocation: dummyLocations[0], endLocation: dummyLocations[1]);
Submarine.MainSub?.SetPosition(Level.Loaded.StartPosition);
if (Submarine.MainSub != null)
{
Vector2 startPos = Level.Loaded.StartPosition;
if (Level.Loaded.StartOutpost != null)
{
startPos.Y -= Level.Loaded.StartOutpost.Borders.Height / 2 + Submarine.MainSub.Borders.Height / 2;
}
Submarine.MainSub?.SetPosition(startPos);
}
GameMain.LightManager.AddLight(pointerLightSource); GameMain.LightManager.AddLight(pointerLightSource);
if (!wasLevelLoaded || Cam.Position.X < 0 || Cam.Position.Y < 0 || Cam.Position.Y > Level.Loaded.Size.X || Cam.Position.Y > Level.Loaded.Size.Y) if (!wasLevelLoaded || Cam.Position.X < 0 || Cam.Position.Y < 0 || Cam.Position.Y > Level.Loaded.Size.X || Cam.Position.Y > Level.Loaded.Size.Y)
{ {
@@ -76,6 +76,8 @@ namespace Barotrauma
private GUITextBlock tutorialHeader, tutorialDescription; private GUITextBlock tutorialHeader, tutorialDescription;
private GUIListBox tutorialList; private GUIListBox tutorialList;
private GUIComponent versionMismatchWarning;
#region Creation #region Creation
public MainMenuScreen(GameMain game) public MainMenuScreen(GameMain game)
{ {
@@ -105,6 +107,28 @@ namespace Barotrauma
} }
}; };
versionMismatchWarning = new GUIFrame(new RectTransform(new Vector2(0.7f, 0.065f), Frame.RectTransform) { AbsoluteOffset = new Point(GUI.IntScale(15)) }, style: "InnerFrame", color: GUIStyle.Red)
{
IgnoreLayoutGroups = true,
Visible = false
};
var versionMismatchContent = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), versionMismatchWarning.RectTransform, Anchor.Center), isHorizontal: true)
{
RelativeSpacing = 0.05f,
};
new GUIImage(new RectTransform(new Vector2(1.0f), versionMismatchContent.RectTransform, scaleBasis: ScaleBasis.Smallest), style: "GUINotificationButton")
{
Color = GUIStyle.Orange
};
new GUITextBlock(new RectTransform(new Vector2(0.85f, 1.0f), versionMismatchContent.RectTransform),
TextManager.GetWithVariables("versionmismatchwarning",
("[gameversion]", GameMain.Version.ToString()),
("[contentversion]", ContentPackageManager.VanillaCorePackage.GameVersion.ToString())),
wrap: true)
{
TextColor = GUIStyle.Orange
};
new GUIImage(new RectTransform(new Vector2(0.4f, 0.25f), Frame.RectTransform, Anchor.BottomRight) new GUIImage(new RectTransform(new Vector2(0.4f, 0.25f), Frame.RectTransform, Anchor.BottomRight)
{ RelativeOffset = new Vector2(0.08f, 0.05f), AbsoluteOffset = new Point(-8, -8) }, { RelativeOffset = new Vector2(0.08f, 0.05f), AbsoluteOffset = new Point(-8, -8) },
style: "TitleText") style: "TitleText")
@@ -141,6 +165,7 @@ namespace Barotrauma
} }
} }
#else #else
SpamServerFilters.RequestGlobalSpamFilter();
FetchRemoteContent(); FetchRemoteContent();
#endif #endif
@@ -608,6 +633,8 @@ namespace Barotrauma
GameMain.SubEditorScreen?.ClearBackedUpSubInfo(); GameMain.SubEditorScreen?.ClearBackedUpSubInfo();
Submarine.Unload(); Submarine.Unload();
versionMismatchWarning.Visible = GameMain.Version < ContentPackageManager.VanillaCorePackage.GameVersion;
ResetButtonStates(null); ResetButtonStates(null);
} }
@@ -683,7 +710,18 @@ namespace Barotrauma
.ToArray(); .ToArray();
foreach (var newServerExe in newServerExes) foreach (var newServerExe in newServerExes)
{ {
serverExecutableDropdown.AddItem($"{newServerExe.ContentPackage.Name} - {Path.GetFileNameWithoutExtension(newServerExe.Path.Value)}", userData: newServerExe); var serverExeEntry = serverExecutableDropdown.AddItem($"{newServerExe.ContentPackage.Name} - {Path.GetFileNameWithoutExtension(newServerExe.Path.Value)}", userData: newServerExe);
if (newServerExe.ContentPackage.GameVersion < GameMain.VanillaContent.GameVersion)
{
serverExeEntry.ToolTip =
TextManager.GetWithVariables("versionmismatchwarning",
("[gameversion]", newServerExe.ContentPackage.GameVersion.ToString()),
("[contentversion]", GameMain.VanillaContent.GameVersion.ToString()));
if (serverExeEntry is GUITextBlock serverExeText)
{
serverExeText.TextColor = GUIStyle.Red;
}
}
} }
serverExecutableDropdown.ListBox.Content.Children.ForEach(c => serverExecutableDropdown.ListBox.Content.Children.ForEach(c =>
{ {
@@ -1496,34 +1534,58 @@ namespace Barotrauma
{ {
OnClicked = (btn, userdata) => OnClicked = (btn, userdata) =>
{ {
string name = serverNameBox.Text; CheckServerName();
if (string.IsNullOrEmpty(name))
{
serverNameBox.Flash();
return false;
}
if (isPublicBox.Selected && ForbiddenWordFilter.IsForbidden(name, out string forbiddenWord))
{
var msgBox = new GUIMessageBox("",
TextManager.GetWithVariables("forbiddenservernameverification", ("[forbiddenword]", forbiddenWord), ("[servername]", name)),
new LocalizedString[] { TextManager.Get("yes"), TextManager.Get("no") });
msgBox.Buttons[0].OnClicked += (_, __) =>
{
TryStartServer();
msgBox.Close();
return true;
};
msgBox.Buttons[1].OnClicked += msgBox.Close;
}
else
{
TryStartServer();
}
return true; return true;
} }
}; };
void CheckServerName()
{
string name = serverNameBox.Text;
if (string.IsNullOrEmpty(name))
{
serverNameBox.Flash();
return;
}
if (isPublicBox.Selected && ForbiddenWordFilter.IsForbidden(name, out string forbiddenWord))
{
var msgBox = new GUIMessageBox("",
TextManager.GetWithVariables("forbiddenservernameverification", ("[forbiddenword]", forbiddenWord), ("[servername]", name)),
new LocalizedString[] { TextManager.Get("yes"), TextManager.Get("no") });
msgBox.Buttons[0].OnClicked += (_, __) =>
{
CheckServerExe();
msgBox.Close();
return true;
};
msgBox.Buttons[1].OnClicked += msgBox.Close;
return;
}
CheckServerExe();
}
void CheckServerExe()
{
if (serverExecutableDropdown?.SelectedData is ServerExecutableFile serverExe &&
serverExe.ContentPackage.GameVersion < GameMain.VanillaContent.GameVersion)
{
var msgBox = new GUIMessageBox(string.Empty,
TextManager.GetWithVariables("versionmismatchwarning",
("[gameversion]", serverExe.ContentPackage.GameVersion.ToString()),
("[contentversion]", GameMain.VanillaContent.GameVersion.ToString())) + "\n\n"+
TextManager.GetWithVariable("versionmismatch.verifylaunch", "[exename]", serverExe.ContentPackage.Name),
new LocalizedString[] { TextManager.Get("yes"), TextManager.Get("no") });
msgBox.Buttons[0].OnClicked += (_, __) =>
{
TryStartServer();
msgBox.Close();
return true;
};
msgBox.Buttons[1].OnClicked += msgBox.Close;
return;
}
TryStartServer();
}
} }
private void SetServerPlayStyle(PlayStyle playStyle) private void SetServerPlayStyle(PlayStyle playStyle)
@@ -1520,6 +1520,7 @@ namespace Barotrauma
}; };
bool nameChangePending = isGameRunning && GameMain.Client.PendingName != string.Empty && GameMain.Client?.Character?.Name != GameMain.Client.PendingName; bool nameChangePending = isGameRunning && GameMain.Client.PendingName != string.Empty && GameMain.Client?.Character?.Name != GameMain.Client.PendingName;
changesPendingText?.Parent?.RemoveChild(changesPendingText);
changesPendingText = null; changesPendingText = null;
if (TabMenu.PendingChanges) if (TabMenu.PendingChanges)
@@ -2389,10 +2390,20 @@ namespace Barotrauma
options.Add(kickOption); options.Add(kickOption);
} }
options.Add(new ContextMenuOption("Ban", isEnabled: canBan, onSelected: delegate if (GameMain.Client?.ServerSettings?.BanList?.BannedPlayers?.Any(bp => bp.MatchesClient(client)) ?? false)
{ {
GameMain.Client?.CreateKickReasonPrompt(client.Name, true); options.Add(new ContextMenuOption("clientpermission.unban", isEnabled: canBan, onSelected: delegate
})); {
GameMain.Client?.UnbanPlayer(client.Name);
}));
}
else
{
options.Add(new ContextMenuOption("Ban", isEnabled: canBan, onSelected: delegate
{
GameMain.Client?.CreateKickReasonPrompt(client.Name, true);
}));
}
GUIContextMenu.CreateContextMenu(null, client.Name, headerColor: clientColor, options.ToArray()); GUIContextMenu.CreateContextMenu(null, client.Name, headerColor: clientColor, options.ToArray());
} }
@@ -2591,11 +2602,11 @@ namespace Barotrauma
foreach (DebugConsole.Command command in DebugConsole.Commands) foreach (DebugConsole.Command command in DebugConsole.Commands)
{ {
var commandTickBox = new GUITickBox(new RectTransform(new Vector2(0.15f, 0.15f), commandList.Content.RectTransform), var commandTickBox = new GUITickBox(new RectTransform(new Vector2(0.15f, 0.15f), commandList.Content.RectTransform),
command.names[0], font: GUIStyle.SmallFont) command.Names[0].Value, font: GUIStyle.SmallFont)
{ {
Selected = selectedClient.PermittedConsoleCommands.Contains(command), Selected = selectedClient.PermittedConsoleCommands.Contains(command),
Enabled = !myClient, Enabled = !myClient,
ToolTip = command.help, ToolTip = command.Help,
UserData = command UserData = command
}; };
commandTickBox.OnSelected += (GUITickBox tickBox) => commandTickBox.OnSelected += (GUITickBox tickBox) =>
@@ -2630,12 +2641,25 @@ namespace Barotrauma
{ {
if (GameMain.Client.HasPermission(ClientPermissions.Ban)) if (GameMain.Client.HasPermission(ClientPermissions.Ban))
{ {
var banButton = new GUIButton(new RectTransform(new Vector2(0.34f, 1.0f), buttonAreaTop.RectTransform), GUIButton banButton;
TextManager.Get("Ban")) if (GameMain.Client?.ServerSettings?.BanList?.BannedPlayers?.Any(bp => bp.MatchesClient(selectedClient)) ?? false)
{ {
UserData = selectedClient banButton = new GUIButton(new RectTransform(new Vector2(0.34f, 1.0f), buttonAreaTop.RectTransform),
}; TextManager.Get("clientpermission.unban"))
banButton.OnClicked = (bt, userdata) => { BanPlayer(selectedClient); return true; }; {
UserData = selectedClient
};
banButton.OnClicked = (bt, userdata) => { GameMain.Client?.UnbanPlayer(selectedClient.Name); return true; };
}
else
{
banButton = new GUIButton(new RectTransform(new Vector2(0.34f, 1.0f), buttonAreaTop.RectTransform),
TextManager.Get("Ban"))
{
UserData = selectedClient
};
banButton.OnClicked = (bt, userdata) => { BanPlayer(selectedClient); return true; };
}
banButton.OnClicked += ClosePlayerFrame; banButton.OnClicked += ClosePlayerFrame;
} }
@@ -3147,12 +3171,12 @@ namespace Barotrauma
GUIButton jobButton = null; GUIButton jobButton = null;
var availableJobs = JobPrefab.Prefabs.Where(jobPrefab => var availableJobs = JobPrefab.Prefabs.Where(jobPrefab =>
jobPrefab.MaxNumber > 0 && JobList.Content.Children.All(c => !(c.UserData is JobVariant prefab) || prefab.Prefab != jobPrefab) !jobPrefab.HiddenJob && jobPrefab.MaxNumber > 0 && JobList.Content.Children.All(c => c.UserData is not JobVariant prefab || prefab.Prefab != jobPrefab)
).Select(j => new JobVariant(j, 0)); ).Select(j => new JobVariant(j, 0));
availableJobs = availableJobs.Concat( availableJobs = availableJobs.Concat(
JobPrefab.Prefabs.Where(jobPrefab => JobPrefab.Prefabs.Where(jobPrefab =>
jobPrefab.MaxNumber > 0 && JobList.Content.Children.Any(c => (c.UserData is JobVariant prefab) && prefab.Prefab == jobPrefab) !jobPrefab.HiddenJob && jobPrefab.MaxNumber > 0 && JobList.Content.Children.Any(c => (c.UserData is JobVariant prefab) && prefab.Prefab == jobPrefab)
).Select(j => (JobVariant)JobList.Content.FindChild(c => (c.UserData is JobVariant prefab) && prefab.Prefab == j).UserData)); ).Select(j => (JobVariant)JobList.Content.FindChild(c => (c.UserData is JobVariant prefab) && prefab.Prefab == j).UserData));
availableJobs = availableJobs.ToList(); availableJobs = availableJobs.ToList();
@@ -655,7 +655,8 @@ namespace Barotrauma
ScrollBarVisible = true, ScrollBarVisible = true,
OnSelected = (btn, obj) => OnSelected = (btn, obj) =>
{ {
if (!(obj is ServerInfo serverInfo)) { return false; } if (GUI.MouseOn is GUIButton) { return false; }
if (obj is not ServerInfo serverInfo) { return false; }
joinButton.Enabled = true; joinButton.Enabled = true;
selectedServer = Option<ServerInfo>.Some(serverInfo); selectedServer = Option<ServerInfo>.Some(serverInfo);
@@ -852,6 +853,13 @@ namespace Barotrauma
}); });
} }
public void HideServerPreview()
{
serverPreviewContainer.Visible = false;
panelAnimator.RightEnabled = false;
panelAnimator.RightVisible = false;
}
private void InsertServer(ServerInfo serverInfo, GUIComponent component) private void InsertServer(ServerInfo serverInfo, GUIComponent component)
{ {
var children = serverList.Content.RectTransform.Children.Reverse().ToList(); var children = serverList.Content.RectTransform.Children.Reverse().ToList();
@@ -973,7 +981,7 @@ namespace Barotrauma
} }
} }
private void FilterServers() public void FilterServers()
{ {
RemoveMsgFromServerList(MsgUserData.NoMatchingServers); RemoveMsgFromServerList(MsgUserData.NoMatchingServers);
foreach (GUIComponent child in serverList.Content.Children) foreach (GUIComponent child in serverList.Content.Children)
@@ -1013,6 +1021,7 @@ namespace Barotrauma
return false; return false;
} }
#endif #endif
if (SpamServerFilters.IsFiltered(serverInfo)) { return false; }
if (!string.IsNullOrEmpty(searchBox.Text) && !serverInfo.ServerName.Contains(searchBox.Text, StringComparison.OrdinalIgnoreCase)) { return false; } if (!string.IsNullOrEmpty(searchBox.Text) && !serverInfo.ServerName.Contains(searchBox.Text, StringComparison.OrdinalIgnoreCase)) { return false; }
@@ -1553,15 +1562,169 @@ namespace Barotrauma
var serverFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.06f), serverList.Content.RectTransform) { MinSize = new Point(0, 35) }, var serverFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.06f), serverList.Content.RectTransform) { MinSize = new Point(0, 35) },
style: "ListBoxElement") style: "ListBoxElement")
{ {
UserData = serverInfo UserData = serverInfo,
}; };
serverFrame.OnSecondaryClicked += (_, data) =>
{
if (data is not ServerInfo info) { return false; }
CreateContextMenu(info);
return true;
};
new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f), serverFrame.RectTransform, Anchor.Center), isHorizontal: true, childAnchor: Anchor.CenterLeft) new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f), serverFrame.RectTransform, Anchor.Center), isHorizontal: true, childAnchor: Anchor.CenterLeft)
{ {
Stretch = false Stretch = false
}; };
UpdateServerInfoUI(serverInfo); UpdateServerInfoUI(serverInfo);
if (!skipPing) { PingUtils.GetServerPing(serverInfo, UpdateServerInfoUI); } if (!skipPing) { PingUtils.GetServerPing(serverInfo, UpdateServerInfoUI); }
}
private static readonly Vector2 confirmPopupSize = new Vector2(0.2f, 0.2625f);
private static readonly Point confirmPopupMinSize = new Point(300, 300);
private void CreateContextMenu(ServerInfo info)
{
var favoriteOption = new ContextMenuOption(IsFavorite(info) ? "removefromfavorites" : "addtofavorites", isEnabled: true, () =>
{
if (IsFavorite(info))
{
RemoveFromFavoriteServers(info);
}
else
{
AddToFavoriteServers(info);
}
FilterServers();
});
var reportOption = new ContextMenuOption("reportserver", isEnabled: true, () => { CreateReportPrompt(info); });
var filterOption = new ContextMenuOption("filterserver", isEnabled: true, () =>
{
CreateFilterServerPrompt(info);
})
{
Tooltip = TextManager.Get("filterservertooltip")
};
GUIContextMenu.CreateContextMenu(favoriteOption, filterOption, reportOption);
}
public static void CreateFilterServerPrompt(ServerInfo info)
{
GUI.AskForConfirmation(
header: TextManager.Get("filterserver"),
body: TextManager.GetWithVariables("filterserverconfirm", ("[server]", info.ServerName), ("[filepath]", SpamServerFilter.SavePath)),
onConfirm: () =>
{
SpamServerFilters.AddServerToLocalSpamList(info);
if (GameMain.ServerListScreen is not { } serverListScreen) { return; }
if (serverListScreen.selectedServer.TryUnwrap(out var selectedServer) && selectedServer.Equals(info))
{
serverListScreen.HideServerPreview();
}
serverListScreen.FilterServers();
}, relativeSize: confirmPopupSize, minSize: confirmPopupMinSize);
}
private enum ReportReason
{
Spam,
Advertising,
Inappropriate
}
public static void CreateReportPrompt(ServerInfo info)
{
if (!GameAnalyticsManager.SendUserStatistics)
{
GUI.NotifyPrompt(TextManager.Get("reportserver"), TextManager.Get("reportserverdisabled"));
return;
}
var msgBox = new GUIMessageBox(
headerText: TextManager.Get("reportserver"),
text: string.Empty,
relativeSize: new Vector2(0.2f, 0.4f),
minSize: new Point(380, 430),
buttons: Array.Empty<LocalizedString>());
var layout = new GUILayoutGroup(new RectTransform(Vector2.One, msgBox.Content.RectTransform, Anchor.Center));
new GUITextBlock(new RectTransform(new Vector2(1f, 0.3f), layout.RectTransform), TextManager.GetWithVariable("reportserverexplanation", "[server]", info.ServerName), wrap: true)
{
ToolTip = TextManager.Get("reportserverprompttooltip")
};
var listBox = new GUIListBox(new RectTransform(new Vector2(1f, 0.3f), layout.RectTransform));
var enums = Enum.GetValues<ReportReason>();
foreach (ReportReason reason in enums)
{
new GUITickBox(new RectTransform(new Vector2(1f, 1f / enums.Length), listBox.Content.RectTransform), TextManager.Get($"reportreason.{reason}"))
{
UserData = reason
};
}
// padding
new GUIFrame(new RectTransform(new Vector2(1f, 0.05f), layout.RectTransform), style: null);
var buttonLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.3f), layout.RectTransform))
{
Stretch = true
};
var reportAndHideButton = new GUIButton(new RectTransform(new Vector2(1f, 0.333f), buttonLayout.RectTransform), TextManager.Get("reportoption.reportandhide"))
{
Enabled = false,
OnClicked = (_, _) =>
{
CreateFilterServerPrompt(info);
msgBox.Close();
return true;
}
};
var reportButton = new GUIButton(new RectTransform(new Vector2(1f, 0.333f), buttonLayout.RectTransform), TextManager.Get("reportoption.report"))
{
Enabled = false,
OnClicked = (_, _) =>
{
ReportServer(info, GetUserSelectedReasons());
msgBox.Close();
return true;
}
};
new GUIButton(new RectTransform(new Vector2(1f, 0.333f), buttonLayout.RectTransform), TextManager.Get("cancel"))
{
OnClicked = (_, _) =>
{
msgBox.Close();
return true;
}
};
foreach (var child in listBox.Content.GetAllChildren<GUITickBox>())
{
child.OnSelected += _ =>
{
reportAndHideButton.Enabled = reportButton.Enabled = GetUserSelectedReasons().Any();
return true;
};
}
IEnumerable<ReportReason> GetUserSelectedReasons()
=> listBox.Content.Children
.Where(static c => c.UserData is ReportReason && c.Selected)
.Select(static c => (ReportReason)c.UserData).ToArray();
}
private static void ReportServer(ServerInfo info, IEnumerable<ReportReason> reasons)
{
if (!reasons.Any()) { return; }
GameAnalyticsManager.AddErrorEvent(GameAnalyticsManager.ErrorSeverity.Info, $"[Spam] Reported server: Name: \"{info.ServerName}\", Message: \"{info.ServerMessage}\", Endpoint: \"{info.Endpoint.StringRepresentation}\". Reason: \"{string.Join(", ", reasons)}\".");
} }
private void UpdateServerInfoUI(ServerInfo serverInfo) private void UpdateServerInfoUI(ServerInfo serverInfo)
@@ -1571,7 +1734,6 @@ namespace Barotrauma
serverFrame.UserData = serverInfo; serverFrame.UserData = serverInfo;
serverFrame.ToolTip = "";
var serverContent = serverFrame.Children.First() as GUILayoutGroup; var serverContent = serverFrame.Children.First() as GUILayoutGroup;
serverContent.ClearChildren(); serverContent.ClearChildren();
@@ -1584,14 +1746,13 @@ namespace Barotrauma
style: null); style: null);
} }
void errorTooltip(RichString toolTip) void disableElementFocus()
{ {
sections.Values.ForEach(c => sections.Values.ForEach(c =>
{ {
c.CanBeFocused = false; c.CanBeFocused = false;
c.Children.First().CanBeFocused = false; c.Children.First().CanBeFocused = false;
}); });
serverFrame.ToolTip = toolTip;
} }
RectTransform columnRT(ColumnLabel label, float scale = 0.95f) RectTransform columnRT(ColumnLabel label, float scale = 0.95f)
@@ -1664,9 +1825,10 @@ namespace Barotrauma
serverPingText.TextColor = Color.DarkRed; serverPingText.TextColor = Color.DarkRed;
} }
LocalizedString toolTip = "";
if (!serverInfo.Checked) if (!serverInfo.Checked)
{ {
errorTooltip(TextManager.Get("ServerOffline")); toolTip = TextManager.Get("ServerOffline");
serverName.TextColor *= 0.8f; serverName.TextColor *= 0.8f;
serverPlayers.TextColor *= 0.8f; serverPlayers.TextColor *= 0.8f;
} }
@@ -1681,7 +1843,6 @@ namespace Barotrauma
} }
else if (!compatibleBox.Selected) else if (!compatibleBox.Selected)
{ {
LocalizedString toolTip = "";
if (serverInfo.GameVersion != GameMain.Version) if (serverInfo.GameVersion != GameMain.Version)
{ {
toolTip = TextManager.GetWithVariable("ServerListIncompatibleVersion", "[version]", serverInfo.GameVersion.ToString()); toolTip = TextManager.GetWithVariable("ServerListIncompatibleVersion", "[version]", serverInfo.GameVersion.ToString());
@@ -1707,14 +1868,12 @@ namespace Barotrauma
toolTip += '\n' + TextManager.GetWithVariable("workshopitemdownloadprompttruncated", "[number]", (incompatibleModNames.Count - maxIncompatibleToList).ToString()); toolTip += '\n' + TextManager.GetWithVariable("workshopitemdownloadprompttruncated", "[number]", (incompatibleModNames.Count - maxIncompatibleToList).ToString());
} }
} }
errorTooltip(toolTip);
serverName.TextColor *= 0.5f; serverName.TextColor *= 0.5f;
serverPlayers.TextColor *= 0.5f; serverPlayers.TextColor *= 0.5f;
} }
else else
{ {
LocalizedString toolTip = "";
foreach (var contentPackage in serverInfo.ContentPackages) foreach (var contentPackage in serverInfo.ContentPackages)
{ {
if (ContentPackageManager.EnabledPackages.All.None(cp => cp.Hash.StringRepresentation == contentPackage.Hash)) if (ContentPackageManager.EnabledPackages.All.None(cp => cp.Hash.StringRepresentation == contentPackage.Hash))
@@ -1724,8 +1883,11 @@ namespace Barotrauma
break; break;
} }
} }
errorTooltip(toolTip);
} }
disableElementFocus();
string separator = toolTip.IsNullOrWhiteSpace() ? "" : "\n\n";
serverFrame.ToolTip = RichString.Rich(toolTip + separator + $"‖color:gui.blue‖{TextManager.GetWithVariable("serverlisttooltip", "[button]", PlayerInput.SecondaryMouseLabel)}‖end‖");
foreach (var section in sections.Values) foreach (var section in sections.Values)
{ {
@@ -16,9 +16,6 @@ namespace Barotrauma
{ {
class SubEditorScreen : EditorScreen class SubEditorScreen : EditorScreen
{ {
public const string CircuitBoxDeletionWarningHeader = "Selection contains circuit boxes",
CircuitBoxDeletionWarningBody = "Are you sure you want to delete the selection? Any wiring inside circuit boxes will be lost and cannot be recovered.";
public const int MaxStructures = 2000; public const int MaxStructures = 2000;
public const int MaxWalls = 500; public const int MaxWalls = 500;
public const int MaxItems = 5000; public const int MaxItems = 5000;
@@ -1290,7 +1287,8 @@ namespace Barotrauma
if (legacy) { textBlock.TextColor *= 0.6f; } if (legacy) { textBlock.TextColor *= 0.6f; }
if (name.IsNullOrEmpty()) if (name.IsNullOrEmpty())
{ {
DebugConsole.AddWarning($"Entity \"{ep.Identifier.Value}\" has no name!"); DebugConsole.AddWarning($"Entity \"{ep.Identifier.Value}\" has no name!",
contentPackage: ep.ContentPackage);
textBlock.Text = frame.ToolTip = ep.Identifier.Value; textBlock.Text = frame.ToolTip = ep.Identifier.Value;
textBlock.TextColor = GUIStyle.Red; textBlock.TextColor = GUIStyle.Red;
} }
@@ -1562,8 +1560,17 @@ namespace Barotrauma
if (editorSelectedTime.TryUnwrap(out DateTime selectedTime)) if (editorSelectedTime.TryUnwrap(out DateTime selectedTime))
{ {
TimeSpan timeInEditor = DateTime.Now - selectedTime; TimeSpan timeInEditor = DateTime.Now - selectedTime;
SteamAchievementManager.IncrementStat("hoursineditor".ToIdentifier(), (float)timeInEditor.TotalHours); if (timeInEditor.TotalSeconds > Timing.TotalTime)
editorSelectedTime = Option<DateTime>.None(); {
DebugConsole.ThrowErrorAndLogToGA(
"SubEditorScreen.DeselectEditorSpecific:InvalidTimeInEditor",
$"Error in sub editor screen. Calculated time in editor {timeInEditor} was larger than the time the game has run ({Timing.TotalTime} s).");
}
else
{
SteamAchievementManager.IncrementStat("hoursineditor".ToIdentifier(), (float)timeInEditor.TotalHours);
editorSelectedTime = Option<DateTime>.None();
}
} }
#endif #endif
@@ -2368,49 +2375,58 @@ namespace Barotrauma
//--------------------------------------- //---------------------------------------
var beaconSettingsContainer = new GUILayoutGroup(new RectTransform(Vector2.One, subTypeDependentSettingFrame.RectTransform)) var extraSettingsContainer = new GUILayoutGroup(new RectTransform(new Vector2(1, 0.5f), subTypeDependentSettingFrame.RectTransform))
{ {
CanBeFocused = true, CanBeFocused = true,
Visible = false, Visible = false,
Stretch = true Stretch = true
}; };
// ------------------- var minDifficultyGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.25f), extraSettingsContainer.RectTransform), isHorizontal: true)
var beaconMinDifficultyGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.25f), beaconSettingsContainer.RectTransform), isHorizontal: true)
{ {
Stretch = true Stretch = true
}; };
new GUITextBlock(new RectTransform(new Vector2(0.6f, 1.0f), beaconMinDifficultyGroup.RectTransform), new GUITextBlock(new RectTransform(new Vector2(0.6f, 1.0f), minDifficultyGroup.RectTransform),
TextManager.Get("minleveldifficulty"), textAlignment: Alignment.CenterLeft, wrap: true); TextManager.Get("minleveldifficulty"), textAlignment: Alignment.CenterLeft, wrap: true);
var numInput = new GUINumberInput(new RectTransform(new Vector2(0.4f, 1.0f), beaconMinDifficultyGroup.RectTransform), NumberType.Int) var numInput = new GUINumberInput(new RectTransform(new Vector2(0.4f, 1.0f), minDifficultyGroup.RectTransform), NumberType.Int)
{ {
IntValue = (int)(MainSub?.Info?.BeaconStationInfo?.MinLevelDifficulty ?? 0), IntValue = (int)(MainSub?.Info?.GetExtraSubmarineInfo?.MinLevelDifficulty ?? 0),
MinValueInt = 0, MinValueInt = 0,
MaxValueInt = 100, MaxValueInt = 100,
OnValueChanged = (numberInput) => OnValueChanged = (numberInput) =>
{ {
MainSub.Info.BeaconStationInfo.MinLevelDifficulty = numberInput.IntValue; MainSub.Info.GetExtraSubmarineInfo.MinLevelDifficulty = numberInput.IntValue;
} }
}; };
beaconMinDifficultyGroup.RectTransform.MaxSize = numInput.TextBox.RectTransform.MaxSize; minDifficultyGroup.RectTransform.MaxSize = numInput.TextBox.RectTransform.MaxSize;
var beaconMaxDifficultyGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.25f), beaconSettingsContainer.RectTransform), isHorizontal: true) var maxDifficultyGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.25f), extraSettingsContainer.RectTransform), isHorizontal: true)
{ {
Stretch = true Stretch = true
}; };
new GUITextBlock(new RectTransform(new Vector2(0.6f, 1.0f), beaconMaxDifficultyGroup.RectTransform), new GUITextBlock(new RectTransform(new Vector2(0.6f, 1.0f), maxDifficultyGroup.RectTransform),
TextManager.Get("maxleveldifficulty"), textAlignment: Alignment.CenterLeft, wrap: true); TextManager.Get("maxleveldifficulty"), textAlignment: Alignment.CenterLeft, wrap: true);
numInput = new GUINumberInput(new RectTransform(new Vector2(0.4f, 1.0f), beaconMaxDifficultyGroup.RectTransform), NumberType.Int) numInput = new GUINumberInput(new RectTransform(new Vector2(0.4f, 1.0f), maxDifficultyGroup.RectTransform), NumberType.Int)
{ {
IntValue = (int)(MainSub?.Info?.BeaconStationInfo?.MaxLevelDifficulty ?? 100), IntValue = (int)(MainSub?.Info?.GetExtraSubmarineInfo?.MaxLevelDifficulty ?? 100),
MinValueInt = 0, MinValueInt = 0,
MaxValueInt = 100, MaxValueInt = 100,
OnValueChanged = (numberInput) => OnValueChanged = (numberInput) =>
{ {
MainSub.Info.BeaconStationInfo.MaxLevelDifficulty = numberInput.IntValue; MainSub.Info.GetExtraSubmarineInfo.MaxLevelDifficulty = numberInput.IntValue;
} }
}; };
beaconMaxDifficultyGroup.RectTransform.MaxSize = numInput.TextBox.RectTransform.MaxSize; maxDifficultyGroup.RectTransform.MaxSize = numInput.TextBox.RectTransform.MaxSize;
//---------------------------------------
var beaconSettingsContainer = new GUILayoutGroup(new RectTransform(Vector2.One, extraSettingsContainer.RectTransform))
{
CanBeFocused = true,
Visible = false,
Stretch = true
};
new GUITickBox(new RectTransform(new Vector2(1.0f, 0.25f), beaconSettingsContainer.RectTransform), TextManager.Get("allowdamagedwalls")) new GUITickBox(new RectTransform(new Vector2(1.0f, 0.25f), beaconSettingsContainer.RectTransform), TextManager.Get("allowdamagedwalls"))
{ {
Selected = MainSub?.Info?.BeaconStationInfo?.AllowDamagedWalls ?? true, Selected = MainSub?.Info?.BeaconStationInfo?.AllowDamagedWalls ?? true,
@@ -2672,8 +2688,13 @@ namespace Barotrauma
{ {
MainSub.Info.BeaconStationInfo ??= new BeaconStationInfo(MainSub.Info); MainSub.Info.BeaconStationInfo ??= new BeaconStationInfo(MainSub.Info);
} }
else if (type == SubmarineType.Wreck)
{
MainSub.Info.WreckInfo ??= new WreckInfo(MainSub.Info);
}
previewImageButtonHolder.Children.ForEach(c => c.Enabled = MainSub.Info.AllowPreviewImage); previewImageButtonHolder.Children.ForEach(c => c.Enabled = MainSub.Info.AllowPreviewImage);
outpostSettingsContainer.Visible = type == SubmarineType.OutpostModule; outpostSettingsContainer.Visible = type == SubmarineType.OutpostModule;
extraSettingsContainer.Visible = type == SubmarineType.BeaconStation || type == SubmarineType.Wreck;
beaconSettingsContainer.Visible = type == SubmarineType.BeaconStation; beaconSettingsContainer.Visible = type == SubmarineType.BeaconStation;
subSettingsContainer.Visible = type == SubmarineType.Player; subSettingsContainer.Visible = type == SubmarineType.Player;
return true; return true;
@@ -3921,28 +3942,15 @@ namespace Barotrauma
new ContextMenuOption("editor.cut", isEnabled: hasTargets, onSelected: () => MapEntity.Cut(targets)), new ContextMenuOption("editor.cut", isEnabled: hasTargets, onSelected: () => MapEntity.Cut(targets)),
new ContextMenuOption("editor.copytoclipboard", isEnabled: hasTargets, onSelected: () => MapEntity.Copy(targets)), new ContextMenuOption("editor.copytoclipboard", isEnabled: hasTargets, onSelected: () => MapEntity.Copy(targets)),
new ContextMenuOption("editor.paste", isEnabled: MapEntity.CopiedList.Any(), onSelected: () => MapEntity.Paste(cam.ScreenToWorld(PlayerInput.MousePosition))), new ContextMenuOption("editor.paste", isEnabled: MapEntity.CopiedList.Any(), onSelected: () => MapEntity.Paste(cam.ScreenToWorld(PlayerInput.MousePosition))),
new ContextMenuOption("delete", isEnabled: hasTargets, onSelected: () => RemoveEntitiesWithPossibleWarning(targets)), new ContextMenuOption("delete", isEnabled: hasTargets, onSelected: () =>
new ContextMenuOption(TextManager.Get("editortip.shiftforextraoptions") + '\n' + TextManager.Get("editortip.altforruler"), isEnabled: false, onSelected: null)); {
} StoreCommand(new AddOrDeleteCommand(targets, true));
} foreach (var me in targets)
{
public static void RemoveEntitiesWithPossibleWarning(List<MapEntity> targets) if (!me.Removed) { me.Remove(); }
{ }
if (targets.Any(static t => t is Item it && it.GetComponent<CircuitBox>() is not null)) }),
{ new ContextMenuOption(TextManager.GetWithVariable("editortip.shiftforextraoptions", "[button]", PlayerInput.SecondaryMouseLabel) + '\n' + TextManager.Get("editortip.altforruler"), isEnabled: false, onSelected: null));
GUI.AskForConfirmation(CircuitBoxDeletionWarningHeader, CircuitBoxDeletionWarningBody, onConfirm: Delete);
return;
}
Delete();
void Delete()
{
StoreCommand(new AddOrDeleteCommand(targets, true));
foreach (var me in targets)
{
if (!me.Removed) { me.Remove(); }
}
} }
} }
@@ -4442,6 +4450,7 @@ namespace Barotrauma
MapEntity.SelectEntity(itemContainer); MapEntity.SelectEntity(itemContainer);
dummyCharacter.SelectedItem = itemContainer; dummyCharacter.SelectedItem = itemContainer;
FilterEntities(entityFilterBox.Text); FilterEntities(entityFilterBox.Text);
MapEntity.StopSelection();
} }
/// <summary> /// <summary>
@@ -5472,9 +5481,11 @@ namespace Barotrauma
{ {
foreach (LightComponent lightComponent in item.GetComponents<LightComponent>()) foreach (LightComponent lightComponent in item.GetComponents<LightComponent>())
{ {
lightComponent.Light.Color = item.Container != null || (item.body != null && !item.body.Enabled) ? lightComponent.Light.Color =
Color.Transparent : item.body == null || item.body.Enabled ||
lightComponent.LightColor; (item.ParentInventory is ItemInventory itemInventory && !itemInventory.Container.HideItems) ?
lightComponent.LightColor :
Color.Transparent;
lightComponent.Light.LightSpriteEffect = lightComponent.Item.SpriteEffects; lightComponent.Light.LightSpriteEffect = lightComponent.Item.SpriteEffects;
} }
} }
@@ -5559,11 +5570,32 @@ namespace Barotrauma
dummyCharacter.Submarine = MainSub; dummyCharacter.Submarine = MainSub;
} }
// Deposit item from our "infinite stack" into inventory slots if (dummyCharacter?.SelectedItem != null)
var inv = dummyCharacter?.SelectedItem?.OwnInventory;
if (inv?.visualSlots != null && !PlayerInput.IsCtrlDown())
{ {
var dragginMouse = MouseDragStart != Vector2.Zero && Vector2.Distance(PlayerInput.MousePosition, MouseDragStart) >= GUI.Scale * 20; // Deposit item from our "infinite stack" into inventory slots
TryDragItemsToItem(dummyCharacter.SelectedItem);
foreach (Item linkedItem in dummyCharacter.SelectedItem.linkedTo.OfType<Item>())
{
TryDragItemsToItem(linkedItem);
}
}
void TryDragItemsToItem(Item item)
{
foreach (ItemContainer ic in item.GetComponents<ItemContainer>())
{
if (ic.Inventory?.visualSlots != null)
{
TryDragItemsToInventory(ic.Inventory);
}
}
}
void TryDragItemsToInventory(Inventory inv)
{
if (PlayerInput.IsCtrlDown()) { return; }
var draggingMouse = MouseDragStart != Vector2.Zero && Vector2.Distance(PlayerInput.MousePosition, MouseDragStart) >= GUI.Scale * 20;
// So we don't accidentally drag inventory items while doing this // So we don't accidentally drag inventory items while doing this
if (DraggedItemPrefab != null) { Inventory.DraggingItems.Clear(); } if (DraggedItemPrefab != null) { Inventory.DraggingItems.Clear(); }
@@ -5571,134 +5603,134 @@ namespace Barotrauma
switch (DraggedItemPrefab) switch (DraggedItemPrefab)
{ {
// regular item prefabs // regular item prefabs
case ItemPrefab itemPrefab when PlayerInput.PrimaryMouseButtonClicked() || dragginMouse: case ItemPrefab itemPrefab when PlayerInput.PrimaryMouseButtonClicked() || draggingMouse:
{
bool spawnedItem = false;
for (var i = 0; i < inv.Capacity; i++)
{ {
var slot = inv.visualSlots[i]; bool spawnedItem = false;
var itemContainer = inv.GetItemAt(i)?.GetComponent<ItemContainer>(); for (var i = 0; i < inv.Capacity; i++)
// check if the slot is empty or if we can place the item into a container, for example an oxygen tank into a diving suit
if (Inventory.IsMouseOnSlot(slot))
{ {
var newItem = new Item(itemPrefab, Vector2.Zero, MainSub); var slot = inv.visualSlots[i];
var itemContainer = inv.GetItemAt(i)?.GetComponent<ItemContainer>();
if (inv.CanBePutInSlot(itemPrefab, i, condition: null)) // check if the slot is empty or if we can place the item into a container, for example an oxygen tank into a diving suit
if (Inventory.IsMouseOnSlot(slot))
{ {
bool placedItem = inv.TryPutItem(newItem, i, false, true, dummyCharacter); var newItem = new Item(itemPrefab, Vector2.Zero, MainSub);
spawnedItem |= placedItem;
if (!placedItem) if (inv.CanBePutInSlot(itemPrefab, i, condition: null))
{ {
newItem.Remove(); bool placedItem = inv.TryPutItem(newItem, i, false, true, dummyCharacter);
spawnedItem |= placedItem;
if (!placedItem)
{
newItem.Remove();
}
} }
} else if (itemContainer != null && itemContainer.Inventory.CanBePut(itemPrefab))
else if (itemContainer != null && itemContainer.Inventory.CanBePut(itemPrefab))
{
bool placedItem = itemContainer.Inventory.TryPutItem(newItem, dummyCharacter);
spawnedItem |= placedItem;
// try to place the item into the inventory of the item we are hovering over
if (!placedItem)
{ {
newItem.Remove(); bool placedItem = itemContainer.Inventory.TryPutItem(newItem, dummyCharacter);
spawnedItem |= placedItem;
// try to place the item into the inventory of the item we are hovering over
if (!placedItem)
{
newItem.Remove();
}
else
{
slot.ShowBorderHighlight(GUIStyle.Green, 0.1f, 0.4f);
}
} }
else else
{ {
slot.ShowBorderHighlight(GUIStyle.Green, 0.1f, 0.4f); newItem.Remove();
slot.ShowBorderHighlight(GUIStyle.Red, 0.1f, 0.4f);
}
if (!newItem.Removed)
{
BulkItemBufferInUse = ItemAddMutex;
BulkItemBuffer.Add(new AddOrDeleteCommand(new List<MapEntity> { newItem }, false));
}
if (!draggingMouse)
{
SoundPlayer.PlayUISound(spawnedItem ? GUISoundType.PickItem : GUISoundType.PickItemFail);
} }
} }
else
{
newItem.Remove();
slot.ShowBorderHighlight(GUIStyle.Red, 0.1f, 0.4f);
}
if (!newItem.Removed)
{
BulkItemBufferInUse = ItemAddMutex;
BulkItemBuffer.Add(new AddOrDeleteCommand(new List<MapEntity> { newItem }, false));
}
if (!dragginMouse)
{
SoundPlayer.PlayUISound(spawnedItem ? GUISoundType.PickItem : GUISoundType.PickItemFail);
}
} }
break;
} }
break;
}
// item assemblies // item assemblies
case ItemAssemblyPrefab assemblyPrefab when PlayerInput.PrimaryMouseButtonClicked(): case ItemAssemblyPrefab assemblyPrefab when PlayerInput.PrimaryMouseButtonClicked():
{
bool spawnedItems = false;
for (var i = 0; i < inv.visualSlots.Length; i++)
{ {
var slot = inv.visualSlots[i]; bool spawnedItems = false;
var item = inv?.GetItemAt(i); for (var i = 0; i < inv.visualSlots.Length; i++)
var itemContainer = item?.GetComponent<ItemContainer>();
if (item == null && Inventory.IsMouseOnSlot(slot))
{ {
// load the items var slot = inv.visualSlots[i];
var itemInstance = LoadItemAssemblyInventorySafe(assemblyPrefab); var item = inv?.GetItemAt(i);
var itemContainer = item?.GetComponent<ItemContainer>();
// counter for items that failed so we so we known that slot remained empty if (item == null && Inventory.IsMouseOnSlot(slot))
var failedCount = 0;
for (var j = 0; j < itemInstance.Count(); j++)
{ {
var newItem = itemInstance[j]; // load the items
var newSpot = i + j - failedCount; var itemInstance = LoadItemAssemblyInventorySafe(assemblyPrefab);
// try to find a valid slot to put the items // counter for items that failed so we so we known that slot remained empty
while (inv.visualSlots.Length > newSpot) var failedCount = 0;
for (var j = 0; j < itemInstance.Count; j++)
{ {
if (inv.GetItemAt(newSpot) == null) { break; } var newItem = itemInstance[j];
newSpot++; var newSpot = i + j - failedCount;
}
// valid slot found // try to find a valid slot to put the items
if (inv.visualSlots.Length > newSpot) while (inv.visualSlots.Length > newSpot)
{
var placedItem = inv.TryPutItem(newItem, newSpot, false, true, dummyCharacter);
spawnedItems |= placedItem;
if (!placedItem)
{ {
failedCount++; if (inv.GetItemAt(newSpot) == null) { break; }
// delete the included items too so we don't get a popup asking if we want to keep them newSpot++;
newItem?.OwnInventory?.DeleteAllItems(); }
newItem.Remove();
// valid slot found
if (inv.visualSlots.Length > newSpot)
{
var placedItem = inv.TryPutItem(newItem, newSpot, false, true, dummyCharacter);
spawnedItems |= placedItem;
if (!placedItem)
{
failedCount++;
// delete the included items too so we don't get a popup asking if we want to keep them
newItem?.OwnInventory?.DeleteAllItems();
newItem.Remove();
}
}
else
{
var placedItem = inv.TryPutItem(newItem, dummyCharacter);
spawnedItems |= placedItem;
// if our while loop didn't find a valid slot then let the inventory decide where to put it as a last resort
if (!placedItem)
{
// delete the included items too so we don't get a popup asking if we want to keep them
newItem?.OwnInventory?.DeleteAllItems();
newItem.Remove();
}
} }
} }
else
List<MapEntity> placedEntities = itemInstance.Where(it => !it.Removed).Cast<MapEntity>().ToList();
if (placedEntities.Any())
{ {
var placedItem = inv.TryPutItem(newItem, dummyCharacter); BulkItemBufferInUse = ItemAddMutex;
spawnedItems |= placedItem; BulkItemBuffer.Add(new AddOrDeleteCommand(placedEntities, false));
// if our while loop didn't find a valid slot then let the inventory decide where to put it as a last resort
if (!placedItem)
{
// delete the included items too so we don't get a popup asking if we want to keep them
newItem?.OwnInventory?.DeleteAllItems();
newItem.Remove();
}
} }
} }
List<MapEntity> placedEntities = itemInstance.Where(it => !it.Removed).Cast<MapEntity>().ToList();
if (placedEntities.Any())
{
BulkItemBufferInUse = ItemAddMutex;
BulkItemBuffer.Add(new AddOrDeleteCommand(placedEntities, false));
}
} }
}
SoundPlayer.PlayUISound(spawnedItems ? GUISoundType.PickItem : GUISoundType.PickItemFail); SoundPlayer.PlayUISound(spawnedItems ? GUISoundType.PickItem : GUISoundType.PickItemFail);
break; break;
} }
} }
} }
@@ -571,16 +571,37 @@ namespace Barotrauma
numberInput.MinValueFloat = editableAttribute.MinValueFloat; numberInput.MinValueFloat = editableAttribute.MinValueFloat;
numberInput.MaxValueFloat = editableAttribute.MaxValueFloat; numberInput.MaxValueFloat = editableAttribute.MaxValueFloat;
numberInput.DecimalsToDisplay = editableAttribute.DecimalCount; numberInput.DecimalsToDisplay = editableAttribute.DecimalCount;
numberInput.valueStep = editableAttribute.ValueStep; numberInput.ValueStep = editableAttribute.ValueStep;
numberInput.ForceShowPlusMinusButtons = editableAttribute.ForceShowPlusMinusButtons;
numberInput.FloatValue = value; numberInput.FloatValue = value;
numberInput.OnValueChanged += (numInput) => numberInput.OnValueChanged += numInput =>
{ {
if (SetPropertyValue(property, entity, numInput.FloatValue)) if (SetPropertyValue(property, entity, numInput.FloatValue))
{ {
TrySendNetworkUpdate(entity, property); TrySendNetworkUpdate(entity, property);
} }
}; };
// Lots of UI boilerplate to handle all(?) cases where the property's setter may be called
// and modify the input value (e.g. rotation value wrapping)
void HandleSetterModifyingInput(GUINumberInput numInput)
{
var inputFloatValue = numInput.FloatValue;
var resultingFloatValue = property.GetFloatValue(entity);
if (!MathUtils.NearlyEqual(resultingFloatValue, inputFloatValue))
{
numInput.FloatValue = resultingFloatValue;
}
}
bool HandleSetterModifyingInputOnButtonPressed() { HandleSetterModifyingInput(numberInput); return true; }
bool HandleSetterModifyingInputOnButtonClicked(GUIButton _, object __) { HandleSetterModifyingInput(numberInput); return true; }
numberInput.OnValueEntered += HandleSetterModifyingInput;
numberInput.PlusButton.OnPressed += HandleSetterModifyingInputOnButtonPressed;
numberInput.PlusButton.OnClicked += HandleSetterModifyingInputOnButtonClicked;
numberInput.MinusButton.OnPressed += HandleSetterModifyingInputOnButtonPressed;
numberInput.MinusButton.OnClicked += HandleSetterModifyingInputOnButtonClicked;
refresh += () => refresh += () =>
{ {
if (!numberInput.TextBox.Selected) { numberInput.FloatValue = (float)property.GetValue(entity); } if (!numberInput.TextBox.Selected) { numberInput.FloatValue = (float)property.GetValue(entity); }
@@ -859,7 +880,7 @@ namespace Barotrauma
numberInput.MinValueFloat = editableAttribute.MinValueFloat; numberInput.MinValueFloat = editableAttribute.MinValueFloat;
numberInput.MaxValueFloat = editableAttribute.MaxValueFloat; numberInput.MaxValueFloat = editableAttribute.MaxValueFloat;
numberInput.DecimalsToDisplay = editableAttribute.DecimalCount; numberInput.DecimalsToDisplay = editableAttribute.DecimalCount;
numberInput.valueStep = editableAttribute.ValueStep; numberInput.ValueStep = editableAttribute.ValueStep;
if (i == 0) if (i == 0)
numberInput.FloatValue = value.X; numberInput.FloatValue = value.X;
@@ -930,7 +951,7 @@ namespace Barotrauma
numberInput.MinValueFloat = editableAttribute.MinValueFloat; numberInput.MinValueFloat = editableAttribute.MinValueFloat;
numberInput.MaxValueFloat = editableAttribute.MaxValueFloat; numberInput.MaxValueFloat = editableAttribute.MaxValueFloat;
numberInput.DecimalsToDisplay = editableAttribute.DecimalCount; numberInput.DecimalsToDisplay = editableAttribute.DecimalCount;
numberInput.valueStep = editableAttribute.ValueStep; numberInput.ValueStep = editableAttribute.ValueStep;
if (i == 0) if (i == 0)
numberInput.FloatValue = value.X; numberInput.FloatValue = value.X;
@@ -1006,7 +1027,7 @@ namespace Barotrauma
numberInput.MinValueFloat = editableAttribute.MinValueFloat; numberInput.MinValueFloat = editableAttribute.MinValueFloat;
numberInput.MaxValueFloat = editableAttribute.MaxValueFloat; numberInput.MaxValueFloat = editableAttribute.MaxValueFloat;
numberInput.DecimalsToDisplay = editableAttribute.DecimalCount; numberInput.DecimalsToDisplay = editableAttribute.DecimalCount;
numberInput.valueStep = editableAttribute.ValueStep; numberInput.ValueStep = editableAttribute.ValueStep;
if (i == 0) if (i == 0)
numberInput.FloatValue = value.X; numberInput.FloatValue = value.X;
@@ -727,7 +727,21 @@ namespace Barotrauma
Slider(layout, (0.75f, 1.25f), 51, Percentage, unsavedConfig.Graphics.InventoryScale, v => unsavedConfig.Graphics.InventoryScale = v); Slider(layout, (0.75f, 1.25f), 51, Percentage, unsavedConfig.Graphics.InventoryScale, v => unsavedConfig.Graphics.InventoryScale = v);
Label(layout, TextManager.Get("TextScale"), GUIStyle.SubHeadingFont); Label(layout, TextManager.Get("TextScale"), GUIStyle.SubHeadingFont);
Slider(layout, (0.75f, 1.25f), 51, Percentage, unsavedConfig.Graphics.TextScale, v => unsavedConfig.Graphics.TextScale = v); Slider(layout, (0.75f, 1.25f), 51, Percentage, unsavedConfig.Graphics.TextScale, v => unsavedConfig.Graphics.TextScale = v);
Spacer(layout);
var resetSpamListFilter =
new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), layout.RectTransform),
TextManager.Get("clearserverlistfilters"), style: "GUIButtonSmall")
{
OnClicked = static (_, _) =>
{
GUI.AskForConfirmation(
header: TextManager.Get("clearserverlistfilters"),
body: TextManager.Get("clearserverlistfiltersconfirmation"),
onConfirm: SpamServerFilters.ClearLocalSpamFilter);
return true;
}
};
Spacer(layout);
#if !OSX #if !OSX
Spacer(layout); Spacer(layout);
var statisticsTickBox = new GUITickBox(NewItemRectT(layout), TextManager.Get("statisticsconsenttickbox")) var statisticsTickBox = new GUITickBox(NewItemRectT(layout), TextManager.Get("statisticsconsenttickbox"))
@@ -1,22 +1,23 @@
using System; using NVorbis;
using OpenAL; using OpenAL;
using NVorbis; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Xml.Linq;
namespace Barotrauma.Sounds namespace Barotrauma.Sounds
{ {
sealed class OggSound : Sound sealed class OggSound : Sound
{ {
private VorbisReader streamReader; private readonly VorbisReader streamReader;
public long MaxStreamSamplePos => streamReader == null ? 0 : streamReader.TotalSamples * streamReader.Channels * 2;
private List<float> playbackAmplitude; private List<float> playbackAmplitude;
private const int AMPLITUDE_SAMPLE_COUNT = 4410; //100ms in a 44100hz file private const int AMPLITUDE_SAMPLE_COUNT = 4410; //100ms in a 44100hz file
private short[] sampleBuffer = Array.Empty<short>(); private short[] sampleBuffer = Array.Empty<short>();
private short[] muffleBuffer = Array.Empty<short>(); private short[] muffleBuffer = Array.Empty<short>();
public OggSound(SoundManager owner, string filename, bool stream, XElement xElement) : base(owner, filename, public OggSound(SoundManager owner, string filename, bool stream, ContentXElement xElement) : base(owner, filename,
stream, true, xElement) stream, true, xElement)
{ {
var reader = new VorbisReader(Filename); var reader = new VorbisReader(Filename);
@@ -101,7 +102,7 @@ namespace Barotrauma.Sounds
if (!Stream) { throw new Exception("Called FillStreamBuffer on a non-streamed sound!"); } if (!Stream) { throw new Exception("Called FillStreamBuffer on a non-streamed sound!"); }
if (streamReader == null) { throw new Exception("Called FillStreamBuffer when the reader is null!"); } if (streamReader == null) { throw new Exception("Called FillStreamBuffer when the reader is null!"); }
if (samplePos >= streamReader.TotalSamples * streamReader.Channels * 2) return 0; if (samplePos >= MaxStreamSamplePos) { return 0; }
samplePos /= streamReader.Channels * 2; samplePos /= streamReader.Channels * 2;
streamReader.DecodedPosition = samplePos; streamReader.DecodedPosition = samplePos;
@@ -18,7 +18,7 @@ namespace Barotrauma.Sounds
public readonly string Filename; public readonly string Filename;
public readonly XElement XElement; public readonly ContentXElement XElement;
public readonly bool Stream; public readonly bool Stream;
@@ -60,14 +60,14 @@ namespace Barotrauma.Sounds
public float BaseNear; public float BaseNear;
public float BaseFar; public float BaseFar;
public Sound(SoundManager owner, string filename, bool stream, bool streamsReliably, XElement xElement = null, bool getFullPath = true) public Sound(SoundManager owner, string filename, bool stream, bool streamsReliably, ContentXElement xElement = null, bool getFullPath = true)
{ {
Owner = owner; Owner = owner;
Filename = getFullPath ? Path.GetFullPath(filename.CleanUpPath()).CleanUpPath() : filename; Filename = getFullPath ? Path.GetFullPath(filename.CleanUpPath()).CleanUpPath() : filename;
Stream = stream; Stream = stream;
StreamsReliably = streamsReliably; StreamsReliably = streamsReliably;
XElement = xElement; XElement = xElement;
sourcePoolIndex = XElement.GetAttributeEnum("sourcepool", SoundManager.SourcePoolIndex.Default); sourcePoolIndex = XElement?.GetAttributeEnum("sourcepool", SoundManager.SourcePoolIndex.Default) ?? SoundManager.SourcePoolIndex.Default;
BaseGain = 1.0f; BaseGain = 1.0f;
BaseNear = 100.0f; BaseNear = 100.0f;
@@ -444,6 +444,18 @@ namespace Barotrauma.Sounds
} }
} }
public long MaxStreamSeekPos
{
get
{
if (!IsStream || Sound is not OggSound oggSound)
{
return 0;
}
return oggSound.MaxStreamSamplePos;
}
}
private readonly object mutex; private readonly object mutex;
public bool IsPlaying public bool IsPlaying
@@ -564,7 +576,7 @@ namespace Barotrauma.Sounds
throw new Exception("Generated streamBuffer[" + i.ToString() + "] is invalid! " + debugName); throw new Exception("Generated streamBuffer[" + i.ToString() + "] is invalid! " + debugName);
} }
} }
Sound.Owner.InitStreamThread(); Sound.Owner.InitUpdateChannelThread();
SetProperties(); SetProperties();
} }
} }
@@ -609,6 +621,7 @@ namespace Barotrauma.Sounds
public void FadeOutAndDispose() public void FadeOutAndDispose()
{ {
FadingOutAndDisposing = true; FadingOutAndDisposing = true;
Sound.Owner.InitUpdateChannelThread();
} }
public void Dispose() public void Dispose()
@@ -39,7 +39,7 @@ namespace Barotrauma.Sounds
public bool Disconnected { get; private set; } public bool Disconnected { get; private set; }
private Thread streamingThread; private Thread updateChannelsThread;
private Vector3 listenerPosition; private Vector3 listenerPosition;
public Vector3 ListenerPosition public Vector3 ListenerPosition
@@ -201,7 +201,7 @@ namespace Barotrauma.Sounds
public SoundManager() public SoundManager()
{ {
loadedSounds = new List<Sound>(); loadedSounds = new List<Sound>();
streamingThread = null; updateChannelsThread = null;
sourcePools = new SoundSourcePool[2]; sourcePools = new SoundSourcePool[2];
playingChannels[(int)SourcePoolIndex.Default] = new SoundChannel[SOURCE_COUNT]; playingChannels[(int)SourcePoolIndex.Default] = new SoundChannel[SOURCE_COUNT];
@@ -696,7 +696,7 @@ namespace Barotrauma.Sounds
CompressionDynamicRangeGain = 1.0f; CompressionDynamicRangeGain = 1.0f;
} }
if (streamingThread == null || streamingThread.ThreadState.HasFlag(ThreadState.Stopped)) if (updateChannelsThread == null || updateChannelsThread.ThreadState.HasFlag(ThreadState.Stopped))
{ {
bool startedStreamThread = false; bool startedStreamThread = false;
for (int i = 0; i < playingChannels.Length; i++) for (int i = 0; i < playingChannels.Length; i++)
@@ -708,7 +708,7 @@ namespace Barotrauma.Sounds
if (playingChannels[i][j] == null) { continue; } if (playingChannels[i][j] == null) { continue; }
if (playingChannels[i][j].IsStream && playingChannels[i][j].IsPlaying) if (playingChannels[i][j].IsStream && playingChannels[i][j].IsPlaying)
{ {
InitStreamThread(); InitUpdateChannelThread();
startedStreamThread = true; startedStreamThread = true;
} }
if (startedStreamThread) { break; } if (startedStreamThread) { break; }
@@ -728,36 +728,42 @@ namespace Barotrauma.Sounds
SetCategoryGainMultiplier("voip", Math.Min(GameSettings.CurrentConfig.Audio.VoiceChatVolume, 1.0f), 0); SetCategoryGainMultiplier("voip", Math.Min(GameSettings.CurrentConfig.Audio.VoiceChatVolume, 1.0f), 0);
} }
public void InitStreamThread() /// <summary>
/// Initializes the thread that handles streaming audio and fading out and disposing channels that are no longer needed.
/// </summary>
public void InitUpdateChannelThread()
{ {
if (Disabled) { return; } if (Disabled) { return; }
bool isStreamThreadDying; bool isUpdateChannelsThreadDying;
lock (threadDeathMutex) lock (threadDeathMutex)
{ {
isStreamThreadDying = !areStreamsPlaying; isUpdateChannelsThreadDying = !needsUpdateChannels;
} }
if (streamingThread == null || streamingThread.ThreadState.HasFlag(ThreadState.Stopped) || isStreamThreadDying) if (updateChannelsThread == null || updateChannelsThread.ThreadState.HasFlag(ThreadState.Stopped) || isUpdateChannelsThreadDying)
{ {
if (streamingThread != null && !streamingThread.Join(1000)) if (updateChannelsThread != null && !updateChannelsThread.Join(1000))
{ {
DebugConsole.ThrowError("Sound stream thread join timed out!"); DebugConsole.ThrowError("SoundManager.UpdateChannels thread join timed out!");
} }
areStreamsPlaying = true; needsUpdateChannels = true;
streamingThread = new Thread(UpdateStreaming) updateChannelsThread = new Thread(UpdateChannels)
{ {
Name = "SoundManager Streaming Thread", Name = "SoundManager.UpdateChannels Thread",
IsBackground = true //this should kill the thread if the game crashes IsBackground = true //this should kill the thread if the game crashes
}; };
streamingThread.Start(); updateChannelsThread.Start();
} }
} }
bool areStreamsPlaying = false; private bool needsUpdateChannels = false;
ManualResetEvent streamMre = null; private ManualResetEvent updateChannelsMre = null;
void UpdateStreaming() /// <summary>
/// Handles streaming audio and fading out and disposing channels that are no longer needed.
/// </summary>
private void UpdateChannels()
{ {
streamMre = new ManualResetEvent(false); updateChannelsMre = new ManualResetEvent(false);
bool killThread = false; bool killThread = false;
while (!killThread) while (!killThread)
{ {
@@ -784,6 +790,7 @@ namespace Barotrauma.Sounds
} }
else if (playingChannels[i][j].FadingOutAndDisposing) else if (playingChannels[i][j].FadingOutAndDisposing)
{ {
killThread = false;
playingChannels[i][j].Gain -= 0.1f; playingChannels[i][j].Gain -= 0.1f;
if (playingChannels[i][j].Gain <= 0.0f) if (playingChannels[i][j].Gain <= 0.0f)
{ {
@@ -794,18 +801,18 @@ namespace Barotrauma.Sounds
} }
} }
} }
streamMre.WaitOne(10); updateChannelsMre.WaitOne(10);
streamMre.Reset(); updateChannelsMre.Reset();
lock (threadDeathMutex) lock (threadDeathMutex)
{ {
areStreamsPlaying = !killThread; needsUpdateChannels = !killThread;
} }
} }
} }
public void ForceStreamUpdate() public void ForceStreamUpdate()
{ {
streamMre?.Set(); updateChannelsMre?.Set();
} }
private void ReloadSounds() private void ReloadSounds()
@@ -824,12 +831,12 @@ namespace Barotrauma.Sounds
{ {
for (int j = 0; j < playingChannels[i].Length; j++) for (int j = 0; j < playingChannels[i].Length; j++)
{ {
if (playingChannels[i][j] != null) playingChannels[i][j].Dispose(); playingChannels[i][j]?.Dispose();
} }
} }
} }
streamingThread?.Join(); updateChannelsThread?.Join();
for (int i = loadedSounds.Count - 1; i >= 0; i--) for (int i = loadedSounds.Count - 1; i >= 0; i--)
{ {
if (keepSounds) if (keepSounds)
@@ -709,6 +709,11 @@ namespace Barotrauma
{ {
musicChannel[i].StreamSeekPos = targetMusic[i].PreviousTime; musicChannel[i].StreamSeekPos = targetMusic[i].PreviousTime;
} }
else if (targetMusic[i].StartFromRandomTime)
{
musicChannel[i].StreamSeekPos =
(int)(musicChannel[i].MaxStreamSeekPos * Rand.Range(0.0f, 1.0f, Rand.RandSync.Unsynced));
}
musicChannel[i].Looping = true; musicChannel[i].Looping = true;
} }
} }
@@ -241,6 +241,7 @@ namespace Barotrauma
public readonly bool MuteIntensityTracks; public readonly bool MuteIntensityTracks;
public readonly float? ForceIntensityTrack; public readonly float? ForceIntensityTrack;
public readonly bool StartFromRandomTime;
public readonly bool ContinueFromPreviousTime; public readonly bool ContinueFromPreviousTime;
public int PreviousTime; public int PreviousTime;
@@ -255,6 +256,7 @@ namespace Barotrauma
ForceIntensityTrack = element.GetAttributeFloat(nameof(ForceIntensityTrack), 0.0f); ForceIntensityTrack = element.GetAttributeFloat(nameof(ForceIntensityTrack), 0.0f);
} }
Volume = element.GetAttributeFloat(nameof(Volume), 1.0f); Volume = element.GetAttributeFloat(nameof(Volume), 1.0f);
StartFromRandomTime = element.GetAttributeBool(nameof(StartFromRandomTime), false);
ContinueFromPreviousTime = element.GetAttributeBool(nameof(ContinueFromPreviousTime), false); ContinueFromPreviousTime = element.GetAttributeBool(nameof(ContinueFromPreviousTime), false);
} }
} }
@@ -0,0 +1,330 @@
#nullable enable
using System;
using System.Collections.Immutable;
using System.Linq;
using System.Net;
using System.Net.Cache;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Linq;
using Barotrauma.IO;
using Barotrauma.Networking;
using RestSharp;
using XmlWriter = Barotrauma.IO.XmlWriter;
namespace Barotrauma
{
public enum SpamServerFilterType
{
Invalid,
NameEquals,
NameContains,
MessageEquals,
MessageContains,
PlayerCountLarger,
PlayerCountExact,
MaxPlayersLarger,
MaxPlayersExact,
GameModeEquals,
PlayStyleEquals,
Endpoint,
LanguageEquals
}
internal readonly record struct SpamFilter(ImmutableHashSet<(SpamServerFilterType Type, string Value)> Filters)
{
public bool IsFiltered(ServerInfo info)
{
if (!Filters.Any()) { return false; }
foreach (var (type, value) in Filters)
{
if (!IsFiltered(info, type, value)) { return false; }
}
return true;
}
private static bool IsFiltered(ServerInfo info, SpamServerFilterType type, string value)
{
string desc = info.ServerMessage,
name = info.ServerName;
int.TryParse(value, out int parsedInt);
return type switch
{
SpamServerFilterType.NameEquals => CompareEquals(name, value),
SpamServerFilterType.NameContains => CompareContains(name, value),
SpamServerFilterType.MessageEquals => CompareEquals(desc, value),
SpamServerFilterType.MessageContains => CompareContains(desc, value),
SpamServerFilterType.Endpoint => info.Endpoint.StringRepresentation.Equals(value, StringComparison.OrdinalIgnoreCase),
SpamServerFilterType.PlayerCountLarger => info.PlayerCount > parsedInt,
SpamServerFilterType.PlayerCountExact => info.PlayerCount == parsedInt,
SpamServerFilterType.MaxPlayersLarger => info.MaxPlayers > parsedInt,
SpamServerFilterType.MaxPlayersExact => info.MaxPlayers == parsedInt,
SpamServerFilterType.GameModeEquals => info.GameMode == value,
SpamServerFilterType.PlayStyleEquals => info.PlayStyle.ToIdentifier() == value,
SpamServerFilterType.LanguageEquals => info.Language.Value == value,
_ => false
};
static bool CompareEquals(string a, string b)
=> a.Equals(b, StringComparison.OrdinalIgnoreCase) || Homoglyphs.Compare(a, b);
static bool CompareContains(string a, string b)
=> a.Contains(b, StringComparison.OrdinalIgnoreCase);
}
public XElement Serialize()
{
var element = new XElement("Filter");
foreach (var (type, value) in Filters)
{
element.Add(new XAttribute(type.ToString().ToLowerInvariant(), value));
}
return element;
}
public static bool TryParse(XElement element, out SpamFilter filter)
{
var builder = ImmutableHashSet.CreateBuilder<(SpamServerFilterType Type, string Value)>();
foreach (var attribute in element.Attributes())
{
if (!Enum.TryParse(attribute.Name.ToString(), ignoreCase: true, out SpamServerFilterType e))
{
DebugConsole.ThrowError($"Failed to parse spam filter attribute \"{attribute.Name}\"");
continue;
}
if (e is SpamServerFilterType.Invalid) { continue; }
builder.Add((e, attribute.Value));
}
if (builder.Any())
{
filter = new SpamFilter(builder.ToImmutable());
return true;
}
filter = default;
return false;
}
public override string ToString()
{
return !Filters.Any() ? "Invalid Filter" : string.Join(", ", Filters.Select(static f => $"{f.Type}: {f.Value}"));
}
}
internal sealed class SpamServerFilter
{
public readonly ImmutableArray<SpamFilter> Filters;
public bool IsFiltered(ServerInfo info)
{
foreach (var f in Filters)
{
if (f.IsFiltered(info)) { return true; }
}
return false;
}
public SpamServerFilter(XElement element)
{
var builder = ImmutableArray.CreateBuilder<SpamFilter>();
foreach (var subElement in element.Elements())
{
if (SpamFilter.TryParse(subElement, out var filter))
{
builder.Add(filter);
}
}
Filters = builder.ToImmutable();
}
public SpamServerFilter(ImmutableArray<SpamFilter> filters)
=> Filters = filters;
public readonly static string SavePath = Path.Combine("Data", "serverblacklist.xml");
public void Save(string path)
{
var comment = new XComment(SpamServerFilters.LocalFilterComment);
var doc = new XDocument(comment, new XElement("Filters"));
foreach (var filter in Filters)
{
doc.Root?.Add(filter.Serialize());
}
try
{
using var writer = XmlWriter.Create(path, new XmlWriterSettings { Indent = true });
doc.SaveSafe(writer);
}
catch (Exception e)
{
DebugConsole.ThrowError("Saving spam filter failed.", e);
}
}
}
internal static class SpamServerFilters
{
public static Option<SpamServerFilter> LocalSpamFilter;
public static Option<SpamServerFilter> GlobalSpamFilter;
public const string LocalFilterComment = @"
This file contains a list of filters that can be used to hide servers from the server list.
You can add filters by right-clicking a server in the server list and selecting ""Hide server"" or by reporting the server and choosing ""Report and hide server"".
The filters are saved in this file, which you can edit manually if you want to.
The available filter types are:
- NameEquals: The server name must equal the specified value. Homoglyphs are also checked.
- NameContains: The server name must contain the specified value.
- MessageEquals: The server description must equal the specified value. Homoglyphs are also checked.
- MessageContains: The server description must contain the specified value.
- PlayerCountLarger: The player count must be larger than the specified value.
- PlayerCountExact: The player count must match the specified value exactly.
- MaxPlayersLarger: The max player count must be larger than the specified value.
- MaxPlayersExact: The max player count must match the specified value exactly.
- GameModeEquals: The game mode identifier must match the specified value exactly.
- PlayStyleEquals: The play style must match the specified value exactly.
- Endpoint: The server endpoint, which is a Steam ID or an IP address, must match the specified value exactly. Steam ID is in the format of STEAM_X:Y:Z.
- LanguageEquals: The server language must match the specified value exactly.
The filter values are case-insensitive and adding multiple conditions on one filter will require all of them to be met.
Homoglyph comparison is used for NameEquals and MessageEquals filters, which means that it checks whether the words look the same, meaning you can't abuse identical-looking but different symbols to work around the filter. For example ""lmaobox"" and ""lmаobox"" (with a cyrillic a) are considered equal.
Examples:
<Filters>
<Filter namecontains=""discord.gg"" />
<Filter messagecontains=""discord.gg"" />
<Filter nameequals=""get good get lmaobox"" maxplayersexact=""999"" />
</Filters>
These will hide all servers that have a discord.gg link in their name or description and servers with the name ""get good get lmaobox"" that have 999 max players.
";
static SpamServerFilters()
{
XDocument? doc;
if (!File.Exists(SpamServerFilter.SavePath))
{
var comment = new XComment(LocalFilterComment);
doc = new XDocument(comment, new XElement("Filters"));
try
{
using var writer = XmlWriter.Create(SpamServerFilter.SavePath, new XmlWriterSettings { Indent = true });
doc.SaveSafe(writer);
}
catch (Exception e)
{
DebugConsole.ThrowError("Saving spam filter failed.", e);
}
}
else
{
doc = XMLExtensions.TryLoadXml(SpamServerFilter.SavePath);
}
if (doc?.Root is { } root)
{
LocalSpamFilter = Option.Some(new SpamServerFilter(root));
}
}
public static bool IsFiltered(ServerInfo info)
{
if (LocalSpamFilter.TryUnwrap(out var localFilter) && localFilter.IsFiltered(info)) { return true; }
if (GlobalSpamFilter.TryUnwrap(out var globalFilter) && globalFilter.IsFiltered(info)) { return true; }
return false;
}
public static void AddServerToLocalSpamList(ServerInfo info)
{
if (!LocalSpamFilter.TryUnwrap(out var localFilter)) { return; }
if (localFilter.IsFiltered(info)) { return; }
var filters = localFilter.Filters.Add(new SpamFilter(ImmutableHashSet.Create((NameExact: SpamServerFilterType.NameEquals, info.ServerName))));
var newFilter = new SpamServerFilter(filters);
newFilter.Save(SpamServerFilter.SavePath);
LocalSpamFilter = Option.Some(newFilter);
}
public static void ClearLocalSpamFilter()
{
var newFilter = new SpamServerFilter(ImmutableArray<SpamFilter>.Empty);
newFilter.Save(SpamServerFilter.SavePath);
LocalSpamFilter = Option.Some(newFilter);
}
public static void RequestGlobalSpamFilter()
{
if (GameSettings.CurrentConfig.DisableGlobalSpamList) { return; }
string remoteContentUrl = GameSettings.CurrentConfig.RemoteMainMenuContentUrl;
if (string.IsNullOrEmpty(remoteContentUrl)) { return; }
try
{
var client = new RestClient($"{remoteContentUrl}spamfilter")
{
CachePolicy = new HttpRequestCachePolicy(HttpRequestCacheLevel.NoCacheNoStore)
};
client.AddDefaultHeader("Cache-Control", "no-cache");
client.AddDefaultHeader("Pragma", "no-cache");
var request = new RestRequest("serve_spamlist.php", Method.GET);
TaskPool.Add("RequestGlobalSpamFilter", client.ExecuteAsync(request), RemoteContentReceived);
}
catch (Exception e)
{
#if DEBUG
DebugConsole.ThrowError("Fetching global spam list failed.", e);
#endif
GameAnalyticsManager.AddErrorEventOnce("SpamServerFilters.RequestGlobalSpamFilter:Exception", GameAnalyticsManager.ErrorSeverity.Error,
"Fetching global spam list failed. " + e.Message);
}
static void RemoteContentReceived(Task t)
{
try
{
if (!t.TryGetResult(out IRestResponse remoteContentResponse)) { throw new Exception("Task did not return a valid result"); }
if (remoteContentResponse.StatusCode != HttpStatusCode.OK)
{
DebugConsole.AddWarning(
"Failed to receive global spam filter." +
"There may be an issue with your internet connection, or the master server might be temporarily unavailable " +
$"(error code: {remoteContentResponse.StatusCode})");
return;
}
string data = remoteContentResponse.Content;
if (string.IsNullOrWhiteSpace(data)) { return; }
if (XDocument.Parse(data).Root is { } root)
{
GlobalSpamFilter = Option.Some(new SpamServerFilter(root));
}
}
catch (Exception e)
{
#if DEBUG
DebugConsole.ThrowError("Reading received global spam filter failed.", e);
#endif
GameAnalyticsManager.AddErrorEventOnce("SpamServerFilters.RemoteContentReceived:Exception", GameAnalyticsManager.ErrorSeverity.Error,
"Reading received global spam filter failed. " + e.Message);
}
}
}
}
}
@@ -111,7 +111,7 @@ namespace Barotrauma
partial void LoadTexture(ref Vector4 sourceVector, ref bool shouldReturn) partial void LoadTexture(ref Vector4 sourceVector, ref bool shouldReturn)
{ {
texture = LoadTexture(FilePath.Value, Compress); texture = LoadTexture(FilePath.Value, Compress, contentPackage: SourceElement?.ContentPackage);
if (texture == null) if (texture == null)
{ {
@@ -175,7 +175,7 @@ namespace Barotrauma
return; return;
} }
texture.Dispose(); texture.Dispose();
texture = TextureLoader.FromFile(FilePath.Value, Compress); texture = TextureLoader.FromFile(FilePath.Value, Compress, contentPackage: SourceElement?.ContentPackage);
Identifier pathKey = FullPath.ToIdentifier(); Identifier pathKey = FullPath.ToIdentifier();
if (textureRefCounts.ContainsKey(pathKey)) if (textureRefCounts.ContainsKey(pathKey))
{ {
@@ -195,7 +195,7 @@ namespace Barotrauma
sourceRect = new Rectangle(0, 0, texture.Width, texture.Height); sourceRect = new Rectangle(0, 0, texture.Width, texture.Height);
} }
public static Texture2D LoadTexture(string file, bool compress = true) public static Texture2D LoadTexture(string file, bool compress = true, ContentPackage contentPackage = null)
{ {
if (string.IsNullOrWhiteSpace(file)) if (string.IsNullOrWhiteSpace(file))
{ {
@@ -221,11 +221,11 @@ namespace Barotrauma
if (!ToolBox.IsProperFilenameCase(file)) if (!ToolBox.IsProperFilenameCase(file))
{ {
#if DEBUG #if DEBUG
DebugConsole.ThrowError("Texture file \"" + file + "\" has incorrect case!"); DebugConsole.ThrowError("Texture file \"" + file + "\" has incorrect case!", contentPackage: contentPackage);
#endif #endif
} }
Texture2D newTexture = TextureLoader.FromFile(file, compress); Texture2D newTexture = TextureLoader.FromFile(file, compress, contentPackage: contentPackage);
lock (list) lock (list)
{ {
if (!textureRefCounts.TryAdd(fullPath, if (!textureRefCounts.TryAdd(fullPath,
@@ -284,17 +284,44 @@ namespace Barotrauma
} }
} }
public void DrawTiled(ISpriteBatch spriteBatch, Vector2 position, Vector2 targetSize, public void DrawTiled(ISpriteBatch spriteBatch,
Color? color = null, Vector2? startOffset = null, Vector2? textureScale = null, float? depth = null) Vector2 position,
Vector2 targetSize,
float rotation = 0f,
Vector2? origin = null,
Color? color = null,
Vector2? startOffset = null,
Vector2? textureScale = null,
float? depth = null,
SpriteEffects? spriteEffects = null)
{ {
if (Texture == null) { return; } if (Texture == null) { return; }
spriteEffects ??= effects;
bool flipHorizontal = (spriteEffects.Value & SpriteEffects.FlipHorizontally) != 0;
bool flipVertical = (spriteEffects.Value & SpriteEffects.FlipVertically) != 0;
float addedRotation = rotation + this.rotation;
if (flipHorizontal != flipVertical) { addedRotation = -addedRotation; }
Vector2 advanceX = addedRotation == 0.0f ? Vector2.UnitX : new Vector2((float)Math.Cos(addedRotation), (float)Math.Sin(addedRotation));
Vector2 advanceY = new Vector2(-advanceX.Y, advanceX.X);
//Init optional values //Init optional values
Vector2 drawOffset = startOffset ?? Vector2.Zero; Vector2 drawOffset = startOffset ?? Vector2.Zero;
Vector2 scale = textureScale ?? Vector2.One; Vector2 scale = textureScale ?? Vector2.One;
Color drawColor = color ?? Color.White; Color drawColor = color ?? Color.White;
Vector2 transformedOrigin = origin ?? Vector2.Zero;
bool flipHorizontal = (effects & SpriteEffects.FlipHorizontally) != 0; transformedOrigin = advanceX * transformedOrigin.X + advanceY * transformedOrigin.Y;
bool flipVertical = (effects & SpriteEffects.FlipVertically) != 0;
void drawSection(Vector2 slicePos, Rectangle sliceRect)
{
Vector2 transformedPos = slicePos - position;
transformedPos = advanceX * transformedPos.X + advanceY * transformedPos.Y;
transformedPos += position - transformedOrigin;
spriteBatch.Draw(texture, transformedPos, sliceRect, drawColor, addedRotation, Vector2.Zero, scale, spriteEffects.Value, depth ?? this.depth);
}
//wrap the drawOffset inside the sourceRect //wrap the drawOffset inside the sourceRect
drawOffset.X = (drawOffset.X / scale.X) % sourceRect.Width; drawOffset.X = (drawOffset.X / scale.X) % sourceRect.Width;
@@ -369,7 +396,7 @@ namespace Barotrauma
slicePos.Y += flippedDrawOffset.Y; slicePos.Y += flippedDrawOffset.Y;
} }
spriteBatch.Draw(texture, slicePos, sliceRect, drawColor, rotation, Vector2.Zero, scale, effects, depth ?? this.depth); drawSection(slicePos, sliceRect);
currDrawPosition.X = slicePos.X + sliceWidth; currDrawPosition.X = slicePos.X + sliceWidth;
} }
} }
@@ -416,7 +443,7 @@ namespace Barotrauma
sliceRect.Y = SourceRect.Y; sliceRect.Y = SourceRect.Y;
sliceRect.Height = (int)(sliceHeight / scale.Y); sliceRect.Height = (int)(sliceHeight / scale.Y);
spriteBatch.Draw(texture, slicePos, sliceRect, drawColor, rotation, Vector2.Zero, scale, effects, depth ?? this.depth); drawSection(slicePos, sliceRect);
currDrawPosition.Y = slicePos.Y + sliceHeight; currDrawPosition.Y = slicePos.Y + sliceHeight;
} }
@@ -433,8 +460,7 @@ namespace Barotrauma
} }
} }
spriteBatch.Draw(texture, currDrawPosition, drawSection(currDrawPosition, texPerspective);
texPerspective, drawColor, rotation, Vector2.Zero, scale, effects, depth ?? this.depth);
currDrawPosition.Y += texPerspective.Height * scale.Y; currDrawPosition.Y += texPerspective.Height * scale.Y;
} }
@@ -210,7 +210,7 @@ namespace Barotrauma
statusEffect.soundChannel.FadeOutAndDispose(); statusEffect.soundChannel.FadeOutAndDispose();
statusEffect.soundChannel = null; statusEffect.soundChannel = null;
} }
else else if (statusEffect.soundEmitter is { Removed: false })
{ {
statusEffect.soundChannel.Position = new Vector3(statusEffect.soundEmitter.WorldPosition, 0.0f); statusEffect.soundChannel.Position = new Vector3(statusEffect.soundEmitter.WorldPosition, 0.0f);
if (doMuffleCheck && !statusEffect.ignoreMuffling) if (doMuffleCheck && !statusEffect.ignoreMuffling)
@@ -101,13 +101,36 @@ namespace Barotrauma.Steam
{ {
Color = GUIStyle.Green Color = GUIStyle.Green
}; };
var textShadow = new GUITextBlock(new RectTransform(Vector2.One, itemDownloadProgress.RectTransform) { AbsoluteOffset = new Point(GUI.IntScale(3)) }, "",
textColor: Color.Black, textAlignment: Alignment.Center);
var text = new GUITextBlock(new RectTransform(Vector2.One, itemDownloadProgress.RectTransform), "",
textAlignment: Alignment.Center);
var itemDownloadProgressUpdater = new GUICustomComponent( var itemDownloadProgressUpdater = new GUICustomComponent(
new RectTransform(Vector2.Zero, msgBox.Content.RectTransform), new RectTransform(Vector2.Zero, msgBox.Content.RectTransform),
onUpdate: (f, component) => onUpdate: (f, component) =>
{ {
float progress = 0.0f; float progress = 0.0f;
if (item.IsDownloading) { progress = item.DownloadAmount; } if (item.IsDownloading)
else if (itemDownloadProgress.BarSize > 0.0f) { progress = 1.0f; } {
progress = item.DownloadAmount;
text.Text = textShadow.Text = TextManager.GetWithVariable(
"PublishPopupDownload",
"[percentage]",
((int)MathF.Round(item.DownloadAmount * 100)).ToString());
}
else if (itemDownloadProgress.BarSize > 0.0f)
{
if (!item.IsInstalled && !SteamManager.Workshop.CanBeInstalled(item.Id))
{
itemDownloadProgress.Color = GUIStyle.Red;
text.Text = textShadow.Text = TextManager.Get("workshopiteminstallfailed");
}
else
{
text.Text = textShadow.Text = TextManager.Get(item.IsInstalled ? "workshopiteminstalled" : "PublishPopupInstall");
}
progress = 1.0f;
}
itemDownloadProgress.BarSize = Math.Max(itemDownloadProgress.BarSize, itemDownloadProgress.BarSize = Math.Max(itemDownloadProgress.BarSize,
MathHelper.Lerp(itemDownloadProgress.BarSize, progress, 0.1f)); MathHelper.Lerp(itemDownloadProgress.BarSize, progress, 0.1f));
@@ -134,9 +157,16 @@ namespace Barotrauma.Steam
{ {
foreach (var item in itemsToDownload) foreach (var item in itemsToDownload)
{ {
DebugConsole.Log($"Reinstalling {item.Title}...");
await SteamManager.Workshop.Reinstall(item); await SteamManager.Workshop.Reinstall(item);
if (!GUIMessageBox.MessageBoxes.Contains(msgBox)) { break; } DebugConsole.Log($"Finished installing {item.Title}...");
if (!GUIMessageBox.MessageBoxes.Contains(msgBox))
{
DebugConsole.Log($"Download prompt closed, interrupting {nameof(DownloadItems)}.");
break;
}
} }
DebugConsole.Log($"{nameof(DownloadItems)} finished.");
} }
} }
} }
@@ -120,6 +120,10 @@ namespace Barotrauma.Steam
currentLobby?.SetData("playstyle", serverSettings.PlayStyle.ToString()); currentLobby?.SetData("playstyle", serverSettings.PlayStyle.ToString());
currentLobby?.SetData("gamemode", GameMain.NetLobbyScreen?.SelectedMode?.Identifier.Value ?? ""); currentLobby?.SetData("gamemode", GameMain.NetLobbyScreen?.SelectedMode?.Identifier.Value ?? "");
currentLobby?.SetData("language", serverSettings.Language.ToString()); currentLobby?.SetData("language", serverSettings.Language.ToString());
if (GameMain.NetLobbyScreen?.SelectedSub != null)
{
currentLobby?.SetData("submarine", GameMain.NetLobbyScreen.SelectedSub.Name);
}
DebugConsole.Log("Lobby updated!"); DebugConsole.Log("Lobby updated!");
} }
@@ -298,7 +298,7 @@ namespace Barotrauma.Steam
public static void OnItemDownloadComplete(ulong id, bool forceInstall = false) public static void OnItemDownloadComplete(ulong id, bool forceInstall = false)
{ {
if (!(Screen.Selected is MainMenuScreen) && !forceInstall) if (Screen.Selected is not MainMenuScreen && !forceInstall)
{ {
if (!MainMenuScreen.WorkshopItemsToUpdate.Contains(id)) if (!MainMenuScreen.WorkshopItemsToUpdate.Contains(id))
{ {
@@ -306,13 +306,26 @@ namespace Barotrauma.Steam
} }
return; return;
} }
else if (CanBeInstalled(id) else if (!CanBeInstalled(id))
&& !ContentPackageManager.WorkshopPackages.Any(p => {
DebugConsole.Log($"Cannot install {id}");
InstallWaiter.StopWaiting(id);
}
else if (ContentPackageManager.WorkshopPackages.Any(p =>
p.UgcId.TryUnwrap(out var ugcId) p.UgcId.TryUnwrap(out var ugcId)
&& ugcId is SteamWorkshopId workshopId && ugcId is SteamWorkshopId workshopId
&& workshopId.Value == id) && workshopId.Value == id))
&& !InstallTaskCounter.IsInstalling(id))
{ {
DebugConsole.Log($"Already installed {id}.");
InstallWaiter.StopWaiting(id);
}
else if (InstallTaskCounter.IsInstalling(id))
{
DebugConsole.Log($"Already installing {id}.");
}
else
{
DebugConsole.Log($"Finished downloading {id}, installing...");
TaskPool.Add($"InstallItem{id}", InstallMod(id), t => InstallWaiter.StopWaiting(id)); TaskPool.Add($"InstallItem{id}", InstallMod(id), t => InstallWaiter.StopWaiting(id));
} }
} }

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