fish-station/Content.Client/LateJoin/LateJoinGui.cs
VigersRay c0ee7e3cc9 Merge remote-tracking branch 'refs/remotes/wizards/master'
# Conflicts:
#	.github/PULL_REQUEST_TEMPLATE.md
#	Content.Client/Lobby/LobbyUIController.cs
#	Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs
#	Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs
#	Content.Server/Administration/Systems/BwoinkSystem.cs
#	Content.Server/IoC/ServerContentIoC.cs
#	Content.Server/Players/PlayTimeTracking/PlayTimeTrackingSystem.cs
#	Content.Server/Shuttles/Systems/ArrivalsSystem.cs
#	Content.Shared/Preferences/Loadouts/LoadoutPrototype.cs
#	Content.Shared/Roles/JobRequirements.cs
#	Resources/Prototypes/Accents/word_replacements.yml
#	Resources/Prototypes/Catalog/Fills/Lockers/security.yml
#	Resources/Prototypes/Entities/Mobs/Species/base.yml
#	Resources/Prototypes/Loadouts/Jobs/Cargo/cargo_technician.yml
#	Resources/Prototypes/Loadouts/Jobs/Cargo/quartermaster.yml
#	Resources/Prototypes/Loadouts/Jobs/Civilian/bartender.yml
#	Resources/Prototypes/Loadouts/Jobs/Civilian/botanist.yml
#	Resources/Prototypes/Loadouts/Jobs/Civilian/chaplain.yml
#	Resources/Prototypes/Loadouts/Jobs/Civilian/chef.yml
#	Resources/Prototypes/Loadouts/Jobs/Civilian/janitor.yml
#	Resources/Prototypes/Loadouts/Jobs/Civilian/lawyer.yml
#	Resources/Prototypes/Loadouts/Jobs/Civilian/librarian.yml
#	Resources/Prototypes/Loadouts/Jobs/Civilian/mime.yml
#	Resources/Prototypes/Loadouts/Jobs/Civilian/passenger.yml
#	Resources/Prototypes/Loadouts/Jobs/Command/captain.yml
#	Resources/Prototypes/Loadouts/Jobs/Command/head_of_personnel.yml
#	Resources/Prototypes/Loadouts/Jobs/Engineering/atmospheric_technician.yml
#	Resources/Prototypes/Loadouts/Jobs/Engineering/chief_engineer.yml
#	Resources/Prototypes/Loadouts/Jobs/Engineering/station_engineer.yml
#	Resources/Prototypes/Loadouts/Jobs/Engineering/technical_assistant.yml
#	Resources/Prototypes/Loadouts/Jobs/Medical/chemist.yml
#	Resources/Prototypes/Loadouts/Jobs/Medical/chief_medical_officer.yml
#	Resources/Prototypes/Loadouts/Jobs/Medical/medical_doctor.yml
#	Resources/Prototypes/Loadouts/Jobs/Medical/medical_intern.yml
#	Resources/Prototypes/Loadouts/Jobs/Medical/paramedic.yml
#	Resources/Prototypes/Loadouts/Jobs/Science/research_director.yml
#	Resources/Prototypes/Loadouts/Jobs/Science/scientist.yml
#	Resources/Prototypes/Loadouts/Jobs/Security/detective.yml
#	Resources/Prototypes/Loadouts/Jobs/Security/head_of_security.yml
#	Resources/Prototypes/Loadouts/Jobs/Security/security_cadet.yml
#	Resources/Prototypes/Loadouts/Jobs/Security/security_officer.yml
#	Resources/Prototypes/Loadouts/Jobs/Security/warden.yml
#	Resources/Prototypes/Loadouts/Miscellaneous/trinkets.yml
#	Resources/Prototypes/Recipes/Lathes/medical.yml
#	Resources/Prototypes/Roles/Jobs/Cargo/quartermaster.yml
#	Resources/Prototypes/Roles/Jobs/Command/captain.yml
#	Resources/Prototypes/Roles/Jobs/Command/head_of_personnel.yml
#	Resources/Prototypes/Roles/Jobs/Engineering/chief_engineer.yml
#	Resources/Prototypes/Roles/Jobs/Medical/chief_medical_officer.yml
#	Resources/Prototypes/Roles/Jobs/Science/research_director.yml
#	Resources/Prototypes/Roles/Jobs/Security/head_of_security.yml
#	Resources/Textures/Clothing/Mask/sadmime.rsi/meta.json
#	Resources/Textures/Clothing/Mask/scaredmime.rsi/meta.json
#	Resources/Textures/Clothing/OuterClothing/WinterCoats/coat.rsi/meta.json
#	Resources/Textures/Clothing/OuterClothing/WinterCoats/coatatmos.rsi/meta.json
#	Resources/Textures/Clothing/OuterClothing/WinterCoats/coatcap.rsi/meta.json
#	Resources/Textures/Clothing/OuterClothing/WinterCoats/coatcargo.rsi/meta.json
#	Resources/Textures/Clothing/OuterClothing/WinterCoats/coatce.rsi/meta.json
#	Resources/Textures/Clothing/OuterClothing/WinterCoats/coatcentcom.rsi/meta.json
#	Resources/Textures/Clothing/OuterClothing/WinterCoats/coatchem.rsi/meta.json
#	Resources/Textures/Clothing/OuterClothing/WinterCoats/coatcmo.rsi/meta.json
#	Resources/Textures/Clothing/OuterClothing/WinterCoats/coatengi.rsi/meta.json
#	Resources/Textures/Clothing/OuterClothing/WinterCoats/coatgen.rsi/meta.json
#	Resources/Textures/Clothing/OuterClothing/WinterCoats/coathop.rsi/meta.json
#	Resources/Textures/Clothing/OuterClothing/WinterCoats/coathos.rsi/meta.json
#	Resources/Textures/Clothing/OuterClothing/WinterCoats/coathydro.rsi/meta.json
#	Resources/Textures/Clothing/OuterClothing/WinterCoats/coatjani.rsi/meta.json
#	Resources/Textures/Clothing/OuterClothing/WinterCoats/coatmed.rsi/meta.json
#	Resources/Textures/Clothing/OuterClothing/WinterCoats/coatmime.rsi/meta.json
#	Resources/Textures/Clothing/OuterClothing/WinterCoats/coatminer.rsi/meta.json
#	Resources/Textures/Clothing/OuterClothing/WinterCoats/coatparamed.rsi/meta.json
#	Resources/Textures/Clothing/OuterClothing/WinterCoats/coatqm.rsi/meta.json
#	Resources/Textures/Clothing/OuterClothing/WinterCoats/coatrd.rsi/meta.json
#	Resources/Textures/Clothing/OuterClothing/WinterCoats/coatrobo.rsi/meta.json
#	Resources/Textures/Clothing/OuterClothing/WinterCoats/coatsci.rsi/meta.json
#	Resources/Textures/Clothing/OuterClothing/WinterCoats/coatsec.rsi/meta.json
#	Resources/Textures/Clothing/OuterClothing/WinterCoats/coatviro.rsi/meta.json
#	Resources/Textures/Clothing/OuterClothing/WinterCoats/coatwarden.rsi/meta.json
#	Resources/Textures/Structures/Power/apc.rsi/base.png
#	Resources/Textures/Structures/Power/apc.rsi/broken.png
#	Resources/Textures/Structures/Power/apc.rsi/display-charging.png
#	Resources/Textures/Structures/Power/apc.rsi/display-full.png
#	Resources/Textures/Structures/Power/apc.rsi/display-lack.png
#	Resources/Textures/Structures/Power/apc.rsi/display-remote.png
#	Resources/Textures/Structures/Power/apc.rsi/emag-unlit.png
#	Resources/Textures/Structures/Power/apc.rsi/frame.png
#	Resources/Textures/Structures/Power/apc.rsi/static.png
2024-08-05 17:22:05 +03:00

