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:
metalgearsloth
2025-02-12 04:30:24 +11:00
committed by GitHub
parent c197d3db37
commit 15b28936df
17 changed files with 4314 additions and 5 deletions

View File

@@ -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);

View File

@@ -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">

View File

@@ -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)

File diff suppressed because it is too large Load Diff

View File

@@ -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");
}
}
}

View File

@@ -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");

File diff suppressed because it is too large Load Diff

View File

@@ -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");
}
}
}

View File

@@ -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");

View File

@@ -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>

View File

@@ -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)

View File

@@ -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>

View File

@@ -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;
} }

View File

@@ -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>

View File

@@ -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));

View File

@@ -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

View File

@@ -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