Unstable 1.2.4.0

This commit is contained in:
Markus Isberg
2023-11-30 13:53:00 +02:00
parent 8a2e2ea0ae
commit fb5ea537bf
210 changed files with 4201 additions and 1283 deletions

View File

@@ -6,7 +6,7 @@ using System;
namespace Barotrauma
{
class Camera : IDisposable
class Camera
{
public static bool FollowSub = true;
@@ -147,21 +147,10 @@ namespace Barotrauma
position = Vector2.Zero;
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);
}
private bool disposed = false;
public void Dispose()
{
if (!disposed) { GameMain.Instance.ResolutionChanged -= CreateMatrices; }
disposed = true;
}
public Vector2 TargetPos { get; set; }
public Vector2 GetPosition()
@@ -207,6 +196,12 @@ namespace Barotrauma
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;
float interpolatedZoom = interpolate ? Timing.Interpolate(prevZoom, zoom) : zoom;

View File

@@ -136,6 +136,7 @@ namespace Barotrauma
set
{
if (!MathUtils.IsValid(value)) { return; }
if (this != Controlled) { return; }
if (Screen.Selected?.Cam != null)
{
Screen.Selected.Cam.Shake = value;
@@ -521,22 +522,25 @@ namespace Barotrauma
if (controlled == this)
{
controlled = null;
if (!(Screen.Selected?.Cam is null))
if (Screen.Selected?.Cam is not null)
{
Screen.Selected.Cam.TargetPos = Vector2.Zero;
Lights.LightManager.ViewTarget = null;
}
}
sounds.ForEach(s => s.Sound?.Dispose());
sounds.Clear();
if (GameMain.GameSession?.CrewManager != null &&
GameMain.GameSession.CrewManager.GetCharacters().Contains(this))
{
GameMain.GameSession.CrewManager.RemoveCharacter(this);
}
if (GameMain.Client?.Character == this) GameMain.Client.Character = null;
if (Lights.LightManager.ViewTarget == this) Lights.LightManager.ViewTarget = null;
if (GameMain.Client?.Character == this) { GameMain.Client.Character = null; }
if (Lights.LightManager.ViewTarget == this) { Lights.LightManager.ViewTarget = null; }
}

View File

@@ -894,7 +894,7 @@ namespace Barotrauma
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;
drawPos += Vector2.UnitX * order.SymbolSprite.size.X * 1.5f * orderIndicatorCount[target];
GUI.DrawIndicator(spriteBatch, drawPos, cam, 100.0f, order.SymbolSprite, order.Color * iconAlpha,

View File

@@ -217,6 +217,7 @@ namespace Barotrauma
if ((int)newLevel > (int)prevLevel)
{
Character.Controlled?.SelectedItem?.OnPlayerSkillsChanged();
int increase = Math.Max((int)newLevel - (int)prevLevel, 1);
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);
}
public static CharacterInfo ClientRead(Identifier speciesName, IReadMessage inc)
public static CharacterInfo ClientRead(Identifier speciesName, IReadMessage inc, bool requireJobPrefabFound = true)
{
ushort infoID = inc.ReadUInt16();
string newName = inc.ReadString();
@@ -554,14 +555,19 @@ namespace Barotrauma
if (jobIdentifier > 0)
{
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}\".");
}
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();
skillLevels.Add(skillPrefab.Identifier, skillLevel);
if (jobSkills != null && i < jobSkills.Count)
{
skillLevels.Add(jobSkills[i].Identifier, skillLevel);
}
}
}

View File

@@ -2170,6 +2170,8 @@ namespace Barotrauma
medUIExtra?.Remove();
medUIExtra = null;
Character.OnAttacked -= OnAttacked;
limbIndicatorOverlay?.Remove();
limbIndicatorOverlay = null;

View File

@@ -1216,7 +1216,7 @@ namespace Barotrauma
pos: new Vector2(body.DrawPosition.X, -body.DrawPosition.Y),
srcRect: w.Sprite.SourceRect,
color: Color.White,
rotation: rotation,
rotationRad: rotation,
origin: origin,
scale: new Vector2(scale, scale),
effects: spriteEffect,

View File

@@ -681,6 +681,7 @@ namespace Barotrauma
AssignRelayToServer("savebinds", false);
AssignRelayToServer("spreadsheetexport", false);
#if DEBUG
AssignRelayToServer("listspamfilters", false);
AssignRelayToServer("crash", false);
AssignRelayToServer("showballastflorasprite", false);
AssignRelayToServer("simulatedlatency", false);
@@ -2234,6 +2235,30 @@ namespace Barotrauma
}));
#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) =>
{
if (1 > args.Length || Screen.Selected != GameMain.SubEditorScreen) { return; }
@@ -3094,7 +3119,7 @@ namespace Barotrauma
int i = 0;
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++;
}
ShowQuestionPrompt("Select a destination (0 - " + (campaign.Map.CurrentLocation.Connections.Count - 1) + "):", (string selectedDestination) =>

View File

@@ -16,6 +16,11 @@ partial class EventObjectiveAction : EventAction
int width = 450,
int height = 80)
{
if (Type == SegmentActionType.AddIfNotFound)
{
if (ObjectiveManager.IsSegmentActive(Identifier)) { return; }
}
ObjectiveManager.Segment? segment = null;
// Only need to create the segment when it's being triggered (otherwise the tutorial already has the segment instance)
if (Type == SegmentActionType.Trigger)
@@ -24,7 +29,8 @@ partial class EventObjectiveAction : EventAction
new ObjectiveManager.Segment.Text(TextTag, width, height, Anchor.Center),
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);
}
@@ -33,10 +39,12 @@ partial class EventObjectiveAction : EventAction
segment.CanBeCompleted = CanBeCompleted;
segment.ParentId = ParentObjectiveId;
}
switch (Type)
{
case SegmentActionType.Trigger:
case SegmentActionType.Add:
case SegmentActionType.AddIfNotFound:
ObjectiveManager.TriggerSegment(segment);
break;
case SegmentActionType.Complete:

View File

@@ -1,22 +1,19 @@
#nullable enable
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma;
partial class TutorialHighlightAction : EventAction
partial class HighlightAction : EventAction
{
private static readonly Color highlightColor = Color.Orange;
partial void UpdateProjSpecific()
partial void SetHighlightProjSpecific(Entity entity, IEnumerable<Character>? targetCharacters)
{
if (GameMain.GameSession?.GameMode is not TutorialMode) { return; }
foreach (var target in ParentEvent.GetTargets(TargetTag))
if (targetCharacters != null && !targetCharacters.Contains(Character.Controlled))
{
SetHighlight(target);
return;
}
}
private void SetHighlight(Entity entity)
{
if (entity is Item i)
{
SetItemHighlight(i);

View File

@@ -581,7 +581,14 @@ namespace Barotrauma
StatusEffect effect = StatusEffect.Load(subElement, $"EventManager.ClientRead ({eventIdentifier})");
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;

View File

@@ -126,7 +126,13 @@ namespace Barotrauma
void GiveMissionExperience(CharacterInfo info)
{
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.GiveExperience((int)(experienceGain * experienceGainMultiplierIndividual.Value));
}

View File

@@ -247,30 +247,33 @@ namespace Barotrauma
UpdateCrew();
}
public void UpdateHireables()
{
UpdateHireables(campaign?.CurrentLocation);
}
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));
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)
{
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.2f), hireableList.Content.RectTransform), TextManager.Get("HireUnavailable"), textAlignment: Alignment.Center)
{
CanBeFocused = false
};
}
else
{
foreach (CharacterInfo c in hireableCharacters)
{
if (c == null) { continue; }
CreateCharacterFrame(c, hireableList);
}
}
sortingDropDown.SelectItem(SortingMethod.JobAsc);
hireableList.UpdateScrollBarSize();
CanBeFocused = false
};
}
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)
@@ -434,7 +437,7 @@ namespace Barotrauma
if (listBox != crewList)
{
new GUITextBlock(new RectTransform(new Vector2(width, 1.0f), mainGroup.RectTransform),
TextManager.FormatCurrency(characterInfo.Salary),
TextManager.FormatCurrency(HireManager.GetSalaryFor(characterInfo)),
textAlignment: Alignment.Center)
{
CanBeFocused = false
@@ -692,11 +695,8 @@ namespace Barotrauma
private void SetTotalHireCost()
{
if (pendingList == null || totalBlock == null || validateHiresButton == null) { return; }
int total = 0;
pendingList.Content.Children.ForEach(c =>
{
total += ((InfoSkill)c.UserData).CharacterInfo.Salary;
});
var infos = pendingList.Content.Children.Select(static c => ((InfoSkill)c.UserData).CharacterInfo).ToArray();
int total = HireManager.GetSalaryFor(infos);
totalBlock.Text = TextManager.FormatCurrency(total);
bool enoughMoney = campaign == null || campaign.CanAfford(total);
totalBlock.TextColor = enoughMoney ? Color.White : Color.Red;
@@ -718,14 +718,14 @@ namespace Barotrauma
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; }
bool atLeastOneHired = false;
foreach (CharacterInfo ci in nonDuplicateHires)
{
if (campaign.TryHireCharacter(campaign.Map.CurrentLocation, ci))
if (campaign.TryHireCharacter(campaign.Map.CurrentLocation, ci, Character.Controlled))
{
atLeastOneHired = true;
}
@@ -741,7 +741,7 @@ namespace Barotrauma
SelectCharacter(null, null, null);
var dialog = new GUIMessageBox(
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") });
dialog.Buttons[0].OnClicked += dialog.Close;
}

View File

@@ -512,10 +512,18 @@ namespace Barotrauma
soundStr += " (stopped)";
clr *= 0.5f;
}
else if (playingSoundChannel.Muffled)
else
{
soundStr += " (muffled)";
clr = Color.Lerp(clr, Color.LightGray, 0.5f);
if (playingSoundChannel.Muffled)
{
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") };
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
msgBox.Buttons[1].OnClicked = delegate

View File

@@ -775,23 +775,30 @@ namespace Barotrauma
toolTipBlock.UserData = toolTip;
}
toolTipBlock.RectTransform.AbsoluteOffset =
RectTransform.CalculateAnchorPoint(anchor, targetElement) +
RectTransform.CalculatePivotOffset(pivot, toolTipBlock.RectTransform.NonScaledSize);
CalculateOffset();
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)
{
toolTipBlock.RectTransform.AbsoluteOffset -= new Point(
0,
toolTipBlock.Rect.Bottom - (GameMain.GraphicsHeight - 10));
anchor = RectTransform.MoveAnchorTop(anchor);
pivot = (Pivot)RectTransform.MoveAnchorBottom((Anchor)pivot);
CalculateOffset();
}
toolTipBlock.SetTextPos();
toolTipBlock.DrawManually(spriteBatch);
void CalculateOffset()
{
toolTipBlock.RectTransform.AbsoluteOffset =
RectTransform.CalculateAnchorPoint(anchor, targetElement) +
RectTransform.CalculatePivotOffset(pivot, toolTipBlock.RectTransform.NonScaledSize);
}
}
#endregion

View File

@@ -313,13 +313,18 @@ namespace Barotrauma
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
{
get
{
return Prefabs.ActivePrefab.Color;
return Prefabs?.ActivePrefab?.Color ?? fallbackColor;
}
}

View File

@@ -75,72 +75,72 @@ namespace Barotrauma
/// <summary>
/// General green color used for elements whose colors are set from code
/// </summary>
public readonly static GUIColor Green = new GUIColor("Green");
public readonly static GUIColor Green = new GUIColor("Green", new Color(154, 213, 163, 255));
/// <summary>
/// General red color used for elements whose colors are set from code
/// </summary>
public readonly static GUIColor Orange = new GUIColor("Orange");
public readonly static GUIColor Orange = new GUIColor("Orange", new Color(243, 162, 50, 255));
/// <summary>
/// General red color used for elements whose colors are set from code
/// </summary>
public readonly static GUIColor Red = new GUIColor("Red");
public readonly static GUIColor Red = new GUIColor("Red", new Color(245, 105, 105, 255));
/// <summary>
/// General blue color used for elements whose colors are set from code
/// </summary>
public readonly static GUIColor Blue = new GUIColor("Blue");
public readonly static GUIColor Blue = new GUIColor("Blue", new Color(126, 211, 224, 255));
/// <summary>
/// General yellow color used for elements whose colors are set from code
/// </summary>
public readonly static GUIColor Yellow = new GUIColor("Yellow");
public readonly static GUIColor Yellow = new GUIColor("Yellow", new Color(255, 255, 0, 255));
/// <summary>
/// Color to display the name of modded servers in the server list.
/// </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 ColorInventoryHalf = new GUIColor("ColorInventoryHalf");
public readonly static GUIColor ColorInventoryFull = new GUIColor("ColorInventoryFull");
public readonly static GUIColor ColorInventoryBackground = new GUIColor("ColorInventoryBackground");
public readonly static GUIColor ColorInventoryEmptyOverlay = new GUIColor("ColorInventoryEmptyOverlay");
public readonly static GUIColor ColorInventoryEmpty = new GUIColor("ColorInventoryEmpty", new Color(245, 105, 105, 255));
public readonly static GUIColor ColorInventoryHalf = new GUIColor("ColorInventoryHalf", new Color(243, 162, 50, 255));
public readonly static GUIColor ColorInventoryFull = new GUIColor("ColorInventoryFull", new Color(96, 222, 146, 255));
public readonly static GUIColor ColorInventoryBackground = new GUIColor("ColorInventoryBackground", new Color(56, 56, 56, 255));
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 TextColorBright = new GUIColor("TextColorBright");
public readonly static GUIColor TextColorDark = new GUIColor("TextColorDark");
public readonly static GUIColor TextColorDim = new GUIColor("TextColorDim");
public readonly static GUIColor TextColorNormal = new GUIColor("TextColorNormal", new Color(228, 217, 167, 255));
public readonly static GUIColor TextColorBright = new GUIColor("TextColorBright", new Color(255, 255, 255, 255));
public readonly static GUIColor TextColorDark = new GUIColor("TextColorDark", new Color(0, 0, 0, 230));
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 ItemQualityColorNormal = new GUIColor("ItemQualityColorNormal");
public readonly static GUIColor ItemQualityColorGood = new GUIColor("ItemQualityColorGood");
public readonly static GUIColor ItemQualityColorExcellent = new GUIColor("ItemQualityColorExcellent");
public readonly static GUIColor ItemQualityColorMasterwork = new GUIColor("ItemQualityColorMasterwork");
public readonly static GUIColor ItemQualityColorPoor = new GUIColor("ItemQualityColorPoor", new Color(128, 128, 128, 255));
public readonly static GUIColor ItemQualityColorNormal = new GUIColor("ItemQualityColorNormal", new Color(255, 255, 255, 255));
public readonly static GUIColor ItemQualityColorGood = new GUIColor("ItemQualityColorGood", new Color(144, 238, 144, 255));
public readonly static GUIColor ItemQualityColorExcellent = new GUIColor("ItemQualityColorExcellent", new Color(173, 216, 230, 255));
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 ColorReputationLow = new GUIColor("ColorReputationLow");
public readonly static GUIColor ColorReputationNeutral = new GUIColor("ColorReputationNeutral");
public readonly static GUIColor ColorReputationHigh = new GUIColor("ColorReputationHigh");
public readonly static GUIColor ColorReputationVeryHigh = new GUIColor("ColorReputationVeryHigh");
public readonly static GUIColor ColorReputationVeryLow = new GUIColor("ColorReputationVeryLow", new Color(192, 60, 60, 255));
public readonly static GUIColor ColorReputationLow = new GUIColor("ColorReputationLow", new Color(203, 145, 23, 255));
public readonly static GUIColor ColorReputationNeutral = new GUIColor("ColorReputationNeutral", new Color(228, 217, 167, 255));
public readonly static GUIColor ColorReputationHigh = new GUIColor("ColorReputationHigh", new Color(51, 152, 64, 255));
public readonly static GUIColor ColorReputationVeryHigh = new GUIColor("ColorReputationVeryHigh", new Color(71, 160, 164, 255));
// 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
public readonly static GUIColor BuffColorLow = new GUIColor("BuffColorLow");
public readonly static GUIColor BuffColorMedium = new GUIColor("BuffColorMedium");
public readonly static GUIColor BuffColorHigh = new GUIColor("BuffColorHigh");
public readonly static GUIColor BuffColorLow = new GUIColor("BuffColorLow", new Color(66, 170, 73, 255));
public readonly static GUIColor BuffColorMedium = new GUIColor("BuffColorMedium", new Color(110, 168, 118, 255));
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 DebuffColorMedium = new GUIColor("DebuffColorMedium");
public readonly static GUIColor DebuffColorHigh = new GUIColor("DebuffColorHigh");
public readonly static GUIColor DebuffColorLow = new GUIColor("DebuffColorLow", new Color(243, 162, 50, 255));
public readonly static GUIColor DebuffColorMedium = new GUIColor("DebuffColorMedium", new Color(155, 55, 55, 255));
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 HealthBarColorMedium = new GUIColor("HealthBarColorMedium");
public readonly static GUIColor HealthBarColorHigh = new GUIColor("HealthBarColorHigh");
public readonly static GUIColor HealthBarColorPoisoned = new GUIColor("HealthBarColorPoisoned");
public readonly static GUIColor HealthBarColorLow = new GUIColor("HealthBarColorLow", new Color(255, 0, 0, 255));
public readonly static GUIColor HealthBarColorMedium = new GUIColor("HealthBarColorMedium", new Color(255, 165, 0, 255));
public readonly static GUIColor HealthBarColorHigh = new GUIColor("HealthBarColorHigh", new Color(78, 114, 88));
public readonly static GUIColor HealthBarColorPoisoned = new GUIColor("HealthBarColorPoisoned", new Color(100, 150, 0, 255));
private readonly static Point defaultItemFrameMargin = new Point(50, 56);

View File

@@ -461,14 +461,16 @@ namespace Barotrauma
}
private ImmutableArray<Vector2> cachedCaretPositions = ImmutableArray<Vector2>.Empty;
//which text were the cached caret positions calculated for?
private string cachedCaretPositionsText;
public ImmutableArray<Vector2> GetAllCaretPositions()
{
if (cachedCaretPositions.Any())
string textDrawn = Censor ? CensoredText : Text.SanitizedValue;
if (cachedCaretPositions.Any() &&
textDrawn == cachedCaretPositionsText)
{
return cachedCaretPositions;
}
string textDrawn = Censor ? CensoredText : Text.SanitizedValue;
float w = Wrap
? (Rect.Width - Padding.X - Padding.Z) / TextScale
: float.PositiveInfinity;
@@ -482,6 +484,7 @@ namespace Barotrauma
.Select(p => p - new Vector2(alignmentXDiff, 0))
.Select(p => p * TextScale + TextPos - Origin * TextScale)
.ToImmutableArray();
cachedCaretPositionsText = textDrawn;
return cachedCaretPositions;
}

