v1.7.7.0 (Winter Update 2024)

This commit is contained in:
Regalis11
2024-12-11 13:26:13 +02:00
parent 7d5b7a310a
commit f6349b2175
256 changed files with 4794 additions and 1653 deletions
@@ -184,6 +184,13 @@ namespace Barotrauma.Items.Components
{
shakePos = Vector2.Zero;
}
if (Character.Controlled is Character character && character.FocusedItem == item)
{
if ((IsFullyOpen || IsFullyClosed) && MathF.Abs(openState - lastOpenState) > 0)
{
CharacterHUD.RecreateHudTexts = true;
}
}
}
public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1, Color? overrideColor = null)
@@ -14,6 +14,7 @@ namespace Barotrauma.Items.Components
public override void AddTooltipInfo(ref LocalizedString name, ref LocalizedString description)
{
bool mergedMaterialTainted = false;
if (!materialName.IsNullOrEmpty() && item.ContainedItems.Count() > 0)
{
LocalizedString mergedMaterialName = materialName;
@@ -22,11 +23,15 @@ namespace Barotrauma.Items.Components
var containedMaterial = containedItem.GetComponent<GeneticMaterial>();
if (containedMaterial == null) { continue; }
mergedMaterialName += ", " + containedMaterial.materialName;
if (containedMaterial.Tainted)
{
mergedMaterialTainted = true;
}
}
name = name.Replace(materialName, mergedMaterialName);
}
if (Tainted)
if (Tainted || mergedMaterialTainted)
{
name = TextManager.GetWithVariable("entityname.taintedgeneticmaterial", "[geneticmaterialname]", name);
}
@@ -71,13 +71,13 @@ namespace Barotrauma.Items.Components
}
public override bool ValidateEventData(NetEntityEvent.IData data)
=> TryExtractEventData<EventData>(data, out _);
=> TryExtractEventData<AttachEventData>(data, out _);
public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null)
{
if (!attachable || body == null) { return; }
var eventData = ExtractEventData<EventData>(extraData);
var eventData = ExtractEventData<AttachEventData>(extraData);
Vector2 attachPos = eventData.AttachPos;
msg.WriteSingle(attachPos.X);
@@ -94,7 +94,9 @@ namespace Barotrauma.Items.Components
bool shouldBeAttached = msg.ReadBoolean();
Vector2 simPosition = new Vector2(msg.ReadSingle(), msg.ReadSingle());
UInt16 submarineID = msg.ReadUInt16();
UInt16 attacherID = msg.ReadUInt16();
Submarine sub = Entity.FindEntityByID(submarineID) as Submarine;
Character attacher = Entity.FindEntityByID(attacherID) as Character;
if (shouldBeAttached)
{
@@ -104,6 +106,8 @@ namespace Barotrauma.Items.Components
item.SetTransform(simPosition, 0.0f);
item.Submarine = sub;
AttachToWall();
PlaySound(ActionType.OnUse, attacher);
ApplyStatusEffects(ActionType.OnUse, (float)Timing.Step, character: attacher, user: attacher);
}
}
else
@@ -62,6 +62,10 @@ namespace Barotrauma.Items.Components
private readonly Dictionary<ActionType, List<ItemSound>> sounds;
private Dictionary<ActionType, SoundSelectionMode> soundSelectionModes;
/// <summary>
/// Starts the timer for delayed client-side corrections (<see cref="StartDelayedCorrection(IReadMessage, float, bool)"/>) - in other words,
/// the client will not attempt to read server updates for this component until the timer elapses.
/// </summary>
protected float correctionTimer;
public float IsActiveTimer;
@@ -760,7 +764,11 @@ namespace Barotrauma.Items.Components
/// </summary>
protected virtual void CreateGUI() { }
//Starts a coroutine that will read the correct state of the component from the NetBuffer when correctionTimer reaches zero.
/// <summary>
/// Starts a coroutine that will read the correct state of the component from the NetBuffer when correctionTimer reaches zero.
/// Useful in cases where we a client is constantly adjusting some value, and we don't want state updates from the server to interfere with it
/// (e.g. setting the value back to what a client just set it to, when the client has already modified the value further).
/// </summary>
protected void StartDelayedCorrection(IReadMessage buffer, float sendingTime, bool waitForMidRoundSync = false)
{
if (delayedCorrectionCoroutine != null) { CoroutineManager.StopCoroutines(delayedCorrectionCoroutine); }
@@ -372,17 +372,17 @@ namespace Barotrauma.Items.Components
outputContainer.Inventory.RectTransform = outputInventoryHolder.RectTransform;
}
private static LocalizedString GetRecipeNameAndAmount(FabricationRecipe fabricationRecipe)
private static RichString GetRecipeNameAndAmount(FabricationRecipe fabricationRecipe)
{
if (fabricationRecipe == null) { return ""; }
if (fabricationRecipe.Amount > 1)
{
return TextManager.GetWithVariables("fabricationrecipenamewithamount",
("[name]", fabricationRecipe.DisplayName), ("[amount]", fabricationRecipe.Amount.ToString()));
("[name]", RichString.Rich(fabricationRecipe.DisplayName)), ("[amount]", fabricationRecipe.Amount.ToString()));
}
else
{
return fabricationRecipe.DisplayName;
return RichString.Rich(fabricationRecipe.DisplayName);
}
}
@@ -1978,25 +1978,6 @@ namespace Barotrauma.Items.Components
2, GUIStyle.SmallFont);
}
protected override void RemoveComponentSpecific()
{
base.RemoveComponentSpecific();
sonarBlip?.Remove();
pingCircle?.Remove();
directionalPingCircle?.Remove();
screenOverlay?.Remove();
screenBackground?.Remove();
lineSprite?.Remove();
foreach (var t in targetIcons.Values)
{
t.Item1.Remove();
}
targetIcons.Clear();
MineralClusters = null;
}
public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null)
{
msg.WriteBoolean(currentMode == Mode.Active);
@@ -50,7 +50,7 @@ namespace Barotrauma.Items.Components
Hull hull = Entity.FindEntityByID(hullID) as Hull;
item.Submarine = submarine;
item.CurrentHull = hull;
item.body.SetTransform(simPosition, item.body.Rotation);
item.body.SetTransformIgnoreContacts(simPosition, item.body.Rotation);
switch (targetType)
{
@@ -12,7 +12,7 @@ namespace Barotrauma.Items.Components
private readonly List<GUIComponent> uiElements = new List<GUIComponent>();
private GUILayoutGroup uiElementContainer;
private bool readingNetworkEvent;
private bool suppressNetworkEvents;
private GUIComponent insufficientPowerWarning;
@@ -70,7 +70,7 @@ namespace Barotrauma.Items.Components
}
else
{
item.CreateClientEvent(this);
CreateClientEventWithCorrectionDelay();
}
};
@@ -100,13 +100,10 @@ namespace Barotrauma.Items.Components
ValueStep = numberInputStep,
OnValueChanged = (ni) =>
{
if (GameMain.Client == null)
ValueChanged(ni.UserData as CustomInterfaceElement, ni.FloatValue);
if (!suppressNetworkEvents && GameMain.Client != null)
{
ValueChanged(ni.UserData as CustomInterfaceElement, ni.FloatValue);
}
else if (!readingNetworkEvent)
{
item.CreateClientEvent(this);
CreateClientEventWithCorrectionDelay();
}
}
};
@@ -126,13 +123,10 @@ namespace Barotrauma.Items.Components
ValueStep = numberInputStep,
OnValueChanged = (ni) =>
{
if (GameMain.Client == null)
ValueChanged(ni.UserData as CustomInterfaceElement, ni.IntValue);
if (!suppressNetworkEvents && GameMain.Client != null)
{
ValueChanged(ni.UserData as CustomInterfaceElement, ni.IntValue);
}
else if (!readingNetworkEvent)
{
item.CreateClientEvent(this);
CreateClientEventWithCorrectionDelay();
}
}
};
@@ -162,13 +156,10 @@ namespace Barotrauma.Items.Components
};
tickBox.OnSelected += (tBox) =>
{
if (GameMain.Client == null)
TickBoxToggled(tBox.UserData as CustomInterfaceElement, tBox.Selected);
if (!suppressNetworkEvents && GameMain.Client != null)
{
TickBoxToggled(tBox.UserData as CustomInterfaceElement, tBox.Selected);
}
else if (!readingNetworkEvent)
{
item.CreateClientEvent(this);
CreateClientEventWithCorrectionDelay();
}
return true;
};
@@ -191,8 +182,10 @@ namespace Barotrauma.Items.Components
{
ButtonClicked(btnElement);
}
else if (!readingNetworkEvent)
else if (!suppressNetworkEvents && GameMain.Client != null)
{
//don't use CreateClientEventWithCorrectionDelay here, because buttons have no state,
//which means we don't need to worry about server updates interfering with client-side changes to the values in the interface
item.CreateClientEvent(this, new EventData(btnElement));
}
return true;
@@ -215,6 +208,12 @@ namespace Barotrauma.Items.Components
Visible = false
};
}
void CreateClientEventWithCorrectionDelay()
{
item.CreateClientEvent(this);
correctionTimer = CorrectionDelay;
}
}
public override void CreateEditingHUD(SerializableEntityEditor editor)
@@ -268,7 +267,7 @@ namespace Barotrauma.Items.Components
if (visible)
{
visibleElementCount++;
if (element.GetValueInterval > 0.0f)
if (element.GetValueInterval > 0.0f && correctionTimer <= 0.0f)
{
element.GetValueTimer -= deltaTime;
if (element.GetValueTimer <= 0.0f)
@@ -330,7 +329,7 @@ namespace Barotrauma.Items.Components
LocalizedString CreateLabelText(int elementIndex)
{
var label = customInterfaceElementList[elementIndex].Label;
string label = customInterfaceElementList[elementIndex].Label;
return string.IsNullOrWhiteSpace(label) ?
TextManager.GetWithVariable("connection.signaloutx", "[num]", (elementIndex + 1).ToString()) :
TextManager.Get(label).Fallback(label);
@@ -373,6 +372,9 @@ namespace Barotrauma.Items.Components
private void UpdateSignalProjSpecific(GUIComponent uiElement)
{
if (uiElement.UserData is not CustomInterfaceElement element) { return; }
suppressNetworkEvents = true;
string signal = element.Signal;
if (uiElement is GUITextBox tb)
{
@@ -384,14 +386,22 @@ namespace Barotrauma.Items.Components
{
if (ni.InputType == NumberType.Int)
{
int.TryParse(signal, out int value);
ni.IntValue = value;
if (int.TryParse(signal, out int value))
{
ni.IntValue = value;
}
else if (float.TryParse(signal, out float floatValue))
{
ni.IntValue = (int)MathF.Round(floatValue);
}
}
}
else if (uiElement is GUITickBox tickBox)
{
tickBox.Selected = signal.Equals("true", StringComparison.OrdinalIgnoreCase);
}
suppressNetworkEvents = false;
}
public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null)
@@ -429,38 +439,62 @@ namespace Barotrauma.Items.Components
public void ClientEventRead(IReadMessage msg, float sendingTime)
{
readingNetworkEvent = true;
int msgStartPos = msg.BitPosition;
suppressNetworkEvents = true;
try
{
string[] stringValues = new string[customInterfaceElementList.Count];
bool[] boolValues = new bool[customInterfaceElementList.Count];
for (int i = 0; i < customInterfaceElementList.Count; i++)
{
var element = customInterfaceElementList[i];
switch (element.InputType)
{
case CustomInterfaceElement.InputTypeOption.Number:
case CustomInterfaceElement.InputTypeOption.Text:
stringValues[i] = msg.ReadString();
break;
case CustomInterfaceElement.InputTypeOption.TickBox:
case CustomInterfaceElement.InputTypeOption.Button:
boolValues[i] = msg.ReadBoolean();
break;
}
}
if (correctionTimer > 0.0f)
{
int msgLength = msg.BitPosition - msgStartPos;
msg.BitPosition = msgStartPos;
StartDelayedCorrection(msg.ExtractBits(msgLength), sendingTime);
return;
}
for (int i = 0; i < customInterfaceElementList.Count; i++)
{
var element = customInterfaceElementList[i];
switch (element.InputType)
{
case CustomInterfaceElement.InputTypeOption.Number:
string newValue = msg.ReadString();
switch (element.NumberType)
{
case NumberType.Int when int.TryParse(newValue, out int value):
case NumberType.Int when int.TryParse(stringValues[i], out int value):
ValueChanged(element, value);
break;
case NumberType.Float when TryParseFloatInvariantCulture(newValue, out float value):
case NumberType.Float when TryParseFloatInvariantCulture(stringValues[i], out float value):
ValueChanged(element, value);
break;
}
break;
case CustomInterfaceElement.InputTypeOption.Text:
string newTextValue = msg.ReadString();
TextChanged(element, newTextValue);
TextChanged(element, stringValues[i]);
break;
case CustomInterfaceElement.InputTypeOption.TickBox:
bool tickBoxState = msg.ReadBoolean();
bool tickBoxState = boolValues[i];
((GUITickBox)uiElements[i]).Selected = tickBoxState;
TickBoxToggled(element, tickBoxState);
break;
case CustomInterfaceElement.InputTypeOption.Button:
bool buttonState = msg.ReadBoolean();
if (buttonState)
if (boolValues[i])
{
ButtonClicked(element);
}
@@ -472,7 +506,7 @@ namespace Barotrauma.Items.Components
}
finally
{
readingNetworkEvent = false;
suppressNetworkEvents = false;
}
}
}
@@ -1,6 +1,7 @@
using Barotrauma.Extensions;
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
@@ -21,13 +22,16 @@ namespace Barotrauma.Items.Components
private GUIListBox historyBox;
private GUITextBlock fillerBlock;
private GUITextBox inputBox;
private GUILayoutGroup layoutGroup;
private bool shouldSelectInputBox;
private readonly List<GUIComponent> inputElements = new List<GUIComponent>();
partial void InitProjSpecific(XElement element)
{
float marginMultiplier = element.GetAttributeFloat("marginmultiplier", 1.0f);
var layoutGroup = new GUILayoutGroup(new RectTransform(GuiFrame.Rect.Size - GUIStyle.ItemFrameMargin.Multiply(marginMultiplier), GuiFrame.RectTransform, Anchor.Center) { AbsoluteOffset = GUIStyle.ItemFrameOffset.Multiply(marginMultiplier) })
layoutGroup = new GUILayoutGroup(new RectTransform(GuiFrame.Rect.Size - GUIStyle.ItemFrameMargin.Multiply(marginMultiplier), GuiFrame.RectTransform, Anchor.Center) { AbsoluteOffset = GUIStyle.ItemFrameOffset.Multiply(marginMultiplier) })
{
ChildAnchor = Anchor.TopCenter,
RelativeSpacing = 0.02f,
@@ -39,43 +43,53 @@ namespace Barotrauma.Items.Components
AutoHideScrollBar = this.AutoHideScrollbar
};
if (!Readonly)
inputElements.Add(CreateFillerBlock());
inputElements.Add(new GUIFrame(new RectTransform(new Vector2(0.9f, 0.01f), layoutGroup.RectTransform), style: "HorizontalLine"));
inputBox = new GUITextBox(new RectTransform(new Vector2(1, .1f), layoutGroup.RectTransform), textColor: TextColor)
{
CreateFillerBlock();
new GUIFrame(new RectTransform(new Vector2(0.9f, 0.01f), layoutGroup.RectTransform), style: "HorizontalLine");
inputBox = new GUITextBox(new RectTransform(new Vector2(1, .1f), layoutGroup.RectTransform), textColor: TextColor)
MaxTextLength = MaxMessageLength,
OverflowClip = true,
OnEnterPressed = (GUITextBox textBox, string text) =>
{
MaxTextLength = MaxMessageLength,
OverflowClip = true,
OnEnterPressed = (GUITextBox textBox, string text) =>
if (GameMain.NetworkMember == null)
{
if (GameMain.NetworkMember == null)
{
SendOutput(text);
}
else
{
item.CreateClientEvent(this, new ClientEventData(text));
}
textBox.Text = string.Empty;
return true;
SendOutput(text);
}
};
}
else
{
item.CreateClientEvent(this, new ClientEventData(text));
}
textBox.Text = string.Empty;
return true;
}
};
inputElements.Add(inputBox);
RefreshInputElements();
}
layoutGroup.Recalculate();
/// <summary>
/// Refreshes the visibility of the input box and the layout of the UI depending on whether the terminal is readonly or not.
/// </summary>
private void RefreshInputElements()
{
foreach (var inputElement in inputElements)
{
inputElement.Visible = !_readonly;
inputElement.IgnoreLayoutGroups = !inputElement.Visible;
}
layoutGroup?.Recalculate();
}
// Create fillerBlock to cover historyBox so new values appear at the bottom of historyBox
// This could be removed if GUIListBox supported aligning its children
public void CreateFillerBlock()
public GUIComponent CreateFillerBlock()
{
fillerBlock = new GUITextBlock(new RectTransform(new Vector2(1, 1), historyBox.Content.RectTransform, anchor: Anchor.TopCenter), string.Empty)
{
CanBeFocused = false
};
return fillerBlock;
}
private void SendOutput(string input)
@@ -199,6 +199,8 @@ namespace Barotrauma.Items.Components
return;
}
if (Width * wireSprite.size.Y * Screen.Selected.Cam.Zoom < 1.0f) { return; }
Vector2 drawOffset = GetDrawOffset() + offset;
float baseDepth = UseSpriteDepth ? item.SpriteDepth : wireSprite.Depth;
@@ -282,7 +282,7 @@ namespace Barotrauma.Items.Components
}
else
{
if (!target.CustomInteractHUDText.IsNullOrEmpty() && target.AllowCustomInteract)
if (target.ShouldShowCustomInteractText)
{
texts.Add(target.CustomInteractHUDText);
textColors.Add(GUIStyle.Green);
@@ -1552,10 +1552,19 @@ namespace Barotrauma
debugInitialHudPositions.Clear();
foreach (ItemComponent ic in activeHUDs)
{
if (ic.GuiFrame == null || ic.AllowUIOverlap || ic.GetLinkUIToComponent() != null) { continue; }
if (!ignoreLocking && ic.LockGuiFramePosition) { continue; }
//if the frame covers nearly all of the screen, don't trying to prevent overlaps because it'd fail anyway
if (ic.GuiFrame.Rect.Width >= GameMain.GraphicsWidth * 0.9f && ic.GuiFrame.Rect.Height >= GameMain.GraphicsHeight * 0.9f) { continue; }
if (ic.GuiFrame == null || ic.GetLinkUIToComponent() != null) { continue; }
bool nearlyCoversScreen = ic.GuiFrame.Rect.Width >= GameMain.GraphicsWidth * 0.9f &&
ic.GuiFrame.Rect.Height >= GameMain.GraphicsHeight * 0.9f;
// when we are not using overlap prevention, we still need to clamp the frame to the screen area to
// prevent frames becoming inaccessible outside the screen for example after a resolution change
if (ic.AllowUIOverlap || (!ignoreLocking && ic.LockGuiFramePosition) || nearlyCoversScreen)
{
ic.GuiFrame.ClampToArea(new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight));
continue;
}
ic.GuiFrame.RectTransform.ScreenSpaceOffset = ic.GuiFrameOffset;
elementsToMove.Add(ic.GuiFrame);
debugInitialHudPositions.Add(ic.GuiFrame.Rect);
@@ -2281,7 +2290,13 @@ namespace Barotrauma
if (!components.Contains(ic)) { return; }
var eventData = new ComponentStateEventData(ic, extraData);
if (!ic.ValidateEventData(eventData)) { throw new Exception($"Component event creation failed: {typeof(T).Name}.{nameof(ItemComponent.ValidateEventData)} returned false"); }
if (!ic.ValidateEventData(eventData)) {
string errorMsg =
$"Client-side component event creation for the item \"{Prefab.Identifier}\" failed: {typeof(T).Name}.{nameof(ItemComponent.ValidateEventData)} returned false. " +
$"Data: {extraData?.GetType().ToString() ?? "null"}";
GameAnalyticsManager.AddErrorEventOnce($"Item.CreateClientEvent:ValidateEventData:{Prefab.Identifier}", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
throw new Exception(errorMsg);
}
GameMain.Client.CreateEntityEvent(this, eventData);
}
@@ -2487,12 +2502,18 @@ namespace Barotrauma
if (inventory != null)
{
if (inventorySlotIndex >= 0 && inventorySlotIndex < 255 &&
inventory.TryPutItem(item, inventorySlotIndex, false, false, null, false))
if (inventorySlotIndex is >= 0 and < 255)
{
return item;
if (!inventory.TryPutItem(item, inventorySlotIndex, allowSwapping: false, allowCombine: false, user: null, createNetworkEvent: false, ignoreCondition: true) &&
inventory.IsSlotEmpty(inventorySlotIndex))
{
//If the item won't go nicely, force it to the slot. If the server says the item is in the slot, it should go in the slot.
//May happen e.g. when a character is configured to spawn with an item that won't normally go in its inventory slots.
inventory.ForceToSlot(item, index: inventorySlotIndex);
return item;
}
}
inventory.TryPutItem(item, null, item.AllowedSlots, false);
inventory.TryPutItem(item, user: null, allowedSlots: item.AllowedSlots, createNetworkEvent: false);
}
return item;