Client customization late-join spawner priority for arrivals/cryostorage (#24586)

* Initial commit, requires server restart to take effect

* Exposes callbacks directly instead, takes effect immediately

* Cleaned up control flow, swapped cvar for client customization

* Switched to int, dictionary of callbacks, migration

* Update Content.Shared/Preferences/SpawnPriorityPreference.cs

* krunkle stan

---------

Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
This commit is contained in:
Krunklehorn
2024-02-01 05:12:09 -05:00
committed by GitHub
parent d76121d470
commit ed0f2aa221
18 changed files with 3654 additions and 20 deletions

View File

@@ -96,6 +96,12 @@
<Control HorizontalExpand="True"/>
<OptionButton Name="CBackpackButton" HorizontalAlignment="Right" />
</BoxContainer>
<!-- Spawn Priority -->
<BoxContainer HorizontalExpand="True">
<Label Text="{Loc 'humanoid-profile-editor-spawn-priority-label'}" />
<Control HorizontalExpand="True"/>
<OptionButton Name="CSpawnPriorityButton" HorizontalAlignment="Right" />
</BoxContainer>
</BoxContainer>
<!-- Skin -->
<BoxContainer Margin="10" HorizontalExpand="True" Orientation="Vertical">

View File

@@ -72,6 +72,7 @@ namespace Content.Client.Preferences.UI
private Slider _skinColor => CSkin;
private OptionButton _clothingButton => CClothingButton;
private OptionButton _backpackButton => CBackpackButton;
private OptionButton _spawnPriorityButton => CSpawnPriorityButton;
private SingleMarkingPicker _hairPicker => CHairStylePicker;
private SingleMarkingPicker _facialHairPicker => CFacialHairPicker;
private EyeColorPicker _eyesPicker => CEyeColorPicker;
@@ -340,6 +341,21 @@ namespace Content.Client.Preferences.UI
#endregion Backpack
#region SpawnPriority
foreach (var value in Enum.GetValues<SpawnPriorityPreference>())
{
_spawnPriorityButton.AddItem(Loc.GetString($"humanoid-profile-editor-preference-spawn-priority-{value.ToString().ToLower()}"), (int) value);
}
_spawnPriorityButton.OnItemSelected += args =>
{
_spawnPriorityButton.SelectId(args.Id);
SetSpawnPriority((SpawnPriorityPreference) args.Id);
};
#endregion SpawnPriority
#region Eyes
_eyesPicker.OnEyeColorPicked += newColor =>
@@ -799,6 +815,12 @@ namespace Content.Client.Preferences.UI
IsDirty = true;
}
private void SetSpawnPriority(SpawnPriorityPreference newSpawnPriority)
{
Profile = Profile?.WithSpawnPriorityPreference(newSpawnPriority);
IsDirty = true;
}
public void Save()
{
IsDirty = false;
@@ -974,6 +996,16 @@ namespace Content.Client.Preferences.UI
_backpackButton.SelectId((int) Profile.Backpack);
}
private void UpdateSpawnPriorityControls()
{
if (Profile == null)
{
return;
}
_spawnPriorityButton.SelectId((int) Profile.SpawnPriority);
}
private void UpdateHairPickers()
{
if (Profile == null)
@@ -1113,6 +1145,7 @@ namespace Content.Client.Preferences.UI
UpdateSpecies();
UpdateClothingControls();
UpdateBackpackControls();
UpdateSpawnPriorityControls();
UpdateAgeEdit();
UpdateEyePickers();
UpdateSaveButton();

View File

@@ -56,6 +56,7 @@ namespace Content.IntegrationTests.Tests.Preferences
),
ClothingPreference.Jumpskirt,
BackpackPreference.Backpack,
SpawnPriorityPreference.Arrivals,
new Dictionary<string, JobPriority>
{
{SharedGameTicker.FallbackOverflowJob, JobPriority.High}

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 SpawnPriorityPreference : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "spawn_priority",
table: "profile",
type: "integer",
nullable: false,
defaultValue: 0);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "spawn_priority",
table: "profile");
}
}
}

View File