View File

@@ -353,6 +353,10 @@ namespace Barotrauma
{
CaretIndex = Math.Clamp(CaretIndex, 0, textBlock.Text.Length);
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];
caretPosDirty = false;
}

View File

@@ -784,11 +784,95 @@ namespace Barotrauma
#region Static methods
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>
@@ -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 height = size.Y;
switch (pivot)
switch (anchor)
{
case Pivot.TopLeft:
return Point.Zero;
@@ -836,7 +920,7 @@ namespace Barotrauma
case Pivot.BottomRight:
return new Point(-width, -height);
default:
throw new NotImplementedException(pivot.ToString());
throw new NotImplementedException(anchor.ToString());
}
}

View File

@@ -2127,11 +2127,10 @@ namespace Barotrauma
{
var dialog = new GUIMessageBox(
TextManager.Get("newsupplies"),
TextManager.GetWithVariable("suppliespurchasedmessage", "[location]", campaignUI?.Campaign?.Map?.CurrentLocation?.Name));
TextManager.GetWithVariable("suppliespurchasedmessage", "[location]", campaignUI?.Campaign?.Map?.CurrentLocation?.DisplayName));
dialog.Buttons[0].OnClicked += dialog.Close;
}
}
return false;
}

View File

@@ -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) };
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);
playerBalanceElement = CampaignUI.AddBalanceElement(header, new Vector2(1.0f, 1.5f));

View File

@@ -165,6 +165,11 @@ namespace Barotrauma
public TabMenu()
{
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);
SelectInfoFrameTab(SelectedTab);
}
@@ -303,7 +308,7 @@ namespace Barotrauma
{
var missionBtn = createTabButton(InfoFrameTab.Mission, "mission");
eventLogNotification = GameSession.CreateNotificationIcon(missionBtn);
eventLogNotification.Visible = GameMain.GameSession.EventManager?.EventLog?.UnreadEntries ?? false;
eventLogNotification.Visible = GameMain.GameSession?.EventManager?.EventLog?.UnreadEntries ?? false;
if (eventLogNotification.Visible)
{
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);
}
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);
if (location.Faction?.Prefab != null)

View File

@@ -123,6 +123,10 @@ namespace Barotrauma
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;
private bool exiting;
@@ -404,7 +408,7 @@ namespace Barotrauma
//do this here because we need it for the loading screen
WaterRenderer.Instance = new WaterRenderer(base.GraphicsDevice);
Quad.Init(GraphicsDevice);
GraphicsQuad.Init(GraphicsDevice);
loadingScreenOpen = true;
TitleScreen = new LoadingScreen(GraphicsDevice)

View File

@@ -192,12 +192,12 @@ namespace Barotrauma
if (Level.Loaded.EndOutpost == null || !Level.Loaded.EndOutpost.DockedTo.Contains(leavingSub))
{
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;
}
break;
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;
break;
case TransitionType.ReturnToPreviousLocation:
@@ -205,7 +205,7 @@ namespace Barotrauma
if (Level.Loaded.StartOutpost == null || !Level.Loaded.StartOutpost.DockedTo.Contains(leavingSub))
{
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;
}
break;
@@ -221,7 +221,7 @@ namespace Barotrauma
endRoundButton.Color = GUIStyle.Red * 0.7f;
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;
}
else

View File

