Skip to content

Commit 0fb912f

Browse files
committed
fixed SettingsTextArea sending outdated messages
1 parent dc2c354 commit 0fb912f

File tree

5 files changed

+150
-43
lines changed

5 files changed

+150
-43
lines changed

LabExtended/API/Settings/Entries/SettingsKeyBind.cs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -142,11 +142,14 @@ internal override void Internal_Updated()
142142

143143
_isPressed = IsPressed;
144144

145-
LastPressTime = Time.realtimeSinceStartup;
145+
if (IsPressed)
146+
{
147+
LastPressTime = Time.realtimeSinceStartup;
146148

147-
HandlePress(IsPressed);
148-
149-
OnPressed.InvokeSafe(this);
149+
HandlePress(IsPressed);
150+
151+
OnPressed.InvokeSafe(this);
152+
}
150153
}
151154

152155
/// <summary>

LabExtended/API/Settings/Entries/SettingsTextArea.cs

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -79,19 +79,15 @@ public SSTextArea.FoldoutMode FoldoutMode
7979
public string Text
8080
{
8181
get => Base.Label;
82-
set
83-
{
84-
Base.Label = value;
85-
SendText(value);
86-
}
82+
set => SendText(value);
8783
}
88-
84+
8985
/// <summary>
9086
/// Sends a text update.
9187
/// </summary>
9288
/// <param name="text">The text to set.</param>
9389
public void SendText(string text)
94-
=> Player?.Connection?.Send(new SSSUpdateMessage(Base, writer => writer.WriteString(text)));
90+
=> Base.SendLabelUpdate(text, true, hub => hub != null && hub == Player.ReferenceHub);
9591

9692
/// <summary>
9793
/// Returns a string that represents the current state of the SettingsTextArea instance.

LabExtended/API/Settings/Menus/SettingsMenu.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,4 +145,17 @@ public void ShowMenu()
145145

146146
SettingsManager.SyncEntries(Player);
147147
}
148+
149+
/// <summary>
150+
/// Synchronizes the current player's settings entries with the settings manager.
151+
/// </summary>
152+
/// <remarks>This method has no effect if the player is not available. Call this method to ensure that any
153+
/// changes to the player's settings are reflected in the settings manager.</remarks>
154+
public void SyncEntries()
155+
{
156+
if (!Player)
157+
return;
158+
159+
SettingsManager.SyncEntries(Player);
160+
}
148161
}

LabExtended/API/Settings/SettingsManager.cs

Lines changed: 41 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -63,62 +63,71 @@ public static void SyncEntries(this ExPlayer player)
6363
var list = ListPool<ServerSpecificSettingBase>.Shared.Rent();
6464
var headers = ListPool<string>.Shared.Rent();
6565

66-
foreach (var menuEntry in player.settingsMenuLookup)
66+
if (player.settingsMenuLookup?.Count > 0)
6767
{
68-
if (menuEntry.Value.IsHidden)
69-
continue;
70-
71-
if (!string.IsNullOrWhiteSpace(menuEntry.Value.Header) && !headers.Contains(menuEntry.Value.CustomId))
68+
foreach (var menuEntry in player.settingsMenuLookup)
7269
{
73-
headers.Add(menuEntry.Value.CustomId);
70+
if (menuEntry.Value.IsHidden)
71+
continue;
7472

75-
list.Add(new SSGroupHeader(menuEntry.Value.Header,
76-
menuEntry.Value.HeaderReducedPadding, menuEntry.Value.HeaderHint));
77-
}
73+
if (!string.IsNullOrWhiteSpace(menuEntry.Value.Header) && !headers.Contains(menuEntry.Value.CustomId))
74+
{
75+
headers.Add(menuEntry.Value.CustomId);
7876

79-
foreach (var menuSetting in menuEntry.Value.Entries)
80-
{
81-
if (menuSetting?.Base == null)
82-
continue;
77+
list.Add(new SSGroupHeader(menuEntry.Value.Header,
78+
menuEntry.Value.HeaderReducedPadding, menuEntry.Value.HeaderHint));
79+
}
8380

84-
if (!menuSetting.Player)
85-
continue;
81+
foreach (var menuSetting in menuEntry.Value.Entries)
82+
{
83+
if (menuSetting?.Base == null)
84+
continue;
8685

87-
if (menuSetting.IsHidden)
88-
continue;
86+
if (!menuSetting.Player)
87+
continue;
88+
89+
if (menuSetting.IsHidden)
90+
continue;
8991

90-
list.Add(menuSetting.Base);
92+
list.Add(menuSetting.Base);
93+
}
9194
}
9295
}
9396

