Persist construction menu favorites server-side (#35867)
* Persist construction menu favorites to player profile * Use `ProtoId`s for construction favorites * Validate construction favorites updates from the client * Actually await the async database call
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
using Content.Client.Lobby;
|
||||||
using Content.Client.Stylesheets;
|
using Content.Client.Stylesheets;
|
||||||
using Content.Client.UserInterface.Systems.MenuBar.Widgets;
|
using Content.Client.UserInterface.Systems.MenuBar.Widgets;
|
||||||
using Content.Shared.Construction.Prototypes;
|
using Content.Shared.Construction.Prototypes;
|
||||||
@@ -28,6 +29,7 @@ namespace Content.Client.Construction.UI
|
|||||||
[Dependency] private readonly IPlacementManager _placementManager = default!;
|
[Dependency] private readonly IPlacementManager _placementManager = default!;
|
||||||
[Dependency] private readonly IUserInterfaceManager _uiManager = default!;
|
[Dependency] private readonly IUserInterfaceManager _uiManager = default!;
|
||||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||||
|
[Dependency] private readonly IClientPreferencesManager _preferencesManager = default!;
|
||||||
private readonly SpriteSystem _spriteSystem;
|
private readonly SpriteSystem _spriteSystem;
|
||||||
|
|
||||||
private readonly IConstructionMenuView _constructionView;
|
private readonly IConstructionMenuView _constructionView;
|
||||||
@@ -116,7 +118,7 @@ namespace Content.Client.Construction.UI
|
|||||||
|
|
||||||
_constructionView.RecipeFavorited += (_, _) => OnViewFavoriteRecipe();
|
_constructionView.RecipeFavorited += (_, _) => OnViewFavoriteRecipe();
|
||||||
|
|
||||||
PopulateCategories();
|
SetFavorites(_preferencesManager.Preferences?.ConstructionFavorites ?? []);
|
||||||
OnViewPopulateRecipes(_constructionView, (string.Empty, string.Empty));
|
OnViewPopulateRecipes(_constructionView, (string.Empty, string.Empty));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -493,10 +495,34 @@ namespace Content.Client.Construction.UI
|
|||||||
_favoritedRecipes.Count > 0 ? (string.Empty, FavoriteCatName) : (string.Empty, string.Empty));
|
_favoritedRecipes.Count > 0 ? (string.Empty, FavoriteCatName) : (string.Empty, string.Empty));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var newFavorites = new List<ProtoId<ConstructionPrototype>>(_favoritedRecipes.Count);
|
||||||
|
foreach (var recipe in _favoritedRecipes)
|
||||||
|
newFavorites.Add(recipe.ID);
|
||||||
|
|
||||||
|
_preferencesManager.UpdateConstructionFavorites(newFavorites);
|
||||||
PopulateInfo(_selected);
|
PopulateInfo(_selected);
|
||||||
PopulateCategories(_selectedCategory);
|
PopulateCategories(_selectedCategory);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetFavorites(IReadOnlyList<ProtoId<ConstructionPrototype>> favorites)
|
||||||
|
{
|
||||||
|
_favoritedRecipes.Clear();
|
||||||
|
|
||||||
|
foreach (var id in favorites)
|
||||||
|
{
|
||||||
|
if (_prototypeManager.TryIndex(id, out ConstructionPrototype? recipe, logError: false))
|
||||||
|
_favoritedRecipes.Add(recipe);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_selectedCategory == FavoriteCatName)
|
||||||
|
{
|
||||||
|
OnViewPopulateRecipes(_constructionView,
|
||||||
|
_favoritedRecipes.Count > 0 ? (string.Empty, FavoriteCatName) : (string.Empty, string.Empty));
|
||||||
|
}
|
||||||
|
|
||||||
|
PopulateCategories(_selectedCategory);
|
||||||
|
}
|
||||||
|
|
||||||
private void SystemBindingChanged(ConstructionSystem? newSystem)
|
private void SystemBindingChanged(ConstructionSystem? newSystem)
|
||||||
{
|
{
|
||||||
if (newSystem is null)
|
if (newSystem is null)
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Content.Shared.Construction.Prototypes;
|
||||||
using Content.Shared.Preferences;
|
using Content.Shared.Preferences;
|
||||||
using Robust.Client;
|
using Robust.Client;
|
||||||
using Robust.Client.Player;
|
using Robust.Client.Player;
|
||||||
using Robust.Shared.Network;
|
using Robust.Shared.Network;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Utility;
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
namespace Content.Client.Lobby
|
namespace Content.Client.Lobby
|
||||||
@@ -49,7 +51,7 @@ namespace Content.Client.Lobby
|
|||||||
|
|
||||||
public void SelectCharacter(int slot)
|
public void SelectCharacter(int slot)
|
||||||
{
|
{
|
||||||
Preferences = new PlayerPreferences(Preferences.Characters, slot, Preferences.AdminOOCColor);
|
Preferences = new PlayerPreferences(Preferences.Characters, slot, Preferences.AdminOOCColor, Preferences.ConstructionFavorites);
|
||||||
var msg = new MsgSelectCharacter
|
var msg = new MsgSelectCharacter
|
||||||
{
|
{
|
||||||
SelectedCharacterIndex = slot
|
SelectedCharacterIndex = slot
|
||||||
@@ -62,7 +64,7 @@ namespace Content.Client.Lobby
|
|||||||
var collection = IoCManager.Instance!;
|
var collection = IoCManager.Instance!;
|
||||||
profile.EnsureValid(_playerManager.LocalSession!, collection);
|
profile.EnsureValid(_playerManager.LocalSession!, collection);
|
||||||
var characters = new Dictionary<int, ICharacterProfile>(Preferences.Characters) {[slot] = profile};
|
var characters = new Dictionary<int, ICharacterProfile>(Preferences.Characters) {[slot] = profile};
|
||||||
Preferences = new PlayerPreferences(characters, Preferences.SelectedCharacterIndex, Preferences.AdminOOCColor);
|
Preferences = new PlayerPreferences(characters, Preferences.SelectedCharacterIndex, Preferences.AdminOOCColor, Preferences.ConstructionFavorites);
|
||||||
var msg = new MsgUpdateCharacter
|
var msg = new MsgUpdateCharacter
|
||||||
{
|
{
|
||||||
Profile = profile,
|
Profile = profile,
|
||||||
@@ -85,7 +87,7 @@ namespace Content.Client.Lobby
|
|||||||
|
|
||||||
var l = lowest.Value;
|
var l = lowest.Value;
|
||||||
characters.Add(l, profile);
|
characters.Add(l, profile);
|
||||||
Preferences = new PlayerPreferences(characters, Preferences.SelectedCharacterIndex, Preferences.AdminOOCColor);
|
Preferences = new PlayerPreferences(characters, Preferences.SelectedCharacterIndex, Preferences.AdminOOCColor, Preferences.ConstructionFavorites);
|
||||||
|
|
||||||
UpdateCharacter(profile, l);
|
UpdateCharacter(profile, l);
|
||||||
}
|
}
|
||||||
@@ -98,7 +100,7 @@ namespace Content.Client.Lobby
|
|||||||
public void DeleteCharacter(int slot)
|
public void DeleteCharacter(int slot)
|
||||||
{
|
{
|
||||||
var characters = Preferences.Characters.Where(p => p.Key != slot);
|
var characters = Preferences.Characters.Where(p => p.Key != slot);
|
||||||
Preferences = new PlayerPreferences(characters, Preferences.SelectedCharacterIndex, Preferences.AdminOOCColor);
|
Preferences = new PlayerPreferences(characters, Preferences.SelectedCharacterIndex, Preferences.AdminOOCColor, Preferences.ConstructionFavorites);
|
||||||
var msg = new MsgDeleteCharacter
|
var msg = new MsgDeleteCharacter
|
||||||
{
|
{
|
||||||
Slot = slot
|
Slot = slot
|
||||||
@@ -106,6 +108,16 @@ namespace Content.Client.Lobby
|
|||||||
_netManager.ClientSendMessage(msg);
|
_netManager.ClientSendMessage(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void UpdateConstructionFavorites(List<ProtoId<ConstructionPrototype>> favorites)
|
||||||
|
{
|
||||||
|
Preferences = new PlayerPreferences(Preferences.Characters, Preferences.SelectedCharacterIndex, Preferences.AdminOOCColor, favorites);
|
||||||
|
var msg = new MsgUpdateConstructionFavorites
|
||||||
|
{
|
||||||
|
Favorites = favorites
|
||||||
|
};
|
||||||
|
_netManager.ClientSendMessage(msg);
|
||||||
|
}
|
||||||
|
|
||||||
private void HandlePreferencesAndSettings(MsgPreferencesAndSettings message)
|
private void HandlePreferencesAndSettings(MsgPreferencesAndSettings message)
|
||||||
{
|
{
|
||||||
Preferences = message.Preferences;
|
Preferences = message.Preferences;
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
|
using Content.Shared.Construction.Prototypes;
|
||||||
using Content.Shared.Preferences;
|
using Content.Shared.Preferences;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
namespace Content.Client.Lobby
|
namespace Content.Client.Lobby
|
||||||
{
|
{
|
||||||
@@ -17,5 +19,6 @@ namespace Content.Client.Lobby
|
|||||||
void CreateCharacter(ICharacterProfile profile);
|
void CreateCharacter(ICharacterProfile profile);
|
||||||
void DeleteCharacter(ICharacterProfile profile);
|
void DeleteCharacter(ICharacterProfile profile);
|
||||||
void DeleteCharacter(int slot);
|
void DeleteCharacter(int slot);
|
||||||
|
void UpdateConstructionFavorites(List<ProtoId<ConstructionPrototype>> favorites);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
2120
Content.Server.Database/Migrations/Postgres/20250314222016_ConstructionFavorites.Designer.cs
generated
Normal file
2120
Content.Server.Database/Migrations/Postgres/20250314222016_ConstructionFavorites.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,29 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Content.Server.Database.Migrations.Postgres
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class ConstructionFavorites : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<string[]>(
|
||||||
|
name: "construction_favorites",
|
||||||
|
table: "preference",
|
||||||
|
type: "text[]",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: new string[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "construction_favorites",
|
||||||
|
table: "preference");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,7 +20,7 @@ namespace Content.Server.Database.Migrations.Postgres
|
|||||||
{
|
{
|
||||||
#pragma warning disable 612, 618
|
#pragma warning disable 612, 618
|
||||||
modelBuilder
|
modelBuilder
|
||||||
.HasAnnotation("ProductVersion", "8.0.0")
|
.HasAnnotation("ProductVersion", "9.0.1")
|
||||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||||
|
|
||||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||||
@@ -283,8 +283,7 @@ namespace Content.Server.Database.Migrations.Postgres
|
|||||||
.HasColumnType("timestamp with time zone")
|
.HasColumnType("timestamp with time zone")
|
||||||
.HasColumnName("expiration_time");
|
.HasColumnName("expiration_time");
|
||||||
|
|
||||||
b.Property<DateTime?>("LastEditedAt")
|
b.Property<DateTime>("LastEditedAt")
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("timestamp with time zone")
|
.HasColumnType("timestamp with time zone")
|
||||||
.HasColumnName("last_edited_at");
|
.HasColumnName("last_edited_at");
|
||||||
|
|
||||||
@@ -418,8 +417,7 @@ namespace Content.Server.Database.Migrations.Postgres
|
|||||||
.HasColumnType("timestamp with time zone")
|
.HasColumnType("timestamp with time zone")
|
||||||
.HasColumnName("expiration_time");
|
.HasColumnName("expiration_time");
|
||||||
|
|
||||||
b.Property<DateTime?>("LastEditedAt")
|
b.Property<DateTime>("LastEditedAt")
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("timestamp with time zone")
|
.HasColumnType("timestamp with time zone")
|
||||||
.HasColumnName("last_edited_at");
|
.HasColumnName("last_edited_at");
|
||||||
|
|
||||||
@@ -797,6 +795,11 @@ namespace Content.Server.Database.Migrations.Postgres
|
|||||||
.HasColumnType("text")
|
.HasColumnType("text")
|
||||||
.HasColumnName("admin_ooc_color");
|
.HasColumnName("admin_ooc_color");
|
||||||
|
|
||||||
|
b.PrimitiveCollection<string[]>("ConstructionFavorites")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text[]")
|
||||||
|
.HasColumnName("construction_favorites");
|
||||||
|
|
||||||
b.Property<int>("SelectedCharacterSlot")
|
b.Property<int>("SelectedCharacterSlot")
|
||||||
.HasColumnType("integer")
|
.HasColumnType("integer")
|
||||||
.HasColumnName("selected_character_slot");
|
.HasColumnName("selected_character_slot");
|
||||||
|
|||||||
2044
Content.Server.Database/Migrations/Sqlite/20250314222717_ConstructionFavorites.Designer.cs
generated
Normal file
2044
Content.Server.Database/Migrations/Sqlite/20250314222717_ConstructionFavorites.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,29 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Content.Server.Database.Migrations.Sqlite
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class ConstructionFavorites : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "construction_favorites",
|
||||||
|
table: "preference",
|
||||||
|
type: "TEXT",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: "[]");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "construction_favorites",
|
||||||
|
table: "preference");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,7 +15,7 @@ namespace Content.Server.Database.Migrations.Sqlite
|
|||||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||||
{
|
{
|
||||||
#pragma warning disable 612, 618
|
#pragma warning disable 612, 618
|
||||||
modelBuilder.HasAnnotation("ProductVersion", "8.0.0");
|
modelBuilder.HasAnnotation("ProductVersion", "9.0.1");
|
||||||
|
|
||||||
modelBuilder.Entity("Content.Server.Database.Admin", b =>
|
modelBuilder.Entity("Content.Server.Database.Admin", b =>
|
||||||
{
|
{
|
||||||
@@ -264,8 +264,7 @@ namespace Content.Server.Database.Migrations.Sqlite
|
|||||||
.HasColumnType("TEXT")
|
.HasColumnType("TEXT")
|
||||||
.HasColumnName("expiration_time");
|
.HasColumnName("expiration_time");
|
||||||
|
|
||||||
b.Property<DateTime?>("LastEditedAt")
|
b.Property<DateTime>("LastEditedAt")
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("TEXT")
|
.HasColumnType("TEXT")
|
||||||
.HasColumnName("last_edited_at");
|
.HasColumnName("last_edited_at");
|
||||||
|
|
||||||
@@ -393,8 +392,7 @@ namespace Content.Server.Database.Migrations.Sqlite
|
|||||||
.HasColumnType("TEXT")
|
.HasColumnType("TEXT")
|
||||||
.HasColumnName("expiration_time");
|
.HasColumnName("expiration_time");
|
||||||
|
|
||||||
b.Property<DateTime?>("LastEditedAt")
|
b.Property<DateTime>("LastEditedAt")
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("TEXT")
|
.HasColumnType("TEXT")
|
||||||
.HasColumnName("last_edited_at");
|
.HasColumnName("last_edited_at");
|
||||||
|
|
||||||
@@ -751,6 +749,11 @@ namespace Content.Server.Database.Migrations.Sqlite
|
|||||||
.HasColumnType("TEXT")
|
.HasColumnType("TEXT")
|
||||||
.HasColumnName("admin_ooc_color");
|
.HasColumnName("admin_ooc_color");
|
||||||
|
|
||||||
|
b.PrimitiveCollection<string>("ConstructionFavorites")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("construction_favorites");
|
||||||
|
|
||||||
b.Property<int>("SelectedCharacterSlot")
|
b.Property<int>("SelectedCharacterSlot")
|
||||||
.HasColumnType("INTEGER")
|
.HasColumnType("INTEGER")
|
||||||
.HasColumnName("selected_character_slot");
|
.HasColumnName("selected_character_slot");
|
||||||
|
|||||||
@@ -392,6 +392,7 @@ namespace Content.Server.Database
|
|||||||
public Guid UserId { get; set; }
|
public Guid UserId { get; set; }
|
||||||
public int SelectedCharacterSlot { get; set; }
|
public int SelectedCharacterSlot { get; set; }
|
||||||
public string AdminOOCColor { get; set; } = null!;
|
public string AdminOOCColor { get; set; } = null!;
|
||||||
|
public List<string> ConstructionFavorites { get; set; } = new();
|
||||||
public List<Profile> Profiles { get; } = new();
|
public List<Profile> Profiles { get; } = new();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ using System.Threading.Tasks;
|
|||||||
using Content.Server.Administration.Logs;
|
using Content.Server.Administration.Logs;
|
||||||
using Content.Server.Administration.Managers;
|
using Content.Server.Administration.Managers;
|
||||||
using Content.Shared.Administration.Logs;
|
using Content.Shared.Administration.Logs;
|
||||||
|
using Content.Shared.Construction.Prototypes;
|
||||||
using Content.Shared.Database;
|
using Content.Shared.Database;
|
||||||
using Content.Shared.Humanoid;
|
using Content.Shared.Humanoid;
|
||||||
using Content.Shared.Humanoid.Markings;
|
using Content.Shared.Humanoid.Markings;
|
||||||
@@ -65,7 +66,11 @@ namespace Content.Server.Database
|
|||||||
profiles[profile.Slot] = ConvertProfiles(profile);
|
profiles[profile.Slot] = ConvertProfiles(profile);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new PlayerPreferences(profiles, prefs.SelectedCharacterSlot, Color.FromHex(prefs.AdminOOCColor));
|
var constructionFavorites = new List<ProtoId<ConstructionPrototype>>(prefs.ConstructionFavorites.Count);
|
||||||
|
foreach (var favorite in prefs.ConstructionFavorites)
|
||||||
|
constructionFavorites.Add(new ProtoId<ConstructionPrototype>(favorite));
|
||||||
|
|
||||||
|
return new PlayerPreferences(profiles, prefs.SelectedCharacterSlot, Color.FromHex(prefs.AdminOOCColor), constructionFavorites);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SaveSelectedCharacterIndexAsync(NetUserId userId, int index)
|
public async Task SaveSelectedCharacterIndexAsync(NetUserId userId, int index)
|
||||||
@@ -143,7 +148,8 @@ namespace Content.Server.Database
|
|||||||
{
|
{
|
||||||
UserId = userId.UserId,
|
UserId = userId.UserId,
|
||||||
SelectedCharacterSlot = 0,
|
SelectedCharacterSlot = 0,
|
||||||
AdminOOCColor = Color.Red.ToHex()
|
AdminOOCColor = Color.Red.ToHex(),
|
||||||
|
ConstructionFavorites = [],
|
||||||
};
|
};
|
||||||
|
|
||||||
prefs.Profiles.Add(profile);
|
prefs.Profiles.Add(profile);
|
||||||
@@ -152,7 +158,7 @@ namespace Content.Server.Database
|
|||||||
|
|
||||||
await db.DbContext.SaveChangesAsync();
|
await db.DbContext.SaveChangesAsync();
|
||||||
|
|
||||||
return new PlayerPreferences(new[] {new KeyValuePair<int, ICharacterProfile>(0, defaultProfile)}, 0, Color.FromHex(prefs.AdminOOCColor));
|
return new PlayerPreferences(new[] { new KeyValuePair<int, ICharacterProfile>(0, defaultProfile) }, 0, Color.FromHex(prefs.AdminOOCColor), []);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task DeleteSlotAndSetSelectedIndex(NetUserId userId, int deleteSlot, int newSlot)
|
public async Task DeleteSlotAndSetSelectedIndex(NetUserId userId, int deleteSlot, int newSlot)
|
||||||
@@ -178,6 +184,19 @@ namespace Content.Server.Database
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task SaveConstructionFavoritesAsync(NetUserId userId, List<ProtoId<ConstructionPrototype>> constructionFavorites)
|
||||||
|
{
|
||||||
|
await using var db = await GetDb();
|
||||||
|
var prefs = await db.DbContext.Preference.SingleAsync(p => p.UserId == userId.UserId);
|
||||||
|
|
||||||
|
var favorites = new List<string>(constructionFavorites.Count);
|
||||||
|
foreach (var favorite in constructionFavorites)
|
||||||
|
favorites.Add(favorite.Id);
|
||||||
|
prefs.ConstructionFavorites = favorites;
|
||||||
|
|
||||||
|
await db.DbContext.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
private static async Task SetSelectedCharacterSlotAsync(NetUserId userId, int newSlot, ServerDbContext db)
|
private static async Task SetSelectedCharacterSlotAsync(NetUserId userId, int newSlot, ServerDbContext db)
|
||||||
{
|
{
|
||||||
var prefs = await db.Preference.SingleAsync(p => p.UserId == userId.UserId);
|
var prefs = await db.Preference.SingleAsync(p => p.UserId == userId.UserId);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ using System.Threading.Tasks;
|
|||||||
using Content.Server.Administration.Logs;
|
using Content.Server.Administration.Logs;
|
||||||
using Content.Shared.Administration.Logs;
|
using Content.Shared.Administration.Logs;
|
||||||
using Content.Shared.CCVar;
|
using Content.Shared.CCVar;
|
||||||
|
using Content.Shared.Construction.Prototypes;
|
||||||
using Content.Shared.Database;
|
using Content.Shared.Database;
|
||||||
using Content.Shared.Preferences;
|
using Content.Shared.Preferences;
|
||||||
using Content.Shared.Roles;
|
using Content.Shared.Roles;
|
||||||
@@ -42,6 +43,8 @@ namespace Content.Server.Database
|
|||||||
|
|
||||||
Task SaveAdminOOCColorAsync(NetUserId userId, Color color);
|
Task SaveAdminOOCColorAsync(NetUserId userId, Color color);
|
||||||
|
|
||||||
|
Task SaveConstructionFavoritesAsync(NetUserId userId, List<ProtoId<ConstructionPrototype>> constructionFavorites);
|
||||||
|
|
||||||
// Single method for two operations for transaction.
|
// Single method for two operations for transaction.
|
||||||
Task DeleteSlotAndSetSelectedIndex(NetUserId userId, int deleteSlot, int newSlot);
|
Task DeleteSlotAndSetSelectedIndex(NetUserId userId, int deleteSlot, int newSlot);
|
||||||
Task<PlayerPreferences?> GetPlayerPreferencesAsync(NetUserId userId, CancellationToken cancel);
|
Task<PlayerPreferences?> GetPlayerPreferencesAsync(NetUserId userId, CancellationToken cancel);
|
||||||
@@ -489,6 +492,12 @@ namespace Content.Server.Database
|
|||||||
return RunDbCommand(() => _db.SaveAdminOOCColorAsync(userId, color));
|
return RunDbCommand(() => _db.SaveAdminOOCColorAsync(userId, color));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task SaveConstructionFavoritesAsync(NetUserId userId, List<ProtoId<ConstructionPrototype>> constructionFavorites)
|
||||||
|
{
|
||||||
|
DbWriteOpsMetric.Inc();
|
||||||
|
return RunDbCommand(() => _db.SaveConstructionFavoritesAsync(userId, constructionFavorites));
|
||||||
|
}
|
||||||
|
|
||||||
public Task<PlayerPreferences?> GetPlayerPreferencesAsync(NetUserId userId, CancellationToken cancel)
|
public Task<PlayerPreferences?> GetPlayerPreferencesAsync(NetUserId userId, CancellationToken cancel)
|
||||||
{
|
{
|
||||||
DbReadOpsMetric.Inc();
|
DbReadOpsMetric.Inc();
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Content.Shared.Construction.Prototypes;
|
||||||
using Content.Shared.Preferences;
|
using Content.Shared.Preferences;
|
||||||
using Robust.Shared.Network;
|
using Robust.Shared.Network;
|
||||||
using Robust.Shared.Player;
|
using Robust.Shared.Player;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
namespace Content.Server.Preferences.Managers
|
namespace Content.Server.Preferences.Managers
|
||||||
{
|
{
|
||||||
@@ -22,5 +24,6 @@ namespace Content.Server.Preferences.Managers
|
|||||||
bool HavePreferencesLoaded(ICommonSession session);
|
bool HavePreferencesLoaded(ICommonSession session);
|
||||||
|
|
||||||
Task SetProfile(NetUserId userId, int slot, ICharacterProfile profile);
|
Task SetProfile(NetUserId userId, int slot, ICharacterProfile profile);
|
||||||
|
Task SetConstructionFavorites(NetUserId userId, List<ProtoId<ConstructionPrototype>> favorites);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,13 @@ using System.Threading;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Content.Server.Database;
|
using Content.Server.Database;
|
||||||
using Content.Shared.CCVar;
|
using Content.Shared.CCVar;
|
||||||
|
using Content.Shared.Construction.Prototypes;
|
||||||
using Content.Shared.Preferences;
|
using Content.Shared.Preferences;
|
||||||
using Robust.Server.Player;
|
using Robust.Server.Player;
|
||||||
using Robust.Shared.Configuration;
|
using Robust.Shared.Configuration;
|
||||||
using Robust.Shared.Network;
|
using Robust.Shared.Network;
|
||||||
using Robust.Shared.Player;
|
using Robust.Shared.Player;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Utility;
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
namespace Content.Server.Preferences.Managers
|
namespace Content.Server.Preferences.Managers
|
||||||
@@ -26,6 +28,7 @@ namespace Content.Server.Preferences.Managers
|
|||||||
[Dependency] private readonly IDependencyCollection _dependencies = default!;
|
[Dependency] private readonly IDependencyCollection _dependencies = default!;
|
||||||
[Dependency] private readonly ILogManager _log = default!;
|
[Dependency] private readonly ILogManager _log = default!;
|
||||||
[Dependency] private readonly UserDbDataManager _userDb = default!;
|
[Dependency] private readonly UserDbDataManager _userDb = default!;
|
||||||
|
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||||
|
|
||||||
// Cache player prefs on the server so we don't need as much async hell related to them.
|
// Cache player prefs on the server so we don't need as much async hell related to them.
|
||||||
private readonly Dictionary<NetUserId, PlayerPrefData> _cachedPlayerPrefs =
|
private readonly Dictionary<NetUserId, PlayerPrefData> _cachedPlayerPrefs =
|
||||||
@@ -41,6 +44,7 @@ namespace Content.Server.Preferences.Managers
|
|||||||
_netManager.RegisterNetMessage<MsgSelectCharacter>(HandleSelectCharacterMessage);
|
_netManager.RegisterNetMessage<MsgSelectCharacter>(HandleSelectCharacterMessage);
|
||||||
_netManager.RegisterNetMessage<MsgUpdateCharacter>(HandleUpdateCharacterMessage);
|
_netManager.RegisterNetMessage<MsgUpdateCharacter>(HandleUpdateCharacterMessage);
|
||||||
_netManager.RegisterNetMessage<MsgDeleteCharacter>(HandleDeleteCharacterMessage);
|
_netManager.RegisterNetMessage<MsgDeleteCharacter>(HandleDeleteCharacterMessage);
|
||||||
|
_netManager.RegisterNetMessage<MsgUpdateConstructionFavorites>(HandleUpdateConstructionFavoritesMessage);
|
||||||
_sawmill = _log.GetSawmill("prefs");
|
_sawmill = _log.GetSawmill("prefs");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,7 +72,7 @@ namespace Content.Server.Preferences.Managers
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
prefsData.Prefs = new PlayerPreferences(curPrefs.Characters, index, curPrefs.AdminOOCColor);
|
prefsData.Prefs = new PlayerPreferences(curPrefs.Characters, index, curPrefs.AdminOOCColor, curPrefs.ConstructionFavorites);
|
||||||
|
|
||||||
if (ShouldStorePrefs(message.MsgChannel.AuthType))
|
if (ShouldStorePrefs(message.MsgChannel.AuthType))
|
||||||
{
|
{
|
||||||
@@ -108,12 +112,28 @@ namespace Content.Server.Preferences.Managers
|
|||||||
[slot] = profile
|
[slot] = profile
|
||||||
};
|
};
|
||||||
|
|
||||||
prefsData.Prefs = new PlayerPreferences(profiles, slot, curPrefs.AdminOOCColor);
|
prefsData.Prefs = new PlayerPreferences(profiles, slot, curPrefs.AdminOOCColor, curPrefs.ConstructionFavorites);
|
||||||
|
|
||||||
if (ShouldStorePrefs(session.Channel.AuthType))
|
if (ShouldStorePrefs(session.Channel.AuthType))
|
||||||
await _db.SaveCharacterSlotAsync(userId, profile, slot);
|
await _db.SaveCharacterSlotAsync(userId, profile, slot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task SetConstructionFavorites(NetUserId userId, List<ProtoId<ConstructionPrototype>> favorites)
|
||||||
|
{
|
||||||
|
if (!_cachedPlayerPrefs.TryGetValue(userId, out var prefsData) || !prefsData.PrefsLoaded)
|
||||||
|
{
|
||||||
|
_sawmill.Error($"Tried to modify user {userId} preferences before they loaded.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var curPrefs = prefsData.Prefs!;
|
||||||
|
prefsData.Prefs = new PlayerPreferences(curPrefs.Characters, curPrefs.SelectedCharacterIndex, curPrefs.AdminOOCColor, favorites);
|
||||||
|
|
||||||
|
var session = _playerManager.GetSessionById(userId);
|
||||||
|
if (ShouldStorePrefs(session.Channel.AuthType))
|
||||||
|
await _db.SaveConstructionFavoritesAsync(userId, favorites);
|
||||||
|
}
|
||||||
|
|
||||||
private async void HandleDeleteCharacterMessage(MsgDeleteCharacter message)
|
private async void HandleDeleteCharacterMessage(MsgDeleteCharacter message)
|
||||||
{
|
{
|
||||||
var slot = message.Slot;
|
var slot = message.Slot;
|
||||||
@@ -151,7 +171,7 @@ namespace Content.Server.Preferences.Managers
|
|||||||
var arr = new Dictionary<int, ICharacterProfile>(curPrefs.Characters);
|
var arr = new Dictionary<int, ICharacterProfile>(curPrefs.Characters);
|
||||||
arr.Remove(slot);
|
arr.Remove(slot);
|
||||||
|
|
||||||
prefsData.Prefs = new PlayerPreferences(arr, nextSlot ?? curPrefs.SelectedCharacterIndex, curPrefs.AdminOOCColor);
|
prefsData.Prefs = new PlayerPreferences(arr, nextSlot ?? curPrefs.SelectedCharacterIndex, curPrefs.AdminOOCColor, curPrefs.ConstructionFavorites);
|
||||||
|
|
||||||
if (ShouldStorePrefs(message.MsgChannel.AuthType))
|
if (ShouldStorePrefs(message.MsgChannel.AuthType))
|
||||||
{
|
{
|
||||||
@@ -166,6 +186,40 @@ namespace Content.Server.Preferences.Managers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async void HandleUpdateConstructionFavoritesMessage(MsgUpdateConstructionFavorites message)
|
||||||
|
{
|
||||||
|
var userId = message.MsgChannel.UserId;
|
||||||
|
if (!_cachedPlayerPrefs.TryGetValue(userId, out var prefsData) || !prefsData.PrefsLoaded)
|
||||||
|
{
|
||||||
|
_sawmill.Warning($"User {userId} tried to modify preferences before they loaded.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate items in the message so that a modified client cannot freely store a gigabyte of arbitrary data.
|
||||||
|
var validatedSet = new HashSet<ProtoId<ConstructionPrototype>>();
|
||||||
|
foreach (var favorite in message.Favorites)
|
||||||
|
{
|
||||||
|
if (_prototypeManager.HasIndex(favorite))
|
||||||
|
validatedSet.Add(favorite);
|
||||||
|
}
|
||||||
|
|
||||||
|
var validatedList = message.Favorites;
|
||||||
|
if (validatedSet.Count != message.Favorites.Count)
|
||||||
|
{
|
||||||
|
// A difference in counts indicates that unrecognized or duplicate IDs are present.
|
||||||
|
_sawmill.Warning($"User {userId} sent invalid construction favorites.");
|
||||||
|
validatedList = validatedSet.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
var curPrefs = prefsData.Prefs!;
|
||||||
|
prefsData.Prefs = new PlayerPreferences(curPrefs.Characters, curPrefs.SelectedCharacterIndex, curPrefs.AdminOOCColor, validatedList);
|
||||||
|
|
||||||
|
if (ShouldStorePrefs(message.MsgChannel.AuthType))
|
||||||
|
{
|
||||||
|
await _db.SaveConstructionFavoritesAsync(userId, validatedList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Should only be called via UserDbDataManager.
|
// Should only be called via UserDbDataManager.
|
||||||
public async Task LoadData(ICommonSession session, CancellationToken cancel)
|
public async Task LoadData(ICommonSession session, CancellationToken cancel)
|
||||||
{
|
{
|
||||||
@@ -176,8 +230,8 @@ namespace Content.Server.Preferences.Managers
|
|||||||
{
|
{
|
||||||
PrefsLoaded = true,
|
PrefsLoaded = true,
|
||||||
Prefs = new PlayerPreferences(
|
Prefs = new PlayerPreferences(
|
||||||
new[] {new KeyValuePair<int, ICharacterProfile>(0, HumanoidCharacterProfile.Random())},
|
new[] { new KeyValuePair<int, ICharacterProfile>(0, HumanoidCharacterProfile.Random()) },
|
||||||
0, Color.Transparent)
|
0, Color.Transparent, [])
|
||||||
};
|
};
|
||||||
|
|
||||||
_cachedPlayerPrefs[session.UserId] = prefsData;
|
_cachedPlayerPrefs[session.UserId] = prefsData;
|
||||||
@@ -294,7 +348,7 @@ namespace Content.Server.Preferences.Managers
|
|||||||
return new PlayerPreferences(prefs.Characters.Select(p =>
|
return new PlayerPreferences(prefs.Characters.Select(p =>
|
||||||
{
|
{
|
||||||
return new KeyValuePair<int, ICharacterProfile>(p.Key, p.Value.Validated(session, collection));
|
return new KeyValuePair<int, ICharacterProfile>(p.Key, p.Value.Validated(session, collection));
|
||||||
}), prefs.SelectedCharacterIndex, prefs.AdminOOCColor);
|
}), prefs.SelectedCharacterIndex, prefs.AdminOOCColor, prefs.ConstructionFavorites);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<KeyValuePair<NetUserId, ICharacterProfile>> GetSelectedProfilesForPlayers(
|
public IEnumerable<KeyValuePair<NetUserId, ICharacterProfile>> GetSelectedProfilesForPlayers(
|
||||||
|
|||||||
36
Content.Shared/Preferences/MsgUpdateConstructionFavorites.cs
Normal file
36
Content.Shared/Preferences/MsgUpdateConstructionFavorites.cs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
using Content.Shared.Construction.Prototypes;
|
||||||
|
using Lidgren.Network;
|
||||||
|
using Robust.Shared.Network;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
|
namespace Content.Shared.Preferences;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The client sends this to update their construction favorites.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class MsgUpdateConstructionFavorites : NetMessage
|
||||||
|
{
|
||||||
|
public override MsgGroups MsgGroup => MsgGroups.Command;
|
||||||
|
|
||||||
|
public List<ProtoId<ConstructionPrototype>> Favorites = [];
|
||||||
|
|
||||||
|
public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer)
|
||||||
|
{
|
||||||
|
var length = buffer.ReadVariableInt32();
|
||||||
|
Favorites.Clear();
|
||||||
|
for (var i = 0; i < length; i++)
|
||||||
|
{
|
||||||
|
Favorites.Add(new ProtoId<ConstructionPrototype>(buffer.ReadString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serializer)
|
||||||
|
{
|
||||||
|
buffer.WriteVariableInt32(Favorites.Count);
|
||||||
|
foreach (var favorite in Favorites)
|
||||||
|
{
|
||||||
|
buffer.Write(favorite);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
using Content.Shared.Construction.Prototypes;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Serialization;
|
using Robust.Shared.Serialization;
|
||||||
using Robust.Shared.Utility;
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
@@ -13,11 +15,12 @@ namespace Content.Shared.Preferences
|
|||||||
{
|
{
|
||||||
private Dictionary<int, ICharacterProfile> _characters;
|
private Dictionary<int, ICharacterProfile> _characters;
|
||||||
|
|
||||||
public PlayerPreferences(IEnumerable<KeyValuePair<int, ICharacterProfile>> characters, int selectedCharacterIndex, Color adminOOCColor)
|
public PlayerPreferences(IEnumerable<KeyValuePair<int, ICharacterProfile>> characters, int selectedCharacterIndex, Color adminOOCColor, List<ProtoId<ConstructionPrototype>> constructionFavorites)
|
||||||
{
|
{
|
||||||
_characters = new Dictionary<int, ICharacterProfile>(characters);
|
_characters = new Dictionary<int, ICharacterProfile>(characters);
|
||||||
SelectedCharacterIndex = selectedCharacterIndex;
|
SelectedCharacterIndex = selectedCharacterIndex;
|
||||||
AdminOOCColor = adminOOCColor;
|
AdminOOCColor = adminOOCColor;
|
||||||
|
ConstructionFavorites = constructionFavorites;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -42,6 +45,11 @@ namespace Content.Shared.Preferences
|
|||||||
|
|
||||||
public Color AdminOOCColor { get; set; }
|
public Color AdminOOCColor { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// List of favorite items in the construction menu.
|
||||||
|
/// </summary>
|
||||||
|
public List<ProtoId<ConstructionPrototype>> ConstructionFavorites { get; set; } = [];
|
||||||
|
|
||||||
public int IndexOfCharacter(ICharacterProfile profile)
|
public int IndexOfCharacter(ICharacterProfile profile)
|
||||||
{
|
{
|
||||||
return _characters.FirstOrNull(p => p.Value == profile)?.Key ?? -1;
|
return _characters.FirstOrNull(p => p.Value == profile)?.Key ?? -1;
|
||||||
|
|||||||
Reference in New Issue
Block a user