@@ -614,7 +614,7 @@ namespace Barotrauma
{
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;
}
LocationConnection connection = campaign.Map.CurrentLocation.Connections[availableMission.ConnectionIndex];
@@ -647,7 +647,15 @@ namespace Barotrauma
{
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);
GameAnalyticsManager.AddErrorEventOnce(
"MultiPlayerCampaign.ClientRead.OwnerSubIndexOutOfBounds" + ownedSubIndex,
@@ -822,11 +830,12 @@ namespace Barotrauma
UInt16 id = msg.ReadUInt16();
bool hasCharacterData = msg.ReadBoolean();
CharacterInfo myCharacterInfo = null;
bool waitForModsDownloaded = Screen.Selected is ModDownloadScreen;
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)
{

View File

@@ -548,9 +548,12 @@ namespace Barotrauma
}
else
{
//wasn't initially docked (sub doesn't have a docking port?)
// -> choose a destination when the sub is far enough from the start outpost
if (!Submarine.MainSub.AtStartExit && !Level.Loaded.StartOutpost.ExitPoints.Any())
//force the map to open if the sub is somehow not at the start of the outpost level
//UNLESS the level has specific exit points, in that case the sub needs to get to those
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;
CampaignUI.SelectTab(InteractionType.Map);

View File

@@ -48,6 +48,8 @@ namespace Barotrauma
private GUIImage eventLogNotification;
private Point prevTopLeftButtonsResolution;
private void CreateTopLeftButtons()
{
if (topLeftButtonGroup != null)
@@ -61,10 +63,6 @@ namespace Barotrauma
AbsoluteSpacing = HUDLayoutSettings.Padding,
CanBeFocused = false
};
topLeftButtonGroup.RectTransform.ParentChanged += (_) =>
{
GameMain.Instance.ResolutionChanged -= CreateTopLeftButtons;
};
int buttonHeight = GUI.IntScale(40);
Vector2 buttonSpriteSize = GUIStyle.GetComponentStyle("CrewListToggleButton").GetDefaultSprite().size;
int buttonWidth = (int)((buttonHeight / buttonSpriteSize.Y) * buttonSpriteSize.X);
@@ -98,8 +96,6 @@ namespace Barotrauma
talentPointNotification = CreateNotificationIcon(tabMenuButton);
eventLogNotification = CreateNotificationIcon(tabMenuButton);
GameMain.Instance.ResolutionChanged += CreateTopLeftButtons;
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)
{
@@ -121,6 +117,7 @@ namespace Barotrauma
return true;
}
};
prevTopLeftButtonsResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
}
public void AddToGUIUpdateList()
@@ -133,7 +130,8 @@ namespace Barotrauma
if ((GameMode is not CampaignMode campaign || (!campaign.ForceMapUI && !campaign.ShowCampaignUI)) &&
!CoroutineManager.IsCoroutineRunning("LevelTransition") && !CoroutineManager.IsCoroutineRunning("SubmarineTransition"))
{
if (topLeftButtonGroup == null)
if (topLeftButtonGroup == null ||
prevTopLeftButtonsResolution.X != GameMain.GraphicsWidth || prevTopLeftButtonsResolution.Y != GameMain.GraphicsHeight)
{
CreateTopLeftButtons();
}

View File

@@ -139,6 +139,11 @@ static class ObjectiveManager
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)
{
if (segment.SegmentType != SegmentType.InfoBox)
@@ -361,9 +366,18 @@ static class ObjectiveManager
activeObjectives.IndexOf(parentSegment) + activeObjectives.Count(s => s.ParentId == segment.ParentId);
if (objectiveGroup.RectTransform.GetChildIndex(frameRt) != childIndex)
{
frameRt.RepositionChildInHierarchy(childIndex);
activeObjectives.Remove(segment);
activeObjectives.Insert(childIndex, segment);
if (childIndex < 0 || childIndex >= frameRt.Parent.CountChildren)
{
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();

View File

@@ -564,7 +564,7 @@ namespace Barotrauma
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;
if (gameOver)
@@ -576,23 +576,23 @@ namespace Barotrauma
switch (transitionType)
{
case CampaignMode.TransitionType.LeaveLocation:
locationName = startLocation?.Name;
locationName = startLocation?.DisplayName;
textTag = "RoundSummaryLeaving";
break;
case CampaignMode.TransitionType.ProgressToNextLocation:
locationName = endLocation?.Name;
locationName = endLocation?.DisplayName;
textTag = "RoundSummaryProgress";
break;
case CampaignMode.TransitionType.ProgressToNextEmptyLocation:
locationName = endLocation?.Name;
locationName = endLocation?.DisplayName;
textTag = "RoundSummaryProgressToEmptyLocation";
break;
case CampaignMode.TransitionType.ReturnToPreviousLocation:
locationName = startLocation?.Name;
locationName = startLocation?.DisplayName;
textTag = "RoundSummaryReturn";
break;
case CampaignMode.TransitionType.ReturnToPreviousEmptyLocation:
locationName = startLocation?.Name;
locationName = startLocation?.DisplayName;
textTag = "RoundSummaryReturnToEmptyLocation";
break;
default:
@@ -603,14 +603,14 @@ namespace Barotrauma
if (startLocation?.Biome != null && startLocation.Biome.IsEndBiome)
{
locationName ??= startLocation.Name;
locationName ??= startLocation.DisplayName;
}
if (textTag == null) { return ""; }
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]";
}

View File

@@ -568,7 +568,7 @@ namespace Barotrauma
itemContainer.KeepOpenWhenEquippedBy(character) &&
!DraggingItems.Contains(item) &&
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);
}
@@ -709,11 +709,11 @@ namespace Barotrauma
private void ShowSubInventory(SlotReference slotRef, float deltaTime, Camera cam, List<SlotReference> hideSubInventories, bool isEquippedSubInventory)
{
Rectangle hoverArea = GetSubInventoryHoverArea(slotRef);
if (isEquippedSubInventory)
if (isEquippedSubInventory && slotRef.Inventory is not ItemInventory { Container.MovableFrame: true, Container.KeepOpenWhenEquipped: true })
{
foreach (SlotReference highlightedSubInventorySlot in highlightedSubInventorySlots)
{
if (highlightedSubInventorySlot == slotRef) continue;
if (highlightedSubInventorySlot == slotRef) { continue; }
if (hoverArea.Intersects(GetSubInventoryHoverArea(highlightedSubInventorySlot)))
{
return; // If an equipped one intersects with a currently active hover one, do not open
@@ -818,7 +818,8 @@ namespace Barotrauma
if (selectedContainer != null &&
selectedContainer.Inventory != null &&
!selectedContainer.Inventory.Locked &&
!selectedContainer.Inventory.Locked &&
selectedContainer.DrawInventory &&
allowInventorySwap)
{
//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 =>
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)
{
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
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
//(in that case, the quick action should just fill up the stack)
bool disallowSwapping =

View File

@@ -260,6 +260,12 @@ namespace Barotrauma.Items.Components
if (!hasSoundsOfType[(int)type]) { 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 (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.Type != type) { return; }
if (loopingSoundChannel != null)
{
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)
{
if (sound == null) { return 0.0f; }
@@ -761,6 +770,8 @@ namespace Barotrauma.Items.Components
}
}
public virtual void OnPlayerSkillsChanged() { }
public virtual void AddTooltipInfo(ref LocalizedString name, ref LocalizedString description) { }
}
}

View File

@@ -346,9 +346,9 @@ namespace Barotrauma.Items.Components
public bool KeepOpenWhenEquippedBy(Character character)
{
if (!character.CanAccessInventory(Inventory) ||
!KeepOpenWhenEquipped ||
!character.HasEquippedItem(Item))
if (!KeepOpenWhenEquipped ||
!character.HasEquippedItem(Item) ||
!character.CanAccessInventory(Inventory))
{
return false;
}
@@ -571,11 +571,13 @@ namespace Barotrauma.Items.Components
{
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;
}
if (item.FlippedY)
bool flipY = item.FlippedY;
if (flipY)
{
spriteEffects |= MathUtils.NearlyEqual(spriteRotation % 180, 90.0f) ? SpriteEffects.FlipHorizontally : SpriteEffects.FlipVertically;
}
@@ -589,6 +591,7 @@ namespace Barotrauma.Items.Components
contained.Item.Scale,
spriteEffects,
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>())
{

View File

@@ -227,7 +227,7 @@ namespace Barotrauma.Items.Components
switch (text)
{
case "[CurrentLocationName]":
SetDisplayText(Level.Loaded?.StartLocation?.Name ?? string.Empty);
SetDisplayText(Level.Loaded?.StartLocation?.DisplayName.Value ?? string.Empty);
break;
case "[CurrentBiomeName]":
SetDisplayText(Level.Loaded?.LevelData?.Biome?.DisplayName.Value ?? string.Empty);

View File

@@ -67,7 +67,7 @@ namespace Barotrauma.Items.Components
Light.Position = item.Position;
}
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.LightSpriteEffect = (body.Dir > 0.0f) ? SpriteEffects.None : SpriteEffects.FlipVertically;

View File

@@ -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)
{
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")
{

View File

@@ -4,6 +4,7 @@ using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
namespace Barotrauma.Items.Components
@@ -825,11 +826,22 @@ namespace Barotrauma.Items.Components
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)
{
this.selectedItem = selectedItem;
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);
if (amountInput != null)
@@ -853,8 +865,10 @@ namespace Barotrauma.Items.Components
LocalizedString itemName = GetRecipeNameAndAmount(selectedItem);
LocalizedString name = itemName;
float quality = selectedItem.Quality ?? GetFabricatedItemQuality(selectedItem, user);
if (quality > 0)
QualityResult result = GetFabricatedItemQuality(selectedItem, user);
float quality = selectedItem.Quality ?? result.Quality;
if (quality > 0 || result.HasRandomQualityRollChance)
{
name = TextManager.GetWithVariable("itemname.quality" + (int)quality, "[itemname]", itemName + '\n')
.Fallback(TextManager.GetWithVariable("itemname.quality3", "[itemname]", itemName + '\n'));
@@ -865,6 +879,49 @@ namespace Barotrauma.Items.Components
{
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);
if (nameBlock.TextScale < 0.7f)
{
@@ -875,15 +932,15 @@ namespace Barotrauma.Items.Components
nameBlock.Wrap = true;
nameBlock.SetTextPos();
nameBlock.RectTransform.MinSize = new Point(0, (int)(nameBlock.TextSize.Y * nameBlock.TextScale));
}
}
if (!selectedItem.TargetItem.Description.IsNullOrEmpty())
{
var description = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedFrame.RectTransform),
selectedItem.TargetItem.Description,
font: GUIStyle.SmallFont, wrap: true);
description.Padding = new Vector4(0, description.Padding.Y, description.Padding.Z, description.Padding.W);
while (description.Rect.Height + nameBlock.Rect.Height > paddedFrame.Rect.Height)
{
var lines = description.WrappedText.Split('\n');
@@ -894,13 +951,13 @@ namespace Barotrauma.Items.Components
description.ToolTip = selectedItem.TargetItem.Description;
}
}
IEnumerable<Skill> inadequateSkills = Enumerable.Empty<Skill>();
if (user != null)
{
inadequateSkills = selectedItem.RequiredSkills.Where(skill => user.GetSkillLevel(skill.Identifier) < Math.Round(skill.Level * SkillRequirementMultiplier));
}
if (selectedItem.RequiredSkills.Any())
{
LocalizedString text = "";
@@ -921,9 +978,10 @@ namespace Barotrauma.Items.Components
float degreeOfSuccess = user == null ? 0.0f : FabricationDegreeOfSuccess(user, selectedItem.RequiredSkills);
if (degreeOfSuccess > 0.5f) { degreeOfSuccess = 1.0f; }
float requiredTime = overrideRequiredTime ??
(user == null ? selectedItem.RequiredTime : GetRequiredTime(selectedItem, user));
float requiredTime = overrideRequiredTime.TryUnwrap(out var time)
? time
: (user == null ? selectedItem.RequiredTime : GetRequiredTime(selectedItem, user));
if ((int)requiredTime > 0)
{
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedReqFrame.RectTransform),
@@ -946,7 +1004,6 @@ namespace Barotrauma.Items.Components
font: GUIStyle.SmallFont);
}
return true;
}
public void HighlightRecipe(string identifier, Color color)
@@ -1056,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()
{
if (requiredTimeBlock == null) { return; }

View File

@@ -991,7 +991,7 @@ namespace Barotrauma.Items.Components
if (Level.Loaded.StartLocation?.Type is { ShowSonarMarker: true })
{
DrawMarker(spriteBatch,
Level.Loaded.StartLocation.Name,
Level.Loaded.StartLocation.DisplayName.Value,
(Level.Loaded.StartOutpost != null ? "outpost" : "location").ToIdentifier(),
"startlocation",
Level.Loaded.StartExitPosition, transducerCenter,
@@ -1001,7 +1001,7 @@ namespace Barotrauma.Items.Components
if (Level.Loaded is { EndLocation.Type.ShowSonarMarker: true, Type: LevelData.LevelType.LocationConnection })
{
DrawMarker(spriteBatch,
Level.Loaded.EndLocation.Name,
Level.Loaded.EndLocation.DisplayName.Value,
(Level.Loaded.EndOutpost != null ? "outpost" : "location").ToIdentifier(),
"endlocation",
Level.Loaded.EndExitPosition, transducerCenter,

View File

@@ -216,7 +216,7 @@ namespace Barotrauma.Items.Components
}
};
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")
{
Enabled = autoPilot,
@@ -243,7 +243,7 @@ namespace Barotrauma.Items.Components
};
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")
{
Enabled = autoPilot,
@@ -389,7 +389,7 @@ namespace Barotrauma.Items.Components
if (!ObjectiveManager.AllActiveObjectivesCompleted())
{
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") });
exitOutpostPrompt.Buttons[0].OnClicked += (_, _) =>
{
@@ -509,9 +509,9 @@ namespace Barotrauma.Items.Components
noPowerTip = TextManager.Get("SteeringNoPowerTip");
autoPilotMaintainPosTip = TextManager.Get("SteeringAutoPilotMaintainPosTip");
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]",
GameMain.GameSession?.EndLocation == null ? "End" : GameMain.GameSession.EndLocation.Name);
GameMain.GameSession?.EndLocation == null ? "End" : GameMain.GameSession.EndLocation.DisplayName);
}
protected override void OnResolutionChanged()

View File

@@ -16,6 +16,8 @@ namespace Barotrauma.Items.Components
private GUIProgressBar powerIndicator;
private Vector2? debugDrawTargetPos;
public int UIElementHeight
{
get
@@ -422,9 +424,23 @@ namespace Barotrauma.Items.Components
if (GameMain.DebugDraw)
{
Vector2 firingPos = GetRelativeFiringPosition();
Vector2 endPos = firingPos + 3500 * GetBarrelDir();
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.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; }

View File

@@ -730,7 +730,7 @@ namespace Barotrauma
DraggingInventory = null;
subInventory.savedPosition = PlayerInput.MousePosition.ToPoint();
}
else
else if (DraggingInventory == subInventory)
{
subInventory.savedPosition = PlayerInput.MousePosition.ToPoint();
}
@@ -901,7 +901,7 @@ namespace Barotrauma
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++)
{
@@ -1107,7 +1107,7 @@ namespace Barotrauma
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);
CreateSlots();
@@ -1116,7 +1116,7 @@ namespace Barotrauma
draggableIndicatorOffset = DraggableIndicator.size * draggableIndicatorScale / 2f;
draggableIndicatorOffset += new Vector2(height / 2f - draggableIndicatorOffset.Y);
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))
@@ -1127,11 +1127,20 @@ namespace Barotrauma
prevScreenResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
prevUIScale = UIScale;
prevHUDScale = GUI.Scale;
positionUpdateQueued = true;
container.Inventory.positionUpdateQueued = true;
}
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);
}
}
@@ -1269,12 +1278,19 @@ namespace Barotrauma
if (DraggingItems.Count(it => !it.IsFullCondition && it.Condition > 0.0f) > 1 ||
selectedInventory.GetItemsAt(slotIndex).Count(it => !it.IsFullCondition && it.Condition > 0.0f) > 1)
{
allowCombine = false;
allowCombine = false;
}
int itemCount = 0;
foreach (Item item in DraggingItems)
{
bool success = selectedInventory.TryPutItem(item, slotIndex, allowSwapping: !anySuccess, allowCombine, Character.Controlled);
if (selectedInventory.GetItemAt(slotIndex)?.OwnInventory?.Container is { } container)
{
if (!container.AllowDragAndDrop || !container.DrawInventory)
{
allowCombine = false;
}
}
bool success = selectedInventory.TryPutItem(item, slotIndex, allowSwapping: !anySuccess, allowCombine, Character.Controlled);
if (success)
{
anySuccess = true;
@@ -1380,18 +1396,17 @@ namespace Barotrauma
protected static Rectangle GetSubInventoryHoverArea(SlotReference subSlot)
{
Rectangle hoverArea;
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)))
if (Character.Controlled == null)
{
//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);
return Rectangle.Empty;
}
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.Location += subSlot.Slot.DrawOffset.ToPoint();
@@ -1400,6 +1415,13 @@ namespace Barotrauma
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)
{
@@ -1584,11 +1606,16 @@ namespace Barotrauma
if (DraggingItems.Any() && inventory != null && slotIndex > -1 && slotIndex < inventory.visualSlots.Length)
{
var itemInSlot = inventory.slots[slotIndex].FirstOrDefault();
if (inventory.CanBePutInSlot(DraggingItems.First(), slotIndex))
{
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;
}

View File

@@ -286,7 +286,8 @@ namespace Barotrauma
{
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;
foreach (IDrawableComponent drawable in drawableComponents)
@@ -386,9 +387,9 @@ namespace Barotrauma
{
if (Prefab.ResizeHorizontal || Prefab.ResizeVertical)
{
Vector2 size = new Vector2(rect.Width, rect.Height);
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,
size, color: color,
textureScale: Vector2.One * Scale,
@@ -401,20 +402,7 @@ namespace Barotrauma
textureScale: Vector2.One * Scale,
depth: d);
}
foreach (var decorativeSprite in Prefab.DecorativeSprites)
{
if (!spriteAnimState[decorativeSprite].IsActive) { continue; }
Color decorativeSpriteColor = GetSpriteColor(decorativeSprite.Color);
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: decorativeSpriteColor,
textureScale: Vector2.One * Scale,
depth: Math.Min(depth + (decorativeSprite.Sprite.Depth - activeSprite.Depth), 0.999f));
}
DrawDecorativeSprites(spriteBatch, DrawPosition, flippedX && Prefab.CanSpriteFlipX, flippedY && Prefab.CanSpriteFlipY, rotation: 0, depth);
}
}
else
@@ -434,21 +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.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)
{
if (!spriteAnimState[decorativeSprite].IsActive) { continue; }
Color decorativeSpriteColor = GetSpriteColor(decorativeSprite.Color);
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)), decorativeSpriteColor,
RotationRad + rot, decorativeSprite.GetScale(spriteAnimState[decorativeSprite].RandomScaleFactor) * Scale, activeSprite.effects,
depth: Math.Min(depth + (decorativeSprite.Sprite.Depth - activeSprite.Depth), 0.999f));
}
DrawDecorativeSprites(spriteBatch, DrawPosition, flippedX && Prefab.CanSpriteFlipX, flippedY && Prefab.CanSpriteFlipY, -RotationRad, depth);
}
}
else if (body.Enabled)
@@ -492,21 +467,7 @@ namespace Barotrauma
float d = Math.Min(depth + (fadeInBrokenSprite.Sprite.Depth - activeSprite.Depth - 0.000001f), 0.999f);
body.Draw(spriteBatch, fadeInBrokenSprite.Sprite, color * fadeInBrokenSpriteAlpha, d, Scale);
}
foreach (var decorativeSprite in Prefab.DecorativeSprites)
{
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));
}
DrawDecorativeSprites(spriteBatch, body.DrawPosition, flipX: body.Dir < 0, flipY: false, rotation: body.Rotation, depth: depth);
}
foreach (var upgrade in Upgrades)
@@ -524,7 +485,6 @@ namespace Barotrauma
rotation, decorativeSprite.GetScale(spriteAnimState[decorativeSprite].RandomScaleFactor) * Scale, activeSprite.effects,
depth: depth + (decorativeSprite.Sprite.Depth - activeSprite.Depth));
}
}
activeSprite.effects = oldEffects;
@@ -569,8 +529,14 @@ namespace Barotrauma
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)));
drawPos = new Vector2(MathF.Floor(drawPos.X), MathF.Floor(drawPos.Y));
GUI.DrawRectangle(spriteBatch, drawPos, drawSize,
Color.White, false, 0, thickness: Math.Max(1, (int)(2 / Screen.Selected.Cam.Zoom)));
GUI.DrawRectangle(sb: spriteBatch,
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)
{
@@ -629,6 +595,55 @@ namespace Barotrauma
}
}
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)
{
if (impact > 1.0f &&
@@ -804,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)
{
activeEditors.Clear();
@@ -861,6 +889,11 @@ namespace Barotrauma
CanBeFocused = true
};
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"),
@@ -873,6 +906,7 @@ namespace Barotrauma
}
if (!SelectedList.Contains(this)) { FlipX(relativeToSub: false); }
ColorFlipButton(button, FlippedX);
if (rotationField != null) { rotationField.FloatValue = Rotation; }
return true;
}
};
@@ -889,6 +923,7 @@ namespace Barotrauma
}
if (!SelectedList.Contains(this)) { FlipY(relativeToSub: false); }
ColorFlipButton(button, FlippedY);
if (rotationField != null) { rotationField.FloatValue = Rotation; }
return true;
}
};
@@ -1553,6 +1588,23 @@ namespace Barotrauma
RemoveFromDroppedStack(allowClientExecute: true);
}
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:
throw new Exception($"Malformed incoming item event: unsupported event type {eventType}");
}
@@ -1953,5 +2005,13 @@ namespace Barotrauma
Inventory.DraggingSlot = null;
}
}
public void OnPlayerSkillsChanged()
{
foreach (ItemComponent ic in components)
{
ic.OnPlayerSkillsChanged();
}
}
}
}