94-
foreach (var settingsEntry in player.settingsIdLookup)
97+
if (player.settingsIdLookup?.Count > 0)
9598
{
96-
if (settingsEntry.Value?.Base == null)
97-
continue;
99+
foreach (var settingsEntry in player.settingsIdLookup)
100+
{
101+
if (settingsEntry.Value?.Base == null)
102+
continue;
98103

99-
if (!settingsEntry.Value.Player)
100-
continue;
104+
if (!settingsEntry.Value.Player)
105+
continue;
101106

102-
if (settingsEntry.Value.IsHidden)
103-
continue;
107+
if (settingsEntry.Value.IsHidden)
108+
continue;
104109

105-
if (settingsEntry.Value.Menu != null)
106-
continue;
110+
if (settingsEntry.Value.Menu != null)
111+
continue;
107112

108-
list.Add(settingsEntry.Value.Base);
113+
list.Add(settingsEntry.Value.Base);
114+
}
109115
}
110116

111117
var playerSettings = DictionaryPool<Assembly, ServerSpecificSettingBase[]>.Shared.Rent(GlobalSettingsByAssembly); // todo Use SendOnJoinFilter
112118

113-
if (player.settingsByAssembly != null)
119+
if (player.settingsByAssembly?.Count > 0)
114120
playerSettings.AddRange(player.settingsByAssembly);
115121