@@ -7,6 +7,7 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
using NpgsqlTypes;
#nullable disable
@@ -19,7 +20,7 @@ namespace Content.Server.Database.Migrations.Postgres
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "7.0.4")
.HasAnnotation("ProductVersion", "8.0.0")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
@@ -803,6 +804,10 @@ namespace Content.Server.Database.Migrations.Postgres
.HasColumnType("integer")
.HasColumnName("slot");
b.Property<int>("SpawnPriority")
.HasColumnType("integer")
.HasColumnName("spawn_priority");
b.Property<string>("Species")
.IsRequired()
.HasColumnType("text")
@@ -879,7 +884,7 @@ namespace Content.Server.Database.Migrations.Postgres
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<ValueTuple<IPAddress, int>?>("Address")
b.Property<NpgsqlInet?>("Address")
.HasColumnType("inet")
.HasColumnName("address");
@@ -1021,7 +1026,7 @@ namespace Content.Server.Database.Migrations.Postgres
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<ValueTuple<IPAddress, int>?>("Address")
b.Property<NpgsqlInet?>("Address")
.HasColumnType("inet")
.HasColumnName("address");

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 SpawnPriorityPreference : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "spawn_priority",
table: "profile",
type: "INTEGER",
nullable: false,
defaultValue: 0);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "spawn_priority",
table: "profile");
}
}
}

View File