View File

@@ -371,19 +371,27 @@ namespace Barotrauma
{
if (!ResizeHorizontal && !ResizeVertical)
{
sprite.Draw(spriteBatch, new Vector2(placeRect.Center.X, -(placeRect.Y - placeRect.Height / 2)), SpriteColor * 0.8f, scale: scale, rotate: rotation);
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
{
Vector2 position = placeRect.Location.ToVector2();
Vector2 placeSize = placeRect.Size.ToVector2();
sprite?.DrawTiled(
spriteBatch,
new Vector2(position.X, -position.Y),
placeSize,
spriteBatch: spriteBatch,
position: new Vector2(position.X, -position.Y),
targetSize: placeSize,
rotation: rotation,
textureScale: Vector2.One * scale,
color: SpriteColor * 0.8f);
color: SpriteColor * 0.8f,
spriteEffects: spriteEffects ^ sprite.effects);
}
}

View File

@@ -86,7 +86,7 @@ namespace Barotrauma
MathUtils.RoundTowardsClosest(center.Y, Submarine.GridSize.Y) - center.Y - Submarine.GridSize.Y / 2);
MapEntity.SelectedList.Clear();
assemblyEntities.ForEach(e => MapEntity.AddSelection(e));
entities.ForEach(e => MapEntity.AddSelection(e));
foreach (MapEntity mapEntity in assemblyEntities)
{

View File

@@ -1,10 +1,9 @@
using Barotrauma.Networking;
using FarseerPhysics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Linq;
using System.Collections.Generic;
using FarseerPhysics;
namespace Barotrauma
{

View File

@@ -162,6 +162,8 @@ namespace Barotrauma
CanBeVisible =
Sprite != 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));
}

View File

@@ -23,7 +23,14 @@ namespace Barotrauma
private Rectangle currentGridIndices;
public bool ForceRefreshVisibleObjects;
partial void RemoveProjSpecific()
{
visibleObjectsBack.Clear();
visibleObjectsMid.Clear();
visibleObjectsFront.Clear();
}
partial void UpdateProjSpecific(float deltaTime)
{
foreach (LevelObject obj in visibleObjectsBack)

View File

@@ -133,6 +133,8 @@ namespace Barotrauma.Lights
public Rectangle BoundingBox { get; private set; }
public bool IsInvalid { get; private set; }
public ConvexHull(Rectangle rect, bool isHorizontal, MapEntity parent)
{
shadowEffect ??= new BasicEffect(GameMain.Instance.GraphicsDevice)
@@ -481,15 +483,34 @@ namespace Barotrauma.Lights
for (int i = 0; i < 4; i++)
{
vertices[i].WorldPos = vertices[i].Pos;
ValidateVertex(vertices[i].WorldPos, "vertices[i].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;
ValidateVertex(segments[i].End.WorldPos, "segments[i].End.Pos");
}
if (ParentEntity == null || ParentEntity.Submarine == null) { return; }
for (int i = 0; i < 4; i++)
{
vertices[i].WorldPos += ParentEntity.Submarine.DrawPosition;
ValidateVertex(vertices[i].WorldPos, "vertices[i].WorldPos");
segments[i].Start.WorldPos += ParentEntity.Submarine.DrawPosition;
ValidateVertex(segments[i].Start.WorldPos, "segments[i].Start.WorldPos");
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);
}
}
}

View File

@@ -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)
{
if (!LightingEnabled) { return; }
@@ -259,7 +268,8 @@ namespace Barotrauma.Lights
{
if (!light.Enabled) { continue; }
if ((light.Color.A < 1 || light.Range < 1.0f) && !light.LightSourceParams.OverrideLightSpriteAlpha.HasValue) { continue; }
//above the top boundary of the level (in an inactive respawn shuttle?)
if (Level.Loaded != null && light.WorldPosition.Y > Level.Loaded.Size.Y) { continue; }
if (light.ParentBody != null)
{
light.ParentBody.UpdateDrawPosition();
@@ -801,6 +811,8 @@ namespace Barotrauma.Lights
public void ClearLights()
{
activeLights.Clear();
activeLightsWithLightVolume.Clear();
lights.Clear();
}
}

View File

@@ -37,9 +37,9 @@ namespace Barotrauma.Lights
TextureRange = range;
if (OverrideLightTexture != null)
{
TextureRange += Math.Max(
Math.Abs(OverrideLightTexture.RelativeOrigin.X - 0.5f) * OverrideLightTexture.size.X,
Math.Abs(OverrideLightTexture.RelativeOrigin.Y - 0.5f) * OverrideLightTexture.size.Y);
TextureRange *= 1.0f + Math.Max(
Math.Abs(OverrideLightTexture.RelativeOrigin.X - 0.5f),
Math.Abs(OverrideLightTexture.RelativeOrigin.Y - 0.5f));
}
}
}
@@ -238,7 +238,11 @@ namespace Barotrauma.Lights
private bool needsRecalculationWhenUpToDate;
public bool NeedsRecalculation
{
get { return needsRecalculation; }
get
{
if (ParentBody?.UserData is Item it && it.Prefab.Identifier == "flashlight") { return true; }
return needsRecalculation;
}
set
{
if (!needsRecalculation && value)
@@ -708,6 +712,7 @@ namespace Barotrauma.Lights
{
foreach (ConvexHull hull in chList.List)
{
if (hull.IsInvalid) { continue; }
if (!chList.IsHidden.Contains(hull))
{
//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);
}
const float MinPointDistance = 6;
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
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 &&
Math.Abs(points[i].WorldPos.Y - points[j].WorldPos.Y) < MinPointDistance)
@@ -892,14 +897,14 @@ namespace Barotrauma.Lights
}
}
var compareCCW = new CompareSegmentPointCW(drawPos);
try
{
points.Sort(compareCCW);
var compareCW = new CompareSegmentPointCW(drawPos);
points.Sort(compareCW);
}
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)
{
sb.AppendLine(sp.Pos.ToString());
@@ -914,7 +919,11 @@ namespace Barotrauma.Lights
verts.Clear();
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;
//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
//we definitely want to generate new geometry here
verts.Add(isPoint1 ? p.WorldPos : intersection1.pos);
verts.Add(isPoint2 ? p.WorldPos : intersection2.pos);
markAsVisible = true;
if (isPoint1)
{
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)
{
@@ -959,15 +995,32 @@ namespace Barotrauma.Lights
//remove points that are very close to each other
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 &&
Math.Abs(verts[i].Y - verts[j].Y) < 6)
if (Math.Abs(verts[i].X - verts[j].X) < MinPointDistance &&
Math.Abs(verts[i].Y - verts[j].Y) < MinPointDistance)
{
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;
state = LightVertexState.PendingVertexRecalculation;
}
@@ -1114,7 +1167,7 @@ namespace Barotrauma.Lights
//add the normals together and use some magic numbers to create
//a somewhat useful/good-looking blur
float blurDistance = 40.0f;
float blurDistance = 25.0f;
Vector2 nDiff = nDiff1 * blurDistance;
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>
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;
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);
}
}
}*/
if (DeformableLightSprite != null)
{
@@ -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)
{
if (Range < 1.0f || Color.A < 1 || CurrentBrightness <= 0.0f) { return; }

View File

@@ -291,14 +291,14 @@ namespace Barotrauma
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);
if (!messages.Any()) { return; }
string msg = messages.GetRandom(Rand.RandSync.Unsynced)
.Replace("[previousname]", $"‖color:gui.yellow‖{prevName}‖end‖")
.Replace("[name]", $"‖color:gui.yellow‖{location.Name}‖end‖");
.Replace("[name]", $"‖color:gui.yellow‖{location.DisplayName}‖end‖");
location.LastTypeChangeMessage = msg;
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;
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())
{
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);
var connectionSprite = connection.Passed ? generationParams.PassedConnectionSprite : generationParams.ConnectionSprite;
if (connectionSprite?.Texture == null) { continue; }
Color segmentColor = connectionColor;
int segmentWidth = width;
@@ -1092,9 +1093,6 @@ namespace Barotrauma
segmentWidth /= 2;
segmentColor = connection.Passed ? generationParams.ConnectionColor : generationParams.UnvisitedConnectionColor;
}
else
{
}
}
spriteBatch.Draw(connectionSprite.Texture,

View File

@@ -162,21 +162,9 @@ namespace Barotrauma
{
if (SelectedAny)
{
if (SelectedList.Any(static t => t is Item it && it.GetComponent<CircuitBox>() is not null))
{
GUI.AskForConfirmation(SubEditorScreen.CircuitBoxDeletionWarningHeader, SubEditorScreen.CircuitBoxDeletionWarningBody, onConfirm: Delete);
}
else
{
Delete();
}
void Delete()
{
SubEditorScreen.StoreCommand(new AddOrDeleteCommand(new List<MapEntity>(SelectedList), true));
SelectedList.ForEach(static e => { if (!e.Removed) { e.Remove(); } });
SelectedList.Clear();
}
SubEditorScreen.StoreCommand(new AddOrDeleteCommand(new List<MapEntity>(SelectedList), true));
SelectedList.ForEachMod(static e => { if (!e.Removed) { e.Remove(); } });
SelectedList.Clear();
}
}
@@ -1332,12 +1320,15 @@ namespace Barotrauma
HashSet<MapEntity> foundEntities = new HashSet<MapEntity>();
Rectangle selectionRect = Submarine.AbsRect(pos, size);
Quad2D selectionQuad = Quad2D.FromSubmarineRectangle(selectionRect);
foreach (MapEntity entity in MapEntityList)
{
if (!entity.SelectableInEditor) { continue; }
if (Submarine.RectsOverlap(selectionRect, entity.rect))
Quad2D entityQuad = entity.GetTransformedQuad();
if (selectionQuad.Intersects(entityQuad))
{
foundEntities.Add(entity);
entity.IsIncludedInSelection = true;

View File

@@ -236,11 +236,14 @@ namespace Barotrauma
public override bool IsVisible(Rectangle worldView)
{
Rectangle worldRect = WorldRect;
RectangleF worldRect = Quad2D.FromSubmarineRectangle(WorldRect).Rotated(
FlippedX != FlippedY
? rotationRad
: -rotationRad).BoundingAxisAlignedRectangle;
Vector2 worldPos = WorldPosition;
Vector2 min = new Vector2(worldRect.X, worldRect.Y - worldRect.Height);
Vector2 max = new Vector2(worldRect.Right, worldRect.Y);
Vector2 min = new Vector2(worldRect.X, worldRect.Y);
Vector2 max = new Vector2(worldRect.Right, worldRect.Y + worldRect.Height);
foreach (DecorativeSprite decorativeSprite in Prefab.DecorativeSprites)
{
float scale = decorativeSprite.GetScale(spriteAnimState[decorativeSprite].RandomScaleFactor) * Scale;
@@ -312,7 +315,12 @@ namespace Barotrauma
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)));
}

View File

@@ -96,9 +96,6 @@ namespace Barotrauma
public override void DrawPlacing(SpriteBatch spriteBatch, Rectangle placeRect, float scale = 1.0f, float rotation = 0.0f, SpriteEffects spriteEffects = SpriteEffects.None)
{
SpriteEffects oldEffects = Sprite.effects;
Sprite.effects ^= spriteEffects;
var position = placeRect.Location.ToVector2().FlipY();
position += placeRect.Size.ToVector2() * 0.5f;
@@ -109,9 +106,8 @@ namespace Barotrauma
color: Color.White * 0.8f,
origin: placeRect.Size.ToVector2() * 0.5f,
rotation: rotation,
textureScale: TextureScale * scale);
Sprite.effects = oldEffects;
textureScale: TextureScale * scale,
spriteEffects: spriteEffects ^ Sprite.effects);
}
}
}

View File

