Add loadout names (#31303)
* Add loadout names Did it for AI, breaking change for pgsql + migrations in general. Nothing atm uses it. * the box * Spawning cherry pick * Fix nit * revert * Final cleanup * Real * Name UI fix * Migrations * a * Review * Re-run migrations --------- Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
This commit is contained in:
@@ -1015,6 +1015,13 @@ namespace Content.Client.Lobby.UI
|
|||||||
_loadoutWindow.RefreshLoadouts(roleLoadout, session, collection);
|
_loadoutWindow.RefreshLoadouts(roleLoadout, session, collection);
|
||||||
_loadoutWindow.OpenCenteredLeft();
|
_loadoutWindow.OpenCenteredLeft();
|
||||||
|
|
||||||
|
_loadoutWindow.OnNameChanged += name =>
|
||||||
|
{
|
||||||
|
roleLoadout.EntityName = name;
|
||||||
|
Profile = Profile.WithLoadout(roleLoadout);
|
||||||
|
SetDirty();
|
||||||
|
};
|
||||||
|
|
||||||
_loadoutWindow.OnLoadoutPressed += (loadoutGroup, loadoutProto) =>
|
_loadoutWindow.OnLoadoutPressed += (loadoutGroup, loadoutProto) =>
|
||||||
{
|
{
|
||||||
roleLoadout.AddLoadout(loadoutGroup, loadoutProto, _prototypeManager);
|
roleLoadout.AddLoadout(loadoutGroup, loadoutProto, _prototypeManager);
|
||||||
|
|||||||
@@ -5,17 +5,15 @@
|
|||||||
SetSize="800 800"
|
SetSize="800 800"
|
||||||
MinSize="800 128">
|
MinSize="800 128">
|
||||||
<BoxContainer Orientation="Vertical" VerticalExpand="True">
|
<BoxContainer Orientation="Vertical" VerticalExpand="True">
|
||||||
<!--
|
|
||||||
<BoxContainer Name="RoleNameBox" Orientation="Vertical" Margin="10">
|
<BoxContainer Name="RoleNameBox" Orientation="Vertical" Margin="10">
|
||||||
<Label Name="LoadoutNameLabel" Text="{Loc 'loadout-name-edit-label'}"/>
|
<Label Name="LoadoutNameLabel" Text="{Loc 'loadout-name-edit-label'}"/>
|
||||||
<PanelContainer HorizontalExpand="True" SetHeight="24">
|
<PanelContainer HorizontalExpand="True" SetHeight="24">
|
||||||
<PanelContainer.PanelOverride>
|
<PanelContainer.PanelOverride>
|
||||||
<graphics:StyleBoxFlat BackgroundColor="#1B1B1E" />
|
<graphics:StyleBoxFlat BackgroundColor="#1B1B1E" />
|
||||||
</PanelContainer.PanelOverride>
|
</PanelContainer.PanelOverride>
|
||||||
<LineEdit Name="RoleNameEdit" ToolTip="{Loc 'loadout-name-edit-tooltip'}" VerticalExpand="True" HorizontalExpand="True"/>
|
<LineEdit Name="RoleNameEdit" VerticalExpand="True" HorizontalExpand="True"/>
|
||||||
</PanelContainer>
|
</PanelContainer>
|
||||||
</BoxContainer>
|
</BoxContainer>
|
||||||
-->
|
|
||||||
<VerticalTabContainer Name="LoadoutGroupsContainer"
|
<VerticalTabContainer Name="LoadoutGroupsContainer"
|
||||||
VerticalExpand="True"
|
VerticalExpand="True"
|
||||||
HorizontalExpand="True">
|
HorizontalExpand="True">
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using Content.Client.UserInterface.Controls;
|
using Content.Client.UserInterface.Controls;
|
||||||
|
using Content.Shared.Dataset;
|
||||||
using Content.Shared.Preferences;
|
using Content.Shared.Preferences;
|
||||||
using Content.Shared.Preferences.Loadouts;
|
using Content.Shared.Preferences.Loadouts;
|
||||||
|
using Content.Shared.Random.Helpers;
|
||||||
using Robust.Client.AutoGenerated;
|
using Robust.Client.AutoGenerated;
|
||||||
using Robust.Client.UserInterface.XAML;
|
using Robust.Client.UserInterface.XAML;
|
||||||
using Robust.Shared.Player;
|
using Robust.Shared.Player;
|
||||||
@@ -13,6 +15,7 @@ namespace Content.Client.Lobby.UI.Loadouts;
|
|||||||
[GenerateTypedNameReferences]
|
[GenerateTypedNameReferences]
|
||||||
public sealed partial class LoadoutWindow : FancyWindow
|
public sealed partial class LoadoutWindow : FancyWindow
|
||||||
{
|
{
|
||||||
|
public event Action<string>? OnNameChanged;
|
||||||
public event Action<ProtoId<LoadoutGroupPrototype>, ProtoId<LoadoutPrototype>>? OnLoadoutPressed;
|
public event Action<ProtoId<LoadoutGroupPrototype>, ProtoId<LoadoutPrototype>>? OnLoadoutPressed;
|
||||||
public event Action<ProtoId<LoadoutGroupPrototype>, ProtoId<LoadoutPrototype>>? OnLoadoutUnpressed;
|
public event Action<ProtoId<LoadoutGroupPrototype>, ProtoId<LoadoutPrototype>>? OnLoadoutUnpressed;
|
||||||
|
|
||||||
@@ -25,6 +28,23 @@ public sealed partial class LoadoutWindow : FancyWindow
|
|||||||
RobustXamlLoader.Load(this);
|
RobustXamlLoader.Load(this);
|
||||||
Profile = profile;
|
Profile = profile;
|
||||||
var protoManager = collection.Resolve<IPrototypeManager>();
|
var protoManager = collection.Resolve<IPrototypeManager>();
|
||||||
|
RoleNameEdit.IsValid = text => text.Length <= HumanoidCharacterProfile.MaxLoadoutNameLength;
|
||||||
|
|
||||||
|
// Hide if we can't edit the name.
|
||||||
|
if (!proto.CanCustomizeName)
|
||||||
|
{
|
||||||
|
RoleNameBox.Visible = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var name = loadout.EntityName;
|
||||||
|
|
||||||
|
RoleNameEdit.ToolTip = Loc.GetString(
|
||||||
|
"loadout-name-edit-tooltip",
|
||||||
|
("max", HumanoidCharacterProfile.MaxLoadoutNameLength));
|
||||||
|
RoleNameEdit.Text = name ?? string.Empty;
|
||||||
|
RoleNameEdit.OnTextChanged += args => OnNameChanged?.Invoke(args.Text);
|
||||||
|
}
|
||||||
|
|
||||||
// Hide if no groups
|
// Hide if no groups
|
||||||
if (proto.Groups.Count == 0)
|
if (proto.Groups.Count == 0)
|
||||||
|
|||||||
2117
Content.Server.Database/Migrations/Postgres/20250211131539_LoadoutNames.Designer.cs
generated
Normal file
2117
Content.Server.Database/Migrations/Postgres/20250211131539_LoadoutNames.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 LoadoutNames : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "entity_name",
|
||||||
|
table: "profile_role_loadout",
|
||||||
|
type: "character varying(256)",
|
||||||
|
maxLength: 256,
|
||||||
|
nullable: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "entity_name",
|
||||||
|
table: "profile_role_loadout");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -975,6 +975,11 @@ namespace Content.Server.Database.Migrations.Postgres
|
|||||||
|
|
||||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("EntityName")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)")
|
||||||
|
.HasColumnName("entity_name");
|
||||||
|
|
||||||
b.Property<int>("ProfileId")
|
b.Property<int>("ProfileId")
|
||||||
.HasColumnType("integer")
|
.HasColumnType("integer")
|
||||||
.HasColumnName("profile_id");
|
.HasColumnName("profile_id");
|
||||||
|
|||||||
2041
Content.Server.Database/Migrations/Sqlite/20250211131517_LoadoutNames.Designer.cs
generated
Normal file
2041
Content.Server.Database/Migrations/Sqlite/20250211131517_LoadoutNames.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 LoadoutNames : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "entity_name",
|
||||||
|
table: "profile_role_loadout",
|
||||||
|
type: "TEXT",
|
||||||
|
maxLength: 256,
|
||||||
|
nullable: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "entity_name",
|
||||||
|
table: "profile_role_loadout");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -921,6 +921,11 @@ namespace Content.Server.Database.Migrations.Sqlite
|
|||||||
.HasColumnType("INTEGER")
|
.HasColumnType("INTEGER")
|
||||||
.HasColumnName("profile_role_loadout_id");
|
.HasColumnName("profile_role_loadout_id");
|
||||||
|
|
||||||
|
b.Property<string>("EntityName")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("entity_name");
|
||||||
|
|
||||||
b.Property<int>("ProfileId")
|
b.Property<int>("ProfileId")
|
||||||
.HasColumnType("INTEGER")
|
.HasColumnType("INTEGER")
|
||||||
.HasColumnName("profile_id");
|
.HasColumnName("profile_id");
|
||||||
|
|||||||
@@ -480,6 +480,12 @@ namespace Content.Server.Database
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string RoleName { get; set; } = string.Empty;
|
public string RoleName { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Custom name of the role loadout if it supports it.
|
||||||
|
/// </summary>
|
||||||
|
[MaxLength(256)]
|
||||||
|
public string? EntityName { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Store the saved loadout groups. These may get validated and removed when loaded at runtime.
|
/// Store the saved loadout groups. These may get validated and removed when loaded at runtime.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -222,6 +222,7 @@ namespace Content.Server.Database
|
|||||||
{
|
{
|
||||||
var loadout = new RoleLoadout(role.RoleName)
|
var loadout = new RoleLoadout(role.RoleName)
|
||||||
{
|
{
|
||||||
|
EntityName = role.EntityName,
|
||||||
};
|
};
|
||||||
|
|
||||||
foreach (var group in role.Groups)
|
foreach (var group in role.Groups)
|
||||||
@@ -319,6 +320,7 @@ namespace Content.Server.Database
|
|||||||
var dz = new ProfileRoleLoadout()
|
var dz = new ProfileRoleLoadout()
|
||||||
{
|
{
|
||||||
RoleName = role,
|
RoleName = role,
|
||||||
|
EntityName = loadouts.EntityName ?? string.Empty,
|
||||||
};
|
};
|
||||||
|
|
||||||
foreach (var (group, groupLoadouts) in loadouts.SelectedLoadouts)
|
foreach (var (group, groupLoadouts) in loadouts.SelectedLoadouts)
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ namespace Content.Shared.Preferences
|
|||||||
private static readonly Regex ICNameCaseRegex = new(@"^(?<word>\w)|\b(?<word>\w)(?=\w*$)");
|
private static readonly Regex ICNameCaseRegex = new(@"^(?<word>\w)|\b(?<word>\w)(?=\w*$)");
|
||||||
|
|
||||||
public const int MaxNameLength = 32;
|
public const int MaxNameLength = 32;
|
||||||
|
public const int MaxLoadoutNameLength = 32;
|
||||||
public const int MaxDescLength = 512;
|
public const int MaxDescLength = 512;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -22,6 +22,11 @@ public sealed partial class RoleLoadout : IEquatable<RoleLoadout>
|
|||||||
[DataField]
|
[DataField]
|
||||||
public Dictionary<ProtoId<LoadoutGroupPrototype>, List<Loadout>> SelectedLoadouts = new();
|
public Dictionary<ProtoId<LoadoutGroupPrototype>, List<Loadout>> SelectedLoadouts = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Loadout specific name.
|
||||||
|
/// </summary>
|
||||||
|
public string? EntityName;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Loadout-specific data used for validation.
|
* Loadout-specific data used for validation.
|
||||||
*/
|
*/
|
||||||
@@ -42,6 +47,8 @@ public sealed partial class RoleLoadout : IEquatable<RoleLoadout>
|
|||||||
weh.SelectedLoadouts.Add(selected.Key, new List<Loadout>(selected.Value));
|
weh.SelectedLoadouts.Add(selected.Key, new List<Loadout>(selected.Value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
weh.EntityName = EntityName;
|
||||||
|
|
||||||
return weh;
|
return weh;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,10 +62,34 @@ public sealed partial class RoleLoadout : IEquatable<RoleLoadout>
|
|||||||
|
|
||||||
if (!protoManager.TryIndex(Role, out var roleProto))
|
if (!protoManager.TryIndex(Role, out var roleProto))
|
||||||
{
|
{
|
||||||
|
EntityName = null;
|
||||||
SelectedLoadouts.Clear();
|
SelectedLoadouts.Clear();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove name not allowed.
|
||||||
|
if (!roleProto.CanCustomizeName)
|
||||||
|
{
|
||||||
|
EntityName = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate name length
|
||||||
|
// TODO: Probably allow regex to be supplied?
|
||||||
|
if (EntityName != null)
|
||||||
|
{
|
||||||
|
var name = EntityName.Trim();
|
||||||
|
|
||||||
|
if (name.Length > HumanoidCharacterProfile.MaxNameLength)
|
||||||
|
{
|
||||||
|
EntityName = name[..HumanoidCharacterProfile.MaxNameLength];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name.Length == 0)
|
||||||
|
{
|
||||||
|
EntityName = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// In some instances we might not have picked up a new group for existing data.
|
// In some instances we might not have picked up a new group for existing data.
|
||||||
foreach (var groupProto in roleProto.Groups)
|
foreach (var groupProto in roleProto.Groups)
|
||||||
{
|
{
|
||||||
@@ -322,7 +353,8 @@ public sealed partial class RoleLoadout : IEquatable<RoleLoadout>
|
|||||||
|
|
||||||
if (!Role.Equals(other.Role) ||
|
if (!Role.Equals(other.Role) ||
|
||||||
SelectedLoadouts.Count != other.SelectedLoadouts.Count ||
|
SelectedLoadouts.Count != other.SelectedLoadouts.Count ||
|
||||||
Points != other.Points)
|
Points != other.Points ||
|
||||||
|
EntityName != other.EntityName)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,12 @@ public sealed partial class RoleLoadoutPrototype : IPrototype
|
|||||||
[IdDataField]
|
[IdDataField]
|
||||||
public string ID { get; } = string.Empty;
|
public string ID { get; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Can the user edit their entity name for this role loadout?
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public bool CanCustomizeName;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Should we use a random name for this loadout?
|
/// Should we use a random name for this loadout?
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -68,6 +68,11 @@ public abstract class SharedStationSpawningSystem : EntitySystem
|
|||||||
{
|
{
|
||||||
string? name = null;
|
string? name = null;
|
||||||
|
|
||||||
|
if (roleProto.CanCustomizeName)
|
||||||
|
{
|
||||||
|
name = loadout.EntityName;
|
||||||
|
}
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(name) && PrototypeManager.TryIndex(roleProto.NameDataset, out var nameData))
|
if (string.IsNullOrEmpty(name) && PrototypeManager.TryIndex(roleProto.NameDataset, out var nameData))
|
||||||
{
|
{
|
||||||
name = Loc.GetString(_random.Pick(nameData.Values));
|
name = Loc.GetString(_random.Pick(nameData.Values));
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Name
|
# Name
|
||||||
loadout-name-edit-label = Custom name
|
loadout-name-edit-label = Custom name
|
||||||
loadout-name-edit-tooltip = 32 characters max. If no name is specified a random one may be chosen for you.
|
loadout-name-edit-tooltip = {$max} characters max. If no name is specified a random one may be chosen for you.
|
||||||
|
|
||||||
# Restrictions
|
# Restrictions
|
||||||
loadout-restrictions = Restrictions
|
loadout-restrictions = Restrictions
|
||||||
|
|||||||
@@ -26,9 +26,15 @@
|
|||||||
- GroupSpeciesBreathTool
|
- GroupSpeciesBreathTool
|
||||||
|
|
||||||
# Silicons
|
# Silicons
|
||||||
|
#- type: roleLoadout
|
||||||
|
# id: JobBorg
|
||||||
|
# nameDataset: roleloadout doesn't support both so need to update that first.
|
||||||
|
# canCustomizeName: true
|
||||||
|
|
||||||
- type: roleLoadout
|
- type: roleLoadout
|
||||||
id: JobStationAi
|
id: JobStationAi
|
||||||
nameDataset: NamesAI
|
nameDataset: NamesAI
|
||||||
|
canCustomizeName: true
|
||||||
|
|
||||||
# Civilian
|
# Civilian
|
||||||
- type: roleLoadout
|
- type: roleLoadout
|
||||||
|
|||||||
Reference in New Issue
Block a user