Merge remote-tracking branch 'upstream/stable' into ed-12-05-2025-upstream
# Conflicts: # .github/CODEOWNERS # Content.Client/Construction/UI/ConstructionMenuPresenter.cs # Content.Shared/Construction/Prototypes/ConstructionPrototype.cs # Content.Shared/Damage/Systems/SharedStaminaSystem.cs # Content.Shared/Lock/LockSystem.cs # Resources/Prototypes/Entities/Mobs/Customization/Markings/human_hair.yml # Resources/Prototypes/Entities/Objects/Specific/chemistry.yml # Resources/Prototypes/Procedural/vgroid.yml
This commit is contained in:
75
Content.Client/UserInterface/BuiPreTickUpdateSystem.cs
Normal file
75
Content.Client/UserInterface/BuiPreTickUpdateSystem.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.UserInterface;
|
||||
|
||||
/// <summary>
|
||||
/// Interface for <see cref="BoundUserInterface"/>s that need some updating logic
|
||||
/// ran in the <see cref="ModUpdateLevel.PreEngine"/> stage.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This is called on all open <see cref="BoundUserInterface"/>s that implement this interface.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// One intended use case is coalescing input events (e.g. via <see cref="InputCoalescer{T}"/>) to send them to the
|
||||
/// server only once per tick.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <seealso cref="BuiPreTickUpdateSystem"/>
|
||||
public interface IBuiPreTickUpdate
|
||||
{
|
||||
void PreTickUpdate();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements <see cref="BuiPreTickUpdateSystem"/>.
|
||||
/// </summary>
|
||||
public sealed class BuiPreTickUpdateSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IPlayerManager _playerManager = null!;
|
||||
[Dependency] private readonly UserInterfaceSystem _uiSystem = null!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = null!;
|
||||
|
||||
private EntityQuery<UserInterfaceUserComponent> _userQuery;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_userQuery = GetEntityQuery<UserInterfaceUserComponent>();
|
||||
}
|
||||
|
||||
public void RunUpdates()
|
||||
{
|
||||
if (!_gameTiming.IsFirstTimePredicted)
|
||||
return;
|
||||
|
||||
var localSession = _playerManager.LocalSession;
|
||||
if (localSession?.AttachedEntity is not { } localEntity)
|
||||
return;
|
||||
|
||||
if (!_userQuery.TryGetComponent(localEntity, out var userUIComp))
|
||||
return;
|
||||
|
||||
foreach (var (entity, uis) in userUIComp.OpenInterfaces)
|
||||
{
|
||||
foreach (var key in uis)
|
||||
{
|
||||
if (!_uiSystem.TryGetOpenUi(entity, key, out var ui))
|
||||
{
|
||||
DebugTools.Assert("Unable to find UI that was in the open UIs list??");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ui is IBuiPreTickUpdate tickUpdate)
|
||||
{
|
||||
tickUpdate.PreTickUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
80
Content.Client/UserInterface/BuiPredictionState.cs
Normal file
80
Content.Client/UserInterface/BuiPredictionState.cs
Normal file
@@ -0,0 +1,80 @@
|
||||
using System.Linq;
|
||||
using Robust.Client.Timing;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.UserInterface;
|
||||
|
||||
/// <summary>
|
||||
/// A local buffer for <see cref="BoundUserInterface"/>s to manually implement prediction.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// In many current (and future) cases, it is not practically possible to implement prediction for UIs
|
||||
/// by implementing the logic in shared. At the same time, we want to implement prediction for the best user experience
|
||||
/// (and it is sometimes the easiest way to make even a middling user experience).
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// You can queue predicted messages into this class with <see cref="SendMessage"/>,
|
||||
/// and then call <see cref="MessagesToReplay"/> later from <see cref="BoundUserInterface.UpdateState"/>
|
||||
/// to get all messages that are still "ahead" of the latest server state.
|
||||
/// These messages can then manually be "applied" to the latest state received from the server.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Note that this system only works if the server is guaranteed to send some kind of update in response to UI messages,
|
||||
/// or at a regular schedule. If it does not, there is no opportunity to error correct the prediction.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public sealed class BuiPredictionState
|
||||
{
|
||||
private readonly BoundUserInterface _parent;
|
||||
private readonly IClientGameTiming _gameTiming;
|
||||
|
||||
private readonly Queue<MessageData> _queuedMessages = new();
|
||||
|
||||
public BuiPredictionState(BoundUserInterface parent, IClientGameTiming gameTiming)
|
||||
{
|
||||
_parent = parent;
|
||||
_gameTiming = gameTiming;
|
||||
}
|
||||
|
||||
public void SendMessage(BoundUserInterfaceMessage message)
|
||||
{
|
||||
if (_gameTiming.IsFirstTimePredicted)
|
||||
{
|
||||
var messageData = new MessageData
|
||||
{
|
||||
TickSent = _gameTiming.CurTick,
|
||||
Message = message,
|
||||
};
|
||||
|
||||
_queuedMessages.Enqueue(messageData);
|
||||
}
|
||||
|
||||
_parent.SendPredictedMessage(message);
|
||||
}
|
||||
|
||||
public IEnumerable<BoundUserInterfaceMessage> MessagesToReplay()
|
||||
{
|
||||
var curTick = _gameTiming.LastRealTick;
|
||||
while (_queuedMessages.TryPeek(out var data) && data.TickSent <= curTick)
|
||||
{
|
||||
_queuedMessages.Dequeue();
|
||||
}
|
||||
|
||||
if (_queuedMessages.Count == 0)
|
||||
return [];
|
||||
|
||||
return _queuedMessages.Select(c => c.Message);
|
||||
}
|
||||
|
||||
private struct MessageData
|
||||
{
|
||||
public GameTick TickSent;
|
||||
public required BoundUserInterfaceMessage Message;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Message} @ {TickSent}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -81,4 +81,54 @@ namespace Content.Client.UserInterface.Controls
|
||||
return mode;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper functions for working with <see cref="FancyWindow"/>.
|
||||
/// </summary>
|
||||
public static class FancyWindowExt
|
||||
{
|
||||
/// <summary>
|
||||
/// Sets information for a window (title and guidebooks) based on an entity.
|
||||
/// </summary>
|
||||
/// <param name="window">The window to modify.</param>
|
||||
/// <param name="entityManager">Entity manager used to retrieve the information.</param>
|
||||
/// <param name="entity">The entity that this window represents.</param>
|
||||
/// <seealso cref="SetTitleFromEntity"/>
|
||||
/// <seealso cref="SetGuidebookFromEntity"/>
|
||||
public static void SetInfoFromEntity(this FancyWindow window, IEntityManager entityManager, EntityUid entity)
|
||||
{
|
||||
window.SetTitleFromEntity(entityManager, entity);
|
||||
window.SetGuidebookFromEntity(entityManager, entity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set a window's title to the name of an entity.
|
||||
/// </summary>
|
||||
/// <param name="window">The window to modify.</param>
|
||||
/// <param name="entityManager">Entity manager used to retrieve the information.</param>
|
||||
/// <param name="entity">The entity that this window represents.</param>
|
||||
/// <seealso cref="SetInfoFromEntity"/>
|
||||
public static void SetTitleFromEntity(
|
||||
this FancyWindow window,
|
||||
IEntityManager entityManager,
|
||||
EntityUid entity)
|
||||
{
|
||||
window.Title = entityManager.GetComponent<MetaDataComponent>(entity).EntityName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set a window's guidebook IDs to those of an entity.
|
||||
/// </summary>
|
||||
/// <param name="window">The window to modify.</param>
|
||||
/// <param name="entityManager">Entity manager used to retrieve the information.</param>
|
||||
/// <param name="entity">The entity that this window represents.</param>
|
||||
/// <seealso cref="SetInfoFromEntity"/>
|
||||
public static void SetGuidebookFromEntity(
|
||||
this FancyWindow window,
|
||||
IEntityManager entityManager,
|
||||
EntityUid entity)
|
||||
{
|
||||
window.HelpGuidebookIds = entityManager.GetComponentOrNull<GuideHelpComponent>(entity)?.Guides;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -264,12 +264,6 @@ public class ListContainer : Control
|
||||
_updateChildren = false;
|
||||
|
||||
var toRemove = new Dictionary<ListData, ListContainerButton>(_buttons);
|
||||
foreach (var child in Children.ToArray())
|
||||
{
|
||||
if (child == _vScrollBar)
|
||||
continue;
|
||||
RemoveChild(child);
|
||||
}
|
||||
|
||||
if (_data.Count > 0)
|
||||
{
|
||||
@@ -292,8 +286,9 @@ public class ListContainer : Control
|
||||
|
||||
if (Toggle && data == _selected)
|
||||
button.Pressed = true;
|
||||
AddChild(button);
|
||||
}
|
||||
AddChild(button);
|
||||
button.SetPositionInParent(i - _topIndex);
|
||||
button.Measure(finalSize);
|
||||
}
|
||||
}
|
||||
|
||||
79
Content.Client/UserInterface/Controls/MonotoneButton.cs
Normal file
79
Content.Client/UserInterface/Controls/MonotoneButton.cs
Normal file
@@ -0,0 +1,79 @@
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using static Robust.Client.UserInterface.Controls.Label;
|
||||
|
||||
namespace Content.Client.UserInterface.Controls;
|
||||
|
||||
/// <summary>
|
||||
/// A button intended for use with a monotone color palette
|
||||
/// </summary>
|
||||
public sealed class MonotoneButton : ContainerButton
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies the color of the label text when the button is pressed.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public Color AltTextColor { set; get; } = new Color(0.2f, 0.2f, 0.2f);
|
||||
|
||||
/// <summary>
|
||||
/// The label that holds the button text.
|
||||
/// </summary>
|
||||
public Label Label { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The text displayed by the button.
|
||||
/// </summary>
|
||||
[PublicAPI, ViewVariables]
|
||||
public string? Text { get => Label.Text; set => Label.Text = value; }
|
||||
|
||||
/// <summary>
|
||||
/// How to align the text inside the button.
|
||||
/// </summary>
|
||||
[PublicAPI, ViewVariables]
|
||||
public AlignMode TextAlign { get => Label.Align; set => Label.Align = value; }
|
||||
|
||||
/// <summary>
|
||||
/// If true, the button will allow shrinking and clip text
|
||||
/// to prevent the text from going outside the bounds of the button.
|
||||
/// If false, the minimum size will always fit the contained text.
|
||||
/// </summary>
|
||||
[PublicAPI, ViewVariables]
|
||||
public bool ClipText
|
||||
{
|
||||
get => Label.ClipText;
|
||||
set => Label.ClipText = value;
|
||||
}
|
||||
|
||||
public MonotoneButton()
|
||||
{
|
||||
Label = new Label
|
||||
{
|
||||
StyleClasses = { StyleClassButton }
|
||||
};
|
||||
|
||||
AddChild(Label);
|
||||
UpdateAppearance();
|
||||
}
|
||||
|
||||
private void UpdateAppearance()
|
||||
{
|
||||
// Recolor the label
|
||||
if (Label != null)
|
||||
Label.ModulateSelfOverride = DrawMode == DrawModeEnum.Pressed ? AltTextColor : null;
|
||||
|
||||
// Modulate the button if disabled
|
||||
Modulate = Disabled ? Color.Gray : Color.White;
|
||||
}
|
||||
|
||||
protected override void StylePropertiesChanged()
|
||||
{
|
||||
base.StylePropertiesChanged();
|
||||
UpdateAppearance();
|
||||
}
|
||||
|
||||
protected override void DrawModeChanged()
|
||||
{
|
||||
base.DrawModeChanged();
|
||||
UpdateAppearance();
|
||||
}
|
||||
}
|
||||
24
Content.Client/UserInterface/Controls/MonotoneCheckBox.cs
Normal file
24
Content.Client/UserInterface/Controls/MonotoneCheckBox.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
|
||||
namespace Content.Client.UserInterface.Controls;
|
||||
|
||||
/// <summary>
|
||||
/// A check box intended for use with a monotone color palette
|
||||
/// </summary>
|
||||
public sealed class MonotoneCheckBox : CheckBox
|
||||
{
|
||||
public const string StyleClassMonotoneCheckBox = "monotoneCheckBox";
|
||||
|
||||
public MonotoneCheckBox()
|
||||
{
|
||||
TextureRect.AddStyleClass(StyleClassMonotoneCheckBox);
|
||||
}
|
||||
|
||||
protected override void DrawModeChanged()
|
||||
{
|
||||
base.DrawModeChanged();
|
||||
|
||||
// Appearance modulations
|
||||
Modulate = Disabled ? Color.Gray : Color.White;
|
||||
}
|
||||
}
|
||||
6
Content.Client/UserInterface/Controls/OnOffButton.xaml
Normal file
6
Content.Client/UserInterface/Controls/OnOffButton.xaml
Normal file
@@ -0,0 +1,6 @@
|
||||
<Control xmlns="https://spacestation14.io">
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Button Name="OffButton" StyleClasses="OpenRight" Text="{Loc 'ui-button-off'}" />
|
||||
<Button Name="OnButton" StyleClasses="OpenLeft" Text="{Loc 'ui-button-on'}" />
|
||||
</BoxContainer>
|
||||
</Control>
|
||||
48
Content.Client/UserInterface/Controls/OnOffButton.xaml.cs
Normal file
48
Content.Client/UserInterface/Controls/OnOffButton.xaml.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client.UserInterface.Controls;
|
||||
|
||||
/// <summary>
|
||||
/// A simple control that displays a toggleable on/off button.
|
||||
/// </summary>
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class OnOffButton : Control
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether the control is currently in the "on" state.
|
||||
/// </summary>
|
||||
public bool IsOn
|
||||
{
|
||||
get => OnButton.Pressed;
|
||||
set
|
||||
{
|
||||
if (value)
|
||||
OnButton.Pressed = true;
|
||||
else
|
||||
OffButton.Pressed = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised when the user changes the state of the control.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This does not get raised if state is changed with <see cref="set_IsOn"/>.
|
||||
/// </remarks>
|
||||
public event Action<bool>? StateChanged;
|
||||
|
||||
public OnOffButton()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
var group = new ButtonGroup(isNoneSetAllowed: false);
|
||||
OffButton.Group = group;
|
||||
OnButton.Group = group;
|
||||
|
||||
OffButton.OnPressed += _ => StateChanged?.Invoke(false);
|
||||
OnButton.OnPressed += _ => StateChanged?.Invoke(true);
|
||||
}
|
||||
}
|
||||
40
Content.Client/UserInterface/InputCoalescer.cs
Normal file
40
Content.Client/UserInterface/InputCoalescer.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Content.Client.UserInterface;
|
||||
|
||||
/// <summary>
|
||||
/// A simple utility class to "coalesce" multiple input events into a single one, fired later.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public struct InputCoalescer<T>
|
||||
{
|
||||
public bool IsModified;
|
||||
public T LastValue;
|
||||
|
||||
/// <summary>
|
||||
/// Replace the value in the <see cref="InputCoalescer{T}"/>. This sets <see cref="IsModified"/> to true.
|
||||
/// </summary>
|
||||
public void Set(T value)
|
||||
{
|
||||
LastValue = value;
|
||||
IsModified = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the <see cref="InputCoalescer{T}"/> has been modified.
|
||||
/// If it was, return the value and clear <see cref="IsModified"/>.
|
||||
/// </summary>
|
||||
/// <returns>True if the value was modified since the last check.</returns>
|
||||
public bool CheckIsModified([MaybeNullWhen(false)] out T value)
|
||||
{
|
||||
if (IsModified)
|
||||
{
|
||||
value = LastValue;
|
||||
IsModified = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
value = default;
|
||||
return IsModified;
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,8 @@ namespace Content.Client.UserInterface.Systems.Alerts.Controls
|
||||
{
|
||||
public sealed class AlertControl : BaseButton
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
|
||||
public AlertPrototype Alert { get; }
|
||||
|
||||
/// <summary>
|
||||
@@ -33,8 +35,7 @@ namespace Content.Client.UserInterface.Systems.Alerts.Controls
|
||||
private (TimeSpan Start, TimeSpan End)? _cooldown;
|
||||
|
||||
private short? _severity;
|
||||
private readonly IGameTiming _gameTiming;
|
||||
private readonly IEntityManager _entityManager;
|
||||
|
||||
private readonly SpriteView _icon;
|
||||
private readonly CooldownGraphic _cooldownGraphic;
|
||||
|
||||
@@ -47,8 +48,10 @@ namespace Content.Client.UserInterface.Systems.Alerts.Controls
|
||||
/// <param name="severity">severity of alert, null if alert doesn't have severity levels</param>
|
||||
public AlertControl(AlertPrototype alert, short? severity)
|
||||
{
|
||||
_gameTiming = IoCManager.Resolve<IGameTiming>();
|
||||
_entityManager = IoCManager.Resolve<IEntityManager>();
|
||||
// Alerts will handle this.
|
||||
MuteSounds = true;
|
||||
|
||||
IoCManager.InjectDependencies(this);
|
||||
TooltipSupplier = SupplyTooltip;
|
||||
Alert = alert;
|
||||
_severity = severity;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.Atmos.EntitySystems;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.UserInterface;
|
||||
@@ -17,7 +18,7 @@ namespace Content.Client.UserInterface.Systems.Atmos.GasTank
|
||||
|
||||
public void SetOutputPressure(float value)
|
||||
{
|
||||
SendMessage(new GasTankSetPressureMessage
|
||||
SendPredictedMessage(new GasTankSetPressureMessage
|
||||
{
|
||||
Pressure = value
|
||||
});
|
||||
@@ -25,13 +26,14 @@ namespace Content.Client.UserInterface.Systems.Atmos.GasTank
|
||||
|
||||
public void ToggleInternals()
|
||||
{
|
||||
SendMessage(new GasTankToggleInternalsMessage());
|
||||
SendPredictedMessage(new GasTankToggleInternalsMessage());
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
_window = this.CreateWindow<GasTankWindow>();
|
||||
_window.Entity = Owner;
|
||||
_window.SetTitle(EntMan.GetComponent<MetaDataComponent>(Owner).EntityName);
|
||||
_window.OnOutputPressure += SetOutputPressure;
|
||||
_window.OnToggleInternals += ToggleInternals;
|
||||
@@ -41,6 +43,12 @@ namespace Content.Client.UserInterface.Systems.Atmos.GasTank
|
||||
{
|
||||
base.UpdateState(state);
|
||||
|
||||
if (EntMan.TryGetComponent(Owner, out GasTankComponent? component))
|
||||
{
|
||||
var canConnect = EntMan.System<SharedGasTankSystem>().CanConnectToInternals((Owner, component));
|
||||
_window?.Update(canConnect, component.IsConnected, component.OutputPressure);
|
||||
}
|
||||
|
||||
if (state is GasTankBoundUserInterfaceState cast)
|
||||
_window?.UpdateState(cast);
|
||||
}
|
||||
|
||||
@@ -3,11 +3,14 @@ using Content.Client.Message;
|
||||
using Content.Client.Resources;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.Atmos.EntitySystems;
|
||||
using Content.Shared.Timing;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.Timing;
|
||||
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
||||
|
||||
namespace Content.Client.UserInterface.Systems.Atmos.GasTank;
|
||||
@@ -15,6 +18,7 @@ namespace Content.Client.UserInterface.Systems.Atmos.GasTank;
|
||||
public sealed class GasTankWindow
|
||||
: BaseWindow
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
[Dependency] private readonly IResourceCache _cache = default!;
|
||||
|
||||
private readonly RichTextLabel _lblPressure;
|
||||
@@ -23,6 +27,8 @@ public sealed class GasTankWindow
|
||||
private readonly Button _btnInternals;
|
||||
private readonly Label _topLabel;
|
||||
|
||||
public EntityUid Entity;
|
||||
|
||||
public event Action<float>? OnOutputPressure;
|
||||
public event Action? OnToggleInternals;
|
||||
|
||||
@@ -194,12 +200,30 @@ public sealed class GasTankWindow
|
||||
public void UpdateState(GasTankBoundUserInterfaceState state)
|
||||
{
|
||||
_lblPressure.SetMarkup(Loc.GetString("gas-tank-window-tank-pressure-text", ("tankPressure", $"{state.TankPressure:0.##}")));
|
||||
_btnInternals.Disabled = !state.CanConnectInternals;
|
||||
}
|
||||
|
||||
public void Update(bool canConnectInternals, bool internalsConnected, float outputPressure)
|
||||
{
|
||||
_btnInternals.Disabled = !canConnectInternals;
|
||||
_lblInternals.SetMarkup(Loc.GetString("gas-tank-window-internal-text",
|
||||
("status", Loc.GetString(state.InternalsConnected ? "gas-tank-window-internal-connected" : "gas-tank-window-internal-disconnected"))));
|
||||
if (state.OutputPressure.HasValue)
|
||||
("status", Loc.GetString(internalsConnected ? "gas-tank-window-internal-connected" : "gas-tank-window-internal-disconnected"))));
|
||||
_spbPressure.Value = outputPressure;
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
|
||||
// Easier than managing state on any ent changes. Previously this was just ticked on server's GasTankSystem.
|
||||
if (_entManager.TryGetComponent(Entity, out GasTankComponent? tank))
|
||||
{
|
||||
_spbPressure.Value = state.OutputPressure.Value;
|
||||
var canConnectInternals = _entManager.System<SharedGasTankSystem>().CanConnectToInternals((Entity, tank));
|
||||
_btnInternals.Disabled = !canConnectInternals;
|
||||
}
|
||||
|
||||
if (!_btnInternals.Disabled)
|
||||
{
|
||||
_btnInternals.Disabled = _entManager.System<UseDelaySystem>().IsDelayed(Entity, id: SharedGasTankSystem.GasTankDelay);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -918,6 +918,11 @@ public sealed class ChatUIController : UIController
|
||||
_typingIndicator?.ClientChangedChatText();
|
||||
}
|
||||
|
||||
public void NotifyChatFocus(bool isFocused)
|
||||
{
|
||||
_typingIndicator?.ClientChangedChatFocus(isFocused);
|
||||
}
|
||||
|
||||
public void Repopulate()
|
||||
{
|
||||
foreach (var chat in _chats)
|
||||
|
||||
@@ -34,6 +34,8 @@ public partial class ChatBox : UIWidget
|
||||
ChatInput.Input.OnTextEntered += OnTextEntered;
|
||||
ChatInput.Input.OnKeyBindDown += OnInputKeyBindDown;
|
||||
ChatInput.Input.OnTextChanged += OnTextChanged;
|
||||
ChatInput.Input.OnFocusEnter += OnFocusEnter;
|
||||
ChatInput.Input.OnFocusExit += OnFocusExit;
|
||||
ChatInput.ChannelSelector.OnChannelSelect += OnChannelSelect;
|
||||
ChatInput.FilterButton.Popup.OnChannelFilter += OnChannelFilter;
|
||||
|
||||
@@ -174,6 +176,18 @@ public partial class ChatBox : UIWidget
|
||||
_controller.NotifyChatTextChange();
|
||||
}
|
||||
|
||||
private void OnFocusEnter(LineEditEventArgs args)
|
||||
{
|
||||
// Warn typing indicator about focus
|
||||
_controller.NotifyChatFocus(true);
|
||||
}
|
||||
|
||||
private void OnFocusExit(LineEditEventArgs args)
|
||||
{
|
||||
// Warn typing indicator about focus
|
||||
_controller.NotifyChatFocus(false);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
Reference in New Issue
Block a user