@@ -421,7 +421,7 @@ namespace Barotrauma
float scale = element.GetAttributeFloat("scale", 1f);
Color color = element.GetAttributeColor("spritecolor", Color.White);
float rotation = element.GetAttributeFloat("rotation", 0f);
float rotationRad = MathHelper.ToRadians(element.GetAttributeFloat("rotation", 0f));
MapEntityPrefab prefab;
if (element.NameAsIdentifier() == "item"
@@ -455,7 +455,7 @@ namespace Barotrauma
ItemPrefab itemPrefab = prefab as ItemPrefab;
if (itemPrefab != null)
{
BakeItemComponents(itemPrefab, rect, color, scale, rotation, depth, out overrideSprite);
BakeItemComponents(itemPrefab, rect, color, scale, rotationRad, depth, out overrideSprite);
}
if (!overrideSprite)
@@ -485,13 +485,15 @@ namespace Barotrauma
MathUtils.PositiveModulo((int)-textureOffset.Y, prefab.Sprite.SourceRect.Height));
prefab.Sprite.DrawTiled(
spriteRecorder,
rect.Location.ToVector2() * new Vector2(1f, -1f),
rect.Size.ToVector2(),
spriteBatch: spriteRecorder,
position: new Vector2(rect.X + rect.Width / 2, -(rect.Y - rect.Height / 2)),
targetSize: rect.Size.ToVector2(),
origin: rect.Size.ToVector2() * new Vector2(0.5f, 0.5f),
color: color,
startOffset: backGroundOffset,
textureScale: textureScale * scale,
depth: depth);
depth: depth,
rotation: rotationRad);
}
else if (itemPrefab != null)
{
@@ -552,7 +554,7 @@ namespace Barotrauma
spritePos * new Vector2(1f, -1f),
color,
prefab.Sprite.Origin,
rotation,
rotationRad,
scale,
prefab.Sprite.effects, depth);
@@ -564,7 +566,7 @@ namespace Barotrauma
if (flippedX) { offset.X = -offset.X; }
if (flippedY) { offset.Y = -offset.Y; }
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));
}
}
@@ -577,7 +579,7 @@ namespace Barotrauma
private void BakeItemComponents(
ItemPrefab prefab,
Rectangle rect, Color color,
float scale, float rotation, float depth,
float scale, float rotationRad, float depth,
out bool overrideSprite)
{
overrideSprite = false;
@@ -607,7 +609,7 @@ namespace Barotrauma
Vector2 relativeBarrelPos = barrelPos * prefab.Scale - new Vector2(rect.Width / 2, rect.Height / 2);
var transformedBarrelPos = MathUtils.RotatePoint(
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);
drawPos.Y = -drawPos.Y;
@@ -615,13 +617,13 @@ namespace Barotrauma
railSprite?.Draw(spriteRecorder,
drawPos,
color,
rotation + MathHelper.PiOver2, scale,
rotationRad, scale,
SpriteEffects.None, depth + (railSprite.Depth - prefab.Sprite.Depth));
barrelSprite?.Draw(spriteRecorder,
drawPos,
color,
rotation + MathHelper.PiOver2, scale,
rotationRad, scale,
SpriteEffects.None, depth + (barrelSprite.Depth - prefab.Sprite.Depth));
break;
@@ -781,7 +783,7 @@ namespace Barotrauma
previewFrame = null;
}
spriteRecorder?.Dispose(); spriteRecorder = null;
camera?.Dispose(); camera = null;
camera = null;
isDisposed = true;
}
}

View File

@@ -2222,7 +2222,7 @@ namespace Barotrauma.Networking
}
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);
}

View File

@@ -181,33 +181,6 @@ namespace Barotrauma.Networking
};
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),
TextManager.AddPunctuation(':', TextManager.Get("ServerListVersion"),
GameVersion == new Version(0, 0, 0, 0) ? TextManager.Get("Unknown") : GameVersion.ToString()))
@@ -263,6 +236,59 @@ namespace Barotrauma.Networking
{
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 -----------------------------------------------------------------------------
var playStyleContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.15f), content.RectTransform), isHorizontal: true)

View File

@@ -254,7 +254,7 @@ namespace Barotrauma
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
};
@@ -598,9 +598,10 @@ namespace Barotrauma
break;
case CampaignMode.InteractionType.Crew:
CrewManagement.UpdateCrew();
CrewManagement.UpdateHireables();
break;
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);
break;
case CampaignMode.InteractionType.Map:

View File

@@ -258,8 +258,8 @@ namespace Barotrauma
graphics.BlendState = BlendState.NonPremultiplied;
graphics.SamplerStates[0] = SamplerState.LinearWrap;
Quad.UseBasicEffect(renderTargetBackground);
Quad.Render();
GraphicsQuad.UseBasicEffect(renderTargetBackground);
GraphicsQuad.Render();
//Draw the rest of the structures, characters and front structures
spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.NonPremultiplied, null, DepthStencilState.None, null, null, cam.Transform);
@@ -312,8 +312,8 @@ namespace Barotrauma
graphics.BlendState = BlendState.Opaque;
graphics.SamplerStates[0] = SamplerState.LinearWrap;
Quad.UseBasicEffect(renderTarget);
Quad.Render();
GraphicsQuad.UseBasicEffect(renderTarget);
GraphicsQuad.Render();
//draw alpha blended particles that are inside a sub
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, null, DepthStencilState.DepthRead, null, null, cam.Transform);
@@ -379,8 +379,8 @@ namespace Barotrauma
graphics.DepthStencilState = DepthStencilState.None;
graphics.SamplerStates[0] = SamplerState.LinearWrap;
graphics.BlendState = CustomBlendStates.Multiplicative;
Quad.UseBasicEffect(GameMain.LightManager.LightMap);
Quad.Render();
GraphicsQuad.UseBasicEffect(GameMain.LightManager.LightMap);
GraphicsQuad.Render();
}
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, SamplerState.LinearWrap, DepthStencilState.None, null, null, cam.Transform);
@@ -389,6 +389,8 @@ namespace Barotrauma
c.DrawFront(spriteBatch, cam);
}
GameMain.LightManager.DebugDrawVertices(spriteBatch);
Level.Loaded?.DrawDebugOverlay(spriteBatch, cam);
if (GameMain.DebugDraw)
{
@@ -437,7 +439,7 @@ namespace Barotrauma
graphics.SamplerStates[0] = SamplerState.PointClamp;
graphics.SamplerStates[1] = SamplerState.PointClamp;
GameMain.LightManager.LosEffect.CurrentTechnique.Passes[0].Apply();
Quad.Render();
GraphicsQuad.Render();
graphics.SamplerStates[0] = SamplerState.LinearWrap;
graphics.SamplerStates[1] = SamplerState.LinearWrap;
}
@@ -505,7 +507,7 @@ namespace Barotrauma
graphics.DepthStencilState = DepthStencilState.None;
if (string.IsNullOrEmpty(postProcessTechnique))
{
Quad.UseBasicEffect(renderTargetFinal);
GraphicsQuad.UseBasicEffect(renderTargetFinal);
}
else
{
@@ -514,7 +516,7 @@ namespace Barotrauma
PostProcessEffect.CurrentTechnique = PostProcessEffect.Techniques[postProcessTechnique];
PostProcessEffect.CurrentTechnique.Passes[0].Apply();
}
Quad.Render();
GraphicsQuad.Render();
if (fadeToBlackState > 0.0f)
{

View File

@@ -221,7 +221,17 @@ namespace Barotrauma
currentLevelData.AllowInvalidOutpost = allowInvalidOutpost.Selected;
var dummyLocations = GameSession.CreateDummyLocations(currentLevelData);
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);
if (!wasLevelLoaded || Cam.Position.X < 0 || Cam.Position.Y < 0 || Cam.Position.Y > Level.Loaded.Size.X || Cam.Position.Y > Level.Loaded.Size.Y)
{

View File

@@ -165,6 +165,7 @@ namespace Barotrauma
}
}
#else
SpamServerFilters.RequestGlobalSpamFilter();
FetchRemoteContent();
#endif

View File

@@ -1520,6 +1520,7 @@ namespace Barotrauma
};
bool nameChangePending = isGameRunning && GameMain.Client.PendingName != string.Empty && GameMain.Client?.Character?.Name != GameMain.Client.PendingName;
changesPendingText?.Parent?.RemoveChild(changesPendingText);
changesPendingText = null;
if (TabMenu.PendingChanges)

View File

@@ -655,6 +655,7 @@ namespace Barotrauma
ScrollBarVisible = true,
OnSelected = (btn, obj) =>
{
if (GUI.MouseOn is GUIButton) { return false; }
if (obj is not ServerInfo serverInfo) { return false; }
joinButton.Enabled = true;
@@ -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)
{
var children = serverList.Content.RectTransform.Children.Reverse().ToList();
@@ -973,7 +981,7 @@ namespace Barotrauma
}
}
private void FilterServers()
public void FilterServers()
{
RemoveMsgFromServerList(MsgUserData.NoMatchingServers);
foreach (GUIComponent child in serverList.Content.Children)
@@ -1013,6 +1021,7 @@ namespace Barotrauma
return false;
}
#endif
if (SpamServerFilters.IsFiltered(serverInfo)) { 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) },
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)
{
Stretch = false
};
UpdateServerInfoUI(serverInfo);
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)
@@ -1571,7 +1734,6 @@ namespace Barotrauma
serverFrame.UserData = serverInfo;
serverFrame.ToolTip = "";
var serverContent = serverFrame.Children.First() as GUILayoutGroup;
serverContent.ClearChildren();
@@ -1583,15 +1745,14 @@ namespace Barotrauma
new RectTransform(new Vector2(columns[label].RelativeWidth, 1.0f), serverContent.RectTransform),
style: null);
}
void errorTooltip(RichString toolTip)
void disableElementFocus()
{
sections.Values.ForEach(c =>
{
c.CanBeFocused = false;
c.Children.First().CanBeFocused = false;
});
serverFrame.ToolTip = toolTip;
}
RectTransform columnRT(ColumnLabel label, float scale = 0.95f)
@@ -1611,7 +1772,7 @@ namespace Barotrauma
NetworkMember.IsCompatible(GameMain.Version, serverInfo.GameVersion),
UserData = "compatible"
};
var passwordBox = new GUITickBox(columnRT(ColumnLabel.ServerListHasPassword, scale: 0.6f), label: "", style: "GUIServerListPasswordTickBox")
{
Selected = serverInfo.HasPassword,
@@ -1664,9 +1825,10 @@ namespace Barotrauma
serverPingText.TextColor = Color.DarkRed;
}
LocalizedString toolTip = "";
if (!serverInfo.Checked)
{
errorTooltip(TextManager.Get("ServerOffline"));
toolTip = TextManager.Get("ServerOffline");
serverName.TextColor *= 0.8f;
serverPlayers.TextColor *= 0.8f;
}
@@ -1681,7 +1843,6 @@ namespace Barotrauma
}
else if (!compatibleBox.Selected)
{
LocalizedString toolTip = "";
if (serverInfo.GameVersion != GameMain.Version)
{
toolTip = TextManager.GetWithVariable("ServerListIncompatibleVersion", "[version]", serverInfo.GameVersion.ToString());
@@ -1707,14 +1868,12 @@ namespace Barotrauma
toolTip += '\n' + TextManager.GetWithVariable("workshopitemdownloadprompttruncated", "[number]", (incompatibleModNames.Count - maxIncompatibleToList).ToString());
}
}
errorTooltip(toolTip);
serverName.TextColor *= 0.5f;
serverPlayers.TextColor *= 0.5f;
}
else
{
LocalizedString toolTip = "";
foreach (var contentPackage in serverInfo.ContentPackages)
{
if (ContentPackageManager.EnabledPackages.All.None(cp => cp.Hash.StringRepresentation == contentPackage.Hash))
@@ -1724,8 +1883,11 @@ namespace Barotrauma
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)
{

View File

@@ -16,9 +16,6 @@ namespace Barotrauma
{
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 MaxWalls = 500;
public const int MaxItems = 5000;
@@ -1560,8 +1557,17 @@ namespace Barotrauma
if (editorSelectedTime.TryUnwrap(out DateTime selectedTime))
{
TimeSpan timeInEditor = DateTime.Now - selectedTime;
SteamAchievementManager.IncrementStat("hoursineditor".ToIdentifier(), (float)timeInEditor.TotalHours);
editorSelectedTime = Option<DateTime>.None();
if (timeInEditor.TotalSeconds > Timing.TotalTime)
{
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
@@ -3933,28 +3939,15 @@ namespace Barotrauma
new ContextMenuOption("editor.cut", isEnabled: hasTargets, onSelected: () => MapEntity.Cut(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("delete", isEnabled: hasTargets, onSelected: () => RemoveEntitiesWithPossibleWarning(targets)),
new ContextMenuOption(TextManager.Get("editortip.shiftforextraoptions") + '\n' + TextManager.Get("editortip.altforruler"), isEnabled: false, onSelected: null));
}
}
public static void RemoveEntitiesWithPossibleWarning(List<MapEntity> targets)
{
if (targets.Any(static t => t is Item it && it.GetComponent<CircuitBox>() is not 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(); }
}
new ContextMenuOption("delete", isEnabled: hasTargets, onSelected: () =>
{
StoreCommand(new AddOrDeleteCommand(targets, true));
foreach (var me in targets)
{
if (!me.Removed) { me.Remove(); }
}
}),
new ContextMenuOption(TextManager.GetWithVariable("editortip.shiftforextraoptions", "[button]", PlayerInput.SecondaryMouseLabel) + '\n' + TextManager.Get("editortip.altforruler"), isEnabled: false, onSelected: null));
}
}
@@ -5485,9 +5478,11 @@ namespace Barotrauma
{
foreach (LightComponent lightComponent in item.GetComponents<LightComponent>())
{
lightComponent.Light.Color = item.Container != null || (item.body != null && !item.body.Enabled) ?
Color.Transparent :
lightComponent.LightColor;
lightComponent.Light.Color =
item.body == null || item.body.Enabled ||
(item.ParentInventory is ItemInventory itemInventory && !itemInventory.Container.HideItems) ?
lightComponent.LightColor :
Color.Transparent;
lightComponent.Light.LightSpriteEffect = lightComponent.Item.SpriteEffects;
}
}

View File

@@ -727,7 +727,21 @@ namespace Barotrauma
Slider(layout, (0.75f, 1.25f), 51, Percentage, unsavedConfig.Graphics.InventoryScale, v => unsavedConfig.Graphics.InventoryScale = v);
Label(layout, TextManager.Get("TextScale"), GUIStyle.SubHeadingFont);
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
Spacer(layout);
var statisticsTickBox = new GUITickBox(NewItemRectT(layout), TextManager.Get("statisticsconsenttickbox"))

View File

@@ -1,15 +1,16 @@
using System;
using NVorbis;
using OpenAL;
using NVorbis;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Xml.Linq;
namespace Barotrauma.Sounds
{
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 const int AMPLITUDE_SAMPLE_COUNT = 4410; //100ms in a 44100hz file
@@ -101,7 +102,7 @@ namespace Barotrauma.Sounds
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 (samplePos >= streamReader.TotalSamples * streamReader.Channels * 2) return 0;
if (samplePos >= MaxStreamSamplePos) { return 0; }
samplePos /= streamReader.Channels * 2;
streamReader.DecodedPosition = samplePos;

View File

@@ -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;
public bool IsPlaying
@@ -564,7 +576,7 @@ namespace Barotrauma.Sounds
throw new Exception("Generated streamBuffer[" + i.ToString() + "] is invalid! " + debugName);
}
}
Sound.Owner.InitStreamThread();
Sound.Owner.InitUpdateChannelThread();
SetProperties();
}
}
@@ -609,6 +621,7 @@ namespace Barotrauma.Sounds
public void FadeOutAndDispose()
{
FadingOutAndDisposing = true;
Sound.Owner.InitUpdateChannelThread();
}
public void Dispose()

