Files
tbd-station-14/Content.Client/Lobby/LobbyUIController.cs
Leon Friedrich adeed705e6 Add Job preference tests (#28625)
* Misc Job related changes

* Add JobTest

* A

* Aa

* Lets not confuse the yaml linter

* fixes

* a
2024-06-06 00:19:24 +10:00

423 lines
14 KiB
C#

using System.Linq;
using Content.Client.Humanoid;
using Content.Client.Inventory;
using Content.Client.Lobby.UI;
using Content.Client.Players.PlayTimeTracking;
using Content.Client.Station;
using Content.Shared.CCVar;
using Content.Shared.Clothing;
using Content.Shared.GameTicking;
using Content.Shared.Humanoid;
using Content.Shared.Humanoid.Markings;
using Content.Shared.Humanoid.Prototypes;
using Content.Shared.Preferences;
using Content.Shared.Preferences.Loadouts;
using Content.Shared.Roles;
using Content.Shared.Traits;
using Robust.Client.Player;
using Robust.Client.ResourceManagement;
using Robust.Client.State;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controllers;
using Robust.Shared.Configuration;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Client.Lobby;
public sealed class LobbyUIController : UIController, IOnStateEntered<LobbyState>, IOnStateExited<LobbyState>
{
[Dependency] private readonly IClientPreferencesManager _preferencesManager = default!;
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
[Dependency] private readonly IFileDialogManager _dialogManager = default!;
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IResourceCache _resourceCache = default!;
[Dependency] private readonly IStateManager _stateManager = default!;
[Dependency] private readonly JobRequirementsManager _requirements = default!;
[Dependency] private readonly MarkingManager _markings = default!;
[UISystemDependency] private readonly HumanoidAppearanceSystem _humanoid = default!;
[UISystemDependency] private readonly ClientInventorySystem _inventory = default!;
[UISystemDependency] private readonly StationSpawningSystem _spawn = default!;
private CharacterSetupGui? _characterSetup;
private HumanoidProfileEditor? _profileEditor;
/// <summary>
/// This is the characher preview panel in the chat. This should only update if their character updates.
/// </summary>
private LobbyCharacterPreviewPanel? PreviewPanel => GetLobbyPreview();
/// <summary>
/// This is the modified profile currently being edited.
/// </summary>
private HumanoidCharacterProfile? EditedProfile => _profileEditor?.Profile;
private int? EditedSlot => _profileEditor?.CharacterSlot;
public override void Initialize()
{
base.Initialize();
_prototypeManager.PrototypesReloaded += OnProtoReload;
_preferencesManager.OnServerDataLoaded += PreferencesDataLoaded;
_requirements.Updated += OnRequirementsUpdated;
_configurationManager.OnValueChanged(CCVars.FlavorText, args =>
{
_profileEditor?.RefreshFlavorText();
});
_configurationManager.OnValueChanged(CCVars.GameRoleTimers, _ => RefreshProfileEditor());
_configurationManager.OnValueChanged(CCVars.GameRoleWhitelist, _ => RefreshProfileEditor());
}
private LobbyCharacterPreviewPanel? GetLobbyPreview()
{
if (_stateManager.CurrentState is LobbyState lobby)
{
return lobby.Lobby?.CharacterPreview;
}
return null;
}
private void OnRequirementsUpdated()
{
if (_profileEditor != null)
{
_profileEditor.RefreshAntags();
_profileEditor.RefreshJobs();
}
}
private void OnProtoReload(PrototypesReloadedEventArgs obj)
{
if (_profileEditor != null)
{
if (obj.WasModified<AntagPrototype>())
{
_profileEditor.RefreshAntags();
}
if (obj.WasModified<JobPrototype>() ||
obj.WasModified<DepartmentPrototype>())
{
_profileEditor.RefreshJobs();
}
if (obj.WasModified<LoadoutPrototype>() ||
obj.WasModified<LoadoutGroupPrototype>() ||
obj.WasModified<RoleLoadoutPrototype>())
{
_profileEditor.RefreshLoadouts();
}
if (obj.WasModified<SpeciesPrototype>())
{
_profileEditor.RefreshSpecies();
}
if (obj.WasModified<TraitPrototype>())
{
_profileEditor.RefreshTraits();
}
}
}
private void PreferencesDataLoaded()
{
PreviewPanel?.SetLoaded(true);
if (_stateManager.CurrentState is not LobbyState)
return;
ReloadCharacterSetup();
}
public void OnStateEntered(LobbyState state)
{
PreviewPanel?.SetLoaded(_preferencesManager.ServerDataLoaded);
ReloadCharacterSetup();
}
public void OnStateExited(LobbyState state)
{
PreviewPanel?.SetLoaded(false);
_profileEditor?.Dispose();
_characterSetup?.Dispose();
_characterSetup = null;
_profileEditor = null;
}
/// <summary>
/// Reloads every single character setup control.
/// </summary>
public void ReloadCharacterSetup()
{
RefreshLobbyPreview();
var (characterGui, profileEditor) = EnsureGui();
characterGui.ReloadCharacterPickers();
profileEditor.SetProfile(
(HumanoidCharacterProfile?) _preferencesManager.Preferences?.SelectedCharacter,
_preferencesManager.Preferences?.SelectedCharacterIndex);
}
/// <summary>
/// Refreshes the character preview in the lobby chat.
/// </summary>
private void RefreshLobbyPreview()
{
if (PreviewPanel == null)
return;
// Get selected character, load it, then set it
var character = _preferencesManager.Preferences?.SelectedCharacter;
if (character is not HumanoidCharacterProfile humanoid)
{
PreviewPanel.SetSprite(EntityUid.Invalid);
PreviewPanel.SetSummaryText(string.Empty);
return;
}
var dummy = LoadProfileEntity(humanoid, null, true);
PreviewPanel.SetSprite(dummy);
PreviewPanel.SetSummaryText(humanoid.Summary);
}
private void RefreshProfileEditor()
{
_profileEditor?.RefreshAntags();
_profileEditor?.RefreshJobs();
_profileEditor?.RefreshLoadouts();
}
private void SaveProfile()
{
DebugTools.Assert(EditedProfile != null);
if (EditedProfile == null || EditedSlot == null)
return;
var selected = _preferencesManager.Preferences?.SelectedCharacterIndex;
if (selected == null)
return;
_preferencesManager.UpdateCharacter(EditedProfile, EditedSlot.Value);
ReloadCharacterSetup();
}
private (CharacterSetupGui, HumanoidProfileEditor) EnsureGui()
{
if (_characterSetup != null && _profileEditor != null)
{
_characterSetup.Visible = true;
_profileEditor.Visible = true;
return (_characterSetup, _profileEditor);
}
_profileEditor = new HumanoidProfileEditor(
_preferencesManager,
_configurationManager,
EntityManager,
_dialogManager,
_logManager,
_playerManager,
_prototypeManager,
_requirements,
_markings);
_characterSetup = new CharacterSetupGui(EntityManager, _prototypeManager, _resourceCache, _preferencesManager, _profileEditor);
_characterSetup.CloseButton.OnPressed += _ =>
{
// Reset sliders etc.
_profileEditor.SetProfile(null, null);
_profileEditor.Visible = false;
if (_stateManager.CurrentState is LobbyState lobbyGui)
{
lobbyGui.SwitchState(LobbyGui.LobbyGuiState.Default);
}
};
_profileEditor.Save += SaveProfile;
_characterSetup.SelectCharacter += args =>
{
_preferencesManager.SelectCharacter(args);
ReloadCharacterSetup();
};
_characterSetup.DeleteCharacter += args =>
{
_preferencesManager.DeleteCharacter(args);
// Reload everything
if (EditedSlot == args)
{
ReloadCharacterSetup();
}
else
{
// Only need to reload character pickers
_characterSetup?.ReloadCharacterPickers();
}
};
if (_stateManager.CurrentState is LobbyState lobby)
{
lobby.Lobby?.CharacterSetupState.AddChild(_characterSetup);
}
return (_characterSetup, _profileEditor);
}
#region Helpers
/// <summary>
/// Applies the highest priority job's clothes to the dummy.
/// </summary>
public void GiveDummyJobClothesLoadout(EntityUid dummy, JobPrototype? jobProto, HumanoidCharacterProfile profile)
{
var job = jobProto ?? GetPreferredJob(profile);
GiveDummyJobClothes(dummy, profile, job);
if (_prototypeManager.HasIndex<RoleLoadoutPrototype>(LoadoutSystem.GetJobPrototype(job.ID)))
{
var loadout = profile.GetLoadoutOrDefault(LoadoutSystem.GetJobPrototype(job.ID), profile.Species, EntityManager, _prototypeManager);
GiveDummyLoadout(dummy, loadout);
}
}
/// <summary>
/// Gets the highest priority job for the profile.
/// </summary>
public JobPrototype GetPreferredJob(HumanoidCharacterProfile profile)
{
var highPriorityJob = profile.JobPriorities.FirstOrDefault(p => p.Value == JobPriority.High).Key;
// ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract (what is resharper smoking?)
return _prototypeManager.Index<JobPrototype>(highPriorityJob.Id ?? SharedGameTicker.FallbackOverflowJob);
}
public void GiveDummyLoadout(EntityUid uid, RoleLoadout? roleLoadout)
{
if (roleLoadout == null)
return;
foreach (var group in roleLoadout.SelectedLoadouts.Values)
{
foreach (var loadout in group)
{
if (!_prototypeManager.TryIndex(loadout.Prototype, out var loadoutProto))
continue;
_spawn.EquipStartingGear(uid, _prototypeManager.Index(loadoutProto.Equipment));
}
}
}
/// <summary>
/// Applies the specified job's clothes to the dummy.
/// </summary>
public void GiveDummyJobClothes(EntityUid dummy, HumanoidCharacterProfile profile, JobPrototype job)
{
if (!_inventory.TryGetSlots(dummy, out var slots))
return;
// Apply loadout
if (profile.Loadouts.TryGetValue(job.ID, out var jobLoadout))
{
foreach (var loadouts in jobLoadout.SelectedLoadouts.Values)
{
foreach (var loadout in loadouts)
{
if (!_prototypeManager.TryIndex(loadout.Prototype, out var loadoutProto))
continue;
// TODO: Need some way to apply starting gear to an entity coz holy fucking shit dude.
var loadoutGear = _prototypeManager.Index(loadoutProto.Equipment);
foreach (var slot in slots)
{
var itemType = loadoutGear.GetGear(slot.Name);
if (_inventory.TryUnequip(dummy, slot.Name, out var unequippedItem, silent: true, force: true, reparent: false))
{
EntityManager.DeleteEntity(unequippedItem.Value);
}
if (itemType != string.Empty)
{
var item = EntityManager.SpawnEntity(itemType, MapCoordinates.Nullspace);
_inventory.TryEquip(dummy, item, slot.Name, true, true);
}
}
}
}
}
if (job.StartingGear == null)
return;
var gear = _prototypeManager.Index<StartingGearPrototype>(job.StartingGear);
foreach (var slot in slots)
{
var itemType = gear.GetGear(slot.Name);
if (_inventory.TryUnequip(dummy, slot.Name, out var unequippedItem, silent: true, force: true, reparent: false))
{
EntityManager.DeleteEntity(unequippedItem.Value);
}
if (itemType != string.Empty)
{
var item = EntityManager.SpawnEntity(itemType, MapCoordinates.Nullspace);
_inventory.TryEquip(dummy, item, slot.Name, true, true);
}
}
}
/// <summary>
/// Loads the profile onto a dummy entity.
/// </summary>
public EntityUid LoadProfileEntity(HumanoidCharacterProfile? humanoid, JobPrototype? job, bool jobClothes)
{
EntityUid dummyEnt;
if (humanoid is not null)
{
var dummy = _prototypeManager.Index<SpeciesPrototype>(humanoid.Species).DollPrototype;
dummyEnt = EntityManager.SpawnEntity(dummy, MapCoordinates.Nullspace);
}
else
{
dummyEnt = EntityManager.SpawnEntity(_prototypeManager.Index<SpeciesPrototype>(SharedHumanoidAppearanceSystem.DefaultSpecies).DollPrototype, MapCoordinates.Nullspace);
}
_humanoid.LoadProfile(dummyEnt, humanoid);
if (humanoid != null && jobClothes)
{
job ??= GetPreferredJob(humanoid);
GiveDummyJobClothes(dummyEnt, humanoid, job);
if (_prototypeManager.HasIndex<RoleLoadoutPrototype>(LoadoutSystem.GetJobPrototype(job.ID)))
{
var loadout = humanoid.GetLoadoutOrDefault(LoadoutSystem.GetJobPrototype(job.ID), humanoid.Species, EntityManager, _prototypeManager);
GiveDummyLoadout(dummyEnt, loadout);
}
}
return dummyEnt;
}
#endregion
}