369 lines
14 KiB
C#

using System.Linq;
using System.Numerics;
using Content.Client.CrewManifest;
using Content.Client.GameTicking.Managers;
using Content.Client.Lobby;
using Content.Client.UserInterface.Controls;
using Content.Client.Players.PlayTimeTracking;
using Content.Shared.CCVar;
using Content.Shared.Preferences;
using Content.Shared.Roles;
using Content.Shared.StatusIcon;
using Robust.Client.Console;
using Robust.Client.GameObjects;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Configuration;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
using static Robust.Client.UserInterface.Controls.BoxContainer;
namespace Content.Client.LateJoin
{
public sealed class LateJoinGui : DefaultWindow
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IClientConsoleHost _consoleHost = default!;
[Dependency] private readonly IConfigurationManager _configManager = default!;
[Dependency] private readonly IEntitySystemManager _entitySystem = default!;
[Dependency] private readonly JobRequirementsManager _jobRequirements = default!;
[Dependency] private readonly IClientPreferencesManager _preferencesManager = default!;
public event Action<(NetEntity, string)> SelectedId;
private readonly ClientGameTicker _gameTicker;
private readonly SpriteSystem _sprites;
private readonly CrewManifestSystem _crewManifest;
private readonly Dictionary<NetEntity, Dictionary<string, List<JobButton>>> _jobButtons = new();
private readonly Dictionary<NetEntity, Dictionary<string, BoxContainer>> _jobCategories = new();
private readonly List<ScrollContainer> _jobLists = new();
private readonly Control _base;
public LateJoinGui()
{
MinSize = SetSize = new Vector2(450, 560);
IoCManager.InjectDependencies(this);
_sprites = _entitySystem.GetEntitySystem<SpriteSystem>();
_crewManifest = _entitySystem.GetEntitySystem<CrewManifestSystem>();
_gameTicker = _entitySystem.GetEntitySystem<ClientGameTicker>();
Title = Loc.GetString("late-join-gui-title");
_base = new BoxContainer()
{
Orientation = LayoutOrientation.Vertical,
VerticalExpand = true,
};
Contents.AddChild(_base);
_jobRequirements.Updated += RebuildUI;
RebuildUI();
SelectedId += x =>
{
var (station, jobId) = x;
Logger.InfoS("latejoin", $"Late joining as ID: {jobId}");
_consoleHost.ExecuteCommand($"joingame {CommandParsing.Escape(jobId)} {station}");
Close();
};
_gameTicker.LobbyJobsAvailableUpdated += JobsAvailableUpdated;
}
private void RebuildUI()
{
_base.RemoveAllChildren();
_jobLists.Clear();
_jobButtons.Clear();
_jobCategories.Clear();
if (!_gameTicker.DisallowedLateJoin && _gameTicker.StationNames.Count == 0)
Logger.Warning("No stations exist, nothing to display in late-join GUI");
foreach (var (id, name) in _gameTicker.StationNames)
{
var jobList = new BoxContainer
{
Orientation = LayoutOrientation.Vertical,
Margin = new Thickness(0, 0, 5f, 0),
};
var collapseButton = new ContainerButton()
{
HorizontalAlignment = HAlignment.Right,
ToggleMode = true,
Children =
{
new TextureRect
{
StyleClasses = { OptionButton.StyleClassOptionTriangle },
Margin = new Thickness(8, 0),
HorizontalAlignment = HAlignment.Center,
VerticalAlignment = VAlignment.Center,
}
}
};
_base.AddChild(new StripeBack()
{
Children =
{
new PanelContainer()
{
Children =
{
new Label()
{
StyleClasses = { "LabelBig" },
Text = name,
Align = Label.AlignMode.Center,
},
collapseButton
}
}
}
});
if (_configManager.GetCVar(CCVars.CrewManifestWithoutEntity))
{
var crewManifestButton = new Button()
{
Text = Loc.GetString("crew-manifest-button-label")
};
crewManifestButton.OnPressed += _ => _crewManifest.RequestCrewManifest(id);
_base.AddChild(crewManifestButton);
}
var jobListScroll = new ScrollContainer()
{
VerticalExpand = true,
Children = { jobList },
Visible = false,
};
if (_jobLists.Count == 0)
jobListScroll.Visible = true;
_jobLists.Add(jobListScroll);
_base.AddChild(jobListScroll);
collapseButton.OnToggled += _ =>
{
foreach (var section in _jobLists)
{
section.Visible = false;
}
jobListScroll.Visible = true;
};
var firstCategory = true;
var departments = _prototypeManager.EnumeratePrototypes<DepartmentPrototype>().ToArray();
Array.Sort(departments, DepartmentUIComparer.Instance);
_jobButtons[id] = new Dictionary<string, List<JobButton>>();
foreach (var department in departments)
{
var departmentName = Loc.GetString($"department-{department.ID}");
_jobCategories[id] = new Dictionary<string, BoxContainer>();
var stationAvailable = _gameTicker.JobsAvailable[id];
var jobsAvailable = new List<JobPrototype>();
foreach (var jobId in department.Roles)
{
if (!stationAvailable.ContainsKey(jobId))
continue;
jobsAvailable.Add(_prototypeManager.Index<JobPrototype>(jobId));
}
jobsAvailable.Sort(JobUIComparer.Instance);
// Do not display departments with no jobs available.
if (jobsAvailable.Count == 0)
continue;
var category = new BoxContainer
{
Orientation = LayoutOrientation.Vertical,
Name = department.ID,
ToolTip = Loc.GetString("late-join-gui-jobs-amount-in-department-tooltip",
("departmentName", departmentName))
};
if (firstCategory)
{
firstCategory = false;
}
else
{
category.AddChild(new Control
{
MinSize = new Vector2(0, 23),
});
}
category.AddChild(new PanelContainer
{
Children =
{
new Label
{
StyleClasses = { "LabelBig" },
Text = Loc.GetString("late-join-gui-department-jobs-label", ("departmentName", departmentName))
}
}
});
_jobCategories[id][department.ID] = category;
jobList.AddChild(category);
foreach (var prototype in jobsAvailable)
{
var value = stationAvailable[prototype.ID];
var jobLabel = new Label
{
Margin = new Thickness(5f, 0, 0, 0)
};
var jobButton = new JobButton(jobLabel, prototype.ID, prototype.LocalizedName, value);
var jobSelector = new BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
HorizontalExpand = true
};
var icon = new TextureRect
{
TextureScale = new Vector2(2, 2),
VerticalAlignment = VAlignment.Center
};
var jobIcon = _prototypeManager.Index(prototype.Icon);
icon.Texture = _sprites.Frame0(jobIcon.Icon);
jobSelector.AddChild(icon);
jobSelector.AddChild(jobLabel);
jobButton.AddChild(jobSelector);
category.AddChild(jobButton);
jobButton.OnPressed += _ => SelectedId.Invoke((id, jobButton.JobId));
if (!_jobRequirements.IsAllowed(prototype, (HumanoidCharacterProfile?)_preferencesManager.Preferences?.SelectedCharacter, out var reason))
{
jobButton.Disabled = true;
if (!reason.IsEmpty)
{
var tooltip = new Tooltip();
tooltip.SetMessage(reason);
jobButton.TooltipSupplier = _ => tooltip;
}
jobSelector.AddChild(new TextureRect
{
TextureScale = new Vector2(0.4f, 0.4f),
Stretch = TextureRect.StretchMode.KeepCentered,
Texture = _sprites.Frame0(new SpriteSpecifier.Texture(new ("/Textures/Interface/Nano/lock.svg.192dpi.png"))),
HorizontalExpand = true,
HorizontalAlignment = HAlignment.Right,
});
}
else if (value == 0)
{
jobButton.Disabled = true;
}
if (!_jobButtons[id].ContainsKey(prototype.ID))
{
_jobButtons[id][prototype.ID] = new List<JobButton>();
}
_jobButtons[id][prototype.ID].Add(jobButton);
}
}
}
}
private void JobsAvailableUpdated(IReadOnlyDictionary<NetEntity, Dictionary<ProtoId<JobPrototype>, int?>> updatedJobs)
{
foreach (var stationEntries in updatedJobs)
{
if (_jobButtons.ContainsKey(stationEntries.Key))
{
var jobsAvailable = stationEntries.Value;
var existingJobEntries = _jobButtons[stationEntries.Key];
foreach (var existingJobEntry in existingJobEntries)
{
if (jobsAvailable.ContainsKey(existingJobEntry.Key))
{
var updatedJobValue = jobsAvailable[existingJobEntry.Key];
foreach (var matchingJobButton in existingJobEntry.Value)
{
if (matchingJobButton.Amount != updatedJobValue)
{
matchingJobButton.RefreshLabel(updatedJobValue);
matchingJobButton.Disabled |= matchingJobButton.Amount == 0;
}
}
}
}
}
}
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
{
_jobRequirements.Updated -= RebuildUI;
_gameTicker.LobbyJobsAvailableUpdated -= JobsAvailableUpdated;
_jobButtons.Clear();
_jobCategories.Clear();
}
}
}
sealed class JobButton : ContainerButton
{
public Label JobLabel { get; }
public string JobId { get; }
public string JobLocalisedName { get; }
public int? Amount { get; private set; }
private bool _initialised = false;
public JobButton(Label jobLabel, ProtoId<JobPrototype> jobId, string jobLocalisedName, int? amount)
{
JobLabel = jobLabel;
JobId = jobId;
JobLocalisedName = jobLocalisedName;
RefreshLabel(amount);
AddStyleClass(StyleClassButton);
_initialised = true;
}
public void RefreshLabel(int? amount)
{
if (Amount == amount && _initialised)
{
return;
}
Amount = amount;
JobLabel.Text = Amount != null ?
Loc.GetString("late-join-gui-job-slot-capped", ("jobName", JobLocalisedName), ("amount", Amount)) :
Loc.GetString("late-join-gui-job-slot-uncapped", ("jobName", JobLocalisedName));
}
}
}