View File

@@ -39,7 +39,7 @@ namespace Barotrauma.Sounds
public bool Disconnected { get; private set; }
private Thread streamingThread;
private Thread updateChannelsThread;
private Vector3 listenerPosition;
public Vector3 ListenerPosition
@@ -201,7 +201,7 @@ namespace Barotrauma.Sounds
public SoundManager()
{
loadedSounds = new List<Sound>();
streamingThread = null;
updateChannelsThread = null;
sourcePools = new SoundSourcePool[2];
playingChannels[(int)SourcePoolIndex.Default] = new SoundChannel[SOURCE_COUNT];
@@ -696,7 +696,7 @@ namespace Barotrauma.Sounds
CompressionDynamicRangeGain = 1.0f;
}
if (streamingThread == null || streamingThread.ThreadState.HasFlag(ThreadState.Stopped))
if (updateChannelsThread == null || updateChannelsThread.ThreadState.HasFlag(ThreadState.Stopped))
{
bool startedStreamThread = false;
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].IsStream && playingChannels[i][j].IsPlaying)
{
InitStreamThread();
InitUpdateChannelThread();
startedStreamThread = true;
}
if (startedStreamThread) { break; }
@@ -727,37 +727,43 @@ namespace Barotrauma.Sounds
SetCategoryGainMultiplier("music", GameSettings.CurrentConfig.Audio.MusicVolume, 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; }
bool isStreamThreadDying;
bool isUpdateChannelsThreadDying;
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;
streamingThread = new Thread(UpdateStreaming)
needsUpdateChannels = true;
updateChannelsThread = new Thread(UpdateChannels)
{
Name = "SoundManager Streaming Thread",
Name = "SoundManager.UpdateChannels Thread",
IsBackground = true //this should kill the thread if the game crashes
};
streamingThread.Start();
updateChannelsThread.Start();
}
}
bool areStreamsPlaying = false;
ManualResetEvent streamMre = null;
private bool needsUpdateChannels = false;
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;
while (!killThread)
{
@@ -784,6 +790,7 @@ namespace Barotrauma.Sounds
}
else if (playingChannels[i][j].FadingOutAndDisposing)
{
killThread = false;
playingChannels[i][j].Gain -= 0.1f;
if (playingChannels[i][j].Gain <= 0.0f)
{
@@ -794,18 +801,18 @@ namespace Barotrauma.Sounds
}
}
}
streamMre.WaitOne(10);
streamMre.Reset();
updateChannelsMre.WaitOne(10);
updateChannelsMre.Reset();
lock (threadDeathMutex)
{
areStreamsPlaying = !killThread;
needsUpdateChannels = !killThread;
}
}
}
public void ForceStreamUpdate()
{
streamMre?.Set();
updateChannelsMre?.Set();
}
private void ReloadSounds()
@@ -824,12 +831,12 @@ namespace Barotrauma.Sounds
{
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--)
{
if (keepSounds)

View File

@@ -709,6 +709,11 @@ namespace Barotrauma
{
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;
}
}

View File

@@ -241,6 +241,7 @@ namespace Barotrauma
public readonly bool MuteIntensityTracks;
public readonly float? ForceIntensityTrack;
public readonly bool StartFromRandomTime;
public readonly bool ContinueFromPreviousTime;
public int PreviousTime;
@@ -255,6 +256,7 @@ namespace Barotrauma
ForceIntensityTrack = element.GetAttributeFloat(nameof(ForceIntensityTrack), 0.0f);
}
Volume = element.GetAttributeFloat(nameof(Volume), 1.0f);
StartFromRandomTime = element.GetAttributeBool(nameof(StartFromRandomTime), false);
ContinueFromPreviousTime = element.GetAttributeBool(nameof(ContinueFromPreviousTime), false);
}
}

View File

@@ -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);
}
}
}
}
}

View File

@@ -284,13 +284,22 @@ namespace Barotrauma
}
}
public void DrawTiled(ISpriteBatch spriteBatch, Vector2 position, Vector2 targetSize, float rotation = 0f, Vector2? origin = null,
Color? color = null, Vector2? startOffset = null, Vector2? textureScale = null, float? depth = null)
public void DrawTiled(ISpriteBatch spriteBatch,
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; }
bool flipHorizontal = (effects & SpriteEffects.FlipHorizontally) != 0;
bool flipVertical = (effects & SpriteEffects.FlipVertically) != 0;
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; }
@@ -311,7 +320,7 @@ namespace Barotrauma
Vector2 transformedPos = slicePos - position;
transformedPos = advanceX * transformedPos.X + advanceY * transformedPos.Y;
transformedPos += position - transformedOrigin;
spriteBatch.Draw(texture, transformedPos, sliceRect, drawColor, addedRotation, Vector2.Zero, scale, effects, depth ?? this.depth);
spriteBatch.Draw(texture, transformedPos, sliceRect, drawColor, addedRotation, Vector2.Zero, scale, spriteEffects.Value, depth ?? this.depth);
}
//wrap the drawOffset inside the sourceRect

View File

@@ -210,7 +210,7 @@ namespace Barotrauma
statusEffect.soundChannel.FadeOutAndDispose();
statusEffect.soundChannel = null;
}
else
else if (statusEffect.soundEmitter is { Removed: false })
{
statusEffect.soundChannel.Position = new Vector3(statusEffect.soundEmitter.WorldPosition, 0.0f);
if (doMuffleCheck && !statusEffect.ignoreMuffling)

View File

@@ -101,13 +101,36 @@ namespace Barotrauma.Steam
{
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(
new RectTransform(Vector2.Zero, msgBox.Content.RectTransform),
onUpdate: (f, component) =>
{
float progress = 0.0f;
if (item.IsDownloading) { progress = item.DownloadAmount; }
else if (itemDownloadProgress.BarSize > 0.0f) { progress = 1.0f; }
if (item.IsDownloading)
{
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,
MathHelper.Lerp(itemDownloadProgress.BarSize, progress, 0.1f));
@@ -134,9 +157,16 @@ namespace Barotrauma.Steam
{
foreach (var item in itemsToDownload)
{
DebugConsole.Log($"Reinstalling {item.Title}...");
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.");
}
}
}

View File

@@ -298,7 +298,7 @@ namespace Barotrauma.Steam
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))
{
@@ -306,13 +306,26 @@ namespace Barotrauma.Steam
}
return;
}
else if (CanBeInstalled(id)
&& !ContentPackageManager.WorkshopPackages.Any(p =>
else if (!CanBeInstalled(id))
{
DebugConsole.Log($"Cannot install {id}");
InstallWaiter.StopWaiting(id);
}
else if (ContentPackageManager.WorkshopPackages.Any(p =>
p.UgcId.TryUnwrap(out var ugcId)
&& ugcId is SteamWorkshopId workshopId
&& workshopId.Value == id)
&& !InstallTaskCounter.IsInstalling(id))
&& workshopId.Value == 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));
}
}

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Xml.Linq;
using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
@@ -116,6 +117,9 @@ namespace Barotrauma
private readonly bool WasDeleted;
private readonly List<AddOrDeleteCommand> ContainedItemsCommand = new List<AddOrDeleteCommand>();
// We need to 'snapshot' the state of the circuit box and the best way to do that is to save it to XML.
private readonly List<XElement> CircuitBoxData = new List<XElement>();
/// <summary>
/// Creates a command where all entities share the same state.
/// </summary>
@@ -143,13 +147,17 @@ namespace Barotrauma
List<MapEntity> itemsToDelete = new List<MapEntity>();
foreach (MapEntity receiver in Receivers)
{
if (receiver is Item it)
if (receiver is not Item it) { continue; }
foreach (var cb in it.GetComponents<CircuitBox>())
{
foreach (ItemContainer component in it.GetComponents<ItemContainer>())
{
if (component.Inventory == null) { continue; }
itemsToDelete.AddRange(component.Inventory.AllItems.Where(item => !item.Removed));
}
CircuitBoxData.Add(cb.Save(new XElement("root")));
}
foreach (ItemContainer component in it.GetComponents<ItemContainer>())
{
if (component.Inventory == null) { continue; }
itemsToDelete.AddRange(component.Inventory.AllItems.Where(static item => !item.Removed));
}
}
@@ -192,34 +200,50 @@ namespace Barotrauma
}
public override void Execute()
{
var items = DeleteUndelete(true);
ContainedItemsCommand?.ForEach(static cmd => cmd.Execute());
CircuitBoxWorkaround(items);
}
=> Process(true);
public override void UnExecute()
=> Process(false);
private void Process(bool redo)
{
var items = DeleteUndelete(false);
ContainedItemsCommand?.ForEach(static cmd => cmd.UnExecute());
CircuitBoxWorkaround(items);
var items = DeleteUndelete(redo);
foreach (var cmd in ContainedItemsCommand)
{
cmd.Process(redo);
}
ApplyCircuitBoxDataIfAny(items);
}
// FIXME Temporary workaround for circuit boxes throwing console errors and breaking completely when undoing a deletion
private static void CircuitBoxWorkaround(Option<ImmutableArray<MapEntity>> entitiesOption)
/// <summary>
/// We need to manually copy over the circuit box data because of how the undo handles inventory items.
/// The undo system recursively deletes inventory items and creates a separate command for each one.
/// This causes the circuit box to lose its internal inventory when it's cloned and then restored and make it
/// unable to load the state from XML.
///
/// The workaround to this is to ignore the XML that is being loaded when the item is created and instead
/// save the XML into the command and then load it back after the undo system has restored the items which
/// is what this function does.
/// </summary>
private void ApplyCircuitBoxDataIfAny(ImmutableArray<Item> items)
{
if (!entitiesOption.TryUnwrap(out var entities)) { return; }
foreach (var entity in entities)
int cbIndex = 0;
foreach (var newItem in items)
{
if (entity is not Item it) { continue; }
if (it.GetComponent<CircuitBox>() is not null)
foreach (ItemComponent component in newItem.Components)
{
foreach (var container in it.GetComponents<ItemContainer>())
if (component is not CircuitBox cb) { continue; }
if (cbIndex < 0 || cbIndex >= CircuitBoxData.Count)
{
container.Inventory.DeleteAllItems();
DebugConsole.ThrowError("Unable to restore wiring in circuit box, index out of range.");
continue;
}
var cbData = CircuitBoxData[cbIndex];
cbIndex++;
cb.LoadFromXML(new ContentXElement(null, cbData));
}
}
}
@@ -238,15 +262,19 @@ namespace Barotrauma
Receivers.Clear();
PreviousInventories?.Clear();
ContainedItemsCommand?.ForEach(static cmd => cmd.Cleanup());
CircuitBoxData.Clear();
}
private Option<ImmutableArray<MapEntity>> DeleteUndelete(bool redo)
private ImmutableArray<Item> DeleteUndelete(bool redo)
{
bool wasDeleted = WasDeleted;
// We are redoing instead of undoing, flip the behavior
if (redo) { wasDeleted = !wasDeleted; }
// collect newly created items so we can update their circuit boxes if any
var builder = ImmutableArray.CreateBuilder<Item>();
if (wasDeleted)
{
Debug.Assert(Receivers.All(static entity => entity.GetReplacementOrThis().Removed), "Tried to redo a deletion but some items were not deleted");
@@ -260,6 +288,7 @@ namespace Barotrauma
if (receiver.GetReplacementOrThis() is Item item && clone is Item cloneItem)
{
builder.Add(cloneItem);
foreach (ItemComponent ic in item.Components)
{
int index = item.GetComponentIndex(ic);
@@ -303,7 +332,7 @@ namespace Barotrauma
clone.Submarine = Submarine.MainSub;
}
return Option.Some(clones.ToImmutableArray());
return builder.ToImmutable();
}
else
{
@@ -316,7 +345,7 @@ namespace Barotrauma
}
}
return Option.None;
return builder.ToImmutable();
}
}

View File

@@ -6,7 +6,7 @@ using System.Text;
namespace Barotrauma
{
static class Quad
static class GraphicsQuad
{
private static VertexBuffer vertexBuffer = null;
private static IndexBuffer indexBuffer = null;

View File

@@ -35,7 +35,7 @@ namespace Barotrauma
Vector2 pos,
Rectangle srcRect,
Color color,
float rotation,
float rotationRad,
Vector2 origin,
Vector2 scale,
SpriteEffects effects,
@@ -55,9 +55,8 @@ namespace Barotrauma
(srcRectBottom, srcRectTop) = (srcRectTop, srcRectBottom);
}
rotation = MathHelper.ToRadians(rotation);
float sin = (float)Math.Sin(rotation);
float cos = (float)Math.Cos(rotation);
float sin = (float)Math.Sin(rotationRad);
float cos = (float)Math.Cos(rotationRad);
var size = srcRect.Size.ToVector2() * scale;
@@ -183,11 +182,11 @@ namespace Barotrauma
commandList.Add(command);
}
public void Draw(Texture2D texture, Vector2 pos, Rectangle? srcRect, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects effects, float depth)
public void Draw(Texture2D texture, Vector2 pos, Rectangle? srcRect, Color color, float rotationRad, Vector2 origin, Vector2 scale, SpriteEffects effects, float depth)
{
if (isDisposed) { return; }
var command = Command.FromTransform(texture, pos, srcRect ?? texture.Bounds, color, rotation, origin, scale, effects, depth, commandList.Count);
var command = Command.FromTransform(texture, pos, srcRect ?? texture.Bounds, color, rotationRad, origin, scale, effects, depth, commandList.Count);
AppendCommand(command);
}

