diff --git a/.github/mapchecker/config.py b/.github/mapchecker/config.py index 2ba450aa546..ac9f699e35b 100644 --- a/.github/mapchecker/config.py +++ b/.github/mapchecker/config.py @@ -4,13 +4,8 @@ "DoNotMap", "DEBUG", "Admeme", - "CaptainSabre", - "ClothingBeltSheath", - "MagazinePistolHighCapacity", - "MagazinePistolHighCapacityRubber", "EncryptionKeyCommand", "SurveillanceCameraWireless", - "CrewMonitoringServer", "APCHighCapacity", "APCSuperCapacity", "APCHyperCapacity", @@ -36,30 +31,20 @@ ], "Custom": [ ], - "Security": [ # These matchers are illegal unless the ship is part of the security shipyard. - "Security", # Anything with the word security in it should also only be appearing on security ships. - "Plastitanium", # Plastitanium walls should only be appearing on security ships. - "Kammerer", # Opportunity - "HighSecDoor", - "ShuttleGun", + "Security": [ ], "Syndicate": [ - "Plastitanium", # And also on blackmarket ships cause syndicate. - "ShuttleGun", ], "BlackMarket": [ - "Plastitanium", # And also on blackmarket ships cause syndicate. - "ShuttleGun", ], "Sr": [ ], "Medical": [ ], + "Ussp": [ + ], # It is assumed that mapped instances of plastitanium, security gear, etc. are deemed acceptable "PointOfInterest": [ - "Plastitanium", - "Security", - "HighSecDoor", "WallPlastitaniumIndestructible", "WallPlastitaniumDiagonalIndestructible", "PlastititaniumWindowIndestructible", diff --git a/Content.Client/Lobby/ClientPreferencesManager.cs b/Content.Client/Lobby/ClientPreferencesManager.cs index 3f01e1a8f67..4bd760e7869 100644 --- a/Content.Client/Lobby/ClientPreferencesManager.cs +++ b/Content.Client/Lobby/ClientPreferencesManager.cs @@ -1,9 +1,11 @@ using System.Linq; +using Content.Shared._Mono.Company; using Content.Shared.Preferences; using Robust.Client; using Robust.Client.Player; using Robust.Shared.Network; using Robust.Shared.Utility; +using Robust.Shared.Prototypes; namespace Content.Client.Lobby { @@ -60,6 +62,19 @@ public void SelectCharacter(int slot) public void UpdateCharacter(ICharacterProfile profile, int slot) { var collection = IoCManager.Instance!; + + // Verify company exists if this is a humanoid profile + if (profile is HumanoidCharacterProfile humanoidProfile) + { + var protoManager = IoCManager.Resolve(); + if (!string.IsNullOrEmpty(humanoidProfile.Company) && + humanoidProfile.Company != "None" && + !protoManager.HasIndex(humanoidProfile.Company)) + { + profile = humanoidProfile.WithCompany("None"); + } + } + profile.EnsureValid(_playerManager.LocalSession!, collection); var characters = new Dictionary(Preferences.Characters) {[slot] = profile}; Preferences = new PlayerPreferences(characters, Preferences.SelectedCharacterIndex, Preferences.AdminOOCColor); @@ -111,6 +126,47 @@ private void HandlePreferencesAndSettings(MsgPreferencesAndSettings message) Preferences = message.Preferences; Settings = message.Settings; + // Check if any character profiles have invalid companies and fix them + if (Preferences != null) + { + var protoManager = IoCManager.Resolve(); + var needsUpdate = false; + var characters = new Dictionary(); + + foreach (var (slot, profile) in Preferences.Characters) + { + var updatedProfile = profile; + + if (profile is HumanoidCharacterProfile humanoidProfile && + !string.IsNullOrEmpty(humanoidProfile.Company) && + humanoidProfile.Company != "None" && + !protoManager.HasIndex(humanoidProfile.Company)) + { + updatedProfile = humanoidProfile.WithCompany("None"); + needsUpdate = true; + } + + characters[slot] = updatedProfile; + } + + if (needsUpdate) + { + Preferences = new PlayerPreferences(characters, Preferences.SelectedCharacterIndex, Preferences.AdminOOCColor); + + // Update the selected character on the server if needed + var selectedIndex = Preferences.SelectedCharacterIndex; + if (characters.TryGetValue(selectedIndex, out var selectedProfile)) + { + var msg = new MsgUpdateCharacter + { + Profile = selectedProfile, + Slot = selectedIndex + }; + _netManager.ClientSendMessage(msg); + } + } + } + OnServerDataLoaded?.Invoke(); } } diff --git a/Content.Client/Lobby/LobbyUIController.cs b/Content.Client/Lobby/LobbyUIController.cs index 9159056bfde..fd4d9d2e6b9 100644 --- a/Content.Client/Lobby/LobbyUIController.cs +++ b/Content.Client/Lobby/LobbyUIController.cs @@ -5,6 +5,7 @@ using Content.Client.Lobby.UI; using Content.Client.Players.PlayTimeTracking; using Content.Client.Station; +using Content.Shared._Mono.Company; using Content.Shared.CCVar; using Content.Shared.Clothing; using Content.Shared.GameTicking; @@ -185,13 +186,40 @@ private void RefreshLobbyPreview() PreviewPanel.SetSprite(EntityUid.Invalid); PreviewPanel.SetSummaryText(string.Empty); PreviewPanel.SetBankBalanceText(string.Empty); // Frontier + PreviewPanel.SetCompanyText(string.Empty); // Company Display return; } + // Verify company exists, if not set it to "None" + if (!string.IsNullOrEmpty(humanoid.Company) && + humanoid.Company != "None" && + !_prototypeManager.HasIndex(humanoid.Company)) + { + // Create a new profile with the company set to "None" + humanoid = humanoid.WithCompany("None"); + + // Update the character in preferences + if (_preferencesManager.Preferences != null) + { + _preferencesManager.UpdateCharacter(humanoid, _preferencesManager.Preferences.SelectedCharacterIndex); + } + } + var dummy = LoadProfileEntity(humanoid, null, true); PreviewPanel.SetSprite(dummy); PreviewPanel.SetSummaryText(humanoid.Summary); PreviewPanel.SetBankBalanceText(humanoid.BankBalanceText); // Frontier + + // Company Display + var companyId = humanoid.Company; + if (_prototypeManager.TryIndex(companyId, out var company)) + { + PreviewPanel.SetCompanyText($"Company: [color={company.Color.ToHex()}]{company.Name}[/color]"); + } + else + { + PreviewPanel.SetCompanyText($"Company: [color=yellow]{companyId}[/color]"); + } } private void RefreshProfileEditor() diff --git a/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml b/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml index 7a4439440c9..1b49efb6521 100644 --- a/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml +++ b/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml @@ -144,6 +144,18 @@ + + + + + + + + + diff --git a/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs b/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs index cd82587f28f..bd50e4bef23 100644 --- a/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs +++ b/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs @@ -75,12 +75,11 @@ using Content.Client.Message; using Content.Client.Players.PlayTimeTracking; using Content.Client.Sprite; -using Content.Client.Stylesheets; using Content.Client.UserInterface.Systems.Guidebook; using Content.Client.UserInterface.Controls; +using Content.Shared._Mono.Company; using Content.Shared.CCVar; using Content.Shared.Clothing; -using Content.Shared.GameTicking; using Content.Shared.Guidebook; using Content.Shared.Humanoid; using Content.Shared.Humanoid.Markings; @@ -120,6 +119,7 @@ public sealed partial class HumanoidProfileEditor : BoxContainer private readonly MarkingManager _markingManager; private readonly JobRequirementsManager _requirements; private readonly LobbyUIController _controller; + private readonly EntityWhitelistSystem _whitelist; // Frontier private readonly SpriteSystem _sprite; private FlavorText.FlavorText? _flavorText; @@ -175,58 +175,6 @@ public sealed partial class HumanoidProfileEditor : BoxContainer private ISawmill _sawmill; - /// - /// Safely sets a tab title for the specified child control by resolving its current index in the TabContainer. - /// This avoids hard-coding tab indices that can drift if tabs are added or removed dynamically. - /// - /// The direct child control of the TabContainer representing a tab. - /// The title to set. - private void SetTabTitleForChild(Control? child, string title) - { - if (child == null) - return; - - for (var i = 0; i < TabContainer.ChildCount; i++) - { - if (TabContainer.GetChild(i) == child) - { - TabContainer.SetTabTitle(i, title); - return; - } - } - } - - /// - /// Updates all tab titles based on the current TabContainer children. - /// - private void UpdateTabTitles() - { - // Appearance is the first tab in XAML - var appearanceTab = TabContainer.ChildCount > 0 ? TabContainer.GetChild(0) as Control : null; - SetTabTitleForChild(appearanceTab, Loc.GetString("humanoid-profile-editor-appearance-tab")); - - // Jobs tab: ancestor of JobList - var jobsTab = JobList?.Parent?.Parent as Control; - SetTabTitleForChild(jobsTab, Loc.GetString("humanoid-profile-editor-jobs-tab")); - - // Antags tab: ancestor of AntagList - var antagsTab = AntagList?.Parent?.Parent as Control; - SetTabTitleForChild(antagsTab, Loc.GetString("humanoid-profile-editor-antags-tab")); - - // Traits tab: ancestor of TraitsList - var traitsTab = TraitsList?.Parent?.Parent as Control; - SetTabTitleForChild(traitsTab, Loc.GetString("humanoid-profile-editor-traits-tab")); - - // Markings tab: explicitly named in XAML as MarkingsTab - SetTabTitleForChild(MarkingsTab, Loc.GetString("humanoid-profile-editor-markings-tab")); - - // Flavor text (description) tab is dynamically added as the last child when enabled - if (_flavorText != null) - { - TabContainer.SetTabTitle(TabContainer.ChildCount - 1, Loc.GetString("humanoid-profile-editor-flavortext-tab")); - } - } - public HumanoidProfileEditor( IClientPreferencesManager preferencesManager, IConfigurationManager configurationManager, @@ -251,6 +199,8 @@ public HumanoidProfileEditor( _resManager = resManager; _requirements = requirements; _controller = UserInterfaceManager.GetUIController(); + + _whitelist = _entManager.System(); // Frontier _sprite = _entManager.System(); ImportButton.OnPressed += args => @@ -557,14 +507,66 @@ public HumanoidProfileEditor( #endregion Jobs - // Ensure tab titles match XAML order: 0 Appearance, 1 Jobs, 2 Antags, 3 Traits, 4 Markings - TabContainer.SetTabTitle(2, Loc.GetString("humanoid-profile-editor-antags-tab")); // Frontier: show proper label if present - RefreshTraits(); + #region Company + + TabContainer.SetTabTitle(3, Loc.GetString("humanoid-profile-editor-company-tab")); + + // Clear any existing items + CompanyButton.Clear(); + + var username = _playerManager.LocalPlayer?.Session?.Name; //Lua modified - company login support + + // Add all companies from prototypes - use consistent sorting with UpdateCompanyControls + var companies = _prototypeManager.EnumeratePrototypes() + //.Where(c => !c.Disabled) // Filter out disabled companies + .Where(c => !c.Disabled || (username != null && c.Logins.Contains(username))) //Lua modified - company login support + .ToList(); + companies.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.Ordinal)); + + // Make sure "None" is first in the list + var noneIndex = companies.FindIndex(c => c.ID == "None"); + if (noneIndex != -1) + { + var none = companies[noneIndex]; + companies.RemoveAt(noneIndex); + companies.Insert(0, none); + } + + // Add to TSF company dropdown + for (var i = 0; i < companies.Count; i++) + { + CompanyButton.AddItem(companies[i].Name, i); + Logger.Debug($"Added company to dropdown: {i} - {companies[i].ID} - {companies[i].Name}"); + } + + CompanyButton.OnItemSelected += args => + { + CompanyButton.SelectId(args.Id); + if (args.Id >= 0 && args.Id < companies.Count) + { + string companyId = companies[args.Id].ID; + + // Get the current profile for comparison + var oldCompany = Profile?.Company; + + // Update the profile with the new company + Profile = Profile?.WithCompany(companyId); + + // Debug logging to verify selection + Logger.Debug($"Company changed from {oldCompany} to {companyId}"); + + // Explicitly call SetDirty to update save button state + SetDirty(); + } + }; + + #endregion Company + #region Markings - TabContainer.SetTabTitle(4, Loc.GetString("humanoid-profile-editor-markings-tab")); // Frontier: Markings is the 5th tab in XAML + TabContainer.SetTabTitle(4, Loc.GetString("humanoid-profile-editor-markings-tab")); Markings.OnMarkingAdded += OnMarkingChange; Markings.OnMarkingRemoved += OnMarkingChange; @@ -575,9 +577,6 @@ public HumanoidProfileEditor( RefreshFlavorText(); - // Ensure tab titles are correct after initial setup. - UpdateTabTitles(); - #region Dummy SpriteRotateLeft.OnPressed += _ => @@ -603,6 +602,7 @@ public HumanoidProfileEditor( SpeciesInfoButton.OnPressed += OnSpeciesInfoButtonPressed; UpdateSpeciesGuidebookIcon(); + UpdateCompanyControls(); IsDirty = false; } @@ -647,7 +647,7 @@ public void RefreshTraits() EnforceSpeciesTraitRestrictions(); var traits = _prototypeManager.EnumeratePrototypes().OrderBy(t => Loc.GetString(t.Name)).ToList(); - TabContainer.SetTabTitle(3, Loc.GetString("humanoid-profile-editor-traits-tab")); + TabContainer.SetTabTitle(2, Loc.GetString("humanoid-profile-editor-traits-tab")); if (traits.Count < 1) { @@ -1169,15 +1169,32 @@ public void RefreshAntags() private void SetDirty() { - // If it equals default then reset the button. - if (Profile == null || _preferencesManager.Preferences?.SelectedCharacter.MemberwiseEquals(Profile) == true) + // If profile is null, we can't be dirty + if (Profile == null) { IsDirty = false; return; } - // TODO: Check if profile matches default. - IsDirty = true; + // If we have no selected character to compare against, we're dirty + if (_preferencesManager.Preferences?.SelectedCharacter == null) + { + IsDirty = true; + return; + } + + // Get the selected character for comparison + var selectedCharacter = (HumanoidCharacterProfile)_preferencesManager.Preferences.SelectedCharacter; + + // Check explicitly if company changed + if (selectedCharacter.Company != Profile.Company) + { + IsDirty = true; + return; + } + + // Check if entire profile matches + IsDirty = !selectedCharacter.MemberwiseEquals(Profile); } /// @@ -1245,6 +1262,7 @@ public void SetProfile(HumanoidCharacterProfile? profile, int? slot) UpdateHairPickers(); UpdateCMarkingsHair(); UpdateCMarkingsFacialHair(); + UpdateCompanyControls(); RefreshAntags(); RefreshJobs(); @@ -2131,8 +2149,9 @@ private void UpdateEyePickers() private void UpdateSaveButton() { - SaveButton.Disabled = Profile is null || !IsDirty; - ResetButton.Disabled = Profile is null || !IsDirty; + var canSave = Profile is not null; + SaveButton.Disabled = !canSave || !IsDirty; + ResetButton.Disabled = !canSave || !IsDirty; } private void SetPreviewRotation(Direction direction) @@ -2246,5 +2265,57 @@ private void EndExport() ImportButton.Disabled = false; ExportButton.Disabled = false; } + + private void UpdateCompanyControls() + { + if (Profile is null) + return; + + var username = _playerManager.LocalPlayer?.Session?.Name; //Lua modified - company login support + + var companies = _prototypeManager.EnumeratePrototypes() + //.Where(c => !c.Disabled) // Filter out disabled companies + .Where(c => !c.Disabled || (username != null && c.Logins.Contains(username))) //Lua modified - company login support + .ToList(); + companies.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.Ordinal)); + + // Make sure "None" is first in the list + var noneIndex = companies.FindIndex(c => c.ID == "None"); + if (noneIndex != -1) + { + var none = companies[noneIndex]; + companies.RemoveAt(noneIndex); + companies.Insert(0, none); + } + + Logger.Debug($"Updating company controls. Current profile company: {Profile.Company}"); + + // Find the company in the list and select it + bool found = false; + for (var i = 0; i < companies.Count; i++) + { + if (companies[i].ID != Profile.Company) + continue; // Short circuit. + + Logger.Debug($"Found company at index {i}: {companies[i].ID} - {companies[i].Name}"); + CompanyButton.SelectId(i); + + found = true; + break; + } + + // If company wasn't found, default to "None" (index 0) + if (!found) + { + Logger.Debug($"Company {Profile.Company} not found in list, defaulting to None"); + CompanyButton.SelectId(0); + + // Also reset the profile's company to None if the current one is disabled + if (_prototypeManager.TryIndex(Profile.Company, out var companyProto) && companyProto.Disabled) + { + Profile = Profile.WithCompany("None"); + } + } + } } } diff --git a/Content.Client/Lobby/UI/LobbyCharacterPreviewPanel.xaml b/Content.Client/Lobby/UI/LobbyCharacterPreviewPanel.xaml index c4ef8b45773..96a413401b1 100644 --- a/Content.Client/Lobby/UI/LobbyCharacterPreviewPanel.xaml +++ b/Content.Client/Lobby/UI/LobbyCharacterPreviewPanel.xaml @@ -11,6 +11,8 @@