471 lines
15 KiB
C#
471 lines
15 KiB
C#
using System.Linq;
|
|
using Content.Client.Guidebook;
|
|
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!;
|
|
[UISystemDependency] private readonly GuidebookSystem _guide = default!;
|
|
|
|
private CharacterSetupGui? _characterSetup;
|
|
private HumanoidProfileEditor? _profileEditor;
|
|
private CharacterSetupGuiSavePanel? _savePanel;
|
|
|
|
/// <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 void CloseProfileEditor()
|
|
{
|
|
if (_profileEditor == null)
|
|
return;
|
|
|
|
_profileEditor.SetProfile(null, null);
|
|
_profileEditor.Visible = false;
|
|
|
|
if (_stateManager.CurrentState is LobbyState lobbyGui)
|
|
{
|
|
lobbyGui.SwitchState(LobbyGui.LobbyGuiState.Default);
|
|
}
|
|
}
|
|
|
|
private void OpenSavePanel()
|
|
{
|
|
if (_savePanel is { IsOpen: true })
|
|
return;
|
|
|
|
_savePanel = new CharacterSetupGuiSavePanel();
|
|
|
|
_savePanel.SaveButton.OnPressed += _ =>
|
|
{
|
|
SaveProfile();
|
|
|
|
_savePanel.Close();
|
|
|
|
CloseProfileEditor();
|
|
};
|
|
|
|
_savePanel.NoSaveButton.OnPressed += _ =>
|
|
{
|
|
_savePanel.Close();
|
|
|
|
CloseProfileEditor();
|
|
};
|
|
|
|
_savePanel.OpenCentered();
|
|
}
|
|
|
|
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,
|
|
_resourceCache,
|
|
_requirements,
|
|
_markings);
|
|
|
|
_profileEditor.OnOpenGuidebook += _guide.OpenHelp;
|
|
|
|
_characterSetup = new CharacterSetupGui(EntityManager, _prototypeManager, _resourceCache, _preferencesManager, _profileEditor);
|
|
|
|
_characterSetup.CloseButton.OnPressed += _ =>
|
|
{
|
|
// Open the save panel if we have unsaved changes.
|
|
if (_profileEditor.Profile != null && _profileEditor.IsDirty)
|
|
{
|
|
OpenSavePanel();
|
|
|
|
return;
|
|
}
|
|
|
|
// Reset sliders etc.
|
|
CloseProfileEditor();
|
|
};
|
|
|
|
_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), _playerManager.LocalSession, 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), _playerManager.LocalSession, humanoid.Species, EntityManager, _prototypeManager);
|
|
GiveDummyLoadout(dummyEnt, loadout);
|
|
}
|
|
}
|
|
|
|
return dummyEnt;
|
|
}
|
|
|
|
#endregion
|
|
}
|