View File

@@ -76,7 +76,7 @@ namespace Barotrauma
float zoom = (float)texWidth / (float)boundingBox.Width;
int texHeight = (int)(zoom * boundingBox.Height);
using Camera cam = new Camera();
Camera cam = new Camera();
cam.SetResolution(new Point(texWidth, texHeight));
cam.MaxZoom = zoom;
cam.MinZoom = zoom * 0.5f;

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product>
<Version>1.2.1.0</Version>
<Version>1.2.4.0</Version>
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName>

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product>
<Version>1.2.1.0</Version>
<Version>1.2.4.0</Version>
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName>

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product>
<Version>1.2.1.0</Version>
<Version>1.2.4.0</Version>
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName>

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product>
<Version>1.2.1.0</Version>
<Version>1.2.4.0</Version>
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName>

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product>
<Version>1.2.1.0</Version>
<Version>1.2.4.0</Version>
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName>

View File

@@ -80,7 +80,9 @@ namespace Barotrauma
{
msg.WriteUInt32(Job.Prefab.UintIdentifier);
msg.WriteByte((byte)Job.Variant);
foreach (SkillPrefab skillPrefab in Job.Prefab.Skills.OrderBy(s => s.Identifier))
var skills = Job.Prefab.Skills.OrderBy(s => s.Identifier);
msg.WriteByte((byte)skills.Count());
foreach (SkillPrefab skillPrefab in skills)
{
msg.WriteSingle(Job.GetSkill(skillPrefab.Identifier)?.Level ?? 0.0f);
}

View File

@@ -1134,7 +1134,12 @@ namespace Barotrauma
createMessage("Traitors:");
foreach (var ev in traitorManager.ActiveEvents)
{
createMessage($" - {ev.Traitor.Name}: {ev.TraitorEvent.Prefab.Identifier} ({ev.TraitorEvent.CurrentState})");
string msg = $" - {ev.TraitorEvent.Prefab.Identifier} ({ev.TraitorEvent.CurrentState}): {ev.Traitor.Name}";
if (ev.TraitorEvent.SecondaryTraitors.Any())
{
msg += $" secondary traitors: {string.Join(", ", ev.TraitorEvent.SecondaryTraitors.Select(t => t.Name))}";
}
createMessage(msg);
}
}
@@ -1416,7 +1421,7 @@ namespace Barotrauma
if (GameMain.GameSession?.GameMode is MultiPlayerCampaign mpCampaign &&
GameMain.NetLobbyScreen.SelectedMode == GameModePreset.MultiPlayerCampaign)
{
MultiPlayerCampaign.LoadCampaign(GameMain.GameSession.SavePath);
MultiPlayerCampaign.LoadCampaign(GameMain.GameSession.SavePath, client: null);
}
else
{
@@ -1461,7 +1466,7 @@ namespace Barotrauma
return;
}
var location = GameMain.GameSession.Campaign.Map.Locations.FirstOrDefault(l => l.Name.Equals(args[0], StringComparison.OrdinalIgnoreCase));
var location = GameMain.GameSession.Campaign.Map.Locations.FirstOrDefault(l => l.DisplayName.Equals(args[0], StringComparison.OrdinalIgnoreCase));
if (location == null)
{
ThrowError($"Could not find a location with the name {args[0]}.");
@@ -1484,7 +1489,7 @@ namespace Barotrauma
return new string[][]
{
GameMain.GameSession.Campaign.Map.Locations.Select(l => l.Name).ToArray(),
GameMain.GameSession.Campaign.Map.Locations.Select(l => l.DisplayName.Value).ToArray(),
LocationType.Prefabs.Select(lt => lt.Name.Value).ToArray()
};
}));
@@ -2457,7 +2462,7 @@ namespace Barotrauma
}
Location location = campaign.Map.CurrentLocation.Connections[destinationIndex].OtherLocation(campaign.Map.CurrentLocation);
campaign.Map.SelectLocation(location);
GameMain.Server.SendConsoleMessage(location.Name + " selected.", senderClient);
GameMain.Server.SendConsoleMessage($"{location.DisplayName.Value} selected.", senderClient);
}
);

View File

@@ -0,0 +1,24 @@
#nullable enable
using Barotrauma.Networking;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma;
partial class HighlightAction : EventAction
{
partial void SetHighlightProjSpecific(Entity entity, IEnumerable<Character>? targetCharacters)
{
if (entity is Item item && GameMain.Server != null)
{
IEnumerable<Client>? targetClients = null;
if (targetCharacters != null)
{
targetClients = targetCharacters
.Select(c => GameMain.Server.ConnectedClients.FirstOrDefault(client => client.Character == c))
.Where(c => c != null)!;
}
GameMain.Server?.CreateEntityEvent(item, new Item.SetHighlightEventData(State, highlightColor, targetClients));
}
}
}

View File

@@ -97,7 +97,13 @@ namespace Barotrauma
void GiveMissionExperience(CharacterInfo info)
{
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);
int finalExperienceGain = (int)(experienceGain * experienceGainMultiplierIndividual.Value);

View File

@@ -120,28 +120,41 @@ namespace Barotrauma
SaveUtil.SaveGame(GameMain.GameSession.SavePath);
DebugConsole.NewMessage("Campaign started!", Color.Cyan);
DebugConsole.NewMessage("Current location: " + GameMain.GameSession.Map.CurrentLocation.Name, Color.Cyan);
DebugConsole.NewMessage("Current location: " + GameMain.GameSession.Map.CurrentLocation.DisplayName, Color.Cyan);
((MultiPlayerCampaign)GameMain.GameSession.GameMode).LoadInitialLevel();
}
public static void LoadCampaign(string selectedSave)
public static void LoadCampaign(string selectedSave, Client client)
{
GameMain.NetLobbyScreen.ToggleCampaignMode(true);
SaveUtil.LoadGame(selectedSave);
if (GameMain.GameSession.GameMode is MultiPlayerCampaign mpCampaign)
try
{
mpCampaign.LastSaveID++;
SaveUtil.LoadGame(selectedSave);
if (GameMain.GameSession.GameMode is MultiPlayerCampaign mpCampaign)
{
mpCampaign.LastSaveID++;
}
else
{
DebugConsole.ThrowError("Failed to load a campaign. Unexpected game mode: " + GameMain.GameSession.GameMode ?? "none");
return;
}
}
else
catch (Exception e)
{
DebugConsole.ThrowError("Unexpected game mode: " + GameMain.GameSession.GameMode);
string errorMsg = $"Error while loading the save {selectedSave}";
if (client != null)
{
GameMain.Server?.SendDirectChatMessage($"{errorMsg}: {e.Message}\n{e.StackTrace}", client, ChatMessageType.Error);
}
DebugConsole.ThrowError(errorMsg, e);
return;
}
DebugConsole.NewMessage("Campaign loaded!", Color.Cyan);
DebugConsole.NewMessage(
GameMain.GameSession.Map.SelectedLocation == null ?
GameMain.GameSession.Map.CurrentLocation.Name :
GameMain.GameSession.Map.CurrentLocation.Name + " -> " + GameMain.GameSession.Map.SelectedLocation.Name, Color.Cyan);
GameMain.GameSession.Map.CurrentLocation.DisplayName :
GameMain.GameSession.Map.CurrentLocation.DisplayName + " -> " + GameMain.GameSession.Map.SelectedLocation.DisplayName, Color.Cyan);
}
protected override void LoadInitialLevel()
@@ -188,7 +201,14 @@ namespace Barotrauma
}
else
{
LoadCampaign(saveFiles[saveIndex].FilePath);
try
{
LoadCampaign(saveFiles[saveIndex].FilePath, client: null);
}
catch (Exception ex)
{
DebugConsole.ThrowError("Failed to load the campaign.", ex);
}
}
});
}
@@ -377,7 +397,7 @@ namespace Barotrauma
{
PendingSubmarineSwitch = null;
GameMain.Server.EndGame(TransitionType.None, wasSaved: false);
LoadCampaign(GameMain.GameSession.SavePath);
LoadCampaign(GameMain.GameSession.SavePath, client: null);
LastSaveID++;
IncrementAllLastUpdateIds();
yield return CoroutineStatus.Success;
@@ -1231,13 +1251,13 @@ namespace Barotrauma
{
foreach (CharacterInfo hireInfo in location.HireManager.PendingHires)
{
if (TryHireCharacter(location, hireInfo, sender))
if (TryHireCharacter(location, hireInfo, sender.Character, sender))
{
hiredCharacters.Add(hireInfo);
};
}
}
}
if (updatePending)
{
List<CharacterInfo> pendingHireInfos = new List<CharacterInfo>();

View File

@@ -66,7 +66,7 @@ namespace Barotrauma
{
foreach (Item item in slots[i].Items.ToList())
{
if (!receivedItemIdsFromClient[i].Contains(item.ID))
if (!receivedItemIdsFromClient[i].Contains(item.ID) && item.IsInteractable(c.Character))
{
Item droppedItem = item;
Entity prevOwner = Owner;
@@ -107,8 +107,7 @@ namespace Barotrauma
if (Entity.FindEntityByID(id) is not Item item || slots[i].Contains(item)) { continue; }
if (item.GetComponent<Pickable>() is not Pickable pickable ||
(pickable.IsAttached && !pickable.PickingDone) ||
item.AllowedSlots.None())
(pickable.IsAttached && !pickable.PickingDone) || item.AllowedSlots.None() || !item.IsInteractable(c.Character))
{
DebugConsole.AddWarning($"Client {c.Name} tried to pick up a non-pickable item \"{item}\" (parent inventory: {item.ParentInventory?.Owner.ToString() ?? "null"})",
item.Prefab.ContentPackage);

View File

@@ -161,6 +161,20 @@ namespace Barotrauma
msg.WriteUInt16(droppedItem.ID);
}
break;
case SetHighlightEventData highlightEventData:
bool isTargetedForClient =
highlightEventData.TargetClients.IsEmpty ||
highlightEventData.TargetClients.Contains(c);
msg.WriteBoolean(isTargetedForClient);
if (isTargetedForClient)
{
msg.WriteBoolean(highlightEventData.Highlighted);
if (highlightEventData.Highlighted)
{
msg.WriteColorR8G8B8A8(highlightEventData.Color);
}
}
break;
default:
throw error($"Unsupported event type {itemEventData.GetType().Name}");
}

View File

@@ -1,4 +1,7 @@
using System.Collections.Generic;
#nullable enable
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
@@ -16,4 +19,20 @@ partial class Item
Items = items.Distinct().ToImmutableArray();
}
}
public readonly struct SetHighlightEventData : IEventData
{
public EventType EventType => EventType.SetHighlight;
public readonly bool Highlighted;
public readonly Color Color;
public readonly ImmutableArray<Client> TargetClients;
public SetHighlightEventData(bool highlighted, Color color, IEnumerable<Client>? targetClients)
{
Highlighted = highlighted;
Color = color;
TargetClients = (targetClients ?? Enumerable.Empty<Client>()).ToImmutableArray();
}
}
}

View File

@@ -804,7 +804,7 @@ namespace Barotrauma.Networking
{
using (dosProtection.Pause(connectedClient))
{
MultiPlayerCampaign.LoadCampaign(saveName);
MultiPlayerCampaign.LoadCampaign(saveName, connectedClient);
}
}
}
@@ -1230,11 +1230,6 @@ namespace Barotrauma.Networking
}
c.LastRecvEntityEventID = lastRecvEntityEventID;
#warning TODO: remove this later
/*if (!CoroutineManager.IsCoroutineRunning("RoundRestartLoop"))
{
CoroutineManager.StartCoroutine(RoundRestartLoop(), "RoundRestartLoop");
}*/
}
else if (lastRecvEntityEventID != c.LastRecvEntityEventID && GameSettings.CurrentConfig.VerboseLogging)
{
@@ -1484,7 +1479,7 @@ namespace Barotrauma.Networking
{
using (dosProtection.Pause(sender))
{
MultiPlayerCampaign.LoadCampaign(GameMain.GameSession.SavePath);
MultiPlayerCampaign.LoadCampaign(GameMain.GameSession.SavePath, sender);
}
}
}
@@ -3945,7 +3940,6 @@ namespace Barotrauma.Networking
if (remainingJobs.None())
{
DebugConsole.ThrowError("Failed to assign a suitable job for bot \"" + c.Name + "\" (all jobs already have the maximum numbers of players). Assigning a random job...");
#warning TODO: is this randsync correct?
c.Job = Job.Random(Rand.RandSync.ServerAndClient);
assignedPlayerCount[c.Job.Prefab]++;
}

View File

@@ -426,8 +426,9 @@ namespace Barotrauma.Networking
}
else
{
double midRoundSyncTimeOut = uniqueEvents.Count / 100 * server.UpdateInterval.TotalSeconds;
midRoundSyncTimeOut = Math.Max(10.0f, midRoundSyncTimeOut * 10.0f);
//assume we can get at least 10 events per second through
double midRoundSyncTimeOut = uniqueEvents.Count / 10 * server.UpdateInterval.TotalSeconds;
midRoundSyncTimeOut = Math.Max(midRoundSyncTimeOut, server.ServerSettings.MinimumMidRoundSyncTimeout);
client.UnreceivedEntityEventCount = (UInt16)uniqueEvents.Count;
client.NeedsMidRoundSync = true;

View File

@@ -537,7 +537,7 @@ namespace Barotrauma.Networking
}
//add the ID card tags they should've gotten when spawning in the shuttle
character.GiveIdCardTags(shuttleSpawnPoints[i], requireSpawnPointTagsNotGiven: false, createNetworkEvent: true);
character.GiveIdCardTags(shuttleSpawnPoints[i], createNetworkEvent: true);
}
}