@@ -15,7 +15,7 @@ namespace Content.Server.Database.Migrations.Sqlite
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "7.0.4");
modelBuilder.HasAnnotation("ProductVersion", "8.0.0");
modelBuilder.Entity("Content.Server.Database.Admin", b =>
{
@@ -757,6 +757,10 @@ namespace Content.Server.Database.Migrations.Sqlite
.HasColumnType("INTEGER")
.HasColumnName("slot");
b.Property<int>("SpawnPriority")
.HasColumnType("INTEGER")
.HasColumnName("spawn_priority");
b.Property<string>("Species")
.IsRequired()
.HasColumnType("TEXT")

View File

@@ -338,6 +338,7 @@ namespace Content.Server.Database
public string SkinColor { get; set; } = null!;
public string Clothing { get; set; } = null!;
public string Backpack { get; set; } = null!;
public int SpawnPriority { get; set; } = 0;
public List<Job> Jobs { get; } = new();
public List<Antag> Antags { get; } = new();
public List<Trait> Traits { get; } = new();

View File

@@ -186,6 +186,8 @@ namespace Content.Server.Database
if (Enum.TryParse<BackpackPreference>(profile.Backpack, true, out var backpackVal))
backpack = backpackVal;
var spawnPriority = (SpawnPriorityPreference) profile.SpawnPriority;
var gender = sex == Sex.Male ? Gender.Male : Gender.Female;
if (Enum.TryParse<Gender>(profile.Gender, true, out var genderVal))
gender = genderVal;
@@ -225,6 +227,7 @@ namespace Content.Server.Database
),
clothing,
backpack,
spawnPriority,
jobs,
(PreferenceUnavailableMode) profile.PreferenceUnavailable,
antags.ToList(),

View File

@@ -12,7 +12,6 @@ using Content.Server.Screens.Components;
using Content.Server.Shuttles.Components;
using Content.Server.Shuttles.Events;
using Content.Server.Spawners.Components;
using Content.Server.Spawners.EntitySystems;
using Content.Server.Station.Components;
using Content.Server.Station.Systems;
using Content.Shared.Administration;
@@ -82,7 +81,6 @@ public sealed class ArrivalsSystem : EntitySystem
{
base.Initialize();
SubscribeLocalEvent<PlayerSpawningEvent>(OnPlayerSpawn, before: new[] { typeof(SpawnPointSystem), typeof(ContainerSpawnPointSystem) });
SubscribeLocalEvent<StationArrivalsComponent, ComponentStartup>(OnArrivalsStartup);
SubscribeLocalEvent<ArrivalsShuttleComponent, ComponentStartup>(OnShuttleStartup);
@@ -314,7 +312,7 @@ public sealed class ArrivalsSystem : EntitySystem
}
}
private void OnPlayerSpawn(PlayerSpawningEvent ev)
public void HandlePlayerSpawning(PlayerSpawningEvent ev)
{
if (ev.SpawnResult != null)
return;

View File

@@ -1,5 +1,4 @@
using Content.Server.GameTicking;
using Content.Server.Shuttles.Systems;
using Content.Server.Spawners.Components;
using Content.Server.Station.Systems;
using Robust.Server.Containers;
@@ -16,12 +15,7 @@ public sealed class ContainerSpawnPointSystem : EntitySystem
[Dependency] private readonly StationSystem _station = default!;
[Dependency] private readonly StationSpawningSystem _stationSpawning = default!;
public override void Initialize()
{
SubscribeLocalEvent<PlayerSpawningEvent>(OnSpawnPlayer, before: new[] { typeof(SpawnPointSystem) }, after: new[] { typeof(ArrivalsSystem) });
}
private void OnSpawnPlayer(PlayerSpawningEvent args)
public void HandlePlayerSpawning(PlayerSpawningEvent args)
{
if (args.SpawnResult != null)
return;

View File

@@ -1,5 +1,4 @@
using Content.Server.GameTicking;
using Content.Server.Shuttles.Systems;
using Content.Server.Spawners.Components;
using Content.Server.Station.Systems;
using Robust.Shared.Map;
@@ -16,10 +15,10 @@ public sealed class SpawnPointSystem : EntitySystem
public override void Initialize()
{
SubscribeLocalEvent<PlayerSpawningEvent>(OnSpawnPlayer, after: new[] { typeof(ContainerSpawnPointSystem), typeof(ArrivalsSystem) });
SubscribeLocalEvent<PlayerSpawningEvent>(OnPlayerSpawning);
}
private void OnSpawnPlayer(PlayerSpawningEvent args)
private void OnPlayerSpawning(PlayerSpawningEvent args)
{
if (args.SpawnResult != null)
return;

View File

@@ -4,6 +4,8 @@ using Content.Server.Humanoid;
using Content.Server.IdentityManagement;
using Content.Server.Mind.Commands;
using Content.Server.PDA;
using Content.Server.Shuttles.Systems;
using Content.Server.Spawners.EntitySystems;
using Content.Server.Station.Components;
using Content.Shared.Access.Components;
using Content.Shared.Access.Systems;
@@ -44,12 +46,23 @@ public sealed class StationSpawningSystem : SharedStationSpawningSystem
[Dependency] private readonly IdentitySystem _identity = default!;
[Dependency] private readonly MetaDataSystem _metaSystem = default!;
[Dependency] private readonly ArrivalsSystem _arrivalsSystem = default!;
[Dependency] private readonly ContainerSpawnPointSystem _containerSpawnPointSystem = default!;
private bool _randomizeCharacters;
private Dictionary<SpawnPriorityPreference, Action<PlayerSpawningEvent>> _spawnerCallbacks = new();
/// <inheritdoc/>
public override void Initialize()
{
_configurationManager.OnValueChanged(CCVars.ICRandomCharacters, e => _randomizeCharacters = e, true);
_spawnerCallbacks = new Dictionary<SpawnPriorityPreference, Action<PlayerSpawningEvent>>()
{
{ SpawnPriorityPreference.Arrivals, _arrivalsSystem.HandlePlayerSpawning },
{ SpawnPriorityPreference.Cryosleep, _containerSpawnPointSystem.HandlePlayerSpawning }
};
}
/// <summary>
@@ -70,6 +83,30 @@ public sealed class StationSpawningSystem : SharedStationSpawningSystem
throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station));
var ev = new PlayerSpawningEvent(job, profile, station);
if (station != null && profile != null)
{
/// Try to call the character's preferred spawner first.
if (_spawnerCallbacks.TryGetValue(profile.SpawnPriority, out var preferredSpawner))
{
preferredSpawner(ev);
foreach (var (key, remainingSpawner) in _spawnerCallbacks)
{
if (key == profile.SpawnPriority)
continue;
remainingSpawner(ev);
}
}
else
{
/// Call all of them in the typical order.
foreach (var typicalSpawner in _spawnerCallbacks.Values)
typicalSpawner(ev);
}
}
RaiseLocalEvent(ev);
DebugTools.Assert(ev.SpawnResult is { Valid: true } or null);

View File

@@ -41,6 +41,7 @@ namespace Content.Shared.Preferences
HumanoidCharacterAppearance appearance,
ClothingPreference clothing,
BackpackPreference backpack,
SpawnPriorityPreference spawnPriority,
Dictionary<string, JobPriority> jobPriorities,
PreferenceUnavailableMode preferenceUnavailable,
List<string> antagPreferences,
@@ -55,6 +56,7 @@ namespace Content.Shared.Preferences
Appearance = appearance;
Clothing = clothing;
Backpack = backpack;
SpawnPriority = spawnPriority;
_jobPriorities = jobPriorities;
PreferenceUnavailable = preferenceUnavailable;
_antagPreferences = antagPreferences;
@@ -67,7 +69,7 @@ namespace Content.Shared.Preferences
Dictionary<string, JobPriority> jobPriorities,
List<string> antagPreferences,
List<string> traitPreferences)
: this(other.Name, other.FlavorText, other.Species, other.Age, other.Sex, other.Gender, other.Appearance, other.Clothing, other.Backpack,
: this(other.Name, other.FlavorText, other.Species, other.Age, other.Sex, other.Gender, other.Appearance, other.Clothing, other.Backpack, other.SpawnPriority,
jobPriorities, other.PreferenceUnavailable, antagPreferences, traitPreferences)
{
}
@@ -88,11 +90,12 @@ namespace Content.Shared.Preferences
HumanoidCharacterAppearance appearance,
ClothingPreference clothing,
BackpackPreference backpack,
SpawnPriorityPreference spawnPriority,
IReadOnlyDictionary<string, JobPriority> jobPriorities,
PreferenceUnavailableMode preferenceUnavailable,
IReadOnlyList<string> antagPreferences,
IReadOnlyList<string> traitPreferences)
: this(name, flavortext, species, age, sex, gender, appearance, clothing, backpack, new Dictionary<string, JobPriority>(jobPriorities),
: this(name, flavortext, species, age, sex, gender, appearance, clothing, backpack, spawnPriority, new Dictionary<string, JobPriority>(jobPriorities),
preferenceUnavailable, new List<string>(antagPreferences), new List<string>(traitPreferences))
{
}
@@ -112,6 +115,7 @@ namespace Content.Shared.Preferences
new HumanoidCharacterAppearance(),
ClothingPreference.Jumpsuit,
BackpackPreference.Backpack,
SpawnPriorityPreference.Arrivals,
new Dictionary<string, JobPriority>
{
{SharedGameTicker.FallbackOverflowJob, JobPriority.High}
@@ -139,6 +143,7 @@ namespace Content.Shared.Preferences
HumanoidCharacterAppearance.DefaultWithSpecies(species),
ClothingPreference.Jumpsuit,
BackpackPreference.Backpack,
SpawnPriorityPreference.Arrivals,
new Dictionary<string, JobPriority>
{
{SharedGameTicker.FallbackOverflowJob, JobPriority.High}
@@ -180,7 +185,7 @@ namespace Content.Shared.Preferences
var name = GetName(species, gender);
return new HumanoidCharacterProfile(name, "", species, age, sex, gender, HumanoidCharacterAppearance.Random(species, sex), ClothingPreference.Jumpsuit, BackpackPreference.Backpack,
return new HumanoidCharacterProfile(name, "", species, age, sex, gender, HumanoidCharacterAppearance.Random(species, sex), ClothingPreference.Jumpsuit, BackpackPreference.Backpack, SpawnPriorityPreference.Arrivals,
new Dictionary<string, JobPriority>
{
{SharedGameTicker.FallbackOverflowJob, JobPriority.High},
@@ -206,6 +211,7 @@ namespace Content.Shared.Preferences
public HumanoidCharacterAppearance Appearance { get; private set; }
public ClothingPreference Clothing { get; private set; }
public BackpackPreference Backpack { get; private set; }
public SpawnPriorityPreference SpawnPriority { get; private set; }
public IReadOnlyDictionary<string, JobPriority> JobPriorities => _jobPriorities;
public IReadOnlyList<string> AntagPreferences => _antagPreferences;
public IReadOnlyList<string> TraitPreferences => _traitPreferences;
@@ -255,6 +261,10 @@ namespace Content.Shared.Preferences
{
return new(this) { Backpack = backpack };
}
public HumanoidCharacterProfile WithSpawnPriorityPreference(SpawnPriorityPreference spawnPriority)
{
return new(this) { SpawnPriority = spawnPriority };
}
public HumanoidCharacterProfile WithJobPriorities(IEnumerable<KeyValuePair<string, JobPriority>> jobPriorities)
{
return new(this, new Dictionary<string, JobPriority>(jobPriorities), _antagPreferences, _traitPreferences);
@@ -344,6 +354,7 @@ namespace Content.Shared.Preferences
if (PreferenceUnavailable != other.PreferenceUnavailable) return false;
if (Clothing != other.Clothing) return false;
if (Backpack != other.Backpack) return false;
if (SpawnPriority != other.SpawnPriority) return false;
if (!_jobPriorities.SequenceEqual(other._jobPriorities)) return false;
if (!_antagPreferences.SequenceEqual(other._antagPreferences)) return false;
if (!_traitPreferences.SequenceEqual(other._traitPreferences)) return false;
@@ -457,6 +468,13 @@ namespace Content.Shared.Preferences
_ => BackpackPreference.Backpack // Invalid enum values.
};
var spawnPriority = SpawnPriority switch
{
SpawnPriorityPreference.Arrivals => SpawnPriorityPreference.Arrivals,
SpawnPriorityPreference.Cryosleep => SpawnPriorityPreference.Cryosleep,
_ => SpawnPriorityPreference.Arrivals // Invalid enum values.
};
var priorities = new Dictionary<string, JobPriority>(JobPriorities
.Where(p => prototypeManager.HasIndex<JobPrototype>(p.Key) && p.Value switch
{
@@ -483,6 +501,7 @@ namespace Content.Shared.Preferences
Appearance = appearance;
Clothing = clothing;
Backpack = backpack;
SpawnPriority = spawnPriority;
_jobPriorities.Clear();
@@ -526,6 +545,7 @@ namespace Content.Shared.Preferences
Clothing,
Backpack
),
SpawnPriority,
PreferenceUnavailable,
_jobPriorities,
_antagPreferences,

View File

@@ -0,0 +1,15 @@
namespace Content.Shared.Preferences
{
/// <summary>
/// The spawn priority preference for a profile. Stored in database!
/// </summary>
public enum SpawnPriorityPreference
{
///////////////////////
/// DO NOT TOUCH!!! ///
///////////////////////
None = 0,
Arrivals = 1,
Cryosleep = 2,
}
}

View File

@@ -21,6 +21,7 @@ humanoid-profile-editor-export-button = Export
humanoid-profile-editor-save-button = Save
humanoid-profile-editor-clothing-label = Clothing:
humanoid-profile-editor-backpack-label = Backpack:
humanoid-profile-editor-spawn-priority-label = Spawn priority:
humanoid-profile-editor-eyes-label = Eye color:
humanoid-profile-editor-jobs-tab = Jobs
humanoid-profile-editor-preference-unavailable-stay-in-lobby-button = Stay in lobby if preference unavailable.
@@ -30,6 +31,12 @@ humanoid-profile-editor-preference-jumpskirt = Jumpskirt
humanoid-profile-editor-preference-backpack = Backpack
humanoid-profile-editor-preference-satchel = Satchel
humanoid-profile-editor-preference-duffelbag = Duffelbag
# Spawn priority
humanoid-profile-editor-preference-spawn-priority-none = None
humanoid-profile-editor-preference-spawn-priority-arrivals = Arrivals
humanoid-profile-editor-preference-spawn-priority-cryosleep = Cryosleep
humanoid-profile-editor-jobs-amount-in-department-tooltip = Jobs in the {$departmentName} department
humanoid-profile-editor-department-jobs-label = {$departmentName} jobs
humanoid-profile-editor-antags-tab = Antags