116122
foreach (var sssByAssemblyEntry in playerSettings)
117123
{
118124
var pluginAssembly = sssByAssemblyEntry.Key;
119125
var collection = sssByAssemblyEntry.Value;
120126

121-
if (collection.First() is not SSGroupHeader)
127+
if (collection?.Length < 1)
128+
continue;
129+
130+
if (collection[0] is not SSGroupHeader)
122131
{
123132
var pluginName = PluginLoader.Plugins.TryGetFirst(o => o.Value.Equals(pluginAssembly), out var result)
124133
? result.Key.Name
@@ -137,6 +146,7 @@ public static void SyncEntries(this ExPlayer player)
137146
player.Send(new SSSEntriesPack(ListPool<ServerSpecificSettingBase>.Shared.ToArrayReturn(list), Version));
138147

139148
ListPool<string>.Shared.Return(headers);
149+
DictionaryPool<Assembly, ServerSpecificSettingBase[]>.Shared.Return(playerSettings);
140150
}
141151

142152
/// <summary>
@@ -898,7 +908,7 @@ internal static void OnResponseMessage(NetworkConnection connection, SSSClientRe
898908
entry.Menu.OnTextInput(textArea);
899909
break;
900910

901-
case SettingsKeyBind keyBind:
911+
case SettingsKeyBind keyBind when keyBind.IsPressed:
902912
entry.Menu.OnKeyBindPressed(keyBind);
903913
break;
904914

LabExtended/Utilities/FileUtils.cs

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,68 @@ static FileUtils()
3838
nonIndentedSettings.Converters.Add(new StringEnumConverter(new CamelCaseNamingStrategy(), true));
3939
}
4040

41+
/// <summary>
42+
/// Creates a directory path by combining the specified path segments, ensuring that all intermediate
43+
/// directories exist.
44+
/// </summary>
45+
/// <remarks>If any intermediate directories in the specified path do not exist, they are created.
46+
/// Path segments that include file extensions are treated as files and do not result in directory creation for
47+
/// that segment. This method is useful for preparing a file path and ensuring the directory structure exists
48+
/// before file operations.</remarks>
49+
/// <param name="parts">An array of path segments to combine. Each segment represents a part of the final path. Cannot be null or
50+
/// empty.</param>
51+
/// <returns>The absolute path created by combining the specified segments. The returned path is fully qualified.</returns>
52+
public static string CreatePath(params string[] parts)
53+
{
54+
for (var x = 0; x < parts.Length; x++)
55+
{
56+
var part = parts[x];
57+
var extension = Path.GetExtension(part);
58+
59+
if (!string.IsNullOrEmpty(extension) && (x + 1 >= parts.Length || !string.IsNullOrEmpty(Path.GetExtension(parts[x + 1]))))
60+
continue;
61+
62+
var currentPath = string.Join(Path.DirectorySeparatorChar.ToString(), parts.Take(x + 1));
63+
64+
if (!Directory.Exists(currentPath))
65+
Directory.CreateDirectory(currentPath);
66+
}
67+
68+
return Path.GetFullPath(Path.Combine(parts));
69+
}
70+
71+
/// <summary>
72+
/// Creates all directories and subdirectories in the specified file path that do not already exist.
73+
/// </summary>
74+
/// <remarks>This method does not create the file itself; it only ensures that all directories in
75+
/// the specified path exist. If the directories already exist, no action is taken. The returned path is the
76+
/// absolute path corresponding to the input.</remarks>
77+
/// <param name="filePath">The full or relative path of the file for which to ensure all parent directories exist. Cannot be null or
78+
/// empty.</param>
79+
/// <returns>A fully qualified file path with all necessary directories created.</returns>
80+
public static string CreatePath(string filePath)
81+
{
82+
filePath = Path.GetFullPath(filePath);
83+
84+
var parts = filePath.Split(Path.DirectorySeparatorChar);
85+
86+
for (var x = 0; x < parts.Length; x++)
87+
{
88+
var part = parts[x];
89+
var extension = Path.GetExtension(part);
90+
91+
if (!string.IsNullOrEmpty(extension) && (x + 1 >= parts.Length || !string.IsNullOrEmpty(Path.GetExtension(parts[x + 1]))))
92+
continue;
93+
94+
var currentPath = string.Join(Path.DirectorySeparatorChar.ToString(), parts.Take(x + 1));
95+
96+
if (!Directory.Exists(currentPath))
97+
Directory.CreateDirectory(currentPath);
98+
}
99+
100+
return filePath;
101+
}
102+
41103
/// <summary>
42104
/// Attempts to save a binary file to the specified directory using the provided writer delegate.
43105
/// </summary>
@@ -713,7 +775,30 @@ public static bool TryLoadYamlFile<T>(string filePath, IDeserializer? deserializ
713775
return false;
714776
}
715777
}
716-
778+
779+
/// <summary>
780+
/// Attempts to load and deserialize a YAML file from the specified path parts into an object of the given type.
781+
/// </summary>
782+
/// <typeparam name="T">The type of the object to deserialize the YAML content into.</typeparam>
783+
/// <param name="pathParts">An array of path segments that are combined to form the full file path to the YAML file. Cannot be null or
784+
/// empty.</param>
785+
/// <param name="type">The type to use for deserialization. Must be compatible with <typeparamref name="T"/>.</param>
786+
/// <param name="deserializer">The YAML deserializer to use. If null, a default deserializer is used.</param>
787+
/// <param name="result">When this method returns, contains the deserialized object if the operation succeeds; otherwise, the default
788+
/// value for <typeparamref name="T"/>.</param>
789+
/// <returns><see langword="true"/> if the YAML file is successfully loaded and deserialized; otherwise, <see
790+
/// langword="false"/>.</returns>
791+
public static bool TryLoadYamlFile<T>(string[] pathParts, Type type, IDeserializer? deserializer, out T result)
792+
{
793+
if (pathParts == null || pathParts.Length == 0)
794+
{
795+
result = default!;
796+
return false;
797+
}
798+
799+
return TryLoadYamlFile(Path.Combine(pathParts), type, deserializer, out result);
800+
}
801+
717802
/// <summary>
718803
/// Attempts to load and deserialize a YAML file into an object of type <typeparamref name="T"/>.
719804
/// </summary>

0 commit comments

Comments
 (0)