View File

@@ -263,7 +263,7 @@ namespace Barotrauma
if (amountToChoose > viableTraitors.Count)
{
DebugConsole.ThrowError(
$"Error in traitor event {traitorEvent.Prefab.Identifier}. Not enough players to choose {amountToChoose} secondary traitors."+
$"Error in traitor event {traitorEvent.Prefab.Identifier}. Not enough players to choose {amountToChoose} secondary traitors. " +
$"Make sure the {nameof(traitorEvent.Prefab.MinPlayerCount)} of the event is high enough to support to desired amount of secondary traitors.",
contentPackage: traitorEvent.Prefab.ContentPackage);
amountToChoose = viableTraitors.Count;
@@ -455,6 +455,15 @@ namespace Barotrauma
activeEvent.TraitorEvent.Prefab,
activeEvent.TraitorEvent.CurrentState,
activeEvent.Traitor));
if (activeEvent.TraitorEvent.CurrentState == TraitorEvent.State.Completed)
{
SteamAchievementManager.OnTraitorWin(activeEvent.TraitorEvent.Traitor?.Character);
foreach (var secondaryTraitor in activeEvent.TraitorEvent.SecondaryTraitors)
{
SteamAchievementManager.OnTraitorWin(secondaryTraitor?.Character);
}
}
}
if (previousTraitorEvents.Count > MaxPreviousEventHistory)
{

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product>
<Version>1.2.1.0</Version>
<Version>1.2.4.0</Version>
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName>

View File

@@ -91,6 +91,11 @@ namespace Barotrauma
private IEnumerable<Hull> visibleHulls;
private float hullVisibilityTimer;
const float hullVisibilityInterval = 0.5f;
/// <summary>
/// Returns hulls that are visible to the character, including the current hull.
/// Note that this is not an accurate visibility check, it only checks for open gaps between the adjacent and linked hulls.
/// </summary>
public IEnumerable<Hull> VisibleHulls
{
get
@@ -353,7 +358,7 @@ namespace Barotrauma
public static void UnequipContainedItems(Character character, Item parentItem, Func<Item, bool> predicate, bool avoidDroppingInSea = true, int? unequipMax = null)
{
var inventory = parentItem.OwnInventory;
if (inventory == null) { return; }
if (inventory == null || !inventory.Container.DrawInventory) { return; }
int removed = 0;
if (predicate == null || inventory.AllItems.Any(predicate))
{

View File

@@ -262,7 +262,7 @@ namespace Barotrauma
if (aiElements.Count == 0)
{
DebugConsole.ThrowError("Error in file \"" + c.Params.File + "\" - no AI element found.",
DebugConsole.ThrowError("Error in file \"" + c.Params.File.Path + "\" - no AI element found.",
contentPackage: c.Prefab?.ContentPackage);
outsideSteering = new SteeringManager(this);
insideSteering = new IndoorsSteeringManager(this, false, false);
@@ -312,7 +312,7 @@ namespace Barotrauma
}
ReevaluateAttacks();
outsideSteering = new SteeringManager(this);
insideSteering = new IndoorsSteeringManager(this, Character.Params.AI.CanOpenDoors, canAttackDoors);
insideSteering = new IndoorsSteeringManager(this, AIParams.CanOpenDoors, canAttackDoors);
steeringManager = outsideSteering;
State = AIState.Idle;
requiredHoleCount = (int)Math.Ceiling(ConvertUnits.ToDisplayUnits(colliderWidth) / Structure.WallSectionSize);
@@ -322,6 +322,10 @@ namespace Barotrauma
}
private CharacterParams.AIParams _aiParams;
/// <summary>
/// Shorthand for <see cref="Character.Params.AI"/> with null checking.
/// </summary>
/// <returns><see cref="Character.Params.AI"/> or an empty params. Does not return nulls.</returns>
public CharacterParams.AIParams AIParams
{
get
@@ -565,7 +569,7 @@ namespace Barotrauma
}
}
if (Character.Params.UsePathFinding && Character.Params.AI.UsePathFindingToGetInside && AIParams.CanOpenDoors)
if (Character.Params.UsePathFinding && AIParams.UsePathFindingToGetInside && AIParams.CanOpenDoors)
{
// Meant for monsters outside the player sub that target something inside the sub and can use the doors to access the sub (Husk).
bool IsCloseEnoughToTargetSub(float threshold) => SelectedAiTarget?.Entity?.Submarine is Submarine sub && sub != null && Vector2.DistanceSquared(Character.WorldPosition, sub.WorldPosition) < MathUtils.Pow(Math.Max(sub.Borders.Size.X, sub.Borders.Size.Y) / 2 + threshold, 2);
@@ -3097,7 +3101,10 @@ namespace Barotrauma
break;
}
valueModifier *= targetMemory.Priority / (float)Math.Sqrt(dist);
valueModifier *=
targetMemory.Priority /
//sqrt = the further the target is, the less the distance matters
MathF.Sqrt(dist);
if (valueModifier > targetValue)
{

View File

@@ -685,8 +685,8 @@ namespace Barotrauma
}
if (removeDivingSuit)
{
var divingSuit = Character.Inventory.FindItemByTag(Tags.HeavyDivingGear);
if (divingSuit != null && !divingSuit.HasTag(Tags.DivingGearWearableIndoors))
var divingSuit = Character.Inventory.FindEquippedItemByTag(Tags.HeavyDivingGear);
if (divingSuit != null && !divingSuit.HasTag(Tags.DivingGearWearableIndoors) && divingSuit.IsInteractable(Character))
{
if (shouldActOnSuffocation || Character.Submarine?.TeamID != Character.TeamID || ObjectiveManager.GetCurrentPriority() >= AIObjectiveManager.RunPriority)
{
@@ -727,54 +727,51 @@ namespace Barotrauma
}
}
if (takeMaskOff)
{
if (Character.HasEquippedItem(Tags.LightDivingGear))
{
var mask = Character.Inventory.FindEquippedItemByTag(Tags.LightDivingGear);
if (mask != null)
{
var mask = Character.Inventory.FindItemByTag(Tags.LightDivingGear);
if (mask != null)
if (!mask.AllowedSlots.Contains(InvSlotType.Any) || !Character.Inventory.TryPutItem(mask, Character, new List<InvSlotType>() { InvSlotType.Any }))
{
if (!mask.AllowedSlots.Contains(InvSlotType.Any) || !Character.Inventory.TryPutItem(mask, Character, new List<InvSlotType>() { InvSlotType.Any }))
if (Character.Submarine?.TeamID != Character.TeamID || ObjectiveManager.GetCurrentPriority() >= AIObjectiveManager.RunPriority)
{
if (Character.Submarine?.TeamID != Character.TeamID || ObjectiveManager.GetCurrentPriority() >= AIObjectiveManager.RunPriority)
mask.Drop(Character);
HandleRelocation(mask);
ReequipUnequipped();
}
else if (findItemState == FindItemState.None || findItemState == FindItemState.DivingMask)
{
findItemState = FindItemState.DivingMask;
if (FindSuitableContainer(mask, out Item targetContainer))
{
mask.Drop(Character);
HandleRelocation(mask);
ReequipUnequipped();
}
else if (findItemState == FindItemState.None || findItemState == FindItemState.DivingMask)
{
findItemState = FindItemState.DivingMask;
if (FindSuitableContainer(mask, out Item targetContainer))
findItemState = FindItemState.None;
itemIndex = 0;
if (targetContainer != null)
{
findItemState = FindItemState.None;
itemIndex = 0;
if (targetContainer != null)
var decontainObjective = new AIObjectiveDecontainItem(Character, mask, ObjectiveManager, targetContainer: targetContainer.GetComponent<ItemContainer>());
decontainObjective.Abandoned += () =>
{
var decontainObjective = new AIObjectiveDecontainItem(Character, mask, ObjectiveManager, targetContainer: targetContainer.GetComponent<ItemContainer>());
decontainObjective.Abandoned += () =>
{
ReequipUnequipped();
IgnoredItems.Add(targetContainer);
};
decontainObjective.Completed += () => ReequipUnequipped();
ObjectiveManager.CurrentObjective.AddSubObjective(decontainObjective, addFirst: true);
return;
}
else
{
mask.Drop(Character);
HandleRelocation(mask);
ReequipUnequipped();
}
IgnoredItems.Add(targetContainer);
};
decontainObjective.Completed += () => ReequipUnequipped();
ObjectiveManager.CurrentObjective.AddSubObjective(decontainObjective, addFirst: true);
return;
}
else
{
mask.Drop(Character);
HandleRelocation(mask);
ReequipUnequipped();
}
}
}
else
{
ReequipUnequipped();
}
}
}
else
{
ReequipUnequipped();
}
}
}
}
}
@@ -784,13 +781,11 @@ namespace Barotrauma
if (findItemState == FindItemState.None || findItemState == FindItemState.OtherItem)
{
for (int i = 0; i < 2; i++)
foreach (Item item in Character.HeldItems)
{
var hand = i == 0 ? InvSlotType.RightHand : InvSlotType.LeftHand;
Item item = Character.Inventory.GetItemInLimbSlot(hand);
if (item == null) { continue; }
if (item == null || !item.IsInteractable(Character)) { continue; }
if (!item.AllowedSlots.Contains(InvSlotType.Any) || !Character.Inventory.TryPutItem(item, Character, new List<InvSlotType>() { InvSlotType.Any }) && Character.Submarine?.TeamID == Character.TeamID )
if (!item.AllowedSlots.Contains(InvSlotType.Any) || !Character.Inventory.TryPutItem(item, Character, CharacterInventory.AnySlot) && Character.Submarine?.TeamID == Character.TeamID)
{
if (item.AllowedSlots.Contains(InvSlotType.Bag) && Character.Inventory.TryPutItem(item, Character, new List<InvSlotType>() { InvSlotType.Bag })) { continue; }
findItemState = FindItemState.OtherItem;
@@ -1389,7 +1384,10 @@ namespace Barotrauma
// Don't react to friendly enemy AI attacking other characters. E.g. husks attacking someone when whe are a cultist.
continue;
}
bool isWitnessing = otherHumanAI.VisibleHulls.Contains(Character.CurrentHull) || otherHumanAI.VisibleHulls.Contains(attacker.CurrentHull);
bool isWitnessing =
otherHumanAI.VisibleHulls.Contains(Character.CurrentHull) ||
otherHumanAI.VisibleHulls.Contains(attacker.CurrentHull) ||
otherCharacter.CanSeeTarget(attacker, seeThroughWindows: true);
if (!isWitnessing)
{
//if the other character did not witness the attack, and the character is not within report range (or capable of reporting)
@@ -1754,11 +1752,11 @@ namespace Barotrauma
if (otherCharacter == character || otherCharacter.TeamID == character.TeamID || otherCharacter.IsDead ||
otherCharacter.Info?.Job == null ||
otherCharacter.AIController is not HumanAIController otherHumanAI ||
!otherHumanAI.VisibleHulls.Contains(character.CurrentHull))
Vector2.DistanceSquared(otherCharacter.WorldPosition, character.WorldPosition) > 1000.0f * 1000.0f)
{
continue;
}
if (!otherCharacter.CanSeeTarget(character)) { continue; }
if (!otherCharacter.CanSeeTarget(character, seeThroughWindows: true)) { continue; }
if (!otherHumanAI.structureDamageAccumulator.ContainsKey(character)) { otherHumanAI.structureDamageAccumulator.Add(character, 0.0f); }
float prevAccumulatedDamage = otherHumanAI.structureDamageAccumulator[character];
@@ -1840,13 +1838,13 @@ namespace Barotrauma
foreach (Character otherCharacter in Character.CharacterList)
{
if (otherCharacter == thief || otherCharacter.TeamID == thief.TeamID || otherCharacter.IsIncapacitated || otherCharacter.Stun > 0.0f ||
otherCharacter.Info?.Job == null || !(otherCharacter.AIController is HumanAIController otherHumanAI) ||
!otherHumanAI.VisibleHulls.Contains(thief.CurrentHull))
otherCharacter.Info?.Job == null || otherCharacter.AIController is not HumanAIController otherHumanAI ||
Vector2.DistanceSquared(otherCharacter.WorldPosition, thief.WorldPosition) > 1000.0f * 1000.0f)
{
continue;
}
//if (!otherCharacter.IsFacing(thief.WorldPosition)) { continue; }
if (!otherCharacter.CanSeeTarget(thief)) { continue; }
if (!otherCharacter.CanSeeTarget(thief, seeThroughWindows: true)) { continue; }
// Don't react if the player is taking an extinguisher and there's any fires on the sub, or diving gear when the sub is flooding
// -> allow them to use the emergency items
if (thief.Submarine != null)

View File

@@ -1070,7 +1070,8 @@ namespace Barotrauma
{
// Try reload ammunition from inventory
static bool IsInsideHeadset(Item i) => i.ParentInventory?.Owner is Item ownerItem && ownerItem.HasTag(Tags.MobileRadio);
Item ammunition = character.Inventory.FindItem(i => i.HasIdentifierOrTags(ammunitionIdentifiers) && i.Condition > 0 && !IsInsideHeadset(i), recursive: true);
Item ammunition = character.Inventory.FindItem(i =>
i.HasIdentifierOrTags(ammunitionIdentifiers) && i.Condition > 0 && !IsInsideHeadset(i) && i.IsInteractable(character), recursive: true);
if (ammunition != null)
{
var container = Weapon.GetComponent<ItemContainer>();
@@ -1089,6 +1090,9 @@ namespace Barotrauma
}
else if (!HoldPosition && IsOffensiveOrArrest && seekAmmo && ammunitionIdentifiers != null)
{
// Inventory not drawn = it's not interactable
// If the weapon is empty and the inventory is inaccessible, it can't be reloaded
if (!Weapon.OwnInventory.Container.DrawInventory) { return false; }
SeekAmmunition(ammunitionIdentifiers);
}
return false;

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